Skip to content

Commit 6ce135a

Browse files
feat: Implement full CRUD functionality for bridal gown (veil) management with image uploads and a dedicated UI.
1 parent e682bce commit 6ce135a

20 files changed

Lines changed: 472 additions & 57 deletions

backend/package-lock.json

Lines changed: 39 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

backend/package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
"@nestjs/mongoose": "^11.0.4",
2929
"@nestjs/passport": "^11.0.5",
3030
"@nestjs/platform-express": "^11.0.1",
31+
"@nestjs/serve-static": "^5.0.4",
3132
"bcrypt": "^6.0.0",
3233
"class-transformer": "^0.5.1",
3334
"class-validator": "^0.14.3",
@@ -46,6 +47,7 @@
4647
"@types/bcrypt": "^6.0.0",
4748
"@types/express": "^5.0.0",
4849
"@types/jest": "^30.0.0",
50+
"@types/multer": "^2.0.0",
4951
"@types/node": "^22.10.7",
5052
"@types/passport-jwt": "^4.0.1",
5153
"@types/supertest": "^6.0.2",

backend/src/app.module.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
import { Module } from '@nestjs/common';
2+
import { ServeStaticModule } from '@nestjs/serve-static';
3+
import { join } from 'path';
24
import { AppController } from './app.controller';
35
import { AppService } from './app.service';
46
import { AppConfigModule } from './common/config/app-config.module';
@@ -22,6 +24,10 @@ import { PaymentModule } from './payment/payment.module';
2224
GalleryModule,
2325
AuthModule,
2426
PaymentModule,
27+
ServeStaticModule.forRoot({
28+
rootPath: join(__dirname, '..', 'uploads'),
29+
serveRoot: '/uploads',
30+
}),
2531
],
2632
controllers: [AppController],
2733
providers: [AppService],

backend/src/veil/presentation/dto/create-veil.dto.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import {
66
IsOptional,
77
IsBoolean,
88
} from 'class-validator';
9+
import { Type } from 'class-transformer';
910

1011
export class CreateVeilDto {
1112
@IsString()
@@ -18,10 +19,12 @@ export class CreateVeilDto {
1819

1920
@IsNumber()
2021
@IsNotEmpty()
22+
@Type(() => Number)
2123
price: number;
2224

2325
@IsNumber()
2426
@IsNotEmpty()
27+
@Type(() => Number)
2528
rentalPrice: number;
2629

2730
@IsArray()
@@ -59,5 +62,6 @@ export class CreateVeilDto {
5962

6063
@IsNumber()
6164
@IsOptional()
65+
@Type(() => Number)
6266
stock?: number;
6367
}

backend/src/veil/presentation/veil.controller.ts

Lines changed: 74 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,12 @@ import {
66
Put,
77
Param,
88
Delete,
9+
UseInterceptors,
10+
UploadedFiles,
911
} from '@nestjs/common';
12+
import { FilesInterceptor } from '@nestjs/platform-express';
13+
import { diskStorage } from 'multer';
14+
import { extname } from 'path';
1015
import { VeilService } from '../application/veil.service';
1116
import { Veil } from '../domain/veil.entity';
1217
import { CreateVeilDto } from './dto/create-veil.dto';
@@ -32,19 +37,85 @@ export class VeilController {
3237
}
3338

3439
@Post()
35-
async create(@Body() createVeilDto: CreateVeilDto): Promise<Veil> {
36-
const veil = createVeilDto as unknown as Omit<Veil, 'id' | 'createdAt'>;
40+
@UseInterceptors(
41+
FilesInterceptor('files', 10, {
42+
storage: diskStorage({
43+
destination: './uploads/veils',
44+
filename: (req, file, callback) => {
45+
const uniqueSuffix =
46+
Date.now() + '-' + Math.round(Math.random() * 1e9);
47+
const ext = extname(file.originalname);
48+
callback(null, `${file.fieldname}-${uniqueSuffix}${ext}`);
49+
},
50+
}),
51+
}),
52+
)
53+
async create(
54+
@Body() createVeilDto: CreateVeilDto,
55+
@UploadedFiles() files: Array<Express.Multer.File>,
56+
): Promise<Veil> {
57+
const imagePaths = files
58+
? files.map((file) => `/uploads/veils/${file.filename}`)
59+
: [];
60+
const veilData = {
61+
...createVeilDto,
62+
images: [
63+
...(createVeilDto.images || []),
64+
...imagePaths,
65+
],
66+
};
67+
68+
// The service expects Omit<Veil, 'id' | 'createdAt'>
69+
// We cast to unknown first because DTO might have optional fields that are required in Entity or vice versa,
70+
// but effectively it matches.
71+
const veil = veilData as unknown as Omit<Veil, 'id' | 'createdAt'>;
3772
return this.veilService.create(veil);
3873
}
3974

4075
@Put(':id')
76+
@UseInterceptors(
77+
FilesInterceptor('files', 10, {
78+
storage: diskStorage({
79+
destination: './uploads/veils',
80+
filename: (req, file, callback) => {
81+
const uniqueSuffix =
82+
Date.now() + '-' + Math.round(Math.random() * 1e9);
83+
const ext = extname(file.originalname);
84+
callback(null, `${file.fieldname}-${uniqueSuffix}${ext}`);
85+
},
86+
}),
87+
}),
88+
)
4189
async update(
4290
@Param('id') id: string,
4391
@Body() updateVeilDto: UpdateVeilDto,
92+
@UploadedFiles() files: Array<Express.Multer.File>,
4493
): Promise<Veil> {
94+
const imagePaths = files
95+
? files.map((file) => `/uploads/veils/${file.filename}`)
96+
: [];
97+
98+
// Handle existing images.
99+
// If updateVeilDto.images is a string (single file from FormData), make it array.
100+
// If it's already array, keep it.
101+
// If undefined, start with empty array.
102+
let existingImages: string[] = [];
103+
if (updateVeilDto.images) {
104+
if (Array.isArray(updateVeilDto.images)) {
105+
existingImages = updateVeilDto.images;
106+
} else {
107+
existingImages = [updateVeilDto.images];
108+
}
109+
}
110+
111+
const veilData = {
112+
...updateVeilDto,
113+
images: [...existingImages, ...imagePaths],
114+
};
115+
45116
return this.veilService.update(
46117
id,
47-
updateVeilDto as unknown as Partial<Veil>,
118+
veilData as unknown as Partial<Veil>,
48119
);
49120
}
50121

62 KB
Loading
62 KB
Loading

frontend/package-lock.json

Lines changed: 16 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

frontend/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
"preview": "ng serve --configuration=production"
1010
},
1111
"dependencies": {
12+
"@angular/animations": "^21.1.3",
1213
"@angular/build": "^21.0.0",
1314
"@angular/cli": "^21.0.0",
1415
"@angular/common": "^21.0.0",

frontend/src/app.component.html

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,5 +25,6 @@ <h1 class="font-serif text-4xl text-white" i18n="@@appBrandName">Mavluda Beauty<
2525
</div>
2626
</div>
2727
} @else {
28+
<app-global-error></app-global-error>
2829
<router-outlet></router-outlet>
2930
}

0 commit comments

Comments
 (0)