chore: cleanup legacy logic (#15072)
This commit is contained in:
1 parent
8c0e1ba04e
commit
81760fd45c
142 files changed
+3303
-2401
No files matched your search
@@ -1410,22 +1410,6 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"customerIo": {
|
|
||||||
"type": "object",
|
|
||||||
"description": "Configuration for customerIo module",
|
|
||||||
"properties": {
|
|
||||||
"enabled": {
|
|
||||||
"type": "boolean",
|
|
||||||
"description": "Enable customer.io integration\n@default false",
|
|
||||||
"default": false
|
|
||||||
},
|
|
||||||
"token": {
|
|
||||||
"type": "string",
|
|
||||||
"description": "Customer.io token\n@default \"\"",
|
|
||||||
"default": ""
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"oauth": {
|
"oauth": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"description": "Configuration for oauth module",
|
"description": "Configuration for oauth module",
|
||||||
@@ -1524,16 +1508,6 @@
|
|||||||
"description": "Whether enable lifetime price and allow user to pay for it.\n@default true",
|
"description": "Whether enable lifetime price and allow user to pay for it.\n@default true",
|
||||||
"default": true
|
"default": true
|
||||||
},
|
},
|
||||||
"apiKey": {
|
|
||||||
"type": "string",
|
|
||||||
"description": "[Deprecated] Stripe API key. Use payment.stripe.apiKey instead.\n@default \"\"\n@environment `STRIPE_API_KEY`",
|
|
||||||
"default": ""
|
|
||||||
},
|
|
||||||
"webhookKey": {
|
|
||||||
"type": "string",
|
|
||||||
"description": "[Deprecated] Stripe webhook key. Use payment.stripe.webhookKey instead.\n@default \"\"\n@environment `STRIPE_WEBHOOK_KEY`",
|
|
||||||
"default": ""
|
|
||||||
},
|
|
||||||
"stripe": {
|
"stripe": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"description": "Stripe sdk options and credentials\n@default {\"apiKey\":\"\",\"webhookKey\":\"\"}\n@link https://docs.stripe.com/api",
|
"description": "Stripe sdk options and credentials\n@default {\"apiKey\":\"\",\"webhookKey\":\"\"}\n@link https://docs.stripe.com/api",
|
||||||
|
|||||||
Generated
+338
-35
@@ -40,7 +40,7 @@ checksum = "b169f7a6d4742236a0a00c541b845991d0ac43e546831af1249753ab4c3aa3a0"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"cfg-if",
|
"cfg-if",
|
||||||
"cipher",
|
"cipher",
|
||||||
"cpufeatures",
|
"cpufeatures 0.2.17",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -104,7 +104,7 @@ name = "affine_media_capture"
|
|||||||
version = "0.0.0"
|
version = "0.0.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"block2",
|
"block2",
|
||||||
"core-foundation",
|
"core-foundation 0.10.1",
|
||||||
"coreaudio-rs 0.12.1",
|
"coreaudio-rs 0.12.1",
|
||||||
"cpal",
|
"cpal",
|
||||||
"criterion2",
|
"criterion2",
|
||||||
@@ -215,7 +215,6 @@ dependencies = [
|
|||||||
"aes-gcm",
|
"aes-gcm",
|
||||||
"affine_common",
|
"affine_common",
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"base64-simd",
|
|
||||||
"chrono",
|
"chrono",
|
||||||
"file-format",
|
"file-format",
|
||||||
"hex",
|
"hex",
|
||||||
@@ -235,8 +234,7 @@ dependencies = [
|
|||||||
"p256",
|
"p256",
|
||||||
"rand 0.9.4",
|
"rand 0.9.4",
|
||||||
"rayon",
|
"rayon",
|
||||||
"reqwest",
|
"safefetch",
|
||||||
"rustls",
|
|
||||||
"schemars",
|
"schemars",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
@@ -246,7 +244,6 @@ dependencies = [
|
|||||||
"tokio",
|
"tokio",
|
||||||
"url",
|
"url",
|
||||||
"v_htmlescape",
|
"v_htmlescape",
|
||||||
"webpki-roots",
|
|
||||||
"y-octo",
|
"y-octo",
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -1036,6 +1033,17 @@ dependencies = [
|
|||||||
"libc",
|
"libc",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "chacha20"
|
||||||
|
version = "0.10.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "6f8d983286843e49675a4b7a2d174efe136dc93a18d69130dd18198a6c167601"
|
||||||
|
dependencies = [
|
||||||
|
"cfg-if",
|
||||||
|
"cpufeatures 0.3.0",
|
||||||
|
"rand_core 0.10.1",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "chinese-number"
|
name = "chinese-number"
|
||||||
version = "0.7.8"
|
version = "0.7.8"
|
||||||
@@ -1272,7 +1280,17 @@ version = "0.3.5"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "bebfc66b5ef3f159b3c4a6097e7c2a2e2cc71ec77fff7913e9895fae45f4c349"
|
checksum = "bebfc66b5ef3f159b3c4a6097e7c2a2e2cc71ec77fff7913e9895fae45f4c349"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"core-foundation",
|
"core-foundation 0.10.1",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "core-foundation"
|
||||||
|
version = "0.9.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f"
|
||||||
|
dependencies = [
|
||||||
|
"core-foundation-sys",
|
||||||
|
"libc",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -1299,7 +1317,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "fa95a34622365fa5bbf40b20b75dba8dfa8c94c734aea8ac9a5ca38af14316f1"
|
checksum = "fa95a34622365fa5bbf40b20b75dba8dfa8c94c734aea8ac9a5ca38af14316f1"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bitflags 2.11.0",
|
"bitflags 2.11.0",
|
||||||
"core-foundation",
|
"core-foundation 0.10.1",
|
||||||
"core-graphics-types",
|
"core-graphics-types",
|
||||||
"foreign-types",
|
"foreign-types",
|
||||||
"libc",
|
"libc",
|
||||||
@@ -1312,7 +1330,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "064badf302c3194842cf2c5d61f56cc88e54a759313879cdf03abdd27d0c3b97"
|
checksum = "064badf302c3194842cf2c5d61f56cc88e54a759313879cdf03abdd27d0c3b97"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bitflags 2.11.0",
|
"bitflags 2.11.0",
|
||||||
"core-foundation",
|
"core-foundation 0.10.1",
|
||||||
"core-graphics-types",
|
"core-graphics-types",
|
||||||
"foreign-types",
|
"foreign-types",
|
||||||
"libc",
|
"libc",
|
||||||
@@ -1325,7 +1343,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "3d44a101f213f6c4cdc1853d4b78aef6db6bdfa3468798cc1d9912f4735013eb"
|
checksum = "3d44a101f213f6c4cdc1853d4b78aef6db6bdfa3468798cc1d9912f4735013eb"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bitflags 2.11.0",
|
"bitflags 2.11.0",
|
||||||
"core-foundation",
|
"core-foundation 0.10.1",
|
||||||
"libc",
|
"libc",
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -1336,7 +1354,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "6592bdc3e2b3479140e8c64d419b62d613526c8b61136ca990cd0bae4377db4f"
|
checksum = "6592bdc3e2b3479140e8c64d419b62d613526c8b61136ca990cd0bae4377db4f"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"core-audio-types-rs",
|
"core-audio-types-rs",
|
||||||
"core-foundation",
|
"core-foundation 0.10.1",
|
||||||
"core-utils-rs",
|
"core-utils-rs",
|
||||||
"core-video-rs",
|
"core-video-rs",
|
||||||
"thiserror 2.0.18",
|
"thiserror 2.0.18",
|
||||||
@@ -1348,7 +1366,7 @@ version = "0.3.5"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "791d9e8eb99cff8db5e2b6a5bb420b893476b7a064bb6889b2a691c1bb6d745c"
|
checksum = "791d9e8eb99cff8db5e2b6a5bb420b893476b7a064bb6889b2a691c1bb6d745c"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"core-foundation",
|
"core-foundation 0.10.1",
|
||||||
"four-char-code",
|
"four-char-code",
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -1358,7 +1376,7 @@ version = "0.3.5"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "f1f1614e163a5d6fb972305fa14fc5c2989143da0426c213b74785b31e1fadb8"
|
checksum = "f1f1614e163a5d6fb972305fa14fc5c2989143da0426c213b74785b31e1fadb8"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"core-foundation",
|
"core-foundation 0.10.1",
|
||||||
"core-graphics 0.24.0",
|
"core-graphics 0.24.0",
|
||||||
"core-utils-rs",
|
"core-utils-rs",
|
||||||
"io-surface",
|
"io-surface",
|
||||||
@@ -1424,7 +1442,7 @@ dependencies = [
|
|||||||
"core-foundation-sys",
|
"core-foundation-sys",
|
||||||
"coreaudio-rs 0.11.3",
|
"coreaudio-rs 0.11.3",
|
||||||
"dasp_sample",
|
"dasp_sample",
|
||||||
"jni",
|
"jni 0.21.1",
|
||||||
"js-sys",
|
"js-sys",
|
||||||
"libc",
|
"libc",
|
||||||
"mach2",
|
"mach2",
|
||||||
@@ -1446,6 +1464,15 @@ dependencies = [
|
|||||||
"libc",
|
"libc",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "cpufeatures"
|
||||||
|
version = "0.3.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "8b2a41393f66f16b0823bb79094d54ac5fbd34ab292ddafb9a0456ac9f87d201"
|
||||||
|
dependencies = [
|
||||||
|
"libc",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "crc"
|
name = "crc"
|
||||||
version = "3.4.0"
|
version = "3.4.0"
|
||||||
@@ -1523,6 +1550,12 @@ dependencies = [
|
|||||||
"walkdir",
|
"walkdir",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "critical-section"
|
||||||
|
version = "1.2.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "790eea4361631c5e7d22598ecd5723ff611904e3344ce8720784c93e3d83d40b"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "crossbeam-channel"
|
name = "crossbeam-channel"
|
||||||
version = "0.5.15"
|
version = "0.5.15"
|
||||||
@@ -2473,6 +2506,7 @@ dependencies = [
|
|||||||
"cfg-if",
|
"cfg-if",
|
||||||
"libc",
|
"libc",
|
||||||
"r-efi 6.0.0",
|
"r-efi 6.0.0",
|
||||||
|
"rand_core 0.10.1",
|
||||||
"wasip2",
|
"wasip2",
|
||||||
"wasip3",
|
"wasip3",
|
||||||
]
|
]
|
||||||
@@ -2741,6 +2775,85 @@ version = "0.4.3"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70"
|
checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "hickory-net"
|
||||||
|
version = "0.26.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e2295ed2f9c31e471e1428a8f88a3f0e1f4b27c15049592138d1eebe9c35b183"
|
||||||
|
dependencies = [
|
||||||
|
"async-trait",
|
||||||
|
"bytes",
|
||||||
|
"cfg-if",
|
||||||
|
"data-encoding",
|
||||||
|
"futures-channel",
|
||||||
|
"futures-io",
|
||||||
|
"futures-util",
|
||||||
|
"h2",
|
||||||
|
"hickory-proto",
|
||||||
|
"http",
|
||||||
|
"idna",
|
||||||
|
"ipnet",
|
||||||
|
"jni 0.22.4",
|
||||||
|
"rand 0.10.1",
|
||||||
|
"rustls",
|
||||||
|
"thiserror 2.0.18",
|
||||||
|
"tinyvec",
|
||||||
|
"tokio",
|
||||||
|
"tokio-rustls",
|
||||||
|
"tracing",
|
||||||
|
"url",
|
||||||
|
"webpki-roots",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "hickory-proto"
|
||||||
|
version = "0.26.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "0bab31817bfb44672a252e97fe81cd0c18d1b2cf892108922f6818820df8c643"
|
||||||
|
dependencies = [
|
||||||
|
"data-encoding",
|
||||||
|
"idna",
|
||||||
|
"ipnet",
|
||||||
|
"jni 0.22.4",
|
||||||
|
"once_cell",
|
||||||
|
"prefix-trie",
|
||||||
|
"rand 0.10.1",
|
||||||
|
"ring",
|
||||||
|
"thiserror 2.0.18",
|
||||||
|
"tinyvec",
|
||||||
|
"tracing",
|
||||||
|
"url",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "hickory-resolver"
|
||||||
|
version = "0.26.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f0d58d28879ceecde6607729660c2667a081ccdc082e082675042793960f178c"
|
||||||
|
dependencies = [
|
||||||
|
"cfg-if",
|
||||||
|
"futures-util",
|
||||||
|
"hickory-net",
|
||||||
|
"hickory-proto",
|
||||||
|
"ipconfig",
|
||||||
|
"ipnet",
|
||||||
|
"jni 0.22.4",
|
||||||
|
"moka",
|
||||||
|
"ndk-context",
|
||||||
|
"once_cell",
|
||||||
|
"parking_lot",
|
||||||
|
"rand 0.10.1",
|
||||||
|
"resolv-conf",
|
||||||
|
"rustls",
|
||||||
|
"smallvec",
|
||||||
|
"system-configuration",
|
||||||
|
"thiserror 2.0.18",
|
||||||
|
"tokio",
|
||||||
|
"tokio-rustls",
|
||||||
|
"tracing",
|
||||||
|
"webpki-roots",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "hkdf"
|
name = "hkdf"
|
||||||
version = "0.12.4"
|
version = "0.12.4"
|
||||||
@@ -3333,16 +3446,32 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "554b8c5d64ec09a3a520fe58e4d48a73e00ff32899cdcbe32a4877afd4968b8e"
|
checksum = "554b8c5d64ec09a3a520fe58e4d48a73e00ff32899cdcbe32a4877afd4968b8e"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"cgl",
|
"cgl",
|
||||||
"core-foundation",
|
"core-foundation 0.10.1",
|
||||||
"core-foundation-sys",
|
"core-foundation-sys",
|
||||||
"leaky-cow",
|
"leaky-cow",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "ipconfig"
|
||||||
|
version = "0.3.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "4d40460c0ce33d6ce4b0630ad68ff63d6661961c48b6dba35e5a4d81cfb48222"
|
||||||
|
dependencies = [
|
||||||
|
"socket2",
|
||||||
|
"widestring",
|
||||||
|
"windows-registry",
|
||||||
|
"windows-result 0.4.1",
|
||||||
|
"windows-sys 0.61.2",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ipnet"
|
name = "ipnet"
|
||||||
version = "2.12.0"
|
version = "2.12.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "d98f6fed1fde3f8c21bc40a1abb88dd75e67924f9cffc3ef95607bad8017f8e2"
|
checksum = "d98f6fed1fde3f8c21bc40a1abb88dd75e67924f9cffc3ef95607bad8017f8e2"
|
||||||
|
dependencies = [
|
||||||
|
"serde",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "iri-string"
|
name = "iri-string"
|
||||||
@@ -3442,19 +3571,68 @@ dependencies = [
|
|||||||
"cesu8",
|
"cesu8",
|
||||||
"cfg-if",
|
"cfg-if",
|
||||||
"combine",
|
"combine",
|
||||||
"jni-sys",
|
"jni-sys 0.3.0",
|
||||||
"log",
|
"log",
|
||||||
"thiserror 1.0.69",
|
"thiserror 1.0.69",
|
||||||
"walkdir",
|
"walkdir",
|
||||||
"windows-sys 0.45.0",
|
"windows-sys 0.45.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "jni"
|
||||||
|
version = "0.22.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "5efd9a482cf3a427f00d6b35f14332adc7902ce91efb778580e180ff90fa3498"
|
||||||
|
dependencies = [
|
||||||
|
"cfg-if",
|
||||||
|
"combine",
|
||||||
|
"jni-macros",
|
||||||
|
"jni-sys 0.4.1",
|
||||||
|
"log",
|
||||||
|
"simd_cesu8",
|
||||||
|
"thiserror 2.0.18",
|
||||||
|
"walkdir",
|
||||||
|
"windows-link 0.2.1",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "jni-macros"
|
||||||
|
version = "0.22.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a00109accc170f0bdb141fed3e393c565b6f5e072365c3bd58f5b062591560a3"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"rustc_version",
|
||||||
|
"simd_cesu8",
|
||||||
|
"syn 2.0.117",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "jni-sys"
|
name = "jni-sys"
|
||||||
version = "0.3.0"
|
version = "0.3.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "8eaf4bc02d17cbdd7ff4c7438cafcdf7fb9a4613313ad11b4f8fefe7d3fa0130"
|
checksum = "8eaf4bc02d17cbdd7ff4c7438cafcdf7fb9a4613313ad11b4f8fefe7d3fa0130"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "jni-sys"
|
||||||
|
version = "0.4.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "c6377a88cb3910bee9b0fa88d4f42e1d2da8e79915598f65fb0c7ee14c878af2"
|
||||||
|
dependencies = [
|
||||||
|
"jni-sys-macros",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "jni-sys-macros"
|
||||||
|
version = "0.4.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "38c0b942f458fe50cdac086d2f946512305e5631e720728f2a61aabcd47a6264"
|
||||||
|
dependencies = [
|
||||||
|
"quote",
|
||||||
|
"syn 2.0.117",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "jobserver"
|
name = "jobserver"
|
||||||
version = "0.1.34"
|
version = "0.1.34"
|
||||||
@@ -3529,7 +3707,7 @@ version = "0.1.6"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "cb26cec98cce3a3d96cbb7bced3c4b16e3d13f27ec56dbd62cbc8f39cfb9d653"
|
checksum = "cb26cec98cce3a3d96cbb7bced3c4b16e3d13f27ec56dbd62cbc8f39cfb9d653"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"cpufeatures",
|
"cpufeatures 0.2.17",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -4011,15 +4189,32 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "mio"
|
name = "mio"
|
||||||
version = "1.1.1"
|
version = "1.2.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "a69bcab0ad47271a0234d9422b131806bf3968021e5dc9328caf2d4cd58557fc"
|
checksum = "02bd0af71c67b473010cbbc60715ee815645a4dc942899111f494b4b737d6fda"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"libc",
|
"libc",
|
||||||
"wasi",
|
"wasi",
|
||||||
"windows-sys 0.61.2",
|
"windows-sys 0.61.2",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "moka"
|
||||||
|
version = "0.12.15"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "957228ad12042ee839f93c8f257b62b4c0ab5eaae1d4fa60de53b27c9d7c5046"
|
||||||
|
dependencies = [
|
||||||
|
"crossbeam-channel",
|
||||||
|
"crossbeam-epoch",
|
||||||
|
"crossbeam-utils",
|
||||||
|
"equivalent",
|
||||||
|
"parking_lot",
|
||||||
|
"portable-atomic",
|
||||||
|
"smallvec",
|
||||||
|
"tagptr",
|
||||||
|
"uuid",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "moxcms"
|
name = "moxcms"
|
||||||
version = "0.7.11"
|
version = "0.7.11"
|
||||||
@@ -4138,7 +4333,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "2076a31b7010b17a38c01907c45b945e8f11495ee4dd588309718901b1f7a5b7"
|
checksum = "2076a31b7010b17a38c01907c45b945e8f11495ee4dd588309718901b1f7a5b7"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bitflags 2.11.0",
|
"bitflags 2.11.0",
|
||||||
"jni-sys",
|
"jni-sys 0.3.0",
|
||||||
"log",
|
"log",
|
||||||
"ndk-sys",
|
"ndk-sys",
|
||||||
"num_enum",
|
"num_enum",
|
||||||
@@ -4157,7 +4352,7 @@ version = "0.5.0+25.2.9519653"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "8c196769dd60fd4f363e11d948139556a344e79d451aeb2fa2fd040738ef7691"
|
checksum = "8c196769dd60fd4f363e11d948139556a344e79d451aeb2fa2fd040738ef7691"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"jni-sys",
|
"jni-sys 0.3.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -4431,7 +4626,7 @@ version = "0.6.1"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "e8b61bebd49e5d43f5f8cc7ee2891c16e0f41ec7954d36bcb6c14c5e0de867fb"
|
checksum = "e8b61bebd49e5d43f5f8cc7ee2891c16e0f41ec7954d36bcb6c14c5e0de867fb"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"jni",
|
"jni 0.21.1",
|
||||||
"ndk",
|
"ndk",
|
||||||
"ndk-context",
|
"ndk-context",
|
||||||
"num-derive",
|
"num-derive",
|
||||||
@@ -4462,6 +4657,10 @@ name = "once_cell"
|
|||||||
version = "1.21.4"
|
version = "1.21.4"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50"
|
checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50"
|
||||||
|
dependencies = [
|
||||||
|
"critical-section",
|
||||||
|
"portable-atomic",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "once_cell_polyfill"
|
name = "once_cell_polyfill"
|
||||||
@@ -4900,7 +5099,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "9d1fe60d06143b2430aa532c94cfe9e29783047f06c0d7fd359a9a51b729fa25"
|
checksum = "9d1fe60d06143b2430aa532c94cfe9e29783047f06c0d7fd359a9a51b729fa25"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"cfg-if",
|
"cfg-if",
|
||||||
"cpufeatures",
|
"cpufeatures 0.2.17",
|
||||||
"opaque-debug",
|
"opaque-debug",
|
||||||
"universal-hash",
|
"universal-hash",
|
||||||
]
|
]
|
||||||
@@ -4970,6 +5169,17 @@ version = "0.1.1"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "925383efa346730478fb4838dbe9137d2a47675ad789c546d150a6e1dd4ab31c"
|
checksum = "925383efa346730478fb4838dbe9137d2a47675ad789c546d150a6e1dd4ab31c"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "prefix-trie"
|
||||||
|
version = "0.8.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "4cf6e3177f0684016a5c209b00882e15f8bdd3f3bb48f0491df10cd102d0c6e7"
|
||||||
|
dependencies = [
|
||||||
|
"either",
|
||||||
|
"ipnet",
|
||||||
|
"num-traits",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "prettyplease"
|
name = "prettyplease"
|
||||||
version = "0.2.37"
|
version = "0.2.37"
|
||||||
@@ -5246,6 +5456,17 @@ dependencies = [
|
|||||||
"rand_core 0.9.5",
|
"rand_core 0.9.5",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rand"
|
||||||
|
version = "0.10.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d2e8e8bcc7961af1fdac401278c6a831614941f6164ee3bf4ce61b7edb162207"
|
||||||
|
dependencies = [
|
||||||
|
"chacha20",
|
||||||
|
"getrandom 0.4.2",
|
||||||
|
"rand_core 0.10.1",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rand_chacha"
|
name = "rand_chacha"
|
||||||
version = "0.3.1"
|
version = "0.3.1"
|
||||||
@@ -5284,6 +5505,12 @@ dependencies = [
|
|||||||
"getrandom 0.3.4",
|
"getrandom 0.3.4",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rand_core"
|
||||||
|
version = "0.10.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "63b8176103e19a2643978565ca18b50549f6101881c443590420e4dc998a3c69"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rand_distr"
|
name = "rand_distr"
|
||||||
version = "0.5.1"
|
version = "0.5.1"
|
||||||
@@ -5458,9 +5685,9 @@ checksum = "dc897dd8d9e8bd1ed8cdad82b5966c3e0ecae09fb1907d58efaa013543185d0a"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "reqwest"
|
name = "reqwest"
|
||||||
version = "0.13.3"
|
version = "0.13.4"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "62e0021ea2c22aed41653bc7e1419abb2c97e038ff2c33d0e1309e49a97deec0"
|
checksum = "219c5811de6525e5416c7d5d53bb656d3afdbc6c5af816e0802bcfa42dbdc1c3"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"base64",
|
"base64",
|
||||||
"bytes",
|
"bytes",
|
||||||
@@ -5496,6 +5723,12 @@ dependencies = [
|
|||||||
"web-sys",
|
"web-sys",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "resolv-conf"
|
||||||
|
version = "0.7.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "1e061d1b48cb8d38042de4ae0a7a6401009d6143dc80d2e2d6f31f0bdd6470c7"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rfc6979"
|
name = "rfc6979"
|
||||||
version = "0.4.0"
|
version = "0.4.0"
|
||||||
@@ -5706,9 +5939,9 @@ version = "0.6.2"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "1d99feebc72bae7ab76ba994bb5e121b8d83d910ca40b36e0921f53becc41784"
|
checksum = "1d99feebc72bae7ab76ba994bb5e121b8d83d910ca40b36e0921f53becc41784"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"core-foundation",
|
"core-foundation 0.10.1",
|
||||||
"core-foundation-sys",
|
"core-foundation-sys",
|
||||||
"jni",
|
"jni 0.21.1",
|
||||||
"log",
|
"log",
|
||||||
"once_cell",
|
"once_cell",
|
||||||
"rustls",
|
"rustls",
|
||||||
@@ -5781,6 +6014,22 @@ version = "1.0.23"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "9774ba4a74de5f7b1c1451ed6cd5285a32eddb5cccb8cc655a4e50009e06477f"
|
checksum = "9774ba4a74de5f7b1c1451ed6cd5285a32eddb5cccb8cc655a4e50009e06477f"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "safefetch"
|
||||||
|
version = "0.1.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "69a2a7c208b48e0fb427db7d16ce97ed87982cfc2484ba4407f1eafb45055364"
|
||||||
|
dependencies = [
|
||||||
|
"anyhow",
|
||||||
|
"hickory-resolver",
|
||||||
|
"image",
|
||||||
|
"reqwest",
|
||||||
|
"rustls",
|
||||||
|
"tokio",
|
||||||
|
"url",
|
||||||
|
"webpki-roots",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "same-file"
|
name = "same-file"
|
||||||
version = "1.0.6"
|
version = "1.0.6"
|
||||||
@@ -5842,7 +6091,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "0c62ae379564f834110c5b020cc731a7c14b8ec4879b4367579a467a0a704c21"
|
checksum = "0c62ae379564f834110c5b020cc731a7c14b8ec4879b4367579a467a0a704c21"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"block2",
|
"block2",
|
||||||
"core-foundation",
|
"core-foundation 0.10.1",
|
||||||
"core-graphics 0.25.0",
|
"core-graphics 0.25.0",
|
||||||
"core-media-rs",
|
"core-media-rs",
|
||||||
"core-utils-rs",
|
"core-utils-rs",
|
||||||
@@ -5892,7 +6141,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "b7f4bc775c73d9a02cde8bf7b2ec4c9d12743edf609006c7facc23998404cd1d"
|
checksum = "b7f4bc775c73d9a02cde8bf7b2ec4c9d12743edf609006c7facc23998404cd1d"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bitflags 2.11.0",
|
"bitflags 2.11.0",
|
||||||
"core-foundation",
|
"core-foundation 0.10.1",
|
||||||
"core-foundation-sys",
|
"core-foundation-sys",
|
||||||
"libc",
|
"libc",
|
||||||
"security-framework-sys",
|
"security-framework-sys",
|
||||||
@@ -6014,7 +6263,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba"
|
checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"cfg-if",
|
"cfg-if",
|
||||||
"cpufeatures",
|
"cpufeatures 0.2.17",
|
||||||
"digest",
|
"digest",
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -6025,7 +6274,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283"
|
checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"cfg-if",
|
"cfg-if",
|
||||||
"cpufeatures",
|
"cpufeatures 0.2.17",
|
||||||
"digest",
|
"digest",
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -6080,6 +6329,22 @@ version = "0.3.8"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "e320a6c5ad31d271ad523dcf3ad13e2767ad8b1cb8f047f75a8aeaf8da139da2"
|
checksum = "e320a6c5ad31d271ad523dcf3ad13e2767ad8b1cb8f047f75a8aeaf8da139da2"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "simd_cesu8"
|
||||||
|
version = "1.1.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "94f90157bb87cddf702797c5dadfa0be7d266cdf49e22da2fcaa32eff75b2c33"
|
||||||
|
dependencies = [
|
||||||
|
"rustc_version",
|
||||||
|
"simdutf8",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "simdutf8"
|
||||||
|
version = "0.1.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e3a9fe34e3e7a50316060351f37187a3f546bce95496156754b601a5fa71b76e"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "simplecss"
|
name = "simplecss"
|
||||||
version = "0.2.2"
|
version = "0.2.2"
|
||||||
@@ -6765,6 +7030,33 @@ dependencies = [
|
|||||||
"yaml-rust",
|
"yaml-rust",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "system-configuration"
|
||||||
|
version = "0.7.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a13f3d0daba03132c0aa9767f98351b3488edc2c100cda2d2ec2b04f3d8d3c8b"
|
||||||
|
dependencies = [
|
||||||
|
"bitflags 2.11.0",
|
||||||
|
"core-foundation 0.9.4",
|
||||||
|
"system-configuration-sys",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "system-configuration-sys"
|
||||||
|
version = "0.6.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "8e1d1b10ced5ca923a1fcb8d03e96b8d3268065d724548c0211415ff6ac6bac4"
|
||||||
|
dependencies = [
|
||||||
|
"core-foundation-sys",
|
||||||
|
"libc",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "tagptr"
|
||||||
|
version = "0.2.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "7b2093cf4c8eb1e67749a6762251bc9cd836b6fc171623bd0a9d324d37af2417"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tempfile"
|
name = "tempfile"
|
||||||
version = "3.27.0"
|
version = "3.27.0"
|
||||||
@@ -6978,9 +7270,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tokio"
|
name = "tokio"
|
||||||
version = "1.50.0"
|
version = "1.52.3"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "27ad5e34374e03cfffefc301becb44e9dc3c17584f414349ebe29ed26661822d"
|
checksum = "8fc7f01b389ac15039e4dc9531aa973a135d7a4135281b12d7c1bc79fd57fffe"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bytes",
|
"bytes",
|
||||||
"libc",
|
"libc",
|
||||||
@@ -6995,9 +7287,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tokio-macros"
|
name = "tokio-macros"
|
||||||
version = "2.6.1"
|
version = "2.7.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "5c55a2eff8b69ce66c84f85e1da1c233edc36ceb85a2058d11b0d6a3c7e7569c"
|
checksum = "385a6cb71ab9ab790c5fe8d67f1645e6c450a7ce006a33de03daa956cf70a496"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
@@ -8581,6 +8873,17 @@ dependencies = [
|
|||||||
"windows-link 0.1.3",
|
"windows-link 0.1.3",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows-registry"
|
||||||
|
version = "0.6.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "02752bf7fbdcce7f2a27a742f798510f3e5ad88dbe84871e5168e2120c3d5720"
|
||||||
|
dependencies = [
|
||||||
|
"windows-link 0.2.1",
|
||||||
|
"windows-result 0.4.1",
|
||||||
|
"windows-strings 0.5.1",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "windows-result"
|
name = "windows-result"
|
||||||
version = "0.1.2"
|
version = "0.1.2"
|
||||||
|
|||||||
@@ -97,6 +97,7 @@ resolver = "3"
|
|||||||
readability = { version = "0.3.0", default-features = false }
|
readability = { version = "0.3.0", default-features = false }
|
||||||
regex = "1.10"
|
regex = "1.10"
|
||||||
rubato = "0.16"
|
rubato = "0.16"
|
||||||
|
safefetch = "0.1.0"
|
||||||
schemars = "0.8"
|
schemars = "0.8"
|
||||||
screencapturekit = "0.3"
|
screencapturekit = "0.3"
|
||||||
serde = "1"
|
serde = "1"
|
||||||
|
|||||||
@@ -17,7 +17,6 @@ affine_common = { workspace = true, features = [
|
|||||||
"ydoc-loader",
|
"ydoc-loader",
|
||||||
] }
|
] }
|
||||||
anyhow = { workspace = true }
|
anyhow = { workspace = true }
|
||||||
base64-simd = { workspace = true }
|
|
||||||
chrono = { workspace = true }
|
chrono = { workspace = true }
|
||||||
file-format = { workspace = true }
|
file-format = { workspace = true }
|
||||||
hex = { workspace = true }
|
hex = { workspace = true }
|
||||||
@@ -34,11 +33,7 @@ napi = { workspace = true, features = ["async", "serde-json"] }
|
|||||||
napi-derive = { workspace = true }
|
napi-derive = { workspace = true }
|
||||||
p256 = { workspace = true }
|
p256 = { workspace = true }
|
||||||
rand = { workspace = true }
|
rand = { workspace = true }
|
||||||
reqwest = { version = "0.13.3", default-features = false, features = [
|
safefetch = { workspace = true }
|
||||||
"blocking",
|
|
||||||
"rustls",
|
|
||||||
] }
|
|
||||||
rustls = "0.23"
|
|
||||||
schemars = { workspace = true }
|
schemars = { workspace = true }
|
||||||
serde = { workspace = true, features = ["derive"] }
|
serde = { workspace = true, features = ["derive"] }
|
||||||
serde_json = { workspace = true }
|
serde_json = { workspace = true }
|
||||||
@@ -47,7 +42,6 @@ sha3 = { workspace = true }
|
|||||||
tiktoken-rs = { workspace = true }
|
tiktoken-rs = { workspace = true }
|
||||||
url = { workspace = true }
|
url = { workspace = true }
|
||||||
v_htmlescape = { workspace = true }
|
v_htmlescape = { workspace = true }
|
||||||
webpki-roots = "1"
|
|
||||||
y-octo = { workspace = true, features = ["large_refs"] }
|
y-octo = { workspace = true, features = ["large_refs"] }
|
||||||
|
|
||||||
[target.'cfg(not(target_os = "linux"))'.dependencies]
|
[target.'cfg(not(target_os = "linux"))'.dependencies]
|
||||||
|
|||||||
Vendored
+70
-1
@@ -48,6 +48,8 @@ export interface ActionTrace {
|
|||||||
errorCode?: string
|
errorCode?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export declare function activateLicense(request: LicenseKeyRequest): Promise<LicenseResponse>
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Adds a document ID to the workspace root doc's meta.pages array.
|
* Adds a document ID to the workspace root doc's meta.pages array.
|
||||||
* This registers the document in the workspace so it appears in the UI.
|
* This registers the document in the workspace so it appears in the UI.
|
||||||
@@ -151,11 +153,17 @@ export interface CapabilityModelContract {
|
|||||||
capabilities: Array<CapabilityModelCapability>
|
capabilities: Array<CapabilityModelCapability>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export declare function checkLicenseHealth(request: LicenseHealthRequest): Promise<LicenseResponse>
|
||||||
|
|
||||||
export interface Chunk {
|
export interface Chunk {
|
||||||
index: number
|
index: number
|
||||||
content: string
|
content: string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface CommandResponse {
|
||||||
|
error?: LicenseError
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Converts markdown content to AFFiNE-compatible y-octo document binary.
|
* Converts markdown content to AFFiNE-compatible y-octo document binary.
|
||||||
*
|
*
|
||||||
@@ -169,6 +177,10 @@ export interface Chunk {
|
|||||||
*/
|
*/
|
||||||
export declare function createDocWithMarkdown(title: string, markdown: string, docId: string): Buffer
|
export declare function createDocWithMarkdown(title: string, markdown: string, docId: string): Buffer
|
||||||
|
|
||||||
|
export declare function createLicenseCustomerPortal(request: LicenseKeyRequest): Promise<PortalResponse>
|
||||||
|
|
||||||
|
export declare function deactivateLicense(request: LicenseKeyRequest): Promise<CommandResponse>
|
||||||
|
|
||||||
export declare function evaluatePermissionV1(input: any): any
|
export declare function evaluatePermissionV1(input: any): any
|
||||||
|
|
||||||
export declare function fetchRemoteAttachment(request: RemoteAttachmentFetchRequest): Promise<RemoteAttachmentFetchResponse>
|
export declare function fetchRemoteAttachment(request: RemoteAttachmentFetchRequest): Promise<RemoteAttachmentFetchResponse>
|
||||||
@@ -195,6 +207,43 @@ export declare function inferRemoteMimeType(request: RemoteMimeTypeRequest): Pro
|
|||||||
|
|
||||||
export declare function inspectImageForProxy(input: Buffer, options?: ImageInspectionOptions | undefined | null): ImageInspection
|
export declare function inspectImageForProxy(input: Buffer, options?: ImageInspectionOptions | undefined | null): ImageInspection
|
||||||
|
|
||||||
|
export interface LicenseError {
|
||||||
|
status: number
|
||||||
|
body: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface LicenseHealthRequest {
|
||||||
|
licenseKey: string
|
||||||
|
validateKey: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface LicenseInfo {
|
||||||
|
plan: string
|
||||||
|
recurring: string
|
||||||
|
quantity: number
|
||||||
|
expiresAt: number
|
||||||
|
validateKey: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface LicenseKeyRequest {
|
||||||
|
licenseKey: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface LicenseRecurringRequest {
|
||||||
|
licenseKey: string
|
||||||
|
recurring: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface LicenseResponse {
|
||||||
|
license?: LicenseInfo
|
||||||
|
error?: LicenseError
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface LicenseSeatsRequest {
|
||||||
|
licenseKey: string
|
||||||
|
seats: number
|
||||||
|
}
|
||||||
|
|
||||||
export declare function llmBuildCanonicalRequest(request: CanonicalChatRequestContract): LlmRequestContract
|
export declare function llmBuildCanonicalRequest(request: CanonicalChatRequestContract): LlmRequestContract
|
||||||
|
|
||||||
export declare function llmBuildCanonicalStructuredRequest(request: CanonicalStructuredRequestContract): LlmStructuredRequestContract
|
export declare function llmBuildCanonicalStructuredRequest(request: CanonicalStructuredRequestContract): LlmStructuredRequestContract
|
||||||
@@ -481,6 +530,11 @@ export declare function permissionActionRoleMatrixV1(): any
|
|||||||
|
|
||||||
export declare function permissionActionRoleMatrixV1Json(): string
|
export declare function permissionActionRoleMatrixV1Json(): string
|
||||||
|
|
||||||
|
export interface PortalResponse {
|
||||||
|
url?: string
|
||||||
|
error?: LicenseError
|
||||||
|
}
|
||||||
|
|
||||||
export declare function processImage(input: Buffer, maxEdge: number, keepExif: boolean): Promise<Buffer>
|
export declare function processImage(input: Buffer, maxEdge: number, keepExif: boolean): Promise<Buffer>
|
||||||
|
|
||||||
export type PromptBuiltin = 'Date'|
|
export type PromptBuiltin = 'Date'|
|
||||||
@@ -687,15 +741,26 @@ export declare function runNativeActionRecipePreparedStream(input: ActionRuntime
|
|||||||
export declare function safeFetch(request: SafeFetchRequest): Promise<SafeFetchResponse>
|
export declare function safeFetch(request: SafeFetchRequest): Promise<SafeFetchResponse>
|
||||||
|
|
||||||
export type SafeFetchMethod = 'get'|
|
export type SafeFetchMethod = 'get'|
|
||||||
'head';
|
'head'|
|
||||||
|
'post'|
|
||||||
|
'put'|
|
||||||
|
'propfind'|
|
||||||
|
'report';
|
||||||
|
|
||||||
export interface SafeFetchRequest {
|
export interface SafeFetchRequest {
|
||||||
url: string
|
url: string
|
||||||
method?: SafeFetchMethod
|
method?: SafeFetchMethod
|
||||||
headers?: Record<string, string>
|
headers?: Record<string, string>
|
||||||
|
body?: Buffer
|
||||||
timeoutMs?: number
|
timeoutMs?: number
|
||||||
maxRedirects?: number
|
maxRedirects?: number
|
||||||
maxBytes?: number
|
maxBytes?: number
|
||||||
|
allowedHeaders?: Array<string>
|
||||||
|
allowedHosts?: Array<string>
|
||||||
|
allowHttp?: boolean
|
||||||
|
allowPrivateTargetOrigin?: boolean
|
||||||
|
enableEch?: boolean
|
||||||
|
echConfigList?: Buffer
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface SafeFetchResponse {
|
export interface SafeFetchResponse {
|
||||||
@@ -754,6 +819,10 @@ export declare function updateDocTitle(existingBinary: Buffer, title: string, do
|
|||||||
*/
|
*/
|
||||||
export declare function updateDocWithMarkdown(existingBinary: Buffer, newMarkdown: string, docId: string): Buffer
|
export declare function updateDocWithMarkdown(existingBinary: Buffer, newMarkdown: string, docId: string): Buffer
|
||||||
|
|
||||||
|
export declare function updateLicenseRecurring(request: LicenseRecurringRequest): Promise<CommandResponse>
|
||||||
|
|
||||||
|
export declare function updateLicenseSeats(request: LicenseSeatsRequest): Promise<CommandResponse>
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Updates a document title in the workspace root doc's meta.pages array.
|
* Updates a document title in the workspace root doc's meta.pages array.
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ pub mod file_type;
|
|||||||
pub mod hashcash;
|
pub mod hashcash;
|
||||||
pub mod html_sanitize;
|
pub mod html_sanitize;
|
||||||
pub mod image;
|
pub mod image;
|
||||||
|
pub mod license;
|
||||||
pub mod llm;
|
pub mod llm;
|
||||||
pub mod permission;
|
pub mod permission;
|
||||||
pub mod safe_fetch;
|
pub mod safe_fetch;
|
||||||
|
|||||||
@@ -0,0 +1,501 @@
|
|||||||
|
use std::{
|
||||||
|
collections::HashMap,
|
||||||
|
sync::{Mutex, OnceLock},
|
||||||
|
time::{Duration, SystemTime, UNIX_EPOCH},
|
||||||
|
};
|
||||||
|
|
||||||
|
use anyhow::{Context, Result as AnyResult, bail};
|
||||||
|
use napi::{Env, Error, Result, Status, Task, bindgen_prelude::AsyncTask};
|
||||||
|
use napi_derive::napi;
|
||||||
|
use serde::de::DeserializeOwned;
|
||||||
|
use url::Url;
|
||||||
|
|
||||||
|
const AFFINE_PRO_ENDPOINT: &str = "https://app.affine.pro";
|
||||||
|
const AFFINE_PRO_HOST: &str = "app.affine.pro";
|
||||||
|
const AFFINE_PRO_REQUEST_TIMEOUT_MS: u32 = 10_000;
|
||||||
|
const AFFINE_PRO_MAX_BYTES: u32 = 1024 * 1024;
|
||||||
|
const ECH_DNS_QUERY_TIMEOUT_MS: u32 = 5_000;
|
||||||
|
|
||||||
|
static AFFINE_PRO_ECH_CONFIG: OnceLock<Mutex<Option<Vec<u8>>>> = OnceLock::new();
|
||||||
|
|
||||||
|
#[napi(object)]
|
||||||
|
pub struct LicenseKeyRequest {
|
||||||
|
pub license_key: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[napi(object)]
|
||||||
|
pub struct LicenseHealthRequest {
|
||||||
|
pub license_key: String,
|
||||||
|
pub validate_key: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[napi(object)]
|
||||||
|
pub struct LicenseRecurringRequest {
|
||||||
|
pub license_key: String,
|
||||||
|
pub recurring: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[napi(object)]
|
||||||
|
pub struct LicenseSeatsRequest {
|
||||||
|
pub license_key: String,
|
||||||
|
pub seats: u32,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[napi(object)]
|
||||||
|
pub struct LicenseInfo {
|
||||||
|
pub plan: String,
|
||||||
|
pub recurring: String,
|
||||||
|
pub quantity: u32,
|
||||||
|
pub expires_at: f64,
|
||||||
|
pub validate_key: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[napi(object)]
|
||||||
|
pub struct LicenseError {
|
||||||
|
pub status: u16,
|
||||||
|
pub body: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[napi(object)]
|
||||||
|
pub struct LicenseResponse {
|
||||||
|
pub license: Option<LicenseInfo>,
|
||||||
|
pub error: Option<LicenseError>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[napi(object)]
|
||||||
|
pub struct CommandResponse {
|
||||||
|
pub error: Option<LicenseError>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[napi(object)]
|
||||||
|
pub struct PortalResponse {
|
||||||
|
pub url: Option<String>,
|
||||||
|
pub error: Option<LicenseError>,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct AsyncActivateLicenseTask {
|
||||||
|
request: LicenseKeyRequest,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct AsyncDeactivateLicenseTask {
|
||||||
|
request: LicenseKeyRequest,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct AsyncCheckLicenseHealthTask {
|
||||||
|
request: LicenseHealthRequest,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct AsyncUpdateLicenseRecurringTask {
|
||||||
|
request: LicenseRecurringRequest,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct AsyncUpdateLicenseSeatsTask {
|
||||||
|
request: LicenseSeatsRequest,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct AsyncCreateCustomerPortalTask {
|
||||||
|
request: LicenseKeyRequest,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[napi]
|
||||||
|
impl Task for AsyncActivateLicenseTask {
|
||||||
|
type Output = LicenseResponse;
|
||||||
|
type JsValue = LicenseResponse;
|
||||||
|
|
||||||
|
fn compute(&mut self) -> Result<Self::Output> {
|
||||||
|
license_info(
|
||||||
|
&format!("/api/team/licenses/{}/activate", self.request.license_key),
|
||||||
|
safefetch::SafeFetchMethod::Post,
|
||||||
|
None,
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
.map_err(invalid_arg)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn resolve(&mut self, _: Env, output: Self::Output) -> Result<Self::JsValue> {
|
||||||
|
Ok(output)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[napi]
|
||||||
|
pub fn activate_license(request: LicenseKeyRequest) -> AsyncTask<AsyncActivateLicenseTask> {
|
||||||
|
AsyncTask::new(AsyncActivateLicenseTask { request })
|
||||||
|
}
|
||||||
|
|
||||||
|
#[napi]
|
||||||
|
impl Task for AsyncDeactivateLicenseTask {
|
||||||
|
type Output = CommandResponse;
|
||||||
|
type JsValue = CommandResponse;
|
||||||
|
|
||||||
|
fn compute(&mut self) -> Result<Self::Output> {
|
||||||
|
command(
|
||||||
|
&format!("/api/team/licenses/{}/deactivate", self.request.license_key),
|
||||||
|
safefetch::SafeFetchMethod::Post,
|
||||||
|
None,
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
.map_err(invalid_arg)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn resolve(&mut self, _: Env, output: Self::Output) -> Result<Self::JsValue> {
|
||||||
|
Ok(output)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[napi]
|
||||||
|
pub fn deactivate_license(request: LicenseKeyRequest) -> AsyncTask<AsyncDeactivateLicenseTask> {
|
||||||
|
AsyncTask::new(AsyncDeactivateLicenseTask { request })
|
||||||
|
}
|
||||||
|
|
||||||
|
#[napi]
|
||||||
|
impl Task for AsyncCheckLicenseHealthTask {
|
||||||
|
type Output = LicenseResponse;
|
||||||
|
type JsValue = LicenseResponse;
|
||||||
|
|
||||||
|
fn compute(&mut self) -> Result<Self::Output> {
|
||||||
|
license_info(
|
||||||
|
&format!("/api/team/licenses/{}/health", self.request.license_key),
|
||||||
|
safefetch::SafeFetchMethod::Get,
|
||||||
|
Some(HashMap::from([(
|
||||||
|
"x-validate-key".to_string(),
|
||||||
|
self.request.validate_key.clone(),
|
||||||
|
)])),
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
.map_err(invalid_arg)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn resolve(&mut self, _: Env, output: Self::Output) -> Result<Self::JsValue> {
|
||||||
|
Ok(output)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[napi]
|
||||||
|
pub fn check_license_health(request: LicenseHealthRequest) -> AsyncTask<AsyncCheckLicenseHealthTask> {
|
||||||
|
AsyncTask::new(AsyncCheckLicenseHealthTask { request })
|
||||||
|
}
|
||||||
|
|
||||||
|
#[napi]
|
||||||
|
impl Task for AsyncUpdateLicenseRecurringTask {
|
||||||
|
type Output = CommandResponse;
|
||||||
|
type JsValue = CommandResponse;
|
||||||
|
|
||||||
|
fn compute(&mut self) -> Result<Self::Output> {
|
||||||
|
let body = serde_json::to_vec(&serde_json::json!({
|
||||||
|
"recurring": self.request.recurring,
|
||||||
|
}))
|
||||||
|
.map_err(invalid_arg)?;
|
||||||
|
command(
|
||||||
|
&format!("/api/team/licenses/{}/recurring", self.request.license_key),
|
||||||
|
safefetch::SafeFetchMethod::Post,
|
||||||
|
None,
|
||||||
|
Some(body),
|
||||||
|
)
|
||||||
|
.map_err(invalid_arg)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn resolve(&mut self, _: Env, output: Self::Output) -> Result<Self::JsValue> {
|
||||||
|
Ok(output)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[napi]
|
||||||
|
pub fn update_license_recurring(request: LicenseRecurringRequest) -> AsyncTask<AsyncUpdateLicenseRecurringTask> {
|
||||||
|
AsyncTask::new(AsyncUpdateLicenseRecurringTask { request })
|
||||||
|
}
|
||||||
|
|
||||||
|
#[napi]
|
||||||
|
impl Task for AsyncUpdateLicenseSeatsTask {
|
||||||
|
type Output = CommandResponse;
|
||||||
|
type JsValue = CommandResponse;
|
||||||
|
|
||||||
|
fn compute(&mut self) -> Result<Self::Output> {
|
||||||
|
let body = serde_json::to_vec(&serde_json::json!({
|
||||||
|
"seats": self.request.seats,
|
||||||
|
}))
|
||||||
|
.map_err(invalid_arg)?;
|
||||||
|
command(
|
||||||
|
&format!("/api/team/licenses/{}/seats", self.request.license_key),
|
||||||
|
safefetch::SafeFetchMethod::Post,
|
||||||
|
None,
|
||||||
|
Some(body),
|
||||||
|
)
|
||||||
|
.map_err(invalid_arg)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn resolve(&mut self, _: Env, output: Self::Output) -> Result<Self::JsValue> {
|
||||||
|
Ok(output)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[napi]
|
||||||
|
pub fn update_license_seats(request: LicenseSeatsRequest) -> AsyncTask<AsyncUpdateLicenseSeatsTask> {
|
||||||
|
AsyncTask::new(AsyncUpdateLicenseSeatsTask { request })
|
||||||
|
}
|
||||||
|
|
||||||
|
#[napi]
|
||||||
|
impl Task for AsyncCreateCustomerPortalTask {
|
||||||
|
type Output = PortalResponse;
|
||||||
|
type JsValue = PortalResponse;
|
||||||
|
|
||||||
|
fn compute(&mut self) -> Result<Self::Output> {
|
||||||
|
let response = match affine_pro_request(
|
||||||
|
&format!("/api/team/licenses/{}/create-customer-portal", self.request.license_key),
|
||||||
|
safefetch::SafeFetchMethod::Post,
|
||||||
|
None,
|
||||||
|
None,
|
||||||
|
) {
|
||||||
|
Ok(response) => response,
|
||||||
|
Err(_) => {
|
||||||
|
return Ok(PortalResponse {
|
||||||
|
url: None,
|
||||||
|
error: Some(internal_affine_pro_error()),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
if let Some(error) = affine_pro_error(&response) {
|
||||||
|
return Ok(PortalResponse {
|
||||||
|
url: None,
|
||||||
|
error: Some(error),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
let body: PortalPayload = match parse_body(&response) {
|
||||||
|
Ok(body) => body,
|
||||||
|
Err(_) => {
|
||||||
|
return Ok(PortalResponse {
|
||||||
|
url: None,
|
||||||
|
error: Some(internal_affine_pro_error()),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
if body.url.is_empty() {
|
||||||
|
return Ok(PortalResponse {
|
||||||
|
url: None,
|
||||||
|
error: Some(internal_affine_pro_error()),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
Ok(PortalResponse {
|
||||||
|
url: Some(body.url),
|
||||||
|
error: None,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn resolve(&mut self, _: Env, output: Self::Output) -> Result<Self::JsValue> {
|
||||||
|
Ok(output)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[napi]
|
||||||
|
pub fn create_license_customer_portal(request: LicenseKeyRequest) -> AsyncTask<AsyncCreateCustomerPortalTask> {
|
||||||
|
AsyncTask::new(AsyncCreateCustomerPortalTask { request })
|
||||||
|
}
|
||||||
|
|
||||||
|
fn license_info(
|
||||||
|
path: &str,
|
||||||
|
method: safefetch::SafeFetchMethod,
|
||||||
|
headers: Option<HashMap<String, String>>,
|
||||||
|
body: Option<Vec<u8>>,
|
||||||
|
) -> AnyResult<LicenseResponse> {
|
||||||
|
let response = match affine_pro_request(path, method, headers, body) {
|
||||||
|
Ok(response) => response,
|
||||||
|
Err(_) => {
|
||||||
|
return Ok(LicenseResponse {
|
||||||
|
license: None,
|
||||||
|
error: Some(internal_affine_pro_error()),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
if let Some(error) = affine_pro_error(&response) {
|
||||||
|
return Ok(LicenseResponse {
|
||||||
|
license: None,
|
||||||
|
error: Some(error),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
let license = match parse_license_info(&response) {
|
||||||
|
Ok(license) => license,
|
||||||
|
Err(error) if error.to_string() == "license_expired" => {
|
||||||
|
return Ok(LicenseResponse {
|
||||||
|
license: None,
|
||||||
|
error: Some(license_expired_error()),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
Err(_) => {
|
||||||
|
return Ok(LicenseResponse {
|
||||||
|
license: None,
|
||||||
|
error: Some(internal_affine_pro_error()),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
Ok(LicenseResponse {
|
||||||
|
license: Some(license),
|
||||||
|
error: None,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn command(
|
||||||
|
path: &str,
|
||||||
|
method: safefetch::SafeFetchMethod,
|
||||||
|
headers: Option<HashMap<String, String>>,
|
||||||
|
body: Option<Vec<u8>>,
|
||||||
|
) -> AnyResult<CommandResponse> {
|
||||||
|
let response = match affine_pro_request(path, method, headers, body) {
|
||||||
|
Ok(response) => response,
|
||||||
|
Err(_) => {
|
||||||
|
return Ok(CommandResponse {
|
||||||
|
error: Some(internal_affine_pro_error()),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
Ok(CommandResponse {
|
||||||
|
error: affine_pro_error(&response),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn affine_pro_request(
|
||||||
|
path: &str,
|
||||||
|
method: safefetch::SafeFetchMethod,
|
||||||
|
headers: Option<HashMap<String, String>>,
|
||||||
|
body: Option<Vec<u8>>,
|
||||||
|
) -> AnyResult<safefetch::SafeFetchResponse> {
|
||||||
|
let url = Url::parse(AFFINE_PRO_ENDPOINT)
|
||||||
|
.context("invalid affine pro endpoint")?
|
||||||
|
.join(path)
|
||||||
|
.context("invalid affine pro path")?;
|
||||||
|
let mut headers = headers.unwrap_or_default();
|
||||||
|
headers.insert("Content-Type".to_string(), "application/json".to_string());
|
||||||
|
|
||||||
|
safefetch::safe_fetch(&safefetch::SafeFetchRequest {
|
||||||
|
url: url.to_string(),
|
||||||
|
method: Some(method),
|
||||||
|
headers: Some(headers),
|
||||||
|
body,
|
||||||
|
timeout_ms: Some(AFFINE_PRO_REQUEST_TIMEOUT_MS),
|
||||||
|
max_redirects: Some(3),
|
||||||
|
max_bytes: Some(AFFINE_PRO_MAX_BYTES),
|
||||||
|
allowed_headers: Some(vec![
|
||||||
|
"authorization".to_string(),
|
||||||
|
"content-type".to_string(),
|
||||||
|
"x-validate-key".to_string(),
|
||||||
|
]),
|
||||||
|
allowed_hosts: Some(vec![AFFINE_PRO_HOST.to_string()]),
|
||||||
|
allow_http: Some(false),
|
||||||
|
allow_private_target_origin: None,
|
||||||
|
ech_config_list: Some(affine_pro_ech_config()?),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_license_info(response: &safefetch::SafeFetchResponse) -> AnyResult<LicenseInfo> {
|
||||||
|
let body: LicensePayload = parse_body(response)?;
|
||||||
|
let expires_at = parse_future_end_at(&body.end_at)?;
|
||||||
|
Ok(LicenseInfo {
|
||||||
|
plan: body.plan,
|
||||||
|
recurring: body.recurring,
|
||||||
|
quantity: body.quantity,
|
||||||
|
expires_at,
|
||||||
|
validate_key: response.headers.get("x-next-validate-key").cloned().unwrap_or_default(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn affine_pro_error(response: &safefetch::SafeFetchResponse) -> Option<LicenseError> {
|
||||||
|
if (200..300).contains(&response.status) {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
let body = String::from_utf8_lossy(&response.body).to_string();
|
||||||
|
if serde_json::from_str::<serde_json::Value>(&body).is_err() {
|
||||||
|
return Some(internal_affine_pro_error());
|
||||||
|
}
|
||||||
|
Some(LicenseError {
|
||||||
|
status: response.status,
|
||||||
|
body,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn internal_affine_pro_error() -> LicenseError {
|
||||||
|
LicenseError {
|
||||||
|
status: 500,
|
||||||
|
body: serde_json::json!({
|
||||||
|
"status": 500,
|
||||||
|
"type": "internal_server_error",
|
||||||
|
"name": "internal_server_error",
|
||||||
|
"message": "Failed to contact with https://app.affine.pro",
|
||||||
|
"data": null,
|
||||||
|
})
|
||||||
|
.to_string(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn license_expired_error() -> LicenseError {
|
||||||
|
LicenseError {
|
||||||
|
status: 400,
|
||||||
|
body: serde_json::json!({
|
||||||
|
"status": 400,
|
||||||
|
"type": "bad_request",
|
||||||
|
"name": "license_expired",
|
||||||
|
"message": "License has expired.",
|
||||||
|
"data": null,
|
||||||
|
})
|
||||||
|
.to_string(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_body<T: DeserializeOwned>(response: &safefetch::SafeFetchResponse) -> AnyResult<T> {
|
||||||
|
serde_json::from_slice(&response.body).context("invalid affine pro response")
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_future_end_at(value: &serde_json::Value) -> AnyResult<f64> {
|
||||||
|
let millis = match value {
|
||||||
|
serde_json::Value::Number(number) => number.as_f64().context("invalid license expiration")?,
|
||||||
|
serde_json::Value::String(value) => value
|
||||||
|
.parse::<f64>()
|
||||||
|
.or_else(|_| chrono::DateTime::parse_from_rfc3339(value).map(|date| date.timestamp_millis() as f64))
|
||||||
|
.context("invalid license expiration")?,
|
||||||
|
_ => bail!("invalid license expiration"),
|
||||||
|
};
|
||||||
|
if !millis.is_finite() || millis <= now_millis() {
|
||||||
|
bail!("license_expired");
|
||||||
|
}
|
||||||
|
Ok(millis)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn now_millis() -> f64 {
|
||||||
|
SystemTime::now()
|
||||||
|
.duration_since(UNIX_EPOCH)
|
||||||
|
.unwrap_or_default()
|
||||||
|
.as_millis() as f64
|
||||||
|
}
|
||||||
|
|
||||||
|
fn affine_pro_ech_config() -> AnyResult<Vec<u8>> {
|
||||||
|
let cache = AFFINE_PRO_ECH_CONFIG.get_or_init(|| Mutex::new(None));
|
||||||
|
{
|
||||||
|
let cached = cache.lock().map_err(|_| anyhow::anyhow!("ech cache poisoned"))?;
|
||||||
|
if let Some(config) = cached.as_ref() {
|
||||||
|
return Ok(config.clone());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let config = safefetch::ech::cloudflare_https_ech_config_list(
|
||||||
|
AFFINE_PRO_HOST,
|
||||||
|
Duration::from_millis(ECH_DNS_QUERY_TIMEOUT_MS as u64),
|
||||||
|
)?;
|
||||||
|
let mut cached = cache.lock().map_err(|_| anyhow::anyhow!("ech cache poisoned"))?;
|
||||||
|
*cached = Some(config.clone());
|
||||||
|
Ok(config)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn invalid_arg(error: impl ToString) -> Error {
|
||||||
|
Error::new(Status::InvalidArg, error.to_string())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(serde::Deserialize)]
|
||||||
|
struct LicensePayload {
|
||||||
|
plan: String,
|
||||||
|
recurring: String,
|
||||||
|
quantity: u32,
|
||||||
|
#[serde(rename = "endAt")]
|
||||||
|
end_at: serde_json::Value,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(serde::Deserialize)]
|
||||||
|
struct PortalPayload {
|
||||||
|
url: String,
|
||||||
|
}
|
||||||
@@ -1,46 +1,37 @@
|
|||||||
use std::{
|
use std::collections::HashMap;
|
||||||
collections::HashMap,
|
|
||||||
io::{Cursor, Read},
|
|
||||||
net::{IpAddr, SocketAddr, ToSocketAddrs},
|
|
||||||
time::Duration,
|
|
||||||
};
|
|
||||||
|
|
||||||
use ::image::{ImageFormat, ImageReader};
|
|
||||||
use anyhow::{Context, Result as AnyResult, bail};
|
|
||||||
use napi::{
|
use napi::{
|
||||||
Env, Error, Result, Status, Task,
|
Env, Error, Result, Status, Task,
|
||||||
bindgen_prelude::{AsyncTask, Buffer},
|
bindgen_prelude::{AsyncTask, Buffer},
|
||||||
};
|
};
|
||||||
use napi_derive::napi;
|
use napi_derive::napi;
|
||||||
use reqwest::{
|
|
||||||
Method,
|
|
||||||
blocking::{Client, Response},
|
|
||||||
header::{HeaderMap, HeaderName, HeaderValue, LOCATION},
|
|
||||||
};
|
|
||||||
use url::Url;
|
|
||||||
|
|
||||||
const DEFAULT_TIMEOUT_MS: u32 = 10_000;
|
|
||||||
const DEFAULT_MAX_REDIRECTS: u32 = 3;
|
|
||||||
const DEFAULT_MAX_BYTES: u32 = 10 * 1024 * 1024;
|
|
||||||
const MAX_IMAGE_DIMENSION: u32 = 16_384;
|
|
||||||
const MAX_IMAGE_PIXELS: u64 = 40_000_000;
|
|
||||||
|
|
||||||
#[napi(string_enum = "snake_case")]
|
#[napi(string_enum = "snake_case")]
|
||||||
#[derive(Clone, Copy, Debug)]
|
#[derive(Clone, Copy, Debug)]
|
||||||
pub enum SafeFetchMethod {
|
pub enum SafeFetchMethod {
|
||||||
Get,
|
Get,
|
||||||
Head,
|
Head,
|
||||||
|
Post,
|
||||||
|
Put,
|
||||||
|
Propfind,
|
||||||
|
Report,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[napi(object)]
|
#[napi(object)]
|
||||||
#[derive(Clone, Debug)]
|
|
||||||
pub struct SafeFetchRequest {
|
pub struct SafeFetchRequest {
|
||||||
pub url: String,
|
pub url: String,
|
||||||
pub method: Option<SafeFetchMethod>,
|
pub method: Option<SafeFetchMethod>,
|
||||||
pub headers: Option<HashMap<String, String>>,
|
pub headers: Option<HashMap<String, String>>,
|
||||||
|
pub body: Option<Buffer>,
|
||||||
pub timeout_ms: Option<u32>,
|
pub timeout_ms: Option<u32>,
|
||||||
pub max_redirects: Option<u32>,
|
pub max_redirects: Option<u32>,
|
||||||
pub max_bytes: Option<u32>,
|
pub max_bytes: Option<u32>,
|
||||||
|
pub allowed_headers: Option<Vec<String>>,
|
||||||
|
pub allowed_hosts: Option<Vec<String>>,
|
||||||
|
pub allow_http: Option<bool>,
|
||||||
|
pub allow_private_target_origin: Option<bool>,
|
||||||
|
pub enable_ech: Option<bool>,
|
||||||
|
pub ech_config_list: Option<Buffer>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[napi(object)]
|
#[napi(object)]
|
||||||
@@ -111,36 +102,27 @@ pub struct AsyncRemoteMimeTypeTask {
|
|||||||
request: RemoteMimeTypeRequest,
|
request: RemoteMimeTypeRequest,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct SafeFetchOutput {
|
impl From<SafeFetchMethod> for safefetch::SafeFetchMethod {
|
||||||
status: u16,
|
fn from(method: SafeFetchMethod) -> Self {
|
||||||
final_url: String,
|
match method {
|
||||||
headers: HashMap<String, String>,
|
SafeFetchMethod::Get => safefetch::SafeFetchMethod::Get,
|
||||||
body: Vec<u8>,
|
SafeFetchMethod::Head => safefetch::SafeFetchMethod::Head,
|
||||||
|
SafeFetchMethod::Post => safefetch::SafeFetchMethod::Post,
|
||||||
|
SafeFetchMethod::Put => safefetch::SafeFetchMethod::Put,
|
||||||
|
SafeFetchMethod::Propfind => safefetch::SafeFetchMethod::Propfind,
|
||||||
|
SafeFetchMethod::Report => safefetch::SafeFetchMethod::Report,
|
||||||
}
|
}
|
||||||
|
|
||||||
struct SafeFetchParams {
|
|
||||||
url: String,
|
|
||||||
method: Option<SafeFetchMethod>,
|
|
||||||
headers: Option<HashMap<String, String>>,
|
|
||||||
timeout_ms: Option<u32>,
|
|
||||||
max_redirects: Option<u32>,
|
|
||||||
max_bytes: Option<u32>,
|
|
||||||
allow_private_origins: Option<Vec<String>>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct RemoteAttachmentFetchOutput {
|
|
||||||
final_url: String,
|
|
||||||
mime_type: String,
|
|
||||||
body: Vec<u8>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[napi]
|
#[napi]
|
||||||
impl Task for AsyncSafeFetchTask {
|
impl Task for AsyncSafeFetchTask {
|
||||||
type Output = SafeFetchOutput;
|
type Output = safefetch::SafeFetchResponse;
|
||||||
type JsValue = SafeFetchResponse;
|
type JsValue = SafeFetchResponse;
|
||||||
|
|
||||||
fn compute(&mut self) -> Result<Self::Output> {
|
fn compute(&mut self) -> Result<Self::Output> {
|
||||||
safe_fetch_inner(&self.request).map_err(|error| Error::new(Status::InvalidArg, error.to_string()))
|
let request = safe_fetch_request(&self.request).map_err(invalid_arg)?;
|
||||||
|
safefetch::safe_fetch(&request).map_err(invalid_arg)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn resolve(&mut self, _: Env, output: Self::Output) -> Result<Self::JsValue> {
|
fn resolve(&mut self, _: Env, output: Self::Output) -> Result<Self::JsValue> {
|
||||||
@@ -160,29 +142,44 @@ pub fn safe_fetch(request: SafeFetchRequest) -> AsyncTask<AsyncSafeFetchTask> {
|
|||||||
|
|
||||||
#[napi]
|
#[napi]
|
||||||
pub fn assert_safe_url(request: AssertSafeUrlRequest) -> Result<()> {
|
pub fn assert_safe_url(request: AssertSafeUrlRequest) -> Result<()> {
|
||||||
assert_safe_url_inner(&request).map_err(|error| Error::new(Status::InvalidArg, error.to_string()))
|
safefetch::assert_safe_url(&request.url).map_err(invalid_arg)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[napi]
|
#[napi]
|
||||||
pub fn inspect_image_for_proxy(input: Buffer, options: Option<ImageInspectionOptions>) -> Result<ImageInspection> {
|
pub fn inspect_image_for_proxy(input: Buffer, options: Option<ImageInspectionOptions>) -> Result<ImageInspection> {
|
||||||
inspect_image_for_proxy_inner(
|
let output = safefetch::inspect_image(
|
||||||
&input,
|
&input,
|
||||||
options.unwrap_or(ImageInspectionOptions {
|
image_inspection_options(options.unwrap_or(ImageInspectionOptions {
|
||||||
max_width: None,
|
max_width: None,
|
||||||
max_height: None,
|
max_height: None,
|
||||||
max_pixels: None,
|
max_pixels: None,
|
||||||
}),
|
})),
|
||||||
)
|
)
|
||||||
.map_err(|error| Error::new(Status::InvalidArg, error.to_string()))
|
.map_err(invalid_arg)?;
|
||||||
|
Ok(ImageInspection {
|
||||||
|
mime_type: output.mime_type,
|
||||||
|
width: output.width,
|
||||||
|
height: output.height,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
#[napi]
|
#[napi]
|
||||||
impl Task for AsyncRemoteAttachmentFetchTask {
|
impl Task for AsyncRemoteAttachmentFetchTask {
|
||||||
type Output = RemoteAttachmentFetchOutput;
|
type Output = safefetch::RemoteAttachmentFetchResponse;
|
||||||
type JsValue = RemoteAttachmentFetchResponse;
|
type JsValue = RemoteAttachmentFetchResponse;
|
||||||
|
|
||||||
fn compute(&mut self) -> Result<Self::Output> {
|
fn compute(&mut self) -> Result<Self::Output> {
|
||||||
fetch_remote_attachment_inner(&self.request).map_err(|error| Error::new(Status::InvalidArg, error.to_string()))
|
safefetch::fetch_remote_attachment(&safefetch::RemoteAttachmentFetchRequest {
|
||||||
|
url: self.request.url.clone(),
|
||||||
|
timeout_ms: self.request.timeout_ms,
|
||||||
|
max_bytes: self.request.max_bytes,
|
||||||
|
allow_private_target_origin: self.request.allow_private_target_origin,
|
||||||
|
expected_content_type_prefix: self.request.expected_content_type_prefix.clone(),
|
||||||
|
max_image_width: self.request.max_image_width,
|
||||||
|
max_image_height: self.request.max_image_height,
|
||||||
|
max_image_pixels: self.request.max_image_pixels,
|
||||||
|
})
|
||||||
|
.map_err(invalid_arg)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn resolve(&mut self, _: Env, output: Self::Output) -> Result<Self::JsValue> {
|
fn resolve(&mut self, _: Env, output: Self::Output) -> Result<Self::JsValue> {
|
||||||
@@ -205,7 +202,10 @@ impl Task for AsyncRemoteMimeTypeTask {
|
|||||||
type JsValue = String;
|
type JsValue = String;
|
||||||
|
|
||||||
fn compute(&mut self) -> Result<Self::Output> {
|
fn compute(&mut self) -> Result<Self::Output> {
|
||||||
Ok(infer_remote_mime_type_inner(&self.request))
|
Ok(safefetch::infer_remote_mime_type(&safefetch::RemoteMimeTypeRequest {
|
||||||
|
url: self.request.url.clone(),
|
||||||
|
timeout_ms: self.request.timeout_ms,
|
||||||
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn resolve(&mut self, _: Env, output: Self::Output) -> Result<Self::JsValue> {
|
fn resolve(&mut self, _: Env, output: Self::Output) -> Result<Self::JsValue> {
|
||||||
@@ -218,472 +218,41 @@ pub fn infer_remote_mime_type(request: RemoteMimeTypeRequest) -> AsyncTask<Async
|
|||||||
AsyncTask::new(AsyncRemoteMimeTypeTask { request })
|
AsyncTask::new(AsyncRemoteMimeTypeTask { request })
|
||||||
}
|
}
|
||||||
|
|
||||||
fn safe_fetch_inner(request: &SafeFetchRequest) -> AnyResult<SafeFetchOutput> {
|
pub(crate) fn safe_fetch_request(request: &SafeFetchRequest) -> anyhow::Result<safefetch::SafeFetchRequest> {
|
||||||
safe_fetch_params_inner(&SafeFetchParams {
|
Ok(safefetch::SafeFetchRequest {
|
||||||
url: request.url.clone(),
|
url: request.url.clone(),
|
||||||
method: request.method,
|
method: request.method.map(Into::into),
|
||||||
headers: request.headers.clone(),
|
headers: request.headers.clone(),
|
||||||
|
body: request.body.as_ref().map(|body| body.to_vec()),
|
||||||
timeout_ms: request.timeout_ms,
|
timeout_ms: request.timeout_ms,
|
||||||
max_redirects: request.max_redirects,
|
max_redirects: request.max_redirects,
|
||||||
max_bytes: request.max_bytes,
|
max_bytes: request.max_bytes,
|
||||||
allow_private_origins: None,
|
allowed_headers: request.allowed_headers.clone(),
|
||||||
|
allowed_hosts: request.allowed_hosts.clone(),
|
||||||
|
allow_http: request.allow_http,
|
||||||
|
allow_private_target_origin: request.allow_private_target_origin,
|
||||||
|
ech_config_list: ech_config_list(request)?,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fn safe_fetch_params_inner(request: &SafeFetchParams) -> AnyResult<SafeFetchOutput> {
|
fn image_inspection_options(options: ImageInspectionOptions) -> safefetch::ImageInspectionOptions {
|
||||||
let timeout = Duration::from_millis(u64::from(request.timeout_ms.unwrap_or(DEFAULT_TIMEOUT_MS)));
|
safefetch::ImageInspectionOptions {
|
||||||
let max_redirects = request.max_redirects.unwrap_or(DEFAULT_MAX_REDIRECTS);
|
max_width: options.max_width,
|
||||||
let max_bytes = usize::try_from(request.max_bytes.unwrap_or(DEFAULT_MAX_BYTES)).context("invalid maxBytes")?;
|
max_height: options.max_height,
|
||||||
let method = request.method.unwrap_or(SafeFetchMethod::Get);
|
max_pixels: options.max_pixels,
|
||||||
let mut current = parse_safe_url(&request.url)?;
|
|
||||||
let headers = build_headers(request.headers.as_ref())?;
|
|
||||||
|
|
||||||
for redirect_count in 0..=max_redirects {
|
|
||||||
let addrs = resolve_safe_socket_addrs(¤t, request.allow_private_origins.as_deref())?;
|
|
||||||
let client = build_pinned_client(¤t, &addrs, timeout)?;
|
|
||||||
let response = send_request(&client, method, current.clone(), headers.clone())?;
|
|
||||||
|
|
||||||
if response.status().is_redirection() {
|
|
||||||
if redirect_count >= max_redirects {
|
|
||||||
bail!("too_many_redirects");
|
|
||||||
}
|
|
||||||
let Some(location) = response.headers().get(LOCATION) else {
|
|
||||||
return response_to_output(response, current, max_bytes, method);
|
|
||||||
};
|
|
||||||
let location = location.to_str().context("invalid redirect location")?;
|
|
||||||
current = parse_safe_url(current.join(location).context("invalid redirect location")?.as_str())?;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
return response_to_output(response, current, max_bytes, method);
|
|
||||||
}
|
|
||||||
|
|
||||||
bail!("too_many_redirects")
|
|
||||||
}
|
|
||||||
|
|
||||||
fn fetch_remote_attachment_inner(request: &RemoteAttachmentFetchRequest) -> AnyResult<RemoteAttachmentFetchOutput> {
|
|
||||||
let allow_private_origins =
|
|
||||||
private_target_origin_allowlist(&request.url, request.allow_private_target_origin.unwrap_or(false))?;
|
|
||||||
let response = safe_fetch_params_inner(&SafeFetchParams {
|
|
||||||
url: request.url.clone(),
|
|
||||||
method: Some(SafeFetchMethod::Get),
|
|
||||||
headers: None,
|
|
||||||
timeout_ms: request.timeout_ms,
|
|
||||||
max_redirects: Some(DEFAULT_MAX_REDIRECTS),
|
|
||||||
max_bytes: Some(request.max_bytes),
|
|
||||||
allow_private_origins,
|
|
||||||
})?;
|
|
||||||
if !(200..300).contains(&response.status) {
|
|
||||||
bail!("fetch_failed_status: {}", response.status);
|
|
||||||
}
|
|
||||||
let mime_type = normalize_mime_type(response.headers.get("content-type"));
|
|
||||||
if let Some(expected) = request.expected_content_type_prefix.as_deref() {
|
|
||||||
if !mime_type.starts_with(expected) {
|
|
||||||
bail!("content_type_mismatch");
|
|
||||||
}
|
|
||||||
if expected.starts_with("image/") {
|
|
||||||
inspect_image_for_proxy_inner(
|
|
||||||
&response.body,
|
|
||||||
ImageInspectionOptions {
|
|
||||||
max_width: request.max_image_width,
|
|
||||||
max_height: request.max_image_height,
|
|
||||||
max_pixels: request.max_image_pixels,
|
|
||||||
},
|
|
||||||
)?;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(RemoteAttachmentFetchOutput {
|
fn ech_config_list(request: &SafeFetchRequest) -> anyhow::Result<Option<Vec<u8>>> {
|
||||||
final_url: response.final_url,
|
if !request.enable_ech.unwrap_or(false) {
|
||||||
mime_type,
|
|
||||||
body: response.body,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
fn infer_remote_mime_type_inner(request: &RemoteMimeTypeRequest) -> String {
|
|
||||||
let Ok(url) = Url::parse(&request.url) else {
|
|
||||||
return "application/octet-stream".to_string();
|
|
||||||
};
|
|
||||||
if let Some(mime_type) = infer_mime_type_from_extension(&url) {
|
|
||||||
return mime_type.to_string();
|
|
||||||
}
|
|
||||||
let Ok(response) = safe_fetch_params_inner(&SafeFetchParams {
|
|
||||||
url: request.url.clone(),
|
|
||||||
method: Some(SafeFetchMethod::Head),
|
|
||||||
headers: None,
|
|
||||||
timeout_ms: request.timeout_ms,
|
|
||||||
max_redirects: Some(DEFAULT_MAX_REDIRECTS),
|
|
||||||
max_bytes: Some(0),
|
|
||||||
allow_private_origins: None,
|
|
||||||
}) else {
|
|
||||||
return "application/octet-stream".to_string();
|
|
||||||
};
|
|
||||||
normalize_mime_type(response.headers.get("content-type"))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn private_target_origin_allowlist(raw_url: &str, allow_private_target_origin: bool) -> AnyResult<Option<Vec<String>>> {
|
|
||||||
if !allow_private_target_origin {
|
|
||||||
return Ok(None);
|
return Ok(None);
|
||||||
}
|
}
|
||||||
Ok(Some(vec![parse_safe_url(raw_url)?.origin().ascii_serialization()]))
|
let Some(config_list) = request.ech_config_list.as_ref() else {
|
||||||
}
|
anyhow::bail!("ech_config_required");
|
||||||
|
|
||||||
fn normalize_mime_type(value: Option<&String>) -> String {
|
|
||||||
value
|
|
||||||
.and_then(|value| value.split(';').next())
|
|
||||||
.map(str::trim)
|
|
||||||
.filter(|value| !value.is_empty())
|
|
||||||
.unwrap_or("application/octet-stream")
|
|
||||||
.to_string()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn infer_mime_type_from_extension(url: &Url) -> Option<&'static str> {
|
|
||||||
let extension = url.path_segments()?.next_back()?.rsplit_once('.')?.1;
|
|
||||||
match extension.to_ascii_lowercase().as_str() {
|
|
||||||
"pdf" => Some("application/pdf"),
|
|
||||||
"mp3" => Some("audio/mpeg"),
|
|
||||||
"opus" => Some("audio/opus"),
|
|
||||||
"ogg" => Some("audio/ogg"),
|
|
||||||
"aac" => Some("audio/aac"),
|
|
||||||
"m4a" => Some("audio/aac"),
|
|
||||||
"flac" => Some("audio/flac"),
|
|
||||||
"ogv" => Some("video/ogg"),
|
|
||||||
"wav" => Some("audio/wav"),
|
|
||||||
"png" => Some("image/png"),
|
|
||||||
"jpeg" | "jpg" => Some("image/jpeg"),
|
|
||||||
"webp" => Some("image/webp"),
|
|
||||||
"txt" | "md" => Some("text/plain"),
|
|
||||||
"mov" => Some("video/mov"),
|
|
||||||
"mpeg" => Some("video/mpeg"),
|
|
||||||
"mp4" => Some("video/mp4"),
|
|
||||||
"avi" => Some("video/avi"),
|
|
||||||
"wmv" => Some("video/wmv"),
|
|
||||||
"flv" => Some("video/flv"),
|
|
||||||
_ => None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn assert_safe_url_inner(request: &AssertSafeUrlRequest) -> AnyResult<()> {
|
|
||||||
let url = parse_safe_url(&request.url)?;
|
|
||||||
resolve_safe_socket_addrs(&url, None)?;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn parse_safe_url(raw: &str) -> AnyResult<Url> {
|
|
||||||
let url = Url::parse(raw).context("invalid_url")?;
|
|
||||||
match url.scheme() {
|
|
||||||
"http" | "https" => {}
|
|
||||||
_ => bail!("disallowed_protocol"),
|
|
||||||
}
|
|
||||||
if !url.username().is_empty() || url.password().is_some() {
|
|
||||||
bail!("url_has_credentials");
|
|
||||||
}
|
|
||||||
if url.host_str().is_none() {
|
|
||||||
bail!("blocked_hostname");
|
|
||||||
}
|
|
||||||
Ok(url)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn resolve_safe_socket_addrs(url: &Url, allow_private_origins: Option<&[String]>) -> AnyResult<Vec<SocketAddr>> {
|
|
||||||
let host = url.host_str().context("blocked_hostname")?;
|
|
||||||
let port = url.port_or_known_default().context("blocked_hostname")?;
|
|
||||||
let origin = url.origin().ascii_serialization();
|
|
||||||
let allow_private = allow_private_origins
|
|
||||||
.map(|origins| origins.iter().any(|allowed| allowed == &origin))
|
|
||||||
.unwrap_or(false);
|
|
||||||
let addrs: Vec<SocketAddr> = (host, port)
|
|
||||||
.to_socket_addrs()
|
|
||||||
.context("unresolvable_hostname")?
|
|
||||||
.collect();
|
|
||||||
if addrs.is_empty() {
|
|
||||||
bail!("unresolvable_hostname");
|
|
||||||
}
|
|
||||||
for addr in &addrs {
|
|
||||||
if is_blocked_ip(addr.ip()) && !allow_private {
|
|
||||||
bail!("blocked_ip");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Ok(addrs)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn build_pinned_client(url: &Url, addrs: &[SocketAddr], timeout: Duration) -> AnyResult<Client> {
|
|
||||||
let host = url.host_str().context("blocked_hostname")?;
|
|
||||||
Client::builder()
|
|
||||||
.timeout(timeout)
|
|
||||||
.no_proxy()
|
|
||||||
.redirect(reqwest::redirect::Policy::none())
|
|
||||||
.tls_backend_preconfigured(webpki_tls_config()?)
|
|
||||||
.resolve_to_addrs(host, addrs)
|
|
||||||
.build()
|
|
||||||
.context("failed to build http client")
|
|
||||||
}
|
|
||||||
|
|
||||||
fn webpki_tls_config() -> AnyResult<rustls::ClientConfig> {
|
|
||||||
let root_store = rustls::RootCertStore {
|
|
||||||
roots: webpki_roots::TLS_SERVER_ROOTS.to_vec(),
|
|
||||||
};
|
};
|
||||||
Ok(
|
Ok(Some(config_list.to_vec()))
|
||||||
rustls::ClientConfig::builder_with_provider(rustls::crypto::aws_lc_rs::default_provider().into())
|
|
||||||
.with_safe_default_protocol_versions()
|
|
||||||
.context("failed to build tls protocol config")?
|
|
||||||
.with_root_certificates(root_store)
|
|
||||||
.with_no_client_auth(),
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn build_headers(headers: Option<&HashMap<String, String>>) -> AnyResult<HeaderMap> {
|
fn invalid_arg(error: impl ToString) -> Error {
|
||||||
let mut out = HeaderMap::new();
|
Error::new(Status::InvalidArg, error.to_string())
|
||||||
let Some(headers) = headers else {
|
|
||||||
return Ok(out);
|
|
||||||
};
|
|
||||||
for (name, value) in headers {
|
|
||||||
let lower = name.to_ascii_lowercase();
|
|
||||||
if !(lower.starts_with("sec-") || lower.starts_with("accept") || lower == "user-agent") {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
out.insert(
|
|
||||||
HeaderName::from_bytes(name.as_bytes()).context("invalid header name")?,
|
|
||||||
HeaderValue::from_str(value).context("invalid header value")?,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
Ok(out)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn send_request(client: &Client, method: SafeFetchMethod, url: Url, headers: HeaderMap) -> AnyResult<Response> {
|
|
||||||
let method = match method {
|
|
||||||
SafeFetchMethod::Get => Method::GET,
|
|
||||||
SafeFetchMethod::Head => Method::HEAD,
|
|
||||||
};
|
|
||||||
client
|
|
||||||
.request(method, url)
|
|
||||||
.headers(headers)
|
|
||||||
.send()
|
|
||||||
.context("failed to fetch url")
|
|
||||||
}
|
|
||||||
|
|
||||||
fn response_to_output(
|
|
||||||
mut response: Response,
|
|
||||||
url: Url,
|
|
||||||
max_bytes: usize,
|
|
||||||
method: SafeFetchMethod,
|
|
||||||
) -> AnyResult<SafeFetchOutput> {
|
|
||||||
let status = response.status().as_u16();
|
|
||||||
let headers = response_headers(response.headers());
|
|
||||||
if matches!(method, SafeFetchMethod::Head) {
|
|
||||||
return Ok(SafeFetchOutput {
|
|
||||||
status,
|
|
||||||
final_url: url.to_string(),
|
|
||||||
headers,
|
|
||||||
body: Vec::new(),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
let mut body = Vec::new();
|
|
||||||
if let Some(len) = response.content_length()
|
|
||||||
&& len > max_bytes as u64
|
|
||||||
{
|
|
||||||
bail!("response_too_large");
|
|
||||||
}
|
|
||||||
response
|
|
||||||
.by_ref()
|
|
||||||
.take(u64::try_from(max_bytes).unwrap_or(u64::MAX) + 1)
|
|
||||||
.read_to_end(&mut body)
|
|
||||||
.context("failed to read response")?;
|
|
||||||
if body.len() > max_bytes {
|
|
||||||
bail!("response_too_large");
|
|
||||||
}
|
|
||||||
Ok(SafeFetchOutput {
|
|
||||||
status,
|
|
||||||
final_url: url.to_string(),
|
|
||||||
headers,
|
|
||||||
body,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
fn response_headers(headers: &HeaderMap) -> HashMap<String, String> {
|
|
||||||
headers
|
|
||||||
.iter()
|
|
||||||
.filter_map(|(name, value)| {
|
|
||||||
value
|
|
||||||
.to_str()
|
|
||||||
.ok()
|
|
||||||
.map(|value| (name.as_str().to_string(), value.to_string()))
|
|
||||||
})
|
|
||||||
.collect()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn is_blocked_ip(ip: IpAddr) -> bool {
|
|
||||||
match ip {
|
|
||||||
IpAddr::V4(ip) => {
|
|
||||||
ip.is_private()
|
|
||||||
|| ip.is_loopback()
|
|
||||||
|| ip.is_link_local()
|
|
||||||
|| ip.is_broadcast()
|
|
||||||
|| ip.is_multicast()
|
|
||||||
|| ip.is_documentation()
|
|
||||||
|| ip.octets()[0] == 0
|
|
||||||
|| ip.octets()[0] >= 224
|
|
||||||
|| ip.octets()[0] == 100 && (64..=127).contains(&ip.octets()[1])
|
|
||||||
|| ip.octets()[0] == 169 && ip.octets()[1] == 254
|
|
||||||
|| ip.octets()[0] == 198 && (18..=19).contains(&ip.octets()[1])
|
|
||||||
|| ip.octets()[0] == 192 && ip.octets()[1] == 0 && ip.octets()[2] == 0
|
|
||||||
}
|
|
||||||
IpAddr::V6(ip) => {
|
|
||||||
if let Some(v4) = ip.to_ipv4_mapped() {
|
|
||||||
return is_blocked_ip(IpAddr::V4(v4));
|
|
||||||
}
|
|
||||||
if let Some(v4) = extract_6to4_ipv4(ip).or_else(|| extract_teredo_client_ipv4(ip)) {
|
|
||||||
return is_blocked_ip(IpAddr::V4(v4));
|
|
||||||
}
|
|
||||||
(ip.segments()[0] & 0xe000 != 0x2000)
|
|
||||||
|| ip.is_loopback()
|
|
||||||
|| ip.is_unspecified()
|
|
||||||
|| ip.is_multicast()
|
|
||||||
|| (ip.segments()[0] & 0xfe00 == 0xfc00)
|
|
||||||
|| (ip.segments()[0] & 0xffc0 == 0xfe80)
|
|
||||||
|| (ip.segments()[0] == 0x2001 && ip.segments()[1] == 0x0db8)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn extract_6to4_ipv4(ip: std::net::Ipv6Addr) -> Option<std::net::Ipv4Addr> {
|
|
||||||
let segments = ip.segments();
|
|
||||||
if segments[0] != 0x2002 {
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
Some(std::net::Ipv4Addr::new(
|
|
||||||
(segments[1] >> 8) as u8,
|
|
||||||
segments[1] as u8,
|
|
||||||
(segments[2] >> 8) as u8,
|
|
||||||
segments[2] as u8,
|
|
||||||
))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn extract_teredo_client_ipv4(ip: std::net::Ipv6Addr) -> Option<std::net::Ipv4Addr> {
|
|
||||||
let segments = ip.segments();
|
|
||||||
if segments[0] != 0x2001 || segments[1] != 0 {
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
Some(std::net::Ipv4Addr::new(
|
|
||||||
(!(segments[6] >> 8)) as u8,
|
|
||||||
(!segments[6]) as u8,
|
|
||||||
(!(segments[7] >> 8)) as u8,
|
|
||||||
(!segments[7]) as u8,
|
|
||||||
))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn inspect_image_for_proxy_inner(input: &[u8], options: ImageInspectionOptions) -> AnyResult<ImageInspection> {
|
|
||||||
let inspection = parse_image_header(input).context("failed to decode image")?;
|
|
||||||
validate_image_dimensions(&inspection, options)?;
|
|
||||||
Ok(inspection)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn validate_image_dimensions(image: &ImageInspection, options: ImageInspectionOptions) -> AnyResult<()> {
|
|
||||||
let max_width = options.max_width.unwrap_or(MAX_IMAGE_DIMENSION);
|
|
||||||
let max_height = options.max_height.unwrap_or(MAX_IMAGE_DIMENSION);
|
|
||||||
let max_pixels = u64::from(options.max_pixels.unwrap_or(MAX_IMAGE_PIXELS as u32));
|
|
||||||
if image.width == 0 || image.height == 0 {
|
|
||||||
bail!("failed to decode image");
|
|
||||||
}
|
|
||||||
if image.width > max_width || image.height > max_height {
|
|
||||||
bail!("image dimensions exceed limit");
|
|
||||||
}
|
|
||||||
if u64::from(image.width) * u64::from(image.height) > max_pixels {
|
|
||||||
bail!("image pixel count exceeds limit");
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn parse_image_header(input: &[u8]) -> AnyResult<ImageInspection> {
|
|
||||||
let format = ::image::guess_format(input).context("unsupported image format")?;
|
|
||||||
let mime_type = image_mime_type(format).context("unsupported image format")?;
|
|
||||||
let (width, height) = ImageReader::with_format(Cursor::new(input), format)
|
|
||||||
.into_dimensions()
|
|
||||||
.context("failed to decode image")?;
|
|
||||||
Ok(ImageInspection {
|
|
||||||
mime_type: mime_type.to_string(),
|
|
||||||
width,
|
|
||||||
height,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
fn image_mime_type(format: ImageFormat) -> Option<&'static str> {
|
|
||||||
match format {
|
|
||||||
ImageFormat::Png => Some("image/png"),
|
|
||||||
ImageFormat::Jpeg => Some("image/jpeg"),
|
|
||||||
ImageFormat::Gif => Some("image/gif"),
|
|
||||||
ImageFormat::WebP => Some("image/webp"),
|
|
||||||
ImageFormat::Bmp => Some("image/bmp"),
|
|
||||||
_ => None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod tests {
|
|
||||||
use super::*;
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn blocks_private_ips() {
|
|
||||||
assert!(is_blocked_ip("127.0.0.1".parse().unwrap()));
|
|
||||||
assert!(is_blocked_ip("10.0.0.1".parse().unwrap()));
|
|
||||||
assert!(is_blocked_ip("169.254.169.254".parse().unwrap()));
|
|
||||||
assert!(is_blocked_ip("::1".parse().unwrap()));
|
|
||||||
assert!(is_blocked_ip("::ffff:127.0.0.1".parse().unwrap()));
|
|
||||||
assert!(is_blocked_ip("2002:7f00:0001::1".parse().unwrap()));
|
|
||||||
assert!(is_blocked_ip("2002:c0a8:0001::1".parse().unwrap()));
|
|
||||||
assert!(is_blocked_ip(
|
|
||||||
"2001:0000:4136:e378:8000:63bf:807f:fffe".parse().unwrap()
|
|
||||||
));
|
|
||||||
assert!(!is_blocked_ip("8.8.8.8".parse().unwrap()));
|
|
||||||
assert!(!is_blocked_ip("2002:0808:0808::1".parse().unwrap()));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn builds_https_client_with_embedded_roots() {
|
|
||||||
let url = Url::parse("https://example.com/").unwrap();
|
|
||||||
let addrs = ["93.184.216.34:443".parse().unwrap()];
|
|
||||||
build_pinned_client(&url, &addrs, Duration::from_secs(1)).unwrap();
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn inspects_png_dimensions_without_decode() {
|
|
||||||
let png = base64_simd::STANDARD
|
|
||||||
.decode_to_vec(b"iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mP8/x8AAwMCAO+jfJ8AAAAASUVORK5CYII=")
|
|
||||||
.unwrap();
|
|
||||||
let inspected = inspect_image_for_proxy_inner(
|
|
||||||
&png,
|
|
||||||
ImageInspectionOptions {
|
|
||||||
max_width: None,
|
|
||||||
max_height: None,
|
|
||||||
max_pixels: None,
|
|
||||||
},
|
|
||||||
)
|
|
||||||
.unwrap();
|
|
||||||
assert_eq!(inspected.mime_type, "image/png");
|
|
||||||
assert_eq!(inspected.width, 1);
|
|
||||||
assert_eq!(inspected.height, 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn rejects_oversized_dimensions() {
|
|
||||||
let png = [
|
|
||||||
b"\x89PNG\r\n\x1a\n".as_slice(),
|
|
||||||
&[0, 0, 0, 13],
|
|
||||||
b"IHDR".as_slice(),
|
|
||||||
&100_000u32.to_be_bytes(),
|
|
||||||
&100_000u32.to_be_bytes(),
|
|
||||||
&[8, 6, 0, 0, 0],
|
|
||||||
]
|
|
||||||
.concat();
|
|
||||||
assert!(
|
|
||||||
inspect_image_for_proxy_inner(
|
|
||||||
&png,
|
|
||||||
ImageInspectionOptions {
|
|
||||||
max_width: None,
|
|
||||||
max_height: None,
|
|
||||||
max_pixels: None,
|
|
||||||
}
|
|
||||||
)
|
|
||||||
.is_err()
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@@ -38,7 +38,7 @@ test('change email', async t => {
|
|||||||
const jwt = signedIn?.token.token;
|
const jwt = signedIn?.token.token;
|
||||||
t.truthy(jwt);
|
t.truthy(jwt);
|
||||||
|
|
||||||
await sendChangeEmail(app, u1Email, '/email-change');
|
await sendChangeEmail(app, '/email-change');
|
||||||
|
|
||||||
const changeMail = app.mails.last('ChangeEmail');
|
const changeMail = app.mails.last('ChangeEmail');
|
||||||
|
|
||||||
@@ -157,12 +157,11 @@ test('should forbid graphql callbackUrl to external origin', async t => {
|
|||||||
.set({ 'x-request-id': 'test', 'x-operation-name': 'test' })
|
.set({ 'x-request-id': 'test', 'x-operation-name': 'test' })
|
||||||
.send({
|
.send({
|
||||||
query: `
|
query: `
|
||||||
mutation($email: String!, $callbackUrl: String!) {
|
mutation($callbackUrl: String!) {
|
||||||
sendChangeEmail(email: $email, callbackUrl: $callbackUrl)
|
sendChangeEmail(callbackUrl: $callbackUrl)
|
||||||
}
|
}
|
||||||
`,
|
`,
|
||||||
variables: {
|
variables: {
|
||||||
email: u1Email,
|
|
||||||
callbackUrl: 'https://evil.example',
|
callbackUrl: 'https://evil.example',
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -787,7 +787,7 @@ test('test key failure disables a saved key and success restores it', async t =>
|
|||||||
apiKey: 'sk-test-primary',
|
apiKey: 'sk-test-primary',
|
||||||
});
|
});
|
||||||
|
|
||||||
const fetch = Sinon.stub(globalThis, 'fetch');
|
const fetch = Sinon.stub(t.context.byok as any, 'probeFetch');
|
||||||
fetch
|
fetch
|
||||||
.onFirstCall()
|
.onFirstCall()
|
||||||
.resolves(
|
.resolves(
|
||||||
@@ -858,7 +858,7 @@ test('local key test does not mutate saved server config', async t => {
|
|||||||
apiKey: 'sk-server',
|
apiKey: 'sk-server',
|
||||||
});
|
});
|
||||||
|
|
||||||
const fetch = Sinon.stub(globalThis, 'fetch').resolves(
|
const fetch = Sinon.stub(t.context.byok as any, 'probeFetch').resolves(
|
||||||
new Response('{"error":"invalid sk-local"}', { status: 401 })
|
new Response('{"error":"invalid sk-local"}', { status: 401 })
|
||||||
);
|
);
|
||||||
t.teardown(() => fetch.restore());
|
t.teardown(() => fetch.restore());
|
||||||
@@ -889,7 +889,7 @@ test('Gemini key test sends key in header and returns safe failure message', asy
|
|||||||
const { user, workspace } = await createUserWorkspace(t);
|
const { user, workspace } = await createUserWorkspace(t);
|
||||||
await grantUserPlan(t, user.id);
|
await grantUserPlan(t, user.id);
|
||||||
|
|
||||||
const fetch = Sinon.stub(globalThis, 'fetch').resolves(
|
const fetch = Sinon.stub(t.context.byok as any, 'probeFetch').resolves(
|
||||||
new Response(
|
new Response(
|
||||||
'failed https://generativelanguage.googleapis.com/v1beta/models?key=gemini-secret',
|
'failed https://generativelanguage.googleapis.com/v1beta/models?key=gemini-secret',
|
||||||
{ status: 401 }
|
{ status: 401 }
|
||||||
@@ -916,6 +916,7 @@ test('Gemini key test sends key in header and returns safe failure message', asy
|
|||||||
],
|
],
|
||||||
'gemini-secret'
|
'gemini-secret'
|
||||||
);
|
);
|
||||||
|
t.deepEqual(fetch.firstCall.args[2]?.allowedHeaders, ['x-goog-api-key']);
|
||||||
t.false(result.message?.includes('gemini-secret'));
|
t.false(result.message?.includes('gemini-secret'));
|
||||||
t.is(result.message, 'Provider rejected the BYOK key.');
|
t.is(result.message, 'Provider rejected the BYOK key.');
|
||||||
});
|
});
|
||||||
@@ -924,7 +925,7 @@ test('FAL key test uses read-only platform API probe endpoint', async t => {
|
|||||||
const { user, workspace } = await createUserWorkspace(t);
|
const { user, workspace } = await createUserWorkspace(t);
|
||||||
await grantUserPlan(t, user.id);
|
await grantUserPlan(t, user.id);
|
||||||
|
|
||||||
const fetch = Sinon.stub(globalThis, 'fetch').resolves(
|
const fetch = Sinon.stub(t.context.byok as any, 'probeFetch').resolves(
|
||||||
new Response('{}', { status: 200 })
|
new Response('{}', { status: 200 })
|
||||||
);
|
);
|
||||||
t.teardown(() => fetch.restore());
|
t.teardown(() => fetch.restore());
|
||||||
@@ -943,6 +944,7 @@ test('FAL key test uses read-only platform API probe endpoint', async t => {
|
|||||||
(fetch.firstCall.args[1]!.headers as Record<string, string>).Authorization,
|
(fetch.firstCall.args[1]!.headers as Record<string, string>).Authorization,
|
||||||
'Key fal-secret'
|
'Key fal-secret'
|
||||||
);
|
);
|
||||||
|
t.deepEqual(fetch.firstCall.args[2]?.allowedHeaders, ['Authorization']);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('provider test failures do not return raw provider response body', async t => {
|
test('provider test failures do not return raw provider response body', async t => {
|
||||||
@@ -970,7 +972,7 @@ test('provider test failures do not return raw provider response body', async t
|
|||||||
message: 'Provider service is unavailable.',
|
message: 'Provider service is unavailable.',
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
const fetch = Sinon.stub(globalThis, 'fetch');
|
const fetch = Sinon.stub(t.context.byok as any, 'probeFetch');
|
||||||
for (const [index, matrixCase] of cases.entries()) {
|
for (const [index, matrixCase] of cases.entries()) {
|
||||||
fetch
|
fetch
|
||||||
.onCall(index)
|
.onCall(index)
|
||||||
|
|||||||
@@ -1673,6 +1673,7 @@ test('should be able to manage context', async t => {
|
|||||||
'workspace.file.embed.finished',
|
'workspace.file.embed.finished',
|
||||||
{
|
{
|
||||||
contextId: session.id,
|
contextId: session.id,
|
||||||
|
workspaceId: session.workspaceId,
|
||||||
fileId: file.id,
|
fileId: file.id,
|
||||||
chunkSize: 1,
|
chunkSize: 1,
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -53,7 +53,7 @@ test('admin feature resolver rejects commercial projection features', async t =>
|
|||||||
|
|
||||||
test('should get null if user feature not found', async t => {
|
test('should get null if user feature not found', async t => {
|
||||||
const { model, u1 } = t.context;
|
const { model, u1 } = t.context;
|
||||||
const userFeature = await model.get(u1.id, 'ai_early_access');
|
const userFeature = await model.get(u1.id, 'administrator');
|
||||||
t.is(userFeature, null);
|
t.is(userFeature, null);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -93,7 +93,7 @@ test('should directly test user feature existence', async t => {
|
|||||||
|
|
||||||
await model.add(u1.id, 'free_plan_v1', 'legacy projection');
|
await model.add(u1.id, 'free_plan_v1', 'legacy projection');
|
||||||
t.true(await model.has(u1.id, 'free_plan_v1'));
|
t.true(await model.has(u1.id, 'free_plan_v1'));
|
||||||
t.false(await model.has(u1.id, 'ai_early_access'));
|
t.false(await model.has(u1.id, 'administrator'));
|
||||||
});
|
});
|
||||||
|
|
||||||
test('should add user feature', async t => {
|
test('should add user feature', async t => {
|
||||||
|
|||||||
@@ -924,7 +924,7 @@ test('oidc should not fall back to default email claim when custom claim is conf
|
|||||||
|
|
||||||
test('oidc discovery should remove oauth feature on failure and restore it after backoff retry succeeds', async t => {
|
test('oidc discovery should remove oauth feature on failure and restore it after backoff retry succeeds', async t => {
|
||||||
const { provider, factory, server } = createOidcRegistrationHarness();
|
const { provider, factory, server } = createOidcRegistrationHarness();
|
||||||
const fetchStub = Sinon.stub(globalThis, 'fetch');
|
const fetchStub = Sinon.stub(provider as any, 'oidcFetch');
|
||||||
const scheduledRetries: Array<() => void> = [];
|
const scheduledRetries: Array<() => void> = [];
|
||||||
const retryDelays: number[] = [];
|
const retryDelays: number[] = [];
|
||||||
const setTimeoutStub = Sinon.stub(globalThis, 'setTimeout').callsFake(((
|
const setTimeoutStub = Sinon.stub(globalThis, 'setTimeout').callsFake(((
|
||||||
|
|||||||
@@ -1,121 +0,0 @@
|
|||||||
# Snapshot report for `src/__tests__/payment/service.spec.ts`
|
|
||||||
|
|
||||||
The actual snapshot is saved in `service.spec.ts.snap`.
|
|
||||||
|
|
||||||
Generated by [AVA](https://avajs.dev).
|
|
||||||
|
|
||||||
## should list normal price for unauthenticated user
|
|
||||||
|
|
||||||
> Snapshot 1
|
|
||||||
|
|
||||||
[
|
|
||||||
'pro_monthly',
|
|
||||||
'pro_yearly',
|
|
||||||
'pro_lifetime',
|
|
||||||
'ai_yearly',
|
|
||||||
'team_monthly',
|
|
||||||
'team_yearly',
|
|
||||||
]
|
|
||||||
|
|
||||||
## should list normal prices for authenticated user
|
|
||||||
|
|
||||||
> Snapshot 1
|
|
||||||
|
|
||||||
[
|
|
||||||
'pro_monthly',
|
|
||||||
'pro_yearly',
|
|
||||||
'pro_lifetime',
|
|
||||||
'ai_yearly',
|
|
||||||
'team_monthly',
|
|
||||||
'team_yearly',
|
|
||||||
]
|
|
||||||
|
|
||||||
## should not show lifetime price if not enabled
|
|
||||||
|
|
||||||
> Snapshot 1
|
|
||||||
|
|
||||||
[
|
|
||||||
'pro_monthly',
|
|
||||||
'pro_yearly',
|
|
||||||
'ai_yearly',
|
|
||||||
'team_monthly',
|
|
||||||
'team_yearly',
|
|
||||||
]
|
|
||||||
|
|
||||||
## should list early access prices for pro ea user
|
|
||||||
|
|
||||||
> Snapshot 1
|
|
||||||
|
|
||||||
[
|
|
||||||
'pro_monthly',
|
|
||||||
'pro_lifetime',
|
|
||||||
'pro_yearly_earlyaccess',
|
|
||||||
'ai_yearly',
|
|
||||||
'team_monthly',
|
|
||||||
'team_yearly',
|
|
||||||
]
|
|
||||||
|
|
||||||
## should list normal prices for pro ea user with old subscriptions
|
|
||||||
|
|
||||||
> Snapshot 1
|
|
||||||
|
|
||||||
[
|
|
||||||
'pro_monthly',
|
|
||||||
'pro_yearly',
|
|
||||||
'pro_lifetime',
|
|
||||||
'ai_yearly',
|
|
||||||
'team_monthly',
|
|
||||||
'team_yearly',
|
|
||||||
]
|
|
||||||
|
|
||||||
## should list early access prices for ai ea user
|
|
||||||
|
|
||||||
> Snapshot 1
|
|
||||||
|
|
||||||
[
|
|
||||||
'pro_monthly',
|
|
||||||
'pro_yearly',
|
|
||||||
'pro_lifetime',
|
|
||||||
'ai_yearly_earlyaccess',
|
|
||||||
'team_monthly',
|
|
||||||
'team_yearly',
|
|
||||||
]
|
|
||||||
|
|
||||||
## should list early access prices for pro and ai ea user
|
|
||||||
|
|
||||||
> Snapshot 1
|
|
||||||
|
|
||||||
[
|
|
||||||
'pro_monthly',
|
|
||||||
'pro_lifetime',
|
|
||||||
'pro_yearly_earlyaccess',
|
|
||||||
'ai_yearly_earlyaccess',
|
|
||||||
'team_monthly',
|
|
||||||
'team_yearly',
|
|
||||||
]
|
|
||||||
|
|
||||||
## should list normal prices for ai ea user with old subscriptions
|
|
||||||
|
|
||||||
> Snapshot 1
|
|
||||||
|
|
||||||
[
|
|
||||||
'pro_monthly',
|
|
||||||
'pro_yearly',
|
|
||||||
'pro_lifetime',
|
|
||||||
'ai_yearly',
|
|
||||||
'team_monthly',
|
|
||||||
'team_yearly',
|
|
||||||
]
|
|
||||||
|
|
||||||
## should be able to list prices for team
|
|
||||||
|
|
||||||
> Snapshot 1
|
|
||||||
|
|
||||||
[
|
|
||||||
'pro_monthly',
|
|
||||||
'pro_yearly',
|
|
||||||
'pro_lifetime',
|
|
||||||
'ai_yearly',
|
|
||||||
'team_monthly',
|
|
||||||
'team_yearly',
|
|
||||||
]
|
|
||||||
Binary file not shown.
@@ -7,7 +7,7 @@ import { WorkspacePolicyService } from '../../core/permission';
|
|||||||
import { QuotaStateService } from '../../core/quota/state';
|
import { QuotaStateService } from '../../core/quota/state';
|
||||||
import { WorkspaceService } from '../../core/workspaces';
|
import { WorkspaceService } from '../../core/workspaces';
|
||||||
import { Models } from '../../models';
|
import { Models } from '../../models';
|
||||||
import { LicenseService } from '../../plugins/license/service';
|
import { licenseClient, LicenseService } from '../../plugins/license/service';
|
||||||
import { PaymentEventHandlers } from '../../plugins/payment/event';
|
import { PaymentEventHandlers } from '../../plugins/payment/event';
|
||||||
import {
|
import {
|
||||||
SubscriptionPlan,
|
SubscriptionPlan,
|
||||||
@@ -19,6 +19,12 @@ type Context = Record<string, never>;
|
|||||||
|
|
||||||
const test = ava as TestFn<Context>;
|
const test = ava as TestFn<Context>;
|
||||||
|
|
||||||
|
const originalActivateLicense = licenseClient.activate;
|
||||||
|
|
||||||
|
test.afterEach.always(() => {
|
||||||
|
licenseClient.activate = originalActivateLicense;
|
||||||
|
});
|
||||||
|
|
||||||
test('workspace subscription activation only sends upgrade notification', async t => {
|
test('workspace subscription activation only sends upgrade notification', async t => {
|
||||||
const events: Array<{ name: string; payload: unknown }> = [];
|
const events: Array<{ name: string; payload: unknown }> = [];
|
||||||
let reconciled = false;
|
let reconciled = false;
|
||||||
@@ -120,7 +126,6 @@ test('onetime selfhost license seat allocation ignores projected license quantit
|
|||||||
|
|
||||||
test('recurring selfhost license activation returns activation projection without remote health recheck', async t => {
|
test('recurring selfhost license activation returns activation projection without remote health recheck', async t => {
|
||||||
const events: Array<{ name: string; payload: unknown }> = [];
|
const events: Array<{ name: string; payload: unknown }> = [];
|
||||||
const affineProRequests: string[] = [];
|
|
||||||
const upserts: unknown[] = [];
|
const upserts: unknown[] = [];
|
||||||
const entitlements: unknown[] = [];
|
const entitlements: unknown[] = [];
|
||||||
const expiresAt = Date.now() + 30 * 24 * 60 * 60 * 1000;
|
const expiresAt = Date.now() + 30 * 24 * 60 * 60 * 1000;
|
||||||
@@ -154,28 +159,17 @@ test('recurring selfhost license activation returns activation projection withou
|
|||||||
{} as unknown as QuotaStateService
|
{} as unknown as QuotaStateService
|
||||||
);
|
);
|
||||||
|
|
||||||
(
|
let activatedLicenseKey: string | undefined;
|
||||||
service as unknown as {
|
licenseClient.activate = async ({ licenseKey }) => {
|
||||||
fetchAffinePro: (path: string) => Promise<{
|
activatedLicenseKey = licenseKey;
|
||||||
plan: SubscriptionPlan;
|
|
||||||
recurring: SubscriptionRecurring;
|
|
||||||
quantity: number;
|
|
||||||
endAt: number;
|
|
||||||
res: Response;
|
|
||||||
}>;
|
|
||||||
}
|
|
||||||
).fetchAffinePro = async (path: string) => {
|
|
||||||
affineProRequests.push(path);
|
|
||||||
return {
|
return {
|
||||||
|
license: {
|
||||||
plan: SubscriptionPlan.SelfHostedTeam,
|
plan: SubscriptionPlan.SelfHostedTeam,
|
||||||
recurring: SubscriptionRecurring.Monthly,
|
recurring: SubscriptionRecurring.Monthly,
|
||||||
quantity: 3,
|
quantity: 3,
|
||||||
endAt: expiresAt,
|
expiresAt,
|
||||||
res: new Response(null, {
|
validateKey: 'next-validate-key',
|
||||||
headers: {
|
|
||||||
'x-next-validate-key': 'next-validate-key',
|
|
||||||
},
|
},
|
||||||
}),
|
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -189,7 +183,7 @@ test('recurring selfhost license activation returns activation projection withou
|
|||||||
});
|
});
|
||||||
t.is(entitlements.length, 1);
|
t.is(entitlements.length, 1);
|
||||||
t.is(upserts.length, 1);
|
t.is(upserts.length, 1);
|
||||||
t.deepEqual(affineProRequests, ['/api/team/licenses/license-key/activate']);
|
t.is(activatedLicenseKey, 'license-key');
|
||||||
t.deepEqual(events, [
|
t.deepEqual(events, [
|
||||||
{
|
{
|
||||||
name: 'workspace.subscription.activated',
|
name: 'workspace.subscription.activated',
|
||||||
|
|||||||
@@ -10,17 +10,14 @@ import { EventBus } from '../../base';
|
|||||||
import { ConfigFactory, ConfigModule } from '../../base/config';
|
import { ConfigFactory, ConfigModule } from '../../base/config';
|
||||||
import { CurrentUser } from '../../core/auth';
|
import { CurrentUser } from '../../core/auth';
|
||||||
import { AuthService } from '../../core/auth/service';
|
import { AuthService } from '../../core/auth/service';
|
||||||
import { EarlyAccessType, FeatureService } from '../../core/features';
|
|
||||||
import { SubscriptionCronJobs } from '../../plugins/payment/cron';
|
import { SubscriptionCronJobs } from '../../plugins/payment/cron';
|
||||||
import { SubscriptionService } from '../../plugins/payment/service';
|
import { SubscriptionService } from '../../plugins/payment/service';
|
||||||
import { StripeFactory } from '../../plugins/payment/stripe';
|
import { StripeFactory } from '../../plugins/payment/stripe';
|
||||||
import {
|
import {
|
||||||
CouponType,
|
|
||||||
encodeLookupKey,
|
encodeLookupKey,
|
||||||
SubscriptionPlan,
|
SubscriptionPlan,
|
||||||
SubscriptionRecurring,
|
SubscriptionRecurring,
|
||||||
SubscriptionStatus,
|
SubscriptionStatus,
|
||||||
SubscriptionVariant,
|
|
||||||
} from '../../plugins/payment/types';
|
} from '../../plugins/payment/types';
|
||||||
import { createTestingApp, type TestingApp } from '../utils';
|
import { createTestingApp, type TestingApp } from '../utils';
|
||||||
|
|
||||||
@@ -31,15 +28,25 @@ const unixNow = () => {
|
|||||||
const PRO_MONTHLY = `${SubscriptionPlan.Pro}_${SubscriptionRecurring.Monthly}`;
|
const PRO_MONTHLY = `${SubscriptionPlan.Pro}_${SubscriptionRecurring.Monthly}`;
|
||||||
const PRO_YEARLY = `${SubscriptionPlan.Pro}_${SubscriptionRecurring.Yearly}`;
|
const PRO_YEARLY = `${SubscriptionPlan.Pro}_${SubscriptionRecurring.Yearly}`;
|
||||||
const PRO_LIFETIME = `${SubscriptionPlan.Pro}_${SubscriptionRecurring.Lifetime}`;
|
const PRO_LIFETIME = `${SubscriptionPlan.Pro}_${SubscriptionRecurring.Lifetime}`;
|
||||||
const PRO_EA_YEARLY = `${SubscriptionPlan.Pro}_${SubscriptionRecurring.Yearly}_${SubscriptionVariant.EA}`;
|
|
||||||
const AI_YEARLY = `${SubscriptionPlan.AI}_${SubscriptionRecurring.Yearly}`;
|
const AI_YEARLY = `${SubscriptionPlan.AI}_${SubscriptionRecurring.Yearly}`;
|
||||||
const AI_YEARLY_EA = `${SubscriptionPlan.AI}_${SubscriptionRecurring.Yearly}_${SubscriptionVariant.EA}`;
|
|
||||||
const TEAM_MONTHLY = `${SubscriptionPlan.Team}_${SubscriptionRecurring.Monthly}`;
|
const TEAM_MONTHLY = `${SubscriptionPlan.Team}_${SubscriptionRecurring.Monthly}`;
|
||||||
const TEAM_YEARLY = `${SubscriptionPlan.Team}_${SubscriptionRecurring.Yearly}`;
|
const TEAM_YEARLY = `${SubscriptionPlan.Team}_${SubscriptionRecurring.Yearly}`;
|
||||||
// prices for code redeeming
|
const NORMAL_USER_PRICES = [
|
||||||
const PRO_MONTHLY_CODE = `${SubscriptionPlan.Pro}_${SubscriptionRecurring.Monthly}_${SubscriptionVariant.Onetime}`;
|
PRO_MONTHLY,
|
||||||
const PRO_YEARLY_CODE = `${SubscriptionPlan.Pro}_${SubscriptionRecurring.Yearly}_${SubscriptionVariant.Onetime}`;
|
PRO_YEARLY,
|
||||||
const AI_YEARLY_CODE = `${SubscriptionPlan.AI}_${SubscriptionRecurring.Yearly}_${SubscriptionVariant.Onetime}`;
|
PRO_LIFETIME,
|
||||||
|
AI_YEARLY,
|
||||||
|
TEAM_MONTHLY,
|
||||||
|
TEAM_YEARLY,
|
||||||
|
];
|
||||||
|
|
||||||
|
const NORMAL_USER_PRICES_WITHOUT_LIFETIME = [
|
||||||
|
PRO_MONTHLY,
|
||||||
|
PRO_YEARLY,
|
||||||
|
AI_YEARLY,
|
||||||
|
TEAM_MONTHLY,
|
||||||
|
TEAM_YEARLY,
|
||||||
|
];
|
||||||
|
|
||||||
const PRICES = {
|
const PRICES = {
|
||||||
[PRO_MONTHLY]: {
|
[PRO_MONTHLY]: {
|
||||||
@@ -66,15 +73,6 @@ const PRICES = {
|
|||||||
id: PRO_LIFETIME,
|
id: PRO_LIFETIME,
|
||||||
lookup_key: PRO_LIFETIME,
|
lookup_key: PRO_LIFETIME,
|
||||||
},
|
},
|
||||||
[PRO_EA_YEARLY]: {
|
|
||||||
recurring: {
|
|
||||||
interval: 'year',
|
|
||||||
},
|
|
||||||
unit_amount: 5000,
|
|
||||||
currency: 'usd',
|
|
||||||
id: PRO_EA_YEARLY,
|
|
||||||
lookup_key: PRO_EA_YEARLY,
|
|
||||||
},
|
|
||||||
[AI_YEARLY]: {
|
[AI_YEARLY]: {
|
||||||
recurring: {
|
recurring: {
|
||||||
interval: 'year',
|
interval: 'year',
|
||||||
@@ -84,33 +82,6 @@ const PRICES = {
|
|||||||
id: AI_YEARLY,
|
id: AI_YEARLY,
|
||||||
lookup_key: AI_YEARLY,
|
lookup_key: AI_YEARLY,
|
||||||
},
|
},
|
||||||
[AI_YEARLY_EA]: {
|
|
||||||
recurring: {
|
|
||||||
interval: 'year',
|
|
||||||
},
|
|
||||||
unit_amount: 9999,
|
|
||||||
currency: 'usd',
|
|
||||||
id: AI_YEARLY_EA,
|
|
||||||
lookup_key: AI_YEARLY_EA,
|
|
||||||
},
|
|
||||||
[PRO_MONTHLY_CODE]: {
|
|
||||||
unit_amount: 799,
|
|
||||||
currency: 'usd',
|
|
||||||
id: PRO_MONTHLY_CODE,
|
|
||||||
lookup_key: PRO_MONTHLY_CODE,
|
|
||||||
},
|
|
||||||
[PRO_YEARLY_CODE]: {
|
|
||||||
unit_amount: 8100,
|
|
||||||
currency: 'usd',
|
|
||||||
id: PRO_YEARLY_CODE,
|
|
||||||
lookup_key: PRO_YEARLY_CODE,
|
|
||||||
},
|
|
||||||
[AI_YEARLY_CODE]: {
|
|
||||||
unit_amount: 10680,
|
|
||||||
currency: 'usd',
|
|
||||||
id: AI_YEARLY_CODE,
|
|
||||||
lookup_key: AI_YEARLY_CODE,
|
|
||||||
},
|
|
||||||
[TEAM_MONTHLY]: {
|
[TEAM_MONTHLY]: {
|
||||||
unit_amount: 1500,
|
unit_amount: 1500,
|
||||||
currency: 'usd',
|
currency: 'usd',
|
||||||
@@ -164,7 +135,6 @@ const test = ava as TestFn<{
|
|||||||
app: TestingApp;
|
app: TestingApp;
|
||||||
service: SubscriptionService;
|
service: SubscriptionService;
|
||||||
event: Sinon.SinonStubbedInstance<EventBus>;
|
event: Sinon.SinonStubbedInstance<EventBus>;
|
||||||
feature: Sinon.SinonStubbedInstance<FeatureService>;
|
|
||||||
stripe: {
|
stripe: {
|
||||||
customers: Sinon.SinonStubbedInstance<Stripe.CustomersResource>;
|
customers: Sinon.SinonStubbedInstance<Stripe.CustomersResource>;
|
||||||
prices: Sinon.SinonStubbedInstance<Stripe.PricesResource>;
|
prices: Sinon.SinonStubbedInstance<Stripe.PricesResource>;
|
||||||
@@ -203,16 +173,12 @@ test.before(async t => {
|
|||||||
AppModule,
|
AppModule,
|
||||||
],
|
],
|
||||||
tapModule: m => {
|
tapModule: m => {
|
||||||
m.overrideProvider(FeatureService).useValue(
|
|
||||||
Sinon.createStubInstance(FeatureService)
|
|
||||||
);
|
|
||||||
m.overrideProvider(EventBus).useValue(Sinon.createStubInstance(EventBus));
|
m.overrideProvider(EventBus).useValue(Sinon.createStubInstance(EventBus));
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
t.context.event = app.get(EventBus);
|
t.context.event = app.get(EventBus);
|
||||||
t.context.service = app.get(SubscriptionService);
|
t.context.service = app.get(SubscriptionService);
|
||||||
t.context.feature = app.get(FeatureService);
|
|
||||||
t.context.db = app.get(PrismaClient);
|
t.context.db = app.get(PrismaClient);
|
||||||
t.context.app = app;
|
t.context.app = app;
|
||||||
|
|
||||||
@@ -286,18 +252,21 @@ test('should list normal price for unauthenticated user', async t => {
|
|||||||
|
|
||||||
const prices = await service.listPrices();
|
const prices = await service.listPrices();
|
||||||
|
|
||||||
t.snapshot(prices.map(p => encodeLookupKey(p.lookupKey)));
|
t.deepEqual(
|
||||||
|
prices.map(p => encodeLookupKey(p.lookupKey)),
|
||||||
|
NORMAL_USER_PRICES
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('should list normal prices for authenticated user', async t => {
|
test('should list normal prices for authenticated user', async t => {
|
||||||
const { feature, service, u1 } = t.context;
|
const { service, u1 } = t.context;
|
||||||
|
|
||||||
feature.isEarlyAccessUser.withArgs(u1.id).resolves(false);
|
|
||||||
feature.isEarlyAccessUser.withArgs(u1.id, EarlyAccessType.AI).resolves(false);
|
|
||||||
|
|
||||||
const prices = await service.listPrices(u1);
|
const prices = await service.listPrices(u1);
|
||||||
|
|
||||||
t.snapshot(prices.map(p => encodeLookupKey(p.lookupKey)));
|
t.deepEqual(
|
||||||
|
prices.map(p => encodeLookupKey(p.lookupKey)),
|
||||||
|
NORMAL_USER_PRICES
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('should not show lifetime price if not enabled', async t => {
|
test('should not show lifetime price if not enabled', async t => {
|
||||||
@@ -311,25 +280,14 @@ test('should not show lifetime price if not enabled', async t => {
|
|||||||
|
|
||||||
const prices = await service.listPrices(t.context.u1);
|
const prices = await service.listPrices(t.context.u1);
|
||||||
|
|
||||||
t.snapshot(prices.map(p => encodeLookupKey(p.lookupKey)));
|
t.deepEqual(
|
||||||
|
prices.map(p => encodeLookupKey(p.lookupKey)),
|
||||||
|
NORMAL_USER_PRICES_WITHOUT_LIFETIME
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('should list early access prices for pro ea user', async t => {
|
test('should list normal prices for user with old pro subscriptions', async t => {
|
||||||
const { feature, service, u1 } = t.context;
|
const { service, u1, stripe } = t.context;
|
||||||
|
|
||||||
feature.isEarlyAccessUser.withArgs(u1.id).resolves(true);
|
|
||||||
feature.isEarlyAccessUser.withArgs(u1.id, EarlyAccessType.AI).resolves(false);
|
|
||||||
|
|
||||||
const prices = await service.listPrices(u1);
|
|
||||||
|
|
||||||
t.snapshot(prices.map(p => encodeLookupKey(p.lookupKey)));
|
|
||||||
});
|
|
||||||
|
|
||||||
test('should list normal prices for pro ea user with old subscriptions', async t => {
|
|
||||||
const { feature, service, u1, stripe } = t.context;
|
|
||||||
|
|
||||||
feature.isEarlyAccessUser.withArgs(u1.id).resolves(true);
|
|
||||||
feature.isEarlyAccessUser.withArgs(u1.id, EarlyAccessType.AI).resolves(false);
|
|
||||||
|
|
||||||
stripe.subscriptions.list.resolves({
|
stripe.subscriptions.list.resolves({
|
||||||
data: [
|
data: [
|
||||||
@@ -352,35 +310,14 @@ test('should list normal prices for pro ea user with old subscriptions', async t
|
|||||||
|
|
||||||
const prices = await service.listPrices(u1);
|
const prices = await service.listPrices(u1);
|
||||||
|
|
||||||
t.snapshot(prices.map(p => encodeLookupKey(p.lookupKey)));
|
t.deepEqual(
|
||||||
|
prices.map(p => encodeLookupKey(p.lookupKey)),
|
||||||
|
NORMAL_USER_PRICES
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('should list early access prices for ai ea user', async t => {
|
test('should list normal prices for user with old ai subscriptions', async t => {
|
||||||
const { feature, service, u1 } = t.context;
|
const { service, u1, stripe } = t.context;
|
||||||
|
|
||||||
feature.isEarlyAccessUser.withArgs(u1.id).resolves(false);
|
|
||||||
feature.isEarlyAccessUser.withArgs(u1.id, EarlyAccessType.AI).resolves(true);
|
|
||||||
|
|
||||||
const prices = await service.listPrices(u1);
|
|
||||||
|
|
||||||
t.snapshot(prices.map(p => encodeLookupKey(p.lookupKey)));
|
|
||||||
});
|
|
||||||
|
|
||||||
test('should list early access prices for pro and ai ea user', async t => {
|
|
||||||
const { feature, service, u1 } = t.context;
|
|
||||||
|
|
||||||
feature.isEarlyAccessUser.withArgs(u1.id).resolves(true);
|
|
||||||
|
|
||||||
const prices = await service.listPrices(u1);
|
|
||||||
|
|
||||||
t.snapshot(prices.map(p => encodeLookupKey(p.lookupKey)));
|
|
||||||
});
|
|
||||||
|
|
||||||
test('should list normal prices for ai ea user with old subscriptions', async t => {
|
|
||||||
const { feature, service, u1, stripe } = t.context;
|
|
||||||
|
|
||||||
feature.isEarlyAccessUser.withArgs(u1.id).resolves(false);
|
|
||||||
feature.isEarlyAccessUser.withArgs(u1.id, EarlyAccessType.AI).resolves(true);
|
|
||||||
|
|
||||||
stripe.subscriptions.list.resolves({
|
stripe.subscriptions.list.resolves({
|
||||||
data: [
|
data: [
|
||||||
@@ -403,7 +340,10 @@ test('should list normal prices for ai ea user with old subscriptions', async t
|
|||||||
|
|
||||||
const prices = await service.listPrices(u1);
|
const prices = await service.listPrices(u1);
|
||||||
|
|
||||||
t.snapshot(prices.map(p => encodeLookupKey(p.lookupKey)));
|
t.deepEqual(
|
||||||
|
prices.map(p => encodeLookupKey(p.lookupKey)),
|
||||||
|
NORMAL_USER_PRICES
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
// ============= end prices ================
|
// ============= end prices ================
|
||||||
@@ -470,11 +410,9 @@ test('should allow checkout after local subscription period ended', async t => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
test('should get correct pro plan price for checking out', async t => {
|
test('should get correct pro plan price for checking out', async t => {
|
||||||
const { app, service, u1, stripe, feature } = t.context;
|
const { app, service, u1, stripe } = t.context;
|
||||||
// non-ea user
|
// monthly
|
||||||
{
|
{
|
||||||
feature.isEarlyAccessUser.resolves(false);
|
|
||||||
|
|
||||||
await service.checkout(
|
await service.checkout(
|
||||||
{
|
{
|
||||||
plan: SubscriptionPlan.Pro,
|
plan: SubscriptionPlan.Pro,
|
||||||
@@ -490,27 +428,8 @@ test('should get correct pro plan price for checking out', async t => {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// ea user, but monthly
|
// yearly
|
||||||
{
|
{
|
||||||
feature.isEarlyAccessUser.resolves(true);
|
|
||||||
await service.checkout(
|
|
||||||
{
|
|
||||||
plan: SubscriptionPlan.Pro,
|
|
||||||
recurring: SubscriptionRecurring.Monthly,
|
|
||||||
successCallbackLink: '',
|
|
||||||
},
|
|
||||||
{ user: u1 }
|
|
||||||
);
|
|
||||||
|
|
||||||
t.deepEqual(getLastCheckoutPrice(stripe.checkout.sessions.create), {
|
|
||||||
price: PRO_MONTHLY,
|
|
||||||
coupon: undefined,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// ea user, yearly
|
|
||||||
{
|
|
||||||
feature.isEarlyAccessUser.resolves(true);
|
|
||||||
await service.checkout(
|
await service.checkout(
|
||||||
{
|
{
|
||||||
plan: SubscriptionPlan.Pro,
|
plan: SubscriptionPlan.Pro,
|
||||||
@@ -521,14 +440,13 @@ test('should get correct pro plan price for checking out', async t => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
t.deepEqual(getLastCheckoutPrice(stripe.checkout.sessions.create), {
|
t.deepEqual(getLastCheckoutPrice(stripe.checkout.sessions.create), {
|
||||||
price: PRO_EA_YEARLY,
|
price: PRO_YEARLY,
|
||||||
coupon: CouponType.ProEarlyAccessOneYearFree,
|
coupon: undefined,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// ea user, yearly recurring, but has old subscription
|
// yearly recurring, but has old subscription
|
||||||
{
|
{
|
||||||
feature.isEarlyAccessUser.resolves(true);
|
|
||||||
stripe.subscriptions.list.resolves({
|
stripe.subscriptions.list.resolves({
|
||||||
data: [
|
data: [
|
||||||
{
|
{
|
||||||
@@ -561,27 +479,10 @@ test('should get correct pro plan price for checking out', async t => {
|
|||||||
price: PRO_YEARLY,
|
price: PRO_YEARLY,
|
||||||
coupon: undefined,
|
coupon: undefined,
|
||||||
});
|
});
|
||||||
|
|
||||||
await t.throwsAsync(
|
|
||||||
() =>
|
|
||||||
service.checkout(
|
|
||||||
{
|
|
||||||
plan: SubscriptionPlan.Pro,
|
|
||||||
recurring: SubscriptionRecurring.Yearly,
|
|
||||||
variant: SubscriptionVariant.EA,
|
|
||||||
successCallbackLink: '',
|
|
||||||
},
|
|
||||||
{ user: u1 }
|
|
||||||
),
|
|
||||||
{
|
|
||||||
message: 'You are trying to access a unknown subscription plan.',
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// any user, lifetime recurring
|
// any user, lifetime recurring
|
||||||
{
|
{
|
||||||
feature.isEarlyAccessUser.resolves(false);
|
|
||||||
app.get(ConfigFactory).override({
|
app.get(ConfigFactory).override({
|
||||||
payment: {
|
payment: {
|
||||||
showLifetimePrice: true,
|
showLifetimePrice: true,
|
||||||
@@ -605,12 +506,10 @@ test('should get correct pro plan price for checking out', async t => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
test('should get correct ai plan price for checking out', async t => {
|
test('should get correct ai plan price for checking out', async t => {
|
||||||
const { service, u1, stripe, feature } = t.context;
|
const { service, u1, stripe } = t.context;
|
||||||
|
|
||||||
// non-ea user
|
// user
|
||||||
{
|
{
|
||||||
feature.isEarlyAccessUser.resolves(false);
|
|
||||||
|
|
||||||
await service.checkout(
|
await service.checkout(
|
||||||
{
|
{
|
||||||
plan: SubscriptionPlan.AI,
|
plan: SubscriptionPlan.AI,
|
||||||
@@ -626,111 +525,8 @@ test('should get correct ai plan price for checking out', async t => {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// ea user
|
// user with old subscription
|
||||||
{
|
{
|
||||||
feature.isEarlyAccessUser.resolves(true);
|
|
||||||
|
|
||||||
await service.checkout(
|
|
||||||
{
|
|
||||||
plan: SubscriptionPlan.AI,
|
|
||||||
recurring: SubscriptionRecurring.Yearly,
|
|
||||||
successCallbackLink: '',
|
|
||||||
},
|
|
||||||
{ user: u1 }
|
|
||||||
);
|
|
||||||
|
|
||||||
t.deepEqual(getLastCheckoutPrice(stripe.checkout.sessions.create), {
|
|
||||||
price: AI_YEARLY_EA,
|
|
||||||
coupon: CouponType.AIEarlyAccessOneYearFree,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// ea user, but has old subscription
|
|
||||||
{
|
|
||||||
feature.isEarlyAccessUser.withArgs(u1.id).resolves(false);
|
|
||||||
feature.isEarlyAccessUser
|
|
||||||
.withArgs(u1.id, EarlyAccessType.AI)
|
|
||||||
.resolves(true);
|
|
||||||
stripe.subscriptions.list.resolves({
|
|
||||||
data: [
|
|
||||||
{
|
|
||||||
id: 'sub_1',
|
|
||||||
status: 'canceled',
|
|
||||||
items: {
|
|
||||||
data: [
|
|
||||||
{
|
|
||||||
// @ts-expect-error stub
|
|
||||||
price: {
|
|
||||||
lookup_key: AI_YEARLY,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
],
|
|
||||||
});
|
|
||||||
|
|
||||||
await service.checkout(
|
|
||||||
{
|
|
||||||
plan: SubscriptionPlan.AI,
|
|
||||||
recurring: SubscriptionRecurring.Yearly,
|
|
||||||
successCallbackLink: '',
|
|
||||||
},
|
|
||||||
{ user: u1 }
|
|
||||||
);
|
|
||||||
|
|
||||||
t.deepEqual(getLastCheckoutPrice(stripe.checkout.sessions.create), {
|
|
||||||
price: AI_YEARLY,
|
|
||||||
coupon: undefined,
|
|
||||||
});
|
|
||||||
|
|
||||||
await t.throwsAsync(
|
|
||||||
() =>
|
|
||||||
service.checkout(
|
|
||||||
{
|
|
||||||
plan: SubscriptionPlan.AI,
|
|
||||||
recurring: SubscriptionRecurring.Yearly,
|
|
||||||
variant: SubscriptionVariant.EA,
|
|
||||||
successCallbackLink: '',
|
|
||||||
},
|
|
||||||
{ user: u1 }
|
|
||||||
),
|
|
||||||
{
|
|
||||||
message: 'You are trying to access a unknown subscription plan.',
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// pro ea user
|
|
||||||
{
|
|
||||||
feature.isEarlyAccessUser.withArgs(u1.id).resolves(true);
|
|
||||||
feature.isEarlyAccessUser
|
|
||||||
.withArgs(u1.id, EarlyAccessType.AI)
|
|
||||||
.resolves(false);
|
|
||||||
// @ts-expect-error stub
|
|
||||||
stripe.subscriptions.list.resolves({ data: [] });
|
|
||||||
|
|
||||||
await service.checkout(
|
|
||||||
{
|
|
||||||
plan: SubscriptionPlan.AI,
|
|
||||||
recurring: SubscriptionRecurring.Yearly,
|
|
||||||
successCallbackLink: '',
|
|
||||||
},
|
|
||||||
{ user: u1 }
|
|
||||||
);
|
|
||||||
|
|
||||||
t.deepEqual(getLastCheckoutPrice(stripe.checkout.sessions.create), {
|
|
||||||
price: AI_YEARLY,
|
|
||||||
coupon: CouponType.ProEarlyAccessAIOneYearFree,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// pro ea user, but has old subscription
|
|
||||||
{
|
|
||||||
feature.isEarlyAccessUser.withArgs(u1.id).resolves(true);
|
|
||||||
feature.isEarlyAccessUser
|
|
||||||
.withArgs(u1.id, EarlyAccessType.AI)
|
|
||||||
.resolves(false);
|
|
||||||
stripe.subscriptions.list.resolves({
|
stripe.subscriptions.list.resolves({
|
||||||
data: [
|
data: [
|
||||||
{
|
{
|
||||||
@@ -1218,44 +1014,6 @@ const lifetimeInvoice: Stripe.Invoice = {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
const onetimeMonthlyInvoice: Stripe.Invoice = {
|
|
||||||
id: 'in_2',
|
|
||||||
object: 'invoice',
|
|
||||||
amount_paid: 799,
|
|
||||||
total: 799,
|
|
||||||
customer: 'cus_1',
|
|
||||||
customer_email: 'u1@affine.pro',
|
|
||||||
currency: 'usd',
|
|
||||||
status: 'paid',
|
|
||||||
lines: {
|
|
||||||
data: [
|
|
||||||
// @ts-expect-error stub
|
|
||||||
{
|
|
||||||
price: PRICES[PRO_MONTHLY_CODE],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
const onetimeYearlyInvoice: Stripe.Invoice = {
|
|
||||||
id: 'in_3',
|
|
||||||
object: 'invoice',
|
|
||||||
amount_paid: 8100,
|
|
||||||
total: 8100,
|
|
||||||
customer: 'cus_1',
|
|
||||||
customer_email: 'u1@affine.pro',
|
|
||||||
currency: 'usd',
|
|
||||||
status: 'paid',
|
|
||||||
lines: {
|
|
||||||
data: [
|
|
||||||
// @ts-expect-error stub
|
|
||||||
{
|
|
||||||
price: PRICES[PRO_YEARLY_CODE],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
test('should not be able to checkout for lifetime recurring if not enabled', async t => {
|
test('should not be able to checkout for lifetime recurring if not enabled', async t => {
|
||||||
const { service, u1, app } = t.context;
|
const { service, u1, app } = t.context;
|
||||||
app.get(ConfigFactory).override({
|
app.get(ConfigFactory).override({
|
||||||
@@ -1337,32 +1095,6 @@ test('should not be able to checkout for lifetime recurring if already subscribe
|
|||||||
),
|
),
|
||||||
{ message: 'You have already subscribed to the pro plan.' }
|
{ message: 'You have already subscribed to the pro plan.' }
|
||||||
);
|
);
|
||||||
|
|
||||||
await db.subscription.updateMany({
|
|
||||||
where: { targetId: u1.id },
|
|
||||||
data: {
|
|
||||||
stripeSubscriptionId: null,
|
|
||||||
recurring: SubscriptionRecurring.Monthly,
|
|
||||||
variant: SubscriptionVariant.Onetime,
|
|
||||||
end: new Date(Date.now() + 100000),
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
await t.throwsAsync(
|
|
||||||
() =>
|
|
||||||
service.checkout(
|
|
||||||
{
|
|
||||||
plan: SubscriptionPlan.Pro,
|
|
||||||
recurring: SubscriptionRecurring.Lifetime,
|
|
||||||
variant: null,
|
|
||||||
successCallbackLink: '',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
user: u1,
|
|
||||||
}
|
|
||||||
),
|
|
||||||
{ message: 'You have already subscribed to the pro plan.' }
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
test('should be able to subscribe to lifetime recurring', async t => {
|
test('should be able to subscribe to lifetime recurring', async t => {
|
||||||
@@ -1474,226 +1206,16 @@ test('should not be able to update lifetime recurring', async t => {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
// ============== Onetime Subscription ===============
|
|
||||||
test('should be able to checkout for onetime payment', async t => {
|
|
||||||
const { service, u1, stripe } = t.context;
|
|
||||||
|
|
||||||
await service.checkout(
|
|
||||||
{
|
|
||||||
plan: SubscriptionPlan.Pro,
|
|
||||||
recurring: SubscriptionRecurring.Monthly,
|
|
||||||
variant: SubscriptionVariant.Onetime,
|
|
||||||
successCallbackLink: '',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
user: u1,
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
t.true(stripe.checkout.sessions.create.calledOnce);
|
|
||||||
const arg = stripe.checkout.sessions.create.firstCall
|
|
||||||
.args[0] as Stripe.Checkout.SessionCreateParams;
|
|
||||||
t.is(arg.mode, 'payment');
|
|
||||||
t.deepEqual(getLastCheckoutPrice(stripe.checkout.sessions.create), {
|
|
||||||
price: PRO_MONTHLY_CODE,
|
|
||||||
coupon: undefined,
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
test('should be able to checkout onetime payment if previous subscription is onetime', async t => {
|
|
||||||
const { service, u1, stripe, db } = t.context;
|
|
||||||
|
|
||||||
await db.subscription.create({
|
|
||||||
data: {
|
|
||||||
targetId: u1.id,
|
|
||||||
stripeSubscriptionId: 'sub_1',
|
|
||||||
plan: SubscriptionPlan.Pro,
|
|
||||||
recurring: SubscriptionRecurring.Monthly,
|
|
||||||
variant: SubscriptionVariant.Onetime,
|
|
||||||
status: SubscriptionStatus.Active,
|
|
||||||
start: new Date(),
|
|
||||||
end: new Date(Date.now() + 100000),
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
await service.checkout(
|
|
||||||
{
|
|
||||||
plan: SubscriptionPlan.Pro,
|
|
||||||
recurring: SubscriptionRecurring.Monthly,
|
|
||||||
variant: SubscriptionVariant.Onetime,
|
|
||||||
successCallbackLink: '',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
user: u1,
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
t.true(stripe.checkout.sessions.create.calledOnce);
|
|
||||||
const arg = stripe.checkout.sessions.create.firstCall
|
|
||||||
.args[0] as Stripe.Checkout.SessionCreateParams;
|
|
||||||
t.is(arg.mode, 'payment');
|
|
||||||
t.deepEqual(getLastCheckoutPrice(stripe.checkout.sessions.create), {
|
|
||||||
price: PRO_MONTHLY_CODE,
|
|
||||||
coupon: undefined,
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
test('should not be able to checkout out onetime payment if previous subscription is not onetime', async t => {
|
|
||||||
const { service, u1, db } = t.context;
|
|
||||||
|
|
||||||
await db.subscription.create({
|
|
||||||
data: {
|
|
||||||
targetId: u1.id,
|
|
||||||
stripeSubscriptionId: 'sub_1',
|
|
||||||
plan: SubscriptionPlan.Pro,
|
|
||||||
recurring: SubscriptionRecurring.Monthly,
|
|
||||||
status: SubscriptionStatus.Active,
|
|
||||||
start: new Date(),
|
|
||||||
end: new Date(Date.now() + 100000),
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
await t.throwsAsync(
|
|
||||||
() =>
|
|
||||||
service.checkout(
|
|
||||||
{
|
|
||||||
plan: SubscriptionPlan.Pro,
|
|
||||||
recurring: SubscriptionRecurring.Monthly,
|
|
||||||
variant: SubscriptionVariant.Onetime,
|
|
||||||
successCallbackLink: '',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
user: u1,
|
|
||||||
}
|
|
||||||
),
|
|
||||||
{ message: 'You have already subscribed to the pro plan.' }
|
|
||||||
);
|
|
||||||
|
|
||||||
await db.subscription.updateMany({
|
|
||||||
where: { targetId: u1.id },
|
|
||||||
data: {
|
|
||||||
stripeSubscriptionId: null,
|
|
||||||
recurring: SubscriptionRecurring.Lifetime,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
await t.throwsAsync(
|
|
||||||
() =>
|
|
||||||
service.checkout(
|
|
||||||
{
|
|
||||||
plan: SubscriptionPlan.Pro,
|
|
||||||
recurring: SubscriptionRecurring.Monthly,
|
|
||||||
variant: SubscriptionVariant.Onetime,
|
|
||||||
successCallbackLink: '',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
user: u1,
|
|
||||||
}
|
|
||||||
),
|
|
||||||
{ message: 'You have already subscribed to the pro plan.' }
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('should be able to subscribe onetime payment subscription', async t => {
|
|
||||||
const { service, db, u1, event } = t.context;
|
|
||||||
|
|
||||||
await service.saveStripeInvoice(onetimeMonthlyInvoice);
|
|
||||||
|
|
||||||
const subInDB = await db.subscription.findFirst({
|
|
||||||
where: { targetId: u1.id },
|
|
||||||
});
|
|
||||||
|
|
||||||
t.true(
|
|
||||||
event.emit.calledOnceWith('user.subscription.activated', {
|
|
||||||
userId: u1.id,
|
|
||||||
plan: SubscriptionPlan.Pro,
|
|
||||||
recurring: SubscriptionRecurring.Monthly,
|
|
||||||
})
|
|
||||||
);
|
|
||||||
t.is(subInDB?.plan, SubscriptionPlan.Pro);
|
|
||||||
t.is(subInDB?.recurring, SubscriptionRecurring.Monthly);
|
|
||||||
t.is(subInDB?.status, SubscriptionStatus.Active);
|
|
||||||
t.is(subInDB?.stripeSubscriptionId, null);
|
|
||||||
t.is(
|
|
||||||
subInDB?.end?.toDateString(),
|
|
||||||
new Date(Date.now() + 30 * 24 * 60 * 60 * 1000).toDateString()
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('should be able to accumulate onetime payment subscription period', async t => {
|
|
||||||
const { service, db, u1 } = t.context;
|
|
||||||
|
|
||||||
await service.saveStripeInvoice(onetimeMonthlyInvoice);
|
|
||||||
|
|
||||||
let subInDB = await db.subscription.findFirst({
|
|
||||||
where: { targetId: u1.id },
|
|
||||||
});
|
|
||||||
|
|
||||||
t.truthy(subInDB);
|
|
||||||
|
|
||||||
let end = subInDB!.end!;
|
|
||||||
await service.saveStripeInvoice(onetimeYearlyInvoice);
|
|
||||||
subInDB = await db.subscription.findFirst({
|
|
||||||
where: { targetId: u1.id },
|
|
||||||
});
|
|
||||||
|
|
||||||
// add 365 days
|
|
||||||
t.is(subInDB!.end!.getTime(), end.getTime() + 365 * 24 * 60 * 60 * 1000);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('should be able to recalculate onetime payment subscription period after expiration', async t => {
|
|
||||||
const { service, db, u1 } = t.context;
|
|
||||||
|
|
||||||
await service.saveStripeInvoice(onetimeMonthlyInvoice);
|
|
||||||
|
|
||||||
let subInDB = await db.subscription.findFirst({
|
|
||||||
where: { targetId: u1.id },
|
|
||||||
});
|
|
||||||
|
|
||||||
// make subscription expired
|
|
||||||
await db.subscription.update({
|
|
||||||
where: { id: subInDB!.id },
|
|
||||||
data: {
|
|
||||||
end: new Date(Date.now() - 1000),
|
|
||||||
},
|
|
||||||
});
|
|
||||||
await service.saveStripeInvoice(onetimeYearlyInvoice);
|
|
||||||
subInDB = await db.subscription.findFirst({
|
|
||||||
where: { targetId: u1.id },
|
|
||||||
});
|
|
||||||
|
|
||||||
// add 365 days from now
|
|
||||||
t.is(
|
|
||||||
subInDB?.end?.toDateString(),
|
|
||||||
new Date(Date.now() + 365 * 24 * 60 * 60 * 1000).toDateString()
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('should not accumulate onetime payment subscription period for redeemed invoices', async t => {
|
|
||||||
const { service, db, u1 } = t.context;
|
|
||||||
|
|
||||||
// save invoices received more than once, should only redeem them once.
|
|
||||||
await service.saveStripeInvoice(onetimeYearlyInvoice);
|
|
||||||
await service.saveStripeInvoice(onetimeYearlyInvoice);
|
|
||||||
await service.saveStripeInvoice(onetimeYearlyInvoice);
|
|
||||||
|
|
||||||
const subInDB = await db.subscription.findFirst({
|
|
||||||
where: { targetId: u1.id },
|
|
||||||
});
|
|
||||||
|
|
||||||
t.is(
|
|
||||||
subInDB?.end?.toDateString(),
|
|
||||||
new Date(Date.now() + 365 * 24 * 60 * 60 * 1000).toDateString()
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
// TEAM
|
// TEAM
|
||||||
test('should be able to list prices for team', async t => {
|
test('should be able to list prices for team', async t => {
|
||||||
const { service } = t.context;
|
const { service } = t.context;
|
||||||
|
|
||||||
const prices = await service.listPrices(undefined);
|
const prices = await service.listPrices(undefined);
|
||||||
|
|
||||||
t.snapshot(prices.map(p => encodeLookupKey(p.lookupKey)));
|
t.deepEqual(
|
||||||
|
prices.map(p => encodeLookupKey(p.lookupKey)),
|
||||||
|
NORMAL_USER_PRICES
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('should be able to checkout for team', async t => {
|
test('should be able to checkout for team', async t => {
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ export async function inviteUsers(
|
|||||||
app: TestingApp,
|
app: TestingApp,
|
||||||
workspaceId: string,
|
workspaceId: string,
|
||||||
emails: string[]
|
emails: string[]
|
||||||
): Promise<Array<{ email: string; inviteId?: string; sentSuccess?: boolean }>> {
|
): Promise<Array<{ email: string; inviteId?: string }>> {
|
||||||
const res = await app.gql(
|
const res = await app.gql(
|
||||||
`
|
`
|
||||||
mutation inviteMembers($workspaceId: String!, $emails: [String!]!) {
|
mutation inviteMembers($workspaceId: String!, $emails: [String!]!) {
|
||||||
@@ -31,7 +31,6 @@ export async function inviteUsers(
|
|||||||
) {
|
) {
|
||||||
email
|
email
|
||||||
inviteId
|
inviteId
|
||||||
sentSuccess
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
`,
|
`,
|
||||||
|
|||||||
@@ -42,19 +42,6 @@ export async function listNotifications(
|
|||||||
return res.currentUser.notifications;
|
return res.currentUser.notifications;
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getNotificationCount(app: TestingApp): Promise<number> {
|
|
||||||
const res = await app.gql(
|
|
||||||
`
|
|
||||||
query notificationCount {
|
|
||||||
currentUser {
|
|
||||||
notificationCount
|
|
||||||
}
|
|
||||||
}
|
|
||||||
`
|
|
||||||
);
|
|
||||||
return res.currentUser.notificationCount;
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function mentionUser(
|
export async function mentionUser(
|
||||||
app: TestingApp,
|
app: TestingApp,
|
||||||
input: MentionInput
|
input: MentionInput
|
||||||
|
|||||||
@@ -34,12 +34,11 @@ export async function getPublicUserById(
|
|||||||
|
|
||||||
export async function sendChangeEmail(
|
export async function sendChangeEmail(
|
||||||
app: TestingApp,
|
app: TestingApp,
|
||||||
email: string,
|
|
||||||
callbackUrl: string
|
callbackUrl: string
|
||||||
): Promise<boolean> {
|
): Promise<boolean> {
|
||||||
const res = await app.gql(`
|
const res = await app.gql(`
|
||||||
mutation {
|
mutation {
|
||||||
sendChangeEmail(email: "${email}", callbackUrl: "${callbackUrl}")
|
sendChangeEmail(callbackUrl: "${callbackUrl}")
|
||||||
}
|
}
|
||||||
`);
|
`);
|
||||||
|
|
||||||
|
|||||||
@@ -56,7 +56,6 @@ import { ModelsModule } from './models';
|
|||||||
import { CalendarModule } from './plugins/calendar';
|
import { CalendarModule } from './plugins/calendar';
|
||||||
import { CaptchaModule } from './plugins/captcha';
|
import { CaptchaModule } from './plugins/captcha';
|
||||||
import { CopilotModule, CopilotRealtimeModule } from './plugins/copilot';
|
import { CopilotModule, CopilotRealtimeModule } from './plugins/copilot';
|
||||||
import { CustomerIoModule } from './plugins/customerio';
|
|
||||||
import { GCloudModule } from './plugins/gcloud';
|
import { GCloudModule } from './plugins/gcloud';
|
||||||
import { IndexerModule } from './plugins/indexer';
|
import { IndexerModule } from './plugins/indexer';
|
||||||
import { LicenseModule } from './plugins/license';
|
import { LicenseModule } from './plugins/license';
|
||||||
@@ -205,7 +204,6 @@ export function buildAppModule(env: Env) {
|
|||||||
CaptchaModule,
|
CaptchaModule,
|
||||||
OAuthModule,
|
OAuthModule,
|
||||||
CalendarModule,
|
CalendarModule,
|
||||||
CustomerIoModule,
|
|
||||||
TelemetryModule,
|
TelemetryModule,
|
||||||
CommentModule,
|
CommentModule,
|
||||||
AccessTokenModule,
|
AccessTokenModule,
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ export type SSRFBlockReason =
|
|||||||
| 'disallowed_protocol'
|
| 'disallowed_protocol'
|
||||||
| 'url_has_credentials'
|
| 'url_has_credentials'
|
||||||
| 'blocked_hostname'
|
| 'blocked_hostname'
|
||||||
|
| 'host_not_allowed'
|
||||||
| 'unresolvable_hostname'
|
| 'unresolvable_hostname'
|
||||||
| 'blocked_ip'
|
| 'blocked_ip'
|
||||||
| 'too_many_redirects';
|
| 'too_many_redirects';
|
||||||
@@ -19,6 +20,7 @@ const SSRF_REASONS = new Set<string>([
|
|||||||
'disallowed_protocol',
|
'disallowed_protocol',
|
||||||
'url_has_credentials',
|
'url_has_credentials',
|
||||||
'blocked_hostname',
|
'blocked_hostname',
|
||||||
|
'host_not_allowed',
|
||||||
'unresolvable_hostname',
|
'unresolvable_hostname',
|
||||||
'blocked_ip',
|
'blocked_ip',
|
||||||
'too_many_redirects',
|
'too_many_redirects',
|
||||||
@@ -47,6 +49,12 @@ export interface SafeFetchOptions {
|
|||||||
timeoutMs?: number;
|
timeoutMs?: number;
|
||||||
maxRedirects?: number;
|
maxRedirects?: number;
|
||||||
maxBytes?: number;
|
maxBytes?: number;
|
||||||
|
allowedHeaders?: string[];
|
||||||
|
allowedHosts?: string[];
|
||||||
|
allowHttp?: boolean;
|
||||||
|
allowPrivateTargetOrigin?: boolean;
|
||||||
|
enableEch?: boolean;
|
||||||
|
echConfigList?: Buffer;
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function assertSsrFSafeUrl(rawUrl: string | URL): Promise<URL> {
|
export async function assertSsrFSafeUrl(rawUrl: string | URL): Promise<URL> {
|
||||||
@@ -72,20 +80,25 @@ export async function safeFetch(
|
|||||||
): Promise<Response> {
|
): Promise<Response> {
|
||||||
const url = rawUrl.toString();
|
const url = rawUrl.toString();
|
||||||
const method = String(init.method ?? 'GET').toUpperCase();
|
const method = String(init.method ?? 'GET').toUpperCase();
|
||||||
if (method !== 'GET' && method !== 'HEAD') {
|
if (!['GET', 'HEAD', 'POST', 'PUT', 'PROPFIND', 'REPORT'].includes(method)) {
|
||||||
throw new Error(`Unsupported safeFetch method: ${method}`);
|
throw new Error(`Unsupported safeFetch method: ${method}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const response = await safeFetchFromNative({
|
const response = await safeFetchFromNative({
|
||||||
url,
|
url,
|
||||||
method: (method === 'HEAD' ? 'head' : 'get') as NonNullable<
|
method: method.toLowerCase() as NonNullable<SafeFetchRequest['method']>,
|
||||||
SafeFetchRequest['method']
|
|
||||||
>,
|
|
||||||
headers: normalizeHeaders(init.headers),
|
headers: normalizeHeaders(init.headers),
|
||||||
|
body: normalizeBody(init.body),
|
||||||
timeoutMs: options.timeoutMs,
|
timeoutMs: options.timeoutMs,
|
||||||
maxRedirects: options.maxRedirects,
|
maxRedirects: options.maxRedirects,
|
||||||
maxBytes: options.maxBytes,
|
maxBytes: options.maxBytes,
|
||||||
|
allowedHeaders: options.allowedHeaders,
|
||||||
|
allowedHosts: options.allowedHosts,
|
||||||
|
allowHttp: options.allowHttp,
|
||||||
|
allowPrivateTargetOrigin: options.allowPrivateTargetOrigin,
|
||||||
|
enableEch: options.enableEch,
|
||||||
|
echConfigList: options.echConfigList,
|
||||||
});
|
});
|
||||||
const body =
|
const body =
|
||||||
method === 'HEAD' || [204, 205, 304].includes(response.status)
|
method === 'HEAD' || [204, 205, 304].includes(response.status)
|
||||||
@@ -117,6 +130,22 @@ function normalizeHeaders(headers: RequestInit['headers'] | undefined) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function normalizeBody(body: RequestInit['body'] | null | undefined) {
|
||||||
|
if (body === null || body === undefined) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
if (typeof body === 'string') {
|
||||||
|
return Buffer.from(body);
|
||||||
|
}
|
||||||
|
if (body instanceof ArrayBuffer) {
|
||||||
|
return Buffer.from(body);
|
||||||
|
}
|
||||||
|
if (ArrayBuffer.isView(body)) {
|
||||||
|
return Buffer.from(body.buffer, body.byteOffset, body.byteLength);
|
||||||
|
}
|
||||||
|
throw new Error('Unsupported safeFetch body type.');
|
||||||
|
}
|
||||||
|
|
||||||
export function bufferToArrayBuffer(buffer: Buffer): ArrayBuffer {
|
export function bufferToArrayBuffer(buffer: Buffer): ArrayBuffer {
|
||||||
const copy = new Uint8Array(buffer.byteLength);
|
const copy = new Uint8Array(buffer.byteLength);
|
||||||
copy.set(buffer);
|
copy.set(buffer);
|
||||||
|
|||||||
@@ -187,9 +187,7 @@ export class AuthResolver {
|
|||||||
@Mutation(() => Boolean)
|
@Mutation(() => Boolean)
|
||||||
async sendChangeEmail(
|
async sendChangeEmail(
|
||||||
@CurrentUser() user: CurrentUser,
|
@CurrentUser() user: CurrentUser,
|
||||||
@Args('callbackUrl') callbackUrl: string,
|
@Args('callbackUrl') callbackUrl: string
|
||||||
// @deprecated
|
|
||||||
@Args('email', { nullable: true }) _email?: string
|
|
||||||
) {
|
) {
|
||||||
if (!user.emailVerified) {
|
if (!user.emailVerified) {
|
||||||
throw new EmailVerificationRequired();
|
throw new EmailVerificationRequired();
|
||||||
|
|||||||
@@ -299,18 +299,13 @@ export class CommentResolver {
|
|||||||
@CurrentUser() me: UserType,
|
@CurrentUser() me: UserType,
|
||||||
@Parent() workspace: WorkspaceType,
|
@Parent() workspace: WorkspaceType,
|
||||||
@Args('docId') docId: string,
|
@Args('docId') docId: string,
|
||||||
@Args({
|
@Args({ name: 'pagination' })
|
||||||
name: 'pagination',
|
|
||||||
})
|
|
||||||
pagination: PaginationInput
|
pagination: PaginationInput
|
||||||
): Promise<PaginatedCommentChangeObjectType> {
|
): Promise<PaginatedCommentChangeObjectType> {
|
||||||
// DEPRECATED-0.26-COMPAT(realtime): remove after server no longer supports 0.26.x clients.
|
// DEPRECATED-0.26-COMPAT(realtime): remove after server no longer supports 0.26.x clients.
|
||||||
await this.assertPermission(
|
await this.assertPermission(
|
||||||
me,
|
me,
|
||||||
{
|
{ workspaceId: workspace.id, docId },
|
||||||
workspaceId: workspace.id,
|
|
||||||
docId,
|
|
||||||
},
|
|
||||||
'Doc.Comments.Read'
|
'Doc.Comments.Read'
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import {
|
|||||||
Config,
|
Config,
|
||||||
CryptoHelper,
|
CryptoHelper,
|
||||||
getOrGenRequestId,
|
getOrGenRequestId,
|
||||||
|
safeFetch,
|
||||||
UserFriendlyError,
|
UserFriendlyError,
|
||||||
} from '../../base';
|
} from '../../base';
|
||||||
import { Models } from '../../models';
|
import { Models } from '../../models';
|
||||||
@@ -303,7 +304,17 @@ export class RpcDocReader extends DatabaseDocReader {
|
|||||||
if (body) {
|
if (body) {
|
||||||
requestInit.body = body;
|
requestInit.body = body;
|
||||||
}
|
}
|
||||||
const res = await fetch(url, requestInit);
|
const res = await safeFetch(url, requestInit, {
|
||||||
|
timeoutMs: 10_000,
|
||||||
|
maxRedirects: 0,
|
||||||
|
maxBytes: 50 * 1024 * 1024,
|
||||||
|
allowedHeaders: [
|
||||||
|
'content-type',
|
||||||
|
'x-access-token',
|
||||||
|
'x-cloud-trace-context',
|
||||||
|
],
|
||||||
|
allowPrivateTargetOrigin: true,
|
||||||
|
});
|
||||||
if (!res.ok) {
|
if (!res.ok) {
|
||||||
if (res.status === 404) {
|
if (res.status === 404) {
|
||||||
return null;
|
return null;
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import { Injectable } from '@nestjs/common';
|
|||||||
import { Entitlement, Prisma, PrismaClient } from '@prisma/client';
|
import { Entitlement, Prisma, PrismaClient } from '@prisma/client';
|
||||||
|
|
||||||
import { BadRequest, CryptoHelper, EventBus } from '../../base';
|
import { BadRequest, CryptoHelper, EventBus } from '../../base';
|
||||||
import { resolveEntitlementV1 } from '../../native';
|
import { checkLicenseHealth, resolveEntitlementV1 } from '../../native';
|
||||||
import {
|
import {
|
||||||
SubscriptionPlan,
|
SubscriptionPlan,
|
||||||
SubscriptionRecurring,
|
SubscriptionRecurring,
|
||||||
@@ -47,15 +47,7 @@ export interface SelfhostLicenseEntitlementInput {
|
|||||||
license?: Buffer | null;
|
license?: Buffer | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface RemoteSelfhostLicense {
|
|
||||||
plan: string;
|
|
||||||
recurring: string;
|
|
||||||
quantity: number;
|
|
||||||
endAt: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
const REMOTE_SELFHOST_LICENSE_REVALIDATE_INTERVAL = 1000 * 60 * 10;
|
const REMOTE_SELFHOST_LICENSE_REVALIDATE_INTERVAL = 1000 * 60 * 10;
|
||||||
const REMOTE_SELFHOST_LICENSE_HEALTH_TIMEOUT = 10_000;
|
|
||||||
|
|
||||||
declare global {
|
declare global {
|
||||||
interface Events {
|
interface Events {
|
||||||
@@ -844,44 +836,25 @@ export class EntitlementService {
|
|||||||
return cached.entitlement;
|
return cached.entitlement;
|
||||||
}
|
}
|
||||||
|
|
||||||
const endpoint =
|
|
||||||
process.env.AFFINE_PRO_SERVER_ENDPOINT ?? 'https://app.affine.pro';
|
|
||||||
const signal = AbortSignal.timeout(REMOTE_SELFHOST_LICENSE_HEALTH_TIMEOUT);
|
|
||||||
try {
|
try {
|
||||||
const res = await fetch(
|
const res = await checkLicenseHealth({
|
||||||
`${endpoint}/api/team/licenses/${entitlement.subjectId}/health`,
|
licenseKey: entitlement.subjectId,
|
||||||
{
|
validateKey: metadata.validateKey,
|
||||||
signal,
|
});
|
||||||
headers: {
|
if (res.error) {
|
||||||
'Content-Type': 'application/json',
|
if (res.error.status >= 500) {
|
||||||
'x-validate-key': metadata.validateKey,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
);
|
|
||||||
if (!res.ok) {
|
|
||||||
if (res.status >= 500) {
|
|
||||||
return this.remoteSelfhostFallbackEntitlement(entitlement);
|
return this.remoteSelfhostFallbackEntitlement(entitlement);
|
||||||
}
|
}
|
||||||
|
|
||||||
await this.markRemoteSelfhostLicenseNeedsReupload(
|
await this.markRemoteSelfhostLicenseNeedsReupload(
|
||||||
entitlement,
|
entitlement,
|
||||||
`Remote license health check failed: ${res.status}`
|
`Remote license health check failed: ${res.error.status}`
|
||||||
);
|
);
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const payload = (await res
|
const license = res.license;
|
||||||
.json()
|
if (!license || license.plan !== SubscriptionPlan.SelfHostedTeam) {
|
||||||
.catch(() => null)) as RemoteSelfhostLicense | null;
|
|
||||||
if (!payload) {
|
|
||||||
return this.remoteSelfhostFallbackEntitlement(entitlement);
|
|
||||||
}
|
|
||||||
const expiresAt = this.remoteSelfhostLicenseExpiresAt(payload.endAt);
|
|
||||||
if (
|
|
||||||
payload.plan !== SubscriptionPlan.SelfHostedTeam ||
|
|
||||||
payload.quantity < 1 ||
|
|
||||||
!expiresAt
|
|
||||||
) {
|
|
||||||
await this.markRemoteSelfhostLicenseNeedsReupload(
|
await this.markRemoteSelfhostLicenseNeedsReupload(
|
||||||
entitlement,
|
entitlement,
|
||||||
'Remote license health payload is invalid.'
|
'Remote license health payload is invalid.'
|
||||||
@@ -889,17 +862,17 @@ export class EntitlementService {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const validateKey =
|
const expiresAt = new Date(license.expiresAt);
|
||||||
res.headers.get('x-next-validate-key') ?? metadata.validateKey;
|
const validateKey = license.validateKey || metadata.validateKey;
|
||||||
const [updated] = await Promise.all([
|
const [updated] = await Promise.all([
|
||||||
this.db.entitlement.update({
|
this.db.entitlement.update({
|
||||||
where: { id: entitlement.id },
|
where: { id: entitlement.id },
|
||||||
data: {
|
data: {
|
||||||
status: 'active',
|
status: 'active',
|
||||||
quantity: this.normalizedQuantity(payload.quantity),
|
quantity: this.normalizedQuantity(license.quantity),
|
||||||
metadata: {
|
metadata: {
|
||||||
...metadata,
|
...metadata,
|
||||||
recurring: payload.recurring,
|
recurring: license.recurring,
|
||||||
validateKey,
|
validateKey,
|
||||||
remoteValidated: true,
|
remoteValidated: true,
|
||||||
errorCode: null,
|
errorCode: null,
|
||||||
@@ -913,8 +886,8 @@ export class EntitlementService {
|
|||||||
.updateMany({
|
.updateMany({
|
||||||
where: { key: entitlement.subjectId },
|
where: { key: entitlement.subjectId },
|
||||||
data: {
|
data: {
|
||||||
quantity: this.normalizedQuantity(payload.quantity),
|
quantity: this.normalizedQuantity(license.quantity),
|
||||||
recurring: payload.recurring,
|
recurring: license.recurring,
|
||||||
validateKey,
|
validateKey,
|
||||||
validatedAt: new Date(),
|
validatedAt: new Date(),
|
||||||
expiredAt: expiresAt,
|
expiredAt: expiresAt,
|
||||||
@@ -950,14 +923,6 @@ export class EntitlementService {
|
|||||||
return cached.entitlement;
|
return cached.entitlement;
|
||||||
}
|
}
|
||||||
|
|
||||||
private remoteSelfhostLicenseExpiresAt(endAt: unknown) {
|
|
||||||
const expiresAt = new Date(endAt as string | number | Date);
|
|
||||||
if (!Number.isFinite(expiresAt.getTime()) || expiresAt <= new Date()) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
return expiresAt;
|
|
||||||
}
|
|
||||||
|
|
||||||
private async markRemoteSelfhostLicenseNeedsReupload(
|
private async markRemoteSelfhostLicenseNeedsReupload(
|
||||||
entitlement: Entitlement,
|
entitlement: Entitlement,
|
||||||
reason: string
|
reason: string
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import {
|
|||||||
AdminFeatureManagementResolver,
|
AdminFeatureManagementResolver,
|
||||||
UserFeatureResolver,
|
UserFeatureResolver,
|
||||||
} from './resolver';
|
} from './resolver';
|
||||||
import { EarlyAccessType, FeatureService } from './service';
|
import { FeatureService } from './service';
|
||||||
|
|
||||||
@Module({
|
@Module({
|
||||||
imports: [EntitlementModule],
|
imports: [EntitlementModule],
|
||||||
@@ -18,5 +18,5 @@ import { EarlyAccessType, FeatureService } from './service';
|
|||||||
})
|
})
|
||||||
export class FeatureModule {}
|
export class FeatureModule {}
|
||||||
|
|
||||||
export { EarlyAccessType, FeatureService };
|
export { FeatureService };
|
||||||
export { AvailableUserFeatureConfig } from './types';
|
export { AvailableUserFeatureConfig } from './types';
|
||||||
@@ -4,11 +4,6 @@ import { Models } from '../../models';
|
|||||||
|
|
||||||
const STAFF = ['@toeverything.info', '@affine.pro'];
|
const STAFF = ['@toeverything.info', '@affine.pro'];
|
||||||
|
|
||||||
export enum EarlyAccessType {
|
|
||||||
App = 'app',
|
|
||||||
AI = 'ai',
|
|
||||||
}
|
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class FeatureService {
|
export class FeatureService {
|
||||||
protected logger = new Logger(FeatureService.name);
|
protected logger = new Logger(FeatureService.name);
|
||||||
@@ -32,15 +27,4 @@ export class FeatureService {
|
|||||||
addAdmin(userId: string) {
|
addAdmin(userId: string) {
|
||||||
return this.models.userFeature.add(userId, 'administrator', 'Admin user');
|
return this.models.userFeature.add(userId, 'administrator', 'Admin user');
|
||||||
}
|
}
|
||||||
|
|
||||||
// ======== Early Access ========
|
|
||||||
async isEarlyAccessUser(
|
|
||||||
userId: string,
|
|
||||||
type: EarlyAccessType = EarlyAccessType.App
|
|
||||||
) {
|
|
||||||
return await this.models.userFeature.has(
|
|
||||||
userId,
|
|
||||||
type === EarlyAccessType.App ? 'early_access' : 'ai_early_access'
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@@ -5,14 +5,10 @@ import { Feature, UserFeatureName } from '../../models';
|
|||||||
@Injectable()
|
@Injectable()
|
||||||
export class AvailableUserFeatureConfig {
|
export class AvailableUserFeatureConfig {
|
||||||
availableUserFeatures(): Set<UserFeatureName> {
|
availableUserFeatures(): Set<UserFeatureName> {
|
||||||
return new Set([Feature.Admin, Feature.EarlyAccess, Feature.AIEarlyAccess]);
|
return new Set([Feature.Admin]);
|
||||||
}
|
}
|
||||||
|
|
||||||
configurableUserFeatures(): Set<UserFeatureName> {
|
configurableUserFeatures(): Set<UserFeatureName> {
|
||||||
return new Set(
|
return new Set([Feature.Admin]);
|
||||||
env.selfhosted
|
|
||||||
? [Feature.Admin]
|
|
||||||
: [Feature.EarlyAccess, Feature.AIEarlyAccess, Feature.Admin]
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,11 +1,4 @@
|
|||||||
import {
|
import { Args, ID, Mutation, ResolveField, Resolver } from '@nestjs/graphql';
|
||||||
Args,
|
|
||||||
ID,
|
|
||||||
Int,
|
|
||||||
Mutation,
|
|
||||||
ResolveField,
|
|
||||||
Resolver,
|
|
||||||
} from '@nestjs/graphql';
|
|
||||||
|
|
||||||
import {
|
import {
|
||||||
MentionUserDocAccessDenied,
|
MentionUserDocAccessDenied,
|
||||||
@@ -45,16 +38,6 @@ export class UserNotificationResolver {
|
|||||||
return paginate(notifications, 'createdAt', pagination, totalCount);
|
return paginate(notifications, 'createdAt', pagination, totalCount);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ResolveField(() => Int, {
|
|
||||||
description: 'Get user notification count',
|
|
||||||
deprecationReason:
|
|
||||||
'Use realtime subscription "notification.count.changed" instead.',
|
|
||||||
})
|
|
||||||
async notificationCount(@CurrentUser() me: UserType): Promise<number> {
|
|
||||||
// DEPRECATED-0.26-COMPAT(realtime): remove after server no longer supports 0.26.x clients.
|
|
||||||
return await this.service.countByUserId(me.id);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Mutation(() => ID, {
|
@Mutation(() => ID, {
|
||||||
description: 'mention user in a doc',
|
description: 'mention user in a doc',
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -155,7 +155,6 @@ test('quota service exposes history period in seconds', async t => {
|
|||||||
usedStorageQuota: 0,
|
usedStorageQuota: 0,
|
||||||
memberCount: 1,
|
memberCount: 1,
|
||||||
overcapacityMemberCount: 0,
|
overcapacityMemberCount: 0,
|
||||||
usedSize: 0,
|
|
||||||
}).historyPeriod,
|
}).historyPeriod,
|
||||||
'30 days'
|
'30 days'
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -76,7 +76,6 @@ export class QuotaService {
|
|||||||
usedStorageQuota: Number(state.usedStorageQuota),
|
usedStorageQuota: Number(state.usedStorageQuota),
|
||||||
memberCount: state.memberCount,
|
memberCount: state.memberCount,
|
||||||
overcapacityMemberCount: state.overcapacityMemberCount,
|
overcapacityMemberCount: state.overcapacityMemberCount,
|
||||||
usedSize: Number(state.usedStorageQuota),
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -118,12 +118,4 @@ export class WorkspaceQuotaType implements Partial<WorkspaceQuota> {
|
|||||||
|
|
||||||
@Field()
|
@Field()
|
||||||
humanReadable!: WorkspaceQuotaHumanReadableType;
|
humanReadable!: WorkspaceQuotaHumanReadableType;
|
||||||
|
|
||||||
/**
|
|
||||||
* @deprecated
|
|
||||||
*/
|
|
||||||
@Field(() => SafeIntResolver, {
|
|
||||||
deprecationReason: 'use `usedStorageQuota` instead',
|
|
||||||
})
|
|
||||||
usedSize!: number;
|
|
||||||
}
|
}
|
||||||
@@ -166,16 +166,6 @@ export class InviteResult {
|
|||||||
})
|
})
|
||||||
inviteId?: string;
|
inviteId?: string;
|
||||||
|
|
||||||
/**
|
|
||||||
* @deprecated
|
|
||||||
*/
|
|
||||||
@Field(() => Boolean, {
|
|
||||||
description: 'Invite email sent success',
|
|
||||||
deprecationReason: 'Notification will be sent asynchronously',
|
|
||||||
defaultValue: true,
|
|
||||||
})
|
|
||||||
sentSuccess?: boolean;
|
|
||||||
|
|
||||||
@Field(() => GraphQLJSONObject, {
|
@Field(() => GraphQLJSONObject, {
|
||||||
nullable: true,
|
nullable: true,
|
||||||
description: 'Invite error',
|
description: 'Invite error',
|
||||||
|
|||||||
@@ -43,8 +43,6 @@ export enum FeatureType {
|
|||||||
export enum Feature {
|
export enum Feature {
|
||||||
// user
|
// user
|
||||||
Admin = 'administrator',
|
Admin = 'administrator',
|
||||||
EarlyAccess = 'early_access',
|
|
||||||
AIEarlyAccess = 'ai_early_access',
|
|
||||||
UnlimitedCopilot = 'unlimited_copilot',
|
UnlimitedCopilot = 'unlimited_copilot',
|
||||||
FreePlan = 'free_plan_v1',
|
FreePlan = 'free_plan_v1',
|
||||||
ProPlan = 'pro_plan_v1',
|
ProPlan = 'pro_plan_v1',
|
||||||
@@ -58,10 +56,8 @@ export enum Feature {
|
|||||||
|
|
||||||
// TODO(@forehalo): may merge `FeatureShapes` and `FeatureConfigs`?
|
// TODO(@forehalo): may merge `FeatureShapes` and `FeatureConfigs`?
|
||||||
export const FeaturesShapes = {
|
export const FeaturesShapes = {
|
||||||
early_access: z.object({ whitelist: z.array(z.string()).readonly() }),
|
|
||||||
unlimited_workspace: EMPTY_CONFIG,
|
unlimited_workspace: EMPTY_CONFIG,
|
||||||
unlimited_copilot: EMPTY_CONFIG,
|
unlimited_copilot: EMPTY_CONFIG,
|
||||||
ai_early_access: EMPTY_CONFIG,
|
|
||||||
administrator: EMPTY_CONFIG,
|
administrator: EMPTY_CONFIG,
|
||||||
free_plan_v1: UserPlanQuotaConfig,
|
free_plan_v1: UserPlanQuotaConfig,
|
||||||
pro_plan_v1: UserPlanQuotaConfig,
|
pro_plan_v1: UserPlanQuotaConfig,
|
||||||
@@ -72,8 +68,6 @@ export const FeaturesShapes = {
|
|||||||
|
|
||||||
export type UserFeatureName = keyof Pick<
|
export type UserFeatureName = keyof Pick<
|
||||||
typeof FeaturesShapes,
|
typeof FeaturesShapes,
|
||||||
| 'early_access'
|
|
||||||
| 'ai_early_access'
|
|
||||||
| 'unlimited_copilot'
|
| 'unlimited_copilot'
|
||||||
| 'administrator'
|
| 'administrator'
|
||||||
| 'free_plan_v1'
|
| 'free_plan_v1'
|
||||||
@@ -142,11 +136,6 @@ const TeamFeature = {
|
|||||||
},
|
},
|
||||||
} as const;
|
} as const;
|
||||||
|
|
||||||
const WhitelistFeature = {
|
|
||||||
type: FeatureType.Feature,
|
|
||||||
configs: { whitelist: [] },
|
|
||||||
} as const;
|
|
||||||
|
|
||||||
const EmptyFeature = {
|
const EmptyFeature = {
|
||||||
type: FeatureType.Feature,
|
type: FeatureType.Feature,
|
||||||
configs: {},
|
configs: {},
|
||||||
@@ -164,10 +153,8 @@ export const FeatureConfigs: {
|
|||||||
pro_plan_v1: ProFeature,
|
pro_plan_v1: ProFeature,
|
||||||
lifetime_pro_plan_v1: LifetimeProFeature,
|
lifetime_pro_plan_v1: LifetimeProFeature,
|
||||||
team_plan_v1: TeamFeature,
|
team_plan_v1: TeamFeature,
|
||||||
early_access: WhitelistFeature,
|
|
||||||
unlimited_workspace: EmptyFeature,
|
unlimited_workspace: EmptyFeature,
|
||||||
quota_exceeded_readonly_workspace_v1: EmptyFeature,
|
quota_exceeded_readonly_workspace_v1: EmptyFeature,
|
||||||
unlimited_copilot: EmptyFeature,
|
unlimited_copilot: EmptyFeature,
|
||||||
ai_early_access: EmptyFeature,
|
|
||||||
administrator: EmptyFeature,
|
administrator: EmptyFeature,
|
||||||
};
|
};
|
||||||
@@ -9,8 +9,16 @@ import serverNativeModule, {
|
|||||||
type CanonicalStructuredRequestContract,
|
type CanonicalStructuredRequestContract,
|
||||||
type CapabilityAttachmentContract,
|
type CapabilityAttachmentContract,
|
||||||
type CapabilityModelCapability,
|
type CapabilityModelCapability,
|
||||||
|
type CommandResponse,
|
||||||
type ImageInspection,
|
type ImageInspection,
|
||||||
type ImageInspectionOptions,
|
type ImageInspectionOptions,
|
||||||
|
type LicenseError,
|
||||||
|
type LicenseHealthRequest,
|
||||||
|
type LicenseInfo,
|
||||||
|
type LicenseKeyRequest,
|
||||||
|
type LicenseRecurringRequest,
|
||||||
|
type LicenseResponse,
|
||||||
|
type LicenseSeatsRequest,
|
||||||
type LlmCoreMessage,
|
type LlmCoreMessage,
|
||||||
type LlmEmbeddingRequestContract,
|
type LlmEmbeddingRequestContract,
|
||||||
type LlmImageRequestContract,
|
type LlmImageRequestContract,
|
||||||
@@ -20,6 +28,7 @@ import serverNativeModule, {
|
|||||||
type ModelConditionsContract,
|
type ModelConditionsContract,
|
||||||
type ModelRegistryMatchResponse,
|
type ModelRegistryMatchResponse,
|
||||||
type ModelRegistryResolveResponse,
|
type ModelRegistryResolveResponse,
|
||||||
|
type PortalResponse,
|
||||||
type PromptMessageContract,
|
type PromptMessageContract,
|
||||||
type PromptMetadataContract,
|
type PromptMetadataContract,
|
||||||
type PromptMetadataResult,
|
type PromptMetadataResult,
|
||||||
@@ -45,9 +54,18 @@ export type {
|
|||||||
AssertSafeUrlRequest,
|
AssertSafeUrlRequest,
|
||||||
CapabilityAttachmentContract,
|
CapabilityAttachmentContract,
|
||||||
CapabilityModelCapability,
|
CapabilityModelCapability,
|
||||||
|
CommandResponse,
|
||||||
ImageInspection,
|
ImageInspection,
|
||||||
ImageInspectionOptions,
|
ImageInspectionOptions,
|
||||||
|
LicenseError,
|
||||||
|
LicenseHealthRequest,
|
||||||
|
LicenseInfo,
|
||||||
|
LicenseKeyRequest,
|
||||||
|
LicenseRecurringRequest,
|
||||||
|
LicenseResponse,
|
||||||
|
LicenseSeatsRequest,
|
||||||
ModelConditionsContract,
|
ModelConditionsContract,
|
||||||
|
PortalResponse,
|
||||||
PromptMessageContract,
|
PromptMessageContract,
|
||||||
PromptStructuredResponseContract,
|
PromptStructuredResponseContract,
|
||||||
RemoteAttachmentFetchRequest,
|
RemoteAttachmentFetchRequest,
|
||||||
@@ -143,6 +161,13 @@ export const fetchRemoteAttachment = serverNativeModule.fetchRemoteAttachment;
|
|||||||
export const inferRemoteMimeType = serverNativeModule.inferRemoteMimeType;
|
export const inferRemoteMimeType = serverNativeModule.inferRemoteMimeType;
|
||||||
export const assertSafeUrl = serverNativeModule.assertSafeUrl;
|
export const assertSafeUrl = serverNativeModule.assertSafeUrl;
|
||||||
export const safeFetch = serverNativeModule.safeFetch;
|
export const safeFetch = serverNativeModule.safeFetch;
|
||||||
|
export const activateLicense = serverNativeModule.activateLicense;
|
||||||
|
export const checkLicenseHealth = serverNativeModule.checkLicenseHealth;
|
||||||
|
export const createCustomerPortal =
|
||||||
|
serverNativeModule.createLicenseCustomerPortal;
|
||||||
|
export const deactivateLicense = serverNativeModule.deactivateLicense;
|
||||||
|
export const updateLicenseRecurring = serverNativeModule.updateLicenseRecurring;
|
||||||
|
export const updateLicenseSeats = serverNativeModule.updateLicenseSeats;
|
||||||
export const parseDoc = serverNativeModule.parseDoc;
|
export const parseDoc = serverNativeModule.parseDoc;
|
||||||
export const htmlSanitize = serverNativeModule.htmlSanitize;
|
export const htmlSanitize = serverNativeModule.htmlSanitize;
|
||||||
export const processImage = serverNativeModule.processImage;
|
export const processImage = serverNativeModule.processImage;
|
||||||
|
|||||||
@@ -5,9 +5,9 @@ import { XMLParser } from 'fast-xml-parser';
|
|||||||
import { escape } from 'lodash-es';
|
import { escape } from 'lodash-es';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
assertSsrFSafeUrl,
|
|
||||||
CalendarProviderRequestError,
|
CalendarProviderRequestError,
|
||||||
GraphqlBadRequest,
|
GraphqlBadRequest,
|
||||||
|
safeFetch,
|
||||||
SsrfBlockedError,
|
SsrfBlockedError,
|
||||||
} from '../../../base';
|
} from '../../../base';
|
||||||
import type {
|
import type {
|
||||||
@@ -35,6 +35,8 @@ const XML_PARSER = new XMLParser({
|
|||||||
|
|
||||||
const DEFAULT_REQUEST_TIMEOUT_MS = 10_000;
|
const DEFAULT_REQUEST_TIMEOUT_MS = 10_000;
|
||||||
const DEFAULT_MAX_REDIRECTS = 5;
|
const DEFAULT_MAX_REDIRECTS = 5;
|
||||||
|
const DEFAULT_MAX_RESPONSE_BYTES = 50 * 1024 * 1024;
|
||||||
|
const CALDAV_SAFE_FETCH_HEADERS = ['authorization', 'content-type', 'depth'];
|
||||||
|
|
||||||
type CalDAVCredentials = {
|
type CalDAVCredentials = {
|
||||||
username: string;
|
username: string;
|
||||||
@@ -106,9 +108,6 @@ const formatUtcForIcal = (iso: string) => {
|
|||||||
].join('');
|
].join('');
|
||||||
};
|
};
|
||||||
|
|
||||||
const isRedirectStatus = (status: number) =>
|
|
||||||
[301, 302, 303, 307, 308].includes(status);
|
|
||||||
|
|
||||||
const splitHeaderTokens = (value: string) =>
|
const splitHeaderTokens = (value: string) =>
|
||||||
value
|
value
|
||||||
.split(/,(?=(?:[^"]*"[^"]*")*[^"]*$)/)
|
.split(/,(?=(?:[^"]*"[^"]*")*[^"]*$)/)
|
||||||
@@ -536,36 +535,24 @@ class CalDAVRequestPolicy {
|
|||||||
return this.config.calendar.caldav.maxRedirects ?? DEFAULT_MAX_REDIRECTS;
|
return this.config.calendar.caldav.maxRedirects ?? DEFAULT_MAX_REDIRECTS;
|
||||||
}
|
}
|
||||||
|
|
||||||
async fetch(
|
async fetch(url: string, init: RequestInit): Promise<Response> {
|
||||||
url: string,
|
|
||||||
init: RequestInit,
|
|
||||||
redirects = 0
|
|
||||||
): Promise<Response> {
|
|
||||||
await this.assertAllowedUrl(url);
|
await this.assertAllowedUrl(url);
|
||||||
|
|
||||||
const controller = new AbortController();
|
try {
|
||||||
const timer = setTimeout(() => controller.abort(), this.timeoutMs);
|
return await safeFetch(url, init, {
|
||||||
const response = await fetch(url, {
|
timeoutMs: this.timeoutMs,
|
||||||
...init,
|
maxRedirects: this.maxRedirects,
|
||||||
redirect: 'manual',
|
maxBytes: DEFAULT_MAX_RESPONSE_BYTES,
|
||||||
signal: controller.signal,
|
allowedHeaders: CALDAV_SAFE_FETCH_HEADERS,
|
||||||
}).finally(() => clearTimeout(timer));
|
allowedHosts: this.allowedHosts,
|
||||||
|
allowHttp: this.allowInsecureHttp,
|
||||||
if (isRedirectStatus(response.status)) {
|
allowPrivateTargetOrigin: !this.blockPrivateNetwork,
|
||||||
const location = response.headers.get('location');
|
|
||||||
if (location) {
|
|
||||||
if (redirects >= this.maxRedirects) {
|
|
||||||
throw new GraphqlBadRequest({
|
|
||||||
code: 'caldav_max_redirects',
|
|
||||||
message: 'CalDAV request exceeded redirect limit.',
|
|
||||||
});
|
});
|
||||||
|
} catch (error) {
|
||||||
|
const ssrfError = this.toGraphqlSsrfError(error);
|
||||||
|
if (ssrfError) throw ssrfError;
|
||||||
|
throw error;
|
||||||
}
|
}
|
||||||
const nextUrl = resolveHref(location, url);
|
|
||||||
return this.fetch(nextUrl, init, redirects + 1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return response;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private async assertAllowedUrl(urlValue: string) {
|
private async assertAllowedUrl(urlValue: string) {
|
||||||
@@ -599,52 +586,62 @@ class CalDAVRequestPolicy {
|
|||||||
message: 'CalDAV host is not allowed.',
|
message: 'CalDAV host is not allowed.',
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!this.blockPrivateNetwork) {
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
private toGraphqlSsrfError(error: unknown) {
|
||||||
await assertSsrFSafeUrl(url);
|
if (!(error instanceof SsrfBlockedError)) {
|
||||||
} catch (error) {
|
return null;
|
||||||
if (error instanceof SsrfBlockedError) {
|
}
|
||||||
|
|
||||||
const reason = String(error.data?.reason ?? '');
|
const reason = String(error.data?.reason ?? '');
|
||||||
|
|
||||||
if (reason === 'blocked_ip') {
|
if (reason === 'blocked_ip') {
|
||||||
throw new GraphqlBadRequest({
|
return new GraphqlBadRequest({
|
||||||
code: 'caldav_private_network',
|
code: 'caldav_private_network',
|
||||||
message: 'CalDAV host is in a private network.',
|
message: 'CalDAV host is in a private network.',
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (reason === 'unresolvable_hostname') {
|
if (reason === 'unresolvable_hostname') {
|
||||||
throw new GraphqlBadRequest({
|
return new GraphqlBadRequest({
|
||||||
code: 'caldav_dns_failed',
|
code: 'caldav_dns_failed',
|
||||||
message: 'Unable to resolve CalDAV host.',
|
message: 'Unable to resolve CalDAV host.',
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (reason === 'disallowed_protocol') {
|
if (reason === 'disallowed_protocol') {
|
||||||
throw new GraphqlBadRequest({
|
return new GraphqlBadRequest({
|
||||||
code: 'caldav_insecure_url',
|
code: 'caldav_insecure_url',
|
||||||
message: 'CalDAV URL must use https.',
|
message: 'CalDAV URL must use https.',
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (reason === 'too_many_redirects') {
|
||||||
|
return new GraphqlBadRequest({
|
||||||
|
code: 'caldav_max_redirects',
|
||||||
|
message: 'CalDAV request exceeded redirect limit.',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (reason === 'host_not_allowed') {
|
||||||
|
return new GraphqlBadRequest({
|
||||||
|
code: 'caldav_host_blocked',
|
||||||
|
message: 'CalDAV host is not allowed.',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
if (
|
if (
|
||||||
reason === 'invalid_url' ||
|
reason === 'invalid_url' ||
|
||||||
reason === 'blocked_hostname' ||
|
reason === 'blocked_hostname' ||
|
||||||
reason === 'url_has_credentials'
|
reason === 'url_has_credentials'
|
||||||
) {
|
) {
|
||||||
throw new GraphqlBadRequest({
|
return new GraphqlBadRequest({
|
||||||
code: 'caldav_invalid_url',
|
code: 'caldav_invalid_url',
|
||||||
message: 'CalDAV URL is invalid.',
|
message: 'CalDAV URL is invalid.',
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
throw error;
|
return null;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2,7 +2,13 @@ import { createHash, createHmac, randomUUID } from 'node:crypto';
|
|||||||
|
|
||||||
import { BadRequestException, Injectable } from '@nestjs/common';
|
import { BadRequestException, Injectable } from '@nestjs/common';
|
||||||
|
|
||||||
import { BadRequest, Cache, CryptoHelper, metrics } from '../../../base';
|
import {
|
||||||
|
BadRequest,
|
||||||
|
Cache,
|
||||||
|
CryptoHelper,
|
||||||
|
metrics,
|
||||||
|
safeFetch,
|
||||||
|
} from '../../../base';
|
||||||
import { Models } from '../../../models';
|
import { Models } from '../../../models';
|
||||||
import type { CopilotProviderProfile } from '../config';
|
import type { CopilotProviderProfile } from '../config';
|
||||||
import { ByokEntitlementPolicy } from './policy';
|
import { ByokEntitlementPolicy } from './policy';
|
||||||
@@ -103,6 +109,8 @@ type ByokProfileMeta = {
|
|||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class ByokService {
|
export class ByokService {
|
||||||
|
private readonly probeFetch = safeFetch;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private readonly models: Models,
|
private readonly models: Models,
|
||||||
private readonly crypto: CryptoHelper,
|
private readonly crypto: CryptoHelper,
|
||||||
@@ -755,23 +763,25 @@ export class ByokService {
|
|||||||
apiKey: string,
|
apiKey: string,
|
||||||
endpoint: string | null
|
endpoint: string | null
|
||||||
) {
|
) {
|
||||||
const controller = new AbortController();
|
|
||||||
const timeout = setTimeout(() => controller.abort(), TEST_TIMEOUT_MS);
|
|
||||||
try {
|
|
||||||
const request = this.buildProbeRequest(provider, apiKey, endpoint);
|
const request = this.buildProbeRequest(provider, apiKey, endpoint);
|
||||||
const response = await fetch(request.url, {
|
const response = await this.probeFetch(
|
||||||
|
request.url,
|
||||||
|
{
|
||||||
method: request.method,
|
method: request.method,
|
||||||
headers: request.headers as unknown as Record<string, string>,
|
headers: request.headers as unknown as Record<string, string>,
|
||||||
signal: controller.signal,
|
},
|
||||||
});
|
{
|
||||||
|
timeoutMs: TEST_TIMEOUT_MS,
|
||||||
|
maxRedirects: 3,
|
||||||
|
maxBytes: 1024,
|
||||||
|
allowedHeaders: Object.keys(request.headers),
|
||||||
|
}
|
||||||
|
);
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
throw new BadRequestException(
|
throw new BadRequestException(
|
||||||
this.providerProbeFailureMessage(response.status)
|
this.providerProbeFailureMessage(response.status)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
} finally {
|
|
||||||
clearTimeout(timeout);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private buildProbeRequest(
|
private buildProbeRequest(
|
||||||
|
|||||||
@@ -66,12 +66,12 @@ export class CopilotEmbeddingRealtimeProvider implements OnModuleInit {
|
|||||||
|
|
||||||
@OnEvent('workspace.file.embed.finished', { suppressError: true })
|
@OnEvent('workspace.file.embed.finished', { suppressError: true })
|
||||||
async onFileEmbedFinished(payload: Events['workspace.file.embed.finished']) {
|
async onFileEmbedFinished(payload: Events['workspace.file.embed.finished']) {
|
||||||
await this.publishContext(payload.contextId, 'finished');
|
await this.publishEmbeddingProgress(payload, 'finished');
|
||||||
}
|
}
|
||||||
|
|
||||||
@OnEvent('workspace.file.embed.failed', { suppressError: true })
|
@OnEvent('workspace.file.embed.failed', { suppressError: true })
|
||||||
async onFileEmbedFailed(payload: Events['workspace.file.embed.failed']) {
|
async onFileEmbedFailed(payload: Events['workspace.file.embed.failed']) {
|
||||||
await this.publishContext(payload.contextId, 'failed');
|
await this.publishEmbeddingProgress(payload, 'failed');
|
||||||
}
|
}
|
||||||
|
|
||||||
@OnEvent('workspace.blob.embed.finished', { suppressError: true })
|
@OnEvent('workspace.blob.embed.finished', { suppressError: true })
|
||||||
@@ -90,11 +90,29 @@ export class CopilotEmbeddingRealtimeProvider implements OnModuleInit {
|
|||||||
) {
|
) {
|
||||||
if (!this.publisher) return;
|
if (!this.publisher) return;
|
||||||
const context = await this.context.get(contextId);
|
const context = await this.context.get(contextId);
|
||||||
|
this.publishWorkspace(context.workspaceId, reason);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async publishEmbeddingProgress(
|
||||||
|
payload:
|
||||||
|
| Events['workspace.file.embed.finished']
|
||||||
|
| Events['workspace.file.embed.failed'],
|
||||||
|
reason: 'finished' | 'failed'
|
||||||
|
) {
|
||||||
|
if (!this.publisher) return;
|
||||||
|
if (payload.contextId) {
|
||||||
|
await this.publishContext(payload.contextId, reason);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.publishWorkspace(payload.workspaceId, reason);
|
||||||
|
}
|
||||||
|
|
||||||
|
private publishWorkspace(workspaceId: string, reason: 'finished' | 'failed') {
|
||||||
this.publisher.publish(
|
this.publisher.publish(
|
||||||
'workspace.embedding.progress.changed',
|
'workspace.embedding.progress.changed',
|
||||||
{ workspaceId: context.workspaceId },
|
{ workspaceId },
|
||||||
{ reason },
|
{ reason },
|
||||||
{ room: workspaceEmbeddingRoom(context.workspaceId) }
|
{ room: workspaceEmbeddingRoom(workspaceId) }
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -354,6 +354,7 @@ export class CopilotContextService implements OnApplicationBootstrap {
|
|||||||
fileId,
|
fileId,
|
||||||
chunkSize,
|
chunkSize,
|
||||||
}: Events['workspace.file.embed.finished']) {
|
}: Events['workspace.file.embed.finished']) {
|
||||||
|
if (!contextId) return;
|
||||||
const context = await this.get(contextId);
|
const context = await this.get(contextId);
|
||||||
await context.saveFileRecord(fileId, file => ({
|
await context.saveFileRecord(fileId, file => ({
|
||||||
...(file as ContextFile),
|
...(file as ContextFile),
|
||||||
@@ -368,6 +369,7 @@ export class CopilotContextService implements OnApplicationBootstrap {
|
|||||||
fileId,
|
fileId,
|
||||||
error,
|
error,
|
||||||
}: Events['workspace.file.embed.failed']) {
|
}: Events['workspace.file.embed.failed']) {
|
||||||
|
if (!contextId) return;
|
||||||
const context = await this.get(contextId);
|
const context = await this.get(contextId);
|
||||||
await context.saveFileRecord(fileId, file => ({
|
await context.saveFileRecord(fileId, file => ({
|
||||||
...(file as ContextFile),
|
...(file as ContextFile),
|
||||||
|
|||||||
@@ -300,21 +300,19 @@ export class CopilotEmbeddingJob {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (contextId) {
|
|
||||||
this.event.emit('workspace.file.embed.finished', {
|
this.event.emit('workspace.file.embed.finished', {
|
||||||
contextId,
|
contextId,
|
||||||
|
workspaceId,
|
||||||
fileId,
|
fileId,
|
||||||
chunkSize: total,
|
chunkSize: total,
|
||||||
});
|
});
|
||||||
}
|
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
if (contextId) {
|
|
||||||
this.event.emit('workspace.file.embed.failed', {
|
this.event.emit('workspace.file.embed.failed', {
|
||||||
contextId,
|
contextId,
|
||||||
|
workspaceId,
|
||||||
fileId,
|
fileId,
|
||||||
error: mapAnyError(error).message,
|
error: mapAnyError(error).message,
|
||||||
});
|
});
|
||||||
}
|
|
||||||
|
|
||||||
// passthrough error to job queue
|
// passthrough error to job queue
|
||||||
throw error;
|
throw error;
|
||||||
|
|||||||
@@ -43,13 +43,15 @@ declare global {
|
|||||||
};
|
};
|
||||||
|
|
||||||
'workspace.file.embed.finished': {
|
'workspace.file.embed.finished': {
|
||||||
contextId: string;
|
contextId?: string;
|
||||||
|
workspaceId: string;
|
||||||
fileId: string;
|
fileId: string;
|
||||||
chunkSize: number;
|
chunkSize: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
'workspace.file.embed.failed': {
|
'workspace.file.embed.failed': {
|
||||||
contextId: string;
|
contextId?: string;
|
||||||
|
workspaceId: string;
|
||||||
fileId: string;
|
fileId: string;
|
||||||
error: string;
|
error: string;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,22 +0,0 @@
|
|||||||
import { defineModuleConfig } from '../../base';
|
|
||||||
|
|
||||||
declare global {
|
|
||||||
interface AppConfigSchema {
|
|
||||||
customerIo: {
|
|
||||||
enabled: boolean;
|
|
||||||
token: string;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
defineModuleConfig('customerIo', {
|
|
||||||
enabled: {
|
|
||||||
desc: 'Enable customer.io integration',
|
|
||||||
default: false,
|
|
||||||
},
|
|
||||||
token: {
|
|
||||||
desc: 'Customer.io token',
|
|
||||||
default: '',
|
|
||||||
schema: { type: 'string' },
|
|
||||||
},
|
|
||||||
});
|
|
||||||
@@ -1,10 +0,0 @@
|
|||||||
import './config';
|
|
||||||
|
|
||||||
import { Module } from '@nestjs/common';
|
|
||||||
|
|
||||||
import { CustomerIoService } from './service';
|
|
||||||
|
|
||||||
@Module({
|
|
||||||
providers: [CustomerIoService],
|
|
||||||
})
|
|
||||||
export class CustomerIoModule {}
|
|
||||||
@@ -1,72 +0,0 @@
|
|||||||
import { Injectable } from '@nestjs/common';
|
|
||||||
|
|
||||||
import { Config, OnEvent } from '../../base';
|
|
||||||
|
|
||||||
@Injectable()
|
|
||||||
export class CustomerIoService {
|
|
||||||
#fetch: ((url: string, options?: RequestInit) => Promise<Response>) | null =
|
|
||||||
null;
|
|
||||||
constructor(private readonly config: Config) {}
|
|
||||||
|
|
||||||
@OnEvent('config.init')
|
|
||||||
setup() {
|
|
||||||
const { enabled, token } = this.config.customerIo;
|
|
||||||
if (enabled && token) {
|
|
||||||
this.#fetch = (url, options) => {
|
|
||||||
return fetch(url, {
|
|
||||||
...options,
|
|
||||||
headers: {
|
|
||||||
Authorization: `Basic ${token}`,
|
|
||||||
'Content-Type': 'application/json',
|
|
||||||
...options?.headers,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
};
|
|
||||||
} else {
|
|
||||||
this.#fetch = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@OnEvent('config.changed')
|
|
||||||
onConfigChanged(event: Events['config.changed']) {
|
|
||||||
if (event.updates.customerIo) {
|
|
||||||
this.setup();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@OnEvent('user.created')
|
|
||||||
@OnEvent('user.updated')
|
|
||||||
async onUserUpdated(user: Events['user.updated'] | Events['user.created']) {
|
|
||||||
await this.#fetch?.(
|
|
||||||
`https://track.customer.io/api/v1/customers/${user.id}`,
|
|
||||||
{
|
|
||||||
method: 'PUT',
|
|
||||||
body: JSON.stringify({
|
|
||||||
name: user.name,
|
|
||||||
email: user.email,
|
|
||||||
created_at: Number(user.createdAt) / 1000,
|
|
||||||
}),
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
@OnEvent('user.deleted')
|
|
||||||
async onUserDeleted(user: Events['user.deleted']) {
|
|
||||||
if (user.emailVerifiedAt) {
|
|
||||||
// suppress email if email is verified
|
|
||||||
await this.#fetch?.(
|
|
||||||
`https://track.customer.io/api/v1/customers/${user.email}/suppress`,
|
|
||||||
{
|
|
||||||
method: 'POST',
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
await this.#fetch?.(
|
|
||||||
`https://track.customer.io/api/v1/customers/${user.id}`,
|
|
||||||
{
|
|
||||||
method: 'DELETE',
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
+35
@@ -1803,6 +1803,41 @@ test('should parse es query with custom term mapping field work', async t => {
|
|||||||
t.snapshot(result3);
|
t.snapshot(result3);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('should parse es query with parent and nested must_not work', async t => {
|
||||||
|
const nestedMust = {
|
||||||
|
must: [
|
||||||
|
{ term: { workspace_id: 'workspaceId1' } },
|
||||||
|
{ bool: { must_not: { term: { doc_id: 'docId1' } } } },
|
||||||
|
],
|
||||||
|
};
|
||||||
|
const parentMustNot = { term: { doc_id: 'docId2' } };
|
||||||
|
const expectedMust = [{ equals: { workspace_id: 'workspaceId1' } }];
|
||||||
|
const expectedMustNot = ['docId1', 'docId2'];
|
||||||
|
|
||||||
|
const queryWithParentMustNotFirst = {
|
||||||
|
bool: { must_not: parentMustNot, must: nestedMust.must },
|
||||||
|
};
|
||||||
|
const queryWithParentMustNotLast = {
|
||||||
|
bool: { must: nestedMust.must, must_not: parentMustNot },
|
||||||
|
};
|
||||||
|
|
||||||
|
// @ts-expect-error use private method
|
||||||
|
const result = searchProvider.parseESQuery(queryWithParentMustNotFirst);
|
||||||
|
// @ts-expect-error use private method
|
||||||
|
const result2 = searchProvider.parseESQuery(queryWithParentMustNotLast);
|
||||||
|
|
||||||
|
t.deepEqual(result.bool.must, expectedMust);
|
||||||
|
t.deepEqual(result2.bool.must, expectedMust);
|
||||||
|
t.deepEqual(
|
||||||
|
result.bool.must_not.map((clause: any) => clause.equals.doc_id).sort(),
|
||||||
|
expectedMustNot
|
||||||
|
);
|
||||||
|
t.deepEqual(
|
||||||
|
result2.bool.must_not.map((clause: any) => clause.equals.doc_id).sort(),
|
||||||
|
expectedMustNot
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
test('should parse es query exists work', async t => {
|
test('should parse es query exists work', async t => {
|
||||||
const query = {
|
const query = {
|
||||||
exists: {
|
exists: {
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import { Injectable } from '@nestjs/common';
|
|||||||
import {
|
import {
|
||||||
InternalServerError,
|
InternalServerError,
|
||||||
InvalidSearchProviderRequest,
|
InvalidSearchProviderRequest,
|
||||||
|
safeFetch,
|
||||||
} from '../../../base';
|
} from '../../../base';
|
||||||
import { SearchProviderType } from '../config';
|
import { SearchProviderType } from '../config';
|
||||||
import { DateFieldNames, SearchTable, SearchTableUniqueId } from '../tables';
|
import { DateFieldNames, SearchTable, SearchTableUniqueId } from '../tables';
|
||||||
@@ -61,6 +62,14 @@ interface ESAggregateResponse extends ESSearchResponse {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const INDEXER_FETCH_OPTIONS = {
|
||||||
|
timeoutMs: 30_000,
|
||||||
|
maxRedirects: 0,
|
||||||
|
maxBytes: 50 * 1024 * 1024,
|
||||||
|
allowedHeaders: ['authorization', 'content-type'],
|
||||||
|
allowPrivateTargetOrigin: true,
|
||||||
|
};
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class ElasticsearchProvider extends SearchProvider {
|
export class ElasticsearchProvider extends SearchProvider {
|
||||||
type = SearchProviderType.Elasticsearch;
|
type = SearchProviderType.Elasticsearch;
|
||||||
@@ -278,11 +287,11 @@ export class ElasticsearchProvider extends SearchProvider {
|
|||||||
} else if (this.config.provider.password) {
|
} else if (this.config.provider.password) {
|
||||||
headers.Authorization = `Basic ${Buffer.from(`${this.config.provider.username}:${this.config.provider.password}`).toString('base64')}`;
|
headers.Authorization = `Basic ${Buffer.from(`${this.config.provider.username}:${this.config.provider.password}`).toString('base64')}`;
|
||||||
}
|
}
|
||||||
const response = await fetch(url, {
|
const response = await safeFetch(
|
||||||
method,
|
url,
|
||||||
body,
|
{ method, body, headers },
|
||||||
headers,
|
INDEXER_FETCH_OPTIONS
|
||||||
});
|
);
|
||||||
const data = await response.json();
|
const data = await response.json();
|
||||||
if (ignoreErrorStatus?.includes(response.status)) {
|
if (ignoreErrorStatus?.includes(response.status)) {
|
||||||
return data;
|
return data;
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { Injectable } from '@nestjs/common';
|
import { Injectable } from '@nestjs/common';
|
||||||
import { omit } from 'lodash-es';
|
import { omit } from 'lodash-es';
|
||||||
|
|
||||||
import { InternalServerError } from '../../../base';
|
import { InternalServerError, safeFetch } from '../../../base';
|
||||||
import { SearchProviderType } from '../config';
|
import { SearchProviderType } from '../config';
|
||||||
import { SearchTable } from '../tables';
|
import { SearchTable } from '../tables';
|
||||||
import {
|
import {
|
||||||
@@ -32,6 +32,14 @@ interface MSSearchResponse {
|
|||||||
scroll: string;
|
scroll: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const INDEXER_FETCH_OPTIONS = {
|
||||||
|
timeoutMs: 30_000,
|
||||||
|
maxRedirects: 0,
|
||||||
|
maxBytes: 50 * 1024 * 1024,
|
||||||
|
allowedHeaders: ['authorization', 'content-type'],
|
||||||
|
allowPrivateTargetOrigin: true,
|
||||||
|
};
|
||||||
|
|
||||||
const SupportIndexedAttributes = [
|
const SupportIndexedAttributes = [
|
||||||
'flavour',
|
'flavour',
|
||||||
'parent_flavour',
|
'parent_flavour',
|
||||||
@@ -58,6 +66,11 @@ const ConvertEmptyStringToNullValueFields = new Set([
|
|||||||
'parent_flavour',
|
'parent_flavour',
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
function boolClauses(value: unknown) {
|
||||||
|
if (value === undefined) return [];
|
||||||
|
return Array.isArray(value) ? value : [value];
|
||||||
|
}
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class ManticoresearchProvider extends ElasticsearchProvider {
|
export class ManticoresearchProvider extends ElasticsearchProvider {
|
||||||
override type = SearchProviderType.Manticoresearch;
|
override type = SearchProviderType.Manticoresearch;
|
||||||
@@ -282,7 +295,9 @@ export class ManticoresearchProvider extends ElasticsearchProvider {
|
|||||||
for (const occur in query.bool) {
|
for (const occur in query.bool) {
|
||||||
const conditions = query.bool[occur];
|
const conditions = query.bool[occur];
|
||||||
if (Array.isArray(conditions)) {
|
if (Array.isArray(conditions)) {
|
||||||
node.bool[occur] = [];
|
const parsedConditions: Record<string, any>[] = [];
|
||||||
|
const existing = node.bool[occur];
|
||||||
|
node.bool[occur] = parsedConditions;
|
||||||
// { must: [ { term: [Object] }, { bool: [Object] } ] }
|
// { must: [ { term: [Object] }, { bool: [Object] } ] }
|
||||||
// {
|
// {
|
||||||
// must: [ { term: [Object] }, { term: [Object] }, { bool: [Object] } ]
|
// must: [ { term: [Object] }, { term: [Object] }, { bool: [Object] } ]
|
||||||
@@ -290,16 +305,41 @@ export class ManticoresearchProvider extends ElasticsearchProvider {
|
|||||||
for (const item of conditions) {
|
for (const item of conditions) {
|
||||||
this.parseESQuery(item, {
|
this.parseESQuery(item, {
|
||||||
...options,
|
...options,
|
||||||
parentNodes: node.bool[occur],
|
parentNodes: parsedConditions,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
if (existing !== undefined) {
|
||||||
|
node.bool[occur] = [...boolClauses(existing), ...parsedConditions];
|
||||||
|
}
|
||||||
|
if (occur === 'must') {
|
||||||
|
for (let i = node.bool.must.length - 1; i >= 0; i--) {
|
||||||
|
const child = node.bool.must[i];
|
||||||
|
const childBool = child.bool;
|
||||||
|
if (
|
||||||
|
childBool &&
|
||||||
|
Object.keys(childBool).length === 1 &&
|
||||||
|
childBool.must_not !== undefined
|
||||||
|
) {
|
||||||
|
node.bool.must.splice(i, 1);
|
||||||
|
node.bool.must_not = [
|
||||||
|
...boolClauses(node.bool.must_not),
|
||||||
|
...boolClauses(childBool.must_not),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
// {
|
// {
|
||||||
// must_not: { term: { doc_id: 'docId' } }
|
// must_not: { term: { doc_id: 'docId' } }
|
||||||
// }
|
// }
|
||||||
node.bool[occur] = this.parseESQuery(conditions, {
|
const parsed = this.parseESQuery(conditions, {
|
||||||
termMappingField: options?.termMappingField,
|
termMappingField: options?.termMappingField,
|
||||||
});
|
});
|
||||||
|
if (node.bool[occur] !== undefined) {
|
||||||
|
node.bool[occur] = [...boolClauses(node.bool[occur]), parsed];
|
||||||
|
} else {
|
||||||
|
node.bool[occur] = parsed;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if (query.term) {
|
} else if (query.term) {
|
||||||
@@ -460,11 +500,11 @@ export class ManticoresearchProvider extends ElasticsearchProvider {
|
|||||||
headers.Authorization = `Basic ${Buffer.from(`${this.config.provider.username}:${this.config.provider.password}`).toString('base64')}`;
|
headers.Authorization = `Basic ${Buffer.from(`${this.config.provider.username}:${this.config.provider.password}`).toString('base64')}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
const response = await fetch(url, {
|
const response = await safeFetch(
|
||||||
method: 'POST',
|
url,
|
||||||
body: sql,
|
{ method: 'POST', body: sql, headers },
|
||||||
headers,
|
INDEXER_FETCH_OPTIONS
|
||||||
});
|
);
|
||||||
const text = (await response.text()).trim();
|
const text = (await response.text()).trim();
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
this.logger.error(`failed to execute SQL "${sql}", response: ${text}`);
|
this.logger.error(`failed to execute SQL "${sql}", response: ${text}`);
|
||||||
|
|||||||
@@ -17,21 +17,34 @@ import { EntitlementService } from '../../core/entitlement';
|
|||||||
import { WorkspacePolicyService } from '../../core/permission';
|
import { WorkspacePolicyService } from '../../core/permission';
|
||||||
import { QuotaStateService } from '../../core/quota/state';
|
import { QuotaStateService } from '../../core/quota/state';
|
||||||
import { Models } from '../../models';
|
import { Models } from '../../models';
|
||||||
import { ResolvedEntitlement, resolveEntitlementV1 } from '../../native';
|
import {
|
||||||
|
activateLicense,
|
||||||
|
checkLicenseHealth,
|
||||||
|
type CommandResponse,
|
||||||
|
createCustomerPortal,
|
||||||
|
deactivateLicense,
|
||||||
|
type LicenseError,
|
||||||
|
type LicenseResponse,
|
||||||
|
type PortalResponse,
|
||||||
|
type ResolvedEntitlement,
|
||||||
|
resolveEntitlementV1,
|
||||||
|
updateLicenseRecurring,
|
||||||
|
updateLicenseSeats,
|
||||||
|
} from '../../native';
|
||||||
import {
|
import {
|
||||||
SubscriptionPlan,
|
SubscriptionPlan,
|
||||||
SubscriptionRecurring,
|
SubscriptionRecurring,
|
||||||
SubscriptionVariant,
|
SubscriptionVariant,
|
||||||
} from '../payment/types';
|
} from '../payment/types';
|
||||||
|
|
||||||
interface License {
|
export const licenseClient = {
|
||||||
plan: SubscriptionPlan;
|
activate: activateLicense,
|
||||||
recurring: SubscriptionRecurring;
|
checkHealth: checkLicenseHealth,
|
||||||
quantity: number;
|
createCustomerPortal,
|
||||||
endAt: number;
|
deactivate: deactivateLicense,
|
||||||
}
|
updateRecurring: updateLicenseRecurring,
|
||||||
|
updateSeats: updateLicenseSeats,
|
||||||
const AFFINE_PRO_REQUEST_TIMEOUT = 10_000;
|
};
|
||||||
|
|
||||||
export interface LicensePreview {
|
export interface LicensePreview {
|
||||||
id: string;
|
id: string;
|
||||||
@@ -157,31 +170,27 @@ export class LicenseService {
|
|||||||
throw new WorkspaceLicenseAlreadyExists();
|
throw new WorkspaceLicenseAlreadyExists();
|
||||||
}
|
}
|
||||||
|
|
||||||
const data = await this.fetchAffinePro<License>(
|
const data = this.remoteLicense(
|
||||||
`/api/team/licenses/${licenseKey}/activate`,
|
await licenseClient.activate({ licenseKey })
|
||||||
{
|
|
||||||
method: 'POST',
|
|
||||||
}
|
|
||||||
);
|
);
|
||||||
|
|
||||||
const validatedAt = new Date();
|
const validatedAt = new Date();
|
||||||
const expiresAt = this.remoteLicenseExpiresAt(data);
|
const expiresAt = new Date(data.expiresAt);
|
||||||
const validateKey = data.res.headers.get('x-next-validate-key') ?? '';
|
|
||||||
|
|
||||||
this.event.emit('workspace.subscription.activated', {
|
this.event.emit('workspace.subscription.activated', {
|
||||||
workspaceId,
|
workspaceId,
|
||||||
plan: data.plan,
|
plan: data.plan as SubscriptionPlan,
|
||||||
recurring: data.recurring,
|
recurring: data.recurring as SubscriptionRecurring,
|
||||||
quantity: data.quantity,
|
quantity: data.quantity,
|
||||||
});
|
});
|
||||||
await this.entitlement.upsertFromValidatedSelfhostLicense({
|
await this.entitlement.upsertFromValidatedSelfhostLicense({
|
||||||
workspaceId,
|
workspaceId,
|
||||||
licenseKey,
|
licenseKey,
|
||||||
recurring: data.recurring,
|
recurring: data.recurring as SubscriptionRecurring,
|
||||||
quantity: data.quantity,
|
quantity: data.quantity,
|
||||||
expiresAt,
|
expiresAt,
|
||||||
validatedAt,
|
validatedAt,
|
||||||
validateKey,
|
validateKey: data.validateKey,
|
||||||
});
|
});
|
||||||
|
|
||||||
return this.db.installedLicense.upsert({
|
return this.db.installedLicense.upsert({
|
||||||
@@ -189,9 +198,9 @@ export class LicenseService {
|
|||||||
update: {
|
update: {
|
||||||
key: licenseKey,
|
key: licenseKey,
|
||||||
quantity: data.quantity,
|
quantity: data.quantity,
|
||||||
recurring: data.recurring,
|
recurring: data.recurring as SubscriptionRecurring,
|
||||||
variant: null,
|
variant: null,
|
||||||
validateKey,
|
validateKey: data.validateKey,
|
||||||
validatedAt,
|
validatedAt,
|
||||||
expiredAt: expiresAt,
|
expiredAt: expiresAt,
|
||||||
license: null,
|
license: null,
|
||||||
@@ -200,9 +209,9 @@ export class LicenseService {
|
|||||||
workspaceId,
|
workspaceId,
|
||||||
key: licenseKey,
|
key: licenseKey,
|
||||||
quantity: data.quantity,
|
quantity: data.quantity,
|
||||||
recurring: data.recurring,
|
recurring: data.recurring as SubscriptionRecurring,
|
||||||
variant: null,
|
variant: null,
|
||||||
validateKey,
|
validateKey: data.validateKey,
|
||||||
validatedAt,
|
validatedAt,
|
||||||
expiredAt: expiresAt,
|
expiredAt: expiresAt,
|
||||||
license: null,
|
license: null,
|
||||||
@@ -242,18 +251,18 @@ export class LicenseService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async deactivateTeamLicense(license: InstalledLicense) {
|
async deactivateTeamLicense(license: InstalledLicense) {
|
||||||
await this.fetchAffinePro(`/api/team/licenses/${license.key}/deactivate`, {
|
this.remoteCommand(
|
||||||
method: 'POST',
|
await licenseClient.deactivate({ licenseKey: license.key })
|
||||||
});
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
async updateTeamRecurring(key: string, recurring: SubscriptionRecurring) {
|
async updateTeamRecurring(key: string, recurring: SubscriptionRecurring) {
|
||||||
await this.fetchAffinePro(`/api/team/licenses/${key}/recurring`, {
|
this.remoteCommand(
|
||||||
method: 'POST',
|
await licenseClient.updateRecurring({
|
||||||
body: JSON.stringify({
|
licenseKey: key,
|
||||||
recurring,
|
recurring,
|
||||||
}),
|
})
|
||||||
});
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
async createCustomerPortal(workspaceId: string) {
|
async createCustomerPortal(workspaceId: string) {
|
||||||
@@ -267,12 +276,12 @@ export class LicenseService {
|
|||||||
throw new LicenseNotFound();
|
throw new LicenseNotFound();
|
||||||
}
|
}
|
||||||
|
|
||||||
return this.fetchAffinePro<{ url: string }>(
|
const portal = this.remotePortal(
|
||||||
`/api/team/licenses/${license.key}/create-customer-portal`,
|
await licenseClient.createCustomerPortal({
|
||||||
{
|
licenseKey: license.key,
|
||||||
method: 'POST',
|
})
|
||||||
}
|
|
||||||
);
|
);
|
||||||
|
return { url: portal.url };
|
||||||
}
|
}
|
||||||
|
|
||||||
@OnEvent('workspace.members.updated')
|
@OnEvent('workspace.members.updated')
|
||||||
@@ -301,12 +310,12 @@ export class LicenseService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const count = await this.models.workspaceUser.chargedCount(workspaceId);
|
const count = await this.models.workspaceUser.chargedCount(workspaceId);
|
||||||
await this.fetchAffinePro(`/api/team/licenses/${license.key}/seats`, {
|
this.remoteCommand(
|
||||||
method: 'POST',
|
await licenseClient.updateSeats({
|
||||||
body: JSON.stringify({
|
licenseKey: license.key,
|
||||||
seats: count,
|
seats: count,
|
||||||
}),
|
})
|
||||||
});
|
);
|
||||||
|
|
||||||
// stripe payment is async, we can't directly the charge result in update calling
|
// stripe payment is async, we can't directly the charge result in update calling
|
||||||
await this.waitUntilLicenseUpdated(license, count);
|
await this.waitUntilLicenseUpdated(license, count);
|
||||||
@@ -356,30 +365,28 @@ export class LicenseService {
|
|||||||
|
|
||||||
private async revalidateRecurringLicense(license: InstalledLicense) {
|
private async revalidateRecurringLicense(license: InstalledLicense) {
|
||||||
try {
|
try {
|
||||||
const res = await this.fetchAffinePro<License>(
|
const res = this.remoteLicense(
|
||||||
`/api/team/licenses/${license.key}/health`,
|
await licenseClient.checkHealth({
|
||||||
{
|
licenseKey: license.key,
|
||||||
headers: {
|
validateKey: license.validateKey,
|
||||||
'x-validate-key': license.validateKey,
|
})
|
||||||
},
|
|
||||||
}
|
|
||||||
);
|
);
|
||||||
|
|
||||||
const validatedAt = new Date();
|
const validatedAt = new Date();
|
||||||
const expiresAt = this.remoteLicenseExpiresAt(res);
|
const expiresAt = new Date(res.expiresAt);
|
||||||
const validateKey = res.res.headers.get('x-next-validate-key') ?? '';
|
const validateKey = res.validateKey || license.validateKey;
|
||||||
|
|
||||||
this.event.emit('workspace.subscription.activated', {
|
this.event.emit('workspace.subscription.activated', {
|
||||||
workspaceId: license.workspaceId,
|
workspaceId: license.workspaceId,
|
||||||
plan: res.plan,
|
plan: res.plan as SubscriptionPlan,
|
||||||
recurring: res.recurring,
|
recurring: res.recurring as SubscriptionRecurring,
|
||||||
quantity: res.quantity,
|
quantity: res.quantity,
|
||||||
});
|
});
|
||||||
await this.db.installedLicense.update({
|
await this.db.installedLicense.update({
|
||||||
where: { key: license.key },
|
where: { key: license.key },
|
||||||
data: {
|
data: {
|
||||||
quantity: res.quantity,
|
quantity: res.quantity,
|
||||||
recurring: res.recurring,
|
recurring: res.recurring as SubscriptionRecurring,
|
||||||
validateKey,
|
validateKey,
|
||||||
validatedAt,
|
validatedAt,
|
||||||
expiredAt: expiresAt,
|
expiredAt: expiresAt,
|
||||||
@@ -390,7 +397,7 @@ export class LicenseService {
|
|||||||
await this.entitlement.upsertFromSelfhostLicense({
|
await this.entitlement.upsertFromSelfhostLicense({
|
||||||
workspaceId: license.workspaceId,
|
workspaceId: license.workspaceId,
|
||||||
licenseKey: license.key,
|
licenseKey: license.key,
|
||||||
recurring: res.recurring,
|
recurring: res.recurring as SubscriptionRecurring,
|
||||||
quantity: res.quantity,
|
quantity: res.quantity,
|
||||||
expiresAt,
|
expiresAt,
|
||||||
validatedAt,
|
validatedAt,
|
||||||
@@ -401,7 +408,7 @@ export class LicenseService {
|
|||||||
await this.entitlement.upsertFromValidatedSelfhostLicense({
|
await this.entitlement.upsertFromValidatedSelfhostLicense({
|
||||||
workspaceId: license.workspaceId,
|
workspaceId: license.workspaceId,
|
||||||
licenseKey: license.key,
|
licenseKey: license.key,
|
||||||
recurring: res.recurring,
|
recurring: res.recurring as SubscriptionRecurring,
|
||||||
quantity: res.quantity,
|
quantity: res.quantity,
|
||||||
expiresAt,
|
expiresAt,
|
||||||
validatedAt,
|
validatedAt,
|
||||||
@@ -430,55 +437,38 @@ export class LicenseService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private remoteLicenseExpiresAt(license: Pick<License, 'endAt'>) {
|
private remoteLicense(response: LicenseResponse) {
|
||||||
const expiresAt = new Date(license.endAt);
|
this.throwRemoteLicenseError(response.error);
|
||||||
if (!Number.isFinite(expiresAt.getTime()) || expiresAt <= new Date()) {
|
if (!response.license) {
|
||||||
throw new LicenseExpired();
|
throw new InternalServerError('Invalid AFFiNE Pro license response.');
|
||||||
}
|
}
|
||||||
return expiresAt;
|
return response.license;
|
||||||
}
|
}
|
||||||
|
|
||||||
private async fetchAffinePro<T = any>(
|
private remoteCommand(response: CommandResponse) {
|
||||||
path: string,
|
this.throwRemoteLicenseError(response.error);
|
||||||
init?: RequestInit
|
}
|
||||||
): Promise<T & { res: Response }> {
|
|
||||||
const endpoint =
|
|
||||||
process.env.AFFINE_PRO_SERVER_ENDPOINT ?? 'https://app.affine.pro';
|
|
||||||
|
|
||||||
|
private remotePortal(response: PortalResponse) {
|
||||||
|
this.throwRemoteLicenseError(response.error);
|
||||||
|
if (!response.url) {
|
||||||
|
throw new InternalServerError('Invalid AFFiNE Pro portal response.');
|
||||||
|
}
|
||||||
|
return { url: response.url };
|
||||||
|
}
|
||||||
|
|
||||||
|
private throwRemoteLicenseError(
|
||||||
|
error?: LicenseError | null
|
||||||
|
): asserts error is null | undefined {
|
||||||
|
if (!error) return;
|
||||||
try {
|
try {
|
||||||
const signal =
|
throw UserFriendlyError.fromUserFriendlyErrorJSON(JSON.parse(error.body));
|
||||||
init?.signal ??
|
|
||||||
(AbortSignal.timeout
|
|
||||||
? AbortSignal.timeout(AFFINE_PRO_REQUEST_TIMEOUT)
|
|
||||||
: undefined);
|
|
||||||
const res = await fetch(endpoint + path, {
|
|
||||||
...init,
|
|
||||||
signal,
|
|
||||||
headers: {
|
|
||||||
'Content-Type': 'application/json',
|
|
||||||
...init?.headers,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!res.ok) {
|
|
||||||
const body = (await res.json()) as UserFriendlyError;
|
|
||||||
throw UserFriendlyError.fromUserFriendlyErrorJSON(body);
|
|
||||||
}
|
|
||||||
|
|
||||||
const data = (await res.json()) as T;
|
|
||||||
return {
|
|
||||||
...data,
|
|
||||||
res,
|
|
||||||
};
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
if (e instanceof UserFriendlyError) {
|
if (e instanceof UserFriendlyError) {
|
||||||
throw e;
|
throw e;
|
||||||
}
|
}
|
||||||
|
|
||||||
throw new InternalServerError(
|
throw new InternalServerError(
|
||||||
e instanceof Error
|
'Failed to contact with https://app.affine.pro'
|
||||||
? e.message
|
|
||||||
: 'Failed to contact with https://app.affine.pro'
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import {
|
|||||||
InvalidOauthCallbackCode,
|
InvalidOauthCallbackCode,
|
||||||
InvalidOauthResponse,
|
InvalidOauthResponse,
|
||||||
OnEvent,
|
OnEvent,
|
||||||
|
safeFetch,
|
||||||
} from '../../../base';
|
} from '../../../base';
|
||||||
import { OAuthProviderName } from '../config';
|
import { OAuthProviderName } from '../config';
|
||||||
import { OAuthProviderFactory } from '../factory';
|
import { OAuthProviderFactory } from '../factory';
|
||||||
@@ -83,10 +84,16 @@ export abstract class OAuthProvider {
|
|||||||
init?: RequestInit,
|
init?: RequestInit,
|
||||||
options?: { treatServerErrorAsInvalid?: boolean }
|
options?: { treatServerErrorAsInvalid?: boolean }
|
||||||
) {
|
) {
|
||||||
const response = await fetch(url, {
|
const response = await safeFetch(
|
||||||
...init,
|
url,
|
||||||
headers: { ...init?.headers, Accept: 'application/json' },
|
{ ...init, headers: { ...init?.headers, Accept: 'application/json' } },
|
||||||
});
|
{
|
||||||
|
timeoutMs: 10_000,
|
||||||
|
maxRedirects: 3,
|
||||||
|
maxBytes: 1024 * 1024,
|
||||||
|
allowedHeaders: ['authorization', 'content-type', 'accept'],
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
const body = await response.text();
|
const body = await response.text();
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
|
|||||||
@@ -1,5 +1,10 @@
|
|||||||
import { Injectable, OnModuleDestroy } from '@nestjs/common';
|
import { Injectable, OnModuleDestroy } from '@nestjs/common';
|
||||||
import { createRemoteJWKSet, type JWTPayload, jwtVerify } from 'jose';
|
import {
|
||||||
|
createRemoteJWKSet,
|
||||||
|
customFetch,
|
||||||
|
type JWTPayload,
|
||||||
|
jwtVerify,
|
||||||
|
} from 'jose';
|
||||||
import { omit } from 'lodash-es';
|
import { omit } from 'lodash-es';
|
||||||
import { z } from 'zod';
|
import { z } from 'zod';
|
||||||
|
|
||||||
@@ -7,6 +12,7 @@ import {
|
|||||||
ExponentialBackoffScheduler,
|
ExponentialBackoffScheduler,
|
||||||
InvalidAuthState,
|
InvalidAuthState,
|
||||||
InvalidOauthResponse,
|
InvalidOauthResponse,
|
||||||
|
safeFetch,
|
||||||
URLHelper,
|
URLHelper,
|
||||||
} from '../../../base';
|
} from '../../../base';
|
||||||
import { OAuthOIDCProviderConfig, OAuthProviderName } from '../config';
|
import { OAuthOIDCProviderConfig, OAuthProviderName } from '../config';
|
||||||
@@ -59,12 +65,19 @@ type OIDCConfiguration = z.infer<typeof OIDCConfigurationSchema>;
|
|||||||
|
|
||||||
const OIDC_DISCOVERY_INITIAL_RETRY_DELAY = 1000;
|
const OIDC_DISCOVERY_INITIAL_RETRY_DELAY = 1000;
|
||||||
const OIDC_DISCOVERY_MAX_RETRY_DELAY = 60_000;
|
const OIDC_DISCOVERY_MAX_RETRY_DELAY = 60_000;
|
||||||
|
const OIDC_FETCH_OPTIONS = {
|
||||||
|
timeoutMs: 10_000,
|
||||||
|
maxRedirects: 3,
|
||||||
|
maxBytes: 1024 * 1024,
|
||||||
|
allowedHeaders: ['accept'],
|
||||||
|
};
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class OIDCProvider extends OAuthProvider implements OnModuleDestroy {
|
export class OIDCProvider extends OAuthProvider implements OnModuleDestroy {
|
||||||
override provider = OAuthProviderName.OIDC;
|
override provider = OAuthProviderName.OIDC;
|
||||||
#endpoints: OIDCConfiguration | null = null;
|
#endpoints: OIDCConfiguration | null = null;
|
||||||
#jwks: ReturnType<typeof createRemoteJWKSet> | null = null;
|
#jwks: ReturnType<typeof createRemoteJWKSet> | null = null;
|
||||||
|
private readonly oidcFetch = safeFetch;
|
||||||
readonly #retryScheduler = new ExponentialBackoffScheduler({
|
readonly #retryScheduler = new ExponentialBackoffScheduler({
|
||||||
baseDelayMs: OIDC_DISCOVERY_INITIAL_RETRY_DELAY,
|
baseDelayMs: OIDC_DISCOVERY_INITIAL_RETRY_DELAY,
|
||||||
maxDelayMs: OIDC_DISCOVERY_MAX_RETRY_DELAY,
|
maxDelayMs: OIDC_DISCOVERY_MAX_RETRY_DELAY,
|
||||||
@@ -132,12 +145,10 @@ export class OIDCProvider extends OAuthProvider implements OnModuleDestroy {
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const res = await fetch(
|
const res = await this.oidcFetch(
|
||||||
`${config.issuer}/.well-known/openid-configuration`,
|
`${config.issuer}/.well-known/openid-configuration`,
|
||||||
{
|
{ method: 'GET', headers: { Accept: 'application/json' } },
|
||||||
method: 'GET',
|
OIDC_FETCH_OPTIONS
|
||||||
headers: { Accept: 'application/json' },
|
|
||||||
}
|
|
||||||
);
|
);
|
||||||
|
|
||||||
if (generation !== this.#validationGeneration) {
|
if (generation !== this.#validationGeneration) {
|
||||||
@@ -163,7 +174,10 @@ export class OIDCProvider extends OAuthProvider implements OnModuleDestroy {
|
|||||||
}
|
}
|
||||||
|
|
||||||
this.#endpoints = configuration;
|
this.#endpoints = configuration;
|
||||||
this.#jwks = createRemoteJWKSet(new URL(configuration.jwks_uri));
|
this.#jwks = createRemoteJWKSet(new URL(configuration.jwks_uri), {
|
||||||
|
[customFetch]: (url, init) =>
|
||||||
|
this.oidcFetch(url, init, OIDC_FETCH_OPTIONS),
|
||||||
|
});
|
||||||
this.#retryScheduler.reset();
|
this.#retryScheduler.reset();
|
||||||
super.setup();
|
super.setup();
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
|||||||
@@ -27,14 +27,6 @@ declare global {
|
|||||||
payment: {
|
payment: {
|
||||||
enabled: boolean;
|
enabled: boolean;
|
||||||
showLifetimePrice: boolean;
|
showLifetimePrice: boolean;
|
||||||
/**
|
|
||||||
* @deprecated use payment.stripe.apiKey
|
|
||||||
*/
|
|
||||||
apiKey: string;
|
|
||||||
/**
|
|
||||||
* @deprecated use payment.stripe.webhookKey
|
|
||||||
*/
|
|
||||||
webhookKey: string;
|
|
||||||
stripe: ConfigItem<
|
stripe: ConfigItem<
|
||||||
{
|
{
|
||||||
/** Preferred place for Stripe API key */
|
/** Preferred place for Stripe API key */
|
||||||
@@ -70,16 +62,6 @@ defineModuleConfig('payment', {
|
|||||||
desc: 'Whether enable lifetime price and allow user to pay for it.',
|
desc: 'Whether enable lifetime price and allow user to pay for it.',
|
||||||
default: true,
|
default: true,
|
||||||
},
|
},
|
||||||
apiKey: {
|
|
||||||
desc: '[Deprecated] Stripe API key. Use payment.stripe.apiKey instead.',
|
|
||||||
default: '',
|
|
||||||
env: 'STRIPE_API_KEY',
|
|
||||||
},
|
|
||||||
webhookKey: {
|
|
||||||
desc: '[Deprecated] Stripe webhook key. Use payment.stripe.webhookKey instead.',
|
|
||||||
default: '',
|
|
||||||
env: 'STRIPE_WEBHOOK_KEY',
|
|
||||||
},
|
|
||||||
stripe: {
|
stripe: {
|
||||||
desc: 'Stripe sdk options and credentials',
|
desc: 'Stripe sdk options and credentials',
|
||||||
default: {
|
default: {
|
||||||
|
|||||||
@@ -19,9 +19,7 @@ export class StripeWebhookController {
|
|||||||
@Public()
|
@Public()
|
||||||
@Post('/webhook')
|
@Post('/webhook')
|
||||||
async handleWebhook(@Req() req: RawBodyRequest<Request>) {
|
async handleWebhook(@Req() req: RawBodyRequest<Request>) {
|
||||||
const nestedWebhookKey = this.config.payment.stripe?.webhookKey;
|
const webhookKey = this.config.payment.stripe?.webhookKey || '';
|
||||||
const legacyWebhookKey = this.config.payment.webhookKey;
|
|
||||||
const webhookKey = nestedWebhookKey || legacyWebhookKey || '';
|
|
||||||
// Retrieve the event by verifying the signature using the raw body and secret.
|
// Retrieve the event by verifying the signature using the raw body and secret.
|
||||||
const signature = req.headers['stripe-signature'];
|
const signature = req.headers['stripe-signature'];
|
||||||
try {
|
try {
|
||||||
|
|||||||
@@ -256,19 +256,21 @@ export abstract class SubscriptionManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async getPrice(lookupKey: LookupKey): Promise<KnownStripePrice | null> {
|
async getPrice(lookupKey: LookupKey): Promise<KnownStripePrice | null> {
|
||||||
|
let key: string;
|
||||||
|
try {
|
||||||
|
key = encodeLookupKey(lookupKey);
|
||||||
|
} catch {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
const prices = await this.stripe.prices.list({
|
const prices = await this.stripe.prices.list({
|
||||||
lookup_keys: [encodeLookupKey(lookupKey)],
|
lookup_keys: [key],
|
||||||
limit: 1,
|
limit: 1,
|
||||||
});
|
});
|
||||||
|
|
||||||
const price = prices.data[0];
|
const price = prices.data[0];
|
||||||
|
|
||||||
return price
|
return price ? { lookupKey, price } : null;
|
||||||
? {
|
|
||||||
lookupKey,
|
|
||||||
price,
|
|
||||||
}
|
|
||||||
: null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected async getCouponFromPromotionCode(
|
protected async getCouponFromPromotionCode(
|
||||||
|
|||||||
@@ -7,23 +7,18 @@ import { z } from 'zod';
|
|||||||
import {
|
import {
|
||||||
Config,
|
Config,
|
||||||
EventBus,
|
EventBus,
|
||||||
InternalServerError,
|
|
||||||
InvalidCheckoutParameters,
|
InvalidCheckoutParameters,
|
||||||
ManagedByAppStoreOrPlay,
|
ManagedByAppStoreOrPlay,
|
||||||
Mutex,
|
Mutex,
|
||||||
OneMonth,
|
|
||||||
OnEvent,
|
OnEvent,
|
||||||
OneYear,
|
|
||||||
SubscriptionAlreadyExists,
|
SubscriptionAlreadyExists,
|
||||||
SubscriptionPlanNotFound,
|
SubscriptionPlanNotFound,
|
||||||
TooManyRequest,
|
TooManyRequest,
|
||||||
URLHelper,
|
URLHelper,
|
||||||
} from '../../../base';
|
} from '../../../base';
|
||||||
import { EntitlementService } from '../../../core/entitlement';
|
import { EntitlementService } from '../../../core/entitlement';
|
||||||
import { EarlyAccessType, FeatureService } from '../../../core/features';
|
|
||||||
import { StripeFactory } from '../stripe';
|
import { StripeFactory } from '../stripe';
|
||||||
import {
|
import {
|
||||||
CouponType,
|
|
||||||
KnownStripeInvoice,
|
KnownStripeInvoice,
|
||||||
KnownStripePrice,
|
KnownStripePrice,
|
||||||
KnownStripeSubscription,
|
KnownStripeSubscription,
|
||||||
@@ -32,7 +27,6 @@ import {
|
|||||||
SubscriptionPlan,
|
SubscriptionPlan,
|
||||||
SubscriptionRecurring,
|
SubscriptionRecurring,
|
||||||
SubscriptionStatus,
|
SubscriptionStatus,
|
||||||
SubscriptionVariant,
|
|
||||||
} from '../types';
|
} from '../types';
|
||||||
import {
|
import {
|
||||||
activeSubscriptionWhere,
|
activeSubscriptionWhere,
|
||||||
@@ -42,11 +36,8 @@ import {
|
|||||||
} from './common';
|
} from './common';
|
||||||
|
|
||||||
interface PriceStrategyStatus {
|
interface PriceStrategyStatus {
|
||||||
proEarlyAccess: boolean;
|
|
||||||
aiEarlyAccess: boolean;
|
|
||||||
proSubscribed: boolean;
|
proSubscribed: boolean;
|
||||||
aiSubscribed: boolean;
|
aiSubscribed: boolean;
|
||||||
onetime: boolean;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export const UserSubscriptionIdentity = z.object({
|
export const UserSubscriptionIdentity = z.object({
|
||||||
@@ -67,7 +58,6 @@ export class UserSubscriptionManager extends SubscriptionManager {
|
|||||||
stripeProvider: StripeFactory,
|
stripeProvider: StripeFactory,
|
||||||
db: PrismaClient,
|
db: PrismaClient,
|
||||||
private readonly config: Config,
|
private readonly config: Config,
|
||||||
private readonly feature: FeatureService,
|
|
||||||
private readonly event: EventBus,
|
private readonly event: EventBus,
|
||||||
private readonly url: URLHelper,
|
private readonly url: URLHelper,
|
||||||
private readonly mutex: Mutex,
|
private readonly mutex: Mutex,
|
||||||
@@ -78,22 +68,12 @@ export class UserSubscriptionManager extends SubscriptionManager {
|
|||||||
|
|
||||||
async filterPrices(
|
async filterPrices(
|
||||||
prices: KnownStripePrice[],
|
prices: KnownStripePrice[],
|
||||||
customer?: UserStripeCustomer
|
_customer?: UserStripeCustomer
|
||||||
) {
|
) {
|
||||||
const strategyStatus = customer
|
|
||||||
? await this.strategyStatus(customer)
|
|
||||||
: {
|
|
||||||
proEarlyAccess: false,
|
|
||||||
aiEarlyAccess: false,
|
|
||||||
proSubscribed: false,
|
|
||||||
aiSubscribed: false,
|
|
||||||
onetime: false,
|
|
||||||
};
|
|
||||||
|
|
||||||
const availablePrices: KnownStripePrice[] = [];
|
const availablePrices: KnownStripePrice[] = [];
|
||||||
|
|
||||||
for (const price of prices) {
|
for (const price of prices) {
|
||||||
if (await this.isPriceAvailable(price, strategyStatus)) {
|
if (await this.isPriceAvailable(price)) {
|
||||||
availablePrices.push(price);
|
availablePrices.push(price);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -107,8 +87,9 @@ export class UserSubscriptionManager extends SubscriptionManager {
|
|||||||
{ user }: z.infer<typeof UserSubscriptionCheckoutArgs>
|
{ user }: z.infer<typeof UserSubscriptionCheckoutArgs>
|
||||||
) {
|
) {
|
||||||
if (
|
if (
|
||||||
lookupKey.plan !== SubscriptionPlan.Pro &&
|
(lookupKey.plan !== SubscriptionPlan.Pro &&
|
||||||
lookupKey.plan !== SubscriptionPlan.AI
|
lookupKey.plan !== SubscriptionPlan.AI) ||
|
||||||
|
lookupKey.variant !== null
|
||||||
) {
|
) {
|
||||||
throw new InvalidCheckoutParameters();
|
throw new InvalidCheckoutParameters();
|
||||||
}
|
}
|
||||||
@@ -125,15 +106,8 @@ export class UserSubscriptionManager extends SubscriptionManager {
|
|||||||
active &&
|
active &&
|
||||||
// do not allow to re-subscribe unless
|
// do not allow to re-subscribe unless
|
||||||
!(
|
!(
|
||||||
/* current subscription is a onetime subscription and so as the one that's checking out */
|
active.recurring !== SubscriptionRecurring.Lifetime &&
|
||||||
(
|
lookupKey.recurring === SubscriptionRecurring.Lifetime
|
||||||
(active.variant === SubscriptionVariant.Onetime &&
|
|
||||||
lookupKey.variant === SubscriptionVariant.Onetime) ||
|
|
||||||
/* current subscription is normal subscription and is checking-out a lifetime subscription */
|
|
||||||
(active.recurring !== SubscriptionRecurring.Lifetime &&
|
|
||||||
active.variant !== SubscriptionVariant.Onetime &&
|
|
||||||
lookupKey.recurring === SubscriptionRecurring.Lifetime)
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
) {
|
) {
|
||||||
throw new SubscriptionAlreadyExists({ plan: lookupKey.plan });
|
throw new SubscriptionAlreadyExists({ plan: lookupKey.plan });
|
||||||
@@ -141,12 +115,9 @@ export class UserSubscriptionManager extends SubscriptionManager {
|
|||||||
|
|
||||||
const customer = await this.getOrCreateCustomer(user.id);
|
const customer = await this.getOrCreateCustomer(user.id);
|
||||||
const strategy = await this.strategyStatus(customer);
|
const strategy = await this.strategyStatus(customer);
|
||||||
const price = await this.autoPrice(lookupKey, strategy);
|
const price = await this.getPrice(lookupKey);
|
||||||
|
|
||||||
if (
|
if (!price || !(await this.isPriceAvailable(price))) {
|
||||||
!price ||
|
|
||||||
!(await this.isPriceAvailable(price, { ...strategy, onetime: true }))
|
|
||||||
) {
|
|
||||||
throw new SubscriptionPlanNotFound({
|
throw new SubscriptionPlanNotFound({
|
||||||
plan: lookupKey.plan,
|
plan: lookupKey.plan,
|
||||||
recurring: lookupKey.recurring,
|
recurring: lookupKey.recurring,
|
||||||
@@ -154,10 +125,7 @@ export class UserSubscriptionManager extends SubscriptionManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const discounts = await (async () => {
|
const discounts = await (async () => {
|
||||||
const coupon = await this.getBuildInCoupon(customer, price);
|
if (params.coupon) {
|
||||||
if (coupon) {
|
|
||||||
return { discounts: [{ coupon }] };
|
|
||||||
} else if (params.coupon) {
|
|
||||||
const couponId = await this.getCouponFromPromotionCode(
|
const couponId = await this.getCouponFromPromotionCode(
|
||||||
params.coupon,
|
params.coupon,
|
||||||
customer
|
customer
|
||||||
@@ -179,10 +147,9 @@ export class UserSubscriptionManager extends SubscriptionManager {
|
|||||||
return undefined;
|
return undefined;
|
||||||
})();
|
})();
|
||||||
|
|
||||||
// mode: 'subscription' or 'payment' for lifetime and onetime payment
|
// mode: 'subscription' or 'payment' for lifetime payment
|
||||||
const mode =
|
const mode =
|
||||||
lookupKey.recurring === SubscriptionRecurring.Lifetime ||
|
lookupKey.recurring === SubscriptionRecurring.Lifetime
|
||||||
lookupKey.variant === SubscriptionVariant.Onetime
|
|
||||||
? {
|
? {
|
||||||
mode: 'payment' as const,
|
mode: 'payment' as const,
|
||||||
invoice_creation: {
|
invoice_creation: {
|
||||||
@@ -339,37 +306,6 @@ export class UserSubscriptionManager extends SubscriptionManager {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private async getBuildInCoupon(
|
|
||||||
customer: UserStripeCustomer,
|
|
||||||
price: KnownStripePrice
|
|
||||||
) {
|
|
||||||
const strategyStatus = await this.strategyStatus(customer);
|
|
||||||
|
|
||||||
// onetime price is allowed for checkout
|
|
||||||
strategyStatus.onetime = true;
|
|
||||||
|
|
||||||
if (!(await this.isPriceAvailable(price, strategyStatus))) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
let coupon: CouponType | undefined;
|
|
||||||
|
|
||||||
if (price.lookupKey.variant === SubscriptionVariant.EA) {
|
|
||||||
if (price.lookupKey.plan === SubscriptionPlan.Pro) {
|
|
||||||
coupon = CouponType.ProEarlyAccessOneYearFree;
|
|
||||||
} else if (price.lookupKey.plan === SubscriptionPlan.AI) {
|
|
||||||
coupon = CouponType.AIEarlyAccessOneYearFree;
|
|
||||||
}
|
|
||||||
} else if (price.lookupKey.plan === SubscriptionPlan.AI) {
|
|
||||||
const { proEarlyAccess, aiSubscribed } = strategyStatus;
|
|
||||||
if (proEarlyAccess && !aiSubscribed) {
|
|
||||||
coupon = CouponType.ProEarlyAccessAIOneYearFree;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return coupon;
|
|
||||||
}
|
|
||||||
|
|
||||||
async saveInvoice(knownInvoice: KnownStripeInvoice) {
|
async saveInvoice(knownInvoice: KnownStripeInvoice) {
|
||||||
const { userId, lookupKey, stripeInvoice } = knownInvoice;
|
const { userId, lookupKey, stripeInvoice } = knownInvoice;
|
||||||
this.assertUserIdExists(userId);
|
this.assertUserIdExists(userId);
|
||||||
@@ -387,11 +323,11 @@ export class UserSubscriptionManager extends SubscriptionManager {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
// onetime and lifetime subscription is a special "subscription" that doesn't get involved with stripe subscription system
|
// Lifetime subscription does not get involved with the Stripe subscription system.
|
||||||
// we track the deals by invoice only.
|
// We track the deal by invoice only.
|
||||||
if (stripeInvoice.status === 'paid') {
|
if (stripeInvoice.status === 'paid') {
|
||||||
await using lock = await this.mutex.acquire(
|
await using lock = await this.mutex.acquire(
|
||||||
`redeem-onetime-subscription:${stripeInvoice.id}`
|
`redeem-lifetime-subscription:${stripeInvoice.id}`
|
||||||
);
|
);
|
||||||
|
|
||||||
if (!lock) {
|
if (!lock) {
|
||||||
@@ -400,8 +336,6 @@ export class UserSubscriptionManager extends SubscriptionManager {
|
|||||||
|
|
||||||
if (lookupKey.recurring === SubscriptionRecurring.Lifetime) {
|
if (lookupKey.recurring === SubscriptionRecurring.Lifetime) {
|
||||||
await this.saveLifetimeSubscription(knownInvoice);
|
await this.saveLifetimeSubscription(knownInvoice);
|
||||||
} else if (lookupKey.variant === SubscriptionVariant.Onetime) {
|
|
||||||
await this.saveOnetimePaymentSubscription(knownInvoice);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -470,106 +404,7 @@ export class UserSubscriptionManager extends SubscriptionManager {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async saveOnetimePaymentSubscription(knownInvoice: KnownStripeInvoice) {
|
async revokeLifetime(knownInvoice: KnownStripeInvoice) {
|
||||||
this.assertUserIdExists(knownInvoice.userId);
|
|
||||||
const { userId, lookupKey, stripeInvoice } = knownInvoice;
|
|
||||||
|
|
||||||
const invoice = await this.db.invoice.findUnique({
|
|
||||||
where: {
|
|
||||||
stripeInvoiceId: stripeInvoice.id,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!invoice) {
|
|
||||||
// never happens
|
|
||||||
throw new InternalServerError('Invoice not found');
|
|
||||||
}
|
|
||||||
|
|
||||||
if (invoice.onetimeSubscriptionRedeemed) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
await this.db.invoice.update({
|
|
||||||
select: {
|
|
||||||
onetimeSubscriptionRedeemed: true,
|
|
||||||
},
|
|
||||||
where: {
|
|
||||||
stripeInvoiceId: stripeInvoice.id,
|
|
||||||
},
|
|
||||||
data: { onetimeSubscriptionRedeemed: true },
|
|
||||||
});
|
|
||||||
|
|
||||||
const existingSubscription = await this.db.subscription.findUnique({
|
|
||||||
where: {
|
|
||||||
targetId_plan: {
|
|
||||||
targetId: userId,
|
|
||||||
plan: lookupKey.plan,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
const subscriptionTime =
|
|
||||||
lookupKey.recurring === SubscriptionRecurring.Monthly
|
|
||||||
? OneMonth
|
|
||||||
: OneYear;
|
|
||||||
|
|
||||||
let subscription: Subscription;
|
|
||||||
|
|
||||||
// extends the subscription time if exists
|
|
||||||
if (existingSubscription) {
|
|
||||||
if (!existingSubscription.end) {
|
|
||||||
throw new InternalServerError(
|
|
||||||
'Unexpected onetime subscription with no end date'
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const period =
|
|
||||||
// expired, reset the period
|
|
||||||
existingSubscription.end <= new Date()
|
|
||||||
? {
|
|
||||||
start: new Date(),
|
|
||||||
end: new Date(Date.now() + subscriptionTime),
|
|
||||||
}
|
|
||||||
: {
|
|
||||||
end: new Date(
|
|
||||||
existingSubscription.end.getTime() + subscriptionTime
|
|
||||||
),
|
|
||||||
};
|
|
||||||
|
|
||||||
subscription = await this.db.subscription.update({
|
|
||||||
where: {
|
|
||||||
id: existingSubscription.id,
|
|
||||||
},
|
|
||||||
data: period,
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
subscription = await this.db.subscription.create({
|
|
||||||
data: {
|
|
||||||
targetId: userId,
|
|
||||||
stripeSubscriptionId: null,
|
|
||||||
...lookupKey,
|
|
||||||
start: new Date(),
|
|
||||||
end: new Date(Date.now() + subscriptionTime),
|
|
||||||
status: SubscriptionStatus.Active,
|
|
||||||
nextBillAt: null,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
this.event.emit('user.subscription.activated', {
|
|
||||||
userId,
|
|
||||||
plan: lookupKey.plan,
|
|
||||||
recurring: lookupKey.recurring,
|
|
||||||
});
|
|
||||||
await this.entitlement.upsertFromCloudSubscription({
|
|
||||||
...subscription,
|
|
||||||
targetId: userId,
|
|
||||||
});
|
|
||||||
|
|
||||||
return subscription;
|
|
||||||
}
|
|
||||||
|
|
||||||
async revokeOnetimeOrLifetime(knownInvoice: KnownStripeInvoice) {
|
|
||||||
this.assertUserIdExists(knownInvoice.userId);
|
this.assertUserIdExists(knownInvoice.userId);
|
||||||
const { userId, lookupKey } = knownInvoice;
|
const { userId, lookupKey } = knownInvoice;
|
||||||
|
|
||||||
@@ -609,7 +444,7 @@ export class UserSubscriptionManager extends SubscriptionManager {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async restoreOnetimeOrLifetime(knownInvoice: KnownStripeInvoice) {
|
async restoreLifetime(knownInvoice: KnownStripeInvoice) {
|
||||||
this.assertUserIdExists(knownInvoice.userId);
|
this.assertUserIdExists(knownInvoice.userId);
|
||||||
const { userId, lookupKey, stripeInvoice } = knownInvoice;
|
const { userId, lookupKey, stripeInvoice } = knownInvoice;
|
||||||
|
|
||||||
@@ -627,18 +462,6 @@ export class UserSubscriptionManager extends SubscriptionManager {
|
|||||||
? stripeInvoice.created
|
? stripeInvoice.created
|
||||||
: Date.now() / 1000);
|
: Date.now() / 1000);
|
||||||
|
|
||||||
let end: Date | null = null;
|
|
||||||
|
|
||||||
if (lookupKey.recurring === SubscriptionRecurring.Lifetime) {
|
|
||||||
end = null;
|
|
||||||
} else if (lookupKey.variant === SubscriptionVariant.Onetime) {
|
|
||||||
const isMonthly = lookupKey.recurring === SubscriptionRecurring.Monthly;
|
|
||||||
const duration = isMonthly ? OneMonth : OneYear;
|
|
||||||
end = subscription?.end ?? new Date(start * 1000 + duration);
|
|
||||||
} else {
|
|
||||||
end = subscription?.end ?? null;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (subscription) {
|
if (subscription) {
|
||||||
const saved = await this.db.subscription.update({
|
const saved = await this.db.subscription.update({
|
||||||
where: { id: subscription.id },
|
where: { id: subscription.id },
|
||||||
@@ -647,7 +470,7 @@ export class UserSubscriptionManager extends SubscriptionManager {
|
|||||||
canceledAt: null,
|
canceledAt: null,
|
||||||
nextBillAt: null,
|
nextBillAt: null,
|
||||||
start: subscription.start ?? new Date(start * 1000),
|
start: subscription.start ?? new Date(start * 1000),
|
||||||
end,
|
end: null,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
await this.entitlement.upsertFromCloudSubscription(saved);
|
await this.entitlement.upsertFromCloudSubscription(saved);
|
||||||
@@ -658,7 +481,7 @@ export class UserSubscriptionManager extends SubscriptionManager {
|
|||||||
stripeSubscriptionId: null,
|
stripeSubscriptionId: null,
|
||||||
...lookupKey,
|
...lookupKey,
|
||||||
start: new Date(start * 1000),
|
start: new Date(start * 1000),
|
||||||
end,
|
end: null,
|
||||||
status: SubscriptionStatus.Active,
|
status: SubscriptionStatus.Active,
|
||||||
nextBillAt: null,
|
nextBillAt: null,
|
||||||
},
|
},
|
||||||
@@ -673,111 +496,43 @@ export class UserSubscriptionManager extends SubscriptionManager {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private async autoPrice(lookupKey: LookupKey, strategy: PriceStrategyStatus) {
|
private async isPriceAvailable(price: KnownStripePrice) {
|
||||||
// auto select ea variant when available if not specified
|
|
||||||
let variant: SubscriptionVariant | null = lookupKey.variant;
|
|
||||||
|
|
||||||
if (!variant) {
|
|
||||||
// make the if conditions separated, more readable
|
|
||||||
// pro early access
|
|
||||||
if (
|
|
||||||
lookupKey.plan === SubscriptionPlan.Pro &&
|
|
||||||
lookupKey.recurring === SubscriptionRecurring.Yearly &&
|
|
||||||
strategy.proEarlyAccess &&
|
|
||||||
!strategy.proSubscribed
|
|
||||||
) {
|
|
||||||
variant = SubscriptionVariant.EA;
|
|
||||||
}
|
|
||||||
|
|
||||||
// ai early access
|
|
||||||
if (
|
|
||||||
lookupKey.plan === SubscriptionPlan.AI &&
|
|
||||||
lookupKey.recurring === SubscriptionRecurring.Yearly &&
|
|
||||||
strategy.aiEarlyAccess &&
|
|
||||||
!strategy.aiSubscribed
|
|
||||||
) {
|
|
||||||
variant = SubscriptionVariant.EA;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return this.getPrice({
|
|
||||||
plan: lookupKey.plan,
|
|
||||||
recurring: lookupKey.recurring,
|
|
||||||
variant,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private async isPriceAvailable(
|
|
||||||
price: KnownStripePrice,
|
|
||||||
strategy: PriceStrategyStatus
|
|
||||||
) {
|
|
||||||
if (price.lookupKey.plan === SubscriptionPlan.Pro) {
|
if (price.lookupKey.plan === SubscriptionPlan.Pro) {
|
||||||
return this.isProPriceAvailable(price, strategy);
|
return this.isProPriceAvailable(price);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (price.lookupKey.plan === SubscriptionPlan.AI) {
|
if (price.lookupKey.plan === SubscriptionPlan.AI) {
|
||||||
return this.isAIPriceAvailable(price, strategy);
|
return this.isAIPriceAvailable(price);
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
private async isProPriceAvailable(
|
private async isProPriceAvailable({ lookupKey }: KnownStripePrice) {
|
||||||
{ lookupKey }: KnownStripePrice,
|
|
||||||
{ proEarlyAccess, proSubscribed, onetime }: PriceStrategyStatus
|
|
||||||
) {
|
|
||||||
if (lookupKey.recurring === SubscriptionRecurring.Lifetime) {
|
if (lookupKey.recurring === SubscriptionRecurring.Lifetime) {
|
||||||
return this.config.payment.showLifetimePrice;
|
return this.config.payment.showLifetimePrice;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (lookupKey.variant === SubscriptionVariant.Onetime) {
|
|
||||||
return onetime;
|
|
||||||
}
|
|
||||||
|
|
||||||
// no special price for monthly plan
|
// no special price for monthly plan
|
||||||
if (lookupKey.recurring === SubscriptionRecurring.Monthly) {
|
if (lookupKey.recurring === SubscriptionRecurring.Monthly) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// show EA price instead of normal price if early access is available
|
return lookupKey.variant === null;
|
||||||
return proEarlyAccess && !proSubscribed
|
|
||||||
? lookupKey.variant === SubscriptionVariant.EA
|
|
||||||
: lookupKey.variant !== SubscriptionVariant.EA;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private async isAIPriceAvailable(
|
private async isAIPriceAvailable({ lookupKey }: KnownStripePrice) {
|
||||||
{ lookupKey }: KnownStripePrice,
|
|
||||||
{ aiEarlyAccess, aiSubscribed, onetime }: PriceStrategyStatus
|
|
||||||
) {
|
|
||||||
// no lifetime price for AI
|
// no lifetime price for AI
|
||||||
if (lookupKey.recurring === SubscriptionRecurring.Lifetime) {
|
if (lookupKey.recurring === SubscriptionRecurring.Lifetime) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// never show onetime prices
|
return lookupKey.variant === null;
|
||||||
if (lookupKey.variant === SubscriptionVariant.Onetime) {
|
|
||||||
return onetime;
|
|
||||||
}
|
|
||||||
|
|
||||||
// show EA price instead of normal price if early access is available
|
|
||||||
return aiEarlyAccess && !aiSubscribed
|
|
||||||
? lookupKey.variant === SubscriptionVariant.EA
|
|
||||||
: lookupKey.variant !== SubscriptionVariant.EA;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private async strategyStatus(
|
private async strategyStatus(
|
||||||
customer: UserStripeCustomer
|
customer: UserStripeCustomer
|
||||||
): Promise<PriceStrategyStatus> {
|
): Promise<PriceStrategyStatus> {
|
||||||
const proEarlyAccess = await this.feature.isEarlyAccessUser(
|
|
||||||
customer.userId,
|
|
||||||
EarlyAccessType.App
|
|
||||||
);
|
|
||||||
|
|
||||||
const aiEarlyAccess = await this.feature.isEarlyAccessUser(
|
|
||||||
customer.userId,
|
|
||||||
EarlyAccessType.AI
|
|
||||||
);
|
|
||||||
|
|
||||||
let proSubscribed = false;
|
let proSubscribed = false;
|
||||||
let aiSubscribed = false;
|
let aiSubscribed = false;
|
||||||
|
|
||||||
@@ -786,8 +541,6 @@ export class UserSubscriptionManager extends SubscriptionManager {
|
|||||||
status: 'all',
|
status: 'all',
|
||||||
});
|
});
|
||||||
|
|
||||||
// if the early access user had early access subscription in the past, but it got canceled or past due,
|
|
||||||
// the user will lose the early access privilege
|
|
||||||
for (const sub of subscriptions.data) {
|
for (const sub of subscriptions.data) {
|
||||||
const lookupKey = retriveLookupKeyFromStripeSubscription(sub);
|
const lookupKey = retriveLookupKeyFromStripeSubscription(sub);
|
||||||
if (!lookupKey) {
|
if (!lookupKey) {
|
||||||
@@ -803,13 +556,7 @@ export class UserSubscriptionManager extends SubscriptionManager {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return { proSubscribed, aiSubscribed };
|
||||||
proEarlyAccess,
|
|
||||||
aiEarlyAccess,
|
|
||||||
proSubscribed,
|
|
||||||
aiSubscribed,
|
|
||||||
onetime: false,
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private assertUserIdExists(
|
private assertUserIdExists(
|
||||||
|
|||||||
@@ -166,14 +166,6 @@ export class InvoiceType implements Partial<Invoice> {
|
|||||||
@Field(() => Date)
|
@Field(() => Date)
|
||||||
updatedAt!: Date;
|
updatedAt!: Date;
|
||||||
|
|
||||||
// deprecated fields
|
|
||||||
@Field(() => String, {
|
|
||||||
name: 'id',
|
|
||||||
nullable: true,
|
|
||||||
deprecationReason: 'removed',
|
|
||||||
})
|
|
||||||
stripeInvoiceId?: string;
|
|
||||||
|
|
||||||
@Field(() => SubscriptionPlan, {
|
@Field(() => SubscriptionPlan, {
|
||||||
nullable: true,
|
nullable: true,
|
||||||
deprecationReason: 'removed',
|
deprecationReason: 'removed',
|
||||||
@@ -475,12 +467,7 @@ export class UserSubscriptionResolver {
|
|||||||
) {}
|
) {}
|
||||||
|
|
||||||
private normalizeSubscription(s: Subscription) {
|
private normalizeSubscription(s: Subscription) {
|
||||||
if (
|
if (s.variant && s.variant !== SubscriptionVariant.Onetime) {
|
||||||
s.variant &&
|
|
||||||
![SubscriptionVariant.EA, SubscriptionVariant.Onetime].includes(
|
|
||||||
s.variant as SubscriptionVariant
|
|
||||||
)
|
|
||||||
) {
|
|
||||||
s.variant = null;
|
s.variant = null;
|
||||||
}
|
}
|
||||||
return s;
|
return s;
|
||||||
|
|||||||
@@ -55,7 +55,6 @@ import {
|
|||||||
SubscriptionPlan,
|
SubscriptionPlan,
|
||||||
SubscriptionRecurring,
|
SubscriptionRecurring,
|
||||||
SubscriptionStatus,
|
SubscriptionStatus,
|
||||||
SubscriptionVariant,
|
|
||||||
} from './types';
|
} from './types';
|
||||||
|
|
||||||
export const CheckoutExtraArgs = z.union([
|
export const CheckoutExtraArgs = z.union([
|
||||||
@@ -536,9 +535,8 @@ export class SubscriptionService {
|
|||||||
reason === 'dispute_open' ||
|
reason === 'dispute_open' ||
|
||||||
reason === 'dispute_lost';
|
reason === 'dispute_lost';
|
||||||
const restore = reason === 'dispute_won';
|
const restore = reason === 'dispute_won';
|
||||||
const isOneTimeOrLifetime =
|
const isLifetime =
|
||||||
knownInvoice.lookupKey.recurring === SubscriptionRecurring.Lifetime ||
|
knownInvoice.lookupKey.recurring === SubscriptionRecurring.Lifetime;
|
||||||
knownInvoice.lookupKey.variant === SubscriptionVariant.Onetime;
|
|
||||||
|
|
||||||
if (restore) {
|
if (restore) {
|
||||||
if (invoice.subscription) {
|
if (invoice.subscription) {
|
||||||
@@ -564,11 +562,11 @@ export class SubscriptionService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (
|
if (
|
||||||
isOneTimeOrLifetime &&
|
isLifetime &&
|
||||||
(knownInvoice.lookupKey.plan === SubscriptionPlan.Pro ||
|
(knownInvoice.lookupKey.plan === SubscriptionPlan.Pro ||
|
||||||
knownInvoice.lookupKey.plan === SubscriptionPlan.AI)
|
knownInvoice.lookupKey.plan === SubscriptionPlan.AI)
|
||||||
) {
|
) {
|
||||||
await this.userManager.restoreOnetimeOrLifetime(knownInvoice);
|
await this.userManager.restoreLifetime(knownInvoice);
|
||||||
}
|
}
|
||||||
|
|
||||||
return;
|
return;
|
||||||
@@ -615,11 +613,11 @@ export class SubscriptionService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (
|
if (
|
||||||
isOneTimeOrLifetime &&
|
isLifetime &&
|
||||||
(knownInvoice.lookupKey.plan === SubscriptionPlan.Pro ||
|
(knownInvoice.lookupKey.plan === SubscriptionPlan.Pro ||
|
||||||
knownInvoice.lookupKey.plan === SubscriptionPlan.AI)
|
knownInvoice.lookupKey.plan === SubscriptionPlan.AI)
|
||||||
) {
|
) {
|
||||||
await this.userManager.revokeOnetimeOrLifetime(knownInvoice);
|
await this.userManager.revokeLifetime(knownInvoice);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -7,7 +7,6 @@ import {
|
|||||||
decodeLookupKey,
|
decodeLookupKey,
|
||||||
DEFAULT_PRICES,
|
DEFAULT_PRICES,
|
||||||
SubscriptionRecurring,
|
SubscriptionRecurring,
|
||||||
SubscriptionVariant,
|
|
||||||
} from './types';
|
} from './types';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
@@ -39,20 +38,18 @@ export class StripeFactory {
|
|||||||
}
|
}
|
||||||
|
|
||||||
setup() {
|
setup() {
|
||||||
// Prefer new keys under payment.stripe.*, fallback to legacy root keys for backward compatibility
|
|
||||||
const {
|
const {
|
||||||
apiKey: nestedApiKey,
|
apiKey,
|
||||||
webhookKey: _,
|
webhookKey: _,
|
||||||
...config
|
...config
|
||||||
} = this.config.payment.stripe || {};
|
} = this.config.payment.stripe || {};
|
||||||
// NOTE:
|
// NOTE:
|
||||||
// we always fake a key if not set because `new Stripe` will complain if it's empty string
|
// we always fake a key if not set because `new Stripe` will complain if it's empty string
|
||||||
// this will make code cleaner than providing `Stripe` instance as optional one.
|
// this will make code cleaner than providing `Stripe` instance as optional one.
|
||||||
const apiKey =
|
const stripeApiKey = apiKey || 'stripe-api-key';
|
||||||
nestedApiKey || this.config.payment.apiKey || 'stripe-api-key';
|
|
||||||
|
|
||||||
// TODO@(@darkskygit): use per-requests api key injection
|
// TODO@(@darkskygit): use per-requests api key injection
|
||||||
this.#stripe = new Stripe(apiKey, config);
|
this.#stripe = new Stripe(stripeApiKey, config);
|
||||||
if (this.config.payment.enabled) {
|
if (this.config.payment.enabled) {
|
||||||
this.server.enableFeature(ServerFeature.Payment);
|
this.server.enableFeature(ServerFeature.Payment);
|
||||||
} else {
|
} else {
|
||||||
@@ -107,8 +104,7 @@ export class StripeFactory {
|
|||||||
lookup_key: key,
|
lookup_key: key,
|
||||||
tax_behavior: 'inclusive',
|
tax_behavior: 'inclusive',
|
||||||
recurring:
|
recurring:
|
||||||
lookupKey.recurring === SubscriptionRecurring.Lifetime ||
|
lookupKey.recurring === SubscriptionRecurring.Lifetime
|
||||||
lookupKey.variant === SubscriptionVariant.Onetime
|
|
||||||
? undefined
|
? undefined
|
||||||
: {
|
: {
|
||||||
interval:
|
interval:
|
||||||
|
|||||||
@@ -20,7 +20,6 @@ export enum SubscriptionPlan {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export enum SubscriptionVariant {
|
export enum SubscriptionVariant {
|
||||||
EA = 'earlyaccess',
|
|
||||||
Onetime = 'onetime',
|
Onetime = 'onetime',
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -44,12 +43,6 @@ export enum InvoiceStatus {
|
|||||||
Uncollectible = 'uncollectible',
|
Uncollectible = 'uncollectible',
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum CouponType {
|
|
||||||
ProEarlyAccessOneYearFree = 'pro_ea_one_year_free',
|
|
||||||
AIEarlyAccessOneYearFree = 'ai_ea_one_year_free',
|
|
||||||
ProEarlyAccessAIOneYearFree = 'ai_pro_ea_one_year_free',
|
|
||||||
}
|
|
||||||
|
|
||||||
declare global {
|
declare global {
|
||||||
interface Events {
|
interface Events {
|
||||||
'user.subscription.activated': {
|
'user.subscription.activated': {
|
||||||
@@ -199,11 +192,6 @@ export const DEFAULT_PRICES = new Map([
|
|||||||
price: 8100,
|
price: 8100,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
// only EA for yearly pro
|
|
||||||
[
|
|
||||||
`${SubscriptionPlan.Pro}_${SubscriptionRecurring.Yearly}_${SubscriptionVariant.EA}`,
|
|
||||||
{ product: 'AFFiNE Pro', price: 5000 },
|
|
||||||
],
|
|
||||||
[
|
[
|
||||||
`${SubscriptionPlan.Pro}_${SubscriptionRecurring.Lifetime}`,
|
`${SubscriptionPlan.Pro}_${SubscriptionRecurring.Lifetime}`,
|
||||||
{
|
{
|
||||||
@@ -211,29 +199,11 @@ export const DEFAULT_PRICES = new Map([
|
|||||||
price: 49900,
|
price: 49900,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
[
|
|
||||||
`${SubscriptionPlan.Pro}_${SubscriptionRecurring.Monthly}_${SubscriptionVariant.Onetime}`,
|
|
||||||
{ product: 'AFFiNE Pro - One Month', price: 799 },
|
|
||||||
],
|
|
||||||
[
|
|
||||||
`${SubscriptionPlan.Pro}_${SubscriptionRecurring.Yearly}_${SubscriptionVariant.Onetime}`,
|
|
||||||
{ product: 'AFFiNE Pro - One Year', price: 8100 },
|
|
||||||
],
|
|
||||||
|
|
||||||
// ai
|
// ai
|
||||||
[
|
[
|
||||||
`${SubscriptionPlan.AI}_${SubscriptionRecurring.Yearly}`,
|
`${SubscriptionPlan.AI}_${SubscriptionRecurring.Yearly}`,
|
||||||
{ product: 'AFFiNE AI', price: 10680 },
|
{ product: 'AFFiNE AI', price: 10680 },
|
||||||
],
|
],
|
||||||
// only EA for yearly AI
|
|
||||||
[
|
|
||||||
`${SubscriptionPlan.AI}_${SubscriptionRecurring.Yearly}_${SubscriptionVariant.EA}`,
|
|
||||||
{ product: 'AFFiNE AI', price: 9900 },
|
|
||||||
],
|
|
||||||
[
|
|
||||||
`${SubscriptionPlan.AI}_${SubscriptionRecurring.Yearly}_${SubscriptionVariant.Onetime}`,
|
|
||||||
{ product: 'AFFiNE AI - One Year', price: 10680 },
|
|
||||||
],
|
|
||||||
|
|
||||||
// team
|
// team
|
||||||
[
|
[
|
||||||
@@ -284,7 +254,7 @@ export function decodeLookupKey(key: string): LookupKey {
|
|||||||
return {
|
return {
|
||||||
plan: plan as SubscriptionPlan,
|
plan: plan as SubscriptionPlan,
|
||||||
recurring: recurring as SubscriptionRecurring,
|
recurring: recurring as SubscriptionRecurring,
|
||||||
variant: variant as SubscriptionVariant,
|
variant: (variant as SubscriptionVariant) ?? null,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -60,6 +60,8 @@ function toBadRequestReason(reason: SSRFBlockReason) {
|
|||||||
return 'Failed to resolve hostname';
|
return 'Failed to resolve hostname';
|
||||||
case 'too_many_redirects':
|
case 'too_many_redirects':
|
||||||
return 'Too many redirects';
|
return 'Too many redirects';
|
||||||
|
default:
|
||||||
|
return 'Blocked by SSRF protection';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1113,9 +1113,7 @@ type ExpectToUpdateDocUserRoleDataType {
|
|||||||
}
|
}
|
||||||
|
|
||||||
enum FeatureType {
|
enum FeatureType {
|
||||||
AIEarlyAccess
|
|
||||||
Admin
|
Admin
|
||||||
EarlyAccess
|
|
||||||
FreePlan
|
FreePlan
|
||||||
LifetimeProPlan
|
LifetimeProPlan
|
||||||
ProPlan
|
ProPlan
|
||||||
@@ -1340,9 +1338,6 @@ type InviteResult {
|
|||||||
|
|
||||||
"""Invite id, null if invite record create failed"""
|
"""Invite id, null if invite record create failed"""
|
||||||
inviteId: String
|
inviteId: String
|
||||||
|
|
||||||
"""Invite email sent success"""
|
|
||||||
sentSuccess: Boolean! @deprecated(reason: "Notification will be sent asynchronously")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type InviteUserType {
|
type InviteUserType {
|
||||||
@@ -1393,7 +1388,6 @@ type InvoiceType {
|
|||||||
amount: Int!
|
amount: Int!
|
||||||
createdAt: DateTime!
|
createdAt: DateTime!
|
||||||
currency: String!
|
currency: String!
|
||||||
id: String @deprecated(reason: "removed")
|
|
||||||
lastPaymentError: String
|
lastPaymentError: String
|
||||||
link: String
|
link: String
|
||||||
plan: SubscriptionPlan @deprecated(reason: "removed")
|
plan: SubscriptionPlan @deprecated(reason: "removed")
|
||||||
@@ -1695,7 +1689,7 @@ type Mutation {
|
|||||||
revokeMember(userId: String!, workspaceId: String!): Boolean!
|
revokeMember(userId: String!, workspaceId: String!): Boolean!
|
||||||
revokePublicDoc(docId: String!, workspaceId: String!): DocType!
|
revokePublicDoc(docId: String!, workspaceId: String!): DocType!
|
||||||
revokeUserAccessToken(id: String!): Boolean!
|
revokeUserAccessToken(id: String!): Boolean!
|
||||||
sendChangeEmail(callbackUrl: String!, email: String): Boolean!
|
sendChangeEmail(callbackUrl: String!): Boolean!
|
||||||
sendChangePasswordEmail(callbackUrl: String!, email: String @deprecated(reason: "fetched from signed in user")): Boolean!
|
sendChangePasswordEmail(callbackUrl: String!, email: String @deprecated(reason: "fetched from signed in user")): Boolean!
|
||||||
sendSetPasswordEmail(callbackUrl: String!, email: String @deprecated(reason: "fetched from signed in user")): Boolean!
|
sendSetPasswordEmail(callbackUrl: String!, email: String @deprecated(reason: "fetched from signed in user")): Boolean!
|
||||||
sendTestEmail(config: JSONObject!): Boolean!
|
sendTestEmail(config: JSONObject!): Boolean!
|
||||||
@@ -2399,7 +2393,6 @@ type SubscriptionType {
|
|||||||
}
|
}
|
||||||
|
|
||||||
enum SubscriptionVariant {
|
enum SubscriptionVariant {
|
||||||
EA
|
|
||||||
Onetime
|
Onetime
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2667,9 +2660,6 @@ type UserType {
|
|||||||
"""User name"""
|
"""User name"""
|
||||||
name: String!
|
name: String!
|
||||||
|
|
||||||
"""Get user notification count"""
|
|
||||||
notificationCount: Int! @deprecated(reason: "Use realtime subscription \"notification.count.changed\" instead.")
|
|
||||||
|
|
||||||
"""Get current user notifications"""
|
"""Get current user notifications"""
|
||||||
notifications(pagination: PaginationInput!): PaginatedNotificationObjectType!
|
notifications(pagination: PaginationInput!): PaginatedNotificationObjectType!
|
||||||
quota: UserQuotaType!
|
quota: UserQuotaType!
|
||||||
@@ -2835,7 +2825,6 @@ type WorkspaceQuotaType {
|
|||||||
name: String!
|
name: String!
|
||||||
overcapacityMemberCount: Int!
|
overcapacityMemberCount: Int!
|
||||||
storageQuota: SafeInt!
|
storageQuota: SafeInt!
|
||||||
usedSize: SafeInt! @deprecated(reason: "use `usedStorageQuota` instead")
|
|
||||||
usedStorageQuota: SafeInt!
|
usedStorageQuota: SafeInt!
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2348,7 +2348,6 @@ export const invoicesQuery = {
|
|||||||
currentUser {
|
currentUser {
|
||||||
invoiceCount
|
invoiceCount
|
||||||
invoices(take: $take, skip: $skip) {
|
invoices(take: $take, skip: $skip) {
|
||||||
id
|
|
||||||
status
|
status
|
||||||
currency
|
currency
|
||||||
amount
|
amount
|
||||||
@@ -2359,7 +2358,6 @@ export const invoicesQuery = {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}`,
|
}`,
|
||||||
deprecations: ["'id' is deprecated: removed"],
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export const leaveWorkspaceMutation = {
|
export const leaveWorkspaceMutation = {
|
||||||
@@ -2983,10 +2981,8 @@ export const inviteByEmailsMutation = {
|
|||||||
inviteMembers(workspaceId: $workspaceId, emails: $emails) {
|
inviteMembers(workspaceId: $workspaceId, emails: $emails) {
|
||||||
email
|
email
|
||||||
inviteId
|
inviteId
|
||||||
sentSuccess
|
|
||||||
}
|
}
|
||||||
}`,
|
}`,
|
||||||
deprecations: ["'sentSuccess' is deprecated: Notification will be sent asynchronously"],
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export const acceptInviteByInviteIdMutation = {
|
export const acceptInviteByInviteIdMutation = {
|
||||||
@@ -3023,7 +3019,6 @@ export const workspaceInvoicesQuery = {
|
|||||||
workspace(id: $workspaceId) {
|
workspace(id: $workspaceId) {
|
||||||
invoiceCount
|
invoiceCount
|
||||||
invoices(take: $take, skip: $skip) {
|
invoices(take: $take, skip: $skip) {
|
||||||
id
|
|
||||||
status
|
status
|
||||||
currency
|
currency
|
||||||
amount
|
amount
|
||||||
@@ -3034,7 +3029,6 @@ export const workspaceInvoicesQuery = {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}`,
|
}`,
|
||||||
deprecations: ["'id' is deprecated: removed"],
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getWorkspaceRolePermissionsQuery = {
|
export const getWorkspaceRolePermissionsQuery = {
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ query invoices($take: Int!, $skip: Int!) {
|
|||||||
currentUser {
|
currentUser {
|
||||||
invoiceCount
|
invoiceCount
|
||||||
invoices(take: $take, skip: $skip) {
|
invoices(take: $take, skip: $skip) {
|
||||||
id
|
|
||||||
status
|
status
|
||||||
currency
|
currency
|
||||||
amount
|
amount
|
||||||
|
|||||||
@@ -2,6 +2,5 @@ mutation inviteByEmails($workspaceId: String!, $emails: [String!]!) {
|
|||||||
inviteMembers(workspaceId: $workspaceId, emails: $emails) {
|
inviteMembers(workspaceId: $workspaceId, emails: $emails) {
|
||||||
email
|
email
|
||||||
inviteId
|
inviteId
|
||||||
sentSuccess
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -2,7 +2,6 @@ query workspaceInvoices($take: Int!, $skip: Int!, $workspaceId: String!) {
|
|||||||
workspace(id: $workspaceId) {
|
workspace(id: $workspaceId) {
|
||||||
invoiceCount
|
invoiceCount
|
||||||
invoices(take: $take, skip: $skip) {
|
invoices(take: $take, skip: $skip) {
|
||||||
id
|
|
||||||
status
|
status
|
||||||
currency
|
currency
|
||||||
amount
|
amount
|
||||||
|
|||||||
@@ -1338,9 +1338,7 @@ export interface ExpectToUpdateDocUserRoleDataType {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export enum FeatureType {
|
export enum FeatureType {
|
||||||
AIEarlyAccess = 'AIEarlyAccess',
|
|
||||||
Admin = 'Admin',
|
Admin = 'Admin',
|
||||||
EarlyAccess = 'EarlyAccess',
|
|
||||||
FreePlan = 'FreePlan',
|
FreePlan = 'FreePlan',
|
||||||
LifetimeProPlan = 'LifetimeProPlan',
|
LifetimeProPlan = 'LifetimeProPlan',
|
||||||
ProPlan = 'ProPlan',
|
ProPlan = 'ProPlan',
|
||||||
@@ -1564,11 +1562,6 @@ export interface InviteResult {
|
|||||||
error: Maybe<Scalars['JSONObject']['output']>;
|
error: Maybe<Scalars['JSONObject']['output']>;
|
||||||
/** Invite id, null if invite record create failed */
|
/** Invite id, null if invite record create failed */
|
||||||
inviteId: Maybe<Scalars['String']['output']>;
|
inviteId: Maybe<Scalars['String']['output']>;
|
||||||
/**
|
|
||||||
* Invite email sent success
|
|
||||||
* @deprecated Notification will be sent asynchronously
|
|
||||||
*/
|
|
||||||
sentSuccess: Scalars['Boolean']['output'];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface InviteUserType {
|
export interface InviteUserType {
|
||||||
@@ -1617,8 +1610,6 @@ export interface InvoiceType {
|
|||||||
amount: Scalars['Int']['output'];
|
amount: Scalars['Int']['output'];
|
||||||
createdAt: Scalars['DateTime']['output'];
|
createdAt: Scalars['DateTime']['output'];
|
||||||
currency: Scalars['String']['output'];
|
currency: Scalars['String']['output'];
|
||||||
/** @deprecated removed */
|
|
||||||
id: Maybe<Scalars['String']['output']>;
|
|
||||||
lastPaymentError: Maybe<Scalars['String']['output']>;
|
lastPaymentError: Maybe<Scalars['String']['output']>;
|
||||||
link: Maybe<Scalars['String']['output']>;
|
link: Maybe<Scalars['String']['output']>;
|
||||||
/** @deprecated removed */
|
/** @deprecated removed */
|
||||||
@@ -2273,7 +2264,6 @@ export interface MutationRevokeUserAccessTokenArgs {
|
|||||||
|
|
||||||
export interface MutationSendChangeEmailArgs {
|
export interface MutationSendChangeEmailArgs {
|
||||||
callbackUrl: Scalars['String']['input'];
|
callbackUrl: Scalars['String']['input'];
|
||||||
email?: InputMaybe<Scalars['String']['input']>;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface MutationSendChangePasswordEmailArgs {
|
export interface MutationSendChangePasswordEmailArgs {
|
||||||
@@ -3124,7 +3114,6 @@ export interface SubscriptionType {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export enum SubscriptionVariant {
|
export enum SubscriptionVariant {
|
||||||
EA = 'EA',
|
|
||||||
Onetime = 'Onetime',
|
Onetime = 'Onetime',
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -3396,11 +3385,6 @@ export interface UserType {
|
|||||||
invoices: Array<InvoiceType>;
|
invoices: Array<InvoiceType>;
|
||||||
/** User name */
|
/** User name */
|
||||||
name: Scalars['String']['output'];
|
name: Scalars['String']['output'];
|
||||||
/**
|
|
||||||
* Get user notification count
|
|
||||||
* @deprecated Use realtime subscription "notification.count.changed" instead.
|
|
||||||
*/
|
|
||||||
notificationCount: Scalars['Int']['output'];
|
|
||||||
/** Get current user notifications */
|
/** Get current user notifications */
|
||||||
notifications: PaginatedNotificationObjectType;
|
notifications: PaginatedNotificationObjectType;
|
||||||
quota: UserQuotaType;
|
quota: UserQuotaType;
|
||||||
@@ -3597,8 +3581,6 @@ export interface WorkspaceQuotaType {
|
|||||||
name: Scalars['String']['output'];
|
name: Scalars['String']['output'];
|
||||||
overcapacityMemberCount: Scalars['Int']['output'];
|
overcapacityMemberCount: Scalars['Int']['output'];
|
||||||
storageQuota: Scalars['SafeInt']['output'];
|
storageQuota: Scalars['SafeInt']['output'];
|
||||||
/** @deprecated use `usedStorageQuota` instead */
|
|
||||||
usedSize: Scalars['SafeInt']['output'];
|
|
||||||
usedStorageQuota: Scalars['SafeInt']['output'];
|
usedStorageQuota: Scalars['SafeInt']['output'];
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -6874,7 +6856,6 @@ export type InvoicesQuery = {
|
|||||||
invoiceCount: number;
|
invoiceCount: number;
|
||||||
invoices: Array<{
|
invoices: Array<{
|
||||||
__typename?: 'InvoiceType';
|
__typename?: 'InvoiceType';
|
||||||
id: string | null;
|
|
||||||
status: InvoiceStatus;
|
status: InvoiceStatus;
|
||||||
currency: string;
|
currency: string;
|
||||||
amount: number;
|
amount: number;
|
||||||
@@ -7598,7 +7579,6 @@ export type InviteByEmailsMutation = {
|
|||||||
__typename?: 'InviteResult';
|
__typename?: 'InviteResult';
|
||||||
email: string;
|
email: string;
|
||||||
inviteId: string | null;
|
inviteId: string | null;
|
||||||
sentSuccess: boolean;
|
|
||||||
}>;
|
}>;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -7648,7 +7628,6 @@ export type WorkspaceInvoicesQuery = {
|
|||||||
invoiceCount: number;
|
invoiceCount: number;
|
||||||
invoices: Array<{
|
invoices: Array<{
|
||||||
__typename?: 'InvoiceType';
|
__typename?: 'InvoiceType';
|
||||||
id: string | null;
|
|
||||||
status: InvoiceStatus;
|
status: InvoiceStatus;
|
||||||
currency: string;
|
currency: string;
|
||||||
amount: number;
|
amount: number;
|
||||||
|
|||||||
@@ -429,16 +429,6 @@
|
|||||||
"desc": "Number of workspaces automatically indexed per batch"
|
"desc": "Number of workspaces automatically indexed per batch"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"customerIo": {
|
|
||||||
"enabled": {
|
|
||||||
"type": "Boolean",
|
|
||||||
"desc": "Enable customer.io integration"
|
|
||||||
},
|
|
||||||
"token": {
|
|
||||||
"type": "String",
|
|
||||||
"desc": "Customer.io token"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"oauth": {
|
"oauth": {
|
||||||
"providers.google": {
|
"providers.google": {
|
||||||
"type": "Object",
|
"type": "Object",
|
||||||
@@ -470,16 +460,6 @@
|
|||||||
"type": "Boolean",
|
"type": "Boolean",
|
||||||
"desc": "Whether enable lifetime price and allow user to pay for it."
|
"desc": "Whether enable lifetime price and allow user to pay for it."
|
||||||
},
|
},
|
||||||
"apiKey": {
|
|
||||||
"type": "String",
|
|
||||||
"desc": "[Deprecated] Stripe API key. Use payment.stripe.apiKey instead.",
|
|
||||||
"env": "STRIPE_API_KEY"
|
|
||||||
},
|
|
||||||
"webhookKey": {
|
|
||||||
"type": "String",
|
|
||||||
"desc": "[Deprecated] Stripe webhook key. Use payment.stripe.webhookKey instead.",
|
|
||||||
"env": "STRIPE_WEBHOOK_KEY"
|
|
||||||
},
|
|
||||||
"stripe": {
|
"stripe": {
|
||||||
"type": "Object",
|
"type": "Object",
|
||||||
"desc": "Stripe sdk options and credentials",
|
"desc": "Stripe sdk options and credentials",
|
||||||
|
|||||||
@@ -37,13 +37,7 @@ type ConfigGroup<T extends AppConfigModule> = {
|
|||||||
const IGNORED_MODULES: (keyof AppConfig)[] = [];
|
const IGNORED_MODULES: (keyof AppConfig)[] = [];
|
||||||
|
|
||||||
if (environment.isSelfHosted) {
|
if (environment.isSelfHosted) {
|
||||||
IGNORED_MODULES.push(
|
IGNORED_MODULES.push('payment', 'captcha', 'telemetry', 'metrics');
|
||||||
'payment',
|
|
||||||
'customerIo',
|
|
||||||
'captcha',
|
|
||||||
'telemetry',
|
|
||||||
'metrics'
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const ALL_CONFIGURABLE_MODULES = Object.keys(CONFIG_DESCRIPTORS).filter(
|
const ALL_CONFIGURABLE_MODULES = Object.keys(CONFIG_DESCRIPTORS).filter(
|
||||||
|
|||||||
+1
-1
@@ -30,7 +30,7 @@ public class AdminUpdateWorkspaceMutation: GraphQLMutation {
|
|||||||
AdminUpdateWorkspaceMutation.Data.self
|
AdminUpdateWorkspaceMutation.Data.self
|
||||||
] }
|
] }
|
||||||
|
|
||||||
/// Update workspace flags and features for admin
|
/// Update workspace flags for admin
|
||||||
public var adminUpdateWorkspace: AdminUpdateWorkspace? { __data["adminUpdateWorkspace"] }
|
public var adminUpdateWorkspace: AdminUpdateWorkspace? { __data["adminUpdateWorkspace"] }
|
||||||
|
|
||||||
/// AdminUpdateWorkspace
|
/// AdminUpdateWorkspace
|
||||||
|
|||||||
+35
@@ -0,0 +1,35 @@
|
|||||||
|
// @generated
|
||||||
|
// This file was automatically generated and should not be edited.
|
||||||
|
|
||||||
|
@_exported import ApolloAPI
|
||||||
|
|
||||||
|
public class ClearWorkspaceByokConfigsMutation: GraphQLMutation {
|
||||||
|
public static let operationName: String = "clearWorkspaceByokConfigs"
|
||||||
|
public static let operationDocument: ApolloAPI.OperationDocument = .init(
|
||||||
|
definition: .init(
|
||||||
|
#"mutation clearWorkspaceByokConfigs($workspaceId: String!) { clearWorkspaceByokConfigs(workspaceId: $workspaceId) }"#
|
||||||
|
))
|
||||||
|
|
||||||
|
public var workspaceId: String
|
||||||
|
|
||||||
|
public init(workspaceId: String) {
|
||||||
|
self.workspaceId = workspaceId
|
||||||
|
}
|
||||||
|
|
||||||
|
public var __variables: Variables? { ["workspaceId": workspaceId] }
|
||||||
|
|
||||||
|
public struct Data: AffineGraphQL.SelectionSet {
|
||||||
|
public let __data: DataDict
|
||||||
|
public init(_dataDict: DataDict) { __data = _dataDict }
|
||||||
|
|
||||||
|
public static var __parentType: any ApolloAPI.ParentType { AffineGraphQL.Objects.Mutation }
|
||||||
|
public static var __selections: [ApolloAPI.Selection] { [
|
||||||
|
.field("clearWorkspaceByokConfigs", Bool.self, arguments: ["workspaceId": .variable("workspaceId")]),
|
||||||
|
] }
|
||||||
|
public static var __fulfilledFragments: [any ApolloAPI.SelectionSet.Type] { [
|
||||||
|
ClearWorkspaceByokConfigsMutation.Data.self
|
||||||
|
] }
|
||||||
|
|
||||||
|
public var clearWorkspaceByokConfigs: Bool { __data["clearWorkspaceByokConfigs"] }
|
||||||
|
}
|
||||||
|
}
|
||||||
+56
@@ -0,0 +1,56 @@
|
|||||||
|
// @generated
|
||||||
|
// This file was automatically generated and should not be edited.
|
||||||
|
|
||||||
|
@_exported import ApolloAPI
|
||||||
|
|
||||||
|
public class CreateWorkspaceByokLocalLeaseMutation: GraphQLMutation {
|
||||||
|
public static let operationName: String = "createWorkspaceByokLocalLease"
|
||||||
|
public static let operationDocument: ApolloAPI.OperationDocument = .init(
|
||||||
|
definition: .init(
|
||||||
|
#"mutation createWorkspaceByokLocalLease($input: CreateWorkspaceByokLocalLeaseInput!) { createWorkspaceByokLocalLease(input: $input) { __typename leaseId expiresAt } }"#
|
||||||
|
))
|
||||||
|
|
||||||
|
public var input: CreateWorkspaceByokLocalLeaseInput
|
||||||
|
|
||||||
|
public init(input: CreateWorkspaceByokLocalLeaseInput) {
|
||||||
|
self.input = input
|
||||||
|
}
|
||||||
|
|
||||||
|
public var __variables: Variables? { ["input": input] }
|
||||||
|
|
||||||
|
public struct Data: AffineGraphQL.SelectionSet {
|
||||||
|
public let __data: DataDict
|
||||||
|
public init(_dataDict: DataDict) { __data = _dataDict }
|
||||||
|
|
||||||
|
public static var __parentType: any ApolloAPI.ParentType { AffineGraphQL.Objects.Mutation }
|
||||||
|
public static var __selections: [ApolloAPI.Selection] { [
|
||||||
|
.field("createWorkspaceByokLocalLease", CreateWorkspaceByokLocalLease.self, arguments: ["input": .variable("input")]),
|
||||||
|
] }
|
||||||
|
public static var __fulfilledFragments: [any ApolloAPI.SelectionSet.Type] { [
|
||||||
|
CreateWorkspaceByokLocalLeaseMutation.Data.self
|
||||||
|
] }
|
||||||
|
|
||||||
|
public var createWorkspaceByokLocalLease: CreateWorkspaceByokLocalLease { __data["createWorkspaceByokLocalLease"] }
|
||||||
|
|
||||||
|
/// CreateWorkspaceByokLocalLease
|
||||||
|
///
|
||||||
|
/// Parent Type: `CreateWorkspaceByokLocalLeaseResultType`
|
||||||
|
public struct CreateWorkspaceByokLocalLease: AffineGraphQL.SelectionSet {
|
||||||
|
public let __data: DataDict
|
||||||
|
public init(_dataDict: DataDict) { __data = _dataDict }
|
||||||
|
|
||||||
|
public static var __parentType: any ApolloAPI.ParentType { AffineGraphQL.Objects.CreateWorkspaceByokLocalLeaseResultType }
|
||||||
|
public static var __selections: [ApolloAPI.Selection] { [
|
||||||
|
.field("__typename", String.self),
|
||||||
|
.field("leaseId", String.self),
|
||||||
|
.field("expiresAt", AffineGraphQL.DateTime.self),
|
||||||
|
] }
|
||||||
|
public static var __fulfilledFragments: [any ApolloAPI.SelectionSet.Type] { [
|
||||||
|
CreateWorkspaceByokLocalLeaseMutation.Data.CreateWorkspaceByokLocalLease.self
|
||||||
|
] }
|
||||||
|
|
||||||
|
public var leaseId: String { __data["leaseId"] }
|
||||||
|
public var expiresAt: AffineGraphQL.DateTime { __data["expiresAt"] }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+46
@@ -0,0 +1,46 @@
|
|||||||
|
// @generated
|
||||||
|
// This file was automatically generated and should not be edited.
|
||||||
|
|
||||||
|
@_exported import ApolloAPI
|
||||||
|
|
||||||
|
public class DeleteWorkspaceByokConfigMutation: GraphQLMutation {
|
||||||
|
public static let operationName: String = "deleteWorkspaceByokConfig"
|
||||||
|
public static let operationDocument: ApolloAPI.OperationDocument = .init(
|
||||||
|
definition: .init(
|
||||||
|
#"mutation deleteWorkspaceByokConfig($workspaceId: String!, $id: ID!) { deleteWorkspaceByokConfig(workspaceId: $workspaceId, id: $id) }"#
|
||||||
|
))
|
||||||
|
|
||||||
|
public var workspaceId: String
|
||||||
|
public var id: ID
|
||||||
|
|
||||||
|
public init(
|
||||||
|
workspaceId: String,
|
||||||
|
id: ID
|
||||||
|
) {
|
||||||
|
self.workspaceId = workspaceId
|
||||||
|
self.id = id
|
||||||
|
}
|
||||||
|
|
||||||
|
public var __variables: Variables? { [
|
||||||
|
"workspaceId": workspaceId,
|
||||||
|
"id": id
|
||||||
|
] }
|
||||||
|
|
||||||
|
public struct Data: AffineGraphQL.SelectionSet {
|
||||||
|
public let __data: DataDict
|
||||||
|
public init(_dataDict: DataDict) { __data = _dataDict }
|
||||||
|
|
||||||
|
public static var __parentType: any ApolloAPI.ParentType { AffineGraphQL.Objects.Mutation }
|
||||||
|
public static var __selections: [ApolloAPI.Selection] { [
|
||||||
|
.field("deleteWorkspaceByokConfig", Bool.self, arguments: [
|
||||||
|
"workspaceId": .variable("workspaceId"),
|
||||||
|
"id": .variable("id")
|
||||||
|
]),
|
||||||
|
] }
|
||||||
|
public static var __fulfilledFragments: [any ApolloAPI.SelectionSet.Type] { [
|
||||||
|
DeleteWorkspaceByokConfigMutation.Data.self
|
||||||
|
] }
|
||||||
|
|
||||||
|
public var deleteWorkspaceByokConfig: Bool { __data["deleteWorkspaceByokConfig"] }
|
||||||
|
}
|
||||||
|
}
|
||||||
+1
-5
@@ -7,7 +7,7 @@ public class InviteByEmailsMutation: GraphQLMutation {
|
|||||||
public static let operationName: String = "inviteByEmails"
|
public static let operationName: String = "inviteByEmails"
|
||||||
public static let operationDocument: ApolloAPI.OperationDocument = .init(
|
public static let operationDocument: ApolloAPI.OperationDocument = .init(
|
||||||
definition: .init(
|
definition: .init(
|
||||||
#"mutation inviteByEmails($workspaceId: String!, $emails: [String!]!) { inviteMembers(workspaceId: $workspaceId, emails: $emails) { __typename email inviteId sentSuccess } }"#
|
#"mutation inviteByEmails($workspaceId: String!, $emails: [String!]!) { inviteMembers(workspaceId: $workspaceId, emails: $emails) { __typename email inviteId } }"#
|
||||||
))
|
))
|
||||||
|
|
||||||
public var workspaceId: String
|
public var workspaceId: String
|
||||||
@@ -55,7 +55,6 @@ public class InviteByEmailsMutation: GraphQLMutation {
|
|||||||
.field("__typename", String.self),
|
.field("__typename", String.self),
|
||||||
.field("email", String.self),
|
.field("email", String.self),
|
||||||
.field("inviteId", String?.self),
|
.field("inviteId", String?.self),
|
||||||
.field("sentSuccess", Bool.self),
|
|
||||||
] }
|
] }
|
||||||
public static var __fulfilledFragments: [any ApolloAPI.SelectionSet.Type] { [
|
public static var __fulfilledFragments: [any ApolloAPI.SelectionSet.Type] { [
|
||||||
InviteByEmailsMutation.Data.InviteMember.self
|
InviteByEmailsMutation.Data.InviteMember.self
|
||||||
@@ -64,9 +63,6 @@ public class InviteByEmailsMutation: GraphQLMutation {
|
|||||||
public var email: String { __data["email"] }
|
public var email: String { __data["email"] }
|
||||||
/// Invite id, null if invite record create failed
|
/// Invite id, null if invite record create failed
|
||||||
public var inviteId: String? { __data["inviteId"] }
|
public var inviteId: String? { __data["inviteId"] }
|
||||||
/// Invite email sent success
|
|
||||||
@available(*, deprecated, message: "Notification will be sent asynchronously")
|
|
||||||
public var sentSuccess: Bool { __data["sentSuccess"] }
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
+74
@@ -0,0 +1,74 @@
|
|||||||
|
// @generated
|
||||||
|
// This file was automatically generated and should not be edited.
|
||||||
|
|
||||||
|
@_exported import ApolloAPI
|
||||||
|
|
||||||
|
public class PreviewLicenseMutation: GraphQLMutation {
|
||||||
|
public static let operationName: String = "previewLicense"
|
||||||
|
public static let operationDocument: ApolloAPI.OperationDocument = .init(
|
||||||
|
definition: .init(
|
||||||
|
#"mutation previewLicense($license: Upload!) { previewLicense(license: $license) { __typename id workspaceId plan recurring quantity issuedAt expiresAt endAt entity issuer valid } }"#
|
||||||
|
))
|
||||||
|
|
||||||
|
public var license: Upload
|
||||||
|
|
||||||
|
public init(license: Upload) {
|
||||||
|
self.license = license
|
||||||
|
}
|
||||||
|
|
||||||
|
public var __variables: Variables? { ["license": license] }
|
||||||
|
|
||||||
|
public struct Data: AffineGraphQL.SelectionSet {
|
||||||
|
public let __data: DataDict
|
||||||
|
public init(_dataDict: DataDict) { __data = _dataDict }
|
||||||
|
|
||||||
|
public static var __parentType: any ApolloAPI.ParentType { AffineGraphQL.Objects.Mutation }
|
||||||
|
public static var __selections: [ApolloAPI.Selection] { [
|
||||||
|
.field("previewLicense", PreviewLicense.self, arguments: ["license": .variable("license")]),
|
||||||
|
] }
|
||||||
|
public static var __fulfilledFragments: [any ApolloAPI.SelectionSet.Type] { [
|
||||||
|
PreviewLicenseMutation.Data.self
|
||||||
|
] }
|
||||||
|
|
||||||
|
public var previewLicense: PreviewLicense { __data["previewLicense"] }
|
||||||
|
|
||||||
|
/// PreviewLicense
|
||||||
|
///
|
||||||
|
/// Parent Type: `AdminLicensePreview`
|
||||||
|
public struct PreviewLicense: AffineGraphQL.SelectionSet {
|
||||||
|
public let __data: DataDict
|
||||||
|
public init(_dataDict: DataDict) { __data = _dataDict }
|
||||||
|
|
||||||
|
public static var __parentType: any ApolloAPI.ParentType { AffineGraphQL.Objects.AdminLicensePreview }
|
||||||
|
public static var __selections: [ApolloAPI.Selection] { [
|
||||||
|
.field("__typename", String.self),
|
||||||
|
.field("id", String.self),
|
||||||
|
.field("workspaceId", String.self),
|
||||||
|
.field("plan", GraphQLEnum<AffineGraphQL.SubscriptionPlan>.self),
|
||||||
|
.field("recurring", GraphQLEnum<AffineGraphQL.SubscriptionRecurring>.self),
|
||||||
|
.field("quantity", Int.self),
|
||||||
|
.field("issuedAt", AffineGraphQL.DateTime.self),
|
||||||
|
.field("expiresAt", AffineGraphQL.DateTime.self),
|
||||||
|
.field("endAt", AffineGraphQL.DateTime.self),
|
||||||
|
.field("entity", String.self),
|
||||||
|
.field("issuer", String.self),
|
||||||
|
.field("valid", Bool.self),
|
||||||
|
] }
|
||||||
|
public static var __fulfilledFragments: [any ApolloAPI.SelectionSet.Type] { [
|
||||||
|
PreviewLicenseMutation.Data.PreviewLicense.self
|
||||||
|
] }
|
||||||
|
|
||||||
|
public var id: String { __data["id"] }
|
||||||
|
public var workspaceId: String { __data["workspaceId"] }
|
||||||
|
public var plan: GraphQLEnum<AffineGraphQL.SubscriptionPlan> { __data["plan"] }
|
||||||
|
public var recurring: GraphQLEnum<AffineGraphQL.SubscriptionRecurring> { __data["recurring"] }
|
||||||
|
public var quantity: Int { __data["quantity"] }
|
||||||
|
public var issuedAt: AffineGraphQL.DateTime { __data["issuedAt"] }
|
||||||
|
public var expiresAt: AffineGraphQL.DateTime { __data["expiresAt"] }
|
||||||
|
public var endAt: AffineGraphQL.DateTime { __data["endAt"] }
|
||||||
|
public var entity: String { __data["entity"] }
|
||||||
|
public var issuer: String { __data["issuer"] }
|
||||||
|
public var valid: Bool { __data["valid"] }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+56
@@ -0,0 +1,56 @@
|
|||||||
|
// @generated
|
||||||
|
// This file was automatically generated and should not be edited.
|
||||||
|
|
||||||
|
@_exported import ApolloAPI
|
||||||
|
|
||||||
|
public class ReorderWorkspaceByokConfigsMutation: GraphQLMutation {
|
||||||
|
public static let operationName: String = "reorderWorkspaceByokConfigs"
|
||||||
|
public static let operationDocument: ApolloAPI.OperationDocument = .init(
|
||||||
|
definition: .init(
|
||||||
|
#"mutation reorderWorkspaceByokConfigs($input: ReorderWorkspaceByokConfigsInput!) { reorderWorkspaceByokConfigs(input: $input) { __typename id sortOrder } }"#
|
||||||
|
))
|
||||||
|
|
||||||
|
public var input: ReorderWorkspaceByokConfigsInput
|
||||||
|
|
||||||
|
public init(input: ReorderWorkspaceByokConfigsInput) {
|
||||||
|
self.input = input
|
||||||
|
}
|
||||||
|
|
||||||
|
public var __variables: Variables? { ["input": input] }
|
||||||
|
|
||||||
|
public struct Data: AffineGraphQL.SelectionSet {
|
||||||
|
public let __data: DataDict
|
||||||
|
public init(_dataDict: DataDict) { __data = _dataDict }
|
||||||
|
|
||||||
|
public static var __parentType: any ApolloAPI.ParentType { AffineGraphQL.Objects.Mutation }
|
||||||
|
public static var __selections: [ApolloAPI.Selection] { [
|
||||||
|
.field("reorderWorkspaceByokConfigs", [ReorderWorkspaceByokConfig].self, arguments: ["input": .variable("input")]),
|
||||||
|
] }
|
||||||
|
public static var __fulfilledFragments: [any ApolloAPI.SelectionSet.Type] { [
|
||||||
|
ReorderWorkspaceByokConfigsMutation.Data.self
|
||||||
|
] }
|
||||||
|
|
||||||
|
public var reorderWorkspaceByokConfigs: [ReorderWorkspaceByokConfig] { __data["reorderWorkspaceByokConfigs"] }
|
||||||
|
|
||||||
|
/// ReorderWorkspaceByokConfig
|
||||||
|
///
|
||||||
|
/// Parent Type: `WorkspaceByokKeyConfigType`
|
||||||
|
public struct ReorderWorkspaceByokConfig: AffineGraphQL.SelectionSet {
|
||||||
|
public let __data: DataDict
|
||||||
|
public init(_dataDict: DataDict) { __data = _dataDict }
|
||||||
|
|
||||||
|
public static var __parentType: any ApolloAPI.ParentType { AffineGraphQL.Objects.WorkspaceByokKeyConfigType }
|
||||||
|
public static var __selections: [ApolloAPI.Selection] { [
|
||||||
|
.field("__typename", String.self),
|
||||||
|
.field("id", AffineGraphQL.ID.self),
|
||||||
|
.field("sortOrder", AffineGraphQL.SafeInt.self),
|
||||||
|
] }
|
||||||
|
public static var __fulfilledFragments: [any ApolloAPI.SelectionSet.Type] { [
|
||||||
|
ReorderWorkspaceByokConfigsMutation.Data.ReorderWorkspaceByokConfig.self
|
||||||
|
] }
|
||||||
|
|
||||||
|
public var id: AffineGraphQL.ID { __data["id"] }
|
||||||
|
public var sortOrder: AffineGraphQL.SafeInt { __data["sortOrder"] }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+67
@@ -0,0 +1,67 @@
|
|||||||
|
// @generated
|
||||||
|
// This file was automatically generated and should not be edited.
|
||||||
|
|
||||||
|
@_exported import ApolloAPI
|
||||||
|
|
||||||
|
public class RetryTranscriptTaskMutation: GraphQLMutation {
|
||||||
|
public static let operationName: String = "retryTranscriptTask"
|
||||||
|
public static let operationDocument: ApolloAPI.OperationDocument = .init(
|
||||||
|
definition: .init(
|
||||||
|
#"mutation retryTranscriptTask($workspaceId: String!, $taskId: String!) { retryTranscriptTask(workspaceId: $workspaceId, taskId: $taskId) { __typename id status } }"#
|
||||||
|
))
|
||||||
|
|
||||||
|
public var workspaceId: String
|
||||||
|
public var taskId: String
|
||||||
|
|
||||||
|
public init(
|
||||||
|
workspaceId: String,
|
||||||
|
taskId: String
|
||||||
|
) {
|
||||||
|
self.workspaceId = workspaceId
|
||||||
|
self.taskId = taskId
|
||||||
|
}
|
||||||
|
|
||||||
|
public var __variables: Variables? { [
|
||||||
|
"workspaceId": workspaceId,
|
||||||
|
"taskId": taskId
|
||||||
|
] }
|
||||||
|
|
||||||
|
public struct Data: AffineGraphQL.SelectionSet {
|
||||||
|
public let __data: DataDict
|
||||||
|
public init(_dataDict: DataDict) { __data = _dataDict }
|
||||||
|
|
||||||
|
public static var __parentType: any ApolloAPI.ParentType { AffineGraphQL.Objects.Mutation }
|
||||||
|
public static var __selections: [ApolloAPI.Selection] { [
|
||||||
|
.field("retryTranscriptTask", RetryTranscriptTask?.self, arguments: [
|
||||||
|
"workspaceId": .variable("workspaceId"),
|
||||||
|
"taskId": .variable("taskId")
|
||||||
|
]),
|
||||||
|
] }
|
||||||
|
public static var __fulfilledFragments: [any ApolloAPI.SelectionSet.Type] { [
|
||||||
|
RetryTranscriptTaskMutation.Data.self
|
||||||
|
] }
|
||||||
|
|
||||||
|
public var retryTranscriptTask: RetryTranscriptTask? { __data["retryTranscriptTask"] }
|
||||||
|
|
||||||
|
/// RetryTranscriptTask
|
||||||
|
///
|
||||||
|
/// Parent Type: `TranscriptionResultType`
|
||||||
|
public struct RetryTranscriptTask: AffineGraphQL.SelectionSet {
|
||||||
|
public let __data: DataDict
|
||||||
|
public init(_dataDict: DataDict) { __data = _dataDict }
|
||||||
|
|
||||||
|
public static var __parentType: any ApolloAPI.ParentType { AffineGraphQL.Objects.TranscriptionResultType }
|
||||||
|
public static var __selections: [ApolloAPI.Selection] { [
|
||||||
|
.field("__typename", String.self),
|
||||||
|
.field("id", AffineGraphQL.ID.self),
|
||||||
|
.field("status", GraphQLEnum<AffineGraphQL.AiJobStatus>.self),
|
||||||
|
] }
|
||||||
|
public static var __fulfilledFragments: [any ApolloAPI.SelectionSet.Type] { [
|
||||||
|
RetryTranscriptTaskMutation.Data.RetryTranscriptTask.self
|
||||||
|
] }
|
||||||
|
|
||||||
|
public var id: AffineGraphQL.ID { __data["id"] }
|
||||||
|
public var status: GraphQLEnum<AffineGraphQL.AiJobStatus> { __data["status"] }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+6
-1
@@ -7,9 +7,10 @@ public class SendTestEmailMutation: GraphQLMutation {
|
|||||||
public static let operationName: String = "sendTestEmail"
|
public static let operationName: String = "sendTestEmail"
|
||||||
public static let operationDocument: ApolloAPI.OperationDocument = .init(
|
public static let operationDocument: ApolloAPI.OperationDocument = .init(
|
||||||
definition: .init(
|
definition: .init(
|
||||||
#"mutation sendTestEmail($host: String!, $port: Int!, $sender: String!, $username: String!, $password: String!, $ignoreTLS: Boolean!) { sendTestEmail( config: { host: $host port: $port sender: $sender username: $username password: $password ignoreTLS: $ignoreTLS } ) }"#
|
#"mutation sendTestEmail($name: String!, $host: String!, $port: Int!, $sender: String!, $username: String!, $password: String!, $ignoreTLS: Boolean!) { sendTestEmail( config: { name: $name host: $host port: $port sender: $sender username: $username password: $password ignoreTLS: $ignoreTLS } ) }"#
|
||||||
))
|
))
|
||||||
|
|
||||||
|
public var name: String
|
||||||
public var host: String
|
public var host: String
|
||||||
public var port: Int
|
public var port: Int
|
||||||
public var sender: String
|
public var sender: String
|
||||||
@@ -18,6 +19,7 @@ public class SendTestEmailMutation: GraphQLMutation {
|
|||||||
public var ignoreTLS: Bool
|
public var ignoreTLS: Bool
|
||||||
|
|
||||||
public init(
|
public init(
|
||||||
|
name: String,
|
||||||
host: String,
|
host: String,
|
||||||
port: Int,
|
port: Int,
|
||||||
sender: String,
|
sender: String,
|
||||||
@@ -25,6 +27,7 @@ public class SendTestEmailMutation: GraphQLMutation {
|
|||||||
password: String,
|
password: String,
|
||||||
ignoreTLS: Bool
|
ignoreTLS: Bool
|
||||||
) {
|
) {
|
||||||
|
self.name = name
|
||||||
self.host = host
|
self.host = host
|
||||||
self.port = port
|
self.port = port
|
||||||
self.sender = sender
|
self.sender = sender
|
||||||
@@ -34,6 +37,7 @@ public class SendTestEmailMutation: GraphQLMutation {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public var __variables: Variables? { [
|
public var __variables: Variables? { [
|
||||||
|
"name": name,
|
||||||
"host": host,
|
"host": host,
|
||||||
"port": port,
|
"port": port,
|
||||||
"sender": sender,
|
"sender": sender,
|
||||||
@@ -49,6 +53,7 @@ public class SendTestEmailMutation: GraphQLMutation {
|
|||||||
public static var __parentType: any ApolloAPI.ParentType { AffineGraphQL.Objects.Mutation }
|
public static var __parentType: any ApolloAPI.ParentType { AffineGraphQL.Objects.Mutation }
|
||||||
public static var __selections: [ApolloAPI.Selection] { [
|
public static var __selections: [ApolloAPI.Selection] { [
|
||||||
.field("sendTestEmail", Bool.self, arguments: ["config": [
|
.field("sendTestEmail", Bool.self, arguments: ["config": [
|
||||||
|
"name": .variable("name"),
|
||||||
"host": .variable("host"),
|
"host": .variable("host"),
|
||||||
"port": .variable("port"),
|
"port": .variable("port"),
|
||||||
"sender": .variable("sender"),
|
"sender": .variable("sender"),
|
||||||
|
|||||||
+274
@@ -0,0 +1,274 @@
|
|||||||
|
// @generated
|
||||||
|
// This file was automatically generated and should not be edited.
|
||||||
|
|
||||||
|
@_exported import ApolloAPI
|
||||||
|
|
||||||
|
public class SettleTranscriptTaskMutation: GraphQLMutation {
|
||||||
|
public static let operationName: String = "settleTranscriptTask"
|
||||||
|
public static let operationDocument: ApolloAPI.OperationDocument = .init(
|
||||||
|
definition: .init(
|
||||||
|
#"mutation settleTranscriptTask($workspaceId: String!, $taskId: String!) { settleTranscriptTask(workspaceId: $workspaceId, taskId: $taskId) { __typename id status title summary actions sourceAudio { __typename blobId mimeType durationMs sampleRate channels } quality { __typename degraded overflowCount } sliceManifest { __typename index fileName mimeType startSec durationSec byteSize } normalizedSegments { __typename speaker startSec endSec start end text } normalizedTranscript summaryJson { __typename title durationMinutes attendees keyPoints actionItems { __typename description owner deadline } decisions openQuestions blockers } transcription { __typename speaker start end transcription } } }"#
|
||||||
|
))
|
||||||
|
|
||||||
|
public var workspaceId: String
|
||||||
|
public var taskId: String
|
||||||
|
|
||||||
|
public init(
|
||||||
|
workspaceId: String,
|
||||||
|
taskId: String
|
||||||
|
) {
|
||||||
|
self.workspaceId = workspaceId
|
||||||
|
self.taskId = taskId
|
||||||
|
}
|
||||||
|
|
||||||
|
public var __variables: Variables? { [
|
||||||
|
"workspaceId": workspaceId,
|
||||||
|
"taskId": taskId
|
||||||
|
] }
|
||||||
|
|
||||||
|
public struct Data: AffineGraphQL.SelectionSet {
|
||||||
|
public let __data: DataDict
|
||||||
|
public init(_dataDict: DataDict) { __data = _dataDict }
|
||||||
|
|
||||||
|
public static var __parentType: any ApolloAPI.ParentType { AffineGraphQL.Objects.Mutation }
|
||||||
|
public static var __selections: [ApolloAPI.Selection] { [
|
||||||
|
.field("settleTranscriptTask", SettleTranscriptTask?.self, arguments: [
|
||||||
|
"workspaceId": .variable("workspaceId"),
|
||||||
|
"taskId": .variable("taskId")
|
||||||
|
]),
|
||||||
|
] }
|
||||||
|
public static var __fulfilledFragments: [any ApolloAPI.SelectionSet.Type] { [
|
||||||
|
SettleTranscriptTaskMutation.Data.self
|
||||||
|
] }
|
||||||
|
|
||||||
|
public var settleTranscriptTask: SettleTranscriptTask? { __data["settleTranscriptTask"] }
|
||||||
|
|
||||||
|
/// SettleTranscriptTask
|
||||||
|
///
|
||||||
|
/// Parent Type: `TranscriptionResultType`
|
||||||
|
public struct SettleTranscriptTask: AffineGraphQL.SelectionSet {
|
||||||
|
public let __data: DataDict
|
||||||
|
public init(_dataDict: DataDict) { __data = _dataDict }
|
||||||
|
|
||||||
|
public static var __parentType: any ApolloAPI.ParentType { AffineGraphQL.Objects.TranscriptionResultType }
|
||||||
|
public static var __selections: [ApolloAPI.Selection] { [
|
||||||
|
.field("__typename", String.self),
|
||||||
|
.field("id", AffineGraphQL.ID.self),
|
||||||
|
.field("status", GraphQLEnum<AffineGraphQL.AiJobStatus>.self),
|
||||||
|
.field("title", String?.self),
|
||||||
|
.field("summary", String?.self),
|
||||||
|
.field("actions", String?.self),
|
||||||
|
.field("sourceAudio", SourceAudio?.self),
|
||||||
|
.field("quality", Quality?.self),
|
||||||
|
.field("sliceManifest", [SliceManifest]?.self),
|
||||||
|
.field("normalizedSegments", [NormalizedSegment]?.self),
|
||||||
|
.field("normalizedTranscript", String?.self),
|
||||||
|
.field("summaryJson", SummaryJson?.self),
|
||||||
|
.field("transcription", [Transcription]?.self),
|
||||||
|
] }
|
||||||
|
public static var __fulfilledFragments: [any ApolloAPI.SelectionSet.Type] { [
|
||||||
|
SettleTranscriptTaskMutation.Data.SettleTranscriptTask.self
|
||||||
|
] }
|
||||||
|
|
||||||
|
public var id: AffineGraphQL.ID { __data["id"] }
|
||||||
|
public var status: GraphQLEnum<AffineGraphQL.AiJobStatus> { __data["status"] }
|
||||||
|
public var title: String? { __data["title"] }
|
||||||
|
public var summary: String? { __data["summary"] }
|
||||||
|
public var actions: String? { __data["actions"] }
|
||||||
|
public var sourceAudio: SourceAudio? { __data["sourceAudio"] }
|
||||||
|
public var quality: Quality? { __data["quality"] }
|
||||||
|
public var sliceManifest: [SliceManifest]? { __data["sliceManifest"] }
|
||||||
|
public var normalizedSegments: [NormalizedSegment]? { __data["normalizedSegments"] }
|
||||||
|
public var normalizedTranscript: String? { __data["normalizedTranscript"] }
|
||||||
|
public var summaryJson: SummaryJson? { __data["summaryJson"] }
|
||||||
|
public var transcription: [Transcription]? { __data["transcription"] }
|
||||||
|
|
||||||
|
/// SettleTranscriptTask.SourceAudio
|
||||||
|
///
|
||||||
|
/// Parent Type: `TranscriptionSourceAudioType`
|
||||||
|
public struct SourceAudio: AffineGraphQL.SelectionSet {
|
||||||
|
public let __data: DataDict
|
||||||
|
public init(_dataDict: DataDict) { __data = _dataDict }
|
||||||
|
|
||||||
|
public static var __parentType: any ApolloAPI.ParentType { AffineGraphQL.Objects.TranscriptionSourceAudioType }
|
||||||
|
public static var __selections: [ApolloAPI.Selection] { [
|
||||||
|
.field("__typename", String.self),
|
||||||
|
.field("blobId", String?.self),
|
||||||
|
.field("mimeType", String?.self),
|
||||||
|
.field("durationMs", Int?.self),
|
||||||
|
.field("sampleRate", Int?.self),
|
||||||
|
.field("channels", Int?.self),
|
||||||
|
] }
|
||||||
|
public static var __fulfilledFragments: [any ApolloAPI.SelectionSet.Type] { [
|
||||||
|
SettleTranscriptTaskMutation.Data.SettleTranscriptTask.SourceAudio.self
|
||||||
|
] }
|
||||||
|
|
||||||
|
public var blobId: String? { __data["blobId"] }
|
||||||
|
public var mimeType: String? { __data["mimeType"] }
|
||||||
|
public var durationMs: Int? { __data["durationMs"] }
|
||||||
|
public var sampleRate: Int? { __data["sampleRate"] }
|
||||||
|
public var channels: Int? { __data["channels"] }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// SettleTranscriptTask.Quality
|
||||||
|
///
|
||||||
|
/// Parent Type: `TranscriptionQualityType`
|
||||||
|
public struct Quality: AffineGraphQL.SelectionSet {
|
||||||
|
public let __data: DataDict
|
||||||
|
public init(_dataDict: DataDict) { __data = _dataDict }
|
||||||
|
|
||||||
|
public static var __parentType: any ApolloAPI.ParentType { AffineGraphQL.Objects.TranscriptionQualityType }
|
||||||
|
public static var __selections: [ApolloAPI.Selection] { [
|
||||||
|
.field("__typename", String.self),
|
||||||
|
.field("degraded", Bool?.self),
|
||||||
|
.field("overflowCount", Int?.self),
|
||||||
|
] }
|
||||||
|
public static var __fulfilledFragments: [any ApolloAPI.SelectionSet.Type] { [
|
||||||
|
SettleTranscriptTaskMutation.Data.SettleTranscriptTask.Quality.self
|
||||||
|
] }
|
||||||
|
|
||||||
|
public var degraded: Bool? { __data["degraded"] }
|
||||||
|
public var overflowCount: Int? { __data["overflowCount"] }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// SettleTranscriptTask.SliceManifest
|
||||||
|
///
|
||||||
|
/// Parent Type: `AudioSliceManifestItemType`
|
||||||
|
public struct SliceManifest: AffineGraphQL.SelectionSet {
|
||||||
|
public let __data: DataDict
|
||||||
|
public init(_dataDict: DataDict) { __data = _dataDict }
|
||||||
|
|
||||||
|
public static var __parentType: any ApolloAPI.ParentType { AffineGraphQL.Objects.AudioSliceManifestItemType }
|
||||||
|
public static var __selections: [ApolloAPI.Selection] { [
|
||||||
|
.field("__typename", String.self),
|
||||||
|
.field("index", Int.self),
|
||||||
|
.field("fileName", String.self),
|
||||||
|
.field("mimeType", String.self),
|
||||||
|
.field("startSec", Double.self),
|
||||||
|
.field("durationSec", Double.self),
|
||||||
|
.field("byteSize", Int?.self),
|
||||||
|
] }
|
||||||
|
public static var __fulfilledFragments: [any ApolloAPI.SelectionSet.Type] { [
|
||||||
|
SettleTranscriptTaskMutation.Data.SettleTranscriptTask.SliceManifest.self
|
||||||
|
] }
|
||||||
|
|
||||||
|
public var index: Int { __data["index"] }
|
||||||
|
public var fileName: String { __data["fileName"] }
|
||||||
|
public var mimeType: String { __data["mimeType"] }
|
||||||
|
public var startSec: Double { __data["startSec"] }
|
||||||
|
public var durationSec: Double { __data["durationSec"] }
|
||||||
|
public var byteSize: Int? { __data["byteSize"] }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// SettleTranscriptTask.NormalizedSegment
|
||||||
|
///
|
||||||
|
/// Parent Type: `NormalizedTranscriptSegmentType`
|
||||||
|
public struct NormalizedSegment: AffineGraphQL.SelectionSet {
|
||||||
|
public let __data: DataDict
|
||||||
|
public init(_dataDict: DataDict) { __data = _dataDict }
|
||||||
|
|
||||||
|
public static var __parentType: any ApolloAPI.ParentType { AffineGraphQL.Objects.NormalizedTranscriptSegmentType }
|
||||||
|
public static var __selections: [ApolloAPI.Selection] { [
|
||||||
|
.field("__typename", String.self),
|
||||||
|
.field("speaker", String.self),
|
||||||
|
.field("startSec", Double.self),
|
||||||
|
.field("endSec", Double.self),
|
||||||
|
.field("start", String.self),
|
||||||
|
.field("end", String.self),
|
||||||
|
.field("text", String.self),
|
||||||
|
] }
|
||||||
|
public static var __fulfilledFragments: [any ApolloAPI.SelectionSet.Type] { [
|
||||||
|
SettleTranscriptTaskMutation.Data.SettleTranscriptTask.NormalizedSegment.self
|
||||||
|
] }
|
||||||
|
|
||||||
|
public var speaker: String { __data["speaker"] }
|
||||||
|
public var startSec: Double { __data["startSec"] }
|
||||||
|
public var endSec: Double { __data["endSec"] }
|
||||||
|
public var start: String { __data["start"] }
|
||||||
|
public var end: String { __data["end"] }
|
||||||
|
public var text: String { __data["text"] }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// SettleTranscriptTask.SummaryJson
|
||||||
|
///
|
||||||
|
/// Parent Type: `MeetingSummaryV2Type`
|
||||||
|
public struct SummaryJson: AffineGraphQL.SelectionSet {
|
||||||
|
public let __data: DataDict
|
||||||
|
public init(_dataDict: DataDict) { __data = _dataDict }
|
||||||
|
|
||||||
|
public static var __parentType: any ApolloAPI.ParentType { AffineGraphQL.Objects.MeetingSummaryV2Type }
|
||||||
|
public static var __selections: [ApolloAPI.Selection] { [
|
||||||
|
.field("__typename", String.self),
|
||||||
|
.field("title", String.self),
|
||||||
|
.field("durationMinutes", Double.self),
|
||||||
|
.field("attendees", [String].self),
|
||||||
|
.field("keyPoints", [String].self),
|
||||||
|
.field("actionItems", [ActionItem].self),
|
||||||
|
.field("decisions", [String].self),
|
||||||
|
.field("openQuestions", [String].self),
|
||||||
|
.field("blockers", [String].self),
|
||||||
|
] }
|
||||||
|
public static var __fulfilledFragments: [any ApolloAPI.SelectionSet.Type] { [
|
||||||
|
SettleTranscriptTaskMutation.Data.SettleTranscriptTask.SummaryJson.self
|
||||||
|
] }
|
||||||
|
|
||||||
|
public var title: String { __data["title"] }
|
||||||
|
public var durationMinutes: Double { __data["durationMinutes"] }
|
||||||
|
public var attendees: [String] { __data["attendees"] }
|
||||||
|
public var keyPoints: [String] { __data["keyPoints"] }
|
||||||
|
public var actionItems: [ActionItem] { __data["actionItems"] }
|
||||||
|
public var decisions: [String] { __data["decisions"] }
|
||||||
|
public var openQuestions: [String] { __data["openQuestions"] }
|
||||||
|
public var blockers: [String] { __data["blockers"] }
|
||||||
|
|
||||||
|
/// SettleTranscriptTask.SummaryJson.ActionItem
|
||||||
|
///
|
||||||
|
/// Parent Type: `MeetingActionItemType`
|
||||||
|
public struct ActionItem: AffineGraphQL.SelectionSet {
|
||||||
|
public let __data: DataDict
|
||||||
|
public init(_dataDict: DataDict) { __data = _dataDict }
|
||||||
|
|
||||||
|
public static var __parentType: any ApolloAPI.ParentType { AffineGraphQL.Objects.MeetingActionItemType }
|
||||||
|
public static var __selections: [ApolloAPI.Selection] { [
|
||||||
|
.field("__typename", String.self),
|
||||||
|
.field("description", String.self),
|
||||||
|
.field("owner", String?.self),
|
||||||
|
.field("deadline", String?.self),
|
||||||
|
] }
|
||||||
|
public static var __fulfilledFragments: [any ApolloAPI.SelectionSet.Type] { [
|
||||||
|
SettleTranscriptTaskMutation.Data.SettleTranscriptTask.SummaryJson.ActionItem.self
|
||||||
|
] }
|
||||||
|
|
||||||
|
public var description: String { __data["description"] }
|
||||||
|
public var owner: String? { __data["owner"] }
|
||||||
|
public var deadline: String? { __data["deadline"] }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// SettleTranscriptTask.Transcription
|
||||||
|
///
|
||||||
|
/// Parent Type: `TranscriptionItemType`
|
||||||
|
public struct Transcription: AffineGraphQL.SelectionSet {
|
||||||
|
public let __data: DataDict
|
||||||
|
public init(_dataDict: DataDict) { __data = _dataDict }
|
||||||
|
|
||||||
|
public static var __parentType: any ApolloAPI.ParentType { AffineGraphQL.Objects.TranscriptionItemType }
|
||||||
|
public static var __selections: [ApolloAPI.Selection] { [
|
||||||
|
.field("__typename", String.self),
|
||||||
|
.field("speaker", String.self),
|
||||||
|
.field("start", String.self),
|
||||||
|
.field("end", String.self),
|
||||||
|
.field("transcription", String.self),
|
||||||
|
] }
|
||||||
|
public static var __fulfilledFragments: [any ApolloAPI.SelectionSet.Type] { [
|
||||||
|
SettleTranscriptTaskMutation.Data.SettleTranscriptTask.Transcription.self
|
||||||
|
] }
|
||||||
|
|
||||||
|
public var speaker: String { __data["speaker"] }
|
||||||
|
public var start: String { __data["start"] }
|
||||||
|
public var end: String { __data["end"] }
|
||||||
|
public var transcription: String { __data["transcription"] }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+82
@@ -0,0 +1,82 @@
|
|||||||
|
// @generated
|
||||||
|
// This file was automatically generated and should not be edited.
|
||||||
|
|
||||||
|
@_exported import ApolloAPI
|
||||||
|
|
||||||
|
public class SubmitTranscriptTaskMutation: GraphQLMutation {
|
||||||
|
public static let operationName: String = "submitTranscriptTask"
|
||||||
|
public static let operationDocument: ApolloAPI.OperationDocument = .init(
|
||||||
|
definition: .init(
|
||||||
|
#"mutation submitTranscriptTask($workspaceId: String!, $blobId: String!, $blob: Upload, $blobs: [Upload!], $input: SubmitAudioTranscriptionInput) { submitTranscriptTask( blob: $blob blobs: $blobs blobId: $blobId workspaceId: $workspaceId input: $input ) { __typename id status } }"#
|
||||||
|
))
|
||||||
|
|
||||||
|
public var workspaceId: String
|
||||||
|
public var blobId: String
|
||||||
|
public var blob: GraphQLNullable<Upload>
|
||||||
|
public var blobs: GraphQLNullable<[Upload]>
|
||||||
|
public var input: GraphQLNullable<SubmitAudioTranscriptionInput>
|
||||||
|
|
||||||
|
public init(
|
||||||
|
workspaceId: String,
|
||||||
|
blobId: String,
|
||||||
|
blob: GraphQLNullable<Upload>,
|
||||||
|
blobs: GraphQLNullable<[Upload]>,
|
||||||
|
input: GraphQLNullable<SubmitAudioTranscriptionInput>
|
||||||
|
) {
|
||||||
|
self.workspaceId = workspaceId
|
||||||
|
self.blobId = blobId
|
||||||
|
self.blob = blob
|
||||||
|
self.blobs = blobs
|
||||||
|
self.input = input
|
||||||
|
}
|
||||||
|
|
||||||
|
public var __variables: Variables? { [
|
||||||
|
"workspaceId": workspaceId,
|
||||||
|
"blobId": blobId,
|
||||||
|
"blob": blob,
|
||||||
|
"blobs": blobs,
|
||||||
|
"input": input
|
||||||
|
] }
|
||||||
|
|
||||||
|
public struct Data: AffineGraphQL.SelectionSet {
|
||||||
|
public let __data: DataDict
|
||||||
|
public init(_dataDict: DataDict) { __data = _dataDict }
|
||||||
|
|
||||||
|
public static var __parentType: any ApolloAPI.ParentType { AffineGraphQL.Objects.Mutation }
|
||||||
|
public static var __selections: [ApolloAPI.Selection] { [
|
||||||
|
.field("submitTranscriptTask", SubmitTranscriptTask?.self, arguments: [
|
||||||
|
"blob": .variable("blob"),
|
||||||
|
"blobs": .variable("blobs"),
|
||||||
|
"blobId": .variable("blobId"),
|
||||||
|
"workspaceId": .variable("workspaceId"),
|
||||||
|
"input": .variable("input")
|
||||||
|
]),
|
||||||
|
] }
|
||||||
|
public static var __fulfilledFragments: [any ApolloAPI.SelectionSet.Type] { [
|
||||||
|
SubmitTranscriptTaskMutation.Data.self
|
||||||
|
] }
|
||||||
|
|
||||||
|
public var submitTranscriptTask: SubmitTranscriptTask? { __data["submitTranscriptTask"] }
|
||||||
|
|
||||||
|
/// SubmitTranscriptTask
|
||||||
|
///
|
||||||
|
/// Parent Type: `TranscriptionResultType`
|
||||||
|
public struct SubmitTranscriptTask: AffineGraphQL.SelectionSet {
|
||||||
|
public let __data: DataDict
|
||||||
|
public init(_dataDict: DataDict) { __data = _dataDict }
|
||||||
|
|
||||||
|
public static var __parentType: any ApolloAPI.ParentType { AffineGraphQL.Objects.TranscriptionResultType }
|
||||||
|
public static var __selections: [ApolloAPI.Selection] { [
|
||||||
|
.field("__typename", String.self),
|
||||||
|
.field("id", AffineGraphQL.ID.self),
|
||||||
|
.field("status", GraphQLEnum<AffineGraphQL.AiJobStatus>.self),
|
||||||
|
] }
|
||||||
|
public static var __fulfilledFragments: [any ApolloAPI.SelectionSet.Type] { [
|
||||||
|
SubmitTranscriptTaskMutation.Data.SubmitTranscriptTask.self
|
||||||
|
] }
|
||||||
|
|
||||||
|
public var id: AffineGraphQL.ID { __data["id"] }
|
||||||
|
public var status: GraphQLEnum<AffineGraphQL.AiJobStatus> { __data["status"] }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+58
@@ -0,0 +1,58 @@
|
|||||||
|
// @generated
|
||||||
|
// This file was automatically generated and should not be edited.
|
||||||
|
|
||||||
|
@_exported import ApolloAPI
|
||||||
|
|
||||||
|
public class TestWorkspaceByokConfigMutation: GraphQLMutation {
|
||||||
|
public static let operationName: String = "testWorkspaceByokConfig"
|
||||||
|
public static let operationDocument: ApolloAPI.OperationDocument = .init(
|
||||||
|
definition: .init(
|
||||||
|
#"mutation testWorkspaceByokConfig($input: TestWorkspaceByokConfigInput!) { testWorkspaceByokConfig(input: $input) { __typename ok status message } }"#
|
||||||
|
))
|
||||||
|
|
||||||
|
public var input: TestWorkspaceByokConfigInput
|
||||||
|
|
||||||
|
public init(input: TestWorkspaceByokConfigInput) {
|
||||||
|
self.input = input
|
||||||
|
}
|
||||||
|
|
||||||
|
public var __variables: Variables? { ["input": input] }
|
||||||
|
|
||||||
|
public struct Data: AffineGraphQL.SelectionSet {
|
||||||
|
public let __data: DataDict
|
||||||
|
public init(_dataDict: DataDict) { __data = _dataDict }
|
||||||
|
|
||||||
|
public static var __parentType: any ApolloAPI.ParentType { AffineGraphQL.Objects.Mutation }
|
||||||
|
public static var __selections: [ApolloAPI.Selection] { [
|
||||||
|
.field("testWorkspaceByokConfig", TestWorkspaceByokConfig.self, arguments: ["input": .variable("input")]),
|
||||||
|
] }
|
||||||
|
public static var __fulfilledFragments: [any ApolloAPI.SelectionSet.Type] { [
|
||||||
|
TestWorkspaceByokConfigMutation.Data.self
|
||||||
|
] }
|
||||||
|
|
||||||
|
public var testWorkspaceByokConfig: TestWorkspaceByokConfig { __data["testWorkspaceByokConfig"] }
|
||||||
|
|
||||||
|
/// TestWorkspaceByokConfig
|
||||||
|
///
|
||||||
|
/// Parent Type: `TestWorkspaceByokConfigResultType`
|
||||||
|
public struct TestWorkspaceByokConfig: AffineGraphQL.SelectionSet {
|
||||||
|
public let __data: DataDict
|
||||||
|
public init(_dataDict: DataDict) { __data = _dataDict }
|
||||||
|
|
||||||
|
public static var __parentType: any ApolloAPI.ParentType { AffineGraphQL.Objects.TestWorkspaceByokConfigResultType }
|
||||||
|
public static var __selections: [ApolloAPI.Selection] { [
|
||||||
|
.field("__typename", String.self),
|
||||||
|
.field("ok", Bool.self),
|
||||||
|
.field("status", GraphQLEnum<AffineGraphQL.ByokKeyTestStatus>.self),
|
||||||
|
.field("message", String?.self),
|
||||||
|
] }
|
||||||
|
public static var __fulfilledFragments: [any ApolloAPI.SelectionSet.Type] { [
|
||||||
|
TestWorkspaceByokConfigMutation.Data.TestWorkspaceByokConfig.self
|
||||||
|
] }
|
||||||
|
|
||||||
|
public var ok: Bool { __data["ok"] }
|
||||||
|
public var status: GraphQLEnum<AffineGraphQL.ByokKeyTestStatus> { __data["status"] }
|
||||||
|
public var message: String? { __data["message"] }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+54
@@ -0,0 +1,54 @@
|
|||||||
|
// @generated
|
||||||
|
// This file was automatically generated and should not be edited.
|
||||||
|
|
||||||
|
@_exported import ApolloAPI
|
||||||
|
|
||||||
|
public class UpsertWorkspaceByokConfigMutation: GraphQLMutation {
|
||||||
|
public static let operationName: String = "upsertWorkspaceByokConfig"
|
||||||
|
public static let operationDocument: ApolloAPI.OperationDocument = .init(
|
||||||
|
definition: .init(
|
||||||
|
#"mutation upsertWorkspaceByokConfig($input: UpsertWorkspaceByokConfigInput!) { upsertWorkspaceByokConfig(input: $input) { __typename id } }"#
|
||||||
|
))
|
||||||
|
|
||||||
|
public var input: UpsertWorkspaceByokConfigInput
|
||||||
|
|
||||||
|
public init(input: UpsertWorkspaceByokConfigInput) {
|
||||||
|
self.input = input
|
||||||
|
}
|
||||||
|
|
||||||
|
public var __variables: Variables? { ["input": input] }
|
||||||
|
|
||||||
|
public struct Data: AffineGraphQL.SelectionSet {
|
||||||
|
public let __data: DataDict
|
||||||
|
public init(_dataDict: DataDict) { __data = _dataDict }
|
||||||
|
|
||||||
|
public static var __parentType: any ApolloAPI.ParentType { AffineGraphQL.Objects.Mutation }
|
||||||
|
public static var __selections: [ApolloAPI.Selection] { [
|
||||||
|
.field("upsertWorkspaceByokConfig", UpsertWorkspaceByokConfig.self, arguments: ["input": .variable("input")]),
|
||||||
|
] }
|
||||||
|
public static var __fulfilledFragments: [any ApolloAPI.SelectionSet.Type] { [
|
||||||
|
UpsertWorkspaceByokConfigMutation.Data.self
|
||||||
|
] }
|
||||||
|
|
||||||
|
public var upsertWorkspaceByokConfig: UpsertWorkspaceByokConfig { __data["upsertWorkspaceByokConfig"] }
|
||||||
|
|
||||||
|
/// UpsertWorkspaceByokConfig
|
||||||
|
///
|
||||||
|
/// Parent Type: `WorkspaceByokKeyConfigType`
|
||||||
|
public struct UpsertWorkspaceByokConfig: AffineGraphQL.SelectionSet {
|
||||||
|
public let __data: DataDict
|
||||||
|
public init(_dataDict: DataDict) { __data = _dataDict }
|
||||||
|
|
||||||
|
public static var __parentType: any ApolloAPI.ParentType { AffineGraphQL.Objects.WorkspaceByokKeyConfigType }
|
||||||
|
public static var __selections: [ApolloAPI.Selection] { [
|
||||||
|
.field("__typename", String.self),
|
||||||
|
.field("id", AffineGraphQL.ID.self),
|
||||||
|
] }
|
||||||
|
public static var __fulfilledFragments: [any ApolloAPI.SelectionSet.Type] { [
|
||||||
|
UpsertWorkspaceByokConfigMutation.Data.UpsertWorkspaceByokConfig.self
|
||||||
|
] }
|
||||||
|
|
||||||
|
public var id: AffineGraphQL.ID { __data["id"] }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+316
@@ -0,0 +1,316 @@
|
|||||||
|
// @generated
|
||||||
|
// This file was automatically generated and should not be edited.
|
||||||
|
|
||||||
|
@_exported import ApolloAPI
|
||||||
|
|
||||||
|
public class GetTranscriptTaskQuery: GraphQLQuery {
|
||||||
|
public static let operationName: String = "getTranscriptTask"
|
||||||
|
public static let operationDocument: ApolloAPI.OperationDocument = .init(
|
||||||
|
definition: .init(
|
||||||
|
#"query getTranscriptTask($workspaceId: String!, $taskId: String, $blobId: String) { currentUser { __typename copilot(workspaceId: $workspaceId) { __typename transcriptTask(taskId: $taskId, blobId: $blobId) { __typename id status title summary sourceAudio { __typename blobId mimeType durationMs sampleRate channels } quality { __typename degraded overflowCount } sliceManifest { __typename index fileName mimeType startSec durationSec byteSize } normalizedSegments { __typename speaker startSec endSec start end text } normalizedTranscript summaryJson { __typename title durationMinutes attendees keyPoints actionItems { __typename description owner deadline } decisions openQuestions blockers } transcription { __typename speaker start end transcription } } } } }"#
|
||||||
|
))
|
||||||
|
|
||||||
|
public var workspaceId: String
|
||||||
|
public var taskId: GraphQLNullable<String>
|
||||||
|
public var blobId: GraphQLNullable<String>
|
||||||
|
|
||||||
|
public init(
|
||||||
|
workspaceId: String,
|
||||||
|
taskId: GraphQLNullable<String>,
|
||||||
|
blobId: GraphQLNullable<String>
|
||||||
|
) {
|
||||||
|
self.workspaceId = workspaceId
|
||||||
|
self.taskId = taskId
|
||||||
|
self.blobId = blobId
|
||||||
|
}
|
||||||
|
|
||||||
|
public var __variables: Variables? { [
|
||||||
|
"workspaceId": workspaceId,
|
||||||
|
"taskId": taskId,
|
||||||
|
"blobId": blobId
|
||||||
|
] }
|
||||||
|
|
||||||
|
public struct Data: AffineGraphQL.SelectionSet {
|
||||||
|
public let __data: DataDict
|
||||||
|
public init(_dataDict: DataDict) { __data = _dataDict }
|
||||||
|
|
||||||
|
public static var __parentType: any ApolloAPI.ParentType { AffineGraphQL.Objects.Query }
|
||||||
|
public static var __selections: [ApolloAPI.Selection] { [
|
||||||
|
.field("currentUser", CurrentUser?.self),
|
||||||
|
] }
|
||||||
|
public static var __fulfilledFragments: [any ApolloAPI.SelectionSet.Type] { [
|
||||||
|
GetTranscriptTaskQuery.Data.self
|
||||||
|
] }
|
||||||
|
|
||||||
|
/// Get current user
|
||||||
|
public var currentUser: CurrentUser? { __data["currentUser"] }
|
||||||
|
|
||||||
|
/// CurrentUser
|
||||||
|
///
|
||||||
|
/// Parent Type: `UserType`
|
||||||
|
public struct CurrentUser: AffineGraphQL.SelectionSet {
|
||||||
|
public let __data: DataDict
|
||||||
|
public init(_dataDict: DataDict) { __data = _dataDict }
|
||||||
|
|
||||||
|
public static var __parentType: any ApolloAPI.ParentType { AffineGraphQL.Objects.UserType }
|
||||||
|
public static var __selections: [ApolloAPI.Selection] { [
|
||||||
|
.field("__typename", String.self),
|
||||||
|
.field("copilot", Copilot.self, arguments: ["workspaceId": .variable("workspaceId")]),
|
||||||
|
] }
|
||||||
|
public static var __fulfilledFragments: [any ApolloAPI.SelectionSet.Type] { [
|
||||||
|
GetTranscriptTaskQuery.Data.CurrentUser.self
|
||||||
|
] }
|
||||||
|
|
||||||
|
public var copilot: Copilot { __data["copilot"] }
|
||||||
|
|
||||||
|
/// CurrentUser.Copilot
|
||||||
|
///
|
||||||
|
/// Parent Type: `Copilot`
|
||||||
|
public struct Copilot: AffineGraphQL.SelectionSet {
|
||||||
|
public let __data: DataDict
|
||||||
|
public init(_dataDict: DataDict) { __data = _dataDict }
|
||||||
|
|
||||||
|
public static var __parentType: any ApolloAPI.ParentType { AffineGraphQL.Objects.Copilot }
|
||||||
|
public static var __selections: [ApolloAPI.Selection] { [
|
||||||
|
.field("__typename", String.self),
|
||||||
|
.field("transcriptTask", TranscriptTask?.self, arguments: [
|
||||||
|
"taskId": .variable("taskId"),
|
||||||
|
"blobId": .variable("blobId")
|
||||||
|
]),
|
||||||
|
] }
|
||||||
|
public static var __fulfilledFragments: [any ApolloAPI.SelectionSet.Type] { [
|
||||||
|
GetTranscriptTaskQuery.Data.CurrentUser.Copilot.self
|
||||||
|
] }
|
||||||
|
|
||||||
|
@available(*, deprecated, message: "Use realtime subscription \"copilot.transcript.task.changed\" instead.")
|
||||||
|
public var transcriptTask: TranscriptTask? { __data["transcriptTask"] }
|
||||||
|
|
||||||
|
/// CurrentUser.Copilot.TranscriptTask
|
||||||
|
///
|
||||||
|
/// Parent Type: `TranscriptionResultType`
|
||||||
|
public struct TranscriptTask: AffineGraphQL.SelectionSet {
|
||||||
|
public let __data: DataDict
|
||||||
|
public init(_dataDict: DataDict) { __data = _dataDict }
|
||||||
|
|
||||||
|
public static var __parentType: any ApolloAPI.ParentType { AffineGraphQL.Objects.TranscriptionResultType }
|
||||||
|
public static var __selections: [ApolloAPI.Selection] { [
|
||||||
|
.field("__typename", String.self),
|
||||||
|
.field("id", AffineGraphQL.ID.self),
|
||||||
|
.field("status", GraphQLEnum<AffineGraphQL.AiJobStatus>.self),
|
||||||
|
.field("title", String?.self),
|
||||||
|
.field("summary", String?.self),
|
||||||
|
.field("sourceAudio", SourceAudio?.self),
|
||||||
|
.field("quality", Quality?.self),
|
||||||
|
.field("sliceManifest", [SliceManifest]?.self),
|
||||||
|
.field("normalizedSegments", [NormalizedSegment]?.self),
|
||||||
|
.field("normalizedTranscript", String?.self),
|
||||||
|
.field("summaryJson", SummaryJson?.self),
|
||||||
|
.field("transcription", [Transcription]?.self),
|
||||||
|
] }
|
||||||
|
public static var __fulfilledFragments: [any ApolloAPI.SelectionSet.Type] { [
|
||||||
|
GetTranscriptTaskQuery.Data.CurrentUser.Copilot.TranscriptTask.self
|
||||||
|
] }
|
||||||
|
|
||||||
|
public var id: AffineGraphQL.ID { __data["id"] }
|
||||||
|
public var status: GraphQLEnum<AffineGraphQL.AiJobStatus> { __data["status"] }
|
||||||
|
public var title: String? { __data["title"] }
|
||||||
|
public var summary: String? { __data["summary"] }
|
||||||
|
public var sourceAudio: SourceAudio? { __data["sourceAudio"] }
|
||||||
|
public var quality: Quality? { __data["quality"] }
|
||||||
|
public var sliceManifest: [SliceManifest]? { __data["sliceManifest"] }
|
||||||
|
public var normalizedSegments: [NormalizedSegment]? { __data["normalizedSegments"] }
|
||||||
|
public var normalizedTranscript: String? { __data["normalizedTranscript"] }
|
||||||
|
public var summaryJson: SummaryJson? { __data["summaryJson"] }
|
||||||
|
public var transcription: [Transcription]? { __data["transcription"] }
|
||||||
|
|
||||||
|
/// CurrentUser.Copilot.TranscriptTask.SourceAudio
|
||||||
|
///
|
||||||
|
/// Parent Type: `TranscriptionSourceAudioType`
|
||||||
|
public struct SourceAudio: AffineGraphQL.SelectionSet {
|
||||||
|
public let __data: DataDict
|
||||||
|
public init(_dataDict: DataDict) { __data = _dataDict }
|
||||||
|
|
||||||
|
public static var __parentType: any ApolloAPI.ParentType { AffineGraphQL.Objects.TranscriptionSourceAudioType }
|
||||||
|
public static var __selections: [ApolloAPI.Selection] { [
|
||||||
|
.field("__typename", String.self),
|
||||||
|
.field("blobId", String?.self),
|
||||||
|
.field("mimeType", String?.self),
|
||||||
|
.field("durationMs", Int?.self),
|
||||||
|
.field("sampleRate", Int?.self),
|
||||||
|
.field("channels", Int?.self),
|
||||||
|
] }
|
||||||
|
public static var __fulfilledFragments: [any ApolloAPI.SelectionSet.Type] { [
|
||||||
|
GetTranscriptTaskQuery.Data.CurrentUser.Copilot.TranscriptTask.SourceAudio.self
|
||||||
|
] }
|
||||||
|
|
||||||
|
public var blobId: String? { __data["blobId"] }
|
||||||
|
public var mimeType: String? { __data["mimeType"] }
|
||||||
|
public var durationMs: Int? { __data["durationMs"] }
|
||||||
|
public var sampleRate: Int? { __data["sampleRate"] }
|
||||||
|
public var channels: Int? { __data["channels"] }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// CurrentUser.Copilot.TranscriptTask.Quality
|
||||||
|
///
|
||||||
|
/// Parent Type: `TranscriptionQualityType`
|
||||||
|
public struct Quality: AffineGraphQL.SelectionSet {
|
||||||
|
public let __data: DataDict
|
||||||
|
public init(_dataDict: DataDict) { __data = _dataDict }
|
||||||
|
|
||||||
|
public static var __parentType: any ApolloAPI.ParentType { AffineGraphQL.Objects.TranscriptionQualityType }
|
||||||
|
public static var __selections: [ApolloAPI.Selection] { [
|
||||||
|
.field("__typename", String.self),
|
||||||
|
.field("degraded", Bool?.self),
|
||||||
|
.field("overflowCount", Int?.self),
|
||||||
|
] }
|
||||||
|
public static var __fulfilledFragments: [any ApolloAPI.SelectionSet.Type] { [
|
||||||
|
GetTranscriptTaskQuery.Data.CurrentUser.Copilot.TranscriptTask.Quality.self
|
||||||
|
] }
|
||||||
|
|
||||||
|
public var degraded: Bool? { __data["degraded"] }
|
||||||
|
public var overflowCount: Int? { __data["overflowCount"] }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// CurrentUser.Copilot.TranscriptTask.SliceManifest
|
||||||
|
///
|
||||||
|
/// Parent Type: `AudioSliceManifestItemType`
|
||||||
|
public struct SliceManifest: AffineGraphQL.SelectionSet {
|
||||||
|
public let __data: DataDict
|
||||||
|
public init(_dataDict: DataDict) { __data = _dataDict }
|
||||||
|
|
||||||
|
public static var __parentType: any ApolloAPI.ParentType { AffineGraphQL.Objects.AudioSliceManifestItemType }
|
||||||
|
public static var __selections: [ApolloAPI.Selection] { [
|
||||||
|
.field("__typename", String.self),
|
||||||
|
.field("index", Int.self),
|
||||||
|
.field("fileName", String.self),
|
||||||
|
.field("mimeType", String.self),
|
||||||
|
.field("startSec", Double.self),
|
||||||
|
.field("durationSec", Double.self),
|
||||||
|
.field("byteSize", Int?.self),
|
||||||
|
] }
|
||||||
|
public static var __fulfilledFragments: [any ApolloAPI.SelectionSet.Type] { [
|
||||||
|
GetTranscriptTaskQuery.Data.CurrentUser.Copilot.TranscriptTask.SliceManifest.self
|
||||||
|
] }
|
||||||
|
|
||||||
|
public var index: Int { __data["index"] }
|
||||||
|
public var fileName: String { __data["fileName"] }
|
||||||
|
public var mimeType: String { __data["mimeType"] }
|
||||||
|
public var startSec: Double { __data["startSec"] }
|
||||||
|
public var durationSec: Double { __data["durationSec"] }
|
||||||
|
public var byteSize: Int? { __data["byteSize"] }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// CurrentUser.Copilot.TranscriptTask.NormalizedSegment
|
||||||
|
///
|
||||||
|
/// Parent Type: `NormalizedTranscriptSegmentType`
|
||||||
|
public struct NormalizedSegment: AffineGraphQL.SelectionSet {
|
||||||
|
public let __data: DataDict
|
||||||
|
public init(_dataDict: DataDict) { __data = _dataDict }
|
||||||
|
|
||||||
|
public static var __parentType: any ApolloAPI.ParentType { AffineGraphQL.Objects.NormalizedTranscriptSegmentType }
|
||||||
|
public static var __selections: [ApolloAPI.Selection] { [
|
||||||
|
.field("__typename", String.self),
|
||||||
|
.field("speaker", String.self),
|
||||||
|
.field("startSec", Double.self),
|
||||||
|
.field("endSec", Double.self),
|
||||||
|
.field("start", String.self),
|
||||||
|
.field("end", String.self),
|
||||||
|
.field("text", String.self),
|
||||||
|
] }
|
||||||
|
public static var __fulfilledFragments: [any ApolloAPI.SelectionSet.Type] { [
|
||||||
|
GetTranscriptTaskQuery.Data.CurrentUser.Copilot.TranscriptTask.NormalizedSegment.self
|
||||||
|
] }
|
||||||
|
|
||||||
|
public var speaker: String { __data["speaker"] }
|
||||||
|
public var startSec: Double { __data["startSec"] }
|
||||||
|
public var endSec: Double { __data["endSec"] }
|
||||||
|
public var start: String { __data["start"] }
|
||||||
|
public var end: String { __data["end"] }
|
||||||
|
public var text: String { __data["text"] }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// CurrentUser.Copilot.TranscriptTask.SummaryJson
|
||||||
|
///
|
||||||
|
/// Parent Type: `MeetingSummaryV2Type`
|
||||||
|
public struct SummaryJson: AffineGraphQL.SelectionSet {
|
||||||
|
public let __data: DataDict
|
||||||
|
public init(_dataDict: DataDict) { __data = _dataDict }
|
||||||
|
|
||||||
|
public static var __parentType: any ApolloAPI.ParentType { AffineGraphQL.Objects.MeetingSummaryV2Type }
|
||||||
|
public static var __selections: [ApolloAPI.Selection] { [
|
||||||
|
.field("__typename", String.self),
|
||||||
|
.field("title", String.self),
|
||||||
|
.field("durationMinutes", Double.self),
|
||||||
|
.field("attendees", [String].self),
|
||||||
|
.field("keyPoints", [String].self),
|
||||||
|
.field("actionItems", [ActionItem].self),
|
||||||
|
.field("decisions", [String].self),
|
||||||
|
.field("openQuestions", [String].self),
|
||||||
|
.field("blockers", [String].self),
|
||||||
|
] }
|
||||||
|
public static var __fulfilledFragments: [any ApolloAPI.SelectionSet.Type] { [
|
||||||
|
GetTranscriptTaskQuery.Data.CurrentUser.Copilot.TranscriptTask.SummaryJson.self
|
||||||
|
] }
|
||||||
|
|
||||||
|
public var title: String { __data["title"] }
|
||||||
|
public var durationMinutes: Double { __data["durationMinutes"] }
|
||||||
|
public var attendees: [String] { __data["attendees"] }
|
||||||
|
public var keyPoints: [String] { __data["keyPoints"] }
|
||||||
|
public var actionItems: [ActionItem] { __data["actionItems"] }
|
||||||
|
public var decisions: [String] { __data["decisions"] }
|
||||||
|
public var openQuestions: [String] { __data["openQuestions"] }
|
||||||
|
public var blockers: [String] { __data["blockers"] }
|
||||||
|
|
||||||
|
/// CurrentUser.Copilot.TranscriptTask.SummaryJson.ActionItem
|
||||||
|
///
|
||||||
|
/// Parent Type: `MeetingActionItemType`
|
||||||
|
public struct ActionItem: AffineGraphQL.SelectionSet {
|
||||||
|
public let __data: DataDict
|
||||||
|
public init(_dataDict: DataDict) { __data = _dataDict }
|
||||||
|
|
||||||
|
public static var __parentType: any ApolloAPI.ParentType { AffineGraphQL.Objects.MeetingActionItemType }
|
||||||
|
public static var __selections: [ApolloAPI.Selection] { [
|
||||||
|
.field("__typename", String.self),
|
||||||
|
.field("description", String.self),
|
||||||
|
.field("owner", String?.self),
|
||||||
|
.field("deadline", String?.self),
|
||||||
|
] }
|
||||||
|
public static var __fulfilledFragments: [any ApolloAPI.SelectionSet.Type] { [
|
||||||
|
GetTranscriptTaskQuery.Data.CurrentUser.Copilot.TranscriptTask.SummaryJson.ActionItem.self
|
||||||
|
] }
|
||||||
|
|
||||||
|
public var description: String { __data["description"] }
|
||||||
|
public var owner: String? { __data["owner"] }
|
||||||
|
public var deadline: String? { __data["deadline"] }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// CurrentUser.Copilot.TranscriptTask.Transcription
|
||||||
|
///
|
||||||
|
/// Parent Type: `TranscriptionItemType`
|
||||||
|
public struct Transcription: AffineGraphQL.SelectionSet {
|
||||||
|
public let __data: DataDict
|
||||||
|
public init(_dataDict: DataDict) { __data = _dataDict }
|
||||||
|
|
||||||
|
public static var __parentType: any ApolloAPI.ParentType { AffineGraphQL.Objects.TranscriptionItemType }
|
||||||
|
public static var __selections: [ApolloAPI.Selection] { [
|
||||||
|
.field("__typename", String.self),
|
||||||
|
.field("speaker", String.self),
|
||||||
|
.field("start", String.self),
|
||||||
|
.field("end", String.self),
|
||||||
|
.field("transcription", String.self),
|
||||||
|
] }
|
||||||
|
public static var __fulfilledFragments: [any ApolloAPI.SelectionSet.Type] { [
|
||||||
|
GetTranscriptTaskQuery.Data.CurrentUser.Copilot.TranscriptTask.Transcription.self
|
||||||
|
] }
|
||||||
|
|
||||||
|
public var speaker: String { __data["speaker"] }
|
||||||
|
public var start: String { __data["start"] }
|
||||||
|
public var end: String { __data["end"] }
|
||||||
|
public var transcription: String { __data["transcription"] }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+1
-4
@@ -7,7 +7,7 @@ public class InvoicesQuery: GraphQLQuery {
|
|||||||
public static let operationName: String = "invoices"
|
public static let operationName: String = "invoices"
|
||||||
public static let operationDocument: ApolloAPI.OperationDocument = .init(
|
public static let operationDocument: ApolloAPI.OperationDocument = .init(
|
||||||
definition: .init(
|
definition: .init(
|
||||||
#"query invoices($take: Int!, $skip: Int!) { currentUser { __typename invoiceCount invoices(take: $take, skip: $skip) { __typename id status currency amount reason lastPaymentError link createdAt } } }"#
|
#"query invoices($take: Int!, $skip: Int!) { currentUser { __typename invoiceCount invoices(take: $take, skip: $skip) { __typename status currency amount reason lastPaymentError link createdAt } } }"#
|
||||||
))
|
))
|
||||||
|
|
||||||
public var take: Int
|
public var take: Int
|
||||||
@@ -75,7 +75,6 @@ public class InvoicesQuery: GraphQLQuery {
|
|||||||
public static var __parentType: any ApolloAPI.ParentType { AffineGraphQL.Objects.InvoiceType }
|
public static var __parentType: any ApolloAPI.ParentType { AffineGraphQL.Objects.InvoiceType }
|
||||||
public static var __selections: [ApolloAPI.Selection] { [
|
public static var __selections: [ApolloAPI.Selection] { [
|
||||||
.field("__typename", String.self),
|
.field("__typename", String.self),
|
||||||
.field("id", String?.self),
|
|
||||||
.field("status", GraphQLEnum<AffineGraphQL.InvoiceStatus>.self),
|
.field("status", GraphQLEnum<AffineGraphQL.InvoiceStatus>.self),
|
||||||
.field("currency", String.self),
|
.field("currency", String.self),
|
||||||
.field("amount", Int.self),
|
.field("amount", Int.self),
|
||||||
@@ -88,8 +87,6 @@ public class InvoicesQuery: GraphQLQuery {
|
|||||||
InvoicesQuery.Data.CurrentUser.Invoice.self
|
InvoicesQuery.Data.CurrentUser.Invoice.self
|
||||||
] }
|
] }
|
||||||
|
|
||||||
@available(*, deprecated, message: "removed")
|
|
||||||
public var id: String? { __data["id"] }
|
|
||||||
public var status: GraphQLEnum<AffineGraphQL.InvoiceStatus> { __data["status"] }
|
public var status: GraphQLEnum<AffineGraphQL.InvoiceStatus> { __data["status"] }
|
||||||
public var currency: String { __data["currency"] }
|
public var currency: String { __data["currency"] }
|
||||||
public var amount: Int { __data["amount"] }
|
public var amount: Int { __data["amount"] }
|
||||||
|
|||||||
+212
@@ -0,0 +1,212 @@
|
|||||||
|
// @generated
|
||||||
|
// This file was automatically generated and should not be edited.
|
||||||
|
|
||||||
|
@_exported import ApolloAPI
|
||||||
|
|
||||||
|
public class WorkspaceByokSettingsQuery: GraphQLQuery {
|
||||||
|
public static let operationName: String = "workspaceByokSettings"
|
||||||
|
public static let operationDocument: ApolloAPI.OperationDocument = .init(
|
||||||
|
definition: .init(
|
||||||
|
#"query workspaceByokSettings($id: String!, $from: DateTime!, $to: DateTime!) { workspace(id: $id) { __typename id byokSettings { __typename workspaceId entitled serverEntitled localEntitled entitlementRequired allowedProviders localStorageSupported customEndpointSupported hasAiPlan keys { __typename id provider name description storage configured enabled endpoint endpointEditable sortOrder capabilities testStatus disabledReason lastTestedAt lastTestError lastUsedAt lastErrorAt lastError } warnings { __typename featureKind reason requiredProviders } } byokUsage(from: $from, to: $to) { __typename date featureKind totalTokens } } }"#
|
||||||
|
))
|
||||||
|
|
||||||
|
public var id: String
|
||||||
|
public var from: DateTime
|
||||||
|
public var to: DateTime
|
||||||
|
|
||||||
|
public init(
|
||||||
|
id: String,
|
||||||
|
from: DateTime,
|
||||||
|
to: DateTime
|
||||||
|
) {
|
||||||
|
self.id = id
|
||||||
|
self.from = from
|
||||||
|
self.to = to
|
||||||
|
}
|
||||||
|
|
||||||
|
public var __variables: Variables? { [
|
||||||
|
"id": id,
|
||||||
|
"from": from,
|
||||||
|
"to": to
|
||||||
|
] }
|
||||||
|
|
||||||
|
public struct Data: AffineGraphQL.SelectionSet {
|
||||||
|
public let __data: DataDict
|
||||||
|
public init(_dataDict: DataDict) { __data = _dataDict }
|
||||||
|
|
||||||
|
public static var __parentType: any ApolloAPI.ParentType { AffineGraphQL.Objects.Query }
|
||||||
|
public static var __selections: [ApolloAPI.Selection] { [
|
||||||
|
.field("workspace", Workspace.self, arguments: ["id": .variable("id")]),
|
||||||
|
] }
|
||||||
|
public static var __fulfilledFragments: [any ApolloAPI.SelectionSet.Type] { [
|
||||||
|
WorkspaceByokSettingsQuery.Data.self
|
||||||
|
] }
|
||||||
|
|
||||||
|
/// Get workspace by id
|
||||||
|
public var workspace: Workspace { __data["workspace"] }
|
||||||
|
|
||||||
|
/// Workspace
|
||||||
|
///
|
||||||
|
/// Parent Type: `WorkspaceType`
|
||||||
|
public struct Workspace: AffineGraphQL.SelectionSet {
|
||||||
|
public let __data: DataDict
|
||||||
|
public init(_dataDict: DataDict) { __data = _dataDict }
|
||||||
|
|
||||||
|
public static var __parentType: any ApolloAPI.ParentType { AffineGraphQL.Objects.WorkspaceType }
|
||||||
|
public static var __selections: [ApolloAPI.Selection] { [
|
||||||
|
.field("__typename", String.self),
|
||||||
|
.field("id", AffineGraphQL.ID.self),
|
||||||
|
.field("byokSettings", ByokSettings.self),
|
||||||
|
.field("byokUsage", [ByokUsage].self, arguments: [
|
||||||
|
"from": .variable("from"),
|
||||||
|
"to": .variable("to")
|
||||||
|
]),
|
||||||
|
] }
|
||||||
|
public static var __fulfilledFragments: [any ApolloAPI.SelectionSet.Type] { [
|
||||||
|
WorkspaceByokSettingsQuery.Data.Workspace.self
|
||||||
|
] }
|
||||||
|
|
||||||
|
public var id: AffineGraphQL.ID { __data["id"] }
|
||||||
|
public var byokSettings: ByokSettings { __data["byokSettings"] }
|
||||||
|
public var byokUsage: [ByokUsage] { __data["byokUsage"] }
|
||||||
|
|
||||||
|
/// Workspace.ByokSettings
|
||||||
|
///
|
||||||
|
/// Parent Type: `WorkspaceByokSettingsType`
|
||||||
|
public struct ByokSettings: AffineGraphQL.SelectionSet {
|
||||||
|
public let __data: DataDict
|
||||||
|
public init(_dataDict: DataDict) { __data = _dataDict }
|
||||||
|
|
||||||
|
public static var __parentType: any ApolloAPI.ParentType { AffineGraphQL.Objects.WorkspaceByokSettingsType }
|
||||||
|
public static var __selections: [ApolloAPI.Selection] { [
|
||||||
|
.field("__typename", String.self),
|
||||||
|
.field("workspaceId", String.self),
|
||||||
|
.field("entitled", Bool.self),
|
||||||
|
.field("serverEntitled", Bool.self),
|
||||||
|
.field("localEntitled", Bool.self),
|
||||||
|
.field("entitlementRequired", [String].self),
|
||||||
|
.field("allowedProviders", [GraphQLEnum<AffineGraphQL.ByokProvider>].self),
|
||||||
|
.field("localStorageSupported", Bool.self),
|
||||||
|
.field("customEndpointSupported", Bool.self),
|
||||||
|
.field("hasAiPlan", Bool.self),
|
||||||
|
.field("keys", [Key].self),
|
||||||
|
.field("warnings", [Warning].self),
|
||||||
|
] }
|
||||||
|
public static var __fulfilledFragments: [any ApolloAPI.SelectionSet.Type] { [
|
||||||
|
WorkspaceByokSettingsQuery.Data.Workspace.ByokSettings.self
|
||||||
|
] }
|
||||||
|
|
||||||
|
public var workspaceId: String { __data["workspaceId"] }
|
||||||
|
public var entitled: Bool { __data["entitled"] }
|
||||||
|
public var serverEntitled: Bool { __data["serverEntitled"] }
|
||||||
|
public var localEntitled: Bool { __data["localEntitled"] }
|
||||||
|
public var entitlementRequired: [String] { __data["entitlementRequired"] }
|
||||||
|
public var allowedProviders: [GraphQLEnum<AffineGraphQL.ByokProvider>] { __data["allowedProviders"] }
|
||||||
|
public var localStorageSupported: Bool { __data["localStorageSupported"] }
|
||||||
|
public var customEndpointSupported: Bool { __data["customEndpointSupported"] }
|
||||||
|
public var hasAiPlan: Bool { __data["hasAiPlan"] }
|
||||||
|
public var keys: [Key] { __data["keys"] }
|
||||||
|
public var warnings: [Warning] { __data["warnings"] }
|
||||||
|
|
||||||
|
/// Workspace.ByokSettings.Key
|
||||||
|
///
|
||||||
|
/// Parent Type: `WorkspaceByokKeyConfigType`
|
||||||
|
public struct Key: AffineGraphQL.SelectionSet {
|
||||||
|
public let __data: DataDict
|
||||||
|
public init(_dataDict: DataDict) { __data = _dataDict }
|
||||||
|
|
||||||
|
public static var __parentType: any ApolloAPI.ParentType { AffineGraphQL.Objects.WorkspaceByokKeyConfigType }
|
||||||
|
public static var __selections: [ApolloAPI.Selection] { [
|
||||||
|
.field("__typename", String.self),
|
||||||
|
.field("id", AffineGraphQL.ID.self),
|
||||||
|
.field("provider", GraphQLEnum<AffineGraphQL.ByokProvider>.self),
|
||||||
|
.field("name", String.self),
|
||||||
|
.field("description", String?.self),
|
||||||
|
.field("storage", GraphQLEnum<AffineGraphQL.ByokKeyStorage>.self),
|
||||||
|
.field("configured", Bool.self),
|
||||||
|
.field("enabled", Bool.self),
|
||||||
|
.field("endpoint", String?.self),
|
||||||
|
.field("endpointEditable", Bool.self),
|
||||||
|
.field("sortOrder", AffineGraphQL.SafeInt.self),
|
||||||
|
.field("capabilities", [String].self),
|
||||||
|
.field("testStatus", GraphQLEnum<AffineGraphQL.ByokKeyTestStatus>.self),
|
||||||
|
.field("disabledReason", String?.self),
|
||||||
|
.field("lastTestedAt", AffineGraphQL.DateTime?.self),
|
||||||
|
.field("lastTestError", String?.self),
|
||||||
|
.field("lastUsedAt", AffineGraphQL.DateTime?.self),
|
||||||
|
.field("lastErrorAt", AffineGraphQL.DateTime?.self),
|
||||||
|
.field("lastError", String?.self),
|
||||||
|
] }
|
||||||
|
public static var __fulfilledFragments: [any ApolloAPI.SelectionSet.Type] { [
|
||||||
|
WorkspaceByokSettingsQuery.Data.Workspace.ByokSettings.Key.self
|
||||||
|
] }
|
||||||
|
|
||||||
|
public var id: AffineGraphQL.ID { __data["id"] }
|
||||||
|
public var provider: GraphQLEnum<AffineGraphQL.ByokProvider> { __data["provider"] }
|
||||||
|
public var name: String { __data["name"] }
|
||||||
|
public var description: String? { __data["description"] }
|
||||||
|
public var storage: GraphQLEnum<AffineGraphQL.ByokKeyStorage> { __data["storage"] }
|
||||||
|
public var configured: Bool { __data["configured"] }
|
||||||
|
public var enabled: Bool { __data["enabled"] }
|
||||||
|
public var endpoint: String? { __data["endpoint"] }
|
||||||
|
public var endpointEditable: Bool { __data["endpointEditable"] }
|
||||||
|
public var sortOrder: AffineGraphQL.SafeInt { __data["sortOrder"] }
|
||||||
|
public var capabilities: [String] { __data["capabilities"] }
|
||||||
|
public var testStatus: GraphQLEnum<AffineGraphQL.ByokKeyTestStatus> { __data["testStatus"] }
|
||||||
|
public var disabledReason: String? { __data["disabledReason"] }
|
||||||
|
public var lastTestedAt: AffineGraphQL.DateTime? { __data["lastTestedAt"] }
|
||||||
|
public var lastTestError: String? { __data["lastTestError"] }
|
||||||
|
public var lastUsedAt: AffineGraphQL.DateTime? { __data["lastUsedAt"] }
|
||||||
|
public var lastErrorAt: AffineGraphQL.DateTime? { __data["lastErrorAt"] }
|
||||||
|
public var lastError: String? { __data["lastError"] }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Workspace.ByokSettings.Warning
|
||||||
|
///
|
||||||
|
/// Parent Type: `WorkspaceByokCapabilityWarningType`
|
||||||
|
public struct Warning: AffineGraphQL.SelectionSet {
|
||||||
|
public let __data: DataDict
|
||||||
|
public init(_dataDict: DataDict) { __data = _dataDict }
|
||||||
|
|
||||||
|
public static var __parentType: any ApolloAPI.ParentType { AffineGraphQL.Objects.WorkspaceByokCapabilityWarningType }
|
||||||
|
public static var __selections: [ApolloAPI.Selection] { [
|
||||||
|
.field("__typename", String.self),
|
||||||
|
.field("featureKind", String.self),
|
||||||
|
.field("reason", String.self),
|
||||||
|
.field("requiredProviders", [GraphQLEnum<AffineGraphQL.ByokProvider>].self),
|
||||||
|
] }
|
||||||
|
public static var __fulfilledFragments: [any ApolloAPI.SelectionSet.Type] { [
|
||||||
|
WorkspaceByokSettingsQuery.Data.Workspace.ByokSettings.Warning.self
|
||||||
|
] }
|
||||||
|
|
||||||
|
public var featureKind: String { __data["featureKind"] }
|
||||||
|
public var reason: String { __data["reason"] }
|
||||||
|
public var requiredProviders: [GraphQLEnum<AffineGraphQL.ByokProvider>] { __data["requiredProviders"] }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Workspace.ByokUsage
|
||||||
|
///
|
||||||
|
/// Parent Type: `WorkspaceByokUsagePointType`
|
||||||
|
public struct ByokUsage: AffineGraphQL.SelectionSet {
|
||||||
|
public let __data: DataDict
|
||||||
|
public init(_dataDict: DataDict) { __data = _dataDict }
|
||||||
|
|
||||||
|
public static var __parentType: any ApolloAPI.ParentType { AffineGraphQL.Objects.WorkspaceByokUsagePointType }
|
||||||
|
public static var __selections: [ApolloAPI.Selection] { [
|
||||||
|
.field("__typename", String.self),
|
||||||
|
.field("date", AffineGraphQL.DateTime.self),
|
||||||
|
.field("featureKind", String.self),
|
||||||
|
.field("totalTokens", AffineGraphQL.SafeInt.self),
|
||||||
|
] }
|
||||||
|
public static var __fulfilledFragments: [any ApolloAPI.SelectionSet.Type] { [
|
||||||
|
WorkspaceByokSettingsQuery.Data.Workspace.ByokUsage.self
|
||||||
|
] }
|
||||||
|
|
||||||
|
public var date: AffineGraphQL.DateTime { __data["date"] }
|
||||||
|
public var featureKind: String { __data["featureKind"] }
|
||||||
|
public var totalTokens: AffineGraphQL.SafeInt { __data["totalTokens"] }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+1
-4
@@ -7,7 +7,7 @@ public class WorkspaceInvoicesQuery: GraphQLQuery {
|
|||||||
public static let operationName: String = "workspaceInvoices"
|
public static let operationName: String = "workspaceInvoices"
|
||||||
public static let operationDocument: ApolloAPI.OperationDocument = .init(
|
public static let operationDocument: ApolloAPI.OperationDocument = .init(
|
||||||
definition: .init(
|
definition: .init(
|
||||||
#"query workspaceInvoices($take: Int!, $skip: Int!, $workspaceId: String!) { workspace(id: $workspaceId) { __typename invoiceCount invoices(take: $take, skip: $skip) { __typename id status currency amount reason lastPaymentError link createdAt } } }"#
|
#"query workspaceInvoices($take: Int!, $skip: Int!, $workspaceId: String!) { workspace(id: $workspaceId) { __typename invoiceCount invoices(take: $take, skip: $skip) { __typename status currency amount reason lastPaymentError link createdAt } } }"#
|
||||||
))
|
))
|
||||||
|
|
||||||
public var take: Int
|
public var take: Int
|
||||||
@@ -79,7 +79,6 @@ public class WorkspaceInvoicesQuery: GraphQLQuery {
|
|||||||
public static var __parentType: any ApolloAPI.ParentType { AffineGraphQL.Objects.InvoiceType }
|
public static var __parentType: any ApolloAPI.ParentType { AffineGraphQL.Objects.InvoiceType }
|
||||||
public static var __selections: [ApolloAPI.Selection] { [
|
public static var __selections: [ApolloAPI.Selection] { [
|
||||||
.field("__typename", String.self),
|
.field("__typename", String.self),
|
||||||
.field("id", String?.self),
|
|
||||||
.field("status", GraphQLEnum<AffineGraphQL.InvoiceStatus>.self),
|
.field("status", GraphQLEnum<AffineGraphQL.InvoiceStatus>.self),
|
||||||
.field("currency", String.self),
|
.field("currency", String.self),
|
||||||
.field("amount", Int.self),
|
.field("amount", Int.self),
|
||||||
@@ -92,8 +91,6 @@ public class WorkspaceInvoicesQuery: GraphQLQuery {
|
|||||||
WorkspaceInvoicesQuery.Data.Workspace.Invoice.self
|
WorkspaceInvoicesQuery.Data.Workspace.Invoice.self
|
||||||
] }
|
] }
|
||||||
|
|
||||||
@available(*, deprecated, message: "removed")
|
|
||||||
public var id: String? { __data["id"] }
|
|
||||||
public var status: GraphQLEnum<AffineGraphQL.InvoiceStatus> { __data["status"] }
|
public var status: GraphQLEnum<AffineGraphQL.InvoiceStatus> { __data["status"] }
|
||||||
public var currency: String { __data["currency"] }
|
public var currency: String { __data["currency"] }
|
||||||
public var amount: Int { __data["amount"] }
|
public var amount: Int { __data["amount"] }
|
||||||
|
|||||||
+9
@@ -0,0 +1,9 @@
|
|||||||
|
// @generated
|
||||||
|
// This file was automatically generated and should not be edited.
|
||||||
|
|
||||||
|
import ApolloAPI
|
||||||
|
|
||||||
|
public enum ByokKeyStorage: String, EnumType {
|
||||||
|
case local = "local"
|
||||||
|
case server = "server"
|
||||||
|
}
|
||||||
+10
@@ -0,0 +1,10 @@
|
|||||||
|
// @generated
|
||||||
|
// This file was automatically generated and should not be edited.
|
||||||
|
|
||||||
|
import ApolloAPI
|
||||||
|
|
||||||
|
public enum ByokKeyTestStatus: String, EnumType {
|
||||||
|
case failed = "failed"
|
||||||
|
case passed = "passed"
|
||||||
|
case untested = "untested"
|
||||||
|
}
|
||||||
+11
@@ -0,0 +1,11 @@
|
|||||||
|
// @generated
|
||||||
|
// This file was automatically generated and should not be edited.
|
||||||
|
|
||||||
|
import ApolloAPI
|
||||||
|
|
||||||
|
public enum ByokProvider: String, EnumType {
|
||||||
|
case anthropic = "anthropic"
|
||||||
|
case fal = "fal"
|
||||||
|
case gemini = "gemini"
|
||||||
|
case openai = "openai"
|
||||||
|
}
|
||||||
-2
@@ -4,9 +4,7 @@
|
|||||||
import ApolloAPI
|
import ApolloAPI
|
||||||
|
|
||||||
public enum FeatureType: String, EnumType {
|
public enum FeatureType: String, EnumType {
|
||||||
case aIEarlyAccess = "AIEarlyAccess"
|
|
||||||
case admin = "Admin"
|
case admin = "Admin"
|
||||||
case earlyAccess = "EarlyAccess"
|
|
||||||
case freePlan = "FreePlan"
|
case freePlan = "FreePlan"
|
||||||
case lifetimeProPlan = "LifetimeProPlan"
|
case lifetimeProPlan = "LifetimeProPlan"
|
||||||
case proPlan = "ProPlan"
|
case proPlan = "ProPlan"
|
||||||
|
|||||||
-1
@@ -4,6 +4,5 @@
|
|||||||
import ApolloAPI
|
import ApolloAPI
|
||||||
|
|
||||||
public enum SubscriptionVariant: String, EnumType {
|
public enum SubscriptionVariant: String, EnumType {
|
||||||
case ea = "EA"
|
|
||||||
case onetime = "Onetime"
|
case onetime = "Onetime"
|
||||||
}
|
}
|
||||||
+18
@@ -10,6 +10,23 @@ public struct CreateChatMessageInput: InputObject {
|
|||||||
__data = data
|
__data = data
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public init(
|
||||||
|
blob: GraphQLNullable<Upload> = nil,
|
||||||
|
blobs: GraphQLNullable<[Upload]> = nil,
|
||||||
|
content: GraphQLNullable<String> = nil,
|
||||||
|
params: GraphQLNullable<JSON> = nil,
|
||||||
|
sessionId: String
|
||||||
|
) {
|
||||||
|
__data = InputDict([
|
||||||
|
"blob": blob,
|
||||||
|
"blobs": blobs,
|
||||||
|
"content": content,
|
||||||
|
"params": params,
|
||||||
|
"sessionId": sessionId
|
||||||
|
])
|
||||||
|
}
|
||||||
|
|
||||||
|
@available(*, deprecated, message: "Argument 'attachments' is deprecated.")
|
||||||
public init(
|
public init(
|
||||||
attachments: GraphQLNullable<[String]> = nil,
|
attachments: GraphQLNullable<[String]> = nil,
|
||||||
blob: GraphQLNullable<Upload> = nil,
|
blob: GraphQLNullable<Upload> = nil,
|
||||||
@@ -28,6 +45,7 @@ public struct CreateChatMessageInput: InputObject {
|
|||||||
])
|
])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@available(*, deprecated, message: "use blobs")
|
||||||
public var attachments: GraphQLNullable<[String]> {
|
public var attachments: GraphQLNullable<[String]> {
|
||||||
get { __data["attachments"] }
|
get { __data["attachments"] }
|
||||||
set { __data["attachments"] = newValue }
|
set { __data["attachments"] = newValue }
|
||||||
|
|||||||
+20
@@ -10,6 +10,25 @@ public struct CreateCheckoutSessionInput: InputObject {
|
|||||||
__data = data
|
__data = data
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public init(
|
||||||
|
args: GraphQLNullable<JSONObject> = nil,
|
||||||
|
coupon: GraphQLNullable<String> = nil,
|
||||||
|
plan: GraphQLNullable<GraphQLEnum<SubscriptionPlan>> = nil,
|
||||||
|
recurring: GraphQLNullable<GraphQLEnum<SubscriptionRecurring>> = nil,
|
||||||
|
successCallbackLink: String,
|
||||||
|
variant: GraphQLNullable<GraphQLEnum<SubscriptionVariant>> = nil
|
||||||
|
) {
|
||||||
|
__data = InputDict([
|
||||||
|
"args": args,
|
||||||
|
"coupon": coupon,
|
||||||
|
"plan": plan,
|
||||||
|
"recurring": recurring,
|
||||||
|
"successCallbackLink": successCallbackLink,
|
||||||
|
"variant": variant
|
||||||
|
])
|
||||||
|
}
|
||||||
|
|
||||||
|
@available(*, deprecated, message: "Argument 'idempotencyKey' is deprecated.")
|
||||||
public init(
|
public init(
|
||||||
args: GraphQLNullable<JSONObject> = nil,
|
args: GraphQLNullable<JSONObject> = nil,
|
||||||
coupon: GraphQLNullable<String> = nil,
|
coupon: GraphQLNullable<String> = nil,
|
||||||
@@ -40,6 +59,7 @@ public struct CreateCheckoutSessionInput: InputObject {
|
|||||||
set { __data["coupon"] = newValue }
|
set { __data["coupon"] = newValue }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@available(*, deprecated, message: "not required anymore")
|
||||||
public var idempotencyKey: GraphQLNullable<String> {
|
public var idempotencyKey: GraphQLNullable<String> {
|
||||||
get { __data["idempotencyKey"] }
|
get { __data["idempotencyKey"] }
|
||||||
set { __data["idempotencyKey"] = newValue }
|
set { __data["idempotencyKey"] = newValue }
|
||||||
|
|||||||
+32
@@ -0,0 +1,32 @@
|
|||||||
|
// @generated
|
||||||
|
// This file was automatically generated and should not be edited.
|
||||||
|
|
||||||
|
import ApolloAPI
|
||||||
|
|
||||||
|
public struct CreateWorkspaceByokLocalLeaseInput: InputObject {
|
||||||
|
public private(set) var __data: InputDict
|
||||||
|
|
||||||
|
public init(_ data: InputDict) {
|
||||||
|
__data = data
|
||||||
|
}
|
||||||
|
|
||||||
|
public init(
|
||||||
|
providers: [CreateWorkspaceByokLocalLeaseProviderInput],
|
||||||
|
workspaceId: String
|
||||||
|
) {
|
||||||
|
__data = InputDict([
|
||||||
|
"providers": providers,
|
||||||
|
"workspaceId": workspaceId
|
||||||
|
])
|
||||||
|
}
|
||||||
|
|
||||||
|
public var providers: [CreateWorkspaceByokLocalLeaseProviderInput] {
|
||||||
|
get { __data["providers"] }
|
||||||
|
set { __data["providers"] = newValue }
|
||||||
|
}
|
||||||
|
|
||||||
|
public var workspaceId: String {
|
||||||
|
get { __data["workspaceId"] }
|
||||||
|
set { __data["workspaceId"] = newValue }
|
||||||
|
}
|
||||||
|
}
|
||||||
+67
@@ -0,0 +1,67 @@
|
|||||||
|
// @generated
|
||||||
|
// This file was automatically generated and should not be edited.
|
||||||
|
|
||||||
|
import ApolloAPI
|
||||||
|
|
||||||
|
public struct CreateWorkspaceByokLocalLeaseProviderInput: InputObject {
|
||||||
|
public private(set) var __data: InputDict
|
||||||
|
|
||||||
|
public init(_ data: InputDict) {
|
||||||
|
__data = data
|
||||||
|
}
|
||||||
|
|
||||||
|
public init(
|
||||||
|
apiKey: String,
|
||||||
|
description: GraphQLNullable<String> = nil,
|
||||||
|
enabled: GraphQLNullable<Bool> = nil,
|
||||||
|
endpoint: GraphQLNullable<String> = nil,
|
||||||
|
name: String,
|
||||||
|
provider: GraphQLEnum<ByokProvider>,
|
||||||
|
sortOrder: GraphQLNullable<SafeInt> = nil
|
||||||
|
) {
|
||||||
|
__data = InputDict([
|
||||||
|
"apiKey": apiKey,
|
||||||
|
"description": description,
|
||||||
|
"enabled": enabled,
|
||||||
|
"endpoint": endpoint,
|
||||||
|
"name": name,
|
||||||
|
"provider": provider,
|
||||||
|
"sortOrder": sortOrder
|
||||||
|
])
|
||||||
|
}
|
||||||
|
|
||||||
|
public var apiKey: String {
|
||||||
|
get { __data["apiKey"] }
|
||||||
|
set { __data["apiKey"] = newValue }
|
||||||
|
}
|
||||||
|
|
||||||
|
public var description: GraphQLNullable<String> {
|
||||||
|
get { __data["description"] }
|
||||||
|
set { __data["description"] = newValue }
|
||||||
|
}
|
||||||
|
|
||||||
|
public var enabled: GraphQLNullable<Bool> {
|
||||||
|
get { __data["enabled"] }
|
||||||
|
set { __data["enabled"] = newValue }
|
||||||
|
}
|
||||||
|
|
||||||
|
public var endpoint: GraphQLNullable<String> {
|
||||||
|
get { __data["endpoint"] }
|
||||||
|
set { __data["endpoint"] = newValue }
|
||||||
|
}
|
||||||
|
|
||||||
|
public var name: String {
|
||||||
|
get { __data["name"] }
|
||||||
|
set { __data["name"] = newValue }
|
||||||
|
}
|
||||||
|
|
||||||
|
public var provider: GraphQLEnum<ByokProvider> {
|
||||||
|
get { __data["provider"] }
|
||||||
|
set { __data["provider"] = newValue }
|
||||||
|
}
|
||||||
|
|
||||||
|
public var sortOrder: GraphQLNullable<SafeInt> {
|
||||||
|
get { __data["sortOrder"] }
|
||||||
|
set { __data["sortOrder"] = newValue }
|
||||||
|
}
|
||||||
|
}
|
||||||
+39
@@ -0,0 +1,39 @@
|
|||||||
|
// @generated
|
||||||
|
// This file was automatically generated and should not be edited.
|
||||||
|
|
||||||
|
import ApolloAPI
|
||||||
|
|
||||||
|
public struct ReorderWorkspaceByokConfigsInput: InputObject {
|
||||||
|
public private(set) var __data: InputDict
|
||||||
|
|
||||||
|
public init(_ data: InputDict) {
|
||||||
|
__data = data
|
||||||
|
}
|
||||||
|
|
||||||
|
public init(
|
||||||
|
ids: [ID],
|
||||||
|
storage: GraphQLEnum<ByokKeyStorage>,
|
||||||
|
workspaceId: String
|
||||||
|
) {
|
||||||
|
__data = InputDict([
|
||||||
|
"ids": ids,
|
||||||
|
"storage": storage,
|
||||||
|
"workspaceId": workspaceId
|
||||||
|
])
|
||||||
|
}
|
||||||
|
|
||||||
|
public var ids: [ID] {
|
||||||
|
get { __data["ids"] }
|
||||||
|
set { __data["ids"] = newValue }
|
||||||
|
}
|
||||||
|
|
||||||
|
public var storage: GraphQLEnum<ByokKeyStorage> {
|
||||||
|
get { __data["storage"] }
|
||||||
|
set { __data["storage"] = newValue }
|
||||||
|
}
|
||||||
|
|
||||||
|
public var workspaceId: String {
|
||||||
|
get { __data["workspaceId"] }
|
||||||
|
set { __data["workspaceId"] = newValue }
|
||||||
|
}
|
||||||
|
}
|
||||||
+9
-2
@@ -13,12 +13,14 @@ public struct SubmitAudioTranscriptionInput: InputObject {
|
|||||||
public init(
|
public init(
|
||||||
quality: GraphQLNullable<TranscriptionQualityInput> = nil,
|
quality: GraphQLNullable<TranscriptionQualityInput> = nil,
|
||||||
sliceManifest: GraphQLNullable<[AudioSliceManifestItemInput]> = nil,
|
sliceManifest: GraphQLNullable<[AudioSliceManifestItemInput]> = nil,
|
||||||
sourceAudio: GraphQLNullable<TranscriptionSourceAudioInput> = nil
|
sourceAudio: GraphQLNullable<TranscriptionSourceAudioInput> = nil,
|
||||||
|
strategy: GraphQLNullable<String> = nil
|
||||||
) {
|
) {
|
||||||
__data = InputDict([
|
__data = InputDict([
|
||||||
"quality": quality,
|
"quality": quality,
|
||||||
"sliceManifest": sliceManifest,
|
"sliceManifest": sliceManifest,
|
||||||
"sourceAudio": sourceAudio
|
"sourceAudio": sourceAudio,
|
||||||
|
"strategy": strategy
|
||||||
])
|
])
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -36,4 +38,9 @@ public struct SubmitAudioTranscriptionInput: InputObject {
|
|||||||
get { __data["sourceAudio"] }
|
get { __data["sourceAudio"] }
|
||||||
set { __data["sourceAudio"] = newValue }
|
set { __data["sourceAudio"] = newValue }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public var strategy: GraphQLNullable<String> {
|
||||||
|
get { __data["strategy"] }
|
||||||
|
set { __data["strategy"] = newValue }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
+60
@@ -0,0 +1,60 @@
|
|||||||
|
// @generated
|
||||||
|
// This file was automatically generated and should not be edited.
|
||||||
|
|
||||||
|
import ApolloAPI
|
||||||
|
|
||||||
|
public struct TestWorkspaceByokConfigInput: InputObject {
|
||||||
|
public private(set) var __data: InputDict
|
||||||
|
|
||||||
|
public init(_ data: InputDict) {
|
||||||
|
__data = data
|
||||||
|
}
|
||||||
|
|
||||||
|
public init(
|
||||||
|
apiKey: GraphQLNullable<String> = nil,
|
||||||
|
configId: GraphQLNullable<ID> = nil,
|
||||||
|
endpoint: GraphQLNullable<String> = nil,
|
||||||
|
provider: GraphQLEnum<ByokProvider>,
|
||||||
|
storage: GraphQLEnum<ByokKeyStorage>,
|
||||||
|
workspaceId: String
|
||||||
|
) {
|
||||||
|
__data = InputDict([
|
||||||
|
"apiKey": apiKey,
|
||||||
|
"configId": configId,
|
||||||
|
"endpoint": endpoint,
|
||||||
|
"provider": provider,
|
||||||
|
"storage": storage,
|
||||||
|
"workspaceId": workspaceId
|
||||||
|
])
|
||||||
|
}
|
||||||
|
|
||||||
|
public var apiKey: GraphQLNullable<String> {
|
||||||
|
get { __data["apiKey"] }
|
||||||
|
set { __data["apiKey"] = newValue }
|
||||||
|
}
|
||||||
|
|
||||||
|
public var configId: GraphQLNullable<ID> {
|
||||||
|
get { __data["configId"] }
|
||||||
|
set { __data["configId"] = newValue }
|
||||||
|
}
|
||||||
|
|
||||||
|
public var endpoint: GraphQLNullable<String> {
|
||||||
|
get { __data["endpoint"] }
|
||||||
|
set { __data["endpoint"] = newValue }
|
||||||
|
}
|
||||||
|
|
||||||
|
public var provider: GraphQLEnum<ByokProvider> {
|
||||||
|
get { __data["provider"] }
|
||||||
|
set { __data["provider"] = newValue }
|
||||||
|
}
|
||||||
|
|
||||||
|
public var storage: GraphQLEnum<ByokKeyStorage> {
|
||||||
|
get { __data["storage"] }
|
||||||
|
set { __data["storage"] = newValue }
|
||||||
|
}
|
||||||
|
|
||||||
|
public var workspaceId: String {
|
||||||
|
get { __data["workspaceId"] }
|
||||||
|
set { __data["workspaceId"] = newValue }
|
||||||
|
}
|
||||||
|
}
|
||||||
+88
@@ -0,0 +1,88 @@
|
|||||||
|
// @generated
|
||||||
|
// This file was automatically generated and should not be edited.
|
||||||
|
|
||||||
|
import ApolloAPI
|
||||||
|
|
||||||
|
public struct UpsertWorkspaceByokConfigInput: InputObject {
|
||||||
|
public private(set) var __data: InputDict
|
||||||
|
|
||||||
|
public init(_ data: InputDict) {
|
||||||
|
__data = data
|
||||||
|
}
|
||||||
|
|
||||||
|
public init(
|
||||||
|
apiKey: GraphQLNullable<String> = nil,
|
||||||
|
description: GraphQLNullable<String> = nil,
|
||||||
|
enabled: GraphQLNullable<Bool> = nil,
|
||||||
|
endpoint: GraphQLNullable<String> = nil,
|
||||||
|
id: GraphQLNullable<ID> = nil,
|
||||||
|
name: String,
|
||||||
|
provider: GraphQLEnum<ByokProvider>,
|
||||||
|
sortOrder: GraphQLNullable<SafeInt> = nil,
|
||||||
|
storage: GraphQLEnum<ByokKeyStorage>,
|
||||||
|
workspaceId: String
|
||||||
|
) {
|
||||||
|
__data = InputDict([
|
||||||
|
"apiKey": apiKey,
|
||||||
|
"description": description,
|
||||||
|
"enabled": enabled,
|
||||||
|
"endpoint": endpoint,
|
||||||
|
"id": id,
|
||||||
|
"name": name,
|
||||||
|
"provider": provider,
|
||||||
|
"sortOrder": sortOrder,
|
||||||
|
"storage": storage,
|
||||||
|
"workspaceId": workspaceId
|
||||||
|
])
|
||||||
|
}
|
||||||
|
|
||||||
|
public var apiKey: GraphQLNullable<String> {
|
||||||
|
get { __data["apiKey"] }
|
||||||
|
set { __data["apiKey"] = newValue }
|
||||||
|
}
|
||||||
|
|
||||||
|
public var description: GraphQLNullable<String> {
|
||||||
|
get { __data["description"] }
|
||||||
|
set { __data["description"] = newValue }
|
||||||
|
}
|
||||||
|
|
||||||
|
public var enabled: GraphQLNullable<Bool> {
|
||||||
|
get { __data["enabled"] }
|
||||||
|
set { __data["enabled"] = newValue }
|
||||||
|
}
|
||||||
|
|
||||||
|
public var endpoint: GraphQLNullable<String> {
|
||||||
|
get { __data["endpoint"] }
|
||||||
|
set { __data["endpoint"] = newValue }
|
||||||
|
}
|
||||||
|
|
||||||
|
public var id: GraphQLNullable<ID> {
|
||||||
|
get { __data["id"] }
|
||||||
|
set { __data["id"] = newValue }
|
||||||
|
}
|
||||||
|
|
||||||
|
public var name: String {
|
||||||
|
get { __data["name"] }
|
||||||
|
set { __data["name"] = newValue }
|
||||||
|
}
|
||||||
|
|
||||||
|
public var provider: GraphQLEnum<ByokProvider> {
|
||||||
|
get { __data["provider"] }
|
||||||
|
set { __data["provider"] = newValue }
|
||||||
|
}
|
||||||
|
|
||||||
|
public var sortOrder: GraphQLNullable<SafeInt> {
|
||||||
|
get { __data["sortOrder"] }
|
||||||
|
set { __data["sortOrder"] = newValue }
|
||||||
|
}
|
||||||
|
|
||||||
|
public var storage: GraphQLEnum<ByokKeyStorage> {
|
||||||
|
get { __data["storage"] }
|
||||||
|
set { __data["storage"] = newValue }
|
||||||
|
}
|
||||||
|
|
||||||
|
public var workspaceId: String {
|
||||||
|
get { __data["workspaceId"] }
|
||||||
|
set { __data["workspaceId"] = newValue }
|
||||||
|
}
|
||||||
|
}
|
||||||
+2
-2
@@ -4,8 +4,8 @@
|
|||||||
import ApolloAPI
|
import ApolloAPI
|
||||||
|
|
||||||
public extension Objects {
|
public extension Objects {
|
||||||
static let GrantedDocUserType = ApolloAPI.Object(
|
static let AdminLicensePreview = ApolloAPI.Object(
|
||||||
typename: "GrantedDocUserType",
|
typename: "AdminLicensePreview",
|
||||||
implementedInterfaces: [],
|
implementedInterfaces: [],
|
||||||
keyFields: nil
|
keyFields: nil
|
||||||
)
|
)
|
||||||
+12
@@ -0,0 +1,12 @@
|
|||||||
|
// @generated
|
||||||
|
// This file was automatically generated and should not be edited.
|
||||||
|
|
||||||
|
import ApolloAPI
|
||||||
|
|
||||||
|
public extension Objects {
|
||||||
|
static let CreateWorkspaceByokLocalLeaseResultType = ApolloAPI.Object(
|
||||||
|
typename: "CreateWorkspaceByokLocalLeaseResultType",
|
||||||
|
implementedInterfaces: [],
|
||||||
|
keyFields: nil
|
||||||
|
)
|
||||||
|
}
|
||||||
Loaded 100 of 142 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user