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 ,
046e3352 Andy McCall 8 days ago ↶
48
49
pub iteration_id : Option< i64 > ,
e1ec9fbf Andy McCall 1 month ago ↶
50
}
51
046e3352 Andy McCall 8 days ago ↶
52
53
54
# [ derive ( Clone, Debug, FromRow ) ]
55
pub struct Iteration {
56
pub id : i64 ,
57
pub project_id : i64 ,
58
pub name : String,
59
pub start_ms : i64 ,
60
61
pub end_ms : i64 ,
62
pub closed_ms : Option< i64 > ,
63
}
64
439ecd87 Andy McCall 11 days ago ↶
65
66
const DAY_MS : i64 = 86_400_000 ;
67
e1ec9fbf Andy McCall 1 month ago ↶
68
pub const DEFAULT_COLUMNS : [ & str ; 3 ] = [ " Backlog" , " Doing" , " Done" ] ;
69
70
pub async fn create ( pool : & Pool, owner : & str , title : & str , description : & str , public : bool ) -> sqlx:: Result< Project> {
941abdd6 Andy McCall 1 month ago ↶
71
let mut tx = super :: begin_write( pool) .await? ;
e1ec9fbf Andy McCall 1 month ago ↶
72
let number: i64 =
5d90e0e0 Andy McCall 20 days ago
73
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 ↶
74
let now = now_ms ( ) ;
75
let p: Project = sqlx:: query_as(
5d90e0e0 Andy McCall 20 days ago
76
" 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 ↶
77
)
78
.bind ( owner)
79
.bind ( number)
80
.bind ( title)
81
.bind ( description)
82
.bind ( public)
83
.bind ( now)
84
.bind ( now)
85
.fetch_one ( & mut * tx)
86
.await? ;
b1e92a62 Andy McCall 25 days ago ↶
87
5d90e0e0 Andy McCall 20 days ago
88
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 ↶
89
for ( i, name) in DEFAULT_COLUMNS .iter ( ) .enumerate ( ) {
1000b40a Andy McCall 10 days ago ↶
90
sqlx:: query( " INSERT INTO project_columns (project_id, name, position, takes_finished) VALUES ($1, $2, $3, $4)" )
e1ec9fbf Andy McCall 1 month ago ↶
91
.bind ( p.id)
92
.bind ( name)
93
.bind ( i as i64 )
1000b40a Andy McCall 10 days ago ↶
94
.bind ( * name = = " Done" )
e1ec9fbf Andy McCall 1 month ago ↶
95
.execute ( & mut * tx)
96
.await? ;
97
}
98
tx.commit ( ) .await? ;
99
Ok ( p)
100
}
101
102
pub async fn of_owner ( pool : & Pool, owner : & str ) -> sqlx:: Result< Vec< Project> > {
5d90e0e0 Andy McCall 20 days ago
103
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 ↶
104
}
105
b1e92a62 Andy McCall 25 days ago ↶
106
107
108
pub async fn transfer ( pool : & Pool, id : i64 , to : & str ) -> sqlx:: Result< Project> {
109
let mut tx = super :: begin_write( pool) .await? ;
5d90e0e0 Andy McCall 20 days ago
110
let old: Project = sqlx:: query_as( " SELECT * FROM projects WHERE id = $1" ) .bind ( id) .fetch_one ( & mut * tx) .await? ;
b1e92a62 Andy McCall 25 days ago ↶
111
let number: i64 =
5d90e0e0 Andy McCall 20 days ago
112
sqlx:: query_scalar( " SELECT COALESCE(MAX(number), 0) + 1 FROM projects WHERE owner = $1" ) .bind ( to) .fetch_one ( & mut * tx) .await? ;
d1bf2d69 Andy McCall 19 days ago ↶
113
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 25 days ago ↶
114
.bind ( & old.owner)
115
.bind ( old.number)
116
.bind ( id)
117
.execute ( & mut * tx)
118
.await? ;
5d90e0e0 Andy McCall 20 days ago
119
sqlx:: query( " DELETE FROM project_redirects WHERE owner = $1 AND number = $2" ) .bind ( to) .bind ( number) .execute ( & mut * tx) .await? ;
120
let moved: Project = sqlx:: query_as( " UPDATE projects SET owner = $1, number = $2, updated_ms = $3 WHERE id = $4 RETURNING *" )
b1e92a62 Andy McCall 25 days ago ↶
121
.bind ( to)
122
.bind ( number)
123
.bind ( now_ms ( ) )
124
.bind ( id)
125
.fetch_one ( & mut * tx)
126
.await? ;
127
tx.commit ( ) .await? ;
128
Ok ( moved)
129
}
130
131
132
133
pub async fn redirected ( pool : & Pool, owner : & str , number : i64 ) -> sqlx:: Result< Option< Project> > {
5d90e0e0 Andy McCall 20 days ago
134
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 25 days ago ↶
135
.bind ( owner)
136
.bind ( number)
137
.fetch_optional ( pool)
138
.await
139
}
140
e1ec9fbf Andy McCall 1 month ago ↶
141
pub async fn get ( pool : & Pool, owner : & str , number : i64 ) -> sqlx:: Result< Option< Project> > {
5d90e0e0 Andy McCall 20 days ago
142
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 ↶
143
}
144
145
pub async fn set ( pool : & Pool, id : i64 , title : & str , description : & str , public : bool , closed : bool ) -> sqlx:: Result< ( ) > {
5d90e0e0 Andy McCall 20 days ago
146
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 ↶
147
.bind ( title)
148
.bind ( description)
149
.bind ( public)
150
.bind ( closed)
151
.bind ( now_ms ( ) )
152
.bind ( id)
153
.execute ( pool)
154
.await? ;
155
Ok ( ( ) )
156
}
157
158
pub async fn delete ( pool : & Pool, id : i64 ) -> sqlx:: Result< ( ) > {
5d90e0e0 Andy McCall 20 days ago
159
sqlx:: query( " DELETE FROM projects WHERE id = $1" ) .bind ( id) .execute ( pool) .await? ;
e1ec9fbf Andy McCall 1 month ago ↶
160
Ok ( ( ) )
161
}
162
163
async fn touch ( pool : & Pool, id : i64 ) -> sqlx:: Result< ( ) > {
5d90e0e0 Andy McCall 20 days ago
164
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 ↶
165
Ok ( ( ) )
166
}
167
168
pub async fn columns ( pool : & Pool, project_id : i64 ) -> sqlx:: Result< Vec< Column> > {
5d90e0e0 Andy McCall 20 days ago
169
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 ↶
170
}
171
172
pub async fn cards ( pool : & Pool, project_id : i64 ) -> sqlx:: Result< Vec< Card> > {
5d90e0e0 Andy McCall 20 days ago
173
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 ↶
174
}
175
176
pub async fn add_column ( pool : & Pool, project_id : i64 , name : & str ) -> sqlx:: Result< ( ) > {
5d90e0e0 Andy McCall 20 days ago
177
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 ↶
178
.bind ( project_id)
179
.fetch_one ( pool)
180
.await? ;
5d90e0e0 Andy McCall 20 days ago
181
sqlx:: query( " INSERT INTO project_columns (project_id, name, position) VALUES ($1, $2, $3)" )
e1ec9fbf Andy McCall 1 month ago ↶
182
.bind ( project_id)
183
.bind ( name)
184
.bind ( pos)
185
.execute ( pool)
186
.await? ;
187
touch ( pool, project_id) .await
188
}
189
190
pub async fn rename_column ( pool : & Pool, project_id : i64 , column_id : i64 , name : & str ) -> sqlx:: Result< ( ) > {
5d90e0e0 Andy McCall 20 days ago
191
sqlx:: query( " UPDATE project_columns SET name = $1 WHERE id = $2 AND project_id = $3" )
e1ec9fbf Andy McCall 1 month ago ↶
192
.bind ( name)
193
.bind ( column_id)
194
.bind ( project_id)
195
.execute ( pool)
196
.await? ;
197
touch ( pool, project_id) .await
198
}
199
200
201
pub async fn shift_column ( pool : & Pool, project_id : i64 , column_id : i64 , by : i64 ) -> sqlx:: Result< ( ) > {
202
let cols = columns ( pool, project_id) .await? ;
203
let Some ( i) = cols.iter ( ) .position ( | c | c.id = = column_id ) else { return Ok ( ( ) ) } ;
204
let j = i as i64 + by;
205
if j < 0 | | j as usize > = cols.len ( ) {
206
return Ok ( ( ) ) ;
207
}
208
let mut ids: Vec< i64 > = cols.iter ( ) .map ( | c | c.id ) .collect ( ) ;
209
ids.swap ( i, j as usize ) ;
210
for ( pos, id) in ids.iter ( ) .enumerate ( ) {
5d90e0e0 Andy McCall 20 days ago
211
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 ↶
212
}
213
touch ( pool, project_id) .await
214
}
215
216
217
pub async fn remove_column ( pool : & Pool, project_id : i64 , column_id : i64 ) -> sqlx:: Result< bool > {
218
let cols = columns ( pool, project_id) .await? ;
219
let Some ( to) = cols.iter ( ) .find ( | c | c.id ! = column_id ) else { return Ok ( false ) } ;
220
if ! cols.iter ( ) .any ( | c | c.id = = column_id ) {
221
return Ok ( false ) ;
222
}
941abdd6 Andy McCall 1 month ago ↶
223
let mut tx = super :: begin_write( pool) .await? ;
5d90e0e0 Andy McCall 20 days ago
224
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 ↶
225
.bind ( to.id)
226
.fetch_one ( & mut * tx)
227
.await? ;
5d90e0e0 Andy McCall 20 days ago
228
sqlx:: query( " UPDATE project_cards SET column_id = $1, position = position + $2 WHERE column_id = $3" )
e1ec9fbf Andy McCall 1 month ago ↶
229
.bind ( to.id)
230
.bind ( base)
231
.bind ( column_id)
232
.execute ( & mut * tx)
233
.await? ;
5d90e0e0 Andy McCall 20 days ago
234
sqlx:: query( " DELETE FROM project_columns WHERE id = $1" ) .bind ( column_id) .execute ( & mut * tx) .await? ;
e1ec9fbf Andy McCall 1 month ago ↶
235
tx.commit ( ) .await? ;
236
touch ( pool, project_id) .await? ;
237
Ok ( true )
238
}
239
240
pub struct NewCard < 'a > {
241
pub column_id : i64 ,
242
pub kind : & 'a str ,
243
pub repo_id : Option< i64 > ,
244
pub number : Option< i64 > ,
245
pub note : & 'a str ,
246
pub added_by : & 'a str ,
247
}
248
249
250
251
pub async fn add_card ( pool : & Pool, project_id : i64 , c : NewCard< '_ > ) -> sqlx:: Result< bool > {
5d90e0e0 Andy McCall 20 days ago
252
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 ↶
253
.bind ( c.column_id)
254
.fetch_one ( pool)
255
.await? ;
256
let r = sqlx:: query(
439ecd87 Andy McCall 11 days ago ↶
257
" 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 ↶
258
)
259
.bind ( project_id)
260
.bind ( c.column_id)
261
.bind ( pos)
262
.bind ( c.kind)
263
.bind ( c.repo_id)
264
.bind ( c.number)
265
.bind ( c.note)
266
.bind ( c.added_by)
267
.bind ( now_ms ( ) )
268
.execute ( pool)
269
.await? ;
270
touch ( pool, project_id) .await? ;
271
Ok ( r.rows_affected ( ) = = 1 )
272
}
273
274
275
pub async fn move_card ( pool : & Pool, project_id : i64 , card_id : i64 , column_id : i64 ) -> sqlx:: Result< ( ) > {
5d90e0e0 Andy McCall 20 days ago
276
let ok: i64 = sqlx:: query_scalar( " SELECT COUNT(*) FROM project_columns WHERE id = $1 AND project_id = $2" )
e1ec9fbf Andy McCall 1 month ago ↶
277
.bind ( column_id)
278
.bind ( project_id)
279
.fetch_one ( pool)
280
.await? ;
281
if ok = = 0 {
282
return Ok ( ( ) ) ;
283
}
5d90e0e0 Andy McCall 20 days ago
284
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 ↶
285
.bind ( column_id)
286
.fetch_one ( pool)
287
.await? ;
439ecd87 Andy McCall 11 days ago ↶
288
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 ↶
289
.bind ( column_id)
290
.bind ( pos)
439ecd87 Andy McCall 11 days ago ↶
291
.bind ( now_ms ( ) )
e1ec9fbf Andy McCall 1 month ago ↶
292
.bind ( card_id)
293
.bind ( project_id)
294
.execute ( pool)
295
.await? ;
296
touch ( pool, project_id) .await
297
}
298
456f4197 Andy McCall 11 days ago ↶
299
300
301
pub async fn move_cards ( pool : & Pool, project_id : i64 , card_ids : & [i64 ], column_id : i64 ) -> sqlx:: Result< ( ) > {
302
let mut tx = pool.begin ( ) .await? ;
303
let pos: i64 = sqlx:: query_scalar( " SELECT COALESCE(MAX(position), -1) + 1 FROM project_cards WHERE column_id = $1" )
304
.bind ( column_id)
305
.fetch_one ( & mut * tx)
306
.await? ;
439ecd87 Andy McCall 11 days ago ↶
307
let now = now_ms ( ) ;
456f4197 Andy McCall 11 days ago ↶
308
for ( i, id) in card_ids.iter ( ) .enumerate ( ) {
439ecd87 Andy McCall 11 days ago ↶
309
sqlx:: query( " UPDATE project_cards SET column_id = $1, position = $2, moved_ms = $3 WHERE id = $4 AND project_id = $5" )
456f4197 Andy McCall 11 days ago ↶
310
.bind ( column_id)
311
.bind ( pos + i as i64 )
439ecd87 Andy McCall 11 days ago ↶
312
.bind ( now)
456f4197 Andy McCall 11 days ago ↶
313
.bind ( id)
314
.bind ( project_id)
315
.execute ( & mut * tx)
316
.await? ;
317
}
439ecd87 Andy McCall 11 days ago ↶
318
sqlx:: query( " UPDATE projects SET updated_ms = $1 WHERE id = $2" ) .bind ( now) .bind ( project_id) .execute ( & mut * tx) .await? ;
456f4197 Andy McCall 11 days ago ↶
319
tx.commit ( ) .await
439ecd87 Andy McCall 11 days ago ↶
320
}
321
322
323
pub async fn archive_cards ( pool : & Pool, project_id : i64 , card_ids : & [i64 ]) -> sqlx:: Result< u64 > {
324
let mut tx = pool.begin ( ) .await? ;
325
let now = now_ms ( ) ;
326
let mut n = 0 ;
327
for id in card_ids {
328
n + = sqlx:: query( " UPDATE project_cards SET archived_ms = $1 WHERE id = $2 AND project_id = $3 AND archived_ms IS NULL" )
329
.bind ( now)
330
.bind ( id)
331
.bind ( project_id)
332
.execute ( & mut * tx)
333
.await?
334
.rows_affected ( ) ;
335
}
336
sqlx:: query( " UPDATE projects SET updated_ms = $1 WHERE id = $2" ) .bind ( now) .bind ( project_id) .execute ( & mut * tx) .await? ;
337
tx.commit ( ) .await? ;
338
Ok ( n)
339
}
340
341
342
343
344
pub async fn restore_card ( pool : & Pool, project_id : i64 , card_id : i64 ) -> sqlx:: Result< bool > {
345
let mut tx = pool.begin ( ) .await? ;
346
let pos: i64 = sqlx:: query_scalar(
347
" SELECT COALESCE(MAX(position), -1) + 1 FROM project_cards WHERE column_id = (SELECT column_id FROM project_cards WHERE id = $1)" ,
348
)
349
.bind ( card_id)
350
.fetch_one ( & mut * tx)
351
.await? ;
352
let now = now_ms ( ) ;
046e3352 Andy McCall 8 days ago ↶
353
439ecd87 Andy McCall 11 days ago ↶
354
let r = sqlx:: query(
046e3352 Andy McCall 8 days ago ↶
355
" UPDATE project_cards SET archived_ms = NULL, iteration_id = NULL, moved_ms = $1, position = $2 \
356
WHERE id = $3 AND project_id = $4 AND archived_ms IS NOT NULL" ,
439ecd87 Andy McCall 11 days ago ↶
357
)
358
.bind ( now)
359
.bind ( pos)
360
.bind ( card_id)
361
.bind ( project_id)
362
.execute ( & mut * tx)
363
.await? ;
364
sqlx:: query( " UPDATE projects SET updated_ms = $1 WHERE id = $2" ) .bind ( now) .bind ( project_id) .execute ( & mut * tx) .await? ;
365
tx.commit ( ) .await? ;
366
Ok ( r.rows_affected ( ) = = 1 )
367
}
368
369
370
pub fn older_than ( card : & Card, days : i64 , now : i64 ) -> bool {
371
card.moved_ms < now - days * DAY_MS
046e3352 Andy McCall 8 days ago ↶
372
}
373
374
375
pub async fn iterations ( pool : & Pool, project_id : i64 ) -> sqlx:: Result< Vec< Iteration> > {
376
sqlx:: query_as( " SELECT * FROM project_iterations WHERE project_id = $1 ORDER BY start_ms DESC, id DESC" )
377
.bind ( project_id)
378
.fetch_all ( pool)
379
.await
380
}
381
382
383
pub async fn start_iteration ( pool : & Pool, project_id : i64 , name : & str , start_ms : i64 , end_ms : i64 ) -> sqlx:: Result< Option< Iteration> > {
384
let mut tx = super :: begin_write( pool) .await? ;
385
let open: i64 = sqlx:: query_scalar( " SELECT COUNT(*) FROM project_iterations WHERE project_id = $1 AND closed_ms IS NULL" )
386
.bind ( project_id)
387
.fetch_one ( & mut * tx)
388
.await? ;
389
if open > 0 {
390
return Ok ( None ) ;
391
}
392
let it: Iteration =
393
sqlx:: query_as( " INSERT INTO project_iterations (project_id, name, start_ms, end_ms) VALUES ($1, $2, $3, $4) RETURNING *" )
394
.bind ( project_id)
395
.bind ( name)
396
.bind ( start_ms)
397
.bind ( end_ms)
398
.fetch_one ( & mut * tx)
399
.await? ;
400
sqlx:: query( " UPDATE projects SET updated_ms = $1 WHERE id = $2" ) .bind ( now_ms ( ) ) .bind ( project_id) .execute ( & mut * tx) .await? ;
401
tx.commit ( ) .await? ;
402
Ok ( Some ( it) )
403
}
404
405
406
407
pub fn done_column ( columns : & [Column]) -> Option< i64 > {
408
columns.iter ( ) .find ( | c | c.takes_finished ) .or ( columns.last ( ) ) .map ( | c | c.id )
409
}
410
411
412
413
pub async fn close_iteration ( pool : & Pool, project_id : i64 , iteration_id : i64 , done : Option< i64 > ) -> sqlx:: Result< Option< u64 > > {
414
let mut tx = super :: begin_write( pool) .await? ;
415
let now = now_ms ( ) ;
416
let closed = sqlx:: query( " UPDATE project_iterations SET closed_ms = $1 WHERE id = $2 AND project_id = $3 AND closed_ms IS NULL" )
417
.bind ( now)
418
.bind ( iteration_id)
419
.bind ( project_id)
420
.execute ( & mut * tx)
421
.await? ;
422
if closed.rows_affected ( ) = = 0 {
423
return Ok ( None ) ;
424
}
425
let mut n = 0 ;
426
if let Some ( done) = done {
427
n = sqlx:: query(
428
" UPDATE project_cards SET archived_ms = $1, iteration_id = $2 WHERE project_id = $3 AND column_id = $4 AND archived_ms IS NULL" ,
429
)
430
.bind ( now)
431
.bind ( iteration_id)
432
.bind ( project_id)
433
.bind ( done)
434
.execute ( & mut * tx)
435
.await?
436
.rows_affected ( ) ;
437
}
438
sqlx:: query( " UPDATE projects SET updated_ms = $1 WHERE id = $2" ) .bind ( now) .bind ( project_id) .execute ( & mut * tx) .await? ;
439
tx.commit ( ) .await? ;
440
Ok ( Some ( n) )
441
}
442
443
444
445
446
pub async fn done_times ( pool : & Pool, it : & Iteration, done : Option< i64 > ) -> sqlx:: Result< Vec< i64 > > {
447
sqlx:: query_scalar(
448
" SELECT moved_ms FROM project_cards WHERE project_id = $1 AND (iteration_id = $2 \
449
OR ($3 AND iteration_id IS NULL AND column_id = $4 AND (archived_ms IS NULL OR archived_ms >= $5))) ORDER BY moved_ms" ,
450
)
451
.bind ( it.project_id)
452
.bind ( it.id)
453
.bind ( it.closed_ms.is_none ( ) )
454
.bind ( done.unwrap_or ( - 1 ) )
455
.bind ( it.start_ms)
456
.fetch_all ( pool)
457
.await
458
}
459
460
461
462
463
pub fn burn_up ( times : & [i64 ], start_ms : i64 , end_ms : i64 , now : i64 ) -> Vec< usize > {
464
let days = ( ( end_ms - start_ms + DAY_MS - 1 ) / DAY_MS ) .max ( 1 ) ;
465
let upto = ( ( now.min ( end_ms - 1 ) - start_ms) / DAY_MS + 1 ) .clamp ( 0 , days) ;
466
( 0 .. upto) .map ( | d | times.iter ( ) .filter ( | & & t | t < start_ms + ( d + 1 ) * DAY_MS ) .count ( ) ) .collect ( )
467
}
468
469
470
pub fn iteration_days ( it : & Iteration) -> i64 {
471
( ( it.end_ms - it.start_ms + DAY_MS - 1 ) / DAY_MS ) .max ( 1 )
439ecd87 Andy McCall 11 days ago ↶
472
}
473
946fe7a0 Andy McCall 10 days ago ↶
474
475
476
439ecd87 Andy McCall 11 days ago ↶
477
pub async fn set_archive_after ( pool : & Pool, project_id : i64 , column_id : i64 , days : Option< i64 > ) -> sqlx:: Result< ( ) > {
478
sqlx:: query( " UPDATE project_columns SET archive_after_days = $1 WHERE id = $2 AND project_id = $3" )
479
.bind ( days)
480
.bind ( column_id)
481
.bind ( project_id)
482
.execute ( pool)
483
.await? ;
484
touch ( pool, project_id) .await
485
}
486
946fe7a0 Andy McCall 10 days ago ↶
487
488
489
pub const FINISHED_KEPT_DAYS : i64 = 14 ;
490
491
492
pub const NEVER : i64 = - 1 ;
493
494
495
496
497
498
439ecd87 Andy McCall 11 days ago ↶
499
pub async fn archive_due ( pool : & Pool, now : i64 ) -> sqlx:: Result< u64 > {
946fe7a0 Andy McCall 10 days ago ↶
500
let set = sqlx:: query(
439ecd87 Andy McCall 11 days ago ↶
501
" UPDATE project_cards SET archived_ms = $1 WHERE archived_ms IS NULL AND EXISTS \
946fe7a0 Andy McCall 10 days ago ↶
502
(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 ↶
503
AND project_cards.moved_ms < $1 - c.archive_after_days * $2)" ,
504
)
505
.bind ( now)
506
.bind ( DAY_MS )
946fe7a0 Andy McCall 10 days ago ↶
507
.execute ( pool)
508
.await? ;
509
let finished = sqlx:: query(
510
" UPDATE project_cards SET archived_ms = $1 WHERE archived_ms IS NULL AND moved_ms < $2 \
511
AND EXISTS (SELECT 1 FROM project_columns c WHERE c.id = project_cards.column_id AND c.archive_after_days IS NULL) \
512
AND ((kind = 'issue' AND EXISTS (SELECT 1 FROM issues i WHERE i.repo_id = project_cards.repo_id \
513
AND i.number = project_cards.number AND i.state = 'closed')) \
514
OR (kind = 'proposal' AND EXISTS (SELECT 1 FROM proposals p WHERE p.repo_id = project_cards.repo_id \
515
AND p.number = project_cards.number AND p.state IN ('closed', 'landed'))))" ,
516
)
517
.bind ( now)
518
.bind ( now - FINISHED_KEPT_DAYS * DAY_MS )
439ecd87 Andy McCall 11 days ago ↶
519
.execute ( pool)
520
.await? ;
946fe7a0 Andy McCall 10 days ago ↶
521
Ok ( set.rows_affected ( ) + finished.rows_affected ( ) )
456f4197 Andy McCall 11 days ago ↶
522
}
523
e1ec9fbf Andy McCall 1 month ago ↶
524
525
pub async fn shift_card ( pool : & Pool, project_id : i64 , card_id : i64 , by : i64 ) -> sqlx:: Result< ( ) > {
5d90e0e0 Andy McCall 20 days ago
526
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 ↶
527
.bind ( card_id)
528
.bind ( project_id)
529
.fetch_optional ( pool)
530
.await?
531
else {
532
return Ok ( ( ) ) ;
533
} ;
5d90e0e0 Andy McCall 20 days ago
534
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 ↶
535
.bind ( card.column_id)
536
.fetch_all ( pool)
537
.await? ;
538
let Some ( i) = col.iter ( ) .position ( | c | c.id = = card_id ) else { return Ok ( ( ) ) } ;
539
let j = i as i64 + by;
540
if j < 0 | | j as usize > = col.len ( ) {
541
return Ok ( ( ) ) ;
542
}
543
let mut ids: Vec< i64 > = col.iter ( ) .map ( | c | c.id ) .collect ( ) ;
544
ids.swap ( i, j as usize ) ;
545
for ( pos, id) in ids.iter ( ) .enumerate ( ) {
5d90e0e0 Andy McCall 20 days ago
546
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 ↶
547
}
548
touch ( pool, project_id) .await
549
}
550
551
pub async fn remove_card ( pool : & Pool, project_id : i64 , card_id : i64 ) -> sqlx:: Result< ( ) > {
5d90e0e0 Andy McCall 20 days ago
552
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 ↶
553
touch ( pool, project_id) .await
554
}
555
556
1000b40a Andy McCall 10 days ago ↶
557
558
pub async fn set_takes_finished ( pool : & Pool, project_id : i64 , column_id : Option< i64 > ) -> sqlx:: Result< ( ) > {
559
let mut tx = super :: begin_write( pool) .await? ;
560
sqlx:: query( " UPDATE project_columns SET takes_finished = FALSE WHERE project_id = $1" ) .bind ( project_id) .execute ( & mut * tx) .await? ;
561
if let Some ( c) = column_id {
562
sqlx:: query( " UPDATE project_columns SET takes_finished = TRUE WHERE id = $1 AND project_id = $2" )
563
.bind ( c)
564
.bind ( project_id)
565
.execute ( & mut * tx)
566
.await? ;
567
}
568
tx.commit ( ) .await? ;
569
touch ( pool, project_id) .await
570
}
571
572
573
574
575
pub async fn finished ( pool : & Pool, repo_id : i64 , number : i64 ) -> sqlx:: Result< ( ) > {
576
let cards: Vec< ( i64 , i64 , i64 ) > = sqlx:: query_as(
577
" SELECT c.id, c.project_id, c.column_id FROM project_cards c
578
WHERE c.repo_id = $1 AND c.number = $2 AND c.kind <> 'note' AND c.archived_ms IS NULL" ,
579
)
580
.bind ( repo_id)
581
.bind ( number)
582
.fetch_all ( pool)
583
.await? ;
584
for ( card, project, column) in cards {
585
let to: Option< i64 > =
586
sqlx:: query_scalar( " SELECT id FROM project_columns WHERE project_id = $1 AND takes_finished ORDER BY position LIMIT 1" )
587
.bind ( project)
588
.fetch_optional ( pool)
589
.await? ;
590
if let Some ( to) = to.filter ( | to | * to ! = column) {
591
move_card ( pool, project, card, to) .await? ;
592
}
593
}
594
Ok ( ( ) )
595
}
596
e1ec9fbf Andy McCall 1 month ago ↶
597
pub async fn placements ( pool : & Pool, repo_id : i64 , number : i64 ) -> sqlx:: Result< Vec< ( Project, String) > > {
439ecd87 Andy McCall 11 days ago ↶
598
let rows: Vec< ( i64 , String, Option< i64 > ) > = sqlx:: query_as(
599
" 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 ↶
600
)
601
.bind ( repo_id)
602
.bind ( number)
603
.fetch_all ( pool)
604
.await? ;
605
let mut out = Vec :: new( ) ;
439ecd87 Andy McCall 11 days ago ↶
606
for ( pid, col, archived) in rows {
607
let col = if archived.is_some ( ) { format! ( " {col} , archived" ) } else { col } ;
5d90e0e0 Andy McCall 20 days ago
608
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 ↶
609
out.push ( ( p, col) ) ;
610
}
611
}
612
Ok ( out)
613
}