arche / internal/archesrv/repos.go

commit 154431fd
  1package archesrv
  2
  3import (
  4	"database/sql"
  5	"fmt"
  6	"path/filepath"
  7	"time"
  8
  9	"arche/internal/repo"
 10)
 11
 12type RepoRecord struct {
 13	ID          int64
 14	Name        string
 15	Description string
 16	Visibility  string
 17	CreatedAt   time.Time
 18}
 19
 20func (r *RepoRecord) IsPublic() bool { return r.Visibility == "public" }
 21
 22func repoPath(dataDir, name string) string {
 23	return filepath.Join(dataDir, name)
 24}
 25
 26func (d *DB) CreateRepo(name, description, visibility string) (*RepoRecord, error) {
 27	if visibility != "public" && visibility != "private" {
 28		visibility = "private"
 29	}
 30	now := time.Now()
 31	res, err := d.db.Exec(
 32		"INSERT INTO repos(name,description,visibility,created_at) VALUES(?,?,?,?)",
 33		name, description, visibility, now.Unix(),
 34	)
 35	if err != nil {
 36		return nil, fmt.Errorf("create repo: %w", err)
 37	}
 38	id, _ := res.LastInsertId()
 39	return &RepoRecord{
 40		ID:          id,
 41		Name:        name,
 42		Description: description,
 43		Visibility:  visibility,
 44		CreatedAt:   now,
 45	}, nil
 46}
 47
 48func (d *DB) GetRepo(name string) (*RepoRecord, error) {
 49	r := &RepoRecord{}
 50	var ts int64
 51	err := d.db.QueryRow(
 52		"SELECT id, name, description, visibility, created_at FROM repos WHERE name=?", name,
 53	).Scan(&r.ID, &r.Name, &r.Description, &r.Visibility, &ts)
 54	if err == sql.ErrNoRows {
 55		return nil, nil
 56	}
 57	if err != nil {
 58		return nil, err
 59	}
 60	r.CreatedAt = time.Unix(ts, 0)
 61	return r, nil
 62}
 63
 64func (d *DB) ListRepos() ([]RepoRecord, error) {
 65	rows, err := d.db.Query(
 66		"SELECT id, name, description, visibility, created_at FROM repos ORDER BY name",
 67	)
 68	if err != nil {
 69		return nil, err
 70	}
 71	defer rows.Close()
 72	var out []RepoRecord
 73	for rows.Next() {
 74		var r RepoRecord
 75		var ts int64
 76		if err := rows.Scan(&r.ID, &r.Name, &r.Description, &r.Visibility, &ts); err != nil {
 77			return nil, err
 78		}
 79		r.CreatedAt = time.Unix(ts, 0)
 80		out = append(out, r)
 81	}
 82	return out, rows.Err()
 83}
 84
 85func (d *DB) DeleteRepo(name string) error {
 86	_, err := d.db.Exec("DELETE FROM repos WHERE name=?", name)
 87	return err
 88}
 89
 90func (d *DB) CanRead(rec *RepoRecord, u *User) bool {
 91	if rec.IsPublic() {
 92		return true
 93	}
 94	if u == nil {
 95		return false
 96	}
 97	if u.IsAdmin {
 98		return true
 99	}
100	return d.hasRole(rec.ID, u.ID, "read", "write", "admin")
101}
102
103func (d *DB) CanWrite(rec *RepoRecord, u *User) bool {
104	if u == nil {
105		return false
106	}
107	if u.IsAdmin {
108		return true
109	}
110	return d.hasRole(rec.ID, u.ID, "write", "admin")
111}
112
113func (d *DB) hasRole(repoID, userID int64, roles ...string) bool {
114	for _, role := range roles {
115		var count int
116		d.db.QueryRow( //nolint:errcheck
117			"SELECT COUNT(*) FROM repo_permissions WHERE repo_id=? AND user_id=? AND role=?",
118			repoID, userID, role,
119		).Scan(&count)
120		if count > 0 {
121			return true
122		}
123	}
124	return false
125}
126
127func (d *DB) SetPermission(repoID, userID int64, role string) error {
128	_, err := d.db.Exec(
129		`INSERT INTO repo_permissions(repo_id,user_id,role) VALUES(?,?,?)
130		 ON CONFLICT(repo_id,user_id) DO UPDATE SET role=excluded.role`,
131		repoID, userID, role,
132	)
133	return err
134}
135
136func (d *DB) RemovePermission(repoID, userID int64) error {
137	_, err := d.db.Exec("DELETE FROM repo_permissions WHERE repo_id=? AND user_id=?", repoID, userID)
138	return err
139}
140
141type CollabEntry struct {
142	UserID   int64
143	Username string
144	Role     string
145}
146
147func (d *DB) ListCollaborators(repoID int64) ([]CollabEntry, error) {
148	rows, err := d.db.Query(
149		`SELECT u.id, u.username, rp.role
150		 FROM repo_permissions rp
151		 JOIN users u ON u.id = rp.user_id
152		 WHERE rp.repo_id = ?
153		 ORDER BY u.username`,
154		repoID,
155	)
156	if err != nil {
157		return nil, err
158	}
159	defer rows.Close()
160	var out []CollabEntry
161	for rows.Next() {
162		var e CollabEntry
163		if err := rows.Scan(&e.UserID, &e.Username, &e.Role); err != nil {
164			return nil, err
165		}
166		out = append(out, e)
167	}
168	return out, rows.Err()
169}
170
171func (d *DB) UpdateRepo(name, description, visibility string) error {
172	if visibility != "public" && visibility != "private" {
173		visibility = "private"
174	}
175	_, err := d.db.Exec(
176		"UPDATE repos SET description=?, visibility=? WHERE name=?",
177		description, visibility, name,
178	)
179	return err
180}
181
182func (d *DB) GetRepoHookConfig(repoID int64) (allowShellHooks bool, postReceiveScript string, err error) {
183	err = d.db.QueryRow(
184		"SELECT allow_shell_hooks, post_receive_script FROM repos WHERE id=?", repoID,
185	).Scan(&allowShellHooks, &postReceiveScript)
186	return
187}
188
189func (d *DB) SetRepoAllowShellHooks(repoID int64, allow bool, scriptPath string) error {
190	_, err := d.db.Exec(
191		"UPDATE repos SET allow_shell_hooks=?, post_receive_script=? WHERE id=?",
192		allow, scriptPath, repoID,
193	)
194	return err
195}
196
197func (d *DB) hasWriteCollaborator(repoID int64) bool {
198	var count int
199	d.db.QueryRow( //nolint:errcheck
200		`SELECT COUNT(*) FROM repo_permissions WHERE repo_id=? AND (role='write' OR role='admin')`,
201		repoID,
202	).Scan(&count)
203	return count > 0
204}
205
206func openRepo(dataDir, name string) (*repo.Repo, error) {
207	return repo.Open(repoPath(dataDir, name))
208}