Phase0
Some checks failed
CI / Node — lint, typecheck, test, build (push) Failing after 9s
CI / Go — node-agent tests (push) Failing after 22s
CI / Go — edge-gateway build (push) Successful in 13s

This commit is contained in:
smueller
2026-06-26 11:12:51 +02:00
parent e25e871067
commit 4fe25e6ec3
106 changed files with 1002 additions and 160 deletions

View File

@@ -11,8 +11,8 @@ API_URL=http://localhost:3001
TRUSTED_PROXY_COUNT=0
# --- Security ---
SESSION_SECRET=change-me-use-openssl-rand-base64-32
ENCRYPTION_KEY=change-me-use-openssl-rand-base64-32
SESSION_SECRET=dev-only-change-me-use-openssl-rand-base64-32-chars-min
ENCRYPTION_KEY=dev-only-change-me-use-openssl-rand-base64-32-chars-min
# --- Database ---
DATABASE_URL=postgresql://gamecloud:gamecloud@localhost:5432/gamecloud

View File

@@ -0,0 +1 @@
{"files":{"packages/auth/.turbo/turbo-build.log":{"size":101,"mtime_nanos":1782464567254733300,"mode":420,"is_dir":false},"packages/auth/dist/index.js.map":{"size":258,"mtime_nanos":1782463695062467100,"mode":420,"is_dir":false},"packages/auth/dist/index.js":{"size":413,"mtime_nanos":1782463695064454800,"mode":420,"is_dir":false},"packages/auth/dist/index.d.ts":{"size":579,"mtime_nanos":1782463695080927000,"mode":420,"is_dir":false},"packages/auth/dist/index.d.ts.map":{"size":641,"mtime_nanos":1782463695077610400,"mode":420,"is_dir":false},"packages/auth/dist":{"size":0,"mtime_nanos":0,"mode":0,"is_dir":true}},"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"]}

View File

@@ -0,0 +1 @@
{"hash":"0eec5a132ba3c7f3","duration":1723,"sha":"e25e871067e99bc1f6c7fb7b8e1d9269c9499b79","dirty_hash":"acf566bdb1108e010e8663121c2c7115cc1516c9606d035f8a46f85fa8d73e4f"}

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

Binary file not shown.

View File

@@ -0,0 +1 @@
{"files":{"apps/api/.turbo/turbo-build.log":{"size":102,"mtime_nanos":1782464575032738700,"mode":420,"is_dir":false}},"order":["apps/api/.turbo/turbo-build.log"]}

View File

@@ -0,0 +1 @@
{"hash":"122d77301488cc8f","duration":6752,"sha":"e25e871067e99bc1f6c7fb7b8e1d9269c9499b79","dirty_hash":"acf566bdb1108e010e8663121c2c7115cc1516c9606d035f8a46f85fa8d73e4f"}

BIN
.turbo/cache/122d77301488cc8f.tar.zst vendored Normal file

Binary file not shown.

View File

@@ -0,0 +1 @@
{"files":{"apps/web/.turbo/turbo-test.log":{"size":116,"mtime_nanos":1782464450468010700,"mode":420,"is_dir":false}},"order":["apps/web/.turbo/turbo-test.log"]}

View File

@@ -0,0 +1 @@
{"hash":"1332ff3e057b879e","duration":1097,"sha":"e25e871067e99bc1f6c7fb7b8e1d9269c9499b79","dirty_hash":"98375958cc7c51ff190dc9305391fc2702cb885563996e8f4b76010e6f0911a5"}

BIN
.turbo/cache/1332ff3e057b879e.tar.zst vendored Normal file

Binary file not shown.

View File

@@ -0,0 +1 @@
{"files":{"packages/database/.turbo/turbo-typecheck.log":{"size":570,"mtime_nanos":1782464276390079900,"mode":420,"is_dir":false}},"order":["packages/database/.turbo/turbo-typecheck.log"]}

View File

@@ -0,0 +1 @@
{"hash":"1b5423f4e78a55b9","duration":5652,"sha":"e25e871067e99bc1f6c7fb7b8e1d9269c9499b79","dirty_hash":"e845c03c811f1be302e853e13c210075bc720859ad22a8b7b0dc6aca02e48034"}

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

Binary file not shown.

View File

@@ -0,0 +1 @@
{"files":{"apps/api/.turbo/turbo-build.log":{"size":102,"mtime_nanos":1782464473620820600,"mode":420,"is_dir":false}},"order":["apps/api/.turbo/turbo-build.log"]}

View File

@@ -0,0 +1 @@
{"hash":"20c12b39307b68c5","duration":6748,"sha":"e25e871067e99bc1f6c7fb7b8e1d9269c9499b79","dirty_hash":"61f87e8bf5c1e3926749710d903d5923806a1f8a1a32c15e3edc0183f385cb47"}

BIN
.turbo/cache/20c12b39307b68c5.tar.zst vendored Normal file

Binary file not shown.

View File

@@ -0,0 +1 @@
{"files":{"apps/worker/.turbo/turbo-lint.log":{"size":122,"mtime_nanos":1782464257789949000,"mode":420,"is_dir":false}},"order":["apps/worker/.turbo/turbo-lint.log"]}

View File

@@ -0,0 +1 @@
{"hash":"2a1b1819c72cabae","duration":1196,"sha":"e25e871067e99bc1f6c7fb7b8e1d9269c9499b79","dirty_hash":"406580bdd7c79070c8eae9ca536fc73493b5ea888c285c99ae81d39dd526691f"}

BIN
.turbo/cache/2a1b1819c72cabae.tar.zst vendored Normal file

Binary file not shown.

View File

