Lindenii Project Forge
Login

server

Lindenii Forge’s main backend daemon
Commit info
ID
dd0017446dcd605b3670784336d38c1083895abe
Author
Runxi Yu <me@runxiyu.org>
Author date
Mon, 18 Aug 2025 05:10:14 +0800
Committer
Runxi Yu <me@runxiyu.org>
Committer date
Mon, 18 Aug 2025 05:10:14 +0800
Actions
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 }} &ndash; {{ .global.forge_title -}}</title>
		<title>{{- range $i, $s := $group_path -}}{{- $s -}}{{- if ne $i (len $group_path) -}}/{{- end -}}{{- end }} &ndash; {{ .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 -}}