diff --git a/.claude-flow/metrics/system-metrics.json b/.claude-flow/metrics/system-metrics.json
deleted file mode 100644
index 8c475465d..000000000
--- a/.claude-flow/metrics/system-metrics.json
+++ /dev/null
@@ -1,662 +0,0 @@
-[
- {
- "timestamp": 1764952212026,
- "memoryTotal": 34359738368,
- "memoryUsed": 34261319680,
- "memoryFree": 98418688,
- "memoryUsagePercent": 99.71356391906738,
- "memoryEfficiency": 0.2864360809326172,
- "cpuCount": 12,
- "cpuLoad": 0.18159993489583334,
- "platform": "darwin",
- "uptime": 316234
- },
- {
- "timestamp": 1764952242025,
- "memoryTotal": 34359738368,
- "memoryUsed": 33978384384,
- "memoryFree": 381353984,
- "memoryUsagePercent": 98.8901138305664,
- "memoryEfficiency": 1.1098861694335938,
- "cpuCount": 12,
- "cpuLoad": 0.3077799479166667,
- "platform": "darwin",
- "uptime": 316264
- },
- {
- "timestamp": 1764952272027,
- "memoryTotal": 34359738368,
- "memoryUsed": 31443189760,
- "memoryFree": 2916548608,
- "memoryUsagePercent": 91.51172637939453,
- "memoryEfficiency": 8.488273620605469,
- "cpuCount": 12,
- "cpuLoad": 0.3214518229166667,
- "platform": "darwin",
- "uptime": 316294
- },
- {
- "timestamp": 1764952302028,
- "memoryTotal": 34359738368,
- "memoryUsed": 32673497088,
- "memoryFree": 1686241280,
- "memoryUsagePercent": 95.09239196777344,
- "memoryEfficiency": 4.9076080322265625,
- "cpuCount": 12,
- "cpuLoad": 0.2928873697916667,
- "platform": "darwin",
- "uptime": 316324
- },
- {
- "timestamp": 1764952332029,
- "memoryTotal": 34359738368,
- "memoryUsed": 33272102912,
- "memoryFree": 1087635456,
- "memoryUsagePercent": 96.83456420898438,
- "memoryEfficiency": 3.165435791015625,
- "cpuCount": 12,
- "cpuLoad": 0.2457275390625,
- "platform": "darwin",
- "uptime": 316354
- },
- {
- "timestamp": 1764952362030,
- "memoryTotal": 34359738368,
- "memoryUsed": 33491288064,
- "memoryFree": 868450304,
- "memoryUsagePercent": 97.47247695922852,
- "memoryEfficiency": 2.5275230407714844,
- "cpuCount": 12,
- "cpuLoad": 0.21903483072916666,
- "platform": "darwin",
- "uptime": 316384
- },
- {
- "timestamp": 1764952392031,
- "memoryTotal": 34359738368,
- "memoryUsed": 33697579008,
- "memoryFree": 662159360,
- "memoryUsagePercent": 98.07286262512207,
- "memoryEfficiency": 1.9271373748779297,
- "cpuCount": 12,
- "cpuLoad": 0.19921875,
- "platform": "darwin",
- "uptime": 316414
- },
- {
- "timestamp": 1764952422033,
- "memoryTotal": 34359738368,
- "memoryUsed": 33743257600,
- "memoryFree": 616480768,
- "memoryUsagePercent": 98.2058048248291,
- "memoryEfficiency": 1.7941951751708984,
- "cpuCount": 12,
- "cpuLoad": 0.3559977213541667,
- "platform": "darwin",
- "uptime": 316444
- },
- {
- "timestamp": 1764952454157,
- "memoryTotal": 34359738368,
- "memoryUsed": 34170847232,
- "memoryFree": 188891136,
- "memoryUsagePercent": 99.45025444030762,
- "memoryEfficiency": 0.5497455596923828,
- "cpuCount": 12,
- "cpuLoad": 0.8998616536458334,
- "platform": "darwin",
- "uptime": 316476
- },
- {
- "timestamp": 1764952484155,
- "memoryTotal": 34359738368,
- "memoryUsed": 34279800832,
- "memoryFree": 79937536,
- "memoryUsagePercent": 99.7673511505127,
- "memoryEfficiency": 0.2326488494873047,
- "cpuCount": 12,
- "cpuLoad": 0.6012369791666666,
- "platform": "darwin",
- "uptime": 316506
- },
- {
- "timestamp": 1764952516186,
- "memoryTotal": 34359738368,
- "memoryUsed": 34296479744,
- "memoryFree": 63258624,
- "memoryUsagePercent": 99.81589317321777,
- "memoryEfficiency": 0.18410682678222656,
- "cpuCount": 12,
- "cpuLoad": 0.4691975911458333,
- "platform": "darwin",
- "uptime": 316538
- },
- {
- "timestamp": 1764952548258,
- "memoryTotal": 34359738368,
- "memoryUsed": 34266447872,
- "memoryFree": 93290496,
- "memoryUsagePercent": 99.72848892211914,
- "memoryEfficiency": 0.2715110778808594,
- "cpuCount": 12,
- "cpuLoad": 0.340087890625,
- "platform": "darwin",
- "uptime": 316570
- },
- {
- "timestamp": 1764952578260,
- "memoryTotal": 34359738368,
- "memoryUsed": 34260549632,
- "memoryFree": 99188736,
- "memoryUsagePercent": 99.71132278442383,
- "memoryEfficiency": 0.2886772155761719,
- "cpuCount": 12,
- "cpuLoad": 0.2496337890625,
- "platform": "darwin",
- "uptime": 316600
- },
- {
- "timestamp": 1764952609578,
- "memoryTotal": 34359738368,
- "memoryUsed": 33692418048,
- "memoryFree": 667320320,
- "memoryUsagePercent": 98.05784225463867,
- "memoryEfficiency": 1.9421577453613281,
- "cpuCount": 12,
- "cpuLoad": 0.19681803385416666,
- "platform": "darwin",
- "uptime": 316631
- },
- {
- "timestamp": 1764952641004,
- "memoryTotal": 34359738368,
- "memoryUsed": 34198962176,
- "memoryFree": 160776192,
- "memoryUsagePercent": 99.53207969665527,
- "memoryEfficiency": 0.46792030334472656,
- "cpuCount": 12,
- "cpuLoad": 0.23486328125,
- "platform": "darwin",
- "uptime": 316663
- },
- {
- "timestamp": 1764952671007,
- "memoryTotal": 34359738368,
- "memoryUsed": 34222686208,
- "memoryFree": 137052160,
- "memoryUsagePercent": 99.60112571716309,
- "memoryEfficiency": 0.39887428283691406,
- "cpuCount": 12,
- "cpuLoad": 0.18770345052083334,
- "platform": "darwin",
- "uptime": 316693
- },
- {
- "timestamp": 1764952702795,
- "memoryTotal": 34359738368,
- "memoryUsed": 34269872128,
- "memoryFree": 89866240,
- "memoryUsagePercent": 99.73845481872559,
- "memoryEfficiency": 0.26154518127441406,
- "cpuCount": 12,
- "cpuLoad": 0.15791829427083334,
- "platform": "darwin",
- "uptime": 316724
- },
- {
- "timestamp": 1764952732797,
- "memoryTotal": 34359738368,
- "memoryUsed": 34242723840,
- "memoryFree": 117014528,
- "memoryUsagePercent": 99.65944290161133,
- "memoryEfficiency": 0.3405570983886719,
- "cpuCount": 12,
- "cpuLoad": 0.12320963541666667,
- "platform": "darwin",
- "uptime": 316754
- },
- {
- "timestamp": 1764952764879,
- "memoryTotal": 34359738368,
- "memoryUsed": 34213707776,
- "memoryFree": 146030592,
- "memoryUsagePercent": 99.57499504089355,
- "memoryEfficiency": 0.4250049591064453,
- "cpuCount": 12,
- "cpuLoad": 0.11909993489583333,
- "platform": "darwin",
- "uptime": 316786
- },
- {
- "timestamp": 1764952796332,
- "memoryTotal": 34359738368,
- "memoryUsed": 34039250944,
- "memoryFree": 320487424,
- "memoryUsagePercent": 99.06725883483887,
- "memoryEfficiency": 0.9327411651611328,
- "cpuCount": 12,
- "cpuLoad": 0.135009765625,
- "platform": "darwin",
- "uptime": 316818
- },
- {
- "timestamp": 1764952826334,
- "memoryTotal": 34359738368,
- "memoryUsed": 34186772480,
- "memoryFree": 172965888,
- "memoryUsagePercent": 99.49660301208496,
- "memoryEfficiency": 0.5033969879150391,
- "cpuCount": 12,
- "cpuLoad": 0.21162923177083334,
- "platform": "darwin",
- "uptime": 316848
- },
- {
- "timestamp": 1764952858356,
- "memoryTotal": 34359738368,
- "memoryUsed": 34186379264,
- "memoryFree": 173359104,
- "memoryUsagePercent": 99.49545860290527,
- "memoryEfficiency": 0.5045413970947266,
- "cpuCount": 12,
- "cpuLoad": 0.2408447265625,
- "platform": "darwin",
- "uptime": 316880
- },
- {
- "timestamp": 1764952889730,
- "memoryTotal": 34359738368,
- "memoryUsed": 34280357888,
- "memoryFree": 79380480,
- "memoryUsagePercent": 99.76897239685059,
- "memoryEfficiency": 0.23102760314941406,
- "cpuCount": 12,
- "cpuLoad": 0.3212890625,
- "platform": "darwin",
- "uptime": 316911
- },
- {
- "timestamp": 1764952919730,
- "memoryTotal": 34359738368,
- "memoryUsed": 33718976512,
- "memoryFree": 640761856,
- "memoryUsagePercent": 98.1351375579834,
- "memoryEfficiency": 1.8648624420166016,
- "cpuCount": 12,
- "cpuLoad": 0.2577311197916667,
- "platform": "darwin",
- "uptime": 316941
- },
- {
- "timestamp": 1764952949731,
- "memoryTotal": 34359738368,
- "memoryUsed": 33727643648,
- "memoryFree": 632094720,
- "memoryUsagePercent": 98.16036224365234,
- "memoryEfficiency": 1.8396377563476562,
- "cpuCount": 12,
- "cpuLoad": 0.2508138020833333,
- "platform": "darwin",
- "uptime": 316971
- },
- {
- "timestamp": 1764952979732,
- "memoryTotal": 34359738368,
- "memoryUsed": 33519927296,
- "memoryFree": 839811072,
- "memoryUsagePercent": 97.55582809448242,
- "memoryEfficiency": 2.444171905517578,
- "cpuCount": 12,
- "cpuLoad": 1.6138916015625,
- "platform": "darwin",
- "uptime": 317001
- },
- {
- "timestamp": 1764953009732,
- "memoryTotal": 34359738368,
- "memoryUsed": 33729118208,
- "memoryFree": 630620160,
- "memoryUsagePercent": 98.16465377807617,
- "memoryEfficiency": 1.8353462219238281,
- "cpuCount": 12,
- "cpuLoad": 1.1908365885416667,
- "platform": "darwin",
- "uptime": 317031
- },
- {
- "timestamp": 1764953039733,
- "memoryTotal": 34359738368,
- "memoryUsed": 34194341888,
- "memoryFree": 165396480,
- "memoryUsagePercent": 99.51863288879395,
- "memoryEfficiency": 0.4813671112060547,
- "cpuCount": 12,
- "cpuLoad": 0.8716227213541666,
- "platform": "darwin",
- "uptime": 317061
- },
- {
- "timestamp": 1764953069734,
- "memoryTotal": 34359738368,
- "memoryUsed": 34195587072,
- "memoryFree": 164151296,
- "memoryUsagePercent": 99.52225685119629,
- "memoryEfficiency": 0.47774314880371094,
- "cpuCount": 12,
- "cpuLoad": 0.6322835286458334,
- "platform": "darwin",
- "uptime": 317091
- },
- {
- "timestamp": 1764953099734,
- "memoryTotal": 34359738368,
- "memoryUsed": 34240970752,
- "memoryFree": 118767616,
- "memoryUsagePercent": 99.65434074401855,
- "memoryEfficiency": 0.3456592559814453,
- "cpuCount": 12,
- "cpuLoad": 0.5132242838541666,
- "platform": "darwin",
- "uptime": 317121
- },
- {
- "timestamp": 1764953129734,
- "memoryTotal": 34359738368,
- "memoryUsed": 34246590464,
- "memoryFree": 113147904,
- "memoryUsagePercent": 99.67069625854492,
- "memoryEfficiency": 0.3293037414550781,
- "cpuCount": 12,
- "cpuLoad": 0.4134521484375,
- "platform": "darwin",
- "uptime": 317151
- },
- {
- "timestamp": 1764953159735,
- "memoryTotal": 34359738368,
- "memoryUsed": 34252914688,
- "memoryFree": 106823680,
- "memoryUsagePercent": 99.68910217285156,
- "memoryEfficiency": 0.3108978271484375,
- "cpuCount": 12,
- "cpuLoad": 0.5005696614583334,
- "platform": "darwin",
- "uptime": 317181
- },
- {
- "timestamp": 1764953189735,
- "memoryTotal": 34359738368,
- "memoryUsed": 34234728448,
- "memoryFree": 125009920,
- "memoryUsagePercent": 99.63617324829102,
- "memoryEfficiency": 0.3638267517089844,
- "cpuCount": 12,
- "cpuLoad": 0.3972981770833333,
- "platform": "darwin",
- "uptime": 317211
- },
- {
- "timestamp": 1764953219736,
- "memoryTotal": 34359738368,
- "memoryUsed": 33779810304,
- "memoryFree": 579928064,
- "memoryUsagePercent": 98.31218719482422,
- "memoryEfficiency": 1.6878128051757812,
- "cpuCount": 12,
- "cpuLoad": 0.3188883463541667,
- "platform": "darwin",
- "uptime": 317241
- },
- {
- "timestamp": 1764953249736,
- "memoryTotal": 34359738368,
- "memoryUsed": 34159230976,
- "memoryFree": 200507392,
- "memoryUsagePercent": 99.41644668579102,
- "memoryEfficiency": 0.5835533142089844,
- "cpuCount": 12,
- "cpuLoad": 0.2990315755208333,
- "platform": "darwin",
- "uptime": 317271
- },
- {
- "timestamp": 1764953279736,
- "memoryTotal": 34359738368,
- "memoryUsed": 34207203328,
- "memoryFree": 152535040,
- "memoryUsagePercent": 99.55606460571289,
- "memoryEfficiency": 0.4439353942871094,
- "cpuCount": 12,
- "cpuLoad": 0.4497477213541667,
- "platform": "darwin",
- "uptime": 317301
- },
- {
- "timestamp": 1764953309737,
- "memoryTotal": 34359738368,
- "memoryUsed": 34149695488,
- "memoryFree": 210042880,
- "memoryUsagePercent": 99.3886947631836,
- "memoryEfficiency": 0.6113052368164062,
- "cpuCount": 12,
- "cpuLoad": 0.3463541666666667,
- "platform": "darwin",
- "uptime": 317331
- },
- {
- "timestamp": 1764953339738,
- "memoryTotal": 34359738368,
- "memoryUsed": 33949761536,
- "memoryFree": 409976832,
- "memoryUsagePercent": 98.80681037902832,
- "memoryEfficiency": 1.1931896209716797,
- "cpuCount": 12,
- "cpuLoad": 0.3301188151041667,
- "platform": "darwin",
- "uptime": 317361
- },
- {
- "timestamp": 1764953369738,
- "memoryTotal": 34359738368,
- "memoryUsed": 34282471424,
- "memoryFree": 77266944,
- "memoryUsagePercent": 99.7751235961914,
- "memoryEfficiency": 0.22487640380859375,
- "cpuCount": 12,
- "cpuLoad": 0.28466796875,
- "platform": "darwin",
- "uptime": 317391
- },
- {
- "timestamp": 1764953399740,
- "memoryTotal": 34359738368,
- "memoryUsed": 32868974592,
- "memoryFree": 1490763776,
- "memoryUsagePercent": 95.66130638122559,
- "memoryEfficiency": 4.338693618774414,
- "cpuCount": 12,
- "cpuLoad": 0.224365234375,
- "platform": "darwin",
- "uptime": 317421
- },
- {
- "timestamp": 1764953429741,
- "memoryTotal": 34359738368,
- "memoryUsed": 33954299904,
- "memoryFree": 405438464,
- "memoryUsagePercent": 98.82001876831055,
- "memoryEfficiency": 1.1799812316894531,
- "cpuCount": 12,
- "cpuLoad": 0.18465169270833334,
- "platform": "darwin",
- "uptime": 317451
- },
- {
- "timestamp": 1764953459742,
- "memoryTotal": 34359738368,
- "memoryUsed": 33574420480,
- "memoryFree": 785317888,
- "memoryUsagePercent": 97.71442413330078,
- "memoryEfficiency": 2.2855758666992188,
- "cpuCount": 12,
- "cpuLoad": 0.1524658203125,
- "platform": "darwin",
- "uptime": 317481
- },
- {
- "timestamp": 1764953489742,
- "memoryTotal": 34359738368,
- "memoryUsed": 33800306688,
- "memoryFree": 559431680,
- "memoryUsagePercent": 98.37183952331543,
- "memoryEfficiency": 1.6281604766845703,
- "cpuCount": 12,
- "cpuLoad": 0.20756022135416666,
- "platform": "darwin",
- "uptime": 317511
- },
- {
- "timestamp": 1764953519743,
- "memoryTotal": 34359738368,
- "memoryUsed": 34025553920,
- "memoryFree": 334184448,
- "memoryUsagePercent": 99.02739524841309,
- "memoryEfficiency": 0.9726047515869141,
- "cpuCount": 12,
- "cpuLoad": 0.23970540364583334,
- "platform": "darwin",
- "uptime": 317541
- },
- {
- "timestamp": 1764953549744,
- "memoryTotal": 34359738368,
- "memoryUsed": 34073149440,
- "memoryFree": 286588928,
- "memoryUsagePercent": 99.1659164428711,
- "memoryEfficiency": 0.8340835571289062,
- "cpuCount": 12,
- "cpuLoad": 0.2720133463541667,
- "platform": "darwin",
- "uptime": 317571
- },
- {
- "timestamp": 1764953579745,
- "memoryTotal": 34359738368,
- "memoryUsed": 34250440704,
- "memoryFree": 109297664,
- "memoryUsagePercent": 99.6819019317627,
- "memoryEfficiency": 0.3180980682373047,
- "cpuCount": 12,
- "cpuLoad": 0.2032470703125,
- "platform": "darwin",
- "uptime": 317601
- },
- {
- "timestamp": 1764953609745,
- "memoryTotal": 34359738368,
- "memoryUsed": 34292432896,
- "memoryFree": 67305472,
- "memoryUsagePercent": 99.80411529541016,
- "memoryEfficiency": 0.19588470458984375,
- "cpuCount": 12,
- "cpuLoad": 0.22269694010416666,
- "platform": "darwin",
- "uptime": 317631
- },
- {
- "timestamp": 1764953639747,
- "memoryTotal": 34359738368,
- "memoryUsed": 34245033984,
- "memoryFree": 114704384,
- "memoryUsagePercent": 99.66616630554199,
- "memoryEfficiency": 0.3338336944580078,
- "cpuCount": 12,
- "cpuLoad": 0.1802978515625,
- "platform": "darwin",
- "uptime": 317661
- },
- {
- "timestamp": 1764953669748,
- "memoryTotal": 34359738368,
- "memoryUsed": 34262171648,
- "memoryFree": 97566720,
- "memoryUsagePercent": 99.71604347229004,
- "memoryEfficiency": 0.28395652770996094,
- "cpuCount": 12,
- "cpuLoad": 0.16731770833333334,
- "platform": "darwin",
- "uptime": 317691
- },
- {
- "timestamp": 1764953699748,
- "memoryTotal": 34359738368,
- "memoryUsed": 33642708992,
- "memoryFree": 717029376,
- "memoryUsagePercent": 97.91316986083984,
- "memoryEfficiency": 2.0868301391601562,
- "cpuCount": 12,
- "cpuLoad": 0.1925048828125,
- "platform": "darwin",
- "uptime": 317721
- },
- {
- "timestamp": 1764953729749,
- "memoryTotal": 34359738368,
- "memoryUsed": 33480491008,
- "memoryFree": 879247360,
- "memoryUsagePercent": 97.44105339050293,
- "memoryEfficiency": 2.5589466094970703,
- "cpuCount": 12,
- "cpuLoad": 0.327880859375,
- "platform": "darwin",
- "uptime": 317751
- },
- {
- "timestamp": 1764953759749,
- "memoryTotal": 34359738368,
- "memoryUsed": 34072002560,
- "memoryFree": 287735808,
- "memoryUsagePercent": 99.16257858276367,
- "memoryEfficiency": 0.8374214172363281,
- "cpuCount": 12,
- "cpuLoad": 0.2535400390625,
- "platform": "darwin",
- "uptime": 317781
- },
- {
- "timestamp": 1764953789750,
- "memoryTotal": 34359738368,
- "memoryUsed": 34169667584,
- "memoryFree": 190070784,
- "memoryUsagePercent": 99.44682121276855,
- "memoryEfficiency": 0.5531787872314453,
- "cpuCount": 12,
- "cpuLoad": 0.19832356770833334,
- "platform": "darwin",
- "uptime": 317811
- },
- {
- "timestamp": 1764953819750,
- "memoryTotal": 34359738368,
- "memoryUsed": 34144272384,
- "memoryFree": 215465984,
- "memoryUsagePercent": 99.37291145324707,
- "memoryEfficiency": 0.6270885467529297,
- "cpuCount": 12,
- "cpuLoad": 0.209716796875,
- "platform": "darwin",
- "uptime": 317841
- },
- {
- "timestamp": 1764953849751,
- "memoryTotal": 34359738368,
- "memoryUsed": 34237202432,
- "memoryFree": 122535936,
- "memoryUsagePercent": 99.64337348937988,
- "memoryEfficiency": 0.3566265106201172,
- "cpuCount": 12,
- "cpuLoad": 0.24357096354166666,
- "platform": "darwin",
- "uptime": 317871
- }
-]
\ No newline at end of file
diff --git a/.claude/guidelines/database.md b/.claude/guidelines/database.md
index 49bfbebf4..a503f86cd 100644
--- a/.claude/guidelines/database.md
+++ b/.claude/guidelines/database.md
@@ -349,6 +349,15 @@ async function getPaginated(
## Migrations
+> **Comprehensive Documentation**: See **[docs/DATABASE_MIGRATIONS.md](/docs/DATABASE_MIGRATIONS.md)** for full migration internals, CI/CD integration, zero-downtime patterns, and troubleshooting.
+
+### Quick Reference
+
+| Environment | Command | Purpose |
+| --------------- | ----------------- | ------------------------------- |
+| **Development** | `pnpm db:push` | Fast iteration, direct sync |
+| **Production** | `pnpm db:migrate` | Tracked migrations with history |
+
### Configuration
```typescript
@@ -358,9 +367,9 @@ import { defineConfig } from 'drizzle-kit';
export default defineConfig({
schema: './src/db/schema/index.ts',
out: './src/db/migrations',
- driver: 'pg',
+ dialect: 'postgresql',
dbCredentials: {
- connectionString: process.env.DATABASE_URL!,
+ url: process.env.DATABASE_URL!,
},
verbose: true,
strict: true,
@@ -370,41 +379,85 @@ export default defineConfig({
### Commands
```bash
-# Generate migration from schema changes
-pnpm drizzle-kit generate
+# Development - push schema directly (fast, no history)
+pnpm db:push
-# Push schema directly (development only)
-pnpm drizzle-kit push
-
-# Open Drizzle Studio
-pnpm drizzle-kit studio
-
-# Run migrations (production)
+# Production - generate and run migrations
+pnpm db:generate --name add_user_preferences
pnpm db:migrate
+
+# Open Drizzle Studio for database inspection
+pnpm db:studio
```
-### Migration Runner
+### Migration Workflow
+
+```
+┌─────────────────────────────────────────────────────────────────┐
+│ Which command should I use? │
+├─────────────────────────────────────────────────────────────────┤
+│ │
+│ Local development? │
+│ └── YES → pnpm db:push (fast, no tracking) │
+│ │
+│ Staging/Production? │
+│ └── YES → pnpm db:generate + pnpm db:migrate (tracked) │
+│ │
+│ Schema changed by someone else? │
+│ └── YES → git pull + pnpm db:push (local) │
+│ git pull + pnpm db:migrate (staging/prod) │
+│ │
+└─────────────────────────────────────────────────────────────────┘
+```
+
+### Key Concepts
+
+1. **Advisory Locks**: Migrations use PostgreSQL advisory locks to prevent concurrent execution
+2. **Migration Tracking**: `__drizzle_migrations` table + `meta/_journal.json` file
+3. **Migrations run BEFORE code deployment**: Ensures database is ready for new code
+4. **Never modify applied migrations**: Create new migrations instead
+5. **Zero-downtime**: Use expand-contract pattern for breaking schema changes
+
+### Production Migration Script
+
+Production backends use a migration script with advisory locks:
```typescript
-// src/db/migrate.ts
-import { drizzle } from 'drizzle-orm/postgres-js';
-import { migrate } from 'drizzle-orm/postgres-js/migrator';
-import postgres from 'postgres';
+// src/db/migrate.ts - Key features:
+// - Advisory lock (pg_try_advisory_lock) prevents concurrent migrations
+// - Retry logic with exponential backoff for transient failures
+// - Timeout protection (default 5 minutes)
+// - Graceful handling when no migrations exist
-async function runMigrations() {
- const connection = postgres(process.env.DATABASE_URL!, { max: 1 });
- const db = drizzle(connection);
+const MIGRATION_LOCK_ID = 987654321; // Unique per service
- console.log('Running migrations...');
- await migrate(db, { migrationsFolder: './src/db/migrations' });
- console.log('Migrations complete');
-
- await connection.end();
+async function acquireLock(db) {
+ const result = await db.execute(
+ sql`SELECT pg_try_advisory_lock(${MIGRATION_LOCK_ID}) as acquired`
+ );
+ return result[0]?.acquired === true;
}
-
-runMigrations().catch(console.error);
```
+See `services/mana-core-auth/src/db/migrate.ts` for the full implementation.
+
+### Best Practices
+
+**DO:**
+
+- Run migrations before deploying new code
+- Test migrations in staging before production
+- Use `CONCURRENTLY` for index creation
+- Keep migrations small and focused
+- Commit migration files to version control
+
+**DON'T:**
+
+- Run `db:push` in production
+- Delete or modify applied migrations
+- Add NOT NULL without default or backfill
+- Drop columns immediately (wait 1-2 weeks)
+
## Query Patterns
### Select with Joins
diff --git a/.claude/guidelines/sveltekit-web.md b/.claude/guidelines/sveltekit-web.md
index a307c9352..4a69dd273 100644
--- a/.claude/guidelines/sveltekit-web.md
+++ b/.claude/guidelines/sveltekit-web.md
@@ -698,15 +698,138 @@ export default {
## Environment Variables
-```typescript
-// Access in .svelte or .ts files
-import { PUBLIC_BACKEND_URL, PUBLIC_MANA_CORE_AUTH_URL } from '$env/static/public';
+### Build-Time vs Runtime Variables
-// .env file
+SvelteKit has **two types** of environment variables:
+
+1. **Build-time** (`$env/static/public`) - Baked into the bundle at build time
+2. **Runtime** (`process.env`) - Available at runtime in server code
+
+**CRITICAL**: For Docker deployments, browser-facing URLs must use **runtime injection** because:
+
+- Docker images are built once but deployed to different environments (staging, production)
+- Build-time variables would require rebuilding the image for each environment
+- The browser cannot access `process.env` - it needs values injected into the HTML
+
+### ❌ WRONG - Hardcoded or Build-Time URLs
+
+```typescript
+// ❌ BAD - Hardcoded URL (won't work in Docker)
+const MANA_AUTH_URL = 'http://localhost:3001';
+
+// ❌ BAD - Build-time variable (works locally, breaks in Docker)
+import { PUBLIC_MANA_CORE_AUTH_URL } from '$env/static/public';
+const MANA_AUTH_URL = PUBLIC_MANA_CORE_AUTH_URL || 'http://localhost:3001';
+
+// ❌ BAD - import.meta.env is also build-time
+const MANA_AUTH_URL = import.meta.env.PUBLIC_MANA_CORE_AUTH_URL || 'http://localhost:3001';
+```
+
+### ✅ CORRECT - Runtime Injection Pattern
+
+**Step 1: Create `hooks.server.ts`** to inject env vars into HTML:
+
+```typescript
+// src/hooks.server.ts
+import type { Handle } from '@sveltejs/kit';
+
+// Get client-side URLs from Docker runtime environment
+const PUBLIC_MANA_CORE_AUTH_URL_CLIENT =
+ process.env.PUBLIC_MANA_CORE_AUTH_URL_CLIENT || process.env.PUBLIC_MANA_CORE_AUTH_URL || '';
+const PUBLIC_BACKEND_URL_CLIENT =
+ process.env.PUBLIC_BACKEND_URL_CLIENT || process.env.PUBLIC_BACKEND_URL || '';
+
+export const handle: Handle = async ({ event, resolve }) => {
+ return resolve(event, {
+ transformPageChunk: ({ html }) => {
+ // Inject runtime environment variables into the HTML
+ const envScript = ``;
+ return html.replace('
', `${envScript}`);
+ },
+ });
+};
+```
+
+**Step 2: Read from `window` in client code:**
+
+```typescript
+// src/lib/stores/auth.svelte.ts
+import { browser } from '$app/environment';
+
+function getAuthUrl(): string {
+ if (browser && typeof window !== 'undefined') {
+ // Client-side: use injected window variable
+ const injectedUrl = (window as unknown as { __PUBLIC_MANA_CORE_AUTH_URL__?: string })
+ .__PUBLIC_MANA_CORE_AUTH_URL__;
+ return injectedUrl || 'http://localhost:3001';
+ }
+ // Server-side (SSR): use Docker internal URL
+ return process.env.PUBLIC_MANA_CORE_AUTH_URL || 'http://localhost:3001';
+}
+
+// Use in auth service initialization
+const auth = initializeWebAuth({ baseUrl: getAuthUrl() });
+```
+
+**Step 3: Set environment variables in `docker-compose.staging.yml`:**
+
+```yaml
+services:
+ myapp-web:
+ environment:
+ # Server-side URLs (Docker internal network)
+ PUBLIC_BACKEND_URL: http://myapp-backend:3000
+ PUBLIC_MANA_CORE_AUTH_URL: http://mana-core-auth:3001
+ # Client-side URLs (browser access via public IP)
+ PUBLIC_BACKEND_URL_CLIENT: http://46.224.108.214:3000
+ PUBLIC_MANA_CORE_AUTH_URL_CLIENT: http://46.224.108.214:3001
+```
+
+### Why Two URLs?
+
+| Variable | Purpose | Example |
+| ---------------------------------- | --------------------------------- | ---------------------------- |
+| `PUBLIC_MANA_CORE_AUTH_URL` | Server-to-server (SSR, API calls) | `http://mana-core-auth:3001` |
+| `PUBLIC_MANA_CORE_AUTH_URL_CLIENT` | Browser to server | `http://46.224.108.214:3001` |
+
+Docker containers can reach each other by service name (`mana-core-auth`), but browsers need the public IP/domain.
+
+### Apps Using This Pattern Correctly
+
+- ✅ `chat/apps/web` - Has `hooks.server.ts` with runtime injection
+- ✅ `todo/apps/web` - Fixed
+- ✅ `calendar/apps/web` - Fixed
+- ✅ `clock/apps/web` - Fixed
+
+### Apps That Still Need Fixing
+
+- ❌ `contacts/apps/web`
+- ❌ `manadeck/apps/web`
+- ❌ `manacore/apps/web`
+- ❌ `zitare/apps/web`
+- ❌ `picture/apps/web`
+
+### Quick Checklist for New SvelteKit Apps
+
+- [ ] Create `src/hooks.server.ts` with env injection
+- [ ] Update `auth.svelte.ts` to use `getAuthUrl()` pattern
+- [ ] Update `user-settings.svelte.ts` to use `getAuthUrl()` pattern
+- [ ] Update any feedback services to use runtime URL
+- [ ] Add both `_CLIENT` and non-client env vars to `docker-compose.staging.yml`
+- [ ] Never hardcode `localhost:3001` anywhere
+
+### Simple .env (for local development only)
+
+```env
PUBLIC_BACKEND_URL=http://localhost:3016
PUBLIC_MANA_CORE_AUTH_URL=http://localhost:3001
```
+These work locally because both the browser and server access `localhost`.
+
## Anti-Patterns to Avoid
### Don't Use Old Svelte Syntax
diff --git a/.github/workflows/cd-production.yml b/.github/workflows/cd-production.yml
index 9c98e5982..564aa0c84 100644
--- a/.github/workflows/cd-production.yml
+++ b/.github/workflows/cd-production.yml
@@ -1,3 +1,10 @@
+# Production Deployment
+#
+# Triggered by:
+# - Manual only (workflow_dispatch with confirmation)
+#
+# Flow: dev (staging) → main (production)
+# Requires typing "deploy" to confirm
name: CD - Production Deployment
on:
@@ -205,8 +212,52 @@ jobs:
ssh ${{ secrets.PRODUCTION_USER }}@${{ secrets.PRODUCTION_HOST }} << 'EOF'
cd ~/manacore-production
- # Run migrations before deploying new code
- docker compose run --rm mana-core-auth pnpm run db:migrate || echo "Migrations completed or skipped"
+ echo "=== Running Database Migrations ==="
+ echo ""
+
+ # Migration function with retry logic
+ run_migration() {
+ local service=$1
+ local max_attempts=3
+ local timeout=300 # 5 minutes
+ local attempt=1
+
+ while [ $attempt -le $max_attempts ]; do
+ echo "[$service] Migration attempt $attempt/$max_attempts..."
+
+ # Run migration with timeout using a temporary container
+ if timeout $timeout docker compose run --rm $service pnpm run db:migrate 2>&1; then
+ echo "✅ [$service] Migration succeeded"
+ return 0
+ else
+ exit_code=$?
+ if [ $exit_code -eq 124 ]; then
+ echo "⚠️ [$service] Migration timeout after ${timeout}s"
+ else
+ echo "⚠️ [$service] Migration failed with exit code $exit_code"
+ fi
+
+ attempt=$((attempt + 1))
+ if [ $attempt -le $max_attempts ]; then
+ wait_time=$((10 * attempt)) # Backoff: 10s, 20s, 30s
+ echo " Waiting ${wait_time}s before retry..."
+ sleep $wait_time
+ fi
+ fi
+ done
+
+ echo "❌ [$service] Migration failed after $max_attempts attempts"
+ return 1
+ }
+
+ # Run migrations for mana-core-auth (central auth service)
+ run_migration mana-core-auth || {
+ echo "❌ mana-core-auth migration failed"
+ echo "⚠️ Continuing with deployment - manual migration may be required"
+ }
+
+ echo ""
+ echo "✅ Migration step completed"
EOF
- name: Deploy with zero-downtime
diff --git a/.github/workflows/cd-staging-tagged.yml b/.github/workflows/cd-staging-tagged.yml
index 72a267e8a..5efce798b 100644
--- a/.github/workflows/cd-staging-tagged.yml
+++ b/.github/workflows/cd-staging-tagged.yml
@@ -21,6 +21,10 @@ on:
- zitare
- presi
- mana-core-auth
+ - manacore
+ - todo
+ - calendar
+ - clock
apps:
description: 'Apps to deploy (comma-separated: backend,web,landing or "all")'
required: true
@@ -36,7 +40,8 @@ env:
NODE_VERSION: '20'
PNPM_VERSION: '9.15.0'
REGISTRY: ghcr.io
- IMAGE_PREFIX: ghcr.io/${{ github.repository_owner }}
+ # Note: repository_owner is lowercased for Docker compatibility
+ IMAGE_PREFIX: ghcr.io/memo-2023
jobs:
# Parse tag or inputs to determine what to deploy
@@ -104,6 +109,10 @@ jobs:
PROJECT_APPS[zitare]="backend,web"
PROJECT_APPS[presi]="backend,web"
PROJECT_APPS[mana-core-auth]="service"
+ PROJECT_APPS[manacore]="web"
+ PROJECT_APPS[todo]="backend,web"
+ PROJECT_APPS[calendar]="backend,web"
+ PROJECT_APPS[clock]="backend,web"
# Expand "all" to available apps
if [ "$APPS" == "all" ]; then
@@ -135,8 +144,17 @@ jobs:
IMAGE_NAME="${PROJECT}-backend"
;;
web)
- DOCKERFILE_PATH="docker/templates/Dockerfile.sveltekit"
- CONTEXT_PATH="apps/$PROJECT/apps/web"
+ # Apps with their own Dockerfiles (need monorepo root for shared packages)
+ case "$PROJECT" in
+ manacore|todo|calendar|clock)
+ DOCKERFILE_PATH="apps/$PROJECT/apps/web/Dockerfile"
+ CONTEXT_PATH="."
+ ;;
+ *)
+ DOCKERFILE_PATH="docker/templates/Dockerfile.sveltekit"
+ CONTEXT_PATH="apps/$PROJECT/apps/web"
+ ;;
+ esac
IMAGE_NAME="${PROJECT}-web"
;;
landing)
@@ -146,15 +164,29 @@ jobs:
;;
esac
- # Set ports per project
+ # Set backend ports per project (must match docker-compose.staging.yml)
case "$PROJECT" in
chat) PORT="3002" ;;
picture) PORT="3006" ;;
manadeck) PORT="3009" ;;
zitare) PORT="3007" ;;
presi) PORT="3008" ;;
+ calendar) PORT="3016" ;;
+ clock) PORT="3017" ;;
+ todo) PORT="3018" ;;
esac
- HEALTH_PATH="/api/health"
+
+ # Override ports for web apps (SvelteKit uses different ports)
+ if [ "$APP" == "web" ]; then
+ case "$PROJECT" in
+ manacore) PORT="5173" ;;
+ todo) PORT="5188" ;;
+ calendar) PORT="5186" ;;
+ clock) PORT="5187" ;;
+ *) PORT="5173" ;; # default SvelteKit port
+ esac
+ fi
+ HEALTH_PATH="/api/v1/health"
;;
esac
@@ -267,25 +299,71 @@ jobs:
mkdir -p ~/.ssh
ssh-keyscan -H ${{ secrets.STAGING_HOST }} >> ~/.ssh/known_hosts
+ - name: Sync docker-compose to staging
+ run: |
+ # Ensure staging directory exists
+ ssh ${{ secrets.STAGING_USER }}@${{ secrets.STAGING_HOST }} "mkdir -p ~/manacore-staging"
+ # Copy the docker-compose file
+ scp docker-compose.staging.yml ${{ secrets.STAGING_USER }}@${{ secrets.STAGING_HOST }}:~/manacore-staging/docker-compose.yml
+
+ - name: Login to GHCR on staging server
+ run: |
+ ssh ${{ secrets.STAGING_USER }}@${{ secrets.STAGING_HOST }} << EOF
+ echo "${{ secrets.GITHUB_TOKEN }}" | docker login ghcr.io -u ${{ github.actor }} --password-stdin
+ EOF
+
- name: Deploy service
env:
VERSION: ${{ needs.parse-deployment.outputs.version }}
IMAGE_NAME: ${{ matrix.image_name }}
+ APP_TYPE: ${{ matrix.app }}
+ PROJECT: ${{ needs.parse-deployment.outputs.project }}
run: |
+ # Compute the version variable name locally (before SSH)
+ # Map: todo-web -> TODO_WEB_VERSION, chat-backend -> CHAT_VERSION
+ case "$IMAGE_NAME" in
+ *-web)
+ PROJECT_UPPER=$(echo "$PROJECT" | tr '[:lower:]-' '[:upper:]_')
+ VERSION_VAR="${PROJECT_UPPER}_WEB_VERSION"
+ ;;
+ *-backend)
+ PROJECT_UPPER=$(echo "$PROJECT" | tr '[:lower:]-' '[:upper:]_')
+ VERSION_VAR="${PROJECT_UPPER}_VERSION"
+ ;;
+ mana-core-auth)
+ VERSION_VAR="AUTH_VERSION"
+ ;;
+ *)
+ VERSION_VAR=$(echo "$IMAGE_NAME" | tr '[:lower:]-' '[:upper:]_')_VERSION
+ ;;
+ esac
+
+ echo "Will set $VERSION_VAR=$VERSION for docker-compose"
+
ssh ${{ secrets.STAGING_USER }}@${{ secrets.STAGING_HOST }} << EOF
cd ~/manacore-staging
echo "Deploying $IMAGE_NAME:$VERSION to staging..."
- # Pull the new image
+ # Pull the new image with specific version tag
docker pull ${{ env.IMAGE_PREFIX }}/$IMAGE_NAME:$VERSION
- # Restart only the specific service
- SERVICE_NAME=\$(echo "$IMAGE_NAME" | tr '-' '_')
+ # Update .env file with the version for this service
+ # This ensures docker-compose uses the correct image tag
+ if grep -q "^$VERSION_VAR=" .env 2>/dev/null; then
+ sed -i "s/^$VERSION_VAR=.*/$VERSION_VAR=$VERSION/" .env
+ else
+ echo "$VERSION_VAR=$VERSION" >> .env
+ fi
- if docker compose ps | grep -q "\$SERVICE_NAME"; then
+ echo "Updated .env: $VERSION_VAR=$VERSION"
+ grep "$VERSION_VAR" .env || true
+
+ # Service name matches docker-compose service name (with hyphens)
+ SERVICE_NAME="$IMAGE_NAME"
+
+ if docker compose ps -a | grep -q "$IMAGE_NAME"; then
echo "Updating existing service: \$SERVICE_NAME"
- docker compose pull \$SERVICE_NAME || true
docker compose up -d --no-deps --force-recreate \$SERVICE_NAME
else
echo "Service \$SERVICE_NAME not found in compose, starting..."
@@ -296,6 +374,10 @@ jobs:
sleep 10
docker compose ps \$SERVICE_NAME
+ # Verify correct image is running
+ echo "Running image:"
+ docker inspect --format='{{.Config.Image}}' ${IMAGE_NAME}-staging 2>/dev/null || true
+
# Cleanup old images
docker image prune -f
EOF
diff --git a/.github/workflows/cd-staging.yml b/.github/workflows/cd-staging.yml
index 6b0e52d80..4af222aed 100644
--- a/.github/workflows/cd-staging.yml
+++ b/.github/workflows/cd-staging.yml
@@ -1,11 +1,13 @@
-# Simplified staging config: mana-core-auth + chat-backend only
+# Staging Deployment
+#
+# Triggered by:
+# - Automatic: Push to dev branch (via ci.yml)
+# - Manual: workflow_dispatch
+#
# Full config archived at: .github/workflows/cd-staging.full.yml
#
-# To restore full config:
-# cp .github/workflows/cd-staging.full.yml .github/workflows/cd-staging.yml
-#
-# To add a service back:
-# 1. Add service to workflow_dispatch options (line ~10)
+# To add a service:
+# 1. Add service to workflow_dispatch options
# 2. Add health check in "Run health checks" step
# 3. Add service to docker-compose.staging.yml
name: CD - Staging Deployment
@@ -22,6 +24,13 @@ on:
- mana-core-auth
- chat-backend
- chat-web
+ - manacore-web
+ - todo-backend
+ - todo-web
+ - calendar-backend
+ - calendar-web
+ - clock-backend
+ - clock-web
workflow_call:
permissions:
@@ -182,9 +191,81 @@ jobs:
# Create chat database (for chat-backend service)
docker compose exec -T postgres psql -U postgres -c "CREATE DATABASE chat;" 2>/dev/null || echo "chat database already exists"
+ # Create todo database (for todo-backend service)
+ docker compose exec -T postgres psql -U postgres -c "CREATE DATABASE todo;" 2>/dev/null || echo "todo database already exists"
+
+ # Create calendar database (for calendar-backend service)
+ docker compose exec -T postgres psql -U postgres -c "CREATE DATABASE calendar;" 2>/dev/null || echo "calendar database already exists"
+
+ # Create clock database (for clock-backend service)
+ docker compose exec -T postgres psql -U postgres -c "CREATE DATABASE clock;" 2>/dev/null || echo "clock database already exists"
+
echo "✅ Databases ready"
EOF
+ - name: Run database migrations
+ env:
+ STAGING_USER: deploy
+ STAGING_HOST: 46.224.108.214
+ run: |
+ ssh $STAGING_USER@$STAGING_HOST << 'EOF'
+ cd ~/manacore-staging
+
+ echo "=== Running Database Migrations ==="
+ echo ""
+
+ # Migration function with retry logic
+ run_migration() {
+ local service=$1
+ local max_attempts=3
+ local timeout=300 # 5 minutes
+ local attempt=1
+
+ while [ $attempt -le $max_attempts ]; do
+ echo "[$service] Migration attempt $attempt/$max_attempts..."
+
+ # Run migration with timeout
+ if timeout $timeout docker compose exec -T $service pnpm run db:migrate 2>&1; then
+ echo "✅ [$service] Migration succeeded"
+ return 0
+ else
+ exit_code=$?
+ if [ $exit_code -eq 124 ]; then
+ echo "⚠️ [$service] Migration timeout after ${timeout}s"
+ else
+ echo "⚠️ [$service] Migration failed with exit code $exit_code"
+ fi
+
+ attempt=$((attempt + 1))
+ if [ $attempt -le $max_attempts ]; then
+ wait_time=$((10 * attempt)) # Backoff: 10s, 20s, 30s
+ echo " Waiting ${wait_time}s before retry..."
+ sleep $wait_time
+ fi
+ fi
+ done
+
+ echo "❌ [$service] Migration failed after $max_attempts attempts"
+ return 1
+ }
+
+ # Run migrations for services that have db:migrate script
+ # mana-core-auth - central auth service
+ if docker compose exec -T mana-core-auth test -f src/db/migrate.ts 2>/dev/null || \
+ docker compose exec -T mana-core-auth pnpm run db:migrate --help 2>/dev/null; then
+ run_migration mana-core-auth || {
+ echo "❌ mana-core-auth migration failed - aborting deployment"
+ exit 1
+ }
+ else
+ echo "⏭️ [mana-core-auth] No db:migrate script, using db:push..."
+ docker compose exec -T mana-core-auth npx drizzle-kit push --force || echo "Auth schema push completed"
+ fi
+
+ echo ""
+ echo "✅ All migrations completed"
+ EOF
+
- name: Run health checks
env:
STAGING_USER: deploy
@@ -193,66 +274,69 @@ jobs:
ssh $STAGING_USER@$STAGING_HOST << 'EOF'
cd ~/manacore-staging
- # Wait for services to fully start
- echo "Waiting 60s for services to fully initialize..."
- sleep 60
+ echo "=== Health Checks with Polling ==="
+ echo ""
+
+ # Health check function with retry polling
+ check_health() {
+ local service=$1
+ local url=$2
+ local max_attempts=24 # 24 * 5s = 2 minutes max wait
+ local attempt=1
+
+ echo "Checking $service..."
+
+ while [ $attempt -le $max_attempts ]; do
+ # Check if container is running
+ if ! docker compose ps $service 2>/dev/null | grep -q "Up"; then
+ if [ $attempt -eq 1 ]; then
+ echo " ⏳ Waiting for container to start..."
+ fi
+ sleep 5
+ attempt=$((attempt + 1))
+ continue
+ fi
+
+ # Check health endpoint
+ if docker compose exec -T $service wget -q -O - $url > /dev/null 2>&1; then
+ echo " ✅ $service is healthy (attempt $attempt)"
+ return 0
+ fi
+
+ if [ $attempt -eq 1 ]; then
+ echo " ⏳ Waiting for $service to become healthy..."
+ fi
+
+ sleep 5
+ attempt=$((attempt + 1))
+ done
+
+ echo " ❌ $service health check failed after $max_attempts attempts"
+ echo " === Recent Logs ==="
+ docker compose logs --tail=50 $service
+ return 1
+ }
echo "=== Container Status ==="
docker compose ps
-
echo ""
- echo "=== Health Checks ==="
- # Check mana-core-auth
- echo "Checking mana-core-auth..."
- if docker compose exec -T mana-core-auth wget -q -O - http://localhost:3001/api/v1/health > /dev/null 2>&1; then
- echo "✅ mana-core-auth is healthy"
- else
- echo "❌ mana-core-auth health check failed"
- echo "=== Logs ==="
- docker compose logs --tail=50 mana-core-auth
- exit 1
- fi
-
- # Check chat-backend
- echo "Checking chat-backend..."
- if docker compose exec -T chat-backend wget -q -O - http://localhost:3002/api/v1/health > /dev/null 2>&1; then
- echo "✅ chat-backend is healthy"
- else
- echo "❌ chat-backend health check failed"
- echo "=== Logs ==="
- docker compose logs --tail=50 chat-backend
- exit 1
- fi
-
- # Check chat-web
- echo "Checking chat-web..."
- if docker compose exec -T chat-web wget -q -O - http://localhost:3000/health > /dev/null 2>&1; then
- echo "✅ chat-web is healthy"
- else
- echo "❌ chat-web health check failed"
- echo "=== Logs ==="
- docker compose logs --tail=50 chat-web
- exit 1
- fi
+ # Check all services with polling
+ check_health mana-core-auth http://localhost:3001/api/v1/health || exit 1
+ check_health chat-backend http://localhost:3002/api/v1/health || exit 1
+ check_health chat-web http://localhost:3000/health || exit 1
+ check_health manacore-web http://localhost:5173/health || exit 1
+ check_health todo-backend http://localhost:3018/api/v1/health || exit 1
+ check_health todo-web http://localhost:5188/health || exit 1
+ check_health calendar-backend http://localhost:3016/api/v1/health || exit 1
+ check_health calendar-web http://localhost:5186/health || exit 1
+ check_health clock-backend http://localhost:3017/api/v1/health || exit 1
+ check_health clock-web http://localhost:5187/health || exit 1
echo ""
echo "✅ All health checks passed!"
EOF
- - name: Run database migrations
- env:
- STAGING_USER: deploy
- STAGING_HOST: 46.224.108.214
- run: |
- # Run migrations for services that need them
- ssh $STAGING_USER@$STAGING_HOST << 'EOF'
- cd ~/manacore-staging
-
- # Mana Core Auth - push schema using Drizzle (--force skips interactive confirmation)
- docker compose exec -T mana-core-auth npx drizzle-kit push --force || echo "Auth schema push skipped"
- EOF
-
- name: Deployment summary
run: |
echo "## Staging Deployment Summary" >> $GITHUB_STEP_SUMMARY
diff --git a/.github/workflows/ci-main.yml b/.github/workflows/ci.yml
similarity index 50%
rename from .github/workflows/ci-main.yml
rename to .github/workflows/ci.yml
index 6ffe6f93c..1d56ac667 100644
--- a/.github/workflows/ci-main.yml
+++ b/.github/workflows/ci.yml
@@ -1,31 +1,98 @@
-# MINIMAL: Only builds mana-core-auth + chat Docker images, no validation
-# Full config archived at: .github/workflows/ci-main.full.yml
+# CI Pipeline: Validates code on PRs, builds images on push
#
-# To restore: cp .github/workflows/ci-main.full.yml .github/workflows/ci-main.yml
+# Flow:
+# PR → dev/main : Runs validation (required status check)
+# Push → dev/main : Builds Docker images (NO auto-deploy)
+#
+# Deployments are triggered separately:
+# - Manual: workflow_dispatch on cd-staging.yml / cd-production.yml
+# - Tag-based: push tag like "chat-staging-v1.0.0" triggers cd-staging-tagged.yml
+#
+# Full config archived at: .github/workflows/ci-main.full.yml
-name: CI - Main Branch
+name: CI
on:
push:
branches:
- main
+ - dev
+ paths:
+ - 'apps/**'
+ - 'packages/**'
+ - 'services/**'
+ - 'package.json'
+ - 'pnpm-lock.yaml'
+ - 'turbo.json'
+ pull_request:
+ branches:
+ - main
+ - dev
+ paths:
+ - 'apps/**'
+ - 'packages/**'
+ - 'services/**'
+ - 'package.json'
+ - 'pnpm-lock.yaml'
+ - 'turbo.json'
workflow_dispatch:
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
+env:
+ NODE_VERSION: '20'
+ PNPM_VERSION: '9.15.0'
+
jobs:
- # Build Docker images directly - Dockerfiles handle their own dependencies
+ # Validation job - runs on PRs to catch issues before merge
+ validate:
+ name: Validate
+ runs-on: ubuntu-latest
+ if: github.event_name == 'pull_request'
+ steps:
+ - name: Checkout code
+ uses: actions/checkout@v4
+
+ - name: Setup pnpm
+ uses: pnpm/action-setup@v2
+ with:
+ version: ${{ env.PNPM_VERSION }}
+
+ - name: Setup Node.js
+ uses: actions/setup-node@v4
+ with:
+ node-version: ${{ env.NODE_VERSION }}
+ cache: 'pnpm'
+
+ - name: Install dependencies
+ run: pnpm install --frozen-lockfile
+
+ - name: Type check
+ run: pnpm run type-check
+
+ - name: Lint
+ run: pnpm run lint || echo "Lint warnings found"
+
+ # Build Docker images - only on push to dev/main (not PRs)
build-docker-images:
name: Build ${{ matrix.service.name }}
runs-on: ubuntu-latest
+ if: github.event_name == 'push'
strategy:
matrix:
service:
- { name: 'mana-core-auth', path: 'services/mana-core-auth', port: '3001' }
- { name: 'chat-backend', path: 'apps/chat/apps/backend', port: '3002' }
- { name: 'chat-web', path: 'apps/chat/apps/web', port: '3000' }
+ - { name: 'manacore-web', path: 'apps/manacore/apps/web', port: '5173' }
+ - { name: 'todo-backend', path: 'apps/todo/apps/backend', port: '3018' }
+ - { name: 'todo-web', path: 'apps/todo/apps/web', port: '5188' }
+ - { name: 'calendar-backend', path: 'apps/calendar/apps/backend', port: '3016' }
+ - { name: 'calendar-web', path: 'apps/calendar/apps/web', port: '5186' }
+ - { name: 'clock-backend', path: 'apps/clock/apps/backend', port: '3017' }
+ - { name: 'clock-web', path: 'apps/clock/apps/web', port: '5187' }
fail-fast: false
steps:
- name: Checkout code
@@ -73,20 +140,3 @@ jobs:
cache-from: type=gha
cache-to: type=gha,mode=max
- # Trigger staging deployment after all images are built
- deploy-staging:
- name: Deploy to Staging
- runs-on: ubuntu-latest
- needs: build-docker-images
- steps:
- - name: Trigger staging deployment
- uses: actions/github-script@v7
- with:
- script: |
- await github.rest.actions.createWorkflowDispatch({
- owner: context.repo.owner,
- repo: context.repo.repo,
- workflow_id: 'cd-staging.yml',
- ref: 'main'
- });
- console.log('Staging deployment triggered');
diff --git a/.gitignore b/.gitignore
index 1a98da2cc..5303d2a89 100644
--- a/.gitignore
+++ b/.gitignore
@@ -95,3 +95,4 @@ yarn.lock
# Claude Flow metrics
.claude-flow/
+.claude-flow/metrics/
diff --git a/CLAUDE.md b/CLAUDE.md
index 229a8a5c2..f925c51e2 100644
--- a/CLAUDE.md
+++ b/CLAUDE.md
@@ -60,19 +60,46 @@ These projects are temporarily archived and excluded from the workspace. To re-a
## Development Commands
-```bash
-# Install dependencies
-pnpm install
+For detailed local development setup, see **[docs/LOCAL_DEVELOPMENT.md](docs/LOCAL_DEVELOPMENT.md)**.
+### Quick Start (Recommended)
+
+Use `dev:*:full` commands to start any app with automatic database setup:
+
+```bash
+pnpm docker:up # Start PostgreSQL, Redis, MinIO
+pnpm dev:chat:full # Start chat with auth + auto DB setup
+pnpm dev:zitare:full # Start zitare with auth + auto DB setup
+pnpm dev:contacts:full # Start contacts with auth + auto DB setup
+pnpm dev:calendar:full # Start calendar with auth + auto DB setup
+pnpm dev:clock:full # Start clock with auth + auto DB setup
+pnpm dev:todo:full # Start todo with auth + auto DB setup
+pnpm dev:picture:full # Start picture with auth + auto DB setup
+```
+
+These commands automatically:
+1. Create the database if missing
+2. Push the latest schema
+3. Start auth, backend, and web with colored output
+
+### Database Setup
+
+```bash
+pnpm setup:db # Setup ALL databases and schemas
+pnpm setup:db:chat # Setup just chat
+pnpm setup:db:auth # Setup just auth
+```
+
+### Individual App Commands
+
+```bash
# Start specific project (runs all apps in project)
pnpm run manacore:dev
pnpm run manadeck:dev
pnpm run picture:dev
pnpm run chat:dev
pnpm run zitare:dev
-pnpm run presi:dev
pnpm run contacts:dev
-pnpm run mail:dev
# Start specific app within project
pnpm run dev:chat:mobile # Just mobile app
@@ -607,7 +634,9 @@ PORT=...
## Project-Specific Documentation
+- **[docs/LOCAL_DEVELOPMENT.md](docs/LOCAL_DEVELOPMENT.md)** - Database setup and `dev:*:full` commands
- **[docs/ENVIRONMENT_VARIABLES.md](docs/ENVIRONMENT_VARIABLES.md)** - Complete environment setup guide
+- **[docs/DATABASE_MIGRATIONS.md](docs/DATABASE_MIGRATIONS.md)** - Migration best practices, CI/CD, rollback procedures
Each project has its own `CLAUDE.md` with detailed information:
diff --git a/TROUBLESHOOTING.md b/TROUBLESHOOTING.md
index aafb6b21d..a84dcb359 100644
--- a/TROUBLESHOOTING.md
+++ b/TROUBLESHOOTING.md
@@ -16,6 +16,8 @@ Common issues and solutions for the manacore-monorepo.
- [Client-Side Calling localhost Instead of Public IP](#problem-5-client-side-calling-localhost-instead-of-public-ip)
- [CORS Blocking Cross-Origin Requests](#problem-6-cors-blocking-cross-origin-requests)
- [Missing Database Schema](#problem-7-missing-database-schema)
+ - [pnpm Symlinks Broken in Docker Container](#problem-8-pnpm-symlinks-broken-in-docker-container)
+ - [Hardcoded localhost URLs in SvelteKit Web Apps](#problem-9-hardcoded-localhost-urls-in-sveltekit-web-apps)
---
@@ -922,6 +924,229 @@ docker compose exec -T chat-backend wget -q -O - http://localhost:3002/api/v1/he
12. **Drizzle Kit Interactive Mode:** `drizzle-kit push` prompts for confirmation. Use `--force` flag in CI/CD to skip interactive mode.
+13. **pnpm Symlinks in Docker:** pnpm uses symlinks to a central `.pnpm` store. When copying `node_modules` in Docker, you must preserve both the symlinks AND the target directory they point to. See [Problem 8](#problem-8-pnpm-symlinks-broken-in-docker-container).
+
+---
+
+### Problem 8: pnpm Symlinks Broken in Docker Container
+
+**Symptoms:**
+
+- Container starts but crashes with: `Cannot find package 'date-fns' imported from /app/build/server/chunks/_page.svelte-xxx.js`
+- Error `ERR_MODULE_NOT_FOUND` for packages that ARE in node_modules
+- Works locally but fails in Docker production stage
+- `ls node_modules/date-fns` shows a symlink pointing to `../../../../../node_modules/.pnpm/...`
+
+**Root Cause:**
+
+pnpm uses symlinks to a central `.pnpm` store at the monorepo root. When you copy only the app's `node_modules` in Docker, the symlinks point to paths that don't exist:
+
+```
+# In builder stage (pnpm workspace):
+/app/apps/todo/apps/web/node_modules/date-fns → ../../../../../node_modules/.pnpm/date-fns@4.1.0/node_modules/date-fns
+
+# In production stage (old broken approach):
+/app/node_modules/date-fns → ../../../../../node_modules/.pnpm/...
+# ↑ BROKEN! This path doesn't exist because we only copied to /app/
+```
+
+**❌ WRONG - Flattening Directory Structure:**
+
+```dockerfile
+# Production stage
+FROM node:20-alpine AS production
+
+WORKDIR /app # ❌ Different from builder structure
+
+# Copy node_modules (symlinks will be broken!)
+COPY --from=builder /app/apps/todo/apps/web/node_modules ./node_modules # ❌ BROKEN
+COPY --from=builder /app/apps/todo/apps/web/build ./build
+COPY --from=builder /app/apps/todo/apps/web/package.json ./
+
+CMD ["node", "build"]
+```
+
+The symlinks in `node_modules` point to `../../../../../node_modules/.pnpm/...` which resolves to a non-existent path from `/app/`.
+
+**✅ CORRECT - Preserve Directory Structure + Copy .pnpm Store:**
+
+```dockerfile
+# Production stage
+FROM node:20-alpine AS production
+
+# Keep same directory structure as builder so pnpm symlinks resolve correctly
+WORKDIR /app/apps/todo/apps/web # ✅ Same as builder
+
+# Copy the pnpm store that symlinks point to (at /app/node_modules/.pnpm)
+COPY --from=builder /app/node_modules/.pnpm /app/node_modules/.pnpm # ✅ Target of symlinks
+
+# Copy the app's node_modules (contains symlinks to the pnpm store)
+COPY --from=builder /app/apps/todo/apps/web/node_modules ./node_modules # ✅ Symlinks work now
+
+# Copy built application
+COPY --from=builder /app/apps/todo/apps/web/build ./build
+COPY --from=builder /app/apps/todo/apps/web/package.json ./
+
+CMD ["node", "build"]
+```
+
+**Why This Works:**
+
+1. `WORKDIR /app/apps/todo/apps/web` - Production container has same path as builder
+2. Symlinks in `./node_modules/` point to `../../../../../node_modules/.pnpm/...`
+3. From `/app/apps/todo/apps/web/node_modules/`, going up 5 directories reaches `/app/`
+4. `/app/node_modules/.pnpm/` exists because we copied it!
+
+**How to Debug:**
+
+```bash
+# Check if symlinks are broken in the container
+docker exec ls -la node_modules/date-fns
+# Shows: date-fns -> ../../../../../node_modules/.pnpm/date-fns@4.1.0/node_modules/date-fns
+
+# Check if the target exists
+docker exec ls -la /app/node_modules/.pnpm/date-fns@4.1.0/
+# If "No such file or directory" → symlink is broken
+
+# Check the image size (should be ~1GB with .pnpm store, ~50MB without)
+docker images | grep todo-web
+```
+
+**Trade-offs:**
+
+| Approach | Image Size | Symlinks | Works |
+| ------------------------------------- | ---------- | -------- | ----- |
+| Copy only app's node_modules | ~50MB | Broken | ❌ |
+| Copy app's node_modules + .pnpm store | ~1GB | Working | ✅ |
+
+The larger image size is the cost of pnpm's deduplication strategy. In a monorepo, this is actually more efficient than copying all dependencies flat.
+
+**Alternative: Use npm Instead of pnpm in Docker:**
+
+If image size is critical, you could use npm in the Docker build:
+
+```dockerfile
+# Alternative approach (not recommended for monorepos)
+FROM node:20-alpine AS production
+WORKDIR /app
+COPY --from=builder /app/apps/todo/apps/web/build ./build
+COPY --from=builder /app/apps/todo/apps/web/package.json ./
+
+# Clean install with npm (flattens dependencies)
+RUN npm install --omit=dev
+
+CMD ["node", "build"]
+```
+
+⚠️ **Warning:** This may fail with `workspace:*` protocol in package.json dependencies. Only works if all dependencies are published to npm.
+
+**Affected Files:**
+
+- `apps/todo/apps/web/Dockerfile`
+- `apps/manacore/apps/web/Dockerfile`
+- `apps/chat/apps/web/Dockerfile`
+- `apps/calendar/apps/web/Dockerfile`
+- `apps/clock/apps/web/Dockerfile`
+
+**Related Commits:**
+
+- `fd1c0ee6` - fix(docker): preserve pnpm symlink structure in web Dockerfiles
+
+---
+
+### Problem 9: Hardcoded localhost URLs in SvelteKit Web Apps
+
+**Symptoms:**
+
+- Browser console shows: `POST http://localhost:3001/api/v1/auth/login net::ERR_CONNECTION_REFUSED`
+- App works locally but auth fails on staging/production
+- The `window.__PUBLIC_MANA_CORE_AUTH_URL__` may be set correctly, but code doesn't use it
+- Looking at the source code reveals hardcoded URLs like `const MANA_AUTH_URL = 'http://localhost:3001'`
+
+**Root Cause:**
+
+Developers hardcode `localhost:3001` directly in TypeScript files instead of using the runtime injection pattern. This works locally but breaks in Docker deployments.
+
+**Common Locations of Hardcoded URLs:**
+
+```typescript
+// ❌ These patterns are WRONG:
+
+// In auth.svelte.ts
+const MANA_AUTH_URL = 'http://localhost:3001';
+
+// In user-settings.svelte.ts
+const MANA_AUTH_URL = 'http://localhost:3001';
+
+// In feedback.ts or feedback page
+apiUrl: 'http://localhost:3001',
+
+// Using build-time env vars (also wrong for Docker)
+const MANA_AUTH_URL = import.meta.env.PUBLIC_MANA_CORE_AUTH_URL || 'http://localhost:3001';
+```
+
+**Solution:**
+
+1. **Create `hooks.server.ts`** if it doesn't exist (see Problem 5)
+2. **Use `getAuthUrl()` pattern in all files:**
+
+```typescript
+// ✅ CORRECT pattern
+import { browser } from '$app/environment';
+
+function getAuthUrl(): string {
+ if (browser && typeof window !== 'undefined') {
+ const injectedUrl = (window as unknown as { __PUBLIC_MANA_CORE_AUTH_URL__?: string })
+ .__PUBLIC_MANA_CORE_AUTH_URL__;
+ return injectedUrl || 'http://localhost:3001';
+ }
+ return process.env.PUBLIC_MANA_CORE_AUTH_URL || 'http://localhost:3001';
+}
+
+// Use getAuthUrl() instead of hardcoded string
+const auth = initializeWebAuth({ baseUrl: getAuthUrl() });
+```
+
+**How to Find Hardcoded URLs:**
+
+```bash
+# Search for hardcoded localhost:3001 in web apps
+grep -r "localhost:3001" apps/*/apps/web/src --include="*.ts" --include="*.svelte"
+
+# Check for the correct pattern (window injection)
+grep -r "__PUBLIC_MANA_CORE_AUTH_URL__" apps/*/apps/web/src
+```
+
+**Apps Status (as of 2024-12):**
+
+| App | Status | Files to Fix |
+| ------------------- | ------------ | ---------------------------------------------------------------- |
+| `chat/apps/web` | ✅ Fixed | - |
+| `todo/apps/web` | ✅ Fixed | - |
+| `calendar/apps/web` | ✅ Fixed | - |
+| `clock/apps/web` | ✅ Fixed | - |
+| `contacts/apps/web` | ❌ Needs Fix | auth.svelte.ts, user-settings.svelte.ts, feedback.ts |
+| `manadeck/apps/web` | ❌ Needs Fix | user-settings.svelte.ts, feedback.ts |
+| `manacore/apps/web` | ❌ Needs Fix | auth.svelte.ts, user-settings.svelte.ts, feedback.ts, credits.ts |
+| `zitare/apps/web` | ❌ Needs Fix | auth.svelte.ts, user-settings.svelte.ts, feedback.ts |
+| `picture/apps/web` | ❌ Needs Fix | user-settings.svelte.ts |
+
+**Complete Fix Checklist for Each App:**
+
+- [ ] Create/update `src/hooks.server.ts` with env injection
+- [ ] Update `src/lib/stores/auth.svelte.ts` → use `getAuthUrl()` pattern
+- [ ] Update `src/lib/stores/user-settings.svelte.ts` → use `getAuthUrl()` pattern
+- [ ] Update any `feedback.ts` or feedback services → use `getAuthUrl()` pattern
+- [ ] Update any other files with hardcoded URLs
+- [ ] Add `PUBLIC_MANA_CORE_AUTH_URL_CLIENT` to `docker-compose.staging.yml`
+- [ ] Test locally with `pnpm dev`
+- [ ] Deploy and test on staging
+
+**See Also:**
+
+- [Problem 5: Client-Side Calling localhost Instead of Public IP](#problem-5-client-side-calling-localhost-instead-of-public-ip)
+- [SvelteKit Web Guidelines - Environment Variables](/.claude/guidelines/sveltekit-web.md#environment-variables)
+
---
## References
diff --git a/apps/calendar/apps/backend/Dockerfile b/apps/calendar/apps/backend/Dockerfile
new file mode 100644
index 000000000..129862166
--- /dev/null
+++ b/apps/calendar/apps/backend/Dockerfile
@@ -0,0 +1,68 @@
+# Build stage
+FROM node:20-alpine AS builder
+
+# Install pnpm
+RUN corepack enable && corepack prepare pnpm@9.15.0 --activate
+
+WORKDIR /app
+
+# Copy root workspace files
+COPY pnpm-workspace.yaml ./
+COPY package.json ./
+COPY pnpm-lock.yaml ./
+
+# Copy shared packages
+COPY packages/shared-errors ./packages/shared-errors
+COPY packages/shared-nestjs-auth ./packages/shared-nestjs-auth
+
+# Copy calendar packages and backend
+COPY apps/calendar/packages ./apps/calendar/packages
+COPY apps/calendar/apps/backend ./apps/calendar/apps/backend
+
+# Install dependencies
+RUN pnpm install --frozen-lockfile
+
+# Build shared packages first
+WORKDIR /app/packages/shared-errors
+RUN pnpm build
+
+WORKDIR /app/packages/shared-nestjs-auth
+RUN pnpm build
+
+# Build the backend
+WORKDIR /app/apps/calendar/apps/backend
+RUN pnpm build
+
+# Production stage
+FROM node:20-alpine AS production
+
+# Install pnpm and postgresql-client for health checks
+RUN corepack enable && corepack prepare pnpm@9.15.0 --activate \
+ && apk add --no-cache postgresql-client
+
+WORKDIR /app
+
+# Copy everything from builder (including node_modules)
+COPY --from=builder /app/pnpm-workspace.yaml ./
+COPY --from=builder /app/package.json ./
+COPY --from=builder /app/pnpm-lock.yaml ./
+COPY --from=builder /app/node_modules ./node_modules
+COPY --from=builder /app/packages ./packages
+COPY --from=builder /app/apps/calendar ./apps/calendar
+
+# Copy entrypoint script
+COPY apps/calendar/apps/backend/docker-entrypoint.sh /usr/local/bin/
+RUN chmod +x /usr/local/bin/docker-entrypoint.sh
+
+WORKDIR /app/apps/calendar/apps/backend
+
+# Expose port
+EXPOSE 3016
+
+# Health check
+HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 \
+ CMD wget --no-verbose --tries=1 --spider http://localhost:3016/api/v1/health || exit 1
+
+# Run entrypoint script
+ENTRYPOINT ["docker-entrypoint.sh"]
+CMD ["node", "dist/main.js"]
diff --git a/apps/calendar/apps/backend/docker-entrypoint.sh b/apps/calendar/apps/backend/docker-entrypoint.sh
new file mode 100644
index 000000000..bda857019
--- /dev/null
+++ b/apps/calendar/apps/backend/docker-entrypoint.sh
@@ -0,0 +1,23 @@
+#!/bin/sh
+set -e
+
+echo "=== Calendar Backend Entrypoint ==="
+
+# Wait for PostgreSQL to be ready
+echo "Waiting for PostgreSQL..."
+until pg_isready -h ${DB_HOST:-postgres} -p ${DB_PORT:-5432} -U ${DB_USER:-postgres} 2>/dev/null; do
+ echo "PostgreSQL is unavailable - sleeping"
+ sleep 2
+done
+echo "PostgreSQL is up!"
+
+cd /app/apps/calendar/apps/backend
+
+# Run schema push
+echo "Pushing database schema..."
+npx drizzle-kit push --force
+echo "Schema push completed!"
+
+# Execute the main command
+echo "Starting application..."
+exec "$@"
diff --git a/apps/calendar/apps/web/Dockerfile b/apps/calendar/apps/web/Dockerfile
new file mode 100644
index 000000000..53c964525
--- /dev/null
+++ b/apps/calendar/apps/web/Dockerfile
@@ -0,0 +1,84 @@
+# Build stage
+FROM node:20-alpine AS builder
+
+# Build arguments for SvelteKit static env vars
+ARG PUBLIC_BACKEND_URL=http://calendar-backend:3016
+ARG PUBLIC_MANA_CORE_AUTH_URL=http://mana-core-auth:3001
+
+# Set as environment variables for build
+ENV PUBLIC_BACKEND_URL=$PUBLIC_BACKEND_URL
+ENV PUBLIC_MANA_CORE_AUTH_URL=$PUBLIC_MANA_CORE_AUTH_URL
+
+# Install pnpm
+RUN corepack enable && corepack prepare pnpm@9.15.0 --activate
+
+WORKDIR /app
+
+# Copy root workspace files
+COPY pnpm-workspace.yaml ./
+COPY package.json ./
+COPY pnpm-lock.yaml ./
+
+# Copy shared packages needed by calendar web
+COPY packages/shared-auth ./packages/shared-auth
+COPY packages/shared-auth-ui ./packages/shared-auth-ui
+COPY packages/shared-branding ./packages/shared-branding
+COPY packages/shared-feedback-service ./packages/shared-feedback-service
+COPY packages/shared-feedback-types ./packages/shared-feedback-types
+COPY packages/shared-feedback-ui ./packages/shared-feedback-ui
+COPY packages/shared-i18n ./packages/shared-i18n
+COPY packages/shared-icons ./packages/shared-icons
+COPY packages/shared-tailwind ./packages/shared-tailwind
+COPY packages/shared-theme ./packages/shared-theme
+COPY packages/shared-theme-ui ./packages/shared-theme-ui
+COPY packages/shared-subscription-types ./packages/shared-subscription-types
+COPY packages/shared-subscription-ui ./packages/shared-subscription-ui
+COPY packages/shared-profile-ui ./packages/shared-profile-ui
+COPY packages/shared-ui ./packages/shared-ui
+COPY packages/shared-utils ./packages/shared-utils
+
+# Copy calendar packages and web
+COPY apps/calendar/packages ./apps/calendar/packages
+COPY apps/calendar/apps/web ./apps/calendar/apps/web
+
+# Install dependencies
+RUN pnpm install --frozen-lockfile
+
+# Build shared packages that need building
+WORKDIR /app/packages/shared-auth
+RUN pnpm build || true
+
+# Build the web app
+WORKDIR /app/apps/calendar/apps/web
+RUN pnpm build
+
+# Production stage
+FROM node:20-alpine AS production
+
+# Keep same directory structure as builder so pnpm symlinks resolve correctly
+WORKDIR /app/apps/calendar/apps/web
+
+# Copy the pnpm store that symlinks point to (at /app/node_modules/.pnpm)
+COPY --from=builder /app/node_modules/.pnpm /app/node_modules/.pnpm
+
+# Copy the app's node_modules (contains symlinks to the pnpm store)
+COPY --from=builder /app/apps/calendar/apps/web/node_modules ./node_modules
+
+# Copy built application
+COPY --from=builder /app/apps/calendar/apps/web/build ./build
+COPY --from=builder /app/apps/calendar/apps/web/package.json ./
+
+# Expose port
+EXPOSE 5186
+
+# Set environment variables
+ENV NODE_ENV=production
+ENV PORT=5186
+ENV HOST=0.0.0.0
+
+# Health check
+HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 \
+ CMD wget --no-verbose --tries=1 --spider http://localhost:5186/health || exit 1
+
+# Run the app
+CMD ["node", "build"]
diff --git a/apps/calendar/apps/web/package.json b/apps/calendar/apps/web/package.json
index d76fb665d..4f71acf61 100644
--- a/apps/calendar/apps/web/package.json
+++ b/apps/calendar/apps/web/package.json
@@ -13,7 +13,7 @@
"type-check": "echo 'Skipping type-check for now'"
},
"devDependencies": {
- "@sveltejs/adapter-auto": "^3.0.0",
+ "@sveltejs/adapter-node": "^5.0.0",
"@sveltejs/kit": "^2.0.0",
"@sveltejs/vite-plugin-svelte": "^5.0.0",
"@tailwindcss/vite": "^4.1.7",
diff --git a/apps/calendar/apps/web/src/hooks.server.ts b/apps/calendar/apps/web/src/hooks.server.ts
new file mode 100644
index 000000000..6d7a5089d
--- /dev/null
+++ b/apps/calendar/apps/web/src/hooks.server.ts
@@ -0,0 +1,27 @@
+/**
+ * Server Hooks for SvelteKit
+ * - Injects runtime environment variables for client-side use
+ * - Auth is handled client-side via Mana Core Auth
+ */
+
+import type { Handle } from '@sveltejs/kit';
+
+// Get client-side URLs from environment (Docker runtime)
+const PUBLIC_MANA_CORE_AUTH_URL_CLIENT =
+ process.env.PUBLIC_MANA_CORE_AUTH_URL_CLIENT || process.env.PUBLIC_MANA_CORE_AUTH_URL || '';
+const PUBLIC_BACKEND_URL_CLIENT =
+ process.env.PUBLIC_BACKEND_URL_CLIENT || process.env.PUBLIC_BACKEND_URL || '';
+
+export const handle: Handle = async ({ event, resolve }) => {
+ return resolve(event, {
+ transformPageChunk: ({ html }) => {
+ // Inject runtime environment variables into the HTML
+ // These will be available on window.__PUBLIC_*__ for client-side code
+ const envScript = ``;
+ return html.replace('', `${envScript}`);
+ },
+ });
+};
diff --git a/apps/calendar/apps/web/src/lib/services/feedback.ts b/apps/calendar/apps/web/src/lib/services/feedback.ts
index 5f86dbd7c..de6a8a6d0 100644
--- a/apps/calendar/apps/web/src/lib/services/feedback.ts
+++ b/apps/calendar/apps/web/src/lib/services/feedback.ts
@@ -2,14 +2,22 @@
* Feedback Service Instance for Calendar Web App
*/
+import { browser } from '$app/environment';
import { createFeedbackService } from '@manacore/shared-feedback-service';
import { authStore } from '$lib/stores/auth.svelte';
-import { PUBLIC_MANA_CORE_AUTH_URL } from '$env/static/public';
-const MANA_AUTH_URL = PUBLIC_MANA_CORE_AUTH_URL || 'http://localhost:3001';
+// Get auth URL dynamically at runtime
+function getAuthUrl(): string {
+ if (browser && typeof window !== 'undefined') {
+ const injectedUrl = (window as unknown as { __PUBLIC_MANA_CORE_AUTH_URL__?: string })
+ .__PUBLIC_MANA_CORE_AUTH_URL__;
+ return injectedUrl || 'http://localhost:3001';
+ }
+ return 'http://localhost:3001';
+}
export const feedbackService = createFeedbackService({
- apiUrl: MANA_AUTH_URL,
+ apiUrl: getAuthUrl(),
appId: 'calendar',
getAuthToken: async () => authStore.getAccessToken(),
});
diff --git a/apps/calendar/apps/web/src/lib/stores/auth.svelte.ts b/apps/calendar/apps/web/src/lib/stores/auth.svelte.ts
index da5eceae2..874675890 100644
--- a/apps/calendar/apps/web/src/lib/stores/auth.svelte.ts
+++ b/apps/calendar/apps/web/src/lib/stores/auth.svelte.ts
@@ -7,8 +7,18 @@ import { browser } from '$app/environment';
import { initializeWebAuth } from '@manacore/shared-auth';
import type { UserData } from '@manacore/shared-auth';
-// Initialize Mana Core Auth only on the client side
-const MANA_AUTH_URL = 'http://localhost:3001';
+// Get auth URL dynamically at runtime - fallback for SSR and client
+function getAuthUrl(): string {
+ if (browser && typeof window !== 'undefined') {
+ // Client-side: use injected window variable (set by hooks.server.ts)
+ // Falls back to localhost for local development
+ const injectedUrl = (window as unknown as { __PUBLIC_MANA_CORE_AUTH_URL__?: string })
+ .__PUBLIC_MANA_CORE_AUTH_URL__;
+ return injectedUrl || 'http://localhost:3001';
+ }
+ // Server-side (SSR): use Docker internal URL for container-to-container communication
+ return process.env.PUBLIC_MANA_CORE_AUTH_URL || 'http://localhost:3001';
+}
// Lazy initialization to avoid SSR issues with localStorage
let _authService: ReturnType['authService'] | null = null;
@@ -17,7 +27,7 @@ let _tokenManager: ReturnType['tokenManager'] | null =
function getAuthService() {
if (!browser) return null;
if (!_authService) {
- const auth = initializeWebAuth({ baseUrl: MANA_AUTH_URL });
+ const auth = initializeWebAuth({ baseUrl: getAuthUrl() });
_authService = auth.authService;
_tokenManager = auth.tokenManager;
}
diff --git a/apps/calendar/apps/web/src/lib/stores/user-settings.svelte.ts b/apps/calendar/apps/web/src/lib/stores/user-settings.svelte.ts
index 6cc7b62ed..a2e5e08b7 100644
--- a/apps/calendar/apps/web/src/lib/stores/user-settings.svelte.ts
+++ b/apps/calendar/apps/web/src/lib/stores/user-settings.svelte.ts
@@ -7,13 +7,22 @@
* - localStorage caching for offline support
*/
+import { browser } from '$app/environment';
import { createUserSettingsStore } from '@manacore/shared-theme';
import { authStore } from './auth.svelte';
-const MANA_AUTH_URL = 'http://localhost:3001';
+// Get auth URL dynamically at runtime
+function getAuthUrl(): string {
+ if (browser && typeof window !== 'undefined') {
+ const injectedUrl = (window as unknown as { __PUBLIC_MANA_CORE_AUTH_URL__?: string })
+ .__PUBLIC_MANA_CORE_AUTH_URL__;
+ return injectedUrl || 'http://localhost:3001';
+ }
+ return 'http://localhost:3001';
+}
export const userSettings = createUserSettingsStore({
appId: 'calendar',
- authUrl: MANA_AUTH_URL,
+ authUrl: getAuthUrl(),
getAccessToken: () => authStore.getAccessToken(),
});
diff --git a/apps/calendar/apps/web/src/routes/+layout.svelte b/apps/calendar/apps/web/src/routes/+layout.svelte
index 6d8411e8f..6cbb7c5d9 100644
--- a/apps/calendar/apps/web/src/routes/+layout.svelte
+++ b/apps/calendar/apps/web/src/routes/+layout.svelte
@@ -32,52 +32,7 @@
{:else}
-
-
-
-
-
- {@render children()}
-
-
+
+ {@render children()}
{/if}
diff --git a/apps/calendar/apps/web/svelte.config.js b/apps/calendar/apps/web/svelte.config.js
index c8b303bb6..a7a917e4c 100644
--- a/apps/calendar/apps/web/svelte.config.js
+++ b/apps/calendar/apps/web/svelte.config.js
@@ -1,11 +1,13 @@
-import adapter from '@sveltejs/adapter-auto';
+import adapter from '@sveltejs/adapter-node';
import { vitePreprocess } from '@sveltejs/vite-plugin-svelte';
/** @type {import('@sveltejs/kit').Config} */
const config = {
preprocess: vitePreprocess(),
kit: {
- adapter: adapter(),
+ adapter: adapter({
+ out: 'build',
+ }),
},
};
diff --git a/apps/chat/apps/web/Dockerfile b/apps/chat/apps/web/Dockerfile
index e6200335f..544d462a2 100644
--- a/apps/chat/apps/web/Dockerfile
+++ b/apps/chat/apps/web/Dockerfile
@@ -1,6 +1,14 @@
# Build stage
FROM node:20-alpine AS builder
+# Build arguments for SvelteKit static env vars
+ARG PUBLIC_BACKEND_URL=http://chat-backend:3002
+ARG PUBLIC_MANA_CORE_AUTH_URL=http://mana-core-auth:3001
+
+# Set as environment variables for build
+ENV PUBLIC_BACKEND_URL=$PUBLIC_BACKEND_URL
+ENV PUBLIC_MANA_CORE_AUTH_URL=$PUBLIC_MANA_CORE_AUTH_URL
+
# Install pnpm
RUN corepack enable && corepack prepare pnpm@9.15.0 --activate
@@ -47,15 +55,19 @@ RUN pnpm build
# Production stage
FROM node:20-alpine AS production
-WORKDIR /app
+# Keep same directory structure as builder so pnpm symlinks resolve correctly
+WORKDIR /app/apps/chat/apps/web
+
+# Copy the pnpm store that symlinks point to (at /app/node_modules/.pnpm)
+COPY --from=builder /app/node_modules/.pnpm /app/node_modules/.pnpm
+
+# Copy the app's node_modules (contains symlinks to the pnpm store)
+COPY --from=builder /app/apps/chat/apps/web/node_modules ./node_modules
# Copy built application
COPY --from=builder /app/apps/chat/apps/web/build ./build
COPY --from=builder /app/apps/chat/apps/web/package.json ./
-# Install only production dependencies for the built app
-RUN npm install --omit=dev 2>/dev/null || true
-
# Expose port
EXPOSE 3000
diff --git a/apps/clock/apps/backend/Dockerfile b/apps/clock/apps/backend/Dockerfile
new file mode 100644
index 000000000..a2b05c80b
--- /dev/null
+++ b/apps/clock/apps/backend/Dockerfile
@@ -0,0 +1,68 @@
+# Build stage
+FROM node:20-alpine AS builder
+
+# Install pnpm
+RUN corepack enable && corepack prepare pnpm@9.15.0 --activate
+
+WORKDIR /app
+
+# Copy root workspace files
+COPY pnpm-workspace.yaml ./
+COPY package.json ./
+COPY pnpm-lock.yaml ./
+
+# Copy shared packages
+COPY packages/shared-errors ./packages/shared-errors
+COPY packages/shared-nestjs-auth ./packages/shared-nestjs-auth
+
+# Copy clock packages and backend
+COPY apps/clock/packages ./apps/clock/packages
+COPY apps/clock/apps/backend ./apps/clock/apps/backend
+
+# Install dependencies
+RUN pnpm install --frozen-lockfile
+
+# Build shared packages first
+WORKDIR /app/packages/shared-errors
+RUN pnpm build
+
+WORKDIR /app/packages/shared-nestjs-auth
+RUN pnpm build
+
+# Build the backend
+WORKDIR /app/apps/clock/apps/backend
+RUN pnpm build
+
+# Production stage
+FROM node:20-alpine AS production
+
+# Install pnpm and postgresql-client for health checks
+RUN corepack enable && corepack prepare pnpm@9.15.0 --activate \
+ && apk add --no-cache postgresql-client
+
+WORKDIR /app
+
+# Copy everything from builder (including node_modules)
+COPY --from=builder /app/pnpm-workspace.yaml ./
+COPY --from=builder /app/package.json ./
+COPY --from=builder /app/pnpm-lock.yaml ./
+COPY --from=builder /app/node_modules ./node_modules
+COPY --from=builder /app/packages ./packages
+COPY --from=builder /app/apps/clock ./apps/clock
+
+# Copy entrypoint script
+COPY apps/clock/apps/backend/docker-entrypoint.sh /usr/local/bin/
+RUN chmod +x /usr/local/bin/docker-entrypoint.sh
+
+WORKDIR /app/apps/clock/apps/backend
+
+# Expose port
+EXPOSE 3017
+
+# Health check
+HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 \
+ CMD wget --no-verbose --tries=1 --spider http://localhost:3017/api/v1/health || exit 1
+
+# Run entrypoint script
+ENTRYPOINT ["docker-entrypoint.sh"]
+CMD ["node", "dist/main.js"]
diff --git a/apps/clock/apps/backend/docker-entrypoint.sh b/apps/clock/apps/backend/docker-entrypoint.sh
new file mode 100644
index 000000000..f34aebf6e
--- /dev/null
+++ b/apps/clock/apps/backend/docker-entrypoint.sh
@@ -0,0 +1,23 @@
+#!/bin/sh
+set -e
+
+echo "=== Clock Backend Entrypoint ==="
+
+# Wait for PostgreSQL to be ready
+echo "Waiting for PostgreSQL..."
+until pg_isready -h ${DB_HOST:-postgres} -p ${DB_PORT:-5432} -U ${DB_USER:-postgres} 2>/dev/null; do
+ echo "PostgreSQL is unavailable - sleeping"
+ sleep 2
+done
+echo "PostgreSQL is up!"
+
+cd /app/apps/clock/apps/backend
+
+# Run schema push
+echo "Pushing database schema..."
+npx drizzle-kit push --force
+echo "Schema push completed!"
+
+# Execute the main command
+echo "Starting application..."
+exec "$@"
diff --git a/apps/clock/apps/web/Dockerfile b/apps/clock/apps/web/Dockerfile
new file mode 100644
index 000000000..2f5e6c366
--- /dev/null
+++ b/apps/clock/apps/web/Dockerfile
@@ -0,0 +1,84 @@
+# Build stage
+FROM node:20-alpine AS builder
+
+# Build arguments for SvelteKit static env vars
+ARG PUBLIC_BACKEND_URL=http://clock-backend:3017
+ARG PUBLIC_MANA_CORE_AUTH_URL=http://mana-core-auth:3001
+
+# Set as environment variables for build
+ENV PUBLIC_BACKEND_URL=$PUBLIC_BACKEND_URL
+ENV PUBLIC_MANA_CORE_AUTH_URL=$PUBLIC_MANA_CORE_AUTH_URL
+
+# Install pnpm
+RUN corepack enable && corepack prepare pnpm@9.15.0 --activate
+
+WORKDIR /app
+
+# Copy root workspace files
+COPY pnpm-workspace.yaml ./
+COPY package.json ./
+COPY pnpm-lock.yaml ./
+
+# Copy shared packages needed by clock web
+COPY packages/shared-auth ./packages/shared-auth
+COPY packages/shared-auth-ui ./packages/shared-auth-ui
+COPY packages/shared-branding ./packages/shared-branding
+COPY packages/shared-feedback-service ./packages/shared-feedback-service
+COPY packages/shared-feedback-types ./packages/shared-feedback-types
+COPY packages/shared-feedback-ui ./packages/shared-feedback-ui
+COPY packages/shared-i18n ./packages/shared-i18n
+COPY packages/shared-icons ./packages/shared-icons
+COPY packages/shared-tailwind ./packages/shared-tailwind
+COPY packages/shared-theme ./packages/shared-theme
+COPY packages/shared-theme-ui ./packages/shared-theme-ui
+COPY packages/shared-subscription-types ./packages/shared-subscription-types
+COPY packages/shared-subscription-ui ./packages/shared-subscription-ui
+COPY packages/shared-profile-ui ./packages/shared-profile-ui
+COPY packages/shared-ui ./packages/shared-ui
+COPY packages/shared-utils ./packages/shared-utils
+
+# Copy clock packages and web
+COPY apps/clock/packages ./apps/clock/packages
+COPY apps/clock/apps/web ./apps/clock/apps/web
+
+# Install dependencies
+RUN pnpm install --frozen-lockfile
+
+# Build shared packages that need building
+WORKDIR /app/packages/shared-auth
+RUN pnpm build || true
+
+# Build the web app
+WORKDIR /app/apps/clock/apps/web
+RUN pnpm build
+
+# Production stage
+FROM node:20-alpine AS production
+
+# Keep same directory structure as builder so pnpm symlinks resolve correctly
+WORKDIR /app/apps/clock/apps/web
+
+# Copy the pnpm store that symlinks point to (at /app/node_modules/.pnpm)
+COPY --from=builder /app/node_modules/.pnpm /app/node_modules/.pnpm
+
+# Copy the app's node_modules (contains symlinks to the pnpm store)
+COPY --from=builder /app/apps/clock/apps/web/node_modules ./node_modules
+
+# Copy built application
+COPY --from=builder /app/apps/clock/apps/web/build ./build
+COPY --from=builder /app/apps/clock/apps/web/package.json ./
+
+# Expose port
+EXPOSE 5187
+
+# Set environment variables
+ENV NODE_ENV=production
+ENV PORT=5187
+ENV HOST=0.0.0.0
+
+# Health check
+HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 \
+ CMD wget --no-verbose --tries=1 --spider http://localhost:5187/health || exit 1
+
+# Run the app
+CMD ["node", "build"]
diff --git a/apps/clock/apps/web/package.json b/apps/clock/apps/web/package.json
index c6ae5a7cf..b279bf2c2 100644
--- a/apps/clock/apps/web/package.json
+++ b/apps/clock/apps/web/package.json
@@ -13,7 +13,7 @@
"type-check": "echo 'Skipping type-check for now'"
},
"devDependencies": {
- "@sveltejs/adapter-auto": "^3.0.0",
+ "@sveltejs/adapter-node": "^5.0.0",
"@sveltejs/kit": "^2.0.0",
"@sveltejs/vite-plugin-svelte": "^5.0.0",
"@tailwindcss/vite": "^4.1.7",
diff --git a/apps/clock/apps/web/src/hooks.server.ts b/apps/clock/apps/web/src/hooks.server.ts
new file mode 100644
index 000000000..6d7a5089d
--- /dev/null
+++ b/apps/clock/apps/web/src/hooks.server.ts
@@ -0,0 +1,27 @@
+/**
+ * Server Hooks for SvelteKit
+ * - Injects runtime environment variables for client-side use
+ * - Auth is handled client-side via Mana Core Auth
+ */
+
+import type { Handle } from '@sveltejs/kit';
+
+// Get client-side URLs from environment (Docker runtime)
+const PUBLIC_MANA_CORE_AUTH_URL_CLIENT =
+ process.env.PUBLIC_MANA_CORE_AUTH_URL_CLIENT || process.env.PUBLIC_MANA_CORE_AUTH_URL || '';
+const PUBLIC_BACKEND_URL_CLIENT =
+ process.env.PUBLIC_BACKEND_URL_CLIENT || process.env.PUBLIC_BACKEND_URL || '';
+
+export const handle: Handle = async ({ event, resolve }) => {
+ return resolve(event, {
+ transformPageChunk: ({ html }) => {
+ // Inject runtime environment variables into the HTML
+ // These will be available on window.__PUBLIC_*__ for client-side code
+ const envScript = ``;
+ return html.replace('', `${envScript}`);
+ },
+ });
+};
diff --git a/apps/clock/apps/web/src/lib/stores/auth.svelte.ts b/apps/clock/apps/web/src/lib/stores/auth.svelte.ts
index 555153459..6be177cbe 100644
--- a/apps/clock/apps/web/src/lib/stores/auth.svelte.ts
+++ b/apps/clock/apps/web/src/lib/stores/auth.svelte.ts
@@ -6,8 +6,18 @@
import { browser } from '$app/environment';
import { initializeWebAuth, type UserData } from '@manacore/shared-auth';
-// Initialize Mana Core Auth only on the client side
-const MANA_AUTH_URL = 'http://localhost:3001';
+// Get auth URL dynamically at runtime - fallback for SSR and client
+function getAuthUrl(): string {
+ if (browser && typeof window !== 'undefined') {
+ // Client-side: use injected window variable (set by hooks.server.ts)
+ // Falls back to localhost for local development
+ const injectedUrl = (window as unknown as { __PUBLIC_MANA_CORE_AUTH_URL__?: string })
+ .__PUBLIC_MANA_CORE_AUTH_URL__;
+ return injectedUrl || 'http://localhost:3001';
+ }
+ // Server-side (SSR): use Docker internal URL for container-to-container communication
+ return process.env.PUBLIC_MANA_CORE_AUTH_URL || 'http://localhost:3001';
+}
// Lazy initialization to avoid SSR issues with localStorage
let _authService: ReturnType
['authService'] | null = null;
@@ -16,7 +26,7 @@ let _tokenManager: ReturnType['tokenManager'] | null =
function getAuthService() {
if (!browser) return null;
if (!_authService) {
- const auth = initializeWebAuth({ baseUrl: MANA_AUTH_URL });
+ const auth = initializeWebAuth({ baseUrl: getAuthUrl() });
_authService = auth.authService;
_tokenManager = auth.tokenManager;
}
diff --git a/apps/clock/apps/web/src/lib/stores/user-settings.svelte.ts b/apps/clock/apps/web/src/lib/stores/user-settings.svelte.ts
index 3b9727cdc..f88fb5453 100644
--- a/apps/clock/apps/web/src/lib/stores/user-settings.svelte.ts
+++ b/apps/clock/apps/web/src/lib/stores/user-settings.svelte.ts
@@ -7,13 +7,22 @@
* - localStorage caching for offline support
*/
+import { browser } from '$app/environment';
import { createUserSettingsStore } from '@manacore/shared-theme';
import { authStore } from './auth.svelte';
-const MANA_AUTH_URL = 'http://localhost:3001';
+// Get auth URL dynamically at runtime
+function getAuthUrl(): string {
+ if (browser && typeof window !== 'undefined') {
+ const injectedUrl = (window as unknown as { __PUBLIC_MANA_CORE_AUTH_URL__?: string })
+ .__PUBLIC_MANA_CORE_AUTH_URL__;
+ return injectedUrl || 'http://localhost:3001';
+ }
+ return 'http://localhost:3001';
+}
export const userSettings = createUserSettingsStore({
appId: 'clock',
- authUrl: MANA_AUTH_URL,
+ authUrl: getAuthUrl(),
getAccessToken: () => authStore.getAccessToken(),
});
diff --git a/apps/clock/apps/web/src/routes/(app)/feedback/+page.svelte b/apps/clock/apps/web/src/routes/(app)/feedback/+page.svelte
index 1a2521e87..1d3c8d9c2 100644
--- a/apps/clock/apps/web/src/routes/(app)/feedback/+page.svelte
+++ b/apps/clock/apps/web/src/routes/(app)/feedback/+page.svelte
@@ -1,12 +1,23 @@
diff --git a/apps/clock/apps/web/svelte.config.js b/apps/clock/apps/web/svelte.config.js
index c8b303bb6..a7a917e4c 100644
--- a/apps/clock/apps/web/svelte.config.js
+++ b/apps/clock/apps/web/svelte.config.js
@@ -1,11 +1,13 @@
-import adapter from '@sveltejs/adapter-auto';
+import adapter from '@sveltejs/adapter-node';
import { vitePreprocess } from '@sveltejs/vite-plugin-svelte';
/** @type {import('@sveltejs/kit').Config} */
const config = {
preprocess: vitePreprocess(),
kit: {
- adapter: adapter(),
+ adapter: adapter({
+ out: 'build',
+ }),
},
};
diff --git a/apps/context/apps/mobile/components/documents/BatchDocumentCreator.tsx b/apps/context/apps/mobile/components/documents/BatchDocumentCreator.tsx
index 767a762ac..701e23324 100644
--- a/apps/context/apps/mobile/components/documents/BatchDocumentCreator.tsx
+++ b/apps/context/apps/mobile/components/documents/BatchDocumentCreator.tsx
@@ -50,9 +50,9 @@ export const BatchDocumentCreator: React.FC = ({
const [subjectList, setSubjectList] = useState([]);
const [documents, setDocuments] = useState([]);
const [selectedDocuments, setSelectedDocuments] = useState([]);
- const [documentFilter, setDocumentFilter] = useState<
- 'all' | 'text' | 'context' | 'prompt'
- >('context');
+ const [documentFilter, setDocumentFilter] = useState<'all' | 'text' | 'context' | 'prompt'>(
+ 'context'
+ );
const [promptDocuments, setPromptDocuments] = useState([]);
const { mode, colors } = useTheme();
const isDark = mode === 'dark';
diff --git a/apps/context/apps/mobile/components/navigation/Breadcrumbs.tsx b/apps/context/apps/mobile/components/navigation/Breadcrumbs.tsx
index bbc3537ce..676242dc2 100644
--- a/apps/context/apps/mobile/components/navigation/Breadcrumbs.tsx
+++ b/apps/context/apps/mobile/components/navigation/Breadcrumbs.tsx
@@ -216,9 +216,7 @@ export const Breadcrumbs: React.FC = ({
? 'font-medium text-gray-800 dark:text-gray-200'
: 'text-gray-500 dark:text-gray-400'
)}
- style={[
- pressed && !isLast && styles.textHovered,
- ]}
+ style={[pressed && !isLast && styles.textHovered]}
>
{item.label}
diff --git a/apps/context/apps/mobile/components/ui/FilterPill.tsx b/apps/context/apps/mobile/components/ui/FilterPill.tsx
index e61c519d6..e9ff5c3dc 100644
--- a/apps/context/apps/mobile/components/ui/FilterPill.tsx
+++ b/apps/context/apps/mobile/components/ui/FilterPill.tsx
@@ -186,7 +186,7 @@ export const FilterPill: React.FC = ({
: isDark
? '#1f2937'
: '#d1d5db',
- opacity: disabled ? 0.6 : (pressed ? 0.8 : 1),
+ opacity: disabled ? 0.6 : pressed ? 0.8 : 1,
},
]}
onPress={disabled ? undefined : actionButton.onPress}
diff --git a/apps/manacore/apps/web/Dockerfile b/apps/manacore/apps/web/Dockerfile
new file mode 100644
index 000000000..b400e79d5
--- /dev/null
+++ b/apps/manacore/apps/web/Dockerfile
@@ -0,0 +1,85 @@
+# Build stage
+FROM node:20-alpine AS builder
+
+# Build arguments for SvelteKit static env vars
+ARG PUBLIC_MANA_CORE_AUTH_URL=http://mana-core-auth:3001
+ARG MIDDLEWARE_URL=http://mana-core-middleware:3000
+
+# Set as environment variables for build
+ENV PUBLIC_MANA_CORE_AUTH_URL=$PUBLIC_MANA_CORE_AUTH_URL
+ENV MIDDLEWARE_URL=$MIDDLEWARE_URL
+
+# Install pnpm
+RUN corepack enable && corepack prepare pnpm@9.15.0 --activate
+
+WORKDIR /app
+
+# Copy root workspace files
+COPY pnpm-workspace.yaml ./
+COPY package.json ./
+COPY pnpm-lock.yaml ./
+
+# Copy shared packages needed by manacore web
+COPY packages/shared-auth ./packages/shared-auth
+COPY packages/shared-auth-ui ./packages/shared-auth-ui
+COPY packages/shared-branding ./packages/shared-branding
+COPY packages/shared-config ./packages/shared-config
+COPY packages/shared-feedback-service ./packages/shared-feedback-service
+COPY packages/shared-feedback-types ./packages/shared-feedback-types
+COPY packages/shared-feedback-ui ./packages/shared-feedback-ui
+COPY packages/shared-i18n ./packages/shared-i18n
+COPY packages/shared-icons ./packages/shared-icons
+COPY packages/shared-tailwind ./packages/shared-tailwind
+COPY packages/shared-theme ./packages/shared-theme
+COPY packages/shared-theme-ui ./packages/shared-theme-ui
+COPY packages/shared-subscription-types ./packages/shared-subscription-types
+COPY packages/shared-subscription-ui ./packages/shared-subscription-ui
+COPY packages/shared-profile-ui ./packages/shared-profile-ui
+COPY packages/shared-types ./packages/shared-types
+COPY packages/shared-ui ./packages/shared-ui
+COPY packages/shared-utils ./packages/shared-utils
+
+# Copy manacore web
+COPY apps/manacore/apps/web ./apps/manacore/apps/web
+
+# Install dependencies
+RUN pnpm install --frozen-lockfile
+
+# Build shared packages that need building
+WORKDIR /app/packages/shared-auth
+RUN pnpm build || true
+
+# Build the web app
+WORKDIR /app/apps/manacore/apps/web
+RUN pnpm build
+
+# Production stage
+FROM node:20-alpine AS production
+
+# Keep same directory structure as builder so pnpm symlinks resolve correctly
+WORKDIR /app/apps/manacore/apps/web
+
+# Copy the pnpm store that symlinks point to (at /app/node_modules/.pnpm)
+COPY --from=builder /app/node_modules/.pnpm /app/node_modules/.pnpm
+
+# Copy the app's node_modules (contains symlinks to the pnpm store)
+COPY --from=builder /app/apps/manacore/apps/web/node_modules ./node_modules
+
+# Copy built application
+COPY --from=builder /app/apps/manacore/apps/web/build ./build
+COPY --from=builder /app/apps/manacore/apps/web/package.json ./
+
+# Expose port
+EXPOSE 5173
+
+# Set environment variables
+ENV NODE_ENV=production
+ENV PORT=5173
+ENV HOST=0.0.0.0
+
+# Health check
+HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 \
+ CMD wget --no-verbose --tries=1 --spider http://localhost:5173/health || exit 1
+
+# Run the app
+CMD ["node", "build"]
diff --git a/apps/manacore/apps/web/package.json b/apps/manacore/apps/web/package.json
index ae3c273cf..81222619d 100644
--- a/apps/manacore/apps/web/package.json
+++ b/apps/manacore/apps/web/package.json
@@ -16,8 +16,7 @@
},
"devDependencies": {
"@playwright/test": "^1.51.0",
- "@sveltejs/adapter-auto": "^7.0.0",
- "@sveltejs/adapter-netlify": "^5.2.4",
+ "@sveltejs/adapter-node": "^5.0.0",
"@sveltejs/kit": "^2.15.7",
"@sveltejs/vite-plugin-svelte": "^5.0.4",
"@tailwindcss/postcss": "^4.1.17",
@@ -50,15 +49,12 @@
"@manacore/shared-profile-ui": "workspace:*",
"@manacore/shared-subscription-types": "workspace:*",
"@manacore/shared-subscription-ui": "workspace:*",
- "@manacore/shared-supabase": "workspace:*",
"@manacore/shared-tailwind": "workspace:*",
"@manacore/shared-theme": "workspace:*",
"@manacore/shared-theme-ui": "workspace:*",
"@manacore/shared-types": "workspace:*",
"@manacore/shared-ui": "workspace:*",
"@manacore/shared-utils": "workspace:*",
- "@supabase/ssr": "^0.5.2",
- "@supabase/supabase-js": "^2.81.1",
"svelte-dnd-action": "^0.9.68",
"svelte-i18n": "^4.0.0"
},
diff --git a/apps/manacore/apps/web/src/app.d.ts b/apps/manacore/apps/web/src/app.d.ts
index 9272c6861..1476def65 100644
--- a/apps/manacore/apps/web/src/app.d.ts
+++ b/apps/manacore/apps/web/src/app.d.ts
@@ -1,18 +1,15 @@
-import type { Session, SupabaseClient, User } from '@supabase/supabase-js';
-
+/**
+ * App type declarations for ManaCore web app
+ *
+ * Authentication is handled entirely by Mana Core Auth (@manacore/shared-auth).
+ * No Supabase is needed - all data comes from mana-core-auth APIs.
+ */
declare global {
namespace App {
- interface Locals {
- supabase: SupabaseClient;
- safeGetSession: () => Promise<{ session: Session | null; user: User | null }>;
- session: Session | null;
- user: User | null;
- }
- interface PageData {
- // Auth is handled by Mana Core Auth (@manacore/shared-auth), not Supabase
- // Supabase is used for database operations only
- supabase?: SupabaseClient;
- }
+ // eslint-disable-next-line @typescript-eslint/no-empty-object-type
+ interface Locals {}
+ // eslint-disable-next-line @typescript-eslint/no-empty-object-type
+ interface PageData {}
// interface Error {}
// interface Platform {}
}
diff --git a/apps/manacore/apps/web/src/hooks.server.ts b/apps/manacore/apps/web/src/hooks.server.ts
index 1ae4f5d6a..21b618758 100644
--- a/apps/manacore/apps/web/src/hooks.server.ts
+++ b/apps/manacore/apps/web/src/hooks.server.ts
@@ -1,29 +1,38 @@
-import { PUBLIC_SUPABASE_URL, PUBLIC_SUPABASE_ANON_KEY } from '$env/static/public';
-import { createServerClient } from '@supabase/ssr';
import type { Handle } from '@sveltejs/kit';
/**
* Server hooks for ManaCore web app
*
- * Note: Authentication is handled client-side via Mana Core Auth.
- * Supabase is only used for database operations (not auth).
+ * Injects runtime environment variables into the HTML for client-side access.
+ * This is necessary because SvelteKit's $env/static/public bakes values at build time,
+ * but Docker containers need runtime configuration.
*/
-export const handle: Handle = async ({ event, resolve }) => {
- // Create Supabase client for database operations only
- event.locals.supabase = createServerClient(PUBLIC_SUPABASE_URL, PUBLIC_SUPABASE_ANON_KEY, {
- cookies: {
- getAll: () => event.cookies.getAll(),
- setAll: (cookiesToSet) => {
- cookiesToSet.forEach(({ name, value, options }) => {
- event.cookies.set(name, value, { ...options, path: '/' });
- });
- },
- },
- }) as any;
+// Auth URL
+const PUBLIC_MANA_CORE_AUTH_URL_CLIENT =
+ process.env.PUBLIC_MANA_CORE_AUTH_URL_CLIENT || process.env.PUBLIC_MANA_CORE_AUTH_URL || '';
+
+// Backend URLs for dashboard widgets
+const PUBLIC_TODO_API_URL_CLIENT =
+ process.env.PUBLIC_TODO_API_URL_CLIENT || process.env.PUBLIC_TODO_API_URL || '';
+const PUBLIC_CALENDAR_API_URL_CLIENT =
+ process.env.PUBLIC_CALENDAR_API_URL_CLIENT || process.env.PUBLIC_CALENDAR_API_URL || '';
+const PUBLIC_CLOCK_API_URL_CLIENT =
+ process.env.PUBLIC_CLOCK_API_URL_CLIENT || process.env.PUBLIC_CLOCK_API_URL || '';
+const PUBLIC_CONTACTS_API_URL_CLIENT =
+ process.env.PUBLIC_CONTACTS_API_URL_CLIENT || process.env.PUBLIC_CONTACTS_API_URL || '';
+
+export const handle: Handle = async ({ event, resolve }) => {
return resolve(event, {
- filterSerializedResponseHeaders(name) {
- return name === 'content-range' || name === 'x-supabase-api-version';
+ transformPageChunk: ({ html }) => {
+ const envScript = ``;
+ return html.replace('', `${envScript}`);
},
});
};
diff --git a/apps/manacore/apps/web/src/lib/api/services/calendar.ts b/apps/manacore/apps/web/src/lib/api/services/calendar.ts
index cefced7d4..444b6ef2d 100644
--- a/apps/manacore/apps/web/src/lib/api/services/calendar.ts
+++ b/apps/manacore/apps/web/src/lib/api/services/calendar.ts
@@ -4,12 +4,32 @@
* Fetches events from the Calendar backend for dashboard widgets.
*/
+import { browser } from '$app/environment';
import { createApiClient, type ApiResult } from '../base-client';
-// Backend URL - falls back to localhost for development
-const CALENDAR_API_URL = import.meta.env.PUBLIC_CALENDAR_API_URL || 'http://localhost:3014/api/v1';
+// Get Calendar API URL dynamically at runtime
+function getCalendarApiUrl(): string {
+ if (browser && typeof window !== 'undefined') {
+ // Client-side: use injected window variable (set by hooks.server.ts)
+ const injectedUrl = (window as unknown as { __PUBLIC_CALENDAR_API_URL__?: string })
+ .__PUBLIC_CALENDAR_API_URL__;
+ if (injectedUrl) {
+ return `${injectedUrl}/api/v1`;
+ }
+ }
+ // Fallback for local development
+ return 'http://localhost:3016/api/v1';
+}
-const client = createApiClient(CALENDAR_API_URL);
+// Lazy-initialized client to ensure we get the correct URL at runtime
+let _client: ReturnType | null = null;
+
+function getClient() {
+ if (!_client) {
+ _client = createApiClient(getCalendarApiUrl());
+ }
+ return _client;
+}
/**
* Calendar entity from Calendar backend
@@ -59,7 +79,7 @@ export const calendarService = {
const startDate = new Date().toISOString().split('T')[0];
const endDate = new Date(Date.now() + days * 24 * 60 * 60 * 1000).toISOString().split('T')[0];
- const result = await client.get<{ events: CalendarEvent[] }>(
+ const result = await getClient().get<{ events: CalendarEvent[] }>(
`/events?startDate=${startDate}&endDate=${endDate}`
);
@@ -75,7 +95,7 @@ export const calendarService = {
*/
async getTodayEvents(): Promise> {
const today = new Date().toISOString().split('T')[0];
- const result = await client.get<{ events: CalendarEvent[] }>(
+ const result = await getClient().get<{ events: CalendarEvent[] }>(
`/events?startDate=${today}&endDate=${today}`
);
@@ -90,7 +110,7 @@ export const calendarService = {
* Get all calendars
*/
async getCalendars(): Promise> {
- const result = await client.get<{ calendars: Calendar[] }>('/calendars');
+ const result = await getClient().get<{ calendars: Calendar[] }>('/calendars');
if (result.error || !result.data) {
return { data: null, error: result.error };
@@ -109,7 +129,7 @@ export const calendarService = {
const startDate = new Date().toISOString().split('T')[0];
const endDate = new Date(Date.now() + days * 24 * 60 * 60 * 1000).toISOString().split('T')[0];
- const result = await client.get<{ events: CalendarEvent[] }>(
+ const result = await getClient().get<{ events: CalendarEvent[] }>(
`/events?calendarIds=${calendarId}&startDate=${startDate}&endDate=${endDate}`
);
diff --git a/apps/manacore/apps/web/src/lib/api/services/contacts.ts b/apps/manacore/apps/web/src/lib/api/services/contacts.ts
index 2491e8427..b2aa226f3 100644
--- a/apps/manacore/apps/web/src/lib/api/services/contacts.ts
+++ b/apps/manacore/apps/web/src/lib/api/services/contacts.ts
@@ -4,12 +4,32 @@
* Fetches contacts from the Contacts backend for dashboard widgets.
*/
+import { browser } from '$app/environment';
import { createApiClient, type ApiResult } from '../base-client';
-// Backend URL - falls back to localhost for development
-const CONTACTS_API_URL = import.meta.env.PUBLIC_CONTACTS_API_URL || 'http://localhost:3015/api/v1';
+// Get Contacts API URL dynamically at runtime
+function getContactsApiUrl(): string {
+ if (browser && typeof window !== 'undefined') {
+ // Client-side: use injected window variable (set by hooks.server.ts)
+ const injectedUrl = (window as unknown as { __PUBLIC_CONTACTS_API_URL__?: string })
+ .__PUBLIC_CONTACTS_API_URL__;
+ if (injectedUrl) {
+ return `${injectedUrl}/api/v1`;
+ }
+ }
+ // Fallback for local development
+ return 'http://localhost:3015/api/v1';
+}
-const client = createApiClient(CONTACTS_API_URL);
+// Lazy-initialized client to ensure we get the correct URL at runtime
+let _client: ReturnType | null = null;
+
+function getClient() {
+ if (!_client) {
+ _client = createApiClient(getContactsApiUrl());
+ }
+ return _client;
+}
/**
* Contact entity from Contacts backend
@@ -55,7 +75,7 @@ export const contactsService = {
* Get favorite contacts
*/
async getFavoriteContacts(limit: number = 5): Promise> {
- const result = await client.get(`/contacts?isFavorite=true&limit=${limit}`);
+ const result = await getClient().get(`/contacts?isFavorite=true&limit=${limit}`);
return result;
},
@@ -63,7 +83,7 @@ export const contactsService = {
* Get recent contacts (by updatedAt)
*/
async getRecentContacts(limit: number = 5): Promise> {
- const result = await client.get(`/contacts?limit=${limit}`);
+ const result = await getClient().get(`/contacts?limit=${limit}`);
if (result.error || !result.data) {
return result;
@@ -82,7 +102,7 @@ export const contactsService = {
* Get contacts with upcoming birthdays
*/
async getUpcomingBirthdays(days: number = 30): Promise> {
- const result = await client.get('/contacts');
+ const result = await getClient().get('/contacts');
if (result.error || !result.data) {
return result;
@@ -113,7 +133,7 @@ export const contactsService = {
* Get contact count
*/
async getContactCount(): Promise> {
- const result = await client.get('/contacts');
+ const result = await getClient().get('/contacts');
if (result.error || !result.data) {
return { data: null, error: result.error };
diff --git a/apps/manacore/apps/web/src/lib/api/services/todo.ts b/apps/manacore/apps/web/src/lib/api/services/todo.ts
index 8ab3f6bc6..16389b036 100644
--- a/apps/manacore/apps/web/src/lib/api/services/todo.ts
+++ b/apps/manacore/apps/web/src/lib/api/services/todo.ts
@@ -4,12 +4,32 @@
* Fetches tasks from the Todo backend for dashboard widgets.
*/
+import { browser } from '$app/environment';
import { createApiClient, type ApiResult } from '../base-client';
-// Backend URL - falls back to localhost for development
-const TODO_API_URL = import.meta.env.PUBLIC_TODO_API_URL || 'http://localhost:3017/api/v1';
+// Get Todo API URL dynamically at runtime
+function getTodoApiUrl(): string {
+ if (browser && typeof window !== 'undefined') {
+ // Client-side: use injected window variable (set by hooks.server.ts)
+ const injectedUrl = (window as unknown as { __PUBLIC_TODO_API_URL__?: string })
+ .__PUBLIC_TODO_API_URL__;
+ if (injectedUrl) {
+ return `${injectedUrl}/api/v1`;
+ }
+ }
+ // Fallback for local development
+ return 'http://localhost:3018/api/v1';
+}
-const client = createApiClient(TODO_API_URL);
+// Lazy-initialized client to ensure we get the correct URL at runtime
+let _client: ReturnType | null = null;
+
+function getClient() {
+ if (!_client) {
+ _client = createApiClient(getTodoApiUrl());
+ }
+ return _client;
+}
/**
* Task entity from Todo backend
@@ -49,7 +69,7 @@ export const todoService = {
* Get today's tasks
*/
async getTodayTasks(): Promise> {
- const result = await client.get<{ tasks: Task[] }>('/tasks/today');
+ const result = await getClient().get<{ tasks: Task[] }>('/tasks/today');
if (result.error || !result.data) {
return { data: null, error: result.error };
@@ -62,7 +82,7 @@ export const todoService = {
* Get upcoming tasks for the next N days
*/
async getUpcomingTasks(days: number = 7): Promise> {
- const result = await client.get<{ tasks: Task[] }>(`/tasks/upcoming?days=${days}`);
+ const result = await getClient().get<{ tasks: Task[] }>(`/tasks/upcoming?days=${days}`);
if (result.error || !result.data) {
return { data: null, error: result.error };
@@ -75,7 +95,7 @@ export const todoService = {
* Get inbox tasks (unassigned to project)
*/
async getInboxTasks(): Promise> {
- const result = await client.get<{ tasks: Task[] }>('/tasks/inbox');
+ const result = await getClient().get<{ tasks: Task[] }>('/tasks/inbox');
if (result.error || !result.data) {
return { data: null, error: result.error };
@@ -88,7 +108,7 @@ export const todoService = {
* Get all projects
*/
async getProjects(): Promise> {
- const result = await client.get<{ projects: Project[] }>('/projects');
+ const result = await getClient().get<{ projects: Project[] }>('/projects');
if (result.error || !result.data) {
return { data: null, error: result.error };
diff --git a/apps/manacore/apps/web/src/lib/server/supabase.ts b/apps/manacore/apps/web/src/lib/server/supabase.ts
deleted file mode 100644
index 31f4b14a4..000000000
--- a/apps/manacore/apps/web/src/lib/server/supabase.ts
+++ /dev/null
@@ -1,43 +0,0 @@
-import type { RequestEvent } from '@sveltejs/kit';
-
-export async function getUser(event: RequestEvent) {
- const {
- data: { user },
- error,
- } = await event.locals.supabase.auth.getUser();
-
- if (error) {
- console.error('Error fetching user:', error);
- return null;
- }
-
- return user;
-}
-
-export async function getSession(event: RequestEvent) {
- const {
- data: { session },
- error,
- } = await event.locals.supabase.auth.getSession();
-
- if (error) {
- console.error('Error fetching session:', error);
- return null;
- }
-
- return session;
-}
-
-export async function requireAuth(event: RequestEvent) {
- const session = await getSession(event);
-
- if (!session) {
- throw new Error('Unauthorized');
- }
-
- return session;
-}
-
-export function getSupabaseServerClient(event: RequestEvent) {
- return event.locals.supabase;
-}
diff --git a/apps/manacore/apps/web/src/lib/stores/auth.svelte.ts b/apps/manacore/apps/web/src/lib/stores/auth.svelte.ts
index 12cb7b306..126070f7b 100644
--- a/apps/manacore/apps/web/src/lib/stores/auth.svelte.ts
+++ b/apps/manacore/apps/web/src/lib/stores/auth.svelte.ts
@@ -7,9 +7,18 @@ import { browser } from '$app/environment';
import { initializeWebAuth } from '@manacore/shared-auth';
import type { UserData } from '@manacore/shared-auth';
-// Initialize Mana Core Auth only on the client side
-// TODO: Use PUBLIC_MANA_CORE_AUTH_URL from env when available
-const MANA_AUTH_URL = 'http://localhost:3001';
+// Get auth URL dynamically at runtime - fallback for SSR and client
+function getAuthUrl(): string {
+ if (browser && typeof window !== 'undefined') {
+ // Client-side: use injected window variable (set by hooks.server.ts)
+ // Falls back to localhost for local development
+ const injectedUrl = (window as unknown as { __PUBLIC_MANA_CORE_AUTH_URL__?: string })
+ .__PUBLIC_MANA_CORE_AUTH_URL__;
+ return injectedUrl || 'http://localhost:3001';
+ }
+ // Server-side (SSR): use Docker internal URL for container-to-container communication
+ return process.env.PUBLIC_MANA_CORE_AUTH_URL || 'http://localhost:3001';
+}
// Lazy initialization to avoid SSR issues with localStorage
let _authService: ReturnType['authService'] | null = null;
@@ -18,7 +27,7 @@ let _tokenManager: ReturnType['tokenManager'] | null =
function getAuthService() {
if (!browser) return null;
if (!_authService) {
- const auth = initializeWebAuth({ baseUrl: MANA_AUTH_URL });
+ const auth = initializeWebAuth({ baseUrl: getAuthUrl() });
_authService = auth.authService;
_tokenManager = auth.tokenManager;
}
@@ -183,6 +192,29 @@ export const authStore = {
}
},
+ /**
+ * Reset password with token
+ */
+ async resetPassword(token: string, newPassword: string) {
+ const authService = getAuthService();
+ if (!authService) {
+ return { success: false, error: 'Auth not available on server' };
+ }
+
+ try {
+ const result = await authService.resetPassword(token, newPassword);
+
+ if (!result.success) {
+ return { success: false, error: result.error || 'Password reset failed' };
+ }
+
+ return { success: true };
+ } catch (error) {
+ const errorMessage = error instanceof Error ? error.message : 'Unknown error';
+ return { success: false, error: errorMessage };
+ }
+ },
+
/**
* Get access token for API calls
*/
diff --git a/apps/manacore/apps/web/src/lib/stores/user-settings.svelte.ts b/apps/manacore/apps/web/src/lib/stores/user-settings.svelte.ts
index 6e178a802..7ce7f1600 100644
--- a/apps/manacore/apps/web/src/lib/stores/user-settings.svelte.ts
+++ b/apps/manacore/apps/web/src/lib/stores/user-settings.svelte.ts
@@ -7,14 +7,22 @@
* - localStorage caching for offline support
*/
+import { browser } from '$app/environment';
import { createUserSettingsStore } from '@manacore/shared-theme';
import { authStore } from './auth.svelte';
-// TODO: Use PUBLIC_MANA_CORE_AUTH_URL from env when available
-const MANA_AUTH_URL = 'http://localhost:3001';
+// Get auth URL dynamically at runtime
+function getAuthUrl(): string {
+ if (browser && typeof window !== 'undefined') {
+ const injectedUrl = (window as unknown as { __PUBLIC_MANA_CORE_AUTH_URL__?: string })
+ .__PUBLIC_MANA_CORE_AUTH_URL__;
+ return injectedUrl || 'http://localhost:3001';
+ }
+ return 'http://localhost:3001';
+}
export const userSettings = createUserSettingsStore({
appId: 'manacore',
- authUrl: MANA_AUTH_URL,
+ authUrl: getAuthUrl(),
getAccessToken: () => authStore.getAccessToken(),
});
diff --git a/apps/manacore/apps/web/src/routes/(app)/settings/+page.svelte b/apps/manacore/apps/web/src/routes/(app)/settings/+page.svelte
index 56ff38f9a..fe223d193 100644
--- a/apps/manacore/apps/web/src/routes/(app)/settings/+page.svelte
+++ b/apps/manacore/apps/web/src/routes/(app)/settings/+page.svelte
@@ -1,7 +1,7 @@
Reset Password
- {#if verifying}
- Verifying your reset link...
+ {#if success}
+ Password reset successfully
{:else if hasToken}
Enter your new password
{:else}
- Token missing or expired
+ Invalid or missing reset token
{/if}
- {#if verifying}
+ {#if success}
-
-
Verifying your password reset link...
-
-
- {:else if verificationError}
-
-
-
⚠️
+
✅
- {verificationError}
+ Your password has been reset successfully. You will be redirected to the login page
+ shortly.
- Request a new reset link
+ Go to login
{:else if hasToken}
-
@@ -173,9 +136,10 @@
name="confirmPassword"
id="confirmPassword"
autocomplete="new-password"
- placeholder="••••••••"
+ placeholder="Confirm new password"
required
- minlength={6}
+ minlength={12}
+ bind:value={confirmPassword}
/>
@@ -195,10 +159,10 @@
This password reset link is invalid or has expired.
- Back to login
+ Request a new reset link
diff --git a/apps/manacore/apps/web/src/routes/+layout.server.ts b/apps/manacore/apps/web/src/routes/+layout.server.ts
index 11770fd80..907b4b2a0 100644
--- a/apps/manacore/apps/web/src/routes/+layout.server.ts
+++ b/apps/manacore/apps/web/src/routes/+layout.server.ts
@@ -1,7 +1,8 @@
import type { LayoutServerLoad } from './$types';
-export const load: LayoutServerLoad = async ({ cookies }) => {
- return {
- cookies: cookies.getAll(),
- };
+/**
+ * Server layout load - minimal, auth handled by mana-core-auth client-side
+ */
+export const load: LayoutServerLoad = async () => {
+ return {};
};
diff --git a/apps/manacore/apps/web/src/routes/+layout.ts b/apps/manacore/apps/web/src/routes/+layout.ts
index b238de6e1..d33856fe5 100644
--- a/apps/manacore/apps/web/src/routes/+layout.ts
+++ b/apps/manacore/apps/web/src/routes/+layout.ts
@@ -1,35 +1,14 @@
import { waitLocale } from '$lib/i18n';
import '$lib/i18n'; // This triggers the init() call at module scope
-import { createBrowserClient } from '@supabase/ssr';
-import { PUBLIC_SUPABASE_URL, PUBLIC_SUPABASE_ANON_KEY } from '$env/static/public';
import type { LayoutLoad } from './$types';
-export const load: LayoutLoad = async ({ data, depends }) => {
+/**
+ * Layout load function
+ *
+ * Auth is handled entirely by Mana Core Auth (@manacore/shared-auth).
+ * No Supabase is needed - all data comes from mana-core-auth APIs.
+ */
+export const load: LayoutLoad = async () => {
await waitLocale();
-
- /**
- * Declare a dependency so the layout will be invalidated when `invalidate('supabase:auth')` is called.
- */
- depends('supabase:auth');
-
- // Create Supabase client for database operations only
- // Auth is handled by Mana Core Auth (@manacore/shared-auth)
- const supabase = createBrowserClient(PUBLIC_SUPABASE_URL, PUBLIC_SUPABASE_ANON_KEY, {
- global: {
- fetch,
- },
- cookies: {
- getAll() {
- return data.cookies;
- },
- setAll(cookiesToSet) {
- // Browser client handles cookies automatically through the browser
- // This is a no-op as cookies are managed via document.cookie in the browser
- },
- },
- });
-
- // Note: Auth session is managed by Mana Core Auth via authStore,
- // not Supabase auth. Supabase is used for database operations only.
- return { supabase };
+ return {};
};
diff --git a/apps/manacore/apps/web/src/routes/api/auth/set-session/+server.ts b/apps/manacore/apps/web/src/routes/api/auth/set-session/+server.ts
deleted file mode 100644
index a03687c6f..000000000
--- a/apps/manacore/apps/web/src/routes/api/auth/set-session/+server.ts
+++ /dev/null
@@ -1,36 +0,0 @@
-import { json } from '@sveltejs/kit';
-import type { RequestHandler } from './$types';
-
-export const POST: RequestHandler = async ({ request, locals: { supabase } }) => {
- try {
- const { access_token, refresh_token } = await request.json();
-
- if (!access_token || !refresh_token) {
- return json(
- { success: false, error: 'Access token and refresh token are required' },
- { status: 400 }
- );
- }
-
- // Set the session using the tokens from the URL hash
- const { data, error } = await supabase.auth.setSession({
- access_token,
- refresh_token,
- });
-
- if (error) {
- console.error('Set session error:', error);
- return json({ success: false, error: error.message }, { status: 400 });
- }
-
- if (!data.session) {
- return json({ success: false, error: 'Failed to create session' }, { status: 400 });
- }
-
- // Session is now set via cookies by the Supabase client
- return json({ success: true });
- } catch (error) {
- console.error('Unexpected error in set session:', error);
- return json({ success: false, error: 'An unexpected error occurred' }, { status: 500 });
- }
-};
diff --git a/apps/manacore/apps/web/src/routes/api/auth/verify-token/+server.ts b/apps/manacore/apps/web/src/routes/api/auth/verify-token/+server.ts
deleted file mode 100644
index a780638fe..000000000
--- a/apps/manacore/apps/web/src/routes/api/auth/verify-token/+server.ts
+++ /dev/null
@@ -1,33 +0,0 @@
-import { json } from '@sveltejs/kit';
-import type { RequestHandler } from './$types';
-
-export const POST: RequestHandler = async ({ request, locals: { supabase } }) => {
- try {
- const { token, type } = await request.json();
-
- if (!token || type !== 'recovery') {
- return json({ success: false, error: 'Invalid token or type' }, { status: 400 });
- }
-
- // Verify the OTP token and create a session
- const { data, error } = await supabase.auth.verifyOtp({
- token_hash: token,
- type: 'recovery',
- });
-
- if (error) {
- console.error('Token verification error:', error);
- return json({ success: false, error: error.message }, { status: 400 });
- }
-
- if (!data.session) {
- return json({ success: false, error: 'Failed to create session' }, { status: 400 });
- }
-
- // Session is now set via cookies by the Supabase client
- return json({ success: true });
- } catch (error) {
- console.error('Unexpected error in token verification:', error);
- return json({ success: false, error: 'An unexpected error occurred' }, { status: 500 });
- }
-};
diff --git a/apps/manacore/apps/web/src/routes/auth/reset-password/+page.server.ts b/apps/manacore/apps/web/src/routes/auth/reset-password/+page.server.ts
deleted file mode 100644
index 70b0f8002..000000000
--- a/apps/manacore/apps/web/src/routes/auth/reset-password/+page.server.ts
+++ /dev/null
@@ -1,44 +0,0 @@
-import { redirect, fail } from '@sveltejs/kit';
-import type { Actions } from './$types';
-
-export const actions: Actions = {
- default: async ({ request, locals: { supabase } }) => {
- const formData = await request.formData();
- const password = formData.get('password') as string;
- const confirmPassword = formData.get('confirmPassword') as string;
-
- // Validate inputs
- if (!password || !confirmPassword) {
- return fail(400, {
- error: 'Both password fields are required',
- });
- }
-
- if (password.length < 6) {
- return fail(400, {
- error: 'Password must be at least 6 characters long',
- });
- }
-
- if (password !== confirmPassword) {
- return fail(400, {
- error: 'Passwords do not match',
- });
- }
-
- // Update the user's password
- const { error } = await supabase.auth.updateUser({
- password,
- });
-
- if (error) {
- console.error('Password update error:', error);
- return fail(400, {
- error: error.message,
- });
- }
-
- // Success - redirect to dashboard
- throw redirect(303, '/dashboard');
- },
-};
diff --git a/apps/manacore/apps/web/src/routes/auth/reset-password/+page.svelte b/apps/manacore/apps/web/src/routes/auth/reset-password/+page.svelte
index abaff7f14..ff15f6e4c 100644
--- a/apps/manacore/apps/web/src/routes/auth/reset-password/+page.svelte
+++ b/apps/manacore/apps/web/src/routes/auth/reset-password/+page.svelte
@@ -1,214 +1,23 @@
-
- Reset Password - ManaCore
-
-
-
-
-
Reset Password
-
- {#if verifying}
- Verifying your reset link...
- {:else if hasToken}
- Enter your new password
- {:else}
- Token missing or expired
- {/if}
-
-
-
- {#if verifying}
-
-
-
-
Verifying your password reset link...
-
-
- {:else if verificationError}
-
-
-
⚠️
-
- {verificationError}
-
-
- Request a new reset link
-
-
-
- {:else if hasToken}
-
-
-
- {:else}
-
-
-
⚠️
-
- This password reset link is invalid or has expired.
-
-
- Back to login
-
-
-
- {/if}
+
diff --git a/apps/manacore/apps/web/svelte.config.js b/apps/manacore/apps/web/svelte.config.js
index c797b64e2..632e2e7c9 100644
--- a/apps/manacore/apps/web/svelte.config.js
+++ b/apps/manacore/apps/web/svelte.config.js
@@ -1,4 +1,4 @@
-import adapter from '@sveltejs/adapter-netlify';
+import adapter from '@sveltejs/adapter-node';
import { vitePreprocess } from '@sveltejs/vite-plugin-svelte';
/** @type {import('@sveltejs/kit').Config} */
@@ -6,7 +6,9 @@ const config = {
preprocess: vitePreprocess(),
kit: {
- adapter: adapter(),
+ adapter: adapter({
+ out: 'build',
+ }),
alias: {
$lib: 'src/lib',
$components: 'src/lib/components',
diff --git a/apps/todo/apps/backend/Dockerfile b/apps/todo/apps/backend/Dockerfile
new file mode 100644
index 000000000..e589ef07b
--- /dev/null
+++ b/apps/todo/apps/backend/Dockerfile
@@ -0,0 +1,67 @@
+# Build stage
+FROM node:20-alpine AS builder
+
+# Install pnpm
+RUN corepack enable && corepack prepare pnpm@9.15.0 --activate
+
+WORKDIR /app
+
+# Copy root workspace files
+COPY pnpm-workspace.yaml ./
+COPY package.json ./
+COPY pnpm-lock.yaml ./
+
+# Copy shared packages
+COPY packages/shared-errors ./packages/shared-errors
+COPY packages/shared-nestjs-auth ./packages/shared-nestjs-auth
+
+# Copy todo backend
+COPY apps/todo/apps/backend ./apps/todo/apps/backend
+
+# Install dependencies
+RUN pnpm install --frozen-lockfile
+
+# Build shared packages first
+WORKDIR /app/packages/shared-errors
+RUN pnpm build
+
+WORKDIR /app/packages/shared-nestjs-auth
+RUN pnpm build
+
+# Build the backend
+WORKDIR /app/apps/todo/apps/backend
+RUN pnpm build
+
+# Production stage
+FROM node:20-alpine AS production
+
+# Install pnpm and postgresql-client for health checks
+RUN corepack enable && corepack prepare pnpm@9.15.0 --activate \
+ && apk add --no-cache postgresql-client
+
+WORKDIR /app
+
+# Copy everything from builder (including node_modules)
+COPY --from=builder /app/pnpm-workspace.yaml ./
+COPY --from=builder /app/package.json ./
+COPY --from=builder /app/pnpm-lock.yaml ./
+COPY --from=builder /app/node_modules ./node_modules
+COPY --from=builder /app/packages ./packages
+COPY --from=builder /app/apps/todo/apps/backend ./apps/todo/apps/backend
+
+# Copy entrypoint script
+COPY apps/todo/apps/backend/docker-entrypoint.sh /usr/local/bin/
+RUN chmod +x /usr/local/bin/docker-entrypoint.sh
+
+WORKDIR /app/apps/todo/apps/backend
+
+# Expose port
+EXPOSE 3018
+
+# Health check
+HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 \
+ CMD wget --no-verbose --tries=1 --spider http://localhost:3018/api/v1/health || exit 1
+
+# Run entrypoint script
+ENTRYPOINT ["docker-entrypoint.sh"]
+CMD ["node", "dist/main.js"]
diff --git a/apps/todo/apps/backend/docker-entrypoint.sh b/apps/todo/apps/backend/docker-entrypoint.sh
new file mode 100644
index 000000000..e50cbfd33
--- /dev/null
+++ b/apps/todo/apps/backend/docker-entrypoint.sh
@@ -0,0 +1,23 @@
+#!/bin/sh
+set -e
+
+echo "=== Todo Backend Entrypoint ==="
+
+# Wait for PostgreSQL to be ready
+echo "Waiting for PostgreSQL..."
+until pg_isready -h ${DB_HOST:-postgres} -p ${DB_PORT:-5432} -U ${DB_USER:-postgres} 2>/dev/null; do
+ echo "PostgreSQL is unavailable - sleeping"
+ sleep 2
+done
+echo "PostgreSQL is up!"
+
+cd /app/apps/todo/apps/backend
+
+# Run schema push
+echo "Pushing database schema..."
+npx drizzle-kit push --force
+echo "Schema push completed!"
+
+# Execute the main command
+echo "Starting application..."
+exec "$@"
diff --git a/apps/todo/apps/web/Dockerfile b/apps/todo/apps/web/Dockerfile
new file mode 100644
index 000000000..e29ff1815
--- /dev/null
+++ b/apps/todo/apps/web/Dockerfile
@@ -0,0 +1,84 @@
+# Build stage
+FROM node:20-alpine AS builder
+
+# Build arguments for SvelteKit static env vars
+ARG PUBLIC_BACKEND_URL=http://todo-backend:3018
+ARG PUBLIC_MANA_CORE_AUTH_URL=http://mana-core-auth:3001
+
+# Set as environment variables for build
+ENV PUBLIC_BACKEND_URL=$PUBLIC_BACKEND_URL
+ENV PUBLIC_MANA_CORE_AUTH_URL=$PUBLIC_MANA_CORE_AUTH_URL
+
+# Install pnpm
+RUN corepack enable && corepack prepare pnpm@9.15.0 --activate
+
+WORKDIR /app
+
+# Copy root workspace files
+COPY pnpm-workspace.yaml ./
+COPY package.json ./
+COPY pnpm-lock.yaml ./
+
+# Copy shared packages needed by todo web
+COPY packages/shared-auth ./packages/shared-auth
+COPY packages/shared-auth-ui ./packages/shared-auth-ui
+COPY packages/shared-branding ./packages/shared-branding
+COPY packages/shared-feedback-service ./packages/shared-feedback-service
+COPY packages/shared-feedback-types ./packages/shared-feedback-types
+COPY packages/shared-feedback-ui ./packages/shared-feedback-ui
+COPY packages/shared-i18n ./packages/shared-i18n
+COPY packages/shared-icons ./packages/shared-icons
+COPY packages/shared-tailwind ./packages/shared-tailwind
+COPY packages/shared-theme ./packages/shared-theme
+COPY packages/shared-theme-ui ./packages/shared-theme-ui
+COPY packages/shared-subscription-types ./packages/shared-subscription-types
+COPY packages/shared-subscription-ui ./packages/shared-subscription-ui
+COPY packages/shared-profile-ui ./packages/shared-profile-ui
+COPY packages/shared-ui ./packages/shared-ui
+COPY packages/shared-utils ./packages/shared-utils
+
+# Copy todo packages and web
+COPY apps/todo/packages ./apps/todo/packages
+COPY apps/todo/apps/web ./apps/todo/apps/web
+
+# Install dependencies
+RUN pnpm install --frozen-lockfile
+
+# Build shared packages that need building
+WORKDIR /app/packages/shared-auth
+RUN pnpm build || true
+
+# Build the web app
+WORKDIR /app/apps/todo/apps/web
+RUN pnpm build
+
+# Production stage
+FROM node:20-alpine AS production
+
+# Keep same directory structure as builder so pnpm symlinks resolve correctly
+WORKDIR /app/apps/todo/apps/web
+
+# Copy the pnpm store that symlinks point to (at /app/node_modules/.pnpm)
+COPY --from=builder /app/node_modules/.pnpm /app/node_modules/.pnpm
+
+# Copy the app's node_modules (contains symlinks to the pnpm store)
+COPY --from=builder /app/apps/todo/apps/web/node_modules ./node_modules
+
+# Copy built application
+COPY --from=builder /app/apps/todo/apps/web/build ./build
+COPY --from=builder /app/apps/todo/apps/web/package.json ./
+
+# Expose port
+EXPOSE 5188
+
+# Set environment variables
+ENV NODE_ENV=production
+ENV PORT=5188
+ENV HOST=0.0.0.0
+
+# Health check
+HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 \
+ CMD wget --no-verbose --tries=1 --spider http://localhost:5188/health || exit 1
+
+# Run the app
+CMD ["node", "build"]
diff --git a/apps/todo/apps/web/package.json b/apps/todo/apps/web/package.json
index b3abfd6d0..2d79f2a44 100644
--- a/apps/todo/apps/web/package.json
+++ b/apps/todo/apps/web/package.json
@@ -13,7 +13,7 @@
"type-check": "echo 'Skipping type-check for now'"
},
"devDependencies": {
- "@sveltejs/adapter-auto": "^3.0.0",
+ "@sveltejs/adapter-node": "^5.0.0",
"@sveltejs/kit": "^2.0.0",
"@sveltejs/vite-plugin-svelte": "^5.0.0",
"@tailwindcss/vite": "^4.1.7",
diff --git a/apps/todo/apps/web/src/hooks.server.ts b/apps/todo/apps/web/src/hooks.server.ts
new file mode 100644
index 000000000..6d7a5089d
--- /dev/null
+++ b/apps/todo/apps/web/src/hooks.server.ts
@@ -0,0 +1,27 @@
+/**
+ * Server Hooks for SvelteKit
+ * - Injects runtime environment variables for client-side use
+ * - Auth is handled client-side via Mana Core Auth
+ */
+
+import type { Handle } from '@sveltejs/kit';
+
+// Get client-side URLs from environment (Docker runtime)
+const PUBLIC_MANA_CORE_AUTH_URL_CLIENT =
+ process.env.PUBLIC_MANA_CORE_AUTH_URL_CLIENT || process.env.PUBLIC_MANA_CORE_AUTH_URL || '';
+const PUBLIC_BACKEND_URL_CLIENT =
+ process.env.PUBLIC_BACKEND_URL_CLIENT || process.env.PUBLIC_BACKEND_URL || '';
+
+export const handle: Handle = async ({ event, resolve }) => {
+ return resolve(event, {
+ transformPageChunk: ({ html }) => {
+ // Inject runtime environment variables into the HTML
+ // These will be available on window.__PUBLIC_*__ for client-side code
+ const envScript = ``;
+ return html.replace('', `${envScript}`);
+ },
+ });
+};
diff --git a/apps/todo/apps/web/src/lib/api/client.ts b/apps/todo/apps/web/src/lib/api/client.ts
index 606a54945..eb4e6ed00 100644
--- a/apps/todo/apps/web/src/lib/api/client.ts
+++ b/apps/todo/apps/web/src/lib/api/client.ts
@@ -12,12 +12,28 @@ interface ApiError {
statusCode: number;
}
+/**
+ * Get the backend URL, preferring runtime-injected value in browser
+ * This allows Docker to inject PUBLIC_BACKEND_URL_CLIENT at runtime
+ * instead of using the build-time PUBLIC_BACKEND_URL
+ */
+function getBackendUrl(): string {
+ if (browser && typeof window !== 'undefined') {
+ const runtimeUrl = (window as Window & { __PUBLIC_BACKEND_URL__?: string })
+ .__PUBLIC_BACKEND_URL__;
+ if (runtimeUrl) {
+ return runtimeUrl;
+ }
+ }
+ return PUBLIC_BACKEND_URL || 'http://localhost:3018';
+}
+
class ApiClient {
- private baseUrl: string;
private accessToken: string | null = null;
- constructor() {
- this.baseUrl = PUBLIC_BACKEND_URL || 'http://localhost:3018';
+ // Use getter to evaluate URL at request time (browser may hydrate after construction)
+ private get baseUrl(): string {
+ return getBackendUrl();
}
setAccessToken(token: string | null) {
diff --git a/apps/todo/apps/web/src/lib/stores/auth.svelte.ts b/apps/todo/apps/web/src/lib/stores/auth.svelte.ts
index 1d2fd96d9..885a63266 100644
--- a/apps/todo/apps/web/src/lib/stores/auth.svelte.ts
+++ b/apps/todo/apps/web/src/lib/stores/auth.svelte.ts
@@ -7,8 +7,18 @@ import { browser } from '$app/environment';
import { initializeWebAuth, type UserData } from '@manacore/shared-auth';
import { apiClient } from '$lib/api/client';
-// Initialize Mana Core Auth only on the client side
-const MANA_AUTH_URL = 'http://localhost:3001';
+// Get auth URL dynamically at runtime - fallback for SSR and client
+function getAuthUrl(): string {
+ if (browser && typeof window !== 'undefined') {
+ // Client-side: use injected window variable (set by hooks.server.ts)
+ // Falls back to localhost for local development
+ const injectedUrl = (window as unknown as { __PUBLIC_MANA_CORE_AUTH_URL__?: string })
+ .__PUBLIC_MANA_CORE_AUTH_URL__;
+ return injectedUrl || 'http://localhost:3001';
+ }
+ // Server-side (SSR): use Docker internal URL for container-to-container communication
+ return process.env.PUBLIC_MANA_CORE_AUTH_URL || 'http://localhost:3001';
+}
// Lazy initialization to avoid SSR issues with localStorage
let _authService: ReturnType
['authService'] | null = null;
@@ -17,7 +27,7 @@ let _tokenManager: ReturnType['tokenManager'] | null =
function getAuthService() {
if (!browser) return null;
if (!_authService) {
- const auth = initializeWebAuth({ baseUrl: MANA_AUTH_URL });
+ const auth = initializeWebAuth({ baseUrl: getAuthUrl() });
_authService = auth.authService;
_tokenManager = auth.tokenManager;
}
diff --git a/apps/todo/apps/web/src/lib/stores/user-settings.svelte.ts b/apps/todo/apps/web/src/lib/stores/user-settings.svelte.ts
index 18af057b7..536074626 100644
--- a/apps/todo/apps/web/src/lib/stores/user-settings.svelte.ts
+++ b/apps/todo/apps/web/src/lib/stores/user-settings.svelte.ts
@@ -1,10 +1,19 @@
+import { browser } from '$app/environment';
import { createUserSettingsStore } from '@manacore/shared-theme';
import { authStore } from './auth.svelte';
-const MANA_AUTH_URL = import.meta.env.PUBLIC_MANA_CORE_AUTH_URL || 'http://localhost:3001';
+// Get auth URL dynamically at runtime
+function getAuthUrl(): string {
+ if (browser && typeof window !== 'undefined') {
+ const injectedUrl = (window as unknown as { __PUBLIC_MANA_CORE_AUTH_URL__?: string })
+ .__PUBLIC_MANA_CORE_AUTH_URL__;
+ return injectedUrl || 'http://localhost:3001';
+ }
+ return 'http://localhost:3001';
+}
export const userSettings = createUserSettingsStore({
appId: 'todo',
- authUrl: MANA_AUTH_URL,
+ authUrl: getAuthUrl(),
getAccessToken: () => authStore.getAccessToken(),
});
diff --git a/apps/todo/apps/web/svelte.config.js b/apps/todo/apps/web/svelte.config.js
index c8b303bb6..a7a917e4c 100644
--- a/apps/todo/apps/web/svelte.config.js
+++ b/apps/todo/apps/web/svelte.config.js
@@ -1,11 +1,13 @@
-import adapter from '@sveltejs/adapter-auto';
+import adapter from '@sveltejs/adapter-node';
import { vitePreprocess } from '@sveltejs/vite-plugin-svelte';
/** @type {import('@sveltejs/kit').Config} */
const config = {
preprocess: vitePreprocess(),
kit: {
- adapter: adapter(),
+ adapter: adapter({
+ out: 'build',
+ }),
},
};
diff --git a/docker-compose.staging.yml b/docker-compose.staging.yml
index 509561d3e..e8741a4bf 100644
--- a/docker-compose.staging.yml
+++ b/docker-compose.staging.yml
@@ -74,8 +74,8 @@ services:
JWT_SECRET: ${JWT_SECRET}
JWT_PUBLIC_KEY: ${JWT_PUBLIC_KEY}
JWT_PRIVATE_KEY: ${JWT_PRIVATE_KEY}
- # CORS - Allow chat-web and other staging origins
- CORS_ORIGINS: http://46.224.108.214:3000,http://46.224.108.214:3002,http://localhost:3000
+ # CORS - Allow all staging web app origins
+ CORS_ORIGINS: http://46.224.108.214:3000,http://46.224.108.214:5173,http://46.224.108.214:5186,http://46.224.108.214:5187,http://46.224.108.214:5188,http://localhost:3000,http://localhost:5173,http://localhost:5186,http://localhost:5187,http://localhost:5188
ports:
- "3001:3001"
healthcheck:
@@ -159,6 +159,250 @@ services:
max-size: "10m"
max-file: "3"
+ # ============================================
+ # Manacore App
+ # ============================================
+
+ manacore-web:
+ image: ${DOCKER_REGISTRY:-ghcr.io/memo-2023}/manacore-web:${MANACORE_WEB_VERSION:-latest}
+ container_name: manacore-web-staging
+ restart: unless-stopped
+ depends_on:
+ mana-core-auth:
+ condition: service_healthy
+ environment:
+ NODE_ENV: staging
+ PORT: 5173
+ # Auth URLs
+ PUBLIC_MANA_CORE_AUTH_URL: http://mana-core-auth:3001
+ PUBLIC_MANA_CORE_AUTH_URL_CLIENT: http://46.224.108.214:3001
+ # Backend URLs for dashboard widgets
+ PUBLIC_TODO_API_URL: http://todo-backend:3018
+ PUBLIC_TODO_API_URL_CLIENT: http://46.224.108.214:3018
+ PUBLIC_CALENDAR_API_URL: http://calendar-backend:3016
+ PUBLIC_CALENDAR_API_URL_CLIENT: http://46.224.108.214:3016
+ PUBLIC_CLOCK_API_URL: http://clock-backend:3017
+ PUBLIC_CLOCK_API_URL_CLIENT: http://46.224.108.214:3017
+ ports:
+ - "5173:5173"
+ healthcheck:
+ test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://localhost:5173/health"]
+ interval: 30s
+ timeout: 10s
+ retries: 3
+ start_period: 40s
+ networks:
+ - manacore-network
+ logging:
+ driver: "json-file"
+ options:
+ max-size: "10m"
+ max-file: "3"
+
+ # ============================================
+ # Todo App
+ # ============================================
+
+ todo-backend:
+ image: ${DOCKER_REGISTRY:-ghcr.io/memo-2023}/todo-backend:${TODO_VERSION:-latest}
+ container_name: todo-backend-staging
+ restart: unless-stopped
+ depends_on:
+ mana-core-auth:
+ condition: service_healthy
+ postgres:
+ condition: service_healthy
+ environment:
+ NODE_ENV: staging
+ PORT: 3018
+ DATABASE_URL: postgresql://${POSTGRES_USER:-postgres}:${POSTGRES_PASSWORD}@postgres:5432/todo
+ DB_HOST: postgres
+ DB_PORT: 5432
+ DB_USER: ${POSTGRES_USER:-postgres}
+ MANA_CORE_AUTH_URL: http://mana-core-auth:3001
+ CORS_ORIGINS: http://46.224.108.214:5188,http://46.224.108.214:5173,http://localhost:5188,http://localhost:5173
+ ports:
+ - "3018:3018"
+ healthcheck:
+ test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://localhost:3018/api/v1/health"]
+ interval: 30s
+ timeout: 10s
+ retries: 3
+ start_period: 40s
+ networks:
+ - manacore-network
+ logging:
+ driver: "json-file"
+ options:
+ max-size: "10m"
+ max-file: "3"
+
+ todo-web:
+ image: ${DOCKER_REGISTRY:-ghcr.io/memo-2023}/todo-web:${TODO_WEB_VERSION:-latest}
+ container_name: todo-web-staging
+ restart: unless-stopped
+ depends_on:
+ todo-backend:
+ condition: service_healthy
+ environment:
+ NODE_ENV: staging
+ PORT: 5188
+ PUBLIC_BACKEND_URL: http://todo-backend:3018
+ PUBLIC_MANA_CORE_AUTH_URL: http://mana-core-auth:3001
+ PUBLIC_BACKEND_URL_CLIENT: http://46.224.108.214:3018
+ PUBLIC_MANA_CORE_AUTH_URL_CLIENT: http://46.224.108.214:3001
+ ports:
+ - "5188:5188"
+ healthcheck:
+ test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://localhost:5188/health"]
+ interval: 30s
+ timeout: 10s
+ retries: 3
+ start_period: 40s
+ networks:
+ - manacore-network
+ logging:
+ driver: "json-file"
+ options:
+ max-size: "10m"
+ max-file: "3"
+
+ # ============================================
+ # Calendar App
+ # ============================================
+
+ calendar-backend:
+ image: ${DOCKER_REGISTRY:-ghcr.io/memo-2023}/calendar-backend:${CALENDAR_VERSION:-latest}
+ container_name: calendar-backend-staging
+ restart: unless-stopped
+ depends_on:
+ mana-core-auth:
+ condition: service_healthy
+ postgres:
+ condition: service_healthy
+ environment:
+ NODE_ENV: staging
+ PORT: 3016
+ DATABASE_URL: postgresql://${POSTGRES_USER:-postgres}:${POSTGRES_PASSWORD}@postgres:5432/calendar
+ DB_HOST: postgres
+ DB_PORT: 5432
+ DB_USER: ${POSTGRES_USER:-postgres}
+ MANA_CORE_AUTH_URL: http://mana-core-auth:3001
+ CORS_ORIGINS: http://46.224.108.214:5186,http://46.224.108.214:5173,http://localhost:5186,http://localhost:5173
+ ports:
+ - "3016:3016"
+ healthcheck:
+ test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://localhost:3016/api/v1/health"]
+ interval: 30s
+ timeout: 10s
+ retries: 3
+ start_period: 40s
+ networks:
+ - manacore-network
+ logging:
+ driver: "json-file"
+ options:
+ max-size: "10m"
+ max-file: "3"
+
+ calendar-web:
+ image: ${DOCKER_REGISTRY:-ghcr.io/memo-2023}/calendar-web:${CALENDAR_WEB_VERSION:-latest}
+ container_name: calendar-web-staging
+ restart: unless-stopped
+ depends_on:
+ calendar-backend:
+ condition: service_healthy
+ environment:
+ NODE_ENV: staging
+ PORT: 5186
+ PUBLIC_BACKEND_URL: http://calendar-backend:3016
+ PUBLIC_MANA_CORE_AUTH_URL: http://mana-core-auth:3001
+ PUBLIC_BACKEND_URL_CLIENT: http://46.224.108.214:3016
+ PUBLIC_MANA_CORE_AUTH_URL_CLIENT: http://46.224.108.214:3001
+ ports:
+ - "5186:5186"
+ healthcheck:
+ test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://localhost:5186/health"]
+ interval: 30s
+ timeout: 10s
+ retries: 3
+ start_period: 40s
+ networks:
+ - manacore-network
+ logging:
+ driver: "json-file"
+ options:
+ max-size: "10m"
+ max-file: "3"
+
+ # ============================================
+ # Clock App
+ # ============================================
+
+ clock-backend:
+ image: ${DOCKER_REGISTRY:-ghcr.io/memo-2023}/clock-backend:${CLOCK_VERSION:-latest}
+ container_name: clock-backend-staging
+ restart: unless-stopped
+ depends_on:
+ mana-core-auth:
+ condition: service_healthy
+ postgres:
+ condition: service_healthy
+ environment:
+ NODE_ENV: staging
+ PORT: 3017
+ DATABASE_URL: postgresql://${POSTGRES_USER:-postgres}:${POSTGRES_PASSWORD}@postgres:5432/clock
+ DB_HOST: postgres
+ DB_PORT: 5432
+ DB_USER: ${POSTGRES_USER:-postgres}
+ MANA_CORE_AUTH_URL: http://mana-core-auth:3001
+ CORS_ORIGINS: http://46.224.108.214:5187,http://46.224.108.214:5173,http://localhost:5187,http://localhost:5173
+ ports:
+ - "3017:3017"
+ healthcheck:
+ test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://localhost:3017/api/v1/health"]
+ interval: 30s
+ timeout: 10s
+ retries: 3
+ start_period: 40s
+ networks:
+ - manacore-network
+ logging:
+ driver: "json-file"
+ options:
+ max-size: "10m"
+ max-file: "3"
+
+ clock-web:
+ image: ${DOCKER_REGISTRY:-ghcr.io/memo-2023}/clock-web:${CLOCK_WEB_VERSION:-latest}
+ container_name: clock-web-staging
+ restart: unless-stopped
+ depends_on:
+ clock-backend:
+ condition: service_healthy
+ environment:
+ NODE_ENV: staging
+ PORT: 5187
+ PUBLIC_BACKEND_URL: http://clock-backend:3017
+ PUBLIC_MANA_CORE_AUTH_URL: http://mana-core-auth:3001
+ PUBLIC_BACKEND_URL_CLIENT: http://46.224.108.214:3017
+ PUBLIC_MANA_CORE_AUTH_URL_CLIENT: http://46.224.108.214:3001
+ ports:
+ - "5187:5187"
+ healthcheck:
+ test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://localhost:5187/health"]
+ interval: 30s
+ timeout: 10s
+ retries: 3
+ start_period: 40s
+ networks:
+ - manacore-network
+ logging:
+ driver: "json-file"
+ options:
+ max-size: "10m"
+ max-file: "3"
+
# ============================================
# Networks
# ============================================
diff --git a/docker/init-db/01-create-databases.sql b/docker/init-db/01-create-databases.sql
index 428bb33d8..b22fe3cfb 100644
--- a/docker/init-db/01-create-databases.sql
+++ b/docker/init-db/01-create-databases.sql
@@ -1,21 +1,37 @@
-- Create databases for all services
-- This script runs on first container initialization
--- Create chat database
-CREATE DATABASE chat;
-
--- Create voxel_lava database
-CREATE DATABASE voxel_lava;
-
--- Create storage database (cloud drive)
-CREATE DATABASE storage;
-
--- Create todo database
-CREATE DATABASE todo;
+-- Core databases
+CREATE DATABASE IF NOT EXISTS chat;
+CREATE DATABASE IF NOT EXISTS zitare;
+CREATE DATABASE IF NOT EXISTS contacts;
+CREATE DATABASE IF NOT EXISTS calendar;
+CREATE DATABASE IF NOT EXISTS clock;
+CREATE DATABASE IF NOT EXISTS todo;
+CREATE DATABASE IF NOT EXISTS manadeck;
+CREATE DATABASE IF NOT EXISTS storage;
+CREATE DATABASE IF NOT EXISTS mail;
+CREATE DATABASE IF NOT EXISTS moodlit;
+CREATE DATABASE IF NOT EXISTS finance;
+CREATE DATABASE IF NOT EXISTS inventory;
+CREATE DATABASE IF NOT EXISTS techbase;
+CREATE DATABASE IF NOT EXISTS voxel_lava;
+CREATE DATABASE IF NOT EXISTS figgos;
-- Grant all privileges to the default user
GRANT ALL PRIVILEGES ON DATABASE chat TO manacore;
-GRANT ALL PRIVILEGES ON DATABASE voxel_lava TO manacore;
-GRANT ALL PRIVILEGES ON DATABASE manacore TO manacore;
-GRANT ALL PRIVILEGES ON DATABASE storage TO manacore;
+GRANT ALL PRIVILEGES ON DATABASE zitare TO manacore;
+GRANT ALL PRIVILEGES ON DATABASE contacts TO manacore;
+GRANT ALL PRIVILEGES ON DATABASE calendar TO manacore;
+GRANT ALL PRIVILEGES ON DATABASE clock TO manacore;
GRANT ALL PRIVILEGES ON DATABASE todo TO manacore;
+GRANT ALL PRIVILEGES ON DATABASE manadeck TO manacore;
+GRANT ALL PRIVILEGES ON DATABASE storage TO manacore;
+GRANT ALL PRIVILEGES ON DATABASE mail TO manacore;
+GRANT ALL PRIVILEGES ON DATABASE moodlit TO manacore;
+GRANT ALL PRIVILEGES ON DATABASE finance TO manacore;
+GRANT ALL PRIVILEGES ON DATABASE inventory TO manacore;
+GRANT ALL PRIVILEGES ON DATABASE techbase TO manacore;
+GRANT ALL PRIVILEGES ON DATABASE voxel_lava TO manacore;
+GRANT ALL PRIVILEGES ON DATABASE figgos TO manacore;
+GRANT ALL PRIVILEGES ON DATABASE manacore TO manacore;
diff --git a/docs/DATABASE_MIGRATIONS.md b/docs/DATABASE_MIGRATIONS.md
new file mode 100644
index 000000000..1f9b262eb
--- /dev/null
+++ b/docs/DATABASE_MIGRATIONS.md
@@ -0,0 +1,667 @@
+# Database Migration Guide
+
+This document describes database migration best practices, procedures, and tooling for the ManaCore monorepo. **This is a core system concept** - all developers should understand these patterns.
+
+## Table of Contents
+
+1. [Overview](#overview)
+2. [Drizzle Migration Internals](#drizzle-migration-internals)
+3. [Migration Commands](#migration-commands)
+4. [Development vs Production](#development-vs-production)
+5. [CI/CD Pipeline](#cicd-pipeline)
+6. [Advisory Locks](#advisory-locks)
+7. [Zero-Downtime Migrations](#zero-downtime-migrations)
+8. [Rollback Procedures](#rollback-procedures)
+9. [Troubleshooting](#troubleshooting)
+
+---
+
+## Overview
+
+All backends in the ManaCore monorepo use **Drizzle ORM** for database schema management. We use two different approaches depending on the environment:
+
+| Environment | Command | Purpose |
+|-------------|---------|---------|
+| **Development** | `drizzle-kit push` | Fast iteration, direct schema sync |
+| **Production** | `drizzle-kit generate` + `migrate` | Tracked migrations with history |
+
+### Key Principles
+
+1. **Migrations run BEFORE code deployment** - Ensures database is ready for new code
+2. **Advisory locks prevent concurrent migrations** - Safe for multi-replica deployments
+3. **Expand-contract pattern for breaking changes** - Zero-downtime schema changes
+4. **Data persistence** - Migrations never delete user data unless explicitly requested
+
+### Quick Decision Guide
+
+```
+┌─────────────────────────────────────────────────────────────────┐
+│ Which command should I use? │
+├─────────────────────────────────────────────────────────────────┤
+│ │
+│ Local development? │
+│ └── YES → pnpm db:push (fast, no tracking) │
+│ │
+│ Staging/Production? │
+│ └── YES → pnpm db:generate + pnpm db:migrate (tracked) │
+│ │
+│ Need to inspect data? │
+│ └── YES → pnpm db:studio (opens Drizzle Studio) │
+│ │
+│ Schema changed by someone else? │
+│ └── YES → git pull + pnpm db:push (local) │
+│ git pull + pnpm db:migrate (staging/prod) │
+│ │
+└─────────────────────────────────────────────────────────────────┘
+```
+
+---
+
+## Drizzle Migration Internals
+
+Understanding how Drizzle manages migrations is essential for debugging issues.
+
+### The Two Modes
+
+#### 1. Push Mode (`drizzle-kit push`)
+
+**How it works:**
+1. Drizzle introspects your TypeScript schema files
+2. Drizzle introspects the current database schema
+3. Drizzle computes the diff between them
+4. Drizzle generates and **immediately executes** the SQL to sync them
+
+**Characteristics:**
+- No migration files created
+- No history tracking
+- Direct database modification
+- Interactive confirmation (use `--force` to skip)
+
+**When to use:** Local development, experimentation, prototyping
+
+#### 2. Generate + Migrate Mode (`drizzle-kit generate` + `migrate`)
+
+**How it works:**
+
+**Step 1: Generate** (`drizzle-kit generate`)
+1. Drizzle introspects your TypeScript schema files
+2. Drizzle reads the last snapshot from `migrations/meta/`
+3. Drizzle computes the diff
+4. Drizzle creates migration files (SQL + snapshot)
+
+**Step 2: Migrate** (`pnpm db:migrate`)
+1. Script reads `migrations/meta/_journal.json`
+2. Script queries `__drizzle_migrations` table in database
+3. Script determines which migrations haven't been applied
+4. Script executes pending migrations in order
+5. Script records applied migrations in `__drizzle_migrations`
+
+**Characteristics:**
+- Creates versioned SQL files
+- Full history tracking
+- Repeatable deployments
+- Can be reviewed before applying
+
+**When to use:** Staging, production, CI/CD pipelines
+
+### Migration File Structure
+
+```
+src/db/migrations/
+├── 0000_initial_schema/
+│ ├── migration.sql # The actual SQL to execute
+│ └── snapshot.json # Schema snapshot AFTER this migration
+├── 0001_add_user_preferences/
+│ ├── migration.sql
+│ └── snapshot.json
+├── 0002_add_credits_table/
+│ ├── migration.sql
+│ └── snapshot.json
+└── meta/
+ └── _journal.json # Migration registry (order + metadata)
+```
+
+### The Journal File (`_journal.json`)
+
+This file tracks all generated migrations:
+
+```json
+{
+ "version": "7",
+ "dialect": "postgresql",
+ "entries": [
+ {
+ "idx": 0,
+ "version": "7",
+ "when": 1733066521000,
+ "tag": "0000_initial_schema",
+ "breakpoints": true
+ },
+ {
+ "idx": 1,
+ "version": "7",
+ "when": 1733152921000,
+ "tag": "0001_add_user_preferences",
+ "breakpoints": true
+ }
+ ]
+}
+```
+
+**Key fields:**
+- `idx`: Sequential index (order matters!)
+- `tag`: Folder name containing the migration
+- `when`: Unix timestamp when generated
+- `breakpoints`: Whether to use statement breakpoints
+
+### The Database Tracking Table (`__drizzle_migrations`)
+
+Drizzle creates this table automatically to track applied migrations:
+
+```sql
+-- Schema: drizzle
+-- Table: __drizzle_migrations
+CREATE TABLE drizzle.__drizzle_migrations (
+ id SERIAL PRIMARY KEY,
+ hash TEXT NOT NULL,
+ created_at BIGINT NOT NULL
+);
+```
+
+**Query applied migrations:**
+```sql
+SELECT * FROM drizzle.__drizzle_migrations ORDER BY created_at;
+```
+
+### How Migration Tracking Works
+
+```
+┌─────────────────┐ ┌─────────────────┐
+│ _journal.json │ │ __drizzle_ │
+│ (filesystem) │ │ migrations (db) │
+└────────┬────────┘ └────────┬────────┘
+ │ │
+ ▼ ▼
+ [0000, 0001, 0002] [hash_0000, hash_0001]
+ │ │
+ └───────────┬───────────┘
+ │
+ ▼
+ Pending: [0002]
+ │
+ ▼
+ Execute 0002/migration.sql
+ │
+ ▼
+ Insert into __drizzle_migrations
+```
+
+### Snapshot Files
+
+Each migration includes a `snapshot.json` that captures the **complete schema state** after that migration. This allows Drizzle to:
+
+1. Compute diffs for the next migration
+2. Detect schema drift
+3. Generate accurate SQL
+
+**Important:** Never modify snapshots manually!
+
+---
+
+## Migration Commands
+
+### All Backends
+
+```bash
+# Development - push schema directly (fast, no history)
+pnpm db:push
+
+# Generate migration files from schema changes
+pnpm db:generate
+
+# Run migrations with advisory locks (production-safe)
+pnpm db:migrate
+
+# Open Drizzle Studio for database inspection
+pnpm db:studio
+```
+
+### Root-Level Commands
+
+```bash
+# Setup all databases (creates DBs + pushes schemas)
+pnpm setup:db
+
+# Setup specific service
+pnpm setup:db:auth
+pnpm setup:db:chat
+```
+
+### Per-Service Commands
+
+```bash
+# mana-core-auth
+pnpm --filter mana-core-auth db:push
+pnpm --filter mana-core-auth db:generate
+pnpm --filter mana-core-auth db:migrate
+
+# chat-backend
+pnpm --filter @chat/backend db:push
+pnpm --filter @chat/backend db:migrate
+```
+
+---
+
+## Development vs Production
+
+### Development Workflow
+
+For local development, use `db:push` for fast iteration:
+
+```bash
+# 1. Make schema changes in src/db/schema/*.ts
+# 2. Push changes to local database
+pnpm db:push
+
+# Or use the full dev command which handles this automatically
+pnpm dev:chat:full
+```
+
+**Why `push` for development?**
+- Instant feedback on schema changes
+- No migration file clutter during experimentation
+- Automatically handled by `dev:*:full` commands
+
+### Production Workflow
+
+For staging/production, use migration files for trackability:
+
+```bash
+# 1. Make schema changes in src/db/schema/*.ts
+
+# 2. Generate migration file
+pnpm db:generate --name add_user_preferences
+
+# 3. Review generated SQL
+cat src/db/migrations/*/migration.sql
+
+# 4. Commit migration files
+git add src/db/migrations/
+git commit -m "feat: add user preferences table"
+
+# 5. CI/CD runs migrations automatically on deploy
+```
+
+**Why migrations for production?**
+- Audit trail of all schema changes
+- Repeatable deployments
+- Rollback capability (with manual down migrations)
+
+---
+
+## CI/CD Pipeline
+
+### Deployment Flow
+
+```
+┌─────────────┐ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐
+│ Build │───>│ Create DB │───>│ Migrate │───>│ Deploy │
+│ Images │ │ (if new) │ │ Database │ │ Code │
+└─────────────┘ └─────────────┘ └─────────────┘ └─────────────┘
+```
+
+### Migration Step Features
+
+1. **Retry logic** - 3 attempts with exponential backoff (10s, 20s, 30s)
+2. **Timeout protection** - 5-minute timeout per migration
+3. **Advisory locks** - Prevents concurrent migrations
+4. **Graceful fallback** - Falls back to `db:push` if `db:migrate` unavailable
+
+### Staging Deployment
+
+Migrations run automatically after database creation:
+
+```yaml
+# .github/workflows/cd-staging.yml
+- name: Run database migrations
+ run: |
+ docker compose exec -T mana-core-auth pnpm run db:migrate
+```
+
+### Production Deployment
+
+Migrations run BEFORE deploying new code:
+
+```yaml
+# .github/workflows/cd-production.yml
+- name: Run database migrations
+ run: |
+ docker compose run --rm mana-core-auth pnpm run db:migrate
+
+- name: Deploy with zero-downtime
+ run: |
+ docker compose up -d
+```
+
+---
+
+## Advisory Locks
+
+Advisory locks prevent multiple instances from running migrations simultaneously.
+
+### How It Works
+
+```typescript
+// services/mana-core-auth/src/db/migrate.ts
+
+const MIGRATION_LOCK_ID = 987654321;
+
+// Acquire lock before migration
+await db.execute(sql`SELECT pg_try_advisory_lock(${LOCK_ID})`);
+
+// Run migrations...
+
+// Release lock after migration
+await db.execute(sql`SELECT pg_advisory_unlock(${LOCK_ID})`);
+```
+
+### Lock Behavior
+
+| Scenario | Behavior |
+|----------|----------|
+| Lock acquired | Migration runs immediately |
+| Lock held by another process | Waits up to 5 minutes, then fails |
+| Lock stuck | Manual release required (see Troubleshooting) |
+
+### Lock IDs by Service
+
+| Service | Lock ID |
+|---------|---------|
+| mana-core-auth | `987654321` |
+| chat-backend | (to be assigned) |
+| todo-backend | (to be assigned) |
+
+### Migration Script Architecture
+
+The production migration script (`src/db/migrate.ts`) is designed for safe, concurrent-safe deployments:
+
+```
+┌─────────────────────────────────────────────────────────────────┐
+│ migrate.ts Execution Flow │
+├─────────────────────────────────────────────────────────────────┤
+│ │
+│ 1. Load environment variables (.env) │
+│ └── DATABASE_URL, MIGRATION_TIMEOUT │
+│ │
+│ 2. Create single-connection pool │
+│ └── max: 1 (dedicated migration connection) │
+│ │
+│ 3. Test database connectivity (with retry) │
+│ └── SELECT 1 (max 3 attempts, exponential backoff) │
+│ │
+│ 4. Acquire advisory lock │
+│ ├── pg_try_advisory_lock() - non-blocking attempt │
+│ └── If busy: poll every 5s until timeout (default: 5 min) │
+│ │
+│ 5. Check for migration files │
+│ └── If meta/_journal.json missing: exit gracefully │
+│ │
+│ 6. Run Drizzle migrations │
+│ └── migrate(db, { migrationsFolder }) │
+│ │
+│ 7. Cleanup (always runs, even on error) │
+│ ├── Release advisory lock │
+│ └── Close database connection │
+│ │
+└─────────────────────────────────────────────────────────────────┘
+```
+
+**Key Components:**
+
+| Component | Purpose | Configuration |
+|-----------|---------|---------------|
+| `withRetry()` | Retry transient errors (network, connection) | 3 attempts, exponential backoff |
+| `acquireLock()` | Non-blocking lock attempt | `pg_try_advisory_lock()` |
+| `waitForLock()` | Polling wait for lock | 5s intervals, configurable timeout |
+| `releaseLock()` | Release lock in finally block | Always runs |
+
+**Error Handling:**
+
+```typescript
+// Transient errors (will retry):
+- ECONNREFUSED, ETIMEDOUT, ENOTFOUND
+- Connection errors
+- PostgreSQL 57P03 (cannot connect now)
+
+// Non-transient errors (immediate failure):
+- Missing DATABASE_URL
+- SQL syntax errors
+- Schema conflicts
+- Lock timeout
+```
+
+**Exit Codes:**
+
+| Code | Meaning |
+|------|---------|
+| 0 | Success - all migrations applied |
+| 1 | Failure - check logs for details |
+
+---
+
+## Zero-Downtime Migrations
+
+For breaking schema changes, use the **expand-contract pattern**:
+
+### Phase 1: Expand
+
+Add new schema elements alongside existing ones:
+
+```sql
+-- Migration: 001_add_full_name.sql
+ALTER TABLE users ADD COLUMN full_name TEXT;
+```
+
+### Phase 2: Migrate
+
+Update application to write to both, backfill data:
+
+```typescript
+// Application code - dual write
+await db.update(users).set({
+ name: newName, // Old column
+ fullName: newName, // New column
+});
+
+// Backfill script
+UPDATE users SET full_name = name WHERE full_name IS NULL;
+```
+
+### Phase 3: Contract
+
+After 1-2 weeks, remove old column:
+
+```sql
+-- Migration: 002_drop_name_column.sql
+ALTER TABLE users DROP COLUMN name;
+```
+
+### Common Patterns
+
+| Change Type | Approach |
+|-------------|----------|
+| Add column | Direct `ALTER TABLE ADD COLUMN` |
+| Drop column | Remove from code first, wait 2 weeks, then drop |
+| Rename column | Add new → dual-write → backfill → drop old |
+| Change type | Add new column → backfill with cast → swap |
+| Add NOT NULL | Add nullable → backfill → add constraint |
+
+### Index Creation
+
+Always use `CONCURRENTLY` to avoid table locks:
+
+```sql
+-- Good
+CREATE INDEX CONCURRENTLY idx_users_email ON users(email);
+
+-- Bad (locks table)
+CREATE INDEX idx_users_email ON users(email);
+```
+
+---
+
+## Rollback Procedures
+
+### Automatic Rollback (Not Supported)
+
+Drizzle ORM does not support automatic rollbacks. Plan your migrations carefully.
+
+### Manual Rollback
+
+1. **Write down migration scripts** alongside up migrations:
+
+```
+src/db/migrations/
+├── 001_add_referrals.up.sql
+├── 001_add_referrals.down.sql # Manual rollback script
+```
+
+2. **Execute rollback manually**:
+
+```bash
+# Connect to database
+docker compose exec -T postgres psql -U postgres -d manacore_auth
+
+# Run down migration
+\i /path/to/001_add_referrals.down.sql
+```
+
+### Rollback Checklist
+
+- [ ] Identify affected migration
+- [ ] Verify rollback script exists and is tested
+- [ ] Create database backup before rollback
+- [ ] Execute rollback in staging first
+- [ ] Monitor for issues after rollback
+- [ ] Update application code if needed
+
+---
+
+## Troubleshooting
+
+### Migration Lock Stuck
+
+If a migration lock is stuck (process crashed without releasing):
+
+```sql
+-- Check for stuck locks
+SELECT * FROM pg_locks WHERE locktype = 'advisory';
+
+-- Release specific lock (replace LOCK_ID)
+SELECT pg_advisory_unlock(987654321);
+
+-- Release all advisory locks for current session
+SELECT pg_advisory_unlock_all();
+```
+
+### Migration Timeout
+
+If migrations time out:
+
+1. Check for long-running queries: `SELECT * FROM pg_stat_activity;`
+2. Increase timeout: `MIGRATION_TIMEOUT=600 pnpm db:migrate`
+3. Break large migrations into smaller steps
+
+### Schema Drift
+
+If staging/production schema differs from expected:
+
+```bash
+# Generate migration from current schema
+pnpm db:generate --name sync_schema
+
+# Review and apply
+pnpm db:migrate
+```
+
+### Connection Issues
+
+```bash
+# Test database connectivity
+docker compose exec -T postgres pg_isready -U postgres
+
+# Check environment variables
+echo $DATABASE_URL
+
+# Manual connection test
+docker compose exec -T postgres psql -U postgres -d manacore_auth -c "SELECT 1"
+```
+
+### Migration Fails in CI/CD
+
+1. Check GitHub Actions logs for specific error
+2. Verify DATABASE_URL is correctly set in secrets
+3. Ensure database exists before migration runs
+4. Check if another migration is running (advisory lock)
+
+---
+
+## Best Practices
+
+### DO
+
+- Run migrations before deploying new code
+- Test migrations in staging before production
+- Use `CONCURRENTLY` for index creation
+- Keep migrations small and focused
+- Commit migration files to version control
+- Wait 1-2 weeks before dropping columns
+
+### DON'T
+
+- Run `db:push` in production
+- Delete migration files after they've been applied
+- Modify migration files after they've been applied
+- Add NOT NULL without default or backfill
+- Create indexes without `CONCURRENTLY`
+- Drop columns immediately after removing from code
+
+---
+
+## Migration File Structure
+
+```
+services/mana-core-auth/
+├── src/db/
+│ ├── schema/
+│ │ ├── index.ts # Export all schemas
+│ │ ├── auth.schema.ts # User, session tables
+│ │ └── credits.schema.ts # Credit system tables
+│ ├── migrations/
+│ │ ├── 0001_initial/
+│ │ │ ├── snapshot.json
+│ │ │ └── migration.sql
+│ │ └── meta/
+│ │ └── _journal.json # Migration history
+│ ├── connection.ts # Database connection
+│ └── migrate.ts # Migration script with locks
+└── drizzle.config.ts # Drizzle configuration
+```
+
+---
+
+## Environment Variables
+
+| Variable | Description | Default |
+|----------|-------------|---------|
+| `DATABASE_URL` | PostgreSQL connection string | Required |
+| `MIGRATION_TIMEOUT` | Max seconds for migration | `300` |
+
+---
+
+## References
+
+- [Drizzle ORM Migrations](https://orm.drizzle.team/docs/migrations)
+- [PostgreSQL Advisory Locks](https://www.postgresql.org/docs/current/explicit-locking.html#ADVISORY-LOCKS)
+- [Expand-Contract Pattern](https://martinfowler.com/bliki/ParallelChange.html)
+- [Zero-Downtime PostgreSQL Migrations](https://postgres.ai/blog/20210923-zero-downtime-postgres-schema-migrations-lock-timeout-and-retries)
diff --git a/docs/LOCAL_DEVELOPMENT.md b/docs/LOCAL_DEVELOPMENT.md
new file mode 100644
index 000000000..1647adc02
--- /dev/null
+++ b/docs/LOCAL_DEVELOPMENT.md
@@ -0,0 +1,275 @@
+# Local Development Guide
+
+This guide explains how to set up and run applications locally with automatic database setup.
+
+## Quick Start
+
+For any project with a backend, use the `dev:*:full` command:
+
+```bash
+pnpm dev:chat:full # Start chat with auth + database setup
+pnpm dev:zitare:full # Start zitare with auth + database setup
+pnpm dev:contacts:full # Start contacts with auth + database setup
+# ... etc
+```
+
+These commands automatically:
+1. Create the database if it doesn't exist
+2. Push the latest schema (Drizzle `db:push`)
+3. Start the auth service (mana-core-auth)
+4. Start the backend and web app with colored output
+
+## Available Full Dev Commands
+
+| Command | Database | Backend Port | Web Port |
+|---------|----------|--------------|----------|
+| `pnpm dev:chat:full` | chat | 3002 | 5173 |
+| `pnpm dev:zitare:full` | zitare | 3007 | 5177 |
+| `pnpm dev:contacts:full` | contacts | 3015 | 5184 |
+| `pnpm dev:calendar:full` | calendar | 3014 | 5179 |
+| `pnpm dev:clock:full` | clock | 3017 | 5187 |
+| `pnpm dev:todo:full` | todo | 3018 | 5188 |
+| `pnpm dev:picture:full` | picture | 3006 | 5175 |
+
+## Prerequisites
+
+Before running any `dev:*:full` command:
+
+```bash
+# 1. Start Docker infrastructure (PostgreSQL, Redis, MinIO)
+pnpm docker:up
+
+# 2. Generate environment files (runs automatically on pnpm install)
+pnpm setup:env
+```
+
+## Database Setup Commands
+
+### Individual Service Setup
+
+```bash
+pnpm setup:db:auth # Setup mana-core-auth database + schema
+pnpm setup:db:chat # Setup chat database + schema
+pnpm setup:db:zitare # Setup zitare database + schema
+pnpm setup:db:contacts # Setup contacts database + schema
+pnpm setup:db:calendar # Setup calendar database + schema
+pnpm setup:db:clock # Setup clock database + schema
+pnpm setup:db:todo # Setup todo database + schema
+pnpm setup:db:picture # Setup picture database + schema
+```
+
+### Setup All Databases
+
+```bash
+pnpm setup:db # Creates ALL databases and pushes ALL schemas
+```
+
+This is useful when setting up a fresh environment or after pulling new schema changes.
+
+## How It Works
+
+### Docker Init Script
+
+On first `pnpm docker:up`, the PostgreSQL container runs `docker/init-db/01-create-databases.sql` which creates all databases:
+
+- manacore, chat, zitare, contacts, calendar, clock, todo, manadeck
+- storage, mail, moodlit, finance, inventory, techbase, voxel_lava, figgos
+
+### Setup Script
+
+The `scripts/setup-databases.sh` script:
+
+1. **Creates database** if it doesn't exist (using `psql`)
+2. **Pushes schema** using `drizzle-kit push --force`
+
+The `--force` flag auto-approves schema changes without interactive prompts.
+
+## Troubleshooting
+
+### Database doesn't exist
+
+If you see `database "xxx" does not exist`:
+
+```bash
+# Option 1: Run the setup script
+pnpm setup:db:chat # or whichever service
+
+# Option 2: Create manually
+PGPASSWORD=devpassword psql -h localhost -U manacore -d postgres -c "CREATE DATABASE chat;"
+```
+
+### Schema out of date
+
+If you see errors about missing tables/columns:
+
+```bash
+# Push the latest schema
+pnpm --filter @chat/backend db:push --force
+```
+
+### Port already in use
+
+If auth (port 3001) is already running:
+
+```bash
+# Check what's using the port
+lsof -i :3001
+
+# Kill the process if needed
+kill
+```
+
+### Fresh Start (Nuclear Option)
+
+To completely reset all databases:
+
+```bash
+# Stop and remove all containers + volumes
+pnpm docker:clean
+
+# Start fresh
+pnpm docker:up
+
+# Setup all databases
+pnpm setup:db
+```
+
+## Apps Without Full Commands
+
+Some apps don't have backends or don't use Drizzle:
+
+| App | Reason |
+|-----|--------|
+| manacore | No backend (uses other services) |
+| manadeck | Backend exists but no db:push |
+| bauntown, context, maerchenzauber, memoro, news, nutriphi, presi, quote, reader, storage, wisekeep | No backends |
+
+For these, use the regular dev commands:
+
+```bash
+pnpm dev:manacore:web
+pnpm dev:manadeck:app
+```
+
+## Adding a New Application
+
+### Step 1: Create Project Structure
+
+Create the standard project structure under `apps/`:
+
+```
+apps/newproject/
+├── apps/
+│ ├── backend/ # NestJS API (if needed)
+│ ├── mobile/ # Expo React Native app
+│ ├── web/ # SvelteKit web app
+│ └── landing/ # Astro marketing page
+├── packages/ # Project-specific shared code
+├── package.json # Workspace root
+├── pnpm-workspace.yaml
+└── CLAUDE.md # Project documentation
+```
+
+### Step 2: Configure Backend Database (if applicable)
+
+If your backend uses Drizzle ORM:
+
+1. **Add database to Docker init** (`docker/init-db/01-create-databases.sql`):
+ ```sql
+ CREATE DATABASE IF NOT EXISTS newproject;
+ GRANT ALL PRIVILEGES ON DATABASE newproject TO manacore;
+ ```
+
+2. **Add to setup script** (`scripts/setup-databases.sh`):
+
+ In the `setup_service()` function, add a new case:
+ ```bash
+ newproject)
+ create_db_if_not_exists "newproject"
+ push_schema "@newproject/backend" "newproject"
+ ;;
+ ```
+
+ Also add to the `ALL_DATABASES` array:
+ ```bash
+ ALL_DATABASES=(
+ ...
+ "newproject"
+ )
+ ```
+
+ And to the services loop at the bottom:
+ ```bash
+ for service in auth chat ... newproject; do
+ ```
+
+3. **Add DATABASE_URL to `.env.development`**:
+ ```env
+ NEWPROJECT_DATABASE_URL=postgresql://manacore:devpassword@localhost:5432/newproject
+ ```
+
+4. **Update `scripts/generate-env.mjs`** to generate the backend `.env` file.
+
+### Step 3: Add Package.json Scripts
+
+Add to root `package.json`:
+
+```json
+{
+ "scripts": {
+ // Project-level dev (all apps)
+ "newproject:dev": "turbo run dev --filter=newproject...",
+
+ // Individual app commands
+ "dev:newproject:web": "pnpm --filter @newproject/web dev",
+ "dev:newproject:mobile": "pnpm --filter @newproject/mobile dev",
+ "dev:newproject:backend": "pnpm --filter @newproject/backend dev",
+ "dev:newproject:landing": "pnpm --filter @newproject/landing dev",
+ "dev:newproject:app": "turbo run dev --filter=@newproject/web --filter=@newproject/backend",
+
+ // Full dev with auto database setup
+ "dev:newproject:full": "./scripts/setup-databases.sh newproject && ./scripts/setup-databases.sh auth && concurrently -n auth,backend,web -c blue,green,cyan \"pnpm dev:auth\" \"pnpm dev:newproject:backend\" \"pnpm dev:newproject:web\"",
+
+ // Database shortcuts
+ "newproject:db:push": "pnpm --filter @newproject/backend db:push",
+ "newproject:db:studio": "pnpm --filter @newproject/backend db:studio",
+
+ // Setup shortcut
+ "setup:db:newproject": "./scripts/setup-databases.sh newproject"
+ }
+}
+```
+
+### Step 4: Create Project CLAUDE.md
+
+Create `apps/newproject/CLAUDE.md` with:
+- Project overview
+- Structure diagram
+- Available commands
+- API endpoints (if backend)
+- Environment variables
+- Tech stack details
+
+See existing projects like `apps/chat/CLAUDE.md` for reference.
+
+### Step 5: Test the Setup
+
+```bash
+# Create database and push schema
+pnpm setup:db:newproject
+
+# Start with full dev command
+pnpm dev:newproject:full
+```
+
+## Checklist for New Projects
+
+- [ ] Create project structure under `apps/newproject/`
+- [ ] Add `pnpm-workspace.yaml` in project root
+- [ ] Add database to `docker/init-db/01-create-databases.sql`
+- [ ] Add service to `scripts/setup-databases.sh`
+- [ ] Add DATABASE_URL to `.env.development`
+- [ ] Update `scripts/generate-env.mjs` for env generation
+- [ ] Add scripts to root `package.json`
+- [ ] Create `CLAUDE.md` with project documentation
+- [ ] Test with `pnpm dev:newproject:full`
diff --git a/docs/SETUP_TEMPLATES.md b/docs/SETUP_TEMPLATES.md
new file mode 100644
index 000000000..d8cc2fe86
--- /dev/null
+++ b/docs/SETUP_TEMPLATES.md
@@ -0,0 +1,450 @@
+# Setup Templates & Checklists
+
+Quick-reference templates for recurring setup tasks. Copy and customize for new projects.
+
+## Table of Contents
+
+1. [New SvelteKit Web App](#1-new-sveltekit-web-app)
+2. [New NestJS Backend](#2-new-nestjs-backend)
+3. [Deploying New Service to Staging](#3-deploying-new-service-to-staging)
+4. [Adding Backend to ManaCore Dashboard](#4-adding-backend-to-manacore-dashboard)
+5. [Quick Reference Port Assignments](#5-quick-reference-port-assignments)
+
+---
+
+## 1. New SvelteKit Web App
+
+### Checklist
+
+- [ ] Create `src/hooks.server.ts` with runtime env injection
+- [ ] Update auth store to use `getAuthUrl()` pattern
+- [ ] Update user-settings store to use `getAuthUrl()` pattern
+- [ ] Update any API services to use lazy client initialization
+- [ ] Add Dockerfile with pnpm symlink preservation
+- [ ] Add to `docker-compose.staging.yml` with both internal and client URLs
+- [ ] Test locally with `pnpm dev`
+- [ ] Deploy and verify `window.__PUBLIC_*__` variables in browser console
+
+### Template: hooks.server.ts
+
+```typescript
+// src/hooks.server.ts
+import type { Handle } from '@sveltejs/kit';
+
+// Runtime environment variables for client-side injection
+const PUBLIC_MANA_CORE_AUTH_URL_CLIENT =
+ process.env.PUBLIC_MANA_CORE_AUTH_URL_CLIENT || process.env.PUBLIC_MANA_CORE_AUTH_URL || '';
+const PUBLIC_BACKEND_URL_CLIENT =
+ process.env.PUBLIC_BACKEND_URL_CLIENT || process.env.PUBLIC_BACKEND_URL || '';
+
+export const handle: Handle = async ({ event, resolve }) => {
+ return resolve(event, {
+ transformPageChunk: ({ html }) => {
+ const envScript = ``;
+ return html.replace('', `${envScript}`);
+ },
+ });
+};
+```
+
+### Template: getAuthUrl() Pattern
+
+```typescript
+// src/lib/stores/auth.svelte.ts
+import { browser } from '$app/environment';
+
+function getAuthUrl(): string {
+ if (browser && typeof window !== 'undefined') {
+ const injectedUrl = (window as unknown as { __PUBLIC_MANA_CORE_AUTH_URL__?: string })
+ .__PUBLIC_MANA_CORE_AUTH_URL__;
+ return injectedUrl || 'http://localhost:3001';
+ }
+ return process.env.PUBLIC_MANA_CORE_AUTH_URL || 'http://localhost:3001';
+}
+
+// Usage
+const auth = initializeWebAuth({ baseUrl: getAuthUrl() });
+```
+
+### Template: Lazy API Client Initialization
+
+```typescript
+// src/lib/api/services/myservice.ts
+import { browser } from '$app/environment';
+import { createApiClient } from '../base-client';
+
+function getApiUrl(): string {
+ if (browser && typeof window !== 'undefined') {
+ const injectedUrl = (window as unknown as { __PUBLIC_BACKEND_URL__?: string })
+ .__PUBLIC_BACKEND_URL__;
+ if (injectedUrl) {
+ return `${injectedUrl}/api/v1`;
+ }
+ }
+ return 'http://localhost:3000/api/v1';
+}
+
+// IMPORTANT: Lazy initialization - don't create client at module level!
+let _client: ReturnType | null = null;
+
+function getClient() {
+ if (!_client) {
+ _client = createApiClient(getApiUrl());
+ }
+ return _client;
+}
+
+export async function getData() {
+ return getClient().get('/data');
+}
+```
+
+### Template: Dockerfile (SvelteKit + pnpm)
+
+```dockerfile
+# Build stage
+FROM node:20-alpine AS builder
+
+RUN npm install -g pnpm@9.15.0
+
+WORKDIR /app
+
+# Copy workspace files
+COPY pnpm-lock.yaml pnpm-workspace.yaml package.json ./
+COPY apps/MYPROJECT/apps/web/package.json apps/MYPROJECT/apps/web/
+COPY packages/ packages/
+
+# Install all dependencies
+RUN pnpm install --frozen-lockfile
+
+# Copy source and build
+COPY apps/MYPROJECT/apps/web apps/MYPROJECT/apps/web
+RUN pnpm --filter @myproject/web build
+
+# Production stage - PRESERVE PNPM SYMLINK STRUCTURE
+FROM node:20-alpine AS production
+
+# Keep same directory structure as builder
+WORKDIR /app/apps/MYPROJECT/apps/web
+
+# Copy pnpm store (target of symlinks)
+COPY --from=builder /app/node_modules/.pnpm /app/node_modules/.pnpm
+
+# Copy app's node_modules (contains symlinks)
+COPY --from=builder /app/apps/MYPROJECT/apps/web/node_modules ./node_modules
+
+# Copy built app
+COPY --from=builder /app/apps/MYPROJECT/apps/web/build ./build
+COPY --from=builder /app/apps/MYPROJECT/apps/web/package.json ./
+
+EXPOSE 5173
+CMD ["node", "build"]
+```
+
+### Template: docker-compose.staging.yml Entry
+
+```yaml
+myproject-web:
+ image: ghcr.io/memo-2023/myproject-web:${MYPROJECT_WEB_VERSION:-latest}
+ container_name: myproject-web-staging
+ restart: unless-stopped
+ ports:
+ - '51XX:5173'
+ environment:
+ NODE_ENV: production
+ # Server-side URLs (Docker internal network)
+ PUBLIC_BACKEND_URL: http://myproject-backend:30XX
+ PUBLIC_MANA_CORE_AUTH_URL: http://mana-core-auth:3001
+ # Client-side URLs (browser access via public IP)
+ PUBLIC_BACKEND_URL_CLIENT: http://46.224.108.214:30XX
+ PUBLIC_MANA_CORE_AUTH_URL_CLIENT: http://46.224.108.214:3001
+ depends_on:
+ myproject-backend:
+ condition: service_healthy
+ healthcheck:
+ test: ['CMD', 'wget', '--no-verbose', '--tries=1', '--spider', 'http://localhost:5173/health']
+ interval: 30s
+ timeout: 10s
+ retries: 3
+ start_period: 40s
+ networks:
+ - manacore-network
+```
+
+---
+
+## 2. New NestJS Backend
+
+### Checklist
+
+- [ ] Use `text` type for all `user_id` columns (NOT `uuid`)
+- [ ] Add health check endpoint at `/api/v1/health`
+- [ ] Configure CORS to include manacore-web origin (port 5173)
+- [ ] Add database to `docker/init-db/01-create-databases.sql`
+- [ ] Add to `scripts/setup-databases.sh`
+- [ ] Add `dev:myproject:full` command to root `package.json`
+- [ ] Add Dockerfile with correct health check
+- [ ] Add to `docker-compose.staging.yml` with proper CORS config
+
+### Template: Drizzle Schema (user_id as text)
+
+```typescript
+// src/db/schema/main.schema.ts
+import { pgTable, uuid, text, timestamp } from 'drizzle-orm/pg-core';
+
+export const items = pgTable('items', {
+ id: uuid('id').defaultRandom().primaryKey(),
+ userId: text('user_id').notNull(), // ALWAYS text, not uuid!
+ title: text('title').notNull(),
+ createdAt: timestamp('created_at').defaultNow().notNull(),
+ updatedAt: timestamp('updated_at').defaultNow().notNull(),
+});
+```
+
+### Template: Health Controller
+
+```typescript
+// src/health/health.controller.ts
+import { Controller, Get } from '@nestjs/common';
+
+@Controller('api/v1/health')
+export class HealthController {
+ @Get()
+ check() {
+ return { status: 'ok', timestamp: new Date().toISOString() };
+ }
+}
+```
+
+### Template: CORS Configuration
+
+```typescript
+// src/main.ts
+async function bootstrap() {
+ const app = await NestFactory.create(AppModule);
+
+ const corsOrigins = process.env.CORS_ORIGINS?.split(',') || [
+ 'http://localhost:5173', // Local dev
+ 'http://localhost:51XX', // App's own web
+ ];
+
+ app.enableCors({
+ origin: corsOrigins,
+ credentials: true,
+ });
+
+ await app.listen(process.env.PORT || 30XX);
+}
+```
+
+### Template: docker-compose.staging.yml Entry
+
+```yaml
+myproject-backend:
+ image: ghcr.io/memo-2023/myproject-backend:${MYPROJECT_BACKEND_VERSION:-latest}
+ container_name: myproject-backend-staging
+ restart: unless-stopped
+ ports:
+ - '30XX:30XX'
+ environment:
+ NODE_ENV: production
+ PORT: 30XX
+ DATABASE_URL: postgresql://postgres:${POSTGRES_PASSWORD}@postgres:5432/myproject
+ MANA_CORE_AUTH_URL: http://mana-core-auth:3001
+ # CORS - Include app's web AND manacore-web dashboard
+ CORS_ORIGINS: http://46.224.108.214:51XX,http://46.224.108.214:5173,http://localhost:51XX,http://localhost:5173
+ depends_on:
+ postgres:
+ condition: service_healthy
+ mana-core-auth:
+ condition: service_healthy
+ healthcheck:
+ test: ['CMD', 'wget', '--no-verbose', '--tries=1', '--spider', 'http://localhost:30XX/api/v1/health']
+ interval: 30s
+ timeout: 10s
+ retries: 3
+ start_period: 40s
+ networks:
+ - manacore-network
+```
+
+---
+
+## 3. Deploying New Service to Staging
+
+### Pre-Deployment Checklist
+
+- [ ] Database exists on staging PostgreSQL
+- [ ] Dockerfile has correct health check path (`/api/v1/health` for backends)
+- [ ] `docker-compose.staging.yml` has service definition
+- [ ] CORS_ORIGINS includes all required origins
+- [ ] Environment variables set correctly
+- [ ] Tag format matches project name exactly
+
+### Create Database on Staging
+
+```bash
+ssh -i ~/.ssh/hetzner_deploy_key deploy@46.224.108.214
+
+# Create database
+docker exec manacore-postgres-staging psql -U postgres -c 'CREATE DATABASE myproject;'
+
+# Verify
+docker exec manacore-postgres-staging psql -U postgres -c '\l' | grep myproject
+```
+
+### Deployment Tag Formats
+
+| Project | Correct Tag Format | Wrong Format |
+|---------|-------------------|--------------|
+| mana-core-auth | `mana-core-auth-staging-v1.0.X` | `auth-staging-v1.0.X` |
+| chat | `chat-staging-v1.0.X` or `chat-all-staging-v1.0.X` | - |
+| todo | `todo-staging-v1.0.X` or `todo-all-staging-v1.0.X` | - |
+| calendar | `calendar-staging-v1.0.X` | - |
+| clock | `clock-staging-v1.0.X` | - |
+| myproject | `myproject-staging-v1.0.X` | - |
+
+### Post-Deployment Verification
+
+```bash
+# Check container is running correct version
+docker ps --format '{{.Names}}: {{.Image}}' | grep myproject
+
+# Check health endpoint
+curl http://46.224.108.214:30XX/api/v1/health
+
+# Check logs for errors
+docker logs myproject-backend-staging --tail 50
+
+# Test CORS (from manacore-web origin)
+curl -I -X OPTIONS http://46.224.108.214:30XX/api/v1/endpoint \
+ -H "Origin: http://46.224.108.214:5173" \
+ -H "Access-Control-Request-Method: GET"
+```
+
+---
+
+## 4. Adding Backend to ManaCore Dashboard
+
+When adding a new backend service that manacore-web dashboard should call:
+
+### Checklist
+
+- [ ] Add CORS origin for manacore-web (port 5173) to backend
+- [ ] Create API service file in `manacore/apps/web/src/lib/api/services/`
+- [ ] Add runtime URL injection in `manacore/apps/web/src/hooks.server.ts`
+- [ ] Add environment variables to `docker-compose.staging.yml` for manacore-web
+- [ ] Deploy both manacore-web and the backend with new config
+
+### Template: API Service File
+
+```typescript
+// apps/manacore/apps/web/src/lib/api/services/myservice.ts
+import { browser } from '$app/environment';
+import { createApiClient, type ApiResult } from '../base-client';
+
+function getMyServiceApiUrl(): string {
+ if (browser && typeof window !== 'undefined') {
+ const injectedUrl = (window as unknown as { __PUBLIC_MYSERVICE_API_URL__?: string })
+ .__PUBLIC_MYSERVICE_API_URL__;
+ if (injectedUrl) {
+ return `${injectedUrl}/api/v1`;
+ }
+ }
+ return 'http://localhost:30XX/api/v1';
+}
+
+let _client: ReturnType | null = null;
+
+function getClient() {
+ if (!_client) {
+ _client = createApiClient(getMyServiceApiUrl());
+ }
+ return _client;
+}
+
+// Export API functions
+export async function getItems(): Promise> {
+ return getClient().get('/items');
+}
+
+export async function createItem(data: CreateItemDto): Promise> {
+ return getClient().post('/items', data);
+}
+```
+
+### Template: hooks.server.ts Addition
+
+```typescript
+// Add to existing hooks.server.ts
+const PUBLIC_MYSERVICE_API_URL_CLIENT =
+ process.env.PUBLIC_MYSERVICE_API_URL_CLIENT || process.env.PUBLIC_MYSERVICE_API_URL || '';
+
+// In transformPageChunk, add:
+window.__PUBLIC_MYSERVICE_API_URL__ = "${PUBLIC_MYSERVICE_API_URL_CLIENT}";
+```
+
+### Template: docker-compose.staging.yml Addition
+
+```yaml
+manacore-web:
+ environment:
+ # ... existing env vars ...
+ # Add new backend URL
+ PUBLIC_MYSERVICE_API_URL: http://myservice-backend:30XX
+ PUBLIC_MYSERVICE_API_URL_CLIENT: http://46.224.108.214:30XX
+```
+
+---
+
+## 5. Quick Reference Port Assignments
+
+### Backend Ports (3000-3099)
+
+| Port | Service |
+|------|---------|
+| 3000 | chat-web (legacy) |
+| 3001 | mana-core-auth |
+| 3002 | chat-backend |
+| 3006 | picture-backend |
+| 3007 | zitare-backend |
+| 3009 | manadeck-backend |
+| 3015 | contacts-backend |
+| 3016 | calendar-backend |
+| 3017 | clock-backend |
+| 3018 | todo-backend |
+
+### Web App Ports (5100-5199)
+
+| Port | Service |
+|------|---------|
+| 5173 | manacore-web |
+| 5175 | picture-web |
+| 5177 | zitare-web |
+| 5179 | calendar-web |
+| 5184 | contacts-web |
+| 5186 | calendar-web (staging) |
+| 5187 | clock-web |
+| 5188 | todo-web |
+
+### Next Available Ports
+
+- **Backend**: 3019, 3020, 3021...
+- **Web**: 5189, 5190, 5191...
+
+---
+
+## Common Mistakes Quick Reference
+
+| Mistake | Fix |
+|---------|-----|
+| `import.meta.env` in Docker | Use `window.__PUBLIC_*__` injection |
+| API client at module level | Use lazy `getClient()` pattern |
+| `uuid` type for user_id | Use `text` type |
+| Missing CORS for 5173 | Add manacore-web to CORS_ORIGINS |
+| `auth-staging-v*` tag | Use `mana-core-auth-staging-v*` |
+| ALTER TABLE without USING | Use `USING column::text` |
+| `/api/health` endpoint | Use `/api/v1/health` |
diff --git a/docs/STAGING_DEPLOYMENT_ISSUES.md b/docs/STAGING_DEPLOYMENT_ISSUES.md
new file mode 100644
index 000000000..4df93e2dd
--- /dev/null
+++ b/docs/STAGING_DEPLOYMENT_ISSUES.md
@@ -0,0 +1,408 @@
+# Staging Deployment Issues & Solutions
+
+This document captures common issues encountered during staging deployments and their solutions. Reference this when debugging deployment problems.
+
+## Table of Contents
+
+1. [Runtime Environment Variables (SvelteKit)](#1-runtime-environment-variables-sveltekit)
+2. [CORS Configuration](#2-cors-configuration)
+3. [CD Workflow Version Tags](#3-cd-workflow-version-tags)
+4. [Database Setup](#4-database-setup)
+5. [User ID Format (Better Auth)](#5-user-id-format-better-auth)
+6. [Debugging Checklist](#6-debugging-checklist)
+7. [Summary: Common Mistakes to Avoid](#summary-common-mistakes-to-avoid)
+
+---
+
+## 1. Runtime Environment Variables (SvelteKit)
+
+### Problem
+
+SvelteKit apps use `import.meta.env.PUBLIC_*` which gets **baked in at build time**. When running in Docker, the container uses whatever values were present during the GitHub Actions build, not the runtime environment variables.
+
+**Symptoms:**
+- Web apps calling `localhost:3001` instead of staging server IP
+- API calls going to wrong URLs despite correct Docker env vars
+
+### Solution
+
+Use **runtime env injection** via `hooks.server.ts`:
+
+```typescript
+// src/hooks.server.ts
+import type { Handle } from '@sveltejs/kit';
+
+const PUBLIC_MANA_CORE_AUTH_URL_CLIENT =
+ process.env.PUBLIC_MANA_CORE_AUTH_URL_CLIENT || '';
+const PUBLIC_BACKEND_URL_CLIENT =
+ process.env.PUBLIC_BACKEND_URL_CLIENT || '';
+
+export const handle: Handle = async ({ event, resolve }) => {
+ return resolve(event, {
+ transformPageChunk: ({ html }) => {
+ const envScript = ``;
+ return html.replace('', `${envScript}`);
+ },
+ });
+};
+```
+
+Then in client code, read from `window` instead of `import.meta.env`:
+
+```typescript
+import { browser } from '$app/environment';
+
+function getApiUrl(): string {
+ if (browser && typeof window !== 'undefined') {
+ const injectedUrl = (window as any).__PUBLIC_BACKEND_URL__;
+ if (injectedUrl) return injectedUrl;
+ }
+ return 'http://localhost:3000'; // fallback for local dev
+}
+```
+
+### Lazy Client Initialization Pattern
+
+**Important**: API clients must be lazily initialized to read the URL at request time, not at module load time:
+
+```typescript
+// CORRECT - Lazy initialization
+let _client: ReturnType | null = null;
+
+function getClient() {
+ if (!_client) {
+ _client = createApiClient(getApiUrl()); // URL evaluated when called
+ }
+ return _client;
+}
+
+export async function getTasks() {
+ return getClient().get('/tasks'); // Client created on first use
+}
+```
+
+```typescript
+// WRONG - Module-level initialization
+const client = createApiClient(getApiUrl()); // URL evaluated at import time!
+
+export async function getTasks() {
+ return client.get('/tasks'); // Will use stale URL
+}
+```
+
+**Why this matters**: When the module is imported, the `window` object may not have the injected environment variables yet. The lazy pattern ensures the URL is read only when the client is actually needed.
+
+### Docker Compose Pattern
+
+Use two environment variables:
+- `PUBLIC_*_URL` - Internal Docker network URL (container-to-container)
+- `PUBLIC_*_URL_CLIENT` - External URL for browser access
+
+```yaml
+environment:
+ PUBLIC_BACKEND_URL: http://backend-container:3000 # Server-side
+ PUBLIC_BACKEND_URL_CLIENT: http://46.224.108.214:3000 # Browser-side
+```
+
+---
+
+## 2. CORS Configuration
+
+### Problem
+
+Backends only allow CORS from their own web apps, blocking requests from other origins like manacore-web dashboard.
+
+**Symptoms:**
+- `Access to fetch blocked by CORS policy`
+- `No 'Access-Control-Allow-Origin' header`
+
+### Solution
+
+Add all necessary origins to `CORS_ORIGINS` in docker-compose.staging.yml:
+
+```yaml
+todo-backend:
+ environment:
+ # Include both the app's own web AND manacore-web dashboard
+ CORS_ORIGINS: http://46.224.108.214:5188,http://46.224.108.214:5173,http://localhost:5188,http://localhost:5173
+```
+
+### Checklist for New Backends
+
+When deploying a new backend that will be called from manacore-web dashboard:
+1. Add `http://46.224.108.214:5173` to CORS_ORIGINS
+2. Add `http://localhost:5173` for local development
+3. Restart the container after config changes
+
+### Testing CORS
+
+```bash
+curl -I -X OPTIONS http://46.224.108.214:3018/api/v1/endpoint \
+ -H "Origin: http://46.224.108.214:5173" \
+ -H "Access-Control-Request-Method: GET"
+
+# Should see:
+# Access-Control-Allow-Origin: http://46.224.108.214:5173
+```
+
+---
+
+## 3. CD Workflow Version Tags
+
+### Problem
+
+docker-compose uses variables like `${TODO_WEB_VERSION:-latest}`, but the CD workflow wasn't updating the `.env` file on the staging server, causing containers to always use `latest` instead of the tagged version.
+
+**Symptoms:**
+- Deployed new version but container still running old code
+- `docker ps` shows wrong image tag
+
+### Solution
+
+The CD workflow (`.github/workflows/cd-staging-tagged.yml`) now:
+1. Computes the version variable name (e.g., `TODO_WEB_VERSION`)
+2. Updates the `.env` file on staging server
+3. docker-compose reads from `.env`
+
+### Tag Naming Convention
+
+Tags must follow the exact project name as defined in the CD workflow:
+
+| Project | Correct Tag Format | Wrong Format |
+|---------|-------------------|--------------|
+| mana-core-auth | `mana-core-auth-staging-v1.0.0` | `auth-staging-v1.0.0` |
+| chat | `chat-staging-v1.0.0` or `chat-all-staging-v1.0.0` | - |
+| todo | `todo-staging-v1.0.0` or `todo-all-staging-v1.0.0` | - |
+
+**Note**: Using the wrong tag format (e.g., `auth-staging-*` instead of `mana-core-auth-staging-*`) will cause the workflow to fail because it won't find the correct Dockerfile path.
+
+### Verifying Deployment
+
+```bash
+# Check running container version
+docker ps --format '{{.Names}}: {{.Image}}' | grep todo
+
+# Check .env file
+cat ~/manacore-staging/.env | grep VERSION
+```
+
+---
+
+## 4. Database Setup
+
+### Problem
+
+New backends fail with `database "X" does not exist` because the PostgreSQL databases weren't created.
+
+**Symptoms:**
+- 500 Internal Server Error
+- Logs show: `PostgresError: database "todo" does not exist`
+
+### Solution
+
+Create databases manually on first deployment:
+
+```bash
+# SSH to staging
+ssh deploy@46.224.108.214
+
+# Create databases
+docker exec manacore-postgres-staging psql -U postgres -c 'CREATE DATABASE todo;'
+docker exec manacore-postgres-staging psql -U postgres -c 'CREATE DATABASE calendar;'
+docker exec manacore-postgres-staging psql -U postgres -c 'CREATE DATABASE clock;'
+
+# Restart backends (they auto-migrate schemas on startup)
+cd ~/manacore-staging
+docker compose restart todo-backend calendar-backend clock-backend
+```
+
+### Checklist for New Apps
+
+When deploying a new app with a database:
+1. Create the database: `CREATE DATABASE appname;`
+2. The backend will auto-migrate the schema on startup
+3. Verify tables exist: `\dt` in psql
+
+---
+
+## 5. User ID Format (Better Auth)
+
+### Problem
+
+Backend database schemas use `uuid` type for `user_id`, but Better Auth generates non-UUID user IDs like `otUe1YrfENPdHnrF3g1vSBfpkQfambCZ`.
+
+**Symptoms:**
+- 500 Internal Server Error on authenticated requests
+- Logs show: `invalid input syntax for type uuid: "otUe1YrfENPdHnrF3g1vSBfpkQfambCZ"`
+
+### Solution
+
+Change `user_id` columns from `uuid` to `text`:
+
+```sql
+-- For each table with user_id (use USING clause for explicit conversion)
+ALTER TABLE tasks ALTER COLUMN user_id TYPE text USING user_id::text;
+ALTER TABLE projects ALTER COLUMN user_id TYPE text USING user_id::text;
+-- etc.
+```
+
+**Important**: Always use the `USING` clause when converting column types. Without it, PostgreSQL may silently fail or produce unexpected results:
+
+```sql
+-- CORRECT - Explicit conversion
+ALTER TABLE events ALTER COLUMN user_id TYPE text USING user_id::text;
+
+-- RISKY - May fail silently on some data types
+ALTER TABLE events ALTER COLUMN user_id TYPE text;
+```
+
+### Prevention
+
+When creating new backend schemas, **always use `text` type for user_id**:
+
+```typescript
+// Drizzle schema - CORRECT
+export const tasks = pgTable('tasks', {
+ id: uuid('id').defaultRandom().primaryKey(),
+ userId: text('user_id').notNull(), // Use text, not uuid
+ // ...
+});
+
+// WRONG - Don't do this
+export const tasks = pgTable('tasks', {
+ userId: uuid('user_id').notNull(), // Will fail with Better Auth
+});
+```
+
+---
+
+## Quick Debugging Commands
+
+```bash
+# Check container logs
+docker logs --tail 50
+
+# Check container is running correct version
+docker ps --format '{{.Names}}: {{.Image}}'
+
+# Test CORS
+curl -I -X OPTIONS -H "Origin: "
+
+# Check database exists
+docker exec manacore-postgres-staging psql -U postgres -c '\l'
+
+# Check tables in database
+docker exec manacore-postgres-staging psql -U postgres -d -c '\dt'
+
+# Restart a service
+cd ~/manacore-staging && docker compose restart
+
+# Force recreate with new config
+cd ~/manacore-staging && docker compose up -d --no-deps --force-recreate
+```
+
+---
+
+## Port Reference
+
+| Service | Port |
+|---------|------|
+| mana-core-auth | 3001 |
+| chat-backend | 3002 |
+| calendar-backend | 3016 |
+| clock-backend | 3017 |
+| todo-backend | 3018 |
+| chat-web | 3000 |
+| manacore-web | 5173 |
+| calendar-web | 5186 |
+| clock-web | 5187 |
+| todo-web | 5188 |
+
+---
+
+## 6. Debugging Checklist
+
+When something doesn't work on staging, follow this checklist:
+
+### API Returns Wrong Data or Fails
+
+1. **Check if calling correct URL**
+ ```bash
+ # In browser console
+ console.log(window.__PUBLIC_BACKEND_URL__)
+ ```
+ If undefined or localhost, the runtime env injection isn't working.
+
+2. **Check CORS**
+ ```bash
+ curl -I -X OPTIONS http://46.224.108.214:/api/v1/endpoint \
+ -H "Origin: http://46.224.108.214:5173"
+ ```
+ Should return `Access-Control-Allow-Origin` header.
+
+3. **Check container logs**
+ ```bash
+ ssh deploy@46.224.108.214 "docker logs --tail 100"
+ ```
+
+### 500 Internal Server Error
+
+1. **Check database exists**
+ ```bash
+ docker exec manacore-postgres-staging psql -U postgres -c '\l'
+ ```
+
+2. **Check tables exist**
+ ```bash
+ docker exec manacore-postgres-staging psql -U postgres -d -c '\dt'
+ ```
+
+3. **Check for type mismatches** (especially user_id uuid vs text)
+
+### 401 Unauthorized
+
+1. **Check token is being sent**
+ ```bash
+ # In browser Network tab, check Authorization header
+ ```
+
+2. **Check JWKS endpoint**
+ ```bash
+ curl http://46.224.108.214:3001/api/v1/auth/jwks
+ ```
+
+3. **Check issuer/audience match** - Token must have `iss: manacore` and `aud: manacore`
+
+### Container Not Updated
+
+1. **Check image version**
+ ```bash
+ docker ps --format '{{.Names}}: {{.Image}}'
+ ```
+
+2. **Check .env file**
+ ```bash
+ cat ~/manacore-staging/.env | grep VERSION
+ ```
+
+3. **Force recreate**
+ ```bash
+ docker compose up -d --no-deps --force-recreate
+ ```
+
+---
+
+## Summary: Common Mistakes to Avoid
+
+| Mistake | Consequence | Prevention |
+|---------|-------------|------------|
+| Using `import.meta.env` for Docker runtime | URLs baked at build time | Use `window.__PUBLIC_*__` with runtime injection |
+| Initializing API clients at module level | Client uses stale URLs | Use lazy initialization pattern |
+| Using `uuid` type for user_id | Better Auth IDs fail validation | Always use `text` type for user_id |
+| Missing CORS origin for manacore-web | Dashboard can't call backends | Add port 5173 to all backend CORS configs |
+| Wrong tag format for mana-core-auth | Deployment fails, can't find Dockerfile | Use `mana-core-auth-staging-v*` not `auth-staging-v*` |
+| Forgetting to create database | Backend crashes on startup | Create database before first deployment |
+| ALTER TABLE without USING clause | Silent failures on type conversion | Always use `USING column::new_type` |
diff --git a/package.json b/package.json
index 0fdf5c01d..232bddfbf 100644
--- a/package.json
+++ b/package.json
@@ -17,6 +17,9 @@
"format": "prettier --config .prettierrc.json --write \"**/*.{ts,tsx,js,jsx,json,md,svelte,astro}\"",
"format:check": "prettier --config .prettierrc.json --check \"**/*.{ts,tsx,js,jsx,json,md,svelte,astro}\"",
"setup:env": "node scripts/generate-env.mjs",
+ "setup:db": "./scripts/setup-databases.sh",
+ "setup:db:chat": "./scripts/setup-databases.sh chat",
+ "setup:db:auth": "./scripts/setup-databases.sh auth",
"build:packages": "pnpm --filter '@manacore/*' build",
"postinstall": "node scripts/generate-env.mjs || true && pnpm run build:packages || true",
"manacore:dev": "turbo run dev --filter=manacore...",
@@ -39,25 +42,28 @@
"dev:picture:mobile": "pnpm --filter @picture/mobile dev",
"dev:picture:backend": "pnpm --filter @picture/backend dev",
"dev:picture:app": "turbo run dev --filter=@picture/web --filter=@picture/backend",
+ "dev:picture:full": "./scripts/setup-databases.sh picture && ./scripts/setup-databases.sh auth && concurrently -n auth,backend,web -c blue,green,cyan \"pnpm dev:auth\" \"pnpm dev:picture:backend\" \"pnpm dev:picture:web\"",
"dev:chat:mobile": "pnpm --filter @chat/mobile dev",
"dev:chat:web": "pnpm --filter @chat/web dev",
"dev:chat:landing": "pnpm --filter @chat/landing dev",
"dev:chat:backend": "pnpm --filter @chat/backend start:dev",
"dev:chat:app": "turbo run dev --filter=@chat/web --filter=@chat/backend",
"dev:auth": "pnpm --filter mana-core-auth start:dev",
- "dev:chat:full": "concurrently \"pnpm dev:auth\" \"pnpm dev:chat:backend\"",
+ "dev:chat:full": "./scripts/setup-databases.sh chat && ./scripts/setup-databases.sh auth && concurrently -n auth,backend,web -c blue,green,cyan \"pnpm dev:auth\" \"pnpm dev:chat:backend\" \"pnpm dev:chat:web\"",
"zitare:dev": "turbo run dev --filter=zitare...",
"dev:zitare:mobile": "pnpm --filter @zitare/mobile dev",
"dev:zitare:web": "pnpm --filter @zitare/web dev",
"dev:zitare:landing": "pnpm --filter @zitare/landing dev",
"dev:zitare:backend": "pnpm --filter @zitare/backend dev",
"dev:zitare:app": "turbo run dev --filter=@zitare/web --filter=@zitare/backend",
+ "dev:zitare:full": "./scripts/setup-databases.sh zitare && ./scripts/setup-databases.sh auth && concurrently -n auth,backend,web -c blue,green,cyan \"pnpm dev:auth\" \"pnpm dev:zitare:backend\" \"pnpm dev:zitare:web\"",
"contacts:dev": "turbo run dev --filter=contacts...",
"dev:contacts:mobile": "pnpm --filter @contacts/mobile dev",
"dev:contacts:web": "pnpm --filter @contacts/web dev",
"dev:contacts:landing": "pnpm --filter @contacts/landing dev",
"dev:contacts:backend": "pnpm --filter @contacts/backend dev",
"dev:contacts:app": "turbo run dev --filter=@contacts/web --filter=@contacts/backend",
+ "dev:contacts:full": "./scripts/setup-databases.sh contacts && ./scripts/setup-databases.sh auth && concurrently -n auth,backend,web -c blue,green,cyan \"pnpm dev:auth\" \"pnpm dev:contacts:backend\" \"pnpm dev:contacts:web\"",
"contacts:db:push": "pnpm --filter @contacts/backend db:push",
"contacts:db:studio": "pnpm --filter @contacts/backend db:studio",
"contacts:db:seed": "pnpm --filter @contacts/backend db:seed",
@@ -67,6 +73,7 @@
"dev:calendar:landing": "pnpm --filter @calendar/landing dev",
"dev:calendar:backend": "pnpm --filter @calendar/backend dev",
"dev:calendar:app": "turbo run dev --filter=@calendar/web --filter=@calendar/backend",
+ "dev:calendar:full": "./scripts/setup-databases.sh calendar && ./scripts/setup-databases.sh auth && concurrently -n auth,backend,web -c blue,green,cyan \"pnpm dev:auth\" \"pnpm dev:calendar:backend\" \"pnpm dev:calendar:web\"",
"calendar:db:push": "pnpm --filter @calendar/backend db:push",
"calendar:db:studio": "pnpm --filter @calendar/backend db:studio",
"calendar:db:seed": "pnpm --filter @calendar/backend db:seed",
@@ -75,6 +82,7 @@
"dev:clock:landing": "pnpm --filter @clock/landing dev",
"dev:clock:backend": "pnpm --filter @clock/backend dev",
"dev:clock:app": "turbo run dev --filter=@clock/web --filter=@clock/backend",
+ "dev:clock:full": "./scripts/setup-databases.sh clock && ./scripts/setup-databases.sh auth && concurrently -n auth,backend,web -c blue,green,cyan \"pnpm dev:auth\" \"pnpm dev:clock:backend\" \"pnpm dev:clock:web\"",
"clock:db:push": "pnpm --filter @clock/backend db:push",
"clock:db:studio": "pnpm --filter @clock/backend db:studio",
"clock:db:seed": "pnpm --filter @clock/backend db:seed",
@@ -92,6 +100,7 @@
"dev:todo:landing": "pnpm --filter @todo/landing dev",
"dev:todo:backend": "pnpm --filter @todo/backend dev",
"dev:todo:app": "turbo run dev --filter=@todo/web --filter=@todo/backend",
+ "dev:todo:full": "./scripts/setup-databases.sh todo && ./scripts/setup-databases.sh auth && concurrently -n auth,backend,web -c blue,green,cyan \"pnpm dev:auth\" \"pnpm dev:todo:backend\" \"pnpm dev:todo:web\"",
"todo:db:push": "pnpm --filter @todo/backend db:push",
"todo:db:studio": "pnpm --filter @todo/backend db:studio",
"todo:db:seed": "pnpm --filter @todo/backend db:seed",
diff --git a/packages/shared-auth/src/core/authService.ts b/packages/shared-auth/src/core/authService.ts
index 3caaf4bcd..0d42ecfb0 100644
--- a/packages/shared-auth/src/core/authService.ts
+++ b/packages/shared-auth/src/core/authService.ts
@@ -39,6 +39,7 @@ const DEFAULT_ENDPOINTS: AuthEndpoints = {
refresh: '/api/v1/auth/refresh',
validate: '/api/v1/auth/validate',
forgotPassword: '/api/v1/auth/forgot-password',
+ resetPassword: '/api/v1/auth/reset-password',
googleSignIn: '/api/v1/auth/google-signin',
appleSignIn: '/api/v1/auth/apple-signin',
credits: '/api/v1/credits/balance',
@@ -192,6 +193,41 @@ export function createAuthService(config: AuthServiceConfig) {
}
},
+ /**
+ * Reset password with token
+ */
+ async resetPassword(token: string, newPassword: string): Promise {
+ try {
+ const response = await fetch(`${baseUrl}${endpoints.resetPassword}`, {
+ method: 'POST',
+ headers: { 'Content-Type': 'application/json' },
+ body: JSON.stringify({ token, newPassword }),
+ });
+
+ if (!response.ok) {
+ const errorData = await response.json();
+
+ if (errorData.message?.includes('expired')) {
+ return { success: false, error: 'Reset link has expired. Please request a new one.' };
+ }
+
+ if (errorData.message?.includes('invalid')) {
+ return { success: false, error: 'Invalid reset link. Please request a new one.' };
+ }
+
+ return { success: false, error: errorData.message || 'Password reset failed' };
+ }
+
+ return { success: true };
+ } catch (error) {
+ console.error('Error resetting password:', error);
+ return {
+ success: false,
+ error: error instanceof Error ? error.message : 'Unknown error during password reset',
+ };
+ }
+ },
+
/**
* Refresh the authentication tokens
*/
diff --git a/packages/shared-auth/src/types/index.ts b/packages/shared-auth/src/types/index.ts
index 2308bdc24..32d233bb0 100644
--- a/packages/shared-auth/src/types/index.ts
+++ b/packages/shared-auth/src/types/index.ts
@@ -128,6 +128,7 @@ export interface AuthEndpoints {
refresh: string;
validate: string;
forgotPassword: string;
+ resetPassword: string;
googleSignIn: string;
appleSignIn: string;
credits: string;
diff --git a/packages/shared-branding/src/index.ts b/packages/shared-branding/src/index.ts
index 9d4fd4b64..95f9340bd 100644
--- a/packages/shared-branding/src/index.ts
+++ b/packages/shared-branding/src/index.ts
@@ -31,6 +31,7 @@ export {
MailLogo,
MoodlitLogo,
InventoryLogo,
+ ClockLogo,
} from './logos';
// Configuration
diff --git a/packages/shared-branding/src/logos/ClockLogo.svelte b/packages/shared-branding/src/logos/ClockLogo.svelte
new file mode 100644
index 000000000..ff2c93eaf
--- /dev/null
+++ b/packages/shared-branding/src/logos/ClockLogo.svelte
@@ -0,0 +1,13 @@
+
+
+
diff --git a/packages/shared-branding/src/logos/index.ts b/packages/shared-branding/src/logos/index.ts
index 06ce0a38d..235e0e2c6 100644
--- a/packages/shared-branding/src/logos/index.ts
+++ b/packages/shared-branding/src/logos/index.ts
@@ -18,3 +18,4 @@ export { default as TodoLogo } from './TodoLogo.svelte';
export { default as MailLogo } from './MailLogo.svelte';
export { default as MoodlitLogo } from './MoodlitLogo.svelte';
export { default as InventoryLogo } from './InventoryLogo.svelte';
+export { default as ClockLogo } from './ClockLogo.svelte';
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 66a999cfb..4a38c9847 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -116,7 +116,7 @@ importers:
devDependencies:
'@nestjs/cli':
specifier: ^10.4.9
- version: 10.4.9(esbuild@0.27.0)
+ version: 10.4.9(esbuild@0.19.12)
'@nestjs/schematics':
specifier: ^10.2.3
version: 10.2.3(chokidar@3.6.0)(typescript@5.9.3)
@@ -149,7 +149,7 @@ importers:
version: 0.5.21
ts-loader:
specifier: ^9.5.1
- version: 9.5.4(typescript@5.9.3)(webpack@5.100.2(esbuild@0.27.0))
+ version: 9.5.4(typescript@5.9.3)(webpack@5.97.1(esbuild@0.19.12))
ts-node:
specifier: ^10.9.2
version: 10.9.2(@types/node@22.19.1)(typescript@5.9.3)
@@ -173,14 +173,14 @@ importers:
version: link:../../../../packages/shared-landing-ui
astro:
specifier: ^5.16.0
- version: 5.16.0(@netlify/blobs@10.4.1)(@types/node@20.19.25)(ioredis@5.8.2)(jiti@2.6.1)(lightningcss@1.30.2)(rollup@4.53.3)(terser@5.44.1)(tsx@4.20.6)(typescript@5.9.3)(yaml@2.8.1)
+ version: 5.16.0(@netlify/blobs@10.4.1)(@types/node@20.19.25)(ioredis@5.8.2)(jiti@1.21.7)(lightningcss@1.30.2)(rollup@4.53.3)(terser@5.44.1)(tsx@4.20.6)(typescript@5.9.3)(yaml@2.8.1)
typescript:
specifier: ^5.9.2
version: 5.9.3
devDependencies:
'@astrojs/tailwind':
specifier: ^6.0.2
- version: 6.0.2(astro@5.16.0(@netlify/blobs@10.4.1)(@types/node@20.19.25)(ioredis@5.8.2)(jiti@2.6.1)(lightningcss@1.30.2)(rollup@4.53.3)(terser@5.44.1)(tsx@4.20.6)(typescript@5.9.3)(yaml@2.8.1))(tailwindcss@3.4.18(tsx@4.20.6)(yaml@2.8.1))(ts-node@10.9.2(@types/node@20.19.25)(typescript@5.9.3))
+ version: 6.0.2(astro@5.16.0(@netlify/blobs@10.4.1)(@types/node@20.19.25)(ioredis@5.8.2)(jiti@1.21.7)(lightningcss@1.30.2)(rollup@4.53.3)(terser@5.44.1)(tsx@4.20.6)(typescript@5.9.3)(yaml@2.8.1))(tailwindcss@3.4.18(tsx@4.20.6)(yaml@2.8.1))(ts-node@10.9.2(@types/node@20.19.25)(typescript@5.9.3))
'@tailwindcss/typography':
specifier: ^0.5.18
version: 0.5.19(tailwindcss@3.4.18(tsx@4.20.6)(yaml@2.8.1))
@@ -189,13 +189,13 @@ importers:
version: 20.19.25
eslint:
specifier: ^9.0.0
- version: 9.39.1(jiti@2.6.1)
+ version: 9.39.1(jiti@1.21.7)
eslint-config-prettier:
specifier: ^9.1.0
- version: 9.1.2(eslint@9.39.1(jiti@2.6.1))
+ version: 9.1.2(eslint@9.39.1(jiti@1.21.7))
eslint-plugin-astro:
specifier: ^1.0.0
- version: 1.5.0(eslint@9.39.1(jiti@2.6.1))
+ version: 1.5.0(eslint@9.39.1(jiti@1.21.7))
prettier:
specifier: ^3.6.2
version: 3.6.2
@@ -266,9 +266,9 @@ importers:
specifier: ^4.0.1
version: 4.0.1(svelte@5.44.0)
devDependencies:
- '@sveltejs/adapter-auto':
- specifier: ^3.0.0
- version: 3.3.1(@sveltejs/kit@2.49.0(@opentelemetry/api@1.9.0)(@sveltejs/vite-plugin-svelte@5.1.1(svelte@5.44.0)(vite@6.4.1(@types/node@20.19.25)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.20.6)(yaml@2.8.1)))(svelte@5.44.0)(vite@6.4.1(@types/node@20.19.25)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.20.6)(yaml@2.8.1)))
+ '@sveltejs/adapter-node':
+ specifier: ^5.0.0
+ version: 5.4.0(@sveltejs/kit@2.49.0(@opentelemetry/api@1.9.0)(@sveltejs/vite-plugin-svelte@5.1.1(svelte@5.44.0)(vite@6.4.1(@types/node@20.19.25)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.20.6)(yaml@2.8.1)))(svelte@5.44.0)(vite@6.4.1(@types/node@20.19.25)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.20.6)(yaml@2.8.1)))
'@sveltejs/kit':
specifier: ^2.0.0
version: 2.49.0(@opentelemetry/api@1.9.0)(@sveltejs/vite-plugin-svelte@5.1.1(svelte@5.44.0)(vite@6.4.1(@types/node@20.19.25)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.20.6)(yaml@2.8.1)))(svelte@5.44.0)(vite@6.4.1(@types/node@20.19.25)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.20.6)(yaml@2.8.1))
@@ -528,19 +528,19 @@ importers:
version: 18.3.27
'@typescript-eslint/eslint-plugin':
specifier: ^7.7.0
- version: 7.18.0(@typescript-eslint/parser@7.18.0(eslint@9.39.1(jiti@1.21.7))(typescript@5.3.3))(eslint@9.39.1(jiti@1.21.7))(typescript@5.3.3)
+ version: 7.18.0(@typescript-eslint/parser@7.18.0(eslint@9.39.1(jiti@2.6.1))(typescript@5.3.3))(eslint@9.39.1(jiti@2.6.1))(typescript@5.3.3)
'@typescript-eslint/parser':
specifier: ^7.7.0
- version: 7.18.0(eslint@9.39.1(jiti@1.21.7))(typescript@5.3.3)
+ version: 7.18.0(eslint@9.39.1(jiti@2.6.1))(typescript@5.3.3)
dotenv:
specifier: ^16.4.7
version: 16.6.1
eslint:
specifier: ^9.39.1
- version: 9.39.1(jiti@1.21.7)
+ version: 9.39.1(jiti@2.6.1)
eslint-config-universe:
specifier: ^12.0.1
- version: 12.1.0(@types/eslint@9.6.1)(eslint@9.39.1(jiti@1.21.7))(prettier@3.6.2)(typescript@5.3.3)
+ version: 12.1.0(@types/eslint@9.6.1)(eslint@9.39.1(jiti@2.6.1))(prettier@3.6.2)(typescript@5.3.3)
prettier:
specifier: ^3.2.5
version: 3.6.2
@@ -856,9 +856,9 @@ importers:
specifier: ^3.1.0
version: 3.1.0
devDependencies:
- '@sveltejs/adapter-auto':
- specifier: ^3.0.0
- version: 3.3.1(@sveltejs/kit@2.49.0(@opentelemetry/api@1.9.0)(@sveltejs/vite-plugin-svelte@5.1.1(svelte@5.44.0)(vite@6.4.1(@types/node@20.19.25)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.20.6)(yaml@2.8.1)))(svelte@5.44.0)(vite@6.4.1(@types/node@20.19.25)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.20.6)(yaml@2.8.1)))
+ '@sveltejs/adapter-node':
+ specifier: ^5.0.0
+ version: 5.4.0(@sveltejs/kit@2.49.0(@opentelemetry/api@1.9.0)(@sveltejs/vite-plugin-svelte@5.1.1(svelte@5.44.0)(vite@6.4.1(@types/node@20.19.25)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.20.6)(yaml@2.8.1)))(svelte@5.44.0)(vite@6.4.1(@types/node@20.19.25)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.20.6)(yaml@2.8.1)))
'@sveltejs/kit':
specifier: ^2.0.0
version: 2.49.0(@opentelemetry/api@1.9.0)(@sveltejs/vite-plugin-svelte@5.1.1(svelte@5.44.0)(vite@6.4.1(@types/node@20.19.25)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.20.6)(yaml@2.8.1)))(svelte@5.44.0)(vite@6.4.1(@types/node@20.19.25)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.20.6)(yaml@2.8.1))
@@ -1442,9 +1442,6 @@ importers:
'@manacore/shared-subscription-ui':
specifier: workspace:*
version: link:../../../../packages/shared-subscription-ui
- '@manacore/shared-supabase':
- specifier: workspace:*
- version: link:../../../../packages/shared-supabase
'@manacore/shared-tailwind':
specifier: workspace:*
version: link:../../../../packages/shared-tailwind
@@ -1463,12 +1460,6 @@ importers:
'@manacore/shared-utils':
specifier: workspace:*
version: link:../../../../packages/shared-utils
- '@supabase/ssr':
- specifier: ^0.5.2
- version: 0.5.2(@supabase/supabase-js@2.84.0)
- '@supabase/supabase-js':
- specifier: ^2.81.1
- version: 2.84.0
svelte-dnd-action:
specifier: ^0.9.68
version: 0.9.68(svelte@5.44.0)
@@ -1479,12 +1470,9 @@ importers:
'@playwright/test':
specifier: ^1.51.0
version: 1.57.0
- '@sveltejs/adapter-auto':
- specifier: ^7.0.0
- version: 7.0.0(@sveltejs/kit@2.49.0(@opentelemetry/api@1.9.0)(@sveltejs/vite-plugin-svelte@5.1.1(svelte@5.44.0)(vite@6.4.1(@types/node@22.19.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.20.6)(yaml@2.8.1)))(svelte@5.44.0)(vite@6.4.1(@types/node@22.19.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.20.6)(yaml@2.8.1)))
- '@sveltejs/adapter-netlify':
- specifier: ^5.2.4
- version: 5.2.4(@sveltejs/kit@2.49.0(@opentelemetry/api@1.9.0)(@sveltejs/vite-plugin-svelte@5.1.1(svelte@5.44.0)(vite@6.4.1(@types/node@22.19.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.20.6)(yaml@2.8.1)))(svelte@5.44.0)(vite@6.4.1(@types/node@22.19.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.20.6)(yaml@2.8.1)))
+ '@sveltejs/adapter-node':
+ specifier: ^5.0.0
+ version: 5.4.0(@sveltejs/kit@2.49.0(@opentelemetry/api@1.9.0)(@sveltejs/vite-plugin-svelte@5.1.1(svelte@5.44.0)(vite@6.4.1(@types/node@22.19.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.20.6)(yaml@2.8.1)))(svelte@5.44.0)(vite@6.4.1(@types/node@22.19.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.20.6)(yaml@2.8.1)))
'@sveltejs/kit':
specifier: ^2.15.7
version: 2.49.0(@opentelemetry/api@1.9.0)(@sveltejs/vite-plugin-svelte@5.1.1(svelte@5.44.0)(vite@6.4.1(@types/node@22.19.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.20.6)(yaml@2.8.1)))(svelte@5.44.0)(vite@6.4.1(@types/node@22.19.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.20.6)(yaml@2.8.1))
@@ -2611,9 +2599,9 @@ importers:
specifier: ^4.0.1
version: 4.0.1(svelte@5.44.0)
devDependencies:
- '@sveltejs/adapter-auto':
- specifier: ^3.0.0
- version: 3.3.1(@sveltejs/kit@2.49.0(@opentelemetry/api@1.9.0)(@sveltejs/vite-plugin-svelte@5.1.1(svelte@5.44.0)(vite@6.4.1(@types/node@20.19.25)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.20.6)(yaml@2.8.1)))(svelte@5.44.0)(vite@6.4.1(@types/node@20.19.25)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.20.6)(yaml@2.8.1)))
+ '@sveltejs/adapter-node':
+ specifier: ^5.0.0
+ version: 5.4.0(@sveltejs/kit@2.49.0(@opentelemetry/api@1.9.0)(@sveltejs/vite-plugin-svelte@5.1.1(svelte@5.44.0)(vite@6.4.1(@types/node@20.19.25)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.20.6)(yaml@2.8.1)))(svelte@5.44.0)(vite@6.4.1(@types/node@20.19.25)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.20.6)(yaml@2.8.1)))
'@sveltejs/kit':
specifier: ^2.0.0
version: 2.49.0(@opentelemetry/api@1.9.0)(@sveltejs/vite-plugin-svelte@5.1.1(svelte@5.44.0)(vite@6.4.1(@types/node@20.19.25)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.20.6)(yaml@2.8.1)))(svelte@5.44.0)(vite@6.4.1(@types/node@20.19.25)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.20.6)(yaml@2.8.1))
@@ -4159,7 +4147,7 @@ importers:
version: 1.57.0
jest:
specifier: ^29.0.0
- version: 29.7.0(@types/node@24.10.1)
+ version: 29.7.0(@types/node@24.10.1)(ts-node@10.9.2(@types/node@24.10.1)(typescript@5.9.3))
vitest:
specifier: ^3.0.0
version: 3.2.4(@types/debug@4.1.12)(@types/node@24.10.1)(@vitest/browser@3.2.4)(@vitest/ui@3.2.4)(jiti@2.6.1)(jsdom@27.2.0)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.20.6)(yaml@2.8.1)
@@ -4279,7 +4267,7 @@ importers:
devDependencies:
'@nestjs/cli':
specifier: ^11.0.0
- version: 11.0.12(@types/node@22.19.1)(esbuild@0.19.12)
+ version: 11.0.12(@types/node@22.19.1)
'@nestjs/schematics':
specifier: ^11.0.0
version: 11.0.9(chokidar@4.0.3)(typescript@5.9.3)
@@ -4333,10 +4321,10 @@ importers:
version: 7.1.4
ts-jest:
specifier: ^29.2.5
- version: 29.4.5(@babel/core@7.28.5)(@jest/transform@30.2.0)(@jest/types@30.2.0)(babel-jest@30.2.0(@babel/core@7.28.5))(esbuild@0.19.12)(jest-util@30.2.0)(jest@29.7.0(@types/node@22.19.1)(ts-node@10.9.2(@types/node@22.19.1)(typescript@5.9.3)))(typescript@5.9.3)
+ version: 29.4.5(@babel/core@7.28.5)(@jest/transform@30.2.0)(@jest/types@30.2.0)(babel-jest@30.2.0(@babel/core@7.28.5))(jest-util@30.2.0)(jest@29.7.0(@types/node@22.19.1)(ts-node@10.9.2(@types/node@22.19.1)(typescript@5.9.3)))(typescript@5.9.3)
ts-loader:
specifier: ^9.5.1
- version: 9.5.4(typescript@5.9.3)(webpack@5.100.2(esbuild@0.19.12))
+ version: 9.5.4(typescript@5.9.3)(webpack@5.100.2)
ts-node:
specifier: ^10.9.2
version: 10.9.2(@types/node@22.19.1)(typescript@5.9.3)
@@ -6551,7 +6539,7 @@ packages:
'@expo/bunyan@4.0.1':
resolution: {integrity: sha512-+Lla7nYSiHZirgK+U/uYzsLv/X+HaJienbD5AKX1UQZHYfWaP+9uuQluRB4GrEVWF0GZ7vEVp/jzaOT9k/SQlg==}
- engines: {node: '>=0.10.0'}
+ engines: {'0': node >=0.10.0}
'@expo/cli@0.22.26':
resolution: {integrity: sha512-I689wc8Fn/AX7aUGiwrh3HnssiORMJtR2fpksX+JIe8Cj/EDleblYMSwRPd0025wrwOV9UN1KM/RuEt/QjCS3Q==}
@@ -9045,11 +9033,6 @@ packages:
resolution: {integrity: sha512-ThqjxiCwWiZAroHnYPmnNl6tZk6jxGcG2a7Hp/3kcolPcMj89kWjUTA3cHmhdIWYsP84fHp8MAQjYWMLf7HEUg==}
engines: {node: '>=20.0.0'}
- '@supabase/ssr@0.5.2':
- resolution: {integrity: sha512-n3plRhr2Bs8Xun1o4S3k1CDv17iH5QY9YcoEvXX3bxV1/5XSasA0mNXYycFmADIdtdE6BG9MRjP5CGIs8qxC8A==}
- peerDependencies:
- '@supabase/supabase-js': ^2.43.4
-
'@supabase/ssr@0.7.0':
resolution: {integrity: sha512-G65t5EhLSJ5c8hTCcXifSL9Q/ZRXvqgXeNo+d3P56f4U1IxwTqjB64UfmfixvmMcjuxnq2yGqEWVJqUcO+AzAg==}
peerDependencies:
@@ -19713,6 +19696,16 @@ snapshots:
transitivePeerDependencies:
- ts-node
+ '@astrojs/tailwind@6.0.2(astro@5.16.0(@netlify/blobs@10.4.1)(@types/node@20.19.25)(ioredis@5.8.2)(jiti@1.21.7)(lightningcss@1.30.2)(rollup@4.53.3)(terser@5.44.1)(tsx@4.20.6)(typescript@5.9.3)(yaml@2.8.1))(tailwindcss@3.4.18(tsx@4.20.6)(yaml@2.8.1))(ts-node@10.9.2(@types/node@20.19.25)(typescript@5.9.3))':
+ dependencies:
+ astro: 5.16.0(@netlify/blobs@10.4.1)(@types/node@20.19.25)(ioredis@5.8.2)(jiti@1.21.7)(lightningcss@1.30.2)(rollup@4.53.3)(terser@5.44.1)(tsx@4.20.6)(typescript@5.9.3)(yaml@2.8.1)
+ autoprefixer: 10.4.22(postcss@8.5.6)
+ postcss: 8.5.6
+ postcss-load-config: 4.0.2(postcss@8.5.6)(ts-node@10.9.2(@types/node@20.19.25)(typescript@5.9.3))
+ tailwindcss: 3.4.18(tsx@4.20.6)(yaml@2.8.1)
+ transitivePeerDependencies:
+ - ts-node
+
'@astrojs/tailwind@6.0.2(astro@5.16.0(@netlify/blobs@10.4.1)(@types/node@20.19.25)(ioredis@5.8.2)(jiti@2.6.1)(lightningcss@1.30.2)(rollup@4.53.3)(terser@5.44.1)(tsx@4.20.6)(typescript@5.9.3)(yaml@2.8.1))(tailwindcss@3.4.18(tsx@4.20.6)(yaml@2.8.1))(ts-node@10.9.2(@types/node@20.19.25)(typescript@5.9.3))':
dependencies:
astro: 5.16.0(@netlify/blobs@10.4.1)(@types/node@20.19.25)(ioredis@5.8.2)(jiti@2.6.1)(lightningcss@1.30.2)(rollup@4.53.3)(terser@5.44.1)(tsx@4.20.6)(typescript@5.9.3)(yaml@2.8.1)
@@ -22226,7 +22219,7 @@ snapshots:
wrap-ansi: 7.0.0
ws: 8.18.3
optionalDependencies:
- expo-router: 6.0.15(jiucxy5ca3jdtbnulaxuc46jdq)
+ expo-router: 6.0.15(5e7ih2rh6mb55wruwvjljgzihq)
react-native: 0.81.4(@babel/core@7.28.5)(@types/react@19.2.7)(react@19.1.0)
transitivePeerDependencies:
- '@modelcontextprotocol/sdk'
@@ -22303,7 +22296,7 @@ snapshots:
wrap-ansi: 7.0.0
ws: 8.18.3
optionalDependencies:
- expo-router: 6.0.15(ohit2up6tuxb3x34brxduivol4)
+ expo-router: 6.0.15(nttrd3tw67nnyhowcwgdzipb5e)
react-native: 0.81.5(@babel/core@7.28.5)(@types/react@19.2.7)(react@19.1.0)
transitivePeerDependencies:
- '@modelcontextprotocol/sdk'
@@ -23538,41 +23531,6 @@ snapshots:
jest-util: 30.2.0
slash: 3.0.0
- '@jest/core@29.7.0':
- dependencies:
- '@jest/console': 29.7.0
- '@jest/reporters': 29.7.0
- '@jest/test-result': 29.7.0
- '@jest/transform': 29.7.0
- '@jest/types': 29.6.3
- '@types/node': 22.19.1
- ansi-escapes: 4.3.2
- chalk: 4.1.2
- ci-info: 3.9.0
- exit: 0.1.2
- graceful-fs: 4.2.11
- jest-changed-files: 29.7.0
- jest-config: 29.7.0(@types/node@22.19.1)
- jest-haste-map: 29.7.0
- jest-message-util: 29.7.0
- jest-regex-util: 29.6.3
- jest-resolve: 29.7.0
- jest-resolve-dependencies: 29.7.0
- jest-runner: 29.7.0
- jest-runtime: 29.7.0
- jest-snapshot: 29.7.0
- jest-util: 29.7.0
- jest-validate: 29.7.0
- jest-watcher: 29.7.0
- micromatch: 4.0.8
- pretty-format: 29.7.0
- slash: 3.0.0
- strip-ansi: 6.0.1
- transitivePeerDependencies:
- - babel-plugin-macros
- - supports-color
- - ts-node
-
'@jest/core@29.7.0(ts-node@10.9.2(@types/node@22.19.1)(typescript@5.9.3))':
dependencies:
'@jest/console': 29.7.0
@@ -23608,6 +23566,78 @@ snapshots:
- supports-color
- ts-node
+ '@jest/core@29.7.0(ts-node@10.9.2(@types/node@24.10.1)(typescript@5.9.3))':
+ dependencies:
+ '@jest/console': 29.7.0
+ '@jest/reporters': 29.7.0
+ '@jest/test-result': 29.7.0
+ '@jest/transform': 29.7.0
+ '@jest/types': 29.6.3
+ '@types/node': 22.19.1
+ ansi-escapes: 4.3.2
+ chalk: 4.1.2
+ ci-info: 3.9.0
+ exit: 0.1.2
+ graceful-fs: 4.2.11
+ jest-changed-files: 29.7.0
+ jest-config: 29.7.0(@types/node@22.19.1)(ts-node@10.9.2(@types/node@24.10.1)(typescript@5.9.3))
+ jest-haste-map: 29.7.0
+ jest-message-util: 29.7.0
+ jest-regex-util: 29.6.3
+ jest-resolve: 29.7.0
+ jest-resolve-dependencies: 29.7.0
+ jest-runner: 29.7.0
+ jest-runtime: 29.7.0
+ jest-snapshot: 29.7.0
+ jest-util: 29.7.0
+ jest-validate: 29.7.0
+ jest-watcher: 29.7.0
+ micromatch: 4.0.8
+ pretty-format: 29.7.0
+ slash: 3.0.0
+ strip-ansi: 6.0.1
+ transitivePeerDependencies:
+ - babel-plugin-macros
+ - supports-color
+ - ts-node
+
+ '@jest/core@30.2.0(esbuild-register@3.6.0(esbuild@0.19.12))(ts-node@10.9.2(@types/node@22.19.1)(typescript@5.9.3))':
+ dependencies:
+ '@jest/console': 30.2.0
+ '@jest/pattern': 30.0.1
+ '@jest/reporters': 30.2.0
+ '@jest/test-result': 30.2.0
+ '@jest/transform': 30.2.0
+ '@jest/types': 30.2.0
+ '@types/node': 22.19.1
+ ansi-escapes: 4.3.2
+ chalk: 4.1.2
+ ci-info: 4.3.1
+ exit-x: 0.2.2
+ graceful-fs: 4.2.11
+ jest-changed-files: 30.2.0
+ jest-config: 30.2.0(@types/node@22.19.1)(esbuild-register@3.6.0(esbuild@0.19.12))(ts-node@10.9.2(@types/node@22.19.1)(typescript@5.9.3))
+ jest-haste-map: 30.2.0
+ jest-message-util: 30.2.0
+ jest-regex-util: 30.0.1
+ jest-resolve: 30.2.0
+ jest-resolve-dependencies: 30.2.0
+ jest-runner: 30.2.0
+ jest-runtime: 30.2.0
+ jest-snapshot: 30.2.0
+ jest-util: 30.2.0
+ jest-validate: 30.2.0
+ jest-watcher: 30.2.0
+ micromatch: 4.0.8
+ pretty-format: 30.2.0
+ slash: 3.0.0
+ transitivePeerDependencies:
+ - babel-plugin-macros
+ - esbuild-register
+ - supports-color
+ - ts-node
+ optional: true
+
'@jest/core@30.2.0(esbuild-register@3.6.0(esbuild@0.27.0))':
dependencies:
'@jest/console': 30.2.0
@@ -24032,6 +24062,32 @@ snapshots:
- uglify-js
- webpack-cli
+ '@nestjs/cli@10.4.9(esbuild@0.19.12)':
+ dependencies:
+ '@angular-devkit/core': 17.3.11(chokidar@3.6.0)
+ '@angular-devkit/schematics': 17.3.11(chokidar@3.6.0)
+ '@angular-devkit/schematics-cli': 17.3.11(chokidar@3.6.0)
+ '@nestjs/schematics': 10.2.3(chokidar@3.6.0)(typescript@5.7.2)
+ chalk: 4.1.2
+ chokidar: 3.6.0
+ cli-table3: 0.6.5
+ commander: 4.1.1
+ fork-ts-checker-webpack-plugin: 9.0.2(typescript@5.7.2)(webpack@5.97.1(esbuild@0.19.12))
+ glob: 10.4.5
+ inquirer: 8.2.6
+ node-emoji: 1.11.0
+ ora: 5.4.1
+ tree-kill: 1.2.2
+ tsconfig-paths: 4.2.0
+ tsconfig-paths-webpack-plugin: 4.2.0
+ typescript: 5.7.2
+ webpack: 5.97.1(esbuild@0.19.12)
+ webpack-node-externals: 3.0.0
+ transitivePeerDependencies:
+ - esbuild
+ - uglify-js
+ - webpack-cli
+
'@nestjs/cli@10.4.9(esbuild@0.27.0)':
dependencies:
'@angular-devkit/core': 17.3.11(chokidar@3.6.0)
@@ -24058,7 +24114,7 @@ snapshots:
- uglify-js
- webpack-cli
- '@nestjs/cli@11.0.12(@types/node@22.19.1)(esbuild@0.19.12)':
+ '@nestjs/cli@11.0.12(@types/node@22.19.1)':
dependencies:
'@angular-devkit/core': 19.2.19(chokidar@4.0.3)
'@angular-devkit/schematics': 19.2.19(chokidar@4.0.3)
@@ -24069,14 +24125,14 @@ snapshots:
chokidar: 4.0.3
cli-table3: 0.6.5
commander: 4.1.1
- fork-ts-checker-webpack-plugin: 9.1.0(typescript@5.9.3)(webpack@5.100.2(esbuild@0.19.12))
+ fork-ts-checker-webpack-plugin: 9.1.0(typescript@5.9.3)(webpack@5.100.2)
glob: 12.0.0
node-emoji: 1.11.0
ora: 5.4.1
tsconfig-paths: 4.2.0
tsconfig-paths-webpack-plugin: 4.2.0
typescript: 5.9.3
- webpack: 5.100.2(esbuild@0.19.12)
+ webpack: 5.100.2
webpack-node-externals: 3.0.0
transitivePeerDependencies:
- '@types/node'
@@ -26168,12 +26224,6 @@ snapshots:
- bufferutil
- utf-8-validate
- '@supabase/ssr@0.5.2(@supabase/supabase-js@2.84.0)':
- dependencies:
- '@supabase/supabase-js': 2.84.0
- '@types/cookie': 0.6.0
- cookie: 0.7.2
-
'@supabase/ssr@0.7.0(@supabase/supabase-js@2.84.0)':
dependencies:
'@supabase/supabase-js': 2.84.0
@@ -26211,21 +26261,10 @@ snapshots:
dependencies:
'@sveltejs/kit': 2.49.0(@opentelemetry/api@1.9.0)(@sveltejs/vite-plugin-svelte@6.2.1(svelte@5.44.0)(vite@7.2.4(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.20.6)(yaml@2.8.1)))(svelte@5.44.0)(vite@7.2.4(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.20.6)(yaml@2.8.1))
- '@sveltejs/adapter-auto@7.0.0(@sveltejs/kit@2.49.0(@opentelemetry/api@1.9.0)(@sveltejs/vite-plugin-svelte@5.1.1(svelte@5.44.0)(vite@6.4.1(@types/node@22.19.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.20.6)(yaml@2.8.1)))(svelte@5.44.0)(vite@6.4.1(@types/node@22.19.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.20.6)(yaml@2.8.1)))':
- dependencies:
- '@sveltejs/kit': 2.49.0(@opentelemetry/api@1.9.0)(@sveltejs/vite-plugin-svelte@5.1.1(svelte@5.44.0)(vite@6.4.1(@types/node@22.19.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.20.6)(yaml@2.8.1)))(svelte@5.44.0)(vite@6.4.1(@types/node@22.19.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.20.6)(yaml@2.8.1))
-
'@sveltejs/adapter-auto@7.0.0(@sveltejs/kit@2.49.0(@opentelemetry/api@1.9.0)(@sveltejs/vite-plugin-svelte@6.2.1(svelte@5.44.0)(vite@7.2.4(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.20.6)(yaml@2.8.1)))(svelte@5.44.0)(vite@7.2.4(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.20.6)(yaml@2.8.1)))':
dependencies:
'@sveltejs/kit': 2.49.0(@opentelemetry/api@1.9.0)(@sveltejs/vite-plugin-svelte@6.2.1(svelte@5.44.0)(vite@7.2.4(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.20.6)(yaml@2.8.1)))(svelte@5.44.0)(vite@7.2.4(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.20.6)(yaml@2.8.1))
- '@sveltejs/adapter-netlify@5.2.4(@sveltejs/kit@2.49.0(@opentelemetry/api@1.9.0)(@sveltejs/vite-plugin-svelte@5.1.1(svelte@5.44.0)(vite@6.4.1(@types/node@22.19.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.20.6)(yaml@2.8.1)))(svelte@5.44.0)(vite@6.4.1(@types/node@22.19.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.20.6)(yaml@2.8.1)))':
- dependencies:
- '@iarna/toml': 2.2.5
- '@sveltejs/kit': 2.49.0(@opentelemetry/api@1.9.0)(@sveltejs/vite-plugin-svelte@5.1.1(svelte@5.44.0)(vite@6.4.1(@types/node@22.19.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.20.6)(yaml@2.8.1)))(svelte@5.44.0)(vite@6.4.1(@types/node@22.19.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.20.6)(yaml@2.8.1))
- esbuild: 0.25.12
- set-cookie-parser: 2.7.2
-
'@sveltejs/adapter-netlify@5.2.4(@sveltejs/kit@2.49.0(@opentelemetry/api@1.9.0)(@sveltejs/vite-plugin-svelte@6.2.1(svelte@5.44.0)(vite@7.2.4(@types/node@20.19.25)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.20.6)(yaml@2.8.1)))(svelte@5.44.0)(vite@7.2.4(@types/node@20.19.25)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.20.6)(yaml@2.8.1)))':
dependencies:
'@iarna/toml': 2.2.5
@@ -26233,6 +26272,22 @@ snapshots:
esbuild: 0.25.12
set-cookie-parser: 2.7.2
+ '@sveltejs/adapter-node@5.4.0(@sveltejs/kit@2.49.0(@opentelemetry/api@1.9.0)(@sveltejs/vite-plugin-svelte@5.1.1(svelte@5.44.0)(vite@6.4.1(@types/node@20.19.25)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.20.6)(yaml@2.8.1)))(svelte@5.44.0)(vite@6.4.1(@types/node@20.19.25)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.20.6)(yaml@2.8.1)))':
+ dependencies:
+ '@rollup/plugin-commonjs': 28.0.9(rollup@4.53.3)
+ '@rollup/plugin-json': 6.1.0(rollup@4.53.3)
+ '@rollup/plugin-node-resolve': 16.0.3(rollup@4.53.3)
+ '@sveltejs/kit': 2.49.0(@opentelemetry/api@1.9.0)(@sveltejs/vite-plugin-svelte@5.1.1(svelte@5.44.0)(vite@6.4.1(@types/node@20.19.25)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.20.6)(yaml@2.8.1)))(svelte@5.44.0)(vite@6.4.1(@types/node@20.19.25)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.20.6)(yaml@2.8.1))
+ rollup: 4.53.3
+
+ '@sveltejs/adapter-node@5.4.0(@sveltejs/kit@2.49.0(@opentelemetry/api@1.9.0)(@sveltejs/vite-plugin-svelte@5.1.1(svelte@5.44.0)(vite@6.4.1(@types/node@22.19.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.20.6)(yaml@2.8.1)))(svelte@5.44.0)(vite@6.4.1(@types/node@22.19.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.20.6)(yaml@2.8.1)))':
+ dependencies:
+ '@rollup/plugin-commonjs': 28.0.9(rollup@4.53.3)
+ '@rollup/plugin-json': 6.1.0(rollup@4.53.3)
+ '@rollup/plugin-node-resolve': 16.0.3(rollup@4.53.3)
+ '@sveltejs/kit': 2.49.0(@opentelemetry/api@1.9.0)(@sveltejs/vite-plugin-svelte@5.1.1(svelte@5.44.0)(vite@6.4.1(@types/node@22.19.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.20.6)(yaml@2.8.1)))(svelte@5.44.0)(vite@6.4.1(@types/node@22.19.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.20.6)(yaml@2.8.1))
+ rollup: 4.53.3
+
'@sveltejs/adapter-node@5.4.0(@sveltejs/kit@2.49.0(@opentelemetry/api@1.9.0)(@sveltejs/vite-plugin-svelte@6.2.1(svelte@5.44.0)(vite@7.2.4(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.20.6)(yaml@2.8.1)))(svelte@5.44.0)(vite@7.2.4(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.20.6)(yaml@2.8.1)))':
dependencies:
'@rollup/plugin-commonjs': 28.0.9(rollup@4.53.3)
@@ -26631,7 +26686,7 @@ snapshots:
react-test-renderer: 19.1.0(react@19.1.0)
redent: 3.0.0
- '@testing-library/react-native@13.3.3(jest@29.7.0(@types/node@22.19.1)(ts-node@10.9.2(@types/node@22.19.1)(typescript@5.9.3)))(react-native@0.81.5(@babel/core@7.28.5)(@types/react@19.2.7)(react@19.1.0))(react-test-renderer@19.1.0(react@19.1.0))(react@19.1.0)':
+ '@testing-library/react-native@13.3.3(jest@30.2.0(@types/node@22.19.1)(esbuild-register@3.6.0(esbuild@0.19.12))(ts-node@10.9.2(@types/node@22.19.1)(typescript@5.9.3)))(react-native@0.81.5(@babel/core@7.28.5)(@types/react@19.2.7)(react@19.1.0))(react-test-renderer@19.1.0(react@19.1.0))(react@19.1.0)':
dependencies:
jest-matcher-utils: 30.2.0
picocolors: 1.1.1
@@ -26641,20 +26696,7 @@ snapshots:
react-test-renderer: 19.1.0(react@19.1.0)
redent: 3.0.0
optionalDependencies:
- jest: 29.7.0(@types/node@22.19.1)(ts-node@10.9.2(@types/node@22.19.1)(typescript@5.9.3))
- optional: true
-
- '@testing-library/react-native@13.3.3(jest@30.2.0(@types/node@20.19.25)(esbuild-register@3.6.0(esbuild@0.27.0)))(react-native@0.81.4(@babel/core@7.28.5)(@types/react@19.2.7)(react@19.1.0))(react-test-renderer@19.1.0(react@19.1.0))(react@19.1.0)':
- dependencies:
- jest-matcher-utils: 30.2.0
- picocolors: 1.1.1
- pretty-format: 30.2.0
- react: 19.1.0
- react-native: 0.81.4(@babel/core@7.28.5)(@types/react@19.2.7)(react@19.1.0)
- react-test-renderer: 19.1.0(react@19.1.0)
- redent: 3.0.0
- optionalDependencies:
- jest: 30.2.0(@types/node@20.19.25)(esbuild-register@3.6.0(esbuild@0.27.0))
+ jest: 30.2.0(@types/node@22.19.1)(esbuild-register@3.6.0(esbuild@0.19.12))(ts-node@10.9.2(@types/node@22.19.1)(typescript@5.9.3))
optional: true
'@testing-library/react-native@13.3.3(jest@30.2.0(@types/node@24.10.1)(esbuild-register@3.6.0(esbuild@0.27.0)))(react-native@0.81.4(@babel/core@7.28.5)(@types/react@19.2.7)(react@19.1.0))(react-test-renderer@19.1.0(react@19.1.0))(react@19.1.0)':
@@ -27168,16 +27210,16 @@ snapshots:
transitivePeerDependencies:
- supports-color
- '@typescript-eslint/eslint-plugin@6.21.0(@typescript-eslint/parser@6.21.0(eslint@9.39.1(jiti@1.21.7))(typescript@5.3.3))(eslint@9.39.1(jiti@1.21.7))(typescript@5.3.3)':
+ '@typescript-eslint/eslint-plugin@6.21.0(@typescript-eslint/parser@6.21.0(eslint@9.39.1(jiti@2.6.1))(typescript@5.3.3))(eslint@9.39.1(jiti@2.6.1))(typescript@5.3.3)':
dependencies:
'@eslint-community/regexpp': 4.12.2
- '@typescript-eslint/parser': 6.21.0(eslint@9.39.1(jiti@1.21.7))(typescript@5.3.3)
+ '@typescript-eslint/parser': 6.21.0(eslint@9.39.1(jiti@2.6.1))(typescript@5.3.3)
'@typescript-eslint/scope-manager': 6.21.0
- '@typescript-eslint/type-utils': 6.21.0(eslint@9.39.1(jiti@1.21.7))(typescript@5.3.3)
- '@typescript-eslint/utils': 6.21.0(eslint@9.39.1(jiti@1.21.7))(typescript@5.3.3)
+ '@typescript-eslint/type-utils': 6.21.0(eslint@9.39.1(jiti@2.6.1))(typescript@5.3.3)
+ '@typescript-eslint/utils': 6.21.0(eslint@9.39.1(jiti@2.6.1))(typescript@5.3.3)
'@typescript-eslint/visitor-keys': 6.21.0
debug: 4.4.3
- eslint: 9.39.1(jiti@1.21.7)
+ eslint: 9.39.1(jiti@2.6.1)
graphemer: 1.4.0
ignore: 5.3.2
natural-compare: 1.4.0
@@ -27226,15 +27268,15 @@ snapshots:
transitivePeerDependencies:
- supports-color
- '@typescript-eslint/eslint-plugin@7.18.0(@typescript-eslint/parser@7.18.0(eslint@9.39.1(jiti@1.21.7))(typescript@5.3.3))(eslint@9.39.1(jiti@1.21.7))(typescript@5.3.3)':
+ '@typescript-eslint/eslint-plugin@7.18.0(@typescript-eslint/parser@7.18.0(eslint@9.39.1(jiti@2.6.1))(typescript@5.3.3))(eslint@9.39.1(jiti@2.6.1))(typescript@5.3.3)':
dependencies:
'@eslint-community/regexpp': 4.12.2
- '@typescript-eslint/parser': 7.18.0(eslint@9.39.1(jiti@1.21.7))(typescript@5.3.3)
+ '@typescript-eslint/parser': 7.18.0(eslint@9.39.1(jiti@2.6.1))(typescript@5.3.3)
'@typescript-eslint/scope-manager': 7.18.0
- '@typescript-eslint/type-utils': 7.18.0(eslint@9.39.1(jiti@1.21.7))(typescript@5.3.3)
- '@typescript-eslint/utils': 7.18.0(eslint@9.39.1(jiti@1.21.7))(typescript@5.3.3)
+ '@typescript-eslint/type-utils': 7.18.0(eslint@9.39.1(jiti@2.6.1))(typescript@5.3.3)
+ '@typescript-eslint/utils': 7.18.0(eslint@9.39.1(jiti@2.6.1))(typescript@5.3.3)
'@typescript-eslint/visitor-keys': 7.18.0
- eslint: 9.39.1(jiti@1.21.7)
+ eslint: 9.39.1(jiti@2.6.1)
graphemer: 1.4.0
ignore: 5.3.2
natural-compare: 1.4.0
@@ -27326,14 +27368,14 @@ snapshots:
transitivePeerDependencies:
- supports-color
- '@typescript-eslint/parser@6.21.0(eslint@9.39.1(jiti@1.21.7))(typescript@5.3.3)':
+ '@typescript-eslint/parser@6.21.0(eslint@9.39.1(jiti@2.6.1))(typescript@5.3.3)':
dependencies:
'@typescript-eslint/scope-manager': 6.21.0
'@typescript-eslint/types': 6.21.0
'@typescript-eslint/typescript-estree': 6.21.0(typescript@5.3.3)
'@typescript-eslint/visitor-keys': 6.21.0
debug: 4.4.3
- eslint: 9.39.1(jiti@1.21.7)
+ eslint: 9.39.1(jiti@2.6.1)
optionalDependencies:
typescript: 5.3.3
transitivePeerDependencies:
@@ -27365,14 +27407,14 @@ snapshots:
transitivePeerDependencies:
- supports-color
- '@typescript-eslint/parser@7.18.0(eslint@9.39.1(jiti@1.21.7))(typescript@5.3.3)':
+ '@typescript-eslint/parser@7.18.0(eslint@9.39.1(jiti@2.6.1))(typescript@5.3.3)':
dependencies:
'@typescript-eslint/scope-manager': 7.18.0
'@typescript-eslint/types': 7.18.0
'@typescript-eslint/typescript-estree': 7.18.0(typescript@5.3.3)
'@typescript-eslint/visitor-keys': 7.18.0
debug: 4.4.3
- eslint: 9.39.1(jiti@1.21.7)
+ eslint: 9.39.1(jiti@2.6.1)
optionalDependencies:
typescript: 5.3.3
transitivePeerDependencies:
@@ -27498,12 +27540,12 @@ snapshots:
transitivePeerDependencies:
- supports-color
- '@typescript-eslint/type-utils@6.21.0(eslint@9.39.1(jiti@1.21.7))(typescript@5.3.3)':
+ '@typescript-eslint/type-utils@6.21.0(eslint@9.39.1(jiti@2.6.1))(typescript@5.3.3)':
dependencies:
'@typescript-eslint/typescript-estree': 6.21.0(typescript@5.3.3)
- '@typescript-eslint/utils': 6.21.0(eslint@9.39.1(jiti@1.21.7))(typescript@5.3.3)
+ '@typescript-eslint/utils': 6.21.0(eslint@9.39.1(jiti@2.6.1))(typescript@5.3.3)
debug: 4.4.3
- eslint: 9.39.1(jiti@1.21.7)
+ eslint: 9.39.1(jiti@2.6.1)
ts-api-utils: 1.4.3(typescript@5.3.3)
optionalDependencies:
typescript: 5.3.3
@@ -27534,12 +27576,12 @@ snapshots:
transitivePeerDependencies:
- supports-color
- '@typescript-eslint/type-utils@7.18.0(eslint@9.39.1(jiti@1.21.7))(typescript@5.3.3)':
+ '@typescript-eslint/type-utils@7.18.0(eslint@9.39.1(jiti@2.6.1))(typescript@5.3.3)':
dependencies:
'@typescript-eslint/typescript-estree': 7.18.0(typescript@5.3.3)
- '@typescript-eslint/utils': 7.18.0(eslint@9.39.1(jiti@1.21.7))(typescript@5.3.3)
+ '@typescript-eslint/utils': 7.18.0(eslint@9.39.1(jiti@2.6.1))(typescript@5.3.3)
debug: 4.4.3
- eslint: 9.39.1(jiti@1.21.7)
+ eslint: 9.39.1(jiti@2.6.1)
ts-api-utils: 1.4.3(typescript@5.3.3)
optionalDependencies:
typescript: 5.3.3
@@ -27721,15 +27763,15 @@ snapshots:
- supports-color
- typescript
- '@typescript-eslint/utils@6.21.0(eslint@9.39.1(jiti@1.21.7))(typescript@5.3.3)':
+ '@typescript-eslint/utils@6.21.0(eslint@9.39.1(jiti@2.6.1))(typescript@5.3.3)':
dependencies:
- '@eslint-community/eslint-utils': 4.9.0(eslint@9.39.1(jiti@1.21.7))
+ '@eslint-community/eslint-utils': 4.9.0(eslint@9.39.1(jiti@2.6.1))
'@types/json-schema': 7.0.15
'@types/semver': 7.7.1
'@typescript-eslint/scope-manager': 6.21.0
'@typescript-eslint/types': 6.21.0
'@typescript-eslint/typescript-estree': 6.21.0(typescript@5.3.3)
- eslint: 9.39.1(jiti@1.21.7)
+ eslint: 9.39.1(jiti@2.6.1)
semver: 7.7.3
transitivePeerDependencies:
- supports-color
@@ -27760,13 +27802,13 @@ snapshots:
- supports-color
- typescript
- '@typescript-eslint/utils@7.18.0(eslint@9.39.1(jiti@1.21.7))(typescript@5.3.3)':
+ '@typescript-eslint/utils@7.18.0(eslint@9.39.1(jiti@2.6.1))(typescript@5.3.3)':
dependencies:
- '@eslint-community/eslint-utils': 4.9.0(eslint@9.39.1(jiti@1.21.7))
+ '@eslint-community/eslint-utils': 4.9.0(eslint@9.39.1(jiti@2.6.1))
'@typescript-eslint/scope-manager': 7.18.0
'@typescript-eslint/types': 7.18.0
'@typescript-eslint/typescript-estree': 7.18.0(typescript@5.3.3)
- eslint: 9.39.1(jiti@1.21.7)
+ eslint: 9.39.1(jiti@2.6.1)
transitivePeerDependencies:
- supports-color
- typescript
@@ -28567,6 +28609,108 @@ snapshots:
transitivePeerDependencies:
- supports-color
+ astro@5.16.0(@netlify/blobs@10.4.1)(@types/node@20.19.25)(ioredis@5.8.2)(jiti@1.21.7)(lightningcss@1.30.2)(rollup@4.53.3)(terser@5.44.1)(tsx@4.20.6)(typescript@5.9.3)(yaml@2.8.1):
+ dependencies:
+ '@astrojs/compiler': 2.13.0
+ '@astrojs/internal-helpers': 0.7.5
+ '@astrojs/markdown-remark': 6.3.9
+ '@astrojs/telemetry': 3.3.0
+ '@capsizecss/unpack': 3.0.1
+ '@oslojs/encoding': 1.1.0
+ '@rollup/pluginutils': 5.3.0(rollup@4.53.3)
+ acorn: 8.15.0
+ aria-query: 5.3.2
+ axobject-query: 4.1.0
+ boxen: 8.0.1
+ ci-info: 4.3.1
+ clsx: 2.1.1
+ common-ancestor-path: 1.0.1
+ cookie: 1.1.0
+ cssesc: 3.0.0
+ debug: 4.4.3
+ deterministic-object-hash: 2.0.2
+ devalue: 5.5.0
+ diff: 5.2.0
+ dlv: 1.1.3
+ dset: 3.1.4
+ es-module-lexer: 1.7.0
+ esbuild: 0.25.12
+ estree-walker: 3.0.3
+ flattie: 1.1.1
+ fontace: 0.3.1
+ github-slugger: 2.0.0
+ html-escaper: 3.0.3
+ http-cache-semantics: 4.2.0
+ import-meta-resolve: 4.2.0
+ js-yaml: 4.1.1
+ magic-string: 0.30.21
+ magicast: 0.5.1
+ mrmime: 2.0.1
+ neotraverse: 0.6.18
+ p-limit: 6.2.0
+ p-queue: 8.1.1
+ package-manager-detector: 1.5.0
+ piccolore: 0.1.3
+ picomatch: 4.0.3
+ prompts: 2.4.2
+ rehype: 13.0.2
+ semver: 7.7.3
+ shiki: 3.15.0
+ smol-toml: 1.5.2
+ svgo: 4.0.0
+ tinyexec: 1.0.2
+ tinyglobby: 0.2.15
+ tsconfck: 3.1.6(typescript@5.9.3)
+ ultrahtml: 1.6.0
+ unifont: 0.6.0
+ unist-util-visit: 5.0.0
+ unstorage: 1.17.3(@netlify/blobs@10.4.1)(ioredis@5.8.2)
+ vfile: 6.0.3
+ vite: 6.4.1(@types/node@20.19.25)(jiti@1.21.7)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.20.6)(yaml@2.8.1)
+ vitefu: 1.1.1(vite@6.4.1(@types/node@20.19.25)(jiti@1.21.7)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.20.6)(yaml@2.8.1))
+ xxhash-wasm: 1.1.0
+ yargs-parser: 21.1.1
+ yocto-spinner: 0.2.3
+ zod: 3.25.76
+ zod-to-json-schema: 3.25.0(zod@3.25.76)
+ zod-to-ts: 1.2.0(typescript@5.9.3)(zod@3.25.76)
+ optionalDependencies:
+ sharp: 0.34.5
+ transitivePeerDependencies:
+ - '@azure/app-configuration'
+ - '@azure/cosmos'
+ - '@azure/data-tables'
+ - '@azure/identity'
+ - '@azure/keyvault-secrets'
+ - '@azure/storage-blob'
+ - '@capacitor/preferences'
+ - '@deno/kv'
+ - '@netlify/blobs'
+ - '@planetscale/database'
+ - '@types/node'
+ - '@upstash/redis'
+ - '@vercel/blob'
+ - '@vercel/functions'
+ - '@vercel/kv'
+ - aws4fetch
+ - db0
+ - idb-keyval
+ - ioredis
+ - jiti
+ - less
+ - lightningcss
+ - rollup
+ - sass
+ - sass-embedded
+ - stylus
+ - sugarss
+ - supports-color
+ - terser
+ - tsx
+ - typescript
+ - uploadthing
+ - yaml
+
astro@5.16.0(@netlify/blobs@10.4.1)(@types/node@20.19.25)(ioredis@5.8.2)(jiti@2.6.1)(lightningcss@1.30.2)(rollup@4.53.3)(terser@5.44.1)(tsx@4.20.6)(typescript@5.9.3)(yaml@2.8.1):
dependencies:
'@astrojs/compiler': 2.13.0
@@ -29788,13 +29932,13 @@ snapshots:
- supports-color
- ts-node
- create-jest@29.7.0(@types/node@24.10.1):
+ create-jest@29.7.0(@types/node@24.10.1)(ts-node@10.9.2(@types/node@24.10.1)(typescript@5.9.3)):
dependencies:
'@jest/types': 29.6.3
chalk: 4.1.2
exit: 0.1.2
graceful-fs: 4.2.11
- jest-config: 29.7.0(@types/node@24.10.1)
+ jest-config: 29.7.0(@types/node@24.10.1)(ts-node@10.9.2(@types/node@24.10.1)(typescript@5.9.3))
jest-util: 29.7.0
prompts: 2.4.2
transitivePeerDependencies:
@@ -30852,6 +30996,11 @@ snapshots:
optionalDependencies:
source-map: 0.6.1
+ eslint-compat-utils@0.6.5(eslint@9.39.1(jiti@1.21.7)):
+ dependencies:
+ eslint: 9.39.1(jiti@1.21.7)
+ semver: 7.7.3
+
eslint-compat-utils@0.6.5(eslint@9.39.1(jiti@2.6.1)):
dependencies:
eslint: 9.39.1(jiti@2.6.1)
@@ -30862,9 +31011,9 @@ snapshots:
'@typescript-eslint/eslint-plugin': 8.48.1(@typescript-eslint/parser@8.48.1(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3)
'@typescript-eslint/parser': 8.48.1(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3)
eslint: 9.39.1(jiti@2.6.1)
- eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.32.0)(eslint@9.39.1(jiti@2.6.1))
+ eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.48.1(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.1(jiti@2.6.1)))(eslint@9.39.1(jiti@2.6.1))
eslint-plugin-expo: 1.0.0(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3)
- eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.48.1(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.1(jiti@2.6.1))
+ eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.48.1(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3))(eslint-import-resolver-typescript@3.10.1)(eslint@9.39.1(jiti@2.6.1))
eslint-plugin-react: 7.37.5(eslint@9.39.1(jiti@2.6.1))
eslint-plugin-react-hooks: 5.2.0(eslint@9.39.1(jiti@2.6.1))
globals: 16.5.0
@@ -30879,9 +31028,9 @@ snapshots:
'@typescript-eslint/eslint-plugin': 8.48.1(@typescript-eslint/parser@8.48.1(eslint@9.39.1(jiti@2.6.1))(typescript@5.8.3))(eslint@9.39.1(jiti@2.6.1))(typescript@5.8.3)
'@typescript-eslint/parser': 8.48.1(eslint@9.39.1(jiti@2.6.1))(typescript@5.8.3)
eslint: 9.39.1(jiti@2.6.1)
- eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.32.0)(eslint@9.39.1(jiti@2.6.1))
+ eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.48.1(eslint@9.39.1(jiti@2.6.1))(typescript@5.8.3))(eslint@9.39.1(jiti@2.6.1)))(eslint@9.39.1(jiti@2.6.1))
eslint-plugin-expo: 0.1.4(eslint@9.39.1(jiti@2.6.1))(typescript@5.8.3)
- eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.48.1(eslint@9.39.1(jiti@2.6.1))(typescript@5.8.3))(eslint@9.39.1(jiti@2.6.1))
+ eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.48.1(eslint@9.39.1(jiti@2.6.1))(typescript@5.8.3))(eslint-import-resolver-typescript@3.10.1)(eslint@9.39.1(jiti@2.6.1))
eslint-plugin-react: 7.37.5(eslint@9.39.1(jiti@2.6.1))
eslint-plugin-react-hooks: 5.2.0(eslint@9.39.1(jiti@2.6.1))
globals: 16.5.0
@@ -30899,14 +31048,14 @@ snapshots:
dependencies:
eslint: 8.57.1
- eslint-config-prettier@8.10.2(eslint@9.39.1(jiti@1.21.7)):
- dependencies:
- eslint: 9.39.1(jiti@1.21.7)
-
eslint-config-prettier@8.10.2(eslint@9.39.1(jiti@2.6.1)):
dependencies:
eslint: 9.39.1(jiti@2.6.1)
+ eslint-config-prettier@9.1.2(eslint@9.39.1(jiti@1.21.7)):
+ dependencies:
+ eslint: 9.39.1(jiti@1.21.7)
+
eslint-config-prettier@9.1.2(eslint@9.39.1(jiti@2.6.1)):
dependencies:
eslint: 9.39.1(jiti@2.6.1)
@@ -30931,17 +31080,17 @@ snapshots:
- supports-color
- typescript
- eslint-config-universe@12.1.0(@types/eslint@9.6.1)(eslint@9.39.1(jiti@1.21.7))(prettier@3.6.2)(typescript@5.3.3):
+ eslint-config-universe@12.1.0(@types/eslint@9.6.1)(eslint@9.39.1(jiti@2.6.1))(prettier@3.6.2)(typescript@5.3.3):
dependencies:
- '@typescript-eslint/eslint-plugin': 6.21.0(@typescript-eslint/parser@6.21.0(eslint@9.39.1(jiti@1.21.7))(typescript@5.3.3))(eslint@9.39.1(jiti@1.21.7))(typescript@5.3.3)
- '@typescript-eslint/parser': 6.21.0(eslint@9.39.1(jiti@1.21.7))(typescript@5.3.3)
- eslint: 9.39.1(jiti@1.21.7)
- eslint-config-prettier: 8.10.2(eslint@9.39.1(jiti@1.21.7))
- eslint-plugin-import: 2.32.0(@typescript-eslint/parser@6.21.0(eslint@9.39.1(jiti@1.21.7))(typescript@5.3.3))(eslint@9.39.1(jiti@1.21.7))
- eslint-plugin-node: 11.1.0(eslint@9.39.1(jiti@1.21.7))
- eslint-plugin-prettier: 5.5.4(@types/eslint@9.6.1)(eslint-config-prettier@8.10.2(eslint@9.39.1(jiti@1.21.7)))(eslint@9.39.1(jiti@1.21.7))(prettier@3.6.2)
- eslint-plugin-react: 7.37.5(eslint@9.39.1(jiti@1.21.7))
- eslint-plugin-react-hooks: 4.6.2(eslint@9.39.1(jiti@1.21.7))
+ '@typescript-eslint/eslint-plugin': 6.21.0(@typescript-eslint/parser@6.21.0(eslint@9.39.1(jiti@2.6.1))(typescript@5.3.3))(eslint@9.39.1(jiti@2.6.1))(typescript@5.3.3)
+ '@typescript-eslint/parser': 6.21.0(eslint@9.39.1(jiti@2.6.1))(typescript@5.3.3)
+ eslint: 9.39.1(jiti@2.6.1)
+ eslint-config-prettier: 8.10.2(eslint@9.39.1(jiti@2.6.1))
+ eslint-plugin-import: 2.32.0(@typescript-eslint/parser@6.21.0(eslint@9.39.1(jiti@2.6.1))(typescript@5.3.3))(eslint@9.39.1(jiti@2.6.1))
+ eslint-plugin-node: 11.1.0(eslint@9.39.1(jiti@2.6.1))
+ eslint-plugin-prettier: 5.5.4(@types/eslint@9.6.1)(eslint-config-prettier@8.10.2(eslint@9.39.1(jiti@2.6.1)))(eslint@9.39.1(jiti@2.6.1))(prettier@3.6.2)
+ eslint-plugin-react: 7.37.5(eslint@9.39.1(jiti@2.6.1))
+ eslint-plugin-react-hooks: 4.6.2(eslint@9.39.1(jiti@2.6.1))
optionalDependencies:
prettier: 3.6.2
transitivePeerDependencies:
@@ -30979,7 +31128,7 @@ snapshots:
transitivePeerDependencies:
- supports-color
- eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0)(eslint@9.39.1(jiti@2.6.1)):
+ eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.48.1(eslint@9.39.1(jiti@2.6.1))(typescript@5.8.3))(eslint@9.39.1(jiti@2.6.1)))(eslint@9.39.1(jiti@2.6.1)):
dependencies:
'@nolyfill/is-core-module': 1.0.39
debug: 4.4.3
@@ -30990,7 +31139,22 @@ snapshots:
tinyglobby: 0.2.15
unrs-resolver: 1.11.1
optionalDependencies:
- eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.48.1(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.1(jiti@2.6.1))
+ eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.48.1(eslint@9.39.1(jiti@2.6.1))(typescript@5.8.3))(eslint-import-resolver-typescript@3.10.1)(eslint@9.39.1(jiti@2.6.1))
+ transitivePeerDependencies:
+ - supports-color
+
+ eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.48.1(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.1(jiti@2.6.1)))(eslint@9.39.1(jiti@2.6.1)):
+ dependencies:
+ '@nolyfill/is-core-module': 1.0.39
+ debug: 4.4.3
+ eslint: 9.39.1(jiti@2.6.1)
+ get-tsconfig: 4.13.0
+ is-bun-module: 2.0.0
+ stable-hash: 0.0.5
+ tinyglobby: 0.2.15
+ unrs-resolver: 1.11.1
+ optionalDependencies:
+ eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.48.1(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3))(eslint-import-resolver-typescript@3.10.1)(eslint@9.39.1(jiti@2.6.1))
transitivePeerDependencies:
- supports-color
@@ -31004,12 +31168,12 @@ snapshots:
transitivePeerDependencies:
- supports-color
- eslint-module-utils@2.12.1(@typescript-eslint/parser@6.21.0(eslint@9.39.1(jiti@1.21.7))(typescript@5.3.3))(eslint-import-resolver-node@0.3.9)(eslint@9.39.1(jiti@1.21.7)):
+ eslint-module-utils@2.12.1(@typescript-eslint/parser@6.21.0(eslint@9.39.1(jiti@2.6.1))(typescript@5.3.3))(eslint-import-resolver-node@0.3.9)(eslint@9.39.1(jiti@2.6.1)):
dependencies:
debug: 3.2.7
optionalDependencies:
- '@typescript-eslint/parser': 6.21.0(eslint@9.39.1(jiti@1.21.7))(typescript@5.3.3)
- eslint: 9.39.1(jiti@1.21.7)
+ '@typescript-eslint/parser': 6.21.0(eslint@9.39.1(jiti@2.6.1))(typescript@5.3.3)
+ eslint: 9.39.1(jiti@2.6.1)
eslint-import-resolver-node: 0.3.9
transitivePeerDependencies:
- supports-color
@@ -31024,25 +31188,39 @@ snapshots:
transitivePeerDependencies:
- supports-color
- eslint-module-utils@2.12.1(@typescript-eslint/parser@8.48.1(eslint@9.39.1(jiti@2.6.1))(typescript@5.8.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1)(eslint@9.39.1(jiti@2.6.1)):
+ eslint-module-utils@2.12.1(@typescript-eslint/parser@8.48.1(eslint@9.39.1(jiti@2.6.1))(typescript@5.8.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.48.1(eslint@9.39.1(jiti@2.6.1))(typescript@5.8.3))(eslint@9.39.1(jiti@2.6.1)))(eslint@9.39.1(jiti@2.6.1)))(eslint@9.39.1(jiti@2.6.1)):
dependencies:
debug: 3.2.7
optionalDependencies:
'@typescript-eslint/parser': 8.48.1(eslint@9.39.1(jiti@2.6.1))(typescript@5.8.3)
eslint: 9.39.1(jiti@2.6.1)
eslint-import-resolver-node: 0.3.9
- eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.32.0)(eslint@9.39.1(jiti@2.6.1))
+ eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.48.1(eslint@9.39.1(jiti@2.6.1))(typescript@5.8.3))(eslint@9.39.1(jiti@2.6.1)))(eslint@9.39.1(jiti@2.6.1))
transitivePeerDependencies:
- supports-color
- eslint-module-utils@2.12.1(@typescript-eslint/parser@8.48.1(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1)(eslint@9.39.1(jiti@2.6.1)):
+ eslint-module-utils@2.12.1(@typescript-eslint/parser@8.48.1(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.48.1(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.1(jiti@2.6.1)))(eslint@9.39.1(jiti@2.6.1)))(eslint@9.39.1(jiti@2.6.1)):
dependencies:
debug: 3.2.7
optionalDependencies:
'@typescript-eslint/parser': 8.48.1(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3)
eslint: 9.39.1(jiti@2.6.1)
eslint-import-resolver-node: 0.3.9
- eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.32.0)(eslint@9.39.1(jiti@2.6.1))
+ eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.48.1(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.1(jiti@2.6.1)))(eslint@9.39.1(jiti@2.6.1))
+ transitivePeerDependencies:
+ - supports-color
+
+ eslint-plugin-astro@1.5.0(eslint@9.39.1(jiti@1.21.7)):
+ dependencies:
+ '@eslint-community/eslint-utils': 4.9.0(eslint@9.39.1(jiti@1.21.7))
+ '@jridgewell/sourcemap-codec': 1.5.5
+ '@typescript-eslint/types': 8.48.0
+ astro-eslint-parser: 1.2.2
+ eslint: 9.39.1(jiti@1.21.7)
+ eslint-compat-utils: 0.6.5(eslint@9.39.1(jiti@1.21.7))
+ globals: 16.5.0
+ postcss: 8.5.6
+ postcss-selector-parser: 7.1.0
transitivePeerDependencies:
- supports-color
@@ -31066,12 +31244,6 @@ snapshots:
eslint-utils: 2.1.0
regexpp: 3.2.0
- eslint-plugin-es@3.0.1(eslint@9.39.1(jiti@1.21.7)):
- dependencies:
- eslint: 9.39.1(jiti@1.21.7)
- eslint-utils: 2.1.0
- regexpp: 3.2.0
-
eslint-plugin-es@3.0.1(eslint@9.39.1(jiti@2.6.1)):
dependencies:
eslint: 9.39.1(jiti@2.6.1)
@@ -31125,7 +31297,7 @@ snapshots:
- eslint-import-resolver-webpack
- supports-color
- eslint-plugin-import@2.32.0(@typescript-eslint/parser@6.21.0(eslint@9.39.1(jiti@1.21.7))(typescript@5.3.3))(eslint@9.39.1(jiti@1.21.7)):
+ eslint-plugin-import@2.32.0(@typescript-eslint/parser@6.21.0(eslint@9.39.1(jiti@2.6.1))(typescript@5.3.3))(eslint@9.39.1(jiti@2.6.1)):
dependencies:
'@rtsao/scc': 1.1.0
array-includes: 3.1.9
@@ -31134,9 +31306,9 @@ snapshots:
array.prototype.flatmap: 1.3.3
debug: 3.2.7
doctrine: 2.1.0
- eslint: 9.39.1(jiti@1.21.7)
+ eslint: 9.39.1(jiti@2.6.1)
eslint-import-resolver-node: 0.3.9
- eslint-module-utils: 2.12.1(@typescript-eslint/parser@6.21.0(eslint@9.39.1(jiti@1.21.7))(typescript@5.3.3))(eslint-import-resolver-node@0.3.9)(eslint@9.39.1(jiti@1.21.7))
+ eslint-module-utils: 2.12.1(@typescript-eslint/parser@6.21.0(eslint@9.39.1(jiti@2.6.1))(typescript@5.3.3))(eslint-import-resolver-node@0.3.9)(eslint@9.39.1(jiti@2.6.1))
hasown: 2.0.2
is-core-module: 2.16.1
is-glob: 4.0.3
@@ -31148,7 +31320,7 @@ snapshots:
string.prototype.trimend: 1.0.9
tsconfig-paths: 3.15.0
optionalDependencies:
- '@typescript-eslint/parser': 6.21.0(eslint@9.39.1(jiti@1.21.7))(typescript@5.3.3)
+ '@typescript-eslint/parser': 6.21.0(eslint@9.39.1(jiti@2.6.1))(typescript@5.3.3)
transitivePeerDependencies:
- eslint-import-resolver-typescript
- eslint-import-resolver-webpack
@@ -31183,7 +31355,7 @@ snapshots:
- eslint-import-resolver-webpack
- supports-color
- eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.48.1(eslint@9.39.1(jiti@2.6.1))(typescript@5.8.3))(eslint@9.39.1(jiti@2.6.1)):
+ eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.48.1(eslint@9.39.1(jiti@2.6.1))(typescript@5.8.3))(eslint-import-resolver-typescript@3.10.1)(eslint@9.39.1(jiti@2.6.1)):
dependencies:
'@rtsao/scc': 1.1.0
array-includes: 3.1.9
@@ -31194,7 +31366,7 @@ snapshots:
doctrine: 2.1.0
eslint: 9.39.1(jiti@2.6.1)
eslint-import-resolver-node: 0.3.9
- eslint-module-utils: 2.12.1(@typescript-eslint/parser@8.48.1(eslint@9.39.1(jiti@2.6.1))(typescript@5.8.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1)(eslint@9.39.1(jiti@2.6.1))
+ eslint-module-utils: 2.12.1(@typescript-eslint/parser@8.48.1(eslint@9.39.1(jiti@2.6.1))(typescript@5.8.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.48.1(eslint@9.39.1(jiti@2.6.1))(typescript@5.8.3))(eslint@9.39.1(jiti@2.6.1)))(eslint@9.39.1(jiti@2.6.1)))(eslint@9.39.1(jiti@2.6.1))
hasown: 2.0.2
is-core-module: 2.16.1
is-glob: 4.0.3
@@ -31212,7 +31384,7 @@ snapshots:
- eslint-import-resolver-webpack
- supports-color
- eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.48.1(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.1(jiti@2.6.1)):
+ eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.48.1(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3))(eslint-import-resolver-typescript@3.10.1)(eslint@9.39.1(jiti@2.6.1)):
dependencies:
'@rtsao/scc': 1.1.0
array-includes: 3.1.9
@@ -31223,7 +31395,7 @@ snapshots:
doctrine: 2.1.0
eslint: 9.39.1(jiti@2.6.1)
eslint-import-resolver-node: 0.3.9
- eslint-module-utils: 2.12.1(@typescript-eslint/parser@8.48.1(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1)(eslint@9.39.1(jiti@2.6.1))
+ eslint-module-utils: 2.12.1(@typescript-eslint/parser@8.48.1(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.48.1(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.1(jiti@2.6.1)))(eslint@9.39.1(jiti@2.6.1)))(eslint@9.39.1(jiti@2.6.1))
hasown: 2.0.2
is-core-module: 2.16.1
is-glob: 4.0.3
@@ -31251,16 +31423,6 @@ snapshots:
resolve: 1.22.11
semver: 6.3.1
- eslint-plugin-node@11.1.0(eslint@9.39.1(jiti@1.21.7)):
- dependencies:
- eslint: 9.39.1(jiti@1.21.7)
- eslint-plugin-es: 3.0.1(eslint@9.39.1(jiti@1.21.7))
- eslint-utils: 2.1.0
- ignore: 5.3.2
- minimatch: 3.1.2
- resolve: 1.22.11
- semver: 6.3.1
-
eslint-plugin-node@11.1.0(eslint@9.39.1(jiti@2.6.1)):
dependencies:
eslint: 9.39.1(jiti@2.6.1)
@@ -31291,16 +31453,6 @@ snapshots:
'@types/eslint': 9.6.1
eslint-config-prettier: 8.10.2(eslint@8.57.1)
- eslint-plugin-prettier@5.5.4(@types/eslint@9.6.1)(eslint-config-prettier@8.10.2(eslint@9.39.1(jiti@1.21.7)))(eslint@9.39.1(jiti@1.21.7))(prettier@3.6.2):
- dependencies:
- eslint: 9.39.1(jiti@1.21.7)
- prettier: 3.6.2
- prettier-linter-helpers: 1.0.0
- synckit: 0.11.11
- optionalDependencies:
- '@types/eslint': 9.6.1
- eslint-config-prettier: 8.10.2(eslint@9.39.1(jiti@1.21.7))
-
eslint-plugin-prettier@5.5.4(@types/eslint@9.6.1)(eslint-config-prettier@8.10.2(eslint@9.39.1(jiti@2.6.1)))(eslint@9.39.1(jiti@2.6.1))(prettier@3.6.2):
dependencies:
eslint: 9.39.1(jiti@2.6.1)
@@ -31325,10 +31477,6 @@ snapshots:
dependencies:
eslint: 8.57.1
- eslint-plugin-react-hooks@4.6.2(eslint@9.39.1(jiti@1.21.7)):
- dependencies:
- eslint: 9.39.1(jiti@1.21.7)
-
eslint-plugin-react-hooks@4.6.2(eslint@9.39.1(jiti@2.6.1)):
dependencies:
eslint: 9.39.1(jiti@2.6.1)
@@ -31359,28 +31507,6 @@ snapshots:
string.prototype.matchall: 4.0.12
string.prototype.repeat: 1.0.0
- eslint-plugin-react@7.37.5(eslint@9.39.1(jiti@1.21.7)):
- dependencies:
- array-includes: 3.1.9
- array.prototype.findlast: 1.2.5
- array.prototype.flatmap: 1.3.3
- array.prototype.tosorted: 1.1.4
- doctrine: 2.1.0
- es-iterator-helpers: 1.2.1
- eslint: 9.39.1(jiti@1.21.7)
- estraverse: 5.3.0
- hasown: 2.0.2
- jsx-ast-utils: 3.3.5
- minimatch: 3.1.2
- object.entries: 1.1.9
- object.fromentries: 2.0.8
- object.values: 1.2.1
- prop-types: 15.8.1
- resolve: 2.0.0-next.5
- semver: 6.3.1
- string.prototype.matchall: 4.0.12
- string.prototype.repeat: 1.0.0
-
eslint-plugin-react@7.37.5(eslint@9.39.1(jiti@2.6.1)):
dependencies:
array-includes: 3.1.9
@@ -32500,54 +32626,7 @@ snapshots:
- '@types/react-dom'
- supports-color
- expo-router@6.0.15(jiucxy5ca3jdtbnulaxuc46jdq):
- dependencies:
- '@expo/metro-runtime': 6.1.2(expo@54.0.25)(react-dom@19.1.0(react@19.1.0))(react-native@0.81.4(@babel/core@7.28.5)(@types/react@19.2.7)(react@19.1.0))(react@19.1.0)
- '@expo/schema-utils': 0.1.7
- '@radix-ui/react-slot': 1.2.0(@types/react@19.2.7)(react@19.1.0)
- '@radix-ui/react-tabs': 1.1.13(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
- '@react-navigation/bottom-tabs': 7.8.6(@react-navigation/native@7.1.21(react-native@0.81.4(@babel/core@7.28.5)(@types/react@19.2.7)(react@19.1.0))(react@19.1.0))(react-native-safe-area-context@5.6.2(react-native@0.81.4(@babel/core@7.28.5)(@types/react@19.2.7)(react@19.1.0))(react@19.1.0))(react-native-screens@4.16.0(react-native@0.81.4(@babel/core@7.28.5)(@types/react@19.2.7)(react@19.1.0))(react@19.1.0))(react-native@0.81.4(@babel/core@7.28.5)(@types/react@19.2.7)(react@19.1.0))(react@19.1.0)
- '@react-navigation/native': 7.1.21(react-native@0.81.4(@babel/core@7.28.5)(@types/react@19.2.7)(react@19.1.0))(react@19.1.0)
- '@react-navigation/native-stack': 7.8.0(@react-navigation/native@7.1.21(react-native@0.81.4(@babel/core@7.28.5)(@types/react@19.2.7)(react@19.1.0))(react@19.1.0))(react-native-safe-area-context@5.6.2(react-native@0.81.4(@babel/core@7.28.5)(@types/react@19.2.7)(react@19.1.0))(react@19.1.0))(react-native-screens@4.16.0(react-native@0.81.4(@babel/core@7.28.5)(@types/react@19.2.7)(react@19.1.0))(react@19.1.0))(react-native@0.81.4(@babel/core@7.28.5)(@types/react@19.2.7)(react@19.1.0))(react@19.1.0)
- client-only: 0.0.1
- debug: 4.4.3
- escape-string-regexp: 4.0.0
- expo: 54.0.25(@babel/core@7.28.5)(@expo/metro-runtime@6.1.2)(expo-router@6.0.15)(react-native-webview@13.12.2(react-native@0.81.4(@babel/core@7.28.5)(@types/react@19.2.7)(react@19.1.0))(react@19.1.0))(react-native@0.81.4(@babel/core@7.28.5)(@types/react@19.2.7)(react@19.1.0))(react@19.1.0)
- expo-constants: 18.0.10(expo@54.0.25)(react-native@0.81.4(@babel/core@7.28.5)(@types/react@19.2.7)(react@19.1.0))
- expo-linking: 8.0.9(expo@54.0.25)(react-native@0.81.4(@babel/core@7.28.5)(@types/react@19.2.7)(react@19.1.0))(react@19.1.0)
- expo-server: 1.0.4
- fast-deep-equal: 3.1.3
- invariant: 2.2.4
- nanoid: 3.3.11
- query-string: 7.1.3
- react: 19.1.0
- react-fast-compare: 3.2.2
- react-native: 0.81.4(@babel/core@7.28.5)(@types/react@19.2.7)(react@19.1.0)
- react-native-is-edge-to-edge: 1.2.1(react-native@0.81.4(@babel/core@7.28.5)(@types/react@19.2.7)(react@19.1.0))(react@19.1.0)
- react-native-safe-area-context: 5.6.2(react-native@0.81.4(@babel/core@7.28.5)(@types/react@19.2.7)(react@19.1.0))(react@19.1.0)
- react-native-screens: 4.16.0(react-native@0.81.4(@babel/core@7.28.5)(@types/react@19.2.7)(react@19.1.0))(react@19.1.0)
- semver: 7.6.3
- server-only: 0.0.1
- sf-symbols-typescript: 2.1.0
- shallowequal: 1.1.0
- use-latest-callback: 0.2.6(react@19.1.0)
- vaul: 1.1.2(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
- optionalDependencies:
- '@react-navigation/drawer': 7.7.4(@react-navigation/native@7.1.21(react-native@0.81.4(@babel/core@7.28.5)(@types/react@19.2.7)(react@19.1.0))(react@19.1.0))(react-native-gesture-handler@2.28.0(react-native@0.81.4(@babel/core@7.28.5)(@types/react@19.2.7)(react@19.1.0))(react@19.1.0))(react-native-reanimated@4.1.5(@babel/core@7.28.5)(react-native-worklets@0.6.1(@babel/core@7.28.5)(react-native@0.81.4(@babel/core@7.28.5)(@types/react@19.2.7)(react@19.1.0))(react@19.1.0))(react-native@0.81.4(@babel/core@7.28.5)(@types/react@19.2.7)(react@19.1.0))(react@19.1.0))(react-native-safe-area-context@5.6.2(react-native@0.81.4(@babel/core@7.28.5)(@types/react@19.2.7)(react@19.1.0))(react@19.1.0))(react-native-screens@4.16.0(react-native@0.81.4(@babel/core@7.28.5)(@types/react@19.2.7)(react@19.1.0))(react@19.1.0))(react-native@0.81.4(@babel/core@7.28.5)(@types/react@19.2.7)(react@19.1.0))(react@19.1.0)
- '@testing-library/react-native': 13.3.3(jest@30.2.0(@types/node@20.19.25)(esbuild-register@3.6.0(esbuild@0.27.0)))(react-native@0.81.4(@babel/core@7.28.5)(@types/react@19.2.7)(react@19.1.0))(react-test-renderer@19.1.0(react@19.1.0))(react@19.1.0)
- react-dom: 19.1.0(react@19.1.0)
- react-native-gesture-handler: 2.28.0(react-native@0.81.4(@babel/core@7.28.5)(@types/react@19.2.7)(react@19.1.0))(react@19.1.0)
- react-native-reanimated: 4.1.5(@babel/core@7.28.5)(react-native-worklets@0.6.1(@babel/core@7.28.5)(react-native@0.81.4(@babel/core@7.28.5)(@types/react@19.2.7)(react@19.1.0))(react@19.1.0))(react-native@0.81.4(@babel/core@7.28.5)(@types/react@19.2.7)(react@19.1.0))(react@19.1.0)
- react-native-web: 0.21.2(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
- react-server-dom-webpack: 19.0.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(webpack@5.100.2(esbuild@0.27.0))
- transitivePeerDependencies:
- - '@react-native-masked-view/masked-view'
- - '@types/react'
- - '@types/react-dom'
- - supports-color
- optional: true
-
- expo-router@6.0.15(ohit2up6tuxb3x34brxduivol4):
+ expo-router@6.0.15(nttrd3tw67nnyhowcwgdzipb5e):
dependencies:
'@expo/metro-runtime': 6.1.2(expo@54.0.25)(react-dom@19.1.0(react@19.1.0))(react-native@0.81.5(@babel/core@7.28.5)(@types/react@19.2.7)(react@19.1.0))(react@19.1.0)
'@expo/schema-utils': 0.1.7
@@ -32581,12 +32660,12 @@ snapshots:
vaul: 1.1.2(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
optionalDependencies:
'@react-navigation/drawer': 7.7.4(@react-navigation/native@7.1.21(react-native@0.81.5(@babel/core@7.28.5)(@types/react@19.2.7)(react@19.1.0))(react@19.1.0))(react-native-gesture-handler@2.28.0(react-native@0.81.5(@babel/core@7.28.5)(@types/react@19.2.7)(react@19.1.0))(react@19.1.0))(react-native-reanimated@4.1.5(@babel/core@7.28.5)(react-native-worklets@0.6.1(@babel/core@7.28.5)(react-native@0.81.5(@babel/core@7.28.5)(@types/react@19.2.7)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(@babel/core@7.28.5)(@types/react@19.2.7)(react@19.1.0))(react@19.1.0))(react-native-safe-area-context@5.6.2(react-native@0.81.5(@babel/core@7.28.5)(@types/react@19.2.7)(react@19.1.0))(react@19.1.0))(react-native-screens@4.16.0(react-native@0.81.5(@babel/core@7.28.5)(@types/react@19.2.7)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(@babel/core@7.28.5)(@types/react@19.2.7)(react@19.1.0))(react@19.1.0)
- '@testing-library/react-native': 13.3.3(jest@29.7.0(@types/node@22.19.1)(ts-node@10.9.2(@types/node@22.19.1)(typescript@5.9.3)))(react-native@0.81.5(@babel/core@7.28.5)(@types/react@19.2.7)(react@19.1.0))(react-test-renderer@19.1.0(react@19.1.0))(react@19.1.0)
+ '@testing-library/react-native': 13.3.3(jest@30.2.0(@types/node@22.19.1)(esbuild-register@3.6.0(esbuild@0.19.12))(ts-node@10.9.2(@types/node@22.19.1)(typescript@5.9.3)))(react-native@0.81.5(@babel/core@7.28.5)(@types/react@19.2.7)(react@19.1.0))(react-test-renderer@19.1.0(react@19.1.0))(react@19.1.0)
react-dom: 19.1.0(react@19.1.0)
react-native-gesture-handler: 2.28.0(react-native@0.81.5(@babel/core@7.28.5)(@types/react@19.2.7)(react@19.1.0))(react@19.1.0)
react-native-reanimated: 4.1.5(@babel/core@7.28.5)(react-native-worklets@0.6.1(@babel/core@7.28.5)(react-native@0.81.5(@babel/core@7.28.5)(@types/react@19.2.7)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(@babel/core@7.28.5)(@types/react@19.2.7)(react@19.1.0))(react@19.1.0)
react-native-web: 0.21.2(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
- react-server-dom-webpack: 19.0.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(webpack@5.100.2(esbuild@0.19.12))
+ react-server-dom-webpack: 19.0.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(webpack@5.97.1(esbuild@0.19.12))
transitivePeerDependencies:
- '@react-native-masked-view/masked-view'
- '@types/react'
@@ -33427,6 +33506,23 @@ snapshots:
cross-spawn: 7.0.6
signal-exit: 4.1.0
+ fork-ts-checker-webpack-plugin@9.0.2(typescript@5.7.2)(webpack@5.97.1(esbuild@0.19.12)):
+ dependencies:
+ '@babel/code-frame': 7.27.1
+ chalk: 4.1.2
+ chokidar: 3.6.0
+ cosmiconfig: 8.3.6(typescript@5.7.2)
+ deepmerge: 4.3.1
+ fs-extra: 10.1.0
+ memfs: 3.5.3
+ minimatch: 3.1.2
+ node-abort-controller: 3.1.1
+ schema-utils: 3.3.0
+ semver: 7.7.3
+ tapable: 2.3.0
+ typescript: 5.7.2
+ webpack: 5.97.1(esbuild@0.19.12)
+
fork-ts-checker-webpack-plugin@9.0.2(typescript@5.7.2)(webpack@5.97.1(esbuild@0.27.0)):
dependencies:
'@babel/code-frame': 7.27.1
@@ -33461,23 +33557,6 @@ snapshots:
typescript: 5.7.2
webpack: 5.97.1
- fork-ts-checker-webpack-plugin@9.1.0(typescript@5.9.3)(webpack@5.100.2(esbuild@0.19.12)):
- dependencies:
- '@babel/code-frame': 7.27.1
- chalk: 4.1.2
- chokidar: 4.0.3
- cosmiconfig: 8.3.6(typescript@5.9.3)
- deepmerge: 4.3.1
- fs-extra: 10.1.0
- memfs: 3.5.3
- minimatch: 3.1.2
- node-abort-controller: 3.1.1
- schema-utils: 3.3.0
- semver: 7.7.3
- tapable: 2.3.0
- typescript: 5.9.3
- webpack: 5.100.2(esbuild@0.19.12)
-
fork-ts-checker-webpack-plugin@9.1.0(typescript@5.9.3)(webpack@5.100.2(esbuild@0.27.0)):
dependencies:
'@babel/code-frame': 7.27.1
@@ -33495,6 +33574,23 @@ snapshots:
typescript: 5.9.3
webpack: 5.100.2(esbuild@0.27.0)
+ fork-ts-checker-webpack-plugin@9.1.0(typescript@5.9.3)(webpack@5.100.2):
+ dependencies:
+ '@babel/code-frame': 7.27.1
+ chalk: 4.1.2
+ chokidar: 4.0.3
+ cosmiconfig: 8.3.6(typescript@5.9.3)
+ deepmerge: 4.3.1
+ fs-extra: 10.1.0
+ memfs: 3.5.3
+ minimatch: 3.1.2
+ node-abort-controller: 3.1.1
+ schema-utils: 3.3.0
+ semver: 7.7.3
+ tapable: 2.3.0
+ typescript: 5.9.3
+ webpack: 5.100.2
+
form-data-encoder@1.7.2: {}
form-data@3.0.4:
@@ -34661,16 +34757,16 @@ snapshots:
- supports-color
- ts-node
- jest-cli@29.7.0(@types/node@24.10.1):
+ jest-cli@29.7.0(@types/node@24.10.1)(ts-node@10.9.2(@types/node@24.10.1)(typescript@5.9.3)):
dependencies:
- '@jest/core': 29.7.0
+ '@jest/core': 29.7.0(ts-node@10.9.2(@types/node@24.10.1)(typescript@5.9.3))
'@jest/test-result': 29.7.0
'@jest/types': 29.6.3
chalk: 4.1.2
- create-jest: 29.7.0(@types/node@24.10.1)
+ create-jest: 29.7.0(@types/node@24.10.1)(ts-node@10.9.2(@types/node@24.10.1)(typescript@5.9.3))
exit: 0.1.2
import-local: 3.2.0
- jest-config: 29.7.0(@types/node@24.10.1)
+ jest-config: 29.7.0(@types/node@24.10.1)(ts-node@10.9.2(@types/node@24.10.1)(typescript@5.9.3))
jest-util: 29.7.0
jest-validate: 29.7.0
yargs: 17.7.2
@@ -34680,15 +34776,15 @@ snapshots:
- supports-color
- ts-node
- jest-cli@30.2.0(@types/node@20.19.25)(esbuild-register@3.6.0(esbuild@0.27.0)):
+ jest-cli@30.2.0(@types/node@22.19.1)(esbuild-register@3.6.0(esbuild@0.19.12))(ts-node@10.9.2(@types/node@22.19.1)(typescript@5.9.3)):
dependencies:
- '@jest/core': 30.2.0(esbuild-register@3.6.0(esbuild@0.27.0))
+ '@jest/core': 30.2.0(esbuild-register@3.6.0(esbuild@0.19.12))(ts-node@10.9.2(@types/node@22.19.1)(typescript@5.9.3))
'@jest/test-result': 30.2.0
'@jest/types': 30.2.0
chalk: 4.1.2
exit-x: 0.2.2
import-local: 3.2.0
- jest-config: 30.2.0(@types/node@20.19.25)(esbuild-register@3.6.0(esbuild@0.27.0))
+ jest-config: 30.2.0(@types/node@22.19.1)(esbuild-register@3.6.0(esbuild@0.19.12))(ts-node@10.9.2(@types/node@22.19.1)(typescript@5.9.3))
jest-util: 30.2.0
jest-validate: 30.2.0
yargs: 17.7.2
@@ -34758,36 +34854,6 @@ snapshots:
- ts-node
optional: true
- jest-config@29.7.0(@types/node@22.19.1):
- dependencies:
- '@babel/core': 7.28.5
- '@jest/test-sequencer': 29.7.0
- '@jest/types': 29.6.3
- babel-jest: 29.7.0(@babel/core@7.28.5)
- chalk: 4.1.2
- ci-info: 3.9.0
- deepmerge: 4.3.1
- glob: 7.2.3
- graceful-fs: 4.2.11
- jest-circus: 29.7.0
- jest-environment-node: 29.7.0
- jest-get-type: 29.6.3
- jest-regex-util: 29.6.3
- jest-resolve: 29.7.0
- jest-runner: 29.7.0
- jest-util: 29.7.0
- jest-validate: 29.7.0
- micromatch: 4.0.8
- parse-json: 5.2.0
- pretty-format: 29.7.0
- slash: 3.0.0
- strip-json-comments: 3.1.1
- optionalDependencies:
- '@types/node': 22.19.1
- transitivePeerDependencies:
- - babel-plugin-macros
- - supports-color
-
jest-config@29.7.0(@types/node@22.19.1)(ts-node@10.9.2(@types/node@22.19.1)(typescript@5.9.3)):
dependencies:
'@babel/core': 7.28.5
@@ -34819,7 +34885,38 @@ snapshots:
- babel-plugin-macros
- supports-color
- jest-config@29.7.0(@types/node@24.10.1):
+ jest-config@29.7.0(@types/node@22.19.1)(ts-node@10.9.2(@types/node@24.10.1)(typescript@5.9.3)):
+ dependencies:
+ '@babel/core': 7.28.5
+ '@jest/test-sequencer': 29.7.0
+ '@jest/types': 29.6.3
+ babel-jest: 29.7.0(@babel/core@7.28.5)
+ chalk: 4.1.2
+ ci-info: 3.9.0
+ deepmerge: 4.3.1
+ glob: 7.2.3
+ graceful-fs: 4.2.11
+ jest-circus: 29.7.0
+ jest-environment-node: 29.7.0
+ jest-get-type: 29.6.3
+ jest-regex-util: 29.6.3
+ jest-resolve: 29.7.0
+ jest-runner: 29.7.0
+ jest-util: 29.7.0
+ jest-validate: 29.7.0
+ micromatch: 4.0.8
+ parse-json: 5.2.0
+ pretty-format: 29.7.0
+ slash: 3.0.0
+ strip-json-comments: 3.1.1
+ optionalDependencies:
+ '@types/node': 22.19.1
+ ts-node: 10.9.2(@types/node@24.10.1)(typescript@5.9.3)
+ transitivePeerDependencies:
+ - babel-plugin-macros
+ - supports-color
+
+ jest-config@29.7.0(@types/node@24.10.1)(ts-node@10.9.2(@types/node@24.10.1)(typescript@5.9.3)):
dependencies:
'@babel/core': 7.28.5
'@jest/test-sequencer': 29.7.0
@@ -34845,11 +34942,12 @@ snapshots:
strip-json-comments: 3.1.1
optionalDependencies:
'@types/node': 24.10.1
+ ts-node: 10.9.2(@types/node@24.10.1)(typescript@5.9.3)
transitivePeerDependencies:
- babel-plugin-macros
- supports-color
- jest-config@30.2.0(@types/node@20.19.25)(esbuild-register@3.6.0(esbuild@0.27.0)):
+ jest-config@30.2.0(@types/node@22.19.1)(esbuild-register@3.6.0(esbuild@0.19.12))(ts-node@10.9.2(@types/node@22.19.1)(typescript@5.9.3)):
dependencies:
'@babel/core': 7.28.5
'@jest/get-type': 30.1.0
@@ -34876,8 +34974,9 @@ snapshots:
slash: 3.0.0
strip-json-comments: 3.1.1
optionalDependencies:
- '@types/node': 20.19.25
- esbuild-register: 3.6.0(esbuild@0.27.0)
+ '@types/node': 22.19.1
+ esbuild-register: 3.6.0(esbuild@0.19.12)
+ ts-node: 10.9.2(@types/node@22.19.1)(typescript@5.9.3)
transitivePeerDependencies:
- babel-plugin-macros
- supports-color
@@ -35526,24 +35625,24 @@ snapshots:
- supports-color
- ts-node
- jest@29.7.0(@types/node@24.10.1):
+ jest@29.7.0(@types/node@24.10.1)(ts-node@10.9.2(@types/node@24.10.1)(typescript@5.9.3)):
dependencies:
- '@jest/core': 29.7.0
+ '@jest/core': 29.7.0(ts-node@10.9.2(@types/node@24.10.1)(typescript@5.9.3))
'@jest/types': 29.6.3
import-local: 3.2.0
- jest-cli: 29.7.0(@types/node@24.10.1)
+ jest-cli: 29.7.0(@types/node@24.10.1)(ts-node@10.9.2(@types/node@24.10.1)(typescript@5.9.3))
transitivePeerDependencies:
- '@types/node'
- babel-plugin-macros
- supports-color
- ts-node
- jest@30.2.0(@types/node@20.19.25)(esbuild-register@3.6.0(esbuild@0.27.0)):
+ jest@30.2.0(@types/node@22.19.1)(esbuild-register@3.6.0(esbuild@0.19.12))(ts-node@10.9.2(@types/node@22.19.1)(typescript@5.9.3)):
dependencies:
- '@jest/core': 30.2.0(esbuild-register@3.6.0(esbuild@0.27.0))
+ '@jest/core': 30.2.0(esbuild-register@3.6.0(esbuild@0.19.12))(ts-node@10.9.2(@types/node@22.19.1)(typescript@5.9.3))
'@jest/types': 30.2.0
import-local: 3.2.0
- jest-cli: 30.2.0(@types/node@20.19.25)(esbuild-register@3.6.0(esbuild@0.27.0))
+ jest-cli: 30.2.0(@types/node@22.19.1)(esbuild-register@3.6.0(esbuild@0.19.12))(ts-node@10.9.2(@types/node@22.19.1)(typescript@5.9.3))
transitivePeerDependencies:
- '@types/node'
- babel-plugin-macros
@@ -39310,16 +39409,6 @@ snapshots:
optionalDependencies:
'@types/react': 19.2.7
- react-server-dom-webpack@19.0.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(webpack@5.100.2(esbuild@0.19.12)):
- dependencies:
- acorn-loose: 8.5.2
- neo-async: 2.6.2
- react: 19.1.0
- react-dom: 19.1.0(react@19.1.0)
- webpack: 5.100.2(esbuild@0.19.12)
- webpack-sources: 3.3.3
- optional: true
-
react-server-dom-webpack@19.0.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(webpack@5.100.2(esbuild@0.27.0)):
dependencies:
acorn-loose: 8.5.2
@@ -39329,6 +39418,16 @@ snapshots:
webpack: 5.100.2(esbuild@0.27.0)
webpack-sources: 3.3.3
+ react-server-dom-webpack@19.0.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(webpack@5.97.1(esbuild@0.19.12)):
+ dependencies:
+ acorn-loose: 8.5.2
+ neo-async: 2.6.2
+ react: 19.1.0
+ react-dom: 19.1.0(react@19.1.0)
+ webpack: 5.97.1(esbuild@0.19.12)
+ webpack-sources: 3.3.3
+ optional: true
+
react-style-singleton@2.2.3(@types/react@19.2.7)(react@19.1.0):
dependencies:
get-nonce: 1.0.1
@@ -40580,14 +40679,14 @@ snapshots:
ansi-escapes: 4.3.2
supports-hyperlinks: 2.3.0
- terser-webpack-plugin@5.3.14(esbuild@0.19.12)(webpack@5.100.2(esbuild@0.19.12)):
+ terser-webpack-plugin@5.3.14(esbuild@0.19.12)(webpack@5.97.1(esbuild@0.19.12)):
dependencies:
'@jridgewell/trace-mapping': 0.3.31
jest-worker: 27.5.1
schema-utils: 4.3.3
serialize-javascript: 6.0.2
terser: 5.44.1
- webpack: 5.100.2(esbuild@0.19.12)
+ webpack: 5.97.1(esbuild@0.19.12)
optionalDependencies:
esbuild: 0.19.12
@@ -40613,6 +40712,15 @@ snapshots:
optionalDependencies:
esbuild: 0.27.0
+ terser-webpack-plugin@5.3.14(webpack@5.100.2):
+ dependencies:
+ '@jridgewell/trace-mapping': 0.3.31
+ jest-worker: 27.5.1
+ schema-utils: 4.3.3
+ serialize-javascript: 6.0.2
+ terser: 5.44.1
+ webpack: 5.100.2
+
terser-webpack-plugin@5.3.14(webpack@5.97.1):
dependencies:
'@jridgewell/trace-mapping': 0.3.31
@@ -40774,27 +40882,6 @@ snapshots:
ts-interface-checker@0.1.13: {}
- ts-jest@29.4.5(@babel/core@7.28.5)(@jest/transform@30.2.0)(@jest/types@30.2.0)(babel-jest@30.2.0(@babel/core@7.28.5))(esbuild@0.19.12)(jest-util@30.2.0)(jest@29.7.0(@types/node@22.19.1)(ts-node@10.9.2(@types/node@22.19.1)(typescript@5.9.3)))(typescript@5.9.3):
- dependencies:
- bs-logger: 0.2.6
- fast-json-stable-stringify: 2.1.0
- handlebars: 4.7.8
- jest: 29.7.0(@types/node@22.19.1)(ts-node@10.9.2(@types/node@22.19.1)(typescript@5.9.3))
- json5: 2.2.3
- lodash.memoize: 4.1.2
- make-error: 1.3.6
- semver: 7.7.3
- type-fest: 4.41.0
- typescript: 5.9.3
- yargs-parser: 21.1.1
- optionalDependencies:
- '@babel/core': 7.28.5
- '@jest/transform': 30.2.0
- '@jest/types': 30.2.0
- babel-jest: 30.2.0(@babel/core@7.28.5)
- esbuild: 0.19.12
- jest-util: 30.2.0
-
ts-jest@29.4.5(@babel/core@7.28.5)(@jest/transform@30.2.0)(@jest/types@30.2.0)(babel-jest@30.2.0(@babel/core@7.28.5))(esbuild@0.27.0)(jest-util@30.2.0)(jest@30.2.0(@types/node@22.19.1)(esbuild-register@3.6.0(esbuild@0.27.0))(ts-node@10.9.2(@types/node@22.19.1)(typescript@5.9.3)))(typescript@5.9.3):
dependencies:
bs-logger: 0.2.6
@@ -40816,15 +40903,25 @@ snapshots:
esbuild: 0.27.0
jest-util: 30.2.0
- ts-loader@9.5.4(typescript@5.9.3)(webpack@5.100.2(esbuild@0.19.12)):
+ ts-jest@29.4.5(@babel/core@7.28.5)(@jest/transform@30.2.0)(@jest/types@30.2.0)(babel-jest@30.2.0(@babel/core@7.28.5))(jest-util@30.2.0)(jest@29.7.0(@types/node@22.19.1)(ts-node@10.9.2(@types/node@22.19.1)(typescript@5.9.3)))(typescript@5.9.3):
dependencies:
- chalk: 4.1.2
- enhanced-resolve: 5.18.3
- micromatch: 4.0.8
+ bs-logger: 0.2.6
+ fast-json-stable-stringify: 2.1.0
+ handlebars: 4.7.8
+ jest: 29.7.0(@types/node@22.19.1)(ts-node@10.9.2(@types/node@22.19.1)(typescript@5.9.3))
+ json5: 2.2.3
+ lodash.memoize: 4.1.2
+ make-error: 1.3.6
semver: 7.7.3
- source-map: 0.7.6
+ type-fest: 4.41.0
typescript: 5.9.3
- webpack: 5.100.2(esbuild@0.19.12)
+ yargs-parser: 21.1.1
+ optionalDependencies:
+ '@babel/core': 7.28.5
+ '@jest/transform': 30.2.0
+ '@jest/types': 30.2.0
+ babel-jest: 30.2.0(@babel/core@7.28.5)
+ jest-util: 30.2.0
ts-loader@9.5.4(typescript@5.9.3)(webpack@5.100.2(esbuild@0.27.0)):
dependencies:
@@ -40836,6 +40933,26 @@ snapshots:
typescript: 5.9.3
webpack: 5.100.2(esbuild@0.27.0)
+ ts-loader@9.5.4(typescript@5.9.3)(webpack@5.100.2):
+ dependencies:
+ chalk: 4.1.2
+ enhanced-resolve: 5.18.3
+ micromatch: 4.0.8
+ semver: 7.7.3
+ source-map: 0.7.6
+ typescript: 5.9.3
+ webpack: 5.100.2
+
+ ts-loader@9.5.4(typescript@5.9.3)(webpack@5.97.1(esbuild@0.19.12)):
+ dependencies:
+ chalk: 4.1.2
+ enhanced-resolve: 5.18.3
+ micromatch: 4.0.8
+ semver: 7.7.3
+ source-map: 0.7.6
+ typescript: 5.9.3
+ webpack: 5.97.1(esbuild@0.19.12)
+
ts-node@10.9.2(@types/node@20.19.25)(typescript@5.9.3):
dependencies:
'@cspotcode/source-map-support': 0.8.1
@@ -41415,6 +41532,23 @@ snapshots:
lightningcss: 1.30.2
terser: 5.44.1
+ vite@6.4.1(@types/node@20.19.25)(jiti@1.21.7)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.20.6)(yaml@2.8.1):
+ dependencies:
+ esbuild: 0.25.12
+ fdir: 6.5.0(picomatch@4.0.3)
+ picomatch: 4.0.3
+ postcss: 8.5.6
+ rollup: 4.53.3
+ tinyglobby: 0.2.15
+ optionalDependencies:
+ '@types/node': 20.19.25
+ fsevents: 2.3.3
+ jiti: 1.21.7
+ lightningcss: 1.30.2
+ terser: 5.44.1
+ tsx: 4.20.6
+ yaml: 2.8.1
+
vite@6.4.1(@types/node@20.19.25)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.20.6)(yaml@2.8.1):
dependencies:
esbuild: 0.25.12
@@ -41517,6 +41651,10 @@ snapshots:
tsx: 4.20.6
yaml: 2.8.1
+ vitefu@1.1.1(vite@6.4.1(@types/node@20.19.25)(jiti@1.21.7)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.20.6)(yaml@2.8.1)):
+ optionalDependencies:
+ vite: 6.4.1(@types/node@20.19.25)(jiti@1.21.7)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.20.6)(yaml@2.8.1)
+
vitefu@1.1.1(vite@6.4.1(@types/node@20.19.25)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.20.6)(yaml@2.8.1)):
optionalDependencies:
vite: 6.4.1(@types/node@20.19.25)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.20.6)(yaml@2.8.1)
@@ -41773,7 +41911,7 @@ snapshots:
webpack-sources@3.3.3: {}
- webpack@5.100.2(esbuild@0.19.12):
+ webpack@5.100.2:
dependencies:
'@types/eslint-scope': 3.7.7
'@types/estree': 1.0.8
@@ -41797,7 +41935,7 @@ snapshots:
neo-async: 2.6.2
schema-utils: 4.3.3
tapable: 2.3.0
- terser-webpack-plugin: 5.3.14(esbuild@0.19.12)(webpack@5.100.2(esbuild@0.19.12))
+ terser-webpack-plugin: 5.3.14(webpack@5.100.2)
watchpack: 2.4.4
webpack-sources: 3.3.3
transitivePeerDependencies:
@@ -41867,6 +42005,36 @@ snapshots:
- esbuild
- uglify-js
+ webpack@5.97.1(esbuild@0.19.12):
+ dependencies:
+ '@types/eslint-scope': 3.7.7
+ '@types/estree': 1.0.8
+ '@webassemblyjs/ast': 1.14.1
+ '@webassemblyjs/wasm-edit': 1.14.1
+ '@webassemblyjs/wasm-parser': 1.14.1
+ acorn: 8.15.0
+ browserslist: 4.28.0
+ chrome-trace-event: 1.0.4
+ enhanced-resolve: 5.18.3
+ es-module-lexer: 1.7.0
+ eslint-scope: 5.1.1
+ events: 3.3.0
+ glob-to-regexp: 0.4.1
+ graceful-fs: 4.2.11
+ json-parse-even-better-errors: 2.3.1
+ loader-runner: 4.3.1
+ mime-types: 2.1.35
+ neo-async: 2.6.2
+ schema-utils: 3.3.0
+ tapable: 2.3.0
+ terser-webpack-plugin: 5.3.14(esbuild@0.19.12)(webpack@5.97.1(esbuild@0.19.12))
+ watchpack: 2.4.4
+ webpack-sources: 3.3.3
+ transitivePeerDependencies:
+ - '@swc/core'
+ - esbuild
+ - uglify-js
+
webpack@5.97.1(esbuild@0.27.0):
dependencies:
'@types/eslint-scope': 3.7.7
diff --git a/scripts/setup-databases.sh b/scripts/setup-databases.sh
new file mode 100755
index 000000000..9fbb75809
--- /dev/null
+++ b/scripts/setup-databases.sh
@@ -0,0 +1,169 @@
+#!/bin/bash
+
+# Setup script for creating databases and pushing schemas
+# Usage: ./scripts/setup-databases.sh [service]
+# Examples:
+# ./scripts/setup-databases.sh # Setup all
+# ./scripts/setup-databases.sh chat # Setup only chat
+# ./scripts/setup-databases.sh auth # Setup only auth
+
+set -e
+
+# Database connection details (from .env.development)
+DB_HOST="${DB_HOST:-localhost}"
+DB_PORT="${DB_PORT:-5432}"
+DB_USER="${POSTGRES_USER:-manacore}"
+DB_PASSWORD="${POSTGRES_PASSWORD:-devpassword}"
+
+# Colors for output
+GREEN='\033[0;32m'
+YELLOW='\033[1;33m'
+RED='\033[0;31m'
+NC='\033[0m' # No Color
+
+echo -e "${GREEN}🗄️ Database Setup Script${NC}"
+echo "======================================"
+
+# Function to create database if it doesn't exist
+create_db_if_not_exists() {
+ local db_name=$1
+ echo -e "${YELLOW}Checking database: ${db_name}${NC}"
+
+ if PGPASSWORD=$DB_PASSWORD psql -h $DB_HOST -p $DB_PORT -U $DB_USER -d postgres -tc \
+ "SELECT 1 FROM pg_database WHERE datname = '$db_name'" | grep -q 1; then
+ echo -e " ${GREEN}✓ Exists${NC}"
+ else
+ echo -e " Creating database ${db_name}..."
+ PGPASSWORD=$DB_PASSWORD psql -h $DB_HOST -p $DB_PORT -U $DB_USER -d postgres -c "CREATE DATABASE $db_name;" > /dev/null
+ PGPASSWORD=$DB_PASSWORD psql -h $DB_HOST -p $DB_PORT -U $DB_USER -d postgres -c "GRANT ALL PRIVILEGES ON DATABASE $db_name TO $DB_USER;" > /dev/null
+ echo -e " ${GREEN}✓ Created${NC}"
+ fi
+}
+
+# Function to push schema for a service
+push_schema() {
+ local filter=$1
+ local name=$2
+ echo -e "${YELLOW}Pushing schema for ${name}...${NC}"
+ # Use --force to auto-approve in development (skips interactive prompts)
+ if pnpm --filter "$filter" db:push --force 2>/dev/null; then
+ echo -e " ${GREEN}✓ Schema pushed${NC}"
+ else
+ echo -e " ${RED}✗ Failed (may not have db:push script)${NC}"
+ fi
+}
+
+# All databases that should exist
+ALL_DATABASES=(
+ "manacore"
+ "chat"
+ "zitare"
+ "contacts"
+ "calendar"
+ "clock"
+ "todo"
+ "manadeck"
+ "storage"
+ "mail"
+ "moodlit"
+ "finance"
+ "inventory"
+ "techbase"
+ "voxel_lava"
+ "figgos"
+)
+
+# Check if specific service requested
+SERVICE_FILTER=${1:-""}
+
+setup_service() {
+ local service=$1
+
+ case $service in
+ auth|mana-core-auth)
+ create_db_if_not_exists "manacore"
+ push_schema "mana-core-auth" "mana-core-auth"
+ ;;
+ chat)
+ create_db_if_not_exists "chat"
+ push_schema "@chat/backend" "chat"
+ ;;
+ zitare)
+ create_db_if_not_exists "zitare"
+ push_schema "@zitare/backend" "zitare"
+ ;;
+ contacts)
+ create_db_if_not_exists "contacts"
+ push_schema "@contacts/backend" "contacts"
+ ;;
+ calendar)
+ create_db_if_not_exists "calendar"
+ push_schema "@calendar/backend" "calendar"
+ ;;
+ clock)
+ create_db_if_not_exists "clock"
+ push_schema "@clock/backend" "clock"
+ ;;
+ todo)
+ create_db_if_not_exists "todo"
+ push_schema "@todo/backend" "todo"
+ ;;
+ manadeck)
+ create_db_if_not_exists "manadeck"
+ push_schema "@manadeck/backend" "manadeck"
+ ;;
+ mail)
+ create_db_if_not_exists "mail"
+ push_schema "@mail/backend" "mail"
+ ;;
+ moodlit)
+ create_db_if_not_exists "moodlit"
+ push_schema "@moodlit/backend" "moodlit"
+ ;;
+ picture)
+ create_db_if_not_exists "picture"
+ push_schema "@picture/backend" "picture"
+ ;;
+ finance)
+ create_db_if_not_exists "finance"
+ push_schema "@finance/backend" "finance"
+ ;;
+ voxel-lava)
+ create_db_if_not_exists "voxel_lava"
+ push_schema "@voxel-lava/backend" "voxel-lava"
+ ;;
+ figgos)
+ create_db_if_not_exists "figgos"
+ push_schema "@figgos/backend" "figgos"
+ ;;
+ *)
+ echo -e "${RED}Unknown service: $service${NC}"
+ echo "Available services: auth, chat, zitare, contacts, calendar, clock, todo, manadeck, mail, moodlit, finance, voxel-lava, figgos"
+ exit 1
+ ;;
+ esac
+}
+
+if [ -n "$SERVICE_FILTER" ]; then
+ echo -e "Setting up for service: ${SERVICE_FILTER}"
+ setup_service "$SERVICE_FILTER"
+ echo -e "\n${GREEN}✓ Setup complete!${NC}"
+ exit 0
+fi
+
+# Setup all databases
+echo -e "\n${GREEN}Step 1: Creating databases${NC}"
+echo "--------------------------------------"
+for db in "${ALL_DATABASES[@]}"; do
+ create_db_if_not_exists "$db"
+done
+
+echo -e "\n${GREEN}Step 2: Pushing schemas${NC}"
+echo "--------------------------------------"
+
+# Push schemas for all known services
+for service in auth chat zitare contacts calendar clock todo manadeck picture mail moodlit finance voxel-lava figgos; do
+ setup_service "$service" 2>/dev/null || true
+done
+
+echo -e "\n${GREEN}✓ Database setup complete!${NC}"
diff --git a/services/mana-core-auth/CLAUDE.md b/services/mana-core-auth/CLAUDE.md
index fff9c7d9a..d7d321613 100644
--- a/services/mana-core-auth/CLAUDE.md
+++ b/services/mana-core-auth/CLAUDE.md
@@ -91,7 +91,9 @@ services/mana-core-auth/
│ ├── credits/ # Credit system
│ ├── db/
│ │ ├── schema/ # Drizzle schemas
-│ │ └── connection.ts # DB connection
+│ │ ├── migrations/ # Generated migration files
+│ │ ├── connection.ts # DB connection
+│ │ └── migrate.ts # Migration script with advisory locks
│ └── config/
│ └── configuration.ts # App config
├── docs/
@@ -99,6 +101,16 @@ services/mana-core-auth/
└── test/
```
+## Database Migrations
+
+For comprehensive migration documentation, see **[docs/DATABASE_MIGRATIONS.md](/docs/DATABASE_MIGRATIONS.md)**.
+
+Key points:
+- Use `db:push` for development (fast iteration)
+- Use `db:generate` + `db:migrate` for production (tracked migrations)
+- Migrations use advisory locks to prevent concurrent execution
+- CI/CD runs migrations automatically before code deployment
+
## Key Files
| File | Purpose |
diff --git a/services/mana-core-auth/package.json b/services/mana-core-auth/package.json
index ae517f1d9..1eb6fb0fc 100644
--- a/services/mana-core-auth/package.json
+++ b/services/mana-core-auth/package.json
@@ -16,6 +16,8 @@
"test:cov": "jest --coverage",
"test:e2e": "jest --config ./test/jest-e2e.json",
"db:push": "drizzle-kit push",
+ "db:generate": "drizzle-kit generate",
+ "db:migrate": "tsx src/db/migrate.ts",
"db:studio": "drizzle-kit studio"
},
"dependencies": {
diff --git a/services/mana-core-auth/src/auth/auth.controller.ts b/services/mana-core-auth/src/auth/auth.controller.ts
index 980ff9301..5ad646960 100644
--- a/services/mana-core-auth/src/auth/auth.controller.ts
+++ b/services/mana-core-auth/src/auth/auth.controller.ts
@@ -18,6 +18,8 @@ import { RegisterB2BDto } from './dto/register-b2b.dto';
import { InviteEmployeeDto } from './dto/invite-employee.dto';
import { AcceptInvitationDto } from './dto/accept-invitation.dto';
import { SetActiveOrganizationDto } from './dto/set-active-organization.dto';
+import { ForgotPasswordDto } from './dto/forgot-password.dto';
+import { ResetPasswordDto } from './dto/reset-password.dto';
import { JwtAuthGuard } from '../common/guards/jwt-auth.guard';
/**
@@ -137,6 +139,39 @@ export class AuthController {
return this.betterAuthService.getJwks();
}
+ // =========================================================================
+ // Password Reset Endpoints
+ // =========================================================================
+
+ /**
+ * Request password reset
+ *
+ * Initiates the password reset flow by sending an email with a reset link.
+ * Always returns success to prevent email enumeration attacks.
+ */
+ @Post('forgot-password')
+ @HttpCode(HttpStatus.OK)
+ async forgotPassword(@Body() forgotPasswordDto: ForgotPasswordDto) {
+ return this.betterAuthService.requestPasswordReset(
+ forgotPasswordDto.email,
+ forgotPasswordDto.redirectTo
+ );
+ }
+
+ /**
+ * Reset password with token
+ *
+ * Completes the password reset using the token from the email link.
+ */
+ @Post('reset-password')
+ @HttpCode(HttpStatus.OK)
+ async resetPassword(@Body() resetPasswordDto: ResetPasswordDto) {
+ return this.betterAuthService.resetPassword(
+ resetPasswordDto.token,
+ resetPasswordDto.newPassword
+ );
+ }
+
// =========================================================================
// B2B Registration
// =========================================================================
diff --git a/services/mana-core-auth/src/auth/better-auth.config.ts b/services/mana-core-auth/src/auth/better-auth.config.ts
index 0f0018824..13efe856f 100644
--- a/services/mana-core-auth/src/auth/better-auth.config.ts
+++ b/services/mana-core-auth/src/auth/better-auth.config.ts
@@ -80,12 +80,36 @@ export function createBetterAuth(databaseUrl: string) {
},
}),
- // Email/password authentication only
+ // Email/password authentication with password reset
emailAndPassword: {
enabled: true,
requireEmailVerification: false, // Can enable later
minPasswordLength: 12,
maxPasswordLength: 128,
+
+ /**
+ * Password Reset Configuration
+ *
+ * Better Auth provides password reset via:
+ * - auth.api.forgetPassword({ email }) - Sends reset email
+ * - auth.api.resetPassword({ newPassword, token }) - Resets password
+ *
+ * @see https://www.better-auth.com/docs/authentication/email-password#password-reset
+ */
+ sendResetPassword: async ({ user, url, token }) => {
+ // TODO: Implement email sending service (e.g., Resend, SendGrid)
+ // For now, log the reset URL for development
+ console.log('[Password Reset] User:', user.email);
+ console.log('[Password Reset] Reset URL:', url);
+ console.log('[Password Reset] Token:', token);
+
+ // In production, send an email like:
+ // await sendEmail({
+ // to: user.email,
+ // subject: 'Reset your password',
+ // html: `Reset your password`
+ // });
+ },
},
// Session configuration
diff --git a/services/mana-core-auth/src/auth/dto/forgot-password.dto.ts b/services/mana-core-auth/src/auth/dto/forgot-password.dto.ts
new file mode 100644
index 000000000..22a1c8bb8
--- /dev/null
+++ b/services/mana-core-auth/src/auth/dto/forgot-password.dto.ts
@@ -0,0 +1,22 @@
+import { IsEmail, IsOptional, IsString, IsUrl } from 'class-validator';
+
+/**
+ * Forgot Password DTO
+ *
+ * Request body for initiating password reset.
+ */
+export class ForgotPasswordDto {
+ /**
+ * User's email address
+ */
+ @IsEmail()
+ email: string;
+
+ /**
+ * Optional redirect URL after password reset
+ * The reset token will be appended as a query parameter
+ */
+ @IsOptional()
+ @IsString()
+ redirectTo?: string;
+}
diff --git a/services/mana-core-auth/src/auth/dto/reset-password.dto.ts b/services/mana-core-auth/src/auth/dto/reset-password.dto.ts
new file mode 100644
index 000000000..fa4d2367b
--- /dev/null
+++ b/services/mana-core-auth/src/auth/dto/reset-password.dto.ts
@@ -0,0 +1,22 @@
+import { IsString, MinLength, MaxLength } from 'class-validator';
+
+/**
+ * Reset Password DTO
+ *
+ * Request body for resetting password with token.
+ */
+export class ResetPasswordDto {
+ /**
+ * Reset token from email link
+ */
+ @IsString()
+ token: string;
+
+ /**
+ * New password (must meet password requirements)
+ */
+ @IsString()
+ @MinLength(12, { message: 'Password must be at least 12 characters long' })
+ @MaxLength(128, { message: 'Password must be at most 128 characters long' })
+ newPassword: string;
+}
diff --git a/services/mana-core-auth/src/auth/services/better-auth.service.ts b/services/mana-core-auth/src/auth/services/better-auth.service.ts
index 11cfce791..25a1be0fc 100644
--- a/services/mana-core-auth/src/auth/services/better-auth.service.ts
+++ b/services/mana-core-auth/src/auth/services/better-auth.service.ts
@@ -845,6 +845,92 @@ export class BetterAuthService {
}
}
+ // =========================================================================
+ // Password Reset Methods
+ // =========================================================================
+
+ /**
+ * Request password reset
+ *
+ * Sends a password reset email to the user.
+ * Uses Better Auth's forgetPassword API.
+ *
+ * @param email - User's email address
+ * @param redirectTo - Optional URL to redirect after reset (used in email link)
+ * @returns Success status
+ */
+ async requestPasswordReset(
+ email: string,
+ redirectTo?: string
+ ): Promise<{ success: boolean; message: string }> {
+ try {
+ // Better Auth's forgetPassword method
+ // See: https://www.better-auth.com/docs/authentication/email-password#password-reset
+ await (this.auth.api as any).forgetPassword({
+ body: {
+ email,
+ redirectTo,
+ },
+ });
+
+ // Always return success to prevent email enumeration
+ return {
+ success: true,
+ message: 'If an account with that email exists, a password reset link has been sent',
+ };
+ } catch (error) {
+ console.error('[requestPasswordReset] Error:', error);
+ // Always return success to prevent email enumeration attacks
+ return {
+ success: true,
+ message: 'If an account with that email exists, a password reset link has been sent',
+ };
+ }
+ }
+
+ /**
+ * Reset password with token
+ *
+ * Resets the user's password using the token from the reset email.
+ * Uses Better Auth's resetPassword API.
+ *
+ * @param token - Reset token from email link
+ * @param newPassword - New password to set
+ * @returns Success status
+ * @throws UnauthorizedException if token is invalid or expired
+ */
+ async resetPassword(
+ token: string,
+ newPassword: string
+ ): Promise<{ success: boolean; message: string }> {
+ try {
+ // Better Auth's resetPassword method
+ // See: https://www.better-auth.com/docs/authentication/email-password#password-reset
+ await (this.auth.api as any).resetPassword({
+ body: {
+ token,
+ newPassword,
+ },
+ });
+
+ return {
+ success: true,
+ message: 'Password has been reset successfully',
+ };
+ } catch (error: unknown) {
+ if (error instanceof Error) {
+ if (
+ error.message?.includes('invalid') ||
+ error.message?.includes('expired') ||
+ error.message?.includes('not found')
+ ) {
+ throw new UnauthorizedException('Invalid or expired reset token');
+ }
+ }
+ throw error;
+ }
+ }
+
/**
* Get JWKS (JSON Web Key Set)
*
diff --git a/services/mana-core-auth/src/common/guards/jwt-auth.guard.ts b/services/mana-core-auth/src/common/guards/jwt-auth.guard.ts
index bd83c130d..27724df9f 100644
--- a/services/mana-core-auth/src/common/guards/jwt-auth.guard.ts
+++ b/services/mana-core-auth/src/common/guards/jwt-auth.guard.ts
@@ -5,7 +5,7 @@ import {
UnauthorizedException,
} from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
-import { jwtVerify, createRemoteJWKSet, type JWTPayload } from 'jose';
+import { jwtVerify, createRemoteJWKSet } from 'jose';
/**
* JWT Auth Guard using JWKS (Better Auth compatible)
@@ -23,7 +23,10 @@ export class JwtAuthGuard implements CanActivate {
const request = context.switchToHttp().getRequest();
const token = this.extractTokenFromHeader(request);
+ console.log('[JwtAuthGuard] Token (first 50 chars):', token?.substring(0, 50));
+
if (!token) {
+ console.log('[JwtAuthGuard] No token provided');
throw new UnauthorizedException('No token provided');
}
@@ -32,17 +35,22 @@ export class JwtAuthGuard implements CanActivate {
if (!this.jwks) {
const baseUrl = this.configService.get('BASE_URL') || 'http://localhost:3001';
const jwksUrl = new URL('/api/v1/auth/jwks', baseUrl);
+ console.log('[JwtAuthGuard] Initializing JWKS from:', jwksUrl.toString());
this.jwks = createRemoteJWKSet(jwksUrl);
}
const issuer = this.configService.get('jwt.issuer') || 'manacore';
const audience = this.configService.get('jwt.audience') || 'manacore';
+ console.log('[JwtAuthGuard] Verifying with issuer:', issuer, 'audience:', audience);
+
const { payload } = await jwtVerify(token, this.jwks, {
issuer,
audience,
});
+ console.log('[JwtAuthGuard] Verification SUCCESS, user:', payload.sub);
+
// Attach user to request
request.user = {
userId: payload.sub,
@@ -52,7 +60,7 @@ export class JwtAuthGuard implements CanActivate {
return true;
} catch (error) {
- console.debug('[JwtAuthGuard] Token verification failed:', error);
+ console.error('[JwtAuthGuard] Token verification FAILED:', error);
throw new UnauthorizedException('Invalid token');
}
}
diff --git a/services/mana-core-auth/src/db/migrate.ts b/services/mana-core-auth/src/db/migrate.ts
new file mode 100644
index 000000000..3bff51152
--- /dev/null
+++ b/services/mana-core-auth/src/db/migrate.ts
@@ -0,0 +1,222 @@
+/**
+ * Database Migration Script with Advisory Locks
+ *
+ * This script safely runs database migrations with the following features:
+ * - Advisory locks to prevent concurrent migrations
+ * - Retry logic for transient network failures
+ * - Timeout protection
+ * - Proper cleanup on exit
+ * - Graceful handling when no migrations exist
+ *
+ * Usage:
+ * pnpm db:migrate # Run migrations
+ * MIGRATION_TIMEOUT=600 pnpm db:migrate # With custom timeout (seconds)
+ */
+
+import { drizzle } from 'drizzle-orm/postgres-js';
+import { migrate } from 'drizzle-orm/postgres-js/migrator';
+import { sql } from 'drizzle-orm';
+import postgres from 'postgres';
+import * as dotenv from 'dotenv';
+import * as fs from 'fs';
+import * as path from 'path';
+
+// Load environment variables
+dotenv.config();
+
+// Configuration
+const MIGRATION_LOCK_ID = 987654321; // Unique lock ID for mana-core-auth migrations
+const MAX_LOCK_WAIT_MS = parseInt(process.env.MIGRATION_TIMEOUT || '300', 10) * 1000; // Default 5 minutes
+const MAX_RETRIES = 3;
+const RETRY_DELAY_MS = 2000;
+
+/**
+ * Retry wrapper for transient errors
+ */
+async function withRetry(
+ operation: () => Promise,
+ operationName: string,
+ maxRetries = MAX_RETRIES
+): Promise {
+ let lastError: Error | undefined;
+
+ for (let attempt = 1; attempt <= maxRetries; attempt++) {
+ try {
+ return await operation();
+ } catch (error) {
+ lastError = error as Error;
+
+ // Check if error is transient (network-related)
+ const isTransient =
+ lastError.message?.includes('ECONNREFUSED') ||
+ lastError.message?.includes('ETIMEDOUT') ||
+ lastError.message?.includes('ENOTFOUND') ||
+ lastError.message?.includes('connection') ||
+ (lastError as any).code === '57P03'; // PostgreSQL: cannot connect now
+
+ if (!isTransient || attempt === maxRetries) {
+ throw error;
+ }
+
+ const delay = RETRY_DELAY_MS * Math.pow(2, attempt - 1); // Exponential backoff
+ console.log(
+ `\u26a0\ufe0f [${operationName}] Transient error, retrying in ${delay}ms... (attempt ${attempt}/${maxRetries})`
+ );
+ console.log(` Error: ${lastError.message}`);
+ await new Promise((resolve) => setTimeout(resolve, delay));
+ }
+ }
+
+ throw lastError!;
+}
+
+/**
+ * Acquire PostgreSQL advisory lock
+ */
+async function acquireLock(db: ReturnType): Promise {
+ const result = await db.execute(
+ sql`SELECT pg_try_advisory_lock(${MIGRATION_LOCK_ID}) as acquired`
+ );
+ return (result as any)[0]?.acquired === true;
+}
+
+/**
+ * Release PostgreSQL advisory lock
+ */
+async function releaseLock(db: ReturnType): Promise {
+ await db.execute(sql`SELECT pg_advisory_unlock(${MIGRATION_LOCK_ID})`);
+}
+
+/**
+ * Wait for migration lock with timeout
+ */
+async function waitForLock(db: ReturnType): Promise {
+ const startTime = Date.now();
+
+ while (Date.now() - startTime < MAX_LOCK_WAIT_MS) {
+ const acquired = await acquireLock(db);
+ if (acquired) {
+ return true;
+ }
+
+ const elapsed = Math.round((Date.now() - startTime) / 1000);
+ console.log(`\u23f3 Waiting for migration lock... (${elapsed}s / ${MAX_LOCK_WAIT_MS / 1000}s)`);
+ await new Promise((resolve) => setTimeout(resolve, 5000));
+ }
+
+ return false;
+}
+
+/**
+ * Main migration function
+ */
+async function runMigrations(): Promise {
+ const databaseUrl = process.env.DATABASE_URL;
+
+ if (!databaseUrl) {
+ throw new Error('DATABASE_URL environment variable is not set');
+ }
+
+ console.log('\n\ud83d\udd04 Starting database migration process...');
+ console.log(` Lock ID: ${MIGRATION_LOCK_ID}`);
+ console.log(` Timeout: ${MAX_LOCK_WAIT_MS / 1000}s`);
+ console.log('');
+
+ // Create connection with single connection for migrations
+ const connection = postgres(databaseUrl, {
+ max: 1,
+ idle_timeout: 20,
+ connect_timeout: 30,
+ });
+
+ const db = drizzle(connection);
+ let lockAcquired = false;
+
+ try {
+ // Test database connection
+ console.log('\ud83d\udd0c Testing database connection...');
+ await withRetry(async () => {
+ await db.execute(sql`SELECT 1`);
+ }, 'Database connection');
+ console.log('\u2705 Database connection successful\n');
+
+ // Attempt to acquire advisory lock
+ console.log('\ud83d\udd12 Attempting to acquire migration lock...');
+
+ lockAcquired = await withRetry(() => acquireLock(db), 'Acquire lock');
+
+ if (!lockAcquired) {
+ console.log('\u23f3 Another instance is running migrations. Waiting for lock...');
+
+ lockAcquired = await waitForLock(db);
+
+ if (!lockAcquired) {
+ throw new Error(
+ `Migration lock timeout after ${MAX_LOCK_WAIT_MS / 1000}s - another migration may be stuck`
+ );
+ }
+ }
+
+ console.log('\u2705 Migration lock acquired\n');
+
+ // Check if migration files exist
+ const migrationsFolder = './src/db/migrations';
+ const journalPath = path.join(migrationsFolder, 'meta', '_journal.json');
+
+ if (!fs.existsSync(journalPath)) {
+ console.log('\u26a0\ufe0f No migration files found (meta/_journal.json missing)');
+ console.log(' This is normal if you have not generated any migrations yet.');
+ console.log(' To generate migrations, run: pnpm db:generate');
+ console.log(' For development, you can use: pnpm db:push');
+ console.log('\n\u2705 No migrations to run\n');
+ return;
+ }
+
+ // Run migrations
+ console.log('\ud83d\udce6 Running database migrations...');
+
+ await withRetry(
+ async () => {
+ await migrate(db, {
+ migrationsFolder,
+ });
+ },
+ 'Run migrations',
+ 1 // Only 1 attempt for actual migrations (they should be idempotent)
+ );
+
+ console.log('\u2705 Migrations completed successfully\n');
+ } catch (error) {
+ console.error('\n\u274c Migration failed:', error);
+ throw error;
+ } finally {
+ // Always attempt to release lock
+ if (lockAcquired) {
+ try {
+ await releaseLock(db);
+ console.log('\ud83d\udd13 Migration lock released');
+ } catch (unlockError) {
+ console.error('\u26a0\ufe0f Failed to release lock:', unlockError);
+ }
+ }
+
+ // Close connection
+ try {
+ await connection.end();
+ console.log('\ud83d\udd0c Database connection closed\n');
+ } catch (closeError) {
+ console.error('\u26a0\ufe0f Failed to close connection:', closeError);
+ }
+ }
+}
+
+// Run migrations
+runMigrations()
+ .then(() => {
+ console.log('\ud83c\udf89 Migration process completed successfully');
+ process.exit(0);
+ })
+ .catch((error) => {
+ console.error('\n\ud83d\udca5 Migration process failed:', error.message);
+ process.exit(1);
+ });
diff --git a/services/mana-core-auth/src/db/migrations/0001_zippy_ma_gnuci.sql b/services/mana-core-auth/src/db/migrations/0001_zippy_ma_gnuci.sql
deleted file mode 100644
index 2ad2cfbfc..000000000
--- a/services/mana-core-auth/src/db/migrations/0001_zippy_ma_gnuci.sql
+++ /dev/null
@@ -1,39 +0,0 @@
-CREATE SCHEMA "feedback";
---> statement-breakpoint
-CREATE TYPE "public"."feedback_category" AS ENUM('bug', 'feature', 'improvement', 'question', 'other');--> statement-breakpoint
-CREATE TYPE "public"."feedback_status" AS ENUM('submitted', 'under_review', 'planned', 'in_progress', 'completed', 'declined');--> statement-breakpoint
-CREATE TABLE "feedback"."feedback_votes" (
- "id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
- "feedback_id" uuid NOT NULL,
- "user_id" uuid NOT NULL,
- "created_at" timestamp with time zone DEFAULT now() NOT NULL
-);
---> statement-breakpoint
-CREATE TABLE "feedback"."user_feedback" (
- "id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
- "user_id" uuid NOT NULL,
- "app_id" text NOT NULL,
- "title" text,
- "feedback_text" text NOT NULL,
- "category" "feedback_category" DEFAULT 'feature' NOT NULL,
- "status" "feedback_status" DEFAULT 'submitted' NOT NULL,
- "is_public" boolean DEFAULT false NOT NULL,
- "admin_response" text,
- "vote_count" integer DEFAULT 0 NOT NULL,
- "device_info" jsonb,
- "created_at" timestamp with time zone DEFAULT now() NOT NULL,
- "updated_at" timestamp with time zone DEFAULT now() NOT NULL,
- "published_at" timestamp with time zone,
- "completed_at" timestamp with time zone
-);
---> statement-breakpoint
-ALTER TABLE "feedback"."feedback_votes" ADD CONSTRAINT "feedback_votes_feedback_id_user_feedback_id_fk" FOREIGN KEY ("feedback_id") REFERENCES "feedback"."user_feedback"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
-ALTER TABLE "feedback"."feedback_votes" ADD CONSTRAINT "feedback_votes_user_id_users_id_fk" FOREIGN KEY ("user_id") REFERENCES "auth"."users"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
-ALTER TABLE "feedback"."user_feedback" ADD CONSTRAINT "user_feedback_user_id_users_id_fk" FOREIGN KEY ("user_id") REFERENCES "auth"."users"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
-CREATE UNIQUE INDEX "feedback_vote_unique" ON "feedback"."feedback_votes" USING btree ("feedback_id","user_id");--> statement-breakpoint
-CREATE INDEX "feedback_votes_feedback_idx" ON "feedback"."feedback_votes" USING btree ("feedback_id");--> statement-breakpoint
-CREATE INDEX "feedback_user_idx" ON "feedback"."user_feedback" USING btree ("user_id");--> statement-breakpoint
-CREATE INDEX "feedback_app_idx" ON "feedback"."user_feedback" USING btree ("app_id");--> statement-breakpoint
-CREATE INDEX "feedback_public_idx" ON "feedback"."user_feedback" USING btree ("is_public");--> statement-breakpoint
-CREATE INDEX "feedback_status_idx" ON "feedback"."user_feedback" USING btree ("status");--> statement-breakpoint
-CREATE INDEX "feedback_created_at_idx" ON "feedback"."user_feedback" USING btree ("created_at");
\ No newline at end of file
diff --git a/services/mana-core-auth/src/db/migrations/meta/0001_snapshot.json b/services/mana-core-auth/src/db/migrations/meta/0001_snapshot.json
deleted file mode 100644
index c3e223168..000000000
--- a/services/mana-core-auth/src/db/migrations/meta/0001_snapshot.json
+++ /dev/null
@@ -1,1501 +0,0 @@
-{
- "id": "ecb1358f-1cde-49ae-973b-4a2d9d7d5c2b",
- "prevId": "83697ac3-d241-4743-96a1-880ad990aa0b",
- "version": "7",
- "dialect": "postgresql",
- "tables": {
- "auth.accounts": {
- "name": "accounts",
- "schema": "auth",
- "columns": {
- "id": {
- "name": "id",
- "type": "uuid",
- "primaryKey": true,
- "notNull": true,
- "default": "gen_random_uuid()"
- },
- "user_id": {
- "name": "user_id",
- "type": "uuid",
- "primaryKey": false,
- "notNull": true
- },
- "provider": {
- "name": "provider",
- "type": "text",
- "primaryKey": false,
- "notNull": true
- },
- "provider_account_id": {
- "name": "provider_account_id",
- "type": "text",
- "primaryKey": false,
- "notNull": true
- },
- "access_token": {
- "name": "access_token",
- "type": "text",
- "primaryKey": false,
- "notNull": false
- },
- "refresh_token": {
- "name": "refresh_token",
- "type": "text",
- "primaryKey": false,
- "notNull": false
- },
- "expires_at": {
- "name": "expires_at",
- "type": "timestamp with time zone",
- "primaryKey": false,
- "notNull": false
- },
- "token_type": {
- "name": "token_type",
- "type": "text",
- "primaryKey": false,
- "notNull": false
- },
- "scope": {
- "name": "scope",
- "type": "text",
- "primaryKey": false,
- "notNull": false
- },
- "id_token": {
- "name": "id_token",
- "type": "text",
- "primaryKey": false,
- "notNull": false
- },
- "metadata": {
- "name": "metadata",
- "type": "jsonb",
- "primaryKey": false,
- "notNull": false
- },
- "created_at": {
- "name": "created_at",
- "type": "timestamp with time zone",
- "primaryKey": false,
- "notNull": true,
- "default": "now()"
- },
- "updated_at": {
- "name": "updated_at",
- "type": "timestamp with time zone",
- "primaryKey": false,
- "notNull": true,
- "default": "now()"
- }
- },
- "indexes": {},
- "foreignKeys": {
- "accounts_user_id_users_id_fk": {
- "name": "accounts_user_id_users_id_fk",
- "tableFrom": "accounts",
- "tableTo": "users",
- "schemaTo": "auth",
- "columnsFrom": ["user_id"],
- "columnsTo": ["id"],
- "onDelete": "cascade",
- "onUpdate": "no action"
- }
- },
- "compositePrimaryKeys": {},
- "uniqueConstraints": {},
- "policies": {},
- "checkConstraints": {},
- "isRLSEnabled": false
- },
- "auth.passwords": {
- "name": "passwords",
- "schema": "auth",
- "columns": {
- "user_id": {
- "name": "user_id",
- "type": "uuid",
- "primaryKey": true,
- "notNull": true
- },
- "hashed_password": {
- "name": "hashed_password",
- "type": "text",
- "primaryKey": false,
- "notNull": true
- },
- "created_at": {
- "name": "created_at",
- "type": "timestamp with time zone",
- "primaryKey": false,
- "notNull": true,
- "default": "now()"
- },
- "updated_at": {
- "name": "updated_at",
- "type": "timestamp with time zone",
- "primaryKey": false,
- "notNull": true,
- "default": "now()"
- }
- },
- "indexes": {},
- "foreignKeys": {
- "passwords_user_id_users_id_fk": {
- "name": "passwords_user_id_users_id_fk",
- "tableFrom": "passwords",
- "tableTo": "users",
- "schemaTo": "auth",
- "columnsFrom": ["user_id"],
- "columnsTo": ["id"],
- "onDelete": "cascade",
- "onUpdate": "no action"
- }
- },
- "compositePrimaryKeys": {},
- "uniqueConstraints": {},
- "policies": {},
- "checkConstraints": {},
- "isRLSEnabled": false
- },
- "auth.security_events": {
- "name": "security_events",
- "schema": "auth",
- "columns": {
- "id": {
- "name": "id",
- "type": "uuid",
- "primaryKey": true,
- "notNull": true,
- "default": "gen_random_uuid()"
- },
- "user_id": {
- "name": "user_id",
- "type": "uuid",
- "primaryKey": false,
- "notNull": false
- },
- "event_type": {
- "name": "event_type",
- "type": "text",
- "primaryKey": false,
- "notNull": true
- },
- "ip_address": {
- "name": "ip_address",
- "type": "text",
- "primaryKey": false,
- "notNull": false
- },
- "user_agent": {
- "name": "user_agent",
- "type": "text",
- "primaryKey": false,
- "notNull": false
- },
- "metadata": {
- "name": "metadata",
- "type": "jsonb",
- "primaryKey": false,
- "notNull": false
- },
- "created_at": {
- "name": "created_at",
- "type": "timestamp with time zone",
- "primaryKey": false,
- "notNull": true,
- "default": "now()"
- }
- },
- "indexes": {},
- "foreignKeys": {
- "security_events_user_id_users_id_fk": {
- "name": "security_events_user_id_users_id_fk",
- "tableFrom": "security_events",
- "tableTo": "users",
- "schemaTo": "auth",
- "columnsFrom": ["user_id"],
- "columnsTo": ["id"],
- "onDelete": "cascade",
- "onUpdate": "no action"
- }
- },
- "compositePrimaryKeys": {},
- "uniqueConstraints": {},
- "policies": {},
- "checkConstraints": {},
- "isRLSEnabled": false
- },
- "auth.sessions": {
- "name": "sessions",
- "schema": "auth",
- "columns": {
- "id": {
- "name": "id",
- "type": "uuid",
- "primaryKey": true,
- "notNull": true,
- "default": "gen_random_uuid()"
- },
- "user_id": {
- "name": "user_id",
- "type": "uuid",
- "primaryKey": false,
- "notNull": true
- },
- "token": {
- "name": "token",
- "type": "text",
- "primaryKey": false,
- "notNull": true
- },
- "refresh_token": {
- "name": "refresh_token",
- "type": "text",
- "primaryKey": false,
- "notNull": true
- },
- "refresh_token_expires_at": {
- "name": "refresh_token_expires_at",
- "type": "timestamp with time zone",
- "primaryKey": false,
- "notNull": true
- },
- "ip_address": {
- "name": "ip_address",
- "type": "text",
- "primaryKey": false,
- "notNull": false
- },
- "user_agent": {
- "name": "user_agent",
- "type": "text",
- "primaryKey": false,
- "notNull": false
- },
- "device_id": {
- "name": "device_id",
- "type": "text",
- "primaryKey": false,
- "notNull": false
- },
- "device_name": {
- "name": "device_name",
- "type": "text",
- "primaryKey": false,
- "notNull": false
- },
- "last_activity_at": {
- "name": "last_activity_at",
- "type": "timestamp with time zone",
- "primaryKey": false,
- "notNull": true,
- "default": "now()"
- },
- "created_at": {
- "name": "created_at",
- "type": "timestamp with time zone",
- "primaryKey": false,
- "notNull": true,
- "default": "now()"
- },
- "expires_at": {
- "name": "expires_at",
- "type": "timestamp with time zone",
- "primaryKey": false,
- "notNull": true
- },
- "revoked_at": {
- "name": "revoked_at",
- "type": "timestamp with time zone",
- "primaryKey": false,
- "notNull": false
- }
- },
- "indexes": {},
- "foreignKeys": {
- "sessions_user_id_users_id_fk": {
- "name": "sessions_user_id_users_id_fk",
- "tableFrom": "sessions",
- "tableTo": "users",
- "schemaTo": "auth",
- "columnsFrom": ["user_id"],
- "columnsTo": ["id"],
- "onDelete": "cascade",
- "onUpdate": "no action"
- }
- },
- "compositePrimaryKeys": {},
- "uniqueConstraints": {
- "sessions_token_unique": {
- "name": "sessions_token_unique",
- "nullsNotDistinct": false,
- "columns": ["token"]
- },
- "sessions_refresh_token_unique": {
- "name": "sessions_refresh_token_unique",
- "nullsNotDistinct": false,
- "columns": ["refresh_token"]
- }
- },
- "policies": {},
- "checkConstraints": {},
- "isRLSEnabled": false
- },
- "auth.two_factor_auth": {
- "name": "two_factor_auth",
- "schema": "auth",
- "columns": {
- "user_id": {
- "name": "user_id",
- "type": "uuid",
- "primaryKey": true,
- "notNull": true
- },
- "secret": {
- "name": "secret",
- "type": "text",
- "primaryKey": false,
- "notNull": true
- },
- "enabled": {
- "name": "enabled",
- "type": "boolean",
- "primaryKey": false,
- "notNull": true,
- "default": false
- },
- "backup_codes": {
- "name": "backup_codes",
- "type": "jsonb",
- "primaryKey": false,
- "notNull": false
- },
- "created_at": {
- "name": "created_at",
- "type": "timestamp with time zone",
- "primaryKey": false,
- "notNull": true,
- "default": "now()"
- },
- "enabled_at": {
- "name": "enabled_at",
- "type": "timestamp with time zone",
- "primaryKey": false,
- "notNull": false
- }
- },
- "indexes": {},
- "foreignKeys": {
- "two_factor_auth_user_id_users_id_fk": {
- "name": "two_factor_auth_user_id_users_id_fk",
- "tableFrom": "two_factor_auth",
- "tableTo": "users",
- "schemaTo": "auth",
- "columnsFrom": ["user_id"],
- "columnsTo": ["id"],
- "onDelete": "cascade",
- "onUpdate": "no action"
- }
- },
- "compositePrimaryKeys": {},
- "uniqueConstraints": {},
- "policies": {},
- "checkConstraints": {},
- "isRLSEnabled": false
- },
- "auth.users": {
- "name": "users",
- "schema": "auth",
- "columns": {
- "id": {
- "name": "id",
- "type": "uuid",
- "primaryKey": true,
- "notNull": true,
- "default": "gen_random_uuid()"
- },
- "email": {
- "name": "email",
- "type": "text",
- "primaryKey": false,
- "notNull": true
- },
- "email_verified": {
- "name": "email_verified",
- "type": "boolean",
- "primaryKey": false,
- "notNull": true,
- "default": false
- },
- "name": {
- "name": "name",
- "type": "text",
- "primaryKey": false,
- "notNull": false
- },
- "avatar_url": {
- "name": "avatar_url",
- "type": "text",
- "primaryKey": false,
- "notNull": false
- },
- "role": {
- "name": "role",
- "type": "user_role",
- "typeSchema": "public",
- "primaryKey": false,
- "notNull": true,
- "default": "'user'"
- },
- "created_at": {
- "name": "created_at",
- "type": "timestamp with time zone",
- "primaryKey": false,
- "notNull": true,
- "default": "now()"
- },
- "updated_at": {
- "name": "updated_at",
- "type": "timestamp with time zone",
- "primaryKey": false,
- "notNull": true,
- "default": "now()"
- },
- "deleted_at": {
- "name": "deleted_at",
- "type": "timestamp with time zone",
- "primaryKey": false,
- "notNull": false
- }
- },
- "indexes": {},
- "foreignKeys": {},
- "compositePrimaryKeys": {},
- "uniqueConstraints": {
- "users_email_unique": {
- "name": "users_email_unique",
- "nullsNotDistinct": false,
- "columns": ["email"]
- }
- },
- "policies": {},
- "checkConstraints": {},
- "isRLSEnabled": false
- },
- "auth.verification_tokens": {
- "name": "verification_tokens",
- "schema": "auth",
- "columns": {
- "id": {
- "name": "id",
- "type": "uuid",
- "primaryKey": true,
- "notNull": true,
- "default": "gen_random_uuid()"
- },
- "user_id": {
- "name": "user_id",
- "type": "uuid",
- "primaryKey": false,
- "notNull": true
- },
- "token": {
- "name": "token",
- "type": "text",
- "primaryKey": false,
- "notNull": true
- },
- "type": {
- "name": "type",
- "type": "text",
- "primaryKey": false,
- "notNull": true
- },
- "expires_at": {
- "name": "expires_at",
- "type": "timestamp with time zone",
- "primaryKey": false,
- "notNull": true
- },
- "created_at": {
- "name": "created_at",
- "type": "timestamp with time zone",
- "primaryKey": false,
- "notNull": true,
- "default": "now()"
- },
- "used_at": {
- "name": "used_at",
- "type": "timestamp with time zone",
- "primaryKey": false,
- "notNull": false
- }
- },
- "indexes": {},
- "foreignKeys": {
- "verification_tokens_user_id_users_id_fk": {
- "name": "verification_tokens_user_id_users_id_fk",
- "tableFrom": "verification_tokens",
- "tableTo": "users",
- "schemaTo": "auth",
- "columnsFrom": ["user_id"],
- "columnsTo": ["id"],
- "onDelete": "cascade",
- "onUpdate": "no action"
- }
- },
- "compositePrimaryKeys": {},
- "uniqueConstraints": {
- "verification_tokens_token_unique": {
- "name": "verification_tokens_token_unique",
- "nullsNotDistinct": false,
- "columns": ["token"]
- }
- },
- "policies": {},
- "checkConstraints": {},
- "isRLSEnabled": false
- },
- "credits.balances": {
- "name": "balances",
- "schema": "credits",
- "columns": {
- "user_id": {
- "name": "user_id",
- "type": "uuid",
- "primaryKey": true,
- "notNull": true
- },
- "balance": {
- "name": "balance",
- "type": "integer",
- "primaryKey": false,
- "notNull": true,
- "default": 0
- },
- "free_credits_remaining": {
- "name": "free_credits_remaining",
- "type": "integer",
- "primaryKey": false,
- "notNull": true,
- "default": 150
- },
- "daily_free_credits": {
- "name": "daily_free_credits",
- "type": "integer",
- "primaryKey": false,
- "notNull": true,
- "default": 5
- },
- "last_daily_reset_at": {
- "name": "last_daily_reset_at",
- "type": "timestamp with time zone",
- "primaryKey": false,
- "notNull": false,
- "default": "now()"
- },
- "total_earned": {
- "name": "total_earned",
- "type": "integer",
- "primaryKey": false,
- "notNull": true,
- "default": 0
- },
- "total_spent": {
- "name": "total_spent",
- "type": "integer",
- "primaryKey": false,
- "notNull": true,
- "default": 0
- },
- "version": {
- "name": "version",
- "type": "integer",
- "primaryKey": false,
- "notNull": true,
- "default": 0
- },
- "created_at": {
- "name": "created_at",
- "type": "timestamp with time zone",
- "primaryKey": false,
- "notNull": true,
- "default": "now()"
- },
- "updated_at": {
- "name": "updated_at",
- "type": "timestamp with time zone",
- "primaryKey": false,
- "notNull": true,
- "default": "now()"
- }
- },
- "indexes": {},
- "foreignKeys": {
- "balances_user_id_users_id_fk": {
- "name": "balances_user_id_users_id_fk",
- "tableFrom": "balances",
- "tableTo": "users",
- "schemaTo": "auth",
- "columnsFrom": ["user_id"],
- "columnsTo": ["id"],
- "onDelete": "cascade",
- "onUpdate": "no action"
- }
- },
- "compositePrimaryKeys": {},
- "uniqueConstraints": {},
- "policies": {},
- "checkConstraints": {},
- "isRLSEnabled": false
- },
- "credits.packages": {
- "name": "packages",
- "schema": "credits",
- "columns": {
- "id": {
- "name": "id",
- "type": "uuid",
- "primaryKey": true,
- "notNull": true,
- "default": "gen_random_uuid()"
- },
- "name": {
- "name": "name",
- "type": "text",
- "primaryKey": false,
- "notNull": true
- },
- "description": {
- "name": "description",
- "type": "text",
- "primaryKey": false,
- "notNull": false
- },
- "credits": {
- "name": "credits",
- "type": "integer",
- "primaryKey": false,
- "notNull": true
- },
- "price_euro_cents": {
- "name": "price_euro_cents",
- "type": "integer",
- "primaryKey": false,
- "notNull": true
- },
- "stripe_price_id": {
- "name": "stripe_price_id",
- "type": "text",
- "primaryKey": false,
- "notNull": false
- },
- "active": {
- "name": "active",
- "type": "boolean",
- "primaryKey": false,
- "notNull": true,
- "default": true
- },
- "sort_order": {
- "name": "sort_order",
- "type": "integer",
- "primaryKey": false,
- "notNull": true,
- "default": 0
- },
- "metadata": {
- "name": "metadata",
- "type": "jsonb",
- "primaryKey": false,
- "notNull": false
- },
- "created_at": {
- "name": "created_at",
- "type": "timestamp with time zone",
- "primaryKey": false,
- "notNull": true,
- "default": "now()"
- },
- "updated_at": {
- "name": "updated_at",
- "type": "timestamp with time zone",
- "primaryKey": false,
- "notNull": true,
- "default": "now()"
- }
- },
- "indexes": {},
- "foreignKeys": {},
- "compositePrimaryKeys": {},
- "uniqueConstraints": {
- "packages_stripe_price_id_unique": {
- "name": "packages_stripe_price_id_unique",
- "nullsNotDistinct": false,
- "columns": ["stripe_price_id"]
- }
- },
- "policies": {},
- "checkConstraints": {},
- "isRLSEnabled": false
- },
- "credits.purchases": {
- "name": "purchases",
- "schema": "credits",
- "columns": {
- "id": {
- "name": "id",
- "type": "uuid",
- "primaryKey": true,
- "notNull": true,
- "default": "gen_random_uuid()"
- },
- "user_id": {
- "name": "user_id",
- "type": "uuid",
- "primaryKey": false,
- "notNull": true
- },
- "package_id": {
- "name": "package_id",
- "type": "uuid",
- "primaryKey": false,
- "notNull": false
- },
- "credits": {
- "name": "credits",
- "type": "integer",
- "primaryKey": false,
- "notNull": true
- },
- "price_euro_cents": {
- "name": "price_euro_cents",
- "type": "integer",
- "primaryKey": false,
- "notNull": true
- },
- "stripe_payment_intent_id": {
- "name": "stripe_payment_intent_id",
- "type": "text",
- "primaryKey": false,
- "notNull": false
- },
- "stripe_customer_id": {
- "name": "stripe_customer_id",
- "type": "text",
- "primaryKey": false,
- "notNull": false
- },
- "status": {
- "name": "status",
- "type": "transaction_status",
- "typeSchema": "public",
- "primaryKey": false,
- "notNull": true,
- "default": "'pending'"
- },
- "metadata": {
- "name": "metadata",
- "type": "jsonb",
- "primaryKey": false,
- "notNull": false
- },
- "created_at": {
- "name": "created_at",
- "type": "timestamp with time zone",
- "primaryKey": false,
- "notNull": true,
- "default": "now()"
- },
- "completed_at": {
- "name": "completed_at",
- "type": "timestamp with time zone",
- "primaryKey": false,
- "notNull": false
- }
- },
- "indexes": {
- "purchases_user_id_idx": {
- "name": "purchases_user_id_idx",
- "columns": [
- {
- "expression": "user_id",
- "isExpression": false,
- "asc": true,
- "nulls": "last"
- }
- ],
- "isUnique": false,
- "concurrently": false,
- "method": "btree",
- "with": {}
- },
- "purchases_stripe_payment_intent_id_idx": {
- "name": "purchases_stripe_payment_intent_id_idx",
- "columns": [
- {
- "expression": "stripe_payment_intent_id",
- "isExpression": false,
- "asc": true,
- "nulls": "last"
- }
- ],
- "isUnique": false,
- "concurrently": false,
- "method": "btree",
- "with": {}
- }
- },
- "foreignKeys": {
- "purchases_user_id_users_id_fk": {
- "name": "purchases_user_id_users_id_fk",
- "tableFrom": "purchases",
- "tableTo": "users",
- "schemaTo": "auth",
- "columnsFrom": ["user_id"],
- "columnsTo": ["id"],
- "onDelete": "cascade",
- "onUpdate": "no action"
- },
- "purchases_package_id_packages_id_fk": {
- "name": "purchases_package_id_packages_id_fk",
- "tableFrom": "purchases",
- "tableTo": "packages",
- "schemaTo": "credits",
- "columnsFrom": ["package_id"],
- "columnsTo": ["id"],
- "onDelete": "no action",
- "onUpdate": "no action"
- }
- },
- "compositePrimaryKeys": {},
- "uniqueConstraints": {
- "purchases_stripe_payment_intent_id_unique": {
- "name": "purchases_stripe_payment_intent_id_unique",
- "nullsNotDistinct": false,
- "columns": ["stripe_payment_intent_id"]
- }
- },
- "policies": {},
- "checkConstraints": {},
- "isRLSEnabled": false
- },
- "credits.transactions": {
- "name": "transactions",
- "schema": "credits",
- "columns": {
- "id": {
- "name": "id",
- "type": "uuid",
- "primaryKey": true,
- "notNull": true,
- "default": "gen_random_uuid()"
- },
- "user_id": {
- "name": "user_id",
- "type": "uuid",
- "primaryKey": false,
- "notNull": true
- },
- "type": {
- "name": "type",
- "type": "transaction_type",
- "typeSchema": "public",
- "primaryKey": false,
- "notNull": true
- },
- "status": {
- "name": "status",
- "type": "transaction_status",
- "typeSchema": "public",
- "primaryKey": false,
- "notNull": true,
- "default": "'pending'"
- },
- "amount": {
- "name": "amount",
- "type": "integer",
- "primaryKey": false,
- "notNull": true
- },
- "balance_before": {
- "name": "balance_before",
- "type": "integer",
- "primaryKey": false,
- "notNull": true
- },
- "balance_after": {
- "name": "balance_after",
- "type": "integer",
- "primaryKey": false,
- "notNull": true
- },
- "app_id": {
- "name": "app_id",
- "type": "text",
- "primaryKey": false,
- "notNull": true
- },
- "description": {
- "name": "description",
- "type": "text",
- "primaryKey": false,
- "notNull": true
- },
- "metadata": {
- "name": "metadata",
- "type": "jsonb",
- "primaryKey": false,
- "notNull": false
- },
- "idempotency_key": {
- "name": "idempotency_key",
- "type": "text",
- "primaryKey": false,
- "notNull": false
- },
- "created_at": {
- "name": "created_at",
- "type": "timestamp with time zone",
- "primaryKey": false,
- "notNull": true,
- "default": "now()"
- },
- "completed_at": {
- "name": "completed_at",
- "type": "timestamp with time zone",
- "primaryKey": false,
- "notNull": false
- }
- },
- "indexes": {
- "transactions_user_id_idx": {
- "name": "transactions_user_id_idx",
- "columns": [
- {
- "expression": "user_id",
- "isExpression": false,
- "asc": true,
- "nulls": "last"
- }
- ],
- "isUnique": false,
- "concurrently": false,
- "method": "btree",
- "with": {}
- },
- "transactions_app_id_idx": {
- "name": "transactions_app_id_idx",
- "columns": [
- {
- "expression": "app_id",
- "isExpression": false,
- "asc": true,
- "nulls": "last"
- }
- ],
- "isUnique": false,
- "concurrently": false,
- "method": "btree",
- "with": {}
- },
- "transactions_created_at_idx": {
- "name": "transactions_created_at_idx",
- "columns": [
- {
- "expression": "created_at",
- "isExpression": false,
- "asc": true,
- "nulls": "last"
- }
- ],
- "isUnique": false,
- "concurrently": false,
- "method": "btree",
- "with": {}
- },
- "transactions_idempotency_key_idx": {
- "name": "transactions_idempotency_key_idx",
- "columns": [
- {
- "expression": "idempotency_key",
- "isExpression": false,
- "asc": true,
- "nulls": "last"
- }
- ],
- "isUnique": false,
- "concurrently": false,
- "method": "btree",
- "with": {}
- }
- },
- "foreignKeys": {
- "transactions_user_id_users_id_fk": {
- "name": "transactions_user_id_users_id_fk",
- "tableFrom": "transactions",
- "tableTo": "users",
- "schemaTo": "auth",
- "columnsFrom": ["user_id"],
- "columnsTo": ["id"],
- "onDelete": "cascade",
- "onUpdate": "no action"
- }
- },
- "compositePrimaryKeys": {},
- "uniqueConstraints": {
- "transactions_idempotency_key_unique": {
- "name": "transactions_idempotency_key_unique",
- "nullsNotDistinct": false,
- "columns": ["idempotency_key"]
- }
- },
- "policies": {},
- "checkConstraints": {},
- "isRLSEnabled": false
- },
- "credits.usage_stats": {
- "name": "usage_stats",
- "schema": "credits",
- "columns": {
- "id": {
- "name": "id",
- "type": "uuid",
- "primaryKey": true,
- "notNull": true,
- "default": "gen_random_uuid()"
- },
- "user_id": {
- "name": "user_id",
- "type": "uuid",
- "primaryKey": false,
- "notNull": true
- },
- "app_id": {
- "name": "app_id",
- "type": "text",
- "primaryKey": false,
- "notNull": true
- },
- "credits_used": {
- "name": "credits_used",
- "type": "integer",
- "primaryKey": false,
- "notNull": true
- },
- "date": {
- "name": "date",
- "type": "timestamp with time zone",
- "primaryKey": false,
- "notNull": true
- },
- "metadata": {
- "name": "metadata",
- "type": "jsonb",
- "primaryKey": false,
- "notNull": false
- }
- },
- "indexes": {
- "usage_stats_user_id_date_idx": {
- "name": "usage_stats_user_id_date_idx",
- "columns": [
- {
- "expression": "user_id",
- "isExpression": false,
- "asc": true,
- "nulls": "last"
- },
- {
- "expression": "date",
- "isExpression": false,
- "asc": true,
- "nulls": "last"
- }
- ],
- "isUnique": false,
- "concurrently": false,
- "method": "btree",
- "with": {}
- },
- "usage_stats_app_id_date_idx": {
- "name": "usage_stats_app_id_date_idx",
- "columns": [
- {
- "expression": "app_id",
- "isExpression": false,
- "asc": true,
- "nulls": "last"
- },
- {
- "expression": "date",
- "isExpression": false,
- "asc": true,
- "nulls": "last"
- }
- ],
- "isUnique": false,
- "concurrently": false,
- "method": "btree",
- "with": {}
- }
- },
- "foreignKeys": {
- "usage_stats_user_id_users_id_fk": {
- "name": "usage_stats_user_id_users_id_fk",
- "tableFrom": "usage_stats",
- "tableTo": "users",
- "schemaTo": "auth",
- "columnsFrom": ["user_id"],
- "columnsTo": ["id"],
- "onDelete": "cascade",
- "onUpdate": "no action"
- }
- },
- "compositePrimaryKeys": {},
- "uniqueConstraints": {},
- "policies": {},
- "checkConstraints": {},
- "isRLSEnabled": false
- },
- "feedback.feedback_votes": {
- "name": "feedback_votes",
- "schema": "feedback",
- "columns": {
- "id": {
- "name": "id",
- "type": "uuid",
- "primaryKey": true,
- "notNull": true,
- "default": "gen_random_uuid()"
- },
- "feedback_id": {
- "name": "feedback_id",
- "type": "uuid",
- "primaryKey": false,
- "notNull": true
- },
- "user_id": {
- "name": "user_id",
- "type": "uuid",
- "primaryKey": false,
- "notNull": true
- },
- "created_at": {
- "name": "created_at",
- "type": "timestamp with time zone",
- "primaryKey": false,
- "notNull": true,
- "default": "now()"
- }
- },
- "indexes": {
- "feedback_vote_unique": {
- "name": "feedback_vote_unique",
- "columns": [
- {
- "expression": "feedback_id",
- "isExpression": false,
- "asc": true,
- "nulls": "last"
- },
- {
- "expression": "user_id",
- "isExpression": false,
- "asc": true,
- "nulls": "last"
- }
- ],
- "isUnique": true,
- "concurrently": false,
- "method": "btree",
- "with": {}
- },
- "feedback_votes_feedback_idx": {
- "name": "feedback_votes_feedback_idx",
- "columns": [
- {
- "expression": "feedback_id",
- "isExpression": false,
- "asc": true,
- "nulls": "last"
- }
- ],
- "isUnique": false,
- "concurrently": false,
- "method": "btree",
- "with": {}
- }
- },
- "foreignKeys": {
- "feedback_votes_feedback_id_user_feedback_id_fk": {
- "name": "feedback_votes_feedback_id_user_feedback_id_fk",
- "tableFrom": "feedback_votes",
- "tableTo": "user_feedback",
- "schemaTo": "feedback",
- "columnsFrom": ["feedback_id"],
- "columnsTo": ["id"],
- "onDelete": "cascade",
- "onUpdate": "no action"
- },
- "feedback_votes_user_id_users_id_fk": {
- "name": "feedback_votes_user_id_users_id_fk",
- "tableFrom": "feedback_votes",
- "tableTo": "users",
- "schemaTo": "auth",
- "columnsFrom": ["user_id"],
- "columnsTo": ["id"],
- "onDelete": "cascade",
- "onUpdate": "no action"
- }
- },
- "compositePrimaryKeys": {},
- "uniqueConstraints": {},
- "policies": {},
- "checkConstraints": {},
- "isRLSEnabled": false
- },
- "feedback.user_feedback": {
- "name": "user_feedback",
- "schema": "feedback",
- "columns": {
- "id": {
- "name": "id",
- "type": "uuid",
- "primaryKey": true,
- "notNull": true,
- "default": "gen_random_uuid()"
- },
- "user_id": {
- "name": "user_id",
- "type": "uuid",
- "primaryKey": false,
- "notNull": true
- },
- "app_id": {
- "name": "app_id",
- "type": "text",
- "primaryKey": false,
- "notNull": true
- },
- "title": {
- "name": "title",
- "type": "text",
- "primaryKey": false,
- "notNull": false
- },
- "feedback_text": {
- "name": "feedback_text",
- "type": "text",
- "primaryKey": false,
- "notNull": true
- },
- "category": {
- "name": "category",
- "type": "feedback_category",
- "typeSchema": "public",
- "primaryKey": false,
- "notNull": true,
- "default": "'feature'"
- },
- "status": {
- "name": "status",
- "type": "feedback_status",
- "typeSchema": "public",
- "primaryKey": false,
- "notNull": true,
- "default": "'submitted'"
- },
- "is_public": {
- "name": "is_public",
- "type": "boolean",
- "primaryKey": false,
- "notNull": true,
- "default": false
- },
- "admin_response": {
- "name": "admin_response",
- "type": "text",
- "primaryKey": false,
- "notNull": false
- },
- "vote_count": {
- "name": "vote_count",
- "type": "integer",
- "primaryKey": false,
- "notNull": true,
- "default": 0
- },
- "device_info": {
- "name": "device_info",
- "type": "jsonb",
- "primaryKey": false,
- "notNull": false
- },
- "created_at": {
- "name": "created_at",
- "type": "timestamp with time zone",
- "primaryKey": false,
- "notNull": true,
- "default": "now()"
- },
- "updated_at": {
- "name": "updated_at",
- "type": "timestamp with time zone",
- "primaryKey": false,
- "notNull": true,
- "default": "now()"
- },
- "published_at": {
- "name": "published_at",
- "type": "timestamp with time zone",
- "primaryKey": false,
- "notNull": false
- },
- "completed_at": {
- "name": "completed_at",
- "type": "timestamp with time zone",
- "primaryKey": false,
- "notNull": false
- }
- },
- "indexes": {
- "feedback_user_idx": {
- "name": "feedback_user_idx",
- "columns": [
- {
- "expression": "user_id",
- "isExpression": false,
- "asc": true,
- "nulls": "last"
- }
- ],
- "isUnique": false,
- "concurrently": false,
- "method": "btree",
- "with": {}
- },
- "feedback_app_idx": {
- "name": "feedback_app_idx",
- "columns": [
- {
- "expression": "app_id",
- "isExpression": false,
- "asc": true,
- "nulls": "last"
- }
- ],
- "isUnique": false,
- "concurrently": false,
- "method": "btree",
- "with": {}
- },
- "feedback_public_idx": {
- "name": "feedback_public_idx",
- "columns": [
- {
- "expression": "is_public",
- "isExpression": false,
- "asc": true,
- "nulls": "last"
- }
- ],
- "isUnique": false,
- "concurrently": false,
- "method": "btree",
- "with": {}
- },
- "feedback_status_idx": {
- "name": "feedback_status_idx",
- "columns": [
- {
- "expression": "status",
- "isExpression": false,
- "asc": true,
- "nulls": "last"
- }
- ],
- "isUnique": false,
- "concurrently": false,
- "method": "btree",
- "with": {}
- },
- "feedback_created_at_idx": {
- "name": "feedback_created_at_idx",
- "columns": [
- {
- "expression": "created_at",
- "isExpression": false,
- "asc": true,
- "nulls": "last"
- }
- ],
- "isUnique": false,
- "concurrently": false,
- "method": "btree",
- "with": {}
- }
- },
- "foreignKeys": {
- "user_feedback_user_id_users_id_fk": {
- "name": "user_feedback_user_id_users_id_fk",
- "tableFrom": "user_feedback",
- "tableTo": "users",
- "schemaTo": "auth",
- "columnsFrom": ["user_id"],
- "columnsTo": ["id"],
- "onDelete": "cascade",
- "onUpdate": "no action"
- }
- },
- "compositePrimaryKeys": {},
- "uniqueConstraints": {},
- "policies": {},
- "checkConstraints": {},
- "isRLSEnabled": false
- }
- },
- "enums": {
- "public.user_role": {
- "name": "user_role",
- "schema": "public",
- "values": ["user", "admin", "service"]
- },
- "public.transaction_status": {
- "name": "transaction_status",
- "schema": "public",
- "values": ["pending", "completed", "failed", "cancelled"]
- },
- "public.transaction_type": {
- "name": "transaction_type",
- "schema": "public",
- "values": ["purchase", "usage", "refund", "bonus", "expiry", "adjustment"]
- },
- "public.feedback_category": {
- "name": "feedback_category",
- "schema": "public",
- "values": ["bug", "feature", "improvement", "question", "other"]
- },
- "public.feedback_status": {
- "name": "feedback_status",
- "schema": "public",
- "values": ["submitted", "under_review", "planned", "in_progress", "completed", "declined"]
- }
- },
- "schemas": {
- "auth": "auth",
- "credits": "credits",
- "feedback": "feedback"
- },
- "sequences": {},
- "roles": {},
- "policies": {},
- "views": {},
- "_meta": {
- "columns": {},
- "schemas": {},
- "tables": {}
- }
-}