Skip to content

Commit

Permalink
merge :: user api (#21)
Browse files Browse the repository at this point in the history
* add :: user api

* chore :: code format

* chore :: code format

* bug :: promise fending resolve

* chore :: http method

* bug :: update query

* add :: upload profile

* add :: query user info api

* add :: update user info api

* chore :: code format
  • Loading branch information
jyk1029 authored Mar 17, 2024
1 parent 22bd000 commit d41702b
Show file tree
Hide file tree
Showing 10 changed files with 545 additions and 12 deletions.
297 changes: 294 additions & 3 deletions package-lock.json

Large diffs are not rendered by default.

7 changes: 6 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,10 @@
"@nestjs/passport": "^10.0.3",
"@nestjs/platform-express": "^10.0.0",
"@nestjs/typeorm": "^10.0.2",
"aws-sdk": "^2.1574.0",
"bcrypt": "^5.1.1",
"class-validator": "^0.14.1",
"multer-s3": "^2.10.0",
"mysql2": "^3.9.1",
"passport": "^0.7.0",
"passport-google-oauth20": "^2.0.0",
Expand All @@ -38,7 +40,8 @@
"reflect-metadata": "^0.2.0",
"rxjs": "^7.8.1",
"typeorm": "^0.3.20",
"typeorm-naming-strategies": "^4.1.0"
"typeorm-naming-strategies": "^4.1.0",
"uuid": "^9.0.1"
},
"devDependencies": {
"@nestjs/cli": "^10.0.0",
Expand All @@ -47,9 +50,11 @@
"@types/bcrypt": "^5.0.2",
"@types/express": "^4.17.17",
"@types/jest": "^29.5.2",
"@types/multer": "^1.4.11",
"@types/node": "^20.3.1",
"@types/passport": "^1.0.16",
"@types/passport-google-oauth20": "^2.0.14",
"@types/passport-jwt": "^4.0.1",
"@types/passport-kakao": "^1.0.3",
"@types/passport-naver": "^1.0.4",
"@types/supertest": "^6.0.0",
Expand Down
60 changes: 58 additions & 2 deletions src/application/domain/user/controller/user.controller.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,23 @@
import { Controller, Get, Param, Post, UseFilters, UseGuards } from "@nestjs/common";
import {
Body,
Controller,
Get,
Param,
Patch,
Post,
Query,
UploadedFile,
UseFilters,
UseGuards,
UseInterceptors
} from "@nestjs/common";
import { GlobalExceptionFilter } from "../../../../infrastructure/global/filter/global.exception.filter";
import { UserService } from "../service/user.service";
import { AuthGuard } from "@nestjs/passport";
import { CurrentUser } from "../../../../infrastructure/global/decorator/current-user";
import { UserEntity } from "../../../../infrastructure/domain/user/persistence/user.entity";
import { FileInterceptor } from "@nestjs/platform-express";
import { UpdateUserRequest } from "../dto/user.dto";

@UseFilters(GlobalExceptionFilter)
@Controller('user')
Expand All @@ -14,7 +28,7 @@ export class UserController {
}

@UseGuards(AuthGuard())
@Post('apply/:id')
@Patch('apply/:id')
async applyFriend(@Param() id, @CurrentUser() user: UserEntity) {
await this.userService.applyFriend(id, user)
}
Expand All @@ -24,4 +38,46 @@ export class UserController {
async queryApplyFriend(@CurrentUser() user: UserEntity) {
return await this.userService.queryApplyFriend(user)
}

@UseGuards(AuthGuard())
@Get('friend')
async queryFriend(@CurrentUser() user: UserEntity) {
return await this.userService.queryFriend(user)
}

@Get('search')
async searchUser(@Query('keyword') keyword: string) {
return await this.userService.searchUser(keyword)
}

@UseGuards(AuthGuard())
@Post('status/:id')
async updateApplyStatus(@Param() id, @Query('status') status, @CurrentUser() user: UserEntity) {
await this.userService.updateApplyStatus(id, status, user)
}

@UseGuards(AuthGuard())
@Patch('notification')
async updateIsTurnOn(@Query('is-turn-on') isTurnOn: boolean, @CurrentUser() user: UserEntity) {
await this.userService.notificationOnOff(isTurnOn, user)
}

@UseInterceptors(FileInterceptor('file'))
@UseGuards(AuthGuard())
@Patch('upload')
async uploadProfile(@UploadedFile('file') file: Express.Multer.File, @CurrentUser() user: UserEntity) {
await this.userService.uploadProfile(file, user)
}

@UseGuards(AuthGuard())
@Get('info')
async queryUserInfo(@CurrentUser() user: UserEntity) {
return await this.userService.queryUserInfo(user)
}

@UseGuards(AuthGuard())
@Patch('update')
async updateUserInfo(@CurrentUser() user: UserEntity, @Body() request: UpdateUserRequest) {
return await this.userService.updateUser(user, request)
}
}
22 changes: 22 additions & 0 deletions src/application/domain/user/dto/user.dto.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,31 @@
import { int } from "aws-sdk/clients/datapipeline";
import { IsNotEmpty, IsNumber, IsString, Matches } from "class-validator";

export class QueryApplyFriendListResponse {
users: FriendListElement[]
}

export class FriendListElement {
id: string
nickname: string
profile: string
isTurnOn: boolean
}

export class QueryUserInfoResponse {
id: string
nickname: string
email: string
point: number
profile: string
}

export class UpdateUserRequest {
@Matches(/^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,4}$/, {
message: '유효한 이메일 주소를 입력해주세요.',
})
email: string;
@IsNotEmpty()
@IsString()
nickname: string;
}
123 changes: 120 additions & 3 deletions src/application/domain/user/service/user.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,11 @@ import { HttpException, HttpStatus, Injectable } from "@nestjs/common";
import { InjectRepository } from "@nestjs/typeorm";
import { Repository } from "typeorm";
import { FriendApplyEntity } from "../../../../infrastructure/domain/user/persistence/friend.apply.entity";
import { FriendListElement, QueryApplyFriendListResponse } from "../dto/user.dto";
import { FriendListElement, QueryApplyFriendListResponse, QueryUserInfoResponse } from "../dto/user.dto";
import { UserEntity } from "../../../../infrastructure/domain/user/persistence/user.entity";
import { FriendEntity } from "../../../../infrastructure/domain/user/persistence/friend.entity";
import { AwsService } from "../../../../infrastructure/global/utils/s3/aws.service";
import { randomUUID } from "crypto";

