Finished
This commit is contained in:
490
frontend/utils/RegistrarDao.java
Normal file
490
frontend/utils/RegistrarDao.java
Normal file
@@ -0,0 +1,490 @@
|
||||
package ins.frontend.utils;
|
||||
|
||||
import java.sql.*;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
* Registrar DAO for schema:
|
||||
* <ul>
|
||||
* <li>tln(id, name, info, owner_id, is_public, allow_subdomains)</li>
|
||||
* <li>infonames(id, info, tln_id, uid)</li>
|
||||
* <li>subnames(id, name, infoname_id)</li>
|
||||
* <li>records(id, infoname_id, subname_id, type, value, ttl, priority, port, weight)</li>
|
||||
* </ul>
|
||||
*
|
||||
* <p>Rules:</p>
|
||||
* <ul>
|
||||
* <li>Create InfoName under TLN allowed if TLN is public OR owned by user.</li>
|
||||
* <li>Subnames allowed for non-owner only if allow_subdomains=1. Owner always allowed.</li>
|
||||
* <li>TLN name is not editable; only info/is_public/allow_subdomains are editable (owner-only).</li>
|
||||
* </ul>
|
||||
*/
|
||||
public final class RegistrarDao {
|
||||
|
||||
private final UserDao.DataSourceProvider dataSource;
|
||||
|
||||
/**
|
||||
* Creates the DAO.
|
||||
*
|
||||
* @param dataSource JDBC connection provider
|
||||
*/
|
||||
public RegistrarDao(UserDao.DataSourceProvider dataSource) {
|
||||
this.dataSource = Objects.requireNonNull(dataSource, "dataSource");
|
||||
}
|
||||
|
||||
// ---------- TLN ----------
|
||||
|
||||
/**
|
||||
* TLN usable for creating InfoNames if public OR owned.
|
||||
*
|
||||
* @param tln TLN row
|
||||
* @param userId current user id
|
||||
* @return allowed
|
||||
*/
|
||||
public static boolean canUseTln(TlnRow tln, int userId) {
|
||||
if (tln == null || userId <= 0) return false;
|
||||
if (tln.isPublic) return true;
|
||||
return tln.ownerId != null && tln.ownerId == userId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Subdomain/subname allowed if:
|
||||
* <ul>
|
||||
* <li>no sub requested (root) OR</li>
|
||||
* <li>TLN allows subdomains OR</li>
|
||||
* <li>user is TLN owner</li>
|
||||
* </ul>
|
||||
*
|
||||
* @param tln TLN row
|
||||
* @param userId current user id
|
||||
* @param sub sub label (nullable)
|
||||
* @return allowed
|
||||
*/
|
||||
public static boolean canUseSubname(TlnRow tln, int userId, String sub) {
|
||||
if (tln == null || userId <= 0) return false;
|
||||
boolean wantsSub = sub != null && !sub.isBlank();
|
||||
if (!wantsSub) return true;
|
||||
if (tln.allowSubdomains) return true;
|
||||
return tln.ownerId != null && tln.ownerId == userId;
|
||||
}
|
||||
|
||||
private static TlnRow mapTln(ResultSet rs) throws SQLException {
|
||||
return new TlnRow(
|
||||
rs.getInt("id"),
|
||||
rs.getString("name"),
|
||||
rs.getString("info"),
|
||||
(Integer) rs.getObject("owner_id"),
|
||||
rs.getInt("is_public") == 1,
|
||||
rs.getInt("allow_subdomains") == 1
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a TLN.
|
||||
*
|
||||
* @param name tln.name (unique)
|
||||
* @param info tln.info (editable, can be null/blank)
|
||||
* @param ownerId owner user id (nullable in DB, pass null to create unowned TLN)
|
||||
* @param isPublic tln.is_public
|
||||
* @param allowSubdomains tln.allow_subdomains
|
||||
* @return new tln.id
|
||||
* @throws SQLException on SQL errors
|
||||
*/
|
||||
public int createTln(String name, String info, Integer ownerId, boolean isPublic, boolean allowSubdomains) throws SQLException {
|
||||
if (name == null || name.isBlank()) throw new IllegalArgumentException("name must not be blank");
|
||||
|
||||
String sql = "INSERT INTO tln (name, info, owner_id, is_public, allow_subdomains) VALUES (?, ?, ?, ?, ?)";
|
||||
|
||||
try (Connection c = dataSource.getConnection();
|
||||
PreparedStatement ps = c.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS)) {
|
||||
|
||||
ps.setString(1, name.trim());
|
||||
ps.setString(2, info);
|
||||
if (ownerId == null) ps.setNull(3, Types.INTEGER);
|
||||
else ps.setInt(3, ownerId);
|
||||
ps.setInt(4, isPublic ? 1 : 0);
|
||||
ps.setInt(5, allowSubdomains ? 1 : 0);
|
||||
|
||||
ps.executeUpdate();
|
||||
|
||||
try (ResultSet rs = ps.getGeneratedKeys()) {
|
||||
if (rs.next()) return rs.getInt(1);
|
||||
}
|
||||
}
|
||||
|
||||
throw new SQLException("No generated key returned for tln.id");
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds TLN by name.
|
||||
*
|
||||
* @param name tln.name
|
||||
* @return optional row
|
||||
* @throws SQLException on SQL errors
|
||||
*/
|
||||
public Optional<TlnRow> findTlnByName(String name) throws SQLException {
|
||||
if (name == null || name.isBlank()) return Optional.empty();
|
||||
|
||||
String sql = "SELECT id, name, info, owner_id, is_public, allow_subdomains FROM tln WHERE name = ? LIMIT 1";
|
||||
|
||||
try (Connection c = dataSource.getConnection();
|
||||
PreparedStatement ps = c.prepareStatement(sql)) {
|
||||
|
||||
ps.setString(1, name.trim());
|
||||
|
||||
try (ResultSet rs = ps.executeQuery()) {
|
||||
if (!rs.next()) return Optional.empty();
|
||||
return Optional.of(mapTln(rs));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ---------- Permissions ----------
|
||||
|
||||
/**
|
||||
* Lists TLNs visible to a user: owned OR public OR unowned+public.
|
||||
*
|
||||
* @param userId current user id
|
||||
* @return rows
|
||||
* @throws SQLException on SQL errors
|
||||
*/
|
||||
public TlnRow[] listVisibleTlns(int userId) throws SQLException {
|
||||
String sql = """
|
||||
SELECT id, name, info, owner_id, is_public, allow_subdomains
|
||||
FROM tln
|
||||
WHERE is_public = 1 OR owner_id = ?
|
||||
ORDER BY name ASC
|
||||
""";
|
||||
|
||||
List<TlnRow> out = new ArrayList<>();
|
||||
|
||||
try (Connection c = dataSource.getConnection();
|
||||
PreparedStatement ps = c.prepareStatement(sql)) {
|
||||
|
||||
ps.setInt(1, userId);
|
||||
|
||||
try (ResultSet rs = ps.executeQuery()) {
|
||||
while (rs.next()) out.add(mapTln(rs));
|
||||
}
|
||||
}
|
||||
|
||||
return out.toArray(new TlnRow[0]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates TLN fields except name (owner-only).
|
||||
*
|
||||
* @param tlnId tln.id
|
||||
* @param ownerUserId users.id (must match tln.owner_id)
|
||||
* @param newInfo new info text (nullable)
|
||||
* @param isPublic is_public
|
||||
* @param allowSubdomains allow_subdomains
|
||||
* @return true if updated, false if not owned/not found
|
||||
* @throws SQLException on SQL errors
|
||||
*/
|
||||
public boolean updateTlnOwned(int tlnId, int ownerUserId, String newInfo, boolean isPublic, boolean allowSubdomains) throws SQLException {
|
||||
if (tlnId <= 0) throw new IllegalArgumentException("tlnId must be > 0");
|
||||
if (ownerUserId <= 0) throw new IllegalArgumentException("ownerUserId must be > 0");
|
||||
|
||||
String sql = "UPDATE tln SET info = ?, is_public = ?, allow_subdomains = ? WHERE id = ? AND owner_id = ?";
|
||||
|
||||
try (Connection c = dataSource.getConnection();
|
||||
PreparedStatement ps = c.prepareStatement(sql)) {
|
||||
|
||||
ps.setString(1, newInfo);
|
||||
ps.setInt(2, isPublic ? 1 : 0);
|
||||
ps.setInt(3, allowSubdomains ? 1 : 0);
|
||||
ps.setInt(4, tlnId);
|
||||
ps.setInt(5, ownerUserId);
|
||||
|
||||
return ps.executeUpdate() > 0;
|
||||
}
|
||||
}
|
||||
|
||||
// ---------- InfoNames ----------
|
||||
|
||||
/**
|
||||
* Deletes TLN (owner-only).
|
||||
*
|
||||
* @param tlnId tln.id
|
||||
* @param ownerUserId users.id
|
||||
* @return true if deleted
|
||||
* @throws SQLException on SQL errors
|
||||
*/
|
||||
public boolean deleteTlnOwned(int tlnId, int ownerUserId) throws SQLException {
|
||||
if (tlnId <= 0) throw new IllegalArgumentException("tlnId must be > 0");
|
||||
if (ownerUserId <= 0) throw new IllegalArgumentException("ownerUserId must be > 0");
|
||||
|
||||
String sql = "DELETE FROM tln WHERE id = ? AND owner_id = ?";
|
||||
|
||||
try (Connection c = dataSource.getConnection();
|
||||
PreparedStatement ps = c.prepareStatement(sql)) {
|
||||
|
||||
ps.setInt(1, tlnId);
|
||||
ps.setInt(2, ownerUserId);
|
||||
|
||||
return ps.executeUpdate() > 0;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an InfoName under the given TLN.
|
||||
*
|
||||
* @param tln TLN row
|
||||
* @param info infonames.info
|
||||
* @param userId infonames.uid (users.id)
|
||||
* @return new infonames.id
|
||||
* @throws SQLException on SQL errors
|
||||
*/
|
||||
public int createInfoName(TlnRow tln, String info, int userId) throws SQLException {
|
||||
if (tln == null) throw new IllegalArgumentException("tln must not be null");
|
||||
if (userId <= 0) throw new IllegalArgumentException("userId must be > 0");
|
||||
if (!canUseTln(tln, userId)) throw new SQLException("TLN not public and not owned by user.");
|
||||
if (info == null || info.isBlank()) throw new IllegalArgumentException("info must not be blank");
|
||||
|
||||
String sql = "INSERT INTO infonames (info, tln_id, uid) VALUES (?, ?, ?)";
|
||||
|
||||
try (Connection c = dataSource.getConnection();
|
||||
PreparedStatement ps = c.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS)) {
|
||||
|
||||
ps.setString(1, info.trim());
|
||||
ps.setInt(2, tln.id);
|
||||
ps.setInt(3, userId);
|
||||
|
||||
ps.executeUpdate();
|
||||
|
||||
try (ResultSet rs = ps.getGeneratedKeys()) {
|
||||
if (rs.next()) return rs.getInt(1);
|
||||
}
|
||||
}
|
||||
|
||||
throw new SQLException("No generated key returned for infonames.id");
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether a user owns the infoname.
|
||||
*
|
||||
* @param infonameId infonames.id
|
||||
* @param userId users.id
|
||||
* @return true if owned
|
||||
* @throws SQLException on SQL errors
|
||||
*/
|
||||
public boolean isOwnerOfInfoName(int infonameId, int userId) throws SQLException {
|
||||
if (infonameId <= 0 || userId <= 0) return false;
|
||||
|
||||
String sql = "SELECT 1 FROM infonames WHERE id = ? AND uid = ? LIMIT 1";
|
||||
|
||||
try (Connection c = dataSource.getConnection();
|
||||
PreparedStatement ps = c.prepareStatement(sql)) {
|
||||
|
||||
ps.setInt(1, infonameId);
|
||||
ps.setInt(2, userId);
|
||||
|
||||
try (ResultSet rs = ps.executeQuery()) {
|
||||
return rs.next();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes an InfoName (owner-only).
|
||||
*
|
||||
* @param infonameId infonames.id
|
||||
* @param ownerUserId users.id
|
||||
* @return true if deleted
|
||||
* @throws SQLException on SQL errors
|
||||
*/
|
||||
public boolean deleteInfoNameOwned(int infonameId, int ownerUserId) throws SQLException {
|
||||
if (infonameId <= 0) throw new IllegalArgumentException("infonameId must be > 0");
|
||||
if (ownerUserId <= 0) throw new IllegalArgumentException("ownerUserId must be > 0");
|
||||
|
||||
String sql = "DELETE FROM infonames WHERE id = ? AND uid = ?";
|
||||
|
||||
try (Connection c = dataSource.getConnection();
|
||||
PreparedStatement ps = c.prepareStatement(sql)) {
|
||||
|
||||
ps.setInt(1, infonameId);
|
||||
ps.setInt(2, ownerUserId);
|
||||
|
||||
return ps.executeUpdate() > 0;
|
||||
}
|
||||
}
|
||||
|
||||
// ---------- Subnames + Records ----------
|
||||
|
||||
/**
|
||||
* Lists InfoNames owned by user (joined with TLN data).
|
||||
*
|
||||
* @param userId users.id
|
||||
* @return rows
|
||||
* @throws SQLException on SQL errors
|
||||
*/
|
||||
public InfoNameRow[] listOwnedInfoNames(int userId) throws SQLException {
|
||||
if (userId <= 0) return new InfoNameRow[0];
|
||||
|
||||
String sql = """
|
||||
SELECT i.id AS iid, i.info AS info, i.tln_id AS tln_id,
|
||||
t.name AS tln_name, t.info AS tln_info, t.owner_id AS owner_id,
|
||||
t.is_public AS is_public, t.allow_subdomains AS allow_subdomains
|
||||
FROM infonames i
|
||||
INNER JOIN tln t ON t.id = i.tln_id
|
||||
WHERE i.uid = ?
|
||||
ORDER BY t.name ASC, i.info ASC
|
||||
""";
|
||||
|
||||
List<InfoNameRow> out = new ArrayList<>();
|
||||
|
||||
try (Connection c = dataSource.getConnection();
|
||||
PreparedStatement ps = c.prepareStatement(sql)) {
|
||||
|
||||
ps.setInt(1, userId);
|
||||
|
||||
try (ResultSet rs = ps.executeQuery()) {
|
||||
while (rs.next()) {
|
||||
TlnRow tln = new TlnRow(
|
||||
rs.getInt("tln_id"),
|
||||
rs.getString("tln_name"),
|
||||
rs.getString("tln_info"),
|
||||
(Integer) rs.getObject("owner_id"),
|
||||
rs.getInt("is_public") == 1,
|
||||
rs.getInt("allow_subdomains") == 1
|
||||
);
|
||||
out.add(new InfoNameRow(
|
||||
rs.getInt("iid"),
|
||||
rs.getString("info"),
|
||||
tln
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return out.toArray(new InfoNameRow[0]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensures a subname exists for an infoname; returns subnames.id.
|
||||
*
|
||||
* <p>If sub is null/blank, returns null (root).</p>
|
||||
*
|
||||
* @param infonameId infonames.id
|
||||
* @param sub sub label (nullable)
|
||||
* @return subnames.id or null for root
|
||||
* @throws SQLException on SQL errors
|
||||
*/
|
||||
public Integer ensureSubname(int infonameId, String sub) throws SQLException {
|
||||
if (sub == null || sub.isBlank()) return null;
|
||||
if (infonameId <= 0) throw new IllegalArgumentException("infonameId must be > 0");
|
||||
|
||||
String name = sub.trim();
|
||||
|
||||
// 1) find
|
||||
String find = "SELECT id FROM subnames WHERE infoname_id = ? AND name = ? LIMIT 1";
|
||||
try (Connection c = dataSource.getConnection();
|
||||
PreparedStatement ps = c.prepareStatement(find)) {
|
||||
|
||||
ps.setInt(1, infonameId);
|
||||
ps.setString(2, name);
|
||||
|
||||
try (ResultSet rs = ps.executeQuery()) {
|
||||
if (rs.next()) return rs.getInt("id");
|
||||
}
|
||||
}
|
||||
|
||||
// 2) insert
|
||||
String ins = "INSERT INTO subnames (name, infoname_id) VALUES (?, ?)";
|
||||
try (Connection c = dataSource.getConnection();
|
||||
PreparedStatement ps = c.prepareStatement(ins, Statement.RETURN_GENERATED_KEYS)) {
|
||||
|
||||
ps.setString(1, name);
|
||||
ps.setInt(2, infonameId);
|
||||
ps.executeUpdate();
|
||||
|
||||
try (ResultSet rs = ps.getGeneratedKeys()) {
|
||||
if (rs.next()) return rs.getInt(1);
|
||||
}
|
||||
}
|
||||
|
||||
throw new SQLException("No generated key returned for subnames.id");
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a DNS-like record.
|
||||
*
|
||||
* @param infonameId infonames.id
|
||||
* @param subnameId subnames.id or null for root
|
||||
* @param type enum type (A/AAAA/TXT/CNAME/MX/SRV/NS)
|
||||
* @param value record value
|
||||
* @param ttl ttl (default 3600)
|
||||
* @param priority priority (MX/SRV)
|
||||
* @param port port (SRV)
|
||||
* @param weight weight (SRV)
|
||||
* @return new records.id
|
||||
* @throws SQLException on SQL errors
|
||||
*/
|
||||
public int addRecord(int infonameId,
|
||||
Integer subnameId,
|
||||
String type,
|
||||
String value,
|
||||
int ttl,
|
||||
Integer priority,
|
||||
Integer port,
|
||||
Integer weight) throws SQLException {
|
||||
|
||||
if (infonameId <= 0) throw new IllegalArgumentException("infonameId must be > 0");
|
||||
if (type == null || type.isBlank()) throw new IllegalArgumentException("type must not be blank");
|
||||
if (value == null || value.isBlank()) throw new IllegalArgumentException("value must not be blank");
|
||||
if (ttl <= 0) ttl = 3600;
|
||||
|
||||
String t = type.trim().toUpperCase();
|
||||
|
||||
String sql = """
|
||||
INSERT INTO records (infoname_id, subname_id, type, value, ttl, priority, port, weight)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?)
|
||||
""";
|
||||
|
||||
try (Connection c = dataSource.getConnection();
|
||||
PreparedStatement ps = c.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS)) {
|
||||
|
||||
ps.setInt(1, infonameId);
|
||||
|
||||
if (subnameId == null) ps.setNull(2, Types.INTEGER);
|
||||
else ps.setInt(2, subnameId);
|
||||
|
||||
ps.setString(3, t);
|
||||
ps.setString(4, value.trim());
|
||||
ps.setInt(5, ttl);
|
||||
|
||||
if (priority == null) ps.setNull(6, Types.INTEGER);
|
||||
else ps.setInt(6, priority);
|
||||
|
||||
if (port == null) ps.setNull(7, Types.INTEGER);
|
||||
else ps.setInt(7, port);
|
||||
|
||||
if (weight == null) ps.setNull(8, Types.INTEGER);
|
||||
else ps.setInt(8, weight);
|
||||
|
||||
ps.executeUpdate();
|
||||
|
||||
try (ResultSet rs = ps.getGeneratedKeys()) {
|
||||
if (rs.next()) return rs.getInt(1);
|
||||
}
|
||||
}
|
||||
|
||||
throw new SQLException("No generated key returned for records.id");
|
||||
}
|
||||
|
||||
/**
|
||||
* TLN row.
|
||||
*/
|
||||
public record TlnRow(int id, String name, String info, Integer ownerId, boolean isPublic, boolean allowSubdomains) {
|
||||
}
|
||||
|
||||
/**
|
||||
* InfoName row (includes TLN).
|
||||
*/
|
||||
public record InfoNameRow(int id, String info, TlnRow tln) {
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user