Compare commits

...

4 Commits

Author SHA1 Message Date
TheOnlyMace
50cd4b3ffd Enhance API with OIDC support, including login and callback endpoints. Update environment variables for OIDC configuration in .env.example. Add new features to the catalog service for listing software families, Minecraft versions, and deployment regions. Implement server management actions such as kill, delete, and update in the servers module. Integrate feature flags for maintenance mode in server operations. Update pnpm-lock.yaml with new dependencies and versions.
Some checks failed
CI / Node — lint, typecheck, test, build (push) Failing after 12s
CI / Go — node-agent tests (push) Failing after 9s
CI / Go — edge-gateway build (push) Successful in 17s
2026-07-05 18:39:53 +02:00
TheOnlyMace
bf36cb3159 Add '@hexahost/contracts' dependency in package.json, export emailSchema from contracts, and update bootstrap-admin script to validate email input. Update README with example email. Enhance pnpm-lock.yaml for workspace integration.
Some checks failed
CI / Node — lint, typecheck, test, build (push) Failing after 10s
CI / Go — node-agent tests (push) Failing after 8s
CI / Go — edge-gateway build (push) Successful in 18s
2026-07-05 14:43:52 +02:00
TheOnlyMace
2126e7bebf Refactor environment variable handling in package.json scripts, update .env.example for consistency, and enhance Turbo configuration for environment variable passthrough. Modify API content type parsing and resolve circular dependencies in Auth and Billing modules. Add unique username generation logic in bootstrap-admin script.
Some checks failed
CI / Node — lint, typecheck, test, build (push) Failing after 9s
CI / Go — node-agent tests (push) Failing after 8s
CI / Go — edge-gateway build (push) Successful in 19s
2026-07-05 14:40:57 +02:00
TheOnlyMace
20e044d32b Remove TypeScript build info files from multiple packages to streamline project structure and reduce clutter. 2026-07-05 14:40:45 +02:00
319 changed files with 18081 additions and 457 deletions

View File

@@ -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
View File

@@ -7,3 +7,4 @@ dist/
bin/ bin/
*.log *.log
data/ data/
.logs/

View 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"]}

View File

@@ -0,0 +1 @@
{"hash":"083c8105a3916fd9","duration":2162,"sha":"27a3ec8244e70774e6f4673201397e2302c32dfe","dirty_hash":"abc4a2c6304f10abbf2d6c628ed6a63dbf9e6ca06fecaaea2935e4ea7f4ff660"}

BIN
.turbo/cache/083c8105a3916fd9.tar.zst vendored Normal file

Binary file not shown.

View 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"]}

View File

@@ -0,0 +1 @@
{"hash":"0b354b78644f8aad","duration":3330,"sha":"27a3ec8244e70774e6f4673201397e2302c32dfe","dirty_hash":"67d44d689fa95ed97ad8bb0d3e753762649434cb9a64ff731786cdc979515618"}

BIN
.turbo/cache/0b354b78644f8aad.tar.zst vendored Normal file

Binary file not shown.

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1 @@
{"hash":"1a030577ec3e46e3","duration":1256,"sha":"27a3ec8244e70774e6f4673201397e2302c32dfe","dirty_hash":"67d44d689fa95ed97ad8bb0d3e753762649434cb9a64ff731786cdc979515618"}

BIN
.turbo/cache/1a030577ec3e46e3.tar.zst vendored Normal file

Binary file not shown.

View 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"]}

View File

@@ -0,0 +1 @@
{"hash":"1b4ddb8b5e9b564a","duration":2992,"sha":"2126e7bebf9ff0796ac77f8548cba399f45e759a","dirty_hash":null}

BIN
.turbo/cache/1b4ddb8b5e9b564a.tar.zst vendored Normal file

Binary file not shown.

View 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"]}

View File

@@ -0,0 +1 @@
{"hash":"3bace58668d8c69c","duration":1783,"sha":"27a3ec8244e70774e6f4673201397e2302c32dfe","dirty_hash":"67d44d689fa95ed97ad8bb0d3e753762649434cb9a64ff731786cdc979515618"}

BIN
.turbo/cache/3bace58668d8c69c.tar.zst vendored Normal file

Binary file not shown.

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1 @@
{"hash":"51664cdd00a0048d","duration":2814,"sha":"27a3ec8244e70774e6f4673201397e2302c32dfe","dirty_hash":"abc4a2c6304f10abbf2d6c628ed6a63dbf9e6ca06fecaaea2935e4ea7f4ff660"}

