From eed82a3c6f26d969123027fca703711dbd4d3593 Mon Sep 17 00:00:00 2001 From: Przemyslaw Olszewski Date: Sun, 30 Aug 2026 21:58:22 +0200 Subject: [PATCH] feat: Add working workspace collaboration --- diff --git a/Cargo.lock b/Cargo.lock --- a/Cargo.lock +++ b/Cargo.lock @@ -1,3420 +1,3551 @@ -# This file is automatically @generated by Cargo. -# It is not intended for manual editing. -version = 4 - -[[package]] -name = "Inflector" -version = "0.11.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fe438c63458706e03479442743baae6c88256498e6431708f6dfc520a26515d3" - -[[package]] -name = "aho-corasick" -version = "1.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c982642fa9e8606056828ee9a8505737230110bb1099153c79efe865c59d12ba" -dependencies = [ - "memchr", -] - -[[package]] -name = "allocator-api2" -version = "0.2.21" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" - -[[package]] -name = "anstream" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "824a212faf96e9acacdbd09febd34438f8f711fb84e09a8916013cd7815ca28d" -dependencies = [ - "anstyle", - "anstyle-parse", - "anstyle-query", - "anstyle-wincon", - "colorchoice", - "is_terminal_polyfill", - "utf8parse", -] - -[[package]] -name = "anstyle" -version = "1.0.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "940b3a0ca603d1eade50a4846a2afffd5ef57a9feac2c0e2ec2e14f9ead76000" - -[[package]] -name = "anstyle-parse" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52ce7f38b242319f7cabaa6813055467063ecdc9d355bbb4ce0c68908cd8130e" -dependencies = [ - "utf8parse", -] - -[[package]] -name = "anstyle-query" -version = "1.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc" -dependencies = [ - "windows-sys 0.61.2", -] - -[[package]] -name = "anstyle-wincon" -version = "3.0.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d" -dependencies = [ - "anstyle", - "once_cell_polyfill", - "windows-sys 0.61.2", -] - -[[package]] -name = "anyhow" -version = "1.0.104" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "330a5ed07fa54e4702c9d6c4174f74427fc0ef6e214bbd677ae50a5099946470" - -[[package]] -name = "ascii_utils" -version = "0.9.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "71938f30533e4d95a6d17aa530939da3842c2ab6f4f84b9dae68447e4129f74a" - -[[package]] -name = "async-graphql" -version = "7.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1057a9f7ccf2404d94571dec3451ade1cb524790df6f1ada0d19c2a49f6b0f40" -dependencies = [ - "async-graphql-derive", - "async-graphql-parser", - "async-graphql-value", - "async-io", - "async-trait", - "asynk-strim", - "base64", - "bytes", - "fast_chemail", - "fnv", - "futures-util", - "handlebars", - "http", - "indexmap", - "mime", - "multer", - "num-traits", - "pin-project-lite", - "regex", - "serde", - "serde_json", - "serde_urlencoded", - "static_assertions_next", - "tempfile", - "thiserror", -] - -[[package]] -name = "async-graphql-axum" -version = "7.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1e37c5532e4b686acf45e7162bc93da91fc2c702fb0d465efc2c20c8f973795" -dependencies = [ - "async-graphql", - "axum", - "bytes", - "futures-util", - "serde_json", - "tokio", - "tokio-stream", - "tokio-util", - "tower-service", -] - -[[package]] -name = "async-graphql-derive" -version = "7.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2e6cbeadc8515e66450fba0985ce722192e28443697799988265d86304d7cc68" -dependencies = [ - "Inflector", - "async-graphql-parser", - "darling 0.23.0", - "proc-macro-crate", - "proc-macro2", - "quote", - "strum", - "syn 2.0.119", - "thiserror", -] - -[[package]] -name = "async-graphql-parser" -version = "7.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e64ef70f77a1c689111e52076da1cd18f91834bcb847de0a9171f83624b07fbf" -dependencies = [ - "async-graphql-value", - "pest", - "serde", - "serde_json", -] - -[[package]] -name = "async-graphql-value" -version = "7.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3e3ef112905abea9dea592fc868a6873b10ebd3f983e83308f995d6284e9ba41" -dependencies = [ - "bytes", - "indexmap", - "serde", - "serde_json", -] - -[[package]] -name = "async-io" -version = "2.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "456b8a8feb6f42d237746d4b3e9a178494627745c3c56c6ea55d92ba50d026fc" -dependencies = [ - "autocfg", - "cfg-if", - "concurrent-queue", - "futures-io", - "futures-lite", - "parking", - "polling", - "rustix", - "slab", - "windows-sys 0.61.2", -] - -[[package]] -name = "async-trait" -version = "0.1.92" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "82f6aeea286b8eb4dd3431a1be1b59d290ace00f5bfd8e2a159bc2a05e2c1667" -dependencies = [ - "proc-macro2", - "quote", - "syn 3.0.4", -] - -[[package]] -name = "asynk-strim" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52697735bdaac441a29391a9e97102c74c6ef0f9b60a40cf109b1b404e29d2f6" -dependencies = [ - "futures-core", - "pin-project-lite", -] - -[[package]] -name = "atoi" -version = "2.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f28d99ec8bfea296261ca1af174f24225171fea9664ba9003cbebee704810528" -dependencies = [ - "num-traits", -] - -[[package]] -name = "atomic-waker" -version = "1.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" - -[[package]] -name = "autocfg" -version = "1.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2032f911046de80f0a198e0901378627c33f59ea0ac00e363d481118bd70a53" - -[[package]] -name = "axum" -version = "0.8.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "31b698c5f9a010f6573133b09e0de5408834d0c82f8d7475a89fc1867a71cd90" -dependencies = [ - "axum-core", - "base64", - "bytes", - "form_urlencoded", - "futures-util", - "http", - "http-body", - "http-body-util", - "hyper", - "hyper-util", - "itoa", - "matchit", - "memchr", - "mime", - "percent-encoding", - "pin-project-lite", - "serde_core", - "serde_json", - "serde_path_to_error", - "serde_urlencoded", - "sha1", - "sync_wrapper", - "tokio", - "tokio-tungstenite", - "tower", - "tower-layer", - "tower-service", - "tracing", -] - -[[package]] -name = "axum-core" -version = "0.5.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08c78f31d7b1291f7ee735c1c6780ccde7785daae9a9206026862dab7d8792d1" -dependencies = [ - "bytes", - "futures-core", - "http", - "http-body", - "http-body-util", - "mime", - "pin-project-lite", - "sync_wrapper", - "tower-layer", - "tower-service", - "tracing", -] - -[[package]] -name = "base64" -version = "0.22.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" - -[[package]] -name = "base64ct" -version = "1.8.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2af50177e190e07a26ab74f8b1efbfe2ef87da2116221318cb1c2e82baf7de06" - -[[package]] -name = "bitflags" -version = "2.13.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b588b76d00fde79687d7646a9b5bdf3cc0f655e0bbd080335a95d7e96f3587da" -dependencies = [ - "serde_core", -] - -[[package]] -name = "block-buffer" -version = "0.10.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" -dependencies = [ - "generic-array", -] - -[[package]] -name = "block-buffer" -version = "0.12.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2f6c7dbe95a6ed67ad9f18e57daf93a2f034c524b99fd2b76d18fdfeb6660aa" -dependencies = [ - "hybrid-array", -] - -[[package]] -name = "bumpalo" -version = "3.20.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72f5acc6cb2ba439de613abc23857ec3d78374d8ed5ac84e9d11336e87da8649" - -[[package]] -name = "byteorder" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" - -[[package]] -name = "bytes" -version = "1.12.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc652a48c352aef3ea3aed32080501cf3ef6ed5da78602a020c991775b0aff04" -dependencies = [ - "serde", -] - -[[package]] -name = "cc" -version = "1.4.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ad534f4357a5264cce5019c989cf66a4f0dc4e0d1b1d15f8aacec0ff7360273" -dependencies = [ - "find-msvc-tools", - "shlex", -] - -[[package]] -name = "cfg-if" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" - -[[package]] -name = "clap" -version = "4.6.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "473c7e07f409a8d772161724aa8db6a765a2532a70f9667eeb7b49d3d02fbdca" -dependencies = [ - "clap_builder", - "clap_derive", -] - -[[package]] -name = "clap_builder" -version = "4.6.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b48fea5a88e9ae728a2dcbedbfc0e730f7d60da42e1cb049a83c9fb8b789889" -dependencies = [ - "anstream", - "anstyle", - "clap_lex", - "strsim", -] - -[[package]] -name = "clap_derive" -version = "4.6.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d012d2b9d65aca7f18f4d9878a045bc17899bba951561ba5ec3c2ba1eed9a061" -dependencies = [ - "heck", - "proc-macro2", - "quote", - "syn 3.0.4", -] - -[[package]] -name = "clap_lex" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8d4a3bb8b1e0c1050499d1815f5ab16d04f0959b233085fb31653fbfc9d98f9" - -[[package]] -name = "cmov" -version = "0.5.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c9ea0ac24bc397ab3c98583a3c9ba74fa56b09a4449bbe172b9b1ddb016027a" - -[[package]] -name = "colorchoice" -version = "1.0.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d07550c9036bf2ae0c684c4297d503f838287c83c53686d05370d0e139ae570" - -[[package]] -name = "concurrent-queue" -version = "2.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ca0197aee26d1ae37445ee532fefce43251d24cc7c166799f4d46817f1d3973" -dependencies = [ - "crossbeam-utils", -] - -[[package]] -name = "const-oid" -version = "0.9.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" - -[[package]] -name = "const-oid" -version = "0.10.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a6ef517f0926dd24a1582492c791b6a4818a4d94e789a334894aa15b0d12f55c" - -[[package]] -name = "cpufeatures" -version = "0.2.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" -dependencies = [ - "libc", -] - -[[package]] -name = "cpufeatures" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ca28b0ae3115b884660db4118d803791fd6756b6e88f39c0f3f7859060d7566" -dependencies = [ - "libc", -] - -[[package]] -name = "crc" -version = "3.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5eb8a2a1cd12ab0d987a5d5e825195d372001a4094a0376319d5a0ad71c1ba0d" -dependencies = [ - "crc-catalog", -] - -[[package]] -name = "crc-catalog" -version = "2.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "217698eaf96b4a3f0bc4f3662aaa55bdf913cd54d7204591faa790070c6d0853" - -[[package]] -name = "crossbeam-queue" -version = "0.3.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "803d13fb3b09d88be9f4dbc29062c66b19bf7170867ceb746d2a8689bf6c7a26" -dependencies = [ - "crossbeam-utils", -] - -[[package]] -name = "crossbeam-utils" -version = "0.8.22" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61803da095bee82a81bb1a452ecc25d3b2f1416d1897eb86430c6159ef717c17" - -[[package]] -name = "crypto-common" -version = "0.1.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78c8292055d1c1df0cce5d180393dc8cce0abec0a7102adb6c7b1eef6016d60a" -dependencies = [ - "generic-array", - "typenum", -] - -[[package]] -name = "crypto-common" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce6e4c961d6cd6c9a86db418387425e8bdeaf05b3c8bc1411e6dca4c252f1453" -dependencies = [ - "hybrid-array", -] - -[[package]] -name = "ctutils" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d5515a3834141de9eafb9717ad39eea8247b5674e6066c404e8c4b365d2a29e" -dependencies = [ - "cmov", -] - -[[package]] -name = "darling" -version = "0.20.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc7f46116c46ff9ab3eb1597a45688b6715c6e628b5c133e288e709a29bcb4ee" -dependencies = [ - "darling_core 0.20.11", - "darling_macro 0.20.11", -] - -[[package]] -name = "darling" -version = "0.23.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "25ae13da2f202d56bd7f91c25fba009e7717a1e4a1cc98a76d844b65ae912e9d" -dependencies = [ - "darling_core 0.23.0", - "darling_macro 0.23.0", -] - -[[package]] -name = "darling_core" -version = "0.20.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d00b9596d185e565c2207a0b01f8bd1a135483d02d9b7b0a54b11da8d53412e" -dependencies = [ - "fnv", - "ident_case", - "proc-macro2", - "quote", - "strsim", - "syn 2.0.119", -] - -[[package]] -name = "darling_core" -version = "0.23.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9865a50f7c335f53564bb694ef660825eb8610e0a53d3e11bf1b0d3df31e03b0" -dependencies = [ - "ident_case", - "proc-macro2", - "quote", - "strsim", - "syn 2.0.119", -] - -[[package]] -name = "darling_macro" -version = "0.20.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc34b93ccb385b40dc71c6fceac4b2ad23662c7eeb248cf10d529b7e055b6ead" -dependencies = [ - "darling_core 0.20.11", - "quote", - "syn 2.0.119", -] - -[[package]] -name = "darling_macro" -version = "0.23.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac3984ec7bd6cfa798e62b4a642426a5be0e68f9401cfc2a01e3fa9ea2fcdb8d" -dependencies = [ - "darling_core 0.23.0", - "quote", - "syn 2.0.119", -] - -[[package]] -name = "data-encoding" -version = "2.11.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4583a4551df46e2792f82ceeac45e850d2e2d5debba0b91f102385cda5b11f06" - -[[package]] -name = "der" -version = "0.7.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e7c1832837b905bbfb5101e07cc24c8deddf52f93225eee6ead5f4d63d53ddcb" -dependencies = [ - "const-oid 0.9.6", - "pem-rfc7468", - "zeroize", -] - -[[package]] -name = "derive_builder" -version = "0.20.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "507dfb09ea8b7fa618fcf76e953f4f5e192547945816d5358edffe39f6f94947" -dependencies = [ - "derive_builder_macro", -] - -[[package]] -name = "derive_builder_core" -version = "0.20.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d5bcf7b024d6835cfb3d473887cd966994907effbe9227e8c8219824d06c4e8" -dependencies = [ - "darling 0.20.11", - "proc-macro2", - "quote", - "syn 2.0.119", -] - -[[package]] -name = "derive_builder_macro" -version = "0.20.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ab63b0e2bf4d5928aff72e83a7dace85d7bba5fe12dcc3c5a572d78caffd3f3c" -dependencies = [ - "derive_builder_core", - "syn 2.0.119", -] - -[[package]] -name = "digest" -version = "0.10.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" -dependencies = [ - "block-buffer 0.10.4", - "const-oid 0.9.6", - "crypto-common 0.1.7", - "subtle", -] - -[[package]] -name = "digest" -version = "0.11.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1dd6dbb5841937940781866fa1281a1ff7bd3bf827091440879f9994983d5c2" -dependencies = [ - "block-buffer 0.12.1", - "const-oid 0.10.2", - "crypto-common 0.2.2", - "ctutils", -] - -[[package]] -name = "displaydoc" -version = "0.2.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c6232dd377dcc64799954cbd3a9bb882e9cdc1308ccd87b1c098f1fb2eaf82a8" -dependencies = [ - "proc-macro2", - "quote", - "syn 3.0.4", -] - -[[package]] -name = "dotenvy" -version = "0.15.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1aaf95b3e5c8f23aa320147307562d361db0ae0d51242340f558153b4eb2439b" - -[[package]] -name = "either" -version = "1.18.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "252afb9ae5eaa683babdc6a068b3f5726eb19e05070c731f9b2a23a7c3e8ed34" -dependencies = [ - "serde", -] - -[[package]] -name = "encoding_rs" -version = "0.8.35" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75030f3c4f45dafd7586dd6780965a8c7e8e285a5ecb86713e63a79c5b2766f3" -dependencies = [ - "cfg-if", -] - -[[package]] -name = "equivalent" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" - -[[package]] -name = "errno" -version = "0.3.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" -dependencies = [ - "libc", - "windows-sys 0.61.2", -] - -[[package]] -name = "etcetera" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "136d1b5283a1ab77bd9257427ffd09d8667ced0570b6f938942bc7568ed5b943" -dependencies = [ - "cfg-if", - "home", - "windows-sys 0.48.0", -] - -[[package]] -name = "event-listener" -version = "5.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a23add41df1562121a9393cb065eab5146a1242410f23a644851e90cfd669d2" -dependencies = [ - "parking", - "pin-project-lite", -] - -[[package]] -name = "fast_chemail" -version = "0.9.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "495a39d30d624c2caabe6312bfead73e7717692b44e0b32df168c275a2e8e9e4" -dependencies = [ - "ascii_utils", -] - -[[package]] -name = "fastrand" -version = "2.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da7c62ceae207dd37ea5b845da6a0696c799f85e97da1ab5b7910be3c1c80223" - -[[package]] -name = "find-msvc-tools" -version = "0.1.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d45db016d36b838f563236e9193d0ee6ce38f3f68b6c94e914b4929c96bbb890" - -[[package]] -name = "fixedbitset" -version = "0.5.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d674e81391d1e1ab681a28d99df07927c6d4aa5b027d7da16ba32d1d21ecd99" - -[[package]] -name = "flume" -version = "0.11.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da0e4dd2a88388a1f4ccc7c9ce104604dab68d9f408dc34cd45823d5a9069095" -dependencies = [ - "futures-core", - "futures-sink", - "spin", -] - -[[package]] -name = "fnv" -version = "1.0.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" - -[[package]] -name = "foldhash" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" - -[[package]] -name = "form_urlencoded" -version = "1.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb4cb245038516f5f85277875cdaa4f7d2c9a0fa0468de06ed190163b1581fcf" -dependencies = [ - "percent-encoding", -] - -[[package]] -name = "futures-channel" -version = "0.3.34" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1f9e3d69d39e4862ffed03ed071a76f9a13ba1d9109d355b0f0aa6b15e393c4" -dependencies = [ - "futures-core", - "futures-sink", -] - -[[package]] -name = "futures-core" -version = "0.3.34" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92d699e522242e69e3003b94ecc1f960f3a5e015aa7c5d7486e65ad01dd94f5e" - -[[package]] -name = "futures-executor" -version = "0.3.34" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "031b47cf1a3c6cc8bc2fc76cd437f521619387907d469316e7c0bc278f1f5432" -dependencies = [ - "futures-core", - "futures-task", - "futures-util", -] - -[[package]] -name = "futures-intrusive" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d930c203dd0b6ff06e0201a4a2fe9149b43c684fd4420555b26d21b1a02956f" -dependencies = [ - "futures-core", - "lock_api", - "parking_lot", -] - -[[package]] -name = "futures-io" -version = "0.3.34" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53c0fa8157de1303bfffdaa1cc2a673bfffb60102f76b0ef4441659124373fed" - -[[package]] -name = "futures-lite" -version = "2.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f78e10609fe0e0b3f4157ffab1876319b5b0db102a2c60dc4626306dc46b44ad" -dependencies = [ - "futures-core", - "pin-project-lite", -] - -[[package]] -name = "futures-macro" -version = "0.3.34" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9fb9654ba8355388abeb8dcb4fc62f511300867002afc858860463bdd9fe0c44" -dependencies = [ - "proc-macro2", - "quote", - "syn 3.0.4", -] - -[[package]] -name = "futures-sink" -version = "0.3.34" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1944426bf7d03f1d14f708785e4b33efd750b36d48a157b836b3efc15ede8e1d" - -[[package]] -name = "futures-task" -version = "0.3.34" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd417de3d1d015fc3bfd2b1ea46dfc7bab72ef86f1cc7cc9c78e728b34a6d1fd" - -[[package]] -name = "futures-util" -version = "0.3.34" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d50a92467f8ba5dd6e3ee5d4bd04d73ab2e4e1c44474a0674821dfce14b79bc" -dependencies = [ - "futures-core", - "futures-io", - "futures-macro", - "futures-sink", - "futures-task", - "memchr", - "pin-project-lite", - "slab", -] - -[[package]] -name = "generic-array" -version = "0.14.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" -dependencies = [ - "typenum", - "version_check", -] - -[[package]] -name = "getrandom" -version = "0.2.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0" -dependencies = [ - "cfg-if", - "libc", - "wasi", -] - -[[package]] -name = "getrandom" -version = "0.3.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" -dependencies = [ - "cfg-if", - "libc", - "r-efi 5.3.0", - "wasip2", -] - -[[package]] -name = "getrandom" -version = "0.4.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "300e883d756b2e4ec94e02791f39b04b522276138852cfc41d9fb7e904106099" -dependencies = [ - "cfg-if", - "libc", - "r-efi 6.0.0", -] - -[[package]] -name = "h2" -version = "0.4.19" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ef8e5e5a340588f4452631496976cf8636d4a7ecf600239fdc27615d2530bc16" -dependencies = [ - "atomic-waker", - "bytes", - "fnv", - "futures-core", - "futures-sink", - "http", - "indexmap", - "slab", - "tokio", - "tokio-util", - "tracing", -] - -[[package]] -name = "handlebars" -version = "6.4.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75c54236f9045c8004a77942bebc52145b4844639db934a5c70fe08617fbe61a" -dependencies = [ - "derive_builder", - "log", - "num-order", - "pest", - "pest_derive", - "serde", - "serde_json", - "thiserror", -] - -[[package]] -name = "hashbrown" -version = "0.15.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" -dependencies = [ - "allocator-api2", - "equivalent", - "foldhash", -] - -[[package]] -name = "hashbrown" -version = "0.17.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a" - -[[package]] -name = "hashlink" -version = "0.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7382cf6263419f2d8df38c55d7da83da5c18aef87fc7a7fc1fb1e344edfe14c1" -dependencies = [ - "hashbrown 0.15.5", -] - -[[package]] -name = "heck" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" - -[[package]] -name = "hermit-abi" -version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc0fef456e4baa96da950455cd02c081ca953b141298e41db3fc7e36b1da849c" - -[[package]] -name = "hex" -version = "0.4.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" - -[[package]] -name = "hkdf" -version = "0.12.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b5f8eb2ad728638ea2c7d47a21db23b7b58a72ed6a38256b8a1849f15fbbdf7" -dependencies = [ - "hmac 0.12.1", -] - -[[package]] -name = "hmac" -version = "0.12.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" -dependencies = [ - "digest 0.10.7", -] - -[[package]] -name = "hmac" -version = "0.13.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6303bc9732ae41b04cb554b844a762b4115a61bfaa81e3e83050991eeb56863f" -dependencies = [ - "digest 0.11.3", -] - -[[package]] -name = "home" -version = "0.5.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc627f471c528ff0c4a49e1d5e60450c8f6461dd6d10ba9dcd3a61d3dff7728d" -dependencies = [ - "windows-sys 0.61.2", -] - -[[package]] -name = "http" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "918d3568bebf352712bc2ef3d46a8bcf1a75b373be6539de198e9105cbbf9ce0" -dependencies = [ - "bytes", - "itoa", -] - -[[package]] -name = "http-body" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca2a8f2913ee65f60facd6a5905613afaa448497a0230cc41ce022d93290bc2c" -dependencies = [ - "bytes", - "http", -] - -[[package]] -name = "http-body-util" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23169fe34a5fbcdd3f3862e78fb9b6fccd5f02a6dc6f732547005d45631ce71c" -dependencies = [ - "bytes", - "futures-core", - "http", - "http-body", - "pin-project-lite", -] - -[[package]] -name = "httparse" -version = "1.10.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87" - -[[package]] -name = "httpdate" -version = "1.0.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" - -[[package]] -name = "hybrid-array" -version = "0.4.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "707114b52a152fa7bdb290cd7cd5912d9467273b6d74e21b8d81aca1f8533f6b" -dependencies = [ - "typenum", -] - -[[package]] -name = "hyper" -version = "1.11.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "27b501faa50e7a26c3d3560ca625132f4078a17771f4810baf70475ae48cbe43" -dependencies = [ - "atomic-waker", - "bytes", - "futures-channel", - "futures-core", - "h2", - "http", - "http-body", - "httparse", - "httpdate", - "itoa", - "pin-project-lite", - "smallvec", - "tokio", - "want", -] - -[[package]] -name = "hyper-timeout" -version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b90d566bffbce6a75bd8b09a05aa8c2cb1fabb6cb348f8840c9e4c90a0d83b0" -dependencies = [ - "hyper", - "hyper-util", - "pin-project-lite", - "tokio", - "tower-service", -] - -[[package]] -name = "hyper-util" -version = "0.1.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96547c2556ec9d12fb1578c4eaf448b04993e7fb79cbaad930a656880a6bdfa0" -dependencies = [ - "bytes", - "futures-channel", - "futures-util", - "http", - "http-body", - "hyper", - "libc", - "pin-project-lite", - "socket2", - "tokio", - "tower-service", - "tracing", -] - -[[package]] -name = "icu_collections" -version = "2.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa68d21081c4a05d5a901a1c62add574c77048b6a1c67be3b50ce0b60d4ca513" -dependencies = [ - "displaydoc", - "potential_utf", - "utf8_iter", - "yoke", - "zerofrom", - "zerovec", -] - -[[package]] -name = "icu_locale_core" -version = "2.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d56e28588da92eee5c3201a6eff33fabdd49b62269c8938d4ff050ce4d900deb" -dependencies = [ - "displaydoc", - "litemap", - "tinystr", - "writeable", - "zerovec", -] - -[[package]] -name = "icu_normalizer" -version = "2.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "12f9cf5f235641ed274641dd81c3f28d870e276763d0797aeeab72317b1c646f" -dependencies = [ - "icu_collections", - "icu_normalizer_data", - "icu_properties", - "icu_provider", - "smallvec", - "zerovec", -] - -[[package]] -name = "icu_normalizer_data" -version = "2.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1563da1ed3e0b3bf3d74c9b85917ac9c56464d2f57242270c09c9e752f8021a0" - -[[package]] -name = "icu_properties" -version = "2.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e7ca276ad3145661a65914e6daf131ca5120cd3dcee8f8f3214b8875184a148" -dependencies = [ - "displaydoc", - "icu_collections", - "icu_locale_core", - "icu_properties_data", - "icu_provider", - "zerotrie", - "zerovec", -] - -[[package]] -name = "icu_properties_data" -version = "2.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e590f038c1464a96894fd6d10127e90a8be4509f56ff7ecef851b15cee0b7caa" - -[[package]] -name = "icu_provider" -version = "2.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d27bbb9d3abbefac45d55f647c9de1d44aafcd1186eb91879afef17c396c3e73" -dependencies = [ - "displaydoc", - "icu_locale_core", - "writeable", - "yoke", - "zerofrom", - "zerotrie", - "zerovec", -] - -[[package]] -name = "ident_case" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" - -[[package]] -name = "idna" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b0875f23caa03898994f6ddc501886a45c7d3d62d04d2d90788d47be1b1e4de" -dependencies = [ - "idna_adapter", - "smallvec", - "utf8_iter", -] - -[[package]] -name = "idna_adapter" -version = "1.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb68373c0d6620ef8105e855e7745e18b0d00d3bdb07fb532e434244cdb9a714" -dependencies = [ - "icu_normalizer", - "icu_properties", -] - -[[package]] -name = "indexmap" -version = "2.14.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07aa2048142242915a31d35844fb311e0e53fcca590c3a0a40dcf1b841fa09eb" -dependencies = [ - "equivalent", - "hashbrown 0.17.1", - "serde", - "serde_core", -] - -[[package]] -name = "is_terminal_polyfill" -version = "1.70.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695" - -[[package]] -name = "itertools" -version = "0.14.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b192c782037fadd9cfa75548310488aabdbf3d2da73885b31bd0abd03351285" -dependencies = [ - "either", -] - -[[package]] -name = "itoa" -version = "1.0.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" - -[[package]] -name = "js-sys" -version = "0.3.104" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0e0c1080212aad755ea003d18543e8768dd432c48819efd73a7bf1e39b7a5a3a" -dependencies = [ - "cfg-if", - "futures-util", - "wasm-bindgen", -] - -[[package]] -name = "lazy_static" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" -dependencies = [ - "spin", -] - -[[package]] -name = "libc" -version = "0.2.189" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3eaf3ede3fee6db1a4c2ee091bf8a8b4dccdc6d17f656fb07896ee72867612f2" - -[[package]] -name = "libm" -version = "0.2.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6d2cec3eae94f9f509c767b45932f1ada8350c4bdb85af2fcab4a3c14807981" - -[[package]] -name = "libredox" -version = "0.1.21" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d7955dfc218a8afb29dfeffd540e3a6e96baeb94fe7138228dd7cc6937fbbf96" -dependencies = [ - "bitflags", - "libc", - "plain", - "redox_syscall 0.9.3", -] - -[[package]] -name = "libsqlite3-sys" -version = "0.30.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2e99fb7a497b1e3339bc746195567ed8d3e24945ecd636e3619d20b9de9e9149" -dependencies = [ - "pkg-config", - "vcpkg", -] - -[[package]] -name = "linux-raw-sys" -version = "0.12.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32a66949e030da00e8c7d4434b251670a91556f4144941d37452769c25d58a53" - -[[package]] -name = "litemap" -version = "0.8.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "47d9d19d1d6efa0109d2f65ff4c85cddd50bd572e5a00127ab10987290bcefae" - -[[package]] -name = "lock_api" -version = "0.4.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965" -dependencies = [ - "scopeguard", -] - -[[package]] -name = "log" -version = "0.4.34" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f9f8bd3e56ce4dfc153cf470fffbfa98c7620958b312ca5c3a4b8d5181fd13c6" - -[[package]] -name = "matchit" -version = "0.8.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "47e1ffaa40ddd1f3ed91f717a33c8c0ee23fff369e3aa8772b9605cc1d22f4c3" - -[[package]] -name = "md-5" -version = "0.10.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d89e7ee0cfbedfc4da3340218492196241d89eefb6dab27de5df917a6d2e78cf" -dependencies = [ - "cfg-if", - "digest 0.10.7", -] - -[[package]] -name = "memchr" -version = "2.8.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf8baf1c55e62ffcace7a9f06f4bd9cd3f0c4beb022d3b367256b91b87513d98" - -[[package]] -name = "mime" -version = "0.3.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" - -[[package]] -name = "mio" -version = "1.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "30d65c71f1ce40ab09135ce117d742b9f8a19ff91a41a8b57ed50bc2de59c427" -dependencies = [ - "libc", - "wasi", - "windows-sys 0.61.2", -] - -[[package]] -name = "multer" -version = "3.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "83e87776546dc87511aa5ee218730c92b666d7264ab6ed41f9d215af9cd5224b" -dependencies = [ - "bytes", - "encoding_rs", - "futures-util", - "http", - "httparse", - "memchr", - "mime", - "spin", - "version_check", -] - -[[package]] -name = "multimap" -version = "0.10.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d87ecb2933e8aeadb3e3a02b828fed80a7528047e68b4f424523a0981a3a084" - -[[package]] -name = "num-bigint-dig" -version = "0.8.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e661dda6640fad38e827a6d4a310ff4763082116fe217f279885c97f511bb0b7" -dependencies = [ - "lazy_static", - "libm", - "num-integer", - "num-iter", - "num-traits", - "rand 0.8.8", - "smallvec", - "zeroize", -] - -[[package]] -name = "num-integer" -version = "0.1.47" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ce2d95d4b3734dc35aa2f45e1aa22cd416814592a4f9d9205e11affd5b8e10b" -dependencies = [ - "num-traits", -] - -[[package]] -name = "num-iter" -version = "0.1.46" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c92800bd69a1eac91786bcfe9da64a897eb72911b8dc3095decbd07429e8048b" -dependencies = [ - "num-integer", - "num-traits", -] - -[[package]] -name = "num-modular" -version = "0.6.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd8e500409e6cd603b03e477c26a6caecdc27ac58979a53e881c75eafc079f44" - -[[package]] -name = "num-order" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "537b596b97c40fcf8056d153049eb22f481c17ebce72a513ec9286e4986d1bb6" -dependencies = [ - "num-modular", -] - -[[package]] -name = "num-traits" -version = "0.2.19" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" -dependencies = [ - "autocfg", - "libm", -] - -[[package]] -name = "once_cell" -version = "1.21.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" - -[[package]] -name = "once_cell_polyfill" -version = "1.70.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe" - -[[package]] -name = "parking" -version = "2.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f38d5652c16fde515bb1ecef450ab0f6a219d619a7274976324d5e377f7dceba" - -[[package]] -name = "parking_lot" -version = "0.12.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a" -dependencies = [ - "lock_api", - "parking_lot_core", -] - -[[package]] -name = "parking_lot_core" -version = "0.9.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1" -dependencies = [ - "cfg-if", - "libc", - "redox_syscall 0.5.18", - "smallvec", - "windows-link", -] - -[[package]] -name = "pem-rfc7468" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "88b39c9bfcfc231068454382784bb460aae594343fb030d46e9f50a645418412" -dependencies = [ - "base64ct", -] - -[[package]] -name = "percent-encoding" -version = "2.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" - -[[package]] -name = "pest" -version = "2.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a07a60cc7a4d00c91f95c685609d1d2f79050e6804b70ebedd7650f0b839bcf" -dependencies = [ - "memchr", - "ucd-trie", -] - -[[package]] -name = "pest_derive" -version = "2.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b3a83744a5c8455b8b3e0dc5031362780a347c878bdd11584d1a8984228cc88d" -dependencies = [ - "pest", - "pest_generator", -] - -[[package]] -name = "pest_generator" -version = "2.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e0cd3451aa3de60d4b9a1e736885e4dea6b31617598026f12256ad566d63304a" -dependencies = [ - "pest", - "pest_meta", - "proc-macro2", - "quote", - "syn 2.0.119", -] - -[[package]] -name = "pest_meta" -version = "2.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e04d3a0849e241d7dfce834c83b1c5edc8622009e8dd51a12ba1927c32f05496" -dependencies = [ - "pest", -] - -[[package]] -name = "petgraph" -version = "0.8.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8701b58ea97060d5e5b155d383a69952a60943f0e6dfe30b04c287beb0b27455" -dependencies = [ - "fixedbitset", - "hashbrown 0.15.5", - "indexmap", -] - -[[package]] -name = "pin-project" -version = "1.1.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2466b2336ed02bcdca6b294417127b90ec92038d1d5c4fbeac971a922e0e0924" -dependencies = [ - "pin-project-internal", -] - -[[package]] -name = "pin-project-internal" -version = "1.1.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c96395f0a926bc13b1c17622aaddda1ecb55d49c8f1bf9777e4d877800a43f8b" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.119", -] - -[[package]] -name = "pin-project-lite" -version = "0.2.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd" - -[[package]] -name = "pkcs1" -version = "0.7.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8ffb9f10fa047879315e6625af03c164b16962a5368d724ed16323b68ace47f" -dependencies = [ - "der", - "pkcs8", - "spki", -] - -[[package]] -name = "pkcs8" -version = "0.10.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f950b2377845cebe5cf8b5165cb3cc1a5e0fa5cfa3e1f7f55707d8fd82e0a7b7" -dependencies = [ - "der", - "spki", -] - -[[package]] -name = "pkg-config" -version = "0.3.34" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6b464fbc74e149a392436b17d523f769e057cb6877f6a5c4618bc6f11800548" - -[[package]] -name = "plain" -version = "0.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4596b6d070b27117e987119b4dac604f3c58cfb0b191112e24771b2faeac1a6" - -[[package]] -name = "polling" -version = "3.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d0e4f59085d47d8241c88ead0f274e8a0cb551f3625263c05eb8dd897c34218" -dependencies = [ - "cfg-if", - "concurrent-queue", - "hermit-abi", - "pin-project-lite", - "rustix", - "windows-sys 0.61.2", -] - -[[package]] -name = "potential_utf" -version = "0.1.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d83eb9bc6d8e5cf568e7a1101d60ee05e81ed50ea106026f3d18deeb046d7661" -dependencies = [ - "zerovec", -] - -[[package]] -name = "ppv-lite86" -version = "0.2.21" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" -dependencies = [ - "zerocopy", -] - -[[package]] -name = "prettyplease" -version = "0.2.37" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b" -dependencies = [ - "proc-macro2", - "syn 2.0.119", -] - -[[package]] -name = "proc-macro-crate" -version = "3.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e67ba7e9b2b56446f1d419b1d807906278ffa1a658a8a5d8a39dcb1f5a78614f" -dependencies = [ - "toml_edit", -] - -[[package]] -name = "proc-macro2" -version = "1.0.107" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "985e7ec9bb745e6ce6535b544d84d6cd6f7ad8bd711c398938ae983b91a766d9" -dependencies = [ - "unicode-ident", -] - -[[package]] -name = "prost" -version = "0.14.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "528ac67416ff8646872a3c02cad9cc4ee5dc9f9540c9b10771855c95cb2e5ae1" -dependencies = [ - "bytes", - "prost-derive", -] - -[[package]] -name = "prost-build" -version = "0.14.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03da047801ff44bb6a4d407d4860c05fd70bb81714e6b2f3812603d5b145b042" -dependencies = [ - "heck", - "itertools", - "log", - "multimap", - "petgraph", - "prettyplease", - "prost", - "prost-types", - "pulldown-cmark", - "pulldown-cmark-to-cmark", - "regex", - "syn 2.0.119", - "tempfile", -] - -[[package]] -name = "prost-derive" -version = "0.14.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b570b25f7617e43d59005d0990ccb79e950a423952cea19671b7a876da390adf" -dependencies = [ - "anyhow", - "itertools", - "proc-macro2", - "quote", - "syn 2.0.119", -] - -[[package]] -name = "prost-types" -version = "0.14.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f94967dc7688f3054c7fac87473ffae4cc4c3904800e2d9f5b857246d8963b0a" -dependencies = [ - "prost", -] - -[[package]] -name = "protoc-bin-vendored" -version = "3.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d1c381df33c98266b5f08186583660090a4ffa0889e76c7e9a5e175f645a67fa" -dependencies = [ - "protoc-bin-vendored-linux-aarch_64", - "protoc-bin-vendored-linux-ppcle_64", - "protoc-bin-vendored-linux-s390_64", - "protoc-bin-vendored-linux-x86_32", - "protoc-bin-vendored-linux-x86_64", - "protoc-bin-vendored-macos-aarch_64", - "protoc-bin-vendored-macos-x86_64", - "protoc-bin-vendored-win32", -] - -[[package]] -name = "protoc-bin-vendored-linux-aarch_64" -version = "3.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c350df4d49b5b9e3ca79f7e646fde2377b199e13cfa87320308397e1f37e1a4c" - -[[package]] -name = "protoc-bin-vendored-linux-ppcle_64" -version = "3.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a55a63e6c7244f19b5c6393f025017eb5d793fd5467823a099740a7a4222440c" - -[[package]] -name = "protoc-bin-vendored-linux-s390_64" -version = "3.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1dba5565db4288e935d5330a07c264a4ee8e4a5b4a4e6f4e83fad824cc32f3b0" - -[[package]] -name = "protoc-bin-vendored-linux-x86_32" -version = "3.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8854774b24ee28b7868cd71dccaae8e02a2365e67a4a87a6cd11ee6cdbdf9cf5" - -[[package]] -name = "protoc-bin-vendored-linux-x86_64" -version = "3.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b38b07546580df720fa464ce124c4b03630a6fb83e05c336fea2a241df7e5d78" - -[[package]] -name = "protoc-bin-vendored-macos-aarch_64" -version = "3.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89278a9926ce312e51f1d999fee8825d324d603213344a9a706daa009f1d8092" - -[[package]] -name = "protoc-bin-vendored-macos-x86_64" -version = "3.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81745feda7ccfb9471d7a4de888f0652e806d5795b61480605d4943176299756" - -[[package]] -name = "protoc-bin-vendored-win32" -version = "3.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95067976aca6421a523e491fce939a3e65249bac4b977adee0ee9771568e8aa3" - -[[package]] -name = "pulldown-cmark" -version = "0.13.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e9f068eba8e7071c5f9511831b44f32c740d5adf574e990f946ddb53db2f314e" -dependencies = [ - "bitflags", - "memchr", - "unicase", -] - -[[package]] -name = "pulldown-cmark-to-cmark" -version = "22.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ab1ad36992cead65f02aa399a373a42730922f1525d988172634fdefdecb8a60" -dependencies = [ - "pulldown-cmark", -] - -[[package]] -name = "quote" -version = "1.0.47" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fbf4db142a473a8d80c26bbf18454ed458bf8d26c8219c331daecfdbd079001" -dependencies = [ - "proc-macro2", -] - -[[package]] -name = "r-efi" -version = "5.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" - -[[package]] -name = "r-efi" -version = "6.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf" - -[[package]] -name = "rand" -version = "0.8.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e058c7de0b26af77780c769414d6257830bb240f3c38477dbc2c16e5f54d6d4c" -dependencies = [ - "libc", - "rand_chacha 0.3.1", - "rand_core 0.6.4", -] - -[[package]] -name = "rand" -version = "0.9.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9ef1d0d795eb7d84685bca4f72f3649f064e6641543d3a8c415898726a57b41" -dependencies = [ - "rand_chacha 0.9.0", - "rand_core 0.9.5", -] - -[[package]] -name = "rand_chacha" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" -dependencies = [ - "ppv-lite86", - "rand_core 0.6.4", -] - -[[package]] -name = "rand_chacha" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" -dependencies = [ - "ppv-lite86", - "rand_core 0.9.5", -] - -[[package]] -name = "rand_core" -version = "0.6.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" -dependencies = [ - "getrandom 0.2.17", -] - -[[package]] -name = "rand_core" -version = "0.9.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "76afc826de14238e6e8c374ddcc1fa19e374fd8dd986b0d2af0d02377261d83c" -dependencies = [ - "getrandom 0.3.4", -] - -[[package]] -name = "redox_syscall" -version = "0.5.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" -dependencies = [ - "bitflags", -] - -[[package]] -name = "redox_syscall" -version = "0.9.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d678d17679829e73d371e96880897e98fee2ded7acc0a50bdf8af2affa4b2fe5" -dependencies = [ - "bitflags", -] - -[[package]] -name = "regex" -version = "1.13.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f020237b6c8eed93db2e2cb53c00c60a8e1bc73da7d073199a1180401450218d" -dependencies = [ - "aho-corasick", - "memchr", - "regex-automata", - "regex-syntax", -] - -[[package]] -name = "regex-automata" -version = "0.4.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ad8553b9b26413251cbf30e620595c7a41b3887f03da04579c0e6b0d6a06b4b2" -dependencies = [ - "aho-corasick", - "memchr", - "regex-syntax", -] - -[[package]] -name = "regex-syntax" -version = "0.8.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d6f6ff9a378485b298a5286656da665ba74413d36db0979633275d2e708145d4" - -[[package]] -name = "ring" -version = "0.17.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7" -dependencies = [ - "cc", - "cfg-if", - "getrandom 0.2.17", - "libc", - "untrusted", - "windows-sys 0.52.0", -] - -[[package]] -name = "rsa" -version = "0.9.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8573f03f5883dcaebdfcf4725caa1ecb9c15b2ef50c43a07b816e06799bb12d" -dependencies = [ - "const-oid 0.9.6", - "digest 0.10.7", - "num-bigint-dig", - "num-integer", - "num-traits", - "pkcs1", - "pkcs8", - "rand_core 0.6.4", - "signature", - "spki", - "subtle", - "zeroize", -] - -[[package]] -name = "rustix" -version = "1.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6fe4565b9518b83ef4f91bb47ce29620ca828bd32cb7e408f0062e9930ba190" -dependencies = [ - "bitflags", - "errno", - "libc", - "linux-raw-sys", - "windows-sys 0.61.2", -] - -[[package]] -name = "rustls" -version = "0.23.43" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0283386ce02abc0151e1761d08802dfe86c173b0b494af5cbc086574e453da06" -dependencies = [ - "once_cell", - "ring", - "rustls-pki-types", - "rustls-webpki", - "subtle", - "zeroize", -] - -[[package]] -name = "rustls-pki-types" -version = "1.15.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2f4925028c7eb5d1fcdaf196971378ed9d2c1c4efc7dc5d011256f76c99c0a96" -dependencies = [ - "zeroize", -] - -[[package]] -name = "rustls-webpki" -version = "0.103.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f3c3cf1d8b1e7d4927e2d154c3fcb02979afb9939629c62cd9048d4f07b60ac2" -dependencies = [ - "ring", - "rustls-pki-types", - "untrusted", -] - -[[package]] -name = "rustversion" -version = "1.0.23" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf54715a573b99ac80df0bc206da022bcd442c974952c7b9720069370852e21f" - -[[package]] -name = "ryu" -version = "1.0.23" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9774ba4a74de5f7b1c1451ed6cd5285a32eddb5cccb8cc655a4e50009e06477f" - -[[package]] -name = "scopeguard" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" - -[[package]] -name = "serde" -version = "1.0.229" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4148590afebada386688f18773da617792bf2ef03ffc1e4cbd2b1d45b023e0ba" -dependencies = [ - "serde_core", - "serde_derive", -] - -[[package]] -name = "serde_core" -version = "1.0.229" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67dca2c9c51e58a4791a4b1ed58308b39c64224d349a935ab5039aa360942a48" -dependencies = [ - "serde_derive", -] - -[[package]] -name = "serde_derive" -version = "1.0.229" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e7a5d71263a5a7d47b41f6b3f06ba276f10cc18b0931f1799f710578e2309348" -dependencies = [ - "proc-macro2", - "quote", - "syn 3.0.4", -] - -[[package]] -name = "serde_json" -version = "1.0.151" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c841b55ecdae098c80dcae9cf767f6f8a0c2cdb3416bbef72181df4d0fe73f14" -dependencies = [ - "itoa", - "memchr", - "serde", - "serde_core", - "zmij", -] - -[[package]] -name = "serde_path_to_error" -version = "0.1.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "10a9ff822e371bb5403e391ecd83e182e0e77ba7f6fe0160b795797109d1b457" -dependencies = [ - "itoa", - "serde", - "serde_core", -] - -[[package]] -name = "serde_urlencoded" -version = "0.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" -dependencies = [ - "form_urlencoded", - "itoa", - "ryu", - "serde", -] - -[[package]] -name = "sha1" -version = "0.10.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a978451301f4db1d02937a4ab3ccce137717b81826e79b7d49ffe3244a13c3b8" -dependencies = [ - "cfg-if", - "cpufeatures 0.2.17", - "digest 0.10.7", -] - -[[package]] -name = "sha2" -version = "0.10.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" -dependencies = [ - "cfg-if", - "cpufeatures 0.2.17", - "digest 0.10.7", -] - -[[package]] -name = "sha2" -version = "0.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "446ba717509524cb3f22f17ecc096f10f4822d76ab5c0b9822c5f9c284e825f4" -dependencies = [ - "cfg-if", - "cpufeatures 0.3.1", - "digest 0.11.3", -] - -[[package]] -name = "shlex" -version = "2.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8fadd59c855ef2080decdef8ff161eb6661b86933c9d82e5ba29dc602a55aba" - -[[package]] -name = "signal-hook-registry" -version = "1.4.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4db69cba1110affc0e9f7bcd48bbf87b3f4fc7c61fc9155afd4c469eb3d6c1b" -dependencies = [ - "errno", - "libc", -] - -[[package]] -name = "signature" -version = "2.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de" -dependencies = [ - "digest 0.10.7", - "rand_core 0.6.4", -] - -[[package]] -name = "slab" -version = "0.4.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5" - -[[package]] -name = "smallvec" -version = "1.15.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ed6a63f02c8539c91a8685a86f4099661ba3da017932f6ebbea6de3f0fa7c90" -dependencies = [ - "serde", -] - -[[package]] -name = "socket2" -version = "0.6.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3d1e2c7f27f8d4cb10542a02c49005dbd6e93095799d6f3be745fae9f8fedd4" -dependencies = [ - "libc", - "windows-sys 0.61.2", -] - -[[package]] -name = "spin" -version = "0.9.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3763264f6b73151db08c50ff20d7d8a0b8796e021cdea7ceedad07b80155fa0e" -dependencies = [ - "lock_api", -] - -[[package]] -name = "spki" -version = "0.7.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d91ed6c858b01f942cd56b37a94b3e0a1798290327d1236e4d9cf4eaca44d29d" -dependencies = [ - "base64ct", - "der", -] - -[[package]] -name = "sqlx" -version = "0.8.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fefb893899429669dcdd979aff487bd78f4064e5e7907e4269081e0ef7d97dc" -dependencies = [ - "sqlx-core", - "sqlx-macros", - "sqlx-mysql", - "sqlx-postgres", - "sqlx-sqlite", -] - -[[package]] -name = "sqlx-core" -version = "0.8.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ee6798b1838b6a0f69c007c133b8df5866302197e404e8b6ee8ed3e3a5e68dc6" -dependencies = [ - "base64", - "bytes", - "crc", - "crossbeam-queue", - "either", - "event-listener", - "futures-core", - "futures-intrusive", - "futures-io", - "futures-util", - "hashbrown 0.15.5", - "hashlink", - "indexmap", - "log", - "memchr", - "once_cell", - "percent-encoding", - "rustls", - "serde", - "serde_json", - "sha2 0.10.9", - "smallvec", - "thiserror", - "tokio", - "tokio-stream", - "tracing", - "url", - "uuid", - "webpki-roots 0.26.11", -] - -[[package]] -name = "sqlx-macros" -version = "0.8.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2d452988ccaacfbf5e0bdbc348fb91d7c8af5bee192173ac3636b5fb6e6715d" -dependencies = [ - "proc-macro2", - "quote", - "sqlx-core", - "sqlx-macros-core", - "syn 2.0.119", -] - -[[package]] -name = "sqlx-macros-core" -version = "0.8.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "19a9c1841124ac5a61741f96e1d9e2ec77424bf323962dd894bdb93f37d5219b" -dependencies = [ - "dotenvy", - "either", - "heck", - "hex", - "once_cell", - "proc-macro2", - "quote", - "serde", - "serde_json", - "sha2 0.10.9", - "sqlx-core", - "sqlx-mysql", - "sqlx-postgres", - "sqlx-sqlite", - "syn 2.0.119", - "tokio", - "url", -] - -[[package]] -name = "sqlx-mysql" -version = "0.8.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aa003f0038df784eb8fecbbac13affe3da23b45194bd57dba231c8f48199c526" -dependencies = [ - "atoi", - "base64", - "bitflags", - "byteorder", - "bytes", - "crc", - "digest 0.10.7", - "dotenvy", - "either", - "futures-channel", - "futures-core", - "futures-io", - "futures-util", - "generic-array", - "hex", - "hkdf", - "hmac 0.12.1", - "itoa", - "log", - "md-5", - "memchr", - "once_cell", - "percent-encoding", - "rand 0.8.8", - "rsa", - "serde", - "sha1", - "sha2 0.10.9", - "smallvec", - "sqlx-core", - "stringprep", - "thiserror", - "tracing", - "uuid", - "whoami", -] - -[[package]] -name = "sqlx-postgres" -version = "0.8.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db58fcd5a53cf07c184b154801ff91347e4c30d17a3562a635ff028ad5deda46" -dependencies = [ - "atoi", - "base64", - "bitflags", - "byteorder", - "crc", - "dotenvy", - "etcetera", - "futures-channel", - "futures-core", - "futures-util", - "hex", - "hkdf", - "hmac 0.12.1", - "home", - "itoa", - "log", - "md-5", - "memchr", - "once_cell", - "rand 0.8.8", - "serde", - "serde_json", - "sha2 0.10.9", - "smallvec", - "sqlx-core", - "stringprep", - "thiserror", - "tracing", - "uuid", - "whoami", -] - -[[package]] -name = "sqlx-sqlite" -version = "0.8.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c2d12fe70b2c1b4401038055f90f151b78208de1f9f89a7dbfd41587a10c3eea" -dependencies = [ - "atoi", - "flume", - "futures-channel", - "futures-core", - "futures-executor", - "futures-intrusive", - "futures-util", - "libsqlite3-sys", - "log", - "percent-encoding", - "serde", - "serde_urlencoded", - "sqlx-core", - "thiserror", - "tracing", - "url", - "uuid", -] - -[[package]] -name = "stable_deref_trait" -version = "1.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" - -[[package]] -name = "static_assertions_next" -version = "1.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d7beae5182595e9a8b683fa98c4317f956c9a2dec3b9716990d20023cc60c766" - -[[package]] -name = "stringprep" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b4df3d392d81bd458a8a621b8bffbd2302a12ffe288a9d931670948749463b1" -dependencies = [ - "unicode-bidi", - "unicode-normalization", - "unicode-properties", -] - -[[package]] -name = "strsim" -version = "0.11.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" - -[[package]] -name = "strum" -version = "0.27.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af23d6f6c1a224baef9d3f61e287d2761385a5b88fdab4eb4c6f11aeb54c4bcf" -dependencies = [ - "strum_macros", -] - -[[package]] -name = "strum_macros" -version = "0.27.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7695ce3845ea4b33927c055a39dc438a45b059f7c1b3d91d38d10355fb8cbca7" -dependencies = [ - "heck", - "proc-macro2", - "quote", - "syn 2.0.119", -] - -[[package]] -name = "subtle" -version = "2.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" - -[[package]] -name = "syn" -version = "2.0.119" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "872831b642d1a07999a962a351ed35b955ea2cfc8f3862091e2a240a84f17297" -dependencies = [ - "proc-macro2", - "quote", - "unicode-ident", -] - -[[package]] -name = "syn" -version = "3.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6275cddf4610d1775e6d1fe9469b2e77d0f39fd98fb7450901b821e0c53649f" -dependencies = [ - "proc-macro2", - "quote", - "unicode-ident", -] - -[[package]] -name = "sync_wrapper" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263" - -[[package]] -name = "syncode-collab" -version = "0.6.0" -dependencies = [ - "async-graphql-axum", - "axum", - "clap", - "hex", - "hmac 0.13.0", - "serde_json", - "sha2 0.11.0", - "syncode-collab-api-graphql", - "syncode-collab-api-grpc", - "syncode-collab-application", - "syncode-collab-model", - "syncode-collab-store", - "thiserror", - "tokio", - "tokio-stream", - "tonic", -] - -[[package]] -name = "syncode-collab-api-graphql" -version = "0.6.0" -dependencies = [ - "async-graphql", - "async-graphql-axum", - "axum", - "syncode-collab-application", - "syncode-collab-model", -] - -[[package]] -name = "syncode-collab-api-grpc" -version = "0.6.0" -dependencies = [ - "prost", - "prost-build", - "protoc-bin-vendored", - "syncode-collab-application", - "syncode-collab-model", - "tonic", - "tonic-prost", - "tonic-prost-build", -] - -[[package]] -name = "syncode-collab-application" -version = "0.6.0" -dependencies = [ - "async-trait", - "serde_json", - "syncode-collab-model", - "thiserror", - "tokio", - "uuid", -] - -[[package]] -name = "syncode-collab-model" -version = "0.6.0" -dependencies = [ - "serde", - "serde_json", - "thiserror", - "uuid", -] - -[[package]] -name = "syncode-collab-store" -version = "0.6.0" -dependencies = [ - "async-trait", - "serde_json", - "sqlx", - "syncode-collab-application", - "syncode-collab-model", - "thiserror", - "tokio", - "uuid", -] - -[[package]] -name = "synstructure" -version = "0.13.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.119", -] - -[[package]] -name = "tempfile" -version = "3.27.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32497e9a4c7b38532efcdebeef879707aa9f794296a4f0244f6f69e9bc8574bd" -dependencies = [ - "fastrand", - "getrandom 0.4.3", - "once_cell", - "rustix", - "windows-sys 0.61.2", -] - -[[package]] -name = "thiserror" -version = "2.0.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec86235f5fcc2a73650310756d2ac5b138a5780bbbdfae3eeccec992c435ba4f" -dependencies = [ - "thiserror-impl", -] - -[[package]] -name = "thiserror-impl" -version = "2.0.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc04cd3e1236dd4a98afca4569f2deb3f120e5422a4023be2cb683f8486292af" -dependencies = [ - "proc-macro2", - "quote", - "syn 3.0.4", -] - -[[package]] -name = "tinystr" -version = "0.8.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1e27c91459209c2986af3dcf603a5a74a4368754ce37414f59acc971167f643" -dependencies = [ - "displaydoc", - "zerovec", -] - -[[package]] -name = "tinyvec" -version = "1.12.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bb4ebadaa0af04fab11ae01eb5f9fdb5f9c5b875506e210e71c07873528baa7f" -dependencies = [ - "tinyvec_macros", -] - -[[package]] -name = "tinyvec_macros" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" - -[[package]] -name = "tokio" -version = "1.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "202caea871b69668250d242070849eb495be178ed697a3e98aebce5bc81a0bed" -dependencies = [ - "bytes", - "libc", - "mio", - "pin-project-lite", - "signal-hook-registry", - "socket2", - "tokio-macros", - "windows-sys 0.61.2", -] - -[[package]] -name = "tokio-macros" -version = "2.7.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78773a2a397f451582ce068015985c33193cf6dea8b74d2a639fe457b2f07b0e" -dependencies = [ - "proc-macro2", - "quote", - "syn 3.0.4", -] - -[[package]] -name = "tokio-stream" -version = "0.1.19" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a3d06f0b082ba57c26b79407372e57cf2a1e28124f78e9479fe80322cf53420b" -dependencies = [ - "futures-core", - "pin-project-lite", - "tokio", -] - -[[package]] -name = "tokio-tungstenite" -version = "0.29.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f72a05e828585856dacd553fba484c242c46e391fb0e58917c942ee9202915c" -dependencies = [ - "futures-util", - "log", - "tokio", - "tungstenite", -] - -[[package]] -name = "tokio-util" -version = "0.7.19" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "494815d09bf52b5548659851081238f0ca39ff638363907596da739561c62c52" -dependencies = [ - "bytes", - "futures-core", - "futures-io", - "futures-sink", - "libc", - "pin-project-lite", - "tokio", -] - -[[package]] -name = "toml_datetime" -version = "1.1.1+spec-1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3165f65f62e28e0115a00b2ebdd37eb6f3b641855f9d636d3cd4103767159ad7" -dependencies = [ - "serde_core", -] - -[[package]] -name = "toml_edit" -version = "0.25.13+spec-1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6975367e4d2ef766d86af01ffad14b622fecc8d4357a998fbc4deb6e9bacaf9b" -dependencies = [ - "indexmap", - "toml_datetime", - "toml_parser", - "winnow", -] - -[[package]] -name = "toml_parser" -version = "1.1.3+spec-1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d38ac1cf9b95face32296c0a3ede1fdc270627c9d9c02a7274dd6d960dc4d56" -dependencies = [ - "winnow", -] - -[[package]] -name = "tonic" -version = "0.14.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac2a5518c70fa84342385732db33fb3f44bc4cc748936eb5833d2df34d6445ef" -dependencies = [ - "async-trait", - "axum", - "base64", - "bytes", - "h2", - "http", - "http-body", - "http-body-util", - "hyper", - "hyper-timeout", - "hyper-util", - "percent-encoding", - "pin-project", - "socket2", - "sync_wrapper", - "tokio", - "tokio-stream", - "tower", - "tower-layer", - "tower-service", - "tracing", -] - -[[package]] -name = "tonic-build" -version = "0.14.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c68f61875ac5293cf72e6c8cf0158086428c82c37229e98c840878f1706b0322" -dependencies = [ - "prettyplease", - "proc-macro2", - "quote", - "syn 2.0.119", -] - -[[package]] -name = "tonic-prost" -version = "0.14.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50849f68853be452acf590cde0b146665b8d507b3b8af17261df47e02c209ea0" -dependencies = [ - "bytes", - "prost", - "tonic", -] - -[[package]] -name = "tonic-prost-build" -version = "0.14.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "654e5643eff75d7f8c99197ce1440ed19a3474eada74c12bbac488b2cafdae27" -dependencies = [ - "prettyplease", - "proc-macro2", - "prost-build", - "prost-types", - "quote", - "syn 2.0.119", - "tempfile", - "tonic-build", -] - -[[package]] -name = "tower" -version = "0.5.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ebe5ef63511595f1344e2d5cfa636d973292adc0eec1f0ad45fae9f0851ab1d4" -dependencies = [ - "futures-core", - "futures-util", - "indexmap", - "pin-project-lite", - "slab", - "sync_wrapper", - "tokio", - "tokio-util", - "tower-layer", - "tower-service", - "tracing", -] - -[[package]] -name = "tower-layer" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "121c2a6cda46980bb0fcd1647ffaf6cd3fc79a013de288782836f6df9c48780e" - -[[package]] -name = "tower-service" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" - -[[package]] -name = "tracing" -version = "0.1.44" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100" -dependencies = [ - "log", - "pin-project-lite", - "tracing-attributes", - "tracing-core", -] - -[[package]] -name = "tracing-attributes" -version = "0.1.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.119", -] - -[[package]] -name = "tracing-core" -version = "0.1.36" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a" -dependencies = [ - "once_cell", -] - -[[package]] -name = "try-lock" -version = "0.2.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" - -[[package]] -name = "tungstenite" -version = "0.29.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c01152af293afb9c7c2a57e4b559c5620b421f6d133261c60dd2d0cdb38e6b8" -dependencies = [ - "bytes", - "data-encoding", - "http", - "httparse", - "log", - "rand 0.9.5", - "sha1", - "thiserror", -] - -[[package]] -name = "typenum" -version = "1.20.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6f5e870be6c3b371b77fe0ee0bafb859fa4964b4404c27de1d380043c4dda20" - -[[package]] -name = "ucd-trie" -version = "0.1.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2896d95c02a80c6d6a5d6e953d479f5ddf2dfdb6a244441010e373ac0fb88971" - -[[package]] -name = "unicase" -version = "2.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dbc4bc3a9f746d862c45cb89d705aa10f187bb96c76001afab07a0d35ce60142" - -[[package]] -name = "unicode-bidi" -version = "0.3.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c1cb5db39152898a79168971543b1cb5020dff7fe43c8dc468b0885f5e29df5" - -[[package]] -name = "unicode-ident" -version = "1.0.24" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" - -[[package]] -name = "unicode-normalization" -version = "0.1.25" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5fd4f6878c9cb28d874b009da9e8d183b5abc80117c40bbd187a1fde336be6e8" -dependencies = [ - "tinyvec", -] - -[[package]] -name = "unicode-properties" -version = "0.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7df058c713841ad818f1dc5d3fd88063241cc61f49f5fbea4b951e8cf5a8d71d" - -[[package]] -name = "untrusted" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" - -[[package]] -name = "url" -version = "2.5.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff67a8a4397373c3ef660812acab3268222035010ab8680ec4215f38ba3d0eed" -dependencies = [ - "form_urlencoded", - "idna", - "percent-encoding", - "serde", -] - -[[package]] -name = "utf8_iter" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" - -[[package]] -name = "utf8parse" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" - -[[package]] -name = "uuid" -version = "1.26.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5772d71c9be8a8a6ac2117d949c5b224c1b72241bb611d9a3012edcf8af7812" -dependencies = [ - "getrandom 0.4.3", - "js-sys", - "serde_core", - "wasm-bindgen", -] - -[[package]] -name = "vcpkg" -version = "0.2.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" - -[[package]] -name = "version_check" -version = "0.9.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" - -[[package]] -name = "want" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" -dependencies = [ - "try-lock", -] - -[[package]] -name = "wasi" -version = "0.11.1+wasi-snapshot-preview1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" - -[[package]] -name = "wasip2" -version = "1.0.4+wasi-0.2.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b67efb37e106e55ce722a510d6b5f9c17f083e5fc79afc2badeb12cc313d9487" -dependencies = [ - "wit-bindgen", -] - -[[package]] -name = "wasite" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8dad83b4f25e74f184f64c43b150b91efe7647395b42289f38e50566d82855b" - -[[package]] -name = "wasm-bindgen" -version = "0.2.127" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b70935747edd64d89de3efa29d73789b806c15798f8e7dca4d8ac356b50ce70" -dependencies = [ - "cfg-if", - "once_cell", - "rustversion", - "wasm-bindgen-macro", - "wasm-bindgen-shared", -] - -[[package]] -name = "wasm-bindgen-macro" -version = "0.2.127" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77775f8f3f7217702089053b94958f8f54061a3f663417df76e19cbdcca29bc1" -dependencies = [ - "quote", - "wasm-bindgen-macro-support", -] - -[[package]] -name = "wasm-bindgen-macro-support" -version = "0.2.127" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e11d33f857dc2fb11b8bc75aee111aa9cbeb12cd9f25efd3d4c2a3dd4e235284" -dependencies = [ - "bumpalo", - "proc-macro2", - "quote", - "syn 2.0.119", - "wasm-bindgen-shared", -] - -[[package]] -name = "wasm-bindgen-shared" -version = "0.2.127" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ef64dbcc55df09c7e5a46182d181c2cfa3e925f3da937ea764728b4bbb9dcbf" -dependencies = [ - "unicode-ident", -] - -[[package]] -name = "webpki-roots" -version = "0.26.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "521bc38abb08001b01866da9f51eb7c5d647a19260e00054a8c7fd5f9e57f7a9" -dependencies = [ - "webpki-roots 1.0.9", -] - -[[package]] -name = "webpki-roots" -version = "1.0.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7dcd9d09a39985f5344844e66b0c530a33843579125f23e21e9f0f220850f22a" -dependencies = [ - "rustls-pki-types", -] - -[[package]] -name = "whoami" -version = "1.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d4a4db5077702ca3015d3d02d74974948aba2ad9e12ab7df718ee64ccd7e97d" -dependencies = [ - "libredox", - "wasite", -] - -[[package]] -name = "windows-link" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" - -[[package]] -name = "windows-sys" -version = "0.48.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" -dependencies = [ - "windows-targets 0.48.5", -] - -[[package]] -name = "windows-sys" -version = "0.52.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" -dependencies = [ - "windows-targets 0.52.6", -] - -[[package]] -name = "windows-sys" -version = "0.61.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" -dependencies = [ - "windows-link", -] - -[[package]] -name = "windows-targets" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" -dependencies = [ - "windows_aarch64_gnullvm 0.48.5", - "windows_aarch64_msvc 0.48.5", - "windows_i686_gnu 0.48.5", - "windows_i686_msvc 0.48.5", - "windows_x86_64_gnu 0.48.5", - "windows_x86_64_gnullvm 0.48.5", - "windows_x86_64_msvc 0.48.5", -] - -[[package]] -name = "windows-targets" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" -dependencies = [ - "windows_aarch64_gnullvm 0.52.6", - "windows_aarch64_msvc 0.52.6", - "windows_i686_gnu 0.52.6", - "windows_i686_gnullvm", - "windows_i686_msvc 0.52.6", - "windows_x86_64_gnu 0.52.6", - "windows_x86_64_gnullvm 0.52.6", - "windows_x86_64_msvc 0.52.6", -] - -[[package]] -name = "windows_aarch64_gnullvm" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" - -[[package]] -name = "windows_aarch64_gnullvm" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" - -[[package]] -name = "windows_aarch64_msvc" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" - -[[package]] -name = "windows_aarch64_msvc" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" - -[[package]] -name = "windows_i686_gnu" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" - -[[package]] -name = "windows_i686_gnu" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" - -[[package]] -name = "windows_i686_gnullvm" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" - -[[package]] -name = "windows_i686_msvc" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" - -[[package]] -name = "windows_i686_msvc" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" - -[[package]] -name = "windows_x86_64_gnu" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" - -[[package]] -name = "windows_x86_64_gnu" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" - -[[package]] -name = "windows_x86_64_gnullvm" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" - -[[package]] -name = "windows_x86_64_gnullvm" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" - -[[package]] -name = "windows_x86_64_msvc" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" - -[[package]] -name = "windows_x86_64_msvc" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" - -[[package]] -name = "winnow" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23b97319f7b8343df12cc98938e5c3eb436064524c8d2b4e30a1d3a36eecdf81" -dependencies = [ - "memchr", -] - -[[package]] -name = "wit-bindgen" -version = "0.57.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ebf944e87a7c253233ad6766e082e3cd714b5d03812acc24c318f549614536e" - -[[package]] -name = "writeable" -version = "0.6.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3ad82d2a33cdc9674dc7465672f271e096168fcdbe0f799d9e6db8c5892679dc" - -[[package]] -name = "yoke" -version = "0.8.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "709fe23a0424b6a435d82152b1bd3fdfb0833487d5fa90d05d42762a9891fef5" -dependencies = [ - "stable_deref_trait", - "yoke-derive", - "zerofrom", -] - -[[package]] -name = "yoke-derive" -version = "0.8.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "de844c262c8848816172cef550288e7dc6c7b7814b4ee56b3e1553f275f1858e" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.119", - "synstructure", -] - -[[package]] -name = "zerocopy" -version = "0.8.56" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "556764e583adb45a9f8d413c2a147fa7e8d821e48e12b14fd560b607998b75eb" -dependencies = [ - "zerocopy-derive", -] - -[[package]] -name = "zerocopy-derive" -version = "0.8.56" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2ab42fc20575779bd240faa45f94a74256f755c0fa9e89f0ede20d91d0cdfc1" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.119", -] - -[[package]] -name = "zerofrom" -version = "0.1.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ec05a11813ea801ff6d75110ad09cd0824ddba17dfe17128ea0d5f68e6c5272" -dependencies = [ - "zerofrom-derive", -] - -[[package]] -name = "zerofrom-derive" -version = "0.1.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "11532158c46691caf0f2593ea8358fed6bbf68a0315e80aae9bd41fbade684a1" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.119", - "synstructure", -] - -[[package]] -name = "zeroize" -version = "1.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e13c156562582aa81c60cb29407084cdb54c4164760106ab78e6c5b0858cf64e" - -[[package]] -name = "zerotrie" -version = "0.2.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ea269c3bd32f0a32c321907a2ae912ba6f4649bb0fc764a15627e99a7095a3f" -dependencies = [ - "displaydoc", - "yoke", - "zerofrom", -] - -[[package]] -name = "zerovec" -version = "0.11.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bb0464e17806c1d976d5cba29399c7f08e516e279e2ba493f63123b5fca67dd8" -dependencies = [ - "yoke", - "zerofrom", - "zerovec-derive", -] - -[[package]] -name = "zerovec-derive" -version = "0.11.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34df6fc39dbd26ddc9c10e6a2984476e13acce22e64e4487636ef494369225da" -dependencies = [ - "proc-macro2", - "quote", - "syn 3.0.4", -] - -[[package]] -name = "zmij" -version = "1.0.23" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "29666d0abbfad1e3dc4dcf6144730dd3a3ab225bbbdac83319345b1b44ccfc1b" +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "Inflector" +version = "0.11.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe438c63458706e03479442743baae6c88256498e6431708f6dfc520a26515d3" + +[[package]] +name = "aho-corasick" +version = "1.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c982642fa9e8606056828ee9a8505737230110bb1099153c79efe865c59d12ba" +dependencies = [ + "memchr", +] + +[[package]] +name = "allocator-api2" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" + +[[package]] +name = "android_system_properties" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae221649c9976a6f6c56ae1facf410f3ddb33cc661c4b7b61020a912d4237fbc" +dependencies = [ + "libc", +] + +[[package]] +name = "anstream" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "824a212faf96e9acacdbd09febd34438f8f711fb84e09a8916013cd7815ca28d" +dependencies = [ + "anstyle", + "anstyle-parse", + "anstyle-query", + "anstyle-wincon", + "colorchoice", + "is_terminal_polyfill", + "utf8parse", +] + +[[package]] +name = "anstyle" +version = "1.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "940b3a0ca603d1eade50a4846a2afffd5ef57a9feac2c0e2ec2e14f9ead76000" + +[[package]] +name = "anstyle-parse" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52ce7f38b242319f7cabaa6813055467063ecdc9d355bbb4ce0c68908cd8130e" +dependencies = [ + "utf8parse", +] + +[[package]] +name = "anstyle-query" +version = "1.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc" +dependencies = [ + "windows-sys 0.61.2", +] + +[[package]] +name = "anstyle-wincon" +version = "3.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d" +dependencies = [ + "anstyle", + "once_cell_polyfill", + "windows-sys 0.61.2", +] + +[[package]] +name = "anyhow" +version = "1.0.104" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "330a5ed07fa54e4702c9d6c4174f74427fc0ef6e214bbd677ae50a5099946470" + +[[package]] +name = "ascii_utils" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "71938f30533e4d95a6d17aa530939da3842c2ab6f4f84b9dae68447e4129f74a" + +[[package]] +name = "async-graphql" +version = "7.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1057a9f7ccf2404d94571dec3451ade1cb524790df6f1ada0d19c2a49f6b0f40" +dependencies = [ + "async-graphql-derive", + "async-graphql-parser", + "async-graphql-value", + "async-io", + "async-trait", + "asynk-strim", + "base64", + "bytes", + "fast_chemail", + "fnv", + "futures-util", + "handlebars", + "http", + "indexmap", + "mime", + "multer", + "num-traits", + "pin-project-lite", + "regex", + "serde", + "serde_json", + "serde_urlencoded", + "static_assertions_next", + "tempfile", + "thiserror", +] + +[[package]] +name = "async-graphql-axum" +version = "7.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1e37c5532e4b686acf45e7162bc93da91fc2c702fb0d465efc2c20c8f973795" +dependencies = [ + "async-graphql", + "axum", + "bytes", + "futures-util", + "serde_json", + "tokio", + "tokio-stream", + "tokio-util", + "tower-service", +] + +[[package]] +name = "async-graphql-derive" +version = "7.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e6cbeadc8515e66450fba0985ce722192e28443697799988265d86304d7cc68" +dependencies = [ + "Inflector", + "async-graphql-parser", + "darling 0.23.0", + "proc-macro-crate", + "proc-macro2", + "quote", + "strum", + "syn 2.0.119", + "thiserror", +] + +[[package]] +name = "async-graphql-parser" +version = "7.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e64ef70f77a1c689111e52076da1cd18f91834bcb847de0a9171f83624b07fbf" +dependencies = [ + "async-graphql-value", + "pest", + "serde", + "serde_json", +] + +[[package]] +name = "async-graphql-value" +version = "7.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3e3ef112905abea9dea592fc868a6873b10ebd3f983e83308f995d6284e9ba41" +dependencies = [ + "bytes", + "indexmap", + "serde", + "serde_json", +] + +[[package]] +name = "async-io" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "456b8a8feb6f42d237746d4b3e9a178494627745c3c56c6ea55d92ba50d026fc" +dependencies = [ + "autocfg", + "cfg-if", + "concurrent-queue", + "futures-io", + "futures-lite", + "parking", + "polling", + "rustix", + "slab", + "windows-sys 0.61.2", +] + +[[package]] +name = "async-trait" +version = "0.1.92" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "82f6aeea286b8eb4dd3431a1be1b59d290ace00f5bfd8e2a159bc2a05e2c1667" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.4", +] + +[[package]] +name = "asynk-strim" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52697735bdaac441a29391a9e97102c74c6ef0f9b60a40cf109b1b404e29d2f6" +dependencies = [ + "futures-core", + "pin-project-lite", +] + +[[package]] +name = "atoi" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f28d99ec8bfea296261ca1af174f24225171fea9664ba9003cbebee704810528" +dependencies = [ + "num-traits", +] + +[[package]] +name = "atomic-waker" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" + +[[package]] +name = "autocfg" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2032f911046de80f0a198e0901378627c33f59ea0ac00e363d481118bd70a53" + +[[package]] +name = "axum" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "31b698c5f9a010f6573133b09e0de5408834d0c82f8d7475a89fc1867a71cd90" +dependencies = [ + "axum-core", + "base64", + "bytes", + "form_urlencoded", + "futures-util", + "http", + "http-body", + "http-body-util", + "hyper", + "hyper-util", + "itoa", + "matchit", + "memchr", + "mime", + "percent-encoding", + "pin-project-lite", + "serde_core", + "serde_json", + "serde_path_to_error", + "serde_urlencoded", + "sha1", + "sync_wrapper", + "tokio", + "tokio-tungstenite", + "tower", + "tower-layer", + "tower-service", + "tracing", +] + +[[package]] +name = "axum-core" +version = "0.5.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08c78f31d7b1291f7ee735c1c6780ccde7785daae9a9206026862dab7d8792d1" +dependencies = [ + "bytes", + "futures-core", + "http", + "http-body", + "http-body-util", + "mime", + "pin-project-lite", + "sync_wrapper", + "tower-layer", + "tower-service", + "tracing", +] + +[[package]] +name = "base64" +version = "0.22.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" + +[[package]] +name = "base64ct" +version = "1.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2af50177e190e07a26ab74f8b1efbfe2ef87da2116221318cb1c2e82baf7de06" + +[[package]] +name = "bitflags" +version = "2.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b588b76d00fde79687d7646a9b5bdf3cc0f655e0bbd080335a95d7e96f3587da" +dependencies = [ + "serde_core", +] + +[[package]] +name = "block-buffer" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array", +] + +[[package]] +name = "block-buffer" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2f6c7dbe95a6ed67ad9f18e57daf93a2f034c524b99fd2b76d18fdfeb6660aa" +dependencies = [ + "hybrid-array", +] + +[[package]] +name = "bumpalo" +version = "3.20.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72f5acc6cb2ba439de613abc23857ec3d78374d8ed5ac84e9d11336e87da8649" + +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + +[[package]] +name = "bytes" +version = "1.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc652a48c352aef3ea3aed32080501cf3ef6ed5da78602a020c991775b0aff04" +dependencies = [ + "serde", +] + +[[package]] +name = "cc" +version = "1.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ad534f4357a5264cce5019c989cf66a4f0dc4e0d1b1d15f8aacec0ff7360273" +dependencies = [ + "find-msvc-tools", + "shlex", +] + +[[package]] +name = "cfg-if" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" + +[[package]] +name = "chrono" +version = "0.4.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1aa79e62e7697b8e29b513a68abacf485adcd1fe8284a4316c5ae868e6633327" +dependencies = [ + "iana-time-zone", + "js-sys", + "num-traits", + "serde", + "wasm-bindgen", + "windows-link", +] + +[[package]] +name = "clap" +version = "4.6.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "473c7e07f409a8d772161724aa8db6a765a2532a70f9667eeb7b49d3d02fbdca" +dependencies = [ + "clap_builder", + "clap_derive", +] + +[[package]] +name = "clap_builder" +version = "4.6.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b48fea5a88e9ae728a2dcbedbfc0e730f7d60da42e1cb049a83c9fb8b789889" +dependencies = [ + "anstream", + "anstyle", + "clap_lex", + "strsim", +] + +[[package]] +name = "clap_derive" +version = "4.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d012d2b9d65aca7f18f4d9878a045bc17899bba951561ba5ec3c2ba1eed9a061" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn 3.0.4", +] + +[[package]] +name = "clap_lex" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8d4a3bb8b1e0c1050499d1815f5ab16d04f0959b233085fb31653fbfc9d98f9" + +[[package]] +name = "cmov" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c9ea0ac24bc397ab3c98583a3c9ba74fa56b09a4449bbe172b9b1ddb016027a" + +[[package]] +name = "colorchoice" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d07550c9036bf2ae0c684c4297d503f838287c83c53686d05370d0e139ae570" + +[[package]] +name = "concurrent-queue" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ca0197aee26d1ae37445ee532fefce43251d24cc7c166799f4d46817f1d3973" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "const-oid" +version = "0.9.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" + +[[package]] +name = "const-oid" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6ef517f0926dd24a1582492c791b6a4818a4d94e789a334894aa15b0d12f55c" + +[[package]] +name = "core-foundation-sys" +version = "0.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" + +[[package]] +name = "cpufeatures" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" +dependencies = [ + "libc", +] + +[[package]] +name = "cpufeatures" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ca28b0ae3115b884660db4118d803791fd6756b6e88f39c0f3f7859060d7566" +dependencies = [ + "libc", +] + +[[package]] +name = "crc" +version = "3.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5eb8a2a1cd12ab0d987a5d5e825195d372001a4094a0376319d5a0ad71c1ba0d" +dependencies = [ + "crc-catalog", +] + +[[package]] +name = "crc-catalog" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "217698eaf96b4a3f0bc4f3662aaa55bdf913cd54d7204591faa790070c6d0853" + +[[package]] +name = "crossbeam-queue" +version = "0.3.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "803d13fb3b09d88be9f4dbc29062c66b19bf7170867ceb746d2a8689bf6c7a26" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61803da095bee82a81bb1a452ecc25d3b2f1416d1897eb86430c6159ef717c17" + +[[package]] +name = "crypto-common" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78c8292055d1c1df0cce5d180393dc8cce0abec0a7102adb6c7b1eef6016d60a" +dependencies = [ + "generic-array", + "typenum", +] + +[[package]] +name = "crypto-common" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce6e4c961d6cd6c9a86db418387425e8bdeaf05b3c8bc1411e6dca4c252f1453" +dependencies = [ + "hybrid-array", +] + +[[package]] +name = "ctutils" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d5515a3834141de9eafb9717ad39eea8247b5674e6066c404e8c4b365d2a29e" +dependencies = [ + "cmov", +] + +[[package]] +name = "darling" +version = "0.20.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc7f46116c46ff9ab3eb1597a45688b6715c6e628b5c133e288e709a29bcb4ee" +dependencies = [ + "darling_core 0.20.11", + "darling_macro 0.20.11", +] + +[[package]] +name = "darling" +version = "0.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25ae13da2f202d56bd7f91c25fba009e7717a1e4a1cc98a76d844b65ae912e9d" +dependencies = [ + "darling_core 0.23.0", + "darling_macro 0.23.0", +] + +[[package]] +name = "darling_core" +version = "0.20.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d00b9596d185e565c2207a0b01f8bd1a135483d02d9b7b0a54b11da8d53412e" +dependencies = [ + "fnv", + "ident_case", + "proc-macro2", + "quote", + "strsim", + "syn 2.0.119", +] + +[[package]] +name = "darling_core" +version = "0.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9865a50f7c335f53564bb694ef660825eb8610e0a53d3e11bf1b0d3df31e03b0" +dependencies = [ + "ident_case", + "proc-macro2", + "quote", + "strsim", + "syn 2.0.119", +] + +[[package]] +name = "darling_macro" +version = "0.20.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc34b93ccb385b40dc71c6fceac4b2ad23662c7eeb248cf10d529b7e055b6ead" +dependencies = [ + "darling_core 0.20.11", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "darling_macro" +version = "0.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3984ec7bd6cfa798e62b4a642426a5be0e68f9401cfc2a01e3fa9ea2fcdb8d" +dependencies = [ + "darling_core 0.23.0", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "data-encoding" +version = "2.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4583a4551df46e2792f82ceeac45e850d2e2d5debba0b91f102385cda5b11f06" + +[[package]] +name = "der" +version = "0.7.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7c1832837b905bbfb5101e07cc24c8deddf52f93225eee6ead5f4d63d53ddcb" +dependencies = [ + "const-oid 0.9.6", + "pem-rfc7468", + "zeroize", +] + +[[package]] +name = "derive_builder" +version = "0.20.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "507dfb09ea8b7fa618fcf76e953f4f5e192547945816d5358edffe39f6f94947" +dependencies = [ + "derive_builder_macro", +] + +[[package]] +name = "derive_builder_core" +version = "0.20.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d5bcf7b024d6835cfb3d473887cd966994907effbe9227e8c8219824d06c4e8" +dependencies = [ + "darling 0.20.11", + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "derive_builder_macro" +version = "0.20.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab63b0e2bf4d5928aff72e83a7dace85d7bba5fe12dcc3c5a572d78caffd3f3c" +dependencies = [ + "derive_builder_core", + "syn 2.0.119", +] + +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer 0.10.4", + "const-oid 0.9.6", + "crypto-common 0.1.7", + "subtle", +] + +[[package]] +name = "digest" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1dd6dbb5841937940781866fa1281a1ff7bd3bf827091440879f9994983d5c2" +dependencies = [ + "block-buffer 0.12.1", + "const-oid 0.10.2", + "crypto-common 0.2.2", + "ctutils", +] + +[[package]] +name = "displaydoc" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6232dd377dcc64799954cbd3a9bb882e9cdc1308ccd87b1c098f1fb2eaf82a8" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.4", +] + +[[package]] +name = "dotenvy" +version = "0.15.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1aaf95b3e5c8f23aa320147307562d361db0ae0d51242340f558153b4eb2439b" + +[[package]] +name = "either" +version = "1.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "252afb9ae5eaa683babdc6a068b3f5726eb19e05070c731f9b2a23a7c3e8ed34" +dependencies = [ + "serde", +] + +[[package]] +name = "encoding_rs" +version = "0.8.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75030f3c4f45dafd7586dd6780965a8c7e8e285a5ecb86713e63a79c5b2766f3" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "equivalent" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" + +[[package]] +name = "errno" +version = "0.3.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" +dependencies = [ + "libc", + "windows-sys 0.61.2", +] + +[[package]] +name = "etcetera" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "136d1b5283a1ab77bd9257427ffd09d8667ced0570b6f938942bc7568ed5b943" +dependencies = [ + "cfg-if", + "home", + "windows-sys 0.48.0", +] + +[[package]] +name = "event-listener" +version = "5.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a23add41df1562121a9393cb065eab5146a1242410f23a644851e90cfd669d2" +dependencies = [ + "parking", + "pin-project-lite", +] + +[[package]] +name = "fast_chemail" +version = "0.9.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "495a39d30d624c2caabe6312bfead73e7717692b44e0b32df168c275a2e8e9e4" +dependencies = [ + "ascii_utils", +] + +[[package]] +name = "fastrand" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da7c62ceae207dd37ea5b845da6a0696c799f85e97da1ab5b7910be3c1c80223" + +[[package]] +name = "find-msvc-tools" +version = "0.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d45db016d36b838f563236e9193d0ee6ce38f3f68b6c94e914b4929c96bbb890" + +[[package]] +name = "fixedbitset" +version = "0.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d674e81391d1e1ab681a28d99df07927c6d4aa5b027d7da16ba32d1d21ecd99" + +[[package]] +name = "flume" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da0e4dd2a88388a1f4ccc7c9ce104604dab68d9f408dc34cd45823d5a9069095" +dependencies = [ + "futures-core", + "futures-sink", + "spin", +] + +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + +[[package]] +name = "foldhash" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" + +[[package]] +name = "form_urlencoded" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb4cb245038516f5f85277875cdaa4f7d2c9a0fa0468de06ed190163b1581fcf" +dependencies = [ + "percent-encoding", +] + +[[package]] +name = "futures-channel" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1f9e3d69d39e4862ffed03ed071a76f9a13ba1d9109d355b0f0aa6b15e393c4" +dependencies = [ + "futures-core", + "futures-sink", +] + +[[package]] +name = "futures-core" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92d699e522242e69e3003b94ecc1f960f3a5e015aa7c5d7486e65ad01dd94f5e" + +[[package]] +name = "futures-executor" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "031b47cf1a3c6cc8bc2fc76cd437f521619387907d469316e7c0bc278f1f5432" +dependencies = [ + "futures-core", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-intrusive" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d930c203dd0b6ff06e0201a4a2fe9149b43c684fd4420555b26d21b1a02956f" +dependencies = [ + "futures-core", + "lock_api", + "parking_lot", +] + +[[package]] +name = "futures-io" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53c0fa8157de1303bfffdaa1cc2a673bfffb60102f76b0ef4441659124373fed" + +[[package]] +name = "futures-lite" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f78e10609fe0e0b3f4157ffab1876319b5b0db102a2c60dc4626306dc46b44ad" +dependencies = [ + "futures-core", + "pin-project-lite", +] + +[[package]] +name = "futures-macro" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fb9654ba8355388abeb8dcb4fc62f511300867002afc858860463bdd9fe0c44" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.4", +] + +[[package]] +name = "futures-sink" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1944426bf7d03f1d14f708785e4b33efd750b36d48a157b836b3efc15ede8e1d" + +[[package]] +name = "futures-task" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd417de3d1d015fc3bfd2b1ea46dfc7bab72ef86f1cc7cc9c78e728b34a6d1fd" + +[[package]] +name = "futures-util" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d50a92467f8ba5dd6e3ee5d4bd04d73ab2e4e1c44474a0674821dfce14b79bc" +dependencies = [ + "futures-core", + "futures-io", + "futures-macro", + "futures-sink", + "futures-task", + "memchr", + "pin-project-lite", + "slab", +] + +[[package]] +name = "generic-array" +version = "0.14.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +dependencies = [ + "typenum", + "version_check", +] + +[[package]] +name = "getrandom" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0" +dependencies = [ + "cfg-if", + "libc", + "wasi", +] + +[[package]] +name = "getrandom" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" +dependencies = [ + "cfg-if", + "libc", + "r-efi 5.3.0", + "wasip2", +] + +[[package]] +name = "getrandom" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "300e883d756b2e4ec94e02791f39b04b522276138852cfc41d9fb7e904106099" +dependencies = [ + "cfg-if", + "libc", + "r-efi 6.0.0", +] + +[[package]] +name = "h2" +version = "0.4.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef8e5e5a340588f4452631496976cf8636d4a7ecf600239fdc27615d2530bc16" +dependencies = [ + "atomic-waker", + "bytes", + "fnv", + "futures-core", + "futures-sink", + "http", + "indexmap", + "slab", + "tokio", + "tokio-util", + "tracing", +] + +[[package]] +name = "handlebars" +version = "6.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75c54236f9045c8004a77942bebc52145b4844639db934a5c70fe08617fbe61a" +dependencies = [ + "derive_builder", + "log", + "num-order", + "pest", + "pest_derive", + "serde", + "serde_json", + "thiserror", +] + +[[package]] +name = "hashbrown" +version = "0.15.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" +dependencies = [ + "allocator-api2", + "equivalent", + "foldhash", +] + +[[package]] +name = "hashbrown" +version = "0.17.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a" + +[[package]] +name = "hashlink" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7382cf6263419f2d8df38c55d7da83da5c18aef87fc7a7fc1fb1e344edfe14c1" +dependencies = [ + "hashbrown 0.15.5", +] + +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + +[[package]] +name = "hermit-abi" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc0fef456e4baa96da950455cd02c081ca953b141298e41db3fc7e36b1da849c" + +[[package]] +name = "hex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" + +[[package]] +name = "hkdf" +version = "0.12.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b5f8eb2ad728638ea2c7d47a21db23b7b58a72ed6a38256b8a1849f15fbbdf7" +dependencies = [ + "hmac 0.12.1", +] + +[[package]] +name = "hmac" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" +dependencies = [ + "digest 0.10.7", +] + +[[package]] +name = "hmac" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6303bc9732ae41b04cb554b844a762b4115a61bfaa81e3e83050991eeb56863f" +dependencies = [ + "digest 0.11.3", +] + +[[package]] +name = "home" +version = "0.5.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc627f471c528ff0c4a49e1d5e60450c8f6461dd6d10ba9dcd3a61d3dff7728d" +dependencies = [ + "windows-sys 0.61.2", +] + +[[package]] +name = "http" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "918d3568bebf352712bc2ef3d46a8bcf1a75b373be6539de198e9105cbbf9ce0" +dependencies = [ + "bytes", + "itoa", +] + +[[package]] +name = "http-body" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca2a8f2913ee65f60facd6a5905613afaa448497a0230cc41ce022d93290bc2c" +dependencies = [ + "bytes", + "http", +] + +[[package]] +name = "http-body-util" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23169fe34a5fbcdd3f3862e78fb9b6fccd5f02a6dc6f732547005d45631ce71c" +dependencies = [ + "bytes", + "futures-core", + "http", + "http-body", + "pin-project-lite", +] + +[[package]] +name = "httparse" +version = "1.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87" + +[[package]] +name = "httpdate" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" + +[[package]] +name = "hybrid-array" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "707114b52a152fa7bdb290cd7cd5912d9467273b6d74e21b8d81aca1f8533f6b" +dependencies = [ + "typenum", +] + +[[package]] +name = "hyper" +version = "1.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "27b501faa50e7a26c3d3560ca625132f4078a17771f4810baf70475ae48cbe43" +dependencies = [ + "atomic-waker", + "bytes", + "futures-channel", + "futures-core", + "h2", + "http", + "http-body", + "httparse", + "httpdate", + "itoa", + "pin-project-lite", + "smallvec", + "tokio", + "want", +] + +[[package]] +name = "hyper-timeout" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b90d566bffbce6a75bd8b09a05aa8c2cb1fabb6cb348f8840c9e4c90a0d83b0" +dependencies = [ + "hyper", + "hyper-util", + "pin-project-lite", + "tokio", + "tower-service", +] + +[[package]] +name = "hyper-util" +version = "0.1.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96547c2556ec9d12fb1578c4eaf448b04993e7fb79cbaad930a656880a6bdfa0" +dependencies = [ + "bytes", + "futures-channel", + "futures-util", + "http", + "http-body", + "hyper", + "libc", + "pin-project-lite", + "socket2", + "tokio", + "tower-service", + "tracing", +] + +[[package]] +name = "iana-time-zone" +version = "0.1.65" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e31bc9ad994ba00e440a8aa5c9ef0ec67d5cb5e5cb0cc7f8b744a35b389cc470" +dependencies = [ + "android_system_properties", + "core-foundation-sys", + "iana-time-zone-haiku", + "js-sys", + "log", + "wasm-bindgen", + "windows-core", +] + +[[package]] +name = "iana-time-zone-haiku" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" +dependencies = [ + "cc", +] + +[[package]] +name = "icu_collections" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa68d21081c4a05d5a901a1c62add574c77048b6a1c67be3b50ce0b60d4ca513" +dependencies = [ + "displaydoc", + "potential_utf", + "utf8_iter", + "yoke", + "zerofrom", + "zerovec", +] + +[[package]] +name = "icu_locale_core" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d56e28588da92eee5c3201a6eff33fabdd49b62269c8938d4ff050ce4d900deb" +dependencies = [ + "displaydoc", + "litemap", + "tinystr", + "writeable", + "zerovec", +] + +[[package]] +name = "icu_normalizer" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "12f9cf5f235641ed274641dd81c3f28d870e276763d0797aeeab72317b1c646f" +dependencies = [ + "icu_collections", + "icu_normalizer_data", + "icu_properties", + "icu_provider", + "smallvec", + "zerovec", +] + +[[package]] +name = "icu_normalizer_data" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1563da1ed3e0b3bf3d74c9b85917ac9c56464d2f57242270c09c9e752f8021a0" + +[[package]] +name = "icu_properties" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e7ca276ad3145661a65914e6daf131ca5120cd3dcee8f8f3214b8875184a148" +dependencies = [ + "displaydoc", + "icu_collections", + "icu_locale_core", + "icu_properties_data", + "icu_provider", + "zerotrie", + "zerovec", +] + +[[package]] +name = "icu_properties_data" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e590f038c1464a96894fd6d10127e90a8be4509f56ff7ecef851b15cee0b7caa" + +[[package]] +name = "icu_provider" +version = "2.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d27bbb9d3abbefac45d55f647c9de1d44aafcd1186eb91879afef17c396c3e73" +dependencies = [ + "displaydoc", + "icu_locale_core", + "writeable", + "yoke", + "zerofrom", + "zerotrie", + "zerovec", +] + +[[package]] +name = "ident_case" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" + +[[package]] +name = "idna" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b0875f23caa03898994f6ddc501886a45c7d3d62d04d2d90788d47be1b1e4de" +dependencies = [ + "idna_adapter", + "smallvec", + "utf8_iter", +] + +[[package]] +name = "idna_adapter" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb68373c0d6620ef8105e855e7745e18b0d00d3bdb07fb532e434244cdb9a714" +dependencies = [ + "icu_normalizer", + "icu_properties", +] + +[[package]] +name = "indexmap" +version = "2.14.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07aa2048142242915a31d35844fb311e0e53fcca590c3a0a40dcf1b841fa09eb" +dependencies = [ + "equivalent", + "hashbrown 0.17.1", + "serde", + "serde_core", +] + +[[package]] +name = "is_terminal_polyfill" +version = "1.70.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695" + +[[package]] +name = "itertools" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b192c782037fadd9cfa75548310488aabdbf3d2da73885b31bd0abd03351285" +dependencies = [ + "either", +] + +[[package]] +name = "itoa" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" + +[[package]] +name = "js-sys" +version = "0.3.104" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e0c1080212aad755ea003d18543e8768dd432c48819efd73a7bf1e39b7a5a3a" +dependencies = [ + "cfg-if", + "futures-util", + "wasm-bindgen", +] + +[[package]] +name = "lazy_static" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" +dependencies = [ + "spin", +] + +[[package]] +name = "libc" +version = "0.2.189" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3eaf3ede3fee6db1a4c2ee091bf8a8b4dccdc6d17f656fb07896ee72867612f2" + +[[package]] +name = "libm" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6d2cec3eae94f9f509c767b45932f1ada8350c4bdb85af2fcab4a3c14807981" + +[[package]] +name = "libredox" +version = "0.1.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7955dfc218a8afb29dfeffd540e3a6e96baeb94fe7138228dd7cc6937fbbf96" +dependencies = [ + "bitflags", + "libc", + "plain", + "redox_syscall 0.9.3", +] + +[[package]] +name = "libsqlite3-sys" +version = "0.30.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e99fb7a497b1e3339bc746195567ed8d3e24945ecd636e3619d20b9de9e9149" +dependencies = [ + "pkg-config", + "vcpkg", +] + +[[package]] +name = "linux-raw-sys" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a66949e030da00e8c7d4434b251670a91556f4144941d37452769c25d58a53" + +[[package]] +name = "litemap" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47d9d19d1d6efa0109d2f65ff4c85cddd50bd572e5a00127ab10987290bcefae" + +[[package]] +name = "lock_api" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965" +dependencies = [ + "scopeguard", +] + +[[package]] +name = "log" +version = "0.4.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9f8bd3e56ce4dfc153cf470fffbfa98c7620958b312ca5c3a4b8d5181fd13c6" + +[[package]] +name = "matchit" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47e1ffaa40ddd1f3ed91f717a33c8c0ee23fff369e3aa8772b9605cc1d22f4c3" + +[[package]] +name = "md-5" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d89e7ee0cfbedfc4da3340218492196241d89eefb6dab27de5df917a6d2e78cf" +dependencies = [ + "cfg-if", + "digest 0.10.7", +] + +[[package]] +name = "memchr" +version = "2.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf8baf1c55e62ffcace7a9f06f4bd9cd3f0c4beb022d3b367256b91b87513d98" + +[[package]] +name = "mime" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" + +[[package]] +name = "mio" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30d65c71f1ce40ab09135ce117d742b9f8a19ff91a41a8b57ed50bc2de59c427" +dependencies = [ + "libc", + "wasi", + "windows-sys 0.61.2", +] + +[[package]] +name = "multer" +version = "3.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "83e87776546dc87511aa5ee218730c92b666d7264ab6ed41f9d215af9cd5224b" +dependencies = [ + "bytes", + "encoding_rs", + "futures-util", + "http", + "httparse", + "memchr", + "mime", + "spin", + "version_check", +] + +[[package]] +name = "multimap" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d87ecb2933e8aeadb3e3a02b828fed80a7528047e68b4f424523a0981a3a084" + +[[package]] +name = "num-bigint-dig" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e661dda6640fad38e827a6d4a310ff4763082116fe217f279885c97f511bb0b7" +dependencies = [ + "lazy_static", + "libm", + "num-integer", + "num-iter", + "num-traits", + "rand 0.8.8", + "smallvec", + "zeroize", +] + +[[package]] +name = "num-integer" +version = "0.1.47" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ce2d95d4b3734dc35aa2f45e1aa22cd416814592a4f9d9205e11affd5b8e10b" +dependencies = [ + "num-traits", +] + +[[package]] +name = "num-iter" +version = "0.1.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c92800bd69a1eac91786bcfe9da64a897eb72911b8dc3095decbd07429e8048b" +dependencies = [ + "num-integer", + "num-traits", +] + +[[package]] +name = "num-modular" +version = "0.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd8e500409e6cd603b03e477c26a6caecdc27ac58979a53e881c75eafc079f44" + +[[package]] +name = "num-order" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "537b596b97c40fcf8056d153049eb22f481c17ebce72a513ec9286e4986d1bb6" +dependencies = [ + "num-modular", +] + +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", + "libm", +] + +[[package]] +name = "once_cell" +version = "1.21.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" + +[[package]] +name = "once_cell_polyfill" +version = "1.70.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe" + +[[package]] +name = "parking" +version = "2.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f38d5652c16fde515bb1ecef450ab0f6a219d619a7274976324d5e377f7dceba" + +[[package]] +name = "parking_lot" +version = "0.12.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall 0.5.18", + "smallvec", + "windows-link", +] + +[[package]] +name = "pem-rfc7468" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88b39c9bfcfc231068454382784bb460aae594343fb030d46e9f50a645418412" +dependencies = [ + "base64ct", +] + +[[package]] +name = "percent-encoding" +version = "2.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" + +[[package]] +name = "pest" +version = "2.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a07a60cc7a4d00c91f95c685609d1d2f79050e6804b70ebedd7650f0b839bcf" +dependencies = [ + "memchr", + "ucd-trie", +] + +[[package]] +name = "pest_derive" +version = "2.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b3a83744a5c8455b8b3e0dc5031362780a347c878bdd11584d1a8984228cc88d" +dependencies = [ + "pest", + "pest_generator", +] + +[[package]] +name = "pest_generator" +version = "2.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e0cd3451aa3de60d4b9a1e736885e4dea6b31617598026f12256ad566d63304a" +dependencies = [ + "pest", + "pest_meta", + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "pest_meta" +version = "2.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e04d3a0849e241d7dfce834c83b1c5edc8622009e8dd51a12ba1927c32f05496" +dependencies = [ + "pest", +] + +[[package]] +name = "petgraph" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8701b58ea97060d5e5b155d383a69952a60943f0e6dfe30b04c287beb0b27455" +dependencies = [ + "fixedbitset", + "hashbrown 0.15.5", + "indexmap", +] + +[[package]] +name = "pin-project" +version = "1.1.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2466b2336ed02bcdca6b294417127b90ec92038d1d5c4fbeac971a922e0e0924" +dependencies = [ + "pin-project-internal", +] + +[[package]] +name = "pin-project-internal" +version = "1.1.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c96395f0a926bc13b1c17622aaddda1ecb55d49c8f1bf9777e4d877800a43f8b" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "pin-project-lite" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd" + +[[package]] +name = "pkcs1" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8ffb9f10fa047879315e6625af03c164b16962a5368d724ed16323b68ace47f" +dependencies = [ + "der", + "pkcs8", + "spki", +] + +[[package]] +name = "pkcs8" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f950b2377845cebe5cf8b5165cb3cc1a5e0fa5cfa3e1f7f55707d8fd82e0a7b7" +dependencies = [ + "der", + "spki", +] + +[[package]] +name = "pkg-config" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6b464fbc74e149a392436b17d523f769e057cb6877f6a5c4618bc6f11800548" + +[[package]] +name = "plain" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4596b6d070b27117e987119b4dac604f3c58cfb0b191112e24771b2faeac1a6" + +[[package]] +name = "polling" +version = "3.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d0e4f59085d47d8241c88ead0f274e8a0cb551f3625263c05eb8dd897c34218" +dependencies = [ + "cfg-if", + "concurrent-queue", + "hermit-abi", + "pin-project-lite", + "rustix", + "windows-sys 0.61.2", +] + +[[package]] +name = "potential_utf" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d83eb9bc6d8e5cf568e7a1101d60ee05e81ed50ea106026f3d18deeb046d7661" +dependencies = [ + "zerovec", +] + +[[package]] +name = "ppv-lite86" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" +dependencies = [ + "zerocopy", +] + +[[package]] +name = "prettyplease" +version = "0.2.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b" +dependencies = [ + "proc-macro2", + "syn 2.0.119", +] + +[[package]] +name = "proc-macro-crate" +version = "3.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e67ba7e9b2b56446f1d419b1d807906278ffa1a658a8a5d8a39dcb1f5a78614f" +dependencies = [ + "toml_edit", +] + +[[package]] +name = "proc-macro2" +version = "1.0.107" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "985e7ec9bb745e6ce6535b544d84d6cd6f7ad8bd711c398938ae983b91a766d9" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "prost" +version = "0.14.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "528ac67416ff8646872a3c02cad9cc4ee5dc9f9540c9b10771855c95cb2e5ae1" +dependencies = [ + "bytes", + "prost-derive", +] + +[[package]] +name = "prost-build" +version = "0.14.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03da047801ff44bb6a4d407d4860c05fd70bb81714e6b2f3812603d5b145b042" +dependencies = [ + "heck", + "itertools", + "log", + "multimap", + "petgraph", + "prettyplease", + "prost", + "prost-types", + "pulldown-cmark", + "pulldown-cmark-to-cmark", + "regex", + "syn 2.0.119", + "tempfile", +] + +[[package]] +name = "prost-derive" +version = "0.14.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b570b25f7617e43d59005d0990ccb79e950a423952cea19671b7a876da390adf" +dependencies = [ + "anyhow", + "itertools", + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "prost-types" +version = "0.14.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f94967dc7688f3054c7fac87473ffae4cc4c3904800e2d9f5b857246d8963b0a" +dependencies = [ + "prost", +] + +[[package]] +name = "protoc-bin-vendored" +version = "3.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d1c381df33c98266b5f08186583660090a4ffa0889e76c7e9a5e175f645a67fa" +dependencies = [ + "protoc-bin-vendored-linux-aarch_64", + "protoc-bin-vendored-linux-ppcle_64", + "protoc-bin-vendored-linux-s390_64", + "protoc-bin-vendored-linux-x86_32", + "protoc-bin-vendored-linux-x86_64", + "protoc-bin-vendored-macos-aarch_64", + "protoc-bin-vendored-macos-x86_64", + "protoc-bin-vendored-win32", +] + +[[package]] +name = "protoc-bin-vendored-linux-aarch_64" +version = "3.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c350df4d49b5b9e3ca79f7e646fde2377b199e13cfa87320308397e1f37e1a4c" + +[[package]] +name = "protoc-bin-vendored-linux-ppcle_64" +version = "3.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a55a63e6c7244f19b5c6393f025017eb5d793fd5467823a099740a7a4222440c" + +[[package]] +name = "protoc-bin-vendored-linux-s390_64" +version = "3.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1dba5565db4288e935d5330a07c264a4ee8e4a5b4a4e6f4e83fad824cc32f3b0" + +[[package]] +name = "protoc-bin-vendored-linux-x86_32" +version = "3.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8854774b24ee28b7868cd71dccaae8e02a2365e67a4a87a6cd11ee6cdbdf9cf5" + +[[package]] +name = "protoc-bin-vendored-linux-x86_64" +version = "3.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b38b07546580df720fa464ce124c4b03630a6fb83e05c336fea2a241df7e5d78" + +[[package]] +name = "protoc-bin-vendored-macos-aarch_64" +version = "3.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89278a9926ce312e51f1d999fee8825d324d603213344a9a706daa009f1d8092" + +[[package]] +name = "protoc-bin-vendored-macos-x86_64" +version = "3.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81745feda7ccfb9471d7a4de888f0652e806d5795b61480605d4943176299756" + +[[package]] +name = "protoc-bin-vendored-win32" +version = "3.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95067976aca6421a523e491fce939a3e65249bac4b977adee0ee9771568e8aa3" + +[[package]] +name = "pulldown-cmark" +version = "0.13.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e9f068eba8e7071c5f9511831b44f32c740d5adf574e990f946ddb53db2f314e" +dependencies = [ + "bitflags", + "memchr", + "unicase", +] + +[[package]] +name = "pulldown-cmark-to-cmark" +version = "22.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab1ad36992cead65f02aa399a373a42730922f1525d988172634fdefdecb8a60" +dependencies = [ + "pulldown-cmark", +] + +[[package]] +name = "quote" +version = "1.0.47" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fbf4db142a473a8d80c26bbf18454ed458bf8d26c8219c331daecfdbd079001" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "r-efi" +version = "5.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" + +[[package]] +name = "r-efi" +version = "6.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf" + +[[package]] +name = "rand" +version = "0.8.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e058c7de0b26af77780c769414d6257830bb240f3c38477dbc2c16e5f54d6d4c" +dependencies = [ + "libc", + "rand_chacha 0.3.1", + "rand_core 0.6.4", +] + +[[package]] +name = "rand" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9ef1d0d795eb7d84685bca4f72f3649f064e6641543d3a8c415898726a57b41" +dependencies = [ + "rand_chacha 0.9.0", + "rand_core 0.9.5", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core 0.6.4", +] + +[[package]] +name = "rand_chacha" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" +dependencies = [ + "ppv-lite86", + "rand_core 0.9.5", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom 0.2.17", +] + +[[package]] +name = "rand_core" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76afc826de14238e6e8c374ddcc1fa19e374fd8dd986b0d2af0d02377261d83c" +dependencies = [ + "getrandom 0.3.4", +] + +[[package]] +name = "redox_syscall" +version = "0.5.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" +dependencies = [ + "bitflags", +] + +[[package]] +name = "redox_syscall" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d678d17679829e73d371e96880897e98fee2ded7acc0a50bdf8af2affa4b2fe5" +dependencies = [ + "bitflags", +] + +[[package]] +name = "regex" +version = "1.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f020237b6c8eed93db2e2cb53c00c60a8e1bc73da7d073199a1180401450218d" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.4.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad8553b9b26413251cbf30e620595c7a41b3887f03da04579c0e6b0d6a06b4b2" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.8.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6f6ff9a378485b298a5286656da665ba74413d36db0979633275d2e708145d4" + +[[package]] +name = "ring" +version = "0.17.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7" +dependencies = [ + "cc", + "cfg-if", + "getrandom 0.2.17", + "libc", + "untrusted", + "windows-sys 0.52.0", +] + +[[package]] +name = "rsa" +version = "0.9.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8573f03f5883dcaebdfcf4725caa1ecb9c15b2ef50c43a07b816e06799bb12d" +dependencies = [ + "const-oid 0.9.6", + "digest 0.10.7", + "num-bigint-dig", + "num-integer", + "num-traits", + "pkcs1", + "pkcs8", + "rand_core 0.6.4", + "signature", + "spki", + "subtle", + "zeroize", +] + +[[package]] +name = "rustix" +version = "1.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6fe4565b9518b83ef4f91bb47ce29620ca828bd32cb7e408f0062e9930ba190" +dependencies = [ + "bitflags", + "errno", + "libc", + "linux-raw-sys", + "windows-sys 0.61.2", +] + +[[package]] +name = "rustls" +version = "0.23.43" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0283386ce02abc0151e1761d08802dfe86c173b0b494af5cbc086574e453da06" +dependencies = [ + "once_cell", + "ring", + "rustls-pki-types", + "rustls-webpki", + "subtle", + "zeroize", +] + +[[package]] +name = "rustls-pki-types" +version = "1.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f4925028c7eb5d1fcdaf196971378ed9d2c1c4efc7dc5d011256f76c99c0a96" +dependencies = [ + "zeroize", +] + +[[package]] +name = "rustls-webpki" +version = "0.103.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3c3cf1d8b1e7d4927e2d154c3fcb02979afb9939629c62cd9048d4f07b60ac2" +dependencies = [ + "ring", + "rustls-pki-types", + "untrusted", +] + +[[package]] +name = "rustversion" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf54715a573b99ac80df0bc206da022bcd442c974952c7b9720069370852e21f" + +[[package]] +name = "ryu" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9774ba4a74de5f7b1c1451ed6cd5285a32eddb5cccb8cc655a4e50009e06477f" + +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[package]] +name = "serde" +version = "1.0.229" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4148590afebada386688f18773da617792bf2ef03ffc1e4cbd2b1d45b023e0ba" +dependencies = [ + "serde_core", + "serde_derive", +] + +[[package]] +name = "serde_core" +version = "1.0.229" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67dca2c9c51e58a4791a4b1ed58308b39c64224d349a935ab5039aa360942a48" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.229" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7a5d71263a5a7d47b41f6b3f06ba276f10cc18b0931f1799f710578e2309348" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.4", +] + +[[package]] +name = "serde_json" +version = "1.0.151" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c841b55ecdae098c80dcae9cf767f6f8a0c2cdb3416bbef72181df4d0fe73f14" +dependencies = [ + "itoa", + "memchr", + "serde", + "serde_core", + "zmij", +] + +[[package]] +name = "serde_path_to_error" +version = "0.1.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "10a9ff822e371bb5403e391ecd83e182e0e77ba7f6fe0160b795797109d1b457" +dependencies = [ + "itoa", + "serde", + "serde_core", +] + +[[package]] +name = "serde_urlencoded" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" +dependencies = [ + "form_urlencoded", + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "sha1" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a978451301f4db1d02937a4ab3ccce137717b81826e79b7d49ffe3244a13c3b8" +dependencies = [ + "cfg-if", + "cpufeatures 0.2.17", + "digest 0.10.7", +] + +[[package]] +name = "sha2" +version = "0.10.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" +dependencies = [ + "cfg-if", + "cpufeatures 0.2.17", + "digest 0.10.7", +] + +[[package]] +name = "sha2" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "446ba717509524cb3f22f17ecc096f10f4822d76ab5c0b9822c5f9c284e825f4" +dependencies = [ + "cfg-if", + "cpufeatures 0.3.1", + "digest 0.11.3", +] + +[[package]] +name = "shlex" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8fadd59c855ef2080decdef8ff161eb6661b86933c9d82e5ba29dc602a55aba" + +[[package]] +name = "signal-hook-registry" +version = "1.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4db69cba1110affc0e9f7bcd48bbf87b3f4fc7c61fc9155afd4c469eb3d6c1b" +dependencies = [ + "errno", + "libc", +] + +[[package]] +name = "signature" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de" +dependencies = [ + "digest 0.10.7", + "rand_core 0.6.4", +] + +[[package]] +name = "slab" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5" + +[[package]] +name = "smallvec" +version = "1.15.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ed6a63f02c8539c91a8685a86f4099661ba3da017932f6ebbea6de3f0fa7c90" +dependencies = [ + "serde", +] + +[[package]] +name = "socket2" +version = "0.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3d1e2c7f27f8d4cb10542a02c49005dbd6e93095799d6f3be745fae9f8fedd4" +dependencies = [ + "libc", + "windows-sys 0.61.2", +] + +[[package]] +name = "spin" +version = "0.9.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3763264f6b73151db08c50ff20d7d8a0b8796e021cdea7ceedad07b80155fa0e" +dependencies = [ + "lock_api", +] + +[[package]] +name = "spki" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d91ed6c858b01f942cd56b37a94b3e0a1798290327d1236e4d9cf4eaca44d29d" +dependencies = [ + "base64ct", + "der", +] + +[[package]] +name = "sqlx" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fefb893899429669dcdd979aff487bd78f4064e5e7907e4269081e0ef7d97dc" +dependencies = [ + "sqlx-core", + "sqlx-macros", + "sqlx-mysql", + "sqlx-postgres", + "sqlx-sqlite", +] + +[[package]] +name = "sqlx-core" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee6798b1838b6a0f69c007c133b8df5866302197e404e8b6ee8ed3e3a5e68dc6" +dependencies = [ + "base64", + "bytes", + "chrono", + "crc", + "crossbeam-queue", + "either", + "event-listener", + "futures-core", + "futures-intrusive", + "futures-io", + "futures-util", + "hashbrown 0.15.5", + "hashlink", + "indexmap", + "log", + "memchr", + "once_cell", + "percent-encoding", + "rustls", + "serde", + "serde_json", + "sha2 0.10.9", + "smallvec", + "thiserror", + "tokio", + "tokio-stream", + "tracing", + "url", + "uuid", + "webpki-roots 0.26.11", +] + +[[package]] +name = "sqlx-macros" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2d452988ccaacfbf5e0bdbc348fb91d7c8af5bee192173ac3636b5fb6e6715d" +dependencies = [ + "proc-macro2", + "quote", + "sqlx-core", + "sqlx-macros-core", + "syn 2.0.119", +] + +[[package]] +name = "sqlx-macros-core" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19a9c1841124ac5a61741f96e1d9e2ec77424bf323962dd894bdb93f37d5219b" +dependencies = [ + "dotenvy", + "either", + "heck", + "hex", + "once_cell", + "proc-macro2", + "quote", + "serde", + "serde_json", + "sha2 0.10.9", + "sqlx-core", + "sqlx-mysql", + "sqlx-postgres", + "sqlx-sqlite", + "syn 2.0.119", + "tokio", + "url", +] + +[[package]] +name = "sqlx-mysql" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa003f0038df784eb8fecbbac13affe3da23b45194bd57dba231c8f48199c526" +dependencies = [ + "atoi", + "base64", + "bitflags", + "byteorder", + "bytes", + "chrono", + "crc", + "digest 0.10.7", + "dotenvy", + "either", + "futures-channel", + "futures-core", + "futures-io", + "futures-util", + "generic-array", + "hex", + "hkdf", + "hmac 0.12.1", + "itoa", + "log", + "md-5", + "memchr", + "once_cell", + "percent-encoding", + "rand 0.8.8", + "rsa", + "serde", + "sha1", + "sha2 0.10.9", + "smallvec", + "sqlx-core", + "stringprep", + "thiserror", + "tracing", + "uuid", + "whoami", +] + +[[package]] +name = "sqlx-postgres" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db58fcd5a53cf07c184b154801ff91347e4c30d17a3562a635ff028ad5deda46" +dependencies = [ + "atoi", + "base64", + "bitflags", + "byteorder", + "chrono", + "crc", + "dotenvy", + "etcetera", + "futures-channel", + "futures-core", + "futures-util", + "hex", + "hkdf", + "hmac 0.12.1", + "home", + "itoa", + "log", + "md-5", + "memchr", + "once_cell", + "rand 0.8.8", + "serde", + "serde_json", + "sha2 0.10.9", + "smallvec", + "sqlx-core", + "stringprep", + "thiserror", + "tracing", + "uuid", + "whoami", +] + +[[package]] +name = "sqlx-sqlite" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2d12fe70b2c1b4401038055f90f151b78208de1f9f89a7dbfd41587a10c3eea" +dependencies = [ + "atoi", + "chrono", + "flume", + "futures-channel", + "futures-core", + "futures-executor", + "futures-intrusive", + "futures-util", + "libsqlite3-sys", + "log", + "percent-encoding", + "serde", + "serde_urlencoded", + "sqlx-core", + "thiserror", + "tracing", + "url", + "uuid", +] + +[[package]] +name = "stable_deref_trait" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" + +[[package]] +name = "static_assertions_next" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7beae5182595e9a8b683fa98c4317f956c9a2dec3b9716990d20023cc60c766" + +[[package]] +name = "stringprep" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b4df3d392d81bd458a8a621b8bffbd2302a12ffe288a9d931670948749463b1" +dependencies = [ + "unicode-bidi", + "unicode-normalization", + "unicode-properties", +] + +[[package]] +name = "strsim" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" + +[[package]] +name = "strum" +version = "0.27.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af23d6f6c1a224baef9d3f61e287d2761385a5b88fdab4eb4c6f11aeb54c4bcf" +dependencies = [ + "strum_macros", +] + +[[package]] +name = "strum_macros" +version = "0.27.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7695ce3845ea4b33927c055a39dc438a45b059f7c1b3d91d38d10355fb8cbca7" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "subtle" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" + +[[package]] +name = "syn" +version = "2.0.119" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "872831b642d1a07999a962a351ed35b955ea2cfc8f3862091e2a240a84f17297" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "syn" +version = "3.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6275cddf4610d1775e6d1fe9469b2e77d0f39fd98fb7450901b821e0c53649f" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "sync_wrapper" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263" + +[[package]] +name = "syncode-collab" +version = "0.6.0" +dependencies = [ + "async-graphql", + "async-graphql-axum", + "axum", + "clap", + "hex", + "hmac 0.13.0", + "serde_json", + "sha2 0.11.0", + "syncode-collab-api-graphql", + "syncode-collab-api-grpc", + "syncode-collab-application", + "syncode-collab-model", + "syncode-collab-store", + "thiserror", + "tokio", + "tokio-stream", + "tonic", + "tower-http", + "uuid", +] + +[[package]] +name = "syncode-collab-api-graphql" +version = "0.6.0" +dependencies = [ + "async-graphql", + "async-graphql-axum", + "axum", + "syncode-collab-application", + "syncode-collab-model", + "uuid", +] + +[[package]] +name = "syncode-collab-api-grpc" +version = "0.6.0" +dependencies = [ + "prost", + "prost-build", + "protoc-bin-vendored", + "syncode-collab-application", + "syncode-collab-model", + "tonic", + "tonic-prost", + "tonic-prost-build", + "uuid", +] + +[[package]] +name = "syncode-collab-application" +version = "0.6.0" +dependencies = [ + "async-trait", + "serde_json", + "syncode-collab-model", + "thiserror", + "tokio", + "uuid", +] + +[[package]] +name = "syncode-collab-model" +version = "0.6.0" +dependencies = [ + "chrono", + "serde", + "serde_json", + "thiserror", + "uuid", +] + +[[package]] +name = "syncode-collab-store" +version = "0.6.0" +dependencies = [ + "async-trait", + "chrono", + "serde_json", + "sqlx", + "syncode-collab-application", + "syncode-collab-model", + "thiserror", + "tokio", + "uuid", +] + +[[package]] +name = "synstructure" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "tempfile" +version = "3.27.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32497e9a4c7b38532efcdebeef879707aa9f794296a4f0244f6f69e9bc8574bd" +dependencies = [ + "fastrand", + "getrandom 0.4.3", + "once_cell", + "rustix", + "windows-sys 0.61.2", +] + +[[package]] +name = "thiserror" +version = "2.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec86235f5fcc2a73650310756d2ac5b138a5780bbbdfae3eeccec992c435ba4f" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "2.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc04cd3e1236dd4a98afca4569f2deb3f120e5422a4023be2cb683f8486292af" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.4", +] + +[[package]] +name = "tinystr" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1e27c91459209c2986af3dcf603a5a74a4368754ce37414f59acc971167f643" +dependencies = [ + "displaydoc", + "zerovec", +] + +[[package]] +name = "tinyvec" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb4ebadaa0af04fab11ae01eb5f9fdb5f9c5b875506e210e71c07873528baa7f" +dependencies = [ + "tinyvec_macros", +] + +[[package]] +name = "tinyvec_macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" + +[[package]] +name = "tokio" +version = "1.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "202caea871b69668250d242070849eb495be178ed697a3e98aebce5bc81a0bed" +dependencies = [ + "bytes", + "libc", + "mio", + "pin-project-lite", + "signal-hook-registry", + "socket2", + "tokio-macros", + "windows-sys 0.61.2", +] + +[[package]] +name = "tokio-macros" +version = "2.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78773a2a397f451582ce068015985c33193cf6dea8b74d2a639fe457b2f07b0e" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.4", +] + +[[package]] +name = "tokio-stream" +version = "0.1.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a3d06f0b082ba57c26b79407372e57cf2a1e28124f78e9479fe80322cf53420b" +dependencies = [ + "futures-core", + "pin-project-lite", + "tokio", +] + +[[package]] +name = "tokio-tungstenite" +version = "0.29.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f72a05e828585856dacd553fba484c242c46e391fb0e58917c942ee9202915c" +dependencies = [ + "futures-util", + "log", + "tokio", + "tungstenite", +] + +[[package]] +name = "tokio-util" +version = "0.7.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "494815d09bf52b5548659851081238f0ca39ff638363907596da739561c62c52" +dependencies = [ + "bytes", + "futures-core", + "futures-io", + "futures-sink", + "libc", + "pin-project-lite", + "tokio", +] + +[[package]] +name = "toml_datetime" +version = "1.1.1+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3165f65f62e28e0115a00b2ebdd37eb6f3b641855f9d636d3cd4103767159ad7" +dependencies = [ + "serde_core", +] + +[[package]] +name = "toml_edit" +version = "0.25.13+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6975367e4d2ef766d86af01ffad14b622fecc8d4357a998fbc4deb6e9bacaf9b" +dependencies = [ + "indexmap", + "toml_datetime", + "toml_parser", + "winnow", +] + +[[package]] +name = "toml_parser" +version = "1.1.3+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d38ac1cf9b95face32296c0a3ede1fdc270627c9d9c02a7274dd6d960dc4d56" +dependencies = [ + "winnow", +] + +[[package]] +name = "tonic" +version = "0.14.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac2a5518c70fa84342385732db33fb3f44bc4cc748936eb5833d2df34d6445ef" +dependencies = [ + "async-trait", + "axum", + "base64", + "bytes", + "h2", + "http", + "http-body", + "http-body-util", + "hyper", + "hyper-timeout", + "hyper-util", + "percent-encoding", + "pin-project", + "socket2", + "sync_wrapper", + "tokio", + "tokio-stream", + "tower", + "tower-layer", + "tower-service", + "tracing", +] + +[[package]] +name = "tonic-build" +version = "0.14.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c68f61875ac5293cf72e6c8cf0158086428c82c37229e98c840878f1706b0322" +dependencies = [ + "prettyplease", + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "tonic-prost" +version = "0.14.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50849f68853be452acf590cde0b146665b8d507b3b8af17261df47e02c209ea0" +dependencies = [ + "bytes", + "prost", + "tonic", +] + +[[package]] +name = "tonic-prost-build" +version = "0.14.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "654e5643eff75d7f8c99197ce1440ed19a3474eada74c12bbac488b2cafdae27" +dependencies = [ + "prettyplease", + "proc-macro2", + "prost-build", + "prost-types", + "quote", + "syn 2.0.119", + "tempfile", + "tonic-build", +] + +[[package]] +name = "tower" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebe5ef63511595f1344e2d5cfa636d973292adc0eec1f0ad45fae9f0851ab1d4" +dependencies = [ + "futures-core", + "futures-util", + "indexmap", + "pin-project-lite", + "slab", + "sync_wrapper", + "tokio", + "tokio-util", + "tower-layer", + "tower-service", + "tracing", +] + +[[package]] +name = "tower-http" +version = "0.6.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4cfcf7e2740e6fc6d4d688b4ef00650406bb94adf4731e43c096c3a19fe40840" +dependencies = [ + "bitflags", + "bytes", + "http", + "pin-project-lite", + "tower-layer", + "tower-service", +] + +[[package]] +name = "tower-layer" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "121c2a6cda46980bb0fcd1647ffaf6cd3fc79a013de288782836f6df9c48780e" + +[[package]] +name = "tower-service" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" + +[[package]] +name = "tracing" +version = "0.1.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100" +dependencies = [ + "log", + "pin-project-lite", + "tracing-attributes", + "tracing-core", +] + +[[package]] +name = "tracing-attributes" +version = "0.1.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "tracing-core" +version = "0.1.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a" +dependencies = [ + "once_cell", +] + +[[package]] +name = "try-lock" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" + +[[package]] +name = "tungstenite" +version = "0.29.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c01152af293afb9c7c2a57e4b559c5620b421f6d133261c60dd2d0cdb38e6b8" +dependencies = [ + "bytes", + "data-encoding", + "http", + "httparse", + "log", + "rand 0.9.5", + "sha1", + "thiserror", +] + +[[package]] +name = "typenum" +version = "1.20.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6f5e870be6c3b371b77fe0ee0bafb859fa4964b4404c27de1d380043c4dda20" + +[[package]] +name = "ucd-trie" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2896d95c02a80c6d6a5d6e953d479f5ddf2dfdb6a244441010e373ac0fb88971" + +[[package]] +name = "unicase" +version = "2.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dbc4bc3a9f746d862c45cb89d705aa10f187bb96c76001afab07a0d35ce60142" + +[[package]] +name = "unicode-bidi" +version = "0.3.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c1cb5db39152898a79168971543b1cb5020dff7fe43c8dc468b0885f5e29df5" + +[[package]] +name = "unicode-ident" +version = "1.0.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" + +[[package]] +name = "unicode-normalization" +version = "0.1.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5fd4f6878c9cb28d874b009da9e8d183b5abc80117c40bbd187a1fde336be6e8" +dependencies = [ + "tinyvec", +] + +[[package]] +name = "unicode-properties" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7df058c713841ad818f1dc5d3fd88063241cc61f49f5fbea4b951e8cf5a8d71d" + +[[package]] +name = "untrusted" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" + +[[package]] +name = "url" +version = "2.5.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff67a8a4397373c3ef660812acab3268222035010ab8680ec4215f38ba3d0eed" +dependencies = [ + "form_urlencoded", + "idna", + "percent-encoding", + "serde", +] + +[[package]] +name = "utf8_iter" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" + +[[package]] +name = "utf8parse" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" + +[[package]] +name = "uuid" +version = "1.26.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5772d71c9be8a8a6ac2117d949c5b224c1b72241bb611d9a3012edcf8af7812" +dependencies = [ + "getrandom 0.4.3", + "js-sys", + "serde_core", + "wasm-bindgen", +] + +[[package]] +name = "vcpkg" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" + +[[package]] +name = "version_check" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" + +[[package]] +name = "want" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" +dependencies = [ + "try-lock", +] + +[[package]] +name = "wasi" +version = "0.11.1+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" + +[[package]] +name = "wasip2" +version = "1.0.4+wasi-0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b67efb37e106e55ce722a510d6b5f9c17f083e5fc79afc2badeb12cc313d9487" +dependencies = [ + "wit-bindgen", +] + +[[package]] +name = "wasite" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8dad83b4f25e74f184f64c43b150b91efe7647395b42289f38e50566d82855b" + +[[package]] +name = "wasm-bindgen" +version = "0.2.127" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b70935747edd64d89de3efa29d73789b806c15798f8e7dca4d8ac356b50ce70" +dependencies = [ + "cfg-if", + "once_cell", + "rustversion", + "wasm-bindgen-macro", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.127" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77775f8f3f7217702089053b94958f8f54061a3f663417df76e19cbdcca29bc1" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.127" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e11d33f857dc2fb11b8bc75aee111aa9cbeb12cd9f25efd3d4c2a3dd4e235284" +dependencies = [ + "bumpalo", + "proc-macro2", + "quote", + "syn 2.0.119", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.127" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ef64dbcc55df09c7e5a46182d181c2cfa3e925f3da937ea764728b4bbb9dcbf" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "webpki-roots" +version = "0.26.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "521bc38abb08001b01866da9f51eb7c5d647a19260e00054a8c7fd5f9e57f7a9" +dependencies = [ + "webpki-roots 1.0.9", +] + +[[package]] +name = "webpki-roots" +version = "1.0.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7dcd9d09a39985f5344844e66b0c530a33843579125f23e21e9f0f220850f22a" +dependencies = [ + "rustls-pki-types", +] + +[[package]] +name = "whoami" +version = "1.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d4a4db5077702ca3015d3d02d74974948aba2ad9e12ab7df718ee64ccd7e97d" +dependencies = [ + "libredox", + "wasite", +] + +[[package]] +name = "windows-core" +version = "0.62.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8e83a14d34d0623b51dce9581199302a221863196a1dde71a7663a4c2be9deb" +dependencies = [ + "windows-implement", + "windows-interface", + "windows-link", + "windows-result", + "windows-strings", +] + +[[package]] +name = "windows-implement" +version = "0.60.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "windows-interface" +version = "0.59.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "windows-link" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" + +[[package]] +name = "windows-result" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7781fa89eaf60850ac3d2da7af8e5242a5ea78d1a11c49bf2910bb5a73853eb5" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-strings" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7837d08f69c77cf6b07689544538e017c1bfcf57e34b4c0ff58e6c2cd3b37091" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-sys" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" +dependencies = [ + "windows-targets 0.48.5", +] + +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-sys" +version = "0.61.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-targets" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" +dependencies = [ + "windows_aarch64_gnullvm 0.48.5", + "windows_aarch64_msvc 0.48.5", + "windows_i686_gnu 0.48.5", + "windows_i686_msvc 0.48.5", + "windows_x86_64_gnu 0.48.5", + "windows_x86_64_gnullvm 0.48.5", + "windows_x86_64_msvc 0.48.5", +] + +[[package]] +name = "windows-targets" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +dependencies = [ + "windows_aarch64_gnullvm 0.52.6", + "windows_aarch64_msvc 0.52.6", + "windows_i686_gnu 0.52.6", + "windows_i686_gnullvm", + "windows_i686_msvc 0.52.6", + "windows_x86_64_gnu 0.52.6", + "windows_x86_64_gnullvm 0.52.6", + "windows_x86_64_msvc 0.52.6", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" + +[[package]] +name = "windows_i686_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" + +[[package]] +name = "windows_i686_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" + +[[package]] +name = "winnow" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23b97319f7b8343df12cc98938e5c3eb436064524c8d2b4e30a1d3a36eecdf81" +dependencies = [ + "memchr", +] + +[[package]] +name = "wit-bindgen" +version = "0.57.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ebf944e87a7c253233ad6766e082e3cd714b5d03812acc24c318f549614536e" + +[[package]] +name = "writeable" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ad82d2a33cdc9674dc7465672f271e096168fcdbe0f799d9e6db8c5892679dc" + +[[package]] +name = "yoke" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "709fe23a0424b6a435d82152b1bd3fdfb0833487d5fa90d05d42762a9891fef5" +dependencies = [ + "stable_deref_trait", + "yoke-derive", + "zerofrom", +] + +[[package]] +name = "yoke-derive" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "de844c262c8848816172cef550288e7dc6c7b7814b4ee56b3e1553f275f1858e" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", + "synstructure", +] + +[[package]] +name = "zerocopy" +version = "0.8.56" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "556764e583adb45a9f8d413c2a147fa7e8d821e48e12b14fd560b607998b75eb" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.8.56" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2ab42fc20575779bd240faa45f94a74256f755c0fa9e89f0ede20d91d0cdfc1" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "zerofrom" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ec05a11813ea801ff6d75110ad09cd0824ddba17dfe17128ea0d5f68e6c5272" +dependencies = [ + "zerofrom-derive", +] + +[[package]] +name = "zerofrom-derive" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11532158c46691caf0f2593ea8358fed6bbf68a0315e80aae9bd41fbade684a1" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", + "synstructure", +] + +[[package]] +name = "zeroize" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e13c156562582aa81c60cb29407084cdb54c4164760106ab78e6c5b0858cf64e" + +[[package]] +name = "zerotrie" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ea269c3bd32f0a32c321907a2ae912ba6f4649bb0fc764a15627e99a7095a3f" +dependencies = [ + "displaydoc", + "yoke", + "zerofrom", +] + +[[package]] +name = "zerovec" +version = "0.11.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb0464e17806c1d976d5cba29399c7f08e516e279e2ba493f63123b5fca67dd8" +dependencies = [ + "yoke", + "zerofrom", + "zerovec-derive", +] + +[[package]] +name = "zerovec-derive" +version = "0.11.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34df6fc39dbd26ddc9c10e6a2984476e13acce22e64e4487636ef494369225da" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.4", +] + +[[package]] +name = "zmij" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29666d0abbfad1e3dc4dcf6144730dd3a3ab225bbbdac83319345b1b44ccfc1b" diff --git a/Cargo.toml b/Cargo.toml --- a/Cargo.toml +++ b/Cargo.toml @@ -1,61 +1,64 @@ -[package] -name = "syncode-collab" -description = "SynCode collaboration plane" -version.workspace = true -edition.workspace = true -rust-version.workspace = true -license.workspace = true -repository.workspace = true -publish = false - -[workspace] -members = [ - ".", - "crates/collab-api-graphql", - "crates/collab-api-grpc", - "crates/collab-application", - "crates/collab-model", - "crates/collab-store", -] -resolver = "3" - -[workspace.package] -version = "0.6.0" -edition = "2024" -rust-version = "1.95" -license = "MIT" -repository = "https://syncode.sh/syncode/collab" - -[workspace.lints.rust] -unsafe_code = "forbid" - -[workspace.lints.clippy] -expect_used = "deny" -panic = "deny" -unwrap_used = "deny" - -[dependencies] -async-graphql-axum = "7.2.1" -axum = "0.8.9" -clap = { version = "4.6.4", features = ["derive", "env"] } -hex = "0.4.3" -hmac = "0.13.0" -serde_json = "1.0.149" -sha2 = "0.11.0" -syncode-collab-api-graphql = { path = "crates/collab-api-graphql" } -syncode-collab-api-grpc = { path = "crates/collab-api-grpc" } -syncode-collab-application = { path = "crates/collab-application" } -syncode-collab-model = { path = "crates/collab-model" } -syncode-collab-store = { path = "crates/collab-store" } -thiserror = "2.0.19" -tokio = { version = "1.49.0", features = ["macros", "net", "rt-multi-thread", "signal", "time"] } -tonic = "0.14.6" - -[dev-dependencies] -syncode-collab-api-grpc = { path = "crates/collab-api-grpc" } -tokio = { version = "1.49.0", features = ["macros", "net", "rt-multi-thread"] } -tokio-stream = { version = "0.1.18", features = ["net"] } -tonic = "0.14.6" - -[lints] -workspace = true +[package] +name = "syncode-collab" +description = "SynCode collaboration plane" +version.workspace = true +edition.workspace = true +rust-version.workspace = true +license.workspace = true +repository.workspace = true +publish = false + +[workspace] +members = [ + ".", + "crates/collab-api-graphql", + "crates/collab-api-grpc", + "crates/collab-application", + "crates/collab-model", + "crates/collab-store", +] +resolver = "3" + +[workspace.package] +version = "0.6.0" +edition = "2024" +rust-version = "1.95" +license = "MIT" +repository = "https://syncode.sh/syncode/collab" + +[workspace.lints.rust] +unsafe_code = "forbid" + +[workspace.lints.clippy] +expect_used = "deny" +panic = "deny" +unwrap_used = "deny" + +[dependencies] +async-graphql = "7.2.1" +async-graphql-axum = "7.2.1" +axum = "0.8.9" +clap = { version = "4.6.4", features = ["derive", "env"] } +hex = "0.4.3" +hmac = "0.13.0" +serde_json = "1.0.149" +sha2 = "0.11.0" +syncode-collab-api-graphql = { path = "crates/collab-api-graphql" } +syncode-collab-api-grpc = { path = "crates/collab-api-grpc" } +syncode-collab-application = { path = "crates/collab-application" } +syncode-collab-model = { path = "crates/collab-model" } +syncode-collab-store = { path = "crates/collab-store" } +thiserror = "2.0.19" +tokio = { version = "1.49.0", features = ["macros", "net", "rt-multi-thread", "signal", "time"] } +tonic = "0.14.6" +tower-http = { version = "0.6.8", features = ["cors"] } +uuid = { version = "1.24.0", features = ["v4"] } + +[dev-dependencies] +syncode-collab-api-grpc = { path = "crates/collab-api-grpc" } +tokio = { version = "1.49.0", features = ["macros", "net", "rt-multi-thread"] } +tokio-stream = { version = "0.1.18", features = ["net"] } +tonic = "0.14.6" + +[lints] +workspace = true diff --git a/README.md b/README.md --- a/README.md +++ b/README.md @@ -1,44 +1,47 @@ -# syncode-collab - -The SynCode collaboration plane. It is the authority for Workspaces, Projects, -Issues, pull requests, review, Documents, Versions, webhooks, the activity feed -and the Inbox. - -Collaboration keeps stable `repository_id` and `principal_id` values but never -joins another plane's database. Facts about refs, tags, principals and checks -arrive as versioned events through a deduplicating inbox; decisions and state -changes leave as commands over typed ports. There is no fallback path, no -dual-write and no read of a foreign store. - -## Layout - -| Crate | Role | -| --- | --- | -| `collab-model` | domain types; free of transport, storage and runtime | -| `collab-application` | use cases and ports; free of transport and storage | -| `collab-store` | PostgreSQL state, migrations, inbox and outbox | -| `collab-api-graphql` | typed GraphQL surface for the front | -| `collab-api-grpc` | versioned gRPC surface for `syn` | - -## Running - -```sh -export SYNCODE_COLLAB_DATABASE_URL=postgres://collab:collab@localhost:5432/collab -export SYNCODE_COLLAB_EVENT_SECRET=replace-with-a-shared-secret -cargo run --bin syncode-collab -``` - -Migrations are applied on start, so an empty database is a valid starting point. -gRPC listens on `8300` and HTTP on `8301`. - -## Contracts - -`crates/collab-api-graphql/schema.graphql` is the committed GraphQL contract. -Regenerate it after changing the schema and commit the result: - -```sh -cargo run --bin collab_schema > crates/collab-api-graphql/schema.graphql -``` - -The gRPC contract lives in `crates/collab-api-grpc/proto/collab.proto` and is -generated at build time. CI fails when either contract drifts from its source. +# syncode-collab + +The SynCode collaboration plane. It is the authority for Workspaces, Projects, +Issues, pull requests, review, Documents, Versions, webhooks, the activity feed +and the Inbox. + +Collaboration keeps stable `repository_id` and `principal_id` values but never +joins another plane's database. Facts about refs, tags, principals and checks +arrive as versioned events through a deduplicating inbox; decisions and state +changes leave as commands over typed ports. There is no fallback path, no +dual-write and no read of a foreign store. + +## Layout + +| Crate | Role | +| --- | --- | +| `collab-model` | domain types; free of transport, storage and runtime | +| `collab-application` | use cases and ports; free of transport and storage | +| `collab-store` | PostgreSQL state, migrations, inbox and outbox | +| `collab-api-graphql` | typed GraphQL surface for the front | +| `collab-api-grpc` | versioned gRPC surface for `syn` | + +## Running + +```sh +export SYNCODE_COLLAB_DATABASE_URL=postgres://collab:collab@localhost:5432/collab +export SYNCODE_COLLAB_EVENT_SECRET=replace-with-a-shared-secret +export SYNCODE_IDENTITY_GRPC_URL=http://127.0.0.1:8190 +export SYNCODE_IDENTITY_SHARED_SECRET=replace-with-the-identity-secret +export SYNCODE_COLLAB_CORS_ORIGINS=http://127.0.0.1:8081 +cargo run --bin syncode-collab +``` + +Migrations are applied on start, so an empty database is a valid starting point. +gRPC listens on `8300` and HTTP on `8301`. + +## Contracts + +`crates/collab-api-graphql/schema.graphql` is the committed GraphQL contract. +Regenerate it after changing the schema and commit the result: + +```sh +cargo run --bin collab_schema > crates/collab-api-graphql/schema.graphql +``` + +The gRPC contract lives in `crates/collab-api-grpc/proto/collab.proto` and is +generated at build time. CI fails when either contract drifts from its source. diff --git a/src/config.rs b/src/config.rs --- a/src/config.rs +++ b/src/config.rs @@ -1,32 +1,41 @@ -use clap::Parser; - -#[derive(Clone, Debug, Parser)] -#[command(name = "syncode-collab", about = "SynCode collaboration plane")] -pub struct Config { - #[arg( - long, - env = "SYNCODE_COLLAB_LISTEN_GRPC", - default_value = "0.0.0.0:8300" - )] - pub listen_grpc: String, - - #[arg( - long, - env = "SYNCODE_COLLAB_LISTEN_HTTP", - default_value = "0.0.0.0:8301" - )] - pub listen_http: String, - - #[arg(long, env = "SYNCODE_COLLAB_DATABASE_URL")] - pub database_url: String, - - #[arg( - long, - env = "SYNCODE_COLLAB_DATABASE_MAX_CONNECTIONS", - default_value_t = 16 - )] - pub database_max_connections: u32, - - #[arg(long, env = "SYNCODE_COLLAB_EVENT_SECRET", hide_env_values = true)] - pub event_secret: String, -} +use clap::Parser; + +#[derive(Clone, Debug, Parser)] +#[command(name = "syncode-collab", about = "SynCode collaboration plane")] +pub struct Config { + #[arg( + long, + env = "SYNCODE_COLLAB_LISTEN_GRPC", + default_value = "0.0.0.0:8300" + )] + pub listen_grpc: String, + + #[arg( + long, + env = "SYNCODE_COLLAB_LISTEN_HTTP", + default_value = "0.0.0.0:8301" + )] + pub listen_http: String, + + #[arg(long, env = "SYNCODE_COLLAB_DATABASE_URL")] + pub database_url: String, + + #[arg( + long, + env = "SYNCODE_COLLAB_DATABASE_MAX_CONNECTIONS", + default_value_t = 16 + )] + pub database_max_connections: u32, + + #[arg(long, env = "SYNCODE_COLLAB_EVENT_SECRET", hide_env_values = true)] + pub event_secret: String, + + #[arg(long, env = "SYNCODE_IDENTITY_GRPC_URL")] + pub identity_grpc_url: String, + + #[arg(long, env = "SYNCODE_IDENTITY_SHARED_SECRET", hide_env_values = true)] + pub identity_shared_secret: String, + + #[arg(long, env = "SYNCODE_COLLAB_CORS_ORIGINS", value_delimiter = ',')] + pub cors_origins: Vec, +} diff --git a/src/http.rs b/src/http.rs --- a/src/http.rs +++ b/src/http.rs @@ -1,97 +1,138 @@ -use async_graphql_axum::GraphQL; -use axum::body::Bytes; -use axum::http::{HeaderMap, StatusCode}; -use axum::routing::{get, post}; -use axum::{Router, extract::State}; -use hmac::{Hmac, KeyInit, Mac}; -use sha2::Sha256; -use std::sync::Arc; -use syncode_collab_application::{Acceptance, EventInbox, InboxError, StoreError, accept}; -use syncode_collab_model::Envelope; - -#[derive(Clone)] -struct HttpState { - inbox: Arc, - event_secret: String, -} - -pub fn router(inbox: Arc, event_secret: String) -> Router { - Router::new() - .route("/healthz", get(|| async { "ok" })) - .route("/events", post(receive_event)) - .route_service( - "/graphql", - GraphQL::new(syncode_collab_api_graphql::build()), - ) - .with_state(HttpState { - inbox, - event_secret, - }) -} - -async fn receive_event( - State(state): State, - headers: HeaderMap, - body: Bytes, -) -> StatusCode { - if !valid_signature(&headers, &body, &state.event_secret) { - return StatusCode::UNAUTHORIZED; - } - let Ok(envelope) = serde_json::from_slice::(&body) else { - return StatusCode::BAD_REQUEST; - }; - if headers - .get("x-syncode-event") - .and_then(|value| value.to_str().ok()) - != Some(envelope.message_type.as_str()) - || headers - .get("x-syncode-delivery") - .and_then(|value| value.to_str().ok()) - != Some(envelope.message_id.to_string().as_str()) - { - return StatusCode::BAD_REQUEST; - } - match accept(state.inbox.as_ref(), &envelope).await { - Ok(Acceptance::Applied) => StatusCode::NO_CONTENT, - Ok(Acceptance::AlreadyApplied) => StatusCode::OK, - Err(InboxError::Store(StoreError::Unavailable(_))) => StatusCode::SERVICE_UNAVAILABLE, - Err(InboxError::Store(StoreError::Rejected(_))) => StatusCode::BAD_REQUEST, - Err(_) => StatusCode::BAD_REQUEST, - } -} - -fn valid_signature(headers: &HeaderMap, body: &[u8], secret: &str) -> bool { - let Some(signature) = headers - .get("x-syncode-signature") - .and_then(|value| value.to_str().ok()) - .and_then(|value| hex::decode(value).ok()) - else { - return false; - }; - let Ok(mut mac) = Hmac::::new_from_slice(secret.as_bytes()) else { - return false; - }; - mac.update(body); - mac.verify_slice(&signature).is_ok() -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn verifies_the_raw_event_body() -> Result<(), Box> { - let body = br#"{"message_id":"00000000-0000-0000-0000-000000000000"}"#; - let mut mac = Hmac::::new_from_slice(b"secret")?; - mac.update(body); - let mut headers = HeaderMap::new(); - headers.insert( - "x-syncode-signature", - hex::encode(mac.finalize().into_bytes()).parse()?, - ); - - assert!(valid_signature(&headers, body, "secret")); - assert!(!valid_signature(&headers, b"changed", "secret")); - Ok(()) - } -} +use async_graphql_axum::{GraphQLRequest, GraphQLResponse}; +use axum::body::Bytes; +use axum::http::{HeaderMap, StatusCode}; +use axum::routing::{get, post}; +use axum::{Router, extract::State}; +use hmac::{Hmac, KeyInit, Mac}; +use sha2::Sha256; +use std::sync::Arc; +use syncode_collab_application::Collaboration; +use syncode_collab_application::{Acceptance, EventInbox, InboxError, StoreError, accept}; +use syncode_collab_model::Envelope; + +#[derive(Clone)] +struct HttpState { + inbox: Arc, + event_secret: String, + schema: syncode_collab_api_graphql::CollabSchema, +} + +pub fn router( + inbox: Arc, + collaboration: Arc, + event_secret: String, +) -> Router { + Router::new() + .route("/healthz", get(|| async { "ok" })) + .route("/events", post(receive_event)) + .route("/graphql", post(graphql)) + .with_state(HttpState { + inbox, + event_secret, + schema: syncode_collab_api_graphql::build(collaboration), + }) +} + +async fn graphql( + State(state): State, + headers: HeaderMap, + request: GraphQLRequest, +) -> GraphQLResponse { + let token = match session_token(&headers) { + Ok(token) => token.map(ToOwned::to_owned), + Err(()) => { + return async_graphql::Response::from_errors(vec![async_graphql::ServerError::new( + "invalid cookie header", + None, + )]) + .into(); + } + }; + state + .schema + .execute( + request + .into_inner() + .data(syncode_collab_api_graphql::SessionToken(token)), + ) + .await + .into() +} + +fn session_token(headers: &HeaderMap) -> Result, ()> { + let Some(value) = headers.get(axum::http::header::COOKIE) else { + return Ok(None); + }; + let value = value.to_str().map_err(|_| ())?; + Ok(value + .split(';') + .map(str::trim) + .find_map(|pair| pair.strip_prefix("syncode_identity_session="))) +} + +async fn receive_event( + State(state): State, + headers: HeaderMap, + body: Bytes, +) -> StatusCode { + if !valid_signature(&headers, &body, &state.event_secret) { + return StatusCode::UNAUTHORIZED; + } + let Ok(envelope) = serde_json::from_slice::(&body) else { + return StatusCode::BAD_REQUEST; + }; + if headers + .get("x-syncode-event") + .and_then(|value| value.to_str().ok()) + != Some(envelope.message_type.as_str()) + || headers + .get("x-syncode-delivery") + .and_then(|value| value.to_str().ok()) + != Some(envelope.message_id.to_string().as_str()) + { + return StatusCode::BAD_REQUEST; + } + match accept(state.inbox.as_ref(), &envelope).await { + Ok(Acceptance::Applied) => StatusCode::NO_CONTENT, + Ok(Acceptance::AlreadyApplied) => StatusCode::OK, + Err(InboxError::Store(StoreError::Unavailable(_))) => StatusCode::SERVICE_UNAVAILABLE, + Err(InboxError::Store(StoreError::Rejected(_))) => StatusCode::BAD_REQUEST, + Err(_) => StatusCode::BAD_REQUEST, + } +} + +fn valid_signature(headers: &HeaderMap, body: &[u8], secret: &str) -> bool { + let Some(signature) = headers + .get("x-syncode-signature") + .and_then(|value| value.to_str().ok()) + .and_then(|value| hex::decode(value).ok()) + else { + return false; + }; + let Ok(mut mac) = Hmac::::new_from_slice(secret.as_bytes()) else { + return false; + }; + mac.update(body); + mac.verify_slice(&signature).is_ok() +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn verifies_the_raw_event_body() -> Result<(), Box> { + let body = br#"{"message_id":"00000000-0000-0000-0000-000000000000"}"#; + let mut mac = Hmac::::new_from_slice(b"secret")?; + mac.update(body); + let mut headers = HeaderMap::new(); + headers.insert( + "x-syncode-signature", + hex::encode(mac.finalize().into_bytes()).parse()?, + ); + + assert!(valid_signature(&headers, body, "secret")); + assert!(!valid_signature(&headers, b"changed", "secret")); + Ok(()) + } +} diff --git a/src/lib.rs b/src/lib.rs --- a/src/lib.rs +++ b/src/lib.rs @@ -1,6 +1,7 @@ -pub mod config; -pub mod http; -pub mod runtime; - -pub use config::Config; -pub use runtime::{RuntimeError, run}; +pub mod config; +pub mod http; +pub mod identity; +pub mod runtime; + +pub use config::Config; +pub use runtime::{RuntimeError, run}; diff --git a/src/runtime.rs b/src/runtime.rs --- a/src/runtime.rs +++ b/src/runtime.rs @@ -1,54 +1,92 @@ -use std::sync::Arc; -use syncode_collab_api_grpc::CollabService; -use syncode_collab_store::{PostgresInbox, connect, migrate}; - -use crate::config::Config; -use crate::http::router; - -pub async fn run(config: Config) -> Result<(), RuntimeError> { - if config.event_secret.is_empty() { - return Err(RuntimeError::Configuration( - "SYNCODE_COLLAB_EVENT_SECRET must not be empty".to_owned(), - )); - } - let pool = connect(&config.database_url, config.database_max_connections) - .await - .map_err(|error| RuntimeError::Store(error.to_string()))?; - migrate(&pool) - .await - .map_err(|error| RuntimeError::Store(error.to_string()))?; - - let http = tokio::net::TcpListener::bind(&config.listen_http) - .await - .map_err(|error| RuntimeError::Listen(config.listen_http.clone(), error.to_string()))?; - let grpc = config - .listen_grpc - .parse() - .map_err(|_| RuntimeError::Address(config.listen_grpc.clone()))?; - - let inbox = Arc::new(PostgresInbox::new(pool)); - let serve_http = axum::serve(http, router(inbox, config.event_secret)); - let serve_grpc = tonic::transport::Server::builder() - .add_service(CollabService.into_server()) - .serve(grpc); - - tokio::select! { - result = serve_http => result.map_err(|error| RuntimeError::Serve(error.to_string())), - result = serve_grpc => result.map_err(|error| RuntimeError::Serve(error.to_string())), - _ = tokio::signal::ctrl_c() => Ok(()), - } -} - -#[derive(Debug, thiserror::Error)] -pub enum RuntimeError { - #[error("invalid collaboration configuration: {0}")] - Configuration(String), - #[error("{0} is not a valid socket address")] - Address(String), - #[error("cannot listen on {0}: {1}")] - Listen(String, String), - #[error("collaboration store failed: {0}")] - Store(String), - #[error("serving failed: {0}")] - Serve(String), -} +use std::sync::Arc; +use syncode_collab_api_grpc::CollabService; +use syncode_collab_application::Collaboration; +use syncode_collab_store::{PostgresCollaboration, PostgresInbox, connect, migrate}; +use tower_http::cors::{AllowOrigin, CorsLayer}; + +use crate::config::Config; +use crate::http::router; +use crate::identity::IdentityClientAccess; + +pub async fn run(config: Config) -> Result<(), RuntimeError> { + if config.event_secret.is_empty() { + return Err(RuntimeError::Configuration( + "SYNCODE_COLLAB_EVENT_SECRET must not be empty".to_owned(), + )); + } + if config.identity_shared_secret.is_empty() { + return Err(RuntimeError::Configuration( + "SYNCODE_IDENTITY_SHARED_SECRET must not be empty".to_owned(), + )); + } + let pool = connect(&config.database_url, config.database_max_connections) + .await + .map_err(|error| RuntimeError::Store(error.to_string()))?; + migrate(&pool) + .await + .map_err(|error| RuntimeError::Store(error.to_string()))?; + + let http = tokio::net::TcpListener::bind(&config.listen_http) + .await + .map_err(|error| RuntimeError::Listen(config.listen_http.clone(), error.to_string()))?; + let grpc = config + .listen_grpc + .parse() + .map_err(|_| RuntimeError::Address(config.listen_grpc.clone()))?; + + let identity = Arc::new( + IdentityClientAccess::connect(config.identity_grpc_url, config.identity_shared_secret) + .await + .map_err(|error| RuntimeError::Identity(error.to_string()))?, + ); + let collaboration = Arc::new(Collaboration::new( + Arc::new(PostgresCollaboration::new(pool.clone())), + identity, + )); + let inbox = Arc::new(PostgresInbox::new(pool)); + let mut http_router = router(inbox, Arc::clone(&collaboration), config.event_secret); + if !config.cors_origins.is_empty() { + let allowed = config.cors_origins; + http_router = http_router.layer( + CorsLayer::new() + .allow_credentials(true) + .allow_methods([ + axum::http::Method::GET, + axum::http::Method::POST, + axum::http::Method::OPTIONS, + ]) + .allow_headers([axum::http::header::CONTENT_TYPE]) + .allow_origin(AllowOrigin::predicate(move |origin, _| { + origin + .to_str() + .is_ok_and(|origin| allowed.iter().any(|value| value == origin)) + })), + ); + } + let serve_http = axum::serve(http, http_router); + let serve_grpc = tonic::transport::Server::builder() + .add_service(CollabService::new(collaboration).into_server()) + .serve(grpc); + + tokio::select! { + result = serve_http => result.map_err(|error| RuntimeError::Serve(error.to_string())), + result = serve_grpc => result.map_err(|error| RuntimeError::Serve(error.to_string())), + _ = tokio::signal::ctrl_c() => Ok(()), + } +} + +#[derive(Debug, thiserror::Error)] +pub enum RuntimeError { + #[error("invalid collaboration configuration: {0}")] + Configuration(String), + #[error("{0} is not a valid socket address")] + Address(String), + #[error("cannot listen on {0}: {1}")] + Listen(String, String), + #[error("collaboration store failed: {0}")] + Store(String), + #[error("identity integration failed: {0}")] + Identity(String), + #[error("serving failed: {0}")] + Serve(String), +} diff --git a/tests/grpc_plane_info.rs b/tests/grpc_plane_info.rs --- a/tests/grpc_plane_info.rs +++ b/tests/grpc_plane_info.rs @@ -1,27 +1,27 @@ -use syncode_collab_api_grpc::CollabService; -use syncode_collab_api_grpc::proto::GetPlaneInfoRequest; -use syncode_collab_api_grpc::proto::collab_client::CollabClient; -use tokio::net::TcpListener; -use tokio_stream::wrappers::TcpListenerStream; - -/// The gRPC contract is generated from `collab.proto` at build time. This test -/// proves the generated server and client agree and the service answers. -#[tokio::test] -async fn serves_plane_info() -> Result<(), Box> { - let listener = TcpListener::bind("127.0.0.1:0").await?; - let address = listener.local_addr()?; - - tokio::spawn(async move { - let _ = tonic::transport::Server::builder() - .add_service(CollabService.into_server()) - .serve_with_incoming(TcpListenerStream::new(listener)) - .await; - }); - - let mut client = CollabClient::connect(format!("http://{address}")).await?; - let info = client.get_plane_info(GetPlaneInfoRequest {}).await?; - - assert_eq!(env!("CARGO_PKG_VERSION"), info.get_ref().version); - assert_eq!(1, info.get_ref().envelope_protocol_version); - Ok(()) -} +use syncode_collab_api_grpc::CollabService; +use syncode_collab_api_grpc::proto::GetPlaneInfoRequest; +use syncode_collab_api_grpc::proto::collab_client::CollabClient; +use tokio::net::TcpListener; +use tokio_stream::wrappers::TcpListenerStream; + +/// The gRPC contract is generated from `collab.proto` at build time. This test +/// proves the generated server and client agree and the service answers. +#[tokio::test] +async fn serves_plane_info() -> Result<(), Box> { + let listener = TcpListener::bind("127.0.0.1:0").await?; + let address = listener.local_addr()?; + + tokio::spawn(async move { + let _ = tonic::transport::Server::builder() + .add_service(CollabService::default().into_server()) + .serve_with_incoming(TcpListenerStream::new(listener)) + .await; + }); + + let mut client = CollabClient::connect(format!("http://{address}")).await?; + let info = client.get_plane_info(GetPlaneInfoRequest {}).await?; + + assert_eq!(env!("CARGO_PKG_VERSION"), info.get_ref().version); + assert_eq!(1, info.get_ref().envelope_protocol_version); + Ok(()) +} diff --git a/crates/collab-api-graphql/Cargo.toml b/crates/collab-api-graphql/Cargo.toml --- a/crates/collab-api-graphql/Cargo.toml +++ b/crates/collab-api-graphql/Cargo.toml @@ -1,19 +1,20 @@ -[package] -name = "syncode-collab-api-graphql" -description = "GraphQL surface of the SynCode collaboration plane" -version.workspace = true -edition.workspace = true -rust-version.workspace = true -license.workspace = true -repository.workspace = true -publish = false - -[dependencies] -async-graphql = "7.2.1" -async-graphql-axum = "7.2.1" -axum = { version = "0.8.9", default-features = false, features = ["http1", "tokio"] } -syncode-collab-application = { path = "../collab-application" } -syncode-collab-model = { path = "../collab-model" } - -[lints] -workspace = true +[package] +name = "syncode-collab-api-graphql" +description = "GraphQL surface of the SynCode collaboration plane" +version.workspace = true +edition.workspace = true +rust-version.workspace = true +license.workspace = true +repository.workspace = true +publish = false + +[dependencies] +async-graphql = "7.2.1" +async-graphql-axum = "7.2.1" +axum = { version = "0.8.9", default-features = false, features = ["http1", "tokio"] } +syncode-collab-application = { path = "../collab-application" } +syncode-collab-model = { path = "../collab-model" } +uuid = "1.24.0" + +[lints] +workspace = true diff --git a/crates/collab-api-graphql/schema.graphql b/crates/collab-api-graphql/schema.graphql --- a/crates/collab-api-graphql/schema.graphql +++ b/crates/collab-api-graphql/schema.graphql @@ -1,23 +1,185 @@ -type Query { - """ - Version of the collaboration plane serving this schema. - """ - version: String! - """ - Protocol version of the cross-plane event envelope this service accepts. - """ - envelopeProtocolVersion: Int! -} - -""" -Directs the executor to include this field or fragment only when the `if` argument is true. -""" -directive @include(if: Boolean!) on FIELD | FRAGMENT_SPREAD | INLINE_FRAGMENT -""" -Directs the executor to skip this field or fragment when the `if` argument is true. -""" -directive @skip(if: Boolean!) on FIELD | FRAGMENT_SPREAD | INLINE_FRAGMENT -schema { - query: Query -} - +input AttachRepositoryInput { + workspaceId: ID! + owner: String! + name: String! + role: RepositoryRole! + branch: String! +} + +type Checkout { + workspace: Workspace! + environment: String! + repositories: [CheckoutRepository!]! + omittedRepositories: Int! +} + +type CheckoutRepository { + repositoryId: ID! + owner: String! + name: String! + role: RepositoryRole! + branch: String! +} + +input ConfigureEnvironmentInput { + workspaceId: ID! + name: String! + defaultSourceBranch: String! + infraBranch: String +} + +input CreateIssueInput { + projectId: ID! + title: String! + description: String! = "" + priority: IssuePriority! +} + +input CreateProjectInput { + workspaceId: ID! + key: String! + name: String! + slug: String! + description: String! = "" + kind: ProjectKind! +} + +input CreateVersionInput { + projectId: ID! + name: String! + slug: String! +} + +input CreateWorkspaceInput { + ownerKind: OwnerKind! + ownerId: ID! + name: String! + slug: String! + defaultEnvironment: String! + defaultSourceBranch: String! +} + +type Document { + id: ID! + workspaceId: ID! + projectId: ID + path: String! + revisionId: ID! + content: String! +} + +type Environment { + name: String! + defaultSourceBranch: String! + infraBranch: String +} + +type Issue { + id: ID! + projectId: ID! + number: Int! + title: String! + description: String! + statusId: ID! + typeId: ID! + priority: IssuePriority! + version: Int! +} + +enum IssuePriority { + NONE + LOW + NORMAL + HIGH + URGENT +} + +type Mutation { + createWorkspace(input: CreateWorkspaceInput!): Workspace! + attachRepository(input: AttachRepositoryInput!): Boolean! + configureEnvironment(input: ConfigureEnvironmentInput!): Environment! + createProject(input: CreateProjectInput!): Project! + putDocument(input: PutDocumentInput!): Document! + createIssue(input: CreateIssueInput!): Issue! + createVersion(input: CreateVersionInput!): Version! +} + +enum OwnerKind { + USER + ORGANIZATION +} + +type Project { + id: ID! + workspaceId: ID! + key: String! + name: String! + slug: String! + description: String! + kind: ProjectKind! + lifecycle: String! + version: Int! +} + +enum ProjectKind { + CONTINUOUS + FINITE +} + +input PutDocumentInput { + workspaceId: ID! + projectId: ID + path: String! + content: String! + baseRevisionId: ID +} + +type Query { + version: String! + envelopeProtocolVersion: Int! + workspaces: [Workspace!]! + workspace(id: ID!): Workspace! + checkout(workspaceId: ID!, environment: String, partial: Boolean): Checkout! + projects(workspaceId: ID!): [Project!]! + environments(workspaceId: ID!): [Environment!]! + documents(workspaceId: ID!, projectId: ID): [Document!]! + issues(projectId: ID!): [Issue!]! + versions(projectId: ID!): [Version!]! +} + +enum RepositoryRole { + SOURCE + INFRA +} + +type Version { + id: ID! + projectId: ID! + name: String! + slug: String! + state: String! +} + +type Workspace { + id: ID! + ownerKind: OwnerKind! + ownerId: ID! + name: String! + slug: String! + defaultEnvironment: String! + version: Int! +} + +""" +Directs the executor to include this field or fragment only when the `if` argument is true. +""" +directive @include(if: Boolean!) on FIELD | FRAGMENT_SPREAD | INLINE_FRAGMENT +""" +Directs the executor to skip this field or fragment when the `if` argument is true. +""" +directive @skip(if: Boolean!) on FIELD | FRAGMENT_SPREAD | INLINE_FRAGMENT +schema { + query: Query + mutation: Mutation +} + diff --git a/crates/collab-api-grpc/Cargo.toml b/crates/collab-api-grpc/Cargo.toml --- a/crates/collab-api-grpc/Cargo.toml +++ b/crates/collab-api-grpc/Cargo.toml @@ -1,24 +1,25 @@ -[package] -name = "syncode-collab-api-grpc" -description = "Versioned gRPC API of the SynCode collaboration plane" -version.workspace = true -edition.workspace = true -rust-version.workspace = true -license.workspace = true -repository.workspace = true -publish = false - -[dependencies] -prost = "0.14.4" -syncode-collab-application = { path = "../collab-application" } -syncode-collab-model = { path = "../collab-model" } -tonic = "0.14.6" -tonic-prost = "0.14.6" - -[build-dependencies] -prost-build = "0.14.4" -protoc-bin-vendored = "3.2.0" -tonic-prost-build = "0.14.6" - -[lints] -workspace = true +[package] +name = "syncode-collab-api-grpc" +description = "Versioned gRPC API of the SynCode collaboration plane" +version.workspace = true +edition.workspace = true +rust-version.workspace = true +license.workspace = true +repository.workspace = true +publish = false + +[dependencies] +prost = "0.14.4" +syncode-collab-application = { path = "../collab-application" } +syncode-collab-model = { path = "../collab-model" } +tonic = "0.14.6" +tonic-prost = "0.14.6" +uuid = "1.24.0" + +[build-dependencies] +prost-build = "0.14.4" +protoc-bin-vendored = "3.2.0" +tonic-prost-build = "0.14.6" + +[lints] +workspace = true diff --git a/crates/collab-api-grpc/build.rs b/crates/collab-api-grpc/build.rs --- a/crates/collab-api-grpc/build.rs +++ b/crates/collab-api-grpc/build.rs @@ -1,13 +1,13 @@ -use std::error::Error; - -fn main() -> Result<(), Box> { - let mut config = prost_build::Config::new(); - config.protoc_executable(protoc_bin_vendored::protoc_bin_path()?); - tonic_prost_build::configure().compile_with_config( - config, - &["proto/collab.proto"], - &["proto"], - )?; - println!("cargo:rerun-if-changed=proto"); - Ok(()) -} +use std::error::Error; + +fn main() -> Result<(), Box> { + let mut config = prost_build::Config::new(); + config.protoc_executable(protoc_bin_vendored::protoc_bin_path()?); + tonic_prost_build::configure().compile_with_config( + config, + &["proto/collab.proto", "proto/identity.proto"], + &["proto"], + )?; + println!("cargo:rerun-if-changed=proto"); + Ok(()) +} diff --git a/crates/collab-model/Cargo.toml b/crates/collab-model/Cargo.toml --- a/crates/collab-model/Cargo.toml +++ b/crates/collab-model/Cargo.toml @@ -1,18 +1,19 @@ -[package] -name = "syncode-collab-model" -description = "Domain model of the SynCode collaboration plane" -version.workspace = true -edition.workspace = true -rust-version.workspace = true -license.workspace = true -repository.workspace = true -publish = false - -[dependencies] -serde = { version = "1.0", features = ["derive"] } -serde_json = "1.0" -thiserror = "2.0.19" -uuid = { version = "1.24.0", features = ["serde", "v4"] } - -[lints] -workspace = true +[package] +name = "syncode-collab-model" +description = "Domain model of the SynCode collaboration plane" +version.workspace = true +edition.workspace = true +rust-version.workspace = true +license.workspace = true +repository.workspace = true +publish = false + +[dependencies] +chrono = { version = "0.4.42", features = ["serde"] } +serde = { version = "1.0", features = ["derive"] } +serde_json = "1.0" +thiserror = "2.0.19" +uuid = { version = "1.24.0", features = ["serde", "v4"] } + +[lints] +workspace = true diff --git a/crates/collab-store/Cargo.toml b/crates/collab-store/Cargo.toml --- a/crates/collab-store/Cargo.toml +++ b/crates/collab-store/Cargo.toml @@ -1,27 +1,28 @@ -[package] -name = "syncode-collab-store" -description = "PostgreSQL state store of the SynCode collaboration plane" -version.workspace = true -edition.workspace = true -rust-version.workspace = true -license.workspace = true -repository.workspace = true -publish = false - -[dependencies] -async-trait = "0.1.89" -serde_json = "1.0" -sqlx = { version = "0.8.6", default-features = false, features = ["json", "macros", "migrate", "postgres", "runtime-tokio", "tls-rustls", "uuid"] } -syncode-collab-application = { path = "../collab-application" } -syncode-collab-model = { path = "../collab-model" } -thiserror = "2.0.19" -uuid = "1.24.0" - -[dev-dependencies] -serde_json = "1.0" -sqlx = { version = "0.8.6", default-features = false, features = ["postgres", "runtime-tokio", "tls-rustls"] } -tokio = { version = "1.49.0", features = ["macros", "rt-multi-thread"] } -uuid = { version = "1.24.0", features = ["v4"] } - -[lints] -workspace = true +[package] +name = "syncode-collab-store" +description = "PostgreSQL state store of the SynCode collaboration plane" +version.workspace = true +edition.workspace = true +rust-version.workspace = true +license.workspace = true +repository.workspace = true +publish = false + +[dependencies] +async-trait = "0.1.89" +chrono = "0.4.42" +serde_json = "1.0" +sqlx = { version = "0.8.6", default-features = false, features = ["chrono", "json", "macros", "migrate", "postgres", "runtime-tokio", "tls-rustls", "uuid"] } +syncode-collab-application = { path = "../collab-application" } +syncode-collab-model = { path = "../collab-model" } +thiserror = "2.0.19" +uuid = "1.24.0" + +[dev-dependencies] +serde_json = "1.0" +sqlx = { version = "0.8.6", default-features = false, features = ["postgres", "runtime-tokio", "tls-rustls"] } +tokio = { version = "1.49.0", features = ["macros", "rt-multi-thread"] } +uuid = { version = "1.24.0", features = ["v4"] } + +[lints] +workspace = true diff --git a/crates/collab-api-graphql/src/lib.rs b/crates/collab-api-graphql/src/lib.rs --- a/crates/collab-api-graphql/src/lib.rs +++ b/crates/collab-api-graphql/src/lib.rs @@ -1,3 +1,7 @@ -pub mod schema; - -pub use schema::{CollabSchema, build, sdl}; +mod context; +mod mutation; +mod query; +pub mod schema; +mod types; + +pub use schema::{CollabSchema, SessionToken, build, sdl}; diff --git a/crates/collab-api-graphql/src/schema.rs b/crates/collab-api-graphql/src/schema.rs --- a/crates/collab-api-graphql/src/schema.rs +++ b/crates/collab-api-graphql/src/schema.rs @@ -1,26 +1,24 @@ -use async_graphql::{EmptyMutation, EmptySubscription, Object, Schema}; - -pub type CollabSchema = Schema; - -pub struct Query; - -#[Object] -impl Query { - /// Version of the collaboration plane serving this schema. - async fn version(&self) -> &'static str { - env!("CARGO_PKG_VERSION") - } - - /// Protocol version of the cross-plane event envelope this service accepts. - async fn envelope_protocol_version(&self) -> u32 { - syncode_collab_model::envelope::PROTOCOL_VERSION.0 - } -} - -pub fn build() -> CollabSchema { - Schema::build(Query, EmptyMutation, EmptySubscription).finish() -} - -pub fn sdl() -> String { - build().sdl() -} +use std::sync::Arc; + +use async_graphql::{EmptySubscription, Schema}; +use syncode_collab_application::Collaboration; + +use crate::mutation::Mutation; +use crate::query::Query; + +pub type CollabSchema = Schema; + +#[derive(Clone)] +pub struct SessionToken(pub Option); + +pub fn build(collaboration: Arc) -> CollabSchema { + Schema::build(Query, Mutation, EmptySubscription) + .data(collaboration) + .finish() +} + +pub fn sdl() -> String { + Schema::build(Query, Mutation, EmptySubscription) + .finish() + .sdl() +} diff --git a/crates/collab-api-grpc/proto/collab.proto b/crates/collab-api-grpc/proto/collab.proto --- a/crates/collab-api-grpc/proto/collab.proto +++ b/crates/collab-api-grpc/proto/collab.proto @@ -1,14 +1,245 @@ -syntax = "proto3"; - -package syncode.collab.v1; - -service Collab { - rpc GetPlaneInfo(GetPlaneInfoRequest) returns (PlaneInfo); -} - -message GetPlaneInfoRequest {} - -message PlaneInfo { - string version = 1; - uint32 envelope_protocol_version = 2; -} +syntax = "proto3"; + +package syncode.collab.v1; + +service Collab { + rpc GetPlaneInfo(GetPlaneInfoRequest) returns (PlaneInfo); + rpc CreateWorkspace(CreateWorkspaceRequest) returns (Workspace); + rpc ListWorkspaces(ListWorkspacesRequest) returns (ListWorkspacesResponse); + rpc GetWorkspace(GetWorkspaceRequest) returns (Workspace); + rpc AttachRepository(AttachRepositoryRequest) returns (OperationResponse); + rpc GetCheckout(GetCheckoutRequest) returns (Checkout); + rpc ConfigureEnvironment(ConfigureEnvironmentRequest) returns (Environment); + rpc ListEnvironments(ListEnvironmentsRequest) returns (ListEnvironmentsResponse); + rpc CreateProject(CreateProjectRequest) returns (Project); + rpc ListProjects(ListProjectsRequest) returns (ListProjectsResponse); + rpc PutDocument(PutDocumentRequest) returns (Document); + rpc ListDocuments(ListDocumentsRequest) returns (ListDocumentsResponse); + rpc CreateIssue(CreateIssueRequest) returns (Issue); + rpc ListIssues(ListIssuesRequest) returns (ListIssuesResponse); + rpc CreateVersion(CreateVersionRequest) returns (Version); + rpc ListVersions(ListVersionsRequest) returns (ListVersionsResponse); +} + +message GetPlaneInfoRequest {} + +message PlaneInfo { + string version = 1; + uint32 envelope_protocol_version = 2; +} + +enum OwnerKind { + OWNER_KIND_UNSPECIFIED = 0; + OWNER_KIND_USER = 1; + OWNER_KIND_ORGANIZATION = 2; +} + +enum RepositoryRole { + REPOSITORY_ROLE_UNSPECIFIED = 0; + REPOSITORY_ROLE_SOURCE = 1; + REPOSITORY_ROLE_INFRA = 2; +} + +enum ProjectKind { + PROJECT_KIND_UNSPECIFIED = 0; + PROJECT_KIND_CONTINUOUS = 1; + PROJECT_KIND_FINITE = 2; +} + +enum IssuePriority { + ISSUE_PRIORITY_UNSPECIFIED = 0; + ISSUE_PRIORITY_NONE = 1; + ISSUE_PRIORITY_LOW = 2; + ISSUE_PRIORITY_NORMAL = 3; + ISSUE_PRIORITY_HIGH = 4; + ISSUE_PRIORITY_URGENT = 5; +} + +message Workspace { + string id = 1; + OwnerKind owner_kind = 2; + string owner_id = 3; + string name = 4; + string slug = 5; + string default_environment = 6; + int64 version = 7; +} + +message CreateWorkspaceRequest { + string session_token = 1; + OwnerKind owner_kind = 2; + string owner_id = 3; + string name = 4; + string slug = 5; + string default_environment = 6; + string default_source_branch = 7; +} + +message ListWorkspacesRequest { string session_token = 1; } +message ListWorkspacesResponse { repeated Workspace workspaces = 1; } + +message GetWorkspaceRequest { + string session_token = 1; + string workspace_id = 2; +} + +message AttachRepositoryRequest { + string session_token = 1; + string workspace_id = 2; + string owner = 3; + string name = 4; + RepositoryRole role = 5; + string branch = 6; +} + +message OperationResponse {} + +message GetCheckoutRequest { + string session_token = 1; + string workspace_id = 2; + string environment = 3; + bool partial = 4; +} + +message CheckoutRepository { + string repository_id = 1; + string owner = 2; + string name = 3; + RepositoryRole role = 4; + string branch = 5; +} + +message Checkout { + Workspace workspace = 1; + string environment = 2; + repeated CheckoutRepository repositories = 3; + uint32 omitted_repositories = 4; +} + +message Environment { + string workspace_id = 1; + string name = 2; + string default_source_branch = 3; + string infra_branch = 4; +} + +message ConfigureEnvironmentRequest { + string session_token = 1; + string workspace_id = 2; + string name = 3; + string default_source_branch = 4; + string infra_branch = 5; +} + +message ListEnvironmentsRequest { + string session_token = 1; + string workspace_id = 2; +} + +message ListEnvironmentsResponse { repeated Environment environments = 1; } + +message Project { + string id = 1; + string workspace_id = 2; + string key = 3; + string name = 4; + string slug = 5; + string description = 6; + ProjectKind kind = 7; + string lifecycle = 8; + string default_status_id = 9; + string completion_status_id = 10; + int64 version = 11; +} + +message CreateProjectRequest { + string session_token = 1; + string workspace_id = 2; + string key = 3; + string name = 4; + string slug = 5; + string description = 6; + ProjectKind kind = 7; +} + +message ListProjectsRequest { + string session_token = 1; + string workspace_id = 2; +} + +message ListProjectsResponse { repeated Project projects = 1; } + +message Document { + string id = 1; + string workspace_id = 2; + string project_id = 3; + string path = 4; + string revision_id = 5; + string content = 6; +} + +message PutDocumentRequest { + string session_token = 1; + string workspace_id = 2; + string project_id = 3; + string path = 4; + string content = 5; + string base_revision_id = 6; +} + +message ListDocumentsRequest { + string session_token = 1; + string workspace_id = 2; + string project_id = 3; +} + +message ListDocumentsResponse { repeated Document documents = 1; } + +message Issue { + string id = 1; + string project_id = 2; + int64 number = 3; + string title = 4; + string description = 5; + string status_id = 6; + string type_id = 7; + IssuePriority priority = 8; + int64 version = 9; +} + +message CreateIssueRequest { + string session_token = 1; + string project_id = 2; + string title = 3; + string description = 4; + IssuePriority priority = 5; +} + +message ListIssuesRequest { + string session_token = 1; + string project_id = 2; +} + +message ListIssuesResponse { repeated Issue issues = 1; } + +message Version { + string id = 1; + string project_id = 2; + string name = 3; + string slug = 4; + string state = 5; +} + +message CreateVersionRequest { + string session_token = 1; + string project_id = 2; + string name = 3; + string slug = 4; +} + +message ListVersionsRequest { + string session_token = 1; + string project_id = 2; +} + +message ListVersionsResponse { repeated Version versions = 1; } diff --git a/crates/collab-api-grpc/src/lib.rs b/crates/collab-api-grpc/src/lib.rs --- a/crates/collab-api-grpc/src/lib.rs +++ b/crates/collab-api-grpc/src/lib.rs @@ -1,8 +1,15 @@ -pub mod service; - -pub mod proto { - #![allow(clippy::doc_overindented_list_items)] - tonic::include_proto!("syncode.collab.v1"); -} - -pub use service::CollabService; +mod mapping; +mod operations; +pub mod service; + +pub mod proto { + #![allow(clippy::doc_overindented_list_items)] + tonic::include_proto!("syncode.collab.v1"); +} + +pub mod identity_proto { + #![allow(clippy::doc_overindented_list_items)] + tonic::include_proto!("syncode.identity.v1"); +} + +pub use service::CollabService; diff --git a/crates/collab-api-grpc/src/service.rs b/crates/collab-api-grpc/src/service.rs --- a/crates/collab-api-grpc/src/service.rs +++ b/crates/collab-api-grpc/src/service.rs @@ -1,26 +1,179 @@ -use tonic::{Request, Response, Status}; - -use crate::proto::collab_server::{Collab, CollabServer}; -use crate::proto::{GetPlaneInfoRequest, PlaneInfo}; - -#[derive(Default)] -pub struct CollabService; - -impl CollabService { - pub fn into_server(self) -> CollabServer { - CollabServer::new(self) - } -} - -#[tonic::async_trait] -impl Collab for CollabService { - async fn get_plane_info( - &self, - _: Request, - ) -> Result, Status> { - Ok(Response::new(PlaneInfo { - version: env!("CARGO_PKG_VERSION").to_owned(), - envelope_protocol_version: syncode_collab_model::envelope::PROTOCOL_VERSION.0, - })) - } -} +use std::sync::Arc; + +use tonic::{Request, Response, Status}; + +use crate::operations; +use crate::proto::collab_server::{Collab, CollabServer}; +use crate::proto::*; +use syncode_collab_application::Collaboration; + +#[derive(Clone, Default)] +pub struct CollabService { + collaboration: Option>, +} + +impl CollabService { + pub fn new(collaboration: Arc) -> Self { + Self { + collaboration: Some(collaboration), + } + } + + pub fn into_server(self) -> CollabServer { + CollabServer::new(self) + } + + fn collaboration(&self) -> Result<&Collaboration, Status> { + self.collaboration + .as_deref() + .ok_or_else(|| Status::unavailable("collaboration application is not configured")) + } +} + +#[tonic::async_trait] +impl Collab for CollabService { + async fn get_plane_info( + &self, + _: Request, + ) -> Result, Status> { + Ok(Response::new(PlaneInfo { + version: env!("CARGO_PKG_VERSION").to_owned(), + envelope_protocol_version: syncode_collab_model::envelope::PROTOCOL_VERSION.0, + })) + } + + async fn create_workspace( + &self, + request: Request, + ) -> Result, Status> { + operations::create_workspace(self.collaboration()?, request.into_inner()) + .await + .map(Response::new) + } + + async fn list_workspaces( + &self, + request: Request, + ) -> Result, Status> { + operations::list_workspaces(self.collaboration()?, request.into_inner()) + .await + .map(Response::new) + } + + async fn get_workspace( + &self, + request: Request, + ) -> Result, Status> { + operations::get_workspace(self.collaboration()?, request.into_inner()) + .await + .map(Response::new) + } + + async fn attach_repository( + &self, + request: Request, + ) -> Result, Status> { + operations::attach_repository(self.collaboration()?, request.into_inner()) + .await + .map(Response::new) + } + + async fn get_checkout( + &self, + request: Request, + ) -> Result, Status> { + operations::get_checkout(self.collaboration()?, request.into_inner()) + .await + .map(Response::new) + } + + async fn configure_environment( + &self, + request: Request, + ) -> Result, Status> { + operations::configure_environment(self.collaboration()?, request.into_inner()) + .await + .map(Response::new) + } + + async fn list_environments( + &self, + request: Request, + ) -> Result, Status> { + operations::list_environments(self.collaboration()?, request.into_inner()) + .await + .map(Response::new) + } + + async fn create_project( + &self, + request: Request, + ) -> Result, Status> { + operations::create_project(self.collaboration()?, request.into_inner()) + .await + .map(Response::new) + } + + async fn list_projects( + &self, + request: Request, + ) -> Result, Status> { + operations::list_projects(self.collaboration()?, request.into_inner()) + .await + .map(Response::new) + } + + async fn put_document( + &self, + request: Request, + ) -> Result, Status> { + operations::put_document(self.collaboration()?, request.into_inner()) + .await + .map(Response::new) + } + + async fn list_documents( + &self, + request: Request, + ) -> Result, Status> { + operations::list_documents(self.collaboration()?, request.into_inner()) + .await + .map(Response::new) + } + + async fn create_issue( + &self, + request: Request, + ) -> Result, Status> { + operations::create_issue(self.collaboration()?, request.into_inner()) + .await + .map(Response::new) + } + + async fn list_issues( + &self, + request: Request, + ) -> Result, Status> { + operations::list_issues(self.collaboration()?, request.into_inner()) + .await + .map(Response::new) + } + + async fn create_version( + &self, + request: Request, + ) -> Result, Status> { + operations::create_version(self.collaboration()?, request.into_inner()) + .await + .map(Response::new) + } + + async fn list_versions( + &self, + request: Request, + ) -> Result, Status> { + operations::list_versions(self.collaboration()?, request.into_inner()) + .await + .map(Response::new) + } +} diff --git a/crates/collab-application/src/lib.rs b/crates/collab-application/src/lib.rs --- a/crates/collab-application/src/lib.rs +++ b/crates/collab-application/src/lib.rs @@ -1,9 +1,18 @@ -pub mod inbox; -pub mod outbox; -pub mod ports; - -pub use inbox::{Acceptance, InboxError, accept}; -pub use outbox::{ - DispatchReport, EventPublisher, OutboxMessage, OutboxQueue, PublishError, backoff, dispatch, -}; -pub use ports::{EventInbox, EventOutbox, OutboxRecord, StoreError}; +pub mod inbox; +pub mod outbox; +pub mod ports; +mod validation; +mod working_types; +mod working_versions; +mod working_workspace; + +pub use inbox::{Acceptance, InboxError, accept}; +pub use outbox::{ + DispatchReport, EventPublisher, OutboxMessage, OutboxQueue, PublishError, backoff, dispatch, +}; +pub use ports::{EventInbox, EventOutbox, OutboxRecord, StoreError}; +pub use working_types::{ + CollaborationError, ConfigureEnvironment, CreateDocument, CreateIssue, CreateProject, + CreateVersion, CreateWorkspace, Principal, PrincipalKind, RepositoryCoordinates, +}; +pub use working_workspace::Collaboration; diff --git a/crates/collab-application/src/ports.rs b/crates/collab-application/src/ports.rs --- a/crates/collab-application/src/ports.rs +++ b/crates/collab-application/src/ports.rs @@ -1,39 +1,188 @@ -use async_trait::async_trait; -use syncode_collab_model::Envelope; -use uuid::Uuid; - -#[derive(Clone, Debug)] -pub struct OutboxRecord { - pub message_id: Uuid, - pub message_type: String, - pub resource_id: Uuid, - pub payload: serde_json::Value, -} - -/// Records a foreign fact exactly once. The implementation must write the -/// deduplication key in the same transaction as the effect it applies. -#[async_trait] -pub trait EventInbox: Send + Sync { - async fn already_applied(&self, message_id: Uuid) -> Result; - - async fn last_position( - &self, - source_node_id: &str, - resource_id: Uuid, - ) -> Result, StoreError>; - - async fn apply(&self, envelope: &Envelope) -> Result<(), StoreError>; -} - -#[async_trait] -pub trait EventOutbox: Send + Sync { - async fn enqueue(&self, record: OutboxRecord) -> Result<(), StoreError>; -} - -#[derive(Debug, thiserror::Error)] -pub enum StoreError { - #[error("collaboration store is unavailable: {0}")] - Unavailable(String), - #[error("collaboration store rejected the write: {0}")] - Rejected(String), -} +use async_trait::async_trait; +use syncode_collab_model::{ + Document, DocumentRevision, Envelope, Environment, Issue, Project, RepositoryRole, Version, + Workspace, +}; +use uuid::Uuid; + +#[derive(Clone, Debug)] +pub struct OutboxRecord { + pub message_id: Uuid, + pub message_type: String, + pub resource_id: Uuid, + pub payload: serde_json::Value, +} + +/// Records a foreign fact exactly once. The implementation must write the +/// deduplication key in the same transaction as the effect it applies. +#[async_trait] +pub trait EventInbox: Send + Sync { + async fn already_applied(&self, message_id: Uuid) -> Result; + + async fn last_position( + &self, + source_node_id: &str, + resource_id: Uuid, + ) -> Result, StoreError>; + + async fn apply(&self, envelope: &Envelope) -> Result<(), StoreError>; +} + +#[async_trait] +pub trait EventOutbox: Send + Sync { + async fn enqueue(&self, record: OutboxRecord) -> Result<(), StoreError>; +} + +#[async_trait] +pub trait IdentityAccess: Send + Sync { + async fn authenticate(&self, session_token: &str) -> Result; + + async fn require_organization_admin( + &self, + principal: crate::Principal, + organization_id: Uuid, + ) -> Result<(), AccessError>; + + async fn resolve_readable_repository( + &self, + principal: crate::Principal, + owner: &str, + name: &str, + ) -> Result; + + async fn repository_coordinates( + &self, + principal: crate::Principal, + repository_id: Uuid, + ) -> Result; +} + +#[derive(Clone, Debug)] +pub struct StoredRepository { + pub repository_id: Uuid, + pub role: RepositoryRole, +} + +#[async_trait] +pub trait CollaborationStore: Send + Sync { + async fn create_workspace( + &self, + principal_id: Uuid, + command: &crate::CreateWorkspace, + ) -> Result; + + async fn list_workspaces(&self, principal_id: Uuid) -> Result, StoreError>; + + async fn workspace( + &self, + principal_id: Uuid, + workspace_id: Uuid, + ) -> Result; + + async fn attach_repository( + &self, + principal_id: Uuid, + workspace_id: Uuid, + repository_id: Uuid, + role: RepositoryRole, + branch: &str, + ) -> Result<(), StoreError>; + + async fn repositories( + &self, + principal_id: Uuid, + workspace_id: Uuid, + ) -> Result, StoreError>; + + async fn environment( + &self, + principal_id: Uuid, + workspace_id: Uuid, + name: &str, + ) -> Result; + + async fn configure_environment( + &self, + principal_id: Uuid, + command: &crate::ConfigureEnvironment, + ) -> Result; + + async fn list_environments( + &self, + principal_id: Uuid, + workspace_id: Uuid, + ) -> Result, StoreError>; + + async fn create_project( + &self, + principal_id: Uuid, + command: &crate::CreateProject, + ) -> Result; + + async fn list_projects( + &self, + principal_id: Uuid, + workspace_id: Uuid, + ) -> Result, StoreError>; + + async fn write_document( + &self, + principal_id: Uuid, + command: &crate::CreateDocument, + ) -> Result<(Document, DocumentRevision), StoreError>; + + async fn list_documents( + &self, + principal_id: Uuid, + workspace_id: Uuid, + project_id: Option, + ) -> Result, StoreError>; + + async fn create_issue( + &self, + principal_id: Uuid, + command: &crate::CreateIssue, + ) -> Result; + + async fn list_issues( + &self, + principal_id: Uuid, + project_id: Uuid, + ) -> Result, StoreError>; + + async fn create_version( + &self, + principal_id: Uuid, + command: &crate::CreateVersion, + ) -> Result; + + async fn list_versions( + &self, + principal_id: Uuid, + project_id: Uuid, + ) -> Result, StoreError>; +} + +#[derive(Debug, thiserror::Error)] +pub enum AccessError { + #[error("session is not authenticated")] + Unauthenticated, + #[error("access is denied")] + Denied, + #[error("identity resource was not found")] + NotFound, + #[error("identity is unavailable: {0}")] + Unavailable(String), +} + +#[derive(Debug, thiserror::Error)] +pub enum StoreError { + #[error("collaboration store is unavailable: {0}")] + Unavailable(String), + #[error("collaboration store rejected the write: {0}")] + Rejected(String), + #[error("collaboration resource was not found")] + NotFound, + #[error("collaboration write conflicts with current state: {0}")] + Conflict(String), +} diff --git a/crates/collab-model/src/lib.rs b/crates/collab-model/src/lib.rs --- a/crates/collab-model/src/lib.rs +++ b/crates/collab-model/src/lib.rs @@ -1,5 +1,20 @@ -pub mod envelope; -pub mod plane; - -pub use envelope::{Envelope, EnvelopeError, MessageType, ProtocolVersion, SchemaVersion}; -pub use plane::Plane; +pub mod document; +pub mod envelope; +pub mod issue; +pub mod plane; +pub mod project; +pub mod version; +pub mod workspace; + +pub use document::{Document, DocumentRevision, DocumentScope}; +pub use envelope::{Envelope, EnvelopeError, MessageType, ProtocolVersion, SchemaVersion}; +pub use issue::{Issue, IssuePriority}; +pub use plane::Plane; +pub use project::{ + IssueStatus, IssueStatusCategory, IssueType, Project, ProjectKind, ProjectLifecycle, +}; +pub use version::{Version, VersionState}; +pub use workspace::{ + Checkout, CheckoutRepository, Environment, OwnerKind, RepositoryRole, Workspace, + WorkspaceRepository, +}; diff --git a/crates/collab-store/src/lib.rs b/crates/collab-store/src/lib.rs --- a/crates/collab-store/src/lib.rs +++ b/crates/collab-store/src/lib.rs @@ -1,7 +1,16 @@ -pub mod inbox; -pub mod outbox; -pub mod pool; - -pub use inbox::PostgresInbox; -pub use outbox::PostgresOutbox; -pub use pool::{PoolError, connect, migrate}; +pub mod inbox; +pub mod outbox; +pub mod pool; +mod working; +mod working_documents; +mod working_environments; +mod working_issues; +mod working_projects; +mod working_rows; +mod working_versions; +mod working_workspaces; + +pub use inbox::PostgresInbox; +pub use outbox::PostgresOutbox; +pub use pool::{PoolError, connect, migrate}; +pub use working::PostgresCollaboration; diff --git a/crates/collab-api-graphql/src/context.rs b/crates/collab-api-graphql/src/context.rs --- /dev/null +++ b/crates/collab-api-graphql/src/context.rs @@ -1,0 +1,48 @@ +use async_graphql::{Context, ErrorExtensions, ID}; +use syncode_collab_application::ports::{AccessError, StoreError}; +use syncode_collab_application::{Collaboration, CollaborationError, Principal}; +use uuid::Uuid; + +use crate::schema::SessionToken; + +pub(crate) async fn principal(ctx: &Context<'_>) -> async_graphql::Result { + let app = ctx.data::>()?; + let token = ctx + .data::()? + .0 + .as_deref() + .ok_or_else(|| error(CollaborationError::Access(AccessError::Unauthenticated)))?; + app.authenticate(token).await.map_err(error) +} + +pub(crate) fn app<'a>(ctx: &'a Context<'_>) -> async_graphql::Result<&'a Collaboration> { + Ok(ctx.data::>()?.as_ref()) +} + +pub(crate) fn id(value: &ID, field: &str) -> async_graphql::Result { + value + .as_str() + .parse() + .map_err(|_| async_graphql::Error::new(format!("{field} must be a UUID"))) +} + +pub(crate) fn optional_id(value: Option<&ID>, field: &str) -> async_graphql::Result> { + value.map(|value| id(value, field)).transpose() +} + +pub(crate) fn error(error: CollaborationError) -> async_graphql::Error { + let code = match &error { + CollaborationError::Access(AccessError::Unauthenticated) => "UNAUTHENTICATED", + CollaborationError::Access(AccessError::Denied) => "FORBIDDEN", + CollaborationError::Access(AccessError::NotFound) + | CollaborationError::Store(StoreError::NotFound) => "NOT_FOUND", + CollaborationError::Store(StoreError::Conflict(_)) => "CONFLICT", + CollaborationError::Invalid(_) => "BAD_USER_INPUT", + CollaborationError::Access(AccessError::Unavailable(_)) + | CollaborationError::Store(StoreError::Unavailable(_)) => "UNAVAILABLE", + CollaborationError::Store(StoreError::Rejected(_)) => "FAILED_PRECONDITION", + }; + async_graphql::Error::new(error.to_string()).extend_with(|_, extensions| { + extensions.set("code", code); + }) +} diff --git a/crates/collab-api-graphql/src/mutation.rs b/crates/collab-api-graphql/src/mutation.rs --- /dev/null +++ b/crates/collab-api-graphql/src/mutation.rs @@ -1,0 +1,248 @@ +use async_graphql::{Context, ID, InputObject, Object}; +use syncode_collab_application::{ + ConfigureEnvironment, CreateDocument, CreateIssue, CreateProject, CreateVersion, + CreateWorkspace, +}; +use syncode_collab_model as model; + +use crate::context; +use crate::types::{ + Document, Environment, Issue, IssuePriority, OwnerKind, Project, ProjectKind, RepositoryRole, + Version, Workspace, +}; + +#[derive(InputObject)] +struct CreateWorkspaceInput { + owner_kind: OwnerKind, + owner_id: ID, + name: String, + slug: String, + default_environment: String, + default_source_branch: String, +} + +#[derive(InputObject)] +struct AttachRepositoryInput { + workspace_id: ID, + owner: String, + name: String, + role: RepositoryRole, + branch: String, +} + +#[derive(InputObject)] +struct ConfigureEnvironmentInput { + workspace_id: ID, + name: String, + default_source_branch: String, + infra_branch: Option, +} + +#[derive(InputObject)] +struct CreateProjectInput { + workspace_id: ID, + key: String, + name: String, + slug: String, + #[graphql(default)] + description: String, + kind: ProjectKind, +} + +#[derive(InputObject)] +struct PutDocumentInput { + workspace_id: ID, + project_id: Option, + path: String, + content: String, + base_revision_id: Option, +} + +#[derive(InputObject)] +struct CreateIssueInput { + project_id: ID, + title: String, + #[graphql(default)] + description: String, + priority: IssuePriority, +} + +#[derive(InputObject)] +struct CreateVersionInput { + project_id: ID, + name: String, + slug: String, +} + +pub struct Mutation; + +#[Object] +impl Mutation { + async fn create_workspace( + &self, + ctx: &Context<'_>, + input: CreateWorkspaceInput, + ) -> async_graphql::Result { + let principal = context::principal(ctx).await?; + context::app(ctx)? + .create_workspace( + principal, + CreateWorkspace { + owner_kind: match input.owner_kind { + OwnerKind::User => model::OwnerKind::User, + OwnerKind::Organization => model::OwnerKind::Organization, + }, + owner_id: context::id(&input.owner_id, "owner id")?, + name: input.name, + slug: input.slug, + default_environment: input.default_environment, + default_source_branch: input.default_source_branch, + }, + ) + .await + .map(Into::into) + .map_err(context::error) + } + + async fn attach_repository( + &self, + ctx: &Context<'_>, + input: AttachRepositoryInput, + ) -> async_graphql::Result { + let principal = context::principal(ctx).await?; + context::app(ctx)? + .attach_repository( + principal, + context::id(&input.workspace_id, "workspace id")?, + &input.owner, + &input.name, + match input.role { + RepositoryRole::Source => model::RepositoryRole::Source, + RepositoryRole::Infra => model::RepositoryRole::Infra, + }, + &input.branch, + ) + .await + .map(|()| true) + .map_err(context::error) + } + + async fn configure_environment( + &self, + ctx: &Context<'_>, + input: ConfigureEnvironmentInput, + ) -> async_graphql::Result { + let principal = context::principal(ctx).await?; + context::app(ctx)? + .configure_environment( + principal, + ConfigureEnvironment { + workspace_id: context::id(&input.workspace_id, "workspace id")?, + name: input.name, + default_source_branch: input.default_source_branch, + infra_branch: input.infra_branch, + }, + ) + .await + .map(Into::into) + .map_err(context::error) + } + + async fn create_project( + &self, + ctx: &Context<'_>, + input: CreateProjectInput, + ) -> async_graphql::Result { + let principal = context::principal(ctx).await?; + context::app(ctx)? + .create_project( + principal, + CreateProject { + workspace_id: context::id(&input.workspace_id, "workspace id")?, + key: input.key, + name: input.name, + slug: input.slug, + description: input.description, + kind: match input.kind { + ProjectKind::Continuous => model::ProjectKind::Continuous, + ProjectKind::Finite => model::ProjectKind::Finite, + }, + }, + ) + .await + .map(Into::into) + .map_err(context::error) + } + + async fn put_document( + &self, + ctx: &Context<'_>, + input: PutDocumentInput, + ) -> async_graphql::Result { + let principal = context::principal(ctx).await?; + context::app(ctx)? + .write_document( + principal, + CreateDocument { + workspace_id: context::id(&input.workspace_id, "workspace id")?, + project_id: context::optional_id(input.project_id.as_ref(), "project id")?, + path: input.path, + content: input.content, + base_revision_id: context::optional_id( + input.base_revision_id.as_ref(), + "base revision id", + )?, + }, + ) + .await + .map(Into::into) + .map_err(context::error) + } + + async fn create_issue( + &self, + ctx: &Context<'_>, + input: CreateIssueInput, + ) -> async_graphql::Result { + let principal = context::principal(ctx).await?; + context::app(ctx)? + .create_issue( + principal, + CreateIssue { + project_id: context::id(&input.project_id, "project id")?, + title: input.title, + description: input.description, + priority: match input.priority { + IssuePriority::None => model::IssuePriority::None, + IssuePriority::Low => model::IssuePriority::Low, + IssuePriority::Normal => model::IssuePriority::Normal, + IssuePriority::High => model::IssuePriority::High, + IssuePriority::Urgent => model::IssuePriority::Urgent, + }, + }, + ) + .await + .map(Into::into) + .map_err(context::error) + } + + async fn create_version( + &self, + ctx: &Context<'_>, + input: CreateVersionInput, + ) -> async_graphql::Result { + let principal = context::principal(ctx).await?; + context::app(ctx)? + .create_version( + principal, + CreateVersion { + project_id: context::id(&input.project_id, "project id")?, + name: input.name, + slug: input.slug, + }, + ) + .await + .map(Into::into) + .map_err(context::error) + } +} diff --git a/crates/collab-api-graphql/src/query.rs b/crates/collab-api-graphql/src/query.rs --- /dev/null +++ b/crates/collab-api-graphql/src/query.rs @@ -1,0 +1,121 @@ +use async_graphql::{Context, ID, Object}; + +use crate::context; +use crate::types::{Checkout, Document, Environment, Issue, Project, Version, Workspace}; + +pub struct Query; + +#[Object] +impl Query { + async fn version(&self) -> &'static str { + env!("CARGO_PKG_VERSION") + } + + async fn envelope_protocol_version(&self) -> u32 { + syncode_collab_model::envelope::PROTOCOL_VERSION.0 + } + + async fn workspaces(&self, ctx: &Context<'_>) -> async_graphql::Result> { + let principal = context::principal(ctx).await?; + context::app(ctx)? + .list_workspaces(principal) + .await + .map(|values| values.into_iter().map(Into::into).collect()) + .map_err(context::error) + } + + async fn workspace(&self, ctx: &Context<'_>, id: ID) -> async_graphql::Result { + let principal = context::principal(ctx).await?; + context::app(ctx)? + .workspace(principal, context::id(&id, "workspace id")?) + .await + .map(Into::into) + .map_err(context::error) + } + + async fn checkout( + &self, + ctx: &Context<'_>, + workspace_id: ID, + environment: Option, + partial: Option, + ) -> async_graphql::Result { + let principal = context::principal(ctx).await?; + context::app(ctx)? + .checkout( + principal, + context::id(&workspace_id, "workspace id")?, + environment.as_deref(), + partial.unwrap_or(false), + ) + .await + .map(Into::into) + .map_err(context::error) + } + + async fn projects( + &self, + ctx: &Context<'_>, + workspace_id: ID, + ) -> async_graphql::Result> { + let principal = context::principal(ctx).await?; + context::app(ctx)? + .list_projects(principal, context::id(&workspace_id, "workspace id")?) + .await + .map(|values| values.into_iter().map(Into::into).collect()) + .map_err(context::error) + } + + async fn environments( + &self, + ctx: &Context<'_>, + workspace_id: ID, + ) -> async_graphql::Result> { + let principal = context::principal(ctx).await?; + context::app(ctx)? + .list_environments(principal, context::id(&workspace_id, "workspace id")?) + .await + .map(|values| values.into_iter().map(Into::into).collect()) + .map_err(context::error) + } + + async fn documents( + &self, + ctx: &Context<'_>, + workspace_id: ID, + project_id: Option, + ) -> async_graphql::Result> { + let principal = context::principal(ctx).await?; + context::app(ctx)? + .list_documents( + principal, + context::id(&workspace_id, "workspace id")?, + context::optional_id(project_id.as_ref(), "project id")?, + ) + .await + .map(|values| values.into_iter().map(Into::into).collect()) + .map_err(context::error) + } + + async fn issues(&self, ctx: &Context<'_>, project_id: ID) -> async_graphql::Result> { + let principal = context::principal(ctx).await?; + context::app(ctx)? + .list_issues(principal, context::id(&project_id, "project id")?) + .await + .map(|values| values.into_iter().map(Into::into).collect()) + .map_err(context::error) + } + + async fn versions( + &self, + ctx: &Context<'_>, + project_id: ID, + ) -> async_graphql::Result> { + let principal = context::principal(ctx).await?; + context::app(ctx)? + .list_versions(principal, context::id(&project_id, "project id")?) + .await + .map(|values| values.into_iter().map(Into::into).collect()) + .map_err(context::error) + } +} diff --git a/crates/collab-api-graphql/src/types.rs b/crates/collab-api-graphql/src/types.rs --- /dev/null +++ b/crates/collab-api-graphql/src/types.rs @@ -1,0 +1,226 @@ +use async_graphql::{Enum, ID, SimpleObject}; +use syncode_collab_model as model; + +#[derive(Clone, Copy, Debug, Eq, PartialEq, Enum)] +pub enum OwnerKind { + User, + Organization, +} + +#[derive(Clone, Copy, Debug, Eq, PartialEq, Enum)] +pub enum RepositoryRole { + Source, + Infra, +} + +#[derive(Clone, Copy, Debug, Eq, PartialEq, Enum)] +pub enum ProjectKind { + Continuous, + Finite, +} + +#[derive(Clone, Copy, Debug, Eq, PartialEq, Enum)] +pub enum IssuePriority { + None, + Low, + Normal, + High, + Urgent, +} + +#[derive(Clone, SimpleObject)] +pub struct Workspace { + pub id: ID, + pub owner_kind: OwnerKind, + pub owner_id: ID, + pub name: String, + pub slug: String, + pub default_environment: String, + pub version: i64, +} + +#[derive(Clone, SimpleObject)] +pub struct CheckoutRepository { + pub repository_id: ID, + pub owner: String, + pub name: String, + pub role: RepositoryRole, + pub branch: String, +} + +#[derive(Clone, SimpleObject)] +pub struct Checkout { + pub workspace: Workspace, + pub environment: String, + pub repositories: Vec, + pub omitted_repositories: u32, +} + +#[derive(Clone, SimpleObject)] +pub struct Environment { + pub name: String, + pub default_source_branch: String, + pub infra_branch: Option, +} + +#[derive(Clone, SimpleObject)] +pub struct Project { + pub id: ID, + pub workspace_id: ID, + pub key: String, + pub name: String, + pub slug: String, + pub description: String, + pub kind: ProjectKind, + pub lifecycle: String, + pub version: i64, +} + +#[derive(Clone, SimpleObject)] +pub struct Document { + pub id: ID, + pub workspace_id: ID, + pub project_id: Option, + pub path: String, + pub revision_id: ID, + pub content: String, +} + +#[derive(Clone, SimpleObject)] +pub struct Issue { + pub id: ID, + pub project_id: ID, + pub number: i64, + pub title: String, + pub description: String, + pub status_id: ID, + pub type_id: ID, + pub priority: IssuePriority, + pub version: i64, +} + +#[derive(Clone, SimpleObject)] +pub struct Version { + pub id: ID, + pub project_id: ID, + pub name: String, + pub slug: String, + pub state: String, +} + +impl From for Workspace { + fn from(value: model::Workspace) -> Self { + Self { + id: value.id.into(), + owner_kind: match value.owner_kind { + model::OwnerKind::User => OwnerKind::User, + model::OwnerKind::Organization => OwnerKind::Organization, + }, + owner_id: value.owner_id.into(), + name: value.name, + slug: value.slug, + default_environment: value.default_environment, + version: value.version, + } + } +} + +impl From for Checkout { + fn from(value: model::Checkout) -> Self { + Self { + workspace: value.workspace.into(), + environment: value.environment, + repositories: value + .repositories + .into_iter() + .map(|repository| CheckoutRepository { + repository_id: repository.repository_id.into(), + owner: repository.owner, + name: repository.name, + role: match repository.role { + model::RepositoryRole::Source => RepositoryRole::Source, + model::RepositoryRole::Infra => RepositoryRole::Infra, + }, + branch: repository.branch, + }) + .collect(), + omitted_repositories: value.omitted_repositories, + } + } +} + +impl From for Environment { + fn from(value: model::Environment) -> Self { + Self { + name: value.name, + default_source_branch: value.default_source_branch, + infra_branch: value.infra_branch, + } + } +} + +impl From for Project { + fn from(value: model::Project) -> Self { + Self { + id: value.id.into(), + workspace_id: value.workspace_id.into(), + key: value.key, + name: value.name, + slug: value.slug, + description: value.description, + kind: match value.kind { + model::ProjectKind::Continuous => ProjectKind::Continuous, + model::ProjectKind::Finite => ProjectKind::Finite, + }, + lifecycle: format!("{:?}", value.lifecycle).to_lowercase(), + version: value.version, + } + } +} + +impl From<(model::Document, model::DocumentRevision)> for Document { + fn from(value: (model::Document, model::DocumentRevision)) -> Self { + Self { + id: value.0.id.into(), + workspace_id: value.0.workspace_id.into(), + project_id: value.0.project_id.map(Into::into), + path: value.0.path, + revision_id: value.1.id.into(), + content: value.1.content, + } + } +} + +impl From for Issue { + fn from(value: model::Issue) -> Self { + Self { + id: value.id.into(), + project_id: value.project_id.into(), + number: value.number, + title: value.title, + description: value.description, + status_id: value.status_id.into(), + type_id: value.type_id.into(), + priority: match value.priority { + model::IssuePriority::None => IssuePriority::None, + model::IssuePriority::Low => IssuePriority::Low, + model::IssuePriority::Normal => IssuePriority::Normal, + model::IssuePriority::High => IssuePriority::High, + model::IssuePriority::Urgent => IssuePriority::Urgent, + }, + version: value.version, + } + } +} + +impl From for Version { + fn from(value: model::Version) -> Self { + Self { + id: value.id.into(), + project_id: value.project_id.into(), + name: value.name, + slug: value.slug, + state: format!("{:?}", value.state).to_lowercase(), + } + } +} diff --git a/crates/collab-api-grpc/proto/identity.proto b/crates/collab-api-grpc/proto/identity.proto --- /dev/null +++ b/crates/collab-api-grpc/proto/identity.proto @@ -1,0 +1,62 @@ +syntax = "proto3"; + +package syncode.identity.v1; + +service Identity { + rpc ValidateSession(ValidateSessionRequest) returns (ValidateSessionResponse); + rpc CheckCapability(CheckCapabilityRequest) returns (CheckCapabilityResponse); + rpc ResolveRepository(ResolveRepositoryRequest) returns (ResolveRepositoryResponse); + rpc GetRepositoryCoordinates(GetRepositoryCoordinatesRequest) returns (GetRepositoryCoordinatesResponse); +} + +enum PrincipalKind { + PRINCIPAL_KIND_UNSPECIFIED = 0; + PRINCIPAL_KIND_USER = 1; + PRINCIPAL_KIND_PLATFORM_AGENT = 2; + PRINCIPAL_KIND_LOCAL_AGENT = 3; + PRINCIPAL_KIND_ACCESS_TOKEN = 4; +} + +enum ResourceKind { + RESOURCE_KIND_UNSPECIFIED = 0; + RESOURCE_KIND_ORGANIZATION = 1; + RESOURCE_KIND_REPOSITORY = 2; + RESOURCE_KIND_INSTANCE = 3; +} + +message ValidateSessionRequest { string session_token = 1; } + +message ValidateSessionResponse { + string principal_id = 1; + PrincipalKind principal_kind = 2; + int64 expires_at_unix = 3; + string owner_user_id = 4; + repeated string capabilities = 5; + string audience = 6; + ResourceKind resource_kind = 7; + string resource_id = 8; +} + +message CheckCapabilityRequest { + string principal_id = 1; + PrincipalKind principal_kind = 2; + ResourceKind resource_kind = 3; + string resource_id = 4; + string capability = 5; +} + +message CheckCapabilityResponse { bool allowed = 1; } + +message ResolveRepositoryRequest { + string owner = 1; + string name = 2; +} + +message ResolveRepositoryResponse { string repository_id = 1; } + +message GetRepositoryCoordinatesRequest { string repository_id = 1; } + +message GetRepositoryCoordinatesResponse { + string owner = 1; + string name = 2; +} diff --git a/crates/collab-api-grpc/src/mapping.rs b/crates/collab-api-grpc/src/mapping.rs --- /dev/null +++ b/crates/collab-api-grpc/src/mapping.rs @@ -1,0 +1,127 @@ +use tonic::Status; +use uuid::Uuid; + +use crate::proto; +use syncode_collab_application::CollaborationError; +use syncode_collab_application::ports::{AccessError, StoreError}; +use syncode_collab_model as model; + +pub(crate) fn id(value: &str, field: &str) -> Result { + value + .parse() + .map_err(|_| Status::invalid_argument(format!("{field} must be a UUID"))) +} + +pub(crate) fn optional_id(value: &str, field: &str) -> Result, Status> { + (!value.is_empty()).then(|| id(value, field)).transpose() +} + +pub(crate) fn workspace(value: model::Workspace) -> proto::Workspace { + proto::Workspace { + id: value.id.to_string(), + owner_kind: match value.owner_kind { + model::OwnerKind::User => proto::OwnerKind::User.into(), + model::OwnerKind::Organization => proto::OwnerKind::Organization.into(), + }, + owner_id: value.owner_id.to_string(), + name: value.name, + slug: value.slug, + default_environment: value.default_environment, + version: value.version, + } +} + +pub(crate) fn environment(value: model::Environment) -> proto::Environment { + proto::Environment { + workspace_id: value.workspace_id.to_string(), + name: value.name, + default_source_branch: value.default_source_branch, + infra_branch: value.infra_branch.unwrap_or_default(), + } +} + +pub(crate) fn project(value: model::Project) -> proto::Project { + proto::Project { + id: value.id.to_string(), + workspace_id: value.workspace_id.to_string(), + key: value.key, + name: value.name, + slug: value.slug, + description: value.description, + kind: match value.kind { + model::ProjectKind::Continuous => proto::ProjectKind::Continuous.into(), + model::ProjectKind::Finite => proto::ProjectKind::Finite.into(), + }, + lifecycle: format!("{:?}", value.lifecycle).to_lowercase(), + default_status_id: value.default_status_id.to_string(), + completion_status_id: value.completion_status_id.to_string(), + version: value.version, + } +} + +pub(crate) fn document(value: (model::Document, model::DocumentRevision)) -> proto::Document { + proto::Document { + id: value.0.id.to_string(), + workspace_id: value.0.workspace_id.to_string(), + project_id: value + .0 + .project_id + .map(|id| id.to_string()) + .unwrap_or_default(), + path: value.0.path, + revision_id: value.1.id.to_string(), + content: value.1.content, + } +} + +pub(crate) fn issue(value: model::Issue) -> proto::Issue { + proto::Issue { + id: value.id.to_string(), + project_id: value.project_id.to_string(), + number: value.number, + title: value.title, + description: value.description, + status_id: value.status_id.to_string(), + type_id: value.type_id.to_string(), + priority: match value.priority { + model::IssuePriority::None => proto::IssuePriority::None.into(), + model::IssuePriority::Low => proto::IssuePriority::Low.into(), + model::IssuePriority::Normal => proto::IssuePriority::Normal.into(), + model::IssuePriority::High => proto::IssuePriority::High.into(), + model::IssuePriority::Urgent => proto::IssuePriority::Urgent.into(), + }, + version: value.version, + } +} + +pub(crate) fn version(value: model::Version) -> proto::Version { + proto::Version { + id: value.id.to_string(), + project_id: value.project_id.to_string(), + name: value.name, + slug: value.slug, + state: format!("{:?}", value.state).to_lowercase(), + } +} + +pub(crate) fn error(error: CollaborationError) -> Status { + match error { + CollaborationError::Access(AccessError::Unauthenticated) => { + Status::unauthenticated(error.to_string()) + } + CollaborationError::Access(AccessError::Denied) => { + Status::permission_denied(error.to_string()) + } + CollaborationError::Access(AccessError::NotFound) + | CollaborationError::Store(StoreError::NotFound) => Status::not_found(error.to_string()), + CollaborationError::Store(StoreError::Conflict(_)) => Status::aborted(error.to_string()), + CollaborationError::Invalid(_) => Status::invalid_argument(error.to_string()), + CollaborationError::Access(AccessError::Unavailable(_)) + | CollaborationError::Store(StoreError::Unavailable(_)) => { + Status::unavailable(error.to_string()) + } + CollaborationError::Store(StoreError::Rejected(_)) => { + Status::failed_precondition(error.to_string()) + } + } +} diff --git a/crates/collab-api-grpc/src/operations/content.rs b/crates/collab-api-grpc/src/operations/content.rs --- /dev/null +++ b/crates/collab-api-grpc/src/operations/content.rs @@ -1,0 +1,192 @@ +use tonic::Status; + +use crate::mapping; +use crate::proto::*; +use syncode_collab_application::{ + Collaboration, CreateDocument, CreateIssue, CreateProject, CreateVersion, +}; +use syncode_collab_model as model; + +pub(crate) async fn create_project( + app: &Collaboration, + request: CreateProjectRequest, +) -> Result { + let principal = app + .authenticate(&request.session_token) + .await + .map_err(mapping::error)?; + let kind = match ProjectKind::try_from(request.kind) { + Ok(ProjectKind::Continuous) => model::ProjectKind::Continuous, + Ok(ProjectKind::Finite) => model::ProjectKind::Finite, + Ok(ProjectKind::Unspecified) | Err(_) => { + return Err(Status::invalid_argument("kind is required")); + } + }; + app.create_project( + principal, + CreateProject { + workspace_id: mapping::id(&request.workspace_id, "workspace_id")?, + key: request.key, + name: request.name, + slug: request.slug, + description: request.description, + kind, + }, + ) + .await + .map(mapping::project) + .map_err(mapping::error) +} + +pub(crate) async fn list_projects( + app: &Collaboration, + request: ListProjectsRequest, +) -> Result { + let principal = app + .authenticate(&request.session_token) + .await + .map_err(mapping::error)?; + let projects = app + .list_projects( + principal, + mapping::id(&request.workspace_id, "workspace_id")?, + ) + .await + .map_err(mapping::error)? + .into_iter() + .map(mapping::project) + .collect(); + Ok(ListProjectsResponse { projects }) +} + +pub(crate) async fn put_document( + app: &Collaboration, + request: PutDocumentRequest, +) -> Result { + let principal = app + .authenticate(&request.session_token) + .await + .map_err(mapping::error)?; + app.write_document( + principal, + CreateDocument { + workspace_id: mapping::id(&request.workspace_id, "workspace_id")?, + project_id: mapping::optional_id(&request.project_id, "project_id")?, + path: request.path, + content: request.content, + base_revision_id: mapping::optional_id(&request.base_revision_id, "base_revision_id")?, + }, + ) + .await + .map(mapping::document) + .map_err(mapping::error) +} + +pub(crate) async fn list_documents( + app: &Collaboration, + request: ListDocumentsRequest, +) -> Result { + let principal = app + .authenticate(&request.session_token) + .await + .map_err(mapping::error)?; + let documents = app + .list_documents( + principal, + mapping::id(&request.workspace_id, "workspace_id")?, + mapping::optional_id(&request.project_id, "project_id")?, + ) + .await + .map_err(mapping::error)? + .into_iter() + .map(mapping::document) + .collect(); + Ok(ListDocumentsResponse { documents }) +} + +pub(crate) async fn create_issue( + app: &Collaboration, + request: CreateIssueRequest, +) -> Result { + let principal = app + .authenticate(&request.session_token) + .await + .map_err(mapping::error)?; + let priority = match IssuePriority::try_from(request.priority) { + Ok(IssuePriority::None | IssuePriority::Unspecified) => model::IssuePriority::None, + Ok(IssuePriority::Low) => model::IssuePriority::Low, + Ok(IssuePriority::Normal) => model::IssuePriority::Normal, + Ok(IssuePriority::High) => model::IssuePriority::High, + Ok(IssuePriority::Urgent) => model::IssuePriority::Urgent, + Err(_) => return Err(Status::invalid_argument("priority is invalid")), + }; + app.create_issue( + principal, + CreateIssue { + project_id: mapping::id(&request.project_id, "project_id")?, + title: request.title, + description: request.description, + priority, + }, + ) + .await + .map(mapping::issue) + .map_err(mapping::error) +} + +pub(crate) async fn list_issues( + app: &Collaboration, + request: ListIssuesRequest, +) -> Result { + let principal = app + .authenticate(&request.session_token) + .await + .map_err(mapping::error)?; + let issues = app + .list_issues(principal, mapping::id(&request.project_id, "project_id")?) + .await + .map_err(mapping::error)? + .into_iter() + .map(mapping::issue) + .collect(); + Ok(ListIssuesResponse { issues }) +} + +pub(crate) async fn create_version( + app: &Collaboration, + request: CreateVersionRequest, +) -> Result { + let principal = app + .authenticate(&request.session_token) + .await + .map_err(mapping::error)?; + app.create_version( + principal, + CreateVersion { + project_id: mapping::id(&request.project_id, "project_id")?, + name: request.name, + slug: request.slug, + }, + ) + .await + .map(mapping::version) + .map_err(mapping::error) +} + +pub(crate) async fn list_versions( + app: &Collaboration, + request: ListVersionsRequest, +) -> Result { + let principal = app + .authenticate(&request.session_token) + .await + .map_err(mapping::error)?; + let versions = app + .list_versions(principal, mapping::id(&request.project_id, "project_id")?) + .await + .map_err(mapping::error)? + .into_iter() + .map(mapping::version) + .collect(); + Ok(ListVersionsResponse { versions }) +} diff --git a/crates/collab-api-grpc/src/operations/mod.rs b/crates/collab-api-grpc/src/operations/mod.rs --- /dev/null +++ b/crates/collab-api-grpc/src/operations/mod.rs @@ -1,0 +1,11 @@ +mod content; +mod workspaces; + +pub(crate) use content::{ + create_issue, create_project, create_version, list_documents, list_issues, list_projects, + list_versions, put_document, +}; +pub(crate) use workspaces::{ + attach_repository, configure_environment, create_workspace, get_checkout, get_workspace, + list_environments, list_workspaces, +}; diff --git a/crates/collab-api-grpc/src/operations/workspaces.rs b/crates/collab-api-grpc/src/operations/workspaces.rs --- /dev/null +++ b/crates/collab-api-grpc/src/operations/workspaces.rs @@ -1,0 +1,188 @@ +use tonic::Status; + +use crate::mapping; +use crate::proto::*; +use syncode_collab_application::{Collaboration, ConfigureEnvironment, CreateWorkspace}; +use syncode_collab_model as model; + +pub(crate) async fn create_workspace( + app: &Collaboration, + request: CreateWorkspaceRequest, +) -> Result { + let principal = app + .authenticate(&request.session_token) + .await + .map_err(mapping::error)?; + let owner_kind = match OwnerKind::try_from(request.owner_kind) { + Ok(OwnerKind::User) => model::OwnerKind::User, + Ok(OwnerKind::Organization) => model::OwnerKind::Organization, + Ok(OwnerKind::Unspecified) | Err(_) => { + return Err(Status::invalid_argument("owner_kind is required")); + } + }; + let owner_id = if request.owner_id.is_empty() && owner_kind == model::OwnerKind::User { + principal.id + } else { + mapping::id(&request.owner_id, "owner_id")? + }; + let workspace = app + .create_workspace( + principal, + CreateWorkspace { + owner_kind, + owner_id, + name: request.name, + slug: request.slug, + default_environment: request.default_environment, + default_source_branch: request.default_source_branch, + }, + ) + .await + .map_err(mapping::error)?; + Ok(mapping::workspace(workspace)) +} + +pub(crate) async fn list_workspaces( + app: &Collaboration, + request: ListWorkspacesRequest, +) -> Result { + let principal = app + .authenticate(&request.session_token) + .await + .map_err(mapping::error)?; + let workspaces = app + .list_workspaces(principal) + .await + .map_err(mapping::error)? + .into_iter() + .map(mapping::workspace) + .collect(); + Ok(ListWorkspacesResponse { workspaces }) +} + +pub(crate) async fn get_workspace( + app: &Collaboration, + request: GetWorkspaceRequest, +) -> Result { + let principal = app + .authenticate(&request.session_token) + .await + .map_err(mapping::error)?; + app.workspace( + principal, + mapping::id(&request.workspace_id, "workspace_id")?, + ) + .await + .map(mapping::workspace) + .map_err(mapping::error) +} + +pub(crate) async fn attach_repository( + app: &Collaboration, + request: AttachRepositoryRequest, +) -> Result { + let principal = app + .authenticate(&request.session_token) + .await + .map_err(mapping::error)?; + let role = match RepositoryRole::try_from(request.role) { + Ok(RepositoryRole::Source) => model::RepositoryRole::Source, + Ok(RepositoryRole::Infra) => model::RepositoryRole::Infra, + Ok(RepositoryRole::Unspecified) | Err(_) => { + return Err(Status::invalid_argument("role is required")); + } + }; + app.attach_repository( + principal, + mapping::id(&request.workspace_id, "workspace_id")?, + &request.owner, + &request.name, + role, + &request.branch, + ) + .await + .map_err(mapping::error)?; + Ok(OperationResponse {}) +} + +pub(crate) async fn get_checkout( + app: &Collaboration, + request: GetCheckoutRequest, +) -> Result { + let principal = app + .authenticate(&request.session_token) + .await + .map_err(mapping::error)?; + let environment = (!request.environment.is_empty()).then_some(request.environment.as_str()); + let value = app + .checkout( + principal, + mapping::id(&request.workspace_id, "workspace_id")?, + environment, + request.partial, + ) + .await + .map_err(mapping::error)?; + Ok(Checkout { + workspace: Some(mapping::workspace(value.workspace)), + environment: value.environment, + repositories: value + .repositories + .into_iter() + .map(|repository| CheckoutRepository { + repository_id: repository.repository_id.to_string(), + owner: repository.owner, + name: repository.name, + role: match repository.role { + model::RepositoryRole::Source => RepositoryRole::Source.into(), + model::RepositoryRole::Infra => RepositoryRole::Infra.into(), + }, + branch: repository.branch, + }) + .collect(), + omitted_repositories: value.omitted_repositories, + }) +} + +pub(crate) async fn configure_environment( + app: &Collaboration, + request: ConfigureEnvironmentRequest, +) -> Result { + let principal = app + .authenticate(&request.session_token) + .await + .map_err(mapping::error)?; + app.configure_environment( + principal, + ConfigureEnvironment { + workspace_id: mapping::id(&request.workspace_id, "workspace_id")?, + name: request.name, + default_source_branch: request.default_source_branch, + infra_branch: (!request.infra_branch.is_empty()).then_some(request.infra_branch), + }, + ) + .await + .map(mapping::environment) + .map_err(mapping::error) +} + +pub(crate) async fn list_environments( + app: &Collaboration, + request: ListEnvironmentsRequest, +) -> Result { + let principal = app + .authenticate(&request.session_token) + .await + .map_err(mapping::error)?; + let environments = app + .list_environments( + principal, + mapping::id(&request.workspace_id, "workspace_id")?, + ) + .await + .map_err(mapping::error)? + .into_iter() + .map(mapping::environment) + .collect(); + Ok(ListEnvironmentsResponse { environments }) +} diff --git a/crates/collab-application/src/validation.rs b/crates/collab-application/src/validation.rs --- /dev/null +++ b/crates/collab-application/src/validation.rs @@ -1,0 +1,82 @@ +use syncode_collab_model::{Environment, RepositoryRole}; +use uuid::Uuid; + +use crate::CollaborationError; + +pub(crate) fn branch_for( + environment: &Environment, + repository_id: Uuid, + role: RepositoryRole, +) -> Result { + if let Some((_, branch)) = environment + .branch_overrides + .iter() + .find(|(id, _)| *id == repository_id) + { + return Ok(branch.clone()); + } + match role { + RepositoryRole::Source => Ok(environment.default_source_branch.clone()), + RepositoryRole::Infra => environment.infra_branch.clone().ok_or_else(|| { + CollaborationError::Invalid("infra branch is not configured".to_owned()) + }), + } +} + +pub(crate) fn validate_name(value: &str, field: &str) -> Result<(), CollaborationError> { + if value.trim().is_empty() || value.len() > 200 { + return Err(CollaborationError::Invalid(format!( + "{field} must contain 1 to 200 characters" + ))); + } + Ok(()) +} + +pub(crate) fn validate_slug(value: &str) -> Result<(), CollaborationError> { + if value.is_empty() + || value.len() > 80 + || !value + .bytes() + .all(|byte| byte.is_ascii_lowercase() || byte.is_ascii_digit() || byte == b'-') + { + return Err(CollaborationError::Invalid( + "slug must contain only lowercase letters, digits, and hyphens".to_owned(), + )); + } + Ok(()) +} + +pub(crate) fn validate_project_key(value: &str) -> Result<(), CollaborationError> { + if value.len() < 2 + || value.len() > 10 + || !value + .bytes() + .all(|byte| byte.is_ascii_uppercase() || byte.is_ascii_digit()) + { + return Err(CollaborationError::Invalid( + "project key must contain 2 to 10 uppercase letters or digits".to_owned(), + )); + } + Ok(()) +} + +pub(crate) fn validate_branch(value: &str) -> Result<(), CollaborationError> { + validate_name(value, "branch")?; + if value.starts_with('-') || value.contains("..") || value.contains([' ', '~', '^', ':']) { + return Err(CollaborationError::Invalid("branch is invalid".to_owned())); + } + Ok(()) +} + +pub(crate) fn validate_document_path(value: &str) -> Result<(), CollaborationError> { + if value.is_empty() + || value.starts_with('/') + || value.split('/').any(|part| part.is_empty() || part == "..") + || !value.ends_with(".md") + { + return Err(CollaborationError::Invalid( + "document path must be a relative Markdown path".to_owned(), + )); + } + Ok(()) +} diff --git a/crates/collab-application/src/working_types.rs b/crates/collab-application/src/working_types.rs --- /dev/null +++ b/crates/collab-application/src/working_types.rs @@ -1,0 +1,87 @@ +use syncode_collab_model::{IssuePriority, OwnerKind, ProjectKind}; +use uuid::Uuid; + +use crate::ports::{AccessError, StoreError}; + +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub enum PrincipalKind { + User, + PlatformAgent, + LocalAgent, + AccessToken, +} + +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub struct Principal { + pub id: Uuid, + pub kind: PrincipalKind, +} + +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct RepositoryCoordinates { + pub id: Uuid, + pub owner: String, + pub name: String, +} + +#[derive(Clone, Debug)] +pub struct CreateWorkspace { + pub owner_kind: OwnerKind, + pub owner_id: Uuid, + pub name: String, + pub slug: String, + pub default_environment: String, + pub default_source_branch: String, +} + +#[derive(Clone, Debug)] +pub struct CreateProject { + pub workspace_id: Uuid, + pub key: String, + pub name: String, + pub slug: String, + pub description: String, + pub kind: ProjectKind, +} + +#[derive(Clone, Debug)] +pub struct CreateDocument { + pub workspace_id: Uuid, + pub project_id: Option, + pub path: String, + pub content: String, + pub base_revision_id: Option, +} + +#[derive(Clone, Debug)] +pub struct CreateIssue { + pub project_id: Uuid, + pub title: String, + pub description: String, + pub priority: IssuePriority, +} + +#[derive(Clone, Debug)] +pub struct CreateVersion { + pub project_id: Uuid, + pub name: String, + pub slug: String, +} + +#[derive(Clone, Debug)] +pub struct ConfigureEnvironment { + pub workspace_id: Uuid, + pub name: String, + pub default_source_branch: String, + pub infra_branch: Option, +} + +#[derive(Debug, thiserror::Error)] +pub enum CollaborationError { + #[error(transparent)] + Access(#[from] AccessError), + #[error(transparent)] + Store(#[from] StoreError), + #[error("invalid collaboration input: {0}")] + Invalid(String), +} diff --git a/crates/collab-application/src/working_versions.rs b/crates/collab-application/src/working_versions.rs --- /dev/null +++ b/crates/collab-application/src/working_versions.rs @@ -1,0 +1,25 @@ +use syncode_collab_model::Version; +use uuid::Uuid; + +use crate::validation::{validate_name, validate_slug}; +use crate::{Collaboration, CollaborationError, CreateVersion, Principal}; + +impl Collaboration { + pub async fn create_version( + &self, + principal: Principal, + command: CreateVersion, + ) -> Result { + validate_name(&command.name, "version name")?; + validate_slug(&command.slug)?; + Ok(self.store.create_version(principal.id, &command).await?) + } + + pub async fn list_versions( + &self, + principal: Principal, + project_id: Uuid, + ) -> Result, CollaborationError> { + Ok(self.store.list_versions(principal.id, project_id).await?) + } +} diff --git a/crates/collab-application/src/working_workspace.rs b/crates/collab-application/src/working_workspace.rs --- /dev/null +++ b/crates/collab-application/src/working_workspace.rs @@ -1,0 +1,239 @@ +use std::sync::Arc; + +use syncode_collab_model::{ + Checkout, CheckoutRepository, Document, DocumentRevision, Issue, OwnerKind, Project, + RepositoryRole, Workspace, +}; +use uuid::Uuid; + +use crate::ports::{AccessError, CollaborationStore, IdentityAccess}; +use crate::validation::{ + branch_for, validate_branch, validate_document_path, validate_name, validate_project_key, + validate_slug, +}; +use crate::{ + CollaborationError, ConfigureEnvironment, CreateDocument, CreateIssue, CreateProject, + CreateWorkspace, Principal, +}; + +#[derive(Clone)] +pub struct Collaboration { + pub(crate) store: Arc, + identity: Arc, +} + +impl Collaboration { + pub fn new(store: Arc, identity: Arc) -> Self { + Self { store, identity } + } + + pub async fn authenticate(&self, token: &str) -> Result { + if token.is_empty() { + return Err(CollaborationError::Access(AccessError::Unauthenticated)); + } + Ok(self.identity.authenticate(token).await?) + } + + pub async fn create_workspace( + &self, + principal: Principal, + command: CreateWorkspace, + ) -> Result { + validate_name(&command.name, "workspace name")?; + validate_slug(&command.slug)?; + validate_name(&command.default_environment, "environment name")?; + validate_branch(&command.default_source_branch)?; + match command.owner_kind { + OwnerKind::User if command.owner_id != principal.id => { + return Err(CollaborationError::Access(AccessError::Denied)); + } + OwnerKind::Organization => { + self.identity + .require_organization_admin(principal, command.owner_id) + .await?; + } + OwnerKind::User => {} + } + Ok(self.store.create_workspace(principal.id, &command).await?) + } + + pub async fn list_workspaces( + &self, + principal: Principal, + ) -> Result, CollaborationError> { + Ok(self.store.list_workspaces(principal.id).await?) + } + + pub async fn workspace( + &self, + principal: Principal, + workspace_id: Uuid, + ) -> Result { + Ok(self.store.workspace(principal.id, workspace_id).await?) + } + + pub async fn attach_repository( + &self, + principal: Principal, + workspace_id: Uuid, + owner: &str, + name: &str, + role: RepositoryRole, + branch: &str, + ) -> Result<(), CollaborationError> { + validate_branch(branch)?; + self.store.workspace(principal.id, workspace_id).await?; + let repository = self + .identity + .resolve_readable_repository(principal, owner, name) + .await?; + for stored in self.store.repositories(principal.id, workspace_id).await? { + let existing = self + .identity + .repository_coordinates(principal, stored.repository_id) + .await?; + if role == RepositoryRole::Source + && stored.role == RepositoryRole::Source + && existing.name == repository.name + && existing.id != repository.id + { + return Err(CollaborationError::Invalid(format!( + "workspace already contains a source path named {}", + repository.name + ))); + } + } + self.store + .attach_repository(principal.id, workspace_id, repository.id, role, branch) + .await?; + Ok(()) + } + + pub async fn checkout( + &self, + principal: Principal, + workspace_id: Uuid, + environment: Option<&str>, + partial: bool, + ) -> Result { + let workspace = self.store.workspace(principal.id, workspace_id).await?; + let environment_name = environment.unwrap_or(&workspace.default_environment); + let environment = self + .store + .environment(principal.id, workspace_id, environment_name) + .await?; + let repositories = self.store.repositories(principal.id, workspace_id).await?; + let mut checkout = Vec::new(); + let mut omitted = 0_u32; + for stored in repositories { + match self + .identity + .repository_coordinates(principal, stored.repository_id) + .await + { + Ok(repository) => checkout.push(CheckoutRepository { + repository_id: repository.id, + owner: repository.owner, + name: repository.name, + role: stored.role, + branch: branch_for(&environment, stored.repository_id, stored.role)?, + }), + Err(AccessError::Denied | AccessError::NotFound) if partial => omitted += 1, + Err(error) => return Err(error.into()), + } + } + Ok(Checkout { + workspace, + environment: environment.name, + repositories: checkout, + omitted_repositories: omitted, + }) + } + + pub async fn configure_environment( + &self, + principal: Principal, + command: ConfigureEnvironment, + ) -> Result { + validate_name(&command.name, "environment name")?; + validate_branch(&command.default_source_branch)?; + if let Some(branch) = &command.infra_branch { + validate_branch(branch)?; + } + Ok(self + .store + .configure_environment(principal.id, &command) + .await?) + } + + pub async fn list_environments( + &self, + principal: Principal, + workspace_id: Uuid, + ) -> Result, CollaborationError> { + Ok(self + .store + .list_environments(principal.id, workspace_id) + .await?) + } + + pub async fn create_project( + &self, + principal: Principal, + command: CreateProject, + ) -> Result { + self.store + .workspace(principal.id, command.workspace_id) + .await?; + validate_project_key(&command.key)?; + validate_name(&command.name, "project name")?; + validate_slug(&command.slug)?; + Ok(self.store.create_project(principal.id, &command).await?) + } + + pub async fn list_projects( + &self, + principal: Principal, + workspace_id: Uuid, + ) -> Result, CollaborationError> { + Ok(self.store.list_projects(principal.id, workspace_id).await?) + } + + pub async fn write_document( + &self, + principal: Principal, + command: CreateDocument, + ) -> Result<(Document, DocumentRevision), CollaborationError> { + validate_document_path(&command.path)?; + Ok(self.store.write_document(principal.id, &command).await?) + } + + pub async fn list_documents( + &self, + principal: Principal, + workspace_id: Uuid, + project_id: Option, + ) -> Result, CollaborationError> { + Ok(self + .store + .list_documents(principal.id, workspace_id, project_id) + .await?) + } + + pub async fn create_issue( + &self, + principal: Principal, + command: CreateIssue, + ) -> Result { + validate_name(&command.title, "issue title")?; + Ok(self.store.create_issue(principal.id, &command).await?) + } + + pub async fn list_issues( + &self, + principal: Principal, + project_id: Uuid, + ) -> Result, CollaborationError> { + Ok(self.store.list_issues(principal.id, project_id).await?) + } +} diff --git a/crates/collab-model/src/document.rs b/crates/collab-model/src/document.rs --- /dev/null +++ b/crates/collab-model/src/document.rs @@ -1,0 +1,29 @@ +use chrono::{DateTime, Utc}; +use uuid::Uuid; + +#[derive(Clone, Copy, Debug, Eq, PartialEq, serde::Deserialize, serde::Serialize)] +#[serde(rename_all = "snake_case")] +pub enum DocumentScope { + Workspace, + Project, +} + +#[derive(Clone, Debug, Eq, PartialEq, serde::Deserialize, serde::Serialize)] +pub struct Document { + pub id: Uuid, + pub workspace_id: Uuid, + pub project_id: Option, + pub path: String, + pub current_revision_id: Uuid, + pub updated_at: DateTime, +} + +#[derive(Clone, Debug, Eq, PartialEq, serde::Deserialize, serde::Serialize)] +pub struct DocumentRevision { + pub id: Uuid, + pub document_id: Uuid, + pub previous_revision_id: Option, + pub author_principal_id: Uuid, + pub content: String, + pub created_at: DateTime, +} diff --git a/crates/collab-model/src/issue.rs b/crates/collab-model/src/issue.rs --- /dev/null +++ b/crates/collab-model/src/issue.rs @@ -1,0 +1,30 @@ +use chrono::{DateTime, Utc}; +use uuid::Uuid; + +#[derive(Clone, Copy, Debug, Eq, PartialEq, serde::Deserialize, serde::Serialize)] +#[serde(rename_all = "snake_case")] +pub enum IssuePriority { + None, + Low, + Normal, + High, + Urgent, +} + +#[derive(Clone, Debug, Eq, PartialEq, serde::Deserialize, serde::Serialize)] +pub struct Issue { + pub id: Uuid, + pub project_id: Uuid, + pub number: i64, + pub title: String, + pub description: String, + pub type_id: Uuid, + pub status_id: Uuid, + pub priority: IssuePriority, + pub assignee_principal_id: Option, + pub parent_issue_id: Option, + pub author_principal_id: Uuid, + pub version: i64, + pub created_at: DateTime, + pub updated_at: DateTime, +} diff --git a/crates/collab-model/src/project.rs b/crates/collab-model/src/project.rs --- /dev/null +++ b/crates/collab-model/src/project.rs @@ -1,0 +1,61 @@ +use chrono::{DateTime, Utc}; +use uuid::Uuid; + +#[derive(Clone, Copy, Debug, Eq, PartialEq, serde::Deserialize, serde::Serialize)] +#[serde(rename_all = "snake_case")] +pub enum ProjectKind { + Continuous, + Finite, +} + +#[derive(Clone, Copy, Debug, Eq, PartialEq, serde::Deserialize, serde::Serialize)] +#[serde(rename_all = "snake_case")] +pub enum ProjectLifecycle { + Planned, + Active, + Completed, + Canceled, +} + +#[derive(Clone, Copy, Debug, Eq, PartialEq, serde::Deserialize, serde::Serialize)] +#[serde(rename_all = "snake_case")] +pub enum IssueStatusCategory { + Backlog, + Unstarted, + Started, + Completed, + Canceled, +} + +#[derive(Clone, Debug, Eq, PartialEq, serde::Deserialize, serde::Serialize)] +pub struct Project { + pub id: Uuid, + pub workspace_id: Uuid, + pub key: String, + pub name: String, + pub slug: String, + pub description: String, + pub kind: ProjectKind, + pub lifecycle: ProjectLifecycle, + pub default_status_id: Uuid, + pub completion_status_id: Uuid, + pub version: i64, + pub created_at: DateTime, + pub updated_at: DateTime, +} + +#[derive(Clone, Debug, Eq, PartialEq, serde::Deserialize, serde::Serialize)] +pub struct IssueStatus { + pub id: Uuid, + pub project_id: Uuid, + pub name: String, + pub category: IssueStatusCategory, + pub position: i32, +} + +#[derive(Clone, Debug, Eq, PartialEq, serde::Deserialize, serde::Serialize)] +pub struct IssueType { + pub id: Uuid, + pub project_id: Uuid, + pub name: String, +} diff --git a/crates/collab-model/src/version.rs b/crates/collab-model/src/version.rs --- /dev/null +++ b/crates/collab-model/src/version.rs @@ -1,0 +1,20 @@ +use chrono::{DateTime, Utc}; +use uuid::Uuid; + +#[derive(Clone, Copy, Debug, Eq, PartialEq, serde::Deserialize, serde::Serialize)] +#[serde(rename_all = "snake_case")] +pub enum VersionState { + Planned, + Released, +} + +#[derive(Clone, Debug, Eq, PartialEq, serde::Deserialize, serde::Serialize)] +pub struct Version { + pub id: Uuid, + pub project_id: Uuid, + pub name: String, + pub slug: String, + pub state: VersionState, + pub created_at: DateTime, + pub released_at: Option>, +} diff --git a/crates/collab-model/src/workspace.rs b/crates/collab-model/src/workspace.rs --- /dev/null +++ b/crates/collab-model/src/workspace.rs @@ -1,0 +1,64 @@ +use chrono::{DateTime, Utc}; +use uuid::Uuid; + +#[derive(Clone, Copy, Debug, Eq, PartialEq, serde::Deserialize, serde::Serialize)] +#[serde(rename_all = "snake_case")] +pub enum OwnerKind { + User, + Organization, +} + +#[derive(Clone, Copy, Debug, Eq, PartialEq, serde::Deserialize, serde::Serialize)] +#[serde(rename_all = "snake_case")] +pub enum RepositoryRole { + Source, + Infra, +} + +#[derive(Clone, Debug, Eq, PartialEq, serde::Deserialize, serde::Serialize)] +pub struct Workspace { + pub id: Uuid, + pub owner_kind: OwnerKind, + pub owner_id: Uuid, + pub name: String, + pub slug: String, + pub default_environment: String, + pub version: i64, + pub created_at: DateTime, + pub updated_at: DateTime, +} + +#[derive(Clone, Debug, Eq, PartialEq, serde::Deserialize, serde::Serialize)] +pub struct WorkspaceRepository { + pub repository_id: Uuid, + pub owner: String, + pub name: String, + pub role: RepositoryRole, +} + +#[derive(Clone, Debug, Eq, PartialEq, serde::Deserialize, serde::Serialize)] +pub struct Environment { + pub id: Uuid, + pub workspace_id: Uuid, + pub name: String, + pub default_source_branch: String, + pub infra_branch: Option, + pub branch_overrides: Vec<(Uuid, String)>, +} + +#[derive(Clone, Debug, Eq, PartialEq, serde::Deserialize, serde::Serialize)] +pub struct CheckoutRepository { + pub repository_id: Uuid, + pub owner: String, + pub name: String, + pub role: RepositoryRole, + pub branch: String, +} + +#[derive(Clone, Debug, Eq, PartialEq, serde::Deserialize, serde::Serialize)] +pub struct Checkout { + pub workspace: Workspace, + pub environment: String, + pub repositories: Vec, + pub omitted_repositories: u32, +} diff --git a/crates/collab-store/migrations/0003_working_workspace.sql b/crates/collab-store/migrations/0003_working_workspace.sql --- /dev/null +++ b/crates/collab-store/migrations/0003_working_workspace.sql @@ -1,0 +1,144 @@ +CREATE TABLE workspace ( + id uuid PRIMARY KEY, + owner_kind text NOT NULL CHECK (owner_kind IN ('user', 'organization')), + owner_id uuid NOT NULL, + name text NOT NULL CHECK (length(name) BETWEEN 1 AND 200), + slug text NOT NULL CHECK (slug ~ '^[a-z0-9][a-z0-9-]{0,79}$'), + default_environment text NOT NULL, + version bigint NOT NULL DEFAULT 1, + created_at timestamptz NOT NULL DEFAULT now(), + updated_at timestamptz NOT NULL DEFAULT now(), + UNIQUE (owner_kind, owner_id, slug) +); + +CREATE TABLE workspace_access ( + workspace_id uuid NOT NULL REFERENCES workspace(id) ON DELETE CASCADE, + principal_id uuid NOT NULL, + capabilities text[] NOT NULL, + PRIMARY KEY (workspace_id, principal_id) +); + +CREATE TABLE workspace_repository ( + workspace_id uuid NOT NULL REFERENCES workspace(id) ON DELETE CASCADE, + repository_id uuid NOT NULL, + role text NOT NULL CHECK (role IN ('source', 'infra')), + PRIMARY KEY (workspace_id, repository_id) +); + +CREATE UNIQUE INDEX workspace_one_infra + ON workspace_repository (workspace_id) WHERE role = 'infra'; + +CREATE TABLE workspace_environment ( + id uuid PRIMARY KEY, + workspace_id uuid NOT NULL REFERENCES workspace(id) ON DELETE CASCADE, + name text NOT NULL, + default_source_branch text NOT NULL, + infra_branch text, + UNIQUE (workspace_id, name) +); + +CREATE TABLE environment_branch_override ( + environment_id uuid NOT NULL REFERENCES workspace_environment(id) ON DELETE CASCADE, + repository_id uuid NOT NULL, + branch text NOT NULL, + PRIMARY KEY (environment_id, repository_id) +); + +CREATE TABLE project ( + id uuid PRIMARY KEY, + workspace_id uuid NOT NULL REFERENCES workspace(id) ON DELETE CASCADE, + key text NOT NULL CHECK (key ~ '^[A-Z0-9]{2,10}$'), + name text NOT NULL CHECK (length(name) BETWEEN 1 AND 200), + slug text NOT NULL CHECK (slug ~ '^[a-z0-9][a-z0-9-]{0,79}$'), + description text NOT NULL DEFAULT '', + kind text NOT NULL CHECK (kind IN ('continuous', 'finite')), + lifecycle text NOT NULL DEFAULT 'active' + CHECK (lifecycle IN ('planned', 'active', 'completed', 'canceled')), + default_status_id uuid NOT NULL, + completion_status_id uuid NOT NULL, + next_issue_number bigint NOT NULL DEFAULT 1, + version bigint NOT NULL DEFAULT 1, + created_at timestamptz NOT NULL DEFAULT now(), + updated_at timestamptz NOT NULL DEFAULT now(), + UNIQUE (workspace_id, key), + UNIQUE (workspace_id, slug) +); + +CREATE TABLE issue_status ( + id uuid PRIMARY KEY, + project_id uuid NOT NULL REFERENCES project(id) ON DELETE CASCADE, + name text NOT NULL, + category text NOT NULL CHECK ( + category IN ('backlog', 'unstarted', 'started', 'completed', 'canceled') + ), + position integer NOT NULL, + UNIQUE (project_id, name), + UNIQUE (project_id, position) +); + +CREATE TABLE issue_type ( + id uuid PRIMARY KEY, + project_id uuid NOT NULL REFERENCES project(id) ON DELETE CASCADE, + name text NOT NULL, + UNIQUE (project_id, name) +); + +ALTER TABLE project + ADD CONSTRAINT project_default_status_fk + FOREIGN KEY (default_status_id) REFERENCES issue_status(id) DEFERRABLE INITIALLY DEFERRED, + ADD CONSTRAINT project_completion_status_fk + FOREIGN KEY (completion_status_id) REFERENCES issue_status(id) DEFERRABLE INITIALLY DEFERRED; + +CREATE TABLE document ( + id uuid PRIMARY KEY, + workspace_id uuid NOT NULL REFERENCES workspace(id) ON DELETE CASCADE, + project_id uuid REFERENCES project(id) ON DELETE CASCADE, + path text NOT NULL, + current_revision_id uuid NOT NULL, + updated_at timestamptz NOT NULL DEFAULT now() +); + +CREATE UNIQUE INDEX workspace_document_path + ON document (workspace_id, path) WHERE project_id IS NULL; + +CREATE UNIQUE INDEX project_document_path + ON document (project_id, path) WHERE project_id IS NOT NULL; + +CREATE TABLE document_revision ( + id uuid PRIMARY KEY, + document_id uuid NOT NULL REFERENCES document(id) ON DELETE CASCADE, + previous_revision_id uuid REFERENCES document_revision(id), + author_principal_id uuid NOT NULL, + content text NOT NULL, + created_at timestamptz NOT NULL DEFAULT now() +); + +ALTER TABLE document + ADD CONSTRAINT document_current_revision_fk + FOREIGN KEY (current_revision_id) REFERENCES document_revision(id) + DEFERRABLE INITIALLY DEFERRED; + +CREATE TABLE issue ( + id uuid PRIMARY KEY, + project_id uuid NOT NULL REFERENCES project(id) ON DELETE CASCADE, + number bigint NOT NULL, + title text NOT NULL CHECK (length(title) BETWEEN 1 AND 200), + description text NOT NULL DEFAULT '', + type_id uuid NOT NULL REFERENCES issue_type(id), + status_id uuid NOT NULL REFERENCES issue_status(id), + priority text NOT NULL DEFAULT 'none' + CHECK (priority IN ('none', 'low', 'normal', 'high', 'urgent')), + assignee_principal_id uuid, + parent_issue_id uuid REFERENCES issue(id), + author_principal_id uuid NOT NULL, + version bigint NOT NULL DEFAULT 1, + created_at timestamptz NOT NULL DEFAULT now(), + updated_at timestamptz NOT NULL DEFAULT now(), + UNIQUE (project_id, number), + CHECK (parent_issue_id IS NULL OR parent_issue_id <> id) +); + +CREATE INDEX workspace_access_principal ON workspace_access (principal_id, workspace_id); +CREATE INDEX project_workspace ON project (workspace_id); +CREATE INDEX issue_project ON issue (project_id, number); +CREATE INDEX document_workspace ON document (workspace_id, project_id, path); diff --git a/crates/collab-store/migrations/0004_versions.sql b/crates/collab-store/migrations/0004_versions.sql --- /dev/null +++ b/crates/collab-store/migrations/0004_versions.sql @@ -1,0 +1,13 @@ +CREATE TABLE project_version ( + id uuid PRIMARY KEY, + project_id uuid NOT NULL REFERENCES project(id) ON DELETE CASCADE, + name text NOT NULL CHECK (length(name) BETWEEN 1 AND 200), + slug text NOT NULL CHECK (slug ~ '^[a-z0-9][a-z0-9-]{0,79}$'), + state text NOT NULL DEFAULT 'planned' CHECK (state IN ('planned', 'released')), + created_at timestamptz NOT NULL DEFAULT now(), + released_at timestamptz, + UNIQUE (project_id, slug), + CHECK ((state = 'released') = (released_at IS NOT NULL)) +); + +CREATE INDEX project_version_project ON project_version (project_id, created_at, id); diff --git a/crates/collab-store/src/working.rs b/crates/collab-store/src/working.rs --- /dev/null +++ b/crates/collab-store/src/working.rs @@ -1,0 +1,187 @@ +use async_trait::async_trait; +use sqlx::PgPool; +use syncode_collab_application::StoreError; +use syncode_collab_application::ports::{CollaborationStore, StoredRepository}; +use syncode_collab_application::{CreateDocument, CreateIssue, CreateProject, CreateWorkspace}; +use syncode_collab_model::{ + Document, DocumentRevision, Environment, Issue, Project, RepositoryRole, Version, Workspace, +}; +use uuid::Uuid; + +pub struct PostgresCollaboration { + pub(crate) pool: PgPool, +} + +impl PostgresCollaboration { + pub fn new(pool: PgPool) -> Self { + Self { pool } + } +} + +#[async_trait] +impl CollaborationStore for PostgresCollaboration { + async fn create_workspace( + &self, + principal_id: Uuid, + command: &CreateWorkspace, + ) -> Result { + crate::working_workspaces::create(&self.pool, principal_id, command).await + } + + async fn list_workspaces(&self, principal_id: Uuid) -> Result, StoreError> { + crate::working_workspaces::list(&self.pool, principal_id).await + } + + async fn workspace( + &self, + principal_id: Uuid, + workspace_id: Uuid, + ) -> Result { + crate::working_workspaces::get(&self.pool, principal_id, workspace_id).await + } + + async fn attach_repository( + &self, + principal_id: Uuid, + workspace_id: Uuid, + repository_id: Uuid, + role: RepositoryRole, + branch: &str, + ) -> Result<(), StoreError> { + crate::working_workspaces::attach_repository( + &self.pool, + principal_id, + workspace_id, + repository_id, + role, + branch, + ) + .await + } + + async fn repositories( + &self, + principal_id: Uuid, + workspace_id: Uuid, + ) -> Result, StoreError> { + crate::working_workspaces::repositories(&self.pool, principal_id, workspace_id).await + } + + async fn environment( + &self, + principal_id: Uuid, + workspace_id: Uuid, + name: &str, + ) -> Result { + crate::working_environments::get(&self.pool, principal_id, workspace_id, name).await + } + + async fn configure_environment( + &self, + principal_id: Uuid, + command: &syncode_collab_application::ConfigureEnvironment, + ) -> Result { + crate::working_environments::configure(&self.pool, principal_id, command).await + } + + async fn list_environments( + &self, + principal_id: Uuid, + workspace_id: Uuid, + ) -> Result, StoreError> { + crate::working_environments::list(&self.pool, principal_id, workspace_id).await + } + + async fn create_project( + &self, + principal_id: Uuid, + command: &CreateProject, + ) -> Result { + crate::working_projects::create(&self.pool, principal_id, command).await + } + + async fn list_projects( + &self, + principal_id: Uuid, + workspace_id: Uuid, + ) -> Result, StoreError> { + crate::working_projects::list(&self.pool, principal_id, workspace_id).await + } + + async fn write_document( + &self, + principal_id: Uuid, + command: &CreateDocument, + ) -> Result<(Document, DocumentRevision), StoreError> { + crate::working_documents::write(&self.pool, principal_id, command).await + } + + async fn list_documents( + &self, + principal_id: Uuid, + workspace_id: Uuid, + project_id: Option, + ) -> Result, StoreError> { + crate::working_documents::list(&self.pool, principal_id, workspace_id, project_id).await + } + + async fn create_issue( + &self, + principal_id: Uuid, + command: &CreateIssue, + ) -> Result { + crate::working_issues::create(&self.pool, principal_id, command).await + } + + async fn list_issues( + &self, + principal_id: Uuid, + project_id: Uuid, + ) -> Result, StoreError> { + crate::working_issues::list(&self.pool, principal_id, project_id).await + } + + async fn create_version( + &self, + principal_id: Uuid, + command: &syncode_collab_application::CreateVersion, + ) -> Result { + crate::working_versions::create(&self.pool, principal_id, command).await + } + + async fn list_versions( + &self, + principal_id: Uuid, + project_id: Uuid, + ) -> Result, StoreError> { + crate::working_versions::list(&self.pool, principal_id, project_id).await + } +} + +pub(crate) fn unavailable(error: sqlx::Error) -> StoreError { + StoreError::Unavailable(error.to_string()) +} + +pub(crate) fn rejected(error: sqlx::Error) -> StoreError { + StoreError::Rejected(error.to_string()) +} + +pub(crate) async fn outbox( + transaction: &mut sqlx::Transaction<'_, sqlx::Postgres>, + message_type: &str, + resource_id: Uuid, + payload: serde_json::Value, +) -> Result<(), StoreError> { + sqlx::query( + "INSERT INTO event_outbox (message_id, message_type, resource_id, payload) \ + VALUES ($1, $2, $3, $4)", + ) + .bind(Uuid::new_v4()) + .bind(message_type) + .bind(resource_id) + .bind(payload) + .execute(&mut **transaction) + .await + .map_err(rejected)?; + Ok(()) +} diff --git a/crates/collab-store/src/working_documents.rs b/crates/collab-store/src/working_documents.rs --- /dev/null +++ b/crates/collab-store/src/working_documents.rs @@ -1,0 +1,205 @@ +use sqlx::{FromRow, PgPool}; +use syncode_collab_application::{CreateDocument, StoreError}; +use syncode_collab_model::{Document, DocumentRevision}; +use uuid::Uuid; + +use crate::working::{outbox, rejected, unavailable}; + +#[derive(FromRow)] +struct DocumentRow { + id: Uuid, + workspace_id: Uuid, + project_id: Option, + path: String, + current_revision_id: Uuid, + updated_at: chrono::DateTime, + revision_id: Uuid, + previous_revision_id: Option, + author_principal_id: Uuid, + content: String, + revision_created_at: chrono::DateTime, +} + +impl DocumentRow { + fn split(self) -> (Document, DocumentRevision) { + ( + Document { + id: self.id, + workspace_id: self.workspace_id, + project_id: self.project_id, + path: self.path, + current_revision_id: self.current_revision_id, + updated_at: self.updated_at, + }, + DocumentRevision { + id: self.revision_id, + document_id: self.id, + previous_revision_id: self.previous_revision_id, + author_principal_id: self.author_principal_id, + content: self.content, + created_at: self.revision_created_at, + }, + ) + } +} + +const COLUMNS: &str = "d.id, d.workspace_id, d.project_id, d.path, \ + d.current_revision_id, d.updated_at, r.id AS revision_id, \ + r.previous_revision_id, r.author_principal_id, r.content, \ + r.created_at AS revision_created_at"; + +pub async fn write( + pool: &PgPool, + principal_id: Uuid, + command: &CreateDocument, +) -> Result<(Document, DocumentRevision), StoreError> { + let mut transaction = pool.begin().await.map_err(unavailable)?; + require_scope( + &mut transaction, + principal_id, + command.workspace_id, + command.project_id, + ) + .await?; + let existing: Option<(Uuid, Uuid)> = sqlx::query_as( + "SELECT id, current_revision_id FROM document WHERE workspace_id = $1 \ + AND project_id IS NOT DISTINCT FROM $2 AND path = $3 FOR UPDATE", + ) + .bind(command.workspace_id) + .bind(command.project_id) + .bind(&command.path) + .fetch_optional(&mut *transaction) + .await + .map_err(unavailable)?; + let (document_id, previous_revision_id) = match existing { + Some((document_id, current)) => { + if command.base_revision_id != Some(current) { + return Err(StoreError::Conflict( + "document base revision is stale".to_owned(), + )); + } + (document_id, Some(current)) + } + None if command.base_revision_id.is_some() => { + return Err(StoreError::Conflict( + "new document cannot have a base revision".to_owned(), + )); + } + None => (Uuid::new_v4(), None), + }; + let revision_id = Uuid::new_v4(); + if existing.is_none() { + sqlx::query( + "INSERT INTO document \ + (id, workspace_id, project_id, path, current_revision_id) \ + VALUES ($1, $2, $3, $4, $5)", + ) + .bind(document_id) + .bind(command.workspace_id) + .bind(command.project_id) + .bind(&command.path) + .bind(revision_id) + .execute(&mut *transaction) + .await + .map_err(rejected)?; + } + sqlx::query( + "INSERT INTO document_revision \ + (id, document_id, previous_revision_id, author_principal_id, content) \ + VALUES ($1, $2, $3, $4, $5)", + ) + .bind(revision_id) + .bind(document_id) + .bind(previous_revision_id) + .bind(principal_id) + .bind(&command.content) + .execute(&mut *transaction) + .await + .map_err(rejected)?; + if existing.is_some() { + sqlx::query( + "UPDATE document SET current_revision_id = $2, updated_at = now() WHERE id = $1", + ) + .bind(document_id) + .bind(revision_id) + .execute(&mut *transaction) + .await + .map_err(rejected)?; + } + outbox( + &mut transaction, + "collaboration.document.revised", + document_id, + serde_json::json!({"document_id": document_id, "revision_id": revision_id, "actor_id": principal_id}), + ) + .await?; + transaction.commit().await.map_err(unavailable)?; + get(pool, principal_id, document_id).await +} + +pub async fn list( + pool: &PgPool, + principal_id: Uuid, + workspace_id: Uuid, + project_id: Option, +) -> Result, StoreError> { + let query = format!( + "SELECT {COLUMNS} FROM document d \ + JOIN document_revision r ON r.id = d.current_revision_id \ + JOIN workspace_access a ON a.workspace_id = d.workspace_id \ + WHERE d.workspace_id = $1 AND d.project_id IS NOT DISTINCT FROM $2 \ + AND a.principal_id = $3 AND 'read' = ANY(a.capabilities) ORDER BY d.path" + ); + Ok(sqlx::query_as::<_, DocumentRow>(&query) + .bind(workspace_id) + .bind(project_id) + .bind(principal_id) + .fetch_all(pool) + .await + .map_err(unavailable)? + .into_iter() + .map(DocumentRow::split) + .collect()) +} + +async fn get( + pool: &PgPool, + principal_id: Uuid, + document_id: Uuid, +) -> Result<(Document, DocumentRevision), StoreError> { + let query = format!( + "SELECT {COLUMNS} FROM document d \ + JOIN document_revision r ON r.id = d.current_revision_id \ + JOIN workspace_access a ON a.workspace_id = d.workspace_id \ + WHERE d.id = $1 AND a.principal_id = $2 AND 'read' = ANY(a.capabilities)" + ); + sqlx::query_as::<_, DocumentRow>(&query) + .bind(document_id) + .bind(principal_id) + .fetch_optional(pool) + .await + .map_err(unavailable)? + .map(DocumentRow::split) + .ok_or(StoreError::NotFound) +} + +async fn require_scope( + transaction: &mut sqlx::Transaction<'_, sqlx::Postgres>, + principal_id: Uuid, + workspace_id: Uuid, + project_id: Option, +) -> Result<(), StoreError> { + let allowed: Option<(bool,)> = sqlx::query_as( + "SELECT true FROM workspace_access a WHERE a.workspace_id = $1 \ + AND a.principal_id = $2 AND 'write' = ANY(a.capabilities) \ + AND ($3::uuid IS NULL OR EXISTS (SELECT 1 FROM project p \ + WHERE p.id = $3 AND p.workspace_id = a.workspace_id))", + ) + .bind(workspace_id) + .bind(principal_id) + .bind(project_id) + .fetch_optional(&mut **transaction) + .await + .map_err(unavailable)?; + allowed.map(|_| ()).ok_or(StoreError::NotFound) +} diff --git a/crates/collab-store/src/working_environments.rs b/crates/collab-store/src/working_environments.rs --- /dev/null +++ b/crates/collab-store/src/working_environments.rs @@ -1,0 +1,95 @@ +use sqlx::PgPool; +use syncode_collab_application::{ConfigureEnvironment, StoreError}; +use syncode_collab_model::Environment; +use uuid::Uuid; + +use crate::working::{outbox, rejected, unavailable}; + +pub async fn get( + pool: &PgPool, + principal_id: Uuid, + workspace_id: Uuid, + name: &str, +) -> Result { + crate::working_workspaces::get(pool, principal_id, workspace_id).await?; + let row: Option<(Uuid, String, String, Option)> = sqlx::query_as( + "SELECT id, name, default_source_branch, infra_branch \ + FROM workspace_environment WHERE workspace_id = $1 AND name = $2", + ) + .bind(workspace_id) + .bind(name) + .fetch_optional(pool) + .await + .map_err(unavailable)?; + let (id, name, default_source_branch, infra_branch) = row.ok_or(StoreError::NotFound)?; + let branch_overrides = sqlx::query_as( + "SELECT repository_id, branch FROM environment_branch_override \ + WHERE environment_id = $1 ORDER BY repository_id", + ) + .bind(id) + .fetch_all(pool) + .await + .map_err(unavailable)?; + Ok(Environment { + id, + workspace_id, + name, + default_source_branch, + infra_branch, + branch_overrides, + }) +} + +pub async fn configure( + pool: &PgPool, + principal_id: Uuid, + command: &ConfigureEnvironment, +) -> Result { + let mut transaction = pool.begin().await.map_err(unavailable)?; + crate::working_workspaces::require_write(&mut transaction, principal_id, command.workspace_id) + .await?; + sqlx::query( + "INSERT INTO workspace_environment \ + (id, workspace_id, name, default_source_branch, infra_branch) \ + VALUES ($1,$2,$3,$4,$5) ON CONFLICT (workspace_id, name) DO UPDATE SET \ + default_source_branch = EXCLUDED.default_source_branch, \ + infra_branch = EXCLUDED.infra_branch", + ) + .bind(Uuid::new_v4()) + .bind(command.workspace_id) + .bind(&command.name) + .bind(&command.default_source_branch) + .bind(&command.infra_branch) + .execute(&mut *transaction) + .await + .map_err(rejected)?; + outbox( + &mut transaction, + "collaboration.workspace.environment_configured", + command.workspace_id, + serde_json::json!({"workspace_id": command.workspace_id, "environment": command.name, "actor_id": principal_id}), + ) + .await?; + transaction.commit().await.map_err(unavailable)?; + get(pool, principal_id, command.workspace_id, &command.name).await +} + +pub async fn list( + pool: &PgPool, + principal_id: Uuid, + workspace_id: Uuid, +) -> Result, StoreError> { + crate::working_workspaces::get(pool, principal_id, workspace_id).await?; + let names: Vec<(String,)> = sqlx::query_as( + "SELECT name FROM workspace_environment WHERE workspace_id = $1 ORDER BY name", + ) + .bind(workspace_id) + .fetch_all(pool) + .await + .map_err(unavailable)?; + let mut values = Vec::with_capacity(names.len()); + for (name,) in names { + values.push(get(pool, principal_id, workspace_id, &name).await?); + } + Ok(values) +} diff --git a/crates/collab-store/src/working_issues.rs b/crates/collab-store/src/working_issues.rs --- /dev/null +++ b/crates/collab-store/src/working_issues.rs @@ -1,0 +1,143 @@ +use sqlx::{FromRow, PgPool}; +use syncode_collab_application::{CreateIssue, StoreError}; +use syncode_collab_model::{Issue, IssuePriority}; +use uuid::Uuid; + +use crate::working::{outbox, rejected, unavailable}; + +#[derive(FromRow)] +struct IssueRow { + id: Uuid, + project_id: Uuid, + number: i64, + title: String, + description: String, + type_id: Uuid, + status_id: Uuid, + priority: String, + assignee_principal_id: Option, + parent_issue_id: Option, + author_principal_id: Uuid, + version: i64, + created_at: chrono::DateTime, + updated_at: chrono::DateTime, +} + +impl TryFrom for Issue { + type Error = StoreError; + + fn try_from(row: IssueRow) -> Result { + let priority = match row.priority.as_str() { + "none" => IssuePriority::None, + "low" => IssuePriority::Low, + "normal" => IssuePriority::Normal, + "high" => IssuePriority::High, + "urgent" => IssuePriority::Urgent, + value => { + return Err(StoreError::Rejected(format!( + "unknown issue priority {value}" + ))); + } + }; + Ok(Self { + id: row.id, + project_id: row.project_id, + number: row.number, + title: row.title, + description: row.description, + type_id: row.type_id, + status_id: row.status_id, + priority, + assignee_principal_id: row.assignee_principal_id, + parent_issue_id: row.parent_issue_id, + author_principal_id: row.author_principal_id, + version: row.version, + created_at: row.created_at, + updated_at: row.updated_at, + }) + } +} + +const COLUMNS: &str = "i.id, i.project_id, i.number, i.title, i.description, \ + i.type_id, i.status_id, i.priority, i.assignee_principal_id, i.parent_issue_id, \ + i.author_principal_id, i.version, i.created_at, i.updated_at"; + +pub async fn create( + pool: &PgPool, + principal_id: Uuid, + command: &CreateIssue, +) -> Result { + let mut transaction = pool.begin().await.map_err(unavailable)?; + let project: Option<(i64, Uuid, Uuid)> = sqlx::query_as( + "UPDATE project p SET next_issue_number = next_issue_number + 1, \ + version = version + 1, updated_at = now() FROM workspace_access a \ + WHERE p.id = $1 AND a.workspace_id = p.workspace_id AND a.principal_id = $2 \ + AND 'write' = ANY(a.capabilities) \ + RETURNING p.next_issue_number - 1, p.default_status_id, \ + (SELECT id FROM issue_type WHERE project_id = p.id ORDER BY name LIMIT 1)", + ) + .bind(command.project_id) + .bind(principal_id) + .fetch_optional(&mut *transaction) + .await + .map_err(unavailable)?; + let (number, status_id, type_id) = project.ok_or(StoreError::NotFound)?; + let priority = match command.priority { + IssuePriority::None => "none", + IssuePriority::Low => "low", + IssuePriority::Normal => "normal", + IssuePriority::High => "high", + IssuePriority::Urgent => "urgent", + }; + let issue_id = Uuid::new_v4(); + let row = sqlx::query_as::<_, IssueRow>( + "INSERT INTO issue (id, project_id, number, title, description, type_id, \ + status_id, priority, author_principal_id) VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9) \ + RETURNING id, project_id, number, title, description, type_id, status_id, \ + priority, assignee_principal_id, parent_issue_id, author_principal_id, \ + version, created_at, updated_at", + ) + .bind(issue_id) + .bind(command.project_id) + .bind(number) + .bind(&command.title) + .bind(&command.description) + .bind(type_id) + .bind(status_id) + .bind(priority) + .bind(principal_id) + .fetch_one(&mut *transaction) + .await + .map_err(rejected)?; + outbox( + &mut transaction, + "collaboration.issue.created", + issue_id, + serde_json::json!({"issue_id": issue_id, "project_id": command.project_id, "number": number, "actor_id": principal_id}), + ) + .await?; + transaction.commit().await.map_err(unavailable)?; + row.try_into() +} + +pub async fn list( + pool: &PgPool, + principal_id: Uuid, + project_id: Uuid, +) -> Result, StoreError> { + let query = format!( + "SELECT {COLUMNS} FROM issue i JOIN project p ON p.id = i.project_id \ + JOIN workspace_access a ON a.workspace_id = p.workspace_id \ + WHERE i.project_id = $1 AND a.principal_id = $2 \ + AND 'read' = ANY(a.capabilities) ORDER BY i.number" + ); + sqlx::query_as::<_, IssueRow>(&query) + .bind(project_id) + .bind(principal_id) + .fetch_all(pool) + .await + .map_err(unavailable)? + .into_iter() + .map(TryInto::try_into) + .collect() +} diff --git a/crates/collab-store/src/working_projects.rs b/crates/collab-store/src/working_projects.rs --- /dev/null +++ b/crates/collab-store/src/working_projects.rs @@ -1,0 +1,172 @@ +use sqlx::{FromRow, PgPool}; +use syncode_collab_application::{CreateProject, StoreError}; +use syncode_collab_model::{Project, ProjectKind, ProjectLifecycle}; +use uuid::Uuid; + +use crate::working::{outbox, rejected, unavailable}; + +#[derive(FromRow)] +struct ProjectRow { + id: Uuid, + workspace_id: Uuid, + key: String, + name: String, + slug: String, + description: String, + kind: String, + lifecycle: String, + default_status_id: Uuid, + completion_status_id: Uuid, + version: i64, + created_at: chrono::DateTime, + updated_at: chrono::DateTime, +} + +impl TryFrom for Project { + type Error = StoreError; + + fn try_from(row: ProjectRow) -> Result { + let kind = match row.kind.as_str() { + "continuous" => ProjectKind::Continuous, + "finite" => ProjectKind::Finite, + value => { + return Err(StoreError::Rejected(format!( + "unknown project kind {value}" + ))); + } + }; + let lifecycle = match row.lifecycle.as_str() { + "planned" => ProjectLifecycle::Planned, + "active" => ProjectLifecycle::Active, + "completed" => ProjectLifecycle::Completed, + "canceled" => ProjectLifecycle::Canceled, + value => { + return Err(StoreError::Rejected(format!( + "unknown project lifecycle {value}" + ))); + } + }; + Ok(Self { + id: row.id, + workspace_id: row.workspace_id, + key: row.key, + name: row.name, + slug: row.slug, + description: row.description, + kind, + lifecycle, + default_status_id: row.default_status_id, + completion_status_id: row.completion_status_id, + version: row.version, + created_at: row.created_at, + updated_at: row.updated_at, + }) + } +} + +const COLUMNS: &str = "p.id, p.workspace_id, p.key, p.name, p.slug, p.description, \ + p.kind, p.lifecycle, p.default_status_id, p.completion_status_id, p.version, \ + p.created_at, p.updated_at"; + +pub async fn create( + pool: &PgPool, + principal_id: Uuid, + command: &CreateProject, +) -> Result { + let mut transaction = pool.begin().await.map_err(unavailable)?; + let allowed: Option<(bool,)> = sqlx::query_as( + "SELECT true FROM workspace_access WHERE workspace_id = $1 \ + AND principal_id = $2 AND 'write' = ANY(capabilities)", + ) + .bind(command.workspace_id) + .bind(principal_id) + .fetch_optional(&mut *transaction) + .await + .map_err(unavailable)?; + if allowed.is_none() { + return Err(StoreError::NotFound); + } + let project_id = Uuid::new_v4(); + let statuses = [ + (Uuid::new_v4(), "Backlog", "backlog", 0), + (Uuid::new_v4(), "Todo", "unstarted", 1), + (Uuid::new_v4(), "In Progress", "started", 2), + (Uuid::new_v4(), "Done", "completed", 3), + (Uuid::new_v4(), "Canceled", "canceled", 4), + ]; + let default_status_id = statuses[0].0; + let completion_status_id = statuses[3].0; + let kind = match command.kind { + ProjectKind::Continuous => "continuous", + ProjectKind::Finite => "finite", + }; + let row = sqlx::query_as::<_, ProjectRow>( + "INSERT INTO project (id, workspace_id, key, name, slug, description, kind, \ + default_status_id, completion_status_id) VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9) \ + RETURNING id, workspace_id, key, name, slug, description, kind, lifecycle, \ + default_status_id, completion_status_id, version, created_at, updated_at", + ) + .bind(project_id) + .bind(command.workspace_id) + .bind(&command.key) + .bind(&command.name) + .bind(&command.slug) + .bind(&command.description) + .bind(kind) + .bind(default_status_id) + .bind(completion_status_id) + .fetch_one(&mut *transaction) + .await + .map_err(rejected)?; + for (id, name, category, position) in statuses { + sqlx::query( + "INSERT INTO issue_status (id, project_id, name, category, position) \ + VALUES ($1, $2, $3, $4, $5)", + ) + .bind(id) + .bind(project_id) + .bind(name) + .bind(category) + .bind(position) + .execute(&mut *transaction) + .await + .map_err(rejected)?; + } + sqlx::query("INSERT INTO issue_type (id, project_id, name) VALUES ($1, $2, 'Issue')") + .bind(Uuid::new_v4()) + .bind(project_id) + .execute(&mut *transaction) + .await + .map_err(rejected)?; + outbox( + &mut transaction, + "collaboration.project.created", + project_id, + serde_json::json!({"project_id": project_id, "workspace_id": command.workspace_id, "actor_id": principal_id}), + ) + .await?; + transaction.commit().await.map_err(unavailable)?; + row.try_into() +} + +pub async fn list( + pool: &PgPool, + principal_id: Uuid, + workspace_id: Uuid, +) -> Result, StoreError> { + let query = format!( + "SELECT {COLUMNS} FROM project p \ + JOIN workspace_access a ON a.workspace_id = p.workspace_id \ + WHERE p.workspace_id = $1 AND a.principal_id = $2 \ + AND 'read' = ANY(a.capabilities) ORDER BY p.name, p.id" + ); + sqlx::query_as::<_, ProjectRow>(&query) + .bind(workspace_id) + .bind(principal_id) + .fetch_all(pool) + .await + .map_err(unavailable)? + .into_iter() + .map(TryInto::try_into) + .collect() +} diff --git a/crates/collab-store/src/working_rows.rs b/crates/collab-store/src/working_rows.rs --- /dev/null +++ b/crates/collab-store/src/working_rows.rs @@ -1,0 +1,43 @@ +use sqlx::FromRow; +use syncode_collab_application::StoreError; +use syncode_collab_model::{OwnerKind, Workspace}; +use uuid::Uuid; + +#[derive(FromRow)] +pub(crate) struct WorkspaceRow { + id: Uuid, + owner_kind: String, + owner_id: Uuid, + name: String, + slug: String, + default_environment: String, + version: i64, + created_at: chrono::DateTime, + updated_at: chrono::DateTime, +} + +impl TryFrom for Workspace { + type Error = StoreError; + + fn try_from(row: WorkspaceRow) -> Result { + let owner_kind = match row.owner_kind.as_str() { + "user" => OwnerKind::User, + "organization" => OwnerKind::Organization, + value => return Err(StoreError::Rejected(format!("unknown owner kind {value}"))), + }; + Ok(Self { + id: row.id, + owner_kind, + owner_id: row.owner_id, + name: row.name, + slug: row.slug, + default_environment: row.default_environment, + version: row.version, + created_at: row.created_at, + updated_at: row.updated_at, + }) + } +} + +pub(crate) const WORKSPACE_COLUMNS: &str = "w.id, w.owner_kind, w.owner_id, w.name, w.slug, \ + w.default_environment, w.version, w.created_at, w.updated_at"; diff --git a/crates/collab-store/src/working_versions.rs b/crates/collab-store/src/working_versions.rs --- /dev/null +++ b/crates/collab-store/src/working_versions.rs @@ -1,0 +1,106 @@ +use sqlx::{FromRow, PgPool}; +use syncode_collab_application::{CreateVersion, StoreError}; +use syncode_collab_model::{Version, VersionState}; +use uuid::Uuid; + +use crate::working::{outbox, rejected, unavailable}; + +#[derive(FromRow)] +struct VersionRow { + id: Uuid, + project_id: Uuid, + name: String, + slug: String, + state: String, + created_at: chrono::DateTime, + released_at: Option>, +} + +impl TryFrom for Version { + type Error = StoreError; + + fn try_from(row: VersionRow) -> Result { + let state = match row.state.as_str() { + "planned" => VersionState::Planned, + "released" => VersionState::Released, + value => { + return Err(StoreError::Rejected(format!( + "unknown version state {value}" + ))); + } + }; + Ok(Self { + id: row.id, + project_id: row.project_id, + name: row.name, + slug: row.slug, + state, + created_at: row.created_at, + released_at: row.released_at, + }) + } +} + +const COLUMNS: &str = "v.id, v.project_id, v.name, v.slug, v.state, v.created_at, v.released_at"; + +pub async fn create( + pool: &PgPool, + principal_id: Uuid, + command: &CreateVersion, +) -> Result { + let mut transaction = pool.begin().await.map_err(unavailable)?; + let allowed: Option<(bool,)> = sqlx::query_as( + "SELECT true FROM project p JOIN workspace_access a ON a.workspace_id = p.workspace_id \ + WHERE p.id = $1 AND a.principal_id = $2 AND 'write' = ANY(a.capabilities)", + ) + .bind(command.project_id) + .bind(principal_id) + .fetch_optional(&mut *transaction) + .await + .map_err(unavailable)?; + if allowed.is_none() { + return Err(StoreError::NotFound); + } + let id = Uuid::new_v4(); + let row = sqlx::query_as::<_, VersionRow>( + "INSERT INTO project_version (id, project_id, name, slug) VALUES ($1,$2,$3,$4) \ + RETURNING id, project_id, name, slug, state, created_at, released_at", + ) + .bind(id) + .bind(command.project_id) + .bind(&command.name) + .bind(&command.slug) + .fetch_one(&mut *transaction) + .await + .map_err(rejected)?; + outbox( + &mut transaction, + "collaboration.version.created", + id, + serde_json::json!({"version_id": id, "project_id": command.project_id, "actor_id": principal_id}), + ) + .await?; + transaction.commit().await.map_err(unavailable)?; + row.try_into() +} + +pub async fn list( + pool: &PgPool, + principal_id: Uuid, + project_id: Uuid, +) -> Result, StoreError> { + let query = format!( + "SELECT {COLUMNS} FROM project_version v JOIN project p ON p.id = v.project_id \ + JOIN workspace_access a ON a.workspace_id = p.workspace_id WHERE v.project_id = $1 \ + AND a.principal_id = $2 AND 'read' = ANY(a.capabilities) ORDER BY v.created_at, v.id" + ); + sqlx::query_as::<_, VersionRow>(&query) + .bind(project_id) + .bind(principal_id) + .fetch_all(pool) + .await + .map_err(unavailable)? + .into_iter() + .map(TryInto::try_into) + .collect() +} diff --git a/crates/collab-store/src/working_workspaces.rs b/crates/collab-store/src/working_workspaces.rs --- /dev/null +++ b/crates/collab-store/src/working_workspaces.rs @@ -1,0 +1,202 @@ +use sqlx::PgPool; +use syncode_collab_application::ports::StoredRepository; +use syncode_collab_application::{CreateWorkspace, StoreError}; +use syncode_collab_model::{OwnerKind, RepositoryRole, Workspace}; +use uuid::Uuid; + +use crate::working::{outbox, rejected, unavailable}; +use crate::working_rows::{WORKSPACE_COLUMNS, WorkspaceRow}; + +pub async fn create( + pool: &PgPool, + principal_id: Uuid, + command: &CreateWorkspace, +) -> Result { + let mut transaction = pool.begin().await.map_err(unavailable)?; + let workspace_id = Uuid::new_v4(); + let environment_id = Uuid::new_v4(); + let owner_kind = match command.owner_kind { + OwnerKind::User => "user", + OwnerKind::Organization => "organization", + }; + let row = sqlx::query_as::<_, WorkspaceRow>( + "INSERT INTO workspace \ + (id, owner_kind, owner_id, name, slug, default_environment) \ + VALUES ($1, $2, $3, $4, $5, $6) \ + RETURNING id, owner_kind, owner_id, name, slug, default_environment, \ + version, created_at, updated_at", + ) + .bind(workspace_id) + .bind(owner_kind) + .bind(command.owner_id) + .bind(&command.name) + .bind(&command.slug) + .bind(&command.default_environment) + .fetch_one(&mut *transaction) + .await + .map_err(rejected)?; + sqlx::query( + "INSERT INTO workspace_access (workspace_id, principal_id, capabilities) \ + VALUES ($1, $2, ARRAY['read', 'write', 'admin'])", + ) + .bind(workspace_id) + .bind(principal_id) + .execute(&mut *transaction) + .await + .map_err(rejected)?; + sqlx::query( + "INSERT INTO workspace_environment \ + (id, workspace_id, name, default_source_branch) VALUES ($1, $2, $3, $4)", + ) + .bind(environment_id) + .bind(workspace_id) + .bind(&command.default_environment) + .bind(&command.default_source_branch) + .execute(&mut *transaction) + .await + .map_err(rejected)?; + outbox( + &mut transaction, + "collaboration.workspace.created", + workspace_id, + serde_json::json!({"workspace_id": workspace_id, "actor_id": principal_id}), + ) + .await?; + transaction.commit().await.map_err(unavailable)?; + row.try_into() +} + +pub async fn list(pool: &PgPool, principal_id: Uuid) -> Result, StoreError> { + let query = format!( + "SELECT {WORKSPACE_COLUMNS} FROM workspace w \ + JOIN workspace_access a ON a.workspace_id = w.id \ + WHERE a.principal_id = $1 AND 'read' = ANY(a.capabilities) ORDER BY w.name, w.id" + ); + sqlx::query_as::<_, WorkspaceRow>(&query) + .bind(principal_id) + .fetch_all(pool) + .await + .map_err(unavailable)? + .into_iter() + .map(TryInto::try_into) + .collect() +} + +pub async fn get( + pool: &PgPool, + principal_id: Uuid, + workspace_id: Uuid, +) -> Result { + let query = format!( + "SELECT {WORKSPACE_COLUMNS} FROM workspace w \ + JOIN workspace_access a ON a.workspace_id = w.id \ + WHERE w.id = $1 AND a.principal_id = $2 AND 'read' = ANY(a.capabilities)" + ); + sqlx::query_as::<_, WorkspaceRow>(&query) + .bind(workspace_id) + .bind(principal_id) + .fetch_optional(pool) + .await + .map_err(unavailable)? + .ok_or(StoreError::NotFound)? + .try_into() +} + +pub async fn attach_repository( + pool: &PgPool, + principal_id: Uuid, + workspace_id: Uuid, + repository_id: Uuid, + role: RepositoryRole, + branch: &str, +) -> Result<(), StoreError> { + let mut transaction = pool.begin().await.map_err(unavailable)?; + require_write(&mut transaction, principal_id, workspace_id).await?; + let role = match role { + RepositoryRole::Source => "source", + RepositoryRole::Infra => "infra", + }; + sqlx::query( + "INSERT INTO workspace_repository (workspace_id, repository_id, role) \ + VALUES ($1, $2, $3)", + ) + .bind(workspace_id) + .bind(repository_id) + .bind(role) + .execute(&mut *transaction) + .await + .map_err(rejected)?; + if role == "infra" { + sqlx::query("UPDATE workspace_environment SET infra_branch = $2 WHERE workspace_id = $1") + .bind(workspace_id) + .bind(branch) + .execute(&mut *transaction) + .await + .map_err(rejected)?; + } else { + sqlx::query( + "INSERT INTO environment_branch_override (environment_id, repository_id, branch) \ + SELECT id, $2, $3 FROM workspace_environment WHERE workspace_id = $1", + ) + .bind(workspace_id) + .bind(repository_id) + .bind(branch) + .execute(&mut *transaction) + .await + .map_err(rejected)?; + } + outbox( + &mut transaction, + "collaboration.workspace.repository_attached", + workspace_id, + serde_json::json!({"workspace_id": workspace_id, "repository_id": repository_id, "role": role}), + ) + .await?; + transaction.commit().await.map_err(unavailable) +} + +pub async fn repositories( + pool: &PgPool, + principal_id: Uuid, + workspace_id: Uuid, +) -> Result, StoreError> { + get(pool, principal_id, workspace_id).await?; + let rows: Vec<(Uuid, String)> = sqlx::query_as( + "SELECT repository_id, role FROM workspace_repository \ + WHERE workspace_id = $1 ORDER BY role, repository_id", + ) + .bind(workspace_id) + .fetch_all(pool) + .await + .map_err(unavailable)?; + rows.into_iter() + .map(|(repository_id, role)| { + let role = match role.as_str() { + "source" => RepositoryRole::Source, + "infra" => RepositoryRole::Infra, + value => return Err(StoreError::Rejected(format!("unknown role {value}"))), + }; + Ok(StoredRepository { + repository_id, + role, + }) + }) + .collect() +} + +pub(crate) async fn require_write( + transaction: &mut sqlx::Transaction<'_, sqlx::Postgres>, + principal_id: Uuid, + workspace_id: Uuid, +) -> Result<(), StoreError> { + let allowed: Option<(bool,)> = sqlx::query_as( + "SELECT true FROM workspace_access WHERE workspace_id = $1 \ + AND principal_id = $2 AND 'write' = ANY(capabilities)", + ) + .bind(workspace_id) + .bind(principal_id) + .fetch_optional(&mut **transaction) + .await + .map_err(unavailable)?; + allowed.map(|_| ()).ok_or(StoreError::NotFound) +} diff --git a/crates/collab-store/tests/working_workspace_postgres.rs b/crates/collab-store/tests/working_workspace_postgres.rs --- /dev/null +++ b/crates/collab-store/tests/working_workspace_postgres.rs @@ -1,0 +1,216 @@ +use syncode_collab_application::ports::CollaborationStore; +use syncode_collab_application::{ + ConfigureEnvironment, CreateDocument, CreateIssue, CreateProject, CreateVersion, + CreateWorkspace, StoreError, +}; +use syncode_collab_model::{IssuePriority, OwnerKind, ProjectKind, RepositoryRole}; +use syncode_collab_store::{PostgresCollaboration, connect, migrate}; +use uuid::Uuid; + +async fn store() -> Option<(PostgresCollaboration, sqlx::PgPool)> { + let url = std::env::var("SYNCODE_COLLAB_TEST_DATABASE_URL").ok()?; + let pool = connect(&url, 4).await.ok()?; + migrate(&pool).await.ok()?; + sqlx::query( + "TRUNCATE issue, document_revision, document, project_version, issue_type, issue_status, project, \ + environment_branch_override, workspace_environment, workspace_repository, \ + workspace_access, workspace, event_outbox RESTART IDENTITY CASCADE", + ) + .execute(&pool) + .await + .ok()?; + Some((PostgresCollaboration::new(pool.clone()), pool)) +} + +async fn workspace( + store: &PostgresCollaboration, + principal_id: Uuid, +) -> Result { + store + .create_workspace( + principal_id, + &CreateWorkspace { + owner_kind: OwnerKind::User, + owner_id: principal_id, + name: "SynCode".to_owned(), + slug: "syncode".to_owned(), + default_environment: "development".to_owned(), + default_source_branch: "develop".to_owned(), + }, + ) + .await +} + +async fn project( + store: &PostgresCollaboration, + principal_id: Uuid, + workspace_id: Uuid, +) -> Result { + store + .create_project( + principal_id, + &CreateProject { + workspace_id, + key: "COL".to_owned(), + name: "Collaboration plane".to_owned(), + slug: "collaboration-plane".to_owned(), + description: String::new(), + kind: ProjectKind::Finite, + }, + ) + .await +} + +#[tokio::test] +async fn creates_a_working_workspace_and_enforces_its_invariants() +-> Result<(), Box> { + let Some((store, _pool)) = store().await else { + return Ok(()); + }; + let principal_id = Uuid::new_v4(); + let workspace = workspace(&store, principal_id).await?; + let source_id = Uuid::new_v4(); + store + .attach_repository( + principal_id, + workspace.id, + source_id, + RepositoryRole::Source, + "feature/workspace", + ) + .await?; + store + .attach_repository( + principal_id, + workspace.id, + Uuid::new_v4(), + RepositoryRole::Infra, + "main", + ) + .await?; + assert!(matches!( + store + .attach_repository( + principal_id, + workspace.id, + Uuid::new_v4(), + RepositoryRole::Infra, + "main", + ) + .await, + Err(StoreError::Rejected(_)) + )); + let environment = store + .environment(principal_id, workspace.id, "development") + .await?; + assert_eq!(Some("main"), environment.infra_branch.as_deref()); + assert_eq!( + Some(&(source_id, "feature/workspace".to_owned())), + environment.branch_overrides.first() + ); + let production = store + .configure_environment( + principal_id, + &ConfigureEnvironment { + workspace_id: workspace.id, + name: "production".to_owned(), + default_source_branch: "main".to_owned(), + infra_branch: Some("main".to_owned()), + }, + ) + .await?; + assert_eq!("production", production.name); + assert_eq!( + 2, + store + .list_environments(principal_id, workspace.id) + .await? + .len() + ); + assert!(store.list_workspaces(Uuid::new_v4()).await?.is_empty()); + Ok(()) +} + +#[tokio::test] +async fn rejects_stale_documents_and_allocates_issue_numbers() +-> Result<(), Box> { + let Some((store, _pool)) = store().await else { + return Ok(()); + }; + let principal_id = Uuid::new_v4(); + let workspace = workspace(&store, principal_id).await?; + let project = project(&store, principal_id, workspace.id).await?; + store + .create_version( + principal_id, + &CreateVersion { + project_id: project.id, + name: "0.6.0".to_owned(), + slug: "v0-6-0".to_owned(), + }, + ) + .await?; + assert_eq!( + 1, + store.list_versions(principal_id, project.id).await?.len() + ); + let first = store + .write_document( + principal_id, + &CreateDocument { + workspace_id: workspace.id, + project_id: Some(project.id), + path: "README.md".to_owned(), + content: "first".to_owned(), + base_revision_id: None, + }, + ) + .await?; + let second = store + .write_document( + principal_id, + &CreateDocument { + workspace_id: workspace.id, + project_id: Some(project.id), + path: "README.md".to_owned(), + content: "second".to_owned(), + base_revision_id: Some(first.1.id), + }, + ) + .await?; + assert!(matches!( + store + .write_document( + principal_id, + &CreateDocument { + workspace_id: workspace.id, + project_id: Some(project.id), + path: "README.md".to_owned(), + content: "stale".to_owned(), + base_revision_id: Some(first.1.id), + }, + ) + .await, + Err(StoreError::Conflict(_)) + )); + assert_eq!("second", second.1.content); + for title in ["First", "Second"] { + store + .create_issue( + principal_id, + &CreateIssue { + project_id: project.id, + title: title.to_owned(), + description: String::new(), + priority: IssuePriority::Normal, + }, + ) + .await?; + } + let issues = store.list_issues(principal_id, project.id).await?; + assert_eq!( + vec![1, 2], + issues.iter().map(|issue| issue.number).collect::>() + ); + Ok(()) +} diff --git a/src/identity.rs b/src/identity.rs --- /dev/null +++ b/src/identity.rs @@ -1,0 +1,182 @@ +use syncode_collab_api_grpc::identity_proto::identity_client::IdentityClient; +use syncode_collab_api_grpc::identity_proto::{ + CheckCapabilityRequest, GetRepositoryCoordinatesRequest, PrincipalKind as WirePrincipalKind, + ResolveRepositoryRequest, ResourceKind, ValidateSessionRequest, +}; +use syncode_collab_application::ports::{AccessError, IdentityAccess}; +use syncode_collab_application::{Principal, PrincipalKind, RepositoryCoordinates}; +use tonic::metadata::{Ascii, MetadataValue}; +use tonic::transport::Channel; +use tonic::{Code, Request}; +use uuid::Uuid; + +#[derive(Clone)] +pub struct IdentityClientAccess { + client: IdentityClient, + authorization: MetadataValue, +} + +impl IdentityClientAccess { + pub async fn connect(endpoint: String, shared_secret: String) -> Result { + let authorization = format!("Bearer {shared_secret}") + .parse() + .map_err(|_| AccessError::Unavailable("identity secret is invalid".to_owned()))?; + let client = IdentityClient::connect(endpoint) + .await + .map_err(|error| AccessError::Unavailable(error.to_string()))?; + Ok(Self { + client, + authorization, + }) + } + + fn request(&self, body: T) -> Request { + let mut request = Request::new(body); + request + .metadata_mut() + .insert("authorization", self.authorization.clone()); + request + } + + async fn require( + &self, + principal: Principal, + resource_kind: ResourceKind, + resource_id: Uuid, + capability: &str, + ) -> Result<(), AccessError> { + let allowed = self + .client + .clone() + .check_capability(self.request(CheckCapabilityRequest { + principal_id: principal.id.to_string(), + principal_kind: wire_principal_kind(principal.kind).into(), + resource_kind: resource_kind.into(), + resource_id: resource_id.to_string(), + capability: capability.to_owned(), + })) + .await + .map_err(map_status)? + .into_inner() + .allowed; + allowed.then_some(()).ok_or(AccessError::Denied) + } +} + +#[tonic::async_trait] +impl IdentityAccess for IdentityClientAccess { + async fn authenticate(&self, session_token: &str) -> Result { + let response = self + .client + .clone() + .validate_session(self.request(ValidateSessionRequest { + session_token: session_token.to_owned(), + })) + .await + .map_err(map_status)? + .into_inner(); + let id = response.principal_id.parse().map_err(|_| { + AccessError::Unavailable("identity returned an invalid principal".to_owned()) + })?; + let kind = match WirePrincipalKind::try_from(response.principal_kind) { + Ok(WirePrincipalKind::User) => PrincipalKind::User, + Ok(WirePrincipalKind::PlatformAgent) => PrincipalKind::PlatformAgent, + Ok(WirePrincipalKind::LocalAgent) => PrincipalKind::LocalAgent, + Ok(WirePrincipalKind::AccessToken) => PrincipalKind::AccessToken, + Ok(WirePrincipalKind::Unspecified) | Err(_) => { + return Err(AccessError::Unavailable( + "identity returned an invalid principal kind".to_owned(), + )); + } + }; + Ok(Principal { id, kind }) + } + + async fn require_organization_admin( + &self, + principal: Principal, + organization_id: Uuid, + ) -> Result<(), AccessError> { + self.require( + principal, + ResourceKind::Organization, + organization_id, + "admin", + ) + .await + } + + async fn resolve_readable_repository( + &self, + principal: Principal, + owner: &str, + name: &str, + ) -> Result { + let response = self + .client + .clone() + .resolve_repository(self.request(ResolveRepositoryRequest { + owner: owner.to_owned(), + name: name.to_owned(), + })) + .await + .map_err(map_status)? + .into_inner(); + let id = response.repository_id.parse().map_err(|_| { + AccessError::Unavailable("identity returned an invalid repository".to_owned()) + })?; + self.require(principal, ResourceKind::Repository, id, "repo:read") + .await?; + Ok(RepositoryCoordinates { + id, + owner: owner.to_owned(), + name: name.to_owned(), + }) + } + + async fn repository_coordinates( + &self, + principal: Principal, + repository_id: Uuid, + ) -> Result { + self.require( + principal, + ResourceKind::Repository, + repository_id, + "repo:read", + ) + .await?; + let response = self + .client + .clone() + .get_repository_coordinates(self.request(GetRepositoryCoordinatesRequest { + repository_id: repository_id.to_string(), + })) + .await + .map_err(map_status)? + .into_inner(); + Ok(RepositoryCoordinates { + id: repository_id, + owner: response.owner, + name: response.name, + }) + } +} + +fn wire_principal_kind(kind: PrincipalKind) -> WirePrincipalKind { + match kind { + PrincipalKind::User => WirePrincipalKind::User, + PrincipalKind::PlatformAgent => WirePrincipalKind::PlatformAgent, + PrincipalKind::LocalAgent => WirePrincipalKind::LocalAgent, + PrincipalKind::AccessToken => WirePrincipalKind::AccessToken, + } +} + +fn map_status(status: tonic::Status) -> AccessError { + match status.code() { + Code::Unauthenticated => AccessError::Unauthenticated, + Code::PermissionDenied => AccessError::Denied, + Code::NotFound => AccessError::NotFound, + _ => AccessError::Unavailable(status.to_string()), + } +} -- SynCode