@@ -0,0 +1 @@
{"files":{"packages/database/.turbo/turbo-build.log":{"size":509,"mtime_nanos":1782464455439723200,"mode":420,"is_dir":false},"packages/database/dist/index.d.ts":{"size":585,"mtime_nanos":1782463905198409500,"mode":420,"is_dir":false},"packages/database/dist/index.js":{"size":962,"mtime_nanos":1782463905155282000,"mode":420,"is_dir":false},"packages/database/dist":{"size":0,"mtime_nanos":0,"mode":0,"is_dir":true},"packages/database/dist/index.js.map":{"size":617,"mtime_nanos":1782463905154791000,"mode":420,"is_dir":false},"packages/database/dist/index.d.ts.map":{"size":447,"mtime_nanos":1782463905197280200,"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":"336e23a0e123fa3c","duration":6065,"sha":"e25e871067e99bc1f6c7fb7b8e1d9269c9499b79","dirty_hash":"98375958cc7c51ff190dc9305391fc2702cb885563996e8f4b76010e6f0911a5"}

BIN
.turbo/cache/336e23a0e123fa3c.tar.zst vendored Normal file

Binary file not shown.

View File

@@ -0,0 +1 @@
{"files":{"apps/api/.turbo/turbo-typecheck.log":{"size":108,"mtime_nanos":1782464281233368400,"mode":420,"is_dir":false}},"order":["apps/api/.turbo/turbo-typecheck.log"]}

View File

@@ -0,0 +1 @@
{"hash":"656880a48d330f84","duration":4838,"sha":"e25e871067e99bc1f6c7fb7b8e1d9269c9499b79","dirty_hash":"e845c03c811f1be302e853e13c210075bc720859ad22a8b7b0dc6aca02e48034"}

BIN
.turbo/cache/656880a48d330f84.tar.zst vendored Normal file

Binary file not shown.

View File

@@ -0,0 +1 @@
{"files":{"apps/worker/dist/health.d.ts":{"size":156,"mtime_nanos":1782463924001574000,"mode":420,"is_dir":false},"apps/worker/.turbo/turbo-build.log":{"size":101,"mtime_nanos":1782464200211473000,"mode":420,"is_dir":false},"apps/worker/dist/index.d.ts.map":{"size":104,"mtime_nanos":1782463967924729800,"mode":420,"is_dir":false},"apps/worker/dist/health.js":{"size":2284,"mtime_nanos":1782463923995265100,"mode":420,"is_dir":false},"apps/worker/dist/index.js.map":{"size":1971,"mtime_nanos":1782463967922326200,"mode":420,"is_dir":false},"apps/worker/dist/health.d.ts.map":{"size":203,"mtime_nanos":1782463924000248300,"mode":420,"is_dir":false},"apps/worker/dist/health.js.map":{"size":1971,"mtime_nanos":1782463923993536700,"mode":420,"is_dir":false},"apps/worker/dist/index.d.ts":{"size":46,"mtime_nanos":1782463924052923900,"mode":420,"is_dir":false},"apps/worker/dist/logger.js.map":{"size":405,"mtime_nanos":1782463923953999600,"mode":420,"is_dir":false},"apps/worker/dist/main.js":{"size":483,"mtime_nanos":1782463715732328600,"mode":420,"is_dir":false},"apps/worker/dist/queues.js":{"size":538,"mtime_nanos":1782463924007328900,"mode":420,"is_dir":false},"apps/worker/dist/queues.d.ts":{"size":400,"mtime_nanos":1782463924018216400,"mode":420,"is_dir":false},"apps/worker/dist/index.js":{"size":1899,"mtime_nanos":1782463967923283600,"mode":420,"is_dir":false},"apps/worker/dist/logger.js":{"size":616,"mtime_nanos":1782463923956015700,"mode":420,"is_dir":false},"apps/worker/dist/logger.d.ts":{"size":119,"mtime_nanos":1782463923971765000,"mode":420,"is_dir":false},"apps/worker/dist":{"size":0,"mtime_nanos":0,"mode":0,"is_dir":true},"apps/worker/dist/queues.d.ts.map":{"size":321,"mtime_nanos":1782463924016227600,"mode":420,"is_dir":false},"apps/worker/dist/queues.js.map":{"size":279,"mtime_nanos":1782463924006472100,"mode":420,"is_dir":false},"apps/worker/dist/logger.d.ts.map":{"size":168,"mtime_nanos":1782463923969877300,"mode":420,"is_dir":false}},"order":["apps/worker/.turbo/turbo-build.log","apps/worker/dist","apps/worker/dist/health.d.ts","apps/worker/dist/health.d.ts.map","apps/worker/dist/health.js","apps/worker/dist/health.js.map","apps/worker/dist/index.d.ts","apps/worker/dist/index.d.ts.map","apps/worker/dist/index.js","apps/worker/dist/index.js.map","apps/worker/dist/logger.d.ts","apps/worker/dist/logger.d.ts.map","apps/worker/dist/logger.js","apps/worker/dist/logger.js.map","apps/worker/dist/main.js","apps/worker/dist/queues.d.ts","apps/worker/dist/queues.d.ts.map","apps/worker/dist/queues.js","apps/worker/dist/queues.js.map"]}

View File

@@ -0,0 +1 @@
{"hash":"69074382bcb14d0e","duration":3048,"sha":"e25e871067e99bc1f6c7fb7b8e1d9269c9499b79","dirty_hash":"d026a735788db199bdf9df611e51688d74a58cd6fc83f4aaeb86a1c266a0ab97"}

BIN
.turbo/cache/69074382bcb14d0e.tar.zst vendored Normal file

Binary file not shown.

View File

@@ -0,0 +1 @@
{"files":{"apps/api/.turbo/turbo-lint.log":{"size":116,"mtime_nanos":1782464257786287100,"mode":420,"is_dir":false}},"order":["apps/api/.turbo/turbo-lint.log"]}

View File

@@ -0,0 +1 @@
{"hash":"6f1cb46f9e95fee2","duration":1192,"sha":"e25e871067e99bc1f6c7fb7b8e1d9269c9499b79","dirty_hash":"406580bdd7c79070c8eae9ca536fc73493b5ea888c285c99ae81d39dd526691f"}

BIN
.turbo/cache/6f1cb46f9e95fee2.tar.zst vendored Normal file

Binary file not shown.

View File

@@ -0,0 +1 @@
{"files":{"packages/config/.turbo/turbo-typecheck.log":{"size":118,"mtime_nanos":1782464273337568500,"mode":420,"is_dir":false}},"order":["packages/config/.turbo/turbo-typecheck.log"]}

View File

@@ -0,0 +1 @@
{"hash":"6f89daa434f500b0","duration":2601,"sha":"e25e871067e99bc1f6c7fb7b8e1d9269c9499b79","dirty_hash":"e845c03c811f1be302e853e13c210075bc720859ad22a8b7b0dc6aca02e48034"}

BIN
.turbo/cache/6f89daa434f500b0.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":"70e72492c407d800","duration":41196,"sha":"e25e871067e99bc1f6c7fb7b8e1d9269c9499b79","dirty_hash":"acf566bdb1108e010e8663121c2c7115cc1516c9606d035f8a46f85fa8d73e4f"}

BIN
.turbo/cache/70e72492c407d800.tar.zst vendored Normal file

Binary file not shown.

View File

@@ -0,0 +1 @@
{"files":{"packages/contracts/dist/health.js":{"size":801,"mtime_nanos":1782464453215257400,"mode":420,"is_dir":false},"packages/contracts/dist/error.test.d.ts.map":{"size":114,"mtime_nanos":1782464453203196100,"mode":420,"is_dir":false},"packages/contracts/dist/health.test.d.ts.map":{"size":116,"mtime_nanos":1782464453232100100,"mode":420,"is_dir":false},"packages/contracts/dist/error.js":{"size":954,"mtime_nanos":1782464453185668200,"mode":420,"is_dir":false},"packages/contracts/dist":{"size":0,"mtime_nanos":0,"mode":0,"is_dir":true},"packages/contracts/.turbo/turbo-build.log":{"size":111,"mtime_nanos":1782464453366334100,"mode":420,"is_dir":false},"packages/contracts/dist/error.d.ts.map":{"size":402,"mtime_nanos":1782464453192713900,"mode":420,"is_dir":false},"packages/contracts/dist/error.js.map":{"size":934,"mtime_nanos":1782464453184710600,"mode":420,"is_dir":false},"packages/contracts/dist/health.d.ts.map":{"size":506,"mtime_nanos":1782464453222205200,"mode":420,"is_dir":false},"packages/contracts/dist/health.test.d.ts":{"size":52,"mtime_nanos":1782464453233717000,"mode":420,"is_dir":false},"packages/contracts/dist/health.test.js.map":{"size":762,"mtime_nanos":1782464453228542000,"mode":420,"is_dir":false},"packages/contracts/dist/health.d.ts":{"size":2265,"mtime_nanos":1782463690835044200,"mode":420,"is_dir":false},"packages/contracts/dist/error.test.js.map":{"size":505,"mtime_nanos":1782464453199560800,"mode":420,"is_dir":false},"packages/contracts/dist/index.d.ts":{"size":446,"mtime_nanos":1782463690872264400,"mode":420,"is_dir":false},"packages/contracts/dist/error.test.js":{"size":654,"mtime_nanos":1782464453201656200,"mode":420,"is_dir":false},"packages/contracts/dist/index.js":{"size":1779,"mtime_nanos":1782464453256377800,"mode":420,"is_dir":false},"packages/contracts/dist/pagination.d.ts":{"size":2597,"mtime_nanos":1782463690861646700,"mode":420,"is_dir":false},"packages/contracts/dist/pagination.d.ts.map":{"size":606,"mtime_nanos":1782464453248377600,"mode":420,"is_dir":false},"packages/contracts/dist/error.d.ts":{"size":1291,"mtime_nanos":1782463690806846900,"mode":420,"is_dir":false},"packages/contracts/dist/pagination.js.map":{"size":1494,"mtime_nanos":1782464453241363100,"mode":420,"is_dir":false},"packages/contracts/dist/index.js.map":{"size":357,"mtime_nanos":1782464453255382100,"mode":420,"is_dir":false},"packages/contracts/dist/health.test.js":{"size":873,"mtime_nanos":1782464453229688300,"mode":420,"is_dir":false},"packages/contracts/dist/index.d.ts.map":{"size":408,"mtime_nanos":1782464453258690500,"mode":420,"is_dir":false},"packages/contracts/dist/pagination.js":{"size":1468,"mtime_nanos":1782464453242180800,"mode":420,"is_dir":false},"packages/contracts/dist/error.test.d.ts":{"size":51,"mtime_nanos":1782464453206177100,"mode":420,"is_dir":false},"packages/contracts/dist/health.js.map":{"size":722,"mtime_nanos":1782464453214256400,"mode":420,"is_dir":false}},"order":["packages/contracts/.turbo/turbo-build.log","packages/contracts/dist","packages/contracts/dist/error.d.ts","packages/contracts/dist/error.d.ts.map","packages/contracts/dist/error.js","packages/contracts/dist/error.js.map","packages/contracts/dist/error.test.d.ts","packages/contracts/dist/error.test.d.ts.map","packages/contracts/dist/error.test.js","packages/contracts/dist/error.test.js.map","packages/contracts/dist/health.d.ts","packages/contracts/dist/health.d.ts.map","packages/contracts/dist/health.js","packages/contracts/dist/health.js.map","packages/contracts/dist/health.test.d.ts","packages/contracts/dist/health.test.d.ts.map","packages/contracts/dist/health.test.js","packages/contracts/dist/health.test.js.map","packages/contracts/dist/index.d.ts","packages/contracts/dist/index.d.ts.map","packages/contracts/dist/index.js","packages/contracts/dist/index.js.map","packages/contracts/dist/pagination.d.ts","packages/contracts/dist/pagination.d.ts.map","packages/contracts/dist/pagination.js","packages/contracts/dist/pagination.js.map"]}

View File

@@ -0,0 +1 @@
{"hash":"71befc3179f6bc50","duration":3990,"sha":"e25e871067e99bc1f6c7fb7b8e1d9269c9499b79","dirty_hash":"98375958cc7c51ff190dc9305391fc2702cb885563996e8f4b76010e6f0911a5"}

BIN
.turbo/cache/71befc3179f6bc50.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":"959ecf58e6f5753f","duration":38181,"sha":"e25e871067e99bc1f6c7fb7b8e1d9269c9499b79","dirty_hash":"d026a735788db199bdf9df611e51688d74a58cd6fc83f4aaeb86a1c266a0ab97"}

BIN
.turbo/cache/959ecf58e6f5753f.tar.zst vendored Normal file

Binary file not shown.

View File

@@ -0,0 +1 @@
{"files":{"packages/contracts/dist/index.js":{"size":1779,"mtime_nanos":1782464453256377800,"mode":420,"is_dir":false},"packages/contracts/dist/pagination.js":{"size":1468,"mtime_nanos":1782464453242180800,"mode":420,"is_dir":false},"packages/contracts/.turbo/turbo-build.log":{"size":111,"mtime_nanos":1782464466857614900,"mode":420,"is_dir":false},"packages/contracts/dist":{"size":0,"mtime_nanos":0,"mode":0,"is_dir":true},"packages/contracts/dist/error.test.js":{"size":654,"mtime_nanos":1782464453201656200,"mode":420,"is_dir":false},"packages/contracts/dist/index.js.map":{"size":357,"mtime_nanos":1782464453255382100,"mode":420,"is_dir":false},"packages/contracts/dist/error.test.d.ts":{"size":51,"mtime_nanos":1782464453206177100,"mode":420,"is_dir":false},"packages/contracts/dist/health.d.ts.map":{"size":506,"mtime_nanos":1782464453222205200,"mode":420,"is_dir":false},"packages/contracts/dist/health.test.js":{"size":873,"mtime_nanos":1782464453229688300,"mode":420,"is_dir":false},"packages/contracts/dist/error.js":{"size":954,"mtime_nanos":1782464453185668200,"mode":420,"is_dir":false},"packages/contracts/dist/index.d.ts":{"size":446,"mtime_nanos":1782463690872264400,"mode":420,"is_dir":false},"packages/contracts/dist/pagination.d.ts.map":{"size":606,"mtime_nanos":1782464453248377600,"mode":420,"is_dir":false},"packages/contracts/dist/pagination.js.map":{"size":1494,"mtime_nanos":1782464453241363100,"mode":420,"is_dir":false},"packages/contracts/dist/error.d.ts":{"size":1291,"mtime_nanos":1782463690806846900,"mode":420,"is_dir":false},"packages/contracts/dist/health.test.d.ts":{"size":52,"mtime_nanos":1782464453233717000,"mode":420,"is_dir":false},"packages/contracts/dist/index.d.ts.map":{"size":408,"mtime_nanos":1782464453258690500,"mode":420,"is_dir":false},"packages/contracts/dist/pagination.d.ts":{"size":2597,"mtime_nanos":1782463690861646700,"mode":420,"is_dir":false},"packages/contracts/dist/health.test.d.ts.map":{"size":116,"mtime_nanos":1782464453232100100,"mode":420,"is_dir":false},"packages/contracts/dist/error.d.ts.map":{"size":402,"mtime_nanos":1782464453192713900,"mode":420,"is_dir":false},"packages/contracts/dist/health.test.js.map":{"size":762,"mtime_nanos":1782464453228542000,"mode":420,"is_dir":false},"packages/contracts/dist/health.js.map":{"size":722,"mtime_nanos":1782464453214256400,"mode":420,"is_dir":false},"packages/contracts/dist/health.d.ts":{"size":2265,"mtime_nanos":1782463690835044200,"mode":420,"is_dir":false},"packages/contracts/dist/error.js.map":{"size":934,"mtime_nanos":1782464453184710600,"mode":420,"is_dir":false},"packages/contracts/dist/error.test.d.ts.map":{"size":114,"mtime_nanos":1782464453203196100,"mode":420,"is_dir":false},"packages/contracts/dist/health.js":{"size":801,"mtime_nanos":1782464453215257400,"mode":420,"is_dir":false},"packages/contracts/dist/error.test.js.map":{"size":505,"mtime_nanos":1782464453199560800,"mode":420,"is_dir":false}},"order":["packages/contracts/.turbo/turbo-build.log","packages/contracts/dist","packages/contracts/dist/error.d.ts","packages/contracts/dist/error.d.ts.map","packages/contracts/dist/error.js","packages/contracts/dist/error.js.map","packages/contracts/dist/error.test.d.ts","packages/contracts/dist/error.test.d.ts.map","packages/contracts/dist/error.test.js","packages/contracts/dist/error.test.js.map","packages/contracts/dist/health.d.ts","packages/contracts/dist/health.d.ts.map","packages/contracts/dist/health.js","packages/contracts/dist/health.js.map","packages/contracts/dist/health.test.d.ts","packages/contracts/dist/health.test.d.ts.map","packages/contracts/dist/health.test.js","packages/contracts/dist/health.test.js.map","packages/contracts/dist/index.d.ts","packages/contracts/dist/index.d.ts.map","packages/contracts/dist/index.js","packages/contracts/dist/index.js.map","packages/contracts/dist/pagination.d.ts","packages/contracts/dist/pagination.d.ts.map","packages/contracts/dist/pagination.js","packages/contracts/dist/pagination.js.map"]}

View File

@@ -0,0 +1 @@
{"hash":"968e8e475372cd35","duration":2788,"sha":"e25e871067e99bc1f6c7fb7b8e1d9269c9499b79","dirty_hash":"61f87e8bf5c1e3926749710d903d5923806a1f8a1a32c15e3edc0183f385cb47"}

BIN
.turbo/cache/968e8e475372cd35.tar.zst vendored Normal file

Binary file not shown.

View File

@@ -0,0 +1 @@
{"files":{"apps/web/.turbo/turbo-lint.log":{"size":367,"mtime_nanos":1782464263918799800,"mode":420,"is_dir":false}},"order":["apps/web/.turbo/turbo-lint.log"]}

View File

@@ -0,0 +1 @@
{"hash":"a8d48a55cfff3228","duration":7327,"sha":"e25e871067e99bc1f6c7fb7b8e1d9269c9499b79","dirty_hash":"406580bdd7c79070c8eae9ca536fc73493b5ea888c285c99ae81d39dd526691f"}

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

Binary file not shown.

View File

@@ -0,0 +1 @@
{"files":{"apps/api/.turbo/turbo-test.log":{"size":858,"mtime_nanos":1782464456960847100,"mode":420,"is_dir":false}},"order":["apps/api/.turbo/turbo-test.log"]}

View File

@@ -0,0 +1 @@
{"hash":"ae505b6ad37293f1","duration":1505,"sha":"e25e871067e99bc1f6c7fb7b8e1d9269c9499b79","dirty_hash":"98375958cc7c51ff190dc9305391fc2702cb885563996e8f4b76010e6f0911a5"}

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

Binary file not shown.

View File

@@ -0,0 +1 @@
{"files":{"packages/contracts/dist/pagination.d.ts":{"size":2597,"mtime_nanos":1782463690861646700,"mode":420,"is_dir":false},"packages/contracts/dist/index.js.map":{"size":357,"mtime_nanos":1782464568174556700,"mode":420,"is_dir":false},"packages/contracts/dist/health.d.ts":{"size":2265,"mtime_nanos":1782463690835044200,"mode":420,"is_dir":false},"packages/contracts/dist/error.test.js.map":{"size":505,"mtime_nanos":1782464453199560800,"mode":420,"is_dir":false},"packages/contracts/dist/health.test.d.ts":{"size":52,"mtime_nanos":1782464453233717000,"mode":420,"is_dir":false},"packages/contracts/dist/health.test.js.map":{"size":762,"mtime_nanos":1782464453228542000,"mode":420,"is_dir":false},"packages/contracts/dist/error.d.ts.map":{"size":402,"mtime_nanos":1782464568129438300,"mode":420,"is_dir":false},"packages/contracts/dist":{"size":0,"mtime_nanos":0,"mode":0,"is_dir":true},"packages/contracts/dist/error.test.js":{"size":654,"mtime_nanos":1782464453201656200,"mode":420,"is_dir":false},"packages/contracts/dist/error.js":{"size":954,"mtime_nanos":1782464568114744400,"mode":420,"is_dir":false},"packages/contracts/dist/index.d.ts":{"size":446,"mtime_nanos":1782463690872264400,"mode":420,"is_dir":false},"packages/contracts/dist/pagination.js":{"size":1468,"mtime_nanos":1782464568161250400,"mode":420,"is_dir":false},"packages/contracts/dist/pagination.js.map":{"size":1494,"mtime_nanos":1782464568160111800,"mode":420,"is_dir":false},"packages/contracts/.turbo/turbo-build.log":{"size":111,"mtime_nanos":1782464568269331600,"mode":420,"is_dir":false},"packages/contracts/dist/index.d.ts.map":{"size":408,"mtime_nanos":1782464568178049200,"mode":420,"is_dir":false},"packages/contracts/dist/health.js":{"size":801,"mtime_nanos":1782464568141250100,"mode":420,"is_dir":false},"packages/contracts/dist/pagination.d.ts.map":{"size":606,"mtime_nanos":1782464568166709600,"mode":420,"is_dir":false},"packages/contracts/dist/health.d.ts.map":{"size":506,"mtime_nanos":1782464568148241500,"mode":420,"is_dir":false},"packages/contracts/dist/health.js.map":{"size":722,"mtime_nanos":1782464568140218200,"mode":420,"is_dir":false},"packages/contracts/dist/error.d.ts":{"size":1291,"mtime_nanos":1782463690806846900,"mode":420,"is_dir":false},"packages/contracts/dist/error.js.map":{"size":934,"mtime_nanos":1782464568113270500,"mode":420,"is_dir":false},"packages/contracts/dist/error.test.d.ts":{"size":51,"mtime_nanos":1782464453206177100,"mode":420,"is_dir":false},"packages/contracts/dist/error.test.d.ts.map":{"size":114,"mtime_nanos":1782464453203196100,"mode":420,"is_dir":false},"packages/contracts/dist/health.test.d.ts.map":{"size":116,"mtime_nanos":1782464453232100100,"mode":420,"is_dir":false},"packages/contracts/dist/health.test.js":{"size":873,"mtime_nanos":1782464453229688300,"mode":420,"is_dir":false},"packages/contracts/dist/index.js":{"size":1779,"mtime_nanos":1782464568176047900,"mode":420,"is_dir":false}},"order":["packages/contracts/.turbo/turbo-build.log","packages/contracts/dist","packages/contracts/dist/error.d.ts","packages/contracts/dist/error.d.ts.map","packages/contracts/dist/error.js","packages/contracts/dist/error.js.map","packages/contracts/dist/error.test.d.ts","packages/contracts/dist/error.test.d.ts.map","packages/contracts/dist/error.test.js","packages/contracts/dist/error.test.js.map","packages/contracts/dist/health.d.ts","packages/contracts/dist/health.d.ts.map","packages/contracts/dist/health.js","packages/contracts/dist/health.js.map","packages/contracts/dist/health.test.d.ts","packages/contracts/dist/health.test.d.ts.map","packages/contracts/dist/health.test.js","packages/contracts/dist/health.test.js.map","packages/contracts/dist/index.d.ts","packages/contracts/dist/index.d.ts.map","packages/contracts/dist/index.js","packages/contracts/dist/index.js.map","packages/contracts/dist/pagination.d.ts","packages/contracts/dist/pagination.d.ts.map","packages/contracts/dist/pagination.js","packages/contracts/dist/pagination.js.map"]}

View File

@@ -0,0 +1 @@
{"hash":"b7b7b32e3fa37e33","duration":2735,"sha":"e25e871067e99bc1f6c7fb7b8e1d9269c9499b79","dirty_hash":"acf566bdb1108e010e8663121c2c7115cc1516c9606d035f8a46f85fa8d73e4f"}

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

Binary file not shown.

View File

@@ -0,0 +1 @@
{"files":{"apps/web/.turbo/turbo-typecheck.log":{"size":108,"mtime_nanos":1782464275843199200,"mode":420,"is_dir":false}},"order":["apps/web/.turbo/turbo-typecheck.log"]}

View File

@@ -0,0 +1 @@
{"hash":"c4e8f936852186cd","duration":5103,"sha":"e25e871067e99bc1f6c7fb7b8e1d9269c9499b79","dirty_hash":"e845c03c811f1be302e853e13c210075bc720859ad22a8b7b0dc6aca02e48034"}

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

Binary file not shown.

View File

@@ -0,0 +1 @@
{"files":{"apps/worker/.turbo/turbo-typecheck.log":{"size":114,"mtime_nanos":1782464279593518300,"mode":420,"is_dir":false}},"order":["apps/worker/.turbo/turbo-typecheck.log"]}

View File

@@ -0,0 +1 @@
{"hash":"cd699e748d88daaf","duration":3197,"sha":"e25e871067e99bc1f6c7fb7b8e1d9269c9499b79","dirty_hash":"e845c03c811f1be302e853e13c210075bc720859ad22a8b7b0dc6aca02e48034"}

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

Binary file not shown.

View File

@@ -0,0 +1 @@
{"files":{"apps/api/.turbo/turbo-build.log":{"size":102,"mtime_nanos":1782464203904064900,"mode":420,"is_dir":false}},"order":["apps/api/.turbo/turbo-build.log"]}

View File

@@ -0,0 +1 @@
{"hash":"db52b9a38fc13e75","duration":6738,"sha":"e25e871067e99bc1f6c7fb7b8e1d9269c9499b79","dirty_hash":"d026a735788db199bdf9df611e51688d74a58cd6fc83f4aaeb86a1c266a0ab97"}

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

Binary file not shown.

View File

@@ -0,0 +1 @@
{"files":{"packages/auth/dist/index.d.ts":{"size":579,"mtime_nanos":1782463695080927000,"mode":420,"is_dir":false},"packages/auth/dist/index.js":{"size":413,"mtime_nanos":1782463695064454800,"mode":420,"is_dir":false},"packages/auth/dist/index.js.map":{"size":258,"mtime_nanos":1782463695062467100,"mode":420,"is_dir":false},"packages/auth/dist":{"size":0,"mtime_nanos":0,"mode":0,"is_dir":true},"packages/auth/dist/index.d.ts.map":{"size":641,"mtime_nanos":1782463695077610400,"mode":420,"is_dir":false},"packages/auth/.turbo/turbo-build.log":{"size":101,"mtime_nanos":1782464465814532500,"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"]}

View File

@@ -0,0 +1 @@
{"hash":"dbfd1053a49910ec","duration":1746,"sha":"e25e871067e99bc1f6c7fb7b8e1d9269c9499b79","dirty_hash":"61f87e8bf5c1e3926749710d903d5923806a1f8a1a32c15e3edc0183f385cb47"}

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

Binary file not shown.

View File

@@ -0,0 +1 @@
{"files":{"packages/contracts/.turbo/turbo-test.log":{"size":459,"mtime_nanos":1782464453287320700,"mode":420,"is_dir":false}},"order":["packages/contracts/.turbo/turbo-test.log"]}

View File

@@ -0,0 +1 @@
{"hash":"dccc5fbc20881585","duration":3911,"sha":"e25e871067e99bc1f6c7fb7b8e1d9269c9499b79","dirty_hash":"98375958cc7c51ff190dc9305391fc2702cb885563996e8f4b76010e6f0911a5"}

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

Binary file not shown.

View File

@@ -0,0 +1 @@
{"files":{"apps/worker/dist/index.d.ts.map":{"size":104,"mtime_nanos":1782463967924729800,"mode":420,"is_dir":false},"apps/worker/dist/queues.d.ts.map":{"size":321,"mtime_nanos":1782463924016227600,"mode":420,"is_dir":false},"apps/worker/dist/logger.js":{"size":616,"mtime_nanos":1782463923956015700,"mode":420,"is_dir":false},"apps/worker/dist/main.js":{"size":483,"mtime_nanos":1782463715732328600,"mode":420,"is_dir":false},"apps/worker/dist/logger.d.ts":{"size":119,"mtime_nanos":1782463923971765000,"mode":420,"is_dir":false},"apps/worker/dist/index.d.ts":{"size":46,"mtime_nanos":1782463924052923900,"mode":420,"is_dir":false},"apps/worker/dist/index.js":{"size":1899,"mtime_nanos":1782463967923283600,"mode":420,"is_dir":false},"apps/worker/dist/health.js.map":{"size":1971,"mtime_nanos":1782463923993536700,"mode":420,"is_dir":false},"apps/worker/dist/queues.js.map":{"size":279,"mtime_nanos":1782463924006472100,"mode":420,"is_dir":false},"apps/worker/.turbo/turbo-build.log":{"size":101,"mtime_nanos":1782464467111466700,"mode":420,"is_dir":false},"apps/worker/dist":{"size":0,"mtime_nanos":0,"mode":0,"is_dir":true},"apps/worker/dist/queues.d.ts":{"size":400,"mtime_nanos":1782463924018216400,"mode":420,"is_dir":false},"apps/worker/dist/health.js":{"size":2284,"mtime_nanos":1782463923995265100,"mode":420,"is_dir":false},"apps/worker/dist/health.d.ts.map":{"size":203,"mtime_nanos":1782463924000248300,"mode":420,"is_dir":false},"apps/worker/dist/queues.js":{"size":538,"mtime_nanos":1782463924007328900,"mode":420,"is_dir":false},"apps/worker/dist/health.d.ts":{"size":156,"mtime_nanos":1782463924001574000,"mode":420,"is_dir":false},"apps/worker/dist/index.js.map":{"size":1971,"mtime_nanos":1782463967922326200,"mode":420,"is_dir":false},"apps/worker/dist/logger.js.map":{"size":405,"mtime_nanos":1782463923953999600,"mode":420,"is_dir":false},"apps/worker/dist/logger.d.ts.map":{"size":168,"mtime_nanos":1782463923969877300,"mode":420,"is_dir":false}},"order":["apps/worker/.turbo/turbo-build.log","apps/worker/dist","apps/worker/dist/health.d.ts","apps/worker/dist/health.d.ts.map","apps/worker/dist/health.js","apps/worker/dist/health.js.map","apps/worker/dist/index.d.ts","apps/worker/dist/index.d.ts.map","apps/worker/dist/index.js","apps/worker/dist/index.js.map","apps/worker/dist/logger.d.ts","apps/worker/dist/logger.d.ts.map","apps/worker/dist/logger.js","apps/worker/dist/logger.js.map","apps/worker/dist/main.js","apps/worker/dist/queues.d.ts","apps/worker/dist/queues.d.ts.map","apps/worker/dist/queues.js","apps/worker/dist/queues.js.map"]}

View File

@@ -0,0 +1 @@
{"hash":"eb7eb672d777a9b7","duration":3034,"sha":"e25e871067e99bc1f6c7fb7b8e1d9269c9499b79","dirty_hash":"61f87e8bf5c1e3926749710d903d5923806a1f8a1a32c15e3edc0183f385cb47"}

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

Binary file not shown.

View File

@@ -1 +1 @@
{"files":{"packages/config/.turbo/turbo-build.log":{"size":105,"mtime_nanos":1782463916544826200,"mode":420,"is_dir":false},"packages/config/dist":{"size":0,"mtime_nanos":0,"mode":0,"is_dir":true},"packages/config/dist/index.js.map":{"size":3214,"mtime_nanos":1782463901597337500,"mode":420,"is_dir":false},"packages/config/dist/index.d.ts":{"size":1874,"mtime_nanos":1782463696518849500,"mode":420,"is_dir":false},"packages/config/dist/index.d.ts.map":{"size":521,"mtime_nanos":1782463901632066900,"mode":420,"is_dir":false},"packages/config/dist/index.js":{"size":3043,"mtime_nanos":1782463901598335900,"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"]}
{"files":{"packages\\config\\dist\\index.d.ts.map":{"size":521,"mtime_nanos":1782464197152772300,"mode":420,"is_dir":false},"packages\\config\\.turbo\\turbo-build.log":{"size":105,"mtime_nanos":1782464197148252400,"mode":420,"is_dir":false},"packages\\config\\dist\\index.js":{"size":3043,"mtime_nanos":1782464197156299900,"mode":420,"is_dir":false},"packages\\config\\dist\\index.d.ts":{"size":1874,"mtime_nanos":1782464197150533600,"mode":420,"is_dir":false},"packages\\config\\dist\\index.js.map":{"size":3214,"mtime_nanos":1782464197159045300,"mode":420,"is_dir":false}},"order":["packages\\config\\.turbo\\turbo-build.log","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 @@
{"files":{"apps/web/.turbo/turbo-test.log":{"size":116,"mtime_nanos":1782464289631967800,"mode":420,"is_dir":false}},"order":["apps/web/.turbo/turbo-test.log"]}

View File

@@ -0,0 +1 @@
{"hash":"fa164319fda95b75","duration":1173,"sha":"e25e871067e99bc1f6c7fb7b8e1d9269c9499b79","dirty_hash":"aafb41798da2afa8a7d5fb76b575f4c632115714431ba8b6cdcbf913b63e78d2"}

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

Binary file not shown.

View File

@@ -0,0 +1 @@
{"files":{"apps/worker/.turbo/turbo-test.log":{"size":832,"mtime_nanos":1782464456873005100,"mode":420,"is_dir":false}},"order":["apps/worker/.turbo/turbo-test.log"]}

View File

@@ -0,0 +1 @@
{"hash":"fc1410a798df3a1a","duration":1419,"sha":"e25e871067e99bc1f6c7fb7b8e1d9269c9499b79","dirty_hash":"98375958cc7c51ff190dc9305391fc2702cb885563996e8f4b76010e6f0911a5"}

BIN
.turbo/cache/fc1410a798df3a1a.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":{"size":3043,"mtime_nanos":1782464197156299900,"mode":420,"is_dir":false},"packages/config/dist/index.js.map":{"size":3214,"mtime_nanos":1782464197159045300,"mode":420,"is_dir":false},"packages/config/.turbo/turbo-build.log":{"size":105,"mtime_nanos":1782464452081179600,"mode":420,"is_dir":false},"packages/config/dist/index.d.ts":{"size":1874,"mtime_nanos":1782464197150533600,"mode":420,"is_dir":false},"packages/config/dist/index.d.ts.map":{"size":521,"mtime_nanos":1782464197152772300,"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":"fd4a8ec518b6accd","duration":2708,"sha":"e25e871067e99bc1f6c7fb7b8e1d9269c9499b79","dirty_hash":"98375958cc7c51ff190dc9305391fc2702cb885563996e8f4b76010e6f0911a5"}

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

Binary file not shown.

101
README.md Normal file
View File

@@ -0,0 +1,101 @@
# HexaHost GameCloud
Selbstgehostete, mandantenfähige Plattform für On-Demand-Minecraft-Server.
Der Projektname **HexaHost GameCloud** ist ein Arbeitstitel und zentral über `APP_NAME` / `NEXT_PUBLIC_APP_NAME` konfigurierbar.
## Voraussetzungen
| Tool | Version |
|------|---------|
| Node.js | ≥ 22 |
| pnpm | 9.15.x (`corepack enable`) |
| Docker | für lokale Infrastruktur |
| Go | 1.23+ (Node Agent) |
## Schnellstart (lokal)
```bash
# Abhängigkeiten installieren
corepack enable
pnpm install
# Umgebung vorbereiten
cp .env.example .env
# SESSION_SECRET und ENCRYPTION_KEY auf mindestens 32 Zeichen setzen
# Infrastruktur (PostgreSQL, Redis, MinIO, Mailpit)
pnpm dev:infra
# Datenbankschema anwenden
pnpm db:push
# Entwicklungsserver (API, Worker, Web parallel)
pnpm dev
```
### Dienste (Entwicklung)
| Dienst | URL |
|--------|-----|
| Web | http://localhost:3000 |
| API | http://localhost:3001/api/v1 |
| OpenAPI | http://localhost:3001/api/v1/docs |
| Worker Health | http://localhost:3002/health |
| Mailpit | http://localhost:8025 |
| MinIO Console | http://localhost:9001 |
Optional für Docker Compose mit Traefik-Netzwerk:
```bash
docker network create traefik-network
```
## Monorepo-Struktur
```text
apps/
web/ Next.js Weboberfläche
api/ NestJS API (Fastify)
worker/ BullMQ Hintergrundjobs
node-agent/ Go-Daemon auf Game-Nodes
edge-gateway/ Go TCP/UDP Gateway (Phase 8)
packages/
database/ Prisma Schema & Client
contracts/ Zod-Schemas & API-Verträge
config/ Typisierte Konfiguration
auth/ Authentifizierungslogik
ui/ Gemeinsame UI-Komponenten
deploy/ Docker Compose, systemd, Ansible
docs/ Architektur, ADR, Betrieb
integrations/ WHMCS-Module (später)
```
## Befehle
```bash
pnpm build # Alle Pakete bauen
pnpm lint # Linting
pnpm typecheck # TypeScript-Prüfung
pnpm test # Unit-Tests
pnpm db:generate # Prisma Client generieren
pnpm db:migrate # Migrationen ausführen
```
Go Node Agent:
```bash
cd apps/node-agent
make test
make build
```
## Aktueller Stand
**Phase 0** (Repository & Grundlagen) — abgeschlossen.
Siehe [docs/IMPLEMENTATION_STATUS.md](docs/IMPLEMENTATION_STATUS.md) für Details und nächste Schritte.
## Lizenz
Proprietär — siehe Repository-Eigentümer.

View File

@@ -1,19 +1,31 @@
FROM node:22-alpine AS builder
WORKDIR /app
RUN corepack enable && corepack prepare pnpm@9.15.0 --activate
COPY package.json pnpm-workspace.yaml turbo.json ./
COPY apps/api/package.json apps/api/
RUN pnpm install --filter @hexahost/api...
COPY apps/api apps/api
RUN pnpm --filter @hexahost/api build
# syntax=docker/dockerfile:1
FROM node:22-alpine
FROM node:22-alpine AS base
RUN corepack enable && corepack prepare pnpm@9.15.0 --activate
WORKDIR /app
FROM base AS builder
COPY package.json pnpm-lock.yaml pnpm-workspace.yaml turbo.json ./
COPY packages packages
COPY apps/api apps/api
RUN pnpm install --frozen-lockfile
RUN pnpm --filter @hexahost/api... build
FROM base AS runner
WORKDIR /app
ENV NODE_ENV=production
ENV API_PORT=3001
COPY --from=builder /app/apps/api/dist ./dist
COPY --from=builder /app/apps/api/package.json ./
COPY --from=builder /app/package.json ./
COPY --from=builder /app/pnpm-lock.yaml ./
COPY --from=builder /app/pnpm-workspace.yaml ./
COPY --from=builder /app/node_modules ./node_modules
COPY --from=builder /app/packages ./packages
COPY --from=builder /app/apps/api/dist ./apps/api/dist
COPY --from=builder /app/apps/api/package.json ./apps/api/package.json
WORKDIR /app/apps/api
EXPOSE 3001
HEALTHCHECK --interval=15s --timeout=5s --retries=3 \
CMD wget -qO- http://localhost:3001/healthz || exit 1
CMD wget -qO- http://localhost:3001/api/v1/health/live || exit 1
CMD ["node", "dist/main.js"]

View File

@@ -2,12 +2,18 @@ import { describe, it } from "node:test";
import assert from "node:assert/strict";
import http from "node:http";
describe("api health", () => {
it("returns ok for healthz", async () => {
describe("api health contract", () => {
it("returns ok for liveness-style health response", async () => {
const server = http.createServer((req, res) => {
if (req.url === "/healthz") {
if (req.url === "/api/v1/health/live") {
res.writeHead(200, { "Content-Type": "application/json" });
res.end(JSON.stringify({ status: "ok", service: "api" }));
res.end(
JSON.stringify({
status: "ok",
service: "api",
timestamp: new Date().toISOString(),
}),
);
return;
}
res.writeHead(404);
@@ -19,10 +25,11 @@ describe("api health", () => {
assert.ok(addr && typeof addr === "object");
const port = addr.port;
const response = await fetch(`http://127.0.0.1:${port}/healthz`);
const response = await fetch(`http://127.0.0.1:${port}/api/v1/health/live`);
assert.equal(response.status, 200);
const body = (await response.json()) as { status: string };
const body = await response.json();
assert.equal(body.status, "ok");
assert.equal(body.service, "api");
await new Promise((resolve, reject) =>
server.close((err) => (err ? reject(err) : resolve())),

File diff suppressed because one or more lines are too long

View File

@@ -1,20 +1,31 @@
FROM node:22-alpine AS builder
WORKDIR /app
# syntax=docker/dockerfile:1
FROM node:22-alpine AS base
RUN corepack enable && corepack prepare pnpm@9.15.0 --activate
COPY package.json pnpm-workspace.yaml turbo.json ./
COPY apps/web/package.json apps/web/
RUN pnpm install --filter @hexahost/web...
WORKDIR /app
FROM base AS builder
COPY package.json pnpm-lock.yaml pnpm-workspace.yaml turbo.json ./
COPY packages packages
COPY apps/web apps/web
RUN pnpm install --frozen-lockfile
ENV DOCKER_BUILD=1
RUN pnpm --filter @hexahost/web build
FROM node:22-alpine
FROM node:22-alpine AS runner
WORKDIR /app
ENV NODE_ENV=production
ENV WEB_PORT=3000
COPY --from=builder /app/apps/web/dist ./dist
COPY --from=builder /app/apps/web/public ./public
COPY --from=builder /app/apps/web/package.json ./
ENV PORT=3000
ENV HOSTNAME=0.0.0.0
RUN addgroup --system --gid 1001 nodejs && adduser --system --uid 1001 nextjs
COPY --from=builder /app/apps/web/public ./apps/web/public
COPY --from=builder --chown=nextjs:nodejs /app/apps/web/.next/standalone ./
COPY --from=builder --chown=nextjs:nodejs /app/apps/web/.next/static ./apps/web/.next/static
USER nextjs
EXPOSE 3000
HEALTHCHECK --interval=15s --timeout=5s --retries=3 \
CMD wget -qO- http://localhost:3000/api/health || exit 1
CMD ["node", "dist/server.js"]
CMD wget -qO- http://localhost:3000/de || exit 1
CMD ["node", "apps/web/server.js"]

View File

@@ -1,5 +1,6 @@
/// <reference types="next" />
/// <reference types="next/image-types/global" />
/// <reference path="./.next/types/routes.d.ts" />
// NOTE: This file should not be edited
// see https://nextjs.org/docs/app/api-reference/config/typescript for more information.

View File

@@ -1,9 +1,18 @@
import type { NextConfig } from "next";
import createNextIntlPlugin from "next-intl/plugin";
import path from "node:path";
const withNextIntl = createNextIntlPlugin("./src/i18n/request.ts");
const isDockerBuild = process.env.DOCKER_BUILD === "1";
const nextConfig: NextConfig = {
...(isDockerBuild
? {
output: "standalone" as const,
outputFileTracingRoot: path.join(__dirname, "../.."),
}
: {}),
transpilePackages: ["@hexahost/ui"],
};

View File

@@ -1,49 +0,0 @@
import http from "node:http";
import fs from "node:fs";
import path from "node:path";
const port = Number(process.env.WEB_PORT ?? process.env.PORT ?? 3000);
const appName = process.env.APP_NAME ?? "HexaHost GameCloud";
const publicDir = path.join(process.cwd(), "public");
const indexHtml = `<!DOCTYPE html>
<html lang="de">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>${appName}</title>
</head>
<body>
<main>
<h1>${appName}</h1>
<p>Phase 0 — Web stub. Next.js UI arrives in Phase 1.</p>
</main>
</body>
</html>`;
const server = http.createServer((req, res) => {
if (req.url === "/api/health") {
res.writeHead(200, { "Content-Type": "application/json" });
res.end(JSON.stringify({ status: "ok", service: "web", phase: "0" }));
return;
}
if (req.url === "/" || req.url === "/index.html") {
const filePath = path.join(publicDir, "index.html");
if (fs.existsSync(filePath)) {
res.writeHead(200, { "Content-Type": "text/html; charset=utf-8" });
fs.createReadStream(filePath).pipe(res);
return;
}
res.writeHead(200, { "Content-Type": "text/html; charset=utf-8" });
res.end(indexHtml);
return;
}
res.writeHead(404, { "Content-Type": "application/json" });
res.end(JSON.stringify({ status: "not_found" }));
});
server.listen(port, () => {
console.log(JSON.stringify({ level: "info", msg: "web listening", port }));
});

File diff suppressed because one or more lines are too long

View File

@@ -1,15 +1,31 @@
FROM node:22-alpine AS builder
WORKDIR /app
RUN corepack enable && corepack prepare pnpm@9.15.0 --activate
COPY package.json pnpm-workspace.yaml turbo.json ./
COPY apps/worker/package.json apps/worker/
RUN pnpm install --filter @hexahost/worker...
COPY apps/worker apps/worker
RUN pnpm --filter @hexahost/worker build
# syntax=docker/dockerfile:1
FROM node:22-alpine
FROM node:22-alpine AS base
RUN corepack enable && corepack prepare pnpm@9.15.0 --activate
WORKDIR /app
FROM base AS builder
COPY package.json pnpm-lock.yaml pnpm-workspace.yaml turbo.json ./
COPY packages packages
COPY apps/worker apps/worker
RUN pnpm install --frozen-lockfile
RUN pnpm --filter @hexahost/worker... build
FROM base AS runner
WORKDIR /app
ENV NODE_ENV=production
COPY --from=builder /app/apps/worker/dist ./dist
COPY --from=builder /app/apps/worker/package.json ./
CMD ["node", "dist/main.js"]
ENV WORKER_PORT=3002
COPY --from=builder /app/package.json ./
COPY --from=builder /app/pnpm-lock.yaml ./
COPY --from=builder /app/pnpm-workspace.yaml ./
COPY --from=builder /app/node_modules ./node_modules
COPY --from=builder /app/packages ./packages
COPY --from=builder /app/apps/worker/dist ./apps/worker/dist
COPY --from=builder /app/apps/worker/package.json ./apps/worker/package.json
WORKDIR /app/apps/worker
EXPOSE 3002
HEALTHCHECK --interval=15s --timeout=5s --retries=3 \
CMD wget -qO- http://localhost:3002/health || exit 1
CMD ["node", "dist/index.js"]

View File

@@ -101,7 +101,7 @@ services:
redis:
condition: service_healthy
healthcheck:
test: ["CMD", "wget", "-qO-", "http://localhost:3001/healthz"]
test: ["CMD", "wget", "-qO-", "http://localhost:3001/api/v1/health/live"]
interval: 15s
timeout: 5s
retries: 5

View File

@@ -2,45 +2,50 @@
Last updated: 2026-06-26
## Current phase: Phase 0 — Repository and foundations
## Current phase: Phase 0 — Repository and foundations
### Completed
| Area | Status | Notes |
|------|--------|-------|
| Monorepo layout | Done | pnpm workspace, Turborepo |
| Go node-agent | Done | Config, protocol types, health, agent loop stub |
| Go edge-gateway | Stub | Disabled by default; Phase 8 |
| Docker Compose dev stack | Done | PostgreSQL, Redis, MinIO, Mailpit, API, Worker, Web |
| Environment template | Done | `.env.example` |
| CI pipeline | Done | Lint, typecheck, tests, Go tests, build |
| Documentation skeleton | Done | ADR, architecture, threat model, installation |
| WHMCS integration layout | Placeholder | Directory structure and README |
| Deploy placeholders | Done | Traefik, Ansible, systemd, Prometheus |
### In progress / next (Phase 1)
- User registration, login, sessions
- Email verification and password reset
- TOTP 2FA
- Admin bootstrap CLI
- Prisma schema and migrations
| Monorepo layout | Done | pnpm workspace, Turborepo, shared TypeScript config |
| `@hexahost/database` | Done | Prisma 6, 14 Kernmodelle, UUID-IDs, optimistic locking |
| `@hexahost/contracts` | Done | Zod-Schemas (Health, RFC7807, Pagination) + Vitest |
| `@hexahost/config` | Done | Typisierter Env-Loader mit Validierung |
| `@hexahost/auth` | Done | Rollen-Enum und Typen (Stub für Phase 1) |
| `@hexahost/ui` | Done | Button, Card (Tailwind, a11y) |
| `@hexahost/api` | Done | NestJS 11 + Fastify, Health, OpenAPI, Pino, Rate Limits |
| `@hexahost/worker` | Done | BullMQ-Queues (Stubs), Health HTTP :3002 |
| `@hexahost/web` | Done | Next.js 15, i18n de/en, Dark Mode, Landing, Auth-UI, Dashboard |
| Go node-agent | Done | Protokoll-Typen, Health, Agent-Loop, Unit-Tests |
| Go edge-gateway | Stub | Deaktiviert (Phase 8) |
| Docker Compose dev | Done | PostgreSQL, Redis, MinIO, Mailpit, API, Worker, Web |
| `.env.example` | Done | Vollständige Konfigurationsvorlage |
| CI pipeline | Done | Lint, typecheck, test, build, Go-Tests |
| Dokumentation | Done | ADR, Architektur, Threat Model, Installation |
| WHMCS-Layout | Placeholder | Verzeichnisstruktur für spätere Integration |
| README | Done | Startbefehle und Übersicht |
### Abnahmekriterium Phase 0
- [x] Repository structure and configuration baseline
- [x] Local infrastructure compose file
- [x] Node agent builds and tests pass
- [x] CI workflow defined
- [ ] All services start locally end-to-end (requires `docker compose up` validation)
- [ ] Full API/Web/Worker feature implementation (Phase 1+)
- [x] Monorepo-Struktur mit allen Grundverzeichnissen
- [x] pnpm-workspace, Turborepo, Next.js, NestJS, Worker, Go-Module
- [x] PostgreSQL/Prisma-Grundschema
- [x] Redis- und MinIO-Konfiguration in Compose
- [x] Healthchecks (API, Worker, Node Agent)
- [x] Strukturiertes Logging (Pino)
- [x] OpenAPI unter `/api/v1/docs`
- [x] Basis-CI
- [x] ADR und IMPLEMENTATION_STATUS
- [x] Build, Lint, Typecheck und Tests lokal grün
### Known limitations
- Edge gateway is intentionally not implemented (Join-to-Start is Phase 8).
- API, Worker, and Web are minimal health-check stubs in Phase 0.
- Traefik, Ansible, and production compose are documented but not fully implemented.
- Node agent does not yet connect to the control plane WebSocket.
- Edge Gateway ist bewusst nicht implementiert (Join-to-Start = Phase 8).
- Authentifizierung, Server-Lifecycle und Node-WebSocket folgen in Phase 12.
- Traefik-, Ansible- und Produktions-Compose sind dokumentiert, nicht vollständig umgesetzt.
- Docker Compose erfordert optional `docker network create traefik-network`.
- WHMCS-Integration ist nur als Verzeichnisstruktur vorbereitet.
### Test commands
@@ -49,5 +54,13 @@ pnpm install
pnpm lint
pnpm typecheck
pnpm test
pnpm build
cd apps/node-agent && make test
```
### Next phase: Phase 1 — Authentication & user panel
- Registrierung, E-Mail-Verifizierung, Login, Sessions
- Passwort-Reset, TOTP 2FA, Recovery Codes
- Admin-Bootstrap-CLI
- Vollständiger sicherer Login-Flow mit Tests

File diff suppressed because one or more lines are too long

View File

@@ -6,13 +6,15 @@
"types": "./dist/index.d.ts",
"scripts": {
"build": "tsc",
"typecheck": "tsc --noEmit"
"typecheck": "tsc --noEmit",
"test": "vitest run"
},
"dependencies": {
"zod": "^3.24.1"
},
"devDependencies": {
"@hexahost/typescript-config": "workspace:*",
"typescript": "^5.7.3"
"typescript": "^5.7.3",
"vitest": "^3.0.8"
}
}

View File

@@ -0,0 +1,16 @@
import { describe, expect, it } from "vitest";
import { problemDetailsSchema } from "./error";
describe("problemDetailsSchema", () => {
it("accepts RFC 7807 problem details", () => {
const result = problemDetailsSchema.safeParse({
type: "https://example.com/errors/validation",
title: "Validation failed",
status: 400,
detail: "Invalid input",
instance: "/api/v1/servers",
});
expect(result.success).toBe(true);
});
});

View File

@@ -0,0 +1,23 @@
import { describe, expect, it } from "vitest";
import { healthResponseSchema } from "./health";
describe("healthResponseSchema", () => {
it("accepts a valid liveness response", () => {
const result = healthResponseSchema.safeParse({
status: "ok",
service: "api",
timestamp: new Date().toISOString(),
});
expect(result.success).toBe(true);
});
it("rejects invalid status values", () => {
const result = healthResponseSchema.safeParse({
status: "unknown",
service: "api",
timestamp: new Date().toISOString(),
});
expect(result.success).toBe(false);
});
});

View File

@@ -5,5 +5,5 @@
"rootDir": "./src"
},
"include": ["src/**/*"],
"exclude": ["node_modules", "dist"]
"exclude": ["node_modules", "dist", "src/**/*.test.ts"]
}

Some files were not shown because too many files have changed in this diff Show More