e1ec9fbf Andy McCall 1 month ago ↶
1
2
3
use super :: { now_ms, Pool} ;
4
use sqlx:: FromRow;
5
6
# [ derive ( Clone, Debug, FromRow ) ]
7
pub struct Project {
8
pub id : i64 ,
9
pub owner : String,
10
pub number : i64 ,
11
pub title : String,
12
pub description : String,
13
pub public : bool ,
14
pub closed : bool ,
15
pub created_ms : i64 ,
16
pub updated_ms : i64 ,
17
}
18
19
# [ derive ( Clone, Debug, FromRow ) ]
20
pub struct Column {
21
pub id : i64 ,
22
pub project_id : i64 ,
23
pub name : String,
24
pub position : i64 ,
439ecd87 Andy McCall 11 days ago ↶
25
26
pub archive_after_days : Option< i64 > ,
1000b40a Andy McCall 10 days ago ↶
27
28
29
pub takes_finished : bool ,
e1ec9fbf Andy McCall 1 month ago ↶
30
}
31
32
# [ derive ( Clone, Debug, FromRow ) ]
33
pub struct Card {
34
pub id : i64 ,
35
pub project_id : i64 ,
36
pub column_id : i64 ,
37
pub position : i64 ,
38
pub kind : String,
39
pub repo_id : Option< i64 > ,
40
pub number : Option< i64 > ,
41
pub note : String,
42
pub added_by : String,
43
pub created_ms : i64 ,
439ecd87 Andy McCall 11 days ago ↶
44
45
pub archived_ms : Option< i64 > ,
46
47
pub moved_ms : i64 ,
e1ec9fbf Andy McCall 1 month ago ↶
48
}
49
439ecd87 Andy McCall 11 days ago ↶
50
51
const DAY_MS : i64 = 86_400_000 ;
52
e1ec9fbf Andy McCall 1 month ago ↶
53
pub const DEFAULT_COLUMNS : [ & str ; 3 ] = [ " Backlog" , " Doing" , " Done" ] ;
54
55
pub async fn create ( pool : & Pool, owner : & str , title : & str , description : & str , public : bool ) -> sqlx:: Result< Project> {
941abdd6 Andy McCall 1 month ago ↶
56
let mut tx = super :: begin_write( pool) .await? ;
e1ec9fbf Andy McCall 1 month ago ↶
57
let number: i64 =
5d90e0e0 Andy McCall 20 days ago
58
sqlx:: query_scalar( " SELECT COALESCE(MAX(number), 0) + 1 FROM projects WHERE owner = $1" ) .bind ( owner) .fetch_one ( & mut * tx) .await? ;
e1ec9fbf Andy McCall 1 month ago ↶
59
let now = now_ms ( ) ;
60
let p: Project = sqlx:: query_as(
5d90e0e0 Andy McCall 20 days ago
61
" INSERT INTO projects (owner, number, title, description, public, created_ms, updated_ms) VALUES ($1, $2, $3, $4, $5, $6, $7) RETURNING *" ,
e1ec9fbf Andy McCall 1 month ago ↶
62
)
63
.bind ( owner)
64
.bind ( number)
65
.bind ( title)
66
.bind ( description)
67
.bind ( public)
68
.bind ( now)
69
.bind ( now)
70
.fetch_one ( & mut * tx)
71
.await? ;
b1e92a62 Andy McCall 26 days ago ↶
72
5d90e0e0 Andy McCall 20 days ago
73
sqlx:: query( " DELETE FROM project_redirects WHERE owner = $1 AND number = $2" ) .bind ( owner) .bind ( number) .execute ( & mut * tx) .await? ;
e1ec9fbf Andy McCall 1 month ago ↶
74
for ( i, name) in DEFAULT_COLUMNS .iter ( ) .enumerate ( ) {
1000b40a Andy McCall 10 days ago ↶
75
sqlx:: query( " INSERT INTO project_columns (project_id, name, position, takes_finished) VALUES ($1, $2, $3, $4)" )
e1ec9fbf Andy McCall 1 month ago ↶
76
.bind ( p.id)
77
.bind ( name)
78
.bind ( i as i64 )
1000b40a Andy McCall 10 days ago ↶
79
.bind ( * name = = " Done" )
e1ec9fbf Andy McCall 1 month ago ↶
80
.execute ( & mut * tx)
81
.await? ;
82
}
83
tx.commit ( ) .await? ;
84
Ok ( p)
85
}
86
87
pub async fn of_owner ( pool : & Pool, owner : & str ) -> sqlx:: Result< Vec< Project> > {
5d90e0e0 Andy McCall 20 days ago
88
sqlx:: query_as( " SELECT * FROM projects WHERE owner = $1 ORDER BY closed, updated_ms DESC" ) .bind ( owner) .fetch_all ( pool) .await
e1ec9fbf Andy McCall 1 month ago ↶
89
}
90
b1e92a62 Andy McCall 26 days ago ↶
91
92
93
pub async fn transfer ( pool : & Pool, id : i64 , to : & str ) -> sqlx:: Result< Project> {
94
let mut tx = super :: begin_write( pool) .await? ;
5d90e0e0 Andy McCall 20 days ago
95
let old: Project = sqlx:: query_as( " SELECT * FROM projects WHERE id = $1" ) .bind ( id) .fetch_one ( & mut * tx) .await? ;
b1e92a62 Andy McCall 26 days ago ↶
96
let number: i64 =
5d90e0e0 Andy McCall 20 days ago
97
sqlx:: query_scalar( " SELECT COALESCE(MAX(number), 0) + 1 FROM projects WHERE owner = $1" ) .bind ( to) .fetch_one ( & mut * tx) .await? ;
d1bf2d69 Andy McCall 20 days ago ↶
98
sqlx:: query( " INSERT INTO project_redirects (owner, number, project_id) VALUES ($1, $2, $3) ON CONFLICT (owner, number) DO UPDATE SET project_id = excluded.project_id" )
b1e92a62 Andy McCall 26 days ago ↶
99
.bind ( & old.owner)
100
.bind ( old.number)
101
.bind ( id)
102
.execute ( & mut * tx)
103
.await? ;
5d90e0e0 Andy McCall 20 days ago
104
sqlx:: query( " DELETE FROM project_redirects WHERE owner = $1 AND number = $2" ) .bind ( to) .bind ( number) .execute ( & mut * tx) .await? ;
105
let moved: Project = sqlx:: query_as( " UPDATE projects SET owner = $1, number = $2, updated_ms = $3 WHERE id = $4 RETURNING *" )
b1e92a62 Andy McCall 26 days ago ↶
106
.bind ( to)
107
.bind ( number)
108
.bind ( now_ms ( ) )
109
.bind ( id)
110
.fetch_one ( & mut * tx)
111
.await? ;
112
tx.commit ( ) .await? ;
113
Ok ( moved)
114
}
115
116
117
118
pub async fn redirected ( pool : & Pool, owner : & str , number : i64 ) -> sqlx:: Result< Option< Project> > {
5d90e0e0 Andy McCall 20 days ago
119
sqlx:: query_as( " SELECT p.* FROM project_redirects r JOIN projects p ON p.id = r.project_id WHERE r.owner = $1 AND r.number = $2" )
b1e92a62 Andy McCall 26 days ago ↶
120
.bind ( owner)
121
.bind ( number)
122
.fetch_optional ( pool)
123
.await
124
}
125
e1ec9fbf Andy McCall 1 month ago ↶
126
pub async fn get ( pool : & Pool, owner : & str , number : i64 ) -> sqlx:: Result< Option< Project> > {
5d90e0e0 Andy McCall 20 days ago
127
sqlx:: query_as( " SELECT * FROM projects WHERE owner = $1 AND number = $2" ) .bind ( owner) .bind ( number) .fetch_optional ( pool) .await
e1ec9fbf Andy McCall 1 month ago ↶
128
}
129
130
pub async fn set ( pool : & Pool, id : i64 , title : & str , description : & str , public : bool , closed : bool ) -> sqlx:: Result< ( ) > {
5d90e0e0 Andy McCall 20 days ago
131
sqlx:: query( " UPDATE projects SET title = $1, description = $2, public = $3, closed = $4, updated_ms = $5 WHERE id = $6" )
e1ec9fbf Andy McCall 1 month ago ↶
132
.bind ( title)
133
.bind ( description)
134
.bind ( public)
135
.bind ( closed)
136
.bind ( now_ms ( ) )
137
.bind ( id)
138
.execute ( pool)
139
.await? ;
140
Ok ( ( ) )
141
}
142
143
pub async fn delete ( pool : & Pool, id : i64 ) -> sqlx:: Result< ( ) > {
5d90e0e0 Andy McCall 20 days ago
144
sqlx:: query( " DELETE FROM projects WHERE id = $1" ) .bind ( id) .execute ( pool) .await? ;
e1ec9fbf Andy McCall 1 month ago ↶
145
Ok ( ( ) )
146
}
147
148
async fn touch ( pool : & Pool, id : i64 ) -> sqlx:: Result< ( ) > {
5d90e0e0 Andy McCall 20 days ago
149
sqlx:: query( " UPDATE projects SET updated_ms = $1 WHERE id = $2" ) .bind ( now_ms ( ) ) .bind ( id) .execute ( pool) .await? ;
e1ec9fbf Andy McCall 1 month ago ↶
150
Ok ( ( ) )
151
}
152
153
pub async fn columns ( pool : & Pool, project_id : i64 ) -> sqlx:: Result< Vec< Column> > {
5d90e0e0 Andy McCall 20 days ago
154
sqlx:: query_as( " SELECT * FROM project_columns WHERE project_id = $1 ORDER BY position, id" ) .bind ( project_id) .fetch_all ( pool) .await
e1ec9fbf Andy McCall 1 month ago ↶
155
}
156
157
pub async fn cards ( pool : & Pool, project_id : i64 ) -> sqlx:: Result< Vec< Card> > {
5d90e0e0 Andy McCall 20 days ago
158
sqlx:: query_as( " SELECT * FROM project_cards WHERE project_id = $1 ORDER BY position, id" ) .bind ( project_id) .fetch_all ( pool) .await
e1ec9fbf Andy McCall 1 month ago ↶
159
}
160
161
pub async fn add_column ( pool : & Pool, project_id : i64 , name : & str ) -> sqlx:: Result< ( ) > {
5d90e0e0 Andy McCall 20 days ago
162
let pos: i64 = sqlx:: query_scalar( " SELECT COALESCE(MAX(position), -1) + 1 FROM project_columns WHERE project_id = $1" )
e1ec9fbf Andy McCall 1 month ago ↶
163
.bind ( project_id)
164
.fetch_one ( pool)
165
.await? ;
5d90e0e0 Andy McCall 20 days ago
166
sqlx:: query( " INSERT INTO project_columns (project_id, name, position) VALUES ($1, $2, $3)" )
e1ec9fbf Andy McCall 1 month ago ↶
167
.bind ( project_id)
168
.bind ( name)
169
.bind ( pos)
170
.execute ( pool)
171
.await? ;
172
touch ( pool, project_id) .await
173
}
174
175
pub async fn rename_column ( pool : & Pool, project_id : i64 , column_id : i64 , name : & str ) -> sqlx:: Result< ( ) > {
5d90e0e0 Andy McCall 20 days ago
176
sqlx:: query( " UPDATE project_columns SET name = $1 WHERE id = $2 AND project_id = $3" )
e1ec9fbf Andy McCall 1 month ago ↶
177
.bind ( name)
178
.bind ( column_id)
179
.bind ( project_id)
180
.execute ( pool)
181
.await? ;
182
touch ( pool, project_id) .await
183
}
184
185
186
pub async fn shift_column ( pool : & Pool, project_id : i64 , column_id : i64 , by : i64 ) -> sqlx:: Result< ( ) > {
187
let cols = columns ( pool, project_id) .await? ;
188
let Some ( i) = cols.iter ( ) .position ( | c | c.id = = column_id ) else { return Ok ( ( ) ) } ;
189
let j = i as i64 + by;
190
if j < 0 | | j as usize > = cols.len ( ) {
191
return Ok ( ( ) ) ;
192
}
193
let mut ids: Vec< i64 > = cols.iter ( ) .map ( | c | c.id ) .collect ( ) ;
194
ids.swap ( i, j as usize ) ;
195
for ( pos, id) in ids.iter ( ) .enumerate ( ) {
5d90e0e0 Andy McCall 20 days ago
196
sqlx:: query( " UPDATE project_columns SET position = $1 WHERE id = $2" ) .bind ( pos as i64 ) .bind ( id) .execute ( pool) .await? ;
e1ec9fbf Andy McCall 1 month ago ↶
197
}
198
touch ( pool, project_id) .await
199
}
200
201
202
pub async fn remove_column ( pool : & Pool, project_id : i64 , column_id : i64 ) -> sqlx:: Result< bool > {
203
let cols = columns ( pool, project_id) .await? ;
204
let Some ( to) = cols.iter ( ) .find ( | c | c.id ! = column_id ) else { return Ok ( false ) } ;
205
if ! cols.iter ( ) .any ( | c | c.id = = column_id ) {
206
return Ok ( false ) ;
207
}
941abdd6 Andy McCall 1 month ago ↶
208
let mut tx = super :: begin_write( pool) .await? ;
5d90e0e0 Andy McCall 20 days ago
209
let base: i64 = sqlx:: query_scalar( " SELECT COALESCE(MAX(position), -1) + 1 FROM project_cards WHERE column_id = $1" )
e1ec9fbf Andy McCall 1 month ago ↶
210
.bind ( to.id)
211
.fetch_one ( & mut * tx)
212
.await? ;
5d90e0e0 Andy McCall 20 days ago
213
sqlx:: query( " UPDATE project_cards SET column_id = $1, position = position + $2 WHERE column_id = $3" )
e1ec9fbf Andy McCall 1 month ago ↶
214
.bind ( to.id)
215
.bind ( base)
216
.bind ( column_id)
217
.execute ( & mut * tx)
218
.await? ;
5d90e0e0 Andy McCall 20 days ago
219
sqlx:: query( " DELETE FROM project_columns WHERE id = $1" ) .bind ( column_id) .execute ( & mut * tx) .await? ;
e1ec9fbf Andy McCall 1 month ago ↶
220
tx.commit ( ) .await? ;
221
touch ( pool, project_id) .await? ;
222
Ok ( true )
223
}
224
225
pub struct NewCard < 'a > {
226
pub column_id : i64 ,
227
pub kind : & 'a str ,
228
pub repo_id : Option< i64 > ,
229
pub number : Option< i64 > ,
230
pub note : & 'a str ,
231
pub added_by : & 'a str ,
232
}
233
234
235
236
pub async fn add_card ( pool : & Pool, project_id : i64 , c : NewCard< '_ > ) -> sqlx:: Result< bool > {
5d90e0e0 Andy McCall 20 days ago
237
let pos: i64 = sqlx:: query_scalar( " SELECT COALESCE(MAX(position), -1) + 1 FROM project_cards WHERE column_id = $1" )
e1ec9fbf Andy McCall 1 month ago ↶
238
.bind ( c.column_id)
239
.fetch_one ( pool)
240
.await? ;
241
let r = sqlx:: query(
439ecd87 Andy McCall 11 days ago ↶
242
" INSERT INTO project_cards (project_id, column_id, position, kind, repo_id, number, note, added_by, created_ms, moved_ms) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $9) ON CONFLICT DO NOTHING" ,
e1ec9fbf Andy McCall 1 month ago ↶
243
)
244
.bind ( project_id)
245
.bind ( c.column_id)
246
.bind ( pos)
247
.bind ( c.kind)
248
.bind ( c.repo_id)
249
.bind ( c.number)
250
.bind ( c.note)
251
.bind ( c.added_by)
252
.bind ( now_ms ( ) )
253
.execute ( pool)
254
.await? ;
255
touch ( pool, project_id) .await? ;
256
Ok ( r.rows_affected ( ) = = 1 )
257
}
258
259
260
pub async fn move_card ( pool : & Pool, project_id : i64 , card_id : i64 , column_id : i64 ) -> sqlx:: Result< ( ) > {
5d90e0e0 Andy McCall 20 days ago
261
let ok: i64 = sqlx:: query_scalar( " SELECT COUNT(*) FROM project_columns WHERE id = $1 AND project_id = $2" )
e1ec9fbf Andy McCall 1 month ago ↶
262
.bind ( column_id)
263
.bind ( project_id)
264
.fetch_one ( pool)
265
.await? ;
266
if ok = = 0 {
267
return Ok ( ( ) ) ;
268
}
5d90e0e0 Andy McCall 20 days ago
269
let pos: i64 = sqlx:: query_scalar( " SELECT COALESCE(MAX(position), -1) + 1 FROM project_cards WHERE column_id = $1" )
e1ec9fbf Andy McCall 1 month ago ↶
270
.bind ( column_id)
271
.fetch_one ( pool)
272
.await? ;
439ecd87 Andy McCall 11 days ago ↶
273
sqlx:: query( " UPDATE project_cards SET moved_ms = CASE WHEN column_id = $1 THEN moved_ms ELSE $3 END, column_id = $1, position = $2 WHERE id = $4 AND project_id = $5" )
e1ec9fbf Andy McCall 1 month ago ↶
274
.bind ( column_id)
275
.bind ( pos)
439ecd87 Andy McCall 11 days ago ↶
276
.bind ( now_ms ( ) )
e1ec9fbf Andy McCall 1 month ago ↶
277
.bind ( card_id)
278
.bind ( project_id)
279
.execute ( pool)
280
.await? ;
281
touch ( pool, project_id) .await
282
}
283
456f4197 Andy McCall 12 days ago ↶
284
285
286
pub async fn move_cards ( pool : & Pool, project_id : i64 , card_ids : & [i64 ], column_id : i64 ) -> sqlx:: Result< ( ) > {
287
let mut tx = pool.begin ( ) .await? ;
288
let pos: i64 = sqlx:: query_scalar( " SELECT COALESCE(MAX(position), -1) + 1 FROM project_cards WHERE column_id = $1" )
289
.bind ( column_id)
290
.fetch_one ( & mut * tx)
291
.await? ;
439ecd87 Andy McCall 11 days ago ↶
292
let now = now_ms ( ) ;
456f4197 Andy McCall 12 days ago ↶
293
for ( i, id) in card_ids.iter ( ) .enumerate ( ) {
439ecd87 Andy McCall 11 days ago ↶
294
sqlx:: query( " UPDATE project_cards SET column_id = $1, position = $2, moved_ms = $3 WHERE id = $4 AND project_id = $5" )
456f4197 Andy McCall 12 days ago ↶
295
.bind ( column_id)
296
.bind ( pos + i as i64 )
439ecd87 Andy McCall 11 days ago ↶
297
.bind ( now)
456f4197 Andy McCall 12 days ago ↶
298
.bind ( id)
299
.bind ( project_id)
300
.execute ( & mut * tx)
301
.await? ;
302
}
439ecd87 Andy McCall 11 days ago ↶
303
sqlx:: query( " UPDATE projects SET updated_ms = $1 WHERE id = $2" ) .bind ( now) .bind ( project_id) .execute ( & mut * tx) .await? ;
456f4197 Andy McCall 12 days ago ↶
304
tx.commit ( ) .await
439ecd87 Andy McCall 11 days ago ↶
305
}
306
307
308
pub async fn archive_cards ( pool : & Pool, project_id : i64 , card_ids : & [i64 ]) -> sqlx:: Result< u64 > {
309
let mut tx = pool.begin ( ) .await? ;
310
let now = now_ms ( ) ;
311
let mut n = 0 ;
312
for id in card_ids {
313
n + = sqlx:: query( " UPDATE project_cards SET archived_ms = $1 WHERE id = $2 AND project_id = $3 AND archived_ms IS NULL" )
314
.bind ( now)
315
.bind ( id)
316
.bind ( project_id)
317
.execute ( & mut * tx)
318
.await?
319
.rows_affected ( ) ;
320
}
321
sqlx:: query( " UPDATE projects SET updated_ms = $1 WHERE id = $2" ) .bind ( now) .bind ( project_id) .execute ( & mut * tx) .await? ;
322
tx.commit ( ) .await? ;
323
Ok ( n)
324
}
325
326
327
328
329
pub async fn restore_card ( pool : & Pool, project_id : i64 , card_id : i64 ) -> sqlx:: Result< bool > {
330
let mut tx = pool.begin ( ) .await? ;
331
let pos: i64 = sqlx:: query_scalar(
332
" SELECT COALESCE(MAX(position), -1) + 1 FROM project_cards WHERE column_id = (SELECT column_id FROM project_cards WHERE id = $1)" ,
333
)
334
.bind ( card_id)
335
.fetch_one ( & mut * tx)
336
.await? ;
337
let now = now_ms ( ) ;
338
let r = sqlx:: query(
339
" UPDATE project_cards SET archived_ms = NULL, moved_ms = $1, position = $2 WHERE id = $3 AND project_id = $4 AND archived_ms IS NOT NULL" ,
340
)
341
.bind ( now)
342
.bind ( pos)
343
.bind ( card_id)
344
.bind ( project_id)
345
.execute ( & mut * tx)
346
.await? ;
347
sqlx:: query( " UPDATE projects SET updated_ms = $1 WHERE id = $2" ) .bind ( now) .bind ( project_id) .execute ( & mut * tx) .await? ;
348
tx.commit ( ) .await? ;
349
Ok ( r.rows_affected ( ) = = 1 )
350
}
351
352
353
pub fn older_than ( card : & Card, days : i64 , now : i64 ) -> bool {
354
card.moved_ms < now - days * DAY_MS
355
}
356
946fe7a0 Andy McCall 11 days ago ↶
357
358
359
439ecd87 Andy McCall 11 days ago ↶
360
pub async fn set_archive_after ( pool : & Pool, project_id : i64 , column_id : i64 , days : Option< i64 > ) -> sqlx:: Result< ( ) > {
361
sqlx:: query( " UPDATE project_columns SET archive_after_days = $1 WHERE id = $2 AND project_id = $3" )
362
.bind ( days)
363
.bind ( column_id)
364
.bind ( project_id)
365
.execute ( pool)
366
.await? ;
367
touch ( pool, project_id) .await
368
}
369
946fe7a0 Andy McCall 11 days ago ↶
370
371
372
pub const FINISHED_KEPT_DAYS : i64 = 14 ;
373
374
375
pub const NEVER : i64 = - 1 ;
376
377
378
379
380
381
439ecd87 Andy McCall 11 days ago ↶
382
pub async fn archive_due ( pool : & Pool, now : i64 ) -> sqlx:: Result< u64 > {
946fe7a0 Andy McCall 11 days ago ↶
383
let set = sqlx:: query(
439ecd87 Andy McCall 11 days ago ↶
384
" UPDATE project_cards SET archived_ms = $1 WHERE archived_ms IS NULL AND EXISTS \
946fe7a0 Andy McCall 11 days ago ↶
385
(SELECT 1 FROM project_columns c WHERE c.id = project_cards.column_id AND c.archive_after_days >= 0 \
439ecd87 Andy McCall 11 days ago ↶
386
AND project_cards.moved_ms < $1 - c.archive_after_days * $2)" ,
387
)
388
.bind ( now)
389
.bind ( DAY_MS )
946fe7a0 Andy McCall 11 days ago ↶
390
.execute ( pool)
391
.await? ;
392
let finished = sqlx:: query(
393
" UPDATE project_cards SET archived_ms = $1 WHERE archived_ms IS NULL AND moved_ms < $2 \
394
AND EXISTS (SELECT 1 FROM project_columns c WHERE c.id = project_cards.column_id AND c.archive_after_days IS NULL) \
395
AND ((kind = 'issue' AND EXISTS (SELECT 1 FROM issues i WHERE i.repo_id = project_cards.repo_id \
396
AND i.number = project_cards.number AND i.state = 'closed')) \
397
OR (kind = 'proposal' AND EXISTS (SELECT 1 FROM proposals p WHERE p.repo_id = project_cards.repo_id \
398
AND p.number = project_cards.number AND p.state IN ('closed', 'landed'))))" ,
399
)
400
.bind ( now)
401
.bind ( now - FINISHED_KEPT_DAYS * DAY_MS )
439ecd87 Andy McCall 11 days ago ↶
402
.execute ( pool)
403
.await? ;
946fe7a0 Andy McCall 11 days ago ↶
404
Ok ( set.rows_affected ( ) + finished.rows_affected ( ) )
456f4197 Andy McCall 12 days ago ↶
405
}
406
e1ec9fbf Andy McCall 1 month ago ↶
407
408
pub async fn shift_card ( pool : & Pool, project_id : i64 , card_id : i64 , by : i64 ) -> sqlx:: Result< ( ) > {
5d90e0e0 Andy McCall 20 days ago
409
let Some ( card) : Option< Card> = sqlx:: query_as( " SELECT * FROM project_cards WHERE id = $1 AND project_id = $2" )
e1ec9fbf Andy McCall 1 month ago ↶
410
.bind ( card_id)
411
.bind ( project_id)
412
.fetch_optional ( pool)
413
.await?
414
else {
415
return Ok ( ( ) ) ;
416
} ;
5d90e0e0 Andy McCall 20 days ago
417
let col: Vec< Card> = sqlx:: query_as( " SELECT * FROM project_cards WHERE column_id = $1 ORDER BY position, id" )
e1ec9fbf Andy McCall 1 month ago ↶
418
.bind ( card.column_id)
419
.fetch_all ( pool)
420
.await? ;
421
let Some ( i) = col.iter ( ) .position ( | c | c.id = = card_id ) else { return Ok ( ( ) ) } ;
422
let j = i as i64 + by;
423
if j < 0 | | j as usize > = col.len ( ) {
424
return Ok ( ( ) ) ;
425
}
426
let mut ids: Vec< i64 > = col.iter ( ) .map ( | c | c.id ) .collect ( ) ;
427
ids.swap ( i, j as usize ) ;
428
for ( pos, id) in ids.iter ( ) .enumerate ( ) {
5d90e0e0 Andy McCall 20 days ago
429
sqlx:: query( " UPDATE project_cards SET position = $1 WHERE id = $2" ) .bind ( pos as i64 ) .bind ( id) .execute ( pool) .await? ;
e1ec9fbf Andy McCall 1 month ago ↶
430
}
431
touch ( pool, project_id) .await
432
}
433
434
pub async fn remove_card ( pool : & Pool, project_id : i64 , card_id : i64 ) -> sqlx:: Result< ( ) > {
5d90e0e0 Andy McCall 20 days ago
435
sqlx:: query( " DELETE FROM project_cards WHERE id = $1 AND project_id = $2" ) .bind ( card_id) .bind ( project_id) .execute ( pool) .await? ;
e1ec9fbf Andy McCall 1 month ago ↶
436
touch ( pool, project_id) .await
437
}
438
439
1000b40a Andy McCall 10 days ago ↶
440
441
pub async fn set_takes_finished ( pool : & Pool, project_id : i64 , column_id : Option< i64 > ) -> sqlx:: Result< ( ) > {
442
let mut tx = super :: begin_write( pool) .await? ;
443
sqlx:: query( " UPDATE project_columns SET takes_finished = FALSE WHERE project_id = $1" ) .bind ( project_id) .execute ( & mut * tx) .await? ;
444
if let Some ( c) = column_id {
445
sqlx:: query( " UPDATE project_columns SET takes_finished = TRUE WHERE id = $1 AND project_id = $2" )
446
.bind ( c)
447
.bind ( project_id)
448
.execute ( & mut * tx)
449
.await? ;
450
}
451
tx.commit ( ) .await? ;
452
touch ( pool, project_id) .await
453
}
454
455
456
457
458
pub async fn finished ( pool : & Pool, repo_id : i64 , number : i64 ) -> sqlx:: Result< ( ) > {
459
let cards: Vec< ( i64 , i64 , i64 ) > = sqlx:: query_as(
460
" SELECT c.id, c.project_id, c.column_id FROM project_cards c
461
WHERE c.repo_id = $1 AND c.number = $2 AND c.kind <> 'note' AND c.archived_ms IS NULL" ,
462
)
463
.bind ( repo_id)
464
.bind ( number)
465
.fetch_all ( pool)
466
.await? ;
467
for ( card, project, column) in cards {
468
let to: Option< i64 > =
469
sqlx:: query_scalar( " SELECT id FROM project_columns WHERE project_id = $1 AND takes_finished ORDER BY position LIMIT 1" )
470
.bind ( project)
471
.fetch_optional ( pool)
472
.await? ;
473
if let Some ( to) = to.filter ( | to | * to ! = column) {
474
move_card ( pool, project, card, to) .await? ;
475
}
476
}
477
Ok ( ( ) )
478
}
479
e1ec9fbf Andy McCall 1 month ago ↶
480
pub async fn placements ( pool : & Pool, repo_id : i64 , number : i64 ) -> sqlx:: Result< Vec< ( Project, String) > > {
439ecd87 Andy McCall 11 days ago ↶
481
let rows: Vec< ( i64 , String, Option< i64 > ) > = sqlx:: query_as(
482
" SELECT c.project_id, col.name, c.archived_ms FROM project_cards c JOIN project_columns col ON col.id = c.column_id WHERE c.repo_id = $1 AND c.number = $2 AND c.kind <> 'note'" ,
e1ec9fbf Andy McCall 1 month ago ↶
483
)
484
.bind ( repo_id)
485
.bind ( number)
486
.fetch_all ( pool)
487
.await? ;
488
let mut out = Vec :: new( ) ;
439ecd87 Andy McCall 11 days ago ↶
489
for ( pid, col, archived) in rows {
490
let col = if archived.is_some ( ) { format! ( " {col} , archived" ) } else { col } ;
5d90e0e0 Andy McCall 20 days ago
491
if let Some ( p) = sqlx:: query_as:: < _ , Project> ( " SELECT * FROM projects WHERE id = $1" ) .bind ( pid) .fetch_optional ( pool) .await? {
e1ec9fbf Andy McCall 1 month ago ↶
492
out.push ( ( p, col) ) ;
493
}
494
}
495
Ok ( out)
496
}