cashup/backend/src/index.js

99 lines
3.5 KiB
JavaScript

import Fastify from 'fastify'
import cookie from '@fastify/cookie'
import cors from '@fastify/cors'
import multipart from '@fastify/multipart'
import staticFiles from '@fastify/static'
import { fileURLToPath } from 'url'
import { dirname, join } from 'path'
import { initDb } from './db.js'
import { cashupRoutes } from './routes/cashup.js'
import { newbookRoutes } from './routes/newbook.js'
import { reportRoutes } from './routes/reports.js'
import { floatRoutes } from './routes/floats.js'
import { settingsRoutes } from './routes/settings.js'
const __dirname = dirname(fileURLToPath(import.meta.url))
const UPLOADS_DIR = join(__dirname, '..', '..', 'uploads')
const app = Fastify({ logger: true, trustProxy: true })
await app.register(cookie)
await app.register(cors, {
origin: process.env.CORS_ORIGIN || false,
credentials: true,
})
await app.register(multipart, { limits: { fileSize: 10 * 1024 * 1024 } })
await app.register(staticFiles, {
root: UPLOADS_DIR,
prefix: '/api/uploads/',
decorateReply: false,
})
app.get('/health', async () => ({ status: 'healthy' }))
await app.register(cashupRoutes)
await app.register(newbookRoutes)
await app.register(reportRoutes)
await app.register(floatRoutes)
await app.register(settingsRoutes)
// File upload for cash up receipt attachments
import { requireAuth } from './auth.js'
import { pool } from './db.js'
import { createWriteStream } from 'fs'
import { mkdir } from 'fs/promises'
import { randomUUID } from 'crypto'
import { extname } from 'path'
app.post('/api/attachments/upload/:cash_up_id', { preHandler: requireAuth }, async (req, reply) => {
const cashUpId = parseInt(req.params.cash_up_id)
const { rows } = await pool.query('SELECT id FROM cash_ups WHERE id = $1', [cashUpId])
if (!rows.length) return reply.status(404).send({ error: 'Cash up not found' })
const data = await req.file()
if (!data) return reply.status(400).send({ error: 'No file uploaded' })
const allowed = ['image/jpeg', 'image/jpg', 'image/png', 'application/pdf']
if (!allowed.includes(data.mimetype)) {
return reply.status(400).send({ error: 'Only JPEG, PNG and PDF files are allowed' })
}
const ext = extname(data.filename) || '.bin'
const filename = randomUUID() + ext
const dir = join(UPLOADS_DIR, 'cashup', String(cashUpId))
await mkdir(dir, { recursive: true })
let size = 0
const dest = createWriteStream(join(dir, filename))
for await (const chunk of data.file) { dest.write(chunk); size += chunk.length }
await new Promise(r => dest.end(r))
const filePath = `/cashup/${cashUpId}/${filename}`
const { rows: ins } = await pool.query(
`INSERT INTO cash_count_attachments (cash_up_id, file_name, file_path, file_size, mime_type, uploaded_by)
VALUES ($1,$2,$3,$4,$5,$6) RETURNING *`,
[cashUpId, data.filename, filePath, size, data.mimetype, req.user.email]
)
return ins.rows[0]
})
app.delete('/api/attachments/:id', { preHandler: requireAuth }, async (req, reply) => {
const { rows } = await pool.query('SELECT * FROM cash_count_attachments WHERE id = $1', [req.params.id])
if (!rows.length) return reply.status(404).send({ error: 'Not found' })
// Delete file from disk (best effort)
const { unlink } = await import('fs/promises')
await unlink(join(UPLOADS_DIR, rows[0].file_path)).catch(() => {})
await pool.query('DELETE FROM cash_count_attachments WHERE id = $1', [req.params.id])
return { message: 'Deleted' }
})
try {
await initDb()
await app.listen({ port: 3001, host: '0.0.0.0' })
} catch (err) {
app.log.error(err)
process.exit(1)
}