d23464c0 Andy McCall 1 month ago ↶
1
2
3
use super :: { now_ms, Pool} ;
4
use hub_api:: Visibility;
5
use sqlx:: FromRow;
6
e7158350 Andy McCall 23 days ago ↶
7
# [ derive ( Clone, Debug, Default, FromRow ) ]
d23464c0 Andy McCall 1 month ago ↶
8
pub struct RepoRow {
9
pub id : i64 ,
10
pub owner : String,
11
pub name : String,
12
pub description : String,
13
pub default_bookmark : String,
14
pub visibility : String,
15
pub created_ms : i64 ,
16
pub updated_ms : i64 ,
3f4b0ea6 Andy McCall 1 month ago ↶
17
#[sqlx(default)]
18
pub git_mirror : bool ,
19
#[sqlx(default)]
20
pub git_mirror_state : String,
07b1a5bb Andy McCall 1 month ago ↶
21
22
#[sqlx(default)]
23
pub forked_from : Option< i64 > ,
1553bfc9 Andy McCall 1 month ago ↶
24
25
#[sqlx(default)]
26
pub topics : String,
9008bf6c Andy McCall 1 month ago ↶
27
28
#[sqlx(default)]
29
pub flow : String,
e7158350 Andy McCall 23 days ago ↶
30
31
32
#[sqlx(default)]
33
pub diffusion : String,
d0fbea2e Andy McCall 1 month ago ↶
34
35
#[sqlx(default)]
36
pub archived : bool ,
37
#[sqlx(default)]
38
pub archived_reason : String,
1ac84c62 Andy McCall 1 month ago ↶
39
40
#[sqlx(default)]
41
pub open_issues : i64 ,
42
#[sqlx(default)]
43
pub open_proposals : i64 ,
d23464c0 Andy McCall 1 month ago ↶
44
}
45
46
impl RepoRow {
47
pub fn visibility ( & self ) -> Visibility {
48
Visibility:: parse( & self .visibility) .unwrap_or ( Visibility:: Private)
49
}
50
1553bfc9 Andy McCall 1 month ago ↶
51
pub fn topic_list ( & self ) -> Vec< & str > {
52
self .topics.split_whitespace ( ) .collect ( )
53
}
54
d23464c0 Andy McCall 1 month ago ↶
55
pub fn slug ( & self ) -> String {
56
format! ( " {} /{} " , self .owner, self .name)
57
}
58
}
59
60
# [ derive ( Clone, Debug, FromRow ) ]
61
pub struct PushRow {
62
pub id : i64 ,
63
pub at_ms : i64 ,
64
pub reference : String,
65
pub ok : bool ,
66
pub reason : String,
62d6e969 Andy McCall 1 month ago ↶
67
pub old_state : String,
68
pub new_state : String,
69
pub heads : i64 ,
70
pub changes : i64 ,
19bb31ff Andy McCall 1 month ago ↶
71
pub pusher : String,
62d6e969 Andy McCall 1 month ago ↶
72
}
73
74
75
# [ derive ( Clone, Debug ) ]
76
pub struct PushOutcome {
77
pub reference : String,
78
pub ok : bool ,
79
pub reason : String,
80
pub old_state : String,
81
pub new_state : String,
82
pub heads : i64 ,
83
pub changes : i64 ,
d23464c0 Andy McCall 1 month ago ↶
84
}
85
86
pub async fn list ( pool : & Pool) -> sqlx:: Result< Vec< RepoRow> > {
87
sqlx:: query_as( " SELECT * FROM repositories ORDER BY updated_ms DESC, owner, name" ) .fetch_all ( pool) .await
88
}
89
19bb31ff Andy McCall 1 month ago ↶
90
pub async fn get_by_id ( pool : & Pool, id : i64 ) -> sqlx:: Result< Option< RepoRow> > {
91
sqlx:: query_as( " SELECT * FROM repositories WHERE id = ?" ) .bind ( id) .fetch_optional ( pool) .await
92
}
93
d23464c0 Andy McCall 1 month ago ↶
94
pub async fn list_by_owner ( pool : & Pool, owner : & str ) -> sqlx:: Result< Vec< RepoRow> > {
95
sqlx:: query_as( " SELECT * FROM repositories WHERE owner = ? ORDER BY updated_ms DESC, name" ) .bind ( owner) .fetch_all ( pool) .await
96
}
97
1ac84c62 Andy McCall 1 month ago ↶
98
d23464c0 Andy McCall 1 month ago ↶
99
pub async fn get ( pool : & Pool, owner : & str , name : & str ) -> sqlx:: Result< Option< RepoRow> > {
1ac84c62 Andy McCall 1 month ago ↶
100
sqlx:: query_as(
101
" SELECT r.*, \
102
(SELECT COUNT(*) FROM issues i WHERE i.repo_id = r.id AND i.state = 'open') AS open_issues, \
103
(SELECT COUNT(*) FROM proposals p WHERE p.repo_id = r.id AND p.state IN ('open', 'draft')) AS open_proposals \
104
FROM repositories r WHERE r.owner = ? AND r.name = ?" ,
105
)
106
.bind ( owner)
107
.bind ( name)
108
.fetch_optional ( pool)
109
.await
d23464c0 Andy McCall 1 month ago ↶
110
}
111
ff656953 Andy McCall 1 month ago ↶
112
113
114
d23464c0 Andy McCall 1 month ago ↶
115
pub async fn create ( pool : & Pool, owner : & str , name : & str , description : & str , visibility : Visibility) -> sqlx:: Result< RepoRow> {
116
let now = now_ms ( ) ;
117
sqlx:: query_as(
462f88d1 Andy McCall 1 month ago ↶
118
" INSERT INTO repositories (owner, name, description, visibility, created_ms, updated_ms, required_approvals) \
ff656953 Andy McCall 1 month ago ↶
119
VALUES (?, ?, ?, ?, ?, ?, (SELECT COUNT(*) >= 2 FROM org_members m JOIN organisations o ON o.id = m.org_id WHERE o.name = ?)) RETURNING *" ,
d23464c0 Andy McCall 1 month ago ↶
120
)
121
.bind ( owner)
122
.bind ( name)
123
.bind ( description)
124
.bind ( visibility.as_str ( ) )
125
.bind ( now)
126
.bind ( now)
462f88d1 Andy McCall 1 month ago ↶
127
.bind ( owner)
d23464c0 Andy McCall 1 month ago ↶
128
.fetch_one ( pool)
129
.await
d0fbea2e Andy McCall 1 month ago ↶
130
}
131
132
133
pub async fn moved_to ( pool : & Pool, owner : & str , name : & str ) -> sqlx:: Result< Option< RepoRow> > {
134
let id: Option< i64 > = sqlx:: query_scalar( " SELECT repo_id FROM repo_redirects WHERE old_owner = ? AND old_name = ?" )
135
.bind ( owner)
136
.bind ( name)
137
.fetch_optional ( pool)
138
.await? ;
139
match id {
140
Some ( id) => get_by_id ( pool, id) .await,
141
None => Ok ( None ) ,
142
}
143
}
144
145
146
147
148
pub async fn transfer ( pool : & Pool, id : i64 , old_owner : & str , name : & str , new_owner : & str ) -> sqlx:: Result< ( ) > {
149
sqlx:: query( " UPDATE repositories SET owner = ?, updated_ms = ? WHERE id = ?" )
150
.bind ( new_owner)
151
.bind ( now_ms ( ) )
152
.bind ( id)
153
.execute ( pool)
154
.await? ;
155
sqlx:: query( " DELETE FROM repo_redirects WHERE old_owner = ? AND old_name = ?" ) .bind ( new_owner) .bind ( name) .execute ( pool) .await? ;
156
sqlx:: query( " INSERT OR REPLACE INTO repo_redirects (old_owner, old_name, repo_id, moved_ms) VALUES (?, ?, ?, ?)" )
157
.bind ( old_owner)
158
.bind ( name)
159
.bind ( id)
160
.bind ( now_ms ( ) )
161
.execute ( pool)
162
.await? ;
163
Ok ( ( ) )
164
}
165
166
167
pub async fn set_archived ( pool : & Pool, id : i64 , archived : bool , reason : & str ) -> sqlx:: Result< ( ) > {
168
sqlx:: query( " UPDATE repositories SET archived = ?, archived_reason = ?, updated_ms = ? WHERE id = ?" )
169
.bind ( archived)
170
.bind ( if archived { reason } else { " " } )
171
.bind ( now_ms ( ) )
172
.bind ( id)
173
.execute ( pool)
174
.await? ;
175
Ok ( ( ) )
d23464c0 Andy McCall 1 month ago ↶
176
}
177
178
pub async fn delete ( pool : & Pool, id : i64 ) -> sqlx:: Result< ( ) > {
179
sqlx:: query( " DELETE FROM repositories WHERE id = ?" ) .bind ( id) .execute ( pool) .await? ;
180
Ok ( ( ) )
181
}
182
183
pub async fn set_description ( pool : & Pool, id : i64 , description : & str ) -> sqlx:: Result< ( ) > {
184
sqlx:: query( " UPDATE repositories SET description = ? WHERE id = ?" ) .bind ( description) .bind ( id) .execute ( pool) .await? ;
185
Ok ( ( ) )
186
}
187
188
pub async fn set_default_bookmark ( pool : & Pool, id : i64 , bookmark : & str ) -> sqlx:: Result< ( ) > {
189
sqlx:: query( " UPDATE repositories SET default_bookmark = ? WHERE id = ?" ) .bind ( bookmark) .bind ( id) .execute ( pool) .await? ;
190
Ok ( ( ) )
191
}
192
193
pub async fn set_visibility ( pool : & Pool, id : i64 , visibility : Visibility) -> sqlx:: Result< ( ) > {
194
sqlx:: query( " UPDATE repositories SET visibility = ? WHERE id = ?" ) .bind ( visibility.as_str ( ) ) .bind ( id) .execute ( pool) .await? ;
195
Ok ( ( ) )
196
}
197
198
19bb31ff Andy McCall 1 month ago ↶
199
pub async fn record_push ( pool : & Pool, id : i64 , pusher : Option< ( i64 , & str ) > , results : & [PushOutcome]) -> sqlx:: Result< ( ) > {
d23464c0 Andy McCall 1 month ago ↶
200
let now = now_ms ( ) ;
941abdd6 Andy McCall 1 month ago ↶
201
let mut tx = super :: begin_write( pool) .await? ;
62d6e969 Andy McCall 1 month ago ↶
202
for r in results {
19bb31ff Andy McCall 1 month ago ↶
203
sqlx:: query( " INSERT INTO pushes (repo_id, at_ms, reference, ok, reason, old_state, new_state, heads, changes, account_id, pusher) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)" )
d23464c0 Andy McCall 1 month ago ↶
204
.bind ( id)
205
.bind ( now)
62d6e969 Andy McCall 1 month ago ↶
206
.bind ( & r.reference)
207
.bind ( r.ok)
208
.bind ( & r.reason)
209
.bind ( & r.old_state)
210
.bind ( & r.new_state)
211
.bind ( r.heads)
212
.bind ( r.changes)
19bb31ff Andy McCall 1 month ago ↶
213
.bind ( pusher.map ( | p | p.0 ) .filter ( | id | * id > 0 ) )
214
.bind ( pusher.map ( | p | p.1 ) .unwrap_or ( " " ) )
d23464c0 Andy McCall 1 month ago ↶
215
.execute ( & mut * tx)
216
.await? ;
217
}
62d6e969 Andy McCall 1 month ago ↶
218
if results.iter ( ) .any ( | r | r.ok ) {
d23464c0 Andy McCall 1 month ago ↶
219
sqlx:: query( " UPDATE repositories SET updated_ms = ? WHERE id = ?" ) .bind ( now) .bind ( id) .execute ( & mut * tx) .await? ;
220
}
221
tx.commit ( ) .await
222
}
223
224
pub async fn pushes ( pool : & Pool, id : i64 , limit : i64 ) -> sqlx:: Result< Vec< PushRow> > {
19bb31ff Andy McCall 1 month ago ↶
225
sqlx:: query_as( " SELECT id, at_ms, reference, ok, reason, old_state, new_state, heads, changes, pusher FROM pushes WHERE repo_id = ? ORDER BY at_ms DESC, id DESC LIMIT ?" )
d23464c0 Andy McCall 1 month ago ↶
226
.bind ( id)
227
.bind ( limit)
228
.fetch_all ( pool)
229
.await
230
}
3f4b0ea6 Andy McCall 1 month ago ↶
231
1553bfc9 Andy McCall 1 month ago ↶
232
233
234
235
pub fn parse_topics ( input : & str ) -> Result< String, String> {
236
let mut out: Vec< String> = Vec :: new( ) ;
237
for t in input.split ( [ ' ,' , ' ' , ' \n ' , ' \t ' ] ) .map ( | t | t.trim ( ) .to_ascii_lowercase ( ) ) .filter ( | t | ! t.is_empty ( ) ) {
238
if t.len ( ) > 35 | | ! t.chars ( ) .all ( | c | c.is_ascii_alphanumeric ( ) | | c = = ' -' ) | | t.starts_with ( ' -' ) {
239
return Err ( format! ( " \" {t} \" is not a topic: use letters, digits and dashes, up to 35 characters" ) ) ;
240
}
241
if ! out.contains ( & t) {
242
out.push ( t) ;
243
}
244
}
245
if out.len ( ) > 20 {
246
return Err ( " At most 20 topics." .into ( ) ) ;
247
}
248
Ok ( out.join ( " " ) )
249
}
250
251
pub async fn set_topics ( pool : & Pool, id : i64 , topics : & str ) -> sqlx:: Result< ( ) > {
252
sqlx:: query( " UPDATE repositories SET topics = ? WHERE id = ?" ) .bind ( topics) .bind ( id) .execute ( pool) .await? ;
253
Ok ( ( ) )
254
}
255
256
257
pub async fn toggle_star ( pool : & Pool, account_id : i64 , repo_id : i64 ) -> sqlx:: Result< bool > {
258
let gone = sqlx:: query( " DELETE FROM stars WHERE account_id = ? AND repo_id = ?" ) .bind ( account_id) .bind ( repo_id) .execute ( pool) .await? ;
259
if gone.rows_affected ( ) > 0 {
260
return Ok ( false ) ;
261
}
262
sqlx:: query( " INSERT INTO stars (account_id, repo_id, at_ms) VALUES (?, ?, ?)" )
263
.bind ( account_id)
264
.bind ( repo_id)
265
.bind ( now_ms ( ) )
266
.execute ( pool)
267
.await? ;
268
Ok ( true )
269
}
270
271
pub async fn starred ( pool : & Pool, account_id : i64 , repo_id : i64 ) -> sqlx:: Result< bool > {
272
let n: i64 = sqlx:: query_scalar( " SELECT COUNT(*) FROM stars WHERE account_id = ? AND repo_id = ?" )
273
.bind ( account_id)
274
.bind ( repo_id)
275
.fetch_one ( pool)
276
.await? ;
277
Ok ( n > 0 )
278
}
279
280
281
pub async fn counts ( pool : & Pool, repo_id : i64 ) -> sqlx:: Result< ( i64 , i64 ) > {
282
let stars: i64 = sqlx:: query_scalar( " SELECT COUNT(*) FROM stars WHERE repo_id = ?" ) .bind ( repo_id) .fetch_one ( pool) .await? ;
283
let watchers: i64 = sqlx:: query_scalar( " SELECT COUNT(*) FROM watches WHERE repo_id = ?" ) .bind ( repo_id) .fetch_one ( pool) .await? ;
284
Ok ( ( stars, watchers) )
285
}
286
287
288
pub async fn star_counts ( pool : & Pool) -> sqlx:: Result< std:: collections:: HashMap< i64 , i64 > > {
289
let rows: Vec< ( i64 , i64 ) > = sqlx:: query_as( " SELECT repo_id, COUNT(*) FROM stars GROUP BY repo_id" ) .fetch_all ( pool) .await? ;
290
Ok ( rows.into_iter ( ) .collect ( ) )
291
}
292
07b1a5bb Andy McCall 1 month ago ↶
293
294
pub async fn set_forked_from ( pool : & Pool, id : i64 , from : i64 ) -> sqlx:: Result< ( ) > {
295
sqlx:: query( " UPDATE repositories SET forked_from = ? WHERE id = ?" ) .bind ( from) .bind ( id) .execute ( pool) .await? ;
296
Ok ( ( ) )
297
}
298
299
300
pub async fn forks_of ( pool : & Pool, id : i64 ) -> sqlx:: Result< Vec< RepoRow> > {
301
sqlx:: query_as( " SELECT * FROM repositories WHERE forked_from = ? ORDER BY updated_ms DESC" ) .bind ( id) .fetch_all ( pool) .await
9008bf6c Andy McCall 1 month ago ↶
302
}
303
304
305
pub async fn set_flow ( pool : & Pool, id : i64 , setting : & str ) -> sqlx:: Result< ( ) > {
306
sqlx:: query( " UPDATE repositories SET flow = ? WHERE id = ?" ) .bind ( setting) .bind ( id) .execute ( pool) .await? ;
e7158350 Andy McCall 23 days ago ↶
307
Ok ( ( ) )
308
}
309
310
pub async fn set_diffusion ( pool : & Pool, id : i64 , setting : & str ) -> sqlx:: Result< ( ) > {
311
sqlx:: query( " UPDATE repositories SET diffusion = ? WHERE id = ?" ) .bind ( setting) .bind ( id) .execute ( pool) .await? ;
9008bf6c Andy McCall 1 month ago ↶
312
Ok ( ( ) )
07b1a5bb Andy McCall 1 month ago ↶
313
}
314
3f4b0ea6 Andy McCall 1 month ago ↶
315
pub async fn set_git_mirror ( pool : & Pool, id : i64 , on : bool ) -> sqlx:: Result< ( ) > {
316
sqlx:: query( " UPDATE repositories SET git_mirror = ? WHERE id = ?" ) .bind ( on) .bind ( id) .execute ( pool) .await? ;
317
Ok ( ( ) )
318
}
319
320
pub async fn set_git_mirror_state ( pool : & Pool, id : i64 , state : & str ) -> sqlx:: Result< ( ) > {
321
sqlx:: query( " UPDATE repositories SET git_mirror_state = ? WHERE id = ?" ) .bind ( state) .bind ( id) .execute ( pool) .await? ;
322
Ok ( ( ) )
323
}
1553bfc9 Andy McCall 1 month ago ↶
324
325
# [ cfg ( test ) ]
326
mod topic_tests {
327
use super :: parse_topics;
328
329
# [ test ]
330
fn topics_are_normalised_and_checked ( ) {
331
assert_eq! ( parse_topics ( " Rust, vcs retro-computing,rust" ) .unwrap ( ) , " rust vcs retro-computing" ) ;
332
assert_eq! ( parse_topics ( " " ) .unwrap ( ) , " " ) ;
333
assert! ( parse_topics ( " c++" ) .is_err ( ) ) ;
334
assert! ( parse_topics ( " -lead" ) .is_err ( ) ) ;
335
assert! ( parse_topics ( & " x" .repeat ( 36 ) ) .is_err ( ) ) ;
336
assert! ( parse_topics ( & ( 0 .. 21 ) .map ( | i | format! ( " t{i} " ) ) .collect:: < Vec< _ > > ( ) .join ( " " ) ) .is_err ( ) ) ;
337
}
338
}