~bigbes/sr-ht-dolt

ref: 74d2612eac643b9e6e038e6dd1a3dda9b7940519 sr-ht-dolt/web/deps.go -rw-r--r-- 6.9 KiB
74d2612e — Eugene Blikh fix(db): map repository_path_key to ErrNameTaken 25 days ago
                                                                                
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
// Package web is the HTTP layer of dolt.sr.ht: the chi router, request
// handlers, SourceHut nav/chrome, and the html/template views for the database
// dashboard, browse pages, settings and dolt-key management.
//
// # Dependency injection
//
// web is deliberately decoupled from the packages that touch Postgres, disk and
// the remotesapi. It depends directly only on the pure/committed packages it
// renders (core, browse) and authn (for the caller in the request context).
// Everything with side effects — the metadata store, the on-disk store manager,
// the browse opener, and username resolution against meta — is reached through
// SMALL local interfaces declared here and satisfied by thin adapters (see
// adapters.go for the production wiring, and the tests for fakes). This keeps
// httptest coverage free of Postgres and dolt internals, and lets the Phase-3
// main assemble the real Config without web importing storage/ or remoteapi/.
package web

import (
	"context"

	"github.com/vaughan0/go-ini"

	"sourcecraft.dev/bigbes/sr-ht-core/auth"

	"sourcecraft.dev/bigbes/sr-ht-dolt/authn"
	"sourcecraft.dev/bigbes/sr-ht-dolt/browse"
	"sourcecraft.dev/bigbes/sr-ht-dolt/core"
	"sourcecraft.dev/bigbes/sr-ht-dolt/db"
)

// Config carries everything the router and handlers need. The Phase-3 main
// builds one and passes it to Register.
type Config struct {
	// Conf is the shared instance config (the same ini.File every *.sr.ht
	// service reads). Used to render the nav/chrome and resolve origins.
	Conf ini.File
	// ReposRoot is the absolute directory holding the bare NBS stores, one per
	// database at <ReposRoot>/~<owner>/<name>. Passed to StoreManager.DeleteStore
	// as the containment root.
	ReposRoot string
	// StaticDir is the directory holding built static assets (the hashed
	// main.min.<sha>.css and logo.svg). The CSS filename is discovered from it at
	// Register time; "" falls back to the dev stylesheet /static/main.css.
	StaticDir string

	// Stores manages the on-disk NBS chunk stores. Satisfied in production by a
	// storage-backed adapter (Phase 3); web never imports storage/.
	Stores StoreManager
	// Repos is the metadata store (repositories, ACLs, dolt keys). Satisfied in
	// production by dbAdapter over db.Store; fakes are used in tests.
	Repos RepoStore
	// Browse opens read-only handles to bare stores for the browse pages.
	// Satisfied in production by browseAdapter over browse.Open.
	Browse BrowseOpener
	// Users resolves a SourceHut username to its account (for ACL add-by-username),
	// mirroring the meta profile on first sight. Satisfied in production by a
	// core-go auth.LookupUser adapter.
	Users UserResolver
	// RepoDiskPath returns the absolute on-disk store dir for owner/name. In
	// production this is storage.RepoDiskPath bound to ReposRoot.
	RepoDiskPath func(owner, name string) string
}

// StoreManager is the on-disk store lifecycle the create/delete handlers drive.
// It mirrors the storage package's InitStore/DeleteStore functions and the
// Cache.Evict method; web declares it as an interface so it never imports
// storage/.
type StoreManager interface {
	// InitStore creates a bare store at absPath and writes an empty repo authored
	// by ownerName/ownerEmail. On any failure it must leave no partial store.
	InitStore(ctx context.Context, absPath, ownerName, ownerEmail string) error
	// DeleteStore removes the store at absPath, refusing anything outside root.
	DeleteStore(ctx context.Context, root, absPath string) error
	// Evict closes and drops any memoized served handle for diskPath, so a
	// recreation at the same path never reuses a stale store.
	Evict(diskPath string) error
}

// RepoStore is the subset of db.Store the handlers use. Declaring it as an
// interface lets tests inject a fake without Postgres; the production dbAdapter
// (adapters.go) is a compile-time-checked implementation over the real store.
// Every method takes ctx first; the production adapter reads the request-scoped
// *sql.DB from ctx (db.FromContext) so a single adapter value serves all
// requests.
type RepoStore interface {
	CreateRepo(ctx context.Context, r *core.Repo) (*core.Repo, error)
	GetRepoByOwnerAndName(ctx context.Context, ownerUsername, name string) (*core.Repo, error)
	ListReposByOwner(ctx context.Context, ownerUsername string, viewer *core.Caller) ([]*core.Repo, error)
	ListReposForDashboard(ctx context.Context, userID int) ([]*core.Repo, error)
	UpdateRepo(ctx context.Context, id int, description string, visibility core.Visibility) error
	DeleteRepo(ctx context.Context, id int) error

	EffectiveAccess(ctx context.Context, userID, repoID int) (*core.AccessMode, error)
	ListACL(ctx context.Context, repoID int) ([]*db.ACLEntry, error)
	UpsertACL(ctx context.Context, repoID, userID int, mode core.AccessMode) error
	DeleteACL(ctx context.Context, repoID, userID int) error

	InsertKey(ctx context.Context, userID int, kid string, pubkey []byte, comment string) (*db.DoltKey, error)
	ListKeysByUser(ctx context.Context, userID int) ([]*db.DoltKey, error)
	DeleteKey(ctx context.Context, id, userID int) error
}

// BrowseSession is the read-only browse surface a single request uses. It is
// exactly the method set of *browse.DB (plus Close), so the production adapter
// returns a *browse.DB directly. Fakes implement it for httptest.
type BrowseSession interface {
	Branches(ctx context.Context) ([]browse.Branch, error)
	Log(ctx context.Context, refStr, fromHash string, limit int) ([]browse.CommitInfo, string, error)
	Tables(ctx context.Context, refStr string) ([]browse.TableInfo, error)
	Rows(ctx context.Context, refStr, table string, offset, limit int) (*browse.RowPage, error)
	CommitSummary(ctx context.Context, hashStr string) (*browse.CommitDiff, error)
	Close() error
}

// BrowseOpener opens a BrowseSession over the bare store at diskPath. Open must
// be paired with Session.Close by the caller (handlers defer it).
type BrowseOpener interface {
	Open(ctx context.Context, diskPath string) (BrowseSession, error)
}

// UserResolver resolves a username to a core account, mirroring the meta
// profile into the local user table on first sight (so the resolved UserID can
// be used as an ACL grantee). Returns an error the caller treats as "no such
// user" for a permanent miss.
type UserResolver interface {
	LookupUser(ctx context.Context, username string) (*core.Caller, error)
}

// authContext aliases core-go's auth.AuthContext for brevity in handler
// signatures; it is the authenticated caller (nil = anonymous).
type authContext = auth.AuthContext

// callerOf returns the resolved caller for a request context: the raw
// *auth.AuthContext (nil = anonymous) for chrome rendering, and the pure
// core.Caller for the access-control matrix. It is the single bridge from the
// authn context value to the domain types used throughout the handlers.
func callerOf(ctx context.Context) (*auth.AuthContext, *core.Caller) {
	ac := authn.CallerFromContext(ctx)
	return ac, authn.AsCoreCaller(ac)
}