BIN
.turbo/cache/51664cdd00a0048d.tar.zst vendored Normal file

Binary file not shown.

View 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"]}

View File

@@ -0,0 +1 @@
{"hash":"53573aa3334db733","duration":2278,"sha":"27a3ec8244e70774e6f4673201397e2302c32dfe","dirty_hash":"abc4a2c6304f10abbf2d6c628ed6a63dbf9e6ca06fecaaea2935e4ea7f4ff660"}

BIN
.turbo/cache/53573aa3334db733.tar.zst vendored Normal file

Binary file not shown.

View 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"]}

View File

@@ -0,0 +1 @@
{"hash":"7138297b16f09c78","duration":2780,"sha":"27a3ec8244e70774e6f4673201397e2302c32dfe","dirty_hash":"abc4a2c6304f10abbf2d6c628ed6a63dbf9e6ca06fecaaea2935e4ea7f4ff660"}

BIN
.turbo/cache/7138297b16f09c78.tar.zst vendored Normal file

Binary file not shown.

View 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"]}

View File

@@ -0,0 +1 @@
{"hash":"7c83d574699fc347","duration":1442,"sha":"27a3ec8244e70774e6f4673201397e2302c32dfe","dirty_hash":"67d44d689fa95ed97ad8bb0d3e753762649434cb9a64ff731786cdc979515618"}

BIN
.turbo/cache/7c83d574699fc347.tar.zst vendored Normal file

Binary file not shown.

View 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"]}

View File

@@ -0,0 +1 @@
{"hash":"7ec080769e22b52e","duration":2294,"sha":"27a3ec8244e70774e6f4673201397e2302c32dfe","dirty_hash":"abc4a2c6304f10abbf2d6c628ed6a63dbf9e6ca06fecaaea2935e4ea7f4ff660"}

BIN
.turbo/cache/7ec080769e22b52e.tar.zst vendored Normal file

Binary file not shown.

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1 @@
{"hash":"83bc945bf697348a","duration":17907,"sha":"27a3ec8244e70774e6f4673201397e2302c32dfe","dirty_hash":"67d44d689fa95ed97ad8bb0d3e753762649434cb9a64ff731786cdc979515618"}

BIN
.turbo/cache/83bc945bf697348a.tar.zst vendored Normal file

Binary file not shown.

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1 @@
{"hash":"8c5cbe4308e26227","duration":1073,"sha":"27a3ec8244e70774e6f4673201397e2302c32dfe","dirty_hash":"abc4a2c6304f10abbf2d6c628ed6a63dbf9e6ca06fecaaea2935e4ea7f4ff660"}

BIN
.turbo/cache/8c5cbe4308e26227.tar.zst vendored Normal file

Binary file not shown.

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1 @@
{"hash":"8de6070230e6cb7f","duration":1207,"sha":"2126e7bebf9ff0796ac77f8548cba399f45e759a","dirty_hash":null}

BIN
.turbo/cache/8de6070230e6cb7f.tar.zst vendored Normal file

Binary file not shown.

View 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"]}

View File

@@ -0,0 +1 @@
{"hash":"95e8fc4f72d7869c","duration":1541,"sha":"27a3ec8244e70774e6f4673201397e2302c32dfe","dirty_hash":"67d44d689fa95ed97ad8bb0d3e753762649434cb9a64ff731786cdc979515618"}

BIN
.turbo/cache/95e8fc4f72d7869c.tar.zst vendored Normal file

Binary file not shown.

View 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"]}

View File

@@ -0,0 +1 @@
{"hash":"a2830d8f7ca0f71c","duration":1674,"sha":"27a3ec8244e70774e6f4673201397e2302c32dfe","dirty_hash":"67d44d689fa95ed97ad8bb0d3e753762649434cb9a64ff731786cdc979515618"}

BIN
.turbo/cache/a2830d8f7ca0f71c.tar.zst vendored Normal file

Binary file not shown.

View 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"]}

View File

@@ -0,0 +1 @@
{"hash":"a30020bd73a840d7","duration":2209,"sha":"27a3ec8244e70774e6f4673201397e2302c32dfe","dirty_hash":"67d44d689fa95ed97ad8bb0d3e753762649434cb9a64ff731786cdc979515618"}

BIN
.turbo/cache/a30020bd73a840d7.tar.zst vendored Normal file

