diff --git a/internal/codeview/codeview.go b/internal/codeview/codeview.go index e07eba2..e4b64dc 100644 --- a/internal/codeview/codeview.go +++ b/internal/codeview/codeview.go @@ -1,10 +1,12 @@ package codeview import ( + "encoding/json" "fmt" "io" "log/slog" "net/http" + "net/url" "strings" "github.com/kulak/gemini" @@ -16,18 +18,16 @@ import ( * * Features: * - [x] Individual file view with line numbers? - * - [ ] Dir view with links to files + * - [x] Dir view with links to files * - [ ] Project root view with markdown to gemtxt conversion and file tree */ -// https://git.travisshears.com/travisshears/personal-gemini-capsule/raw/branch/main/internal/guestbook/guestbook.go -// /codeview/raw/personal-gemini-capsule/src/branch/main/internal/guestbook/guestbook.go func HandleRequest(w gemini.ResponseWriter, req *gemini.Request) { path := req.URL.Path switch { - // case path == "/gemlog" || path == "/gemlog/": - // g.serveIndex(w, req) + case strings.HasPrefix(path, "/codeview/dir"): + serveDir(w, req, path) case strings.HasPrefix(path, "/codeview/raw/"): serveFile(w, req, path) default: @@ -35,6 +35,94 @@ func HandleRequest(w gemini.ResponseWriter, req *gemini.Request) { } } +func serveDir(w gemini.ResponseWriter, req *gemini.Request, path string) { + repo := req.URL.Query().Get("repo") + filepath := url.QueryEscape(req.URL.Query().Get("filepath")) + branch := req.URL.Query().Get("branch") + commit := req.URL.Query().Get("commit") + slog.Info("Serving directory", "repo", repo, "filepath", filepath, "branch", branch, "commit", commit) + var gitURL string + ref := "" + if branch != "" { + ref = branch + } else if commit != "" { + ref = commit + } + + if filepath == "." { + gitURL = "https://git.travisshears.com/api/v1/repos/travisshears/" + repo + "/contents?ref=" + ref + } else { + gitURL = "https://git.travisshears.com/api/v1/repos/travisshears/" + repo + "/contents/" + filepath + "?ref=" + ref + } + + slog.Info("Fetching directory contents", "url", gitURL) + httpReq, err := http.NewRequest("GET", gitURL, nil) + if err != nil { + slog.Error("Failed to create request to git server", "error", err) + w.WriteStatusMsg(gemini.StatusGeneralPermFail, "Problem connecting to git server") + return + } + res, err := http.DefaultClient.Do(httpReq) + if err != nil { + slog.Error("Failed to send request to git server", "error", err) + w.WriteStatusMsg(gemini.StatusGeneralPermFail, "Problem connecting to git server") + return + } + defer res.Body.Close() + + body, err := io.ReadAll(res.Body) + if err != nil { + slog.Error("Failed to read res body", "error", err) + w.WriteStatusMsg(gemini.StatusGeneralPermFail, "Problem connecting to git server") + return + } + + if res.StatusCode < 200 || res.StatusCode >= 300 { + slog.Error("unexpected status code", "statusCode", res.StatusCode, "body", string(body)) + w.WriteStatusMsg(gemini.StatusGeneralPermFail, "Problem connecting to git server") + return + } + + var dirRes []struct { + Name string `json:"name"` + Path string `json:"path"` + URL string `json:"html_url"` + Type string `json:"type"` + } + + if err := json.Unmarshal(body, &dirRes); err != nil { + slog.Error("failed to parse response", "error", err) + w.WriteStatusMsg(gemini.StatusGeneralPermFail, "Problem parsing response from git server") + return + } + slog.Info("Successfully parsed response from git server", "res", dirRes) + + var content strings.Builder + w.WriteStatusMsg(gemini.StatusSuccess, "text/gemini") + content.WriteString("# Dir CodeView\n") + httpURL := fmt.Sprintf("https://git.travisshears.com/travisshears/personal-gemini-capsule/src/branch/main/%s", filepath) + if branch != "" { + httpURL = fmt.Sprintf("https://git.travisshears.com/travisshears/personal-gemini-capsule/src/branch/%s/%s", branch, filepath) + } else if commit != "" { + httpURL = fmt.Sprintf("https://git.travisshears.com/travisshears/personal-gemini-capsule/src/commit/%s/%s", commit, filepath) + } + content.WriteString(fmt.Sprintf("This is a code view which proxies my personal git server. On the clearnet the following dir is available:\n=> %s here\n\n", httpURL)) + + for _, file := range dirRes { + switch file.Type { + case "dir": + url := fmt.Sprintf("/codeview/dir?repo=%s&filepath=%s&commit=%s&branch=%s", repo, file.Path, commit, branch) + content.WriteString(fmt.Sprintf("=> %s %s/\n", url, file.Path)) + case "file": + p := strings.TrimPrefix(file.URL, "https://git.travisshears.com/travisshears/") + url := fmt.Sprintf("/codeview/raw/%s", p) + content.WriteString(fmt.Sprintf("=> %s %s\n", url, file.Path)) + } + } + content.WriteString("\n\n-----------------------------\n\n=> / Back to home\n") + w.WriteBody([]byte(content.String())) +} + func serveFile(w gemini.ResponseWriter, req *gemini.Request, path string) { path = strings.TrimPrefix(path, "/codeview/raw/") rawURL := strings.Replace(path, "src", "raw", 1)