upleb.uk

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

summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.env.example2
-rw-r--r--Cargo.lock239
-rw-r--r--Cargo.toml1
-rw-r--r--src/git/authorization.rs12
-rw-r--r--src/git/handlers.rs7
-rw-r--r--src/http/mod.rs9
-rw-r--r--src/nostr/builder.rs56
7 files changed, 289 insertions, 37 deletions
diff --git a/.env.example b/.env.example
index 0a27bbb..796415e 100644
--- a/.env.example
+++ b/.env.example
@@ -17,7 +17,7 @@ NGIT_RELAY_DATA_PATH=./data/relay
17# Database backend (memory, nostrdb, lmdb) 17# Database backend (memory, nostrdb, lmdb)
18# - memory: In-memory database (default, fastest, no persistence) 18# - memory: In-memory database (default, fastest, no persistence)
19# - nostrdb: NostrDB backend (persistent, optimized for Nostr) [Not yet implemented] 19# - nostrdb: NostrDB backend (persistent, optimized for Nostr) [Not yet implemented]
20# - lmdb: LMDB backend (persistent, general purpose) [Not yet implemented] 20# - lmdb: LMDB backend (persistent, general purpose)
21NGIT_DATABASE_BACKEND=memory 21NGIT_DATABASE_BACKEND=memory
22 22
23# Server configuration 23# Server configuration
diff --git a/Cargo.lock b/Cargo.lock
index aa78542..6fcb65f 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -248,6 +248,9 @@ name = "bitflags"
248version = "2.10.0" 248version = "2.10.0"
249source = "registry+https://github.com/rust-lang/crates.io-index" 249source = "registry+https://github.com/rust-lang/crates.io-index"
250checksum = "812e12b5285cc515a9c72a5c1d3b6d46a19dac5acfef5265968c166106e31dd3" 250checksum = "812e12b5285cc515a9c72a5c1d3b6d46a19dac5acfef5265968c166106e31dd3"
251dependencies = [
252 "serde_core",
253]
251 254
252[[package]] 255[[package]]
253name = "block-buffer" 256name = "block-buffer"
@@ -274,6 +277,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
274checksum = "46c5e41b57b8bba42a04676d81cb89e9ee8e859a1a66f80a5a72e1cb76b34d43" 277checksum = "46c5e41b57b8bba42a04676d81cb89e9ee8e859a1a66f80a5a72e1cb76b34d43"
275 278
276[[package]] 279[[package]]
280name = "byteorder"
281version = "1.5.0"
282source = "registry+https://github.com/rust-lang/crates.io-index"
283checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b"
284
285[[package]]
277name = "bytes" 286name = "bytes"
278version = "1.10.1" 287version = "1.10.1"
279source = "registry+https://github.com/rust-lang/crates.io-index" 288source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -433,6 +442,21 @@ dependencies = [
433] 442]
434 443
435[[package]] 444[[package]]
445name = "crossbeam-queue"
446version = "0.3.12"
447source = "registry+https://github.com/rust-lang/crates.io-index"
448checksum = "0f58bbc28f91df819d0aa2a2c00cd19754769c2fad90579b3592b1c9ba7a3115"
449dependencies = [
450 "crossbeam-utils",
451]
452
453[[package]]
454name = "crossbeam-utils"
455version = "0.8.21"
456source = "registry+https://github.com/rust-lang/crates.io-index"
457checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28"
458
459[[package]]
436name = "crypto-common" 460name = "crypto-common"
437version = "0.1.6" 461version = "0.1.6"
438source = "registry+https://github.com/rust-lang/crates.io-index" 462source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -478,6 +502,15 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
478checksum = "1aaf95b3e5c8f23aa320147307562d361db0ae0d51242340f558153b4eb2439b" 502checksum = "1aaf95b3e5c8f23aa320147307562d361db0ae0d51242340f558153b4eb2439b"
479 503
480[[package]] 504[[package]]
505name = "doxygen-rs"
506version = "0.4.2"
507source = "registry+https://github.com/rust-lang/crates.io-index"
508checksum = "415b6ec780d34dcf624666747194393603d0373b7141eef01d12ee58881507d9"
509dependencies = [
510 "phf",
511]
512
513[[package]]
481name = "either" 514name = "either"
482version = "1.15.0" 515version = "1.15.0"
483source = "registry+https://github.com/rust-lang/crates.io-index" 516source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -521,6 +554,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
521checksum = "52051878f80a721bb68ebfbc930e07b65ba72f2da88968ea5c06fd6ca3d3a127" 554checksum = "52051878f80a721bb68ebfbc930e07b65ba72f2da88968ea5c06fd6ca3d3a127"
522 555
523[[package]] 556[[package]]
557name = "flatbuffers"
558version = "25.9.23"
559source = "registry+https://github.com/rust-lang/crates.io-index"
560checksum = "09b6620799e7340ebd9968d2e0708eb82cf1971e9a16821e2091b6d6e475eed5"
561dependencies = [
562 "bitflags 2.10.0",
563 "rustc_version",
564]
565
566[[package]]
524name = "flate2" 567name = "flate2"
525version = "1.1.5" 568version = "1.1.5"
526source = "registry+https://github.com/rust-lang/crates.io-index" 569source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -531,6 +574,18 @@ dependencies = [
531] 574]
532 575
533[[package]] 576[[package]]
577name = "flume"
578version = "0.11.1"
579source = "registry+https://github.com/rust-lang/crates.io-index"
580checksum = "da0e4dd2a88388a1f4ccc7c9ce104604dab68d9f408dc34cd45823d5a9069095"
581dependencies = [
582 "futures-core",
583 "futures-sink",
584 "nanorand",
585 "spin",
586]
587
588[[package]]
534name = "fnv" 589name = "fnv"
535version = "1.0.7" 590version = "1.0.7"
536source = "registry+https://github.com/rust-lang/crates.io-index" 591source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -769,6 +824,40 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
769checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" 824checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
770 825
771[[package]] 826[[package]]
827name = "heed"
828version = "0.20.5"
829source = "registry+https://github.com/rust-lang/crates.io-index"
830checksum = "7d4f449bab7320c56003d37732a917e18798e2f1709d80263face2b4f9436ddb"
831dependencies = [
832 "bitflags 2.10.0",
833 "byteorder",
834 "heed-traits",
835 "heed-types",
836 "libc",
837 "lmdb-master-sys",
838 "once_cell",
839 "page_size",
840 "synchronoise",
841 "url",
842]
843
844[[package]]
845name = "heed-traits"
846version = "0.20.0"
847source = "registry+https://github.com/rust-lang/crates.io-index"
848checksum = "eb3130048d404c57ce5a1ac61a903696e8fcde7e8c2991e9fcfc1f27c3ef74ff"
849
850[[package]]
851name = "heed-types"
852version = "0.20.1"
853source = "registry+https://github.com/rust-lang/crates.io-index"
854checksum = "9d3f528b053a6d700b2734eabcd0fd49cb8230647aa72958467527b0b7917114"
855dependencies = [
856 "byteorder",
857 "heed-traits",
858]
859
860[[package]]
772name = "hex" 861name = "hex"
773version = "0.4.3" 862version = "0.4.3"
774source = "registry+https://github.com/rust-lang/crates.io-index" 863source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1152,6 +1241,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
1152checksum = "6373607a59f0be73a39b6fe456b8192fcc3585f602af20751600e974dd455e77" 1241checksum = "6373607a59f0be73a39b6fe456b8192fcc3585f602af20751600e974dd455e77"
1153 1242
1154[[package]] 1243[[package]]
1244name = "lmdb-master-sys"
1245version = "0.2.5"
1246source = "registry+https://github.com/rust-lang/crates.io-index"
1247checksum = "864808e0b19fb6dd3b70ba94ee671b82fce17554cf80aeb0a155c65bb08027df"
1248dependencies = [
1249 "cc",
1250 "doxygen-rs",
1251 "libc",
1252]
1253
1254[[package]]
1155name = "lock_api" 1255name = "lock_api"
1156version = "0.4.14" 1256version = "0.4.14"
1157source = "registry+https://github.com/rust-lang/crates.io-index" 1257source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1215,6 +1315,15 @@ dependencies = [
1215] 1315]
1216 1316
1217[[package]] 1317[[package]]
1318name = "nanorand"
1319version = "0.7.0"
1320source = "registry+https://github.com/rust-lang/crates.io-index"
1321checksum = "6a51313c5820b0b02bd422f4b44776fbf47961755c74ce64afc73bfad10226c3"
1322dependencies = [
1323 "getrandom 0.2.16",
1324]
1325
1326[[package]]
1218name = "native-tls" 1327name = "native-tls"
1219version = "0.2.14" 1328version = "0.2.14"
1220source = "registry+https://github.com/rust-lang/crates.io-index" 1329source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1250,6 +1359,7 @@ dependencies = [
1250 "http-body-util", 1359 "http-body-util",
1251 "hyper 1.8.1", 1360 "hyper 1.8.1",
1252 "hyper-util", 1361 "hyper-util",
1362 "nostr-lmdb",
1253 "nostr-relay-builder", 1363 "nostr-relay-builder",
1254 "nostr-sdk 0.44.1", 1364 "nostr-sdk 0.44.1",
1255 "serde", 1365 "serde",
@@ -1327,6 +1437,7 @@ version = "0.44.0"
1327source = "registry+https://github.com/rust-lang/crates.io-index" 1437source = "registry+https://github.com/rust-lang/crates.io-index"
1328checksum = "7462c9d8ae5ef6a28d66a192d399ad2530f1f2130b13186296dbb11bdef5b3d1" 1438checksum = "7462c9d8ae5ef6a28d66a192d399ad2530f1f2130b13186296dbb11bdef5b3d1"
1329dependencies = [ 1439dependencies = [
1440 "flatbuffers",
1330 "lru", 1441 "lru",
1331 "nostr 0.44.1", 1442 "nostr 0.44.1",
1332 "tokio", 1443 "tokio",
@@ -1342,6 +1453,21 @@ dependencies = [
1342] 1453]
1343 1454
1344[[package]] 1455[[package]]
1456name = "nostr-lmdb"
1457version = "0.44.0"
1458source = "registry+https://github.com/rust-lang/crates.io-index"
1459checksum = "1201bcf1f900c352f9f2cea5249960dc6b23049b65699a516e1327243becf6a2"
1460dependencies = [
1461 "async-utility",
1462 "flume",
1463 "heed",
1464 "nostr 0.44.1",
1465 "nostr-database 0.44.0",
1466 "tokio",
1467 "tracing",
1468]
1469
1470[[package]]
1345name = "nostr-relay-builder" 1471name = "nostr-relay-builder"
1346version = "0.44.0" 1472version = "0.44.0"
1347source = "registry+https://github.com/rust-lang/crates.io-index" 1473source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1502,6 +1628,16 @@ dependencies = [
1502] 1628]
1503 1629
1504[[package]] 1630[[package]]
1631name = "page_size"
1632version = "0.6.0"
1633source = "registry+https://github.com/rust-lang/crates.io-index"
1634checksum = "30d5b2194ed13191c1999ae0704b7839fb18384fa22e49b57eeaa97d79ce40da"
1635dependencies = [
1636 "libc",
1637 "winapi",
1638]
1639
1640[[package]]
1505name = "parking_lot" 1641name = "parking_lot"
1506version = "0.12.5" 1642version = "0.12.5"
1507source = "registry+https://github.com/rust-lang/crates.io-index" 1643source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1552,6 +1688,48 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
1552checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" 1688checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220"
1553 1689
1554[[package]] 1690[[package]]
1691name = "phf"
1692version = "0.11.3"
1693source = "registry+https://github.com/rust-lang/crates.io-index"
1694checksum = "1fd6780a80ae0c52cc120a26a1a42c1ae51b247a253e4e06113d23d2c2edd078"
1695dependencies = [
1696 "phf_macros",
1697 "phf_shared",
1698]
1699
1700[[package]]
1701name = "phf_generator"
1702version = "0.11.3"
1703source = "registry+https://github.com/rust-lang/crates.io-index"
1704checksum = "3c80231409c20246a13fddb31776fb942c38553c51e871f8cbd687a4cfb5843d"
1705dependencies = [
1706 "phf_shared",
1707 "rand 0.8.5",
1708]
1709
1710[[package]]
1711name = "phf_macros"
1712version = "0.11.3"
1713source = "registry+https://github.com/rust-lang/crates.io-index"
1714checksum = "f84ac04429c13a7ff43785d75ad27569f2951ce0ffd30a3321230db2fc727216"
1715dependencies = [
1716 "phf_generator",
1717 "phf_shared",
1718 "proc-macro2",
1719 "quote",
1720 "syn",
1721]
1722
1723[[package]]
1724name = "phf_shared"
1725version = "0.11.3"
1726source = "registry+https://github.com/rust-lang/crates.io-index"
1727checksum = "67eabc2ef2a60eb7faa00097bd1ffdb5bd28e62bf39990626a582201b7a754e5"
1728dependencies = [
1729 "siphasher",
1730]
1731
1732[[package]]
1555name = "pin-project-lite" 1733name = "pin-project-lite"
1556version = "0.2.16" 1734version = "0.2.16"
1557source = "registry+https://github.com/rust-lang/crates.io-index" 1735source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1774,6 +1952,15 @@ dependencies = [
1774] 1952]
1775 1953
1776[[package]] 1954[[package]]
1955name = "rustc_version"
1956version = "0.4.1"
1957source = "registry+https://github.com/rust-lang/crates.io-index"
1958checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92"
1959dependencies = [
1960 "semver",
1961]
1962
1963[[package]]
1777name = "rustix" 1964name = "rustix"
1778version = "1.1.2" 1965version = "1.1.2"
1779source = "registry+https://github.com/rust-lang/crates.io-index" 1966source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1921,6 +2108,12 @@ dependencies = [
1921] 2108]
1922 2109
1923[[package]] 2110[[package]]
2111name = "semver"
2112version = "1.0.27"
2113source = "registry+https://github.com/rust-lang/crates.io-index"
2114checksum = "d767eb0aabc880b29956c35734170f26ed551a859dbd361d140cdbeca61ab1e2"
2115
2116[[package]]
1924name = "serde" 2117name = "serde"
1925version = "1.0.228" 2118version = "1.0.228"
1926source = "registry+https://github.com/rust-lang/crates.io-index" 2119source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -2028,6 +2221,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
2028checksum = "d66dc143e6b11c1eddc06d5c423cfc97062865baf299914ab64caa38182078fe" 2221checksum = "d66dc143e6b11c1eddc06d5c423cfc97062865baf299914ab64caa38182078fe"
2029 2222
2030[[package]] 2223[[package]]
2224name = "siphasher"
2225version = "1.0.1"
2226source = "registry+https://github.com/rust-lang/crates.io-index"
2227checksum = "56199f7ddabf13fe5074ce809e7d3f42b42ae711800501b5b16ea82ad029c39d"
2228
2229[[package]]
2031name = "slab" 2230name = "slab"
2032version = "0.4.11" 2231version = "0.4.11"
2033source = "registry+https://github.com/rust-lang/crates.io-index" 2232source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -2060,6 +2259,15 @@ dependencies = [
2060] 2259]
2061 2260
2062[[package]] 2261[[package]]
2262name = "spin"
2263version = "0.9.8"
2264source = "registry+https://github.com/rust-lang/crates.io-index"
2265checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67"
2266dependencies = [
2267 "lock_api",
2268]
2269
2270[[package]]
2063name = "stable_deref_trait" 2271name = "stable_deref_trait"
2064version = "1.2.1" 2272version = "1.2.1"
2065source = "registry+https://github.com/rust-lang/crates.io-index" 2273source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -2095,6 +2303,15 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
2095checksum = "2047c6ded9c721764247e62cd3b03c09ffc529b2ba5b10ec482ae507a4a70160" 2303checksum = "2047c6ded9c721764247e62cd3b03c09ffc529b2ba5b10ec482ae507a4a70160"
2096 2304
2097[[package]] 2305[[package]]
2306name = "synchronoise"
2307version = "1.0.1"
2308source = "registry+https://github.com/rust-lang/crates.io-index"
2309checksum = "3dbc01390fc626ce8d1cffe3376ded2b72a11bb70e1c75f404a210e4daa4def2"
2310dependencies = [
2311 "crossbeam-queue",
2312]
2313
2314[[package]]
2098name = "synstructure" 2315name = "synstructure"
2099version = "0.13.2" 2316version = "0.13.2"
2100source = "registry+https://github.com/rust-lang/crates.io-index" 2317source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -2625,6 +2842,28 @@ dependencies = [
2625] 2842]
2626 2843
2627[[package]] 2844[[package]]
2845name = "winapi"
2846version = "0.3.9"
2847source = "registry+https://github.com/rust-lang/crates.io-index"
2848checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
2849dependencies = [
2850 "winapi-i686-pc-windows-gnu",
2851 "winapi-x86_64-pc-windows-gnu",
2852]
2853
2854[[package]]
2855name = "winapi-i686-pc-windows-gnu"
2856version = "0.4.0"
2857source = "registry+https://github.com/rust-lang/crates.io-index"
2858checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
2859
2860[[package]]
2861name = "winapi-x86_64-pc-windows-gnu"
2862version = "0.4.0"
2863source = "registry+https://github.com/rust-lang/crates.io-index"
2864checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
2865
2866[[package]]
2628name = "windows-core" 2867name = "windows-core"
2629version = "0.62.2" 2868version = "0.62.2"
2630source = "registry+https://github.com/rust-lang/crates.io-index" 2869source = "registry+https://github.com/rust-lang/crates.io-index"
diff --git a/Cargo.toml b/Cargo.toml
index 3e836f9..02c66ac 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -21,6 +21,7 @@ nostr-relay-builder = "0.44"
21 21
22# Nostr 22# Nostr
23nostr-sdk = "0.44" 23nostr-sdk = "0.44"
24nostr-lmdb = "0.44"
24 25
25# Utilities 26# Utilities
26futures-util = "0.3" 27futures-util = "0.3"
diff --git a/src/git/authorization.rs b/src/git/authorization.rs
index 3b0e759..4896fc0 100644
--- a/src/git/authorization.rs
+++ b/src/git/authorization.rs
@@ -31,9 +31,9 @@ use anyhow::{anyhow, Result};
31use nostr_relay_builder::prelude::*; 31use nostr_relay_builder::prelude::*;
32use nostr_sdk::{EventId, ToBech32}; 32use nostr_sdk::{EventId, ToBech32};
33use std::collections::{HashMap, HashSet}; 33use std::collections::{HashMap, HashSet};
34use std::sync::Arc;
35use tracing::debug; 34use tracing::debug;
36 35
36use crate::nostr::builder::SharedDatabase;
37use crate::nostr::events::{ 37use crate::nostr::events::{
38 RepositoryAnnouncement, RepositoryState, KIND_PR, KIND_PR_UPDATE, KIND_REPOSITORY_ANNOUNCEMENT, 38 RepositoryAnnouncement, RepositoryState, KIND_PR, KIND_PR_UPDATE, KIND_REPOSITORY_ANNOUNCEMENT,
39 KIND_REPOSITORY_STATE, 39 KIND_REPOSITORY_STATE,
@@ -56,7 +56,7 @@ pub struct RepositoryData {
56/// This performs a single database query to fetch both announcement and state events, 56/// This performs a single database query to fetch both announcement and state events,
57/// which is more efficient than separate queries. 57/// which is more efficient than separate queries.
58pub async fn fetch_repository_data( 58pub async fn fetch_repository_data(
59 database: &Arc<MemoryDatabase>, 59 database: &SharedDatabase,
60 identifier: &str, 60 identifier: &str,
61) -> Result<RepositoryData> { 61) -> Result<RepositoryData> {
62 let filter = Filter::new() 62 let filter = Filter::new()
@@ -284,7 +284,7 @@ pub fn is_latest_state(
284/// 284///
285/// Returns an `AuthorizationResult` that indicates whether a push is authorized. 285/// Returns an `AuthorizationResult` that indicates whether a push is authorized.
286pub async fn get_authorization_from_db( 286pub async fn get_authorization_from_db(
287 database: &Arc<MemoryDatabase>, 287 database: &SharedDatabase,
288 identifier: &str, 288 identifier: &str,
289) -> Result<AuthorizationResult> { 289) -> Result<AuthorizationResult> {
290 // Fetch all repository data with a single query 290 // Fetch all repository data with a single query
@@ -340,7 +340,7 @@ pub async fn get_authorization_from_db(
340/// 340///
341/// Returns an `AuthorizationResult` that indicates whether a push is authorized. 341/// Returns an `AuthorizationResult` that indicates whether a push is authorized.
342pub async fn get_authorization_for_owner( 342pub async fn get_authorization_for_owner(
343 database: &Arc<MemoryDatabase>, 343 database: &SharedDatabase,
344 identifier: &str, 344 identifier: &str,
345 owner_pubkey: &str, 345 owner_pubkey: &str,
346) -> Result<AuthorizationResult> { 346) -> Result<AuthorizationResult> {
@@ -817,7 +817,7 @@ pub fn npub_to_pubkey(npub: &str) -> Result<String> {
817/// - `Ok(None)` if the event doesn't exist (push should be allowed) 817/// - `Ok(None)` if the event doesn't exist (push should be allowed)
818/// - `Err(_)` on database errors 818/// - `Err(_)` on database errors
819pub async fn get_event_commit_tag( 819pub async fn get_event_commit_tag(
820 database: &Arc<MemoryDatabase>, 820 database: &SharedDatabase,
821 event_id: &EventId, 821 event_id: &EventId,
822) -> Result<Option<String>> { 822) -> Result<Option<String>> {
823 // Query for PR (1618) and PR Update (1619) events with this ID 823 // Query for PR (1618) and PR Update (1619) events with this ID
@@ -872,7 +872,7 @@ pub async fn get_event_commit_tag(
872/// * `Ok(())` if all refs/nostr/ pushes are valid 872/// * `Ok(())` if all refs/nostr/ pushes are valid
873/// * `Err(_)` if any ref has invalid event ID format or fails commit validation 873/// * `Err(_)` if any ref has invalid event ID format or fails commit validation
874pub async fn validate_nostr_ref_pushes( 874pub async fn validate_nostr_ref_pushes(
875 database: &Arc<MemoryDatabase>, 875 database: &SharedDatabase,
876 pushed_refs: &[(String, String, String)], 876 pushed_refs: &[(String, String, String)],
877) -> Result<()> { 877) -> Result<()> {
878 for (_, new_oid, ref_name) in pushed_refs { 878 for (_, new_oid, ref_name) in pushed_refs {
diff --git a/src/git/handlers.rs b/src/git/handlers.rs
index e84cabb..8e5f5e1 100644
--- a/src/git/handlers.rs
+++ b/src/git/handlers.rs
@@ -4,9 +4,7 @@
4 4
5use http_body_util::Full; 5use http_body_util::Full;
6use hyper::{body::Bytes, Response, StatusCode}; 6use hyper::{body::Bytes, Response, StatusCode};
7use nostr_relay_builder::prelude::MemoryDatabase;
8use std::path::PathBuf; 7use std::path::PathBuf;
9use std::sync::Arc;
10use tokio::io::{AsyncReadExt, AsyncWriteExt}; 8use tokio::io::{AsyncReadExt, AsyncWriteExt};
11use tracing::{debug, error, info, warn}; 9use tracing::{debug, error, info, warn};
12 10
@@ -18,6 +16,7 @@ use super::protocol::{GitService, PktLine};
18use super::subprocess::GitSubprocess; 16use super::subprocess::GitSubprocess;
19use super::try_set_head_if_available; 17use super::try_set_head_if_available;
20 18
19use crate::nostr::builder::SharedDatabase;
21use crate::nostr::events::RepositoryState; 20use crate::nostr::events::RepositoryState;
22 21
23/// Handle GET /info/refs?service=git-{upload,receive}-pack 22/// Handle GET /info/refs?service=git-{upload,receive}-pack
@@ -178,7 +177,7 @@ pub async fn handle_upload_pack(
178pub async fn handle_receive_pack( 177pub async fn handle_receive_pack(
179 repo_path: PathBuf, 178 repo_path: PathBuf,
180 request_body: Bytes, 179 request_body: Bytes,
181 database: Option<Arc<MemoryDatabase>>, 180 database: Option<SharedDatabase>,
182 identifier: &str, 181 identifier: &str,
183 owner_pubkey: &str, 182 owner_pubkey: &str,
184) -> Result<Response<Full<Bytes>>, GitError> { 183) -> Result<Response<Full<Bytes>>, GitError> {
@@ -310,7 +309,7 @@ pub async fn handle_receive_pack(
310/// 5. Validates that pushed refs match the state 309/// 5. Validates that pushed refs match the state
311/// 6. Validates refs/nostr/<event-id> has valid event id and if event exists, `c` tag matches ref 310/// 6. Validates refs/nostr/<event-id> has valid event id and if event exists, `c` tag matches ref
312async fn authorize_push( 311async fn authorize_push(
313 database: &Arc<MemoryDatabase>, 312 database: &SharedDatabase,
314 identifier: &str, 313 identifier: &str,
315 owner_pubkey: &str, 314 owner_pubkey: &str,
316 request_body: &Bytes, 315 request_body: &Bytes,
diff --git a/src/http/mod.rs b/src/http/mod.rs
index 5cf8dbe..4665281 100644
--- a/src/http/mod.rs
+++ b/src/http/mod.rs
@@ -7,7 +7,6 @@ pub mod nip11;
7use std::future::Future; 7use std::future::Future;
8use std::net::SocketAddr; 8use std::net::SocketAddr;
9use std::pin::Pin; 9use std::pin::Pin;
10use std::sync::Arc;
11 10
12use base64::Engine; 11use base64::Engine;
13use http_body_util::{BodyExt, Full}; 12use http_body_util::{BodyExt, Full};
@@ -17,7 +16,6 @@ use hyper::server::conn::http1;
17use hyper::service::Service; 16use hyper::service::Service;
18use hyper::{Method, Request, Response}; 17use hyper::{Method, Request, Response};
19use hyper_util::rt::TokioIo; 18use hyper_util::rt::TokioIo;
20use nostr_relay_builder::prelude::MemoryDatabase;
21use nostr_relay_builder::LocalRelay; 19use nostr_relay_builder::LocalRelay;
22use nostr_sdk::hashes::sha1::Hash as Sha1Hash; 20use nostr_sdk::hashes::sha1::Hash as Sha1Hash;
23use nostr_sdk::hashes::{Hash, HashEngine}; 21use nostr_sdk::hashes::{Hash, HashEngine};
@@ -26,6 +24,7 @@ use tokio::net::TcpListener;
26 24
27use crate::config::Config; 25use crate::config::Config;
28use crate::git; 26use crate::git;
27use crate::nostr::builder::SharedDatabase;
29 28
30/// CORS headers required by GRASP-01 specification (lines 40-47) 29/// CORS headers required by GRASP-01 specification (lines 40-47)
31const CORS_ALLOW_ORIGIN: &str = "*"; 30const CORS_ALLOW_ORIGIN: &str = "*";
@@ -90,7 +89,7 @@ struct HttpService {
90 config: Config, 89 config: Config,
91 remote: SocketAddr, 90 remote: SocketAddr,
92 /// Database reference for direct queries (e.g., push authorization) 91 /// Database reference for direct queries (e.g., push authorization)
93 database: Arc<MemoryDatabase>, 92 database: SharedDatabase,
94} 93}
95 94
96impl HttpService { 95impl HttpService {
@@ -98,7 +97,7 @@ impl HttpService {
98 relay: LocalRelay, 97 relay: LocalRelay,
99 config: Config, 98 config: Config,
100 remote: SocketAddr, 99 remote: SocketAddr,
101 database: Arc<MemoryDatabase>, 100 database: SharedDatabase,
102 ) -> Self { 101 ) -> Self {
103 Self { 102 Self {
104 relay, 103 relay,
@@ -423,7 +422,7 @@ fn derive_accept_key(request_key: &[u8]) -> String {
423pub async fn run_server( 422pub async fn run_server(
424 config: Config, 423 config: Config,
425 relay: LocalRelay, 424 relay: LocalRelay,
426 database: Arc<MemoryDatabase>, 425 database: SharedDatabase,
427) -> anyhow::Result<()> { 426) -> anyhow::Result<()> {
428 let bind_addr: SocketAddr = config.bind_address.parse()?; 427 let bind_addr: SocketAddr = config.bind_address.parse()?;
429 428
diff --git a/src/nostr/builder.rs b/src/nostr/builder.rs
index 2f182ea..eabb38f 100644
--- a/src/nostr/builder.rs
+++ b/src/nostr/builder.rs
@@ -9,6 +9,7 @@ use std::sync::Arc;
9use nostr::nips::nip19::ToBech32; 9use nostr::nips::nip19::ToBech32;
10use nostr::prelude::{Alphabet, SingleLetterTag}; 10use nostr::prelude::{Alphabet, SingleLetterTag};
11use nostr::{EventId, Filter, Kind, PublicKey}; 11use nostr::{EventId, Filter, Kind, PublicKey};
12use nostr_lmdb::NostrLMDB;
12use nostr_relay_builder::prelude::*; 13use nostr_relay_builder::prelude::*;
13 14
14use crate::config::{Config, DatabaseBackend}; 15use crate::config::{Config, DatabaseBackend};
@@ -18,6 +19,9 @@ use crate::nostr::events::{
18 KIND_PR_UPDATE, KIND_REPOSITORY_ANNOUNCEMENT, KIND_REPOSITORY_STATE, 19 KIND_PR_UPDATE, KIND_REPOSITORY_ANNOUNCEMENT, KIND_REPOSITORY_STATE,
19}; 20};
20 21
22/// Type alias for the shared database used by the relay
23pub type SharedDatabase = Arc<dyn NostrDatabase>;
24
21/// Result of aligning a repository with authorized state 25/// Result of aligning a repository with authorized state
22#[derive(Debug, Default)] 26#[derive(Debug, Default)]
23struct AlignmentResult { 27struct AlignmentResult {
@@ -35,23 +39,33 @@ struct AlignmentResult {
35/// 39///
36/// Validates all events according to GRASP-01 specification: 40/// Validates all events according to GRASP-01 specification:
37/// - Repository announcements must list service in clone and relays tags 41/// - Repository announcements must list service in clone and relays tags
38/// - Repository state announcements must have valid structure 42/// - Repository state announcements must have valid structure
39/// - Other events must reference accepted repositories or events 43/// - Other events must reference accepted repositories or events
40/// - Forward references are supported (events referenced by accepted events) 44/// - Forward references are supported (events referenced by accepted events)
41/// - Orphan events with no valid references are rejected 45/// - Orphan events with no valid references are rejected
42/// 46///
43/// Uses stateful database queries to check event relationships. 47/// Uses stateful database queries to check event relationships.
44#[derive(Debug, Clone)] 48#[derive(Clone)]
45pub struct Nip34WritePolicy { 49pub struct Nip34WritePolicy {
46 domain: String, 50 domain: String,
47 database: Arc<MemoryDatabase>, 51 database: SharedDatabase,
48 git_data_path: PathBuf, 52 git_data_path: PathBuf,
49} 53}
50 54
55impl std::fmt::Debug for Nip34WritePolicy {
56 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
57 f.debug_struct("Nip34WritePolicy")
58 .field("domain", &self.domain)
59 .field("git_data_path", &self.git_data_path)
60 .field("database", &"<database>")
61 .finish()
62 }
63}
64
51impl Nip34WritePolicy { 65impl Nip34WritePolicy {
52 pub fn new( 66 pub fn new(
53 domain: impl Into<String>, 67 domain: impl Into<String>,
54 database: Arc<MemoryDatabase>, 68 database: SharedDatabase,
55 git_data_path: impl Into<PathBuf>, 69 git_data_path: impl Into<PathBuf>,
56 ) -> Self { 70 ) -> Self {
57 Self { 71 Self {
@@ -104,7 +118,7 @@ impl Nip34WritePolicy {
104 /// The authorized_pubkeys should be the owner and maintainers of a specific 118 /// The authorized_pubkeys should be the owner and maintainers of a specific
105 /// announcement, so different owners with the same identifier don't interfere. 119 /// announcement, so different owners with the same identifier don't interfere.
106 async fn is_latest_state_for_identifier( 120 async fn is_latest_state_for_identifier(
107 database: &Arc<MemoryDatabase>, 121 database: &SharedDatabase,
108 state: &RepositoryState, 122 state: &RepositoryState,
109 authorized_pubkeys: &[PublicKey], 123 authorized_pubkeys: &[PublicKey],
110 ) -> Result<bool, String> { 124 ) -> Result<bool, String> {
@@ -155,7 +169,7 @@ impl Nip34WritePolicy {
155 /// should update HEAD in the repository of the announcement owner, 169 /// should update HEAD in the repository of the announcement owner,
156 /// not in the maintainer's own (possibly non-existent) repository. 170 /// not in the maintainer's own (possibly non-existent) repository.
157 async fn find_authorized_announcements( 171 async fn find_authorized_announcements(
158 database: &Arc<MemoryDatabase>, 172 database: &SharedDatabase,
159 identifier: &str, 173 identifier: &str,
160 state_author: &PublicKey, 174 state_author: &PublicKey,
161 ) -> Result<Vec<RepositoryAnnouncement>, String> { 175 ) -> Result<Vec<RepositoryAnnouncement>, String> {
@@ -205,7 +219,7 @@ impl Nip34WritePolicy {
205 /// - This state event is the latest for the identifier in that context 219 /// - This state event is the latest for the identifier in that context
206 async fn identify_owner_repositories( 220 async fn identify_owner_repositories(
207 &self, 221 &self,
208 database: &Arc<MemoryDatabase>, 222 database: &SharedDatabase,
209 state: &RepositoryState, 223 state: &RepositoryState,
210 ) -> Result<Vec<(RepositoryAnnouncement, std::path::PathBuf)>, String> { 224 ) -> Result<Vec<(RepositoryAnnouncement, std::path::PathBuf)>, String> {
211 // Find all announcements where state author is authorized 225 // Find all announcements where state author is authorized
@@ -485,7 +499,7 @@ impl Nip34WritePolicy {
485 /// Ok(Some(n)) if n refs were deleted, Ok(None) if no action taken, Err on failure 499 /// Ok(Some(n)) if n refs were deleted, Ok(None) if no action taken, Err on failure
486 async fn validate_pr_nostr_ref( 500 async fn validate_pr_nostr_ref(
487 &self, 501 &self,
488 database: &Arc<MemoryDatabase>, 502 database: &SharedDatabase,
489 event: &Event, 503 event: &Event,
490 ) -> Result<Option<usize>, String> { 504 ) -> Result<Option<usize>, String> {
491 let event_id = event.id.to_hex(); 505 let event_id = event.id.to_hex();
@@ -651,7 +665,7 @@ impl Nip34WritePolicy {
651 /// Check if any addressable events (repositories) exist in database 665 /// Check if any addressable events (repositories) exist in database
652 /// Returns the first matching addressable reference found, or None if none match 666 /// Returns the first matching addressable reference found, or None if none match
653 async fn find_accepted_repository( 667 async fn find_accepted_repository(
654 database: &Arc<MemoryDatabase>, 668 database: &SharedDatabase,
655 addressables: &[String], 669 addressables: &[String],
656 ) -> Result<Option<String>, String> { 670 ) -> Result<Option<String>, String> {
657 if addressables.is_empty() { 671 if addressables.is_empty() {
@@ -724,7 +738,7 @@ impl Nip34WritePolicy {
724 /// Check if any events exist in database 738 /// Check if any events exist in database
725 /// Returns the first matching event ID found, or None if none match 739 /// Returns the first matching event ID found, or None if none match
726 async fn find_accepted_event( 740 async fn find_accepted_event(
727 database: &Arc<MemoryDatabase>, 741 database: &SharedDatabase,
728 event_ids: &[EventId], 742 event_ids: &[EventId],
729 ) -> Result<Option<EventId>, String> { 743 ) -> Result<Option<EventId>, String> {
730 if event_ids.is_empty() { 744 if event_ids.is_empty() {
@@ -752,7 +766,7 @@ impl Nip34WritePolicy {
752 /// This optimization recognizes that replaceable events are referenced by coordinate address, 766 /// This optimization recognizes that replaceable events are referenced by coordinate address,
753 /// while regular events are referenced by event ID. 767 /// while regular events are referenced by event ID.
754 async fn is_referenced_by_accepted( 768 async fn is_referenced_by_accepted(
755 database: &Arc<MemoryDatabase>, 769 database: &SharedDatabase,
756 event: &Event, 770 event: &Event,
757 ) -> Result<bool, String> { 771 ) -> Result<bool, String> {
758 let kind_u16 = event.kind.as_u16(); 772 let kind_u16 = event.kind.as_u16();
@@ -1152,14 +1166,14 @@ pub struct RelayWithDatabase {
1152 /// The local relay instance 1166 /// The local relay instance
1153 pub relay: LocalRelay, 1167 pub relay: LocalRelay,
1154 /// The database Arc that can be used for direct queries 1168 /// The database Arc that can be used for direct queries
1155 pub database: Arc<MemoryDatabase>, 1169 pub database: SharedDatabase,
1156} 1170}
1157 1171
1158/// Create a configured LocalRelay with full GRASP-01 validation 1172/// Create a configured LocalRelay with full GRASP-01 validation
1159/// 1173///
1160/// Returns a `RelayWithDatabase` struct containing: 1174/// Returns a `RelayWithDatabase` struct containing:
1161/// - The `LocalRelay` for handling WebSocket connections 1175/// - The `LocalRelay` for handling WebSocket connections
1162/// - The `Arc<MemoryDatabase>` for direct database queries (e.g., push authorization) 1176/// - The `SharedDatabase` for direct database queries (e.g., push authorization)
1163pub fn create_relay(config: &Config) -> Result<RelayWithDatabase> { 1177pub fn create_relay(config: &Config) -> Result<RelayWithDatabase> {
1164 tracing::info!("Configuring nostr relay with GRASP-01 validation..."); 1178 tracing::info!("Configuring nostr relay with GRASP-01 validation...");
1165 1179
@@ -1167,7 +1181,7 @@ pub fn create_relay(config: &Config) -> Result<RelayWithDatabase> {
1167 let db_path = Path::new(&config.relay_data_path); 1181 let db_path = Path::new(&config.relay_data_path);
1168 1182
1169 // Create database based on configuration 1183 // Create database based on configuration
1170 let database = match config.database_backend { 1184 let database: SharedDatabase = match config.database_backend {
1171 DatabaseBackend::Memory => { 1185 DatabaseBackend::Memory => {
1172 tracing::info!("Using in-memory database (no persistence)"); 1186 tracing::info!("Using in-memory database (no persistence)");
1173 Arc::new(MemoryDatabase::with_opts(MemoryDatabaseOptions { 1187 Arc::new(MemoryDatabase::with_opts(MemoryDatabaseOptions {
@@ -1187,13 +1201,13 @@ pub fn create_relay(config: &Config) -> Result<RelayWithDatabase> {
1187 } 1201 }
1188 DatabaseBackend::Lmdb => { 1202 DatabaseBackend::Lmdb => {
1189 tracing::info!("Using LMDB backend at: {}", db_path.display()); 1203 tracing::info!("Using LMDB backend at: {}", db_path.display());
1190 // TODO: Implement LMDB backend once nostr-relay-builder supports it 1204 // Ensure the database directory exists
1191 // For now, fall back to memory database 1205 std::fs::create_dir_all(db_path).map_err(|e| {
1192 tracing::warn!("LMDB backend not yet implemented, using in-memory database"); 1206 anyhow::anyhow!("Failed to create LMDB directory {}: {}", db_path.display(), e)
1193 Arc::new(MemoryDatabase::with_opts(MemoryDatabaseOptions { 1207 })?;
1194 events: true, 1208 Arc::new(NostrLMDB::open(db_path).map_err(|e| {
1195 max_events: Some(100_000), 1209 anyhow::anyhow!("Failed to open LMDB database at {}: {}", db_path.display(), e)
1196 })) 1210 })?)
1197 } 1211 }
1198 }; 1212 };
1199 1213