package models import ( "context" "database/sql" "errors" "fmt" "git.eeqj.de/sneak/upaas/internal/database" ) // PortProtocol represents the protocol for a port mapping. type PortProtocol string // Port protocol constants. const ( PortProtocolTCP PortProtocol = "tcp" PortProtocolUDP PortProtocol = "udp" ) // Port represents a port mapping for an app container. type Port struct { db *database.Database ID int64 AppID string HostPort int ContainerPort int Protocol PortProtocol } // NewPort creates a new Port with a database reference. func NewPort(db *database.Database) *Port { return &Port{db: db, Protocol: PortProtocolTCP} } // Save inserts or updates the port in the database. func (p *Port) Save(ctx context.Context) error { if p.ID == 0 { return p.insert(ctx) } return p.update(ctx) } // Delete removes the port from the database. func (p *Port) Delete(ctx context.Context) error { _, err := p.db.Exec(ctx, "DELETE FROM app_ports WHERE id = ?", p.ID) return err } func (p *Port) insert(ctx context.Context) error { query := ` INSERT INTO app_ports (app_id, host_port, container_port, protocol) VALUES (?, ?, ?, ?)` result, err := p.db.Exec(ctx, query, p.AppID, p.HostPort, p.ContainerPort, p.Protocol, ) if err != nil { return err } id, err := result.LastInsertId() if err != nil { return err } p.ID = id return nil } func (p *Port) update(ctx context.Context) error { query := ` UPDATE app_ports SET host_port = ?, container_port = ?, protocol = ? WHERE id = ?` _, err := p.db.Exec(ctx, query, p.HostPort, p.ContainerPort, p.Protocol, p.ID) return err } // FindPort finds a port by ID. // //nolint:nilnil // returning nil,nil is idiomatic for "not found" in Active Record func FindPort( ctx context.Context, db *database.Database, id int64, ) (*Port, error) { port := NewPort(db) query := ` SELECT id, app_id, host_port, container_port, protocol FROM app_ports WHERE id = ?` row := db.QueryRow(ctx, query, id) err := row.Scan( &port.ID, &port.AppID, &port.HostPort, &port.ContainerPort, &port.Protocol, ) if err != nil { if errors.Is(err, sql.ErrNoRows) { return nil, nil } return nil, fmt.Errorf("scanning port: %w", err) } return port, nil } // FindPortsByAppID finds all ports for an app. func FindPortsByAppID( ctx context.Context, db *database.Database, appID string, ) ([]*Port, error) { query := ` SELECT id, app_id, host_port, container_port, protocol FROM app_ports WHERE app_id = ? ORDER BY host_port` rows, err := db.Query(ctx, query, appID) if err != nil { return nil, fmt.Errorf("querying ports by app: %w", err) } defer func() { _ = rows.Close() }() var ports []*Port for rows.Next() { port := NewPort(db) scanErr := rows.Scan( &port.ID, &port.AppID, &port.HostPort, &port.ContainerPort, &port.Protocol, ) if scanErr != nil { return nil, scanErr } ports = append(ports, port) } return ports, rows.Err() } // DeletePortsByAppID deletes all ports for an app. func DeletePortsByAppID( ctx context.Context, db *database.Database, appID string, ) error { _, err := db.Exec(ctx, "DELETE FROM app_ports WHERE app_id = ?", appID) return err }