multy-stub/server/routers/dry-wash/order.js

317 lines
9.4 KiB
JavaScript
Raw Permalink Normal View History

2025-01-18 23:02:45 +03:00
const mongoose = require("mongoose")
const router = require('express').Router()
2025-02-22 16:58:58 +03:00
const multer = require('multer')
2025-01-18 23:02:45 +03:00
const { MasterModel } = require('./model/master')
const { OrderModel } = require('./model/order')
2025-02-22 16:58:58 +03:00
const { OrderCarImgModel } = require('./model/order.car-img')
2025-01-18 23:02:45 +03:00
const { orderStatus } = require('./model/const')
const isValidPhoneNumber = (value) => /^(\+)?\d{9,15}/.test(value)
const isValidCarNumber = (value) => /^[авекмнорстух][0-9]{3}[авекмнорстух]{2}[0-9]{2,3}$/i.test(value)
const isValidCarBodyType = (value) => typeof value === 'number' && value > 0 && value < 100
const isValidCarColor = (value) => value.length < 50 && /^[#a-z0-9а-я-\s,.()]+$/i.test(value)
const isValidISODate = (value) => /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:.\d{1,3})?Z$/.test(value)
const latitudeRe = /^(-?[1-8]?\d(?:\.\d{1,18})?|90(?:\.0{1,18})?)$/
const longitudeRe = /^(-?(?:1[0-7]|[1-9])?\d(?:\.\d{1,18})?|180(?:\.0{1,18})?)$/
const addressRe = /^[а-я0-9\s,.'-/()]*$/i
const isValidLocation = (value) => {
if (value.length > 200) {
return false
}
const [coordinates, address] = value.split(' ')
const [latitude, longitude] = coordinates.split(',')
return latitudeRe.test(latitude) && longitudeRe.test(longitude) && addressRe.test(address)
}
const isValidOrderStatus = (value) => Object.values(orderStatus).includes(value)
const isValidOrderNotes = (value) => value.length < 500
2025-02-22 16:58:58 +03:00
const allowedMimeTypes = ['image/jpeg', 'image/png']
const sizeLimitInMegaBytes = 5
2025-01-18 23:02:45 +03:00
const VALIDATION_MESSAGES = {
order: {
notFound: 'Order not found'
},
orderId: {
invalid: 'Valid order ID is required',
},
orderStatus: {
invalid: 'Invalid order status'
},
orderNotes: {
invalid: 'Invalid order notes'
},
master: {
notFound: 'Master not found'
},
masterId: {
invalid: 'Invalid master ID',
},
phoneNumber: {
required: 'Phone number is required',
invalid: 'Invalid phone number'
},
carNumber: {
required: 'Car number is required',
invalid: 'Invalid car number'
},
carBody: {
required: 'Car body type is required',
invalid: 'Invalid car body type'
},
carColor: {
invalid: 'Invalid car color'
},
2025-02-22 16:58:58 +03:00
carImg: {
required: 'Car image file is required',
invalid: {
type: `Invalid car image file type. Allowed types: ${allowedMimeTypes}`,
size: `Invalid car image file size. Limit is ${sizeLimitInMegaBytes}MB`
}
},
2025-01-18 23:02:45 +03:00
washingBegin: {
required: 'Begin time of washing is required',
invalid: 'Invalid begin time of washing'
},
washingEnd: {
required: 'End time of washing is required',
invalid: 'Invalid end time of washing'
},
washingLocation: {
required: 'Location of washing is required',
invalid: 'Invalid location of washing'
},
}
router.post('/create', async (req, res, next) => {
const bodyErrors = []
const { customer } = req.body
if (!customer.phone) {
bodyErrors.push(VALIDATION_MESSAGES.phoneNumber.required)
} else if (!isValidPhoneNumber(customer.phone)) {
bodyErrors.push(VALIDATION_MESSAGES.phoneNumber.invalid)
}
const { car } = req.body
if (!car.number) {
bodyErrors.push(VALIDATION_MESSAGES.carNumber.required)
} else if (!isValidCarNumber(car.number)) {
bodyErrors.push(VALIDATION_MESSAGES.carNumber.invalid)
}
if (!car.body) {
bodyErrors.push(VALIDATION_MESSAGES.carBody.required)
} else if (!isValidCarBodyType(car.body)) {
bodyErrors.push(VALIDATION_MESSAGES.carBody.invalid)
}
if (!isValidCarColor(car.color)) {
bodyErrors.push(VALIDATION_MESSAGES.carColor.invalid)
}
const { washing } = req.body
if (!washing.begin) {
bodyErrors.push(VALIDATION_MESSAGES.washingBegin.required)
} else if (!isValidISODate(washing.begin)) {
bodyErrors.push(VALIDATION_MESSAGES.washingBegin.invalid)
}
if (!washing.end) {
bodyErrors.push(VALIDATION_MESSAGES.washingEnd.required)
} else if (!isValidISODate(washing.end)) {
bodyErrors.push(VALIDATION_MESSAGES.washingEnd.invalid)
}
if (!washing.location) {
bodyErrors.push(VALIDATION_MESSAGES.washingLocation.required)
} else if (!isValidLocation(washing.location)) {
bodyErrors.push(VALIDATION_MESSAGES.washingLocation.invalid)
}
if (bodyErrors.length > 0) {
throw new Error(bodyErrors.join(', '))
}
try {
const order = await OrderModel.create({
phone: customer.phone,
carNumber: car.number,
carBody: car.body,
carColor: car.color,
startWashTime: washing.begin,
endWashTime: washing.end,
location: washing.location,
status: orderStatus.PROGRESS,
notes: '',
created: new Date().toISOString(),
})
res.status(200).send({ success: true, body: order })
} catch (error) {
next(error)
}
})
router.get('/:id', async (req, res, next) => {
const { id } = req.params
if (!mongoose.Types.ObjectId.isValid(id)) {
throw new Error(VALIDATION_MESSAGES.orderId.invalid)
}
try {
const order = await OrderModel.findById(id)
if (!order) {
throw new Error(VALIDATION_MESSAGES.order.notFound)
}
res.status(200).send({ success: true, body: order })
} catch (error) {
next(error)
}
})
router.patch('/:id', async (req, res, next) => {
const { id } = req.params
if (!mongoose.Types.ObjectId.isValid(id)) {
throw new Error(VALIDATION_MESSAGES.orderId.invalid)
}
const bodyErrors = []
const { status } = req.body
if (status) {
if (!isValidOrderStatus(status)) {
bodyErrors.push(VALIDATION_MESSAGES.orderStatus.invalid)
}
}
const { master: masterId } = req.body
if (masterId) {
if (!mongoose.Types.ObjectId.isValid(masterId)) {
bodyErrors.push(VALIDATION_MESSAGES.masterId.invalid)
} else {
try {
const master = await MasterModel.findById(masterId)
if (!master) {
bodyErrors.push(VALIDATION_MESSAGES.master.notFound)
}
} catch (error) {
next(error)
}
}
}
const { notes } = req.body
if (notes) {
if (!isValidOrderNotes(notes)) {
bodyErrors.push(VALIDATION_MESSAGES.orderNotes.invalid)
}
}
if (bodyErrors.length > 0) {
throw new Error(bodyErrors.join(', '))
}
try {
const updateData = {}
if (status) {
updateData.status = status
}
if (masterId) {
updateData.master = masterId
}
if (notes) {
updateData.notes = notes
}
updateData.updated = new Date().toISOString()
const order = await OrderModel.findByIdAndUpdate(
id,
updateData,
{ new: true }
)
if (!order) {
throw new Error(VALIDATION_MESSAGES.order.notFound)
}
res.status(200).send({ success: true, body: order })
} catch (error) {
next(error)
}
})
router.delete('/:id', async (req, res, next) => {
const { id } = req.params
if (!mongoose.Types.ObjectId.isValid(id)) {
throw new Error(VALIDATION_MESSAGES.orderId.invalid)
}
try {
const order = await OrderModel.findByIdAndDelete(id, {
new: true,
})
if (!order) {
throw new Error(VALIDATION_MESSAGES.order.notFound)
}
res.status(200).send({ success: true, body: order })
} catch (error) {
next(error)
}
})
2025-02-22 16:58:58 +03:00
const storage = multer.memoryStorage()
const upload = multer({
storage: storage,
limits: { fileSize: sizeLimitInMegaBytes * 1024 * 1024 },
fileFilter: (req, file, cb) => {
if (allowedMimeTypes.includes(file.mimetype)) {
cb(null, true)
} else {
cb(new Error(VALIDATION_MESSAGES.carImg.invalid.type), false)
}
}
})
const convertFileToBase64 = (file) => {
const base64Image = file.buffer.toString('base64')
return base64Image
}
router.post('/:id/upload-car-img', upload.single('file'), async (req, res) => {
const { id: orderId } = req.params
if (!mongoose.Types.ObjectId.isValid(orderId)) {
throw new Error(VALIDATION_MESSAGES.orderId.invalid)
}
const order = await OrderModel.findById(orderId)
if (!order) {
throw new Error(VALIDATION_MESSAGES.order.notFound)
}
if (!req.file) {
throw new Error(VALIDATION_MESSAGES.carImg.required)
}
const orderCarImg = await OrderCarImgModel.create({
file: convertFileToBase64(req.file),
orderId,
created: new Date().toISOString(),
})
res.status(200).send({ success: true, body: orderCarImg })
})
router.use((err, req, res, next) => {
if (err instanceof multer.MulterError) {
switch (err.message) {
case 'File too large':
return res.status(400).json({ success: false, error: VALIDATION_MESSAGES.carImg.invalid.size })
default:
return res.status(400).json({ success: false, error: err.message })
}
}
throw new Error(err.message)
})
2025-01-18 23:02:45 +03:00
module.exports = router