Compare commits
3 Commits
07c1285bb9
...
b823c1b58d
| Author | SHA1 | Date | |
|---|---|---|---|
| b823c1b58d | |||
| 043b2fd681 | |||
| 7eb4fb731b |
26
README.md
@@ -1,6 +1,6 @@
|
|||||||
# home-service
|
# home-service
|
||||||
|
|
||||||
Современный монорепозиторий для сервиса домашней автоматизации с бэкендом и админ-панелью.
|
Современный монорепозиторий для сервиса домашней автоматизации с бэкендом и фронтенд-панелью.
|
||||||
|
|
||||||
## 🚀 Стек технологий
|
## 🚀 Стек технологий
|
||||||
|
|
||||||
@@ -15,7 +15,7 @@
|
|||||||
- Zod для валидации
|
- Zod для валидации
|
||||||
- date-fns для работы с датами
|
- date-fns для работы с датами
|
||||||
|
|
||||||
**Admin:**
|
**Frontend:**
|
||||||
- Next.js 16 (App Router)
|
- Next.js 16 (App Router)
|
||||||
- React 19
|
- React 19
|
||||||
- TailwindCSS 4
|
- TailwindCSS 4
|
||||||
@@ -34,7 +34,7 @@ home-service/
|
|||||||
│ ├── migrations/ # Миграции БД
|
│ ├── migrations/ # Миграции БД
|
||||||
│ └── data/ # База данных PGLite
|
│ └── data/ # База данных PGLite
|
||||||
│
|
│
|
||||||
├── admin/ # Next.js админка (@home-service/admin)
|
├── frontend/ # Next.js админка (@home-service/frontend)
|
||||||
│ └── src/
|
│ └── src/
|
||||||
│ ├── app/ # Страницы
|
│ ├── app/ # Страницы
|
||||||
│ ├── components/ # React компоненты
|
│ ├── components/ # React компоненты
|
||||||
@@ -86,13 +86,13 @@ pnpm dev
|
|||||||
# Запустить только backend
|
# Запустить только backend
|
||||||
pnpm --filter @home-service/backend dev
|
pnpm --filter @home-service/backend dev
|
||||||
|
|
||||||
# Запустить только admin
|
# Запустить только frontend
|
||||||
pnpm --filter @home-service/admin dev
|
pnpm --filter @home-service/frontend dev
|
||||||
```
|
```
|
||||||
|
|
||||||
**Адреса:**
|
**Адреса:**
|
||||||
- Backend API: `http://localhost:3000`
|
- Backend API: `http://localhost:3000`
|
||||||
- Admin панель: `http://localhost:3001`
|
- Frontend панель: `http://localhost:3001`
|
||||||
|
|
||||||
### Сборка
|
### Сборка
|
||||||
|
|
||||||
@@ -103,8 +103,8 @@ pnpm build
|
|||||||
# Собрать только backend
|
# Собрать только backend
|
||||||
pnpm --filter @home-service/backend build
|
pnpm --filter @home-service/backend build
|
||||||
|
|
||||||
# Собрать только admin
|
# Собрать только frontend
|
||||||
pnpm --filter @home-service/admin build
|
pnpm --filter @home-service/frontend build
|
||||||
```
|
```
|
||||||
|
|
||||||
### Другие команды
|
### Другие команды
|
||||||
@@ -183,7 +183,7 @@ CORS_ORIGIN=http://localhost:3001
|
|||||||
DATABASE_PATH=./data/events.db
|
DATABASE_PATH=./data/events.db
|
||||||
```
|
```
|
||||||
|
|
||||||
### Admin (.env.local)
|
### Frontend (.env.local)
|
||||||
```
|
```
|
||||||
NEXT_PUBLIC_API_URL=http://localhost:3000
|
NEXT_PUBLIC_API_URL=http://localhost:3000
|
||||||
```
|
```
|
||||||
@@ -205,7 +205,7 @@ curl -X POST http://localhost:3000/api/events \
|
|||||||
```bash
|
```bash
|
||||||
pnpm build
|
pnpm build
|
||||||
pnpm --filter @home-service/backend start:prod
|
pnpm --filter @home-service/backend start:prod
|
||||||
pnpm --filter @home-service/admin start
|
pnpm --filter @home-service/frontend start
|
||||||
```
|
```
|
||||||
|
|
||||||
### Backend отдельно
|
### Backend отдельно
|
||||||
@@ -214,10 +214,10 @@ pnpm --filter @home-service/backend build
|
|||||||
pnpm --filter @home-service/backend start:prod
|
pnpm --filter @home-service/backend start:prod
|
||||||
```
|
```
|
||||||
|
|
||||||
### Admin отдельно
|
### Frontend отдельно
|
||||||
```bash
|
```bash
|
||||||
pnpm --filter @home-service/admin build
|
pnpm --filter @home-service/frontend build
|
||||||
pnpm --filter @home-service/admin start
|
pnpm --filter @home-service/frontend start
|
||||||
```
|
```
|
||||||
|
|
||||||
## 🛠️ Разработка
|
## 🛠️ Разработка
|
||||||
|
|||||||
@@ -1,26 +0,0 @@
|
|||||||
@import "tailwindcss";
|
|
||||||
|
|
||||||
:root {
|
|
||||||
--background: #ffffff;
|
|
||||||
--foreground: #171717;
|
|
||||||
}
|
|
||||||
|
|
||||||
@theme inline {
|
|
||||||
--color-background: var(--background);
|
|
||||||
--color-foreground: var(--foreground);
|
|
||||||
--font-sans: var(--font-geist-sans);
|
|
||||||
--font-mono: var(--font-geist-mono);
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (prefers-color-scheme: dark) {
|
|
||||||
:root {
|
|
||||||
--background: #0a0a0a;
|
|
||||||
--foreground: #ededed;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
body {
|
|
||||||
background: var(--background);
|
|
||||||
color: var(--foreground);
|
|
||||||
font-family: Arial, Helvetica, sans-serif;
|
|
||||||
}
|
|
||||||
3
backend/.gitignore
vendored
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
|
||||||
|
uploads/*
|
||||||
|
!uploads/.gitkeep
|
||||||
29
backend/migrations/0001_dashing_molten_man.sql
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
CREATE TABLE "wishlist_categories" (
|
||||||
|
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
|
||||||
|
"name" text NOT NULL,
|
||||||
|
"slug" text NOT NULL,
|
||||||
|
"min_price" integer DEFAULT 0 NOT NULL,
|
||||||
|
"max_price" integer,
|
||||||
|
"color" text,
|
||||||
|
"icon" text,
|
||||||
|
"order" integer DEFAULT 0 NOT NULL,
|
||||||
|
"created_at" timestamp with time zone DEFAULT now() NOT NULL,
|
||||||
|
"updated_at" timestamp with time zone DEFAULT now() NOT NULL,
|
||||||
|
CONSTRAINT "wishlist_categories_name_unique" UNIQUE("name"),
|
||||||
|
CONSTRAINT "wishlist_categories_slug_unique" UNIQUE("slug")
|
||||||
|
);
|
||||||
|
--> statement-breakpoint
|
||||||
|
CREATE TABLE "wishlist_items" (
|
||||||
|
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
|
||||||
|
"title" text NOT NULL,
|
||||||
|
"description" text,
|
||||||
|
"price" integer NOT NULL,
|
||||||
|
"currency" text DEFAULT 'RUB' NOT NULL,
|
||||||
|
"link" text,
|
||||||
|
"images" text[] DEFAULT ARRAY[]::text[] NOT NULL,
|
||||||
|
"category_id" uuid NOT NULL,
|
||||||
|
"created_at" timestamp with time zone DEFAULT now() NOT NULL,
|
||||||
|
"updated_at" timestamp with time zone DEFAULT now() NOT NULL
|
||||||
|
);
|
||||||
|
--> statement-breakpoint
|
||||||
|
ALTER TABLE "wishlist_items" ADD CONSTRAINT "wishlist_items_category_id_wishlist_categories_id_fk" FOREIGN KEY ("category_id") REFERENCES "public"."wishlist_categories"("id") ON DELETE restrict ON UPDATE no action;
|
||||||
311
backend/migrations/meta/0001_snapshot.json
Normal file
@@ -0,0 +1,311 @@
|
|||||||
|
{
|
||||||
|
"id": "172d78f6-8d17-4bc0-882d-c85b984f0c22",
|
||||||
|
"prevId": "852b1a6a-31c7-4f44-bdef-a378a18e2c7d",
|
||||||
|
"version": "7",
|
||||||
|
"dialect": "postgresql",
|
||||||
|
"tables": {
|
||||||
|
"public.events": {
|
||||||
|
"name": "events",
|
||||||
|
"schema": "",
|
||||||
|
"columns": {
|
||||||
|
"id": {
|
||||||
|
"name": "id",
|
||||||
|
"type": "uuid",
|
||||||
|
"primaryKey": true,
|
||||||
|
"notNull": true,
|
||||||
|
"default": "gen_random_uuid()"
|
||||||
|
},
|
||||||
|
"title": {
|
||||||
|
"name": "title",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
"emoji": {
|
||||||
|
"name": "emoji",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
"month": {
|
||||||
|
"name": "month",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
"day": {
|
||||||
|
"name": "day",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
"start_year": {
|
||||||
|
"name": "start_year",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"end_month": {
|
||||||
|
"name": "end_month",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"end_day": {
|
||||||
|
"name": "end_day",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"end_year": {
|
||||||
|
"name": "end_year",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"description": {
|
||||||
|
"name": "description",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"color": {
|
||||||
|
"name": "color",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"is_active": {
|
||||||
|
"name": "is_active",
|
||||||
|
"type": "boolean",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"default": true
|
||||||
|
},
|
||||||
|
"tags": {
|
||||||
|
"name": "tags",
|
||||||
|
"type": "text[]",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"created_at": {
|
||||||
|
"name": "created_at",
|
||||||
|
"type": "timestamp with time zone",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"default": "now()"
|
||||||
|
},
|
||||||
|
"updated_at": {
|
||||||
|
"name": "updated_at",
|
||||||
|
"type": "timestamp with time zone",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"default": "now()"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"indexes": {},
|
||||||
|
"foreignKeys": {},
|
||||||
|
"compositePrimaryKeys": {},
|
||||||
|
"uniqueConstraints": {},
|
||||||
|
"policies": {},
|
||||||
|
"checkConstraints": {},
|
||||||
|
"isRLSEnabled": false
|
||||||
|
},
|
||||||
|
"public.wishlist_categories": {
|
||||||
|
"name": "wishlist_categories",
|
||||||
|
"schema": "",
|
||||||
|
"columns": {
|
||||||
|
"id": {
|
||||||
|
"name": "id",
|
||||||
|
"type": "uuid",
|
||||||
|
"primaryKey": true,
|
||||||
|
"notNull": true,
|
||||||
|
"default": "gen_random_uuid()"
|
||||||
|
},
|
||||||
|
"name": {
|
||||||
|
"name": "name",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
"slug": {
|
||||||
|
"name": "slug",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
"min_price": {
|
||||||
|
"name": "min_price",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"default": 0
|
||||||
|
},
|
||||||
|
"max_price": {
|
||||||
|
"name": "max_price",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"color": {
|
||||||
|
"name": "color",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"icon": {
|
||||||
|
"name": "icon",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"order": {
|
||||||
|
"name": "order",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"default": 0
|
||||||
|
},
|
||||||
|
"created_at": {
|
||||||
|
"name": "created_at",
|
||||||
|
"type": "timestamp with time zone",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"default": "now()"
|
||||||
|
},
|
||||||
|
"updated_at": {
|
||||||
|
"name": "updated_at",
|
||||||
|
"type": "timestamp with time zone",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"default": "now()"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"indexes": {},
|
||||||
|
"foreignKeys": {},
|
||||||
|
"compositePrimaryKeys": {},
|
||||||
|
"uniqueConstraints": {
|
||||||
|
"wishlist_categories_name_unique": {
|
||||||
|
"name": "wishlist_categories_name_unique",
|
||||||
|
"nullsNotDistinct": false,
|
||||||
|
"columns": [
|
||||||
|
"name"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"wishlist_categories_slug_unique": {
|
||||||
|
"name": "wishlist_categories_slug_unique",
|
||||||
|
"nullsNotDistinct": false,
|
||||||
|
"columns": [
|
||||||
|
"slug"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"policies": {},
|
||||||
|
"checkConstraints": {},
|
||||||
|
"isRLSEnabled": false
|
||||||
|
},
|
||||||
|
"public.wishlist_items": {
|
||||||
|
"name": "wishlist_items",
|
||||||
|
"schema": "",
|
||||||
|
"columns": {
|
||||||
|
"id": {
|
||||||
|
"name": "id",
|
||||||
|
"type": "uuid",
|
||||||
|
"primaryKey": true,
|
||||||
|
"notNull": true,
|
||||||
|
"default": "gen_random_uuid()"
|
||||||
|
},
|
||||||
|
"title": {
|
||||||
|
"name": "title",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
"description": {
|
||||||
|
"name": "description",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"price": {
|
||||||
|
"name": "price",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
"currency": {
|
||||||
|
"name": "currency",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"default": "'RUB'"
|
||||||
|
},
|
||||||
|
"link": {
|
||||||
|
"name": "link",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"images": {
|
||||||
|
"name": "images",
|
||||||
|
"type": "text[]",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"default": "ARRAY[]::text[]"
|
||||||
|
},
|
||||||
|
"category_id": {
|
||||||
|
"name": "category_id",
|
||||||
|
"type": "uuid",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
"created_at": {
|
||||||
|
"name": "created_at",
|
||||||
|
"type": "timestamp with time zone",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"default": "now()"
|
||||||
|
},
|
||||||
|
"updated_at": {
|
||||||
|
"name": "updated_at",
|
||||||
|
"type": "timestamp with time zone",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"default": "now()"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"indexes": {},
|
||||||
|
"foreignKeys": {
|
||||||
|
"wishlist_items_category_id_wishlist_categories_id_fk": {
|
||||||
|
"name": "wishlist_items_category_id_wishlist_categories_id_fk",
|
||||||
|
"tableFrom": "wishlist_items",
|
||||||
|
"tableTo": "wishlist_categories",
|
||||||
|
"columnsFrom": [
|
||||||
|
"category_id"
|
||||||
|
],
|
||||||
|
"columnsTo": [
|
||||||
|
"id"
|
||||||
|
],
|
||||||
|
"onDelete": "restrict",
|
||||||
|
"onUpdate": "no action"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"compositePrimaryKeys": {},
|
||||||
|
"uniqueConstraints": {},
|
||||||
|
"policies": {},
|
||||||
|
"checkConstraints": {},
|
||||||
|
"isRLSEnabled": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"enums": {},
|
||||||
|
"schemas": {},
|
||||||
|
"sequences": {},
|
||||||
|
"roles": {},
|
||||||
|
"policies": {},
|
||||||
|
"views": {},
|
||||||
|
"_meta": {
|
||||||
|
"columns": {},
|
||||||
|
"schemas": {},
|
||||||
|
"tables": {}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -8,6 +8,13 @@
|
|||||||
"when": 1764421778123,
|
"when": 1764421778123,
|
||||||
"tag": "0000_lyrical_gabe_jones",
|
"tag": "0000_lyrical_gabe_jones",
|
||||||
"breakpoints": true
|
"breakpoints": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idx": 1,
|
||||||
|
"version": "7",
|
||||||
|
"when": 1765007898910,
|
||||||
|
"tag": "0001_dashing_molten_man",
|
||||||
|
"breakpoints": true
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
@@ -25,7 +25,8 @@
|
|||||||
"@nestjs/common": "^11.0.1",
|
"@nestjs/common": "^11.0.1",
|
||||||
"@nestjs/config": "^4.0.2",
|
"@nestjs/config": "^4.0.2",
|
||||||
"@nestjs/core": "^11.0.1",
|
"@nestjs/core": "^11.0.1",
|
||||||
"@nestjs/platform-express": "^11.0.1",
|
"@nestjs/platform-express": "^11.1.9",
|
||||||
|
"@nestjs/serve-static": "^5.0.4",
|
||||||
"class-transformer": "^0.5.1",
|
"class-transformer": "^0.5.1",
|
||||||
"class-validator": "^0.14.3",
|
"class-validator": "^0.14.3",
|
||||||
"date-fns": "^4.1.0",
|
"date-fns": "^4.1.0",
|
||||||
@@ -41,6 +42,7 @@
|
|||||||
"@nestjs/testing": "^11.0.1",
|
"@nestjs/testing": "^11.0.1",
|
||||||
"@types/express": "^5.0.0",
|
"@types/express": "^5.0.0",
|
||||||
"@types/jest": "^30.0.0",
|
"@types/jest": "^30.0.0",
|
||||||
|
"@types/multer": "^2.0.0",
|
||||||
"@types/node": "^22.19.1",
|
"@types/node": "^22.19.1",
|
||||||
"@types/supertest": "^6.0.2",
|
"@types/supertest": "^6.0.2",
|
||||||
"drizzle-kit": "^0.31.7",
|
"drizzle-kit": "^0.31.7",
|
||||||
@@ -51,6 +53,7 @@
|
|||||||
"ts-loader": "^9.5.2",
|
"ts-loader": "^9.5.2",
|
||||||
"ts-node": "^10.9.2",
|
"ts-node": "^10.9.2",
|
||||||
"tsconfig-paths": "^4.2.0",
|
"tsconfig-paths": "^4.2.0",
|
||||||
|
"tsx": "^4.21.0",
|
||||||
"typescript": "^5.7.3"
|
"typescript": "^5.7.3"
|
||||||
},
|
},
|
||||||
"jest": {
|
"jest": {
|
||||||
|
|||||||
120
backend/seed.ts
Normal file
@@ -0,0 +1,120 @@
|
|||||||
|
import { NestFactory } from '@nestjs/core';
|
||||||
|
import { AppModule } from './src/app.module';
|
||||||
|
import { WishlistCategoriesService } from './src/wishlist/categories.service';
|
||||||
|
import { WishlistService } from './src/wishlist/wishlist.service';
|
||||||
|
|
||||||
|
async function seed() {
|
||||||
|
const app = await NestFactory.createApplicationContext(AppModule);
|
||||||
|
|
||||||
|
const categoriesService = app.get(WishlistCategoriesService);
|
||||||
|
const wishlistService = app.get(WishlistService);
|
||||||
|
|
||||||
|
console.log('🌱 Seeding wishlist categories...');
|
||||||
|
|
||||||
|
// Создать категории
|
||||||
|
const category1 = await categoriesService.create({
|
||||||
|
name: 'БЮДЖЕТНО',
|
||||||
|
slug: 'tier-1',
|
||||||
|
minPrice: 0,
|
||||||
|
maxPrice: 150000, // 1500 руб
|
||||||
|
color: '#00ff41',
|
||||||
|
icon: '🟢',
|
||||||
|
order: 1,
|
||||||
|
});
|
||||||
|
|
||||||
|
const category2 = await categoriesService.create({
|
||||||
|
name: 'СРЕДНИЙ',
|
||||||
|
slug: 'tier-2',
|
||||||
|
minPrice: 150001,
|
||||||
|
maxPrice: 500000, // 5000 руб
|
||||||
|
color: '#00cc33',
|
||||||
|
icon: '🟡',
|
||||||
|
order: 2,
|
||||||
|
});
|
||||||
|
|
||||||
|
const category3 = await categoriesService.create({
|
||||||
|
name: 'ТОП',
|
||||||
|
slug: 'tier-3',
|
||||||
|
minPrice: 500001,
|
||||||
|
maxPrice: null, // без ограничения
|
||||||
|
color: '#009922',
|
||||||
|
icon: '🔴',
|
||||||
|
order: 3,
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log(`✅ Created 3 categories`);
|
||||||
|
|
||||||
|
console.log('🌱 Seeding wishlist items...');
|
||||||
|
|
||||||
|
// Создать примерные товары
|
||||||
|
await wishlistService.create({
|
||||||
|
title: 'Зерновой Кофе (Эфиопия)',
|
||||||
|
description: 'Люблю светлую обжарку. Желательно Эфиопия или Кения. Нужен именно в зернах.',
|
||||||
|
price: 85000, // 850 руб
|
||||||
|
currency: 'RUB',
|
||||||
|
link: 'https://ozon.ru',
|
||||||
|
imageUrls: ['https://images.unsplash.com/photo-1497935586351-b67a49e012bf?w=500'],
|
||||||
|
categoryId: category1.id,
|
||||||
|
});
|
||||||
|
|
||||||
|
await wishlistService.create({
|
||||||
|
title: 'Молескин в точку',
|
||||||
|
description: 'Черный, классический. Обязательно в точку, а не в линейку.',
|
||||||
|
price: 120000, // 1200 руб
|
||||||
|
currency: 'RUB',
|
||||||
|
link: 'https://wildberries.ru',
|
||||||
|
imageUrls: ['https://images.unsplash.com/photo-1531346878377-a513bc957374?w=500'],
|
||||||
|
categoryId: category1.id,
|
||||||
|
});
|
||||||
|
|
||||||
|
await wishlistService.create({
|
||||||
|
title: 'Винил: Daft Punk',
|
||||||
|
description: 'Альбом "Random Access Memories". Мечтаю послушать его на проигрывателе.',
|
||||||
|
price: 350000, // 3500 руб
|
||||||
|
currency: 'RUB',
|
||||||
|
link: 'https://market.yandex.ru',
|
||||||
|
imageUrls: ['https://images.unsplash.com/photo-1603048588665-791ca8aea617?w=500'],
|
||||||
|
categoryId: category2.id,
|
||||||
|
});
|
||||||
|
|
||||||
|
await wishlistService.create({
|
||||||
|
title: 'D&D Стартовый набор',
|
||||||
|
description: '5-я редакция. Хочу попробовать поиграть с друзьями.',
|
||||||
|
price: 290000, // 2900 руб
|
||||||
|
currency: 'RUB',
|
||||||
|
link: 'https://hobbygames.ru',
|
||||||
|
imageUrls: ['https://images.unsplash.com/photo-1632501641765-e568d90e09b2?w=500'],
|
||||||
|
categoryId: category2.id,
|
||||||
|
});
|
||||||
|
|
||||||
|
await wishlistService.create({
|
||||||
|
title: 'LEGO Speed Champions',
|
||||||
|
description: 'Любая машинка из этой серии, желательно Porsche или Ferrari.',
|
||||||
|
price: 250000, // 2500 руб
|
||||||
|
currency: 'RUB',
|
||||||
|
link: 'https://detmir.ru',
|
||||||
|
imageUrls: ['https://images.unsplash.com/photo-1585366119957-e9730b6d0f60?w=500'],
|
||||||
|
categoryId: category2.id,
|
||||||
|
});
|
||||||
|
|
||||||
|
await wishlistService.create({
|
||||||
|
title: 'Keychron K2',
|
||||||
|
description: 'Механическая клавиатура. Свичи Red или Brown. Нужна подсветка.',
|
||||||
|
price: 900000, // 9000 руб
|
||||||
|
currency: 'RUB',
|
||||||
|
link: 'https://geekboards.ru',
|
||||||
|
imageUrls: ['https://images.unsplash.com/photo-1595225476474-87563907a212?w=500'],
|
||||||
|
categoryId: category3.id,
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log(`✅ Created 6 wishlist items`);
|
||||||
|
console.log('✨ Seeding completed!');
|
||||||
|
|
||||||
|
await app.close();
|
||||||
|
process.exit(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
seed().catch((error) => {
|
||||||
|
console.error('❌ Seeding failed:', error);
|
||||||
|
process.exit(1);
|
||||||
|
});
|
||||||
@@ -1,15 +1,24 @@
|
|||||||
import { Module } from '@nestjs/common';
|
import { Module } from '@nestjs/common';
|
||||||
import { ConfigModule } from '@nestjs/config';
|
import { ConfigModule } from '@nestjs/config';
|
||||||
|
import { ServeStaticModule } from '@nestjs/serve-static';
|
||||||
|
import { join } from 'path';
|
||||||
import { DatabaseModule } from './database/database.module';
|
import { DatabaseModule } from './database/database.module';
|
||||||
import { EventsModule } from './events/events.module';
|
import { EventsModule } from './events/events.module';
|
||||||
|
import { WishlistModule } from './wishlist/wishlist.module';
|
||||||
|
|
||||||
@Module({
|
@Module({
|
||||||
imports: [
|
imports: [
|
||||||
ConfigModule.forRoot({
|
ConfigModule.forRoot({
|
||||||
isGlobal: true,
|
isGlobal: true,
|
||||||
}),
|
}),
|
||||||
|
ServeStaticModule.forRoot({
|
||||||
|
rootPath: join(__dirname, '..', 'uploads'),
|
||||||
|
serveRoot: '/uploads',
|
||||||
|
}),
|
||||||
DatabaseModule,
|
DatabaseModule,
|
||||||
EventsModule,
|
EventsModule,
|
||||||
|
WishlistModule,
|
||||||
],
|
],
|
||||||
})
|
})
|
||||||
export class AppModule { }
|
export class AppModule { }
|
||||||
|
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ const databaseProvider = {
|
|||||||
const client = new PGlite(dbPath);
|
const client = new PGlite(dbPath);
|
||||||
const db = drizzle(client, { schema });
|
const db = drizzle(client, { schema });
|
||||||
|
|
||||||
// Создаем таблицу напрямую вместо использования миграций
|
// Создаем таблицы напрямую вместо использования миграций
|
||||||
try {
|
try {
|
||||||
await client.exec(`
|
await client.exec(`
|
||||||
CREATE TABLE IF NOT EXISTS events (
|
CREATE TABLE IF NOT EXISTS events (
|
||||||
@@ -42,6 +42,32 @@ const databaseProvider = {
|
|||||||
created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(),
|
created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(),
|
||||||
updated_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW()
|
updated_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW()
|
||||||
);
|
);
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS wishlist_categories (
|
||||||
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||||
|
name TEXT NOT NULL UNIQUE,
|
||||||
|
slug TEXT NOT NULL UNIQUE,
|
||||||
|
min_price INTEGER NOT NULL DEFAULT 0,
|
||||||
|
max_price INTEGER,
|
||||||
|
color TEXT,
|
||||||
|
icon TEXT,
|
||||||
|
"order" INTEGER NOT NULL DEFAULT 0,
|
||||||
|
created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(),
|
||||||
|
updated_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW()
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS wishlist_items (
|
||||||
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||||
|
title TEXT NOT NULL,
|
||||||
|
description TEXT,
|
||||||
|
price INTEGER NOT NULL,
|
||||||
|
currency TEXT NOT NULL DEFAULT 'RUB',
|
||||||
|
link TEXT,
|
||||||
|
images TEXT[] NOT NULL DEFAULT ARRAY[]::TEXT[],
|
||||||
|
category_id UUID NOT NULL REFERENCES wishlist_categories(id) ON DELETE RESTRICT,
|
||||||
|
created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(),
|
||||||
|
updated_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW()
|
||||||
|
);
|
||||||
`);
|
`);
|
||||||
console.log('✅ Database initialized successfully');
|
console.log('✅ Database initialized successfully');
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|||||||
@@ -37,3 +37,80 @@ export const events = pgTable('events', {
|
|||||||
|
|
||||||
export type Event = typeof events.$inferSelect;
|
export type Event = typeof events.$inferSelect;
|
||||||
export type NewEvent = typeof events.$inferInsert;
|
export type NewEvent = typeof events.$inferInsert;
|
||||||
|
|
||||||
|
// Wishlist Categories Table
|
||||||
|
export const wishlistCategories = pgTable('wishlist_categories', {
|
||||||
|
id: uuid('id')
|
||||||
|
.primaryKey()
|
||||||
|
.default(sql`gen_random_uuid()`),
|
||||||
|
|
||||||
|
// Основная информация
|
||||||
|
name: text('name').notNull().unique(), // "БЮДЖЕТНО", "СРЕДНИЙ", "ТОП"
|
||||||
|
slug: text('slug').notNull().unique(), // "tier-1", "tier-2", "tier-3"
|
||||||
|
|
||||||
|
// Диапазон цен (в копейках)
|
||||||
|
minPrice: integer('min_price').notNull().default(0),
|
||||||
|
maxPrice: integer('max_price'), // null = без ограничения
|
||||||
|
|
||||||
|
// Визуальные настройки
|
||||||
|
color: text('color'), // hex цвет для UI
|
||||||
|
icon: text('icon'), // emoji или класс иконки
|
||||||
|
order: integer('order').notNull().default(0), // порядок сортировки
|
||||||
|
|
||||||
|
// Служебные поля
|
||||||
|
createdAt: timestamp('created_at', { withTimezone: true })
|
||||||
|
.notNull()
|
||||||
|
.defaultNow(),
|
||||||
|
updatedAt: timestamp('updated_at', { withTimezone: true })
|
||||||
|
.notNull()
|
||||||
|
.defaultNow()
|
||||||
|
.$onUpdate(() => new Date()),
|
||||||
|
});
|
||||||
|
|
||||||
|
export type WishlistCategory = typeof wishlistCategories.$inferSelect;
|
||||||
|
export type NewWishlistCategory = typeof wishlistCategories.$inferInsert;
|
||||||
|
|
||||||
|
// Wishlist Items Table
|
||||||
|
export const wishlistItems = pgTable('wishlist_items', {
|
||||||
|
id: uuid('id')
|
||||||
|
.primaryKey()
|
||||||
|
.default(sql`gen_random_uuid()`),
|
||||||
|
|
||||||
|
// Основная информация
|
||||||
|
title: text('title').notNull(),
|
||||||
|
description: text('description'),
|
||||||
|
price: integer('price').notNull(), // в копейках
|
||||||
|
currency: text('currency').notNull().default('RUB'),
|
||||||
|
|
||||||
|
// Ссылка на товар
|
||||||
|
link: text('link'),
|
||||||
|
|
||||||
|
// Множественные изображения (массив строк)
|
||||||
|
// Формат: ["url:https://...", "uploaded:/uploads/wishlist/file.jpg", ...]
|
||||||
|
images: text('images').array().notNull().default(sql`ARRAY[]::text[]`),
|
||||||
|
|
||||||
|
// Связь с категорией
|
||||||
|
categoryId: uuid('category_id')
|
||||||
|
.notNull()
|
||||||
|
.references(() => wishlistCategories.id, { onDelete: 'restrict' }),
|
||||||
|
|
||||||
|
// Служебные поля
|
||||||
|
createdAt: timestamp('created_at', { withTimezone: true })
|
||||||
|
.notNull()
|
||||||
|
.defaultNow(),
|
||||||
|
updatedAt: timestamp('updated_at', { withTimezone: true })
|
||||||
|
.notNull()
|
||||||
|
.defaultNow()
|
||||||
|
.$onUpdate(() => new Date()),
|
||||||
|
});
|
||||||
|
|
||||||
|
export type WishlistItem = typeof wishlistItems.$inferSelect;
|
||||||
|
export type NewWishlistItem = typeof wishlistItems.$inferInsert;
|
||||||
|
|
||||||
|
// Helper type for image objects
|
||||||
|
export type WishlistImage = {
|
||||||
|
type: 'url' | 'uploaded';
|
||||||
|
path: string;
|
||||||
|
alt?: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
|||||||
41
backend/src/wishlist/categories.controller.ts
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
import { Controller, Get, Post, Patch, Delete, Body, Param, UsePipes } from '@nestjs/common';
|
||||||
|
import { WishlistCategoriesService } from './categories.service';
|
||||||
|
import type { CreateWishlistCategoryDto, UpdateWishlistCategoryDto } from './dto/category.dto';
|
||||||
|
import { ZodValidationPipe } from '../pipes/zod-validation.pipe';
|
||||||
|
import { CreateWishlistCategorySchema, UpdateWishlistCategorySchema } from './dto/category.dto';
|
||||||
|
|
||||||
|
@Controller('api/wishlist/categories')
|
||||||
|
export class WishlistCategoriesController {
|
||||||
|
constructor(private readonly categoriesService: WishlistCategoriesService) { }
|
||||||
|
|
||||||
|
@Get()
|
||||||
|
async findAll() {
|
||||||
|
return this.categoriesService.findAll();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Get(':id')
|
||||||
|
async findOne(@Param('id') id: string) {
|
||||||
|
return this.categoriesService.findOne(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Post()
|
||||||
|
@UsePipes(new ZodValidationPipe(CreateWishlistCategorySchema))
|
||||||
|
async create(@Body() dto: CreateWishlistCategoryDto) {
|
||||||
|
return this.categoriesService.create(dto);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Patch(':id')
|
||||||
|
@UsePipes(new ZodValidationPipe(UpdateWishlistCategorySchema))
|
||||||
|
async update(
|
||||||
|
@Param('id') id: string,
|
||||||
|
@Body() dto: UpdateWishlistCategoryDto
|
||||||
|
) {
|
||||||
|
return this.categoriesService.update(id, dto);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Delete(':id')
|
||||||
|
async delete(@Param('id') id: string) {
|
||||||
|
await this.categoriesService.delete(id);
|
||||||
|
return { message: 'Category deleted successfully' };
|
||||||
|
}
|
||||||
|
}
|
||||||
151
backend/src/wishlist/categories.service.ts
Normal file
@@ -0,0 +1,151 @@
|
|||||||
|
import { Injectable, ConflictException, NotFoundException, Inject } from '@nestjs/common';
|
||||||
|
import { eq, and, lte, gte, or, isNull, asc } from 'drizzle-orm';
|
||||||
|
import { PgliteDatabase } from 'drizzle-orm/pglite';
|
||||||
|
import { DATABASE_CONNECTION } from '../database/database.module';
|
||||||
|
import * as schema from '../database/schema';
|
||||||
|
import { wishlistCategories, WishlistCategory, NewWishlistCategory } from '../database/schema';
|
||||||
|
import type { CreateWishlistCategoryDto, UpdateWishlistCategoryDto } from './dto/category.dto';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class WishlistCategoriesService {
|
||||||
|
constructor(
|
||||||
|
@Inject(DATABASE_CONNECTION)
|
||||||
|
private readonly db: PgliteDatabase<typeof schema>,
|
||||||
|
) { }
|
||||||
|
|
||||||
|
async findAll(): Promise<WishlistCategory[]> {
|
||||||
|
return this.db
|
||||||
|
.select()
|
||||||
|
.from(wishlistCategories)
|
||||||
|
.orderBy(asc(wishlistCategories.order));
|
||||||
|
}
|
||||||
|
|
||||||
|
async findOne(id: string): Promise<WishlistCategory> {
|
||||||
|
const [category] = await this.db
|
||||||
|
.select()
|
||||||
|
.from(wishlistCategories)
|
||||||
|
.where(eq(wishlistCategories.id, id))
|
||||||
|
.limit(1);
|
||||||
|
|
||||||
|
if (!category) {
|
||||||
|
throw new NotFoundException(`Category with ID ${id} not found`);
|
||||||
|
}
|
||||||
|
|
||||||
|
return category;
|
||||||
|
}
|
||||||
|
|
||||||
|
async findBySlug(slug: string): Promise<WishlistCategory | null> {
|
||||||
|
const [category] = await this.db
|
||||||
|
.select()
|
||||||
|
.from(wishlistCategories)
|
||||||
|
.where(eq(wishlistCategories.slug, slug))
|
||||||
|
.limit(1);
|
||||||
|
|
||||||
|
return category || null;
|
||||||
|
}
|
||||||
|
|
||||||
|
async create(dto: CreateWishlistCategoryDto): Promise<WishlistCategory> {
|
||||||
|
// Проверить уникальность slug
|
||||||
|
const existing = await this.findBySlug(dto.slug);
|
||||||
|
if (existing) {
|
||||||
|
throw new ConflictException(`Category with slug "${dto.slug}" already exists`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Проверить пересечение диапазонов цен
|
||||||
|
await this.validatePriceRange(dto.minPrice, dto.maxPrice);
|
||||||
|
|
||||||
|
const newCategory: NewWishlistCategory = {
|
||||||
|
...dto,
|
||||||
|
createdAt: new Date(),
|
||||||
|
updatedAt: new Date(),
|
||||||
|
};
|
||||||
|
|
||||||
|
const [category] = await this.db
|
||||||
|
.insert(wishlistCategories)
|
||||||
|
.values(newCategory)
|
||||||
|
.returning();
|
||||||
|
|
||||||
|
return category;
|
||||||
|
}
|
||||||
|
|
||||||
|
async update(id: string, dto: UpdateWishlistCategoryDto): Promise<WishlistCategory> {
|
||||||
|
const category = await this.findOne(id);
|
||||||
|
|
||||||
|
// Проверить уникальность slug если изменяется
|
||||||
|
if (dto.slug && dto.slug !== category.slug) {
|
||||||
|
const existing = await this.findBySlug(dto.slug);
|
||||||
|
if (existing) {
|
||||||
|
throw new ConflictException(`Category with slug "${dto.slug}" already exists`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Проверить пересечение диапазонов цен если изменяются
|
||||||
|
if (dto.minPrice !== undefined || dto.maxPrice !== undefined) {
|
||||||
|
const minPrice = dto.minPrice ?? category.minPrice;
|
||||||
|
const maxPrice = dto.maxPrice ?? category.maxPrice;
|
||||||
|
await this.validatePriceRange(minPrice, maxPrice, id);
|
||||||
|
}
|
||||||
|
|
||||||
|
const [updated] = await this.db
|
||||||
|
.update(wishlistCategories)
|
||||||
|
.set({ ...dto, updatedAt: new Date() })
|
||||||
|
.where(eq(wishlistCategories.id, id))
|
||||||
|
.returning();
|
||||||
|
|
||||||
|
return updated;
|
||||||
|
}
|
||||||
|
|
||||||
|
async delete(id: string): Promise<void> {
|
||||||
|
await this.findOne(id);
|
||||||
|
|
||||||
|
await this.db
|
||||||
|
.delete(wishlistCategories)
|
||||||
|
.where(eq(wishlistCategories.id, id));
|
||||||
|
}
|
||||||
|
|
||||||
|
async findCategoryByPrice(price: number): Promise<WishlistCategory | null> {
|
||||||
|
const [category] = await this.db
|
||||||
|
.select()
|
||||||
|
.from(wishlistCategories)
|
||||||
|
.where(
|
||||||
|
and(
|
||||||
|
lte(wishlistCategories.minPrice, price),
|
||||||
|
or(
|
||||||
|
gte(wishlistCategories.maxPrice, price),
|
||||||
|
isNull(wishlistCategories.maxPrice)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.orderBy(asc(wishlistCategories.order))
|
||||||
|
.limit(1);
|
||||||
|
|
||||||
|
return category || null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async validatePriceRange(
|
||||||
|
minPrice: number,
|
||||||
|
maxPrice: number | null | undefined,
|
||||||
|
excludeId?: string
|
||||||
|
): Promise<void> {
|
||||||
|
const categories = await this.findAll();
|
||||||
|
|
||||||
|
for (const cat of categories) {
|
||||||
|
if (excludeId && cat.id === excludeId) continue;
|
||||||
|
|
||||||
|
const catMin = cat.minPrice;
|
||||||
|
const catMax = cat.maxPrice;
|
||||||
|
|
||||||
|
// Проверить пересечение диапазонов
|
||||||
|
const hasOverlap =
|
||||||
|
(maxPrice === null || maxPrice === undefined || catMax === null || catMax === undefined)
|
||||||
|
? true // Если хотя бы один диапазон открыт сверху
|
||||||
|
: (minPrice <= catMax && maxPrice >= catMin);
|
||||||
|
|
||||||
|
if (hasOverlap && !(maxPrice !== null && maxPrice !== undefined && maxPrice < catMin)) {
|
||||||
|
throw new ConflictException(
|
||||||
|
`Price range [${minPrice}, ${maxPrice || '∞'}] overlaps with category "${cat.name}" [${catMin}, ${catMax || '∞'}]`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
16
backend/src/wishlist/dto/category.dto.ts
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
import { z } from 'zod';
|
||||||
|
|
||||||
|
export const CreateWishlistCategorySchema = z.object({
|
||||||
|
name: z.string().min(1).max(100),
|
||||||
|
slug: z.string().regex(/^[a-z0-9-]+$/),
|
||||||
|
minPrice: z.number().int().min(0).default(0),
|
||||||
|
maxPrice: z.number().int().positive().optional().nullable(),
|
||||||
|
color: z.string().regex(/^#[0-9A-Fa-f]{6}$/).optional(),
|
||||||
|
icon: z.string().optional(),
|
||||||
|
order: z.number().int().default(0),
|
||||||
|
});
|
||||||
|
|
||||||
|
export const UpdateWishlistCategorySchema = CreateWishlistCategorySchema.partial();
|
||||||
|
|
||||||
|
export type CreateWishlistCategoryDto = z.infer<typeof CreateWishlistCategorySchema>;
|
||||||
|
export type UpdateWishlistCategoryDto = z.infer<typeof UpdateWishlistCategorySchema>;
|
||||||
17
backend/src/wishlist/dto/wishlist-item.dto.ts
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
import { z } from 'zod';
|
||||||
|
|
||||||
|
export const CreateWishlistItemSchema = z.object({
|
||||||
|
title: z.string().min(1).max(200),
|
||||||
|
description: z.string().max(1000).optional(),
|
||||||
|
price: z.number().int().positive(),
|
||||||
|
currency: z.string().default('RUB'),
|
||||||
|
link: z.string().url().optional().or(z.literal('')),
|
||||||
|
// Images будут обрабатываться отдельно (файлы + URL)
|
||||||
|
imageUrls: z.array(z.string().url()).optional().default([]),
|
||||||
|
categoryId: z.string().uuid(),
|
||||||
|
});
|
||||||
|
|
||||||
|
export const UpdateWishlistItemSchema = CreateWishlistItemSchema.partial();
|
||||||
|
|
||||||
|
export type CreateWishlistItemDto = z.infer<typeof CreateWishlistItemSchema>;
|
||||||
|
export type UpdateWishlistItemDto = z.infer<typeof UpdateWishlistItemSchema>;
|
||||||
67
backend/src/wishlist/wishlist.controller.ts
Normal file
@@ -0,0 +1,67 @@
|
|||||||
|
import {
|
||||||
|
Controller,
|
||||||
|
Get,
|
||||||
|
Post,
|
||||||
|
Patch,
|
||||||
|
Delete,
|
||||||
|
Body,
|
||||||
|
Param,
|
||||||
|
Query,
|
||||||
|
UseInterceptors,
|
||||||
|
UploadedFiles,
|
||||||
|
UsePipes,
|
||||||
|
} from '@nestjs/common';
|
||||||
|
import { FilesInterceptor } from '@nestjs/platform-express';
|
||||||
|
import { WishlistService } from './wishlist.service';
|
||||||
|
import { type CreateWishlistItemDto, type UpdateWishlistItemDto } from './dto/wishlist-item.dto';
|
||||||
|
import { ZodValidationPipe } from '../pipes/zod-validation.pipe';
|
||||||
|
import { CreateWishlistItemSchema, UpdateWishlistItemSchema } from './dto/wishlist-item.dto';
|
||||||
|
|
||||||
|
@Controller('api/wishlist')
|
||||||
|
export class WishlistController {
|
||||||
|
constructor(private readonly wishlistService: WishlistService) { }
|
||||||
|
|
||||||
|
@Get()
|
||||||
|
async findAll(@Query('categoryId') categoryId?: string) {
|
||||||
|
return this.wishlistService.findAll(categoryId);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Get(':id')
|
||||||
|
async findOne(@Param('id') id: string) {
|
||||||
|
return this.wishlistService.findOne(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Post()
|
||||||
|
@UseInterceptors(FilesInterceptor('images', 5))
|
||||||
|
@UsePipes(new ZodValidationPipe(CreateWishlistItemSchema))
|
||||||
|
async create(
|
||||||
|
@Body() dto: CreateWishlistItemDto,
|
||||||
|
@UploadedFiles() files?: Express.Multer.File[]
|
||||||
|
) {
|
||||||
|
return this.wishlistService.create(dto, files);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Patch(':id')
|
||||||
|
@UseInterceptors(FilesInterceptor('images', 5))
|
||||||
|
@UsePipes(new ZodValidationPipe(UpdateWishlistItemSchema))
|
||||||
|
async update(
|
||||||
|
@Param('id') id: string,
|
||||||
|
@Body() dto: UpdateWishlistItemDto,
|
||||||
|
@UploadedFiles() files?: Express.Multer.File[]
|
||||||
|
) {
|
||||||
|
return this.wishlistService.update(id, dto, files);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Delete(':id')
|
||||||
|
async delete(@Param('id') id: string) {
|
||||||
|
await this.wishlistService.delete(id);
|
||||||
|
return { message: 'Wishlist item deleted successfully' };
|
||||||
|
}
|
||||||
|
|
||||||
|
@Post('upload')
|
||||||
|
@UseInterceptors(FilesInterceptor('images', 5))
|
||||||
|
async uploadImages(@UploadedFiles() files: Express.Multer.File[]) {
|
||||||
|
const paths = await this.wishlistService.uploadImages(files);
|
||||||
|
return { images: paths };
|
||||||
|
}
|
||||||
|
}
|
||||||
40
backend/src/wishlist/wishlist.module.ts
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
import { Module } from '@nestjs/common';
|
||||||
|
import { MulterModule } from '@nestjs/platform-express';
|
||||||
|
import { diskStorage } from 'multer';
|
||||||
|
import { extname } from 'path';
|
||||||
|
import { WishlistController } from './wishlist.controller';
|
||||||
|
import { WishlistCategoriesController } from './categories.controller';
|
||||||
|
import { WishlistService } from './wishlist.service';
|
||||||
|
import { WishlistCategoriesService } from './categories.service';
|
||||||
|
import { DatabaseModule } from '../database/database.module';
|
||||||
|
|
||||||
|
@Module({
|
||||||
|
imports: [
|
||||||
|
DatabaseModule,
|
||||||
|
MulterModule.register({
|
||||||
|
storage: diskStorage({
|
||||||
|
destination: './uploads/wishlist',
|
||||||
|
filename: (req, file, cb) => {
|
||||||
|
const uniqueSuffix = Date.now() + '-' + Math.round(Math.random() * 1e9);
|
||||||
|
const ext = extname(file.originalname);
|
||||||
|
cb(null, `${uniqueSuffix}${ext}`);
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
limits: {
|
||||||
|
fileSize: 5 * 1024 * 1024, // 5MB
|
||||||
|
},
|
||||||
|
fileFilter: (req, file, cb) => {
|
||||||
|
// Разрешить только изображения
|
||||||
|
if (file.mimetype.match(/^image\//)) {
|
||||||
|
cb(null, true);
|
||||||
|
} else {
|
||||||
|
cb(new Error('Only image files are allowed'), false);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
controllers: [WishlistController, WishlistCategoriesController],
|
||||||
|
providers: [WishlistService, WishlistCategoriesService],
|
||||||
|
exports: [WishlistService, WishlistCategoriesService],
|
||||||
|
})
|
||||||
|
export class WishlistModule { }
|
||||||
162
backend/src/wishlist/wishlist.service.ts
Normal file
@@ -0,0 +1,162 @@
|
|||||||
|
import { Injectable, NotFoundException, Inject } from '@nestjs/common';
|
||||||
|
import { eq } from 'drizzle-orm';
|
||||||
|
import { PgliteDatabase } from 'drizzle-orm/pglite';
|
||||||
|
import { DATABASE_CONNECTION } from '../database/database.module';
|
||||||
|
import * as schema from '../database/schema';
|
||||||
|
import { wishlistItems, WishlistItem, NewWishlistItem } from '../database/schema';
|
||||||
|
import type { CreateWishlistItemDto, UpdateWishlistItemDto } from './dto/wishlist-item.dto';
|
||||||
|
import { WishlistCategoriesService } from './categories.service';
|
||||||
|
import * as fs from 'fs/promises';
|
||||||
|
import * as path from 'path';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class WishlistService {
|
||||||
|
constructor(
|
||||||
|
@Inject(DATABASE_CONNECTION)
|
||||||
|
private readonly db: PgliteDatabase<typeof schema>,
|
||||||
|
private readonly categoriesService: WishlistCategoriesService,
|
||||||
|
) { }
|
||||||
|
|
||||||
|
async findAll(categoryId?: string): Promise<WishlistItem[]> {
|
||||||
|
const query = this.db.select().from(wishlistItems);
|
||||||
|
|
||||||
|
if (categoryId) {
|
||||||
|
query.where(eq(wishlistItems.categoryId, categoryId));
|
||||||
|
}
|
||||||
|
|
||||||
|
return query;
|
||||||
|
}
|
||||||
|
|
||||||
|
async findOne(id: string): Promise<WishlistItem> {
|
||||||
|
const [item] = await this.db
|
||||||
|
.select()
|
||||||
|
.from(wishlistItems)
|
||||||
|
.where(eq(wishlistItems.id, id))
|
||||||
|
.limit(1);
|
||||||
|
|
||||||
|
if (!item) {
|
||||||
|
throw new NotFoundException(`Wishlist item with ID ${id} not found`);
|
||||||
|
}
|
||||||
|
|
||||||
|
return item;
|
||||||
|
}
|
||||||
|
|
||||||
|
async create(
|
||||||
|
dto: CreateWishlistItemDto,
|
||||||
|
uploadedFiles?: Express.Multer.File[]
|
||||||
|
): Promise<WishlistItem> {
|
||||||
|
// Проверить существование категории
|
||||||
|
await this.categoriesService.findOne(dto.categoryId);
|
||||||
|
|
||||||
|
// Собрать массив изображений
|
||||||
|
const images: string[] = [];
|
||||||
|
|
||||||
|
// Добавить загруженные файлы
|
||||||
|
if (uploadedFiles && uploadedFiles.length > 0) {
|
||||||
|
for (const file of uploadedFiles) {
|
||||||
|
images.push(`uploaded:/uploads/wishlist/${file.filename}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Добавить URL изображения
|
||||||
|
if (dto.imageUrls && dto.imageUrls.length > 0) {
|
||||||
|
for (const url of dto.imageUrls) {
|
||||||
|
images.push(`url:${url}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const newItem: NewWishlistItem = {
|
||||||
|
title: dto.title,
|
||||||
|
description: dto.description,
|
||||||
|
price: dto.price,
|
||||||
|
currency: dto.currency,
|
||||||
|
link: dto.link,
|
||||||
|
images,
|
||||||
|
categoryId: dto.categoryId,
|
||||||
|
createdAt: new Date(),
|
||||||
|
updatedAt: new Date(),
|
||||||
|
};
|
||||||
|
|
||||||
|
const [item] = await this.db
|
||||||
|
.insert(wishlistItems)
|
||||||
|
.values(newItem)
|
||||||
|
.returning();
|
||||||
|
|
||||||
|
return item;
|
||||||
|
}
|
||||||
|
|
||||||
|
async update(
|
||||||
|
id: string,
|
||||||
|
dto: UpdateWishlistItemDto,
|
||||||
|
uploadedFiles?: Express.Multer.File[]
|
||||||
|
): Promise<WishlistItem> {
|
||||||
|
const item = await this.findOne(id);
|
||||||
|
|
||||||
|
// Проверить существование категории если изменяется
|
||||||
|
if (dto.categoryId) {
|
||||||
|
await this.categoriesService.findOne(dto.categoryId);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Обновить массив изображений
|
||||||
|
let images = [...item.images];
|
||||||
|
|
||||||
|
// Добавить новые загруженные файлы
|
||||||
|
if (uploadedFiles && uploadedFiles.length > 0) {
|
||||||
|
for (const file of uploadedFiles) {
|
||||||
|
images.push(`uploaded:/uploads/wishlist/${file.filename}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Добавить новые URL
|
||||||
|
if (dto.imageUrls && dto.imageUrls.length > 0) {
|
||||||
|
for (const url of dto.imageUrls) {
|
||||||
|
if (!images.includes(`url:${url}`)) {
|
||||||
|
images.push(`url:${url}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const [updated] = await this.db
|
||||||
|
.update(wishlistItems)
|
||||||
|
.set({
|
||||||
|
...dto,
|
||||||
|
images,
|
||||||
|
updatedAt: new Date(),
|
||||||
|
})
|
||||||
|
.where(eq(wishlistItems.id, id))
|
||||||
|
.returning();
|
||||||
|
|
||||||
|
return updated;
|
||||||
|
}
|
||||||
|
|
||||||
|
async delete(id: string): Promise<void> {
|
||||||
|
const item = await this.findOne(id);
|
||||||
|
|
||||||
|
// Удалить загруженные файлы
|
||||||
|
await this.deleteUploadedImages(item.images);
|
||||||
|
|
||||||
|
await this.db
|
||||||
|
.delete(wishlistItems)
|
||||||
|
.where(eq(wishlistItems.id, id));
|
||||||
|
}
|
||||||
|
|
||||||
|
async uploadImages(files: Express.Multer.File[]): Promise<string[]> {
|
||||||
|
return files.map(file => `/uploads/wishlist/${file.filename}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async deleteUploadedImages(images: string[]): Promise<void> {
|
||||||
|
const uploadedImages = images.filter(img => img.startsWith('uploaded:'));
|
||||||
|
|
||||||
|
for (const img of uploadedImages) {
|
||||||
|
const filePath = img.replace('uploaded:', '');
|
||||||
|
const fullPath = path.join(process.cwd(), filePath);
|
||||||
|
|
||||||
|
try {
|
||||||
|
await fs.unlink(fullPath);
|
||||||
|
} catch (error) {
|
||||||
|
// Игнорировать ошибки удаления (файл может не существовать)
|
||||||
|
console.warn(`Failed to delete file ${fullPath}:`, error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
1
backend/uploads/.gitkeep
Normal file
@@ -0,0 +1 @@
|
|||||||
|
# Keep this directory in git but ignore uploaded files
|
||||||
0
admin/.gitignore → frontend/.gitignore
vendored
@@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"name": "@home-service/admin",
|
"name": "@home-service/frontend",
|
||||||
"version": "0.1.0",
|
"version": "0.1.0",
|
||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
Before Width: | Height: | Size: 391 B After Width: | Height: | Size: 391 B |
|
Before Width: | Height: | Size: 1.0 KiB After Width: | Height: | Size: 1.0 KiB |
|
Before Width: | Height: | Size: 1.3 KiB After Width: | Height: | Size: 1.3 KiB |
|
Before Width: | Height: | Size: 128 B After Width: | Height: | Size: 128 B |
|
Before Width: | Height: | Size: 385 B After Width: | Height: | Size: 385 B |
|
Before Width: | Height: | Size: 25 KiB After Width: | Height: | Size: 25 KiB |
146
frontend/src/app/globals.css
Normal file
@@ -0,0 +1,146 @@
|
|||||||
|
@import "tailwindcss";
|
||||||
|
|
||||||
|
:root {
|
||||||
|
--background: #ffffff;
|
||||||
|
--foreground: #171717;
|
||||||
|
|
||||||
|
/* Terminal theme colors */
|
||||||
|
--term-bg: #0a0a0a;
|
||||||
|
--term-green: #00ff41;
|
||||||
|
--term-dim: #008f11;
|
||||||
|
--term-dark: #0d0208;
|
||||||
|
}
|
||||||
|
|
||||||
|
@theme inline {
|
||||||
|
--color-background: var(--background);
|
||||||
|
--color-foreground: var(--foreground);
|
||||||
|
--font-sans: var(--font-geist-sans);
|
||||||
|
--font-mono: var(--font-geist-mono);
|
||||||
|
|
||||||
|
/* Terminal theme utilities */
|
||||||
|
--color-term-bg: var(--term-bg);
|
||||||
|
--color-term-green: var(--term-green);
|
||||||
|
--color-term-dim: var(--term-dim);
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (prefers-color-scheme: dark) {
|
||||||
|
:root {
|
||||||
|
--background: #0a0a0a;
|
||||||
|
--foreground: #ededed;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
background: var(--background);
|
||||||
|
color: var(--foreground);
|
||||||
|
font-family: Arial, Helvetica, sans-serif;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Terminal theme classes */
|
||||||
|
.bg-term-bg {
|
||||||
|
background-color: var(--term-bg);
|
||||||
|
}
|
||||||
|
|
||||||
|
.text-term-green {
|
||||||
|
color: var(--term-green);
|
||||||
|
}
|
||||||
|
|
||||||
|
.text-term-dim {
|
||||||
|
color: var(--term-dim);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Custom Scrollbar */
|
||||||
|
::-webkit-scrollbar {
|
||||||
|
width: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
::-webkit-scrollbar-track {
|
||||||
|
background: var(--term-bg);
|
||||||
|
border-left: 1px solid var(--term-dim);
|
||||||
|
}
|
||||||
|
|
||||||
|
::-webkit-scrollbar-thumb {
|
||||||
|
background: var(--term-dim);
|
||||||
|
}
|
||||||
|
|
||||||
|
::-webkit-scrollbar-thumb:hover {
|
||||||
|
background: var(--term-green);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Scanline effect */
|
||||||
|
.scanline {
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 100vw;
|
||||||
|
height: 100vh;
|
||||||
|
background: linear-gradient(
|
||||||
|
to bottom,
|
||||||
|
rgba(255, 255, 255, 0),
|
||||||
|
rgba(255, 255, 255, 0) 50%,
|
||||||
|
rgba(0, 0, 0, 0.1) 50%,
|
||||||
|
rgba(0, 0, 0, 0.1)
|
||||||
|
);
|
||||||
|
background-size: 100% 4px;
|
||||||
|
pointer-events: none;
|
||||||
|
z-index: 50;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Terminal box */
|
||||||
|
.term-border {
|
||||||
|
border: 1px solid var(--term-dim);
|
||||||
|
}
|
||||||
|
|
||||||
|
.term-box {
|
||||||
|
border: 1px solid var(--term-dim);
|
||||||
|
box-shadow: 2px 2px 0px var(--term-dim);
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.term-box:hover {
|
||||||
|
box-shadow: 4px 4px 0px var(--term-green);
|
||||||
|
border-color: var(--term-green);
|
||||||
|
transform: translate(-2px, -2px);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Image effects */
|
||||||
|
.term-img {
|
||||||
|
filter: grayscale(100%) contrast(1.2) brightness(0.8);
|
||||||
|
transition: filter 0.3s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.term-box:hover .term-img {
|
||||||
|
filter: grayscale(0%) contrast(1) brightness(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Cursor blinkAnimation */
|
||||||
|
@keyframes blink {
|
||||||
|
0%,
|
||||||
|
100% {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
50% {
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.cursor-blink {
|
||||||
|
animation: blink 1s step-end infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Nav link hover */
|
||||||
|
.nav-link {
|
||||||
|
transition: all 0.2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-link:hover {
|
||||||
|
background-color: var(--term-green);
|
||||||
|
color: #000;
|
||||||
|
}
|
||||||
|
|
||||||
|
.active-link {
|
||||||
|
background-color: var(--term-green);
|
||||||
|
color: #000;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
35
frontend/src/app/wishlist/page.tsx
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
export default function WishlistPage() {
|
||||||
|
return (
|
||||||
|
<div className="min-h-screen p-4 md:p-8 bg-term-bg text-term-green font-mono selection:bg-term-green selection:text-black">
|
||||||
|
<div className="scanline" />
|
||||||
|
<div className="max-w-7xl mx-auto">
|
||||||
|
<header className="term-border p-6 mb-8">
|
||||||
|
<div className="flex flex-col md:flex-row justify-between items-start md:items-end gap-4">
|
||||||
|
<div>
|
||||||
|
<h1 className="text-3xl md:text-5xl font-bold mb-2">
|
||||||
|
> WISHLIST_LOADED<span className="cursor-blink">_</span>
|
||||||
|
</h1>
|
||||||
|
<p className="text-term-dim text-sm md:text-base max-w-2xl">
|
||||||
|
// USER: ALEX<br />
|
||||||
|
// STATUS: WAITING_FOR_DROPS<br />
|
||||||
|
// LOCATION: EARTH_C-137<br />
|
||||||
|
<br />
|
||||||
|
Привет. Это мой список желаний. Я люблю технику, книги и старый винил.
|
||||||
|
Ниже список вещей, которые сделают меня немного счастливее.
|
||||||
|
Выбирайте любой уровень сложности (цены).
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div className="text-right hidden md:block">
|
||||||
|
<div className="text-6xl font-bold opacity-20 select-none">2025</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<div className="text-center text-term-dim mt-16">
|
||||||
|
<p className="mb-4">Frontend реализация в процессе...</p>
|
||||||
|
<p className="text-sm">Backend API готов на http://localhost:3000/api/wishlist</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -51,8 +51,7 @@ export function EventForm({ onEventCreated }: EventFormProps) {
|
|||||||
setEventType('recurring');
|
setEventType('recurring');
|
||||||
setFormData({ ...formData, startYear: undefined, endYear: undefined, endMonth: undefined, endDay: undefined });
|
setFormData({ ...formData, startYear: undefined, endYear: undefined, endMonth: undefined, endDay: undefined });
|
||||||
}}
|
}}
|
||||||
className={`px-4 py-2 rounded-lg font-medium transition-colors ${
|
className={`px-4 py-2 rounded-lg font-medium transition-colors ${eventType === 'recurring'
|
||||||
eventType === 'recurring'
|
|
||||||
? 'bg-blue-600 text-white'
|
? 'bg-blue-600 text-white'
|
||||||
: 'bg-gray-800 text-gray-400 hover:bg-gray-700'
|
: 'bg-gray-800 text-gray-400 hover:bg-gray-700'
|
||||||
}`}
|
}`}
|
||||||
@@ -65,8 +64,7 @@ export function EventForm({ onEventCreated }: EventFormProps) {
|
|||||||
setEventType('anniversary');
|
setEventType('anniversary');
|
||||||
setFormData({ ...formData, endYear: undefined, endMonth: undefined, endDay: undefined });
|
setFormData({ ...formData, endYear: undefined, endMonth: undefined, endDay: undefined });
|
||||||
}}
|
}}
|
||||||
className={`px-4 py-2 rounded-lg font-medium transition-colors ${
|
className={`px-4 py-2 rounded-lg font-medium transition-colors ${eventType === 'anniversary'
|
||||||
eventType === 'anniversary'
|
|
||||||
? 'bg-blue-600 text-white'
|
? 'bg-blue-600 text-white'
|
||||||
: 'bg-gray-800 text-gray-400 hover:bg-gray-700'
|
: 'bg-gray-800 text-gray-400 hover:bg-gray-700'
|
||||||
}`}
|
}`}
|
||||||
@@ -76,8 +74,7 @@ export function EventForm({ onEventCreated }: EventFormProps) {
|
|||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={() => setEventType('duration')}
|
onClick={() => setEventType('duration')}
|
||||||
className={`px-4 py-2 rounded-lg font-medium transition-colors ${
|
className={`px-4 py-2 rounded-lg font-medium transition-colors ${eventType === 'duration'
|
||||||
eventType === 'duration'
|
|
||||||
? 'bg-blue-600 text-white'
|
? 'bg-blue-600 text-white'
|
||||||
: 'bg-gray-800 text-gray-400 hover:bg-gray-700'
|
: 'bg-gray-800 text-gray-400 hover:bg-gray-700'
|
||||||
}`}
|
}`}
|
||||||
@@ -116,7 +113,7 @@ export function EventForm({ onEventCreated }: EventFormProps) {
|
|||||||
<label className="block text-sm font-medium mb-2">Год начала</label>
|
<label className="block text-sm font-medium mb-2">Год начала</label>
|
||||||
<input
|
<input
|
||||||
type="number"
|
type="number"
|
||||||
required={eventType !== 'recurring'}
|
required
|
||||||
value={formData.startYear || ''}
|
value={formData.startYear || ''}
|
||||||
onChange={(e) =>
|
onChange={(e) =>
|
||||||
setFormData({
|
setFormData({
|
||||||
359
pnpm-lock.yaml
generated
@@ -64,8 +64,11 @@ importers:
|
|||||||
specifier: ^11.0.1
|
specifier: ^11.0.1
|
||||||
version: 11.1.9(@nestjs/common@11.1.9(class-transformer@0.5.1)(class-validator@0.14.3)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/platform-express@11.1.9)(reflect-metadata@0.2.2)(rxjs@7.8.2)
|
version: 11.1.9(@nestjs/common@11.1.9(class-transformer@0.5.1)(class-validator@0.14.3)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/platform-express@11.1.9)(reflect-metadata@0.2.2)(rxjs@7.8.2)
|
||||||
'@nestjs/platform-express':
|
'@nestjs/platform-express':
|
||||||
specifier: ^11.0.1
|
specifier: ^11.1.9
|
||||||
version: 11.1.9(@nestjs/common@11.1.9(class-transformer@0.5.1)(class-validator@0.14.3)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.9)
|
version: 11.1.9(@nestjs/common@11.1.9(class-transformer@0.5.1)(class-validator@0.14.3)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.9)
|
||||||
|
'@nestjs/serve-static':
|
||||||
|
specifier: ^5.0.4
|
||||||
|
version: 5.0.4(@nestjs/common@11.1.9(class-transformer@0.5.1)(class-validator@0.14.3)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.9)(express@5.1.0)
|
||||||
class-transformer:
|
class-transformer:
|
||||||
specifier: ^0.5.1
|
specifier: ^0.5.1
|
||||||
version: 0.5.1
|
version: 0.5.1
|
||||||
@@ -106,6 +109,9 @@ importers:
|
|||||||
'@types/jest':
|
'@types/jest':
|
||||||
specifier: ^30.0.0
|
specifier: ^30.0.0
|
||||||
version: 30.0.0
|
version: 30.0.0
|
||||||
|
'@types/multer':
|
||||||
|
specifier: ^2.0.0
|
||||||
|
version: 2.0.0
|
||||||
'@types/node':
|
'@types/node':
|
||||||
specifier: ^22.19.1
|
specifier: ^22.19.1
|
||||||
version: 22.19.1
|
version: 22.19.1
|
||||||
@@ -136,10 +142,50 @@ importers:
|
|||||||
tsconfig-paths:
|
tsconfig-paths:
|
||||||
specifier: ^4.2.0
|
specifier: ^4.2.0
|
||||||
version: 4.2.0
|
version: 4.2.0
|
||||||
|
tsx:
|
||||||
|
specifier: ^4.21.0
|
||||||
|
version: 4.21.0
|
||||||
typescript:
|
typescript:
|
||||||
specifier: ^5.7.3
|
specifier: ^5.7.3
|
||||||
version: 5.9.3
|
version: 5.9.3
|
||||||
|
|
||||||
|
frontend:
|
||||||
|
dependencies:
|
||||||
|
next:
|
||||||
|
specifier: 16.0.5
|
||||||
|
version: 16.0.5(@babel/core@7.28.5)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)
|
||||||
|
react:
|
||||||
|
specifier: 19.2.0
|
||||||
|
version: 19.2.0
|
||||||
|
react-dom:
|
||||||
|
specifier: 19.2.0
|
||||||
|
version: 19.2.0(react@19.2.0)
|
||||||
|
devDependencies:
|
||||||
|
'@tailwindcss/postcss':
|
||||||
|
specifier: ^4
|
||||||
|
version: 4.1.17
|
||||||
|
'@types/node':
|
||||||
|
specifier: ^20
|
||||||
|
version: 20.19.25
|
||||||
|
'@types/react':
|
||||||
|
specifier: ^19
|
||||||
|
version: 19.2.7
|
||||||
|
'@types/react-dom':
|
||||||
|
specifier: ^19
|
||||||
|
version: 19.2.3(@types/react@19.2.7)
|
||||||
|
eslint:
|
||||||
|
specifier: ^9
|
||||||
|
version: 9.39.1(jiti@2.6.1)
|
||||||
|
eslint-config-next:
|
||||||
|
specifier: 16.0.5
|
||||||
|
version: 16.0.5(@typescript-eslint/parser@8.48.1(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3)
|
||||||
|
tailwindcss:
|
||||||
|
specifier: ^4
|
||||||
|
version: 4.1.17
|
||||||
|
typescript:
|
||||||
|
specifier: ^5
|
||||||
|
version: 5.9.3
|
||||||
|
|
||||||
packages:
|
packages:
|
||||||
|
|
||||||
'@alloc/quick-lru@5.2.0':
|
'@alloc/quick-lru@5.2.0':
|
||||||
@@ -435,6 +481,12 @@ packages:
|
|||||||
cpu: [ppc64]
|
cpu: [ppc64]
|
||||||
os: [aix]
|
os: [aix]
|
||||||
|
|
||||||
|
'@esbuild/aix-ppc64@0.27.1':
|
||||||
|
resolution: {integrity: sha512-HHB50pdsBX6k47S4u5g/CaLjqS3qwaOVE5ILsq64jyzgMhLuCuZ8rGzM9yhsAjfjkbgUPMzZEPa7DAp7yz6vuA==}
|
||||||
|
engines: {node: '>=18'}
|
||||||
|
cpu: [ppc64]
|
||||||
|
os: [aix]
|
||||||
|
|
||||||
'@esbuild/android-arm64@0.18.20':
|
'@esbuild/android-arm64@0.18.20':
|
||||||
resolution: {integrity: sha512-Nz4rJcchGDtENV0eMKUNa6L12zz2zBDXuhj/Vjh18zGqB44Bi7MBMSXjgunJgjRhCmKOjnPuZp4Mb6OKqtMHLQ==}
|
resolution: {integrity: sha512-Nz4rJcchGDtENV0eMKUNa6L12zz2zBDXuhj/Vjh18zGqB44Bi7MBMSXjgunJgjRhCmKOjnPuZp4Mb6OKqtMHLQ==}
|
||||||
engines: {node: '>=12'}
|
engines: {node: '>=12'}
|
||||||
@@ -447,6 +499,12 @@ packages:
|
|||||||
cpu: [arm64]
|
cpu: [arm64]
|
||||||
os: [android]
|
os: [android]
|
||||||
|
|
||||||
|
'@esbuild/android-arm64@0.27.1':
|
||||||
|
resolution: {integrity: sha512-45fuKmAJpxnQWixOGCrS+ro4Uvb4Re9+UTieUY2f8AEc+t7d4AaZ6eUJ3Hva7dtrxAAWHtlEFsXFMAgNnGU9uQ==}
|
||||||
|
engines: {node: '>=18'}
|
||||||
|
cpu: [arm64]
|
||||||
|
os: [android]
|
||||||
|
|
||||||
'@esbuild/android-arm@0.18.20':
|
'@esbuild/android-arm@0.18.20':
|
||||||
resolution: {integrity: sha512-fyi7TDI/ijKKNZTUJAQqiG5T7YjJXgnzkURqmGj13C6dCqckZBLdl4h7bkhHt/t0WP+zO9/zwroDvANaOqO5Sw==}
|
resolution: {integrity: sha512-fyi7TDI/ijKKNZTUJAQqiG5T7YjJXgnzkURqmGj13C6dCqckZBLdl4h7bkhHt/t0WP+zO9/zwroDvANaOqO5Sw==}
|
||||||
engines: {node: '>=12'}
|
engines: {node: '>=12'}
|
||||||
@@ -459,6 +517,12 @@ packages:
|
|||||||
cpu: [arm]
|
cpu: [arm]
|
||||||
os: [android]
|
os: [android]
|
||||||
|
|
||||||
|
'@esbuild/android-arm@0.27.1':
|
||||||
|
resolution: {integrity: sha512-kFqa6/UcaTbGm/NncN9kzVOODjhZW8e+FRdSeypWe6j33gzclHtwlANs26JrupOntlcWmB0u8+8HZo8s7thHvg==}
|
||||||
|
engines: {node: '>=18'}
|
||||||
|
cpu: [arm]
|
||||||
|
os: [android]
|
||||||
|
|
||||||
'@esbuild/android-x64@0.18.20':
|
'@esbuild/android-x64@0.18.20':
|
||||||
resolution: {integrity: sha512-8GDdlePJA8D6zlZYJV/jnrRAi6rOiNaCC/JclcXpB+KIuvfBN4owLtgzY2bsxnx666XjJx2kDPUmnTtR8qKQUg==}
|
resolution: {integrity: sha512-8GDdlePJA8D6zlZYJV/jnrRAi6rOiNaCC/JclcXpB+KIuvfBN4owLtgzY2bsxnx666XjJx2kDPUmnTtR8qKQUg==}
|
||||||
engines: {node: '>=12'}
|
engines: {node: '>=12'}
|
||||||
@@ -471,6 +535,12 @@ packages:
|
|||||||
cpu: [x64]
|
cpu: [x64]
|
||||||
os: [android]
|
os: [android]
|
||||||
|
|
||||||
|
'@esbuild/android-x64@0.27.1':
|
||||||
|
resolution: {integrity: sha512-LBEpOz0BsgMEeHgenf5aqmn/lLNTFXVfoWMUox8CtWWYK9X4jmQzWjoGoNb8lmAYml/tQ/Ysvm8q7szu7BoxRQ==}
|
||||||
|
engines: {node: '>=18'}
|
||||||
|
cpu: [x64]
|
||||||
|
os: [android]
|
||||||
|
|
||||||
'@esbuild/darwin-arm64@0.18.20':
|
'@esbuild/darwin-arm64@0.18.20':
|
||||||
resolution: {integrity: sha512-bxRHW5kHU38zS2lPTPOyuyTm+S+eobPUnTNkdJEfAddYgEcll4xkT8DB9d2008DtTbl7uJag2HuE5NZAZgnNEA==}
|
resolution: {integrity: sha512-bxRHW5kHU38zS2lPTPOyuyTm+S+eobPUnTNkdJEfAddYgEcll4xkT8DB9d2008DtTbl7uJag2HuE5NZAZgnNEA==}
|
||||||
engines: {node: '>=12'}
|
engines: {node: '>=12'}
|
||||||
@@ -483,6 +553,12 @@ packages:
|
|||||||
cpu: [arm64]
|
cpu: [arm64]
|
||||||
os: [darwin]
|
os: [darwin]
|
||||||
|
|
||||||
|
'@esbuild/darwin-arm64@0.27.1':
|
||||||
|
resolution: {integrity: sha512-veg7fL8eMSCVKL7IW4pxb54QERtedFDfY/ASrumK/SbFsXnRazxY4YykN/THYqFnFwJ0aVjiUrVG2PwcdAEqQQ==}
|
||||||
|
engines: {node: '>=18'}
|
||||||
|
cpu: [arm64]
|
||||||
|
os: [darwin]
|
||||||
|
|
||||||
'@esbuild/darwin-x64@0.18.20':
|
'@esbuild/darwin-x64@0.18.20':
|
||||||
resolution: {integrity: sha512-pc5gxlMDxzm513qPGbCbDukOdsGtKhfxD1zJKXjCCcU7ju50O7MeAZ8c4krSJcOIJGFR+qx21yMMVYwiQvyTyQ==}
|
resolution: {integrity: sha512-pc5gxlMDxzm513qPGbCbDukOdsGtKhfxD1zJKXjCCcU7ju50O7MeAZ8c4krSJcOIJGFR+qx21yMMVYwiQvyTyQ==}
|
||||||
engines: {node: '>=12'}
|
engines: {node: '>=12'}
|
||||||
@@ -495,6 +571,12 @@ packages:
|
|||||||
cpu: [x64]
|
cpu: [x64]
|
||||||
os: [darwin]
|
os: [darwin]
|
||||||
|
|
||||||
|
'@esbuild/darwin-x64@0.27.1':
|
||||||
|
resolution: {integrity: sha512-+3ELd+nTzhfWb07Vol7EZ+5PTbJ/u74nC6iv4/lwIU99Ip5uuY6QoIf0Hn4m2HoV0qcnRivN3KSqc+FyCHjoVQ==}
|
||||||
|
engines: {node: '>=18'}
|
||||||
|
cpu: [x64]
|
||||||
|
os: [darwin]
|
||||||
|
|
||||||
'@esbuild/freebsd-arm64@0.18.20':
|
'@esbuild/freebsd-arm64@0.18.20':
|
||||||
resolution: {integrity: sha512-yqDQHy4QHevpMAaxhhIwYPMv1NECwOvIpGCZkECn8w2WFHXjEwrBn3CeNIYsibZ/iZEUemj++M26W3cNR5h+Tw==}
|
resolution: {integrity: sha512-yqDQHy4QHevpMAaxhhIwYPMv1NECwOvIpGCZkECn8w2WFHXjEwrBn3CeNIYsibZ/iZEUemj++M26W3cNR5h+Tw==}
|
||||||
engines: {node: '>=12'}
|
engines: {node: '>=12'}
|
||||||
@@ -507,6 +589,12 @@ packages:
|
|||||||
cpu: [arm64]
|
cpu: [arm64]
|
||||||
os: [freebsd]
|
os: [freebsd]
|
||||||
|
|
||||||
|
'@esbuild/freebsd-arm64@0.27.1':
|
||||||
|
resolution: {integrity: sha512-/8Rfgns4XD9XOSXlzUDepG8PX+AVWHliYlUkFI3K3GB6tqbdjYqdhcb4BKRd7C0BhZSoaCxhv8kTcBrcZWP+xg==}
|
||||||
|
engines: {node: '>=18'}
|
||||||
|
cpu: [arm64]
|
||||||
|
os: [freebsd]
|
||||||
|
|
||||||
'@esbuild/freebsd-x64@0.18.20':
|
'@esbuild/freebsd-x64@0.18.20':
|
||||||
resolution: {integrity: sha512-tgWRPPuQsd3RmBZwarGVHZQvtzfEBOreNuxEMKFcd5DaDn2PbBxfwLcj4+aenoh7ctXcbXmOQIn8HI6mCSw5MQ==}
|
resolution: {integrity: sha512-tgWRPPuQsd3RmBZwarGVHZQvtzfEBOreNuxEMKFcd5DaDn2PbBxfwLcj4+aenoh7ctXcbXmOQIn8HI6mCSw5MQ==}
|
||||||
engines: {node: '>=12'}
|
engines: {node: '>=12'}
|
||||||
@@ -519,6 +607,12 @@ packages:
|
|||||||
cpu: [x64]
|
cpu: [x64]
|
||||||
os: [freebsd]
|
os: [freebsd]
|
||||||
|
|
||||||
|
'@esbuild/freebsd-x64@0.27.1':
|
||||||
|
resolution: {integrity: sha512-GITpD8dK9C+r+5yRT/UKVT36h/DQLOHdwGVwwoHidlnA168oD3uxA878XloXebK4Ul3gDBBIvEdL7go9gCUFzQ==}
|
||||||
|
engines: {node: '>=18'}
|
||||||
|
cpu: [x64]
|
||||||
|
os: [freebsd]
|
||||||
|
|
||||||
'@esbuild/linux-arm64@0.18.20':
|
'@esbuild/linux-arm64@0.18.20':
|
||||||
resolution: {integrity: sha512-2YbscF+UL7SQAVIpnWvYwM+3LskyDmPhe31pE7/aoTMFKKzIc9lLbyGUpmmb8a8AixOL61sQ/mFh3jEjHYFvdA==}
|
resolution: {integrity: sha512-2YbscF+UL7SQAVIpnWvYwM+3LskyDmPhe31pE7/aoTMFKKzIc9lLbyGUpmmb8a8AixOL61sQ/mFh3jEjHYFvdA==}
|
||||||
engines: {node: '>=12'}
|
engines: {node: '>=12'}
|
||||||
@@ -531,6 +625,12 @@ packages:
|
|||||||
cpu: [arm64]
|
cpu: [arm64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
|
|
||||||
|
'@esbuild/linux-arm64@0.27.1':
|
||||||
|
resolution: {integrity: sha512-W9//kCrh/6in9rWIBdKaMtuTTzNj6jSeG/haWBADqLLa9P8O5YSRDzgD5y9QBok4AYlzS6ARHifAb75V6G670Q==}
|
||||||
|
engines: {node: '>=18'}
|
||||||
|
cpu: [arm64]
|
||||||
|
os: [linux]
|
||||||
|
|
||||||
'@esbuild/linux-arm@0.18.20':
|
'@esbuild/linux-arm@0.18.20':
|
||||||
resolution: {integrity: sha512-/5bHkMWnq1EgKr1V+Ybz3s1hWXok7mDFUMQ4cG10AfW3wL02PSZi5kFpYKrptDsgb2WAJIvRcDm+qIvXf/apvg==}
|
resolution: {integrity: sha512-/5bHkMWnq1EgKr1V+Ybz3s1hWXok7mDFUMQ4cG10AfW3wL02PSZi5kFpYKrptDsgb2WAJIvRcDm+qIvXf/apvg==}
|
||||||
engines: {node: '>=12'}
|
engines: {node: '>=12'}
|
||||||
@@ -543,6 +643,12 @@ packages:
|
|||||||
cpu: [arm]
|
cpu: [arm]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
|
|
||||||
|
'@esbuild/linux-arm@0.27.1':
|
||||||
|
resolution: {integrity: sha512-ieMID0JRZY/ZeCrsFQ3Y3NlHNCqIhTprJfDgSB3/lv5jJZ8FX3hqPyXWhe+gvS5ARMBJ242PM+VNz/ctNj//eA==}
|
||||||
|
engines: {node: '>=18'}
|
||||||
|
cpu: [arm]
|
||||||
|
os: [linux]
|
||||||
|
|
||||||
'@esbuild/linux-ia32@0.18.20':
|
'@esbuild/linux-ia32@0.18.20':
|
||||||
resolution: {integrity: sha512-P4etWwq6IsReT0E1KHU40bOnzMHoH73aXp96Fs8TIT6z9Hu8G6+0SHSw9i2isWrD2nbx2qo5yUqACgdfVGx7TA==}
|
resolution: {integrity: sha512-P4etWwq6IsReT0E1KHU40bOnzMHoH73aXp96Fs8TIT6z9Hu8G6+0SHSw9i2isWrD2nbx2qo5yUqACgdfVGx7TA==}
|
||||||
engines: {node: '>=12'}
|
engines: {node: '>=12'}
|
||||||
@@ -555,6 +661,12 @@ packages:
|
|||||||
cpu: [ia32]
|
cpu: [ia32]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
|
|
||||||
|
'@esbuild/linux-ia32@0.27.1':
|
||||||
|
resolution: {integrity: sha512-VIUV4z8GD8rtSVMfAj1aXFahsi/+tcoXXNYmXgzISL+KB381vbSTNdeZHHHIYqFyXcoEhu9n5cT+05tRv13rlw==}
|
||||||
|
engines: {node: '>=18'}
|
||||||
|
cpu: [ia32]
|
||||||
|
os: [linux]
|
||||||
|
|
||||||
'@esbuild/linux-loong64@0.18.20':
|
'@esbuild/linux-loong64@0.18.20':
|
||||||
resolution: {integrity: sha512-nXW8nqBTrOpDLPgPY9uV+/1DjxoQ7DoB2N8eocyq8I9XuqJ7BiAMDMf9n1xZM9TgW0J8zrquIb/A7s3BJv7rjg==}
|
resolution: {integrity: sha512-nXW8nqBTrOpDLPgPY9uV+/1DjxoQ7DoB2N8eocyq8I9XuqJ7BiAMDMf9n1xZM9TgW0J8zrquIb/A7s3BJv7rjg==}
|
||||||
engines: {node: '>=12'}
|
engines: {node: '>=12'}
|
||||||
@@ -567,6 +679,12 @@ packages:
|
|||||||
cpu: [loong64]
|
cpu: [loong64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
|
|
||||||
|
'@esbuild/linux-loong64@0.27.1':
|
||||||
|
resolution: {integrity: sha512-l4rfiiJRN7sTNI//ff65zJ9z8U+k6zcCg0LALU5iEWzY+a1mVZ8iWC1k5EsNKThZ7XCQ6YWtsZ8EWYm7r1UEsg==}
|
||||||
|
engines: {node: '>=18'}
|
||||||
|
cpu: [loong64]
|
||||||
|
os: [linux]
|
||||||
|
|
||||||
'@esbuild/linux-mips64el@0.18.20':
|
'@esbuild/linux-mips64el@0.18.20':
|
||||||
resolution: {integrity: sha512-d5NeaXZcHp8PzYy5VnXV3VSd2D328Zb+9dEq5HE6bw6+N86JVPExrA6O68OPwobntbNJ0pzCpUFZTo3w0GyetQ==}
|
resolution: {integrity: sha512-d5NeaXZcHp8PzYy5VnXV3VSd2D328Zb+9dEq5HE6bw6+N86JVPExrA6O68OPwobntbNJ0pzCpUFZTo3w0GyetQ==}
|
||||||
engines: {node: '>=12'}
|
engines: {node: '>=12'}
|
||||||
@@ -579,6 +697,12 @@ packages:
|
|||||||
cpu: [mips64el]
|
cpu: [mips64el]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
|
|
||||||
|
'@esbuild/linux-mips64el@0.27.1':
|
||||||
|
resolution: {integrity: sha512-U0bEuAOLvO/DWFdygTHWY8C067FXz+UbzKgxYhXC0fDieFa0kDIra1FAhsAARRJbvEyso8aAqvPdNxzWuStBnA==}
|
||||||
|
engines: {node: '>=18'}
|
||||||
|
cpu: [mips64el]
|
||||||
|
os: [linux]
|
||||||
|
|
||||||
'@esbuild/linux-ppc64@0.18.20':
|
'@esbuild/linux-ppc64@0.18.20':
|
||||||
resolution: {integrity: sha512-WHPyeScRNcmANnLQkq6AfyXRFr5D6N2sKgkFo2FqguP44Nw2eyDlbTdZwd9GYk98DZG9QItIiTlFLHJHjxP3FA==}
|
resolution: {integrity: sha512-WHPyeScRNcmANnLQkq6AfyXRFr5D6N2sKgkFo2FqguP44Nw2eyDlbTdZwd9GYk98DZG9QItIiTlFLHJHjxP3FA==}
|
||||||
engines: {node: '>=12'}
|
engines: {node: '>=12'}
|
||||||
@@ -591,6 +715,12 @@ packages:
|
|||||||
cpu: [ppc64]
|
cpu: [ppc64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
|
|
||||||
|
'@esbuild/linux-ppc64@0.27.1':
|
||||||
|
resolution: {integrity: sha512-NzdQ/Xwu6vPSf/GkdmRNsOfIeSGnh7muundsWItmBsVpMoNPVpM61qNzAVY3pZ1glzzAxLR40UyYM23eaDDbYQ==}
|
||||||
|
engines: {node: '>=18'}
|
||||||
|
cpu: [ppc64]
|
||||||
|
os: [linux]
|
||||||
|
|
||||||
'@esbuild/linux-riscv64@0.18.20':
|
'@esbuild/linux-riscv64@0.18.20':
|
||||||
resolution: {integrity: sha512-WSxo6h5ecI5XH34KC7w5veNnKkju3zBRLEQNY7mv5mtBmrP/MjNBCAlsM2u5hDBlS3NGcTQpoBvRzqBcRtpq1A==}
|
resolution: {integrity: sha512-WSxo6h5ecI5XH34KC7w5veNnKkju3zBRLEQNY7mv5mtBmrP/MjNBCAlsM2u5hDBlS3NGcTQpoBvRzqBcRtpq1A==}
|
||||||
engines: {node: '>=12'}
|
engines: {node: '>=12'}
|
||||||
@@ -603,6 +733,12 @@ packages:
|
|||||||
cpu: [riscv64]
|
cpu: [riscv64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
|
|
||||||
|
'@esbuild/linux-riscv64@0.27.1':
|
||||||
|
resolution: {integrity: sha512-7zlw8p3IApcsN7mFw0O1Z1PyEk6PlKMu18roImfl3iQHTnr/yAfYv6s4hXPidbDoI2Q0pW+5xeoM4eTCC0UdrQ==}
|
||||||
|
engines: {node: '>=18'}
|
||||||
|
cpu: [riscv64]
|
||||||
|
os: [linux]
|
||||||
|
|
||||||
'@esbuild/linux-s390x@0.18.20':
|
'@esbuild/linux-s390x@0.18.20':
|
||||||
resolution: {integrity: sha512-+8231GMs3mAEth6Ja1iK0a1sQ3ohfcpzpRLH8uuc5/KVDFneH6jtAJLFGafpzpMRO6DzJ6AvXKze9LfFMrIHVQ==}
|
resolution: {integrity: sha512-+8231GMs3mAEth6Ja1iK0a1sQ3ohfcpzpRLH8uuc5/KVDFneH6jtAJLFGafpzpMRO6DzJ6AvXKze9LfFMrIHVQ==}
|
||||||
engines: {node: '>=12'}
|
engines: {node: '>=12'}
|
||||||
@@ -615,6 +751,12 @@ packages:
|
|||||||
cpu: [s390x]
|
cpu: [s390x]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
|
|
||||||
|
'@esbuild/linux-s390x@0.27.1':
|
||||||
|
resolution: {integrity: sha512-cGj5wli+G+nkVQdZo3+7FDKC25Uh4ZVwOAK6A06Hsvgr8WqBBuOy/1s+PUEd/6Je+vjfm6stX0kmib5b/O2Ykw==}
|
||||||
|
engines: {node: '>=18'}
|
||||||
|
cpu: [s390x]
|
||||||
|
os: [linux]
|
||||||
|
|
||||||
'@esbuild/linux-x64@0.18.20':
|
'@esbuild/linux-x64@0.18.20':
|
||||||
resolution: {integrity: sha512-UYqiqemphJcNsFEskc73jQ7B9jgwjWrSayxawS6UVFZGWrAAtkzjxSqnoclCXxWtfwLdzU+vTpcNYhpn43uP1w==}
|
resolution: {integrity: sha512-UYqiqemphJcNsFEskc73jQ7B9jgwjWrSayxawS6UVFZGWrAAtkzjxSqnoclCXxWtfwLdzU+vTpcNYhpn43uP1w==}
|
||||||
engines: {node: '>=12'}
|
engines: {node: '>=12'}
|
||||||
@@ -627,12 +769,24 @@ packages:
|
|||||||
cpu: [x64]
|
cpu: [x64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
|
|
||||||
|
'@esbuild/linux-x64@0.27.1':
|
||||||
|
resolution: {integrity: sha512-z3H/HYI9MM0HTv3hQZ81f+AKb+yEoCRlUby1F80vbQ5XdzEMyY/9iNlAmhqiBKw4MJXwfgsh7ERGEOhrM1niMA==}
|
||||||
|
engines: {node: '>=18'}
|
||||||
|
cpu: [x64]
|
||||||
|
os: [linux]
|
||||||
|
|
||||||
'@esbuild/netbsd-arm64@0.25.12':
|
'@esbuild/netbsd-arm64@0.25.12':
|
||||||
resolution: {integrity: sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg==}
|
resolution: {integrity: sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg==}
|
||||||
engines: {node: '>=18'}
|
engines: {node: '>=18'}
|
||||||
cpu: [arm64]
|
cpu: [arm64]
|
||||||
os: [netbsd]
|
os: [netbsd]
|
||||||
|
|
||||||
|
'@esbuild/netbsd-arm64@0.27.1':
|
||||||
|
resolution: {integrity: sha512-wzC24DxAvk8Em01YmVXyjl96Mr+ecTPyOuADAvjGg+fyBpGmxmcr2E5ttf7Im8D0sXZihpxzO1isus8MdjMCXQ==}
|
||||||
|
engines: {node: '>=18'}
|
||||||
|
cpu: [arm64]
|
||||||
|
os: [netbsd]
|
||||||
|
|
||||||
'@esbuild/netbsd-x64@0.18.20':
|
'@esbuild/netbsd-x64@0.18.20':
|
||||||
resolution: {integrity: sha512-iO1c++VP6xUBUmltHZoMtCUdPlnPGdBom6IrO4gyKPFFVBKioIImVooR5I83nTew5UOYrk3gIJhbZh8X44y06A==}
|
resolution: {integrity: sha512-iO1c++VP6xUBUmltHZoMtCUdPlnPGdBom6IrO4gyKPFFVBKioIImVooR5I83nTew5UOYrk3gIJhbZh8X44y06A==}
|
||||||
engines: {node: '>=12'}
|
engines: {node: '>=12'}
|
||||||
@@ -645,12 +799,24 @@ packages:
|
|||||||
cpu: [x64]
|
cpu: [x64]
|
||||||
os: [netbsd]
|
os: [netbsd]
|
||||||
|
|
||||||
|
'@esbuild/netbsd-x64@0.27.1':
|
||||||
|
resolution: {integrity: sha512-1YQ8ybGi2yIXswu6eNzJsrYIGFpnlzEWRl6iR5gMgmsrR0FcNoV1m9k9sc3PuP5rUBLshOZylc9nqSgymI+TYg==}
|
||||||
|
engines: {node: '>=18'}
|
||||||
|
cpu: [x64]
|
||||||
|
os: [netbsd]
|
||||||
|
|
||||||
'@esbuild/openbsd-arm64@0.25.12':
|
'@esbuild/openbsd-arm64@0.25.12':
|
||||||
resolution: {integrity: sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A==}
|
resolution: {integrity: sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A==}
|
||||||
engines: {node: '>=18'}
|
engines: {node: '>=18'}
|
||||||
cpu: [arm64]
|
cpu: [arm64]
|
||||||
os: [openbsd]
|
os: [openbsd]
|
||||||
|
|
||||||
|
'@esbuild/openbsd-arm64@0.27.1':
|
||||||
|
resolution: {integrity: sha512-5Z+DzLCrq5wmU7RDaMDe2DVXMRm2tTDvX2KU14JJVBN2CT/qov7XVix85QoJqHltpvAOZUAc3ndU56HSMWrv8g==}
|
||||||
|
engines: {node: '>=18'}
|
||||||
|
cpu: [arm64]
|
||||||
|
os: [openbsd]
|
||||||
|
|
||||||
'@esbuild/openbsd-x64@0.18.20':
|
'@esbuild/openbsd-x64@0.18.20':
|
||||||
resolution: {integrity: sha512-e5e4YSsuQfX4cxcygw/UCPIEP6wbIL+se3sxPdCiMbFLBWu0eiZOJ7WoD+ptCLrmjZBK1Wk7I6D/I3NglUGOxg==}
|
resolution: {integrity: sha512-e5e4YSsuQfX4cxcygw/UCPIEP6wbIL+se3sxPdCiMbFLBWu0eiZOJ7WoD+ptCLrmjZBK1Wk7I6D/I3NglUGOxg==}
|
||||||
engines: {node: '>=12'}
|
engines: {node: '>=12'}
|
||||||
@@ -663,12 +829,24 @@ packages:
|
|||||||
cpu: [x64]
|
cpu: [x64]
|
||||||
os: [openbsd]
|
os: [openbsd]
|
||||||
|
|
||||||
|
'@esbuild/openbsd-x64@0.27.1':
|
||||||
|
resolution: {integrity: sha512-Q73ENzIdPF5jap4wqLtsfh8YbYSZ8Q0wnxplOlZUOyZy7B4ZKW8DXGWgTCZmF8VWD7Tciwv5F4NsRf6vYlZtqg==}
|
||||||
|
engines: {node: '>=18'}
|
||||||
|
cpu: [x64]
|
||||||
|
os: [openbsd]
|
||||||
|
|
||||||
'@esbuild/openharmony-arm64@0.25.12':
|
'@esbuild/openharmony-arm64@0.25.12':
|
||||||
resolution: {integrity: sha512-rm0YWsqUSRrjncSXGA7Zv78Nbnw4XL6/dzr20cyrQf7ZmRcsovpcRBdhD43Nuk3y7XIoW2OxMVvwuRvk9XdASg==}
|
resolution: {integrity: sha512-rm0YWsqUSRrjncSXGA7Zv78Nbnw4XL6/dzr20cyrQf7ZmRcsovpcRBdhD43Nuk3y7XIoW2OxMVvwuRvk9XdASg==}
|
||||||
engines: {node: '>=18'}
|
engines: {node: '>=18'}
|
||||||
cpu: [arm64]
|
cpu: [arm64]
|
||||||
os: [openharmony]
|
os: [openharmony]
|
||||||
|
|
||||||
|
'@esbuild/openharmony-arm64@0.27.1':
|
||||||
|
resolution: {integrity: sha512-ajbHrGM/XiK+sXM0JzEbJAen+0E+JMQZ2l4RR4VFwvV9JEERx+oxtgkpoKv1SevhjavK2z2ReHk32pjzktWbGg==}
|
||||||
|
engines: {node: '>=18'}
|
||||||
|
cpu: [arm64]
|
||||||
|
os: [openharmony]
|
||||||
|
|
||||||
'@esbuild/sunos-x64@0.18.20':
|
'@esbuild/sunos-x64@0.18.20':
|
||||||
resolution: {integrity: sha512-kDbFRFp0YpTQVVrqUd5FTYmWo45zGaXe0X8E1G/LKFC0v8x0vWrhOWSLITcCn63lmZIxfOMXtCfti/RxN/0wnQ==}
|
resolution: {integrity: sha512-kDbFRFp0YpTQVVrqUd5FTYmWo45zGaXe0X8E1G/LKFC0v8x0vWrhOWSLITcCn63lmZIxfOMXtCfti/RxN/0wnQ==}
|
||||||
engines: {node: '>=12'}
|
engines: {node: '>=12'}
|
||||||
@@ -681,6 +859,12 @@ packages:
|
|||||||
cpu: [x64]
|
cpu: [x64]
|
||||||
os: [sunos]
|
os: [sunos]
|
||||||
|
|
||||||
|
'@esbuild/sunos-x64@0.27.1':
|
||||||
|
resolution: {integrity: sha512-IPUW+y4VIjuDVn+OMzHc5FV4GubIwPnsz6ubkvN8cuhEqH81NovB53IUlrlBkPMEPxvNnf79MGBoz8rZ2iW8HA==}
|
||||||
|
engines: {node: '>=18'}
|
||||||
|
cpu: [x64]
|
||||||
|
os: [sunos]
|
||||||
|
|
||||||
'@esbuild/win32-arm64@0.18.20':
|
'@esbuild/win32-arm64@0.18.20':
|
||||||
resolution: {integrity: sha512-ddYFR6ItYgoaq4v4JmQQaAI5s7npztfV4Ag6NrhiaW0RrnOXqBkgwZLofVTlq1daVTQNhtI5oieTvkRPfZrePg==}
|
resolution: {integrity: sha512-ddYFR6ItYgoaq4v4JmQQaAI5s7npztfV4Ag6NrhiaW0RrnOXqBkgwZLofVTlq1daVTQNhtI5oieTvkRPfZrePg==}
|
||||||
engines: {node: '>=12'}
|
engines: {node: '>=12'}
|
||||||
@@ -693,6 +877,12 @@ packages:
|
|||||||
cpu: [arm64]
|
cpu: [arm64]
|
||||||
os: [win32]
|
os: [win32]
|
||||||
|
|
||||||
|
'@esbuild/win32-arm64@0.27.1':
|
||||||
|
resolution: {integrity: sha512-RIVRWiljWA6CdVu8zkWcRmGP7iRRIIwvhDKem8UMBjPql2TXM5PkDVvvrzMtj1V+WFPB4K7zkIGM7VzRtFkjdg==}
|
||||||
|
engines: {node: '>=18'}
|
||||||
|
cpu: [arm64]
|
||||||
|
os: [win32]
|
||||||
|
|
||||||
'@esbuild/win32-ia32@0.18.20':
|
'@esbuild/win32-ia32@0.18.20':
|
||||||
resolution: {integrity: sha512-Wv7QBi3ID/rROT08SABTS7eV4hX26sVduqDOTe1MvGMjNd3EjOz4b7zeexIR62GTIEKrfJXKL9LFxTYgkyeu7g==}
|
resolution: {integrity: sha512-Wv7QBi3ID/rROT08SABTS7eV4hX26sVduqDOTe1MvGMjNd3EjOz4b7zeexIR62GTIEKrfJXKL9LFxTYgkyeu7g==}
|
||||||
engines: {node: '>=12'}
|
engines: {node: '>=12'}
|
||||||
@@ -705,6 +895,12 @@ packages:
|
|||||||
cpu: [ia32]
|
cpu: [ia32]
|
||||||
os: [win32]
|
os: [win32]
|
||||||
|
|
||||||
|
'@esbuild/win32-ia32@0.27.1':
|
||||||
|
resolution: {integrity: sha512-2BR5M8CPbptC1AK5JbJT1fWrHLvejwZidKx3UMSF0ecHMa+smhi16drIrCEggkgviBwLYd5nwrFLSl5Kho96RQ==}
|
||||||
|
engines: {node: '>=18'}
|
||||||
|
cpu: [ia32]
|
||||||
|
os: [win32]
|
||||||
|
|
||||||
'@esbuild/win32-x64@0.18.20':
|
'@esbuild/win32-x64@0.18.20':
|
||||||
resolution: {integrity: sha512-kTdfRcSiDfQca/y9QIkng02avJ+NCaQvrMejlsB3RRv5sE9rRoeBPISaZpKxHELzRxZyLvNts1P27W3wV+8geQ==}
|
resolution: {integrity: sha512-kTdfRcSiDfQca/y9QIkng02avJ+NCaQvrMejlsB3RRv5sE9rRoeBPISaZpKxHELzRxZyLvNts1P27W3wV+8geQ==}
|
||||||
engines: {node: '>=12'}
|
engines: {node: '>=12'}
|
||||||
@@ -717,6 +913,12 @@ packages:
|
|||||||
cpu: [x64]
|
cpu: [x64]
|
||||||
os: [win32]
|
os: [win32]
|
||||||
|
|
||||||
|
'@esbuild/win32-x64@0.27.1':
|
||||||
|
resolution: {integrity: sha512-d5X6RMYv6taIymSk8JBP+nxv8DQAMY6A51GPgusqLdK9wBz5wWIXy1KjTck6HnjE9hqJzJRdk+1p/t5soSbCtw==}
|
||||||
|
engines: {node: '>=18'}
|
||||||
|
cpu: [x64]
|
||||||
|
os: [win32]
|
||||||
|
|
||||||
'@eslint-community/eslint-utils@4.9.0':
|
'@eslint-community/eslint-utils@4.9.0':
|
||||||
resolution: {integrity: sha512-ayVFHdtZ+hsq1t2Dy24wCmGXGe4q9Gu3smhLYALJrr473ZH27MsnSL+LKUlimp4BWJqMDMLmPpx/Q9R3OAlL4g==}
|
resolution: {integrity: sha512-ayVFHdtZ+hsq1t2Dy24wCmGXGe4q9Gu3smhLYALJrr473ZH27MsnSL+LKUlimp4BWJqMDMLmPpx/Q9R3OAlL4g==}
|
||||||
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
|
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
|
||||||
@@ -1243,6 +1445,22 @@ packages:
|
|||||||
peerDependencies:
|
peerDependencies:
|
||||||
typescript: '>=4.8.2'
|
typescript: '>=4.8.2'
|
||||||
|
|
||||||
|
'@nestjs/serve-static@5.0.4':
|
||||||
|
resolution: {integrity: sha512-3kO1M9D3vsPyWPFardxIjUYeuolS58PnhCoBTkS7t3BrdZFZCKHnBZ15js+UOzOR2Q6HmD7ssGjLd0DVYVdvOw==}
|
||||||
|
peerDependencies:
|
||||||
|
'@fastify/static': ^8.0.4
|
||||||
|
'@nestjs/common': ^11.0.2
|
||||||
|
'@nestjs/core': ^11.0.2
|
||||||
|
express: ^5.0.1
|
||||||
|
fastify: ^5.2.1
|
||||||
|
peerDependenciesMeta:
|
||||||
|
'@fastify/static':
|
||||||
|
optional: true
|
||||||
|
express:
|
||||||
|
optional: true
|
||||||
|
fastify:
|
||||||
|
optional: true
|
||||||
|
|
||||||
'@nestjs/testing@11.1.9':
|
'@nestjs/testing@11.1.9':
|
||||||
resolution: {integrity: sha512-UFxerBDdb0RUNxQNj25pvkvNE7/vxKhXYWBt3QuwBFnYISzRIzhVlyIqLfoV5YI3zV0m0Nn4QAn1KM0zzwfEng==}
|
resolution: {integrity: sha512-UFxerBDdb0RUNxQNj25pvkvNE7/vxKhXYWBt3QuwBFnYISzRIzhVlyIqLfoV5YI3zV0m0Nn4QAn1KM0zzwfEng==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
@@ -1531,6 +1749,9 @@ packages:
|
|||||||
'@types/methods@1.1.4':
|
'@types/methods@1.1.4':
|
||||||
resolution: {integrity: sha512-ymXWVrDiCxTBE3+RIrrP533E70eA+9qu7zdWoHuOmGujkYtzf4HQF96b8nwHLqhuf4ykX61IGRIB38CC6/sImQ==}
|
resolution: {integrity: sha512-ymXWVrDiCxTBE3+RIrrP533E70eA+9qu7zdWoHuOmGujkYtzf4HQF96b8nwHLqhuf4ykX61IGRIB38CC6/sImQ==}
|
||||||
|
|
||||||
|
'@types/multer@2.0.0':
|
||||||
|
resolution: {integrity: sha512-C3Z9v9Evij2yST3RSBktxP9STm6OdMc5uR1xF1SGr98uv8dUlAL2hqwrZ3GVB3uyMyiegnscEK6PGtYvNrjTjw==}
|
||||||
|
|
||||||
'@types/node@20.19.25':
|
'@types/node@20.19.25':
|
||||||
resolution: {integrity: sha512-ZsJzA5thDQMSQO788d7IocwwQbI8B5OPzmqNvpf3NY/+MHDAS759Wo0gd2WQeXYt5AAAQjzcrTVC6SKCuYgoCQ==}
|
resolution: {integrity: sha512-ZsJzA5thDQMSQO788d7IocwwQbI8B5OPzmqNvpf3NY/+MHDAS759Wo0gd2WQeXYt5AAAQjzcrTVC6SKCuYgoCQ==}
|
||||||
|
|
||||||
@@ -2482,6 +2703,11 @@ packages:
|
|||||||
engines: {node: '>=18'}
|
engines: {node: '>=18'}
|
||||||
hasBin: true
|
hasBin: true
|
||||||
|
|
||||||
|
esbuild@0.27.1:
|
||||||
|
resolution: {integrity: sha512-yY35KZckJJuVVPXpvjgxiCuVEJT67F6zDeVTv4rizyPrfGBUpZQsvmxnN+C371c2esD/hNMjj4tpBhuueLN7aA==}
|
||||||
|
engines: {node: '>=18'}
|
||||||
|
hasBin: true
|
||||||
|
|
||||||
escalade@3.2.0:
|
escalade@3.2.0:
|
||||||
resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==}
|
resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==}
|
||||||
engines: {node: '>=6'}
|
engines: {node: '>=6'}
|
||||||
@@ -4265,6 +4491,11 @@ packages:
|
|||||||
tslib@2.8.1:
|
tslib@2.8.1:
|
||||||
resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==}
|
resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==}
|
||||||
|
|
||||||
|
tsx@4.21.0:
|
||||||
|
resolution: {integrity: sha512-5C1sg4USs1lfG0GFb2RLXsdpXqBSEhAaA/0kPL01wxzpMqLILNxIxIOKiILz+cdg/pLnOUxFYOR5yhHU666wbw==}
|
||||||
|
engines: {node: '>=18.0.0'}
|
||||||
|
hasBin: true
|
||||||
|
|
||||||
turbo-darwin-64@2.6.3:
|
turbo-darwin-64@2.6.3:
|
||||||
resolution: {integrity: sha512-BlJJDc1CQ7SK5Y5qnl7AzpkvKSnpkfPmnA+HeU/sgny3oHZckPV2776ebO2M33CYDSor7+8HQwaodY++IINhYg==}
|
resolution: {integrity: sha512-BlJJDc1CQ7SK5Y5qnl7AzpkvKSnpkfPmnA+HeU/sgny3oHZckPV2776ebO2M33CYDSor7+8HQwaodY++IINhYg==}
|
||||||
cpu: [x64]
|
cpu: [x64]
|
||||||
@@ -4851,147 +5082,225 @@ snapshots:
|
|||||||
'@esbuild/aix-ppc64@0.25.12':
|
'@esbuild/aix-ppc64@0.25.12':
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
|
'@esbuild/aix-ppc64@0.27.1':
|
||||||
|
optional: true
|
||||||
|
|
||||||
'@esbuild/android-arm64@0.18.20':
|
'@esbuild/android-arm64@0.18.20':
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
'@esbuild/android-arm64@0.25.12':
|
'@esbuild/android-arm64@0.25.12':
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
|
'@esbuild/android-arm64@0.27.1':
|
||||||
|
optional: true
|
||||||
|
|
||||||
'@esbuild/android-arm@0.18.20':
|
'@esbuild/android-arm@0.18.20':
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
'@esbuild/android-arm@0.25.12':
|
'@esbuild/android-arm@0.25.12':
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
|
'@esbuild/android-arm@0.27.1':
|
||||||
|
optional: true
|
||||||
|
|
||||||
'@esbuild/android-x64@0.18.20':
|
'@esbuild/android-x64@0.18.20':
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
'@esbuild/android-x64@0.25.12':
|
'@esbuild/android-x64@0.25.12':
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
|
'@esbuild/android-x64@0.27.1':
|
||||||
|
optional: true
|
||||||
|
|
||||||
'@esbuild/darwin-arm64@0.18.20':
|
'@esbuild/darwin-arm64@0.18.20':
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
'@esbuild/darwin-arm64@0.25.12':
|
'@esbuild/darwin-arm64@0.25.12':
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
|
'@esbuild/darwin-arm64@0.27.1':
|
||||||
|
optional: true
|
||||||
|
|
||||||
'@esbuild/darwin-x64@0.18.20':
|
'@esbuild/darwin-x64@0.18.20':
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
'@esbuild/darwin-x64@0.25.12':
|
'@esbuild/darwin-x64@0.25.12':
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
|
'@esbuild/darwin-x64@0.27.1':
|
||||||
|
optional: true
|
||||||
|
|
||||||
'@esbuild/freebsd-arm64@0.18.20':
|
'@esbuild/freebsd-arm64@0.18.20':
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
'@esbuild/freebsd-arm64@0.25.12':
|
'@esbuild/freebsd-arm64@0.25.12':
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
|
'@esbuild/freebsd-arm64@0.27.1':
|
||||||
|
optional: true
|
||||||
|
|
||||||
'@esbuild/freebsd-x64@0.18.20':
|
'@esbuild/freebsd-x64@0.18.20':
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
'@esbuild/freebsd-x64@0.25.12':
|
'@esbuild/freebsd-x64@0.25.12':
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
|
'@esbuild/freebsd-x64@0.27.1':
|
||||||
|
optional: true
|
||||||
|
|
||||||
'@esbuild/linux-arm64@0.18.20':
|
'@esbuild/linux-arm64@0.18.20':
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
'@esbuild/linux-arm64@0.25.12':
|
'@esbuild/linux-arm64@0.25.12':
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
|
'@esbuild/linux-arm64@0.27.1':
|
||||||
|
optional: true
|
||||||
|
|
||||||
'@esbuild/linux-arm@0.18.20':
|
'@esbuild/linux-arm@0.18.20':
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
'@esbuild/linux-arm@0.25.12':
|
'@esbuild/linux-arm@0.25.12':
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
|
'@esbuild/linux-arm@0.27.1':
|
||||||
|
optional: true
|
||||||
|
|
||||||
'@esbuild/linux-ia32@0.18.20':
|
'@esbuild/linux-ia32@0.18.20':
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
'@esbuild/linux-ia32@0.25.12':
|
'@esbuild/linux-ia32@0.25.12':
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
|
'@esbuild/linux-ia32@0.27.1':
|
||||||
|
optional: true
|
||||||
|
|
||||||
'@esbuild/linux-loong64@0.18.20':
|
'@esbuild/linux-loong64@0.18.20':
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
'@esbuild/linux-loong64@0.25.12':
|
'@esbuild/linux-loong64@0.25.12':
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
|
'@esbuild/linux-loong64@0.27.1':
|
||||||
|
optional: true
|
||||||
|
|
||||||
'@esbuild/linux-mips64el@0.18.20':
|
'@esbuild/linux-mips64el@0.18.20':
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
'@esbuild/linux-mips64el@0.25.12':
|
'@esbuild/linux-mips64el@0.25.12':
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
|
'@esbuild/linux-mips64el@0.27.1':
|
||||||
|
optional: true
|
||||||
|
|
||||||
'@esbuild/linux-ppc64@0.18.20':
|
'@esbuild/linux-ppc64@0.18.20':
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
'@esbuild/linux-ppc64@0.25.12':
|
'@esbuild/linux-ppc64@0.25.12':
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
|
'@esbuild/linux-ppc64@0.27.1':
|
||||||
|
optional: true
|
||||||
|
|
||||||
'@esbuild/linux-riscv64@0.18.20':
|
'@esbuild/linux-riscv64@0.18.20':
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
'@esbuild/linux-riscv64@0.25.12':
|
'@esbuild/linux-riscv64@0.25.12':
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
|
'@esbuild/linux-riscv64@0.27.1':
|
||||||
|
optional: true
|
||||||
|
|
||||||
'@esbuild/linux-s390x@0.18.20':
|
'@esbuild/linux-s390x@0.18.20':
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
'@esbuild/linux-s390x@0.25.12':
|
'@esbuild/linux-s390x@0.25.12':
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
|
'@esbuild/linux-s390x@0.27.1':
|
||||||
|
optional: true
|
||||||
|
|
||||||
'@esbuild/linux-x64@0.18.20':
|
'@esbuild/linux-x64@0.18.20':
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
'@esbuild/linux-x64@0.25.12':
|
'@esbuild/linux-x64@0.25.12':
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
|
'@esbuild/linux-x64@0.27.1':
|
||||||
|
optional: true
|
||||||
|
|
||||||
'@esbuild/netbsd-arm64@0.25.12':
|
'@esbuild/netbsd-arm64@0.25.12':
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
|
'@esbuild/netbsd-arm64@0.27.1':
|
||||||
|
optional: true
|
||||||
|
|
||||||
'@esbuild/netbsd-x64@0.18.20':
|
'@esbuild/netbsd-x64@0.18.20':
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
'@esbuild/netbsd-x64@0.25.12':
|
'@esbuild/netbsd-x64@0.25.12':
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
|
'@esbuild/netbsd-x64@0.27.1':
|
||||||
|
optional: true
|
||||||
|
|
||||||
'@esbuild/openbsd-arm64@0.25.12':
|
'@esbuild/openbsd-arm64@0.25.12':
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
|
'@esbuild/openbsd-arm64@0.27.1':
|
||||||
|
optional: true
|
||||||
|
|
||||||
'@esbuild/openbsd-x64@0.18.20':
|
'@esbuild/openbsd-x64@0.18.20':
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
'@esbuild/openbsd-x64@0.25.12':
|
'@esbuild/openbsd-x64@0.25.12':
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
|
'@esbuild/openbsd-x64@0.27.1':
|
||||||
|
optional: true
|
||||||
|
|
||||||
'@esbuild/openharmony-arm64@0.25.12':
|
'@esbuild/openharmony-arm64@0.25.12':
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
|
'@esbuild/openharmony-arm64@0.27.1':
|
||||||
|
optional: true
|
||||||
|
|
||||||
'@esbuild/sunos-x64@0.18.20':
|
'@esbuild/sunos-x64@0.18.20':
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
'@esbuild/sunos-x64@0.25.12':
|
'@esbuild/sunos-x64@0.25.12':
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
|
'@esbuild/sunos-x64@0.27.1':
|
||||||
|
optional: true
|
||||||
|
|
||||||
'@esbuild/win32-arm64@0.18.20':
|
'@esbuild/win32-arm64@0.18.20':
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
'@esbuild/win32-arm64@0.25.12':
|
'@esbuild/win32-arm64@0.25.12':
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
|
'@esbuild/win32-arm64@0.27.1':
|
||||||
|
optional: true
|
||||||
|
|
||||||
'@esbuild/win32-ia32@0.18.20':
|
'@esbuild/win32-ia32@0.18.20':
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
'@esbuild/win32-ia32@0.25.12':
|
'@esbuild/win32-ia32@0.25.12':
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
|
'@esbuild/win32-ia32@0.27.1':
|
||||||
|
optional: true
|
||||||
|
|
||||||
'@esbuild/win32-x64@0.18.20':
|
'@esbuild/win32-x64@0.18.20':
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
'@esbuild/win32-x64@0.25.12':
|
'@esbuild/win32-x64@0.25.12':
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
|
'@esbuild/win32-x64@0.27.1':
|
||||||
|
optional: true
|
||||||
|
|
||||||
'@eslint-community/eslint-utils@4.9.0(eslint@9.39.1(jiti@2.6.1))':
|
'@eslint-community/eslint-utils@4.9.0(eslint@9.39.1(jiti@2.6.1))':
|
||||||
dependencies:
|
dependencies:
|
||||||
eslint: 9.39.1(jiti@2.6.1)
|
eslint: 9.39.1(jiti@2.6.1)
|
||||||
@@ -5614,6 +5923,14 @@ snapshots:
|
|||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- chokidar
|
- chokidar
|
||||||
|
|
||||||
|
'@nestjs/serve-static@5.0.4(@nestjs/common@11.1.9(class-transformer@0.5.1)(class-validator@0.14.3)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.9)(express@5.1.0)':
|
||||||
|
dependencies:
|
||||||
|
'@nestjs/common': 11.1.9(class-transformer@0.5.1)(class-validator@0.14.3)(reflect-metadata@0.2.2)(rxjs@7.8.2)
|
||||||
|
'@nestjs/core': 11.1.9(@nestjs/common@11.1.9(class-transformer@0.5.1)(class-validator@0.14.3)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/platform-express@11.1.9)(reflect-metadata@0.2.2)(rxjs@7.8.2)
|
||||||
|
path-to-regexp: 8.3.0
|
||||||
|
optionalDependencies:
|
||||||
|
express: 5.1.0
|
||||||
|
|
||||||
'@nestjs/testing@11.1.9(@nestjs/common@11.1.9(class-transformer@0.5.1)(class-validator@0.14.3)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.9)(@nestjs/platform-express@11.1.9)':
|
'@nestjs/testing@11.1.9(@nestjs/common@11.1.9(class-transformer@0.5.1)(class-validator@0.14.3)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.9)(@nestjs/platform-express@11.1.9)':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@nestjs/common': 11.1.9(class-transformer@0.5.1)(class-validator@0.14.3)(reflect-metadata@0.2.2)(rxjs@7.8.2)
|
'@nestjs/common': 11.1.9(class-transformer@0.5.1)(class-validator@0.14.3)(reflect-metadata@0.2.2)(rxjs@7.8.2)
|
||||||
@@ -5869,6 +6186,10 @@ snapshots:
|
|||||||
|
|
||||||
'@types/methods@1.1.4': {}
|
'@types/methods@1.1.4': {}
|
||||||
|
|
||||||
|
'@types/multer@2.0.0':
|
||||||
|
dependencies:
|
||||||
|
'@types/express': 5.0.6
|
||||||
|
|
||||||
'@types/node@20.19.25':
|
'@types/node@20.19.25':
|
||||||
dependencies:
|
dependencies:
|
||||||
undici-types: 6.21.0
|
undici-types: 6.21.0
|
||||||
@@ -6879,6 +7200,35 @@ snapshots:
|
|||||||
'@esbuild/win32-ia32': 0.25.12
|
'@esbuild/win32-ia32': 0.25.12
|
||||||
'@esbuild/win32-x64': 0.25.12
|
'@esbuild/win32-x64': 0.25.12
|
||||||
|
|
||||||
|
esbuild@0.27.1:
|
||||||
|
optionalDependencies:
|
||||||
|
'@esbuild/aix-ppc64': 0.27.1
|
||||||
|
'@esbuild/android-arm': 0.27.1
|
||||||
|
'@esbuild/android-arm64': 0.27.1
|
||||||
|
'@esbuild/android-x64': 0.27.1
|
||||||
|
'@esbuild/darwin-arm64': 0.27.1
|
||||||
|
'@esbuild/darwin-x64': 0.27.1
|
||||||
|
'@esbuild/freebsd-arm64': 0.27.1
|
||||||
|
'@esbuild/freebsd-x64': 0.27.1
|
||||||
|
'@esbuild/linux-arm': 0.27.1
|
||||||
|
'@esbuild/linux-arm64': 0.27.1
|
||||||
|
'@esbuild/linux-ia32': 0.27.1
|
||||||
|
'@esbuild/linux-loong64': 0.27.1
|
||||||
|
'@esbuild/linux-mips64el': 0.27.1
|
||||||
|
'@esbuild/linux-ppc64': 0.27.1
|
||||||
|
'@esbuild/linux-riscv64': 0.27.1
|
||||||
|
'@esbuild/linux-s390x': 0.27.1
|
||||||
|
'@esbuild/linux-x64': 0.27.1
|
||||||
|
'@esbuild/netbsd-arm64': 0.27.1
|
||||||
|
'@esbuild/netbsd-x64': 0.27.1
|
||||||
|
'@esbuild/openbsd-arm64': 0.27.1
|
||||||
|
'@esbuild/openbsd-x64': 0.27.1
|
||||||
|
'@esbuild/openharmony-arm64': 0.27.1
|
||||||
|
'@esbuild/sunos-x64': 0.27.1
|
||||||
|
'@esbuild/win32-arm64': 0.27.1
|
||||||
|
'@esbuild/win32-ia32': 0.27.1
|
||||||
|
'@esbuild/win32-x64': 0.27.1
|
||||||
|
|
||||||
escalade@3.2.0: {}
|
escalade@3.2.0: {}
|
||||||
|
|
||||||
escape-html@1.0.3: {}
|
escape-html@1.0.3: {}
|
||||||
@@ -9029,6 +9379,13 @@ snapshots:
|
|||||||
|
|
||||||
tslib@2.8.1: {}
|
tslib@2.8.1: {}
|
||||||
|
|
||||||
|
tsx@4.21.0:
|
||||||
|
dependencies:
|
||||||
|
esbuild: 0.27.1
|
||||||
|
get-tsconfig: 4.13.0
|
||||||
|
optionalDependencies:
|
||||||
|
fsevents: 2.3.3
|
||||||
|
|
||||||
turbo-darwin-64@2.6.3:
|
turbo-darwin-64@2.6.3:
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
|
|||||||
@@ -1,3 +1,3 @@
|
|||||||
packages:
|
packages:
|
||||||
- 'admin'
|
- 'frontend'
|
||||||
- 'backend'
|
- 'backend'
|
||||||
|
|||||||