@Injectable()
export class UserService {
Expand All @@ -12,6 +15,9 @@ export class UserService {
private friendApplyRepository: Repository<FriendApplyEntity>,
@InjectRepository(UserEntity)
private userRepository: Repository<UserEntity>,
@InjectRepository(FriendEntity)
private friendRepository: Repository<FriendEntity>,
private awsService: AwsService
) {
}

Expand All @@ -35,14 +41,51 @@ export class UserService {
}

async queryApplyFriend(req) {
let applyList = await this.friendApplyRepository.findBy({ receiveUserId: req })
let friendListResponse = new QueryApplyFriendListResponse();

friendListResponse.users = await Promise.all(applyList.map(async (friend) => {
let requestUser = await this.userRepository.findOneBy(friend.requestUserId);
let element = new FriendListElement();

element.id = friend.id
element.nickname = requestUser.nickname
element.profile = requestUser.profile
element.isTurnOn = requestUser.isTurnOn

let friendList = await this.friendApplyRepository.findBy({ receiveUserId: req })
return element
}))

return friendListResponse
}

async queryFriend(req) {
let friendList = await this.friendRepository.findBy([ { userId1: req }, { userId2: req } ])
let friendListResponse = new QueryApplyFriendListResponse();

friendListResponse.users = await Promise.all(friendList.map(async (friend) => {
let requestUser = await this.userRepository.findOneBy(friend.requestUserId);
let requestUser = await this.userRepository.findOneBy({ id: friend.userId2.id });
let element = new FriendListElement();

element.id = friend.id
element.nickname = requestUser.nickname
element.profile = requestUser.profile
element.isTurnOn = requestUser.isTurnOn

return element
}))
return friendListResponse
}

async searchUser(keyword) {
let users = await this.userRepository.findBy({ nickname: keyword })

let friendListResponse = new QueryApplyFriendListResponse();
friendListResponse.users = await Promise.all(users.map(async (user) => {
let requestUser = await this.userRepository.findOneBy({ id: user.id });
let element = new FriendListElement();

element.id = user.id
element.nickname = requestUser.nickname
element.profile = requestUser.profile
element.isTurnOn = requestUser.isTurnOn
Expand All @@ -52,4 +95,78 @@ export class UserService {

return friendListResponse
}

async updateApplyStatus(id, status, req) {
let apply = await this.friendApplyRepository.findOne({ where: id })

if (!apply) {
throw new HttpException('Apply Not Found', HttpStatus.NOT_FOUND)
}

if (status === 'ACCEPT') {
let user = await apply.requestUserId

let friend = new FriendEntity();

friend.userId1 = req
friend.userId2 = user

await this.friendApplyRepository.delete(apply)
await this.friendRepository.save(friend)
} else if (status === 'REJECT') {
await this.friendApplyRepository.delete(apply)
}
}

async notificationOnOff(isTurnOn, req) {
let user = await this.userRepository.findOne({ where: req })

if (!user) {
throw new HttpException('User Not Found', 404)
}

user.isTurnOn = isTurnOn == 'true' ? true : false
await this.userRepository.save(user)
}

async uploadProfile(file, req) {
let user = await this.userRepository.findOne({ where: req })

if (!user) {
throw new HttpException('User Not Found', 404)
}

user.profile = await this.awsService.upload(randomUUID(), file)
await this.userRepository.save(user)
}

async queryUserInfo(req) {
let user = await this.userRepository.findOne({ where: req })
let response = new QueryUserInfoResponse()

if (!user) {
throw new HttpException('User Not Found', 404)
}

response.id = user.id
response.email = user.email
response.nickname = user.nickname
response.point = user.point
response.profile = user.profile

return response
}

async updateUser(req, request) {
let user = await this.userRepository.findOne({ where: req })

if (!user) {
throw new HttpException('User Not Found', 404)
}

user.email = request.email
user.nickname = request.nickname

await this.userRepository.save(user)
}
}
4 changes: 2 additions & 2 deletions src/infrastructure/domain/user/persistence/friend.entity.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,9 @@ export class FriendEntity {

@ManyToOne(() => UserEntity)
@JoinColumn({ name: 'user_id1' })
userId1: string;
userId1: UserEntity;

@ManyToOne(() => UserEntity)
@JoinColumn({ name: 'user_id2' })
user_id2: string;
userId2: UserEntity;
}
3 changes: 3 additions & 0 deletions src/infrastructure/domain/user/persistence/user.entity.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,9 @@ export class UserEntity {
@Column('varchar', { nullable: false, length: 3000 })
profile: string;

@Column('int', {nullable: false, default: 0})
point: number;

@Column('boolean', { nullable: false, default: true })
isTurnOn: boolean;

Expand Down
3 changes: 3 additions & 0 deletions src/infrastructure/global/jwt/jwt.guard.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import { AuthGuard } from "@nestjs/passport";

export class JwtGuard extends AuthGuard('jwt') {}
3 changes: 2 additions & 1 deletion src/infrastructure/global/module/user.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,15 @@ import { FriendApplyEntity } from "../../domain/user/persistence/friend.apply.en
import { FriendEntity } from "../../domain/user/persistence/friend.entity";
import { UserController } from "../../../application/domain/user/controller/user.controller";
import { UserService } from "../../../application/domain/user/service/user.service";
import { AwsService } from "../utils/s3/aws.service";

const USER_REPOSITORY = TypeOrmModule.forFeature([ UserEntity, FriendApplyEntity, FriendEntity ]);

@Global()
@Module({
imports: [ USER_REPOSITORY ],
controllers: [ UserController ],
providers: [ UserService ],
providers: [ UserService, AwsService ],
exports: [ USER_REPOSITORY ]
})
export class UserModule {
Expand Down
35 changes: 35 additions & 0 deletions src/infrastructure/global/utils/s3/aws.service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import { HttpException, Injectable } from "@nestjs/common";
import { ConfigService } from "@nestjs/config";
import * as AWS from 'aws-sdk';

@Injectable()
export class AwsService {
private s3Client
constructor(private config: ConfigService) {
this.s3Client = new AWS.S3({
accessKeyId: config.get('AWS_S3_ACCESS_KEY'),
secretAccessKey: config.get('AWS_S3_SECRET_KEY'),
region: config.get('AWS_S3_REGION'),
});
}
async upload(fileName: string, file) {
const allowedExtensions = ['jpg', 'jpeg', 'png', 'gif'];

const extension = file.originalname.split('.').pop()

if (!allowedExtensions.includes(extension)) {
throw new HttpException('Unsupported file extension', 400);
}
const command = {
Bucket: this.config.get('AWS_S3_BUCKET_NAME'),
Key: fileName,
Body: file.buffer,
ACL: 'public-read',
ContentType: file.mimeType,
}

await this.s3Client.upload(command).promise()

return `https://s3.${this.config.get('AWS_S3_REGION')}.amazonaws.com/${this.config.get('AWS_S3_BUCKET_NAME')}/${fileName}`;
}
}

0 comments on commit d41702b

Please sign in to comment.