Commit

Author:

Hash:

Timestamp:

+1420 -3479 +/-99 browse

Kevin Schoon [me@kevinschoon.com]

394a7b279ab0e646443e7d95d1099dd71a7f83b3

Wed, 06 May 2026 08:59:57 +0000 (1 month ago)

migrate from sqlx to diesel
migrate from sqlx to diesel

Diesel is well designed and maintained. Although it's ORM abilities can
be confusing it reduces a lot of boilerplate and doesn't require async.
1diff --git a/Cargo.lock b/Cargo.lock
2index 8d83389..232e1d1 100644
3--- a/Cargo.lock
4+++ b/Cargo.lock
5 @@ -12,12 +12,6 @@ dependencies = [
6 ]
7
8 [[package]]
9- name = "allocator-api2"
10- version = "0.2.21"
11- source = "registry+https://github.com/rust-lang/crates.io-index"
12- checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923"
13-
14- [[package]]
15 name = "android_system_properties"
16 version = "0.1.5"
17 source = "registry+https://github.com/rust-lang/crates.io-index"
18 @@ -121,7 +115,7 @@ dependencies = [
19 "memchr",
20 "serde",
21 "serde_derive",
22- "winnow",
23+ "winnow 0.7.15",
24 ]
25
26 [[package]]
27 @@ -145,15 +139,6 @@ dependencies = [
28 ]
29
30 [[package]]
31- name = "atoi"
32- version = "2.0.0"
33- source = "registry+https://github.com/rust-lang/crates.io-index"
34- checksum = "f28d99ec8bfea296261ca1af174f24225171fea9664ba9003cbebee704810528"
35- dependencies = [
36- "num-traits",
37- ]
38-
39- [[package]]
40 name = "atom_syndication"
41 version = "0.12.7"
42 source = "registry+https://github.com/rust-lang/crates.io-index"
43 @@ -179,6 +164,28 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
44 checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8"
45
46 [[package]]
47+ name = "aws-lc-rs"
48+ version = "1.16.3"
49+ source = "registry+https://github.com/rust-lang/crates.io-index"
50+ checksum = "0ec6fb3fe69024a75fa7e1bfb48aa6cf59706a101658ea01bfd33b2b248a038f"
51+ dependencies = [
52+ "aws-lc-sys",
53+ "zeroize",
54+ ]
55+
56+ [[package]]
57+ name = "aws-lc-sys"
58+ version = "0.40.0"
59+ source = "registry+https://github.com/rust-lang/crates.io-index"
60+ checksum = "f50037ee5e1e41e7b8f9d161680a725bd1626cb6f8c7e901f91f942850852fe7"
61+ dependencies = [
62+ "cc",
63+ "cmake",
64+ "dunce",
65+ "fs_extra",
66+ ]
67+
68+ [[package]]
69 name = "axum"
70 version = "0.8.9"
71 source = "registry+https://github.com/rust-lang/crates.io-index"
72 @@ -341,7 +348,6 @@ dependencies = [
73 name = "ayllu-build"
74 version = "0.2.1"
75 dependencies = [
76- "async-trait",
77 "ayllu_api",
78 "ayllu_cmd",
79 "ayllu_config",
80 @@ -349,7 +355,6 @@ dependencies = [
81 "ayllu_git",
82 "ayllu_logging",
83 "bytes",
84- "futures",
85 "petgraph",
86 "reqwest",
87 "serde",
88 @@ -424,7 +429,7 @@ dependencies = [
89 "serde_json",
90 "tempfile",
91 "thiserror 2.0.18",
92- "toml",
93+ "toml 0.8.23",
94 "toml_edit",
95 ]
96
97 @@ -432,12 +437,12 @@ dependencies = [
98 name = "ayllu_database"
99 version = "0.2.1"
100 dependencies = [
101- "async-trait",
102- "futures",
103+ "diesel",
104+ "diesel_migrations",
105 "serde",
106 "serde_json",
107- "sqlx",
108- "time",
109+ "thiserror 2.0.18",
110+ "timeutil",
111 "tracing",
112 ]
113
114 @@ -446,7 +451,7 @@ name = "ayllu_git"
115 version = "0.2.1"
116 dependencies = [
117 "git2",
118- "rand 0.9.4",
119+ "rand",
120 "serde",
121 "tempfile",
122 "tokio",
123 @@ -483,12 +488,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
124 checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6"
125
126 [[package]]
127- name = "base64ct"
128- version = "1.8.3"
129- source = "registry+https://github.com/rust-lang/crates.io-index"
130- checksum = "2af50177e190e07a26ab74f8b1efbfe2ef87da2116221318cb1c2e82baf7de06"
131-
132- [[package]]
133 name = "basic-toml"
134 version = "0.1.10"
135 source = "registry+https://github.com/rust-lang/crates.io-index"
136 @@ -502,9 +501,6 @@ name = "bitflags"
137 version = "2.11.1"
138 source = "registry+https://github.com/rust-lang/crates.io-index"
139 checksum = "c4512299f36f043ab09a583e57bceb5a5aab7a73db1805848e8fef3c9e8c78b3"
140- dependencies = [
141- "serde_core",
142- ]
143
144 [[package]]
145 name = "block-buffer"
146 @@ -561,6 +557,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
147 checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801"
148
149 [[package]]
150+ name = "cfg_aliases"
151+ version = "0.2.1"
152+ source = "registry+https://github.com/rust-lang/crates.io-index"
153+ checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724"
154+
155+ [[package]]
156 name = "chrono"
157 version = "0.4.44"
158 source = "registry+https://github.com/rust-lang/crates.io-index"
159 @@ -623,6 +625,15 @@ dependencies = [
160 ]
161
162 [[package]]
163+ name = "cmake"
164+ version = "0.1.58"
165+ source = "registry+https://github.com/rust-lang/crates.io-index"
166+ checksum = "c0f78a02292a74a88ac736019ab962ece0bc380e3f977bf72e376c5d78ff0678"
167+ dependencies = [
168+ "cc",
169+ ]
170+
171+ [[package]]
172 name = "colorchoice"
173 version = "1.0.5"
174 source = "registry+https://github.com/rust-lang/crates.io-index"
175 @@ -653,15 +664,6 @@ dependencies = [
176 ]
177
178 [[package]]
179- name = "concurrent-queue"
180- version = "2.5.0"
181- source = "registry+https://github.com/rust-lang/crates.io-index"
182- checksum = "4ca0197aee26d1ae37445ee532fefce43251d24cc7c166799f4d46817f1d3973"
183- dependencies = [
184- "crossbeam-utils",
185- ]
186-
187- [[package]]
188 name = "console"
189 version = "0.15.11"
190 source = "registry+https://github.com/rust-lang/crates.io-index"
191 @@ -681,12 +683,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
192 checksum = "cb16320721f6441f979feff82c8b4fe789a5423f48e4e724d124211c44be43af"
193
194 [[package]]
195- name = "const-oid"
196- version = "0.9.6"
197- source = "registry+https://github.com/rust-lang/crates.io-index"
198- checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8"
199-
200- [[package]]
201 name = "convert_case"
202 version = "0.6.0"
203 source = "registry+https://github.com/rust-lang/crates.io-index"
204 @@ -732,36 +728,6 @@ dependencies = [
205 ]
206
207 [[package]]
208- name = "crc"
209- version = "3.4.0"
210- source = "registry+https://github.com/rust-lang/crates.io-index"
211- checksum = "5eb8a2a1cd12ab0d987a5d5e825195d372001a4094a0376319d5a0ad71c1ba0d"
212- dependencies = [
213- "crc-catalog",
214- ]
215-
216- [[package]]
217- name = "crc-catalog"
218- version = "2.5.0"
219- source = "registry+https://github.com/rust-lang/crates.io-index"
220- checksum = "217698eaf96b4a3f0bc4f3662aaa55bdf913cd54d7204591faa790070c6d0853"
221-
222- [[package]]
223- name = "crossbeam-queue"
224- version = "0.3.12"
225- source = "registry+https://github.com/rust-lang/crates.io-index"
226- checksum = "0f58bbc28f91df819d0aa2a2c00cd19754769c2fad90579b3592b1c9ba7a3115"
227- dependencies = [
228- "crossbeam-utils",
229- ]
230-
231- [[package]]
232- name = "crossbeam-utils"
233- version = "0.8.21"
234- source = "registry+https://github.com/rust-lang/crates.io-index"
235- checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28"
236-
237- [[package]]
238 name = "crypto-common"
239 version = "0.1.7"
240 source = "registry+https://github.com/rust-lang/crates.io-index"
241 @@ -783,6 +749,16 @@ dependencies = [
242
243 [[package]]
244 name = "darling"
245+ version = "0.21.3"
246+ source = "registry+https://github.com/rust-lang/crates.io-index"
247+ checksum = "9cdf337090841a411e2a7f3deb9187445851f91b309c0c0a29e05f74a00a48c0"
248+ dependencies = [
249+ "darling_core 0.21.3",
250+ "darling_macro 0.21.3",
251+ ]
252+
253+ [[package]]
254+ name = "darling"
255 version = "0.23.0"
256 source = "registry+https://github.com/rust-lang/crates.io-index"
257 checksum = "25ae13da2f202d56bd7f91c25fba009e7717a1e4a1cc98a76d844b65ae912e9d"
258 @@ -807,6 +783,20 @@ dependencies = [
259
260 [[package]]
261 name = "darling_core"
262+ version = "0.21.3"
263+ source = "registry+https://github.com/rust-lang/crates.io-index"
264+ checksum = "1247195ecd7e3c85f83c8d2a366e4210d588e802133e1e355180a9870b517ea4"
265+ dependencies = [
266+ "fnv",
267+ "ident_case",
268+ "proc-macro2",
269+ "quote",
270+ "strsim",
271+ "syn",
272+ ]
273+
274+ [[package]]
275+ name = "darling_core"
276 version = "0.23.0"
277 source = "registry+https://github.com/rust-lang/crates.io-index"
278 checksum = "9865a50f7c335f53564bb694ef660825eb8610e0a53d3e11bf1b0d3df31e03b0"
279 @@ -831,24 +821,24 @@ dependencies = [
280
281 [[package]]
282 name = "darling_macro"
283- version = "0.23.0"
284+ version = "0.21.3"
285 source = "registry+https://github.com/rust-lang/crates.io-index"
286- checksum = "ac3984ec7bd6cfa798e62b4a642426a5be0e68f9401cfc2a01e3fa9ea2fcdb8d"
287+ checksum = "d38308df82d1080de0afee5d069fa14b0326a88c14f15c5ccda35b4a6c414c81"
288 dependencies = [
289- "darling_core 0.23.0",
290+ "darling_core 0.21.3",
291 "quote",
292 "syn",
293 ]
294
295 [[package]]
296- name = "der"
297- version = "0.7.10"
298+ name = "darling_macro"
299+ version = "0.23.0"
300 source = "registry+https://github.com/rust-lang/crates.io-index"
301- checksum = "e7c1832837b905bbfb5101e07cc24c8deddf52f93225eee6ead5f4d63d53ddcb"
302+ checksum = "ac3984ec7bd6cfa798e62b4a642426a5be0e68f9401cfc2a01e3fa9ea2fcdb8d"
303 dependencies = [
304- "const-oid",
305- "pem-rfc7468",
306- "zeroize",
307+ "darling_core 0.23.0",
308+ "quote",
309+ "syn",
310 ]
311
312 [[package]]
313 @@ -910,15 +900,59 @@ dependencies = [
314 ]
315
316 [[package]]
317+ name = "diesel"
318+ version = "2.3.9"
319+ source = "registry+https://github.com/rust-lang/crates.io-index"
320+ checksum = "9940fb8467a0a06312218ed384185cb8536aa10d8ec017d0ce7fad2c1bd882d5"
321+ dependencies = [
322+ "diesel_derives",
323+ "downcast-rs",
324+ "libsqlite3-sys",
325+ "sqlite-wasm-rs",
326+ "time",
327+ ]
328+
329+ [[package]]
330+ name = "diesel_derives"
331+ version = "2.3.9"
332+ source = "registry+https://github.com/rust-lang/crates.io-index"
333+ checksum = "d1817b7f4279b947fc4cafddec12b0e5f8727141706561ce3ac94a60bddd1cf5"
334+ dependencies = [
335+ "diesel_table_macro_syntax",
336+ "dsl_auto_type",
337+ "proc-macro2",
338+ "quote",
339+ "syn",
340+ ]
341+
342+ [[package]]
343+ name = "diesel_migrations"
344+ version = "2.3.2"
345+ source = "registry+https://github.com/rust-lang/crates.io-index"
346+ checksum = "28d0f4a98124ba6d4ca75da535f65984badec16a003b6e2f94a01e31a79490b8"
347+ dependencies = [
348+ "diesel",
349+ "migrations_internals",
350+ "migrations_macros",
351+ ]
352+
353+ [[package]]
354+ name = "diesel_table_macro_syntax"
355+ version = "0.3.0"
356+ source = "registry+https://github.com/rust-lang/crates.io-index"
357+ checksum = "fe2444076b48641147115697648dc743c2c00b61adade0f01ce67133c7babe8c"
358+ dependencies = [
359+ "syn",
360+ ]
361+
362+ [[package]]
363 name = "digest"
364 version = "0.10.7"
365 source = "registry+https://github.com/rust-lang/crates.io-index"
366 checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292"
367 dependencies = [
368 "block-buffer",
369- "const-oid",
370 "crypto-common",
371- "subtle",
372 ]
373
374 [[package]]
375 @@ -942,10 +976,30 @@ dependencies = [
376 ]
377
378 [[package]]
379- name = "dotenvy"
380- version = "0.15.7"
381+ name = "downcast-rs"
382+ version = "2.0.2"
383+ source = "registry+https://github.com/rust-lang/crates.io-index"
384+ checksum = "117240f60069e65410b3ae1bb213295bd828f707b5bec6596a1afc8793ce0cbc"
385+
386+ [[package]]
387+ name = "dsl_auto_type"
388+ version = "0.2.0"
389+ source = "registry+https://github.com/rust-lang/crates.io-index"
390+ checksum = "dd122633e4bef06db27737f21d3738fb89c8f6d5360d6d9d7635dda142a7757e"
391+ dependencies = [
392+ "darling 0.21.3",
393+ "either",
394+ "heck",
395+ "proc-macro2",
396+ "quote",
397+ "syn",
398+ ]
399+
400+ [[package]]
401+ name = "dunce"
402+ version = "1.0.5"
403 source = "registry+https://github.com/rust-lang/crates.io-index"
404- checksum = "1aaf95b3e5c8f23aa320147307562d361db0ae0d51242340f558153b4eb2439b"
405+ checksum = "92773504d58c093f6de2459af4af33faa518c13451eb8f2b5698ed3d36e7c813"
406
407 [[package]]
408 name = "dyn-clone"
409 @@ -958,9 +1012,6 @@ name = "either"
410 version = "1.15.0"
411 source = "registry+https://github.com/rust-lang/crates.io-index"
412 checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719"
413- dependencies = [
414- "serde",
415- ]
416
417 [[package]]
418 name = "encode_unicode"
419 @@ -1000,28 +1051,6 @@ dependencies = [
420 ]
421
422 [[package]]
423- name = "etcetera"
424- version = "0.8.0"
425- source = "registry+https://github.com/rust-lang/crates.io-index"
426- checksum = "136d1b5283a1ab77bd9257427ffd09d8667ced0570b6f938942bc7568ed5b943"
427- dependencies = [
428- "cfg-if",
429- "home",
430- "windows-sys 0.48.0",
431- ]
432-
433- [[package]]
434- name = "event-listener"
435- version = "5.4.1"
436- source = "registry+https://github.com/rust-lang/crates.io-index"
437- checksum = "e13b66accf52311f30a0db42147dadea9850cb48cd070028831ae5f5d4b856ab"
438- dependencies = [
439- "concurrent-queue",
440- "parking",
441- "pin-project-lite",
442- ]
443-
444- [[package]]
445 name = "fastrand"
446 version = "2.4.1"
447 source = "registry+https://github.com/rust-lang/crates.io-index"
448 @@ -1060,17 +1089,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
449 checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80"
450
451 [[package]]
452- name = "flume"
453- version = "0.11.1"
454- source = "registry+https://github.com/rust-lang/crates.io-index"
455- checksum = "da0e4dd2a88388a1f4ccc7c9ce104604dab68d9f408dc34cd45823d5a9069095"
456- dependencies = [
457- "futures-core",
458- "futures-sink",
459- "spin",
460- ]
461-
462- [[package]]
463 name = "fnv"
464 version = "1.0.7"
465 source = "registry+https://github.com/rust-lang/crates.io-index"
466 @@ -1083,6 +1101,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
467 checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2"
468
469 [[package]]
470+ name = "foldhash"
471+ version = "0.2.0"
472+ source = "registry+https://github.com/rust-lang/crates.io-index"
473+ checksum = "77ce24cb58228fbb8aa041425bb1050850ac19177686ea6e0f41a70416f56fdb"
474+
475+ [[package]]
476 name = "form_urlencoded"
477 version = "1.2.2"
478 source = "registry+https://github.com/rust-lang/crates.io-index"
479 @@ -1092,6 +1116,12 @@ dependencies = [
480 ]
481
482 [[package]]
483+ name = "fs_extra"
484+ version = "1.3.0"
485+ source = "registry+https://github.com/rust-lang/crates.io-index"
486+ checksum = "42703706b716c37f96a77aea830392ad231f44c9e9a67872fa5548707e11b11c"
487+
488+ [[package]]
489 name = "futures"
490 version = "0.3.32"
491 source = "registry+https://github.com/rust-lang/crates.io-index"
492 @@ -1134,17 +1164,6 @@ dependencies = [
493 ]
494
495 [[package]]
496- name = "futures-intrusive"
497- version = "0.5.0"
498- source = "registry+https://github.com/rust-lang/crates.io-index"
499- checksum = "1d930c203dd0b6ff06e0201a4a2fe9149b43c684fd4420555b26d21b1a02956f"
500- dependencies = [
501- "futures-core",
502- "lock_api",
503- "parking_lot",
504- ]
505-
506- [[package]]
507 name = "futures-io"
508 version = "0.3.32"
509 source = "registry+https://github.com/rust-lang/crates.io-index"
510 @@ -1207,8 +1226,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
511 checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0"
512 dependencies = [
513 "cfg-if",
514+ "js-sys",
515 "libc",
516 "wasi",
517+ "wasm-bindgen",
518 ]
519
520 [[package]]
521 @@ -1218,9 +1239,11 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
522 checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd"
523 dependencies = [
524 "cfg-if",
525+ "js-sys",
526 "libc",
527 "r-efi 5.3.0",
528 "wasip2",
529+ "wasm-bindgen",
530 ]
531
532 [[package]]
533 @@ -1280,25 +1303,23 @@ version = "0.15.5"
534 source = "registry+https://github.com/rust-lang/crates.io-index"
535 checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1"
536 dependencies = [
537- "allocator-api2",
538- "equivalent",
539- "foldhash",
540+ "foldhash 0.1.5",
541 ]
542
543 [[package]]
544 name = "hashbrown"
545- version = "0.17.0"
546+ version = "0.16.1"
547 source = "registry+https://github.com/rust-lang/crates.io-index"
548- checksum = "4f467dd6dccf739c208452f8014c75c18bb8301b050ad1cfb27153803edb0f51"
549+ checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100"
550+ dependencies = [
551+ "foldhash 0.2.0",
552+ ]
553
554 [[package]]
555- name = "hashlink"
556- version = "0.10.0"
557+ name = "hashbrown"
558+ version = "0.17.0"
559 source = "registry+https://github.com/rust-lang/crates.io-index"
560- checksum = "7382cf6263419f2d8df38c55d7da83da5c18aef87fc7a7fc1fb1e344edfe14c1"
561- dependencies = [
562- "hashbrown 0.15.5",
563- ]
564+ checksum = "4f467dd6dccf739c208452f8014c75c18bb8301b050ad1cfb27153803edb0f51"
565
566 [[package]]
567 name = "headers"
568 @@ -1337,33 +1358,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
569 checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70"
570
571 [[package]]
572- name = "hkdf"
573- version = "0.12.4"
574- source = "registry+https://github.com/rust-lang/crates.io-index"
575- checksum = "7b5f8eb2ad728638ea2c7d47a21db23b7b58a72ed6a38256b8a1849f15fbbdf7"
576- dependencies = [
577- "hmac",
578- ]
579-
580- [[package]]
581- name = "hmac"
582- version = "0.12.1"
583- source = "registry+https://github.com/rust-lang/crates.io-index"
584- checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e"
585- dependencies = [
586- "digest",
587- ]
588-
589- [[package]]
590- name = "home"
591- version = "0.5.12"
592- source = "registry+https://github.com/rust-lang/crates.io-index"
593- checksum = "cc627f471c528ff0c4a49e1d5e60450c8f6461dd6d10ba9dcd3a61d3dff7728d"
594- dependencies = [
595- "windows-sys 0.61.2",
596- ]
597-
598- [[package]]
599 name = "http"
600 version = "1.4.0"
601 source = "registry+https://github.com/rust-lang/crates.io-index"
602 @@ -1761,9 +1755,6 @@ name = "lazy_static"
603 version = "1.5.0"
604 source = "registry+https://github.com/rust-lang/crates.io-index"
605 checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe"
606- dependencies = [
607- "spin",
608- ]
609
610 [[package]]
611 name = "leb128fmt"
612 @@ -1800,12 +1791,6 @@ dependencies = [
613 ]
614
615 [[package]]
616- name = "libm"
617- version = "0.2.16"
618- source = "registry+https://github.com/rust-lang/crates.io-index"
619- checksum = "b6d2cec3eae94f9f509c767b45932f1ada8350c4bdb85af2fcab4a3c14807981"
620-
621- [[package]]
622 name = "libredox"
623 version = "0.1.16"
624 source = "registry+https://github.com/rust-lang/crates.io-index"
625 @@ -1819,11 +1804,10 @@ dependencies = [
626
627 [[package]]
628 name = "libsqlite3-sys"
629- version = "0.30.1"
630+ version = "0.37.0"
631 source = "registry+https://github.com/rust-lang/crates.io-index"
632- checksum = "2e99fb7a497b1e3339bc746195567ed8d3e24945ecd636e3619d20b9de9e9149"
633+ checksum = "b1f111c8c41e7c61a49cd34e44c7619462967221a6443b0ec299e0ac30cfb9b1"
634 dependencies = [
635- "cc",
636 "pkg-config",
637 "vcpkg",
638 ]
639 @@ -1868,6 +1852,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
640 checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897"
641
642 [[package]]
643+ name = "lru-slab"
644+ version = "0.1.2"
645+ source = "registry+https://github.com/rust-lang/crates.io-index"
646+ checksum = "112b39cec0b298b6c1999fee3e31427f74f676e4cb9879ed1a121b43661a4154"
647+
648+ [[package]]
649 name = "matchers"
650 version = "0.2.0"
651 source = "registry+https://github.com/rust-lang/crates.io-index"
652 @@ -1899,6 +1889,27 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
653 checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79"
654
655 [[package]]
656+ name = "migrations_internals"
657+ version = "2.3.0"
658+ source = "registry+https://github.com/rust-lang/crates.io-index"
659+ checksum = "36c791ecdf977c99f45f23280405d7723727470f6689a5e6dbf513ac547ae10d"
660+ dependencies = [
661+ "serde",
662+ "toml 0.9.12+spec-1.1.0",
663+ ]
664+
665+ [[package]]
666+ name = "migrations_macros"
667+ version = "2.3.0"
668+ source = "registry+https://github.com/rust-lang/crates.io-index"
669+ checksum = "36fc5ac76be324cfd2d3f2cf0fdf5d5d3c4f14ed8aaebadb09e304ba42282703"
670+ dependencies = [
671+ "migrations_internals",
672+ "proc-macro2",
673+ "quote",
674+ ]
675+
676+ [[package]]
677 name = "mime"
678 version = "0.3.17"
679 source = "registry+https://github.com/rust-lang/crates.io-index"
680 @@ -1941,55 +1952,18 @@ dependencies = [
681 ]
682
683 [[package]]
684- name = "num-bigint-dig"
685- version = "0.8.6"
686- source = "registry+https://github.com/rust-lang/crates.io-index"
687- checksum = "e661dda6640fad38e827a6d4a310ff4763082116fe217f279885c97f511bb0b7"
688- dependencies = [
689- "lazy_static",
690- "libm",
691- "num-integer",
692- "num-iter",
693- "num-traits",
694- "rand 0.8.6",
695- "smallvec",
696- "zeroize",
697- ]
698-
699- [[package]]
700 name = "num-conv"
701 version = "0.2.1"
702 source = "registry+https://github.com/rust-lang/crates.io-index"
703 checksum = "c6673768db2d862beb9b39a78fdcb1a69439615d5794a1be50caa9bc92c81967"
704
705 [[package]]
706- name = "num-integer"
707- version = "0.1.46"
708+ name = "num-traits"
709+ version = "0.2.19"
710 source = "registry+https://github.com/rust-lang/crates.io-index"
711- checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f"
712- dependencies = [
713- "num-traits",
714- ]
715-
716- [[package]]
717- name = "num-iter"
718- version = "0.1.45"
719- source = "registry+https://github.com/rust-lang/crates.io-index"
720- checksum = "1429034a0490724d0075ebb2bc9e875d6503c3cf69e235a8941aa757d83ef5bf"
721- dependencies = [
722- "autocfg",
723- "num-integer",
724- "num-traits",
725- ]
726-
727- [[package]]
728- name = "num-traits"
729- version = "0.2.19"
730- source = "registry+https://github.com/rust-lang/crates.io-index"
731- checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841"
732+ checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841"
733 dependencies = [
734 "autocfg",
735- "libm",
736 ]
737
738 [[package]]
739 @@ -2048,12 +2022,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
740 checksum = "7c87def4c32ab89d880effc9e097653c8da5d6ef28e6b539d313baaacfbafcbe"
741
742 [[package]]
743- name = "parking"
744- version = "2.2.1"
745- source = "registry+https://github.com/rust-lang/crates.io-index"
746- checksum = "f38d5652c16fde515bb1ecef450ab0f6a219d619a7274976324d5e377f7dceba"
747-
748- [[package]]
749 name = "parking_lot"
750 version = "0.12.5"
751 source = "registry+https://github.com/rust-lang/crates.io-index"
752 @@ -2077,15 +2045,6 @@ dependencies = [
753 ]
754
755 [[package]]
756- name = "pem-rfc7468"
757- version = "0.7.0"
758- source = "registry+https://github.com/rust-lang/crates.io-index"
759- checksum = "88b39c9bfcfc231068454382784bb460aae594343fb030d46e9f50a645418412"
760- dependencies = [
761- "base64ct",
762- ]
763-
764- [[package]]
765 name = "percent-encoding"
766 version = "2.3.2"
767 source = "registry+https://github.com/rust-lang/crates.io-index"
768 @@ -2110,27 +2069,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
769 checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd"
770
771 [[package]]
772- name = "pkcs1"
773- version = "0.7.5"
774- source = "registry+https://github.com/rust-lang/crates.io-index"
775- checksum = "c8ffb9f10fa047879315e6625af03c164b16962a5368d724ed16323b68ace47f"
776- dependencies = [
777- "der",
778- "pkcs8",
779- "spki",
780- ]
781-
782- [[package]]
783- name = "pkcs8"
784- version = "0.10.2"
785- source = "registry+https://github.com/rust-lang/crates.io-index"
786- checksum = "f950b2377845cebe5cf8b5165cb3cc1a5e0fa5cfa3e1f7f55707d8fd82e0a7b7"
787- dependencies = [
788- "der",
789- "spki",
790- ]
791-
792- [[package]]
793 name = "pkg-config"
794 version = "0.3.33"
795 source = "registry+https://github.com/rust-lang/crates.io-index"
796 @@ -2196,6 +2134,62 @@ dependencies = [
797 ]
798
799 [[package]]
800+ name = "quinn"
801+ version = "0.11.9"
802+ source = "registry+https://github.com/rust-lang/crates.io-index"
803+ checksum = "b9e20a958963c291dc322d98411f541009df2ced7b5a4f2bd52337638cfccf20"
804+ dependencies = [
805+ "bytes",
806+ "cfg_aliases",
807+ "pin-project-lite",
808+ "quinn-proto",
809+ "quinn-udp",
810+ "rustc-hash",
811+ "rustls",
812+ "socket2",
813+ "thiserror 2.0.18",
814+ "tokio",
815+ "tracing",
816+ "web-time",
817+ ]
818+
819+ [[package]]
820+ name = "quinn-proto"
821+ version = "0.11.14"
822+ source = "registry+https://github.com/rust-lang/crates.io-index"
823+ checksum = "434b42fec591c96ef50e21e886936e66d3cc3f737104fdb9b737c40ffb94c098"
824+ dependencies = [
825+ "aws-lc-rs",
826+ "bytes",
827+ "getrandom 0.3.4",
828+ "lru-slab",
829+ "rand",
830+ "ring",
831+ "rustc-hash",
832+ "rustls",
833+ "rustls-pki-types",
834+ "slab",
835+ "thiserror 2.0.18",
836+ "tinyvec",
837+ "tracing",
838+ "web-time",
839+ ]
840+
841+ [[package]]
842+ name = "quinn-udp"
843+ version = "0.5.14"
844+ source = "registry+https://github.com/rust-lang/crates.io-index"
845+ checksum = "addec6a0dcad8a8d96a771f815f0eaf55f9d1805756410b39f5fa81332574cbd"
846+ dependencies = [
847+ "cfg_aliases",
848+ "libc",
849+ "once_cell",
850+ "socket2",
851+ "tracing",
852+ "windows-sys 0.52.0",
853+ ]
854+
855+ [[package]]
856 name = "quipu"
857 version = "0.5.1"
858 dependencies = [
859 @@ -2235,33 +2229,12 @@ checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf"
860
861 [[package]]
862 name = "rand"
863- version = "0.8.6"
864- source = "registry+https://github.com/rust-lang/crates.io-index"
865- checksum = "5ca0ecfa931c29007047d1bc58e623ab12e5590e8c7cc53200d5202b69266d8a"
866- dependencies = [
867- "libc",
868- "rand_chacha 0.3.1",
869- "rand_core 0.6.4",
870- ]
871-
872- [[package]]
873- name = "rand"
874 version = "0.9.4"
875 source = "registry+https://github.com/rust-lang/crates.io-index"
876 checksum = "44c5af06bb1b7d3216d91932aed5265164bf384dc89cd6ba05cf59a35f5f76ea"
877 dependencies = [
878- "rand_chacha 0.9.0",
879- "rand_core 0.9.5",
880- ]
881-
882- [[package]]
883- name = "rand_chacha"
884- version = "0.3.1"
885- source = "registry+https://github.com/rust-lang/crates.io-index"
886- checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88"
887- dependencies = [
888- "ppv-lite86",
889- "rand_core 0.6.4",
890+ "rand_chacha",
891+ "rand_core",
892 ]
893
894 [[package]]
895 @@ -2271,16 +2244,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
896 checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb"
897 dependencies = [
898 "ppv-lite86",
899- "rand_core 0.9.5",
900- ]
901-
902- [[package]]
903- name = "rand_core"
904- version = "0.6.4"
905- source = "registry+https://github.com/rust-lang/crates.io-index"
906- checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c"
907- dependencies = [
908- "getrandom 0.2.17",
909+ "rand_core",
910 ]
911
912 [[package]]
913 @@ -2380,6 +2344,7 @@ dependencies = [
914 "log",
915 "percent-encoding",
916 "pin-project-lite",
917+ "quinn",
918 "rustls",
919 "rustls-pki-types",
920 "rustls-platform-verifier",
921 @@ -2420,23 +2385,13 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
922 checksum = "323c417e1d9665a65b263ec744ba09030cfb277e9daa0b018a4ab62e57bc8189"
923
924 [[package]]
925- name = "rsa"
926- version = "0.9.10"
927+ name = "rsqlite-vfs"
928+ version = "0.1.0"
929 source = "registry+https://github.com/rust-lang/crates.io-index"
930- checksum = "b8573f03f5883dcaebdfcf4725caa1ecb9c15b2ef50c43a07b816e06799bb12d"
931+ checksum = "a8a1f2315036ef6b1fbacd1972e8ee7688030b0a2121edfc2a6550febd41574d"
932 dependencies = [
933- "const-oid",
934- "digest",
935- "num-bigint-dig",
936- "num-integer",
937- "num-traits",
938- "pkcs1",
939- "pkcs8",
940- "rand_core 0.6.4",
941- "signature",
942- "spki",
943- "subtle",
944- "zeroize",
945+ "hashbrown 0.16.1",
946+ "thiserror 2.0.18",
947 ]
948
949 [[package]]
950 @@ -2485,6 +2440,7 @@ version = "0.23.40"
951 source = "registry+https://github.com/rust-lang/crates.io-index"
952 checksum = "ef86cd5876211988985292b91c96a8f2d298df24e75989a43a3c73f2d4d8168b"
953 dependencies = [
954+ "aws-lc-rs",
955 "once_cell",
956 "rustls-pki-types",
957 "rustls-webpki",
958 @@ -2510,6 +2466,7 @@ version = "1.14.1"
959 source = "registry+https://github.com/rust-lang/crates.io-index"
960 checksum = "30a7197ae7eb376e574fe940d068c30fe0462554a3ddbe4eca7838e049c937a9"
961 dependencies = [
962+ "web-time",
963 "zeroize",
964 ]
965
966 @@ -2546,6 +2503,7 @@ version = "0.103.13"
967 source = "registry+https://github.com/rust-lang/crates.io-index"
968 checksum = "61c429a8649f110dddef65e2a5ad240f747e85f7758a6bccc7e5777bd33f756e"
969 dependencies = [
970+ "aws-lc-rs",
971 "ring",
972 "rustls-pki-types",
973 "untrusted",
974 @@ -2718,6 +2676,15 @@ dependencies = [
975 ]
976
977 [[package]]
978+ name = "serde_spanned"
979+ version = "1.1.1"
980+ source = "registry+https://github.com/rust-lang/crates.io-index"
981+ checksum = "6662b5879511e06e8999a8a235d848113e942c9124f211511b16466ee2995f26"
982+ dependencies = [
983+ "serde_core",
984+ ]
985+
986+ [[package]]
987 name = "serde_urlencoded"
988 version = "0.7.1"
989 source = "registry+https://github.com/rust-lang/crates.io-index"
990 @@ -2814,16 +2781,6 @@ dependencies = [
991 ]
992
993 [[package]]
994- name = "signature"
995- version = "2.2.0"
996- source = "registry+https://github.com/rust-lang/crates.io-index"
997- checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de"
998- dependencies = [
999- "digest",
1000- "rand_core 0.6.4",
1001- ]
1002-
1003- [[package]]
1004 name = "simd_cesu8"
1005 version = "1.1.1"
1006 source = "registry+https://github.com/rust-lang/crates.io-index"
1007 @@ -2860,9 +2817,6 @@ name = "smallvec"
1008 version = "1.15.1"
1009 source = "registry+https://github.com/rust-lang/crates.io-index"
1010 checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03"
1011- dependencies = [
1012- "serde",
1013- ]
1014
1015 [[package]]
1016 name = "socket2"
1017 @@ -2875,210 +2829,15 @@ dependencies = [
1018 ]
1019
1020 [[package]]
1021- name = "spin"
1022- version = "0.9.8"
1023- source = "registry+https://github.com/rust-lang/crates.io-index"
1024- checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67"
1025- dependencies = [
1026- "lock_api",
1027- ]
1028-
1029- [[package]]
1030- name = "spki"
1031- version = "0.7.3"
1032- source = "registry+https://github.com/rust-lang/crates.io-index"
1033- checksum = "d91ed6c858b01f942cd56b37a94b3e0a1798290327d1236e4d9cf4eaca44d29d"
1034- dependencies = [
1035- "base64ct",
1036- "der",
1037- ]
1038-
1039- [[package]]
1040- name = "sqlx"
1041- version = "0.8.6"
1042- source = "registry+https://github.com/rust-lang/crates.io-index"
1043- checksum = "1fefb893899429669dcdd979aff487bd78f4064e5e7907e4269081e0ef7d97dc"
1044- dependencies = [
1045- "sqlx-core",
1046- "sqlx-macros",
1047- "sqlx-mysql",
1048- "sqlx-postgres",
1049- "sqlx-sqlite",
1050- ]
1051-
1052- [[package]]
1053- name = "sqlx-core"
1054- version = "0.8.6"
1055- source = "registry+https://github.com/rust-lang/crates.io-index"
1056- checksum = "ee6798b1838b6a0f69c007c133b8df5866302197e404e8b6ee8ed3e3a5e68dc6"
1057- dependencies = [
1058- "base64 0.22.1",
1059- "bytes",
1060- "crc",
1061- "crossbeam-queue",
1062- "either",
1063- "event-listener",
1064- "futures-core",
1065- "futures-intrusive",
1066- "futures-io",
1067- "futures-util",
1068- "hashbrown 0.15.5",
1069- "hashlink",
1070- "indexmap 2.14.0",
1071- "log",
1072- "memchr",
1073- "once_cell",
1074- "percent-encoding",
1075- "serde",
1076- "serde_json",
1077- "sha2",
1078- "smallvec",
1079- "thiserror 2.0.18",
1080- "tokio",
1081- "tokio-stream",
1082- "tracing",
1083- "url",
1084- ]
1085-
1086- [[package]]
1087- name = "sqlx-macros"
1088- version = "0.8.6"
1089- source = "registry+https://github.com/rust-lang/crates.io-index"
1090- checksum = "a2d452988ccaacfbf5e0bdbc348fb91d7c8af5bee192173ac3636b5fb6e6715d"
1091- dependencies = [
1092- "proc-macro2",
1093- "quote",
1094- "sqlx-core",
1095- "sqlx-macros-core",
1096- "syn",
1097- ]
1098-
1099- [[package]]
1100- name = "sqlx-macros-core"
1101- version = "0.8.6"
1102- source = "registry+https://github.com/rust-lang/crates.io-index"
1103- checksum = "19a9c1841124ac5a61741f96e1d9e2ec77424bf323962dd894bdb93f37d5219b"
1104- dependencies = [
1105- "dotenvy",
1106- "either",
1107- "heck",
1108- "hex",
1109- "once_cell",
1110- "proc-macro2",
1111- "quote",
1112- "serde",
1113- "serde_json",
1114- "sha2",
1115- "sqlx-core",
1116- "sqlx-mysql",
1117- "sqlx-postgres",
1118- "sqlx-sqlite",
1119- "syn",
1120- "tokio",
1121- "url",
1122- ]
1123-
1124- [[package]]
1125- name = "sqlx-mysql"
1126- version = "0.8.6"
1127- source = "registry+https://github.com/rust-lang/crates.io-index"
1128- checksum = "aa003f0038df784eb8fecbbac13affe3da23b45194bd57dba231c8f48199c526"
1129- dependencies = [
1130- "atoi",
1131- "base64 0.22.1",
1132- "bitflags",
1133- "byteorder",
1134- "bytes",
1135- "crc",
1136- "digest",
1137- "dotenvy",
1138- "either",
1139- "futures-channel",
1140- "futures-core",
1141- "futures-io",
1142- "futures-util",
1143- "generic-array",
1144- "hex",
1145- "hkdf",
1146- "hmac",
1147- "itoa",
1148- "log",
1149- "md-5",
1150- "memchr",
1151- "once_cell",
1152- "percent-encoding",
1153- "rand 0.8.6",
1154- "rsa",
1155- "serde",
1156- "sha1",
1157- "sha2",
1158- "smallvec",
1159- "sqlx-core",
1160- "stringprep",
1161- "thiserror 2.0.18",
1162- "tracing",
1163- "whoami",
1164- ]
1165-
1166- [[package]]
1167- name = "sqlx-postgres"
1168- version = "0.8.6"
1169- source = "registry+https://github.com/rust-lang/crates.io-index"
1170- checksum = "db58fcd5a53cf07c184b154801ff91347e4c30d17a3562a635ff028ad5deda46"
1171- dependencies = [
1172- "atoi",
1173- "base64 0.22.1",
1174- "bitflags",
1175- "byteorder",
1176- "crc",
1177- "dotenvy",
1178- "etcetera",
1179- "futures-channel",
1180- "futures-core",
1181- "futures-util",
1182- "hex",
1183- "hkdf",
1184- "hmac",
1185- "home",
1186- "itoa",
1187- "log",
1188- "md-5",
1189- "memchr",
1190- "once_cell",
1191- "rand 0.8.6",
1192- "serde",
1193- "serde_json",
1194- "sha2",
1195- "smallvec",
1196- "sqlx-core",
1197- "stringprep",
1198- "thiserror 2.0.18",
1199- "tracing",
1200- "whoami",
1201- ]
1202-
1203- [[package]]
1204- name = "sqlx-sqlite"
1205- version = "0.8.6"
1206+ name = "sqlite-wasm-rs"
1207+ version = "0.5.3"
1208 source = "registry+https://github.com/rust-lang/crates.io-index"
1209- checksum = "c2d12fe70b2c1b4401038055f90f151b78208de1f9f89a7dbfd41587a10c3eea"
1210+ checksum = "1b2c760607300407ddeaee518acf28c795661b7108c75421303dbefb237d3a36"
1211 dependencies = [
1212- "atoi",
1213- "flume",
1214- "futures-channel",
1215- "futures-core",
1216- "futures-executor",
1217- "futures-intrusive",
1218- "futures-util",
1219- "libsqlite3-sys",
1220- "log",
1221- "percent-encoding",
1222- "serde",
1223- "serde_urlencoded",
1224- "sqlx-core",
1225- "thiserror 2.0.18",
1226- "tracing",
1227- "url",
1228+ "cc",
1229+ "js-sys",
1230+ "rsqlite-vfs",
1231+ "wasm-bindgen",
1232 ]
1233
1234 [[package]]
1235 @@ -3094,17 +2853,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
1236 checksum = "2b2231b7c3057d5e4ad0156fb3dc807d900806020c5ffa3ee6ff2c8c76fb8520"
1237
1238 [[package]]
1239- name = "stringprep"
1240- version = "0.1.5"
1241- source = "registry+https://github.com/rust-lang/crates.io-index"
1242- checksum = "7b4df3d392d81bd458a8a621b8bffbd2302a12ffe288a9d931670948749463b1"
1243- dependencies = [
1244- "unicode-bidi",
1245- "unicode-normalization",
1246- "unicode-properties",
1247- ]
1248-
1249- [[package]]
1250 name = "strsim"
1251 version = "0.11.1"
1252 source = "registry+https://github.com/rust-lang/crates.io-index"
1253 @@ -3285,9 +3033,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20"
1254
1255 [[package]]
1256 name = "tokio"
1257- version = "1.52.1"
1258+ version = "1.52.2"
1259 source = "registry+https://github.com/rust-lang/crates.io-index"
1260- checksum = "b67dee974fe86fd92cc45b7a95fdd2f99a36a6d7b0d431a231178d3d670bbcc6"
1261+ checksum = "110a78583f19d5cdb2c5ccf321d1290344e71313c6c37d43520d386027d18386"
1262 dependencies = [
1263 "bytes",
1264 "libc",
1265 @@ -3353,12 +3101,25 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
1266 checksum = "dc1beb996b9d83529a9e75c17a1686767d148d70663143c7854d8b4a09ced362"
1267 dependencies = [
1268 "serde",
1269- "serde_spanned",
1270- "toml_datetime",
1271+ "serde_spanned 0.6.9",
1272+ "toml_datetime 0.6.11",
1273 "toml_edit",
1274 ]
1275
1276 [[package]]
1277+ name = "toml"
1278+ version = "0.9.12+spec-1.1.0"
1279+ source = "registry+https://github.com/rust-lang/crates.io-index"
1280+ checksum = "cf92845e79fc2e2def6a5d828f0801e29a2f8acc037becc5ab08595c7d5e9863"
1281+ dependencies = [
1282+ "serde_core",
1283+ "serde_spanned 1.1.1",
1284+ "toml_datetime 0.7.5+spec-1.1.0",
1285+ "toml_parser",
1286+ "winnow 0.7.15",
1287+ ]
1288+
1289+ [[package]]
1290 name = "toml_datetime"
1291 version = "0.6.11"
1292 source = "registry+https://github.com/rust-lang/crates.io-index"
1293 @@ -3368,6 +3129,15 @@ dependencies = [
1294 ]
1295
1296 [[package]]
1297+ name = "toml_datetime"
1298+ version = "0.7.5+spec-1.1.0"
1299+ source = "registry+https://github.com/rust-lang/crates.io-index"
1300+ checksum = "92e1cfed4a3038bc5a127e35a2d360f145e1f4b971b551a2ba5fd7aedf7e1347"
1301+ dependencies = [
1302+ "serde_core",
1303+ ]
1304+
1305+ [[package]]
1306 name = "toml_edit"
1307 version = "0.22.27"
1308 source = "registry+https://github.com/rust-lang/crates.io-index"
1309 @@ -3375,10 +3145,19 @@ checksum = "41fe8c660ae4257887cf66394862d21dbca4a6ddd26f04a3560410406a2f819a"
1310 dependencies = [
1311 "indexmap 2.14.0",
1312 "serde",
1313- "serde_spanned",
1314- "toml_datetime",
1315+ "serde_spanned 0.6.9",
1316+ "toml_datetime 0.6.11",
1317 "toml_write",
1318- "winnow",
1319+ "winnow 0.7.15",
1320+ ]
1321+
1322+ [[package]]
1323+ name = "toml_parser"
1324+ version = "1.1.2+spec-1.1.0"
1325+ source = "registry+https://github.com/rust-lang/crates.io-index"
1326+ checksum = "a2abe9b86193656635d2411dc43050282ca48aa31c2451210f4202550afb7526"
1327+ dependencies = [
1328+ "winnow 1.0.2",
1329 ]
1330
1331 [[package]]
1332 @@ -3553,12 +3332,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
1333 checksum = "dbc4bc3a9f746d862c45cb89d705aa10f187bb96c76001afab07a0d35ce60142"
1334
1335 [[package]]
1336- name = "unicode-bidi"
1337- version = "0.3.18"
1338- source = "registry+https://github.com/rust-lang/crates.io-index"
1339- checksum = "5c1cb5db39152898a79168971543b1cb5020dff7fe43c8dc468b0885f5e29df5"
1340-
1341- [[package]]
1342 name = "unicode-ident"
1343 version = "1.0.24"
1344 source = "registry+https://github.com/rust-lang/crates.io-index"
1345 @@ -3574,12 +3347,6 @@ dependencies = [
1346 ]
1347
1348 [[package]]
1349- name = "unicode-properties"
1350- version = "0.1.4"
1351- source = "registry+https://github.com/rust-lang/crates.io-index"
1352- checksum = "7df058c713841ad818f1dc5d3fd88063241cc61f49f5fbea4b951e8cf5a8d71d"
1353-
1354- [[package]]
1355 name = "unicode-segmentation"
1356 version = "1.13.2"
1357 source = "registry+https://github.com/rust-lang/crates.io-index"
1358 @@ -3702,12 +3469,6 @@ dependencies = [
1359 ]
1360
1361 [[package]]
1362- name = "wasite"
1363- version = "0.1.0"
1364- source = "registry+https://github.com/rust-lang/crates.io-index"
1365- checksum = "b8dad83b4f25e74f184f64c43b150b91efe7647395b42289f38e50566d82855b"
1366-
1367- [[package]]
1368 name = "wasm-bindgen"
1369 version = "0.2.120"
1370 source = "registry+https://github.com/rust-lang/crates.io-index"
1371 @@ -3820,6 +3581,16 @@ dependencies = [
1372 ]
1373
1374 [[package]]
1375+ name = "web-time"
1376+ version = "1.1.0"
1377+ source = "registry+https://github.com/rust-lang/crates.io-index"
1378+ checksum = "5a6580f308b1fad9207618087a65c04e7a10bc77e02c8e84e9b00dd4b12fa0bb"
1379+ dependencies = [
1380+ "js-sys",
1381+ "wasm-bindgen",
1382+ ]
1383+
1384+ [[package]]
1385 name = "webfinger-rs"
1386 version = "0.0.24"
1387 source = "registry+https://github.com/rust-lang/crates.io-index"
1388 @@ -3849,16 +3620,6 @@ dependencies = [
1389 ]
1390
1391 [[package]]
1392- name = "whoami"
1393- version = "1.6.1"
1394- source = "registry+https://github.com/rust-lang/crates.io-index"
1395- checksum = "5d4a4db5077702ca3015d3d02d74974948aba2ad9e12ab7df718ee64ccd7e97d"
1396- dependencies = [
1397- "libredox",
1398- "wasite",
1399- ]
1400-
1401- [[package]]
1402 name = "winapi-util"
1403 version = "0.1.11"
1404 source = "registry+https://github.com/rust-lang/crates.io-index"
1405 @@ -3928,20 +3689,11 @@ dependencies = [
1406
1407 [[package]]
1408 name = "windows-sys"
1409- version = "0.48.0"
1410- source = "registry+https://github.com/rust-lang/crates.io-index"
1411- checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9"
1412- dependencies = [
1413- "windows-targets 0.48.5",
1414- ]
1415-
1416- [[package]]
1417- name = "windows-sys"
1418 version = "0.52.0"
1419 source = "registry+https://github.com/rust-lang/crates.io-index"
1420 checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d"
1421 dependencies = [
1422- "windows-targets 0.52.6",
1423+ "windows-targets",
1424 ]
1425
1426 [[package]]
1427 @@ -3950,7 +3702,7 @@ version = "0.59.0"
1428 source = "registry+https://github.com/rust-lang/crates.io-index"
1429 checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b"
1430 dependencies = [
1431- "windows-targets 0.52.6",
1432+ "windows-targets",
1433 ]
1434
1435 [[package]]
1436 @@ -3964,67 +3716,34 @@ dependencies = [
1437
1438 [[package]]
1439 name = "windows-targets"
1440- version = "0.48.5"
1441- source = "registry+https://github.com/rust-lang/crates.io-index"
1442- checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c"
1443- dependencies = [
1444- "windows_aarch64_gnullvm 0.48.5",
1445- "windows_aarch64_msvc 0.48.5",
1446- "windows_i686_gnu 0.48.5",
1447- "windows_i686_msvc 0.48.5",
1448- "windows_x86_64_gnu 0.48.5",
1449- "windows_x86_64_gnullvm 0.48.5",
1450- "windows_x86_64_msvc 0.48.5",
1451- ]
1452-
1453- [[package]]
1454- name = "windows-targets"
1455 version = "0.52.6"
1456 source = "registry+https://github.com/rust-lang/crates.io-index"
1457 checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973"
1458 dependencies = [
1459- "windows_aarch64_gnullvm 0.52.6",
1460- "windows_aarch64_msvc 0.52.6",
1461- "windows_i686_gnu 0.52.6",
1462+ "windows_aarch64_gnullvm",
1463+ "windows_aarch64_msvc",
1464+ "windows_i686_gnu",
1465 "windows_i686_gnullvm",
1466- "windows_i686_msvc 0.52.6",
1467- "windows_x86_64_gnu 0.52.6",
1468- "windows_x86_64_gnullvm 0.52.6",
1469- "windows_x86_64_msvc 0.52.6",
1470+ "windows_i686_msvc",
1471+ "windows_x86_64_gnu",
1472+ "windows_x86_64_gnullvm",
1473+ "windows_x86_64_msvc",
1474 ]
1475
1476 [[package]]
1477 name = "windows_aarch64_gnullvm"
1478- version = "0.48.5"
1479- source = "registry+https://github.com/rust-lang/crates.io-index"
1480- checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8"
1481-
1482- [[package]]
1483- name = "windows_aarch64_gnullvm"
1484 version = "0.52.6"
1485 source = "registry+https://github.com/rust-lang/crates.io-index"
1486 checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3"
1487
1488 [[package]]
1489 name = "windows_aarch64_msvc"
1490- version = "0.48.5"
1491- source = "registry+https://github.com/rust-lang/crates.io-index"
1492- checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc"
1493-
1494- [[package]]
1495- name = "windows_aarch64_msvc"
1496 version = "0.52.6"
1497 source = "registry+https://github.com/rust-lang/crates.io-index"
1498 checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469"
1499
1500 [[package]]
1501 name = "windows_i686_gnu"
1502- version = "0.48.5"
1503- source = "registry+https://github.com/rust-lang/crates.io-index"
1504- checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e"
1505-
1506- [[package]]
1507- name = "windows_i686_gnu"
1508 version = "0.52.6"
1509 source = "registry+https://github.com/rust-lang/crates.io-index"
1510 checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b"
1511 @@ -4037,48 +3756,24 @@ checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66"
1512
1513 [[package]]
1514 name = "windows_i686_msvc"
1515- version = "0.48.5"
1516- source = "registry+https://github.com/rust-lang/crates.io-index"
1517- checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406"
1518-
1519- [[package]]
1520- name = "windows_i686_msvc"
1521 version = "0.52.6"
1522 source = "registry+https://github.com/rust-lang/crates.io-index"
1523 checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66"
1524
1525 [[package]]
1526 name = "windows_x86_64_gnu"
1527- version = "0.48.5"
1528- source = "registry+https://github.com/rust-lang/crates.io-index"
1529- checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e"
1530-
1531- [[package]]
1532- name = "windows_x86_64_gnu"
1533 version = "0.52.6"
1534 source = "registry+https://github.com/rust-lang/crates.io-index"
1535 checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78"
1536
1537 [[package]]
1538 name = "windows_x86_64_gnullvm"
1539- version = "0.48.5"
1540- source = "registry+https://github.com/rust-lang/crates.io-index"
1541- checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc"
1542-
1543- [[package]]
1544- name = "windows_x86_64_gnullvm"
1545 version = "0.52.6"
1546 source = "registry+https://github.com/rust-lang/crates.io-index"
1547 checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d"
1548
1549 [[package]]
1550 name = "windows_x86_64_msvc"
1551- version = "0.48.5"
1552- source = "registry+https://github.com/rust-lang/crates.io-index"
1553- checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538"
1554-
1555- [[package]]
1556- name = "windows_x86_64_msvc"
1557 version = "0.52.6"
1558 source = "registry+https://github.com/rust-lang/crates.io-index"
1559 checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
1560 @@ -4093,6 +3788,12 @@ dependencies = [
1561 ]
1562
1563 [[package]]
1564+ name = "winnow"
1565+ version = "1.0.2"
1566+ source = "registry+https://github.com/rust-lang/crates.io-index"
1567+ checksum = "2ee1708bef14716a11bae175f579062d4554d95be2c6829f518df847b7b3fdd0"
1568+
1569+ [[package]]
1570 name = "wit-bindgen"
1571 version = "0.51.0"
1572 source = "registry+https://github.com/rust-lang/crates.io-index"
1573 diff --git a/Cargo.toml b/Cargo.toml
1574index 4ce2ac2..161fdb1 100644
1575--- a/Cargo.toml
1576+++ b/Cargo.toml
1577 @@ -36,8 +36,8 @@ futures = "0.3.31"
1578 tracing-subscriber = { version = "0.3.22", features = ["env-filter"] }
1579 openssh-keys = "0.6.5"
1580 url = { version = "2.5.8", features = ["serde"]}
1581- sqlx = { version = "0.8.6", features = ["runtime-tokio", "sqlite"] }
1582- reqwest = { version = "0.13.3", default-features = false, features = ["json", "http2", "stream"] }
1583+
1584+ reqwest = { version = "0.13.3", default-features = false, features = ["rustls", "json", "http2", "stream"] }
1585
1586 tokio = { version = "1.49.0", features = ["full"] }
1587 tokio-util = { version = "0.7.18", features = ["io", "compat"] }
1588 diff --git a/ayllu-build/Cargo.toml b/ayllu-build/Cargo.toml
1589index 3c29a8b..795ba2a 100644
1590--- a/ayllu-build/Cargo.toml
1591+++ b/ayllu-build/Cargo.toml
1592 @@ -15,16 +15,14 @@ ayllu_logging = {path = "../crates/logging"}
1593 ayllu_git = {path = "../crates/git"}
1594 ayllu_cmd = {path = "../crates/cmd"}
1595
1596- async-trait = { workspace = true }
1597 bytes = { workspace = true }
1598 tracing = { workspace = true }
1599 serde = { workspace = true }
1600 petgraph = { workspace = true }
1601- tokio = { workspace = true }
1602- tokio-util = { workspace = true }
1603- futures = { workspace = true }
1604 thiserror = { workspace = true }
1605 reqwest = { workspace = true }
1606+ tokio = { workspace = true }
1607+ tokio-util = { workspace = true }
1608
1609 serde_json = "1.0.149"
1610 tar = "0.4.45"
1611 diff --git a/ayllu-build/src/database_ext.rs b/ayllu-build/src/database_ext.rs
1612deleted file mode 100644
1613index f3fdc45..0000000
1614--- a/ayllu-build/src/database_ext.rs
1615+++ /dev/null
1616 @@ -1,269 +0,0 @@
1617- use std::collections::HashMap;
1618-
1619- use async_trait::async_trait;
1620-
1621-
1622-
1623- use ayllu_database::{Error, Wrapper as Database};
1624-
1625- #[derive(Clone)]
1626- // everything needed to populate the build status page
1627- pub struct Normalized {
1628- pub manifest: Manifest,
1629- pub samples: Vec<Sample>,
1630- pub workflows: Vec<Workflow>,
1631- // TODO: for huge builds this maybe should be broken up into seperate
1632- // calls via the API. For now everything is displayed together on a single
1633- // huge page.
1634- pub steps: Vec<Vec<Step>>,
1635- }
1636-
1637- #[derive(Clone, Default)]
1638- pub struct Sample {
1639- pub id: i64,
1640- pub load_1m: Option<i64>,
1641- pub load_5m: Option<i64>,
1642- pub load_15m: Option<i64>,
1643- pub disk_total_bytes: Option<i64>,
1644- pub disk_free_bytes: Option<i64>,
1645- pub net_sent_bytes: Option<i64>,
1646- pub net_received_bytes: Option<i64>,
1647- pub net_sent_packets: Option<i64>,
1648- pub net_received_packets: Option<i64>,
1649- pub mem_total_bytes: Option<i64>,
1650- pub mem_free_bytes: Option<i64>,
1651- pub mem_available_bytes: Option<i64>,
1652- }
1653-
1654- #[derive(Clone)]
1655- pub struct Manifest {
1656- pub id: i64,
1657- pub repository_url: String,
1658- pub git_hash: String,
1659- pub created_at: i64,
1660- pub started_at: Option<i64>,
1661- pub duration: Option<i64>,
1662- pub dot_content: String,
1663- }
1664-
1665- // TODO
1666- //
1667- #[derive(Clone)]
1668- pub struct Workflow {
1669- pub id: i64,
1670- pub name: String,
1671- pub started_at: Option<i64>,
1672- pub duration: Option<i64>,
1673- }
1674-
1675- #[derive(Clone)]
1676- pub struct Step {
1677- pub id: i64,
1678- pub name: String,
1679- pub input: String,
1680- pub stdout: Option<String>,
1681- pub stderr: Option<String>,
1682- pub started_at: Option<i64>,
1683- pub duration: Option<i64>,
1684- pub exit_code: Option<i64>,
1685- }
1686-
1687- #[async_trait]
1688- pub trait BuildExt {
1689- // manifests
1690- async fn create_manifest(&self, repository_url: &str, git_hash: &str) -> Result<i64, Error>;
1691- async fn update_manifest_start(&self, manifest_id: i64) -> Result<(), Error>;
1692- async fn update_manifest_finish(&self, manifest_id: i64, duration: i64) -> Result<(), Error>;
1693- async fn list_manifest(&self, limit: i64, offset: i64) -> Result<Vec<Manifest>, Error>;
1694- async fn read_manifest(&self, manifest_id: i64) -> Result<Normalized, Error>;
1695-
1696- // workflows
1697- async fn create_workflow(&self, manifest_id: i64, name: &str) -> Result<i64, Error>;
1698- async fn update_workflow_start(&self, workflow_id: i64) -> Result<(), Error>;
1699- async fn update_workflow_finish(&self, workflow_id: i64, duration: i64) -> Result<(), Error>;
1700- // steps
1701- async fn create_step(
1702- &self,
1703- job_id: i64,
1704- name: &str,
1705- input: &str,
1706- shell: &str,
1707- environment: HashMap<String, String>,
1708- ) -> Result<i64, Error>;
1709- async fn update_step_start(&self, step_id: i64) -> Result<(), Error>;
1710- async fn update_step_finish(
1711- &self,
1712- step_id: i64,
1713- duration: i64,
1714- stdout: &str,
1715- stderr: &str,
1716- exit_code: i8,
1717- ) -> Result<(), Error>;
1718-
1719- // misc
1720- async fn create_sample(&self, manifest_id: i64, sample: Sample) -> Result<i64, Error>;
1721- async fn create_dag(&self, manifest_id: i64, dot_content: &str) -> Result<i64, Error>;
1722- }
1723-
1724- #[async_trait]
1725- impl BuildExt for Database {
1726- // manifests
1727- async fn create_manifest(&self, repository_url: &str, git_hash: &str) -> Result<i64, Error> {
1728- let ret = sqlx::query_file!("queries/manifests_create.sql", repository_url, git_hash)
1729- .fetch_one(&self.pool)
1730- .await?;
1731- Ok(ret.id)
1732- }
1733-
1734- async fn update_manifest_start(&self, manifest_id: i64) -> Result<(), Error> {
1735- sqlx::query_file!("queries/manifests_update_start.sql", manifest_id,)
1736- .execute(&self.pool)
1737- .await?;
1738- Ok(())
1739- }
1740-
1741- async fn update_manifest_finish(&self, manifest_id: i64, duration: i64) -> Result<(), Error> {
1742- sqlx::query_file!("queries/manifests_update_finish.sql", duration, manifest_id)
1743- .execute(&self.pool)
1744- .await?;
1745- Ok(())
1746- }
1747-
1748- async fn list_manifest(&self, _limit: i64, _offset: i64) -> Result<Vec<Manifest>, Error> {
1749- todo!()
1750- }
1751-
1752- // return a fully normalized manifest with everything needed to return to
1753- // the RPC client
1754- async fn read_manifest(&self, manifest_id: i64) -> Result<Normalized, Error> {
1755- // TODO: evaluation order needs to be preserved
1756- let manifest = sqlx::query_file_as!(Manifest, "queries/manifests_read.sql", manifest_id)
1757- .fetch_one(&self.pool)
1758- .await?;
1759- let samples: Vec<Sample> =
1760- sqlx::query_file_as!(Sample, "queries/samples_read.sql", manifest_id)
1761- .fetch_all(&self.pool)
1762- .await?;
1763- let workflows = sqlx::query_file_as!(Workflow, "queries/workflows_list.sql", manifest_id)
1764- .fetch_all(&self.pool)
1765- .await?;
1766- let mut all_steps: Vec<Vec<Step>> = Vec::new();
1767- for workflow in workflows.iter() {
1768- let workflow_id = workflow.id;
1769- let steps = sqlx::query_file_as!(Step, "queries/steps_list.sql", workflow_id)
1770- .fetch_all(&self.pool)
1771- .await?;
1772- all_steps.push(steps);
1773- }
1774- Ok(Normalized {
1775- manifest,
1776- samples,
1777- workflows,
1778- steps: all_steps,
1779- })
1780- }
1781-
1782- // workflows
1783-
1784- async fn create_workflow(&self, manifest_id: i64, name: &str) -> Result<i64, Error> {
1785- let ret = sqlx::query_file!("queries/workflows_create.sql", manifest_id, name)
1786- .fetch_one(&self.pool)
1787- .await?;
1788- Ok(ret.id)
1789- }
1790-
1791- async fn update_workflow_start(&self, workflow_id: i64) -> Result<(), Error> {
1792- sqlx::query_file!("queries/workflows_update_start.sql", workflow_id,)
1793- .execute(&self.pool)
1794- .await?;
1795- Ok(())
1796- }
1797-
1798- async fn update_workflow_finish(&self, workflow_id: i64, duration: i64) -> Result<(), Error> {
1799- sqlx::query_file!("queries/workflows_update_finish.sql", duration, workflow_id)
1800- .execute(&self.pool)
1801- .await?;
1802- Ok(())
1803- }
1804-
1805- // steps
1806-
1807- async fn create_step(
1808- &self,
1809- workflow_id: i64,
1810- name: &str,
1811- input: &str,
1812- shell: &str,
1813- environment: HashMap<String, String>,
1814- ) -> Result<i64, Error> {
1815- let environment_json = serde_json::ser::to_string(&environment).unwrap();
1816- let ret = sqlx::query_file!(
1817- "queries/steps_create.sql",
1818- workflow_id,
1819- name,
1820- input,
1821- shell,
1822- environment_json
1823- )
1824- .fetch_one(&self.pool)
1825- .await?;
1826- Ok(ret.id)
1827- }
1828-
1829- async fn update_step_start(&self, step_id: i64) -> Result<(), Error> {
1830- sqlx::query_file!("queries/steps_update_start.sql", step_id)
1831- .execute(&self.pool)
1832- .await?;
1833- Ok(())
1834- }
1835-
1836- async fn update_step_finish(
1837- &self,
1838- step_id: i64,
1839- duration: i64,
1840- stdout: &str,
1841- stderr: &str,
1842- exit_code: i8,
1843- ) -> Result<(), Error> {
1844- sqlx::query_file!(
1845- "queries/steps_update_finish.sql",
1846- duration,
1847- stdout,
1848- stderr,
1849- exit_code,
1850- step_id,
1851- )
1852- .execute(&self.pool)
1853- .await?;
1854- Ok(())
1855- }
1856-
1857- async fn create_sample(&self, manifest_id: i64, sample: Sample) -> Result<i64, Error> {
1858- let ret = sqlx::query_file!(
1859- "queries/samples_create.sql",
1860- manifest_id,
1861- sample.load_1m,
1862- sample.load_5m,
1863- sample.load_15m,
1864- sample.disk_total_bytes,
1865- sample.disk_free_bytes,
1866- sample.net_sent_bytes,
1867- sample.net_received_bytes,
1868- sample.net_sent_packets,
1869- sample.net_received_packets,
1870- sample.mem_total_bytes,
1871- sample.mem_free_bytes,
1872- sample.mem_available_bytes,
1873- )
1874- .fetch_one(&self.pool)
1875- .await?;
1876- Ok(ret.id)
1877- }
1878-
1879- async fn create_dag(&self, manifest_id: i64, dot_content: &str) -> Result<i64, Error> {
1880- let ret = sqlx::query_file!("queries/dags_create.sql", manifest_id, dot_content,)
1881- .fetch_one(&self.pool)
1882- .await?;
1883- Ok(ret.id)
1884- }
1885- }
1886 diff --git a/ayllu-build/src/error.rs b/ayllu-build/src/error.rs
1887index bc9a3d1..9296349 100644
1888--- a/ayllu-build/src/error.rs
1889+++ b/ayllu-build/src/error.rs
1890 @@ -25,10 +25,11 @@ pub enum Error {
1891 name: String,
1892 },
1893 DuplicateWorkflows,
1894+ Database(ayllu_database::Error),
1895 // general io error during execution
1896 Io(std::io::Error),
1897- Db(ayllu_database::Error),
1898- Libpod(crate::libpod::Error),
1899+ Reqwest(reqwest::Error),
1900+ Libpod(crate::libpod::ErrorMessage),
1901 }
1902
1903 impl std::fmt::Display for Error {
1904 @@ -45,7 +46,6 @@ impl std::fmt::Display for Error {
1905 Error::DuplicateStepNames { name } => write!(f, "Duplicate step detected: {name}"),
1906 Error::DuplicateWorkflows => write!(f, "Duplicate Workflows!"),
1907 Error::Io(error) => write!(f, "Io Error: {error}"),
1908- Error::Db(error) => write!(f, "SQL Error: {error}"),
1909 Error::CannotReadManifestFromRepository { path, repo_err } => write!(
1910 f,
1911 "Cannot read manifest from repository {path:?}: {repo_err}"
1912 @@ -53,9 +53,13 @@ impl std::fmt::Display for Error {
1913 Error::RepositoryDoesNotContainManifest { path } => {
1914 write!(f, "Repository {path:?} does not contain a manifest")
1915 }
1916- Error::Libpod(err) => {
1917- write!(f, "Libpod failure: {err:?}")
1918- }
1919+ Error::Database(error) => write!(f, "Database error: {error}"),
1920+ Error::Reqwest(error) => write!(f, "Error making libpod request: {error:?}"),
1921+ Error::Libpod(error_message) => write!(
1922+ f,
1923+ "Libpod API Error: {}: {} {}",
1924+ error_message.cause, error_message.message, error_message.response
1925+ ),
1926 }
1927 }
1928 }
1929 @@ -64,7 +68,7 @@ impl std::error::Error for Error {}
1930
1931 impl From<ayllu_database::Error> for Error {
1932 fn from(value: ayllu_database::Error) -> Self {
1933- Error::Db(value)
1934+ Self::Database(value)
1935 }
1936 }
1937
1938 @@ -73,3 +77,9 @@ impl From<std::io::Error> for Error {
1939 Error::Io(value)
1940 }
1941 }
1942+
1943+ impl From<reqwest::Error> for Error {
1944+ fn from(value: reqwest::Error) -> Self {
1945+ Self::Reqwest(value)
1946+ }
1947+ }
1948 diff --git a/ayllu-build/src/evaluate.rs b/ayllu-build/src/evaluate.rs
1949index a4f3e1e..6eeed7b 100644
1950--- a/ayllu-build/src/evaluate.rs
1951+++ b/ayllu-build/src/evaluate.rs
1952 @@ -2,6 +2,7 @@ use std::fs::metadata;
1953 use std::path::PathBuf;
1954 use std::{collections::HashMap, path::Path};
1955
1956+ use ayllu_database::build::workflows::CreateWorkflowArgs;
1957 use petgraph::{
1958 algo::is_cyclic_directed,
1959 dot::Dot,
1960 @@ -9,22 +10,16 @@ use petgraph::{
1961 visit::Topo,
1962 };
1963
1964- use crate::executor::InitializeArgs;
1965+ use crate::libpod_executor::InitializeArgs;
1966 use crate::package::Package;
1967 use crate::{
1968 DEFAULT_BUILD_FILE,
1969 models::{Manifest, Step},
1970 };
1971- use crate::{
1972- error::Error,
1973- executor::{Context, Executor},
1974- };
1975- use ayllu_database::{
1976- Tx, Wrapper as Database,
1977- build::{BuildExt, BuildTx},
1978- };
1979+ use crate::{error::Error, libpod_executor::Context};
1980
1981 use ayllu_api::build::Unit;
1982+ use ayllu_database::Wrapper as Database;
1983
1984 pub type BuildGraph = Graph<Unit, u8>;
1985
1986 @@ -121,13 +116,6 @@ impl Runtime {
1987 }
1988 }
1989
1990- fn source_dir(&self) -> &Path {
1991- match self.source {
1992- Source::Path(ref path_buf) => path_buf.as_path(),
1993- _ => todo!(),
1994- }
1995- }
1996-
1997 // FIXME: Fundementally broken, assumes everything is a git repo with fragile paths
1998 fn git_information(&self) -> Result<(String, String, String), ayllu_git::Error> {
1999 let manifest_path = match &self.source {
2000 @@ -147,110 +135,107 @@ impl Runtime {
2001 }
2002
2003 /// Allocate the manifest in the database along with it's DAG
2004- async fn allocate(&self, tx: &mut Tx) -> Result<(i64, BuildGraph), Error> {
2005+ fn allocate(&mut self) -> Result<(i32, BuildGraph), Error> {
2006 let manifest = self.setup()?;
2007 manifest.validate()?;
2008 let (collection, name, git_hash) = self.git_information().unwrap();
2009- let manifest_id = tx.create_manifest(&collection, &name, &git_hash).await?;
2010- let mut workflows_by_name: HashMap<String, NodeIndex> = HashMap::new();
2011- let mut steps_by_name: HashMap<String, HashMap<String, NodeIndex>> = HashMap::new();
2012- let mut graph: BuildGraph = Graph::new();
2013- // Allocate components into the database
2014- for workflow in manifest.workflows.iter() {
2015- if workflow.steps.is_empty() {
2016- return Err(Error::EmptyWorkflow {
2017- name: workflow.name.clone(),
2018- });
2019- }
2020- let workflow_id = tx
2021- .create_workflow(manifest_id, &workflow.name, &workflow.image)
2022- .await?;
2023- workflows_by_name.insert(
2024- workflow.name.clone(),
2025- graph.add_node(Unit::Workflow(workflow_id)),
2026- );
2027- let mut by_name: HashMap<String, NodeIndex> = HashMap::new();
2028- for step in workflow.steps.iter() {
2029- let step_id = tx
2030- .create_step(
2031- workflow_id,
2032- &step.name,
2033- &step.input,
2034- &step.shell,
2035- step.environment.clone(),
2036- )
2037- .await?;
2038- by_name.insert(step.name.clone(), graph.add_node(Unit::Step(step_id)));
2039+ self.db.with(move |mut tx| {
2040+ let manifest_id = tx.manifest_create(&collection, &name, &git_hash)?;
2041+
2042+ let mut workflows_by_name: HashMap<String, NodeIndex> = HashMap::new();
2043+ let mut steps_by_name: HashMap<String, HashMap<String, NodeIndex>> = HashMap::new();
2044+ let mut graph: BuildGraph = Graph::new();
2045+ // Allocate components into the database
2046+ for workflow in manifest.workflows.iter() {
2047+ if workflow.steps.is_empty() {
2048+ return Err(Error::EmptyWorkflow {
2049+ name: workflow.name.clone(),
2050+ });
2051+ }
2052+ let workflow_id = tx.workflow_create(&CreateWorkflowArgs {
2053+ name: &workflow.name,
2054+ image: &workflow.image,
2055+ manifest_id,
2056+ })?;
2057+ workflows_by_name.insert(
2058+ workflow.name.clone(),
2059+ graph.add_node(Unit::Workflow(workflow_id)),
2060+ );
2061+ let mut by_name: HashMap<String, NodeIndex> = HashMap::new();
2062+ for step in workflow.steps.iter() {
2063+ let step_id =
2064+ tx.step_create(&ayllu_database::build::steps::CreateStepArgs {
2065+ name: &step.name,
2066+ shell: &step.shell,
2067+ input: &step.input,
2068+ workflow_id,
2069+ })?;
2070+ by_name.insert(step.name.clone(), graph.add_node(Unit::Step(step_id)));
2071+ }
2072+ steps_by_name.insert(workflow.name.clone(), by_name);
2073 }
2074- steps_by_name.insert(workflow.name.clone(), by_name);
2075- }
2076- // set edges for steps from each workflow
2077- for workflow in manifest.workflows.iter() {
2078- let workflow_steps = steps_by_name.get(&workflow.name).unwrap();
2079- for step in workflow.steps.iter() {
2080- let step_index = workflow_steps.get(&step.name).unwrap();
2081- for dependency in step.depends_on.iter() {
2082- let dependency_index = workflow_steps.get(dependency).unwrap();
2083- graph.add_edge(*dependency_index, *step_index, 0);
2084+ // set edges for steps from each workflow
2085+ for workflow in manifest.workflows.iter() {
2086+ let workflow_steps = steps_by_name.get(&workflow.name).unwrap();
2087+ for step in workflow.steps.iter() {
2088+ let step_index = workflow_steps.get(&step.name).unwrap();
2089+ for dependency in step.depends_on.iter() {
2090+ let dependency_index = workflow_steps.get(dependency).unwrap();
2091+ graph.add_edge(*dependency_index, *step_index, 0);
2092+ }
2093 }
2094 }
2095- }
2096
2097- for workflow in manifest.workflows.iter() {
2098- let workflow_index = workflows_by_name.get(&workflow.name).unwrap();
2099- let steps_with_no_dependencies: Vec<&Step> = workflow
2100- .steps
2101- .iter()
2102- .filter(|step| step.depends_on.is_empty())
2103- .collect();
2104- for step in steps_with_no_dependencies.iter() {
2105- let step_index = steps_by_name
2106- .get(&workflow.name)
2107- .unwrap()
2108- .get(&step.name)
2109- .unwrap();
2110- graph.add_edge(*workflow_index, *step_index, 0);
2111+ for workflow in manifest.workflows.iter() {
2112+ let workflow_index = workflows_by_name.get(&workflow.name).unwrap();
2113+ let steps_with_no_dependencies: Vec<&Step> = workflow
2114+ .steps
2115+ .iter()
2116+ .filter(|step| step.depends_on.is_empty())
2117+ .collect();
2118+ for step in steps_with_no_dependencies.iter() {
2119+ let step_index = steps_by_name
2120+ .get(&workflow.name)
2121+ .unwrap()
2122+ .get(&step.name)
2123+ .unwrap();
2124+ graph.add_edge(*workflow_index, *step_index, 0);
2125+ }
2126 }
2127- }
2128
2129- for workflow in manifest.workflows.iter() {
2130- let workflow_index = workflows_by_name.get(&workflow.name).unwrap();
2131- for dependency in workflow.depends_on.iter() {
2132- let dependency_index = workflows_by_name.get(&dependency.clone()).unwrap();
2133- graph.add_edge(*dependency_index, *workflow_index, 1);
2134+ for workflow in manifest.workflows.iter() {
2135+ let workflow_index = workflows_by_name.get(&workflow.name).unwrap();
2136+ for dependency in workflow.depends_on.iter() {
2137+ let dependency_index = workflows_by_name.get(&dependency.clone()).unwrap();
2138+ graph.add_edge(*dependency_index, *workflow_index, 1);
2139+ }
2140 }
2141- }
2142-
2143- if is_cyclic_directed(&graph) {
2144- return Err(Error::CycleDetected);
2145- }
2146
2147- let dag_json = serde_json::ser::to_string(&graph).unwrap();
2148+ if is_cyclic_directed(&graph) {
2149+ return Err(Error::CycleDetected);
2150+ }
2151
2152- tx.create_dag(manifest_id, &dag_json).await?;
2153+ let dag_json = serde_json::ser::to_string(&graph).unwrap();
2154+ tx.manifest_set_dag_content(manifest_id, &dag_json)?;
2155
2156- Ok((manifest_id, graph))
2157+ Ok((manifest_id, graph))
2158+ })
2159 }
2160
2161 /// Allocate a job graph and then execute it sequentially
2162- pub async fn evaluate(&self, executor: &impl Executor) -> Result<i64, Error> {
2163+ pub async fn evaluate(
2164+ &mut self,
2165+ executor: &crate::libpod_executor::Libpod,
2166+ ) -> Result<i32, Error> {
2167 let this_binary = std::env::current_exe().unwrap();
2168- let mut tx = self.db.begin().await?;
2169- let (manifest_id, graph) = match self.allocate(&mut tx).await {
2170- Ok(db_op) => {
2171- tx.commit().await?;
2172- db_op
2173- }
2174- Err(e) => {
2175- tracing::error!("Failed to allocate graph: {e:?}");
2176- tx.rollback().await?;
2177- // FIXME rollback but how
2178- return Err(e);
2179- }
2180+ let (manifest_id, graph) = self.allocate()?;
2181+ let source_dir = match &self.source {
2182+ Source::Path(path_buf) => path_buf.as_path().parent().unwrap(),
2183+ _ => todo!(),
2184 };
2185 let package = Package {
2186 temp_dir: &self.work_dir.join(format!("ayllu-{manifest_id}")),
2187- source_dir: self.source_dir().parent().unwrap(),
2188+ source_dir,
2189 };
2190 let payload = package.build()?;
2191 let dot_string = Dot::new(&graph).to_string();
2192 @@ -260,25 +245,26 @@ impl Runtime {
2193 graph.node_count(),
2194 dot_string
2195 );
2196- self.db.update_manifest_start(manifest_id).await?;
2197+ let mut conn = self.db.call();
2198+ conn.manifest_start(manifest_id)?;
2199 // TODO: Currently no parallelism is supported, need to implement the
2200 // options in the manifest and then break steps into asynchronous chunks
2201 // that can be run in parallel where appropriate.
2202 let mut topo = Topo::new(&graph);
2203- let mut current_workflow: Option<i64> = None;
2204+ let mut current_workflow: Option<i32> = None;
2205 while let Some(nx) = topo.next(&graph) {
2206 let unit = &graph[nx];
2207 match unit {
2208 Unit::Workflow(next_workflow_id) => {
2209- let next_workflow = self.db.read_workflow(*next_workflow_id).await?;
2210+ let next_workflow = conn.workflow_read(*next_workflow_id)?;
2211 tracing::info!("starting workflow {}", next_workflow.name);
2212 match current_workflow.as_ref() {
2213 Some(current_workflow_id) => {
2214- self.db.update_workflow_finish(*current_workflow_id).await?;
2215- executor.shutdown(manifest_id, *current_workflow_id).await?;
2216+ conn.workflow_finish(*current_workflow_id)?;
2217+ executor.shutdown(manifest_id, *current_workflow_id)?;
2218 }
2219 None => {
2220- self.db.update_workflow_start(next_workflow.id).await?;
2221+ conn.workflow_start(*next_workflow_id)?;
2222 }
2223 };
2224 current_workflow = Some(*next_workflow_id);
2225 @@ -289,16 +275,16 @@ impl Runtime {
2226 build_dir: self.work_dir.as_path(),
2227 image: &next_workflow.image,
2228 init_args: &["/bin/sleep", "inf"],
2229- source_dir: self.source_dir().parent().unwrap(),
2230+ source_dir,
2231 absolute_ayllu_build_binary: &this_binary,
2232 src_package: &payload,
2233 })
2234 .await?;
2235 }
2236 Unit::Step(next_step_id) => {
2237- let next_step = self.db.read_step(*next_step_id).await?;
2238+ let next_step = conn.step_read(*next_step_id)?;
2239 tracing::info!("starting step: {}", next_step.name);
2240- self.db.update_step_start(next_step.id).await?;
2241+ conn.step_start(next_step.id)?;
2242 // TODO: more context
2243 let ctx = Context {
2244 manifest_id,
2245 @@ -307,20 +293,18 @@ impl Runtime {
2246 repo_url: self.source.url(),
2247 ..Default::default()
2248 };
2249- let (lines, exit_code) = executor.execute(&next_step, &ctx).await?;
2250- // let (step_id, duration) = state.current_step().unwrap();
2251- self.db
2252- .update_step_finish(*next_step_id, lines.as_slice(), exit_code as i8)
2253- .await?;
2254+ let exit_code = executor.execute(&next_step, &ctx, &mut conn).await?;
2255+ conn.step_finish(*next_step_id, exit_code)?;
2256 }
2257 }
2258 }
2259 // clean up last workflow
2260 if let Some(id) = current_workflow {
2261- self.db.update_workflow_finish(id).await?;
2262+ conn.workflow_finish(id)?;
2263 }
2264
2265- self.db.update_manifest_finish(manifest_id).await?;
2266+ // TODO also set results here to speed up UI summary
2267+ // self.db.update_manifest_finish(manifest_id).await?;
2268
2269 Ok(manifest_id)
2270 }
2271 diff --git a/ayllu-build/src/executor.rs b/ayllu-build/src/executor.rs
2272deleted file mode 100644
2273index 3880f9b..0000000
2274--- a/ayllu-build/src/executor.rs
2275+++ /dev/null
2276 @@ -1,66 +0,0 @@
2277- use std::{collections::HashMap, path::Path};
2278-
2279- use ayllu_database::build::{LogLine, Step};
2280- use serde::Deserialize;
2281-
2282- use crate::error::Error;
2283-
2284- // context exposed to the runtime of individual steps, also used in other
2285- // parts of the build process.
2286- #[derive(Deserialize, Debug, Clone, Default)]
2287- pub struct Context {
2288- pub manifest_id: i64,
2289- pub workflow_id: i64,
2290- pub step_id: i64,
2291- pub git_hash: String,
2292- pub repo_url: String,
2293- pub environment: HashMap<String, Option<String>>,
2294- }
2295-
2296- pub struct InitializeArgs<'a> {
2297- pub manifest_id: i64,
2298- pub workflow_id: i64,
2299- pub build_dir: &'a Path,
2300- pub image: &'a str,
2301- pub init_args: &'a [&'a str],
2302- pub source_dir: &'a Path,
2303- pub absolute_ayllu_build_binary: &'a Path,
2304- // path to source tree and other things needed in the ct runtime
2305- pub src_package: &'a Path,
2306- }
2307-
2308- // An executor runs the step in a process container of some kind. Currently
2309- // the only executor that exists is the Local exector.
2310- pub trait Executor {
2311- async fn initialize(&self, args: &InitializeArgs) -> Result<(), Error>;
2312- async fn execute(&self, step: &Step, context: &Context) -> Result<(Vec<LogLine>, i32), Error>;
2313- async fn shutdown(&self, manifest_id: i64, workflow_id: i64) -> Result<(), Error>;
2314- }
2315-
2316- #[cfg(test)]
2317- mod tests {
2318-
2319- // use super::*;
2320-
2321- // #[tokio::test]
2322- // async fn test_local_executor() {
2323- // let executor = Local {
2324- // temp_dir: Path::new("logs").to_path_buf(),
2325- // tee_output: true,
2326- // };
2327- // let result = executor
2328- // .execute(
2329- // &Step {
2330- // name: String::from("test"),
2331- // shell: String::from("/bin/sh"),
2332- // input: String::from("echo -n hello"),
2333- // depends_on: Vec::new(),
2334- // environment: HashMap::new(),
2335- // },
2336- // Context::default(),
2337- // )
2338- // .expect("failed to run command");
2339- // assert!(result.2.success());
2340- // assert!(result.0 == "hello");
2341- // }
2342- }
2343 diff --git a/ayllu-build/src/executor_libpod.rs b/ayllu-build/src/executor_libpod.rs
2344deleted file mode 100644
2345index 344fcc7..0000000
2346--- a/ayllu-build/src/executor_libpod.rs
2347+++ /dev/null
2348 @@ -1,150 +0,0 @@
2349- use std::{
2350- path::Path,
2351- time::{Duration, Instant},
2352- };
2353-
2354- use ayllu_database::build::LogLine;
2355- use tokio::sync::mpsc;
2356-
2357- use crate::{
2358- executor::InitializeArgs,
2359- libpod::{
2360- Client, Request,
2361- container::{self, Mount},
2362- },
2363- };
2364-
2365- pub struct Libpod {
2366- client: Client,
2367- }
2368-
2369- impl Libpod {
2370- pub fn new(socket_path: &Path) -> Self {
2371- Self {
2372- client: Client::new(socket_path),
2373- }
2374- }
2375- }
2376-
2377- impl super::executor::Executor for Libpod {
2378- async fn initialize<'a>(&self, args: &InitializeArgs<'a>) -> Result<(), crate::error::Error> {
2379- let container_name = format!("ayllu-build-{}-{}", args.manifest_id, args.workflow_id);
2380- let base_dir = args.build_dir.join(&container_name); // TODO Maybe timestamp?
2381- std::fs::create_dir_all(base_dir)?;
2382- let ayllu_binary_path_string = args
2383- .absolute_ayllu_build_binary
2384- .to_string_lossy()
2385- .to_string();
2386- assert!(
2387- self.client
2388- .call(&Request::Ping)
2389- .await?
2390- .status()
2391- .is_success()
2392- );
2393- if !self
2394- .client
2395- .call(&Request::ContainerExists(container::Exists {
2396- name: container_name.clone(),
2397- }))
2398- .await?
2399- .status()
2400- .is_success()
2401- {
2402- self.client
2403- .call(&Request::CreateContainer(container::Create {
2404- name: container_name.clone(),
2405- image: args.image.to_string(),
2406- command: vec![
2407- String::from("/usr/bin/ayllu-build"),
2408- String::from("monitor"),
2409- ],
2410- mounts: vec![
2411- // Mount {
2412- // read_only: true,
2413- // source: source_dir_string,
2414- // destination: String::from("/_ayllu/src"),
2415- // r#type: String::from("bind"),
2416- // },
2417- Mount {
2418- read_only: true,
2419- source: ayllu_binary_path_string,
2420- destination: String::from("/usr/bin/ayllu-build"),
2421- r#type: String::from("bind"),
2422- },
2423- ],
2424- terminal: true,
2425- }))
2426- .await?;
2427- }
2428-
2429- self.client
2430- .upload_tree(&container_name, args.src_package, "/_ayllu")
2431- .await?;
2432-
2433- self.client
2434- .call(&Request::StartContainer(container::Start {
2435- name: container_name.clone(),
2436- }))
2437- .await?;
2438-
2439- Ok(())
2440- }
2441-
2442- async fn execute(
2443- &self,
2444- step: &ayllu_database::build::Step,
2445- context: &crate::executor::Context,
2446- ) -> Result<(Vec<ayllu_database::build::LogLine>, i32), crate::error::Error> {
2447- // FIXME stream directly into the database because it will result in
2448- // fast log output in the frontend.
2449- let (tx, mut rx) = mpsc::channel::<(crate::libpod::Stream, String)>(1);
2450- let (tx_signal, rx_signal) = tokio::sync::oneshot::channel::<i32>();
2451- let shell = step.shell.to_string();
2452- let input = step.input.to_string();
2453- let client = self.client.clone();
2454- let ctx = context.clone();
2455- tokio::spawn(async move {
2456- if let Err(err) = client.exec(&ctx, &shell, &input, tx, tx_signal).await {
2457- tracing::error!("Error spawning step: {err:?}");
2458- }
2459- });
2460- let mut lines: Vec<LogLine> = Vec::new();
2461- let start = Instant::now();
2462- while let Some(msg) = rx.recv().await {
2463- match msg.0 {
2464- crate::libpod::Stream::Stdout => tracing::info!("{}", msg.1.trim_end()),
2465- crate::libpod::Stream::Stderr => tracing::warn!("{}", msg.1.trim_end()),
2466- _ => unreachable!(),
2467- }
2468- lines.push(LogLine {
2469- output: match msg.0 {
2470- crate::libpod::Stream::Stdout => ayllu_database::build::Output::Stdout,
2471- crate::libpod::Stream::Stderr => ayllu_database::build::Output::Stderr,
2472- _ => unimplemented!(),
2473- },
2474- runtime: Instant::now().duration_since(start),
2475- line: msg.1,
2476- });
2477- }
2478-
2479- let signal = rx_signal.await.unwrap();
2480- if signal == 0 {
2481- tracing::info!("Process executed normally")
2482- } else {
2483- tracing::warn!("Non-zero exit code: {signal}")
2484- }
2485-
2486- // self.client.call().await.unwrap();
2487- Ok((lines, signal))
2488- }
2489-
2490- async fn shutdown(
2491- &self,
2492- _manifest_id: i64,
2493- _workflow_id: i64,
2494- ) -> Result<(), crate::error::Error> {
2495- // TODO
2496- Ok(())
2497- }
2498- }
2499 diff --git a/ayllu-build/src/libpod.rs b/ayllu-build/src/libpod.rs
2500index be5e0e6..3ff7a8c 100644
2501--- a/ayllu-build/src/libpod.rs
2502+++ b/ayllu-build/src/libpod.rs
2503 @@ -1,21 +1,16 @@
2504- use std::{collections::HashMap, io::Cursor, path::Path, sync::Arc, time::Duration};
2505+ use std::{collections::HashMap, path::Path};
2506
2507- use bytes::Bytes;
2508 use serde_json::json;
2509- use tokio::{
2510- fs::File,
2511- io::{AsyncReadExt, AsyncWriteExt},
2512- sync::{Mutex, RwLock, mpsc::Sender},
2513- };
2514+ use tokio::{fs::File, io::AsyncReadExt, sync::mpsc::Sender};
2515
2516 use reqwest::{
2517- Body, ClientBuilder, RequestBuilder, Upgraded, Url,
2518+ Body, ClientBuilder, Url,
2519 header::{HeaderMap, HeaderName, HeaderValue},
2520 };
2521 use serde::{Deserialize, Serialize};
2522 use tokio_util::codec::{BytesCodec, FramedRead};
2523
2524- use crate::{executor::Context, monitor::Message};
2525+ use crate::{error::Error, libpod_executor::Context};
2526
2527 #[derive(Deserialize, Debug)]
2528 pub struct ErrorMessage {
2529 @@ -25,24 +20,6 @@ pub struct ErrorMessage {
2530 }
2531
2532 #[derive(Debug)]
2533- pub enum Error {
2534- Network(reqwest::Error),
2535- Podman(ErrorMessage),
2536- }
2537-
2538- impl From<Error> for crate::error::Error {
2539- fn from(value: Error) -> Self {
2540- todo!()
2541- }
2542- }
2543-
2544- impl From<reqwest::Error> for Error {
2545- fn from(value: reqwest::Error) -> Self {
2546- todo!()
2547- }
2548- }
2549-
2550- #[derive(Debug)]
2551 pub enum Stream {
2552 Stdin,
2553 Stdout,
2554 @@ -51,8 +28,6 @@ pub enum Stream {
2555
2556 pub mod container {
2557
2558- use std::path::PathBuf;
2559-
2560 use super::*;
2561
2562 #[derive(Serialize, Debug)]
2563 @@ -298,7 +273,7 @@ impl Client {
2564 } else {
2565 tracing::error!("libpod: {}: {}", url, res.status());
2566 let error_message = res.json::<ErrorMessage>().await?;
2567- Err(Error::Podman(error_message))
2568+ Err(Error::Libpod(error_message))
2569 }
2570 }
2571
2572 diff --git a/ayllu-build/src/libpod_executor.rs b/ayllu-build/src/libpod_executor.rs
2573new file mode 100644
2574index 0000000..5711ea2
2575--- /dev/null
2576+++ b/ayllu-build/src/libpod_executor.rs
2577 @@ -0,0 +1,172 @@
2578+ use std::{collections::HashMap, path::Path};
2579+
2580+ use tokio::sync::{mpsc, oneshot};
2581+
2582+ use ayllu_database::build::logs::{Stream, WriteLineArgs};
2583+ use ayllu_database::build::steps::Step;
2584+ use serde::Deserialize;
2585+
2586+ use crate::libpod::{
2587+ Client, Request,
2588+ container::{self, Mount},
2589+ };
2590+
2591+ // context exposed to the runtime of individual steps, also used in other
2592+ // parts of the build process.
2593+ #[derive(Deserialize, Debug, Clone, Default)]
2594+ pub struct Context {
2595+ pub manifest_id: i32,
2596+ pub workflow_id: i32,
2597+ pub step_id: i32,
2598+ pub git_hash: String,
2599+ pub repo_url: String,
2600+ pub environment: HashMap<String, Option<String>>,
2601+ }
2602+
2603+ pub struct InitializeArgs<'a> {
2604+ pub manifest_id: i32,
2605+ pub workflow_id: i32,
2606+ pub build_dir: &'a Path,
2607+ pub image: &'a str,
2608+ pub init_args: &'a [&'a str],
2609+ pub source_dir: &'a Path,
2610+ pub absolute_ayllu_build_binary: &'a Path,
2611+ // path to source tree and other things needed in the ct runtime
2612+ pub src_package: &'a Path,
2613+ }
2614+
2615+ pub struct Libpod {
2616+ client: Client,
2617+ }
2618+
2619+ impl Libpod {
2620+ pub fn new(socket_path: &Path) -> Self {
2621+ Self {
2622+ client: Client::new(socket_path),
2623+ }
2624+ }
2625+ }
2626+
2627+ impl Libpod {
2628+ pub async fn initialize<'a>(
2629+ &self,
2630+ args: &InitializeArgs<'a>,
2631+ ) -> Result<(), crate::error::Error> {
2632+ let container_name = format!("ayllu-build-{}-{}", args.manifest_id, args.workflow_id);
2633+ let base_dir = args.build_dir.join(&container_name); // TODO Maybe timestamp?
2634+ std::fs::create_dir_all(base_dir)?;
2635+ let ayllu_binary_path_string = args
2636+ .absolute_ayllu_build_binary
2637+ .to_string_lossy()
2638+ .to_string();
2639+ assert!(
2640+ self.client
2641+ .call(&Request::Ping)
2642+ .await?
2643+ .status()
2644+ .is_success()
2645+ );
2646+ if !self
2647+ .client
2648+ .call(&Request::ContainerExists(container::Exists {
2649+ name: container_name.clone(),
2650+ }))
2651+ .await?
2652+ .status()
2653+ .is_success()
2654+ {
2655+ self.client
2656+ .call(&Request::CreateContainer(container::Create {
2657+ name: container_name.clone(),
2658+ image: args.image.to_string(),
2659+ command: vec![
2660+ String::from("/usr/bin/ayllu-build"),
2661+ String::from("monitor"),
2662+ ],
2663+ mounts: vec![
2664+ // Mount {
2665+ // read_only: true,
2666+ // source: source_dir_string,
2667+ // destination: String::from("/_ayllu/src"),
2668+ // r#type: String::from("bind"),
2669+ // },
2670+ Mount {
2671+ read_only: true,
2672+ source: ayllu_binary_path_string,
2673+ destination: String::from("/usr/bin/ayllu-build"),
2674+ r#type: String::from("bind"),
2675+ },
2676+ ],
2677+ terminal: true,
2678+ }))
2679+ .await?;
2680+ }
2681+
2682+ self.client
2683+ .upload_tree(&container_name, args.src_package, "/_ayllu")
2684+ .await?;
2685+
2686+ self.client
2687+ .call(&Request::StartContainer(container::Start {
2688+ name: container_name.clone(),
2689+ }))
2690+ .await?;
2691+
2692+ Ok(())
2693+ }
2694+
2695+ pub async fn execute<'a>(
2696+ &self,
2697+ step: &Step,
2698+ context: &Context,
2699+ db: &mut ayllu_database::Ptr<'a>,
2700+ ) -> Result<i32, crate::error::Error> {
2701+ let (tx, mut rx) = mpsc::channel::<(crate::libpod::Stream, String)>(1);
2702+ let (tx_signal, rx_signal) = oneshot::channel::<i32>();
2703+ let shell = step.shell.to_string();
2704+ let input = step.input.to_string();
2705+ let client = self.client.clone();
2706+ let ctx = context.clone();
2707+ tokio::spawn(async move {
2708+ if let Err(err) = client.exec(&ctx, &shell, &input, tx, tx_signal).await {
2709+ tracing::error!("Error spawning step: {err:?}");
2710+ }
2711+ });
2712+ while let Some(msg) = rx.recv().await {
2713+ match msg.0 {
2714+ crate::libpod::Stream::Stdout => tracing::info!("{}", msg.1.trim_end()),
2715+ crate::libpod::Stream::Stderr => tracing::warn!("{}", msg.1.trim_end()),
2716+ _ => unreachable!(),
2717+ }
2718+ let stream = match msg.0 {
2719+ crate::libpod::Stream::Stdin => unimplemented!(),
2720+ crate::libpod::Stream::Stdout => Stream::Stdout,
2721+ crate::libpod::Stream::Stderr => Stream::Stderr,
2722+ };
2723+ db.log_write_line(&WriteLineArgs {
2724+ step_id: step.id,
2725+ stream,
2726+ line: &msg.1,
2727+ })?;
2728+ }
2729+
2730+ let signal = rx_signal.await.unwrap();
2731+ if signal == 0 {
2732+ tracing::info!("Process executed normally")
2733+ } else {
2734+ tracing::warn!("Non-zero exit code: {signal}")
2735+ }
2736+
2737+ // self.client.call().await.unwrap();
2738+ Ok(signal)
2739+ }
2740+
2741+ pub fn shutdown(
2742+ &self,
2743+ _manifest_id: i32,
2744+ _workflow_id: i32,
2745+ ) -> Result<(), crate::error::Error> {
2746+ // TODO
2747+ Ok(())
2748+ }
2749+ }
2750 diff --git a/ayllu-build/src/libpod_monitor.rs b/ayllu-build/src/libpod_monitor.rs
2751new file mode 100644
2752index 0000000..1c1af6d
2753--- /dev/null
2754+++ b/ayllu-build/src/libpod_monitor.rs
2755 @@ -0,0 +1,21 @@
2756+ use std::{path::Path, time::Duration};
2757+
2758+ pub struct InitializeArgs {}
2759+
2760+ pub struct Libpod;
2761+
2762+ impl Libpod {
2763+ pub fn initialize(&self, args: &InitializeArgs) -> Result<(), crate::error::Error> {
2764+ tracing::info!("Initializing build environment");
2765+ ayllu_git::clone("/_ayllu/src.bundle", Path::new("/src"), None).unwrap();
2766+ Ok(())
2767+ }
2768+
2769+ pub async fn monitor(&self) -> Result<(), crate::error::Error> {
2770+ tracing::info!("Ayllu monitor running");
2771+ // TODO!
2772+ loop {
2773+ tokio::time::sleep(Duration::from_secs(1)).await
2774+ }
2775+ }
2776+ }
2777 diff --git a/ayllu-build/src/main.rs b/ayllu-build/src/main.rs
2778index 002fcb7..e502b64 100644
2779--- a/ayllu-build/src/main.rs
2780+++ b/ayllu-build/src/main.rs
2781 @@ -3,21 +3,17 @@ use std::path::Path;
2782 use tracing::Level;
2783
2784 use ayllu_cmd::build::{Command, Commands};
2785- use ayllu_database::Builder;
2786+ use ayllu_database::Wrapper as Database;
2787+
2788+ use crate::evaluate::{Runtime, Source};
2789
2790- use crate::{
2791- evaluate::{Runtime, Source},
2792- monitor::Monitor,
2793- };
2794 mod config;
2795 mod error;
2796 mod evaluate;
2797- mod executor;
2798- mod executor_libpod;
2799 mod libpod;
2800+ mod libpod_executor;
2801+ mod libpod_monitor;
2802 mod models;
2803- mod monitor;
2804- mod monitor_libpod;
2805 mod package;
2806
2807 const DEFAULT_BUILD_FILE: &str = ".ayllu-build.json";
2808 @@ -33,29 +29,23 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
2809 tee_output,
2810 } => {
2811 let workdir = cfg.builder.work_dir.clone();
2812- let db = Builder::default()
2813- .path(cfg.database.path.as_path())
2814- .log_queries(false) // FIXME
2815- .build()
2816- .await?;
2817+ let db = Database::new(&cfg.database.path)?;
2818 let source_path = alternate_path.unwrap_or(Path::new(DEFAULT_BUILD_FILE).to_path_buf());
2819 let source_path = std::fs::canonicalize(source_path)?;
2820- let rt = Runtime {
2821+ let mut rt = Runtime {
2822 db,
2823 source: Source::Path(source_path),
2824 tee_output,
2825 work_dir: workdir.clone(),
2826 };
2827- let executor = executor_libpod::Libpod::new(Path::new("/tmp/podman.sock"));
2828+ let executor = libpod_executor::Libpod::new(Path::new("/tmp/podman.sock"));
2829 rt.evaluate(&executor).await?;
2830 Ok(())
2831 }
2832 Commands::Monitor => {
2833 tracing::info!("Ayllu build process has started");
2834- let monitor = crate::monitor_libpod::Libpod {
2835- socket_path: Path::new("/tmp/ayllu-build.socket"),
2836- };
2837- monitor.initialize(&monitor::InitializeArgs {}).await?;
2838+ let monitor = libpod_monitor::Libpod;
2839+ monitor.initialize(&libpod_monitor::InitializeArgs {})?;
2840 monitor.monitor().await?;
2841 Ok(())
2842 }
2843 diff --git a/ayllu-build/src/monitor.rs b/ayllu-build/src/monitor.rs
2844deleted file mode 100644
2845index 2692d2d..0000000
2846--- a/ayllu-build/src/monitor.rs
2847+++ /dev/null
2848 @@ -1,18 +0,0 @@
2849- use serde::{Deserialize, Serialize};
2850-
2851- use crate::error::Error;
2852-
2853- #[derive(Serialize, Deserialize, Clone, Copy)]
2854- pub enum Message {
2855- Ping,
2856- Pong,
2857- Shutdown,
2858- }
2859-
2860- pub struct InitializeArgs {}
2861-
2862- /// Monitor process for use within a build environment
2863- pub trait Monitor {
2864- async fn initialize(&self, args: &InitializeArgs) -> Result<(), Error>;
2865- async fn monitor(&self) -> Result<(), Error>;
2866- }
2867 diff --git a/ayllu-build/src/monitor_libpod.rs b/ayllu-build/src/monitor_libpod.rs
2868deleted file mode 100644
2869index 1707987..0000000
2870--- a/ayllu-build/src/monitor_libpod.rs
2871+++ /dev/null
2872 @@ -1,77 +0,0 @@
2873- use std::{path::Path, sync::Arc, time::Duration};
2874-
2875- use tokio::{
2876- io::{self, AsyncBufReadExt, AsyncRead, AsyncReadExt, AsyncWriteExt, BufReader},
2877- net::{UnixListener, UnixStream},
2878- signal,
2879- sync::Semaphore,
2880- };
2881-
2882- use crate::monitor::{Message, Monitor};
2883-
2884- pub struct Libpod<'a> {
2885- pub socket_path: &'a Path,
2886- }
2887-
2888- impl Libpod<'_> {
2889- async fn handle(stream: UnixStream) -> Result<bool, std::io::Error> {
2890- let (reader, mut writer) = stream.into_split();
2891- let mut lines = BufReader::new(reader).lines();
2892-
2893- while let Some(line) = lines.next_line().await? {
2894- let line = line.trim();
2895- if line.is_empty() {
2896- continue;
2897- }
2898- let msg = serde_json::from_str::<Message>(line).unwrap();
2899- match msg {
2900- Message::Ping => {
2901- let mut msg = serde_json::to_vec(&Message::Pong).unwrap();
2902- msg.push(b'\n');
2903- writer.write_all(&msg).await?;
2904- }
2905- Message::Pong => {}
2906- Message::Shutdown => return Ok(true),
2907- }
2908- }
2909- Ok(false)
2910- }
2911- }
2912-
2913- impl Monitor for Libpod<'_> {
2914- async fn initialize(
2915- &self,
2916- args: &crate::monitor::InitializeArgs,
2917- ) -> Result<(), crate::error::Error> {
2918- tracing::info!("Initializing build environment");
2919- ayllu_git::clone("/_ayllu/src.bundle", Path::new("/src"), None).unwrap();
2920- Ok(())
2921- }
2922-
2923- async fn monitor(&self) -> Result<(), crate::error::Error> {
2924- tracing::info!("Ayllu monitor running");
2925- if self.socket_path.exists() {
2926- std::fs::remove_file(self.socket_path)?;
2927- }
2928- let listener = UnixListener::bind(self.socket_path)?;
2929- tracing::info!("Starting Ayllu monitor listening @ {:?}", self.socket_path);
2930- loop {
2931- tokio::select! {
2932- Ok((stream, addr)) = listener.accept() => {
2933- tracing::debug!("Handling connection from {addr:?}");
2934- if Libpod::handle(stream).await.expect("Connection failed") {
2935- tracing::info!("Got shutdown signal from client");
2936- break
2937- }
2938- }
2939- _ = signal::ctrl_c() => {
2940- tracing::info!("Shutting down Ayllu monitor");
2941- let _ = std::fs::remove_file(self.socket_path);
2942- break;
2943- }
2944- }
2945- }
2946-
2947- Ok(())
2948- }
2949- }
2950 diff --git a/ayllu-migrate/src/main.rs b/ayllu-migrate/src/main.rs
2951index ba7e127..c80817f 100644
2952--- a/ayllu-migrate/src/main.rs
2953+++ b/ayllu-migrate/src/main.rs
2954 @@ -15,25 +15,8 @@ impl Configurable for Config {}
2955 async fn main() -> Result<(), Box<dyn std::error::Error>> {
2956 let args = ayllu_cmd::parse::<Command>();
2957 let config: Config = ayllu_config::Reader::load(args.config.as_deref())?;
2958-
2959 ayllu_logging::init(Level::INFO);
2960-
2961- let database_path = config.database.path.as_path();
2962-
2963- let migrations_path = args.migrations.unwrap_or(config.database.migrations);
2964-
2965- tracing::info!("Running migrations from {migrations_path:?} against {database_path:?}");
2966-
2967- let wrapper = ayllu_database::Builder::default()
2968- .path(database_path)
2969- .migrations(migrations_path.as_path())
2970- .log_queries(true)
2971- .build()
2972- .await?;
2973-
2974- wrapper.close().await?;
2975-
2976+ ayllu_database::migrate(&config.database.path)?;
2977 tracing::info!("Migrations applied successfully");
2978-
2979 Ok(())
2980 }
2981 diff --git a/ayllu/src/web2/dag.rs b/ayllu/src/web2/dag.rs
2982index efdc0a6..91047ce 100644
2983--- a/ayllu/src/web2/dag.rs
2984+++ b/ayllu/src/web2/dag.rs
2985 @@ -1,7 +1,7 @@
2986 use std::collections::HashMap;
2987
2988 use ayllu_api::build::Unit;
2989- use ayllu_database::build::{ManifestView, Step, Workflow};
2990+ use ayllu_database::build::{manifests::Summary, steps::Step, workflows::Workflow};
2991 use petgraph::{Graph, dot};
2992
2993 #[derive(Debug)]
2994 @@ -20,8 +20,12 @@ impl std::fmt::Display for MappedUnit<'_> {
2995 }
2996
2997 /// Generate a DAG as an SVG chart
2998- pub fn make_svg(view: &ManifestView) -> String {
2999- let workflows_by_id: Vec<(i64, (&Workflow, bool))> = view
3000+ pub fn make_svg(view: &Summary) -> String {
3001+ if view.manifest.dag_content.is_none() {
3002+ return String::default(); // FIXME
3003+ }
3004+ let dag_content_str = view.manifest.dag_content.as_ref().unwrap();
3005+ let workflows_by_id: Vec<(i32, (&Workflow, bool))> = view
3006 .workflows
3007 .iter()
3008 .map(|(workflow, steps)| {
3009 @@ -36,8 +40,8 @@ pub fn make_svg(view: &ManifestView) -> String {
3010 )
3011 })
3012 .collect();
3013- let workflows_by_id: HashMap<i64, (&Workflow, bool)> = HashMap::from_iter(workflows_by_id);
3014- let steps_by_id: Vec<(i64, &Step)> = view
3015+ let workflows_by_id: HashMap<i32, (&Workflow, bool)> = HashMap::from_iter(workflows_by_id);
3016+ let steps_by_id: Vec<(i32, &Step)> = view
3017 .workflows
3018 .iter()
3019 .flat_map(|workflow| {
3020 @@ -45,15 +49,17 @@ pub fn make_svg(view: &ManifestView) -> String {
3021 .1
3022 .iter()
3023 .map(|step| (step.id, step))
3024- .collect::<Vec<(i64, &Step)>>()
3025+ .collect::<Vec<(i32, &Step)>>()
3026 })
3027 .collect();
3028- let steps_by_id: HashMap<i64, &Step> = HashMap::from_iter(steps_by_id);
3029- let graph: Graph<Unit, u8> = serde_json::de::from_str(&view.dag.dag_content).unwrap();
3030+ let steps_by_id: HashMap<i32, &Step> = HashMap::from_iter(steps_by_id);
3031+ let graph: Graph<Unit, u8> = serde_json::de::from_str(&dag_content_str).unwrap(); // FIXME
3032 let mapped: Graph<MappedUnit, u8> = graph.map(
3033 |_, unit| match unit {
3034- Unit::Step(step_id) => MappedUnit::Step(steps_by_id[step_id]),
3035- Unit::Workflow(workflow_id) => MappedUnit::Workflow(workflows_by_id[workflow_id]),
3036+ Unit::Step(step_id) => MappedUnit::Step(steps_by_id[&(*step_id as i32)]),
3037+ Unit::Workflow(workflow_id) => {
3038+ MappedUnit::Workflow(workflows_by_id[&(*workflow_id as i32)])
3039+ }
3040 },
3041 |_, weight| *weight,
3042 );
3043 diff --git a/ayllu/src/web2/middleware/database.rs b/ayllu/src/web2/middleware/database.rs
3044index dfa068e..3bb028b 100644
3045--- a/ayllu/src/web2/middleware/database.rs
3046+++ b/ayllu/src/web2/middleware/database.rs
3047 @@ -1,3 +1,5 @@
3048+ use std::path::PathBuf;
3049+
3050 use axum::{
3051 extract::{Request, State},
3052 middleware::Next,
3053 @@ -7,7 +9,7 @@ use axum::{
3054 use crate::{config::Config, web2::error::Error};
3055
3056 #[derive(Clone)]
3057- pub struct Builds(pub(crate) Option<ayllu_database::Wrapper>);
3058+ pub struct Builds(pub(crate) Option<PathBuf>);
3059
3060 impl Builds {
3061 #[allow(dead_code)]
3062 @@ -15,22 +17,44 @@ impl Builds {
3063 self.0.is_some()
3064 }
3065
3066- pub fn db(&self) -> Result<&ayllu_database::Wrapper, Error> {
3067- match self.0.as_ref() {
3068- Some(wrapper) => Ok(wrapper),
3069- None => Err(Error::ComponentNotEnabled("builds".to_string())),
3070+ /// Call SQLite operations on tokio threads since the Diesel async
3071+ /// interface does this anyways.
3072+ pub async fn call<T, F>(&self, f: F) -> Result<T, Error>
3073+ where
3074+ T: Send + 'static,
3075+ F: FnOnce(&mut ayllu_database::Ptr) -> Result<T, ayllu_database::Error> + Send + 'static,
3076+ {
3077+ if let Some(db_path) = self.0.as_ref() {
3078+ let db_path = db_path.clone();
3079+ let db_result = tokio::task::spawn_blocking(move || {
3080+ let mut handle = ayllu_database::Wrapper::new_ro(&db_path).unwrap();
3081+ let mut ptr = handle.call();
3082+ f(&mut ptr)
3083+ })
3084+ .await
3085+ .unwrap()?;
3086+ Ok(db_result)
3087+ } else {
3088+ Err(Error::ComponentNotEnabled(String::from("Database")))
3089 }
3090 }
3091+
3092+ // pub fn db(&self) -> Result<&ayllu_database::Wrapper, Error> {
3093+ // match self.0.as_ref() {
3094+ // Some(wrapper) => Ok(wrapper),
3095+ // None => Err(Error::ComponentNotEnabled("builds".to_string())),
3096+ // }
3097+ // }
3098 }
3099
3100 pub async fn middleware(
3101- State((config, db)): State<(Config, Option<ayllu_database::Wrapper>)>,
3102+ State((config, db_path)): State<(Config, Option<PathBuf>)>,
3103 mut req: Request,
3104 next: Next,
3105 ) -> Response {
3106 if config.builder.is_some() {
3107- assert!(db.is_some()); // BUG if not enabled
3108- req.extensions_mut().insert(Builds(db));
3109+ assert!(db_path.is_some()); // BUG if not enabled
3110+ req.extensions_mut().insert(Builds(db_path));
3111 }
3112 next.run(req).await
3113 }
3114 diff --git a/ayllu/src/web2/routes/build.rs b/ayllu/src/web2/routes/build.rs
3115index 57ac3f4..8a805e7 100644
3116--- a/ayllu/src/web2/routes/build.rs
3117+++ b/ayllu/src/web2/routes/build.rs
3118 @@ -1,6 +1,10 @@
3119 use askama::Template;
3120 use axum::extract::Path;
3121 use axum::{extract::Extension, response::Html};
3122+ use ayllu_database::build::logs::Line;
3123+ use ayllu_database::build::manifests::Manifest;
3124+ use ayllu_database::build::steps::Step;
3125+ use ayllu_database::build::workflows::Workflow;
3126 use serde::Deserialize;
3127
3128 use crate::config::Config;
3129 @@ -12,15 +16,15 @@ use crate::web2::middleware::repository::Preamble;
3130 use crate::web2::template::Base;
3131 use crate::web2::template::{components, filters};
3132
3133- use ayllu_database::build::{BuildExt, LogLine, Manifest, ManifestItem, Step, Workflow};
3134+ // use ayllu_database::build::{BuildExt, LogLine, Manifest, ManifestItem, Step, Workflow};
3135
3136- fn concat_log_lines(lines: &[LogLine]) -> String {
3137+ fn concat_log_lines(lines: &[Line]) -> String {
3138 let n_lines = lines.len();
3139 lines
3140 .iter()
3141 .enumerate()
3142 .fold(String::default(), |mut text, (i, line)| {
3143- text.push_str(&line.line);
3144+ text.push_str(&line.line.trim_end_matches("\n"));
3145 if i < n_lines {
3146 text.push('\n');
3147 }
3148 @@ -30,9 +34,9 @@ fn concat_log_lines(lines: &[LogLine]) -> String {
3149
3150 #[derive(Deserialize)]
3151 pub struct BuildParams {
3152- pub manifest_id: i64,
3153- pub workflow_id: Option<i64>,
3154- pub step_id: Option<i64>,
3155+ pub manifest_id: i32,
3156+ pub workflow_id: Option<i32>,
3157+ pub step_id: Option<i32>,
3158 }
3159
3160 #[derive(askama::Template)]
3161 @@ -41,12 +45,14 @@ struct BuildTemplate<'a> {
3162 pub base: Base,
3163 pub collection: &'a str,
3164 pub name: &'a str,
3165- pub manifest: Manifest,
3166+ pub manifest_id: i32,
3167+ pub created_at: i32,
3168+ pub runtime: i32,
3169+ // pub manifest: Manifest,
3170 pub workflows: Vec<(Workflow, Vec<Step>)>,
3171 pub dag_svg: String,
3172- pub current_workflow: i64,
3173- pub current_step: Option<(i64, Step)>,
3174- pub step_output: Option<&'a [LogLine]>,
3175+ pub current_workflow: i32,
3176+ pub current_step: Option<(i32, Step, Vec<Line>)>,
3177 }
3178
3179 pub async fn raw(
3180 @@ -58,9 +64,13 @@ pub async fn raw(
3181 step_id,
3182 }): Path<BuildParams>,
3183 ) -> Result<String, Error> {
3184- let db = builds.db()?;
3185- // let step = db.read_step(step_id.unwrap()).await?;
3186- let lines = db.read_log_lines(step_id.unwrap()).await?;
3187+ let step_id = step_id.unwrap();
3188+ let lines = builds
3189+ .call(move |conn| {
3190+ let lines = conn.log_read(step_id, None)?;
3191+ Ok(lines)
3192+ })
3193+ .await?;
3194 Ok(concat_log_lines(lines.as_slice()))
3195 }
3196
3197 @@ -76,50 +86,67 @@ pub async fn build(
3198 step_id,
3199 }): Path<BuildParams>,
3200 ) -> Result<Html<String>, Error> {
3201- let db = builds.db()?;
3202- let view = db
3203- .read_manifest(&preamble.collection_name, &preamble.repo_name, manifest_id)
3204- .await?;
3205+ let (view, current_step) = builds
3206+ .call(move |conn| {
3207+ let view = conn.manifest_summary(manifest_id)?;
3208+ let current_step = if let Some(step_id) = step_id {
3209+ let step = view.workflows.iter().find_map(|wf| {
3210+ wf.1.iter()
3211+ .find(|step| step.id == step_id)
3212+ .map(|step| (wf.0.id, step))
3213+ });
3214+ if let Some(step) = step {
3215+ Some((step.0, step.1.clone(), conn.log_read(step_id, None)?))
3216+ } else {
3217+ None
3218+ }
3219+ } else {
3220+ None
3221+ };
3222
3223- let current_step = if let Some(step_id) = step_id {
3224- view.workflows.iter().find_map(|wf| {
3225- wf.1.iter()
3226- .find(|step| step.id == step_id)
3227- .map(|step| (wf.0.id, step.clone()))
3228+ Ok((view, current_step))
3229 })
3230- } else {
3231- None
3232- };
3233+ .await?;
3234
3235- let step_output = match &current_step {
3236- Some((_, step)) => {
3237- // FIXME: We need to change the db representaiton of log lines and merge
3238- // them together here with stdout/stderr highlighting. For now I concat
3239- // them together which is excessively stupid / annoying.
3240- Some(db.read_log_lines(step.id).await?)
3241- // let (_, highlighted) =
3242- // highlighter.highlight(&concat_log_lines(lines.as_slice()), None, None, None, true);
3243- // Some(highlighted)
3244- }
3245- None => None,
3246- };
3247+ // let step_output = match &current_step {
3248+ // Some((_, step)) => {
3249+ // // FIXME: We need to change the db representaiton of log lines and merge
3250+ // // them together here with stdout/stderr highlighting. For now I concat
3251+ // // them together which is excessively stupid / annoying.
3252+ // Some(db.read_log_lines(step.id).await?)
3253+ // // let (_, highlighted) =
3254+ // // highlighter.highlight(&concat_log_lines(lines.as_slice()), None, None, None, true);
3255+ // // Some(highlighted)
3256+ // }
3257+ // None => None,
3258+ // };
3259
3260 base.nav_elements =
3261 crate::web2::navigation::primary("builds", &preamble.collection_name, &preamble.repo_name);
3262
3263 let dag_svg = make_svg(&view);
3264
3265+ let manifest = view.manifest;
3266+
3267 Ok(Html(
3268 BuildTemplate {
3269 base,
3270 collection: &preamble.collection_name,
3271 name: &preamble.repo_name,
3272- manifest: view.manifest,
3273- workflows: view.workflows,
3274 dag_svg,
3275 current_workflow: workflow_id.unwrap_or(-1),
3276+ manifest_id,
3277+ created_at: manifest.created_at,
3278+ runtime: if let Some(finished_at) = manifest.finished_at {
3279+ manifest
3280+ .started_at
3281+ .unwrap_or_default()
3282+ .strict_sub(finished_at)
3283+ } else {
3284+ -1
3285+ },
3286+ workflows: view.workflows,
3287 current_step,
3288- step_output: step_output.as_deref(),
3289 }
3290 .render()?,
3291 ))
3292 @@ -131,7 +158,7 @@ struct BuildsTemplate<'a> {
3293 pub base: Base,
3294 pub collection: &'a str,
3295 pub name: &'a str,
3296- pub items: Vec<ManifestItem>,
3297+ pub manifests: Vec<Manifest>,
3298 }
3299
3300 pub async fn builds(
3301 @@ -140,24 +167,19 @@ pub async fn builds(
3302 Extension(preamble): Extension<Preamble>,
3303 Extension(mut base): Extension<Base>,
3304 ) -> Result<Html<String>, Error> {
3305- let db = builds.db()?;
3306- let items = db
3307- .list_manifest(
3308- Some(&preamble.collection_name),
3309- Some(&preamble.repo_name),
3310- None,
3311- 9999999,
3312- 0,
3313- )
3314+ // FIXME: Pagination
3315+ let (collection_name, repo_name) =
3316+ (preamble.collection_name.clone(), preamble.repo_name.clone());
3317+ let manifests = builds
3318+ .call(move |conn| conn.manifest_list(&collection_name, &repo_name))
3319 .await?;
3320- // FIXME
3321 // let manifests = db.list_manifest(999999, 0).await?;
3322 base.nav_elements =
3323 crate::web2::navigation::primary("builds", &preamble.collection_name, &preamble.repo_name);
3324 Ok(Html(
3325 BuildsTemplate {
3326 base,
3327- items,
3328+ manifests,
3329 collection: &preamble.collection_name,
3330 name: &preamble.repo_name,
3331 }
3332 diff --git a/ayllu/src/web2/server.rs b/ayllu/src/web2/server.rs
3333index 13f8a29..7a361af 100644
3334--- a/ayllu/src/web2/server.rs
3335+++ b/ayllu/src/web2/server.rs
3336 @@ -1,5 +1,6 @@
3337 use std::error::Error;
3338 use std::net::SocketAddrV4;
3339+ use std::path::PathBuf;
3340 use std::sync::Arc;
3341 use std::{collections::HashMap, path::Path};
3342
3343 @@ -84,21 +85,14 @@ pub async fn serve(cfg: &Config) -> Result<(), Box<dyn Error>> {
3344 Vec::new()
3345 };
3346
3347- let mut database: Option<ayllu_database::Wrapper> = None;
3348+ let mut db_path: Option<PathBuf> = None;
3349
3350 if cfg.builder.is_some() {
3351 tracing::info!("Builder is enabled thus the database is configured");
3352- database = Some(
3353- ayllu_database::Builder::default()
3354- .path(cfg.database.as_ref().unwrap().path.as_path())
3355- .read_only(true)
3356- .log_queries(false) // FIXME
3357- .build()
3358- .await?,
3359- );
3360+ db_path = Some(cfg.database.as_ref().unwrap().path.clone());
3361 }
3362
3363- if database.is_some() {
3364+ if db_path.is_some() {
3365 tracing::info!("SQLite Database is serving");
3366 }
3367
3368 @@ -193,7 +187,7 @@ pub async fn serve(cfg: &Config) -> Result<(), Box<dyn Error>> {
3369 .layer(Extension(cfg.clone()))
3370 .layer(Extension(highlighter))
3371 .layer(Extension(adapter))
3372- .layer(from_fn_with_state((cfg.clone(), database), db::middleware))
3373+ .layer(from_fn_with_state((cfg.clone(), db_path), db::middleware))
3374 .layer(from_fn_with_state(cfg.clone(), template::middleware))
3375 // error handling
3376 .layer(from_fn_with_state(Arc::new(cfg.clone()), error::middleware))
3377 diff --git a/ayllu/src/web2/template.rs b/ayllu/src/web2/template.rs
3378index 2aac611..8fbd5f9 100644
3379--- a/ayllu/src/web2/template.rs
3380+++ b/ayllu/src/web2/template.rs
3381 @@ -87,7 +87,7 @@ impl Default for Base {
3382
3383 pub mod components {
3384 use askama::{Template, filters::Safe};
3385- use ayllu_database::build::{LogLine, Output};
3386+ use ayllu_database::build::logs::{Line, Stream};
3387
3388 use crate::web2::template::DEFAULT_RSS_ICON;
3389
3390 @@ -100,12 +100,12 @@ pub mod components {
3391 {%- for entry in lines -%}
3392 <tr>
3393 <td class="line-number">{{loop.index}} </td>
3394- {%- match entry.output -%}
3395- {% when Output::Stderr %}
3396+ {% match entry.stream %}
3397+ {% when Stream::Stderr %}
3398 <td class="negative">
3399- {% else %}
3400+ {% when Stream::Stdout %}
3401 <td>
3402- {%- endmatch -%}
3403+ {% endmatch %}
3404 {{ entry.line }}
3405 </td>
3406 </tr>
3407 @@ -115,10 +115,10 @@ pub mod components {
3408 "#
3409 )]
3410 struct LogViewer<'a> {
3411- lines: &'a [LogLine],
3412+ lines: &'a [Line],
3413 }
3414
3415- pub fn log_viewer(lines: &[LogLine]) -> Safe<String> {
3416+ pub fn log_viewer(lines: &[Line]) -> Safe<String> {
3417 Safe(LogViewer { lines }.render().unwrap())
3418 }
3419
3420 @@ -237,6 +237,10 @@ pub mod filters {
3421 Ok(timeutil::friendly(*epoch as u64))
3422 }
3423
3424+ pub fn friendly_time_32(epoch: &i32, _: &dyn askama::Values) -> askama::Result<String> {
3425+ Ok(timeutil::friendly(*epoch as u64))
3426+ }
3427+
3428 pub fn friendly_time_maybe(
3429 epoch: &Option<i64>,
3430 values: &dyn askama::Values,
3431 @@ -247,6 +251,16 @@ pub mod filters {
3432 }
3433 }
3434
3435+ pub fn friendly_time_maybe_32(
3436+ epoch: &Option<i32>,
3437+ values: &dyn askama::Values,
3438+ ) -> askama::Result<String> {
3439+ match epoch {
3440+ Some(value) => friendly_time_32(value, values),
3441+ None => Ok("?".to_string()),
3442+ }
3443+ }
3444+
3445 pub fn format_epoch(epoch: &i64, _: &dyn askama::Values) -> askama::Result<String> {
3446 let ts = OffsetDateTime::from_unix_timestamp(*epoch).unwrap();
3447 let formatted = ts.format(&well_known::Rfc2822).unwrap();
3448 diff --git a/ayllu/templates/build.html b/ayllu/templates/build.html
3449index 2eb5a78..a1418f0 100644
3450--- a/ayllu/templates/build.html
3451+++ b/ayllu/templates/build.html
3452 @@ -2,7 +2,7 @@
3453 {% block content %}
3454 <section id="builds" class="scrollable raised">
3455 <section class="info-bar">
3456- <section class="title"><a href="/{{collection}}/{{name}}/builds/{{manifest.id}}">Build for {{collection}}/{{name}}: {{manifest.id}}</a></section>
3457+ <section class="title"><a href="/{{collection}}/{{name}}/builds/{{manifest_id}}">Build for {{collection}}/{{name}}: {{manifest_id}}</a></section>
3458 </section>
3459 <section class="raised lower-half">
3460 <table class="data-table">
3461 @@ -19,11 +19,11 @@
3462 </tr>
3463 <tr>
3464 <td>CreatedAt</td>
3465- <td>{{manifest.created_at | friendly_time }}</td>
3466+ <td>{{created_at | friendly_time_32 }}</td>
3467 </tr>
3468 <tr>
3469 <td>Runtime</td>
3470- <td>{{manifest.runtime()}}s</td>
3471+ <td>{{runtime}}</td>
3472 </tr>
3473 </tbody>
3474 </table>
3475 @@ -34,7 +34,7 @@
3476 <section class="scrollable">
3477 <section class="info-bar">
3478 <section class="title">
3479- <a href="/{{collection}}/{{name}}/builds/{{manifest.id}}/{{workflow.0.id}}">{{ workflow.0.name }}</a>
3480+ <a href="/{{collection}}/{{name}}/builds/{{manifest_id}}/{{workflow.0.id}}">{{ workflow.0.name }}</a>
3481 </section>
3482 <section> Workflow </section>
3483 </section>
3484 @@ -59,11 +59,11 @@
3485 {%- else -%}
3486 <tr>
3487 {%- endif -%}
3488- <td><a href="/{{collection}}/{{name}}/builds/{{manifest.id}}/{{workflow.0.id}}/{{step.id}}">
3489+ <td><a href="/{{collection}}/{{name}}/builds/{{manifest_id}}/{{workflow.0.id}}/{{step.id}}">
3490 {{step.id}}
3491 </td>
3492- <td><a href="/{{collection}}/{{name}}/builds/{{manifest.id}}/{{workflow.0.id}}/{{step.id}}">{{step.name}}</a></td>
3493- <td>{{step.started_at | friendly_time_maybe }}</td>
3494+ <td><a href="/{{collection}}/{{name}}/builds/{{manifest_id}}/{{workflow.0.id}}/{{step.id}}">{{step.name}}</a></td>
3495+ <td>{{step.started_at | friendly_time_maybe_32 }}</td>
3496 <td>{%- if let Some(exit_code) = step.exit_code -%} {{ exit_code }} {%- else -%} ? {%- endif -%}</td>
3497 </tr>
3498 {% endfor %}
3499 @@ -98,11 +98,6 @@
3500 </tr>
3501 </tbody>
3502 </table>
3503- {%- if let Some(log_lines) = step_output -%}
3504- <section id="code-viewer" class="scrollable">
3505- {{ components::log_viewer(log_lines)| safe}}
3506- </section>
3507- {%- endif -%}
3508 </section>
3509 </section>
3510 {%- endif -%}
3511 diff --git a/ayllu/templates/builds.html b/ayllu/templates/builds.html
3512index 63bd12a..717cea4 100644
3513--- a/ayllu/templates/builds.html
3514+++ b/ayllu/templates/builds.html
3515 @@ -15,27 +15,19 @@
3516 </tr>
3517 </thead>
3518 <tbody>
3519- {% for item in items %}
3520+ {% for manifest in manifests %}
3521 <tr class="build">
3522 <td>
3523 <div class="name">
3524- <a href="/{{collection}}/{{name}}/builds/{{item.manifest.id}}">{{item.manifest.id}}</a>
3525+ <a href="/{{collection}}/{{name}}/builds/{{manifest.id}}">{{manifest.id}}</a>
3526 </div>
3527 </td>
3528 <td>
3529- <a href="/{{collection}}/{{name}}/builds/{{item.manifest.id}}">
3530- {%- if let Some(success) = item.success -%}
3531- {%- if success -%}
3532- <span class="positive"> PASS </span>
3533- {% else %}
3534- <span class="positive"> FAIL </span>
3535- {%- endif -%}
3536- {% else -%}
3537- <span class=""> ? </span>
3538- {%- endif -%}
3539+ <a href="/{{collection}}/{{name}}/builds/{{manifest.id}}">
3540+ ???
3541 </a>
3542 </td>
3543- <td>{{ item.manifest.created_at | friendly_time }}</td>
3544+ <td>{{ manifest.created_at | friendly_time_32 }}</td>
3545 </tr>
3546 {% endfor %}
3547 </tbody>
3548 diff --git a/crates/api/src/build.rs b/crates/api/src/build.rs
3549index de3c49c..a5e7d98 100644
3550--- a/crates/api/src/build.rs
3551+++ b/crates/api/src/build.rs
3552 @@ -5,8 +5,8 @@ use serde::{Deserialize, Serialize};
3553 /// Unit of execution which is either a workflow or a step
3554 #[derive(Serialize, Deserialize, Debug, Clone)]
3555 pub enum Unit {
3556- Step(i64),
3557- Workflow(i64),
3558+ Step(i32),
3559+ Workflow(i32),
3560 }
3561
3562 impl Display for Unit {
3563 diff --git a/crates/database/Cargo.toml b/crates/database/Cargo.toml
3564index cd94a87..350436b 100644
3565--- a/crates/database/Cargo.toml
3566+++ b/crates/database/Cargo.toml
3567 @@ -6,10 +6,13 @@ edition = "2024"
3568 # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
3569
3570 [dependencies]
3571- async-trait = { workspace = true }
3572- futures = { workspace = true }
3573+
3574+ timeutil = { path = "../timeutil" }
3575+
3576 serde = { workspace = true }
3577 serde_json = { workspace = true }
3578- sqlx = { workspace = true }
3579- time = { workspace = true }
3580 tracing = { workspace = true }
3581+ thiserror = { workspace = true }
3582+
3583+ diesel = { version = "2.3.9", features = ["sqlite", "returning_clauses_for_sqlite_3_35"] }
3584+ diesel_migrations = { version = "2.3.2", features = ["sqlite"] }
3585 diff --git a/crates/database/migrations/.diesel_lock b/crates/database/migrations/.diesel_lock
3586new file mode 100644
3587index 0000000..e69de29
3588--- /dev/null
3589+++ b/crates/database/migrations/.diesel_lock
3590 diff --git a/crates/database/migrations/.keep b/crates/database/migrations/.keep
3591new file mode 100644
3592index 0000000..e69de29
3593--- /dev/null
3594+++ b/crates/database/migrations/.keep
3595 diff --git a/crates/database/migrations/2026-05-04-162320-0000_init/down.sql b/crates/database/migrations/2026-05-04-162320-0000_init/down.sql
3596new file mode 100644
3597index 0000000..23a5544
3598--- /dev/null
3599+++ b/crates/database/migrations/2026-05-04-162320-0000_init/down.sql
3600 @@ -0,0 +1,8 @@
3601+ -- This file should undo anything in `up.sql`
3602+ DROP TABLE samples;
3603+ DROP TABLE dags;
3604+ DROP TABLE steps_env_vars;
3605+ DROP TABLE logs;
3606+ DROP TABLE steps;
3607+ DROP TABLE workflows;
3608+ DROP TABLE manifests;
3609 diff --git a/crates/database/migrations/2026-05-04-162320-0000_init/up.sql b/crates/database/migrations/2026-05-04-162320-0000_init/up.sql
3610new file mode 100644
3611index 0000000..9e3aeb0
3612--- /dev/null
3613+++ b/crates/database/migrations/2026-05-04-162320-0000_init/up.sql
3614 @@ -0,0 +1,58 @@
3615+ CREATE TABLE manifests (
3616+ id INTEGER PRIMARY KEY NOT NULL,
3617+ collection TEXT NOT NULL,
3618+ name TEXT NOT NULL,
3619+ git_hash TEXT NOT NULL,
3620+ dag_content TEXT,
3621+ state INTEGER NOT NULL DEFAULT (1) CHECK (state IN (1, 2, 3, 4)),
3622+ created_at INTEGER NOT NULL DEFAULT (UNIXEPOCH()),
3623+ started_at INTEGER CHECK (started_at > 0),
3624+ finished_at INTEGER CHECK (finished_at > 0)
3625+ ) STRICT ;
3626+
3627+ CREATE TABLE workflows (
3628+ id INTEGER PRIMARY KEY NOT NULL,
3629+ manifest_id INTEGER REFERENCES manifests(id) ON DELETE CASCADE NOT NULL,
3630+ name TEXT NOT NULL,
3631+ image TEXT NOT NULL,
3632+ started_at INTEGER CHECK (started_at > 0),
3633+ finished_at INTEGER CHECK (finished_at > 0)
3634+ ) STRICT ;
3635+
3636+ CREATE TABLE steps (
3637+ id INTEGER PRIMARY KEY NOT NULL,
3638+ name TEXT NOT NULL,
3639+ workflow_id INTEGER REFERENCES workflows(id) ON DELETE CASCADE NOT NULL,
3640+ shell TEXT NOT NULL DEFAULT "/bin/sh",
3641+ environment_json TEXT,
3642+ input TEXT NOT NULL,
3643+ started_at INTEGER CHECK (started_at > 0),
3644+ finished_at INTEGER CHECK (finished_at > 0),
3645+ exit_code INTEGER CHECK (exit_code <= 255)
3646+ ) STRICT ;
3647+
3648+ CREATE TABLE logs (
3649+ id INTEGER PRIMARY KEY NOT NULL,
3650+ step_id INTEGER REFERENCES steps(id) ON DELETE CASCADE NOT NULL,
3651+ timestamp INTEGER NOT NULL,
3652+ stream INTEGER NOT NULL CHECK (stream IN (1, 2)),
3653+ line TEXT NOT NULL
3654+ ) STRICT ;
3655+
3656+ CREATE TABLE samples (
3657+ id INTEGER PRIMARY KEY NOT NULL,
3658+ manifest_id INTEGER REFERENCES manifests(id) ON DELETE CASCADE NOT NULL,
3659+ timestamp INTEGER NOT NULL DEFAULT (UNIXEPOCH()),
3660+ load_1m INTEGER,
3661+ load_5m INTEGER,
3662+ load_15m INTEGER,
3663+ disk_total_bytes INTEGER,
3664+ disk_free_bytes INTEGER,
3665+ net_sent_bytes INTEGER,
3666+ net_received_bytes INTEGER,
3667+ net_sent_packets INTEGER,
3668+ net_received_packets INTEGER,
3669+ mem_total_bytes INTEGER,
3670+ mem_free_bytes INTEGER,
3671+ mem_available_bytes INTEGER
3672+ ) STRICT ;
3673 diff --git a/crates/database/queries/authors_list_project.sql b/crates/database/queries/authors_list_project.sql
3674deleted file mode 100644
3675index 832e3de..0000000
3676--- a/crates/database/queries/authors_list_project.sql
3677+++ /dev/null
3678 @@ -1,30 +0,0 @@
3679- WITH RECURSIVE max_git_id AS
3680- (SELECT contributions.id AS hash
3681- FROM contributions
3682- WHERE git_hash = ?
3683- AND repo_path = ?
3684- ORDER BY id DESC
3685- LIMIT 1),
3686- total_contributions AS
3687- (SELECT COUNT(*) AS contributions
3688- FROM contributions
3689- WHERE repo_path = ?
3690- AND id <=
3691- (SELECT hash
3692- FROM max_git_id) )
3693- SELECT authors.username,
3694- authors.email,
3695- COUNT(contributions.id) AS "count: i64",
3696- SUM(contributions.lines_added) AS "lines_added: i64",
3697- SUM(contributions.lines_removed) AS "lines_removed: i64",
3698- ROUND((COUNT(contributions.id)/CAST(
3699- (SELECT contributions
3700- FROM total_contributions) AS REAL))*100, 2) AS "percentage: f64"
3701- FROM contributions
3702- LEFT JOIN authors ON (contributions.author_id = authors.id)
3703- WHERE repo_path = ?
3704- AND contributions.id <=
3705- (SELECT hash
3706- FROM max_git_id)
3707- GROUP BY authors.email
3708- ORDER BY "count: i64" DESC
3709 diff --git a/crates/database/queries/authors_upsert.sql b/crates/database/queries/authors_upsert.sql
3710deleted file mode 100644
3711index 1c55636..0000000
3712--- a/crates/database/queries/authors_upsert.sql
3713+++ /dev/null
3714 @@ -1,10 +0,0 @@
3715- INSERT INTO authors
3716- (username, email)
3717- VALUES
3718- (?, ?)
3719- ON CONFLICT
3720- DO UPDATE
3721- SET
3722- username = username,
3723- email = email
3724- RETURNING id
3725 diff --git a/crates/database/queries/commits_count.sql b/crates/database/queries/commits_count.sql
3726deleted file mode 100644
3727index 0088bb8..0000000
3728--- a/crates/database/queries/commits_count.sql
3729+++ /dev/null
3730 @@ -1,9 +0,0 @@
3731- SELECT
3732- COUNT(id) AS count
3733- FROM contributions
3734- WHERE
3735- repo_path = ? AND
3736- id <= (
3737- SELECT id FROM contributions
3738- WHERE git_hash = ? AND repo_path = ?
3739- )
3740 diff --git a/crates/database/queries/contribution_add.sql b/crates/database/queries/contribution_add.sql
3741deleted file mode 100644
3742index e1085e3..0000000
3743--- a/crates/database/queries/contribution_add.sql
3744+++ /dev/null
3745 @@ -1,11 +0,0 @@
3746- WITH previous(total) AS (
3747- SELECT total FROM contribution_tally
3748- WHERE
3749- author_id = ? AND repo_path = ?
3750- ORDER BY id DESC
3751- LIMIT 1
3752- )
3753- INSERT INTO contribution_tally
3754- (git_hash, repo_path, author_id, total)
3755- VALUES
3756- (?, ?, ?, COALESCE((SELECT(total) FROM previous), 0)+1)
3757 diff --git a/crates/database/queries/contribution_delete.sql b/crates/database/queries/contribution_delete.sql
3758deleted file mode 100644
3759index 6fdbe5c..0000000
3760--- a/crates/database/queries/contribution_delete.sql
3761+++ /dev/null
3762 @@ -1,3 +0,0 @@
3763- DELETE FROM contribution_tally
3764- WHERE
3765- repo_path = ?
3766 diff --git a/crates/database/queries/contributions_add.sql b/crates/database/queries/contributions_add.sql
3767deleted file mode 100644
3768index 9883d94..0000000
3769--- a/crates/database/queries/contributions_add.sql
3770+++ /dev/null
3771 @@ -1,4 +0,0 @@
3772- INSERT INTO contributions
3773- (author_id, git_hash, repo_path, time, lines_added, lines_removed)
3774- VALUES
3775- (?, ?, ?, ?, ?, ?)
3776 diff --git a/crates/database/queries/contributions_bucket.sql b/crates/database/queries/contributions_bucket.sql
3777deleted file mode 100644
3778index c4e2e55..0000000
3779--- a/crates/database/queries/contributions_bucket.sql
3780+++ /dev/null
3781 @@ -1,10 +0,0 @@
3782- SELECT
3783- COUNT(id) AS "count!: i64",
3784- time,
3785- SUM(lines_added) AS "added!: i64",
3786- SUM(lines_removed) AS "removed!: i64"
3787- FROM contributions WHERE
3788- repo_path = ? AND
3789- id <= (SELECT id FROM contributions WHERE git_hash = ?) AND
3790- time <= ? AND time >= ?
3791- GROUP BY strftime(?, time)
3792 diff --git a/crates/database/queries/contributions_delete.sql b/crates/database/queries/contributions_delete.sql
3793deleted file mode 100644
3794index 0ee5563..0000000
3795--- a/crates/database/queries/contributions_delete.sql
3796+++ /dev/null
3797 @@ -1,2 +0,0 @@
3798- DELETE FROM contributions
3799- WHERE repo_path = ?
3800 diff --git a/crates/database/queries/contributions_list.sql b/crates/database/queries/contributions_list.sql
3801deleted file mode 100644
3802index fae5633..0000000
3803--- a/crates/database/queries/contributions_list.sql
3804+++ /dev/null
3805 @@ -1,42 +0,0 @@
3806- WITH RECURSIVE
3807- tallys AS (
3808- SELECT
3809- authors.username AS name,
3810- authors.email AS email,
3811- MAX(contribution_tally.total) AS total
3812- FROM contribution_tally
3813- LEFT JOIN authors ON
3814- (authors.id = contribution_tally.author_id)
3815- WHERE
3816- contribution_tally.id < (
3817- SELECT id FROM contributions
3818- WHERE
3819- repo_path = ? AND git_hash = ?
3820- ORDER BY id DESC
3821- LIMIT 1
3822- ) AND
3823- contribution_tally.repo_path = ?
3824- GROUP BY authors.email
3825- ORDER BY contribution_tally.total DESC
3826- LIMIT 5),
3827- raw_commits AS (
3828- SELECT
3829- COUNT(*) as total
3830- FROM contributions
3831- WHERE
3832- contributions.id < (
3833- SELECT id FROM contributions
3834- WHERE
3835- repo_path = ? AND git_hash = ?
3836- ORDER BY id DESC
3837- LIMIT 1
3838- ) AND
3839- contributions.repo_path = ?
3840- )
3841- SELECT
3842- name,
3843- email,
3844- (
3845- CAST(tallys.total AS REAL)/(SELECT total FROM raw_commits)
3846- ) * 100 AS "percentage: i64"
3847- FROM tallys
3848 diff --git a/crates/database/queries/dags_create.sql b/crates/database/queries/dags_create.sql
3849deleted file mode 100644
3850index 07bd1bb..0000000
3851--- a/crates/database/queries/dags_create.sql
3852+++ /dev/null
3853 @@ -1,2 +0,0 @@
3854- INSERT INTO dags (manifest_id, dag_content)
3855- VALUES (?, ?) RETURNING id
3856 diff --git a/crates/database/queries/dags_read.sql b/crates/database/queries/dags_read.sql
3857deleted file mode 100644
3858index d2bb6ca..0000000
3859--- a/crates/database/queries/dags_read.sql
3860+++ /dev/null
3861 @@ -1,3 +0,0 @@
3862- SELECT
3863- id, manifest_id, dag_content
3864- FROM dags WHERE manifest_id = ?
3865 diff --git a/crates/database/queries/job_tracking_add.sql b/crates/database/queries/job_tracking_add.sql
3866deleted file mode 100644
3867index 7e70756..0000000
3868--- a/crates/database/queries/job_tracking_add.sql
3869+++ /dev/null
3870 @@ -1,4 +0,0 @@
3871- INSERT INTO job_tracking
3872- (repo_path, git_hash, kind, job_id)
3873- VALUES
3874- (?, ?, ?, ?)
3875 diff --git a/crates/database/queries/job_tracking_delete.sql b/crates/database/queries/job_tracking_delete.sql
3876deleted file mode 100644
3877index 3aef33d..0000000
3878--- a/crates/database/queries/job_tracking_delete.sql
3879+++ /dev/null
3880 @@ -1,3 +0,0 @@
3881- DELETE FROM job_tracking
3882- WHERE
3883- repo_path = ?
3884 diff --git a/crates/database/queries/job_tracking_delete_by_repo.sql b/crates/database/queries/job_tracking_delete_by_repo.sql
3885deleted file mode 100644
3886index ebd0363..0000000
3887--- a/crates/database/queries/job_tracking_delete_by_repo.sql
3888+++ /dev/null
3889 @@ -1,3 +0,0 @@
3890- DELETE FROM jobs
3891- WHERE
3892- repo_path = ?
3893 diff --git a/crates/database/queries/job_tracking_read.sql b/crates/database/queries/job_tracking_read.sql
3894deleted file mode 100644
3895index 823dfc5..0000000
3896--- a/crates/database/queries/job_tracking_read.sql
3897+++ /dev/null
3898 @@ -1,8 +0,0 @@
3899- SELECT
3900- git_hash AS "git_hash!"
3901- FROM job_tracking
3902- WHERE
3903- repo_path = ? AND
3904- kind = ?
3905- ORDER BY id DESC
3906- LIMIT 1
3907 diff --git a/crates/database/queries/jobs_create.sql b/crates/database/queries/jobs_create.sql
3908deleted file mode 100644
3909index 4a1bea5..0000000
3910--- a/crates/database/queries/jobs_create.sql
3911+++ /dev/null
3912 @@ -1,5 +0,0 @@
3913- INSERT INTO jobs
3914- (created_at, repo_path, kind)
3915- VALUES
3916- (?, ?, ?)
3917- RETURNING id
3918 diff --git a/crates/database/queries/jobs_delete.sql b/crates/database/queries/jobs_delete.sql
3919deleted file mode 100644
3920index c70ba25..0000000
3921--- a/crates/database/queries/jobs_delete.sql
3922+++ /dev/null
3923 @@ -1,2 +0,0 @@
3924- DELETE FROM jobs
3925- WHERE jobs.id = ?
3926 diff --git a/crates/database/queries/jobs_list.sql b/crates/database/queries/jobs_list.sql
3927deleted file mode 100644
3928index b1e7f72..0000000
3929--- a/crates/database/queries/jobs_list.sql
3930+++ /dev/null
3931 @@ -1,15 +0,0 @@
3932- SELECT
3933- jobs.id AS "id!",
3934- created_at AS "created_at: OffsetDateTime",
3935- jobs.repo_path AS "repo_path!: String",
3936- jobs.kind AS "kind!: String",
3937- runtime AS "runtime: u32",
3938- success AS "success: bool",
3939- (
3940- SELECT COUNT(id) FROM job_tracking
3941- WHERE job_tracking.job_id = jobs.id
3942- ) AS "commits!: i32"
3943- FROM jobs
3944- WHERE
3945- jobs.repo_path = ? OR ? IS NULL
3946- ORDER BY DATETIME(created_at) ASC
3947 diff --git a/crates/database/queries/jobs_update.sql b/crates/database/queries/jobs_update.sql
3948deleted file mode 100644
3949index ea7e521..0000000
3950--- a/crates/database/queries/jobs_update.sql
3951+++ /dev/null
3952 @@ -1,6 +0,0 @@
3953- UPDATE jobs
3954- SET
3955- runtime = ?,
3956- success = ?
3957- WHERE
3958- jobs.id = ?
3959 diff --git a/crates/database/queries/languages_add.sql b/crates/database/queries/languages_add.sql
3960deleted file mode 100644
3961index 54551b3..0000000
3962--- a/crates/database/queries/languages_add.sql
3963+++ /dev/null
3964 @@ -1,4 +0,0 @@
3965- INSERT INTO languages
3966- (git_hash, repo_path, language, loc)
3967- VALUES
3968- (?,?,?,?)
3969 diff --git a/crates/database/queries/languages_delete.sql b/crates/database/queries/languages_delete.sql
3970deleted file mode 100644
3971index 06c4349..0000000
3972--- a/crates/database/queries/languages_delete.sql
3973+++ /dev/null
3974 @@ -1,3 +0,0 @@
3975- DELETE FROM languages
3976- WHERE
3977- repo_path = ?
3978 diff --git a/crates/database/queries/languages_list.sql b/crates/database/queries/languages_list.sql
3979deleted file mode 100644
3980index 608a719..0000000
3981--- a/crates/database/queries/languages_list.sql
3982+++ /dev/null
3983 @@ -1,13 +0,0 @@
3984- SELECT
3985- language,
3986- loc,
3987- ROUND(
3988- CAST(loc AS REAL)/CAST(
3989- (SELECT SUM(loc) FROM languages
3990- WHERE repo_path = ? AND git_hash = ?) AS REAL)*100)
3991- AS "percentage: f64"
3992- FROM languages
3993- WHERE
3994- repo_path = ? AND git_hash = ?
3995- ORDER BY "percentage: f64" DESC
3996- LIMIT 5
3997 diff --git a/crates/database/queries/latest_commit.sql b/crates/database/queries/latest_commit.sql
3998deleted file mode 100644
3999index bd26eac..0000000
4000--- a/crates/database/queries/latest_commit.sql
4001+++ /dev/null
4002 @@ -1,7 +0,0 @@
4003- SELECT
4004- git_hash
4005- FROM contributions
4006- WHERE
4007- repo_path = ?
4008- ORDER BY id DESC
4009- LIMIT 1
4010 diff --git a/crates/database/queries/mail_deactivate_unused.sql b/crates/database/queries/mail_deactivate_unused.sql
4011deleted file mode 100644
4012index 730dc92..0000000
4013--- a/crates/database/queries/mail_deactivate_unused.sql
4014+++ /dev/null
4015 @@ -1 +0,0 @@
4016- UPDATE lists SET enabled = 0 WHERE address NOT IN (?)
4017 diff --git a/crates/database/queries/mail_deliver_message.sql b/crates/database/queries/mail_deliver_message.sql
4018deleted file mode 100644
4019index 6763a82..0000000
4020--- a/crates/database/queries/mail_deliver_message.sql
4021+++ /dev/null
4022 @@ -1,10 +0,0 @@
4023- INSERT INTO messages
4024- (message_id, list_id, in_reply_to, subject, mail_from, content_body, raw_message)
4025- VALUES (
4026- ?,
4027- ( SELECT id FROM lists WHERE address = ? LIMIT 1 ),
4028- ?,
4029- ?,
4030- ?,
4031- ?, ?
4032- ) RETURNING messages.id
4033 diff --git a/crates/database/queries/mail_has_message.sql b/crates/database/queries/mail_has_message.sql
4034deleted file mode 100644
4035index e32ce95..0000000
4036--- a/crates/database/queries/mail_has_message.sql
4037+++ /dev/null
4038 @@ -1,5 +0,0 @@
4039- SELECT
4040- CASE
4041- WHEN (SELECT COUNT(*) FROM messages WHERE message_id = ?) >= 1 THEN TRUE
4042- ELSE FALSE
4043- END AS "has_message!: bool"
4044 diff --git a/crates/database/queries/mail_list_create.sql b/crates/database/queries/mail_list_create.sql
4045deleted file mode 100644
4046index 770b76a..0000000
4047--- a/crates/database/queries/mail_list_create.sql
4048+++ /dev/null
4049 @@ -1,9 +0,0 @@
4050- INSERT INTO lists
4051- (name, address, description, enabled)
4052- VALUES
4053- (?, ?, ?, ?)
4054- ON CONFLICT (address) DO
4055- UPDATE SET
4056- name = ?,
4057- description = ?,
4058- enabled = ?
4059 diff --git a/crates/database/queries/mail_list_outbox.sql b/crates/database/queries/mail_list_outbox.sql
4060deleted file mode 100644
4061index 8eaa79e..0000000
4062--- a/crates/database/queries/mail_list_outbox.sql
4063+++ /dev/null
4064 @@ -1,13 +0,0 @@
4065- SELECT
4066- outbox.id,
4067- messages.message_id AS "message_id!: String",
4068- lists.name AS "list_name!: String",
4069- participants.address AS "address!: String",
4070- messages.raw_message AS raw_message
4071- FROM outbox
4072- LEFT JOIN participants ON outbox.recipient = participants.address
4073- LEFT JOIN messages ON messages.id = outbox.message_id
4074- LEFT JOIN lists ON messages.list_id = lists.id
4075- WHERE
4076- (SELECT COUNT(*) FROM delivery_failures
4077- WHERE delivery_failures.outbox_id = outbox.id) < ?
4078 diff --git a/crates/database/queries/mail_outbox_failed.sql b/crates/database/queries/mail_outbox_failed.sql
4079deleted file mode 100644
4080index c7dafcb..0000000
4081--- a/crates/database/queries/mail_outbox_failed.sql
4082+++ /dev/null
4083 @@ -1,3 +0,0 @@
4084- INSERT INTO delivery_failures
4085- (outbox_id, error_kind, message)
4086- VALUES (?, ?, ?)
4087 diff --git a/crates/database/queries/mail_outbox_successful.sql b/crates/database/queries/mail_outbox_successful.sql
4088deleted file mode 100644
4089index 0b0269c..0000000
4090--- a/crates/database/queries/mail_outbox_successful.sql
4091+++ /dev/null
4092 @@ -1 +0,0 @@
4093- DELETE FROM outbox WHERE id = ?
4094 diff --git a/crates/database/queries/mail_read_message.sql b/crates/database/queries/mail_read_message.sql
4095deleted file mode 100644
4096index 1696cae..0000000
4097--- a/crates/database/queries/mail_read_message.sql
4098+++ /dev/null
4099 @@ -1 +0,0 @@
4100- SELECT * FROM messages WHERE id = ?
4101 diff --git a/crates/database/queries/mail_read_thread.sql b/crates/database/queries/mail_read_thread.sql
4102deleted file mode 100644
4103index 09342d0..0000000
4104--- a/crates/database/queries/mail_read_thread.sql
4105+++ /dev/null
4106 @@ -1,18 +0,0 @@
4107- WITH RECURSIVE tree AS (
4108- SELECT messages.*, 0 AS depth FROM messages
4109- WHERE messages.message_id = ?
4110- UNION ALL
4111- SELECT other.*, depth + 1 FROM messages other
4112- JOIN tree ON other.in_reply_to = tree.message_id
4113- )
4114- SELECT
4115- message_id,
4116- in_reply_to,
4117- subject,
4118- (SELECT address FROM participants WHERE participants.id = tree.mail_from LIMIT 1)
4119- mail_from,
4120- raw_message,
4121- content_body,
4122- reply_count,
4123- depth AS "depth!: i64"
4124- FROM tree ORDER BY depth
4125 diff --git a/crates/database/queries/mail_read_threads.sql b/crates/database/queries/mail_read_threads.sql
4126deleted file mode 100644
4127index d2e3b4a..0000000
4128--- a/crates/database/queries/mail_read_threads.sql
4129+++ /dev/null
4130 @@ -1,9 +0,0 @@
4131- SELECT
4132- messages.message_id,
4133- messages.subject,
4134- messages.reply_count,
4135- participants.address AS "mail_from!: String"
4136- FROM messages
4137- LEFT JOIN lists ON lists.id = messages.id
4138- LEFT JOIN participants ON participants.id = messages.mail_from
4139- WHERE lists.name = ? AND messages.in_reply_to IS NULL
4140 diff --git a/crates/database/queries/mail_thread_count.sql b/crates/database/queries/mail_thread_count.sql
4141deleted file mode 100644
4142index 5dc619a..0000000
4143--- a/crates/database/queries/mail_thread_count.sql
4144+++ /dev/null
4145 @@ -1,4 +0,0 @@
4146- SELECT COUNT(*) AS "count: i64" FROM messages
4147- WHERE
4148- list_id = (SELECT list_id FROM lists WHERE list_id = ?) AND
4149- in_reply_to IS NULL
4150 diff --git a/crates/database/queries/mail_thread_summary.sql b/crates/database/queries/mail_thread_summary.sql
4151deleted file mode 100644
4152index c76aa55..0000000
4153--- a/crates/database/queries/mail_thread_summary.sql
4154+++ /dev/null
4155 @@ -1,3 +0,0 @@
4156- SELECT messages.* FROM messages
4157- LEFT JOIN lists ON lists.id = messages.id
4158- WHERE lists.name = ? AND messages.reply_to IS NULL
4159 diff --git a/crates/database/queries/mail_upsert_participant.sql b/crates/database/queries/mail_upsert_participant.sql
4160deleted file mode 100644
4161index 67b0973..0000000
4162--- a/crates/database/queries/mail_upsert_participant.sql
4163+++ /dev/null
4164 @@ -1,6 +0,0 @@
4165- INSERT INTO participants
4166- (address, authorized_sender)
4167- VALUES (?, ?)
4168- ON CONFLICT DO NOTHING;
4169-
4170- SELECT id FROM participants WHERE address = ?
4171 diff --git a/crates/database/queries/mail_upsert_subscription.sql b/crates/database/queries/mail_upsert_subscription.sql
4172deleted file mode 100644
4173index 78cd886..0000000
4174--- a/crates/database/queries/mail_upsert_subscription.sql
4175+++ /dev/null
4176 @@ -1,6 +0,0 @@
4177- INSERT INTO subscriptions
4178- (participant_id, list_id)
4179- VALUES (
4180- (SELECT id FROM participants WHERE address = ? LIMIT 1),
4181- (SELECT id FROM lists WHERE address = ? LIMIT 1)
4182- ) ON CONFLICT DO NOTHING;
4183 diff --git a/crates/database/queries/manifests_concise.sql b/crates/database/queries/manifests_concise.sql
4184deleted file mode 100644
4185index ff04dcf..0000000
4186--- a/crates/database/queries/manifests_concise.sql
4187+++ /dev/null
4188 @@ -1,25 +0,0 @@
4189- WITH params AS (
4190- SELECT
4191- ? AS collection,
4192- ? AS name,
4193- ? AS manifest_id,
4194- ? AS git_hash
4195- )
4196- SELECT
4197- steps.id AS step_id,
4198- steps.name AS step_name,
4199- workflows.id AS workflow_id,
4200- workflows.name AS workflow_name,
4201- manifests.id AS manifest_id,
4202- steps.exit_code,
4203- steps.finished_at
4204- FROM steps
4205- LEFT JOIN workflows ON workflows.id = steps.workflow_id
4206- LEFT JOIN manifests ON manifests.id = workflows.manifest_id
4207- WHERE
4208- (manifests.collection IS (SELECT collection FROM params) OR (SELECT collection FROM params) IS NULL)
4209- AND (manifests.name IS (SELECT name FROM params) OR (SELECT name FROM params) IS NULL)
4210- AND (manifests.id IS (SELECT manifest_id FROM params) OR (SELECT manifest_id FROM params) IS NULL)
4211- AND (manifests.git_hash IS (SELECT git_hash FROM params) OR (SELECT git_hash FROM params) IS NULL)
4212- LIMIT ?
4213- OFFSET ?
4214 diff --git a/crates/database/queries/manifests_create.sql b/crates/database/queries/manifests_create.sql
4215deleted file mode 100644
4216index e826336..0000000
4217--- a/crates/database/queries/manifests_create.sql
4218+++ /dev/null
4219 @@ -1,2 +0,0 @@
4220- INSERT INTO manifests (collection, name, git_hash)
4221- VALUES (?, ?, ?) RETURNING id
4222 diff --git a/crates/database/queries/manifests_list.sql b/crates/database/queries/manifests_list.sql
4223deleted file mode 100644
4224index 55ea5b1..0000000
4225--- a/crates/database/queries/manifests_list.sql
4226+++ /dev/null
4227 @@ -1,14 +0,0 @@
4228- SELECT id,
4229- collection,
4230- name,
4231- git_hash,
4232- created_at,
4233- started_at,
4234- duration
4235- FROM manifests
4236- WHERE
4237- collection = ? OR collection IS NULL
4238- AND name = ? OR name IS NULL
4239- AND git_hash = ? OR git_hash IS NULL
4240- LIMIT ?
4241- OFFSET ?
4242 diff --git a/crates/database/queries/manifests_read.sql b/crates/database/queries/manifests_read.sql
4243deleted file mode 100644
4244index e74b333..0000000
4245--- a/crates/database/queries/manifests_read.sql
4246+++ /dev/null
4247 @@ -1,18 +0,0 @@
4248- WITH params AS (
4249- SELECT
4250- ? AS _manifest_id,
4251- ? AS _collection,
4252- ? AS _name
4253- )
4254- SELECT manifests.id,
4255- name,
4256- collection,
4257- git_hash,
4258- created_at,
4259- started_at,
4260- finished_at
4261- FROM manifests
4262- WHERE
4263- manifests.id = (SELECT _manifest_id FROM params)
4264- AND (manifests.collection IS (SELECT _collection FROM params) OR (SELECT _collection FROM params) IS NULL)
4265- AND (manifests.name IS (SELECT _name FROM params) OR (SELECT _name FROM params) IS NULL)
4266 diff --git a/crates/database/queries/manifests_update_finish.sql b/crates/database/queries/manifests_update_finish.sql
4267deleted file mode 100644
4268index d0a6513..0000000
4269--- a/crates/database/queries/manifests_update_finish.sql
4270+++ /dev/null
4271 @@ -1,4 +0,0 @@
4272- UPDATE manifests
4273- SET
4274- finished_at = UNIXEPOCH()
4275- WHERE id = ?
4276 diff --git a/crates/database/queries/manifests_update_start.sql b/crates/database/queries/manifests_update_start.sql
4277deleted file mode 100644
4278index fc85a4d..0000000
4279--- a/crates/database/queries/manifests_update_start.sql
4280+++ /dev/null
4281 @@ -1,3 +0,0 @@
4282- UPDATE manifests
4283- SET started_at = UNIXEPOCH()
4284- WHERE id = ?
4285 diff --git a/crates/database/queries/samples_create.sql b/crates/database/queries/samples_create.sql
4286deleted file mode 100644
4287index 7fc297f..0000000
4288--- a/crates/database/queries/samples_create.sql
4289+++ /dev/null
4290 @@ -1,18 +0,0 @@
4291- INSERT INTO samples
4292- (
4293- manifest_id,
4294- load_1m,
4295- load_5m,
4296- load_15m,
4297- disk_total_bytes,
4298- disk_free_bytes,
4299- net_sent_bytes,
4300- net_received_bytes,
4301- net_sent_packets,
4302- net_received_packets,
4303- mem_total_bytes,
4304- mem_free_bytes,
4305- mem_available_bytes
4306- )
4307- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
4308- RETURNING id
4309 diff --git a/crates/database/queries/samples_read.sql b/crates/database/queries/samples_read.sql
4310deleted file mode 100644
4311index 6842939..0000000
4312--- a/crates/database/queries/samples_read.sql
4313+++ /dev/null
4314 @@ -1,15 +0,0 @@
4315- SELECT id,
4316- load_1m,
4317- load_5m,
4318- load_15m,
4319- disk_total_bytes,
4320- disk_free_bytes,
4321- net_sent_bytes,
4322- net_received_bytes,
4323- net_sent_packets,
4324- net_received_packets,
4325- mem_total_bytes,
4326- mem_free_bytes,
4327- mem_available_bytes
4328- FROM samples
4329- WHERE manifest_id = ?
4330 diff --git a/crates/database/queries/steps_create.sql b/crates/database/queries/steps_create.sql
4331deleted file mode 100644
4332index ababf23..0000000
4333--- a/crates/database/queries/steps_create.sql
4334+++ /dev/null
4335 @@ -1,2 +0,0 @@
4336- INSERT INTO steps (workflow_id, name, input, shell, environment_json)
4337- VALUES (?, ?, ?, ?, json_array(?)) RETURNING id
4338 diff --git a/crates/database/queries/steps_list.sql b/crates/database/queries/steps_list.sql
4339deleted file mode 100644
4340index f3f22d8..0000000
4341--- a/crates/database/queries/steps_list.sql
4342+++ /dev/null
4343 @@ -1,9 +0,0 @@
4344- SELECT id,
4345- name,
4346- shell,
4347- input,
4348- started_at,
4349- finished_at,
4350- exit_code
4351- FROM steps
4352- WHERE workflow_id = ?
4353 diff --git a/crates/database/queries/steps_read.sql b/crates/database/queries/steps_read.sql
4354deleted file mode 100644
4355index 9228c4d..0000000
4356--- a/crates/database/queries/steps_read.sql
4357+++ /dev/null
4358 @@ -1,9 +0,0 @@
4359- SELECT
4360- id,
4361- name,
4362- shell,
4363- input,
4364- started_at,
4365- finished_at,
4366- exit_code
4367- FROM steps WHERE id = ?
4368 diff --git a/crates/database/queries/steps_read_env.sql b/crates/database/queries/steps_read_env.sql
4369deleted file mode 100644
4370index 2312f2b..0000000
4371--- a/crates/database/queries/steps_read_env.sql
4372+++ /dev/null
4373 @@ -1 +0,0 @@
4374- SELECT name, value FROM steps_env_vars WHERE step_id = ?
4375 diff --git a/crates/database/queries/steps_read_log_lines.sql b/crates/database/queries/steps_read_log_lines.sql
4376deleted file mode 100644
4377index 5515e25..0000000
4378--- a/crates/database/queries/steps_read_log_lines.sql
4379+++ /dev/null
4380 @@ -1 +0,0 @@
4381- SELECT runtime, stream, line FROM logs WHERE step_id = ?
4382 diff --git a/crates/database/queries/steps_update_finish.sql b/crates/database/queries/steps_update_finish.sql
4383deleted file mode 100644
4384index 5faaf81..0000000
4385--- a/crates/database/queries/steps_update_finish.sql
4386+++ /dev/null
4387 @@ -1,6 +0,0 @@
4388- UPDATE steps
4389- SET
4390- finished_at = UNIXEPOCH(),
4391- exit_code = ?
4392- WHERE
4393- id = ?
4394 diff --git a/crates/database/queries/steps_update_log_line.sql b/crates/database/queries/steps_update_log_line.sql
4395deleted file mode 100644
4396index 8e3bae0..0000000
4397--- a/crates/database/queries/steps_update_log_line.sql
4398+++ /dev/null
4399 @@ -1,4 +0,0 @@
4400- INSERT INTO logs
4401- (step_id, runtime, stream, line)
4402- VALUES
4403- (?, ?, ?, ?)
4404 diff --git a/crates/database/queries/steps_update_start.sql b/crates/database/queries/steps_update_start.sql
4405deleted file mode 100644
4406index 387654b..0000000
4407--- a/crates/database/queries/steps_update_start.sql
4408+++ /dev/null
4409 @@ -1,5 +0,0 @@
4410- UPDATE steps
4411- SET
4412- started_at = UNIXEPOCH()
4413- WHERE
4414- id = ?
4415 diff --git a/crates/database/queries/workflows_create.sql b/crates/database/queries/workflows_create.sql
4416deleted file mode 100644
4417index ac4b392..0000000
4418--- a/crates/database/queries/workflows_create.sql
4419+++ /dev/null
4420 @@ -1,2 +0,0 @@
4421- INSERT INTO workflows (manifest_id, name, image)
4422- VALUES (?, ?, ?) RETURNING id
4423 diff --git a/crates/database/queries/workflows_list.sql b/crates/database/queries/workflows_list.sql
4424deleted file mode 100644
4425index 8914be1..0000000
4426--- a/crates/database/queries/workflows_list.sql
4427+++ /dev/null
4428 @@ -1,7 +0,0 @@
4429- SELECT id,
4430- name,
4431- image,
4432- started_at,
4433- finished_at
4434- FROM workflows
4435- WHERE manifest_id = ?
4436 diff --git a/crates/database/queries/workflows_read.sql b/crates/database/queries/workflows_read.sql
4437deleted file mode 100644
4438index 9335ff0..0000000
4439--- a/crates/database/queries/workflows_read.sql
4440+++ /dev/null
4441 @@ -1 +0,0 @@
4442- SELECT id, name, image, started_at, finished_at FROM workflows WHERE id = ?
4443 diff --git a/crates/database/queries/workflows_update_finish.sql b/crates/database/queries/workflows_update_finish.sql
4444deleted file mode 100644
4445index 769a8be..0000000
4446--- a/crates/database/queries/workflows_update_finish.sql
4447+++ /dev/null
4448 @@ -1,5 +0,0 @@
4449- UPDATE workflows
4450- SET
4451- finished_at = UNIXEPOCH()
4452- WHERE
4453- id = ?
4454 diff --git a/crates/database/queries/workflows_update_start.sql b/crates/database/queries/workflows_update_start.sql
4455deleted file mode 100644
4456index 11be057..0000000
4457--- a/crates/database/queries/workflows_update_start.sql
4458+++ /dev/null
4459 @@ -1,5 +0,0 @@
4460- UPDATE workflows
4461- SET
4462- started_at = UNIXEPOCH()
4463- WHERE
4464- id = ?
4465 diff --git a/crates/database/src/build.rs b/crates/database/src/build.rs
4466index b2fc00f..8823a75 100644
4467--- a/crates/database/src/build.rs
4468+++ b/crates/database/src/build.rs
4469 @@ -1,639 +1,410 @@
4470- use std::{collections::HashMap, fmt::Display, time::Duration};
4471-
4472- use async_trait::async_trait;
4473+ use diesel::{
4474+ Associations, BelongingToDsl, Identifiable, Insertable, QueryDsl, Queryable, Selectable,
4475+ SelectableHelper, deserialize::FromSql, serialize::ToSql, sql_types::Text, sqlite::Sqlite,
4476+ };
4477 use serde::{Deserialize, Serialize};
4478
4479- use crate::{Error, Tx, Wrapper as Database};
4480-
4481- pub type ConciseStep = (i64, String, Option<bool>);
4482- pub type ConciseWorkflow = (i64, String, Vec<ConciseStep>);
4483-
4484- /// An environment variable
4485- pub struct Env((String, Option<String>));
4486- /// Environment Variables
4487- pub struct Environment(Vec<Env>);
4488-
4489- impl From<Environment> for HashMap<String, Option<String>> {
4490- fn from(val: Environment) -> Self {
4491- let values: Vec<(String, Option<String>)> = val
4492- .0
4493- .iter()
4494- .map(|env| (env.0.0.clone(), env.0.1.clone()))
4495- .collect();
4496- HashMap::from_iter(values)
4497- }
4498- }
4499-
4500- // Manifest item with all of it's inner state for quick display
4501- #[derive(Clone, Serialize, Deserialize)]
4502- pub struct ManifestItem {
4503- pub manifest: Manifest,
4504- // Present if the manifest job is completed, true if all items within were successful
4505- pub success: Option<bool>,
4506- // Concise representation of workflows and steps for display in the UI
4507- pub workflows: Vec<ConciseWorkflow>,
4508- }
4509-
4510- #[derive(Clone, Serialize, Deserialize)]
4511- // everything needed to populate the build status page
4512- pub struct ManifestView {
4513- pub manifest: Manifest,
4514- // pub samples: Vec<Sample>,
4515- pub workflows: Vec<(Workflow, Vec<Step>)>,
4516- pub dag: Dag,
4517- }
4518-
4519- #[derive(Clone, Default, Serialize, Deserialize)]
4520- pub struct Sample {
4521- pub id: i64,
4522- pub load_1m: Option<i64>,
4523- pub load_5m: Option<i64>,
4524- pub load_15m: Option<i64>,
4525- pub disk_total_bytes: Option<i64>,
4526- pub disk_free_bytes: Option<i64>,
4527- pub net_sent_bytes: Option<i64>,
4528- pub net_received_bytes: Option<i64>,
4529- pub net_sent_packets: Option<i64>,
4530- pub net_received_packets: Option<i64>,
4531- pub mem_total_bytes: Option<i64>,
4532- pub mem_free_bytes: Option<i64>,
4533- pub mem_available_bytes: Option<i64>,
4534- }
4535-
4536- #[derive(Clone, Serialize, Deserialize)]
4537- pub struct Manifest {
4538- pub id: i64,
4539- pub collection: String,
4540- pub name: String,
4541- pub git_hash: String,
4542- pub created_at: i64,
4543- pub started_at: Option<i64>,
4544- pub finished_at: Option<i64>,
4545- // pub dot_content: String,
4546- }
4547-
4548- impl Manifest {
4549- pub fn runtime(&self) -> i64 {
4550- self.finished_at
4551- .map(|finished_at| finished_at - self.started_at.unwrap_or_default())
4552- .unwrap_or_default()
4553- }
4554- }
4555+ // use crate::schema::{dags, logs, manifests, steps, workflows};
4556+ use crate::{Error, Ptr};
4557
4558- // TODO
4559- //
4560- #[derive(Debug, Clone, Serialize, Deserialize)]
4561- pub struct Workflow {
4562- pub id: i64,
4563- pub name: String,
4564- pub image: String,
4565- pub started_at: Option<i64>,
4566- pub finished_at: Option<i64>,
4567+ /// State of a Manifest, Workflow, or Step
4568+ #[derive(Clone, Copy)]
4569+ pub enum State {
4570+ Success,
4571+ Partial,
4572+ Fail,
4573 }
4574
4575- pub struct WorkflowView {
4576- pub workflow: Workflow,
4577- pub steps: Vec<Step>,
4578- }
4579-
4580- #[derive(Debug, Clone, Serialize, Deserialize)]
4581- pub struct Step {
4582- pub id: i64,
4583- pub name: String,
4584- pub shell: String,
4585- pub input: String,
4586- pub started_at: Option<i64>,
4587- pub finished_at: Option<i64>,
4588- pub exit_code: Option<i64>,
4589- }
4590-
4591- #[derive(Clone, Serialize, Deserialize)]
4592- pub struct Dag {
4593- pub id: i64,
4594- pub manifest_id: i64,
4595- pub dag_content: String,
4596- }
4597-
4598- /// Used to list manifests from the UI
4599- #[derive(Clone)]
4600- pub enum Identifier {
4601- GitSHA(String),
4602- Id(i64),
4603- }
4604-
4605- #[derive(Serialize, Deserialize, Debug, Clone)]
4606- pub struct LogLine {
4607- pub output: Output,
4608- pub runtime: Duration,
4609- pub line: String,
4610- }
4611-
4612- /// standard stream when logging output
4613- #[derive(Serialize, Deserialize, Debug, Clone)]
4614- pub enum Output {
4615- Stdout,
4616- Stderr,
4617- }
4618-
4619- impl Display for Output {
4620- fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
4621- match self {
4622- Output::Stdout => write!(f, "stdout"),
4623- Output::Stderr => write!(f, "stderr"),
4624+ impl FromSql<Text, Sqlite> for State {
4625+ fn from_sql(
4626+ mut bytes: <Sqlite as diesel::backend::Backend>::RawValue<'_>,
4627+ ) -> diesel::deserialize::Result<Self> {
4628+ match String::from_utf8(bytes.read_blob().to_vec())?.as_str() {
4629+ "Success" => Ok(State::Success),
4630+ "Partial" => Ok(State::Partial),
4631+ "Fail" => Ok(State::Fail),
4632+ _ => unreachable!(),
4633 }
4634 }
4635 }
4636
4637- #[async_trait]
4638- pub trait BuildTx {
4639- async fn create_manifest(
4640- &mut self,
4641- collection: &str,
4642- name: &str,
4643- git_hash: &str,
4644- ) -> Result<i64, Error>;
4645- async fn create_workflow(
4646- &mut self,
4647- manifest_id: i64,
4648- name: &str,
4649- image: &str,
4650- ) -> Result<i64, Error>;
4651- async fn create_step(
4652- &mut self,
4653- job_id: i64,
4654- name: &str,
4655- input: &str,
4656- shell: &str,
4657- environment: HashMap<String, String>,
4658- ) -> Result<i64, Error>;
4659- async fn create_dag(&mut self, manifest_id: i64, dag_content: &str) -> Result<i64, Error>;
4660- }
4661-
4662- #[async_trait]
4663- impl BuildTx for Tx {
4664- // manifests
4665- async fn create_manifest(
4666- &mut self,
4667- collection: &str,
4668- name: &str,
4669- git_hash: &str,
4670- ) -> Result<i64, Error> {
4671- let ret = sqlx::query_file!("queries/manifests_create.sql", collection, name, git_hash)
4672- .fetch_one(&mut *self.inner)
4673- .await?;
4674- Ok(ret.id)
4675+ pub mod manifests {
4676+
4677+ use super::*;
4678+ use crate::schema::manifests::{dsl, table};
4679+ use diesel::{
4680+ ExpressionMethods, RunQueryDsl,
4681+ backend::Backend,
4682+ deserialize::{self, FromSqlRow},
4683+ expression::AsExpression,
4684+ serialize::{self, Output},
4685+ sql_types::Integer,
4686+ };
4687+
4688+ #[derive(AsExpression, Debug, Clone, Copy, Serialize, Deserialize, FromSqlRow, PartialEq)]
4689+ #[diesel(sql_type = Integer)]
4690+ pub enum State {
4691+ // Stdin = 0
4692+ Created = 1,
4693+ Running = 2,
4694+ Passed = 3,
4695+ Failed = 4,
4696 }
4697
4698- async fn create_workflow(
4699- &mut self,
4700- manifest_id: i64,
4701- name: &str,
4702- image: &str,
4703- ) -> Result<i64, Error> {
4704- let ret = sqlx::query_file!("queries/workflows_create.sql", manifest_id, name, image)
4705- .fetch_one(&mut *self.inner)
4706- .await?;
4707- Ok(ret.id)
4708+ impl std::fmt::Display for State {
4709+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
4710+ match self {
4711+ State::Created => write!(f, "Created"),
4712+ State::Running => write!(f, "Running"),
4713+ State::Passed => write!(f, "Passed"),
4714+ State::Failed => write!(f, "Failed"),
4715+ }
4716+ }
4717 }
4718
4719- async fn create_step(
4720- &mut self,
4721- workflow_id: i64,
4722- name: &str,
4723- input: &str,
4724- shell: &str,
4725- environment: HashMap<String, String>,
4726- ) -> Result<i64, Error> {
4727- let environment_json = serde_json::ser::to_string(&environment).unwrap();
4728- let ret = sqlx::query_file!(
4729- "queries/steps_create.sql",
4730- workflow_id,
4731- name,
4732- input,
4733- shell,
4734- environment_json
4735- )
4736- .fetch_one(&mut *self.inner)
4737- .await?;
4738- Ok(ret.id)
4739+ impl<DB> FromSql<Integer, DB> for State
4740+ where
4741+ DB: Backend,
4742+ i32: FromSql<Integer, DB>,
4743+ {
4744+ fn from_sql(bytes: DB::RawValue<'_>) -> deserialize::Result<Self> {
4745+ match i32::from_sql(bytes)? {
4746+ 1 => Ok(State::Created),
4747+ 2 => Ok(State::Running),
4748+ 3 => Ok(State::Passed),
4749+ 4 => Ok(State::Failed),
4750+ x => Err(format!("Unrecognized variant {}", x).into()),
4751+ }
4752+ }
4753 }
4754
4755- async fn create_dag(&mut self, manifest_id: i64, dag_content: &str) -> Result<i64, Error> {
4756- let ret = sqlx::query_file!("queries/dags_create.sql", manifest_id, dag_content)
4757- .fetch_one(&mut *self.inner)
4758- .await?;
4759- Ok(ret.id)
4760+ impl<DB> ToSql<Integer, DB> for State
4761+ where
4762+ DB: Backend,
4763+ i32: ToSql<Integer, DB>,
4764+ {
4765+ fn to_sql<'b>(&'b self, out: &mut Output<'b, '_, DB>) -> serialize::Result {
4766+ match *self {
4767+ State::Created => 1_i32.to_sql(out),
4768+ State::Running => 2_i32.to_sql(out),
4769+ State::Passed => 3_i32.to_sql(out),
4770+ State::Failed => 4_i32.to_sql(out),
4771+ }
4772+ }
4773 }
4774- }
4775
4776- #[async_trait]
4777- pub trait BuildExt {
4778- // manifests
4779- async fn create_manifest(
4780- &self,
4781- collection: &str,
4782- name: &str,
4783- git_hash: &str,
4784- ) -> Result<i64, Error>;
4785- async fn update_manifest_start(&self, manifest_id: i64) -> Result<(), Error>;
4786- async fn update_manifest_finish(&self, manifest_id: i64) -> Result<(), Error>;
4787- async fn list_manifest(
4788- &self,
4789- collection: Option<&str>,
4790- name: Option<&str>,
4791- filter: Option<&Identifier>,
4792- limit: i64,
4793- offset: i64,
4794- ) -> Result<Vec<ManifestItem>, Error>;
4795- async fn read_manifest(
4796- &self,
4797- collection: &str,
4798- name: &str,
4799- manifest_id: i64,
4800- ) -> Result<ManifestView, Error>;
4801-
4802- // workflows
4803- async fn create_workflow(
4804- &self,
4805- manifest_id: i64,
4806- name: &str,
4807- image: &str,
4808- ) -> Result<i64, Error>;
4809- async fn update_workflow_start(&self, workflow_id: i64) -> Result<(), Error>;
4810- async fn update_workflow_finish(&self, workflow_id: i64) -> Result<(), Error>;
4811- async fn read_workflow(&self, workflow_id: i64) -> Result<Workflow, Error>;
4812- async fn read_workflow_view(&self, workflow_id: i64) -> Result<WorkflowView, Error>;
4813- // steps
4814- async fn create_step(
4815- &self,
4816- job_id: i64,
4817- name: &str,
4818- input: &str,
4819- shell: &str,
4820- environment: HashMap<String, String>,
4821- ) -> Result<i64, Error>;
4822- async fn read_step(&self, step_id: i64) -> Result<Step, Error>;
4823- async fn read_step_env(&self, step_id: i64) -> Result<Environment, Error>;
4824- async fn update_step_start(&self, step_id: i64) -> Result<(), Error>;
4825- async fn update_step_finish(
4826- &self,
4827- step_id: i64,
4828- lines: &[LogLine],
4829- exit_code: i8,
4830- ) -> Result<(), Error>;
4831- async fn read_log_lines(&self, step_id: i64) -> Result<Vec<LogLine>, Error>;
4832-
4833- // misc
4834- async fn create_sample(&self, manifest_id: i64, sample: Sample) -> Result<i64, Error>;
4835- async fn read_dag(&self, manifest_id: i64) -> Result<Dag, Error>;
4836- }
4837-
4838- #[async_trait]
4839- impl BuildExt for Database {
4840- // manifests
4841- async fn create_manifest(
4842- &self,
4843- collection: &str,
4844- name: &str,
4845- git_hash: &str,
4846- ) -> Result<i64, Error> {
4847- let ret = sqlx::query_file!("queries/manifests_create.sql", collection, name, git_hash)
4848- .fetch_one(&self.pool)
4849- .await?;
4850- Ok(ret.id)
4851+ #[derive(Clone, Debug, Serialize, Deserialize)]
4852+ pub struct Summary {
4853+ pub manifest: Manifest,
4854+ pub workflows: Vec<(super::workflows::Workflow, Vec<super::steps::Step>)>,
4855 }
4856
4857- async fn update_manifest_start(&self, manifest_id: i64) -> Result<(), Error> {
4858- sqlx::query_file!("queries/manifests_update_start.sql", manifest_id,)
4859- .execute(&self.pool)
4860- .await?;
4861- Ok(())
4862+ #[derive(
4863+ Clone, Debug, Deserialize, Serialize, Queryable, Selectable, PartialEq, Identifiable,
4864+ )]
4865+ #[diesel(table_name = crate::schema::manifests)]
4866+ #[diesel(check_for_backend(Sqlite))]
4867+ pub struct Manifest {
4868+ pub id: i32,
4869+ pub collection: String,
4870+ pub name: String,
4871+ pub git_hash: String,
4872+ pub state: State,
4873+ pub created_at: i32,
4874+ pub dag_content: Option<String>,
4875+ pub started_at: Option<i32>,
4876+ pub finished_at: Option<i32>,
4877 }
4878
4879- async fn update_manifest_finish(&self, manifest_id: i64) -> Result<(), Error> {
4880- sqlx::query_file!("queries/manifests_update_finish.sql", manifest_id)
4881- .execute(&self.pool)
4882- .await?;
4883- Ok(())
4884- }
4885+ impl Ptr<'_> {
4886+ pub fn manifest_create(
4887+ &mut self,
4888+ collection: &str,
4889+ name: &str,
4890+ git_hash: &str,
4891+ ) -> Result<i32, Error> {
4892+ diesel::insert_into(table)
4893+ .values((
4894+ dsl::collection.eq(collection),
4895+ dsl::name.eq(name),
4896+ dsl::git_hash.eq(git_hash),
4897+ ))
4898+ .returning(dsl::id)
4899+ .get_result(self.0)
4900+ }
4901
4902- async fn list_manifest(
4903- &self,
4904- collection: Option<&str>,
4905- name: Option<&str>,
4906- filter: Option<&Identifier>,
4907- limit: i64,
4908- offset: i64,
4909- ) -> Result<Vec<ManifestItem>, Error> {
4910- let (manifest_id, git_hash) = match filter {
4911- Some(Identifier::GitSHA(gitsha)) => (None, Some(gitsha.clone())),
4912- Some(Identifier::Id(manifest_id)) => (Some(manifest_id.to_string()), None),
4913- None => (None, None),
4914- };
4915- let records = sqlx::query_file!(
4916- "queries/manifests_concise.sql",
4917- collection,
4918- name,
4919- manifest_id,
4920- git_hash,
4921- limit,
4922- offset
4923- )
4924- .fetch_all(&self.pool)
4925- .await?;
4926- let invocations: HashMap<i64, HashMap<i64, (String, Vec<ConciseStep>)>> =
4927- records.iter().fold(HashMap::new(), |mut mapped, record| {
4928- match mapped.get_mut(&record.manifest_id) {
4929- Some(workflow) => match workflow.get_mut(&record.workflow_id) {
4930- Some(steps) => steps.1.push((
4931- record.step_id,
4932- record.step_name.clone(),
4933- record.exit_code.map(|code| code == 0),
4934- )),
4935- None => {
4936- workflow.insert(
4937- record.workflow_id,
4938- (
4939- record.workflow_name.clone().unwrap_or_default(),
4940- vec![(
4941- record.step_id,
4942- record.step_name.clone(),
4943- record.exit_code.map(|code| code == 0),
4944- )],
4945- ),
4946- );
4947- }
4948- },
4949- None => {
4950- let workflow: HashMap<i64, (String, Vec<ConciseStep>)> = HashMap::from([(
4951- record.workflow_id,
4952- (
4953- record.workflow_name.clone().unwrap_or_default(),
4954- vec![(
4955- record.step_id,
4956- record.step_name.clone(),
4957- record.exit_code.map(|code| code == 0),
4958- )],
4959- ),
4960- )]);
4961- mapped.insert(record.manifest_id, workflow);
4962- }
4963- };
4964- mapped
4965- });
4966-
4967- let mut as_ordered_vec: Vec<(i64, Vec<ConciseWorkflow>)> =
4968- invocations
4969- .iter()
4970- .fold(Vec::new(), |mut accm, (manifest_id, workflows)| {
4971- let mut workflows: Vec<ConciseWorkflow> = workflows.iter().fold(
4972- Vec::new(),
4973- |mut wf_accm, (workflow_id, (workflow_name, steps))| {
4974- wf_accm.push((*workflow_id, workflow_name.clone(), steps.clone()));
4975- wf_accm
4976- },
4977- );
4978- workflows.sort_by(|first, second| first.0.cmp(&second.0));
4979- accm.push((*manifest_id, workflows));
4980- accm
4981- });
4982-
4983- as_ordered_vec.sort_by(|first, second| first.0.cmp(&second.0));
4984-
4985- let mut items: Vec<ManifestItem> = Vec::new();
4986- for (manifest_id, workflows) in as_ordered_vec {
4987- let manifest = sqlx::query_file_as!(
4988- Manifest,
4989- "queries/manifests_read.sql",
4990- manifest_id,
4991- None::<String>,
4992- None::<String>
4993- )
4994- .fetch_one(&self.pool)
4995- .await?;
4996- let success = workflows
4997- .iter()
4998- .all(|(_, _, steps)| steps.iter().all(|(_, _, success)| success.is_some()))
4999- .then(|| {
5000- workflows.iter().all(|(_, _, steps)| {
5001- steps
5002- .iter()
5003- .all(|(_, _, success)| success.is_some_and(|success| success))
5004- })
5005- });
5006-
5007- items.push(ManifestItem {
5008+ pub fn manifest_summary(&mut self, id: i32) -> Result<Summary, Error> {
5009+ let manifest = table
5010+ .filter(manifests::dsl::id.eq(id))
5011+ .select(Manifest::as_select())
5012+ .get_result(self.0)?;
5013+ let workflows = super::workflows::Workflow::belonging_to(&manifest)
5014+ .select(super::workflows::Workflow::as_select())
5015+ .load(self.0)?;
5016+ let workflows: Vec<(super::workflows::Workflow, Vec<super::steps::Step>)> = workflows
5017+ .into_iter()
5018+ .try_fold(Vec::new(), |mut workflows, workflow| {
5019+ let steps = super::steps::Step::belonging_to(&workflow)
5020+ .select(super::steps::Step::as_select())
5021+ .load(self.0)?;
5022+ workflows.push((workflow, steps));
5023+ Ok::<_, Error>(workflows)
5024+ })?;
5025+ Ok(Summary {
5026 manifest,
5027- success,
5028 workflows,
5029- });
5030+ })
5031 }
5032- items.sort_by(|first, second| second.manifest.id.cmp(&first.manifest.id));
5033- Ok(items)
5034- }
5035
5036- async fn read_manifest(
5037- &self,
5038- collection: &str,
5039- name: &str,
5040- manifest_id: i64,
5041- ) -> Result<ManifestView, Error> {
5042- let manifest = sqlx::query_file_as!(
5043- Manifest,
5044- "queries/manifests_read.sql",
5045- manifest_id,
5046- collection,
5047- name
5048- )
5049- .fetch_one(&self.pool)
5050- .await?;
5051- let dag = sqlx::query_file_as!(Dag, "queries/dags_read.sql", manifest_id)
5052- .fetch_one(&self.pool)
5053- .await?;
5054- let mut normalized = ManifestView {
5055- manifest,
5056- workflows: Vec::default(),
5057- dag,
5058- };
5059- let workflows = sqlx::query_file_as!(Workflow, "queries/workflows_list.sql", manifest_id)
5060- .fetch_all(&self.pool)
5061- .await?;
5062- for workflow in workflows {
5063- let steps = sqlx::query_file_as!(Step, "queries/steps_list.sql", workflow.id)
5064- .fetch_all(&self.pool)
5065- .await?;
5066- normalized.workflows.push((workflow, steps));
5067+ pub fn manifest_list(
5068+ &mut self,
5069+ collection: &str,
5070+ name: &str,
5071+ ) -> Result<Vec<Manifest>, Error> {
5072+ table
5073+ .select(Manifest::as_select())
5074+ .filter(dsl::collection.eq(collection))
5075+ .filter(dsl::name.eq(name))
5076+ .load(self.0)
5077 }
5078- Ok(normalized)
5079- }
5080
5081- // workflows
5082+ pub fn manifest_set_dag_content(
5083+ &mut self,
5084+ id: i32,
5085+ dag_content: &str,
5086+ ) -> Result<(), Error> {
5087+ diesel::update(table)
5088+ .filter(dsl::id.eq(id))
5089+ .set(dsl::dag_content.eq(dag_content))
5090+ .execute(self.0)?;
5091+ Ok(())
5092+ }
5093
5094- async fn read_workflow(&self, workflow_id: i64) -> Result<Workflow, Error> {
5095- let workflow = sqlx::query_file_as!(Workflow, "queries/workflows_read.sql", workflow_id)
5096- .fetch_one(&self.pool)
5097- .await?;
5098- Ok(workflow)
5099- }
5100+ pub fn manifest_start(&mut self, id: i32) -> Result<(), Error> {
5101+ diesel::update(table)
5102+ .filter(dsl::id.eq(id))
5103+ .set(dsl::started_at.eq(timeutil::epoch_secs() as i32))
5104+ .execute(self.0)?;
5105+ Ok(())
5106+ }
5107
5108- async fn read_workflow_view(&self, workflow_id: i64) -> Result<WorkflowView, Error> {
5109- let workflow = sqlx::query_file_as!(Workflow, "queries/workflows_read.sql", workflow_id)
5110- .fetch_one(&self.pool)
5111- .await?;
5112- let steps = sqlx::query_file_as!(Step, "queries/steps_list.sql", workflow_id)
5113- .fetch_all(&self.pool)
5114- .await?;
5115- Ok(WorkflowView { workflow, steps })
5116+ pub fn manifest_end(&mut self, id: i32) -> Result<(), Error> {
5117+ diesel::update(table)
5118+ .filter(dsl::id.eq(id))
5119+ .set(dsl::finished_at.eq(timeutil::epoch_secs() as i32))
5120+ .execute(self.0)?;
5121+ Ok(())
5122+ }
5123 }
5124+ }
5125
5126- async fn create_workflow(
5127- &self,
5128- manifest_id: i64,
5129- name: &str,
5130- image: &str,
5131- ) -> Result<i64, Error> {
5132- let ret = sqlx::query_file!("queries/workflows_create.sql", manifest_id, name, image)
5133- .fetch_one(&self.pool)
5134- .await?;
5135- Ok(ret.id)
5136+ pub mod workflows {
5137+ use super::*;
5138+ use crate::schema::workflows::{dsl, table};
5139+ use diesel::{ExpressionMethods, RunQueryDsl};
5140+
5141+ #[derive(Insertable)]
5142+ #[diesel(table_name = crate::schema::workflows)]
5143+ pub struct CreateWorkflowArgs<'a> {
5144+ pub manifest_id: i32,
5145+ pub name: &'a str,
5146+ pub image: &'a str,
5147 }
5148
5149- async fn update_workflow_start(&self, workflow_id: i64) -> Result<(), Error> {
5150- sqlx::query_file!("queries/workflows_update_start.sql", workflow_id,)
5151- .execute(&self.pool)
5152- .await?;
5153- Ok(())
5154+ #[derive(
5155+ Clone, Debug, Serialize, Deserialize, Queryable, Identifiable, Selectable, Associations,
5156+ )]
5157+ #[diesel(table_name = crate::schema::workflows)]
5158+ #[diesel(belongs_to(super::manifests::Manifest))]
5159+ pub struct Workflow {
5160+ pub id: i32,
5161+ pub manifest_id: i32,
5162+ pub name: String,
5163+ pub image: String,
5164+ pub started_at: Option<i32>,
5165+ pub finished_at: Option<i32>,
5166 }
5167
5168- async fn update_workflow_finish(&self, workflow_id: i64) -> Result<(), Error> {
5169- sqlx::query_file!("queries/workflows_update_finish.sql", workflow_id)
5170- .execute(&self.pool)
5171- .await?;
5172- Ok(())
5173+ impl Ptr<'_> {
5174+ pub fn workflow_create(&mut self, args: &CreateWorkflowArgs) -> Result<i32, Error> {
5175+ diesel::insert_into(table)
5176+ .values(args)
5177+ .returning(dsl::id)
5178+ .get_result(self.0)
5179+ }
5180+
5181+ pub fn workflow_read(&mut self, workflow_id: i32) -> Result<Workflow, Error> {
5182+ table
5183+ .select(Workflow::as_select())
5184+ .filter(dsl::id.eq(workflow_id))
5185+ .first::<Workflow>(self.0)
5186+ }
5187+
5188+ pub fn workflow_start(&mut self, workflow_id: i32) -> Result<(), Error> {
5189+ diesel::update(table)
5190+ .filter(dsl::id.eq(workflow_id))
5191+ .set(dsl::started_at.eq(timeutil::epoch_secs() as i32))
5192+ .execute(self.0)?;
5193+ Ok(())
5194+ }
5195+ pub fn workflow_finish(&mut self, workflow_id: i32) -> Result<(), Error> {
5196+ diesel::update(table)
5197+ .filter(dsl::id.eq(workflow_id))
5198+ .set(dsl::finished_at.eq(timeutil::epoch_secs() as i32))
5199+ .execute(self.0)?;
5200+ Ok(())
5201+ }
5202 }
5203+ }
5204
5205- // steps
5206-
5207- async fn create_step(
5208- &self,
5209- workflow_id: i64,
5210- name: &str,
5211- input: &str,
5212- shell: &str,
5213- environment: HashMap<String, String>,
5214- ) -> Result<i64, Error> {
5215- let environment_json = serde_json::ser::to_string(&environment).unwrap();
5216- let ret = sqlx::query_file!(
5217- "queries/steps_create.sql",
5218- workflow_id,
5219- name,
5220- input,
5221- shell,
5222- environment_json
5223- )
5224- .fetch_one(&self.pool)
5225- .await?;
5226- Ok(ret.id)
5227+ pub mod steps {
5228+ use super::*;
5229+ use crate::schema::steps::{dsl, table};
5230+ use diesel::{ExpressionMethods, RunQueryDsl};
5231+
5232+ #[derive(Insertable)]
5233+ #[diesel(table_name = crate::schema::steps)]
5234+ pub struct CreateStepArgs<'a> {
5235+ pub workflow_id: i32,
5236+ pub name: &'a str,
5237+ pub shell: &'a str,
5238+ pub input: &'a str,
5239 }
5240
5241- async fn read_step(&self, step_id: i64) -> Result<Step, Error> {
5242- sqlx::query_file_as!(Step, "queries/steps_read.sql", step_id)
5243- .fetch_one(&self.pool)
5244- .await
5245+ #[derive(
5246+ Clone, Debug, Serialize, Deserialize, Queryable, Identifiable, Associations, Selectable,
5247+ )]
5248+ #[diesel(table_name = crate::schema::steps)]
5249+ #[diesel(belongs_to(super::workflows::Workflow))]
5250+ pub struct Step {
5251+ pub id: i32,
5252+ pub workflow_id: i32,
5253+ pub name: String,
5254+ pub shell: String,
5255+ pub input: String,
5256+ pub environment_json: Option<String>,
5257+ pub exit_code: Option<i32>,
5258+ pub started_at: Option<i32>,
5259+ pub finished_at: Option<i32>,
5260 }
5261
5262- async fn read_step_env(&self, step_id: i64) -> Result<Environment, Error> {
5263- let rows = sqlx::query_file!("queries/steps_read_env.sql", step_id)
5264- .fetch_all(&self.pool)
5265- .await?;
5266+ impl Ptr<'_> {
5267+ pub fn step_create(&mut self, args: &CreateStepArgs) -> Result<i32, Error> {
5268+ diesel::insert_into(table)
5269+ .values(args)
5270+ .returning(dsl::id)
5271+ .get_result(self.0)
5272+ }
5273+
5274+ pub fn step_read(&mut self, id: i32) -> Result<Step, Error> {
5275+ table
5276+ .select(Step::as_select())
5277+ .filter(dsl::id.eq(id))
5278+ .first::<Step>(self.0)
5279+ }
5280
5281- let envs: Vec<Env> = rows
5282- .iter()
5283- .map(|record| Env((record.name.clone(), record.value.clone())))
5284- .collect();
5285+ pub fn step_start(&mut self, id: i32) -> Result<(), Error> {
5286+ diesel::update(table)
5287+ .filter(dsl::id.eq(id))
5288+ .set(dsl::started_at.eq(timeutil::epoch_secs() as i32))
5289+ .execute(self.0)?;
5290+ Ok(())
5291+ }
5292
5293- Ok(Environment(envs))
5294+ pub fn step_finish(&mut self, id: i32, exit_code: i32) -> Result<(), Error> {
5295+ diesel::update(table)
5296+ .filter(dsl::id.eq(id))
5297+ .set((
5298+ dsl::finished_at.eq(timeutil::epoch_secs() as i32),
5299+ dsl::exit_code.eq(exit_code),
5300+ ))
5301+ .execute(self.0)?;
5302+ Ok(())
5303+ }
5304 }
5305+ }
5306
5307- async fn update_step_start(&self, step_id: i64) -> Result<(), Error> {
5308- sqlx::query_file!("queries/steps_update_start.sql", step_id)
5309- .execute(&self.pool)
5310- .await?;
5311- Ok(())
5312+ pub mod logs {
5313+ use super::*;
5314+ use crate::schema::logs::{dsl, table};
5315+ use diesel::{
5316+ ExpressionMethods, RunQueryDsl,
5317+ backend::Backend,
5318+ deserialize::{self, FromSqlRow},
5319+ expression::AsExpression,
5320+ serialize::{self, Output},
5321+ sql_types::Integer,
5322+ };
5323+
5324+ #[derive(AsExpression, Debug, Clone, Copy, Serialize, Deserialize, FromSqlRow)]
5325+ #[diesel(sql_type = Integer)]
5326+ pub enum Stream {
5327+ // Stdin = 0
5328+ Stdout = 1,
5329+ Stderr = 2,
5330 }
5331
5332- async fn update_step_finish(
5333- &self,
5334- step_id: i64,
5335- logs: &[LogLine],
5336- exit_code: i8,
5337- ) -> Result<(), Error> {
5338- sqlx::query_file!("queries/steps_update_finish.sql", exit_code, step_id)
5339- .execute(&self.pool)
5340- .await?;
5341- for log_line in logs {
5342- let runtime_ms = log_line.runtime.as_millis() as i64;
5343- let output = log_line.output.to_string();
5344- let line = log_line.line.clone();
5345- sqlx::query_file!(
5346- "queries/steps_update_log_line.sql",
5347- step_id,
5348- runtime_ms,
5349- output,
5350- line,
5351- )
5352- .execute(&self.pool)
5353- .await?;
5354+ impl<DB> FromSql<Integer, DB> for Stream
5355+ where
5356+ DB: Backend,
5357+ i32: FromSql<Integer, DB>,
5358+ {
5359+ fn from_sql(bytes: DB::RawValue<'_>) -> deserialize::Result<Self> {
5360+ match i32::from_sql(bytes)? {
5361+ 1 => Ok(Stream::Stdout),
5362+ 2 => Ok(Stream::Stderr),
5363+ x => Err(format!("Unrecognized variant {}", x).into()),
5364+ }
5365 }
5366+ }
5367
5368- Ok(())
5369+ impl<DB> ToSql<Integer, DB> for Stream
5370+ where
5371+ DB: Backend,
5372+ i32: ToSql<Integer, DB>,
5373+ {
5374+ fn to_sql<'b>(&'b self, out: &mut Output<'b, '_, DB>) -> serialize::Result {
5375+ match *self {
5376+ Stream::Stdout => 1_i32.to_sql(out),
5377+ Stream::Stderr => 2_i32.to_sql(out),
5378+ }
5379+ }
5380 }
5381
5382- async fn read_log_lines(&self, step_id: i64) -> Result<Vec<LogLine>, Error> {
5383- let records = sqlx::query_file!("queries/steps_read_log_lines.sql", step_id)
5384- .fetch_all(&self.pool)
5385- .await?;
5386- Ok(records
5387- .into_iter()
5388- .map(|record| LogLine {
5389- output: match record.stream.as_str() {
5390- "stdout" => Output::Stdout,
5391- "stderr" => Output::Stderr,
5392- other => unimplemented!("Unknown stream: {other}"),
5393- },
5394- runtime: Duration::from_millis(record.runtime as u64),
5395- line: record.line.unwrap_or_default(),
5396- })
5397- .collect())
5398+ #[derive(Insertable)]
5399+ #[diesel(table_name = crate::schema::logs)]
5400+ pub struct WriteLineArgs<'a> {
5401+ pub step_id: i32,
5402+ pub stream: Stream,
5403+ pub line: &'a str,
5404 }
5405
5406- // misc
5407-
5408- async fn create_sample(&self, manifest_id: i64, sample: Sample) -> Result<i64, Error> {
5409- let ret = sqlx::query_file!(
5410- "queries/samples_create.sql",
5411- manifest_id,
5412- sample.load_1m,
5413- sample.load_5m,
5414- sample.load_15m,
5415- sample.disk_total_bytes,
5416- sample.disk_free_bytes,
5417- sample.net_sent_bytes,
5418- sample.net_received_bytes,
5419- sample.net_sent_packets,
5420- sample.net_received_packets,
5421- sample.mem_total_bytes,
5422- sample.mem_free_bytes,
5423- sample.mem_available_bytes,
5424- )
5425- .fetch_one(&self.pool)
5426- .await?;
5427- Ok(ret.id)
5428+ #[derive(
5429+ Clone, Debug, Serialize, Deserialize, Queryable, Identifiable, Associations, Selectable,
5430+ )]
5431+ #[diesel(table_name = crate::schema::logs)]
5432+ #[diesel(belongs_to(super::steps::Step))]
5433+ pub struct Line {
5434+ pub id: i32,
5435+ pub step_id: i32,
5436+ pub timestamp: i32,
5437+ pub stream: Stream,
5438+ pub line: String,
5439 }
5440
5441- async fn read_dag(&self, manifest_id: i64) -> Result<Dag, Error> {
5442- sqlx::query_file_as!(Dag, "queries/dags_read.sql", manifest_id)
5443- .fetch_one(&self.pool)
5444- .await
5445+ impl Ptr<'_> {
5446+ pub fn log_write_line(&mut self, args: &WriteLineArgs) -> Result<i32, Error> {
5447+ diesel::insert_into(table)
5448+ .values((args, dsl::timestamp.eq(timeutil::epoch_secs() as i32))) // FIXME should be offset from start of step
5449+ .returning(dsl::id)
5450+ .get_result(self.0)
5451+ }
5452+
5453+ pub fn log_read(&mut self, step_id: i32, _offset: Option<i32>) -> Result<Vec<Line>, Error> {
5454+ table
5455+ .select(Line::as_select())
5456+ .filter(dsl::step_id.eq(step_id))
5457+ .load(self.0)
5458+ }
5459 }
5460 }
5461 diff --git a/crates/database/src/contributors.rs b/crates/database/src/contributors.rs
5462deleted file mode 100644
5463index ca44e2b..0000000
5464--- a/crates/database/src/contributors.rs
5465+++ /dev/null
5466 @@ -1,101 +0,0 @@
5467- use futures::TryStreamExt;
5468- use serde::{Deserialize, Serialize};
5469- use sqlx::Error;
5470- use time::OffsetDateTime;
5471-
5472- use crate::Wrapper;
5473-
5474- #[derive(Clone, Serialize, Deserialize)]
5475- pub struct AuthorWithStats {
5476- pub username: Option<String>,
5477- pub email: Option<String>,
5478- pub count: Option<i64>,
5479- pub lines_added: Option<i64>,
5480- pub lines_removed: Option<i64>,
5481- pub percentage: Option<f64>,
5482- }
5483-
5484- pub struct Database<'a>(pub &'a Wrapper);
5485-
5486- impl Database<'_> {
5487- pub async fn upsert_author(&self, name: &str, email: &str) -> Result<i64, Error> {
5488- let ret = sqlx::query_file!("../../queries/authors_upsert.sql", name, email)
5489- .fetch_one(&self.0.pool)
5490- .await?;
5491- Ok(ret.id)
5492- }
5493-
5494- pub async fn authors_list(
5495- &self,
5496- repo_path: &str,
5497- git_hash: &str,
5498- ) -> Result<Vec<AuthorWithStats>, Error> {
5499- sqlx::query_file_as!(
5500- AuthorWithStats,
5501- "../../queries/authors_list_project.sql",
5502- git_hash,
5503- repo_path,
5504- repo_path,
5505- repo_path,
5506- )
5507- .fetch_all(&self.0.pool)
5508- .await
5509- }
5510-
5511- pub async fn contribution_add(
5512- &self,
5513- author_id: i64,
5514- repo_path: &str,
5515- git_hash: &str,
5516- timestamp: OffsetDateTime,
5517- lines_added: i64,
5518- lines_removed: i64,
5519- ) -> Result<(), Error> {
5520- sqlx::query_file!(
5521- "../../queries/contributions_add.sql",
5522- author_id,
5523- git_hash,
5524- repo_path,
5525- timestamp,
5526- lines_added,
5527- lines_removed
5528- )
5529- .execute(&self.0.pool)
5530- .await?
5531- .last_insert_rowid();
5532- sqlx::query_file!(
5533- "../../queries/contribution_add.sql",
5534- author_id,
5535- repo_path,
5536- git_hash,
5537- repo_path,
5538- author_id,
5539- )
5540- .execute(&self.0.pool)
5541- .await?;
5542-
5543- Ok(())
5544- }
5545-
5546- pub async fn contributors_list(
5547- &self,
5548- repo_path: &str,
5549- git_hash: &str,
5550- ) -> Result<Vec<(String, String, i64)>, Error> {
5551- let mut ret: Vec<(String, String, i64)> = Vec::new();
5552- let mut rows = sqlx::query_file!(
5553- "../../queries/contributions_list.sql",
5554- repo_path,
5555- git_hash,
5556- repo_path,
5557- repo_path,
5558- git_hash,
5559- repo_path,
5560- )
5561- .fetch(&self.0.pool);
5562- while let Some(row) = rows.try_next().await? {
5563- ret.push((row.name.unwrap(), row.email.unwrap(), row.percentage))
5564- }
5565- Ok(ret)
5566- }
5567- }
5568 diff --git a/crates/database/src/jobs.rs b/crates/database/src/jobs.rs
5569deleted file mode 100644
5570index 9e3cbcf..0000000
5571--- a/crates/database/src/jobs.rs
5572+++ /dev/null
5573 @@ -1,101 +0,0 @@
5574- use sqlx::Error;
5575- use time::OffsetDateTime;
5576-
5577- use crate::Wrapper;
5578-
5579- #[derive(Clone)]
5580- pub struct Job {
5581- pub id: i64,
5582- pub created_at: OffsetDateTime,
5583- pub repo_path: String,
5584- pub kind: String,
5585- pub runtime: Option<u32>,
5586- pub success: Option<bool>,
5587- pub commits: i32,
5588- }
5589-
5590- pub struct Database<'a>(pub &'a Wrapper);
5591-
5592- impl Database<'_> {
5593- pub async fn create_job(&self, repo_path: Option<&str>, kind: &str) -> Result<i64, Error> {
5594- let created_at = OffsetDateTime::now_utc();
5595- let ret = sqlx::query_file!("../../queries/jobs_create.sql", created_at, repo_path, kind,)
5596- .fetch_one(&self.0.pool)
5597- .await?;
5598- Ok(ret.id)
5599- }
5600-
5601- pub async fn update_job(&self, job_id: i64, runtime: i64, success: bool) -> Result<(), Error> {
5602- sqlx::query_file!("../../queries/jobs_update.sql", runtime, success, job_id)
5603- .execute(&self.0.pool)
5604- .await?;
5605- Ok(())
5606- }
5607-
5608- pub async fn list_jobs(&self, repo_path: Option<&str>) -> Result<Vec<Job>, Error> {
5609- let jobs = sqlx::query_file_as!(Job, "../../queries/jobs_list.sql", repo_path, repo_path)
5610- .fetch_all(&self.0.pool)
5611- .await?;
5612- Ok(jobs)
5613- }
5614-
5615- pub async fn delete_job(&self, job_id: i64) -> Result<(), Error> {
5616- sqlx::query_file_as!(Job, "../../queries/jobs_delete.sql", job_id)
5617- .execute(&self.0.pool)
5618- .await?;
5619- Ok(())
5620- }
5621-
5622- pub async fn purge(&self, repo_path: &str) -> Result<(), Error> {
5623- sqlx::query_file!("../../queries/job_tracking_delete_by_repo.sql", repo_path)
5624- .execute(&self.0.pool)
5625- .await?;
5626- sqlx::query_file!("../../queries/jobs_delete.sql", repo_path)
5627- .execute(&self.0.pool)
5628- .await?;
5629- sqlx::query_file!("../../queries/languages_delete.sql", repo_path)
5630- .execute(&self.0.pool)
5631- .await?;
5632- sqlx::query_file!("../../queries/contribution_delete.sql", repo_path)
5633- .execute(&self.0.pool)
5634- .await?;
5635- sqlx::query_file!("../../queries/contributions_delete.sql", repo_path)
5636- .execute(&self.0.pool)
5637- .await?;
5638- Ok(())
5639- }
5640-
5641- pub async fn create_hash(
5642- &self,
5643- repo_path: &str,
5644- git_hash: &str,
5645- kind: &str,
5646- job_id: i64,
5647- ) -> Result<(), Error> {
5648- sqlx::query_file!(
5649- "../../queries/job_tracking_add.sql",
5650- repo_path,
5651- git_hash,
5652- kind,
5653- job_id
5654- )
5655- .execute(&self.0.pool)
5656- .await?;
5657- Ok(())
5658- }
5659-
5660- // lookup the latest hash for the job kind
5661- pub async fn read_hash(&self, repo_path: &str, kind: &str) -> Result<Option<String>, Error> {
5662- let ret = sqlx::query_file!("../../queries/job_tracking_read.sql", repo_path, kind,)
5663- .fetch_optional(&self.0.pool)
5664- .await?;
5665- Ok(ret.map(|record| record.git_hash))
5666- }
5667-
5668- pub async fn latest_commit(&self, repo_path: &str) -> Result<Option<String>, Error> {
5669- let ret = sqlx::query_file!("../../queries/latest_commit.sql", repo_path)
5670- .fetch_optional(&self.0.pool)
5671- .await?;
5672- Ok(ret.map(|record| record.git_hash))
5673- }
5674- }
5675 diff --git a/crates/database/src/languages.rs b/crates/database/src/languages.rs
5676deleted file mode 100644
5677index d730069..0000000
5678--- a/crates/database/src/languages.rs
5679+++ /dev/null
5680 @@ -1,52 +0,0 @@
5681- use futures::TryStreamExt;
5682- use sqlx::Error;
5683-
5684- use crate::Wrapper;
5685-
5686- pub struct Database<'a>(pub &'a Wrapper);
5687-
5688- impl Database<'_> {
5689- pub async fn language_create(
5690- &self,
5691- repo_path: &str,
5692- git_hash: &str,
5693- languages: Vec<(String, i64)>,
5694- ) -> Result<(), Error> {
5695- for language in languages {
5696- sqlx::query_file!(
5697- "../../queries/languages_add.sql",
5698- git_hash,
5699- repo_path,
5700- language.0,
5701- language.1,
5702- )
5703- .execute(&self.0.pool)
5704- .await?;
5705- }
5706- Ok(())
5707- }
5708-
5709- pub async fn languages_list(
5710- &self,
5711- repo_path: &str,
5712- git_hash: &str,
5713- ) -> Result<Vec<(String, u8, u32)>, Error> {
5714- let mut languages: Vec<(String, u8, u32)> = Vec::new();
5715- let mut rows = sqlx::query_file!(
5716- "../../queries/languages_list.sql",
5717- repo_path,
5718- git_hash,
5719- repo_path,
5720- git_hash
5721- )
5722- .fetch(&self.0.pool);
5723- while let Some(row) = rows.try_next().await? {
5724- languages.push((
5725- row.language.clone(),
5726- row.percentage.unwrap_or(0.0) as u8,
5727- row.loc as u32,
5728- ));
5729- }
5730- Ok(languages)
5731- }
5732- }
5733 diff --git a/crates/database/src/lib.rs b/crates/database/src/lib.rs
5734index 2e3329f..b2561f6 100644
5735--- a/crates/database/src/lib.rs
5736+++ b/crates/database/src/lib.rs
5737 @@ -1,143 +1,81 @@
5738- use std::fs::create_dir_all;
5739- use std::path::{Path, PathBuf};
5740+ use std::path::Path;
5741
5742- use sqlx::ConnectOptions;
5743- use sqlx::migrate::Migrator;
5744- use sqlx::sqlite::{SqliteConnectOptions, SqliteLockingMode, SqlitePool, SqlitePoolOptions};
5745- use sqlx::{Error as SqlxError, Sqlite, Transaction};
5746- use tracing::log;
5747-
5748- // pub mod contributors;
5749- // pub mod jobs;
5750- // pub mod languages;
5751- // pub mod mail;
5752- // pub mod stats;
5753+ use diesel::{Connection, SqliteConnection};
5754+ use diesel_migrations::{EmbeddedMigrations, MigrationHarness, embed_migrations};
5755
5756 pub mod build;
5757+ pub mod schema;
5758
5759- pub type Error = SqlxError;
5760+ pub use diesel::result::Error;
5761
5762- pub async fn migrate(path: &str, migrations: &str) -> Result<(), Error> {
5763- if let Some(path) = Path::new(path).parent() {
5764- create_dir_all(path)?
5765- }
5766- log::info!("running migrations");
5767- let opts = SqliteConnectOptions::new()
5768- .filename(path)
5769- .create_if_missing(true);
5770- let pool = SqlitePool::connect_with(opts).await?;
5771- Migrator::new(Path::new(migrations))
5772- .await?
5773- .run(&pool)
5774- .await?;
5775- Ok(())
5776- }
5777+ const MIGRATIONS: EmbeddedMigrations = embed_migrations!("./migrations");
5778
5779- #[derive(Default)]
5780- pub struct Builder {
5781- pub url: Option<String>,
5782- pub read_only: bool,
5783- pub log_queries: bool,
5784- pub migrations: Option<PathBuf>,
5785+ #[derive(Debug, thiserror::Error)]
5786+ pub enum MigrationError {
5787+ #[error("SQLite Connection Failed: {0}")]
5788+ Connection(#[from] diesel::ConnectionError),
5789+ #[error("Migration failed: {0}")]
5790+ Migration(String),
5791 }
5792
5793- impl Builder {
5794- pub fn path(mut self, path: &Path) -> Self {
5795- self.url = Some(path.to_str().unwrap().to_string());
5796- self
5797- }
5798-
5799- pub fn read_only(mut self, read_only: bool) -> Self {
5800- self.read_only = read_only;
5801- self
5802- }
5803-
5804- pub fn log_queries(mut self, log_queries: bool) -> Self {
5805- self.log_queries = log_queries;
5806- self
5807- }
5808-
5809- pub fn migrations(mut self, path: &Path) -> Self {
5810- self.migrations = Some(path.to_path_buf());
5811- self
5812- }
5813-
5814- pub async fn build(self) -> Result<Wrapper, Error> {
5815- let log_level = if self.log_queries {
5816- // don't log queries when the global logger is at level INFO
5817- log::LevelFilter::Info
5818- } else {
5819- log::LevelFilter::Debug
5820- };
5821-
5822- let mut pool_opts = SqlitePoolOptions::new();
5823- // TODO: set read-only for main web serving mode
5824- let mut opts = SqliteConnectOptions::new()
5825- .log_statements(log_level)
5826- // TODO
5827- .serialized(true)
5828- // .read_only(read_only)
5829- .create_if_missing(true);
5830- match self.url {
5831- Some(url) => {
5832- opts = opts.filename(url);
5833- }
5834- None => {
5835- // no path will automatically place SQLite in :memory: mode
5836- // this code path is only for testing. See
5837- // https://github.com/launchbadge/sqlx/issues/362 for details.
5838- log::warn!("SQLite is configured for debugging");
5839- pool_opts = pool_opts
5840- .max_connections(1)
5841- .idle_timeout(None)
5842- .max_lifetime(None);
5843- }
5844- }
5845+ fn connect(connection_url: &Path, read_only: bool) -> Result<SqliteConnection, MigrationError> {
5846+ let as_str = format!(
5847+ "file://{}{}",
5848+ connection_url.to_string_lossy(),
5849+ if read_only { "?mode=ro" } else { "" }
5850+ );
5851+ tracing::info!("Connecting to SQLite with connection string: {as_str}");
5852+ let conn = SqliteConnection::establish(&as_str)?;
5853+ Ok(conn)
5854+ }
5855
5856- if self.read_only {
5857- opts = opts.read_only(true)
5858- } else {
5859- opts = opts.locking_mode(SqliteLockingMode::Normal)
5860- }
5861- let pool = pool_opts.connect_with(opts).await?;
5862- if let Some(migrations_path) = self.migrations {
5863- log::info!("running migrations");
5864- Migrator::new(Path::new(&migrations_path))
5865- .await?
5866- .run(&pool)
5867- .await?
5868- }
5869- Ok(Wrapper { pool })
5870+ /// Apply all pending migrations.
5871+ /// TODO: Once Ayllu reaches 1.0 we can make this more flexible.
5872+ pub fn migrate(connection_url: &Path) -> Result<(), MigrationError> {
5873+ let mut conn = connect(connection_url, false)?;
5874+ for migration in conn
5875+ .pending_migrations(MIGRATIONS)
5876+ .map_err(|e| MigrationError::Migration(e.to_string()))?
5877+ {
5878+ tracing::info!("Applying migration {}", migration.name());
5879+ let version = conn
5880+ .run_migration(&migration)
5881+ .map_err(|e| MigrationError::Migration(e.to_string()))?;
5882+ tracing::info!("Success: {}", version);
5883 }
5884+ Ok(())
5885 }
5886
5887- pub struct Tx {
5888- inner: Transaction<'static, Sqlite>,
5889+ pub struct Ptr<'a>(&'a mut SqliteConnection);
5890+
5891+ pub struct Wrapper {
5892+ conn: SqliteConnection,
5893 }
5894
5895- impl Tx {
5896- pub async fn commit(self) -> Result<(), Error> {
5897- self.inner.commit().await
5898+ impl Wrapper {
5899+ pub fn new(path: &Path) -> Result<Self, MigrationError> {
5900+ Ok(Self {
5901+ conn: connect(path, false)?,
5902+ })
5903 }
5904
5905- pub async fn rollback(self) -> Result<(), Error> {
5906- self.inner.rollback().await
5907+ pub fn new_ro(path: &Path) -> Result<Self, MigrationError> {
5908+ Ok(Self {
5909+ conn: connect(path, true)?,
5910+ })
5911 }
5912- }
5913
5914- #[derive(Clone, Debug)]
5915- pub struct Wrapper {
5916- pub pool: SqlitePool,
5917- }
5918-
5919- impl Wrapper {
5920- pub async fn begin(&self) -> Result<Tx, Error> {
5921- let inner = self.pool.begin().await?;
5922- Ok(Tx { inner })
5923+ /// Get a synchronous connection to the database
5924+ pub fn call<'a>(&'a mut self) -> Ptr<'a> {
5925+ Ptr(&mut self.conn)
5926 }
5927
5928- pub async fn close(&self) -> Result<(), Error> {
5929- self.pool.close().await;
5930- Ok(())
5931+ /// Execute the closure in a transaction
5932+ pub fn with<F, T, E>(&mut self, f: F) -> Result<T, E>
5933+ where
5934+ E: From<diesel::result::Error>,
5935+ F: FnOnce(Ptr) -> Result<T, E>,
5936+ {
5937+ self.conn.transaction(move |conn| f(Ptr(conn)))
5938 }
5939 }
5940 diff --git a/crates/database/src/mail.rs b/crates/database/src/mail.rs
5941deleted file mode 100644
5942index 8b15da9..0000000
5943--- a/crates/database/src/mail.rs
5944+++ /dev/null
5945 @@ -1,280 +0,0 @@
5946- use std::fmt::Display;
5947-
5948- use serde::{Deserialize, Serialize};
5949- use sqlx::{QueryBuilder, Sqlite};
5950-
5951- use crate::Error;
5952- use crate::Wrapper;
5953-
5954- /// RFC2919 List-Id
5955- /// List-Id: List Header Mailing List <list-header.nisto.com>
5956- /// (Description, Address)
5957- #[derive(Clone, Default)]
5958- pub struct ListId(pub String, pub String);
5959-
5960- impl Display for ListId {
5961- fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
5962- write!(f, "{} <{}>", self.0, self.1)
5963- }
5964- }
5965-
5966- #[derive(Clone, Default, Serialize, Deserialize)]
5967- pub struct List {
5968- pub name: String,
5969- pub address: String,
5970- pub enabled: bool,
5971- pub description: Option<String>,
5972- }
5973-
5974- #[derive(Clone, Default, Serialize, Deserialize)]
5975- pub struct Participant {
5976- pub address: String,
5977- pub name: String,
5978- pub authorized_sender: bool,
5979- pub subscriptions: Option<Vec<String>>,
5980- }
5981-
5982- #[derive(Clone, Default, Serialize, Deserialize)]
5983- pub struct Message {
5984- pub id: i64,
5985- pub message_id: String,
5986- pub list_id: i64,
5987- pub in_reply_to: Option<String>,
5988- pub subject: Option<String>,
5989- pub mail_from: i64,
5990- pub raw_message: Option<Vec<u8>>,
5991- pub content_body: Option<String>,
5992- pub reply_count: i64,
5993- }
5994-
5995- #[derive(Clone, Default, Serialize, Deserialize)]
5996- pub struct ThreadMessage {
5997- pub message_id: String,
5998- pub in_reply_to: Option<String>,
5999- pub subject: Option<String>,
6000- pub mail_from: String,
6001- pub raw_message: Option<Vec<u8>>,
6002- pub content_body: Option<String>,
6003- pub reply_count: i64,
6004- pub depth: i64,
6005- }
6006-
6007- impl Message {
6008- pub fn is_patch(&self) -> bool {
6009- todo!()
6010- }
6011- }
6012-
6013- #[derive(Clone, Default, Serialize, Deserialize)]
6014- pub struct ThreadSummary {
6015- pub message_id: String,
6016- pub mail_from: String,
6017- pub subject: Option<String>,
6018- pub reply_count: i64,
6019- }
6020-
6021- pub struct DeliveryParams<'a> {
6022- pub message_id: &'a str,
6023- pub mail_to: &'a [&'a str],
6024- pub mail_from: &'a str,
6025- pub subject: Option<&'a str>,
6026- pub raw_message: &'a [u8],
6027- pub content_body: &'a str,
6028- pub in_reply_to: Option<&'a str>,
6029- }
6030-
6031- #[derive(Clone)]
6032- pub struct OutgoingMessage {
6033- pub id: i64,
6034- pub message_id: String,
6035- pub list_name: String,
6036- pub address: String,
6037- pub raw_message: Option<Vec<u8>>,
6038- }
6039-
6040- pub enum DeliveryError {
6041- // TODO
6042- Generic(String),
6043- }
6044-
6045- impl DeliveryError {
6046- pub fn kind(&self) -> String {
6047- match self {
6048- DeliveryError::Generic(_) => String::from("Generic"),
6049- }
6050- }
6051- }
6052-
6053- pub struct Database<'a>(pub &'a Wrapper);
6054-
6055- impl Database<'_> {
6056- pub async fn setup(
6057- &self,
6058- lists: Vec<List>,
6059- participants: Option<Vec<Participant>>,
6060- ) -> Result<(), Error> {
6061- let mut tx = self.0.pool.begin().await?;
6062- tracing::info!("Deactivating any unused mailing lists");
6063- let addresses: Vec<String> = lists.iter().map(|list| list.name.clone()).collect();
6064- // NOTE: Macros cannot construct WHERE address IN ($) so we have
6065- // to build it by hand.
6066- // See this note:
6067- // https://github.com/launchbadge/sqlx/blob/main/FAQ.md#how-can-i-do-a-select--where-foo-in--query
6068- let mut query: QueryBuilder<Sqlite> =
6069- QueryBuilder::new("UPDATE lists SET enabled = 0 WHERE address NOT IN");
6070- let update_query = query.push_tuples(addresses, |mut array, value| {
6071- array.push_bind(value.clone());
6072- });
6073- update_query.build().execute(&mut *tx).await?;
6074- for list in lists.iter() {
6075- tracing::info!("Updating mailing list {}", list.address);
6076- sqlx::query_file!(
6077- "../../queries/mail_list_create.sql",
6078- list.name,
6079- list.address,
6080- list.description,
6081- list.enabled,
6082- list.name,
6083- list.description,
6084- list.enabled,
6085- )
6086- .execute(&mut *tx)
6087- .await?;
6088- }
6089- if let Some(participants) = participants.as_ref() {
6090- for participant in participants.iter() {
6091- tracing::info!("Updating participant: {}", participant.address);
6092- sqlx::query_file!(
6093- "../../queries/mail_upsert_participant.sql",
6094- participant.address,
6095- participant.authorized_sender,
6096- participant.address,
6097- )
6098- .fetch_one(&mut *tx)
6099- .await?;
6100- if let Some(subscriptions) = &participant.subscriptions {
6101- for subscription in subscriptions {
6102- // FIXME declarative
6103- tracing::info!("Creating subscription: {}", subscription);
6104- sqlx::query_file!(
6105- "../../queries/mail_upsert_subscription.sql",
6106- participant.address,
6107- subscription
6108- )
6109- .execute(&mut *tx)
6110- .await?;
6111- }
6112- };
6113- }
6114- }
6115- tx.commit().await?;
6116- Ok(())
6117- }
6118-
6119- pub async fn deliver(&self, params: &DeliveryParams<'_>) -> Result<Vec<i64>, Error> {
6120- let mut tx = self.0.pool.begin().await?;
6121- let participant_id = sqlx::query_file!(
6122- "../../queries/mail_upsert_participant.sql",
6123- params.mail_from,
6124- false,
6125- params.mail_from
6126- )
6127- .fetch_one(&mut *tx)
6128- .await?
6129- .id;
6130- let mut message_ids: Vec<i64> = Vec::new();
6131- for mail_to_addr in params.mail_to.iter() {
6132- let ret = sqlx::query_file!(
6133- "../../queries/mail_deliver_message.sql",
6134- params.message_id,
6135- mail_to_addr,
6136- params.in_reply_to,
6137- params.subject,
6138- participant_id,
6139- params.content_body,
6140- params.raw_message,
6141- )
6142- .fetch_one(&mut *tx)
6143- .await?;
6144- message_ids.push(ret.id.unwrap())
6145- }
6146- tx.commit().await?;
6147- // TODO Lookup all subscribers and place new messages in an outbound
6148- // queue.
6149- Ok(message_ids)
6150- }
6151-
6152- pub async fn has_message(&self, message_id: &str) -> Result<bool, Error> {
6153- let rec = sqlx::query_file!("../../queries/mail_has_message.sql", message_id)
6154- .fetch_one(&self.0.pool)
6155- .await?;
6156- Ok(rec.has_message)
6157- }
6158-
6159- pub async fn thread_count(&self, list_id: &ListId) -> Result<i64, Error> {
6160- let rec = sqlx::query_file!("../../queries/mail_thread_count.sql", list_id.1)
6161- .fetch_one(&self.0.pool)
6162- .await?;
6163- Ok(rec.count)
6164- }
6165-
6166- // pub async fn read_message(&self, list_id: &ListId, message_id: &str) -> Result<Message, Error> {
6167- // todo!()
6168- // }
6169-
6170- pub async fn read_thread(
6171- &self,
6172- _list_id: &ListId, // FIXME
6173- message_id: &str,
6174- ) -> Result<Vec<ThreadMessage>, Error> {
6175- sqlx::query_file_as!(
6176- ThreadMessage,
6177- "../../queries/mail_read_thread.sql",
6178- message_id,
6179- )
6180- .fetch_all(&self.0.pool)
6181- .await
6182- }
6183-
6184- pub async fn read_threads(&self, list_id: &ListId) -> Result<Vec<ThreadSummary>, Error> {
6185- let list_name = list_id.0.as_str();
6186- sqlx::query_file_as!(
6187- ThreadSummary,
6188- "../../queries/mail_read_threads.sql",
6189- list_name
6190- )
6191- .fetch_all(&self.0.pool)
6192- .await
6193- }
6194-
6195- pub async fn list_outbox(&self, max_attempts: u8) -> Result<Vec<OutgoingMessage>, Error> {
6196- sqlx::query_file_as!(
6197- OutgoingMessage,
6198- "../../queries/mail_list_outbox.sql",
6199- max_attempts
6200- )
6201- .fetch_all(&self.0.pool)
6202- .await
6203- }
6204-
6205- pub async fn successful(&self, message_id: i64) -> Result<(), Error> {
6206- sqlx::query_file!("../../queries/mail_outbox_successful.sql", message_id)
6207- .execute(&self.0.pool)
6208- .await?;
6209- Ok(())
6210- }
6211-
6212- pub async fn failed(&self, message_id: i64, err: &DeliveryError) -> Result<(), Error> {
6213- let DeliveryError::Generic(message) = err;
6214- let err_kind = err.kind();
6215- sqlx::query_file!(
6216- "../../queries/mail_outbox_failed.sql",
6217- message_id,
6218- err_kind,
6219- message
6220- )
6221- .execute(&self.0.pool)
6222- .await?;
6223- Ok(())
6224- }
6225- }
6226 diff --git a/crates/database/src/schema.rs b/crates/database/src/schema.rs
6227new file mode 100644
6228index 0000000..71b364f
6229--- /dev/null
6230+++ b/crates/database/src/schema.rs
6231 @@ -0,0 +1,77 @@
6232+ // @generated automatically by Diesel CLI.
6233+
6234+ diesel::table! {
6235+ logs (id) {
6236+ id -> Integer,
6237+ step_id -> Integer,
6238+ timestamp -> Integer,
6239+ stream -> Integer,
6240+ line -> Text,
6241+ }
6242+ }
6243+
6244+ diesel::table! {
6245+ manifests (id) {
6246+ id -> Integer,
6247+ collection -> Text,
6248+ name -> Text,
6249+ git_hash -> Text,
6250+ dag_content -> Nullable<Text>,
6251+ state -> Integer,
6252+ created_at -> Integer,
6253+ started_at -> Nullable<Integer>,
6254+ finished_at -> Nullable<Integer>,
6255+ }
6256+ }
6257+
6258+ diesel::table! {
6259+ samples (id) {
6260+ id -> Integer,
6261+ manifest_id -> Integer,
6262+ timestamp -> Integer,
6263+ load_1m -> Nullable<Integer>,
6264+ load_5m -> Nullable<Integer>,
6265+ load_15m -> Nullable<Integer>,
6266+ disk_total_bytes -> Nullable<Integer>,
6267+ disk_free_bytes -> Nullable<Integer>,
6268+ net_sent_bytes -> Nullable<Integer>,
6269+ net_received_bytes -> Nullable<Integer>,
6270+ net_sent_packets -> Nullable<Integer>,
6271+ net_received_packets -> Nullable<Integer>,
6272+ mem_total_bytes -> Nullable<Integer>,
6273+ mem_free_bytes -> Nullable<Integer>,
6274+ mem_available_bytes -> Nullable<Integer>,
6275+ }
6276+ }
6277+
6278+ diesel::table! {
6279+ steps (id) {
6280+ id -> Integer,
6281+ name -> Text,
6282+ workflow_id -> Integer,
6283+ shell -> Text,
6284+ environment_json -> Nullable<Text>,
6285+ input -> Text,
6286+ started_at -> Nullable<Integer>,
6287+ finished_at -> Nullable<Integer>,
6288+ exit_code -> Nullable<Integer>,
6289+ }
6290+ }
6291+
6292+ diesel::table! {
6293+ workflows (id) {
6294+ id -> Integer,
6295+ manifest_id -> Integer,
6296+ name -> Text,
6297+ image -> Text,
6298+ started_at -> Nullable<Integer>,
6299+ finished_at -> Nullable<Integer>,
6300+ }
6301+ }
6302+
6303+ diesel::joinable!(logs -> steps (step_id));
6304+ diesel::joinable!(samples -> manifests (manifest_id));
6305+ diesel::joinable!(steps -> workflows (workflow_id));
6306+ diesel::joinable!(workflows -> manifests (manifest_id));
6307+
6308+ diesel::allow_tables_to_appear_in_same_query!(logs, manifests, samples, steps, workflows,);
6309 diff --git a/crates/database/src/stats.rs b/crates/database/src/stats.rs
6310deleted file mode 100644
6311index a2dc16a..0000000
6312--- a/crates/database/src/stats.rs
6313+++ /dev/null
6314 @@ -1,74 +0,0 @@
6315- use sqlx::Error;
6316- use time::{format_description, OffsetDateTime};
6317-
6318- use crate::Wrapper;
6319-
6320- #[allow(dead_code)]
6321- pub enum Aggregation {
6322- Day,
6323- Week,
6324- Month,
6325- Year,
6326- }
6327-
6328- pub struct Database<'a>(pub &'a Wrapper);
6329-
6330- impl Database<'_> {
6331- pub async fn contribution_buckets_for_repo(
6332- &self,
6333- repo_path: &str,
6334- git_hash: &str,
6335- period: Aggregation,
6336- offset: time::Duration,
6337- ) -> Result<Vec<(i64, i64, i64)>, Error> {
6338- let start = OffsetDateTime::now_utc();
6339- let end = OffsetDateTime::now_utc().saturating_sub(offset);
6340- let start_ts = start.unix_timestamp();
6341- let end_ts = end.unix_timestamp();
6342- let seconds = match period {
6343- Aggregation::Day => 86400_i64,
6344- Aggregation::Week => 604800_i64,
6345- Aggregation::Month => 2592000_i64,
6346- Aggregation::Year => 31536000_i64,
6347- };
6348- let n_buckets = (start_ts - end_ts) / seconds;
6349- let mut buckets: Vec<(i64, i64, i64)> = vec![(0, 0, 0); n_buckets as usize];
6350- let strftime = match period {
6351- Aggregation::Day => "%d%m",
6352- Aggregation::Week => "%w",
6353- Aggregation::Month => "%m",
6354- Aggregation::Year => "%y",
6355- };
6356- let rows = sqlx::query_file!(
6357- "../../queries/contributions_bucket.sql",
6358- repo_path,
6359- git_hash,
6360- start,
6361- end,
6362- strftime
6363- )
6364- .fetch_all(&self.0.pool)
6365- .await?;
6366- for row in rows.iter() {
6367- let parsed =
6368- OffsetDateTime::parse(row.time.as_str(), &format_description::well_known::Rfc3339)
6369- .unwrap();
6370- let diff = ((start - parsed).as_seconds_f64() / seconds as f64).floor();
6371- buckets[(diff - 1.0).abs() as usize] =
6372- (row.count, row.added, (row.removed - row.removed * 2));
6373- }
6374- // buckets.reverse();
6375- Ok(buckets)
6376- }
6377-
6378- pub async fn count_commits(&self, repo_path: &str, git_hash: &str) -> Result<i32, Error> {
6379- let result = sqlx::query_file!(
6380- "../../queries/commits_count.sql",
6381- repo_path,
6382- git_hash,
6383- repo_path
6384- );
6385- let result = result.fetch_one(&self.0.pool).await?;
6386- Ok(result.count.try_into().unwrap())
6387- }
6388- }
6389 diff --git a/crates/timeutil/src/lib.rs b/crates/timeutil/src/lib.rs
6390index 164afc4..daf49d1 100644
6391--- a/crates/timeutil/src/lib.rs
6392+++ b/crates/timeutil/src/lib.rs
6393 @@ -1,4 +1,4 @@
6394- use std::time::SystemTime;
6395+ use std::time::{SystemTime, UNIX_EPOCH};
6396
6397 // apparently these exist in nightly rust?
6398 const SECOND: u64 = 1;
6399 @@ -63,3 +63,10 @@ pub fn timestamp() -> String {
6400 .format(&time::format_description::well_known::Iso8601::DEFAULT)
6401 .unwrap()
6402 }
6403+
6404+ pub fn epoch_secs() -> u64 {
6405+ std::time::SystemTime::now()
6406+ .duration_since(UNIX_EPOCH)
6407+ .unwrap()
6408+ .as_secs()
6409+ }
6410 diff --git a/diesel.toml b/diesel.toml
6411new file mode 100644
6412index 0000000..58e0567
6413--- /dev/null
6414+++ b/diesel.toml
6415 @@ -0,0 +1,9 @@
6416+ # For documentation on how to configure this file,
6417+ # see https://diesel.rs/guides/configuring-diesel-cli
6418+
6419+ [print_schema]
6420+ file = "crates/database/src/schema.rs"
6421+ custom_type_derives = ["diesel::query_builder::QueryId", "Clone"]
6422+
6423+ [migrations_directory]
6424+ dir = "crates/database/migrations"
6425 diff --git a/migrations/20230602111216_init.sql b/migrations/20230602111216_init.sql
6426deleted file mode 100644
6427index d3754fe..0000000
6428--- a/migrations/20230602111216_init.sql
6429+++ /dev/null
6430 @@ -1,51 +0,0 @@
6431- CREATE TABLE authors (
6432- id INTEGER PRIMARY KEY NOT NULL,
6433- username TEXT NOT NULL,
6434- email TEXT NOT NULL,
6435- UNIQUE (username, email)
6436- ) STRICT;
6437-
6438- CREATE TABLE languages (
6439- id INTEGER PRIMARY KEY NOT NULL,
6440- git_hash TEXT NOT NULL,
6441- repo_path TEXT NOT NULL,
6442- language TEXT NOT NULL,
6443- loc INTEGER NOT NULL,
6444- UNIQUE (git_hash, repo_path, language)
6445- ) STRICT;
6446-
6447- CREATE TABLE jobs (
6448- id INTEGER PRIMARY KEY,
6449- created_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP,
6450- kind TEXT NOT NULL,
6451- repo_path TEXT,
6452- runtime INTEGER,
6453- success INTEGER
6454- ) STRICT;
6455-
6456- CREATE TABLE job_tracking (
6457- id INTEGER PRIMARY KEY,
6458- job_id INTEGER REFERENCES jobs(id) ON DELETE CASCADE NOT NULL,
6459- repo_path TEXT NOT NULL,
6460- git_hash TEXT NOT NULL,
6461- kind TEXT NOT NULL,
6462- UNIQUE (repo_path, git_hash, kind)
6463- ) STRICT;
6464-
6465- CREATE TABLE contributions (
6466- id INTEGER PRIMARY KEY,
6467- git_hash TEXT NOT NULL,
6468- repo_path TEXT NOT NULL,
6469- time TEXT NOT NULL,
6470- author_id INTEGER REFERENCES authors(id),
6471- lines_added INTEGER NOT NULL,
6472- lines_removed INTEGER NOT NULL
6473- ) STRICT;
6474-
6475- CREATE TABLE contribution_tally (
6476- id INTEGER PRIMARY KEY,
6477- git_hash TEXT NOT NULL,
6478- repo_path TEXT NOT NULL,
6479- author_id INTEGER REFERENCES authors(id),
6480- total INTEGER NOT NULL
6481- ) STRICT;
6482 diff --git a/migrations/20231204194038_init.sql b/migrations/20231204194038_init.sql
6483deleted file mode 100644
6484index 79b5e5e..0000000
6485--- a/migrations/20231204194038_init.sql
6486+++ /dev/null
6487 @@ -1,71 +0,0 @@
6488- CREATE TABLE manifests (
6489- id INTEGER PRIMARY KEY NOT NULL,
6490- collection TEXT NOT NULL,
6491- name TEXT NOT NULL,
6492- git_hash TEXT NOT NULL,
6493- created_at INTEGER NOT NULL DEFAULT (UNIXEPOCH()),
6494- started_at INTEGER CHECK (started_at > 0),
6495- finished_at INTEGER CHECK (finished_at > 0)
6496- ) STRICT ;
6497-
6498- CREATE TABLE workflows (
6499- id INTEGER PRIMARY KEY NOT NULL,
6500- manifest_id INTEGER REFERENCES manifests(id) ON DELETE CASCADE NOT NULL,
6501- name TEXT NOT NULL,
6502- image TEXT NOT NULL,
6503- started_at INTEGER CHECK (started_at > 0),
6504- finished_at INTEGER CHECK (finished_at > 0)
6505- ) STRICT ;
6506-
6507- CREATE TABLE steps (
6508- id INTEGER PRIMARY KEY NOT NULL,
6509- name TEXT NOT NULL,
6510- workflow_id INTEGER REFERENCES workflows(id) ON DELETE CASCADE NOT NULL,
6511- shell TEXT NOT NULL DEFAULT "/bin/sh",
6512- environment_json TEXT,
6513- input TEXT NOT NULL,
6514- started_at INTEGER CHECK (started_at > 0),
6515- finished_at INTEGER CHECK (finished_at > 0),
6516- stdout TEXT,
6517- stderr TEXT,
6518- exit_code INTEGER CHECK (exit_code <= 255)
6519- ) STRICT ;
6520-
6521- CREATE TABLE logs (
6522- id INTEGER PRIMARY KEY NOT NULL,
6523- step_id INTEGER REFERENCES steps(id) ON DELETE CASCADE NOT NULL,
6524- runtime INTEGER NOT NULL,
6525- stream TEXT NOT NULL CHECK (stream IN ('stdout', 'stderr', 'stdin')),
6526- line TEXT
6527- ) STRICT ;
6528-
6529- CREATE TABLE steps_env_vars (
6530- id INTEGER PRIMARY KEY NOT NULL,
6531- step_id INTEGER REFERENCES steps(id) ON DELETE CASCADE NOT NULL,
6532- name TEXT NOT NULL,
6533- value TEXT
6534- ) STRICT ;
6535-
6536- CREATE TABLE dags (
6537- id INTEGER PRIMARY KEY NOT NULL,
6538- manifest_id INTEGER REFERENCES manifests(id) ON DELETE CASCADE NOT NULL,
6539- dag_content TEXT NOT NULL
6540- ) STRICT ;
6541-
6542- CREATE TABLE samples (
6543- id INTEGER PRIMARY KEY NOT NULL,
6544- manifest_id INTEGER REFERENCES manifests(id) ON DELETE CASCADE NOT NULL,
6545- timestamp INTEGER NOT NULL DEFAULT (UNIXEPOCH()),
6546- load_1m INTEGER,
6547- load_5m INTEGER,
6548- load_15m INTEGER,
6549- disk_total_bytes INTEGER,
6550- disk_free_bytes INTEGER,
6551- net_sent_bytes INTEGER,
6552- net_received_bytes INTEGER,
6553- net_sent_packets INTEGER,
6554- net_received_packets INTEGER,
6555- mem_total_bytes INTEGER,
6556- mem_free_bytes INTEGER,
6557- mem_available_bytes INTEGER
6558- ) STRICT ;
6559 diff --git a/migrations/20241028092919_mail.sql b/migrations/20241028092919_mail.sql
6560deleted file mode 100644
6561index 621471e..0000000
6562--- a/migrations/20241028092919_mail.sql
6563+++ /dev/null
6564 @@ -1,57 +0,0 @@
6565- CREATE TABLE lists (
6566- id INTEGER PRIMARY KEY,
6567- name TEXT NOT NULL,
6568- address TEXT NOT NULL UNIQUE,
6569- description TEXT,
6570- enabled INTEGER NOT NULL DEFAULT 0 CHECK (enabled IN (0, 1))
6571- ) STRICT ;
6572-
6573- CREATE TABLE participants (
6574- id INTEGER PRIMARY KEY,
6575- address TEXT NOT NULL UNIQUE,
6576- authorized_sender INTEGER NOT NULL DEFAULT 0 CHECK (authorized_sender IN (0, 1))
6577- ) STRICT ;
6578-
6579- CREATE TABLE subscriptions (
6580- id INTEGER PRIMARY KEY,
6581- participant_id INTEGER NOT NULL REFERENCES participants(id),
6582- list_id INTEGER NOT NULL REFERENCES lists(id),
6583- UNIQUE(participant_id, list_id) ON CONFLICT REPLACE
6584- ) STRICT ;
6585-
6586- CREATE TABLE outbox (
6587- id INTEGER PRIMARY KEY,
6588- message_id INTEGER NOT NULL REFERENCES messages(id),
6589- recipient INTEGER NOT NULL REFERENCES participants(id),
6590- UNIQUE(message_id, recipient) ON CONFLICT REPLACE
6591- ) STRICT ;
6592-
6593- CREATE TABLE messages (
6594- id INTEGER PRIMARY KEY,
6595- message_id TEXT NOT NULL UNIQUE,
6596- list_id INTEGER NOT NULL REFERENCES lists(id),
6597- in_reply_to TEXT,
6598- subject TEXT,
6599- mail_from INTEGER NOT NULL REFERENCES participants(id),
6600- raw_message BLOB NOT NULL,
6601- content_body TEXT,
6602- reply_count INTEGER NOT NULL DEFAULT 0
6603- ) STRICT ;
6604-
6605- CREATE TABLE delivery_failures (
6606- id INTEGER PRIMARY KEY,
6607- outbox_id INTEGER NOT NULL REFERENCES outbox(id),
6608- error_kind TEXT NOT NULL,
6609- message TEXT
6610- );
6611-
6612- CREATE TRIGGER handle_delivery
6613- AFTER INSERT ON messages
6614- FOR EACH ROW
6615- BEGIN
6616- INSERT INTO outbox (message_id, recipient)
6617- SELECT NEW.id, participants.id FROM participants
6618- LEFT JOIN subscriptions ON participants.id = subscriptions.participant_id
6619- WHERE
6620- subscriptions.list_id = NEW.id AND participants.authorized_sender = 1;
6621- END;