From c097efe694ca9af4e6652ee9c4e4e8c9609f15e2 Mon Sep 17 00:00:00 2001 From: Jonni Liljamo Date: Mon, 16 Oct 2023 18:12:24 +0300 Subject: [PATCH] feat: adding and deleting of domains --- api/key.go | 20 +++ components/manage.templ | 24 +++- components/manage_templ.go | 50 ++++++-- components/managepartials.templ | 74 +++++++++++ components/managepartials_templ.go | 191 +++++++++++++++++++++++++++++ db/domains.go | 64 ++++++++++ felu.go | 6 + handlers/managedomains.go | 76 ++++++++++++ handlers/managepartials.go | 35 ++++++ static/styles.css | 9 ++ 10 files changed, 533 insertions(+), 16 deletions(-) create mode 100644 api/key.go create mode 100644 components/managepartials.templ create mode 100644 components/managepartials_templ.go create mode 100644 db/domains.go create mode 100644 handlers/managedomains.go create mode 100644 handlers/managepartials.go diff --git a/api/key.go b/api/key.go new file mode 100644 index 0000000..1e068be --- /dev/null +++ b/api/key.go @@ -0,0 +1,20 @@ +/* + * Copyright (C) 2023 Jonni Liljamo + * + * This file is licensed under AGPL-3.0-or-later, see NOTICE and LICENSE for + * more information. + */ +package api + +import "math/rand" + +const chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789" + +func GenKey() string { + // NOTE: "Good enough" + b := make([]byte, 48) + for i := range b { + b[i] = chars[rand.Int63() % int64(len(chars))] + } + return string(b) +} diff --git a/components/manage.templ b/components/manage.templ index 7e26b94..4c9a20e 100644 --- a/components/manage.templ +++ b/components/manage.templ @@ -1,10 +1,28 @@ package components +import "git.src.quest/~skye/felu-ddns/config" + +func getDomainPattern() string { + return config.FeluConfig.DNSPattern +} + templ Manage() { @ManageBase("Manage") { -
- something something manaag -
have the domains of a user listed here, with also the edit stuff and all that +
+
+ +
+ + { getDomainPattern() } +
+ + +
+ +
+
+ +
} } diff --git a/components/manage_templ.go b/components/manage_templ.go index 9066715..f96da4c 100644 --- a/components/manage_templ.go +++ b/components/manage_templ.go @@ -9,6 +9,12 @@ import "context" import "io" import "bytes" +import "git.src.quest/~skye/felu-ddns/config" + +func getDomainPattern() string { + return config.FeluConfig.DNSPattern +} + func Manage() templ.Component { return templ.ComponentFunc(func(ctx context.Context, w io.Writer) (err error) { templBuffer, templIsBuffer := w.(*bytes.Buffer) @@ -28,25 +34,43 @@ func Manage() templ.Component { templBuffer = templ.GetBuffer() defer templ.ReleaseBuffer(templBuffer) } - _, err = templBuffer.WriteString("
") + _, err = templBuffer.WriteString("
") if err != nil { return err } - var_4 := `have the domains of a user listed here, with also the edit stuff and all that` - _, err = templBuffer.WriteString(var_4) + var var_4 string = getDomainPattern() + _, err = templBuffer.WriteString(templ.EscapeString(var_4)) if err != nil { return err } - _, err = templBuffer.WriteString("
") + _, err = templBuffer.WriteString("
") if err != nil { return err } @@ -74,12 +98,12 @@ func ManageSettings() templ.Component { defer templ.ReleaseBuffer(templBuffer) } ctx = templ.InitializeContext(ctx) - var_5 := templ.GetChildren(ctx) - if var_5 == nil { - var_5 = templ.NopComponent + var_7 := templ.GetChildren(ctx) + if var_7 == nil { + var_7 = templ.NopComponent } ctx = templ.ClearChildren(ctx) - var_6 := templ.ComponentFunc(func(ctx context.Context, w io.Writer) (err error) { + var_8 := templ.ComponentFunc(func(ctx context.Context, w io.Writer) (err error) { templBuffer, templIsBuffer := w.(*bytes.Buffer) if !templIsBuffer { templBuffer = templ.GetBuffer() @@ -89,8 +113,8 @@ func ManageSettings() templ.Component { if err != nil { return err } - var_7 := `user settings here, like updating email and password` - _, err = templBuffer.WriteString(var_7) + var_9 := `user settings here, like updating email and password` + _, err = templBuffer.WriteString(var_9) if err != nil { return err } @@ -103,7 +127,7 @@ func ManageSettings() templ.Component { } return err }) - err = ManageBase("Settings").Render(templ.WithChildren(ctx, var_6), templBuffer) + err = ManageBase("Settings").Render(templ.WithChildren(ctx, var_8), templBuffer) if err != nil { return err } diff --git a/components/managepartials.templ b/components/managepartials.templ new file mode 100644 index 0000000..3123ed4 --- /dev/null +++ b/components/managepartials.templ @@ -0,0 +1,74 @@ +package components + +import "git.src.quest/~skye/felu-ddns/db" +import "fmt" + +templ ManagePartialDomains(domains []db.Domain) { + if len(domains) > 0 { + + + + + + + + + + + for _, domain := range domains { + + + + + + + } + +
+ Domain + + A Record + + Api Key +
+
+ { domain.Domain } + { getDomainPattern() } +
+
+
+ + +
+
+
+ + + + + show +
+
+
+ +
+
+ } +} diff --git a/components/managepartials_templ.go b/components/managepartials_templ.go new file mode 100644 index 0000000..91f3ef2 --- /dev/null +++ b/components/managepartials_templ.go @@ -0,0 +1,191 @@ +// Code generated by templ@(devel) DO NOT EDIT. + +package components + +//lint:file-ignore SA4006 This context is only used if a nested component is present. + +import "github.com/a-h/templ" +import "context" +import "io" +import "bytes" + +import "git.src.quest/~skye/felu-ddns/db" +import "fmt" + +func ManagePartialDomains(domains []db.Domain) templ.Component { + return templ.ComponentFunc(func(ctx context.Context, w io.Writer) (err error) { + templBuffer, templIsBuffer := w.(*bytes.Buffer) + if !templIsBuffer { + templBuffer = templ.GetBuffer() + defer templ.ReleaseBuffer(templBuffer) + } + ctx = templ.InitializeContext(ctx) + var_1 := templ.GetChildren(ctx) + if var_1 == nil { + var_1 = templ.NopComponent + } + ctx = templ.ClearChildren(ctx) + if len(domains) > 0 { + _, err = templBuffer.WriteString(" ") + if err != nil { + return err + } + for _, domain := range domains { + _, err = templBuffer.WriteString("") + if err != nil { + return err + } + } + _, err = templBuffer.WriteString("
") + if err != nil { + return err + } + var_3 := `Domain` + _, err = templBuffer.WriteString(var_3) + if err != nil { + return err + } + _, err = templBuffer.WriteString("") + if err != nil { + return err + } + var_4 := `A Record` + _, err = templBuffer.WriteString(var_4) + if err != nil { + return err + } + _, err = templBuffer.WriteString("") + if err != nil { + return err + } + var_5 := `Api Key` + _, err = templBuffer.WriteString(var_5) + if err != nil { + return err + } + _, err = templBuffer.WriteString("
") + if err != nil { + return err + } + var var_6 string = domain.Domain + _, err = templBuffer.WriteString(templ.EscapeString(var_6)) + if err != nil { + return err + } + _, err = templBuffer.WriteString("") + if err != nil { + return err + } + var var_7 string = getDomainPattern() + _, err = templBuffer.WriteString(templ.EscapeString(var_7)) + if err != nil { + return err + } + _, err = templBuffer.WriteString("
") + if err != nil { + return err + } + var_11 := `show` + _, err = templBuffer.WriteString(var_11) + if err != nil { + return err + } + _, err = templBuffer.WriteString("
") + if err != nil { + return err + } + } + if !templIsBuffer { + _, err = templBuffer.WriteTo(w) + } + return err + }) +} diff --git a/db/domains.go b/db/domains.go new file mode 100644 index 0000000..fc49c8c --- /dev/null +++ b/db/domains.go @@ -0,0 +1,64 @@ +/* + * Copyright (C) 2023 Jonni Liljamo + * + * This file is licensed under AGPL-3.0-or-later, see NOTICE and LICENSE for + * more information. + */ +package db + +import ( + "git.src.quest/~skye/felu-ddns/api" + "github.com/oklog/ulid/v2" +) + +type Domain struct { + Id string + ApiKey string + Domain string + A string +} + +func FetchDomainsForUser(userId string) ([]Domain, error) { + rows, err := DBConn.Query(`SELECT id, apikey, ddns_domain, a_record + 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) + if err != nil { + return nil, err + } + domains = append(domains, domain) + } + err = rows.Err() + if err != nil { + return nil, err + } + + return domains, nil +} + +func CreateDomain(domain string, aRecord string, owner string) error { + ulid := ulid.Make().String() + apikey := api.GenKey() + _, 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 +} + +func DeleteDomain(id string) error { + _, err := DBConn.Exec(`DELETE FROM domains WHERE id = $1`, id) + if err != nil { + return err + } + return nil +} diff --git a/felu.go b/felu.go index 309b50f..e9d457e 100644 --- a/felu.go +++ b/felu.go @@ -85,6 +85,12 @@ func setupFrontendRouter() *gin.Engine { manage.GET("/settings", func(c *gin.Context) { c.HTML(http.StatusOK, "", components.ManageSettings()) }) + + manage.POST("/domains", handlers.PostDomain()) + manage.PATCH("/domains/:id") // TODO: + manage.DELETE("/domains/:id", handlers.DeleteDomain()) + + manage.GET("/partials/domains", handlers.ManagePartialDomains()) } manageAdmin := r.Group("/manage/admin").Use( middlewares.SessionExists(sessionManager), diff --git a/handlers/managedomains.go b/handlers/managedomains.go new file mode 100644 index 0000000..ef100be --- /dev/null +++ b/handlers/managedomains.go @@ -0,0 +1,76 @@ +/* + * Copyright (C) 2023 Jonni Liljamo + * + * This file is licensed under AGPL-3.0-or-later, see NOTICE and LICENSE for + * more information. + */ +package handlers + +import ( + "log" + "net" + "net/http" + + "git.src.quest/~skye/felu-ddns/db" + "github.com/gin-gonic/gin" +) + +type postDomainData struct { + Domain string `form:"domain"` + ARecord string `form:"a_record"` +} + +func PostDomain() gin.HandlerFunc { + return func(c *gin.Context) { + data := &postDomainData{} + if err := c.Bind(data); err != nil { + log.Printf("[felu] ERROR: Could not bind domain data: %v", err) + c.String(http.StatusBadRequest, "Could not bind domain data") + return + } + + if data.Domain == "" { + c.String(http.StatusBadRequest, "Domain can't be empty") + c.Abort() + return + } + if net.ParseIP(data.ARecord).To4() == nil { + c.String(http.StatusBadRequest, "The A record is invalid") + c.Abort() + return + } + + userId, exists := c.Get("user_id") + if !exists { + c.String(http.StatusInternalServerError, "This should not be possible, but don't quote me on that") + c.Abort() + return + } + + err := db.CreateDomain(data.Domain, data.ARecord, userId.(string)) + if err != nil { + // FIXME: Handle better + c.String(http.StatusInternalServerError, "Something went wrong while creating a new domain") + c.Abort() + return + } + + c.Header("HX-Trigger", "update-domain-list") + } +} + +func DeleteDomain() gin.HandlerFunc { + return func(c *gin.Context) { + id := c.Param("id") + + err := db.DeleteDomain(id) + if err != nil { + // FIXME: Handle better + c.String(http.StatusInternalServerError, "Something went wrong while creating deleting the domain") + c.Abort() + return + } + + c.Header("HX-Trigger", "update-domain-list") + } +} diff --git a/handlers/managepartials.go b/handlers/managepartials.go new file mode 100644 index 0000000..dd69d25 --- /dev/null +++ b/handlers/managepartials.go @@ -0,0 +1,35 @@ +/* + * Copyright (C) 2023 Jonni Liljamo + * + * This file is licensed under AGPL-3.0-or-later, see NOTICE and LICENSE for + * more information. + */ +package handlers + +import ( + "net/http" + + "git.src.quest/~skye/felu-ddns/components" + "git.src.quest/~skye/felu-ddns/db" + "github.com/gin-gonic/gin" +) + +func ManagePartialDomains() gin.HandlerFunc { + return func(c *gin.Context) { + user_id, exists := c.Get("user_id") + if !exists { + c.String(http.StatusInternalServerError, "This should not be possible, but don't quote me on that") + c.Abort() + return + } + + domains, err := db.FetchDomainsForUser(user_id.(string)) + if err != nil { + c.String(http.StatusInternalServerError, "Failed to fetch domains for user") + c.Abort() + return + } + + c.HTML(http.StatusOK, "", components.ManagePartialDomains(domains)) + } +} diff --git a/static/styles.css b/static/styles.css index bdc192b..2c669b0 100644 --- a/static/styles.css +++ b/static/styles.css @@ -584,6 +584,11 @@ video { background-color: rgb(254 205 211 / var(--tw-bg-opacity)); } +.bg-teal-200 { + --tw-bg-opacity: 1; + background-color: rgb(153 246 228 / var(--tw-bg-opacity)); +} + .p-1 { padding: 0.25rem; } @@ -609,6 +614,10 @@ video { line-height: 2.5rem; } +.font-bold { + font-weight: 700; +} + .text-rose-600 { --tw-text-opacity: 1; color: rgb(225 29 72 / var(--tw-text-opacity)); -- 2.44.1