+1
.gitignore
+1
.gitignore
···
···
1
+
/target
+57
CLAUDE.md
+57
CLAUDE.md
···
···
1
+
# Blahg Project Context
2
+
3
+
## Overview
4
+
Blahg is a Rust-based ATProtocol AppView that renders personal blog content. It consumes ATProtocol records to create a blog-like experience by discovering and displaying relevant posts.
5
+
6
+
## Architecture
7
+
- **Core Dependencies**: Uses the atproto ecosystem crates (atproto-identity, atproto-record, atproto-client, atproto-jetstream)
8
+
- **Purpose**: Acts as an AppView to find and store references to ATProtocol records for blog rendering
9
+
- **Language**: Rust
10
+
11
+
## Key Components
12
+
- ATProtocol record discovery and storage
13
+
- Blog post rendering and templating
14
+
- Integration with the ATProtocol ecosystem
15
+
16
+
## Development Guidelines
17
+
- Follow Rust idioms and best practices
18
+
- Ensure proper error handling for network operations
19
+
- Maintain compatibility with the atproto crate ecosystem
20
+
- Use async/await patterns for ATProtocol operations
21
+
22
+
## Visibility
23
+
24
+
Types and methods should have the lowest visibility necessary, defaulting to `private`. If `public` visibility is necessary, attempt to make it public to the crate only. Using completely public visibility should be a last resort.
25
+
26
+
## Error Handling
27
+
28
+
All error strings must use this format:
29
+
30
+
error-blahg-<domain>-<number> <message>: <details>
31
+
32
+
Example errors:
33
+
34
+
* error-blahg-resolve-1 Multiple DIDs resolved for method
35
+
* error-blahg-plc-1 HTTP request failed: https://google.com/ Not Found
36
+
* error-blahg-key-1 Error decoding key: invalid
37
+
38
+
Errors should be represented as enums using the `thiserror` library when possible using `src/errors.rs` as a reference and example.
39
+
40
+
Avoid creating new errors with the `anyhow!(...)` macro.
41
+
42
+
## Testing
43
+
```bash
44
+
cargo test
45
+
```
46
+
47
+
## Linting and Type Checking
48
+
```bash
49
+
cargo clippy
50
+
cargo check
51
+
```
52
+
53
+
## Building
54
+
```bash
55
+
cargo build
56
+
cargo build --release
57
+
```
+5584
Cargo.lock
+5584
Cargo.lock
···
···
1
+
# This file is automatically @generated by Cargo.
2
+
# It is not intended for manual editing.
3
+
version = 4
4
+
5
+
[[package]]
6
+
name = "addr2line"
7
+
version = "0.24.2"
8
+
source = "registry+https://github.com/rust-lang/crates.io-index"
9
+
checksum = "dfbe277e56a376000877090da837660b4427aad530e3028d44e0bffe4f89a1c1"
10
+
dependencies = [
11
+
"gimli",
12
+
]
13
+
14
+
[[package]]
15
+
name = "adler2"
16
+
version = "2.0.1"
17
+
source = "registry+https://github.com/rust-lang/crates.io-index"
18
+
checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa"
19
+
20
+
[[package]]
21
+
name = "aho-corasick"
22
+
version = "1.1.3"
23
+
source = "registry+https://github.com/rust-lang/crates.io-index"
24
+
checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916"
25
+
dependencies = [
26
+
"memchr",
27
+
]
28
+
29
+
[[package]]
30
+
name = "aligned-vec"
31
+
version = "0.6.4"
32
+
source = "registry+https://github.com/rust-lang/crates.io-index"
33
+
checksum = "dc890384c8602f339876ded803c97ad529f3842aba97f6392b3dba0dd171769b"
34
+
dependencies = [
35
+
"equator",
36
+
]
37
+
38
+
[[package]]
39
+
name = "allocator-api2"
40
+
version = "0.2.21"
41
+
source = "registry+https://github.com/rust-lang/crates.io-index"
42
+
checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923"
43
+
44
+
[[package]]
45
+
name = "android-tzdata"
46
+
version = "0.1.1"
47
+
source = "registry+https://github.com/rust-lang/crates.io-index"
48
+
checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0"
49
+
50
+
[[package]]
51
+
name = "android_system_properties"
52
+
version = "0.1.5"
53
+
source = "registry+https://github.com/rust-lang/crates.io-index"
54
+
checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311"
55
+
dependencies = [
56
+
"libc",
57
+
]
58
+
59
+
[[package]]
60
+
name = "anstream"
61
+
version = "0.6.19"
62
+
source = "registry+https://github.com/rust-lang/crates.io-index"
63
+
checksum = "301af1932e46185686725e0fad2f8f2aa7da69dd70bf6ecc44d6b703844a3933"
64
+
dependencies = [
65
+
"anstyle",
66
+
"anstyle-parse",
67
+
"anstyle-query",
68
+
"anstyle-wincon",
69
+
"colorchoice",
70
+
"is_terminal_polyfill",
71
+
"utf8parse",
72
+
]
73
+
74
+
[[package]]
75
+
name = "anstyle"
76
+
version = "1.0.11"
77
+
source = "registry+https://github.com/rust-lang/crates.io-index"
78
+
checksum = "862ed96ca487e809f1c8e5a8447f6ee2cf102f846893800b20cebdf541fc6bbd"
79
+
80
+
[[package]]
81
+
name = "anstyle-parse"
82
+
version = "0.2.7"
83
+
source = "registry+https://github.com/rust-lang/crates.io-index"
84
+
checksum = "4e7644824f0aa2c7b9384579234ef10eb7efb6a0deb83f9630a49594dd9c15c2"
85
+
dependencies = [
86
+
"utf8parse",
87
+
]
88
+
89
+
[[package]]
90
+
name = "anstyle-query"
91
+
version = "1.1.3"
92
+
source = "registry+https://github.com/rust-lang/crates.io-index"
93
+
checksum = "6c8bdeb6047d8983be085bab0ba1472e6dc604e7041dbf6fcd5e71523014fae9"
94
+
dependencies = [
95
+
"windows-sys 0.59.0",
96
+
]
97
+
98
+
[[package]]
99
+
name = "anstyle-wincon"
100
+
version = "3.0.9"
101
+
source = "registry+https://github.com/rust-lang/crates.io-index"
102
+
checksum = "403f75924867bb1033c59fbf0797484329750cfbe3c4325cd33127941fabc882"
103
+
dependencies = [
104
+
"anstyle",
105
+
"once_cell_polyfill",
106
+
"windows-sys 0.59.0",
107
+
]
108
+
109
+
[[package]]
110
+
name = "anyhow"
111
+
version = "1.0.98"
112
+
source = "registry+https://github.com/rust-lang/crates.io-index"
113
+
checksum = "e16d2d3311acee920a9eb8d33b8cbc1787ce4a264e85f964c2404b969bdcd487"
114
+
115
+
[[package]]
116
+
name = "arbitrary"
117
+
version = "1.4.1"
118
+
source = "registry+https://github.com/rust-lang/crates.io-index"
119
+
checksum = "dde20b3d026af13f561bdd0f15edf01fc734f0dafcedbaf42bba506a9517f223"
120
+
121
+
[[package]]
122
+
name = "arg_enum_proc_macro"
123
+
version = "0.3.4"
124
+
source = "registry+https://github.com/rust-lang/crates.io-index"
125
+
checksum = "0ae92a5119aa49cdbcf6b9f893fe4e1d98b04ccbf82ee0584ad948a44a734dea"
126
+
dependencies = [
127
+
"proc-macro2",
128
+
"quote",
129
+
"syn 2.0.104",
130
+
]
131
+
132
+
[[package]]
133
+
name = "arrayvec"
134
+
version = "0.7.6"
135
+
source = "registry+https://github.com/rust-lang/crates.io-index"
136
+
checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50"
137
+
138
+
[[package]]
139
+
name = "async-recursion"
140
+
version = "1.1.1"
141
+
source = "registry+https://github.com/rust-lang/crates.io-index"
142
+
checksum = "3b43422f69d8ff38f95f1b2bb76517c91589a924d1559a0e935d7c8ce0274c11"
143
+
dependencies = [
144
+
"proc-macro2",
145
+
"quote",
146
+
"syn 2.0.104",
147
+
]
148
+
149
+
[[package]]
150
+
name = "async-trait"
151
+
version = "0.1.88"
152
+
source = "registry+https://github.com/rust-lang/crates.io-index"
153
+
checksum = "e539d3fca749fcee5236ab05e93a52867dd549cc157c8cb7f99595f3cedffdb5"
154
+
dependencies = [
155
+
"proc-macro2",
156
+
"quote",
157
+
"syn 2.0.104",
158
+
]
159
+
160
+
[[package]]
161
+
name = "atoi"
162
+
version = "2.0.0"
163
+
source = "registry+https://github.com/rust-lang/crates.io-index"
164
+
checksum = "f28d99ec8bfea296261ca1af174f24225171fea9664ba9003cbebee704810528"
165
+
dependencies = [
166
+
"num-traits",
167
+
]
168
+
169
+
[[package]]
170
+
name = "atomic-waker"
171
+
version = "1.1.2"
172
+
source = "registry+https://github.com/rust-lang/crates.io-index"
173
+
checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0"
174
+
175
+
[[package]]
176
+
name = "atproto-client"
177
+
version = "0.9.3"
178
+
source = "registry+https://github.com/rust-lang/crates.io-index"
179
+
checksum = "39bb72476cc82f1ec957c999366d75fc2de17625e2e2230ac32a145ee3e43431"
180
+
dependencies = [
181
+
"anyhow",
182
+
"atproto-identity",
183
+
"atproto-oauth",
184
+
"atproto-record",
185
+
"bytes",
186
+
"reqwest",
187
+
"reqwest-chain",
188
+
"reqwest-middleware",
189
+
"serde",
190
+
"serde_json",
191
+
"thiserror 2.0.12",
192
+
"tokio",
193
+
"tracing",
194
+
"urlencoding",
195
+
]
196
+
197
+
[[package]]
198
+
name = "atproto-identity"
199
+
version = "0.9.3"
200
+
source = "registry+https://github.com/rust-lang/crates.io-index"
201
+
checksum = "af9c670b1082a66c128be205fdfdd1ccc51529076104677d2914bb8089c0d6bf"
202
+
dependencies = [
203
+
"anyhow",
204
+
"async-trait",
205
+
"axum",
206
+
"ecdsa",
207
+
"elliptic-curve",
208
+
"hickory-resolver",
209
+
"http",
210
+
"k256",
211
+
"lru",
212
+
"multibase",
213
+
"p256",
214
+
"p384",
215
+
"rand 0.8.5",
216
+
"reqwest",
217
+
"serde",
218
+
"serde_ipld_dagcbor",
219
+
"serde_json",
220
+
"thiserror 2.0.12",
221
+
"tokio",
222
+
"tracing",
223
+
]
224
+
225
+
[[package]]
226
+
name = "atproto-jetstream"
227
+
version = "0.9.3"
228
+
source = "registry+https://github.com/rust-lang/crates.io-index"
229
+
checksum = "41a61cbcdb44064d33e69cc38e1ec516cb0d534113339f0ed51f22b8a188f098"
230
+
dependencies = [
231
+
"anyhow",
232
+
"async-trait",
233
+
"atproto-identity",
234
+
"futures",
235
+
"http",
236
+
"serde",
237
+
"serde_json",
238
+
"thiserror 2.0.12",
239
+
"tokio",
240
+
"tokio-util",
241
+
"tokio-websockets",
242
+
"tracing",
243
+
"tracing-subscriber",
244
+
"urlencoding",
245
+
"zstd",
246
+
]
247
+
248
+
[[package]]
249
+
name = "atproto-oauth"
250
+
version = "0.9.3"
251
+
source = "registry+https://github.com/rust-lang/crates.io-index"
252
+
checksum = "20cf08578f77fe77a0bc02a09cfe1a46f709603a70b3467b65368d82461d39ed"
253
+
dependencies = [
254
+
"anyhow",
255
+
"async-trait",
256
+
"atproto-identity",
257
+
"axum",
258
+
"base64",
259
+
"chrono",
260
+
"ecdsa",
261
+
"elliptic-curve",
262
+
"http",
263
+
"k256",
264
+
"lru",
265
+
"multibase",
266
+
"p256",
267
+
"p384",
268
+
"rand 0.8.5",
269
+
"reqwest",
270
+
"reqwest-chain",
271
+
"reqwest-middleware",
272
+
"serde",
273
+
"serde_ipld_dagcbor",
274
+
"serde_json",
275
+
"sha2",
276
+
"thiserror 2.0.12",
277
+
"tokio",
278
+
"tracing",
279
+
"ulid",
280
+
]
281
+
282
+
[[package]]
283
+
name = "atproto-record"
284
+
version = "0.9.3"
285
+
source = "registry+https://github.com/rust-lang/crates.io-index"
286
+
checksum = "97bab917de984b2c5cf8cec9d0dd7c0119217e776b647a99dc631f065e74f61c"
287
+
dependencies = [
288
+
"anyhow",
289
+
"atproto-identity",
290
+
"chrono",
291
+
"ecdsa",
292
+
"k256",
293
+
"multibase",
294
+
"p256",
295
+
"serde",
296
+
"serde_ipld_dagcbor",
297
+
"serde_json",
298
+
"thiserror 2.0.12",
299
+
"tokio",
300
+
"tracing",
301
+
]
302
+
303
+
[[package]]
304
+
name = "autocfg"
305
+
version = "1.5.0"
306
+
source = "registry+https://github.com/rust-lang/crates.io-index"
307
+
checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8"
308
+
309
+
[[package]]
310
+
name = "av1-grain"
311
+
version = "0.2.4"
312
+
source = "registry+https://github.com/rust-lang/crates.io-index"
313
+
checksum = "4f3efb2ca85bc610acfa917b5aaa36f3fcbebed5b3182d7f877b02531c4b80c8"
314
+
dependencies = [
315
+
"anyhow",
316
+
"arrayvec",
317
+
"log",
318
+
"nom",
319
+
"num-rational",
320
+
"v_frame",
321
+
]
322
+
323
+
[[package]]
324
+
name = "avif-serialize"
325
+
version = "0.8.5"
326
+
source = "registry+https://github.com/rust-lang/crates.io-index"
327
+
checksum = "2ea8ef51aced2b9191c08197f55450d830876d9933f8f48a429b354f1d496b42"
328
+
dependencies = [
329
+
"arrayvec",
330
+
]
331
+
332
+
[[package]]
333
+
name = "axum"
334
+
version = "0.8.4"
335
+
source = "registry+https://github.com/rust-lang/crates.io-index"
336
+
checksum = "021e862c184ae977658b36c4500f7feac3221ca5da43e3f25bd04ab6c79a29b5"
337
+
dependencies = [
338
+
"axum-core",
339
+
"axum-macros",
340
+
"bytes",
341
+
"form_urlencoded",
342
+
"futures-util",
343
+
"http",
344
+
"http-body",
345
+
"http-body-util",
346
+
"hyper",
347
+
"hyper-util",
348
+
"itoa",
349
+
"matchit",
350
+
"memchr",
351
+
"mime",
352
+
"percent-encoding",
353
+
"pin-project-lite",
354
+
"rustversion",
355
+
"serde",
356
+
"serde_json",
357
+
"serde_path_to_error",
358
+
"serde_urlencoded",
359
+
"sync_wrapper",
360
+
"tokio",
361
+
"tower",
362
+
"tower-layer",
363
+
"tower-service",
364
+
"tracing",
365
+
]
366
+
367
+
[[package]]
368
+
name = "axum-core"
369
+
version = "0.5.2"
370
+
source = "registry+https://github.com/rust-lang/crates.io-index"
371
+
checksum = "68464cd0412f486726fb3373129ef5d2993f90c34bc2bc1c1e9943b2f4fc7ca6"
372
+
dependencies = [
373
+
"bytes",
374
+
"futures-core",
375
+
"http",
376
+
"http-body",
377
+
"http-body-util",
378
+
"mime",
379
+
"pin-project-lite",
380
+
"rustversion",
381
+
"sync_wrapper",
382
+
"tower-layer",
383
+
"tower-service",
384
+
"tracing",
385
+
]
386
+
387
+
[[package]]
388
+
name = "axum-macros"
389
+
version = "0.5.0"
390
+
source = "registry+https://github.com/rust-lang/crates.io-index"
391
+
checksum = "604fde5e028fea851ce1d8570bbdc034bec850d157f7569d10f347d06808c05c"
392
+
dependencies = [
393
+
"proc-macro2",
394
+
"quote",
395
+
"syn 2.0.104",
396
+
]
397
+
398
+
[[package]]
399
+
name = "axum-template"
400
+
version = "3.0.0"
401
+
source = "registry+https://github.com/rust-lang/crates.io-index"
402
+
checksum = "3df50f7d669bfc3a8c348f08f536fe37e7acfbeded3cfdffd2ad3d76725fc40c"
403
+
dependencies = [
404
+
"axum",
405
+
"minijinja",
406
+
"minijinja-autoreload",
407
+
"serde",
408
+
"thiserror 2.0.12",
409
+
]
410
+
411
+
[[package]]
412
+
name = "backtrace"
413
+
version = "0.3.75"
414
+
source = "registry+https://github.com/rust-lang/crates.io-index"
415
+
checksum = "6806a6321ec58106fea15becdad98371e28d92ccbc7c8f1b3b6dd724fe8f1002"
416
+
dependencies = [
417
+
"addr2line",
418
+
"cfg-if",
419
+
"libc",
420
+
"miniz_oxide",
421
+
"object",
422
+
"rustc-demangle",
423
+
"windows-targets 0.52.6",
424
+
]
425
+
426
+
[[package]]
427
+
name = "base-x"
428
+
version = "0.2.11"
429
+
source = "registry+https://github.com/rust-lang/crates.io-index"
430
+
checksum = "4cbbc9d0964165b47557570cce6c952866c2678457aca742aafc9fb771d30270"
431
+
432
+
[[package]]
433
+
name = "base16ct"
434
+
version = "0.2.0"
435
+
source = "registry+https://github.com/rust-lang/crates.io-index"
436
+
checksum = "4c7f02d4ea65f2c1853089ffd8d2787bdbc63de2f0d29dedbcf8ccdfa0ccd4cf"
437
+
438
+
[[package]]
439
+
name = "base64"
440
+
version = "0.22.1"
441
+
source = "registry+https://github.com/rust-lang/crates.io-index"
442
+
checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6"
443
+
444
+
[[package]]
445
+
name = "base64ct"
446
+
version = "1.8.0"
447
+
source = "registry+https://github.com/rust-lang/crates.io-index"
448
+
checksum = "55248b47b0caf0546f7988906588779981c43bb1bc9d0c44087278f80cdb44ba"
449
+
450
+
[[package]]
451
+
name = "bincode"
452
+
version = "1.3.3"
453
+
source = "registry+https://github.com/rust-lang/crates.io-index"
454
+
checksum = "b1f45e9417d87227c7a56d22e471c6206462cba514c7590c09aff4cf6d1ddcad"
455
+
dependencies = [
456
+
"serde",
457
+
]
458
+
459
+
[[package]]
460
+
name = "bit-set"
461
+
version = "0.5.3"
462
+
source = "registry+https://github.com/rust-lang/crates.io-index"
463
+
checksum = "0700ddab506f33b20a03b13996eccd309a48e5ff77d0d95926aa0210fb4e95f1"
464
+
dependencies = [
465
+
"bit-vec 0.6.3",
466
+
]
467
+
468
+
[[package]]
469
+
name = "bit-vec"
470
+
version = "0.6.3"
471
+
source = "registry+https://github.com/rust-lang/crates.io-index"
472
+
checksum = "349f9b6a179ed607305526ca489b34ad0a41aed5f7980fa90eb03160b69598fb"
473
+
474
+
[[package]]
475
+
name = "bit-vec"
476
+
version = "0.7.0"
477
+
source = "registry+https://github.com/rust-lang/crates.io-index"
478
+
checksum = "d2c54ff287cfc0a34f38a6b832ea1bd8e448a330b3e40a50859e6488bee07f22"
479
+
480
+
[[package]]
481
+
name = "bit_field"
482
+
version = "0.10.2"
483
+
source = "registry+https://github.com/rust-lang/crates.io-index"
484
+
checksum = "dc827186963e592360843fb5ba4b973e145841266c1357f7180c43526f2e5b61"
485
+
486
+
[[package]]
487
+
name = "bitflags"
488
+
version = "1.3.2"
489
+
source = "registry+https://github.com/rust-lang/crates.io-index"
490
+
checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
491
+
492
+
[[package]]
493
+
name = "bitflags"
494
+
version = "2.9.1"
495
+
source = "registry+https://github.com/rust-lang/crates.io-index"
496
+
checksum = "1b8e56985ec62d17e9c1001dc89c88ecd7dc08e47eba5ec7c29c7b5eeecde967"
497
+
dependencies = [
498
+
"serde",
499
+
]
500
+
501
+
[[package]]
502
+
name = "bitstream-io"
503
+
version = "2.6.0"
504
+
source = "registry+https://github.com/rust-lang/crates.io-index"
505
+
checksum = "6099cdc01846bc367c4e7dd630dc5966dccf36b652fae7a74e17b640411a91b2"
506
+
507
+
[[package]]
508
+
name = "blahg"
509
+
version = "0.1.0"
510
+
dependencies = [
511
+
"anyhow",
512
+
"async-trait",
513
+
"atproto-client",
514
+
"atproto-identity",
515
+
"atproto-jetstream",
516
+
"atproto-record",
517
+
"axum",
518
+
"axum-template",
519
+
"base64",
520
+
"bloomfilter",
521
+
"bytes",
522
+
"chrono",
523
+
"comrak",
524
+
"duration-str",
525
+
"ecdsa",
526
+
"elliptic-curve",
527
+
"hickory-resolver",
528
+
"image",
529
+
"k256",
530
+
"lru",
531
+
"minijinja",
532
+
"minijinja-autoreload",
533
+
"minijinja-embed",
534
+
"minio",
535
+
"multibase",
536
+
"p256",
537
+
"reqwest",
538
+
"rust-embed",
539
+
"serde",
540
+
"serde_ipld_dagcbor",
541
+
"serde_json",
542
+
"sha2",
543
+
"slugify",
544
+
"sqlx",
545
+
"tempfile",
546
+
"thiserror 2.0.12",
547
+
"tokio",
548
+
"tokio-util",
549
+
"tower-http 0.5.2",
550
+
"tracing",
551
+
"tracing-subscriber",
552
+
]
553
+
554
+
[[package]]
555
+
name = "block-buffer"
556
+
version = "0.10.4"
557
+
source = "registry+https://github.com/rust-lang/crates.io-index"
558
+
checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71"
559
+
dependencies = [
560
+
"generic-array",
561
+
]
562
+
563
+
[[package]]
564
+
name = "bloomfilter"
565
+
version = "1.0.16"
566
+
source = "registry+https://github.com/rust-lang/crates.io-index"
567
+
checksum = "c541c70a910b485670304fd420f0eab8f7bde68439db6a8d98819c3d2774d7e2"
568
+
dependencies = [
569
+
"bit-vec 0.7.0",
570
+
"getrandom 0.2.16",
571
+
"siphasher",
572
+
]
573
+
574
+
[[package]]
575
+
name = "bon"
576
+
version = "3.6.4"
577
+
source = "registry+https://github.com/rust-lang/crates.io-index"
578
+
checksum = "f61138465baf186c63e8d9b6b613b508cd832cba4ce93cf37ce5f096f91ac1a6"
579
+
dependencies = [
580
+
"bon-macros",
581
+
"rustversion",
582
+
]
583
+
584
+
[[package]]
585
+
name = "bon-macros"
586
+
version = "3.6.4"
587
+
source = "registry+https://github.com/rust-lang/crates.io-index"
588
+
checksum = "40d1dad34aa19bf02295382f08d9bc40651585bd497266831d40ee6296fb49ca"
589
+
dependencies = [
590
+
"darling",
591
+
"ident_case",
592
+
"prettyplease",
593
+
"proc-macro2",
594
+
"quote",
595
+
"rustversion",
596
+
"syn 2.0.104",
597
+
]
598
+
599
+
[[package]]
600
+
name = "built"
601
+
version = "0.7.7"
602
+
source = "registry+https://github.com/rust-lang/crates.io-index"
603
+
checksum = "56ed6191a7e78c36abdb16ab65341eefd73d64d303fffccdbb00d51e4205967b"
604
+
605
+
[[package]]
606
+
name = "bumpalo"
607
+
version = "3.19.0"
608
+
source = "registry+https://github.com/rust-lang/crates.io-index"
609
+
checksum = "46c5e41b57b8bba42a04676d81cb89e9ee8e859a1a66f80a5a72e1cb76b34d43"
610
+
611
+
[[package]]
612
+
name = "bytemuck"
613
+
version = "1.23.1"
614
+
source = "registry+https://github.com/rust-lang/crates.io-index"
615
+
checksum = "5c76a5792e44e4abe34d3abf15636779261d45a7450612059293d1d2cfc63422"
616
+
617
+
[[package]]
618
+
name = "byteorder"
619
+
version = "1.5.0"
620
+
source = "registry+https://github.com/rust-lang/crates.io-index"
621
+
checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b"
622
+
623
+
[[package]]
624
+
name = "byteorder-lite"
625
+
version = "0.1.0"
626
+
source = "registry+https://github.com/rust-lang/crates.io-index"
627
+
checksum = "8f1fe948ff07f4bd06c30984e69f5b4899c516a3ef74f34df92a2df2ab535495"
628
+
629
+
[[package]]
630
+
name = "bytes"
631
+
version = "1.10.1"
632
+
source = "registry+https://github.com/rust-lang/crates.io-index"
633
+
checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a"
634
+
635
+
[[package]]
636
+
name = "caseless"
637
+
version = "0.2.2"
638
+
source = "registry+https://github.com/rust-lang/crates.io-index"
639
+
checksum = "8b6fd507454086c8edfd769ca6ada439193cdb209c7681712ef6275cccbfe5d8"
640
+
dependencies = [
641
+
"unicode-normalization",
642
+
]
643
+
644
+
[[package]]
645
+
name = "cbor4ii"
646
+
version = "0.2.14"
647
+
source = "registry+https://github.com/rust-lang/crates.io-index"
648
+
checksum = "b544cf8c89359205f4f990d0e6f3828db42df85b5dac95d09157a250eb0749c4"
649
+
dependencies = [
650
+
"serde",
651
+
]
652
+
653
+
[[package]]
654
+
name = "cc"
655
+
version = "1.2.29"
656
+
source = "registry+https://github.com/rust-lang/crates.io-index"
657
+
checksum = "5c1599538de2394445747c8cf7935946e3cc27e9625f889d979bfb2aaf569362"
658
+
dependencies = [
659
+
"jobserver",
660
+
"libc",
661
+
"shlex",
662
+
]
663
+
664
+
[[package]]
665
+
name = "cfg-expr"
666
+
version = "0.15.8"
667
+
source = "registry+https://github.com/rust-lang/crates.io-index"
668
+
checksum = "d067ad48b8650848b989a59a86c6c36a995d02d2bf778d45c3c5d57bc2718f02"
669
+
dependencies = [
670
+
"smallvec",
671
+
"target-lexicon",
672
+
]
673
+
674
+
[[package]]
675
+
name = "cfg-if"
676
+
version = "1.0.1"
677
+
source = "registry+https://github.com/rust-lang/crates.io-index"
678
+
checksum = "9555578bc9e57714c812a1f84e4fc5b4d21fcb063490c624de019f7464c91268"
679
+
680
+
[[package]]
681
+
name = "cfg_aliases"
682
+
version = "0.2.1"
683
+
source = "registry+https://github.com/rust-lang/crates.io-index"
684
+
checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724"
685
+
686
+
[[package]]
687
+
name = "chrono"
688
+
version = "0.4.41"
689
+
source = "registry+https://github.com/rust-lang/crates.io-index"
690
+
checksum = "c469d952047f47f91b68d1cba3f10d63c11d73e4636f24f08daf0278abf01c4d"
691
+
dependencies = [
692
+
"android-tzdata",
693
+
"iana-time-zone",
694
+
"js-sys",
695
+
"num-traits",
696
+
"serde",
697
+
"wasm-bindgen",
698
+
"windows-link",
699
+
]
700
+
701
+
[[package]]
702
+
name = "cid"
703
+
version = "0.11.1"
704
+
source = "registry+https://github.com/rust-lang/crates.io-index"
705
+
checksum = "3147d8272e8fa0ccd29ce51194dd98f79ddfb8191ba9e3409884e751798acf3a"
706
+
dependencies = [
707
+
"core2",
708
+
"multibase",
709
+
"multihash",
710
+
"serde",
711
+
"serde_bytes",
712
+
"unsigned-varint",
713
+
]
714
+
715
+
[[package]]
716
+
name = "clap"
717
+
version = "4.5.40"
718
+
source = "registry+https://github.com/rust-lang/crates.io-index"
719
+
checksum = "40b6887a1d8685cebccf115538db5c0efe625ccac9696ad45c409d96566e910f"
720
+
dependencies = [
721
+
"clap_builder",
722
+
"clap_derive",
723
+
]
724
+
725
+
[[package]]
726
+
name = "clap_builder"
727
+
version = "4.5.40"
728
+
source = "registry+https://github.com/rust-lang/crates.io-index"
729
+
checksum = "e0c66c08ce9f0c698cbce5c0279d0bb6ac936d8674174fe48f736533b964f59e"
730
+
dependencies = [
731
+
"anstream",
732
+
"anstyle",
733
+
"clap_lex",
734
+
"strsim",
735
+
"terminal_size",
736
+
]
737
+
738
+
[[package]]
739
+
name = "clap_derive"
740
+
version = "4.5.40"
741
+
source = "registry+https://github.com/rust-lang/crates.io-index"
742
+
checksum = "d2c7947ae4cc3d851207c1adb5b5e260ff0cca11446b1d6d1423788e442257ce"
743
+
dependencies = [
744
+
"heck",
745
+
"proc-macro2",
746
+
"quote",
747
+
"syn 2.0.104",
748
+
]
749
+
750
+
[[package]]
751
+
name = "clap_lex"
752
+
version = "0.7.5"
753
+
source = "registry+https://github.com/rust-lang/crates.io-index"
754
+
checksum = "b94f61472cee1439c0b966b47e3aca9ae07e45d070759512cd390ea2bebc6675"
755
+
756
+
[[package]]
757
+
name = "color_quant"
758
+
version = "1.1.0"
759
+
source = "registry+https://github.com/rust-lang/crates.io-index"
760
+
checksum = "3d7b894f5411737b7867f4827955924d7c254fc9f4d91a6aad6b097804b1018b"
761
+
762
+
[[package]]
763
+
name = "colorchoice"
764
+
version = "1.0.4"
765
+
source = "registry+https://github.com/rust-lang/crates.io-index"
766
+
checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75"
767
+
768
+
[[package]]
769
+
name = "comrak"
770
+
version = "0.39.1"
771
+
source = "registry+https://github.com/rust-lang/crates.io-index"
772
+
checksum = "2fefab951771fc3beeed0773ce66a4f7b706273fc6c4c95b08dd1615744abcf5"
773
+
dependencies = [
774
+
"bon",
775
+
"caseless",
776
+
"clap",
777
+
"entities",
778
+
"memchr",
779
+
"shell-words",
780
+
"slug",
781
+
"syntect",
782
+
"typed-arena",
783
+
"unicode_categories",
784
+
"xdg",
785
+
]
786
+
787
+
[[package]]
788
+
name = "concurrent-queue"
789
+
version = "2.5.0"
790
+
source = "registry+https://github.com/rust-lang/crates.io-index"
791
+
checksum = "4ca0197aee26d1ae37445ee532fefce43251d24cc7c166799f4d46817f1d3973"
792
+
dependencies = [
793
+
"crossbeam-utils",
794
+
]
795
+
796
+
[[package]]
797
+
name = "const-oid"
798
+
version = "0.9.6"
799
+
source = "registry+https://github.com/rust-lang/crates.io-index"
800
+
checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8"
801
+
802
+
[[package]]
803
+
name = "core-foundation"
804
+
version = "0.9.4"
805
+
source = "registry+https://github.com/rust-lang/crates.io-index"
806
+
checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f"
807
+
dependencies = [
808
+
"core-foundation-sys",
809
+
"libc",
810
+
]
811
+
812
+
[[package]]
813
+
name = "core-foundation"
814
+
version = "0.10.1"
815
+
source = "registry+https://github.com/rust-lang/crates.io-index"
816
+
checksum = "b2a6cd9ae233e7f62ba4e9353e81a88df7fc8a5987b8d445b4d90c879bd156f6"
817
+
dependencies = [
818
+
"core-foundation-sys",
819
+
"libc",
820
+
]
821
+
822
+
[[package]]
823
+
name = "core-foundation-sys"
824
+
version = "0.8.7"
825
+
source = "registry+https://github.com/rust-lang/crates.io-index"
826
+
checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b"
827
+
828
+
[[package]]
829
+
name = "core2"
830
+
version = "0.4.0"
831
+
source = "registry+https://github.com/rust-lang/crates.io-index"
832
+
checksum = "b49ba7ef1ad6107f8824dbe97de947cbaac53c44e7f9756a1fba0d37c1eec505"
833
+
dependencies = [
834
+
"memchr",
835
+
]
836
+
837
+
[[package]]
838
+
name = "cpufeatures"
839
+
version = "0.2.17"
840
+
source = "registry+https://github.com/rust-lang/crates.io-index"
841
+
checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280"
842
+
dependencies = [
843
+
"libc",
844
+
]
845
+
846
+
[[package]]
847
+
name = "crc"
848
+
version = "3.3.0"
849
+
source = "registry+https://github.com/rust-lang/crates.io-index"
850
+
checksum = "9710d3b3739c2e349eb44fe848ad0b7c8cb1e42bd87ee49371df2f7acaf3e675"
851
+
dependencies = [
852
+
"crc-catalog",
853
+
]
854
+
855
+
[[package]]
856
+
name = "crc-catalog"
857
+
version = "2.4.0"
858
+
source = "registry+https://github.com/rust-lang/crates.io-index"
859
+
checksum = "19d374276b40fb8bbdee95aef7c7fa6b5316ec764510eb64b8dd0e2ed0d7e7f5"
860
+
861
+
[[package]]
862
+
name = "crc32fast"
863
+
version = "1.4.2"
864
+
source = "registry+https://github.com/rust-lang/crates.io-index"
865
+
checksum = "a97769d94ddab943e4510d138150169a2758b5ef3eb191a9ee688de3e23ef7b3"
866
+
dependencies = [
867
+
"cfg-if",
868
+
]
869
+
870
+
[[package]]
871
+
name = "critical-section"
872
+
version = "1.2.0"
873
+
source = "registry+https://github.com/rust-lang/crates.io-index"
874
+
checksum = "790eea4361631c5e7d22598ecd5723ff611904e3344ce8720784c93e3d83d40b"
875
+
876
+
[[package]]
877
+
name = "crossbeam-channel"
878
+
version = "0.5.15"
879
+
source = "registry+https://github.com/rust-lang/crates.io-index"
880
+
checksum = "82b8f8f868b36967f9606790d1903570de9ceaf870a7bf9fbbd3016d636a2cb2"
881
+
dependencies = [
882
+
"crossbeam-utils",
883
+
]
884
+
885
+
[[package]]
886
+
name = "crossbeam-deque"
887
+
version = "0.8.6"
888
+
source = "registry+https://github.com/rust-lang/crates.io-index"
889
+
checksum = "9dd111b7b7f7d55b72c0a6ae361660ee5853c9af73f70c3c2ef6858b950e2e51"
890
+
dependencies = [
891
+
"crossbeam-epoch",
892
+
"crossbeam-utils",
893
+
]
894
+
895
+
[[package]]
896
+
name = "crossbeam-epoch"
897
+
version = "0.9.18"
898
+
source = "registry+https://github.com/rust-lang/crates.io-index"
899
+
checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e"
900
+
dependencies = [
901
+
"crossbeam-utils",
902
+
]
903
+
904
+
[[package]]
905
+
name = "crossbeam-queue"
906
+
version = "0.3.12"
907
+
source = "registry+https://github.com/rust-lang/crates.io-index"
908
+
checksum = "0f58bbc28f91df819d0aa2a2c00cd19754769c2fad90579b3592b1c9ba7a3115"
909
+
dependencies = [
910
+
"crossbeam-utils",
911
+
]
912
+
913
+
[[package]]
914
+
name = "crossbeam-utils"
915
+
version = "0.8.21"
916
+
source = "registry+https://github.com/rust-lang/crates.io-index"
917
+
checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28"
918
+
919
+
[[package]]
920
+
name = "crunchy"
921
+
version = "0.2.4"
922
+
source = "registry+https://github.com/rust-lang/crates.io-index"
923
+
checksum = "460fbee9c2c2f33933d720630a6a0bac33ba7053db5344fac858d4b8952d77d5"
924
+
925
+
[[package]]
926
+
name = "crypto-bigint"
927
+
version = "0.5.5"
928
+
source = "registry+https://github.com/rust-lang/crates.io-index"
929
+
checksum = "0dc92fb57ca44df6db8059111ab3af99a63d5d0f8375d9972e319a379c6bab76"
930
+
dependencies = [
931
+
"generic-array",
932
+
"rand_core 0.6.4",
933
+
"subtle",
934
+
"zeroize",
935
+
]
936
+
937
+
[[package]]
938
+
name = "crypto-common"
939
+
version = "0.1.6"
940
+
source = "registry+https://github.com/rust-lang/crates.io-index"
941
+
checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3"
942
+
dependencies = [
943
+
"generic-array",
944
+
"typenum",
945
+
]
946
+
947
+
[[package]]
948
+
name = "darling"
949
+
version = "0.20.11"
950
+
source = "registry+https://github.com/rust-lang/crates.io-index"
951
+
checksum = "fc7f46116c46ff9ab3eb1597a45688b6715c6e628b5c133e288e709a29bcb4ee"
952
+
dependencies = [
953
+
"darling_core",
954
+
"darling_macro",
955
+
]
956
+
957
+
[[package]]
958
+
name = "darling_core"
959
+
version = "0.20.11"
960
+
source = "registry+https://github.com/rust-lang/crates.io-index"
961
+
checksum = "0d00b9596d185e565c2207a0b01f8bd1a135483d02d9b7b0a54b11da8d53412e"
962
+
dependencies = [
963
+
"fnv",
964
+
"ident_case",
965
+
"proc-macro2",
966
+
"quote",
967
+
"strsim",
968
+
"syn 2.0.104",
969
+
]
970
+
971
+
[[package]]
972
+
name = "darling_macro"
973
+
version = "0.20.11"
974
+
source = "registry+https://github.com/rust-lang/crates.io-index"
975
+
checksum = "fc34b93ccb385b40dc71c6fceac4b2ad23662c7eeb248cf10d529b7e055b6ead"
976
+
dependencies = [
977
+
"darling_core",
978
+
"quote",
979
+
"syn 2.0.104",
980
+
]
981
+
982
+
[[package]]
983
+
name = "dashmap"
984
+
version = "6.1.0"
985
+
source = "registry+https://github.com/rust-lang/crates.io-index"
986
+
checksum = "5041cc499144891f3790297212f32a74fb938e5136a14943f338ef9e0ae276cf"
987
+
dependencies = [
988
+
"cfg-if",
989
+
"crossbeam-utils",
990
+
"hashbrown 0.14.5",
991
+
"lock_api",
992
+
"once_cell",
993
+
"parking_lot_core",
994
+
]
995
+
996
+
[[package]]
997
+
name = "data-encoding"
998
+
version = "2.9.0"
999
+
source = "registry+https://github.com/rust-lang/crates.io-index"
1000
+
checksum = "2a2330da5de22e8a3cb63252ce2abb30116bf5265e89c0e01bc17015ce30a476"
1001
+
1002
+
[[package]]
1003
+
name = "data-encoding-macro"
1004
+
version = "0.1.18"
1005
+
source = "registry+https://github.com/rust-lang/crates.io-index"
1006
+
checksum = "47ce6c96ea0102f01122a185683611bd5ac8d99e62bc59dd12e6bda344ee673d"
1007
+
dependencies = [
1008
+
"data-encoding",
1009
+
"data-encoding-macro-internal",
1010
+
]
1011
+
1012
+
[[package]]
1013
+
name = "data-encoding-macro-internal"
1014
+
version = "0.1.16"
1015
+
source = "registry+https://github.com/rust-lang/crates.io-index"
1016
+
checksum = "8d162beedaa69905488a8da94f5ac3edb4dd4788b732fadb7bd120b2625c1976"
1017
+
dependencies = [
1018
+
"data-encoding",
1019
+
"syn 2.0.104",
1020
+
]
1021
+
1022
+
[[package]]
1023
+
name = "der"
1024
+
version = "0.7.10"
1025
+
source = "registry+https://github.com/rust-lang/crates.io-index"
1026
+
checksum = "e7c1832837b905bbfb5101e07cc24c8deddf52f93225eee6ead5f4d63d53ddcb"
1027
+
dependencies = [
1028
+
"const-oid",
1029
+
"pem-rfc7468",
1030
+
"zeroize",
1031
+
]
1032
+
1033
+
[[package]]
1034
+
name = "deranged"
1035
+
version = "0.4.0"
1036
+
source = "registry+https://github.com/rust-lang/crates.io-index"
1037
+
checksum = "9c9e6a11ca8224451684bc0d7d5a7adbf8f2fd6887261a1cfc3c0432f9d4068e"
1038
+
dependencies = [
1039
+
"powerfmt",
1040
+
]
1041
+
1042
+
[[package]]
1043
+
name = "derivative"
1044
+
version = "2.2.0"
1045
+
source = "registry+https://github.com/rust-lang/crates.io-index"
1046
+
checksum = "fcc3dd5e9e9c0b295d6e1e4d811fb6f157d5ffd784b8d202fc62eac8035a770b"
1047
+
dependencies = [
1048
+
"proc-macro2",
1049
+
"quote",
1050
+
"syn 1.0.109",
1051
+
]
1052
+
1053
+
[[package]]
1054
+
name = "deunicode"
1055
+
version = "1.6.2"
1056
+
source = "registry+https://github.com/rust-lang/crates.io-index"
1057
+
checksum = "abd57806937c9cc163efc8ea3910e00a62e2aeb0b8119f1793a978088f8f6b04"
1058
+
1059
+
[[package]]
1060
+
name = "digest"
1061
+
version = "0.10.7"
1062
+
source = "registry+https://github.com/rust-lang/crates.io-index"
1063
+
checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292"
1064
+
dependencies = [
1065
+
"block-buffer",
1066
+
"const-oid",
1067
+
"crypto-common",
1068
+
"subtle",
1069
+
]
1070
+
1071
+
[[package]]
1072
+
name = "displaydoc"
1073
+
version = "0.2.5"
1074
+
source = "registry+https://github.com/rust-lang/crates.io-index"
1075
+
checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0"
1076
+
dependencies = [
1077
+
"proc-macro2",
1078
+
"quote",
1079
+
"syn 2.0.104",
1080
+
]
1081
+
1082
+
[[package]]
1083
+
name = "dotenvy"
1084
+
version = "0.15.7"
1085
+
source = "registry+https://github.com/rust-lang/crates.io-index"
1086
+
checksum = "1aaf95b3e5c8f23aa320147307562d361db0ae0d51242340f558153b4eb2439b"
1087
+
1088
+
[[package]]
1089
+
name = "duration-str"
1090
+
version = "0.11.3"
1091
+
source = "registry+https://github.com/rust-lang/crates.io-index"
1092
+
checksum = "f88959de2d447fd3eddcf1909d1f19fe084e27a056a6904203dc5d8b9e771c1e"
1093
+
dependencies = [
1094
+
"chrono",
1095
+
"rust_decimal",
1096
+
"serde",
1097
+
"thiserror 2.0.12",
1098
+
"time",
1099
+
"winnow 0.6.26",
1100
+
]
1101
+
1102
+
[[package]]
1103
+
name = "ecdsa"
1104
+
version = "0.16.9"
1105
+
source = "registry+https://github.com/rust-lang/crates.io-index"
1106
+
checksum = "ee27f32b5c5292967d2d4a9d7f1e0b0aed2c15daded5a60300e4abb9d8020bca"
1107
+
dependencies = [
1108
+
"der",
1109
+
"digest",
1110
+
"elliptic-curve",
1111
+
"rfc6979",
1112
+
"serdect",
1113
+
"signature",
1114
+
"spki",
1115
+
]
1116
+
1117
+
[[package]]
1118
+
name = "either"
1119
+
version = "1.15.0"
1120
+
source = "registry+https://github.com/rust-lang/crates.io-index"
1121
+
checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719"
1122
+
dependencies = [
1123
+
"serde",
1124
+
]
1125
+
1126
+
[[package]]
1127
+
name = "elliptic-curve"
1128
+
version = "0.13.8"
1129
+
source = "registry+https://github.com/rust-lang/crates.io-index"
1130
+
checksum = "b5e6043086bf7973472e0c7dff2142ea0b680d30e18d9cc40f267efbf222bd47"
1131
+
dependencies = [
1132
+
"base16ct",
1133
+
"base64ct",
1134
+
"crypto-bigint",
1135
+
"digest",
1136
+
"ff",
1137
+
"generic-array",
1138
+
"group",
1139
+
"hkdf",
1140
+
"pem-rfc7468",
1141
+
"pkcs8",
1142
+
"rand_core 0.6.4",
1143
+
"sec1",
1144
+
"serde_json",
1145
+
"serdect",
1146
+
"subtle",
1147
+
"zeroize",
1148
+
]
1149
+
1150
+
[[package]]
1151
+
name = "encoding_rs"
1152
+
version = "0.8.35"
1153
+
source = "registry+https://github.com/rust-lang/crates.io-index"
1154
+
checksum = "75030f3c4f45dafd7586dd6780965a8c7e8e285a5ecb86713e63a79c5b2766f3"
1155
+
dependencies = [
1156
+
"cfg-if",
1157
+
]
1158
+
1159
+
[[package]]
1160
+
name = "entities"
1161
+
version = "1.0.1"
1162
+
source = "registry+https://github.com/rust-lang/crates.io-index"
1163
+
checksum = "b5320ae4c3782150d900b79807611a59a99fc9a1d61d686faafc24b93fc8d7ca"
1164
+
1165
+
[[package]]
1166
+
name = "enum-as-inner"
1167
+
version = "0.6.1"
1168
+
source = "registry+https://github.com/rust-lang/crates.io-index"
1169
+
checksum = "a1e6a265c649f3f5979b601d26f1d05ada116434c87741c9493cb56218f76cbc"
1170
+
dependencies = [
1171
+
"heck",
1172
+
"proc-macro2",
1173
+
"quote",
1174
+
"syn 2.0.104",
1175
+
]
1176
+
1177
+
[[package]]
1178
+
name = "env_filter"
1179
+
version = "0.1.3"
1180
+
source = "registry+https://github.com/rust-lang/crates.io-index"
1181
+
checksum = "186e05a59d4c50738528153b83b0b0194d3a29507dfec16eccd4b342903397d0"
1182
+
dependencies = [
1183
+
"log",
1184
+
"regex",
1185
+
]
1186
+
1187
+
[[package]]
1188
+
name = "env_logger"
1189
+
version = "0.11.8"
1190
+
source = "registry+https://github.com/rust-lang/crates.io-index"
1191
+
checksum = "13c863f0904021b108aa8b2f55046443e6b1ebde8fd4a15c399893aae4fa069f"
1192
+
dependencies = [
1193
+
"anstream",
1194
+
"anstyle",
1195
+
"env_filter",
1196
+
"jiff",
1197
+
"log",
1198
+
]
1199
+
1200
+
[[package]]
1201
+
name = "equator"
1202
+
version = "0.4.2"
1203
+
source = "registry+https://github.com/rust-lang/crates.io-index"
1204
+
checksum = "4711b213838dfee0117e3be6ac926007d7f433d7bbe33595975d4190cb07e6fc"
1205
+
dependencies = [
1206
+
"equator-macro",
1207
+
]
1208
+
1209
+
[[package]]
1210
+
name = "equator-macro"
1211
+
version = "0.4.2"
1212
+
source = "registry+https://github.com/rust-lang/crates.io-index"
1213
+
checksum = "44f23cf4b44bfce11a86ace86f8a73ffdec849c9fd00a386a53d278bd9e81fb3"
1214
+
dependencies = [
1215
+
"proc-macro2",
1216
+
"quote",
1217
+
"syn 2.0.104",
1218
+
]
1219
+
1220
+
[[package]]
1221
+
name = "equivalent"
1222
+
version = "1.0.2"
1223
+
source = "registry+https://github.com/rust-lang/crates.io-index"
1224
+
checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f"
1225
+
1226
+
[[package]]
1227
+
name = "errno"
1228
+
version = "0.3.13"
1229
+
source = "registry+https://github.com/rust-lang/crates.io-index"
1230
+
checksum = "778e2ac28f6c47af28e4907f13ffd1e1ddbd400980a9abd7c8df189bf578a5ad"
1231
+
dependencies = [
1232
+
"libc",
1233
+
"windows-sys 0.60.2",
1234
+
]
1235
+
1236
+
[[package]]
1237
+
name = "etcetera"
1238
+
version = "0.8.0"
1239
+
source = "registry+https://github.com/rust-lang/crates.io-index"
1240
+
checksum = "136d1b5283a1ab77bd9257427ffd09d8667ced0570b6f938942bc7568ed5b943"
1241
+
dependencies = [
1242
+
"cfg-if",
1243
+
"home",
1244
+
"windows-sys 0.48.0",
1245
+
]
1246
+
1247
+
[[package]]
1248
+
name = "event-listener"
1249
+
version = "5.4.0"
1250
+
source = "registry+https://github.com/rust-lang/crates.io-index"
1251
+
checksum = "3492acde4c3fc54c845eaab3eed8bd00c7a7d881f78bfc801e43a93dec1331ae"
1252
+
dependencies = [
1253
+
"concurrent-queue",
1254
+
"parking",
1255
+
"pin-project-lite",
1256
+
]
1257
+
1258
+
[[package]]
1259
+
name = "exr"
1260
+
version = "1.73.0"
1261
+
source = "registry+https://github.com/rust-lang/crates.io-index"
1262
+
checksum = "f83197f59927b46c04a183a619b7c29df34e63e63c7869320862268c0ef687e0"
1263
+
dependencies = [
1264
+
"bit_field",
1265
+
"half",
1266
+
"lebe",
1267
+
"miniz_oxide",
1268
+
"rayon-core",
1269
+
"smallvec",
1270
+
"zune-inflate",
1271
+
]
1272
+
1273
+
[[package]]
1274
+
name = "fancy-regex"
1275
+
version = "0.11.0"
1276
+
source = "registry+https://github.com/rust-lang/crates.io-index"
1277
+
checksum = "b95f7c0680e4142284cf8b22c14a476e87d61b004a3a0861872b32ef7ead40a2"
1278
+
dependencies = [
1279
+
"bit-set",
1280
+
"regex",
1281
+
]
1282
+
1283
+
[[package]]
1284
+
name = "fastrand"
1285
+
version = "2.3.0"
1286
+
source = "registry+https://github.com/rust-lang/crates.io-index"
1287
+
checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be"
1288
+
1289
+
[[package]]
1290
+
name = "fdeflate"
1291
+
version = "0.3.7"
1292
+
source = "registry+https://github.com/rust-lang/crates.io-index"
1293
+
checksum = "1e6853b52649d4ac5c0bd02320cddc5ba956bdb407c4b75a2c6b75bf51500f8c"
1294
+
dependencies = [
1295
+
"simd-adler32",
1296
+
]
1297
+
1298
+
[[package]]
1299
+
name = "ff"
1300
+
version = "0.13.1"
1301
+
source = "registry+https://github.com/rust-lang/crates.io-index"
1302
+
checksum = "c0b50bfb653653f9ca9095b427bed08ab8d75a137839d9ad64eb11810d5b6393"
1303
+
dependencies = [
1304
+
"rand_core 0.6.4",
1305
+
"subtle",
1306
+
]
1307
+
1308
+
[[package]]
1309
+
name = "flate2"
1310
+
version = "1.1.2"
1311
+
source = "registry+https://github.com/rust-lang/crates.io-index"
1312
+
checksum = "4a3d7db9596fecd151c5f638c0ee5d5bd487b6e0ea232e5dc96d5250f6f94b1d"
1313
+
dependencies = [
1314
+
"crc32fast",
1315
+
"miniz_oxide",
1316
+
]
1317
+
1318
+
[[package]]
1319
+
name = "flume"
1320
+
version = "0.11.1"
1321
+
source = "registry+https://github.com/rust-lang/crates.io-index"
1322
+
checksum = "da0e4dd2a88388a1f4ccc7c9ce104604dab68d9f408dc34cd45823d5a9069095"
1323
+
dependencies = [
1324
+
"futures-core",
1325
+
"futures-sink",
1326
+
"spin",
1327
+
]
1328
+
1329
+
[[package]]
1330
+
name = "fnv"
1331
+
version = "1.0.7"
1332
+
source = "registry+https://github.com/rust-lang/crates.io-index"
1333
+
checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
1334
+
1335
+
[[package]]
1336
+
name = "foldhash"
1337
+
version = "0.1.5"
1338
+
source = "registry+https://github.com/rust-lang/crates.io-index"
1339
+
checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2"
1340
+
1341
+
[[package]]
1342
+
name = "foreign-types"
1343
+
version = "0.3.2"
1344
+
source = "registry+https://github.com/rust-lang/crates.io-index"
1345
+
checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1"
1346
+
dependencies = [
1347
+
"foreign-types-shared",
1348
+
]
1349
+
1350
+
[[package]]
1351
+
name = "foreign-types-shared"
1352
+
version = "0.1.1"
1353
+
source = "registry+https://github.com/rust-lang/crates.io-index"
1354
+
checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b"
1355
+
1356
+
[[package]]
1357
+
name = "form_urlencoded"
1358
+
version = "1.2.1"
1359
+
source = "registry+https://github.com/rust-lang/crates.io-index"
1360
+
checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456"
1361
+
dependencies = [
1362
+
"percent-encoding",
1363
+
]
1364
+
1365
+
[[package]]
1366
+
name = "fsevent-sys"
1367
+
version = "4.1.0"
1368
+
source = "registry+https://github.com/rust-lang/crates.io-index"
1369
+
checksum = "76ee7a02da4d231650c7cea31349b889be2f45ddb3ef3032d2ec8185f6313fd2"
1370
+
dependencies = [
1371
+
"libc",
1372
+
]
1373
+
1374
+
[[package]]
1375
+
name = "futures"
1376
+
version = "0.3.31"
1377
+
source = "registry+https://github.com/rust-lang/crates.io-index"
1378
+
checksum = "65bc07b1a8bc7c85c5f2e110c476c7389b4554ba72af57d8445ea63a576b0876"
1379
+
dependencies = [
1380
+
"futures-channel",
1381
+
"futures-core",
1382
+
"futures-executor",
1383
+
"futures-io",
1384
+
"futures-sink",
1385
+
"futures-task",
1386
+
"futures-util",
1387
+
]
1388
+
1389
+
[[package]]
1390
+
name = "futures-channel"
1391
+
version = "0.3.31"
1392
+
source = "registry+https://github.com/rust-lang/crates.io-index"
1393
+
checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10"
1394
+
dependencies = [
1395
+
"futures-core",
1396
+
"futures-sink",
1397
+
]
1398
+
1399
+
[[package]]
1400
+
name = "futures-core"
1401
+
version = "0.3.31"
1402
+
source = "registry+https://github.com/rust-lang/crates.io-index"
1403
+
checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e"
1404
+
1405
+
[[package]]
1406
+
name = "futures-executor"
1407
+
version = "0.3.31"
1408
+
source = "registry+https://github.com/rust-lang/crates.io-index"
1409
+
checksum = "1e28d1d997f585e54aebc3f97d39e72338912123a67330d723fdbb564d646c9f"
1410
+
dependencies = [
1411
+
"futures-core",
1412
+
"futures-task",
1413
+
"futures-util",
1414
+
]
1415
+
1416
+
[[package]]
1417
+
name = "futures-intrusive"
1418
+
version = "0.5.0"
1419
+
source = "registry+https://github.com/rust-lang/crates.io-index"
1420
+
checksum = "1d930c203dd0b6ff06e0201a4a2fe9149b43c684fd4420555b26d21b1a02956f"
1421
+
dependencies = [
1422
+
"futures-core",
1423
+
"lock_api",
1424
+
"parking_lot",
1425
+
]
1426
+
1427
+
[[package]]
1428
+
name = "futures-io"
1429
+
version = "0.3.31"
1430
+
source = "registry+https://github.com/rust-lang/crates.io-index"
1431
+
checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6"
1432
+
1433
+
[[package]]
1434
+
name = "futures-macro"
1435
+
version = "0.3.31"
1436
+
source = "registry+https://github.com/rust-lang/crates.io-index"
1437
+
checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650"
1438
+
dependencies = [
1439
+
"proc-macro2",
1440
+
"quote",
1441
+
"syn 2.0.104",
1442
+
]
1443
+
1444
+
[[package]]
1445
+
name = "futures-sink"
1446
+
version = "0.3.31"
1447
+
source = "registry+https://github.com/rust-lang/crates.io-index"
1448
+
checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7"
1449
+
1450
+
[[package]]
1451
+
name = "futures-task"
1452
+
version = "0.3.31"
1453
+
source = "registry+https://github.com/rust-lang/crates.io-index"
1454
+
checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988"
1455
+
1456
+
[[package]]
1457
+
name = "futures-util"
1458
+
version = "0.3.31"
1459
+
source = "registry+https://github.com/rust-lang/crates.io-index"
1460
+
checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81"
1461
+
dependencies = [
1462
+
"futures-channel",
1463
+
"futures-core",
1464
+
"futures-io",
1465
+
"futures-macro",
1466
+
"futures-sink",
1467
+
"futures-task",
1468
+
"memchr",
1469
+
"pin-project-lite",
1470
+
"pin-utils",
1471
+
"slab",
1472
+
]
1473
+
1474
+
[[package]]
1475
+
name = "generator"
1476
+
version = "0.8.5"
1477
+
source = "registry+https://github.com/rust-lang/crates.io-index"
1478
+
checksum = "d18470a76cb7f8ff746cf1f7470914f900252ec36bbc40b569d74b1258446827"
1479
+
dependencies = [
1480
+
"cc",
1481
+
"cfg-if",
1482
+
"libc",
1483
+
"log",
1484
+
"rustversion",
1485
+
"windows",
1486
+
]
1487
+
1488
+
[[package]]
1489
+
name = "generic-array"
1490
+
version = "0.14.7"
1491
+
source = "registry+https://github.com/rust-lang/crates.io-index"
1492
+
checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a"
1493
+
dependencies = [
1494
+
"typenum",
1495
+
"version_check",
1496
+
"zeroize",
1497
+
]
1498
+
1499
+
[[package]]
1500
+
name = "getrandom"
1501
+
version = "0.2.16"
1502
+
source = "registry+https://github.com/rust-lang/crates.io-index"
1503
+
checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592"
1504
+
dependencies = [
1505
+
"cfg-if",
1506
+
"js-sys",
1507
+
"libc",
1508
+
"wasi 0.11.1+wasi-snapshot-preview1",
1509
+
"wasm-bindgen",
1510
+
]
1511
+
1512
+
[[package]]
1513
+
name = "getrandom"
1514
+
version = "0.3.3"
1515
+
source = "registry+https://github.com/rust-lang/crates.io-index"
1516
+
checksum = "26145e563e54f2cadc477553f1ec5ee650b00862f0a58bcd12cbdc5f0ea2d2f4"
1517
+
dependencies = [
1518
+
"cfg-if",
1519
+
"js-sys",
1520
+
"libc",
1521
+
"r-efi",
1522
+
"wasi 0.14.2+wasi-0.2.4",
1523
+
"wasm-bindgen",
1524
+
]
1525
+
1526
+
[[package]]
1527
+
name = "gif"
1528
+
version = "0.13.3"
1529
+
source = "registry+https://github.com/rust-lang/crates.io-index"
1530
+
checksum = "4ae047235e33e2829703574b54fdec96bfbad892062d97fed2f76022287de61b"
1531
+
dependencies = [
1532
+
"color_quant",
1533
+
"weezl",
1534
+
]
1535
+
1536
+
[[package]]
1537
+
name = "gimli"
1538
+
version = "0.31.1"
1539
+
source = "registry+https://github.com/rust-lang/crates.io-index"
1540
+
checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f"
1541
+
1542
+
[[package]]
1543
+
name = "group"
1544
+
version = "0.13.0"
1545
+
source = "registry+https://github.com/rust-lang/crates.io-index"
1546
+
checksum = "f0f9ef7462f7c099f518d754361858f86d8a07af53ba9af0fe635bbccb151a63"
1547
+
dependencies = [
1548
+
"ff",
1549
+
"rand_core 0.6.4",
1550
+
"subtle",
1551
+
]
1552
+
1553
+
[[package]]
1554
+
name = "h2"
1555
+
version = "0.4.11"
1556
+
source = "registry+https://github.com/rust-lang/crates.io-index"
1557
+
checksum = "17da50a276f1e01e0ba6c029e47b7100754904ee8a278f886546e98575380785"
1558
+
dependencies = [
1559
+
"atomic-waker",
1560
+
"bytes",
1561
+
"fnv",
1562
+
"futures-core",
1563
+
"futures-sink",
1564
+
"http",
1565
+
"indexmap",
1566
+
"slab",
1567
+
"tokio",
1568
+
"tokio-util",
1569
+
"tracing",
1570
+
]
1571
+
1572
+
[[package]]
1573
+
name = "half"
1574
+
version = "2.6.0"
1575
+
source = "registry+https://github.com/rust-lang/crates.io-index"
1576
+
checksum = "459196ed295495a68f7d7fe1d84f6c4b7ff0e21fe3017b2f283c6fac3ad803c9"
1577
+
dependencies = [
1578
+
"cfg-if",
1579
+
"crunchy",
1580
+
]
1581
+
1582
+
[[package]]
1583
+
name = "hashbrown"
1584
+
version = "0.14.5"
1585
+
source = "registry+https://github.com/rust-lang/crates.io-index"
1586
+
checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1"
1587
+
1588
+
[[package]]
1589
+
name = "hashbrown"
1590
+
version = "0.15.4"
1591
+
source = "registry+https://github.com/rust-lang/crates.io-index"
1592
+
checksum = "5971ac85611da7067dbfcabef3c70ebb5606018acd9e2a3903a0da507521e0d5"
1593
+
dependencies = [
1594
+
"allocator-api2",
1595
+
"equivalent",
1596
+
"foldhash",
1597
+
]
1598
+
1599
+
[[package]]
1600
+
name = "hashlink"
1601
+
version = "0.10.0"
1602
+
source = "registry+https://github.com/rust-lang/crates.io-index"
1603
+
checksum = "7382cf6263419f2d8df38c55d7da83da5c18aef87fc7a7fc1fb1e344edfe14c1"
1604
+
dependencies = [
1605
+
"hashbrown 0.15.4",
1606
+
]
1607
+
1608
+
[[package]]
1609
+
name = "heck"
1610
+
version = "0.5.0"
1611
+
source = "registry+https://github.com/rust-lang/crates.io-index"
1612
+
checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
1613
+
1614
+
[[package]]
1615
+
name = "hex"
1616
+
version = "0.4.3"
1617
+
source = "registry+https://github.com/rust-lang/crates.io-index"
1618
+
checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70"
1619
+
1620
+
[[package]]
1621
+
name = "hickory-proto"
1622
+
version = "0.25.2"
1623
+
source = "registry+https://github.com/rust-lang/crates.io-index"
1624
+
checksum = "f8a6fe56c0038198998a6f217ca4e7ef3a5e51f46163bd6dd60b5c71ca6c6502"
1625
+
dependencies = [
1626
+
"async-trait",
1627
+
"cfg-if",
1628
+
"data-encoding",
1629
+
"enum-as-inner",
1630
+
"futures-channel",
1631
+
"futures-io",
1632
+
"futures-util",
1633
+
"idna",
1634
+
"ipnet",
1635
+
"once_cell",
1636
+
"rand 0.9.1",
1637
+
"ring",
1638
+
"thiserror 2.0.12",
1639
+
"tinyvec",
1640
+
"tokio",
1641
+
"tracing",
1642
+
"url",
1643
+
]
1644
+
1645
+
[[package]]
1646
+
name = "hickory-resolver"
1647
+
version = "0.25.2"
1648
+
source = "registry+https://github.com/rust-lang/crates.io-index"
1649
+
checksum = "dc62a9a99b0bfb44d2ab95a7208ac952d31060efc16241c87eaf36406fecf87a"
1650
+
dependencies = [
1651
+
"cfg-if",
1652
+
"futures-util",
1653
+
"hickory-proto",
1654
+
"ipconfig",
1655
+
"moka",
1656
+
"once_cell",
1657
+
"parking_lot",
1658
+
"rand 0.9.1",
1659
+
"resolv-conf",
1660
+
"smallvec",
1661
+
"thiserror 2.0.12",
1662
+
"tokio",
1663
+
"tracing",
1664
+
]
1665
+
1666
+
[[package]]
1667
+
name = "hkdf"
1668
+
version = "0.12.4"
1669
+
source = "registry+https://github.com/rust-lang/crates.io-index"
1670
+
checksum = "7b5f8eb2ad728638ea2c7d47a21db23b7b58a72ed6a38256b8a1849f15fbbdf7"
1671
+
dependencies = [
1672
+
"hmac",
1673
+
]
1674
+
1675
+
[[package]]
1676
+
name = "hmac"
1677
+
version = "0.12.1"
1678
+
source = "registry+https://github.com/rust-lang/crates.io-index"
1679
+
checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e"
1680
+
dependencies = [
1681
+
"digest",
1682
+
]
1683
+
1684
+
[[package]]
1685
+
name = "home"
1686
+
version = "0.5.11"
1687
+
source = "registry+https://github.com/rust-lang/crates.io-index"
1688
+
checksum = "589533453244b0995c858700322199b2becb13b627df2851f64a2775d024abcf"
1689
+
dependencies = [
1690
+
"windows-sys 0.59.0",
1691
+
]
1692
+
1693
+
[[package]]
1694
+
name = "http"
1695
+
version = "1.3.1"
1696
+
source = "registry+https://github.com/rust-lang/crates.io-index"
1697
+
checksum = "f4a85d31aea989eead29a3aaf9e1115a180df8282431156e533de47660892565"
1698
+
dependencies = [
1699
+
"bytes",
1700
+
"fnv",
1701
+
"itoa",
1702
+
]
1703
+
1704
+
[[package]]
1705
+
name = "http-body"
1706
+
version = "1.0.1"
1707
+
source = "registry+https://github.com/rust-lang/crates.io-index"
1708
+
checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184"
1709
+
dependencies = [
1710
+
"bytes",
1711
+
"http",
1712
+
]
1713
+
1714
+
[[package]]
1715
+
name = "http-body-util"
1716
+
version = "0.1.3"
1717
+
source = "registry+https://github.com/rust-lang/crates.io-index"
1718
+
checksum = "b021d93e26becf5dc7e1b75b1bed1fd93124b374ceb73f43d4d4eafec896a64a"
1719
+
dependencies = [
1720
+
"bytes",
1721
+
"futures-core",
1722
+
"http",
1723
+
"http-body",
1724
+
"pin-project-lite",
1725
+
]
1726
+
1727
+
[[package]]
1728
+
name = "http-range-header"
1729
+
version = "0.4.2"
1730
+
source = "registry+https://github.com/rust-lang/crates.io-index"
1731
+
checksum = "9171a2ea8a68358193d15dd5d70c1c10a2afc3e7e4c5bc92bc9f025cebd7359c"
1732
+
1733
+
[[package]]
1734
+
name = "httparse"
1735
+
version = "1.10.1"
1736
+
source = "registry+https://github.com/rust-lang/crates.io-index"
1737
+
checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87"
1738
+
1739
+
[[package]]
1740
+
name = "httpdate"
1741
+
version = "1.0.3"
1742
+
source = "registry+https://github.com/rust-lang/crates.io-index"
1743
+
checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9"
1744
+
1745
+
[[package]]
1746
+
name = "hyper"
1747
+
version = "1.6.0"
1748
+
source = "registry+https://github.com/rust-lang/crates.io-index"
1749
+
checksum = "cc2b571658e38e0c01b1fdca3bbbe93c00d3d71693ff2770043f8c29bc7d6f80"
1750
+
dependencies = [
1751
+
"bytes",
1752
+
"futures-channel",
1753
+
"futures-util",
1754
+
"h2",
1755
+
"http",
1756
+
"http-body",
1757
+
"httparse",
1758
+
"httpdate",
1759
+
"itoa",
1760
+
"pin-project-lite",
1761
+
"smallvec",
1762
+
"tokio",
1763
+
"want",
1764
+
]
1765
+
1766
+
[[package]]
1767
+
name = "hyper-rustls"
1768
+
version = "0.27.7"
1769
+
source = "registry+https://github.com/rust-lang/crates.io-index"
1770
+
checksum = "e3c93eb611681b207e1fe55d5a71ecf91572ec8a6705cdb6857f7d8d5242cf58"
1771
+
dependencies = [
1772
+
"http",
1773
+
"hyper",
1774
+
"hyper-util",
1775
+
"rustls",
1776
+
"rustls-pki-types",
1777
+
"tokio",
1778
+
"tokio-rustls",
1779
+
"tower-service",
1780
+
"webpki-roots 1.0.1",
1781
+
]
1782
+
1783
+
[[package]]
1784
+
name = "hyper-tls"
1785
+
version = "0.6.0"
1786
+
source = "registry+https://github.com/rust-lang/crates.io-index"
1787
+
checksum = "70206fc6890eaca9fde8a0bf71caa2ddfc9fe045ac9e5c70df101a7dbde866e0"
1788
+
dependencies = [
1789
+
"bytes",
1790
+
"http-body-util",
1791
+
"hyper",
1792
+
"hyper-util",
1793
+
"native-tls",
1794
+
"tokio",
1795
+
"tokio-native-tls",
1796
+
"tower-service",
1797
+
]
1798
+
1799
+
[[package]]
1800
+
name = "hyper-util"
1801
+
version = "0.1.14"
1802
+
source = "registry+https://github.com/rust-lang/crates.io-index"
1803
+
checksum = "dc2fdfdbff08affe55bb779f33b053aa1fe5dd5b54c257343c17edfa55711bdb"
1804
+
dependencies = [
1805
+
"base64",
1806
+
"bytes",
1807
+
"futures-channel",
1808
+
"futures-core",
1809
+
"futures-util",
1810
+
"http",
1811
+
"http-body",
1812
+
"hyper",
1813
+
"ipnet",
1814
+
"libc",
1815
+
"percent-encoding",
1816
+
"pin-project-lite",
1817
+
"socket2",
1818
+
"system-configuration",
1819
+
"tokio",
1820
+
"tower-service",
1821
+
"tracing",
1822
+
"windows-registry",
1823
+
]
1824
+
1825
+
[[package]]
1826
+
name = "iana-time-zone"
1827
+
version = "0.1.63"
1828
+
source = "registry+https://github.com/rust-lang/crates.io-index"
1829
+
checksum = "b0c919e5debc312ad217002b8048a17b7d83f80703865bbfcfebb0458b0b27d8"
1830
+
dependencies = [
1831
+
"android_system_properties",
1832
+
"core-foundation-sys",
1833
+
"iana-time-zone-haiku",
1834
+
"js-sys",
1835
+
"log",
1836
+
"wasm-bindgen",
1837
+
"windows-core",
1838
+
]
1839
+
1840
+
[[package]]
1841
+
name = "iana-time-zone-haiku"
1842
+
version = "0.1.2"
1843
+
source = "registry+https://github.com/rust-lang/crates.io-index"
1844
+
checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f"
1845
+
dependencies = [
1846
+
"cc",
1847
+
]
1848
+
1849
+
[[package]]
1850
+
name = "icu_collections"
1851
+
version = "2.0.0"
1852
+
source = "registry+https://github.com/rust-lang/crates.io-index"
1853
+
checksum = "200072f5d0e3614556f94a9930d5dc3e0662a652823904c3a75dc3b0af7fee47"
1854
+
dependencies = [
1855
+
"displaydoc",
1856
+
"potential_utf",
1857
+
"yoke",
1858
+
"zerofrom",
1859
+
"zerovec",
1860
+
]
1861
+
1862
+
[[package]]
1863
+
name = "icu_locale_core"
1864
+
version = "2.0.0"
1865
+
source = "registry+https://github.com/rust-lang/crates.io-index"
1866
+
checksum = "0cde2700ccaed3872079a65fb1a78f6c0a36c91570f28755dda67bc8f7d9f00a"
1867
+
dependencies = [
1868
+
"displaydoc",
1869
+
"litemap",
1870
+
"tinystr",
1871
+
"writeable",
1872
+
"zerovec",
1873
+
]
1874
+
1875
+
[[package]]
1876
+
name = "icu_normalizer"
1877
+
version = "2.0.0"
1878
+
source = "registry+https://github.com/rust-lang/crates.io-index"
1879
+
checksum = "436880e8e18df4d7bbc06d58432329d6458cc84531f7ac5f024e93deadb37979"
1880
+
dependencies = [
1881
+
"displaydoc",
1882
+
"icu_collections",
1883
+
"icu_normalizer_data",
1884
+
"icu_properties",
1885
+
"icu_provider",
1886
+
"smallvec",
1887
+
"zerovec",
1888
+
]
1889
+
1890
+
[[package]]
1891
+
name = "icu_normalizer_data"
1892
+
version = "2.0.0"
1893
+
source = "registry+https://github.com/rust-lang/crates.io-index"
1894
+
checksum = "00210d6893afc98edb752b664b8890f0ef174c8adbb8d0be9710fa66fbbf72d3"
1895
+
1896
+
[[package]]
1897
+
name = "icu_properties"
1898
+
version = "2.0.1"
1899
+
source = "registry+https://github.com/rust-lang/crates.io-index"
1900
+
checksum = "016c619c1eeb94efb86809b015c58f479963de65bdb6253345c1a1276f22e32b"
1901
+
dependencies = [
1902
+
"displaydoc",
1903
+
"icu_collections",
1904
+
"icu_locale_core",
1905
+
"icu_properties_data",
1906
+
"icu_provider",
1907
+
"potential_utf",
1908
+
"zerotrie",
1909
+
"zerovec",
1910
+
]
1911
+
1912
+
[[package]]
1913
+
name = "icu_properties_data"
1914
+
version = "2.0.1"
1915
+
source = "registry+https://github.com/rust-lang/crates.io-index"
1916
+
checksum = "298459143998310acd25ffe6810ed544932242d3f07083eee1084d83a71bd632"
1917
+
1918
+
[[package]]
1919
+
name = "icu_provider"
1920
+
version = "2.0.0"
1921
+
source = "registry+https://github.com/rust-lang/crates.io-index"
1922
+
checksum = "03c80da27b5f4187909049ee2d72f276f0d9f99a42c306bd0131ecfe04d8e5af"
1923
+
dependencies = [
1924
+
"displaydoc",
1925
+
"icu_locale_core",
1926
+
"stable_deref_trait",
1927
+
"tinystr",
1928
+
"writeable",
1929
+
"yoke",
1930
+
"zerofrom",
1931
+
"zerotrie",
1932
+
"zerovec",
1933
+
]
1934
+
1935
+
[[package]]
1936
+
name = "ident_case"
1937
+
version = "1.0.1"
1938
+
source = "registry+https://github.com/rust-lang/crates.io-index"
1939
+
checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39"
1940
+
1941
+
[[package]]
1942
+
name = "idna"
1943
+
version = "1.0.3"
1944
+
source = "registry+https://github.com/rust-lang/crates.io-index"
1945
+
checksum = "686f825264d630750a544639377bae737628043f20d38bbc029e8f29ea968a7e"
1946
+
dependencies = [
1947
+
"idna_adapter",
1948
+
"smallvec",
1949
+
"utf8_iter",
1950
+
]
1951
+
1952
+
[[package]]
1953
+
name = "idna_adapter"
1954
+
version = "1.2.1"
1955
+
source = "registry+https://github.com/rust-lang/crates.io-index"
1956
+
checksum = "3acae9609540aa318d1bc588455225fb2085b9ed0c4f6bd0d9d5bcd86f1a0344"
1957
+
dependencies = [
1958
+
"icu_normalizer",
1959
+
"icu_properties",
1960
+
]
1961
+
1962
+
[[package]]
1963
+
name = "image"
1964
+
version = "0.25.6"
1965
+
source = "registry+https://github.com/rust-lang/crates.io-index"
1966
+
checksum = "db35664ce6b9810857a38a906215e75a9c879f0696556a39f59c62829710251a"
1967
+
dependencies = [
1968
+
"bytemuck",
1969
+
"byteorder-lite",
1970
+
"color_quant",
1971
+
"exr",
1972
+
"gif",
1973
+
"image-webp",
1974
+
"num-traits",
1975
+
"png",
1976
+
"qoi",
1977
+
"ravif",
1978
+
"rayon",
1979
+
"rgb",
1980
+
"tiff",
1981
+
"zune-core",
1982
+
"zune-jpeg",
1983
+
]
1984
+
1985
+
[[package]]
1986
+
name = "image-webp"
1987
+
version = "0.2.3"
1988
+
source = "registry+https://github.com/rust-lang/crates.io-index"
1989
+
checksum = "f6970fe7a5300b4b42e62c52efa0187540a5bef546c60edaf554ef595d2e6f0b"
1990
+
dependencies = [
1991
+
"byteorder-lite",
1992
+
"quick-error",
1993
+
]
1994
+
1995
+
[[package]]
1996
+
name = "imgref"
1997
+
version = "1.11.0"
1998
+
source = "registry+https://github.com/rust-lang/crates.io-index"
1999
+
checksum = "d0263a3d970d5c054ed9312c0057b4f3bde9c0b33836d3637361d4a9e6e7a408"
2000
+
2001
+
[[package]]
2002
+
name = "indexmap"
2003
+
version = "2.10.0"
2004
+
source = "registry+https://github.com/rust-lang/crates.io-index"
2005
+
checksum = "fe4cd85333e22411419a0bcae1297d25e58c9443848b11dc6a86fefe8c78a661"
2006
+
dependencies = [
2007
+
"equivalent",
2008
+
"hashbrown 0.15.4",
2009
+
]
2010
+
2011
+
[[package]]
2012
+
name = "inotify"
2013
+
version = "0.11.0"
2014
+
source = "registry+https://github.com/rust-lang/crates.io-index"
2015
+
checksum = "f37dccff2791ab604f9babef0ba14fbe0be30bd368dc541e2b08d07c8aa908f3"
2016
+
dependencies = [
2017
+
"bitflags 2.9.1",
2018
+
"inotify-sys",
2019
+
"libc",
2020
+
]
2021
+
2022
+
[[package]]
2023
+
name = "inotify-sys"
2024
+
version = "0.1.5"
2025
+
source = "registry+https://github.com/rust-lang/crates.io-index"
2026
+
checksum = "e05c02b5e89bff3b946cedeca278abc628fe811e604f027c45a8aa3cf793d0eb"
2027
+
dependencies = [
2028
+
"libc",
2029
+
]
2030
+
2031
+
[[package]]
2032
+
name = "interpolate_name"
2033
+
version = "0.2.4"
2034
+
source = "registry+https://github.com/rust-lang/crates.io-index"
2035
+
checksum = "c34819042dc3d3971c46c2190835914dfbe0c3c13f61449b2997f4e9722dfa60"
2036
+
dependencies = [
2037
+
"proc-macro2",
2038
+
"quote",
2039
+
"syn 2.0.104",
2040
+
]
2041
+
2042
+
[[package]]
2043
+
name = "io-uring"
2044
+
version = "0.7.8"
2045
+
source = "registry+https://github.com/rust-lang/crates.io-index"
2046
+
checksum = "b86e202f00093dcba4275d4636b93ef9dd75d025ae560d2521b45ea28ab49013"
2047
+
dependencies = [
2048
+
"bitflags 2.9.1",
2049
+
"cfg-if",
2050
+
"libc",
2051
+
]
2052
+
2053
+
[[package]]
2054
+
name = "ipconfig"
2055
+
version = "0.3.2"
2056
+
source = "registry+https://github.com/rust-lang/crates.io-index"
2057
+
checksum = "b58db92f96b720de98181bbbe63c831e87005ab460c1bf306eb2622b4707997f"
2058
+
dependencies = [
2059
+
"socket2",
2060
+
"widestring",
2061
+
"windows-sys 0.48.0",
2062
+
"winreg",
2063
+
]
2064
+
2065
+
[[package]]
2066
+
name = "ipld-core"
2067
+
version = "0.4.2"
2068
+
source = "registry+https://github.com/rust-lang/crates.io-index"
2069
+
checksum = "104718b1cc124d92a6d01ca9c9258a7df311405debb3408c445a36452f9bf8db"
2070
+
dependencies = [
2071
+
"cid",
2072
+
"serde",
2073
+
"serde_bytes",
2074
+
]
2075
+
2076
+
[[package]]
2077
+
name = "ipnet"
2078
+
version = "2.11.0"
2079
+
source = "registry+https://github.com/rust-lang/crates.io-index"
2080
+
checksum = "469fb0b9cefa57e3ef31275ee7cacb78f2fdca44e4765491884a2b119d4eb130"
2081
+
2082
+
[[package]]
2083
+
name = "iri-string"
2084
+
version = "0.7.8"
2085
+
source = "registry+https://github.com/rust-lang/crates.io-index"
2086
+
checksum = "dbc5ebe9c3a1a7a5127f920a418f7585e9e758e911d0466ed004f393b0e380b2"
2087
+
dependencies = [
2088
+
"memchr",
2089
+
"serde",
2090
+
]
2091
+
2092
+
[[package]]
2093
+
name = "is_terminal_polyfill"
2094
+
version = "1.70.1"
2095
+
source = "registry+https://github.com/rust-lang/crates.io-index"
2096
+
checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf"
2097
+
2098
+
[[package]]
2099
+
name = "itertools"
2100
+
version = "0.12.1"
2101
+
source = "registry+https://github.com/rust-lang/crates.io-index"
2102
+
checksum = "ba291022dbbd398a455acf126c1e341954079855bc60dfdda641363bd6922569"
2103
+
dependencies = [
2104
+
"either",
2105
+
]
2106
+
2107
+
[[package]]
2108
+
name = "itoa"
2109
+
version = "1.0.15"
2110
+
source = "registry+https://github.com/rust-lang/crates.io-index"
2111
+
checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c"
2112
+
2113
+
[[package]]
2114
+
name = "jiff"
2115
+
version = "0.2.15"
2116
+
source = "registry+https://github.com/rust-lang/crates.io-index"
2117
+
checksum = "be1f93b8b1eb69c77f24bbb0afdf66f54b632ee39af40ca21c4365a1d7347e49"
2118
+
dependencies = [
2119
+
"jiff-static",
2120
+
"log",
2121
+
"portable-atomic",
2122
+
"portable-atomic-util",
2123
+
"serde",
2124
+
]
2125
+
2126
+
[[package]]
2127
+
name = "jiff-static"
2128
+
version = "0.2.15"
2129
+
source = "registry+https://github.com/rust-lang/crates.io-index"
2130
+
checksum = "03343451ff899767262ec32146f6d559dd759fdadf42ff0e227c7c48f72594b4"
2131
+
dependencies = [
2132
+
"proc-macro2",
2133
+
"quote",
2134
+
"syn 2.0.104",
2135
+
]
2136
+
2137
+
[[package]]
2138
+
name = "jobserver"
2139
+
version = "0.1.33"
2140
+
source = "registry+https://github.com/rust-lang/crates.io-index"
2141
+
checksum = "38f262f097c174adebe41eb73d66ae9c06b2844fb0da69969647bbddd9b0538a"
2142
+
dependencies = [
2143
+
"getrandom 0.3.3",
2144
+
"libc",
2145
+
]
2146
+
2147
+
[[package]]
2148
+
name = "jpeg-decoder"
2149
+
version = "0.3.2"
2150
+
source = "registry+https://github.com/rust-lang/crates.io-index"
2151
+
checksum = "00810f1d8b74be64b13dbf3db89ac67740615d6c891f0e7b6179326533011a07"
2152
+
2153
+
[[package]]
2154
+
name = "js-sys"
2155
+
version = "0.3.77"
2156
+
source = "registry+https://github.com/rust-lang/crates.io-index"
2157
+
checksum = "1cfaf33c695fc6e08064efbc1f72ec937429614f25eef83af942d0e227c3a28f"
2158
+
dependencies = [
2159
+
"once_cell",
2160
+
"wasm-bindgen",
2161
+
]
2162
+
2163
+
[[package]]
2164
+
name = "k256"
2165
+
version = "0.13.4"
2166
+
source = "registry+https://github.com/rust-lang/crates.io-index"
2167
+
checksum = "f6e3919bbaa2945715f0bb6d3934a173d1e9a59ac23767fbaaef277265a7411b"
2168
+
dependencies = [
2169
+
"cfg-if",
2170
+
"ecdsa",
2171
+
"elliptic-curve",
2172
+
"once_cell",
2173
+
"sha2",
2174
+
"signature",
2175
+
]
2176
+
2177
+
[[package]]
2178
+
name = "kqueue"
2179
+
version = "1.1.1"
2180
+
source = "registry+https://github.com/rust-lang/crates.io-index"
2181
+
checksum = "eac30106d7dce88daf4a3fcb4879ea939476d5074a9b7ddd0fb97fa4bed5596a"
2182
+
dependencies = [
2183
+
"kqueue-sys",
2184
+
"libc",
2185
+
]
2186
+
2187
+
[[package]]
2188
+
name = "kqueue-sys"
2189
+
version = "1.0.4"
2190
+
source = "registry+https://github.com/rust-lang/crates.io-index"
2191
+
checksum = "ed9625ffda8729b85e45cf04090035ac368927b8cebc34898e7c120f52e4838b"
2192
+
dependencies = [
2193
+
"bitflags 1.3.2",
2194
+
"libc",
2195
+
]
2196
+
2197
+
[[package]]
2198
+
name = "lazy_static"
2199
+
version = "1.5.0"
2200
+
source = "registry+https://github.com/rust-lang/crates.io-index"
2201
+
checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe"
2202
+
dependencies = [
2203
+
"spin",
2204
+
]
2205
+
2206
+
[[package]]
2207
+
name = "lebe"
2208
+
version = "0.5.2"
2209
+
source = "registry+https://github.com/rust-lang/crates.io-index"
2210
+
checksum = "03087c2bad5e1034e8cace5926dec053fb3790248370865f5117a7d0213354c8"
2211
+
2212
+
[[package]]
2213
+
name = "libc"
2214
+
version = "0.2.174"
2215
+
source = "registry+https://github.com/rust-lang/crates.io-index"
2216
+
checksum = "1171693293099992e19cddea4e8b849964e9846f4acee11b3948bcc337be8776"
2217
+
2218
+
[[package]]
2219
+
name = "libfuzzer-sys"
2220
+
version = "0.4.10"
2221
+
source = "registry+https://github.com/rust-lang/crates.io-index"
2222
+
checksum = "5037190e1f70cbeef565bd267599242926f724d3b8a9f510fd7e0b540cfa4404"
2223
+
dependencies = [
2224
+
"arbitrary",
2225
+
"cc",
2226
+
]
2227
+
2228
+
[[package]]
2229
+
name = "libm"
2230
+
version = "0.2.15"
2231
+
source = "registry+https://github.com/rust-lang/crates.io-index"
2232
+
checksum = "f9fbbcab51052fe104eb5e5d351cf728d30a5be1fe14d9be8a3b097481fb97de"
2233
+
2234
+
[[package]]
2235
+
name = "libsqlite3-sys"
2236
+
version = "0.30.1"
2237
+
source = "registry+https://github.com/rust-lang/crates.io-index"
2238
+
checksum = "2e99fb7a497b1e3339bc746195567ed8d3e24945ecd636e3619d20b9de9e9149"
2239
+
dependencies = [
2240
+
"cc",
2241
+
"pkg-config",
2242
+
"vcpkg",
2243
+
]
2244
+
2245
+
[[package]]
2246
+
name = "linked-hash-map"
2247
+
version = "0.5.6"
2248
+
source = "registry+https://github.com/rust-lang/crates.io-index"
2249
+
checksum = "0717cef1bc8b636c6e1c1bbdefc09e6322da8a9321966e8928ef80d20f7f770f"
2250
+
2251
+
[[package]]
2252
+
name = "linux-raw-sys"
2253
+
version = "0.9.4"
2254
+
source = "registry+https://github.com/rust-lang/crates.io-index"
2255
+
checksum = "cd945864f07fe9f5371a27ad7b52a172b4b499999f1d97574c9fa68373937e12"
2256
+
2257
+
[[package]]
2258
+
name = "litemap"
2259
+
version = "0.8.0"
2260
+
source = "registry+https://github.com/rust-lang/crates.io-index"
2261
+
checksum = "241eaef5fd12c88705a01fc1066c48c4b36e0dd4377dcdc7ec3942cea7a69956"
2262
+
2263
+
[[package]]
2264
+
name = "lock_api"
2265
+
version = "0.4.13"
2266
+
source = "registry+https://github.com/rust-lang/crates.io-index"
2267
+
checksum = "96936507f153605bddfcda068dd804796c84324ed2510809e5b2a624c81da765"
2268
+
dependencies = [
2269
+
"autocfg",
2270
+
"scopeguard",
2271
+
]
2272
+
2273
+
[[package]]
2274
+
name = "log"
2275
+
version = "0.4.27"
2276
+
source = "registry+https://github.com/rust-lang/crates.io-index"
2277
+
checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94"
2278
+
2279
+
[[package]]
2280
+
name = "loom"
2281
+
version = "0.7.2"
2282
+
source = "registry+https://github.com/rust-lang/crates.io-index"
2283
+
checksum = "419e0dc8046cb947daa77eb95ae174acfbddb7673b4151f56d1eed8e93fbfaca"
2284
+
dependencies = [
2285
+
"cfg-if",
2286
+
"generator",
2287
+
"scoped-tls",
2288
+
"tracing",
2289
+
"tracing-subscriber",
2290
+
]
2291
+
2292
+
[[package]]
2293
+
name = "loop9"
2294
+
version = "0.1.5"
2295
+
source = "registry+https://github.com/rust-lang/crates.io-index"
2296
+
checksum = "0fae87c125b03c1d2c0150c90365d7d6bcc53fb73a9acaef207d2d065860f062"
2297
+
dependencies = [
2298
+
"imgref",
2299
+
]
2300
+
2301
+
[[package]]
2302
+
name = "lru"
2303
+
version = "0.12.5"
2304
+
source = "registry+https://github.com/rust-lang/crates.io-index"
2305
+
checksum = "234cf4f4a04dc1f57e24b96cc0cd600cf2af460d4161ac5ecdd0af8e1f3b2a38"
2306
+
dependencies = [
2307
+
"hashbrown 0.15.4",
2308
+
]
2309
+
2310
+
[[package]]
2311
+
name = "lru-slab"
2312
+
version = "0.1.2"
2313
+
source = "registry+https://github.com/rust-lang/crates.io-index"
2314
+
checksum = "112b39cec0b298b6c1999fee3e31427f74f676e4cb9879ed1a121b43661a4154"
2315
+
2316
+
[[package]]
2317
+
name = "matchers"
2318
+
version = "0.1.0"
2319
+
source = "registry+https://github.com/rust-lang/crates.io-index"
2320
+
checksum = "8263075bb86c5a1b1427b5ae862e8889656f126e9f77c484496e8b47cf5c5558"
2321
+
dependencies = [
2322
+
"regex-automata 0.1.10",
2323
+
]
2324
+
2325
+
[[package]]
2326
+
name = "matchit"
2327
+
version = "0.8.4"
2328
+
source = "registry+https://github.com/rust-lang/crates.io-index"
2329
+
checksum = "47e1ffaa40ddd1f3ed91f717a33c8c0ee23fff369e3aa8772b9605cc1d22f4c3"
2330
+
2331
+
[[package]]
2332
+
name = "maybe-rayon"
2333
+
version = "0.1.1"
2334
+
source = "registry+https://github.com/rust-lang/crates.io-index"
2335
+
checksum = "8ea1f30cedd69f0a2954655f7188c6a834246d2bcf1e315e2ac40c4b24dc9519"
2336
+
dependencies = [
2337
+
"cfg-if",
2338
+
"rayon",
2339
+
]
2340
+
2341
+
[[package]]
2342
+
name = "md-5"
2343
+
version = "0.10.6"
2344
+
source = "registry+https://github.com/rust-lang/crates.io-index"
2345
+
checksum = "d89e7ee0cfbedfc4da3340218492196241d89eefb6dab27de5df917a6d2e78cf"
2346
+
dependencies = [
2347
+
"cfg-if",
2348
+
"digest",
2349
+
]
2350
+
2351
+
[[package]]
2352
+
name = "md5"
2353
+
version = "0.7.0"
2354
+
source = "registry+https://github.com/rust-lang/crates.io-index"
2355
+
checksum = "490cc448043f947bae3cbee9c203358d62dbee0db12107a74be5c30ccfd09771"
2356
+
2357
+
[[package]]
2358
+
name = "memchr"
2359
+
version = "2.7.5"
2360
+
source = "registry+https://github.com/rust-lang/crates.io-index"
2361
+
checksum = "32a282da65faaf38286cf3be983213fcf1d2e2a58700e808f83f4ea9a4804bc0"
2362
+
2363
+
[[package]]
2364
+
name = "memo-map"
2365
+
version = "0.3.3"
2366
+
source = "registry+https://github.com/rust-lang/crates.io-index"
2367
+
checksum = "38d1115007560874e373613744c6fba374c17688327a71c1476d1a5954cc857b"
2368
+
2369
+
[[package]]
2370
+
name = "mime"
2371
+
version = "0.3.17"
2372
+
source = "registry+https://github.com/rust-lang/crates.io-index"
2373
+
checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a"
2374
+
2375
+
[[package]]
2376
+
name = "mime_guess"
2377
+
version = "2.0.5"
2378
+
source = "registry+https://github.com/rust-lang/crates.io-index"
2379
+
checksum = "f7c44f8e672c00fe5308fa235f821cb4198414e1c77935c1ab6948d3fd78550e"
2380
+
dependencies = [
2381
+
"mime",
2382
+
"unicase",
2383
+
]
2384
+
2385
+
[[package]]
2386
+
name = "minijinja"
2387
+
version = "2.11.0"
2388
+
source = "registry+https://github.com/rust-lang/crates.io-index"
2389
+
checksum = "4e60ac08614cc09062820e51d5d94c2fce16b94ea4e5003bb81b99a95f84e876"
2390
+
dependencies = [
2391
+
"memo-map",
2392
+
"self_cell",
2393
+
"serde",
2394
+
]
2395
+
2396
+
[[package]]
2397
+
name = "minijinja-autoreload"
2398
+
version = "2.11.0"
2399
+
source = "registry+https://github.com/rust-lang/crates.io-index"
2400
+
checksum = "15af8645fdf8689fdd932492efbb48d4478059f4eae9f476c93058e98a149ce3"
2401
+
dependencies = [
2402
+
"minijinja",
2403
+
"notify",
2404
+
]
2405
+
2406
+
[[package]]
2407
+
name = "minijinja-embed"
2408
+
version = "2.11.0"
2409
+
source = "registry+https://github.com/rust-lang/crates.io-index"
2410
+
checksum = "c34af97e83bb3b2e61428e7c35224c9c1e1f6a2f465aa2ace41a713e3e856468"
2411
+
2412
+
[[package]]
2413
+
name = "minimal-lexical"
2414
+
version = "0.2.1"
2415
+
source = "registry+https://github.com/rust-lang/crates.io-index"
2416
+
checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a"
2417
+
2418
+
[[package]]
2419
+
name = "minio"
2420
+
version = "0.3.0"
2421
+
source = "registry+https://github.com/rust-lang/crates.io-index"
2422
+
checksum = "3824101357fa899d01c729e4a245776e20a03f2f6645979e86b9d3d5d9c42741"
2423
+
dependencies = [
2424
+
"async-recursion",
2425
+
"async-trait",
2426
+
"base64",
2427
+
"byteorder",
2428
+
"bytes",
2429
+
"chrono",
2430
+
"crc",
2431
+
"dashmap",
2432
+
"derivative",
2433
+
"env_logger",
2434
+
"futures",
2435
+
"futures-util",
2436
+
"hex",
2437
+
"hmac",
2438
+
"http",
2439
+
"hyper",
2440
+
"lazy_static",
2441
+
"log",
2442
+
"md5",
2443
+
"multimap",
2444
+
"percent-encoding",
2445
+
"rand 0.8.5",
2446
+
"regex",
2447
+
"reqwest",
2448
+
"serde",
2449
+
"serde_json",
2450
+
"sha2",
2451
+
"tokio",
2452
+
"tokio-stream",
2453
+
"tokio-util",
2454
+
"urlencoding",
2455
+
"xmltree",
2456
+
]
2457
+
2458
+
[[package]]
2459
+
name = "miniz_oxide"
2460
+
version = "0.8.9"
2461
+
source = "registry+https://github.com/rust-lang/crates.io-index"
2462
+
checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316"
2463
+
dependencies = [
2464
+
"adler2",
2465
+
"simd-adler32",
2466
+
]
2467
+
2468
+
[[package]]
2469
+
name = "mio"
2470
+
version = "1.0.4"
2471
+
source = "registry+https://github.com/rust-lang/crates.io-index"
2472
+
checksum = "78bed444cc8a2160f01cbcf811ef18cac863ad68ae8ca62092e8db51d51c761c"
2473
+
dependencies = [
2474
+
"libc",
2475
+
"log",
2476
+
"wasi 0.11.1+wasi-snapshot-preview1",
2477
+
"windows-sys 0.59.0",
2478
+
]
2479
+
2480
+
[[package]]
2481
+
name = "moka"
2482
+
version = "0.12.10"
2483
+
source = "registry+https://github.com/rust-lang/crates.io-index"
2484
+
checksum = "a9321642ca94a4282428e6ea4af8cc2ca4eac48ac7a6a4ea8f33f76d0ce70926"
2485
+
dependencies = [
2486
+
"crossbeam-channel",
2487
+
"crossbeam-epoch",
2488
+
"crossbeam-utils",
2489
+
"loom",
2490
+
"parking_lot",
2491
+
"portable-atomic",
2492
+
"rustc_version",
2493
+
"smallvec",
2494
+
"tagptr",
2495
+
"thiserror 1.0.69",
2496
+
"uuid",
2497
+
]
2498
+
2499
+
[[package]]
2500
+
name = "multibase"
2501
+
version = "0.9.1"
2502
+
source = "registry+https://github.com/rust-lang/crates.io-index"
2503
+
checksum = "9b3539ec3c1f04ac9748a260728e855f261b4977f5c3406612c884564f329404"
2504
+
dependencies = [
2505
+
"base-x",
2506
+
"data-encoding",
2507
+
"data-encoding-macro",
2508
+
]
2509
+
2510
+
[[package]]
2511
+
name = "multihash"
2512
+
version = "0.19.3"
2513
+
source = "registry+https://github.com/rust-lang/crates.io-index"
2514
+
checksum = "6b430e7953c29dd6a09afc29ff0bb69c6e306329ee6794700aee27b76a1aea8d"
2515
+
dependencies = [
2516
+
"core2",
2517
+
"serde",
2518
+
"unsigned-varint",
2519
+
]
2520
+
2521
+
[[package]]
2522
+
name = "multimap"
2523
+
version = "0.10.1"
2524
+
source = "registry+https://github.com/rust-lang/crates.io-index"
2525
+
checksum = "1d87ecb2933e8aeadb3e3a02b828fed80a7528047e68b4f424523a0981a3a084"
2526
+
dependencies = [
2527
+
"serde",
2528
+
]
2529
+
2530
+
[[package]]
2531
+
name = "native-tls"
2532
+
version = "0.2.14"
2533
+
source = "registry+https://github.com/rust-lang/crates.io-index"
2534
+
checksum = "87de3442987e9dbec73158d5c715e7ad9072fda936bb03d19d7fa10e00520f0e"
2535
+
dependencies = [
2536
+
"libc",
2537
+
"log",
2538
+
"openssl",
2539
+
"openssl-probe",
2540
+
"openssl-sys",
2541
+
"schannel",
2542
+
"security-framework 2.11.1",
2543
+
"security-framework-sys",
2544
+
"tempfile",
2545
+
]
2546
+
2547
+
[[package]]
2548
+
name = "new_debug_unreachable"
2549
+
version = "1.0.6"
2550
+
source = "registry+https://github.com/rust-lang/crates.io-index"
2551
+
checksum = "650eef8c711430f1a879fdd01d4745a7deea475becfb90269c06775983bbf086"
2552
+
2553
+
[[package]]
2554
+
name = "nom"
2555
+
version = "7.1.3"
2556
+
source = "registry+https://github.com/rust-lang/crates.io-index"
2557
+
checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a"
2558
+
dependencies = [
2559
+
"memchr",
2560
+
"minimal-lexical",
2561
+
]
2562
+
2563
+
[[package]]
2564
+
name = "noop_proc_macro"
2565
+
version = "0.3.0"
2566
+
source = "registry+https://github.com/rust-lang/crates.io-index"
2567
+
checksum = "0676bb32a98c1a483ce53e500a81ad9c3d5b3f7c920c28c24e9cb0980d0b5bc8"
2568
+
2569
+
[[package]]
2570
+
name = "notify"
2571
+
version = "8.1.0"
2572
+
source = "registry+https://github.com/rust-lang/crates.io-index"
2573
+
checksum = "3163f59cd3fa0e9ef8c32f242966a7b9994fd7378366099593e0e73077cd8c97"
2574
+
dependencies = [
2575
+
"bitflags 2.9.1",
2576
+
"fsevent-sys",
2577
+
"inotify",
2578
+
"kqueue",
2579
+
"libc",
2580
+
"log",
2581
+
"mio",
2582
+
"notify-types",
2583
+
"walkdir",
2584
+
"windows-sys 0.60.2",
2585
+
]
2586
+
2587
+
[[package]]
2588
+
name = "notify-types"
2589
+
version = "2.0.0"
2590
+
source = "registry+https://github.com/rust-lang/crates.io-index"
2591
+
checksum = "5e0826a989adedc2a244799e823aece04662b66609d96af8dff7ac6df9a8925d"
2592
+
2593
+
[[package]]
2594
+
name = "nu-ansi-term"
2595
+
version = "0.46.0"
2596
+
source = "registry+https://github.com/rust-lang/crates.io-index"
2597
+
checksum = "77a8165726e8236064dbb45459242600304b42a5ea24ee2948e18e023bf7ba84"
2598
+
dependencies = [
2599
+
"overload",
2600
+
"winapi",
2601
+
]
2602
+
2603
+
[[package]]
2604
+
name = "num-bigint"
2605
+
version = "0.4.6"
2606
+
source = "registry+https://github.com/rust-lang/crates.io-index"
2607
+
checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9"
2608
+
dependencies = [
2609
+
"num-integer",
2610
+
"num-traits",
2611
+
]
2612
+
2613
+
[[package]]
2614
+
name = "num-bigint-dig"
2615
+
version = "0.8.4"
2616
+
source = "registry+https://github.com/rust-lang/crates.io-index"
2617
+
checksum = "dc84195820f291c7697304f3cbdadd1cb7199c0efc917ff5eafd71225c136151"
2618
+
dependencies = [
2619
+
"byteorder",
2620
+
"lazy_static",
2621
+
"libm",
2622
+
"num-integer",
2623
+
"num-iter",
2624
+
"num-traits",
2625
+
"rand 0.8.5",
2626
+
"smallvec",
2627
+
"zeroize",
2628
+
]
2629
+
2630
+
[[package]]
2631
+
name = "num-conv"
2632
+
version = "0.1.0"
2633
+
source = "registry+https://github.com/rust-lang/crates.io-index"
2634
+
checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9"
2635
+
2636
+
[[package]]
2637
+
name = "num-derive"
2638
+
version = "0.4.2"
2639
+
source = "registry+https://github.com/rust-lang/crates.io-index"
2640
+
checksum = "ed3955f1a9c7c0c15e092f9c887db08b1fc683305fdf6eb6684f22555355e202"
2641
+
dependencies = [
2642
+
"proc-macro2",
2643
+
"quote",
2644
+
"syn 2.0.104",
2645
+
]
2646
+
2647
+
[[package]]
2648
+
name = "num-integer"
2649
+
version = "0.1.46"
2650
+
source = "registry+https://github.com/rust-lang/crates.io-index"
2651
+
checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f"
2652
+
dependencies = [
2653
+
"num-traits",
2654
+
]
2655
+
2656
+
[[package]]
2657
+
name = "num-iter"
2658
+
version = "0.1.45"
2659
+
source = "registry+https://github.com/rust-lang/crates.io-index"
2660
+
checksum = "1429034a0490724d0075ebb2bc9e875d6503c3cf69e235a8941aa757d83ef5bf"
2661
+
dependencies = [
2662
+
"autocfg",
2663
+
"num-integer",
2664
+
"num-traits",
2665
+
]
2666
+
2667
+
[[package]]
2668
+
name = "num-rational"
2669
+
version = "0.4.2"
2670
+
source = "registry+https://github.com/rust-lang/crates.io-index"
2671
+
checksum = "f83d14da390562dca69fc84082e73e548e1ad308d24accdedd2720017cb37824"
2672
+
dependencies = [
2673
+
"num-bigint",
2674
+
"num-integer",
2675
+
"num-traits",
2676
+
]
2677
+
2678
+
[[package]]
2679
+
name = "num-traits"
2680
+
version = "0.2.19"
2681
+
source = "registry+https://github.com/rust-lang/crates.io-index"
2682
+
checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841"
2683
+
dependencies = [
2684
+
"autocfg",
2685
+
"libm",
2686
+
]
2687
+
2688
+
[[package]]
2689
+
name = "object"
2690
+
version = "0.36.7"
2691
+
source = "registry+https://github.com/rust-lang/crates.io-index"
2692
+
checksum = "62948e14d923ea95ea2c7c86c71013138b66525b86bdc08d2dcc262bdb497b87"
2693
+
dependencies = [
2694
+
"memchr",
2695
+
]
2696
+
2697
+
[[package]]
2698
+
name = "once_cell"
2699
+
version = "1.21.3"
2700
+
source = "registry+https://github.com/rust-lang/crates.io-index"
2701
+
checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d"
2702
+
dependencies = [
2703
+
"critical-section",
2704
+
"portable-atomic",
2705
+
]
2706
+
2707
+
[[package]]
2708
+
name = "once_cell_polyfill"
2709
+
version = "1.70.1"
2710
+
source = "registry+https://github.com/rust-lang/crates.io-index"
2711
+
checksum = "a4895175b425cb1f87721b59f0f286c2092bd4af812243672510e1ac53e2e0ad"
2712
+
2713
+
[[package]]
2714
+
name = "onig"
2715
+
version = "6.5.1"
2716
+
source = "registry+https://github.com/rust-lang/crates.io-index"
2717
+
checksum = "336b9c63443aceef14bea841b899035ae3abe89b7c486aaf4c5bd8aafedac3f0"
2718
+
dependencies = [
2719
+
"bitflags 2.9.1",
2720
+
"libc",
2721
+
"once_cell",
2722
+
"onig_sys",
2723
+
]
2724
+
2725
+
[[package]]
2726
+
name = "onig_sys"
2727
+
version = "69.9.1"
2728
+
source = "registry+https://github.com/rust-lang/crates.io-index"
2729
+
checksum = "c7f86c6eef3d6df15f23bcfb6af487cbd2fed4e5581d58d5bf1f5f8b7f6727dc"
2730
+
dependencies = [
2731
+
"cc",
2732
+
"pkg-config",
2733
+
]
2734
+
2735
+
[[package]]
2736
+
name = "openssl"
2737
+
version = "0.10.73"
2738
+
source = "registry+https://github.com/rust-lang/crates.io-index"
2739
+
checksum = "8505734d46c8ab1e19a1dce3aef597ad87dcb4c37e7188231769bd6bd51cebf8"
2740
+
dependencies = [
2741
+
"bitflags 2.9.1",
2742
+
"cfg-if",
2743
+
"foreign-types",
2744
+
"libc",
2745
+
"once_cell",
2746
+
"openssl-macros",
2747
+
"openssl-sys",
2748
+
]
2749
+
2750
+
[[package]]
2751
+
name = "openssl-macros"
2752
+
version = "0.1.1"
2753
+
source = "registry+https://github.com/rust-lang/crates.io-index"
2754
+
checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c"
2755
+
dependencies = [
2756
+
"proc-macro2",
2757
+
"quote",
2758
+
"syn 2.0.104",
2759
+
]
2760
+
2761
+
[[package]]
2762
+
name = "openssl-probe"
2763
+
version = "0.1.6"
2764
+
source = "registry+https://github.com/rust-lang/crates.io-index"
2765
+
checksum = "d05e27ee213611ffe7d6348b942e8f942b37114c00cc03cec254295a4a17852e"
2766
+
2767
+
[[package]]
2768
+
name = "openssl-sys"
2769
+
version = "0.9.109"
2770
+
source = "registry+https://github.com/rust-lang/crates.io-index"
2771
+
checksum = "90096e2e47630d78b7d1c20952dc621f957103f8bc2c8359ec81290d75238571"
2772
+
dependencies = [
2773
+
"cc",
2774
+
"libc",
2775
+
"pkg-config",
2776
+
"vcpkg",
2777
+
]
2778
+
2779
+
[[package]]
2780
+
name = "overload"
2781
+
version = "0.1.1"
2782
+
source = "registry+https://github.com/rust-lang/crates.io-index"
2783
+
checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39"
2784
+
2785
+
[[package]]
2786
+
name = "p256"
2787
+
version = "0.13.2"
2788
+
source = "registry+https://github.com/rust-lang/crates.io-index"
2789
+
checksum = "c9863ad85fa8f4460f9c48cb909d38a0d689dba1f6f6988a5e3e0d31071bcd4b"
2790
+
dependencies = [
2791
+
"ecdsa",
2792
+
"elliptic-curve",
2793
+
"primeorder",
2794
+
"serdect",
2795
+
"sha2",
2796
+
]
2797
+
2798
+
[[package]]
2799
+
name = "p384"
2800
+
version = "0.13.1"
2801
+
source = "registry+https://github.com/rust-lang/crates.io-index"
2802
+
checksum = "fe42f1670a52a47d448f14b6a5c61dd78fce51856e68edaa38f7ae3a46b8d6b6"
2803
+
dependencies = [
2804
+
"ecdsa",
2805
+
"elliptic-curve",
2806
+
"primeorder",
2807
+
"serdect",
2808
+
"sha2",
2809
+
]
2810
+
2811
+
[[package]]
2812
+
name = "parking"
2813
+
version = "2.2.1"
2814
+
source = "registry+https://github.com/rust-lang/crates.io-index"
2815
+
checksum = "f38d5652c16fde515bb1ecef450ab0f6a219d619a7274976324d5e377f7dceba"
2816
+
2817
+
[[package]]
2818
+
name = "parking_lot"
2819
+
version = "0.12.4"
2820
+
source = "registry+https://github.com/rust-lang/crates.io-index"
2821
+
checksum = "70d58bf43669b5795d1576d0641cfb6fbb2057bf629506267a92807158584a13"
2822
+
dependencies = [
2823
+
"lock_api",
2824
+
"parking_lot_core",
2825
+
]
2826
+
2827
+
[[package]]
2828
+
name = "parking_lot_core"
2829
+
version = "0.9.11"
2830
+
source = "registry+https://github.com/rust-lang/crates.io-index"
2831
+
checksum = "bc838d2a56b5b1a6c25f55575dfc605fabb63bb2365f6c2353ef9159aa69e4a5"
2832
+
dependencies = [
2833
+
"cfg-if",
2834
+
"libc",
2835
+
"redox_syscall",
2836
+
"smallvec",
2837
+
"windows-targets 0.52.6",
2838
+
]
2839
+
2840
+
[[package]]
2841
+
name = "paste"
2842
+
version = "1.0.15"
2843
+
source = "registry+https://github.com/rust-lang/crates.io-index"
2844
+
checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a"
2845
+
2846
+
[[package]]
2847
+
name = "pem-rfc7468"
2848
+
version = "0.7.0"
2849
+
source = "registry+https://github.com/rust-lang/crates.io-index"
2850
+
checksum = "88b39c9bfcfc231068454382784bb460aae594343fb030d46e9f50a645418412"
2851
+
dependencies = [
2852
+
"base64ct",
2853
+
]
2854
+
2855
+
[[package]]
2856
+
name = "percent-encoding"
2857
+
version = "2.3.1"
2858
+
source = "registry+https://github.com/rust-lang/crates.io-index"
2859
+
checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e"
2860
+
2861
+
[[package]]
2862
+
name = "pin-project-lite"
2863
+
version = "0.2.16"
2864
+
source = "registry+https://github.com/rust-lang/crates.io-index"
2865
+
checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b"
2866
+
2867
+
[[package]]
2868
+
name = "pin-utils"
2869
+
version = "0.1.0"
2870
+
source = "registry+https://github.com/rust-lang/crates.io-index"
2871
+
checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184"
2872
+
2873
+
[[package]]
2874
+
name = "pkcs1"
2875
+
version = "0.7.5"
2876
+
source = "registry+https://github.com/rust-lang/crates.io-index"
2877
+
checksum = "c8ffb9f10fa047879315e6625af03c164b16962a5368d724ed16323b68ace47f"
2878
+
dependencies = [
2879
+
"der",
2880
+
"pkcs8",
2881
+
"spki",
2882
+
]
2883
+
2884
+
[[package]]
2885
+
name = "pkcs8"
2886
+
version = "0.10.2"
2887
+
source = "registry+https://github.com/rust-lang/crates.io-index"
2888
+
checksum = "f950b2377845cebe5cf8b5165cb3cc1a5e0fa5cfa3e1f7f55707d8fd82e0a7b7"
2889
+
dependencies = [
2890
+
"der",
2891
+
"spki",
2892
+
]
2893
+
2894
+
[[package]]
2895
+
name = "pkg-config"
2896
+
version = "0.3.32"
2897
+
source = "registry+https://github.com/rust-lang/crates.io-index"
2898
+
checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c"
2899
+
2900
+
[[package]]
2901
+
name = "plist"
2902
+
version = "1.7.3"
2903
+
source = "registry+https://github.com/rust-lang/crates.io-index"
2904
+
checksum = "546b279bf0638ee811d9e47de2ca5b66575a543035d79fdf83959dd2f5c3b4c3"
2905
+
dependencies = [
2906
+
"base64",
2907
+
"indexmap",
2908
+
"quick-xml",
2909
+
"serde",
2910
+
"time",
2911
+
]
2912
+
2913
+
[[package]]
2914
+
name = "png"
2915
+
version = "0.17.16"
2916
+
source = "registry+https://github.com/rust-lang/crates.io-index"
2917
+
checksum = "82151a2fc869e011c153adc57cf2789ccb8d9906ce52c0b39a6b5697749d7526"
2918
+
dependencies = [
2919
+
"bitflags 1.3.2",
2920
+
"crc32fast",
2921
+
"fdeflate",
2922
+
"flate2",
2923
+
"miniz_oxide",
2924
+
]
2925
+
2926
+
[[package]]
2927
+
name = "portable-atomic"
2928
+
version = "1.11.1"
2929
+
source = "registry+https://github.com/rust-lang/crates.io-index"
2930
+
checksum = "f84267b20a16ea918e43c6a88433c2d54fa145c92a811b5b047ccbe153674483"
2931
+
2932
+
[[package]]
2933
+
name = "portable-atomic-util"
2934
+
version = "0.2.4"
2935
+
source = "registry+https://github.com/rust-lang/crates.io-index"
2936
+
checksum = "d8a2f0d8d040d7848a709caf78912debcc3f33ee4b3cac47d73d1e1069e83507"
2937
+
dependencies = [
2938
+
"portable-atomic",
2939
+
]
2940
+
2941
+
[[package]]
2942
+
name = "potential_utf"
2943
+
version = "0.1.2"
2944
+
source = "registry+https://github.com/rust-lang/crates.io-index"
2945
+
checksum = "e5a7c30837279ca13e7c867e9e40053bc68740f988cb07f7ca6df43cc734b585"
2946
+
dependencies = [
2947
+
"zerovec",
2948
+
]
2949
+
2950
+
[[package]]
2951
+
name = "powerfmt"
2952
+
version = "0.2.0"
2953
+
source = "registry+https://github.com/rust-lang/crates.io-index"
2954
+
checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391"
2955
+
2956
+
[[package]]
2957
+
name = "ppv-lite86"
2958
+
version = "0.2.21"
2959
+
source = "registry+https://github.com/rust-lang/crates.io-index"
2960
+
checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9"
2961
+
dependencies = [
2962
+
"zerocopy",
2963
+
]
2964
+
2965
+
[[package]]
2966
+
name = "prettyplease"
2967
+
version = "0.2.35"
2968
+
source = "registry+https://github.com/rust-lang/crates.io-index"
2969
+
checksum = "061c1221631e079b26479d25bbf2275bfe5917ae8419cd7e34f13bfc2aa7539a"
2970
+
dependencies = [
2971
+
"proc-macro2",
2972
+
"syn 2.0.104",
2973
+
]
2974
+
2975
+
[[package]]
2976
+
name = "primeorder"
2977
+
version = "0.13.6"
2978
+
source = "registry+https://github.com/rust-lang/crates.io-index"
2979
+
checksum = "353e1ca18966c16d9deb1c69278edbc5f194139612772bd9537af60ac231e1e6"
2980
+
dependencies = [
2981
+
"elliptic-curve",
2982
+
"serdect",
2983
+
]
2984
+
2985
+
[[package]]
2986
+
name = "proc-macro2"
2987
+
version = "1.0.95"
2988
+
source = "registry+https://github.com/rust-lang/crates.io-index"
2989
+
checksum = "02b3e5e68a3a1a02aad3ec490a98007cbc13c37cbe84a3cd7b8e406d76e7f778"
2990
+
dependencies = [
2991
+
"unicode-ident",
2992
+
]
2993
+
2994
+
[[package]]
2995
+
name = "profiling"
2996
+
version = "1.0.17"
2997
+
source = "registry+https://github.com/rust-lang/crates.io-index"
2998
+
checksum = "3eb8486b569e12e2c32ad3e204dbaba5e4b5b216e9367044f25f1dba42341773"
2999
+
dependencies = [
3000
+
"profiling-procmacros",
3001
+
]
3002
+
3003
+
[[package]]
3004
+
name = "profiling-procmacros"
3005
+
version = "1.0.17"
3006
+
source = "registry+https://github.com/rust-lang/crates.io-index"
3007
+
checksum = "52717f9a02b6965224f95ca2a81e2e0c5c43baacd28ca057577988930b6c3d5b"
3008
+
dependencies = [
3009
+
"quote",
3010
+
"syn 2.0.104",
3011
+
]
3012
+
3013
+
[[package]]
3014
+
name = "qoi"
3015
+
version = "0.4.1"
3016
+
source = "registry+https://github.com/rust-lang/crates.io-index"
3017
+
checksum = "7f6d64c71eb498fe9eae14ce4ec935c555749aef511cca85b5568910d6e48001"
3018
+
dependencies = [
3019
+
"bytemuck",
3020
+
]
3021
+
3022
+
[[package]]
3023
+
name = "quick-error"
3024
+
version = "2.0.1"
3025
+
source = "registry+https://github.com/rust-lang/crates.io-index"
3026
+
checksum = "a993555f31e5a609f617c12db6250dedcac1b0a85076912c436e6fc9b2c8e6a3"
3027
+
3028
+
[[package]]
3029
+
name = "quick-xml"
3030
+
version = "0.37.5"
3031
+
source = "registry+https://github.com/rust-lang/crates.io-index"
3032
+
checksum = "331e97a1af0bf59823e6eadffe373d7b27f485be8748f71471c662c1f269b7fb"
3033
+
dependencies = [
3034
+
"memchr",
3035
+
]
3036
+
3037
+
[[package]]
3038
+
name = "quinn"
3039
+
version = "0.11.8"
3040
+
source = "registry+https://github.com/rust-lang/crates.io-index"
3041
+
checksum = "626214629cda6781b6dc1d316ba307189c85ba657213ce642d9c77670f8202c8"
3042
+
dependencies = [
3043
+
"bytes",
3044
+
"cfg_aliases",
3045
+
"pin-project-lite",
3046
+
"quinn-proto",
3047
+
"quinn-udp",
3048
+
"rustc-hash",
3049
+
"rustls",
3050
+
"socket2",
3051
+
"thiserror 2.0.12",
3052
+
"tokio",
3053
+
"tracing",
3054
+
"web-time",
3055
+
]
3056
+
3057
+
[[package]]
3058
+
name = "quinn-proto"
3059
+
version = "0.11.12"
3060
+
source = "registry+https://github.com/rust-lang/crates.io-index"
3061
+
checksum = "49df843a9161c85bb8aae55f101bc0bac8bcafd637a620d9122fd7e0b2f7422e"
3062
+
dependencies = [
3063
+
"bytes",
3064
+
"getrandom 0.3.3",
3065
+
"lru-slab",
3066
+
"rand 0.9.1",
3067
+
"ring",
3068
+
"rustc-hash",
3069
+
"rustls",
3070
+
"rustls-pki-types",
3071
+
"slab",
3072
+
"thiserror 2.0.12",
3073
+
"tinyvec",
3074
+
"tracing",
3075
+
"web-time",
3076
+
]
3077
+
3078
+
[[package]]
3079
+
name = "quinn-udp"
3080
+
version = "0.5.13"
3081
+
source = "registry+https://github.com/rust-lang/crates.io-index"
3082
+
checksum = "fcebb1209ee276352ef14ff8732e24cc2b02bbac986cd74a4c81bcb2f9881970"
3083
+
dependencies = [
3084
+
"cfg_aliases",
3085
+
"libc",
3086
+
"once_cell",
3087
+
"socket2",
3088
+
"tracing",
3089
+
"windows-sys 0.59.0",
3090
+
]
3091
+
3092
+
[[package]]
3093
+
name = "quote"
3094
+
version = "1.0.40"
3095
+
source = "registry+https://github.com/rust-lang/crates.io-index"
3096
+
checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d"
3097
+
dependencies = [
3098
+
"proc-macro2",
3099
+
]
3100
+
3101
+
[[package]]
3102
+
name = "r-efi"
3103
+
version = "5.3.0"
3104
+
source = "registry+https://github.com/rust-lang/crates.io-index"
3105
+
checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f"
3106
+
3107
+
[[package]]
3108
+
name = "rand"
3109
+
version = "0.8.5"
3110
+
source = "registry+https://github.com/rust-lang/crates.io-index"
3111
+
checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404"
3112
+
dependencies = [
3113
+
"libc",
3114
+
"rand_chacha 0.3.1",
3115
+
"rand_core 0.6.4",
3116
+
]
3117
+
3118
+
[[package]]
3119
+
name = "rand"
3120
+
version = "0.9.1"
3121
+
source = "registry+https://github.com/rust-lang/crates.io-index"
3122
+
checksum = "9fbfd9d094a40bf3ae768db9361049ace4c0e04a4fd6b359518bd7b73a73dd97"
3123
+
dependencies = [
3124
+
"rand_chacha 0.9.0",
3125
+
"rand_core 0.9.3",
3126
+
]
3127
+
3128
+
[[package]]
3129
+
name = "rand_chacha"
3130
+
version = "0.3.1"
3131
+
source = "registry+https://github.com/rust-lang/crates.io-index"
3132
+
checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88"
3133
+
dependencies = [
3134
+
"ppv-lite86",
3135
+
"rand_core 0.6.4",
3136
+
]
3137
+
3138
+
[[package]]
3139
+
name = "rand_chacha"
3140
+
version = "0.9.0"
3141
+
source = "registry+https://github.com/rust-lang/crates.io-index"
3142
+
checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb"
3143
+
dependencies = [
3144
+
"ppv-lite86",
3145
+
"rand_core 0.9.3",
3146
+
]
3147
+
3148
+
[[package]]
3149
+
name = "rand_core"
3150
+
version = "0.6.4"
3151
+
source = "registry+https://github.com/rust-lang/crates.io-index"
3152
+
checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c"
3153
+
dependencies = [
3154
+
"getrandom 0.2.16",
3155
+
]
3156
+
3157
+
[[package]]
3158
+
name = "rand_core"
3159
+
version = "0.9.3"
3160
+
source = "registry+https://github.com/rust-lang/crates.io-index"
3161
+
checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38"
3162
+
dependencies = [
3163
+
"getrandom 0.3.3",
3164
+
]
3165
+
3166
+
[[package]]
3167
+
name = "rav1e"
3168
+
version = "0.7.1"
3169
+
source = "registry+https://github.com/rust-lang/crates.io-index"
3170
+
checksum = "cd87ce80a7665b1cce111f8a16c1f3929f6547ce91ade6addf4ec86a8dda5ce9"
3171
+
dependencies = [
3172
+
"arbitrary",
3173
+
"arg_enum_proc_macro",
3174
+
"arrayvec",
3175
+
"av1-grain",
3176
+
"bitstream-io",
3177
+
"built",
3178
+
"cfg-if",
3179
+
"interpolate_name",
3180
+
"itertools",
3181
+
"libc",
3182
+
"libfuzzer-sys",
3183
+
"log",
3184
+
"maybe-rayon",
3185
+
"new_debug_unreachable",
3186
+
"noop_proc_macro",
3187
+
"num-derive",
3188
+
"num-traits",
3189
+
"once_cell",
3190
+
"paste",
3191
+
"profiling",
3192
+
"rand 0.8.5",
3193
+
"rand_chacha 0.3.1",
3194
+
"simd_helpers",
3195
+
"system-deps",
3196
+
"thiserror 1.0.69",
3197
+
"v_frame",
3198
+
"wasm-bindgen",
3199
+
]
3200
+
3201
+
[[package]]
3202
+
name = "ravif"
3203
+
version = "0.11.20"
3204
+
source = "registry+https://github.com/rust-lang/crates.io-index"
3205
+
checksum = "5825c26fddd16ab9f515930d49028a630efec172e903483c94796cfe31893e6b"
3206
+
dependencies = [
3207
+
"avif-serialize",
3208
+
"imgref",
3209
+
"loop9",
3210
+
"quick-error",
3211
+
"rav1e",
3212
+
"rayon",
3213
+
"rgb",
3214
+
]
3215
+
3216
+
[[package]]
3217
+
name = "rayon"
3218
+
version = "1.10.0"
3219
+
source = "registry+https://github.com/rust-lang/crates.io-index"
3220
+
checksum = "b418a60154510ca1a002a752ca9714984e21e4241e804d32555251faf8b78ffa"
3221
+
dependencies = [
3222
+
"either",
3223
+
"rayon-core",
3224
+
]
3225
+
3226
+
[[package]]
3227
+
name = "rayon-core"
3228
+
version = "1.12.1"
3229
+
source = "registry+https://github.com/rust-lang/crates.io-index"
3230
+
checksum = "1465873a3dfdaa8ae7cb14b4383657caab0b3e8a0aa9ae8e04b044854c8dfce2"
3231
+
dependencies = [
3232
+
"crossbeam-deque",
3233
+
"crossbeam-utils",
3234
+
]
3235
+
3236
+
[[package]]
3237
+
name = "redox_syscall"
3238
+
version = "0.5.13"
3239
+
source = "registry+https://github.com/rust-lang/crates.io-index"
3240
+
checksum = "0d04b7d0ee6b4a0207a0a7adb104d23ecb0b47d6beae7152d0fa34b692b29fd6"
3241
+
dependencies = [
3242
+
"bitflags 2.9.1",
3243
+
]
3244
+
3245
+
[[package]]
3246
+
name = "regex"
3247
+
version = "1.11.1"
3248
+
source = "registry+https://github.com/rust-lang/crates.io-index"
3249
+
checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191"
3250
+
dependencies = [
3251
+
"aho-corasick",
3252
+
"memchr",
3253
+
"regex-automata 0.4.9",
3254
+
"regex-syntax 0.8.5",
3255
+
]
3256
+
3257
+
[[package]]
3258
+
name = "regex-automata"
3259
+
version = "0.1.10"
3260
+
source = "registry+https://github.com/rust-lang/crates.io-index"
3261
+
checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132"
3262
+
dependencies = [
3263
+
"regex-syntax 0.6.29",
3264
+
]
3265
+
3266
+
[[package]]
3267
+
name = "regex-automata"
3268
+
version = "0.4.9"
3269
+
source = "registry+https://github.com/rust-lang/crates.io-index"
3270
+
checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908"
3271
+
dependencies = [
3272
+
"aho-corasick",
3273
+
"memchr",
3274
+
"regex-syntax 0.8.5",
3275
+
]
3276
+
3277
+
[[package]]
3278
+
name = "regex-syntax"
3279
+
version = "0.6.29"
3280
+
source = "registry+https://github.com/rust-lang/crates.io-index"
3281
+
checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1"
3282
+
3283
+
[[package]]
3284
+
name = "regex-syntax"
3285
+
version = "0.8.5"
3286
+
source = "registry+https://github.com/rust-lang/crates.io-index"
3287
+
checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c"
3288
+
3289
+
[[package]]
3290
+
name = "reqwest"
3291
+
version = "0.12.22"
3292
+
source = "registry+https://github.com/rust-lang/crates.io-index"
3293
+
checksum = "cbc931937e6ca3a06e3b6c0aa7841849b160a90351d6ab467a8b9b9959767531"
3294
+
dependencies = [
3295
+
"base64",
3296
+
"bytes",
3297
+
"encoding_rs",
3298
+
"futures-core",
3299
+
"futures-util",
3300
+
"h2",
3301
+
"http",
3302
+
"http-body",
3303
+
"http-body-util",
3304
+
"hyper",
3305
+
"hyper-rustls",
3306
+
"hyper-tls",
3307
+
"hyper-util",
3308
+
"js-sys",
3309
+
"log",
3310
+
"mime",
3311
+
"mime_guess",
3312
+
"native-tls",
3313
+
"percent-encoding",
3314
+
"pin-project-lite",
3315
+
"quinn",
3316
+
"rustls",
3317
+
"rustls-pki-types",
3318
+
"serde",
3319
+
"serde_json",
3320
+
"serde_urlencoded",
3321
+
"sync_wrapper",
3322
+
"tokio",
3323
+
"tokio-native-tls",
3324
+
"tokio-rustls",
3325
+
"tokio-util",
3326
+
"tower",
3327
+
"tower-http 0.6.6",
3328
+
"tower-service",
3329
+
"url",
3330
+
"wasm-bindgen",
3331
+
"wasm-bindgen-futures",
3332
+
"wasm-streams",
3333
+
"web-sys",
3334
+
"webpki-roots 1.0.1",
3335
+
]
3336
+
3337
+
[[package]]
3338
+
name = "reqwest-chain"
3339
+
version = "1.0.0"
3340
+
source = "registry+https://github.com/rust-lang/crates.io-index"
3341
+
checksum = "da5c014fb79a8227db44a0433d748107750d2550b7fca55c59a3d7ee7d2ee2b2"
3342
+
dependencies = [
3343
+
"anyhow",
3344
+
"async-trait",
3345
+
"http",
3346
+
"reqwest-middleware",
3347
+
]
3348
+
3349
+
[[package]]
3350
+
name = "reqwest-middleware"
3351
+
version = "0.4.2"
3352
+
source = "registry+https://github.com/rust-lang/crates.io-index"
3353
+
checksum = "57f17d28a6e6acfe1733fe24bcd30774d13bffa4b8a22535b4c8c98423088d4e"
3354
+
dependencies = [
3355
+
"anyhow",
3356
+
"async-trait",
3357
+
"http",
3358
+
"reqwest",
3359
+
"serde",
3360
+
"thiserror 1.0.69",
3361
+
"tower-service",
3362
+
]
3363
+
3364
+
[[package]]
3365
+
name = "resolv-conf"
3366
+
version = "0.7.4"
3367
+
source = "registry+https://github.com/rust-lang/crates.io-index"
3368
+
checksum = "95325155c684b1c89f7765e30bc1c42e4a6da51ca513615660cb8a62ef9a88e3"
3369
+
3370
+
[[package]]
3371
+
name = "rfc6979"
3372
+
version = "0.4.0"
3373
+
source = "registry+https://github.com/rust-lang/crates.io-index"
3374
+
checksum = "f8dd2a808d456c4a54e300a23e9f5a67e122c3024119acbfd73e3bf664491cb2"
3375
+
dependencies = [
3376
+
"hmac",
3377
+
"subtle",
3378
+
]
3379
+
3380
+
[[package]]
3381
+
name = "rgb"
3382
+
version = "0.8.51"
3383
+
source = "registry+https://github.com/rust-lang/crates.io-index"
3384
+
checksum = "a457e416a0f90d246a4c3288bd7a25b2304ca727f253f95be383dd17af56be8f"
3385
+
3386
+
[[package]]
3387
+
name = "ring"
3388
+
version = "0.17.14"
3389
+
source = "registry+https://github.com/rust-lang/crates.io-index"
3390
+
checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7"
3391
+
dependencies = [
3392
+
"cc",
3393
+
"cfg-if",
3394
+
"getrandom 0.2.16",
3395
+
"libc",
3396
+
"untrusted",
3397
+
"windows-sys 0.52.0",
3398
+
]
3399
+
3400
+
[[package]]
3401
+
name = "rsa"
3402
+
version = "0.9.8"
3403
+
source = "registry+https://github.com/rust-lang/crates.io-index"
3404
+
checksum = "78928ac1ed176a5ca1d17e578a1825f3d81ca54cf41053a592584b020cfd691b"
3405
+
dependencies = [
3406
+
"const-oid",
3407
+
"digest",
3408
+
"num-bigint-dig",
3409
+
"num-integer",
3410
+
"num-traits",
3411
+
"pkcs1",
3412
+
"pkcs8",
3413
+
"rand_core 0.6.4",
3414
+
"signature",
3415
+
"spki",
3416
+
"subtle",
3417
+
"zeroize",
3418
+
]
3419
+
3420
+
[[package]]
3421
+
name = "rust-embed"
3422
+
version = "8.7.2"
3423
+
source = "registry+https://github.com/rust-lang/crates.io-index"
3424
+
checksum = "025908b8682a26ba8d12f6f2d66b987584a4a87bc024abc5bbc12553a8cd178a"
3425
+
dependencies = [
3426
+
"rust-embed-impl",
3427
+
"rust-embed-utils",
3428
+
"walkdir",
3429
+
]
3430
+
3431
+
[[package]]
3432
+
name = "rust-embed-impl"
3433
+
version = "8.7.2"
3434
+
source = "registry+https://github.com/rust-lang/crates.io-index"
3435
+
checksum = "6065f1a4392b71819ec1ea1df1120673418bf386f50de1d6f54204d836d4349c"
3436
+
dependencies = [
3437
+
"proc-macro2",
3438
+
"quote",
3439
+
"rust-embed-utils",
3440
+
"syn 2.0.104",
3441
+
"walkdir",
3442
+
]
3443
+
3444
+
[[package]]
3445
+
name = "rust-embed-utils"
3446
+
version = "8.7.2"
3447
+
source = "registry+https://github.com/rust-lang/crates.io-index"
3448
+
checksum = "f6cc0c81648b20b70c491ff8cce00c1c3b223bb8ed2b5d41f0e54c6c4c0a3594"
3449
+
dependencies = [
3450
+
"sha2",
3451
+
"walkdir",
3452
+
]
3453
+
3454
+
[[package]]
3455
+
name = "rust_decimal"
3456
+
version = "1.37.2"
3457
+
source = "registry+https://github.com/rust-lang/crates.io-index"
3458
+
checksum = "b203a6425500a03e0919c42d3c47caca51e79f1132046626d2c8871c5092035d"
3459
+
dependencies = [
3460
+
"arrayvec",
3461
+
"num-traits",
3462
+
]
3463
+
3464
+
[[package]]
3465
+
name = "rustc-demangle"
3466
+
version = "0.1.25"
3467
+
source = "registry+https://github.com/rust-lang/crates.io-index"
3468
+
checksum = "989e6739f80c4ad5b13e0fd7fe89531180375b18520cc8c82080e4dc4035b84f"
3469
+
3470
+
[[package]]
3471
+
name = "rustc-hash"
3472
+
version = "2.1.1"
3473
+
source = "registry+https://github.com/rust-lang/crates.io-index"
3474
+
checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d"
3475
+
3476
+
[[package]]
3477
+
name = "rustc_version"
3478
+
version = "0.4.1"
3479
+
source = "registry+https://github.com/rust-lang/crates.io-index"
3480
+
checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92"
3481
+
dependencies = [
3482
+
"semver",
3483
+
]
3484
+
3485
+
[[package]]
3486
+
name = "rustix"
3487
+
version = "1.0.7"
3488
+
source = "registry+https://github.com/rust-lang/crates.io-index"
3489
+
checksum = "c71e83d6afe7ff64890ec6b71d6a69bb8a610ab78ce364b3352876bb4c801266"
3490
+
dependencies = [
3491
+
"bitflags 2.9.1",
3492
+
"errno",
3493
+
"libc",
3494
+
"linux-raw-sys",
3495
+
"windows-sys 0.59.0",
3496
+
]
3497
+
3498
+
[[package]]
3499
+
name = "rustls"
3500
+
version = "0.23.28"
3501
+
source = "registry+https://github.com/rust-lang/crates.io-index"
3502
+
checksum = "7160e3e10bf4535308537f3c4e1641468cd0e485175d6163087c0393c7d46643"
3503
+
dependencies = [
3504
+
"once_cell",
3505
+
"ring",
3506
+
"rustls-pki-types",
3507
+
"rustls-webpki",
3508
+
"subtle",
3509
+
"zeroize",
3510
+
]
3511
+
3512
+
[[package]]
3513
+
name = "rustls-native-certs"
3514
+
version = "0.8.1"
3515
+
source = "registry+https://github.com/rust-lang/crates.io-index"
3516
+
checksum = "7fcff2dd52b58a8d98a70243663a0d234c4e2b79235637849d15913394a247d3"
3517
+
dependencies = [
3518
+
"openssl-probe",
3519
+
"rustls-pki-types",
3520
+
"schannel",
3521
+
"security-framework 3.2.0",
3522
+
]
3523
+
3524
+
[[package]]
3525
+
name = "rustls-pki-types"
3526
+
version = "1.12.0"
3527
+
source = "registry+https://github.com/rust-lang/crates.io-index"
3528
+
checksum = "229a4a4c221013e7e1f1a043678c5cc39fe5171437c88fb47151a21e6f5b5c79"
3529
+
dependencies = [
3530
+
"web-time",
3531
+
"zeroize",
3532
+
]
3533
+
3534
+
[[package]]
3535
+
name = "rustls-webpki"
3536
+
version = "0.103.3"
3537
+
source = "registry+https://github.com/rust-lang/crates.io-index"
3538
+
checksum = "e4a72fe2bcf7a6ac6fd7d0b9e5cb68aeb7d4c0a0271730218b3e92d43b4eb435"
3539
+
dependencies = [
3540
+
"ring",
3541
+
"rustls-pki-types",
3542
+
"untrusted",
3543
+
]
3544
+
3545
+
[[package]]
3546
+
name = "rustversion"
3547
+
version = "1.0.21"
3548
+
source = "registry+https://github.com/rust-lang/crates.io-index"
3549
+
checksum = "8a0d197bd2c9dc6e53b84da9556a69ba4cdfab8619eb41a8bd1cc2027a0f6b1d"
3550
+
3551
+
[[package]]
3552
+
name = "ryu"
3553
+
version = "1.0.20"
3554
+
source = "registry+https://github.com/rust-lang/crates.io-index"
3555
+
checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f"
3556
+
3557
+
[[package]]
3558
+
name = "same-file"
3559
+
version = "1.0.6"
3560
+
source = "registry+https://github.com/rust-lang/crates.io-index"
3561
+
checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502"
3562
+
dependencies = [
3563
+
"winapi-util",
3564
+
]
3565
+
3566
+
[[package]]
3567
+
name = "schannel"
3568
+
version = "0.1.27"
3569
+
source = "registry+https://github.com/rust-lang/crates.io-index"
3570
+
checksum = "1f29ebaa345f945cec9fbbc532eb307f0fdad8161f281b6369539c8d84876b3d"
3571
+
dependencies = [
3572
+
"windows-sys 0.59.0",
3573
+
]
3574
+
3575
+
[[package]]
3576
+
name = "scoped-tls"
3577
+
version = "1.0.1"
3578
+
source = "registry+https://github.com/rust-lang/crates.io-index"
3579
+
checksum = "e1cf6437eb19a8f4a6cc0f7dca544973b0b78843adbfeb3683d1a94a0024a294"
3580
+
3581
+
[[package]]
3582
+
name = "scopeguard"
3583
+
version = "1.2.0"
3584
+
source = "registry+https://github.com/rust-lang/crates.io-index"
3585
+
checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
3586
+
3587
+
[[package]]
3588
+
name = "sec1"
3589
+
version = "0.7.3"
3590
+
source = "registry+https://github.com/rust-lang/crates.io-index"
3591
+
checksum = "d3e97a565f76233a6003f9f5c54be1d9c5bdfa3eccfb189469f11ec4901c47dc"
3592
+
dependencies = [
3593
+
"base16ct",
3594
+
"der",
3595
+
"generic-array",
3596
+
"pkcs8",
3597
+
"serdect",
3598
+
"subtle",
3599
+
"zeroize",
3600
+
]
3601
+
3602
+
[[package]]
3603
+
name = "security-framework"
3604
+
version = "2.11.1"
3605
+
source = "registry+https://github.com/rust-lang/crates.io-index"
3606
+
checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02"
3607
+
dependencies = [
3608
+
"bitflags 2.9.1",
3609
+
"core-foundation 0.9.4",
3610
+
"core-foundation-sys",
3611
+
"libc",
3612
+
"security-framework-sys",
3613
+
]
3614
+
3615
+
[[package]]
3616
+
name = "security-framework"
3617
+
version = "3.2.0"
3618
+
source = "registry+https://github.com/rust-lang/crates.io-index"
3619
+
checksum = "271720403f46ca04f7ba6f55d438f8bd878d6b8ca0a1046e8228c4145bcbb316"
3620
+
dependencies = [
3621
+
"bitflags 2.9.1",
3622
+
"core-foundation 0.10.1",
3623
+
"core-foundation-sys",
3624
+
"libc",
3625
+
"security-framework-sys",
3626
+
]
3627
+
3628
+
[[package]]
3629
+
name = "security-framework-sys"
3630
+
version = "2.14.0"
3631
+
source = "registry+https://github.com/rust-lang/crates.io-index"
3632
+
checksum = "49db231d56a190491cb4aeda9527f1ad45345af50b0851622a7adb8c03b01c32"
3633
+
dependencies = [
3634
+
"core-foundation-sys",
3635
+
"libc",
3636
+
]
3637
+
3638
+
[[package]]
3639
+
name = "self_cell"
3640
+
version = "1.2.0"
3641
+
source = "registry+https://github.com/rust-lang/crates.io-index"
3642
+
checksum = "0f7d95a54511e0c7be3f51e8867aa8cf35148d7b9445d44de2f943e2b206e749"
3643
+
3644
+
[[package]]
3645
+
name = "semver"
3646
+
version = "1.0.26"
3647
+
source = "registry+https://github.com/rust-lang/crates.io-index"
3648
+
checksum = "56e6fa9c48d24d85fb3de5ad847117517440f6beceb7798af16b4a87d616b8d0"
3649
+
3650
+
[[package]]
3651
+
name = "serde"
3652
+
version = "1.0.219"
3653
+
source = "registry+https://github.com/rust-lang/crates.io-index"
3654
+
checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6"
3655
+
dependencies = [
3656
+
"serde_derive",
3657
+
]
3658
+
3659
+
[[package]]
3660
+
name = "serde_bytes"
3661
+
version = "0.11.17"
3662
+
source = "registry+https://github.com/rust-lang/crates.io-index"
3663
+
checksum = "8437fd221bde2d4ca316d61b90e337e9e702b3820b87d63caa9ba6c02bd06d96"
3664
+
dependencies = [
3665
+
"serde",
3666
+
]
3667
+
3668
+
[[package]]
3669
+
name = "serde_derive"
3670
+
version = "1.0.219"
3671
+
source = "registry+https://github.com/rust-lang/crates.io-index"
3672
+
checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00"
3673
+
dependencies = [
3674
+
"proc-macro2",
3675
+
"quote",
3676
+
"syn 2.0.104",
3677
+
]
3678
+
3679
+
[[package]]
3680
+
name = "serde_ipld_dagcbor"
3681
+
version = "0.6.3"
3682
+
source = "registry+https://github.com/rust-lang/crates.io-index"
3683
+
checksum = "99600723cf53fb000a66175555098db7e75217c415bdd9a16a65d52a19dcc4fc"
3684
+
dependencies = [
3685
+
"cbor4ii",
3686
+
"ipld-core",
3687
+
"scopeguard",
3688
+
"serde",
3689
+
]
3690
+
3691
+
[[package]]
3692
+
name = "serde_json"
3693
+
version = "1.0.140"
3694
+
source = "registry+https://github.com/rust-lang/crates.io-index"
3695
+
checksum = "20068b6e96dc6c9bd23e01df8827e6c7e1f2fddd43c21810382803c136b99373"
3696
+
dependencies = [
3697
+
"itoa",
3698
+
"memchr",
3699
+
"ryu",
3700
+
"serde",
3701
+
]
3702
+
3703
+
[[package]]
3704
+
name = "serde_path_to_error"
3705
+
version = "0.1.17"
3706
+
source = "registry+https://github.com/rust-lang/crates.io-index"
3707
+
checksum = "59fab13f937fa393d08645bf3a84bdfe86e296747b506ada67bb15f10f218b2a"
3708
+
dependencies = [
3709
+
"itoa",
3710
+
"serde",
3711
+
]
3712
+
3713
+
[[package]]
3714
+
name = "serde_spanned"
3715
+
version = "0.6.9"
3716
+
source = "registry+https://github.com/rust-lang/crates.io-index"
3717
+
checksum = "bf41e0cfaf7226dca15e8197172c295a782857fcb97fad1808a166870dee75a3"
3718
+
dependencies = [
3719
+
"serde",
3720
+
]
3721
+
3722
+
[[package]]
3723
+
name = "serde_urlencoded"
3724
+
version = "0.7.1"
3725
+
source = "registry+https://github.com/rust-lang/crates.io-index"
3726
+
checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd"
3727
+
dependencies = [
3728
+
"form_urlencoded",
3729
+
"itoa",
3730
+
"ryu",
3731
+
"serde",
3732
+
]
3733
+
3734
+
[[package]]
3735
+
name = "serdect"
3736
+
version = "0.2.0"
3737
+
source = "registry+https://github.com/rust-lang/crates.io-index"
3738
+
checksum = "a84f14a19e9a014bb9f4512488d9829a68e04ecabffb0f9904cd1ace94598177"
3739
+
dependencies = [
3740
+
"base16ct",
3741
+
"serde",
3742
+
]
3743
+
3744
+
[[package]]
3745
+
name = "sha1"
3746
+
version = "0.10.6"
3747
+
source = "registry+https://github.com/rust-lang/crates.io-index"
3748
+
checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba"
3749
+
dependencies = [
3750
+
"cfg-if",
3751
+
"cpufeatures",
3752
+
"digest",
3753
+
]
3754
+
3755
+
[[package]]
3756
+
name = "sha2"
3757
+
version = "0.10.9"
3758
+
source = "registry+https://github.com/rust-lang/crates.io-index"
3759
+
checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283"
3760
+
dependencies = [
3761
+
"cfg-if",
3762
+
"cpufeatures",
3763
+
"digest",
3764
+
]
3765
+
3766
+
[[package]]
3767
+
name = "sharded-slab"
3768
+
version = "0.1.7"
3769
+
source = "registry+https://github.com/rust-lang/crates.io-index"
3770
+
checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6"
3771
+
dependencies = [
3772
+
"lazy_static",
3773
+
]
3774
+
3775
+
[[package]]
3776
+
name = "shell-words"
3777
+
version = "1.1.0"
3778
+
source = "registry+https://github.com/rust-lang/crates.io-index"
3779
+
checksum = "24188a676b6ae68c3b2cb3a01be17fbf7240ce009799bb56d5b1409051e78fde"
3780
+
3781
+
[[package]]
3782
+
name = "shlex"
3783
+
version = "1.3.0"
3784
+
source = "registry+https://github.com/rust-lang/crates.io-index"
3785
+
checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
3786
+
3787
+
[[package]]
3788
+
name = "signal-hook-registry"
3789
+
version = "1.4.5"
3790
+
source = "registry+https://github.com/rust-lang/crates.io-index"
3791
+
checksum = "9203b8055f63a2a00e2f593bb0510367fe707d7ff1e5c872de2f537b339e5410"
3792
+
dependencies = [
3793
+
"libc",
3794
+
]
3795
+
3796
+
[[package]]
3797
+
name = "signature"
3798
+
version = "2.2.0"
3799
+
source = "registry+https://github.com/rust-lang/crates.io-index"
3800
+
checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de"
3801
+
dependencies = [
3802
+
"digest",
3803
+
"rand_core 0.6.4",
3804
+
]
3805
+
3806
+
[[package]]
3807
+
name = "simd-adler32"
3808
+
version = "0.3.7"
3809
+
source = "registry+https://github.com/rust-lang/crates.io-index"
3810
+
checksum = "d66dc143e6b11c1eddc06d5c423cfc97062865baf299914ab64caa38182078fe"
3811
+
3812
+
[[package]]
3813
+
name = "simd_helpers"
3814
+
version = "0.1.0"
3815
+
source = "registry+https://github.com/rust-lang/crates.io-index"
3816
+
checksum = "95890f873bec569a0362c235787f3aca6e1e887302ba4840839bcc6459c42da6"
3817
+
dependencies = [
3818
+
"quote",
3819
+
]
3820
+
3821
+
[[package]]
3822
+
name = "simdutf8"
3823
+
version = "0.1.5"
3824
+
source = "registry+https://github.com/rust-lang/crates.io-index"
3825
+
checksum = "e3a9fe34e3e7a50316060351f37187a3f546bce95496156754b601a5fa71b76e"
3826
+
3827
+
[[package]]
3828
+
name = "siphasher"
3829
+
version = "1.0.1"
3830
+
source = "registry+https://github.com/rust-lang/crates.io-index"
3831
+
checksum = "56199f7ddabf13fe5074ce809e7d3f42b42ae711800501b5b16ea82ad029c39d"
3832
+
3833
+
[[package]]
3834
+
name = "slab"
3835
+
version = "0.4.10"
3836
+
source = "registry+https://github.com/rust-lang/crates.io-index"
3837
+
checksum = "04dc19736151f35336d325007ac991178d504a119863a2fcb3758cdb5e52c50d"
3838
+
3839
+
[[package]]
3840
+
name = "slug"
3841
+
version = "0.1.6"
3842
+
source = "registry+https://github.com/rust-lang/crates.io-index"
3843
+
checksum = "882a80f72ee45de3cc9a5afeb2da0331d58df69e4e7d8eeb5d3c7784ae67e724"
3844
+
dependencies = [
3845
+
"deunicode",
3846
+
"wasm-bindgen",
3847
+
]
3848
+
3849
+
[[package]]
3850
+
name = "slugify"
3851
+
version = "0.1.0"
3852
+
source = "registry+https://github.com/rust-lang/crates.io-index"
3853
+
checksum = "d6b8cf203d2088b831d7558f8e5151bfa420c57a34240b28cee29d0ae5f2ac8b"
3854
+
dependencies = [
3855
+
"unidecode",
3856
+
]
3857
+
3858
+
[[package]]
3859
+
name = "smallvec"
3860
+
version = "1.15.1"
3861
+
source = "registry+https://github.com/rust-lang/crates.io-index"
3862
+
checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03"
3863
+
dependencies = [
3864
+
"serde",
3865
+
]
3866
+
3867
+
[[package]]
3868
+
name = "socket2"
3869
+
version = "0.5.10"
3870
+
source = "registry+https://github.com/rust-lang/crates.io-index"
3871
+
checksum = "e22376abed350d73dd1cd119b57ffccad95b4e585a7cda43e286245ce23c0678"
3872
+
dependencies = [
3873
+
"libc",
3874
+
"windows-sys 0.52.0",
3875
+
]
3876
+
3877
+
[[package]]
3878
+
name = "spin"
3879
+
version = "0.9.8"
3880
+
source = "registry+https://github.com/rust-lang/crates.io-index"
3881
+
checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67"
3882
+
dependencies = [
3883
+
"lock_api",
3884
+
]
3885
+
3886
+
[[package]]
3887
+
name = "spki"
3888
+
version = "0.7.3"
3889
+
source = "registry+https://github.com/rust-lang/crates.io-index"
3890
+
checksum = "d91ed6c858b01f942cd56b37a94b3e0a1798290327d1236e4d9cf4eaca44d29d"
3891
+
dependencies = [
3892
+
"base64ct",
3893
+
"der",
3894
+
]
3895
+
3896
+
[[package]]
3897
+
name = "sqlx"
3898
+
version = "0.8.6"
3899
+
source = "registry+https://github.com/rust-lang/crates.io-index"
3900
+
checksum = "1fefb893899429669dcdd979aff487bd78f4064e5e7907e4269081e0ef7d97dc"
3901
+
dependencies = [
3902
+
"sqlx-core",
3903
+
"sqlx-macros",
3904
+
"sqlx-mysql",
3905
+
"sqlx-postgres",
3906
+
"sqlx-sqlite",
3907
+
]
3908
+
3909
+
[[package]]
3910
+
name = "sqlx-core"
3911
+
version = "0.8.6"
3912
+
source = "registry+https://github.com/rust-lang/crates.io-index"
3913
+
checksum = "ee6798b1838b6a0f69c007c133b8df5866302197e404e8b6ee8ed3e3a5e68dc6"
3914
+
dependencies = [
3915
+
"base64",
3916
+
"bytes",
3917
+
"chrono",
3918
+
"crc",
3919
+
"crossbeam-queue",
3920
+
"either",
3921
+
"event-listener",
3922
+
"futures-core",
3923
+
"futures-intrusive",
3924
+
"futures-io",
3925
+
"futures-util",
3926
+
"hashbrown 0.15.4",
3927
+
"hashlink",
3928
+
"indexmap",
3929
+
"log",
3930
+
"memchr",
3931
+
"once_cell",
3932
+
"percent-encoding",
3933
+
"rustls",
3934
+
"serde",
3935
+
"serde_json",
3936
+
"sha2",
3937
+
"smallvec",
3938
+
"thiserror 2.0.12",
3939
+
"tokio",
3940
+
"tokio-stream",
3941
+
"tracing",
3942
+
"url",
3943
+
"uuid",
3944
+
"webpki-roots 0.26.11",
3945
+
]
3946
+
3947
+
[[package]]
3948
+
name = "sqlx-macros"
3949
+
version = "0.8.6"
3950
+
source = "registry+https://github.com/rust-lang/crates.io-index"
3951
+
checksum = "a2d452988ccaacfbf5e0bdbc348fb91d7c8af5bee192173ac3636b5fb6e6715d"
3952
+
dependencies = [
3953
+
"proc-macro2",
3954
+
"quote",
3955
+
"sqlx-core",
3956
+
"sqlx-macros-core",
3957
+
"syn 2.0.104",
3958
+
]
3959
+
3960
+
[[package]]
3961
+
name = "sqlx-macros-core"
3962
+
version = "0.8.6"
3963
+
source = "registry+https://github.com/rust-lang/crates.io-index"
3964
+
checksum = "19a9c1841124ac5a61741f96e1d9e2ec77424bf323962dd894bdb93f37d5219b"
3965
+
dependencies = [
3966
+
"dotenvy",
3967
+
"either",
3968
+
"heck",
3969
+
"hex",
3970
+
"once_cell",
3971
+
"proc-macro2",
3972
+
"quote",
3973
+
"serde",
3974
+
"serde_json",
3975
+
"sha2",
3976
+
"sqlx-core",
3977
+
"sqlx-mysql",
3978
+
"sqlx-postgres",
3979
+
"sqlx-sqlite",
3980
+
"syn 2.0.104",
3981
+
"tokio",
3982
+
"url",
3983
+
]
3984
+
3985
+
[[package]]
3986
+
name = "sqlx-mysql"
3987
+
version = "0.8.6"
3988
+
source = "registry+https://github.com/rust-lang/crates.io-index"
3989
+
checksum = "aa003f0038df784eb8fecbbac13affe3da23b45194bd57dba231c8f48199c526"
3990
+
dependencies = [
3991
+
"atoi",
3992
+
"base64",
3993
+
"bitflags 2.9.1",
3994
+
"byteorder",
3995
+
"bytes",
3996
+
"chrono",
3997
+
"crc",
3998
+
"digest",
3999
+
"dotenvy",
4000
+
"either",
4001
+
"futures-channel",
4002
+
"futures-core",
4003
+
"futures-io",
4004
+
"futures-util",
4005
+
"generic-array",
4006
+
"hex",
4007
+
"hkdf",
4008
+
"hmac",
4009
+
"itoa",
4010
+
"log",
4011
+
"md-5",
4012
+
"memchr",
4013
+
"once_cell",
4014
+
"percent-encoding",
4015
+
"rand 0.8.5",
4016
+
"rsa",
4017
+
"serde",
4018
+
"sha1",
4019
+
"sha2",
4020
+
"smallvec",
4021
+
"sqlx-core",
4022
+
"stringprep",
4023
+
"thiserror 2.0.12",
4024
+
"tracing",
4025
+
"uuid",
4026
+
"whoami",
4027
+
]
4028
+
4029
+
[[package]]
4030
+
name = "sqlx-postgres"
4031
+
version = "0.8.6"
4032
+
source = "registry+https://github.com/rust-lang/crates.io-index"
4033
+
checksum = "db58fcd5a53cf07c184b154801ff91347e4c30d17a3562a635ff028ad5deda46"
4034
+
dependencies = [
4035
+
"atoi",
4036
+
"base64",
4037
+
"bitflags 2.9.1",
4038
+
"byteorder",
4039
+
"chrono",
4040
+
"crc",
4041
+
"dotenvy",
4042
+
"etcetera",
4043
+
"futures-channel",
4044
+
"futures-core",
4045
+
"futures-util",
4046
+
"hex",
4047
+
"hkdf",
4048
+
"hmac",
4049
+
"home",
4050
+
"itoa",
4051
+
"log",
4052
+
"md-5",
4053
+
"memchr",
4054
+
"once_cell",
4055
+
"rand 0.8.5",
4056
+
"serde",
4057
+
"serde_json",
4058
+
"sha2",
4059
+
"smallvec",
4060
+
"sqlx-core",
4061
+
"stringprep",
4062
+
"thiserror 2.0.12",
4063
+
"tracing",
4064
+
"uuid",
4065
+
"whoami",
4066
+
]
4067
+
4068
+
[[package]]
4069
+
name = "sqlx-sqlite"
4070
+
version = "0.8.6"
4071
+
source = "registry+https://github.com/rust-lang/crates.io-index"
4072
+
checksum = "c2d12fe70b2c1b4401038055f90f151b78208de1f9f89a7dbfd41587a10c3eea"
4073
+
dependencies = [
4074
+
"atoi",
4075
+
"chrono",
4076
+
"flume",
4077
+
"futures-channel",
4078
+
"futures-core",
4079
+
"futures-executor",
4080
+
"futures-intrusive",
4081
+
"futures-util",
4082
+
"libsqlite3-sys",
4083
+
"log",
4084
+
"percent-encoding",
4085
+
"serde",
4086
+
"serde_urlencoded",
4087
+
"sqlx-core",
4088
+
"thiserror 2.0.12",
4089
+
"tracing",
4090
+
"url",
4091
+
"uuid",
4092
+
]
4093
+
4094
+
[[package]]
4095
+
name = "stable_deref_trait"
4096
+
version = "1.2.0"
4097
+
source = "registry+https://github.com/rust-lang/crates.io-index"
4098
+
checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3"
4099
+
4100
+
[[package]]
4101
+
name = "stringprep"
4102
+
version = "0.1.5"
4103
+
source = "registry+https://github.com/rust-lang/crates.io-index"
4104
+
checksum = "7b4df3d392d81bd458a8a621b8bffbd2302a12ffe288a9d931670948749463b1"
4105
+
dependencies = [
4106
+
"unicode-bidi",
4107
+
"unicode-normalization",
4108
+
"unicode-properties",
4109
+
]
4110
+
4111
+
[[package]]
4112
+
name = "strsim"
4113
+
version = "0.11.1"
4114
+
source = "registry+https://github.com/rust-lang/crates.io-index"
4115
+
checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f"
4116
+
4117
+
[[package]]
4118
+
name = "subtle"
4119
+
version = "2.6.1"
4120
+
source = "registry+https://github.com/rust-lang/crates.io-index"
4121
+
checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292"
4122
+
4123
+
[[package]]
4124
+
name = "syn"
4125
+
version = "1.0.109"
4126
+
source = "registry+https://github.com/rust-lang/crates.io-index"
4127
+
checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237"
4128
+
dependencies = [
4129
+
"proc-macro2",
4130
+
"quote",
4131
+
"unicode-ident",
4132
+
]
4133
+
4134
+
[[package]]
4135
+
name = "syn"
4136
+
version = "2.0.104"
4137
+
source = "registry+https://github.com/rust-lang/crates.io-index"
4138
+
checksum = "17b6f705963418cdb9927482fa304bc562ece2fdd4f616084c50b7023b435a40"
4139
+
dependencies = [
4140
+
"proc-macro2",
4141
+
"quote",
4142
+
"unicode-ident",
4143
+
]
4144
+
4145
+
[[package]]
4146
+
name = "sync_wrapper"
4147
+
version = "1.0.2"
4148
+
source = "registry+https://github.com/rust-lang/crates.io-index"
4149
+
checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263"
4150
+
dependencies = [
4151
+
"futures-core",
4152
+
]
4153
+
4154
+
[[package]]
4155
+
name = "synstructure"
4156
+
version = "0.13.2"
4157
+
source = "registry+https://github.com/rust-lang/crates.io-index"
4158
+
checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2"
4159
+
dependencies = [
4160
+
"proc-macro2",
4161
+
"quote",
4162
+
"syn 2.0.104",
4163
+
]
4164
+
4165
+
[[package]]
4166
+
name = "syntect"
4167
+
version = "5.2.0"
4168
+
source = "registry+https://github.com/rust-lang/crates.io-index"
4169
+
checksum = "874dcfa363995604333cf947ae9f751ca3af4522c60886774c4963943b4746b1"
4170
+
dependencies = [
4171
+
"bincode",
4172
+
"bitflags 1.3.2",
4173
+
"fancy-regex",
4174
+
"flate2",
4175
+
"fnv",
4176
+
"once_cell",
4177
+
"onig",
4178
+
"plist",
4179
+
"regex-syntax 0.8.5",
4180
+
"serde",
4181
+
"serde_derive",
4182
+
"serde_json",
4183
+
"thiserror 1.0.69",
4184
+
"walkdir",
4185
+
"yaml-rust",
4186
+
]
4187
+
4188
+
[[package]]
4189
+
name = "system-configuration"
4190
+
version = "0.6.1"
4191
+
source = "registry+https://github.com/rust-lang/crates.io-index"
4192
+
checksum = "3c879d448e9d986b661742763247d3693ed13609438cf3d006f51f5368a5ba6b"
4193
+
dependencies = [
4194
+
"bitflags 2.9.1",
4195
+
"core-foundation 0.9.4",
4196
+
"system-configuration-sys",
4197
+
]
4198
+
4199
+
[[package]]
4200
+
name = "system-configuration-sys"
4201
+
version = "0.6.0"
4202
+
source = "registry+https://github.com/rust-lang/crates.io-index"
4203
+
checksum = "8e1d1b10ced5ca923a1fcb8d03e96b8d3268065d724548c0211415ff6ac6bac4"
4204
+
dependencies = [
4205
+
"core-foundation-sys",
4206
+
"libc",
4207
+
]
4208
+
4209
+
[[package]]
4210
+
name = "system-deps"
4211
+
version = "6.2.2"
4212
+
source = "registry+https://github.com/rust-lang/crates.io-index"
4213
+
checksum = "a3e535eb8dded36d55ec13eddacd30dec501792ff23a0b1682c38601b8cf2349"
4214
+
dependencies = [
4215
+
"cfg-expr",
4216
+
"heck",
4217
+
"pkg-config",
4218
+
"toml",
4219
+
"version-compare",
4220
+
]
4221
+
4222
+
[[package]]
4223
+
name = "tagptr"
4224
+
version = "0.2.0"
4225
+
source = "registry+https://github.com/rust-lang/crates.io-index"
4226
+
checksum = "7b2093cf4c8eb1e67749a6762251bc9cd836b6fc171623bd0a9d324d37af2417"
4227
+
4228
+
[[package]]
4229
+
name = "target-lexicon"
4230
+
version = "0.12.16"
4231
+
source = "registry+https://github.com/rust-lang/crates.io-index"
4232
+
checksum = "61c41af27dd6d1e27b1b16b489db798443478cef1f06a660c96db617ba5de3b1"
4233
+
4234
+
[[package]]
4235
+
name = "tempfile"
4236
+
version = "3.20.0"
4237
+
source = "registry+https://github.com/rust-lang/crates.io-index"
4238
+
checksum = "e8a64e3985349f2441a1a9ef0b853f869006c3855f2cda6862a94d26ebb9d6a1"
4239
+
dependencies = [
4240
+
"fastrand",
4241
+
"getrandom 0.3.3",
4242
+
"once_cell",
4243
+
"rustix",
4244
+
"windows-sys 0.59.0",
4245
+
]
4246
+
4247
+
[[package]]
4248
+
name = "terminal_size"
4249
+
version = "0.4.2"
4250
+
source = "registry+https://github.com/rust-lang/crates.io-index"
4251
+
checksum = "45c6481c4829e4cc63825e62c49186a34538b7b2750b73b266581ffb612fb5ed"
4252
+
dependencies = [
4253
+
"rustix",
4254
+
"windows-sys 0.59.0",
4255
+
]
4256
+
4257
+
[[package]]
4258
+
name = "thiserror"
4259
+
version = "1.0.69"
4260
+
source = "registry+https://github.com/rust-lang/crates.io-index"
4261
+
checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52"
4262
+
dependencies = [
4263
+
"thiserror-impl 1.0.69",
4264
+
]
4265
+
4266
+
[[package]]
4267
+
name = "thiserror"
4268
+
version = "2.0.12"
4269
+
source = "registry+https://github.com/rust-lang/crates.io-index"
4270
+
checksum = "567b8a2dae586314f7be2a752ec7474332959c6460e02bde30d702a66d488708"
4271
+
dependencies = [
4272
+
"thiserror-impl 2.0.12",
4273
+
]
4274
+
4275
+
[[package]]
4276
+
name = "thiserror-impl"
4277
+
version = "1.0.69"
4278
+
source = "registry+https://github.com/rust-lang/crates.io-index"
4279
+
checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1"
4280
+
dependencies = [
4281
+
"proc-macro2",
4282
+
"quote",
4283
+
"syn 2.0.104",
4284
+
]
4285
+
4286
+
[[package]]
4287
+
name = "thiserror-impl"
4288
+
version = "2.0.12"
4289
+
source = "registry+https://github.com/rust-lang/crates.io-index"
4290
+
checksum = "7f7cf42b4507d8ea322120659672cf1b9dbb93f8f2d4ecfd6e51350ff5b17a1d"
4291
+
dependencies = [
4292
+
"proc-macro2",
4293
+
"quote",
4294
+
"syn 2.0.104",
4295
+
]
4296
+
4297
+
[[package]]
4298
+
name = "thread_local"
4299
+
version = "1.1.9"
4300
+
source = "registry+https://github.com/rust-lang/crates.io-index"
4301
+
checksum = "f60246a4944f24f6e018aa17cdeffb7818b76356965d03b07d6a9886e8962185"
4302
+
dependencies = [
4303
+
"cfg-if",
4304
+
]
4305
+
4306
+
[[package]]
4307
+
name = "tiff"
4308
+
version = "0.9.1"
4309
+
source = "registry+https://github.com/rust-lang/crates.io-index"
4310
+
checksum = "ba1310fcea54c6a9a4fd1aad794ecc02c31682f6bfbecdf460bf19533eed1e3e"
4311
+
dependencies = [
4312
+
"flate2",
4313
+
"jpeg-decoder",
4314
+
"weezl",
4315
+
]
4316
+
4317
+
[[package]]
4318
+
name = "time"
4319
+
version = "0.3.41"
4320
+
source = "registry+https://github.com/rust-lang/crates.io-index"
4321
+
checksum = "8a7619e19bc266e0f9c5e6686659d394bc57973859340060a69221e57dbc0c40"
4322
+
dependencies = [
4323
+
"deranged",
4324
+
"itoa",
4325
+
"num-conv",
4326
+
"powerfmt",
4327
+
"serde",
4328
+
"time-core",
4329
+
"time-macros",
4330
+
]
4331
+
4332
+
[[package]]
4333
+
name = "time-core"
4334
+
version = "0.1.4"
4335
+
source = "registry+https://github.com/rust-lang/crates.io-index"
4336
+
checksum = "c9e9a38711f559d9e3ce1cdb06dd7c5b8ea546bc90052da6d06bb76da74bb07c"
4337
+
4338
+
[[package]]
4339
+
name = "time-macros"
4340
+
version = "0.2.22"
4341
+
source = "registry+https://github.com/rust-lang/crates.io-index"
4342
+
checksum = "3526739392ec93fd8b359c8e98514cb3e8e021beb4e5f597b00a0221f8ed8a49"
4343
+
dependencies = [
4344
+
"num-conv",
4345
+
"time-core",
4346
+
]
4347
+
4348
+
[[package]]
4349
+
name = "tinystr"
4350
+
version = "0.8.1"
4351
+
source = "registry+https://github.com/rust-lang/crates.io-index"
4352
+
checksum = "5d4f6d1145dcb577acf783d4e601bc1d76a13337bb54e6233add580b07344c8b"
4353
+
dependencies = [
4354
+
"displaydoc",
4355
+
"zerovec",
4356
+
]
4357
+
4358
+
[[package]]
4359
+
name = "tinyvec"
4360
+
version = "1.9.0"
4361
+
source = "registry+https://github.com/rust-lang/crates.io-index"
4362
+
checksum = "09b3661f17e86524eccd4371ab0429194e0d7c008abb45f7a7495b1719463c71"
4363
+
dependencies = [
4364
+
"tinyvec_macros",
4365
+
]
4366
+
4367
+
[[package]]
4368
+
name = "tinyvec_macros"
4369
+
version = "0.1.1"
4370
+
source = "registry+https://github.com/rust-lang/crates.io-index"
4371
+
checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20"
4372
+
4373
+
[[package]]
4374
+
name = "tokio"
4375
+
version = "1.46.1"
4376
+
source = "registry+https://github.com/rust-lang/crates.io-index"
4377
+
checksum = "0cc3a2344dafbe23a245241fe8b09735b521110d30fcefbbd5feb1797ca35d17"
4378
+
dependencies = [
4379
+
"backtrace",
4380
+
"bytes",
4381
+
"io-uring",
4382
+
"libc",
4383
+
"mio",
4384
+
"parking_lot",
4385
+
"pin-project-lite",
4386
+
"signal-hook-registry",
4387
+
"slab",
4388
+
"socket2",
4389
+
"tokio-macros",
4390
+
"windows-sys 0.52.0",
4391
+
]
4392
+
4393
+
[[package]]
4394
+
name = "tokio-macros"
4395
+
version = "2.5.0"
4396
+
source = "registry+https://github.com/rust-lang/crates.io-index"
4397
+
checksum = "6e06d43f1345a3bcd39f6a56dbb7dcab2ba47e68e8ac134855e7e2bdbaf8cab8"
4398
+
dependencies = [
4399
+
"proc-macro2",
4400
+
"quote",
4401
+
"syn 2.0.104",
4402
+
]
4403
+
4404
+
[[package]]
4405
+
name = "tokio-native-tls"
4406
+
version = "0.3.1"
4407
+
source = "registry+https://github.com/rust-lang/crates.io-index"
4408
+
checksum = "bbae76ab933c85776efabc971569dd6119c580d8f5d448769dec1764bf796ef2"
4409
+
dependencies = [
4410
+
"native-tls",
4411
+
"tokio",
4412
+
]
4413
+
4414
+
[[package]]
4415
+
name = "tokio-rustls"
4416
+
version = "0.26.2"
4417
+
source = "registry+https://github.com/rust-lang/crates.io-index"
4418
+
checksum = "8e727b36a1a0e8b74c376ac2211e40c2c8af09fb4013c60d910495810f008e9b"
4419
+
dependencies = [
4420
+
"rustls",
4421
+
"tokio",
4422
+
]
4423
+
4424
+
[[package]]
4425
+
name = "tokio-stream"
4426
+
version = "0.1.17"
4427
+
source = "registry+https://github.com/rust-lang/crates.io-index"
4428
+
checksum = "eca58d7bba4a75707817a2c44174253f9236b2d5fbd055602e9d5c07c139a047"
4429
+
dependencies = [
4430
+
"futures-core",
4431
+
"pin-project-lite",
4432
+
"tokio",
4433
+
]
4434
+
4435
+
[[package]]
4436
+
name = "tokio-util"
4437
+
version = "0.7.15"
4438
+
source = "registry+https://github.com/rust-lang/crates.io-index"
4439
+
checksum = "66a539a9ad6d5d281510d5bd368c973d636c02dbf8a67300bfb6b950696ad7df"
4440
+
dependencies = [
4441
+
"bytes",
4442
+
"futures-core",
4443
+
"futures-sink",
4444
+
"futures-util",
4445
+
"hashbrown 0.15.4",
4446
+
"pin-project-lite",
4447
+
"tokio",
4448
+
]
4449
+
4450
+
[[package]]
4451
+
name = "tokio-websockets"
4452
+
version = "0.11.4"
4453
+
source = "registry+https://github.com/rust-lang/crates.io-index"
4454
+
checksum = "9fcaf159b4e7a376b05b5bfd77bfd38f3324f5fce751b4213bfc7eaa47affb4e"
4455
+
dependencies = [
4456
+
"base64",
4457
+
"bytes",
4458
+
"futures-core",
4459
+
"futures-sink",
4460
+
"http",
4461
+
"httparse",
4462
+
"rand 0.9.1",
4463
+
"ring",
4464
+
"rustls-native-certs",
4465
+
"rustls-pki-types",
4466
+
"simdutf8",
4467
+
"tokio",
4468
+
"tokio-rustls",
4469
+
"tokio-util",
4470
+
]
4471
+
4472
+
[[package]]
4473
+
name = "toml"
4474
+
version = "0.8.23"
4475
+
source = "registry+https://github.com/rust-lang/crates.io-index"
4476
+
checksum = "dc1beb996b9d83529a9e75c17a1686767d148d70663143c7854d8b4a09ced362"
4477
+
dependencies = [
4478
+
"serde",
4479
+
"serde_spanned",
4480
+
"toml_datetime",
4481
+
"toml_edit",
4482
+
]
4483
+
4484
+
[[package]]
4485
+
name = "toml_datetime"
4486
+
version = "0.6.11"
4487
+
source = "registry+https://github.com/rust-lang/crates.io-index"
4488
+
checksum = "22cddaf88f4fbc13c51aebbf5f8eceb5c7c5a9da2ac40a13519eb5b0a0e8f11c"
4489
+
dependencies = [
4490
+
"serde",
4491
+
]
4492
+
4493
+
[[package]]
4494
+
name = "toml_edit"
4495
+
version = "0.22.27"
4496
+
source = "registry+https://github.com/rust-lang/crates.io-index"
4497
+
checksum = "41fe8c660ae4257887cf66394862d21dbca4a6ddd26f04a3560410406a2f819a"
4498
+
dependencies = [
4499
+
"indexmap",
4500
+
"serde",
4501
+
"serde_spanned",
4502
+
"toml_datetime",
4503
+
"winnow 0.7.11",
4504
+
]
4505
+
4506
+
[[package]]
4507
+
name = "tower"
4508
+
version = "0.5.2"
4509
+
source = "registry+https://github.com/rust-lang/crates.io-index"
4510
+
checksum = "d039ad9159c98b70ecfd540b2573b97f7f52c3e8d9f8ad57a24b916a536975f9"
4511
+
dependencies = [
4512
+
"futures-core",
4513
+
"futures-util",
4514
+
"pin-project-lite",
4515
+
"sync_wrapper",
4516
+
"tokio",
4517
+
"tower-layer",
4518
+
"tower-service",
4519
+
"tracing",
4520
+
]
4521
+
4522
+
[[package]]
4523
+
name = "tower-http"
4524
+
version = "0.5.2"
4525
+
source = "registry+https://github.com/rust-lang/crates.io-index"
4526
+
checksum = "1e9cd434a998747dd2c4276bc96ee2e0c7a2eadf3cae88e52be55a05fa9053f5"
4527
+
dependencies = [
4528
+
"bitflags 2.9.1",
4529
+
"bytes",
4530
+
"futures-util",
4531
+
"http",
4532
+
"http-body",
4533
+
"http-body-util",
4534
+
"http-range-header",
4535
+
"httpdate",
4536
+
"mime",
4537
+
"mime_guess",
4538
+
"percent-encoding",
4539
+
"pin-project-lite",
4540
+
"tokio",
4541
+
"tokio-util",
4542
+
"tower-layer",
4543
+
"tower-service",
4544
+
"tracing",
4545
+
]
4546
+
4547
+
[[package]]
4548
+
name = "tower-http"
4549
+
version = "0.6.6"
4550
+
source = "registry+https://github.com/rust-lang/crates.io-index"
4551
+
checksum = "adc82fd73de2a9722ac5da747f12383d2bfdb93591ee6c58486e0097890f05f2"
4552
+
dependencies = [
4553
+
"bitflags 2.9.1",
4554
+
"bytes",
4555
+
"futures-util",
4556
+
"http",
4557
+
"http-body",
4558
+
"iri-string",
4559
+
"pin-project-lite",
4560
+
"tower",
4561
+
"tower-layer",
4562
+
"tower-service",
4563
+
]
4564
+
4565
+
[[package]]
4566
+
name = "tower-layer"
4567
+
version = "0.3.3"
4568
+
source = "registry+https://github.com/rust-lang/crates.io-index"
4569
+
checksum = "121c2a6cda46980bb0fcd1647ffaf6cd3fc79a013de288782836f6df9c48780e"
4570
+
4571
+
[[package]]
4572
+
name = "tower-service"
4573
+
version = "0.3.3"
4574
+
source = "registry+https://github.com/rust-lang/crates.io-index"
4575
+
checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3"
4576
+
4577
+
[[package]]
4578
+
name = "tracing"
4579
+
version = "0.1.41"
4580
+
source = "registry+https://github.com/rust-lang/crates.io-index"
4581
+
checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0"
4582
+
dependencies = [
4583
+
"log",
4584
+
"pin-project-lite",
4585
+
"tracing-attributes",
4586
+
"tracing-core",
4587
+
]
4588
+
4589
+
[[package]]
4590
+
name = "tracing-attributes"
4591
+
version = "0.1.30"
4592
+
source = "registry+https://github.com/rust-lang/crates.io-index"
4593
+
checksum = "81383ab64e72a7a8b8e13130c49e3dab29def6d0c7d76a03087b3cf71c5c6903"
4594
+
dependencies = [
4595
+
"proc-macro2",
4596
+
"quote",
4597
+
"syn 2.0.104",
4598
+
]
4599
+
4600
+
[[package]]
4601
+
name = "tracing-core"
4602
+
version = "0.1.34"
4603
+
source = "registry+https://github.com/rust-lang/crates.io-index"
4604
+
checksum = "b9d12581f227e93f094d3af2ae690a574abb8a2b9b7a96e7cfe9647b2b617678"
4605
+
dependencies = [
4606
+
"once_cell",
4607
+
"valuable",
4608
+
]
4609
+
4610
+
[[package]]
4611
+
name = "tracing-log"
4612
+
version = "0.2.0"
4613
+
source = "registry+https://github.com/rust-lang/crates.io-index"
4614
+
checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3"
4615
+
dependencies = [
4616
+
"log",
4617
+
"once_cell",
4618
+
"tracing-core",
4619
+
]
4620
+
4621
+
[[package]]
4622
+
name = "tracing-subscriber"
4623
+
version = "0.3.19"
4624
+
source = "registry+https://github.com/rust-lang/crates.io-index"
4625
+
checksum = "e8189decb5ac0fa7bc8b96b7cb9b2701d60d48805aca84a238004d665fcc4008"
4626
+
dependencies = [
4627
+
"matchers",
4628
+
"nu-ansi-term",
4629
+
"once_cell",
4630
+
"regex",
4631
+
"sharded-slab",
4632
+
"smallvec",
4633
+
"thread_local",
4634
+
"tracing",
4635
+
"tracing-core",
4636
+
"tracing-log",
4637
+
]
4638
+
4639
+
[[package]]
4640
+
name = "try-lock"
4641
+
version = "0.2.5"
4642
+
source = "registry+https://github.com/rust-lang/crates.io-index"
4643
+
checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b"
4644
+
4645
+
[[package]]
4646
+
name = "typed-arena"
4647
+
version = "2.0.2"
4648
+
source = "registry+https://github.com/rust-lang/crates.io-index"
4649
+
checksum = "6af6ae20167a9ece4bcb41af5b80f8a1f1df981f6391189ce00fd257af04126a"
4650
+
4651
+
[[package]]
4652
+
name = "typenum"
4653
+
version = "1.18.0"
4654
+
source = "registry+https://github.com/rust-lang/crates.io-index"
4655
+
checksum = "1dccffe3ce07af9386bfd29e80c0ab1a8205a2fc34e4bcd40364df902cfa8f3f"
4656
+
4657
+
[[package]]
4658
+
name = "ulid"
4659
+
version = "1.2.1"
4660
+
source = "registry+https://github.com/rust-lang/crates.io-index"
4661
+
checksum = "470dbf6591da1b39d43c14523b2b469c86879a53e8b758c8e090a470fe7b1fbe"
4662
+
dependencies = [
4663
+
"rand 0.9.1",
4664
+
"web-time",
4665
+
]
4666
+
4667
+
[[package]]
4668
+
name = "unicase"
4669
+
version = "2.8.1"
4670
+
source = "registry+https://github.com/rust-lang/crates.io-index"
4671
+
checksum = "75b844d17643ee918803943289730bec8aac480150456169e647ed0b576ba539"
4672
+
4673
+
[[package]]
4674
+
name = "unicode-bidi"
4675
+
version = "0.3.18"
4676
+
source = "registry+https://github.com/rust-lang/crates.io-index"
4677
+
checksum = "5c1cb5db39152898a79168971543b1cb5020dff7fe43c8dc468b0885f5e29df5"
4678
+
4679
+
[[package]]
4680
+
name = "unicode-ident"
4681
+
version = "1.0.18"
4682
+
source = "registry+https://github.com/rust-lang/crates.io-index"
4683
+
checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512"
4684
+
4685
+
[[package]]
4686
+
name = "unicode-normalization"
4687
+
version = "0.1.24"
4688
+
source = "registry+https://github.com/rust-lang/crates.io-index"
4689
+
checksum = "5033c97c4262335cded6d6fc3e5c18ab755e1a3dc96376350f3d8e9f009ad956"
4690
+
dependencies = [
4691
+
"tinyvec",
4692
+
]
4693
+
4694
+
[[package]]
4695
+
name = "unicode-properties"
4696
+
version = "0.1.3"
4697
+
source = "registry+https://github.com/rust-lang/crates.io-index"
4698
+
checksum = "e70f2a8b45122e719eb623c01822704c4e0907e7e426a05927e1a1cfff5b75d0"
4699
+
4700
+
[[package]]
4701
+
name = "unicode_categories"
4702
+
version = "0.1.1"
4703
+
source = "registry+https://github.com/rust-lang/crates.io-index"
4704
+
checksum = "39ec24b3121d976906ece63c9daad25b85969647682eee313cb5779fdd69e14e"
4705
+
4706
+
[[package]]
4707
+
name = "unidecode"
4708
+
version = "0.3.0"
4709
+
source = "registry+https://github.com/rust-lang/crates.io-index"
4710
+
checksum = "402bb19d8e03f1d1a7450e2bd613980869438e0666331be3e073089124aa1adc"
4711
+
4712
+
[[package]]
4713
+
name = "unsigned-varint"
4714
+
version = "0.8.0"
4715
+
source = "registry+https://github.com/rust-lang/crates.io-index"
4716
+
checksum = "eb066959b24b5196ae73cb057f45598450d2c5f71460e98c49b738086eff9c06"
4717
+
4718
+
[[package]]
4719
+
name = "untrusted"
4720
+
version = "0.9.0"
4721
+
source = "registry+https://github.com/rust-lang/crates.io-index"
4722
+
checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1"
4723
+
4724
+
[[package]]
4725
+
name = "url"
4726
+
version = "2.5.4"
4727
+
source = "registry+https://github.com/rust-lang/crates.io-index"
4728
+
checksum = "32f8b686cadd1473f4bd0117a5d28d36b1ade384ea9b5069a1c40aefed7fda60"
4729
+
dependencies = [
4730
+
"form_urlencoded",
4731
+
"idna",
4732
+
"percent-encoding",
4733
+
]
4734
+
4735
+
[[package]]
4736
+
name = "urlencoding"
4737
+
version = "2.1.3"
4738
+
source = "registry+https://github.com/rust-lang/crates.io-index"
4739
+
checksum = "daf8dba3b7eb870caf1ddeed7bc9d2a049f3cfdfae7cb521b087cc33ae4c49da"
4740
+
4741
+
[[package]]
4742
+
name = "utf8_iter"
4743
+
version = "1.0.4"
4744
+
source = "registry+https://github.com/rust-lang/crates.io-index"
4745
+
checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be"
4746
+
4747
+
[[package]]
4748
+
name = "utf8parse"
4749
+
version = "0.2.2"
4750
+
source = "registry+https://github.com/rust-lang/crates.io-index"
4751
+
checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821"
4752
+
4753
+
[[package]]
4754
+
name = "uuid"
4755
+
version = "1.17.0"
4756
+
source = "registry+https://github.com/rust-lang/crates.io-index"
4757
+
checksum = "3cf4199d1e5d15ddd86a694e4d0dffa9c323ce759fea589f00fef9d81cc1931d"
4758
+
dependencies = [
4759
+
"getrandom 0.3.3",
4760
+
"js-sys",
4761
+
"wasm-bindgen",
4762
+
]
4763
+
4764
+
[[package]]
4765
+
name = "v_frame"
4766
+
version = "0.3.9"
4767
+
source = "registry+https://github.com/rust-lang/crates.io-index"
4768
+
checksum = "666b7727c8875d6ab5db9533418d7c764233ac9c0cff1d469aec8fa127597be2"
4769
+
dependencies = [
4770
+
"aligned-vec",
4771
+
"num-traits",
4772
+
"wasm-bindgen",
4773
+
]
4774
+
4775
+
[[package]]
4776
+
name = "valuable"
4777
+
version = "0.1.1"
4778
+
source = "registry+https://github.com/rust-lang/crates.io-index"
4779
+
checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65"
4780
+
4781
+
[[package]]
4782
+
name = "vcpkg"
4783
+
version = "0.2.15"
4784
+
source = "registry+https://github.com/rust-lang/crates.io-index"
4785
+
checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426"
4786
+
4787
+
[[package]]
4788
+
name = "version-compare"
4789
+
version = "0.2.0"
4790
+
source = "registry+https://github.com/rust-lang/crates.io-index"
4791
+
checksum = "852e951cb7832cb45cb1169900d19760cfa39b82bc0ea9c0e5a14ae88411c98b"
4792
+
4793
+
[[package]]
4794
+
name = "version_check"
4795
+
version = "0.9.5"
4796
+
source = "registry+https://github.com/rust-lang/crates.io-index"
4797
+
checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a"
4798
+
4799
+
[[package]]
4800
+
name = "walkdir"
4801
+
version = "2.5.0"
4802
+
source = "registry+https://github.com/rust-lang/crates.io-index"
4803
+
checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b"
4804
+
dependencies = [
4805
+
"same-file",
4806
+
"winapi-util",
4807
+
]
4808
+
4809
+
[[package]]
4810
+
name = "want"
4811
+
version = "0.3.1"
4812
+
source = "registry+https://github.com/rust-lang/crates.io-index"
4813
+
checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e"
4814
+
dependencies = [
4815
+
"try-lock",
4816
+
]
4817
+
4818
+
[[package]]
4819
+
name = "wasi"
4820
+
version = "0.11.1+wasi-snapshot-preview1"
4821
+
source = "registry+https://github.com/rust-lang/crates.io-index"
4822
+
checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b"
4823
+
4824
+
[[package]]
4825
+
name = "wasi"
4826
+
version = "0.14.2+wasi-0.2.4"
4827
+
source = "registry+https://github.com/rust-lang/crates.io-index"
4828
+
checksum = "9683f9a5a998d873c0d21fcbe3c083009670149a8fab228644b8bd36b2c48cb3"
4829
+
dependencies = [
4830
+
"wit-bindgen-rt",
4831
+
]
4832
+
4833
+
[[package]]
4834
+
name = "wasite"
4835
+
version = "0.1.0"
4836
+
source = "registry+https://github.com/rust-lang/crates.io-index"
4837
+
checksum = "b8dad83b4f25e74f184f64c43b150b91efe7647395b42289f38e50566d82855b"
4838
+
4839
+
[[package]]
4840
+
name = "wasm-bindgen"
4841
+
version = "0.2.100"
4842
+
source = "registry+https://github.com/rust-lang/crates.io-index"
4843
+
checksum = "1edc8929d7499fc4e8f0be2262a241556cfc54a0bea223790e71446f2aab1ef5"
4844
+
dependencies = [
4845
+
"cfg-if",
4846
+
"once_cell",
4847
+
"rustversion",
4848
+
"wasm-bindgen-macro",
4849
+
]
4850
+
4851
+
[[package]]
4852
+
name = "wasm-bindgen-backend"
4853
+
version = "0.2.100"
4854
+
source = "registry+https://github.com/rust-lang/crates.io-index"
4855
+
checksum = "2f0a0651a5c2bc21487bde11ee802ccaf4c51935d0d3d42a6101f98161700bc6"
4856
+
dependencies = [
4857
+
"bumpalo",
4858
+
"log",
4859
+
"proc-macro2",
4860
+
"quote",
4861
+
"syn 2.0.104",
4862
+
"wasm-bindgen-shared",
4863
+
]
4864
+
4865
+
[[package]]
4866
+
name = "wasm-bindgen-futures"
4867
+
version = "0.4.50"
4868
+
source = "registry+https://github.com/rust-lang/crates.io-index"
4869
+
checksum = "555d470ec0bc3bb57890405e5d4322cc9ea83cebb085523ced7be4144dac1e61"
4870
+
dependencies = [
4871
+
"cfg-if",
4872
+
"js-sys",
4873
+
"once_cell",
4874
+
"wasm-bindgen",
4875
+
"web-sys",
4876
+
]
4877
+
4878
+
[[package]]
4879
+
name = "wasm-bindgen-macro"
4880
+
version = "0.2.100"
4881
+
source = "registry+https://github.com/rust-lang/crates.io-index"
4882
+
checksum = "7fe63fc6d09ed3792bd0897b314f53de8e16568c2b3f7982f468c0bf9bd0b407"
4883
+
dependencies = [
4884
+
"quote",
4885
+
"wasm-bindgen-macro-support",
4886
+
]
4887
+
4888
+
[[package]]
4889
+
name = "wasm-bindgen-macro-support"
4890
+
version = "0.2.100"
4891
+
source = "registry+https://github.com/rust-lang/crates.io-index"
4892
+
checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de"
4893
+
dependencies = [
4894
+
"proc-macro2",
4895
+
"quote",
4896
+
"syn 2.0.104",
4897
+
"wasm-bindgen-backend",
4898
+
"wasm-bindgen-shared",
4899
+
]
4900
+
4901
+
[[package]]
4902
+
name = "wasm-bindgen-shared"
4903
+
version = "0.2.100"
4904
+
source = "registry+https://github.com/rust-lang/crates.io-index"
4905
+
checksum = "1a05d73b933a847d6cccdda8f838a22ff101ad9bf93e33684f39c1f5f0eece3d"
4906
+
dependencies = [
4907
+
"unicode-ident",
4908
+
]
4909
+
4910
+
[[package]]
4911
+
name = "wasm-streams"
4912
+
version = "0.4.2"
4913
+
source = "registry+https://github.com/rust-lang/crates.io-index"
4914
+
checksum = "15053d8d85c7eccdbefef60f06769760a563c7f0a9d6902a13d35c7800b0ad65"
4915
+
dependencies = [
4916
+
"futures-util",
4917
+
"js-sys",
4918
+
"wasm-bindgen",
4919
+
"wasm-bindgen-futures",
4920
+
"web-sys",
4921
+
]
4922
+
4923
+
[[package]]
4924
+
name = "web-sys"
4925
+
version = "0.3.77"
4926
+
source = "registry+https://github.com/rust-lang/crates.io-index"
4927
+
checksum = "33b6dd2ef9186f1f2072e409e99cd22a975331a6b3591b12c764e0e55c60d5d2"
4928
+
dependencies = [
4929
+
"js-sys",
4930
+
"wasm-bindgen",
4931
+
]
4932
+
4933
+
[[package]]
4934
+
name = "web-time"
4935
+
version = "1.1.0"
4936
+
source = "registry+https://github.com/rust-lang/crates.io-index"
4937
+
checksum = "5a6580f308b1fad9207618087a65c04e7a10bc77e02c8e84e9b00dd4b12fa0bb"
4938
+
dependencies = [
4939
+
"js-sys",
4940
+
"wasm-bindgen",
4941
+
]
4942
+
4943
+
[[package]]
4944
+
name = "webpki-roots"
4945
+
version = "0.26.11"
4946
+
source = "registry+https://github.com/rust-lang/crates.io-index"
4947
+
checksum = "521bc38abb08001b01866da9f51eb7c5d647a19260e00054a8c7fd5f9e57f7a9"
4948
+
dependencies = [
4949
+
"webpki-roots 1.0.1",
4950
+
]
4951
+
4952
+
[[package]]
4953
+
name = "webpki-roots"
4954
+
version = "1.0.1"
4955
+
source = "registry+https://github.com/rust-lang/crates.io-index"
4956
+
checksum = "8782dd5a41a24eed3a4f40b606249b3e236ca61adf1f25ea4d45c73de122b502"
4957
+
dependencies = [
4958
+
"rustls-pki-types",
4959
+
]
4960
+
4961
+
[[package]]
4962
+
name = "weezl"
4963
+
version = "0.1.10"
4964
+
source = "registry+https://github.com/rust-lang/crates.io-index"
4965
+
checksum = "a751b3277700db47d3e574514de2eced5e54dc8a5436a3bf7a0b248b2cee16f3"
4966
+
4967
+
[[package]]
4968
+
name = "whoami"
4969
+
version = "1.6.0"
4970
+
source = "registry+https://github.com/rust-lang/crates.io-index"
4971
+
checksum = "6994d13118ab492c3c80c1f81928718159254c53c472bf9ce36f8dae4add02a7"
4972
+
dependencies = [
4973
+
"redox_syscall",
4974
+
"wasite",
4975
+
]
4976
+
4977
+
[[package]]
4978
+
name = "widestring"
4979
+
version = "1.2.0"
4980
+
source = "registry+https://github.com/rust-lang/crates.io-index"
4981
+
checksum = "dd7cf3379ca1aac9eea11fba24fd7e315d621f8dfe35c8d7d2be8b793726e07d"
4982
+
4983
+
[[package]]
4984
+
name = "winapi"
4985
+
version = "0.3.9"
4986
+
source = "registry+https://github.com/rust-lang/crates.io-index"
4987
+
checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
4988
+
dependencies = [
4989
+
"winapi-i686-pc-windows-gnu",
4990
+
"winapi-x86_64-pc-windows-gnu",
4991
+
]
4992
+
4993
+
[[package]]
4994
+
name = "winapi-i686-pc-windows-gnu"
4995
+
version = "0.4.0"
4996
+
source = "registry+https://github.com/rust-lang/crates.io-index"
4997
+
checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
4998
+
4999
+
[[package]]
5000
+
name = "winapi-util"
5001
+
version = "0.1.9"
5002
+
source = "registry+https://github.com/rust-lang/crates.io-index"
5003
+
checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb"
5004
+
dependencies = [
5005
+
"windows-sys 0.59.0",
5006
+
]
5007
+
5008
+
[[package]]
5009
+
name = "winapi-x86_64-pc-windows-gnu"
5010
+
version = "0.4.0"
5011
+
source = "registry+https://github.com/rust-lang/crates.io-index"
5012
+
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
5013
+
5014
+
[[package]]
5015
+
name = "windows"
5016
+
version = "0.61.3"
5017
+
source = "registry+https://github.com/rust-lang/crates.io-index"
5018
+
checksum = "9babd3a767a4c1aef6900409f85f5d53ce2544ccdfaa86dad48c91782c6d6893"
5019
+
dependencies = [
5020
+
"windows-collections",
5021
+
"windows-core",
5022
+
"windows-future",
5023
+
"windows-link",
5024
+
"windows-numerics",
5025
+
]
5026
+
5027
+
[[package]]
5028
+
name = "windows-collections"
5029
+
version = "0.2.0"
5030
+
source = "registry+https://github.com/rust-lang/crates.io-index"
5031
+
checksum = "3beeceb5e5cfd9eb1d76b381630e82c4241ccd0d27f1a39ed41b2760b255c5e8"
5032
+
dependencies = [
5033
+
"windows-core",
5034
+
]
5035
+
5036
+
[[package]]
5037
+
name = "windows-core"
5038
+
version = "0.61.2"
5039
+
source = "registry+https://github.com/rust-lang/crates.io-index"
5040
+
checksum = "c0fdd3ddb90610c7638aa2b3a3ab2904fb9e5cdbecc643ddb3647212781c4ae3"
5041
+
dependencies = [
5042
+
"windows-implement",
5043
+
"windows-interface",
5044
+
"windows-link",
5045
+
"windows-result",
5046
+
"windows-strings",
5047
+
]
5048
+
5049
+
[[package]]
5050
+
name = "windows-future"
5051
+
version = "0.2.1"
5052
+
source = "registry+https://github.com/rust-lang/crates.io-index"
5053
+
checksum = "fc6a41e98427b19fe4b73c550f060b59fa592d7d686537eebf9385621bfbad8e"
5054
+
dependencies = [
5055
+
"windows-core",
5056
+
"windows-link",
5057
+
"windows-threading",
5058
+
]
5059
+
5060
+
[[package]]
5061
+
name = "windows-implement"
5062
+
version = "0.60.0"
5063
+
source = "registry+https://github.com/rust-lang/crates.io-index"
5064
+
checksum = "a47fddd13af08290e67f4acabf4b459f647552718f683a7b415d290ac744a836"
5065
+
dependencies = [
5066
+
"proc-macro2",
5067
+
"quote",
5068
+
"syn 2.0.104",
5069
+
]
5070
+
5071
+
[[package]]
5072
+
name = "windows-interface"
5073
+
version = "0.59.1"
5074
+
source = "registry+https://github.com/rust-lang/crates.io-index"
5075
+
checksum = "bd9211b69f8dcdfa817bfd14bf1c97c9188afa36f4750130fcdf3f400eca9fa8"
5076
+
dependencies = [
5077
+
"proc-macro2",
5078
+
"quote",
5079
+
"syn 2.0.104",
5080
+
]
5081
+
5082
+
[[package]]
5083
+
name = "windows-link"
5084
+
version = "0.1.3"
5085
+
source = "registry+https://github.com/rust-lang/crates.io-index"
5086
+
checksum = "5e6ad25900d524eaabdbbb96d20b4311e1e7ae1699af4fb28c17ae66c80d798a"
5087
+
5088
+
[[package]]
5089
+
name = "windows-numerics"
5090
+
version = "0.2.0"
5091
+
source = "registry+https://github.com/rust-lang/crates.io-index"
5092
+
checksum = "9150af68066c4c5c07ddc0ce30421554771e528bde427614c61038bc2c92c2b1"
5093
+
dependencies = [
5094
+
"windows-core",
5095
+
"windows-link",
5096
+
]
5097
+
5098
+
[[package]]
5099
+
name = "windows-registry"
5100
+
version = "0.5.3"
5101
+
source = "registry+https://github.com/rust-lang/crates.io-index"
5102
+
checksum = "5b8a9ed28765efc97bbc954883f4e6796c33a06546ebafacbabee9696967499e"
5103
+
dependencies = [
5104
+
"windows-link",
5105
+
"windows-result",
5106
+
"windows-strings",
5107
+
]
5108
+
5109
+
[[package]]
5110
+
name = "windows-result"
5111
+
version = "0.3.4"
5112
+
source = "registry+https://github.com/rust-lang/crates.io-index"
5113
+
checksum = "56f42bd332cc6c8eac5af113fc0c1fd6a8fd2aa08a0119358686e5160d0586c6"
5114
+
dependencies = [
5115
+
"windows-link",
5116
+
]
5117
+
5118
+
[[package]]
5119
+
name = "windows-strings"
5120
+
version = "0.4.2"
5121
+
source = "registry+https://github.com/rust-lang/crates.io-index"
5122
+
checksum = "56e6c93f3a0c3b36176cb1327a4958a0353d5d166c2a35cb268ace15e91d3b57"
5123
+
dependencies = [
5124
+
"windows-link",
5125
+
]
5126
+
5127
+
[[package]]
5128
+
name = "windows-sys"
5129
+
version = "0.48.0"
5130
+
source = "registry+https://github.com/rust-lang/crates.io-index"
5131
+
checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9"
5132
+
dependencies = [
5133
+
"windows-targets 0.48.5",
5134
+
]
5135
+
5136
+
[[package]]
5137
+
name = "windows-sys"
5138
+
version = "0.52.0"
5139
+
source = "registry+https://github.com/rust-lang/crates.io-index"
5140
+
checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d"
5141
+
dependencies = [
5142
+
"windows-targets 0.52.6",
5143
+
]
5144
+
5145
+
[[package]]
5146
+
name = "windows-sys"
5147
+
version = "0.59.0"
5148
+
source = "registry+https://github.com/rust-lang/crates.io-index"
5149
+
checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b"
5150
+
dependencies = [
5151
+
"windows-targets 0.52.6",
5152
+
]
5153
+
5154
+
[[package]]
5155
+
name = "windows-sys"
5156
+
version = "0.60.2"
5157
+
source = "registry+https://github.com/rust-lang/crates.io-index"
5158
+
checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb"
5159
+
dependencies = [
5160
+
"windows-targets 0.53.2",
5161
+
]
5162
+
5163
+
[[package]]
5164
+
name = "windows-targets"
5165
+
version = "0.48.5"
5166
+
source = "registry+https://github.com/rust-lang/crates.io-index"
5167
+
checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c"
5168
+
dependencies = [
5169
+
"windows_aarch64_gnullvm 0.48.5",
5170
+
"windows_aarch64_msvc 0.48.5",
5171
+
"windows_i686_gnu 0.48.5",
5172
+
"windows_i686_msvc 0.48.5",
5173
+
"windows_x86_64_gnu 0.48.5",
5174
+
"windows_x86_64_gnullvm 0.48.5",
5175
+
"windows_x86_64_msvc 0.48.5",
5176
+
]
5177
+
5178
+
[[package]]
5179
+
name = "windows-targets"
5180
+
version = "0.52.6"
5181
+
source = "registry+https://github.com/rust-lang/crates.io-index"
5182
+
checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973"
5183
+
dependencies = [
5184
+
"windows_aarch64_gnullvm 0.52.6",
5185
+
"windows_aarch64_msvc 0.52.6",
5186
+
"windows_i686_gnu 0.52.6",
5187
+
"windows_i686_gnullvm 0.52.6",
5188
+
"windows_i686_msvc 0.52.6",
5189
+
"windows_x86_64_gnu 0.52.6",
5190
+
"windows_x86_64_gnullvm 0.52.6",
5191
+
"windows_x86_64_msvc 0.52.6",
5192
+
]
5193
+
5194
+
[[package]]
5195
+
name = "windows-targets"
5196
+
version = "0.53.2"
5197
+
source = "registry+https://github.com/rust-lang/crates.io-index"
5198
+
checksum = "c66f69fcc9ce11da9966ddb31a40968cad001c5bedeb5c2b82ede4253ab48aef"
5199
+
dependencies = [
5200
+
"windows_aarch64_gnullvm 0.53.0",
5201
+
"windows_aarch64_msvc 0.53.0",
5202
+
"windows_i686_gnu 0.53.0",
5203
+
"windows_i686_gnullvm 0.53.0",
5204
+
"windows_i686_msvc 0.53.0",
5205
+
"windows_x86_64_gnu 0.53.0",
5206
+
"windows_x86_64_gnullvm 0.53.0",
5207
+
"windows_x86_64_msvc 0.53.0",
5208
+
]
5209
+
5210
+
[[package]]
5211
+
name = "windows-threading"
5212
+
version = "0.1.0"
5213
+
source = "registry+https://github.com/rust-lang/crates.io-index"
5214
+
checksum = "b66463ad2e0ea3bbf808b7f1d371311c80e115c0b71d60efc142cafbcfb057a6"
5215
+
dependencies = [
5216
+
"windows-link",
5217
+
]
5218
+
5219
+
[[package]]
5220
+
name = "windows_aarch64_gnullvm"
5221
+
version = "0.48.5"
5222
+
source = "registry+https://github.com/rust-lang/crates.io-index"
5223
+
checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8"
5224
+
5225
+
[[package]]
5226
+
name = "windows_aarch64_gnullvm"
5227
+
version = "0.52.6"
5228
+
source = "registry+https://github.com/rust-lang/crates.io-index"
5229
+
checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3"
5230
+
5231
+
[[package]]
5232
+
name = "windows_aarch64_gnullvm"
5233
+
version = "0.53.0"
5234
+
source = "registry+https://github.com/rust-lang/crates.io-index"
5235
+
checksum = "86b8d5f90ddd19cb4a147a5fa63ca848db3df085e25fee3cc10b39b6eebae764"
5236
+
5237
+
[[package]]
5238
+
name = "windows_aarch64_msvc"
5239
+
version = "0.48.5"
5240
+
source = "registry+https://github.com/rust-lang/crates.io-index"
5241
+
checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc"
5242
+
5243
+
[[package]]
5244
+
name = "windows_aarch64_msvc"
5245
+
version = "0.52.6"
5246
+
source = "registry+https://github.com/rust-lang/crates.io-index"
5247
+
checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469"
5248
+
5249
+
[[package]]
5250
+
name = "windows_aarch64_msvc"
5251
+
version = "0.53.0"
5252
+
source = "registry+https://github.com/rust-lang/crates.io-index"
5253
+
checksum = "c7651a1f62a11b8cbd5e0d42526e55f2c99886c77e007179efff86c2b137e66c"
5254
+
5255
+
[[package]]
5256
+
name = "windows_i686_gnu"
5257
+
version = "0.48.5"
5258
+
source = "registry+https://github.com/rust-lang/crates.io-index"
5259
+
checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e"
5260
+
5261
+
[[package]]
5262
+
name = "windows_i686_gnu"
5263
+
version = "0.52.6"
5264
+
source = "registry+https://github.com/rust-lang/crates.io-index"
5265
+
checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b"
5266
+
5267
+
[[package]]
5268
+
name = "windows_i686_gnu"
5269
+
version = "0.53.0"
5270
+
source = "registry+https://github.com/rust-lang/crates.io-index"
5271
+
checksum = "c1dc67659d35f387f5f6c479dc4e28f1d4bb90ddd1a5d3da2e5d97b42d6272c3"
5272
+
5273
+
[[package]]
5274
+
name = "windows_i686_gnullvm"
5275
+
version = "0.52.6"
5276
+
source = "registry+https://github.com/rust-lang/crates.io-index"
5277
+
checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66"
5278
+
5279
+
[[package]]
5280
+
name = "windows_i686_gnullvm"
5281
+
version = "0.53.0"
5282
+
source = "registry+https://github.com/rust-lang/crates.io-index"
5283
+
checksum = "9ce6ccbdedbf6d6354471319e781c0dfef054c81fbc7cf83f338a4296c0cae11"
5284
+
5285
+
[[package]]
5286
+
name = "windows_i686_msvc"
5287
+
version = "0.48.5"
5288
+
source = "registry+https://github.com/rust-lang/crates.io-index"
5289
+
checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406"
5290
+
5291
+
[[package]]
5292
+
name = "windows_i686_msvc"
5293
+
version = "0.52.6"
5294
+
source = "registry+https://github.com/rust-lang/crates.io-index"
5295
+
checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66"
5296
+
5297
+
[[package]]
5298
+
name = "windows_i686_msvc"
5299
+
version = "0.53.0"
5300
+
source = "registry+https://github.com/rust-lang/crates.io-index"
5301
+
checksum = "581fee95406bb13382d2f65cd4a908ca7b1e4c2f1917f143ba16efe98a589b5d"
5302
+
5303
+
[[package]]
5304
+
name = "windows_x86_64_gnu"
5305
+
version = "0.48.5"
5306
+
source = "registry+https://github.com/rust-lang/crates.io-index"
5307
+
checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e"
5308
+
5309
+
[[package]]
5310
+
name = "windows_x86_64_gnu"
5311
+
version = "0.52.6"
5312
+
source = "registry+https://github.com/rust-lang/crates.io-index"
5313
+
checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78"
5314
+
5315
+
[[package]]
5316
+
name = "windows_x86_64_gnu"
5317
+
version = "0.53.0"
5318
+
source = "registry+https://github.com/rust-lang/crates.io-index"
5319
+
checksum = "2e55b5ac9ea33f2fc1716d1742db15574fd6fc8dadc51caab1c16a3d3b4190ba"
5320
+
5321
+
[[package]]
5322
+
name = "windows_x86_64_gnullvm"
5323
+
version = "0.48.5"
5324
+
source = "registry+https://github.com/rust-lang/crates.io-index"
5325
+
checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc"
5326
+
5327
+
[[package]]
5328
+
name = "windows_x86_64_gnullvm"
5329
+
version = "0.52.6"
5330
+
source = "registry+https://github.com/rust-lang/crates.io-index"
5331
+
checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d"
5332
+
5333
+
[[package]]
5334
+
name = "windows_x86_64_gnullvm"
5335
+
version = "0.53.0"
5336
+
source = "registry+https://github.com/rust-lang/crates.io-index"
5337
+
checksum = "0a6e035dd0599267ce1ee132e51c27dd29437f63325753051e71dd9e42406c57"
5338
+
5339
+
[[package]]
5340
+
name = "windows_x86_64_msvc"
5341
+
version = "0.48.5"
5342
+
source = "registry+https://github.com/rust-lang/crates.io-index"
5343
+
checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538"
5344
+
5345
+
[[package]]
5346
+
name = "windows_x86_64_msvc"
5347
+
version = "0.52.6"
5348
+
source = "registry+https://github.com/rust-lang/crates.io-index"
5349
+
checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
5350
+
5351
+
[[package]]
5352
+
name = "windows_x86_64_msvc"
5353
+
version = "0.53.0"
5354
+
source = "registry+https://github.com/rust-lang/crates.io-index"
5355
+
checksum = "271414315aff87387382ec3d271b52d7ae78726f5d44ac98b4f4030c91880486"
5356
+
5357
+
[[package]]
5358
+
name = "winnow"
5359
+
version = "0.6.26"
5360
+
source = "registry+https://github.com/rust-lang/crates.io-index"
5361
+
checksum = "1e90edd2ac1aa278a5c4599b1d89cf03074b610800f866d4026dc199d7929a28"
5362
+
dependencies = [
5363
+
"memchr",
5364
+
]
5365
+
5366
+
[[package]]
5367
+
name = "winnow"
5368
+
version = "0.7.11"
5369
+
source = "registry+https://github.com/rust-lang/crates.io-index"
5370
+
checksum = "74c7b26e3480b707944fc872477815d29a8e429d2f93a1ce000f5fa84a15cbcd"
5371
+
dependencies = [
5372
+
"memchr",
5373
+
]
5374
+
5375
+
[[package]]
5376
+
name = "winreg"
5377
+
version = "0.50.0"
5378
+
source = "registry+https://github.com/rust-lang/crates.io-index"
5379
+
checksum = "524e57b2c537c0f9b1e69f1965311ec12182b4122e45035b1508cd24d2adadb1"
5380
+
dependencies = [
5381
+
"cfg-if",
5382
+
"windows-sys 0.48.0",
5383
+
]
5384
+
5385
+
[[package]]
5386
+
name = "wit-bindgen-rt"
5387
+
version = "0.39.0"
5388
+
source = "registry+https://github.com/rust-lang/crates.io-index"
5389
+
checksum = "6f42320e61fe2cfd34354ecb597f86f413484a798ba44a8ca1165c58d42da6c1"
5390
+
dependencies = [
5391
+
"bitflags 2.9.1",
5392
+
]
5393
+
5394
+
[[package]]
5395
+
name = "writeable"
5396
+
version = "0.6.1"
5397
+
source = "registry+https://github.com/rust-lang/crates.io-index"
5398
+
checksum = "ea2f10b9bb0928dfb1b42b65e1f9e36f7f54dbdf08457afefb38afcdec4fa2bb"
5399
+
5400
+
[[package]]
5401
+
name = "xdg"
5402
+
version = "2.5.2"
5403
+
source = "registry+https://github.com/rust-lang/crates.io-index"
5404
+
checksum = "213b7324336b53d2414b2db8537e56544d981803139155afa84f76eeebb7a546"
5405
+
5406
+
[[package]]
5407
+
name = "xml-rs"
5408
+
version = "0.8.26"
5409
+
source = "registry+https://github.com/rust-lang/crates.io-index"
5410
+
checksum = "a62ce76d9b56901b19a74f19431b0d8b3bc7ca4ad685a746dfd78ca8f4fc6bda"
5411
+
5412
+
[[package]]
5413
+
name = "xmltree"
5414
+
version = "0.11.0"
5415
+
source = "registry+https://github.com/rust-lang/crates.io-index"
5416
+
checksum = "b619f8c85654798007fb10afa5125590b43b088c225a25fc2fec100a9fad0fc6"
5417
+
dependencies = [
5418
+
"xml-rs",
5419
+
]
5420
+
5421
+
[[package]]
5422
+
name = "yaml-rust"
5423
+
version = "0.4.5"
5424
+
source = "registry+https://github.com/rust-lang/crates.io-index"
5425
+
checksum = "56c1936c4cc7a1c9ab21a1ebb602eb942ba868cbd44a99cb7cdc5892335e1c85"
5426
+
dependencies = [
5427
+
"linked-hash-map",
5428
+
]
5429
+
5430
+
[[package]]
5431
+
name = "yoke"
5432
+
version = "0.8.0"
5433
+
source = "registry+https://github.com/rust-lang/crates.io-index"
5434
+
checksum = "5f41bb01b8226ef4bfd589436a297c53d118f65921786300e427be8d487695cc"
5435
+
dependencies = [
5436
+
"serde",
5437
+
"stable_deref_trait",
5438
+
"yoke-derive",
5439
+
"zerofrom",
5440
+
]
5441
+
5442
+
[[package]]
5443
+
name = "yoke-derive"
5444
+
version = "0.8.0"
5445
+
source = "registry+https://github.com/rust-lang/crates.io-index"
5446
+
checksum = "38da3c9736e16c5d3c8c597a9aaa5d1fa565d0532ae05e27c24aa62fb32c0ab6"
5447
+
dependencies = [
5448
+
"proc-macro2",
5449
+
"quote",
5450
+
"syn 2.0.104",
5451
+
"synstructure",
5452
+
]
5453
+
5454
+
[[package]]
5455
+
name = "zerocopy"
5456
+
version = "0.8.26"
5457
+
source = "registry+https://github.com/rust-lang/crates.io-index"
5458
+
checksum = "1039dd0d3c310cf05de012d8a39ff557cb0d23087fd44cad61df08fc31907a2f"
5459
+
dependencies = [
5460
+
"zerocopy-derive",
5461
+
]
5462
+
5463
+
[[package]]
5464
+
name = "zerocopy-derive"
5465
+
version = "0.8.26"
5466
+
source = "registry+https://github.com/rust-lang/crates.io-index"
5467
+
checksum = "9ecf5b4cc5364572d7f4c329661bcc82724222973f2cab6f050a4e5c22f75181"
5468
+
dependencies = [
5469
+
"proc-macro2",
5470
+
"quote",
5471
+
"syn 2.0.104",
5472
+
]
5473
+
5474
+
[[package]]
5475
+
name = "zerofrom"
5476
+
version = "0.1.6"
5477
+
source = "registry+https://github.com/rust-lang/crates.io-index"
5478
+
checksum = "50cc42e0333e05660c3587f3bf9d0478688e15d870fab3346451ce7f8c9fbea5"
5479
+
dependencies = [
5480
+
"zerofrom-derive",
5481
+
]
5482
+
5483
+
[[package]]
5484
+
name = "zerofrom-derive"
5485
+
version = "0.1.6"
5486
+
source = "registry+https://github.com/rust-lang/crates.io-index"
5487
+
checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502"
5488
+
dependencies = [
5489
+
"proc-macro2",
5490
+
"quote",
5491
+
"syn 2.0.104",
5492
+
"synstructure",
5493
+
]
5494
+
5495
+
[[package]]
5496
+
name = "zeroize"
5497
+
version = "1.8.1"
5498
+
source = "registry+https://github.com/rust-lang/crates.io-index"
5499
+
checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde"
5500
+
5501
+
[[package]]
5502
+
name = "zerotrie"
5503
+
version = "0.2.2"
5504
+
source = "registry+https://github.com/rust-lang/crates.io-index"
5505
+
checksum = "36f0bbd478583f79edad978b407914f61b2972f5af6fa089686016be8f9af595"
5506
+
dependencies = [
5507
+
"displaydoc",
5508
+
"yoke",
5509
+
"zerofrom",
5510
+
]
5511
+
5512
+
[[package]]
5513
+
name = "zerovec"
5514
+
version = "0.11.2"
5515
+
source = "registry+https://github.com/rust-lang/crates.io-index"
5516
+
checksum = "4a05eb080e015ba39cc9e23bbe5e7fb04d5fb040350f99f34e338d5fdd294428"
5517
+
dependencies = [
5518
+
"yoke",
5519
+
"zerofrom",
5520
+
"zerovec-derive",
5521
+
]
5522
+
5523
+
[[package]]
5524
+
name = "zerovec-derive"
5525
+
version = "0.11.1"
5526
+
source = "registry+https://github.com/rust-lang/crates.io-index"
5527
+
checksum = "5b96237efa0c878c64bd89c436f661be4e46b2f3eff1ebb976f7ef2321d2f58f"
5528
+
dependencies = [
5529
+
"proc-macro2",
5530
+
"quote",
5531
+
"syn 2.0.104",
5532
+
]
5533
+
5534
+
[[package]]
5535
+
name = "zstd"
5536
+
version = "0.13.3"
5537
+
source = "registry+https://github.com/rust-lang/crates.io-index"
5538
+
checksum = "e91ee311a569c327171651566e07972200e76fcfe2242a4fa446149a3881c08a"
5539
+
dependencies = [
5540
+
"zstd-safe",
5541
+
]
5542
+
5543
+
[[package]]
5544
+
name = "zstd-safe"
5545
+
version = "7.2.4"
5546
+
source = "registry+https://github.com/rust-lang/crates.io-index"
5547
+
checksum = "8f49c4d5f0abb602a93fb8736af2a4f4dd9512e36f7f570d66e65ff867ed3b9d"
5548
+
dependencies = [
5549
+
"zstd-sys",
5550
+
]
5551
+
5552
+
[[package]]
5553
+
name = "zstd-sys"
5554
+
version = "2.0.15+zstd.1.5.7"
5555
+
source = "registry+https://github.com/rust-lang/crates.io-index"
5556
+
checksum = "eb81183ddd97d0c74cedf1d50d85c8d08c1b8b68ee863bdee9e706eedba1a237"
5557
+
dependencies = [
5558
+
"cc",
5559
+
"pkg-config",
5560
+
]
5561
+
5562
+
[[package]]
5563
+
name = "zune-core"
5564
+
version = "0.4.12"
5565
+
source = "registry+https://github.com/rust-lang/crates.io-index"
5566
+
checksum = "3f423a2c17029964870cfaabb1f13dfab7d092a62a29a89264f4d36990ca414a"
5567
+
5568
+
[[package]]
5569
+
name = "zune-inflate"
5570
+
version = "0.2.54"
5571
+
source = "registry+https://github.com/rust-lang/crates.io-index"
5572
+
checksum = "73ab332fe2f6680068f3582b16a24f90ad7096d5d39b974d1c0aff0125116f02"
5573
+
dependencies = [
5574
+
"simd-adler32",
5575
+
]
5576
+
5577
+
[[package]]
5578
+
name = "zune-jpeg"
5579
+
version = "0.4.19"
5580
+
source = "registry+https://github.com/rust-lang/crates.io-index"
5581
+
checksum = "2c9e525af0a6a658e031e95f14b7f889976b74a11ba0eca5a5fc9ac8a1c43a6a"
5582
+
dependencies = [
5583
+
"zune-core",
5584
+
]
+75
Cargo.toml
+75
Cargo.toml
···
···
1
+
[package]
2
+
name = "blahg"
3
+
version = "0.1.0"
4
+
edition = "2024"
5
+
6
+
[features]
7
+
default = ["reload", "sqlite", "postgres", "s3"]
8
+
embed = ["dep:minijinja-embed", "dep:rust-embed"]
9
+
reload = ["dep:minijinja-autoreload", "minijinja/loader", "axum-template/minijinja-autoreload"]
10
+
sqlite = ["sqlx/sqlite"]
11
+
postgres = ["sqlx/postgres"]
12
+
s3 = ["dep:minio"]
13
+
14
+
[build-dependencies]
15
+
minijinja-embed = {version = "2.7"}
16
+
17
+
[dependencies]
18
+
# ATProtocol dependencies
19
+
atproto-client = { version = "0.9.2" }
20
+
atproto-identity = { version = "0.9.2" }
21
+
atproto-record = { version = "0.9.2" }
22
+
atproto-jetstream = { version = "0.9.2" }
23
+
24
+
# Web framework
25
+
axum = "0.8"
26
+
axum-template = { version = "3.0", features = ["minijinja"] }
27
+
tower-http = { version = "0.5", features = ["fs"] }
28
+
29
+
# Template engine
30
+
minijinja = { version = "2.7", features = ["builtins", ] }
31
+
minijinja-autoreload = { version = "2.7", optional = true }
32
+
minijinja-embed = { version = "2.7", optional = true }
33
+
rust-embed = { version = "8.5", optional = true }
34
+
35
+
# Database
36
+
sqlx = { version = "0.8", features = ["runtime-tokio-rustls", "chrono", "json", "uuid"] }
37
+
38
+
# Image processing
39
+
image = "0.25"
40
+
41
+
# Core dependencies
42
+
anyhow = "1.0"
43
+
async-trait = "0.1.88"
44
+
base64 = "0.22.1"
45
+
chrono = {version = "0.4.41", default-features = false, features = ["std", "now", "serde"]}
46
+
duration-str = "0.11"
47
+
ecdsa = { version = "0.16.9", features = ["std"] }
48
+
elliptic-curve = { version = "0.13.8", features = ["jwk", "serde"] }
49
+
hickory-resolver = { version = "0.25" }
50
+
k256 = "0.13.4"
51
+
lru = "0.12"
52
+
multibase = "0.9.1"
53
+
p256 = "0.13.2"
54
+
reqwest = { version = "0.12", features = ["json", "rustls-tls"] }
55
+
serde = { version = "1.0", features = ["derive"] }
56
+
serde_ipld_dagcbor = "0.6.3"
57
+
serde_json = "1.0"
58
+
sha2 = "0.10.9"
59
+
thiserror = "2.0"
60
+
tokio = { version = "1.41", features = ["macros", "rt", "rt-multi-thread", "fs", "signal"] }
61
+
tokio-util = { version = "0.7", features = ["rt"] }
62
+
tracing = { version = "0.1", features = ["async-await"] }
63
+
tracing-subscriber = { version = "0.3", features = ["registry", "env-filter"] }
64
+
65
+
# Object storage
66
+
minio = { version = "0.3", optional = true }
67
+
bytes = "1.10.1"
68
+
69
+
# Markdown rendering
70
+
comrak = { version = "0.39", features = ["syntect"] }
71
+
slugify = "0.1.0"
72
+
bloomfilter = "1.0.15"
73
+
74
+
[dev-dependencies]
75
+
tempfile = "3.0"
+60
Dockerfile
+60
Dockerfile
···
···
1
+
# Build stage
2
+
FROM rust:1.87-slim AS builder
3
+
4
+
# Install required system dependencies for building
5
+
RUN apt-get update && apt-get install -y \
6
+
pkg-config \
7
+
libssl-dev \
8
+
&& rm -rf /var/lib/apt/lists/*
9
+
10
+
# Set working directory
11
+
WORKDIR /app
12
+
13
+
# Copy manifests first for better layer caching
14
+
COPY Cargo.toml Cargo.lock build.rs ./
15
+
16
+
ARG FEATURES=embed,postgres,s3
17
+
ARG TEMPLATES=./templates
18
+
ARG STATIC=./static
19
+
20
+
# Copy actual source code and assets
21
+
COPY src ./src
22
+
COPY ${TEMPLATES} ./templates
23
+
COPY ${STATIC} ./static
24
+
25
+
ENV HTTP_TEMPLATE_PATH=/app/templates/
26
+
27
+
# Build the actual application with embed feature only
28
+
RUN cargo build --release --no-default-features --features ${FEATURES}
29
+
30
+
# Runtime stage using distroless
31
+
FROM gcr.io/distroless/cc-debian12
32
+
33
+
# Add OCI labels
34
+
LABEL org.opencontainers.image.title="blahg"
35
+
LABEL org.opencontainers.image.description="An ATProtocol blogging AppView."
36
+
LABEL org.opencontainers.image.version="0.1.0"
37
+
LABEL org.opencontainers.image.authors="Nick Gerakines <nick.gerakines@gmail.com>"
38
+
LABEL org.opencontainers.image.url="https://blog.smokesignal.events/"
39
+
LABEL org.opencontainers.image.source="https://tangled.sh/@smokesignal.events/blahg"
40
+
LABEL org.opencontainers.image.licenses="MIT"
41
+
LABEL org.opencontainers.image.created="2025-01-06T00:00:00Z"
42
+
43
+
# Set working directory
44
+
WORKDIR /app
45
+
46
+
# Copy the binary from builder stage
47
+
COPY --from=builder /app/target/release/blahg /app/blahg
48
+
49
+
# Copy static directory
50
+
COPY --from=builder /app/static ./static
51
+
52
+
# Set environment variables
53
+
ENV HTTP_STATIC_PATH=/app/static
54
+
ENV HTTP_PORT=8080
55
+
56
+
# Expose port
57
+
EXPOSE 8080
58
+
59
+
# Run the application
60
+
ENTRYPOINT ["/app/blahg"]
+9
LICENSE
+9
LICENSE
···
···
1
+
MIT License
2
+
3
+
Copyright (c) 2025 Nick Gerakines
4
+
5
+
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:
6
+
7
+
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
8
+
9
+
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.
+205
README.md
+205
README.md
···
···
1
+
# Blahg
2
+
3
+
A Rust-based ATProtocol AppView for rendering personal blog content from the ATProtocol network.
4
+
5
+
## Overview
6
+
7
+
Blahg is a high-performance blog engine that consumes ATProtocol records to create a traditional blog-like reading experience. It processes both custom blog post records (`tools.smokesignal.blahg.content.post`) and standard ATProtocol social media posts (`app.bsky.feed.post`), providing a unified interface for content discovery and presentation.
8
+
9
+
## Features
10
+
11
+
### Core Functionality
12
+
- **Real-time Event Processing**: Consumes ATProtocol Jetstream events for live content updates
13
+
- **Multi-Format Content Support**: Handles markdown, HTML, and plain text content
14
+
- **Custom Lexicon Support**: Implements `tools.smokesignal.blahg.content.post` schema for rich blog posts
15
+
- **Post Import Tool**: Command-line utility for importing individual posts via AT-URI
16
+
- **Identity Resolution**: Automatic DID and handle resolution with caching
17
+
- **Post Interactions**: Tracks likes, reposts, and other engagement metrics
18
+
19
+
### Storage & Infrastructure
20
+
- **Multi-Database Support**: Both PostgreSQL and SQLite backends supported
21
+
- **Flexible Content Storage**: Filesystem and S3-compatible object storage
22
+
- **Efficient Caching**: LRU caching for posts, identities, and content
23
+
- **Template Engine**: Minijinja-based templating with hot-reload support during development
24
+
- **Markdown Rendering**: Comrak-powered markdown with syntax highlighting
25
+
26
+
### Web Interface
27
+
- **Responsive Design**: Clean, accessible blog interface with Pico.css
28
+
- **SEO Optimized**: Proper meta tags, OpenGraph support, and canonical URLs
29
+
- **Post Interactions**: Display engagement metrics and interaction history
30
+
- **Static Asset Serving**: Efficient static file serving with proper caching
31
+
32
+
## Architecture
33
+
34
+
Blahg consists of two main components:
35
+
36
+
1. **blahg**: The main server application that runs the web interface and processes real-time events
37
+
2. **blahg-import**: A command-line tool for importing individual posts from AT-URIs
38
+
39
+
### Data Flow
40
+
1. ATProtocol events are consumed via Jetstream
41
+
2. Relevant records are filtered and processed
42
+
3. Post content and attachments are stored
43
+
4. Web interface serves rendered content with caching
44
+
45
+
## Dependencies
46
+
47
+
This project uses the following atproto crates:
48
+
- `atproto-identity` - Identity resolution and verification
49
+
- `atproto-record` - Record handling and parsing
50
+
- `atproto-client` - ATProtocol client functionality
51
+
- `atproto-jetstream` - Real-time event streaming
52
+
53
+
## Installation
54
+
55
+
### Prerequisites
56
+
- Rust 1.87 or higher
57
+
- Cargo
58
+
- PostgreSQL or SQLite (depending on your database choice)
59
+
60
+
### Building from Source
61
+
62
+
```bash
63
+
git clone https://github.com/yourusername/blahg.git
64
+
cd blahg
65
+
cargo build --release
66
+
```
67
+
68
+
### Docker
69
+
70
+
```bash
71
+
docker build -t blahg .
72
+
docker run -p 8080:8080 blahg
73
+
```
74
+
75
+
## Usage
76
+
77
+
### Running the Server
78
+
79
+
```bash
80
+
# Run with default configuration
81
+
./target/release/blahg
82
+
83
+
# Run with custom configuration via environment variables
84
+
BLAHG_AUTHOR="did:plc:example" \
85
+
BLAHG_DATABASE_URL="postgresql://user:pass@localhost/blahg" \
86
+
BLAHG_ATTACHMENT_STORAGE="s3://your-bucket/attachments/" \
87
+
./target/release/blahg
88
+
```
89
+
90
+
### Importing Posts
91
+
92
+
```bash
93
+
# Import a single post
94
+
./target/release/blahg-import at://did:plc:example/tools.smokesignal.blahg.content.post/abc123
95
+
96
+
# Import multiple posts
97
+
./target/release/blahg-import \
98
+
at://did:plc:example/tools.smokesignal.blahg.content.post/abc123 \
99
+
at://did:plc:example/tools.smokesignal.blahg.content.post/def456
100
+
```
101
+
102
+
## Configuration
103
+
104
+
Configuration is managed via environment variables:
105
+
106
+
### Core Settings
107
+
- `BLAHG_AUTHOR`: Your ATProtocol DID (required)
108
+
- `BLAHG_EXTERNAL_BASE`: Base URL for your blog (default: `http://localhost:8080`)
109
+
- `BLAHG_HTTP_PORT`: HTTP server port (default: `8080`)
110
+
111
+
### Database
112
+
- `BLAHG_DATABASE_URL`: Database connection string
113
+
- SQLite: `sqlite://blahg.db`
114
+
- PostgreSQL: `postgresql://user:pass@localhost/blahg`
115
+
116
+
### Storage
117
+
- `BLAHG_ATTACHMENT_STORAGE`: Content storage location
118
+
- Filesystem: `./attachments`
119
+
- S3: `s3://bucket/prefix/`
120
+
121
+
### ATProtocol
122
+
- `BLAHG_ENABLE_JETSTREAM`: Enable real-time event processing (default: `true`)
123
+
- `BLAHG_JETSTREAM_CURSOR_PATH`: Path to store Jetstream cursor (optional)
124
+
- `BLAHG_PLC_HOSTNAME`: PLC directory hostname (default: `plc.directory`)
125
+
126
+
### HTTP Client
127
+
- `BLAHG_USER_AGENT`: HTTP client user agent
128
+
- `BLAHG_HTTP_CLIENT_TIMEOUT`: HTTP client timeout (default: `10s`)
129
+
- `BLAHG_CERTIFICATE_BUNDLES`: Additional CA certificates (comma-separated paths)
130
+
131
+
## Development
132
+
133
+
### Feature Flags
134
+
135
+
Blahg supports several feature flags for different deployment scenarios:
136
+
137
+
- `sqlite`: Enable SQLite database support (default: enabled)
138
+
- `postgres`: Enable PostgreSQL database support (default: enabled)
139
+
- `s3`: Enable S3-compatible object storage (default: enabled)
140
+
- `embed`: Embed templates in binary for production (default: disabled)
141
+
- `reload`: Enable template hot-reloading for development (default: enabled)
142
+
143
+
### Development Environment
144
+
145
+
```bash
146
+
# Run in development mode with hot-reloading
147
+
cargo run
148
+
149
+
# Run with specific features
150
+
cargo run --no-default-features --features "sqlite,reload"
151
+
```
152
+
153
+
### Running Tests
154
+
```bash
155
+
cargo test
156
+
```
157
+
158
+
### Linting and Type Checking
159
+
```bash
160
+
cargo clippy
161
+
cargo check
162
+
```
163
+
164
+
### Building for Production
165
+
166
+
```bash
167
+
# Build with embedded templates
168
+
cargo build --release --no-default-features --features "embed,postgres,s3"
169
+
```
170
+
171
+
## API Endpoints
172
+
173
+
Blahg provides a simple web interface with the following endpoints:
174
+
175
+
- `GET /`: Homepage with list of all posts
176
+
- `GET /posts/{slug}`: Individual post page
177
+
- `GET /posts/{slug}/{collection}`: Post interaction references (likes, reposts, etc.)
178
+
- `GET /static/*`: Static asset serving (CSS, JS, images)
179
+
180
+
## ATProtocol Lexicon
181
+
182
+
Blahg implements the `tools.smokesignal.blahg.content.post` lexicon for rich blog posts:
183
+
184
+
```json
185
+
{
186
+
"title": "string", // Post title (max 200 graphemes)
187
+
"content": "blob", // Post content (markdown/HTML/text, max 1MB)
188
+
"publishedAt": "datetime", // Publication timestamp
189
+
"langs": ["string"], // Language codes (max 3)
190
+
"attachments": [ // Optional image attachments
191
+
{
192
+
"content": "blob", // Image blob (max 3MB)
193
+
"alt": "string" // Alt text for accessibility
194
+
}
195
+
]
196
+
}
197
+
```
198
+
199
+
## Contributing
200
+
201
+
Contributions are welcome! Please feel free to submit a Pull Request.
202
+
203
+
## License
204
+
205
+
Blahg is open source software released under the [MIT License](LICENSE).
+22
build.rs
+22
build.rs
···
···
1
+
fn main() {
2
+
#[cfg(all(feature = "embed", feature = "reload"))]
3
+
compile_error!("feature \"embed\" and feature \"reload\" cannot be enabled at the same time");
4
+
5
+
#[cfg(not(any(feature = "sqlite", feature = "postgres")))]
6
+
compile_error!("one of feature \"sqlite\" or feature \"postgres\" must be enabled");
7
+
8
+
#[cfg(feature = "embed")]
9
+
{
10
+
use std::env;
11
+
use std::path::PathBuf;
12
+
let template_path = if let Ok(value) = env::var("HTTP_TEMPLATE_PATH") {
13
+
value.to_string()
14
+
} else {
15
+
PathBuf::from(env!("CARGO_MANIFEST_DIR"))
16
+
.join("templates")
17
+
.display()
18
+
.to_string()
19
+
};
20
+
minijinja_embed::embed_templates!(&template_path);
21
+
}
22
+
}
+262
src/bin/blahg-import.rs
+262
src/bin/blahg-import.rs
···
···
1
+
use atproto_client::com::atproto::repo::get_blob;
2
+
use atproto_client::com::atproto::repo::get_record;
3
+
use atproto_identity::resolve::IdentityResolver;
4
+
use atproto_identity::resolve::InnerIdentityResolver;
5
+
use atproto_identity::resolve::create_resolver;
6
+
use atproto_record::aturi::ATURI;
7
+
use blahg::identity::CachingIdentityResolver;
8
+
use chrono::Utc;
9
+
use slugify::slugify;
10
+
#[cfg(feature = "sqlite")]
11
+
use sqlx::SqlitePool;
12
+
#[cfg(feature = "postgres")]
13
+
use sqlx::postgres::PgPool;
14
+
use std::str::FromStr;
15
+
use std::{env, sync::Arc};
16
+
use tracing::{error, info};
17
+
use tracing_subscriber::prelude::*;
18
+
19
+
use blahg::errors::Result;
20
+
use blahg::lexicon::PostRecord;
21
+
use blahg::storage::Post;
22
+
#[cfg(feature = "s3")]
23
+
use blahg::storage::content::S3FileStorage;
24
+
#[cfg(feature = "s3")]
25
+
use blahg::storage::content::parse_s3_url;
26
+
#[cfg(feature = "postgres")]
27
+
use blahg::storage::postgres::PostgresStorage;
28
+
#[cfg(feature = "sqlite")]
29
+
use blahg::storage::sqlite::SqliteStorage;
30
+
use blahg::{
31
+
config::Config,
32
+
storage::content::FilesystemContentStorage,
33
+
storage::{ContentStorage, Storage},
34
+
};
35
+
36
+
async fn create_content_storage(storage_config: &str) -> Result<Arc<dyn ContentStorage>> {
37
+
if storage_config.starts_with("s3://") {
38
+
#[cfg(feature = "s3")]
39
+
{
40
+
tracing::warn!("S3 content storage used");
41
+
42
+
let (endpoint, access_key, secret_key, bucket, prefix) = parse_s3_url(storage_config)?;
43
+
let s3_storage = S3FileStorage::new(endpoint, access_key, secret_key, bucket, prefix)?;
44
+
Ok(Arc::new(s3_storage))
45
+
}
46
+
#[cfg(not(feature = "s3"))]
47
+
{
48
+
Err(blahg::errors::BlahgError::ConfigFeatureNotEnabled {
49
+
feature: "S3 storage requested but s3 feature is not enabled".to_string(),
50
+
})
51
+
}
52
+
} else {
53
+
tracing::warn!("Filesystem content storage used");
54
+
let storage = FilesystemContentStorage::new(storage_config).await?;
55
+
Ok(Arc::new(storage))
56
+
}
57
+
}
58
+
59
+
async fn import_record(
60
+
http_client: &reqwest::Client,
61
+
identity_resolver: &CachingIdentityResolver<dyn Storage>,
62
+
at_uri: &str,
63
+
storage: Arc<dyn Storage>,
64
+
content_storage: Arc<dyn ContentStorage>,
65
+
) -> Result<()> {
66
+
info!("Importing record from AT-URI: {}", at_uri);
67
+
68
+
// Parse the AT-URI
69
+
let aturi = ATURI::from_str(at_uri)?;
70
+
info!(
71
+
"Parsed AT-URI - repo: {}, collection: {}, rkey: {}",
72
+
aturi.authority, aturi.collection, aturi.record_key
73
+
);
74
+
75
+
// Resolve identity to find PDS
76
+
let identity = identity_resolver.resolve(&aturi.authority).await?;
77
+
78
+
let pds_endpoint = identity.pds_endpoints().first().cloned().ok_or_else(|| {
79
+
blahg::errors::BlahgError::ProcessIdentityResolutionFailed {
80
+
did: aturi.authority.clone(),
81
+
details: "No PDS endpoint found for identity".to_string(),
82
+
}
83
+
})?;
84
+
85
+
info!("Resolved PDS endpoint: {}", pds_endpoint);
86
+
87
+
// Get the record
88
+
let record_response = get_record(
89
+
http_client,
90
+
None,
91
+
pds_endpoint,
92
+
&aturi.authority,
93
+
&aturi.collection,
94
+
&aturi.record_key,
95
+
None, // cid
96
+
)
97
+
.await?;
98
+
99
+
info!("Retrieved record successfully");
100
+
101
+
let (raw_post_record, uri, cid) = match record_response {
102
+
atproto_client::com::atproto::repo::GetRecordResponse::Record {
103
+
uri, cid, value, ..
104
+
} => (value, uri, cid),
105
+
atproto_client::com::atproto::repo::GetRecordResponse::Error(simple_error) => {
106
+
error!("nope: {}", simple_error.error_message());
107
+
return Ok(());
108
+
}
109
+
};
110
+
111
+
let post_record = serde_json::from_value::<PostRecord>(raw_post_record.clone())?;
112
+
113
+
let content_cid = &post_record.content.r#ref.link;
114
+
115
+
let slug = format!("{}-{}", aturi.record_key, slugify!(&post_record.title));
116
+
117
+
let now = Utc::now();
118
+
119
+
let post = Post {
120
+
aturi: uri,
121
+
cid,
122
+
title: post_record.title.clone(),
123
+
slug,
124
+
content: content_cid.clone(),
125
+
record_key: aturi.record_key.clone(),
126
+
created_at: now,
127
+
updated_at: now,
128
+
record: raw_post_record,
129
+
};
130
+
storage.upsert_post(&post).await?;
131
+
132
+
let content_blob = get_blob(http_client, pds_endpoint, &aturi.authority, content_cid).await?;
133
+
134
+
content_storage
135
+
.write_content(content_cid, &content_blob)
136
+
.await?;
137
+
138
+
for attachment in post_record.attachments {
139
+
let attachment_cid = attachment.content.r#ref.link;
140
+
let attachment_blob = get_blob(
141
+
http_client,
142
+
pds_endpoint,
143
+
&aturi.authority,
144
+
&attachment_cid,
145
+
)
146
+
.await?;
147
+
148
+
content_storage
149
+
.write_content(&attachment_cid, &attachment_blob)
150
+
.await?;
151
+
}
152
+
153
+
Ok(())
154
+
}
155
+
156
+
#[tokio::main]
157
+
async fn main() -> Result<()> {
158
+
// Initialize logging
159
+
tracing_subscriber::registry()
160
+
.with(tracing_subscriber::EnvFilter::new(
161
+
std::env::var("RUST_LOG").unwrap_or_else(|_| "blahg=info,info".into()),
162
+
))
163
+
.with(tracing_subscriber::fmt::layer().pretty())
164
+
.init();
165
+
166
+
// Get command line arguments (skip program name)
167
+
let args: Vec<String> = env::args().skip(1).collect();
168
+
169
+
if args.is_empty() {
170
+
eprintln!("Usage: blagh-import <AT-URI> [<AT-URI> ...]");
171
+
eprintln!(
172
+
"Example: blagh-import at://did:plc:example/tools.smokesignal.blahg.content.post/123"
173
+
);
174
+
std::process::exit(1);
175
+
}
176
+
177
+
// Load configuration
178
+
let config = Arc::new(Config::from_env()?);
179
+
info!("Loaded configuration for import");
180
+
181
+
let mut client_builder = reqwest::Client::builder();
182
+
for ca_certificate in &config.certificate_bundles {
183
+
tracing::info!("Loading CA certificate: {:?}", ca_certificate);
184
+
let cert = std::fs::read(ca_certificate)?;
185
+
let cert = reqwest::Certificate::from_pem(&cert)?;
186
+
client_builder = client_builder.add_root_certificate(cert);
187
+
}
188
+
189
+
client_builder = client_builder
190
+
.user_agent(config.user_agent.clone())
191
+
.timeout(config.http_client_timeout);
192
+
let http_client: reqwest::Client = client_builder.build()?;
193
+
194
+
let dns_resolver = create_resolver(&config.dns_nameservers);
195
+
196
+
let base_identity_resolver = IdentityResolver(Arc::new(InnerIdentityResolver {
197
+
dns_resolver,
198
+
http_client: http_client.clone(),
199
+
plc_hostname: config.plc_hostname.clone(),
200
+
}));
201
+
202
+
// Setup database based on the database URL and available features
203
+
let storage: Arc<dyn Storage> = {
204
+
#[cfg(all(feature = "sqlite", not(feature = "postgres")))]
205
+
{
206
+
let pool = SqlitePool::connect(&config.database_url).await?;
207
+
Arc::new(SqliteStorage::new(pool))
208
+
}
209
+
210
+
#[cfg(all(feature = "postgres", not(feature = "sqlite")))]
211
+
{
212
+
let pool = PgPool::connect(&config.database_url).await?;
213
+
Arc::new(PostgresStorage::new(pool))
214
+
}
215
+
216
+
#[cfg(all(feature = "sqlite", feature = "postgres"))]
217
+
{
218
+
if config.database_url.starts_with("postgres://")
219
+
|| config.database_url.starts_with("postgresql://")
220
+
{
221
+
let pool = PgPool::connect(&config.database_url).await?;
222
+
Arc::new(PostgresStorage::new(pool))
223
+
} else {
224
+
let pool = SqlitePool::connect(&config.database_url).await?;
225
+
Arc::new(SqliteStorage::new(pool))
226
+
}
227
+
}
228
+
};
229
+
230
+
// Run migrations
231
+
storage.migrate().await?;
232
+
info!("Database migrations completed");
233
+
234
+
// Create caching identity resolver
235
+
let identity_resolver = CachingIdentityResolver::new(base_identity_resolver, storage.clone());
236
+
237
+
// Setup content storage
238
+
let content_storage = create_content_storage(&config.attachment_storage).await?;
239
+
240
+
// Process each AT-URI
241
+
for at_uri in args {
242
+
match import_record(
243
+
&http_client,
244
+
&identity_resolver,
245
+
&at_uri,
246
+
storage.clone(),
247
+
content_storage.clone(),
248
+
)
249
+
.await
250
+
{
251
+
Ok(()) => {
252
+
info!("Successfully imported record from: {}", at_uri);
253
+
}
254
+
Err(e) => {
255
+
error!("Failed to import record from {}: {}", at_uri, e);
256
+
}
257
+
}
258
+
}
259
+
260
+
info!("Import process completed");
261
+
Ok(())
262
+
}
+392
src/bin/blahg.rs
+392
src/bin/blahg.rs
···
···
1
+
use atproto_identity::resolve::IdentityResolver;
2
+
use atproto_identity::resolve::InnerIdentityResolver;
3
+
use atproto_identity::resolve::create_resolver;
4
+
use atproto_jetstream::{CancellationToken, Consumer as JetstreamConsumer, ConsumerTaskConfig};
5
+
use blahg::consumer::Consumer;
6
+
use blahg::errors::Result;
7
+
use blahg::http::AppEngine;
8
+
use blahg::identity::CachingIdentityResolver;
9
+
use blahg::process::EventProcessor;
10
+
#[cfg(feature = "s3")]
11
+
use blahg::storage::content::S3FileStorage;
12
+
#[cfg(feature = "s3")]
13
+
use blahg::storage::content::parse_s3_url;
14
+
#[cfg(feature = "postgres")]
15
+
use blahg::storage::postgres::PostgresStorage;
16
+
#[cfg(feature = "sqlite")]
17
+
use blahg::storage::sqlite::SqliteStorage;
18
+
use blahg::{
19
+
config::Config,
20
+
http::{AppState, create_router},
21
+
render::{ComrakRenderManager, RenderManager},
22
+
storage::content::FilesystemContentStorage,
23
+
storage::{CachedContentStorage, CachedPostStorage, ContentStorage, Storage},
24
+
};
25
+
#[cfg(feature = "sqlite")]
26
+
use sqlx::SqlitePool;
27
+
#[cfg(feature = "postgres")]
28
+
use sqlx::postgres::PgPool;
29
+
use std::{env, sync::Arc};
30
+
use tokio::net::TcpListener;
31
+
use tokio::signal;
32
+
use tokio_util::task::TaskTracker;
33
+
use tracing_subscriber::prelude::*;
34
+
35
+
#[cfg(feature = "embed")]
36
+
use blahg::templates::build_env;
37
+
38
+
#[cfg(feature = "reload")]
39
+
use blahg::templates::build_env;
40
+
41
+
#[tokio::main]
42
+
async fn main() -> Result<()> {
43
+
// Initialize logging
44
+
tracing_subscriber::registry()
45
+
.with(tracing_subscriber::EnvFilter::new(
46
+
std::env::var("RUST_LOG").unwrap_or_else(|_| "blahg=info,info".into()),
47
+
))
48
+
.with(tracing_subscriber::fmt::layer().pretty())
49
+
.init();
50
+
51
+
// Handle version flag
52
+
env::args().for_each(|arg| {
53
+
if arg == "--version" {
54
+
println!("blahg {}", env!("CARGO_PKG_VERSION"));
55
+
std::process::exit(0);
56
+
}
57
+
});
58
+
59
+
// Load configuration
60
+
let config = Arc::new(Config::from_env()?);
61
+
tracing::info!("Starting Blahg with config");
62
+
63
+
let dns_resolver = create_resolver(&config.dns_nameservers);
64
+
let http_client = reqwest::Client::builder()
65
+
.user_agent(&config.user_agent)
66
+
.timeout(config.http_client_timeout)
67
+
.build()?;
68
+
69
+
let base_identity_resolver = IdentityResolver(Arc::new(InnerIdentityResolver {
70
+
dns_resolver,
71
+
http_client: http_client.clone(),
72
+
plc_hostname: config.plc_hostname.clone(),
73
+
}));
74
+
75
+
// Setup database based on the database URL and available features
76
+
let storage: Arc<dyn Storage> = {
77
+
#[cfg(all(feature = "sqlite", not(feature = "postgres")))]
78
+
{
79
+
let pool = SqlitePool::connect(&config.database_url).await?;
80
+
let base_storage = Arc::new(SqliteStorage::new(pool));
81
+
// Wrap with caching - note: this only caches PostStorage operations
82
+
Arc::new(CachedPostStorage::new(base_storage))
83
+
}
84
+
85
+
#[cfg(all(feature = "postgres", not(feature = "sqlite")))]
86
+
{
87
+
let pool = PgPool::connect(&config.database_url).await?;
88
+
let base_storage = Arc::new(PostgresStorage::new(pool));
89
+
// Wrap with caching - note: this only caches PostStorage operations
90
+
Arc::new(CachedPostStorage::new(base_storage))
91
+
}
92
+
93
+
#[cfg(all(feature = "sqlite", feature = "postgres"))]
94
+
{
95
+
// When both features are enabled, determine based on the database URL
96
+
if config.database_url.starts_with("postgres://")
97
+
|| config.database_url.starts_with("postgresql://")
98
+
{
99
+
let pool = PgPool::connect(&config.database_url).await?;
100
+
let base_storage = Arc::new(PostgresStorage::new(pool));
101
+
// Wrap with caching - note: this only caches PostStorage operations
102
+
Arc::new(CachedPostStorage::new(base_storage))
103
+
} else {
104
+
let pool = SqlitePool::connect(&config.database_url).await?;
105
+
let base_storage = Arc::new(SqliteStorage::new(pool));
106
+
// Wrap with caching - note: this only caches PostStorage operations
107
+
Arc::new(CachedPostStorage::new(base_storage))
108
+
}
109
+
}
110
+
};
111
+
112
+
// Run migrations
113
+
storage.migrate().await?;
114
+
115
+
// Create caching identity resolver
116
+
let identity_resolver = CachingIdentityResolver::new(base_identity_resolver, storage.clone());
117
+
118
+
{
119
+
identity_resolver
120
+
.resolve(&config.author)
121
+
.await
122
+
.expect("this better work");
123
+
}
124
+
125
+
// Setup content storage for markdown files
126
+
let content_storage: Arc<dyn ContentStorage> = if config.attachment_storage.starts_with("s3://")
127
+
{
128
+
#[cfg(feature = "s3")]
129
+
{
130
+
let (endpoint, access_key, secret_key, bucket, prefix) =
131
+
parse_s3_url(&config.attachment_storage)?;
132
+
let s3_storage = Arc::new(S3FileStorage::new(
133
+
endpoint, access_key, secret_key, bucket, prefix,
134
+
)?);
135
+
Arc::new(CachedContentStorage::new(s3_storage))
136
+
}
137
+
#[cfg(not(feature = "s3"))]
138
+
{
139
+
return Err(blahg::errors::BlahgError::ConfigFeatureNotEnabled {
140
+
feature: "S3 storage requested but s3 feature is not enabled".to_string(),
141
+
});
142
+
}
143
+
} else {
144
+
let filesystem_storage =
145
+
Arc::new(FilesystemContentStorage::new(&config.attachment_storage).await?);
146
+
Arc::new(CachedContentStorage::new(filesystem_storage))
147
+
};
148
+
149
+
// Setup markdown render manager
150
+
let render_manager: Arc<dyn RenderManager> = Arc::new(ComrakRenderManager::new(&config.external_base));
151
+
152
+
// Setup template engine
153
+
let template_env = {
154
+
#[cfg(feature = "embed")]
155
+
{
156
+
AppEngine::from(build_env(
157
+
config.external_base.clone(),
158
+
env!("CARGO_PKG_VERSION").to_string(),
159
+
))
160
+
}
161
+
162
+
#[cfg(feature = "reload")]
163
+
{
164
+
AppEngine::from(build_env(config.external_base.clone()))
165
+
}
166
+
167
+
#[cfg(not(any(feature = "reload", feature = "embed")))]
168
+
{
169
+
use minijinja::Environment;
170
+
let mut env = Environment::new();
171
+
// Add basic templates for the minimal case
172
+
env.add_template(
173
+
"index.html",
174
+
"<!DOCTYPE html><html><head><title>{{ title }}</title></head><body><h1>{{ title }}</h1><ul>{% for post in posts %}<li><a href=\"/posts/{{ post.slug }}\">{{ post.title }}</a></li>{% endfor %}</ul></body></html>",
175
+
)
176
+
.unwrap();
177
+
env.add_template(
178
+
"post.html",
179
+
"<!DOCTYPE html><html><head><title>{{ post.title }}</title></head><body><h1>{{ post.title }}</h1><div>{{ post_content }}</div></body></html>",
180
+
)
181
+
.unwrap();
182
+
AppEngine::from(env)
183
+
}
184
+
};
185
+
186
+
// Create application state for HTTP server
187
+
let app_state = AppState::new(
188
+
storage.clone(),
189
+
content_storage.clone(),
190
+
render_manager,
191
+
config.clone(),
192
+
template_env,
193
+
);
194
+
195
+
// Create HTTP router
196
+
let app = create_router(app_state);
197
+
198
+
// Setup task tracking for graceful shutdown
199
+
let tracker = TaskTracker::new();
200
+
let token = CancellationToken::new();
201
+
202
+
// Setup signal handling
203
+
{
204
+
let tracker = tracker.clone();
205
+
let inner_token = token.clone();
206
+
207
+
let ctrl_c = async {
208
+
signal::ctrl_c()
209
+
.await
210
+
.expect("failed to install Ctrl+C handler");
211
+
};
212
+
213
+
let terminate = async {
214
+
signal::unix::signal(signal::unix::SignalKind::terminate())
215
+
.expect("failed to install signal handler")
216
+
.recv()
217
+
.await;
218
+
};
219
+
220
+
tokio::spawn(async move {
221
+
tokio::select! {
222
+
() = inner_token.cancelled() => { },
223
+
_ = terminate => {},
224
+
_ = ctrl_c => {},
225
+
}
226
+
227
+
tracker.close();
228
+
inner_token.cancel();
229
+
});
230
+
}
231
+
232
+
// Only start jetstream consumer and processor if enabled
233
+
if config.enable_jetstream {
234
+
let consumer = Consumer {};
235
+
let (blahg_handler, event_receiver) = consumer.create_blahg_handler();
236
+
237
+
let processor = EventProcessor::new(
238
+
storage.clone(),
239
+
content_storage,
240
+
config.clone(),
241
+
identity_resolver,
242
+
http_client,
243
+
);
244
+
245
+
let inner_token = token.clone();
246
+
tracker.spawn(async move {
247
+
tokio::select! {
248
+
result = processor.start_processing(event_receiver) => {
249
+
if let Err(err) = result {
250
+
tracing::error!("Event processor failed: {}", err);
251
+
}
252
+
}
253
+
() = inner_token.cancelled() => {
254
+
tracing::info!("Badge processor cancelled");
255
+
}
256
+
}
257
+
});
258
+
259
+
// Start Jetstream consumer with reconnect logic
260
+
let inner_token = token.clone();
261
+
let inner_config = config.clone();
262
+
tracker.spawn(async move {
263
+
let mut disconnect_times = Vec::new();
264
+
let disconnect_window = std::time::Duration::from_secs(60); // 1 minute window
265
+
let max_disconnects_per_minute = 1;
266
+
let reconnect_delay = std::time::Duration::from_secs(5);
267
+
268
+
loop {
269
+
// Create new consumer for each connection attempt
270
+
let jetstream_config = ConsumerTaskConfig {
271
+
user_agent: inner_config.user_agent.clone(),
272
+
compression: false,
273
+
zstd_dictionary_location: String::new(),
274
+
jetstream_hostname: "jetstream2.us-east.bsky.network".to_string(),
275
+
collections: vec![
276
+
"tools.smokesignal.blahg.content.*".to_string(),
277
+
"app.bsky.feed.*".to_string(),
278
+
"app.bsky.feed.like".to_string(),
279
+
"community.lexicon.interaction.*".to_string(),
280
+
],
281
+
dids: vec![],
282
+
max_message_size_bytes: Some(10 * 1024 * 1024), // 10MB
283
+
cursor: None,
284
+
require_hello: true,
285
+
};
286
+
287
+
let jetstream_consumer = JetstreamConsumer::new(jetstream_config);
288
+
289
+
// Register badge handler
290
+
if let Err(err) = jetstream_consumer.register_handler(blahg_handler.clone()).await {
291
+
tracing::error!("Failed to register processing handler: {}", err);
292
+
inner_token.cancel();
293
+
break;
294
+
}
295
+
296
+
// Register cursor writer if configured
297
+
if let Some(cursor_path) = inner_config.jetstream_cursor_path.clone() {
298
+
let cursor_writer = consumer.create_cursor_writer_handler(cursor_path);
299
+
if let Err(err) = jetstream_consumer.register_handler(cursor_writer).await {
300
+
tracing::error!("Failed to register cursor writer: {}", err);
301
+
inner_token.cancel();
302
+
break;
303
+
}
304
+
}
305
+
306
+
tokio::select! {
307
+
result = jetstream_consumer.run_background(inner_token.clone()) => {
308
+
if let Err(err) = result {
309
+
let now = std::time::Instant::now();
310
+
disconnect_times.push(now);
311
+
312
+
// Remove disconnect times older than the window
313
+
disconnect_times.retain(|&t| now.duration_since(t) <= disconnect_window);
314
+
315
+
if disconnect_times.len() > max_disconnects_per_minute {
316
+
let rate_error = blahg::errors::BlahgError::ConsumerDisconnectRateExceeded {
317
+
disconnect_count: disconnect_times.len(),
318
+
duration_mins: 1,
319
+
};
320
+
tracing::error!(?rate_error, "Jetstream disconnect rate exceeded, exiting");
321
+
inner_token.cancel();
322
+
break;
323
+
}
324
+
325
+
let disconnect_error = blahg::errors::BlahgError::ConsumerDisconnected {
326
+
details: err.to_string(),
327
+
};
328
+
tracing::error!(?disconnect_error, "Jetstream disconnected, reconnecting in {:?}", reconnect_delay);
329
+
330
+
// Wait before reconnecting
331
+
tokio::select! {
332
+
() = tokio::time::sleep(reconnect_delay) => {},
333
+
() = inner_token.cancelled() => {
334
+
tracing::info!("Jetstream consumer cancelled during reconnect delay");
335
+
break;
336
+
}
337
+
}
338
+
339
+
// Continue the loop to reconnect
340
+
continue;
341
+
}
342
+
}
343
+
() = inner_token.cancelled() => {
344
+
tracing::info!("Jetstream consumer cancelled");
345
+
break;
346
+
}
347
+
}
348
+
349
+
// If we reach here, the consumer exited without error (unlikely)
350
+
tracing::info!("Jetstream consumer exited normally");
351
+
break;
352
+
}
353
+
});
354
+
} else {
355
+
tracing::info!("Jetstream consumer and processor disabled by configuration");
356
+
}
357
+
358
+
// Start HTTP server
359
+
{
360
+
let inner_config = config.clone();
361
+
let http_port = inner_config.http.port;
362
+
let inner_token = token.clone();
363
+
tracker.spawn(async move {
364
+
let bind_address = format!("0.0.0.0:{http_port}");
365
+
tracing::info!("bind_address {bind_address}");
366
+
let listener = TcpListener::bind(&bind_address).await.unwrap();
367
+
368
+
let shutdown_token = inner_token.clone();
369
+
let result = axum::serve(listener, app)
370
+
.with_graceful_shutdown(async move {
371
+
tokio::select! {
372
+
() = shutdown_token.cancelled() => { }
373
+
}
374
+
tracing::info!("axum graceful shutdown complete");
375
+
})
376
+
.await;
377
+
if let Err(err) = result {
378
+
tracing::error!("axum task failed: {}", err);
379
+
}
380
+
381
+
inner_token.cancel();
382
+
});
383
+
}
384
+
385
+
tracing::info!("Blahg started successfully");
386
+
387
+
// Wait for all tasks to complete
388
+
tracker.wait().await;
389
+
390
+
tracing::info!("Blahg shutting down");
391
+
Ok(())
392
+
}
+143
src/config.rs
+143
src/config.rs
···
···
1
+
use serde::{Deserialize, Serialize};
2
+
use std::time::Duration;
3
+
4
+
use crate::errors::{BlahgError, Result};
5
+
6
+
/// Application configuration loaded from environment variables.
7
+
#[derive(Debug, Clone, Serialize, Deserialize)]
8
+
pub struct Config {
9
+
pub http: HttpConfig,
10
+
pub external_base: String,
11
+
pub certificate_bundles: Vec<String>,
12
+
pub user_agent: String,
13
+
pub plc_hostname: String,
14
+
pub dns_nameservers: Vec<std::net::IpAddr>,
15
+
pub http_client_timeout: Duration,
16
+
pub author: String,
17
+
pub attachment_storage: String,
18
+
pub database_url: String,
19
+
pub jetstream_cursor_path: Option<String>,
20
+
pub enable_jetstream: bool,
21
+
}
22
+
23
+
#[derive(Debug, Clone, Serialize, Deserialize)]
24
+
pub struct HttpConfig {
25
+
pub port: u16,
26
+
pub static_path: String,
27
+
pub templates_path: String,
28
+
}
29
+
30
+
impl Default for Config {
31
+
fn default() -> Self {
32
+
Self {
33
+
http: HttpConfig::default(),
34
+
external_base: "http://localhost:8080".to_string(),
35
+
certificate_bundles: Vec::new(),
36
+
user_agent: format!(
37
+
"Blahg/{} (+https://tangled.sh/@smokesignal.events/blahg)",
38
+
env!("CARGO_PKG_VERSION")
39
+
),
40
+
plc_hostname: "plc.directory".to_string(),
41
+
dns_nameservers: Vec::new(),
42
+
http_client_timeout: Duration::from_secs(10),
43
+
author: String::new(),
44
+
attachment_storage: "./attachments".to_string(),
45
+
database_url: "sqlite://blahg.db".to_string(),
46
+
jetstream_cursor_path: None,
47
+
enable_jetstream: true,
48
+
}
49
+
}
50
+
}
51
+
52
+
impl Default for HttpConfig {
53
+
fn default() -> Self {
54
+
Self {
55
+
port: 8080,
56
+
static_path: format!("{}/static", env!("CARGO_MANIFEST_DIR")),
57
+
templates_path: format!("{}/templates", env!("CARGO_MANIFEST_DIR")),
58
+
}
59
+
}
60
+
}
61
+
62
+
impl Config {
63
+
/// Create configuration from environment variables.
64
+
pub fn from_env() -> Result<Self> {
65
+
let mut config = Self::default();
66
+
67
+
if let Ok(port) = std::env::var("HTTP_PORT") {
68
+
config.http.port = port
69
+
.parse()
70
+
.map_err(|_| BlahgError::ConfigHttpPortInvalid { port: port.clone() })?;
71
+
}
72
+
73
+
if let Ok(static_path) = std::env::var("HTTP_STATIC_PATH") {
74
+
config.http.static_path = static_path;
75
+
}
76
+
77
+
if let Ok(templates_path) = std::env::var("HTTP_TEMPLATES_PATH") {
78
+
config.http.templates_path = templates_path;
79
+
}
80
+
81
+
if let Ok(external_base) = std::env::var("EXTERNAL_BASE") {
82
+
config.external_base = external_base;
83
+
}
84
+
85
+
if let Ok(cert_bundles) = std::env::var("CERTIFICATE_BUNDLES") {
86
+
config.certificate_bundles = cert_bundles
87
+
.split(';')
88
+
.map(|s| s.trim().to_string())
89
+
.filter(|s| !s.is_empty())
90
+
.collect();
91
+
}
92
+
93
+
if let Ok(user_agent) = std::env::var("USER_AGENT") {
94
+
config.user_agent = user_agent;
95
+
}
96
+
97
+
if let Ok(plc_hostname) = std::env::var("PLC_HOSTNAME") {
98
+
config.plc_hostname = plc_hostname;
99
+
}
100
+
101
+
if let Ok(dns_nameservers) = std::env::var("DNS_NAMESERVERS") {
102
+
config.dns_nameservers = dns_nameservers
103
+
.split(',')
104
+
.map(|s| s.trim())
105
+
.filter(|s| !s.is_empty())
106
+
.map(|s| {
107
+
s.parse::<std::net::IpAddr>()
108
+
.map_err(|e| BlahgError::NameserverParsingFailed(s.to_string(), e))
109
+
})
110
+
.collect::<Result<Vec<std::net::IpAddr>>>()?;
111
+
}
112
+
113
+
if let Ok(timeout) = std::env::var("HTTP_CLIENT_TIMEOUT") {
114
+
config.http_client_timeout = duration_str::parse(&timeout).map_err(|e| {
115
+
BlahgError::ConfigHttpTimeoutInvalid {
116
+
details: e.to_string(),
117
+
}
118
+
})?;
119
+
}
120
+
121
+
if let Ok(author) = std::env::var("AUTHOR") {
122
+
config.author = author;
123
+
}
124
+
125
+
if let Ok(attachment_storage) = std::env::var("ATTACHMENT_STORAGE") {
126
+
config.attachment_storage = attachment_storage;
127
+
}
128
+
129
+
if let Ok(database_url) = std::env::var("DATABASE_URL") {
130
+
config.database_url = database_url;
131
+
}
132
+
133
+
if let Ok(jetstream_cursor_path) = std::env::var("JETSTREAM_CURSOR_PATH") {
134
+
config.jetstream_cursor_path = Some(jetstream_cursor_path);
135
+
}
136
+
137
+
if let Ok(enable_jetstream) = std::env::var("ENABLE_JETSTREAM") {
138
+
config.enable_jetstream = enable_jetstream.parse().unwrap_or(true);
139
+
}
140
+
141
+
Ok(config)
142
+
}
143
+
}
+176
src/consumer.rs
+176
src/consumer.rs
···
···
1
+
use std::sync::Arc;
2
+
use std::sync::atomic::{AtomicU64, Ordering};
3
+
use std::time::{Duration, Instant};
4
+
5
+
use crate::errors::BlahgError;
6
+
use anyhow::Result;
7
+
use async_trait::async_trait;
8
+
use atproto_jetstream::{EventHandler, JetstreamEvent};
9
+
use tokio::sync::Mutex;
10
+
use tokio::sync::mpsc;
11
+
12
+
pub type BlahgEventReceiver = mpsc::UnboundedReceiver<BlahgEvent>;
13
+
14
+
#[derive(Debug, Clone)]
15
+
pub enum BlahgEvent {
16
+
Commit {
17
+
did: String,
18
+
collection: String,
19
+
rkey: String,
20
+
cid: String,
21
+
record: serde_json::Value,
22
+
},
23
+
Delete {
24
+
did: String,
25
+
collection: String,
26
+
rkey: String,
27
+
},
28
+
}
29
+
30
+
pub struct BlahgEventHandler {
31
+
id: String,
32
+
event_sender: mpsc::UnboundedSender<BlahgEvent>,
33
+
}
34
+
35
+
impl BlahgEventHandler {
36
+
fn new(id: String, event_sender: mpsc::UnboundedSender<BlahgEvent>) -> Self {
37
+
Self { id, event_sender }
38
+
}
39
+
}
40
+
41
+
#[async_trait]
42
+
impl EventHandler for BlahgEventHandler {
43
+
async fn handle_event(&self, event: JetstreamEvent) -> Result<()> {
44
+
let award_event = match event {
45
+
JetstreamEvent::Commit { did, commit, .. } => {
46
+
// Filter for supported collection types
47
+
match commit.collection.as_str() {
48
+
"tools.smokesignal.blahg.content.post"
49
+
| "app.bsky.feed.post"
50
+
| "app.bsky.feed.like"
51
+
| "community.lexicon.interaction.like" => BlahgEvent::Commit {
52
+
did,
53
+
collection: commit.collection,
54
+
rkey: commit.rkey,
55
+
cid: commit.cid,
56
+
record: commit.record,
57
+
},
58
+
_ => return Ok(()),
59
+
}
60
+
}
61
+
JetstreamEvent::Delete { did, commit, .. } => {
62
+
// Filter for supported collection types
63
+
match commit.collection.as_str() {
64
+
"tools.smokesignal.blahg.content.post"
65
+
| "app.bsky.feed.post"
66
+
| "app.bsky.feed.like"
67
+
| "community.lexicon.interaction.like" => BlahgEvent::Delete {
68
+
did,
69
+
collection: commit.collection,
70
+
rkey: commit.rkey,
71
+
},
72
+
_ => return Ok(()),
73
+
}
74
+
}
75
+
JetstreamEvent::Identity { .. } | JetstreamEvent::Account { .. } => {
76
+
return Ok(());
77
+
}
78
+
};
79
+
80
+
if let Err(err) = self.event_sender.send(award_event) {
81
+
let blahg_error = BlahgError::ConsumerQueueSendFailed {
82
+
details: err.to_string(),
83
+
};
84
+
tracing::error!(?blahg_error);
85
+
}
86
+
87
+
Ok(())
88
+
}
89
+
90
+
fn handler_id(&self) -> String {
91
+
self.id.clone()
92
+
}
93
+
}
94
+
95
+
/// Cursor writer handler that periodically writes the latest time_us to a file
96
+
pub struct CursorWriterHandler {
97
+
id: String,
98
+
cursor_path: String,
99
+
last_time_us: Arc<AtomicU64>,
100
+
last_write: Arc<Mutex<Instant>>,
101
+
write_interval: Duration,
102
+
}
103
+
104
+
impl CursorWriterHandler {
105
+
fn new(id: String, cursor_path: String) -> Self {
106
+
Self {
107
+
id,
108
+
cursor_path,
109
+
last_time_us: Arc::new(AtomicU64::new(0)),
110
+
last_write: Arc::new(Mutex::new(Instant::now())),
111
+
write_interval: Duration::from_secs(30),
112
+
}
113
+
}
114
+
115
+
async fn maybe_write_cursor(&self) -> Result<()> {
116
+
let current_time_us = self.last_time_us.load(Ordering::Relaxed);
117
+
if current_time_us == 0 {
118
+
return Ok(());
119
+
}
120
+
121
+
let mut last_write = self.last_write.lock().await;
122
+
if last_write.elapsed() >= self.write_interval {
123
+
tokio::fs::write(&self.cursor_path, current_time_us.to_string()).await?;
124
+
*last_write = Instant::now();
125
+
tracing::debug!("Wrote cursor to {}: {}", self.cursor_path, current_time_us);
126
+
}
127
+
Ok(())
128
+
}
129
+
}
130
+
131
+
#[async_trait]
132
+
impl EventHandler for CursorWriterHandler {
133
+
async fn handle_event(&self, event: JetstreamEvent) -> Result<()> {
134
+
// Extract time_us from any event type
135
+
let time_us = match &event {
136
+
JetstreamEvent::Commit { time_us, .. } => *time_us,
137
+
JetstreamEvent::Delete { time_us, .. } => *time_us,
138
+
JetstreamEvent::Identity { time_us, .. } => *time_us,
139
+
JetstreamEvent::Account { time_us, .. } => *time_us,
140
+
};
141
+
142
+
// Update the latest time_us
143
+
self.last_time_us.store(time_us, Ordering::Relaxed);
144
+
145
+
// Try to write the cursor periodically
146
+
if let Err(err) = self.maybe_write_cursor().await {
147
+
tracing::warn!("Failed to write cursor: {}", err);
148
+
}
149
+
150
+
Ok(())
151
+
}
152
+
153
+
fn handler_id(&self) -> String {
154
+
self.id.clone()
155
+
}
156
+
}
157
+
158
+
pub struct Consumer {}
159
+
160
+
impl Consumer {
161
+
pub fn create_blahg_handler(&self) -> (Arc<BlahgEventHandler>, BlahgEventReceiver) {
162
+
let (sender, receiver) = mpsc::unbounded_channel();
163
+
let handler = Arc::new(BlahgEventHandler::new(
164
+
"blagh-processor".to_string(),
165
+
sender,
166
+
));
167
+
(handler, receiver)
168
+
}
169
+
170
+
pub fn create_cursor_writer_handler(&self, cursor_path: String) -> Arc<CursorWriterHandler> {
171
+
Arc::new(CursorWriterHandler::new(
172
+
"cursor-writer".to_string(),
173
+
cursor_path,
174
+
))
175
+
}
176
+
}
+305
src/errors.rs
+305
src/errors.rs
···
···
1
+
use axum::response::{IntoResponse, Response};
2
+
use reqwest::StatusCode;
3
+
use thiserror::Error;
4
+
5
+
/// Main error type for the Blahg application
6
+
///
7
+
/// All errors follow the format: error-blahg-<domain>-<number> <message>: <details>
8
+
/// Domains are organized alphabetically: config, consumer, http, process, storage
9
+
#[derive(Error, Debug)]
10
+
pub enum BlahgError {
11
+
// Config domain - Configuration-related errors
12
+
#[error("error-blahg-config-1 Failed to parse HTTP client timeout: {details}")]
13
+
/// Failed to parse HTTP client timeout from environment variable.
14
+
ConfigHttpTimeoutInvalid {
15
+
/// Details about the parsing error.
16
+
details: String,
17
+
},
18
+
19
+
#[error("error-blahg-config-2 Failed to parse HTTP port: {port}")]
20
+
/// Failed to parse HTTP port from environment variable.
21
+
ConfigHttpPortInvalid {
22
+
/// The invalid port value.
23
+
port: String,
24
+
},
25
+
26
+
#[error("error-blahg-config-3 Invalid S3 URL format: {details}")]
27
+
/// Failed to parse S3 URL format from environment variable.
28
+
ConfigS3UrlInvalid {
29
+
/// Details about the S3 URL parsing error.
30
+
details: String,
31
+
},
32
+
33
+
#[error("error-blahg-config-4 Required feature not enabled: {feature}")]
34
+
/// Required feature is not enabled in the build.
35
+
ConfigFeatureNotEnabled {
36
+
/// The feature that is required but not enabled.
37
+
feature: String,
38
+
},
39
+
40
+
// Consumer domain - Jetstream consumer errors
41
+
/// Error when a DNS nameserver IP cannot be parsed.
42
+
///
43
+
/// This error occurs when the DNS_NAMESERVERS environment variable contains
44
+
/// an IP address that cannot be parsed as a valid IpAddr.
45
+
#[error("error-blahg-config-5 Unable to parse nameserver IP '{0}': {1}")]
46
+
NameserverParsingFailed(String, std::net::AddrParseError),
47
+
48
+
// Consumer domain - Jetstream consumer errors
49
+
#[error("error-blahg-consumer-1 Failed to send badge event to queue: {details}")]
50
+
/// Failed to send badge event to the processing queue.
51
+
ConsumerQueueSendFailed {
52
+
/// Details about the send failure.
53
+
details: String,
54
+
},
55
+
56
+
#[error("error-blahg-consumer-2 Jetstream disconnected: {details}")]
57
+
/// Jetstream consumer disconnected.
58
+
ConsumerDisconnected {
59
+
/// Details about the disconnection.
60
+
details: String,
61
+
},
62
+
63
+
#[error(
64
+
"error-blahg-consumer-3 Jetstream disconnect rate exceeded: {disconnect_count} disconnects in {duration_mins} minutes"
65
+
)]
66
+
/// Jetstream consumer disconnect rate exceeded the allowable limit.
67
+
ConsumerDisconnectRateExceeded {
68
+
/// Number of disconnects in the time period.
69
+
disconnect_count: usize,
70
+
/// Duration in minutes for the disconnect rate calculation.
71
+
duration_mins: u64,
72
+
},
73
+
74
+
// HTTP domain - HTTP server and template errors
75
+
#[error("error-blahg-http-1 Template rendering failed: {template}")]
76
+
/// Template rendering failed in HTTP response.
77
+
HttpTemplateRenderFailed {
78
+
/// The template that failed to render.
79
+
template: String,
80
+
},
81
+
82
+
#[error("error-blahg-http-2 Internal server error")]
83
+
/// Generic internal server error for HTTP responses.
84
+
HttpInternalServerError,
85
+
86
+
// Process domain - Badge processing errors
87
+
#[error("error-blahg-process-1 Invalid AT-URI format: {uri}")]
88
+
/// Invalid AT-URI format encountered during processing.
89
+
ProcessInvalidAturi {
90
+
/// The invalid URI string.
91
+
uri: String,
92
+
},
93
+
94
+
#[error("error-blahg-process-2 Failed to resolve identity for {did}: {details}")]
95
+
/// Identity resolution failed with detailed error information.
96
+
ProcessIdentityResolutionFailed {
97
+
/// The DID that could not be resolved.
98
+
did: String,
99
+
/// Detailed error information.
100
+
details: String,
101
+
},
102
+
103
+
#[error("error-blahg-process-3 Failed to fetch badge record: {uri}")]
104
+
/// Failed to fetch badge record from AT Protocol.
105
+
ProcessBadgeFetchFailed {
106
+
/// The badge URI that could not be fetched.
107
+
uri: String,
108
+
},
109
+
110
+
#[error("error-blahg-process-4 Failed to fetch badge {uri}: {details}")]
111
+
/// Badge record fetch failed with detailed error information.
112
+
ProcessBadgeRecordFetchFailed {
113
+
/// The badge URI that could not be fetched.
114
+
uri: String,
115
+
/// Detailed error information.
116
+
details: String,
117
+
},
118
+
119
+
#[error("error-blahg-process-5 Failed to download badge image: {image_ref}")]
120
+
/// Failed to download badge image.
121
+
ProcessImageDownloadFailed {
122
+
/// Reference to the image that failed to download.
123
+
image_ref: String,
124
+
},
125
+
126
+
#[error("error-blahg-process-6 Failed to process badge event: {event_type}")]
127
+
/// Failed to process a badge event from Jetstream.
128
+
ProcessEventHandlingFailed {
129
+
/// Type of event that failed to process.
130
+
event_type: String,
131
+
},
132
+
133
+
#[error("error-blahg-process-7 Image file too large: {size} bytes exceeds 3MB limit")]
134
+
/// Badge image file exceeds maximum allowed size.
135
+
ProcessImageTooLarge {
136
+
/// The actual size of the image in bytes.
137
+
size: usize,
138
+
},
139
+
140
+
#[error("error-blahg-process-8 Failed to decode image: {details}")]
141
+
/// Failed to decode badge image data.
142
+
ProcessImageDecodeFailed {
143
+
/// Details about the decode error.
144
+
details: String,
145
+
},
146
+
147
+
#[error("error-blahg-process-9 Unsupported image format: {format}")]
148
+
/// Badge image is in an unsupported format.
149
+
ProcessUnsupportedImageFormat {
150
+
/// The unsupported image format.
151
+
format: String,
152
+
},
153
+
154
+
#[error(
155
+
"error-blahg-process-10 Image dimensions too small: {width}x{height}, minimum is 512x512"
156
+
)]
157
+
/// Badge image dimensions are below minimum requirements.
158
+
ProcessImageTooSmall {
159
+
/// The actual width of the image.
160
+
width: u32,
161
+
/// The actual height of the image.
162
+
height: u32,
163
+
},
164
+
165
+
#[error("error-blahg-process-11 Image width too small after resize: {width}, minimum is 512")]
166
+
/// Badge image width is below minimum after resize.
167
+
ProcessImageWidthTooSmall {
168
+
/// The actual width after resize.
169
+
width: u32,
170
+
},
171
+
172
+
#[error("error-blahg-process-12 No signatures field found in record")]
173
+
/// Badge record is missing required signatures field.
174
+
ProcessNoSignaturesField,
175
+
176
+
#[error("error-blahg-process-13 Missing issuer field in signature")]
177
+
/// Signature is missing required issuer field.
178
+
ProcessMissingIssuerField,
179
+
180
+
#[error("error-blahg-process-14 Missing signature field in signature")]
181
+
/// Signature object is missing required signature field.
182
+
ProcessMissingSignatureField,
183
+
184
+
#[error("error-blahg-process-15 Record serialization failed: {details}")]
185
+
/// Failed to serialize record for signature verification.
186
+
ProcessRecordSerializationFailed {
187
+
/// Details about the serialization error.
188
+
details: String,
189
+
},
190
+
191
+
#[error("error-blahg-process-16 Signature decoding failed: {details}")]
192
+
/// Failed to decode signature bytes.
193
+
ProcessSignatureDecodingFailed {
194
+
/// Details about the decoding error.
195
+
details: String,
196
+
},
197
+
198
+
#[error(
199
+
"error-blahg-process-17 Cryptographic validation failed for issuer {issuer}: {details}"
200
+
)]
201
+
/// Cryptographic signature validation failed.
202
+
ProcessCryptographicValidationFailed {
203
+
/// The issuer DID whose signature validation failed.
204
+
issuer: String,
205
+
/// Detailed error information.
206
+
details: String,
207
+
},
208
+
209
+
// Storage domain - Database and storage errors
210
+
#[error("error-blahg-storage-1 Database operation failed: {operation}")]
211
+
/// Database operation failed.
212
+
StorageDatabaseFailed {
213
+
/// Description of the failed operation.
214
+
operation: String,
215
+
},
216
+
217
+
#[error("error-blahg-storage-2 File storage operation failed: {operation}")]
218
+
/// File storage operation failed.
219
+
StorageFileOperationFailed {
220
+
/// Description of the failed file operation.
221
+
operation: String,
222
+
},
223
+
}
224
+
225
+
/// Result type alias for convenience
226
+
pub type Result<T> = std::result::Result<T, BlahgError>;
227
+
228
+
impl From<sqlx::Error> for BlahgError {
229
+
fn from(err: sqlx::Error) -> Self {
230
+
BlahgError::StorageDatabaseFailed {
231
+
operation: err.to_string(),
232
+
}
233
+
}
234
+
}
235
+
236
+
impl From<serde_json::Error> for BlahgError {
237
+
fn from(err: serde_json::Error) -> Self {
238
+
BlahgError::ProcessEventHandlingFailed {
239
+
event_type: err.to_string(),
240
+
}
241
+
}
242
+
}
243
+
244
+
impl From<reqwest::Error> for BlahgError {
245
+
fn from(err: reqwest::Error) -> Self {
246
+
BlahgError::ProcessBadgeFetchFailed {
247
+
uri: err.to_string(),
248
+
}
249
+
}
250
+
}
251
+
252
+
impl From<image::ImageError> for BlahgError {
253
+
fn from(err: image::ImageError) -> Self {
254
+
BlahgError::ProcessImageDownloadFailed {
255
+
image_ref: err.to_string(),
256
+
}
257
+
}
258
+
}
259
+
260
+
impl From<minijinja::Error> for BlahgError {
261
+
fn from(err: minijinja::Error) -> Self {
262
+
BlahgError::HttpTemplateRenderFailed {
263
+
template: err.to_string(),
264
+
}
265
+
}
266
+
}
267
+
268
+
impl From<std::io::Error> for BlahgError {
269
+
fn from(err: std::io::Error) -> Self {
270
+
BlahgError::ProcessImageDownloadFailed {
271
+
image_ref: err.to_string(),
272
+
}
273
+
}
274
+
}
275
+
276
+
impl From<std::num::ParseIntError> for BlahgError {
277
+
fn from(err: std::num::ParseIntError) -> Self {
278
+
BlahgError::ConfigHttpPortInvalid {
279
+
port: err.to_string(),
280
+
}
281
+
}
282
+
}
283
+
284
+
impl From<anyhow::Error> for BlahgError {
285
+
fn from(err: anyhow::Error) -> Self {
286
+
BlahgError::ProcessEventHandlingFailed {
287
+
event_type: err.to_string(),
288
+
}
289
+
}
290
+
}
291
+
292
+
impl From<atproto_record::errors::AturiError> for BlahgError {
293
+
fn from(err: atproto_record::errors::AturiError) -> Self {
294
+
BlahgError::ProcessInvalidAturi {
295
+
uri: err.to_string(),
296
+
}
297
+
}
298
+
}
299
+
300
+
impl IntoResponse for BlahgError {
301
+
fn into_response(self) -> Response {
302
+
tracing::error!(error = ?self, "internal server error");
303
+
(StatusCode::INTERNAL_SERVER_ERROR).into_response()
304
+
}
305
+
}
+339
src/http.rs
+339
src/http.rs
···
···
1
+
//! HTTP server implementation with Axum web framework.
2
+
//!
3
+
//! Provides REST API endpoints and template-rendered pages for displaying
4
+
//! blog posts, with content storage and markdown rendering.
5
+
6
+
use std::sync::Arc;
7
+
8
+
use axum::{
9
+
Router,
10
+
body::Body,
11
+
extract::{Path, State},
12
+
http::{StatusCode, header},
13
+
response::{IntoResponse, Response},
14
+
routing::get,
15
+
};
16
+
use axum_template::RenderHtml;
17
+
use axum_template::engine::Engine;
18
+
use minijinja::context;
19
+
use serde::{Deserialize, Serialize};
20
+
use tower_http::services::ServeDir;
21
+
22
+
use crate::{
23
+
config::Config,
24
+
errors::{BlahgError, Result},
25
+
render::RenderManager,
26
+
storage::{ContentStorage, Post, Storage},
27
+
};
28
+
29
+
#[cfg(feature = "reload")]
30
+
use minijinja_autoreload::AutoReloader;
31
+
32
+
#[cfg(feature = "reload")]
33
+
/// Template engine with auto-reloading support for development.
34
+
pub type AppEngine = Engine<AutoReloader>;
35
+
36
+
#[cfg(feature = "embed")]
37
+
use minijinja::Environment;
38
+
39
+
#[cfg(feature = "embed")]
40
+
pub type AppEngine = Engine<Environment<'static>>;
41
+
42
+
#[cfg(not(any(feature = "reload", feature = "embed")))]
43
+
pub type AppEngine = Engine<minijinja::Environment<'static>>;
44
+
45
+
/// Application state shared across HTTP handlers.
46
+
#[derive(Clone)]
47
+
pub struct AppState {
48
+
/// Database storage for posts and identities.
49
+
pub(crate) storage: Arc<dyn Storage>,
50
+
/// Content storage for markdown files.
51
+
pub(crate) content_storage: Arc<dyn ContentStorage>,
52
+
/// Markdown renderer.
53
+
pub(crate) render_manager: Arc<dyn RenderManager>,
54
+
/// Application configuration.
55
+
pub(crate) config: Arc<Config>,
56
+
/// Template engine for rendering HTML responses.
57
+
pub(crate) template_env: AppEngine,
58
+
}
59
+
60
+
impl AppState {
61
+
/// Create a new AppState with the given dependencies.
62
+
pub fn new(
63
+
storage: Arc<dyn Storage>,
64
+
content_storage: Arc<dyn ContentStorage>,
65
+
render_manager: Arc<dyn RenderManager>,
66
+
config: Arc<Config>,
67
+
template_env: AppEngine,
68
+
) -> Self {
69
+
Self {
70
+
storage,
71
+
content_storage,
72
+
render_manager,
73
+
config,
74
+
template_env,
75
+
}
76
+
}
77
+
}
78
+
79
+
/// Display structure for post listing.
80
+
#[derive(Debug, Serialize, Deserialize)]
81
+
struct PostDisplay {
82
+
slug: String,
83
+
title: String,
84
+
created_at: String,
85
+
}
86
+
87
+
impl TryFrom<Post> for PostDisplay {
88
+
type Error = BlahgError;
89
+
90
+
fn try_from(post: Post) -> Result<Self> {
91
+
Ok(PostDisplay {
92
+
slug: format!("{}-{}", post.record_key, post.slug),
93
+
title: post.title,
94
+
created_at: post.created_at.format("%Y-%m-%d %H:%M UTC").to_string(),
95
+
})
96
+
}
97
+
}
98
+
99
+
/// Create the main application router with all HTTP routes.
100
+
pub fn create_router(state: AppState) -> Router {
101
+
Router::new()
102
+
.route("/", get(handle_index))
103
+
.route("/posts/{full_slug}", get(handle_post))
104
+
.route(
105
+
"/posts/{full_slug}/{collection}",
106
+
get(handle_post_references),
107
+
)
108
+
.route("/content/{cid}", get(handle_content))
109
+
.nest_service("/static", ServeDir::new(&state.config.http.static_path))
110
+
.with_state(state)
111
+
}
112
+
113
+
/// GET / - Handle the index page (home page).
114
+
/// Gets all posts from storage and lists them ordered by most recent to oldest.
115
+
async fn handle_index(State(state): State<AppState>) -> Result<impl IntoResponse> {
116
+
match get_all_posts(&state).await {
117
+
Ok(posts) => Ok(RenderHtml(
118
+
"index.html",
119
+
state.template_env.clone(),
120
+
context! {
121
+
title => "Recent Posts",
122
+
posts => posts,
123
+
},
124
+
)),
125
+
Err(_) => Err(BlahgError::HttpInternalServerError),
126
+
}
127
+
}
128
+
129
+
/// GET /posts/{record_key}-{slug} - Handle post pages.
130
+
/// Gets the post from storage using the record key to construct an AT-URI, gets the content from file storage,
131
+
/// and renders the markdown content from file storage into html to be passed to the template.
132
+
async fn handle_post(
133
+
Path(full_slug): Path<String>,
134
+
State(state): State<AppState>,
135
+
) -> Result<impl IntoResponse> {
136
+
let record_key = match full_slug.split_once('-') {
137
+
Some((value, _)) => value,
138
+
None => return Ok((StatusCode::NOT_FOUND).into_response()),
139
+
};
140
+
141
+
// Construct AT-URI using the author config and record key
142
+
let aturi = format!(
143
+
"at://{}/tools.smokesignal.blahg.content.post/{}",
144
+
state.config.author, record_key
145
+
);
146
+
147
+
// Get the post from storage using the AT-URI
148
+
let post = match state.storage.get_post(&aturi).await? {
149
+
Some(post) => post,
150
+
None => return Ok((StatusCode::NOT_FOUND).into_response()),
151
+
};
152
+
153
+
let post_display = match PostDisplay::try_from(post.clone()) {
154
+
Ok(value) => value,
155
+
Err(_) => return Ok((StatusCode::INTERNAL_SERVER_ERROR).into_response()),
156
+
};
157
+
158
+
// Get the content from file storage using the content CID
159
+
let content_data = match state.content_storage.read_content(&post.content).await {
160
+
Ok(data) => data,
161
+
Err(_) => return Ok((StatusCode::NOT_FOUND).into_response()),
162
+
};
163
+
164
+
// Convert content data to string
165
+
let markdown_content = match String::from_utf8(content_data) {
166
+
Ok(content) => content,
167
+
Err(_) => return Ok((StatusCode::INTERNAL_SERVER_ERROR).into_response()),
168
+
};
169
+
170
+
// Render the markdown content into HTML
171
+
let html_content = match state
172
+
.render_manager
173
+
.render_markdown(&markdown_content)
174
+
.await
175
+
{
176
+
Ok(html) => html,
177
+
Err(_) => return Ok((StatusCode::INTERNAL_SERVER_ERROR).into_response()),
178
+
};
179
+
180
+
let collection_counts = match state.storage.get_post_reference_count(&aturi).await {
181
+
Ok(value) => value,
182
+
Err(_) => return Ok((StatusCode::INTERNAL_SERVER_ERROR).into_response()),
183
+
};
184
+
185
+
let total_activity: i64 = collection_counts.values().sum();
186
+
187
+
// Render the template with the post data and rendered content
188
+
Ok(RenderHtml(
189
+
"post.html",
190
+
state.template_env.clone(),
191
+
context! {
192
+
post => post_display,
193
+
post_content => html_content,
194
+
total_activity,
195
+
collection_counts,
196
+
},
197
+
)
198
+
.into_response())
199
+
}
200
+
201
+
/// GET /posts/{record_key}-{slug}/{collection} - Handle post references by collection.
202
+
/// Gets the post from storage using the record key to construct an AT-URI, verifies the post content exists,
203
+
/// and returns the list of post references for the specified collection.
204
+
async fn handle_post_references(
205
+
Path((full_slug, collection)): Path<(String, String)>,
206
+
State(state): State<AppState>,
207
+
) -> Result<impl IntoResponse> {
208
+
let record_key = match full_slug.split_once('-') {
209
+
Some((value, _)) => value,
210
+
None => return Ok((StatusCode::NOT_FOUND).into_response()),
211
+
};
212
+
213
+
// Construct AT-URI using the author config and record key
214
+
let aturi = format!(
215
+
"at://{}/tools.smokesignal.blahg.content.post/{}",
216
+
state.config.author, record_key
217
+
);
218
+
219
+
// Get the post from storage using the AT-URI
220
+
let post = match state.storage.get_post(&aturi).await? {
221
+
Some(post) => post,
222
+
None => return Ok((StatusCode::NOT_FOUND).into_response()),
223
+
};
224
+
225
+
let post_display: PostDisplay = match PostDisplay::try_from(post.clone()) {
226
+
Ok(value) => value,
227
+
Err(_) => return Ok((StatusCode::INTERNAL_SERVER_ERROR).into_response()),
228
+
};
229
+
230
+
// Verify the post content exists in content storage
231
+
let content_exists = match state.content_storage.content_exists(&post.content).await {
232
+
Ok(exists) => exists,
233
+
Err(_) => return Ok((StatusCode::INTERNAL_SERVER_ERROR).into_response()),
234
+
};
235
+
236
+
if !content_exists {
237
+
return Ok((StatusCode::NOT_FOUND).into_response());
238
+
}
239
+
240
+
// Get post references for this post and collection
241
+
let post_references = match state
242
+
.storage
243
+
.get_post_references_for_post_for_collection(&aturi, &collection)
244
+
.await
245
+
{
246
+
Ok(references) => references,
247
+
Err(_) => return Ok((StatusCode::INTERNAL_SERVER_ERROR).into_response()),
248
+
};
249
+
250
+
// Render the template with the post data and references
251
+
Ok(RenderHtml(
252
+
"post_references.html",
253
+
state.template_env.clone(),
254
+
context! {
255
+
post => post_display,
256
+
collection => collection,
257
+
post_references => post_references,
258
+
},
259
+
)
260
+
.into_response())
261
+
}
262
+
263
+
/// GET /content/{cid} - Handle content requests.
264
+
/// Gets content from content storage and returns it as a response.
265
+
async fn handle_content(
266
+
Path(cid): Path<String>,
267
+
State(state): State<AppState>,
268
+
) -> Result<impl IntoResponse> {
269
+
// Check if content exists
270
+
let exists = match state.content_storage.content_exists(&cid).await {
271
+
Ok(exists) => exists,
272
+
Err(_) => return Ok((StatusCode::INTERNAL_SERVER_ERROR).into_response()),
273
+
};
274
+
275
+
if !exists {
276
+
return Ok((StatusCode::NOT_FOUND).into_response());
277
+
}
278
+
279
+
// Read the content data
280
+
let content_data = match state.content_storage.read_content(&cid).await {
281
+
Ok(data) => data,
282
+
Err(_) => return Ok((StatusCode::INTERNAL_SERVER_ERROR).into_response()),
283
+
};
284
+
285
+
// Return the content with appropriate headers
286
+
Ok(Response::builder()
287
+
.status(StatusCode::OK)
288
+
.header(header::CONTENT_TYPE, "application/octet-stream")
289
+
.header(header::CACHE_CONTROL, "public, max-age=86400") // Cache for 1 day
290
+
.body(Body::from(content_data))
291
+
.unwrap()
292
+
.into_response())
293
+
}
294
+
295
+
/// Get all posts from storage and format them for display.
296
+
async fn get_all_posts(state: &AppState) -> Result<Vec<PostDisplay>> {
297
+
let mut posts = state.storage.get_posts().await?;
298
+
299
+
// Sort posts by created_at in descending order (most recent first)
300
+
posts.sort_by(|a, b| b.created_at.cmp(&a.created_at));
301
+
302
+
posts
303
+
.into_iter()
304
+
.map(PostDisplay::try_from)
305
+
.collect::<Result<Vec<_>>>()
306
+
}
307
+
308
+
#[cfg(test)]
309
+
mod tests {
310
+
use super::*;
311
+
use crate::storage::Post;
312
+
use chrono::Utc;
313
+
use serde_json::Value;
314
+
315
+
#[tokio::test]
316
+
async fn test_post_display_creation() {
317
+
let post = Post {
318
+
aturi: "at://did:plc:test/com.example.blog/post1".to_string(),
319
+
cid: "bafy123".to_string(),
320
+
title: "Test Post".to_string(),
321
+
slug: "test-post".to_string(),
322
+
content: "bafy456".to_string(),
323
+
record_key: "post1".to_string(),
324
+
created_at: Utc::now(),
325
+
updated_at: Utc::now(),
326
+
record: Value::Null,
327
+
};
328
+
329
+
let display = PostDisplay {
330
+
slug: post.slug,
331
+
title: post.title,
332
+
created_at: post.created_at.format("%Y-%m-%d %H:%M UTC").to_string(),
333
+
};
334
+
335
+
assert_eq!(display.slug, "test-post");
336
+
assert_eq!(display.title, "Test Post");
337
+
assert!(display.created_at.contains("UTC"));
338
+
}
339
+
}
+147
src/identity.rs
+147
src/identity.rs
···
···
1
+
use atproto_identity::model::Document;
2
+
use atproto_identity::resolve::IdentityResolver;
3
+
use atproto_identity::storage::DidDocumentStorage;
4
+
use chrono::Utc;
5
+
use std::sync::Arc;
6
+
7
+
use crate::errors::Result;
8
+
use crate::storage::{Identity, IdentityStorage};
9
+
10
+
/// A caching identity resolver that uses an underlying `IdentityStorage` implementation
11
+
/// to cache resolved identity documents before falling back to the underlying `IdentityResolver`.
12
+
pub struct CachingIdentityResolver<T: IdentityStorage + ?Sized> {
13
+
/// The underlying identity resolver to use when cache misses occur
14
+
resolver: IdentityResolver,
15
+
/// The storage implementation to use for caching
16
+
storage: Arc<T>,
17
+
}
18
+
19
+
impl<T: IdentityStorage + ?Sized> CachingIdentityResolver<T> {
20
+
/// Create a new caching identity resolver with the given resolver and storage.
21
+
pub fn new(resolver: IdentityResolver, storage: Arc<T>) -> Self {
22
+
Self { resolver, storage }
23
+
}
24
+
25
+
/// Resolve a DID to a Document, using the cache when possible.
26
+
pub async fn resolve(&self, did: &str) -> Result<Document> {
27
+
// First, try to get the identity from storage
28
+
if let Some(identity) = self.storage.get_identity_by_did(did).await? {
29
+
// Parse the stored record back to a Document
30
+
let document: Document = serde_json::from_value(identity.record)?;
31
+
return Ok(document);
32
+
}
33
+
34
+
// If not in storage, resolve using the underlying resolver
35
+
let document = self.resolver.resolve(did).await?;
36
+
37
+
// Store the resolved identity for future lookups
38
+
self.store_resolved_identity(&document).await?;
39
+
40
+
Ok(document)
41
+
}
42
+
43
+
/// Store a resolved identity document in the storage.
44
+
async fn store_resolved_identity(&self, doc: &Document) -> Result<()> {
45
+
let handle = doc
46
+
.also_known_as
47
+
.first()
48
+
.and_then(|aka| aka.strip_prefix("at://"))
49
+
.unwrap_or("unknown.handle")
50
+
.to_string();
51
+
52
+
// Create a JSON representation of the document
53
+
let record = serde_json::json!(doc);
54
+
55
+
let identity = Identity {
56
+
did: doc.id.clone(),
57
+
handle,
58
+
record,
59
+
created_at: Utc::now(),
60
+
updated_at: Utc::now(),
61
+
};
62
+
63
+
self.storage.upsert_identity(&identity).await?;
64
+
Ok(())
65
+
}
66
+
}
67
+
68
+
/// A variant that works with DidDocumentStorage instead of IdentityStorage
69
+
pub struct CachingDidDocumentResolver<T: DidDocumentStorage + ?Sized> {
70
+
/// The underlying identity resolver to use when cache misses occur
71
+
resolver: IdentityResolver,
72
+
/// The storage implementation to use for caching
73
+
storage: Arc<T>,
74
+
}
75
+
76
+
impl<T: DidDocumentStorage + ?Sized> CachingDidDocumentResolver<T> {
77
+
/// Create a new caching identity resolver with the given resolver and storage.
78
+
pub fn new(resolver: IdentityResolver, storage: Arc<T>) -> Self {
79
+
Self { resolver, storage }
80
+
}
81
+
82
+
/// Resolve a DID to a Document, using the cache when possible.
83
+
pub async fn resolve(&self, did: &str) -> Result<Document> {
84
+
// First, try to get the document from storage
85
+
if let Some(document) = self.storage.get_document_by_did(did).await? {
86
+
return Ok(document);
87
+
}
88
+
89
+
// If not in storage, resolve using the underlying resolver
90
+
let document = self.resolver.resolve(did).await?;
91
+
92
+
// Store the resolved document for future lookups
93
+
self.storage.store_document(document.clone()).await?;
94
+
95
+
Ok(document)
96
+
}
97
+
98
+
/// Resolve a handle to a Document, using the cache when possible.
99
+
pub async fn resolve_handle(&self, handle: &str) -> Result<Document> {
100
+
// For handle resolution, we need to resolve first since DidDocumentStorage
101
+
// doesn't have a get_by_handle method
102
+
let document = self.resolver.resolve(handle).await?;
103
+
104
+
// Store the resolved document for future lookups
105
+
self.storage.store_document(document.clone()).await?;
106
+
107
+
Ok(document)
108
+
}
109
+
}
110
+
111
+
#[cfg(test)]
112
+
mod tests {
113
+
use super::*;
114
+
use crate::storage::sqlite::SqliteStorage;
115
+
use atproto_identity::resolve::{InnerIdentityResolver, create_resolver};
116
+
use sqlx::SqlitePool;
117
+
use std::sync::Arc;
118
+
119
+
#[tokio::test]
120
+
async fn test_caching_identity_resolver() -> Result<()> {
121
+
// Create an in-memory SQLite database for testing
122
+
let pool = SqlitePool::connect("sqlite::memory:").await?;
123
+
let storage = Arc::new(SqliteStorage::new(pool));
124
+
125
+
// Run migrations using the Storage trait
126
+
use crate::storage::Storage;
127
+
storage.migrate().await?;
128
+
129
+
// Create a mock resolver (this would normally resolve from the network)
130
+
let dns_resolver = create_resolver(&[]);
131
+
let http_client = reqwest::Client::new();
132
+
let inner_resolver = InnerIdentityResolver {
133
+
dns_resolver,
134
+
http_client,
135
+
plc_hostname: "plc.directory".to_string(),
136
+
};
137
+
let resolver = IdentityResolver(Arc::new(inner_resolver));
138
+
139
+
// Create the caching resolver
140
+
let caching_resolver = CachingIdentityResolver::new(resolver, storage.clone());
141
+
142
+
// Test would go here - this is just a skeleton since we'd need real DIDs
143
+
// and network access to properly test the resolution
144
+
145
+
Ok(())
146
+
}
147
+
}
+50
src/lexicon.rs
+50
src/lexicon.rs
···
···
1
+
use chrono::{DateTime, Utc};
2
+
use serde::Deserialize;
3
+
4
+
#[derive(Debug, Clone, Deserialize)]
5
+
pub struct BlobRecord {
6
+
#[serde(rename = "$type")]
7
+
pub r#type: String,
8
+
9
+
#[serde(rename = "mimeType")]
10
+
pub mime_type: String,
11
+
pub size: i64,
12
+
13
+
#[serde(rename = "ref")]
14
+
pub r#ref: BlobRef,
15
+
}
16
+
17
+
#[derive(Debug, Clone, Deserialize)]
18
+
pub struct BlobRef {
19
+
#[serde(rename = "$link")]
20
+
pub link: String,
21
+
}
22
+
23
+
#[derive(Debug, Clone, Deserialize)]
24
+
pub struct PostAttachment {
25
+
#[serde(rename = "$type")]
26
+
pub r#type: String,
27
+
28
+
pub content: BlobRecord,
29
+
}
30
+
31
+
#[derive(Debug, Clone, Deserialize)]
32
+
pub struct PostRecord {
33
+
#[serde(rename = "$type")]
34
+
pub r#type: String,
35
+
36
+
pub title: String,
37
+
pub content: BlobRecord,
38
+
39
+
#[serde(rename = "publishedAt")]
40
+
pub published_at: DateTime<Utc>,
41
+
42
+
#[serde(default = "empty_attachments")]
43
+
pub attachments: Vec<PostAttachment>,
44
+
45
+
pub langs: Vec<String>,
46
+
}
47
+
48
+
fn empty_attachments() -> Vec<PostAttachment> {
49
+
Vec::new()
50
+
}
+12
src/lib.rs
+12
src/lib.rs
+455
src/process.rs
+455
src/process.rs
···
···
1
+
//! Event processing logic for Blahg ATProtocol events.
2
+
//!
3
+
//! Processes different types of ATProtocol events:
4
+
//! - Blog post records (tools.smokesignal.blahg.content.post)
5
+
//! - Feed posts that reference blog content (app.bsky.feed.post)
6
+
//! - Likes on blog posts (app.bsky.feed.like, community.lexicon.interaction.like)
7
+
8
+
use std::collections::{HashMap, HashSet};
9
+
use std::str::FromStr;
10
+
use std::sync::Arc;
11
+
use tokio::sync::RwLock;
12
+
13
+
use crate::config::Config;
14
+
use crate::consumer::{BlahgEvent, BlahgEventReceiver};
15
+
use crate::errors::{BlahgError, Result};
16
+
use crate::identity::CachingIdentityResolver;
17
+
use crate::lexicon::PostRecord;
18
+
use crate::storage::{ContentStorage, Post, PostReference, Storage};
19
+
use atproto_client::com::atproto::repo::get_blob;
20
+
use atproto_record::aturi::ATURI;
21
+
use chrono::Utc;
22
+
use serde::{Deserialize, Serialize};
23
+
use serde_json::Value;
24
+
use slugify::slugify;
25
+
use tracing::{error, info};
26
+
27
+
/// Strong reference to another record (used in posts and likes)
28
+
#[derive(Debug, Deserialize, Serialize, Default)]
29
+
struct StrongRef {
30
+
#[serde(rename = "$type")]
31
+
pub type_: Option<String>,
32
+
pub uri: String,
33
+
pub cid: String,
34
+
}
35
+
36
+
/// app.bsky.feed.post record structure
37
+
#[derive(Debug, Deserialize)]
38
+
struct FeedPostRecord {
39
+
#[serde(rename = "$type")]
40
+
pub type_: Option<String>,
41
+
42
+
#[serde(default)]
43
+
facets: Vec<Facet>,
44
+
45
+
#[serde(default)]
46
+
embed: Option<Embed>,
47
+
48
+
#[serde(default)]
49
+
reply: Option<Reply>,
50
+
}
51
+
52
+
/// Facet in a feed post (for link detection)
53
+
#[derive(Debug, Deserialize)]
54
+
struct Facet {
55
+
features: Vec<Feature>,
56
+
}
57
+
58
+
/// Feature in a facet
59
+
#[derive(Debug, Deserialize)]
60
+
struct Feature {
61
+
#[serde(rename = "$type")]
62
+
type_: String,
63
+
uri: Option<String>,
64
+
}
65
+
66
+
/// Embed in a feed post
67
+
#[derive(Debug, Deserialize)]
68
+
struct Embed {
69
+
#[serde(rename = "$type")]
70
+
type_: String,
71
+
external: Option<External>,
72
+
record: Option<StrongRef>,
73
+
}
74
+
75
+
/// External embed
76
+
#[derive(Debug, Deserialize)]
77
+
struct External {
78
+
uri: String,
79
+
}
80
+
81
+
/// Reply structure
82
+
#[derive(Debug, Deserialize)]
83
+
struct Reply {
84
+
root: StrongRef,
85
+
parent: StrongRef,
86
+
}
87
+
88
+
/// Like record structure (both app.bsky.feed.like and community.lexicon.interaction.like)
89
+
#[derive(Debug, Deserialize)]
90
+
struct LikeRecord {
91
+
subject: StrongRef,
92
+
}
93
+
94
+
/// Background processor for Blahg events
95
+
pub struct EventProcessor {
96
+
storage: Arc<dyn Storage>,
97
+
content_storage: Arc<dyn ContentStorage>,
98
+
config: Arc<Config>,
99
+
identity_resolver: CachingIdentityResolver<dyn Storage>,
100
+
http_client: reqwest::Client,
101
+
post_prefix_cache: RwLock<Option<HashMap<String, String>>>,
102
+
}
103
+
104
+
impl EventProcessor {
105
+
/// Create a new event processor with the required dependencies.
106
+
pub fn new(
107
+
storage: Arc<dyn Storage>,
108
+
content_storage: Arc<dyn ContentStorage>,
109
+
config: Arc<Config>,
110
+
identity_resolver: CachingIdentityResolver<dyn Storage>,
111
+
http_client: reqwest::Client,
112
+
) -> Self {
113
+
Self {
114
+
storage,
115
+
content_storage,
116
+
config,
117
+
identity_resolver,
118
+
http_client,
119
+
post_prefix_cache: RwLock::new(None),
120
+
}
121
+
}
122
+
123
+
/// Start processing events from the queue
124
+
pub async fn start_processing(&self, mut event_receiver: BlahgEventReceiver) -> Result<()> {
125
+
info!("Event processor started");
126
+
127
+
while let Some(event) = event_receiver.recv().await {
128
+
match &event {
129
+
BlahgEvent::Commit {
130
+
did,
131
+
collection,
132
+
rkey,
133
+
cid,
134
+
record,
135
+
} => {
136
+
if let Err(e) = self.handle_commit(did, collection, rkey, cid, record).await {
137
+
error!("Failed to process commit event: {}", e);
138
+
}
139
+
}
140
+
BlahgEvent::Delete {
141
+
did,
142
+
collection,
143
+
rkey,
144
+
} => {
145
+
if let Err(e) = self.handle_delete(did, collection, rkey).await {
146
+
error!("Failed to process delete event: {}", e);
147
+
}
148
+
}
149
+
}
150
+
}
151
+
152
+
info!("Event processor finished");
153
+
Ok(())
154
+
}
155
+
156
+
async fn handle_commit(
157
+
&self,
158
+
did: &str,
159
+
collection: &str,
160
+
rkey: &str,
161
+
cid: &str,
162
+
record: &Value,
163
+
) -> Result<()> {
164
+
// info!("Processing commit: {} {} for {}", collection, rkey, did);
165
+
166
+
match collection {
167
+
"tools.smokesignal.blahg.content.post" => {
168
+
self.handle_blog_post_commit(did, rkey, cid, record).await
169
+
}
170
+
"app.bsky.feed.post" => self.handle_feed_post_commit(did, rkey, cid, record).await,
171
+
"app.bsky.feed.like" | "community.lexicon.interaction.like" => {
172
+
self.handle_like_commit(did, collection, rkey, cid, record)
173
+
.await
174
+
}
175
+
_ => {
176
+
// Unknown collection type, ignore
177
+
Ok(())
178
+
}
179
+
}
180
+
}
181
+
182
+
async fn handle_delete(&self, did: &str, collection: &str, rkey: &str) -> Result<()> {
183
+
let aturi = format!("at://{}/{}/{}", did, collection, rkey);
184
+
185
+
match collection {
186
+
"tools.smokesignal.blahg.content.post" => {
187
+
if (self.storage.delete_post(&aturi).await?).is_some() {
188
+
info!("Successfully deleted post: {}", aturi);
189
+
}
190
+
}
191
+
"app.bsky.feed.post" | "app.bsky.feed.like" | "community.lexicon.interaction.like" => {
192
+
self.storage.delete_post_reference(&aturi).await?;
193
+
}
194
+
_ => {
195
+
// Unknown collection type, ignore
196
+
}
197
+
}
198
+
199
+
Ok(())
200
+
}
201
+
202
+
async fn handle_blog_post_commit(
203
+
&self,
204
+
did: &str,
205
+
rkey: &str,
206
+
cid: &str,
207
+
record: &Value,
208
+
) -> Result<()> {
209
+
// Check if the author matches the configured author
210
+
if self.config.author != did {
211
+
return Ok(());
212
+
}
213
+
214
+
let aturi = format!("at://{}/tools.smokesignal.blahg.content.post/{}", did, rkey);
215
+
216
+
// Parse the post record
217
+
let post_record: PostRecord = serde_json::from_value(record.clone())?;
218
+
219
+
// Resolve the DID to get PDS endpoint
220
+
let document = self.identity_resolver.resolve(did).await?;
221
+
let pds_endpoints = document.pds_endpoints();
222
+
let pds_endpoint =
223
+
pds_endpoints
224
+
.first()
225
+
.ok_or_else(|| BlahgError::ProcessIdentityResolutionFailed {
226
+
did: did.to_string(),
227
+
details: "No PDS endpoint found in DID document".to_string(),
228
+
})?;
229
+
230
+
// Download and store the content blob
231
+
let content_cid = post_record.content.r#ref.link.clone();
232
+
if !self.content_storage.content_exists(&content_cid).await? {
233
+
let content_bytes =
234
+
get_blob(&self.http_client, pds_endpoint, did, &content_cid).await?;
235
+
self.content_storage
236
+
.write_content(&content_cid, &content_bytes)
237
+
.await?;
238
+
}
239
+
240
+
// Download and store attachment blobs
241
+
for attachment in &post_record.attachments {
242
+
let attachment_cid = attachment.content.r#ref.link.clone();
243
+
if !self.content_storage.content_exists(&attachment_cid).await? {
244
+
let attachment_bytes =
245
+
get_blob(&self.http_client, pds_endpoint, did, &attachment_cid).await?;
246
+
self.content_storage
247
+
.write_content(&attachment_cid, &attachment_bytes)
248
+
.await?;
249
+
}
250
+
}
251
+
252
+
// Generate slug from title
253
+
// let slug = self.generate_slug(&post_record.title);
254
+
let slug = format!("{}-{}", rkey, slugify!(&post_record.title));
255
+
256
+
// Create post record
257
+
let post = Post {
258
+
aturi: aturi.to_string(),
259
+
cid: cid.to_string(),
260
+
title: post_record.title.clone(),
261
+
slug,
262
+
content: content_cid,
263
+
record_key: rkey.to_string(),
264
+
created_at: post_record.published_at,
265
+
updated_at: Utc::now(),
266
+
record: record.clone(),
267
+
};
268
+
269
+
// Store post
270
+
self.storage.upsert_post(&post).await?;
271
+
272
+
// Invalidate the post prefix cache
273
+
{
274
+
let mut guard = self.post_prefix_cache.write().await;
275
+
*guard = None;
276
+
}
277
+
278
+
info!("Successfully processed blog post: {}", aturi);
279
+
Ok(())
280
+
}
281
+
282
+
async fn handle_feed_post_commit(
283
+
&self,
284
+
did: &str,
285
+
rkey: &str,
286
+
cid: &str,
287
+
record: &Value,
288
+
) -> Result<()> {
289
+
let aturi = format!("at://{}/app.bsky.feed.post/{}", did, rkey);
290
+
291
+
// Parse the feed post record
292
+
let feed_post: FeedPostRecord = match serde_json::from_value(record.clone()) {
293
+
Ok(post) => post,
294
+
Err(_) => return Ok(()), // Invalid record format, skip
295
+
};
296
+
297
+
// Get the post prefix lookup for URL matching
298
+
let post_prefix_lookup = self.post_prefix_lookup().await?;
299
+
let mut referenced_post_aturis = HashSet::new();
300
+
301
+
// Check facets for post URL prefixes
302
+
for facet in &feed_post.facets {
303
+
for feature in &facet.features {
304
+
if feature.type_ == "app.bsky.richtext.facet#link" {
305
+
if let Some(uri) = &feature.uri {
306
+
// Check if this URI starts with any of the post prefix keys
307
+
for (prefix, post_aturi) in &post_prefix_lookup {
308
+
if uri.starts_with(prefix) {
309
+
referenced_post_aturis.insert(post_aturi.clone());
310
+
break;
311
+
}
312
+
}
313
+
}
314
+
}
315
+
}
316
+
}
317
+
318
+
// Check embed for post URL prefixes and AT-URIs
319
+
if let Some(embed) = &feed_post.embed {
320
+
if embed.type_ == "app.bsky.embed.external" {
321
+
if let Some(external) = &embed.external {
322
+
// Check if this external URI starts with any of the post prefix keys
323
+
for (prefix, post_aturi) in &post_prefix_lookup {
324
+
if external.uri.starts_with(prefix) {
325
+
referenced_post_aturis.insert(post_aturi.clone());
326
+
break;
327
+
}
328
+
}
329
+
}
330
+
}
331
+
// Check for embedded records from known author
332
+
if embed.type_ == "app.bsky.embed.record" {
333
+
if let Some(record_ref) = &embed.record {
334
+
// Check if the record URI is in our post lookup values
335
+
if post_prefix_lookup.values().any(|v| v == &record_ref.uri) {
336
+
referenced_post_aturis.insert(record_ref.uri.clone());
337
+
}
338
+
}
339
+
}
340
+
}
341
+
342
+
// Check reply for known author posts
343
+
if let Some(reply) = &feed_post.reply {
344
+
if post_prefix_lookup.values().any(|v| v == &reply.root.uri) {
345
+
referenced_post_aturis.insert(reply.root.uri.clone());
346
+
}
347
+
if post_prefix_lookup.values().any(|v| v == &reply.parent.uri) {
348
+
referenced_post_aturis.insert(reply.parent.uri.clone());
349
+
}
350
+
}
351
+
352
+
// Store references for each referenced post
353
+
for post_aturi in referenced_post_aturis {
354
+
let post_reference = PostReference {
355
+
aturi: aturi.to_string(),
356
+
cid: cid.to_string(),
357
+
did: did.to_string(),
358
+
collection: "app.bsky.feed.post".to_string(),
359
+
post_aturi: post_aturi.clone(),
360
+
discovered_at: Utc::now(),
361
+
record: record.clone(),
362
+
};
363
+
364
+
self.storage.upsert_post_reference(&post_reference).await?;
365
+
info!("Stored feed post reference: {} -> {}", aturi, post_aturi);
366
+
}
367
+
368
+
Ok(())
369
+
}
370
+
371
+
async fn handle_like_commit(
372
+
&self,
373
+
did: &str,
374
+
collection: &str,
375
+
rkey: &str,
376
+
cid: &str,
377
+
record: &Value,
378
+
) -> Result<()> {
379
+
let aturi = format!("at://{}/{}/{}", did, collection, rkey);
380
+
381
+
// Parse the like record
382
+
let like_record: LikeRecord = match serde_json::from_value(record.clone()) {
383
+
Ok(like) => like,
384
+
Err(_) => return Ok(()), // Invalid record format, skip
385
+
};
386
+
387
+
// Get the post prefix lookup for AT-URI matching
388
+
let post_prefix_lookup = self.post_prefix_lookup().await?;
389
+
390
+
// Check if the subject is a known author post
391
+
if post_prefix_lookup
392
+
.values()
393
+
.any(|v| v == &like_record.subject.uri)
394
+
{
395
+
let post_reference = PostReference {
396
+
aturi: aturi.to_string(),
397
+
cid: cid.to_string(),
398
+
did: did.to_string(),
399
+
collection: collection.to_string(),
400
+
post_aturi: like_record.subject.uri.clone(),
401
+
discovered_at: Utc::now(),
402
+
record: record.clone(),
403
+
};
404
+
405
+
self.storage.upsert_post_reference(&post_reference).await?;
406
+
info!(
407
+
"Stored like reference: {} -> {}",
408
+
aturi, like_record.subject.uri
409
+
);
410
+
}
411
+
412
+
Ok(())
413
+
}
414
+
415
+
/// Check if an AT-URI references a post from a known author
416
+
fn is_known_author_post(&self, uri: &str) -> bool {
417
+
if let Ok(aturi) = ATURI::from_str(uri) {
418
+
return aturi.collection == "tools.smokesignal.blahg.content.post"
419
+
&& self.config.author == aturi.authority;
420
+
}
421
+
false
422
+
}
423
+
424
+
/// Creates a post prefix lookup hashmap with external URL prefixes mapped to AT-URIs
425
+
pub async fn post_prefix_lookup(&self) -> Result<HashMap<String, String>> {
426
+
// Check if we have a cached value
427
+
{
428
+
let guard = self.post_prefix_cache.read().await;
429
+
if let Some(cached) = guard.as_ref() {
430
+
return Ok(cached.clone());
431
+
}
432
+
}
433
+
434
+
let posts = self.storage.get_posts().await?;
435
+
let mut lookup = HashMap::new();
436
+
437
+
for post in posts {
438
+
// Extract the record key from the AT-URI
439
+
// AT-URI format: at://did/tools.smokesignal.blahg.content.post/rkey
440
+
if let Ok(aturi) = ATURI::from_str(&post.aturi) {
441
+
let record_key = aturi.record_key;
442
+
let key = format!("{}/posts/{}-", self.config.external_base, record_key);
443
+
lookup.insert(key, post.aturi);
444
+
}
445
+
}
446
+
447
+
// Cache the result
448
+
{
449
+
let mut guard = self.post_prefix_cache.write().await;
450
+
*guard = Some(lookup.clone());
451
+
}
452
+
453
+
Ok(lookup)
454
+
}
455
+
}
+195
src/render.rs
+195
src/render.rs
···
···
1
+
use anyhow::Result;
2
+
use async_trait::async_trait;
3
+
use comrak::html::format_document_with_formatter;
4
+
use comrak::nodes::Ast;
5
+
use comrak::plugins::syntect::{SyntectAdapter, SyntectAdapterBuilder};
6
+
use comrak::{Options, Plugins};
7
+
use std::cell::RefCell;
8
+
use std::io::BufWriter;
9
+
use std::sync::Arc;
10
+
11
+
12
+
#[async_trait]
13
+
pub trait RenderManager: Send + Sync {
14
+
async fn render_markdown(&self, markdown: &str) -> Result<String>;
15
+
}
16
+
17
+
pub struct ComrakRenderManager<'a> {
18
+
syntect_adapter: Arc<SyntectAdapter>,
19
+
options: Options<'a>,
20
+
external_base: String,
21
+
}
22
+
23
+
impl ComrakRenderManager<'static> {
24
+
pub fn new(external_base: &str) -> Self {
25
+
let syntect_adapter = Arc::new(
26
+
SyntectAdapterBuilder::new()
27
+
.theme("base16-ocean.dark")
28
+
.build(),
29
+
);
30
+
31
+
let mut options = Options::default();
32
+
options.extension.strikethrough = true;
33
+
options.extension.table = true;
34
+
options.extension.autolink = true;
35
+
options.extension.tasklist = true;
36
+
options.extension.superscript = true;
37
+
options.extension.footnotes = true;
38
+
options.extension.description_lists = true;
39
+
options.render.unsafe_ = false;
40
+
41
+
Self {
42
+
syntect_adapter,
43
+
options,
44
+
external_base: external_base.to_string(),
45
+
}
46
+
}
47
+
48
+
pub fn with_theme(theme: &str, external_base: &str) -> Self {
49
+
let syntect_adapter = Arc::new(SyntectAdapterBuilder::new().theme(theme).build());
50
+
51
+
let mut options = Options::default();
52
+
options.extension.strikethrough = true;
53
+
options.extension.table = true;
54
+
options.extension.autolink = true;
55
+
options.extension.tasklist = true;
56
+
options.extension.superscript = true;
57
+
options.extension.footnotes = true;
58
+
options.extension.description_lists = true;
59
+
options.render.unsafe_ = false;
60
+
61
+
Self {
62
+
syntect_adapter,
63
+
options,
64
+
external_base: external_base.to_string(),
65
+
}
66
+
}
67
+
68
+
pub fn with_css(external_base: &str) -> Self {
69
+
let syntect_adapter = Arc::new(SyntectAdapterBuilder::new().css().build());
70
+
71
+
let mut options = Options::default();
72
+
options.extension.strikethrough = true;
73
+
options.extension.table = true;
74
+
options.extension.autolink = true;
75
+
options.extension.tasklist = true;
76
+
options.extension.superscript = true;
77
+
options.extension.footnotes = true;
78
+
options.extension.description_lists = true;
79
+
options.render.unsafe_ = false;
80
+
81
+
Self {
82
+
syntect_adapter,
83
+
options,
84
+
external_base: external_base.to_string(),
85
+
}
86
+
}
87
+
}
88
+
89
+
fn prepend_image_base<'a>(
90
+
nl: &mut comrak::nodes::NodeLink,
91
+
context: &mut comrak::html::Context<String>,
92
+
_node: &'a comrak::arena_tree::Node<'a, RefCell<Ast>>,
93
+
entering: bool,
94
+
) {
95
+
if !entering {
96
+
return;
97
+
}
98
+
99
+
if !nl.url.starts_with("https://") && !nl.url.starts_with("/") {
100
+
nl.url = format!("{}/content/{}", &context.user, nl.url)
101
+
}
102
+
}
103
+
104
+
fn formatter<'a>(
105
+
context: &mut comrak::html::Context<String>,
106
+
node: &'a comrak::nodes::AstNode<'a>,
107
+
entering: bool,
108
+
) -> std::io::Result<comrak::html::ChildRendering> {
109
+
let mut borrow = node.data.borrow_mut();
110
+
if let comrak::nodes::NodeValue::Image(ref mut nl) = borrow.value {
111
+
prepend_image_base(nl, context, node, entering);
112
+
}
113
+
drop(borrow);
114
+
comrak::html::format_node_default(context, node, entering)
115
+
}
116
+
117
+
#[async_trait]
118
+
impl RenderManager for ComrakRenderManager<'static> {
119
+
async fn render_markdown(&self, markdown: &str) -> Result<String> {
120
+
let adapter = self.syntect_adapter.as_ref();
121
+
let mut plugins = Plugins::default();
122
+
plugins.render.codefence_syntax_highlighter = Some(adapter);
123
+
124
+
let arena = comrak::Arena::new();
125
+
let root = comrak::parse_document(&arena, markdown, &self.options);
126
+
let mut bw = BufWriter::new(Vec::new());
127
+
format_document_with_formatter(
128
+
root,
129
+
&self.options,
130
+
&mut bw,
131
+
&plugins,
132
+
formatter,
133
+
self.external_base.clone(),
134
+
)
135
+
.unwrap();
136
+
137
+
Ok(String::from_utf8(bw.into_inner().unwrap()).unwrap())
138
+
}
139
+
}
140
+
141
+
#[cfg(test)]
142
+
mod tests {
143
+
use super::*;
144
+
145
+
#[tokio::test]
146
+
async fn test_render_markdown() {
147
+
let renderer = ComrakRenderManager::new("http://localhost:8080");
148
+
149
+
let markdown = "# Hello World\n\nThis is a **test**.\n\n```rust\nfn main() {\n println!(\"Hello, world!\");\n}\n```";
150
+
151
+
let html = renderer.render_markdown(markdown).await.unwrap();
152
+
153
+
assert!(html.contains("<h1>"));
154
+
assert!(html.contains("Hello World"));
155
+
assert!(html.contains("<strong>test</strong>"));
156
+
assert!(html.contains("main"));
157
+
}
158
+
159
+
#[tokio::test]
160
+
async fn test_render_with_extensions() {
161
+
let renderer = ComrakRenderManager::new("http://localhost:8080");
162
+
163
+
let markdown = "~~strikethrough~~ and https://example.com autolink";
164
+
165
+
let html = renderer.render_markdown(markdown).await.unwrap();
166
+
167
+
assert!(html.contains("<del>"));
168
+
assert!(html.contains("<a href=\"https://example.com\""));
169
+
}
170
+
171
+
#[tokio::test]
172
+
async fn test_syntax_highlighting() {
173
+
let renderer = ComrakRenderManager::new("http://localhost:8080");
174
+
175
+
let markdown = "```rust\nlet x = 42;\n```";
176
+
177
+
let html = renderer.render_markdown(markdown).await.unwrap();
178
+
179
+
assert!(html.contains("<pre"));
180
+
assert!(html.contains("style="));
181
+
}
182
+
183
+
#[tokio::test]
184
+
async fn test_image_url_prefixing() {
185
+
let renderer = ComrakRenderManager::new("https://example.com");
186
+
187
+
let markdown = "  ";
188
+
189
+
let html = renderer.render_markdown(markdown).await.unwrap();
190
+
191
+
assert!(html.contains("https://example.com/image.jpg"));
192
+
assert!(html.contains("https://other.com/image.jpg"));
193
+
assert!(html.contains("\"/root.jpg\""));
194
+
}
195
+
}
+380
src/storage/cached.rs
+380
src/storage/cached.rs
···
···
1
+
use std::collections::HashMap;
2
+
use std::sync::Arc;
3
+
4
+
use crate::errors::Result;
5
+
use async_trait::async_trait;
6
+
use bloomfilter::Bloom;
7
+
use tokio::sync::RwLock;
8
+
9
+
use super::{ContentStorage, Identity, IdentityStorage, Post, PostReference, PostStorage, Storage};
10
+
11
+
pub struct CachedPostStorage<T: Storage> {
12
+
underlying_storage: Arc<T>,
13
+
post_cache: Arc<RwLock<Option<HashMap<String, Post>>>>,
14
+
}
15
+
16
+
impl<T: Storage> CachedPostStorage<T> {
17
+
pub fn new(underlying_storage: Arc<T>) -> Self {
18
+
Self {
19
+
underlying_storage,
20
+
post_cache: Arc::new(RwLock::new(None)),
21
+
}
22
+
}
23
+
24
+
async fn ensure_cache_populated(&self) -> Result<()> {
25
+
let cache = self.post_cache.read().await;
26
+
if cache.is_some() {
27
+
return Ok(());
28
+
}
29
+
drop(cache);
30
+
31
+
let mut cache = self.post_cache.write().await;
32
+
if cache.is_some() {
33
+
return Ok(());
34
+
}
35
+
36
+
let posts = self.underlying_storage.get_posts().await?;
37
+
let mut post_map = HashMap::new();
38
+
39
+
for post in posts {
40
+
post_map.insert(post.aturi.clone(), post);
41
+
}
42
+
43
+
*cache = Some(post_map);
44
+
Ok(())
45
+
}
46
+
47
+
async fn invalidate_cache(&self) -> Result<()> {
48
+
let mut cache = self.post_cache.write().await;
49
+
*cache = None;
50
+
Ok(())
51
+
}
52
+
53
+
async fn refresh_cache(&self) -> Result<()> {
54
+
let mut cache = self.post_cache.write().await;
55
+
let posts = self.underlying_storage.get_posts().await?;
56
+
let mut post_map = HashMap::new();
57
+
58
+
for post in posts {
59
+
post_map.insert(post.aturi.clone(), post);
60
+
}
61
+
62
+
*cache = Some(post_map);
63
+
Ok(())
64
+
}
65
+
}
66
+
67
+
#[async_trait]
68
+
impl<T: Storage> PostStorage for CachedPostStorage<T> {
69
+
async fn upsert_post(&self, post: &Post) -> Result<()> {
70
+
self.underlying_storage.upsert_post(post).await?;
71
+
self.refresh_cache().await?;
72
+
Ok(())
73
+
}
74
+
75
+
async fn get_post(&self, aturi: &str) -> Result<Option<Post>> {
76
+
self.ensure_cache_populated().await?;
77
+
78
+
let cache = self.post_cache.read().await;
79
+
if let Some(ref post_map) = *cache {
80
+
if let Some(post) = post_map.get(aturi) {
81
+
return Ok(Some(post.clone()));
82
+
}
83
+
}
84
+
85
+
Ok(None)
86
+
}
87
+
88
+
async fn get_posts(&self) -> Result<Vec<Post>> {
89
+
self.ensure_cache_populated().await?;
90
+
91
+
let cache = self.post_cache.read().await;
92
+
if let Some(ref post_map) = *cache {
93
+
let mut posts: Vec<Post> = post_map.values().cloned().collect();
94
+
posts.sort_by(|a, b| b.created_at.cmp(&a.created_at));
95
+
return Ok(posts);
96
+
}
97
+
98
+
Ok(Vec::new())
99
+
}
100
+
101
+
async fn delete_post(&self, aturi: &str) -> Result<Option<Post>> {
102
+
let result = self.underlying_storage.delete_post(aturi).await?;
103
+
self.refresh_cache().await?;
104
+
Ok(result)
105
+
}
106
+
107
+
async fn upsert_post_reference(&self, post_reference: &PostReference) -> Result<bool> {
108
+
self.underlying_storage
109
+
.upsert_post_reference(post_reference)
110
+
.await
111
+
}
112
+
113
+
async fn delete_post_reference(&self, aturi: &str) -> Result<()> {
114
+
self.underlying_storage.delete_post_reference(aturi).await
115
+
}
116
+
117
+
async fn get_post_reference_count(&self, post_aturi: &str) -> Result<HashMap<String, i64>> {
118
+
self.underlying_storage
119
+
.get_post_reference_count(post_aturi)
120
+
.await
121
+
}
122
+
123
+
async fn get_post_references_for_post(&self, post_aturi: &str) -> Result<Vec<PostReference>> {
124
+
self.underlying_storage
125
+
.get_post_references_for_post(post_aturi)
126
+
.await
127
+
}
128
+
129
+
async fn get_post_references_for_post_for_collection(
130
+
&self,
131
+
post_aturi: &str,
132
+
collection: &str,
133
+
) -> Result<Vec<PostReference>> {
134
+
self.underlying_storage
135
+
.get_post_references_for_post_for_collection(post_aturi, collection)
136
+
.await
137
+
}
138
+
}
139
+
140
+
#[async_trait]
141
+
impl<T: Storage> IdentityStorage for CachedPostStorage<T> {
142
+
async fn upsert_identity(&self, identity: &Identity) -> Result<()> {
143
+
self.underlying_storage.upsert_identity(identity).await
144
+
}
145
+
146
+
async fn get_identity_by_did(&self, did: &str) -> Result<Option<Identity>> {
147
+
self.underlying_storage.get_identity_by_did(did).await
148
+
}
149
+
150
+
async fn get_identity_by_handle(&self, handle: &str) -> Result<Option<Identity>> {
151
+
self.underlying_storage.get_identity_by_handle(handle).await
152
+
}
153
+
154
+
async fn delete_identity(&self, aturi: &str) -> Result<Option<Identity>> {
155
+
self.underlying_storage.delete_identity(aturi).await
156
+
}
157
+
}
158
+
159
+
#[async_trait]
160
+
impl<T: Storage> Storage for CachedPostStorage<T> {
161
+
async fn migrate(&self) -> Result<()> {
162
+
self.underlying_storage.migrate().await
163
+
}
164
+
}
165
+
166
+
/// A read-through cache implementation for ContentStorage that caches content by CID
167
+
/// and uses a bloom filter to track CIDs that were not found in the underlying storage.
168
+
pub struct CachedContentStorage<T: ContentStorage> {
169
+
underlying_storage: Arc<T>,
170
+
/// Cache of content by CID
171
+
content_cache: Arc<RwLock<HashMap<String, Vec<u8>>>>,
172
+
/// Bloom filter to track CIDs that were not found in underlying storage
173
+
not_found_filter: Arc<RwLock<Bloom<String>>>,
174
+
/// Maximum number of items to cache
175
+
cache_size_limit: usize,
176
+
}
177
+
178
+
impl<T: ContentStorage> CachedContentStorage<T> {
179
+
/// Create a new cached content storage with the given underlying storage.
180
+
///
181
+
/// # Arguments
182
+
/// * `underlying_storage` - The underlying ContentStorage implementation to wrap
183
+
/// * `cache_size_limit` - Maximum number of items to cache (default: 1000)
184
+
/// * `bloom_filter_capacity` - Expected number of items in the bloom filter (default: 10000)
185
+
/// * `bloom_filter_error_rate` - False positive probability for bloom filter (default: 0.01)
186
+
pub fn new(underlying_storage: Arc<T>) -> Self {
187
+
Self::with_config(underlying_storage, 1000, 10000, 0.01)
188
+
}
189
+
190
+
/// Create a new cached content storage with custom configuration.
191
+
pub fn with_config(
192
+
underlying_storage: Arc<T>,
193
+
cache_size_limit: usize,
194
+
bloom_filter_capacity: usize,
195
+
bloom_filter_error_rate: f64,
196
+
) -> Self {
197
+
let bloom_filter = Bloom::new_for_fp_rate(bloom_filter_capacity, bloom_filter_error_rate);
198
+
199
+
Self {
200
+
underlying_storage,
201
+
content_cache: Arc::new(RwLock::new(HashMap::new())),
202
+
not_found_filter: Arc::new(RwLock::new(bloom_filter)),
203
+
cache_size_limit,
204
+
}
205
+
}
206
+
207
+
/// Check if the cache is at capacity and evict items if necessary.
208
+
/// Uses a simple LRU-style eviction by clearing the cache when it's full.
209
+
async fn maybe_evict_cache(&self) {
210
+
let cache = self.content_cache.read().await;
211
+
if cache.len() >= self.cache_size_limit {
212
+
drop(cache);
213
+
let mut cache = self.content_cache.write().await;
214
+
if cache.len() >= self.cache_size_limit {
215
+
// Simple eviction strategy: clear the entire cache
216
+
// In a production system, you might want to implement LRU eviction
217
+
cache.clear();
218
+
}
219
+
}
220
+
}
221
+
222
+
/// Add a CID to the not-found bloom filter
223
+
async fn add_to_not_found_filter(&self, cid: &str) {
224
+
let mut filter = self.not_found_filter.write().await;
225
+
filter.set(&cid.to_string());
226
+
}
227
+
228
+
/// Check if a CID is in the not-found bloom filter
229
+
async fn is_in_not_found_filter(&self, cid: &str) -> bool {
230
+
let filter = self.not_found_filter.read().await;
231
+
filter.check(&cid.to_string())
232
+
}
233
+
234
+
/// Add content to the cache
235
+
async fn add_to_cache(&self, cid: &str, content: Vec<u8>) {
236
+
self.maybe_evict_cache().await;
237
+
let mut cache = self.content_cache.write().await;
238
+
cache.insert(cid.to_string(), content);
239
+
}
240
+
241
+
/// Get content from the cache
242
+
async fn get_from_cache(&self, cid: &str) -> Option<Vec<u8>> {
243
+
let cache = self.content_cache.read().await;
244
+
cache.get(cid).cloned()
245
+
}
246
+
247
+
/// Remove content from the cache (used when content is written)
248
+
async fn remove_from_cache(&self, cid: &str) {
249
+
let mut cache = self.content_cache.write().await;
250
+
cache.remove(cid);
251
+
}
252
+
253
+
/// Clear the not-found filter entry for a CID (used when content is written)
254
+
async fn clear_not_found_filter(&self, _cid: &str) {
255
+
// Note: Bloom filters don't support removal, so we recreate the filter
256
+
// This is a limitation of bloom filters - in production you might want
257
+
// to use a counting bloom filter or periodically reset the filter
258
+
let mut filter = self.not_found_filter.write().await;
259
+
*filter = Bloom::new_for_fp_rate(10000, 0.01);
260
+
}
261
+
}
262
+
263
+
#[async_trait]
264
+
impl<T: ContentStorage> ContentStorage for CachedContentStorage<T> {
265
+
async fn content_exists(&self, cid: &str) -> Result<bool> {
266
+
// First check the cache
267
+
if self.get_from_cache(cid).await.is_some() {
268
+
return Ok(true);
269
+
}
270
+
271
+
// Check the not-found filter to avoid expensive lookups
272
+
if self.is_in_not_found_filter(cid).await {
273
+
return Ok(false);
274
+
}
275
+
276
+
// Check the underlying storage
277
+
let exists = self.underlying_storage.content_exists(cid).await?;
278
+
279
+
// If not found, add to the not-found filter
280
+
if !exists {
281
+
self.add_to_not_found_filter(cid).await;
282
+
}
283
+
284
+
Ok(exists)
285
+
}
286
+
287
+
async fn write_content(&self, cid: &str, data: &[u8]) -> Result<()> {
288
+
// Write to underlying storage first
289
+
self.underlying_storage.write_content(cid, data).await?;
290
+
291
+
// Clear any not-found filter entry
292
+
self.clear_not_found_filter(cid).await;
293
+
294
+
// Remove from cache (it will be cached on next read)
295
+
self.remove_from_cache(cid).await;
296
+
297
+
Ok(())
298
+
}
299
+
300
+
async fn read_content(&self, cid: &str) -> Result<Vec<u8>> {
301
+
// First check the cache
302
+
if let Some(cached_content) = self.get_from_cache(cid).await {
303
+
return Ok(cached_content);
304
+
}
305
+
306
+
// Check the not-found filter to avoid expensive lookups
307
+
if self.is_in_not_found_filter(cid).await {
308
+
return Err(crate::errors::BlahgError::StorageFileOperationFailed {
309
+
operation: format!("Content not found: {}", cid),
310
+
});
311
+
}
312
+
313
+
// Read from underlying storage
314
+
match self.underlying_storage.read_content(cid).await {
315
+
Ok(content) => {
316
+
// Cache the content
317
+
self.add_to_cache(cid, content.clone()).await;
318
+
Ok(content)
319
+
}
320
+
Err(e) => {
321
+
// If it's a not-found error, add to the not-found filter
322
+
if matches!(
323
+
e,
324
+
crate::errors::BlahgError::StorageFileOperationFailed { .. }
325
+
) {
326
+
self.add_to_not_found_filter(cid).await;
327
+
}
328
+
Err(e)
329
+
}
330
+
}
331
+
}
332
+
}
333
+
334
+
#[cfg(test)]
335
+
mod tests {
336
+
use super::*;
337
+
use crate::storage::content::FilesystemContentStorage;
338
+
use std::sync::Arc;
339
+
use tempfile::TempDir;
340
+
341
+
#[tokio::test]
342
+
async fn test_cached_content_storage() -> Result<()> {
343
+
let temp_dir = TempDir::new()?;
344
+
let filesystem_storage = Arc::new(FilesystemContentStorage::new(temp_dir.path()).await?);
345
+
346
+
let cached_storage = CachedContentStorage::new(filesystem_storage.clone());
347
+
348
+
let cid = "bafyreigdyrzt5sfp7udm7hu76uh7y26nf3efuylqabf3oclgtqy55fbzdi";
349
+
let data = b"Hello, cached world!";
350
+
351
+
// Test content doesn't exist initially
352
+
assert!(!cached_storage.content_exists(cid).await?);
353
+
354
+
// Write content
355
+
cached_storage.write_content(cid, data).await?;
356
+
357
+
// Test content exists after writing
358
+
assert!(cached_storage.content_exists(cid).await?);
359
+
360
+
// Read content and verify - this should cache it
361
+
let read_data = cached_storage.read_content(cid).await?;
362
+
assert_eq!(read_data, data);
363
+
364
+
// Read again - this should come from cache
365
+
let read_data_cached = cached_storage.read_content(cid).await?;
366
+
assert_eq!(read_data_cached, data);
367
+
368
+
// Test not-found filtering
369
+
let nonexistent_cid =
370
+
"bafyreigdyrzt5sfp7udm7hu76uh7y26nf3efuylqabf3oclgtqy55fbzdi_nonexistent";
371
+
372
+
// First check should query underlying storage
373
+
assert!(!cached_storage.content_exists(nonexistent_cid).await?);
374
+
375
+
// Second check should use bloom filter and return false without querying underlying storage
376
+
assert!(!cached_storage.content_exists(nonexistent_cid).await?);
377
+
378
+
Ok(())
379
+
}
380
+
}
+326
src/storage/content.rs
+326
src/storage/content.rs
···
···
1
+
use std::path::{Path, PathBuf};
2
+
3
+
use crate::errors::Result;
4
+
use async_trait::async_trait;
5
+
use tokio::fs;
6
+
7
+
#[cfg(feature = "s3")]
8
+
use minio::s3::{Client as MinioClient, creds::StaticProvider, http::BaseUrl, types::S3Api};
9
+
10
+
use super::ContentStorage;
11
+
12
+
pub use self::FilesystemContentStorage as FilesystemStorage;
13
+
14
+
/// Parse an S3 URL in the format: s3://[key]:[secret]@hostname/bucket[/optional_prefix]
15
+
/// Returns (endpoint, access_key, secret_key, bucket, prefix)
16
+
#[cfg(feature = "s3")]
17
+
pub fn parse_s3_url(url: &str) -> Result<(String, String, String, String, Option<String>)> {
18
+
if !url.starts_with("s3://") {
19
+
return Err(crate::errors::BlahgError::ConfigS3UrlInvalid {
20
+
details: format!("Invalid S3 URL format: {}", url),
21
+
});
22
+
}
23
+
24
+
let url_without_scheme = &url[5..]; // Remove "s3://"
25
+
26
+
// Split by '@' to separate credentials from hostname/path
27
+
let parts: Vec<&str> = url_without_scheme.splitn(2, '@').collect();
28
+
if parts.len() != 2 {
29
+
return Err(crate::errors::BlahgError::ConfigS3UrlInvalid {
30
+
details: format!("Invalid S3 URL format - missing @ separator: {}", url),
31
+
});
32
+
}
33
+
34
+
let credentials = parts[0];
35
+
let hostname_and_path = parts[1];
36
+
37
+
// Parse credentials: key:secret
38
+
let cred_parts: Vec<&str> = credentials.splitn(2, ':').collect();
39
+
if cred_parts.len() != 2 {
40
+
return Err(crate::errors::BlahgError::ConfigS3UrlInvalid {
41
+
details: format!(
42
+
"Invalid S3 URL format - credentials must be key:secret: {}",
43
+
url
44
+
),
45
+
});
46
+
}
47
+
48
+
let access_key = cred_parts[0].to_string();
49
+
let secret_key = cred_parts[1].to_string();
50
+
51
+
// Parse hostname and path: hostname/bucket[/prefix]
52
+
let path_parts: Vec<&str> = hostname_and_path.splitn(2, '/').collect();
53
+
if path_parts.len() != 2 {
54
+
return Err(crate::errors::BlahgError::ConfigS3UrlInvalid {
55
+
details: format!("Invalid S3 URL format - must include bucket: {}", url),
56
+
});
57
+
}
58
+
59
+
let hostname = path_parts[0].to_string();
60
+
let bucket_and_prefix = path_parts[1];
61
+
62
+
// Split bucket from optional prefix
63
+
let bucket_parts: Vec<&str> = bucket_and_prefix.splitn(2, '/').collect();
64
+
let bucket = bucket_parts[0].to_string();
65
+
let prefix = if bucket_parts.len() > 1 && !bucket_parts[1].is_empty() {
66
+
Some(bucket_parts[1].to_string())
67
+
} else {
68
+
None
69
+
};
70
+
71
+
let endpoint = if hostname.starts_with("http://") || hostname.starts_with("https://") {
72
+
hostname
73
+
} else {
74
+
format!("https://{}", hostname)
75
+
};
76
+
77
+
Ok((endpoint, access_key, secret_key, bucket, prefix))
78
+
}
79
+
80
+
/// Local filesystem implementation of content storage.
81
+
#[derive(Debug, Clone)]
82
+
pub struct FilesystemContentStorage {
83
+
base_dir: PathBuf,
84
+
}
85
+
86
+
impl FilesystemContentStorage {
87
+
/// Create a new filesystem content storage with the given base directory.
88
+
pub async fn new<P: AsRef<Path>>(base_dir: P) -> Result<Self> {
89
+
let base_dir = base_dir.as_ref().to_path_buf();
90
+
91
+
// Ensure the base directory exists
92
+
fs::create_dir_all(&base_dir).await?;
93
+
94
+
Ok(Self { base_dir })
95
+
}
96
+
97
+
/// Get the file path for a given CID.
98
+
/// Uses a subdirectory structure based on the first few characters of the CID
99
+
/// to avoid having too many files in a single directory.
100
+
fn get_content_path(&self, cid: &str) -> PathBuf {
101
+
// Use first 2 characters for first level directory
102
+
// and next 2 characters for second level directory
103
+
let (dir1, dir2, filename) = if cid.len() >= 4 {
104
+
(&cid[0..2], &cid[2..4], cid)
105
+
} else if cid.len() >= 2 {
106
+
(&cid[0..2], "00", cid)
107
+
} else {
108
+
("00", "00", cid)
109
+
};
110
+
111
+
self.base_dir.join(dir1).join(dir2).join(filename)
112
+
}
113
+
}
114
+
115
+
#[async_trait]
116
+
impl ContentStorage for FilesystemContentStorage {
117
+
async fn content_exists(&self, cid: &str) -> Result<bool> {
118
+
let path = self.get_content_path(cid);
119
+
Ok(fs::try_exists(&path).await?)
120
+
}
121
+
122
+
async fn write_content(&self, cid: &str, data: &[u8]) -> Result<()> {
123
+
let path = self.get_content_path(cid);
124
+
125
+
// Ensure parent directory exists
126
+
if let Some(parent) = path.parent() {
127
+
fs::create_dir_all(parent).await?;
128
+
}
129
+
130
+
// Write content atomically by writing to a temp file first
131
+
let temp_path = path.with_extension("tmp");
132
+
fs::write(&temp_path, data).await?;
133
+
134
+
// Rename temp file to final path (atomic on most filesystems)
135
+
fs::rename(&temp_path, &path).await?;
136
+
137
+
Ok(())
138
+
}
139
+
140
+
async fn read_content(&self, cid: &str) -> Result<Vec<u8>> {
141
+
let path = self.get_content_path(cid);
142
+
Ok(fs::read(&path).await?)
143
+
}
144
+
}
145
+
146
+
#[cfg(feature = "s3")]
147
+
pub struct S3FileStorage {
148
+
client: MinioClient,
149
+
bucket: String,
150
+
prefix: Option<String>,
151
+
}
152
+
153
+
#[cfg(feature = "s3")]
154
+
impl S3FileStorage {
155
+
/// Create a new S3FileStorage with the given credentials and bucket information
156
+
pub fn new(
157
+
endpoint: String,
158
+
access_key: String,
159
+
secret_key: String,
160
+
bucket: String,
161
+
prefix: Option<String>,
162
+
) -> Result<Self> {
163
+
let base_url: BaseUrl = endpoint.parse().unwrap();
164
+
tracing::debug!(?base_url, "s3 file storage base url");
165
+
166
+
let static_provider = StaticProvider::new(&access_key, &secret_key, None);
167
+
tracing::debug!(?static_provider, "s3 file storage static provider");
168
+
169
+
let client = MinioClient::new(base_url, Some(Box::new(static_provider)), None, None)
170
+
.map_err(|e| crate::errors::BlahgError::StorageFileOperationFailed {
171
+
operation: format!("Failed to create S3 client: {}", e),
172
+
})?;
173
+
174
+
Ok(Self {
175
+
client,
176
+
bucket,
177
+
prefix,
178
+
})
179
+
}
180
+
181
+
/// Get the full object key by combining prefix with path
182
+
fn get_object_key(&self, path: &str) -> String {
183
+
match &self.prefix {
184
+
Some(prefix) => {
185
+
if path.starts_with('/') {
186
+
format!("/{prefix}{path}")
187
+
} else {
188
+
format!("/{prefix}/{path}")
189
+
}
190
+
}
191
+
None => {
192
+
if path.starts_with('/') {
193
+
path.to_string()
194
+
} else {
195
+
format!("/{path}")
196
+
}
197
+
}
198
+
}
199
+
}
200
+
}
201
+
202
+
#[cfg(feature = "s3")]
203
+
#[async_trait]
204
+
impl ContentStorage for S3FileStorage {
205
+
async fn content_exists(&self, cid: &str) -> Result<bool> {
206
+
use minio::s3::error::ErrorCode;
207
+
208
+
let object_key = self.get_object_key(cid);
209
+
210
+
match self
211
+
.client
212
+
.stat_object(&self.bucket, &object_key)
213
+
.send()
214
+
.await
215
+
{
216
+
Ok(_) => Ok(true),
217
+
Err(minio::s3::error::Error::S3Error(ref s3_err))
218
+
if s3_err.code == ErrorCode::NoSuchKey =>
219
+
{
220
+
Ok(false)
221
+
}
222
+
Err(e) => Err(crate::errors::BlahgError::StorageFileOperationFailed {
223
+
operation: format!("Failed to check if S3 object exists: {}", e),
224
+
}),
225
+
}
226
+
}
227
+
228
+
async fn write_content(&self, cid: &str, data: &[u8]) -> Result<()> {
229
+
use minio::s3::segmented_bytes::SegmentedBytes;
230
+
231
+
let object_key = self.get_object_key(cid);
232
+
233
+
let put_data = SegmentedBytes::from(bytes::Bytes::copy_from_slice(data));
234
+
235
+
self.client
236
+
.put_object(&self.bucket, &object_key, put_data)
237
+
.send()
238
+
.await
239
+
.map_err(|e| crate::errors::BlahgError::StorageFileOperationFailed {
240
+
operation: format!("Failed to write S3 object: {}", e),
241
+
})?;
242
+
243
+
Ok(())
244
+
}
245
+
246
+
async fn read_content(&self, cid: &str) -> Result<Vec<u8>> {
247
+
let object_key = self.get_object_key(cid);
248
+
249
+
let response = self
250
+
.client
251
+
.get_object(&self.bucket, &object_key)
252
+
.send()
253
+
.await
254
+
.map_err(|e| crate::errors::BlahgError::StorageFileOperationFailed {
255
+
operation: format!("Failed to read S3 object: {}", e),
256
+
})?;
257
+
258
+
let data = response
259
+
.content
260
+
.to_segmented_bytes()
261
+
.await
262
+
.map_err(|e| crate::errors::BlahgError::StorageFileOperationFailed {
263
+
operation: format!("Failed to read S3 object data: {}", e),
264
+
})?
265
+
.to_bytes();
266
+
267
+
Ok(data.to_vec())
268
+
}
269
+
}
270
+
271
+
#[cfg(test)]
272
+
mod tests {
273
+
use super::*;
274
+
use tempfile::TempDir;
275
+
276
+
#[tokio::test]
277
+
async fn test_filesystem_content_storage() -> Result<()> {
278
+
let temp_dir = TempDir::new()?;
279
+
let storage = FilesystemContentStorage::new(temp_dir.path()).await?;
280
+
281
+
let cid = "bafyreigdyrzt5sfp7udm7hu76uh7y26nf3efuylqabf3oclgtqy55fbzdi";
282
+
let data = b"Hello, world!";
283
+
284
+
// Test content doesn't exist initially
285
+
assert!(!storage.content_exists(cid).await?);
286
+
287
+
// Write content
288
+
storage.write_content(cid, data).await?;
289
+
290
+
// Test content exists after writing
291
+
assert!(storage.content_exists(cid).await?);
292
+
293
+
// Read content and verify
294
+
let read_data = storage.read_content(cid).await?;
295
+
assert_eq!(read_data, data);
296
+
297
+
Ok(())
298
+
}
299
+
300
+
#[tokio::test]
301
+
async fn test_filesystem_path_structure() {
302
+
let temp_dir = TempDir::new().unwrap();
303
+
let storage = FilesystemContentStorage::new(temp_dir.path())
304
+
.await
305
+
.unwrap();
306
+
307
+
// Test normal CID
308
+
let path =
309
+
storage.get_content_path("bafyreigdyrzt5sfp7udm7hu76uh7y26nf3efuylqabf3oclgtqy55fbzdi");
310
+
assert_eq!(
311
+
path.file_name().unwrap(),
312
+
"bafyreigdyrzt5sfp7udm7hu76uh7y26nf3efuylqabf3oclgtqy55fbzdi"
313
+
);
314
+
assert!(path.parent().unwrap().ends_with("ba/fy"));
315
+
316
+
// Test short CID
317
+
let path = storage.get_content_path("ab");
318
+
assert_eq!(path.file_name().unwrap(), "ab");
319
+
assert!(path.parent().unwrap().ends_with("ab/00"));
320
+
321
+
// Test very short CID
322
+
let path = storage.get_content_path("a");
323
+
assert_eq!(path.file_name().unwrap(), "a");
324
+
assert!(path.parent().unwrap().ends_with("00/00"));
325
+
}
326
+
}
+99
src/storage/mod.rs
+99
src/storage/mod.rs
···
···
1
+
use crate::errors::Result;
2
+
use async_trait::async_trait;
3
+
use chrono::{DateTime, Utc};
4
+
use serde::{Deserialize, Serialize};
5
+
use serde_json::Value;
6
+
use std::collections::HashMap;
7
+
8
+
#[derive(Clone, Serialize, Deserialize, sqlx::FromRow)]
9
+
pub struct Identity {
10
+
pub did: String,
11
+
pub handle: String,
12
+
pub record: Value,
13
+
pub created_at: DateTime<Utc>,
14
+
pub updated_at: DateTime<Utc>,
15
+
}
16
+
17
+
// tools.smokesignal.blahg.content.post
18
+
#[derive(Clone, Serialize, Deserialize, sqlx::FromRow)]
19
+
pub struct Post {
20
+
pub aturi: String,
21
+
pub cid: String,
22
+
pub title: String,
23
+
pub slug: String,
24
+
pub content: String,
25
+
pub record_key: String,
26
+
pub created_at: DateTime<Utc>,
27
+
pub updated_at: DateTime<Utc>,
28
+
pub record: Value,
29
+
}
30
+
31
+
#[derive(Clone, Serialize, Deserialize, sqlx::FromRow)]
32
+
pub struct PostReference {
33
+
pub aturi: String,
34
+
pub cid: String,
35
+
pub did: String,
36
+
pub collection: String,
37
+
pub post_aturi: String,
38
+
pub discovered_at: DateTime<Utc>,
39
+
pub record: Value,
40
+
}
41
+
42
+
#[async_trait]
43
+
pub trait PostStorage: Send + Sync {
44
+
async fn upsert_post(&self, post: &Post) -> Result<()>;
45
+
46
+
async fn get_post(&self, aturi: &str) -> Result<Option<Post>>;
47
+
48
+
async fn get_posts(&self) -> Result<Vec<Post>>;
49
+
50
+
async fn delete_post(&self, aturi: &str) -> Result<Option<Post>>;
51
+
52
+
async fn upsert_post_reference(&self, post_reference: &PostReference) -> Result<bool>;
53
+
54
+
async fn delete_post_reference(&self, aturi: &str) -> Result<()>;
55
+
56
+
async fn get_post_reference_count(&self, post_aturi: &str) -> Result<HashMap<String, i64>>;
57
+
58
+
async fn get_post_references_for_post(&self, post_aturi: &str) -> Result<Vec<PostReference>>;
59
+
60
+
async fn get_post_references_for_post_for_collection(
61
+
&self,
62
+
post_aturi: &str,
63
+
collection: &str,
64
+
) -> Result<Vec<PostReference>>;
65
+
}
66
+
67
+
#[async_trait]
68
+
pub trait IdentityStorage: Send + Sync {
69
+
async fn upsert_identity(&self, identity: &Identity) -> Result<()>;
70
+
71
+
async fn get_identity_by_did(&self, did: &str) -> Result<Option<Identity>>;
72
+
73
+
async fn get_identity_by_handle(&self, handle: &str) -> Result<Option<Identity>>;
74
+
75
+
async fn delete_identity(&self, aturi: &str) -> Result<Option<Identity>>;
76
+
}
77
+
78
+
#[async_trait]
79
+
pub trait ContentStorage: Send + Sync {
80
+
async fn content_exists(&self, cid: &str) -> Result<bool>;
81
+
82
+
async fn write_content(&self, cid: &str, data: &[u8]) -> Result<()>;
83
+
84
+
async fn read_content(&self, cid: &str) -> Result<Vec<u8>>;
85
+
}
86
+
87
+
#[async_trait]
88
+
pub trait Storage: PostStorage + IdentityStorage + Send + Sync {
89
+
async fn migrate(&self) -> Result<()>;
90
+
}
91
+
92
+
pub mod cached;
93
+
pub mod content;
94
+
pub mod postgres;
95
+
#[cfg(feature = "sqlite")]
96
+
pub mod sqlite;
97
+
98
+
pub use cached::{CachedContentStorage, CachedPostStorage};
99
+
pub use content::FilesystemContentStorage;
+419
src/storage/postgres.rs
+419
src/storage/postgres.rs
···
···
1
+
use std::collections::HashMap;
2
+
use std::sync::Arc;
3
+
4
+
use crate::errors::Result;
5
+
use async_trait::async_trait;
6
+
use atproto_identity::model::Document;
7
+
use atproto_identity::storage::DidDocumentStorage;
8
+
use chrono::Utc;
9
+
use sqlx::Row;
10
+
use sqlx::postgres::PgPool;
11
+
12
+
use super::{Identity, IdentityStorage, Post, PostReference, PostStorage, Storage};
13
+
14
+
/// PostgreSQL storage implementation for blog storage operations
15
+
#[derive(Debug, Clone)]
16
+
pub struct PostgresStorage {
17
+
pool: PgPool,
18
+
}
19
+
20
+
impl PostgresStorage {
21
+
/// Create a new PostgreSQL storage instance with the given connection pool.
22
+
pub fn new(pool: PgPool) -> Self {
23
+
Self { pool }
24
+
}
25
+
}
26
+
27
+
#[async_trait]
28
+
impl PostStorage for PostgresStorage {
29
+
async fn upsert_post(&self, post: &Post) -> Result<()> {
30
+
sqlx::query(
31
+
r#"
32
+
INSERT INTO posts (aturi, cid, title, slug, content, record_key, created_at, updated_at, record)
33
+
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)
34
+
ON CONFLICT(aturi) DO UPDATE SET
35
+
cid = EXCLUDED.cid,
36
+
title = EXCLUDED.title,
37
+
slug = EXCLUDED.slug,
38
+
content = EXCLUDED.content,
39
+
record_key = EXCLUDED.record_key,
40
+
updated_at = EXCLUDED.updated_at,
41
+
record = EXCLUDED.record
42
+
"#,
43
+
)
44
+
.bind(&post.aturi)
45
+
.bind(&post.cid)
46
+
.bind(&post.title)
47
+
.bind(&post.slug)
48
+
.bind(&post.content)
49
+
.bind(&post.record_key)
50
+
.bind(post.created_at)
51
+
.bind(post.updated_at)
52
+
.bind(&post.record)
53
+
.execute(&self.pool)
54
+
.await?;
55
+
56
+
Ok(())
57
+
}
58
+
59
+
async fn get_post(&self, aturi: &str) -> Result<Option<Post>> {
60
+
let row = sqlx::query_as::<_, Post>("SELECT * FROM posts WHERE aturi = $1")
61
+
.bind(aturi)
62
+
.fetch_optional(&self.pool)
63
+
.await?;
64
+
65
+
Ok(row)
66
+
}
67
+
68
+
async fn get_posts(&self) -> Result<Vec<Post>> {
69
+
let rows = sqlx::query_as::<_, Post>("SELECT * FROM posts ORDER BY created_at DESC")
70
+
.fetch_all(&self.pool)
71
+
.await?;
72
+
73
+
Ok(rows)
74
+
}
75
+
76
+
async fn delete_post(&self, aturi: &str) -> Result<Option<Post>> {
77
+
let post = self.get_post(aturi).await?;
78
+
79
+
if post.is_some() {
80
+
sqlx::query("DELETE FROM posts WHERE aturi = $1")
81
+
.bind(aturi)
82
+
.execute(&self.pool)
83
+
.await?;
84
+
}
85
+
86
+
Ok(post)
87
+
}
88
+
89
+
async fn upsert_post_reference(&self, post_reference: &PostReference) -> Result<bool> {
90
+
let existing =
91
+
sqlx::query_as::<_, PostReference>("SELECT * FROM post_references WHERE aturi = $1")
92
+
.bind(&post_reference.aturi)
93
+
.fetch_optional(&self.pool)
94
+
.await?;
95
+
96
+
let is_new = existing.is_none();
97
+
98
+
sqlx::query(
99
+
r#"
100
+
INSERT INTO post_references (aturi, cid, did, collection, post_aturi, discovered_at, record)
101
+
VALUES ($1, $2, $3, $4, $5, $6, $7)
102
+
ON CONFLICT(aturi) DO UPDATE SET
103
+
cid = EXCLUDED.cid,
104
+
did = EXCLUDED.did,
105
+
collection = EXCLUDED.collection,
106
+
post_aturi = EXCLUDED.post_aturi,
107
+
discovered_at = EXCLUDED.discovered_at,
108
+
record = EXCLUDED.record
109
+
"#,
110
+
)
111
+
.bind(&post_reference.aturi)
112
+
.bind(&post_reference.cid)
113
+
.bind(&post_reference.did)
114
+
.bind(&post_reference.collection)
115
+
.bind(&post_reference.post_aturi)
116
+
.bind(post_reference.discovered_at)
117
+
.bind(&post_reference.record)
118
+
.execute(&self.pool)
119
+
.await?;
120
+
121
+
Ok(is_new)
122
+
}
123
+
124
+
async fn delete_post_reference(&self, aturi: &str) -> Result<()> {
125
+
sqlx::query("DELETE FROM post_references WHERE aturi = $1")
126
+
.bind(aturi)
127
+
.execute(&self.pool)
128
+
.await?;
129
+
130
+
Ok(())
131
+
}
132
+
133
+
async fn get_post_reference_count(&self, post_aturi: &str) -> Result<HashMap<String, i64>> {
134
+
let rows = sqlx::query(
135
+
r#"
136
+
SELECT collection, COUNT(*) as count
137
+
FROM post_references
138
+
WHERE post_aturi = $1
139
+
GROUP BY collection
140
+
"#,
141
+
)
142
+
.bind(post_aturi)
143
+
.fetch_all(&self.pool)
144
+
.await?;
145
+
146
+
let mut count_map = HashMap::new();
147
+
for row in rows {
148
+
let collection: String = row.get("collection");
149
+
let count: i64 = row.get("count");
150
+
count_map.insert(collection, count);
151
+
}
152
+
153
+
Ok(count_map)
154
+
}
155
+
156
+
async fn get_post_references_for_post(&self, post_aturi: &str) -> Result<Vec<PostReference>> {
157
+
let rows = sqlx::query_as::<_, PostReference>(
158
+
"SELECT * FROM post_references WHERE post_aturi = $1 ORDER BY discovered_at DESC",
159
+
)
160
+
.bind(post_aturi)
161
+
.fetch_all(&self.pool)
162
+
.await?;
163
+
164
+
Ok(rows)
165
+
}
166
+
167
+
async fn get_post_references_for_post_for_collection(
168
+
&self,
169
+
post_aturi: &str,
170
+
collection: &str,
171
+
) -> Result<Vec<PostReference>> {
172
+
let rows = sqlx::query_as::<_, PostReference>(
173
+
"SELECT * FROM post_references WHERE post_aturi = $1 AND collection = $2 ORDER BY discovered_at DESC",
174
+
)
175
+
.bind(post_aturi)
176
+
.bind(collection)
177
+
.fetch_all(&self.pool)
178
+
.await?;
179
+
180
+
Ok(rows)
181
+
}
182
+
}
183
+
184
+
#[async_trait]
185
+
impl IdentityStorage for PostgresStorage {
186
+
async fn upsert_identity(&self, identity: &Identity) -> Result<()> {
187
+
sqlx::query(
188
+
r#"
189
+
INSERT INTO identities (did, handle, record, created_at, updated_at)
190
+
VALUES ($1, $2, $3, $4, $5)
191
+
ON CONFLICT(did) DO UPDATE SET
192
+
handle = EXCLUDED.handle,
193
+
record = EXCLUDED.record,
194
+
updated_at = EXCLUDED.updated_at
195
+
"#,
196
+
)
197
+
.bind(&identity.did)
198
+
.bind(&identity.handle)
199
+
.bind(&identity.record)
200
+
.bind(identity.created_at)
201
+
.bind(identity.updated_at)
202
+
.execute(&self.pool)
203
+
.await?;
204
+
205
+
Ok(())
206
+
}
207
+
208
+
async fn get_identity_by_did(&self, did: &str) -> Result<Option<Identity>> {
209
+
let row = sqlx::query_as::<_, Identity>("SELECT * FROM identities WHERE did = $1")
210
+
.bind(did)
211
+
.fetch_optional(&self.pool)
212
+
.await?;
213
+
214
+
Ok(row)
215
+
}
216
+
217
+
async fn get_identity_by_handle(&self, handle: &str) -> Result<Option<Identity>> {
218
+
let row = sqlx::query_as::<_, Identity>("SELECT * FROM identities WHERE handle = $1")
219
+
.bind(handle)
220
+
.fetch_optional(&self.pool)
221
+
.await?;
222
+
223
+
Ok(row)
224
+
}
225
+
226
+
async fn delete_identity(&self, did: &str) -> Result<Option<Identity>> {
227
+
let identity = self.get_identity_by_did(did).await?;
228
+
229
+
if identity.is_some() {
230
+
sqlx::query("DELETE FROM identities WHERE did = $1")
231
+
.bind(did)
232
+
.execute(&self.pool)
233
+
.await?;
234
+
}
235
+
236
+
Ok(identity)
237
+
}
238
+
}
239
+
240
+
#[async_trait]
241
+
impl Storage for PostgresStorage {
242
+
async fn migrate(&self) -> Result<()> {
243
+
// Create identities table with JSONB for better JSON performance
244
+
sqlx::query(
245
+
r#"
246
+
CREATE TABLE IF NOT EXISTS identities (
247
+
did TEXT PRIMARY KEY,
248
+
handle TEXT NOT NULL,
249
+
record JSONB NOT NULL,
250
+
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
251
+
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
252
+
);
253
+
"#,
254
+
)
255
+
.execute(&self.pool)
256
+
.await?;
257
+
258
+
// Create indexes for identities table
259
+
sqlx::query("CREATE INDEX IF NOT EXISTS idx_identities_handle ON identities(handle)")
260
+
.execute(&self.pool)
261
+
.await?;
262
+
263
+
sqlx::query(
264
+
"CREATE INDEX IF NOT EXISTS idx_identities_created_at ON identities(created_at)",
265
+
)
266
+
.execute(&self.pool)
267
+
.await?;
268
+
269
+
sqlx::query(
270
+
"CREATE INDEX IF NOT EXISTS idx_identities_updated_at ON identities(updated_at)",
271
+
)
272
+
.execute(&self.pool)
273
+
.await?;
274
+
275
+
// Create posts table with JSONB
276
+
sqlx::query(
277
+
r#"
278
+
CREATE TABLE IF NOT EXISTS posts (
279
+
aturi TEXT PRIMARY KEY,
280
+
cid TEXT NOT NULL,
281
+
title TEXT NOT NULL,
282
+
slug TEXT NOT NULL UNIQUE,
283
+
content TEXT NOT NULL,
284
+
record_key TEXT NOT NULL,
285
+
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
286
+
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
287
+
record JSONB NOT NULL
288
+
);
289
+
"#,
290
+
)
291
+
.execute(&self.pool)
292
+
.await?;
293
+
294
+
// Create indexes for posts table
295
+
sqlx::query("CREATE INDEX IF NOT EXISTS idx_posts_cid ON posts(cid)")
296
+
.execute(&self.pool)
297
+
.await?;
298
+
299
+
sqlx::query("CREATE INDEX IF NOT EXISTS idx_posts_slug ON posts(slug)")
300
+
.execute(&self.pool)
301
+
.await?;
302
+
303
+
sqlx::query("CREATE INDEX IF NOT EXISTS idx_posts_created_at ON posts(created_at)")
304
+
.execute(&self.pool)
305
+
.await?;
306
+
307
+
sqlx::query("CREATE INDEX IF NOT EXISTS idx_posts_updated_at ON posts(updated_at)")
308
+
.execute(&self.pool)
309
+
.await?;
310
+
311
+
sqlx::query("CREATE INDEX IF NOT EXISTS idx_posts_record_key ON posts(record_key)")
312
+
.execute(&self.pool)
313
+
.await?;
314
+
315
+
// Create post_references table with JSONB
316
+
sqlx::query(
317
+
r#"
318
+
CREATE TABLE IF NOT EXISTS post_references (
319
+
aturi TEXT PRIMARY KEY,
320
+
cid TEXT NOT NULL,
321
+
did TEXT NOT NULL,
322
+
collection TEXT NOT NULL,
323
+
post_aturi TEXT NOT NULL,
324
+
discovered_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
325
+
record JSONB NOT NULL
326
+
);
327
+
"#,
328
+
)
329
+
.execute(&self.pool)
330
+
.await?;
331
+
332
+
// Create indexes for post_references table
333
+
sqlx::query("CREATE INDEX IF NOT EXISTS idx_post_references_cid ON post_references(cid)")
334
+
.execute(&self.pool)
335
+
.await?;
336
+
337
+
sqlx::query("CREATE INDEX IF NOT EXISTS idx_post_references_did ON post_references(did)")
338
+
.execute(&self.pool)
339
+
.await?;
340
+
341
+
sqlx::query(
342
+
"CREATE INDEX IF NOT EXISTS idx_post_references_collection ON post_references(collection)",
343
+
)
344
+
.execute(&self.pool)
345
+
.await?;
346
+
347
+
sqlx::query(
348
+
"CREATE INDEX IF NOT EXISTS idx_post_references_discovered_at ON post_references(discovered_at)",
349
+
)
350
+
.execute(&self.pool)
351
+
.await?;
352
+
353
+
sqlx::query(
354
+
"CREATE INDEX IF NOT EXISTS idx_post_references_post_aturi ON post_references(post_aturi)",
355
+
)
356
+
.execute(&self.pool)
357
+
.await?;
358
+
359
+
Ok(())
360
+
}
361
+
}
362
+
363
+
/// PostgreSQL-specific DID document storage adapter
364
+
pub struct PostgresStorageDidDocumentStorage {
365
+
storage: Arc<PostgresStorage>,
366
+
}
367
+
368
+
impl PostgresStorageDidDocumentStorage {
369
+
/// Create a new DID document storage instance backed by PostgreSQL.
370
+
pub fn new(storage: Arc<PostgresStorage>) -> Self {
371
+
Self { storage }
372
+
}
373
+
}
374
+
375
+
#[async_trait]
376
+
impl DidDocumentStorage for PostgresStorageDidDocumentStorage {
377
+
async fn get_document_by_did(&self, did: &str) -> anyhow::Result<Option<Document>> {
378
+
if let Some(identity) = self
379
+
.storage
380
+
.get_identity_by_did(did)
381
+
.await
382
+
.map_err(anyhow::Error::new)?
383
+
{
384
+
let document: Document = serde_json::from_value(identity.record)?;
385
+
Ok(Some(document))
386
+
} else {
387
+
Ok(None)
388
+
}
389
+
}
390
+
391
+
async fn store_document(&self, doc: Document) -> anyhow::Result<()> {
392
+
let handle = doc
393
+
.also_known_as
394
+
.first()
395
+
.and_then(|aka| aka.strip_prefix("at://"))
396
+
.unwrap_or("unknown.handle")
397
+
.to_string();
398
+
399
+
// Create a simple JSON representation of the document
400
+
let record = serde_json::json!(doc);
401
+
402
+
let identity = Identity {
403
+
did: doc.id.clone(),
404
+
handle,
405
+
record,
406
+
created_at: Utc::now(),
407
+
updated_at: Utc::now(),
408
+
};
409
+
410
+
self.storage
411
+
.upsert_identity(&identity)
412
+
.await
413
+
.map_err(anyhow::Error::new)
414
+
}
415
+
416
+
async fn delete_document_by_did(&self, _did: &str) -> anyhow::Result<()> {
417
+
Ok(())
418
+
}
419
+
}
+395
src/storage/sqlite.rs
+395
src/storage/sqlite.rs
···
···
1
+
use std::collections::HashMap;
2
+
use std::sync::Arc;
3
+
4
+
use crate::errors::Result;
5
+
use async_trait::async_trait;
6
+
use atproto_identity::model::Document;
7
+
use atproto_identity::storage::DidDocumentStorage;
8
+
use chrono::Utc;
9
+
use sqlx::Row;
10
+
use sqlx::sqlite::SqlitePool;
11
+
12
+
use super::{Identity, IdentityStorage, Post, PostReference, PostStorage, Storage};
13
+
14
+
/// SQLite storage implementation for blog posts and identities.
15
+
#[derive(Debug, Clone)]
16
+
pub struct SqliteStorage {
17
+
pool: SqlitePool,
18
+
}
19
+
20
+
impl SqliteStorage {
21
+
/// Create a new SQLite storage instance with the given connection pool.
22
+
pub fn new(pool: SqlitePool) -> Self {
23
+
Self { pool }
24
+
}
25
+
26
+
async fn migrate(&self) -> Result<()> {
27
+
// Create identities table
28
+
sqlx::query(
29
+
r#"
30
+
CREATE TABLE IF NOT EXISTS identities (
31
+
did TEXT PRIMARY KEY,
32
+
handle TEXT NOT NULL,
33
+
record JSON NOT NULL,
34
+
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
35
+
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
36
+
);
37
+
"#,
38
+
)
39
+
.execute(&self.pool)
40
+
.await?;
41
+
42
+
// Create indices for identities
43
+
sqlx::query(
44
+
r#"
45
+
CREATE INDEX IF NOT EXISTS idx_identities_handle ON identities(handle);
46
+
CREATE INDEX IF NOT EXISTS idx_identities_created_at ON identities(created_at);
47
+
CREATE INDEX IF NOT EXISTS idx_identities_updated_at ON identities(updated_at);
48
+
"#,
49
+
)
50
+
.execute(&self.pool)
51
+
.await?;
52
+
53
+
// Create posts table
54
+
sqlx::query(
55
+
r#"
56
+
CREATE TABLE IF NOT EXISTS posts (
57
+
aturi TEXT PRIMARY KEY,
58
+
cid TEXT NOT NULL,
59
+
title TEXT NOT NULL,
60
+
slug TEXT NOT NULL UNIQUE,
61
+
content TEXT NOT NULL,
62
+
record_key TEXT NOT NULL,
63
+
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
64
+
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
65
+
record JSON NOT NULL
66
+
);
67
+
"#,
68
+
)
69
+
.execute(&self.pool)
70
+
.await?;
71
+
72
+
// Create indices for posts
73
+
sqlx::query(
74
+
r#"
75
+
CREATE INDEX IF NOT EXISTS idx_posts_slug ON posts(slug);
76
+
CREATE INDEX IF NOT EXISTS idx_posts_record_key ON posts(record_key);
77
+
CREATE INDEX IF NOT EXISTS idx_posts_created_at ON posts(created_at);
78
+
CREATE INDEX IF NOT EXISTS idx_posts_updated_at ON posts(updated_at);
79
+
"#,
80
+
)
81
+
.execute(&self.pool)
82
+
.await?;
83
+
84
+
// Create post_references table
85
+
sqlx::query(
86
+
r#"
87
+
CREATE TABLE IF NOT EXISTS post_references (
88
+
aturi TEXT PRIMARY KEY,
89
+
cid TEXT NOT NULL,
90
+
did TEXT NOT NULL,
91
+
collection TEXT NOT NULL,
92
+
post_aturi TEXT NOT NULL,
93
+
discovered_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
94
+
record JSON NOT NULL
95
+
);
96
+
"#,
97
+
)
98
+
.execute(&self.pool)
99
+
.await?;
100
+
101
+
// Create indices for post_references
102
+
sqlx::query(
103
+
r#"
104
+
CREATE INDEX IF NOT EXISTS idx_post_references_did ON post_references(did);
105
+
CREATE INDEX IF NOT EXISTS idx_post_references_collection ON post_references(collection);
106
+
CREATE INDEX IF NOT EXISTS idx_post_references_post_aturi ON post_references(post_aturi);
107
+
CREATE INDEX IF NOT EXISTS idx_post_references_discovered_at ON post_references(discovered_at);
108
+
"#,
109
+
)
110
+
.execute(&self.pool)
111
+
.await?;
112
+
113
+
Ok(())
114
+
}
115
+
}
116
+
117
+
#[async_trait]
118
+
impl PostStorage for SqliteStorage {
119
+
async fn upsert_post(&self, post: &Post) -> Result<()> {
120
+
sqlx::query(
121
+
r#"
122
+
INSERT INTO posts (aturi, cid, title, slug, content, record_key, created_at, updated_at, record)
123
+
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)
124
+
ON CONFLICT(aturi) DO UPDATE SET
125
+
cid = EXCLUDED.cid,
126
+
title = EXCLUDED.title,
127
+
slug = EXCLUDED.slug,
128
+
content = EXCLUDED.content,
129
+
record_key = EXCLUDED.record_key,
130
+
updated_at = EXCLUDED.updated_at,
131
+
record = EXCLUDED.record
132
+
"#,
133
+
)
134
+
.bind(&post.aturi)
135
+
.bind(&post.cid)
136
+
.bind(&post.title)
137
+
.bind(&post.slug)
138
+
.bind(&post.content)
139
+
.bind(&post.record_key)
140
+
.bind(post.created_at)
141
+
.bind(post.updated_at)
142
+
.bind(&post.record)
143
+
.execute(&self.pool)
144
+
.await?;
145
+
146
+
Ok(())
147
+
}
148
+
149
+
async fn get_post(&self, aturi: &str) -> Result<Option<Post>> {
150
+
let row = sqlx::query_as::<_, Post>("SELECT * FROM posts WHERE aturi = $1")
151
+
.bind(aturi)
152
+
.fetch_optional(&self.pool)
153
+
.await?;
154
+
155
+
Ok(row)
156
+
}
157
+
158
+
async fn get_posts(&self) -> Result<Vec<Post>> {
159
+
let rows = sqlx::query_as::<_, Post>("SELECT * FROM posts ORDER BY created_at DESC")
160
+
.fetch_all(&self.pool)
161
+
.await?;
162
+
163
+
Ok(rows)
164
+
}
165
+
166
+
async fn delete_post(&self, aturi: &str) -> Result<Option<Post>> {
167
+
let post = self.get_post(aturi).await?;
168
+
169
+
if post.is_some() {
170
+
sqlx::query("DELETE FROM posts WHERE aturi = $1")
171
+
.bind(aturi)
172
+
.execute(&self.pool)
173
+
.await?;
174
+
}
175
+
176
+
Ok(post)
177
+
}
178
+
179
+
async fn upsert_post_reference(&self, post_reference: &PostReference) -> Result<bool> {
180
+
let existing = sqlx::query("SELECT 1 FROM post_references WHERE aturi = $1")
181
+
.bind(&post_reference.aturi)
182
+
.fetch_optional(&self.pool)
183
+
.await?;
184
+
185
+
let is_new = existing.is_none();
186
+
187
+
sqlx::query(
188
+
r#"
189
+
INSERT INTO post_references (aturi, cid, did, collection, post_aturi, discovered_at, record)
190
+
VALUES ($1, $2, $3, $4, $5, $6, $7)
191
+
ON CONFLICT(aturi) DO UPDATE SET
192
+
cid = EXCLUDED.cid,
193
+
did = EXCLUDED.did,
194
+
collection = EXCLUDED.collection,
195
+
post_aturi = EXCLUDED.post_aturi,
196
+
record = EXCLUDED.record
197
+
"#,
198
+
)
199
+
.bind(&post_reference.aturi)
200
+
.bind(&post_reference.cid)
201
+
.bind(&post_reference.did)
202
+
.bind(&post_reference.collection)
203
+
.bind(&post_reference.post_aturi)
204
+
.bind(post_reference.discovered_at)
205
+
.bind(&post_reference.record)
206
+
.execute(&self.pool)
207
+
.await?;
208
+
209
+
Ok(is_new)
210
+
}
211
+
212
+
async fn delete_post_reference(&self, aturi: &str) -> Result<()> {
213
+
sqlx::query("DELETE FROM post_references WHERE aturi = $1")
214
+
.bind(aturi)
215
+
.execute(&self.pool)
216
+
.await?;
217
+
218
+
Ok(())
219
+
}
220
+
221
+
async fn get_post_reference_count(&self, post_aturi: &str) -> Result<HashMap<String, i64>> {
222
+
let rows = sqlx::query(
223
+
r#"
224
+
SELECT collection, COUNT(*) as count
225
+
FROM post_references
226
+
WHERE post_aturi = $1
227
+
GROUP BY collection
228
+
"#,
229
+
)
230
+
.bind(post_aturi)
231
+
.fetch_all(&self.pool)
232
+
.await?;
233
+
234
+
let mut count_map = HashMap::new();
235
+
for row in rows {
236
+
let collection: String = row.get("collection");
237
+
let count: i64 = row.get("count");
238
+
count_map.insert(collection, count);
239
+
}
240
+
241
+
Ok(count_map)
242
+
}
243
+
244
+
async fn get_post_references_for_post(&self, post_aturi: &str) -> Result<Vec<PostReference>> {
245
+
let rows = sqlx::query_as::<_, PostReference>(
246
+
"SELECT * FROM post_references WHERE post_aturi = $1 ORDER BY discovered_at DESC",
247
+
)
248
+
.bind(post_aturi)
249
+
.fetch_all(&self.pool)
250
+
.await?;
251
+
252
+
Ok(rows)
253
+
}
254
+
255
+
async fn get_post_references_for_post_for_collection(
256
+
&self,
257
+
post_aturi: &str,
258
+
collection: &str,
259
+
) -> Result<Vec<PostReference>> {
260
+
let rows = sqlx::query_as::<_, PostReference>(
261
+
"SELECT * FROM post_references WHERE post_aturi = $1 AND collection = $2 ORDER BY discovered_at DESC",
262
+
)
263
+
.bind(post_aturi)
264
+
.bind(collection)
265
+
.fetch_all(&self.pool)
266
+
.await?;
267
+
268
+
Ok(rows)
269
+
}
270
+
}
271
+
272
+
#[async_trait]
273
+
impl IdentityStorage for SqliteStorage {
274
+
async fn upsert_identity(&self, identity: &Identity) -> Result<()> {
275
+
sqlx::query(
276
+
r#"
277
+
INSERT INTO identities (did, handle, record, created_at, updated_at)
278
+
VALUES ($1, $2, $3, $4, $5)
279
+
ON CONFLICT(did) DO UPDATE SET
280
+
handle = EXCLUDED.handle,
281
+
record = EXCLUDED.record,
282
+
updated_at = EXCLUDED.updated_at
283
+
"#,
284
+
)
285
+
.bind(&identity.did)
286
+
.bind(&identity.handle)
287
+
.bind(&identity.record)
288
+
.bind(identity.created_at)
289
+
.bind(identity.updated_at)
290
+
.execute(&self.pool)
291
+
.await?;
292
+
293
+
Ok(())
294
+
}
295
+
296
+
async fn get_identity_by_did(&self, did: &str) -> Result<Option<Identity>> {
297
+
let row = sqlx::query_as::<_, Identity>("SELECT * FROM identities WHERE did = $1")
298
+
.bind(did)
299
+
.fetch_optional(&self.pool)
300
+
.await?;
301
+
302
+
Ok(row)
303
+
}
304
+
305
+
async fn get_identity_by_handle(&self, handle: &str) -> Result<Option<Identity>> {
306
+
let row = sqlx::query_as::<_, Identity>("SELECT * FROM identities WHERE handle = $1")
307
+
.bind(handle)
308
+
.fetch_optional(&self.pool)
309
+
.await?;
310
+
311
+
Ok(row)
312
+
}
313
+
314
+
async fn delete_identity(&self, did: &str) -> Result<Option<Identity>> {
315
+
let identity = self.get_identity_by_did(did).await?;
316
+
317
+
if identity.is_some() {
318
+
sqlx::query("DELETE FROM identities WHERE did = $1")
319
+
.bind(did)
320
+
.execute(&self.pool)
321
+
.await?;
322
+
}
323
+
324
+
Ok(identity)
325
+
}
326
+
}
327
+
328
+
#[async_trait]
329
+
impl Storage for SqliteStorage {
330
+
async fn migrate(&self) -> Result<()> {
331
+
self.migrate().await
332
+
}
333
+
}
334
+
335
+
/// DID document storage implementation using SQLite.
336
+
pub struct SqliteStorageDidDocumentStorage {
337
+
storage: Arc<SqliteStorage>,
338
+
}
339
+
340
+
impl SqliteStorageDidDocumentStorage {
341
+
/// Create a new DID document storage instance backed by SQLite.
342
+
pub fn new(storage: Arc<SqliteStorage>) -> Self {
343
+
Self { storage }
344
+
}
345
+
}
346
+
347
+
#[async_trait]
348
+
impl DidDocumentStorage for SqliteStorageDidDocumentStorage {
349
+
async fn get_document_by_did(&self, did: &str) -> anyhow::Result<Option<Document>> {
350
+
if let Some(identity) = self
351
+
.storage
352
+
.get_identity_by_did(did)
353
+
.await
354
+
.map_err(anyhow::Error::new)?
355
+
{
356
+
let document: Document = serde_json::from_value(identity.record)?;
357
+
Ok(Some(document))
358
+
} else {
359
+
Ok(None)
360
+
}
361
+
}
362
+
363
+
async fn store_document(&self, doc: Document) -> anyhow::Result<()> {
364
+
let handle = doc
365
+
.also_known_as
366
+
.first()
367
+
.and_then(|aka| aka.strip_prefix("at://"))
368
+
.unwrap_or("unknown.handle")
369
+
.to_string();
370
+
371
+
// Create a JSON representation of the document
372
+
let record = serde_json::json!(doc);
373
+
374
+
let identity = Identity {
375
+
did: doc.id.clone(),
376
+
handle,
377
+
record,
378
+
created_at: Utc::now(),
379
+
updated_at: Utc::now(),
380
+
};
381
+
382
+
self.storage
383
+
.upsert_identity(&identity)
384
+
.await
385
+
.map_err(anyhow::Error::new)
386
+
}
387
+
388
+
async fn delete_document_by_did(&self, did: &str) -> anyhow::Result<()> {
389
+
self.storage
390
+
.delete_identity(did)
391
+
.await
392
+
.map_err(anyhow::Error::new)?;
393
+
Ok(())
394
+
}
395
+
}
+60
src/templates.rs
+60
src/templates.rs
···
···
1
+
//! Template engine configuration for embedded and reloadable templates.
2
+
//!
3
+
//! Provides template engines with embedded assets (production) or
4
+
//! filesystem auto-reloading (development). Feature-flag controlled.
5
+
6
+
#[cfg(feature = "reload")]
7
+
use minijinja_autoreload::AutoReloader;
8
+
9
+
#[cfg(feature = "embed")]
10
+
use minijinja::Environment;
11
+
12
+
#[cfg(feature = "reload")]
13
+
/// Build template environment with auto-reloading for development
14
+
pub fn build_env(external_base: String) -> AutoReloader {
15
+
reload_env::build_env(external_base)
16
+
}
17
+
18
+
#[cfg(feature = "embed")]
19
+
/// Build template environment with embedded templates for production
20
+
pub fn build_env(external_base: String, version: String) -> Environment<'static> {
21
+
embed_env::build_env(external_base, version)
22
+
}
23
+
24
+
#[cfg(feature = "reload")]
25
+
mod reload_env {
26
+
use std::path::PathBuf;
27
+
28
+
use minijinja::{Environment, path_loader};
29
+
use minijinja_autoreload::AutoReloader;
30
+
31
+
pub fn build_env(external_base: String) -> AutoReloader {
32
+
let inner_external_base = external_base.clone();
33
+
AutoReloader::new(move |notifier| {
34
+
let template_path = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("templates");
35
+
let mut env = Environment::new();
36
+
env.set_trim_blocks(true);
37
+
env.set_lstrip_blocks(true);
38
+
env.add_global("external_base", inner_external_base.as_str());
39
+
env.set_loader(path_loader(&template_path));
40
+
notifier.set_fast_reload(true);
41
+
notifier.watch_path(&template_path, true);
42
+
Ok(env)
43
+
})
44
+
}
45
+
}
46
+
47
+
#[cfg(feature = "embed")]
48
+
mod embed_env {
49
+
use minijinja::Environment;
50
+
51
+
pub fn build_env(external_base: String, version: String) -> Environment<'static> {
52
+
let mut env = Environment::new();
53
+
env.set_trim_blocks(true);
54
+
env.set_lstrip_blocks(true);
55
+
env.add_global("external_base", external_base);
56
+
env.add_global("version", version.clone());
57
+
minijinja_embed::load_templates!(&mut env);
58
+
env
59
+
}
60
+
}
+4
static/pico.colors.css
+4
static/pico.colors.css
···
···
1
+
@charset "UTF-8";/*!
2
+
* Pico CSS ✨ v2.1.1 (https://picocss.com)
3
+
* Copyright 2019-2025 - Licensed under MIT
4
+
*/:host,:root{--pico-color-red-950:#1c0d06;--pico-color-red-900:#30130a;--pico-color-red-850:#45150c;--pico-color-red-800:#5c160d;--pico-color-red-750:#72170f;--pico-color-red-700:#861d13;--pico-color-red-650:#9b2318;--pico-color-red-600:#af291d;--pico-color-red-550:#c52f21;--pico-color-red-500:#d93526;--pico-color-red-450:#ee402e;--pico-color-red-400:#f06048;--pico-color-red-350:#f17961;--pico-color-red-300:#f38f79;--pico-color-red-250:#f5a390;--pico-color-red-200:#f5b7a8;--pico-color-red-150:#f6cabf;--pico-color-red-100:#f8dcd6;--pico-color-red-50:#faeeeb;--pico-color-red:#c52f21;--pico-color-pink-950:#25060c;--pico-color-pink-900:#380916;--pico-color-pink-850:#4b0c1f;--pico-color-pink-800:#5f0e28;--pico-color-pink-750:#740f31;--pico-color-pink-700:#88143b;--pico-color-pink-650:#9d1945;--pico-color-pink-600:#b21e4f;--pico-color-pink-550:#c72259;--pico-color-pink-500:#d92662;--pico-color-pink-450:#f42c6f;--pico-color-pink-400:#f6547e;--pico-color-pink-350:#f7708e;--pico-color-pink-300:#f8889e;--pico-color-pink-250:#f99eae;--pico-color-pink-200:#f9b4be;--pico-color-pink-150:#f9c8ce;--pico-color-pink-100:#f9dbdf;--pico-color-pink-50:#fbedef;--pico-color-pink:#d92662;--pico-color-fuchsia-950:#230518;--pico-color-fuchsia-900:#360925;--pico-color-fuchsia-850:#480b33;--pico-color-fuchsia-800:#5c0d41;--pico-color-fuchsia-750:#700e4f;--pico-color-fuchsia-700:#84135e;--pico-color-fuchsia-650:#98176d;--pico-color-fuchsia-600:#ac1c7c;--pico-color-fuchsia-550:#c1208b;--pico-color-fuchsia-500:#d9269d;--pico-color-fuchsia-450:#ed2aac;--pico-color-fuchsia-400:#f748b7;--pico-color-fuchsia-350:#f869bf;--pico-color-fuchsia-300:#f983c7;--pico-color-fuchsia-250:#fa9acf;--pico-color-fuchsia-200:#f9b1d8;--pico-color-fuchsia-150:#f9c6e1;--pico-color-fuchsia-100:#f9daea;--pico-color-fuchsia-50:#fbedf4;--pico-color-fuchsia:#c1208b;--pico-color-purple-950:#1e0820;--pico-color-purple-900:#2d0f33;--pico-color-purple-850:#3d1545;--pico-color-purple-800:#4d1a57;--pico-color-purple-750:#5e206b;--pico-color-purple-700:#6f277d;--pico-color-purple-650:#802e90;--pico-color-purple-600:#9236a4;--pico-color-purple-550:#aa40bf;--pico-color-purple-500:#b645cd;--pico-color-purple-450:#c652dc;--pico-color-purple-400:#cd68e0;--pico-color-purple-350:#d47de4;--pico-color-purple-300:#db90e8;--pico-color-purple-250:#e2a3eb;--pico-color-purple-200:#e7b6ee;--pico-color-purple-150:#edc9f1;--pico-color-purple-100:#f2dcf4;--pico-color-purple-50:#f8eef9;--pico-color-purple:#9236a4;--pico-color-violet-950:#190928;--pico-color-violet-900:#251140;--pico-color-violet-850:#321856;--pico-color-violet-800:#3f1e6d;--pico-color-violet-750:#4d2585;--pico-color-violet-700:#5b2d9c;--pico-color-violet-650:#6935b3;--pico-color-violet-600:#7540bf;--pico-color-violet-550:#8352c5;--pico-color-violet-500:#9062ca;--pico-color-violet-450:#9b71cf;--pico-color-violet-400:#a780d4;--pico-color-violet-350:#b290d9;--pico-color-violet-300:#bd9fdf;--pico-color-violet-250:#c9afe4;--pico-color-violet-200:#d3bfe8;--pico-color-violet-150:#decfed;--pico-color-violet-100:#e8dff2;--pico-color-violet-50:#f3eff7;--pico-color-violet:#7540bf;--pico-color-indigo-950:#110b31;--pico-color-indigo-900:#181546;--pico-color-indigo-850:#1f1e5e;--pico-color-indigo-800:#272678;--pico-color-indigo-750:#2f2f92;--pico-color-indigo-700:#3838ab;--pico-color-indigo-650:#4040bf;--pico-color-indigo-600:#524ed2;--pico-color-indigo-550:#655cd6;--pico-color-indigo-500:#7569da;--pico-color-indigo-450:#8577dd;--pico-color-indigo-400:#9486e1;--pico-color-indigo-350:#a294e5;--pico-color-indigo-300:#b0a3e8;--pico-color-indigo-250:#bdb2ec;--pico-color-indigo-200:#cac1ee;--pico-color-indigo-150:#d8d0f1;--pico-color-indigo-100:#e5e0f4;--pico-color-indigo-50:#f2f0f9;--pico-color-indigo:#524ed2;--pico-color-blue-950:#080f2d;--pico-color-blue-900:#0c1a41;--pico-color-blue-850:#0e2358;--pico-color-blue-800:#0f2d70;--pico-color-blue-750:#0f3888;--pico-color-blue-700:#1343a0;--pico-color-blue-650:#184eb8;--pico-color-blue-600:#1d59d0;--pico-color-blue-550:#2060df;--pico-color-blue-500:#3c71f7;--pico-color-blue-450:#5c7ef8;--pico-color-blue-400:#748bf8;--pico-color-blue-350:#8999f9;--pico-color-blue-300:#9ca7fa;--pico-color-blue-250:#aeb5fb;--pico-color-blue-200:#bfc3fa;--pico-color-blue-150:#d0d2fa;--pico-color-blue-100:#e0e1fa;--pico-color-blue-50:#f0f0fb;--pico-color-blue:#2060df;--pico-color-azure-950:#04121d;--pico-color-azure-900:#061e2f;--pico-color-azure-850:#052940;--pico-color-azure-800:#033452;--pico-color-azure-750:#014063;--pico-color-azure-700:#014c75;--pico-color-azure-650:#015887;--pico-color-azure-600:#02659a;--pico-color-azure-550:#0172ad;--pico-color-azure-500:#017fc0;--pico-color-azure-450:#018cd4;--pico-color-azure-400:#029ae8;--pico-color-azure-350:#01aaff;--pico-color-azure-300:#51b4ff;--pico-color-azure-250:#79c0ff;--pico-color-azure-200:#9bccfd;--pico-color-azure-150:#b7d9fc;--pico-color-azure-100:#d1e5fb;--pico-color-azure-50:#e9f2fc;--pico-color-azure:#0172ad;--pico-color-cyan-950:#041413;--pico-color-cyan-900:#051f1f;--pico-color-cyan-850:#052b2b;--pico-color-cyan-800:#043737;--pico-color-cyan-750:#014343;--pico-color-cyan-700:#015050;--pico-color-cyan-650:#025d5d;--pico-color-cyan-600:#046a6a;--pico-color-cyan-550:#047878;--pico-color-cyan-500:#058686;--pico-color-cyan-450:#059494;--pico-color-cyan-400:#05a2a2;--pico-color-cyan-350:#0ab1b1;--pico-color-cyan-300:#0ac2c2;--pico-color-cyan-250:#0ccece;--pico-color-cyan-200:#25dddd;--pico-color-cyan-150:#3deceb;--pico-color-cyan-100:#58faf9;--pico-color-cyan-50:#c3fcfa;--pico-color-cyan:#047878;--pico-color-jade-950:#04140c;--pico-color-jade-900:#052014;--pico-color-jade-850:#042c1b;--pico-color-jade-800:#033823;--pico-color-jade-750:#00452b;--pico-color-jade-700:#015234;--pico-color-jade-650:#005f3d;--pico-color-jade-600:#006d46;--pico-color-jade-550:#007a50;--pico-color-jade-500:#00895a;--pico-color-jade-450:#029764;--pico-color-jade-400:#00a66e;--pico-color-jade-350:#00b478;--pico-color-jade-300:#00c482;--pico-color-jade-250:#00cc88;--pico-color-jade-200:#21e299;--pico-color-jade-150:#39f1a6;--pico-color-jade-100:#70fcba;--pico-color-jade-50:#cbfce1;--pico-color-jade:#007a50;--pico-color-green-950:#0b1305;--pico-color-green-900:#131f07;--pico-color-green-850:#152b07;--pico-color-green-800:#173806;--pico-color-green-750:#1a4405;--pico-color-green-700:#205107;--pico-color-green-650:#265e09;--pico-color-green-600:#2c6c0c;--pico-color-green-550:#33790f;--pico-color-green-500:#398712;--pico-color-green-450:#409614;--pico-color-green-400:#47a417;--pico-color-green-350:#4eb31b;--pico-color-green-300:#55c21e;--pico-color-green-250:#5dd121;--pico-color-green-200:#62d926;--pico-color-green-150:#77ef3d;--pico-color-green-100:#95fb62;--pico-color-green-50:#d7fbc1;--pico-color-green:#398712;--pico-color-lime-950:#101203;--pico-color-lime-900:#191d03;--pico-color-lime-850:#202902;--pico-color-lime-800:#273500;--pico-color-lime-750:#304100;--pico-color-lime-700:#394d00;--pico-color-lime-650:#435a00;--pico-color-lime-600:#4d6600;--pico-color-lime-550:#577400;--pico-color-lime-500:#628100;--pico-color-lime-450:#6c8f00;--pico-color-lime-400:#779c00;--pico-color-lime-350:#82ab00;--pico-color-lime-300:#8eb901;--pico-color-lime-250:#99c801;--pico-color-lime-200:#a5d601;--pico-color-lime-150:#b2e51a;--pico-color-lime-100:#c1f335;--pico-color-lime-50:#defc85;--pico-color-lime:#a5d601;--pico-color-yellow-950:#141103;--pico-color-yellow-900:#1f1c02;--pico-color-yellow-850:#2b2600;--pico-color-yellow-800:#363100;--pico-color-yellow-750:#423c00;--pico-color-yellow-700:#4e4700;--pico-color-yellow-650:#5b5300;--pico-color-yellow-600:#685f00;--pico-color-yellow-550:#756b00;--pico-color-yellow-500:#827800;--pico-color-yellow-450:#908501;--pico-color-yellow-400:#9e9200;--pico-color-yellow-350:#ad9f00;--pico-color-yellow-300:#bbac00;--pico-color-yellow-250:#caba01;--pico-color-yellow-200:#d9c800;--pico-color-yellow-150:#e8d600;--pico-color-yellow-100:#f2df0d;--pico-color-yellow-50:#fdf1b4;--pico-color-yellow:#f2df0d;--pico-color-amber-950:#161003;--pico-color-amber-900:#231a03;--pico-color-amber-850:#312302;--pico-color-amber-800:#3f2d00;--pico-color-amber-750:#4d3700;--pico-color-amber-700:#5b4200;--pico-color-amber-650:#694d00;--pico-color-amber-600:#785800;--pico-color-amber-550:#876400;--pico-color-amber-500:#977000;--pico-color-amber-450:#a77c00;--pico-color-amber-400:#b78800;--pico-color-amber-350:#c79400;--pico-color-amber-300:#d8a100;--pico-color-amber-250:#e8ae01;--pico-color-amber-200:#ffbf00;--pico-color-amber-150:#fecc63;--pico-color-amber-100:#fddea6;--pico-color-amber-50:#fcefd9;--pico-color-amber:#ffbf00;--pico-color-pumpkin-950:#180f04;--pico-color-pumpkin-900:#271805;--pico-color-pumpkin-850:#372004;--pico-color-pumpkin-800:#482802;--pico-color-pumpkin-750:#593100;--pico-color-pumpkin-700:#693a00;--pico-color-pumpkin-650:#7a4400;--pico-color-pumpkin-600:#8b4f00;--pico-color-pumpkin-550:#9c5900;--pico-color-pumpkin-500:#ad6400;--pico-color-pumpkin-450:#bf6e00;--pico-color-pumpkin-400:#d27a01;--pico-color-pumpkin-350:#e48500;--pico-color-pumpkin-300:#ff9500;--pico-color-pumpkin-250:#ffa23a;--pico-color-pumpkin-200:#feb670;--pico-color-pumpkin-150:#fcca9b;--pico-color-pumpkin-100:#fcdcc1;--pico-color-pumpkin-50:#fceee3;--pico-color-pumpkin:#ff9500;--pico-color-orange-950:#1b0d06;--pico-color-orange-900:#2d1509;--pico-color-orange-850:#411a0a;--pico-color-orange-800:#561e0a;--pico-color-orange-750:#6b220a;--pico-color-orange-700:#7f270b;--pico-color-orange-650:#942d0d;--pico-color-orange-600:#a83410;--pico-color-orange-550:#bd3c13;--pico-color-orange-500:#d24317;--pico-color-orange-450:#e74b1a;--pico-color-orange-400:#f45d2c;--pico-color-orange-350:#f56b3d;--pico-color-orange-300:#f68e68;--pico-color-orange-250:#f8a283;--pico-color-orange-200:#f8b79f;--pico-color-orange-150:#f8cab9;--pico-color-orange-100:#f9dcd2;--pico-color-orange-50:#faeeea;--pico-color-orange:#d24317;--pico-color-sand-950:#111110;--pico-color-sand-900:#1c1b19;--pico-color-sand-850:#272622;--pico-color-sand-800:#32302b;--pico-color-sand-750:#3d3b35;--pico-color-sand-700:#49463f;--pico-color-sand-650:#55524a;--pico-color-sand-600:#615e55;--pico-color-sand-550:#6e6a60;--pico-color-sand-500:#7b776b;--pico-color-sand-450:#888377;--pico-color-sand-400:#959082;--pico-color-sand-350:#a39e8f;--pico-color-sand-300:#b0ab9b;--pico-color-sand-250:#beb8a7;--pico-color-sand-200:#ccc6b4;--pico-color-sand-150:#dad4c2;--pico-color-sand-100:#e8e2d2;--pico-color-sand-50:#f2f0ec;--pico-color-sand:#ccc6b4;--pico-color-grey-950:#111111;--pico-color-grey-900:#1b1b1b;--pico-color-grey-850:#262626;--pico-color-grey-800:#303030;--pico-color-grey-750:#3b3b3b;--pico-color-grey-700:#474747;--pico-color-grey-650:#525252;--pico-color-grey-600:#5e5e5e;--pico-color-grey-550:#6a6a6a;--pico-color-grey-500:#777777;--pico-color-grey-450:#808080;--pico-color-grey-400:#919191;--pico-color-grey-350:#9e9e9e;--pico-color-grey-300:#ababab;--pico-color-grey-250:#b9b9b9;--pico-color-grey-200:#c6c6c6;--pico-color-grey-150:#d4d4d4;--pico-color-grey-100:#e2e2e2;--pico-color-grey-50:#f1f1f1;--pico-color-grey:#ababab;--pico-color-zinc-950:#0f1114;--pico-color-zinc-900:#191c20;--pico-color-zinc-850:#23262c;--pico-color-zinc-800:#2d3138;--pico-color-zinc-750:#373c44;--pico-color-zinc-700:#424751;--pico-color-zinc-650:#4d535e;--pico-color-zinc-600:#5c6370;--pico-color-zinc-550:#646b79;--pico-color-zinc-500:#6f7887;--pico-color-zinc-450:#7b8495;--pico-color-zinc-400:#8891a4;--pico-color-zinc-350:#969eaf;--pico-color-zinc-300:#a4acba;--pico-color-zinc-250:#b3b9c5;--pico-color-zinc-200:#c2c7d0;--pico-color-zinc-150:#d1d5db;--pico-color-zinc-100:#e0e3e7;--pico-color-zinc-50:#f0f1f3;--pico-color-zinc:#646b79;--pico-color-slate-950:#0e1118;--pico-color-slate-900:#181c25;--pico-color-slate-850:#202632;--pico-color-slate-800:#2a3140;--pico-color-slate-750:#333c4e;--pico-color-slate-700:#3d475c;--pico-color-slate-650:#48536b;--pico-color-slate-600:#525f7a;--pico-color-slate-550:#5d6b89;--pico-color-slate-500:#687899;--pico-color-slate-450:#7385a9;--pico-color-slate-400:#8191b5;--pico-color-slate-350:#909ebe;--pico-color-slate-300:#a0acc7;--pico-color-slate-250:#b0b9d0;--pico-color-slate-200:#bfc7d9;--pico-color-slate-150:#cfd5e2;--pico-color-slate-100:#dfe3eb;--pico-color-slate-50:#eff1f4;--pico-color-slate:#525f7a;--pico-color-light:#fff;--pico-color-dark:#000}.pico-color-red-950{color:var(--pico-color-red-950)}.pico-color-red-900{color:var(--pico-color-red-900)}.pico-color-red-850{color:var(--pico-color-red-850)}.pico-color-red-800{color:var(--pico-color-red-800)}.pico-color-red-750{color:var(--pico-color-red-750)}.pico-color-red-700{color:var(--pico-color-red-700)}.pico-color-red-650{color:var(--pico-color-red-650)}.pico-color-red-600{color:var(--pico-color-red-600)}.pico-color-red-550{color:var(--pico-color-red-550)}.pico-color-red-500{color:var(--pico-color-red-500)}.pico-color-red-450{color:var(--pico-color-red-450)}.pico-color-red-400{color:var(--pico-color-red-400)}.pico-color-red-350{color:var(--pico-color-red-350)}.pico-color-red-300{color:var(--pico-color-red-300)}.pico-color-red-250{color:var(--pico-color-red-250)}.pico-color-red-200{color:var(--pico-color-red-200)}.pico-color-red-150{color:var(--pico-color-red-150)}.pico-color-red-100{color:var(--pico-color-red-100)}.pico-color-red-50{color:var(--pico-color-red-50)}.pico-color-red{color:var(--pico-color-red)}.pico-color-pink-950{color:var(--pico-color-pink-950)}.pico-color-pink-900{color:var(--pico-color-pink-900)}.pico-color-pink-850{color:var(--pico-color-pink-850)}.pico-color-pink-800{color:var(--pico-color-pink-800)}.pico-color-pink-750{color:var(--pico-color-pink-750)}.pico-color-pink-700{color:var(--pico-color-pink-700)}.pico-color-pink-650{color:var(--pico-color-pink-650)}.pico-color-pink-600{color:var(--pico-color-pink-600)}.pico-color-pink-550{color:var(--pico-color-pink-550)}.pico-color-pink-500{color:var(--pico-color-pink-500)}.pico-color-pink-450{color:var(--pico-color-pink-450)}.pico-color-pink-400{color:var(--pico-color-pink-400)}.pico-color-pink-350{color:var(--pico-color-pink-350)}.pico-color-pink-300{color:var(--pico-color-pink-300)}.pico-color-pink-250{color:var(--pico-color-pink-250)}.pico-color-pink-200{color:var(--pico-color-pink-200)}.pico-color-pink-150{color:var(--pico-color-pink-150)}.pico-color-pink-100{color:var(--pico-color-pink-100)}.pico-color-pink-50{color:var(--pico-color-pink-50)}.pico-color-pink{color:var(--pico-color-pink)}.pico-color-fuchsia-950{color:var(--pico-color-fuchsia-950)}.pico-color-fuchsia-900{color:var(--pico-color-fuchsia-900)}.pico-color-fuchsia-850{color:var(--pico-color-fuchsia-850)}.pico-color-fuchsia-800{color:var(--pico-color-fuchsia-800)}.pico-color-fuchsia-750{color:var(--pico-color-fuchsia-750)}.pico-color-fuchsia-700{color:var(--pico-color-fuchsia-700)}.pico-color-fuchsia-650{color:var(--pico-color-fuchsia-650)}.pico-color-fuchsia-600{color:var(--pico-color-fuchsia-600)}.pico-color-fuchsia-550{color:var(--pico-color-fuchsia-550)}.pico-color-fuchsia-500{color:var(--pico-color-fuchsia-500)}.pico-color-fuchsia-450{color:var(--pico-color-fuchsia-450)}.pico-color-fuchsia-400{color:var(--pico-color-fuchsia-400)}.pico-color-fuchsia-350{color:var(--pico-color-fuchsia-350)}.pico-color-fuchsia-300{color:var(--pico-color-fuchsia-300)}.pico-color-fuchsia-250{color:var(--pico-color-fuchsia-250)}.pico-color-fuchsia-200{color:var(--pico-color-fuchsia-200)}.pico-color-fuchsia-150{color:var(--pico-color-fuchsia-150)}.pico-color-fuchsia-100{color:var(--pico-color-fuchsia-100)}.pico-color-fuchsia-50{color:var(--pico-color-fuchsia-50)}.pico-color-fuchsia{color:var(--pico-color-fuchsia)}.pico-color-purple-950{color:var(--pico-color-purple-950)}.pico-color-purple-900{color:var(--pico-color-purple-900)}.pico-color-purple-850{color:var(--pico-color-purple-850)}.pico-color-purple-800{color:var(--pico-color-purple-800)}.pico-color-purple-750{color:var(--pico-color-purple-750)}.pico-color-purple-700{color:var(--pico-color-purple-700)}.pico-color-purple-650{color:var(--pico-color-purple-650)}.pico-color-purple-600{color:var(--pico-color-purple-600)}.pico-color-purple-550{color:var(--pico-color-purple-550)}.pico-color-purple-500{color:var(--pico-color-purple-500)}.pico-color-purple-450{color:var(--pico-color-purple-450)}.pico-color-purple-400{color:var(--pico-color-purple-400)}.pico-color-purple-350{color:var(--pico-color-purple-350)}.pico-color-purple-300{color:var(--pico-color-purple-300)}.pico-color-purple-250{color:var(--pico-color-purple-250)}.pico-color-purple-200{color:var(--pico-color-purple-200)}.pico-color-purple-150{color:var(--pico-color-purple-150)}.pico-color-purple-100{color:var(--pico-color-purple-100)}.pico-color-purple-50{color:var(--pico-color-purple-50)}.pico-color-purple{color:var(--pico-color-purple)}.pico-color-violet-950{color:var(--pico-color-violet-950)}.pico-color-violet-900{color:var(--pico-color-violet-900)}.pico-color-violet-850{color:var(--pico-color-violet-850)}.pico-color-violet-800{color:var(--pico-color-violet-800)}.pico-color-violet-750{color:var(--pico-color-violet-750)}.pico-color-violet-700{color:var(--pico-color-violet-700)}.pico-color-violet-650{color:var(--pico-color-violet-650)}.pico-color-violet-600{color:var(--pico-color-violet-600)}.pico-color-violet-550{color:var(--pico-color-violet-550)}.pico-color-violet-500{color:var(--pico-color-violet-500)}.pico-color-violet-450{color:var(--pico-color-violet-450)}.pico-color-violet-400{color:var(--pico-color-violet-400)}.pico-color-violet-350{color:var(--pico-color-violet-350)}.pico-color-violet-300{color:var(--pico-color-violet-300)}.pico-color-violet-250{color:var(--pico-color-violet-250)}.pico-color-violet-200{color:var(--pico-color-violet-200)}.pico-color-violet-150{color:var(--pico-color-violet-150)}.pico-color-violet-100{color:var(--pico-color-violet-100)}.pico-color-violet-50{color:var(--pico-color-violet-50)}.pico-color-violet{color:var(--pico-color-violet)}.pico-color-indigo-950{color:var(--pico-color-indigo-950)}.pico-color-indigo-900{color:var(--pico-color-indigo-900)}.pico-color-indigo-850{color:var(--pico-color-indigo-850)}.pico-color-indigo-800{color:var(--pico-color-indigo-800)}.pico-color-indigo-750{color:var(--pico-color-indigo-750)}.pico-color-indigo-700{color:var(--pico-color-indigo-700)}.pico-color-indigo-650{color:var(--pico-color-indigo-650)}.pico-color-indigo-600{color:var(--pico-color-indigo-600)}.pico-color-indigo-550{color:var(--pico-color-indigo-550)}.pico-color-indigo-500{color:var(--pico-color-indigo-500)}.pico-color-indigo-450{color:var(--pico-color-indigo-450)}.pico-color-indigo-400{color:var(--pico-color-indigo-400)}.pico-color-indigo-350{color:var(--pico-color-indigo-350)}.pico-color-indigo-300{color:var(--pico-color-indigo-300)}.pico-color-indigo-250{color:var(--pico-color-indigo-250)}.pico-color-indigo-200{color:var(--pico-color-indigo-200)}.pico-color-indigo-150{color:var(--pico-color-indigo-150)}.pico-color-indigo-100{color:var(--pico-color-indigo-100)}.pico-color-indigo-50{color:var(--pico-color-indigo-50)}.pico-color-indigo{color:var(--pico-color-indigo)}.pico-color-blue-950{color:var(--pico-color-blue-950)}.pico-color-blue-900{color:var(--pico-color-blue-900)}.pico-color-blue-850{color:var(--pico-color-blue-850)}.pico-color-blue-800{color:var(--pico-color-blue-800)}.pico-color-blue-750{color:var(--pico-color-blue-750)}.pico-color-blue-700{color:var(--pico-color-blue-700)}.pico-color-blue-650{color:var(--pico-color-blue-650)}.pico-color-blue-600{color:var(--pico-color-blue-600)}.pico-color-blue-550{color:var(--pico-color-blue-550)}.pico-color-blue-500{color:var(--pico-color-blue-500)}.pico-color-blue-450{color:var(--pico-color-blue-450)}.pico-color-blue-400{color:var(--pico-color-blue-400)}.pico-color-blue-350{color:var(--pico-color-blue-350)}.pico-color-blue-300{color:var(--pico-color-blue-300)}.pico-color-blue-250{color:var(--pico-color-blue-250)}.pico-color-blue-200{color:var(--pico-color-blue-200)}.pico-color-blue-150{color:var(--pico-color-blue-150)}.pico-color-blue-100{color:var(--pico-color-blue-100)}.pico-color-blue-50{color:var(--pico-color-blue-50)}.pico-color-blue{color:var(--pico-color-blue)}.pico-color-azure-950{color:var(--pico-color-azure-950)}.pico-color-azure-900{color:var(--pico-color-azure-900)}.pico-color-azure-850{color:var(--pico-color-azure-850)}.pico-color-azure-800{color:var(--pico-color-azure-800)}.pico-color-azure-750{color:var(--pico-color-azure-750)}.pico-color-azure-700{color:var(--pico-color-azure-700)}.pico-color-azure-650{color:var(--pico-color-azure-650)}.pico-color-azure-600{color:var(--pico-color-azure-600)}.pico-color-azure-550{color:var(--pico-color-azure-550)}.pico-color-azure-500{color:var(--pico-color-azure-500)}.pico-color-azure-450{color:var(--pico-color-azure-450)}.pico-color-azure-400{color:var(--pico-color-azure-400)}.pico-color-azure-350{color:var(--pico-color-azure-350)}.pico-color-azure-300{color:var(--pico-color-azure-300)}.pico-color-azure-250{color:var(--pico-color-azure-250)}.pico-color-azure-200{color:var(--pico-color-azure-200)}.pico-color-azure-150{color:var(--pico-color-azure-150)}.pico-color-azure-100{color:var(--pico-color-azure-100)}.pico-color-azure-50{color:var(--pico-color-azure-50)}.pico-color-azure{color:var(--pico-color-azure)}.pico-color-cyan-950{color:var(--pico-color-cyan-950)}.pico-color-cyan-900{color:var(--pico-color-cyan-900)}.pico-color-cyan-850{color:var(--pico-color-cyan-850)}.pico-color-cyan-800{color:var(--pico-color-cyan-800)}.pico-color-cyan-750{color:var(--pico-color-cyan-750)}.pico-color-cyan-700{color:var(--pico-color-cyan-700)}.pico-color-cyan-650{color:var(--pico-color-cyan-650)}.pico-color-cyan-600{color:var(--pico-color-cyan-600)}.pico-color-cyan-550{color:var(--pico-color-cyan-550)}.pico-color-cyan-500{color:var(--pico-color-cyan-500)}.pico-color-cyan-450{color:var(--pico-color-cyan-450)}.pico-color-cyan-400{color:var(--pico-color-cyan-400)}.pico-color-cyan-350{color:var(--pico-color-cyan-350)}.pico-color-cyan-300{color:var(--pico-color-cyan-300)}.pico-color-cyan-250{color:var(--pico-color-cyan-250)}.pico-color-cyan-200{color:var(--pico-color-cyan-200)}.pico-color-cyan-150{color:var(--pico-color-cyan-150)}.pico-color-cyan-100{color:var(--pico-color-cyan-100)}.pico-color-cyan-50{color:var(--pico-color-cyan-50)}.pico-color-cyan{color:var(--pico-color-cyan)}.pico-color-jade-950{color:var(--pico-color-jade-950)}.pico-color-jade-900{color:var(--pico-color-jade-900)}.pico-color-jade-850{color:var(--pico-color-jade-850)}.pico-color-jade-800{color:var(--pico-color-jade-800)}.pico-color-jade-750{color:var(--pico-color-jade-750)}.pico-color-jade-700{color:var(--pico-color-jade-700)}.pico-color-jade-650{color:var(--pico-color-jade-650)}.pico-color-jade-600{color:var(--pico-color-jade-600)}.pico-color-jade-550{color:var(--pico-color-jade-550)}.pico-color-jade-500{color:var(--pico-color-jade-500)}.pico-color-jade-450{color:var(--pico-color-jade-450)}.pico-color-jade-400{color:var(--pico-color-jade-400)}.pico-color-jade-350{color:var(--pico-color-jade-350)}.pico-color-jade-300{color:var(--pico-color-jade-300)}.pico-color-jade-250{color:var(--pico-color-jade-250)}.pico-color-jade-200{color:var(--pico-color-jade-200)}.pico-color-jade-150{color:var(--pico-color-jade-150)}.pico-color-jade-100{color:var(--pico-color-jade-100)}.pico-color-jade-50{color:var(--pico-color-jade-50)}.pico-color-jade{color:var(--pico-color-jade)}.pico-color-green-950{color:var(--pico-color-green-950)}.pico-color-green-900{color:var(--pico-color-green-900)}.pico-color-green-850{color:var(--pico-color-green-850)}.pico-color-green-800{color:var(--pico-color-green-800)}.pico-color-green-750{color:var(--pico-color-green-750)}.pico-color-green-700{color:var(--pico-color-green-700)}.pico-color-green-650{color:var(--pico-color-green-650)}.pico-color-green-600{color:var(--pico-color-green-600)}.pico-color-green-550{color:var(--pico-color-green-550)}.pico-color-green-500{color:var(--pico-color-green-500)}.pico-color-green-450{color:var(--pico-color-green-450)}.pico-color-green-400{color:var(--pico-color-green-400)}.pico-color-green-350{color:var(--pico-color-green-350)}.pico-color-green-300{color:var(--pico-color-green-300)}.pico-color-green-250{color:var(--pico-color-green-250)}.pico-color-green-200{color:var(--pico-color-green-200)}.pico-color-green-150{color:var(--pico-color-green-150)}.pico-color-green-100{color:var(--pico-color-green-100)}.pico-color-green-50{color:var(--pico-color-green-50)}.pico-color-green{color:var(--pico-color-green)}.pico-color-lime-950{color:var(--pico-color-lime-950)}.pico-color-lime-900{color:var(--pico-color-lime-900)}.pico-color-lime-850{color:var(--pico-color-lime-850)}.pico-color-lime-800{color:var(--pico-color-lime-800)}.pico-color-lime-750{color:var(--pico-color-lime-750)}.pico-color-lime-700{color:var(--pico-color-lime-700)}.pico-color-lime-650{color:var(--pico-color-lime-650)}.pico-color-lime-600{color:var(--pico-color-lime-600)}.pico-color-lime-550{color:var(--pico-color-lime-550)}.pico-color-lime-500{color:var(--pico-color-lime-500)}.pico-color-lime-450{color:var(--pico-color-lime-450)}.pico-color-lime-400{color:var(--pico-color-lime-400)}.pico-color-lime-350{color:var(--pico-color-lime-350)}.pico-color-lime-300{color:var(--pico-color-lime-300)}.pico-color-lime-250{color:var(--pico-color-lime-250)}.pico-color-lime-200{color:var(--pico-color-lime-200)}.pico-color-lime-150{color:var(--pico-color-lime-150)}.pico-color-lime-100{color:var(--pico-color-lime-100)}.pico-color-lime-50{color:var(--pico-color-lime-50)}.pico-color-lime{color:var(--pico-color-lime)}.pico-color-yellow-950{color:var(--pico-color-yellow-950)}.pico-color-yellow-900{color:var(--pico-color-yellow-900)}.pico-color-yellow-850{color:var(--pico-color-yellow-850)}.pico-color-yellow-800{color:var(--pico-color-yellow-800)}.pico-color-yellow-750{color:var(--pico-color-yellow-750)}.pico-color-yellow-700{color:var(--pico-color-yellow-700)}.pico-color-yellow-650{color:var(--pico-color-yellow-650)}.pico-color-yellow-600{color:var(--pico-color-yellow-600)}.pico-color-yellow-550{color:var(--pico-color-yellow-550)}.pico-color-yellow-500{color:var(--pico-color-yellow-500)}.pico-color-yellow-450{color:var(--pico-color-yellow-450)}.pico-color-yellow-400{color:var(--pico-color-yellow-400)}.pico-color-yellow-350{color:var(--pico-color-yellow-350)}.pico-color-yellow-300{color:var(--pico-color-yellow-300)}.pico-color-yellow-250{color:var(--pico-color-yellow-250)}.pico-color-yellow-200{color:var(--pico-color-yellow-200)}.pico-color-yellow-150{color:var(--pico-color-yellow-150)}.pico-color-yellow-100{color:var(--pico-color-yellow-100)}.pico-color-yellow-50{color:var(--pico-color-yellow-50)}.pico-color-yellow{color:var(--pico-color-yellow)}.pico-color-amber-950{color:var(--pico-color-amber-950)}.pico-color-amber-900{color:var(--pico-color-amber-900)}.pico-color-amber-850{color:var(--pico-color-amber-850)}.pico-color-amber-800{color:var(--pico-color-amber-800)}.pico-color-amber-750{color:var(--pico-color-amber-750)}.pico-color-amber-700{color:var(--pico-color-amber-700)}.pico-color-amber-650{color:var(--pico-color-amber-650)}.pico-color-amber-600{color:var(--pico-color-amber-600)}.pico-color-amber-550{color:var(--pico-color-amber-550)}.pico-color-amber-500{color:var(--pico-color-amber-500)}.pico-color-amber-450{color:var(--pico-color-amber-450)}.pico-color-amber-400{color:var(--pico-color-amber-400)}.pico-color-amber-350{color:var(--pico-color-amber-350)}.pico-color-amber-300{color:var(--pico-color-amber-300)}.pico-color-amber-250{color:var(--pico-color-amber-250)}.pico-color-amber-200{color:var(--pico-color-amber-200)}.pico-color-amber-150{color:var(--pico-color-amber-150)}.pico-color-amber-100{color:var(--pico-color-amber-100)}.pico-color-amber-50{color:var(--pico-color-amber-50)}.pico-color-amber{color:var(--pico-color-amber)}.pico-color-pumpkin-950{color:var(--pico-color-pumpkin-950)}.pico-color-pumpkin-900{color:var(--pico-color-pumpkin-900)}.pico-color-pumpkin-850{color:var(--pico-color-pumpkin-850)}.pico-color-pumpkin-800{color:var(--pico-color-pumpkin-800)}.pico-color-pumpkin-750{color:var(--pico-color-pumpkin-750)}.pico-color-pumpkin-700{color:var(--pico-color-pumpkin-700)}.pico-color-pumpkin-650{color:var(--pico-color-pumpkin-650)}.pico-color-pumpkin-600{color:var(--pico-color-pumpkin-600)}.pico-color-pumpkin-550{color:var(--pico-color-pumpkin-550)}.pico-color-pumpkin-500{color:var(--pico-color-pumpkin-500)}.pico-color-pumpkin-450{color:var(--pico-color-pumpkin-450)}.pico-color-pumpkin-400{color:var(--pico-color-pumpkin-400)}.pico-color-pumpkin-350{color:var(--pico-color-pumpkin-350)}.pico-color-pumpkin-300{color:var(--pico-color-pumpkin-300)}.pico-color-pumpkin-250{color:var(--pico-color-pumpkin-250)}.pico-color-pumpkin-200{color:var(--pico-color-pumpkin-200)}.pico-color-pumpkin-150{color:var(--pico-color-pumpkin-150)}.pico-color-pumpkin-100{color:var(--pico-color-pumpkin-100)}.pico-color-pumpkin-50{color:var(--pico-color-pumpkin-50)}.pico-color-pumpkin{color:var(--pico-color-pumpkin)}.pico-color-orange-950{color:var(--pico-color-orange-950)}.pico-color-orange-900{color:var(--pico-color-orange-900)}.pico-color-orange-850{color:var(--pico-color-orange-850)}.pico-color-orange-800{color:var(--pico-color-orange-800)}.pico-color-orange-750{color:var(--pico-color-orange-750)}.pico-color-orange-700{color:var(--pico-color-orange-700)}.pico-color-orange-650{color:var(--pico-color-orange-650)}.pico-color-orange-600{color:var(--pico-color-orange-600)}.pico-color-orange-550{color:var(--pico-color-orange-550)}.pico-color-orange-500{color:var(--pico-color-orange-500)}.pico-color-orange-450{color:var(--pico-color-orange-450)}.pico-color-orange-400{color:var(--pico-color-orange-400)}.pico-color-orange-350{color:var(--pico-color-orange-350)}.pico-color-orange-300{color:var(--pico-color-orange-300)}.pico-color-orange-250{color:var(--pico-color-orange-250)}.pico-color-orange-200{color:var(--pico-color-orange-200)}.pico-color-orange-150{color:var(--pico-color-orange-150)}.pico-color-orange-100{color:var(--pico-color-orange-100)}.pico-color-orange-50{color:var(--pico-color-orange-50)}.pico-color-orange{color:var(--pico-color-orange)}.pico-color-sand-950{color:var(--pico-color-sand-950)}.pico-color-sand-900{color:var(--pico-color-sand-900)}.pico-color-sand-850{color:var(--pico-color-sand-850)}.pico-color-sand-800{color:var(--pico-color-sand-800)}.pico-color-sand-750{color:var(--pico-color-sand-750)}.pico-color-sand-700{color:var(--pico-color-sand-700)}.pico-color-sand-650{color:var(--pico-color-sand-650)}.pico-color-sand-600{color:var(--pico-color-sand-600)}.pico-color-sand-550{color:var(--pico-color-sand-550)}.pico-color-sand-500{color:var(--pico-color-sand-500)}.pico-color-sand-450{color:var(--pico-color-sand-450)}.pico-color-sand-400{color:var(--pico-color-sand-400)}.pico-color-sand-350{color:var(--pico-color-sand-350)}.pico-color-sand-300{color:var(--pico-color-sand-300)}.pico-color-sand-250{color:var(--pico-color-sand-250)}.pico-color-sand-200{color:var(--pico-color-sand-200)}.pico-color-sand-150{color:var(--pico-color-sand-150)}.pico-color-sand-100{color:var(--pico-color-sand-100)}.pico-color-sand-50{color:var(--pico-color-sand-50)}.pico-color-sand{color:var(--pico-color-sand)}.pico-color-grey-950{color:var(--pico-color-grey-950)}.pico-color-grey-900{color:var(--pico-color-grey-900)}.pico-color-grey-850{color:var(--pico-color-grey-850)}.pico-color-grey-800{color:var(--pico-color-grey-800)}.pico-color-grey-750{color:var(--pico-color-grey-750)}.pico-color-grey-700{color:var(--pico-color-grey-700)}.pico-color-grey-650{color:var(--pico-color-grey-650)}.pico-color-grey-600{color:var(--pico-color-grey-600)}.pico-color-grey-550{color:var(--pico-color-grey-550)}.pico-color-grey-500{color:var(--pico-color-grey-500)}.pico-color-grey-450{color:var(--pico-color-grey-450)}.pico-color-grey-400{color:var(--pico-color-grey-400)}.pico-color-grey-350{color:var(--pico-color-grey-350)}.pico-color-grey-300{color:var(--pico-color-grey-300)}.pico-color-grey-250{color:var(--pico-color-grey-250)}.pico-color-grey-200{color:var(--pico-color-grey-200)}.pico-color-grey-150{color:var(--pico-color-grey-150)}.pico-color-grey-100{color:var(--pico-color-grey-100)}.pico-color-grey-50{color:var(--pico-color-grey-50)}.pico-color-grey{color:var(--pico-color-grey)}.pico-color-zinc-950{color:var(--pico-color-zinc-950)}.pico-color-zinc-900{color:var(--pico-color-zinc-900)}.pico-color-zinc-850{color:var(--pico-color-zinc-850)}.pico-color-zinc-800{color:var(--pico-color-zinc-800)}.pico-color-zinc-750{color:var(--pico-color-zinc-750)}.pico-color-zinc-700{color:var(--pico-color-zinc-700)}.pico-color-zinc-650{color:var(--pico-color-zinc-650)}.pico-color-zinc-600{color:var(--pico-color-zinc-600)}.pico-color-zinc-550{color:var(--pico-color-zinc-550)}.pico-color-zinc-500{color:var(--pico-color-zinc-500)}.pico-color-zinc-450{color:var(--pico-color-zinc-450)}.pico-color-zinc-400{color:var(--pico-color-zinc-400)}.pico-color-zinc-350{color:var(--pico-color-zinc-350)}.pico-color-zinc-300{color:var(--pico-color-zinc-300)}.pico-color-zinc-250{color:var(--pico-color-zinc-250)}.pico-color-zinc-200{color:var(--pico-color-zinc-200)}.pico-color-zinc-150{color:var(--pico-color-zinc-150)}.pico-color-zinc-100{color:var(--pico-color-zinc-100)}.pico-color-zinc-50{color:var(--pico-color-zinc-50)}.pico-color-zinc{color:var(--pico-color-zinc)}.pico-color-slate-950{color:var(--pico-color-slate-950)}.pico-color-slate-900{color:var(--pico-color-slate-900)}.pico-color-slate-850{color:var(--pico-color-slate-850)}.pico-color-slate-800{color:var(--pico-color-slate-800)}.pico-color-slate-750{color:var(--pico-color-slate-750)}.pico-color-slate-700{color:var(--pico-color-slate-700)}.pico-color-slate-650{color:var(--pico-color-slate-650)}.pico-color-slate-600{color:var(--pico-color-slate-600)}.pico-color-slate-550{color:var(--pico-color-slate-550)}.pico-color-slate-500{color:var(--pico-color-slate-500)}.pico-color-slate-450{color:var(--pico-color-slate-450)}.pico-color-slate-400{color:var(--pico-color-slate-400)}.pico-color-slate-350{color:var(--pico-color-slate-350)}.pico-color-slate-300{color:var(--pico-color-slate-300)}.pico-color-slate-250{color:var(--pico-color-slate-250)}.pico-color-slate-200{color:var(--pico-color-slate-200)}.pico-color-slate-150{color:var(--pico-color-slate-150)}.pico-color-slate-100{color:var(--pico-color-slate-100)}.pico-color-slate-50{color:var(--pico-color-slate-50)}.pico-color-slate{color:var(--pico-color-slate)}.pico-background-red-950{background-color:var(--pico-color-red-950);color:var(--pico-color-light)}.pico-background-red-900{background-color:var(--pico-color-red-900);color:var(--pico-color-light)}.pico-background-red-850{background-color:var(--pico-color-red-850);color:var(--pico-color-light)}.pico-background-red-800{background-color:var(--pico-color-red-800);color:var(--pico-color-light)}.pico-background-red-750{background-color:var(--pico-color-red-750);color:var(--pico-color-light)}.pico-background-red-700{background-color:var(--pico-color-red-700);color:var(--pico-color-light)}.pico-background-red-650{background-color:var(--pico-color-red-650);color:var(--pico-color-light)}.pico-background-red-600{background-color:var(--pico-color-red-600);color:var(--pico-color-light)}.pico-background-red-550{background-color:var(--pico-color-red-550);color:var(--pico-color-light)}.pico-background-red-500{background-color:var(--pico-color-red-500);color:var(--pico-color-light)}.pico-background-red-450{background-color:var(--pico-color-red-450);color:var(--pico-color-light)}.pico-background-red-400{background-color:var(--pico-color-red-400);color:var(--pico-color-dark)}.pico-background-red-350{background-color:var(--pico-color-red-350);color:var(--pico-color-dark)}.pico-background-red-300{background-color:var(--pico-color-red-300);color:var(--pico-color-dark)}.pico-background-red-250{background-color:var(--pico-color-red-250);color:var(--pico-color-dark)}.pico-background-red-200{background-color:var(--pico-color-red-200);color:var(--pico-color-dark)}.pico-background-red-150{background-color:var(--pico-color-red-150);color:var(--pico-color-dark)}.pico-background-red-100{background-color:var(--pico-color-red-100);color:var(--pico-color-dark)}.pico-background-red-50{background-color:var(--pico-color-red-50);color:var(--pico-color-dark)}.pico-background-red{background-color:var(--pico-color-red);color:var(--pico-color-light)}.pico-background-pink-950{background-color:var(--pico-color-pink-950);color:var(--pico-color-light)}.pico-background-pink-900{background-color:var(--pico-color-pink-900);color:var(--pico-color-light)}.pico-background-pink-850{background-color:var(--pico-color-pink-850);color:var(--pico-color-light)}.pico-background-pink-800{background-color:var(--pico-color-pink-800);color:var(--pico-color-light)}.pico-background-pink-750{background-color:var(--pico-color-pink-750);color:var(--pico-color-light)}.pico-background-pink-700{background-color:var(--pico-color-pink-700);color:var(--pico-color-light)}.pico-background-pink-650{background-color:var(--pico-color-pink-650);color:var(--pico-color-light)}.pico-background-pink-600{background-color:var(--pico-color-pink-600);color:var(--pico-color-light)}.pico-background-pink-550{background-color:var(--pico-color-pink-550);color:var(--pico-color-light)}.pico-background-pink-500{background-color:var(--pico-color-pink-500);color:var(--pico-color-light)}.pico-background-pink-450{background-color:var(--pico-color-pink-450);color:var(--pico-color-light)}.pico-background-pink-400{background-color:var(--pico-color-pink-400);color:var(--pico-color-dark)}.pico-background-pink-350{background-color:var(--pico-color-pink-350);color:var(--pico-color-dark)}.pico-background-pink-300{background-color:var(--pico-color-pink-300);color:var(--pico-color-dark)}.pico-background-pink-250{background-color:var(--pico-color-pink-250);color:var(--pico-color-dark)}.pico-background-pink-200{background-color:var(--pico-color-pink-200);color:var(--pico-color-dark)}.pico-background-pink-150{background-color:var(--pico-color-pink-150);color:var(--pico-color-dark)}.pico-background-pink-100{background-color:var(--pico-color-pink-100);color:var(--pico-color-dark)}.pico-background-pink-50{background-color:var(--pico-color-pink-50);color:var(--pico-color-dark)}.pico-background-pink{background-color:var(--pico-color-pink);color:var(--pico-color-light)}.pico-background-fuchsia-950{background-color:var(--pico-color-fuchsia-950);color:var(--pico-color-light)}.pico-background-fuchsia-900{background-color:var(--pico-color-fuchsia-900);color:var(--pico-color-light)}.pico-background-fuchsia-850{background-color:var(--pico-color-fuchsia-850);color:var(--pico-color-light)}.pico-background-fuchsia-800{background-color:var(--pico-color-fuchsia-800);color:var(--pico-color-light)}.pico-background-fuchsia-750{background-color:var(--pico-color-fuchsia-750);color:var(--pico-color-light)}.pico-background-fuchsia-700{background-color:var(--pico-color-fuchsia-700);color:var(--pico-color-light)}.pico-background-fuchsia-650{background-color:var(--pico-color-fuchsia-650);color:var(--pico-color-light)}.pico-background-fuchsia-600{background-color:var(--pico-color-fuchsia-600);color:var(--pico-color-light)}.pico-background-fuchsia-550{background-color:var(--pico-color-fuchsia-550);color:var(--pico-color-light)}.pico-background-fuchsia-500{background-color:var(--pico-color-fuchsia-500);color:var(--pico-color-light)}.pico-background-fuchsia-450{background-color:var(--pico-color-fuchsia-450);color:var(--pico-color-light)}.pico-background-fuchsia-400{background-color:var(--pico-color-fuchsia-400);color:var(--pico-color-dark)}.pico-background-fuchsia-350{background-color:var(--pico-color-fuchsia-350);color:var(--pico-color-dark)}.pico-background-fuchsia-300{background-color:var(--pico-color-fuchsia-300);color:var(--pico-color-dark)}.pico-background-fuchsia-250{background-color:var(--pico-color-fuchsia-250);color:var(--pico-color-dark)}.pico-background-fuchsia-200{background-color:var(--pico-color-fuchsia-200);color:var(--pico-color-dark)}.pico-background-fuchsia-150{background-color:var(--pico-color-fuchsia-150);color:var(--pico-color-dark)}.pico-background-fuchsia-100{background-color:var(--pico-color-fuchsia-100);color:var(--pico-color-dark)}.pico-background-fuchsia-50{background-color:var(--pico-color-fuchsia-50);color:var(--pico-color-dark)}.pico-background-fuchsia{background-color:var(--pico-color-fuchsia);color:var(--pico-color-light)}.pico-background-purple-950{background-color:var(--pico-color-purple-950);color:var(--pico-color-light)}.pico-background-purple-900{background-color:var(--pico-color-purple-900);color:var(--pico-color-light)}.pico-background-purple-850{background-color:var(--pico-color-purple-850);color:var(--pico-color-light)}.pico-background-purple-800{background-color:var(--pico-color-purple-800);color:var(--pico-color-light)}.pico-background-purple-750{background-color:var(--pico-color-purple-750);color:var(--pico-color-light)}.pico-background-purple-700{background-color:var(--pico-color-purple-700);color:var(--pico-color-light)}.pico-background-purple-650{background-color:var(--pico-color-purple-650);color:var(--pico-color-light)}.pico-background-purple-600{background-color:var(--pico-color-purple-600);color:var(--pico-color-light)}.pico-background-purple-550{background-color:var(--pico-color-purple-550);color:var(--pico-color-light)}.pico-background-purple-500{background-color:var(--pico-color-purple-500);color:var(--pico-color-light)}.pico-background-purple-450{background-color:var(--pico-color-purple-450);color:var(--pico-color-dark)}.pico-background-purple-400{background-color:var(--pico-color-purple-400);color:var(--pico-color-dark)}.pico-background-purple-350{background-color:var(--pico-color-purple-350);color:var(--pico-color-dark)}.pico-background-purple-300{background-color:var(--pico-color-purple-300);color:var(--pico-color-dark)}.pico-background-purple-250{background-color:var(--pico-color-purple-250);color:var(--pico-color-dark)}.pico-background-purple-200{background-color:var(--pico-color-purple-200);color:var(--pico-color-dark)}.pico-background-purple-150{background-color:var(--pico-color-purple-150);color:var(--pico-color-dark)}.pico-background-purple-100{background-color:var(--pico-color-purple-100);color:var(--pico-color-dark)}.pico-background-purple-50{background-color:var(--pico-color-purple-50);color:var(--pico-color-dark)}.pico-background-purple{background-color:var(--pico-color-purple);color:var(--pico-color-light)}.pico-background-violet-950{background-color:var(--pico-color-violet-950);color:var(--pico-color-light)}.pico-background-violet-900{background-color:var(--pico-color-violet-900);color:var(--pico-color-light)}.pico-background-violet-850{background-color:var(--pico-color-violet-850);color:var(--pico-color-light)}.pico-background-violet-800{background-color:var(--pico-color-violet-800);color:var(--pico-color-light)}.pico-background-violet-750{background-color:var(--pico-color-violet-750);color:var(--pico-color-light)}.pico-background-violet-700{background-color:var(--pico-color-violet-700);color:var(--pico-color-light)}.pico-background-violet-650{background-color:var(--pico-color-violet-650);color:var(--pico-color-light)}.pico-background-violet-600{background-color:var(--pico-color-violet-600);color:var(--pico-color-light)}.pico-background-violet-550{background-color:var(--pico-color-violet-550);color:var(--pico-color-light)}.pico-background-violet-500{background-color:var(--pico-color-violet-500);color:var(--pico-color-light)}.pico-background-violet-450{background-color:var(--pico-color-violet-450);color:var(--pico-color-dark)}.pico-background-violet-400{background-color:var(--pico-color-violet-400);color:var(--pico-color-dark)}.pico-background-violet-350{background-color:var(--pico-color-violet-350);color:var(--pico-color-dark)}.pico-background-violet-300{background-color:var(--pico-color-violet-300);color:var(--pico-color-dark)}.pico-background-violet-250{background-color:var(--pico-color-violet-250);color:var(--pico-color-dark)}.pico-background-violet-200{background-color:var(--pico-color-violet-200);color:var(--pico-color-dark)}.pico-background-violet-150{background-color:var(--pico-color-violet-150);color:var(--pico-color-dark)}.pico-background-violet-100{background-color:var(--pico-color-violet-100);color:var(--pico-color-dark)}.pico-background-violet-50{background-color:var(--pico-color-violet-50);color:var(--pico-color-dark)}.pico-background-violet{background-color:var(--pico-color-violet);color:var(--pico-color-light)}.pico-background-indigo-950{background-color:var(--pico-color-indigo-950);color:var(--pico-color-light)}.pico-background-indigo-900{background-color:var(--pico-color-indigo-900);color:var(--pico-color-light)}.pico-background-indigo-850{background-color:var(--pico-color-indigo-850);color:var(--pico-color-light)}.pico-background-indigo-800{background-color:var(--pico-color-indigo-800);color:var(--pico-color-light)}.pico-background-indigo-750{background-color:var(--pico-color-indigo-750);color:var(--pico-color-light)}.pico-background-indigo-700{background-color:var(--pico-color-indigo-700);color:var(--pico-color-light)}.pico-background-indigo-650{background-color:var(--pico-color-indigo-650);color:var(--pico-color-light)}.pico-background-indigo-600{background-color:var(--pico-color-indigo-600);color:var(--pico-color-light)}.pico-background-indigo-550{background-color:var(--pico-color-indigo-550);color:var(--pico-color-light)}.pico-background-indigo-500{background-color:var(--pico-color-indigo-500);color:var(--pico-color-light)}.pico-background-indigo-450{background-color:var(--pico-color-indigo-450);color:var(--pico-color-dark)}.pico-background-indigo-400{background-color:var(--pico-color-indigo-400);color:var(--pico-color-dark)}.pico-background-indigo-350{background-color:var(--pico-color-indigo-350);color:var(--pico-color-dark)}.pico-background-indigo-300{background-color:var(--pico-color-indigo-300);color:var(--pico-color-dark)}.pico-background-indigo-250{background-color:var(--pico-color-indigo-250);color:var(--pico-color-dark)}.pico-background-indigo-200{background-color:var(--pico-color-indigo-200);color:var(--pico-color-dark)}.pico-background-indigo-150{background-color:var(--pico-color-indigo-150);color:var(--pico-color-dark)}.pico-background-indigo-100{background-color:var(--pico-color-indigo-100);color:var(--pico-color-dark)}.pico-background-indigo-50{background-color:var(--pico-color-indigo-50);color:var(--pico-color-dark)}.pico-background-indigo{background-color:var(--pico-color-indigo);color:var(--pico-color-light)}.pico-background-blue-950{background-color:var(--pico-color-blue-950);color:var(--pico-color-light)}.pico-background-blue-900{background-color:var(--pico-color-blue-900);color:var(--pico-color-light)}.pico-background-blue-850{background-color:var(--pico-color-blue-850);color:var(--pico-color-light)}.pico-background-blue-800{background-color:var(--pico-color-blue-800);color:var(--pico-color-light)}.pico-background-blue-750{background-color:var(--pico-color-blue-750);color:var(--pico-color-light)}.pico-background-blue-700{background-color:var(--pico-color-blue-700);color:var(--pico-color-light)}.pico-background-blue-650{background-color:var(--pico-color-blue-650);color:var(--pico-color-light)}.pico-background-blue-600{background-color:var(--pico-color-blue-600);color:var(--pico-color-light)}.pico-background-blue-550{background-color:var(--pico-color-blue-550);color:var(--pico-color-light)}.pico-background-blue-500{background-color:var(--pico-color-blue-500);color:var(--pico-color-light)}.pico-background-blue-450{background-color:var(--pico-color-blue-450);color:var(--pico-color-dark)}.pico-background-blue-400{background-color:var(--pico-color-blue-400);color:var(--pico-color-dark)}.pico-background-blue-350{background-color:var(--pico-color-blue-350);color:var(--pico-color-dark)}.pico-background-blue-300{background-color:var(--pico-color-blue-300);color:var(--pico-color-dark)}.pico-background-blue-250{background-color:var(--pico-color-blue-250);color:var(--pico-color-dark)}.pico-background-blue-200{background-color:var(--pico-color-blue-200);color:var(--pico-color-dark)}.pico-background-blue-150{background-color:var(--pico-color-blue-150);color:var(--pico-color-dark)}.pico-background-blue-100{background-color:var(--pico-color-blue-100);color:var(--pico-color-dark)}.pico-background-blue-50{background-color:var(--pico-color-blue-50);color:var(--pico-color-dark)}.pico-background-blue{background-color:var(--pico-color-blue);color:var(--pico-color-light)}.pico-background-azure-950{background-color:var(--pico-color-azure-950);color:var(--pico-color-light)}.pico-background-azure-900{background-color:var(--pico-color-azure-900);color:var(--pico-color-light)}.pico-background-azure-850{background-color:var(--pico-color-azure-850);color:var(--pico-color-light)}.pico-background-azure-800{background-color:var(--pico-color-azure-800);color:var(--pico-color-light)}.pico-background-azure-750{background-color:var(--pico-color-azure-750);color:var(--pico-color-light)}.pico-background-azure-700{background-color:var(--pico-color-azure-700);color:var(--pico-color-light)}.pico-background-azure-650{background-color:var(--pico-color-azure-650);color:var(--pico-color-light)}.pico-background-azure-600{background-color:var(--pico-color-azure-600);color:var(--pico-color-light)}.pico-background-azure-550{background-color:var(--pico-color-azure-550);color:var(--pico-color-light)}.pico-background-azure-500{background-color:var(--pico-color-azure-500);color:var(--pico-color-light)}.pico-background-azure-450{background-color:var(--pico-color-azure-450);color:var(--pico-color-light)}.pico-background-azure-400{background-color:var(--pico-color-azure-400);color:var(--pico-color-light)}.pico-background-azure-350{background-color:var(--pico-color-azure-350);color:var(--pico-color-dark)}.pico-background-azure-300{background-color:var(--pico-color-azure-300);color:var(--pico-color-dark)}.pico-background-azure-250{background-color:var(--pico-color-azure-250);color:var(--pico-color-dark)}.pico-background-azure-200{background-color:var(--pico-color-azure-200);color:var(--pico-color-dark)}.pico-background-azure-150{background-color:var(--pico-color-azure-150);color:var(--pico-color-dark)}.pico-background-azure-100{background-color:var(--pico-color-azure-100);color:var(--pico-color-dark)}.pico-background-azure-50{background-color:var(--pico-color-azure-50);color:var(--pico-color-dark)}.pico-background-azure{background-color:var(--pico-color-azure);color:var(--pico-color-light)}.pico-background-cyan-950{background-color:var(--pico-color-cyan-950);color:var(--pico-color-light)}.pico-background-cyan-900{background-color:var(--pico-color-cyan-900);color:var(--pico-color-light)}.pico-background-cyan-850{background-color:var(--pico-color-cyan-850);color:var(--pico-color-light)}.pico-background-cyan-800{background-color:var(--pico-color-cyan-800);color:var(--pico-color-light)}.pico-background-cyan-750{background-color:var(--pico-color-cyan-750);color:var(--pico-color-light)}.pico-background-cyan-700{background-color:var(--pico-color-cyan-700);color:var(--pico-color-light)}.pico-background-cyan-650{background-color:var(--pico-color-cyan-650);color:var(--pico-color-light)}.pico-background-cyan-600{background-color:var(--pico-color-cyan-600);color:var(--pico-color-light)}.pico-background-cyan-550{background-color:var(--pico-color-cyan-550);color:var(--pico-color-light)}.pico-background-cyan-500{background-color:var(--pico-color-cyan-500);color:var(--pico-color-light)}.pico-background-cyan-450{background-color:var(--pico-color-cyan-450);color:var(--pico-color-light)}.pico-background-cyan-400{background-color:var(--pico-color-cyan-400);color:var(--pico-color-light)}.pico-background-cyan-350{background-color:var(--pico-color-cyan-350);color:var(--pico-color-light)}.pico-background-cyan-300{background-color:var(--pico-color-cyan-300);color:var(--pico-color-dark)}.pico-background-cyan-250{background-color:var(--pico-color-cyan-250);color:var(--pico-color-dark)}.pico-background-cyan-200{background-color:var(--pico-color-cyan-200);color:var(--pico-color-dark)}.pico-background-cyan-150{background-color:var(--pico-color-cyan-150);color:var(--pico-color-dark)}.pico-background-cyan-100{background-color:var(--pico-color-cyan-100);color:var(--pico-color-dark)}.pico-background-cyan-50{background-color:var(--pico-color-cyan-50);color:var(--pico-color-dark)}.pico-background-cyan{background-color:var(--pico-color-cyan);color:var(--pico-color-light)}.pico-background-jade-950{background-color:var(--pico-color-jade-950);color:var(--pico-color-light)}.pico-background-jade-900{background-color:var(--pico-color-jade-900);color:var(--pico-color-light)}.pico-background-jade-850{background-color:var(--pico-color-jade-850);color:var(--pico-color-light)}.pico-background-jade-800{background-color:var(--pico-color-jade-800);color:var(--pico-color-light)}.pico-background-jade-750{background-color:var(--pico-color-jade-750);color:var(--pico-color-light)}.pico-background-jade-700{background-color:var(--pico-color-jade-700);color:var(--pico-color-light)}.pico-background-jade-650{background-color:var(--pico-color-jade-650);color:var(--pico-color-light)}.pico-background-jade-600{background-color:var(--pico-color-jade-600);color:var(--pico-color-light)}.pico-background-jade-550{background-color:var(--pico-color-jade-550);color:var(--pico-color-light)}.pico-background-jade-500{background-color:var(--pico-color-jade-500);color:var(--pico-color-light)}.pico-background-jade-450{background-color:var(--pico-color-jade-450);color:var(--pico-color-light)}.pico-background-jade-400{background-color:var(--pico-color-jade-400);color:var(--pico-color-light)}.pico-background-jade-350{background-color:var(--pico-color-jade-350);color:var(--pico-color-light)}.pico-background-jade-300{background-color:var(--pico-color-jade-300);color:var(--pico-color-dark)}.pico-background-jade-250{background-color:var(--pico-color-jade-250);color:var(--pico-color-dark)}.pico-background-jade-200{background-color:var(--pico-color-jade-200);color:var(--pico-color-dark)}.pico-background-jade-150{background-color:var(--pico-color-jade-150);color:var(--pico-color-dark)}.pico-background-jade-100{background-color:var(--pico-color-jade-100);color:var(--pico-color-dark)}.pico-background-jade-50{background-color:var(--pico-color-jade-50);color:var(--pico-color-dark)}.pico-background-jade{background-color:var(--pico-color-jade);color:var(--pico-color-light)}.pico-background-green-950{background-color:var(--pico-color-green-950);color:var(--pico-color-light)}.pico-background-green-900{background-color:var(--pico-color-green-900);color:var(--pico-color-light)}.pico-background-green-850{background-color:var(--pico-color-green-850);color:var(--pico-color-light)}.pico-background-green-800{background-color:var(--pico-color-green-800);color:var(--pico-color-light)}.pico-background-green-750{background-color:var(--pico-color-green-750);color:var(--pico-color-light)}.pico-background-green-700{background-color:var(--pico-color-green-700);color:var(--pico-color-light)}.pico-background-green-650{background-color:var(--pico-color-green-650);color:var(--pico-color-light)}.pico-background-green-600{background-color:var(--pico-color-green-600);color:var(--pico-color-light)}.pico-background-green-550{background-color:var(--pico-color-green-550);color:var(--pico-color-light)}.pico-background-green-500{background-color:var(--pico-color-green-500);color:var(--pico-color-light)}.pico-background-green-450{background-color:var(--pico-color-green-450);color:var(--pico-color-light)}.pico-background-green-400{background-color:var(--pico-color-green-400);color:var(--pico-color-light)}.pico-background-green-350{background-color:var(--pico-color-green-350);color:var(--pico-color-dark)}.pico-background-green-300{background-color:var(--pico-color-green-300);color:var(--pico-color-dark)}.pico-background-green-250{background-color:var(--pico-color-green-250);color:var(--pico-color-dark)}.pico-background-green-200{background-color:var(--pico-color-green-200);color:var(--pico-color-dark)}.pico-background-green-150{background-color:var(--pico-color-green-150);color:var(--pico-color-dark)}.pico-background-green-100{background-color:var(--pico-color-green-100);color:var(--pico-color-dark)}.pico-background-green-50{background-color:var(--pico-color-green-50);color:var(--pico-color-dark)}.pico-background-green{background-color:var(--pico-color-green);color:var(--pico-color-light)}.pico-background-lime-950{background-color:var(--pico-color-lime-950);color:var(--pico-color-light)}.pico-background-lime-900{background-color:var(--pico-color-lime-900);color:var(--pico-color-light)}.pico-background-lime-850{background-color:var(--pico-color-lime-850);color:var(--pico-color-light)}.pico-background-lime-800{background-color:var(--pico-color-lime-800);color:var(--pico-color-light)}.pico-background-lime-750{background-color:var(--pico-color-lime-750);color:var(--pico-color-light)}.pico-background-lime-700{background-color:var(--pico-color-lime-700);color:var(--pico-color-light)}.pico-background-lime-650{background-color:var(--pico-color-lime-650);color:var(--pico-color-light)}.pico-background-lime-600{background-color:var(--pico-color-lime-600);color:var(--pico-color-light)}.pico-background-lime-550{background-color:var(--pico-color-lime-550);color:var(--pico-color-light)}.pico-background-lime-500{background-color:var(--pico-color-lime-500);color:var(--pico-color-light)}.pico-background-lime-450{background-color:var(--pico-color-lime-450);color:var(--pico-color-light)}.pico-background-lime-400{background-color:var(--pico-color-lime-400);color:var(--pico-color-light)}.pico-background-lime-350{background-color:var(--pico-color-lime-350);color:var(--pico-color-dark)}.pico-background-lime-300{background-color:var(--pico-color-lime-300);color:var(--pico-color-dark)}.pico-background-lime-250{background-color:var(--pico-color-lime-250);color:var(--pico-color-dark)}.pico-background-lime-200{background-color:var(--pico-color-lime-200);color:var(--pico-color-dark)}.pico-background-lime-150{background-color:var(--pico-color-lime-150);color:var(--pico-color-dark)}.pico-background-lime-100{background-color:var(--pico-color-lime-100);color:var(--pico-color-dark)}.pico-background-lime-50{background-color:var(--pico-color-lime-50);color:var(--pico-color-dark)}.pico-background-lime{background-color:var(--pico-color-lime);color:var(--pico-color-dark)}.pico-background-yellow-950{background-color:var(--pico-color-yellow-950);color:var(--pico-color-light)}.pico-background-yellow-900{background-color:var(--pico-color-yellow-900);color:var(--pico-color-light)}.pico-background-yellow-850{background-color:var(--pico-color-yellow-850);color:var(--pico-color-light)}.pico-background-yellow-800{background-color:var(--pico-color-yellow-800);color:var(--pico-color-light)}.pico-background-yellow-750{background-color:var(--pico-color-yellow-750);color:var(--pico-color-light)}.pico-background-yellow-700{background-color:var(--pico-color-yellow-700);color:var(--pico-color-light)}.pico-background-yellow-650{background-color:var(--pico-color-yellow-650);color:var(--pico-color-light)}.pico-background-yellow-600{background-color:var(--pico-color-yellow-600);color:var(--pico-color-light)}.pico-background-yellow-550{background-color:var(--pico-color-yellow-550);color:var(--pico-color-light)}.pico-background-yellow-500{background-color:var(--pico-color-yellow-500);color:var(--pico-color-light)}.pico-background-yellow-450{background-color:var(--pico-color-yellow-450);color:var(--pico-color-light)}.pico-background-yellow-400{background-color:var(--pico-color-yellow-400);color:var(--pico-color-dark)}.pico-background-yellow-350{background-color:var(--pico-color-yellow-350);color:var(--pico-color-dark)}.pico-background-yellow-300{background-color:var(--pico-color-yellow-300);color:var(--pico-color-dark)}.pico-background-yellow-250{background-color:var(--pico-color-yellow-250);color:var(--pico-color-dark)}.pico-background-yellow-200{background-color:var(--pico-color-yellow-200);color:var(--pico-color-dark)}.pico-background-yellow-150{background-color:var(--pico-color-yellow-150);color:var(--pico-color-dark)}.pico-background-yellow-100{background-color:var(--pico-color-yellow-100);color:var(--pico-color-dark)}.pico-background-yellow-50{background-color:var(--pico-color-yellow-50);color:var(--pico-color-dark)}.pico-background-yellow{background-color:var(--pico-color-yellow);color:var(--pico-color-dark)}.pico-background-amber-950{background-color:var(--pico-color-amber-950);color:var(--pico-color-light)}.pico-background-amber-900{background-color:var(--pico-color-amber-900);color:var(--pico-color-light)}.pico-background-amber-850{background-color:var(--pico-color-amber-850);color:var(--pico-color-light)}.pico-background-amber-800{background-color:var(--pico-color-amber-800);color:var(--pico-color-light)}.pico-background-amber-750{background-color:var(--pico-color-amber-750);color:var(--pico-color-light)}.pico-background-amber-700{background-color:var(--pico-color-amber-700);color:var(--pico-color-light)}.pico-background-amber-650{background-color:var(--pico-color-amber-650);color:var(--pico-color-light)}.pico-background-amber-600{background-color:var(--pico-color-amber-600);color:var(--pico-color-light)}.pico-background-amber-550{background-color:var(--pico-color-amber-550);color:var(--pico-color-light)}.pico-background-amber-500{background-color:var(--pico-color-amber-500);color:var(--pico-color-light)}.pico-background-amber-450{background-color:var(--pico-color-amber-450);color:var(--pico-color-light)}.pico-background-amber-400{background-color:var(--pico-color-amber-400);color:var(--pico-color-dark)}.pico-background-amber-350{background-color:var(--pico-color-amber-350);color:var(--pico-color-dark)}.pico-background-amber-300{background-color:var(--pico-color-amber-300);color:var(--pico-color-dark)}.pico-background-amber-250{background-color:var(--pico-color-amber-250);color:var(--pico-color-dark)}.pico-background-amber-200{background-color:var(--pico-color-amber-200);color:var(--pico-color-dark)}.pico-background-amber-150{background-color:var(--pico-color-amber-150);color:var(--pico-color-dark)}.pico-background-amber-100{background-color:var(--pico-color-amber-100);color:var(--pico-color-dark)}.pico-background-amber-50{background-color:var(--pico-color-amber-50);color:var(--pico-color-dark)}.pico-background-amber{background-color:var(--pico-color-amber);color:var(--pico-color-dark)}.pico-background-pumpkin-950{background-color:var(--pico-color-pumpkin-950);color:var(--pico-color-light)}.pico-background-pumpkin-900{background-color:var(--pico-color-pumpkin-900);color:var(--pico-color-light)}.pico-background-pumpkin-850{background-color:var(--pico-color-pumpkin-850);color:var(--pico-color-light)}.pico-background-pumpkin-800{background-color:var(--pico-color-pumpkin-800);color:var(--pico-color-light)}.pico-background-pumpkin-750{background-color:var(--pico-color-pumpkin-750);color:var(--pico-color-light)}.pico-background-pumpkin-700{background-color:var(--pico-color-pumpkin-700);color:var(--pico-color-light)}.pico-background-pumpkin-650{background-color:var(--pico-color-pumpkin-650);color:var(--pico-color-light)}.pico-background-pumpkin-600{background-color:var(--pico-color-pumpkin-600);color:var(--pico-color-light)}.pico-background-pumpkin-550{background-color:var(--pico-color-pumpkin-550);color:var(--pico-color-light)}.pico-background-pumpkin-500{background-color:var(--pico-color-pumpkin-500);color:var(--pico-color-light)}.pico-background-pumpkin-450{background-color:var(--pico-color-pumpkin-450);color:var(--pico-color-light)}.pico-background-pumpkin-400{background-color:var(--pico-color-pumpkin-400);color:var(--pico-color-dark)}.pico-background-pumpkin-350{background-color:var(--pico-color-pumpkin-350);color:var(--pico-color-dark)}.pico-background-pumpkin-300{background-color:var(--pico-color-pumpkin-300);color:var(--pico-color-dark)}.pico-background-pumpkin-250{background-color:var(--pico-color-pumpkin-250);color:var(--pico-color-dark)}.pico-background-pumpkin-200{background-color:var(--pico-color-pumpkin-200);color:var(--pico-color-dark)}.pico-background-pumpkin-150{background-color:var(--pico-color-pumpkin-150);color:var(--pico-color-dark)}.pico-background-pumpkin-100{background-color:var(--pico-color-pumpkin-100);color:var(--pico-color-dark)}.pico-background-pumpkin-50{background-color:var(--pico-color-pumpkin-50);color:var(--pico-color-dark)}.pico-background-pumpkin{background-color:var(--pico-color-pumpkin);color:var(--pico-color-dark)}.pico-background-orange-950{background-color:var(--pico-color-orange-950);color:var(--pico-color-light)}.pico-background-orange-900{background-color:var(--pico-color-orange-900);color:var(--pico-color-light)}.pico-background-orange-850{background-color:var(--pico-color-orange-850);color:var(--pico-color-light)}.pico-background-orange-800{background-color:var(--pico-color-orange-800);color:var(--pico-color-light)}.pico-background-orange-750{background-color:var(--pico-color-orange-750);color:var(--pico-color-light)}.pico-background-orange-700{background-color:var(--pico-color-orange-700);color:var(--pico-color-light)}.pico-background-orange-650{background-color:var(--pico-color-orange-650);color:var(--pico-color-light)}.pico-background-orange-600{background-color:var(--pico-color-orange-600);color:var(--pico-color-light)}.pico-background-orange-550{background-color:var(--pico-color-orange-550);color:var(--pico-color-light)}.pico-background-orange-500{background-color:var(--pico-color-orange-500);color:var(--pico-color-light)}.pico-background-orange-450{background-color:var(--pico-color-orange-450);color:var(--pico-color-light)}.pico-background-orange-400{background-color:var(--pico-color-orange-400);color:var(--pico-color-dark)}.pico-background-orange-350{background-color:var(--pico-color-orange-350);color:var(--pico-color-dark)}.pico-background-orange-300{background-color:var(--pico-color-orange-300);color:var(--pico-color-dark)}.pico-background-orange-250{background-color:var(--pico-color-orange-250);color:var(--pico-color-dark)}.pico-background-orange-200{background-color:var(--pico-color-orange-200);color:var(--pico-color-dark)}.pico-background-orange-150{background-color:var(--pico-color-orange-150);color:var(--pico-color-dark)}.pico-background-orange-100{background-color:var(--pico-color-orange-100);color:var(--pico-color-dark)}.pico-background-orange-50{background-color:var(--pico-color-orange-50);color:var(--pico-color-dark)}.pico-background-orange{background-color:var(--pico-color-orange);color:var(--pico-color-light)}.pico-background-sand-950{background-color:var(--pico-color-sand-950);color:var(--pico-color-light)}.pico-background-sand-900{background-color:var(--pico-color-sand-900);color:var(--pico-color-light)}.pico-background-sand-850{background-color:var(--pico-color-sand-850);color:var(--pico-color-light)}.pico-background-sand-800{background-color:var(--pico-color-sand-800);color:var(--pico-color-light)}.pico-background-sand-750{background-color:var(--pico-color-sand-750);color:var(--pico-color-light)}.pico-background-sand-700{background-color:var(--pico-color-sand-700);color:var(--pico-color-light)}.pico-background-sand-650{background-color:var(--pico-color-sand-650);color:var(--pico-color-light)}.pico-background-sand-600{background-color:var(--pico-color-sand-600);color:var(--pico-color-light)}.pico-background-sand-550{background-color:var(--pico-color-sand-550);color:var(--pico-color-light)}.pico-background-sand-500{background-color:var(--pico-color-sand-500);color:var(--pico-color-light)}.pico-background-sand-450{background-color:var(--pico-color-sand-450);color:var(--pico-color-dark)}.pico-background-sand-400{background-color:var(--pico-color-sand-400);color:var(--pico-color-dark)}.pico-background-sand-350{background-color:var(--pico-color-sand-350);color:var(--pico-color-dark)}.pico-background-sand-300{background-color:var(--pico-color-sand-300);color:var(--pico-color-dark)}.pico-background-sand-250{background-color:var(--pico-color-sand-250);color:var(--pico-color-dark)}.pico-background-sand-200{background-color:var(--pico-color-sand-200);color:var(--pico-color-dark)}.pico-background-sand-150{background-color:var(--pico-color-sand-150);color:var(--pico-color-dark)}.pico-background-sand-100{background-color:var(--pico-color-sand-100);color:var(--pico-color-dark)}.pico-background-sand-50{background-color:var(--pico-color-sand-50);color:var(--pico-color-dark)}.pico-background-sand{background-color:var(--pico-color-sand);color:var(--pico-color-dark)}.pico-background-grey-950{background-color:var(--pico-color-grey-950);color:var(--pico-color-light)}.pico-background-grey-900{background-color:var(--pico-color-grey-900);color:var(--pico-color-light)}.pico-background-grey-850{background-color:var(--pico-color-grey-850);color:var(--pico-color-light)}.pico-background-grey-800{background-color:var(--pico-color-grey-800);color:var(--pico-color-light)}.pico-background-grey-750{background-color:var(--pico-color-grey-750);color:var(--pico-color-light)}.pico-background-grey-700{background-color:var(--pico-color-grey-700);color:var(--pico-color-light)}.pico-background-grey-650{background-color:var(--pico-color-grey-650);color:var(--pico-color-light)}.pico-background-grey-600{background-color:var(--pico-color-grey-600);color:var(--pico-color-light)}.pico-background-grey-550{background-color:var(--pico-color-grey-550);color:var(--pico-color-light)}.pico-background-grey-500{background-color:var(--pico-color-grey-500);color:var(--pico-color-light)}.pico-background-grey-450{background-color:var(--pico-color-grey-450);color:var(--pico-color-dark)}.pico-background-grey-400{background-color:var(--pico-color-grey-400);color:var(--pico-color-dark)}.pico-background-grey-350{background-color:var(--pico-color-grey-350);color:var(--pico-color-dark)}.pico-background-grey-300{background-color:var(--pico-color-grey-300);color:var(--pico-color-dark)}.pico-background-grey-250{background-color:var(--pico-color-grey-250);color:var(--pico-color-dark)}.pico-background-grey-200{background-color:var(--pico-color-grey-200);color:var(--pico-color-dark)}.pico-background-grey-150{background-color:var(--pico-color-grey-150);color:var(--pico-color-dark)}.pico-background-grey-100{background-color:var(--pico-color-grey-100);color:var(--pico-color-dark)}.pico-background-grey-50{background-color:var(--pico-color-grey-50);color:var(--pico-color-dark)}.pico-background-grey{background-color:var(--pico-color-grey);color:var(--pico-color-dark)}.pico-background-zinc-950{background-color:var(--pico-color-zinc-950);color:var(--pico-color-light)}.pico-background-zinc-900{background-color:var(--pico-color-zinc-900);color:var(--pico-color-light)}.pico-background-zinc-850{background-color:var(--pico-color-zinc-850);color:var(--pico-color-light)}.pico-background-zinc-800{background-color:var(--pico-color-zinc-800);color:var(--pico-color-light)}.pico-background-zinc-750{background-color:var(--pico-color-zinc-750);color:var(--pico-color-light)}.pico-background-zinc-700{background-color:var(--pico-color-zinc-700);color:var(--pico-color-light)}.pico-background-zinc-650{background-color:var(--pico-color-zinc-650);color:var(--pico-color-light)}.pico-background-zinc-600{background-color:var(--pico-color-zinc-600);color:var(--pico-color-light)}.pico-background-zinc-550{background-color:var(--pico-color-zinc-550);color:var(--pico-color-light)}.pico-background-zinc-500{background-color:var(--pico-color-zinc-500);color:var(--pico-color-light)}.pico-background-zinc-450{background-color:var(--pico-color-zinc-450);color:var(--pico-color-dark)}.pico-background-zinc-400{background-color:var(--pico-color-zinc-400);color:var(--pico-color-dark)}.pico-background-zinc-350{background-color:var(--pico-color-zinc-350);color:var(--pico-color-dark)}.pico-background-zinc-300{background-color:var(--pico-color-zinc-300);color:var(--pico-color-dark)}.pico-background-zinc-250{background-color:var(--pico-color-zinc-250);color:var(--pico-color-dark)}.pico-background-zinc-200{background-color:var(--pico-color-zinc-200);color:var(--pico-color-dark)}.pico-background-zinc-150{background-color:var(--pico-color-zinc-150);color:var(--pico-color-dark)}.pico-background-zinc-100{background-color:var(--pico-color-zinc-100);color:var(--pico-color-dark)}.pico-background-zinc-50{background-color:var(--pico-color-zinc-50);color:var(--pico-color-dark)}.pico-background-zinc{background-color:var(--pico-color-zinc);color:var(--pico-color-light)}.pico-background-slate-950{background-color:var(--pico-color-slate-950);color:var(--pico-color-light)}.pico-background-slate-900{background-color:var(--pico-color-slate-900);color:var(--pico-color-light)}.pico-background-slate-850{background-color:var(--pico-color-slate-850);color:var(--pico-color-light)}.pico-background-slate-800{background-color:var(--pico-color-slate-800);color:var(--pico-color-light)}.pico-background-slate-750{background-color:var(--pico-color-slate-750);color:var(--pico-color-light)}.pico-background-slate-700{background-color:var(--pico-color-slate-700);color:var(--pico-color-light)}.pico-background-slate-650{background-color:var(--pico-color-slate-650);color:var(--pico-color-light)}.pico-background-slate-600{background-color:var(--pico-color-slate-600);color:var(--pico-color-light)}.pico-background-slate-550{background-color:var(--pico-color-slate-550);color:var(--pico-color-light)}.pico-background-slate-500{background-color:var(--pico-color-slate-500);color:var(--pico-color-light)}.pico-background-slate-450{background-color:var(--pico-color-slate-450);color:var(--pico-color-dark)}.pico-background-slate-400{background-color:var(--pico-color-slate-400);color:var(--pico-color-dark)}.pico-background-slate-350{background-color:var(--pico-color-slate-350);color:var(--pico-color-dark)}.pico-background-slate-300{background-color:var(--pico-color-slate-300);color:var(--pico-color-dark)}.pico-background-slate-250{background-color:var(--pico-color-slate-250);color:var(--pico-color-dark)}.pico-background-slate-200{background-color:var(--pico-color-slate-200);color:var(--pico-color-dark)}.pico-background-slate-150{background-color:var(--pico-color-slate-150);color:var(--pico-color-dark)}.pico-background-slate-100{background-color:var(--pico-color-slate-100);color:var(--pico-color-dark)}.pico-background-slate-50{background-color:var(--pico-color-slate-50);color:var(--pico-color-dark)}.pico-background-slate{background-color:var(--pico-color-slate);color:var(--pico-color-light)}
+4
static/pico.css
+4
static/pico.css
···
···
1
+
@charset "UTF-8";/*!
2
+
* Pico CSS ✨ v2.1.1 (https://picocss.com)
3
+
* Copyright 2019-2025 - Licensed under MIT
4
+
*/:host,:root{--pico-font-family-emoji:"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";--pico-font-family-sans-serif:system-ui,"Segoe UI",Roboto,Oxygen,Ubuntu,Cantarell,Helvetica,Arial,"Helvetica Neue",sans-serif,var(--pico-font-family-emoji);--pico-font-family-monospace:ui-monospace,SFMono-Regular,"SF Mono",Menlo,Consolas,"Liberation Mono",monospace,var(--pico-font-family-emoji);--pico-font-family:var(--pico-font-family-sans-serif);--pico-line-height:1.5;--pico-font-weight:400;--pico-font-size:100%;--pico-text-underline-offset:0.1rem;--pico-border-radius:0.25rem;--pico-border-width:0.0625rem;--pico-outline-width:0.125rem;--pico-transition:0.2s ease-in-out;--pico-spacing:1rem;--pico-typography-spacing-vertical:1rem;--pico-block-spacing-vertical:var(--pico-spacing);--pico-block-spacing-horizontal:var(--pico-spacing);--pico-form-element-spacing-vertical:0.75rem;--pico-form-element-spacing-horizontal:1rem;--pico-group-box-shadow:0 0 0 rgba(0, 0, 0, 0);--pico-group-box-shadow-focus-with-button:0 0 0 var(--pico-outline-width) var(--pico-primary-focus);--pico-group-box-shadow-focus-with-input:0 0 0 0.0625rem var(--pico-form-element-border-color);--pico-modal-overlay-backdrop-filter:blur(0.375rem);--pico-nav-element-spacing-vertical:1rem;--pico-nav-element-spacing-horizontal:0.5rem;--pico-nav-link-spacing-vertical:0.5rem;--pico-nav-link-spacing-horizontal:0.5rem;--pico-nav-breadcrumb-divider:">";--pico-icon-checkbox:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='rgb(255, 255, 255)' stroke-width='4' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='20 6 9 17 4 12'%3E%3C/polyline%3E%3C/svg%3E");--pico-icon-minus:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='rgb(255, 255, 255)' stroke-width='4' stroke-linecap='round' stroke-linejoin='round'%3E%3Cline x1='5' y1='12' x2='19' y2='12'%3E%3C/line%3E%3C/svg%3E");--pico-icon-chevron:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='rgb(136, 145, 164)' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='6 9 12 15 18 9'%3E%3C/polyline%3E%3C/svg%3E");--pico-icon-date:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='rgb(136, 145, 164)' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Crect x='3' y='4' width='18' height='18' rx='2' ry='2'%3E%3C/rect%3E%3Cline x1='16' y1='2' x2='16' y2='6'%3E%3C/line%3E%3Cline x1='8' y1='2' x2='8' y2='6'%3E%3C/line%3E%3Cline x1='3' y1='10' x2='21' y2='10'%3E%3C/line%3E%3C/svg%3E");--pico-icon-time:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='rgb(136, 145, 164)' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Ccircle cx='12' cy='12' r='10'%3E%3C/circle%3E%3Cpolyline points='12 6 12 12 16 14'%3E%3C/polyline%3E%3C/svg%3E");--pico-icon-search:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='rgb(136, 145, 164)' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'%3E%3Ccircle cx='11' cy='11' r='8'%3E%3C/circle%3E%3Cline x1='21' y1='21' x2='16.65' y2='16.65'%3E%3C/line%3E%3C/svg%3E");--pico-icon-close:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='rgb(136, 145, 164)' stroke-width='3' stroke-linecap='round' stroke-linejoin='round'%3E%3Cline x1='18' y1='6' x2='6' y2='18'%3E%3C/line%3E%3Cline x1='6' y1='6' x2='18' y2='18'%3E%3C/line%3E%3C/svg%3E");--pico-icon-loading:url("data:image/svg+xml,%3Csvg fill='none' height='24' width='24' viewBox='0 0 24 24' xmlns='http://www.w3.org/2000/svg' %3E%3Cstyle%3E g %7B animation: rotate 2s linear infinite; transform-origin: center center; %7D circle %7B stroke-dasharray: 75,100; stroke-dashoffset: -5; animation: dash 1.5s ease-in-out infinite; stroke-linecap: round; %7D @keyframes rotate %7B 0%25 %7B transform: rotate(0deg); %7D 100%25 %7B transform: rotate(360deg); %7D %7D @keyframes dash %7B 0%25 %7B stroke-dasharray: 1,100; stroke-dashoffset: 0; %7D 50%25 %7B stroke-dasharray: 44.5,100; stroke-dashoffset: -17.5; %7D 100%25 %7B stroke-dasharray: 44.5,100; stroke-dashoffset: -62; %7D %7D %3C/style%3E%3Cg%3E%3Ccircle cx='12' cy='12' r='10' fill='none' stroke='rgb(136, 145, 164)' stroke-width='4' /%3E%3C/g%3E%3C/svg%3E")}@media (min-width:576px){:host,:root{--pico-font-size:106.25%}}@media (min-width:768px){:host,:root{--pico-font-size:112.5%}}@media (min-width:1024px){:host,:root{--pico-font-size:118.75%}}@media (min-width:1280px){:host,:root{--pico-font-size:125%}}@media (min-width:1536px){:host,:root{--pico-font-size:131.25%}}a{--pico-text-decoration:underline}small{--pico-font-size:0.875em}h1,h2,h3,h4,h5,h6{--pico-font-weight:700}h1{--pico-font-size:2rem;--pico-line-height:1.125;--pico-typography-spacing-top:3rem}h2{--pico-font-size:1.75rem;--pico-line-height:1.15;--pico-typography-spacing-top:2.625rem}h3{--pico-font-size:1.5rem;--pico-line-height:1.175;--pico-typography-spacing-top:2.25rem}h4{--pico-font-size:1.25rem;--pico-line-height:1.2;--pico-typography-spacing-top:1.874rem}h5{--pico-font-size:1.125rem;--pico-line-height:1.225;--pico-typography-spacing-top:1.6875rem}h6{--pico-font-size:1rem;--pico-line-height:1.25;--pico-typography-spacing-top:1.5rem}tfoot td,tfoot th,thead td,thead th{--pico-font-weight:600;--pico-border-width:0.1875rem}code,kbd,pre,samp{--pico-font-family:var(--pico-font-family-monospace)}kbd{--pico-font-weight:bolder}:where(select,textarea),input:not([type=submit],[type=button],[type=reset],[type=checkbox],[type=radio],[type=file]){--pico-outline-width:0.0625rem}[type=search]{--pico-border-radius:5rem}[type=checkbox],[type=radio]{--pico-border-width:0.125rem}[type=checkbox][role=switch]{--pico-border-width:0.1875rem}[role=search]{--pico-border-radius:5rem}[role=group] [role=button],[role=group] [type=button],[role=group] [type=submit],[role=group] button,[role=search] [role=button],[role=search] [type=button],[role=search] [type=submit],[role=search] button{--pico-form-element-spacing-horizontal:2rem}details summary[role=button]::after{filter:brightness(0) invert(1)}[aria-busy=true]:not(input,select,textarea):is(button,[type=submit],[type=button],[type=reset],[role=button])::before{filter:brightness(0) invert(1)}:host(:not([data-theme=dark])),:root:not([data-theme=dark]),[data-theme=light]{color-scheme:light;--pico-background-color:#fff;--pico-color:#373c44;--pico-text-selection-color:rgba(148, 134, 225, 0.25);--pico-muted-color:#646b79;--pico-muted-border-color:rgb(231, 234, 239.5);--pico-primary:#655cd6;--pico-primary-background:#524ed2;--pico-primary-border:var(--pico-primary-background);--pico-primary-underline:rgba(101, 92, 214, 0.5);--pico-primary-hover:#4040bf;--pico-primary-hover-background:#4040bf;--pico-primary-hover-border:var(--pico-primary-hover-background);--pico-primary-hover-underline:var(--pico-primary-hover);--pico-primary-focus:rgba(148, 134, 225, 0.5);--pico-primary-inverse:#fff;--pico-secondary:#5d6b89;--pico-secondary-background:#525f7a;--pico-secondary-border:var(--pico-secondary-background);--pico-secondary-underline:rgba(93, 107, 137, 0.5);--pico-secondary-hover:#48536b;--pico-secondary-hover-background:#48536b;--pico-secondary-hover-border:var(--pico-secondary-hover-background);--pico-secondary-hover-underline:var(--pico-secondary-hover);--pico-secondary-focus:rgba(93, 107, 137, 0.25);--pico-secondary-inverse:#fff;--pico-contrast:#181c25;--pico-contrast-background:#181c25;--pico-contrast-border:var(--pico-contrast-background);--pico-contrast-underline:rgba(24, 28, 37, 0.5);--pico-contrast-hover:#000;--pico-contrast-hover-background:#000;--pico-contrast-hover-border:var(--pico-contrast-hover-background);--pico-contrast-hover-underline:var(--pico-secondary-hover);--pico-contrast-focus:rgba(93, 107, 137, 0.25);--pico-contrast-inverse:#fff;--pico-box-shadow:0.0145rem 0.029rem 0.174rem rgba(129, 145, 181, 0.01698),0.0335rem 0.067rem 0.402rem rgba(129, 145, 181, 0.024),0.0625rem 0.125rem 0.75rem rgba(129, 145, 181, 0.03),0.1125rem 0.225rem 1.35rem rgba(129, 145, 181, 0.036),0.2085rem 0.417rem 2.502rem rgba(129, 145, 181, 0.04302),0.5rem 1rem 6rem rgba(129, 145, 181, 0.06),0 0 0 0.0625rem rgba(129, 145, 181, 0.015);--pico-h1-color:#2d3138;--pico-h2-color:#373c44;--pico-h3-color:#424751;--pico-h4-color:#4d535e;--pico-h5-color:#5c6370;--pico-h6-color:#646b79;--pico-mark-background-color:rgb(252.5, 230.5, 191.5);--pico-mark-color:#0f1114;--pico-ins-color:rgb(28.5, 105.5, 84);--pico-del-color:rgb(136, 56.5, 53);--pico-blockquote-border-color:var(--pico-muted-border-color);--pico-blockquote-footer-color:var(--pico-muted-color);--pico-button-box-shadow:0 0 0 rgba(0, 0, 0, 0);--pico-button-hover-box-shadow:0 0 0 rgba(0, 0, 0, 0);--pico-table-border-color:var(--pico-muted-border-color);--pico-table-row-stripped-background-color:rgba(111, 120, 135, 0.0375);--pico-code-background-color:rgb(243, 244.5, 246.75);--pico-code-color:#646b79;--pico-code-kbd-background-color:var(--pico-color);--pico-code-kbd-color:var(--pico-background-color);--pico-form-element-background-color:rgb(251, 251.5, 252.25);--pico-form-element-selected-background-color:#dfe3eb;--pico-form-element-border-color:#cfd5e2;--pico-form-element-color:#23262c;--pico-form-element-placeholder-color:var(--pico-muted-color);--pico-form-element-active-background-color:#fff;--pico-form-element-active-border-color:var(--pico-primary-border);--pico-form-element-focus-color:var(--pico-primary-border);--pico-form-element-disabled-opacity:0.5;--pico-form-element-invalid-border-color:rgb(183.5, 105.5, 106.5);--pico-form-element-invalid-active-border-color:rgb(200.25, 79.25, 72.25);--pico-form-element-invalid-focus-color:var(--pico-form-element-invalid-active-border-color);--pico-form-element-valid-border-color:rgb(76, 154.5, 137.5);--pico-form-element-valid-active-border-color:rgb(39, 152.75, 118.75);--pico-form-element-valid-focus-color:var(--pico-form-element-valid-active-border-color);--pico-switch-background-color:#bfc7d9;--pico-switch-checked-background-color:var(--pico-primary-background);--pico-switch-color:#fff;--pico-switch-thumb-box-shadow:0 0 0 rgba(0, 0, 0, 0);--pico-range-border-color:#dfe3eb;--pico-range-active-border-color:#bfc7d9;--pico-range-thumb-border-color:var(--pico-background-color);--pico-range-thumb-color:var(--pico-secondary-background);--pico-range-thumb-active-color:var(--pico-primary-background);--pico-accordion-border-color:var(--pico-muted-border-color);--pico-accordion-active-summary-color:var(--pico-primary-hover);--pico-accordion-close-summary-color:var(--pico-color);--pico-accordion-open-summary-color:var(--pico-muted-color);--pico-card-background-color:var(--pico-background-color);--pico-card-border-color:var(--pico-muted-border-color);--pico-card-box-shadow:var(--pico-box-shadow);--pico-card-sectioning-background-color:rgb(251, 251.5, 252.25);--pico-loading-spinner-opacity:0.5;--pico-modal-overlay-background-color:rgba(232, 234, 237, 0.75);--pico-progress-background-color:#dfe3eb;--pico-progress-color:var(--pico-primary-background);--pico-tooltip-background-color:var(--pico-contrast-background);--pico-tooltip-color:var(--pico-contrast-inverse);--pico-icon-valid:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='rgb(76, 154.5, 137.5)' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='20 6 9 17 4 12'%3E%3C/polyline%3E%3C/svg%3E");--pico-icon-invalid:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='rgb(200.25, 79.25, 72.25)' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Ccircle cx='12' cy='12' r='10'%3E%3C/circle%3E%3Cline x1='12' y1='8' x2='12' y2='12'%3E%3C/line%3E%3Cline x1='12' y1='16' x2='12.01' y2='16'%3E%3C/line%3E%3C/svg%3E")}:host(:not([data-theme=dark])) input:is([type=submit],[type=button],[type=reset],[type=checkbox],[type=radio],[type=file]),:root:not([data-theme=dark]) input:is([type=submit],[type=button],[type=reset],[type=checkbox],[type=radio],[type=file]),[data-theme=light] input:is([type=submit],[type=button],[type=reset],[type=checkbox],[type=radio],[type=file]){--pico-form-element-focus-color:var(--pico-primary-focus)}@media only screen and (prefers-color-scheme:dark){:host(:not([data-theme])),:root:not([data-theme]){color-scheme:dark;--pico-background-color:rgb(19, 22.5, 30.5);--pico-color:#c2c7d0;--pico-text-selection-color:rgba(162, 148, 229, 0.1875);--pico-muted-color:#7b8495;--pico-muted-border-color:#202632;--pico-primary:#a294e5;--pico-primary-background:#524ed2;--pico-primary-border:var(--pico-primary-background);--pico-primary-underline:rgba(162, 148, 229, 0.5);--pico-primary-hover:#bdb2ec;--pico-primary-hover-background:#655cd6;--pico-primary-hover-border:var(--pico-primary-hover-background);--pico-primary-hover-underline:var(--pico-primary-hover);--pico-primary-focus:rgba(162, 148, 229, 0.375);--pico-primary-inverse:#fff;--pico-secondary:#969eaf;--pico-secondary-background:#525f7a;--pico-secondary-border:var(--pico-secondary-background);--pico-secondary-underline:rgba(150, 158, 175, 0.5);--pico-secondary-hover:#b3b9c5;--pico-secondary-hover-background:#5d6b89;--pico-secondary-hover-border:var(--pico-secondary-hover-background);--pico-secondary-hover-underline:var(--pico-secondary-hover);--pico-secondary-focus:rgba(144, 158, 190, 0.25);--pico-secondary-inverse:#fff;--pico-contrast:#dfe3eb;--pico-contrast-background:#eff1f4;--pico-contrast-border:var(--pico-contrast-background);--pico-contrast-underline:rgba(223, 227, 235, 0.5);--pico-contrast-hover:#fff;--pico-contrast-hover-background:#fff;--pico-contrast-hover-border:var(--pico-contrast-hover-background);--pico-contrast-hover-underline:var(--pico-contrast-hover);--pico-contrast-focus:rgba(207, 213, 226, 0.25);--pico-contrast-inverse:#000;--pico-box-shadow:0.0145rem 0.029rem 0.174rem rgba(7, 8.5, 12, 0.01698),0.0335rem 0.067rem 0.402rem rgba(7, 8.5, 12, 0.024),0.0625rem 0.125rem 0.75rem rgba(7, 8.5, 12, 0.03),0.1125rem 0.225rem 1.35rem rgba(7, 8.5, 12, 0.036),0.2085rem 0.417rem 2.502rem rgba(7, 8.5, 12, 0.04302),0.5rem 1rem 6rem rgba(7, 8.5, 12, 0.06),0 0 0 0.0625rem rgba(7, 8.5, 12, 0.015);--pico-h1-color:#f0f1f3;--pico-h2-color:#e0e3e7;--pico-h3-color:#c2c7d0;--pico-h4-color:#b3b9c5;--pico-h5-color:#a4acba;--pico-h6-color:#8891a4;--pico-mark-background-color:#014063;--pico-mark-color:#fff;--pico-ins-color:#62af9a;--pico-del-color:rgb(205.5, 126, 123);--pico-blockquote-border-color:var(--pico-muted-border-color);--pico-blockquote-footer-color:var(--pico-muted-color);--pico-button-box-shadow:0 0 0 rgba(0, 0, 0, 0);--pico-button-hover-box-shadow:0 0 0 rgba(0, 0, 0, 0);--pico-table-border-color:var(--pico-muted-border-color);--pico-table-row-stripped-background-color:rgba(111, 120, 135, 0.0375);--pico-code-background-color:rgb(26, 30.5, 40.25);--pico-code-color:#8891a4;--pico-code-kbd-background-color:var(--pico-color);--pico-code-kbd-color:var(--pico-background-color);--pico-form-element-background-color:rgb(28, 33, 43.5);--pico-form-element-selected-background-color:#2a3140;--pico-form-element-border-color:#2a3140;--pico-form-element-color:#e0e3e7;--pico-form-element-placeholder-color:#8891a4;--pico-form-element-active-background-color:rgb(26, 30.5, 40.25);--pico-form-element-active-border-color:var(--pico-primary-border);--pico-form-element-focus-color:var(--pico-primary-border);--pico-form-element-disabled-opacity:0.5;--pico-form-element-invalid-border-color:rgb(149.5, 74, 80);--pico-form-element-invalid-active-border-color:rgb(183.25, 63.5, 59);--pico-form-element-invalid-focus-color:var(--pico-form-element-invalid-active-border-color);--pico-form-element-valid-border-color:#2a7b6f;--pico-form-element-valid-active-border-color:rgb(22, 137, 105.5);--pico-form-element-valid-focus-color:var(--pico-form-element-valid-active-border-color);--pico-switch-background-color:#333c4e;--pico-switch-checked-background-color:var(--pico-primary-background);--pico-switch-color:#fff;--pico-switch-thumb-box-shadow:0 0 0 rgba(0, 0, 0, 0);--pico-range-border-color:#202632;--pico-range-active-border-color:#2a3140;--pico-range-thumb-border-color:var(--pico-background-color);--pico-range-thumb-color:var(--pico-secondary-background);--pico-range-thumb-active-color:var(--pico-primary-background);--pico-accordion-border-color:var(--pico-muted-border-color);--pico-accordion-active-summary-color:var(--pico-primary-hover);--pico-accordion-close-summary-color:var(--pico-color);--pico-accordion-open-summary-color:var(--pico-muted-color);--pico-card-background-color:#181c25;--pico-card-border-color:var(--pico-card-background-color);--pico-card-box-shadow:var(--pico-box-shadow);--pico-card-sectioning-background-color:rgb(26, 30.5, 40.25);--pico-loading-spinner-opacity:0.5;--pico-modal-overlay-background-color:rgba(7.5, 8.5, 10, 0.75);--pico-progress-background-color:#202632;--pico-progress-color:var(--pico-primary-background);--pico-tooltip-background-color:var(--pico-contrast-background);--pico-tooltip-color:var(--pico-contrast-inverse);--pico-icon-valid:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='rgb(42, 123, 111)' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='20 6 9 17 4 12'%3E%3C/polyline%3E%3C/svg%3E");--pico-icon-invalid:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='rgb(149.5, 74, 80)' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Ccircle cx='12' cy='12' r='10'%3E%3C/circle%3E%3Cline x1='12' y1='8' x2='12' y2='12'%3E%3C/line%3E%3Cline x1='12' y1='16' x2='12.01' y2='16'%3E%3C/line%3E%3C/svg%3E")}:host(:not([data-theme])) input:is([type=submit],[type=button],[type=reset],[type=checkbox],[type=radio],[type=file]),:root:not([data-theme]) input:is([type=submit],[type=button],[type=reset],[type=checkbox],[type=radio],[type=file]){--pico-form-element-focus-color:var(--pico-primary-focus)}}[data-theme=dark]{color-scheme:dark;--pico-background-color:rgb(19, 22.5, 30.5);--pico-color:#c2c7d0;--pico-text-selection-color:rgba(162, 148, 229, 0.1875);--pico-muted-color:#7b8495;--pico-muted-border-color:#202632;--pico-primary:#a294e5;--pico-primary-background:#524ed2;--pico-primary-border:var(--pico-primary-background);--pico-primary-underline:rgba(162, 148, 229, 0.5);--pico-primary-hover:#bdb2ec;--pico-primary-hover-background:#655cd6;--pico-primary-hover-border:var(--pico-primary-hover-background);--pico-primary-hover-underline:var(--pico-primary-hover);--pico-primary-focus:rgba(162, 148, 229, 0.375);--pico-primary-inverse:#fff;--pico-secondary:#969eaf;--pico-secondary-background:#525f7a;--pico-secondary-border:var(--pico-secondary-background);--pico-secondary-underline:rgba(150, 158, 175, 0.5);--pico-secondary-hover:#b3b9c5;--pico-secondary-hover-background:#5d6b89;--pico-secondary-hover-border:var(--pico-secondary-hover-background);--pico-secondary-hover-underline:var(--pico-secondary-hover);--pico-secondary-focus:rgba(144, 158, 190, 0.25);--pico-secondary-inverse:#fff;--pico-contrast:#dfe3eb;--pico-contrast-background:#eff1f4;--pico-contrast-border:var(--pico-contrast-background);--pico-contrast-underline:rgba(223, 227, 235, 0.5);--pico-contrast-hover:#fff;--pico-contrast-hover-background:#fff;--pico-contrast-hover-border:var(--pico-contrast-hover-background);--pico-contrast-hover-underline:var(--pico-contrast-hover);--pico-contrast-focus:rgba(207, 213, 226, 0.25);--pico-contrast-inverse:#000;--pico-box-shadow:0.0145rem 0.029rem 0.174rem rgba(7, 8.5, 12, 0.01698),0.0335rem 0.067rem 0.402rem rgba(7, 8.5, 12, 0.024),0.0625rem 0.125rem 0.75rem rgba(7, 8.5, 12, 0.03),0.1125rem 0.225rem 1.35rem rgba(7, 8.5, 12, 0.036),0.2085rem 0.417rem 2.502rem rgba(7, 8.5, 12, 0.04302),0.5rem 1rem 6rem rgba(7, 8.5, 12, 0.06),0 0 0 0.0625rem rgba(7, 8.5, 12, 0.015);--pico-h1-color:#f0f1f3;--pico-h2-color:#e0e3e7;--pico-h3-color:#c2c7d0;--pico-h4-color:#b3b9c5;--pico-h5-color:#a4acba;--pico-h6-color:#8891a4;--pico-mark-background-color:#014063;--pico-mark-color:#fff;--pico-ins-color:#62af9a;--pico-del-color:rgb(205.5, 126, 123);--pico-blockquote-border-color:var(--pico-muted-border-color);--pico-blockquote-footer-color:var(--pico-muted-color);--pico-button-box-shadow:0 0 0 rgba(0, 0, 0, 0);--pico-button-hover-box-shadow:0 0 0 rgba(0, 0, 0, 0);--pico-table-border-color:var(--pico-muted-border-color);--pico-table-row-stripped-background-color:rgba(111, 120, 135, 0.0375);--pico-code-background-color:rgb(26, 30.5, 40.25);--pico-code-color:#8891a4;--pico-code-kbd-background-color:var(--pico-color);--pico-code-kbd-color:var(--pico-background-color);--pico-form-element-background-color:rgb(28, 33, 43.5);--pico-form-element-selected-background-color:#2a3140;--pico-form-element-border-color:#2a3140;--pico-form-element-color:#e0e3e7;--pico-form-element-placeholder-color:#8891a4;--pico-form-element-active-background-color:rgb(26, 30.5, 40.25);--pico-form-element-active-border-color:var(--pico-primary-border);--pico-form-element-focus-color:var(--pico-primary-border);--pico-form-element-disabled-opacity:0.5;--pico-form-element-invalid-border-color:rgb(149.5, 74, 80);--pico-form-element-invalid-active-border-color:rgb(183.25, 63.5, 59);--pico-form-element-invalid-focus-color:var(--pico-form-element-invalid-active-border-color);--pico-form-element-valid-border-color:#2a7b6f;--pico-form-element-valid-active-border-color:rgb(22, 137, 105.5);--pico-form-element-valid-focus-color:var(--pico-form-element-valid-active-border-color);--pico-switch-background-color:#333c4e;--pico-switch-checked-background-color:var(--pico-primary-background);--pico-switch-color:#fff;--pico-switch-thumb-box-shadow:0 0 0 rgba(0, 0, 0, 0);--pico-range-border-color:#202632;--pico-range-active-border-color:#2a3140;--pico-range-thumb-border-color:var(--pico-background-color);--pico-range-thumb-color:var(--pico-secondary-background);--pico-range-thumb-active-color:var(--pico-primary-background);--pico-accordion-border-color:var(--pico-muted-border-color);--pico-accordion-active-summary-color:var(--pico-primary-hover);--pico-accordion-close-summary-color:var(--pico-color);--pico-accordion-open-summary-color:var(--pico-muted-color);--pico-card-background-color:#181c25;--pico-card-border-color:var(--pico-card-background-color);--pico-card-box-shadow:var(--pico-box-shadow);--pico-card-sectioning-background-color:rgb(26, 30.5, 40.25);--pico-loading-spinner-opacity:0.5;--pico-modal-overlay-background-color:rgba(7.5, 8.5, 10, 0.75);--pico-progress-background-color:#202632;--pico-progress-color:var(--pico-primary-background);--pico-tooltip-background-color:var(--pico-contrast-background);--pico-tooltip-color:var(--pico-contrast-inverse);--pico-icon-valid:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='rgb(42, 123, 111)' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='20 6 9 17 4 12'%3E%3C/polyline%3E%3C/svg%3E");--pico-icon-invalid:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='rgb(149.5, 74, 80)' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Ccircle cx='12' cy='12' r='10'%3E%3C/circle%3E%3Cline x1='12' y1='8' x2='12' y2='12'%3E%3C/line%3E%3Cline x1='12' y1='16' x2='12.01' y2='16'%3E%3C/line%3E%3C/svg%3E")}[data-theme=dark] input:is([type=submit],[type=button],[type=reset],[type=checkbox],[type=radio],[type=file]){--pico-form-element-focus-color:var(--pico-primary-focus)}[type=checkbox],[type=radio],[type=range],progress{accent-color:var(--pico-primary)}*,::after,::before{box-sizing:border-box;background-repeat:no-repeat}::after,::before{text-decoration:inherit;vertical-align:inherit}:where(:host),:where(:root){-webkit-tap-highlight-color:transparent;-webkit-text-size-adjust:100%;-moz-text-size-adjust:100%;text-size-adjust:100%;background-color:var(--pico-background-color);color:var(--pico-color);font-weight:var(--pico-font-weight);font-size:var(--pico-font-size);line-height:var(--pico-line-height);font-family:var(--pico-font-family);text-underline-offset:var(--pico-text-underline-offset);text-rendering:optimizeLegibility;overflow-wrap:break-word;-moz-tab-size:4;-o-tab-size:4;tab-size:4}body{width:100%;margin:0}main{display:block}body>footer,body>header,body>main{width:100%;margin-right:auto;margin-left:auto;padding:var(--pico-block-spacing-vertical) var(--pico-block-spacing-horizontal)}@media (min-width:576px){body>footer,body>header,body>main{max-width:510px;padding-right:0;padding-left:0}}@media (min-width:768px){body>footer,body>header,body>main{max-width:700px}}@media (min-width:1024px){body>footer,body>header,body>main{max-width:950px}}@media (min-width:1280px){body>footer,body>header,body>main{max-width:1200px}}@media (min-width:1536px){body>footer,body>header,body>main{max-width:1450px}}section{margin-bottom:var(--pico-block-spacing-vertical)}b,strong{font-weight:bolder}sub,sup{position:relative;font-size:.75em;line-height:0;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}address,blockquote,dl,ol,p,pre,table,ul{margin-top:0;margin-bottom:var(--pico-typography-spacing-vertical);color:var(--pico-color);font-style:normal;font-weight:var(--pico-font-weight)}h1,h2,h3,h4,h5,h6{margin-top:0;margin-bottom:var(--pico-typography-spacing-vertical);color:var(--pico-color);font-weight:var(--pico-font-weight);font-size:var(--pico-font-size);line-height:var(--pico-line-height);font-family:var(--pico-font-family)}h1{--pico-color:var(--pico-h1-color)}h2{--pico-color:var(--pico-h2-color)}h3{--pico-color:var(--pico-h3-color)}h4{--pico-color:var(--pico-h4-color)}h5{--pico-color:var(--pico-h5-color)}h6{--pico-color:var(--pico-h6-color)}:where(article,address,blockquote,dl,figure,form,ol,p,pre,table,ul)~:is(h1,h2,h3,h4,h5,h6){margin-top:var(--pico-typography-spacing-top)}p{margin-bottom:var(--pico-typography-spacing-vertical)}hgroup{margin-bottom:var(--pico-typography-spacing-vertical)}hgroup>*{margin-top:0;margin-bottom:0}hgroup>:not(:first-child):last-child{--pico-color:var(--pico-muted-color);--pico-font-weight:unset;font-size:1rem}:where(ol,ul) li{margin-bottom:calc(var(--pico-typography-spacing-vertical) * .25)}:where(dl,ol,ul) :where(dl,ol,ul){margin:0;margin-top:calc(var(--pico-typography-spacing-vertical) * .25)}ul li{list-style:square}mark{padding:.125rem .25rem;background-color:var(--pico-mark-background-color);color:var(--pico-mark-color);vertical-align:baseline}blockquote{display:block;margin:var(--pico-typography-spacing-vertical) 0;padding:var(--pico-spacing);border-right:none;border-left:.25rem solid var(--pico-blockquote-border-color);border-inline-start:0.25rem solid var(--pico-blockquote-border-color);border-inline-end:none}blockquote footer{margin-top:calc(var(--pico-typography-spacing-vertical) * .5);color:var(--pico-blockquote-footer-color)}abbr[title]{border-bottom:1px dotted;text-decoration:none;cursor:help}ins{color:var(--pico-ins-color);text-decoration:none}del{color:var(--pico-del-color)}::-moz-selection{background-color:var(--pico-text-selection-color)}::selection{background-color:var(--pico-text-selection-color)}:where(a:not([role=button])),[role=link]{--pico-color:var(--pico-primary);--pico-background-color:transparent;--pico-underline:var(--pico-primary-underline);outline:0;background-color:var(--pico-background-color);color:var(--pico-color);-webkit-text-decoration:var(--pico-text-decoration);text-decoration:var(--pico-text-decoration);text-decoration-color:var(--pico-underline);text-underline-offset:0.125em;transition:background-color var(--pico-transition),color var(--pico-transition),box-shadow var(--pico-transition),-webkit-text-decoration var(--pico-transition);transition:background-color var(--pico-transition),color var(--pico-transition),text-decoration var(--pico-transition),box-shadow var(--pico-transition);transition:background-color var(--pico-transition),color var(--pico-transition),text-decoration var(--pico-transition),box-shadow var(--pico-transition),-webkit-text-decoration var(--pico-transition)}:where(a:not([role=button])):is([aria-current]:not([aria-current=false]),:hover,:active,:focus),[role=link]:is([aria-current]:not([aria-current=false]),:hover,:active,:focus){--pico-color:var(--pico-primary-hover);--pico-underline:var(--pico-primary-hover-underline);--pico-text-decoration:underline}:where(a:not([role=button])):focus-visible,[role=link]:focus-visible{box-shadow:0 0 0 var(--pico-outline-width) var(--pico-primary-focus)}a[role=button]{display:inline-block}button{margin:0;overflow:visible;font-family:inherit;text-transform:none}[type=button],[type=reset],[type=submit],button{-webkit-appearance:button}[role=button],[type=button],[type=file]::file-selector-button,[type=reset],[type=submit],button{--pico-background-color:var(--pico-primary-background);--pico-border-color:var(--pico-primary-border);--pico-color:var(--pico-primary-inverse);--pico-box-shadow:var(--pico-button-box-shadow, 0 0 0 rgba(0, 0, 0, 0));padding:var(--pico-form-element-spacing-vertical) var(--pico-form-element-spacing-horizontal);border:var(--pico-border-width) solid var(--pico-border-color);border-radius:var(--pico-border-radius);outline:0;background-color:var(--pico-background-color);box-shadow:var(--pico-box-shadow);color:var(--pico-color);font-weight:var(--pico-font-weight);font-size:1rem;line-height:var(--pico-line-height);text-align:center;text-decoration:none;cursor:pointer;-webkit-user-select:none;-moz-user-select:none;user-select:none;transition:background-color var(--pico-transition),border-color var(--pico-transition),color var(--pico-transition),box-shadow var(--pico-transition)}[role=button]:is(:hover,:active,:focus),[role=button]:is([aria-current]:not([aria-current=false])),[type=button]:is(:hover,:active,:focus),[type=button]:is([aria-current]:not([aria-current=false])),[type=file]::file-selector-button:is(:hover,:active,:focus),[type=file]::file-selector-button:is([aria-current]:not([aria-current=false])),[type=reset]:is(:hover,:active,:focus),[type=reset]:is([aria-current]:not([aria-current=false])),[type=submit]:is(:hover,:active,:focus),[type=submit]:is([aria-current]:not([aria-current=false])),button:is(:hover,:active,:focus),button:is([aria-current]:not([aria-current=false])){--pico-background-color:var(--pico-primary-hover-background);--pico-border-color:var(--pico-primary-hover-border);--pico-box-shadow:var(--pico-button-hover-box-shadow, 0 0 0 rgba(0, 0, 0, 0));--pico-color:var(--pico-primary-inverse)}[role=button]:focus,[role=button]:is([aria-current]:not([aria-current=false])):focus,[type=button]:focus,[type=button]:is([aria-current]:not([aria-current=false])):focus,[type=file]::file-selector-button:focus,[type=file]::file-selector-button:is([aria-current]:not([aria-current=false])):focus,[type=reset]:focus,[type=reset]:is([aria-current]:not([aria-current=false])):focus,[type=submit]:focus,[type=submit]:is([aria-current]:not([aria-current=false])):focus,button:focus,button:is([aria-current]:not([aria-current=false])):focus{--pico-box-shadow:var(--pico-button-hover-box-shadow, 0 0 0 rgba(0, 0, 0, 0)),0 0 0 var(--pico-outline-width) var(--pico-primary-focus)}[type=button],[type=reset],[type=submit]{margin-bottom:var(--pico-spacing)}[type=file]::file-selector-button,[type=reset]{--pico-background-color:var(--pico-secondary-background);--pico-border-color:var(--pico-secondary-border);--pico-color:var(--pico-secondary-inverse);cursor:pointer}[type=file]::file-selector-button:is([aria-current]:not([aria-current=false]),:hover,:active,:focus),[type=reset]:is([aria-current]:not([aria-current=false]),:hover,:active,:focus){--pico-background-color:var(--pico-secondary-hover-background);--pico-border-color:var(--pico-secondary-hover-border);--pico-color:var(--pico-secondary-inverse)}[type=file]::file-selector-button:focus,[type=reset]:focus{--pico-box-shadow:var(--pico-button-hover-box-shadow, 0 0 0 rgba(0, 0, 0, 0)),0 0 0 var(--pico-outline-width) var(--pico-secondary-focus)}:where(button,[type=submit],[type=reset],[type=button],[role=button])[disabled],:where(fieldset[disabled]) :is(button,[type=submit],[type=button],[type=reset],[role=button]){opacity:.5;pointer-events:none}:where(table){width:100%;border-collapse:collapse;border-spacing:0;text-indent:0}td,th{padding:calc(var(--pico-spacing)/ 2) var(--pico-spacing);border-bottom:var(--pico-border-width) solid var(--pico-table-border-color);background-color:var(--pico-background-color);color:var(--pico-color);font-weight:var(--pico-font-weight);text-align:left;text-align:start}tfoot td,tfoot th{border-top:var(--pico-border-width) solid var(--pico-table-border-color);border-bottom:0}table.striped tbody tr:nth-child(odd) td,table.striped tbody tr:nth-child(odd) th{background-color:var(--pico-table-row-stripped-background-color)}:where(audio,canvas,iframe,img,svg,video){vertical-align:middle}audio,video{display:inline-block}audio:not([controls]){display:none;height:0}:where(iframe){border-style:none}img{max-width:100%;height:auto;border-style:none}:where(svg:not([fill])){fill:currentColor}svg:not(:host),svg:not(:root){overflow:hidden}code,kbd,pre,samp{font-size:.875em;font-family:var(--pico-font-family)}pre code,pre samp{font-size:inherit;font-family:inherit}pre{-ms-overflow-style:scrollbar;overflow:auto}code,kbd,pre,samp{border-radius:var(--pico-border-radius);background:var(--pico-code-background-color);color:var(--pico-code-color);font-weight:var(--pico-font-weight);line-height:initial}code,kbd,samp{display:inline-block;padding:.375rem}pre{display:block;margin-bottom:var(--pico-spacing);overflow-x:auto}pre>code,pre>samp{display:block;padding:var(--pico-spacing);background:0 0;line-height:var(--pico-line-height)}kbd{background-color:var(--pico-code-kbd-background-color);color:var(--pico-code-kbd-color);vertical-align:baseline}figure{display:block;margin:0;padding:0}figure figcaption{padding:calc(var(--pico-spacing) * .5) 0;color:var(--pico-muted-color)}hr{height:0;margin:var(--pico-typography-spacing-vertical) 0;border:0;border-top:1px solid var(--pico-muted-border-color);color:inherit}[hidden],template{display:none!important}canvas{display:inline-block}input,optgroup,select,textarea{margin:0;font-size:1rem;line-height:var(--pico-line-height);font-family:inherit;letter-spacing:inherit}input{overflow:visible}select{text-transform:none}legend{max-width:100%;padding:0;color:inherit;white-space:normal}textarea{overflow:auto}[type=checkbox],[type=radio]{padding:0}::-webkit-inner-spin-button,::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}[type=search]::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}::-moz-focus-inner{padding:0;border-style:none}:-moz-focusring{outline:0}:-moz-ui-invalid{box-shadow:none}::-ms-expand{display:none}[type=file],[type=range]{padding:0;border-width:0}input:not([type=checkbox],[type=radio],[type=range]){height:calc(1rem * var(--pico-line-height) + var(--pico-form-element-spacing-vertical) * 2 + var(--pico-border-width) * 2)}fieldset{width:100%;margin:0;margin-bottom:var(--pico-spacing);padding:0;border:0}fieldset legend,label{display:block;margin-bottom:calc(var(--pico-spacing) * .375);color:var(--pico-color);font-weight:var(--pico-form-label-font-weight,var(--pico-font-weight))}fieldset legend{margin-bottom:calc(var(--pico-spacing) * .5)}button[type=submit],input:not([type=checkbox],[type=radio]),select,textarea{width:100%}input:not([type=checkbox],[type=radio],[type=range],[type=file]),select,textarea{-webkit-appearance:none;-moz-appearance:none;appearance:none;padding:var(--pico-form-element-spacing-vertical) var(--pico-form-element-spacing-horizontal)}input,select,textarea{--pico-background-color:var(--pico-form-element-background-color);--pico-border-color:var(--pico-form-element-border-color);--pico-color:var(--pico-form-element-color);--pico-box-shadow:none;border:var(--pico-border-width) solid var(--pico-border-color);border-radius:var(--pico-border-radius);outline:0;background-color:var(--pico-background-color);box-shadow:var(--pico-box-shadow);color:var(--pico-color);font-weight:var(--pico-font-weight);transition:background-color var(--pico-transition),border-color var(--pico-transition),color var(--pico-transition),box-shadow var(--pico-transition)}:where(select,textarea):not([readonly]):is(:active,:focus),input:not([type=submit],[type=button],[type=reset],[type=checkbox],[type=radio],[readonly]):is(:active,:focus){--pico-background-color:var(--pico-form-element-active-background-color)}:where(select,textarea):not([readonly]):is(:active,:focus),input:not([type=submit],[type=button],[type=reset],[role=switch],[readonly]):is(:active,:focus){--pico-border-color:var(--pico-form-element-active-border-color)}:where(select,textarea):not([readonly]):focus,input:not([type=submit],[type=button],[type=reset],[type=range],[type=file],[readonly]):focus{--pico-box-shadow:0 0 0 var(--pico-outline-width) var(--pico-form-element-focus-color)}:where(fieldset[disabled]) :is(input:not([type=submit],[type=button],[type=reset]),select,textarea),input:not([type=submit],[type=button],[type=reset])[disabled],label[aria-disabled=true],select[disabled],textarea[disabled]{opacity:var(--pico-form-element-disabled-opacity);pointer-events:none}label[aria-disabled=true] input[disabled]{opacity:1}:where(input,select,textarea):not([type=checkbox],[type=radio],[type=date],[type=datetime-local],[type=month],[type=time],[type=week],[type=range])[aria-invalid]{padding-right:calc(var(--pico-form-element-spacing-horizontal) + 1.5rem)!important;padding-left:var(--pico-form-element-spacing-horizontal);padding-inline-start:var(--pico-form-element-spacing-horizontal)!important;padding-inline-end:calc(var(--pico-form-element-spacing-horizontal) + 1.5rem)!important;background-position:center right .75rem;background-size:1rem auto;background-repeat:no-repeat}:where(input,select,textarea):not([type=checkbox],[type=radio],[type=date],[type=datetime-local],[type=month],[type=time],[type=week],[type=range])[aria-invalid=false]:not(select){background-image:var(--pico-icon-valid)}:where(input,select,textarea):not([type=checkbox],[type=radio],[type=date],[type=datetime-local],[type=month],[type=time],[type=week],[type=range])[aria-invalid=true]:not(select){background-image:var(--pico-icon-invalid)}:where(input,select,textarea)[aria-invalid=false]{--pico-border-color:var(--pico-form-element-valid-border-color)}:where(input,select,textarea)[aria-invalid=false]:is(:active,:focus){--pico-border-color:var(--pico-form-element-valid-active-border-color)!important}:where(input,select,textarea)[aria-invalid=false]:is(:active,:focus):not([type=checkbox],[type=radio]){--pico-box-shadow:0 0 0 var(--pico-outline-width) var(--pico-form-element-valid-focus-color)!important}:where(input,select,textarea)[aria-invalid=true]{--pico-border-color:var(--pico-form-element-invalid-border-color)}:where(input,select,textarea)[aria-invalid=true]:is(:active,:focus){--pico-border-color:var(--pico-form-element-invalid-active-border-color)!important}:where(input,select,textarea)[aria-invalid=true]:is(:active,:focus):not([type=checkbox],[type=radio]){--pico-box-shadow:0 0 0 var(--pico-outline-width) var(--pico-form-element-invalid-focus-color)!important}[dir=rtl] :where(input,select,textarea):not([type=checkbox],[type=radio]):is([aria-invalid],[aria-invalid=true],[aria-invalid=false]){background-position:center left .75rem}input::-webkit-input-placeholder,input::placeholder,select:invalid,textarea::-webkit-input-placeholder,textarea::placeholder{color:var(--pico-form-element-placeholder-color);opacity:1}input:not([type=checkbox],[type=radio]),select,textarea{margin-bottom:var(--pico-spacing)}select::-ms-expand{border:0;background-color:transparent}select:not([multiple],[size]){padding-right:calc(var(--pico-form-element-spacing-horizontal) + 1.5rem);padding-left:var(--pico-form-element-spacing-horizontal);padding-inline-start:var(--pico-form-element-spacing-horizontal);padding-inline-end:calc(var(--pico-form-element-spacing-horizontal) + 1.5rem);background-image:var(--pico-icon-chevron);background-position:center right .75rem;background-size:1rem auto;background-repeat:no-repeat}select[multiple] option:checked{background:var(--pico-form-element-selected-background-color);color:var(--pico-form-element-color)}[dir=rtl] select:not([multiple],[size]){background-position:center left .75rem}textarea{display:block;resize:vertical}textarea[aria-invalid]{--pico-icon-height:calc(1rem * var(--pico-line-height) + var(--pico-form-element-spacing-vertical) * 2 + var(--pico-border-width) * 2);background-position:top right .75rem!important;background-size:1rem var(--pico-icon-height)!important}:where(input,select,textarea,fieldset)+small{display:block;width:100%;margin-top:calc(var(--pico-spacing) * -.75);margin-bottom:var(--pico-spacing);color:var(--pico-muted-color)}:where(input,select,textarea,fieldset)[aria-invalid=false]+small{color:var(--pico-ins-color)}:where(input,select,textarea,fieldset)[aria-invalid=true]+small{color:var(--pico-del-color)}label>:where(input,select,textarea){margin-top:calc(var(--pico-spacing) * .25)}label:has([type=checkbox],[type=radio]){width:-moz-fit-content;width:fit-content;cursor:pointer}[type=checkbox],[type=radio]{-webkit-appearance:none;-moz-appearance:none;appearance:none;width:1.25em;height:1.25em;margin-top:-.125em;margin-inline-end:.5em;border-width:var(--pico-border-width);vertical-align:middle;cursor:pointer}[type=checkbox]::-ms-check,[type=radio]::-ms-check{display:none}[type=checkbox]:checked,[type=checkbox]:checked:active,[type=checkbox]:checked:focus,[type=radio]:checked,[type=radio]:checked:active,[type=radio]:checked:focus{--pico-background-color:var(--pico-primary-background);--pico-border-color:var(--pico-primary-border);background-image:var(--pico-icon-checkbox);background-position:center;background-size:.75em auto;background-repeat:no-repeat}[type=checkbox]~label,[type=radio]~label{display:inline-block;margin-bottom:0;cursor:pointer}[type=checkbox]~label:not(:last-of-type),[type=radio]~label:not(:last-of-type){margin-inline-end:1em}[type=checkbox]:indeterminate{--pico-background-color:var(--pico-primary-background);--pico-border-color:var(--pico-primary-border);background-image:var(--pico-icon-minus);background-position:center;background-size:.75em auto;background-repeat:no-repeat}[type=radio]{border-radius:50%}[type=radio]:checked,[type=radio]:checked:active,[type=radio]:checked:focus{--pico-background-color:var(--pico-primary-inverse);border-width:.35em;background-image:none}[type=checkbox][role=switch]{--pico-background-color:var(--pico-switch-background-color);--pico-color:var(--pico-switch-color);width:2.25em;height:1.25em;border:var(--pico-border-width) solid var(--pico-border-color);border-radius:1.25em;background-color:var(--pico-background-color);line-height:1.25em}[type=checkbox][role=switch]:not([aria-invalid]){--pico-border-color:var(--pico-switch-background-color)}[type=checkbox][role=switch]:before{display:block;aspect-ratio:1;height:100%;border-radius:50%;background-color:var(--pico-color);box-shadow:var(--pico-switch-thumb-box-shadow);content:"";transition:margin .1s ease-in-out}[type=checkbox][role=switch]:focus{--pico-background-color:var(--pico-switch-background-color);--pico-border-color:var(--pico-switch-background-color)}[type=checkbox][role=switch]:checked{--pico-background-color:var(--pico-switch-checked-background-color);--pico-border-color:var(--pico-switch-checked-background-color);background-image:none}[type=checkbox][role=switch]:checked::before{margin-inline-start:calc(2.25em - 1.25em)}[type=checkbox][role=switch][disabled]{--pico-background-color:var(--pico-border-color)}[type=checkbox][aria-invalid=false]:checked,[type=checkbox][aria-invalid=false]:checked:active,[type=checkbox][aria-invalid=false]:checked:focus,[type=checkbox][role=switch][aria-invalid=false]:checked,[type=checkbox][role=switch][aria-invalid=false]:checked:active,[type=checkbox][role=switch][aria-invalid=false]:checked:focus{--pico-background-color:var(--pico-form-element-valid-border-color)}[type=checkbox]:checked:active[aria-invalid=true],[type=checkbox]:checked:focus[aria-invalid=true],[type=checkbox]:checked[aria-invalid=true],[type=checkbox][role=switch]:checked:active[aria-invalid=true],[type=checkbox][role=switch]:checked:focus[aria-invalid=true],[type=checkbox][role=switch]:checked[aria-invalid=true]{--pico-background-color:var(--pico-form-element-invalid-border-color)}[type=checkbox][aria-invalid=false]:checked,[type=checkbox][aria-invalid=false]:checked:active,[type=checkbox][aria-invalid=false]:checked:focus,[type=checkbox][role=switch][aria-invalid=false]:checked,[type=checkbox][role=switch][aria-invalid=false]:checked:active,[type=checkbox][role=switch][aria-invalid=false]:checked:focus,[type=radio][aria-invalid=false]:checked,[type=radio][aria-invalid=false]:checked:active,[type=radio][aria-invalid=false]:checked:focus{--pico-border-color:var(--pico-form-element-valid-border-color)}[type=checkbox]:checked:active[aria-invalid=true],[type=checkbox]:checked:focus[aria-invalid=true],[type=checkbox]:checked[aria-invalid=true],[type=checkbox][role=switch]:checked:active[aria-invalid=true],[type=checkbox][role=switch]:checked:focus[aria-invalid=true],[type=checkbox][role=switch]:checked[aria-invalid=true],[type=radio]:checked:active[aria-invalid=true],[type=radio]:checked:focus[aria-invalid=true],[type=radio]:checked[aria-invalid=true]{--pico-border-color:var(--pico-form-element-invalid-border-color)}[type=color]::-webkit-color-swatch-wrapper{padding:0}[type=color]::-moz-focus-inner{padding:0}[type=color]::-webkit-color-swatch{border:0;border-radius:calc(var(--pico-border-radius) * .5)}[type=color]::-moz-color-swatch{border:0;border-radius:calc(var(--pico-border-radius) * .5)}input:not([type=checkbox],[type=radio],[type=range],[type=file]):is([type=date],[type=datetime-local],[type=month],[type=time],[type=week]){--pico-icon-position:0.75rem;--pico-icon-width:1rem;padding-right:calc(var(--pico-icon-width) + var(--pico-icon-position));background-image:var(--pico-icon-date);background-position:center right var(--pico-icon-position);background-size:var(--pico-icon-width) auto;background-repeat:no-repeat}input:not([type=checkbox],[type=radio],[type=range],[type=file])[type=time]{background-image:var(--pico-icon-time)}[type=date]::-webkit-calendar-picker-indicator,[type=datetime-local]::-webkit-calendar-picker-indicator,[type=month]::-webkit-calendar-picker-indicator,[type=time]::-webkit-calendar-picker-indicator,[type=week]::-webkit-calendar-picker-indicator{width:var(--pico-icon-width);margin-right:calc(var(--pico-icon-width) * -1);margin-left:var(--pico-icon-position);opacity:0}@-moz-document url-prefix(){[type=date],[type=datetime-local],[type=month],[type=time],[type=week]{padding-right:var(--pico-form-element-spacing-horizontal)!important;background-image:none!important}}[dir=rtl] :is([type=date],[type=datetime-local],[type=month],[type=time],[type=week]){text-align:right}[type=file]{--pico-color:var(--pico-muted-color);margin-left:calc(var(--pico-outline-width) * -1);padding:calc(var(--pico-form-element-spacing-vertical) * .5) 0;padding-left:var(--pico-outline-width);border:0;border-radius:0;background:0 0}[type=file]::file-selector-button{margin-right:calc(var(--pico-spacing)/ 2);padding:calc(var(--pico-form-element-spacing-vertical) * .5) var(--pico-form-element-spacing-horizontal)}[type=file]:is(:hover,:active,:focus)::file-selector-button{--pico-background-color:var(--pico-secondary-hover-background);--pico-border-color:var(--pico-secondary-hover-border)}[type=file]:focus::file-selector-button{--pico-box-shadow:var(--pico-button-hover-box-shadow, 0 0 0 rgba(0, 0, 0, 0)),0 0 0 var(--pico-outline-width) var(--pico-secondary-focus)}[type=range]{-webkit-appearance:none;-moz-appearance:none;appearance:none;width:100%;height:1.25rem;background:0 0}[type=range]::-webkit-slider-runnable-track{width:100%;height:.375rem;border-radius:var(--pico-border-radius);background-color:var(--pico-range-border-color);-webkit-transition:background-color var(--pico-transition),box-shadow var(--pico-transition);transition:background-color var(--pico-transition),box-shadow var(--pico-transition)}[type=range]::-moz-range-track{width:100%;height:.375rem;border-radius:var(--pico-border-radius);background-color:var(--pico-range-border-color);-moz-transition:background-color var(--pico-transition),box-shadow var(--pico-transition);transition:background-color var(--pico-transition),box-shadow var(--pico-transition)}[type=range]::-ms-track{width:100%;height:.375rem;border-radius:var(--pico-border-radius);background-color:var(--pico-range-border-color);-ms-transition:background-color var(--pico-transition),box-shadow var(--pico-transition);transition:background-color var(--pico-transition),box-shadow var(--pico-transition)}[type=range]::-webkit-slider-thumb{-webkit-appearance:none;width:1.25rem;height:1.25rem;margin-top:-.4375rem;border:2px solid var(--pico-range-thumb-border-color);border-radius:50%;background-color:var(--pico-range-thumb-color);cursor:pointer;-webkit-transition:background-color var(--pico-transition),transform var(--pico-transition);transition:background-color var(--pico-transition),transform var(--pico-transition)}[type=range]::-moz-range-thumb{-webkit-appearance:none;width:1.25rem;height:1.25rem;margin-top:-.4375rem;border:2px solid var(--pico-range-thumb-border-color);border-radius:50%;background-color:var(--pico-range-thumb-color);cursor:pointer;-moz-transition:background-color var(--pico-transition),transform var(--pico-transition);transition:background-color var(--pico-transition),transform var(--pico-transition)}[type=range]::-ms-thumb{-webkit-appearance:none;width:1.25rem;height:1.25rem;margin-top:-.4375rem;border:2px solid var(--pico-range-thumb-border-color);border-radius:50%;background-color:var(--pico-range-thumb-color);cursor:pointer;-ms-transition:background-color var(--pico-transition),transform var(--pico-transition);transition:background-color var(--pico-transition),transform var(--pico-transition)}[type=range]:active,[type=range]:focus-within{--pico-range-border-color:var(--pico-range-active-border-color);--pico-range-thumb-color:var(--pico-range-thumb-active-color)}[type=range]:active::-webkit-slider-thumb{transform:scale(1.25)}[type=range]:active::-moz-range-thumb{transform:scale(1.25)}[type=range]:active::-ms-thumb{transform:scale(1.25)}input:not([type=checkbox],[type=radio],[type=range],[type=file])[type=search]{padding-inline-start:calc(var(--pico-form-element-spacing-horizontal) + 1.75rem);background-image:var(--pico-icon-search);background-position:center left calc(var(--pico-form-element-spacing-horizontal) + .125rem);background-size:1rem auto;background-repeat:no-repeat}input:not([type=checkbox],[type=radio],[type=range],[type=file])[type=search][aria-invalid]{padding-inline-start:calc(var(--pico-form-element-spacing-horizontal) + 1.75rem)!important;background-position:center left 1.125rem,center right .75rem}input:not([type=checkbox],[type=radio],[type=range],[type=file])[type=search][aria-invalid=false]{background-image:var(--pico-icon-search),var(--pico-icon-valid)}input:not([type=checkbox],[type=radio],[type=range],[type=file])[type=search][aria-invalid=true]{background-image:var(--pico-icon-search),var(--pico-icon-invalid)}[dir=rtl] :where(input):not([type=checkbox],[type=radio],[type=range],[type=file])[type=search]{background-position:center right 1.125rem}[dir=rtl] :where(input):not([type=checkbox],[type=radio],[type=range],[type=file])[type=search][aria-invalid]{background-position:center right 1.125rem,center left .75rem}details{display:block;margin-bottom:var(--pico-spacing)}details summary{line-height:1rem;list-style-type:none;cursor:pointer;transition:color var(--pico-transition)}details summary:not([role]){color:var(--pico-accordion-close-summary-color)}details summary::-webkit-details-marker{display:none}details summary::marker{display:none}details summary::-moz-list-bullet{list-style-type:none}details summary::after{display:block;width:1rem;height:1rem;margin-inline-start:calc(var(--pico-spacing,1rem) * .5);float:right;transform:rotate(-90deg);background-image:var(--pico-icon-chevron);background-position:right center;background-size:1rem auto;background-repeat:no-repeat;content:"";transition:transform var(--pico-transition)}details summary:focus{outline:0}details summary:focus:not([role]){color:var(--pico-accordion-active-summary-color)}details summary:focus-visible:not([role]){outline:var(--pico-outline-width) solid var(--pico-primary-focus);outline-offset:calc(var(--pico-spacing,1rem) * 0.5);color:var(--pico-primary)}details summary[role=button]{width:100%;text-align:left}details summary[role=button]::after{height:calc(1rem * var(--pico-line-height,1.5))}details[open]>summary{margin-bottom:var(--pico-spacing)}details[open]>summary:not([role]):not(:focus){color:var(--pico-accordion-open-summary-color)}details[open]>summary::after{transform:rotate(0)}[dir=rtl] details summary{text-align:right}[dir=rtl] details summary::after{float:left;background-position:left center}article{margin-bottom:var(--pico-block-spacing-vertical);padding:var(--pico-block-spacing-vertical) var(--pico-block-spacing-horizontal);border-radius:var(--pico-border-radius);background:var(--pico-card-background-color);box-shadow:var(--pico-card-box-shadow)}article>footer,article>header{margin-right:calc(var(--pico-block-spacing-horizontal) * -1);margin-left:calc(var(--pico-block-spacing-horizontal) * -1);padding:calc(var(--pico-block-spacing-vertical) * .66) var(--pico-block-spacing-horizontal);background-color:var(--pico-card-sectioning-background-color)}article>header{margin-top:calc(var(--pico-block-spacing-vertical) * -1);margin-bottom:var(--pico-block-spacing-vertical);border-bottom:var(--pico-border-width) solid var(--pico-card-border-color);border-top-right-radius:var(--pico-border-radius);border-top-left-radius:var(--pico-border-radius)}article>footer{margin-top:var(--pico-block-spacing-vertical);margin-bottom:calc(var(--pico-block-spacing-vertical) * -1);border-top:var(--pico-border-width) solid var(--pico-card-border-color);border-bottom-right-radius:var(--pico-border-radius);border-bottom-left-radius:var(--pico-border-radius)}[role=group],[role=search]{display:inline-flex;position:relative;width:100%;margin-bottom:var(--pico-spacing);border-radius:var(--pico-border-radius);box-shadow:var(--pico-group-box-shadow,0 0 0 transparent);vertical-align:middle;transition:box-shadow var(--pico-transition)}[role=group] input:not([type=checkbox],[type=radio]),[role=group] select,[role=group]>*,[role=search] input:not([type=checkbox],[type=radio]),[role=search] select,[role=search]>*{position:relative;flex:1 1 auto;margin-bottom:0}[role=group] input:not([type=checkbox],[type=radio]):not(:first-child),[role=group] select:not(:first-child),[role=group]>:not(:first-child),[role=search] input:not([type=checkbox],[type=radio]):not(:first-child),[role=search] select:not(:first-child),[role=search]>:not(:first-child){margin-left:0;border-top-left-radius:0;border-bottom-left-radius:0}[role=group] input:not([type=checkbox],[type=radio]):not(:last-child),[role=group] select:not(:last-child),[role=group]>:not(:last-child),[role=search] input:not([type=checkbox],[type=radio]):not(:last-child),[role=search] select:not(:last-child),[role=search]>:not(:last-child){border-top-right-radius:0;border-bottom-right-radius:0}[role=group] input:not([type=checkbox],[type=radio]):focus,[role=group] select:focus,[role=group]>:focus,[role=search] input:not([type=checkbox],[type=radio]):focus,[role=search] select:focus,[role=search]>:focus{z-index:2}[role=group] [role=button]:not(:first-child),[role=group] [type=button]:not(:first-child),[role=group] [type=reset]:not(:first-child),[role=group] [type=submit]:not(:first-child),[role=group] button:not(:first-child),[role=group] input:not([type=checkbox],[type=radio]):not(:first-child),[role=group] select:not(:first-child),[role=search] [role=button]:not(:first-child),[role=search] [type=button]:not(:first-child),[role=search] [type=reset]:not(:first-child),[role=search] [type=submit]:not(:first-child),[role=search] button:not(:first-child),[role=search] input:not([type=checkbox],[type=radio]):not(:first-child),[role=search] select:not(:first-child){margin-left:calc(var(--pico-border-width) * -1)}[role=group] [role=button],[role=group] [type=button],[role=group] [type=reset],[role=group] [type=submit],[role=group] button,[role=search] [role=button],[role=search] [type=button],[role=search] [type=reset],[role=search] [type=submit],[role=search] button{width:auto}@supports selector(:has(*)){[role=group]:has(button:focus,[type=submit]:focus,[type=button]:focus,[role=button]:focus),[role=search]:has(button:focus,[type=submit]:focus,[type=button]:focus,[role=button]:focus){--pico-group-box-shadow:var(--pico-group-box-shadow-focus-with-button)}[role=group]:has(button:focus,[type=submit]:focus,[type=button]:focus,[role=button]:focus) input:not([type=checkbox],[type=radio]),[role=group]:has(button:focus,[type=submit]:focus,[type=button]:focus,[role=button]:focus) select,[role=search]:has(button:focus,[type=submit]:focus,[type=button]:focus,[role=button]:focus) input:not([type=checkbox],[type=radio]),[role=search]:has(button:focus,[type=submit]:focus,[type=button]:focus,[role=button]:focus) select{border-color:transparent}[role=group]:has(input:not([type=submit],[type=button]):focus,select:focus),[role=search]:has(input:not([type=submit],[type=button]):focus,select:focus){--pico-group-box-shadow:var(--pico-group-box-shadow-focus-with-input)}[role=group]:has(input:not([type=submit],[type=button]):focus,select:focus) [role=button],[role=group]:has(input:not([type=submit],[type=button]):focus,select:focus) [type=button],[role=group]:has(input:not([type=submit],[type=button]):focus,select:focus) [type=submit],[role=group]:has(input:not([type=submit],[type=button]):focus,select:focus) button,[role=search]:has(input:not([type=submit],[type=button]):focus,select:focus) [role=button],[role=search]:has(input:not([type=submit],[type=button]):focus,select:focus) [type=button],[role=search]:has(input:not([type=submit],[type=button]):focus,select:focus) [type=submit],[role=search]:has(input:not([type=submit],[type=button]):focus,select:focus) button{--pico-button-box-shadow:0 0 0 var(--pico-border-width) var(--pico-primary-border);--pico-button-hover-box-shadow:0 0 0 var(--pico-border-width) var(--pico-primary-hover-border)}[role=group] [role=button]:focus,[role=group] [type=button]:focus,[role=group] [type=reset]:focus,[role=group] [type=submit]:focus,[role=group] button:focus,[role=search] [role=button]:focus,[role=search] [type=button]:focus,[role=search] [type=reset]:focus,[role=search] [type=submit]:focus,[role=search] button:focus{box-shadow:none}}[role=search]>:first-child{border-top-left-radius:5rem;border-bottom-left-radius:5rem}[role=search]>:last-child{border-top-right-radius:5rem;border-bottom-right-radius:5rem}[aria-busy=true]:not(input,select,textarea,html,form){white-space:nowrap}[aria-busy=true]:not(input,select,textarea,html,form)::before{display:inline-block;width:1em;height:1em;background-image:var(--pico-icon-loading);background-size:1em auto;background-repeat:no-repeat;content:"";vertical-align:-.125em}[aria-busy=true]:not(input,select,textarea,html,form):not(:empty)::before{margin-inline-end:calc(var(--pico-spacing) * .5)}[aria-busy=true]:not(input,select,textarea,html,form):empty{text-align:center}[role=button][aria-busy=true],[type=button][aria-busy=true],[type=reset][aria-busy=true],[type=submit][aria-busy=true],a[aria-busy=true],button[aria-busy=true]{pointer-events:none}:host,:root{--pico-scrollbar-width:0px}dialog{display:flex;z-index:999;position:fixed;top:0;right:0;bottom:0;left:0;align-items:center;justify-content:center;width:inherit;min-width:100%;height:inherit;min-height:100%;padding:0;border:0;-webkit-backdrop-filter:var(--pico-modal-overlay-backdrop-filter);backdrop-filter:var(--pico-modal-overlay-backdrop-filter);background-color:var(--pico-modal-overlay-background-color);color:var(--pico-color)}dialog>article{width:100%;max-height:calc(100vh - var(--pico-spacing) * 2);margin:var(--pico-spacing);overflow:auto}@media (min-width:576px){dialog>article{max-width:510px}}@media (min-width:768px){dialog>article{max-width:700px}}dialog>article>header>*{margin-bottom:0}dialog>article>header :is(a,button)[rel=prev]{margin:0;margin-left:var(--pico-spacing);padding:0;float:right}dialog>article>footer{text-align:right}dialog>article>footer [role=button],dialog>article>footer button{margin-bottom:0}dialog>article>footer [role=button]:not(:first-of-type),dialog>article>footer button:not(:first-of-type){margin-left:calc(var(--pico-spacing) * .5)}dialog>article :is(a,button)[rel=prev]{display:block;width:1rem;height:1rem;margin-top:calc(var(--pico-spacing) * -1);margin-bottom:var(--pico-spacing);margin-left:auto;border:none;background-image:var(--pico-icon-close);background-position:center;background-size:auto 1rem;background-repeat:no-repeat;background-color:transparent;opacity:.5;transition:opacity var(--pico-transition)}dialog>article :is(a,button)[rel=prev]:is([aria-current]:not([aria-current=false]),:hover,:active,:focus){opacity:1}dialog:not([open]),dialog[open=false]{display:none}:where(nav li)::before{float:left;content:""}nav,nav ul{display:flex}nav{justify-content:space-between;overflow:visible}nav ol,nav ul{align-items:center;margin-bottom:0;padding:0;list-style:none}nav ol:first-of-type,nav ul:first-of-type{margin-left:calc(var(--pico-nav-element-spacing-horizontal) * -1)}nav ol:last-of-type,nav ul:last-of-type{margin-right:calc(var(--pico-nav-element-spacing-horizontal) * -1)}nav li{display:inline-block;margin:0;padding:var(--pico-nav-element-spacing-vertical) var(--pico-nav-element-spacing-horizontal)}nav li :where(a,[role=link]){display:inline-block;margin:calc(var(--pico-nav-link-spacing-vertical) * -1) calc(var(--pico-nav-link-spacing-horizontal) * -1);padding:var(--pico-nav-link-spacing-vertical) var(--pico-nav-link-spacing-horizontal);border-radius:var(--pico-border-radius)}nav li :where(a,[role=link]):not(:hover){text-decoration:none}nav li [role=button],nav li [type=button],nav li button,nav li input:not([type=checkbox],[type=radio],[type=range],[type=file]),nav li select{height:auto;margin-right:inherit;margin-bottom:0;margin-left:inherit;padding:calc(var(--pico-nav-link-spacing-vertical) - var(--pico-border-width) * 2) var(--pico-nav-link-spacing-horizontal)}nav[aria-label=breadcrumb]{align-items:center;justify-content:start}nav[aria-label=breadcrumb] ul li:not(:first-child){margin-inline-start:var(--pico-nav-link-spacing-horizontal)}nav[aria-label=breadcrumb] ul li a{margin:calc(var(--pico-nav-link-spacing-vertical) * -1) 0;margin-inline-start:calc(var(--pico-nav-link-spacing-horizontal) * -1)}nav[aria-label=breadcrumb] ul li:not(:last-child)::after{display:inline-block;position:absolute;width:calc(var(--pico-nav-link-spacing-horizontal) * 4);margin:0 calc(var(--pico-nav-link-spacing-horizontal) * -1);content:var(--pico-nav-breadcrumb-divider);color:var(--pico-muted-color);text-align:center;text-decoration:none;white-space:nowrap}nav[aria-label=breadcrumb] a[aria-current]:not([aria-current=false]){background-color:transparent;color:inherit;text-decoration:none;pointer-events:none}aside li,aside nav,aside ol,aside ul{display:block}aside li{padding:calc(var(--pico-nav-element-spacing-vertical) * .5) var(--pico-nav-element-spacing-horizontal)}aside li a{display:block}aside li [role=button]{margin:inherit}[dir=rtl] nav[aria-label=breadcrumb] ul li:not(:last-child) ::after{content:"\\"}progress{display:inline-block;vertical-align:baseline}progress{-webkit-appearance:none;-moz-appearance:none;display:inline-block;appearance:none;width:100%;height:.5rem;margin-bottom:calc(var(--pico-spacing) * .5);overflow:hidden;border:0;border-radius:var(--pico-border-radius);background-color:var(--pico-progress-background-color);color:var(--pico-progress-color)}progress::-webkit-progress-bar{border-radius:var(--pico-border-radius);background:0 0}progress[value]::-webkit-progress-value{background-color:var(--pico-progress-color);-webkit-transition:inline-size var(--pico-transition);transition:inline-size var(--pico-transition)}progress::-moz-progress-bar{background-color:var(--pico-progress-color)}@media (prefers-reduced-motion:no-preference){progress:indeterminate{background:var(--pico-progress-background-color) linear-gradient(to right,var(--pico-progress-color) 30%,var(--pico-progress-background-color) 30%) top left/150% 150% no-repeat;animation:progress-indeterminate 1s linear infinite}progress:indeterminate[value]::-webkit-progress-value{background-color:transparent}progress:indeterminate::-moz-progress-bar{background-color:transparent}}@media (prefers-reduced-motion:no-preference){[dir=rtl] progress:indeterminate{animation-direction:reverse}}@keyframes progress-indeterminate{0%{background-position:200% 0}100%{background-position:-200% 0}}[data-tooltip]{position:relative}[data-tooltip]:not(a,button,input,[role=button]){border-bottom:1px dotted;text-decoration:none;cursor:help}[data-tooltip]::after,[data-tooltip]::before,[data-tooltip][data-placement=top]::after,[data-tooltip][data-placement=top]::before{display:block;z-index:99;position:absolute;bottom:100%;left:50%;padding:.25rem .5rem;overflow:hidden;transform:translate(-50%,-.25rem);border-radius:var(--pico-border-radius);background:var(--pico-tooltip-background-color);content:attr(data-tooltip);color:var(--pico-tooltip-color);font-style:normal;font-weight:var(--pico-font-weight);font-size:.875rem;text-decoration:none;text-overflow:ellipsis;white-space:nowrap;opacity:0;pointer-events:none}[data-tooltip]::after,[data-tooltip][data-placement=top]::after{padding:0;transform:translate(-50%,0);border-top:.3rem solid;border-right:.3rem solid transparent;border-left:.3rem solid transparent;border-radius:0;background-color:transparent;content:"";color:var(--pico-tooltip-background-color)}[data-tooltip][data-placement=bottom]::after,[data-tooltip][data-placement=bottom]::before{top:100%;bottom:auto;transform:translate(-50%,.25rem)}[data-tooltip][data-placement=bottom]:after{transform:translate(-50%,-.3rem);border:.3rem solid transparent;border-bottom:.3rem solid}[data-tooltip][data-placement=left]::after,[data-tooltip][data-placement=left]::before{top:50%;right:100%;bottom:auto;left:auto;transform:translate(-.25rem,-50%)}[data-tooltip][data-placement=left]:after{transform:translate(.3rem,-50%);border:.3rem solid transparent;border-left:.3rem solid}[data-tooltip][data-placement=right]::after,[data-tooltip][data-placement=right]::before{top:50%;right:auto;bottom:auto;left:100%;transform:translate(.25rem,-50%)}[data-tooltip][data-placement=right]:after{transform:translate(-.3rem,-50%);border:.3rem solid transparent;border-right:.3rem solid}[data-tooltip]:focus::after,[data-tooltip]:focus::before,[data-tooltip]:hover::after,[data-tooltip]:hover::before{opacity:1}@media (hover:hover) and (pointer:fine){[data-tooltip]:focus::after,[data-tooltip]:focus::before,[data-tooltip]:hover::after,[data-tooltip]:hover::before{--pico-tooltip-slide-to:translate(-50%, -0.25rem);transform:translate(-50%,.75rem);animation-duration:.2s;animation-fill-mode:forwards;animation-name:tooltip-slide;opacity:0}[data-tooltip]:focus::after,[data-tooltip]:hover::after{--pico-tooltip-caret-slide-to:translate(-50%, 0rem);transform:translate(-50%,-.25rem);animation-name:tooltip-caret-slide}[data-tooltip][data-placement=bottom]:focus::after,[data-tooltip][data-placement=bottom]:focus::before,[data-tooltip][data-placement=bottom]:hover::after,[data-tooltip][data-placement=bottom]:hover::before{--pico-tooltip-slide-to:translate(-50%, 0.25rem);transform:translate(-50%,-.75rem);animation-name:tooltip-slide}[data-tooltip][data-placement=bottom]:focus::after,[data-tooltip][data-placement=bottom]:hover::after{--pico-tooltip-caret-slide-to:translate(-50%, -0.3rem);transform:translate(-50%,-.5rem);animation-name:tooltip-caret-slide}[data-tooltip][data-placement=left]:focus::after,[data-tooltip][data-placement=left]:focus::before,[data-tooltip][data-placement=left]:hover::after,[data-tooltip][data-placement=left]:hover::before{--pico-tooltip-slide-to:translate(-0.25rem, -50%);transform:translate(.75rem,-50%);animation-name:tooltip-slide}[data-tooltip][data-placement=left]:focus::after,[data-tooltip][data-placement=left]:hover::after{--pico-tooltip-caret-slide-to:translate(0.3rem, -50%);transform:translate(.05rem,-50%);animation-name:tooltip-caret-slide}[data-tooltip][data-placement=right]:focus::after,[data-tooltip][data-placement=right]:focus::before,[data-tooltip][data-placement=right]:hover::after,[data-tooltip][data-placement=right]:hover::before{--pico-tooltip-slide-to:translate(0.25rem, -50%);transform:translate(-.75rem,-50%);animation-name:tooltip-slide}[data-tooltip][data-placement=right]:focus::after,[data-tooltip][data-placement=right]:hover::after{--pico-tooltip-caret-slide-to:translate(-0.3rem, -50%);transform:translate(-.05rem,-50%);animation-name:tooltip-caret-slide}}@keyframes tooltip-slide{to{transform:var(--pico-tooltip-slide-to);opacity:1}}@keyframes tooltip-caret-slide{50%{opacity:0}to{transform:var(--pico-tooltip-caret-slide-to);opacity:1}}[aria-controls]{cursor:pointer}[aria-disabled=true],[disabled]{cursor:not-allowed}[aria-hidden=false][hidden]{display:initial}[aria-hidden=false][hidden]:not(:focus){clip:rect(0,0,0,0);position:absolute}[tabindex],a,area,button,input,label,select,summary,textarea{-ms-touch-action:manipulation}[dir=rtl]{direction:rtl}@media (prefers-reduced-motion:reduce){:not([aria-busy=true]),:not([aria-busy=true])::after,:not([aria-busy=true])::before{background-attachment:initial!important;animation-duration:1ms!important;animation-delay:-1ms!important;animation-iteration-count:1!important;scroll-behavior:auto!important;transition-delay:0s!important;transition-duration:0s!important}}
+32
templates/base.html
+32
templates/base.html
···
···
1
+
<!DOCTYPE html>
2
+
<html lang="en" data-theme="auto">
3
+
<head>
4
+
<meta charset="utf-8">
5
+
<meta name="viewport" content="width=device-width, initial-scale=1">
6
+
<title>{% block title %}{{ title | default("Blahg") }}{% endblock %}</title>
7
+
<link rel="stylesheet" href="/static/pico.css">
8
+
<link rel="stylesheet" href="/static/pico.colors.css">
9
+
{% block head %}{% endblock %}
10
+
</head>
11
+
<body>
12
+
<header>
13
+
<hgroup>
14
+
<h1>Smoke Signal Blog</h1>
15
+
</hgroup>
16
+
<nav>
17
+
<ul>
18
+
<li><a href="/">Blog</a></li>
19
+
<li><a href="https://discourse.smokesignal.events/">Discourse</a></li>
20
+
<li><a href="https://smokesignal.events/">Site</a></li>
21
+
</ul>
22
+
</nav>
23
+
</header>
24
+
{% block content %}{% endblock %}
25
+
<footer>
26
+
<p>
27
+
Powered by <a href="https://tangled.sh/@smokesignal.events/blahg">blahg</a> -
28
+
An ATProtocol powered blog
29
+
</p>
30
+
</footer>
31
+
</body>
32
+
</html>
+33
templates/index.html
+33
templates/index.html
···
···
1
+
{% extends "base.html" %}
2
+
{% block title %}Smoke Signal Blog{% endblock %}
3
+
{% block head %}
4
+
<meta name="description" content="The Smoke Signal Events blog.">
5
+
<link rel="canonical" href="{{ external_base }}/">
6
+
<meta property="og:title" content="Smoke Signal Events Blog" />
7
+
<meta property="og:type" content="website" />
8
+
<meta property="og:url" content="{{ external_base }}/" />
9
+
<meta property="og:description" content="The Smoke Signal Events blog." />
10
+
<meta property="og:site_name" content="Smoke Signal Events Blog" />
11
+
{% endblock %}
12
+
{% block content %}
13
+
<main>
14
+
<section>
15
+
<p>This is the blog for <a href="https://smokesignal.events/">smokesignal.events</a>, an event and RSVP management and discovery application built on top of ATProtocol.</p>
16
+
</section>
17
+
</main>
18
+
<main>
19
+
<section>
20
+
{% if posts %}
21
+
<ul>
22
+
{% for post in posts %}
23
+
<li>
24
+
<a href="/posts/{{ post.slug }}">{{ post.title }}</a> published {{ post.created_at }}
25
+
</li>
26
+
{% endfor %}
27
+
</ul>
28
+
{% else %}
29
+
<p><strong><em>There are no posts to display.</em></strong></p>
30
+
{% endif %}
31
+
</section>
32
+
</main>
33
+
{% endblock %}
+41
templates/post.html
+41
templates/post.html
···
···
1
+
{% extends "base.html" %}
2
+
{% block title %}{{ post.title }} - Blahg{% endblock %}
3
+
{% block head %}
4
+
<meta name="description" content="{{ post.title }} posted by @smokesignal.events on {{ post.created_at }}">
5
+
<link rel="canonical" href="{{ external_base }}/">
6
+
<meta property="og:title" content="{{ post.title }}" />
7
+
<meta property="og:type" content="website" />
8
+
<meta property="og:url" content="{{ external_base }}/posts/{{ post.slug }}" />
9
+
<meta property="og:description" content="{{ post.title }} posted by @smokesignal.events on {{ post.created_at }}" />
10
+
<meta property="og:site_name" content="Smoke Signal Events Blog" />
11
+
{% endblock %}
12
+
{% block content %}
13
+
<main>
14
+
<section>
15
+
<hgroup>
16
+
<h1>{{ post.title }}</h1>
17
+
<p>Published by <a href="#">@smokesignal.events</a> on {{ post.created_at }}.</p>
18
+
</hgroup>
19
+
{% autoescape false %}{{ post_content }}{% endautoescape %}
20
+
</section>
21
+
</main>
22
+
<main>
23
+
<section>
24
+
{% if total_activity %}
25
+
<p>This post has had {{ total_activity }} interaction{% if total_activity > 1 %}s{% endif %}.</p>
26
+
<ul>
27
+
{% for collection in collection_counts %}
28
+
{% set collection_count = (collection_counts[collection] | default(0)) %}
29
+
{% if collection_count > 0 %}
30
+
<li>
31
+
<a href="/posts/{{ post.slug }}/{{ collection }}" rel="nofollow">{{ collection }} ({{ collection_count }})</a>
32
+
</li>
33
+
{% endif %}
34
+
{% endfor %}
35
+
</ul>
36
+
{% else %}
37
+
<p>No one has interacted with this post.</p>
38
+
{% endif %}
39
+
</section>
40
+
</main>
41
+
{% endblock %}
+31
templates/post_references.html
+31
templates/post_references.html
···
···
1
+
{% extends "base.html" %}
2
+
{% block title %}{{ post.title }} - Blahg{% endblock %}
3
+
{% block head %}
4
+
<meta name="robots" content="noindex">
5
+
{% endblock %}
6
+
{% block content %}
7
+
<main>
8
+
<section>
9
+
<hgroup>
10
+
<h1>
11
+
<a href="/posts/{{ post.slug }}">Back to Post</a>
12
+
</h1>
13
+
</section>
14
+
</main>
15
+
<main>
16
+
<section>
17
+
{% if post_references %}
18
+
<ul>
19
+
{% for post_reference in post_references %}
20
+
<li>
21
+
<a href="web+{{ post_reference.aturi }}">{{ post_reference.aturi }}</a>
22
+
</li>
23
+
{% endfor %}
24
+
</ul>
25
+
{% else %}
26
+
<p><strong><em>There are no references in this collection to display.</em></strong></p>
27
+
{% endif %}
28
+
29
+
</section>
30
+
</main>
31
+
{% endblock %}
+70
tools.smokesignal.blahg.content.post.json
+70
tools.smokesignal.blahg.content.post.json
···
···
1
+
{
2
+
"lexicon": 1,
3
+
"id": "tools.smokesignal.blahg.content.post",
4
+
"defs": {
5
+
"main": {
6
+
"type": "record",
7
+
"description": "A blagh post",
8
+
"key": "any",
9
+
"record": {
10
+
"type": "object",
11
+
"properties": {
12
+
"title": {
13
+
"type": "string",
14
+
"maxGraphemes": 200,
15
+
"maxLength": 2000
16
+
},
17
+
"content": {
18
+
"type": "blob",
19
+
"description": "The content of the post",
20
+
"accept": [
21
+
"text/plain",
22
+
"text/html",
23
+
"text/markdown"
24
+
],
25
+
"maxSize": 1000000
26
+
},
27
+
"langs": {
28
+
"type": "array",
29
+
"description": "Indicates human language of text content.",
30
+
"maxLength": 3,
31
+
"items": {
32
+
"type": "string",
33
+
"format": "language"
34
+
}
35
+
},
36
+
"attachments": {
37
+
"type": "array",
38
+
"items": {
39
+
"type": "ref",
40
+
"ref": "#attachment"
41
+
}
42
+
},
43
+
"publishedAt": {
44
+
"type": "string",
45
+
"format": "datetime"
46
+
}
47
+
}
48
+
}
49
+
},
50
+
"attachment": {
51
+
"type": "object",
52
+
"required": [
53
+
"content"
54
+
],
55
+
"properties": {
56
+
"content": {
57
+
"type": "blob",
58
+
"accept": [
59
+
"image/*"
60
+
],
61
+
"maxSize": 3000000
62
+
},
63
+
"alt": {
64
+
"type": "string",
65
+
"description": "Alt text description of the content, for accessibility."
66
+
}
67
+
}
68
+
}
69
+
}
70
+
}