99 lines
3.5 KiB
JavaScript
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)
|
|
}
|