19bb31ff Andy McCall 1 month ago ↶
1
2
3
4
use super :: { now_ms, Pool} ;
5
use sqlx:: FromRow;
6
7
# [ derive ( Clone, Debug, FromRow ) ]
8
pub struct Account {
9
pub id : i64 ,
10
pub username : String,
11
pub display_name : String,
12
pub email : String,
13
pub email_verified : bool ,
14
pub password_hash : String,
15
pub session_salt : String,
16
pub totp_secret : Option< String> ,
17
pub recovery_codes : String,
18
pub is_admin : bool ,
19
pub banned : bool ,
20
pub created_ms : i64 ,
21
pub last_seen_ms : i64 ,
18e381ea Andy McCall 1 month ago ↶
22
pub email_notifications : bool ,
23
pub unsubscribe_key : String,
10174045 Andy McCall 1 month ago ↶
24
25
#[sqlx(default)]
26
pub bio : String,
27
#[sqlx(default)]
28
pub website : String,
29
#[sqlx(default)]
30
pub location : String,
19bb31ff Andy McCall 1 month ago ↶
31
}
32
33
impl Account {
34
pub fn name ( & self ) -> & str {
35
if self .display_name.is_empty ( ) {
36
& self .username
37
} else {
38
& self .display_name
39
}
40
}
41
42
pub fn totp_enabled ( & self ) -> bool {
43
self .totp_secret.is_some ( )
44
}
45
}
46
47
# [ derive ( Clone, Debug, FromRow ) ]
48
pub struct Organisation {
49
pub id : i64 ,
50
pub name : String,
51
pub display_name : String,
52
pub description : String,
53
pub default_role : String,
54
pub created_ms : i64 ,
55
}
56
57
# [ derive ( Clone, Debug, FromRow ) ]
58
pub struct Team {
59
pub id : i64 ,
60
pub org_id : i64 ,
61
pub name : String,
62
}
63
64
# [ derive ( Clone, Debug, FromRow ) ]
65
pub struct Token {
66
pub id : i64 ,
67
pub account_id : i64 ,
68
pub name : String,
69
pub hash : String,
70
pub scopes : String,
71
pub repos : String,
72
pub created_ms : i64 ,
73
pub expires_ms : Option< i64 > ,
74
pub last_used_ms : Option< i64 > ,
75
}
76
77
impl Token {
78
pub fn scope_list ( & self ) -> Vec< & str > {
79
self .scopes.split ( ' ,' ) .filter ( | s | ! s.is_empty ( ) ) .collect ( )
80
}
81
82
pub fn has_scope ( & self , scope : & str ) -> bool {
83
self .scope_list ( ) .contains ( & scope)
84
}
85
86
87
88
pub fn covers ( & self , slug : & str ) -> bool {
89
self .repos.is_empty ( ) | | self .repos.split ( ' ,' ) .any ( | r | r.trim ( ) .eq_ignore_ascii_case ( slug) )
90
}
91
92
pub fn expired ( & self , now : i64 ) -> bool {
93
self .expires_ms.is_some_and ( | e | e < now )
94
}
95
}
96
97
# [ derive ( Clone, Debug, FromRow ) ]
98
pub struct LinkedIdentity {
99
pub key : String,
100
pub account_id : i64 ,
101
pub name : String,
102
pub email : String,
103
pub object : Vec< u8 > ,
104
pub linked_ms : i64 ,
105
}
106
107
# [ derive ( Clone, Debug, FromRow ) ]
108
pub struct AuditRow {
109
pub id : i64 ,
110
pub at_ms : i64 ,
111
pub actor : String,
112
pub action : String,
113
pub target : String,
114
pub detail : String,
115
}
116
117
118
119
pub async fn account_by_name ( pool : & Pool, username : & str ) -> sqlx:: Result< Option< Account> > {
120
sqlx:: query_as( " SELECT * FROM accounts WHERE username = ?" ) .bind ( username) .fetch_optional ( pool) .await
121
}
122
123
pub async fn account_by_id ( pool : & Pool, id : i64 ) -> sqlx:: Result< Option< Account> > {
124
sqlx:: query_as( " SELECT * FROM accounts WHERE id = ?" ) .bind ( id) .fetch_optional ( pool) .await
125
}
126
127
pub async fn accounts ( pool : & Pool) -> sqlx:: Result< Vec< Account> > {
128
sqlx:: query_as( " SELECT * FROM accounts ORDER BY username" ) .fetch_all ( pool) .await
129
}
130
131
pub async fn account_count ( pool : & Pool) -> sqlx:: Result< i64 > {
132
sqlx:: query_scalar( " SELECT COUNT(*) FROM accounts" ) .fetch_one ( pool) .await
133
}
134
135
pub async fn create_account (
136
pool : & Pool,
137
username : & str ,
138
email : & str ,
139
password_hash : & str ,
140
session_salt : & str ,
141
is_admin : bool ,
142
) -> sqlx:: Result< Account> {
143
sqlx:: query_as(
144
" INSERT INTO accounts (username, email, password_hash, session_salt, is_admin, created_ms) VALUES (?, ?, ?, ?, ?, ?) RETURNING *" ,
145
)
146
.bind ( username)
147
.bind ( email)
148
.bind ( password_hash)
149
.bind ( session_salt)
150
.bind ( is_admin)
151
.bind ( now_ms ( ) )
152
.fetch_one ( pool)
153
.await
154
}
155
156
pub async fn set_profile ( pool : & Pool, id : i64 , display_name : & str , email : & str ) -> sqlx:: Result< ( ) > {
157
sqlx:: query( " UPDATE accounts SET display_name = ?, email = ? WHERE id = ?" )
158
.bind ( display_name)
159
.bind ( email)
160
.bind ( id)
161
.execute ( pool)
162
.await? ;
163
Ok ( ( ) )
164
}
165
10174045 Andy McCall 1 month ago ↶
166
167
pub async fn set_bio ( pool : & Pool, id : i64 , bio : & str , website : & str , location : & str ) -> sqlx:: Result< ( ) > {
168
sqlx:: query( " UPDATE accounts SET bio = ?, website = ?, location = ? WHERE id = ?" )
169
.bind ( bio)
170
.bind ( website)
171
.bind ( location)
172
.bind ( id)
173
.execute ( pool)
174
.await? ;
175
Ok ( ( ) )
176
}
177
19bb31ff Andy McCall 1 month ago ↶
178
pub async fn set_password ( pool : & Pool, id : i64 , password_hash : & str , session_salt : & str ) -> sqlx:: Result< ( ) > {
179
sqlx:: query( " UPDATE accounts SET password_hash = ?, session_salt = ? WHERE id = ?" )
180
.bind ( password_hash)
181
.bind ( session_salt)
182
.bind ( id)
183
.execute ( pool)
184
.await? ;
185
Ok ( ( ) )
186
}
187
188
pub async fn set_session_salt ( pool : & Pool, id : i64 , session_salt : & str ) -> sqlx:: Result< ( ) > {
189
sqlx:: query( " UPDATE accounts SET session_salt = ? WHERE id = ?" ) .bind ( session_salt) .bind ( id) .execute ( pool) .await? ;
190
Ok ( ( ) )
191
}
192
193
pub async fn set_totp ( pool : & Pool, id : i64 , secret : Option< & str > , recovery_codes : & str ) -> sqlx:: Result< ( ) > {
194
sqlx:: query( " UPDATE accounts SET totp_secret = ?, recovery_codes = ? WHERE id = ?" )
195
.bind ( secret)
196
.bind ( recovery_codes)
197
.bind ( id)
198
.execute ( pool)
199
.await? ;
200
Ok ( ( ) )
201
}
202
203
pub async fn set_admin ( pool : & Pool, id : i64 , is_admin : bool ) -> sqlx:: Result< ( ) > {
204
sqlx:: query( " UPDATE accounts SET is_admin = ? WHERE id = ?" ) .bind ( is_admin) .bind ( id) .execute ( pool) .await? ;
205
Ok ( ( ) )
206
}
207
208
pub async fn set_banned ( pool : & Pool, id : i64 , banned : bool ) -> sqlx:: Result< ( ) > {
209
sqlx:: query( " UPDATE accounts SET banned = ? WHERE id = ?" ) .bind ( banned) .bind ( id) .execute ( pool) .await? ;
210
Ok ( ( ) )
211
}
212
213
pub async fn touch_seen ( pool : & Pool, id : i64 ) -> sqlx:: Result< ( ) > {
214
sqlx:: query( " UPDATE accounts SET last_seen_ms = ? WHERE id = ?" ) .bind ( now_ms ( ) ) .bind ( id) .execute ( pool) .await? ;
215
Ok ( ( ) )
216
}
217
218
219
220
pub async fn org_by_name ( pool : & Pool, name : & str ) -> sqlx:: Result< Option< Organisation> > {
221
sqlx:: query_as( " SELECT * FROM organisations WHERE name = ?" ) .bind ( name) .fetch_optional ( pool) .await
222
}
223
224
pub async fn orgs ( pool : & Pool) -> sqlx:: Result< Vec< Organisation> > {
225
sqlx:: query_as( " SELECT * FROM organisations ORDER BY name" ) .fetch_all ( pool) .await
226
}
227
228
pub async fn orgs_of ( pool : & Pool, account_id : i64 ) -> sqlx:: Result< Vec< ( Organisation, String) > > {
229
let rows: Vec< ( i64 , String, String, String, String, i64 , String) > = sqlx:: query_as(
230
" SELECT o.id, o.name, o.display_name, o.description, o.default_role, o.created_ms, m.role FROM organisations o JOIN org_members m ON m.org_id = o.id WHERE m.account_id = ? ORDER BY o.name" ,
231
)
232
.bind ( account_id)
233
.fetch_all ( pool)
234
.await? ;
235
Ok ( rows
236
.into_iter ( )
237
.map ( | ( id , name , display_name , description , default_role , created_ms , role ) | {
238
( Organisation { id, name, display_name, description, default_role, created_ms } , role)
239
} )
240
.collect ( ) )
241
}
242
243
pub async fn create_org ( pool : & Pool, name : & str , display_name : & str , owner : i64 ) -> sqlx:: Result< Organisation> {
244
let mut tx = pool.begin ( ) .await? ;
245
let org: Organisation = sqlx:: query_as( " INSERT INTO organisations (name, display_name, created_ms) VALUES (?, ?, ?) RETURNING *" )
246
.bind ( name)
247
.bind ( display_name)
248
.bind ( now_ms ( ) )
249
.fetch_one ( & mut * tx)
250
.await? ;
251
sqlx:: query( " INSERT INTO org_members (org_id, account_id, role) VALUES (?, ?, 'owner')" )
252
.bind ( org.id)
253
.bind ( owner)
254
.execute ( & mut * tx)
255
.await? ;
256
tx.commit ( ) .await? ;
257
Ok ( org)
258
}
259
260
pub async fn set_org ( pool : & Pool, id : i64 , display_name : & str , description : & str , default_role : & str ) -> sqlx:: Result< ( ) > {
261
sqlx:: query( " UPDATE organisations SET display_name = ?, description = ?, default_role = ? WHERE id = ?" )
262
.bind ( display_name)
263
.bind ( description)
264
.bind ( default_role)
265
.bind ( id)
266
.execute ( pool)
267
.await? ;
268
Ok ( ( ) )
269
}
270
271
272
pub async fn org_members ( pool : & Pool, org_id : i64 ) -> sqlx:: Result< Vec< ( Account, String) > > {
273
let rows: Vec< ( i64 , String) > =
274
sqlx:: query_as( " SELECT account_id, role FROM org_members WHERE org_id = ?" ) .bind ( org_id) .fetch_all ( pool) .await? ;
275
let mut out = Vec :: new( ) ;
276
for ( id, role) in rows {
277
if let Some ( a) = account_by_id ( pool, id) .await? {
278
out.push ( ( a, role) ) ;
279
}
280
}
281
out.sort_by ( | a , b | a.0. username.cmp ( & b.0. username) ) ;
282
Ok ( out)
283
}
284
285
pub async fn org_role ( pool : & Pool, org_id : i64 , account_id : i64 ) -> sqlx:: Result< Option< String> > {
286
sqlx:: query_scalar( " SELECT role FROM org_members WHERE org_id = ? AND account_id = ?" )
287
.bind ( org_id)
288
.bind ( account_id)
289
.fetch_optional ( pool)
290
.await
291
}
292
293
pub async fn set_org_member ( pool : & Pool, org_id : i64 , account_id : i64 , role : & str ) -> sqlx:: Result< ( ) > {
294
sqlx:: query( " INSERT INTO org_members (org_id, account_id, role) VALUES (?, ?, ?) ON CONFLICT (org_id, account_id) DO UPDATE SET role = excluded.role" )
295
.bind ( org_id)
296
.bind ( account_id)
297
.bind ( role)
298
.execute ( pool)
299
.await? ;
300
Ok ( ( ) )
301
}
302
303
pub async fn remove_org_member ( pool : & Pool, org_id : i64 , account_id : i64 ) -> sqlx:: Result< ( ) > {
304
sqlx:: query( " DELETE FROM org_members WHERE org_id = ? AND account_id = ?" ) .bind ( org_id) .bind ( account_id) .execute ( pool) .await? ;
305
Ok ( ( ) )
306
}
307
308
pub async fn teams ( pool : & Pool, org_id : i64 ) -> sqlx:: Result< Vec< Team> > {
309
sqlx:: query_as( " SELECT * FROM teams WHERE org_id = ? ORDER BY name" ) .bind ( org_id) .fetch_all ( pool) .await
310
}
311
312
pub async fn team_by_name ( pool : & Pool, org_id : i64 , name : & str ) -> sqlx:: Result< Option< Team> > {
313
sqlx:: query_as( " SELECT * FROM teams WHERE org_id = ? AND name = ?" ) .bind ( org_id) .bind ( name) .fetch_optional ( pool) .await
314
}
315
316
pub async fn create_team ( pool : & Pool, org_id : i64 , name : & str ) -> sqlx:: Result< Team> {
317
sqlx:: query_as( " INSERT INTO teams (org_id, name) VALUES (?, ?) RETURNING *" ) .bind ( org_id) .bind ( name) .fetch_one ( pool) .await
318
}
319
320
pub async fn delete_team ( pool : & Pool, id : i64 ) -> sqlx:: Result< ( ) > {
321
sqlx:: query( " DELETE FROM teams WHERE id = ?" ) .bind ( id) .execute ( pool) .await? ;
322
Ok ( ( ) )
323
}
324
325
pub async fn team_members ( pool : & Pool, team_id : i64 ) -> sqlx:: Result< Vec< Account> > {
326
sqlx:: query_as( " SELECT a.* FROM accounts a JOIN team_members t ON t.account_id = a.id WHERE t.team_id = ? ORDER BY a.username" )
327
.bind ( team_id)
328
.fetch_all ( pool)
329
.await
330
}
331
332
pub async fn add_team_member ( pool : & Pool, team_id : i64 , account_id : i64 ) -> sqlx:: Result< ( ) > {
333
sqlx:: query( " INSERT OR IGNORE INTO team_members (team_id, account_id) VALUES (?, ?)" )
334
.bind ( team_id)
335
.bind ( account_id)
336
.execute ( pool)
337
.await? ;
338
Ok ( ( ) )
339
}
340
341
pub async fn remove_team_member ( pool : & Pool, team_id : i64 , account_id : i64 ) -> sqlx:: Result< ( ) > {
342
sqlx:: query( " DELETE FROM team_members WHERE team_id = ? AND account_id = ?" ) .bind ( team_id) .bind ( account_id) .execute ( pool) .await? ;
343
Ok ( ( ) )
344
}
345
346
347
348
# [ derive ( Clone, Debug ) ]
349
pub struct Collaborator {
350
pub account : Option< Account> ,
351
pub team : Option< Team> ,
352
pub role : String,
353
}
354
355
pub async fn collaborators ( pool : & Pool, repo_id : i64 ) -> sqlx:: Result< Vec< Collaborator> > {
356
let rows: Vec< ( Option< i64 > , Option< i64 > , String) > =
357
sqlx:: query_as( " SELECT account_id, team_id, role FROM collaborators WHERE repo_id = ?" ) .bind ( repo_id) .fetch_all ( pool) .await? ;
358
let mut out = Vec :: new( ) ;
359
for ( account_id, team_id, role) in rows {
360
let account = match account_id {
361
Some ( id) => account_by_id ( pool, id) .await? ,
362
None => None ,
363
} ;
364
let team = match team_id {
365
Some ( id) => sqlx:: query_as( " SELECT * FROM teams WHERE id = ?" ) .bind ( id) .fetch_optional ( pool) .await? ,
366
None => None ,
367
} ;
368
out.push ( Collaborator { account, team, role } ) ;
369
}
370
Ok ( out)
371
}
372
373
pub async fn set_collaborator ( pool : & Pool, repo_id : i64 , account_id : Option< i64 > , team_id : Option< i64 > , role : & str ) -> sqlx:: Result< ( ) > {
374
if let Some ( a) = account_id {
375
sqlx:: query( " INSERT INTO collaborators (repo_id, account_id, role) VALUES (?, ?, ?) ON CONFLICT (repo_id, account_id) WHERE account_id IS NOT NULL DO UPDATE SET role = excluded.role" )
376
.bind ( repo_id)
377
.bind ( a)
378
.bind ( role)
379
.execute ( pool)
380
.await? ;
381
} else if let Some ( t) = team_id {
382
sqlx:: query( " INSERT INTO collaborators (repo_id, team_id, role) VALUES (?, ?, ?) ON CONFLICT (repo_id, team_id) WHERE team_id IS NOT NULL DO UPDATE SET role = excluded.role" )
383
.bind ( repo_id)
384
.bind ( t)
385
.bind ( role)
386
.execute ( pool)
387
.await? ;
388
}
389
Ok ( ( ) )
390
}
391
392
pub async fn remove_collaborator ( pool : & Pool, repo_id : i64 , account_id : Option< i64 > , team_id : Option< i64 > ) -> sqlx:: Result< ( ) > {
393
sqlx:: query( " DELETE FROM collaborators WHERE repo_id = ? AND ((account_id IS NOT NULL AND account_id = ?) OR (team_id IS NOT NULL AND team_id = ?))" )
394
.bind ( repo_id)
395
.bind ( account_id)
396
.bind ( team_id)
397
.execute ( pool)
398
.await? ;
399
Ok ( ( ) )
400
}
401
402
403
pub async fn collaborator_role ( pool : & Pool, repo_id : i64 , account_id : i64 ) -> sqlx:: Result< Option< String> > {
404
let rows: Vec< String> = sqlx:: query_scalar(
405
" SELECT role FROM collaborators WHERE repo_id = ? AND (account_id = ? OR team_id IN (SELECT team_id FROM team_members WHERE account_id = ?))" ,
406
)
407
.bind ( repo_id)
408
.bind ( account_id)
409
.bind ( account_id)
410
.fetch_all ( pool)
411
.await? ;
412
Ok ( rows.into_iter ( ) .max_by_key ( | r | crate :: auth:: Role:: parse( r) .map ( | r | r as u8 ) .unwrap_or ( 0 ) ) )
413
}
414
415
416
417
pub async fn tokens_of ( pool : & Pool, account_id : i64 ) -> sqlx:: Result< Vec< Token> > {
418
sqlx:: query_as( " SELECT * FROM tokens WHERE account_id = ? ORDER BY created_ms DESC" ) .bind ( account_id) .fetch_all ( pool) .await
419
}
420
421
pub async fn token_by_hash ( pool : & Pool, hash : & str ) -> sqlx:: Result< Option< Token> > {
422
sqlx:: query_as( " SELECT * FROM tokens WHERE hash = ?" ) .bind ( hash) .fetch_optional ( pool) .await
423
}
424
425
pub async fn create_token (
426
pool : & Pool,
427
account_id : i64 ,
428
name : & str ,
429
hash : & str ,
430
scopes : & str ,
431
repos : & str ,
432
expires_ms : Option< i64 > ,
433
) -> sqlx:: Result< Token> {
434
sqlx:: query_as(
435
" INSERT INTO tokens (account_id, name, hash, scopes, repos, created_ms, expires_ms) VALUES (?, ?, ?, ?, ?, ?, ?) RETURNING *" ,
436
)
437
.bind ( account_id)
438
.bind ( name)
439
.bind ( hash)
440
.bind ( scopes)
441
.bind ( repos)
442
.bind ( now_ms ( ) )
443
.bind ( expires_ms)
444
.fetch_one ( pool)
445
.await
446
}
447
448
pub async fn delete_token ( pool : & Pool, account_id : i64 , id : i64 ) -> sqlx:: Result< ( ) > {
449
sqlx:: query( " DELETE FROM tokens WHERE id = ? AND account_id = ?" ) .bind ( id) .bind ( account_id) .execute ( pool) .await? ;
450
Ok ( ( ) )
451
}
452
453
pub async fn touch_token ( pool : & Pool, id : i64 ) -> sqlx:: Result< ( ) > {
454
sqlx:: query( " UPDATE tokens SET last_used_ms = ? WHERE id = ?" ) .bind ( now_ms ( ) ) .bind ( id) .execute ( pool) .await? ;
455
Ok ( ( ) )
456
}
457
458
459
460
pub async fn identities_of ( pool : & Pool, account_id : i64 ) -> sqlx:: Result< Vec< LinkedIdentity> > {
461
sqlx:: query_as( " SELECT * FROM identities WHERE account_id = ? ORDER BY linked_ms DESC" ) .bind ( account_id) .fetch_all ( pool) .await
462
}
463
464
pub async fn identity_by_key ( pool : & Pool, key : & str ) -> sqlx:: Result< Option< LinkedIdentity> > {
465
sqlx:: query_as( " SELECT * FROM identities WHERE key = ?" ) .bind ( key) .fetch_optional ( pool) .await
466
}
467
468
469
pub async fn identity_accounts ( pool : & Pool) -> sqlx:: Result< Vec< ( String, String) > > {
470
sqlx:: query_as( " SELECT i.key, a.username FROM identities i JOIN accounts a ON a.id = i.account_id" ) .fetch_all ( pool) .await
471
}
472
473
pub async fn link_identity ( pool : & Pool, account_id : i64 , key : & str , name : & str , email : & str , object : & [u8 ]) -> sqlx:: Result< ( ) > {
474
sqlx:: query( " INSERT INTO identities (key, account_id, name, email, object, linked_ms) VALUES (?, ?, ?, ?, ?, ?)" )
475
.bind ( key)
476
.bind ( account_id)
477
.bind ( name)
478
.bind ( email)
479
.bind ( object)
480
.bind ( now_ms ( ) )
481
.execute ( pool)
482
.await? ;
483
Ok ( ( ) )
484
}
485
486
pub async fn unlink_identity ( pool : & Pool, account_id : i64 , key : & str ) -> sqlx:: Result< ( ) > {
487
sqlx:: query( " DELETE FROM identities WHERE key = ? AND account_id = ?" ) .bind ( key) .bind ( account_id) .execute ( pool) .await? ;
488
Ok ( ( ) )
489
}
490
491
pub async fn create_challenge ( pool : & Pool, account_id : i64 , bytes_hex : & str ) -> sqlx:: Result< i64 > {
492
sqlx:: query( " DELETE FROM challenges WHERE created_ms < ?" ) .bind ( now_ms ( ) - 10 * 60 * 1000 ) .execute ( pool) .await? ;
493
let id: i64 = sqlx:: query_scalar( " INSERT INTO challenges (account_id, bytes, created_ms) VALUES (?, ?, ?) RETURNING id" )
494
.bind ( account_id)
495
.bind ( bytes_hex)
496
.bind ( now_ms ( ) )
497
.fetch_one ( pool)
498
.await? ;
499
Ok ( id)
500
}
501
502
503
pub async fn take_challenge ( pool : & Pool, account_id : i64 , id : i64 ) -> sqlx:: Result< Option< String> > {
504
let bytes: Option< String> = sqlx:: query_scalar( " SELECT bytes FROM challenges WHERE id = ? AND account_id = ? AND created_ms >= ?" )
505
.bind ( id)
506
.bind ( account_id)
507
.bind ( now_ms ( ) - 10 * 60 * 1000 )
508
.fetch_optional ( pool)
509
.await? ;
510
sqlx:: query( " DELETE FROM challenges WHERE id = ?" ) .bind ( id) .execute ( pool) .await? ;
511
Ok ( bytes)
512
}
513
514
515
516
pub async fn create_invitation ( pool : & Pool, code : & str , created_by : Option< i64 > ) -> sqlx:: Result< ( ) > {
517
sqlx:: query( " INSERT INTO invitations (code, created_by, created_ms) VALUES (?, ?, ?)" )
518
.bind ( code)
519
.bind ( created_by)
520
.bind ( now_ms ( ) )
521
.execute ( pool)
522
.await? ;
523
Ok ( ( ) )
524
}
525
526
pub async fn invitation_unused ( pool : & Pool, code : & str ) -> sqlx:: Result< bool > {
527
let n: i64 =
528
sqlx:: query_scalar( " SELECT COUNT(*) FROM invitations WHERE code = ? AND used_by IS NULL" ) .bind ( code) .fetch_one ( pool) .await? ;
529
Ok ( n > 0 )
530
}
531
532
pub async fn use_invitation ( pool : & Pool, code : & str , account_id : i64 ) -> sqlx:: Result< ( ) > {
533
sqlx:: query( " UPDATE invitations SET used_by = ? WHERE code = ?" ) .bind ( account_id) .bind ( code) .execute ( pool) .await? ;
534
Ok ( ( ) )
535
}
536
537
pub async fn invitations ( pool : & Pool) -> sqlx:: Result< Vec< ( String, Option< i64 > , i64 ) > > {
538
sqlx:: query_as( " SELECT code, used_by, created_ms FROM invitations ORDER BY created_ms DESC" ) .fetch_all ( pool) .await
539
}
540
541
542
543
pub async fn setting ( pool : & Pool, key : & str ) -> sqlx:: Result< Option< String> > {
544
sqlx:: query_scalar( " SELECT value FROM settings WHERE key = ?" ) .bind ( key) .fetch_optional ( pool) .await
545
}
546
547
pub async fn set_setting ( pool : & Pool, key : & str , value : & str ) -> sqlx:: Result< ( ) > {
548
sqlx:: query( " INSERT INTO settings (key, value) VALUES (?, ?) ON CONFLICT (key) DO UPDATE SET value = excluded.value" )
549
.bind ( key)
550
.bind ( value)
551
.execute ( pool)
552
.await? ;
553
Ok ( ( ) )
554
}
555
556
557
558
pub async fn audit ( pool : & Pool, actor : Option< & Account> , action : & str , target : & str , detail : & str ) -> sqlx:: Result< ( ) > {
559
sqlx:: query( " INSERT INTO audit (at_ms, actor_id, actor, action, target, detail) VALUES (?, ?, ?, ?, ?, ?)" )
560
.bind ( now_ms ( ) )
561
.bind ( actor.map ( | a | a.id ) )
562
.bind ( actor.map ( | a | a.username.as_str ( ) ) .unwrap_or ( " " ) )
563
.bind ( action)
564
.bind ( target)
565
.bind ( detail)
566
.execute ( pool)
567
.await? ;
568
Ok ( ( ) )
569
}
570
571
pub async fn audit_rows ( pool : & Pool, limit : i64 ) -> sqlx:: Result< Vec< AuditRow> > {
572
sqlx:: query_as( " SELECT id, at_ms, actor, action, target, detail FROM audit ORDER BY at_ms DESC, id DESC LIMIT ?" )
573
.bind ( limit)
574
.fetch_all ( pool)
575
.await
576
}
577
578
579
580
pub async fn visible_repo_ids ( pool : & Pool, account_id : i64 ) -> sqlx:: Result< Vec< i64 > > {
581
sqlx:: query_scalar(
582
" SELECT r.id FROM repositories r WHERE r.owner IN (SELECT username FROM accounts WHERE id = ?) \
583
OR r.owner IN (SELECT o.name FROM organisations o JOIN org_members m ON m.org_id = o.id WHERE m.account_id = ?) \
584
OR r.id IN (SELECT repo_id FROM collaborators WHERE account_id = ? OR team_id IN (SELECT team_id FROM team_members WHERE account_id = ?))" ,
585
)
586
.bind ( account_id)
587
.bind ( account_id)
588
.bind ( account_id)
589
.bind ( account_id)
590
.fetch_all ( pool)
591
.await
592
}