Skip to main content

max / makenotwork

3.2 KB · 138 lines History Blame Raw
1 //! Per-repo collaborator access queries.
2
3 use chrono::{DateTime, Utc};
4 use sqlx::{FromRow, PgPool};
5 use uuid::Uuid;
6
7 use super::{GitRepoId, UserId};
8 use crate::error::Result;
9
10 #[derive(Debug, FromRow)]
11 pub struct DbRepoCollaborator {
12 pub id: Uuid,
13 pub repo_id: GitRepoId,
14 pub user_id: UserId,
15 pub can_push: bool,
16 pub created_at: DateTime<Utc>,
17 }
18
19 /// Collaborator with joined username for display.
20 #[derive(Debug, FromRow)]
21 pub struct CollaboratorWithUsername {
22 pub id: Uuid,
23 pub user_id: UserId,
24 pub username: String,
25 pub can_push: bool,
26 pub created_at: DateTime<Utc>,
27 }
28
29 /// Add a collaborator to a repo. Returns the new record.
30 #[tracing::instrument(skip_all)]
31 pub async fn add_collaborator(
32 pool: &PgPool,
33 repo_id: GitRepoId,
34 user_id: UserId,
35 can_push: bool,
36 ) -> Result<DbRepoCollaborator> {
37 let row = sqlx::query_as::<_, DbRepoCollaborator>(
38 r#"
39 INSERT INTO repo_collaborators (repo_id, user_id, can_push)
40 VALUES ($1, $2, $3)
41 RETURNING *
42 "#,
43 )
44 .bind(repo_id)
45 .bind(user_id)
46 .bind(can_push)
47 .fetch_one(pool)
48 .await?;
49
50 Ok(row)
51 }
52
53 /// Remove a collaborator from a repo. Returns true if a row was deleted.
54 #[tracing::instrument(skip_all)]
55 pub async fn remove_collaborator(
56 pool: &PgPool,
57 repo_id: GitRepoId,
58 user_id: UserId,
59 ) -> Result<bool> {
60 let result = sqlx::query(
61 "DELETE FROM repo_collaborators WHERE repo_id = $1 AND user_id = $2",
62 )
63 .bind(repo_id)
64 .bind(user_id)
65 .execute(pool)
66 .await?;
67
68 Ok(result.rows_affected() > 0)
69 }
70
71 /// List collaborators for a repo, with usernames.
72 #[tracing::instrument(skip_all)]
73 pub async fn list_collaborators(
74 pool: &PgPool,
75 repo_id: GitRepoId,
76 ) -> Result<Vec<CollaboratorWithUsername>> {
77 let rows = sqlx::query_as::<_, CollaboratorWithUsername>(
78 r#"
79 SELECT rc.id, rc.user_id, u.username, rc.can_push, rc.created_at
80 FROM repo_collaborators rc
81 JOIN users u ON u.id = rc.user_id
82 WHERE rc.repo_id = $1
83 ORDER BY rc.created_at ASC
84 "#,
85 )
86 .bind(repo_id)
87 .fetch_all(pool)
88 .await?;
89
90 Ok(rows)
91 }
92
93 /// Check if a user has push access to a repo (either owner or collaborator with can_push).
94 #[tracing::instrument(skip_all)]
95 pub async fn can_user_push(
96 pool: &PgPool,
97 repo_id: GitRepoId,
98 user_id: UserId,
99 ) -> Result<bool> {
100 let row: (bool,) = sqlx::query_as(
101 r#"
102 SELECT EXISTS(
103 SELECT 1 FROM repo_collaborators
104 WHERE repo_id = $1 AND user_id = $2 AND can_push = true
105 )
106 "#,
107 )
108 .bind(repo_id)
109 .bind(user_id)
110 .fetch_one(pool)
111 .await?;
112
113 Ok(row.0)
114 }
115
116 /// Check if a user has read access to a repo (any collaborator record, regardless of can_push).
117 #[tracing::instrument(skip_all)]
118 pub async fn is_collaborator(
119 pool: &PgPool,
120 repo_id: GitRepoId,
121 user_id: UserId,
122 ) -> Result<bool> {
123 let row: (bool,) = sqlx::query_as(
124 r#"
125 SELECT EXISTS(
126 SELECT 1 FROM repo_collaborators
127 WHERE repo_id = $1 AND user_id = $2
128 )
129 "#,
130 )
131 .bind(repo_id)
132 .bind(user_id)
133 .fetch_one(pool)
134 .await?;
135
136 Ok(row.0)
137 }
138