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

[feat] 회원 차단 기능 추가 #455

Merged
5 changes: 3 additions & 2 deletions BE/src/postings/postings.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -96,8 +96,9 @@ export class PostingsController {
description: '검색어와 선택 태그의 교집합에 해당하는 게시글을 반환합니다.',
})
@ApiOkResponse({ schema: { example: search_OK } })
async search(@Query() searchPostingDto: SearchPostingDto) {
return this.postingsService.findAll(searchPostingDto);
async search(@Req() request, @Query() searchPostingDto: SearchPostingDto) {
const userId = request['user'].id;
return this.postingsService.findAll(userId, searchPostingDto);
}

@Get('titles')
Expand Down
11 changes: 10 additions & 1 deletion BE/src/postings/postings.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,13 @@ import { Report } from './entities/report.entity';
import { Period, Season } from './postings.types';
import { BLOCKING_LIMIT } from './postings.constants';
import { StorageService } from 'src/storage/storage.service';
import { BlockRepository } from 'src/users/block.repository';

@Injectable()
export class PostingsService {
constructor(
private readonly userRepository: UserRepository,
private readonly blockRepository: BlockRepository,
private readonly postingsRepository: PostingsRepository,
private readonly likedsRepository: LikedsRepository,
private readonly reportsRepository: ReportsRepository,
Expand All @@ -44,8 +46,10 @@ export class PostingsService {
return this.postingsRepository.save(posting);
}

async findAll(dto: SearchPostingDto) {
async findAll(userId: string, dto: SearchPostingDto) {
const blockedIds = await this.findBlockedIds(userId);
const postings = await this.postingsRepository.findAll(
blockedIds,
dto.keyword,
dto.sorting,
dto.offset,
Expand Down Expand Up @@ -226,4 +230,9 @@ export class PostingsService {
reports: posting.reports,
};
}

private async findBlockedIds(blocker: string) {
const blocks = await this.blockRepository.findByBlocker(blocker);
return blocks.map((block) => block.blocked.id);
}
}
10 changes: 9 additions & 1 deletion BE/src/postings/repositories/postings.repository.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ export class PostingsRepository {
}

async findAll(
blockedIds: string[],
keyword: string,
sorting: Sorting,
offset: number,
Expand All @@ -50,7 +51,14 @@ export class PostingsRepository {
withWhos: WithWho[]
) {
const conditions = ['p.title LIKE :keyword'];
let params: { [key: string]: string } = { keyword: `%${keyword}%` };
let params: { [key: string]: string | string[] } = {
keyword: `%${keyword}%`,
};

if (blockedIds.length > 0) {
conditions.push('p.writer NOT IN (:...blockedIds)');
params = { ...params, blockedIds };
}

if (budget) {
conditions.push('p.budget = :budget');
Expand Down
38 changes: 38 additions & 0 deletions BE/src/users/block.repository.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import { Inject, Injectable } from '@nestjs/common';
import { Block } from './entities/block.entity';
import { BLOCK_REPOSITORY } from './users.constants';
import { Repository } from 'typeorm';
import { User } from './entities/user.entity';

@Injectable()
export class BlockRepository {
constructor(
@Inject(BLOCK_REPOSITORY)
private blockRepository: Repository<Block>
) {}

save(blocker: User, blocked: User) {
return this.blockRepository.save({ blocker, blocked });
}

findByBlocker(blocker: string) {
return this.blockRepository
.createQueryBuilder('b')
.leftJoinAndSelect('b.blocker', 'x')
.leftJoinAndSelect('b.blocked', 'y')
.where('b.blocker = :blocker', { blocker })
.getMany();
}

findByBlockerAndBlocked(blocker: string, blocked: string) {
return this.blockRepository
.createQueryBuilder('b')
.leftJoinAndSelect('b.blocker', 'x')
.leftJoinAndSelect('b.blocked', 'y')
.where('b.blocker = :blocker AND b.blocked = :blocked', {
blocker,
blocked,
})
.getOne();
}
}
16 changes: 16 additions & 0 deletions BE/src/users/entities/block.entity.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { Entity, PrimaryGeneratedColumn, ManyToOne, JoinColumn } from 'typeorm';
import { User } from './user.entity';

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

@ManyToOne(() => User, (user) => user.blockers, { nullable: false })
@JoinColumn({ name: 'blocker', referencedColumnName: 'id' })
blocker: User;

@ManyToOne(() => User, (user) => user.blockeds, { nullable: false })
@JoinColumn({ name: 'blocked', referencedColumnName: 'id' })
blocked: User;
}
7 changes: 7 additions & 0 deletions BE/src/users/entities/user.entity.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { Liked } from 'src/postings/entities/liked.entity';
import { Posting } from 'src/postings/entities/posting.entity';
import { Report } from 'src/postings/entities/report.entity';
import { Block } from './block.entity';
import {
Entity,
Column,
Expand Down Expand Up @@ -49,4 +50,10 @@ export class User {
@ManyToOne(() => SocialLogin, (socialLogin) => socialLogin.users)
@JoinColumn({ name: 'social_type', referencedColumnName: 'id' })
socials: SocialLogin;

@OneToMany(() => Block, (block) => block.blocker)
blockers: Block[];

@OneToMany(() => Block, (block) => block.blocked)
blockeds: Block[];
}
1 change: 1 addition & 0 deletions BE/src/users/users.constants.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
export const DATA_SOURCE = 'DATA_SOURCE';
export const USERS_REPOSITORY = 'USERS_REPOSITORY';
export const BLOCK_REPOSITORY = 'BLOCK_REPOSITORY';
13 changes: 13 additions & 0 deletions BE/src/users/users.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@ import {
UploadedFile,
UseGuards,
Req,
Post,
Param,
ParseUUIDPipe,
} from '@nestjs/common';
import { UsersService } from './users.service';
import {
Expand Down Expand Up @@ -110,4 +113,14 @@ export class UsersController {
checkDuplicatedName(@Query('name') name: string) {
return this.usersService.checkDuplicatedName(name);
}

@Post(':id/block')
@ApiOperation({
summary: '특정 회원 차단',
description: '현재 유저가 id에 해당하는 회원을 차단합니다.',
})
blockUser(@Req() request, @Param('id', ParseUUIDPipe) id: string) {
const userId = request['user'].id;
return this.usersService.blockUser(userId, id);
}
}
5 changes: 3 additions & 2 deletions BE/src/users/users.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,12 @@ import { usersProvider } from './users.providers';
import { UserRepository } from './users.repository';
import { StorageModule } from '../storage/storage.module';
import { DatabaseModule } from '../database/database.module';
import { BlockRepository } from './block.repository';

@Module({
imports: [DatabaseModule, StorageModule],
controllers: [UsersController],
providers: [UsersService, ...usersProvider, UserRepository],
exports: [UserRepository, UsersService],
providers: [UsersService, ...usersProvider, UserRepository, BlockRepository],
exports: [UserRepository, UsersService, BlockRepository],
})
export class UsersModule {}
12 changes: 11 additions & 1 deletion BE/src/users/users.providers.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,21 @@
import { Block } from './entities/block.entity';
import { User } from './entities/user.entity';
import { DataSource } from 'typeorm';
import { DATA_SOURCE, USERS_REPOSITORY } from './users.constants';
import {
BLOCK_REPOSITORY,
DATA_SOURCE,
USERS_REPOSITORY,
} from './users.constants';

export const usersProvider = [
{
provide: USERS_REPOSITORY,
useFactory: (dataSource: DataSource) => dataSource.getRepository(User),
inject: [DATA_SOURCE],
},
{
provide: BLOCK_REPOSITORY,
useFactory: (dataSource: DataSource) => dataSource.getRepository(Block),
inject: [DATA_SOURCE],
},
];
24 changes: 24 additions & 0 deletions BE/src/users/users.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,13 @@ import { UserRepository } from './users.repository';
import { CheckDuplicatedNameResponseDto } from './dto/check-duplicated-name-response.dto';
import { UpdateUserDto } from './dto/update-user.dto';
import { UpdateUserIpDto } from './dto/update-user-ip.dto';
import { BlockRepository } from './block.repository';

@Injectable()
export class UsersService {
constructor(
private userRepository: UserRepository,
private blockRepository: BlockRepository,
private readonly storageService: StorageService
) {}

Expand Down Expand Up @@ -113,4 +115,26 @@ export class UsersService {
async updateUserIp(id: string, updateUserIpDto: UpdateUserIpDto) {
return this.userRepository.update(id, updateUserIpDto);
}

async blockUser(blocker: string, blocked: string) {
if (blocker === blocked) {
throw new BadRequestException('자기 자신을 차단할 수 없습니다.');
}

const blockedUser = await this.userRepository.findById(blocked);
if (!blockedUser) {
throw new BadRequestException('존재하지 않는 회원을 차단할 수 없습니다.');
}

const block = await this.blockRepository.findByBlockerAndBlocked(
blocker,
blocked
);
if (block) {
throw new BadRequestException('이미 차단한 회원입니다.');
}

const blockerUser = await this.userRepository.findById(blocker);
return this.blockRepository.save(blockerUser, blockedUser);
}
}