Binary file not shown.

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1 @@
{"hash":"a480ca24eeac5ad4","duration":2232,"sha":"27a3ec8244e70774e6f4673201397e2302c32dfe","dirty_hash":"67d44d689fa95ed97ad8bb0d3e753762649434cb9a64ff731786cdc979515618"}

BIN
.turbo/cache/a480ca24eeac5ad4.tar.zst vendored Normal file

Binary file not shown.

View 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"]}

View File

@@ -0,0 +1 @@
{"hash":"b85ca2f9ca81bcb3","duration":1721,"sha":"27a3ec8244e70774e6f4673201397e2302c32dfe","dirty_hash":"67d44d689fa95ed97ad8bb0d3e753762649434cb9a64ff731786cdc979515618"}

BIN
.turbo/cache/b85ca2f9ca81bcb3.tar.zst vendored Normal file

Binary file not shown.

View 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"]}

View File

@@ -0,0 +1 @@
{"hash":"bc1b6c014f0a15b7","duration":2395,"sha":"27a3ec8244e70774e6f4673201397e2302c32dfe","dirty_hash":"abc4a2c6304f10abbf2d6c628ed6a63dbf9e6ca06fecaaea2935e4ea7f4ff660"}

BIN
.turbo/cache/bc1b6c014f0a15b7.tar.zst vendored Normal file

Binary file not shown.

View 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"]}

View File

@@ -0,0 +1 @@
{"hash":"d12676556f73d54d","duration":3058,"sha":"27a3ec8244e70774e6f4673201397e2302c32dfe","dirty_hash":"abc4a2c6304f10abbf2d6c628ed6a63dbf9e6ca06fecaaea2935e4ea7f4ff660"}

BIN
.turbo/cache/d12676556f73d54d.tar.zst vendored Normal file

Binary file not shown.

View 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"]}

View File

@@ -0,0 +1 @@
{"hash":"d572575f47998158","duration":1932,"sha":"27a3ec8244e70774e6f4673201397e2302c32dfe","dirty_hash":"abc4a2c6304f10abbf2d6c628ed6a63dbf9e6ca06fecaaea2935e4ea7f4ff660"}

BIN
.turbo/cache/d572575f47998158.tar.zst vendored Normal file

Binary file not shown.

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1 @@
{"hash":"e57573f49057e7f1","duration":4583,"sha":"27a3ec8244e70774e6f4673201397e2302c32dfe","dirty_hash":"67d44d689fa95ed97ad8bb0d3e753762649434cb9a64ff731786cdc979515618"}

BIN
.turbo/cache/e57573f49057e7f1.tar.zst vendored Normal file

Binary file not shown.

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1 @@
{"hash":"f065eda65a09cf0d","duration":2291,"sha":"27a3ec8244e70774e6f4673201397e2302c32dfe","dirty_hash":"67d44d689fa95ed97ad8bb0d3e753762649434cb9a64ff731786cdc979515618"}

BIN
.turbo/cache/f065eda65a09cf0d.tar.zst vendored Normal file

Binary file not shown.

View 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"]}

View File

@@ -0,0 +1 @@
{"hash":"f0bf7376519f6918","duration":2258,"sha":"27a3ec8244e70774e6f4673201397e2302c32dfe","dirty_hash":"abc4a2c6304f10abbf2d6c628ed6a63dbf9e6ca06fecaaea2935e4ea7f4ff660"}

BIN
.turbo/cache/f0bf7376519f6918.tar.zst vendored Normal file

Binary file not shown.

View 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"]}

View File

@@ -0,0 +1 @@
{"hash":"f1d80f0b8df92657","duration":1597,"sha":"27a3ec8244e70774e6f4673201397e2302c32dfe","dirty_hash":"67d44d689fa95ed97ad8bb0d3e753762649434cb9a64ff731786cdc979515618"}

BIN
.turbo/cache/f1d80f0b8df92657.tar.zst vendored Normal file

Binary file not shown.

View File

@@ -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 |

View File

@@ -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",

View 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],
);
}
}

View 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 {}

View 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);
}
}

View 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],
);
}
}

View 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,
});
}
}

View 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,
};
}
}

View 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');
}
}

View 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();
}
}

View 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(),
};
}
}

View 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,
});
}
}

View 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(),
})),
};
}
}

View 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);
}
}

View 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 },
});
}
}

View File

@@ -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 {}

View File

@@ -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 {

View File

@@ -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);
}
} }

View File

@@ -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 {}

View File

@@ -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);
}
} }

View File

@@ -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],

View File

@@ -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,
});
}
} }

View File

@@ -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