Lindenii Project Forge
Start doing the group index page...
package handlers import (
"log"
"net/http"
"strings"
"go.lindenii.runxiyu.org/forge/forged/internal/database/queries"
"go.lindenii.runxiyu.org/forge/forged/internal/incoming/web/templates" wtypes "go.lindenii.runxiyu.org/forge/forged/internal/incoming/web/types" ) type GroupHTTP struct { r templates.Renderer } func NewGroupHTTP(r templates.Renderer) *GroupHTTP { return &GroupHTTP{ r: r, } } func (h *GroupHTTP) Index(w http.ResponseWriter, r *http.Request, _ wtypes.Vars) { base := wtypes.Base(r)
_ = h.r.Render(w, "group/index.html", struct { GroupPath string
p, err := base.Queries.GetGroupIDDescByPath(r.Context(), base.URLSegments) if err != nil { log.Println("failed to get group ID by path", "error", err) http.Error(w, "Internal Server Error", http.StatusInternalServerError) return } subgroups, err := base.Queries.GetSubgroups(r.Context(), &p.ID) if err != nil { log.Println("failed to get subgroups", "error", err) http.Error(w, "Internal Server Error", http.StatusInternalServerError) // TODO } repos, err := base.Queries.GetReposInGroup(r.Context(), p.ID) if err != nil { log.Println("failed to get repos in group", "error", err) http.Error(w, "Internal Server Error", http.StatusInternalServerError) // TODO } err = h.r.Render(w, "group", struct { BaseData *wtypes.BaseData Subgroups []queries.GetSubgroupsRow Repos []queries.GetReposInGroupRow Description string
}{
GroupPath: "/" + strings.Join(base.GroupPath, "/") + "/",
BaseData: base, Subgroups: subgroups, Repos: repos, Description: p.Description,
})
if err != nil { log.Println("failed to render index page", "error", err) }
}
-- name: GetRootGroups :many SELECT name, COALESCE(description, '') FROM groups WHERE parent_group IS NULL; -- name: GetGroupIDDescByPath :one WITH RECURSIVE group_path_cte AS ( SELECT id, parent_group, name, 1 AS depth FROM groups WHERE name = ($1::text[])[1] AND parent_group IS NULL UNION ALL SELECT g.id, g.parent_group, g.name, group_path_cte.depth + 1 FROM groups g JOIN group_path_cte ON g.parent_group = group_path_cte.id WHERE g.name = ($1::text[])[group_path_cte.depth + 1] AND group_path_cte.depth + 1 <= cardinality($1::text[]) ) SELECT c.id, COALESCE(g.description, '') FROM group_path_cte c JOIN groups g ON g.id = c.id WHERE c.depth = cardinality($1::text[]);
-- name: GetReposInGroup :many SELECT name, COALESCE(description, '') FROM repos WHERE group_id = $1; -- name: GetSubgroups :many SELECT name, COALESCE(description, '') FROM groups WHERE parent_group = $1;
{{/* SPDX-License-Identifier: AGPL-3.0-only SPDX-FileCopyrightText: Copyright (c) 2025 Runxi Yu <https://runxiyu.org> */}} {{- define "group_view" -}}
{{- if .subgroups -}}
{{- if .Subgroups -}}
<table class="wide"> <thead> <tr> <th colspan="2" class="title-row">Subgroups</th> </tr> <tr> <th scope="col">Name</th> <th scope="col">Description</th> </tr> </thead> <tbody>
{{- range .subgroups -}}
{{- range .Subgroups -}}
<tr> <td> <a href="{{- .Name | path_escape -}}/">{{- .Name -}}</a> </td> <td> {{- .Description -}} </td> </tr> {{- end -}} </tbody> </table> {{- end -}}
{{- if .repos -}}
{{- if .Repos -}}
<table class="wide"> <thead> <tr> <th colspan="2" class="title-row">Repos</th> <tr> <th scope="col">Name</th> <th scope="col">Description</th> </tr> </tr> </thead> <tbody>
{{- range .repos -}}
{{- range .Repos -}}
<tr> <td> <a href="-/repos/{{- .Name | path_escape -}}/">{{- .Name -}}</a> </td> <td> {{- .Description -}} </td> </tr> {{- end -}} </tbody> </table> {{- end -}} {{- end -}}
{{/* SPDX-License-Identifier: AGPL-3.0-only SPDX-FileCopyrightText: Copyright (c) 2025 Runxi Yu <https://runxiyu.org> */}} {{- define "group" -}}
{{- $group_path := .group_path -}}
{{- $group_path := .BaseData.GroupPath -}}
<!DOCTYPE html> <html lang="en"> <head> {{- template "head_common" . -}}
<title>{{- range $i, $s := .group_path -}}{{- $s -}}{{- if ne $i (len $group_path) -}}/{{- end -}}{{- end }} – {{ .global.forge_title -}}</title>
<title>{{- range $i, $s := $group_path -}}{{- $s -}}{{- if ne $i (len $group_path) -}}/{{- end -}}{{- end }} – {{ .BaseData.Global.ForgeTitle -}}</title>
</head> <body class="group"> {{- template "header" . -}} <main> <div class="padding-wrapper">
{{- if .description -}} <p>{{- .description -}}</p>
{{- if .Description -}} <p>{{- .Description -}}</p>
{{- end -}} {{- template "group_view" . -}} </div> {{- if .direct_access -}} <div class="padding-wrapper"> <form method="POST" enctype="application/x-www-form-urlencoded"> <table> <thead> <tr> <th class="title-row" colspan="2"> Create repo </th> </tr> </thead> <tbody> <tr> <th scope="row">Name</th> <td class="tdinput"> <input id="repo-name-input" name="repo_name" type="text" /> </td> </tr> <tr> <th scope="row">Description</th> <td class="tdinput"> <input id="repo-desc-input" name="repo_desc" type="text" /> </td> </tr> <tr> <th scope="row">Contrib</th> <td class="tdinput"> <select id="repo-contrib-input" name="repo_contrib"> <option value="public">Public</option> <option value="ssh_pubkey">SSH public key</option> <option value="federated">Federated service</option> <option value="registered_user">Registered user</option> <option value="closed">Closed</option> </select> </td> </tr> </tbody> <tfoot> <tr> <td class="th-like" colspan="2"> <div class="flex-justify"> <div class="left"> </div> <div class="right"> <input class="btn-primary" type="submit" value="Create" /> </div> </div> </td> </tr> </tfoot> </table> </form> </div> {{- end -}} </main> <footer> {{- template "footer" . -}} </footer> </body> </html> {{- end -}}