fierj
PublicTiny personal git forge
package main
import (
"fmt"
"html/template"
"net/http"
"path/filepath"
"strings"
)
func repoPath(cfg Config, repoName string) string {
return filepath.Join(cfg.Dir, repoName+".git")
}
func ThreadsList(cfg Config, tmpl *template.Template) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
git := &Git{Dir: cfg.Dir, Name: r.PathValue("repo")}
if !checkRepoAccess(w, r, git) {
return
}
state := r.URL.Query().Get("state")
if state == "" {
state = "open"
}
ref := git.DefaultBranch()
rp := repoPath(cfg, git.Name)
threads, openCount, closedCount := listThreadsFiltered(rp, state)
tmpl.ExecuteTemplate(w, "thread_list.html", withUser(r, map[string]any{
"Repo": git.Repo(),
"IsPrivate": git.IsPrivate(),
"Ref": ref,
"Threads": threads,
"State": state,
"OpenCount": openCount,
"ClosedCount": closedCount,
"Description": git.Description(),
"Branches": git.Branches(),
"Tags": git.Tags(),
"Commits": git.CommitCount(ref),
"ActiveTab": "threads",
}))
}
}
func ThreadNewGet(cfg Config, tmpl *template.Template) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
git := &Git{Dir: cfg.Dir, Name: r.PathValue("repo")}
if !checkRepoAccess(w, r, git) {
return
}
ref := git.DefaultBranch()
tmpl.ExecuteTemplate(w, "thread_new.html", withUser(r, map[string]any{
"Repo": git.Repo(),
"IsPrivate": git.IsPrivate(),
"Ref": ref,
"Description": git.Description(),
"Branches": git.Branches(),
"Tags": git.Tags(),
"Commits": git.CommitCount(ref),
"ActiveTab": "threads",
}))
}
}
func ThreadNewPost(cfg Config, tmpl *template.Template) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
git := &Git{Dir: cfg.Dir, Name: r.PathValue("repo")}
if !checkRepoAccess(w, r, git) {
return
}
title := strings.TrimSpace(r.FormValue("title"))
body := strings.TrimSpace(r.FormValue("body"))
if title == "" {
renderError(w, tmpl, http.StatusBadRequest, "Title is required")
return
}
author, authorName := threadAuthor(r)
rp := repoPath(cfg, git.Name)
t, err := createThread(rp, title, body, author, authorName)
if err != nil {
renderError(w, tmpl, http.StatusInternalServerError, err.Error())
return
}
// Deliver to ActivityPub followers (non-blocking).
go deliverThreadCreate(cfg, git.Name, t)
http.Redirect(w, r, fmt.Sprintf("/%s/threads/%s", git.Name, t.ID), http.StatusSeeOther)
}
}
func ThreadView(cfg Config, tmpl *template.Template) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
git := &Git{Dir: cfg.Dir, Name: r.PathValue("repo")}
if !checkRepoAccess(w, r, git) {
return
}
threadID := r.PathValue("threadId")
rp := repoPath(cfg, git.Name)
t, err := loadThread(rp, threadID)
if err != nil {
renderError(w, tmpl, http.StatusNotFound, "Thread not found")
return
}
ref := git.DefaultBranch()
tmpl.ExecuteTemplate(w, "thread_view.html", withUser(r, map[string]any{
"Repo": git.Repo(),
"IsPrivate": git.IsPrivate(),
"Ref": ref,
"Thread": t,
"Description": git.Description(),
"Branches": git.Branches(),
"Tags": git.Tags(),
"Commits": git.CommitCount(ref),
"ActiveTab": "threads",
}))
}
}
func ThreadReplyPost(cfg Config, tmpl *template.Template) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
git := &Git{Dir: cfg.Dir, Name: r.PathValue("repo")}
if !checkRepoAccess(w, r, git) {
return
}
threadID := r.PathValue("threadId")
body := strings.TrimSpace(r.FormValue("body"))
if body == "" {
http.Redirect(w, r, fmt.Sprintf("/%s/threads/%s", git.Name, threadID), http.StatusSeeOther)
return
}
author, authorName := threadAuthor(r)
rp := repoPath(cfg, git.Name)
if err := addReply(rp, threadID, body, author, authorName); err != nil {
renderError(w, tmpl, http.StatusInternalServerError, err.Error())
return
}
http.Redirect(w, r, fmt.Sprintf("/%s/threads/%s", git.Name, threadID), http.StatusSeeOther)
}
}
func threadAuthor(r *http.Request) (author, displayName string) {
author = User(r)
if author == "" {
author = "anonymous"
}
return author, author
}
func closeOrReopenWithReply(cfg Config, repoName string, threadID string, r *http.Request, tmpl *template.Template, w http.ResponseWriter, action func(string, string) error) {
body := strings.TrimSpace(r.FormValue("body"))
rp := repoPath(cfg, repoName)
// If the user wrote a comment, save it before changing state.
if body != "" {
author, authorName := threadAuthor(r)
if err := addReply(rp, threadID, body, author, authorName); err != nil {
renderError(w, tmpl, http.StatusInternalServerError, err.Error())
return
}
}
if err := action(rp, threadID); err != nil {
renderError(w, tmpl, http.StatusInternalServerError, err.Error())
return
}
http.Redirect(w, r, fmt.Sprintf("/%s/threads/%s", repoName, threadID), http.StatusSeeOther)
}
func ThreadClosePost(cfg Config, tmpl *template.Template) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
git := &Git{Dir: cfg.Dir, Name: r.PathValue("repo")}
if !checkRepoAccess(w, r, git) {
return
}
closeOrReopenWithReply(cfg, git.Name, r.PathValue("threadId"), r, tmpl, w, closeThread)
}
}
func ThreadReopenPost(cfg Config, tmpl *template.Template) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
git := &Git{Dir: cfg.Dir, Name: r.PathValue("repo")}
if !checkRepoAccess(w, r, git) {
return
}
closeOrReopenWithReply(cfg, git.Name, r.PathValue("threadId"), r, tmpl, w, reopenThread)
}
}