Skip to content

Commit

Permalink
fix: s3 image 업로드 로직 수정
Browse files Browse the repository at this point in the history
  • Loading branch information
tomatozil committed Apr 24, 2024
1 parent 6c8017b commit 0c1591d
Show file tree
Hide file tree
Showing 20 changed files with 171 additions and 134 deletions.
2 changes: 1 addition & 1 deletion src/auth/auth.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,7 @@ export class AuthController {
const nickname = signupRequestDto.nickname;
const avatar = signupRequestDto.avatar;

await this.authService.signup(user.id, nickname, avatar);
return await this.authService.signup(user.id, nickname, avatar);
}

// TODO: 테스트용 코드. 추후 삭제
Expand Down
30 changes: 24 additions & 6 deletions src/auth/auth.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,22 +34,40 @@ export class AuthService {

private readonly logger = new Logger(AuthService.name);

async signup(userId: number, nickname: string, avatar: string) {
async signup(userId: number, nickname: string, hasAvatar: boolean) {
const user = await this.validateUserExist(userId);

await this.validateUserAlreadySignUp(user);

await this.validateNickname(nickname);

let ret;
let updateUserDataDto;
if (hasAvatar) {
const { avatar, preSignedUrl } =
await this.usersRepository.getAvatarAndPresignedUrl(nickname);
updateUserDataDto = {
nickname: nickname,
avatar: avatar,
};
ret = preSignedUrl;
} else {
updateUserDataDto = {
nickname: nickname,
};
ret = null;
}

const updateRes = await this.usersRepository.update(userId, {
avatar: avatar,
nickname: nickname,
status: UserStatus.ONLINE,
...updateUserDataDto,
});

if (updateRes.affected !== 1) {
throw DBUpdateFailureException(`user ${userId} update failed`);
throw DBUpdateFailureException(
`유저 ${userId}의 db 업데이트가 실패했습니다`,
);
}

return ret;
}

async validateUserExist(userId: number) {
Expand Down
3 changes: 1 addition & 2 deletions src/auth/dto/signup-request.dto.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,7 @@ import { ApiProperty } from '@nestjs/swagger';
export class SignupRequestDto {
@ApiProperty({ description: '아바타' })
@IsNotEmpty()
@IsString()
avatar: string;
avatar: boolean;
@ApiProperty({ description: '닉네임' })
@IsNotEmpty()
@IsString()
Expand Down
30 changes: 15 additions & 15 deletions src/channels/dto/channel-Invitation-list-return.dto.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,18 @@ import { ApiProperty } from '@nestjs/swagger';
import { ChannelUserType } from 'src/common/enum';

export class ChannelInvitationListDto {
@ApiProperty({ description: '채널유저id' })
channelUserId: number;
@ApiProperty({ description: '유저id' })
userId : number;
@ApiProperty({ description: '유저닉네임' })
nickname : string;
@ApiProperty({ description: '유저아바타' })
avatar : string;
@ApiProperty({ description: '친구여부' })
isFriend : boolean;
@ApiProperty({ description: '차단여부' })
isBlocked : boolean;
@ApiProperty({ description: '채널유저타입' })
channelUserType : ChannelUserType;
}
@ApiProperty({ description: '채널유저id' })
channelUserId: number;
@ApiProperty({ description: '유저id' })
userId: number;
@ApiProperty({ description: '유저닉네임' })
nickname: string;
@ApiProperty({ description: '유저아바타' })
avatar: string | null;
@ApiProperty({ description: '친구여부' })
isFriend: boolean;
@ApiProperty({ description: '차단여부' })
isBlocked: boolean;
@ApiProperty({ description: '채널유저타입' })
channelUserType: ChannelUserType;
}
2 changes: 1 addition & 1 deletion src/channels/dto/channel-user-info-return.dto.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ export type ChannelUserInfoReturnDto = {
channelUserId: number;
userId: number;
nickname: string;
avatar: string;
avatar: string | null;
isFriend: boolean;
isBlocked: boolean;
channelUserType: ChannelUserType;
Expand Down
10 changes: 5 additions & 5 deletions src/channels/dto/dmchannel-list-return.dto.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
export class DmChannelListReturnDto {
channelId: number;
partnerName: string;
status: string;
avatar: string;
}
channelId: number;
partnerName: string;
status: string;
avatar: string | null;
}
7 changes: 7 additions & 0 deletions src/common/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,3 +20,10 @@ export const CHANNEL_NAME_REGEXP = /^[ㄱ-ㅎㅏ-ㅣ가-힣a-zA-Z0-9]*$/;
/* If K is of a lower value, then the rating is changed by a small fraction
but if K is of a higher value, then the changes in the rating are significant.*/
export const K = 30;

export const S3_IMAGE_URL_PREFIX = 'https://d5xph0h5q8hbn.cloudfront.net';

export const S3_OBJECT_KEY_PREFIX = 'image';
export const S3_OBJECT_KEY_SUFFIX = '.jpeg';

export const S3_OBJECT_CONTENTTYPE = 'image/jpeg';
3 changes: 1 addition & 2 deletions src/friends/dto/block-user-return.dto.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,7 @@ export class BlockUserReturnDto {
@ApiProperty({ description: '닉네임' })
nickname: string;
@ApiProperty({ description: '아바타' })
@IsNotEmpty()
avatar: string;
avatar: string | null;
@ApiProperty({ description: '상태(온라인 / 오프라인 / 인게임)' })
status: string;
}
2 changes: 1 addition & 1 deletion src/friends/dto/friend-user-return.dto.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
export type FriendUserReturnDto = {
id: number;
nickname: string;
avatar: string;
avatar: string | null;
status: string;
};
2 changes: 1 addition & 1 deletion src/game/dto/emit-event-match-end-param.dto.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { GameType } from '../../common/enum';
export class EmitEventMatchEndParamDto {
gameType: GameType;
rivalName: string;
rivalAvatar: string;
rivalAvatar: string | null;
rivalScore: number | null;
myScore: number | null;
isWin: boolean | null;
Expand Down
2 changes: 1 addition & 1 deletion src/game/dto/emit-event-server-game-ready-param.dto.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
export class EmitEventServerGameReadyParamDto {
rivalNickname: string;
rivalAvatar: string;
rivalAvatar: string | null;
myPosition: string;
ball: {
x: number;
Expand Down
2 changes: 1 addition & 1 deletion src/user-repository/dto/my-profile-response.dto.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
export type MyProfileResponseDto = {
id: number;
nickname: string;
avatar: string;
avatar: string | null;
statusMessage: string | null;
loseCount: number;
winCount: number;
Expand Down
2 changes: 1 addition & 1 deletion src/user-repository/dto/user-profile-return.dto.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
export type UserProfileReturnDto = {
id: number;
nickname: string;
avatar: string;
avatar: string | null;
statusMessage: string | null;
loseCount: number;
winCount: number;
Expand Down
4 changes: 2 additions & 2 deletions src/user-repository/entities/user.entity.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,9 @@ export class User extends BaseEntity {
@Matches(NICKNAME_REGEXP)
nickname: string;

@Column({ default: null })
@Column({ default: null, type: 'varchar' })
@IsString()
avatar: string;
avatar: string | null;

@Column()
@IsNotEmpty()
Expand Down
62 changes: 61 additions & 1 deletion src/user-repository/users.repository.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
import { InjectRedis } from '@liaoliaots/nestjs-redis';
import { BadRequestException, ForbiddenException } from '@nestjs/common';
import {
BadRequestException,
ForbiddenException,
Inject,
} from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import Redis from 'ioredis';
import { UserStatus } from 'src/common/enum';
Expand All @@ -8,11 +12,23 @@ import { MyProfileResponseDto } from './dto/my-profile-response.dto';
import { UserProfileReturnDto } from './dto/user-profile-return.dto';
import { User } from './entities/user.entity';
import { RankUserReturnDto } from '../users/dto/rank-user-return.dto';
import s3Config from '../config/s3.config';
import { ConfigType } from '@nestjs/config';
import { PutObjectCommand } from '@aws-sdk/client-s3';
import { getSignedUrl } from '@aws-sdk/s3-request-presigner';
import {
S3_OBJECT_CONTENTTYPE,
S3_OBJECT_KEY_PREFIX,
S3_OBJECT_KEY_SUFFIX,
S3_IMAGE_URL_PREFIX,
} from '../common/constants';

export class UsersRepository extends Repository<User> {
constructor(
@InjectRepository(User) private dataSource: DataSource,
@InjectRedis() private readonly redis: Redis,
@Inject(s3Config.KEY)
private readonly s3Configure: ConfigType<typeof s3Config>,
) {
super(User, dataSource.manager);
}
Expand Down Expand Up @@ -161,4 +177,48 @@ export class UsersRepository extends Repository<User> {

return { rankUsers, totalItemCount };
}

async getAvatarAndPresignedUrl(nickname: string) {
const date = this.getNowDate();
const key = `${S3_OBJECT_KEY_PREFIX}/${nickname}${date}${S3_OBJECT_KEY_SUFFIX}`;
const preSignedUrl = await this.getPresignedUrl(key);
return {
avatar: `${S3_IMAGE_URL_PREFIX}/${key}`,
preSignedUrl: preSignedUrl,
};
}

private async getPresignedUrl(key: string) {
const command = new PutObjectCommand({
Bucket: this.s3Configure.S3_BUCKET_NAME,
Key: key,
ContentType: S3_OBJECT_CONTENTTYPE,
});
return await getSignedUrl(this.s3Configure.S3Object, command, {
expiresIn: 180, //초 단위
});
}

// async deleteS3Image(userId: number) {
// const command = new DeleteObjectCommand({
// Bucket: this.s3Configure.S3_BUCKET_NAME,
// Key: `images/${userId}.jpeg`,
// });
//
// const response = await this.s3Configure.S3Object.send(command);
// // TODO: s3 이미지 삭제에 실패했을 때?
// if (response.$metadata.httpStatusCode !== 200) {
// console.error(response.$metadata.httpStatusCode);
// // throw new InternalServerErrorException();
// }
// }

private getNowDate(): string {
const date = new Date();
const year = date.getFullYear().toString();
const month = (date.getMonth() + 1).toString().padStart(2, '0'); // 월은 0부터 시작하므로 +1을 해줌
const day = date.getDate().toString().padStart(2, '0');

return `${year}${month}${day}`;
}
}
2 changes: 1 addition & 1 deletion src/users/dto/rank-user-return.dto.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
export type RankUserReturnDto = {
nickname: string;
avatar: string;
avatar: string | null;
ladderScore: number;
ranking: number;
};
2 changes: 1 addition & 1 deletion src/users/dto/user-profile-response.dto.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
export type UserProfileResponseDto = {
id: number;
nickname: string;
avatar: string;
avatar: string | null;
statusMessage: string | null;
loseCount: number;
winCount: number;
Expand Down
10 changes: 3 additions & 7 deletions src/users/ranks.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,15 +15,11 @@ export class RanksService {
@InjectRedis() private readonly redis: Redis,
) {}

async findRanksWithPage(page: number): Promise<RankUserResponseDto> {
async findRanksWithPage(): Promise<RankUserResponseDto> {
//userIDRanking: [userId, userId, userId, ...]
// 시간 재기
const startTime = new Date().getTime();
const userRanking = await this.redis.zrevrange(
'rankings',
(page - 1) * 15,
page * 15 - 1,
);
const userRanking = await this.redis.zrevrange('rankings', 0, 99);
const endTime = new Date().getTime();
console.log(`redis time: ${endTime - startTime}ms`);

Expand All @@ -34,7 +30,7 @@ export class RanksService {
const rankUsers: RankUserReturnDto[] = foundUsers.map(
(user, index) => ({
...user,
ranking: index + 1 + (page - 1) * 15,
ranking: index + 1,
}),
);
const totalItemCount = userRanking.length;
Expand Down
34 changes: 8 additions & 26 deletions src/users/users.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,11 @@ import {
BadRequestException,
Body,
Controller,
Delete,
Get,
Logger,
Param,
ParseIntPipe,
Patch,
Post,
Query,
UseGuards,
} from '@nestjs/common';
Expand Down Expand Up @@ -88,9 +86,9 @@ export class UsersController {
summary: '랭킹 조회',
description: '레디스로부터 pagination해 랭킹 목록을 제공합니다.',
})
async paging(@Query('page', ParseIntPipe, PositiveIntPipe) page: number) {
async paging() {
// const rankResponseDto = await this.usersService.findRanksWithPage(); // 레디스 없이 DB에서 랭킹을 조회하는 코드
let rankResponseDto = await this.ranksServices.findRanksWithPage(page); // 레디스로부터 랭킹을 조회하는 코드
let rankResponseDto = await this.ranksServices.findRanksWithPage(); // 레디스로부터 랭킹을 조회하는 코드
if (rankResponseDto.rankUsers.length === 0) {
this.logger.log('랭킹이 없습니다. 랭킹을 추가합니다.');
rankResponseDto = await this.usersService.findRanksWithPage();
Expand Down Expand Up @@ -136,28 +134,12 @@ export class UsersController {
})
async updateMyAvatar(
@GetUser() user: User,
@Body('avatar') avatar: string,
@Body('avatar') avatar: boolean,
) {
await this.usersService.updateMyAvatar(user.id, avatar);
}

@UseGuards(AuthGuard('access'))
@Get('/s3image')
async getPresignedUrl(@GetUser() user: User) {
const presignedUrl = await this.usersService.getPresignedUrl(user.id);

return presignedUrl;
}

@UseGuards(AuthGuard('access'))
@Delete('/s3image')
async deleteAndGetPresignedUrl(@GetUser() user: User) {
const userId = user.id;

await this.usersService.deleteS3Image(userId);

const presignedUrl = await this.usersService.getPresignedUrl(userId);

return presignedUrl;
return await this.usersService.updateMyAvatar(
user.id,
user.nickname,
avatar,
);
}
}
Loading

0 comments on commit 0c1591d

Please sign in to comment.