/*
* Copyright (C) 2024 Jonni Liljamo <jonni@liljamo.com>
*
* This file is licensed under AGPL-3.0-or-later, see NOTICE and LICENSE for
* more information.
*/
package db
import (
"errors"
"git.src.quest/~liljamo/felu/internal/util"
"github.com/oklog/ulid/v2"
)
// DomainOwner contains a domains owners information.
type DomainOwner struct {
ID string
Email string
}
// Domain contains a domains information.
type Domain struct {
ID string
APIKey string
Domain string
A string
AcmeChallenge string
TsigKey string
Owner DomainOwner
}
// FetchDomainsForUser fetches all domains for a specific user.
func FetchDomainsForUser(userID string) ([]Domain, error) {
rows, err := DBConn.Query(`SELECT id, apikey, ddns_domain, a_record, tsigkey
FROM domains WHERE owner = $1`, userID)
if err != nil {
return nil, err
}
defer rows.Close()
var domains []Domain
for rows.Next() {
var domain Domain
err = rows.Scan(&domain.ID, &domain.APIKey, &domain.Domain, &domain.A, &domain.TsigKey)
if err != nil {
return nil, err
}
domains = append(domains, domain)
}
err = rows.Err()
if err != nil {
return nil, err
}
return domains, nil
}
// FetchAllDomains fetches all domains.
func FetchAllDomains() ([]Domain, error) {
rows, err := DBConn.Query(`SELECT id, ddns_domain, a_record, owner
FROM domains`)
if err != nil {
return nil, err
}
defer rows.Close()
var domains []Domain
for rows.Next() {
var domain Domain
err = rows.Scan(&domain.ID, &domain.Domain, &domain.A, &domain.Owner.ID)
if err != nil {
return nil, err
}
ownerEmail, err := FetchUserEmail(domain.Owner.ID)
if err != nil {
return nil, err
}
domain.Owner.Email = ownerEmail
domains = append(domains, domain)
}
err = rows.Err()
if err != nil {
return nil, err
}
return domains, nil
}
// CreateDomain creates a domains.
func CreateDomain(domain string, aRecord string, owner string) error {
ulid := ulid.Make().String()
apikey := util.GenAPIKey()
_, err := DBConn.Exec(`INSERT INTO domains(id, apikey, ddns_domain, a_record, owner)
VALUES ($1, $2, $3, $4, $5)`, ulid, apikey, domain, aRecord, owner)
if err != nil {
return err
}
return nil
}
// DeleteDomain deletes a domain.
func DeleteDomain(id string, userID string) error {
_, err := DBConn.Exec(`DELETE FROM domains WHERE id = $1 AND owner = $2`, id, userID)
if err != nil {
return err
}
return nil
}
// DeleteDomainsForUser deletes all domains for a user.
func DeleteDomainsForUser(userID string) error {
_, err := DBConn.Exec(`DELETE FROM domains WHERE owner = $1`, userID)
if err != nil {
return err
}
return nil
}
// FetchDomainARecord fetches the A record of a domain.
func FetchDomainARecord(ddnsDomain string) (string, error) {
var aRecord string
err := DBConn.QueryRow(`SELECT a_record FROM domains WHERE ddns_domain = $1`,
ddnsDomain).Scan(&aRecord)
if err != nil {
return "", err
}
return aRecord, nil
}
// UpdateDomainARecord updates the A record of a domain.
func UpdateDomainARecord(ddnsDomain string, providedAPIKey string, aRecord string) error {
var domainAPIKey string
err := DBConn.QueryRow(`SELECT apikey FROM domains WHERE ddns_domain = $1`,
ddnsDomain).Scan(&domainAPIKey)
if err != nil {
return err
}
if domainAPIKey != providedAPIKey {
return errors.New("API key doesn't match")
}
_, err = DBConn.Exec(`UPDATE domains SET a_record = $1 WHERE ddns_domain = $2`,
aRecord, ddnsDomain)
if err != nil {
return err
}
return nil
}
// UpdateDomainARecordManual updates the A record of a domain.
func UpdateDomainARecordManual(id string, userID string, aRecord string) error {
_, err := DBConn.Exec(`UPDATE domains SET a_record = $1 WHERE id = $2 AND owner = $3`,
aRecord, id, userID)
if err != nil {
return err
}
return nil
}
// RefreshDomainAPIKey refreshes the API key of a domain.
func RefreshDomainAPIKey(id string, userID string) error {
apiKey := util.GenAPIKey()
_, err := DBConn.Exec(`UPDATE domains SET apikey = $1 WHERE id = $2 AND owner = $3`, apiKey, id, userID)
if err != nil {
return err
}
return nil
}
// FetchDomainAcmeChallenge fetches the ACME challenge string of a domain.
func FetchDomainAcmeChallenge(ddnsDomain string) (string, error) {
var acmeChallengeString string
err := DBConn.QueryRow(`SELECT acme_challenge FROM domains WHERE ddns_domain = $1`,
ddnsDomain).Scan(&acmeChallengeString)
if err != nil {
return "", err
}
return acmeChallengeString, nil
}
// UpdateDomainAcmeChallenge updates the ACME challenge string of a domain.
func UpdateDomainAcmeChallenge(ddnsDomain string, acmeChallengeString string) error {
_, err := DBConn.Exec(`UPDATE domains SET acme_challenge = $1 WHERE ddns_domain = $2`,
acmeChallengeString, ddnsDomain)
if err != nil {
return err
}
return nil
}
// DeleteDomainAcmeChallenge deletes the ACME challenge string of a domain.
func DeleteDomainAcmeChallenge(ddnsDomain string) error {
_, err := DBConn.Exec(`UPDATE domains SET acme_challenge = NULL WHERE ddns_domain = $1`,
ddnsDomain)
if err != nil {
return err
}
return nil
}
// FetchDomainTsigKey fetches the tsig key of a domain.
func FetchDomainTsigKey(ddnsDomain string) (string, error) {
var tsig string
err := DBConn.QueryRow(`SELECT tsigkey FROM domains WHERE ddns_domain = $1`,
ddnsDomain).Scan(&tsig)
if err != nil {
return "", err
}
return tsig, nil
}
// RefreshDomainTsigKey refreshes the Tsig key of a domain.
func RefreshDomainTsigKey(id string, userID string) error {
tsigKey := util.GenTsigKey()
_, err := DBConn.Exec(`UPDATE domains SET tsigkey = $1 WHERE id = $2 AND owner = $3`, tsigKey, id, userID)
if err != nil {
return err
}
return nil
}