upleb.uk

Public git repos — served from a NIP-34 GRASP relay at git.upleb.uk

summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDanConwayDev <DanConwayDev@protonmail.com>2025-11-19 11:55:32 +0000
committerDanConwayDev <DanConwayDev@protonmail.com>2025-11-19 15:43:29 +0000
commitfa065ad128882755f2a988d6203b59a2ab5e38ff (patch)
treee8326de70a6e6ea56b5bf4250e0a00a3cda4afed
parent98c6fa4bfa897ff0b8f9c95ea698d4d065b5e9f3 (diff)
add landing page and nostr-relay-builder relay on same port
-rw-r--r--Cargo.lock754
-rw-r--r--Cargo.toml18
-rw-r--r--src/http/landing.rs37
-rw-r--r--src/http/mod.rs31
-rw-r--r--src/http/websocket.rs73
-rw-r--r--src/lib.rs2
-rw-r--r--src/main.rs24
-rw-r--r--src/nostr/builder.rs121
-rw-r--r--src/nostr/mod.rs2
-rw-r--r--src/nostr/relay.rs340
-rw-r--r--src/storage/mod.rs132
-rw-r--r--templates/landing.html151
-rw-r--r--tests/nip01_compliance.rs50
-rw-r--r--tests/nip34_announcements.rs111
14 files changed, 1227 insertions, 619 deletions
diff --git a/Cargo.lock b/Cargo.lock
index 0058da8..f316115 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -3,6 +3,209 @@
3version = 3 3version = 3
4 4
5[[package]] 5[[package]]
6name = "actix-codec"
7version = "0.5.2"
8source = "registry+https://github.com/rust-lang/crates.io-index"
9checksum = "5f7b0a21988c1bf877cf4759ef5ddaac04c1c9fe808c9142ecb78ba97d97a28a"
10dependencies = [
11 "bitflags",
12 "bytes",
13 "futures-core",
14 "futures-sink",
15 "memchr",
16 "pin-project-lite",
17 "tokio",
18 "tokio-util",
19 "tracing",
20]
21
22[[package]]
23name = "actix-http"
24version = "3.11.2"
25source = "registry+https://github.com/rust-lang/crates.io-index"
26checksum = "7926860314cbe2fb5d1f13731e387ab43bd32bca224e82e6e2db85de0a3dba49"
27dependencies = [
28 "actix-codec",
29 "actix-rt",
30 "actix-service",
31 "actix-utils",
32 "base64",
33 "bitflags",
34 "brotli",
35 "bytes",
36 "bytestring",
37 "derive_more",
38 "encoding_rs",
39 "flate2",
40 "foldhash",
41 "futures-core",
42 "h2",
43 "http 0.2.12",
44 "httparse",
45 "httpdate",
46 "itoa",
47 "language-tags",
48 "local-channel",
49 "mime",
50 "percent-encoding",
51 "pin-project-lite",
52 "rand 0.9.2",
53 "sha1",
54 "smallvec",
55 "tokio",
56 "tokio-util",
57 "tracing",
58 "zstd",
59]
60
61[[package]]
62name = "actix-macros"
63version = "0.2.4"
64source = "registry+https://github.com/rust-lang/crates.io-index"
65checksum = "e01ed3140b2f8d422c68afa1ed2e85d996ea619c988ac834d255db32138655cb"
66dependencies = [
67 "quote",
68 "syn",
69]
70
71[[package]]
72name = "actix-router"
73version = "0.5.3"
74source = "registry+https://github.com/rust-lang/crates.io-index"
75checksum = "13d324164c51f63867b57e73ba5936ea151b8a41a1d23d1031eeb9f70d0236f8"
76dependencies = [
77 "bytestring",
78 "cfg-if",
79 "http 0.2.12",
80 "regex",
81 "regex-lite",
82 "serde",
83 "tracing",
84]
85
86[[package]]
87name = "actix-rt"
88version = "2.11.0"
89source = "registry+https://github.com/rust-lang/crates.io-index"
90checksum = "92589714878ca59a7626ea19734f0e07a6a875197eec751bb5d3f99e64998c63"
91dependencies = [
92 "futures-core",
93 "tokio",
94]
95
96[[package]]
97name = "actix-server"
98version = "2.6.0"
99source = "registry+https://github.com/rust-lang/crates.io-index"
100checksum = "a65064ea4a457eaf07f2fba30b4c695bf43b721790e9530d26cb6f9019ff7502"
101dependencies = [
102 "actix-rt",
103 "actix-service",
104 "actix-utils",
105 "futures-core",
106 "futures-util",
107 "mio",
108 "socket2 0.5.10",
109 "tokio",
110 "tracing",
111]
112
113[[package]]
114name = "actix-service"
115version = "2.0.3"
116source = "registry+https://github.com/rust-lang/crates.io-index"
117checksum = "9e46f36bf0e5af44bdc4bdb36fbbd421aa98c79a9bce724e1edeb3894e10dc7f"
118dependencies = [
119 "futures-core",
120 "pin-project-lite",
121]
122
123[[package]]
124name = "actix-utils"
125version = "3.0.1"
126source = "registry+https://github.com/rust-lang/crates.io-index"
127checksum = "88a1dcdff1466e3c2488e1cb5c36a71822750ad43839937f85d2f4d9f8b705d8"
128dependencies = [
129 "local-waker",
130 "pin-project-lite",
131]
132
133[[package]]
134name = "actix-web"
135version = "4.12.0"
136source = "registry+https://github.com/rust-lang/crates.io-index"
137checksum = "2233f53f6cb18ae038ce1f0713ca0c72ca0c4b71fe9aaeb59924ce2c89c6dd85"
138dependencies = [
139 "actix-codec",
140 "actix-http",
141 "actix-macros",
142 "actix-router",
143 "actix-rt",
144 "actix-server",
145 "actix-service",
146 "actix-utils",
147 "actix-web-codegen",
148 "bytes",
149 "bytestring",
150 "cfg-if",
151 "cookie",
152 "derive_more",
153 "encoding_rs",
154 "foldhash",
155 "futures-core",
156 "futures-util",
157 "impl-more",
158 "itoa",
159 "language-tags",
160 "log",
161 "mime",
162 "once_cell",
163 "pin-project-lite",
164 "regex",
165 "regex-lite",
166 "serde",
167 "serde_json",
168 "serde_urlencoded",
169 "smallvec",
170 "socket2 0.6.1",
171 "time",
172 "tracing",
173 "url",
174]
175
176[[package]]
177name = "actix-web-codegen"
178version = "4.3.0"
179source = "registry+https://github.com/rust-lang/crates.io-index"
180checksum = "f591380e2e68490b5dfaf1dd1aa0ebe78d84ba7067078512b4ea6e4492d622b8"
181dependencies = [
182 "actix-router",
183 "proc-macro2",
184 "quote",
185 "syn",
186]
187
188[[package]]
189name = "actix-ws"
190version = "0.3.0"
191source = "registry+https://github.com/rust-lang/crates.io-index"
192checksum = "a3a1fb4f9f2794b0aadaf2ba5f14a6f034c7e86957b458c506a8cb75953f2d99"
193dependencies = [
194 "actix-codec",
195 "actix-http",
196 "actix-web",
197 "bytestring",
198 "futures-core",
199 "tokio",
200]
201
202[[package]]
203name = "adler2"
204version = "2.0.1"
205source = "registry+https://github.com/rust-lang/crates.io-index"
206checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa"
207
208[[package]]
6name = "aead" 209name = "aead"
7version = "0.5.2" 210version = "0.5.2"
8source = "registry+https://github.com/rust-lang/crates.io-index" 211source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -22,6 +225,21 @@ dependencies = [
22] 225]
23 226
24[[package]] 227[[package]]
228name = "alloc-no-stdlib"
229version = "2.0.4"
230source = "registry+https://github.com/rust-lang/crates.io-index"
231checksum = "cc7bb162ec39d46ab1ca8c77bf72e890535becd1751bb45f64c597edb4c8c6b3"
232
233[[package]]
234name = "alloc-stdlib"
235version = "0.2.2"
236source = "registry+https://github.com/rust-lang/crates.io-index"
237checksum = "94fb8275041c72129eb51b7d0322c29b8387a0386127718b096429201a5d6ece"
238dependencies = [
239 "alloc-no-stdlib",
240]
241
242[[package]]
25name = "android_system_properties" 243name = "android_system_properties"
26version = "0.1.5" 244version = "0.1.5"
27source = "registry+https://github.com/rust-lang/crates.io-index" 245source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -244,16 +462,31 @@ dependencies = [
244] 462]
245 463
246[[package]] 464[[package]]
247name = "bumpalo" 465name = "brotli"
248version = "3.19.0" 466version = "8.0.2"
249source = "registry+https://github.com/rust-lang/crates.io-index" 467source = "registry+https://github.com/rust-lang/crates.io-index"
250checksum = "46c5e41b57b8bba42a04676d81cb89e9ee8e859a1a66f80a5a72e1cb76b34d43" 468checksum = "4bd8b9603c7aa97359dbd97ecf258968c95f3adddd6db2f7e7a5bef101c84560"
469dependencies = [
470 "alloc-no-stdlib",
471 "alloc-stdlib",
472 "brotli-decompressor",
473]
251 474
252[[package]] 475[[package]]
253name = "byteorder" 476name = "brotli-decompressor"
254version = "1.5.0" 477version = "5.0.0"
478source = "registry+https://github.com/rust-lang/crates.io-index"
479checksum = "874bb8112abecc98cbd6d81ea4fa7e94fb9449648c93cc89aa40c81c24d7de03"
480dependencies = [
481 "alloc-no-stdlib",
482 "alloc-stdlib",
483]
484
485[[package]]
486name = "bumpalo"
487version = "3.19.0"
255source = "registry+https://github.com/rust-lang/crates.io-index" 488source = "registry+https://github.com/rust-lang/crates.io-index"
256checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" 489checksum = "46c5e41b57b8bba42a04676d81cb89e9ee8e859a1a66f80a5a72e1cb76b34d43"
257 490
258[[package]] 491[[package]]
259name = "bytes" 492name = "bytes"
@@ -262,6 +495,15 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
262checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a" 495checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a"
263 496
264[[package]] 497[[package]]
498name = "bytestring"
499version = "1.5.0"
500source = "registry+https://github.com/rust-lang/crates.io-index"
501checksum = "113b4343b5f6617e7ad401ced8de3cc8b012e73a594347c307b90db3e9271289"
502dependencies = [
503 "bytes",
504]
505
506[[package]]
265name = "cbc" 507name = "cbc"
266version = "0.1.2" 508version = "0.1.2"
267source = "registry+https://github.com/rust-lang/crates.io-index" 509source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -277,6 +519,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
277checksum = "37521ac7aabe3d13122dc382493e20c9416f299d2ccd5b3a5340a2570cdeb0f3" 519checksum = "37521ac7aabe3d13122dc382493e20c9416f299d2ccd5b3a5340a2570cdeb0f3"
278dependencies = [ 520dependencies = [
279 "find-msvc-tools", 521 "find-msvc-tools",
522 "jobserver",
523 "libc",
280 "shlex", 524 "shlex",
281] 525]
282 526
@@ -381,6 +625,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
381checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75" 625checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75"
382 626
383[[package]] 627[[package]]
628name = "cookie"
629version = "0.16.2"
630source = "registry+https://github.com/rust-lang/crates.io-index"
631checksum = "e859cd57d0710d9e06c381b550c06e76992472a8c6d527aecd2fc673dcc231fb"
632dependencies = [
633 "percent-encoding",
634 "time",
635 "version_check",
636]
637
638[[package]]
384name = "core-foundation-sys" 639name = "core-foundation-sys"
385version = "0.8.7" 640version = "0.8.7"
386source = "registry+https://github.com/rust-lang/crates.io-index" 641source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -396,6 +651,15 @@ dependencies = [
396] 651]
397 652
398[[package]] 653[[package]]
654name = "crc32fast"
655version = "1.5.0"
656source = "registry+https://github.com/rust-lang/crates.io-index"
657checksum = "9481c1c90cbf2ac953f07c8d4a58aa3945c425b7185c9154d67a65e4230da511"
658dependencies = [
659 "cfg-if",
660]
661
662[[package]]
399name = "crypto-common" 663name = "crypto-common"
400version = "0.1.6" 664version = "0.1.6"
401source = "registry+https://github.com/rust-lang/crates.io-index" 665source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -413,6 +677,36 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
413checksum = "2a2330da5de22e8a3cb63252ce2abb30116bf5265e89c0e01bc17015ce30a476" 677checksum = "2a2330da5de22e8a3cb63252ce2abb30116bf5265e89c0e01bc17015ce30a476"
414 678
415[[package]] 679[[package]]
680name = "deranged"
681version = "0.5.5"
682source = "registry+https://github.com/rust-lang/crates.io-index"
683checksum = "ececcb659e7ba858fb4f10388c250a7252eb0a27373f1a72b8748afdd248e587"
684dependencies = [
685 "powerfmt",
686]
687
688[[package]]
689name = "derive_more"
690version = "2.0.1"
691source = "registry+https://github.com/rust-lang/crates.io-index"
692checksum = "093242cf7570c207c83073cf82f79706fe7b8317e98620a47d5be7c3d8497678"
693dependencies = [
694 "derive_more-impl",
695]
696
697[[package]]
698name = "derive_more-impl"
699version = "2.0.1"
700source = "registry+https://github.com/rust-lang/crates.io-index"
701checksum = "bda628edc44c4bb645fbe0f758797143e4e07926f7ebf4e9bdfbd3d2ce621df3"
702dependencies = [
703 "proc-macro2",
704 "quote",
705 "syn",
706 "unicode-xid",
707]
708
709[[package]]
416name = "digest" 710name = "digest"
417version = "0.10.7" 711version = "0.10.7"
418source = "registry+https://github.com/rust-lang/crates.io-index" 712source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -447,18 +741,49 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
447checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" 741checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719"
448 742
449[[package]] 743[[package]]
744name = "encoding_rs"
745version = "0.8.35"
746source = "registry+https://github.com/rust-lang/crates.io-index"
747checksum = "75030f3c4f45dafd7586dd6780965a8c7e8e285a5ecb86713e63a79c5b2766f3"
748dependencies = [
749 "cfg-if",
750]
751
752[[package]]
753name = "equivalent"
754version = "1.0.2"
755source = "registry+https://github.com/rust-lang/crates.io-index"
756checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f"
757
758[[package]]
450name = "find-msvc-tools" 759name = "find-msvc-tools"
451version = "0.1.4" 760version = "0.1.4"
452source = "registry+https://github.com/rust-lang/crates.io-index" 761source = "registry+https://github.com/rust-lang/crates.io-index"
453checksum = "52051878f80a721bb68ebfbc930e07b65ba72f2da88968ea5c06fd6ca3d3a127" 762checksum = "52051878f80a721bb68ebfbc930e07b65ba72f2da88968ea5c06fd6ca3d3a127"
454 763
455[[package]] 764[[package]]
765name = "flate2"
766version = "1.1.5"
767source = "registry+https://github.com/rust-lang/crates.io-index"
768checksum = "bfe33edd8e85a12a67454e37f8c75e730830d83e313556ab9ebf9ee7fbeb3bfb"
769dependencies = [
770 "crc32fast",
771 "miniz_oxide",
772]
773
774[[package]]
456name = "fnv" 775name = "fnv"
457version = "1.0.7" 776version = "1.0.7"
458source = "registry+https://github.com/rust-lang/crates.io-index" 777source = "registry+https://github.com/rust-lang/crates.io-index"
459checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" 778checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
460 779
461[[package]] 780[[package]]
781name = "foldhash"
782version = "0.1.5"
783source = "registry+https://github.com/rust-lang/crates.io-index"
784checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2"
785
786[[package]]
462name = "form_urlencoded" 787name = "form_urlencoded"
463version = "1.2.2" 788version = "1.2.2"
464source = "registry+https://github.com/rust-lang/crates.io-index" 789source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -611,7 +936,7 @@ dependencies = [
611 "chrono", 936 "chrono",
612 "clap", 937 "clap",
613 "futures", 938 "futures",
614 "nostr-sdk", 939 "nostr-sdk 0.43.0",
615 "serde", 940 "serde",
616 "serde_json", 941 "serde_json",
617 "thiserror 1.0.69", 942 "thiserror 1.0.69",
@@ -623,12 +948,43 @@ dependencies = [
623] 948]
624 949
625[[package]] 950[[package]]
951name = "h2"
952version = "0.3.27"
953source = "registry+https://github.com/rust-lang/crates.io-index"
954checksum = "0beca50380b1fc32983fc1cb4587bfa4bb9e78fc259aad4a0032d2080309222d"
955dependencies = [
956 "bytes",
957 "fnv",
958 "futures-core",
959 "futures-sink",
960 "futures-util",
961 "http 0.2.12",
962 "indexmap",
963 "slab",
964 "tokio",
965 "tokio-util",
966 "tracing",
967]
968
969[[package]]
970name = "hashbrown"
971version = "0.16.0"
972source = "registry+https://github.com/rust-lang/crates.io-index"
973checksum = "5419bdc4f6a9207fbeba6d11b604d481addf78ecd10c11ad51e76c2f6482748d"
974
975[[package]]
626name = "heck" 976name = "heck"
627version = "0.5.0" 977version = "0.5.0"
628source = "registry+https://github.com/rust-lang/crates.io-index" 978source = "registry+https://github.com/rust-lang/crates.io-index"
629checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" 979checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
630 980
631[[package]] 981[[package]]
982name = "hex"
983version = "0.4.3"
984source = "registry+https://github.com/rust-lang/crates.io-index"
985checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70"
986
987[[package]]
632name = "hex-conservative" 988name = "hex-conservative"
633version = "0.1.2" 989version = "0.1.2"
634source = "registry+https://github.com/rust-lang/crates.io-index" 990source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -654,6 +1010,17 @@ dependencies = [
654 1010
655[[package]] 1011[[package]]
656name = "http" 1012name = "http"
1013version = "0.2.12"
1014source = "registry+https://github.com/rust-lang/crates.io-index"
1015checksum = "601cbb57e577e2f5ef5be8e7b83f0f63994f25aa94d673e54a92d5c516d101f1"
1016dependencies = [
1017 "bytes",
1018 "fnv",
1019 "itoa",
1020]
1021
1022[[package]]
1023name = "http"
657version = "1.3.1" 1024version = "1.3.1"
658source = "registry+https://github.com/rust-lang/crates.io-index" 1025source = "registry+https://github.com/rust-lang/crates.io-index"
659checksum = "f4a85d31aea989eead29a3aaf9e1115a180df8282431156e533de47660892565" 1026checksum = "f4a85d31aea989eead29a3aaf9e1115a180df8282431156e533de47660892565"
@@ -670,6 +1037,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
670checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87" 1037checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87"
671 1038
672[[package]] 1039[[package]]
1040name = "httpdate"
1041version = "1.0.3"
1042source = "registry+https://github.com/rust-lang/crates.io-index"
1043checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9"
1044
1045[[package]]
673name = "iana-time-zone" 1046name = "iana-time-zone"
674version = "0.1.64" 1047version = "0.1.64"
675source = "registry+https://github.com/rust-lang/crates.io-index" 1048source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -796,6 +1169,22 @@ dependencies = [
796] 1169]
797 1170
798[[package]] 1171[[package]]
1172name = "impl-more"
1173version = "0.1.9"
1174source = "registry+https://github.com/rust-lang/crates.io-index"
1175checksum = "e8a5a9a0ff0086c7a148acb942baaabeadf9504d10400b5a05645853729b9cd2"
1176
1177[[package]]
1178name = "indexmap"
1179version = "2.12.0"
1180source = "registry+https://github.com/rust-lang/crates.io-index"
1181checksum = "6717a8d2a5a929a1a2eb43a12812498ed141a0bcfb7e8f7844fbdbe4303bba9f"
1182dependencies = [
1183 "equivalent",
1184 "hashbrown",
1185]
1186
1187[[package]]
799name = "inout" 1188name = "inout"
800version = "0.1.4" 1189version = "0.1.4"
801source = "registry+https://github.com/rust-lang/crates.io-index" 1190source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -830,6 +1219,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
830checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" 1219checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c"
831 1220
832[[package]] 1221[[package]]
1222name = "jobserver"
1223version = "0.1.34"
1224source = "registry+https://github.com/rust-lang/crates.io-index"
1225checksum = "9afb3de4395d6b3e67a780b6de64b51c978ecf11cb9a462c66be7d4ca9039d33"
1226dependencies = [
1227 "getrandom 0.3.4",
1228 "libc",
1229]
1230
1231[[package]]
833name = "js-sys" 1232name = "js-sys"
834version = "0.3.82" 1233version = "0.3.82"
835source = "registry+https://github.com/rust-lang/crates.io-index" 1234source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -840,6 +1239,12 @@ dependencies = [
840] 1239]
841 1240
842[[package]] 1241[[package]]
1242name = "language-tags"
1243version = "0.3.2"
1244source = "registry+https://github.com/rust-lang/crates.io-index"
1245checksum = "d4345964bb142484797b161f473a503a434de77149dd8c7427788c6e13379388"
1246
1247[[package]]
843name = "lazy_static" 1248name = "lazy_static"
844version = "1.5.0" 1249version = "1.5.0"
845source = "registry+https://github.com/rust-lang/crates.io-index" 1250source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -858,6 +1263,23 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
858checksum = "6373607a59f0be73a39b6fe456b8192fcc3585f602af20751600e974dd455e77" 1263checksum = "6373607a59f0be73a39b6fe456b8192fcc3585f602af20751600e974dd455e77"
859 1264
860[[package]] 1265[[package]]
1266name = "local-channel"
1267version = "0.1.5"
1268source = "registry+https://github.com/rust-lang/crates.io-index"
1269checksum = "b6cbc85e69b8df4b8bb8b89ec634e7189099cea8927a276b7384ce5488e53ec8"
1270dependencies = [
1271 "futures-core",
1272 "futures-sink",
1273 "local-waker",
1274]
1275
1276[[package]]
1277name = "local-waker"
1278version = "0.1.4"
1279source = "registry+https://github.com/rust-lang/crates.io-index"
1280checksum = "4d873d7c67ce09b42110d801813efbc9364414e356be9935700d368351657487"
1281
1282[[package]]
861name = "lock_api" 1283name = "lock_api"
862version = "0.4.14" 1284version = "0.4.14"
863source = "registry+https://github.com/rust-lang/crates.io-index" 1285source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -894,12 +1316,29 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
894checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273" 1316checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273"
895 1317
896[[package]] 1318[[package]]
1319name = "mime"
1320version = "0.3.17"
1321source = "registry+https://github.com/rust-lang/crates.io-index"
1322checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a"
1323
1324[[package]]
1325name = "miniz_oxide"
1326version = "0.8.9"
1327source = "registry+https://github.com/rust-lang/crates.io-index"
1328checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316"
1329dependencies = [
1330 "adler2",
1331 "simd-adler32",
1332]
1333
1334[[package]]
897name = "mio" 1335name = "mio"
898version = "1.1.0" 1336version = "1.1.0"
899source = "registry+https://github.com/rust-lang/crates.io-index" 1337source = "registry+https://github.com/rust-lang/crates.io-index"
900checksum = "69d83b0086dc8ecf3ce9ae2874b2d1290252e2a30720bea58a5c6639b0092873" 1338checksum = "69d83b0086dc8ecf3ce9ae2874b2d1290252e2a30720bea58a5c6639b0092873"
901dependencies = [ 1339dependencies = [
902 "libc", 1340 "libc",
1341 "log",
903 "wasi", 1342 "wasi",
904 "windows-sys 0.61.2", 1343 "windows-sys 0.61.2",
905] 1344]
@@ -914,20 +1353,22 @@ checksum = "f0efe882e02d206d8d279c20eb40e03baf7cb5136a1476dc084a324fbc3ec42d"
914name = "ngit-grasp" 1353name = "ngit-grasp"
915version = "0.1.0" 1354version = "0.1.0"
916dependencies = [ 1355dependencies = [
1356 "actix-web",
1357 "actix-ws",
917 "anyhow", 1358 "anyhow",
918 "dotenvy", 1359 "dotenvy",
919 "futures-util", 1360 "futures-util",
920 "grasp-audit", 1361 "grasp-audit",
921 "nostr-sdk", 1362 "nostr-relay-builder",
1363 "nostr-sdk 0.44.1",
922 "serde", 1364 "serde",
923 "serde_json", 1365 "serde_json",
924 "thiserror 1.0.69", 1366 "thiserror 1.0.69",
925 "tokio", 1367 "tokio",
926 "tokio-test", 1368 "tokio-test",
927 "tokio-tungstenite 0.21.0", 1369 "tokio-tungstenite 0.28.0",
928 "tracing", 1370 "tracing",
929 "tracing-subscriber", 1371 "tracing-subscriber",
930 "tungstenite 0.21.0",
931 "url", 1372 "url",
932] 1373]
933 1374
@@ -955,14 +1396,75 @@ dependencies = [
955] 1396]
956 1397
957[[package]] 1398[[package]]
1399name = "nostr"
1400version = "0.44.1"
1401source = "registry+https://github.com/rust-lang/crates.io-index"
1402checksum = "d3595fecf0e0aaacb69a0dc0101a4453f3c76eda333d6bbc49f68f64390b3d85"
1403dependencies = [
1404 "base64",
1405 "bech32",
1406 "bip39",
1407 "bitcoin_hashes 0.14.0",
1408 "cbc",
1409 "chacha20",
1410 "chacha20poly1305",
1411 "getrandom 0.2.16",
1412 "hex",
1413 "instant",
1414 "scrypt",
1415 "secp256k1",
1416 "serde",
1417 "serde_json",
1418 "unicode-normalization",
1419 "url",
1420]
1421
1422[[package]]
958name = "nostr-database" 1423name = "nostr-database"
959version = "0.43.0" 1424version = "0.43.0"
960source = "registry+https://github.com/rust-lang/crates.io-index" 1425source = "registry+https://github.com/rust-lang/crates.io-index"
961checksum = "b1c75a8c2175d2785ba73cfddef21d1e30da5fbbdf158569b6808ba44973a15b" 1426checksum = "b1c75a8c2175d2785ba73cfddef21d1e30da5fbbdf158569b6808ba44973a15b"
962dependencies = [ 1427dependencies = [
963 "lru", 1428 "lru",
964 "nostr", 1429 "nostr 0.43.1",
1430 "tokio",
1431]
1432
1433[[package]]
1434name = "nostr-database"
1435version = "0.44.0"
1436source = "registry+https://github.com/rust-lang/crates.io-index"
1437checksum = "7462c9d8ae5ef6a28d66a192d399ad2530f1f2130b13186296dbb11bdef5b3d1"
1438dependencies = [
1439 "lru",
1440 "nostr 0.44.1",
1441 "tokio",
1442]
1443
1444[[package]]
1445name = "nostr-gossip"
1446version = "0.44.0"
1447source = "registry+https://github.com/rust-lang/crates.io-index"
1448checksum = "ade30de16869618919c6b5efc8258f47b654a98b51541eb77f85e8ec5e3c83a6"
1449dependencies = [
1450 "nostr 0.44.1",
1451]
1452
1453[[package]]
1454name = "nostr-relay-builder"
1455version = "0.44.0"
1456source = "registry+https://github.com/rust-lang/crates.io-index"
1457checksum = "51ba8e48eaadd5644e7317ca2f892b89a394a8cc57e99fea9f624dc081df1a24"
1458dependencies = [
1459 "async-utility",
1460 "async-wsocket",
1461 "atomic-destructor",
1462 "hex",
1463 "negentropy",
1464 "nostr 0.44.1",
1465 "nostr-database 0.44.0",
965 "tokio", 1466 "tokio",
1467 "tracing",
966] 1468]
967 1469
968[[package]] 1470[[package]]
@@ -976,8 +1478,26 @@ dependencies = [
976 "atomic-destructor", 1478 "atomic-destructor",
977 "lru", 1479 "lru",
978 "negentropy", 1480 "negentropy",
979 "nostr", 1481 "nostr 0.43.1",
980 "nostr-database", 1482 "nostr-database 0.43.0",
1483 "tokio",
1484 "tracing",
1485]
1486
1487[[package]]
1488name = "nostr-relay-pool"
1489version = "0.44.0"
1490source = "registry+https://github.com/rust-lang/crates.io-index"
1491checksum = "4b1073ccfbaea5549fb914a9d52c68dab2aecda61535e5143dd73e95445a804b"
1492dependencies = [
1493 "async-utility",
1494 "async-wsocket",
1495 "atomic-destructor",
1496 "hex",
1497 "lru",
1498 "negentropy",
1499 "nostr 0.44.1",
1500 "nostr-database 0.44.0",
981 "tokio", 1501 "tokio",
982 "tracing", 1502 "tracing",
983] 1503]
@@ -989,10 +1509,25 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
989checksum = "599f8963d6a1522a13b1a2b0ea6e168acfc367706606f1d33fa595e91fa22db0" 1509checksum = "599f8963d6a1522a13b1a2b0ea6e168acfc367706606f1d33fa595e91fa22db0"
990dependencies = [ 1510dependencies = [
991 "async-utility", 1511 "async-utility",
992 "nostr", 1512 "nostr 0.43.1",
993 "nostr-database", 1513 "nostr-database 0.43.0",
994 "nostr-relay-pool", 1514 "nostr-relay-pool 0.43.1",
1515 "tokio",
1516]
1517
1518[[package]]
1519name = "nostr-sdk"
1520version = "0.44.1"
1521source = "registry+https://github.com/rust-lang/crates.io-index"
1522checksum = "471732576710e779b64f04c55e3f8b5292f865fea228436daf19694f0bf70393"
1523dependencies = [
1524 "async-utility",
1525 "nostr 0.44.1",
1526 "nostr-database 0.44.0",
1527 "nostr-gossip",
1528 "nostr-relay-pool 0.44.0",
995 "tokio", 1529 "tokio",
1530 "tracing",
996] 1531]
997 1532
998[[package]] 1533[[package]]
@@ -1005,6 +1540,12 @@ dependencies = [
1005] 1540]
1006 1541
1007[[package]] 1542[[package]]
1543name = "num-conv"
1544version = "0.1.0"
1545source = "registry+https://github.com/rust-lang/crates.io-index"
1546checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9"
1547
1548[[package]]
1008name = "num-traits" 1549name = "num-traits"
1009version = "0.2.19" 1550version = "0.2.19"
1010source = "registry+https://github.com/rust-lang/crates.io-index" 1551source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1094,6 +1635,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
1094checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" 1635checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184"
1095 1636
1096[[package]] 1637[[package]]
1638name = "pkg-config"
1639version = "0.3.32"
1640source = "registry+https://github.com/rust-lang/crates.io-index"
1641checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c"
1642
1643[[package]]
1097name = "poly1305" 1644name = "poly1305"
1098version = "0.8.0" 1645version = "0.8.0"
1099source = "registry+https://github.com/rust-lang/crates.io-index" 1646source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1114,6 +1661,12 @@ dependencies = [
1114] 1661]
1115 1662
1116[[package]] 1663[[package]]
1664name = "powerfmt"
1665version = "0.2.0"
1666source = "registry+https://github.com/rust-lang/crates.io-index"
1667checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391"
1668
1669[[package]]
1117name = "ppv-lite86" 1670name = "ppv-lite86"
1118version = "0.2.21" 1671version = "0.2.21"
1119source = "registry+https://github.com/rust-lang/crates.io-index" 1672source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1215,6 +1768,18 @@ dependencies = [
1215] 1768]
1216 1769
1217[[package]] 1770[[package]]
1771name = "regex"
1772version = "1.12.2"
1773source = "registry+https://github.com/rust-lang/crates.io-index"
1774checksum = "843bc0191f75f3e22651ae5f1e72939ab2f72a4bc30fa80a066bd66edefc24d4"
1775dependencies = [
1776 "aho-corasick",
1777 "memchr",
1778 "regex-automata",
1779 "regex-syntax",
1780]
1781
1782[[package]]
1218name = "regex-automata" 1783name = "regex-automata"
1219version = "0.4.13" 1784version = "0.4.13"
1220source = "registry+https://github.com/rust-lang/crates.io-index" 1785source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1226,6 +1791,12 @@ dependencies = [
1226] 1791]
1227 1792
1228[[package]] 1793[[package]]
1794name = "regex-lite"
1795version = "0.1.8"
1796source = "registry+https://github.com/rust-lang/crates.io-index"
1797checksum = "8d942b98df5e658f56f20d592c7f868833fe38115e65c33003d8cd224b0155da"
1798
1799[[package]]
1229name = "regex-syntax" 1800name = "regex-syntax"
1230version = "0.8.8" 1801version = "0.8.8"
1231source = "registry+https://github.com/rust-lang/crates.io-index" 1802source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1382,6 +1953,18 @@ dependencies = [
1382] 1953]
1383 1954
1384[[package]] 1955[[package]]
1956name = "serde_urlencoded"
1957version = "0.7.1"
1958source = "registry+https://github.com/rust-lang/crates.io-index"
1959checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd"
1960dependencies = [
1961 "form_urlencoded",
1962 "itoa",
1963 "ryu",
1964 "serde",
1965]
1966
1967[[package]]
1385name = "sha1" 1968name = "sha1"
1386version = "0.10.6" 1969version = "0.10.6"
1387source = "registry+https://github.com/rust-lang/crates.io-index" 1970source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1428,6 +2011,12 @@ dependencies = [
1428] 2011]
1429 2012
1430[[package]] 2013[[package]]
2014name = "simd-adler32"
2015version = "0.3.7"
2016source = "registry+https://github.com/rust-lang/crates.io-index"
2017checksum = "d66dc143e6b11c1eddc06d5c423cfc97062865baf299914ab64caa38182078fe"
2018
2019[[package]]
1431name = "slab" 2020name = "slab"
1432version = "0.4.11" 2021version = "0.4.11"
1433source = "registry+https://github.com/rust-lang/crates.io-index" 2022source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1441,6 +2030,16 @@ checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03"
1441 2030
1442[[package]] 2031[[package]]
1443name = "socket2" 2032name = "socket2"
2033version = "0.5.10"
2034source = "registry+https://github.com/rust-lang/crates.io-index"
2035checksum = "e22376abed350d73dd1cd119b57ffccad95b4e585a7cda43e286245ce23c0678"
2036dependencies = [
2037 "libc",
2038 "windows-sys 0.52.0",
2039]
2040
2041[[package]]
2042name = "socket2"
1444version = "0.6.1" 2043version = "0.6.1"
1445source = "registry+https://github.com/rust-lang/crates.io-index" 2044source = "registry+https://github.com/rust-lang/crates.io-index"
1446checksum = "17129e116933cf371d018bb80ae557e889637989d8638274fb25622827b03881" 2045checksum = "17129e116933cf371d018bb80ae557e889637989d8638274fb25622827b03881"
@@ -1539,6 +2138,37 @@ dependencies = [
1539] 2138]
1540 2139
1541[[package]] 2140[[package]]
2141name = "time"
2142version = "0.3.44"
2143source = "registry+https://github.com/rust-lang/crates.io-index"
2144checksum = "91e7d9e3bb61134e77bde20dd4825b97c010155709965fedf0f49bb138e52a9d"
2145dependencies = [
2146 "deranged",
2147 "itoa",
2148 "num-conv",
2149 "powerfmt",
2150 "serde",
2151 "time-core",
2152 "time-macros",
2153]
2154
2155[[package]]
2156name = "time-core"
2157version = "0.1.6"
2158source = "registry+https://github.com/rust-lang/crates.io-index"
2159checksum = "40868e7c1d2f0b8d73e4a8c7f0ff63af4f6d19be117e90bd73eb1d62cf831c6b"
2160
2161[[package]]
2162name = "time-macros"
2163version = "0.2.24"
2164source = "registry+https://github.com/rust-lang/crates.io-index"
2165checksum = "30cfb0125f12d9c277f35663a0a33f8c30190f4e4574868a330595412d34ebf3"
2166dependencies = [
2167 "num-conv",
2168 "time-core",
2169]
2170
2171[[package]]
1542name = "tinystr" 2172name = "tinystr"
1543version = "0.8.2" 2173version = "0.8.2"
1544source = "registry+https://github.com/rust-lang/crates.io-index" 2174source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1575,7 +2205,7 @@ dependencies = [
1575 "parking_lot", 2205 "parking_lot",
1576 "pin-project-lite", 2206 "pin-project-lite",
1577 "signal-hook-registry", 2207 "signal-hook-registry",
1578 "socket2", 2208 "socket2 0.6.1",
1579 "tokio-macros", 2209 "tokio-macros",
1580 "windows-sys 0.61.2", 2210 "windows-sys 0.61.2",
1581] 2211]
@@ -1639,30 +2269,43 @@ dependencies = [
1639 2269
1640[[package]] 2270[[package]]
1641name = "tokio-tungstenite" 2271name = "tokio-tungstenite"
1642version = "0.21.0" 2272version = "0.26.2"
1643source = "registry+https://github.com/rust-lang/crates.io-index" 2273source = "registry+https://github.com/rust-lang/crates.io-index"
1644checksum = "c83b561d025642014097b66e6c1bb422783339e0909e4429cde4749d1990bc38" 2274checksum = "7a9daff607c6d2bf6c16fd681ccb7eecc83e4e2cdc1ca067ffaadfca5de7f084"
1645dependencies = [ 2275dependencies = [
1646 "futures-util", 2276 "futures-util",
1647 "log", 2277 "log",
2278 "rustls",
2279 "rustls-pki-types",
1648 "tokio", 2280 "tokio",
1649 "tungstenite 0.21.0", 2281 "tokio-rustls",
2282 "tungstenite 0.26.2",
2283 "webpki-roots 0.26.11",
1650] 2284]
1651 2285
1652[[package]] 2286[[package]]
1653name = "tokio-tungstenite" 2287name = "tokio-tungstenite"
1654version = "0.26.2" 2288version = "0.28.0"
1655source = "registry+https://github.com/rust-lang/crates.io-index" 2289source = "registry+https://github.com/rust-lang/crates.io-index"
1656checksum = "7a9daff607c6d2bf6c16fd681ccb7eecc83e4e2cdc1ca067ffaadfca5de7f084" 2290checksum = "d25a406cddcc431a75d3d9afc6a7c0f7428d4891dd973e4d54c56b46127bf857"
1657dependencies = [ 2291dependencies = [
1658 "futures-util", 2292 "futures-util",
1659 "log", 2293 "log",
1660 "rustls",
1661 "rustls-pki-types",
1662 "tokio", 2294 "tokio",
1663 "tokio-rustls", 2295 "tungstenite 0.28.0",
1664 "tungstenite 0.26.2", 2296]
1665 "webpki-roots 0.26.11", 2297
2298[[package]]
2299name = "tokio-util"
2300version = "0.7.17"
2301source = "registry+https://github.com/rust-lang/crates.io-index"
2302checksum = "2efa149fe76073d6e8fd97ef4f4eca7b67f599660115591483572e406e165594"
2303dependencies = [
2304 "bytes",
2305 "futures-core",
2306 "futures-sink",
2307 "pin-project-lite",
2308 "tokio",
1666] 2309]
1667 2310
1668[[package]] 2311[[package]]
@@ -1671,6 +2314,7 @@ version = "0.1.41"
1671source = "registry+https://github.com/rust-lang/crates.io-index" 2314source = "registry+https://github.com/rust-lang/crates.io-index"
1672checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0" 2315checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0"
1673dependencies = [ 2316dependencies = [
2317 "log",
1674 "pin-project-lite", 2318 "pin-project-lite",
1675 "tracing-attributes", 2319 "tracing-attributes",
1676 "tracing-core", 2320 "tracing-core",
@@ -1728,37 +2372,35 @@ dependencies = [
1728 2372
1729[[package]] 2373[[package]]
1730name = "tungstenite" 2374name = "tungstenite"
1731version = "0.21.0" 2375version = "0.26.2"
1732source = "registry+https://github.com/rust-lang/crates.io-index" 2376source = "registry+https://github.com/rust-lang/crates.io-index"
1733checksum = "9ef1a641ea34f399a848dea702823bbecfb4c486f911735368f1f137cb8257e1" 2377checksum = "4793cb5e56680ecbb1d843515b23b6de9a75eb04b66643e256a396d43be33c13"
1734dependencies = [ 2378dependencies = [
1735 "byteorder",
1736 "bytes", 2379 "bytes",
1737 "data-encoding", 2380 "data-encoding",
1738 "http", 2381 "http 1.3.1",
1739 "httparse", 2382 "httparse",
1740 "log", 2383 "log",
1741 "rand 0.8.5", 2384 "rand 0.9.2",
2385 "rustls",
2386 "rustls-pki-types",
1742 "sha1", 2387 "sha1",
1743 "thiserror 1.0.69", 2388 "thiserror 2.0.17",
1744 "url",
1745 "utf-8", 2389 "utf-8",
1746] 2390]
1747 2391
1748[[package]] 2392[[package]]
1749name = "tungstenite" 2393name = "tungstenite"
1750version = "0.26.2" 2394version = "0.28.0"
1751source = "registry+https://github.com/rust-lang/crates.io-index" 2395source = "registry+https://github.com/rust-lang/crates.io-index"
1752checksum = "4793cb5e56680ecbb1d843515b23b6de9a75eb04b66643e256a396d43be33c13" 2396checksum = "8628dcc84e5a09eb3d8423d6cb682965dea9133204e8fb3efee74c2a0c259442"
1753dependencies = [ 2397dependencies = [
1754 "bytes", 2398 "bytes",
1755 "data-encoding", 2399 "data-encoding",
1756 "http", 2400 "http 1.3.1",
1757 "httparse", 2401 "httparse",
1758 "log", 2402 "log",
1759 "rand 0.9.2", 2403 "rand 0.9.2",
1760 "rustls",
1761 "rustls-pki-types",
1762 "sha1", 2404 "sha1",
1763 "thiserror 2.0.17", 2405 "thiserror 2.0.17",
1764 "utf-8", 2406 "utf-8",
@@ -1786,6 +2428,12 @@ dependencies = [
1786] 2428]
1787 2429
1788[[package]] 2430[[package]]
2431name = "unicode-xid"
2432version = "0.2.6"
2433source = "registry+https://github.com/rust-lang/crates.io-index"
2434checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853"
2435
2436[[package]]
1789name = "universal-hash" 2437name = "universal-hash"
1790version = "0.5.1" 2438version = "0.5.1"
1791source = "registry+https://github.com/rust-lang/crates.io-index" 2439source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -2284,3 +2932,31 @@ dependencies = [
2284 "quote", 2932 "quote",
2285 "syn", 2933 "syn",
2286] 2934]
2935
2936[[package]]
2937name = "zstd"
2938version = "0.13.3"
2939source = "registry+https://github.com/rust-lang/crates.io-index"
2940checksum = "e91ee311a569c327171651566e07972200e76fcfe2242a4fa446149a3881c08a"
2941dependencies = [
2942 "zstd-safe",
2943]
2944
2945[[package]]
2946name = "zstd-safe"
2947version = "7.2.4"
2948source = "registry+https://github.com/rust-lang/crates.io-index"
2949checksum = "8f49c4d5f0abb602a93fb8736af2a4f4dd9512e36f7f570d66e65ff867ed3b9d"
2950dependencies = [
2951 "zstd-sys",
2952]
2953
2954[[package]]
2955name = "zstd-sys"
2956version = "2.0.16+zstd.1.5.7"
2957source = "registry+https://github.com/rust-lang/crates.io-index"
2958checksum = "91e19ebc2adc8f83e43039e79776e3fda8ca919132d68a1fed6a5faca2683748"
2959dependencies = [
2960 "cc",
2961 "pkg-config",
2962]
diff --git a/Cargo.toml b/Cargo.toml
index 416f76f..ae84241 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -10,18 +10,19 @@ repository = "https://gitworkshop.dev/ngit-grasp"
10[dependencies] 10[dependencies]
11# Async runtime 11# Async runtime
12tokio = { version = "1.35", features = ["full"] } 12tokio = { version = "1.35", features = ["full"] }
13tokio-tungstenite = "0.21"
14 13
15# WebSocket 14# HTTP server
16tungstenite = "0.21" 15actix-web = "4.4"
17futures-util = "0.3" 16actix-ws = "0.3"
18 17
19# HTTP server (for future use) 18# Nostr relay
20# actix-web = "4.4" 19nostr-relay-builder = "0.44"
21# actix-cors = "0.7"
22 20
23# Nostr 21# Nostr
24nostr-sdk = "0.43" 22nostr-sdk = "0.44"
23
24# Utilities
25futures-util = "0.3"
25 26
26# Serialization 27# Serialization
27serde = { version = "1.0", features = ["derive"] } 28serde = { version = "1.0", features = ["derive"] }
@@ -37,6 +38,7 @@ dotenvy = "0.15"
37# Error handling 38# Error handling
38anyhow = "1.0" 39anyhow = "1.0"
39thiserror = "1.0" 40thiserror = "1.0"
41tokio-tungstenite = "0.28.0"
40 42
41# Git (for future use) 43# Git (for future use)
42# git-http-backend = "0.3" 44# git-http-backend = "0.3"
diff --git a/src/http/landing.rs b/src/http/landing.rs
new file mode 100644
index 0000000..35e49e5
--- /dev/null
+++ b/src/http/landing.rs
@@ -0,0 +1,37 @@
1/// Landing Page Handler
2///
3/// Serves the HTML landing page or upgrades to WebSocket for Nostr relay connections.
4
5use actix_web::{web, HttpRequest, HttpResponse, Result};
6use nostr_relay_builder::LocalRelay;
7
8use crate::config::Config;
9
10/// Handle landing page or WebSocket upgrade
11pub async fn handle(
12 req: HttpRequest,
13 stream: web::Payload,
14 config: web::Data<Config>,
15 relay: web::Data<LocalRelay>,
16) -> Result<HttpResponse> {
17 // Check if this is a WebSocket upgrade request
18 if let Some(upgrade) = req.headers().get("upgrade") {
19 if upgrade.to_str().unwrap_or("").eq_ignore_ascii_case("websocket") {
20 // Delegate to WebSocket handler
21 return crate::http::websocket::handle(req, stream, relay).await;
22 }
23 }
24
25 // Otherwise, serve the landing page
26 let html = format!(
27 include_str!("../../templates/landing.html"),
28 relay_name = config.relay_name,
29 relay_description = config.relay_description,
30 domain = config.domain,
31 bind_address = config.bind_address,
32 );
33
34 Ok(HttpResponse::Ok()
35 .content_type("text/html; charset=utf-8")
36 .body(html))
37} \ No newline at end of file
diff --git a/src/http/mod.rs b/src/http/mod.rs
new file mode 100644
index 0000000..286e8ff
--- /dev/null
+++ b/src/http/mod.rs
@@ -0,0 +1,31 @@
1/// HTTP Server Module
2///
3/// Provides actix-web HTTP server with WebSocket upgrade support for the Nostr relay.
4
5pub mod landing;
6pub mod websocket;
7
8use actix_web::{middleware, web, App, HttpServer};
9use nostr_relay_builder::LocalRelay;
10
11use crate::config::Config;
12
13/// Start the HTTP server with integrated Nostr relay
14pub async fn run_server(config: Config, relay: LocalRelay) -> anyhow::Result<()> {
15 let bind_addr = config.bind_address.clone();
16
17 tracing::info!("Starting HTTP server on {}", bind_addr);
18
19 HttpServer::new(move || {
20 App::new()
21 .app_data(web::Data::new(config.clone()))
22 .app_data(web::Data::new(relay.clone()))
23 .wrap(middleware::Logger::default())
24 .route("/", web::get().to(landing::handle))
25 })
26 .bind(&bind_addr)?
27 .run()
28 .await?;
29
30 Ok(())
31} \ No newline at end of file
diff --git a/src/http/websocket.rs b/src/http/websocket.rs
new file mode 100644
index 0000000..7af847a
--- /dev/null
+++ b/src/http/websocket.rs
@@ -0,0 +1,73 @@
1/// WebSocket Handler
2///
3/// Handles WebSocket upgrade requests and passes connections to the Nostr relay.
4
5use actix_web::{web, HttpRequest, HttpResponse, Result, Error};
6use actix_ws::Message;
7use futures_util::StreamExt;
8use nostr_relay_builder::LocalRelay;
9
10/// Handle WebSocket upgrade and relay connection
11pub async fn handle(
12 req: HttpRequest,
13 stream: web::Payload,
14 relay: web::Data<LocalRelay>,
15) -> Result<HttpResponse, Error> {
16 let (response, mut session, mut msg_stream) = actix_ws::handle(&req, stream)?;
17
18 let peer_addr = req.peer_addr()
19 .unwrap_or_else(|| "0.0.0.0:0".parse().unwrap());
20
21 tracing::debug!("WebSocket connection from {}", peer_addr);
22
23 // Spawn task to handle the WebSocket connection
24 // TODO: Will use relay.take_connection() for full Nostr relay integration
25 let _relay = relay.get_ref().clone();
26 actix_web::rt::spawn(async move {
27 // Create a channel to communicate between actix-ws and relay
28 let (tx, mut rx) = tokio::sync::mpsc::unbounded_channel();
29
30 // Spawn task to send messages from relay to client
31 let mut session_clone = session.clone();
32 actix_web::rt::spawn(async move {
33 while let Some(msg) = rx.recv().await {
34 if session_clone.text(msg).await.is_err() {
35 break;
36 }
37 }
38 });
39
40 // Handle incoming messages from client
41 while let Some(Ok(msg)) = msg_stream.next().await {
42 match msg {
43 Message::Text(text) => {
44 // For now, just echo back - will integrate with relay in next phase
45 tracing::debug!("Received text message: {}", text);
46 if let Err(e) = tx.send(text.to_string()) {
47 tracing::error!("Failed to send message: {}", e);
48 break;
49 }
50 }
51 Message::Binary(_) => {
52 tracing::warn!("Received unexpected binary message");
53 }
54 Message::Close(_) => {
55 tracing::debug!("Client closed connection");
56 break;
57 }
58 Message::Ping(bytes) => {
59 if session.pong(&bytes).await.is_err() {
60 break;
61 }
62 }
63 Message::Pong(_) => {}
64 Message::Continuation(_) => {}
65 Message::Nop => {}
66 }
67 }
68
69 tracing::debug!("WebSocket connection closed for {}", peer_addr);
70 });
71
72 Ok(response)
73} \ No newline at end of file
diff --git a/src/lib.rs b/src/lib.rs
index f4a8cbf..6460716 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -1,3 +1,3 @@
1pub mod config; 1pub mod config;
2pub mod http;
2pub mod nostr; 3pub mod nostr;
3pub mod storage;
diff --git a/src/main.rs b/src/main.rs
index 7da4c73..38a3b95 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -3,8 +3,8 @@ use tracing::{info, Level};
3use tracing_subscriber::FmtSubscriber; 3use tracing_subscriber::FmtSubscriber;
4 4
5mod config; 5mod config;
6mod http;
6mod nostr; 7mod nostr;
7mod storage;
8 8
9use config::Config; 9use config::Config;
10 10
@@ -16,21 +16,23 @@ async fn main() -> Result<()> {
16 .finish(); 16 .finish();
17 tracing::subscriber::set_global_default(subscriber)?; 17 tracing::subscriber::set_global_default(subscriber)?;
18 18
19 info!("Starting ngit-grasp..."); 19 info!("Starting ngit-grasp with nostr-relay-builder...");
20 20
21 // Load configuration 21 // Load configuration
22 let config = Config::from_env()?; 22 let config = Config::from_env()?;
23 info!("Configuration loaded: {}", config.bind_address); 23 info!("Configuration loaded: {}", config.bind_address);
24 24
25 // Initialize storage 25 // Create Nostr relay with NIP-34 validation
26 let storage = storage::Storage::new(&config)?; 26 if let Ok(relay) = nostr::builder::create_relay(&config) {
27 info!("Storage initialized at: {}", config.relay_data_path); 27 info!(
28 28 "Relay created with NIP-34 validation for domain: {}",
29 // Start Nostr relay 29 config.domain
30 let relay = nostr::relay::RelayServer::new(config.clone(), storage)?; 30 );
31 31
32 info!("Starting Nostr relay on {}", config.bind_address); 32 // Start HTTP server with integrated relay
33 relay.run().await?; 33 info!("Starting HTTP server on {}", config.bind_address);
34 http::run_server(config, relay).await?;
35 }
34 36
35 Ok(()) 37 Ok(())
36} 38}
diff --git a/src/nostr/builder.rs b/src/nostr/builder.rs
new file mode 100644
index 0000000..cd1f4d2
--- /dev/null
+++ b/src/nostr/builder.rs
@@ -0,0 +1,121 @@
1/// Nostr Relay Builder Configuration
2///
3/// This module integrates nostr-relay-builder with NIP-34 validation logic
4/// preserved from the original implementation.
5use std::net::SocketAddr;
6use std::path::Path;
7
8use nostr::nips::nip19::ToBech32;
9use nostr_relay_builder::prelude::*;
10
11use crate::config::Config;
12use crate::nostr::events::{
13 validate_announcement, validate_state, KIND_REPOSITORY_ANNOUNCEMENT, KIND_REPOSITORY_STATE,
14};
15
16/// NIP-34 Write Policy
17///
18/// Validates repository announcement and state events according to GRASP-01 spec.
19/// Preserves all original validation logic from src/nostr/events.rs.
20#[derive(Debug, Clone)]
21pub struct Nip34WritePolicy {
22 domain: String,
23}
24
25impl Nip34WritePolicy {
26 pub fn new(domain: impl Into<String>) -> Self {
27 Self {
28 domain: domain.into(),
29 }
30 }
31}
32
33impl WritePolicy for Nip34WritePolicy {
34 fn admit_event<'a>(
35 &'a self,
36 event: &'a nostr_relay_builder::prelude::Event,
37 _addr: &'a SocketAddr,
38 ) -> BoxedFuture<'a, PolicyResult> {
39 Box::pin(async move {
40 match event.kind.as_u16() {
41 KIND_REPOSITORY_ANNOUNCEMENT => match validate_announcement(event, &self.domain) {
42 Ok(_) => {
43 tracing::debug!(
44 "Accepted repository announcement: {}",
45 event
46 .id
47 .to_bech32()
48 .unwrap_or_else(|_| "invalid".to_string())
49 );
50 PolicyResult::Accept
51 }
52 Err(e) => {
53 tracing::warn!(
54 "Rejected repository announcement {}: {}",
55 event
56 .id
57 .to_bech32()
58 .unwrap_or_else(|_| "invalid".to_string()),
59 e
60 );
61 PolicyResult::Reject(e.to_string())
62 }
63 },
64 KIND_REPOSITORY_STATE => match validate_state(event) {
65 Ok(_) => {
66 tracing::debug!(
67 "Accepted repository state: {}",
68 event
69 .id
70 .to_bech32()
71 .unwrap_or_else(|_| "invalid".to_string())
72 );
73 PolicyResult::Accept
74 }
75 Err(e) => {
76 tracing::warn!(
77 "Rejected repository state {}: {}",
78 event
79 .id
80 .to_bech32()
81 .unwrap_or_else(|_| "invalid".to_string()),
82 e
83 );
84 PolicyResult::Reject(e.to_string())
85 }
86 },
87 // Accept all other event kinds without validation
88 _ => PolicyResult::Accept,
89 }
90 })
91 }
92}
93
94/// Create a configured LocalRelay with NIP-34 validation
95pub fn create_relay(config: &Config) -> Result<LocalRelay> {
96 tracing::info!("Configuring nostr relay...");
97
98 // Determine database path
99 let db_path = Path::new(&config.relay_data_path);
100
101 // Create database - using in-memory for now, can switch to persistent later
102 // TODO: Add configuration for NostrDB or LMDB backends
103 let database = MemoryDatabase::with_opts(MemoryDatabaseOptions {
104 events: true,
105 max_events: Some(100_000),
106 });
107
108 tracing::info!("Using in-memory database (path: {})", db_path.display());
109
110 // Build relay with NIP-34 validation
111 let builder = RelayBuilder::default()
112 .database(database)
113 .write_policy(Nip34WritePolicy::new(&config.domain));
114
115 tracing::info!(
116 "Relay configured with NIP-34 validation for domain: {}",
117 config.domain
118 );
119
120 Ok(LocalRelay::new(builder))
121}
diff --git a/src/nostr/mod.rs b/src/nostr/mod.rs
index b485b91..2bf0346 100644
--- a/src/nostr/mod.rs
+++ b/src/nostr/mod.rs
@@ -1,2 +1,2 @@
1pub mod builder;
1pub mod events; 2pub mod events;
2pub mod relay;
diff --git a/src/nostr/relay.rs b/src/nostr/relay.rs
deleted file mode 100644
index 1033b5b..0000000
--- a/src/nostr/relay.rs
+++ /dev/null
@@ -1,340 +0,0 @@
1use anyhow::Result;
2use futures_util::{SinkExt, StreamExt};
3use nostr_sdk::{Event, EventId, Filter, Kind};
4use serde_json::{json, Value};
5use std::collections::HashMap;
6use std::net::SocketAddr;
7use std::sync::Arc;
8use tokio::net::{TcpListener, TcpStream};
9use tokio::sync::RwLock;
10use tokio_tungstenite::{accept_async, tungstenite::Message};
11use tracing::{debug, error, info, warn};
12
13use crate::config::Config;
14use crate::nostr::events::{validate_announcement, validate_state, KIND_REPOSITORY_ANNOUNCEMENT, KIND_REPOSITORY_STATE};
15use crate::storage::Storage;
16
17type Subscriptions = Arc<RwLock<HashMap<String, Vec<Filter>>>>;
18
19pub struct RelayServer {
20 config: Config,
21 storage: Storage,
22}
23
24impl RelayServer {
25 pub fn new(config: Config, storage: Storage) -> Result<Self> {
26 Ok(RelayServer { config, storage })
27 }
28
29 pub async fn run(self) -> Result<()> {
30 let addr: SocketAddr = self.config.bind_address.parse()?;
31 let listener = TcpListener::bind(&addr).await?;
32
33 info!("✅ Nostr relay listening on ws://{}", addr);
34 info!("📡 Ready to accept connections...");
35
36 loop {
37 match listener.accept().await {
38 Ok((stream, peer_addr)) => {
39 debug!("New connection from: {}", peer_addr);
40 let storage = self.storage.clone();
41 tokio::spawn(async move {
42 if let Err(e) = handle_connection(stream, storage).await {
43 error!("Error handling connection from {}: {}", peer_addr, e);
44 }
45 });
46 }
47 Err(e) => {
48 error!("Error accepting connection: {}", e);
49 }
50 }
51 }
52 }
53}
54
55async fn handle_connection(stream: TcpStream, storage: Storage) -> Result<()> {
56 let ws_stream = accept_async(stream).await?;
57 let (mut ws_sender, mut ws_receiver) = ws_stream.split();
58
59 let subscriptions: Subscriptions = Arc::new(RwLock::new(HashMap::new()));
60
61 while let Some(msg) = ws_receiver.next().await {
62 match msg {
63 Ok(Message::Text(text)) => {
64 debug!("Received message: {}", text);
65
66 match handle_message(&text, &storage, &subscriptions).await {
67 Ok(responses) => {
68 for response in responses {
69 let response_text = serde_json::to_string(&response)?;
70 debug!("Sending response: {}", response_text);
71 ws_sender.send(Message::Text(response_text)).await?;
72 }
73 }
74 Err(e) => {
75 warn!("Error handling message: {}", e);
76 let notice = json!(["NOTICE", format!("Error: {}", e)]);
77 ws_sender.send(Message::Text(notice.to_string())).await?;
78 }
79 }
80 }
81 Ok(Message::Close(_)) => {
82 debug!("Client closed connection");
83 break;
84 }
85 Ok(Message::Ping(data)) => {
86 ws_sender.send(Message::Pong(data)).await?;
87 }
88 Ok(_) => {
89 // Ignore other message types
90 }
91 Err(e) => {
92 error!("WebSocket error: {}", e);
93 break;
94 }
95 }
96 }
97
98 Ok(())
99}
100
101async fn handle_message(
102 text: &str,
103 storage: &Storage,
104 subscriptions: &Subscriptions,
105) -> Result<Vec<Value>> {
106 let msg: Value = serde_json::from_str(text)?;
107
108 if let Some(arr) = msg.as_array() {
109 if arr.is_empty() {
110 return Ok(vec![json!(["NOTICE", "Empty message"])]);
111 }
112
113 let msg_type = arr[0].as_str().unwrap_or("");
114
115 match msg_type {
116 "EVENT" => handle_event(arr, storage).await,
117 "REQ" => handle_req(arr, storage, subscriptions).await,
118 "CLOSE" => handle_close(arr, subscriptions).await,
119 _ => Ok(vec![json!(["NOTICE", format!("Unknown message type: {}", msg_type)])]),
120 }
121 } else {
122 Ok(vec![json!(["NOTICE", "Invalid message format"])])
123 }
124}
125
126async fn handle_event(arr: &[Value], storage: &Storage) -> Result<Vec<Value>> {
127 if arr.len() < 2 {
128 return Ok(vec![json!(["NOTICE", "EVENT message requires event object"])]);
129 }
130
131 let event: Event = serde_json::from_value(arr[1].clone())?;
132 let event_id = event.id;
133
134 // Verify event (signature and ID)
135 if event.verify().is_err() {
136 return Ok(vec![json!(["OK", event_id.to_hex(), false, "invalid: signature or ID verification failed"])]);
137 }
138
139 // Check if event already exists
140 if storage.get_event(&event_id.to_hex()).await.is_some() {
141 return Ok(vec![json!(["OK", event_id.to_hex(), true, "duplicate: event already exists"])]);
142 }
143
144 // Validate repository announcements (kind 30617)
145 if event.kind == Kind::from(KIND_REPOSITORY_ANNOUNCEMENT) {
146 // Get domain from storage config
147 let domain = storage.get_domain();
148
149 match validate_announcement(&event, &domain) {
150 Ok(()) => {
151 info!("✅ Valid repository announcement: {} ({})", event_id, event.kind);
152 }
153 Err(e) => {
154 warn!("❌ Invalid repository announcement: {}", e);
155 return Ok(vec![json!(["OK", event_id.to_hex(), false, format!("invalid: {}", e)])]);
156 }
157 }
158 }
159
160 // Validate repository state announcements (kind 30618)
161 if event.kind == Kind::from(KIND_REPOSITORY_STATE) {
162 match validate_state(&event) {
163 Ok(()) => {
164 info!("✅ Valid repository state: {} ({})", event_id, event.kind);
165 }
166 Err(e) => {
167 warn!("❌ Invalid repository state: {}", e);
168 return Ok(vec![json!(["OK", event_id.to_hex(), false, format!("invalid: {}", e)])]);
169 }
170 }
171 }
172
173 // Store the event
174 storage.store_event(event.clone()).await?;
175
176 info!("✅ Stored event: {} (kind: {})", event_id, event.kind);
177
178 Ok(vec![json!(["OK", event_id.to_hex(), true, ""])])
179}
180
181async fn handle_req(
182 arr: &[Value],
183 storage: &Storage,
184 subscriptions: &Subscriptions,
185) -> Result<Vec<Value>> {
186 if arr.len() < 2 {
187 return Ok(vec![json!(["NOTICE", "REQ message requires subscription ID"])]);
188 }
189
190 let sub_id = arr[1].as_str().ok_or_else(|| anyhow::anyhow!("Invalid subscription ID"))?;
191
192 // Parse filters
193 let mut filters = Vec::new();
194 for filter_value in &arr[2..] {
195 let filter: Filter = serde_json::from_value(filter_value.clone())?;
196 filters.push(filter.clone());
197 }
198
199 // Store subscription
200 {
201 let mut subs = subscriptions.write().await;
202 subs.insert(sub_id.to_string(), filters.clone());
203 }
204
205 debug!("Created subscription: {} with {} filters", sub_id, filters.len());
206
207 // Query and send matching events
208 let mut responses = Vec::new();
209
210 for filter in filters {
211 let events = storage.query_events(|event| {
212 matches_filter(event, &filter)
213 }).await;
214
215 for event in events {
216 responses.push(json!(["EVENT", sub_id, event]));
217 }
218 }
219
220 // Send EOSE (End of Stored Events)
221 responses.push(json!(["EOSE", sub_id]));
222
223 debug!("Subscription {} returned {} events", sub_id, responses.len() - 1);
224
225 Ok(responses)
226}
227
228async fn handle_close(arr: &[Value], subscriptions: &Subscriptions) -> Result<Vec<Value>> {
229 if arr.len() < 2 {
230 return Ok(vec![json!(["NOTICE", "CLOSE message requires subscription ID"])]);
231 }
232
233 let sub_id = arr[1].as_str().ok_or_else(|| anyhow::anyhow!("Invalid subscription ID"))?;
234
235 {
236 let mut subs = subscriptions.write().await;
237 subs.remove(sub_id);
238 }
239
240 debug!("Closed subscription: {}", sub_id);
241
242 Ok(vec![])
243}
244
245fn matches_filter(event: &Event, filter: &Filter) -> bool {
246 // Check IDs
247 if let Some(ref ids) = filter.ids {
248 if !ids.is_empty() && !ids.contains(&event.id) {
249 return false;
250 }
251 }
252
253 // Check authors
254 if let Some(ref authors) = filter.authors {
255 if !authors.is_empty() && !authors.contains(&event.pubkey) {
256 return false;
257 }
258 }
259
260 // Check kinds
261 if let Some(ref kinds) = filter.kinds {
262 if !kinds.is_empty() && !kinds.contains(&event.kind) {
263 return false;
264 }
265 }
266
267 // Check since
268 if let Some(since) = filter.since {
269 if event.created_at < since {
270 return false;
271 }
272 }
273
274 // Check until
275 if let Some(until) = filter.until {
276 if event.created_at > until {
277 return false;
278 }
279 }
280
281 // TODO: Check tags (#e, #p, etc.)
282
283 true
284}
285
286#[cfg(test)]
287mod tests {
288 use super::*;
289 use nostr_sdk::{EventBuilder, Keys, Kind};
290
291 #[test]
292 fn test_matches_filter_by_id() {
293 let keys = Keys::generate();
294 let event = EventBuilder::text_note("test")
295 .sign_with_keys(&keys)
296 .unwrap();
297
298 // Filter matching the event ID
299 let filter = Filter::new().id(event.id);
300 assert!(matches_filter(&event, &filter));
301
302 // Filter not matching
303 let other_id = EventId::all_zeros();
304 let filter = Filter::new().id(other_id);
305 assert!(!matches_filter(&event, &filter));
306 }
307
308 #[test]
309 fn test_matches_filter_by_author() {
310 let keys = Keys::generate();
311 let event = EventBuilder::text_note("test")
312 .sign_with_keys(&keys)
313 .unwrap();
314
315 // Filter matching the author
316 let filter = Filter::new().author(keys.public_key());
317 assert!(matches_filter(&event, &filter));
318
319 // Filter not matching
320 let other_keys = Keys::generate();
321 let filter = Filter::new().author(other_keys.public_key());
322 assert!(!matches_filter(&event, &filter));
323 }
324
325 #[test]
326 fn test_matches_filter_by_kind() {
327 let keys = Keys::generate();
328 let event = EventBuilder::text_note("test")
329 .sign_with_keys(&keys)
330 .unwrap();
331
332 // Filter matching the kind
333 let filter = Filter::new().kind(Kind::TextNote);
334 assert!(matches_filter(&event, &filter));
335
336 // Filter not matching
337 let filter = Filter::new().kind(Kind::Metadata);
338 assert!(!matches_filter(&event, &filter));
339 }
340}
diff --git a/src/storage/mod.rs b/src/storage/mod.rs
deleted file mode 100644
index eab8211..0000000
--- a/src/storage/mod.rs
+++ /dev/null
@@ -1,132 +0,0 @@
1use anyhow::Result;
2use nostr_sdk::Event;
3use std::collections::HashMap;
4use std::sync::Arc;
5use tokio::sync::RwLock;
6
7use crate::config::Config;
8
9/// Simple in-memory storage for events
10/// TODO: Persist to disk for production use
11#[derive(Clone)]
12pub struct Storage {
13 events: Arc<RwLock<HashMap<String, Event>>>,
14 data_path: String,
15 domain: String,
16}
17
18impl Storage {
19 pub fn new(config: &Config) -> Result<Self> {
20 // Create data directory if it doesn't exist
21 std::fs::create_dir_all(&config.relay_data_path)?;
22
23 Ok(Storage {
24 events: Arc::new(RwLock::new(HashMap::new())),
25 data_path: config.relay_data_path.clone(),
26 domain: config.domain.clone(),
27 })
28 }
29
30 pub fn get_domain(&self) -> String {
31 self.domain.clone()
32 }
33
34 pub async fn store_event(&self, event: Event) -> Result<()> {
35 let mut events = self.events.write().await;
36 events.insert(event.id.to_hex(), event);
37 Ok(())
38 }
39
40 pub async fn get_event(&self, event_id: &str) -> Option<Event> {
41 let events = self.events.read().await;
42 events.get(event_id).cloned()
43 }
44
45 pub async fn query_events<F>(&self, filter: F) -> Vec<Event>
46 where
47 F: Fn(&Event) -> bool,
48 {
49 let events = self.events.read().await;
50 events.values().filter(|e| filter(e)).cloned().collect()
51 }
52
53 pub async fn count_events(&self) -> usize {
54 let events = self.events.read().await;
55 events.len()
56 }
57}
58
59#[cfg(test)]
60mod tests {
61 use super::*;
62 use nostr_sdk::{EventBuilder, Keys, Kind};
63
64 #[tokio::test]
65 async fn test_store_and_retrieve() {
66 let config = Config {
67 domain: "test".to_string(),
68 owner_npub: "npub1test".to_string(),
69 relay_name: "test".to_string(),
70 relay_description: "test".to_string(),
71 git_data_path: "./test_data/git".to_string(),
72 relay_data_path: "./test_data/relay".to_string(),
73 bind_address: "127.0.0.1:8080".to_string(),
74 };
75
76 let storage = Storage::new(&config).unwrap();
77
78 // Create a test event
79 let keys = Keys::generate();
80 let event = EventBuilder::text_note("test content")
81 .sign_with_keys(&keys)
82 .unwrap();
83
84 // Store it
85 storage.store_event(event.clone()).await.unwrap();
86
87 // Retrieve it
88 let retrieved = storage.get_event(&event.id.to_hex()).await;
89 assert!(retrieved.is_some());
90 assert_eq!(retrieved.unwrap().id, event.id);
91
92 // Count events
93 assert_eq!(storage.count_events().await, 1);
94 }
95
96 #[tokio::test]
97 async fn test_query_events() {
98 let config = Config {
99 domain: "test".to_string(),
100 owner_npub: "npub1test".to_string(),
101 relay_name: "test".to_string(),
102 relay_description: "test".to_string(),
103 git_data_path: "./test_data/git".to_string(),
104 relay_data_path: "./test_data/relay".to_string(),
105 bind_address: "127.0.0.1:8080".to_string(),
106 };
107
108 let storage = Storage::new(&config).unwrap();
109
110 // Create multiple events
111 let keys = Keys::generate();
112 let event1 = EventBuilder::text_note("message 1")
113 .sign_with_keys(&keys)
114 .unwrap();
115 let event2 = EventBuilder::text_note("message 2")
116 .sign_with_keys(&keys)
117 .unwrap();
118
119 storage.store_event(event1.clone()).await.unwrap();
120 storage.store_event(event2.clone()).await.unwrap();
121
122 // Query all events
123 let all_events = storage.query_events(|_| true).await;
124 assert_eq!(all_events.len(), 2);
125
126 // Query by kind
127 let text_notes = storage
128 .query_events(|e| e.kind == Kind::TextNote)
129 .await;
130 assert_eq!(text_notes.len(), 2);
131 }
132}
diff --git a/templates/landing.html b/templates/landing.html
new file mode 100644
index 0000000..84be8ad
--- /dev/null
+++ b/templates/landing.html
@@ -0,0 +1,151 @@
1<!DOCTYPE html>
2<html lang="en">
3<head>
4 <meta charset="UTF-8">
5 <meta name="viewport" content="width=device-width, initial-scale=1.0">
6 <title>{relay_name}</title>
7 <style>
8 * {{
9 margin: 0;
10 padding: 0;
11 box-sizing: border-box;
12 }}
13 body {{
14 font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 'Ubuntu', 'Cantarell', sans-serif;
15 line-height: 1.6;
16 background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
17 color: #333;
18 min-height: 100vh;
19 display: flex;
20 align-items: center;
21 justify-content: center;
22 padding: 20px;
23 }}
24 .container {{
25 max-width: 800px;
26 background: white;
27 padding: 40px;
28 border-radius: 12px;
29 box-shadow: 0 20px 60px rgba(0,0,0,0.3);
30 }}
31 h1 {{
32 color: #667eea;
33 margin-bottom: 10px;
34 font-size: 2.5em;
35 }}
36 h2 {{
37 color: #764ba2;
38 margin-top: 30px;
39 margin-bottom: 15px;
40 font-size: 1.5em;
41 border-bottom: 2px solid #667eea;
42 padding-bottom: 10px;
43 }}
44 .subtitle {{
45 color: #666;
46 margin-bottom: 30px;
47 font-size: 1.1em;
48 }}
49 .info-grid {{
50 display: grid;
51 grid-template-columns: 140px 1fr;
52 gap: 15px;
53 margin: 20px 0;
54 }}
55 .info-label {{
56 font-weight: 600;
57 color: #555;
58 }}
59 .info-value {{
60 color: #333;
61 }}
62 code {{
63 background: #f4f4f4;
64 padding: 3px 8px;
65 border-radius: 4px;
66 font-family: 'Courier New', monospace;
67 color: #667eea;
68 font-size: 0.9em;
69 }}
70 ul {{
71 margin: 15px 0;
72 padding-left: 30px;
73 }}
74 li {{
75 margin: 8px 0;
76 }}
77 .feature-box {{
78 background: #f9f9f9;
79 padding: 20px;
80 border-radius: 8px;
81 margin: 15px 0;
82 border-left: 4px solid #667eea;
83 }}
84 .footer {{
85 margin-top: 40px;
86 padding-top: 20px;
87 border-top: 1px solid #eee;
88 text-align: center;
89 color: #999;
90 font-size: 0.9em;
91 }}
92 .badge {{
93 display: inline-block;
94 background: #667eea;
95 color: white;
96 padding: 4px 12px;
97 border-radius: 12px;
98 font-size: 0.85em;
99 margin-right: 8px;
100 }}
101 </style>
102</head>
103<body>
104 <div class="container">
105 <h1>{relay_name}</h1>
106 <p class="subtitle">{relay_description}</p>
107
108 <h2>📡 Connection Information</h2>
109 <div class="info-grid">
110 <div class="info-label">Domain:</div>
111 <div class="info-value"><code>{domain}</code></div>
112
113 <div class="info-label">WebSocket:</div>
114 <div class="info-value"><code>ws://{bind_address}/</code></div>
115
116 <div class="info-label">HTTP:</div>
117 <div class="info-value"><code>http://{bind_address}/</code></div>
118 </div>
119
120 <h2>🔌 Supported NIPs</h2>
121 <div class="feature-box">
122 <ul>
123 <li><span class="badge">NIP-01</span> Basic protocol flow and event structure</li>
124 <li><span class="badge">NIP-11</span> Relay information document</li>
125 <li><span class="badge">NIP-34</span> Git repository events and announcements</li>
126 </ul>
127 </div>
128
129 <h2>🎯 GRASP Features</h2>
130 <div class="feature-box">
131 <ul>
132 <li><strong>Repository Announcements</strong> (kind 30617) - Declare Git repositories with validation</li>
133 <li><strong>Repository State</strong> (kind 30618) - Track repository state changes</li>
134 <li><strong>NIP-34 Validation</strong> - Automatic validation of repository events</li>
135 <li><strong>Git HTTP Backend</strong> - Coming soon in Phase 2</li>
136 </ul>
137 </div>
138
139 <h2>📚 Documentation</h2>
140 <p>For more information about GRASP (Git Relays Authorized via Signed-Nostr Proofs):</p>
141 <ul>
142 <li><a href="https://gitworkshop.dev/danconwaydev.com/grasp/01.md" target="_blank">GRASP-01 Specification</a></li>
143 <li><a href="https://github.com/nostr-protocol/nips/blob/master/34.md" target="_blank">NIP-34: Git Stuff</a></li>
144 </ul>
145
146 <div class="footer">
147 <p>Powered by <strong>ngit-grasp</strong> with <strong>nostr-relay-builder</strong></p>
148 </div>
149 </div>
150</body>
151</html> \ No newline at end of file
diff --git a/tests/nip01_compliance.rs b/tests/nip01_compliance.rs
index 3e2fdd3..05957fd 100644
--- a/tests/nip01_compliance.rs
+++ b/tests/nip01_compliance.rs
@@ -39,22 +39,22 @@ use grasp_audit::*;
39async fn test_nip01_smoke() { 39async fn test_nip01_smoke() {
40 // Start test relay 40 // Start test relay
41 let relay = TestRelay::start().await; 41 let relay = TestRelay::start().await;
42 42
43 // Create audit client in CI mode (isolated, no cleanup needed) 43 // Create audit client in CI mode (isolated, no cleanup needed)
44 let config = AuditConfig::ci(); 44 let config = AuditConfig::ci();
45 let client = AuditClient::new(relay.url(), config) 45 let client = AuditClient::new(relay.url(), config)
46 .await 46 .await
47 .expect("Failed to create audit client"); 47 .expect("Failed to create audit client");
48 48
49 // Run all NIP-01 smoke tests 49 // Run all NIP-01 smoke tests
50 let results = specs::Nip01SmokeTests::run_all(&client).await; 50 let results = specs::Nip01SmokeTests::run_all(&client).await;
51 51
52 // Print detailed report 52 // Print detailed report
53 results.print_report(); 53 results.print_report();
54 54
55 // Stop relay 55 // Stop relay
56 relay.stop().await; 56 relay.stop().await;
57 57
58 // Assert all tests passed 58 // Assert all tests passed
59 assert!( 59 assert!(
60 results.all_passed(), 60 results.all_passed(),
@@ -70,20 +70,20 @@ async fn test_nip01_smoke() {
70/// for more granular testing or debugging. 70/// for more granular testing or debugging.
71#[tokio::test] 71#[tokio::test]
72async fn test_nip01_individual_tests() { 72async fn test_nip01_individual_tests() {
73 use grasp_audit::specs::nip01_smoke::Nip01SmokeTests; 73 use grasp_audit::specs::grasp01::Nip01SmokeTests;
74 74
75 let relay = TestRelay::start().await; 75 let relay = TestRelay::start().await;
76 let config = AuditConfig::ci(); 76 let config = AuditConfig::ci();
77 let client = AuditClient::new(relay.url(), config) 77 let client = AuditClient::new(relay.url(), config)
78 .await 78 .await
79 .expect("Failed to create audit client"); 79 .expect("Failed to create audit client");
80 80
81 // We can't call private methods, so we'll run the full suite 81 // We can't call private methods, so we'll run the full suite
82 // This test is mainly to show the pattern 82 // This test is mainly to show the pattern
83 let all_results = Nip01SmokeTests::run_all(&client).await; 83 let all_results = Nip01SmokeTests::run_all(&client).await;
84 84
85 relay.stop().await; 85 relay.stop().await;
86 86
87 // Verify 87 // Verify
88 assert!(all_results.all_passed()); 88 assert!(all_results.all_passed());
89} 89}
@@ -99,25 +99,25 @@ async fn test_relay_validates_events() {
99 let client = AuditClient::new(relay.url(), config) 99 let client = AuditClient::new(relay.url(), config)
100 .await 100 .await
101 .expect("Failed to create audit client"); 101 .expect("Failed to create audit client");
102 102
103 // The validation tests are part of the smoke tests 103 // The validation tests are part of the smoke tests
104 let results = specs::Nip01SmokeTests::run_all(&client).await; 104 let results = specs::Nip01SmokeTests::run_all(&client).await;
105 105
106 // Check that validation tests exist and pass 106 // Check that validation tests exist and pass
107 let validation_tests: Vec<_> = results 107 let validation_tests: Vec<_> = results
108 .results 108 .results
109 .iter() 109 .iter()
110 .filter(|t| t.spec_ref.contains("validation")) 110 .filter(|t| t.spec_ref.contains("validation"))
111 .collect(); 111 .collect();
112 112
113 relay.stop().await; 113 relay.stop().await;
114 114
115 // Should have validation tests 115 // Should have validation tests
116 assert!( 116 assert!(
117 !validation_tests.is_empty(), 117 !validation_tests.is_empty(),
118 "No validation tests found in NIP-01 smoke tests" 118 "No validation tests found in NIP-01 smoke tests"
119 ); 119 );
120 120
121 // All validation tests should pass 121 // All validation tests should pass
122 for test in validation_tests { 122 for test in validation_tests {
123 assert!( 123 assert!(
@@ -137,18 +137,18 @@ async fn test_relay_lifecycle() {
137 // Start relay 137 // Start relay
138 let relay = TestRelay::start().await; 138 let relay = TestRelay::start().await;
139 let url = relay.url().to_string(); 139 let url = relay.url().to_string();
140 140
141 // Verify we can connect 141 // Verify we can connect
142 let config = AuditConfig::ci(); 142 let config = AuditConfig::ci();
143 let client = AuditClient::new(&url, config) 143 let client = AuditClient::new(&url, config)
144 .await 144 .await
145 .expect("Failed to connect to relay"); 145 .expect("Failed to connect to relay");
146 146
147 assert!(client.is_connected().await, "Client should be connected"); 147 assert!(client.is_connected().await, "Client should be connected");
148 148
149 // Stop relay 149 // Stop relay
150 relay.stop().await; 150 relay.stop().await;
151 151
152 // Note: We can't easily verify disconnection without modifying grasp-audit 152 // Note: We can't easily verify disconnection without modifying grasp-audit
153 // to expose connection state after relay shutdown. That's okay - the 153 // to expose connection state after relay shutdown. That's okay - the
154 // important part is that the relay starts and stops cleanly. 154 // important part is that the relay starts and stops cleanly.
@@ -162,28 +162,28 @@ async fn test_parallel_relays() {
162 // Start two relays simultaneously 162 // Start two relays simultaneously
163 let relay1 = TestRelay::start().await; 163 let relay1 = TestRelay::start().await;
164 let relay2 = TestRelay::start().await; 164 let relay2 = TestRelay::start().await;
165 165
166 // Should have different URLs (different ports) 166 // Should have different URLs (different ports)
167 assert_ne!( 167 assert_ne!(
168 relay1.url(), 168 relay1.url(),
169 relay2.url(), 169 relay2.url(),
170 "Relays should use different ports" 170 "Relays should use different ports"
171 ); 171 );
172 172
173 // Both should be connectable 173 // Both should be connectable
174 let config = AuditConfig::ci(); 174 let config = AuditConfig::ci();
175 175
176 let client1 = AuditClient::new(relay1.url(), config.clone()) 176 let client1 = AuditClient::new(relay1.url(), config.clone())
177 .await 177 .await
178 .expect("Failed to connect to relay 1"); 178 .expect("Failed to connect to relay 1");
179 179
180 let client2 = AuditClient::new(relay2.url(), config) 180 let client2 = AuditClient::new(relay2.url(), config)
181 .await 181 .await
182 .expect("Failed to connect to relay 2"); 182 .expect("Failed to connect to relay 2");
183 183
184 assert!(client1.is_connected().await); 184 assert!(client1.is_connected().await);
185 assert!(client2.is_connected().await); 185 assert!(client2.is_connected().await);
186 186
187 // Clean up 187 // Clean up
188 relay1.stop().await; 188 relay1.stop().await;
189 relay2.stop().await; 189 relay2.stop().await;
diff --git a/tests/nip34_announcements.rs b/tests/nip34_announcements.rs
index 6e83bb6..535425d 100644
--- a/tests/nip34_announcements.rs
+++ b/tests/nip34_announcements.rs
@@ -36,7 +36,9 @@ const KIND_REPOSITORY_ANNOUNCEMENT: u16 = 30617;
36const KIND_REPOSITORY_STATE: u16 = 30618; 36const KIND_REPOSITORY_STATE: u16 = 30618;
37 37
38/// Helper to connect to a test relay 38/// Helper to connect to a test relay
39async fn connect_to_relay(url: &str) -> tokio_tungstenite::WebSocketStream<tokio_tungstenite::MaybeTlsStream<tokio::net::TcpStream>> { 39async fn connect_to_relay(
40 url: &str,
41) -> tokio_tungstenite::WebSocketStream<tokio_tungstenite::MaybeTlsStream<tokio::net::TcpStream>> {
40 let (ws, _) = connect_async(url) 42 let (ws, _) = connect_async(url)
41 .await 43 .await
42 .expect("Failed to connect to relay"); 44 .expect("Failed to connect to relay");
@@ -54,10 +56,7 @@ fn create_announcement(
54 let mut tags = vec![Tag::custom(TagKind::d(), vec![identifier.to_string()])]; 56 let mut tags = vec![Tag::custom(TagKind::d(), vec![identifier.to_string()])];
55 57
56 for url in clone_urls { 58 for url in clone_urls {
57 tags.push(Tag::custom( 59 tags.push(Tag::custom(TagKind::Clone, vec![url]));
58 TagKind::Clone,
59 vec![url],
60 ));
61 } 60 }
62 61
63 for relay in relays { 62 for relay in relays {
@@ -94,10 +93,10 @@ fn create_state(keys: &Keys, identifier: &str, branches: Vec<(&str, &str)>) -> n
94#[tokio::test] 93#[tokio::test]
95async fn test_relay_accepts_connection() { 94async fn test_relay_accepts_connection() {
96 let relay = TestRelay::start().await; 95 let relay = TestRelay::start().await;
97 96
98 // Try to connect 97 // Try to connect
99 let ws = connect_to_relay(relay.url()).await; 98 let ws = connect_to_relay(relay.url()).await;
100 99
101 drop(ws); // Clean disconnect 100 drop(ws); // Clean disconnect
102} 101}
103 102
@@ -119,14 +118,14 @@ async fn test_accepts_valid_announcement() {
119 118
120 // Send event 119 // Send event
121 let event_msg = json!(["EVENT", event]); 120 let event_msg = json!(["EVENT", event]);
122 ws.send(Message::Text(event_msg.to_string())) 121 ws.send(Message::Text(event_msg.to_string().into()))
123 .await 122 .await
124 .expect("Failed to send event"); 123 .expect("Failed to send event");
125 124
126 // Read response 125 // Read response
127 if let Some(Ok(Message::Text(text))) = ws.next().await { 126 if let Some(Ok(Message::Text(text))) = ws.next().await {
128 let response: Value = serde_json::from_str(&text).expect("Failed to parse response"); 127 let response: Value = serde_json::from_str(&text).expect("Failed to parse response");
129 128
130 // Should be ["OK", event_id, true, ""] 129 // Should be ["OK", event_id, true, ""]
131 assert_eq!(response[0], "OK"); 130 assert_eq!(response[0], "OK");
132 assert_eq!(response[1], event.id.to_hex()); 131 assert_eq!(response[1], event.id.to_hex());
@@ -146,9 +145,7 @@ async fn test_rejects_announcement_without_clone() {
146 let relay = TestRelay::start().await; 145 let relay = TestRelay::start().await;
147 let keys = Keys::generate(); 146 let keys = Keys::generate();
148 147
149 let (mut ws, _) = connect_async(relay.url()) 148 let (mut ws, _) = connect_async(relay.url()).await.expect("Failed to connect");
150 .await
151 .expect("Failed to connect");
152 149
153 // Missing clone tag 150 // Missing clone tag
154 let event = create_announcement( 151 let event = create_announcement(
@@ -160,18 +157,18 @@ async fn test_rejects_announcement_without_clone() {
160 ); 157 );
161 158
162 let event_msg = json!(["EVENT", event]); 159 let event_msg = json!(["EVENT", event]);
163 ws.send(Message::Text(event_msg.to_string())) 160 ws.send(Message::Text(event_msg.to_string().into()))
164 .await 161 .await
165 .expect("Failed to send event"); 162 .expect("Failed to send event");
166 163
167 if let Some(Ok(Message::Text(text))) = ws.next().await { 164 if let Some(Ok(Message::Text(text))) = ws.next().await {
168 let response: Value = serde_json::from_str(&text).expect("Failed to parse"); 165 let response: Value = serde_json::from_str(&text).expect("Failed to parse");
169 166
170 // Should be rejected 167 // Should be rejected
171 assert_eq!(response[0], "OK"); 168 assert_eq!(response[0], "OK");
172 assert_eq!(response[1], event.id.to_hex()); 169 assert_eq!(response[1], event.id.to_hex());
173 assert_eq!(response[2], false, "Event should be rejected"); 170 assert_eq!(response[2], false, "Event should be rejected");
174 171
175 let message = response[3].as_str().unwrap(); 172 let message = response[3].as_str().unwrap();
176 assert!( 173 assert!(
177 message.contains("clone") || message.contains("invalid"), 174 message.contains("clone") || message.contains("invalid"),
@@ -190,9 +187,7 @@ async fn test_rejects_announcement_without_relay() {
190 let relay = TestRelay::start().await; 187 let relay = TestRelay::start().await;
191 let keys = Keys::generate(); 188 let keys = Keys::generate();
192 189
193 let (mut ws, _) = connect_async(relay.url()) 190 let (mut ws, _) = connect_async(relay.url()).await.expect("Failed to connect");
194 .await
195 .expect("Failed to connect");
196 191
197 // Missing relay tag 192 // Missing relay tag
198 let event = create_announcement( 193 let event = create_announcement(
@@ -204,18 +199,18 @@ async fn test_rejects_announcement_without_relay() {
204 ); 199 );
205 200
206 let event_msg = json!(["EVENT", event]); 201 let event_msg = json!(["EVENT", event]);
207 ws.send(Message::Text(event_msg.to_string())) 202 ws.send(Message::Text(event_msg.to_string().into()))
208 .await 203 .await
209 .expect("Failed to send event"); 204 .expect("Failed to send event");
210 205
211 if let Some(Ok(Message::Text(text))) = ws.next().await { 206 if let Some(Ok(Message::Text(text))) = ws.next().await {
212 let response: Value = serde_json::from_str(&text).expect("Failed to parse"); 207 let response: Value = serde_json::from_str(&text).expect("Failed to parse");
213 208
214 // Should be rejected 209 // Should be rejected
215 assert_eq!(response[0], "OK"); 210 assert_eq!(response[0], "OK");
216 assert_eq!(response[1], event.id.to_hex()); 211 assert_eq!(response[1], event.id.to_hex());
217 assert_eq!(response[2], false, "Event should be rejected"); 212 assert_eq!(response[2], false, "Event should be rejected");
218 213
219 let message = response[3].as_str().unwrap(); 214 let message = response[3].as_str().unwrap();
220 assert!( 215 assert!(
221 message.contains("relays") || message.contains("invalid"), 216 message.contains("relays") || message.contains("invalid"),
@@ -233,9 +228,7 @@ async fn test_rejects_announcement_for_other_service() {
233 let relay = TestRelay::start().await; 228 let relay = TestRelay::start().await;
234 let keys = Keys::generate(); 229 let keys = Keys::generate();
235 230
236 let (mut ws, _) = connect_async(relay.url()) 231 let (mut ws, _) = connect_async(relay.url()).await.expect("Failed to connect");
237 .await
238 .expect("Failed to connect");
239 232
240 // Lists different service 233 // Lists different service
241 let event = create_announcement( 234 let event = create_announcement(
@@ -247,13 +240,13 @@ async fn test_rejects_announcement_for_other_service() {
247 ); 240 );
248 241
249 let event_msg = json!(["EVENT", event]); 242 let event_msg = json!(["EVENT", event]);
250 ws.send(Message::Text(event_msg.to_string())) 243 ws.send(Message::Text(event_msg.to_string().into()))
251 .await 244 .await
252 .expect("Failed to send event"); 245 .expect("Failed to send event");
253 246
254 if let Some(Ok(Message::Text(text))) = ws.next().await { 247 if let Some(Ok(Message::Text(text))) = ws.next().await {
255 let response: Value = serde_json::from_str(&text).expect("Failed to parse"); 248 let response: Value = serde_json::from_str(&text).expect("Failed to parse");
256 249
257 // Should be rejected 250 // Should be rejected
258 assert_eq!(response[0], "OK"); 251 assert_eq!(response[0], "OK");
259 assert_eq!(response[1], event.id.to_hex()); 252 assert_eq!(response[1], event.id.to_hex());
@@ -269,9 +262,7 @@ async fn test_accepts_valid_state() {
269 let relay = TestRelay::start().await; 262 let relay = TestRelay::start().await;
270 let keys = Keys::generate(); 263 let keys = Keys::generate();
271 264
272 let (mut ws, _) = connect_async(relay.url()) 265 let (mut ws, _) = connect_async(relay.url()).await.expect("Failed to connect");
273 .await
274 .expect("Failed to connect");
275 266
276 let event = create_state( 267 let event = create_state(
277 &keys, 268 &keys,
@@ -280,13 +271,13 @@ async fn test_accepts_valid_state() {
280 ); 271 );
281 272
282 let event_msg = json!(["EVENT", event]); 273 let event_msg = json!(["EVENT", event]);
283 ws.send(Message::Text(event_msg.to_string())) 274 ws.send(Message::Text(event_msg.to_string().into()))
284 .await 275 .await
285 .expect("Failed to send event"); 276 .expect("Failed to send event");
286 277
287 if let Some(Ok(Message::Text(text))) = ws.next().await { 278 if let Some(Ok(Message::Text(text))) = ws.next().await {
288 let response: Value = serde_json::from_str(&text).expect("Failed to parse"); 279 let response: Value = serde_json::from_str(&text).expect("Failed to parse");
289 280
290 // Should be accepted 281 // Should be accepted
291 assert_eq!(response[0], "OK"); 282 assert_eq!(response[0], "OK");
292 assert_eq!(response[1], event.id.to_hex()); 283 assert_eq!(response[1], event.id.to_hex());
@@ -302,9 +293,7 @@ async fn test_accepts_state_with_multiple_branches() {
302 let relay = TestRelay::start().await; 293 let relay = TestRelay::start().await;
303 let keys = Keys::generate(); 294 let keys = Keys::generate();
304 295
305 let (mut ws, _) = connect_async(relay.url()) 296 let (mut ws, _) = connect_async(relay.url()).await.expect("Failed to connect");
306 .await
307 .expect("Failed to connect");
308 297
309 let event = create_state( 298 let event = create_state(
310 &keys, 299 &keys,
@@ -317,13 +306,13 @@ async fn test_accepts_state_with_multiple_branches() {
317 ); 306 );
318 307
319 let event_msg = json!(["EVENT", event]); 308 let event_msg = json!(["EVENT", event]);
320 ws.send(Message::Text(event_msg.to_string())) 309 ws.send(Message::Text(event_msg.to_string().into()))
321 .await 310 .await
322 .expect("Failed to send event"); 311 .expect("Failed to send event");
323 312
324 if let Some(Ok(Message::Text(text))) = ws.next().await { 313 if let Some(Ok(Message::Text(text))) = ws.next().await {
325 let response: Value = serde_json::from_str(&text).expect("Failed to parse"); 314 let response: Value = serde_json::from_str(&text).expect("Failed to parse");
326 315
327 assert_eq!(response[0], "OK"); 316 assert_eq!(response[0], "OK");
328 assert_eq!(response[2], true, "State event should be accepted"); 317 assert_eq!(response[2], true, "State event should be accepted");
329 } else { 318 } else {
@@ -337,9 +326,7 @@ async fn test_rejects_state_without_identifier() {
337 let relay = TestRelay::start().await; 326 let relay = TestRelay::start().await;
338 let keys = Keys::generate(); 327 let keys = Keys::generate();
339 328
340 let (mut ws, _) = connect_async(relay.url()) 329 let (mut ws, _) = connect_async(relay.url()).await.expect("Failed to connect");
341 .await
342 .expect("Failed to connect");
343 330
344 // Create state without identifier 331 // Create state without identifier
345 let event = EventBuilder::new(Kind::from(KIND_REPOSITORY_STATE), "") 332 let event = EventBuilder::new(Kind::from(KIND_REPOSITORY_STATE), "")
@@ -347,18 +334,18 @@ async fn test_rejects_state_without_identifier() {
347 .expect("Failed to sign event"); 334 .expect("Failed to sign event");
348 335
349 let event_msg = json!(["EVENT", event]); 336 let event_msg = json!(["EVENT", event]);
350 ws.send(Message::Text(event_msg.to_string())) 337 ws.send(Message::Text(event_msg.to_string().into()))
351 .await 338 .await
352 .expect("Failed to send event"); 339 .expect("Failed to send event");
353 340
354 if let Some(Ok(Message::Text(text))) = ws.next().await { 341 if let Some(Ok(Message::Text(text))) = ws.next().await {
355 let response: Value = serde_json::from_str(&text).expect("Failed to parse"); 342 let response: Value = serde_json::from_str(&text).expect("Failed to parse");
356 343
357 // Should be rejected 344 // Should be rejected
358 assert_eq!(response[0], "OK"); 345 assert_eq!(response[0], "OK");
359 assert_eq!(response[1], event.id.to_hex()); 346 assert_eq!(response[1], event.id.to_hex());
360 assert_eq!(response[2], false, "Event should be rejected"); 347 assert_eq!(response[2], false, "Event should be rejected");
361 348
362 let message = response[3].as_str().unwrap(); 349 let message = response[3].as_str().unwrap();
363 assert!( 350 assert!(
364 message.contains("identifier") || message.contains("invalid"), 351 message.contains("identifier") || message.contains("invalid"),
@@ -376,21 +363,22 @@ async fn test_query_announcements() {
376 let relay = TestRelay::start().await; 363 let relay = TestRelay::start().await;
377 let keys = Keys::generate(); 364 let keys = Keys::generate();
378 365
379 let (mut ws, _) = connect_async(relay.url()) 366 let (mut ws, _) = connect_async(relay.url()).await.expect("Failed to connect");
380 .await
381 .expect("Failed to connect");
382 367
383 // Send an announcement 368 // Send an announcement
384 let event = create_announcement( 369 let event = create_announcement(
385 &keys, 370 &keys,
386 &relay.domain(), 371 &relay.domain(),
387 "query-test-repo", 372 "query-test-repo",
388 vec![format!("https://{}/alice/query-test-repo.git", relay.domain())], 373 vec![format!(
374 "https://{}/alice/query-test-repo.git",
375 relay.domain()
376 )],
389 vec![format!("wss://{}", relay.domain())], 377 vec![format!("wss://{}", relay.domain())],
390 ); 378 );
391 379
392 let event_msg = json!(["EVENT", event]); 380 let event_msg = json!(["EVENT", event]);
393 ws.send(Message::Text(event_msg.to_string())) 381 ws.send(Message::Text(event_msg.to_string().into()))
394 .await 382 .await
395 .expect("Failed to send event"); 383 .expect("Failed to send event");
396 384
@@ -409,7 +397,7 @@ async fn test_query_announcements() {
409 } 397 }
410 ]); 398 ]);
411 399
412 ws.send(Message::Text(req.to_string())) 400 ws.send(Message::Text(req.to_string().into()))
413 .await 401 .await
414 .expect("Failed to send REQ"); 402 .expect("Failed to send REQ");
415 403
@@ -420,7 +408,7 @@ async fn test_query_announcements() {
420 for _ in 0..10 { 408 for _ in 0..10 {
421 if let Some(Ok(Message::Text(text))) = ws.next().await { 409 if let Some(Ok(Message::Text(text))) = ws.next().await {
422 let response: Value = serde_json::from_str(&text).expect("Failed to parse"); 410 let response: Value = serde_json::from_str(&text).expect("Failed to parse");
423 411
424 if response[0] == "EVENT" { 412 if response[0] == "EVENT" {
425 assert_eq!(response[1], "test-sub"); 413 assert_eq!(response[1], "test-sub");
426 found_event = true; 414 found_event = true;
@@ -442,9 +430,7 @@ async fn test_query_states() {
442 let relay = TestRelay::start().await; 430 let relay = TestRelay::start().await;
443 let keys = Keys::generate(); 431 let keys = Keys::generate();
444 432
445 let (mut ws, _) = connect_async(relay.url()) 433 let (mut ws, _) = connect_async(relay.url()).await.expect("Failed to connect");
446 .await
447 .expect("Failed to connect");
448 434
449 // Send a state event 435 // Send a state event
450 let event = create_state( 436 let event = create_state(
@@ -454,7 +440,7 @@ async fn test_query_states() {
454 ); 440 );
455 441
456 let event_msg = json!(["EVENT", event]); 442 let event_msg = json!(["EVENT", event]);
457 ws.send(Message::Text(event_msg.to_string())) 443 ws.send(Message::Text(event_msg.to_string().into()))
458 .await 444 .await
459 .expect("Failed to send event"); 445 .expect("Failed to send event");
460 446
@@ -473,7 +459,7 @@ async fn test_query_states() {
473 } 459 }
474 ]); 460 ]);
475 461
476 ws.send(Message::Text(req.to_string())) 462 ws.send(Message::Text(req.to_string().into()))
477 .await 463 .await
478 .expect("Failed to send REQ"); 464 .expect("Failed to send REQ");
479 465
@@ -484,7 +470,7 @@ async fn test_query_states() {
484 for _ in 0..10 { 470 for _ in 0..10 {
485 if let Some(Ok(Message::Text(text))) = ws.next().await { 471 if let Some(Ok(Message::Text(text))) = ws.next().await {
486 let response: Value = serde_json::from_str(&text).expect("Failed to parse"); 472 let response: Value = serde_json::from_str(&text).expect("Failed to parse");
487 473
488 if response[0] == "EVENT" { 474 if response[0] == "EVENT" {
489 assert_eq!(response[1], "test-sub"); 475 assert_eq!(response[1], "test-sub");
490 found_event = true; 476 found_event = true;
@@ -506,21 +492,22 @@ async fn test_duplicate_announcement() {
506 let relay = TestRelay::start().await; 492 let relay = TestRelay::start().await;
507 let keys = Keys::generate(); 493 let keys = Keys::generate();
508 494
509 let (mut ws, _) = connect_async(relay.url()) 495 let (mut ws, _) = connect_async(relay.url()).await.expect("Failed to connect");
510 .await
511 .expect("Failed to connect");
512 496
513 let event = create_announcement( 497 let event = create_announcement(
514 &keys, 498 &keys,
515 &relay.domain(), 499 &relay.domain(),
516 "duplicate-test", 500 "duplicate-test",
517 vec![format!("https://{}/alice/duplicate-test.git", relay.domain())], 501 vec![format!(
502 "https://{}/alice/duplicate-test.git",
503 relay.domain()
504 )],
518 vec![format!("wss://{}", relay.domain())], 505 vec![format!("wss://{}", relay.domain())],
519 ); 506 );
520 507
521 // Send first time 508 // Send first time
522 let event_msg = json!(["EVENT", event]); 509 let event_msg = json!(["EVENT", event]);
523 ws.send(Message::Text(event_msg.to_string())) 510 ws.send(Message::Text(event_msg.to_string().into()))
524 .await 511 .await
525 .expect("Failed to send event"); 512 .expect("Failed to send event");
526 513
@@ -531,14 +518,14 @@ async fn test_duplicate_announcement() {
531 518
532 // Send second time (duplicate) 519 // Send second time (duplicate)
533 let event_msg = json!(["EVENT", event]); 520 let event_msg = json!(["EVENT", event]);
534 ws.send(Message::Text(event_msg.to_string())) 521 ws.send(Message::Text(event_msg.to_string().into()))
535 .await 522 .await
536 .expect("Failed to send event"); 523 .expect("Failed to send event");
537 524
538 if let Some(Ok(Message::Text(text))) = ws.next().await { 525 if let Some(Ok(Message::Text(text))) = ws.next().await {
539 let response2: Value = serde_json::from_str(&text).expect("Failed to parse"); 526 let response2: Value = serde_json::from_str(&text).expect("Failed to parse");
540 assert_eq!(response2[2], true, "Duplicate should be acknowledged"); 527 assert_eq!(response2[2], true, "Duplicate should be acknowledged");
541 528
542 let message = response2[3].as_str().unwrap(); 529 let message = response2[3].as_str().unwrap();
543 assert!( 530 assert!(
544 message.contains("duplicate") || message.is_empty(), 531 message.contains("duplicate") || message.is_empty(),