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:
15
.env.example
Normal file
15
.env.example
Normal 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
24
.gitignore
vendored
Normal 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
|
||||
13
Dockerfile.dev
Normal file
13
Dockerfile.dev
Normal 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
4
app/app.vue
Normal file
@@ -0,0 +1,4 @@
|
||||
<template>
|
||||
<NuxtRouteAnnouncer />
|
||||
<NuxtPage />
|
||||
</template>
|
||||
6
app/pages/dashboard.vue
Normal file
6
app/pages/dashboard.vue
Normal 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
6
app/pages/index.vue
Normal 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
35
docker-compose.yml
Normal 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
10
drizzle.config.ts
Normal 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
4
eslint.config.mjs
Normal 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
6
nuxt.config.ts
Normal 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
36
package.json
Normal 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
10031
pnpm-lock.yaml
generated
Normal file
File diff suppressed because it is too large
Load Diff
BIN
public/favicon.ico
Normal file
BIN
public/favicon.ico
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 4.2 KiB |
2
public/robots.txt
Normal file
2
public/robots.txt
Normal file
@@ -0,0 +1,2 @@
|
||||
User-Agent: *
|
||||
Disallow:
|
||||
5
server/api/health.get.ts
Normal file
5
server/api/health.get.ts
Normal 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
21
server/db/client.ts
Normal 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
2
server/db/schema.ts
Normal file
@@ -0,0 +1,2 @@
|
||||
// Drizzle (Postgres) schema — tables are defined in Task 3.
|
||||
export {}
|
||||
7
tests/smoke.spec.ts
Normal file
7
tests/smoke.spec.ts
Normal 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
18
tsconfig.json
Normal 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
9
vitest.config.ts
Normal 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',
|
||||
},
|
||||
})
|
||||
Reference in New Issue
Block a user