DEVELOPMENT ENVIRONMENT

~liljamo/felu

675ea74b4c91f0f8bdae520875e451deee9547bc — Jonni Liljamo 1 year, 1 month ago 8527401
feat: domains admin view
M internal/components/adminpartials.templ => internal/components/adminpartials.templ +51 -0
@@ 41,3 41,54 @@ templ AdminPartialUsersList(users []db.User) {
		</tbody>
	</table>
}

templ AdminPartialDomainsList(domains []db.Domain) {
	<table class="table-auto">
		<thead>
			<tr>
				<th class="text-start p-2">
					Id
				</th>
				<th class="text-start p-2">
					Domain
				</th>
				<th class="text-start p-2">
					A Record
				</th>
				<th class="text-start p-2">
					Owner (ID/Email)
				</th>
			</tr>
		</thead>
		<tbody>
			for _, domain := range domains {
				<tr class="border">
					<td>
						<div class="p-2">
							<input class="border" value={ domain.Id } disabled/>
						</div>
					</td>
					<td>
						<div class="p-2">
							{ domain.Domain }
						</div>
					</td>
					<td>
						<div class="p-2">
							{ domain.A }
						</div>
					</td>
					<td>
						<div class="p-2">
							<div class="flex flex-row-reverse gap-1">
								<input class="peer" type="checkbox"/>
								<input class="border hidden peer-checked:block" value={ domain.Owner.Email } disabled/>
								<input class="border block peer-checked:hidden" value={ domain.Owner.ID } disabled/>
							</div>
						</div>
					</td>
				</tr>
			}
		</tbody>
	</table>
}

M internal/components/adminpartials_templ.go => internal/components/adminpartials_templ.go +112 -0
@@ 99,3 99,115 @@ func AdminPartialUsersList(users []db.User) templ.Component {
		return err
	})
}

func AdminPartialDomainsList(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_8 := templ.GetChildren(ctx)
		if var_8 == nil {
			var_8 = templ.NopComponent
		}
		ctx = templ.ClearChildren(ctx)
		_, err = templBuffer.WriteString("<table class=\"table-auto\"><thead><tr><th class=\"text-start p-2\">")
		if err != nil {
			return err
		}
		var_9 := `Id`
		_, err = templBuffer.WriteString(var_9)
		if err != nil {
			return err
		}
		_, err = templBuffer.WriteString("</th><th class=\"text-start p-2\">")
		if err != nil {
			return err
		}
		var_10 := `Domain`
		_, err = templBuffer.WriteString(var_10)
		if err != nil {
			return err
		}
		_, err = templBuffer.WriteString("</th><th class=\"text-start p-2\">")
		if err != nil {
			return err
		}
		var_11 := `A Record`
		_, err = templBuffer.WriteString(var_11)
		if err != nil {
			return err
		}
		_, err = templBuffer.WriteString("</th><th class=\"text-start p-2\">")
		if err != nil {
			return err
		}
		var_12 := `Owner (ID/Email)`
		_, err = templBuffer.WriteString(var_12)
		if err != nil {
			return err
		}
		_, err = templBuffer.WriteString("</th></tr></thead><tbody>")
		if err != nil {
			return err
		}
		for _, domain := range domains {
			_, err = templBuffer.WriteString("<tr class=\"border\"><td><div class=\"p-2\"><input class=\"border\" value=\"")
			if err != nil {
				return err
			}
			_, err = templBuffer.WriteString(templ.EscapeString(domain.Id))
			if err != nil {
				return err
			}
			_, err = templBuffer.WriteString("\" disabled></div></td><td><div class=\"p-2\">")
			if err != nil {
				return err
			}
			var var_13 string = domain.Domain
			_, err = templBuffer.WriteString(templ.EscapeString(var_13))
			if err != nil {
				return err
			}
			_, err = templBuffer.WriteString("</div></td><td><div class=\"p-2\">")
			if err != nil {
				return err
			}
			var var_14 string = domain.A
			_, err = templBuffer.WriteString(templ.EscapeString(var_14))
			if err != nil {
				return err
			}
			_, err = templBuffer.WriteString("</div></td><td><div class=\"p-2\"><div class=\"flex flex-row-reverse gap-1\"><input class=\"peer\" type=\"checkbox\"><input class=\"border hidden peer-checked:block\" value=\"")
			if err != nil {
				return err
			}
			_, err = templBuffer.WriteString(templ.EscapeString(domain.Owner.Email))
			if err != nil {
				return err
			}
			_, err = templBuffer.WriteString("\" disabled><input class=\"border block peer-checked:hidden\" value=\"")
			if err != nil {
				return err
			}
			_, err = templBuffer.WriteString(templ.EscapeString(domain.Owner.ID))
			if err != nil {
				return err
			}
			_, err = templBuffer.WriteString("\" disabled></div></div></td></tr>")
			if err != nil {
				return err
			}
		}
		_, err = templBuffer.WriteString("</tbody></table>")
		if err != nil {
			return err
		}
		if !templIsBuffer {
			_, err = templBuffer.WriteTo(w)
		}
		return err
	})
}

