chore: scaffold Nuxt 4 app with Postgres, tooling and health route

Nuxt 4 (app/ structure) + TypeScript, ESLint, Vitest + @nuxt/test-utils,
Drizzle + pg, a /api/health route with a DB ping, and a dev docker-compose
(app + Postgres). Verified: pnpm test, lint, and build pass; the built
server answers /api/health.
This commit is contained in:
Julien Calixte
2026-05-27 22:16:46 +02:00
parent e81ee53f7b
commit c13f3deeff
21 changed files with 10255 additions and 0 deletions

15
.env.example Normal file
View File

@@ -0,0 +1,15 @@
# App
NUXT_SESSION_SECRET=change-me
# Database (Postgres) — Coolify-managed in prod, docker-compose in dev
DATABASE_URL=postgres://andon:andon@localhost:5432/andon
# Google OAuth (Task 9) — restricted to the theodo.com hosted domain
GOOGLE_CLIENT_ID=
GOOGLE_CLIENT_SECRET=
GOOGLE_REDIRECT_URI=
# Web Push / VAPID (Task 10)
VAPID_PUBLIC_KEY=
VAPID_PRIVATE_KEY=
VAPID_SUBJECT=mailto:owner@theodo.com

24
.gitignore vendored Normal file
View File

@@ -0,0 +1,24 @@
# Nuxt dev/build outputs
.output
.data
.nuxt
.nitro
.cache
dist
# Node dependencies
node_modules
# Logs
logs
*.log
# Misc
.DS_Store
.fleet
.idea
# Local env files
.env
.env.*
!.env.example

1
.nuxtrc Normal file
View File

@@ -0,0 +1 @@
setups.@nuxt/test-utils="4.0.3"

13
Dockerfile.dev Normal file
View File

@@ -0,0 +1,13 @@
FROM node:24-alpine
RUN corepack enable
WORKDIR /app
# Install dependencies first for better layer caching.
COPY package.json pnpm-lock.yaml ./
RUN pnpm install --frozen-lockfile
COPY . .
ENV HOST=0.0.0.0
EXPOSE 3000
CMD ["pnpm", "dev"]

4
app/app.vue Normal file
View File

@@ -0,0 +1,4 @@
<template>
<NuxtRouteAnnouncer />
<NuxtPage />
</template>

6
app/pages/dashboard.vue Normal file
View File

@@ -0,0 +1,6 @@
<template>
<main>
<h1>Andon weak point dashboard</h1>
<p>The red-dot defect map lands in Task 7.</p>
</main>
</template>

6
app/pages/index.vue Normal file
View File

@@ -0,0 +1,6 @@
<template>
<main>
<h1>Andon report a board problem</h1>
<p>The interactive board lands in Task 2.</p>
</main>
</template>

35
docker-compose.yml Normal file
View File

@@ -0,0 +1,35 @@
services:
app:
build:
context: .
dockerfile: Dockerfile.dev
ports:
- "3000:3000"
environment:
DATABASE_URL: postgres://andon:andon@db:5432/andon
HOST: 0.0.0.0
depends_on:
db:
condition: service_healthy
volumes:
- .:/app
- /app/node_modules
db:
image: postgres:16-alpine
environment:
POSTGRES_USER: andon
POSTGRES_PASSWORD: andon
POSTGRES_DB: andon
ports:
- "5432:5432"
volumes:
- andon_pgdata:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U andon"]
interval: 5s
timeout: 5s
retries: 5
volumes:
andon_pgdata:

10
drizzle.config.ts Normal file
View File

@@ -0,0 +1,10 @@
import { defineConfig } from 'drizzle-kit'
export default defineConfig({
dialect: 'postgresql',
schema: './server/db/schema.ts',
out: './server/db/migrations',
dbCredentials: {
url: process.env.DATABASE_URL ?? '',
},
})

4
eslint.config.mjs Normal file
View File

