Skip to content

Commit

Permalink
[SPGO-108] Upload image on register (#22)
Browse files Browse the repository at this point in the history
* update user schema

* add file proto

* init file module

* update auth proto

* upload profile image when register

* fix

* fix failed test

* fix failed test

* fix
  • Loading branch information
Nitiwat-owen authored Nov 7, 2023
1 parent 3917cfe commit aeb4ef9
Show file tree
Hide file tree
Showing 15 changed files with 274 additions and 52 deletions.
4 changes: 3 additions & 1 deletion .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -14,4 +14,6 @@ EMAIL_PASSWORD="YOUR_EMAIL_PASSWORD"

AUTH_GRPC_PORT="YOUR_AUTH_GRPC_PORT"
USER_GRPC_PORT="YOUR_USER_GRPC_PORT"
USER_REST_PORT="YOUT_USER_REST_PORT"
USER_REST_PORT="YOUT_USER_REST_PORT"

FILE_GRPC_URL="YOUR_FILE_GRPC_URL"
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
"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",
"migrate": "npx prisma migrate dev",
"proto": "protoc --plugin=./node_modules/.bin/protoc-gen-ts_proto --proto_path=./src/proto --ts_proto_out=./src/auth auth.proto --ts_proto_opt=nestJs=true --ts_proto_opt=fileSuffix=.pb; protoc --plugin=./node_modules/.bin/protoc-gen-ts_proto --proto_path=./src/proto --ts_proto_out=./src/user user.proto --ts_proto_opt=nestJs=true --ts_proto_opt=fileSuffix=.pb"
"proto": "protoc --plugin=./node_modules/.bin/protoc-gen-ts_proto --proto_path=./src/proto --ts_proto_out=./src/auth auth.proto --ts_proto_opt=nestJs=true --ts_proto_opt=fileSuffix=.pb; protoc --plugin=./node_modules/.bin/protoc-gen-ts_proto --proto_path=./src/proto --ts_proto_out=./src/user user.proto --ts_proto_opt=nestJs=true --ts_proto_opt=fileSuffix=.pb; protoc --plugin=./node_modules/.bin/protoc-gen-ts_proto --proto_path=./src/proto --ts_proto_out=./src/file file.proto --ts_proto_opt=nestJs=true --ts_proto_opt=fileSuffix=.pb"
},
"dependencies": {
"@grpc/grpc-js": "^1.9.2",
Expand Down
3 changes: 2 additions & 1 deletion src/app.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,10 @@ import { PrismaModule } from './prisma/prisma.module';
import { AuthModule } from './auth/auth.module';
import { UserModule } from './user/user.module';
import { ClientsModule, Transport } from '@nestjs/microservices';
import { FileModule } from './file/file.module';

@Module({
imports: [PrismaModule, AuthModule, UserModule],
imports: [PrismaModule, AuthModule, UserModule, FileModule],
controllers: [AppController],
providers: [AppService],
})
Expand Down
41 changes: 27 additions & 14 deletions src/auth/auth.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,23 +10,36 @@ import { BlacklistRepository } from 'src/repository/blacklist.repository';

import { SportAreaListRepository } from 'src/repository/sportAreaList.repository';


import { ClientsModule, Transport } from '@nestjs/microservices';
import { FileModule } from '../file/file.module';

@Module({
imports: [PrismaModule, JwtModule.register({}), ClientsModule.register([{
name: 'EMAIL_SERVICE',
transport: Transport.RMQ,
options: {
urls: ['amqp://localhost:5672'],
queue: 'email_queue',
queueOptions: {
durable: false
}
}
}])],
providers: [AuthService, UserRepository, AccessTokenStrategy, ConfigService, BlacklistRepository,SportAreaListRepository,],

imports: [
PrismaModule,
JwtModule.register({}),
ClientsModule.register([
{
name: 'EMAIL_SERVICE',
transport: Transport.RMQ,
options: {
urls: ['amqp://localhost:5672'],
queue: 'email_queue',
queueOptions: {
durable: false,
},
},
},
]),
FileModule,
],
providers: [
AuthService,
UserRepository,
AccessTokenStrategy,
ConfigService,
BlacklistRepository,
SportAreaListRepository,
],
controllers: [AuthController],
})
export class AuthModule {}
3 changes: 3 additions & 0 deletions src/auth/auth.pb.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,13 +21,16 @@ export interface RegisterRequest {
phoneNumber: string;
password: string;
role: string;
imageName: string;
imageData: Uint8Array;
}

export interface RegisterResponse {
firstName: string;
lastName: string;
email: string;
phoneNumber: string;
imageURL: string;
}

export interface Credential {
Expand Down
9 changes: 9 additions & 0 deletions src/auth/auth.service.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { ConfigService } from '@nestjs/config';
import { UserRepository } from '../repository/user.repository';
import { BlacklistRepository } from '../repository/blacklist.repository';
import { SportAreaListRepository } from '../repository/sportAreaList.repository';
import { FileService } from '../file/file.service';
describe('AuthService', () => {
let service: AuthService;
const mockJwtService = {
Expand All @@ -30,6 +31,10 @@ describe('AuthService', () => {
addSportArea: jest.fn(),
};

const mockFileService = {
uploadFile: jest.fn(),
};

const mockEmailService = {
emit: jest.fn(),
};
Expand Down Expand Up @@ -59,6 +64,10 @@ describe('AuthService', () => {
provide: SportAreaListRepository,
useValue: mockSportAreaListRepository,
},
{
provide: FileService,
useValue: mockFileService,
},
{
provide: 'EMAIL_SERVICE',
useValue: mockEmailService,
Expand Down
32 changes: 25 additions & 7 deletions src/auth/auth.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,6 @@ import {
ValidateTokenResponse,
ResetPasswordRequest,
ResetPasswordResponse,
// ValidateGoogleRequest,
// ValidateGoogleResponse,
ValidateOAuthRequest,
UpdateUserSportAreaRequest,
UpdateUserSportAreaResponse,
Expand All @@ -34,8 +32,9 @@ import { Role } from '@prisma/client';
import { BlacklistRepository } from '../repository/blacklist.repository';
import { SportAreaListRepository } from '../repository/sportAreaList.repository';
import { JwtPayload } from './strategies/accessToken.strategy';
import * as nodemailer from 'nodemailer';
import { Observable } from 'rxjs';
import { FileService } from '../file/file.service';
import { UploadFileRequest, UploadFileResponse } from '../file/file.pb';
import { profile } from 'console';

@Injectable()
export class AuthService implements AuthServiceController {
Expand All @@ -45,6 +44,7 @@ export class AuthService implements AuthServiceController {
private jwtService: JwtService,
private configService: ConfigService,
private sportAreaListRepo: SportAreaListRepository,
private fileService: FileService,
@Inject('EMAIL_SERVICE') private client: ClientProxy,
) {}

Expand Down Expand Up @@ -337,17 +337,35 @@ export class AuthService implements AuthServiceController {
message: 'Duplicate Email',
});
} else {
const userId = uuidv4();
let profileImage: UploadFileResponse;
if (request.imageName && request.imageData) {
const uploadFileRequest: UploadFileRequest = {
filename: request.imageName,
data: request.imageData,
userId: userId,
};
profileImage = await this.fileService.uploadFile(uploadFileRequest);
}
const hashedPassword = await bcrypt.hash(request.password, 12);
const newUser = {
id: uuidv4(),
const createdUser = {
id: userId,
firstName: request.firstName,
lastName: request.lastName,
email: request.email,
phoneNumber: request.phoneNumber,
role: request.role == 'USER' ? Role.USER : Role.SPORTAREA,
password: hashedPassword,
photoFileName: profileImage.filename,
};
const user = await this.userRepo.create(createdUser);
return {
firstName: user.firstName,
lastName: user.lastName,
email: user.email,
phoneNumber: user.phoneNumber,
imageURL: profileImage.url,
};
return await this.userRepo.create(newUser);
}
} catch (err) {
console.log(err);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
-- AlterTable
ALTER TABLE "User" ADD COLUMN "photoFileName" TEXT;
37 changes: 19 additions & 18 deletions src/database/schema.prisma
Original file line number Diff line number Diff line change
Expand Up @@ -11,27 +11,28 @@ datasource db {
}

model User {
id String @id
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
firstName String
lastName String
email String @unique
phoneNumber String?
password String?
photoURL String?
role Role @default(USER)
refreshToken String?
status Status @default(ACTIVE)
googleID String? @unique
facebookID String? @unique
sportArea SportArea[]
id String @id
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
firstName String
lastName String
email String @unique
phoneNumber String?
password String?
photoURL String?
photoFileName String?
role Role @default(USER)
refreshToken String?
status Status @default(ACTIVE)
googleID String? @unique
facebookID String? @unique
sportArea SportArea[]
}

model SportArea {
SportAreaId String @unique
userId String
user User @relation(fields: [userId], references: [id])
SportAreaId String @unique
userId String
user User @relation(fields: [userId], references: [id])
}

enum Role {
Expand Down
25 changes: 25 additions & 0 deletions src/file/file.module.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { Module } from '@nestjs/common';
import { ConfigModule } from '@nestjs/config';
import { ClientsModule, Transport } from '@nestjs/microservices';
import { join } from 'path';
import { FileService } from './file.service';

@Module({
imports: [
ConfigModule.forRoot(),
ClientsModule.register([
{
name: 'FILE_PACKAGE',
transport: Transport.GRPC,
options: {
package: 'file',
protoPath: join(__dirname, '../proto/file.proto'),
url: process.env.FILE_GRPC_URL,
},
},
]),
],
providers: [FileService],
exports: [FileService],
})
export class FileModule {}
60 changes: 60 additions & 0 deletions src/file/file.pb.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
/* eslint-disable */
import { GrpcMethod, GrpcStreamMethod } from "@nestjs/microservices";
import { Observable } from "rxjs";

export const protobufPackage = "file";

export interface UploadFileRequest {
filename: string;
data: Uint8Array;
userId: string;
}

export interface UploadFileResponse {
url: string;
filename: string;
}

export interface GetSignedURLRequest {
filename: string;
userId: string;
}

export interface GetSignedURLResponse {
url: string;
}

export const FILE_PACKAGE_NAME = "file";

export interface FileServiceClient {
uploadFile(request: UploadFileRequest): Observable<UploadFileResponse>;

getSignedUrl(request: GetSignedURLRequest): Observable<GetSignedURLResponse>;
}

export interface FileServiceController {
uploadFile(
request: UploadFileRequest,
): Promise<UploadFileResponse> | Observable<UploadFileResponse> | UploadFileResponse;

getSignedUrl(
request: GetSignedURLRequest,
): Promise<GetSignedURLResponse> | Observable<GetSignedURLResponse> | GetSignedURLResponse;
}

export function FileServiceControllerMethods() {
return function (constructor: Function) {
const grpcMethods: string[] = ["uploadFile", "getSignedUrl"];
for (const method of grpcMethods) {
const descriptor: any = Reflect.getOwnPropertyDescriptor(constructor.prototype, method);
GrpcMethod("FileService", method)(constructor.prototype[method], method, descriptor);
}
const grpcStreamMethods: string[] = [];
for (const method of grpcStreamMethods) {
const descriptor: any = Reflect.getOwnPropertyDescriptor(constructor.prototype, method);
GrpcStreamMethod("FileService", method)(constructor.prototype[method], method, descriptor);
}
};
}

export const FILE_SERVICE_NAME = "FileService";
28 changes: 28 additions & 0 deletions src/file/file.service.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import { Test, TestingModule } from '@nestjs/testing';
import { FileService } from './file.service';

describe('FileService', () => {
let service: FileService;

const mockFileClient = {
getService: jest.fn(),
};

beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
providers: [
FileService,
{
provide: 'FILE_PACKAGE',
useValue: mockFileClient,
},
],
}).compile();

service = module.get<FileService>(FileService);
});

it('should be defined', () => {
expect(service).toBeDefined();
});
});
29 changes: 29 additions & 0 deletions src/file/file.service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import { Inject, Injectable, Logger } from '@nestjs/common';
import { ClientGrpc } from '@nestjs/microservices';
import { catchError, firstValueFrom } from 'rxjs';
import {
FileServiceClient,
UploadFileRequest,
UploadFileResponse,
} from './file.pb';

@Injectable()
export class FileService {
private fileClient: FileServiceClient;
private readonly logger = new Logger(FileService.name);

constructor(@Inject('FILE_PACKAGE') private client: ClientGrpc) {
this.fileClient = this.client.getService<FileServiceClient>('FileService');
}

async uploadFile(request: UploadFileRequest): Promise<UploadFileResponse> {
return await firstValueFrom(
this.fileClient.uploadFile(request).pipe(
catchError((error) => {
this.logger.error(error);
throw error;
}),
),
);
}
}
Loading

0 comments on commit aeb4ef9

Please sign in to comment.