Move to sqlite off cloud

This commit is contained in:
2026-03-06 18:49:41 +02:00
parent 27b7d87e68
commit 1b31f3194d
23 changed files with 266 additions and 655 deletions

10
.dockerignore Normal file
View File

@@ -0,0 +1,10 @@
.git
.gitignore
.nuxt
.output
node_modules
*.md
README*
.env
.env.*
!.env.example

2
.gitignore vendored
View File

@@ -24,3 +24,5 @@ logs
.env.* .env.*
!.env.example !.env.example
.vercel .vercel
tmp/

28
Dockerfile Normal file
View File

@@ -0,0 +1,28 @@
FROM oven/bun:1-alpine AS base
FROM base AS deps
WORKDIR /app
COPY package.json bun.lock* ./
RUN bun install --frozen-lockfile
FROM base AS builder
WORKDIR /app
COPY --from=deps /app/node_modules ./node_modules
COPY . .
RUN bun run build
FROM base AS runner
WORKDIR /app
ENV NODE_ENV=production
RUN addgroup --system --gid 1001 nodejs
RUN adduser --system --uid 1001 nuxt
COPY --from=builder /app/.output ./.output
COPY --from=builder /app/package.json ./
USER nuxt
EXPOSE 3000
CMD ["bun", "x", "nuxt", "start"]

28
agents.md Normal file
View File

@@ -0,0 +1,28 @@
# Agents
## Commands
```bash
# Development
bun run dev
# Build
bun run build
# Generate static site
bun run generate
# Type checking
bunx vue-tsc --noEmit
# Database
bunx drizzle-kit push
bunx drizzle-kit studio
```
## Project Info
- Nuxt 4
- Uses bun as package manager and runtime
- Database: Drizzle ORM with Bun's sqlite
- UI: @nuxt/ui v3

View File

