mirror of
https://github.com/storytold/artcraft.git
synced 2026-11-09 23:46:45 -05:00
Compare commits
3157
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ffcc144bb6 | ||
|
|
839f7885fb | ||
|
|
cee3ffb217 | ||
|
|
90198252b8 | ||
|
|
165130321f | ||
|
|
061c2c7b07 | ||
|
|
413960b6cf | ||
|
|
cc4b7deaa9 | ||
|
|
8229a3cfb3 | ||
|
|
b461c7e8e7 | ||
|
|
c58f23c117 | ||
|
|
048cc26b6b | ||
|
|
9b5508d8c0 | ||
|
|
a81428913d | ||
|
|
756c8e09ae | ||
|
|
5b97360724 | ||
|
|
79ad78bdde | ||
|
|
311299105b | ||
|
|
9dad3a153c | ||
|
|
93157b50c8 | ||
|
|
47fbc1704d | ||
|
|
efba106d3c | ||
|
|
e340eee250 | ||
|
|
73be1bbf05 | ||
|
|
81306d1a56 | ||
|
|
8e341f7a83 | ||
|
|
1978e5e653 | ||
|
|
8eaaad9b55 | ||
|
|
082c284add | ||
|
|
a83e5ff224 | ||
|
|
abaaaf2fdb | ||
|
|
3409689566 | ||
|
|
84c0e42964 | ||
|
|
b848c2b549 | ||
|
|
85abbd8587 | ||
|
|
b844191db7 | ||
|
|
0267a4799d | ||
|
|
c50060b161 | ||
|
|
a508a843bd | ||
|
|
d0bad0c8fb | ||
|
|
d2c99a7bad | ||
|
|
681d923ae0 | ||
|
|
9e563a0640 | ||
|
|
db30096cb9 | ||
|
|
76f9f5a850 | ||
|
|
3a0eafee11 | ||
|
|
9fbecd5568 | ||
|
|
b3dedfc5a8 | ||
|
|
8ded313e7b | ||
|
|
e305928b45 | ||
|
|
db48ee0fd8 | ||
|
|
5beedcb5e3 | ||
|
|
e43feeb700 | ||
|
|
38dc835ff4 | ||
|
|
62b19b2b0e | ||
|
|
b7b92f4419 | ||
|
|
7ee7ff3875 | ||
|
|
29237d3ead | ||
|
|
5afbfc0ec4 | ||
|
|
6e5a2d1789 | ||
|
|
13281ea3b1 | ||
|
|
98f80a68de | ||
|
|
075fcfb81e | ||
|
|
a8c45cfac2 | ||
|
|
f0a4f0a4f7 | ||
|
|
3b78853d7c | ||
|
|
98b6bb34a7 | ||
|
|
4d759719f6 | ||
|
|
ac28ff8a39 | ||
|
|
43a33a00ee | ||
|
|
8bbc490b61 | ||
|
|
168a476b72 | ||
|
|
2cebfbb3fc | ||
|
|
d57e070c8c | ||
|
|
bde6b4d562 | ||
|
|
9cde097d38 | ||
|
|
1d7d1ba194 | ||
|
|
632cf5a710 | ||
|
|
c2e287f6f4 | ||
|
|
844799e30e | ||
|
|
c1bd8ed611 | ||
|
|
1466bca4d7 | ||
|
|
12fdf24028 | ||
|
|
cefb770340 | ||
|
|
e971ea98dc | ||
|
|
9ab8b366e9 | ||
|
|
96563fa345 | ||
|
|
67ab94664c | ||
|
|
99f1226115 | ||
|
|
9c45a17b8d | ||
|
|
2cf5a6f942 | ||
|
|
071928d7bf | ||
|
|
b8442b54ee | ||
|
|
530d891ce9 | ||
|
|
972f119468 | ||
|
|
9004432620 | ||
|
|
81a3bc270b | ||
|
|
84fe5a5762 | ||
|
|
a02934ca4e | ||
|
|
4ddcd43e33 | ||
|
|
37be0174af | ||
|
|
f2e314d168 | ||
|
|
3bd216f00d | ||
|
|
dc5885114d | ||
|
|
47873497b8 | ||
|
|
554766bd2a | ||
|
|
86382bf219 | ||
|
|
59b1e28a14 | ||
|
|
3f88a741c9 | ||
|
|
2efcba5da6 | ||
|
|
b4718a2f13 | ||
|
|
efbb0000f3 | ||
|
|
0280e6b706 | ||
|
|
79d568ee14 | ||
|
|
85630390a0 | ||
|
|
215b727bc1 | ||
|
|
e29c0b4c68 | ||
|
|
c81e6af507 | ||
|
|
98d30917e5 | ||
|
|
e62e409857 | ||
|
|
417137a9a8 | ||
|
|
0da1994f0c | ||
|
|
36d016c3c7 | ||
|
|
b22e978e54 | ||
|
|
c387f47c97 | ||
|
|
1a09676327 | ||
|
|
e516f85e5f | ||
|
|
e16b86d36e | ||
|
|
003a86cd31 | ||
|
|
ebf48e20be | ||
|
|
df2444ddc0 | ||
|
|
6b6a2ceeba | ||
|
|
80da0d9649 | ||
|
|
b8fd0486c7 | ||
|
|
12ffb2ded1 | ||
|
|
f810256b9c | ||
|
|
461030b681 | ||
|
|
a33ff3f060 | ||
|
|
98736324dc | ||
|
|
89a6f36f1a | ||
|
|
ac5bcbdd16 | ||
|
|
7566c0d71c | ||
|
|
0ca007b6e7 | ||
|
|
cbe07e2b3e | ||
|
|
495c01c38c | ||
|
|
ff22319c77 | ||
|
|
16f2d5557b | ||
|
|
e5cdb84c52 | ||
|
|
8079be9f7e | ||
|
|
2821240cc0 | ||
|
|
ae0522419a | ||
|
|
46bb4274ca | ||
|
|
f2b6dc4349 | ||
|
|
a395c04265 | ||
|
|
d3a4613cc9 | ||
|
|
bdca25cf4f | ||
|
|
06a053b6d9 | ||
|
|
4104ef0129 | ||
|
|
0f076cbacd | ||
|
|
817b2c01df | ||
|
|
c06c47f02e | ||
|
|
23b0dafd6b | ||
|
|
91e83c8b90 | ||
|
|
4a32ced9cd | ||
|
|
75014a673a | ||
|
|
52afa5462d | ||
|
|
a826d54311 | ||
|
|
4f38e4b20a | ||
|
|
6880efd2f0 | ||
|
|
e372a4b5ec | ||
|
|
f978f1b055 | ||
|
|
ee69e72661 | ||
|
|
6295fd9569 | ||
|
|
53d9d55036 | ||
|
|
7e236f1e81 | ||
|
|
6b7370ee64 | ||
|
|
406d2eb7af | ||
|
|
1cfb3663e9 | ||
|
|
aa24f781fb | ||
|
|
3a2dad2027 | ||
|
|
814c04cdac | ||
|
|
c45a6da8a5 | ||
|
|
a5b2028588 | ||
|
|
4a743bace1 | ||
|
|
5ef5d6832b | ||
|
|
0ebb1e398b | ||
|
|
4be572c36b | ||
|
|
97bea31b1d | ||
|
|
f3dd785331 | ||
|
|
0d99c2fc08 | ||
|
|
edba26a2db | ||
|
|
89a9448c9a | ||
|
|
d557c78123 | ||
|
|
9ac7f33008 | ||
|
|
716e7923ff | ||
|
|
ad389491a2 | ||
|
|
85b5e427b9 | ||
|
|
74d7b543f2 | ||
|
|
2078bac70f | ||
|
|
2e0110b0d9 | ||
|
|
80a32eedf3 | ||
|
|
539c5ae796 | ||
|
|
6440d519f9 | ||
|
|
690f4f1064 | ||
|
|
a1e4ca35cd | ||
|
|
d93c0a5a8d | ||
|
|
9cc5ce9a04 | ||
|
|
202b6c4a97 | ||
|
|
71abf1caa2 | ||
|
|
de01d0fde7 | ||
|
|
bb5ef2a3fe | ||
|
|
a8ab9c1265 | ||
|
|
2412e15868 | ||
|
|
4b45924455 | ||
|
|
72ce767337 | ||
|
|
76c562bf2e | ||
|
|
6ac8759aba | ||
|
|
3503a4527c | ||
|
|
03160b73d4 | ||
|
|
601b786b06 | ||
|
|
05503512d8 | ||
|
|
7db6f074b6 | ||
|
|
e7ad3a34de | ||
|
|
5ccd512a2a | ||
|
|
c40333e2a2 | ||
|
|
edcfc9ff9b | ||
|
|
862be58f1a | ||
|
|
8b08f2ba1d | ||
|
|
fe3f8dec6c | ||
|
|
8ae5d34074 | ||
|
|
89f8837706 | ||
|
|
03ed0b4c7b | ||
|
|
f1d8161481 | ||
|
|
2d20430f55 | ||
|
|
e2b98491cb | ||
|
|
910caa1000 | ||
|
|
0e39999097 | ||
|
|
77779bc231 | ||
|
|
898c22b9cc | ||
|
|
96b80b35a5 | ||
|
|
d182f800f7 | ||
|
|
60af92ea39 | ||
|
|
da8c164b40 | ||
|
|
d304f40984 | ||
|
|
8f0d347c38 | ||
|
|
73db1b1a49 | ||
|
|
97b7120ff8 | ||
|
|
961c5cf43d | ||
|
|
4c2c54bd74 | ||
|
|
c158097b42 | ||
|
|
1c9366da4b | ||
|
|
f648f7b714 | ||
|
|
c3db6ce24a | ||
|
|
1910b6497c | ||
|
|
ac90b89230 | ||
|
|
70e6ba5aec | ||
|
|
5af6a68de7 | ||
|
|
19532388a3 | ||
|
|
27adc9f73e | ||
|
|
6d082f2b9e | ||
|
|
2c86a4189c | ||
|
|
bf5b2b385e | ||
|
|
b7c73930b4 | ||
|
|
f9d61a1482 | ||
|
|
4799d3ceac | ||
|
|
696fd2bd8d | ||
|
|
1cfb3bb715 | ||
|
|
ccb36f7f6e | ||
|
|
83a16f83e3 | ||
|
|
8f9554be9f | ||
|
|
d987397586 | ||
|
|
41e03feb85 | ||
|
|
5c8cab0d90 | ||
|
|
8f40db9a3e | ||
|
|
b0a7c953be | ||
|
|
a08bc3fa64 | ||
|
|
5d111f0a68 | ||
|
|
51a5571d84 | ||
|
|
95f2ec3e40 | ||
|
|
b511420c03 | ||
|
|
aeab9da077 | ||
|
|
46452415fd | ||
|
|
cecbc8e730 | ||
|
|
1ba73c817c | ||
|
|
8395739500 | ||
|
|
c537f09c8f | ||
|
|
1e7de6bf36 | ||
|
|
ab6e3ceb5a | ||
|
|
896938b762 | ||
|
|
486960905d | ||
|
|
2a54d3aac9 | ||
|
|
10ac9fee82 | ||
|
|
cab4840a61 | ||
|
|
68a3c123eb | ||
|
|
582ce9aa93 | ||
|
|
eaeb864c1c | ||
|
|
77d4bc3b75 | ||
|
|
b6be95144c | ||
|
|
5ca4dd3600 | ||
|
|
294c3107b5 | ||
|
|
2de7ce146f | ||
|
|
f7ddd24325 | ||
|
|
1b1ed15f21 | ||
|
|
dc93602306 | ||
|
|
2898f40ad1 | ||
|
|
ced51c7f58 | ||
|
|
b208847ec8 | ||
|
|
744656cfd9 | ||
|
|
a735d3e972 | ||
|
|
90aa091398 | ||
|
|
f8e7ae3ced | ||
|
|
a1b8a22225 | ||
|
|
0a0105410d | ||
|
|
69a79b76ea | ||
|
|
809387958c | ||
|
|
44f7824910 | ||
|
|
1e05dcb7e2 | ||
|
|
72bdea7d10 | ||
|
|
c3981e8554 | ||
|
|
6b937b7cd0 | ||
|
|
5be10ae87a | ||
|
|
12f7e30047 | ||
|
|
a5e0c2ab01 | ||
|
|
0764711439 | ||
|
|
969be4726c | ||
|
|
8244bac6e1 | ||
|
|
1d173a74d5 | ||
|
|
3786a64eb1 | ||
|
|
cc6cf0d6c0 | ||
|
|
144df6ffc0 | ||
|
|
85e0ce4b4a | ||
|
|
107d11f0f9 | ||
|
|
7efb65ae08 | ||
|
|
ba3ff9768a | ||
|
|
b8e37d2e77 | ||
|
|
5a1e61b692 | ||
|
|
19b57b9c1d | ||
|
|
6c33c31289 | ||
|
|
b1bbfe21ec | ||
|
|
5878224f7e | ||
|
|
5497b7c0aa | ||
|
|
bc58b50408 | ||
|
|
ce17a3e1bf | ||
|
|
b8fe3a0e7d | ||
|
|
d41cec6c90 | ||
|
|
6a86f43a92 | ||
|
|
65a7a46914 | ||
|
|
31eafed60d | ||
|
|
6abbbaae01 | ||
|
|
82efbf4d1f | ||
|
|
b1cefa66d5 | ||
|
|
7f5f578abd | ||
|
|
0c574c31d6 | ||
|
|
393bc084d4 | ||
|
|
035faa0561 | ||
|
|
84e2bcd2c3 | ||
|
|
bc69ecbda5 | ||
|
|
5f35323a5f | ||
|
|
30c9f779bd | ||
|
|
4f7d03a505 | ||
|
|
5a031b1b80 | ||
|
|
d1b6facd95 | ||
|
|
79984058c0 | ||
|
|
adc52cd01e | ||
|
|
aad257a1b2 | ||
|
|
b8d5a1fad1 | ||
|
|
f757b94eaf | ||
|
|
8ff2408cbf | ||
|
|
01fa2f8d67 | ||
|
|
25b7024655 | ||
|
|
f7b8423623 | ||
|
|
0f99360c20 | ||
|
|
fe606c1610 | ||
|
|
201481f969 | ||
|
|
665cd83739 | ||
|
|
eefe48f8ca | ||
|
|
a1c5ef6eb2 | ||
|
|
2f4fe2dabc | ||
|
|
d63a505b8b | ||
|
|
fb1bf9940e | ||
|
|
d4de388bb6 | ||
|
|
a0a5c68086 | ||
|
|
ddb106a9af | ||
|
|
7c229ba59b | ||
|
|
d1978298f0 | ||
|
|
1dcf06b571 | ||
|
|
8af2368f32 | ||
|
|
79fba33aae | ||
|
|
bff0f9181d | ||
|
|
bd8f2570e2 | ||
|
|
74c289b338 | ||
|
|
63bb0e7253 | ||
|
|
4140d07924 | ||
|
|
aa03abd3b5 | ||
|
|
fb8142fb49 | ||
|
|
362f41cbf3 | ||
|
|
a79d3bcf9a | ||
|
|
41e7780fa0 | ||
|
|
329e7defc5 | ||
|
|
6ce0437aa2 | ||
|
|
34bb2c5266 | ||
|
|
2b50d2bce5 | ||
|
|
c9f55fbb7c | ||
|
|
2da4971ec7 | ||
|
|
2cd6a98259 | ||
|
|
7ba765b89c | ||
|
|
02d7495e54 | ||
|
|
088535cb5a | ||
|
|
4ef9ad35a7 | ||
|
|
f80f2c8962 | ||
|
|
98fb5758e5 | ||
|
|
06f219a8af | ||
|
|
439527a504 | ||
|
|
61e1cc62b6 | ||
|
|
633dfb5682 | ||
|
|
8df0273ca8 | ||
|
|
41630e1fc5 | ||
|
|
40bc0cf883 | ||
|
|
7fdb5cfe9c | ||
|
|
a0da010dab | ||
|
|
74ff632f4d | ||
|
|
e6d1f0db11 | ||
|
|
d190173f4d | ||
|
|
22e8a14170 | ||
|
|
b6fce130b5 | ||
|
|
1bb0dda330 | ||
|
|
bcbb36a9ed | ||
|
|
dbfd80531d | ||
|
|
4152b346c1 | ||
|
|
3b37cf9e89 | ||
|
|
c49bbd299a | ||
|
|
993fe647b4 | ||
|
|
d46778569c | ||
|
|
2ea0e1450c | ||
|
|
b31bafd639 | ||
|
|
9246ab83cf | ||
|
|
93b06bd8b5 | ||
|
|
271bce9815 | ||
|
|
f0bebc6135 | ||
|
|
98ce37d9a4 | ||
|
|
b0bc019db2 | ||
|
|
f3c34d9a7b | ||
|
|
98c8bbda57 | ||
|
|
18b20d6c4d | ||
|
|
11fd4881ea | ||
|
|
7ab39d0b5c | ||
|
|
77e16d4bd8 | ||
|
|
58c7695a2a | ||
|
|
140c93fc76 | ||
|
|
2b58e3c32d | ||
|
|
142adf10ee | ||
|
|
d1698ead1d | ||
|
|
2909e2dbb4 | ||
|
|
9015efdf9e | ||
|
|
2a5634503f | ||
|
|
623b82dd0e | ||
|
|
9fbafd7fcd | ||
|
|
cc573fe242 | ||
|
|
c4225ce571 | ||
|
|
2f6747b98a | ||
|
|
bc5090af54 | ||
|
|
8062833e8d | ||
|
|
e5a852acd6 | ||
|
|
84ccda1960 | ||
|
|
893a861d22 | ||
|
|
13b014e7fc | ||
|
|
c8598bc9e3 | ||
|
|
ecf84c4b15 | ||
|
|
c4ea221c50 | ||
|
|
8de1722466 | ||
|
|
a716b8963f | ||
|
|
7f70de0148 | ||
|
|
bc033c2202 | ||
|
|
756c357462 | ||
|
|
4b8c454c84 | ||
|
|
d2b3386fea | ||
|
|
f458f29109 | ||
|
|
aa3faca551 | ||
|
|
2512810e81 | ||
|
|
db629a6a0d | ||
|
|
30202e6e5a | ||
|
|
9a263a01a1 | ||
|
|
85ae704ab5 | ||
|
|
b128cdb3f5 | ||
|
|
8755e7a533 | ||
|
|
9f873c33e7 | ||
|
|
81302d0e1d | ||
|
|
9e589c2b72 | ||
|
|
7a33c0721a | ||
|
|
e1793d71b5 | ||
|
|
5b20710f96 | ||
|
|
77e1319b81 | ||
|
|
8061880a83 | ||
|
|
651ed42b64 | ||
|
|
e870d6f4de | ||
|
|
16a2e765ca | ||
|
|
ee7626602a | ||
|
|
533cd5e051 | ||
|
|
7f51ccd01b | ||
|
|
f2bfcfeb75 | ||
|
|
603a678378 | ||
|
|
1ba3215881 | ||
|
|
c4c5b89118 | ||
|
|
9aaee506ed | ||
|
|
ff12990b01 | ||
|
|
45b7af9bd2 | ||
|
|
a34aedc375 | ||
|
|
cb1e092ccd | ||
|
|
07d13a4ab5 | ||
|
|
43c890b27c | ||
|
|
a1510d45b2 | ||
|
|
32c7354798 | ||
|
|
31bb03d9d3 | ||
|
|
66bb76e60f | ||
|
|
68f2e46540 | ||
|
|
d08d3cd543 | ||
|
|
e7fc93de86 | ||
|
|
deb3671be6 | ||
|
|
8591f74ff2 | ||
|
|
26587e51e3 | ||
|
|
2fad965446 | ||
|
|
c2f4c2c057 | ||
|
|
51350e6e5c | ||
|
|
5f598dc516 | ||
|
|
7bcde1e490 | ||
|
|
13d00f6edf | ||
|
|
91695cde00 | ||
|
|
91ac868369 | ||
|
|
60af0ea36d | ||
|
|
46280c7877 | ||
|
|
08c2736ea4 | ||
|
|
02bf8808d0 | ||
|
|
1de10d1b9f | ||
|
|
b649bb17c5 | ||
|
|
587ea70d33 | ||
|
|
ffd7793a8d | ||
|
|
6e13777e84 | ||
|
|
b7fd9aa8d1 | ||
|
|
26e91bd990 | ||
|
|
61d0cbd385 | ||
|
|
28d7a26d97 | ||
|
|
4196a6499b | ||
|
|
2cf07c1346 | ||
|
|
f56ea64dce | ||
|
|
5cfb3f50d7 | ||
|
|
5e0851eb09 | ||
|
|
0d3ffd3f3b | ||
|
|
1b60427ab0 | ||
|
|
75ede876d5 | ||
|
|
086980318c | ||
|
|
c4ca48f399 | ||
|
|
ad88800b4e | ||
|
|
64e3864140 | ||
|
|
81757870ab | ||
|
|
2769a0b3f9 | ||
|
|
89e721e3bc | ||
|
|
353aa4211b | ||
|
|
422e36b405 | ||
|
|
110c7f828f | ||
|
|
b9e2c87025 | ||
|
|
b1d5244620 | ||
|
|
7bcb576436 | ||
|
|
2e561acbb4 | ||
|
|
c1860a0584 | ||
|
|
70140c0e7d | ||
|
|
cc56deb27c | ||
|
|
a15324adee | ||
|
|
fa108a5303 | ||
|
|
7193ddf18b | ||
|
|
21e74c5367 | ||
|
|
11304d77cb | ||
|
|
ba52695f35 | ||
|
|
d28fb07d6d | ||
|
|
740b419989 | ||
|
|
8f2640c5eb | ||
|
|
6e7c708374 | ||
|
|
7b0066bea5 | ||
|
|
3d3dec1e23 | ||
|
|
1ac61b7748 | ||
|
|
24c9cc2d08 | ||
|
|
2392062276 | ||
|
|
94c74eb9e0 | ||
|
|
7f5ccac55c | ||
|
|
62c4f2cbe0 | ||
|
|
507f97bef5 | ||
|
|
a628e986e5 | ||
|
|
2fdd3b38ea | ||
|
|
a3234b3cb4 | ||
|
|
dbf3d541e2 | ||
|
|
f690c73ebf | ||
|
|
567f3143d4 | ||
|
|
2416056f6f | ||
|
|
e390a7d1ac | ||
|
|
00ab91b05c | ||
|
|
b403ed7c9e | ||
|
|
049b0cf375 | ||
|
|
0e6657e9b7 | ||
|
|
acdea58a81 | ||
|
|
9711a60494 | ||
|
|
8edbb3efda | ||
|
|
87b319b99a | ||
|
|
ed6f19c2ab | ||
|
|
31fc96c356 | ||
|
|
4b6eab5a1d | ||
|
|
6af1234095 | ||
|
|
8132a0bcb0 | ||
|
|
c679aa3430 | ||
|
|
33485670dd | ||
|
|
9008e7c044 | ||
|
|
2d2a0b68ff | ||
|
|
a211d45f26 | ||
|
|
d20d8a4c1c | ||
|
|
9aec48d237 | ||
|
|
e4835258cb | ||
|
|
f34fd6fc4c | ||
|
|
413d1b7c1c | ||
|
|
fb99af8a23 | ||
|
|
23b0dab44f | ||
|
|
8ee5eca076 | ||
|
|
9a1df0cd8c | ||
|
|
5377cca3b1 | ||
|
|
badcb4b058 | ||
|
|
28a4f5ca8d | ||
|
|
3efe1afa48 | ||
|
|
d4a7c06f72 | ||
|
|
6b4d0b3ad1 | ||
|
|
23f6e74fd8 | ||
|
|
778d7eba7e | ||
|
|
e5db702289 | ||
|
|
4eb400867a | ||
|
|
4584e9dbcf | ||
|
|
2ca477fd8c | ||
|
|
13487a9046 | ||
|
|
aff94446ac | ||
|
|
15c9c1256b | ||
|
|
009d0eab87 | ||
|
|
0d9a882f3f | ||
|
|
4c878e332e | ||
|
|
d22242ab69 | ||
|
|
5ee51ed8e2 | ||
|
|
560448f1ad | ||
|
|
1b40495ef3 | ||
|
|
e8d376a3eb | ||
|
|
8e6c02d1b1 | ||
|
|
4e4dbf0688 | ||
|
|
1ca54927d7 | ||
|
|
ddd29ada80 | ||
|
|
5ef27bb86f | ||
|
|
c03bdba0a6 | ||
|
|
14432e9808 | ||
|
|
1204ac5399 | ||
|
|
468d2d1d75 | ||
|
|
aac2af635b | ||
|
|
90c96b5f50 | ||
|
|
207e63a5aa | ||
|
|
399ed6cb2e | ||
|
|
d51d6720e6 | ||
|
|
8f807a7719 | ||
|
|
c8ba2d1863 | ||
|
|
2f3cbd4523 | ||
|
|
cf7cd9945d | ||
|
|
6a2a99741e | ||
|
|
dc115a7aa1 | ||
|
|
6ea1bf3226 | ||
|
|
334fc3022e | ||
|
|
3fb26771de | ||
|
|
ba448ccc50 | ||
|
|
fd3081e440 | ||
|
|
66981c4d2c | ||
|
|
c6a1fc18d6 | ||
|
|
afb77393c0 | ||
|
|
a2e71d8944 | ||
|
|
ca63e00905 | ||
|
|
faf689dc09 | ||
|
|
aec31779b8 | ||
|
|
452a3473b3 | ||
|
|
00322c6ed4 | ||
|
|
6dd02d48c4 | ||
|
|
2eaf7c499e | ||
|
|
3b82e7a389 | ||
|
|
c825f17672 | ||
|
|
6bbf4453da | ||
|
|
dda247b5ea | ||
|
|
f0435804ec | ||
|
|
176feda73b | ||
|
|
f81d40b5bb | ||
|
|
c07f100986 | ||
|
|
9feded54bd | ||
|
|
4acec068aa | ||
|
|
e1d4c0f5f3 | ||
|
|
f066658c3c | ||
|
|
17021f04b9 | ||
|
|
0e5b4599bb | ||
|
|
02e7d1f4fa | ||
|
|
360a389e3d | ||
|
|
999d73db81 | ||
|
|
ec1f5edd5c | ||
|
|
2bc00dd6ca | ||
|
|
6e6253bf86 | ||
|
|
97e6fdc51b | ||
|
|
3cb9afa074 | ||
|
|
34d08bce61 | ||
|
|
3b3603d7cf | ||
|
|
ab0180a591 | ||
|
|
40e92a1b24 | ||
|
|
bf9bc013df | ||
|
|
4a58f49d3d | ||
|
|
da7270e058 | ||
|
|
420a2c8b40 | ||
|
|
d1e261e633 | ||
|
|
bfe4b8d090 | ||
|
|
269145a395 | ||
|
|
22122046ce | ||
|
|
4284aeced6 | ||
|
|
d047fa50a9 | ||
|
|
273b5f3c4e | ||
|
|
3477cc395b | ||
|
|
59f2c4a55d | ||
|
|
84c616b6f6 | ||
|
|
57e3dd05c7 | ||
|
|
2b52488bfa | ||
|
|
3d2f28431f | ||
|
|
f1c8364b72 | ||
|
|
bb8ba60829 | ||
|
|
50d6537a73 | ||
|
|
e68b6dc72d | ||
|
|
1da020abd4 | ||
|
|
db5af2c1f6 | ||
|
|
68fec1c8da | ||
|
|
efe7248ce9 | ||
|
|
6dfe929655 | ||
|
|
e86612af4b | ||
|
|
43f441693e | ||
|
|
0fbc410baf | ||
|
|
d393ecda34 | ||
|
|
88fb8f65e2 | ||
|
|
623913d237 | ||
|
|
70daacf559 | ||
|
|
4bd0e09e7d | ||
|
|
94ed34b5ba | ||
|
|
ed3554aeb0 | ||
|
|
5b2a139da3 | ||
|
|
60f38c7f99 | ||
|
|
67b74e98ed | ||
|
|
2b73e38b15 | ||
|
|
4bc33ffea0 | ||
|
|
df5e28ec94 | ||
|
|
2a10ec5e70 | ||
|
|
2f360a0475 | ||
|
|
f6c33fc2a5 | ||
|
|
3835093b51 | ||
|
|
1211262764 | ||
|
|
419d6bc654 | ||
|
|
f81b6091b3 | ||
|
|
28512b685e | ||
|
|
8696613477 | ||
|
|
962510f784 | ||
|
|
47b7c71cdb | ||
|
|
dbd7e949b7 | ||
|
|
740ba1b2e4 | ||
|
|
08c05cbcf2 | ||
|
|
f8175d7666 | ||
|
|
463a7479b9 | ||
|
|
425c0d3244 | ||
|
|
3c4ef83876 | ||
|
|
53b4272792 | ||
|
|
4ef608720b | ||
|
|
078e2eeb9d | ||
|
|
f837db9a89 | ||
|
|
bf26f7864f | ||
|
|
421b38114c | ||
|
|
174e51d6e1 | ||
|
|
a36394238a | ||
|
|
76933402b5 | ||
|
|
6fdd49514e | ||
|
|
bc2d47ca17 | ||
|
|
7a9e15d82e | ||
|
|
d8b701fe91 | ||
|
|
2490f7029f | ||
|
|
db69ca9966 | ||
|
|
136f440f9e | ||
|
|
ffcf1f5944 | ||
|
|
fdf8a92cd8 | ||
|
|
c17726ae40 | ||
|
|
cf7acace8c | ||
|
|
95f15106f9 | ||
|
|
2b9cde68e4 | ||
|
|
6d79f91a0a | ||
|
|
7c97e81830 | ||
|
|
d458d753fb | ||
|
|
fc88730386 | ||
|
|
59b9143012 | ||
|
|
61dd01e5c2 | ||
|
|
101d145709 | ||
|
|
820417ac0f | ||
|
|
b0cf63d75a | ||
|
|
9098ce38d6 | ||
|
|
fe529c7088 | ||
|
|
5a645b10a2 | ||
|
|
b9fed6a933 | ||
|
|
3ae65f5d11 | ||
|
|
823cad0900 | ||
|
|
b807adc73d | ||
|
|
7b07e85191 | ||
|
|
17f82e78b2 | ||
|
|
a09f63537e | ||
|
|
362e18b7d0 | ||
|
|
8edb385728 | ||
|
|
a04628147b | ||
|
|
ab549ab284 | ||
|
|
40a88bb04e | ||
|
|
d4afc42d5c | ||
|
|
b71278aef1 | ||
|
|
39bffb527e | ||
|
|
cfa49edc49 | ||
|
|
b801f7c19e | ||
|
|
f75f55fa22 | ||
|
|
152a2bfad7 | ||
|
|
03c6f1a269 | ||
|
|
5a4be7a580 | ||
|
|
2dd47cab86 | ||
|
|
8aaa9dfceb | ||
|
|
46db3980d9 | ||
|
|
8d56700860 | ||
|
|
db4c260e08 | ||
|
|
f7880fca97 | ||
|
|
b7c7bd7452 | ||
|
|
7a883cdebb | ||
|
|
a622e7a9e8 | ||
|
|
0646dbc8f6 | ||
|
|
b031e96ef2 | ||
|
|
7c3aa0610a | ||
|
|
4106e4cfe2 | ||
|
|
5a584bd6b4 | ||
|
|
ade3162e53 | ||
|
|
43824972e5 | ||
|
|
6b9e19c4a2 | ||
|
|
04ba2b2954 | ||
|
|
acfab87b5c | ||
|
|
c5b04b51bc | ||
|
|
b9b8186e68 | ||
|
|
78ea11bec0 | ||
|
|
46fa940f6d | ||
|
|
853f8c8e6a | ||
|
|
4f2c43ddaa | ||
|
|
a249c6a80e | ||
|
|
8e8a87ba51 | ||
|
|
5a79f20def | ||
|
|
367dbbb3b2 | ||
|
|
7d3d7bc737 | ||
|
|
65cf697cb0 | ||
|
|
0b554f0d0f | ||
|
|
6b2b868745 | ||
|
|
0d096815f2 | ||
|
|
2648688873 | ||
|
|
956e44a452 | ||
|
|
9e1a401a39 | ||
|
|
bda2ee592d | ||
|
|
d65022a4cf | ||
|
|
35d6de38d7 | ||
|
|
a9e551bea5 | ||
|
|
789d4b7607 | ||
|
|
e95e69e2ab | ||
|
|
8792b4c8ae | ||
|
|
508b5467ca | ||
|
|
8df2e3c86e | ||
|
|
b80dc798bb | ||
|
|
9cfde5cdaa | ||
|
|
f0aed0cf5b | ||
|
|
154cb86d0f | ||
|
|
7caf85a648 | ||
|
|
2ac06d7f0b | ||
|
|
08222f6e7e | ||
|
|
2ad25bde37 | ||
|
|
ceac5c1f59 | ||
|
|
5bc337de37 | ||
|
|
3f0b356fd0 | ||
|
|
aebc0ffbee | ||
|
|
198f0e6493 | ||
|
|
784f768cb9 | ||
|
|
65351d4831 | ||
|
|
cde1899804 | ||
|
|
9129a8eed2 | ||
|
|
2cc4a06a10 | ||
|
|
7234ad3d4b | ||
|
|
cb77e94776 | ||
|
|
0d3528e18c | ||
|
|
f8bd7fb681 | ||
|
|
70dccbe4fa | ||
|
|
2f6ac39f3e | ||
|
|
7b4c31e174 | ||
|
|
ceb3f8e559 | ||
|
|
739363cfe7 | ||
|
|
90c1e1bebf | ||
|
|
155294f29e | ||
|
|
2ddeee123f | ||
|
|
2a4236f7aa | ||
|
|
814de89cbe | ||
|
|
36d61a999c | ||
|
|
35824b295e | ||
|
|
dfa3cd3074 | ||
|
|
6e682b8afe | ||
|
|
dcac35f6be | ||
|
|
7cc3f12416 | ||
|
|
09033a3698 | ||
|
|
4cd9f63311 | ||
|
|
f227978403 | ||
|
|
733dc728db | ||
|
|
f7c804c052 | ||
|
|
9da04516d3 | ||
|
|
f923fda09f | ||
|
|
664d7c1c1c | ||
|
|
df05ced4a7 | ||
|
|
399e9aa6f2 | ||
|
|
ffc7df6140 | ||
|
|
350ec18f1e | ||
|
|
d7cf7caf66 | ||
|
|
8a0c42ac66 | ||
|
|
2113b56154 | ||
|
|
f4e8e0c161 | ||
|
|
edf14416e0 | ||
|
|
51e9ea32eb | ||
|
|
3c22ec5f82 | ||
|
|
21d8200c9e | ||
|
|
df03f9c35b | ||
|
|
5040ec8524 | ||
|
|
b9669aa7aa | ||
|
|
d66975abb3 | ||
|
|
d7d09ffab5 | ||
|
|
88691cfa2d | ||
|
|
8c3f45b677 | ||
|
|
d5536ae307 | ||
|
|
23602137a8 | ||
|
|
f94bfa61c0 | ||
|
|
ffd0db90e9 | ||
|
|
e6288e00e9 | ||
|
|
58fa8e6d80 | ||
|
|
5326ffa290 | ||
|
|
b44f1229e2 | ||
|
|
d6002cc10a | ||
|
|
6cb758a12d | ||
|
|
a8737ce0ae | ||
|
|
20607c5367 | ||
|
|
61dac586c6 | ||
|
|
497d941746 | ||
|
|
1a2dc43b4c | ||
|
|
25b9bcf997 | ||
|
|
89a3825564 | ||
|
|
b89be75ab6 | ||
|
|
7ebb3382d3 | ||
|
|
d68aae99f2 | ||
|
|
ef7227509e | ||
|
|
4a72295eee | ||
|
|
340bedbcee | ||
|
|
732ade7540 | ||
|
|
ff2bc5e4fa | ||
|
|
67df72d669 | ||
|
|
fa684466b2 | ||
|
|
da8df046fe | ||
|
|
836536226f | ||
|
|
07923bb34f | ||
|
|
4868d206af | ||
|
|
fcdee8628e | ||
|
|
aa7d1906a9 | ||
|
|
838934880f | ||
|
|
74d7f3c276 | ||
|
|
f313d825de | ||
|
|
c573a923b4 | ||
|
|
db7e770513 | ||
|
|
8c1bf07a19 | ||
|
|
bd8ca1c831 | ||
|
|
0b9818cbcb | ||
|
|
86cd30e134 | ||
|
|
3d696b71ee | ||
|
|
c7fa4b65bc | ||
|
|
4734d20690 | ||
|
|
8d6a384ac7 | ||
|
|
30929b051e | ||
|
|
efe2bcd61d | ||
|
|
d7205ea3af | ||
|
|
61ee753572 | ||
|
|
847805624e | ||
|
|
e945772c6a | ||
|
|
e9b1a4ca8f | ||
|
|
2f17465a84 | ||
|
|
1730da9caa | ||
|
|
95dc7617ff | ||
|
|
030b126a9c | ||
|
|
91eae38e3b | ||
|
|
95617e9c0c | ||
|
|
f069d9e1f3 | ||
|
|
78efb11dbd | ||
|
|
9e55283b86 | ||
|
|
d2cc56940c | ||
|
|
09cd602839 | ||
|
|
5dfe83fe0f | ||
|
|
9dcc7665ff | ||
|
|
e96b58afca | ||
|
|
b049450d35 | ||
|
|
115aff8855 | ||
|
|
252f0e1134 | ||
|
|
a976d3e84d | ||
|
|
41d5526c50 | ||
|
|
26bbbc412b | ||
|
|
e571c36f78 | ||
|
|
283a851a17 | ||
|
|
7b7417ae14 | ||
|
|
c44dc209b3 | ||
|
|
1ca99bdcaf | ||
|
|
7a77207275 | ||
|
|
9633e7515a | ||
|
|
e738d147e4 | ||
|
|
dfe902de4b | ||
|
|
9b54e5b4be | ||
|
|
7e294d77d7 | ||
|
|
65c821e394 | ||
|
|
bf97de31e0 | ||
|
|
8ca4264afd | ||
|
|
eff693692b | ||
|
|
c4e1241966 | ||
|
|
b237fe0f48 | ||
|
|
75c9c2a00d | ||
|
|
e517b393cf | ||
|
|
b0df0a0e4f | ||
|
|
40a83e1bad | ||
|
|
b59d7af915 | ||
|
|
2c390ca20a | ||
|
|
c6fb2716fa | ||
|
|
937f9d86a4 | ||
|
|
38deee044f | ||
|
|
4b0cd9c197 | ||
|
|
ee387ce314 | ||
|
|
480a2dc216 | ||
|
|
844db3f6a8 | ||
|
|
8d2aad48aa | ||
|
|
bee4f25e2d | ||
|
|
e47222ac29 | ||
|
|
78623265df | ||
|
|
5a51a0ca39 | ||
|
|
b726898119 | ||
|
|
83aced2181 | ||
|
|
f1a9def628 | ||
|
|
8931c7175a | ||
|
|
361bfed6d6 | ||
|
|
e6f0908a3d | ||
|
|
9280ec11f7 | ||
|
|
8e0a21421b | ||
|
|
867e1c0ed5 | ||
|
|
420a706f5d | ||
|
|
c4b8e0912a | ||
|
|
598e99d2da | ||
|
|
20a6839eaf | ||
|
|
c9b2d3ea10 | ||
|
|
8854366c12 | ||
|
|
17ff619f79 | ||
|
|
a468795294 | ||
|
|
bea8376066 | ||
|
|
fd272a853a | ||
|
|
93c5ec0257 | ||
|
|
e2c645a17c | ||
|
|
30b484a92d | ||
|
|
0d3faf230d | ||
|
|
62eff2ec82 | ||
|
|
71e8da44bf | ||
|
|
6a4e967a70 | ||
|
|
de0bd9f09d | ||
|
|
5b7d533dc3 | ||
|
|
cdc04b66d7 | ||
|
|
1c3dbbe779 | ||
|
|
416c32d166 | ||
|
|
73de5d185c | ||
|
|
d9039b9071 | ||
|
|
dcde6edba4 | ||
|
|
da6cbd5cfa | ||
|
|
ba1e61a78e | ||
|
|
43160dd9e6 | ||
|
|
4b5c41e67d | ||
|
|
05edf16847 | ||
|
|
cb3c91f802 | ||
|
|
54ca8d4c7a | ||
|
|
56ac627c4a | ||
|
|
b642a9a031 | ||
|
|
84bbd3e2ca | ||
|
|
0a3b99fc70 | ||
|
|
653c282f07 | ||
|
|
23e6f552e7 | ||
|
|
0507488ae1 | ||
|
|
64da009acb | ||
|
|
70b6d1aa34 | ||
|
|
fed4bac5de | ||
|
|
3528359d74 | ||
|
|
e2eb288fa8 | ||
|
|
2a885c7758 | ||
|
|
5df2416524 | ||
|
|
79e61b44af | ||
|
|
701c0139f9 | ||
|
|
dd9733f9a8 | ||
|
|
cf11c115f9 | ||
|
|
0d52e96307 | ||
|
|
262024ec28 | ||
|
|
93c8507386 | ||
|
|
6c131e2da1 | ||
|
|
cd297c1b36 | ||
|
|
c488773bc1 | ||
|
|
bf4e000644 | ||
|
|
0fddc08868 | ||
|
|
7394667f4a | ||
|
|
765ecfb1db | ||
|
|
67f93b84ac | ||
|
|
29f713c157 | ||
|
|
8f25491e78 | ||
|
|
aa43554013 | ||
|
|
820ac51190 | ||
|
|
169739d617 | ||
|
|
3eb23cc3d7 | ||
|
|
3cba684ddc | ||
|
|
cd70d7b06f | ||
|
|
343f8a5b2b | ||
|
|
7313a0d180 | ||
|
|
5cfde7b0f2 | ||
|
|
26cabf3407 | ||
|
|
a1162d1e86 | ||
|
|
2cc117b04f | ||
|
|
4e9919f5a2 | ||
|
|
f0ea7be729 | ||
|
|
65db6a1041 | ||
|
|
8b02351517 | ||
|
|
a50554d4b7 | ||
|
|
e56ac834ee | ||
|
|
c25639baa4 | ||
|
|
4e8c736814 | ||
|
|
ec4816bd00 | ||
|
|
538568e963 | ||
|
|
d6097b66c3 | ||
|
|
fd589f85e8 | ||
|
|
ee6631f432 | ||
|
|
3df44a8d51 | ||
|
|
ee7634b9d0 | ||
|
|
794010df9c | ||
|
|
b800ace754 | ||
|
|
0c29c4133e | ||
|
|
fa5c88ea1e | ||
|
|
75461a49df | ||
|
|
aa59e05d5b | ||
|
|
916a3f8abf | ||
|
|
8494a6c816 | ||
|
|
f2e740133a | ||
|
|
6c04d35ef1 | ||
|
|
7cbede9d3d | ||
|
|
89ee7f6606 | ||
|
|
e5cd8164c3 | ||
|
|
05db14bfbd | ||
|
|
9693bf7704 | ||
|
|
24701aa9a8 | ||
|
|
c30fbfbf82 | ||
|
|
c6656aa417 | ||
|
|
2308d5f5d2 | ||
|
|
4b1aad995d | ||
|
|
1ed43fb510 | ||
|
|
7563f48f05 | ||
|
|
39ab109f19 | ||
|
|
69186282a0 | ||
|
|
99b642f7ac | ||
|
|
1b5c3f6b3f | ||
|
|
313950f7b3 | ||
|
|
db582ca7e7 | ||
|
|
e363f90eba | ||
|
|
6cbcd7354c | ||
|
|
3dbc1890da | ||
|
|
6445244957 | ||
|
|
da5c1893b3 | ||
|
|
37851f8f53 | ||
|
|
3f889c6aad | ||
|
|
4448a81ee5 | ||
|
|
6d6c7bc923 | ||
|
|
e49f6f66df | ||
|
|
30d96a94c5 | ||
|
|
103d9108b1 | ||
|
|
115fc2fe70 | ||
|
|
7f4cb37aea | ||
|
|
ec67c123d2 | ||
|
|
9a05b91788 | ||
|
|
80da30937e | ||
|
|
427c1d2761 | ||
|
|
0edcf2b9c1 | ||
|
|
228e312949 | ||
|
|
2ed75e5b0f | ||
|
|
2d9fa3bd2f | ||
|
|
7a23cb69fa | ||
|
|
aa8f483da0 | ||
|
|
92b31bc2c5 | ||
|
|
245b3397a8 | ||
|
|
c86d8f81d3 | ||
|
|
95ef47459f | ||
|
|
26f42889bf | ||
|
|
ca38a5d69d | ||
|
|
07b480ef26 | ||
|
|
1bff8de1a5 | ||
|
|
a9dbc7303c | ||
|
|
2011f31e70 | ||
|
|
d4e90b9ec8 | ||
|
|
dc3d3cc24a | ||
|
|
51f6e8229f | ||
|
|
795f1a7b45 | ||
|
|
516eb12ecd | ||
|
|
72b6924422 | ||
|
|
6950a86821 | ||
|
|
7e875f6b37 | ||
|
|
ebda0d9317 | ||
|
|
5647cacfa9 | ||
|
|
1ec18e5daa | ||
|
|
7bc698482c | ||
|
|
035acf62c5 | ||
|
|
0cb8cbd802 | ||
|
|
ab90af892f | ||
|
|
d83fbb993e | ||
|
|
52785debf7 | ||
|
|
46b688aa28 | ||
|
|
7a9f747d7d | ||
|
|
5f66017fb7 | ||
|
|
ffe86adc60 | ||
|
|
446dd31f81 | ||
|
|
698c052036 | ||
|
|
76ef8be03f | ||
|
|
4af8aeee5b | ||
|
|
e489d7f245 | ||
|
|
713fc6bd1c | ||
|
|
c0e9fe7d0e | ||
|
|
85f27e603f | ||
|
|
c0833bb69a | ||
|
|
277cb36c85 | ||
|
|
37331f685a | ||
|
|
8f8a3e44f3 | ||
|
|
1343ab2de1 | ||
|
|
c4f42cb4f9 | ||
|
|
c8bfb1f2f0 | ||
|
|
0a27c79ee3 | ||
|
|
322d957105 | ||
|
|
38d58cf0c7 | ||
|
|
fdaa4a2eef | ||
|
|
405834f2d6 | ||
|
|
ee5d4be0a6 | ||
|
|
86953af7c2 | ||
|
|
367595f4e8 | ||
|
|
902ae9e5be | ||
|
|
c3e3bf8ef8 | ||
|
|
95486d659a | ||
|
|
f05eb2589a | ||
|
|
20c5a9578a | ||
|
|
40fdb3d2ac | ||
|
|
5907ba7aa6 | ||
|
|
fd9cfc159b | ||
|
|
22cf399f5c | ||
|
|
7aa33d19b0 | ||
|
|
d29286a164 | ||
|
|
f7bf44f64c | ||
|
|
d77f723c98 | ||
|
|
a6855f5b80 | ||
|
|
380b5d62a3 | ||
|
|
8500c2d888 | ||
|
|
6b03fb84c5 | ||
|
|
cb5a135f68 | ||
|
|
e9fd96cd4b | ||
|
|
aad92d7295 | ||
|
|
00036b0b5d | ||
|
|
eb028fe561 | ||
|
|
dc1fc75c2d | ||
|
|
df7283a407 | ||
|
|
1f41401660 | ||
|
|
b691750df4 | ||
|
|
20676f595a | ||
|
|
ac95040be2 | ||
|
|
5c3afb0e68 | ||
|
|
14159a26a6 | ||
|
|
963c5f7c32 | ||
|
|
62d894b826 | ||
|
|
85a21efaf6 | ||
|
|
684603eebd | ||
|
|
ad722ed505 | ||
|
|
d2f6fb5c3d | ||
|
|
a050c4d2de | ||
|
|
7a83a51ca3 | ||
|
|
f97cdc1ae5 | ||
|
|
60cb078cd3 | ||
|
|
c15f694a1d | ||
|
|
9f508aedbe | ||
|
|
db7f5d20fe | ||
|
|
4777ca25fa | ||
|
|
8138766583 | ||
|
|
adc365cf44 | ||
|
|
654f826598 | ||
|
|
e78e1079e3 | ||
|
|
10726dde2b | ||
|
|
2f74df29ff | ||
|
|
556fa360d3 | ||
|
|
d3435a1f4b | ||
|
|
48394fe5f8 | ||
|
|
732c90f1e6 | ||
|
|
c34de2b8d7 | ||
|
|
d23859fb75 | ||
|
|
1e0847ecdd | ||
|
|
eb11bb5118 | ||
|
|
9668a3274e | ||
|
|
42637b2949 | ||
|
|
9f0763b5ea | ||
|
|
7088b8657d | ||
|
|
2119cfbe09 | ||
|
|
591f3ac2ad | ||
|
|
5db08c3b83 | ||
|
|
18c3913f8f | ||
|
|
bbc268aaef | ||
|
|
4cb208bad7 | ||
|
|
69dad0d854 | ||
|
|
898fe142e9 | ||
|
|
90950567e2 | ||
|
|
20e0f265c1 | ||
|
|
80db649b9f | ||
|
|
2514ec3356 | ||
|
|
885ad53383 | ||
|
|
39906ae9c2 | ||
|
|
fe7b8a8d07 | ||
|
|
dd8945dc5e | ||
|
|
d370d13f2d | ||
|
|
6bf35a8381 | ||
|
|
75aa1f81aa | ||
|
|
9b2c0edcc5 | ||
|
|
c7989106b7 | ||
|
|
807365bd61 | ||
|
|
7e5f2b8c10 | ||
|
|
1e19067113 | ||
|
|
65934b2041 | ||
|
|
6cb6c83dba | ||
|
|
a3bbf7b42c | ||
|
|
23f5051ace | ||
|
|
5ce4bd3b37 | ||
|
|
4aa9724d41 | ||
|
|
6849e6d051 | ||
|
|
38cc8f31a8 | ||
|
|
5c9da5fae5 | ||
|
|
f2f39685a9 | ||
|
|
2b3fdb87e0 | ||
|
|
30c26647da | ||
|
|
a26e11a294 | ||
|
|
c40d772ebc | ||
|
|
e424297abf | ||
|
|
aa7c13ad5d | ||
|
|
c55cc81022 | ||
|
|
aa38f0a71f | ||
|
|
d0c156718a | ||
|
|
6c9fc98c68 | ||
|
|
d209e7fd79 | ||
|
|
8a00f37e9e | ||
|
|
7256630d87 | ||
|
|
5507e3bd6a | ||
|
|
5ba21a9771 | ||
|
|
06a327367c | ||
|
|
a727e261d1 | ||
|
|
5809dd7c40 | ||
|
|
60e0ef5860 | ||
|
|
9b2d2957ca | ||
|
|
1fed826127 | ||
|
|
18ce4e1c62 | ||
|
|
f049a6f7d0 | ||
|
|
0c409c6af1 | ||
|
|
f166837083 | ||
|
|
5dd7efa2d5 | ||
|
|
e05d519e5a | ||
|
|
de80284665 | ||
|
|
5187d25e91 | ||
|
|
12543a1fdd | ||
|
|
6db56b89a4 | ||
|
|
63c960a7aa | ||
|
|
0018e51eb5 | ||
|
|
c2eb31cbb2 | ||
|
|
fc19a1430b | ||
|
|
94ea57deac | ||
|
|
b6ec22e343 | ||
|
|
00fef2a2d2 | ||
|
|
33867a5485 | ||
|
|
90b43bf762 | ||
|
|
f67d048f27 | ||
|
|
e95ff9a7fa | ||
|
|
a0d9bdb0d5 | ||
|
|
5e270d5c69 | ||
|
|
1ccf1e90da | ||
|
|
82120fff8e | ||
|
|
383a5a99ff | ||
|
|
ef8711ab2e | ||
|
|
8ec7f0956b | ||
|
|
535f957467 | ||
|
|
b9e4087e3e | ||
|
|
40b35fb642 | ||
|
|
e3fd377257 | ||
|
|
fb320f0338 | ||
|
|
d29990b877 | ||
|
|
d615a05930 | ||
|
|
ef4a25ee6e | ||
|
|
58af7c52c8 | ||
|
|
7bcb9090ae | ||
|
|
bb10e6fc89 | ||
|
|
f26a3318c4 | ||
|
|
cf57772a39 | ||
|
|
c934e6eeb4 | ||
|
|
aca73c32e3 | ||
|
|
0450adb396 | ||
|
|
0d691ded80 | ||
|
|
5c8731eba9 | ||
|
|
aabf5579e1 | ||
|
|
5ac58bd849 | ||
|
|
ab974cecb2 | ||
|
|
a7d986d3f0 | ||
|
|
c6018b4ebd | ||
|
|
3e9ff8ff7c | ||
|
|
d359427e42 | ||
|
|
591d828da3 | ||
|
|
4d928bd8de | ||
|
|
71dc65a5e4 | ||
|
|
3697c353cc | ||
|
|
00131daedf | ||
|
|
620de4c528 | ||
|
|
e3233f4c32 | ||
|
|
c529756ab7 | ||
|
|
743f2bf560 | ||
|
|
567a572487 | ||
|
|
def7e4f3cf | ||
|
|
da16826ab5 | ||
|
|
a681778046 | ||
|
|
6db5f0187b | ||
|
|
694789cef8 | ||
|
|
27c05a6207 | ||
|
|
9f71d4ec33 | ||
|
|
1ad7d5bc7e | ||
|
|
412830e922 | ||
|
|
ef5140e2f3 | ||
|
|
922e5d5c99 | ||
|
|
b59d19a7c8 | ||
|
|
eeef9cc916 | ||
|
|
24d6d86142 | ||
|
|
fffcdd8514 | ||
|
|
81fa3448e6 | ||
|
|
cd85c61d0d | ||
|
|
d72958b749 | ||
|
|
ec42723cf0 | ||
|
|
451f394807 | ||
|
|
562d37ee7a | ||
|
|
9711ce72b9 | ||
|
|
1ce4f66b1c | ||
|
|
a794397369 | ||
|
|
eb75000c80 | ||
|
|
f70bc0ca2e | ||
|
|
e59fdb2251 | ||
|
|
5bafb0503f | ||
|
|
ecbc76d3b7 | ||
|
|
10d50fc684 | ||
|
|
b88966478b | ||
|
|
c123d499ba | ||
|
|
d1281a1f6c | ||
|
|
15a46805f7 | ||
|
|
2daee0ff83 | ||
|
|
2a9e1a7eed | ||
|
|
163fcabd73 | ||
|
|
2cedbb6ed9 | ||
|
|
e9662fef67 | ||
|
|
e6f8eb3b35 | ||
|
|
75bfbe0753 | ||
|
|
0b908906c0 | ||
|
|
dbd979f126 | ||
|
|
d336ff7985 | ||
|
|
6239b2b765 | ||
|
|
d5646ebbdb | ||
|
|
7877a5505b | ||
|
|
a6bc7908c2 | ||
|
|
d38322a868 | ||
|
|
2c8d512997 | ||
|
|
584aedc905 | ||
|
|
df36f9c246 | ||
|
|
6dfb8eca48 | ||
|
|
340267b394 | ||
|
|
bcc31bcc04 | ||
|
|
1a15029a22 | ||
|
|
d70142219a | ||
|
|
01a96f4141 | ||
|
|
3839ae0dfc | ||
|
|
5c1d1a1bce | ||
|
|
fb877bc290 | ||
|
|
f9871d9d05 | ||
|
|
b897245a09 | ||
|
|
5cc2ee3bda | ||
|
|
cca6d9cdfc | ||
|
|
1fa3b9b880 | ||
|
|
0fef8a6838 | ||
|
|
f0aeb42d06 | ||
|
|
a8886db089 | ||
|
|
09038a4b34 | ||
|
|
33c5087544 | ||
|
|
eca2b62d24 | ||
|
|
71b35183f9 | ||
|
|
75a938e0b3 | ||
|
|
2cbc86ddce | ||
|
|
1c524ed78b | ||
|
|
3702332669 | ||
|
|
fb24e58d56 | ||
|
|
c396a91881 | ||
|
|
90e1db42c4 | ||
|
|
f4c8bb0095 | ||
|
|
6ddc641475 | ||
|
|
a61e144ecb | ||
|
|
21cffc1f03 | ||
|
|
c9112b7714 | ||
|
|
79ee6e408a | ||
|
|
d7c6e0d68d | ||
|
|
3d61120ecb | ||
|
|
c52ce4de0a | ||
|
|
a821afaebe | ||
|
|
76fd658ae6 | ||
|
|
e4fe7f8ed1 | ||
|
|
6516a04eda | ||
|
|
d0ee1962d8 | ||
|
|
1a7fc2ab72 | ||
|
|
d1e5cd9923 | ||
|
|
c071bcfe1e | ||
|
|
7719262904 | ||
|
|
fd0dcb95a4 | ||
|
|
b3ee7bb1f7 | ||
|
|
2522bfca91 | ||
|
|
5f71e30e2d | ||
|
|
94cfaba606 | ||
|
|
20668c267b | ||
|
|
cf28558c27 | ||
|
|
a2f2763759 | ||
|
|
7738d45be0 | ||
|
|
ce21551109 | ||
|
|
3967e1236c | ||
|
|
e4aa009750 | ||
|
|
bf10676680 | ||
|
|
92949a719b | ||
|
|
cd41803b7c | ||
|
|
cde4f33489 | ||
|
|
cc959f88fd | ||
|
|
6bcd1033a1 | ||
|
|
04ea0d7d8c | ||
|
|
444511d79e | ||
|
|
baffa751d6 | ||
|
|
bdf549cc1d | ||
|
|
e7c0c5b6f0 | ||
|
|
a875e138eb | ||
|
|
5586abe7a2 | ||
|
|
0e3059f906 | ||
|
|
c306730c0b | ||
|
|
2c342ac1d9 | ||
|
|
b0e09cf9c5 | ||
|
|
f24cdb6d38 | ||
|
|
b8e4dadaf3 | ||
|
|
db2b69fe53 | ||
|
|
103ef34b06 | ||
|
|
f58a4353dd | ||
|
|
beb68b5051 | ||
|
|
d2f0710097 | ||
|
|
f28e405f6d | ||
|
|
424b98bb9d | ||
|
|
9c447e0442 | ||
|
|
cd508ed946 | ||
|
|
453991f6d8 | ||
|
|
51ba9517d9 | ||
|
|
bd289a06b3 | ||
|
|
26be0594e8 | ||
|
|
b476485cd7 | ||
|
|
4da28003f2 | ||
|
|
304312ce6f | ||
|
|
aced214213 | ||
|
|
13c4f22e49 | ||
|
|
5bc943a9f5 | ||
|
|
560beb0773 | ||
|
|
321322c0a5 | ||
|
|
21ac34b8a5 | ||
|
|
3bbab7232c | ||
|
|
fada5e6b29 | ||
|
|
00d95f9e88 | ||
|
|
196efc52c1 | ||
|
|
e302fe834c | ||
|
|
9515a8c3cc | ||
|
|
ca5a9cf0ef | ||
|
|
679be23066 | ||
|
|
6b745fd3b0 | ||
|
|
0ca7ceadab | ||
|
|
949e707c7e | ||
|
|
c3464c4fe3 | ||
|
|
10f86bf513 | ||
|
|
68f355846f | ||
|
|
3b8aa0a7c6 | ||
|
|
847bbb935c | ||
|
|
ee4be7dc94 | ||
|
|
6b20a11595 | ||
|
|
51bbdf61ba | ||
|
|
84444c3380 | ||
|
|
8d8209d1f8 | ||
|
|
c406972175 | ||
|
|
4ded82b3c7 | ||
|
|
2f49daae78 | ||
|
|
6af385dd48 | ||
|
|
a82572ee5b | ||
|
|
ceee067734 | ||
|
|
34c66465ce | ||
|
|
8463a16fe1 | ||
|
|
02c1442e6c | ||
|
|
12466896e0 | ||
|
|
caac64c567 | ||
|
|
38e1c4d692 | ||
|
|
98b71277ac | ||
|
|
8b95ea3abd | ||
|
|
b783fe5bcb | ||
|
|
38ed9f6804 | ||
|
|
e5d6528788 | ||
|
|
c8818e7e3b | ||
|
|
8776893774 | ||
|
|
f25d731fc4 | ||
|
|
92fda195ec | ||
|
|
af11a1eddf | ||
|
|
4db289c3c0 | ||
|
|
2b1cf957c7 | ||
|
|
76c2fe446c | ||
|
|
31effd2ea8 | ||
|
|
5c76623907 | ||
|
|
3d8da4598f | ||
|
|
323bf9ea8d | ||
|
|
30dfe649ae | ||
|
|
c416069f69 | ||
|
|
f21bac526c | ||
|
|
b380a3197c | ||
|
|
1a98e10a4f | ||
|
|
6e4f1225a6 | ||
|
|
255f54c39a | ||
|
|
a2e4098fd6 | ||
|
|
75df0cda4a | ||
|
|
9aac3beb9c | ||
|
|
806339d295 | ||
|
|
e7dc15d832 | ||
|
|
981b2f746a | ||
|
|
e7c3db608d | ||
|
|
ea64e212b3 | ||
|
|
76e3f96a86 | ||
|
|
0713de8658 | ||
|
|
50b694f8e6 | ||
|
|
be40729ee8 | ||
|
|
adf3741d85 | ||
|
|
c015f431b2 | ||
|
|
84548abf76 | ||
|
|
af4c831aaa | ||
|
|
4fe9305099 | ||
|
|
d603b832cf | ||
|
|
03d8b9ada2 | ||
|
|
1a341c5705 | ||
|
|
c985269953 | ||
|
|
1be0d3b0eb | ||
|
|
a3cb58e716 | ||
|
|
214f048967 | ||
|
|
10a84b624e | ||
|
|
848233b6fa | ||
|
|
544c61b0a8 | ||
|
|
fe75726e04 | ||
|
|
812f43254c | ||
|
|
653fa93c8a | ||
|
|
8e633ee5c5 | ||
|
|
728e7363f2 | ||
|
|
8eebffd7fe | ||
|
|
2c6172eb88 | ||
|
|
3b8c1e7531 | ||
|
|
c2a6663e87 | ||
|
|
12ebee4523 | ||
|
|
9587d2a548 | ||
|
|
bdc6e6d1e9 | ||
|
|
1496090142 | ||
|
|
0a1aa0aeab | ||
|
|
727c57ddaa | ||
|
|
dfb552abfc | ||
|
|
3065f20f9f | ||
|
|
ec1e280eda | ||
|
|
72a0ca01d9 | ||
|
|
5cee2f4c5e | ||
|
|
7d1d1c1791 | ||
|
|
2fcae0a39c | ||
|
|
318221549b | ||
|
|
2d776517cc | ||
|
|
342f9cd10e | ||
|
|
4485cf34d4 | ||
|
|
c9a82d46eb | ||
|
|
8393095be2 | ||
|
|
b01b83cccf | ||
|
|
e83c003320 | ||
|
|
1bc18487f0 | ||
|
|
40b029274e | ||
|
|
e7018c085b | ||
|
|
f088116dfa | ||
|
|
c08000d11a | ||
|
|
23801d90f6 | ||
|
|
9ce83025b4 | ||
|
|
373726b685 | ||
|
|
b6bd4ab68c | ||
|
|
af80527c47 | ||
|
|
df765663eb | ||
|
|
824324f204 | ||
|
|
7bec75e755 | ||
|
|
93ac3c6c49 | ||
|
|
71e5eb3492 | ||
|
|
3d23461863 | ||
|
|
76b1e9ffed | ||
|
|
e62fffe11f | ||
|
|
8143409ada | ||
|
|
8642674481 | ||
|
|
7d02dc5e00 | ||
|
|
94f8ab15bc | ||
|
|
13e2e32ca5 | ||
|
|
9495798583 | ||
|
|
b6a8c40ddb | ||
|
|
abc8264304 | ||
|
|
0cc67e3107 | ||
|
|
b513dddbb6 | ||
|
|
8639d7b804 | ||
|
|
bc3fee54d6 | ||
|
|
5ec3c9cc3c | ||
|
|
6274250fbf | ||
|
|
4bc948d0ac | ||
|
|
802a348fb5 | ||
|
|
d7a9f88493 | ||
|
|
004bc71e96 | ||
|
|
7638c640ff | ||
|
|
3aded66c09 | ||
|
|
d5ed1d9c3b | ||
|
|
dfc52681cd | ||
|
|
9c1a1e88f9 | ||
|
|
f804d0216b | ||
|
|
000f3b9ef5 | ||
|
|
ec450230f8 | ||
|
|
47c391571a | ||
|
|
ce6c39b6c7 | ||
|
|
91f37486e2 | ||
|
|
f14a935012 | ||
|
|
870eba8a9c | ||
|
|
cda1bbda41 | ||
|
|
19473726c6 | ||
|
|
0202dd4d43 | ||
|
|
e2e7950dd9 | ||
|
|
9a4a8fd23a | ||
|
|
6702b42808 | ||
|
|
e8dc191d8e | ||
|
|
3022193ff7 | ||
|
|
a9aa0a5b50 | ||
|
|
ae32779c35 | ||
|
|
7904f6e56b | ||
|
|
7b833d7c66 | ||
|
|
57ae2343dd | ||
|
|
71a19e4794 | ||
|
|
716a75c4f4 | ||
|
|
a1c77fb527 | ||
|
|
7744f0e324 | ||
|
|
077a4239d8 | ||
|
|
ae91e19b9f | ||
|
|
9d46818a56 | ||
|
|
ef994670f2 | ||
|
|
cf20f1a558 | ||
|
|
3f1a5fa729 | ||
|
|
7003e5431e | ||
|
|
dadb26c5f2 | ||
|
|
22de06afe0 | ||
|
|
46f878beaa | ||
|
|
86b9d2ecca | ||
|
|
a0919fffee | ||
|
|
0f2da6f6e5 | ||
|
|
79f8b0ad7f | ||
|
|
5a1dafdc21 | ||
|
|
05c9eff1c5 | ||
|
|
164cb46fff | ||
|
|
446983a477 | ||
|
|
ff9c8e73e4 | ||
|
|
2e61978258 | ||
|
|
14a918a9c6 | ||
|
|
335d355d64 | ||
|
|
16f6e6ebef | ||
|
|
e224eee89f | ||
|
|
236b1210e6 | ||
|
|
8574ed0796 | ||
|
|
e31a5557b6 | ||
|
|
5f77848304 | ||
|
|
f23ceb22bf | ||
|
|
79f7c6c82d | ||
|
|
49dde0a2ff | ||
|
|
04bdfa77fb | ||
|
|
34940f75b1 | ||
|
|
14f1fd327c | ||
|
|
21c2194828 | ||
|
|
a423cf6de9 | ||
|
|
b98f250c07 | ||
|
|
9b0f6badce | ||
|
|
81ab8797d3 | ||
|
|
6f1cf06dd6 | ||
|
|
a9aa9c692f | ||
|
|
7bca17ad73 | ||
|
|
ccd9a69ad7 | ||
|
|
6201f5c49a | ||
|
|
3b5c0b70eb | ||
|
|
8abe0eee62 | ||
|
|
a05b1ed551 | ||
|
|
eee60e3cdd | ||
|
|
1d097c1a64 | ||
|
|
d52bdc6a10 | ||
|
|
18830fea61 | ||
|
|
e0479aa1da | ||
|
|
5e0a1d2e7d | ||
|
|
41fc0ee1ec | ||
|
|
8655e99e43 | ||
|
|
943047e59c | ||
|
|
ecc2c3bec6 | ||
|
|
79a21570e9 | ||
|
|
f5f5a8dd93 | ||
|
|
fd2b01334b | ||
|
|
f27908d4a2 | ||
|
|
6ab5bac9dc | ||
|
|
f601ec9aa1 | ||
|
|
fca64a6cca | ||
|
|
b3a0b49bc1 | ||
|
|
e84070f5bb | ||
|
|
ea566c465e | ||
|
|
4f62a865a5 | ||
|
|
910b3db1eb | ||
|
|
510c8ed09b | ||
|
|
b6c54f23fa | ||
|
|
bef1036d40 | ||
|
|
cf060ea1ff | ||
|
|
f90839f87f | ||
|
|
42d221bd79 | ||
|
|
ece7d66e58 | ||
|
|
dbca8e7fd9 | ||
|
|
063780db3a | ||
|
|
2d7f46cc57 | ||
|
|
a8211a1f9b | ||
|
|
9075f97d7a | ||
|
|
6ceed517de | ||
|
|
f886cecf8d | ||
|
|
62b6b70e36 | ||
|
|
6d7f9ba872 | ||
|
|
b4b8222649 | ||
|
|
e2cc57dd1e | ||
|
|
9e9c4deb34 | ||
|
|
4a706cf5a7 | ||
|
|
0924dd7cc3 | ||
|
|
125b60b508 | ||
|
|
e629166da3 | ||
|
|
843b9755a1 | ||
|
|
e2a9bf81e5 | ||
|
|
69fba14fa4 | ||
|
|
c11ccc36d7 | ||
|
|
226019b09f | ||
|
|
95b5fa8daa | ||
|
|
abfd1a69e5 | ||
|
|
79be37a60c | ||
|
|
3eea12e45b | ||
|
|
5c5039c999 | ||
|
|
074b985222 | ||
|
|
eecaa07638 | ||
|
|
5eda3a9ed3 | ||
|
|
6a717d386e | ||
|
|
88305f5d10 | ||
|
|
62b85a1166 | ||
|
|
d4800f8b62 | ||
|
|
eb3c327dde | ||
|
|
75735ce3c7 | ||
|
|
4a8e2a00cd | ||
|
|
21d3210f66 | ||
|
|
cdc271f70a | ||
|
|
cfd1401b7f | ||
|
|
6182c07d6a | ||
|
|
571f5e8a29 | ||
|
|
6262588697 | ||
|
|
27adcb5d7f | ||
|
|
363d47bcef | ||
|
|
753e0edce6 | ||
|
|
395f3694b1 | ||
|
|
765d4b597d | ||
|
|
594eb0f126 | ||
|
|
c0f74bad3d | ||
|
|
be87c4b281 | ||
|
|
204e1ecd88 | ||
|
|
0d634896a1 | ||
|
|
3ce65d8c25 | ||
|
|
f536ce02cf | ||
|
|
40f403be69 | ||
|
|
1da4ab5588 | ||
|
|
43736d01e2 | ||
|
|
6b73255eae | ||
|
|
107f14104e | ||
|
|
aa493ad197 | ||
|
|
6afe1c168b | ||
|
|
6036934dd9 | ||
|
|
d45b1f6fb2 | ||
|
|
aacc6e5fb9 | ||
|
|
a534940509 | ||
|
|
fa4a43c891 | ||
|
|
003f35b0ba | ||
|
|
9cdd6aa4b3 | ||
|
|
5ea3398151 | ||
|
|
46b613fef2 | ||
|
|
6a07f5f91d | ||
|
|
066cd01dd9 | ||
|
|
2727afd644 | ||
|
|
f77563e03e | ||
|
|
7622c9b0e3 | ||
|
|
b2dcdbbaea | ||
|
|
a8f969dc55 | ||
|
|
effe301d23 | ||
|
|
4993707316 | ||
|
|
0f4d4e9cd4 | ||
|
|
0378735ac3 | ||
|
|
281283ed8b | ||
|
|
7e15d675b6 | ||
|
|
b958b8badf | ||
|
|
a4f3a06f3c | ||
|
|
ab946489cf | ||
|
|
9aae3faab3 | ||
|
|
5d132c8277 | ||
|
|
366bba97cb | ||
|
|
a7e47951e6 | ||
|
|
c9978cecda | ||
|
|
50de9cfbf2 | ||
|
|
73849e825f | ||
|
|
e9ad9bbfe0 | ||
|
|
68aeded077 | ||
|
|
4d9fb18066 | ||
|
|
bf3c0c19da | ||
|
|
a484207eb6 | ||
|
|
bb2c189c56 | ||
|
|
44d1d5bf5e | ||
|
|
0f366884f0 | ||
|
|
9a9812f379 | ||
|
|
2695567ef6 | ||
|
|
878acbad76 | ||
|
|
c0767a1ba1 | ||
|
|
11c4e78ea8 | ||
|
|
b165996f9d | ||
|
|
fbf5b2513a | ||
|
|
09a6aa286d | ||
|
|
a235aa9f88 | ||
|
|
232f66eb69 | ||
|
|
b93f8a8099 | ||
|
|
4a3a39e5e1 | ||
|
|
bc1ccbc176 | ||
|
|
5bd14c1127 | ||
|
|
e154faaf32 | ||
|
|
ddf445701d | ||
|
|
fa546eac05 | ||
|
|
235d86ca2b | ||
|
|
f4437cfe0a | ||
|
|
8c6a2c84e3 | ||
|
|
4748cf6fd0 | ||
|
|
40c17a6337 | ||
|
|
f1ac70f6fe | ||
|
|
b4ce8dfaf7 | ||
|
|
23cdeaddf6 | ||
|
|
173102f1d8 | ||
|
|
9cb3122723 | ||
|
|
bcc83df3bf | ||
|
|
61c583753e | ||
|
|
bedfe75355 | ||
|
|
5e434498ac | ||
|
|
6a04f03f32 | ||
|
|
1979d604b0 | ||
|
|
793fd30c79 | ||
|
|
74c67ee132 | ||
|
|
bdd196308b | ||
|
|
15ed33d1c4 | ||
|
|
122f431bde | ||
|
|
e0150a844a | ||
|
|
7ad7437ef5 | ||
|
|
c1fc3728c3 | ||
|
|
29f815578f | ||
|
|
498ff80a5c | ||
|
|
06d597ebc5 | ||
|
|
d74cc614b8 | ||
|
|
0f68ce115b | ||
|
|
c264f09ee2 | ||
|
|
03b4e92d3a | ||
|
|
57e3fc4ebe | ||
|
|
b7659d5b5d | ||
|
|
758314093b | ||
|
|
80966cd876 | ||
|
|
aa740db35b | ||
|
|
245ef66969 | ||
|
|
0c9751bea5 | ||
|
|
53c1ed5381 | ||
|
|
9893737a57 | ||
|
|
d2a75a29ec | ||
|
|
5076536e6d | ||
|
|
215df96f03 | ||
|
|
03eee526a0 | ||
|
|
47ead0febf | ||
|
|
d0a5959e3c | ||
|
|
e93a390a07 | ||
|
|
d4a4c1bc96 | ||
|
|
2559b73550 | ||
|
|
cb3aa648e5 | ||
|
|
2ad5328c98 | ||
|
|
32a9949559 | ||
|
|
0cc74fe266 | ||
|
|
7d33ecd1df | ||
|
|
938f6d15fa | ||
|
|
63a63fa28d | ||
|
|
d1b7fef17c | ||
|
|
6edbec3cb8 | ||
|
|
b6abf17096 | ||
|
|
3e80c8eba3 | ||
|
|
72e9c17d50 | ||
|
|
cb505f1cb0 | ||
|
|
7514f162ca | ||
|
|
783c61d1b4 | ||
|
|
b8d256d137 | ||
|
|
9b8f0ce028 | ||
|
|
a733a2da0b | ||
|
|
ea2261c07a | ||
|
|
5a94e18779 | ||
|
|
b2cf2dc3eb | ||
|
|
322895c050 | ||
|
|
2cd8f28757 | ||
|
|
9ee55b6a48 | ||
|
|
bb94918905 | ||
|
|
57f1dc5822 | ||
|
|
a3ccf1cc58 | ||
|
|
033c6026ad | ||
|
|
c14f8bddd5 | ||
|
|
19ca3e6671 | ||
|
|
e22cd83b15 | ||
|
|
ac7d4db4a3 | ||
|
|
fc05752ce1 | ||
|
|
bbd758391b | ||
|
|
ee7d92256d | ||
|
|
ff28b4d00e | ||
|
|
cdf331cf08 | ||
|
|
dd8ddaf070 | ||
|
|
2b00bfbf83 | ||
|
|
59040ca240 | ||
|
|
4a81758f79 | ||
|
|
d483d3cc2c | ||
|
|
752fd73a64 | ||
|
|
92231f7ace | ||
|
|
f559ac395e | ||
|
|
79bb1d3614 | ||
|
|
3ca18afceb | ||
|
|
7a3e98bd6b | ||
|
|
ceaf7390c0 | ||
|
|
5a3faa9fb3 | ||
|
|
91f0134283 | ||
|
|
9021499f46 | ||
|
|
dcb66a52d5 | ||
|
|
4e2c18bece | ||
|
|
8bd0f22ff7 | ||
|
|
ebd98a3571 | ||
|
|
67b750d956 | ||
|
|
921435d177 | ||
|
|
134ff704bf | ||
|
|
021213205a | ||
|
|
5c8a1730f2 | ||
|
|
7b9e122eb0 | ||
|
|
2787fa7d67 | ||
|
|
14d99b44b7 | ||
|
|
59c71a82d4 | ||
|
|
3d920f9791 | ||
|
|
cc20bee727 | ||
|
|
ac78cb3f75 | ||
|
|
ec35ad5f66 | ||
|
|
acaa135d0a | ||
|
|
0adc41052b | ||
|
|
15f5d1ae49 | ||
|
|
8611273a89 | ||
|
|
c61a069a6f | ||
|
|
0c4221be9f | ||
|
|
69f2e2164e | ||
|
|
876b149331 | ||
|
|
aeb547fa9c | ||
|
|
8318f615e8 | ||
|
|
8d5d0702e6 | ||
|
|
5e92b34ee6 | ||
|
|
4bf20681fd | ||
|
|
31de77cfd1 | ||
|
|
71758aef4e | ||
|
|
d5a329469e | ||
|
|
2c907b224b | ||
|
|
e96a29ed6c | ||
|
|
8641289f43 | ||
|
|
7a4f6fbbbf | ||
|
|
0ae823be44 | ||
|
|
512829cb09 | ||
|
|
b69f1c2a76 | ||
|
|
bd0d29973c | ||
|
|
aa7e3612f3 | ||
|
|
faf4d85ac6 | ||
|
|
c620cf5b18 | ||
|
|
b5663347a2 | ||
|
|
a1e931b5e8 | ||
|
|
491b51939b | ||
|
|
367b1bbc9f | ||
|
|
0775b0f13f | ||
|
|
3217f96e6f | ||
|
|
bf5f427a61 | ||
|
|
590dc6756b | ||
|
|
419349d429 | ||
|
|
211239cc30 | ||
|
|
e86757d405 | ||
|
|
de7f23ff99 | ||
|
|
43c3d7c3b4 | ||
|
|
f24ccba760 | ||
|
|
5798722724 | ||
|
|
6ee9e7ffdf | ||
|
|
2b70ce61e6 | ||
|
|
2fcb93f60a | ||
|
|
f3451c4a7b | ||
|
|
108f080920 | ||
|
|
d1fb144e99 | ||
|
|
879fce4ef1 | ||
|
|
39e2b74499 | ||
|
|
75e1416383 | ||
|
|
8ec6b8e485 | ||
|
|
28e8f4e67c | ||
|
|
1780c6c0cf | ||
|
|
982a4ea0f6 | ||
|
|
c0538a2a50 | ||
|
|
697942b137 | ||
|
|
ef937d8097 | ||
|
|
88960124fd | ||
|
|
9de4d37273 | ||
|
|
39ba0a633c | ||
|
|
e53a72492c | ||
|
|
848eaa29fe | ||
|
|
18861117a4 | ||
|
|
a892253577 | ||
|
|
a3051ded47 | ||
|
|
c31d1934c2 | ||
|
|
e5abaf2abf | ||
|
|
77b8887b7a | ||
|
|
1a871b4a74 | ||
|
|
f369daf567 | ||
|
|
df54611572 | ||
|
|
67e10dfe3c | ||
|
|
2c8fb4be96 | ||
|
|
e8a94803b1 | ||
|
|
f3998347ca | ||
|
|
05e24f8efc | ||
|
|
314fe59f33 | ||
|
|
e273999f5d | ||
|
|
9fde197164 | ||
|
|
4278e70442 | ||
|
|
21422053be | ||
|
|
2586d39f5f | ||
|
|
82a47f5d27 | ||
|
|
767d7a78e8 | ||
|
|
4489225e74 | ||
|
|
4d820c82cc | ||
|
|
b6f0571836 | ||
|
|
0e0cd92f57 | ||
|
|
76a74a027e | ||
|
|
e460b0e407 | ||
|
|
fdb074b171 | ||
|
|
faa75eae82 | ||
|
|
f4193046e5 | ||
|
|
19099545cb | ||
|
|
226b36edb5 | ||
|
|
f77560a5c6 | ||
|
|
ebd04c82a0 | ||
|
|
37ea12dbdb | ||
|
|
5e1c015e59 | ||
|
|
b693d31240 | ||
|
|
d53a262a6d | ||
|
|
a679908f31 | ||
|
|
417b6d599c | ||
|
|
cab31062e2 | ||
|
|
5c1b0687ab | ||
|
|
e300d7d198 | ||
|
|
040270422e | ||
|
|
c969c4e9e3 | ||
|
|
399b7f671e | ||
|
|
575003107f | ||
|
|
1be8f77b60 | ||
|
|
c25b016b0d | ||
|
|
1d43a86364 | ||
|
|
1e461100e4 | ||
|
|
bc34817905 | ||
|
|
6b250b81eb | ||
|
|
303b3aae74 | ||
|
|
9405aca666 | ||
|
|
df3b72d460 | ||
|
|
856d2145cf | ||
|
|
173446ccce | ||
|
|
cdd2cd1ac4 | ||
|
|
617359c52e | ||
|
|
1812d313a2 | ||
|
|
53268a3d14 | ||
|
|
2f704d0795 | ||
|
|
79b41f4c3b | ||
|
|
72bc0e4e7a | ||
|
|
0fb360192d | ||
|
|
b129273914 | ||
|
|
144723a120 | ||
|
|
1914a8fb78 | ||
|
|
bddbca639c | ||
|
|
5a436a2d43 | ||
|
|
6f850a988e | ||
|
|
8c6f5861a5 | ||
|
|
bd18ed9c09 | ||
|
|
bbb0f91112 | ||
|
|
40f0e76419 | ||
|
|
d9b07cfabe | ||
|
|
e5d33744bf | ||
|
|
259b7f60e3 | ||
|
|
aaad3f64a5 | ||
|
|
52f7de91aa | ||
|
|
1168f94874 | ||
|
|
31532633bf | ||
|
|
a352e9450f | ||
|
|
193fd8d3d6 | ||
|
|
2b8bdf69df | ||
|
|
9ce8f1837c | ||
|
|
94aa24c797 | ||
|
|
71df3f80fa | ||
|
|
dc9e00f7c2 | ||
|
|
26dcada19d | ||
|
|
a88debe52d | ||
|
|
e0373ce0bb | ||
|
|
ff6a14f4d7 | ||
|
|
82caaff720 | ||
|
|
eaabd0ff09 | ||
|
|
463d7b3b70 | ||
|
|
896b7fb7b9 | ||
|
|
d5e5981475 | ||
|
|
f972b0d657 | ||
|
|
c7e749a124 | ||
|
|
f8716c5c3e | ||
|
|
23d10028ce | ||
|
|
d36f9e7141 | ||
|
|
eaaeb47831 | ||
|
|
3b9ce4ff35 | ||
|
|
c70053bd44 | ||
|
|
841e57c306 | ||
|
|
37eee321e5 | ||
|
|
0cd71d1626 | ||
|
|
51284cc6dd | ||
|
|
1f576b8766 | ||
|
|
405a9eb1e1 | ||
|
|
98e675da65 | ||
|
|
399487433b | ||
|
|
6fe54011a7 | ||
|
|
188c9e4593 | ||
|
|
f01e14ba7b | ||
|
|
d4822d0f28 | ||
|
|
65117aa5ce | ||
|
|
d8e38f091f | ||
|
|
7d79fcccdc | ||
|
|
b25608e843 | ||
|
|
56f048ebad | ||
|
|
9e93bbaf0a | ||
|
|
7d120dc4c8 | ||
|
|
1238cd3fc6 | ||
|
|
f8ea7759f1 | ||
|
|
0685bd640f | ||
|
|
9e7b33940e | ||
|
|
b8c2ba2b94 | ||
|
|
5c99d2ce5c | ||
|
|
3ddea5bd67 | ||
|
|
fcb5410663 | ||
|
|
4cf253bec0 | ||
|
|
2d3aac80a7 | ||
|
|
22f7ed7586 | ||
|
|
90d33e70c1 | ||
|
|
e4ee56e0dc | ||
|
|
fa05996e34 | ||
|
|
f2b0ec1c46 | ||
|
|
1126803cab | ||
|
|
518a716c59 | ||
|
|
d3d4ba2f66 | ||
|
|
dbb292c0c1 | ||
|
|
c8d875c95d | ||
|
|
4a222ad815 | ||
|
|
77ad6a7e08 | ||
|
|
8977255088 | ||
|
|
5f02cb0df9 | ||
|
|
df55d55b84 | ||
|
|
e1c39e65de | ||
|
|
ed43e2af6e | ||
|
|
08a94bccb7 | ||
|
|
74de16f952 | ||
|
|
625dac6c19 | ||
|
|
2d3896af55 | ||
|
|
66e535d96f | ||
|
|
5cc08d00cc | ||
|
|
aee237d805 | ||
|
|
d6b5cb5af0 | ||
|
|
8201760b84 | ||
|
|
26bce07709 | ||
|
|
14c36d2745 | ||
|
|
543de0f785 | ||
|
|
978d5b2dfa | ||
|
|
38e83beb58 | ||
|
|
342a4dedbd | ||
|
|
b1b55bed7c | ||
|
|
1333c1f3a1 | ||
|
|
9a56ca2b50 | ||
|
|
0a0719be74 | ||
|
|
2f22131621 | ||
|
|
7a08b772ca | ||
|
|
db492449c2 | ||
|
|
5a6a1c2d28 | ||
|
|
0d0feadc47 | ||
|
|
b36895a81b | ||
|
|
82eeeaacd4 | ||
|
|
fbbbbfa677 | ||
|
|
650e106385 | ||
|
|
93b790fe8e | ||
|
|
4c33046f1f | ||
|
|
7679ca4e5b | ||
|
|
23038489bb | ||
|
|
f1e65c5509 | ||
|
|
dbc81c4816 | ||
|
|
b8addae3e0 | ||
|
|
ce7c409259 | ||
|
|
fb297ecd1b | ||
|
|
514fb08ff1 | ||
|
|
f461cbf2b9 | ||
|
|
b4fbf635d0 | ||
|
|
e17a902560 | ||
|
|
62baa3b210 | ||
|
|
70af17ded0 | ||
|
|
3f143011a0 | ||
|
|
07335a75ff | ||
|
|
a435565a04 | ||
|
|
cdbdf77f15 | ||
|
|
f95b76c893 | ||
|
|
b0ecc38308 | ||
|
|
24b05ce2c4 | ||
|
|
5e03a52547 | ||
|
|
a3a1825014 | ||
|
|
826d419c34 | ||
|
|
eb1144d197 | ||
|
|
762e839a57 | ||
|
|
68d3e4dc39 | ||
|
|
8016a4d24d | ||
|
|
2b7bbe61aa | ||
|
|
bccefd55ed | ||
|
|
9725876300 | ||
|
|
4c0183801a | ||
|
|
274394c235 | ||
|
|
d450a82a37 | ||
|
|
87d4a19aee | ||
|
|
50f5689330 | ||
|
|
14a480faa1 | ||
|
|
fb78650fbb | ||
|
|
67c5c345c2 | ||
|
|
68ea871c9f | ||
|
|
04621392d6 | ||
|
|
0d3f67691d | ||
|
|
e33bed276a | ||
|
|
3731462d77 | ||
|
|
436cb7fa8a | ||
|
|
20f9f6535c | ||
|
|
3f3a207040 | ||
|
|
793178b8b6 | ||
|
|
c7f87e2e3d | ||
|
|
cf0ee7a543 | ||
|
|
cad9d76da6 | ||
|
|
20a8be8375 | ||
|
|
3319d32256 | ||
|
|
7352f3f6d2 | ||
|
|
adb6f45fd3 | ||
|
|
f367fd1e74 | ||
|
|
5fcee17554 | ||
|
|
5e47aece53 | ||
|
|
f5b8af9d07 | ||
|
|
491ea637bd | ||
|
|
35b08c6f5b | ||
|
|
a8c53c0954 | ||
|
|
2021285227 | ||
|
|
6c66da2964 | ||
|
|
43599c2a02 | ||
|
|
47f4f75eb5 | ||
|
|
6bd56aad75 | ||
|
|
6090f56b2c | ||
|
|
bb98218e55 | ||
|
|
7058d77889 | ||
|
|
d498aa5d76 | ||
|
|
191cdc8158 | ||
|
|
01158a8529 | ||
|
|
1ff11a46fc | ||
|
|
8d28402172 | ||
|
|
5f15bba160 | ||
|
|
2eabd03b48 | ||
|
|
ad23fe0c80 | ||
|
|
933a4f4c26 | ||
|
|
e0cafae746 | ||
|
|
6cfd5a54eb | ||
|
|
174843c7ed | ||
|
|
73935076f8 | ||
|
|
b708fa0d1f | ||
|
|
a1af6c6c5d | ||
|
|
7c71835043 | ||
|
|
bb0431fa0f | ||
|
|
471be6b8a9 | ||
|
|
aabdaac3fd | ||
|
|
50fdb15fd2 | ||
|
|
58e9f58a49 | ||
|
|
312593d897 | ||
|
|
7087320cdf | ||
|
|
8ee0e50ea1 | ||
|
|
c504b1dd33 | ||
|
|
a72aa93ad2 | ||
|
|
d844592a65 | ||
|
|
3efb517b77 | ||
|
|
5ad1b1b432 | ||
|
|
ee9a0c1552 | ||
|
|
0877e13cb9 | ||
|
|
aeb905357e | ||
|
|
6aef89c6ef | ||
|
|
9730d31a60 | ||
|
|
c7aba90ee9 | ||
|
|
97fbd58bb7 | ||
|
|
4c5d096db7 | ||
|
|
4c1be4faef | ||
|
|
e7025d035f | ||
|
|
0fda9f97c3 | ||
|
|
ae060729dc | ||
|
|
801304adde | ||
|
|
7e4d5f375d | ||
|
|
8052f3d5a1 | ||
|
|
d98a41a721 | ||
|
|
850401fafa | ||
|
|
b9078063b3 | ||
|
|
36c1c0f007 | ||
|
|
7455e51148 | ||
|
|
4a5ec4bdba | ||
|
|
b50f84c280 | ||
|
|
e850941f40 | ||
|
|
96e900457e | ||
|
|
8238977553 | ||
|
|
b268d98b86 | ||
|
|
649623c39f | ||
|
|
56f707e89b | ||
|
|
48a3e831ea | ||
|
|
c89569aa8e | ||
|
|
cd68e43389 | ||
|
|
663284df87 | ||
|
|
0e2990ee2c | ||
|
|
09ed0163af | ||
|
|
aa7345be2d | ||
|
|
6396b6a690 | ||
|
|
f6619e5fef | ||
|
|
97d9490f2f | ||
|
|
67d094d38e | ||
|
|
dce95dea18 | ||
|
|
fc50a06e7f | ||
|
|
4e97688e9b | ||
|
|
461db14259 | ||
|
|
de5a5b0783 | ||
|
|
d4853707c7 | ||
|
|
9ec8010f10 | ||
|
|
97e94d9e56 | ||
|
|
28fee8eee7 | ||
|
|
ac3d407c35 | ||
|
|
60211d6df6 | ||
|
|
5bb81303c2 | ||
|
|
3a5b45f5ce | ||
|
|
6e76f37552 | ||
|
|
8004011d29 | ||
|
|
74d8d1d90c | ||
|
|
e44198428c | ||
|
|
afea8746e2 | ||
|
|
c559fb74ae | ||
|
|
6a2748b9b3 | ||
|
|
9e2a63b17d | ||
|
|
01b38aeb99 | ||
|
|
49551168af | ||
|
|
80dc4d3853 | ||
|
|
5ec1395b82 | ||
|
|
1c37e79e12 | ||
|
|
139805c606 | ||
|
|
c31dea17e5 | ||
|
|
9b3366c70c | ||
|
|
9883dc0d53 | ||
|
|
3353aed5ff | ||
|
|
82240cb605 | ||
|
|
f300f3e61b | ||
|
|
fee33c4179 | ||
|
|
72b0c94971 | ||
|
|
f87592bfc3 | ||
|
|
a908c43660 | ||
|
|
90a15366b3 | ||
|
|
42f51ecd18 | ||
|
|
0281e206db | ||
|
|
898e32db05 | ||
|
|
078f2183b1 | ||
|
|
985e5e3549 | ||
|
|
478980e64b | ||
|
|
1272891e9c | ||
|
|
93d1d27879 | ||
|
|
a0f5c1987e | ||
|
|
5701b816a1 | ||
|
|
58ec25e3aa | ||
|
|
41334b1ee5 | ||
|
|
f060397402 | ||
|
|
2975473320 | ||
|
|
65cc9922cd | ||
|
|
b8905d7f48 | ||
|
|
b76c294159 | ||
|
|
39270bca4b | ||
|
|
0e599b1748 | ||
|
|
e039516954 | ||
|
|
c5db0e4734 | ||
|
|
b0009596bf | ||
|
|
9568b8333b | ||
|
|
e0d10496c3 | ||
|
|
2fc7d23fb9 | ||
|
|
a90f4b669a | ||
|
|
dc5ec801bf | ||
|
|
abd1772c65 | ||
|
|
6fd42e529c | ||
|
|
7c2aeca5d2 | ||
|
|
6f6101fa77 | ||
|
|
96a5ee4dab | ||
|
|
a6af26b4df | ||
|
|
9922e5a913 | ||
|
|
acd0203177 | ||
|
|
2bafa7cec0 | ||
|
|
66bf126639 | ||
|
|
6e0767bb9d | ||
|
|
a671a66b39 | ||
|
|
0ce7baaa0b | ||
|
|
306f78b762 | ||
|
|
d83cd11aaf | ||
|
|
ea184a452c | ||
|
|
bf55a77079 | ||
|
|
7de69b8cbd | ||
|
|
488a9739f9 | ||
|
|
2e622de648 | ||
|
|
83a4bb919d | ||
|
|
a862124dc5 | ||
|
|
2cc5ea67ba | ||
|
|
3c1bc0b1a8 | ||
|
|
bf68ebba34 | ||
|
|
17091b4b44 | ||
|
|
243d16b7f1 | ||
|
|
d61138cd10 | ||
|
|
7e81c76c82 | ||
|
|
64419fa64e | ||
|
|
62754920cb | ||
|
|
4317bab01a | ||
|
|
565519867d | ||
|
|
e87eed7b8a | ||
|
|
68ae78516d | ||
|
|
bf957be07b | ||
|
|
c8eeb91a20 | ||
|
|
51e182708d | ||
|
|
9a1e7b52e0 | ||
|
|
906f0b4b67 | ||
|
|
40349c08da | ||
|
|
7a27c2eff3 | ||
|
|
a9313b0ff0 | ||
|
|
5a7886520a | ||
|
|
44465a9c76 | ||
|
|
81a96f73c8 | ||
|
|
c7672af5cd | ||
|
|
25220a1309 | ||
|
|
bbb6487b98 | ||
|
|
7452be9b1c | ||
|
|
b496e0b0b0 | ||
|
|
efb054d2f7 | ||
|
|
573aa9f4ca | ||
|
|
b4dd56b65c | ||
|
|
31689ec97e | ||
|
|
2984c1786b | ||
|
|
2dcc4e5896 | ||
|
|
fd91d303b2 | ||
|
|
9cd7e56651 | ||
|
|
4eb1b3e79b | ||
|
|
7e4d663f73 | ||
|
|
9e092eee4a | ||
|
|
d584527187 | ||
|
|
6b89620b3c | ||
|
|
9003eb5c16 | ||
|
|
694d869cd4 | ||
|
|
9fedbcd967 | ||
|
|
3ce772cd80 | ||
|
|
ac1a9281d4 | ||
|
|
d4b1fa93b1 | ||
|
|
450c4ebe0a | ||
|
|
0652199e8a | ||
|
|
a20a7259b5 | ||
|
|
805bad97c3 | ||
|
|
729bdefdbc | ||
|
|
dafe174dc3 | ||
|
|
d3a0a30884 | ||
|
|
0a70b326e7 | ||
|
|
6141c453fd | ||
|
|
d7d52485d7 | ||
|
|
983d301554 | ||
|
|
56b4271295 | ||
|
|
cfb4f8d6dd | ||
|
|
ce64b80fed | ||
|
|
ad0ca149f8 | ||
|
|
ed74711f80 | ||
|
|
a882e89b1c | ||
|
|
83d7534b26 | ||
|
|
99a9a2f966 | ||
|
|
d09aab5743 | ||
|
|
83cee2c4f2 | ||
|
|
9772cabd99 | ||
|
|
f5cf28a589 | ||
|
|
b3432795e5 | ||
|
|
6139bc6b3d | ||
|
|
7b2eff1387 | ||
|
|
8625daa59d | ||
|
|
d2f517b092 | ||
|
|
33bf87a1e4 | ||
|
|
d18efaaefc | ||
|
|
83954a0742 | ||
|
|
c83bf467c0 | ||
|
|
5bb60032c3 | ||
|
|
9081a3da26 | ||
|
|
a4d16224c2 | ||
|
|
2a0cb25a8f | ||
|
|
a06afdde38 | ||
|
|
c2c9dbceed | ||
|
|
52338b9bcb | ||
|
|
95fec69018 | ||
|
|
67a10b0892 | ||
|
|
991f1b8aaa | ||
|
|
ba5366d65e | ||
|
|
65f1d5b980 | ||
|
|
21d40a9fe9 | ||
|
|
790e0a9748 | ||
|
|
0ffa97f9af | ||
|
|
22a48dba60 | ||
|
|
e2671e4380 | ||
|
|
e29d003973 | ||
|
|
2c6e205ac5 | ||
|
|
0ae6682518 | ||
|
|
1721d1b3da | ||
|
|
b581e9cd36 | ||
|
|
02eb6ec94a | ||
|
|
38d3258a44 | ||
|
|
50255bef04 | ||
|
|
cc1f822f5c | ||
|
|
6685e41e14 | ||
|
|
299390f9b4 | ||
|
|
4f8279bc9e | ||
|
|
4f3e5f0e5c | ||
|
|
c591d0d6c1 | ||
|
|
27c5f32e80 | ||
|
|
87fbf45ed9 | ||
|
|
73ef98251e | ||
|
|
0bbbe2f9c2 | ||
|
|
2225c1c962 | ||
|
|
242aa53b7d | ||
|
|
ba56591226 | ||
|
|
2ee94842af | ||
|
|
db4585fa5c | ||
|
|
504e28240a | ||
|
|
ab2f4c33ae | ||
|
|
553afd801d | ||
|
|
4aee7995c5 | ||
|
|
a9e1af8c7f | ||
|
|
54063f2553 | ||
|
|
966bb66b53 | ||
|
|
5b11bca593 | ||
|
|
eadafd34ab | ||
|
|
4dab692e2d | ||
|
|
ef2770a50b | ||
|
|
b0fab79970 | ||
|
|
7b450769bc | ||
|
|
d52d7d9b8e | ||
|
|
8e5f526c2c | ||
|
|
0de5a9bb38 | ||
|
|
a1dc4e124c | ||
|
|
3998022aa0 | ||
|
|
bf2f1ee35f | ||
|
|
7436f088da | ||
|
|
820715bd40 | ||
|
|
bfe2e16a40 | ||
|
|
730bf8b3ce | ||
|
|
2f56db8c06 | ||
|
|
7618577e5e | ||
|
|
c1ff0273ee | ||
|
|
0cd7c0c454 | ||
|
|
c65d0c9b64 | ||
|
|
2df1ba36cc | ||
|
|
71e1de69ad | ||
|
|
e7acd39320 | ||
|
|
0dbb9888d3 | ||
|
|
ce30f437c2 | ||
|
|
8a466882c4 | ||
|
|
e62ff78730 | ||
|
|
e192369bb8 | ||
|
|
fae1d4001c | ||
|
|
726e8ccc56 | ||
|
|
5f6237d03e | ||
|
|
a111e8653a | ||
|
|
2962a29b3f | ||
|
|
4dd75420d6 | ||
|
|
d3592431e7 | ||
|
|
78da717595 | ||
|
|
6aa57fb712 | ||
|
|
4ba7827677 | ||
|
|
9b08c5b16f | ||
|
|
07ab055aca | ||
|
|
7512e06240 | ||
|
|
72e326702a | ||
|
|
9c162d472a | ||
|
|
2e6597048b | ||
|
|
e94b9c44dd | ||
|
|
7d19979b1d | ||
|
|
1db741dc12 | ||
|
|
d50f91fa19 | ||
|
|
bacb2072ef | ||
|
|
88c447d35a | ||
|
|
4a3bd917c6 | ||
|
|
69820d00bd | ||
|
|
3a56e9e0e2 | ||
|
|
6c9740d86c | ||
|
|
2f6484bf82 | ||
|
|
52fbe25154 | ||
|
|
65d397c5f1 | ||
|
|
ebb4bcaf3d | ||
|
|
5bd3f95bfa | ||
|
|
5c57a0aaa1 | ||
|
|
2a6378a687 | ||
|
|
93d2110419 | ||
|
|
a56fe7b6b6 | ||
|
|
fdc395ae54 | ||
|
|
1f21bb17d6 | ||
|
|
9470d037d7 | ||
|
|
d7cc98b1ab | ||
|
|
3ec973d123 | ||
|
|
0a62edafec | ||
|
|
910b68617b | ||
|
|
5905830343 | ||
|
|
220763d47f | ||
|
|
fce6e0f86f | ||
|
|
e681eeff76 | ||
|
|
4fc0363a62 | ||
|
|
0107483c82 | ||
|
|
b807346714 | ||
|
|
58c72b24c3 | ||
|
|
3ce5d4d871 | ||
|
|
5c6f5b019d | ||
|
|
22fc554ea1 | ||
|
|
3c59597309 | ||
|
|
ae4dd1f536 | ||
|
|
783b92aaed | ||
|
|
8364d000f5 | ||
|
|
1ed32ff356 | ||
|
|
ed811d031c | ||
|
|
19dbab3a05 | ||
|
|
40aa93c46b | ||
|
|
f859b9dd74 | ||
|
|
04f3c36201 | ||
|
|
f1351dbf49 | ||
|
|
99bc74cb73 | ||
|
|
0f37d06b99 | ||
|
|
bb6a0e6692 | ||
|
|
2ed9758cbb | ||
|
|
babcdb10d3 | ||
|
|
4a24ea8b11 | ||
|
|
63e850e824 | ||
|
|
96615927cd | ||
|
|
cf8580dce9 | ||
|
|
f7b6c3582f | ||
|
|
6e766720ca | ||
|
|
b1777f74ce | ||
|
|
42bded9656 | ||
|
|
96a146c5e1 | ||
|
|
9570339569 | ||
|
|
6b5c225676 | ||
|
|
47f12cbb94 | ||
|
|
cad84328c4 | ||
|
|
8ac98c973a | ||
|
|
841c644d57 | ||
|
|
d25f9a6a17 | ||
|
|
ea761a76b9 | ||
|
|
374e1a477d | ||
|
|
da29a1f403 | ||
|
|
a8858f2375 | ||
|
|
539570e079 | ||
|
|
d5f4d229ff | ||
|
|
601fcb525d | ||
|
|
de4cf5c174 | ||
|
|
9f73582091 | ||
|
|
6fdbaa2ad1 | ||
|
|
91dea21a2f | ||
|
|
914631c72c | ||
|
|
edd521f2fd | ||
|
|
9baea196d8 | ||
|
|
25de25e3b0 | ||
|
|
ad694e77a4 | ||
|
|
ee5e48eae5 | ||
|
|
89e414225f | ||
|
|
1ca7ee8a5b | ||
|
|
c0cac635f4 | ||
|
|
465a6f87f2 | ||
|
|
1f9235d56e | ||
|
|
56e553ae66 | ||
|
|
6c4d97e860 | ||
|
|
f51a7cd621 | ||
|
|
89d08a449e | ||
|
|
4079ec379c | ||
|
|
3aacd5412e | ||
|
|
c986184fbf | ||
|
|
6985075187 | ||
|
|
c86eeea550 | ||
|
|
03cd0dc0db | ||
|
|
e492e2e10c | ||
|
|
2c33358db7 | ||
|
|
546e5d7a09 | ||
|
|
6982e65e0c | ||
|
|
c2b03c461a | ||
|
|
f051ab76a0 | ||
|
|
57cbe02d7d | ||
|
|
60763c5120 | ||
|
|
3de3a3770c | ||
|
|
f07a628469 | ||
|
|
55947770cd | ||
|
|
346855955c | ||
|
|
b9efb9db60 | ||
|
|
99d13ed283 | ||
|
|
615595c0d3 | ||
|
|
194c220ad2 | ||
|
|
282e05340f | ||
|
|
a03966c209 | ||
|
|
56ba5fbaaa | ||
|
|
da9ffb03bf | ||
|
|
5d0d22c5f8 | ||
|
|
b77de37029 | ||
|
|
01896b7f9a | ||
|
|
3bf8f3a044 | ||
|
|
4408fd664e | ||
|
|
ed1f63c9a3 | ||
|
|
d1d3957310 | ||
|
|
26cb3bdaaf | ||
|
|
5431902f8d | ||
|
|
011690ce8b | ||
|
|
e5f50002da | ||
|
|
1958622d8c | ||
|
|
95561e2568 | ||
|
|
774789c607 | ||
|
|
06d0bbcbba | ||
|
|
9dd998714a | ||
|
|
15570be82d | ||
|
|
dd9362352e | ||
|
|
dee59f5f4c | ||
|
|
32a54e395d | ||
|
|
354c6a7261 | ||
|
|
37d8c9ec85 | ||
|
|
16639c7bed | ||
|
|
7f325513fb | ||
|
|
89d3ae6362 | ||
|
|
ed05ab1551 | ||
|
|
fe5e15f206 | ||
|
|
21bc256478 | ||
|
|
4155fa4e36 | ||
|
|
f096ae07bf | ||
|
|
af20a57e8a | ||
|
|
d63007ac9f | ||
|
|
7e33d9c7ca | ||
|
|
b38c96ae2f | ||
|
|
05b703068a | ||
|
|
6df1e8b072 | ||
|
|
dc9983e45b | ||
|
|
45a8dc446e | ||
|
|
1ae8aafd5a | ||
|
|
47022cdeeb | ||
|
|
7c3f3255b0 | ||
|
|
4b956e594f | ||
|
|
86900188d8 | ||
|
|
708a728b85 | ||
|
|
de07378bcb | ||
|
|
7916f7c307 | ||
|
|
a6ac91331e | ||
|
|
ff0a7d7922 | ||
|
|
6464e6344d | ||
|
|
9f11012252 | ||
|
|
8f611958a3 | ||
|
|
6d928fa7ec | ||
|
|
64e4018640 | ||
|
|
592bebe823 | ||
|
|
089aab7930 | ||
|
|
58cfbef65e | ||
|
|
e88cc5ff24 | ||
|
|
08d7fc6797 | ||
|
|
41e9e45fe7 | ||
|
|
af2f5ba68e | ||
|
|
cd6b5a37fd | ||
|
|
a66a084a84 | ||
|
|
3cd90ded8b | ||
|
|
5cc533061e | ||
|
|
2377494231 | ||
|
|
b9bb56085e | ||
|
|
2364f770f2 | ||
|
|
2dd5894283 | ||
|
|
51b7fce129 | ||
|
|
3c715d9ef8 | ||
|
|
1201fc120a | ||
|
|
9f3f4f7d60 | ||
|
|
4f331f3f83 | ||
|
|
2982a82999 | ||
|
|
0483c2fe6e | ||
|
|
12440fe1fe | ||
|
|
9482fb2302 | ||
|
|
39bdce3380 | ||
|
|
8479a64a6b | ||
|
|
fc043c96a6 | ||
|
|
5bb16790d3 | ||
|
|
71c0edf0e8 | ||
|
|
73afdc7a98 | ||
|
|
737103239c | ||
|
|
cbb9be2074 | ||
|
|
1146bb057d | ||
|
|
ad98211cb9 | ||
|
|
c5a8db7537 | ||
|
|
88c3fe71ad | ||
|
|
fd87bc4fbc | ||
|
|
57eeb6926c | ||
|
|
a0b68fbdd0 | ||
|
|
c34b6358dc | ||
|
|
b09117bb87 | ||
|
|
758097445e | ||
|
|
a153a6e276 | ||
|
|
64d1e0eff3 | ||
|
|
3a66d5ac2e | ||
|
|
3d32bd2fbc | ||
|
|
473ea31f4d | ||
|
|
5521c47ebc | ||
|
|
b68009408c | ||
|
|
5f9d50c154 | ||
|
|
42533a9d0e | ||
|
|
cf5288e913 | ||
|
|
71d10d2cf2 | ||
|
|
ab6924878a | ||
|
|
d0c9c98031 | ||
|
|
94e8ba93cb | ||
|
|
bbea7ad9ff | ||
|
|
b5ee9a8980 | ||
|
|
00db0b5854 | ||
|
|
28036a3702 | ||
|
|
da5ba07365 | ||
|
|
4c44cd498a | ||
|
|
a7c880caf9 | ||
|
|
a04174cfda | ||
|
|
2d92776c3a | ||
|
|
a393a76920 | ||
|
|
ccfbefddd1 | ||
|
|
5c6cd281c4 | ||
|
|
6841ac7783 | ||
|
|
76791e5e0e | ||
|
|
0ac50320e1 | ||
|
|
ac74a5fede | ||
|
|
bb751edc0a | ||
|
|
d9c315fc28 | ||
|
|
b358895089 | ||
|
|
9ba0eeb090 | ||
|
|
eeb213bb76 | ||
|
|
b011ce4b87 | ||
|
|
c232ec9534 | ||
|
|
def50e48d5 | ||
|
|
cdd9d4bac4 | ||
|
|
32248e9e36 | ||
|
|
0ea02db27f | ||
|
|
3344acf6db | ||
|
|
20f2f7a2ad | ||
|
|
43a731b05e | ||
|
|
22d77a7c95 | ||
|
|
63d6b3257f | ||
|
|
90a0e367fa | ||
|
|
fd4bfbe920 | ||
|
|
c6389a50bc | ||
|
|
1b4e220b52 | ||
|
|
c983e88a0b | ||
|
|
cc71a9d8b6 | ||
|
|
db9e412495 | ||
|
|
0bebea4a0e | ||
|
|
adabd2b588 | ||
|
|
9b12fbacdc | ||
|
|
aa9f5e9dad | ||
|
|
208f544e36 | ||
|
|
cd3bb6a3f2 | ||
|
|
66b746f4d2 | ||
|
|
c6840a88d4 | ||
|
|
5fc8f0610c | ||
|
|
31eb0ea2b7 | ||
|
|
b9e199f08d | ||
|
|
053dbda1e2 | ||
|
|
bae724525d | ||
|
|
c6c3084a07 | ||
|
|
beb8303884 | ||
|
|
1efcb1e0d5 | ||
|
|
49547edf42 | ||
|
|
46961eb43d | ||
|
|
a70151d1f1 | ||
|
|
a3ecab05f0 | ||
|
|
493749a86d | ||
|
|
52f986ad0d | ||
|
|
86155ea998 | ||
|
|
202c290e1f | ||
|
|
e00d8abe0e | ||
|
|
e8ab38fda0 | ||
|
|
376b19fc06 | ||
|
|
514656f296 | ||
|
|
0c4a69ba4a | ||
|
|
c0a284a1ac | ||
|
|
fd45d9416a | ||
|
|
dbc776292a | ||
|
|
90d91f731f | ||
|
|
bb5d9aca1c | ||
|
|
3228cf5e12 | ||
|
|
c319b82462 | ||
|
|
f9fd557a27 | ||
|
|
03a3e24d8b | ||
|
|
3f29bed4ad | ||
|
|
635cd41fbe | ||
|
|
28aee28c12 | ||
|
|
535829e791 | ||
|
|
99f84354ad | ||
|
|
4557780803 | ||
|
|
86a3aeef09 | ||
|
|
9b8263815b | ||
|
|
1850dfe418 | ||
|
|
18b3e0cfdf | ||
|
|
6524be41e1 | ||
|
|
c37c07efc0 | ||
|
|
343c7f7092 | ||
|
|
ca2d3eb2b7 | ||
|
|
6eadf185d2 | ||
|
|
5fc4592369 | ||
|
|
ea0fb8efcc | ||
|
|
f74f93da1c | ||
|
|
c7224d4838 | ||
|
|
f86f6aa3ea | ||
|
|
3a6b201ae0 | ||
|
|
13ba34d0b3 | ||
|
|
7e302e7665 | ||
|
|
4ce9909b4e | ||
|
|
d9352990c7 | ||
|
|
952db53020 | ||
|
|
59f29166ea | ||
|
|
ec72a3c832 | ||
|
|
4e9ce2628f | ||
|
|
6cb4dac63b | ||
|
|
196744b976 | ||
|
|
a2850f63ee | ||
|
|
931f195347 | ||
|
|
91d0ab9e67 | ||
|
|
a5dcdca16e | ||
|
|
c80cf6715a | ||
|
|
b2d2d48894 | ||
|
|
65a3dcad7b | ||
|
|
0e8a469db1 | ||
|
|
e87bb21475 | ||
|
|
6e7afc677a | ||
|
|
990faf3092 | ||
|
|
9004b204ed | ||
|
|
23bd68c761 | ||
|
|
c7b34146af | ||
|
|
b0169fac5e | ||
|
|
93f3a2d372 | ||
|
|
a6eb7ad539 | ||
|
|
9b0e4d4a8c | ||
|
|
4425d17d29 | ||
|
|
c9f0613dd3 | ||
|
|
24faba4af1 | ||
|
|
9fadaa3a55 | ||
|
|
9c507e5a47 | ||
|
|
159eff2a1e | ||
|
|
489aff2540 | ||
|
|
740e3c7eec | ||
|
|
0009f9b034 | ||
|
|
6fd6581324 | ||
|
|
b24f5102c0 | ||
|
|
cbe6a2d7db | ||
|
|
9a9fbacde1 | ||
|
|
04c06bbba4 | ||
|
|
de8a8636b4 | ||
|
|
cf70aebc7e | ||
|
|
6ada19f32c | ||
|
|
6c0c0118ff | ||
|
|
9f3b0b8059 | ||
|
|
f90fe671ff | ||
|
|
ae55dd9514 | ||
|
|
32a6d5f259 | ||
|
|
4605b00206 | ||
|
|
ea9249a62a | ||
|
|
5d56da645a | ||
|
|
4f79e3d719 | ||
|
|
730b81a9d9 | ||
|
|
b0be23f618 | ||
|
|
6325246417 | ||
|
|
cbb1a128fe | ||
|
|
29af59d8f3 | ||
|
|
f0fdba9ee5 | ||
|
|
d59e5403ed | ||
|
|
d5b7ab92a0 | ||
|
|
d4280bb399 | ||
|
|
efee5a8bce | ||
|
|
cc3300e0a3 | ||
|
|
3f9b37c28b | ||
|
|
8d2637ea72 | ||
|
|
91a61537bb | ||
|
|
e6d8b1dbdb | ||
|
|
1c709b582e | ||
|
|
8349bad20f | ||
|
|
203bafb79e | ||
|
|
c10819df72 | ||
|
|
12bfb9274f | ||
|
|
86ab91e082 | ||
|
|
d8767823b9 | ||
|
|
0025cb81db | ||
|
|
e24c87c76f | ||
|
|
717b5a3758 | ||
|
|
266ac9cbc1 | ||
|
|
7658d602e2 | ||
|
|
c1860ee33d | ||
|
|
9555aeff18 | ||
|
|
08be337ae0 | ||
|
|
62758a7285 | ||
|
|
44fe6d4cdf | ||
|
|
00c0930d0a | ||
|
|
9c4bdd76ff | ||
|
|
811a86af0b | ||
|
|
31399665f9 | ||
|
|
98b5aef641 | ||
|
|
dfa3661a13 | ||
|
|
46c9d5486f | ||
|
|
aacff3c1ce | ||
|
|
61b169392c | ||
|
|
9dcdc2542a | ||
|
|
ef11ef421b | ||
|
|
27e000c9a9 | ||
|
|
4ac33dcef0 | ||
|
|
44dae5d64a | ||
|
|
540e664b19 | ||
|
|
b230e2674e | ||
|
|
2b1c0f84bb | ||
|
|
ae59d9fd57 | ||
|
|
57c7114c37 | ||
|
|
6c61dfff11 | ||
|
|
720f851343 | ||
|
|
8c937995fa | ||
|
|
795074d112 | ||
|
|
77f0c504df | ||
|
|
e2ebba0aca | ||
|
|
357887ec9e | ||
|
|
08d1989e6e | ||
|
|
e232d20c46 | ||
|
|
014c15e793 | ||
|
|
c0ea0b8fd8 | ||
|
|
321cbe50be | ||
|
|
093757f635 | ||
|
|
b65fed7b00 |
@@ -1,56 +0,0 @@
|
||||
# API Conventions
|
||||
|
||||
## HTTP Handlers (Actix-Web)
|
||||
|
||||
Handlers in `storyteller_web` follow this structure:
|
||||
|
||||
1. **Request/Response types** with `#[derive(Deserialize/Serialize, ToSchema)]`
|
||||
2. **Error enum** implementing `ResponseError`, `Display`, `Serialize`, `ToSchema`
|
||||
3. **Handler function** annotated with `#[utoipa::path(...)]` for OpenAPI docs
|
||||
4. Handler returns `Result<Json<Response>, ErrorType>`. Only use `Result<HttpResponse, ErrorType>` when you need to set headers/cookies.
|
||||
|
||||
### Error Types
|
||||
|
||||
- Use `CommonWebError` for simple handlers that only need standard HTTP error codes
|
||||
- Use a custom error enum when you need endpoint-specific error variants (e.g. `LoginErrorType::InvalidCredentials`)
|
||||
- Custom error enums MUST implement `ResponseError` (actix), `Display`, and `Serialize`
|
||||
- When adding `From<RequireUserSessionError>` or similar conversions, implement them on `CommonWebError` in its own module (orphan rule)
|
||||
- Error response bodies include: `success: false`, `error_code`, `error_code_str`
|
||||
|
||||
### Session/Auth
|
||||
|
||||
Helpers live in `storyteller_web` under `http_server/web_utils/user_session/`. Each takes a
|
||||
generic `mysql_executor: E` (any `sqlx::Executor`), so whether it reuses a connection or acquires
|
||||
its own depends on the **argument you pass**:
|
||||
|
||||
- `require_user_session(http_request, session_checker, mysql_executor)` — require a logged-in user.
|
||||
- `require_user_session_extended(...)` — same, with extra session detail.
|
||||
- `require_moderator(http_request, session_checker, mysql_executor)` — admin-only endpoints.
|
||||
|
||||
PREFER passing an already-open connection (`&mut *conn`) so the helper reuses it. Passing
|
||||
`&server_state.mysql_pool` makes it acquire a fresh connection — avoid that when the handler
|
||||
already holds one (the double-acquire adds pool pressure; this crate has had pool-timeout
|
||||
incidents). There is no `*_using_connection` variant — the reuse/acquire choice is the argument.
|
||||
|
||||
### API Type Definitions
|
||||
|
||||
- Shared request/response types go in `artcraft_api_defs` crate (under `crates/api_clients/artcraft/artcraft_api_defs/`)
|
||||
- Types that need `ResponseError` impl must stay in `storyteller_web` (orphan rule)
|
||||
- New types must be added to `api_doc.rs` schemas (in storyteller-web) for Swagger generation
|
||||
|
||||
## Enums (Database-backed)
|
||||
|
||||
- Located in `crates/schema/public/enums/src/by_table/{table_name}/`
|
||||
- Require manual `to_str()` and `from_str()` implementations
|
||||
- Use `impl_enum_display_and_debug_using_to_str!` and `impl_mysql_enum_coders!` macros
|
||||
- Add comprehensive tests: serialization, round-trip, variant count
|
||||
- YOU CAN ADD NEW VALUES but DO NOT CHANGE EXISTING VALUES without a migration strategy
|
||||
- Note that adding new values can break older versions of the Tauri desktop app that read
|
||||
responses over Rust - make sure it consumes variants with an "Unknown(String)" variant
|
||||
catch-all to future proof the APIs.
|
||||
|
||||
## Tokens (Primary Keys)
|
||||
|
||||
- Located in `crates/schema/public/tokens/src/tokens/`
|
||||
- Opaque `String` wrappers with Stripe-like prefixes (e.g. `user_`, `mf_`)
|
||||
- Use `impl_string_token!`, `impl_mysql_token_from_row!`, `impl_crockford_generator!` macros
|
||||
@@ -1,92 +0,0 @@
|
||||
# Code Style
|
||||
|
||||
## Rust
|
||||
|
||||
- Two spaces for indentation (not four)
|
||||
- No minimum supported Rust version; use latest stable features freely
|
||||
- `#[macro_use] extern crate serde_derive;` is used in binary crates; library crates use `use serde_derive::{Serialize, Deserialize};`
|
||||
- Never use `println!` or `eprintln!` outside of tests; use `log` crate macros (`info!`, `warn!`, `error!`, `debug!`, `trace!`) instead
|
||||
- Use `anyhow::Result` / `AnyhowResult` for fallible functions in application code; new database queries should use `Result<T, sqlx::Error>` instead
|
||||
- SQLx: MySQL queries belong in the `mysql_queries` crate. Do NOT write `sqlx::query!` /
|
||||
`sqlx::query()` in application/service crates (e.g. `storyteller-web`, which has zero direct
|
||||
SQLx queries) — add or reuse a function in `mysql_queries` and call it. The compile-time
|
||||
`query!` / `query_as!` macro conventions for that crate are documented in
|
||||
`crates/schema/database/mysql_queries/CLAUDE.md`.
|
||||
- Enum Display/Debug: use `impl_enum_display_and_debug_using_to_str!` macro, not `derive_more::Display` (CLion doesn't understand it)
|
||||
- Fields that are optional use the `maybe_` prefix: `maybe_creator_user_token`, `maybe_prompt_token`
|
||||
- When implementing `fmt::Display` for error types, use the pattern: `write!(f, "{:?}", self)`
|
||||
|
||||
## File Layout (Top-to-Bottom Reading Order)
|
||||
|
||||
Organize code so a human reading top-to-bottom encounters the most important things first and
|
||||
can drill down into details only when curious. Think of it like a newspaper: headline first,
|
||||
then the story, then the fine print.
|
||||
|
||||
### Ordering within a file
|
||||
|
||||
1. **Constants** at the top, right after imports.
|
||||
2. **The primary public type** (the main struct/enum the file is about) comes next.
|
||||
A reader opening this file should immediately see what it defines.
|
||||
3. **Supporting/subordinate types** (sub-structs, helper enums, serialization DTOs) go
|
||||
below the primary type, not above it. Ask: "Would a reader need to know this type
|
||||
exists before seeing the main type?" — if no, push it down.
|
||||
4. **Type ordering for API types**: Request, then Response (success), then Error.
|
||||
5. **`impl` blocks** after their type definitions.
|
||||
|
||||
### Ordering within an `impl` block
|
||||
|
||||
1. **Constructors** (`new`, `from_*`, `builder`) at the top.
|
||||
2. **Public methods** next — these are the main entry points a reader cares about.
|
||||
3. **Private helper methods** below public methods.
|
||||
- Among helpers: place "meaty" helpers (substantial logic, direct continuations of the
|
||||
public entry point) above "leaf" helpers (small formatters, simple lookups).
|
||||
- A method that calls another method in the same file should appear *above* the method
|
||||
it calls. This lets readers follow the call chain downward.
|
||||
|
||||
### Ordering within test modules
|
||||
|
||||
Put test cases first, helpers last. A reader opening a test file should immediately see
|
||||
*what* is being tested before *how* the test machinery works.
|
||||
|
||||
1. **Imports**
|
||||
2. **Constants** (test IDs, URLs, fixture data) — small context that tests reference
|
||||
3. **Test functions** or **sub-modules** grouped by category
|
||||
4. **Helper functions** (builders, pipeline runners, assertions)
|
||||
|
||||
Group related tests into sub-modules (`mod pricing_tests { ... }`) when a category has 2+
|
||||
tests. Don't create a sub-module for a single test. Keep nesting to two levels max
|
||||
(`mod tests { mod category { ... } }`). Shared helpers go in the parent module so
|
||||
sub-modules can reach them via `use super::*`.
|
||||
|
||||
## Naming
|
||||
|
||||
- Handler functions: `{verb}_{noun}_handler` (e.g. `get_health_check_handler`, `login_handler`)
|
||||
- Error enums: `{Feature}Error` (e.g. `FalWebhookError`, `HealthCheckError`)
|
||||
- Request/response structs: `{Action}Request`, `{Action}Response`, `{Action}SuccessResponse`
|
||||
- Path params: `{Action}PathInfo`
|
||||
- Builder structs: `{Thing}Builder` with `set_` prefixed setters and a `build()` method
|
||||
|
||||
## Imports
|
||||
|
||||
- Group imports: std, external crates, internal workspace crates, `crate::` imports
|
||||
- Prefer `use` imports over inline fully-qualified paths. Inline qualification is acceptable
|
||||
only for genuine one-off references where adding an import would be noise, or for types
|
||||
that collide with std prelude names (`Result`, `Option`, `Error`). If you reference a symbol
|
||||
more than once, always import it.
|
||||
- Prefer specific imports over wildcards, except in `api_doc.rs` and test modules
|
||||
- When two crates export a type with the same name, alias with a suffix describing the source crate:
|
||||
`use enums::...::CommonResolution as CommonResolutionEnum;`
|
||||
`use artcraft_router::...::CommonResolution as CommonResolutionRouter;`
|
||||
This avoids long fully-qualified paths in function signatures and bodies.
|
||||
|
||||
## MySQL
|
||||
|
||||
- MySQL tables: primary key `id` columns must be `BIGINT(20) UNSIGNED`. Do not use signed IDs.
|
||||
- Always create tables with `ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin`.
|
||||
|
||||
## TypeScript / Frontend
|
||||
|
||||
- TypeScript with React, Vite, Zustand, Three.js
|
||||
- Nx monorepo under `crates/frontend/`
|
||||
- Two spaces for indentation
|
||||
|
||||
@@ -1,73 +0,0 @@
|
||||
# Testing
|
||||
|
||||
## Database Fixture Tests
|
||||
|
||||
- Live in-crate (e.g. `storyteller-web`'s omni_gen video pricing tests) and
|
||||
use the `mysql_testing` crate (`crates/schema/database/mysql_testing`) for
|
||||
guarded pools, schema setup, and account/session/wallet fixtures
|
||||
- They RUN BY DEFAULT with `cargo test -p storyteller-web` and from the IDE
|
||||
(requires a local MySQL with an `artcraft_test` database). Machines/CI
|
||||
without MySQL skip them via the off switch:
|
||||
`SQLX_OFFLINE=true cargo test -p storyteller-web --features skip_database_tests`
|
||||
- They connect ONLY via `ARTCRAFT_TEST_DATABASE_URL` (default
|
||||
`mysql://root:@localhost:3306/artcraft_test`); the guard panics on any
|
||||
database whose name lacks "test", on `storyteller`/`artcraft`, and on
|
||||
cloud hosts — see `mysql_testing/README.md`
|
||||
- Database tests run in PARALLEL: each test creates its own users, wallets,
|
||||
and rows; schema setup single-flights via a MySQL named lock. Only tests
|
||||
mutating shared/global state take
|
||||
`mysql_testing::serial::acquire_serial_test_lock().await`
|
||||
- External providers must never be called: the harness stubs Kinovi with an
|
||||
in-process HTTP server via `KINOVI_CUSTOM_API_HOST`/`KINOVI_CUSTOM_CDN_HOST`
|
||||
|
||||
## Running Tests
|
||||
|
||||
- Most crates: `cargo test -p {crate_name}`
|
||||
- Crates using SQLx offline mode: `SQLX_OFFLINE=true cargo test -p {crate_name}`
|
||||
- The `storyteller-web` and `mysql_queries` crates require `SQLX_OFFLINE=true` for compilation
|
||||
- Use `cargo check -p {crate_name}` for fast compilation checks without running tests
|
||||
|
||||
## Test Patterns
|
||||
|
||||
- Tests live in `#[cfg(test)] mod tests { ... }` at the bottom of each file
|
||||
- Group related tests into sub-modules by category (e.g. `mod pricing_tests`, `mod resolution_tests`)
|
||||
- Only create a sub-module when it has 2+ tests — don't wrap a single test in its own module
|
||||
- Keep nesting shallow: `mod tests { mod category { ... } }` is fine; a third level is rarely needed
|
||||
- Shared helpers used by multiple sub-modules belong in the parent `mod tests`, not duplicated in each sub-module
|
||||
- Enum tests should cover: serialization, deserialization, `to_str`/`from_str` round-trip, variant count
|
||||
- Password/crypto tests: be mindful of computation cost, don't add too many bcrypt test cases
|
||||
|
||||
## Test File Layout
|
||||
|
||||
Put test cases first, helpers last. Engineers should see the actual tests before scrolling
|
||||
to understand helper plumbing.
|
||||
|
||||
Within a `mod tests { ... }` block:
|
||||
|
||||
1. **Imports** at the top
|
||||
2. **Constants** (test data IDs, URLs, etc.) — small context that tests reference
|
||||
3. **Test functions** (`#[test]` / `#[tokio::test]`) grouped by category
|
||||
4. **Helper functions** (builders, pipeline runners, etc.) at the very bottom
|
||||
|
||||
If tests are grouped into sub-modules, the same rule applies within each sub-module:
|
||||
constants first, then tests, then helpers at the end of that sub-module.
|
||||
|
||||
## What to Test
|
||||
|
||||
- New error types and their mappings
|
||||
- Enum variant serialization stability (these are stored in the database)
|
||||
- Hash/deduplication functions with known inputs
|
||||
- Edge cases in parsers (empty input, missing fields, malformed data)
|
||||
|
||||
## Imports in Tests
|
||||
|
||||
- Import test fixtures and constants with `use`, not inline fully-qualified paths.
|
||||
Write `use test_data::web::image_urls::JUNO_AT_LAKE_IMAGE_URL;` at the top,
|
||||
then reference `JUNO_AT_LAKE_IMAGE_URL` in test bodies.
|
||||
- Same rule applies to enum variants used in match arms — import them, don't write
|
||||
`crate::some::deep::module::MyEnum::Variant` inline.
|
||||
|
||||
## What Not to Test
|
||||
|
||||
- Don't add integration tests that require a running database unless explicitly asked
|
||||
- Don't add tests for simple struct construction or trivial getters
|
||||
@@ -1,6 +0,0 @@
|
||||
# yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json
|
||||
reviews:
|
||||
auto_review:
|
||||
# Dependabot bumps are checked by CI; skip CodeRabbit reviews on them.
|
||||
ignore_usernames:
|
||||
- "dependabot[bot]"
|
||||
@@ -1,14 +0,0 @@
|
||||
hakari-package = "workspace-build-acceleration"
|
||||
|
||||
resolver = "1"
|
||||
|
||||
dep-format-version = "4"
|
||||
workspace-hack-line-style = "workspace-dotted"
|
||||
|
||||
platforms = [
|
||||
"x86_64-unknown-linux-gnu",
|
||||
"x86_64-apple-darwin",
|
||||
"x86_64-pc-windows-msvc",
|
||||
]
|
||||
|
||||
exact-versions = true
|
||||
@@ -0,0 +1,16 @@
|
||||
*secret*sh
|
||||
*~
|
||||
.dockerignore
|
||||
.env
|
||||
.env-secrets
|
||||
.git
|
||||
.idea/
|
||||
Dockerfile
|
||||
__pycache__/
|
||||
_ignored_schema.rs
|
||||
models/Wav2Lip/python/
|
||||
models/tacotron2/python/
|
||||
production_database_secret_connect.sh
|
||||
secrets.toml
|
||||
secrets.yaml
|
||||
target/
|
||||
@@ -0,0 +1,77 @@
|
||||
SERVER_ENVIRONMENT=development
|
||||
|
||||
DATABASE_URL=mysql://storyteller:password@localhost/storyteller
|
||||
#DATABASE_URL=mysql://root:root@localhost/storyteller
|
||||
#DATABASE_URL=mysql://root@localhost/storyteller
|
||||
|
||||
# Diesel-cli reads this for the migration directory to use.
|
||||
# We've renamed it so the migrations are sorted near the top of the project directory tree
|
||||
# (since they kind of serve as documentation)
|
||||
MIGRATION_DIRECTORY=_sql/migrations/
|
||||
|
||||
# SQLX normally requires a live database to reflect upon at build time.
|
||||
# This setting turns that on and requires a `sql-data.json` cache file instead.
|
||||
# See https://github.com/launchbadge/sqlx/tree/master/sqlx-cli#readme
|
||||
#SQLX_OFFLINE=true
|
||||
|
||||
# NB(bt, 2023-03-20):
|
||||
# These are used in storyteller-web, image-generation-service, and websocket-gateway.
|
||||
# I'm moving them to env configs that live under storyteller-web/.env.
|
||||
# This will break the other services for now.
|
||||
# # We can't serve HTTPS locally without self-signed certificate nonsense.
|
||||
# COOKIE_SECURE=false
|
||||
# COOKIE_DOMAIN=.jungle.horse
|
||||
# WEBSITE_HOMEPAGE_REDIRECT=http://jungle.horse
|
||||
|
||||
# Bucket config (non-secret)
|
||||
REGION_NAME=us-east1
|
||||
|
||||
TTS_DOWNLOAD_BUCKET_NAME=dev-vocodes-private-uploads
|
||||
TTS_DOWNLOAD_BUCKET_ROOT=/user_uploaded_tts_models
|
||||
|
||||
W2L_PRIVATE_DOWNLOAD_BUCKET_NAME=dev-vocodes-private-uploads
|
||||
W2L_PUBLIC_DOWNLOAD_BUCKET_NAME=dev-vocodes-public
|
||||
|
||||
PRIVATE_BUCKET_NAME=dev-vocodes-private-uploads
|
||||
PUBLIC_BUCKET_NAME=dev-vocodes-public
|
||||
|
||||
# Bucket roots
|
||||
AUDIO_UPLOADS_BUCKET_ROOT=/user_uploaded_w2l_audio
|
||||
W2L_DOWNLOAD_BUCKET_ROOT=/user_uploaded_w2l_templates
|
||||
W2L_INFERENCE_OUTPUT_BUCKET_ROOT=/w2l_output
|
||||
|
||||
W2L_CODE_DIRECTORY=/home/bt/dev/storyteller/storyteller-web/models/Wav2Lip
|
||||
W2L_INFERENCE_SCRIPT_NAME=vocodes_process_inference.py
|
||||
W2L_MODEL_FILENAME=wav2lip_gan.pth
|
||||
|
||||
#W2L_MODEL_CHECKPOINT=/home/bt/models/wav2lip/s3fd-619a316812.pth
|
||||
W2L_MODEL_CHECKPOINT=/home/bt/models/wav2lip/wav2lip_gan.pth
|
||||
W2L_END_BUMP_FILENAME=vocodes-short-end-bump.mp4
|
||||
|
||||
W2L_DOWNLOAD_SCRIPT_NAME=vocodes_process_upload.py
|
||||
|
||||
TTS_CODE_DIRECTORY=/home/bt/dev/storyteller/storyteller-ml/tts
|
||||
TTS_INFERENCE_SCRIPT_NAME=vocodes_inference.py
|
||||
|
||||
TACOTRON_ROOT_CODE_DIRECTORY=/home/bt/dev/storyteller/storyteller-ml/tts
|
||||
TACOTRON_VIRTUAL_ENV_ACTIVATION_COMMAND="source python/bin/activate"
|
||||
TACOTRON_MODEL_CHECK_SCRIPT_NAME="vocodes_model_check_tacotron.py"
|
||||
|
||||
TALKNET_ROOT_CODE_DIRECTORY=/home/bt/dev/storyteller/storyteller-ml/tts
|
||||
TALKNET_MODEL_CHECK_SCRIPT_NAME="vocodes_model_check_talknet.py"
|
||||
|
||||
HIFIGAN_ROOT_CODE_DIRECTORY=/home/bt/dev/storyteller/storyteller-ml/tts
|
||||
HIFIGAN_VIRTUAL_ENV_ACTIVATION_COMMAND="source python/bin/activate"
|
||||
HIFIGAN_MODEL_CHECK_SCRIPT_NAME="vocodes_model_check_hifigan.py"
|
||||
|
||||
TTS_INFERENCE_SIDECAR_HOSTNAME=localhost:8000
|
||||
|
||||
#TTS_WAVEGLOW_VOCODER_MODEL_FILENAME=waveglow_256channels_universal_v5.pt # THIS SOUNDS HORRID
|
||||
#TTS_WAVEGLOW_VOCODER_MODEL_FILENAME=waveglow_256channels_v4_uberduck.pt # THIS IS TOO SMALL! 300MB vs 1.2 GB!
|
||||
TTS_WAVEGLOW_VOCODER_MODEL_FILENAME=waveglow_256channels_v4.pt
|
||||
|
||||
TTS_HIFIGAN_VOCODER_MODEL_FILENAME=hifigan_universal_g_02500000.pt
|
||||
TTS_HIFIGAN_SUPERRES_VOCODER_MODEL_FILENAME=hifigan_superres_twilight_33000.pt
|
||||
|
||||
# Development key. Permissible to check in.
|
||||
NEWRELIC_API_KEY=c402be1c4c4216ec83d0cfc0851520cfFFFFNRAL
|
||||
+2
-3
@@ -1,6 +1,5 @@
|
||||
# Ignore certain code for Github language statistics
|
||||
# https://github.com/github/linguist/blob/master/docs/overrides.md
|
||||
old_code/trumpet-trumped.com/dictionary/cmudict-0.7b linguist-vendored
|
||||
old_code/voder-vocodes-1.0/frontends/admin/node_modules/** linguist-vendored
|
||||
test_data/html_scraping/** linguist-vendored
|
||||
windows/sqlite/** linguist-vendored
|
||||
old_code/voder-vocodes-1.0/frontends/admin/node_modules linguist-vendored
|
||||
windows/sqlite linguist-vendored
|
||||
|
||||
@@ -1,30 +0,0 @@
|
||||
version: 2
|
||||
updates:
|
||||
# Maintain Rust / Cargo dependencies
|
||||
- package-ecosystem: "cargo"
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: "daily"
|
||||
time: "06:00"
|
||||
timezone: "America/New_York"
|
||||
# Consolidate updates into a single PR rather than one PR per crate
|
||||
groups:
|
||||
cargo-dependencies:
|
||||
patterns:
|
||||
- "*"
|
||||
commit-message:
|
||||
prefix: "chore(deps)"
|
||||
|
||||
# Maintain GitHub Actions workflows
|
||||
- package-ecosystem: "github-actions"
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: "daily"
|
||||
time: "06:00"
|
||||
timezone: "America/New_York"
|
||||
groups:
|
||||
actions:
|
||||
patterns:
|
||||
- "*"
|
||||
commit-message:
|
||||
prefix: "ci(deps)"
|
||||
@@ -1,195 +0,0 @@
|
||||
name: ArtCraft Linux release
|
||||
on:
|
||||
push:
|
||||
branches: [release, artcraft-release, artcraft-linux]
|
||||
workflow_dispatch:
|
||||
permissions:
|
||||
contents: read
|
||||
concurrency:
|
||||
group: linux-release-${{ github.ref }}
|
||||
cancel-in-progress: false
|
||||
jobs:
|
||||
packaging-check:
|
||||
runs-on: ubuntu-22.04
|
||||
timeout-minutes: 10
|
||||
steps:
|
||||
- uses: actions/checkout@v7
|
||||
- uses: storytold/craft-common-actions/actions/install-apt-packages@main
|
||||
with:
|
||||
packages: rpm jq binutils zsync
|
||||
- run: packaging/linux/package-test.sh
|
||||
build:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
- {arch: x86_64, runner: ubuntu-22.04, target: x86_64-unknown-linux-gnu, bundles: "deb,rpm,appimage"}
|
||||
- {arch: aarch64, runner: ubuntu-22.04-arm, target: aarch64-unknown-linux-gnu, bundles: "deb,rpm,appimage"}
|
||||
- {arch: riscv64, runner: ubuntu-24.04, target: riscv64gc-unknown-linux-gnu, bundles: "deb,rpm"}
|
||||
runs-on: ${{ matrix.runner }}
|
||||
timeout-minutes: 90
|
||||
env:
|
||||
SQLX_OFFLINE: 'true'
|
||||
VITE_ENVIRONMENT_TYPE: production
|
||||
TAURI_FRONTEND_PATH: ${{ github.workspace }}/frontend
|
||||
TAURI_APP_PATH: ${{ github.workspace }}/crates/desktop/artcraft
|
||||
steps:
|
||||
- uses: actions/checkout@v7
|
||||
- uses: storytold/craft-common-actions/actions/install-apt-packages@main
|
||||
with:
|
||||
packages: libwebkit2gtk-4.1-dev libappindicator3-dev librsvg2-dev patchelf libssl-dev libclang-dev libasound2-dev libxdo-dev libdbus-1-dev build-essential clang file jq perl cmake nasm rpm zsync gstreamer1.0-plugins-base gstreamer1.0-plugins-good gstreamer1.0-libav
|
||||
- uses: storytold/craft-common-actions/actions/install-riscv64-cross@main
|
||||
if: matrix.arch == 'riscv64'
|
||||
- name: Remove conflicting host Pango development files
|
||||
if: matrix.arch == 'riscv64'
|
||||
# Ubuntu's Pango GIR differs by architecture despite being in a shared path.
|
||||
# Only the target GTK development packages are needed for this cross build.
|
||||
run: sudo apt-get remove -y libpango1.0-dev:amd64
|
||||
- name: Install RISC-V WebKitGTK dependencies
|
||||
if: matrix.arch == 'riscv64'
|
||||
uses: storytold/craft-common-actions/actions/install-apt-packages@main
|
||||
with:
|
||||
packages: libwebkit2gtk-4.1-dev:riscv64 libappindicator3-dev:riscv64 libssl-dev:riscv64 libxdo-dev:riscv64 libdbus-1-dev:riscv64 g++-riscv64-linux-gnu
|
||||
- name: Configure RISC-V cross compilation
|
||||
if: matrix.arch == 'riscv64'
|
||||
run: |
|
||||
cat >> "$GITHUB_ENV" <<'ENV'
|
||||
CARGO_TARGET_RISCV64GC_UNKNOWN_LINUX_GNU_LINKER=riscv64-linux-gnu-gcc
|
||||
CC_riscv64gc_unknown_linux_gnu=riscv64-linux-gnu-gcc
|
||||
CXX_riscv64gc_unknown_linux_gnu=riscv64-linux-gnu-g++
|
||||
AR_riscv64gc_unknown_linux_gnu=riscv64-linux-gnu-ar
|
||||
PKG_CONFIG_ALLOW_CROSS=1
|
||||
PKG_CONFIG_PATH_riscv64gc_unknown_linux_gnu=/usr/lib/riscv64-linux-gnu/pkgconfig:/usr/share/pkgconfig
|
||||
PKG_CONFIG_LIBDIR_riscv64gc_unknown_linux_gnu=/usr/lib/riscv64-linux-gnu/pkgconfig:/usr/share/pkgconfig
|
||||
ENV
|
||||
echo "CMAKE_TOOLCHAIN_FILE_riscv64gc_unknown_linux_gnu=$GITHUB_WORKSPACE/packaging/linux/riscv64.cmake" >> "$GITHUB_ENV"
|
||||
- uses: actions/setup-node@v7
|
||||
with:
|
||||
node-version: '22'
|
||||
cache: npm
|
||||
cache-dependency-path: frontend/package-lock.json
|
||||
- uses: dtolnay/rust-toolchain@683b588c487a69d01f6c6ddf40f036ff682cc2c8 # v1
|
||||
with:
|
||||
toolchain: stable
|
||||
targets: ${{ matrix.target }}
|
||||
- uses: Swatinem/rust-cache@d1527c8f67ef9af357290475d4af4b0fab8f12e0 # v2.9.3
|
||||
with:
|
||||
key: tauri-linux-${{ matrix.arch }}
|
||||
cache-on-failure: true
|
||||
- run: npm ci --ignore-scripts
|
||||
working-directory: frontend
|
||||
- run: ./node_modules/.bin/nx run artcraft:build
|
||||
working-directory: frontend
|
||||
- uses: tauri-apps/tauri-action@fce9c6108b31ea247710505d3aaaa893ee6768d4 # v0
|
||||
with:
|
||||
projectPath: crates/desktop/artcraft
|
||||
args: --target ${{ matrix.target }} --bundles ${{ matrix.bundles }} --config ${{ github.workspace }}/packaging/linux/tauri.conf.json
|
||||
- name: Package and verify Linux outputs
|
||||
env:
|
||||
ARCH: ${{ matrix.arch }}
|
||||
TARGET: ${{ matrix.target }}
|
||||
run: packaging/linux/package.sh "$ARCH" "$TARGET"
|
||||
- uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: release-linux-${{ matrix.arch }}
|
||||
path: dist/release/*
|
||||
if-no-files-found: error
|
||||
arch:
|
||||
if: always() && !cancelled()
|
||||
needs: build
|
||||
runs-on: ubuntu-24.04
|
||||
strategy:
|
||||
matrix:
|
||||
arch: [x86_64, aarch64]
|
||||
steps:
|
||||
- uses: actions/checkout@v7
|
||||
- uses: actions/download-artifact@v8
|
||||
with:
|
||||
name: release-linux-${{ matrix.arch }}
|
||||
path: dist/linux
|
||||
- name: Find tarball
|
||||
id: tarball
|
||||
env:
|
||||
ARCH: ${{ matrix.arch }}
|
||||
run: |
|
||||
shopt -s nullglob
|
||||
files=(dist/linux/artcraft-*-linux-"$ARCH".tar.gz)
|
||||
test "${#files[@]}" -eq 1
|
||||
echo "path=${files[0]}" >> "$GITHUB_OUTPUT"
|
||||
- uses: storytold/craft-common-actions/actions/arch-package@9d6790582b4997aa2120f4413cdb55c0b6f77fae # extra-depends support (#161)
|
||||
with:
|
||||
tarball: ${{ steps.tarball.outputs.path }}
|
||||
arch: ${{ matrix.arch }}
|
||||
extra-depends: webkit2gtk-4.1 gtk3 openssl
|
||||
- name: Verify Arch runtime dependencies
|
||||
run: |
|
||||
for package in dist/release/*.pkg.tar.zst; do
|
||||
tar --zstd -xOf "$package" .PKGINFO | grep -Fx 'depend = webkit2gtk-4.1'
|
||||
done
|
||||
- uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: release-arch-${{ matrix.arch }}
|
||||
path: dist/release/*.pkg.tar.zst
|
||||
if-no-files-found: error
|
||||
flatpak:
|
||||
if: always() && !cancelled()
|
||||
needs: build
|
||||
runs-on: ${{ matrix.runner }}
|
||||
permissions:
|
||||
contents: read
|
||||
packages: write
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
- {arch: x86_64, runner: ubuntu-24.04}
|
||||
- {arch: aarch64, runner: ubuntu-24.04-arm}
|
||||
steps:
|
||||
- uses: actions/checkout@v7
|
||||
- uses: actions/download-artifact@v8
|
||||
with:
|
||||
name: release-linux-${{ matrix.arch }}
|
||||
path: dist/linux
|
||||
- uses: storytold/craft-common-actions/actions/install-apt-packages@main
|
||||
with:
|
||||
packages: flatpak flatpak-builder elfutils librsvg2-common skopeo
|
||||
- name: Allow Flatpak build sandboxes
|
||||
run: sudo sysctl -w kernel.apparmor_restrict_unprivileged_userns=0
|
||||
- uses: storytold/craft-common-actions/actions/import-signing-key@main
|
||||
with:
|
||||
key: ${{ secrets.LINUX_SIGNING_KEY }}
|
||||
- name: Build Flatpak and publish OCI
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
DOCKERHUB_USERNAME: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||
DOCKERHUB_TOKEN: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
run: packaging/linux/flatpak.sh
|
||||
- uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: release-flatpak-${{ matrix.arch }}
|
||||
path: |
|
||||
dist/release/*.flatpak
|
||||
dist/release/*.oci-manifest.json
|
||||
if-no-files-found: error
|
||||
release:
|
||||
needs: [packaging-check, build, arch, flatpak]
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: write
|
||||
steps:
|
||||
- uses: actions/checkout@v7
|
||||
- name: Read version
|
||||
id: version
|
||||
run: echo "value=$(jq -r .version crates/desktop/artcraft/tauri.conf.json)" >> "$GITHUB_OUTPUT"
|
||||
- name: Require signing key
|
||||
env:
|
||||
SIGNING_KEY: ${{ secrets.LINUX_SIGNING_KEY }}
|
||||
run: test -n "$SIGNING_KEY"
|
||||
- uses: storytold/craft-common-actions/actions/draft-release@main
|
||||
with:
|
||||
version: ${{ steps.version.outputs.value }}
|
||||
app-title: ArtCraft
|
||||
tag-prefix: artcraft-v
|
||||
retain: "true"
|
||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
signing-key: ${{ secrets.LINUX_SIGNING_KEY }}
|
||||
@@ -1,239 +0,0 @@
|
||||
name: "Artcraft MacOS Publish"
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- release
|
||||
- artcraft-release
|
||||
- artcraft-macos # Additional branch for MacOS testing
|
||||
|
||||
# This workflow creates Universal macOS builds with signing certificate support.
|
||||
# On each push to the release branches it will create or update a GitHub release, build your app, and upload the artifacts to the release.
|
||||
|
||||
jobs:
|
||||
publish-artcraft:
|
||||
permissions:
|
||||
contents: write
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
# Universal macOS builds (arm64 and x86_64)
|
||||
- platform: 'macos-latest-xlarge'
|
||||
args: '--target universal-apple-darwin'
|
||||
# Uncomment below for additional platforms
|
||||
# - platform: 'ubuntu-22.04'
|
||||
# args: ''
|
||||
# - platform: 'windows-latest'
|
||||
# args: ''
|
||||
|
||||
runs-on: ${{ matrix.platform }}
|
||||
steps:
|
||||
- uses: actions/checkout@v7
|
||||
|
||||
- name: setup node
|
||||
uses: actions/setup-node@v7
|
||||
with:
|
||||
node-version: lts/*
|
||||
|
||||
- name: install Rust stable
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
with:
|
||||
targets: ${{ matrix.platform == 'macos-latest-xlarge' && 'aarch64-apple-darwin,x86_64-apple-darwin' || '' }}
|
||||
|
||||
- name: install dependencies (ubuntu only)
|
||||
if: matrix.platform == 'ubuntu-22.04'
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y libwebkit2gtk-4.1-dev libappindicator3-dev librsvg2-dev patchelf
|
||||
|
||||
- name: replace tauri config with mac-specific version
|
||||
run: |
|
||||
pushd ${{ github.workspace }}/crates/desktop/artcraft
|
||||
# Use the mac-specific tauri conf for the build
|
||||
# TODO(bt): Replace this with the platform-specific naming conventions
|
||||
cp tauri-mac.conf.json tauri.conf.json
|
||||
popd
|
||||
|
||||
- name: install frontend dependencies
|
||||
run: |
|
||||
# Navigate to the frontend directory to install dependencies
|
||||
cd ./frontend
|
||||
|
||||
npm cache clean --force
|
||||
|
||||
echo "Installing all workspace dependencies..."
|
||||
npm ci
|
||||
|
||||
echo "=== Version Information ==="
|
||||
echo "Node version:"
|
||||
node --version
|
||||
echo "NPM version:"
|
||||
npm --version
|
||||
|
||||
echo "=== Nx versions from lockfile ==="
|
||||
npm list --depth=0 | grep nx
|
||||
|
||||
- name: import Apple Developer Certificate
|
||||
if: matrix.platform == 'macos-latest-xlarge'
|
||||
env:
|
||||
APPLE_CERTIFICATE: ${{ secrets.APPLE_CERTIFICATE }}
|
||||
APPLE_CERTIFICATE_PASSWORD: ${{ secrets.APPLE_CERTIFICATE_PASSWORD }}
|
||||
KEYCHAIN_PASSWORD: ${{ secrets.KEYCHAIN_PASSWORD }}
|
||||
run: |
|
||||
echo "Starting certificate import process..."
|
||||
|
||||
# Create certificate file
|
||||
echo "$APPLE_CERTIFICATE" | base64 --decode > certificate.p12
|
||||
|
||||
# Debug: Check if certificate file exists and has content
|
||||
echo "Certificate file info:"
|
||||
ls -la certificate.p12
|
||||
file certificate.p12
|
||||
|
||||
# Debug: Check certificate contents without password (just structure)
|
||||
echo "Certificate structure (may show error if password protected):"
|
||||
openssl pkcs12 -info -in certificate.p12 -passin pass:"$APPLE_CERTIFICATE_PASSWORD" -noout || echo "Could not read certificate structure"
|
||||
|
||||
# Debug: Try to list what's in the p12 file
|
||||
echo "Attempting to list certificate contents:"
|
||||
security cms -D -i certificate.p12 || echo "Not a CMS file, trying PKCS12..."
|
||||
openssl pkcs12 -in certificate.p12 -passin pass:"$APPLE_CERTIFICATE_PASSWORD" -info -nodes 2>&1 | head -20 || echo "Failed to read PKCS12 contents"
|
||||
|
||||
# Debug: Extract certificate details to verify type and expiration
|
||||
echo "Extracting certificate details..."
|
||||
# Extract just the certificate portion and examine it
|
||||
openssl pkcs12 -in certificate.p12 -passin pass:"$APPLE_CERTIFICATE_PASSWORD" -nokeys 2>/dev/null | openssl x509 -text -noout | grep -E "(Subject:|Issuer:|Not Before|Not After)" || echo "Failed to parse certificate details"
|
||||
|
||||
# Also try to see what type of certificate this is
|
||||
echo "Certificate type check:"
|
||||
openssl pkcs12 -in certificate.p12 -passin pass:"$APPLE_CERTIFICATE_PASSWORD" -nokeys 2>/dev/null | openssl x509 -text -noout | grep -E "(CN=|OU=)" || echo "Could not determine certificate type"
|
||||
|
||||
# Debug: Check if private key exists
|
||||
echo "Checking for private key..."
|
||||
openssl pkcs12 -in certificate.p12 -passin pass:"$APPLE_CERTIFICATE_PASSWORD" -nocerts -nodes 2>&1 | grep -E "(PRIVATE KEY|RSA KEY)" | head -5 || echo "No private key found"
|
||||
|
||||
# Create temporary keychain
|
||||
security create-keychain -p "$KEYCHAIN_PASSWORD" build.keychain
|
||||
security default-keychain -s build.keychain
|
||||
security unlock-keychain -p "$KEYCHAIN_PASSWORD" build.keychain
|
||||
security set-keychain-settings -t 3600 -u build.keychain
|
||||
|
||||
# Import certificate - ensure both certificate and private key are imported
|
||||
echo "Importing certificate into keychain..."
|
||||
# Import the p12 directly - this is the most reliable method
|
||||
security import certificate.p12 -k build.keychain -P "$APPLE_CERTIFICATE_PASSWORD" -T /usr/bin/codesign -T /usr/bin/security -T /usr/bin/productbuild
|
||||
|
||||
# Alternative: Try with additional flags if the first import didn't create an identity
|
||||
echo "Verifying import..."
|
||||
if ! security find-identity -v build.keychain | grep -q "1 valid identities found"; then
|
||||
echo "First import didn't create identity, trying with -A flag..."
|
||||
security import certificate.p12 -k build.keychain -P "$APPLE_CERTIFICATE_PASSWORD" -A
|
||||
fi
|
||||
|
||||
# Set trust settings for the certificate
|
||||
echo "Setting trust settings..."
|
||||
security set-key-partition-list -S apple-tool:,apple:,codesign: -s -k "$KEYCHAIN_PASSWORD" build.keychain
|
||||
|
||||
# Unlock the keychain again to ensure it's accessible
|
||||
security unlock-keychain -p "$KEYCHAIN_PASSWORD" build.keychain
|
||||
|
||||
# Add the keychain to the search list
|
||||
# shellcheck disable=SC2046
|
||||
security list-keychains -d user -s build.keychain $(security list-keychains -d user | sed 's/^[[:space:]]*"//;s/"$//')
|
||||
|
||||
# List all certificates to debug
|
||||
echo "Listing all keychains:"
|
||||
security list-keychains
|
||||
|
||||
echo "Listing all certificates in build keychain:"
|
||||
security find-certificate -a -p build.keychain || echo "No certificates found"
|
||||
|
||||
echo "Detailed certificate info:"
|
||||
security find-certificate -c "Learning Machines" build.keychain || echo "Certificate 'Learning Machines' not found"
|
||||
|
||||
# Check certificate trust settings
|
||||
echo "Checking certificate trust settings:"
|
||||
security dump-trust-settings || echo "No custom trust settings"
|
||||
|
||||
# Try to find codesigning identities
|
||||
echo "Finding all identities in keychain:"
|
||||
security find-identity -v build.keychain || echo "No identities found"
|
||||
|
||||
echo "Finding codesigning identities:"
|
||||
security find-identity -v -p codesigning build.keychain || echo "No codesigning identities found"
|
||||
|
||||
# Additional debug: Check if this is a valid Developer ID cert
|
||||
echo "Checking for Developer ID certificates:"
|
||||
security find-certificate -a build.keychain | grep -A 5 -B 5 "Developer ID" || echo "No Developer ID certificates found"
|
||||
|
||||
- name: verify certificate
|
||||
if: matrix.platform == 'macos-latest-xlarge'
|
||||
run: |
|
||||
echo "=== Certificate Verification ==="
|
||||
|
||||
echo "1. Checking codesigning identities:"
|
||||
security find-identity -v -p codesigning build.keychain || echo "No codesigning identities found"
|
||||
|
||||
echo -e "\n2. Checking all identities:"
|
||||
security find-identity -v build.keychain || echo "No identities found"
|
||||
|
||||
echo -e "\n3. Checking certificates in keychain:"
|
||||
security find-certificate -c "Learning Machines" build.keychain -p | openssl x509 -text -noout | grep -E "(Subject:|CN=)" || echo "Could not parse certificate"
|
||||
|
||||
# Set the correct signing identity
|
||||
echo "APPLE_SIGNING_IDENTITY=Developer ID Application: Learning Machines LLC (DJ6XS33FX8)" >> "$GITHUB_ENV"
|
||||
echo "Proceeding with identity: Developer ID Application: Learning Machines LLC (DJ6XS33FX8)"
|
||||
|
||||
# Also set the team ID
|
||||
echo "APPLE_DEVELOPMENT_TEAM=DJ6XS33FX8" >> "$GITHUB_ENV"
|
||||
|
||||
- name: Pre-build frontend to avoid Tauri build issues
|
||||
run: |
|
||||
# Build the frontend app from the correct directory
|
||||
cd ./frontend
|
||||
|
||||
# Increase Node.js memory limit for the build
|
||||
export NODE_OPTIONS="--max-old-space-size=8192"
|
||||
|
||||
# Use npx to ensure we use the local nx version
|
||||
echo "Building with npx nx..."
|
||||
npx nx run artcraft:build
|
||||
|
||||
echo "Checking build output..."
|
||||
# shellcheck disable=SC2012
|
||||
ls -la apps/artcraft/dist/ | head -10
|
||||
|
||||
# Update Tauri config to skip the build (since we already built)
|
||||
cd ${{ github.workspace }}/crates/desktop/artcraft
|
||||
cp tauri.conf.json tauri.conf.json.bak
|
||||
# Set beforeBuildCommand to empty or a no-op command
|
||||
if command -v jq &> /dev/null; then
|
||||
jq '.build.beforeBuildCommand = ""' tauri.conf.json > tauri.conf.json.tmp
|
||||
mv tauri.conf.json.tmp tauri.conf.json
|
||||
else
|
||||
node -e "const fs = require('fs'); const config = JSON.parse(fs.readFileSync('tauri.conf.json', 'utf8')); config.build.beforeBuildCommand = ''; fs.writeFileSync('tauri.conf.json', JSON.stringify(config, null, 2));"
|
||||
fi
|
||||
|
||||
|
||||
- name: build and publish
|
||||
uses: tauri-apps/tauri-action@v0
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
APPLE_ID: ${{ secrets.APPLE_ID }}
|
||||
APPLE_ID_PASSWORD: ${{ secrets.APPLE_ID_PASSWORD }}
|
||||
APPLE_PASSWORD: ${{ secrets.APPLE_PASSWORD }}
|
||||
APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }}
|
||||
APPLE_CERTIFICATE_PASSWORD: ${{ secrets.APPLE_CERTIFICATE_PASSWORD }}
|
||||
APPLE_SIGNING_IDENTITY: ${{ env.APPLE_SIGNING_IDENTITY }}
|
||||
APPLE_DEVELOPMENT_TEAM: ${{ env.APPLE_DEVELOPMENT_TEAM }}
|
||||
# Use the SQLx query cache for SQLite
|
||||
SQLX_OFFLINE: true
|
||||
with:
|
||||
projectPath: ./crates/desktop/artcraft
|
||||
tagName: artcraft-v__VERSION__ # the action automatically replaces __VERSION__ with the app version.
|
||||
releaseName: "ArtCraft v__VERSION__"
|
||||
releaseBody: "See the assets below to download this version and install."
|
||||
releaseDraft: true
|
||||
prerelease: false
|
||||
args: ${{ matrix.args }}
|
||||
@@ -1,109 +0,0 @@
|
||||
name: "Artcraft Windows Publish"
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- release
|
||||
- artcraft-release
|
||||
- artcraft-windows # Additional branch for Windows testing
|
||||
|
||||
# This workflow creates Windows builds.
|
||||
# On each push to the release branches it will create or update a GitHub release, build your app, and upload the artifacts to the release.
|
||||
|
||||
jobs:
|
||||
publish-artcraft-windows:
|
||||
permissions:
|
||||
contents: write
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
# Windows builds
|
||||
- platform: 'windows-latest'
|
||||
args: ''
|
||||
|
||||
runs-on: ${{ matrix.platform }}
|
||||
steps:
|
||||
- uses: actions/checkout@v7
|
||||
|
||||
- name: install Rust stable
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
|
||||
- name: install wreq dependencies
|
||||
run: |
|
||||
choco install cmake -y
|
||||
choco install strawberryperl -y
|
||||
choco install pkgconfiglite -y
|
||||
choco install llvm -y
|
||||
choco install nasm -y
|
||||
shell: cmd
|
||||
|
||||
- name: setup node
|
||||
uses: actions/setup-node@v7
|
||||
with:
|
||||
node-version: lts/*
|
||||
|
||||
- name: install frontend dependencies
|
||||
shell: pwsh
|
||||
run: |
|
||||
# Navigate to the frontend directory to install dependencies
|
||||
cd ./frontend
|
||||
|
||||
npm cache clean --force
|
||||
|
||||
echo "Installing all workspace dependencies..."
|
||||
npm ci
|
||||
|
||||
echo "=== Version Information ==="
|
||||
echo "Node version:"
|
||||
node --version
|
||||
echo "NPM version:"
|
||||
npm --version
|
||||
|
||||
echo "=== Nx versions from lockfile ==="
|
||||
npm list --depth=0 | findstr nx
|
||||
|
||||
- name: Pre-build frontend to avoid Tauri build issues
|
||||
shell: pwsh
|
||||
run: |
|
||||
# Build the frontend app from the correct directory
|
||||
cd ./frontend
|
||||
|
||||
# Increase Node.js memory limit for the build
|
||||
$env:NODE_OPTIONS="--max-old-space-size=8192"
|
||||
|
||||
# Use npx to ensure we use the local nx version
|
||||
echo "Building with npx nx..."
|
||||
npx nx run artcraft:build
|
||||
if ($LASTEXITCODE -ne 0) {
|
||||
exit $LASTEXITCODE
|
||||
}
|
||||
|
||||
echo "Checking build output..."
|
||||
dir apps/artcraft/dist/ | Select-Object -First 10
|
||||
|
||||
# Update Tauri config to skip the build (since we already built)
|
||||
cd ${{ github.workspace }}/crates/desktop/artcraft
|
||||
Copy-Item tauri.conf.json tauri.conf.json.bak
|
||||
|
||||
# Set beforeBuildCommand to empty using PowerShell
|
||||
$config = Get-Content tauri.conf.json | ConvertFrom-Json
|
||||
$config.build.beforeBuildCommand = ""
|
||||
$config | ConvertTo-Json -Depth 10 | Set-Content tauri.conf.json
|
||||
|
||||
- name: build and publish
|
||||
uses: tauri-apps/tauri-action@v0
|
||||
env:
|
||||
# NB(bt): This is needed by wreq. Hopefully it doesn't kill Tauri
|
||||
RUSTFLAGS: "-C target-feature=+crt-static"
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
# Use the SQLx query cache for SQLite
|
||||
SQLX_OFFLINE: true
|
||||
with:
|
||||
projectPath: ./crates/desktop/artcraft
|
||||
tagName: artcraft-v__VERSION__ # the action automatically replaces __VERSION__ with the app version.
|
||||
releaseName: "ArtCraft v__VERSION__"
|
||||
releaseBody: "Production build of ArtCraft. See the assets below to download and install."
|
||||
releaseDraft: true
|
||||
prerelease: false
|
||||
args: ${{ matrix.args }}
|
||||
@@ -0,0 +1,52 @@
|
||||
name: CI
|
||||
on: [push, pull_request]
|
||||
env:
|
||||
MYSQL_URL: mysql://root:root@localhost/storyteller
|
||||
DATABASE_URL: mysql://root:root@localhost/storyteller
|
||||
REDIS_0_URL: redis://redisd@localhost/0
|
||||
REDIS_1_URL: redis://redisd@localhost/1
|
||||
DEBIAN_FRONTEND: noninteractive
|
||||
|
||||
jobs:
|
||||
setup_build_test:
|
||||
runs-on: ubuntu-20.04
|
||||
steps:
|
||||
- name: Checkout sources
|
||||
uses: actions/checkout@v3
|
||||
- name: Cache cargo registry
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
path: |
|
||||
~/.cargo/registry
|
||||
~/.cargo/git
|
||||
key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.toml') }}
|
||||
- name: Setup mysql
|
||||
run: |
|
||||
sudo systemctl start mysql
|
||||
sudo apt-get update
|
||||
sudo apt-get -y install mysql-client libmysqlclient-dev
|
||||
mysql -e "create user 'storyteller'@'localhost' IDENTIFIED BY 'password';" -uroot -proot
|
||||
mysql -e "create database storyteller; grant all on storyteller.* to 'storyteller'@'localhost';" -uroot -proot
|
||||
- name: Setup Redis
|
||||
run: |
|
||||
sudo apt-get -y install redis-server
|
||||
sudo service redis-server start
|
||||
- name: Setup Rust
|
||||
run: |
|
||||
rustup set profile minimal
|
||||
rustup show
|
||||
rustup install 1.66
|
||||
rustup default 1.66
|
||||
rustup show
|
||||
cargo install sqlx-cli --features openssl-vendored
|
||||
cargo install diesel_cli
|
||||
- name: Run database migrations
|
||||
run: |
|
||||
# nothing to see here ;)
|
||||
set +e +o pipefail
|
||||
diesel migration run
|
||||
set -e -o pipefail
|
||||
- name: Build
|
||||
run: cargo build --release
|
||||
- name: Test
|
||||
run: cargo test --release
|
||||
@@ -0,0 +1,112 @@
|
||||
# Adapted from
|
||||
# https://github.com/actions/starter-workflows/ci/docker-publish.yml
|
||||
# If using base images, don't forget to add READ access, eg. from a package settings page like:
|
||||
# https://github.com/orgs/storytold/packages/container/docker-base-images-rust-ssl/settings/actions_access
|
||||
name: Publish Docker Container to GHCR
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
# NB: Default-branch doesn't work, despite Github's documentation.
|
||||
#- $default-branch
|
||||
- master
|
||||
env:
|
||||
IMAGE_NAME: storyteller-rust
|
||||
|
||||
jobs:
|
||||
# Push image to GitHub Packages.
|
||||
# See also https://docs.docker.com/docker-hub/builds/
|
||||
# See also https://depot.dev/blog/docker-layer-caching-in-github-actions
|
||||
# See also https://evilmartians.com/chronicles/build-images-on-github-actions-with-docker-layer-caching
|
||||
build-docker-image:
|
||||
name: "Build Docker Image"
|
||||
runs-on: ubuntu-22.04 # runs-on: ubuntu-latest
|
||||
if: github.event_name == 'push'
|
||||
permissions:
|
||||
packages: write
|
||||
contents: read
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
# - uses: docker/setup-buildx-action@v1
|
||||
# name: Set up Docker Buildx
|
||||
|
||||
- uses: docker/login-action@v1
|
||||
name: Login to GitHub Container Registry
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.actor }} # username: ${{ github.repository_owner }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Calculate short SHA
|
||||
id: vars
|
||||
# echo "sha_short=$(git rev-parse --short HEAD)" >> $GITHUB_OUTPUT
|
||||
run: |
|
||||
echo "sha_short=$(echo ${{ github.sha }} | cut -c1-12)" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Emit short SHA
|
||||
run: |
|
||||
echo "Full SHA: ${{ github.sha }}"
|
||||
echo "Short SHA: ${{ steps.vars.outputs.sha_short }}"
|
||||
|
||||
#- uses: actions/cache@v2
|
||||
# name: Cache Docker layers
|
||||
# with:
|
||||
# path: /tmp/.buildx-cache
|
||||
# key: ${{ runner.os }}-buildx-${{ github.sha }}
|
||||
# restore-keys: |
|
||||
# ${{ runner.os }}-buildx-
|
||||
|
||||
# Docs for this action are at https://github.com/marketplace/actions/build-docker-images-using-cache
|
||||
- uses: whoan/docker-build-with-cache-action@v6
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.actor }} # username: ${{ github.repository_owner }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
image_name: storytold/storyteller-rust
|
||||
# NB: The `git_push_tag: true` option does not work as expected, so we put the sha in `image_tag`:
|
||||
# https://github.com/whoan/docker-build-with-cache-action/issues/72
|
||||
image_tag: latest,${{ github.sha }},${{ steps.vars.outputs.sha_short }}
|
||||
# NB: Inject the Git SHA into the container so the servers can emit their own version:
|
||||
build_extra_args: "--build-arg=GIT_SHA=${{ github.sha }}"
|
||||
|
||||
# - name: Build image
|
||||
# run: |
|
||||
# SHORT_SHA=$(echo ${GITHUB_SHA} | cut -c1-12)
|
||||
# docker build . --file Dockerfile --tag $IMAGE_NAME --label "runnumber=${GITHUB_RUN_ID}" --build-arg GIT_SHA=${SHORT_SHA}
|
||||
|
||||
# - name: Push image to GitHub Container Registry
|
||||
# run: |
|
||||
# IMAGE_ID=ghcr.io/${{ github.repository_owner }}/$IMAGE_NAME
|
||||
|
||||
# # Change all uppercase to lowercase
|
||||
# IMAGE_ID=$(echo $IMAGE_ID | tr '[A-Z]' '[a-z]')
|
||||
|
||||
# # Strip git ref prefix from version
|
||||
# VERSION=$(echo "${{ github.ref }}" | sed -e 's,.*/\(.*\),\1,')
|
||||
|
||||
# # Strip "v" prefix from tag name
|
||||
# [[ "${{ github.ref }}" == "refs/tags/"* ]] && VERSION=$(echo $VERSION | sed -e 's/^v//')
|
||||
|
||||
# # Use Docker `latest` tag convention
|
||||
# [ "$VERSION" == "$default-branch" ] && VERSION=latest
|
||||
|
||||
# echo IMAGE_ID=$IMAGE_ID
|
||||
# echo VERSION=$VERSION
|
||||
|
||||
# docker tag $IMAGE_NAME $IMAGE_ID:$VERSION
|
||||
# docker push $IMAGE_ID:$VERSION
|
||||
|
||||
# SHORT_SHA=$(echo ${GITHUB_SHA} | cut -c1-12)
|
||||
|
||||
# docker tag $IMAGE_NAME $IMAGE_ID:$SHORT_SHA
|
||||
# docker push $IMAGE_ID:$SHORT_SHA
|
||||
|
||||
- name: Cleanup / Debug
|
||||
# This job will run even on failures to help with debugging.
|
||||
if: always()
|
||||
run: |
|
||||
echo "Report at final github action step:"
|
||||
echo "Disk usage:"
|
||||
pwd
|
||||
du -hsc * | sort -hr
|
||||
@@ -1,24 +0,0 @@
|
||||
# Builds and tests the workspace on FreeBSD, inside a FreeBSD VM on a Linux runner
|
||||
# (GitHub has no FreeBSD runners).
|
||||
name: FreeBSD
|
||||
|
||||
# The VM build is slow, so it runs nightly on main and on pushes to `release`, not on pull requests.
|
||||
on:
|
||||
schedule:
|
||||
- cron: "27 4 * * *"
|
||||
push:
|
||||
branches: [release]
|
||||
workflow_dispatch:
|
||||
|
||||
concurrency:
|
||||
group: freebsd-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
freebsd:
|
||||
# Intentionally tracked at @main (not SHA-pinned) so org-wide CI improvements, security fixes, and build optimizations propagate without PR churn across all craftapp repos.
|
||||
uses: storytold/craft-common-actions/.github/workflows/freebsd.yml@main
|
||||
|
||||
@@ -1,28 +0,0 @@
|
||||
name: House rules
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [main]
|
||||
paths:
|
||||
- AGENTS.md
|
||||
- CLAUDE.md
|
||||
- .github/workflows/house-rules.yml
|
||||
pull_request:
|
||||
paths:
|
||||
- AGENTS.md
|
||||
- CLAUDE.md
|
||||
- .github/workflows/house-rules.yml
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
concurrency:
|
||||
group: house-rules-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
house-rules:
|
||||
uses: storytold/craft-common-actions/.github/workflows/house-rules.yml@main
|
||||
with:
|
||||
rust: false
|
||||
@@ -1,23 +0,0 @@
|
||||
# Quick static checks for the release pipeline (seconds, no Rust build): workflow syntax,
|
||||
# packaging shell/PowerShell scripts, and the Linux desktop/AppStream/Flatpak metadata.
|
||||
name: Packaging lint
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [main, release]
|
||||
paths: [".github/workflows/**", "packaging/**"]
|
||||
pull_request:
|
||||
paths: [".github/workflows/**", "packaging/**"]
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
concurrency:
|
||||
group: packaging-lint-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
lint:
|
||||
# Intentionally tracked at @main (not SHA-pinned) so org-wide CI improvements, security fixes, and build optimizations propagate without PR churn across all craftapp repos.
|
||||
uses: storytold/craft-common-actions/.github/workflows/packaging-lint.yml@main
|
||||
@@ -1,22 +0,0 @@
|
||||
name: SonarCloud
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [main]
|
||||
pull_request:
|
||||
types: [opened, synchronize, reopened]
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
concurrency:
|
||||
group: sonarcloud-${{ github.ref }}
|
||||
# A push to main is the run that publishes coverage; superseded runs there would never finish.
|
||||
cancel-in-progress: ${{ github.ref != 'refs/heads/main' }}
|
||||
|
||||
jobs:
|
||||
sonarcloud:
|
||||
# Tracked at @main so org-wide CI improvements propagate without PR churn.
|
||||
uses: storytold/craft-common-actions/.github/workflows/sonarcloud.yml@main
|
||||
secrets: inherit
|
||||
+8
-57
@@ -2,70 +2,32 @@
|
||||
*secret*sh
|
||||
.env
|
||||
.env-aichatbot-secrets
|
||||
.env-analytics-job
|
||||
.env-elasticsearch-secrets
|
||||
.env-migration-tool-secrets
|
||||
.env-secrets
|
||||
.env-support-tool-secrets
|
||||
_develop/tools/.secrets
|
||||
_develop/tools/db_secrets.toml
|
||||
crates/service/web/storyteller_web/config/storyteller-web.development-secrets.env
|
||||
db.secrets
|
||||
db_secrets.toml
|
||||
env-secrets
|
||||
pubkeys.txt
|
||||
secrets.toml
|
||||
secrets.yaml
|
||||
secrets/
|
||||
twitch_secrets.toml
|
||||
|
||||
# Old env secrets
|
||||
.*-secrets
|
||||
|
||||
# New env secrets (per-app)
|
||||
*-secrets.env
|
||||
|
||||
# Utilities with secrets
|
||||
production_database_secret_connect.sh
|
||||
production_database_secret_query.sh
|
||||
production_database_voice_clone_report_download.sh
|
||||
script/artcraft/mac_signing_build.sh
|
||||
script/production_database_secret_backup.sh
|
||||
script/production_database_secret_connect.sh
|
||||
script/socat_dev_database_connect.sh
|
||||
secrets.toml
|
||||
secrets.yaml
|
||||
_develop/tools/.secrets
|
||||
_develop/tools/db_secrets.toml
|
||||
twitch_secrets.toml
|
||||
|
||||
# Python virtual envs (named by convention)
|
||||
venv/
|
||||
|
||||
# LLM Testing
|
||||
/external/
|
||||
/prompts/
|
||||
/reports/
|
||||
/requests/
|
||||
|
||||
# Scratch data
|
||||
scratch/
|
||||
test_data/temp/
|
||||
untracked/
|
||||
tmp/
|
||||
# New env secrets (per-app)
|
||||
*-secrets.env
|
||||
|
||||
# Rust build directory
|
||||
target/
|
||||
crates/lib/database_queries/target/
|
||||
crates/lib/sqlite_queries/target/
|
||||
|
||||
# Javascript build directories
|
||||
node_modules/
|
||||
|
||||
# Frontend build outputs
|
||||
frontend/apps/artcraft/bundle-report.html
|
||||
|
||||
# Vim noise
|
||||
*.swo
|
||||
*.swp
|
||||
*~
|
||||
|
||||
# IntelliJ/CLion/RustRover noise
|
||||
# IntelliJ/CLion noise
|
||||
.idea/
|
||||
|
||||
# Python noise
|
||||
@@ -74,9 +36,6 @@ __pycache__/
|
||||
# We use diesel for DB migrations only. We don't care about its codgen.
|
||||
_ignored_schema.rs
|
||||
|
||||
# Diesel lock file
|
||||
.diesel_lock
|
||||
|
||||
# AI Chat Sidecar has its own schema managed by diesel, but we don't care for diesel codegen.
|
||||
crates/programs/aichatbot-sidecar/_ignored_schema.rs
|
||||
|
||||
@@ -93,15 +52,7 @@ models/tacotron2/python/
|
||||
# Stuff I want to keep around, but not commit
|
||||
scraps/
|
||||
|
||||
# Mac nonsense
|
||||
.DS_Store
|
||||
|
||||
# Windows build files
|
||||
# These should be built by the platform
|
||||
windows/sqlite/sqlite3.exp
|
||||
windows/sqlite/sqlite3.lib
|
||||
|
||||
# ArtCraft Windows DLLs
|
||||
crates/desktop/src-tauri/*.dll
|
||||
windows_dlls/
|
||||
|
||||
|
||||
Generated
+38
@@ -0,0 +1,38 @@
|
||||
<component name="ProjectDictionaryState">
|
||||
<dictionary name="bt">
|
||||
<words>
|
||||
<w>arpabet</w>
|
||||
<w>backfill</w>
|
||||
<w>cashapp</w>
|
||||
<w>cheermote</w>
|
||||
<w>crockford</w>
|
||||
<w>culted</w>
|
||||
<w>dalle</w>
|
||||
<w>downvote</w>
|
||||
<w>fakeyou</w>
|
||||
<w>ffprobe</w>
|
||||
<w>greenlight</w>
|
||||
<w>greenlighting</w>
|
||||
<w>greenlit</w>
|
||||
<w>hanashi</w>
|
||||
<w>hifigan</w>
|
||||
<w>hparams</w>
|
||||
<w>huggingface</w>
|
||||
<w>replayable</w>
|
||||
<w>retryably</w>
|
||||
<w>scrollback</w>
|
||||
<w>softvc</w>
|
||||
<w>symphonia</w>
|
||||
<w>tacotron</w>
|
||||
<w>techcrunch</w>
|
||||
<w>transcoded</w>
|
||||
<w>transcodings</w>
|
||||
<w>upserting</w>
|
||||
<w>vits</w>
|
||||
<w>vocoded</w>
|
||||
<w>vocoder</w>
|
||||
<w>vocoders</w>
|
||||
<w>vocodes</w>
|
||||
</words>
|
||||
</dictionary>
|
||||
</component>
|
||||
-12
@@ -1,12 +0,0 @@
|
||||
{
|
||||
"db_name": "SQLite",
|
||||
"query": "\n UPDATE tasks\n SET is_dismissed_by_user = 1\n WHERE id = ?\n ",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Right": 1
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "13d435d8f681b73e4080748dcef0579e246e41049af67e1bd79ffc06aa6ba974"
|
||||
}
|
||||
-12
@@ -1,12 +0,0 @@
|
||||
{
|
||||
"db_name": "SQLite",
|
||||
"query": "\n UPDATE tasks\n SET\n task_status = ?,\n on_complete_batch_token = ?,\n on_complete_primary_media_file_token = ?,\n on_complete_primary_media_file_class = ?,\n on_complete_primary_media_file_cdn_url = ?,\n on_complete_primary_media_file_thumbnail_url_template = ?\n WHERE\n provider = ?\n AND provider_job_id = ?\n ",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Right": 8
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "1e3758851fc8451670f86a9432009b73bdb81de4a08dcaa96a86afb5f9be7a68"
|
||||
}
|
||||
-128
@@ -1,128 +0,0 @@
|
||||
{
|
||||
"db_name": "SQLite",
|
||||
"query": "\n SELECT\n id,\n task_status,\n task_type,\n model_type,\n provider,\n provider_job_id,\n frontend_caller,\n frontend_subscriber_id,\n frontend_subscriber_payload,\n on_complete_primary_media_file_token,\n on_complete_primary_media_file_class,\n on_complete_batch_token,\n on_complete_primary_media_file_cdn_url,\n on_complete_primary_media_file_thumbnail_url_template,\n on_failure_type,\n on_failure_message,\n created_at as \"created_at: DateTime<Utc>\",\n updated_at as \"updated_at: DateTime<Utc>\",\n completed_at as \"completed_at: DateTime<Utc>\"\n FROM tasks\n WHERE is_dismissed_by_user == 0\n ",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"name": "id",
|
||||
"ordinal": 0,
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"name": "task_status",
|
||||
"ordinal": 1,
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"name": "task_type",
|
||||
"ordinal": 2,
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"name": "model_type",
|
||||
"ordinal": 3,
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"name": "provider",
|
||||
"ordinal": 4,
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"name": "provider_job_id",
|
||||
"ordinal": 5,
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"name": "frontend_caller",
|
||||
"ordinal": 6,
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"name": "frontend_subscriber_id",
|
||||
"ordinal": 7,
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"name": "frontend_subscriber_payload",
|
||||
"ordinal": 8,
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"name": "on_complete_primary_media_file_token",
|
||||
"ordinal": 9,
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"name": "on_complete_primary_media_file_class",
|
||||
"ordinal": 10,
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"name": "on_complete_batch_token",
|
||||
"ordinal": 11,
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"name": "on_complete_primary_media_file_cdn_url",
|
||||
"ordinal": 12,
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"name": "on_complete_primary_media_file_thumbnail_url_template",
|
||||
"ordinal": 13,
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"name": "on_failure_type",
|
||||
"ordinal": 14,
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"name": "on_failure_message",
|
||||
"ordinal": 15,
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"name": "created_at: DateTime<Utc>",
|
||||
"ordinal": 16,
|
||||
"type_info": "Int64"
|
||||
},
|
||||
{
|
||||
"name": "updated_at: DateTime<Utc>",
|
||||
"ordinal": 17,
|
||||
"type_info": "Int64"
|
||||
},
|
||||
{
|
||||
"name": "completed_at: DateTime<Utc>",
|
||||
"ordinal": 18,
|
||||
"type_info": "Int64"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Right": 0
|
||||
},
|
||||
"nullable": [
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
false,
|
||||
false,
|
||||
true
|
||||
]
|
||||
},
|
||||
"hash": "2436eb7bf4b0e23a899311a67a77f71d89eb35461e2113e43cd9d221bfa91c9a"
|
||||
}
|
||||
-12
@@ -1,12 +0,0 @@
|
||||
{
|
||||
"db_name": "SQLite",
|
||||
"query": "\n UPDATE tasks\n SET task_status = \"cancelled_by_user\"\n WHERE task_status NOT IN (\n \"complete_success\",\n \"complete_failure\",\n \"dead\",\n \"cancelled_by_user\",\n \"cancelled_by_provider\",\n \"cancelled_by_us\"\n )\n ",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Right": 0
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "30272128d83543d9fbfca930b7cdd4af30adfb4d90f2de044463a111c6340495"
|
||||
}
|
||||
-86
@@ -1,86 +0,0 @@
|
||||
{
|
||||
"db_name": "SQLite",
|
||||
"query": "\n SELECT\n id,\n task_status,\n task_type,\n model_type,\n provider,\n provider_job_id,\n frontend_caller,\n queue_status_url,\n queue_response_url,\n prompt_token,\n frontend_subscriber_id,\n frontend_subscriber_payload\n FROM tasks\n WHERE\n provider = ?\n AND provider_job_id = ?\n ",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"name": "id",
|
||||
"ordinal": 0,
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"name": "task_status",
|
||||
"ordinal": 1,
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"name": "task_type",
|
||||
"ordinal": 2,
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"name": "model_type",
|
||||
"ordinal": 3,
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"name": "provider",
|
||||
"ordinal": 4,
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"name": "provider_job_id",
|
||||
"ordinal": 5,
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"name": "frontend_caller",
|
||||
"ordinal": 6,
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"name": "queue_status_url",
|
||||
"ordinal": 7,
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"name": "queue_response_url",
|
||||
"ordinal": 8,
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"name": "prompt_token",
|
||||
"ordinal": 9,
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"name": "frontend_subscriber_id",
|
||||
"ordinal": 10,
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"name": "frontend_subscriber_payload",
|
||||
"ordinal": 11,
|
||||
"type_info": "Text"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Right": 2
|
||||
},
|
||||
"nullable": [
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
true
|
||||
]
|
||||
},
|
||||
"hash": "35677894f7b02f32a1bf6396fa6d10caba69bb0f3f03ee7ffbfa1437689d42df"
|
||||
}
|
||||
-12
@@ -1,12 +0,0 @@
|
||||
{
|
||||
"db_name": "SQLite",
|
||||
"query": "\n INSERT INTO tasks (\n id,\n task_status,\n task_type,\n model_type,\n provider,\n provider_job_id,\n queue_status_url,\n queue_response_url,\n prompt_token,\n frontend_caller,\n frontend_subscriber_id,\n frontend_subscriber_payload\n )\n VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)\n ",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Right": 12
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "4fc9674ae39abaadc36054c200290ec95d0e2c7765336f0fa97805a2265f5c4c"
|
||||
}
|
||||
-12
@@ -1,12 +0,0 @@
|
||||
{
|
||||
"db_name": "SQLite",
|
||||
"query": "\n UPDATE tasks\n SET\n task_status = ?,\n on_complete_batch_token = ?,\n on_complete_primary_media_file_token = ?,\n on_complete_primary_media_file_class = ?,\n on_complete_primary_media_file_cdn_url = ?,\n on_complete_primary_media_file_thumbnail_url_template = ?\n WHERE id = ?\n ",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Right": 7
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "50a2449b87abed7df3302a74ecd0e8ab87940077a247636680cb09b2fe488321"
|
||||
}
|
||||
-12
@@ -1,12 +0,0 @@
|
||||
{
|
||||
"db_name": "SQLite",
|
||||
"query": "\n UPDATE tasks\n SET task_status = ?,\n on_failure_type = ?,\n on_failure_message = ?\n WHERE id = ?\n ",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Right": 4
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "9b99ccd0baf9a4b74d8903f63e3a3dcf4c09b75cad978f0532388cbd743b7516"
|
||||
}
|
||||
-12
@@ -1,12 +0,0 @@
|
||||
{
|
||||
"db_name": "SQLite",
|
||||
"query": "\n UPDATE tasks\n SET task_status = ?\n WHERE id = ?\n ",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Right": 2
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "dc0367e9e43c3d7dda3623e77337ab4529eac2192475be7d08589886703ba1cb"
|
||||
}
|
||||
-12
@@ -1,12 +0,0 @@
|
||||
{
|
||||
"db_name": "SQLite",
|
||||
"query": "\n UPDATE tasks\n SET is_dismissed_by_user = 1\n WHERE is_dismissed_by_user != 1\n ",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Right": 0
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "f83018ec5493982659a56576296914f937bb4e75db3a522eb4bda486d5caa402"
|
||||
}
|
||||
@@ -1,98 +0,0 @@
|
||||
<!-- craft-house-rules:start core source=dde09ea31e2e457f5b4653a6e76f7cdf55944bdf sha256=540e6cea75d12d0710c3dca89658328e81e508ecef102446ca5bb95b7c0b92bb -->
|
||||
## House rules
|
||||
|
||||
These apply in every Craft repository. A later rule may be stricter, not weaker. The source is `standards/house-rules-core.md` in `storytold/craft-contrib`. Propose changes there, not in a copy.
|
||||
|
||||
1. **A person is accountable.** Whoever runs an agent answers for what it opens and for its review comments.
|
||||
2. **Check, then claim.** Before writing code, read the issue comments, search pull requests for `#N` and the keywords, and check recent commits on `main`. Comment on the issue first. Release a claim you will not finish.
|
||||
3. **Verify, don't guess.** Open a definition before using it. Confirm the cause in the code, even when someone names a location.
|
||||
4. **Keep fixes narrow and tested.** Fix what the issue describes, where the bug is. Add a test that fails without the fix.
|
||||
5. **Review the diff before opening a pull request.** Identifiers exist as used, the test fails without the fix, nothing unrelated changed, and the description is true.
|
||||
6. **Look for a better pull request.** If another fixes the same thing, close yours when it is better or equal, and say why.
|
||||
7. **Report only what ran.** Say what you tested and what you could not run. Without a local build or test, say so and rely on CI before marking ready.
|
||||
8. **`main` changes through a pull request.** Do not push to it. Branch protection enforces this. There is no exception.
|
||||
9. **Stop and ask only about** licensing outside the allowlist, publishing, releases, a new remote, secrets, spending money, destructive git, and anything else hard to undo. Otherwise keep working. `plan/` is a gitignored notebook, not rules. Without it, do not ask for it and do not stop.
|
||||
10. **Keep secrets and personal details out** of the repository, logs and requests.
|
||||
11. **CI stays shared.** Workflows call `storytold/craft-common-actions`. Do not change CI in a feature or fix pull request. If CI cannot cover a change, open an issue there.
|
||||
12. **Guards say why.** A check that looks removable says what it prevents. Read that before changing it.
|
||||
13. **Instructions must be readable by everyone.** No notes about one machine, and no links to files a reader cannot open. `craftrules` is retired. Do not link to it.
|
||||
14. **Keep pull requests moving.** Concurrent pull requests are fine; unanswered actionable feedback is not. Respond in 7 days, a reminder at 14, closure eligible at 30, from when an actionable request waits on the author. Suggestions and unconfirmed bot findings do not start the clock. A fix, explanation or agreed plan resets it; unrelated activity does not. Waiting on a reviewer or maintainer pauses it and does not count against the author. A maintainer decides closure; it is never automatic. Reuse search results, do not poll, and stop at a rate limit.
|
||||
15. **No agent attribution.** A pull request does not say an agent wrote it. A repository rule may not require that.
|
||||
<!-- craft-house-rules:end -->
|
||||
|
||||
# AGENTS.md
|
||||
|
||||
This file provides guidance for coding agents when working with the Artcraft monorepo.
|
||||
|
||||
## Project Overview
|
||||
|
||||
Artcraft is a public-facing Tauri desktop app for generating AI images and video.
|
||||
It uses Rust and TypeScript. Backend services, company tooling, and website builds
|
||||
live in the separate `artcraft-services` repository.
|
||||
|
||||
## Project Structure
|
||||
|
||||
```
|
||||
artcraft/
|
||||
├── _database/sql/artcraft_migrations/ # Embedded SQLite task database migrations
|
||||
├── .sqlx/ # SQLite query metadata for offline builds
|
||||
├── crates/
|
||||
│ ├── desktop/artcraft/ # Tauri app, configuration, capabilities, icons
|
||||
│ ├── api_clients/ # Desktop provider clients and shared API types
|
||||
│ ├── lib/ # Desktop support libraries
|
||||
│ ├── schema/database/sqlite_tasks/ # Desktop task persistence
|
||||
│ ├── schema/public/ # Shared wire-format enums and identifiers
|
||||
│ ├── testing/ # Shared test helpers
|
||||
│ └── vendor/tauri-plugin-http/ # HTTP plugin fork with cookie access
|
||||
├── frontend/
|
||||
│ ├── apps/artcraft/ # Tauri app frontend and public resources
|
||||
│ └── libs/ # Shared frontend libraries
|
||||
├── script/artcraft/ # Desktop development and build scripts
|
||||
├── test_data/ # Fixtures used by retained crate tests
|
||||
└── Cargo.toml # Desktop dependency workspace
|
||||
```
|
||||
|
||||
Keep `external/` and `secrets/` untouched. Repository pruning must preserve
|
||||
untracked and ignored local files; delete only Git-tracked paths unless the user
|
||||
explicitly authorizes a cache cleanup.
|
||||
|
||||
## Code Style
|
||||
|
||||
- Rust with no minimum supported version
|
||||
- SQLx for SQLite; prefer `sqlx::query!` / `sqlx::query_as!` compile-time checked macros over runtime `sqlx::query()` whenever possible
|
||||
- A mix of wreq and reqwest for Rust HTTP clients
|
||||
- Never use `println!` or `eprintln!` outside of tests; use `log` crate macros instead
|
||||
- When two crates export the same type name, alias imports with a suffix: `use foo::Bar as BarFoo;`
|
||||
- Prefer `use` imports over inline fully-qualified paths; only qualify inline for true one-offs or std collisions
|
||||
- TypeScript with Nx, React, Vite, Zustand, and Three.js
|
||||
- Use two spaces for indentation
|
||||
- Each Tauri command belongs in its own file module, named after the command. Keep shared state, types, and helpers in separate support modules; see the [Storyteller command conventions](crates/desktop/artcraft/src/services/storyteller/commands/AGENTS.md).
|
||||
|
||||
### File Layout
|
||||
|
||||
Organize for top-to-bottom reading. Important things first, details later.
|
||||
|
||||
- **Constants** at the top (after imports)
|
||||
- **Structs/enums** next; outer structs above inner sub-structs
|
||||
- **API types** in order: Request, Response, Error
|
||||
- **In impl blocks**: constructors first, then public methods, then private helpers
|
||||
- Private helpers go *below* the methods that call them
|
||||
- Among helpers: meatier logic above leaf-level formatters
|
||||
- **In test modules**: constants first, then test cases (grouped into sub-modules when 2+), then helper functions last
|
||||
|
||||
## Markdown
|
||||
|
||||
- **Tables must be space-padded so columns align in plain text.** Markdown
|
||||
tables are read raw (terminals, diffs, editors) at least as often as
|
||||
rendered, and condensed tables are unreadable there. Pad every cell to its
|
||||
column width:
|
||||
|
||||
```markdown
|
||||
| Model | Configuration | Credits | Speed | Score |
|
||||
|--------------|---------------|--------------------|---------|
|
||||
| Meshy 6 | text or image | 104 | 80.0 | +24 |
|
||||
| Rodin 2.5 | text or image | 13 | 10.0 | +3 |
|
||||
```
|
||||
|
||||
Not: `| Model | Configuration | Credits | Speed | Score |` packed tight
|
||||
with varying widths per row.
|
||||
Generated
+6009
-8099
File diff suppressed because it is too large
Load Diff
+65
-124
@@ -1,129 +1,70 @@
|
||||
[workspace]
|
||||
resolver = "2"
|
||||
|
||||
# Desktop application and its local build/test dependencies.
|
||||
members = [
|
||||
"artcraft-native", # Pure Rust (egui) desktop app; no Tauri or webview
|
||||
"crates/api_clients/artcraft/artcraft_api_defs",
|
||||
"crates/api_clients/artcraft/artcraft_client",
|
||||
"crates/api_clients/artcraft/artcraft_router",
|
||||
"crates/api_clients/fal_client",
|
||||
"crates/api_clients/gmicloud_client",
|
||||
"crates/api_clients/grok_api_client",
|
||||
"crates/api_clients/grok_consumer_client",
|
||||
"crates/api_clients/kinovi_web_client",
|
||||
"crates/api_clients/midjourney_client",
|
||||
"crates/api_clients/openai_sora_client",
|
||||
"crates/api_clients/worldlabs_api_client",
|
||||
"crates/api_clients/worldlabs_consumer_client",
|
||||
"crates/desktop/artcraft",
|
||||
"crates/lib/artcraft_client_identity",
|
||||
"crates/lib/browser_emulation",
|
||||
"crates/lib/build_metadata",
|
||||
"crates/lib/caching/memory_store",
|
||||
"crates/lib/cloudflare_errors",
|
||||
"crates/lib/cloudflare_mitigation",
|
||||
"crates/lib/cookie_store",
|
||||
"crates/lib/cookie_store_wrapper",
|
||||
"crates/lib/data/primitives",
|
||||
"crates/lib/data/web_base64",
|
||||
# Types: common strongly-typed tokens, ids, payloads, etc.
|
||||
# Also common cloud buckets and other "strongly typed" configs.
|
||||
"crates/_types/buckets",
|
||||
"crates/_types/enums",
|
||||
"crates/_types/reusable_types", # TODO(bt, 2022-12-21): Deprecate
|
||||
"crates/_types/tokens",
|
||||
|
||||
# CLI tools for operations, etc.
|
||||
"crates/cli/database_backup",
|
||||
"crates/cli/database_cleanup",
|
||||
"crates/cli/scrape_test",
|
||||
|
||||
# Shared libraries
|
||||
"crates/lib/actix_helpers",
|
||||
"crates/lib/api_clients/cloud_storage",
|
||||
"crates/lib/api_clients/fakeyou_client",
|
||||
"crates/lib/api_clients/google_drive_common",
|
||||
"crates/lib/api_clients/twitch_common",
|
||||
"crates/lib/caching/memory_caching",
|
||||
"crates/lib/caching/redis_caching",
|
||||
"crates/lib/concurrency",
|
||||
"crates/lib/crockford",
|
||||
"crates/lib/datetimes",
|
||||
"crates/lib/deprecated/config", # TODO(bt, 2023-02-13): Deprecate or repurpose
|
||||
"crates/lib/deprecated/container_common", # TODO(bt, 2023-02-13): Pull apart and rehome pieces
|
||||
"crates/lib/deprecated/http_server_common", # TODO(bt, 2023-03-20): Move useful pieces to actix_helpers
|
||||
"crates/lib/envvar",
|
||||
"crates/lib/errors",
|
||||
"crates/lib/files/file_to_base64_url",
|
||||
"crates/lib/files/filesys",
|
||||
"crates/lib/files/images",
|
||||
"crates/lib/files/mimetypes",
|
||||
"crates/lib/http_headers",
|
||||
"crates/lib/jwt_light",
|
||||
"crates/lib/url_utils",
|
||||
"crates/lib/uuid_utils",
|
||||
"crates/schema/database/sqlite_tasks",
|
||||
"crates/schema/public/enums",
|
||||
"crates/schema/public/tokens",
|
||||
"crates/testing/test_data",
|
||||
"crates/testing/test_utils",
|
||||
"crates/vendor/tauri-plugin-http",
|
||||
"crates/lib/files",
|
||||
"crates/lib/filesys",
|
||||
"crates/lib/hashing",
|
||||
"crates/lib/idempotency",
|
||||
"crates/lib/jobs_common",
|
||||
"crates/lib/media",
|
||||
"crates/lib/mysql_queries",
|
||||
"crates/lib/redis_common",
|
||||
"crates/lib/sqlite_queries",
|
||||
"crates/lib/sqlx_mysql_helpers",
|
||||
"crates/lib/subprocess_common",
|
||||
"crates/lib/tts_common",
|
||||
"crates/lib/url_config",
|
||||
"crates/lib/user_input_common",
|
||||
"crates/lib/web_scrapers",
|
||||
|
||||
# Programs: Desktop apps
|
||||
"crates/programs/aichatbot-sidecar",
|
||||
"crates/programs/mdm-viewer",
|
||||
|
||||
# Services: API servers, jobs, workers, etc.
|
||||
"crates/service/job/analytics_job",
|
||||
"crates/service/job/download_job",
|
||||
"crates/service/job/inference_job",
|
||||
"crates/service/job/tts_download_job",
|
||||
"crates/service/job/tts_inference_job",
|
||||
"crates/service/job/w2l_download_job",
|
||||
"crates/service/job/w2l_inference_job",
|
||||
"crates/service/other/twitch_pubsub_subscriber",
|
||||
"crates/service/web/dummy_service",
|
||||
"crates/service/web/image_generation_service",
|
||||
"crates/service/web/storyteller_web",
|
||||
"crates/service/web/websocket_gateway",
|
||||
|
||||
# Service Plugins: complete subsystems that can be plugged into services (users, billing, etc.)
|
||||
"crates/service/plugins/billing",
|
||||
"crates/service/plugins/inference_workloads",
|
||||
"crates/service/plugins/users",
|
||||
]
|
||||
default-members = ["crates/desktop/artcraft"]
|
||||
|
||||
[workspace.package]
|
||||
publish = false
|
||||
|
||||
[workspace.dependencies]
|
||||
artcraft_api_defs = { path = "crates/api_clients/artcraft/artcraft_api_defs" }
|
||||
artcraft_client = { path = "crates/api_clients/artcraft/artcraft_client" }
|
||||
artcraft_client_identity = { path = "crates/lib/artcraft_client_identity" }
|
||||
artcraft_router = { path = "crates/api_clients/artcraft/artcraft_router" }
|
||||
browser_emulation = { path = "crates/lib/browser_emulation" }
|
||||
cookie_store_wrapper = { path = "crates/lib/cookie_store_wrapper" }
|
||||
build_metadata = { path = "crates/lib/build_metadata" }
|
||||
cloudflare_errors = { path = "crates/lib/cloudflare_errors" }
|
||||
cloudflare_mitigation = { path = "crates/lib/cloudflare_mitigation" }
|
||||
cookie_store = { path = "crates/lib/cookie_store" }
|
||||
enums = { path = "crates/schema/public/enums" }
|
||||
errors = { path = "crates/lib/errors" }
|
||||
fal_client = { path = "crates/api_clients/fal_client" }
|
||||
gmicloud_client = { path = "crates/api_clients/gmicloud_client" }
|
||||
file_to_base64_url = { path = "crates/lib/files/file_to_base64_url" }
|
||||
filesys = { path = "crates/lib/files/filesys" }
|
||||
grok_api_client = { path = "crates/api_clients/grok_api_client" }
|
||||
grok_consumer_client = { path = "crates/api_clients/grok_consumer_client" }
|
||||
http_headers = { path = "crates/lib/http_headers" }
|
||||
images = { path = "crates/lib/files/images" }
|
||||
jwt_light = { path = "crates/lib/jwt_light" }
|
||||
memory_store = { path = "crates/lib/caching/memory_store" }
|
||||
midjourney_client = { path = "crates/api_clients/midjourney_client" }
|
||||
mimetypes = { path = "crates/lib/files/mimetypes" }
|
||||
openai_sora_client = { path = "crates/api_clients/openai_sora_client" }
|
||||
primitives = { path = "crates/lib/data/primitives" }
|
||||
kinovi_web_client = { path = "crates/api_clients/kinovi_web_client" }
|
||||
sqlite_tasks = { path = "crates/schema/database/sqlite_tasks" }
|
||||
test_data = { path = "crates/testing/test_data" }
|
||||
test_utils = { path = "crates/testing/test_utils" }
|
||||
tokens = { path = "crates/schema/public/tokens" }
|
||||
url_utils = { path = "crates/lib/url_utils" }
|
||||
uuid_utils = { path = "crates/lib/uuid_utils" }
|
||||
web_base64 = { path = "crates/lib/data/web_base64" }
|
||||
worldlabs_api_client = { path = "crates/api_clients/worldlabs_api_client" }
|
||||
worldlabs_consumer_client = { path = "crates/api_clients/worldlabs_consumer_client" }
|
||||
anyhow = "1.0.102"
|
||||
assertor = "0.0.3"
|
||||
base64 = "0.22.1"
|
||||
bstr = "1.12.1"
|
||||
byteorder = "1.5.0"
|
||||
chrono = { version = "0.4.44", features = ["serde"] }
|
||||
cookie = "0.18.1"
|
||||
env = "1.0.1"
|
||||
env_logger = "0.11.9"
|
||||
futures = { version = "0.3.32" }
|
||||
indexmap = "2.13.0"
|
||||
log = "0.4.29"
|
||||
once_cell = "1.21.4"
|
||||
rand = "0.9.1"
|
||||
regex = "1.12.3"
|
||||
reqwest = { version = "0.12.23", default-features = false, features = [ "charset", "http2", "system-proxy", "json", "cookies", "rustls-tls", "gzip" ] }
|
||||
serde = { version = "1.0.228", features = ["derive"] }
|
||||
serde_derive = "1.0.228"
|
||||
serde_json = "1.0.149"
|
||||
sha2 = "0.11.0-rc.5"
|
||||
sha3 = "0.10.8"
|
||||
strum = { version = "0.28.0", features = ["derive"] }
|
||||
tauri = { version = "2.11.2", features = ["unstable", "devtools", "protocol-asset"] } # NB: "devtools" prevents App Store distribution; "ubstable" is needed for window access
|
||||
tauri-plugin-dialog = "2.7.1"
|
||||
tauri-plugin-fs = "2.5.1"
|
||||
tauri-plugin-log = "2.8.0"
|
||||
tauri-plugin-upload = "2.4.0"
|
||||
tempdir = "0.3.7"
|
||||
tempfile = "3.27.0"
|
||||
thiserror = "2.0.18"
|
||||
tokio = { version = "1.50.0", features = ["macros"] }
|
||||
tokio-util = "0.7.12"
|
||||
toml = "1.0.7+spec-1.1.0"
|
||||
url = { version = "2.5.8", features = ["serde"] }
|
||||
utoipa = { version = "5.4.0", features = ["chrono", "actix_extras", "url"] }
|
||||
uuid = "1.22.0"
|
||||
walkdir = "2.4.0"
|
||||
# wreq native prerequisites:
|
||||
# macOS: brew install cmake pkg-config llvm
|
||||
# Windows: choco install cmake strawberryperl pkgconfiglite llvm nasm
|
||||
wreq-util = "3.0.0-rc.10"
|
||||
wreq = { version = "6.0.0-rc.28", features = ["json", "query", "form", "multipart", "cookies", "ws", "stream", "gzip", "brotli", "zstd", "deflate"] }
|
||||
|
||||
-70
@@ -1,70 +0,0 @@
|
||||
# Persist clippy check configurations:
|
||||
# https://github.com/ericseppanen/cargo-cranky
|
||||
# Clippy doesn't allow this (yet):
|
||||
# https://github.com/rust-lang/rust-clippy/issues/6625
|
||||
deny = [
|
||||
# Rust
|
||||
"unsafe_code", # We may remove this if needed
|
||||
|
||||
# Clippy - "serious" lints
|
||||
"clippy::borrow_interior_mutable_const", # Seems dangerous to allow this. Eg. `const F = Lazy::new()`;
|
||||
|
||||
# Clippy - bad math
|
||||
#"clippy::cast_possible_truncation", # Seems dangerous, eg f64 to u64.
|
||||
#"clippy::cast_possible_wrap",
|
||||
|
||||
# Clippy - pointer safety / readability / correctness
|
||||
"clippy::as_ptr_cast_mut",
|
||||
"clippy::borrow_as_ptr",
|
||||
"clippy::borrow_deref_ref",
|
||||
|
||||
# Clippy async
|
||||
"clippy::await_holding_lock",
|
||||
|
||||
# Clippy - performance
|
||||
"clippy::cmp_owned",
|
||||
"clippy::useless_vec",
|
||||
"clippy::format_in_format_args",
|
||||
#"clippy::redundant_clone",
|
||||
#"clippy::unnecessary_to_owned",
|
||||
#"clippy::vec_init_then_push",
|
||||
|
||||
# Clippy - "ergonomic" / "usage" lints
|
||||
"clippy::clone_on_copy", # Don't use clone() on Copy types
|
||||
"clippy::option_option", # Lint: Don't ever Option<Option<T>>! Enums instead.
|
||||
"clippy::zero_sized_map_values", # Lint: use a HashSet instead of HashMap<T, ()>.
|
||||
|
||||
# Clippy - "aesthetic" / "style" lints
|
||||
"clippy::clone_double_ref", # Lint: feels safer/hygienic to disallow this, but I'm not strongly opposed.
|
||||
"clippy::double_comparisons", # Lint: readability
|
||||
"clippy::empty_structs_with_brackets", # Lint: {} feels less aesthetic than ;
|
||||
"clippy::implicit_clone", # Lint: Recommendation here seems more idiomatic.
|
||||
"clippy::needless_late_init", # Don't declare variables without initializing them
|
||||
"clippy::needless_return", # Lint: not the worst offender, but let's try to keep unnecessary returns out.
|
||||
"clippy::redundant_field_names", # Lint: Duplicate names is a burden when reading: you wonder why they're dupes.
|
||||
"clippy::redundant_static_lifetimes", # Lint: feels more modern to elide static lifetimes
|
||||
|
||||
# Rust
|
||||
"non_fmt_panics", # panic!(format!(...)) will be unsupported soon
|
||||
"unused_imports", # These are easy to automatically cull
|
||||
"unused_mut", # Be strict about mutability
|
||||
"unused_parens", # Unused parens are ugly
|
||||
"unused_variables", # Don't leave cruft
|
||||
|
||||
# Possible:
|
||||
# "clippy::ptr_arg",
|
||||
# "clippy::too_many_lines" (note: this is configurable)
|
||||
]
|
||||
|
||||
warn = [
|
||||
]
|
||||
|
||||
allow = [
|
||||
# Rust
|
||||
"deprecated", # Other systems can handle deprecated code instead of the linter
|
||||
"missing_docs", # Not everything needs documentation. Too much documentation is bad. Literate code.
|
||||
|
||||
# Clippy
|
||||
"clippy::if_same_then_else", # There are cases this is clearer! Branches may have notes / considerations.
|
||||
"clippy::single_char_pattern", # This is like punishing trailing commas. Chars are dumb.
|
||||
]
|
||||
+233
@@ -0,0 +1,233 @@
|
||||
# ================================================================
|
||||
# =============== (1) set up core rust build image ===============
|
||||
# ================================================================
|
||||
|
||||
# Custom base image
|
||||
# Make sure to add this repository so it has read acces to the base image:
|
||||
# https://github.com/orgs/storytold/packages/container/docker-base-images-rust-ssl/settings/actions_access
|
||||
# FROM ghcr.io/storytold/docker-base-images-rust-ssl:d94ce4350c3b as rust-build
|
||||
FROM ubuntu:jammy as rust-base
|
||||
|
||||
# NB: This can be "stable" or another version.
|
||||
ARG RUST_TOOLCHAIN="1.66.0"
|
||||
|
||||
WORKDIR /tmp
|
||||
|
||||
# NB: cmake is required for freetype-sys-0.13.1, which in turn has only been added for egui.
|
||||
# NB: fontconfig is required by servo-fontconfig-sys, which is in the dependency chain for egui.
|
||||
# NB: libfontconfig-dev is required by servo-fontconfig-sys, which is in the dependency chain for egui.
|
||||
# NB: pkg-config and libssl are for container TLS; we may switch to rustls in the future.
|
||||
RUN apt-get update \
|
||||
&& DEBIAN_FRONTEND=noninteractive TZ=Etc/UTC apt-get install -y \
|
||||
build-essential \
|
||||
cmake \
|
||||
curl \
|
||||
fontconfig \
|
||||
libfontconfig1-dev \
|
||||
libssl-dev \
|
||||
pkg-config
|
||||
|
||||
# NB: Fix for fontconfig (servo-fontconfig-sys): https://github.com/alacritty/alacritty/issues/4423#issuecomment-727277235
|
||||
# TODO(bt, 2023-02-23): This has not been verified to work yet.
|
||||
RUN export PKG_CONFIG_PATH=/usr/lib/x86_64-linux-gnu/pkgconfig
|
||||
|
||||
# Install Rust
|
||||
RUN curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs \
|
||||
| sh -s -- --default-toolchain $RUST_TOOLCHAIN -y
|
||||
|
||||
# Install correct Rust version
|
||||
#RUN $HOME/.cargo/bin/rustup install $RUST_VERSION
|
||||
#RUN $HOME/.cargo/bin/rustup default $RUST_VERSION
|
||||
|
||||
# Report Rust version for build debugging
|
||||
RUN $HOME/.cargo/bin/rustup show
|
||||
RUN $HOME/.cargo/bin/rustc --version
|
||||
RUN $HOME/.cargo/bin/cargo --version
|
||||
|
||||
# Cargo Chef does Rust build caching: https://github.com/LukeMathWalker/cargo-chef
|
||||
# TODO(bt,2023-03-08): builds are failing with "no space left on device"; disabling build caching
|
||||
#RUN $HOME/.cargo/bin/cargo install cargo-chef --locked
|
||||
|
||||
# ======================================================================
|
||||
# =============== (2) use cargo-chef to "plan" the build ===============
|
||||
# ======================================================================
|
||||
|
||||
FROM rust-base AS planner
|
||||
|
||||
# NB: Copying in everything does not appear to impact cached builds if irrelevant files are changed (at least at this step)
|
||||
# TODO(bt,2023-03-08): builds are failing with "no space left on device"; disabling build caching
|
||||
#COPY . .
|
||||
# TODO(bt,2023-03-08): builds are failing with "no space left on device"; disabling build caching
|
||||
#RUN $HOME/.cargo/bin/cargo chef prepare --recipe-path recipe.json
|
||||
|
||||
# ======================================================================================================
|
||||
# =============== (3) "cook" the libraries (cacheable), then run the app build and tests ===============
|
||||
# ======================================================================================================
|
||||
|
||||
FROM rust-base AS builder
|
||||
|
||||
# TODO(bt,2023-03-08): builds are failing with "no space left on device"; disabling build caching
|
||||
#COPY --from=planner /tmp/recipe.json recipe.json
|
||||
|
||||
# NB: This step builds and caches the dependencies as its own layer.
|
||||
# TODO(bt,2023-03-08): builds are failing with "no space left on device"; disabling build caching
|
||||
#RUN $HOME/.cargo/bin/cargo chef cook --release --recipe-path recipe.json
|
||||
|
||||
# NB: Now we build and test our code.
|
||||
COPY Cargo.lock .
|
||||
COPY Cargo.toml .
|
||||
COPY crates/ ./crates
|
||||
COPY db/ ./db
|
||||
COPY test_data/ ./test_data
|
||||
COPY container_includes/ ./container_includes
|
||||
|
||||
# Print a report on disk space
|
||||
#RUN echo "Disk usage at root (before tests):"
|
||||
#RUN du -hsc / | sort -hr
|
||||
RUN echo "Disk usage at current directory (before tests):"
|
||||
RUN pwd
|
||||
RUN du -hsc * | sort -hr
|
||||
|
||||
# Run all of the tests
|
||||
#RUN SQLX_OFFLINE=true \
|
||||
# LD_LIBRARY_PATH=/usr/lib:${LD_LIBRARY_PATH} \
|
||||
# $HOME/.cargo/bin/cargo test
|
||||
|
||||
# Print a report on disk space
|
||||
#RUN echo "Disk usage at root (after tests):"
|
||||
#RUN du -hsc / | sort -hr
|
||||
RUN echo "Disk usage at current directory (after tests):"
|
||||
RUN pwd
|
||||
RUN du -hsc * | sort -hr
|
||||
|
||||
# Build all the binaries.
|
||||
RUN SQLX_OFFLINE=true \
|
||||
LD_LIBRARY_PATH=/usr/lib:${LD_LIBRARY_PATH} \
|
||||
$HOME/.cargo/bin/cargo build \
|
||||
--release \
|
||||
--bin storyteller-web
|
||||
|
||||
RUN SQLX_OFFLINE=true \
|
||||
LD_LIBRARY_PATH=/usr/lib:${LD_LIBRARY_PATH} \
|
||||
$HOME/.cargo/bin/cargo build \
|
||||
--release \
|
||||
--bin dummy-service
|
||||
|
||||
RUN SQLX_OFFLINE=true \
|
||||
LD_LIBRARY_PATH=/usr/lib:${LD_LIBRARY_PATH} \
|
||||
$HOME/.cargo/bin/cargo build \
|
||||
--release \
|
||||
--bin analytics-job
|
||||
|
||||
RUN SQLX_OFFLINE=true \
|
||||
LD_LIBRARY_PATH=/usr/lib:${LD_LIBRARY_PATH} \
|
||||
$HOME/.cargo/bin/cargo build \
|
||||
--release \
|
||||
--bin download-job
|
||||
|
||||
RUN SQLX_OFFLINE=true \
|
||||
LD_LIBRARY_PATH=/usr/lib:${LD_LIBRARY_PATH} \
|
||||
$HOME/.cargo/bin/cargo build \
|
||||
--release \
|
||||
--bin inference-job
|
||||
|
||||
RUN SQLX_OFFLINE=true \
|
||||
LD_LIBRARY_PATH=/usr/lib:${LD_LIBRARY_PATH} \
|
||||
$HOME/.cargo/bin/cargo build \
|
||||
--release \
|
||||
--bin tts-download-job
|
||||
|
||||
RUN SQLX_OFFLINE=true \
|
||||
LD_LIBRARY_PATH=/usr/lib:${LD_LIBRARY_PATH} \
|
||||
$HOME/.cargo/bin/cargo build \
|
||||
--release \
|
||||
--bin w2l-download-job
|
||||
|
||||
RUN SQLX_OFFLINE=true \
|
||||
LD_LIBRARY_PATH=/usr/lib:${LD_LIBRARY_PATH} \
|
||||
$HOME/.cargo/bin/cargo build \
|
||||
--release \
|
||||
--bin tts-inference-job
|
||||
|
||||
RUN SQLX_OFFLINE=true \
|
||||
LD_LIBRARY_PATH=/usr/lib:${LD_LIBRARY_PATH} \
|
||||
$HOME/.cargo/bin/cargo build \
|
||||
--release \
|
||||
--bin w2l-inference-job
|
||||
|
||||
RUN SQLX_OFFLINE=true \
|
||||
LD_LIBRARY_PATH=/usr/lib:${LD_LIBRARY_PATH} \
|
||||
$HOME/.cargo/bin/cargo build \
|
||||
--release \
|
||||
--bin websocket-gateway
|
||||
|
||||
RUN SQLX_OFFLINE=true \
|
||||
LD_LIBRARY_PATH=/usr/lib:${LD_LIBRARY_PATH} \
|
||||
$HOME/.cargo/bin/cargo build \
|
||||
--release \
|
||||
--bin twitch-pubsub-subscriber
|
||||
|
||||
# Print a report on disk space
|
||||
RUN echo "Disk usage at current directory (after all builds):"
|
||||
RUN pwd
|
||||
RUN du -hsc * | sort -hr
|
||||
|
||||
# =============================================================
|
||||
# =============== (4) construct the final image ===============
|
||||
# =============================================================
|
||||
|
||||
# Final image
|
||||
FROM ubuntu:jammy as final
|
||||
|
||||
# See: https://github.com/opencontainers/image-spec/blob/master/annotations.md
|
||||
LABEL org.opencontainers.image.title='Storyteller Rust'
|
||||
LABEL org.opencontainers.image.authors='bt@brand.io, echelon@gmail.com'
|
||||
LABEL org.opencontainers.image.description='All of the binaries from the Rust monorepo'
|
||||
LABEL org.opencontainers.image.documentation='https://github.com/storytold/storyteller-web'
|
||||
LABEL org.opencontainers.image.source='https://github.com/storytold/storyteller-web'
|
||||
LABEL org.opencontainers.image.url='https://github.com/storytold/storyteller-web'
|
||||
|
||||
WORKDIR /
|
||||
|
||||
# Give the container its version so it can report over HTTP.
|
||||
ARG GIT_SHA
|
||||
RUN echo -n ${GIT_SHA} > GIT_SHA
|
||||
|
||||
# Copy all the binaries.
|
||||
COPY --from=builder /tmp/target/release/storyteller-web /
|
||||
COPY --from=builder /tmp/target/release/dummy-service /
|
||||
COPY --from=builder /tmp/target/release/analytics-job /
|
||||
COPY --from=builder /tmp/target/release/download-job /
|
||||
COPY --from=builder /tmp/target/release/inference-job /
|
||||
COPY --from=builder /tmp/target/release/tts-download-job /
|
||||
COPY --from=builder /tmp/target/release/tts-inference-job /
|
||||
COPY --from=builder /tmp/target/release/w2l-download-job /
|
||||
COPY --from=builder /tmp/target/release/w2l-inference-job /
|
||||
COPY --from=builder /tmp/target/release/websocket-gateway /
|
||||
COPY --from=builder /tmp/target/release/twitch-pubsub-subscriber /
|
||||
|
||||
# SSL certs are required for crypto
|
||||
COPY --from=builder /etc/ssl /etc/ssl
|
||||
|
||||
# Required dynamically linked libraries
|
||||
COPY --from=builder /usr/lib/x86_64-linux-gnu/libssl.* /lib/x86_64-linux-gnu/
|
||||
COPY --from=builder /usr/lib/x86_64-linux-gnu/libcrypto.* /lib/x86_64-linux-gnu/
|
||||
|
||||
# Container includes
|
||||
COPY container_includes/ /container_includes
|
||||
|
||||
# Make sure all the links resolve
|
||||
RUN ldd storyteller-web
|
||||
|
||||
# Without a .env file, Rust crashes "mysteriously" (ugh)
|
||||
RUN touch .env
|
||||
RUN touch .env-secrets
|
||||
|
||||
# Some services have default env files that live under their code directories
|
||||
# These should also be readable from the relative current path
|
||||
COPY crates/service/job/download_job/download-job.env .
|
||||
COPY crates/service/job/inference_job/inference-job.env .
|
||||
COPY crates/service/web/storyteller_web/storyteller-web.env .
|
||||
|
||||
EXPOSE 8080
|
||||
CMD LD_LIBRARY_PATH=/usr/lib /storyteller-web
|
||||
-177
@@ -1,177 +0,0 @@
|
||||
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
-21
@@ -1,21 +0,0 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2026 ArtCraft Team and the ArtCraft contributors
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
@@ -1,51 +0,0 @@
|
||||
ArtCraft
|
||||
Copyright (c) 2026 ArtCraft Team and the ArtCraft contributors.
|
||||
Licensed under the MIT License or the Apache License, Version 2.0, at your option
|
||||
(LICENSE-MIT, LICENSE-APACHE).
|
||||
|
||||
The ArtCraft name, wordmark and logos are trademarks of the ArtCraft Team. They are not covered
|
||||
by the license above.
|
||||
|
||||
Third-party material
|
||||
|
||||
This product bundles third-party material under its own licenses, listed below.
|
||||
|
||||
ammo.js (frontend/apps/artcraft/app/public/jsm/libs/ammo.wasm.*) — zlib License. A port of the
|
||||
Bullet Physics engine. https://github.com/kripken/ammo.js
|
||||
tauri-plugin-http (crates/vendor/tauri-plugin-http/) — MIT or Apache-2.0. Copyright (c) 2017 -
|
||||
Present Tauri Apps Contributors; Copyright 2019-2023 Tauri Programme within The Commons
|
||||
Conservancy. Modified fork; see that directory's LICENSE_MIT and LICENSE_APACHE-2.0.
|
||||
normalize.css v8.0.1 (frontend/apps/artcraft/app/src/styles/normalize.css) — MIT License.
|
||||
Copyright (c) Nicolas Gallagher and Jonathan Neal. https://github.com/necolas/normalize.css
|
||||
three-dots v0.3.2 (portion of frontend/apps/artcraft/app/src/styles/base.css) — MIT License.
|
||||
Copyright (c) 2018 nzbin. https://nzbin.github.io/three-dots/
|
||||
Font Awesome Free icons (frontend/libs/icons/src/lib/brand-icons.tsx,
|
||||
frontend/apps/artcraft/app/public/resources/images/services/generic.svg) — CC BY 4.0.
|
||||
Copyright Fonticons, Inc. https://fontawesome.com/license/free
|
||||
Carter One font (frontend/apps/artcraft/app/public/resources/fonts/CarterOneRegular.json) —
|
||||
SIL Open Font License 1.1. https://fonts.google.com/specimen/Carter+One
|
||||
Inter, Archivo and Geist Mono fonts (artcraft-native/assets/fonts/) — SIL Open Font License 1.1;
|
||||
each font's license is next to it. https://rsms.me/inter/ ·
|
||||
https://fonts.google.com/specimen/Archivo · https://vercel.com/font
|
||||
|
||||
Third-party trademarks
|
||||
|
||||
The AI provider and service logos in frontend/apps/artcraft/app/public/resources/images/services/
|
||||
are trademarks of their respective owners. They are used only to identify those services and are
|
||||
not covered by the license above.
|
||||
|
||||
Material not covered by the license above
|
||||
|
||||
The following files are owned by third parties. They are not licensed under MIT or Apache-2.0,
|
||||
and no rights to them are granted by this repository. They remain the property of their
|
||||
respective owners. These assets will be phased out of ArtCraft over time.
|
||||
|
||||
frontend/apps/artcraft/app/public/resources/sound/ — sound effects from Nintendo games (Golden
|
||||
Sun, Metroid Prime, The Legend of Zelda: Ocarina of Time, Super Mario RPG).
|
||||
frontend/apps/artcraft/app/public/resources/memes/ — meme images and likenesses owned by their
|
||||
respective rights holders.
|
||||
frontend/apps/artcraft/app/public/resources/skybox/gray/ and skybox/night/ — skybox textures
|
||||
from a third-party asset pack.
|
||||
frontend/apps/artcraft/app/public/resources/pose/Lumine Idle cycle.vmd — third-party MMD motion
|
||||
data.
|
||||
frontend/apps/artcraft/app/public/resources/characters_gif/ — renders of Mixamo animations.
|
||||
@@ -1,152 +1,46 @@
|
||||
<h1 align="center">ArtCraft</h1>
|
||||
storyteller-rust
|
||||
================
|
||||
|
||||
<p align="center">
|
||||
<a href="https://sonarcloud.io/project/overview?id=storytold_artcraft"><img alt="Scanned by SonarCloud" src="https://img.shields.io/badge/Scanned_by-SonarCloud-4b9fd5?style=flat-square&logo=sonarqubecloud&logoColor=white"></a>
|
||||
<a href="https://coderabbit.ai"><img alt="Reviewed by CodeRabbit" src="https://img.shields.io/coderabbit/prs/github/storytold/artcraft?style=flat-square&label=Reviewed%20by%20CodeRabbit&logo=coderabbit"></a>
|
||||
</p>
|
||||
<p align="center"><strong>The IDE for artists.</strong></p>
|
||||
Our Rust monorepo, which serves both our API surface area and our downloadable client applications.
|
||||
|
||||
<!-- Main video: https://www.youtube.com/watch?v=kzvQMdg66Go
|
||||
GitHub media issue: https://github.com/storytold/artcraft/issues/1951 -->
|
||||
|
||||
https://github.com/user-attachments/assets/7339bd09-3e0d-44a7-a024-af9de64e4c11
|
||||
|
||||
<p align="center">
|
||||
<a href="https://www.youtube.com/watch?v=kzvQMdg66Go">Watch the two-minute ArtCraft demo on YouTube</a>
|
||||
</p>
|
||||
|
||||
<p align="center">
|
||||
<a href="https://discord.gg/artcraft"><img alt="Discord members online" src="https://img.shields.io/discord/1359579021108842617?style=for-the-badge&label=Discord&color=5865F2&logo=discord&logoColor=white"></a>
|
||||
<a href="https://www.youtube.com/@OfficialArtCraftStudios"><img alt="YouTube" src="https://img.shields.io/badge/YouTube-FF0000?style=for-the-badge&logo=youtube&logoColor=white"></a>
|
||||
<a href="https://x.com/get_artcraft"><img alt="X" src="https://img.shields.io/badge/X-181717?style=for-the-badge&logo=x&logoColor=white"></a>
|
||||
<a href="https://www.linkedin.com/company/artcraft-ai"><img alt="LinkedIn" src="https://img.shields.io/badge/LinkedIn-0A66C2?style=for-the-badge"></a>
|
||||
</p>
|
||||
|
||||
<p align="center">
|
||||
<a href="https://getartcraft.com/">Download ArtCraft</a> ·
|
||||
<a href="https://github.com/storytold/artcraft/releases">GitHub releases</a> ·
|
||||
<a href="./_docs/dev_setup.md">Build from source</a>
|
||||
</p>
|
||||
|
||||
ArtCraft is the IDE for interactive AI image and video creation. Compose in 2D, stage scenes in 3D, and choose the models that fit your work. Turn prompting into *crafting* with visual tools for precise, repeatable results.
|
||||
|
||||
## Show, Don't Tell: Advanced Crafting Features
|
||||
|
||||
Artists need control over composition, character identity, and camera placement. Build the scene before you generate it. Click any preview to view it at full size.
|
||||
|
||||
| Feature | Preview |
|
||||
| ------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| **Image to Location**<br>Place virtual actors in a consistent environment and plan multiple shots in the same room. | <a href="https://github.com/user-attachments/assets/21f103e3-cc19-4882-a630-9caa1b76ae31"><img src="https://github.com/user-attachments/assets/21f103e3-cc19-4882-a630-9caa1b76ae31" width="180" alt="Image to Location demo"></a> |
|
||||
| **3D Image Compositing**<br>Layer backdrops, foregrounds, and props in 3D to build compositions with depth. | <a href="https://github.com/user-attachments/assets/f93a616f-571d-474e-bcc0-53736de7303d"><img src="https://github.com/user-attachments/assets/f93a616f-571d-474e-bcc0-53736de7303d" width="180" alt="3D Image Compositing demo"></a> |
|
||||
| **2D Image Compositing**<br>Compose a scene with image layers, background removal, and drawing tools. | <a href="https://github.com/user-attachments/assets/d6f99391-e496-4c62-9e37-29734ba5f899"><img src="https://github.com/user-attachments/assets/d6f99391-e496-4c62-9e37-29734ba5f899" width="180" alt="2D Image Compositing demo"></a> |
|
||||
| **Image to 3D Mesh**<br>Turn an image into a 3D object you can position, rotate, and frame precisely. | <a href="https://github.com/user-attachments/assets/600a405c-e360-48c1-9b42-6e657ae6243b"><img src="https://github.com/user-attachments/assets/600a405c-e360-48c1-9b42-6e657ae6243b" width="180" alt="Image to 3D Mesh demo"></a> |
|
||||
| **Character Posing**<br>Pose characters and set your camera before generating the final shot. | <a href="https://raw.githubusercontent.com/storytold/github-media/22a27373707b13f48fc56405f1f89ca143c43d5d/character-posing.webp"><img src="https://raw.githubusercontent.com/storytold/github-media/22a27373707b13f48fc56405f1f89ca143c43d5d/character-posing.webp" width="180" alt="Character Posing demo"></a> |
|
||||
| **Scene Blocking with Kitbashing**<br>Combine 3D asset kits to control camera angles, object placement, and depth. | <a href="https://github.com/user-attachments/assets/eef025ac-0346-4a46-a023-d48e23629eb5"><img src="https://github.com/user-attachments/assets/eef025ac-0346-4a46-a023-d48e23629eb5" width="180" alt="Scene Blocking with Kitbashing demo"></a> |
|
||||
| **Character Identity Transfer**<br>Use a posed mannequin to guide the placement and pose of your character. | <a href="https://github.com/user-attachments/assets/629119ee-8c76-4a83-9827-8c6c995a3ec1"><img src="https://github.com/user-attachments/assets/629119ee-8c76-4a83-9827-8c6c995a3ec1" width="180" alt="Character Identity Transfer demo"></a> |
|
||||
| **Background Removal**<br>Isolate subjects to use as props, layers, or backdrops in 2D and 3D scenes. | <a href="https://github.com/user-attachments/assets/90c65057-5531-404f-83af-b34e66e24ec1"><img src="https://github.com/user-attachments/assets/90c65057-5531-404f-83af-b34e66e24ec1" width="180" alt="Background Removal demo"></a> |
|
||||
| **Mixed Asset Crafting**<br>Combine image cutouts, worlds, and 3D meshes in one scene. | <a href="https://raw.githubusercontent.com/storytold/github-media/main/ship-editing.gif"><img src="https://raw.githubusercontent.com/storytold/github-media/main/ship-editing.gif" width="180" alt="Mixed Asset Crafting demo"></a> |
|
||||
|
||||
More previews are coming for scene blocking, canvas editing, and scene relighting.
|
||||
|
||||
## Quick and Easy Prompting
|
||||
|
||||
Start with a prompt when you want to explore an idea quickly, then use the canvas and scene tools to refine it.
|
||||
|
||||
| Feature | Preview |
|
||||
| ------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| **Text to Image**<br>Explore ideas across a wide range of image models with a text prompt. | <a href="https://github.com/user-attachments/assets/9cc289cd-faf4-4eaf-aed2-21134cce127c"><img src="https://github.com/user-attachments/assets/9cc289cd-faf4-4eaf-aed2-21134cce127c" width="180" alt="Text to Image demo"></a> |
|
||||
| **Prompted Image Editing**<br>Describe changes to an image with models such as Nano Banana Pro and GPT Image. | <a href="https://github.com/user-attachments/assets/a06fa6ad-936c-42d0-8767-48fdbb8ff141"><img src="https://github.com/user-attachments/assets/a06fa6ad-936c-42d0-8767-48fdbb8ff141" width="180" alt="Prompted Image Editing demo"></a> |
|
||||
| **Canvas Image Editing**<br>Use drawing, masks, and inpainting to guide exactly where an image changes. | <a href="https://github.com/user-attachments/assets/f036e08a-f3a6-417a-98ee-ec7f04b2b5ff"><img src="https://github.com/user-attachments/assets/f036e08a-f3a6-417a-98ee-ec7f04b2b5ff" width="180" alt="Canvas Image Editing demo"></a> |
|
||||
| **Image to Video**<br>Animate an image with your choice of video models and generation controls. | <a href="https://github.com/user-attachments/assets/2bc6c592-511e-4fba-b40f-03c96699b7f7"><img src="https://github.com/user-attachments/assets/2bc6c592-511e-4fba-b40f-03c96699b7f7" width="180" alt="Image to Video demo"></a> |
|
||||
|
||||
More previews are coming for image inpainting and image ingredients.
|
||||
|
||||
## Models and Providers Supported within ArtCraft
|
||||
|
||||
| Provider | Models and workflows |
|
||||
| ------------ | ----------------------------------------------------------------------------------------------- |
|
||||
| **ArtCraft** | Images, video, music and sound, 3D meshes, and worlds. Browse the complete model catalog below. |
|
||||
| Grok | Grok Imagine and Grok Video. |
|
||||
| Midjourney | Midjourney image generation. |
|
||||
| Sora | Sora 1, Sora 2, and GPT Image 1. |
|
||||
| World Labs | Marble world generation with Gaussian splats. |
|
||||
|
||||
<details>
|
||||
<summary><strong>ArtCraft: the full 62-model catalog</strong></summary>
|
||||
|
||||
<!-- Catalog source: artcraft-services/crates/service/web/storyteller_web/src/configs/omni_gen/
|
||||
image_models.rs, video_models/video_models.rs + by_type/, audio_models.rs,
|
||||
mesh_models.rs, and splat_models.rs. Include the GenerationProvider::Artcraft
|
||||
mappings, not every model defined for other providers. -->
|
||||
|
||||
† Currently disabled. ‡ Restricted model; not currently available in the desktop app.
|
||||
|
||||
#### Images (16)
|
||||
|
||||
| Family | Models |
|
||||
| ----------- | ----------------------------------------------------------------------------------- |
|
||||
| Nano Banana | Nano Banana; Nano Banana 2; Nano Banana Pro |
|
||||
| GPT Image | GPT Image 1.5; GPT Image 2; GPT Image 2.5 Flare; GPT Image 2.5 Sunburst |
|
||||
| FLUX | FLUX.1 [dev]; FLUX.1 [schnell]; FLUX 1.1 [pro]; FLUX 1.1 [pro] ultra |
|
||||
| Seedream | Seedream 4; Seedream 4.5; Seedream 5 Lite; Seedream 5.0 Pro; Seedream 5.0 Pro Ultra |
|
||||
|
||||
#### Video (25)
|
||||
|
||||
| Family | Models |
|
||||
| -------- | ----------------------------------------------------------------------------------------------------------------------------- |
|
||||
| Seedance | Seedance 1.0 Lite †; Seedance 1.5 Pro; Seedance 2.0 |
|
||||
| Kling | Kling 1.6 Pro; Kling 2.1 Pro †; Kling 2.1 Master †; Kling 2.5 Turbo Pro; Kling 2.6 Pro; Kling 3.0 Pro †; Kling 3.0 Standard † |
|
||||
| Veo | Veo 2 †; Veo 3 †; Veo 3 Fast; Veo 3.1; Veo 3.1 Fast; Veo 3.1 Lite |
|
||||
| Sora | Sora 2 †; Sora 2 Pro † |
|
||||
| Vidu | Vidu Q3; Vidu Q3 Turbo |
|
||||
| MiniMax | MiniMax H3; MiniMax H3 Turbo ‡; MiniMax H3 Ultra ‡ |
|
||||
| Flux | Flux 3; Flux 3 Draft |
|
||||
|
||||
#### Music and sound (5)
|
||||
|
||||
| Family | Models |
|
||||
| ---------- | ------------------------------------------------ |
|
||||
| Suno | Suno Music; Suno Remix; Suno Sounds; Suno Sample |
|
||||
| Seed Audio | Seed Audio 1.0 |
|
||||
|
||||
#### 3D meshes (11)
|
||||
|
||||
| Family | Models |
|
||||
| ---------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| Hunyuan 3D | Hunyuan 3D 2.0; Hunyuan 3D 2.1; Hunyuan 3D 3; Hunyuan 3D 3 Sketch; Hunyuan 3D 3.1 Pro; Hunyuan 3D 3.1 Rapid; Hunyuan 3D 3.1 Part; Hunyuan 3D 3.1 Smart Topology |
|
||||
| Tripo3D | Tripo3D H3.1 |
|
||||
| Meshy | Meshy 6 |
|
||||
| Rodin | Rodin 2.5 Fast |
|
||||
|
||||
#### Worlds and Gaussian splats (5)
|
||||
|
||||
| Family | Models |
|
||||
| ---------- | --------------------------------------------------------- |
|
||||
| Marble | Marble 1.0; Marble 1.0 Draft; Marble 1.1; Marble 1.1 Plus |
|
||||
| TripoSplat | TripoSplat |
|
||||
|
||||
</details>
|
||||
|
||||
Additional provider integrations are planned for Kling, Google, Runway, and Luma. We're also interested in supporting existing subscriptions with aggregators such as OpenArt and Freepik.
|
||||
|
||||
## Downloads
|
||||
|
||||
- [Stable releases for Windows, macOS, and Linux](https://getartcraft.com/)
|
||||
- [Latest builds on GitHub](https://github.com/storytold/artcraft/releases) (including AppImage, DEB, RPM, Arch, and Flatpak packages)
|
||||
- [Linux packaging notes](./packaging/linux/README.md)
|
||||
- [Build from source](./_docs/dev_setup.md), including Linux
|
||||
|
||||
## Documentation
|
||||
|
||||
- [Development setup](./_docs/dev_setup.md)
|
||||
- [Linux packaging](./packaging/linux/README.md)
|
||||
- [Developer documentation](./_docs)
|
||||
- [Desktop build and development scripts](./script/artcraft)
|
||||
- [Roadmap](./ROADMAP.md)
|
||||
- [License](#license)
|
||||
|
||||
## License
|
||||
|
||||
ArtCraft is dual-licensed under [MIT](LICENSE-MIT) or [Apache-2.0](LICENSE-APACHE), at your option.
|
||||
Copyright (c) 2026 ArtCraft Team and the ArtCraft contributors. Required notices, including
|
||||
third-party material not covered by this license, are in [NOTICE](NOTICE).
|
||||
```
|
||||
."-,.__
|
||||
`. `. ,
|
||||
.--' .._,'"-' `.
|
||||
. .' `'
|
||||
`. / ,'
|
||||
` '--. ,-"'
|
||||
`"` | \
|
||||
-. \, |
|
||||
`--Y.' ___.
|
||||
\ L._, \
|
||||
_., `. < <\ _
|
||||
,' ' `, `. | \ ( `
|
||||
../, `. ` | .\`. \ \_
|
||||
,' ,.. . _.,' ||\l ) '".
|
||||
, ,' \ ,'.-.`-._,' | . _._`.
|
||||
,' / \ \ `' ' `--/ | \ / / ..\
|
||||
.' / \ . |\__ - _ ,'` ` / / `.`.
|
||||
| ' .. `-...-" | `-' / / . `.
|
||||
| / |L__ | | / / `. `.
|
||||
, / . . | | / / ` `
|
||||
/ / ,. ,`._ `-_ | | _ ,-' / ` \
|
||||
/ . \"`_/. `-_ \_,. ,' +-' `-' _, ..,-. \`.
|
||||
. ' .-f ,' ` '. \__.---' _ .' ' \ \
|
||||
' / `.' l .' / \.. ,_|/ `. ,'` L`
|
||||
|' _.-""` `. \ _,' ` \ `.___`.'"`-. , | | | \
|
||||
|| ,' `. `. ' _,...._ ` | `/ ' | ' .|
|
||||
|| ,' `. ;.,.---' ,' `. `.. `-' .-' /_ .' ;_ ||
|
||||
|| ' V / / ` | ` ,' ,' '. ! `. ||
|
||||
||/ _,-------7 ' . | `-' l / `||
|
||||
. | ,' .- ,' || | .-. `. .' ||
|
||||
`' ,' `".' | | `. '. -.' `'
|
||||
/ ,' | |,' \-.._,.'/'
|
||||
. / . . \ .''
|
||||
.`. | `. / :_,'.'
|
||||
\ `...\ _ ,'-. .' /_.-' S T O R Y T E L L E R
|
||||
`-.__ `, `' . _.>----''. _ __ / Turning memes into dreams
|
||||
.' /"' | "' '_ One Rust Monorepo at a time
|
||||
/_|.-'\ ,". '.'`__'-( \
|
||||
/ ,"'"\,' `/ `-.|"
|
||||
```
|
||||
-47
@@ -1,47 +0,0 @@
|
||||
ArtCraft Roadmap
|
||||
================
|
||||
|
||||
## 2026 Goals (non-exhaustive)
|
||||
|
||||
Product
|
||||
|
||||
- Make ArtCraft better and more useful than model aggregation websites (our #1 goal).
|
||||
We don't dislike those websites; we just set a high standard for ourselves.
|
||||
|
||||
- Build the best "tangible" computing surfaces where you can mold scenes and videos like
|
||||
clay and direct them with precision and clarity.
|
||||
|
||||
- Add every model and provider into ArtCraft so you can build your own crafting IDE.
|
||||
Nothing will be left out.
|
||||
|
||||
- Build purpose-specific image models, eg. for relighting scenes and reposing characters
|
||||
and props.
|
||||
|
||||
Studio
|
||||
|
||||
- Build our content studio. Make lots of narrative shorts and at least one
|
||||
feature-length AI film by the year's end. Theatrical distribution, but also lots
|
||||
of social media content. (We are creators, and we want to employ more creators!)
|
||||
|
||||
- Host festivals and competitions, including in-person meetups and screenings.
|
||||
|
||||
Architecture
|
||||
|
||||
- Remove dependence on ArtCraft hosted services.
|
||||
|
||||
- More Rust, more speed.
|
||||
|
||||
- Code cleanup, more/better tests, CI, ...
|
||||
|
||||
Community
|
||||
|
||||
- Build a safe community free of negativity and anti-AI stigma. A community of filmmakers
|
||||
and artists.
|
||||
|
||||
## Loftier Goals
|
||||
|
||||
- Build an *Open* OpenAI / Google / Anthropic. Open models, open platforms, open hardware.
|
||||
Why? Because the future could be bleak without it. How, you might ask? Get in touch if
|
||||
you're curious.
|
||||
|
||||
- Don't Be Evil™
|
||||
@@ -1,127 +0,0 @@
|
||||
-- noinspection SqlDialectInspectionForFile
|
||||
--
|
||||
-- NB: This version needs to be bumped even if just adding or changing comments!
|
||||
-- Failure to do so will cause Windows to segfault at start. (This is horrible!)
|
||||
--
|
||||
-- NB: `app_state_dir.rs` contains the file version number.
|
||||
--
|
||||
-- Migration history:
|
||||
-- tasks_v1.sqlite - initial version
|
||||
-- tasks_v2.sqlite - added model_type (nullable)
|
||||
-- tasks_v3.sqlite - added frontend_caller, comments
|
||||
-- tasks_v4.sqlite - added is_dismissed_by_user
|
||||
-- tasks_v5.sqlite - added five fields:
|
||||
-- on_complete_batch_token,
|
||||
-- on_complete_primary_media_file_token,
|
||||
-- on_complete_primary_media_file_class,
|
||||
-- on_complete_primary_media_file_cdn_url,
|
||||
-- on_complete_primary_media_file_thumbnail_url_template,
|
||||
-- tasks_v6.sqlite - added two fields:
|
||||
-- on_failure_type
|
||||
-- on_failure_message
|
||||
-- tasks_v7.sqlite - added prompt token + two fields (for fal):
|
||||
-- prompt_token
|
||||
-- queue_status_url
|
||||
-- queue_response_url
|
||||
|
||||
CREATE TABLE tasks (
|
||||
-- Task auto-incrementing primary key.
|
||||
-- We don't use this.
|
||||
id TEXT NOT NULL PRIMARY KEY,
|
||||
|
||||
-- TaskStatus enum
|
||||
-- This is how the job system manages job states.
|
||||
-- e.g. pending, started, complete_success, dead, etc.
|
||||
task_status TEXT NOT NULL,
|
||||
|
||||
-- TaskType enum
|
||||
-- e.g. image_generation, video_generation, etc.
|
||||
task_type TEXT NOT NULL,
|
||||
|
||||
-- TaskModelType enum
|
||||
-- e.g. flux_1_dev, veo_2, etc.
|
||||
model_type TEXT,
|
||||
|
||||
-- GenerationProvider enum
|
||||
-- Together with `provider_job_id`, this creates a foreign key lookup to the job.
|
||||
-- e.g. 'artcraft', 'fal', 'sora', etc.
|
||||
provider TEXT,
|
||||
|
||||
-- The primary key for the job in the provider's system.
|
||||
provider_job_id TEXT,
|
||||
|
||||
-- OPTIONAL.
|
||||
-- For third party jobs, we need to record the prompt details upfront.
|
||||
-- We create a prompt record and store the token to associate once the task completes.
|
||||
prompt_token TEXT,
|
||||
|
||||
-- OPTIONAL.
|
||||
-- Tell the job system which caller initiated the task.
|
||||
-- e.g. 'canvas'
|
||||
frontend_caller TEXT,
|
||||
|
||||
-- OPTIONAL.
|
||||
-- An arbitrary opaque identifier that frontend can set.
|
||||
frontend_subscriber_id TEXT,
|
||||
|
||||
-- OPTIONAL.
|
||||
-- An opaque JSON payload set by the frontend subscriber that
|
||||
-- will be re-emitted back to the frontend.
|
||||
frontend_subscriber_payload TEXT,
|
||||
|
||||
-- Whether the user has dismissed the task from view.
|
||||
is_dismissed_by_user INTEGER NOT NULL DEFAULT 0,
|
||||
|
||||
-- OPTIONAL.
|
||||
-- For some third party systems (eg. FAL), the URL to check the status of the
|
||||
-- job in the queue.
|
||||
queue_status_url TEXT,
|
||||
|
||||
-- OPTIONAL.
|
||||
-- For some third party systems (eg. FAL), the URL to check the
|
||||
-- results (when the job completes)
|
||||
queue_response_url TEXT,
|
||||
|
||||
-- OPTIONAL.
|
||||
-- When the generation completes, the batch token (if any).
|
||||
-- This will point to a collection of multiple files.
|
||||
on_complete_batch_token TEXT,
|
||||
|
||||
-- OPTIONAL.
|
||||
-- When the generation completes, the media token of the "primary" media file.
|
||||
-- We'll probably always generate homogenous types, but just in case this is the first or primary item in a batch.
|
||||
on_complete_primary_media_file_token TEXT,
|
||||
|
||||
-- OPTIONAL.
|
||||
-- When the generation completes, the file type of the primary media file.
|
||||
-- We'll probably always generate homogenous types, but just in case this is the first or primary item in a batch.
|
||||
on_complete_primary_media_file_class TEXT,
|
||||
|
||||
-- OPTIONAL.
|
||||
-- The URL to the media file.
|
||||
on_complete_primary_media_file_cdn_url TEXT,
|
||||
|
||||
-- OPTIONAL.
|
||||
-- The URL template for the thumbnail of the primary media file.
|
||||
on_complete_primary_media_file_thumbnail_url_template TEXT,
|
||||
|
||||
-- OPTIONAL.
|
||||
-- An enum-like string describing the failure reason (even for non-terminal failures).
|
||||
-- The enum is `TaskFailureType`, which is a close analogue of the backend API's `FrontendFailureCategory`.
|
||||
on_failure_type TEXT,
|
||||
|
||||
-- OPTIONAL.
|
||||
-- A human-readable message describing the failure reason (even for non-terminal failures).
|
||||
on_failure_message TEXT,
|
||||
|
||||
created_at INTEGER NOT NULL DEFAULT (unixepoch('now')),
|
||||
updated_at INTEGER NOT NULL DEFAULT (unixepoch('now')),
|
||||
completed_at INTEGER DEFAULT NULL
|
||||
);
|
||||
|
||||
-- Unique Indices
|
||||
CREATE UNIQUE INDEX idx_tasks_on_provider_job_id ON tasks(provider_job_id);
|
||||
|
||||
-- Indices
|
||||
CREATE INDEX idx_tasks_on_status ON tasks(task_status);
|
||||
CREATE INDEX idx_tasks_on_provider ON tasks(provider);
|
||||
@@ -0,0 +1,38 @@
|
||||
AI Chatbot Sidecar
|
||||
==================
|
||||
|
||||
This is the first app that doesn't run as a part of our SoA API architecture. (Though it is written such that we
|
||||
could port it.) This app is responsible for generating all the content that goes into our streaming AI-powered TV
|
||||
programs.
|
||||
|
||||
### Windows Installation
|
||||
|
||||
The application uses sqlite, which is tricky to install on Windows.
|
||||
|
||||
1. Put together an sqlite build directory containing the sources and dll file. One is checked in under this monorepo
|
||||
in `./windows/sqlite` at the time of this writing, though that may change in the future.
|
||||
|
||||
|
||||
2. Add environment variables for the sqlite build directory (see https://github.com/diesel-rs/diesel/issues/487)
|
||||
|
||||
* PATH (append the sqlite build directory to the end)
|
||||
* SQLITE3_LIB_DIR (a new environment variable; set it to the sqllite build path)
|
||||
|
||||
|
||||
3. Build the `sqlite3.lib` file with the following:
|
||||
|
||||
```
|
||||
lib /def:sqlite3.def /out:sqlite3.lib /MACHINE:amd64
|
||||
```
|
||||
|
||||
This must be done in "Developer Command Prompt for VS 2022" (or similar), not `cmd` or `PowerShell`.
|
||||
|
||||
|
||||
4. Run the diesel_cli tool build
|
||||
```
|
||||
cargo install diesel_cli --no-default-features --features sqlite
|
||||
```
|
||||
|
||||
If it still complains about the `sqlite3.lib` file not being found, be sure to double check that `echo %PATH%` shows
|
||||
the updated PATH from the steps above.
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
database schema
|
||||
================
|
||||
|
||||
Twitch Model
|
||||
------------
|
||||
|
||||
- `twitch_user_id`
|
||||
Though numeric, this should always be a string per
|
||||
[https://discuss.dev.twitch.tv/t/bug-v5-api-returns--id-as-string-for-featured-channels/10310]
|
||||
and
|
||||
[https://discuss.dev.twitch.tv/t/type-of-user-id-in-api-responses/10205]
|
||||
|
||||
- `twitch_channel_id`
|
||||
This is the same as `twitch_user_id`. They're exactly 1:1. Prefer to
|
||||
use 'user_id' terminology.
|
||||
|
||||
- `twitch_username`
|
||||
Between 4 and 25 characters.
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
development notes
|
||||
=================
|
||||
|
||||
Actix notes
|
||||
-----------
|
||||
|
||||
json request
|
||||
```
|
||||
async fn handler(request: web::Json<Mytype>) -> impl Responder { "whatever" }
|
||||
```
|
||||
|
||||
form-multipart request
|
||||
```
|
||||
async fn handler(request: web::Form<Mytype>) -> impl Responder { "whatever" }
|
||||
```
|
||||
|
||||
route parameters
|
||||
```
|
||||
#[get("/{name}")]
|
||||
async fn hello(name: web::Path<String>) -> impl Responder { format!("Hi {}", name) }
|
||||
```
|
||||
@@ -0,0 +1,20 @@
|
||||
# Local Kubernetes environment for standing up the APIs and their dependencies
|
||||
|
||||
- Install minikube, kubectl, docker, and musl from your OS package manager
|
||||
- `rustup target add x86_64-unknown-linux-musl`
|
||||
- `cargo build --release --bin storyteller-web --target=x86_64-unknown-linux-musl`
|
||||
- `minikube start --extra-config=apiserver.service-node-port-range=1-65535`
|
||||
- `eval $(minikube docker-env)`
|
||||
- `minikube mount /path/to/storyteller-rust/:/storyteller-rust &> /dev/null &`
|
||||
- `docker build -t storyteller-web -f localdev/Dockerfile .`
|
||||
- `kubectl apply -f localdev/kubernetes.yml`
|
||||
- Get the ip address of minikube with `minikube ip`, this is where all the services will be running on their
|
||||
regular ports (12345 for the api, 6379 for redis, 3306 for mysql)
|
||||
- That's it, we just spun up mysql, applied database migrations, spun up redis, and spun up our api
|
||||
|
||||
# Development tips
|
||||
- Rebuild and "deploy" (locally) with `cargo build --release --bin storyteller-web --target=x86_64-unknown-linux-musl && kubectl rollout restart storyteller-web`
|
||||
- View logs from the api with `kubectl logs storyteller-web<TAB>`
|
||||
- Delete everything minikube (if you start running out of disk space or what-have-you) with `minikube delete`
|
||||
- See pod status with `kubectl get pods`
|
||||
- If there are errors you can get more info with `kubectl describe pods`
|
||||
@@ -0,0 +1,248 @@
|
||||
dev setup
|
||||
=========
|
||||
|
||||
[Back to main README](../../README.md)
|
||||
|
||||
## Debugging and Fixing CUDA/PyTorch
|
||||
|
||||
See this spreadsheet for previous battles with version incompatibility:
|
||||
https://docs.google.com/spreadsheets/d/1BEdLmwOzo3r83-iJn9sj6co1VT92t3lIeJ-lNH25bdQ/edit#gid=0
|
||||
|
||||
Ubuntu might upgrade the driver by accident, and it might need reinstallation. Who knows.
|
||||
This stuff is a nightmare.
|
||||
|
||||
## Database Setup
|
||||
|
||||
### Mac Install
|
||||
|
||||
```
|
||||
brew install mysql
|
||||
# If mysql -uroot fails, reboot the machine:
|
||||
sudo reboot now
|
||||
```
|
||||
|
||||
### Linux Install
|
||||
|
||||
Install the following libraries, and see the notes further below about MySQL on Ubuntu 20.04.
|
||||
|
||||
```
|
||||
jq # for combining mysql codegen outputs
|
||||
libmysqlclient-dev
|
||||
mysql-server
|
||||
libsqlite3-dev
|
||||
pkgconf # if using openssl instead of rustls
|
||||
```
|
||||
|
||||
### Migrations
|
||||
|
||||
To manage the database and perform migrations, install the Rust tools diesel and sqlx.
|
||||
|
||||
We'll be using diesel to manage the migrations, but sqlx within the app to actually perform queries.
|
||||
Diesel is an ORM, which is dumb, so we use sqlx as at-compile-time typesafe SQL.
|
||||
|
||||
The CLI is necessary to perform codegen:
|
||||
|
||||
```
|
||||
sudo apt-get install libmysqlclient-dev
|
||||
cargo install sqlx-cli --no-default-features --features rustls,mysql,sqlite
|
||||
```
|
||||
|
||||
Diesel now supports configuring the migration directory via an environment variable,
|
||||
so we can install the currently published version:
|
||||
|
||||
```
|
||||
cargo install diesel_cli \
|
||||
--no-default-features \
|
||||
--features mysql,sqlite
|
||||
```
|
||||
|
||||
If the modern version fails, try installing a cherry-picked version with a
|
||||
TOML-based migration directory configuration:
|
||||
|
||||
```
|
||||
cargo install diesel_cli \
|
||||
--git https://github.com/diesel-rs/diesel.git \
|
||||
--rev a213fe232a122f35a812b0ce0269708a1845a4c9 \
|
||||
--no-default-features \
|
||||
--features mysql
|
||||
```
|
||||
|
||||
#### Sqlx Error on Linux when Performing Schema Migrations
|
||||
|
||||
You might get this error message during migration,
|
||||
|
||||
```
|
||||
Encountered unknown type for Mysql: enum
|
||||
thread 'main' panicked at 'internal error: entered unreachable code: Mysql only supports a closed set of types.
|
||||
If you ever see this error message please open an issue at https://github.com/diesel-rs/diesel containing a dump of your schema definition.', diesel_cli/src/print_schema.rs:310:17
|
||||
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
|
||||
```
|
||||
|
||||
I haven't found the cause (it doesn't happen on Mac), but the migrations appear to work regardless of the error message.
|
||||
|
||||
#### Linux database notes
|
||||
|
||||
If MySql in local dev can't be connected to, reset the accounts:
|
||||
|
||||
https://linuxconfig.org/how-to-reset-root-mysql-mariadb-password-on-ubuntu-20-04-focal-fossa-linux
|
||||
|
||||
#### MySql on Ubuntu 22.04
|
||||
|
||||
Should work largely out of the box. We'll need a dev account:
|
||||
|
||||
```
|
||||
# Connect to mysql:
|
||||
# sudo mysql -u root -p (password is "root")
|
||||
|
||||
use mysql;
|
||||
CREATE USER 'storyteller'@'localhost' IDENTIFIED BY 'password';
|
||||
GRANT ALL PRIVILEGES ON storyteller.* TO 'storyteller'@'localhost';
|
||||
```
|
||||
|
||||
Then verify with `./dev_mysql_connect.sh`
|
||||
|
||||
#### Fixing dev MySql on Ubuntu 20.04
|
||||
|
||||
For some reason, the MySql default install on 20.04 gave me a bunch of trouble.
|
||||
|
||||
In retrospect, I _think_ this is because 'root@localhost' requires sudo to access, but if this
|
||||
gives any trouble in the future, here's how I got around it (two full hours of distraction!)
|
||||
|
||||
```
|
||||
sudo apt-get install mysql-server
|
||||
|
||||
# But for some reason the default password doesn't work and diesel/sqlx can't connect?
|
||||
|
||||
# Kill everything
|
||||
|
||||
sudo apt purge mysql-server mysql-client mysql-common
|
||||
sudo apt autoremove
|
||||
sudo mv -iv /var/lib/mysql /var/tmp/mysql-backup
|
||||
sudo rm -rf /var/lib/mysql*
|
||||
|
||||
sudo /usr/bin/mysql_secure_installation
|
||||
|
||||
# still no work...
|
||||
|
||||
sudo systemctl stop mysql.service
|
||||
|
||||
# ugh, it wasn't chowned or created (both states observed in different installs)…
|
||||
sudo mkdir -p /var/run/mysqld
|
||||
sudo chown mysql:mysql /var/run/mysqld
|
||||
|
||||
sudo mysqld_safe --skip-grant-tables --skip-networking &
|
||||
|
||||
mysql -u root
|
||||
|
||||
flush privileges;
|
||||
USE mysql;
|
||||
ALTER USER 'root'@'localhost' IDENTIFIED BY 'root';
|
||||
|
||||
sudo killall -u mysql
|
||||
sudo systemctl restart mysql.service
|
||||
|
||||
# now it works
|
||||
sudo mysql -u root -p
|
||||
|
||||
# but now I have to use "sudo" !?!?
|
||||
|
||||
use mysql;
|
||||
|
||||
CREATE USER 'storyteller'@'localhost' IDENTIFIED BY 'password';
|
||||
GRANT ALL PRIVILEGES ON storyteller.* TO 'storyteller'@'localhost';
|
||||
```
|
||||
|
||||
### Install Diesel (migrations only)
|
||||
|
||||
We use Diesel to manage migrations, but we don't use it in server code.
|
||||
Actual server code uses SQLx. To install the CLI tool, run the following:
|
||||
|
||||
```
|
||||
cargo install diesel_cli
|
||||
```
|
||||
|
||||
Mac [has issues](https://github.com/diesel-rs/diesel/issues/2605) and requires a few dependencies:
|
||||
|
||||
```
|
||||
brew install libpq
|
||||
```
|
||||
|
||||
### Database migrations
|
||||
|
||||
To reset the entire database (drop, migrate), run:
|
||||
|
||||
```
|
||||
diesel database reset
|
||||
```
|
||||
|
||||
To migrate at the current step and beyond:
|
||||
|
||||
```
|
||||
diesel migration run
|
||||
```
|
||||
|
||||
To undo migrations at the current step:
|
||||
|
||||
```
|
||||
diesel migration redo
|
||||
```
|
||||
|
||||
### Server Query Codegen
|
||||
|
||||
We use SQLx instead of Diesel in the production server. It's typesafe
|
||||
SQL instead of an ORM like Diesel.
|
||||
|
||||
SQLx connects to a database to derive type information, but obviously
|
||||
cannot do this for builds in CI. In order to cache the types, we build
|
||||
and check in a cache file (necessary for builds):
|
||||
|
||||
```
|
||||
SQLX_OFFLINE=true cargo sqlx prepare
|
||||
```
|
||||
|
||||
Now that we have multiple binaries, it's required to include all the queries in the main
|
||||
binary so we can generate the cached queries as a single target. That's then executed
|
||||
with:
|
||||
|
||||
```
|
||||
SQLX_OFFLINE=true cargo sqlx prepare -- --bin storyteller-web
|
||||
```
|
||||
|
||||
### Setting Up a Local Nginx Proxy
|
||||
|
||||
Set up a local nginx to proxy to the frontend and backend so cookie issues aren't annoying
|
||||
|
||||
Configure Nginx per the checked in Nginx configs (and instructions) in `localdev/nginx-http-config`.
|
||||
|
||||
```
|
||||
|
||||
And in /etc/hosts,
|
||||
|
||||
```
|
||||
127.0.0.1 jungle.horse
|
||||
127.0.0.1 api.jungle.horse
|
||||
|
||||
```
|
||||
|
||||
### Python 3.6 on Apple M1 Mac
|
||||
|
||||
Python3.6 isn't supported on Apple silicon, and it's not in homebrew. It can be installed with
|
||||
[nix using Rosetta](https://stackoverflow.com/a/65980989):
|
||||
|
||||
Download: https://nixos.org/download.html#nix-quick-install
|
||||
|
||||
```
|
||||
nix run nixpkgs.python36 -c python
|
||||
```
|
||||
|
||||
Install venv:
|
||||
|
||||
```
|
||||
nix run nixpkgs.python36 -c python -m venv python
|
||||
```
|
||||
|
||||
Install other packages on Mac that aren't used in venv:
|
||||
|
||||
```
|
||||
python3 -m pip install --user requests gdown youtube_dl
|
||||
```
|
||||
@@ -0,0 +1,77 @@
|
||||
old readme
|
||||
==========
|
||||
|
||||
Contents of the main README.md, which will always be woefully out of date. Docs will migrate into this folder on a
|
||||
per-subject basis. App-specific docs will live under each app.
|
||||
|
||||
Application overview
|
||||
--------------------
|
||||
|
||||
* [FakeYou.com](https://fakeyou.com) is powered by:
|
||||
* HTTP APIs:
|
||||
* `storyteller-web` - HTTP + database monolith for FakeYou
|
||||
* Asynchronous Jobs:
|
||||
* `tts-download-job` - Async download of models
|
||||
* `tts-inference-job` - Async TTS inference
|
||||
* `w2l-download-job` - Async download of videos and images
|
||||
* `w2l-inference-job` - Async W2L inference
|
||||
|
||||
* [StorytellerStream](https://storyteller.stream) is powered by:
|
||||
* HTTP APIs:
|
||||
* `storyteller-web` - HTTP + database monolith for FakeYou
|
||||
* Websocket events:
|
||||
* `websocket-gateway` - Websocket to pull down Twitch events.
|
||||
* Twitch API integration:
|
||||
* `twitch-pubsub-subscriber` - Dynamically subscribes to Twitch PubSub for "active" users
|
||||
|
||||
Schema and API notes
|
||||
--------------------
|
||||
|
||||
* [Redis Schema Notes README](_docs/redis_schema.md)
|
||||
|
||||
Local development
|
||||
-----------------
|
||||
|
||||
[Local development setup README](_docs/local_development_setup.md)
|
||||
|
||||
Production and Deployment
|
||||
-------------------------
|
||||
|
||||
[Production builds and deployment README](_docs/production_builds_and_deployment.md)
|
||||
|
||||
Development notes
|
||||
-----------------
|
||||
|
||||
[Development notes README](_docs/development_notes.md)
|
||||
|
||||
Dump a MySQL report
|
||||
-------------------
|
||||
|
||||
```
|
||||
mysql -u storyteller \
|
||||
-pPasswordHere \
|
||||
-h IP_HERE \
|
||||
-P 3306 \
|
||||
-D storyteller \
|
||||
-B \
|
||||
-e "select * from voice_clone_requests;" | sed "s/'/\'/;s/\t/\",\"/g;s/^/\"/;s/$/\"/;s/\n//g" > voice_clone_report.csv
|
||||
```
|
||||
|
||||
TODO
|
||||
----
|
||||
|
||||
* Revise this README to be more useful.
|
||||
|
||||
Notes / TODOs:
|
||||
|
||||
* Examples for good Actix+Sqlx Tests:
|
||||
https://stackoverflow.com/questions/65370752/how-do-i-create-an-actix-web-server-that-accepts-both-sqlx-database-pools-and-tr
|
||||
|
||||
* Actix/sqlx runtime compat:
|
||||
https://github.com/launchbadge/sqlx/issues/1117#issuecomment-801237734
|
||||
|
||||
* Redis caching of more endpoints
|
||||
|
||||
* Jobs for analytics queries (#uses per model, etc)
|
||||
* Jobs for ingesting Discord, Twitter, Reddit RPAN chat, Twitch chat (not PubSub), etc.
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
production builds and deployment
|
||||
================================
|
||||
|
||||
### Github Docker builds
|
||||
|
||||
The repository needs to be given read access to the base docker image:
|
||||
|
||||
https://github.com/orgs/storytold/packages/container/docker-base-images-rust-ssl/settings
|
||||
|
||||
These instructions assume running on GCP.
|
||||
|
||||
### Database migrations
|
||||
|
||||
1. Set `DATABASE_URL` in `.env` to the production secrets (DO NOT COMMIT!)
|
||||
2. Run `diesel migration run`
|
||||
|
||||
### Setting up public buckets without list permission
|
||||
|
||||
Public buckets that deny the `list` action should use the following Role:
|
||||
|
||||
`roles/storage.legacyObjectReader`
|
||||
|
||||
See:
|
||||
|
||||
* https://stackoverflow.com/a/56354633
|
||||
* https://cloud.google.com/storage/docs/access-control/making-data-public#buckets
|
||||
|
||||
### Generating Bucket Access Key and Secret Key
|
||||
|
||||
https://cloud.telestream.net/tutorials/how-to-setting-up-google-cloud-storage/
|
||||
|
||||
1. Go to the GCS page
|
||||
2. Click "settings"
|
||||
3. Click "interoperability" tab
|
||||
4. (enable interoperable access if not already set)
|
||||
5. Click "create new key"
|
||||
@@ -0,0 +1,27 @@
|
||||
redis schema
|
||||
============
|
||||
|
||||
Database 0: TTS / RateLimiter
|
||||
-----------------------------
|
||||
|
||||
TODO: Document rate limiter
|
||||
|
||||
Public statistics on usage displayed on the website:
|
||||
|
||||
* `ttsUseCount:{model_token}`: INCR, GET
|
||||
* `w2lUseCount:{template_token}`: INCR, GET
|
||||
|
||||
Temporary job progress reports to the user:
|
||||
|
||||
These are written as a single string, such as "downloading model", "uploading model",
|
||||
"generating preview", "executing", etc.
|
||||
|
||||
* `ttsDownloadExtraStatus:{inference_job_token}`: SETEX, GET
|
||||
* `ttsInferenceExtraStatus:{inference_job_token}`: SETEX, GET
|
||||
* `w2lDownloadExtraStatus:{inference_job_token}`: SETEX, GET
|
||||
* `w2lInferenceExtraStatus:{inference_job_token}`: SETEX, GET
|
||||
|
||||
Database 1: Obs / Twitch
|
||||
------------------------
|
||||
|
||||
* WIP
|
||||
@@ -0,0 +1,23 @@
|
||||
FROM rust:latest
|
||||
|
||||
ENV DEBIAN_FRONTEND=noninteractive
|
||||
|
||||
RUN apt-get update && \
|
||||
apt-get install -y \
|
||||
mariadb-client \
|
||||
imagemagick \
|
||||
libmariadb-dev \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
|
||||
RUN cargo install diesel_cli --features mysql && \
|
||||
cargo install sqlx-cli --features mysql
|
||||
|
||||
WORKDIR /storyteller-rust
|
||||
|
||||
ARG GIT_SHA
|
||||
|
||||
RUN echo -n ${GIT_SHA} > /GIT_SHA
|
||||
|
||||
CMD ["/storyteller-rust/localdev/startup.sh"]
|
||||
|
||||
@@ -0,0 +1,153 @@
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: mysql
|
||||
spec:
|
||||
replicas: 1
|
||||
selector:
|
||||
matchLabels:
|
||||
app: mysql
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: mysql
|
||||
spec:
|
||||
containers:
|
||||
- name: mysql
|
||||
image: mysql:8.0.30
|
||||
env:
|
||||
- name: MYSQL_USER
|
||||
value: storyteller
|
||||
- name: MYSQL_PASSWORD
|
||||
value: password
|
||||
- name: MYSQL_DATABASE
|
||||
value: storyteller
|
||||
- name: MYSQL_ROOT_PASSWORD
|
||||
value: password
|
||||
ports:
|
||||
- containerPort: 3306
|
||||
|
||||
---
|
||||
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: redis
|
||||
spec:
|
||||
replicas: 1
|
||||
selector:
|
||||
matchLabels:
|
||||
app: redis
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: redis
|
||||
spec:
|
||||
containers:
|
||||
- name: redis
|
||||
image: redis:7.0.4
|
||||
ports:
|
||||
- containerPort: 6379
|
||||
|
||||
---
|
||||
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: storyteller-web
|
||||
spec:
|
||||
replicas: 1
|
||||
selector:
|
||||
matchLabels:
|
||||
app: storyteller-web
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: storyteller-web
|
||||
spec:
|
||||
containers:
|
||||
- name: storyteller-web
|
||||
image: storyteller-web:latest
|
||||
imagePullPolicy: Never
|
||||
env:
|
||||
- name: MYSQL_URL
|
||||
value: mysql://storyteller:password@mysql.default/storyteller
|
||||
- name: REDIS_0_URL
|
||||
value: redis://redisd@redis.default/0
|
||||
- name: REDIS_1_URL
|
||||
value: redis://redisd@redis.default/1
|
||||
- name: ACCESS_KEY
|
||||
value: garbage
|
||||
- name: SECRET_KEY
|
||||
value: garbage
|
||||
- name: REGION_NAME
|
||||
value: garbage
|
||||
- name: W2L_PRIVATE_DOWNLOAD_BUCKET_NAME
|
||||
value: garbage
|
||||
- name: W2L_PUBLIC_DOWNLOAD_BUCKET_NAME
|
||||
value: garbage
|
||||
- name: AUDIO_UPLOADS_BUCKET_ROOT
|
||||
value: garbage
|
||||
- name: TWITCH_USERNAME
|
||||
value: garbage
|
||||
- name: TWITCH_STREAM_KEY
|
||||
value: garbage
|
||||
- name: TWITCH_APP_CLIENT_ID
|
||||
value: garbage
|
||||
- name: TWITCH_APP_CLIENT_SECRET
|
||||
value: garbage
|
||||
ports:
|
||||
- containerPort: 12345
|
||||
volumeMounts:
|
||||
- mountPath: /storyteller-rust
|
||||
name: storyteller-rust-folder
|
||||
volumes:
|
||||
- name: storyteller-rust-folder
|
||||
hostPath:
|
||||
path: /storyteller-rust
|
||||
---
|
||||
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: mysql
|
||||
spec:
|
||||
type: NodePort
|
||||
ports:
|
||||
- name: mysql
|
||||
port: 3306
|
||||
nodePort: 3306
|
||||
selector:
|
||||
app: mysql
|
||||
|
||||
---
|
||||
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: redis
|
||||
spec:
|
||||
type: NodePort
|
||||
ports:
|
||||
- name: redis
|
||||
port: 6379
|
||||
nodePort: 6379
|
||||
selector:
|
||||
app: redis
|
||||
|
||||
---
|
||||
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: storyteller-web
|
||||
spec:
|
||||
type: NodePort
|
||||
ports:
|
||||
- name: storyteller-web
|
||||
port: 12345
|
||||
nodePort: 12345
|
||||
selector:
|
||||
app: storyteller-web
|
||||
|
||||
|
||||
@@ -0,0 +1,57 @@
|
||||
# Nginx configs for "jungle.horse" and "api.jungle.horse" (http)
|
||||
#
|
||||
# On Ubuntu Linux, this config is located at the following location:
|
||||
#
|
||||
# - /etc/nginx/sites-enabled/{name-of-site}
|
||||
#
|
||||
# On Mac, this is located at the following locations:
|
||||
#
|
||||
# - /opt/homebrew/etc/nginx/servers/{name-of-site} (for nginx installed with homebrew)
|
||||
# - /usr/local/etc/nginx/sites-enabled/{name-of-site} (for default nginx install)
|
||||
#
|
||||
|
||||
# API backend over plain HTTP
|
||||
server {
|
||||
listen 80;
|
||||
server_name api.jungle.horse;
|
||||
location / {
|
||||
proxy_set_header Host $host;
|
||||
proxy_pass http://127.0.0.1:12345;
|
||||
proxy_redirect off;
|
||||
}
|
||||
}
|
||||
|
||||
# TODO(2022-02-13): This is new and untested
|
||||
# WebSocket API backend over plain HTTP
|
||||
server {
|
||||
listen 80;
|
||||
server_name ws.jungle.horse;
|
||||
location / {
|
||||
proxy_set_header Host $host;
|
||||
proxy_pass http://127.0.0.1:54321;
|
||||
proxy_redirect off;
|
||||
}
|
||||
}
|
||||
|
||||
# Javascript frontend over plain HTTP
|
||||
server {
|
||||
listen 80;
|
||||
server_name jungle.horse;
|
||||
location / {
|
||||
proxy_set_header Host $host;
|
||||
proxy_pass http://127.0.0.1:7000;
|
||||
proxy_redirect off;
|
||||
}
|
||||
}
|
||||
|
||||
# TODO(2022-02-13): This is new and untested
|
||||
# Javascript frontend #2 (OBS Layer) over plain HTTP
|
||||
server {
|
||||
listen 80;
|
||||
server_name obs.jungle.horse;
|
||||
location / {
|
||||
proxy_set_header Host $host;
|
||||
proxy_pass http://127.0.0.1:7001;
|
||||
proxy_redirect off;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,258 @@
|
||||
# Nginx configs for "jungle.horse" and "api.jungle.horse" (http)
|
||||
#
|
||||
# On Ubuntu Linux, this config is located at the following location:
|
||||
#
|
||||
# - /etc/nginx/sites-enabled/{name-of-site}
|
||||
#
|
||||
# Move this file there, and then restart with,
|
||||
#
|
||||
# sudo service nginx restart
|
||||
#
|
||||
# On Mac, this is located at the following locations:
|
||||
#
|
||||
# - /opt/homebrew/etc/nginx/servers/{name-of-site} (for nginx installed with homebrew)
|
||||
# - /usr/local/etc/nginx/sites-enabled/{name-of-site} (for default nginx install)
|
||||
#
|
||||
#
|
||||
# On Mac, verify configs and restart with,
|
||||
#
|
||||
# sudo nginx -t
|
||||
# sudo nginx -s reload
|
||||
#
|
||||
# ================
|
||||
# SSL Instructions
|
||||
# ================
|
||||
#
|
||||
# You can launch Chrome in a way it disregards self-signed certs and CORS:
|
||||
#
|
||||
# chromium --disable-web-security --ignore-certificate-errors --user-data-dir=~/chromeTemp
|
||||
#
|
||||
# To create self-signed certs on Ubuntu Linux,
|
||||
#
|
||||
# sudo apt-get install openssl
|
||||
#
|
||||
# sudo openssl req -x509 -nodes -days 365 -newkey rsa:2048 -keyout /etc/ssl/private/localhost.key -out /etc/ssl/certs/localhost.crt
|
||||
#
|
||||
# ^ most fields can be blank, but the "Common Name" should be `*.jungle.horse`
|
||||
#
|
||||
# sudo openssl req -x509 -nodes -days 365 -newkey rsa:2048 -keyout /etc/ssl/private/devfakeyou.key -out /etc/ssl/certs/devfakeyou.crt
|
||||
# sudo openssl req -x509 -nodes -days 365 -newkey rsa:2048 -keyout /etc/ssl/private/devstoryteller.key -out /etc/ssl/certs/devstoryteller.crt
|
||||
#
|
||||
# ^ most fields can be blank, but the "Common Name" should be `*.dev.fakeyou.com` and `*.dev.storyteller.io` respectively.
|
||||
#
|
||||
# For more: https://www.cloudsavvyit.com/1306/how-to-create-and-use-self-signed-ssl-on-nginx/
|
||||
#
|
||||
# To create self-signed certs on Mac,
|
||||
#
|
||||
# openssl req -x509 -sha256 -nodes -newkey rsa:2048 -days 365 -keyout localhost.key -out localhost.crt
|
||||
#
|
||||
# ^ most fields can be blank, but the "Common Name" should be `*.jungle.horse`
|
||||
#
|
||||
# On Mac, load the self signed cert into the keychain with the following command:
|
||||
#
|
||||
# sudo security add-trusted-cert \
|
||||
# -d -r trustRoot \
|
||||
# -k /Library/Keychains/System.keychain /etc/ssl/certs/localhost.crt
|
||||
#
|
||||
# Self signed certs might be considered insecure by the browser. In Firefox,
|
||||
# set about:config `security.tls.insecure_fallback_hosts` to include a list of
|
||||
# permitted domains. You may also need to configure other settings.
|
||||
#
|
||||
#
|
||||
|
||||
# ========================================================
|
||||
# FAKEYOU
|
||||
# ========================================================
|
||||
|
||||
# FakeYou javascript frontend over plain HTTP (launched by Yarn)
|
||||
server {
|
||||
listen 80;
|
||||
server_name dev.fakeyou.com;
|
||||
location / {
|
||||
proxy_set_header Host $host;
|
||||
proxy_pass http://127.0.0.1:7000;
|
||||
proxy_redirect off;
|
||||
}
|
||||
}
|
||||
|
||||
# FakeYou API backend over plain HTTP (Rust app)
|
||||
server {
|
||||
listen 80;
|
||||
server_name api.dev.fakeyou.com;
|
||||
location / {
|
||||
proxy_set_header Host $host;
|
||||
proxy_pass http://127.0.0.1:12345;
|
||||
proxy_redirect off;
|
||||
}
|
||||
}
|
||||
|
||||
# FakeYou javascript frontend over SSL (launched by Yarn)
|
||||
server {
|
||||
listen 443 ssl;
|
||||
listen [::]:443 ssl;
|
||||
ssl_certificate /etc/ssl/certs/devfakeyou.crt;
|
||||
ssl_certificate_key /etc/ssl/private/devfakeyou.key;
|
||||
ssl_protocols TLSv1.2 TLSv1.1 TLSv1;
|
||||
server_name dev.fakeyou.com;
|
||||
location / {
|
||||
proxy_set_header Host $host;
|
||||
proxy_pass http://127.0.0.1:7000;
|
||||
proxy_redirect off;
|
||||
}
|
||||
}
|
||||
|
||||
# FakeYou API backend over SSL (Rust app)
|
||||
server {
|
||||
listen 443 ssl;
|
||||
listen [::]:443 ssl;
|
||||
ssl_certificate /etc/ssl/certs/devfakeyou.crt;
|
||||
ssl_certificate_key /etc/ssl/private/devfakeyou.key;
|
||||
ssl_protocols TLSv1.2 TLSv1.1 TLSv1;
|
||||
server_name api.dev.fakeyou.com;
|
||||
location / {
|
||||
proxy_set_header Host $host;
|
||||
proxy_pass http://127.0.0.1:12345;
|
||||
proxy_redirect off;
|
||||
}
|
||||
}
|
||||
|
||||
# ========================================================
|
||||
# STORYTELLER
|
||||
# ========================================================
|
||||
|
||||
# Storyteller javascript frontend over plain HTTP (launched by Yarn)
|
||||
server {
|
||||
listen 80;
|
||||
server_name dev.storyteller.io;
|
||||
location / {
|
||||
proxy_set_header Host $host;
|
||||
proxy_pass http://127.0.0.1:7000;
|
||||
proxy_redirect off;
|
||||
}
|
||||
}
|
||||
|
||||
# Storyteller API backend over plain HTTP
|
||||
server {
|
||||
listen 80;
|
||||
server_name api.dev.storyteller.io;
|
||||
location / {
|
||||
proxy_set_header Host $host;
|
||||
proxy_pass http://127.0.0.1:12345;
|
||||
proxy_redirect off;
|
||||
}
|
||||
}
|
||||
|
||||
# Storyteller javascript frontend over SSL (launched by Yarn)
|
||||
server {
|
||||
listen 443 ssl;
|
||||
listen [::]:443 ssl;
|
||||
ssl_certificate /etc/ssl/certs/devstoryteller.crt;
|
||||
ssl_certificate_key /etc/ssl/private/devstoryteller.key;
|
||||
ssl_protocols TLSv1.2 TLSv1.1 TLSv1;
|
||||
server_name dev.storyteller.io;
|
||||
location / {
|
||||
proxy_set_header Host $host;
|
||||
proxy_pass http://127.0.0.1:7000;
|
||||
proxy_redirect off;
|
||||
}
|
||||
}
|
||||
|
||||
# Storyteller API backend over SSL
|
||||
server {
|
||||
listen 443 ssl;
|
||||
listen [::]:443 ssl;
|
||||
ssl_certificate /etc/ssl/certs/devstoryteller.crt;
|
||||
ssl_certificate_key /etc/ssl/private/devstoryteller.key;
|
||||
ssl_protocols TLSv1.2 TLSv1.1 TLSv1;
|
||||
server_name api.dev.storyteller.io;
|
||||
location / {
|
||||
proxy_set_header Host $host;
|
||||
proxy_pass http://127.0.0.1:12345;
|
||||
proxy_redirect off;
|
||||
}
|
||||
}
|
||||
|
||||
# ========================================================
|
||||
# OLD PRODUCTS THAT NEED UPDATING
|
||||
# ========================================================
|
||||
|
||||
# ## TODO(2022-02-13): This is new and untested
|
||||
# # WebSocket API backend over plain HTTP
|
||||
# server {
|
||||
# listen 80;
|
||||
# server_name ws.jungle.horse;
|
||||
# location / {
|
||||
# proxy_set_header Host $host;
|
||||
# proxy_pass http://127.0.0.1:54321;
|
||||
# proxy_redirect off;
|
||||
# }
|
||||
# }
|
||||
#
|
||||
# # Websocket API backend over SSL
|
||||
# server {
|
||||
# listen 443 ssl;
|
||||
# listen [::]:443 ssl;
|
||||
# ssl_certificate /etc/ssl/certs/localhost.crt;
|
||||
# ssl_certificate_key /etc/ssl/private/localhost.key;
|
||||
# ssl_protocols TLSv1.2 TLSv1.1 TLSv1;
|
||||
# server_name ws.jungle.horse;
|
||||
# location / {
|
||||
# proxy_set_header Host $host;
|
||||
# proxy_pass http://127.0.0.1:54321;
|
||||
# proxy_redirect off;
|
||||
# }
|
||||
# }
|
||||
#
|
||||
# # Javascript frontend over plain HTTP
|
||||
# server {
|
||||
# listen 80;
|
||||
# server_name jungle.horse;
|
||||
# location / {
|
||||
# proxy_set_header Host $host;
|
||||
# proxy_pass http://127.0.0.1:7000;
|
||||
# proxy_redirect off;
|
||||
# }
|
||||
# }
|
||||
#
|
||||
# # TODO(2022-02-13): This is new and untested
|
||||
# # Javascript frontend (OBS Layer) over plain HTTP
|
||||
# server {
|
||||
# listen 80;
|
||||
# server_name obs.jungle.horse;
|
||||
# location / {
|
||||
# proxy_set_header Host $host;
|
||||
# proxy_pass http://127.0.0.1:7007;
|
||||
# proxy_redirect off;
|
||||
# }
|
||||
# }
|
||||
#
|
||||
# # Javascript frontend over SSL
|
||||
# server {
|
||||
# listen 443 ssl;
|
||||
# listen [::]:443 ssl;
|
||||
# ssl_certificate /etc/ssl/certs/localhost.crt;
|
||||
# ssl_certificate_key /etc/ssl/private/localhost.key;
|
||||
# ssl_protocols TLSv1.2 TLSv1.1 TLSv1;
|
||||
# server_name jungle.horse;
|
||||
# location / {
|
||||
# proxy_set_header Host $host;
|
||||
# proxy_pass http://127.0.0.1:7000;
|
||||
# proxy_redirect off;
|
||||
# }
|
||||
# }
|
||||
#
|
||||
# # Javascript frontend over SSL (OBS Layer)
|
||||
# server {
|
||||
# listen 443 ssl;
|
||||
# listen [::]:443 ssl;
|
||||
# ssl_certificate /etc/ssl/certs/localhost.crt;
|
||||
# ssl_certificate_key /etc/ssl/private/localhost.key;
|
||||
# ssl_protocols TLSv1.2 TLSv1.1 TLSv1;
|
||||
# server_name obs.jungle.horse;
|
||||
# location / {
|
||||
# proxy_set_header Host $host;
|
||||
# proxy_pass http://127.0.0.1:7007;
|
||||
# proxy_redirect off;
|
||||
# }
|
||||
# }
|
||||
#
|
||||
Executable
+6
@@ -0,0 +1,6 @@
|
||||
#!/bin/bash
|
||||
set +e +o pipefail
|
||||
diesel migration run --database-url="${MYSQL_URL}"
|
||||
set -e -o pipefail
|
||||
./target/x86_64-unknown-linux-musl/release/storyteller-web
|
||||
|
||||
Executable
+36
@@ -0,0 +1,36 @@
|
||||
#!/bin/bash
|
||||
|
||||
echo 'Installing APT packages...'
|
||||
|
||||
# OpenSSL requirements: libssl-dev, pkg-config
|
||||
# Database requirements: mysql-server, libmysqlclient-dev
|
||||
# Other tools: jq (db codegen)
|
||||
sudo apt-get install -y \
|
||||
jq \
|
||||
libmysqlclient-dev \
|
||||
libssl-dev \
|
||||
mysql-server \
|
||||
pkg-config \
|
||||
--no-install-recommends
|
||||
|
||||
echo 'Installing Cargo packages...'
|
||||
|
||||
# Install Diesel CLI for migrations
|
||||
cargo install diesel_cli \
|
||||
--no-default-features \
|
||||
--features mysql
|
||||
|
||||
# Install SQLx for codegen
|
||||
cargo install sqlx-cli \
|
||||
--no-default-features \
|
||||
--features rustls,mysql
|
||||
|
||||
echo 'Installing Database user...'
|
||||
|
||||
# Create DB account (using default system mysql password)
|
||||
sudo mysql \
|
||||
-u "root" \
|
||||
-p"password" \
|
||||
-e "use mysql; CREATE DATABASE IF NOT EXISTS storyteller; CREATE USER IF NOT EXISTS 'storyteller'@'localhost' IDENTIFIED BY 'password'; GRANT ALL PRIVILEGES ON storyteller.* TO 'storyteller'@'localhost';"
|
||||
|
||||
|
||||
Executable
+61
@@ -0,0 +1,61 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
import json
|
||||
import re
|
||||
import requests
|
||||
import uuid
|
||||
|
||||
login_url = 'http://localhost:12345/login'
|
||||
create_api_token_url = 'http://localhost:12345/api_tokens/create'
|
||||
edit_api_token_url = 'http://localhost:12345/api_tokens/edit'
|
||||
delete_api_token_url = 'http://localhost:12345/api_tokens/{}/delete'
|
||||
|
||||
payload = {
|
||||
'username_or_email': 'echelon',
|
||||
'password': 'testing',
|
||||
}
|
||||
|
||||
r = requests.post(login_url, json=payload)
|
||||
|
||||
print("===== Login =====")
|
||||
print('Status: {}'.format(r.status_code))
|
||||
print(r.content)
|
||||
for k, v in r.headers.items():
|
||||
print(' {}: {}'.format(k, v))
|
||||
|
||||
raw_set_cookie_header = r.headers['set-cookie']
|
||||
m = re.match(r'session=([^;\s]+);', raw_set_cookie_header)
|
||||
session_cookie = m.group(1)
|
||||
|
||||
cookies = { 'session': session_cookie }
|
||||
|
||||
payload = {
|
||||
'idempotency_token': str(uuid.uuid4()),
|
||||
}
|
||||
|
||||
r = requests.post(create_api_token_url, cookies=cookies, json=payload)
|
||||
|
||||
print("===== Created API Token =====")
|
||||
print('Status: {}'.format(r.status_code))
|
||||
print(r.content)
|
||||
|
||||
api_token = json.loads(r.content)
|
||||
|
||||
|
||||
payload = {
|
||||
'api_token': api_token['api_token'],
|
||||
'maybe_short_description': 'this describes my token',
|
||||
}
|
||||
r = requests.post(edit_api_token_url, cookies=cookies, json=payload)
|
||||
|
||||
print("===== Edited API Token =====")
|
||||
print('Status: {}'.format(r.status_code))
|
||||
print(r.content)
|
||||
|
||||
url = delete_api_token_url.format(api_token['api_token'])
|
||||
r = requests.post(url, cookies=cookies)
|
||||
|
||||
print("===== Deleted API Token =====")
|
||||
print('Status: {}'.format(r.status_code))
|
||||
print(r.content)
|
||||
|
||||
Executable
+150
@@ -0,0 +1,150 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
import json
|
||||
import re
|
||||
import requests
|
||||
import uuid
|
||||
|
||||
login_url = 'http://localhost:12345/login'
|
||||
category_create_url = 'http://localhost:12345/category/create'
|
||||
category_delete_url_format = 'http://localhost:12345/category/{}/delete'
|
||||
category_edit_url_format = 'http://localhost:12345/category/{}/edit'
|
||||
tts_model_list_url = 'http://localhost:12345/tts/list'
|
||||
tts_category_assignment_url = 'http://localhost:12345/category/assign/tts'
|
||||
|
||||
payload = {
|
||||
'username_or_email': 'echelon',
|
||||
'password': 'testing',
|
||||
}
|
||||
|
||||
r = requests.post(login_url, json=payload)
|
||||
|
||||
print("===== [1] Login =====")
|
||||
print('Status: {}'.format(r.status_code))
|
||||
print(r.content)
|
||||
for k, v in r.headers.items():
|
||||
print(' {}: {}'.format(k, v))
|
||||
|
||||
# NB: May not be set due to cookie domain:
|
||||
#r.cookies['session']
|
||||
|
||||
raw_set_cookie_header = r.headers['set-cookie']
|
||||
m = re.match(r'session=([^;\s]+);', raw_set_cookie_header)
|
||||
session_cookie = m.group(1)
|
||||
|
||||
|
||||
print("===== [2] Create Category =====")
|
||||
|
||||
cookies = { 'session': session_cookie }
|
||||
|
||||
payload = {
|
||||
'idempotency_token': str(uuid.uuid4()),
|
||||
'name': 'First Category',
|
||||
'model_type': 'tts', # tts or w2l
|
||||
'can_directly_have_models': False,
|
||||
'can_have_subcategories': True,
|
||||
'is_mod_approved': True,
|
||||
}
|
||||
|
||||
r = requests.post(category_create_url, cookies=cookies, json=payload)
|
||||
|
||||
print('Status: {}'.format(r.status_code))
|
||||
|
||||
d = json.loads(r.content)
|
||||
category_token = d['token']
|
||||
|
||||
print(f'created category token: #{category_token}')
|
||||
|
||||
|
||||
print("===== [3] (Soft) Delete Category =====")
|
||||
|
||||
category_delete_url = category_delete_url_format.format(category_token)
|
||||
|
||||
cookies = { 'session': session_cookie }
|
||||
|
||||
payload = {
|
||||
'set_delete': True,
|
||||
}
|
||||
|
||||
r = requests.post(category_delete_url, cookies=cookies, json=payload)
|
||||
|
||||
print('Status: {}'.format(r.status_code))
|
||||
print(r.content)
|
||||
|
||||
|
||||
print("===== [4] Undelete Category =====")
|
||||
|
||||
cookies = { 'session': session_cookie }
|
||||
|
||||
payload = {
|
||||
'set_delete': False,
|
||||
}
|
||||
|
||||
r = requests.post(category_delete_url, cookies=cookies, json=payload)
|
||||
|
||||
print('Status: {}'.format(r.status_code))
|
||||
print(r.content)
|
||||
|
||||
|
||||
print("===== [5] Edit Category =====")
|
||||
|
||||
category_edit_url = category_edit_url_format.format(category_token)
|
||||
|
||||
cookies = { 'session': session_cookie }
|
||||
|
||||
payload = {
|
||||
'name': 'Edited Name',
|
||||
'dropdown_name': 'Edited Dropdown Name',
|
||||
'can_directly_have_models': True,
|
||||
'can_have_subcategories': True,
|
||||
'can_only_mods_apply': False,
|
||||
'is_mod_approved': True,
|
||||
'maybe_mod_comments': 'mod comment',
|
||||
}
|
||||
|
||||
r = requests.post(category_edit_url, cookies=cookies, json=payload)
|
||||
|
||||
print('Status: {}'.format(r.status_code))
|
||||
print(r.content)
|
||||
|
||||
|
||||
print("===== [6] List TTS Models =====")
|
||||
|
||||
r = requests.get(tts_model_list_url, cookies=cookies)
|
||||
|
||||
print('Status: {}'.format(r.status_code))
|
||||
|
||||
d = json.loads(r.content)
|
||||
|
||||
tts_model_token = d['models'][0]['model_token']
|
||||
|
||||
print('TTS model token: {}'.format(tts_model_token))
|
||||
|
||||
|
||||
print("===== [7] Create Category Assignment to TTS Model =====")
|
||||
|
||||
payload = {
|
||||
'category_token': category_token,
|
||||
'tts_model_token': tts_model_token,
|
||||
'assign': True,
|
||||
}
|
||||
|
||||
r = requests.post(tts_category_assignment_url, cookies=cookies, json=payload)
|
||||
|
||||
print('Status: {}'.format(r.status_code))
|
||||
print(r.content)
|
||||
|
||||
|
||||
print("===== [8] Delete Category Assignment to TTS Model =====")
|
||||
|
||||
payload = {
|
||||
'category_token': category_token,
|
||||
'tts_model_token': tts_model_token,
|
||||
'assign': False,
|
||||
}
|
||||
|
||||
r = requests.post(tts_category_assignment_url, cookies=cookies, json=payload)
|
||||
|
||||
print('Status: {}'.format(r.status_code))
|
||||
print(r.content)
|
||||
|
||||
Executable
+7
@@ -0,0 +1,7 @@
|
||||
#!/bin/bash
|
||||
# LOCALHOST ONLY
|
||||
|
||||
curl -vvv \
|
||||
-H "content-type: application/json" \
|
||||
--data '{"username": "echelon", "password": "testing", "password_confirmation": "testing", "email_address": "echelon@gmail.com"}' \
|
||||
http://localhost:12345/create_account
|
||||
Executable
+7
@@ -0,0 +1,7 @@
|
||||
#!/bin/bash
|
||||
# LOCALHOST ONLY
|
||||
|
||||
curl -vvv \
|
||||
-H "content-type: application/json" \
|
||||
--data '{"username": "ernest", "password": "knowwhatimean", "password_confirmation": "knowwhatimean", "email_address": "vern@knowwhatimean.com"}' \
|
||||
http://localhost:12345/create_account
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
#!/bin/bash
|
||||
# LOCALHOST ONLY
|
||||
|
||||
curl -vvv \
|
||||
-H "content-type: application/json" \
|
||||
--data '{"username": "vocodes", "password": "testing", "password_confirmation": "testing", "email_address": "vocodes@gmail.com"}' \
|
||||
http://localhost:12345/create_account
|
||||
Executable
+43
@@ -0,0 +1,43 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
import requests
|
||||
import re
|
||||
|
||||
login_url = 'http://localhost:12345/login'
|
||||
inference_url = 'http://localhost:12345/tts/inference'
|
||||
|
||||
payload = {
|
||||
'username_or_email': 'echelon',
|
||||
'password': 'testing',
|
||||
}
|
||||
|
||||
r = requests.post(login_url, json=payload)
|
||||
|
||||
print("===== Login =====")
|
||||
print('Status: {}'.format(r.status_code))
|
||||
print(r.content)
|
||||
for k, v in r.headers.items():
|
||||
print(' {}: {}'.format(k, v))
|
||||
|
||||
# NB: May not be set due to cookie domain:
|
||||
#r.cookies['session']
|
||||
|
||||
raw_set_cookie_header = r.headers['set-cookie']
|
||||
m = re.match(r'session=([^;\s]+);', raw_set_cookie_header)
|
||||
session_cookie = m.group(1)
|
||||
|
||||
|
||||
print("===== Infer TTS =====")
|
||||
|
||||
cookies = { 'session': session_cookie }
|
||||
|
||||
payload = {
|
||||
'tts_model_token': 'TTS_TOKEN',
|
||||
'inference_text': 'this is the text to generate',
|
||||
}
|
||||
|
||||
r = requests.post(inference_url, cookies=cookies, json=payload)
|
||||
|
||||
print('Status: {}'.format(r.status_code))
|
||||
print(r.content)
|
||||
|
||||
Executable
+43
@@ -0,0 +1,43 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
import requests
|
||||
import re
|
||||
|
||||
login_url = 'http://localhost:12345/login'
|
||||
inference_url = 'http://localhost:12345/w2l/inference'
|
||||
|
||||
payload = {
|
||||
'username_or_email': 'echelon',
|
||||
'password': 'testing',
|
||||
}
|
||||
|
||||
r = requests.post(login_url, json=payload)
|
||||
|
||||
print("===== Login =====")
|
||||
print('Status: {}'.format(r.status_code))
|
||||
print(r.content)
|
||||
for k, v in r.headers.items():
|
||||
print(' {}: {}'.format(k, v))
|
||||
|
||||
# NB: May not be set due to cookie domain:
|
||||
#r.cookies['session']
|
||||
|
||||
raw_set_cookie_header = r.headers['set-cookie']
|
||||
m = re.match(r'session=([^;\s]+);', raw_set_cookie_header)
|
||||
session_cookie = m.group(1)
|
||||
|
||||
|
||||
print("===== Infer W2L =====")
|
||||
|
||||
cookies = { 'session': session_cookie }
|
||||
|
||||
payload = {
|
||||
'w2l_template_token': 'W2L_TOKEN',
|
||||
'tts_inference_result_token': 'TTS_TOKEN',
|
||||
}
|
||||
|
||||
r = requests.post(inference_url, cookies=cookies, json=payload)
|
||||
|
||||
print('Status: {}'.format(r.status_code))
|
||||
print(r.content)
|
||||
|
||||
Executable
+7
@@ -0,0 +1,7 @@
|
||||
#!/bin/bash
|
||||
# LOCALHOST ONLY
|
||||
|
||||
curl -vvv \
|
||||
-H "content-type: application/json" \
|
||||
--data '{"username_or_email": "echelon", "password": "BAD_PASSWORD"}' \
|
||||
http://localhost:12345/login
|
||||
Executable
+7
@@ -0,0 +1,7 @@
|
||||
#!/bin/bash
|
||||
# LOCALHOST ONLY
|
||||
|
||||
curl -vvv \
|
||||
-H "content-type: application/json" \
|
||||
--data '{"username_or_email": "nobody", "password": "testing"}' \
|
||||
http://localhost:12345/login
|
||||
Executable
+7
@@ -0,0 +1,7 @@
|
||||
#!/bin/bash
|
||||
# LOCALHOST ONLY
|
||||
|
||||
curl -vvv \
|
||||
-H "content-type: application/json" \
|
||||
--data '{"username_or_email": "echelon@gmail.com", "password": "testing"}' \
|
||||
http://localhost:12345/login
|
||||
Executable
+7
@@ -0,0 +1,7 @@
|
||||
#!/bin/bash
|
||||
# LOCALHOST ONLY
|
||||
|
||||
curl -vvv \
|
||||
-H "content-type: application/json" \
|
||||
--data '{"username_or_email": "echelon", "password": "testing"}' \
|
||||
http://localhost:12345/login
|
||||
Executable
+38
@@ -0,0 +1,38 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
import requests
|
||||
import re
|
||||
|
||||
login_url = 'http://localhost:12345/login'
|
||||
logout_url = 'http://localhost:12345/logout'
|
||||
|
||||
payload = {
|
||||
'username_or_email': 'echelon',
|
||||
'password': 'testing',
|
||||
}
|
||||
|
||||
r = requests.post(login_url, json=payload)
|
||||
|
||||
print("===== Login =====")
|
||||
print('Status: {}'.format(r.status_code))
|
||||
print(r.content)
|
||||
for k, v in r.headers.items():
|
||||
print(' {}: {}'.format(k, v))
|
||||
|
||||
# NB: May not be set due to cookie domain:
|
||||
#r.cookies['session']
|
||||
|
||||
raw_set_cookie_header = r.headers['set-cookie']
|
||||
m = re.match(r'session=([^;\s]+);', raw_set_cookie_header)
|
||||
session_cookie = m.group(1)
|
||||
|
||||
cookies = { 'session': session_cookie }
|
||||
|
||||
r = requests.post(logout_url, cookies=cookies)
|
||||
|
||||
print("===== Logout =====")
|
||||
print('Status: {}'.format(r.status_code))
|
||||
print(r.content)
|
||||
for k, v in r.headers.items():
|
||||
print(' {}: {}'.format(k, v))
|
||||
|
||||
Executable
+123
@@ -0,0 +1,123 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
import requests
|
||||
import re
|
||||
import uuid
|
||||
import socket
|
||||
|
||||
# There are some things here that will cause my laptop to freeze!
|
||||
hostname = socket.gethostname()
|
||||
|
||||
def print_response(response):
|
||||
print('\tStatus: {}'.format(response.status_code))
|
||||
for k, v in response.headers.items():
|
||||
print('\t header - {}: {}'.format(k, v))
|
||||
print('\tbody: {}', response.json())
|
||||
|
||||
# ==================== CREATE ACCOUNTS ====================
|
||||
|
||||
def create_user_account(username, password, email):
|
||||
print('Create account: {}'.format(username))
|
||||
signup_url = 'http://localhost:12345/create_account'
|
||||
payload = {
|
||||
'username': username,
|
||||
'password': password,
|
||||
'password_confirmation': password,
|
||||
'email_address': email,
|
||||
}
|
||||
r = requests.post(signup_url, json=payload)
|
||||
print_response(r)
|
||||
|
||||
create_user_account('echelon', 'testing', 'echelon@gmail.com')
|
||||
create_user_account('brandon', 'testing', 'brandon@storyteller.company')
|
||||
create_user_account('nanashi', 'nanashi', 'somebody@somewhere.com')
|
||||
create_user_account('other', 'testing', 'ryu@storyteller.company')
|
||||
create_user_account('raptor9', 'testing', 'raptor9@storyteller.company')
|
||||
create_user_account('ryu', 'testing', 'ryu@storyteller.company')
|
||||
|
||||
# ==================== LOGIN ====================
|
||||
|
||||
def login(username, password):
|
||||
print('Login: {}'.format(username))
|
||||
login_url = 'http://localhost:12345/login'
|
||||
payload = {
|
||||
'username_or_email': username,
|
||||
'password': password,
|
||||
}
|
||||
r = requests.post(login_url, json=payload)
|
||||
print_response(r)
|
||||
raw_set_cookie_header = r.headers['set-cookie']
|
||||
m = re.match(r'session=([^;\s]+);', raw_set_cookie_header)
|
||||
session_cookie = m.group(1)
|
||||
return session_cookie
|
||||
|
||||
session_echelon = login('echelon', 'testing')
|
||||
session_nanashi = login('nanashi', 'nanashi')
|
||||
session_brandon = login('brandon', 'testing')
|
||||
session_other = login('other', 'testing')
|
||||
|
||||
# ==================== UPLOAD W2L TEMPLATES ====================
|
||||
|
||||
def upload_w2l_template(content_url, title, session_cookie):
|
||||
print('Upload W2L template')
|
||||
upload_url = 'http://localhost:12345/w2l/upload'
|
||||
cookies = { 'session': session_cookie }
|
||||
payload = {
|
||||
'idempotency_token': str(uuid.uuid4()),
|
||||
'title': title,
|
||||
'download_url': content_url,
|
||||
}
|
||||
r = requests.post(upload_url, cookies=cookies, json=payload)
|
||||
print_response(r)
|
||||
|
||||
|
||||
upload_w2l_template('https://drive.google.com/file/d/1yoBSb6nwFUZVI4CD-nM2BSofGdBjC8Nn/view?usp=sharing', 'Mr. Musk', session_nanashi)
|
||||
upload_w2l_template('https://drive.google.com/file/d/1ysBAdRjlSmfWQPFjGlMJpv_zcGmVw7RC/view?usp=sharing', 'short bill clinton clip', session_brandon)
|
||||
upload_w2l_template('https://i.imgur.com/NneE7Eq.jpg', 'itsa me mario!', session_echelon)
|
||||
upload_w2l_template('https://i.imgur.com/W1EYiPU.jpg', 'Creepy Sonic does a gasp', session_echelon)
|
||||
upload_w2l_template('https://i.imgur.com/8U3IdUa.png', 'Creepy Sonic actually talks', session_echelon)
|
||||
upload_w2l_template('https://i.imgur.com/lKaQ4Er.jpg', 'hasta la vista', session_brandon)
|
||||
upload_w2l_template('https://i.imgur.com/uoGuTJo.jpeg', 'what do you mean vern?', session_brandon)
|
||||
upload_w2l_template('https://i.imgur.com/v8k9yau.jpeg', 'Miyamoto-san', session_brandon)
|
||||
|
||||
|
||||
if hostname == 'halide':
|
||||
upload_w2l_template('https://www.youtube.com/watch?v=a7mS9ZdU6k4', 'Steve Jobs talks', session_echelon)
|
||||
upload_w2l_template('https://www.youtube.com/watch?v=6fg8SxgEUYI', 'Paul Graham lectures', session_echelon)
|
||||
|
||||
# ==================== UPLOAD TTS MODELS ====================
|
||||
|
||||
def upload_tts_model(content_url, title, session_cookie):
|
||||
print('Upload TTS model')
|
||||
upload_url = 'http://localhost:12345/tts/upload'
|
||||
cookies = { 'session': session_cookie }
|
||||
payload = {
|
||||
'idempotency_token': str(uuid.uuid4()),
|
||||
'title': title,
|
||||
'download_url': content_url,
|
||||
}
|
||||
r = requests.post(upload_url, cookies=cookies, json=payload)
|
||||
print_response(r)
|
||||
|
||||
|
||||
# From others' Google Drives
|
||||
#upload_tts_model('https://drive.google.com/file/d/1grwDGbAsPwCMVfye071TZ6PLUub1KGyR/view?usp=sharing', 'Jorgen', session_nanashi)
|
||||
#upload_tts_model('https://drive.google.com/file/d/1w3oBk5vzyHurqYwP95UEQv-_NeMNu6pt/view?usp=sharing', 'Noire', session_echelon)
|
||||
|
||||
# Copied into Storyteller Google Drive
|
||||
upload_tts_model('https://drive.google.com/file/d/1d8KPwYqbRsGiYx1c4TCW4xCtcRLdIU1s/view?usp=sharing', 'Zephyr', session_echelon)
|
||||
upload_tts_model('https://drive.google.com/file/d/1yhoamPz1NOMy403gu2v8sIx5hPj3LQWs/view?usp=sharing', 'Wakko Warner', session_other)
|
||||
# TEMP COMMENT DUE TO RATE LIMIT upload_tts_model('https://drive.google.com/file/d/1I6KGQZFMMB2ZvCzWEqy9xTgGl0L5cmfP/view?usp=sharing', 'Turret-192', session_echelon)
|
||||
# TEMP COMMENT DUE TO RATE LIMIT upload_tts_model('https://drive.google.com/file/d/1T_hvgGcNOfofhNmDWUqpVtYDdSXQrWdE/view?usp=sharing', 'technoblade-324', session_brandon)
|
||||
# TEMP COMMENT DUE TO RATE LIMIT upload_tts_model('https://drive.google.com/file/d/1NKyRiex3K1J0QyIv2np17D_H0rE98HyK/view?usp=sharing', 'Noire', session_other)
|
||||
# TEMP COMMENT DUE TO RATE LIMIT upload_tts_model('https://drive.google.com/file/d/1VDHDOpsUR8C-MD07vEsnUf1Lpcf8hXg4/view?usp=sharing', 'MatPat80', session_other)
|
||||
# TEMP COMMENT DUE TO RATE LIMIT upload_tts_model('https://drive.google.com/file/d/1atFn4vm7mjkufXNK9TZS6J8WzISrAn3-/view?usp=sharing', 'King Julien-70', session_other)
|
||||
# TEMP COMMENT DUE TO RATE LIMIT upload_tts_model('https://drive.google.com/file/d/1-rlJpi2jAsagn-UCStcCc_ITNycXgn2F/view?usp=sharing', 'Jorgen', session_other)
|
||||
# TEMP COMMENT DUE TO RATE LIMIT upload_tts_model('https://drive.google.com/file/d/1togTNOEKnaarqWWrbZLSwIbZIZ8IT_qw/view?usp=sharing', 'edsheeransinging', session_nanashi)
|
||||
# TEMP COMMENT DUE TO RATE LIMIT
|
||||
# TEMP COMMENT DUE TO RATE LIMIT upload_tts_model('https://drive.google.com/file/d/1OAiu0cZDyUJMemyLc0Btqw7zEQgiN81D/view?usp=sharing', 'Wakko Warner v2 ST21', session_nanashi)
|
||||
|
||||
upload_tts_model('https://drive.google.com/file/d/1vRVI2gQ5tAs8SLQPrR_M1J77L05gGBOp/view?usp=sharing', 'Testing voice', session_nanashi)
|
||||
|
||||
#upload_tts_model('', '', session_other)
|
||||
|
||||
Executable
+41
@@ -0,0 +1,41 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
import requests
|
||||
import re
|
||||
|
||||
login_url = 'http://localhost:12345/login'
|
||||
session_info_url = 'http://localhost:12345/session'
|
||||
|
||||
|
||||
r = requests.get(session_info_url)
|
||||
|
||||
print("===== Session Info (logged out) =====")
|
||||
print(r.content)
|
||||
|
||||
payload = {
|
||||
'username_or_email': 'echelon',
|
||||
'password': 'testing',
|
||||
}
|
||||
|
||||
r = requests.post(login_url, json=payload)
|
||||
|
||||
print("===== Login =====")
|
||||
print('Status: {}'.format(r.status_code))
|
||||
print(r.content)
|
||||
for k, v in r.headers.items():
|
||||
print(' {}: {}'.format(k, v))
|
||||
|
||||
# NB: May not be set due to cookie domain:
|
||||
#r.cookies['session']
|
||||
|
||||
raw_set_cookie_header = r.headers['set-cookie']
|
||||
m = re.match(r'session=([^;\s]+);', raw_set_cookie_header)
|
||||
session_cookie = m.group(1)
|
||||
|
||||
cookies = { 'session': session_cookie }
|
||||
|
||||
r = requests.get(session_info_url, cookies=cookies)
|
||||
|
||||
print("===== Session Info (logged in) =====")
|
||||
print(r.content)
|
||||
|
||||
+306
@@ -0,0 +1,306 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
import json
|
||||
import re
|
||||
import requests
|
||||
import uuid
|
||||
import pprint
|
||||
|
||||
login_url = 'http://localhost:12345/login'
|
||||
create_url = 'http://localhost:12345/twitch/event_rule/create'
|
||||
list_url = 'http://localhost:12345/twitch/event_rule/list'
|
||||
update_url = 'http://localhost:12345/twitch/event_rule/{}/update'
|
||||
delete_url = 'http://localhost:12345/twitch/event_rule/{}/delete'
|
||||
|
||||
|
||||
def delete_rule(token, cookies):
|
||||
url = delete_url.format(token)
|
||||
r = requests.delete(url, cookies=cookies)
|
||||
print("===== Deleted Event Rule Output =====")
|
||||
print('Status: {}'.format(r.status_code))
|
||||
print(r.content)
|
||||
|
||||
def create_rule(payload, cookies):
|
||||
r = requests.post(create_url, cookies=cookies, json=payload)
|
||||
print("===== Created Event Rule Output =====")
|
||||
print('Status: {}'.format(r.status_code))
|
||||
print(r.content)
|
||||
|
||||
|
||||
# ========== Setup / Login ==========
|
||||
|
||||
payload = {
|
||||
'username_or_email': 'echelon',
|
||||
'password': 'testing',
|
||||
}
|
||||
|
||||
r = requests.post(login_url, json=payload)
|
||||
|
||||
print("===== Login =====")
|
||||
print('Status: {}'.format(r.status_code))
|
||||
print(r.content)
|
||||
for k, v in r.headers.items():
|
||||
print(' {}: {}'.format(k, v))
|
||||
|
||||
raw_set_cookie_header = r.headers['set-cookie']
|
||||
m = re.match(r'session=([^;\s]+);', raw_set_cookie_header)
|
||||
session_cookie = m.group(1)
|
||||
|
||||
cookies = { 'session': session_cookie }
|
||||
|
||||
# ========== Delete All Existing ==========
|
||||
|
||||
r = requests.get(list_url, cookies=cookies)
|
||||
|
||||
print("===== List Event Rule Output =====")
|
||||
print('Status: {}'.format(r.status_code))
|
||||
response = json.loads(r.content)
|
||||
|
||||
tokens = [rule['token'] for rule in response['twitch_event_rules']]
|
||||
for token in tokens:
|
||||
delete_rule(token, cookies)
|
||||
|
||||
# ========== Create (1) ==========
|
||||
|
||||
payload = {
|
||||
'idempotency_token': str(uuid.uuid4()),
|
||||
'event_category': 'bits',
|
||||
'event_match_predicate': {
|
||||
'bits_spend_threshold': {
|
||||
'minimum_bits_spent': 1,
|
||||
},
|
||||
},
|
||||
'event_response': {
|
||||
'tts_single_voice': {
|
||||
'tts_model_token': 'TM:40m3aqtt41y0', # "Wakko" voice (dev)
|
||||
}
|
||||
},
|
||||
'user_specified_rule_order': 0,
|
||||
'rule_is_disabled': False,
|
||||
}
|
||||
|
||||
r = requests.post(create_url, cookies=cookies, json=payload)
|
||||
|
||||
print("===== Created Event Rule Output =====")
|
||||
print('Status: {}'.format(r.status_code))
|
||||
print(r.content)
|
||||
|
||||
response = json.loads(r.content)
|
||||
token = response['twitch_event_rule_token']
|
||||
|
||||
# ========== Update (1) ==========
|
||||
|
||||
payload = {
|
||||
'event_match_predicate': {
|
||||
'bits_spend_threshold': {
|
||||
'minimum_bits_spent': 12345,
|
||||
},
|
||||
},
|
||||
'event_response': {
|
||||
'tts_single_voice': {
|
||||
'tts_model_token': 'TM:40m3aqtt41y0', # "Wakko" voice (dev)
|
||||
}
|
||||
},
|
||||
'user_specified_rule_order': 123,
|
||||
'rule_is_disabled': True,
|
||||
}
|
||||
url = update_url.format(token)
|
||||
r = requests.post(url, cookies=cookies, json=payload)
|
||||
|
||||
print("===== Edited Event Rule Output =====")
|
||||
print('Status: {}'.format(r.status_code))
|
||||
print(r.content)
|
||||
|
||||
|
||||
# ========== Create: Bits Spend Threshold ==========
|
||||
|
||||
payload = {
|
||||
'idempotency_token': str(uuid.uuid4()),
|
||||
'event_category': 'bits',
|
||||
'event_match_predicate': {
|
||||
'bits_spend_threshold': {
|
||||
'minimum_bits_spent': 1000,
|
||||
},
|
||||
},
|
||||
'event_response': {
|
||||
'tts_single_voice': {
|
||||
'tts_model_token': 'TM:40m3aqtt41y0', # "Wakko" voice (dev)
|
||||
}
|
||||
},
|
||||
'user_specified_rule_order': 0,
|
||||
'rule_is_disabled': True,
|
||||
}
|
||||
|
||||
create_rule(payload, cookies)
|
||||
|
||||
# ========== Create: Cheermote Exact Match ==========
|
||||
|
||||
payload = {
|
||||
'idempotency_token': str(uuid.uuid4()),
|
||||
'event_category': 'bits',
|
||||
'event_match_predicate': {
|
||||
'bits_cheermote_name_exact_match': {
|
||||
'cheermote_name': 'Cheer1',
|
||||
},
|
||||
},
|
||||
'event_response': {
|
||||
'tts_single_voice': {
|
||||
'tts_model_token': 'TM:40m3aqtt41y0', # "Wakko" voice (dev)
|
||||
}
|
||||
},
|
||||
'user_specified_rule_order': 0,
|
||||
'rule_is_disabled': False,
|
||||
}
|
||||
|
||||
create_rule(payload, cookies)
|
||||
|
||||
payload = {
|
||||
'idempotency_token': str(uuid.uuid4()),
|
||||
'event_category': 'bits',
|
||||
'event_match_predicate': {
|
||||
'bits_cheermote_name_exact_match': {
|
||||
'cheermote_name': 'Corgo5000',
|
||||
},
|
||||
},
|
||||
'event_response': {
|
||||
'tts_single_voice': {
|
||||
'tts_model_token': 'TM:40m3aqtt41y0', # "Wakko" voice (dev)
|
||||
}
|
||||
},
|
||||
'user_specified_rule_order': 0,
|
||||
'rule_is_disabled': False,
|
||||
}
|
||||
|
||||
create_rule(payload, cookies)
|
||||
|
||||
payload = {
|
||||
'idempotency_token': str(uuid.uuid4()),
|
||||
'event_category': 'bits',
|
||||
'event_match_predicate': {
|
||||
'bits_cheermote_name_exact_match': {
|
||||
'cheermote_name': 'ZomboCom5000',
|
||||
},
|
||||
},
|
||||
'event_response': {
|
||||
'tts_single_voice': {
|
||||
'tts_model_token': 'TM:40m3aqtt41y0', # "Wakko" voice (dev)
|
||||
}
|
||||
},
|
||||
'user_specified_rule_order': 0,
|
||||
'rule_is_disabled': False,
|
||||
}
|
||||
|
||||
create_rule(payload, cookies)
|
||||
|
||||
# ========== Create: Cheermote Prefix And Spend Threshold ==========
|
||||
|
||||
payload = {
|
||||
'idempotency_token': str(uuid.uuid4()),
|
||||
'event_category': 'bits',
|
||||
'event_match_predicate': {
|
||||
'bits_cheermote_prefix_spend_threshold': {
|
||||
'cheermote_prefix': 'DoodleCheer',
|
||||
'minimum_bits_spent': 1000,
|
||||
},
|
||||
},
|
||||
'event_response': {
|
||||
'tts_single_voice': {
|
||||
'tts_model_token': 'TM:40m3aqtt41y0', # "Wakko" voice (dev)
|
||||
}
|
||||
},
|
||||
'user_specified_rule_order': 0,
|
||||
'rule_is_disabled': False,
|
||||
}
|
||||
|
||||
create_rule(payload, cookies)
|
||||
|
||||
payload = {
|
||||
'idempotency_token': str(uuid.uuid4()),
|
||||
'event_category': 'bits',
|
||||
'event_match_predicate': {
|
||||
'bits_cheermote_prefix_spend_threshold': {
|
||||
'cheermote_prefix': 'Corgo',
|
||||
'minimum_bits_spent': 12345,
|
||||
},
|
||||
},
|
||||
'event_response': {
|
||||
'tts_single_voice': {
|
||||
'tts_model_token': 'TM:40m3aqtt41y0', # "Wakko" voice (dev)
|
||||
}
|
||||
},
|
||||
'user_specified_rule_order': 0,
|
||||
'rule_is_disabled': False,
|
||||
}
|
||||
|
||||
payload = {
|
||||
'idempotency_token': str(uuid.uuid4()),
|
||||
'event_category': 'bits',
|
||||
'event_match_predicate': {
|
||||
'bits_cheermote_prefix_spend_threshold': {
|
||||
'cheermote_prefix': 'ZomboCom',
|
||||
'minimum_bits_spent': 1000,
|
||||
},
|
||||
},
|
||||
'event_response': {
|
||||
'tts_single_voice': {
|
||||
'tts_model_token': 'TM:40m3aqtt41y0', # "Wakko" voice (dev)
|
||||
}
|
||||
},
|
||||
'user_specified_rule_order': 0,
|
||||
'rule_is_disabled': False,
|
||||
}
|
||||
|
||||
create_rule(payload, cookies)
|
||||
|
||||
# ========== Create: Channel Points ==========
|
||||
|
||||
payload = {
|
||||
'idempotency_token': str(uuid.uuid4()),
|
||||
'event_category': 'channel_points',
|
||||
'event_match_predicate': {
|
||||
'channel_points_reward_name_exact_match': {
|
||||
'reward_name': 'My Reward',
|
||||
},
|
||||
},
|
||||
'event_response': {
|
||||
'tts_single_voice': {
|
||||
'tts_model_token': 'TM:40m3aqtt41y0', # "Wakko" voice (dev)
|
||||
}
|
||||
},
|
||||
'user_specified_rule_order': 0,
|
||||
'rule_is_disabled': False,
|
||||
}
|
||||
|
||||
create_rule(payload, cookies)
|
||||
|
||||
payload = {
|
||||
'idempotency_token': str(uuid.uuid4()),
|
||||
'event_category': 'channel_points',
|
||||
'event_match_predicate': {
|
||||
'channel_points_reward_name_exact_match': {
|
||||
'reward_name': 'My Reward',
|
||||
},
|
||||
},
|
||||
'event_response': {
|
||||
'tts_random_voice': {
|
||||
'tts_model_tokens': [
|
||||
'TM:40m3aqtt41y0', # "Wakko" voice (dev)
|
||||
'TM:4c1hycjj3a3t', # "Zephyr" voice (dev)
|
||||
],
|
||||
}
|
||||
},
|
||||
'user_specified_rule_order': 0,
|
||||
'rule_is_disabled': False,
|
||||
}
|
||||
|
||||
create_rule(payload, cookies)
|
||||
|
||||
# ========== List ==========
|
||||
|
||||
r = requests.get(list_url, cookies=cookies)
|
||||
|
||||
print("===== List Event Rule Output =====")
|
||||
print('Status: {}'.format(r.status_code))
|
||||
response = json.loads(r.content)
|
||||
pprint.pprint(response)
|
||||
|
||||
Executable
+45
@@ -0,0 +1,45 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
import requests
|
||||
import re
|
||||
import uuid
|
||||
|
||||
login_url = 'http://localhost:12345/login'
|
||||
upload_url = 'http://localhost:12345/tts/upload'
|
||||
|
||||
payload = {
|
||||
'username_or_email': 'echelon',
|
||||
'password': 'testing',
|
||||
}
|
||||
|
||||
r = requests.post(login_url, json=payload)
|
||||
|
||||
print("===== Login =====")
|
||||
print('Status: {}'.format(r.status_code))
|
||||
print(r.content)
|
||||
for k, v in r.headers.items():
|
||||
print(' {}: {}'.format(k, v))
|
||||
|
||||
# NB: May not be set due to cookie domain:
|
||||
#r.cookies['session']
|
||||
|
||||
raw_set_cookie_header = r.headers['set-cookie']
|
||||
m = re.match(r'session=([^;\s]+);', raw_set_cookie_header)
|
||||
session_cookie = m.group(1)
|
||||
|
||||
|
||||
print("===== Upload TTS=====")
|
||||
|
||||
cookies = { 'session': session_cookie }
|
||||
|
||||
payload = {
|
||||
'idempotency_token': str(uuid.uuid4()),
|
||||
'title': 'this is a new text to speech model',
|
||||
'download_url': 'https://website.com',
|
||||
}
|
||||
|
||||
r = requests.post(upload_url, cookies=cookies, json=payload)
|
||||
|
||||
print('Status: {}'.format(r.status_code))
|
||||
print(r.content)
|
||||
|
||||
Executable
+45
@@ -0,0 +1,45 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
import requests
|
||||
import re
|
||||
import uuid
|
||||
|
||||
login_url = 'http://localhost:12345/login'
|
||||
upload_url = 'http://localhost:12345/tts/upload'
|
||||
|
||||
payload = {
|
||||
'username_or_email': 'echelon',
|
||||
'password': 'testing',
|
||||
}
|
||||
|
||||
r = requests.post(login_url, json=payload)
|
||||
|
||||
print("===== Login =====")
|
||||
print('Status: {}'.format(r.status_code))
|
||||
print(r.content)
|
||||
for k, v in r.headers.items():
|
||||
print(' {}: {}'.format(k, v))
|
||||
|
||||
# NB: May not be set due to cookie domain:
|
||||
#r.cookies['session']
|
||||
|
||||
raw_set_cookie_header = r.headers['set-cookie']
|
||||
m = re.match(r'session=([^;\s]+);', raw_set_cookie_header)
|
||||
session_cookie = m.group(1)
|
||||
|
||||
|
||||
print("===== Upload TTS=====")
|
||||
|
||||
cookies = { 'session': session_cookie }
|
||||
|
||||
payload = {
|
||||
'idempotency_token': str(uuid.uuid4()),
|
||||
'title': 'this is a new text to speech model',
|
||||
'download_url': 'https://drive.google.com/file/d/15-tkgblTZpa0ifmvvyr_kgWy2cguTpAe/view?usp=sharing',
|
||||
}
|
||||
|
||||
r = requests.post(upload_url, cookies=cookies, json=payload)
|
||||
|
||||
print('Status: {}'.format(r.status_code))
|
||||
print(r.content)
|
||||
|
||||
Executable
+45
@@ -0,0 +1,45 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
import re
|
||||
import requests
|
||||
import uuid
|
||||
|
||||
login_url = 'http://localhost:12345/login'
|
||||
upload_url = 'http://localhost:12345/w2l/upload'
|
||||
|
||||
payload = {
|
||||
'username_or_email': 'echelon',
|
||||
'password': 'testing',
|
||||
}
|
||||
|
||||
r = requests.post(login_url, json=payload)
|
||||
|
||||
print("===== Login =====")
|
||||
print('Status: {}'.format(r.status_code))
|
||||
print(r.content)
|
||||
for k, v in r.headers.items():
|
||||
print(' {}: {}'.format(k, v))
|
||||
|
||||
# NB: May not be set due to cookie domain:
|
||||
#r.cookies['session']
|
||||
|
||||
raw_set_cookie_header = r.headers['set-cookie']
|
||||
m = re.match(r'session=([^;\s]+);', raw_set_cookie_header)
|
||||
session_cookie = m.group(1)
|
||||
|
||||
|
||||
print("===== Upload W2L =====")
|
||||
|
||||
cookies = { 'session': session_cookie }
|
||||
|
||||
payload = {
|
||||
'idempotency_token': str(uuid.uuid4()),
|
||||
'title': 'this is a new text to W2L template',
|
||||
'download_url': 'https://video.com',
|
||||
}
|
||||
|
||||
r = requests.post(upload_url, cookies=cookies, json=payload)
|
||||
|
||||
print('Status: {}'.format(r.status_code))
|
||||
print(r.content)
|
||||
|
||||
+52
@@ -0,0 +1,52 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
import requests
|
||||
import re
|
||||
import uuid
|
||||
|
||||
login_url = 'http://localhost:12345/login'
|
||||
upload_url = 'http://localhost:12345/w2l/upload'
|
||||
|
||||
payload = {
|
||||
'username_or_email': 'echelon',
|
||||
'password': 'testing',
|
||||
}
|
||||
|
||||
r = requests.post(login_url, json=payload)
|
||||
|
||||
print("===== Login =====")
|
||||
print('Status: {}'.format(r.status_code))
|
||||
print(r.content)
|
||||
for k, v in r.headers.items():
|
||||
print(' {}: {}'.format(k, v))
|
||||
|
||||
# NB: May not be set due to cookie domain:
|
||||
#r.cookies['session']
|
||||
|
||||
raw_set_cookie_header = r.headers['set-cookie']
|
||||
m = re.match(r'session=([^;\s]+);', raw_set_cookie_header)
|
||||
session_cookie = m.group(1)
|
||||
|
||||
|
||||
print("===== Upload W2L =====")
|
||||
|
||||
cookies = { 'session': session_cookie }
|
||||
|
||||
# mario.zip
|
||||
#download_url = 'https://drive.google.com/file/d/15-tkgblTZpa0ifmvvyr_kgWy2cguTpAe/view?usp=sharing'
|
||||
# elon_integration_test.jpg
|
||||
download_url = 'https://drive.google.com/file/d/1yoBSb6nwFUZVI4CD-nM2BSofGdBjC8Nn/view?usp=sharing'
|
||||
|
||||
#download_url = 'https://i.imgur.com/...'
|
||||
|
||||
payload = {
|
||||
'idempotency_token': str(uuid.uuid4()),
|
||||
'title': 'this is a new image template',
|
||||
'download_url': download_url,
|
||||
}
|
||||
|
||||
r = requests.post(upload_url, cookies=cookies, json=payload)
|
||||
|
||||
print('Status: {}'.format(r.status_code))
|
||||
print(r.content)
|
||||
|
||||
+61
@@ -0,0 +1,61 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
import requests
|
||||
import re
|
||||
import uuid
|
||||
|
||||
login_url = 'http://localhost:12345/login'
|
||||
upload_url = 'http://localhost:12345/w2l/upload'
|
||||
|
||||
payload = {
|
||||
'username_or_email': 'echelon',
|
||||
'password': 'testing',
|
||||
}
|
||||
|
||||
r = requests.post(login_url, json=payload)
|
||||
|
||||
print("===== Login =====")
|
||||
print('Status: {}'.format(r.status_code))
|
||||
print(r.content)
|
||||
for k, v in r.headers.items():
|
||||
print(' {}: {}'.format(k, v))
|
||||
|
||||
# NB: May not be set due to cookie domain:
|
||||
#r.cookies['session']
|
||||
|
||||
raw_set_cookie_header = r.headers['set-cookie']
|
||||
m = re.match(r'session=([^;\s]+);', raw_set_cookie_header)
|
||||
session_cookie = m.group(1)
|
||||
|
||||
|
||||
def upload(url):
|
||||
print("===== Upload W2L =====")
|
||||
|
||||
cookies = { 'session': session_cookie }
|
||||
|
||||
payload = {
|
||||
'idempotency_token': str(uuid.uuid4()),
|
||||
'title': 'this is a new image template',
|
||||
'download_url': url,
|
||||
}
|
||||
|
||||
r = requests.post(upload_url, cookies=cookies, json=payload)
|
||||
|
||||
print('Status: {}'.format(r.status_code))
|
||||
print(r.content)
|
||||
|
||||
|
||||
upload('https://i.imgur.com/W1EYiPU.jpg')
|
||||
|
||||
upload('https://i.imgur.com/NneE7Eq.jpg')
|
||||
|
||||
upload('https://i.imgur.com/v8k9yau.jpeg')
|
||||
|
||||
upload('https://drive.google.com/file/d/1yoBSb6nwFUZVI4CD-nM2BSofGdBjC8Nn/view?usp=sharing')
|
||||
|
||||
upload('https://drive.google.com/file/d/1ysBAdRjlSmfWQPFjGlMJpv_zcGmVw7RC/view?usp=sharing')
|
||||
|
||||
upload('https://i.imgur.com/lKaQ4Er.jpg')
|
||||
|
||||
upload('https://i.imgur.com/uoGuTJo.jpeg')
|
||||
|
||||
+55
@@ -0,0 +1,55 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
import requests
|
||||
import re
|
||||
import uuid
|
||||
|
||||
login_url = 'http://localhost:12345/login'
|
||||
upload_url = 'http://localhost:12345/w2l/upload'
|
||||
|
||||
payload = {
|
||||
'username_or_email': 'echelon',
|
||||
'password': 'testing',
|
||||
}
|
||||
|
||||
r = requests.post(login_url, json=payload)
|
||||
|
||||
print("===== Login =====")
|
||||
print('Status: {}'.format(r.status_code))
|
||||
print(r.content)
|
||||
for k, v in r.headers.items():
|
||||
print(' {}: {}'.format(k, v))
|
||||
|
||||
# NB: May not be set due to cookie domain:
|
||||
#r.cookies['session']
|
||||
|
||||
raw_set_cookie_header = r.headers['set-cookie']
|
||||
m = re.match(r'session=([^;\s]+);', raw_set_cookie_header)
|
||||
session_cookie = m.group(1)
|
||||
|
||||
|
||||
print("===== Upload W2L =====")
|
||||
|
||||
cookies = { 'session': session_cookie }
|
||||
|
||||
# mario.zip
|
||||
#download_url = 'https://drive.google.com/file/d/15-tkgblTZpa0ifmvvyr_kgWy2cguTpAe/view?usp=sharing'
|
||||
# elon_integration_test.jpg
|
||||
#download_url = 'https://drive.google.com/file/d/1yoBSb6nwFUZVI4CD-nM2BSofGdBjC8Nn/view?usp=sharing'
|
||||
# bill-integration-test.mp4
|
||||
# NB: This freezes up my poor laptop:
|
||||
###download_url = 'https://drive.google.com/file/d/14yO_60lc1FJCYhDGaeiQabgGvYLa86WI/THIS_IS_TOO_BIGview?usp=sharing'
|
||||
# bill-integration-small.mp4 (small enough to run on my laptop)
|
||||
download_url = 'https://drive.google.com/file/d/1ysBAdRjlSmfWQPFjGlMJpv_zcGmVw7RC/view?usp=sharing'
|
||||
|
||||
payload = {
|
||||
'idempotency_token': str(uuid.uuid4()),
|
||||
'title': 'this is a new image template',
|
||||
'download_url': download_url,
|
||||
}
|
||||
|
||||
r = requests.post(upload_url, cookies=cookies, json=payload)
|
||||
|
||||
print('Status: {}'.format(r.status_code))
|
||||
print(r.content)
|
||||
|
||||
Executable
+7
@@ -0,0 +1,7 @@
|
||||
#!/bin/bash
|
||||
# ONLY FOR LOCAL DEV
|
||||
mysql -u storyteller \
|
||||
-ppassword \
|
||||
-h localhost \
|
||||
-D storyteller \
|
||||
-e "select * from badges\G"
|
||||
Executable
+7
@@ -0,0 +1,7 @@
|
||||
#!/bin/bash
|
||||
# ONLY FOR LOCAL DEV
|
||||
mysql -u storyteller \
|
||||
-ppassword \
|
||||
-h localhost \
|
||||
-D storyteller \
|
||||
-e "select * from ip_address_bans\G"
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
#!/bin/bash
|
||||
# ONLY FOR LOCAL DEV
|
||||
mysql -u storyteller \
|
||||
-ppassword \
|
||||
-h localhost \
|
||||
-D storyteller \
|
||||
-e "select * from firehose_entries\G"
|
||||
Executable
+7
@@ -0,0 +1,7 @@
|
||||
#!/bin/bash
|
||||
# ONLY FOR LOCAL DEV
|
||||
mysql -u root \
|
||||
-proot \
|
||||
-h localhost \
|
||||
-D storyteller \
|
||||
-e "select * from user_sessions\G"
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
#!/bin/bash
|
||||
# ONLY FOR LOCAL DEV
|
||||
mysql -u storyteller \
|
||||
-ppassword \
|
||||
-h localhost \
|
||||
-D storyteller \
|
||||
-e "select * from tts_inference_jobs\G"
|
||||
+45
@@ -0,0 +1,45 @@
|
||||
#!/bin/bash
|
||||
# ONLY FOR LOCAL DEV
|
||||
|
||||
mysql -u storyteller \
|
||||
-ppassword \
|
||||
-h localhost \
|
||||
-D storyteller \
|
||||
-e "select * from tts_results\G"
|
||||
|
||||
|
||||
#echo '==============================================='
|
||||
#
|
||||
#read -r -d '' query << EOF
|
||||
#
|
||||
# SELECT
|
||||
# w2l_results.token as w2l_result_token,
|
||||
# w2l_results.maybe_tts_inference_result_token,
|
||||
# w2l_templates.token as maybe_w2l_template_token,
|
||||
# w2l_templates.template_type,
|
||||
# users.token as maybe_creator_user_token,
|
||||
# users.username as maybe_creator_username,
|
||||
# users.display_name as maybe_creator_display_name,
|
||||
# w2l_results.file_size_bytes,
|
||||
# w2l_results.frame_width,
|
||||
# w2l_results.frame_height,
|
||||
# w2l_results.duration_millis,
|
||||
# w2l_results.created_at,
|
||||
# w2l_results.updated_at
|
||||
# FROM
|
||||
# w2l_results
|
||||
# LEFT OUTER JOIN w2l_templates ON w2l_results.maybe_w2l_template_token = w2l_templates.token
|
||||
# LEFT OUTER JOIN users ON w2l_results.maybe_creator_user_token = users.token
|
||||
# WHERE
|
||||
# w2l_results.deleted_at IS NULL
|
||||
# AND users.username = 'echelon'\G
|
||||
#EOF
|
||||
#
|
||||
#echo $query
|
||||
#
|
||||
#mysql -u root \
|
||||
# -proot \
|
||||
# -h localhost \
|
||||
# -D storyteller \
|
||||
# -e "${query}"
|
||||
|
||||
Executable
+7
@@ -0,0 +1,7 @@
|
||||
#!/bin/bash
|
||||
# ONLY FOR LOCAL DEV
|
||||
mysql -u storyteller \
|
||||
-ppassword \
|
||||
-h localhost \
|
||||
-D storyteller \
|
||||
-e "select * from tts_models\G"
|
||||
Executable
+7
@@ -0,0 +1,7 @@
|
||||
#!/bin/bash
|
||||
# ONLY FOR LOCAL DEV
|
||||
mysql -u root \
|
||||
-proot \
|
||||
-h localhost \
|
||||
-D storyteller \
|
||||
-e "select * from tts_model_upload_jobs\G"
|
||||
Executable
+7
@@ -0,0 +1,7 @@
|
||||
#!/bin/bash
|
||||
# ONLY FOR LOCAL DEV
|
||||
mysql -u root \
|
||||
-proot \
|
||||
-h localhost \
|
||||
-D storyteller \
|
||||
-e "select * from users\G"
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
#!/bin/bash
|
||||
# ONLY FOR LOCAL DEV
|
||||
mysql -u root \
|
||||
-proot \
|
||||
-h localhost \
|
||||
-D storyteller \
|
||||
-e "select * from w2l_inference_jobs\G"
|
||||
+45
@@ -0,0 +1,45 @@
|
||||
#!/bin/bash
|
||||
# ONLY FOR LOCAL DEV
|
||||
|
||||
mysql -u storyteller \
|
||||
-ppassword \
|
||||
-h localhost \
|
||||
-D storyteller \
|
||||
-e "select * from w2l_results\G"
|
||||
|
||||
|
||||
echo '==============================================='
|
||||
|
||||
read -r -d '' query << EOF
|
||||
|
||||
SELECT
|
||||
w2l_results.token as w2l_result_token,
|
||||
w2l_results.maybe_tts_inference_result_token,
|
||||
w2l_templates.token as maybe_w2l_template_token,
|
||||
w2l_templates.template_type,
|
||||
users.token as maybe_creator_user_token,
|
||||
users.username as maybe_creator_username,
|
||||
users.display_name as maybe_creator_display_name,
|
||||
w2l_results.file_size_bytes,
|
||||
w2l_results.frame_width,
|
||||
w2l_results.frame_height,
|
||||
w2l_results.duration_millis,
|
||||
w2l_results.created_at,
|
||||
w2l_results.updated_at
|
||||
FROM
|
||||
w2l_results
|
||||
LEFT OUTER JOIN w2l_templates ON w2l_results.maybe_w2l_template_token = w2l_templates.token
|
||||
LEFT OUTER JOIN users ON w2l_results.maybe_creator_user_token = users.token
|
||||
WHERE
|
||||
w2l_results.deleted_at IS NULL
|
||||
AND users.username = 'echelon'\G
|
||||
EOF
|
||||
|
||||
echo $query
|
||||
|
||||
mysql -u storyteller \
|
||||
-ppassword \
|
||||
-h localhost \
|
||||
-D storyteller \
|
||||
-e "${query}"
|
||||
|
||||
Executable
+7
@@ -0,0 +1,7 @@
|
||||
#!/bin/bash
|
||||
# ONLY FOR LOCAL DEV
|
||||
mysql -u storyteller \
|
||||
-ppassword \
|
||||
-h localhost \
|
||||
-D storyteller \
|
||||
-e "select * from w2l_templates\G"
|
||||
Executable
+7
@@ -0,0 +1,7 @@
|
||||
#!/bin/bash
|
||||
# ONLY FOR LOCAL DEV
|
||||
mysql -u root \
|
||||
-proot \
|
||||
-h localhost \
|
||||
-D storyteller \
|
||||
-e "select * from w2l_template_upload_jobs\G"
|
||||
Executable
+7
@@ -0,0 +1,7 @@
|
||||
#!/bin/bash
|
||||
# ONLY FOR LOCAL DEV
|
||||
mysql -u storyteller \
|
||||
-ppassword \
|
||||
-h localhost \
|
||||
-D storyteller \
|
||||
-e "select * from user_roles\G"
|
||||
Executable
+7
@@ -0,0 +1,7 @@
|
||||
#!/bin/bash
|
||||
# ONLY FOR LOCAL DEV
|
||||
mysql -u storyteller \
|
||||
-ppassword \
|
||||
-h localhost \
|
||||
-D storyteller \
|
||||
-e "update users set user_role_slug='admin' where username='echelon' limit 1\G"
|
||||
Executable
+7
@@ -0,0 +1,7 @@
|
||||
#!/bin/bash
|
||||
# ONLY FOR LOCAL DEV
|
||||
mysql -u storyteller \
|
||||
-ppassword \
|
||||
-h localhost \
|
||||
-D storyteller \
|
||||
-e "update users set user_role_slug='user' where username='echelon' limit 1\G"
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
#!/bin/bash
|
||||
# ONLY FOR LOCAL DEV
|
||||
mysql -u storyteller \
|
||||
-ppassword \
|
||||
-h localhost \
|
||||
-D storyteller \
|
||||
-e "select * from tts_result_synthetic_ids\G"
|
||||
@@ -0,0 +1,6 @@
|
||||
# Secrets
|
||||
db_secrets.toml
|
||||
|
||||
# Python Virtual Env
|
||||
python/
|
||||
|
||||
Executable
+88
@@ -0,0 +1,88 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
# FIXME: Needs packages 'toml' and 'mysql-connector-python'. Put in requirements.txt.
|
||||
|
||||
import time
|
||||
import toml
|
||||
import mysql.connector
|
||||
|
||||
DB_SECRETS_FILE = 'db_secrets.toml'
|
||||
|
||||
# TODO: Share db-related setup when more scripts are added.
|
||||
|
||||
def read_database_secrets(secrets_file = DB_SECRETS_FILE):
|
||||
secrets = None
|
||||
with open(secrets_file, "r") as f:
|
||||
secrets = toml.load(f)
|
||||
|
||||
required_keys = ['database', 'hostname', 'port', 'password', 'username']
|
||||
for key in required_keys:
|
||||
if key not in secrets:
|
||||
raise Exception(f"Required key '#{key}' not present")
|
||||
|
||||
return secrets
|
||||
|
||||
def connect_to_mysql():
|
||||
secrets = read_database_secrets()
|
||||
connection = mysql.connector.connect(
|
||||
host = secrets['hostname'],
|
||||
port = secrets['port'],
|
||||
user = secrets['username'],
|
||||
password = secrets['password'],
|
||||
database = secrets['database'],
|
||||
)
|
||||
return connection
|
||||
|
||||
connection = connect_to_mysql()
|
||||
cursor = connection.cursor()
|
||||
|
||||
SAFE_ID = 76000000 # Reasonably completed on 2022-06-24
|
||||
LIMIT = 100000
|
||||
|
||||
#def get_record_count():
|
||||
# query = f"SELECT COUNT(*) FROM tts_inference_jobs"
|
||||
# cursor.execute(query)
|
||||
# result = cursor.fetchall()
|
||||
# print(result)
|
||||
# return result[0]
|
||||
#
|
||||
##total_count = get_record_count()
|
||||
|
||||
#total_count = 75082057
|
||||
total_count = 21925602
|
||||
|
||||
def get_first_record(cursor):
|
||||
query = f"select * from tts_inference_jobs order by id limit 1"
|
||||
cursor.execute(query)
|
||||
result = cursor.fetchall()
|
||||
print(result)
|
||||
return result
|
||||
|
||||
total_deleted = 0
|
||||
|
||||
keep_deleting = True
|
||||
|
||||
while keep_deleting:
|
||||
query = f"DELETE FROM tts_inference_jobs WHERE id < {SAFE_ID} ORDER BY ID ASC LIMIT {LIMIT}"
|
||||
query = f"DELETE FROM tts_inference_jobs WHERE status = 'complete_success' ORDER BY ID ASC LIMIT {LIMIT}"
|
||||
#print(query)
|
||||
|
||||
cursor.execute(query)
|
||||
connection.commit() # NB: Python does not autocommit changes!
|
||||
|
||||
#result = cursor.fetchall()
|
||||
count = cursor.rowcount
|
||||
total_deleted += count
|
||||
|
||||
percent_deleted = (total_deleted / total_count) * 100.0
|
||||
|
||||
print(f'Deleted {count}; total deleted = {total_deleted} ({percent_deleted:.4f})')
|
||||
|
||||
if count < LIMIT - 1:
|
||||
keep_deleting = False
|
||||
|
||||
time.sleep(0.5)
|
||||
|
||||
#record = get_first_record(cursor)
|
||||
#time.sleep(2)
|
||||
|
||||
+100
@@ -0,0 +1,100 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
# FIXME: Needs packages 'toml' and 'mysql-connector-python'. Put in requirements.txt.
|
||||
|
||||
import time
|
||||
import toml
|
||||
import mysql.connector
|
||||
|
||||
DB_SECRETS_FILE = 'db_secrets.toml'
|
||||
|
||||
# TODO: Share db-related setup when more scripts are added.
|
||||
|
||||
def read_database_secrets(secrets_file = DB_SECRETS_FILE):
|
||||
secrets = None
|
||||
with open(secrets_file, "r") as f:
|
||||
secrets = toml.load(f)
|
||||
|
||||
required_keys = ['database', 'hostname', 'port', 'password', 'username']
|
||||
for key in required_keys:
|
||||
if key not in secrets:
|
||||
raise Exception(f"Required key '#{key}' not present")
|
||||
|
||||
return secrets
|
||||
|
||||
def connect_to_mysql():
|
||||
secrets = read_database_secrets()
|
||||
connection = mysql.connector.connect(
|
||||
host = secrets['hostname'],
|
||||
port = secrets['port'],
|
||||
user = secrets['username'],
|
||||
password = secrets['password'],
|
||||
database = secrets['database'],
|
||||
)
|
||||
return connection
|
||||
|
||||
connection = connect_to_mysql()
|
||||
cursor = connection.cursor()
|
||||
|
||||
SAFE_ID = 76000000 # Reasonably completed on 2022-06-24
|
||||
LIMIT = 100000
|
||||
|
||||
#def get_record_count():
|
||||
# query = f"SELECT COUNT(*) FROM tts_inference_jobs"
|
||||
# cursor.execute(query)
|
||||
# result = cursor.fetchall()
|
||||
# print(result)
|
||||
# return result[0]
|
||||
#
|
||||
##total_count = get_record_count()
|
||||
|
||||
#total_count = 75082057
|
||||
total_count = 21925602
|
||||
|
||||
def get_user_tokens(cursor):
|
||||
query = f"""
|
||||
SELECT
|
||||
r.maybe_creator_user_token, r.creator_ip_address
|
||||
FROM
|
||||
tts_results as r
|
||||
LEFT OUTER JOIN
|
||||
users as u
|
||||
ON
|
||||
r.maybe_creator_user_token = u.token
|
||||
WHERE
|
||||
r.raw_inference_text LIKE '%spidermant%'
|
||||
AND r.created_at > ( CURDATE() - INTERVAL 1 HOUR )
|
||||
"""
|
||||
cursor.execute(query)
|
||||
results = cursor.fetchall()
|
||||
|
||||
user_tokens = [row[0] for row in results]
|
||||
user_tokens = [token for token in user_tokens if token is not None]
|
||||
user_tokens = set(user_tokens)
|
||||
|
||||
ip_addresses = [row[1] for row in results]
|
||||
ip_addresses = [ip for ip in ip_addresses if ip is not None]
|
||||
ip_addresses = set(ip_addresses)
|
||||
|
||||
return (user_tokens, ip_addresses)
|
||||
|
||||
(user_tokens, ip_addresses) = get_user_tokens(cursor)
|
||||
|
||||
print(user_tokens)
|
||||
print(len(user_tokens))
|
||||
print(len(ip_addresses))
|
||||
|
||||
with open('101_user_tokens.txt', 'w') as f:
|
||||
for user_token in user_tokens:
|
||||
f.write(user_token)
|
||||
f.write("\n")
|
||||
|
||||
with open('101_ip_addresses.txt', 'w') as f:
|
||||
for ip in ip_addresses:
|
||||
f.write(ip)
|
||||
f.write("\n")
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
mysql-connector-python==8.0.32
|
||||
protobuf==3.20.3
|
||||
toml==0.10.2
|
||||
@@ -1,625 +0,0 @@
|
||||
# ArtCraft Omni API
|
||||
|
||||
> # 📚 Full API reference: **<https://storyteller-docs.netlify.app/>**
|
||||
>
|
||||
> **Every ArtCraft API endpoint is documented there.** This guide covers only the Omni API video
|
||||
> endpoints — for everything else, start at the link above.
|
||||
|
||||
The **Omni API** is the API-key–authenticated surface for ArtCraft generation. It mirrors the
|
||||
in-app "Omni Gen" endpoints but is designed for programmatic use: you authenticate with an API key
|
||||
in the `Authorization` header instead of a browser session cookie.
|
||||
|
||||
This guide covers **video generation from URL inputs** — passing image, video, and audio reference
|
||||
URLs directly. The server downloads each URL, stores it as a media file on your account, and then
|
||||
runs generation against it. You never have to upload files yourself.
|
||||
|
||||
> All examples send **no cookies**. The Omni API is authenticated purely by your API key. If you
|
||||
> send session cookies they are ignored.
|
||||
|
||||
---
|
||||
|
||||
## Base URLs
|
||||
|
||||
| Environment | Base URL |
|
||||
| ------------- | ------------------------------ |
|
||||
| Development | `http://localhost:12345` |
|
||||
| Production | `https://api.storyteller.ai` |
|
||||
|
||||
All paths below are relative to the base URL (e.g. `POST /v1/omni_api/generate/video`).
|
||||
|
||||
The examples in this guide use the **development** base URL. To run against production, swap
|
||||
`http://localhost:12345` for `https://api.storyteller.ai`.
|
||||
|
||||
Note: Our APIs are hosted on "`api.storyteller.ai`", not "`api.getartcraft.com`". This is not a
|
||||
typo. Our Cloudflare CDN URLs are `cdn-2.fakeyou.com`.
|
||||
|
||||
---
|
||||
|
||||
## Getting an API key
|
||||
|
||||
Create and manage API keys from the ArtCraft web app:
|
||||
|
||||
1. Go to **<https://app.getartcraft.com/>**
|
||||
2. Click **Account → Settings → API Keys** (your account must have API keys enabled from our staff)
|
||||
3. Create a key and copy it. The secret is shown **once** — store it somewhere safe.
|
||||
|
||||
> **Don't see the API Keys section?** API access is gated per account. Contact the Artcraft team
|
||||
> to enable it.
|
||||
|
||||
### What an API key looks like
|
||||
|
||||
- Prefix: `artcraft_api_`
|
||||
- Followed by 40 random lowercase Crockford-base32 characters (`0-9 a-z`, excluding `i l o u`)
|
||||
- Total length: **53 characters**, e.g. `artcraft_api_3k7q9w0xv2hs5n8m4d6r1t8y0p2s4f6h8j0k2m4n6`
|
||||
|
||||
Treat the key like a password: never commit it to source control or paste it into logs.
|
||||
|
||||
---
|
||||
|
||||
## Authentication
|
||||
|
||||
Send the secret key in the `Authorization` header. Three forms are accepted:
|
||||
|
||||
```
|
||||
Authorization: Bearer artcraft_api_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
|
||||
Authorization: Key artcraft_api_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
|
||||
Authorization: artcraft_api_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
|
||||
```
|
||||
|
||||
`Bearer` is recommended. **Do not send cookies.**
|
||||
|
||||
---
|
||||
|
||||
## Video generation — `POST /v1/omni_api/generate/video`
|
||||
|
||||
Generates a video and returns an inference job token you can poll for results.
|
||||
|
||||
### Request body
|
||||
|
||||
| Field | Type | Notes |
|
||||
| ---------------------------- | --------------- | ----- |
|
||||
| `model` | string | Generation model, e.g. `seedance_2p0`. **Required.** |
|
||||
| `prompt` | string | Text prompt. |
|
||||
| `duration_seconds` | integer | Length of the output video, e.g. `6`. |
|
||||
| `aspect_ratio` | string | e.g. `wide_sixteen_by_nine`, `tall_nine_by_sixteen`, `square`. |
|
||||
| `idempotency_token` | string (UUID) | **Required.** A fresh UUID per request; prevents accidental duplicates. |
|
||||
| `negative_prompt` | string | Optional. |
|
||||
| `resolution` | string | Optional, e.g. `seven_twenty_p`, `ten_eighty_p`. |
|
||||
| `generate_audio` | boolean | Optional. |
|
||||
| **URL inputs** (this guide) | | |
|
||||
| `start_frame_image_url` | string (URL) | Starting keyframe image. |
|
||||
| `end_frame_image_url` | string (URL) | Ending keyframe image. |
|
||||
| `reference_image_urls` | array of URL | Reference images. |
|
||||
| `reference_video_urls` | array of URL | Reference videos (`mp4`). |
|
||||
| `reference_audio_urls` | array of URL | Reference audio (`wav`, `mp3`, `ogg`, …). |
|
||||
| **Media-token inputs** | | |
|
||||
| `start_frame_image_media_token`, `end_frame_image_media_token`, `reference_image_media_tokens`, `reference_video_media_tokens`, `reference_audio_media_tokens` | token(s) | Use these instead if you've already uploaded media. |
|
||||
|
||||
#### Rules for URL inputs
|
||||
|
||||
- Each URL field and its media-token counterpart are **mutually exclusive**. Sending both
|
||||
`reference_image_urls` and `reference_image_media_tokens`, for example, returns:
|
||||
`Either reference_image_media_tokens or reference_image_urls must be set, not both`.
|
||||
- Every URL must start with `http://` or `https://`, otherwise:
|
||||
`URL must start with http:// or https://, bad URL: <url>`.
|
||||
- Redirects are followed (up to 10), so CDN/redirecting URLs work.
|
||||
- The file type is detected from the downloaded bytes. Images must be a real image
|
||||
(`jpeg`/`png`/`gif`/`webp`), reference videos must be `mp4`, reference audio must be a
|
||||
supported audio type (`wav`, `mp3`, `aac`, `ogg`, `flac`, …).
|
||||
|
||||
### Response
|
||||
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"inference_job_token": "jinf_xxxxxxxxxxxxxxxxxxxxxxxxx",
|
||||
"all_job_tokens": ["jinf_xxxxxxxxxxxxxxxxxxxxxxxxx"]
|
||||
}
|
||||
```
|
||||
|
||||
Use `inference_job_token` to poll for the finished video. `all_job_tokens` lists every job created
|
||||
(batch requests can create more than one).
|
||||
|
||||
---
|
||||
|
||||
## Examples
|
||||
|
||||
The examples reuse these public sample URLs:
|
||||
|
||||
- Images (Wikimedia Commons): an Atlantic puffin, the Matterhorn, a toco toucan
|
||||
- Video: a short sample `mp4`
|
||||
- Audio: a short sample `wav`
|
||||
|
||||
Replace `YOUR_API_KEY` with your `artcraft_api_…` secret, and generate a fresh `idempotency_token`
|
||||
(UUID) for every request.
|
||||
|
||||
---
|
||||
|
||||
### Example 1 — Starting-frame Seedance (`start_frame_image_url`)
|
||||
|
||||
Animate outward from a single starting image.
|
||||
|
||||
#### bash
|
||||
|
||||
```bash
|
||||
curl -s -X POST http://localhost:12345/v1/omni_api/generate/video \
|
||||
-H "Authorization: Bearer YOUR_API_KEY" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"model": "seedance_2p0",
|
||||
"prompt": "An Atlantic puffin stands on a windy cliff edge and turns its head as morning light catches its colorful beak.",
|
||||
"duration_seconds": 6,
|
||||
"aspect_ratio": "wide_sixteen_by_nine",
|
||||
"idempotency_token": "'"$(uuidgen)"'",
|
||||
"start_frame_image_url": "https://commons.wikimedia.org/wiki/Special:FilePath/Puffin_%28Fratercula_arctica%29.jpg?width=900"
|
||||
}'
|
||||
```
|
||||
|
||||
#### python
|
||||
|
||||
```python
|
||||
import uuid
|
||||
import requests
|
||||
|
||||
BASE_URL = "http://localhost:12345" # production: https://api.storyteller.ai
|
||||
API_KEY = "YOUR_API_KEY"
|
||||
|
||||
resp = requests.post(
|
||||
f"{BASE_URL}/v1/omni_api/generate/video",
|
||||
headers={"Authorization": f"Bearer {API_KEY}"},
|
||||
json={
|
||||
"model": "seedance_2p0",
|
||||
"prompt": "An Atlantic puffin stands on a windy cliff edge and turns its head as morning light catches its colorful beak.",
|
||||
"duration_seconds": 6,
|
||||
"aspect_ratio": "wide_sixteen_by_nine",
|
||||
"idempotency_token": str(uuid.uuid4()),
|
||||
"start_frame_image_url": "https://commons.wikimedia.org/wiki/Special:FilePath/Puffin_%28Fratercula_arctica%29.jpg?width=900",
|
||||
},
|
||||
timeout=240,
|
||||
)
|
||||
resp.raise_for_status()
|
||||
print(resp.json())
|
||||
```
|
||||
|
||||
#### javascript
|
||||
|
||||
```javascript
|
||||
import { randomUUID } from "node:crypto";
|
||||
|
||||
const BASE_URL = "http://localhost:12345"; // production: https://api.storyteller.ai
|
||||
const API_KEY = "YOUR_API_KEY";
|
||||
|
||||
const resp = await fetch(`${BASE_URL}/v1/omni_api/generate/video`, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
Authorization: `Bearer ${API_KEY}`,
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({
|
||||
model: "seedance_2p0",
|
||||
prompt:
|
||||
"An Atlantic puffin stands on a windy cliff edge and turns its head as morning light catches its colorful beak.",
|
||||
duration_seconds: 6,
|
||||
aspect_ratio: "wide_sixteen_by_nine",
|
||||
idempotency_token: randomUUID(),
|
||||
start_frame_image_url:
|
||||
"https://commons.wikimedia.org/wiki/Special:FilePath/Puffin_%28Fratercula_arctica%29.jpg?width=900",
|
||||
}),
|
||||
});
|
||||
|
||||
console.log(resp.status, await resp.json());
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Example 2 — Image-reference Seedance (`reference_image_urls`)
|
||||
|
||||
Guide the generation with one or more reference images.
|
||||
|
||||
#### bash
|
||||
|
||||
```bash
|
||||
curl -s -X POST http://localhost:12345/v1/omni_api/generate/video \
|
||||
-H "Authorization: Bearer YOUR_API_KEY" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"model": "seedance_2p0",
|
||||
"prompt": "A whimsical journey: an Atlantic puffin perches on a cliff beneath the snow-capped Matterhorn while a brilliant toco toucan watches from a misty rainforest canopy.",
|
||||
"duration_seconds": 6,
|
||||
"aspect_ratio": "wide_sixteen_by_nine",
|
||||
"idempotency_token": "'"$(uuidgen)"'",
|
||||
"reference_image_urls": [
|
||||
"https://commons.wikimedia.org/wiki/Special:FilePath/Puffin_%28Fratercula_arctica%29.jpg?width=900",
|
||||
"https://commons.wikimedia.org/wiki/Special:FilePath/Matterhorn_from_Domh%C3%BCtte_-_2.jpg?width=900",
|
||||
"https://commons.wikimedia.org/wiki/Special:FilePath/Toco_toucan.jpg?width=900"
|
||||
]
|
||||
}'
|
||||
```
|
||||
|
||||
#### python
|
||||
|
||||
```python
|
||||
import uuid
|
||||
import requests
|
||||
|
||||
BASE_URL = "http://localhost:12345" # production: https://api.storyteller.ai
|
||||
API_KEY = "YOUR_API_KEY"
|
||||
|
||||
resp = requests.post(
|
||||
f"{BASE_URL}/v1/omni_api/generate/video",
|
||||
headers={"Authorization": f"Bearer {API_KEY}"},
|
||||
json={
|
||||
"model": "seedance_2p0",
|
||||
"prompt": "A whimsical journey: an Atlantic puffin perches on a cliff beneath the snow-capped Matterhorn while a brilliant toco toucan watches from a misty rainforest canopy.",
|
||||
"duration_seconds": 6,
|
||||
"aspect_ratio": "wide_sixteen_by_nine",
|
||||
"idempotency_token": str(uuid.uuid4()),
|
||||
"reference_image_urls": [
|
||||
"https://commons.wikimedia.org/wiki/Special:FilePath/Puffin_%28Fratercula_arctica%29.jpg?width=900",
|
||||
"https://commons.wikimedia.org/wiki/Special:FilePath/Matterhorn_from_Domh%C3%BCtte_-_2.jpg?width=900",
|
||||
"https://commons.wikimedia.org/wiki/Special:FilePath/Toco_toucan.jpg?width=900",
|
||||
],
|
||||
},
|
||||
timeout=240,
|
||||
)
|
||||
resp.raise_for_status()
|
||||
print(resp.json())
|
||||
```
|
||||
|
||||
#### javascript
|
||||
|
||||
```javascript
|
||||
import { randomUUID } from "node:crypto";
|
||||
|
||||
const BASE_URL = "http://localhost:12345"; // production: https://api.storyteller.ai
|
||||
const API_KEY = "YOUR_API_KEY";
|
||||
|
||||
const resp = await fetch(`${BASE_URL}/v1/omni_api/generate/video`, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
Authorization: `Bearer ${API_KEY}`,
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({
|
||||
model: "seedance_2p0",
|
||||
prompt:
|
||||
"A whimsical journey: an Atlantic puffin perches on a cliff beneath the snow-capped Matterhorn while a brilliant toco toucan watches from a misty rainforest canopy.",
|
||||
duration_seconds: 6,
|
||||
aspect_ratio: "wide_sixteen_by_nine",
|
||||
idempotency_token: randomUUID(),
|
||||
reference_image_urls: [
|
||||
"https://commons.wikimedia.org/wiki/Special:FilePath/Puffin_%28Fratercula_arctica%29.jpg?width=900",
|
||||
"https://commons.wikimedia.org/wiki/Special:FilePath/Matterhorn_from_Domh%C3%BCtte_-_2.jpg?width=900",
|
||||
"https://commons.wikimedia.org/wiki/Special:FilePath/Toco_toucan.jpg?width=900",
|
||||
],
|
||||
}),
|
||||
});
|
||||
|
||||
console.log(resp.status, await resp.json());
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Example 3 — Image + video reference Seedance (`reference_image_urls` + `reference_video_urls`)
|
||||
|
||||
Combine reference images with a reference video (and, optionally, reference audio). This is the
|
||||
fullest URL-driven request.
|
||||
|
||||
#### bash
|
||||
|
||||
```bash
|
||||
curl -s -X POST http://localhost:12345/v1/omni_api/generate/video \
|
||||
-H "Authorization: Bearer YOUR_API_KEY" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"model": "seedance_2p0",
|
||||
"prompt": "An Atlantic puffin stands on a windy cliff edge; the scene sweeps up to the snow-capped Matterhorn at sunrise before settling on a brilliant toco toucan in a misty rainforest canopy. The camera glides with the motion of the reference video, set to the mood of the reference audio.",
|
||||
"duration_seconds": 6,
|
||||
"aspect_ratio": "wide_sixteen_by_nine",
|
||||
"idempotency_token": "'"$(uuidgen)"'",
|
||||
"reference_image_urls": [
|
||||
"https://commons.wikimedia.org/wiki/Special:FilePath/Puffin_%28Fratercula_arctica%29.jpg?width=900",
|
||||
"https://commons.wikimedia.org/wiki/Special:FilePath/Matterhorn_from_Domh%C3%BCtte_-_2.jpg?width=900",
|
||||
"https://commons.wikimedia.org/wiki/Special:FilePath/Toco_toucan.jpg?width=900"
|
||||
],
|
||||
"reference_video_urls": ["https://download.samplelib.com/mp4/sample-5s.mp4"],
|
||||
"reference_audio_urls": ["https://download.samplelib.com/wav/sample-6s.wav"]
|
||||
}'
|
||||
```
|
||||
|
||||
#### python
|
||||
|
||||
```python
|
||||
import uuid
|
||||
import requests
|
||||
|
||||
BASE_URL = "http://localhost:12345" # production: https://api.storyteller.ai
|
||||
API_KEY = "YOUR_API_KEY"
|
||||
|
||||
resp = requests.post(
|
||||
f"{BASE_URL}/v1/omni_api/generate/video",
|
||||
headers={"Authorization": f"Bearer {API_KEY}"},
|
||||
json={
|
||||
"model": "seedance_2p0",
|
||||
"prompt": "An Atlantic puffin stands on a windy cliff edge; the scene sweeps up to the snow-capped Matterhorn at sunrise before settling on a brilliant toco toucan in a misty rainforest canopy. The camera glides with the motion of the reference video, set to the mood of the reference audio.",
|
||||
"duration_seconds": 6,
|
||||
"aspect_ratio": "wide_sixteen_by_nine",
|
||||
"idempotency_token": str(uuid.uuid4()),
|
||||
"reference_image_urls": [
|
||||
"https://commons.wikimedia.org/wiki/Special:FilePath/Puffin_%28Fratercula_arctica%29.jpg?width=900",
|
||||
"https://commons.wikimedia.org/wiki/Special:FilePath/Matterhorn_from_Domh%C3%BCtte_-_2.jpg?width=900",
|
||||
"https://commons.wikimedia.org/wiki/Special:FilePath/Toco_toucan.jpg?width=900",
|
||||
],
|
||||
"reference_video_urls": ["https://download.samplelib.com/mp4/sample-5s.mp4"],
|
||||
"reference_audio_urls": ["https://download.samplelib.com/wav/sample-6s.wav"],
|
||||
},
|
||||
timeout=240,
|
||||
)
|
||||
resp.raise_for_status()
|
||||
print(resp.json())
|
||||
```
|
||||
|
||||
#### javascript
|
||||
|
||||
```javascript
|
||||
import { randomUUID } from "node:crypto";
|
||||
|
||||
const BASE_URL = "http://localhost:12345"; // production: https://api.storyteller.ai
|
||||
const API_KEY = "YOUR_API_KEY";
|
||||
|
||||
const resp = await fetch(`${BASE_URL}/v1/omni_api/generate/video`, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
Authorization: `Bearer ${API_KEY}`,
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({
|
||||
model: "seedance_2p0",
|
||||
prompt:
|
||||
"An Atlantic puffin stands on a windy cliff edge; the scene sweeps up to the snow-capped Matterhorn at sunrise before settling on a brilliant toco toucan in a misty rainforest canopy. The camera glides with the motion of the reference video, set to the mood of the reference audio.",
|
||||
duration_seconds: 6,
|
||||
aspect_ratio: "wide_sixteen_by_nine",
|
||||
idempotency_token: randomUUID(),
|
||||
reference_image_urls: [
|
||||
"https://commons.wikimedia.org/wiki/Special:FilePath/Puffin_%28Fratercula_arctica%29.jpg?width=900",
|
||||
"https://commons.wikimedia.org/wiki/Special:FilePath/Matterhorn_from_Domh%C3%BCtte_-_2.jpg?width=900",
|
||||
"https://commons.wikimedia.org/wiki/Special:FilePath/Toco_toucan.jpg?width=900",
|
||||
],
|
||||
reference_video_urls: ["https://download.samplelib.com/mp4/sample-5s.mp4"],
|
||||
reference_audio_urls: ["https://download.samplelib.com/wav/sample-6s.wav"],
|
||||
}),
|
||||
});
|
||||
|
||||
console.log(resp.status, await resp.json());
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Checking job status
|
||||
|
||||
Generation is asynchronous. The generate call returns an `inference_job_token`; poll the job-status
|
||||
endpoint until the job reaches a terminal state, then read the result URL.
|
||||
|
||||
### Endpoints
|
||||
|
||||
- **Single job:** `GET /v1/omni_api/job_status/job/{token}`
|
||||
- **Batch (many jobs):** `GET /v1/omni_api/job_status/batch?tokens=jinf_aaa&tokens=jinf_bbb`
|
||||
|
||||
Both require the same `Authorization: Bearer …` header (no cookies).
|
||||
|
||||
### Response shape
|
||||
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"state": {
|
||||
"job_token": "jinf_xxxxxxxxxxxxxxxxxxxxxxxxx",
|
||||
"request": {
|
||||
"inference_category": "video_generation",
|
||||
"maybe_prompt_token": "prompt_xxxxxxxxxxxxxxxxxxxxxxxxx",
|
||||
"maybe_model_type": "seedance_2p0",
|
||||
"maybe_model_token": null
|
||||
},
|
||||
"status": {
|
||||
"status": "pending",
|
||||
"maybe_first_started_at": null,
|
||||
"maybe_failure_category": null,
|
||||
"progress_percentage": 0
|
||||
},
|
||||
"maybe_result": null,
|
||||
"created_at": "2026-06-24T04:06:14Z",
|
||||
"updated_at": "2026-06-24T04:06:14Z"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
- `state.status.status` — the job state. Keep polling while it's `pending`, `started`, or
|
||||
`attempt_failed`. **Terminal** states are `complete_success`, `complete_failure`, `dead`,
|
||||
`cancelled_by_user`, and `cancelled_by_system`.
|
||||
- `state.status.progress_percentage` — an integer 0–100.
|
||||
- `state.maybe_result` — `null` until the job succeeds. On `complete_success` it contains the
|
||||
finished media; the playable/downloadable URL is **`state.maybe_result.media_links.cdn_url`**.
|
||||
For video, `media_links.maybe_video_previews` also carries still/animated preview URLs.
|
||||
|
||||
The batch endpoint returns `{ "success": true, "job_states": [ … ] }` where each entry is the same
|
||||
`state` payload as above.
|
||||
|
||||
### Where the result video / image lives
|
||||
|
||||
When `state.status.status` is `complete_success`, the finished media is under `state.maybe_result`:
|
||||
|
||||
- **`state.maybe_result.media_links.cdn_url`** — the primary, playable/downloadable URL of the
|
||||
finished asset. **This is the field you want.** For a video job it's the `.mp4`; for an image job
|
||||
it's the image file (`.jpg` / `.png` / etc.).
|
||||
- `state.maybe_result.entity_token` — the `m_…` media-file token for the result.
|
||||
- `state.maybe_result.media_links.maybe_video_previews` — **video jobs only**. Contains a `still`
|
||||
(poster `.jpg`) and an `animated` (`.gif`) preview URL, plus `{WIDTH}` thumbnail templates. This
|
||||
is `null` for image jobs.
|
||||
- `state.maybe_result.media_links.maybe_thumbnail_template` — **image jobs only**. A URL template;
|
||||
replace `{WIDTH}` with a pixel width to fetch a resized thumbnail. `null` for video jobs.
|
||||
|
||||
**Example — completed video job:**
|
||||
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"state": {
|
||||
"job_token": "jinf_9fcn2qv4dv3vvbrrxbnmfgen06v",
|
||||
"request": {
|
||||
"inference_category": "video_generation",
|
||||
"maybe_prompt_token": "prompt_q53tp4rh97qh8q159p21tazzt",
|
||||
"maybe_model_type": "seedance_2p0",
|
||||
"maybe_model_token": null
|
||||
},
|
||||
"status": {
|
||||
"status": "complete_success",
|
||||
"maybe_first_started_at": "2026-06-24T04:07:11Z",
|
||||
"maybe_failure_category": null,
|
||||
"progress_percentage": 100
|
||||
},
|
||||
"maybe_result": {
|
||||
"entity_type": "media_file",
|
||||
"entity_token": "m_ss682134tx19yhqs5y0nbnd0bd59a6",
|
||||
"media_links": {
|
||||
"cdn_url": "https://pub-c8a4a5bdbdb048f286b77bdf9f786ff2.r2.dev/media/8/p/n/0/4/8pn04v7q3vfhf60wy173f6awcwkre1qj/artcraft_8pn04v7q3vfhf60wy173f6awcwkre1qj.mp4",
|
||||
"maybe_thumbnail_template": null,
|
||||
"maybe_video_previews": {
|
||||
"still": "https://pub-c8a4a5bdbdb048f286b77bdf9f786ff2.r2.dev/media/8/p/n/0/4/8pn04v7q3vfhf60wy173f6awcwkre1qj/artcraft_8pn04v7q3vfhf60wy173f6awcwkre1qj.mp4-thumb.jpg",
|
||||
"animated": "https://pub-c8a4a5bdbdb048f286b77bdf9f786ff2.r2.dev/media/8/p/n/0/4/8pn04v7q3vfhf60wy173f6awcwkre1qj/artcraft_8pn04v7q3vfhf60wy173f6awcwkre1qj.mp4-thumb.gif",
|
||||
"still_thumbnail_template": "https://pub-c8a4a5bdbdb048f286b77bdf9f786ff2.r2.dev/media/8/p/n/0/4/8pn04v7q3vfhf60wy173f6awcwkre1qj/artcraft_8pn04v7q3vfhf60wy173f6awcwkre1qj.mp4-thumb-{WIDTH}.jpg",
|
||||
"animated_thumbnail_template": "https://pub-c8a4a5bdbdb048f286b77bdf9f786ff2.r2.dev/media/8/p/n/0/4/8pn04v7q3vfhf60wy173f6awcwkre1qj/artcraft_8pn04v7q3vfhf60wy173f6awcwkre1qj.mp4-thumb-{WIDTH}.gif"
|
||||
}
|
||||
},
|
||||
"maybe_successfully_completed_at": "2026-06-24T04:17:30Z"
|
||||
},
|
||||
"created_at": "2026-06-24T04:06:14Z",
|
||||
"updated_at": "2026-06-24T04:17:30Z"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
→ The video is at
|
||||
`https://pub-c8a4a5bdbdb048f286b77bdf9f786ff2.r2.dev/media/8/p/n/0/4/8pn04v7q3vfhf60wy173f6awcwkre1qj/artcraft_8pn04v7q3vfhf60wy173f6awcwkre1qj.mp4`
|
||||
(example, not a live URL).
|
||||
|
||||
**Example — completed image job** (from `POST /v1/omni_api/generate/image`):
|
||||
|
||||
```json
|
||||
{
|
||||
"maybe_result": {
|
||||
"entity_type": "media_file",
|
||||
"entity_token": "m_3kq9w0xv2hs5n8m4d6r1t8y0p2s4f6",
|
||||
"media_links": {
|
||||
"cdn_url": "https://pub-c8a4a5bdbdb048f286b77bdf9f786ff2.r2.dev/media/3/k/q/9/w/3kq9w0xv2hs5n8m4d6r1t8y0p2s4f6/artcraft_3kq9w0xv2hs5n8m4d6r1t8y0p2s4f6.jpg",
|
||||
"maybe_thumbnail_template": "https://pub-c8a4a5bdbdb048f286b77bdf9f786ff2.r2.dev/media/3/k/q/9/w/3kq9w0xv2hs5n8m4d6r1t8y0p2s4f6/artcraft_3kq9w0xv2hs5n8m4d6r1t8y0p2s4f6.jpg-thumb-{WIDTH}.jpg",
|
||||
"maybe_video_previews": null
|
||||
},
|
||||
"maybe_successfully_completed_at": "2026-06-24T04:09:02Z"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
→ The image is at
|
||||
`https://pub-c8a4a5bdbdb048f286b77bdf9f786ff2.r2.dev/media/3/k/q/9/w/3kq9w0xv2hs5n8m4d6r1t8y0p2s4f6/artcraft_3kq9w0xv2hs5n8m4d6r1t8y0p2s4f6.jpg`
|
||||
(example, not a live URL).
|
||||
|
||||
### Poll until complete
|
||||
|
||||
#### bash
|
||||
|
||||
```bash
|
||||
JOB_TOKEN="jinf_xxxxxxxxxxxxxxxxxxxxxxxxx"
|
||||
|
||||
while :; do
|
||||
resp=$(curl -s "http://localhost:12345/v1/omni_api/job_status/job/$JOB_TOKEN" \
|
||||
-H "Authorization: Bearer YOUR_API_KEY")
|
||||
status=$(echo "$resp" | python3 -c "import sys,json;print(json.load(sys.stdin)['state']['status']['status'])")
|
||||
echo "status=$status"
|
||||
case "$status" in
|
||||
complete_success)
|
||||
echo "$resp" | python3 -c "import sys,json;print(json.load(sys.stdin)['state']['maybe_result']['media_links']['cdn_url'])"
|
||||
break ;;
|
||||
complete_failure|dead|cancelled_by_user|cancelled_by_system)
|
||||
echo "job ended without a result: $status"; break ;;
|
||||
esac
|
||||
sleep 5
|
||||
done
|
||||
```
|
||||
|
||||
#### python
|
||||
|
||||
```python
|
||||
import time
|
||||
import requests
|
||||
|
||||
BASE_URL = "http://localhost:12345" # production: https://api.storyteller.ai
|
||||
API_KEY = "YOUR_API_KEY"
|
||||
JOB_TOKEN = "jinf_xxxxxxxxxxxxxxxxxxxxxxxxx"
|
||||
|
||||
TERMINAL_FAILURES = {"complete_failure", "dead", "cancelled_by_user", "cancelled_by_system"}
|
||||
|
||||
while True:
|
||||
resp = requests.get(
|
||||
f"{BASE_URL}/v1/omni_api/job_status/job/{JOB_TOKEN}",
|
||||
headers={"Authorization": f"Bearer {API_KEY}"},
|
||||
timeout=30,
|
||||
)
|
||||
resp.raise_for_status()
|
||||
state = resp.json()["state"]
|
||||
status = state["status"]["status"]
|
||||
print("status:", status, state["status"]["progress_percentage"], "%")
|
||||
|
||||
if status == "complete_success":
|
||||
print("result URL:", state["maybe_result"]["media_links"]["cdn_url"])
|
||||
break
|
||||
if status in TERMINAL_FAILURES:
|
||||
print("job ended without a result:", status)
|
||||
break
|
||||
time.sleep(5)
|
||||
```
|
||||
|
||||
#### javascript
|
||||
|
||||
```javascript
|
||||
const BASE_URL = "http://localhost:12345"; // production: https://api.storyteller.ai
|
||||
const API_KEY = "YOUR_API_KEY";
|
||||
const JOB_TOKEN = "jinf_xxxxxxxxxxxxxxxxxxxxxxxxx";
|
||||
|
||||
const TERMINAL_FAILURES = new Set([
|
||||
"complete_failure", "dead", "cancelled_by_user", "cancelled_by_system",
|
||||
]);
|
||||
const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
|
||||
|
||||
while (true) {
|
||||
const resp = await fetch(`${BASE_URL}/v1/omni_api/job_status/job/${JOB_TOKEN}`, {
|
||||
headers: { Authorization: `Bearer ${API_KEY}` },
|
||||
});
|
||||
const { state } = await resp.json();
|
||||
const status = state.status.status;
|
||||
console.log("status:", status, state.status.progress_percentage, "%");
|
||||
|
||||
if (status === "complete_success") {
|
||||
console.log("result URL:", state.maybe_result.media_links.cdn_url);
|
||||
break;
|
||||
}
|
||||
if (TERMINAL_FAILURES.has(status)) {
|
||||
console.log("job ended without a result:", status);
|
||||
break;
|
||||
}
|
||||
await sleep(5000);
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Notes & troubleshooting
|
||||
|
||||
- **Always send a fresh `idempotency_token`** (UUID). Reusing one is rejected as a duplicate.
|
||||
- **No cookies.** The endpoint authenticates only via the `Authorization` header.
|
||||
- **URL must be `http(s)`** and reachable by the server; redirects are followed.
|
||||
- **Type mismatches** (e.g. a `.webm` passed as a reference video, which requires `mp4`) are
|
||||
rejected with a `400` describing the unpermitted type.
|
||||
- **`401 Unauthorized`** — missing/invalid API key, or the key's owner is banned.
|
||||
- **`402 Payment required`** — insufficient credits/balance.
|
||||
- There is a companion image endpoint, `POST /v1/omni_api/generate/image`, which accepts
|
||||
`image_urls` (mutually exclusive with `image_media_tokens`) following the same rules.
|
||||
@@ -1,86 +0,0 @@
|
||||
Dev Setup
|
||||
=========
|
||||
|
||||
# ArtCraft
|
||||
|
||||
ArtCraft is a Rust / Tauri app.
|
||||
|
||||
To set up the ArtCraft development environment, install the following:
|
||||
|
||||
1. [Install Rust](https://doc.rust-lang.org/cargo/getting-started/installation.html).
|
||||
2. [Install npm](https://nodejs.org/en/download) or [nvm](https://github.com/nvm-sh/nvm). (Node version `v24.13.0` works at time of writing.)
|
||||
3. The frontend's Nx and Vite dependencies are installed locally with npm; a global Nx installation is not required for the combined Unix launcher.
|
||||
4. [Install Tauri CLI](https://v2.tauri.app/reference/cli/). (Version `tauri-cli 2.10.0` works at time of writing.)
|
||||
|
||||
**Mac and Linux Development**
|
||||
|
||||
```bash
|
||||
# Start the frontend and Rust/Tauri together in one terminal
|
||||
./script/artcraft/unix_dev.sh
|
||||
|
||||
# Optionally start the free-port search at another port
|
||||
ARTCRAFT_DEV_PORT=6200 ./script/artcraft/unix_dev.sh
|
||||
```
|
||||
|
||||
Use the combined launcher instead of starting the two legacy dev scripts. It
|
||||
works from any working directory when invoked by its path and installs frontend
|
||||
dependencies if the local Vite installation is missing. Node.js 20+ is required.
|
||||
|
||||
The launcher binds Vite to `127.0.0.1`, starting at port **5193** and trying
|
||||
successive ports until one is available. It leaves existing listeners alone.
|
||||
Only after the real frontend socket is bound does it start Tauri with that exact
|
||||
`devUrl`. HTTP and the HMR websocket share the selected port; Rust communicates
|
||||
through native Tauri IPC and needs no separate HTTP port. Configuration overrides
|
||||
are passed in memory, without changing the checked-in Tauri config.
|
||||
|
||||
The native HTTP bridge normalizes loopback development origins for first-party
|
||||
API requests so an automatically selected port does not break login or media
|
||||
loading. See the [desktop session regression checks](../docs/desktop-session-regression.md)
|
||||
when changing ports, the launcher, or authentication.
|
||||
|
||||
JavaScript, TypeScript, React, and CSS changes use Vite hot reload; compatible
|
||||
React component edits use Fast Refresh. Changes that cannot be hot-replaced
|
||||
reload the page. Rust changes use `cargo tauri dev`'s automatic **rebuild and app
|
||||
restart**, including dependent workspace crates. SQLx metadata and SQLite
|
||||
migrations are also watched. Rust reload is a process restart, so it does not
|
||||
preserve unsaved in-memory app state. See [Tauri's watch behavior](https://v2.tauri.app/develop/#reacting-to-source-code-changes).
|
||||
|
||||
After startup, a Vite configuration restart must reuse the chosen port or report
|
||||
an error; it cannot silently switch to another port while Tauri uses an old URL.
|
||||
Ctrl-C stops this session's frontend, Rust watcher/compiler, and desktop app.
|
||||
If the Rust watcher exits, the launcher closes Vite and propagates the exit code.
|
||||
It never finds or terminates another app by name or port.
|
||||
|
||||
The launcher retains the existing Unix defaults for `VITE_ENVIRONMENT_TYPE`,
|
||||
`SQLX_OFFLINE`, `RUSTFLAGS`, and the Linux WebKit workarounds. Explicit environment
|
||||
overrides are preserved. It uses the normal Artcraft app data and Cargo target
|
||||
directory; port separation does not create an isolated account/data profile, and
|
||||
concurrent Rust builds can wait on Cargo's build lock.
|
||||
|
||||
To verify the launcher without compiling Rust or opening a desktop window:
|
||||
|
||||
```bash
|
||||
node --test frontend/scripts/unix-dev.test.mjs
|
||||
```
|
||||
|
||||
The tests use real Vite listeners and HMR, with a test-owned fake Cargo process
|
||||
tree. They cover occupied ports, simultaneous launches, React edits, shutdown,
|
||||
Rust-process failure, and watcher arguments. To verify native Rust reload, run
|
||||
the launcher, wait for the desktop window, make a Rust source edit, and check for
|
||||
a rebuild/restart in the same terminal. Frontend edits should update the webview
|
||||
without restarting the Rust process.
|
||||
|
||||
**Windows Development**
|
||||
|
||||
```powershell
|
||||
# Run the frontend dev server
|
||||
.\script\artcraft\windows_frontend_dev.ps1
|
||||
|
||||
# Run the Tauri Rust application
|
||||
.\script\artcraft\windows_rust_dev.ps1
|
||||
```
|
||||
|
||||
Backend services and website builds live in the separate `artcraft-services` repository.
|
||||
This repository retains the desktop task database in
|
||||
`_database/sql/artcraft_migrations/` and its SQLite query cache in `.sqlx/`.
|
||||
Use `SQLX_OFFLINE=true cargo check -p artcraft` to check Rust without a database server.
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user