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: * * *

Rules:

* */ 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: * * * @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 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 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 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. * *

If sub is null/blank, returns null (root).

* * @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) { } }