chore: scaffold node/ts/vitest project with health endpoint
This commit is contained in:
6
.gitignore
vendored
Normal file
6
.gitignore
vendored
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
node_modules/
|
||||||
|
server/dist/
|
||||||
|
web/dist/
|
||||||
|
data/
|
||||||
|
*.log
|
||||||
|
.DS_Store
|
||||||
2870
package-lock.json
generated
Normal file
2870
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
31
package.json
Normal file
31
package.json
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
{
|
||||||
|
"name": "record-shop",
|
||||||
|
"version": "0.1.0",
|
||||||
|
"private": true,
|
||||||
|
"type": "module",
|
||||||
|
"engines": { "node": ">=20" },
|
||||||
|
"scripts": {
|
||||||
|
"dev:server": "tsx watch server/src/index.ts",
|
||||||
|
"dev": "npm run dev:server",
|
||||||
|
"test": "vitest run",
|
||||||
|
"test:watch": "vitest",
|
||||||
|
"build:server": "tsc -p server/tsconfig.build.json",
|
||||||
|
"build": "npm run build:server",
|
||||||
|
"start": "node server/dist/index.js",
|
||||||
|
"typecheck": "tsc -p server/tsconfig.json --noEmit"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@fastify/cookie": "^11.0.0",
|
||||||
|
"@fastify/static": "^8.0.0",
|
||||||
|
"argon2": "^0.41.1",
|
||||||
|
"better-sqlite3": "^13.0.3",
|
||||||
|
"fastify": "^5.0.0"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@types/better-sqlite3": "^7.6.11",
|
||||||
|
"@types/node": "^22.5.0",
|
||||||
|
"tsx": "^4.16.0",
|
||||||
|
"typescript": "^5.5.4",
|
||||||
|
"vitest": "^3.0.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
26
server/src/app.ts
Normal file
26
server/src/app.ts
Normal 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
7
server/src/config.ts
Normal 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
10
server/src/db.ts
Normal 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
15
server/src/index.ts
Normal 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
23
server/test/app.test.ts
Normal 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()
|
||||||
|
})
|
||||||
|
})
|
||||||
5
server/tsconfig.build.json
Normal file
5
server/tsconfig.build.json
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
{
|
||||||
|
"extends": "../tsconfig.json",
|
||||||
|
"compilerOptions": { "outDir": "dist", "rootDir": "src" },
|
||||||
|
"include": ["src/**/*"]
|
||||||
|
}
|
||||||
5
server/tsconfig.json
Normal file
5
server/tsconfig.json
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
{
|
||||||
|
"extends": "../tsconfig.json",
|
||||||
|
"compilerOptions": { "noEmit": true },
|
||||||
|
"include": ["src/**/*", "test/**/*"]
|
||||||
|
}
|
||||||
15
tsconfig.json
Normal file
15
tsconfig.json
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"target": "ES2022",
|
||||||
|
"module": "NodeNext",
|
||||||
|
"moduleResolution": "NodeNext",
|
||||||
|
"lib": ["ES2022"],
|
||||||
|
"strict": true,
|
||||||
|
"noUncheckedIndexedAccess": true,
|
||||||
|
"forceConsistentCasingInFileNames": true,
|
||||||
|
"skipLibCheck": true,
|
||||||
|
"esModuleInterop": true,
|
||||||
|
"resolveJsonModule": true,
|
||||||
|
"types": ["node"]
|
||||||
|
}
|
||||||
|
}
|
||||||
7
vitest.config.ts
Normal file
7
vitest.config.ts
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
import { defineConfig } from 'vitest/config'
|
||||||
|
|
||||||
|
export default defineConfig({
|
||||||
|
test: {
|
||||||
|
include: ['server/test/**/*.test.ts'],
|
||||||
|
},
|
||||||
|
})
|
||||||
Reference in New Issue
Block a user