Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Wolfsoul01 microservice recruitment #27

Open
wants to merge 13 commits into
base: development
Choose a base branch
from
Open
21 changes: 21 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
PORT=3000

# PostgreSQL Database Configuration
DB_HOST=localhost
DB_PORT=5432
DB_USERNAME=postgres
DB_PASSWORD=admin
DB_DATABASE=type_orm

# JWT Secret
JWT_SECRET=supersecretjwtkey

# Firebase Configuration
FIREBASE_PROJECT_ID=your-firebase-project-id
FIREBASE_PRIVATE_KEY=-----BEGIN PRIVATE KEY-----\nYOUR_PRIVATE_KEY_HERE\n-----END PRIVATE KEY-----
FIREBASE_CLIENT_EMAIL=firebase-adminsdk@your-project-id.iam.gserviceaccount.com
FIREBASE_STORAGE_BUCKET=your-firebase-storage-bucket

# Redis Configuration
REDIS_HOST=localhost
REDIS_PORT=6379
5 changes: 5 additions & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"conventionalCommits.scopes": [
"App"
]
}
Binary file modified .yarn/install-state.gz
Binary file not shown.
14 changes: 13 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,21 +17,33 @@
"test:watch": "jest --watch",
"test:cov": "jest --coverage",
"test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand",
"test:e2e": "jest --config ./test/jest-e2e.json"
"test:e2e": "jest --config ./test/jest-e2e.json",
"typeorm": "ts-node ./node_modules/typeorm/cli -d ./src/config/infrastructure/typeorm.config.ts",
"migration:generate": "npm run typeorm migration:generate src/db/migrations/migration -t -p",
"migration:run": "npm run typeorm -- migration:run",
"migration:revert": "npm run typeorm -- migration:revert"
},
"dependencies": {
"@nestjs/cache-manager": "2.0.0",
"@nestjs/common": "^10.4.4",
"@nestjs/config": "^3.2.3",
"@nestjs/core": "^10.4.4",
"@nestjs/platform-express": "^10.4.4",
"@nestjs/swagger": "^7.4.2",
"@nestjs/typeorm": "^10.0.2",
"@types/multer": "^1.4.12",
"axios": "^1.7.7",
"cache-manager": "5.2.0",
"cache-manager-redis-yet": "^5.1.5",
"class-transformer": "^0.5.1",
"class-validator": "^0.14.1",
"dotenv": "^16.4.5",
"firebase-admin": "^12.6.0",
"joi": "^17.13.3",
"pg": "^8.13.0",
"reflect-metadata": "^0.2.2",
"rxjs": "^7.8.1",
"sharp": "^0.33.5",
"typeorm": "^0.3.20",
"uuid": "^10.0.0"
},
Expand Down
22 changes: 0 additions & 22 deletions src/app.controller.spec.ts

This file was deleted.

12 changes: 0 additions & 12 deletions src/app.controller.ts

This file was deleted.

51 changes: 44 additions & 7 deletions src/app.module.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,47 @@
import { Module } from '@nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { MiddlewareConsumer, Module, NestModule } from '@nestjs/common';
import { ConfigModule, ConfigService } from '@nestjs/config';
import { CacheModule } from '@nestjs/cache-manager';

import { configSchema } from './config/config.schema';
import { TypeOrmModule } from '@nestjs/typeorm';
import { ImagesModule } from './modules/images/images.module';
import { JwtMockMiddleware } from './shared/middleware';
import { redisStore } from 'cache-manager-redis-yet';
import typeormConfig from './config/infrastructure/typeorm.config';
import { CorrelationIdMiddleware } from './shared/middleware/correlation-id.middleware';

