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 )
}
}
} )
2025-03-03 18:21:32 +03:00
const { v4 : uuidv4 } = require ( "uuid" )
const axios = require ( 'axios' )
const GIGA _CHAT _ACCESS _TOKEN = 'OGJmMDQ4MzAtNDIwNS00NzRmLTkwOTQtNTdmNGNlYzkxZjc0OjFiZDQ0MDM4LTkyYTktNDdiNy04N2M5LWIzNjAwNjBjZTVjNQ=='
const GIGA _CHAT _OAUTH = 'https://ngw.devices.sberbank.ru:9443/api/v2/oauth'
const GIGA _CHAT _API = 'https://gigachat.devices.sberbank.ru/api/v1'
const getToken = async ( req , res ) => {
const rqUID = uuidv4 ( )
const body = new URLSearchParams ( {
scope : "GIGACHAT_API_PERS" ,
} )
const response = await fetch ( GIGA _CHAT _OAUTH , {
method : "POST" ,
headers : {
Authorization : ` Basic ${ GIGA _CHAT _ACCESS _TOKEN } ` ,
"Content-Type" : "application/x-www-form-urlencoded" ,
Accept : "application/json" ,
RqUID : rqUID ,
} ,
body ,
} )
if ( ! response . ok ) {
const errorData = await response . json ( )
console . error ( "Ошибка запроса: " , errorData )
return res . status ( response . status ) . json ( errorData )
}
return await response . json ( )
}
const uploadImage = async ( file , accessToken ) => {
const formData = new FormData ( )
const blob = new Blob ( [ file . buffer ] , { type : file . mimetype } )
formData . append ( 'file' , blob , file . originalname )
formData . append ( 'purpose' , 'general' )
const config = {
maxBodyLength : Infinity ,
headers : {
'Content-Type' : 'multipart/form-data' ,
'Accept' : 'application/json' ,
'Authorization' : ` Bearer ${ accessToken } `
}
}
try {
const response = await axios . post ( ` ${ GIGA _CHAT _API } /files ` , formData , config )
return response . data . id
} catch ( error ) {
console . error ( error )
}
}
const analyzeImage = async ( fileId , token ) => {
const response = await fetch ( ` ${ GIGA _CHAT _API } /chat/completions ` , {
method : "POST" ,
headers : {
"Content-Type" : "application/json" ,
Accept : "application/json" ,
Authorization : ` Bearer ${ token } ` ,
} ,
body : JSON . stringify ( {
model : "GigaChat-Max" ,
stream : false ,
update _interval : 0 ,
messages : [
{
role : "user" ,
content :
` Ты эксперт по оценке степени загрязнения автомобилей. Твоя задача — анализировать фотографии машин и определять степень их загрязнения. Ответ должен содержать: 1. Оценку степени загрязнения в виде числа от 0 до 10, где 0 - без загрязнений, 10 - загрязнена на 100%. 2. Пояснение к оценке в виде абзаца текста с обоснованием выставленной оценки.
Ответ должен быть в виде объекта следующего вида :
{
"value" : число от 0 до 10 ,
"description" : "текст с обоснованием оценки"
} ` ,
attachments : [ fileId ] ,
} ,
] ,
} ) ,
} )
const data = await response . json ( )
return JSON . parse ( data . choices [ 0 ] . message . content )
}
2025-02-22 16:58:58 +03:00
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 )
}
2025-03-03 18:21:32 +03:00
const { access _token } = await getToken ( req , res )
const fileId = await uploadImage ( req . file , access _token )
const { value , description } = await analyzeImage ( fileId , access _token ) ? ? { }
2025-02-22 16:58:58 +03:00
const orderCarImg = await OrderCarImgModel . create ( {
2025-03-03 18:21:32 +03:00
image : convertFileToBase64 ( req . file ) ,
imageRating : value ,
imageDescription : description ,
2025-02-22 16:58:58 +03:00
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 } )
}
}
2025-03-03 18:21:32 +03:00
2025-02-22 16:58:58 +03:00
throw new Error ( err . message )
} )
2025-01-18 23:02:45 +03:00
module . exports = router