Добавлены модели услуг и расходников для фулфилмент центров, реализованы соответствующие мутации и запросы в GraphQL. Обновлен конфигурационный файл и добавлен новый компонент Toaster в макет приложения. Обновлены зависимости в package.json и package-lock.json.

This commit is contained in:
Bivekich
2025-07-17 10:47:20 +03:00
parent 205c9eae98
commit 99e91287f3
22 changed files with 2148 additions and 2 deletions

View File

@ -0,0 +1,5 @@
import { NextResponse } from 'next/server'
export async function GET() {
return NextResponse.json({ status: 'ok', timestamp: new Date().toISOString() })
}

View File

@ -0,0 +1,163 @@
import { NextRequest, NextResponse } from 'next/server'
import { S3Client, PutObjectCommand } from '@aws-sdk/client-s3'
const s3Client = new S3Client({
region: 'ru-1',
endpoint: 'https://s3.twcstorage.ru',
credentials: {
accessKeyId: 'I6XD2OR7YO2ZN6L6Z629',
secretAccessKey: '9xCOoafisG0aB9lJNvdLO1UuK73fBvMcpHMdijrJ'
},
forcePathStyle: true
})
const BUCKET_NAME = '617774af-sfera'
// Разрешенные типы изображений
const ALLOWED_IMAGE_TYPES = [
'image/jpeg',
'image/jpg',
'image/png',
'image/webp',
'image/gif'
]
export async function POST(request: NextRequest) {
try {
const formData = await request.formData()
const file = formData.get('file') as File
const userId = formData.get('userId') as string
const type = formData.get('type') as string // 'service' или 'supply'
if (!file || !userId || !type) {
return NextResponse.json(
{ error: 'File, userId and type are required' },
{ status: 400 }
)
}
// Проверяем тип (services или supplies)
if (!['service', 'supply'].includes(type)) {
return NextResponse.json(
{ error: 'Type must be either "service" or "supply"' },
{ status: 400 }
)
}
// Проверяем, что файл не пустой
if (file.size === 0) {
return NextResponse.json(
{ error: 'File is empty' },
{ status: 400 }
)
}
// Проверяем имя файла
if (!file.name || file.name.trim().length === 0) {
return NextResponse.json(
{ error: 'Invalid file name' },
{ status: 400 }
)
}
// Проверяем тип файла
if (!ALLOWED_IMAGE_TYPES.includes(file.type)) {
return NextResponse.json(
{ error: `File type ${file.type} is not allowed. Only images are supported.` },
{ status: 400 }
)
}
// Ограничиваем размер файла (5MB для изображений)
if (file.size > 5 * 1024 * 1024) {
return NextResponse.json(
{ error: 'File size must be less than 5MB' },
{ status: 400 }
)
}
// Генерируем уникальное имя файла
const timestamp = Date.now()
// Более безопасная очистка имени файла
const safeFileName = file.name
.replace(/[^\w\s.-]/g, '_') // Заменяем недопустимые символы
.replace(/\s+/g, '_') // Заменяем пробелы на подчеркивания
.replace(/_{2,}/g, '_') // Убираем множественные подчеркивания
.toLowerCase() // Приводим к нижнему регистру
const folder = type === 'service' ? 'services' : 'supplies'
const key = `${folder}/${userId}/${timestamp}-${safeFileName}`
// Конвертируем файл в Buffer
const buffer = Buffer.from(await file.arrayBuffer())
// Очищаем метаданные от недопустимых символов
const cleanOriginalName = file.name.replace(/[^\w\s.-]/g, '_')
const cleanUserId = userId.replace(/[^\w-]/g, '')
const cleanType = type.replace(/[^\w]/g, '')
// Загружаем в S3
const command = new PutObjectCommand({
Bucket: BUCKET_NAME,
Key: key,
Body: buffer,
ContentType: file.type,
ACL: 'public-read',
Metadata: {
originalname: cleanOriginalName,
uploadedby: cleanUserId,
type: cleanType
}
})
await s3Client.send(command)
// Возвращаем URL файла и метаданные
const url = `https://s3.twcstorage.ru/${BUCKET_NAME}/${key}`
return NextResponse.json({
success: true,
url,
key,
originalName: file.name,
size: file.size,
type: file.type
})
} catch (error) {
console.error('Error uploading service image:', error)
return NextResponse.json(
{ error: 'Failed to upload image' },
{ status: 500 }
)
}
}
export async function DELETE(request: NextRequest) {
try {
const { key } = await request.json()
if (!key) {
return NextResponse.json(
{ error: 'Key is required' },
{ status: 400 }
)
}
// TODO: Добавить удаление из S3
// const command = new DeleteObjectCommand({
// Bucket: BUCKET_NAME,
// Key: key
// })
// await s3Client.send(command)
return NextResponse.json({ success: true })
} catch (error) {
console.error('Error deleting service image:', error)
return NextResponse.json(
{ error: 'Failed to delete image' },
{ status: 500 }
)
}
}

View File

@ -2,6 +2,7 @@
import { ApolloProvider } from '@apollo/client'
import { apolloClient } from '@/lib/apollo-client'
import { Toaster } from "@/components/ui/sonner"
import "./globals.css"
export default function RootLayout({
@ -15,6 +16,7 @@ export default function RootLayout({
<ApolloProvider client={apolloClient}>
{children}
</ApolloProvider>
<Toaster />
</body>
</html>
)

10
src/app/services/page.tsx Normal file
View File

@ -0,0 +1,10 @@
import { AuthGuard } from "@/components/auth-guard"
import { ServicesDashboard } from "@/components/services/services-dashboard"
export default function ServicesPage() {
return (
<AuthGuard>
<ServicesDashboard />
</AuthGuard>
)
}