@@ -13,6 +13,7 @@
"@vee-validate/nuxt": "^4.15.1", "@vee-validate/nuxt": "^4.15.1",
"@vercel/functions": "^2.2.8", "@vercel/functions": "^2.2.8",
"@vueuse/nuxt": "^13.6.0", "@vueuse/nuxt": "^13.6.0",
"csv-parse": "^6.1.0",
"destr": "^2.0.5", "destr": "^2.0.5",
"drizzle-orm": "^0.44.4", "drizzle-orm": "^0.44.4",
"nuxt": "^4.0.3", "nuxt": "^4.0.3",
@@ -28,6 +29,7 @@
"devDependencies": { "devDependencies": {
"@iconify-json/heroicons": "^1.2.3", "@iconify-json/heroicons": "^1.2.3",
"@iconify-json/lucide": "^1.2.95", "@iconify-json/lucide": "^1.2.95",
"@types/bun": "^1.3.10",
"drizzle-kit": "^0.31.4", "drizzle-kit": "^0.31.4",
"typescript": "^5.9.2", "typescript": "^5.9.2",
"vue-tsc": "^3.0.5", "vue-tsc": "^3.0.5",
@@ -581,6 +583,8 @@
"@tybys/wasm-util": ["@tybys/wasm-util@0.10.1", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg=="], "@tybys/wasm-util": ["@tybys/wasm-util@0.10.1", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg=="],
"@types/bun": ["@types/bun@1.3.10", "", { "dependencies": { "bun-types": "1.3.10" } }, "sha512-0+rlrUrOrTSskibryHbvQkDOWRJwJZqZlxrUs1u4oOoTln8+WIXBPmAuCF35SWB2z4Zl3E84Nl/D0P7803nigQ=="],
"@types/bytes": ["@types/bytes@3.1.5", "", {}, "sha512-VgZkrJckypj85YxEsEavcMmmSOIzkUHqWmM4CCyia5dc54YwsXzJ5uT4fYxBQNEXx+oF1krlhgCbvfubXqZYsQ=="], "@types/bytes": ["@types/bytes@3.1.5", "", {}, "sha512-VgZkrJckypj85YxEsEavcMmmSOIzkUHqWmM4CCyia5dc54YwsXzJ5uT4fYxBQNEXx+oF1krlhgCbvfubXqZYsQ=="],
"@types/estree": ["@types/estree@1.0.8", "", {}, "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w=="], "@types/estree": ["@types/estree@1.0.8", "", {}, "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w=="],
@@ -729,6 +733,8 @@
"baseline-browser-mapping": ["baseline-browser-mapping@2.10.0", "", { "bin": { "baseline-browser-mapping": "dist/cli.cjs" } }, "sha512-lIyg0szRfYbiy67j9KN8IyeD7q7hcmqnJ1ddWmNt19ItGpNN64mnllmxUNFIOdOm6by97jlL6wfpTTJrmnjWAA=="], "baseline-browser-mapping": ["baseline-browser-mapping@2.10.0", "", { "bin": { "baseline-browser-mapping": "dist/cli.cjs" } }, "sha512-lIyg0szRfYbiy67j9KN8IyeD7q7hcmqnJ1ddWmNt19ItGpNN64mnllmxUNFIOdOm6by97jlL6wfpTTJrmnjWAA=="],
"better-sqlite3": ["better-sqlite3@12.6.2", "", { "dependencies": { "bindings": "^1.5.0", "prebuild-install": "^7.1.1" } }, "sha512-8VYKM3MjCa9WcaSAI3hzwhmyHVlH8tiGFwf0RlTsZPWJ1I5MkzjiudCo4KC4DxOaL/53A5B1sI/IbldNFDbsKA=="],
"binary-extensions": ["binary-extensions@2.3.0", "", {}, "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw=="], "binary-extensions": ["binary-extensions@2.3.0", "", {}, "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw=="],
"bindings": ["bindings@1.5.0", "", { "dependencies": { "file-uri-to-path": "1.0.0" } }, "sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ=="], "bindings": ["bindings@1.5.0", "", { "dependencies": { "file-uri-to-path": "1.0.0" } }, "sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ=="],
@@ -755,6 +761,8 @@
"buffer-from": ["buffer-from@1.1.2", "", {}, "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ=="], "buffer-from": ["buffer-from@1.1.2", "", {}, "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ=="],
"bun-types": ["bun-types@1.3.10", "", { "dependencies": { "@types/node": "*" } }, "sha512-tcpfCCl6XWo6nCVnpcVrxQ+9AYN1iqMIzgrSKYMB/fjLtV2eyAVEg7AxQJuCq/26R6HpKWykQXuSOq/21RYcbg=="],
"bundle-name": ["bundle-name@4.1.0", "", { "dependencies": { "run-applescript": "7.0.0" } }, "sha512-tjwM5exMg6BGRI+kNmTntNsvdZS1X8BFYS6tnJ2hdH0kVxM6/eVZ2xy+FqStSWvYmtfFMDLIxurorHwDKfDz5Q=="], "bundle-name": ["bundle-name@4.1.0", "", { "dependencies": { "run-applescript": "7.0.0" } }, "sha512-tjwM5exMg6BGRI+kNmTntNsvdZS1X8BFYS6tnJ2hdH0kVxM6/eVZ2xy+FqStSWvYmtfFMDLIxurorHwDKfDz5Q=="],
"bytes": ["bytes@3.1.2", "", {}, "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg=="], "bytes": ["bytes@3.1.2", "", {}, "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg=="],
@@ -857,6 +865,8 @@
"csstype": ["csstype@3.2.3", "", {}, "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ=="], "csstype": ["csstype@3.2.3", "", {}, "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ=="],
"csv-parse": ["csv-parse@6.1.0", "", {}, "sha512-CEE+jwpgLn+MmtCpVcPtiCZpVtB6Z2OKPTr34pycYYoL7sxdOkXDdQ4lRiw6ioC0q6BLqhc6cKweCVvral8yhw=="],
"data-uri-to-buffer": ["data-uri-to-buffer@4.0.1", "", {}, "sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A=="], "data-uri-to-buffer": ["data-uri-to-buffer@4.0.1", "", {}, "sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A=="],
"db0": ["db0@0.3.4", "", { "peerDependencies": { "@electric-sql/pglite": "*", "@libsql/client": "*", "better-sqlite3": "*", "drizzle-orm": "*", "mysql2": "*", "sqlite3": "*" }, "optionalPeers": ["@electric-sql/pglite", "@libsql/client", "better-sqlite3", "drizzle-orm", "mysql2", "sqlite3"] }, "sha512-RiXXi4WaNzPTHEOu8UPQKMooIbqOEyqA1t7Z6MsdxSCeb8iUC9ko3LcmsLmeUt2SM5bctfArZKkRQggKZz7JNw=="], "db0": ["db0@0.3.4", "", { "peerDependencies": { "@electric-sql/pglite": "*", "@libsql/client": "*", "better-sqlite3": "*", "drizzle-orm": "*", "mysql2": "*", "sqlite3": "*" }, "optionalPeers": ["@electric-sql/pglite", "@libsql/client", "better-sqlite3", "drizzle-orm", "mysql2", "sqlite3"] }, "sha512-RiXXi4WaNzPTHEOu8UPQKMooIbqOEyqA1t7Z6MsdxSCeb8iUC9ko3LcmsLmeUt2SM5bctfArZKkRQggKZz7JNw=="],
@@ -1095,7 +1105,7 @@
"inherits": ["inherits@2.0.4", "", {}, "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="], "inherits": ["inherits@2.0.4", "", {}, "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="],
"ini": ["ini@4.1.1", "", {}, "sha512-QQnnxNyfvmHFIsj7gkPcYymR8Jdw/o7mp5ZFihxn6h8Ci6fh3Dx4E1gPjpQEpIuPo9XVNY/ZUwh4BPMjGyL01g=="], "ini": ["ini@1.3.8", "", {}, "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew=="],
"ioredis": ["ioredis@5.10.0", "", { "dependencies": { "@ioredis/commands": "1.5.1", "cluster-key-slot": "^1.1.0", "debug": "^4.3.4", "denque": "^2.1.0", "lodash.defaults": "^4.2.0", "lodash.isarguments": "^3.1.0", "redis-errors": "^1.2.0", "redis-parser": "^3.0.0", "standard-as-callback": "^2.1.0" } }, "sha512-HVBe9OFuqs+Z6n64q09PQvP1/R4Bm+30PAyyD4wIEqssh3v9L21QjCVk4kRLucMBcDokJTcLjsGeVRlq/nH6DA=="], "ioredis": ["ioredis@5.10.0", "", { "dependencies": { "@ioredis/commands": "1.5.1", "cluster-key-slot": "^1.1.0", "debug": "^4.3.4", "denque": "^2.1.0", "lodash.defaults": "^4.2.0", "lodash.isarguments": "^3.1.0", "redis-errors": "^1.2.0", "redis-parser": "^3.0.0", "standard-as-callback": "^2.1.0" } }, "sha512-HVBe9OFuqs+Z6n64q09PQvP1/R4Bm+30PAyyD4wIEqssh3v9L21QjCVk4kRLucMBcDokJTcLjsGeVRlq/nH6DA=="],
@@ -1999,6 +2009,8 @@
"bl/readable-stream": ["readable-stream@3.6.2", "", { "dependencies": { "inherits": "^2.0.3", "string_decoder": "^1.1.1", "util-deprecate": "^1.0.1" } }, "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA=="], "bl/readable-stream": ["readable-stream@3.6.2", "", { "dependencies": { "inherits": "^2.0.3", "string_decoder": "^1.1.1", "util-deprecate": "^1.0.1" } }, "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA=="],
"bun-types/@types/node": ["@types/node@24.2.1", "", { "dependencies": { "undici-types": "7.10.0" } }, "sha512-DRh5K+ka5eJic8CjH7td8QpYEV6Zo10gfRkjHCO3weqZHWDtAaSTFtl4+VMqOJ4N5jcuhZ9/l+yy8rVgw7BQeQ=="],
"c12/giget": ["giget@2.0.0", "", { "dependencies": { "citty": "0.1.6", "consola": "3.4.2", "defu": "6.1.4", "node-fetch-native": "1.6.7", "nypm": "0.6.1", "pathe": "2.0.3" }, "bin": { "giget": "dist/cli.mjs" } }, "sha512-L5bGsVkxJbJgdnwyuheIunkGatUF/zssUoxxjACCseZYAVbaqdh9Tsmmlkl8vYan09H7sbvKt4pS8GqKLBrEzA=="], "c12/giget": ["giget@2.0.0", "", { "dependencies": { "citty": "0.1.6", "consola": "3.4.2", "defu": "6.1.4", "node-fetch-native": "1.6.7", "nypm": "0.6.1", "pathe": "2.0.3" }, "bin": { "giget": "dist/cli.mjs" } }, "sha512-L5bGsVkxJbJgdnwyuheIunkGatUF/zssUoxxjACCseZYAVbaqdh9Tsmmlkl8vYan09H7sbvKt4pS8GqKLBrEzA=="],
"c12/rc9": ["rc9@2.1.2", "", { "dependencies": { "defu": "6.1.4", "destr": "2.0.5" } }, "sha512-btXCnMmRIBINM2LDZoEmOogIZU7Qe7zn4BpomSKZ/ykbLObuBdvG+mFq11DL6fjH1DRwHhrlgtYWG96bJiC7Cg=="], "c12/rc9": ["rc9@2.1.2", "", { "dependencies": { "defu": "6.1.4", "destr": "2.0.5" } }, "sha512-btXCnMmRIBINM2LDZoEmOogIZU7Qe7zn4BpomSKZ/ykbLObuBdvG+mFq11DL6fjH1DRwHhrlgtYWG96bJiC7Cg=="],
@@ -2019,6 +2031,8 @@
"find-up/unicorn-magic": ["unicorn-magic@0.1.0", "", {}, "sha512-lRfVq8fE8gz6QMBuDM6a+LO3IAzTi05H6gCVaUpir2E1Rwpo4ZUog45KpNXKC/Mn3Yb9UDuHumeFTo9iV/D9FQ=="], "find-up/unicorn-magic": ["unicorn-magic@0.1.0", "", {}, "sha512-lRfVq8fE8gz6QMBuDM6a+LO3IAzTi05H6gCVaUpir2E1Rwpo4ZUog45KpNXKC/Mn3Yb9UDuHumeFTo9iV/D9FQ=="],
"global-directory/ini": ["ini@4.1.1", "", {}, "sha512-QQnnxNyfvmHFIsj7gkPcYymR8Jdw/o7mp5ZFihxn6h8Ci6fh3Dx4E1gPjpQEpIuPo9XVNY/ZUwh4BPMjGyL01g=="],
"h3/cookie-es": ["cookie-es@1.2.2", "", {}, "sha512-+W7VmiVINB+ywl1HGXJXmrqkOhpKrIiVZV6tQuV54ZyQC7MMuBt81Vc336GMLoHBq5hV/F9eXgt5Mnx0Rha5Fg=="], "h3/cookie-es": ["cookie-es@1.2.2", "", {}, "sha512-+W7VmiVINB+ywl1HGXJXmrqkOhpKrIiVZV6tQuV54ZyQC7MMuBt81Vc336GMLoHBq5hV/F9eXgt5Mnx0Rha5Fg=="],
"http-errors/statuses": ["statuses@2.0.1", "", {}, "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ=="], "http-errors/statuses": ["statuses@2.0.1", "", {}, "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ=="],
@@ -2087,8 +2101,6 @@
"prebuild-install/tar-fs": ["tar-fs@2.1.4", "", { "dependencies": { "chownr": "^1.1.1", "mkdirp-classic": "^0.5.2", "pump": "^3.0.0", "tar-stream": "^2.1.4" } }, "sha512-mDAjwmZdh7LTT6pNleZ05Yt65HC3E+NiQzl672vQG38jIrehtJk/J3mNwIg+vShQPcLF/LV7CMnDW6vjj6sfYQ=="], "prebuild-install/tar-fs": ["tar-fs@2.1.4", "", { "dependencies": { "chownr": "^1.1.1", "mkdirp-classic": "^0.5.2", "pump": "^3.0.0", "tar-stream": "^2.1.4" } }, "sha512-mDAjwmZdh7LTT6pNleZ05Yt65HC3E+NiQzl672vQG38jIrehtJk/J3mNwIg+vShQPcLF/LV7CMnDW6vjj6sfYQ=="],
"rc/ini": ["ini@1.3.8", "", {}, "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew=="],
"readdir-glob/minimatch": ["minimatch@5.1.6", "", { "dependencies": { "brace-expansion": "2.0.2" } }, "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g=="], "readdir-glob/minimatch": ["minimatch@5.1.6", "", { "dependencies": { "brace-expansion": "2.0.2" } }, "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g=="],
"reka-ui/@internationalized/date": ["@internationalized/date@3.8.2", "", { "dependencies": { "@swc/helpers": "0.5.17" } }, "sha512-/wENk7CbvLbkUvX1tu0mwq49CVkkWpkXubGel6birjRPyo6uQ4nQpnq5xZu823zRCwwn82zgHrvgF1vZyvmVgA=="], "reka-ui/@internationalized/date": ["@internationalized/date@3.8.2", "", { "dependencies": { "@swc/helpers": "0.5.17" } }, "sha512-/wENk7CbvLbkUvX1tu0mwq49CVkkWpkXubGel6birjRPyo6uQ4nQpnq5xZu823zRCwwn82zgHrvgF1vZyvmVgA=="],
@@ -2397,6 +2409,8 @@
"archiver-utils/glob/path-scurry": ["path-scurry@1.11.1", "", { "dependencies": { "lru-cache": "10.4.3", "minipass": "7.1.2" } }, "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA=="], "archiver-utils/glob/path-scurry": ["path-scurry@1.11.1", "", { "dependencies": { "lru-cache": "10.4.3", "minipass": "7.1.2" } }, "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA=="],
"bun-types/@types/node/undici-types": ["undici-types@7.10.0", "", {}, "sha512-t5Fy/nfn+14LuOc2KNYg75vZqClpAiqscVvMygNnlsHBFpSXdJaYtXMcdNLpl/Qvc3P2cB3s6lOV51nqsFq4ag=="],
"c12/giget/nypm": ["nypm@0.6.1", "", { "dependencies": { "citty": "0.1.6", "consola": "3.4.2", "pathe": "2.0.3", "pkg-types": "2.2.0", "tinyexec": "1.0.1" }, "bin": { "nypm": "dist/cli.mjs" } }, "sha512-hlacBiRiv1k9hZFiphPUkfSQ/ZfQzZDzC+8z0wL3lvDAOUu/2NnChkKuMoMjNur/9OpKuz2QsIeiPVN0xM5Q0w=="], "c12/giget/nypm": ["nypm@0.6.1", "", { "dependencies": { "citty": "0.1.6", "consola": "3.4.2", "pathe": "2.0.3", "pkg-types": "2.2.0", "tinyexec": "1.0.1" }, "bin": { "nypm": "dist/cli.mjs" } }, "sha512-hlacBiRiv1k9hZFiphPUkfSQ/ZfQzZDzC+8z0wL3lvDAOUu/2NnChkKuMoMjNur/9OpKuz2QsIeiPVN0xM5Q0w=="],
"caniuse-api/browserslist/electron-to-chromium": ["electron-to-chromium@1.5.199", "", {}, "sha512-3gl0S7zQd88kCAZRO/DnxtBKuhMO4h0EaQIN3YgZfV6+pW+5+bf2AdQeHNESCoaQqo/gjGVYEf2YM4O5HJQqpQ=="], "caniuse-api/browserslist/electron-to-chromium": ["electron-to-chromium@1.5.199", "", {}, "sha512-3gl0S7zQd88kCAZRO/DnxtBKuhMO4h0EaQIN3YgZfV6+pW+5+bf2AdQeHNESCoaQqo/gjGVYEf2YM4O5HJQqpQ=="],

View File

@@ -2,7 +2,7 @@ import { defineConfig } from "drizzle-kit";
import { get } from "radash"; import { get } from "radash";
export default defineConfig({ export default defineConfig({
dialect: "postgresql", dialect: "sqlite",
schema: "./server/database/schema.ts", schema: "./server/database/schema.ts",
out: "./server/database/migrations", out: "./server/database/migrations",
dbCredentials: { dbCredentials: {

View File

@@ -10,12 +10,10 @@
}, },
"dependencies": { "dependencies": {
"@formkit/tempo": "^0.1.2", "@formkit/tempo": "^0.1.2",
"@neondatabase/serverless": "^1.0.1",
"@nuxt/icon": "1.15.0", "@nuxt/icon": "1.15.0",
"@nuxt/image": "^1.11.0", "@nuxt/image": "^1.11.0",
"@nuxt/ui": "3.3.0", "@nuxt/ui": "3.3.0",
"@vee-validate/nuxt": "^4.15.1", "@vee-validate/nuxt": "^4.15.1",
"@vercel/functions": "^2.2.8",
"@vueuse/nuxt": "^13.6.0", "@vueuse/nuxt": "^13.6.0",
"destr": "^2.0.5", "destr": "^2.0.5",
"drizzle-orm": "^0.44.4", "drizzle-orm": "^0.44.4",
@@ -32,6 +30,7 @@
"devDependencies": { "devDependencies": {
"@iconify-json/heroicons": "^1.2.3", "@iconify-json/heroicons": "^1.2.3",
"@iconify-json/lucide": "^1.2.95", "@iconify-json/lucide": "^1.2.95",
"@types/bun": "^1.3.10",
"drizzle-kit": "^0.31.4", "drizzle-kit": "^0.31.4",
"typescript": "^5.9.2", "typescript": "^5.9.2",
"vue-tsc": "^3.0.5" "vue-tsc": "^3.0.5"

View File

@@ -5,13 +5,13 @@ import {
availableTones, availableTones,
availableUnits, availableUnits,
} from "~/shared/constants"; } from "~/shared/constants";
//
const bodySchema = z.strictObject({ const bodySchema = z.strictObject({
enabled: z.boolean(), enabled: z.boolean(),
language: z.enum(availableLanguages), language: z.enum(availableLanguages),
units: z.enum(availableUnits), units: z.enum(availableUnits).default(availableUnits[1]),
tone: z.array(z.enum(availableTones)), tone: z.array(z.enum(availableTones)).default([]),
highlights: z.array(z.enum(availableHighlights)), highlights: z.array(z.enum(availableHighlights)).default([]),
}); });
export default defineEventHandler(async (event) => { export default defineEventHandler(async (event) => {

View File

@@ -32,25 +32,25 @@ export default defineEventHandler(async (event) => {
user: user!, user: user!,
}).catch((err) => [err]); }).catch((err) => [err]);
if (aiError) { if (aiError) {
console.log(aiError.message);
throw createError({ throw createError({
statusCode: 500, statusCode: 500,
message: `OPENAI API: ${aiError.message}`, message: `OPENAI API: ${aiError.message}`,
}); });
} }
console.log(JSON.stringify(stravaRequestBody)); await strava!(`activities/${body.object_id}`, {
method: "PUT",
// await strava!(`activities/${body.object_id}`, { body: {
// method: "PUT", name: (stravaRequestBody.name as String).replaceAll("—", ","),
// body: { description: (stravaRequestBody.description as String).replaceAll(
// name: stravaRequestBody.name, "—",
// description: stravaRequestBody.description, ",",
// }, ),
// }).catch((error) => { },
// throw createError({ }).catch((error) => {
// statusCode: 500, throw createError({
// message: `Strava API: ${error.message}`, statusCode: 500,
// }); message: `Strava API: ${error.message}`,
// }); });
});
}); });

View File

@@ -4,8 +4,6 @@ import { eq } from "drizzle-orm";
export default defineEventHandler(async (event) => { export default defineEventHandler(async (event) => {
await validateHookdeck(event); await validateHookdeck(event);
const posthog = event.context.posthog;
const body = await readBody(event); const body = await readBody(event);
const db = useDrizzle(); const db = useDrizzle();
@@ -13,13 +11,6 @@ export default defineEventHandler(async (event) => {
return; return;
} }
const user = await db.query.users.findFirst({
where: (f, o) => o.eq(f.id, get(body, "object_id")),
with: {
preferences: true,
},
});
await db await db
.delete(tables.users) .delete(tables.users)
.where(eq(tables.users.id, get(body, "object_id"))); .where(eq(tables.users.id, get(body, "object_id")));

View File

@@ -0,0 +1,28 @@
CREATE TABLE `preferences` (
`id` integer PRIMARY KEY AUTOINCREMENT NOT NULL,
`user_id` numeric,
`data` text,
FOREIGN KEY (`user_id`) REFERENCES `users`(`id`) ON UPDATE no action ON DELETE cascade
);
--> statement-breakpoint
CREATE UNIQUE INDEX `preferences_user_id_unique` ON `preferences` (`user_id`);--> statement-breakpoint
CREATE TABLE `tokens` (
`id` integer PRIMARY KEY AUTOINCREMENT NOT NULL,
`user_id` numeric,
`refresh_token` text,
`access_token` text,
`expires_at` integer NOT NULL,
FOREIGN KEY (`user_id`) REFERENCES `users`(`id`) ON UPDATE no action ON DELETE cascade
);
--> statement-breakpoint
CREATE UNIQUE INDEX `tokens_user_id_unique` ON `tokens` (`user_id`);--> statement-breakpoint
CREATE TABLE `users` (
`id` numeric PRIMARY KEY NOT NULL,
`name` text NOT NULL,
`avatar` text,
`city` text,
`country` text,
`sex` text,
`weight` integer,
`created_at` integer NOT NULL
);

View File

@@ -1,29 +0,0 @@
CREATE TABLE "preferences" (
"id" integer PRIMARY KEY GENERATED ALWAYS AS IDENTITY (sequence name "preferences_id_seq" INCREMENT BY 1 MINVALUE 1 MAXVALUE 2147483647 START WITH 1 CACHE 1),
"user_id" integer,
"data" jsonb,
CONSTRAINT "preferences_user_id_unique" UNIQUE("user_id")
);
--> statement-breakpoint
CREATE TABLE "tokens" (
"id" integer PRIMARY KEY GENERATED ALWAYS AS IDENTITY (sequence name "tokens_id_seq" INCREMENT BY 1 MINVALUE 1 MAXVALUE 2147483647 START WITH 1 CACHE 1),
"user_id" integer,
"refresh_token" text,
"access_token" text,
"expires_at" timestamp DEFAULT now() NOT NULL,
CONSTRAINT "tokens_user_id_unique" UNIQUE("user_id")
);
--> statement-breakpoint
CREATE TABLE "users" (
"id" integer PRIMARY KEY NOT NULL,
"name" text NOT NULL,
"avatar" text NOT NULL,
"city" text,
"country" text,
"sex" text,
"weight" numeric,
"created_at" timestamp DEFAULT now() NOT NULL
);
--> statement-breakpoint
ALTER TABLE "preferences" ADD CONSTRAINT "preferences_user_id_users_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."users"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "tokens" ADD CONSTRAINT "tokens_user_id_users_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."users"("id") ON DELETE cascade ON UPDATE no action;

View File

@@ -1 +0,0 @@
ALTER TABLE "users" ADD COLUMN "premium" boolean;

View File

@@ -1 +0,0 @@
ALTER TABLE "users" ALTER COLUMN "premium" SET NOT NULL;

View File

@@ -1,210 +1,204 @@
{ {
"id": "c8519a52-b999-48f3-b532-42a5678e3905", "version": "6",
"dialect": "sqlite",
"id": "d9537942-cba2-4af4-8396-533366512937",
"prevId": "00000000-0000-0000-0000-000000000000", "prevId": "00000000-0000-0000-0000-000000000000",
"version": "7",
"dialect": "postgresql",
"tables": { "tables": {
"public.preferences": { "preferences": {
"name": "preferences", "name": "preferences",
"schema": "",
"columns": { "columns": {
"id": { "id": {
"name": "id", "name": "id",
"type": "integer", "type": "integer",
"primaryKey": true, "primaryKey": true,
"notNull": true, "notNull": true,
"identity": { "autoincrement": true
"type": "always",
"name": "preferences_id_seq",
"schema": "public",
"increment": "1",
"startWith": "1",
"minValue": "1",
"maxValue": "2147483647",
"cache": "1",
"cycle": false
}
}, },
"user_id": { "user_id": {
"name": "user_id", "name": "user_id",
"type": "integer", "type": "numeric",
"primaryKey": false, "primaryKey": false,
"notNull": false "notNull": false,
"autoincrement": false
}, },
"data": { "data": {
"name": "data", "name": "data",
"type": "jsonb", "type": "text",
"primaryKey": false, "primaryKey": false,
"notNull": false "notNull": false,
"autoincrement": false
}
},
"indexes": {
"preferences_user_id_unique": {
"name": "preferences_user_id_unique",
"columns": [
"user_id"
],
"isUnique": true
} }
}, },
"indexes": {},
"foreignKeys": { "foreignKeys": {
"preferences_user_id_users_id_fk": { "preferences_user_id_users_id_fk": {
"name": "preferences_user_id_users_id_fk", "name": "preferences_user_id_users_id_fk",
"tableFrom": "preferences", "tableFrom": "preferences",
"tableTo": "users", "tableTo": "users",
"columnsFrom": ["user_id"], "columnsFrom": [
"columnsTo": ["id"], "user_id"
],
"columnsTo": [
"id"
],
"onDelete": "cascade", "onDelete": "cascade",
"onUpdate": "no action" "onUpdate": "no action"
} }
}, },
"compositePrimaryKeys": {}, "compositePrimaryKeys": {},
"uniqueConstraints": { "uniqueConstraints": {},
"preferences_user_id_unique": { "checkConstraints": {}
"name": "preferences_user_id_unique",
"nullsNotDistinct": false,
"columns": ["user_id"]
}
}, },
"policies": {}, "tokens": {
"checkConstraints": {},
"isRLSEnabled": false
},
"public.tokens": {
"name": "tokens", "name": "tokens",
"schema": "",
"columns": { "columns": {
"id": { "id": {
"name": "id", "name": "id",
"type": "integer", "type": "integer",
"primaryKey": true, "primaryKey": true,
"notNull": true, "notNull": true,
"identity": { "autoincrement": true
"type": "always",
"name": "tokens_id_seq",
"schema": "public",
"increment": "1",
"startWith": "1",
"minValue": "1",
"maxValue": "2147483647",
"cache": "1",
"cycle": false
}
}, },
"user_id": { "user_id": {
"name": "user_id", "name": "user_id",
"type": "integer", "type": "numeric",
"primaryKey": false, "primaryKey": false,
"notNull": false "notNull": false,
"autoincrement": false
}, },
"refresh_token": { "refresh_token": {
"name": "refresh_token", "name": "refresh_token",
"type": "text", "type": "text",
"primaryKey": false, "primaryKey": false,
"notNull": false "notNull": false,
"autoincrement": false
}, },
"access_token": { "access_token": {
"name": "access_token", "name": "access_token",
"type": "text", "type": "text",
"primaryKey": false, "primaryKey": false,
"notNull": false "notNull": false,
"autoincrement": false
}, },
"expires_at": { "expires_at": {
"name": "expires_at", "name": "expires_at",
"type": "timestamp", "type": "integer",
"primaryKey": false, "primaryKey": false,
"notNull": true, "notNull": true,
"default": "now()" "autoincrement": false
}
},
"indexes": {
"tokens_user_id_unique": {
"name": "tokens_user_id_unique",
"columns": [
"user_id"
],
"isUnique": true
} }
}, },
"indexes": {},
"foreignKeys": { "foreignKeys": {
"tokens_user_id_users_id_fk": { "tokens_user_id_users_id_fk": {
"name": "tokens_user_id_users_id_fk", "name": "tokens_user_id_users_id_fk",
"tableFrom": "tokens", "tableFrom": "tokens",
"tableTo": "users", "tableTo": "users",
"columnsFrom": ["user_id"], "columnsFrom": [
"columnsTo": ["id"], "user_id"
],
"columnsTo": [
"id"
],
"onDelete": "cascade", "onDelete": "cascade",
"onUpdate": "no action" "onUpdate": "no action"
} }
}, },
"compositePrimaryKeys": {}, "compositePrimaryKeys": {},
"uniqueConstraints": { "uniqueConstraints": {},
"tokens_user_id_unique": { "checkConstraints": {}
"name": "tokens_user_id_unique",
"nullsNotDistinct": false,
"columns": ["user_id"]
}
}, },
"policies": {}, "users": {
"checkConstraints": {},
"isRLSEnabled": false
},
"public.users": {
"name": "users", "name": "users",
"schema": "",
"columns": { "columns": {
"id": { "id": {
"name": "id", "name": "id",
"type": "integer", "type": "numeric",
"primaryKey": true, "primaryKey": true,
"notNull": true "notNull": true,
"autoincrement": false
}, },
"name": { "name": {
"name": "name", "name": "name",
"type": "text", "type": "text",
"primaryKey": false, "primaryKey": false,
"notNull": true "notNull": true,
"autoincrement": false
}, },
"avatar": { "avatar": {
"name": "avatar", "name": "avatar",
"type": "text", "type": "text",
"primaryKey": false, "primaryKey": false,
"notNull": true "notNull": false,
"autoincrement": false
}, },
"city": { "city": {
"name": "city", "name": "city",
"type": "text", "type": "text",
"primaryKey": false, "primaryKey": false,
"notNull": false "notNull": false,
"autoincrement": false
}, },
"country": { "country": {
"name": "country", "name": "country",
"type": "text", "type": "text",
"primaryKey": false, "primaryKey": false,
"notNull": false "notNull": false,
"autoincrement": false
}, },
"sex": { "sex": {
"name": "sex", "name": "sex",
"type": "text", "type": "text",
"primaryKey": false, "primaryKey": false,
"notNull": false "notNull": false,
"autoincrement": false
}, },
"weight": { "weight": {
"name": "weight", "name": "weight",
"type": "numeric", "type": "integer",
"primaryKey": false, "primaryKey": false,
"notNull": false "notNull": false,
"autoincrement": false
}, },
"created_at": { "created_at": {
"name": "created_at", "name": "created_at",
"type": "timestamp", "type": "integer",
"primaryKey": false, "primaryKey": false,
"notNull": true, "notNull": true,
"default": "now()" "autoincrement": false
} }
}, },
"indexes": {}, "indexes": {},
"foreignKeys": {}, "foreignKeys": {},
"compositePrimaryKeys": {}, "compositePrimaryKeys": {},
"uniqueConstraints": {}, "uniqueConstraints": {},
"policies": {}, "checkConstraints": {}
"checkConstraints": {},
"isRLSEnabled": false
} }
}, },
"enums": {},
"schemas": {},
"sequences": {},
"roles": {},
"policies": {},
"views": {}, "views": {},
"enums": {},
"_meta": { "_meta": {
"columns": {},
"schemas": {}, "schemas": {},
"tables": {} "tables": {},
"columns": {}
},
"internal": {
"indexes": {}
} }
} }

View File

@@ -1,216 +0,0 @@
{
"id": "912c6e36-57b1-4e7f-a29b-586b187b1c32",
"prevId": "c8519a52-b999-48f3-b532-42a5678e3905",
"version": "7",
"dialect": "postgresql",
"tables": {
"public.preferences": {
"name": "preferences",
"schema": "",
"columns": {
"id": {
"name": "id",
"type": "integer",
"primaryKey": true,
"notNull": true,
"identity": {
"type": "always",
"name": "preferences_id_seq",
"schema": "public",
"increment": "1",
"startWith": "1",
"minValue": "1",
"maxValue": "2147483647",
"cache": "1",
"cycle": false
}
},
"user_id": {
"name": "user_id",
"type": "integer",
"primaryKey": false,
"notNull": false
},
"data": {
"name": "data",
"type": "jsonb",
"primaryKey": false,
"notNull": false
}
},
"indexes": {},
"foreignKeys": {
"preferences_user_id_users_id_fk": {
"name": "preferences_user_id_users_id_fk",
"tableFrom": "preferences",
"tableTo": "users",
"columnsFrom": ["user_id"],
"columnsTo": ["id"],
"onDelete": "cascade",
"onUpdate": "no action"
}
},
"compositePrimaryKeys": {},
"uniqueConstraints": {
"preferences_user_id_unique": {
"name": "preferences_user_id_unique",
"nullsNotDistinct": false,
"columns": ["user_id"]
}
},
"policies": {},
"checkConstraints": {},
"isRLSEnabled": false
},
"public.tokens": {
"name": "tokens",
"schema": "",
"columns": {
"id": {
"name": "id",
"type": "integer",
"primaryKey": true,
"notNull": true,
"identity": {
"type": "always",
"name": "tokens_id_seq",
"schema": "public",
"increment": "1",
"startWith": "1",
"minValue": "1",
"maxValue": "2147483647",
"cache": "1",
"cycle": false
}
},
"user_id": {
"name": "user_id",
"type": "integer",
"primaryKey": false,
"notNull": false
},
"refresh_token": {
"name": "refresh_token",
"type": "text",
"primaryKey": false,
"notNull": false
},
"access_token": {
"name": "access_token",
"type": "text",
"primaryKey": false,
"notNull": false
},
"expires_at": {
"name": "expires_at",
"type": "timestamp",
"primaryKey": false,
"notNull": true,
"default": "now()"
}
},
"indexes": {},
"foreignKeys": {
"tokens_user_id_users_id_fk": {
"name": "tokens_user_id_users_id_fk",
"tableFrom": "tokens",
"tableTo": "users",
"columnsFrom": ["user_id"],
"columnsTo": ["id"],
"onDelete": "cascade",
"onUpdate": "no action"
}
},
"compositePrimaryKeys": {},
"uniqueConstraints": {
"tokens_user_id_unique": {
"name": "tokens_user_id_unique",
"nullsNotDistinct": false,
"columns": ["user_id"]
}
},
"policies": {},
"checkConstraints": {},
"isRLSEnabled": false
},
"public.users": {
"name": "users",
"schema": "",
"columns": {
"id": {
"name": "id",
"type": "integer",
"primaryKey": true,
"notNull": true
},
"name": {
"name": "name",
"type": "text",
"primaryKey": false,
"notNull": true
},
"avatar": {
"name": "avatar",
"type": "text",
"primaryKey": false,
"notNull": true
},
"city": {
"name": "city",
"type": "text",
"primaryKey": false,
"notNull": false
},
"country": {
"name": "country",
"type": "text",
"primaryKey": false,
"notNull": false
},
"sex": {
"name": "sex",
"type": "text",
"primaryKey": false,
"notNull": false
},
"premium": {
"name": "premium",
"type": "boolean",
"primaryKey": false,
"notNull": false
},
"weight": {
"name": "weight",
"type": "numeric",
"primaryKey": false,
"notNull": false
},
"created_at": {
"name": "created_at",
"type": "timestamp",
"primaryKey": false,
"notNull": true,
"default": "now()"
}
},
"indexes": {},
"foreignKeys": {},
"compositePrimaryKeys": {},
"uniqueConstraints": {},
"policies": {},
"checkConstraints": {},
"isRLSEnabled": false
}
},
"enums": {},
"schemas": {},
"sequences": {},
"roles": {},
"policies": {},
"views": {},
"_meta": {
"columns": {},
"schemas": {},
"tables": {}
}
}

View File

@@ -1,216 +0,0 @@
{
"id": "7b607167-551b-4aa3-8305-afe3c4d72ace",
"prevId": "912c6e36-57b1-4e7f-a29b-586b187b1c32",
"version": "7",
"dialect": "postgresql",
"tables": {
"public.preferences": {
"name": "preferences",
"schema": "",
"columns": {
"id": {
"name": "id",
"type": "integer",
"primaryKey": true,
"notNull": true,
"identity": {
"type": "always",
"name": "preferences_id_seq",
"schema": "public",
"increment": "1",
"startWith": "1",
"minValue": "1",
"maxValue": "2147483647",
"cache": "1",
"cycle": false
}
},
"user_id": {
"name": "user_id",
"type": "integer",
"primaryKey": false,
"notNull": false
},
"data": {
"name": "data",
"type": "jsonb",
"primaryKey": false,
"notNull": false
}
},
"indexes": {},
"foreignKeys": {
"preferences_user_id_users_id_fk": {
"name": "preferences_user_id_users_id_fk",
"tableFrom": "preferences",
"tableTo": "users",
"columnsFrom": ["user_id"],
"columnsTo": ["id"],
"onDelete": "cascade",
"onUpdate": "no action"
}
},
"compositePrimaryKeys": {},
"uniqueConstraints": {
"preferences_user_id_unique": {
"name": "preferences_user_id_unique",
"nullsNotDistinct": false,
"columns": ["user_id"]
}
},
"policies": {},
"checkConstraints": {},
"isRLSEnabled": false
},
"public.tokens": {
"name": "tokens",
"schema": "",
"columns": {
"id": {
"name": "id",
"type": "integer",
"primaryKey": true,
"notNull": true,
"identity": {
"type": "always",
"name": "tokens_id_seq",
"schema": "public",
"increment": "1",
"startWith": "1",
"minValue": "1",
"maxValue": "2147483647",
"cache": "1",
"cycle": false
}
},
"user_id": {
"name": "user_id",
"type": "integer",
"primaryKey": false,
"notNull": false
},
"refresh_token": {
"name": "refresh_token",
"type": "text",
"primaryKey": false,
"notNull": false
},
"access_token": {
"name": "access_token",
"type": "text",
"primaryKey": false,
"notNull": false
},
"expires_at": {
"name": "expires_at",
"type": "timestamp",
"primaryKey": false,
"notNull": true,
"default": "now()"
}
},
"indexes": {},
"foreignKeys": {
"tokens_user_id_users_id_fk": {
"name": "tokens_user_id_users_id_fk",
"tableFrom": "tokens",
"tableTo": "users",
"columnsFrom": ["user_id"],
"columnsTo": ["id"],
"onDelete": "cascade",
"onUpdate": "no action"
}
},
"compositePrimaryKeys": {},
"uniqueConstraints": {
"tokens_user_id_unique": {
"name": "tokens_user_id_unique",
"nullsNotDistinct": false,
"columns": ["user_id"]
}
},
"policies": {},
"checkConstraints": {},
"isRLSEnabled": false
},
"public.users": {
"name": "users",
"schema": "",
"columns": {
"id": {
"name": "id",
"type": "integer",
"primaryKey": true,
"notNull": true
},
"name": {
"name": "name",
"type": "text",
"primaryKey": false,
"notNull": true
},
"avatar": {
"name": "avatar",
"type": "text",
"primaryKey": false,
"notNull": true
},
"city": {
"name": "city",
"type": "text",
"primaryKey": false,
"notNull": false
},
"country": {
"name": "country",
"type": "text",
"primaryKey": false,
"notNull": false
},
"sex": {
"name": "sex",
"type": "text",
"primaryKey": false,
"notNull": false
},
"premium": {
"name": "premium",
"type": "boolean",
"primaryKey": false,
"notNull": true
},
"weight": {
"name": "weight",
"type": "numeric",
"primaryKey": false,
"notNull": false
},
"created_at": {
"name": "created_at",
"type": "timestamp",
"primaryKey": false,
"notNull": true,
"default": "now()"
}
},
"indexes": {},
"foreignKeys": {},
"compositePrimaryKeys": {},
"uniqueConstraints": {},
"policies": {},
"checkConstraints": {},
"isRLSEnabled": false
}
},
"enums": {},
"schemas": {},
"sequences": {},
"roles": {},
"policies": {},
"views": {},
"_meta": {
"columns": {},
"schemas": {},
"tables": {}
}
}

View File

@@ -1,26 +1,12 @@
{ {
"version": "7", "version": "7",
"dialect": "postgresql", "dialect": "sqlite",
"entries": [ "entries": [
{ {
"idx": 0, "idx": 0,
"version": "7", "version": "6",
"when": 1745335305323, "when": 1772727834477,
"tag": "0000_slim_blonde_phantom", "tag": "0000_living_oracle",
"breakpoints": true
},
{
"idx": 1,
"version": "7",
"when": 1747547391071,
"tag": "0001_smooth_jazinda",
"breakpoints": true
},
{
"idx": 2,
"version": "7",
"when": 1747564780270,
"tag": "0002_many_marauders",
"breakpoints": true "breakpoints": true
} }
] ]

View File

@@ -1,34 +1,27 @@
import { relations } from "drizzle-orm"; import { relations } from "drizzle-orm";
import { import { sqliteTable, text, integer, numeric } from "drizzle-orm/sqlite-core";
pgTable,
text,
integer,
numeric,
timestamp,
jsonb,
} from "drizzle-orm/pg-core";
export const users = pgTable("users", { export const users = sqliteTable("users", {
id: integer("id").primaryKey(), id: numeric("id").primaryKey(),
name: text("name").notNull(), name: text("name").notNull(),
avatar: text("avatar").notNull(), avatar: text("avatar"),
city: text("city"), city: text("city"),
country: text("country"), country: text("country"),
sex: text("sex"), sex: text("sex"),
weight: numeric("weight", { weight: integer("weight"),
mode: "number", createdAt: integer("created_at", { mode: "timestamp" })
}), .notNull()
createdAt: timestamp("created_at").notNull().defaultNow(), .$defaultFn(() => new Date()),
}); });
export const preferences = pgTable("preferences", { export const preferences = sqliteTable("preferences", {
id: integer("id").primaryKey().generatedAlwaysAsIdentity(), id: integer("id").primaryKey({ autoIncrement: true }),
userId: integer("user_id") userId: numeric("user_id")
.references(() => users.id, { .references(() => users.id, {
onDelete: "cascade", onDelete: "cascade",
}) })
.unique(), .unique(),
data: jsonb("data") data: text("data", { mode: "json" })
.$type<{ .$type<{
enabled: boolean; enabled: boolean;
language: string; language: string;
@@ -45,19 +38,20 @@ export const preferences = pgTable("preferences", {
})), })),
}); });
export const tokens = pgTable("tokens", { export const tokens = sqliteTable("tokens", {
id: integer("id").primaryKey().generatedAlwaysAsIdentity(), id: integer("id").primaryKey({ autoIncrement: true }),
userId: integer("user_id") userId: numeric("user_id")
.references(() => users.id, { .references(() => users.id, {
onDelete: "cascade", onDelete: "cascade",
}) })
.unique(), .unique(),
refreshToken: text("refresh_token"), refreshToken: text("refresh_token"),
accessToken: text("access_token"), accessToken: text("access_token"),
expiresAt: timestamp("expires_at").notNull().defaultNow(), expiresAt: integer("expires_at", { mode: "timestamp" })
.notNull()
.$defaultFn(() => new Date()),
}); });
// Define relationships
export const usersRelations = relations(users, ({ one }) => ({ export const usersRelations = relations(users, ({ one }) => ({
tokens: one(tokens, { tokens: one(tokens, {
fields: [users.id], fields: [users.id],
@@ -69,7 +63,7 @@ export const usersRelations = relations(users, ({ one }) => ({
}), }),
})); }));
export const referencesRelations = relations(preferences, ({ one }) => ({ export const preferencesRelations = relations(preferences, ({ one }) => ({
user: one(users, { user: one(users, {
fields: [preferences.userId], fields: [preferences.userId],
references: [users.id], references: [users.id],

View File

@@ -0,0 +1,12 @@
import { migrate } from "drizzle-orm/bun-sqlite/migrator";
import { drizzle } from "drizzle-orm/bun-sqlite";
import * as schema from "../database/schema";
export default defineNitroPlugin(async () => {
const config = useRuntimeConfig();
const db = drizzle(config.databaseUrl, { schema });
console.log("Running database migrations...");
await migrate(db, { migrationsFolder: "./server/database/migrations" });
console.log("Database migrations complete");
});

View File

@@ -30,7 +30,7 @@ export default defineOAuthStravaEventHandler({
city: auth.user.city, city: auth.user.city,
country: auth.user.country, country: auth.user.country,
sex: auth.user.sex, sex: auth.user.sex,
weight: auth.user.weight, // weight: auth.user.weight,
avatar: auth.user.profile, avatar: auth.user.profile,
}; };

View File

@@ -101,33 +101,16 @@ export const createActivityContent = async ({
: draw(user.preferences.data!.highlights!); : draw(user.preferences.data!.highlights!);
const length = match({ tone }) const length = match({ tone })
.with({ tone: "Minimalist" }, () => "short") .with({ tone: "Minimalist" }, () => "very short")
.otherwise(() => draw(["short", "medium", "a-little-more-than-medium"])); .otherwise(() => draw(["very short", "short"]));
const prompt = ` const prompt = `
Generate a short title and a ${length}-lengthed description for my strava activity. Use my preferred language and unit system.
Use first person, as this will be posting for myself. Try to not exaggerate as I am using Strava often and I want my activites to be unique and easy to read. Don't use repeative language.
Depending the activity conditions and achievements, use one of the following tones to make things less boring:
${availableTones.join(", ")}. Accordingly, depending on the activity's conditions, highlight area exploration, athletic achievements, mood swings or weather conditions.
If there is nothing interesting to say, try making a mild joke or say an interesting fact about the route. Do not add fun facts if mentioned in the recent activities.
Take heart data, suffer score and weather into consideration if available, combine them to understand the effort. No need to mention suffer score every time.
Maybe comment if any interesting fact in comparison to previous activities.
NEVER use — symbol. Not for titles, not for descriptions.
Depending the length of the description, maybe add hashtags.
Language: ${user?.preferences.data!.language} Language: ${user?.preferences.data!.language}
Unit system: ${user?.preferences.data!.units} Unit system: ${user?.preferences.data!.units}
Activity notes: Tone: ${tone}
Distance is in meters, time is in seconds, don't include average speed. Highlight: ${highlight}
Convert time to hours or minutes, whatever's closer. Description length: ${length}
Convert distance to larger units when appropriate, we don't need accuracy. Better say almost 50 instead of 48.67 for example.
In the end of the description, add "${promo}" translated to my language.
The activity data in json format from strava: The activity data in json format from strava:
${stringifyActivity({ activity: currentActivity })} ${stringifyActivity({ activity: currentActivity })}

View File

@@ -1,5 +1,10 @@
import { drizzle } from "drizzle-orm/neon-http"; import { drizzle } from "drizzle-orm/bun-sqlite";
export { sql, eq, and, or } from "drizzle-orm"; import { eq as _eq, and as _and, or as _or, sql as _sql } from "drizzle-orm";
export const sql = _sql;
export const eq = _eq;
export const and = _and;
export const or = _or;
import * as schema from "../database/schema"; import * as schema from "../database/schema";