M internal/components/manageadmin.templ => internal/components/manageadmin.templ +1 -2
@@ 33,8 33,7 @@ templ ManageAdminUsers() {

templ ManageAdminDomains() {
	@ManageBase("ManageAdmin") {
		<div>
			list of all domains on the instance, with owner details and such
		<div hx-get="/manage/admin/partials/domains_list" hx-trigger="load" hx-target="this">
		</div>
	}
}

M internal/components/manageadmin_templ.go => internal/components/manageadmin_templ.go +1 -10
@@ 169,16 169,7 @@ func ManageAdminDomains() templ.Component {
				templBuffer = templ.GetBuffer()
				defer templ.ReleaseBuffer(templBuffer)
			}
			_, err = templBuffer.WriteString("<div>")
			if err != nil {
				return err
			}
			var_14 := `list of all domains on the instance, with owner details and such`
			_, err = templBuffer.WriteString(var_14)
			if err != nil {
				return err
			}
			_, err = templBuffer.WriteString("</div>")
			_, err = templBuffer.WriteString("<div hx-get=\"/manage/admin/partials/domains_list\" hx-trigger=\"load\" hx-target=\"this\"></div>")
			if err != nil {
				return err
			}

M internal/db/domains.go => internal/db/domains.go +39 -0
@@ 13,11 13,18 @@ import (
	"github.com/oklog/ulid/v2"
)

type DomainOwner struct {
	ID    string
	Email string
}

type Domain struct {
	Id      string
	ApiKey  string
	Domain  string
	A       string

	Owner DomainOwner
}

func FetchDomainsForUser(userId string) ([]Domain, error) {


@@ 45,6 52,38 @@ func FetchDomainsForUser(userId string) ([]Domain, error) {
	return domains, nil
}

func FetchAllDomains() ([]Domain, error) {
	rows, err := DBConn.Query(`SELECT id, apikey, 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.ApiKey, &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
}

func CreateDomain(domain string, aRecord string, owner string) error {
	ulid := ulid.Make().String()
	apikey := util.GenApiKey()

M internal/db/users.go => internal/db/users.go +11 -0
@@ 117,6 117,17 @@ func FetchAllUsers() ([]User, error) {
	return users, nil
}

func FetchUserEmail(id string) (string, error) {
	var email string
	err := DBConn.QueryRow(`SELECT email FROM users WHERE id = $1`,
		id).Scan(&email)
	if err != nil {
		return "", err
	}

	return email, nil
}

func DeleteUser(id string) error {
	err := DeleteDomainsForUser(id)
	if err != nil {

M internal/handlers/adminpartials.go => internal/handlers/adminpartials.go +13 -0
@@ 26,3 26,16 @@ func AdminPartialUsersList() gin.HandlerFunc {
		}
	}
}

func AdminPartialDomainsList() gin.HandlerFunc {
	return func(c *gin.Context) {
		users, err := db.FetchAllDomains()
		if err != nil {
			// TODO: Handle this better
			c.String(http.StatusInternalServerError, "Something went wrong while fetching domains")
			c.Abort()
		} else {
			c.HTML(http.StatusOK, "", components.AdminPartialDomainsList(users))
		}
	}
}

M internal/routers/frontend.go => internal/routers/frontend.go +1 -0
@@ 59,6 59,7 @@ func SetupFrontendRouter(sm *scs.SessionManager) *gin.Engine {
		manageAdmin.POST("/users", handlers.PostUser())

		manageAdmin.GET("/partials/users_list", handlers.AdminPartialUsersList())
		manageAdmin.GET("/partials/domains_list", handlers.AdminPartialDomainsList())
	}

	return r

M static/styles.css => static/styles.css +35 -7
@@ 534,6 534,10 @@ video {
  --tw-backdrop-sepia:  ;
}

.block {
  display: block;
}

.flex {
  display: flex;
}


@@ 542,6 546,10 @@ video {
  display: table;
}

.hidden {
  display: none;
}

.w-full {
  width: 100%;
}


@@ 554,6 562,10 @@ video {
  table-layout: auto;
}

.flex-row-reverse {
  flex-direction: row-reverse;
}

.flex-col {
  flex-direction: column;
}


@@ 570,10 582,19 @@ video {
  gap: 1rem;
}

.gap-1 {
  gap: 0.25rem;
}

.border {
  border-width: 1px;
}

.bg-emerald-200 {
  --tw-bg-opacity: 1;
  background-color: rgb(167 243 208 / var(--tw-bg-opacity));
}

.bg-lime-200 {
  --tw-bg-opacity: 1;
  background-color: rgb(217 249 157 / var(--tw-bg-opacity));


@@ 589,19 610,14 @@ video {
  background-color: rgb(153 246 228 / var(--tw-bg-opacity));
}

.bg-yellow-200 {
  --tw-bg-opacity: 1;
  background-color: rgb(254 240 138 / var(--tw-bg-opacity));
}

.bg-violet-200 {
  --tw-bg-opacity: 1;
  background-color: rgb(221 214 254 / var(--tw-bg-opacity));
}

.bg-emerald-200 {
.bg-yellow-200 {
  --tw-bg-opacity: 1;
  background-color: rgb(167 243 208 / var(--tw-bg-opacity));
  background-color: rgb(254 240 138 / var(--tw-bg-opacity));
}

.p-1 {


@@ 637,3 653,15 @@ video {
  --tw-text-opacity: 1;
  color: rgb(225 29 72 / var(--tw-text-opacity));
}

.peer:checked ~ .peer-checked\:block {
  display: block;
}

.peer:checked ~ .peer-checked\:hidden {
  display: none;
}

.peer:disabled ~ .peer-disabled\:block {
  display: block;
}