From 1b31f3194da0fa28ebf946d6bad320c3e3361e2f Mon Sep 17 00:00:00 2001 From: Marios Antonoudiou Date: Fri, 6 Mar 2026 18:49:41 +0200 Subject: [PATCH] Move to sqlite off cloud --- .dockerignore | 10 + .gitignore | 2 + Dockerfile | 28 +++ agents.md | 28 +++ bun.lock | 20 +- drizzle.config.ts | 2 +- package.json | 3 +- server/api/preferences.put.ts | 8 +- server/api/tasks/generate-content.post.ts | 30 +-- server/api/tasks/remove-athlete.post.ts | 9 - .../migrations/0000_living_oracle.sql | 28 +++ .../migrations/0000_slim_blonde_phantom.sql | 29 --- .../migrations/0001_smooth_jazinda.sql | 1 - .../migrations/0002_many_marauders.sql | 1 - .../migrations/meta/0000_snapshot.json | 172 +++++++------- .../migrations/meta/0001_snapshot.json | 216 ------------------ .../migrations/meta/0002_snapshot.json | 216 ------------------ server/database/migrations/meta/_journal.json | 24 +- server/database/schema.ts | 44 ++-- server/plugins/migrations.ts | 12 + server/routes/auth/strava.ts | 2 +- server/utils/create-content.ts | 27 +-- server/utils/drizzle.ts | 9 +- 23 files changed, 266 insertions(+), 655 deletions(-) create mode 100644 .dockerignore create mode 100644 Dockerfile create mode 100644 agents.md create mode 100644 server/database/migrations/0000_living_oracle.sql delete mode 100644 server/database/migrations/0000_slim_blonde_phantom.sql delete mode 100644 server/database/migrations/0001_smooth_jazinda.sql delete mode 100644 server/database/migrations/0002_many_marauders.sql delete mode 100644 server/database/migrations/meta/0001_snapshot.json delete mode 100644 server/database/migrations/meta/0002_snapshot.json create mode 100644 server/plugins/migrations.ts diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..67775b2 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,10 @@ +.git +.gitignore +.nuxt +.output +node_modules +*.md +README* +.env +.env.* +!.env.example diff --git a/.gitignore b/.gitignore index 9c6ff1c..4ec2b8a 100644 --- a/.gitignore +++ b/.gitignore @@ -24,3 +24,5 @@ logs .env.* !.env.example .vercel + +tmp/ diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..2fad138 --- /dev/null +++ b/Dockerfile @@ -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"] diff --git a/agents.md b/agents.md new file mode 100644 index 0000000..9a4b773 --- /dev/null +++ b/agents.md @@ -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 diff --git a/bun.lock b/bun.lock index 356e10d..efc014c 100644 --- a/bun.lock +++ b/bun.lock @@ -13,6 +13,7 @@ "@vee-validate/nuxt": "^4.15.1", "@vercel/functions": "^2.2.8", "@vueuse/nuxt": "^13.6.0", + "csv-parse": "^6.1.0", "destr": "^2.0.5", "drizzle-orm": "^0.44.4", "nuxt": "^4.0.3", @@ -28,6 +29,7 @@ "devDependencies": { "@iconify-json/heroicons": "^1.2.3", "@iconify-json/lucide": "^1.2.95", + "@types/bun": "^1.3.10", "drizzle-kit": "^0.31.4", "typescript": "^5.9.2", "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=="], + "@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/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=="], + "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=="], "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=="], + "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=="], "bytes": ["bytes@3.1.2", "", {}, "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg=="], @@ -857,6 +865,8 @@ "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=="], "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=="], - "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=="], @@ -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=="], + "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/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=="], + "global-directory/ini": ["ini@4.1.1", "", {}, "sha512-QQnnxNyfvmHFIsj7gkPcYymR8Jdw/o7mp5ZFihxn6h8Ci6fh3Dx4E1gPjpQEpIuPo9XVNY/ZUwh4BPMjGyL01g=="], + "h3/cookie-es": ["cookie-es@1.2.2", "", {}, "sha512-+W7VmiVINB+ywl1HGXJXmrqkOhpKrIiVZV6tQuV54ZyQC7MMuBt81Vc336GMLoHBq5hV/F9eXgt5Mnx0Rha5Fg=="], "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=="], - "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=="], "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=="], + "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=="], "caniuse-api/browserslist/electron-to-chromium": ["electron-to-chromium@1.5.199", "", {}, "sha512-3gl0S7zQd88kCAZRO/DnxtBKuhMO4h0EaQIN3YgZfV6+pW+5+bf2AdQeHNESCoaQqo/gjGVYEf2YM4O5HJQqpQ=="], diff --git a/drizzle.config.ts b/drizzle.config.ts index 2faaaa8..b765262 100644 --- a/drizzle.config.ts +++ b/drizzle.config.ts @@ -2,7 +2,7 @@ import { defineConfig } from "drizzle-kit"; import { get } from "radash"; export default defineConfig({ - dialect: "postgresql", + dialect: "sqlite", schema: "./server/database/schema.ts", out: "./server/database/migrations", dbCredentials: { diff --git a/package.json b/package.json index dd378d6..1b13d76 100644 --- a/package.json +++ b/package.json @@ -10,12 +10,10 @@ }, "dependencies": { "@formkit/tempo": "^0.1.2", - "@neondatabase/serverless": "^1.0.1", "@nuxt/icon": "1.15.0", "@nuxt/image": "^1.11.0", "@nuxt/ui": "3.3.0", "@vee-validate/nuxt": "^4.15.1", - "@vercel/functions": "^2.2.8", "@vueuse/nuxt": "^13.6.0", "destr": "^2.0.5", "drizzle-orm": "^0.44.4", @@ -32,6 +30,7 @@ "devDependencies": { "@iconify-json/heroicons": "^1.2.3", "@iconify-json/lucide": "^1.2.95", + "@types/bun": "^1.3.10", "drizzle-kit": "^0.31.4", "typescript": "^5.9.2", "vue-tsc": "^3.0.5" diff --git a/server/api/preferences.put.ts b/server/api/preferences.put.ts index f4b9d53..24fd90a 100644 --- a/server/api/preferences.put.ts +++ b/server/api/preferences.put.ts @@ -5,13 +5,13 @@ import { availableTones, availableUnits, } from "~/shared/constants"; -// + const bodySchema = z.strictObject({ enabled: z.boolean(), language: z.enum(availableLanguages), - units: z.enum(availableUnits), - tone: z.array(z.enum(availableTones)), - highlights: z.array(z.enum(availableHighlights)), + units: z.enum(availableUnits).default(availableUnits[1]), + tone: z.array(z.enum(availableTones)).default([]), + highlights: z.array(z.enum(availableHighlights)).default([]), }); export default defineEventHandler(async (event) => { diff --git a/server/api/tasks/generate-content.post.ts b/server/api/tasks/generate-content.post.ts index 080614f..a024390 100644 --- a/server/api/tasks/generate-content.post.ts +++ b/server/api/tasks/generate-content.post.ts @@ -32,25 +32,25 @@ export default defineEventHandler(async (event) => { user: user!, }).catch((err) => [err]); if (aiError) { - console.log(aiError.message); throw createError({ statusCode: 500, message: `OPENAI API: ${aiError.message}`, }); } - console.log(JSON.stringify(stravaRequestBody)); - - // await strava!(`activities/${body.object_id}`, { - // method: "PUT", - // body: { - // name: stravaRequestBody.name, - // description: stravaRequestBody.description, - // }, - // }).catch((error) => { - // throw createError({ - // statusCode: 500, - // message: `Strava API: ${error.message}`, - // }); - // }); + await strava!(`activities/${body.object_id}`, { + method: "PUT", + body: { + name: (stravaRequestBody.name as String).replaceAll("—", ","), + description: (stravaRequestBody.description as String).replaceAll( + "—", + ",", + ), + }, + }).catch((error) => { + throw createError({ + statusCode: 500, + message: `Strava API: ${error.message}`, + }); + }); }); diff --git a/server/api/tasks/remove-athlete.post.ts b/server/api/tasks/remove-athlete.post.ts index 955c58d..fb76fd1 100644 --- a/server/api/tasks/remove-athlete.post.ts +++ b/server/api/tasks/remove-athlete.post.ts @@ -4,8 +4,6 @@ import { eq } from "drizzle-orm"; export default defineEventHandler(async (event) => { await validateHookdeck(event); - const posthog = event.context.posthog; - const body = await readBody(event); const db = useDrizzle(); @@ -13,13 +11,6 @@ export default defineEventHandler(async (event) => { return; } - const user = await db.query.users.findFirst({ - where: (f, o) => o.eq(f.id, get(body, "object_id")), - with: { - preferences: true, - }, - }); - await db .delete(tables.users) .where(eq(tables.users.id, get(body, "object_id"))); diff --git a/server/database/migrations/0000_living_oracle.sql b/server/database/migrations/0000_living_oracle.sql new file mode 100644 index 0000000..c4e072f --- /dev/null +++ b/server/database/migrations/0000_living_oracle.sql @@ -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 +); diff --git a/server/database/migrations/0000_slim_blonde_phantom.sql b/server/database/migrations/0000_slim_blonde_phantom.sql deleted file mode 100644 index 60c722d..0000000 --- a/server/database/migrations/0000_slim_blonde_phantom.sql +++ /dev/null @@ -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; \ No newline at end of file diff --git a/server/database/migrations/0001_smooth_jazinda.sql b/server/database/migrations/0001_smooth_jazinda.sql deleted file mode 100644 index f4b50d6..0000000 --- a/server/database/migrations/0001_smooth_jazinda.sql +++ /dev/null @@ -1 +0,0 @@ -ALTER TABLE "users" ADD COLUMN "premium" boolean; \ No newline at end of file diff --git a/server/database/migrations/0002_many_marauders.sql b/server/database/migrations/0002_many_marauders.sql deleted file mode 100644 index 1e7cb0c..0000000 --- a/server/database/migrations/0002_many_marauders.sql +++ /dev/null @@ -1 +0,0 @@ -ALTER TABLE "users" ALTER COLUMN "premium" SET NOT NULL; \ No newline at end of file diff --git a/server/database/migrations/meta/0000_snapshot.json b/server/database/migrations/meta/0000_snapshot.json index 3937f7b..7eb9f8b 100644 --- a/server/database/migrations/meta/0000_snapshot.json +++ b/server/database/migrations/meta/0000_snapshot.json @@ -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", - "version": "7", - "dialect": "postgresql", "tables": { - "public.preferences": { + "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 - } + "autoincrement": true }, "user_id": { "name": "user_id", - "type": "integer", + "type": "numeric", "primaryKey": false, - "notNull": false + "notNull": false, + "autoincrement": false }, "data": { "name": "data", - "type": "jsonb", + "type": "text", "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": { "preferences_user_id_users_id_fk": { "name": "preferences_user_id_users_id_fk", "tableFrom": "preferences", "tableTo": "users", - "columnsFrom": ["user_id"], - "columnsTo": ["id"], + "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 + "uniqueConstraints": {}, + "checkConstraints": {} }, - "public.tokens": { + "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 - } + "autoincrement": true }, "user_id": { "name": "user_id", - "type": "integer", + "type": "numeric", "primaryKey": false, - "notNull": false + "notNull": false, + "autoincrement": false }, "refresh_token": { "name": "refresh_token", "type": "text", "primaryKey": false, - "notNull": false + "notNull": false, + "autoincrement": false }, "access_token": { "name": "access_token", "type": "text", "primaryKey": false, - "notNull": false + "notNull": false, + "autoincrement": false }, "expires_at": { "name": "expires_at", - "type": "timestamp", + "type": "integer", "primaryKey": false, "notNull": true, - "default": "now()" + "autoincrement": false + } + }, + "indexes": { + "tokens_user_id_unique": { + "name": "tokens_user_id_unique", + "columns": [ + "user_id" + ], + "isUnique": true } }, - "indexes": {}, "foreignKeys": { "tokens_user_id_users_id_fk": { "name": "tokens_user_id_users_id_fk", "tableFrom": "tokens", "tableTo": "users", - "columnsFrom": ["user_id"], - "columnsTo": ["id"], + "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 + "uniqueConstraints": {}, + "checkConstraints": {} }, - "public.users": { + "users": { "name": "users", - "schema": "", "columns": { "id": { "name": "id", - "type": "integer", + "type": "numeric", "primaryKey": true, - "notNull": true + "notNull": true, + "autoincrement": false }, "name": { "name": "name", "type": "text", "primaryKey": false, - "notNull": true + "notNull": true, + "autoincrement": false }, "avatar": { "name": "avatar", "type": "text", "primaryKey": false, - "notNull": true + "notNull": false, + "autoincrement": false }, "city": { "name": "city", "type": "text", "primaryKey": false, - "notNull": false + "notNull": false, + "autoincrement": false }, "country": { "name": "country", "type": "text", "primaryKey": false, - "notNull": false + "notNull": false, + "autoincrement": false }, "sex": { "name": "sex", "type": "text", "primaryKey": false, - "notNull": false + "notNull": false, + "autoincrement": false }, "weight": { "name": "weight", - "type": "numeric", + "type": "integer", "primaryKey": false, - "notNull": false + "notNull": false, + "autoincrement": false }, "created_at": { "name": "created_at", - "type": "timestamp", + "type": "integer", "primaryKey": false, "notNull": true, - "default": "now()" + "autoincrement": false } }, "indexes": {}, "foreignKeys": {}, "compositePrimaryKeys": {}, "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false + "checkConstraints": {} } }, - "enums": {}, - "schemas": {}, - "sequences": {}, - "roles": {}, - "policies": {}, "views": {}, + "enums": {}, "_meta": { - "columns": {}, "schemas": {}, - "tables": {} + "tables": {}, + "columns": {} + }, + "internal": { + "indexes": {} } -} +} \ No newline at end of file diff --git a/server/database/migrations/meta/0001_snapshot.json b/server/database/migrations/meta/0001_snapshot.json deleted file mode 100644 index b294563..0000000 --- a/server/database/migrations/meta/0001_snapshot.json +++ /dev/null @@ -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": {} - } -} diff --git a/server/database/migrations/meta/0002_snapshot.json b/server/database/migrations/meta/0002_snapshot.json deleted file mode 100644 index ad22a6d..0000000 --- a/server/database/migrations/meta/0002_snapshot.json +++ /dev/null @@ -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": {} - } -} diff --git a/server/database/migrations/meta/_journal.json b/server/database/migrations/meta/_journal.json index 9f30b00..0c0a47d 100644 --- a/server/database/migrations/meta/_journal.json +++ b/server/database/migrations/meta/_journal.json @@ -1,27 +1,13 @@ { "version": "7", - "dialect": "postgresql", + "dialect": "sqlite", "entries": [ { "idx": 0, - "version": "7", - "when": 1745335305323, - "tag": "0000_slim_blonde_phantom", - "breakpoints": true - }, - { - "idx": 1, - "version": "7", - "when": 1747547391071, - "tag": "0001_smooth_jazinda", - "breakpoints": true - }, - { - "idx": 2, - "version": "7", - "when": 1747564780270, - "tag": "0002_many_marauders", + "version": "6", + "when": 1772727834477, + "tag": "0000_living_oracle", "breakpoints": true } ] -} +} \ No newline at end of file diff --git a/server/database/schema.ts b/server/database/schema.ts index f0d5a5e..5d933c5 100644 --- a/server/database/schema.ts +++ b/server/database/schema.ts @@ -1,34 +1,27 @@ import { relations } from "drizzle-orm"; -import { - pgTable, - text, - integer, - numeric, - timestamp, - jsonb, -} from "drizzle-orm/pg-core"; +import { sqliteTable, text, integer, numeric } from "drizzle-orm/sqlite-core"; -export const users = pgTable("users", { - id: integer("id").primaryKey(), +export const users = sqliteTable("users", { + id: numeric("id").primaryKey(), name: text("name").notNull(), - avatar: text("avatar").notNull(), + avatar: text("avatar"), city: text("city"), country: text("country"), sex: text("sex"), - weight: numeric("weight", { - mode: "number", - }), - createdAt: timestamp("created_at").notNull().defaultNow(), + weight: integer("weight"), + createdAt: integer("created_at", { mode: "timestamp" }) + .notNull() + .$defaultFn(() => new Date()), }); -export const preferences = pgTable("preferences", { - id: integer("id").primaryKey().generatedAlwaysAsIdentity(), - userId: integer("user_id") +export const preferences = sqliteTable("preferences", { + id: integer("id").primaryKey({ autoIncrement: true }), + userId: numeric("user_id") .references(() => users.id, { onDelete: "cascade", }) .unique(), - data: jsonb("data") + data: text("data", { mode: "json" }) .$type<{ enabled: boolean; language: string; @@ -45,19 +38,20 @@ export const preferences = pgTable("preferences", { })), }); -export const tokens = pgTable("tokens", { - id: integer("id").primaryKey().generatedAlwaysAsIdentity(), - userId: integer("user_id") +export const tokens = sqliteTable("tokens", { + id: integer("id").primaryKey({ autoIncrement: true }), + userId: numeric("user_id") .references(() => users.id, { onDelete: "cascade", }) .unique(), refreshToken: text("refresh_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 }) => ({ tokens: one(tokens, { 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, { fields: [preferences.userId], references: [users.id], diff --git a/server/plugins/migrations.ts b/server/plugins/migrations.ts new file mode 100644 index 0000000..ad13c4e --- /dev/null +++ b/server/plugins/migrations.ts @@ -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"); +}); diff --git a/server/routes/auth/strava.ts b/server/routes/auth/strava.ts index 1f37204..b039cc8 100644 --- a/server/routes/auth/strava.ts +++ b/server/routes/auth/strava.ts @@ -30,7 +30,7 @@ export default defineOAuthStravaEventHandler({ city: auth.user.city, country: auth.user.country, sex: auth.user.sex, - weight: auth.user.weight, + // weight: auth.user.weight, avatar: auth.user.profile, }; diff --git a/server/utils/create-content.ts b/server/utils/create-content.ts index 293b4a2..5f9ccfa 100644 --- a/server/utils/create-content.ts +++ b/server/utils/create-content.ts @@ -101,33 +101,16 @@ export const createActivityContent = async ({ : draw(user.preferences.data!.highlights!); const length = match({ tone }) - .with({ tone: "Minimalist" }, () => "short") - .otherwise(() => draw(["short", "medium", "a-little-more-than-medium"])); + .with({ tone: "Minimalist" }, () => "very short") + .otherwise(() => draw(["very short", "short"])); 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} Unit system: ${user?.preferences.data!.units} - Activity notes: - Distance is in meters, time is in seconds, don't include average speed. - Convert time to hours or minutes, whatever's closer. - 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. + Tone: ${tone} + Highlight: ${highlight} + Description length: ${length} The activity data in json format from strava: ${stringifyActivity({ activity: currentActivity })} diff --git a/server/utils/drizzle.ts b/server/utils/drizzle.ts index 5629164..42f7c41 100644 --- a/server/utils/drizzle.ts +++ b/server/utils/drizzle.ts @@ -1,5 +1,10 @@ -import { drizzle } from "drizzle-orm/neon-http"; -export { sql, eq, and, or } from "drizzle-orm"; +import { drizzle } from "drizzle-orm/bun-sqlite"; +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";