New Sign in

fierj

Public

Tiny personal git forge

← fierj / thread_handlers.go
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, r, 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, r, 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, r, 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, r, 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, r, tmpl, http.StatusInternalServerError, err.Error())
			return
		}
	}

	if err := action(rp, threadID); err != nil {
		renderError(w, r, 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)
	}
}