@Module({
imports: [],
controllers: [AppController],
providers: [AppService],
imports: [
ConfigModule.forRoot({
isGlobal: true,
validationSchema: configSchema,
load: [typeormConfig],
}),
TypeOrmModule.forRootAsync({
inject: [ConfigService],
useFactory: async (configService: ConfigService) =>
configService.get('typeorm'),
}),
CacheModule.registerAsync({
isGlobal: true,
useFactory: async (configService: ConfigService) => ({
store: await redisStore({
socket: {
host: configService.get('REDIS_HOST'),
port: configService.get('REDIS_PORT'),
},
}),
ttl: configService.get('REDIS_TTl'),
}),
inject: [ConfigService],
}),
ImagesModule,
],
controllers: [],
providers: [],
})
export class AppModule {}
export class AppModule implements NestModule {
configure(consumer: MiddlewareConsumer) {
consumer.apply(JwtMockMiddleware, CorrelationIdMiddleware).forRoutes('*');
}
}
8 changes: 0 additions & 8 deletions src/app.service.ts

This file was deleted.

17 changes: 17 additions & 0 deletions src/config/config.schema.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import * as Joi from 'joi';

export const configSchema = Joi.object({
NODE_ENV: Joi.string()
.valid('development', 'production', 'test')
.default('development'),
PORT: Joi.number().default(3000),
FIREBASE_PROJECT_ID: Joi.string().required(),
FIREBASE_PRIVATE_KEY: Joi.string().required(),
FIREBASE_CLIENT_EMAIL: Joi.string().email().required(),
FIREBASE_STORAGE_BUCKET: Joi.string().required(),
JWT_SECRET: Joi.string().required(),
DB_HOST: Joi.string().required(),
REDIS_HOST: Joi.string().optional(),
REDIS_PORT: Joi.number().optional(),
REDIS_TTl: Joi.number().optional(),
});
21 changes: 21 additions & 0 deletions src/config/infrastructure/typeorm.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { registerAs } from '@nestjs/config';
import { DataSource, DataSourceOptions } from 'typeorm';
import 'dotenv/config';
import { Image } from '../../modules/images/entites/image.entity';

const config = {
type: 'postgres',
host: process.env.DB_HOST,
port: +process.env.DB_PORT,
username: process.env.DB_USERNAME,
password: process.env.DB_PASSWORD,
database: process.env.DB_DATABASE,
entities: [Image],
migrations: ['../database/migrations/*{.ts,.js}'],
//autoLoadEntities: true,
synchronize: !!process.env.DB_SYNCHRONIZE,
};

export default registerAs('typeorm', () => config);

export const connectionSource = new DataSource(config as DataSourceOptions);
6 changes: 6 additions & 0 deletions src/interface/pagination-result.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
export interface PaginatedResult<T> {
totalItems: number;
totalPages: number;
currentPage: number;
items: T[];
}
4 changes: 4 additions & 0 deletions src/interface/user-mocker.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
export interface UserMocker {
id: string;
username: string;
}
26 changes: 25 additions & 1 deletion src/main.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,32 @@
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
import { ValidationPipe } from '@nestjs/common';
import { DocumentBuilder, SwaggerModule } from '@nestjs/swagger';
import { ConfigService } from '@nestjs/config';

async function bootstrap() {
const app = await NestFactory.create(AppModule);
await app.listen(3000);

app.useGlobalPipes(
new ValidationPipe({
whitelist: true,
}),
);

//Documentation Swagger
const configService = app.get(ConfigService);
const PORT = configService.getOrThrow('PORT');
const config = new DocumentBuilder()
.setTitle('Image Service API')
.setDescription('API to upload and manage images.')
.setVersion('1.0')
.build();

const document = SwaggerModule.createDocument(app, config);
SwaggerModule.setup('api', app, document);

await app.listen(PORT);

console.log(`Server is running on port ${PORT}`);
}
bootstrap();
87 changes: 87 additions & 0 deletions src/modules/images/controller/images.controller.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
import {
Controller,
Delete,
Get,
HttpCode,
Param,
ParseUUIDPipe,
Post,
Query,
UploadedFile,
UploadedFiles,
UseGuards,
UseInterceptors,
} from '@nestjs/common';
import { FileInterceptor, FilesInterceptor } from '@nestjs/platform-express';
import { ApiOperation, ApiResponse } from '@nestjs/swagger';

