Files
shop-server/server/src/routes/user-addresses.js
T
2026-05-21 14:22:03 +05:00

204 lines
8.4 KiB
JavaScript

import { prisma } from '../lib/prisma.js'
function normalizePhoneLite(input) {
const s = String(input || '').trim()
if (!s) return ''
return s.replace(/[\s()-]/g, '')
}
function validateAddressPayload(body, reply) {
const labelRaw = body?.label
const label = labelRaw === null || labelRaw === undefined ? null : String(labelRaw).trim()
if (label !== null && label.length > 40) return reply.code(400).send({ error: 'Метка адреса максимум 40 символов' })
const recipientName = String(body?.recipientName || '').trim()
if (!recipientName) return reply.code(400).send({ error: 'Укажите ФИО получателя' })
if (recipientName.length > 80) return reply.code(400).send({ error: 'ФИО получателя максимум 80 символов' })
const recipientPhone = normalizePhoneLite(body?.recipientPhone)
if (!recipientPhone) return reply.code(400).send({ error: 'Укажите телефон получателя' })
if (!/^\+?\d{7,20}$/.test(recipientPhone)) return reply.code(400).send({ error: 'Некорректный телефон получателя' })
const addressLine = String(body?.addressLine || '').trim()
if (!addressLine) return reply.code(400).send({ error: 'Укажите адрес' })
if (addressLine.length > 200) return reply.code(400).send({ error: 'Адрес максимум 200 символов' })
const commentRaw = body?.comment
const comment = commentRaw === null || commentRaw === undefined ? null : String(commentRaw).trim()
if (comment !== null && comment.length > 200)
return reply.code(400).send({ error: 'Комментарий максимум 200 символов' })
const lat = Number(body?.lat)
const lng = Number(body?.lng)
if (!Number.isFinite(lat) || lat < -90 || lat > 90) return reply.code(400).send({ error: 'Некорректная широта' })
if (!Number.isFinite(lng) || lng < -180 || lng > 180) return reply.code(400).send({ error: 'Некорректная долгота' })
return {
label,
recipientName,
recipientPhone,
addressLine,
comment,
lat,
lng,
}
}
export async function registerUserAddressRoutes(fastify) {
fastify.get('/api/me/addresses', { preHandler: [fastify.authenticate] }, async (request, reply) => {
try {
const userId = request.user.sub
const items = await prisma.shippingAddress.findMany({
where: { userId },
orderBy: [{ isDefault: 'desc' }, { updatedAt: 'desc' }],
})
return { items }
} catch (err) {
request.log.error(err)
return reply.code(500).send({ error: 'Не удалось загрузить адреса' })
}
})
fastify.post('/api/me/addresses', { preHandler: [fastify.authenticate] }, async (request, reply) => {
try {
const userId = request.user.sub
const validated = validateAddressPayload(request.body, reply)
if (!validated) return
const isDefault = Boolean(request.body?.isDefault)
const created = await prisma.$transaction(async (tx) => {
if (isDefault) {
await tx.shippingAddress.updateMany({ where: { userId, isDefault: true }, data: { isDefault: false } })
}
return tx.shippingAddress.create({
data: {
userId,
...validated,
isDefault,
},
})
})
return reply.code(201).send({ item: created })
} catch (err) {
request.log.error(err)
return reply.code(500).send({ error: 'Не удалось создать адрес' })
}
})
fastify.patch('/api/me/addresses/:id', { preHandler: [fastify.authenticate] }, async (request, reply) => {
try {
const userId = request.user.sub
const { id } = request.params
const existing = await prisma.shippingAddress.findFirst({ where: { id, userId } })
if (!existing) return reply.code(404).send({ error: 'Адрес не найден' })
const body = request.body ?? {}
const data = {}
if (body.label !== undefined) {
const labelRaw = body.label
const label = labelRaw === null || labelRaw === undefined ? null : String(labelRaw).trim()
if (label !== null && label.length > 40)
return reply.code(400).send({ error: 'Метка адреса максимум 40 символов' })
data.label = label && label.length ? label : null
}
if (body.recipientName !== undefined) {
const v = String(body.recipientName || '').trim()
if (!v) return reply.code(400).send({ error: 'Укажите ФИО получателя' })
if (v.length > 80) return reply.code(400).send({ error: 'ФИО получателя максимум 80 символов' })
data.recipientName = v
}
if (body.recipientPhone !== undefined) {
const v = normalizePhoneLite(body.recipientPhone)
if (!v) return reply.code(400).send({ error: 'Укажите телефон получателя' })
if (!/^\+?\d{7,20}$/.test(v)) return reply.code(400).send({ error: 'Некорректный телефон получателя' })
data.recipientPhone = v
}
if (body.addressLine !== undefined) {
const v = String(body.addressLine || '').trim()
if (!v) return reply.code(400).send({ error: 'Укажите адрес' })
if (v.length > 200) return reply.code(400).send({ error: 'Адрес максимум 200 символов' })
data.addressLine = v
}
if (body.comment !== undefined) {
const commentRaw = body.comment
const comment = commentRaw === null || commentRaw === undefined ? null : String(commentRaw).trim()
if (comment !== null && comment.length > 200)
return reply.code(400).send({ error: 'Комментарий максимум 200 символов' })
data.comment = comment && comment.length ? comment : null
}
if (body.lat !== undefined) {
const lat = Number(body.lat)
if (!Number.isFinite(lat) || lat < -90 || lat > 90)
return reply.code(400).send({ error: 'Некорректная широта' })
data.lat = lat
}
if (body.lng !== undefined) {
const lng = Number(body.lng)
if (!Number.isFinite(lng) || lng < -180 || lng > 180)
return reply.code(400).send({ error: 'Некорректная долгота' })
data.lng = lng
}
const setDefault = body.isDefault === true
const updated = await prisma.$transaction(async (tx) => {
if (setDefault) {
await tx.shippingAddress.updateMany({ where: { userId, isDefault: true }, data: { isDefault: false } })
}
return tx.shippingAddress.update({
where: { id },
data: {
...data,
...(setDefault ? { isDefault: true } : {}),
},
})
})
return { item: updated }
} catch (err) {
request.log.error(err)
return reply.code(500).send({ error: 'Не удалось обновить адрес' })
}
})
fastify.delete('/api/me/addresses/:id', { preHandler: [fastify.authenticate] }, async (request, reply) => {
try {
const userId = request.user.sub
const { id } = request.params
const existing = await prisma.shippingAddress.findFirst({ where: { id, userId } })
if (!existing) return reply.code(404).send({ error: 'Адрес не найден' })
await prisma.shippingAddress.delete({ where: { id } })
return reply.code(204).send()
} catch (err) {
request.log.error(err)
return reply.code(500).send({ error: 'Не удалось удалить адрес' })
}
})
fastify.post('/api/me/addresses/:id/default', { preHandler: [fastify.authenticate] }, async (request, reply) => {
try {
const userId = request.user.sub
const { id } = request.params
const existing = await prisma.shippingAddress.findFirst({ where: { id, userId } })
if (!existing) return reply.code(404).send({ error: 'Адрес не найден' })
const updated = await prisma.$transaction(async (tx) => {
await tx.shippingAddress.updateMany({ where: { userId, isDefault: true }, data: { isDefault: false } })
return tx.shippingAddress.update({ where: { id }, data: { isDefault: true } })
})
return { item: updated }
} catch (err) {
request.log.error(err)
return reply.code(500).send({ error: 'Не удалось установить адрес по умолчанию' })
}
})
}