startup.go
⎇
Raw
1package web
2
3import (
4 "context"
5 "log"
6 "time"
7
8 "hearthforge/internal/db"
9)
10
11// SyncRepos reconciles the repositories table with the repos on disk.
12// New bare repos get a row. Rows without a directory are kept, so an admin
13// can restore the directory or drop the data from the settings page.
14func (s *Server) SyncRepos(ctx context.Context) error {
15 names, err := s.Git.SyncStartup(ctx)
16 if err != nil {
17 return err
18 }
19 onDisk := make(map[string]bool, len(names))
20 for _, n := range names {
21 onDisk[n] = true
22 }
23 rows, err := s.DB.AllRepoRefs(ctx)
24 if err != nil {
25 return err
26 }
27 inDB := make(map[string]bool, len(rows))
28 for _, r := range rows {
29 inDB[r.Name] = true
30 if !onDisk[r.Name] {
31 // Keep the row. Deleting it would cascade to issues and patches.
32 // An admin drops it from the settings page.
33 log.Printf("repo %q missing on disk, keeping its data", r.Name)
34 }
35 }
36 for _, n := range names {
37 if inDB[n] {
38 continue
39 }
40 branch := s.Git.DefaultBranch(ctx, n)
41 if _, err := s.DB.CreateRepo(ctx, n, nil, s.Cfg.ScannedRepoPrivate, branch, db.NowISO()); err != nil {
42 return err
43 }
44 log.Printf("registered scanned repo %q", n)
45 }
46 return nil
47}
48
49// staleSignupAge is how long a passwordless signup may sit without a passkey
50// before the account is removed.
51const staleSignupAge = 15 * time.Minute
52
53// StartSessionCleanup deletes expired sessions and abandoned passwordless
54// signups once an hour.
55func (s *Server) StartSessionCleanup(ctx context.Context) {
56 go func() {
57 t := time.NewTicker(time.Hour)
58 defer t.Stop()
59 for {
60 select {
61 case <-ctx.Done():
62 return
63 case <-t.C:
64 if err := s.DB.DeleteExpiredSessions(ctx); err != nil {
65 log.Printf("session cleanup: %v", err)
66 }
67 cutoff := time.Now().UTC().Add(-staleSignupAge).Format(db.ISOLayout)
68 if err := s.DB.DeleteStalePasskeySignups(ctx, cutoff); err != nil {
69 log.Printf("stale signup cleanup: %v", err)
70 }
71 }
72 }
73 }()
74}
75