projects.go (2721B)
1 package content 2 3 import ( 4 "context" 5 "log/slog" 6 "strings" 7 "sync" 8 9 "git.prime8.dev/moncheeta/prime8dev/internal/config" 10 "git.prime8.dev/moncheeta/prime8dev/internal/external" 11 "git.prime8.dev/moncheeta/prime8dev/internal/utils" 12 ) 13 14 // readmeMemo holds each project's rendered README keyed by the HEAD it was 15 // rendered from. The repos are local, so checking HEAD is one rev-parse and a 16 // push shows up on the next request; there is no TTL or background refresh. 17 var ( 18 readmeMu sync.Mutex 19 readmeMemo = map[string]memoEntry{} 20 ) 21 22 type memoEntry struct { 23 sha string 24 markdown string 25 html string 26 } 27 28 // LoadProjects returns the configured projects with their READMEs. 29 func LoadProjects(gitService *external.Git) ([]config.Project, error) { 30 if gitService == nil { 31 gitService = external.NewGit() 32 } 33 34 projects := config.GetProjects() 35 for i := range projects { 36 withReadme(gitService, &projects[i]) 37 } 38 return projects, nil 39 } 40 41 // GetProjectByID returns one configured project with its README, or nil. 42 func GetProjectByID(gitService *external.Git, id string) (*config.Project, error) { 43 id = strings.TrimSpace(id) 44 if id == "" { 45 return nil, nil 46 } 47 if gitService == nil { 48 gitService = external.NewGit() 49 } 50 51 project := config.GetProjectByID(id) 52 if project == nil { 53 return nil, nil 54 } 55 p := *project 56 withReadme(gitService, &p) 57 return &p, nil 58 } 59 60 func withReadme(gitService *external.Git, project *config.Project) { 61 if strings.TrimSpace(project.Repo) == "" { 62 return 63 } 64 ctx := context.Background() 65 66 sha, err := gitService.HeadSHA(ctx, *project) 67 if err != nil { 68 slog.Warn("project head lookup failed", "project_id", project.ID, "error", err) 69 return 70 } 71 72 readmeMu.Lock() 73 entry, ok := readmeMemo[project.ID] 74 readmeMu.Unlock() 75 76 if !ok || entry.sha != sha { 77 metadata, err := gitService.FetchMetadata(ctx, *project) 78 if err != nil { 79 slog.Warn("project metadata fetch failed", "project_id", project.ID, "error", err) 80 if !ok { 81 return 82 } 83 } else { 84 html, err := projectReadmeHTML(metadata) 85 if err != nil { 86 slog.Warn("project readme markdown render failed", "project_id", project.ID, "error", err) 87 if !ok { 88 return 89 } 90 } else { 91 entry = memoEntry{sha: sha, markdown: strings.TrimSpace(metadata.Readme), html: strings.TrimSpace(html)} 92 readmeMu.Lock() 93 readmeMemo[project.ID] = entry 94 readmeMu.Unlock() 95 } 96 } 97 } 98 99 project.Markdown = entry.markdown 100 project.HTML = entry.html 101 } 102 103 func projectReadmeHTML(metadata *external.GitMetadata) (string, error) { 104 if metadata == nil { 105 return "", nil 106 } 107 108 markdown := strings.TrimSpace(metadata.Readme) 109 if markdown == "" { 110 return "", nil 111 } 112 113 return utils.MarkdownToHTML(utils.RemoveFirstH1(markdown)) 114 }