import { ImagesService } from '../services/images.service';
import { PaginationDTO } from '../../../shared/dtos/pagination.dto';
import { Image } from '../entites/image.entity';
import { JwtAuthGuard } from '../../../shared/guards/jwt-auth.guard';
import {
ApiUploadImage,
ApiUploadBullImages,
} from '../../../shared/decorators/swagger-file.decorator';
import { UserMocker } from '../../../interface/user-mocker';
import { User } from '../../../shared/decorators/user.decorator';

@Controller('images')
@UseGuards(JwtAuthGuard)
export class ImagesController {
constructor(private readonly imagesService: ImagesService) {}

@Post('upload')
@ApiUploadImage()
@UseInterceptors(FileInterceptor('file'))
async uploadImage(
@UploadedFile() file: Express.Multer.File,
@User() user: UserMocker,
): Promise<Image> {
return this.imagesService.uploadImage({ file, userId: user.id });
}

@Post('upload-bull')
@ApiUploadBullImages()
@UseInterceptors(FilesInterceptor('files'))
async uploadBullImage(
@UploadedFiles() files: Express.Multer.File[],
@User() user: UserMocker,
) {
return this.imagesService.uploadBullImages({ files, userId: user.id });
}

@Get()
@ApiOperation({ summary: 'Retrieve paginated list of images' })
@ApiResponse({
status: 200,
description: 'Successfully retrieved images',
})
@ApiResponse({ status: 400, description: 'Invalid parameters' })
async getAllImage(@Query() paginationDto: PaginationDTO) {
return this.imagesService.getAllImage(paginationDto);
}

@Get(':id')
@ApiOperation({ summary: 'Get image by ID' })
@ApiResponse({
status: 200,
description: 'Successfully retrieved image',
})
@ApiResponse({ status: 404, description: 'Image not found' })
async getImageById(@Param('id', ParseUUIDPipe) id: string): Promise<Image> {
return this.imagesService.getImageById(id);
}

@Delete(':id')
@ApiOperation({ summary: 'Delete image by ID' })
@ApiResponse({
status: 204,
description: 'Image deleted successfully',
})
@ApiResponse({ status: 404, description: 'Image not found' })
@HttpCode(204)
async deletedImage(@Param('id', ParseUUIDPipe) id: string): Promise<void> {
return this.imagesService.deleteImage(id);
}
}
2 changes: 2 additions & 0 deletions src/modules/images/dtos/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export { UploadBullFileDto } from './upload-bull-file.dto';
export { UploadFileDto } from './upload-file.dto';
11 changes: 11 additions & 0 deletions src/modules/images/dtos/upload-bull-file.dto.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { IsArray, IsNotEmpty, IsString } from 'class-validator';
import { Express } from 'express';

export class UploadBullFileDto {
@IsArray()
@IsNotEmpty()
files: Express.Multer.File[];

@IsString()
userId: string;
}
10 changes: 10 additions & 0 deletions src/modules/images/dtos/upload-file.dto.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { IsNotEmpty, IsString } from 'class-validator';
import { Express } from 'express';

export class UploadFileDto {
@IsNotEmpty()
file: Express.Multer.File;

@IsString()
userId: string;
}
30 changes: 30 additions & 0 deletions src/modules/images/entites/image.entity.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import { Entity, Column, PrimaryGeneratedColumn } from 'typeorm';

@Entity()
export class Image {
@PrimaryGeneratedColumn('uuid')
id: string;

@Column()
fileName: string;

@Column()
url: string;

//in case use real created relation with new model 1:m
@Column({ nullable: true })
thumbnailUrl: string;

@Column({ nullable: true })
thumbnailFileName: string;

@Column()
mimeType: string;

@Column()
size: number;

//in case use real created relation with model user
@Column({ nullable: true })
uploaderBy: string;
}
Loading