1
0

chore: scaffold node/ts/vitest project with health endpoint

This commit is contained in:
2026-08-29 14:43:44 +02:00
parent 52b2cbd6c5
commit 01ad6fbb40
12 changed files with 3020 additions and 0 deletions

26
server/src/app.ts Normal file
View File

@@ -0,0 +1,26 @@
import Fastify, { FastifyInstance } from 'fastify'
import Database from 'better-sqlite3'
import type { Config } from './config.js'
declare module 'fastify' {
interface FastifyInstance {
db: Database.Database
config: Config
fetchImpl: typeof fetch
}
}
export interface AppOptions {
db: Database.Database
config: Config
fetchImpl?: typeof fetch
}
export async function buildApp(opts: AppOptions): Promise<FastifyInstance> {
const app = Fastify({ logger: false })
app.decorate('db', opts.db)
app.decorate('config', opts.config)
app.decorate('fetchImpl', opts.fetchImpl ?? fetch)
app.get('/api/health', async () => ({ ok: true }))
return app
}

7
server/src/config.ts Normal file
View File

@@ -0,0 +1,7 @@
export interface Config {
dataDir: string
artworkDir: string
dbPath: string
port: number
sessionSecret: string
}

10
server/src/db.ts Normal file
View File

@@ -0,0 +1,10 @@
import Database from 'better-sqlite3'
export type DB = Database.Database
export function openDatabase(path: string): DB {
const db = new Database(path)
db.pragma('journal_mode = WAL')
db.pragma('foreign_keys = ON')
return db
}

15
server/src/index.ts Normal file
View File

@@ -0,0 +1,15 @@
import { buildApp } from './app.js'
import { openDatabase } from './db.js'
import type { Config } from './config.js'
const config: Config = {
dataDir: process.env.DATA_DIR ?? 'data',
artworkDir: (process.env.DATA_DIR ?? 'data') + '/artwork-cache',
dbPath: (process.env.DATA_DIR ?? 'data') + '/record-shop.db',
port: Number(process.env.PORT ?? 3000),
sessionSecret: 'placeholder-replaced-in-task-3',
}
const db = openDatabase(config.dbPath)
const app = await buildApp({ db, config })
await app.listen({ port: config.port, host: '0.0.0.0' })

23
server/test/app.test.ts Normal file
View File

@@ -0,0 +1,23 @@
import { describe, it, expect } from 'vitest'
import { buildApp } from '../src/app.js'
import { openDatabase } from '../src/db.js'
import type { Config } from '../src/config.js'
const config: Config = {
dataDir: ':memory:',
artworkDir: ':memory:',
dbPath: ':memory:',
port: 0,
sessionSecret: 'test-secret-test-secret-test-secret-1234',
}
describe('GET /api/health', () => {
it('returns ok', async () => {
const db = openDatabase(':memory:')
const app = await buildApp({ db, config })
const res = await app.inject({ method: 'GET', url: '/api/health' })
expect(res.statusCode).toBe(200)
expect(res.json()).toEqual({ ok: true })
await app.close()
})
})

View File

@@ -0,0 +1,5 @@
{
"extends": "../tsconfig.json",
"compilerOptions": { "outDir": "dist", "rootDir": "src" },
"include": ["src/**/*"]
}

5
server/tsconfig.json Normal file
View File

@@ -0,0 +1,5 @@
{
"extends": "../tsconfig.json",
"compilerOptions": { "noEmit": true },
"include": ["src/**/*", "test/**/*"]
}