diff options
Diffstat (limited to 'forged/internal/incoming')
19 files changed, 249 insertions, 120 deletions
diff --git a/forged/internal/incoming/hooks/config.go b/forged/internal/incoming/hooks/config.go deleted file mode 100644 index 0d23dc0..0000000 --- a/forged/internal/incoming/hooks/config.go +++ /dev/null @@ -1,6 +0,0 @@ -package hooks - -type Config struct { - Socket string `scfg:"socket"` - Execs string `scfg:"execs"` -} diff --git a/forged/internal/incoming/hooks/hooks.go b/forged/internal/incoming/hooks/hooks.go index dfdf172..effd104 100644 --- a/forged/internal/incoming/hooks/hooks.go +++ b/forged/internal/incoming/hooks/hooks.go @@ -32,10 +32,11 @@ type hookInfo struct { contribReq string } -func New(config Config, global *global.Global) (server *Server) { +func New(global *global.Global) (server *Server) { + cfg := global.Config.Hooks return &Server{ - socketPath: config.Socket, - executablesPath: config.Execs, + socketPath: cfg.Socket, + executablesPath: cfg.Execs, hookMap: cmap.Map[string, hookInfo]{}, global: global, } diff --git a/forged/internal/incoming/lmtp/config.go b/forged/internal/incoming/lmtp/config.go deleted file mode 100644 index 6241608..0000000 --- a/forged/internal/incoming/lmtp/config.go +++ /dev/null @@ -1,9 +0,0 @@ -package lmtp - -type Config struct { - Socket string `scfg:"socket"` - Domain string `scfg:"domain"` - MaxSize int64 `scfg:"max_size"` - WriteTimeout uint32 `scfg:"write_timeout"` - ReadTimeout uint32 `scfg:"read_timeout"` -} diff --git a/forged/internal/incoming/lmtp/lmtp.go b/forged/internal/incoming/lmtp/lmtp.go index a7782a2..c8918f8 100644 --- a/forged/internal/incoming/lmtp/lmtp.go +++ b/forged/internal/incoming/lmtp/lmtp.go @@ -20,13 +20,14 @@ type Server struct { global *global.Global } -func New(config Config, global *global.Global) (server *Server) { +func New(global *global.Global) (server *Server) { + cfg := global.Config.LMTP return &Server{ - socket: config.Socket, - domain: config.Domain, - maxSize: config.MaxSize, - writeTimeout: config.WriteTimeout, - readTimeout: config.ReadTimeout, + socket: cfg.Socket, + domain: cfg.Domain, + maxSize: cfg.MaxSize, + writeTimeout: cfg.WriteTimeout, + readTimeout: cfg.ReadTimeout, global: global, } } diff --git a/forged/internal/incoming/ssh/config.go b/forged/internal/incoming/ssh/config.go deleted file mode 100644 index 7d22cc1..0000000 --- a/forged/internal/incoming/ssh/config.go +++ /dev/null @@ -1,9 +0,0 @@ -package ssh - -type Config struct { - Net string `scfg:"net"` - Addr string `scfg:"addr"` - Key string `scfg:"key"` - Root string `scfg:"root"` - ShutdownTimeout uint32 `scfg:"shutdown_timeout"` -} diff --git a/forged/internal/incoming/ssh/ssh.go b/forged/internal/incoming/ssh/ssh.go index 527cd28..1f27be2 100644 --- a/forged/internal/incoming/ssh/ssh.go +++ b/forged/internal/incoming/ssh/ssh.go @@ -23,18 +23,19 @@ type Server struct { global *global.Global } -func New(config Config, global *global.Global) (server *Server, err error) { +func New(global *global.Global) (server *Server, err error) { + cfg := global.Config.SSH server = &Server{ - net: config.Net, - addr: config.Addr, - root: config.Root, - shutdownTimeout: config.ShutdownTimeout, + net: cfg.Net, + addr: cfg.Addr, + root: cfg.Root, + shutdownTimeout: cfg.ShutdownTimeout, global: global, } //exhaustruct:ignore var privkeyBytes []byte - privkeyBytes, err = os.ReadFile(config.Key) + privkeyBytes, err = os.ReadFile(cfg.Key) if err != nil { return server, fmt.Errorf("read SSH private key: %w", err) } diff --git a/forged/internal/incoming/web/authn.go b/forged/internal/incoming/web/authn.go index 46263ee..9754eb1 100644 --- a/forged/internal/incoming/web/authn.go +++ b/forged/internal/incoming/web/authn.go @@ -21,7 +21,7 @@ func userResolver(r *http.Request) (string, string, error) { tokenHash := sha256.Sum256([]byte(cookie.Value)) - session, err := types.Base(r).Queries.GetUserFromSession(r.Context(), tokenHash[:]) + session, err := types.Base(r).Global.Queries.GetUserFromSession(r.Context(), tokenHash[:]) if err != nil { if errors.Is(err, pgx.ErrNoRows) { return "", "", nil diff --git a/forged/internal/incoming/web/config.go b/forged/internal/incoming/web/config.go deleted file mode 100644 index 8d32b34..0000000 --- a/forged/internal/incoming/web/config.go +++ /dev/null @@ -1,16 +0,0 @@ -package web - -type Config struct { - Net string `scfg:"net"` - Addr string `scfg:"addr"` - Root string `scfg:"root"` - CookieExpiry int `scfg:"cookie_expiry"` - ReadTimeout uint32 `scfg:"read_timeout"` - WriteTimeout uint32 `scfg:"write_timeout"` - IdleTimeout uint32 `scfg:"idle_timeout"` - MaxHeaderBytes int `scfg:"max_header_bytes"` - ReverseProxy bool `scfg:"reverse_proxy"` - ShutdownTimeout uint32 `scfg:"shutdown_timeout"` - TemplatesPath string `scfg:"templates_path"` - StaticPath string `scfg:"static_path"` -} diff --git a/forged/internal/incoming/web/handler.go b/forged/internal/incoming/web/handler.go index 20f7e79..e0e6ced 100644 --- a/forged/internal/incoming/web/handler.go +++ b/forged/internal/incoming/web/handler.go @@ -5,7 +5,6 @@ import ( "net/http" "go.lindenii.runxiyu.org/forge/forged/internal/common/misc" - "go.lindenii.runxiyu.org/forge/forged/internal/database/queries" "go.lindenii.runxiyu.org/forge/forged/internal/global" handlers "go.lindenii.runxiyu.org/forge/forged/internal/incoming/web/handlers" repoHandlers "go.lindenii.runxiyu.org/forge/forged/internal/incoming/web/handlers/repo" @@ -17,8 +16,9 @@ type handler struct { r *Router } -func NewHandler(cfg Config, global *global.Global, queries *queries.Queries) *handler { - h := &handler{r: NewRouter().ReverseProxy(cfg.ReverseProxy).Global(global).Queries(queries).UserResolver(userResolver)} +func NewHandler(global *global.Global) *handler { + cfg := global.Config.Web + h := &handler{r: NewRouter().ReverseProxy(cfg.ReverseProxy).Global(global).UserResolver(userResolver)} staticFS := http.FileServer(http.Dir(cfg.StaticPath)) h.r.ANYHTTP("-/static/*rest", @@ -42,25 +42,17 @@ func NewHandler(cfg Config, global *global.Global, queries *queries.Queries) *ha repoHTTP := repoHandlers.NewHTTP(renderer) notImpl := handlers.NewNotImplementedHTTP(renderer) - // Index h.r.GET("/", indexHTTP.Index) - // Top-level utilities h.r.ANY("-/login", loginHTTP.Login) h.r.ANY("-/users", notImpl.Handle) - // Group index h.r.GET("@group/", groupHTTP.Index) h.r.POST("@group/", groupHTTP.Post) - // Repo index h.r.GET("@group/-/repos/:repo/", repoHTTP.Index) - - // Repo (not implemented yet) h.r.ANY("@group/-/repos/:repo/info", notImpl.Handle) h.r.ANY("@group/-/repos/:repo/git-upload-pack", notImpl.Handle) - - // Repo features h.r.GET("@group/-/repos/:repo/branches/", notImpl.Handle) h.r.GET("@group/-/repos/:repo/log/", notImpl.Handle) h.r.GET("@group/-/repos/:repo/commit/:commit", notImpl.Handle) diff --git a/forged/internal/incoming/web/handlers/group.go b/forged/internal/incoming/web/handlers/group.go index 3201491..4823cb7 100644 --- a/forged/internal/incoming/web/handlers/group.go +++ b/forged/internal/incoming/web/handlers/group.go @@ -1,13 +1,17 @@ package handlers import ( + "fmt" "log/slog" "net/http" + "path/filepath" "strconv" + "github.com/jackc/pgx/v5" "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" + "go.lindenii.runxiyu.org/forge/forged/internal/ipc/git2c" ) type GroupHTTP struct { @@ -31,19 +35,19 @@ func (h *GroupHTTP) Index(w http.ResponseWriter, r *http.Request, _ wtypes.Vars) Column1: base.URLSegments, UserID: userID, } - p, err := base.Queries.GetGroupByPath(r.Context(), queryParams) + p, err := base.Global.Queries.GetGroupByPath(r.Context(), queryParams) if err != nil { slog.Error("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) + subgroups, err := base.Global.Queries.GetSubgroups(r.Context(), &p.ID) if err != nil { slog.Error("failed to get subgroups", "error", err) http.Error(w, "Internal Server Error", http.StatusInternalServerError) // TODO: gracefully fail this part of the page } - repos, err := base.Queries.GetReposInGroup(r.Context(), p.ID) + repos, err := base.Global.Queries.GetReposInGroup(r.Context(), p.ID) if err != nil { slog.Error("failed to get repos in group", "error", err) http.Error(w, "Internal Server Error", http.StatusInternalServerError) @@ -78,7 +82,7 @@ func (h *GroupHTTP) Post(w http.ResponseWriter, r *http.Request, _ wtypes.Vars) Column1: base.URLSegments, UserID: userID, } - p, err := base.Queries.GetGroupByPath(r.Context(), queryParams) + p, err := base.Global.Queries.GetGroupByPath(r.Context(), queryParams) if err != nil { slog.Error("failed to get group ID by path", "error", err) http.Error(w, "Internal Server Error", http.StatusInternalServerError) @@ -89,4 +93,64 @@ func (h *GroupHTTP) Post(w http.ResponseWriter, r *http.Request, _ wtypes.Vars) http.Error(w, "You do not have the necessary permissions to create repositories in this group.", http.StatusForbidden) return } + + name := r.PostFormValue("repo_name") + desc := r.PostFormValue("repo_desc") + contrib := r.PostFormValue("repo_contrib") + if name == "" { + http.Error(w, "Repo name is required", http.StatusBadRequest) + return + } + + if contrib == "" || contrib == "public" { + contrib = "open" + } + + tx, err := base.Global.DB.BeginTx(r.Context(), pgx.TxOptions{}) + if err != nil { + slog.Error("begin tx failed", "error", err) + http.Error(w, "Internal Server Error", http.StatusInternalServerError) + return + } + defer func() { _ = tx.Rollback(r.Context()) }() + + txq := base.Global.Queries.WithTx(tx) + var descPtr *string + if desc != "" { + descPtr = &desc + } + repoID, err := txq.InsertRepo(r.Context(), queries.InsertRepoParams{ + GroupID: p.ID, + Name: name, + Description: descPtr, + ContribRequirements: contrib, + }) + if err != nil { + slog.Error("insert repo failed", "error", err) + http.Error(w, "Failed to create repository", http.StatusInternalServerError) + return + } + + repoPath := filepath.Join(base.Global.Config.Git.RepoDir, fmt.Sprintf("%d.git", repoID)) + + gitc, err := git2c.NewClient(r.Context(), base.Global.Config.Git.Socket) + if err != nil { + slog.Error("git2d connect failed", "error", err) + http.Error(w, "Failed to initialize repository (backend)", http.StatusInternalServerError) + return + } + defer func() { _ = gitc.Close() }() + if err = gitc.InitRepo(repoPath, base.Global.Config.Hooks.Execs); err != nil { + slog.Error("git2d init failed", "error", err) + http.Error(w, "Failed to initialize repository", http.StatusInternalServerError) + return + } + + if err = tx.Commit(r.Context()); err != nil { + slog.Error("commit tx failed", "error", err) + http.Error(w, "Failed to finalize repository creation", http.StatusInternalServerError) + return + } + + http.Redirect(w, r, r.URL.Path, http.StatusSeeOther) } diff --git a/forged/internal/incoming/web/handlers/index.go b/forged/internal/incoming/web/handlers/index.go index 22e6201..a758b07 100644 --- a/forged/internal/incoming/web/handlers/index.go +++ b/forged/internal/incoming/web/handlers/index.go @@ -6,7 +6,6 @@ import ( "go.lindenii.runxiyu.org/forge/forged/internal/database/queries" "go.lindenii.runxiyu.org/forge/forged/internal/incoming/web/templates" - "go.lindenii.runxiyu.org/forge/forged/internal/incoming/web/types" wtypes "go.lindenii.runxiyu.org/forge/forged/internal/incoming/web/types" ) @@ -21,17 +20,17 @@ func NewIndexHTTP(r templates.Renderer) *IndexHTTP { } func (h *IndexHTTP) Index(w http.ResponseWriter, r *http.Request, _ wtypes.Vars) { - groups, err := types.Base(r).Queries.GetRootGroups(r.Context()) + groups, err := wtypes.Base(r).Global.Queries.GetRootGroups(r.Context()) if err != nil { http.Error(w, "failed to get root groups", http.StatusInternalServerError) log.Println("failed to get root groups", "error", err) return } err = h.r.Render(w, "index", struct { - BaseData *types.BaseData + BaseData *wtypes.BaseData Groups []queries.GetRootGroupsRow }{ - BaseData: types.Base(r), + BaseData: wtypes.Base(r), Groups: groups, }) if err != nil { diff --git a/forged/internal/incoming/web/handlers/repo/index.go b/forged/internal/incoming/web/handlers/repo/index.go index 1a804b2..c2cb24a 100644 --- a/forged/internal/incoming/web/handlers/repo/index.go +++ b/forged/internal/incoming/web/handlers/repo/index.go @@ -1,20 +1,132 @@ package repo import ( + "bytes" + "fmt" + "html/template" + "log/slog" "net/http" + "net/url" + "path/filepath" "strings" + "github.com/yuin/goldmark" + "github.com/yuin/goldmark/extension" + "go.lindenii.runxiyu.org/forge/forged/internal/common/misc" + "go.lindenii.runxiyu.org/forge/forged/internal/database/queries" wtypes "go.lindenii.runxiyu.org/forge/forged/internal/incoming/web/types" + "go.lindenii.runxiyu.org/forge/forged/internal/ipc/git2c" ) func (h *HTTP) Index(w http.ResponseWriter, r *http.Request, v wtypes.Vars) { base := wtypes.Base(r) - repo := v["repo"] - _ = h.r.Render(w, "repo/index.html", struct { - Group string - Repo string - }{ - Group: "/" + strings.Join(base.GroupPath, "/") + "/", - Repo: repo, + repoName := v["repo"] + slog.Info("repo index", "group_path", base.GroupPath, "repo", repoName) + + var userID int64 + if base.UserID != "" { + _, _ = fmt.Sscan(base.UserID, &userID) + } + grp, err := base.Global.Queries.GetGroupByPath(r.Context(), queries.GetGroupByPathParams{ + Column1: base.GroupPath, + UserID: userID, + }) + if err != nil { + slog.Error("get group by path", "error", err) + http.Error(w, "Group not found", http.StatusNotFound) + return + } + + repoRow, err := base.Global.Queries.GetRepoByGroupAndName(r.Context(), queries.GetRepoByGroupAndNameParams{ + GroupID: grp.ID, + Name: repoName, }) + if err != nil { + slog.Error("get repo by name", "error", err) + http.Error(w, "Repository not found", http.StatusNotFound) + return + } + + repoPath := filepath.Join(base.Global.Config.Git.RepoDir, fmt.Sprintf("%d.git", repoRow.ID)) + + var commits []git2c.Commit + var readme template.HTML + var commitsErr error + var readmeFile *git2c.FilenameContents + var cerr error + client, err := git2c.NewClient(r.Context(), base.Global.Config.Git.Socket) + if err == nil { + defer func() { _ = client.Close() }() + commits, readmeFile, cerr = client.CmdIndex(repoPath) + if cerr != nil { + commitsErr = cerr + slog.Error("git2d CmdIndex failed", "error", cerr, "path", repoPath) + } else if readmeFile != nil { + nameLower := strings.ToLower(readmeFile.Filename) + if strings.HasSuffix(nameLower, ".md") || strings.HasSuffix(nameLower, ".markdown") || nameLower == "readme" { + md := goldmark.New( + goldmark.WithExtensions(extension.GFM), + ) + var buf bytes.Buffer + if err := md.Convert(readmeFile.Content, &buf); err == nil { + readme = template.HTML(buf.String()) + } else { + readme = template.HTML(template.HTMLEscapeString(string(readmeFile.Content))) + } + } else { + readme = template.HTML(template.HTMLEscapeString(string(readmeFile.Content))) + } + } + } else { + commitsErr = err + slog.Error("git2d connect failed", "error", err) + } + + sshRoot := strings.TrimSuffix(base.Global.Config.SSH.Root, "/") + httpRoot := strings.TrimSuffix(base.Global.Config.Web.Root, "/") + pathPart := misc.SegmentsToURL(base.GroupPath) + "/-/repos/" + url.PathEscape(repoRow.Name) + sshURL := "" + httpURL := "" + if sshRoot != "" { + sshURL = sshRoot + "/" + pathPart + } + if httpRoot != "" { + httpURL = httpRoot + "/" + pathPart + } + + var notes []string + if len(commits) == 0 && commitsErr == nil { + notes = append(notes, "This repository has no commits yet.") + } + if readme == template.HTML("") { + notes = append(notes, "No README found in the default branch.") + } + if sshURL == "" && httpURL == "" { + notes = append(notes, "Clone URLs not configured (missing SSH root and HTTP root).") + } + + cloneURL := sshURL + if cloneURL == "" { + cloneURL = httpURL + } + + data := map[string]any{ + "BaseData": base, + "group_path": base.GroupPath, + "repo_name": repoRow.Name, + "repo_description": repoRow.Description, + "ssh_clone_url": cloneURL, + "ref_name": base.RefName, + "commits": commits, + "commits_err": &commitsErr, + "readme": readme, + "notes": notes, + "global": map[string]any{ + "forge_title": base.Global.ForgeTitle, + }, + } + if err := h.r.Render(w, "repo_index", data); err != nil { + slog.Error("render repo index", "error", err) + http.Error(w, "Internal Server Error", http.StatusInternalServerError) + } } diff --git a/forged/internal/incoming/web/handlers/repo/raw.go b/forged/internal/incoming/web/handlers/repo/raw.go index e421f45..8bdfae3 100644 --- a/forged/internal/incoming/web/handlers/repo/raw.go +++ b/forged/internal/incoming/web/handlers/repo/raw.go @@ -15,5 +15,5 @@ func (h *HTTP) Raw(w http.ResponseWriter, r *http.Request, v wtypes.Vars) { if base.DirMode && rest != "" && !strings.HasSuffix(rest, "/") { rest += "/" } - _, _ = w.Write([]byte(fmt.Sprintf("raw: repo=%q path=%q", repo, rest))) + _, _ = fmt.Fprintf(w, "raw: repo=%q path=%q", repo, rest) } diff --git a/forged/internal/incoming/web/handlers/repo/tree.go b/forged/internal/incoming/web/handlers/repo/tree.go index 3432244..236dd48 100644 --- a/forged/internal/incoming/web/handlers/repo/tree.go +++ b/forged/internal/incoming/web/handlers/repo/tree.go @@ -15,5 +15,5 @@ func (h *HTTP) Tree(w http.ResponseWriter, r *http.Request, v wtypes.Vars) { if base.DirMode && rest != "" && !strings.HasSuffix(rest, "/") { rest += "/" } - _, _ = w.Write([]byte(fmt.Sprintf("tree: repo=%q path=%q", repo, rest))) + _, _ = fmt.Fprintf(w, "tree: repo=%q path=%q", repo, rest) } diff --git a/forged/internal/incoming/web/handlers/special/login.go b/forged/internal/incoming/web/handlers/special/login.go index 0287c47..5672f1f 100644 --- a/forged/internal/incoming/web/handlers/special/login.go +++ b/forged/internal/incoming/web/handlers/special/login.go @@ -14,7 +14,6 @@ import ( "go.lindenii.runxiyu.org/forge/forged/internal/common/misc" "go.lindenii.runxiyu.org/forge/forged/internal/database/queries" "go.lindenii.runxiyu.org/forge/forged/internal/incoming/web/templates" - "go.lindenii.runxiyu.org/forge/forged/internal/incoming/web/types" wtypes "go.lindenii.runxiyu.org/forge/forged/internal/incoming/web/types" ) @@ -33,10 +32,10 @@ func NewLoginHTTP(r templates.Renderer, cookieExpiry int) *LoginHTTP { func (h *LoginHTTP) Login(w http.ResponseWriter, r *http.Request, _ wtypes.Vars) { renderLoginPage := func(loginError string) bool { err := h.r.Render(w, "login", struct { - BaseData *types.BaseData + BaseData *wtypes.BaseData LoginError string }{ - BaseData: types.Base(r), + BaseData: wtypes.Base(r), LoginError: loginError, }) if err != nil { @@ -55,7 +54,7 @@ func (h *LoginHTTP) Login(w http.ResponseWriter, r *http.Request, _ wtypes.Vars) username := r.PostFormValue("username") password := r.PostFormValue("password") - userCreds, err := types.Base(r).Queries.GetUserCreds(r.Context(), &username) + userCreds, err := wtypes.Base(r).Global.Queries.GetUserCreds(r.Context(), &username) if err != nil { if errors.Is(err, pgx.ErrNoRows) { renderLoginPage("User not found") @@ -102,7 +101,7 @@ func (h *LoginHTTP) Login(w http.ResponseWriter, r *http.Request, _ wtypes.Vars) tokenHash := sha256.Sum256(misc.StringToBytes(cookieValue)) - err = types.Base(r).Queries.InsertSession(r.Context(), queries.InsertSessionParams{ + err = wtypes.Base(r).Global.Queries.InsertSession(r.Context(), queries.InsertSessionParams{ UserID: userCreds.ID, TokenHash: tokenHash[:], ExpiresAt: pgtype.Timestamptz{ @@ -110,6 +109,11 @@ func (h *LoginHTTP) Login(w http.ResponseWriter, r *http.Request, _ wtypes.Vars) Valid: true, }, }) + if err != nil { + log.Println("failed to insert session", "error", err) + http.Error(w, "Failed to create session", http.StatusInternalServerError) + return + } http.Redirect(w, r, "/", http.StatusSeeOther) } diff --git a/forged/internal/incoming/web/router.go b/forged/internal/incoming/web/router.go index 8356191..3809afb 100644 --- a/forged/internal/incoming/web/router.go +++ b/forged/internal/incoming/web/router.go @@ -7,7 +7,6 @@ import ( "sort" "strings" - "go.lindenii.runxiyu.org/forge/forged/internal/database/queries" "go.lindenii.runxiyu.org/forge/forged/internal/global" wtypes "go.lindenii.runxiyu.org/forge/forged/internal/incoming/web/types" ) @@ -62,7 +61,6 @@ type Router struct { user UserResolver global *global.Global reverseProxy bool - queries *queries.Queries } func NewRouter() *Router { return &Router{} } @@ -71,10 +69,6 @@ func (r *Router) Global(g *global.Global) *Router { r.global = g return r } -func (r *Router) Queries(q *queries.Queries) *Router { - r.queries = q - return r -} func (r *Router) ReverseProxy(enabled bool) *Router { r.reverseProxy = enabled; return r } func (r *Router) Errors(e ErrorRenderers) *Router { r.errors = e; return r } func (r *Router) UserResolver(u UserResolver) *Router { r.user = u; return r } @@ -148,7 +142,6 @@ func (r *Router) ServeHTTP(w http.ResponseWriter, req *http.Request) { Global: r.global, URLSegments: segments, DirMode: dirMode, - Queries: r.queries, } req = req.WithContext(wtypes.WithBaseData(req.Context(), bd)) @@ -202,7 +195,7 @@ func (r *Router) ServeHTTP(w http.ResponseWriter, req *http.Request) { bd.GroupPath = strings.Split(g, "/") } - if rt.method != "" && !(rt.method == method || (method == http.MethodHead && rt.method == http.MethodGet)) { + if rt.method != "" && rt.method != method && (method != http.MethodHead || rt.method != http.MethodGet) { continue } @@ -423,6 +416,4 @@ func GetParamRefTypeName(request *http.Request) (retRefType, retRefName string, return } -var ( - errDupRefSpec = fmt.Errorf("duplicate ref specifications") -) +var errDupRefSpec = fmt.Errorf("duplicate ref specifications") diff --git a/forged/internal/incoming/web/server.go b/forged/internal/incoming/web/server.go index 465657c..ab70aec 100644 --- a/forged/internal/incoming/web/server.go +++ b/forged/internal/incoming/web/server.go @@ -9,7 +9,6 @@ import ( "time" "go.lindenii.runxiyu.org/forge/forged/internal/common/misc" - "go.lindenii.runxiyu.org/forge/forged/internal/database/queries" "go.lindenii.runxiyu.org/forge/forged/internal/global" ) @@ -22,19 +21,20 @@ type Server struct { global *global.Global } -func New(config Config, global *global.Global, queries *queries.Queries) *Server { +func New(global *global.Global) *Server { + cfg := global.Config.Web httpServer := &http.Server{ - Handler: NewHandler(config, global, queries), - ReadTimeout: time.Duration(config.ReadTimeout) * time.Second, - WriteTimeout: time.Duration(config.WriteTimeout) * time.Second, - IdleTimeout: time.Duration(config.IdleTimeout) * time.Second, - MaxHeaderBytes: config.MaxHeaderBytes, + Handler: NewHandler(global), + ReadTimeout: time.Duration(cfg.ReadTimeout) * time.Second, + WriteTimeout: time.Duration(cfg.WriteTimeout) * time.Second, + IdleTimeout: time.Duration(cfg.IdleTimeout) * time.Second, + MaxHeaderBytes: cfg.MaxHeaderBytes, } //exhaustruct:ignore return &Server{ - net: config.Net, - addr: config.Addr, - root: config.Root, - shutdownTimeout: config.ShutdownTimeout, + net: cfg.Net, + addr: cfg.Addr, + root: cfg.Root, + shutdownTimeout: cfg.ShutdownTimeout, httpServer: httpServer, global: global, } diff --git a/forged/internal/incoming/web/templates/renderer.go b/forged/internal/incoming/web/templates/renderer.go index 1e2f325..350e9ec 100644 --- a/forged/internal/incoming/web/templates/renderer.go +++ b/forged/internal/incoming/web/templates/renderer.go @@ -1,7 +1,9 @@ package templates import ( + "bytes" "html/template" + "log/slog" "net/http" ) @@ -18,6 +20,16 @@ func New(t *template.Template) Renderer { } func (r *tmplRenderer) Render(w http.ResponseWriter, name string, data any) error { + var buf bytes.Buffer + if err := r.t.ExecuteTemplate(&buf, name, data); err != nil { + slog.Error("template render failed", "name", name, "error", err) + return err + } w.Header().Set("Content-Type", "text/html; charset=utf-8") - return r.t.ExecuteTemplate(w, name, data) + n, err := w.Write(buf.Bytes()) + if err != nil { + return err + } + slog.Info("template rendered", "name", name, "bytes", n) + return nil } diff --git a/forged/internal/incoming/web/types/types.go b/forged/internal/incoming/web/types/types.go index bacce24..4b9a65a 100644 --- a/forged/internal/incoming/web/types/types.go +++ b/forged/internal/incoming/web/types/types.go @@ -4,12 +4,9 @@ import ( "context" "net/http" - "go.lindenii.runxiyu.org/forge/forged/internal/database/queries" "go.lindenii.runxiyu.org/forge/forged/internal/global" ) -// BaseData is per-request context computed by the router and read by handlers. -// Keep it small and stable; page-specific data should live in view models. type BaseData struct { UserID string Username string @@ -20,17 +17,14 @@ type BaseData struct { RefType string RefName string Global *global.Global - Queries *queries.Queries } type ctxKey struct{} -// WithBaseData attaches BaseData to a context. func WithBaseData(ctx context.Context, b *BaseData) context.Context { return context.WithValue(ctx, ctxKey{}, b) } -// Base retrieves BaseData from the request (never nil). func Base(r *http.Request) *BaseData { if v, ok := r.Context().Value(ctxKey{}).(*BaseData); ok && v != nil { return v @@ -38,8 +32,6 @@ func Base(r *http.Request) *BaseData { return &BaseData{} } -// Vars are route variables captured by the router (e.g., :repo, *rest). type Vars map[string]string -// HandlerFunc is the router↔handler function contract. type HandlerFunc func(http.ResponseWriter, *http.Request, Vars) |