/
IvanRyabchenko
/
random-coffee-backend
Обзор
Документация
Войти
/
IvanRyabchenko
/
random-coffee-backend
Код
Запросы
1
Задачи
Вики
Пакеты
0
Релизы
0
CI/CD
Аналитика
Безопасность
develop
src/openapi.ts
390 строк
17 KB
Ivan Ryabchenko
feat(products): add optional imageUrl field to Product model and seed data
17 май 2026, 10:00
17 май 2026, 10:00
11737d8
Код
Авторство
О чём код?
export const openApiSpec = { openapi: '3.0.0', info: { title: 'Random Coffee API', version: '1.0.0', description: 'REST API for Random Coffee hackathon project. Auth endpoints use JWT. ' + 'Admin-only endpoints require Authorization: Bearer <token>.', }, servers: [{ url: 'http://localhost:3000', description: 'Local development' }], components: { securitySchemes: { bearerAuth: { type: 'http', scheme: 'bearer', bearerFormat: 'JWT' }, }, schemas: { Error: { type: 'object', properties: { message: { type: 'string' }, statusCode: { type: 'integer' }, error: { type: 'string' }, }, }, Count: { type: 'object', properties: { count: { type: 'integer', example: 42 } }, }, Category: { type: 'object', properties: { id: { type: 'string', format: 'uuid' }, name: { type: 'string', example: 'Coffee' }, createdAt: { type: 'string', format: 'date-time' }, updatedAt: { type: 'string', format: 'date-time' }, }, }, Product: { type: 'object', properties: { id: { type: 'string', format: 'uuid' }, name: { type: 'string', example: 'Espresso' }, description: { type: 'string', example: 'Strong black coffee shot', nullable: true }, price: { type: 'string', example: '2.50' }, imageUrl: { type: 'string', format: 'uri', example: 'https://images.unsplash.com/photo-1509042239860-f550ce710b93?w=400&q=80', nullable: true }, categoryId: { type: 'string', format: 'uuid' }, category: { $ref: '#/components/schemas/Category' }, createdAt: { type: 'string', format: 'date-time' }, updatedAt: { type: 'string', format: 'date-time' }, }, }, OrderItem: { type: 'object', properties: { id: { type: 'string', format: 'uuid' }, orderId: { type: 'string', format: 'uuid' }, productId: { type: 'string', format: 'uuid' }, product: { $ref: '#/components/schemas/Product' }, quantity: { type: 'integer', example: 2 }, price: { type: 'string', example: '2.50' }, }, }, Order: { type: 'object', properties: { id: { type: 'string', format: 'uuid' }, status: { type: 'string', enum: ['PENDING', 'CANCELLED'], example: 'PENDING' }, totalPrice: { type: 'string', example: '5.00' }, items: { type: 'array', items: { $ref: '#/components/schemas/OrderItem' } }, createdAt: { type: 'string', format: 'date-time' }, updatedAt: { type: 'string', format: 'date-time' }, }, }, }, }, tags: [ { name: 'Auth', description: 'Authentication' }, { name: 'Categories', description: 'Category management' }, { name: 'Products', description: 'Product management' }, { name: 'Orders', description: 'Order management' }, { name: 'Stats', description: 'Metrics and statistics (admin only)' }, ], paths: { '/auth/login': { post: { tags: ['Auth'], summary: 'Login and get JWT token', requestBody: { required: true, content: { 'application/json': { schema: { type: 'object', required: ['email', 'password'], properties: { email: { type: 'string', format: 'email', example: 'admin@coffee.com' }, password: { type: 'string', example: 'admin123' }, }, }, }, }, }, responses: { 200: { description: 'JWT token', content: { 'application/json': { schema: { type: 'object', properties: { token: { type: 'string', example: 'eyJhbGciOiJIUzI1NiJ9...' } }, }, }, }, }, 400: { description: 'Validation error', content: { 'application/json': { schema: { $ref: '#/components/schemas/Error' } } } }, 401: { description: 'Invalid credentials', content: { 'application/json': { schema: { $ref: '#/components/schemas/Error' } } } }, }, }, }, '/categories': { get: { tags: ['Categories'], summary: 'List all categories (paginated)', parameters: [ { name: 'page', in: 'query', schema: { type: 'integer', default: 1 } }, { name: 'limit', in: 'query', schema: { type: 'integer', default: 10 } }, ], responses: { 200: { description: 'Paginated list of categories', content: { 'application/json': { schema: { type: 'object', properties: { data: { type: 'array', items: { $ref: '#/components/schemas/Category' } }, total: { type: 'integer', example: 3 }, page: { type: 'integer', example: 1 }, limit: { type: 'integer', example: 10 }, }, }, }, }, }, }, }, post: { tags: ['Categories'], summary: 'Create a category (admin only)', security: [{ bearerAuth: [] }], requestBody: { required: true, content: { 'application/json': { schema: { type: 'object', required: ['name'], properties: { name: { type: 'string', example: 'Coffee' } }, }, }, }, }, responses: { 201: { description: 'Created category', content: { 'application/json': { schema: { $ref: '#/components/schemas/Category' } } } }, 400: { description: 'Validation error', content: { 'application/json': { schema: { $ref: '#/components/schemas/Error' } } } }, 401: { description: 'Unauthorized', content: { 'application/json': { schema: { $ref: '#/components/schemas/Error' } } } }, 409: { description: 'Name already exists', content: { 'application/json': { schema: { $ref: '#/components/schemas/Error' } } } }, }, }, }, '/categories/{id}': { get: { tags: ['Categories'], summary: 'Get category by ID', parameters: [{ name: 'id', in: 'path', required: true, schema: { type: 'string', format: 'uuid' } }], responses: { 200: { description: 'Category', content: { 'application/json': { schema: { $ref: '#/components/schemas/Category' } } } }, 404: { description: 'Not found', content: { 'application/json': { schema: { $ref: '#/components/schemas/Error' } } } }, }, }, delete: { tags: ['Categories'], summary: 'Delete category (admin only)', security: [{ bearerAuth: [] }], parameters: [{ name: 'id', in: 'path', required: true, schema: { type: 'string', format: 'uuid' } }], responses: { 200: { description: 'Deleted', content: { 'application/json': { schema: { type: 'object', properties: { message: { type: 'string' } } } } } }, 401: { description: 'Unauthorized', content: { 'application/json': { schema: { $ref: '#/components/schemas/Error' } } } }, 404: { description: 'Not found', content: { 'application/json': { schema: { $ref: '#/components/schemas/Error' } } } }, }, }, }, '/products': { get: { tags: ['Products'], summary: 'List all products (paginated)', parameters: [ { name: 'page', in: 'query', schema: { type: 'integer', default: 1 } }, { name: 'limit', in: 'query', schema: { type: 'integer', default: 10 } }, ], responses: { 200: { description: 'Paginated list of products', content: { 'application/json': { schema: { type: 'object', properties: { data: { type: 'array', items: { $ref: '#/components/schemas/Product' } }, total: { type: 'integer', example: 10 }, page: { type: 'integer', example: 1 }, limit: { type: 'integer', example: 10 }, }, }, }, }, }, }, }, post: { tags: ['Products'], summary: 'Create a product (admin only)', security: [{ bearerAuth: [] }], requestBody: { required: true, content: { 'application/json': { schema: { type: 'object', required: ['name', 'price', 'categoryId'], properties: { name: { type: 'string', example: 'Espresso' }, description: { type: 'string', example: 'Strong black coffee shot' }, price: { type: 'number', example: 2.5 }, imageUrl: { type: 'string', format: 'uri', example: 'https://images.unsplash.com/photo-1509042239860-f550ce710b93?w=400&q=80' }, categoryId: { type: 'string', format: 'uuid' }, }, }, }, }, }, responses: { 201: { description: 'Created product', content: { 'application/json': { schema: { $ref: '#/components/schemas/Product' } } } }, 400: { description: 'Validation error or category not found', content: { 'application/json': { schema: { $ref: '#/components/schemas/Error' } } } }, 401: { description: 'Unauthorized', content: { 'application/json': { schema: { $ref: '#/components/schemas/Error' } } } }, }, }, }, '/products/{id}': { get: { tags: ['Products'], summary: 'Get product by ID', parameters: [{ name: 'id', in: 'path', required: true, schema: { type: 'string', format: 'uuid' } }], responses: { 200: { description: 'Product', content: { 'application/json': { schema: { $ref: '#/components/schemas/Product' } } } }, 404: { description: 'Not found', content: { 'application/json': { schema: { $ref: '#/components/schemas/Error' } } } }, }, }, delete: { tags: ['Products'], summary: 'Delete product (admin only)', security: [{ bearerAuth: [] }], parameters: [{ name: 'id', in: 'path', required: true, schema: { type: 'string', format: 'uuid' } }], responses: { 200: { description: 'Deleted' }, 401: { description: 'Unauthorized', content: { 'application/json': { schema: { $ref: '#/components/schemas/Error' } } } }, 404: { description: 'Not found', content: { 'application/json': { schema: { $ref: '#/components/schemas/Error' } } } }, 409: { description: 'Product referenced by orders', content: { 'application/json': { schema: { $ref: '#/components/schemas/Error' } } } }, }, }, }, '/orders': { get: { tags: ['Orders'], summary: 'List all orders (admin only, paginated)', security: [{ bearerAuth: [] }], parameters: [ { name: 'page', in: 'query', schema: { type: 'integer', default: 1 } }, { name: 'limit', in: 'query', schema: { type: 'integer', default: 10 } }, ], responses: { 200: { description: 'Paginated list of orders', content: { 'application/json': { schema: { type: 'object', properties: { data: { type: 'array', items: { $ref: '#/components/schemas/Order' } }, total: { type: 'integer' }, page: { type: 'integer' }, limit: { type: 'integer' }, }, }, }, }, }, 401: { description: 'Unauthorized', content: { 'application/json': { schema: { $ref: '#/components/schemas/Error' } } } }, }, }, post: { tags: ['Orders'], summary: 'Place a new order', requestBody: { required: true, content: { 'application/json': { schema: { type: 'object', required: ['items'], properties: { items: { type: 'array', minItems: 1, items: { type: 'object', required: ['productId', 'quantity'], properties: { productId: { type: 'string', format: 'uuid' }, quantity: { type: 'integer', minimum: 1, example: 2 }, }, }, }, }, }, }, }, }, responses: { 201: { description: 'Created order', content: { 'application/json': { schema: { $ref: '#/components/schemas/Order' } } } }, 400: { description: 'Validation error', content: { 'application/json': { schema: { $ref: '#/components/schemas/Error' } } } }, 404: { description: 'Product not found', content: { 'application/json': { schema: { $ref: '#/components/schemas/Error' } } } }, }, }, }, '/orders/{id}': { get: { tags: ['Orders'], summary: 'Get order by ID (admin only)', security: [{ bearerAuth: [] }], parameters: [{ name: 'id', in: 'path', required: true, schema: { type: 'string', format: 'uuid' } }], responses: { 200: { description: 'Order', content: { 'application/json': { schema: { $ref: '#/components/schemas/Order' } } } }, 401: { description: 'Unauthorized', content: { 'application/json': { schema: { $ref: '#/components/schemas/Error' } } } }, 404: { description: 'Not found', content: { 'application/json': { schema: { $ref: '#/components/schemas/Error' } } } }, }, }, }, '/orders/{id}/cancel': { patch: { tags: ['Orders'], summary: 'Cancel an order', parameters: [{ name: 'id', in: 'path', required: true, schema: { type: 'string', format: 'uuid' } }], responses: { 200: { description: 'Cancelled order', content: { 'application/json': { schema: { $ref: '#/components/schemas/Order' } } } }, 400: { description: 'Order already cancelled', content: { 'application/json': { schema: { $ref: '#/components/schemas/Error' } } } }, 404: { description: 'Not found', content: { 'application/json': { schema: { $ref: '#/components/schemas/Error' } } } }, }, }, }, '/stats/requests/total': { get: { tags: ['Stats'], summary: 'Total requests count', security: [{ bearerAuth: [] }], responses: { 200: { description: 'Count', content: { 'application/json': { schema: { $ref: '#/components/schemas/Count' } } } }, 401: { description: 'Unauthorized' } } }, }, '/stats/requests/success': { get: { tags: ['Stats'], summary: 'Successful requests count (status < 400)', security: [{ bearerAuth: [] }], responses: { 200: { description: 'Count', content: { 'application/json': { schema: { $ref: '#/components/schemas/Count' } } } }, 401: { description: 'Unauthorized' } } }, }, '/stats/requests/failed': { get: { tags: ['Stats'], summary: 'Failed requests count (status >= 400)', security: [{ bearerAuth: [] }], responses: { 200: { description: 'Count', content: { 'application/json': { schema: { $ref: '#/components/schemas/Count' } } } }, 401: { description: 'Unauthorized' } } }, }, '/stats/requests/orders': { get: { tags: ['Stats'], summary: 'Order placement requests count', security: [{ bearerAuth: [] }], responses: { 200: { description: 'Count', content: { 'application/json': { schema: { $ref: '#/components/schemas/Count' } } } }, 401: { description: 'Unauthorized' } } }, }, '/stats/products/count': { get: { tags: ['Stats'], summary: 'Total products in DB', security: [{ bearerAuth: [] }], responses: { 200: { description: 'Count', content: { 'application/json': { schema: { $ref: '#/components/schemas/Count' } } } }, 401: { description: 'Unauthorized' } } }, }, '/stats/categories/count': { get: { tags: ['Stats'], summary: 'Total categories in DB', security: [{ bearerAuth: [] }], responses: { 200: { description: 'Count', content: { 'application/json': { schema: { $ref: '#/components/schemas/Count' } } } }, 401: { description: 'Unauthorized' } } }, }, '/stats/orders/count': { get: { tags: ['Stats'], summary: 'Total orders in DB', security: [{ bearerAuth: [] }], responses: { 200: { description: 'Count', content: { 'application/json': { schema: { $ref: '#/components/schemas/Count' } } } }, 401: { description: 'Unauthorized' } } }, }, }, }