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 ,
25
}
26
27
# [ derive ( Clone, Debug, FromRow ) ]
28
pub struct Card {
29
pub id : i64 ,
30
pub project_id : i64 ,
31
pub column_id : i64 ,
32
pub position : i64 ,
33
pub kind : String,
34
pub repo_id : Option< i64 > ,
35
pub number : Option< i64 > ,
36
pub note : String,
37
pub added_by : String,
38
pub created_ms : i64 ,
39
}
40
41
pub const DEFAULT_COLUMNS : [ & str ; 3 ] = [ " Backlog" , " Doing" , " Done" ] ;
42
43
pub async fn create ( pool : & Pool, owner : & str , title : & str , description : & str , public : bool ) -> sqlx:: Result< Project> {
941abdd6 Andy McCall 1 month ago ↶
44
let mut tx = super :: begin_write( pool) .await? ;
e1ec9fbf Andy McCall 1 month ago ↶
45
let number: i64 =
5d90e0e0 Andy McCall 20 days ago
46
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 ↶
47
let now = now_ms ( ) ;
48
let p: Project = sqlx:: query_as(
5d90e0e0 Andy McCall 20 days ago
49
" 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 ↶
50
)
51
.bind ( owner)
52
.bind ( number)
53
.bind ( title)
54
.bind ( description)
55
.bind ( public)
56
.bind ( now)
57
.bind ( now)
58
.fetch_one ( & mut * tx)
59
.await? ;
b1e92a62 Andy McCall 25 days ago ↶
60
5d90e0e0 Andy McCall 20 days ago
61
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 ↶
62
for ( i, name) in DEFAULT_COLUMNS .iter ( ) .enumerate ( ) {
5d90e0e0 Andy McCall 20 days ago
63
sqlx:: query( " INSERT INTO project_columns (project_id, name, position) VALUES ($1, $2, $3)" )
e1ec9fbf Andy McCall 1 month ago ↶
64
.bind ( p.id)
65
.bind ( name)
66
.bind ( i as i64 )
67
.execute ( & mut * tx)
68
.await? ;
69
}
70
tx.commit ( ) .await? ;
71
Ok ( p)
72
}
73
74
pub async fn of_owner ( pool : & Pool, owner : & str ) -> sqlx:: Result< Vec< Project> > {
5d90e0e0 Andy McCall 20 days ago
75
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 ↶
76
}
77
b1e92a62 Andy McCall 25 days ago ↶
78
79
80
pub async fn transfer ( pool : & Pool, id : i64 , to : & str ) -> sqlx:: Result< Project> {
81
let mut tx = super :: begin_write( pool) .await? ;
5d90e0e0 Andy McCall 20 days ago
82
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 ↶
83
let number: i64 =
5d90e0e0 Andy McCall 20 days ago
84
sqlx:: query_scalar( " SELECT COALESCE(MAX(number), 0) + 1 FROM projects WHERE owner = $1" ) .bind ( to) .fetch_one ( & mut * tx) .await? ;
85
sqlx:: query( " INSERT OR REPLACE INTO project_redirects (owner, number, project_id) VALUES ($1, $2, $3)" )
b1e92a62 Andy McCall 25 days ago ↶
86
.bind ( & old.owner)
87
.bind ( old.number)
88
.bind ( id)
89
.execute ( & mut * tx)
90
.await? ;
5d90e0e0 Andy McCall 20 days ago
91
sqlx:: query( " DELETE FROM project_redirects WHERE owner = $1 AND number = $2" ) .bind ( to) .bind ( number) .execute ( & mut * tx) .await? ;
92
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 ↶
93
.bind ( to)
94
.bind ( number)
95
.bind ( now_ms ( ) )
96
.bind ( id)
97
.fetch_one ( & mut * tx)
98
.await? ;
99
tx.commit ( ) .await? ;
100
Ok ( moved)
101
}
102
103
104
105
pub async fn redirected ( pool : & Pool, owner : & str , number : i64 ) -> sqlx:: Result< Option< Project> > {
5d90e0e0 Andy McCall 20 days ago
106
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 ↶
107
.bind ( owner)
108
.bind ( number)
109
.fetch_optional ( pool)
110
.await
111
}
112
e1ec9fbf Andy McCall 1 month ago ↶
113
pub async fn get ( pool : & Pool, owner : & str , number : i64 ) -> sqlx:: Result< Option< Project> > {
5d90e0e0 Andy McCall 20 days ago
114
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 ↶
115
}
116
117
pub async fn set ( pool : & Pool, id : i64 , title : & str , description : & str , public : bool , closed : bool ) -> sqlx:: Result< ( ) > {
5d90e0e0 Andy McCall 20 days ago
118
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 ↶
119
.bind ( title)
120
.bind ( description)
121
.bind ( public)
122
.bind ( closed)
123
.bind ( now_ms ( ) )
124
.bind ( id)
125
.execute ( pool)
126
.await? ;
127
Ok ( ( ) )
128
}
129
130
pub async fn delete ( pool : & Pool, id : i64 ) -> sqlx:: Result< ( ) > {
5d90e0e0 Andy McCall 20 days ago
131
sqlx:: query( " DELETE FROM projects WHERE id = $1" ) .bind ( id) .execute ( pool) .await? ;
e1ec9fbf Andy McCall 1 month ago ↶
132
Ok ( ( ) )
133
}
134
135
async fn touch ( pool : & Pool, id : i64 ) -> sqlx:: Result< ( ) > {
5d90e0e0 Andy McCall 20 days ago
136
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 ↶
137
Ok ( ( ) )
138
}
139
140
pub async fn columns ( pool : & Pool, project_id : i64 ) -> sqlx:: Result< Vec< Column> > {
5d90e0e0 Andy McCall 20 days ago
141
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 ↶
142
}
143
144
pub async fn cards ( pool : & Pool, project_id : i64 ) -> sqlx:: Result< Vec< Card> > {
5d90e0e0 Andy McCall 20 days ago
145
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 ↶
146
}
147
148
pub async fn add_column ( pool : & Pool, project_id : i64 , name : & str ) -> sqlx:: Result< ( ) > {
5d90e0e0 Andy McCall 20 days ago
149
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 ↶
150
.bind ( project_id)
151
.fetch_one ( pool)
152
.await? ;
5d90e0e0 Andy McCall 20 days ago
153
sqlx:: query( " INSERT INTO project_columns (project_id, name, position) VALUES ($1, $2, $3)" )
e1ec9fbf Andy McCall 1 month ago ↶
154
.bind ( project_id)
155
.bind ( name)
156
.bind ( pos)
157
.execute ( pool)
158
.await? ;
159
touch ( pool, project_id) .await
160
}
161
162
pub async fn rename_column ( pool : & Pool, project_id : i64 , column_id : i64 , name : & str ) -> sqlx:: Result< ( ) > {
5d90e0e0 Andy McCall 20 days ago
163
sqlx:: query( " UPDATE project_columns SET name = $1 WHERE id = $2 AND project_id = $3" )
e1ec9fbf Andy McCall 1 month ago ↶
164
.bind ( name)
165
.bind ( column_id)
166
.bind ( project_id)
167
.execute ( pool)
168
.await? ;
169
touch ( pool, project_id) .await
170
}
171
172
173
pub async fn shift_column ( pool : & Pool, project_id : i64 , column_id : i64 , by : i64 ) -> sqlx:: Result< ( ) > {
174
let cols = columns ( pool, project_id) .await? ;
175
let Some ( i) = cols.iter ( ) .position ( | c | c.id = = column_id ) else { return Ok ( ( ) ) } ;
176
let j = i as i64 + by;
177
if j < 0 | | j as usize > = cols.len ( ) {
178
return Ok ( ( ) ) ;
179
}
180
let mut ids: Vec< i64 > = cols.iter ( ) .map ( | c | c.id ) .collect ( ) ;
181
ids.swap ( i, j as usize ) ;
182
for ( pos, id) in ids.iter ( ) .enumerate ( ) {
5d90e0e0 Andy McCall 20 days ago
183
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 ↶
184
}
185
touch ( pool, project_id) .await
186
}
187
188
189
pub async fn remove_column ( pool : & Pool, project_id : i64 , column_id : i64 ) -> sqlx:: Result< bool > {
190
let cols = columns ( pool, project_id) .await? ;
191
let Some ( to) = cols.iter ( ) .find ( | c | c.id ! = column_id ) else { return Ok ( false ) } ;
192
if ! cols.iter ( ) .any ( | c | c.id = = column_id ) {
193
return Ok ( false ) ;
194
}
941abdd6 Andy McCall 1 month ago ↶
195
let mut tx = super :: begin_write( pool) .await? ;
5d90e0e0 Andy McCall 20 days ago
196
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 ↶
197
.bind ( to.id)
198
.fetch_one ( & mut * tx)
199
.await? ;
5d90e0e0 Andy McCall 20 days ago
200
sqlx:: query( " UPDATE project_cards SET column_id = $1, position = position + $2 WHERE column_id = $3" )
e1ec9fbf Andy McCall 1 month ago ↶
201
.bind ( to.id)
202
.bind ( base)
203
.bind ( column_id)
204
.execute ( & mut * tx)
205
.await? ;
5d90e0e0 Andy McCall 20 days ago
206
sqlx:: query( " DELETE FROM project_columns WHERE id = $1" ) .bind ( column_id) .execute ( & mut * tx) .await? ;
e1ec9fbf Andy McCall 1 month ago ↶
207
tx.commit ( ) .await? ;
208
touch ( pool, project_id) .await? ;
209
Ok ( true )
210
}
211
212
pub struct NewCard < 'a > {
213
pub column_id : i64 ,
214
pub kind : & 'a str ,
215
pub repo_id : Option< i64 > ,
216
pub number : Option< i64 > ,
217
pub note : & 'a str ,
218
pub added_by : & 'a str ,
219
}
220
221
222
223
pub async fn add_card ( pool : & Pool, project_id : i64 , c : NewCard< '_ > ) -> sqlx:: Result< bool > {
5d90e0e0 Andy McCall 20 days ago
224
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 ↶
225
.bind ( c.column_id)
226
.fetch_one ( pool)
227
.await? ;
228
let r = sqlx:: query(
5d90e0e0 Andy McCall 20 days ago
229
" INSERT OR IGNORE INTO project_cards (project_id, column_id, position, kind, repo_id, number, note, added_by, created_ms) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)" ,
e1ec9fbf Andy McCall 1 month ago ↶
230
)
231
.bind ( project_id)
232
.bind ( c.column_id)
233
.bind ( pos)
234
.bind ( c.kind)
235
.bind ( c.repo_id)
236
.bind ( c.number)
237
.bind ( c.note)
238
.bind ( c.added_by)
239
.bind ( now_ms ( ) )
240
.execute ( pool)
241
.await? ;
242
touch ( pool, project_id) .await? ;
243
Ok ( r.rows_affected ( ) = = 1 )
244
}
245
246
247
pub async fn move_card ( pool : & Pool, project_id : i64 , card_id : i64 , column_id : i64 ) -> sqlx:: Result< ( ) > {
5d90e0e0 Andy McCall 20 days ago
248
let ok: i64 = sqlx:: query_scalar( " SELECT COUNT(*) FROM project_columns WHERE id = $1 AND project_id = $2" )
e1ec9fbf Andy McCall 1 month ago ↶
249
.bind ( column_id)
250
.bind ( project_id)
251
.fetch_one ( pool)
252
.await? ;
253
if ok = = 0 {
254
return Ok ( ( ) ) ;
255
}
5d90e0e0 Andy McCall 20 days ago
256
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 ↶
257
.bind ( column_id)
258
.fetch_one ( pool)
259
.await? ;
5d90e0e0 Andy McCall 20 days ago
260
sqlx:: query( " UPDATE project_cards SET column_id = $1, position = $2 WHERE id = $3 AND project_id = $4" )
e1ec9fbf Andy McCall 1 month ago ↶
261
.bind ( column_id)
262
.bind ( pos)
263
.bind ( card_id)
264
.bind ( project_id)
265
.execute ( pool)
266
.await? ;
267
touch ( pool, project_id) .await
268
}
269
270
271
pub async fn shift_card ( pool : & Pool, project_id : i64 , card_id : i64 , by : i64 ) -> sqlx:: Result< ( ) > {
5d90e0e0 Andy McCall 20 days ago
272
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 ↶
273
.bind ( card_id)
274
.bind ( project_id)
275
.fetch_optional ( pool)
276
.await?
277
else {
278
return Ok ( ( ) ) ;
279
} ;
5d90e0e0 Andy McCall 20 days ago
280
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 ↶
281
.bind ( card.column_id)
282
.fetch_all ( pool)
283
.await? ;
284
let Some ( i) = col.iter ( ) .position ( | c | c.id = = card_id ) else { return Ok ( ( ) ) } ;
285
let j = i as i64 + by;
286
if j < 0 | | j as usize > = col.len ( ) {
287
return Ok ( ( ) ) ;
288
}
289
let mut ids: Vec< i64 > = col.iter ( ) .map ( | c | c.id ) .collect ( ) ;
290
ids.swap ( i, j as usize ) ;
291
for ( pos, id) in ids.iter ( ) .enumerate ( ) {
5d90e0e0 Andy McCall 20 days ago
292
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 ↶
293
}
294
touch ( pool, project_id) .await
295
}
296
297
pub async fn remove_card ( pool : & Pool, project_id : i64 , card_id : i64 ) -> sqlx:: Result< ( ) > {
5d90e0e0 Andy McCall 20 days ago
298
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 ↶
299
touch ( pool, project_id) .await
300
}
301
302
303
pub async fn placements ( pool : & Pool, repo_id : i64 , number : i64 ) -> sqlx:: Result< Vec< ( Project, String) > > {
304
let rows: Vec< ( i64 , String) > = sqlx:: query_as(
5d90e0e0 Andy McCall 20 days ago
305
" SELECT c.project_id, col.name 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 ↶
306
)
307
.bind ( repo_id)
308
.bind ( number)
309
.fetch_all ( pool)
310
.await? ;
311
let mut out = Vec :: new( ) ;
312
for ( pid, col) in rows {
5d90e0e0 Andy McCall 20 days ago
313
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 ↶
314
out.push ( ( p, col) ) ;
315
}
316
}
317
Ok ( out)
318
}