fix the upload issue and add driver license upload
This commit is contained in:
@@ -9,6 +9,7 @@ import jwt from 'jsonwebtoken'
|
||||
import { z } from 'zod'
|
||||
import { redis } from './lib/redis'
|
||||
import { prisma } from './lib/prisma'
|
||||
import { getStorageRoot } from './lib/storage'
|
||||
import { authLimiter, apiLimiter, publicLimiter, adminLimiter } from './middleware/rateLimiter'
|
||||
|
||||
// ─── Routes ───────────────────────────────────────────────────
|
||||
@@ -122,8 +123,8 @@ subscriber.on('pmessage', (_pattern, channel, message) => {
|
||||
})
|
||||
|
||||
// ─── Middleware ────────────────────────────────────────────────
|
||||
// Serve local file storage
|
||||
app.use('/storage', express.static(require('path').resolve(__dirname, 'lib/storage')))
|
||||
// Serve uploaded assets from the configured storage root.
|
||||
app.use('/storage', express.static(getStorageRoot()))
|
||||
|
||||
// Webhook must use raw body BEFORE express.json()
|
||||
app.use('/api/v1/webhooks', express.raw({ type: 'application/json' }), webhookRouter)
|
||||
|
||||
@@ -2,7 +2,19 @@ import fs from 'fs'
|
||||
import path from 'path'
|
||||
import crypto from 'crypto'
|
||||
|
||||
const STORAGE_ROOT = path.resolve(__dirname, 'storage')
|
||||
const DEFAULT_STORAGE_ROOT = path.resolve(__dirname, '..', '..', 'storage')
|
||||
|
||||
export function getStorageRoot(): string {
|
||||
return process.env.FILE_STORAGE_ROOT
|
||||
? path.resolve(process.env.FILE_STORAGE_ROOT)
|
||||
: DEFAULT_STORAGE_ROOT
|
||||
}
|
||||
|
||||
function ensureStorageRoot(): string {
|
||||
const storageRoot = getStorageRoot()
|
||||
fs.mkdirSync(storageRoot, { recursive: true })
|
||||
return storageRoot
|
||||
}
|
||||
|
||||
function getApiBase(): string {
|
||||
return (process.env.API_URL ?? 'http://localhost:4000').replace(/\/$/, '')
|
||||
@@ -13,7 +25,8 @@ export async function uploadImage(
|
||||
folder: string,
|
||||
publicId?: string
|
||||
): Promise<string> {
|
||||
const folderPath = path.join(STORAGE_ROOT, folder)
|
||||
const storageRoot = ensureStorageRoot()
|
||||
const folderPath = path.join(storageRoot, folder)
|
||||
fs.mkdirSync(folderPath, { recursive: true })
|
||||
|
||||
const filename = publicId
|
||||
@@ -26,10 +39,11 @@ export async function uploadImage(
|
||||
}
|
||||
|
||||
export async function deleteImage(imageUrl: string): Promise<void> {
|
||||
const storageRoot = getStorageRoot()
|
||||
const marker = '/storage/'
|
||||
const idx = imageUrl.indexOf(marker)
|
||||
if (idx === -1) return
|
||||
const relative = imageUrl.slice(idx + marker.length)
|
||||
const filePath = path.join(STORAGE_ROOT, relative)
|
||||
const filePath = path.join(storageRoot, relative)
|
||||
if (fs.existsSync(filePath)) fs.unlinkSync(filePath)
|
||||
}
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import { Router } from 'express'
|
||||
import { z } from 'zod'
|
||||
import multer from 'multer'
|
||||
import { prisma } from '../lib/prisma'
|
||||
import { deleteImage, uploadImage } from '../lib/storage'
|
||||
import { requireCompanyAuth } from '../middleware/requireCompanyAuth'
|
||||
import { requireTenant } from '../middleware/requireTenant'
|
||||
import { requireSubscription } from '../middleware/requireSubscription'
|
||||
@@ -8,6 +10,7 @@ import { requireRole } from '../middleware/requireRole'
|
||||
import { validateAndFlagLicense } from '../services/licenseValidationService'
|
||||
|
||||
const router = Router()
|
||||
const upload = multer({ storage: multer.memoryStorage(), limits: { fileSize: 10 * 1024 * 1024 } })
|
||||
router.use(requireCompanyAuth, requireTenant, requireSubscription)
|
||||
|
||||
const paginationSchema = z.object({
|
||||
@@ -113,6 +116,31 @@ router.post('/:id/validate-license', async (req, res, next) => {
|
||||
} catch (err) { next(err) }
|
||||
})
|
||||
|
||||
router.post('/:id/license-image', upload.single('file'), async (req, res, next) => {
|
||||
try {
|
||||
if (!req.file) {
|
||||
return res.status(400).json({ error: 'missing_file', message: 'A license image file is required', statusCode: 400 })
|
||||
}
|
||||
if (!req.file.mimetype.startsWith('image/')) {
|
||||
return res.status(400).json({ error: 'invalid_file_type', message: 'Only image uploads are supported', statusCode: 400 })
|
||||
}
|
||||
|
||||
const customer = await prisma.customer.findFirstOrThrow({ where: { id: req.params.id, companyId: req.companyId } })
|
||||
const url = await uploadImage(req.file.buffer, `companies/${req.companyId}/customers/${customer.id}`)
|
||||
|
||||
if (customer.licenseImageUrl) {
|
||||
await deleteImage(customer.licenseImageUrl).catch(() => null)
|
||||
}
|
||||
|
||||
const updated = await prisma.customer.update({
|
||||
where: { id: customer.id },
|
||||
data: { licenseImageUrl: url },
|
||||
})
|
||||
|
||||
res.json({ data: updated })
|
||||
} catch (err) { next(err) }
|
||||
})
|
||||
|
||||
router.post('/:id/approve-license', requireRole('MANAGER'), async (req, res, next) => {
|
||||
try {
|
||||
const { decision, note } = z.object({ decision: z.enum(['APPROVE', 'DENY']), note: z.string().optional() }).parse(req.body)
|
||||
|
||||
Reference in New Issue
Block a user