diff --git a/client/src/app/routes/index.tsx b/client/src/app/routes/index.tsx
index a2b1a20..5e9177e 100644
--- a/client/src/app/routes/index.tsx
+++ b/client/src/app/routes/index.tsx
@@ -2,13 +2,11 @@ import { lazy, Suspense } from 'react'
import { Route, Routes } from 'react-router-dom'
import { MainLayout } from '@/app/layout/MainLayout'
import { AboutPage } from '@/pages/about'
-// import { AdminLayoutPage } from '@/pages/admin-layout'
import { AuthCallbackPage, AuthPage } from '@/pages/auth'
import { CartPage } from '@/pages/cart'
import { CheckoutPage } from '@/pages/checkout'
import { HomePage } from '@/pages/home'
import { InfoPage } from '@/pages/info'
-// import { MeLayoutPage } from '@/pages/me'
import { NotFoundPage } from '@/pages/not-found'
import { PrivacyPolicyPage } from '@/pages/privacy-policy'
import { ProductPage } from '@/pages/product'
diff --git a/client/src/entities/cart/index.ts b/client/src/entities/cart/index.ts
new file mode 100644
index 0000000..6870839
--- /dev/null
+++ b/client/src/entities/cart/index.ts
@@ -0,0 +1,3 @@
+export type { CartItem } from './model/types'
+export { fetchMyCart, addToCart, setCartQty, removeCartItem } from './api/cart-api'
+export type { CartResponse } from './api/cart-api'
diff --git a/client/src/entities/notification/index.ts b/client/src/entities/notification/index.ts
new file mode 100644
index 0000000..2394f9c
--- /dev/null
+++ b/client/src/entities/notification/index.ts
@@ -0,0 +1,7 @@
+export {
+ fetchUserNotificationSettings,
+ updateUserNotificationSettings,
+ fetchAdminNotificationSettings,
+ updateAdminNotificationSettings,
+} from './api/notifications-api'
+export type { UserNotificationSettings, AdminNotificationSettings } from './api/notifications-api'
diff --git a/client/src/entities/order/index.ts b/client/src/entities/order/index.ts
new file mode 100644
index 0000000..50d153d
--- /dev/null
+++ b/client/src/entities/order/index.ts
@@ -0,0 +1,9 @@
+export {
+ fetchMyOrders,
+ createOrder,
+ confirmOrderReceived,
+ fetchMyOrder,
+ fetchOrderReviewEligibility,
+} from './api/order-api'
+export { createOrderPayment, getOrderPaymentStatus, postOrderMessage } from './api/order-api'
+export type { OrderListResponse, OrderDetailResponse } from './api/order-api'
diff --git a/client/src/entities/product/index.ts b/client/src/entities/product/index.ts
new file mode 100644
index 0000000..98bfb2e
--- /dev/null
+++ b/client/src/entities/product/index.ts
@@ -0,0 +1,2 @@
+export { fetchPublicProducts, fetchPublicProduct, fetchCategories } from './api/product-api'
+export type { PublicProductsResponse } from './api/product-api'
diff --git a/client/src/entities/review/index.ts b/client/src/entities/review/index.ts
new file mode 100644
index 0000000..48f31fd
--- /dev/null
+++ b/client/src/entities/review/index.ts
@@ -0,0 +1,12 @@
+export {
+ postProductReview,
+ uploadReviewImage,
+ fetchLatestApprovedReviews,
+ fetchPublicProductReviews,
+} from './api/reviews-api'
+export type {
+ PublicReviewFeedItem,
+ PublicReviewsLatestResponse,
+ PublicProductReviewItem,
+ PublicProductReviewsResponse,
+} from './api/reviews-api'
diff --git a/client/src/entities/user/index.ts b/client/src/entities/user/index.ts
new file mode 100644
index 0000000..cfb7e7f
--- /dev/null
+++ b/client/src/entities/user/index.ts
@@ -0,0 +1,10 @@
+export type { AdminUser, ShippingAddress } from './model/types'
+export { fetchAdminUsers, createAdminUser, updateAdminUser, deleteAdminUser } from './api/user-api'
+export type { AdminUsersListResponse } from './api/user-api'
+export {
+ fetchMyAddresses,
+ createMyAddress,
+ updateMyAddress,
+ deleteMyAddress,
+ setMyAddressDefault,
+} from './api/address-api'
diff --git a/client/src/pages/admin-layout/ui/AdminLayoutPage.tsx b/client/src/pages/admin-layout/ui/AdminLayoutPage.tsx
index 69333d8..90c5cb1 100644
--- a/client/src/pages/admin-layout/ui/AdminLayoutPage.tsx
+++ b/client/src/pages/admin-layout/ui/AdminLayoutPage.tsx
@@ -60,7 +60,7 @@ export function AdminLayoutPage() {
{ to: '/admin/orders', label: 'Заказы', icon: },
{ to: '/admin/reviews', label: 'Отзывы', icon: },
{ to: '/admin/users', label: 'Пользователи', icon: },
- { to: '/admin/notifications', label: 'Оповещения', icon: },
+ { to: '/admin/notifications', label: 'Уведомления', icon: },
],
[],
)
diff --git a/client/src/pages/admin-layout/ui/AdminNotificationsPage.tsx b/client/src/pages/admin-layout/ui/AdminNotificationsPage.tsx
index 3508376..71645b2 100644
--- a/client/src/pages/admin-layout/ui/AdminNotificationsPage.tsx
+++ b/client/src/pages/admin-layout/ui/AdminNotificationsPage.tsx
@@ -47,10 +47,10 @@ export function AdminNotificationsPage() {
return (
- Оповещения
+ Уведомления
- Настройка оповещений администратора.
+ Настройка уведомлений администратора.
{error && (
diff --git a/client/src/pages/me/ui/MeLayoutPage.tsx b/client/src/pages/me/ui/MeLayoutPage.tsx
index 0c24ad1..385a927 100644
--- a/client/src/pages/me/ui/MeLayoutPage.tsx
+++ b/client/src/pages/me/ui/MeLayoutPage.tsx
@@ -57,7 +57,7 @@ export function MeLayoutPage() {
{ to: '/me/messages', label: 'Сообщения', icon: },
{ to: '/me/settings', label: 'Настройки', icon: },
{ to: '/me/addresses', label: 'Адреса доставки', icon: },
- { to: '/me/notifications', label: 'Оповещения', icon: },
+ { to: '/me/notifications', label: 'Уведомления', icon: },
],
[],
)
diff --git a/client/src/pages/me/ui/MePage.tsx b/client/src/pages/me/ui/MePage.tsx
deleted file mode 100644
index 107da67..0000000
--- a/client/src/pages/me/ui/MePage.tsx
+++ /dev/null
@@ -1,134 +0,0 @@
-import Alert from '@mui/material/Alert'
-import Box from '@mui/material/Box'
-import Button from '@mui/material/Button'
-import Divider from '@mui/material/Divider'
-import Stack from '@mui/material/Stack'
-import TextField from '@mui/material/TextField'
-import Typography from '@mui/material/Typography'
-import { useUnit } from 'effector-react'
-import { useForm } from 'react-hook-form'
-import {
- $requestEmailChangeCodeError,
- $updateProfileError,
- $user,
- $verifyEmailChangeError,
- requestEmailChangeCodeFx,
- updateProfileFx,
- verifyEmailChangeFx,
-} from '@/shared/model/auth'
-import type { AxiosError } from 'axios'
-
-function getApiErrorMessage(error: unknown): string | null {
- const e = error as AxiosError<{ error?: string }>
- const msg = e?.response?.data?.error
- return msg ? String(msg) : null
-}
-
-export function MePage() {
- const user = useUnit($user)
- const pendingEmailReq = useUnit(requestEmailChangeCodeFx.pending)
- const pendingEmailVerify = useUnit(verifyEmailChangeFx.pending)
- const pendingProfile = useUnit(updateProfileFx.pending)
- const errorEmailReq = useUnit($requestEmailChangeCodeError)
- const errorProfile = useUnit($updateProfileError)
- const errorEmailVerify = useUnit($verifyEmailChangeError)
-
- const emailForm = useForm<{ newEmail: string; code: string }>({
- defaultValues: { newEmail: '', code: '' },
- mode: 'onChange',
- })
-
- const profileForm = useForm<{ displayName: string }>({
- defaultValues: { displayName: user?.displayName ? String(user.displayName) : '' },
- mode: 'onChange',
- })
-
- const emailErrorMsg = getApiErrorMessage(errorEmailReq) ?? getApiErrorMessage(errorEmailVerify)
- const profileErrorMsg = getApiErrorMessage(errorProfile)
-
- if (!user) {
- return Нужно войти. Перейдите на страницу «Вход».
- }
-
- return (
-
-
- Профиль
-
-
- Текущая почта: {user.email}
-
-
- {emailErrorMsg && (
-
- {emailErrorMsg}
-
- )}
- {profileErrorMsg && (
-
- {profileErrorMsg}
-
- )}
-
-
-
-
- Имя / ник
-
-
-
-
-
-
-
-
-
-
-
- Смена почты
-
-
-
-
-
-
-
-
-
-
-
-
- )
-}
diff --git a/client/src/pages/me/ui/sections/NotificationsPage.tsx b/client/src/pages/me/ui/sections/NotificationsPage.tsx
index 85524c3..f1b07be 100644
--- a/client/src/pages/me/ui/sections/NotificationsPage.tsx
+++ b/client/src/pages/me/ui/sections/NotificationsPage.tsx
@@ -57,7 +57,7 @@ export function NotificationsPage() {
return (
- Оповещения
+ Уведомления
Настройте, какие уведомления вы хотите получать на почту.
@@ -78,7 +78,7 @@ export function NotificationsPage() {
onChange={(e) => handleToggle('globalEnabled', e.target.checked)}
/>
}
- label={Получать оповещения}
+ label={Получать уведомления}
/>
Включите, чтобы получать уведомления о заказах на почту.
diff --git a/client/src/pages/terms/ui/TermsPage.tsx b/client/src/pages/terms/ui/TermsPage.tsx
index e5340bf..7699ea3 100644
--- a/client/src/pages/terms/ui/TermsPage.tsx
+++ b/client/src/pages/terms/ui/TermsPage.tsx
@@ -1,7 +1,7 @@
import Box from '@mui/material/Box'
import Paper from '@mui/material/Paper'
import Typography from '@mui/material/Typography'
-import { STORE_EMAIL, STORE_PUBLIC_SITE_URL } from '@/shared/config'
+import { STORE_EMAIL, STORE_PHONE, STORE_PUBLIC_SITE_URL } from '@/shared/config'
const SITE_URL = STORE_PUBLIC_SITE_URL || (typeof window !== 'undefined' ? window.location.origin : '')
@@ -138,8 +138,8 @@ const sections = [
`ИНН: ${OP_INN}`,
`ОГРН: ${OP_OGRN}`,
`Адрес: ${OP_ADDR}`,
- `Телефон: +7 (900) 000-00-00`, // TODO: заменить на реальный номер телефона
- `Email: ${STORE_EMAIL}`, // TODO: заменить на реальный email при настройке STORE_EMAIL
+ `Телефон: ${STORE_PHONE}`,
+ `Email: ${STORE_EMAIL}`,
],
},
]
diff --git a/server/prisma/prisma/dev.db b/server/prisma/prisma/dev.db
index 8a10718..01bce42 100644
Binary files a/server/prisma/prisma/dev.db and b/server/prisma/prisma/dev.db differ
diff --git a/server/src/routes/api/admin-categories.js b/server/src/routes/api/admin-categories.js
index f252cbd..e33bc91 100644
--- a/server/src/routes/api/admin-categories.js
+++ b/server/src/routes/api/admin-categories.js
@@ -6,116 +6,136 @@ import {
import { prisma } from '../../lib/prisma.js'
export async function registerAdminCategoryRoutes(fastify) {
- fastify.get('/api/admin/categories', { preHandler: [fastify.verifyAdmin] }, async () => {
- const items = await prisma.category.findMany({
- orderBy: [{ sort: 'asc' }, { name: 'asc' }],
- })
- return { items }
+ fastify.get('/api/admin/categories', { preHandler: [fastify.verifyAdmin] }, async (request, reply) => {
+ try {
+ const items = await prisma.category.findMany({
+ orderBy: [{ sort: 'asc' }, { name: 'asc' }],
+ })
+ return { items }
+ } catch (err) {
+ request.log.error(err)
+ return reply.code(500).send({ error: 'Не удалось загрузить категории' })
+ }
})
fastify.post('/api/admin/categories', { preHandler: [fastify.verifyAdmin] }, async (request, reply) => {
- const body = request.body ?? {}
- const name = String(body.name ?? '').trim()
- if (!name) {
- reply.code(400).send({ error: 'Укажите название категории' })
- return
+ try {
+ const body = request.body ?? {}
+ const name = String(body.name ?? '').trim()
+ if (!name) {
+ reply.code(400).send({ error: 'Укажите название категории' })
+ return
+ }
+ const slug = String(body.slug ?? '').trim() || request.server.slugify(name) || `cat-${Date.now()}`
+ if (isUnspecifiedCategorySlug(slug)) {
+ reply.code(400).send({ error: `Slug «${UNSPECIFIED_CATEGORY_SLUG}» зарезервирован` })
+ return
+ }
+ const sort = body.sort !== undefined && body.sort !== null && body.sort !== '' ? Number(body.sort) : undefined
+ const exists = await prisma.category.findUnique({ where: { slug } })
+ if (exists) {
+ reply.code(409).send({ error: 'Такой slug уже занят' })
+ return
+ }
+ const category = await prisma.category.create({
+ data: {
+ name,
+ slug,
+ sort: Number.isFinite(sort) ? Math.round(sort) : 0,
+ },
+ })
+ reply.code(201).send(category)
+ } catch (err) {
+ request.log.error(err)
+ return reply.code(500).send({ error: 'Не удалось создать категорию' })
}
- const slug = String(body.slug ?? '').trim() || request.server.slugify(name) || `cat-${Date.now()}`
- if (isUnspecifiedCategorySlug(slug)) {
- reply.code(400).send({ error: `Slug «${UNSPECIFIED_CATEGORY_SLUG}» зарезервирован` })
- return
- }
- const sort = body.sort !== undefined && body.sort !== null && body.sort !== '' ? Number(body.sort) : undefined
- const exists = await prisma.category.findUnique({ where: { slug } })
- if (exists) {
- reply.code(409).send({ error: 'Такой slug уже занят' })
- return
- }
- const category = await prisma.category.create({
- data: {
- name,
- slug,
- sort: Number.isFinite(sort) ? Math.round(sort) : 0,
- },
- })
- reply.code(201).send(category)
})
fastify.patch('/api/admin/categories/:id', { preHandler: [fastify.verifyAdmin] }, async (request, reply) => {
- const { id } = request.params
- const body = request.body ?? {}
- const existing = await prisma.category.findUnique({ where: { id } })
- if (!existing) {
- reply.code(404).send({ error: 'Категория не найдена' })
- return
- }
+ try {
+ const { id } = request.params
+ const body = request.body ?? {}
+ const existing = await prisma.category.findUnique({ where: { id } })
+ if (!existing) {
+ reply.code(404).send({ error: 'Категория не найдена' })
+ return
+ }
- const data = {}
- if (body.name !== undefined) data.name = String(body.name ?? '').trim()
- if (body.sort !== undefined) {
- const s = Number(body.sort)
- if (!Number.isFinite(s)) {
- reply.code(400).send({ error: 'Некорректный sort' })
- return
- }
- data.sort = Math.round(s)
- }
- if (body.slug !== undefined) {
- const s = String(body.slug ?? '').trim()
- if (isUnspecifiedCategorySlug(existing.slug) && s !== UNSPECIFIED_CATEGORY_SLUG) {
- reply.code(400).send({ error: 'Нельзя сменить slug служебной категории «Не указано»' })
- return
- }
- if (!s) {
- reply.code(400).send({ error: 'Slug не может быть пустым' })
- return
- }
- if (s !== existing.slug) {
- if (isUnspecifiedCategorySlug(s)) {
- reply.code(400).send({ error: `Slug «${UNSPECIFIED_CATEGORY_SLUG}» зарезервирован` })
+ const data = {}
+ if (body.name !== undefined) data.name = String(body.name ?? '').trim()
+ if (body.sort !== undefined) {
+ const s = Number(body.sort)
+ if (!Number.isFinite(s)) {
+ reply.code(400).send({ error: 'Некорректный sort' })
return
}
- const clash = await prisma.category.findFirst({ where: { slug: s, NOT: { id } } })
- if (clash) {
- reply.code(409).send({ error: 'Такой slug уже занят' })
+ data.sort = Math.round(s)
+ }
+ if (body.slug !== undefined) {
+ const s = String(body.slug ?? '').trim()
+ if (isUnspecifiedCategorySlug(existing.slug) && s !== UNSPECIFIED_CATEGORY_SLUG) {
+ reply.code(400).send({ error: 'Нельзя сменить slug служебной категории «Не указано»' })
return
}
+ if (!s) {
+ reply.code(400).send({ error: 'Slug не может быть пустым' })
+ return
+ }
+ if (s !== existing.slug) {
+ if (isUnspecifiedCategorySlug(s)) {
+ reply.code(400).send({ error: `Slug «${UNSPECIFIED_CATEGORY_SLUG}» зарезервирован` })
+ return
+ }
+ const clash = await prisma.category.findFirst({ where: { slug: s, NOT: { id } } })
+ if (clash) {
+ reply.code(409).send({ error: 'Такой slug уже занят' })
+ return
+ }
+ }
+ data.slug = s
}
- data.slug = s
- }
- if (Object.keys(data).length === 0) {
- return existing
- }
- if (data.name !== undefined && !data.name) {
- reply.code(400).send({ error: 'Укажите название' })
- return
- }
+ if (Object.keys(data).length === 0) {
+ return existing
+ }
+ if (data.name !== undefined && !data.name) {
+ reply.code(400).send({ error: 'Укажите название' })
+ return
+ }
- const updated = await prisma.category.update({ where: { id }, data })
- return updated
+ const updated = await prisma.category.update({ where: { id }, data })
+ return updated
+ } catch (err) {
+ request.log.error(err)
+ return reply.code(500).send({ error: 'Не удалось обновить категорию' })
+ }
})
fastify.delete('/api/admin/categories/:id', { preHandler: [fastify.verifyAdmin] }, async (request, reply) => {
- const { id } = request.params
- const existing = await prisma.category.findUnique({ where: { id } })
- if (!existing) {
- reply.code(404).send({ error: 'Категория не найдена' })
- return
- }
- if (isUnspecifiedCategorySlug(existing.slug)) {
- reply.code(409).send({ error: 'Служебную категорию «Не указано» нельзя удалить' })
- return
- }
+ try {
+ const { id } = request.params
+ const existing = await prisma.category.findUnique({ where: { id } })
+ if (!existing) {
+ reply.code(404).send({ error: 'Категория не найдена' })
+ return
+ }
+ if (isUnspecifiedCategorySlug(existing.slug)) {
+ reply.code(409).send({ error: 'Служебную категорию «Не указано» нельзя удалить' })
+ return
+ }
- const fallback = await getOrCreateUnspecifiedCategory()
- await prisma.$transaction([
- prisma.product.updateMany({
- where: { categoryId: id },
- data: { categoryId: fallback.id },
- }),
- prisma.category.delete({ where: { id } }),
- ])
- return reply.code(204).send()
+ const fallback = await getOrCreateUnspecifiedCategory()
+ await prisma.$transaction([
+ prisma.product.updateMany({
+ where: { categoryId: id },
+ data: { categoryId: fallback.id },
+ }),
+ prisma.category.delete({ where: { id } }),
+ ])
+ return reply.code(204).send()
+ } catch (err) {
+ request.log.error(err)
+ return reply.code(500).send({ error: 'Не удалось удалить категорию' })
+ }
})
}
diff --git a/server/src/routes/api/catalog-slider.js b/server/src/routes/api/catalog-slider.js
index 4e494c6..76c5e61 100644
--- a/server/src/routes/api/catalog-slider.js
+++ b/server/src/routes/api/catalog-slider.js
@@ -3,89 +3,104 @@ import { prisma } from '../../lib/prisma.js'
const MAX_SLIDES = 20
export async function registerCatalogSliderRoutes(fastify) {
- fastify.get('/api/catalog-slider', async () => {
- const slides = await prisma.catalogSliderSlide.findMany({
- orderBy: { sortOrder: 'asc' },
- include: { galleryImage: true },
- })
- return {
- slides: slides.map((s) => ({
- id: s.id,
- url: s.galleryImage.url,
- caption: s.caption,
- })),
+ fastify.get('/api/catalog-slider', async (request, reply) => {
+ try {
+ const slides = await prisma.catalogSliderSlide.findMany({
+ orderBy: { sortOrder: 'asc' },
+ include: { galleryImage: true },
+ })
+ return {
+ slides: slides.map((s) => ({
+ id: s.id,
+ url: s.galleryImage.url,
+ caption: s.caption,
+ })),
+ }
+ } catch (err) {
+ request.log.error(err)
+ return reply.code(500).send({ error: 'Не удалось загрузить слайдер' })
}
})
- fastify.get('/api/admin/catalog-slider', { preHandler: [fastify.verifyAdmin] }, async () => {
- const slides = await prisma.catalogSliderSlide.findMany({
- orderBy: { sortOrder: 'asc' },
- include: { galleryImage: true },
- })
- return {
- slides: slides.map((s) => ({
- id: s.id,
- galleryImageId: s.galleryImageId,
- url: s.galleryImage.url,
- caption: s.caption,
- })),
+ fastify.get('/api/admin/catalog-slider', { preHandler: [fastify.verifyAdmin] }, async (request, reply) => {
+ try {
+ const slides = await prisma.catalogSliderSlide.findMany({
+ orderBy: { sortOrder: 'asc' },
+ include: { galleryImage: true },
+ })
+ return {
+ slides: slides.map((s) => ({
+ id: s.id,
+ galleryImageId: s.galleryImageId,
+ url: s.galleryImage.url,
+ caption: s.caption,
+ })),
+ }
+ } catch (err) {
+ request.log.error(err)
+ return reply.code(500).send({ error: 'Не удалось загрузить слайдер' })
}
})
fastify.put('/api/admin/catalog-slider', { preHandler: [fastify.verifyAdmin] }, async (request, reply) => {
- const body = request.body ?? {}
- const rawSlides = body.slides
- if (!Array.isArray(rawSlides)) {
- return reply.code(400).send({ error: 'Ожидается slides: массив' })
- }
- if (rawSlides.length > MAX_SLIDES) {
- return reply.code(400).send({ error: `Не более ${MAX_SLIDES} слайдов` })
- }
+ try {
+ const body = request.body ?? {}
+ const rawSlides = body.slides
+ if (!Array.isArray(rawSlides)) {
+ return reply.code(400).send({ error: 'Ожидается slides: массив' })
+ }
+ if (rawSlides.length > MAX_SLIDES) {
+ return reply.code(400).send({ error: `Не более ${MAX_SLIDES} слайдов` })
+ }
- const seenGalleryIds = new Set()
- const normalized = []
- for (let i = 0; i < rawSlides.length; i++) {
- const row = rawSlides[i]
- const galleryImageId = String(row?.galleryImageId ?? '').trim()
- if (!galleryImageId) {
- return reply.code(400).send({ error: `Слайд ${i + 1}: укажите galleryImageId` })
+ const seenGalleryIds = new Set()
+ const normalized = []
+ for (let i = 0; i < rawSlides.length; i++) {
+ const row = rawSlides[i]
+ const galleryImageId = String(row?.galleryImageId ?? '').trim()
+ if (!galleryImageId) {
+ return reply.code(400).send({ error: `Слайд ${i + 1}: укажите galleryImageId` })
+ }
+ if (seenGalleryIds.has(galleryImageId)) {
+ return reply.code(400).send({ error: 'Одно изображение нельзя добавить дважды' })
+ }
+ seenGalleryIds.add(galleryImageId)
+ const img = await prisma.galleryImage.findUnique({ where: { id: galleryImageId } })
+ if (!img) {
+ return reply.code(400).send({ error: `Изображение не найдено: ${galleryImageId}` })
+ }
+ const caption = row?.caption == null ? '' : String(row.caption).slice(0, 500)
+ normalized.push({ galleryImageId, caption, sortOrder: i })
}
- if (seenGalleryIds.has(galleryImageId)) {
- return reply.code(400).send({ error: 'Одно изображение нельзя добавить дважды' })
- }
- seenGalleryIds.add(galleryImageId)
- const img = await prisma.galleryImage.findUnique({ where: { id: galleryImageId } })
- if (!img) {
- return reply.code(400).send({ error: `Изображение не найдено: ${galleryImageId}` })
- }
- const caption = row?.caption == null ? '' : String(row.caption).slice(0, 500)
- normalized.push({ galleryImageId, caption, sortOrder: i })
- }
- await prisma.$transaction(async (tx) => {
- await tx.catalogSliderSlide.deleteMany({})
- for (const n of normalized) {
- await tx.catalogSliderSlide.create({
- data: {
- sortOrder: n.sortOrder,
- caption: n.caption,
- galleryImageId: n.galleryImageId,
- },
- })
- }
- })
+ await prisma.$transaction(async (tx) => {
+ await tx.catalogSliderSlide.deleteMany({})
+ for (const n of normalized) {
+ await tx.catalogSliderSlide.create({
+ data: {
+ sortOrder: n.sortOrder,
+ caption: n.caption,
+ galleryImageId: n.galleryImageId,
+ },
+ })
+ }
+ })
- const slides = await prisma.catalogSliderSlide.findMany({
- orderBy: { sortOrder: 'asc' },
- include: { galleryImage: true },
- })
- return {
- slides: slides.map((s) => ({
- id: s.id,
- galleryImageId: s.galleryImageId,
- url: s.galleryImage.url,
- caption: s.caption,
- })),
+ const slides = await prisma.catalogSliderSlide.findMany({
+ orderBy: { sortOrder: 'asc' },
+ include: { galleryImage: true },
+ })
+ return {
+ slides: slides.map((s) => ({
+ id: s.id,
+ galleryImageId: s.galleryImageId,
+ url: s.galleryImage.url,
+ caption: s.caption,
+ })),
+ }
+ } catch (err) {
+ request.log.error(err)
+ return reply.code(500).send({ error: 'Не удалось обновить слайдер' })
}
})
}
diff --git a/server/src/routes/auth.js b/server/src/routes/auth.js
index 3e25fe5..de8773f 100644
--- a/server/src/routes/auth.js
+++ b/server/src/routes/auth.js
@@ -123,8 +123,7 @@ export async function registerAuthRoutes(fastify) {
const avatarRaw = request.body?.avatar
const avatar = avatarRaw === null || avatarRaw === undefined ? undefined : String(avatarRaw).trim()
const avatarTypeRaw = request.body?.avatarType
- const avatarType =
- avatarTypeRaw === null || avatarTypeRaw === undefined ? undefined : String(avatarTypeRaw).trim()
+ const avatarType = avatarTypeRaw === null || avatarTypeRaw === undefined ? undefined : String(avatarTypeRaw).trim()
const avatarStyleRaw = request.body?.avatarStyle
const avatarStyle =
avatarStyleRaw === null || avatarStyleRaw === undefined ? undefined : String(avatarStyleRaw).trim()
diff --git a/server/src/routes/user-addresses.js b/server/src/routes/user-addresses.js
index 2440907..54d3659 100644
--- a/server/src/routes/user-addresses.js
+++ b/server/src/routes/user-addresses.js
@@ -45,133 +45,159 @@ function validateAddressPayload(body, reply) {
}
export async function registerUserAddressRoutes(fastify) {
- fastify.get('/api/me/addresses', { preHandler: [fastify.authenticate] }, async (request) => {
- const userId = request.user.sub
- const items = await prisma.shippingAddress.findMany({
- where: { userId },
- orderBy: [{ isDefault: 'desc' }, { updatedAt: 'desc' }],
- })
- return { items }
+ 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) => {
- const userId = request.user.sub
- const validated = validateAddressPayload(request.body, reply)
- if (!validated) return
+ 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,
- },
+ 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 })
+ 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) => {
- 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: 'Адрес не найден' })
+ 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 = {}
+ 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 } })
+ 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
}
- return tx.shippingAddress.update({
- where: { id },
- data: {
- ...data,
- ...(setDefault ? { isDefault: true } : {}),
- },
- })
- })
- return { item: updated }
+ 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) => {
- 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: 'Адрес не найден' })
+ 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()
+ 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) => {
- 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: 'Адрес не найден' })
+ 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 } })
- })
+ 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 }
+ return { item: updated }
+ } catch (err) {
+ request.log.error(err)
+ return reply.code(500).send({ error: 'Не удалось установить адрес по умолчанию' })
+ }
})
}
diff --git a/server/src/routes/user-cart.js b/server/src/routes/user-cart.js
index 136453b..8a5ba0e 100644
--- a/server/src/routes/user-cart.js
+++ b/server/src/routes/user-cart.js
@@ -1,76 +1,96 @@
import { prisma } from '../lib/prisma.js'
export async function registerUserCartRoutes(fastify) {
- fastify.get('/api/me/cart', { preHandler: [fastify.authenticate] }, async (request) => {
- const userId = request.user.sub
- const items = await prisma.cartItem.findMany({
- where: { userId },
- include: { product: { include: { category: true, images: { orderBy: { sort: 'asc' } } } } },
- orderBy: { createdAt: 'asc' },
- })
- return {
- items: items.map((x) => ({
- id: x.id,
- qty: x.qty,
- product: x.product,
- })),
+ fastify.get('/api/me/cart', { preHandler: [fastify.authenticate] }, async (request, reply) => {
+ try {
+ const userId = request.user.sub
+ const items = await prisma.cartItem.findMany({
+ where: { userId },
+ include: { product: { include: { category: true, images: { orderBy: { sort: 'asc' } } } } },
+ orderBy: { createdAt: 'asc' },
+ })
+ return {
+ items: items.map((x) => ({
+ id: x.id,
+ qty: x.qty,
+ product: x.product,
+ })),
+ }
+ } catch (err) {
+ request.log.error(err)
+ return reply.code(500).send({ error: 'Не удалось загрузить корзину' })
}
})
fastify.post('/api/me/cart/items', { preHandler: [fastify.authenticate] }, async (request, reply) => {
- const userId = request.user.sub
- const productId = String(request.body?.productId || '').trim()
- const qtyRaw = request.body?.qty
- const qty = qtyRaw === undefined || qtyRaw === null || qtyRaw === '' ? 1 : Number(qtyRaw)
+ try {
+ const userId = request.user.sub
+ const productId = String(request.body?.productId || '').trim()
+ const qtyRaw = request.body?.qty
+ const qty = qtyRaw === undefined || qtyRaw === null || qtyRaw === '' ? 1 : Number(qtyRaw)
- if (!productId) return reply.code(400).send({ error: 'productId обязателен' })
- if (!Number.isFinite(qty) || qty <= 0) return reply.code(400).send({ error: 'qty должен быть > 0' })
+ if (!productId) return reply.code(400).send({ error: 'productId обязателен' })
+ if (!Number.isFinite(qty) || qty <= 0) return reply.code(400).send({ error: 'qty должен быть > 0' })
- const product = await prisma.product.findFirst({ where: { id: productId, published: true } })
- if (!product) return reply.code(404).send({ error: 'Товар не найден' })
+ const product = await prisma.product.findFirst({ where: { id: productId, published: true } })
+ if (!product) return reply.code(404).send({ error: 'Товар не найден' })
- const available = product.quantity
- const existing = await prisma.cartItem.findUnique({ where: { userId_productId: { userId, productId } } })
- const nextQty = (existing?.qty ?? 0) + Math.floor(qty)
- if (nextQty > available) return reply.code(409).send({ error: `Доступно: ${available} шт.` })
+ const available = product.quantity
+ const existing = await prisma.cartItem.findUnique({ where: { userId_productId: { userId, productId } } })
+ const nextQty = (existing?.qty ?? 0) + Math.floor(qty)
+ if (nextQty > available) return reply.code(409).send({ error: `Доступно: ${available} шт.` })
- const item = await prisma.cartItem.upsert({
- where: { userId_productId: { userId, productId } },
- update: { qty: nextQty },
- create: { userId, productId, qty: nextQty },
- })
- return reply.code(201).send({ item })
+ const item = await prisma.cartItem.upsert({
+ where: { userId_productId: { userId, productId } },
+ update: { qty: nextQty },
+ create: { userId, productId, qty: nextQty },
+ })
+ return reply.code(201).send({ item })
+ } catch (err) {
+ request.log.error(err)
+ return reply.code(500).send({ error: 'Не удалось добавить в корзину' })
+ }
})
fastify.patch('/api/me/cart/items/:id', { preHandler: [fastify.authenticate] }, async (request, reply) => {
- const userId = request.user.sub
- const { id } = request.params
- const qtyRaw = request.body?.qty
- const qty = Number(qtyRaw)
- if (!Number.isFinite(qty) || qty < 0) return reply.code(400).send({ error: 'qty должен быть ≥ 0' })
+ try {
+ const userId = request.user.sub
+ const { id } = request.params
+ const qtyRaw = request.body?.qty
+ const qty = Number(qtyRaw)
+ if (!Number.isFinite(qty) || qty < 0) return reply.code(400).send({ error: 'qty должен быть ≥ 0' })
- const existing = await prisma.cartItem.findFirst({ where: { id, userId }, include: { product: true } })
- if (!existing) return reply.code(404).send({ error: 'Позиция корзины не найдена' })
+ const existing = await prisma.cartItem.findFirst({ where: { id, userId }, include: { product: true } })
+ if (!existing) return reply.code(404).send({ error: 'Позиция корзины не найдена' })
- if (qty === 0) {
- await prisma.cartItem.delete({ where: { id } })
- return reply.code(204).send()
+ if (qty === 0) {
+ await prisma.cartItem.delete({ where: { id } })
+ return reply.code(204).send()
+ }
+
+ const available = existing.product.quantity
+ const nextQty = Math.floor(qty)
+ if (nextQty > available) return reply.code(409).send({ error: `Доступно: ${available} шт.` })
+
+ const updated = await prisma.cartItem.update({ where: { id }, data: { qty: nextQty } })
+ return { item: updated }
+ } catch (err) {
+ request.log.error(err)
+ return reply.code(500).send({ error: 'Не удалось обновить количество' })
}
-
- const available = existing.product.quantity
- const nextQty = Math.floor(qty)
- if (nextQty > available) return reply.code(409).send({ error: `Доступно: ${available} шт.` })
-
- const updated = await prisma.cartItem.update({ where: { id }, data: { qty: nextQty } })
- return { item: updated }
})
fastify.delete('/api/me/cart/items/:id', { preHandler: [fastify.authenticate] }, async (request, reply) => {
- const userId = request.user.sub
- const { id } = request.params
- const existing = await prisma.cartItem.findFirst({ where: { id, userId } })
- if (!existing) return reply.code(404).send({ error: 'Позиция корзины не найдена' })
- await prisma.cartItem.delete({ where: { id } })
- return reply.code(204).send()
+ try {
+ const userId = request.user.sub
+ const { id } = request.params
+ const existing = await prisma.cartItem.findFirst({ where: { id, userId } })
+ if (!existing) return reply.code(404).send({ error: 'Позиция корзины не найдена' })
+ await prisma.cartItem.delete({ where: { id } })
+ return reply.code(204).send()
+ } catch (err) {
+ request.log.error(err)
+ return reply.code(500).send({ error: 'Не удалось удалить из корзины' })
+ }
})
}
diff --git a/server/src/routes/user-messages.js b/server/src/routes/user-messages.js
index 6812f33..cd446ad 100644
--- a/server/src/routes/user-messages.js
+++ b/server/src/routes/user-messages.js
@@ -44,22 +44,21 @@ export async function registerUserMessageRoutes(fastify) {
})
if (orders.length === 0) return { count: 0 }
+ const orderIds = orders.map((o) => o.id)
const readStates = await prisma.userOrderMessageReadState.findMany({
where: { userId },
})
const lastReadByOrder = new Map(readStates.map((r) => [r.orderId, r.lastReadAt]))
+ const adminMessages = await prisma.orderMessage.findMany({
+ where: { orderId: { in: orderIds }, authorType: 'admin' },
+ select: { orderId: true, createdAt: true },
+ })
+
let count = 0
- for (const o of orders) {
- const lastRead = lastReadByOrder.get(o.id) ?? new Date(0)
- const n = await prisma.orderMessage.count({
- where: {
- orderId: o.id,
- authorType: 'admin',
- createdAt: { gt: lastRead },
- },
- })
- count += n
+ for (const msg of adminMessages) {
+ const lastRead = lastReadByOrder.get(msg.orderId) ?? new Date(0)
+ if (msg.createdAt > lastRead) count++
}
return { count }
})
@@ -86,25 +85,32 @@ export async function registerUserMessageRoutes(fastify) {
})
const lastReadByOrder = new Map(readStates.map((r) => [r.orderId, r.lastReadAt]))
+ const orderIds = orders.map((o) => o.id)
+ const unreadCounts = new Map()
+ if (orderIds.length > 0) {
+ const adminMessages = await prisma.orderMessage.findMany({
+ where: { orderId: { in: orderIds }, authorType: 'admin' },
+ select: { orderId: true, createdAt: true },
+ })
+ for (const msg of adminMessages) {
+ const lastRead = lastReadByOrder.get(msg.orderId) ?? new Date(0)
+ if (msg.createdAt > lastRead) {
+ unreadCounts.set(msg.orderId, (unreadCounts.get(msg.orderId) ?? 0) + 1)
+ }
+ }
+ }
+
const items = []
for (const o of orders) {
const lastMsg = o.messages[0]
if (!lastMsg) continue
- const lastRead = lastReadByOrder.get(o.id) ?? new Date(0)
- const unreadCount = await prisma.orderMessage.count({
- where: {
- orderId: o.id,
- authorType: 'admin',
- createdAt: { gt: lastRead },
- },
- })
items.push({
orderId: o.id,
status: o.status,
deliveryType: o.deliveryType,
lastMessageAt: lastMsg.createdAt,
preview: lastMsg.text.length > 280 ? `${lastMsg.text.slice(0, 277)}…` : lastMsg.text,
- unreadCount,
+ unreadCount: unreadCounts.get(o.id) ?? 0,
})
}
return { items }
diff --git a/server/src/routes/user-orders.js b/server/src/routes/user-orders.js
index 8ced32d..6916bb1 100644
--- a/server/src/routes/user-orders.js
+++ b/server/src/routes/user-orders.js
@@ -176,35 +176,45 @@ export async function registerUserOrderRoutes(fastify) {
return reply.code(201).send({ orderId: created.id })
})
- fastify.get('/api/me/orders', { preHandler: [fastify.authenticate] }, async (request) => {
- const userId = request.user.sub
- const orders = await prisma.order.findMany({
- where: { userId },
- include: { items: true },
- orderBy: { createdAt: 'desc' },
- })
- return {
- items: orders.map((o) => ({
- id: o.id,
- status: o.status,
- totalCents: o.totalCents,
- currency: o.currency,
- createdAt: o.createdAt,
- updatedAt: o.updatedAt,
- itemsCount: o.items.reduce((s, i) => s + i.qty, 0),
- })),
+ fastify.get('/api/me/orders', { preHandler: [fastify.authenticate] }, async (request, reply) => {
+ try {
+ const userId = request.user.sub
+ const orders = await prisma.order.findMany({
+ where: { userId },
+ include: { items: { select: { qty: true } } },
+ orderBy: { createdAt: 'desc' },
+ })
+ return {
+ items: orders.map((o) => ({
+ id: o.id,
+ status: o.status,
+ totalCents: o.totalCents,
+ currency: o.currency,
+ createdAt: o.createdAt,
+ updatedAt: o.updatedAt,
+ itemsCount: o.items.reduce((s, i) => s + i.qty, 0),
+ })),
+ }
+ } catch (err) {
+ request.log.error(err)
+ return reply.code(500).send({ error: 'Не удалось загрузить заказы' })
}
})
fastify.get('/api/me/orders/:id', { preHandler: [fastify.authenticate] }, async (request, reply) => {
- const userId = request.user.sub
- const { id } = request.params
- const order = await prisma.order.findFirst({
- where: { id, userId },
- include: { items: true, messages: { orderBy: { createdAt: 'asc' } } },
- })
- if (!order) return reply.code(404).send({ error: 'Заказ не найден' })
- return { item: order }
+ try {
+ const userId = request.user.sub
+ const { id } = request.params
+ const order = await prisma.order.findFirst({
+ where: { id, userId },
+ include: { items: true, messages: { orderBy: { createdAt: 'asc' } } },
+ })
+ if (!order) return reply.code(404).send({ error: 'Заказ не найден' })
+ return { item: order }
+ } catch (err) {
+ request.log.error(err)
+ return reply.code(500).send({ error: 'Не удалось загрузить заказ' })
+ }
})
fastify.get(
@@ -251,19 +261,24 @@ export async function registerUserOrderRoutes(fastify) {
'/api/me/orders/:id/confirm-received',
{ preHandler: [fastify.authenticate] },
async (request, reply) => {
- const userId = request.user.sub
- const { id } = request.params
- const order = await prisma.order.findFirst({ where: { id, userId } })
- if (!order) return reply.code(404).send({ error: 'Заказ не найден' })
+ try {
+ const userId = request.user.sub
+ const { id } = request.params
+ const order = await prisma.order.findFirst({ where: { id, userId } })
+ if (!order) return reply.code(404).send({ error: 'Заказ не найден' })
- const okDelivery = order.deliveryType === 'delivery' && order.status === 'SHIPPED'
- const okPickup = order.deliveryType === 'pickup' && order.status === 'READY_FOR_PICKUP'
- if (!okDelivery && !okPickup) {
- return reply.code(409).send({ error: 'Сейчас нельзя подтвердить получение заказа' })
+ const okDelivery = order.deliveryType === 'delivery' && order.status === 'SHIPPED'
+ const okPickup = order.deliveryType === 'pickup' && order.status === 'READY_FOR_PICKUP'
+ if (!okDelivery && !okPickup) {
+ return reply.code(409).send({ error: 'Сейчас нельзя подтвердить получение заказа' })
+ }
+
+ await prisma.order.update({ where: { id }, data: { status: 'DONE' } })
+ return { ok: true, status: 'DONE' }
+ } catch (err) {
+ request.log.error(err)
+ return reply.code(500).send({ error: 'Не удалось подтвердить получение' })
}
-
- await prisma.order.update({ where: { id }, data: { status: 'DONE' } })
- return { ok: true, status: 'DONE' }
},
)
}