prime8dev

prime8dev
git clone https://git.prime8.dev/prime8dev.git
Log | Files | Refs | README | LICENSE

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 }