@@ -0,0 +1,4 @@
// Nuxt generates the base flat config during `nuxt prepare`.
import withNuxt from './.nuxt/eslint.config.mjs'
export default withNuxt()

6
nuxt.config.ts Normal file
View File

@@ -0,0 +1,6 @@
// https://nuxt.com/docs/api/configuration/nuxt-config
export default defineNuxtConfig({
compatibilityDate: '2025-07-15',
devtools: { enabled: true },
modules: ['@nuxt/eslint', '@nuxt/test-utils/module'],
})

36
package.json Normal file
View File

@@ -0,0 +1,36 @@
{
"name": "andon",
"type": "module",
"private": true,
"scripts": {
"build": "nuxt build",
"dev": "nuxt dev",
"generate": "nuxt generate",
"preview": "nuxt preview",
"postinstall": "nuxt prepare",
"lint": "eslint .",
"test": "vitest run",
"test:watch": "vitest",
"typecheck": "nuxt typecheck",
"db:generate": "drizzle-kit generate",
"db:migrate": "tsx server/db/migrate.ts"
},
"dependencies": {
"drizzle-orm": "^0.45.2",
"nuxt": "^4.4.6",
"pg": "^8.21.0",
"vue": "^3.5.34",
"vue-router": "^5.0.7"
},
"devDependencies": {
"@nuxt/eslint": "^1.15.2",
"@nuxt/test-utils": "^4.0.3",
"@types/pg": "^8.20.0",
"@vue/test-utils": "^2.4.10",
"drizzle-kit": "^0.31.10",
"eslint": "^10.4.0",
"happy-dom": "^20.9.0",
"tsx": "^4.22.3",
"vitest": "^4.1.7"
}
}

10031
pnpm-lock.yaml generated Normal file

File diff suppressed because it is too large Load Diff

BIN
public/favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

2
public/robots.txt Normal file
View File

@@ -0,0 +1,2 @@
User-Agent: *
Disallow:

5
server/api/health.get.ts Normal file
View File

@@ -0,0 +1,5 @@
import { checkDb } from '../db/client'
export default defineEventHandler(async () => {
return { ok: true, db: await checkDb() }
})

21
server/db/client.ts Normal file
View File

@@ -0,0 +1,21 @@
import pg from 'pg'
let pool: pg.Pool | undefined
/** Lazily-created shared Postgres connection pool. */
export function getPool(): pg.Pool {
if (!pool) {
pool = new pg.Pool({ connectionString: process.env.DATABASE_URL })
}
return pool
}
/** Liveness probe for the database, used by /api/health. */
export async function checkDb(): Promise<'up' | 'down'> {
try {
await getPool().query('select 1')
return 'up'
} catch {
return 'down'
}
}

2
server/db/schema.ts Normal file
View File

@@ -0,0 +1,2 @@
// Drizzle (Postgres) schema — tables are defined in Task 3.
export {}

7
tests/smoke.spec.ts Normal file
View File

@@ -0,0 +1,7 @@
import { describe, it, expect } from 'vitest'
describe('smoke', () => {
it('runs the test suite', () => {
expect(true).toBe(true)
})
})

18
tsconfig.json Normal file
View File

@@ -0,0 +1,18 @@
{
// https://nuxt.com/docs/guide/concepts/typescript
"files": [],
"references": [
{
"path": "./.nuxt/tsconfig.app.json"
},
{
"path": "./.nuxt/tsconfig.server.json"
},
{
"path": "./.nuxt/tsconfig.shared.json"
},
{
"path": "./.nuxt/tsconfig.node.json"
}
]
}

9
vitest.config.ts Normal file
View File

@@ -0,0 +1,9 @@
import { defineVitestConfig } from '@nuxt/test-utils/config'
export default defineVitestConfig({
test: {
// Default to a plain node environment; component/integration specs opt into
// the Nuxt runtime per-file with `// @vitest-environment nuxt`.
environment: 'node',
},
})