82 lines · 2.3 KB
1 /**
2 * Repository service: lookup and management.
3 */
4
5 import { NotFoundError, ForbiddenError } from '@gitfastr/shared/utils/errors.js';
6 import type { DbRepository, DbUser } from '@gitfastr/shared';
7
8 export class RepoService {
9 constructor(private db: D1Database) {}
10
11 /** Look up a repo by owner username and repo name. */
12 async getByOwnerAndName(
13 ownerUsername: string,
14 repoName: string
15 ): Promise<{ repo: DbRepository; owner: DbUser }> {
16 const row = await this.db
17 .prepare(
18 `SELECT r.*, u.username as owner_username, u.id as uid
19 FROM repositories r
20 JOIN users u ON r.owner_id = u.id
21 WHERE u.username = ? AND r.name = ?`
22 )
23 .bind(ownerUsername, repoName)
24 .first<DbRepository & { owner_username: string; uid: string }>();
25
26 if (!row) {
27 throw new NotFoundError(`Repository ${ownerUsername}/${repoName} not found`);
28 }
29
30 const repo: DbRepository = { ...row };
31 const owner: DbUser = {
32 id: row.uid,
33 username: row.owner_username,
34 } as DbUser;
35
36 return { repo, owner };
37 }
38
39 /** Check if a user has write access to a repo. */
40 async hasWriteAccess(repoId: string, userId: string | null | undefined): Promise<boolean> {
41 if (!userId) return false;
42
43 // Check if user is the owner
44 const repo = await this.db
45 .prepare('SELECT owner_id FROM repositories WHERE id = ?')
46 .bind(repoId)
47 .first<Pick<DbRepository, 'owner_id'>>();
48
49 if (repo?.owner_id === userId) return true;
50
51 // Check collaborators
52 const collab = await this.db
53 .prepare(
54 `SELECT permission FROM collaborators
55 WHERE repo_id = ? AND user_id = ?`
56 )
57 .bind(repoId, userId)
58 .first<{ permission: string }>();
59
60 return collab?.permission === 'write' || collab?.permission === 'admin';
61 }
62
63 /** Check if a user can read a repo. */
64 async hasReadAccess(
65 repo: DbRepository,
66 userId: string | null | undefined
67 ): Promise<boolean> {
68 if (!repo.is_private) return true;
69 if (!userId) return false;
70 if (repo.owner_id === userId) return true;
71
72 const collab = await this.db
73 .prepare(
74 'SELECT 1 FROM collaborators WHERE repo_id = ? AND user_id = ?'
75 )
76 .bind(repo.id, userId)
77 .first();
78
79 return collab !== null;
80 }
81 }
82