From b9f6836f1a78e9000ec045f922eb84f2c28b42ca Mon Sep 17 00:00:00 2001 From: averel10 Date: Fri, 13 Mar 2026 07:50:14 +0100 Subject: [PATCH] refactor: update README and Dockerfile, add drizzle migrations and instrumentation --- .dockerignore | 5 ++ Dockerfile | 101 ++++++++++++++++++++++++++ README.md | 8 +- next.config.ts | 3 +- package-lock.json | 62 +--------------- package.json | 4 +- src/components/CreateDatasetModal.tsx | 2 +- src/instrumentation.ts | 22 ++++++ 8 files changed, 135 insertions(+), 72 deletions(-) create mode 100644 .dockerignore create mode 100644 Dockerfile create mode 100644 src/instrumentation.ts diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..757a923 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,5 @@ +node_modules +.next +tmp +public/datasets +Dockerfile \ No newline at end of file diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..bf2db1f --- /dev/null +++ b/Dockerfile @@ -0,0 +1,101 @@ +# ============================================ +# Stage 1: Dependencies Installation Stage +# ============================================ + +# IMPORTANT: Node.js Version Maintenance +# This Dockerfile uses Node.js 24.13.0-slim, which was the latest LTS version at the time of writing. +# To ensure security and compatibility, regularly update the NODE_VERSION ARG to the latest LTS version. +ARG NODE_VERSION=24.13.0-slim + +FROM node:${NODE_VERSION} AS dependencies + +# Set working directory +WORKDIR /app + +# Copy package-related files first to leverage Docker's caching mechanism +COPY package.json package-lock.json .npmrc* ./ + +# Install project dependencies with frozen lockfile for reproducible builds +RUN --mount=type=cache,target=/root/.npm \ + npm ci --no-audit --no-fund + +# ============================================ +# Stage 2: Build Next.js application in standalone mode +# ============================================ + +FROM node:${NODE_VERSION} AS builder + +# Set working directory +WORKDIR /app + +# Copy project dependencies from dependencies stage +COPY --from=dependencies /app/node_modules ./node_modules + +# Copy application source code +COPY . . + +ENV NODE_ENV=production + +# Next.js collects completely anonymous telemetry data about general usage. +# Learn more here: https://nextjs.org/telemetry +# Uncomment the following line in case you want to disable telemetry during the build. +# ENV NEXT_TELEMETRY_DISABLED=1 + +# Build Next.js application +# If you want to speed up Docker rebuilds, you can cache the build artifacts +# by adding: --mount=type=cache,target=/app/.next/cache +# This caches the .next/cache directory across builds, but it also prevents +# .next/cache/fetch-cache from being included in the final image, meaning +# cached fetch responses from the build won't be available at runtime. +RUN npm run build +RUN npm run db:generate + +# ============================================ +# Stage 3: Run Next.js application +# ============================================ + +FROM node:${NODE_VERSION} AS runner + +# Set working directory +WORKDIR /app + +# Set production environment variables +ENV NODE_ENV=production +ENV PORT=3000 +ENV HOSTNAME="0.0.0.0" + +# Next.js collects completely anonymous telemetry data about general usage. +# Learn more here: https://nextjs.org/telemetry +# Uncomment the following line in case you want to disable telemetry during the run time. +# ENV NEXT_TELEMETRY_DISABLED=1 + +# Copy production assets +COPY --from=builder --chown=node:node /app/public ./public + +# Copy drizzle migrations (needed for instrumentation.ts to read SQL files) +COPY --from=builder --chown=node:node /app/drizzle ./drizzle + +# Set the correct permission for prerender cache +RUN mkdir -p .next +RUN chown node:node .next + +# Ensure the app directory is writable for database file creation +RUN chown -R node:node /app + +# Automatically leverage output traces to reduce image size +# https://nextjs.org/docs/advanced-features/output-file-tracing +COPY --from=builder --chown=node:node /app/.next/standalone ./ +COPY --from=builder --chown=node:node /app/.next/static ./.next/static + +# If you want to persist the fetch cache generated during the build so that +# cached responses are available immediately on startup, uncomment this line: +# COPY --from=builder --chown=node:node /app/.next/cache ./.next/cache + +# Switch to non-root user for security best practices +USER node + +# Expose port 3000 to allow HTTP traffic +EXPOSE 3000 + +# Start Next.js standalone server (migrations run via instrumentation.ts) +CMD ["node", "server.js"] \ No newline at end of file diff --git a/README.md b/README.md index 87782be..9efebb3 100644 --- a/README.md +++ b/README.md @@ -77,13 +77,7 @@ Open [http://localhost:3000](http://localhost:3000) in your browser. - Check the generated SQL file in `drizzle/` to ensure correctness - Make manual adjustments if needed -4. **Apply Migration to Database** - ```bash - npm run db:push - ``` - This applies the migration to your local database. - -5. **Update Server Actions** (if needed) +4. **Update Server Actions** (if needed) - Update or create server actions in `src/app/actions/` to handle the new fields/tables ### Example: Adding a New Field diff --git a/next.config.ts b/next.config.ts index ba5f87b..9ae1f64 100644 --- a/next.config.ts +++ b/next.config.ts @@ -5,8 +5,9 @@ const nextConfig: NextConfig = { experimental: { serverActions: { bodySizeLimit: '10000mb' - } + }, }, + output: "standalone", }; export default nextConfig; diff --git a/package-lock.json b/package-lock.json index c4ed8c7..fe88edf 100644 --- a/package-lock.json +++ b/package-lock.json @@ -15,6 +15,7 @@ "better-auth": "^1.4.19", "better-auth-localization": "^2.3.1", "better-sqlite3": "^12.6.2", + "drizzle-kit": "^0.31.9", "drizzle-orm": "^0.45.1", "extract-zip": "^2.0.1", "next": "16.1.6", @@ -25,7 +26,6 @@ "@types/better-sqlite3": "^7.6.13", "@types/node": "^20", "@types/react": "^19", - "drizzle-kit": "^0.31.9", "typescript": "^5" } }, @@ -663,7 +663,6 @@ "version": "0.10.2", "resolved": "https://registry.npmjs.org/@drizzle-team/brocli/-/brocli-0.10.2.tgz", "integrity": "sha512-z33Il7l5dKjUgGULTqBsQBQwckHh5AbIuxhdsIxDDiZAzBOrZO6q9ogcWC65kU382AfynTfgNumVcNIjuIua6w==", - "devOptional": true, "license": "Apache-2.0" }, "node_modules/@emnapi/runtime": { @@ -681,7 +680,6 @@ "resolved": "https://registry.npmjs.org/@esbuild-kit/core-utils/-/core-utils-3.3.2.tgz", "integrity": "sha512-sPRAnw9CdSsRmEtnsl2WXWdyquogVpB3yZ3dgwJfe8zrOzTsV7cJvmwrKVa+0ma5BoiGJ+BoqkMvawbayKUsqQ==", "deprecated": "Merged into tsx: https://tsx.is", - "devOptional": true, "license": "MIT", "dependencies": { "esbuild": "~0.18.20", @@ -695,7 +693,6 @@ "cpu": [ "arm" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -712,7 +709,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -729,7 +725,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -746,7 +741,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -763,7 +757,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -780,7 +773,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -797,7 +789,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -814,7 +805,6 @@ "cpu": [ "arm" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -831,7 +821,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -848,7 +837,6 @@ "cpu": [ "ia32" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -865,7 +853,6 @@ "cpu": [ "loong64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -882,7 +869,6 @@ "cpu": [ "mips64el" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -899,7 +885,6 @@ "cpu": [ "ppc64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -916,7 +901,6 @@ "cpu": [ "riscv64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -933,7 +917,6 @@ "cpu": [ "s390x" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -950,7 +933,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -967,7 +949,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -984,7 +965,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1001,7 +981,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1018,7 +997,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1035,7 +1013,6 @@ "cpu": [ "ia32" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1052,7 +1029,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1066,7 +1042,6 @@ "version": "0.18.20", "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.18.20.tgz", "integrity": "sha512-ceqxoedUrcayh7Y7ZX6NdbbDzGROiyVBgC4PriJThBKSVPWnnFHZAkfI1lJT8QFkOwH4qOS2SJkS4wvpGl8BpA==", - "devOptional": true, "hasInstallScript": true, "license": "MIT", "bin": { @@ -1105,7 +1080,6 @@ "resolved": "https://registry.npmjs.org/@esbuild-kit/esm-loader/-/esm-loader-2.6.5.tgz", "integrity": "sha512-FxEMIkJKnodyA1OaCUoEvbYRkoZlLZ4d/eXFu9Fh8CbBBgP5EmZxrfTRyN0qpXZ4vOvqnE5YdRdcrmUUXuU+dA==", "deprecated": "Merged into tsx: https://tsx.is", - "devOptional": true, "license": "MIT", "dependencies": { "@esbuild-kit/core-utils": "^3.3.2", @@ -1119,7 +1093,6 @@ "cpu": [ "ppc64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1136,7 +1109,6 @@ "cpu": [ "arm" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1153,7 +1125,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1170,7 +1141,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1187,7 +1157,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1204,7 +1173,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1221,7 +1189,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1238,7 +1205,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1255,7 +1221,6 @@ "cpu": [ "arm" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1272,7 +1237,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1289,7 +1253,6 @@ "cpu": [ "ia32" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1306,7 +1269,6 @@ "cpu": [ "loong64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1323,7 +1285,6 @@ "cpu": [ "mips64el" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1340,7 +1301,6 @@ "cpu": [ "ppc64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1357,7 +1317,6 @@ "cpu": [ "riscv64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1374,7 +1333,6 @@ "cpu": [ "s390x" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1391,7 +1349,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1408,7 +1365,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1425,7 +1381,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1442,7 +1397,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1459,7 +1413,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1476,7 +1429,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1493,7 +1445,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1510,7 +1461,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1527,7 +1477,6 @@ "cpu": [ "ia32" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1544,7 +1493,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -3430,7 +3378,6 @@ "version": "1.1.2", "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", - "devOptional": true, "license": "MIT" }, "node_modules/bundle-name": { @@ -3751,7 +3698,6 @@ "version": "0.31.9", "resolved": "https://registry.npmjs.org/drizzle-kit/-/drizzle-kit-0.31.9.tgz", "integrity": "sha512-GViD3IgsXn7trFyBUUHyTFBpH/FsHTxYJ66qdbVggxef4UBPHRYxQaRzYLTuekYnk9i5FIEL9pbBIwMqX/Uwrg==", - "devOptional": true, "license": "MIT", "dependencies": { "@drizzle-team/brocli": "^0.10.2", @@ -3920,7 +3866,6 @@ "version": "0.25.12", "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.12.tgz", "integrity": "sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg==", - "devOptional": true, "hasInstallScript": true, "license": "MIT", "bin": { @@ -3962,7 +3907,6 @@ "version": "3.6.0", "resolved": "https://registry.npmjs.org/esbuild-register/-/esbuild-register-3.6.0.tgz", "integrity": "sha512-H2/S7Pm8a9CL1uhp9OvjwrBh5Pvx0H8qVOxNu8Wed9Y7qv56MPtq+GGM8RJpq6glYJn9Wspr8uw7l55uyinNeg==", - "devOptional": true, "license": "MIT", "dependencies": { "debug": "^4.3.4" @@ -4099,7 +4043,6 @@ "version": "4.13.6", "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.13.6.tgz", "integrity": "sha512-shZT/QMiSHc/YBLxxOkMtgSid5HFoauqCE3/exfsEcwg1WkeqjG+V40yBbBrsD+jW2HDXcs28xOfcbm2jI8Ddw==", - "devOptional": true, "license": "MIT", "dependencies": { "resolve-pkg-maps": "^1.0.0" @@ -5230,7 +5173,6 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz", "integrity": "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==", - "devOptional": true, "license": "MIT", "funding": { "url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1" @@ -5399,7 +5341,6 @@ "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "devOptional": true, "license": "BSD-3-Clause", "engines": { "node": ">=0.10.0" @@ -5418,7 +5359,6 @@ "version": "0.5.21", "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", - "devOptional": true, "license": "MIT", "dependencies": { "buffer-from": "^1.0.0", diff --git a/package.json b/package.json index ea430c6..108f631 100644 --- a/package.json +++ b/package.json @@ -21,13 +21,13 @@ "extract-zip": "^2.0.1", "next": "16.1.6", "postcss": "^8.5.6", - "tailwindcss": "^4.2.1" + "tailwindcss": "^4.2.1", + "drizzle-kit": "^0.31.9" }, "devDependencies": { "@types/better-sqlite3": "^7.6.13", "@types/node": "^20", "@types/react": "^19", - "drizzle-kit": "^0.31.9", "typescript": "^5" } } diff --git a/src/components/CreateDatasetModal.tsx b/src/components/CreateDatasetModal.tsx index 11636d3..98eaaaf 100644 --- a/src/components/CreateDatasetModal.tsx +++ b/src/components/CreateDatasetModal.tsx @@ -70,7 +70,7 @@ export default function CreateDatasetModal() { }, { label: loading ? 'Creating...' : 'Create', - onClick: (e) => { + onClick: () => { const form = document.getElementById('datasetForm') as HTMLFormElement; form?.dispatchEvent(new Event('submit', { bubbles: true })); }, diff --git a/src/instrumentation.ts b/src/instrumentation.ts new file mode 100644 index 0000000..577a107 --- /dev/null +++ b/src/instrumentation.ts @@ -0,0 +1,22 @@ +import { migrate } from 'drizzle-orm/better-sqlite3/migrator'; +import db from '@/lib/db'; +import { join } from 'path'; + +export async function register() { + if (process.env.NEXT_RUNTIME === 'nodejs') { + try { + console.log('Running database migrations...'); + + const migrationsFolder = join(process.cwd(), 'drizzle'); + + // Use Drizzle's built-in migrate function + migrate(db, { migrationsFolder }); + + console.log('✓ Database migrations completed successfully'); + } catch (error) { + console.error('Failed to run database migrations:', error); + process.exit(1); + } + } +} +