Compare commits
4 Commits
27a3ec8244
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
50cd4b3ffd | ||
|
|
bf36cb3159 | ||
|
|
2126e7bebf | ||
|
|
20e044d32b |
14
.env.example
14
.env.example
@@ -4,7 +4,7 @@
|
|||||||
# =============================================================================
|
# =============================================================================
|
||||||
|
|
||||||
# --- Application ---
|
# --- Application ---
|
||||||
APP_NAME=HexaHost GameCloud
|
APP_NAME="HexaHost GameCloud"
|
||||||
APP_ENV=development
|
APP_ENV=development
|
||||||
APP_URL=http://localhost:3000
|
APP_URL=http://localhost:3000
|
||||||
API_URL=http://localhost:3001
|
API_URL=http://localhost:3001
|
||||||
@@ -76,7 +76,7 @@ EDGE_START_WAIT_SECONDS=180
|
|||||||
EDGE_POLL_INTERVAL_MS=2000
|
EDGE_POLL_INTERVAL_MS=2000
|
||||||
|
|
||||||
# --- Catalog Providers ---
|
# --- Catalog Providers ---
|
||||||
MODRINTH_USER_AGENT=HexaHostGameCloud/0.1.0 (contact@example.net)
|
MODRINTH_USER_AGENT="HexaHostGameCloud/0.1.0 (contact@example.net)"
|
||||||
CURSEFORGE_API_KEY=
|
CURSEFORGE_API_KEY=
|
||||||
|
|
||||||
# --- Billing ---
|
# --- Billing ---
|
||||||
@@ -89,6 +89,16 @@ STRIPE_WEBHOOK_SECRET=
|
|||||||
OTEL_EXPORTER_OTLP_ENDPOINT=
|
OTEL_EXPORTER_OTLP_ENDPOINT=
|
||||||
LOG_LEVEL=info
|
LOG_LEVEL=info
|
||||||
|
|
||||||
|
# --- OIDC (optional) ---
|
||||||
|
OIDC_ISSUER=
|
||||||
|
OIDC_CLIENT_ID=
|
||||||
|
OIDC_CLIENT_SECRET=
|
||||||
|
OIDC_REDIRECT_URI=http://localhost:3001/api/v1/auth/oidc/callback
|
||||||
|
|
||||||
|
# --- Notification delivery (optional) ---
|
||||||
|
NOTIFICATION_WEBHOOK_URL=
|
||||||
|
NOTIFICATION_DISCORD_WEBHOOK_URL=
|
||||||
|
|
||||||
# --- Service Ports (development) ---
|
# --- Service Ports (development) ---
|
||||||
API_PORT=3001
|
API_PORT=3001
|
||||||
WEB_PORT=3000
|
WEB_PORT=3000
|
||||||
|
|||||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -7,3 +7,4 @@ dist/
|
|||||||
bin/
|
bin/
|
||||||
*.log
|
*.log
|
||||||
data/
|
data/
|
||||||
|
.logs/
|
||||||
|
|||||||
1
.turbo/cache/083c8105a3916fd9-manifest.json
vendored
Normal file
1
.turbo/cache/083c8105a3916fd9-manifest.json
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
{"files":{"packages/auth/.turbo/turbo-build.log":{"size":102,"mtime_nanos":1783255052127378550,"mode":420,"is_dir":false},"packages/auth/dist/password.d.ts":{"size":200,"mtime_nanos":1783255318234684822,"mode":420,"is_dir":false},"packages/auth/dist/totp.js":{"size":683,"mtime_nanos":1783255318235066252,"mode":420,"is_dir":false},"packages/auth/dist/totp.d.ts":{"size":269,"mtime_nanos":1783255318235033232,"mode":420,"is_dir":false},"packages/auth/dist/session.js":{"size":672,"mtime_nanos":1783255318234925202,"mode":420,"is_dir":false},"packages/auth/dist/recovery-codes.js.map":{"size":949,"mtime_nanos":1783255318234865402,"mode":420,"is_dir":false},"packages/auth/dist/index.d.ts":{"size":1570,"mtime_nanos":1783255318234480642,"mode":420,"is_dir":false},"packages/auth/dist/recovery-codes.d.ts":{"size":264,"mtime_nanos":1783255318234800762,"mode":420,"is_dir":false},"packages/auth/dist/username.js":{"size":2787,"mtime_nanos":1783255318235144382,"mode":420,"is_dir":false},"packages/auth/dist/tokens.js.map":{"size":760,"mtime_nanos":1783255318235017152,"mode":420,"is_dir":false},"packages/auth/dist/username.js.map":{"size":2294,"mtime_nanos":1783255318235160872,"mode":420,"is_dir":false},"packages/auth/dist/password.d.ts.map":{"size":266,"mtime_nanos":1783255318234717722,"mode":420,"is_dir":false},"packages/auth/dist/index.js.map":{"size":824,"mtime_nanos":1783255318234638252,"mode":420,"is_dir":false},"packages/auth/dist/session.js.map":{"size":493,"mtime_nanos":1783255318234944292,"mode":420,"is_dir":false},"packages/auth/dist/index.d.ts.map":{"size":1443,"mtime_nanos":1783255318234518132,"mode":420,"is_dir":false},"packages/auth/dist/session.d.ts":{"size":334,"mtime_nanos":1783255318234883722,"mode":420,"is_dir":false},"packages/auth/dist/password.js":{"size":482,"mtime_nanos":1783255318234755462,"mode":420,"is_dir":false},"packages/auth/dist/recovery-codes.js":{"size":1089,"mtime_nanos":1783255318234843272,"mode":420,"is_dir":false},"packages/auth/dist/recovery-codes.d.ts.map":{"size":306,"mtime_nanos":1783255318234822282,"mode":420,"is_dir":false},"packages/auth/dist/username.d.ts.map":{"size":430,"mtime_nanos":1783255318235123102,"mode":420,"is_dir":false},"packages/auth/dist/password.js.map":{"size":404,"mtime_nanos":1783255318234775572,"mode":420,"is_dir":false},"packages/auth/dist/tokens.d.ts.map":{"size":281,"mtime_nanos":1783255318234984402,"mode":420,"is_dir":false},"packages/auth/dist/username.d.ts":{"size":442,"mtime_nanos":1783255318235106382,"mode":420,"is_dir":false},"packages/auth/dist/index.js":{"size":4135,"mtime_nanos":1783255318234572542,"mode":420,"is_dir":false},"packages/auth/dist/totp.d.ts.map":{"size":303,"mtime_nanos":1783255318235050102,"mode":420,"is_dir":false},"packages/auth/dist/tokens.js":{"size":864,"mtime_nanos":1783255318235000782,"mode":420,"is_dir":false},"packages/auth/dist/session.d.ts.map":{"size":300,"mtime_nanos":1783255318234901542,"mode":420,"is_dir":false},"packages/auth/dist/totp.js.map":{"size":569,"mtime_nanos":1783255318235085572,"mode":420,"is_dir":false},"packages/auth/dist/tokens.d.ts":{"size":244,"mtime_nanos":1783255318234964842,"mode":420,"is_dir":false}},"order":["packages/auth/.turbo/turbo-build.log","packages/auth/dist/index.d.ts","packages/auth/dist/index.d.ts.map","packages/auth/dist/index.js","packages/auth/dist/index.js.map","packages/auth/dist/password.d.ts","packages/auth/dist/password.d.ts.map","packages/auth/dist/password.js","packages/auth/dist/password.js.map","packages/auth/dist/recovery-codes.d.ts","packages/auth/dist/recovery-codes.d.ts.map","packages/auth/dist/recovery-codes.js","packages/auth/dist/recovery-codes.js.map","packages/auth/dist/session.d.ts","packages/auth/dist/session.d.ts.map","packages/auth/dist/session.js","packages/auth/dist/session.js.map","packages/auth/dist/tokens.d.ts","packages/auth/dist/tokens.d.ts.map","packages/auth/dist/tokens.js","packages/auth/dist/tokens.js.map","packages/auth/dist/totp.d.ts","packages/auth/dist/totp.d.ts.map","packages/auth/dist/totp.js","packages/auth/dist/totp.js.map","packages/auth/dist/username.d.ts","packages/auth/dist/username.d.ts.map","packages/auth/dist/username.js","packages/auth/dist/username.js.map"]}
|
||||||
1
.turbo/cache/083c8105a3916fd9-meta.json
vendored
Normal file
1
.turbo/cache/083c8105a3916fd9-meta.json
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
{"hash":"083c8105a3916fd9","duration":2162,"sha":"27a3ec8244e70774e6f4673201397e2302c32dfe","dirty_hash":"abc4a2c6304f10abbf2d6c628ed6a63dbf9e6ca06fecaaea2935e4ea7f4ff660"}
|
||||||
BIN
.turbo/cache/083c8105a3916fd9.tar.zst
vendored
Normal file
BIN
.turbo/cache/083c8105a3916fd9.tar.zst
vendored
Normal file
Binary file not shown.
1
.turbo/cache/0b354b78644f8aad-manifest.json
vendored
Normal file
1
.turbo/cache/0b354b78644f8aad-manifest.json
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
{"files":{"packages/database/dist/index.js":{"size":962,"mtime_nanos":1783254827141954191,"mode":420,"is_dir":false},"packages/database/dist/index.d.ts":{"size":991,"mtime_nanos":1783254827155894338,"mode":420,"is_dir":false},"packages/database/dist/index.js.map":{"size":617,"mtime_nanos":1783254827141791201,"mode":420,"is_dir":false},"packages/database/.turbo/turbo-build.log":{"size":506,"mtime_nanos":1783254827176786884,"mode":420,"is_dir":false},"packages/database/dist":{"size":0,"mtime_nanos":0,"mode":0,"is_dir":true},"packages/database/dist/index.d.ts.map":{"size":733,"mtime_nanos":1783254827154786924,"mode":420,"is_dir":false}},"order":["packages/database/.turbo/turbo-build.log","packages/database/dist","packages/database/dist/index.d.ts","packages/database/dist/index.d.ts.map","packages/database/dist/index.js","packages/database/dist/index.js.map"]}
|
||||||
1
.turbo/cache/0b354b78644f8aad-meta.json
vendored
Normal file
1
.turbo/cache/0b354b78644f8aad-meta.json
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
{"hash":"0b354b78644f8aad","duration":3330,"sha":"27a3ec8244e70774e6f4673201397e2302c32dfe","dirty_hash":"67d44d689fa95ed97ad8bb0d3e753762649434cb9a64ff731786cdc979515618"}
|
||||||
BIN
.turbo/cache/0b354b78644f8aad.tar.zst
vendored
Normal file
BIN
.turbo/cache/0b354b78644f8aad.tar.zst
vendored
Normal file
Binary file not shown.
1
.turbo/cache/1a030577ec3e46e3-manifest.json
vendored
Normal file
1
.turbo/cache/1a030577ec3e46e3-manifest.json
vendored
Normal file
File diff suppressed because one or more lines are too long
1
.turbo/cache/1a030577ec3e46e3-meta.json
vendored
Normal file
1
.turbo/cache/1a030577ec3e46e3-meta.json
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
{"hash":"1a030577ec3e46e3","duration":1256,"sha":"27a3ec8244e70774e6f4673201397e2302c32dfe","dirty_hash":"67d44d689fa95ed97ad8bb0d3e753762649434cb9a64ff731786cdc979515618"}
|
||||||
BIN
.turbo/cache/1a030577ec3e46e3.tar.zst
vendored
Normal file
BIN
.turbo/cache/1a030577ec3e46e3.tar.zst
vendored
Normal file
Binary file not shown.
1
.turbo/cache/1b4ddb8b5e9b564a-manifest.json
vendored
Normal file
1
.turbo/cache/1b4ddb8b5e9b564a-manifest.json
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
{"files":{"packages/database/.turbo/turbo-build.log":{"size":549,"mtime_nanos":1783255321226890055,"mode":420,"is_dir":false},"packages/database/dist":{"size":0,"mtime_nanos":0,"mode":0,"is_dir":true},"packages/database/dist/index.d.ts":{"size":991,"mtime_nanos":1783255321208338929,"mode":420,"is_dir":false},"packages/database/dist/index.d.ts.map":{"size":733,"mtime_nanos":1783255321207890090,"mode":420,"is_dir":false},"packages/database/dist/index.js":{"size":962,"mtime_nanos":1783255321192311503,"mode":420,"is_dir":false},"packages/database/dist/index.js.map":{"size":617,"mtime_nanos":1783255321191890119,"mode":420,"is_dir":false}},"order":["packages/database/.turbo/turbo-build.log","packages/database/dist","packages/database/dist/index.d.ts","packages/database/dist/index.d.ts.map","packages/database/dist/index.js","packages/database/dist/index.js.map"]}
|
||||||
1
.turbo/cache/1b4ddb8b5e9b564a-meta.json
vendored
Normal file
1
.turbo/cache/1b4ddb8b5e9b564a-meta.json
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
{"hash":"1b4ddb8b5e9b564a","duration":2992,"sha":"2126e7bebf9ff0796ac77f8548cba399f45e759a","dirty_hash":null}
|
||||||
BIN
.turbo/cache/1b4ddb8b5e9b564a.tar.zst
vendored
Normal file
BIN
.turbo/cache/1b4ddb8b5e9b564a.tar.zst
vendored
Normal file
Binary file not shown.
1
.turbo/cache/3bace58668d8c69c-manifest.json
vendored
Normal file
1
.turbo/cache/3bace58668d8c69c-manifest.json
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
{"files":{"packages/config/dist/index.js":{"size":3043,"mtime_nanos":1783254824143649550,"mode":420,"is_dir":false},"packages/config/dist/index.js.map":{"size":3214,"mtime_nanos":1783254824143422120,"mode":420,"is_dir":false},"packages/config/dist/index.d.ts":{"size":1874,"mtime_nanos":1783254824156792366,"mode":420,"is_dir":false},"packages/config/.turbo/turbo-build.log":{"size":102,"mtime_nanos":1783254824177792328,"mode":420,"is_dir":false},"packages/config/dist/index.d.ts.map":{"size":521,"mtime_nanos":1783254824156792366,"mode":420,"is_dir":false},"packages/config/dist":{"size":0,"mtime_nanos":0,"mode":0,"is_dir":true}},"order":["packages/config/.turbo/turbo-build.log","packages/config/dist","packages/config/dist/index.d.ts","packages/config/dist/index.d.ts.map","packages/config/dist/index.js","packages/config/dist/index.js.map"]}
|
||||||
1
.turbo/cache/3bace58668d8c69c-meta.json
vendored
Normal file
1
.turbo/cache/3bace58668d8c69c-meta.json
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
{"hash":"3bace58668d8c69c","duration":1783,"sha":"27a3ec8244e70774e6f4673201397e2302c32dfe","dirty_hash":"67d44d689fa95ed97ad8bb0d3e753762649434cb9a64ff731786cdc979515618"}
|
||||||
BIN
.turbo/cache/3bace58668d8c69c.tar.zst
vendored
Normal file
BIN
.turbo/cache/3bace58668d8c69c.tar.zst
vendored
Normal file
Binary file not shown.
1
.turbo/cache/51664cdd00a0048d-manifest.json
vendored
Normal file
1
.turbo/cache/51664cdd00a0048d-manifest.json
vendored
Normal file
File diff suppressed because one or more lines are too long
1
.turbo/cache/51664cdd00a0048d-meta.json
vendored
Normal file
1
.turbo/cache/51664cdd00a0048d-meta.json
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
{"hash":"51664cdd00a0048d","duration":2814,"sha":"27a3ec8244e70774e6f4673201397e2302c32dfe","dirty_hash":"abc4a2c6304f10abbf2d6c628ed6a63dbf9e6ca06fecaaea2935e4ea7f4ff660"}
|
||||||
BIN
.turbo/cache/51664cdd00a0048d.tar.zst
vendored
Normal file
BIN
.turbo/cache/51664cdd00a0048d.tar.zst
vendored
Normal file
Binary file not shown.
1
.turbo/cache/53573aa3334db733-manifest.json
vendored
Normal file
1
.turbo/cache/53573aa3334db733-manifest.json
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
{"files":{"packages/integration-auth/dist/hmac.test.js":{"size":953,"mtime_nanos":1783255052203378412,"mode":420,"is_dir":false},"packages/integration-auth/dist":{"size":0,"mtime_nanos":0,"mode":0,"is_dir":true},"packages/integration-auth/dist/hmac.d.ts.map":{"size":821,"mtime_nanos":1783255052199378419,"mode":420,"is_dir":false},"packages/integration-auth/.turbo/turbo-build.log":{"size":126,"mtime_nanos":1783255052243378339,"mode":420,"is_dir":false},"packages/integration-auth/dist/mtls.js":{"size":1604,"mtime_nanos":1783255052208378403,"mode":420,"is_dir":false},"packages/integration-auth/dist/hmac.js":{"size":2272,"mtime_nanos":1783255052181441322,"mode":420,"is_dir":false},"packages/integration-auth/dist/mtls.js.map":{"size":1048,"mtime_nanos":1783255052208378403,"mode":420,"is_dir":false},"packages/integration-auth/dist/index.d.ts.map":{"size":316,"mtime_nanos":1783255052217816303,"mode":420,"is_dir":false},"packages/integration-auth/dist/index.js.map":{"size":382,"mtime_nanos":1783255052216516404,"mode":420,"is_dir":false},"packages/integration-auth/dist/hmac.d.ts":{"size":1050,"mtime_nanos":1783255052199378419,"mode":420,"is_dir":false},"packages/integration-auth/dist/hmac.test.d.ts.map":{"size":112,"mtime_nanos":1783255052204378410,"mode":420,"is_dir":false},"packages/integration-auth/dist/hmac.test.d.ts":{"size":50,"mtime_nanos":1783255052205548416,"mode":420,"is_dir":false},"packages/integration-auth/dist/hmac.js.map":{"size":2198,"mtime_nanos":1783255052180273802,"mode":420,"is_dir":false},"packages/integration-auth/dist/hmac.test.js.map":{"size":744,"mtime_nanos":1783255052203378412,"mode":420,"is_dir":false},"packages/integration-auth/dist/index.d.ts":{"size":368,"mtime_nanos":1783255052217816303,"mode":420,"is_dir":false},"packages/integration-auth/dist/index.js":{"size":2061,"mtime_nanos":1783255052216799793,"mode":420,"is_dir":false},"packages/integration-auth/dist/mtls.d.ts":{"size":628,"mtime_nanos":1783255052210378399,"mode":420,"is_dir":false},"packages/integration-auth/dist/mtls.d.ts.map":{"size":370,"mtime_nanos":1783255052210378399,"mode":420,"is_dir":false}},"order":["packages/integration-auth/.turbo/turbo-build.log","packages/integration-auth/dist","packages/integration-auth/dist/hmac.d.ts","packages/integration-auth/dist/hmac.d.ts.map","packages/integration-auth/dist/hmac.js","packages/integration-auth/dist/hmac.js.map","packages/integration-auth/dist/hmac.test.d.ts","packages/integration-auth/dist/hmac.test.d.ts.map","packages/integration-auth/dist/hmac.test.js","packages/integration-auth/dist/hmac.test.js.map","packages/integration-auth/dist/index.d.ts","packages/integration-auth/dist/index.d.ts.map","packages/integration-auth/dist/index.js","packages/integration-auth/dist/index.js.map","packages/integration-auth/dist/mtls.d.ts","packages/integration-auth/dist/mtls.d.ts.map","packages/integration-auth/dist/mtls.js","packages/integration-auth/dist/mtls.js.map"]}
|
||||||
1
.turbo/cache/53573aa3334db733-meta.json
vendored
Normal file
1
.turbo/cache/53573aa3334db733-meta.json
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
{"hash":"53573aa3334db733","duration":2278,"sha":"27a3ec8244e70774e6f4673201397e2302c32dfe","dirty_hash":"abc4a2c6304f10abbf2d6c628ed6a63dbf9e6ca06fecaaea2935e4ea7f4ff660"}
|
||||||
BIN
.turbo/cache/53573aa3334db733.tar.zst
vendored
Normal file
BIN
.turbo/cache/53573aa3334db733.tar.zst
vendored
Normal file
Binary file not shown.
1
.turbo/cache/7138297b16f09c78-manifest.json
vendored
Normal file
1
.turbo/cache/7138297b16f09c78-manifest.json
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
{"files":{"packages/storage/dist/client.js.map":{"size":1685,"mtime_nanos":1783255052719377475,"mode":420,"is_dir":false},"packages/storage/dist/client.js":{"size":2298,"mtime_nanos":1783255052720940624,"mode":420,"is_dir":false},"packages/storage/dist/index.js":{"size":1654,"mtime_nanos":1783255052726484843,"mode":420,"is_dir":false},"packages/storage/dist/keys.test.d.ts":{"size":50,"mtime_nanos":1783255052728421552,"mode":420,"is_dir":false},"packages/storage/dist/config.d.ts":{"size":922,"mtime_nanos":1783255052716923925,"mode":420,"is_dir":false},"packages/storage/dist/index.d.ts.map":{"size":291,"mtime_nanos":1783255052726484843,"mode":420,"is_dir":false},"packages/storage/.turbo/turbo-build.log":{"size":108,"mtime_nanos":1783255052745377428,"mode":420,"is_dir":false},"packages/storage/dist/keys.test.d.ts.map":{"size":112,"mtime_nanos":1783255052728421552,"mode":420,"is_dir":false},"packages/storage/dist/config.d.ts.map":{"size":358,"mtime_nanos":1783255052715377483,"mode":420,"is_dir":false},"packages/storage/dist/client.d.ts.map":{"size":960,"mtime_nanos":1783255052722502344,"mode":420,"is_dir":false},"packages/storage/dist/config.js.map":{"size":967,"mtime_nanos":1783255052705824178,"mode":420,"is_dir":false},"packages/storage/dist/index.js.map":{"size":339,"mtime_nanos":1783255052726285013,"mode":420,"is_dir":false},"packages/storage/dist/client.d.ts":{"size":925,"mtime_nanos":1783255052723096764,"mode":420,"is_dir":false},"packages/storage/dist/config.js":{"size":1011,"mtime_nanos":1783255052705916208,"mode":420,"is_dir":false},"packages/storage/dist/keys.test.js":{"size":623,"mtime_nanos":1783255052728421552,"mode":420,"is_dir":false},"packages/storage/dist/index.d.ts":{"size":290,"mtime_nanos":1783255052726484843,"mode":420,"is_dir":false},"packages/storage/dist":{"size":0,"mtime_nanos":0,"mode":0,"is_dir":true},"packages/storage/dist/keys.test.js.map":{"size":518,"mtime_nanos":1783255052728371962,"mode":420,"is_dir":false}},"order":["packages/storage/.turbo/turbo-build.log","packages/storage/dist","packages/storage/dist/client.d.ts","packages/storage/dist/client.d.ts.map","packages/storage/dist/client.js","packages/storage/dist/client.js.map","packages/storage/dist/config.d.ts","packages/storage/dist/config.d.ts.map","packages/storage/dist/config.js","packages/storage/dist/config.js.map","packages/storage/dist/index.d.ts","packages/storage/dist/index.d.ts.map","packages/storage/dist/index.js","packages/storage/dist/index.js.map","packages/storage/dist/keys.test.d.ts","packages/storage/dist/keys.test.d.ts.map","packages/storage/dist/keys.test.js","packages/storage/dist/keys.test.js.map"]}
|
||||||
1
.turbo/cache/7138297b16f09c78-meta.json
vendored
Normal file
1
.turbo/cache/7138297b16f09c78-meta.json
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
{"hash":"7138297b16f09c78","duration":2780,"sha":"27a3ec8244e70774e6f4673201397e2302c32dfe","dirty_hash":"abc4a2c6304f10abbf2d6c628ed6a63dbf9e6ca06fecaaea2935e4ea7f4ff660"}
|
||||||
BIN
.turbo/cache/7138297b16f09c78.tar.zst
vendored
Normal file
BIN
.turbo/cache/7138297b16f09c78.tar.zst
vendored
Normal file
Binary file not shown.
1
.turbo/cache/7c83d574699fc347-manifest.json
vendored
Normal file
1
.turbo/cache/7c83d574699fc347-manifest.json
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
{"files":{"packages/auth/dist/password.js.map":{"size":404,"mtime_nanos":1783254823699793196,"mode":420,"is_dir":false},"packages/auth/dist":{"size":0,"mtime_nanos":0,"mode":0,"is_dir":true},"packages/auth/dist/tokens.d.ts.map":{"size":281,"mtime_nanos":1783254823716073291,"mode":420,"is_dir":false},"packages/auth/dist/recovery-codes.d.ts.map":{"size":306,"mtime_nanos":1783254823751018942,"mode":420,"is_dir":false},"packages/auth/dist/index.d.ts":{"size":1570,"mtime_nanos":1783254823800793012,"mode":420,"is_dir":false},"packages/auth/dist/username.d.ts.map":{"size":430,"mtime_nanos":1783254823731793138,"mode":420,"is_dir":false},"packages/auth/dist/username.js.map":{"size":2294,"mtime_nanos":1783254823728793143,"mode":420,"is_dir":false},"packages/auth/dist/totp.d.ts":{"size":269,"mtime_nanos":1783254823743793116,"mode":420,"is_dir":false},"packages/auth/dist/totp.js":{"size":683,"mtime_nanos":1783254823741793119,"mode":420,"is_dir":false},"packages/auth/dist/index.d.ts.map":{"size":1443,"mtime_nanos":1783254823800793012,"mode":420,"is_dir":false},"packages/auth/dist/recovery-codes.js":{"size":1089,"mtime_nanos":1783254823749679443,"mode":420,"is_dir":false},"packages/auth/dist/session.d.ts.map":{"size":300,"mtime_nanos":1783254823757744571,"mode":420,"is_dir":false},"packages/auth/dist/username.d.ts":{"size":442,"mtime_nanos":1783254823731793138,"mode":420,"is_dir":false},"packages/auth/dist/session.d.ts":{"size":334,"mtime_nanos":1783254823757986501,"mode":420,"is_dir":false},"packages/auth/dist/tokens.js.map":{"size":760,"mtime_nanos":1783254823712940372,"mode":420,"is_dir":false},"packages/auth/dist/recovery-codes.js.map":{"size":949,"mtime_nanos":1783254823749441483,"mode":420,"is_dir":false},"packages/auth/dist/index.js":{"size":4135,"mtime_nanos":1783254823782793045,"mode":420,"is_dir":false},"packages/auth/dist/password.js":{"size":482,"mtime_nanos":1783254823700699224,"mode":420,"is_dir":false},"packages/auth/dist/session.js.map":{"size":493,"mtime_nanos":1783254823754793096,"mode":420,"is_dir":false},"packages/auth/dist/username.js":{"size":2787,"mtime_nanos":1783254823729845437,"mode":420,"is_dir":false},"packages/auth/dist/password.d.ts.map":{"size":266,"mtime_nanos":1783254823706019293,"mode":420,"is_dir":false},"packages/auth/dist/tokens.d.ts":{"size":244,"mtime_nanos":1783254823716326991,"mode":420,"is_dir":false},"packages/auth/dist/index.js.map":{"size":824,"mtime_nanos":1783254823782793045,"mode":420,"is_dir":false},"packages/auth/dist/totp.d.ts.map":{"size":303,"mtime_nanos":1783254823742793118,"mode":420,"is_dir":false},"packages/auth/dist/session.js":{"size":672,"mtime_nanos":1783254823754793096,"mode":420,"is_dir":false},"packages/auth/dist/totp.js.map":{"size":569,"mtime_nanos":1783254823741793119,"mode":420,"is_dir":false},"packages/auth/.turbo/turbo-build.log":{"size":98,"mtime_nanos":1783254823836792947,"mode":420,"is_dir":false},"packages/auth/dist/recovery-codes.d.ts":{"size":264,"mtime_nanos":1783254823751270432,"mode":420,"is_dir":false},"packages/auth/dist/tokens.js":{"size":864,"mtime_nanos":1783254823713299411,"mode":420,"is_dir":false},"packages/auth/dist/password.d.ts":{"size":200,"mtime_nanos":1783254823706019293,"mode":420,"is_dir":false}},"order":["packages/auth/.turbo/turbo-build.log","packages/auth/dist","packages/auth/dist/index.d.ts","packages/auth/dist/index.d.ts.map","packages/auth/dist/index.js","packages/auth/dist/index.js.map","packages/auth/dist/password.d.ts","packages/auth/dist/password.d.ts.map","packages/auth/dist/password.js","packages/auth/dist/password.js.map","packages/auth/dist/recovery-codes.d.ts","packages/auth/dist/recovery-codes.d.ts.map","packages/auth/dist/recovery-codes.js","packages/auth/dist/recovery-codes.js.map","packages/auth/dist/session.d.ts","packages/auth/dist/session.d.ts.map","packages/auth/dist/session.js","packages/auth/dist/session.js.map","packages/auth/dist/tokens.d.ts","packages/auth/dist/tokens.d.ts.map","packages/auth/dist/tokens.js","packages/auth/dist/tokens.js.map","packages/auth/dist/totp.d.ts","packages/auth/dist/totp.d.ts.map","packages/auth/dist/totp.js","packages/auth/dist/totp.js.map","packages/auth/dist/username.d.ts","packages/auth/dist/username.d.ts.map","packages/auth/dist/username.js","packages/auth/dist/username.js.map"]}
|
||||||
1
.turbo/cache/7c83d574699fc347-meta.json
vendored
Normal file
1
.turbo/cache/7c83d574699fc347-meta.json
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
{"hash":"7c83d574699fc347","duration":1442,"sha":"27a3ec8244e70774e6f4673201397e2302c32dfe","dirty_hash":"67d44d689fa95ed97ad8bb0d3e753762649434cb9a64ff731786cdc979515618"}
|
||||||
BIN
.turbo/cache/7c83d574699fc347.tar.zst
vendored
Normal file
BIN
.turbo/cache/7c83d574699fc347.tar.zst
vendored
Normal file
Binary file not shown.
1
.turbo/cache/7ec080769e22b52e-manifest.json
vendored
Normal file
1
.turbo/cache/7ec080769e22b52e-manifest.json
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
{"files":{"packages/config/dist":{"size":0,"mtime_nanos":0,"mode":0,"is_dir":true},"packages/config/dist/index.js.map":{"size":3214,"mtime_nanos":1783255052195378426,"mode":420,"is_dir":false},"packages/config/.turbo/turbo-build.log":{"size":106,"mtime_nanos":1783255052255378317,"mode":420,"is_dir":false},"packages/config/dist/index.d.ts.map":{"size":521,"mtime_nanos":1783255052219805483,"mode":420,"is_dir":false},"packages/config/dist/index.d.ts":{"size":1874,"mtime_nanos":1783255052220051732,"mode":420,"is_dir":false},"packages/config/dist/index.js":{"size":3043,"mtime_nanos":1783255052196378425,"mode":420,"is_dir":false}},"order":["packages/config/.turbo/turbo-build.log","packages/config/dist","packages/config/dist/index.d.ts","packages/config/dist/index.d.ts.map","packages/config/dist/index.js","packages/config/dist/index.js.map"]}
|
||||||
1
.turbo/cache/7ec080769e22b52e-meta.json
vendored
Normal file
1
.turbo/cache/7ec080769e22b52e-meta.json
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
{"hash":"7ec080769e22b52e","duration":2294,"sha":"27a3ec8244e70774e6f4673201397e2302c32dfe","dirty_hash":"abc4a2c6304f10abbf2d6c628ed6a63dbf9e6ca06fecaaea2935e4ea7f4ff660"}
|
||||||
BIN
.turbo/cache/7ec080769e22b52e.tar.zst
vendored
Normal file
BIN
.turbo/cache/7ec080769e22b52e.tar.zst
vendored
Normal file
Binary file not shown.
1
.turbo/cache/83bc945bf697348a-manifest.json
vendored
Normal file
1
.turbo/cache/83bc945bf697348a-manifest.json
vendored
Normal file
File diff suppressed because one or more lines are too long
1
.turbo/cache/83bc945bf697348a-meta.json
vendored
Normal file
1
.turbo/cache/83bc945bf697348a-meta.json
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
{"hash":"83bc945bf697348a","duration":17907,"sha":"27a3ec8244e70774e6f4673201397e2302c32dfe","dirty_hash":"67d44d689fa95ed97ad8bb0d3e753762649434cb9a64ff731786cdc979515618"}
|
||||||
BIN
.turbo/cache/83bc945bf697348a.tar.zst
vendored
Normal file
BIN
.turbo/cache/83bc945bf697348a.tar.zst
vendored
Normal file
Binary file not shown.
1
.turbo/cache/8c5cbe4308e26227-manifest.json
vendored
Normal file
1
.turbo/cache/8c5cbe4308e26227-manifest.json
vendored
Normal file
File diff suppressed because one or more lines are too long
1
.turbo/cache/8c5cbe4308e26227-meta.json
vendored
Normal file
1
.turbo/cache/8c5cbe4308e26227-meta.json
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
{"hash":"8c5cbe4308e26227","duration":1073,"sha":"27a3ec8244e70774e6f4673201397e2302c32dfe","dirty_hash":"abc4a2c6304f10abbf2d6c628ed6a63dbf9e6ca06fecaaea2935e4ea7f4ff660"}
|
||||||
BIN
.turbo/cache/8c5cbe4308e26227.tar.zst
vendored
Normal file
BIN
.turbo/cache/8c5cbe4308e26227.tar.zst
vendored
Normal file
Binary file not shown.
1
.turbo/cache/8de6070230e6cb7f-manifest.json
vendored
Normal file
1
.turbo/cache/8de6070230e6cb7f-manifest.json
vendored
Normal file
File diff suppressed because one or more lines are too long
1
.turbo/cache/8de6070230e6cb7f-meta.json
vendored
Normal file
1
.turbo/cache/8de6070230e6cb7f-meta.json
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
{"hash":"8de6070230e6cb7f","duration":1207,"sha":"2126e7bebf9ff0796ac77f8548cba399f45e759a","dirty_hash":null}
|
||||||
BIN
.turbo/cache/8de6070230e6cb7f.tar.zst
vendored
Normal file
BIN
.turbo/cache/8de6070230e6cb7f.tar.zst
vendored
Normal file
Binary file not shown.
1
.turbo/cache/95e8fc4f72d7869c-manifest.json
vendored
Normal file
1
.turbo/cache/95e8fc4f72d7869c-manifest.json
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
{"files":{"packages/scheduler/dist/index.js":{"size":2244,"mtime_nanos":1783254823897792836,"mode":420,"is_dir":false},"packages/scheduler/dist/scheduling.test.d.ts.map":{"size":124,"mtime_nanos":1783254823903792825,"mode":420,"is_dir":false},"packages/scheduler/dist/scheduling.test.js":{"size":1778,"mtime_nanos":1783254823903792825,"mode":420,"is_dir":false},"packages/scheduler/dist/index.js.map":{"size":414,"mtime_nanos":1783254823897792836,"mode":420,"is_dir":false},"packages/scheduler/dist/capacity.d.ts":{"size":1024,"mtime_nanos":1783254823885792858,"mode":420,"is_dir":false},"packages/scheduler/dist/types.d.ts":{"size":615,"mtime_nanos":1783254823857792909,"mode":420,"is_dir":false},"packages/scheduler/dist/index.d.ts":{"size":431,"mtime_nanos":1783254823898792834,"mode":420,"is_dir":false},"packages/scheduler/dist/capacity.js.map":{"size":2635,"mtime_nanos":1783254823878792871,"mode":420,"is_dir":false},"packages/scheduler/.turbo/turbo-build.log":{"size":108,"mtime_nanos":1783254823935792767,"mode":420,"is_dir":false},"packages/scheduler/dist":{"size":0,"mtime_nanos":0,"mode":0,"is_dir":true},"packages/scheduler/dist/index.d.ts.map":{"size":378,"mtime_nanos":1783254823898792834,"mode":420,"is_dir":false},"packages/scheduler/dist/select-node.js.map":{"size":939,"mtime_nanos":1783254823890792849,"mode":420,"is_dir":false},"packages/scheduler/dist/capacity.js":{"size":3044,"mtime_nanos":1783254823878792871,"mode":420,"is_dir":false},"packages/scheduler/dist/types.js.map":{"size":102,"mtime_nanos":1783254823853908348,"mode":420,"is_dir":false},"packages/scheduler/dist/scheduling.test.d.ts":{"size":56,"mtime_nanos":1783254823903792825,"mode":420,"is_dir":false},"packages/scheduler/dist/types.js":{"size":110,"mtime_nanos":1783254823853908348,"mode":420,"is_dir":false},"packages/scheduler/dist/select-node.d.ts":{"size":304,"mtime_nanos":1783254823892792845,"mode":420,"is_dir":false},"packages/scheduler/dist/capacity.d.ts.map":{"size":766,"mtime_nanos":1783254823885792858,"mode":420,"is_dir":false},"packages/scheduler/dist/types.d.ts.map":{"size":664,"mtime_nanos":1783254823857792909,"mode":420,"is_dir":false},"packages/scheduler/dist/select-node.d.ts.map":{"size":347,"mtime_nanos":1783254823892792845,"mode":420,"is_dir":false},"packages/scheduler/dist/scheduling.test.js.map":{"size":1650,"mtime_nanos":1783254823903792825,"mode":420,"is_dir":false},"packages/scheduler/dist/select-node.js":{"size":944,"mtime_nanos":1783254823891792847,"mode":420,"is_dir":false}},"order":["packages/scheduler/.turbo/turbo-build.log","packages/scheduler/dist","packages/scheduler/dist/capacity.d.ts","packages/scheduler/dist/capacity.d.ts.map","packages/scheduler/dist/capacity.js","packages/scheduler/dist/capacity.js.map","packages/scheduler/dist/index.d.ts","packages/scheduler/dist/index.d.ts.map","packages/scheduler/dist/index.js","packages/scheduler/dist/index.js.map","packages/scheduler/dist/scheduling.test.d.ts","packages/scheduler/dist/scheduling.test.d.ts.map","packages/scheduler/dist/scheduling.test.js","packages/scheduler/dist/scheduling.test.js.map","packages/scheduler/dist/select-node.d.ts","packages/scheduler/dist/select-node.d.ts.map","packages/scheduler/dist/select-node.js","packages/scheduler/dist/select-node.js.map","packages/scheduler/dist/types.d.ts","packages/scheduler/dist/types.d.ts.map","packages/scheduler/dist/types.js","packages/scheduler/dist/types.js.map"]}
|
||||||
1
.turbo/cache/95e8fc4f72d7869c-meta.json
vendored
Normal file
1
.turbo/cache/95e8fc4f72d7869c-meta.json
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
{"hash":"95e8fc4f72d7869c","duration":1541,"sha":"27a3ec8244e70774e6f4673201397e2302c32dfe","dirty_hash":"67d44d689fa95ed97ad8bb0d3e753762649434cb9a64ff731786cdc979515618"}
|
||||||
BIN
.turbo/cache/95e8fc4f72d7869c.tar.zst
vendored
Normal file
BIN
.turbo/cache/95e8fc4f72d7869c.tar.zst
vendored
Normal file
Binary file not shown.
1
.turbo/cache/a2830d8f7ca0f71c-manifest.json
vendored
Normal file
1
.turbo/cache/a2830d8f7ca0f71c-manifest.json
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
{"files":{"packages/metering/dist/metering.test.js.map":{"size":782,"mtime_nanos":1783254824041792575,"mode":420,"is_dir":false},"packages/metering/.turbo/turbo-build.log":{"size":106,"mtime_nanos":1783254824068792526,"mode":420,"is_dir":false},"packages/metering/dist/metering.test.d.ts":{"size":54,"mtime_nanos":1783254824042792573,"mode":420,"is_dir":false},"packages/metering/dist/index.js":{"size":1548,"mtime_nanos":1783254824029792597,"mode":420,"is_dir":false},"packages/metering/dist/metering.test.d.ts.map":{"size":120,"mtime_nanos":1783254824042792573,"mode":420,"is_dir":false},"packages/metering/dist":{"size":0,"mtime_nanos":0,"mode":0,"is_dir":true},"packages/metering/dist/metering.test.js":{"size":901,"mtime_nanos":1783254824041792575,"mode":420,"is_dir":false},"packages/metering/dist/index.d.ts":{"size":561,"mtime_nanos":1783254824037714265,"mode":420,"is_dir":false},"packages/metering/dist/index.d.ts.map":{"size":479,"mtime_nanos":1783254824036792584,"mode":420,"is_dir":false},"packages/metering/dist/index.js.map":{"size":1220,"mtime_nanos":1783254824029792597,"mode":420,"is_dir":false}},"order":["packages/metering/.turbo/turbo-build.log","packages/metering/dist","packages/metering/dist/index.d.ts","packages/metering/dist/index.d.ts.map","packages/metering/dist/index.js","packages/metering/dist/index.js.map","packages/metering/dist/metering.test.d.ts","packages/metering/dist/metering.test.d.ts.map","packages/metering/dist/metering.test.js","packages/metering/dist/metering.test.js.map"]}
|
||||||
1
.turbo/cache/a2830d8f7ca0f71c-meta.json
vendored
Normal file
1
.turbo/cache/a2830d8f7ca0f71c-meta.json
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
{"hash":"a2830d8f7ca0f71c","duration":1674,"sha":"27a3ec8244e70774e6f4673201397e2302c32dfe","dirty_hash":"67d44d689fa95ed97ad8bb0d3e753762649434cb9a64ff731786cdc979515618"}
|
||||||
BIN
.turbo/cache/a2830d8f7ca0f71c.tar.zst
vendored
Normal file
BIN
.turbo/cache/a2830d8f7ca0f71c.tar.zst
vendored
Normal file
Binary file not shown.
1
.turbo/cache/a30020bd73a840d7-manifest.json
vendored
Normal file
1
.turbo/cache/a30020bd73a840d7-manifest.json
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
{"files":{"packages/storage/.turbo/turbo-build.log":{"size":104,"mtime_nanos":1783254824603791555,"mode":420,"is_dir":false},"packages/storage/dist/index.js.map":{"size":339,"mtime_nanos":1783254824571791613,"mode":420,"is_dir":false},"packages/storage/dist/client.js.map":{"size":1685,"mtime_nanos":1783254824564791625,"mode":420,"is_dir":false},"packages/storage/dist/config.js":{"size":1011,"mtime_nanos":1783254824544080765,"mode":420,"is_dir":false},"packages/storage/dist/index.d.ts":{"size":290,"mtime_nanos":1783254824574588148,"mode":420,"is_dir":false},"packages/storage/dist/keys.test.js":{"size":623,"mtime_nanos":1783254824576753387,"mode":420,"is_dir":false},"packages/storage/dist/index.d.ts.map":{"size":291,"mtime_nanos":1783254824574382738,"mode":420,"is_dir":false},"packages/storage/dist/client.d.ts.map":{"size":960,"mtime_nanos":1783254824567791620,"mode":420,"is_dir":false},"packages/storage/dist":{"size":0,"mtime_nanos":0,"mode":0,"is_dir":true},"packages/storage/dist/client.js":{"size":2298,"mtime_nanos":1783254824565759240,"mode":420,"is_dir":false},"packages/storage/dist/config.d.ts.map":{"size":358,"mtime_nanos":1783254824557791638,"mode":420,"is_dir":false},"packages/storage/dist/index.js":{"size":1654,"mtime_nanos":1783254824571791613,"mode":420,"is_dir":false},"packages/storage/dist/keys.test.d.ts":{"size":50,"mtime_nanos":1783254824576791604,"mode":420,"is_dir":false},"packages/storage/dist/keys.test.d.ts.map":{"size":112,"mtime_nanos":1783254824576791604,"mode":420,"is_dir":false},"packages/storage/dist/config.d.ts":{"size":922,"mtime_nanos":1783254824558932792,"mode":420,"is_dir":false},"packages/storage/dist/client.d.ts":{"size":925,"mtime_nanos":1783254824567791620,"mode":420,"is_dir":false},"packages/storage/dist/config.js.map":{"size":967,"mtime_nanos":1783254824543943805,"mode":420,"is_dir":false},"packages/storage/dist/keys.test.js.map":{"size":518,"mtime_nanos":1783254824576584287,"mode":420,"is_dir":false}},"order":["packages/storage/.turbo/turbo-build.log","packages/storage/dist","packages/storage/dist/client.d.ts","packages/storage/dist/client.d.ts.map","packages/storage/dist/client.js","packages/storage/dist/client.js.map","packages/storage/dist/config.d.ts","packages/storage/dist/config.d.ts.map","packages/storage/dist/config.js","packages/storage/dist/config.js.map","packages/storage/dist/index.d.ts","packages/storage/dist/index.d.ts.map","packages/storage/dist/index.js","packages/storage/dist/index.js.map","packages/storage/dist/keys.test.d.ts","packages/storage/dist/keys.test.d.ts.map","packages/storage/dist/keys.test.js","packages/storage/dist/keys.test.js.map"]}
|
||||||
1
.turbo/cache/a30020bd73a840d7-meta.json
vendored
Normal file
1
.turbo/cache/a30020bd73a840d7-meta.json
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
{"hash":"a30020bd73a840d7","duration":2209,"sha":"27a3ec8244e70774e6f4673201397e2302c32dfe","dirty_hash":"67d44d689fa95ed97ad8bb0d3e753762649434cb9a64ff731786cdc979515618"}
|
||||||
BIN
.turbo/cache/a30020bd73a840d7.tar.zst
vendored
Normal file
BIN
.turbo/cache/a30020bd73a840d7.tar.zst
vendored
Normal file
Binary file not shown.
1
.turbo/cache/a480ca24eeac5ad4-manifest.json
vendored
Normal file
1
.turbo/cache/a480ca24eeac5ad4-manifest.json
vendored
Normal file
File diff suppressed because one or more lines are too long
1
.turbo/cache/a480ca24eeac5ad4-meta.json
vendored
Normal file
1
.turbo/cache/a480ca24eeac5ad4-meta.json
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
{"hash":"a480ca24eeac5ad4","duration":2232,"sha":"27a3ec8244e70774e6f4673201397e2302c32dfe","dirty_hash":"67d44d689fa95ed97ad8bb0d3e753762649434cb9a64ff731786cdc979515618"}
|
||||||
BIN
.turbo/cache/a480ca24eeac5ad4.tar.zst
vendored
Normal file
BIN
.turbo/cache/a480ca24eeac5ad4.tar.zst
vendored
Normal file
Binary file not shown.
1
.turbo/cache/b85ca2f9ca81bcb3-manifest.json
vendored
Normal file
1
.turbo/cache/b85ca2f9ca81bcb3-manifest.json
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
{"files":{"packages/integration-auth/dist":{"size":0,"mtime_nanos":0,"mode":0,"is_dir":true},"packages/integration-auth/dist/hmac.js.map":{"size":2198,"mtime_nanos":1783254824066513338,"mode":420,"is_dir":false},"packages/integration-auth/dist/mtls.d.ts":{"size":628,"mtime_nanos":1783254824092511382,"mode":420,"is_dir":false},"packages/integration-auth/dist/mtls.js.map":{"size":1048,"mtime_nanos":1783254824089792488,"mode":420,"is_dir":false},"packages/integration-auth/dist/hmac.test.d.ts":{"size":50,"mtime_nanos":1783254824086436383,"mode":420,"is_dir":false},"packages/integration-auth/dist/hmac.test.d.ts.map":{"size":112,"mtime_nanos":1783254824086436383,"mode":420,"is_dir":false},"packages/integration-auth/dist/hmac.js":{"size":2272,"mtime_nanos":1783254824066843698,"mode":420,"is_dir":false},"packages/integration-auth/dist/hmac.d.ts":{"size":1050,"mtime_nanos":1783254824080792504,"mode":420,"is_dir":false},"packages/integration-auth/dist/hmac.test.js":{"size":953,"mtime_nanos":1783254824084792497,"mode":420,"is_dir":false},"packages/integration-auth/dist/hmac.test.js.map":{"size":744,"mtime_nanos":1783254824084792497,"mode":420,"is_dir":false},"packages/integration-auth/dist/index.d.ts":{"size":368,"mtime_nanos":1783254824097792473,"mode":420,"is_dir":false},"packages/integration-auth/dist/index.d.ts.map":{"size":316,"mtime_nanos":1783254824097792473,"mode":420,"is_dir":false},"packages/integration-auth/dist/index.js":{"size":2061,"mtime_nanos":1783254824096792475,"mode":420,"is_dir":false},"packages/integration-auth/dist/index.js.map":{"size":382,"mtime_nanos":1783254824096792475,"mode":420,"is_dir":false},"packages/integration-auth/dist/mtls.js":{"size":1604,"mtime_nanos":1783254824090568502,"mode":420,"is_dir":false},"packages/integration-auth/dist/hmac.d.ts.map":{"size":821,"mtime_nanos":1783254824080792504,"mode":420,"is_dir":false},"packages/integration-auth/dist/mtls.d.ts.map":{"size":370,"mtime_nanos":1783254824091792484,"mode":420,"is_dir":false},"packages/integration-auth/.turbo/turbo-build.log":{"size":122,"mtime_nanos":1783254824115792441,"mode":420,"is_dir":false}},"order":["packages/integration-auth/.turbo/turbo-build.log","packages/integration-auth/dist","packages/integration-auth/dist/hmac.d.ts","packages/integration-auth/dist/hmac.d.ts.map","packages/integration-auth/dist/hmac.js","packages/integration-auth/dist/hmac.js.map","packages/integration-auth/dist/hmac.test.d.ts","packages/integration-auth/dist/hmac.test.d.ts.map","packages/integration-auth/dist/hmac.test.js","packages/integration-auth/dist/hmac.test.js.map","packages/integration-auth/dist/index.d.ts","packages/integration-auth/dist/index.d.ts.map","packages/integration-auth/dist/index.js","packages/integration-auth/dist/index.js.map","packages/integration-auth/dist/mtls.d.ts","packages/integration-auth/dist/mtls.d.ts.map","packages/integration-auth/dist/mtls.js","packages/integration-auth/dist/mtls.js.map"]}
|
||||||
1
.turbo/cache/b85ca2f9ca81bcb3-meta.json
vendored
Normal file
1
.turbo/cache/b85ca2f9ca81bcb3-meta.json
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
{"hash":"b85ca2f9ca81bcb3","duration":1721,"sha":"27a3ec8244e70774e6f4673201397e2302c32dfe","dirty_hash":"67d44d689fa95ed97ad8bb0d3e753762649434cb9a64ff731786cdc979515618"}
|
||||||
BIN
.turbo/cache/b85ca2f9ca81bcb3.tar.zst
vendored
Normal file
BIN
.turbo/cache/b85ca2f9ca81bcb3.tar.zst
vendored
Normal file
Binary file not shown.
1
.turbo/cache/bc1b6c014f0a15b7-manifest.json
vendored
Normal file
1
.turbo/cache/bc1b6c014f0a15b7-manifest.json
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
{"files":{"packages/catalog/dist/compatibility.js":{"size":1955,"mtime_nanos":1783255052308333652,"mode":420,"is_dir":false},"packages/catalog/dist/index.d.ts.map":{"size":383,"mtime_nanos":1783255052322408118,"mode":420,"is_dir":false},"packages/catalog/dist/modrinth.js":{"size":2292,"mtime_nanos":1783255052291723086,"mode":420,"is_dir":false},"packages/catalog/dist/compatibility.js.map":{"size":1649,"mtime_nanos":1783255052307378223,"mode":420,"is_dir":false},"packages/catalog/dist/compatibility.test.d.ts.map":{"size":130,"mtime_nanos":1783255052316378207,"mode":420,"is_dir":false},"packages/catalog/dist/modrinth.js.map":{"size":1972,"mtime_nanos":1783255052291723086,"mode":420,"is_dir":false},"packages/catalog/dist/compatibility.test.d.ts":{"size":59,"mtime_nanos":1783255052316378207,"mode":420,"is_dir":false},"packages/catalog/dist/index.js.map":{"size":343,"mtime_nanos":1783255052321378197,"mode":420,"is_dir":false},"packages/catalog/dist/compatibility.test.js":{"size":2546,"mtime_nanos":1783255052315378209,"mode":420,"is_dir":false},"packages/catalog/dist/modrinth.d.ts":{"size":1701,"mtime_nanos":1783255052299378237,"mode":420,"is_dir":false},"packages/catalog/dist/compatibility.test.js.map":{"size":1968,"mtime_nanos":1783255052315378209,"mode":420,"is_dir":false},"packages/catalog/dist/modrinth.d.ts.map":{"size":1722,"mtime_nanos":1783255052299378237,"mode":420,"is_dir":false},"packages/catalog/.turbo/turbo-build.log":{"size":108,"mtime_nanos":1783255052359378128,"mode":420,"is_dir":false},"packages/catalog/dist/index.d.ts":{"size":444,"mtime_nanos":1783255052322408118,"mode":420,"is_dir":false},"packages/catalog/dist":{"size":0,"mtime_nanos":0,"mode":0,"is_dir":true},"packages/catalog/dist/index.js":{"size":1772,"mtime_nanos":1783255052322408118,"mode":420,"is_dir":false},"packages/catalog/dist/compatibility.d.ts.map":{"size":570,"mtime_nanos":1783255052309378219,"mode":420,"is_dir":false},"packages/catalog/dist/compatibility.d.ts":{"size":645,"mtime_nanos":1783255052310780041,"mode":420,"is_dir":false}},"order":["packages/catalog/.turbo/turbo-build.log","packages/catalog/dist","packages/catalog/dist/compatibility.d.ts","packages/catalog/dist/compatibility.d.ts.map","packages/catalog/dist/compatibility.js","packages/catalog/dist/compatibility.js.map","packages/catalog/dist/compatibility.test.d.ts","packages/catalog/dist/compatibility.test.d.ts.map","packages/catalog/dist/compatibility.test.js","packages/catalog/dist/compatibility.test.js.map","packages/catalog/dist/index.d.ts","packages/catalog/dist/index.d.ts.map","packages/catalog/dist/index.js","packages/catalog/dist/index.js.map","packages/catalog/dist/modrinth.d.ts","packages/catalog/dist/modrinth.d.ts.map","packages/catalog/dist/modrinth.js","packages/catalog/dist/modrinth.js.map"]}
|
||||||
1
.turbo/cache/bc1b6c014f0a15b7-meta.json
vendored
Normal file
1
.turbo/cache/bc1b6c014f0a15b7-meta.json
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
{"hash":"bc1b6c014f0a15b7","duration":2395,"sha":"27a3ec8244e70774e6f4673201397e2302c32dfe","dirty_hash":"abc4a2c6304f10abbf2d6c628ed6a63dbf9e6ca06fecaaea2935e4ea7f4ff660"}
|
||||||
BIN
.turbo/cache/bc1b6c014f0a15b7.tar.zst
vendored
Normal file
BIN
.turbo/cache/bc1b6c014f0a15b7.tar.zst
vendored
Normal file
Binary file not shown.
1
.turbo/cache/d12676556f73d54d-manifest.json
vendored
Normal file
1
.turbo/cache/d12676556f73d54d-manifest.json
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
{"files":{"packages/database/.turbo/turbo-build.log":{"size":545,"mtime_nanos":1783255055195372981,"mode":420,"is_dir":false},"packages/database/dist":{"size":0,"mtime_nanos":0,"mode":0,"is_dir":true},"packages/database/dist/index.d.ts":{"size":991,"mtime_nanos":1783255055173955545,"mode":420,"is_dir":false},"packages/database/dist/index.d.ts.map":{"size":733,"mtime_nanos":1783255055172373023,"mode":420,"is_dir":false},"packages/database/dist/index.js":{"size":962,"mtime_nanos":1783255055158449838,"mode":420,"is_dir":false},"packages/database/dist/index.js.map":{"size":617,"mtime_nanos":1783255055158349198,"mode":420,"is_dir":false}},"order":["packages/database/.turbo/turbo-build.log","packages/database/dist","packages/database/dist/index.d.ts","packages/database/dist/index.d.ts.map","packages/database/dist/index.js","packages/database/dist/index.js.map"]}
|
||||||
1
.turbo/cache/d12676556f73d54d-meta.json
vendored
Normal file
1
.turbo/cache/d12676556f73d54d-meta.json
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
{"hash":"d12676556f73d54d","duration":3058,"sha":"27a3ec8244e70774e6f4673201397e2302c32dfe","dirty_hash":"abc4a2c6304f10abbf2d6c628ed6a63dbf9e6ca06fecaaea2935e4ea7f4ff660"}
|
||||||
BIN
.turbo/cache/d12676556f73d54d.tar.zst
vendored
Normal file
BIN
.turbo/cache/d12676556f73d54d.tar.zst
vendored
Normal file
Binary file not shown.
1
.turbo/cache/d572575f47998158-manifest.json
vendored
Normal file
1
.turbo/cache/d572575f47998158-manifest.json
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
{"files":{"packages/metering/dist/index.js":{"size":1548,"mtime_nanos":1783255051818492438,"mode":420,"is_dir":false},"packages/metering/dist/index.d.ts.map":{"size":479,"mtime_nanos":1783255051825379098,"mode":420,"is_dir":false},"packages/metering/.turbo/turbo-build.log":{"size":110,"mtime_nanos":1783255051898378966,"mode":420,"is_dir":false},"packages/metering/dist/metering.test.d.ts":{"size":54,"mtime_nanos":1783255051837379076,"mode":420,"is_dir":false},"packages/metering/dist":{"size":0,"mtime_nanos":0,"mode":0,"is_dir":true},"packages/metering/dist/metering.test.d.ts.map":{"size":120,"mtime_nanos":1783255051837379076,"mode":420,"is_dir":false},"packages/metering/dist/metering.test.js":{"size":901,"mtime_nanos":1783255051832379085,"mode":420,"is_dir":false},"packages/metering/dist/index.js.map":{"size":1220,"mtime_nanos":1783255051816379114,"mode":420,"is_dir":false},"packages/metering/dist/metering.test.js.map":{"size":782,"mtime_nanos":1783255051832379085,"mode":420,"is_dir":false},"packages/metering/dist/index.d.ts":{"size":561,"mtime_nanos":1783255051825379098,"mode":420,"is_dir":false}},"order":["packages/metering/.turbo/turbo-build.log","packages/metering/dist","packages/metering/dist/index.d.ts","packages/metering/dist/index.d.ts.map","packages/metering/dist/index.js","packages/metering/dist/index.js.map","packages/metering/dist/metering.test.d.ts","packages/metering/dist/metering.test.d.ts.map","packages/metering/dist/metering.test.js","packages/metering/dist/metering.test.js.map"]}
|
||||||
1
.turbo/cache/d572575f47998158-meta.json
vendored
Normal file
1
.turbo/cache/d572575f47998158-meta.json
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
{"hash":"d572575f47998158","duration":1932,"sha":"27a3ec8244e70774e6f4673201397e2302c32dfe","dirty_hash":"abc4a2c6304f10abbf2d6c628ed6a63dbf9e6ca06fecaaea2935e4ea7f4ff660"}
|
||||||
BIN
.turbo/cache/d572575f47998158.tar.zst
vendored
Normal file
BIN
.turbo/cache/d572575f47998158.tar.zst
vendored
Normal file
Binary file not shown.
1
.turbo/cache/e57573f49057e7f1-manifest.json
vendored
Normal file
1
.turbo/cache/e57573f49057e7f1-manifest.json
vendored
Normal file
File diff suppressed because one or more lines are too long
1
.turbo/cache/e57573f49057e7f1-meta.json
vendored
Normal file
1
.turbo/cache/e57573f49057e7f1-meta.json
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
{"hash":"e57573f49057e7f1","duration":4583,"sha":"27a3ec8244e70774e6f4673201397e2302c32dfe","dirty_hash":"67d44d689fa95ed97ad8bb0d3e753762649434cb9a64ff731786cdc979515618"}
|
||||||
BIN
.turbo/cache/e57573f49057e7f1.tar.zst
vendored
Normal file
BIN
.turbo/cache/e57573f49057e7f1.tar.zst
vendored
Normal file
Binary file not shown.
1
.turbo/cache/f065eda65a09cf0d-manifest.json
vendored
Normal file
1
.turbo/cache/f065eda65a09cf0d-manifest.json
vendored
Normal file
File diff suppressed because one or more lines are too long
1
.turbo/cache/f065eda65a09cf0d-meta.json
vendored
Normal file
1
.turbo/cache/f065eda65a09cf0d-meta.json
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
{"hash":"f065eda65a09cf0d","duration":2291,"sha":"27a3ec8244e70774e6f4673201397e2302c32dfe","dirty_hash":"67d44d689fa95ed97ad8bb0d3e753762649434cb9a64ff731786cdc979515618"}
|
||||||
BIN
.turbo/cache/f065eda65a09cf0d.tar.zst
vendored
Normal file
BIN
.turbo/cache/f065eda65a09cf0d.tar.zst
vendored
Normal file
Binary file not shown.
1
.turbo/cache/f0bf7376519f6918-manifest.json
vendored
Normal file
1
.turbo/cache/f0bf7376519f6918-manifest.json
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
{"files":{"packages/scheduler/dist/capacity.js":{"size":3044,"mtime_nanos":1783255052166378479,"mode":420,"is_dir":false},"packages/scheduler/dist/scheduling.test.js.map":{"size":1650,"mtime_nanos":1783255052196378425,"mode":420,"is_dir":false},"packages/scheduler/dist/scheduling.test.d.ts.map":{"size":124,"mtime_nanos":1783255052197378423,"mode":420,"is_dir":false},"packages/scheduler/dist/index.js":{"size":2244,"mtime_nanos":1783255052187378441,"mode":420,"is_dir":false},"packages/scheduler/.turbo/turbo-build.log":{"size":112,"mtime_nanos":1783255052223378375,"mode":420,"is_dir":false},"packages/scheduler/dist/scheduling.test.js":{"size":1778,"mtime_nanos":1783255052196378425,"mode":420,"is_dir":false},"packages/scheduler/dist/types.js.map":{"size":102,"mtime_nanos":1783255052146021760,"mode":420,"is_dir":false},"packages/scheduler/dist/types.js":{"size":110,"mtime_nanos":1783255052146621830,"mode":420,"is_dir":false},"packages/scheduler/dist/select-node.d.ts.map":{"size":347,"mtime_nanos":1783255052181811181,"mode":420,"is_dir":false},"packages/scheduler/dist/scheduling.test.d.ts":{"size":56,"mtime_nanos":1783255052197378423,"mode":420,"is_dir":false},"packages/scheduler/dist/types.d.ts":{"size":615,"mtime_nanos":1783255052153378502,"mode":420,"is_dir":false},"packages/scheduler/dist/index.d.ts":{"size":431,"mtime_nanos":1783255052188378440,"mode":420,"is_dir":false},"packages/scheduler/dist/select-node.js.map":{"size":939,"mtime_nanos":1783255052178378458,"mode":420,"is_dir":false},"packages/scheduler/dist/types.d.ts.map":{"size":664,"mtime_nanos":1783255052153378502,"mode":420,"is_dir":false},"packages/scheduler/dist":{"size":0,"mtime_nanos":0,"mode":0,"is_dir":true},"packages/scheduler/dist/capacity.d.ts":{"size":1024,"mtime_nanos":1783255052173378466,"mode":420,"is_dir":false},"packages/scheduler/dist/capacity.d.ts.map":{"size":766,"mtime_nanos":1783255052173378466,"mode":420,"is_dir":false},"packages/scheduler/dist/capacity.js.map":{"size":2635,"mtime_nanos":1783255052165378481,"mode":420,"is_dir":false},"packages/scheduler/dist/select-node.d.ts":{"size":304,"mtime_nanos":1783255052181811181,"mode":420,"is_dir":false},"packages/scheduler/dist/index.d.ts.map":{"size":378,"mtime_nanos":1783255052188378440,"mode":420,"is_dir":false},"packages/scheduler/dist/index.js.map":{"size":414,"mtime_nanos":1783255052187378441,"mode":420,"is_dir":false},"packages/scheduler/dist/select-node.js":{"size":944,"mtime_nanos":1783255052180273802,"mode":420,"is_dir":false}},"order":["packages/scheduler/.turbo/turbo-build.log","packages/scheduler/dist","packages/scheduler/dist/capacity.d.ts","packages/scheduler/dist/capacity.d.ts.map","packages/scheduler/dist/capacity.js","packages/scheduler/dist/capacity.js.map","packages/scheduler/dist/index.d.ts","packages/scheduler/dist/index.d.ts.map","packages/scheduler/dist/index.js","packages/scheduler/dist/index.js.map","packages/scheduler/dist/scheduling.test.d.ts","packages/scheduler/dist/scheduling.test.d.ts.map","packages/scheduler/dist/scheduling.test.js","packages/scheduler/dist/scheduling.test.js.map","packages/scheduler/dist/select-node.d.ts","packages/scheduler/dist/select-node.d.ts.map","packages/scheduler/dist/select-node.js","packages/scheduler/dist/select-node.js.map","packages/scheduler/dist/types.d.ts","packages/scheduler/dist/types.d.ts.map","packages/scheduler/dist/types.js","packages/scheduler/dist/types.js.map"]}
|
||||||
1
.turbo/cache/f0bf7376519f6918-meta.json
vendored
Normal file
1
.turbo/cache/f0bf7376519f6918-meta.json
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
{"hash":"f0bf7376519f6918","duration":2258,"sha":"27a3ec8244e70774e6f4673201397e2302c32dfe","dirty_hash":"abc4a2c6304f10abbf2d6c628ed6a63dbf9e6ca06fecaaea2935e4ea7f4ff660"}
|
||||||
BIN
.turbo/cache/f0bf7376519f6918.tar.zst
vendored
Normal file
BIN
.turbo/cache/f0bf7376519f6918.tar.zst
vendored
Normal file
Binary file not shown.
1
.turbo/cache/f1d80f0b8df92657-manifest.json
vendored
Normal file
1
.turbo/cache/f1d80f0b8df92657-manifest.json
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
{"files":{"packages/catalog/dist/index.d.ts":{"size":444,"mtime_nanos":1783254823962792718,"mode":420,"is_dir":false},"packages/catalog/dist/index.js":{"size":1772,"mtime_nanos":1783254823962617243,"mode":420,"is_dir":false},"packages/catalog/dist":{"size":0,"mtime_nanos":0,"mode":0,"is_dir":true},"packages/catalog/dist/modrinth.d.ts.map":{"size":1722,"mtime_nanos":1783254823942792755,"mode":420,"is_dir":false},"packages/catalog/dist/modrinth.js":{"size":2292,"mtime_nanos":1783254823937826478,"mode":420,"is_dir":false},"packages/catalog/dist/compatibility.test.js.map":{"size":1968,"mtime_nanos":1783254823956792730,"mode":420,"is_dir":false},"packages/catalog/.turbo/turbo-build.log":{"size":104,"mtime_nanos":1783254823991792666,"mode":420,"is_dir":false},"packages/catalog/dist/index.js.map":{"size":343,"mtime_nanos":1783254823961792720,"mode":420,"is_dir":false},"packages/catalog/dist/compatibility.js":{"size":1955,"mtime_nanos":1783254823948276246,"mode":420,"is_dir":false},"packages/catalog/dist/compatibility.test.d.ts":{"size":59,"mtime_nanos":1783254823957792727,"mode":420,"is_dir":false},"packages/catalog/dist/compatibility.d.ts":{"size":645,"mtime_nanos":1783254823951395755,"mode":420,"is_dir":false},"packages/catalog/dist/compatibility.test.js":{"size":2546,"mtime_nanos":1783254823957442534,"mode":420,"is_dir":false},"packages/catalog/dist/compatibility.d.ts.map":{"size":570,"mtime_nanos":1783254823950792740,"mode":420,"is_dir":false},"packages/catalog/dist/compatibility.js.map":{"size":1649,"mtime_nanos":1783254823948276246,"mode":420,"is_dir":false},"packages/catalog/dist/modrinth.d.ts":{"size":1701,"mtime_nanos":1783254823942792755,"mode":420,"is_dir":false},"packages/catalog/dist/index.d.ts.map":{"size":383,"mtime_nanos":1783254823962792718,"mode":420,"is_dir":false},"packages/catalog/dist/compatibility.test.d.ts.map":{"size":130,"mtime_nanos":1783254823957792727,"mode":420,"is_dir":false},"packages/catalog/dist/modrinth.js.map":{"size":1972,"mtime_nanos":1783254823937641058,"mode":420,"is_dir":false}},"order":["packages/catalog/.turbo/turbo-build.log","packages/catalog/dist","packages/catalog/dist/compatibility.d.ts","packages/catalog/dist/compatibility.d.ts.map","packages/catalog/dist/compatibility.js","packages/catalog/dist/compatibility.js.map","packages/catalog/dist/compatibility.test.d.ts","packages/catalog/dist/compatibility.test.d.ts.map","packages/catalog/dist/compatibility.test.js","packages/catalog/dist/compatibility.test.js.map","packages/catalog/dist/index.d.ts","packages/catalog/dist/index.d.ts.map","packages/catalog/dist/index.js","packages/catalog/dist/index.js.map","packages/catalog/dist/modrinth.d.ts","packages/catalog/dist/modrinth.d.ts.map","packages/catalog/dist/modrinth.js","packages/catalog/dist/modrinth.js.map"]}
|
||||||
1
.turbo/cache/f1d80f0b8df92657-meta.json
vendored
Normal file
1
.turbo/cache/f1d80f0b8df92657-meta.json
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
{"hash":"f1d80f0b8df92657","duration":1597,"sha":"27a3ec8244e70774e6f4673201397e2302c32dfe","dirty_hash":"67d44d689fa95ed97ad8bb0d3e753762649434cb9a64ff731786cdc979515618"}
|
||||||
BIN
.turbo/cache/f1d80f0b8df92657.tar.zst
vendored
Normal file
BIN
.turbo/cache/f1d80f0b8df92657.tar.zst
vendored
Normal file
Binary file not shown.
@@ -137,7 +137,7 @@ pnpm db:migrate
|
|||||||
pnpm dev
|
pnpm dev
|
||||||
|
|
||||||
# Admin anlegen (keine Default-Zugangsdaten)
|
# Admin anlegen (keine Default-Zugangsdaten)
|
||||||
pnpm bootstrap:admin -- --email admin@localhost --password 'min-12-chars-here'
|
pnpm bootstrap:admin -- --email admin@example.com --password 'min-12-chars-here'
|
||||||
```
|
```
|
||||||
|
|
||||||
| Flow | URL |
|
| Flow | URL |
|
||||||
|
|||||||
@@ -21,16 +21,22 @@
|
|||||||
"@hexahost/contracts": "workspace:*",
|
"@hexahost/contracts": "workspace:*",
|
||||||
"@hexahost/database": "workspace:*",
|
"@hexahost/database": "workspace:*",
|
||||||
"@hexahost/dns": "workspace:*",
|
"@hexahost/dns": "workspace:*",
|
||||||
|
"@hexahost/feature-flags": "workspace:*",
|
||||||
|
"@hexahost/otel": "workspace:*",
|
||||||
"@hexahost/integration-auth": "workspace:*",
|
"@hexahost/integration-auth": "workspace:*",
|
||||||
"@hexahost/metering": "workspace:*",
|
"@hexahost/metering": "workspace:*",
|
||||||
|
"@hexahost/permissions": "workspace:*",
|
||||||
"@hexahost/scheduler": "workspace:*",
|
"@hexahost/scheduler": "workspace:*",
|
||||||
|
"@hexahost/server-state": "workspace:*",
|
||||||
"@hexahost/storage": "workspace:*",
|
"@hexahost/storage": "workspace:*",
|
||||||
|
"@aws-sdk/client-s3": "^3.750.0",
|
||||||
"@nestjs/common": "^11.0.7",
|
"@nestjs/common": "^11.0.7",
|
||||||
"@nestjs/core": "^11.0.7",
|
"@nestjs/core": "^11.0.7",
|
||||||
"@nestjs/platform-fastify": "^11.0.7",
|
"@nestjs/platform-fastify": "^11.0.7",
|
||||||
"@nestjs/swagger": "^11.0.3",
|
"@nestjs/swagger": "^11.0.3",
|
||||||
"@nestjs/throttler": "^6.4.0",
|
"@nestjs/throttler": "^6.4.0",
|
||||||
"bullmq": "^5.34.8",
|
"bullmq": "^5.34.8",
|
||||||
|
"cron-parser": "^5.0.4",
|
||||||
"fastify": "^5.2.1",
|
"fastify": "^5.2.1",
|
||||||
"ioredis": "^5.4.2",
|
"ioredis": "^5.4.2",
|
||||||
"nestjs-pino": "^4.3.0",
|
"nestjs-pino": "^4.3.0",
|
||||||
|
|||||||
29
apps/api/src/abuse/abuse.controller.ts
Normal file
29
apps/api/src/abuse/abuse.controller.ts
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
import { Body, Controller, Post, UseGuards } from '@nestjs/common';
|
||||||
|
import { ApiOperation, ApiTags } from '@nestjs/swagger';
|
||||||
|
|
||||||
|
import { createAbuseReportSchema } from '@hexahost/contracts';
|
||||||
|
|
||||||
|
import { CurrentUser } from '../auth/decorators/current-user.decorator';
|
||||||
|
import { SessionGuard } from '../auth/guards/session.guard';
|
||||||
|
import { ZodValidationPipe } from '../common/pipes/zod-validation.pipe';
|
||||||
|
|
||||||
|
import { AbuseService } from './abuse.service';
|
||||||
|
|
||||||
|
@ApiTags('abuse')
|
||||||
|
@Controller('abuse/reports')
|
||||||
|
@UseGuards(SessionGuard)
|
||||||
|
export class AbuseController {
|
||||||
|
constructor(private readonly abuseService: AbuseService) {}
|
||||||
|
|
||||||
|
@Post()
|
||||||
|
@ApiOperation({ summary: 'Submit an abuse report' })
|
||||||
|
create(
|
||||||
|
@CurrentUser() user: { id: string },
|
||||||
|
@Body(new ZodValidationPipe(createAbuseReportSchema)) body: unknown,
|
||||||
|
) {
|
||||||
|
return this.abuseService.create(
|
||||||
|
user.id,
|
||||||
|
body as Parameters<AbuseService['create']>[1],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
15
apps/api/src/abuse/abuse.module.ts
Normal file
15
apps/api/src/abuse/abuse.module.ts
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
import { Module } from '@nestjs/common';
|
||||||
|
|
||||||
|
import { AuthModule } from '../auth/auth.module';
|
||||||
|
import { PrismaModule } from '../prisma/prisma.module';
|
||||||
|
|
||||||
|
import { AbuseController } from './abuse.controller';
|
||||||
|
import { AbuseService } from './abuse.service';
|
||||||
|
|
||||||
|
@Module({
|
||||||
|
imports: [PrismaModule, AuthModule],
|
||||||
|
controllers: [AbuseController],
|
||||||
|
providers: [AbuseService],
|
||||||
|
exports: [AbuseService],
|
||||||
|
})
|
||||||
|
export class AbuseModule {}
|
||||||
78
apps/api/src/abuse/abuse.service.ts
Normal file
78
apps/api/src/abuse/abuse.service.ts
Normal file
@@ -0,0 +1,78 @@
|
|||||||
|
import { Injectable } from '@nestjs/common';
|
||||||
|
|
||||||
|
import type {
|
||||||
|
AbuseReport,
|
||||||
|
AbuseReportListResponse,
|
||||||
|
CreateAbuseReport,
|
||||||
|
UpdateAbuseReport,
|
||||||
|
} from '@hexahost/contracts';
|
||||||
|
import type { AbuseReport as AbuseReportRow } from '@hexahost/database';
|
||||||
|
|
||||||
|
import { PrismaService } from '../prisma/prisma.service';
|
||||||
|
|
||||||
|
function mapReport(report: AbuseReportRow): AbuseReport {
|
||||||
|
return {
|
||||||
|
id: report.id,
|
||||||
|
reporterId: report.reporterId,
|
||||||
|
targetUserId: report.targetUserId,
|
||||||
|
serverId: report.serverId,
|
||||||
|
status: report.status,
|
||||||
|
reason: report.reason,
|
||||||
|
details: report.details,
|
||||||
|
resolution: report.resolution,
|
||||||
|
createdAt: report.createdAt.toISOString(),
|
||||||
|
updatedAt: report.updatedAt.toISOString(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class AbuseService {
|
||||||
|
constructor(private readonly prisma: PrismaService) {}
|
||||||
|
|
||||||
|
async create(reporterId: string, input: CreateAbuseReport): Promise<AbuseReport> {
|
||||||
|
const report = await this.prisma.abuseReport.create({
|
||||||
|
data: {
|
||||||
|
reporterId,
|
||||||
|
targetUserId: input.targetUserId,
|
||||||
|
serverId: input.serverId,
|
||||||
|
reason: input.reason.trim(),
|
||||||
|
details: input.details?.trim(),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
return mapReport(report);
|
||||||
|
}
|
||||||
|
|
||||||
|
async listAdmin(options?: {
|
||||||
|
status?: AbuseReport['status'];
|
||||||
|
limit?: number;
|
||||||
|
cursor?: string;
|
||||||
|
}): Promise<AbuseReportListResponse> {
|
||||||
|
const limit = Math.min(Math.max(options?.limit ?? 50, 1), 100);
|
||||||
|
|
||||||
|
const reports = await this.prisma.abuseReport.findMany({
|
||||||
|
where: options?.status ? { status: options.status } : undefined,
|
||||||
|
orderBy: { createdAt: 'desc' },
|
||||||
|
take: limit + 1,
|
||||||
|
...(options?.cursor
|
||||||
|
? { cursor: { id: options.cursor }, skip: 1 }
|
||||||
|
: {}),
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
reports: reports.slice(0, limit).map(mapReport),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
async updateAdmin(reportId: string, input: UpdateAbuseReport): Promise<AbuseReport> {
|
||||||
|
const report = await this.prisma.abuseReport.update({
|
||||||
|
where: { id: reportId },
|
||||||
|
data: {
|
||||||
|
status: input.status,
|
||||||
|
resolution: input.resolution,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
return mapReport(report);
|
||||||
|
}
|
||||||
|
}
|
||||||
58
apps/api/src/admin/admin-abuse.controller.ts
Normal file
58
apps/api/src/admin/admin-abuse.controller.ts
Normal file
@@ -0,0 +1,58 @@
|
|||||||
|
import {
|
||||||
|
Body,
|
||||||
|
Controller,
|
||||||
|
Get,
|
||||||
|
Param,
|
||||||
|
ParseUUIDPipe,
|
||||||
|
Patch,
|
||||||
|
Query,
|
||||||
|
UseGuards,
|
||||||
|
} from '@nestjs/common';
|
||||||
|
import { ApiOperation, ApiTags } from '@nestjs/swagger';
|
||||||
|
|
||||||
|
import { updateAbuseReportSchema } from '@hexahost/contracts';
|
||||||
|
import type { AbuseReport } from '@hexahost/contracts';
|
||||||
|
|
||||||
|
import { CurrentUser } from '../auth/decorators/current-user.decorator';
|
||||||
|
import { SessionGuard } from '../auth/guards/session.guard';
|
||||||
|
import { ZodValidationPipe } from '../common/pipes/zod-validation.pipe';
|
||||||
|
import { AbuseService } from '../abuse/abuse.service';
|
||||||
|
|
||||||
|
import { assertSuperAdmin } from './admin-auth.util';
|
||||||
|
|
||||||
|
@ApiTags('admin')
|
||||||
|
@Controller('admin/abuse')
|
||||||
|
@UseGuards(SessionGuard)
|
||||||
|
export class AdminAbuseController {
|
||||||
|
constructor(private readonly abuseService: AbuseService) {}
|
||||||
|
|
||||||
|
@Get()
|
||||||
|
@ApiOperation({ summary: 'List abuse reports' })
|
||||||
|
list(
|
||||||
|
@CurrentUser() user: { roles: string[] },
|
||||||
|
@Query('status') status?: AbuseReport['status'],
|
||||||
|
@Query('limit') limit?: string,
|
||||||
|
@Query('cursor') cursor?: string,
|
||||||
|
) {
|
||||||
|
assertSuperAdmin(user.roles);
|
||||||
|
return this.abuseService.listAdmin({
|
||||||
|
status,
|
||||||
|
limit: limit ? Number(limit) : undefined,
|
||||||
|
cursor,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Patch(':reportId')
|
||||||
|
@ApiOperation({ summary: 'Update an abuse report' })
|
||||||
|
update(
|
||||||
|
@CurrentUser() user: { roles: string[] },
|
||||||
|
@Param('reportId', ParseUUIDPipe) reportId: string,
|
||||||
|
@Body(new ZodValidationPipe(updateAbuseReportSchema)) body: unknown,
|
||||||
|
) {
|
||||||
|
assertSuperAdmin(user.roles);
|
||||||
|
return this.abuseService.updateAdmin(
|
||||||
|
reportId,
|
||||||
|
body as Parameters<AbuseService['updateAdmin']>[1],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
31
apps/api/src/admin/admin-audit.controller.ts
Normal file
31
apps/api/src/admin/admin-audit.controller.ts
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
import { Controller, Get, Query, UseGuards } from '@nestjs/common';
|
||||||
|
import { ApiOperation, ApiTags } from '@nestjs/swagger';
|
||||||
|
|
||||||
|
import { CurrentUser } from '../auth/decorators/current-user.decorator';
|
||||||
|
import { SessionGuard } from '../auth/guards/session.guard';
|
||||||
|
|
||||||
|
import { assertSuperAdmin } from './admin-auth.util';
|
||||||
|
import { AdminAuditService } from './admin-audit.service';
|
||||||
|
|
||||||
|
@ApiTags('admin')
|
||||||
|
@Controller('admin/audit')
|
||||||
|
@UseGuards(SessionGuard)
|
||||||
|
export class AdminAuditController {
|
||||||
|
constructor(private readonly audit: AdminAuditService) {}
|
||||||
|
|
||||||
|
@Get()
|
||||||
|
@ApiOperation({ summary: 'List audit events' })
|
||||||
|
list(
|
||||||
|
@CurrentUser() user: { roles: string[] },
|
||||||
|
@Query('limit') limit?: string,
|
||||||
|
@Query('cursor') cursor?: string,
|
||||||
|
@Query('action') action?: string,
|
||||||
|
) {
|
||||||
|
assertSuperAdmin(user.roles);
|
||||||
|
return this.audit.listAuditEvents({
|
||||||
|
limit: limit ? Number(limit) : undefined,
|
||||||
|
cursor,
|
||||||
|
action,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
62
apps/api/src/admin/admin-audit.service.ts
Normal file
62
apps/api/src/admin/admin-audit.service.ts
Normal file
@@ -0,0 +1,62 @@
|
|||||||
|
import { Injectable } from '@nestjs/common';
|
||||||
|
|
||||||
|
import { PrismaService } from '../prisma/prisma.service';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class AdminAuditService {
|
||||||
|
constructor(private readonly prisma: PrismaService) {}
|
||||||
|
|
||||||
|
async listAuditEvents(query: {
|
||||||
|
limit?: number;
|
||||||
|
cursor?: string;
|
||||||
|
action?: string;
|
||||||
|
}): Promise<{
|
||||||
|
items: Array<{
|
||||||
|
id: string;
|
||||||
|
action: string;
|
||||||
|
entityType: string | null;
|
||||||
|
entityId: string | null;
|
||||||
|
metadata: unknown;
|
||||||
|
ipAddress: string | null;
|
||||||
|
createdAt: string;
|
||||||
|
user: { id: string; email: string; username: string } | null;
|
||||||
|
}>;
|
||||||
|
nextCursor: string | null;
|
||||||
|
}> {
|
||||||
|
const limit = Math.min(query.limit ?? 50, 100);
|
||||||
|
|
||||||
|
const events = await this.prisma.auditEvent.findMany({
|
||||||
|
where: query.action ? { action: query.action } : undefined,
|
||||||
|
orderBy: { createdAt: 'desc' },
|
||||||
|
take: limit + 1,
|
||||||
|
...(query.cursor
|
||||||
|
? {
|
||||||
|
cursor: { id: query.cursor },
|
||||||
|
skip: 1,
|
||||||
|
}
|
||||||
|
: {}),
|
||||||
|
include: {
|
||||||
|
user: {
|
||||||
|
select: { id: true, email: true, username: true },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const hasMore = events.length > limit;
|
||||||
|
const items = hasMore ? events.slice(0, limit) : events;
|
||||||
|
|
||||||
|
return {
|
||||||
|
items: items.map((event) => ({
|
||||||
|
id: event.id,
|
||||||
|
action: event.action,
|
||||||
|
entityType: event.entityType,
|
||||||
|
entityId: event.entityId,
|
||||||
|
metadata: event.metadata,
|
||||||
|
ipAddress: event.ipAddress,
|
||||||
|
createdAt: event.createdAt.toISOString(),
|
||||||
|
user: event.user,
|
||||||
|
})),
|
||||||
|
nextCursor: hasMore ? items[items.length - 1]?.id ?? null : null,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
18
apps/api/src/admin/admin-auth.util.ts
Normal file
18
apps/api/src/admin/admin-auth.util.ts
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
import { ForbiddenException } from '@nestjs/common';
|
||||||
|
|
||||||
|
import { UserRole } from '@hexahost/auth';
|
||||||
|
|
||||||
|
export function assertSuperAdmin(roles: string[]): void {
|
||||||
|
if (!roles.includes(UserRole.SUPER_ADMIN)) {
|
||||||
|
throw new ForbiddenException('Super administrator access required');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function assertPlatformRole(
|
||||||
|
roles: string[],
|
||||||
|
allowed: UserRole[],
|
||||||
|
): void {
|
||||||
|
if (!allowed.some((role) => roles.includes(role))) {
|
||||||
|
throw new ForbiddenException('Insufficient platform permissions');
|
||||||
|
}
|
||||||
|
}
|
||||||
22
apps/api/src/admin/admin-dashboard.controller.ts
Normal file
22
apps/api/src/admin/admin-dashboard.controller.ts
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
import { Controller, Get, UseGuards } from '@nestjs/common';
|
||||||
|
import { ApiOperation, ApiTags } from '@nestjs/swagger';
|
||||||
|
|
||||||
|
import { CurrentUser } from '../auth/decorators/current-user.decorator';
|
||||||
|
import { SessionGuard } from '../auth/guards/session.guard';
|
||||||
|
|
||||||
|
import { assertSuperAdmin } from './admin-auth.util';
|
||||||
|
import { AdminDashboardService } from './admin-dashboard.service';
|
||||||
|
|
||||||
|
@ApiTags('admin')
|
||||||
|
@Controller('admin/dashboard')
|
||||||
|
@UseGuards(SessionGuard)
|
||||||
|
export class AdminDashboardController {
|
||||||
|
constructor(private readonly dashboard: AdminDashboardService) {}
|
||||||
|
|
||||||
|
@Get()
|
||||||
|
@ApiOperation({ summary: 'Admin dashboard statistics' })
|
||||||
|
stats(@CurrentUser() user: { roles: string[] }) {
|
||||||
|
assertSuperAdmin(user.roles);
|
||||||
|
return this.dashboard.getStats();
|
||||||
|
}
|
||||||
|
}
|
||||||
51
apps/api/src/admin/admin-dashboard.service.ts
Normal file
51
apps/api/src/admin/admin-dashboard.service.ts
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
import { Injectable } from '@nestjs/common';
|
||||||
|
|
||||||
|
import { PrismaService } from '../prisma/prisma.service';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class AdminDashboardService {
|
||||||
|
constructor(private readonly prisma: PrismaService) {}
|
||||||
|
|
||||||
|
async getStats() {
|
||||||
|
const [
|
||||||
|
activeUsers,
|
||||||
|
totalServers,
|
||||||
|
runningServers,
|
||||||
|
queuedServers,
|
||||||
|
onlineNodes,
|
||||||
|
failedJobs24h,
|
||||||
|
openAbuseReports,
|
||||||
|
openTickets,
|
||||||
|
] = await Promise.all([
|
||||||
|
this.prisma.user.count({ where: { status: 'ACTIVE' } }),
|
||||||
|
this.prisma.gameServer.count({
|
||||||
|
where: { status: { not: 'DELETED' } },
|
||||||
|
}),
|
||||||
|
this.prisma.gameServer.count({ where: { status: 'RUNNING' } }),
|
||||||
|
this.prisma.serverStartQueueEntry.count({ where: { status: 'QUEUED' } }),
|
||||||
|
this.prisma.gameNode.count({ where: { status: 'ONLINE' } }),
|
||||||
|
this.prisma.jobRecord.count({
|
||||||
|
where: {
|
||||||
|
status: 'FAILED',
|
||||||
|
createdAt: { gte: new Date(Date.now() - 24 * 60 * 60 * 1000) },
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
this.prisma.abuseReport.count({ where: { status: 'OPEN' } }),
|
||||||
|
this.prisma.supportTicket.count({
|
||||||
|
where: { status: { in: ['OPEN', 'IN_PROGRESS', 'WAITING'] } },
|
||||||
|
}),
|
||||||
|
]);
|
||||||
|
|
||||||
|
return {
|
||||||
|
activeUsers,
|
||||||
|
totalServers,
|
||||||
|
runningServers,
|
||||||
|
queueLength: queuedServers,
|
||||||
|
onlineNodes,
|
||||||
|
failedJobs24h,
|
||||||
|
openAbuseReports,
|
||||||
|
openTickets,
|
||||||
|
generatedAt: new Date().toISOString(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
31
apps/api/src/admin/admin-jobs.controller.ts
Normal file
31
apps/api/src/admin/admin-jobs.controller.ts
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
import { Controller, Get, Query, UseGuards } from '@nestjs/common';
|
||||||
|
import { ApiOperation, ApiTags } from '@nestjs/swagger';
|
||||||
|
|
||||||
|
import { CurrentUser } from '../auth/decorators/current-user.decorator';
|
||||||
|
import { SessionGuard } from '../auth/guards/session.guard';
|
||||||
|
|
||||||
|
import { assertSuperAdmin } from './admin-auth.util';
|
||||||
|
import { AdminJobsService } from './admin-jobs.service';
|
||||||
|
|
||||||
|
@ApiTags('admin')
|
||||||
|
@Controller('admin/jobs')
|
||||||
|
@UseGuards(SessionGuard)
|
||||||
|
export class AdminJobsController {
|
||||||
|
constructor(private readonly jobs: AdminJobsService) {}
|
||||||
|
|
||||||
|
@Get()
|
||||||
|
@ApiOperation({ summary: 'List background jobs' })
|
||||||
|
list(
|
||||||
|
@CurrentUser() user: { roles: string[] },
|
||||||
|
@Query('limit') limit?: string,
|
||||||
|
@Query('status') status?: string,
|
||||||
|
@Query('queue') queue?: string,
|
||||||
|
) {
|
||||||
|
assertSuperAdmin(user.roles);
|
||||||
|
return this.jobs.listJobs({
|
||||||
|
limit: limit ? Number(limit) : undefined,
|
||||||
|
status,
|
||||||
|
queue,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
47
apps/api/src/admin/admin-jobs.service.ts
Normal file
47
apps/api/src/admin/admin-jobs.service.ts
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
import { Injectable } from '@nestjs/common';
|
||||||
|
|
||||||
|
import { PrismaService } from '../prisma/prisma.service';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class AdminJobsService {
|
||||||
|
constructor(private readonly prisma: PrismaService) {}
|
||||||
|
|
||||||
|
async listJobs(query: { limit?: number; status?: string; queue?: string }): Promise<{
|
||||||
|
jobs: Array<{
|
||||||
|
id: string;
|
||||||
|
queue: string;
|
||||||
|
jobName: string;
|
||||||
|
status: string;
|
||||||
|
attempts: number;
|
||||||
|
error: string | null;
|
||||||
|
startedAt: string | null;
|
||||||
|
completedAt: string | null;
|
||||||
|
createdAt: string;
|
||||||
|
}>;
|
||||||
|
}> {
|
||||||
|
const limit = Math.min(query.limit ?? 50, 100);
|
||||||
|
|
||||||
|
const jobs = await this.prisma.jobRecord.findMany({
|
||||||
|
where: {
|
||||||
|
...(query.status ? { status: query.status as never } : {}),
|
||||||
|
...(query.queue ? { queue: query.queue } : {}),
|
||||||
|
},
|
||||||
|
orderBy: { createdAt: 'desc' },
|
||||||
|
take: limit,
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
jobs: jobs.map((job) => ({
|
||||||
|
id: job.id,
|
||||||
|
queue: job.queue,
|
||||||
|
jobName: job.jobName,
|
||||||
|
status: job.status,
|
||||||
|
attempts: job.attempts,
|
||||||
|
error: job.error,
|
||||||
|
startedAt: job.startedAt?.toISOString() ?? null,
|
||||||
|
completedAt: job.completedAt?.toISOString() ?? null,
|
||||||
|
createdAt: job.createdAt.toISOString(),
|
||||||
|
})),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
57
apps/api/src/admin/admin-users.controller.ts
Normal file
57
apps/api/src/admin/admin-users.controller.ts
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
import {
|
||||||
|
Controller,
|
||||||
|
Get,
|
||||||
|
Param,
|
||||||
|
ParseUUIDPipe,
|
||||||
|
Post,
|
||||||
|
Query,
|
||||||
|
UseGuards,
|
||||||
|
} from '@nestjs/common';
|
||||||
|
import { ApiOperation, ApiTags } from '@nestjs/swagger';
|
||||||
|
|
||||||
|
import { CurrentUser } from '../auth/decorators/current-user.decorator';
|
||||||
|
import { SessionGuard } from '../auth/guards/session.guard';
|
||||||
|
|
||||||
|
import { assertSuperAdmin } from './admin-auth.util';
|
||||||
|
import { AdminUsersService } from './admin-users.service';
|
||||||
|
|
||||||
|
@ApiTags('admin')
|
||||||
|
@Controller('admin/users')
|
||||||
|
@UseGuards(SessionGuard)
|
||||||
|
export class AdminUsersController {
|
||||||
|
constructor(private readonly users: AdminUsersService) {}
|
||||||
|
|
||||||
|
@Get()
|
||||||
|
@ApiOperation({ summary: 'Search users' })
|
||||||
|
search(
|
||||||
|
@CurrentUser() user: { roles: string[] },
|
||||||
|
@Query('q') q?: string,
|
||||||
|
@Query('limit') limit?: string,
|
||||||
|
) {
|
||||||
|
assertSuperAdmin(user.roles);
|
||||||
|
return this.users.searchUsers({
|
||||||
|
q,
|
||||||
|
limit: limit ? Number(limit) : undefined,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Post(':id/suspend')
|
||||||
|
@ApiOperation({ summary: 'Suspend a user account' })
|
||||||
|
suspend(
|
||||||
|
@CurrentUser() user: { roles: string[] },
|
||||||
|
@Param('id', ParseUUIDPipe) id: string,
|
||||||
|
) {
|
||||||
|
assertSuperAdmin(user.roles);
|
||||||
|
return this.users.suspendUser(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Post(':id/unsuspend')
|
||||||
|
@ApiOperation({ summary: 'Unsuspend a user account' })
|
||||||
|
unsuspend(
|
||||||
|
@CurrentUser() user: { roles: string[] },
|
||||||
|
@Param('id', ParseUUIDPipe) id: string,
|
||||||
|
) {
|
||||||
|
assertSuperAdmin(user.roles);
|
||||||
|
return this.users.unsuspendUser(id);
|
||||||
|
}
|
||||||
|
}
|
||||||
77
apps/api/src/admin/admin-users.service.ts
Normal file
77
apps/api/src/admin/admin-users.service.ts
Normal file
@@ -0,0 +1,77 @@
|
|||||||
|
import { Injectable } from '@nestjs/common';
|
||||||
|
|
||||||
|
import { PrismaService } from '../prisma/prisma.service';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class AdminUsersService {
|
||||||
|
constructor(private readonly prisma: PrismaService) {}
|
||||||
|
|
||||||
|
async searchUsers(query: { q?: string; limit?: number }): Promise<{
|
||||||
|
users: Array<{
|
||||||
|
id: string;
|
||||||
|
email: string;
|
||||||
|
username: string;
|
||||||
|
displayName: string | null;
|
||||||
|
status: string;
|
||||||
|
roles: string[];
|
||||||
|
twoFactorEnabled: boolean;
|
||||||
|
sessionCount: number;
|
||||||
|
serverCount: number;
|
||||||
|
createdAt: string;
|
||||||
|
}>;
|
||||||
|
}> {
|
||||||
|
const limit = Math.min(query.limit ?? 25, 100);
|
||||||
|
const q = query.q?.trim();
|
||||||
|
|
||||||
|
const users = await this.prisma.user.findMany({
|
||||||
|
where: q
|
||||||
|
? {
|
||||||
|
OR: [
|
||||||
|
{ email: { contains: q, mode: 'insensitive' } },
|
||||||
|
{ username: { contains: q, mode: 'insensitive' } },
|
||||||
|
],
|
||||||
|
}
|
||||||
|
: undefined,
|
||||||
|
orderBy: { createdAt: 'desc' },
|
||||||
|
take: limit,
|
||||||
|
include: {
|
||||||
|
platformRoles: {
|
||||||
|
include: { role: true },
|
||||||
|
},
|
||||||
|
totpCredential: { select: { enabledAt: true } },
|
||||||
|
_count: { select: { sessions: true, gameServers: true } },
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
users: users.map((user) => ({
|
||||||
|
id: user.id,
|
||||||
|
email: user.email,
|
||||||
|
username: user.username,
|
||||||
|
displayName: user.displayName,
|
||||||
|
status: user.status,
|
||||||
|
roles: user.platformRoles.map((entry) => entry.role.name),
|
||||||
|
twoFactorEnabled: Boolean(user.totpCredential?.enabledAt),
|
||||||
|
sessionCount: user._count.sessions,
|
||||||
|
serverCount: user._count.gameServers,
|
||||||
|
createdAt: user.createdAt.toISOString(),
|
||||||
|
})),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
async suspendUser(userId: string, reason?: string): Promise<{ id: string; status: string }> {
|
||||||
|
return this.prisma.user.update({
|
||||||
|
where: { id: userId },
|
||||||
|
data: { status: 'SUSPENDED' },
|
||||||
|
select: { id: true, status: true },
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async unsuspendUser(userId: string): Promise<{ id: string; status: string }> {
|
||||||
|
return this.prisma.user.update({
|
||||||
|
where: { id: userId },
|
||||||
|
data: { status: 'ACTIVE', failedLoginAttempts: 0, lockedUntil: null },
|
||||||
|
select: { id: true, status: true },
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,13 +1,36 @@
|
|||||||
import { Module } from '@nestjs/common';
|
import { Module } from '@nestjs/common';
|
||||||
|
|
||||||
|
import { AbuseModule } from '../abuse/abuse.module';
|
||||||
import { AuthModule } from '../auth/auth.module';
|
import { AuthModule } from '../auth/auth.module';
|
||||||
|
|
||||||
|
import { AdminAbuseController } from './admin-abuse.controller';
|
||||||
|
import { AdminAuditController } from './admin-audit.controller';
|
||||||
|
import { AdminAuditService } from './admin-audit.service';
|
||||||
|
import { AdminDashboardController } from './admin-dashboard.controller';
|
||||||
|
import { AdminDashboardService } from './admin-dashboard.service';
|
||||||
|
import { AdminJobsController } from './admin-jobs.controller';
|
||||||
|
import { AdminJobsService } from './admin-jobs.service';
|
||||||
import { AdminNodesController } from './admin-nodes.controller';
|
import { AdminNodesController } from './admin-nodes.controller';
|
||||||
import { AdminNodesService } from './admin-nodes.service';
|
import { AdminNodesService } from './admin-nodes.service';
|
||||||
|
import { AdminUsersController } from './admin-users.controller';
|
||||||
|
import { AdminUsersService } from './admin-users.service';
|
||||||
|
|
||||||
@Module({
|
@Module({
|
||||||
imports: [AuthModule],
|
imports: [AuthModule, AbuseModule],
|
||||||
controllers: [AdminNodesController],
|
controllers: [
|
||||||
providers: [AdminNodesService],
|
AdminNodesController,
|
||||||
|
AdminDashboardController,
|
||||||
|
AdminAuditController,
|
||||||
|
AdminJobsController,
|
||||||
|
AdminUsersController,
|
||||||
|
AdminAbuseController,
|
||||||
|
],
|
||||||
|
providers: [
|
||||||
|
AdminNodesService,
|
||||||
|
AdminDashboardService,
|
||||||
|
AdminAuditService,
|
||||||
|
AdminJobsService,
|
||||||
|
AdminUsersService,
|
||||||
|
],
|
||||||
})
|
})
|
||||||
export class AdminModule {}
|
export class AdminModule {}
|
||||||
|
|||||||
@@ -1,5 +1,11 @@
|
|||||||
import { MiddlewareConsumer, Module, NestModule } from '@nestjs/common';
|
import { MiddlewareConsumer, Module, NestModule } from '@nestjs/common';
|
||||||
|
|
||||||
|
import { AbuseModule } from './abuse/abuse.module';
|
||||||
|
import { ComplianceModule } from './compliance/compliance.module';
|
||||||
|
import { NotificationsModule } from './notifications/notifications.module';
|
||||||
|
import { FeatureFlagsModule } from './feature-flags/feature-flags.module';
|
||||||
|
import { OrganizationsModule } from './organizations/organizations.module';
|
||||||
|
import { SupportModule } from './support/support.module';
|
||||||
import { RequestIdMiddleware } from './common/middleware/request-id.middleware';
|
import { RequestIdMiddleware } from './common/middleware/request-id.middleware';
|
||||||
import { HealthModule } from './health/health.module';
|
import { HealthModule } from './health/health.module';
|
||||||
import { PrismaModule } from './prisma/prisma.module';
|
import { PrismaModule } from './prisma/prisma.module';
|
||||||
@@ -13,10 +19,14 @@ import { EdgeModule } from './edge/edge.module';
|
|||||||
import { ServersModule } from './servers/servers.module';
|
import { ServersModule } from './servers/servers.module';
|
||||||
import { AppConfigModule } from './config/app-config.module';
|
import { AppConfigModule } from './config/app-config.module';
|
||||||
|
|
||||||
import { APP_GUARD } from '@nestjs/core';
|
import { APP_GUARD, APP_INTERCEPTOR } from '@nestjs/core';
|
||||||
import { ThrottlerGuard, ThrottlerModule } from '@nestjs/throttler';
|
import { ThrottlerGuard, ThrottlerModule } from '@nestjs/throttler';
|
||||||
import { LoggerModule } from 'nestjs-pino';
|
import { LoggerModule } from 'nestjs-pino';
|
||||||
|
|
||||||
|
import { CsrfModule } from './common/csrf/csrf.module';
|
||||||
|
import { CsrfGuard } from './common/csrf/csrf.guard';
|
||||||
|
import { IdempotencyInterceptor } from './common/interceptors/idempotency.interceptor';
|
||||||
|
|
||||||
@Module({
|
@Module({
|
||||||
imports: [
|
imports: [
|
||||||
LoggerModule.forRoot({
|
LoggerModule.forRoot({
|
||||||
@@ -51,12 +61,27 @@ import { LoggerModule } from 'nestjs-pino';
|
|||||||
ServersModule,
|
ServersModule,
|
||||||
NodesModule,
|
NodesModule,
|
||||||
AdminModule,
|
AdminModule,
|
||||||
|
NotificationsModule,
|
||||||
|
ComplianceModule,
|
||||||
|
FeatureFlagsModule,
|
||||||
|
OrganizationsModule,
|
||||||
|
SupportModule,
|
||||||
|
AbuseModule,
|
||||||
|
CsrfModule,
|
||||||
],
|
],
|
||||||
providers: [
|
providers: [
|
||||||
{
|
{
|
||||||
provide: APP_GUARD,
|
provide: APP_GUARD,
|
||||||
useClass: ThrottlerGuard,
|
useClass: ThrottlerGuard,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
provide: APP_GUARD,
|
||||||
|
useClass: CsrfGuard,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
provide: APP_INTERCEPTOR,
|
||||||
|
useClass: IdempotencyInterceptor,
|
||||||
|
},
|
||||||
],
|
],
|
||||||
})
|
})
|
||||||
export class AppModule implements NestModule {
|
export class AppModule implements NestModule {
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import {
|
|||||||
BadRequestException,
|
BadRequestException,
|
||||||
Body,
|
Body,
|
||||||
Controller,
|
Controller,
|
||||||
|
Get,
|
||||||
HttpCode,
|
HttpCode,
|
||||||
HttpStatus,
|
HttpStatus,
|
||||||
Post,
|
Post,
|
||||||
@@ -221,4 +222,35 @@ export class AuthController {
|
|||||||
request.ip,
|
request.ip,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Public()
|
||||||
|
@Get('oidc/login')
|
||||||
|
@ApiOperation({ summary: 'Redirect to OIDC provider (optional)' })
|
||||||
|
oidcLogin(@Res() reply: FastifyReply) {
|
||||||
|
const issuer = process.env['OIDC_ISSUER'];
|
||||||
|
const clientId = process.env['OIDC_CLIENT_ID'];
|
||||||
|
const redirectUri = process.env['OIDC_REDIRECT_URI'];
|
||||||
|
if (!issuer || !clientId || !redirectUri) {
|
||||||
|
throw new BadRequestException('OIDC is not configured');
|
||||||
|
}
|
||||||
|
const url = new URL(`${issuer.replace(/\/$/, '')}/authorize`);
|
||||||
|
url.searchParams.set('client_id', clientId);
|
||||||
|
url.searchParams.set('redirect_uri', redirectUri);
|
||||||
|
url.searchParams.set('response_type', 'code');
|
||||||
|
url.searchParams.set('scope', 'openid email profile');
|
||||||
|
return reply.redirect(url.toString(), 302);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Public()
|
||||||
|
@Get('oidc/callback')
|
||||||
|
@ApiOperation({ summary: 'OIDC callback (optional)' })
|
||||||
|
async oidcCallback(
|
||||||
|
@Query('code') code: string | undefined,
|
||||||
|
@Res() reply: FastifyReply,
|
||||||
|
) {
|
||||||
|
if (!code) {
|
||||||
|
throw new BadRequestException('Missing authorization code');
|
||||||
|
}
|
||||||
|
return this.authService.completeOidcLogin(code, reply);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { Module } from '@nestjs/common';
|
import { forwardRef, Module } from '@nestjs/common';
|
||||||
import { Queue } from 'bullmq';
|
import { Queue } from 'bullmq';
|
||||||
import Redis from 'ioredis';
|
import Redis from 'ioredis';
|
||||||
|
|
||||||
@@ -20,7 +20,7 @@ import { SessionService } from './session.service';
|
|||||||
import { SsoService } from './sso.service';
|
import { SsoService } from './sso.service';
|
||||||
|
|
||||||
@Module({
|
@Module({
|
||||||
imports: [AppConfigModule, BillingModule],
|
imports: [AppConfigModule, forwardRef(() => BillingModule)],
|
||||||
controllers: [AuthController, MeController],
|
controllers: [AuthController, MeController],
|
||||||
providers: [
|
providers: [
|
||||||
AuthService,
|
AuthService,
|
||||||
@@ -50,6 +50,13 @@ import { SsoService } from './sso.service';
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
exports: [AuthService, SessionService, SessionGuard, AuditService, SsoService],
|
exports: [
|
||||||
|
AuthService,
|
||||||
|
SessionService,
|
||||||
|
SessionGuard,
|
||||||
|
AuditService,
|
||||||
|
SsoService,
|
||||||
|
NOTIFICATIONS_QUEUE,
|
||||||
|
],
|
||||||
})
|
})
|
||||||
export class AuthModule {}
|
export class AuthModule {}
|
||||||
|
|||||||
@@ -712,4 +712,77 @@ export class AuthService {
|
|||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async completeOidcLogin(code: string, reply: FastifyReply): Promise<void> {
|
||||||
|
const issuer = process.env['OIDC_ISSUER'];
|
||||||
|
const clientId = process.env['OIDC_CLIENT_ID'];
|
||||||
|
const clientSecret = process.env['OIDC_CLIENT_SECRET'];
|
||||||
|
const redirectUri = process.env['OIDC_REDIRECT_URI'];
|
||||||
|
if (!issuer || !clientId || !clientSecret || !redirectUri) {
|
||||||
|
throw new BadRequestException('OIDC is not configured');
|
||||||
|
}
|
||||||
|
|
||||||
|
const tokenResponse = await fetch(`${issuer.replace(/\/$/, '')}/token`, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
|
||||||
|
body: new URLSearchParams({
|
||||||
|
grant_type: 'authorization_code',
|
||||||
|
code,
|
||||||
|
redirect_uri: redirectUri,
|
||||||
|
client_id: clientId,
|
||||||
|
client_secret: clientSecret,
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!tokenResponse.ok) {
|
||||||
|
throw new UnauthorizedException('OIDC token exchange failed');
|
||||||
|
}
|
||||||
|
|
||||||
|
const tokenJson = (await tokenResponse.json()) as { access_token?: string };
|
||||||
|
if (!tokenJson.access_token) {
|
||||||
|
throw new UnauthorizedException('OIDC token missing');
|
||||||
|
}
|
||||||
|
|
||||||
|
const userInfoResponse = await fetch(`${issuer.replace(/\/$/, '')}/userinfo`, {
|
||||||
|
headers: { Authorization: `Bearer ${tokenJson.access_token}` },
|
||||||
|
});
|
||||||
|
if (!userInfoResponse.ok) {
|
||||||
|
throw new UnauthorizedException('OIDC userinfo failed');
|
||||||
|
}
|
||||||
|
|
||||||
|
const profile = (await userInfoResponse.json()) as {
|
||||||
|
email?: string;
|
||||||
|
sub?: string;
|
||||||
|
name?: string;
|
||||||
|
};
|
||||||
|
if (!profile.email) {
|
||||||
|
throw new BadRequestException('OIDC profile has no email');
|
||||||
|
}
|
||||||
|
|
||||||
|
let user = await this.prisma.user.findUnique({
|
||||||
|
where: { email: profile.email },
|
||||||
|
});
|
||||||
|
if (!user) {
|
||||||
|
const username = generateUsernameFromEmail(profile.email);
|
||||||
|
const passwordHash = await hashPassword(generateSecureToken(32));
|
||||||
|
user = await this.prisma.$transaction(async (tx) => {
|
||||||
|
const created = await tx.user.create({
|
||||||
|
data: {
|
||||||
|
email: profile.email!,
|
||||||
|
username,
|
||||||
|
displayName: profile.name ?? username,
|
||||||
|
emailVerifiedAt: new Date(),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
await tx.userCredential.create({
|
||||||
|
data: { userId: created.id, passwordHash },
|
||||||
|
});
|
||||||
|
return created;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const { token, expiresAt } = await this.sessionService.createSession(user.id);
|
||||||
|
this.sessionService.setSessionCookie(reply, token, expiresAt);
|
||||||
|
reply.redirect(`${this.config.APP_URL}/dashboard`, 302);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { Module } from '@nestjs/common';
|
import { forwardRef, Module } from '@nestjs/common';
|
||||||
|
|
||||||
|
import { AuthModule } from '../auth/auth.module';
|
||||||
import { PrismaModule } from '../prisma/prisma.module';
|
import { PrismaModule } from '../prisma/prisma.module';
|
||||||
|
|
||||||
import { BillingController } from './billing.controller';
|
import { BillingController } from './billing.controller';
|
||||||
@@ -8,7 +9,7 @@ import { StripeWebhookController } from './stripe/stripe-webhook.controller';
|
|||||||
import { StripeWebhookService } from './stripe/stripe-webhook.service';
|
import { StripeWebhookService } from './stripe/stripe-webhook.service';
|
||||||
|
|
||||||
@Module({
|
@Module({
|
||||||
imports: [PrismaModule],
|
imports: [PrismaModule, forwardRef(() => AuthModule)],
|
||||||
controllers: [BillingController, StripeWebhookController],
|
controllers: [BillingController, StripeWebhookController],
|
||||||
providers: [BillingService, StripeWebhookService],
|
providers: [BillingService, StripeWebhookService],
|
||||||
exports: [BillingService],
|
exports: [BillingService],
|
||||||
|
|||||||
@@ -8,6 +8,19 @@ import { PrismaService } from '../prisma/prisma.service';
|
|||||||
|
|
||||||
import { CatalogService } from './catalog.service';
|
import { CatalogService } from './catalog.service';
|
||||||
|
|
||||||
|
const SOFTWARE_FAMILIES = [
|
||||||
|
'VANILLA',
|
||||||
|
'PAPER',
|
||||||
|
'PURPUR',
|
||||||
|
'FABRIC',
|
||||||
|
'FORGE',
|
||||||
|
'NEOFORGE',
|
||||||
|
'QUILT',
|
||||||
|
'BEDROCK_DEDICATED',
|
||||||
|
] as const;
|
||||||
|
|
||||||
|
const MINECRAFT_VERSIONS = ['1.21.1', '1.21', '1.20.6', '1.20.4'];
|
||||||
|
|
||||||
@ApiTags('catalog')
|
@ApiTags('catalog')
|
||||||
@Controller('catalog')
|
@Controller('catalog')
|
||||||
@UseGuards(SessionGuard)
|
@UseGuards(SessionGuard)
|
||||||
@@ -17,6 +30,55 @@ export class CatalogController {
|
|||||||
private readonly prisma: PrismaService,
|
private readonly prisma: PrismaService,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
|
@Get('software')
|
||||||
|
@ApiOperation({ summary: 'List supported server software families' })
|
||||||
|
listSoftware() {
|
||||||
|
return {
|
||||||
|
families: SOFTWARE_FAMILIES.map((family) => ({ id: family, name: family })),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
@Get('versions')
|
||||||
|
@ApiOperation({ summary: 'List supported Minecraft versions' })
|
||||||
|
async listVersions(@Query('edition') edition?: string) {
|
||||||
|
const dbVersions = await this.prisma.minecraftVersion.findMany({
|
||||||
|
where: edition ? { edition: edition as 'JAVA' | 'BEDROCK', isActive: true } : { isActive: true },
|
||||||
|
orderBy: { version: 'desc' },
|
||||||
|
});
|
||||||
|
|
||||||
|
if (dbVersions.length > 0) {
|
||||||
|
return { versions: dbVersions.map((entry) => entry.version) };
|
||||||
|
}
|
||||||
|
|
||||||
|
return { versions: MINECRAFT_VERSIONS };
|
||||||
|
}
|
||||||
|
|
||||||
|
@Get('regions')
|
||||||
|
@ApiOperation({ summary: 'List available deployment regions' })
|
||||||
|
async listRegions(): Promise<{
|
||||||
|
regions: Array<{
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
hostname: string;
|
||||||
|
status: string;
|
||||||
|
}>;
|
||||||
|
}> {
|
||||||
|
const nodes = await this.prisma.gameNode.findMany({
|
||||||
|
where: { status: { in: ['ONLINE', 'OFFLINE'] } },
|
||||||
|
select: { id: true, name: true, hostname: true, status: true },
|
||||||
|
orderBy: { name: 'asc' },
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
regions: nodes.map((node) => ({
|
||||||
|
id: node.id,
|
||||||
|
name: node.name,
|
||||||
|
hostname: node.hostname,
|
||||||
|
status: node.status,
|
||||||
|
})),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
@Get('projects')
|
@Get('projects')
|
||||||
@ApiOperation({ summary: 'Search Modrinth projects' })
|
@ApiOperation({ summary: 'Search Modrinth projects' })
|
||||||
search(@Query() query: Record<string, string | undefined>) {
|
search(@Query() query: Record<string, string | undefined>) {
|
||||||
@@ -64,4 +126,17 @@ export class CatalogController {
|
|||||||
resolvedSoftwareFamily as 'PAPER' | 'FABRIC' | 'PURPUR' | 'FORGE' | 'NEOFORGE' | 'QUILT',
|
resolvedSoftwareFamily as 'PAPER' | 'FABRIC' | 'PURPUR' | 'FORGE' | 'NEOFORGE' | 'QUILT',
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Get('curseforge/projects')
|
||||||
|
@ApiOperation({ summary: 'Search CurseForge mods (requires API key)' })
|
||||||
|
searchCurseForge(@Query('q') q?: string, @Query('limit') limit?: string) {
|
||||||
|
if (!q?.trim()) {
|
||||||
|
throw new BadRequestException('q is required');
|
||||||
|
}
|
||||||
|
const parsedLimit = limit ? Number.parseInt(limit, 10) : 20;
|
||||||
|
return this.catalogService.searchCurseForge({
|
||||||
|
query: q.trim(),
|
||||||
|
limit: Number.isFinite(parsedLimit) ? parsedLimit : 20,
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import {
|
|||||||
isVersionCompatible,
|
isVersionCompatible,
|
||||||
resolveModrinthLoader,
|
resolveModrinthLoader,
|
||||||
resolveProjectType,
|
resolveProjectType,
|
||||||
|
searchCurseForgeMods,
|
||||||
searchModrinthProjects,
|
searchModrinthProjects,
|
||||||
type SoftwareFamily,
|
type SoftwareFamily,
|
||||||
} from '@hexahost/catalog';
|
} from '@hexahost/catalog';
|
||||||
@@ -78,4 +79,26 @@ export class CatalogService {
|
|||||||
})),
|
})),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async searchCurseForge(input: { query: string; limit?: number }) {
|
||||||
|
try {
|
||||||
|
const result = await searchCurseForgeMods({
|
||||||
|
searchFilter: input.query,
|
||||||
|
pageSize: input.limit ?? 20,
|
||||||
|
});
|
||||||
|
return {
|
||||||
|
provider: 'curseforge' as const,
|
||||||
|
projects: result.data.map((project) => ({
|
||||||
|
id: String(project.id),
|
||||||
|
slug: project.slug,
|
||||||
|
name: project.name,
|
||||||
|
description: project.summary,
|
||||||
|
downloads: project.downloadCount,
|
||||||
|
iconUrl: project.logo?.thumbnailUrl ?? null,
|
||||||
|
})),
|
||||||
|
};
|
||||||
|
} catch {
|
||||||
|
throw new BadRequestException('CurseForge integration is not configured');
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user