Skip to content

Commit

Permalink
Merge pull request #136 from serafuku/yunochi/develop
Browse files Browse the repository at this point in the history
✨ 익명 질문자 차단기능
  • Loading branch information
yunochi authored Dec 26, 2024
2 parents 82cd0e7 + 1f75821 commit e4c7821
Show file tree
Hide file tree
Showing 11 changed files with 164 additions and 48 deletions.
6 changes: 3 additions & 3 deletions prisma/schema.prisma
Original file line number Diff line number Diff line change
Expand Up @@ -99,16 +99,16 @@ model server {

model blocking {
id String @id @default(cuid())
blockeeHandle String @db.VarChar(500)
blockeeTarget String @db.VarChar(500) @map("blockeeHandle") /// Handle or ipHash
blockerHandle String @db.VarChar(500)
blocker user @relation("blocks", fields: [blockerHandle], references: [handle], onDelete: Cascade)
createdAt DateTime @default(now())
hidden Boolean @default(false)
imported Boolean @default(false)
@@unique([blockeeHandle, blockerHandle, hidden, imported])
@@unique([blockeeTarget, blockerHandle, hidden, imported])
@@index([blockerHandle, hidden])
@@index([blockeeHandle, hidden])
@@index([blockeeTarget, hidden])
@@index([imported])
}

Expand Down
11 changes: 11 additions & 0 deletions src/app/_components/question.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ interface askProps {
setId: React.Dispatch<React.SetStateAction<number>>;
deleteRef: RefObject<HTMLDialogElement>;
answerRef: RefObject<HTMLDialogElement>;
blockingRef: RefObject<HTMLDialogElement>;
setIsLoading: React.Dispatch<React.SetStateAction<boolean>>;
defaultVisibility: 'public' | 'home' | 'followers' | undefined;
}
Expand All @@ -31,6 +32,7 @@ export default function Question({
setId,
deleteRef,
answerRef,
blockingRef,
setIsLoading,
defaultVisibility,
}: askProps) {
Expand Down Expand Up @@ -172,6 +174,15 @@ export default function Question({
>
삭제
</span>
<span
className="text-red-800 font-bold ml-2 cursor-pointer"
onClick={() => {
setId(singleQuestion.id);
blockingRef.current?.showModal();
}}
>
질문자 차단
</span>
</div>
</div>
<div className="flex justify-end px-2 text-2xl chat chat-end">
Expand Down
12 changes: 11 additions & 1 deletion src/app/_dto/blocking/blocking.dto.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,15 @@
import { blocking, question } from '@prisma/client';
import { IsBoolean, IsEnum, IsInt, IsOptional, IsString, Max, MaxLength, Min } from 'class-validator';

export class CreateBlockDto {
@IsString()
@MaxLength(500)
targetHandle: string;
}

export class createBlockByQuestionDto {
@IsInt()
questionId: question['id'];
}
export class Block {
id: string;
targetHandle: string;
Expand Down Expand Up @@ -53,3 +57,9 @@ export class DeleteBlockDto {
@MaxLength(500)
targetHandle: string;
}

export class DeleteBlockByIdDto {
@IsString()
@MaxLength(200)
targetId: blocking['id'];
}
6 changes: 3 additions & 3 deletions src/app/api/_service/answer/answer-service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -388,7 +388,7 @@ export class AnswerService {
const existList = [];
for (const block of all_blockList) {
const exist = await prisma.user.findUnique({
where: { handle: block.blockeeHandle },
where: { handle: block.blockeeTarget },
});
if (exist) {
existList.push(block);
Expand All @@ -397,9 +397,9 @@ export class AnswerService {
return existList;
};
const blockList = await kv.get(getBlockListOnlyExist, { key: `block-${myHandle}`, ttl: 600 });
const blockedList = await prisma.blocking.findMany({ where: { blockeeHandle: myHandle, hidden: false } });
const blockedList = await prisma.blocking.findMany({ where: { blockeeTarget: myHandle, hidden: false } });
const filteredAnswers = answers.filter((ans) => {
if (blockList.find((b) => b.blockeeHandle === ans.answeredPersonHandle || b.blockeeHandle === ans.questioner)) {
if (blockList.find((b) => b.blockeeTarget === ans.answeredPersonHandle || b.blockeeTarget === ans.questioner)) {
return false;
}
if (blockedList.find((b) => b.blockerHandle === ans.answeredPersonHandle || b.blockerHandle === ans.questioner)) {
Expand Down
105 changes: 81 additions & 24 deletions src/app/api/_service/blocking/blocking-service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,9 @@ import { GetPrismaClient } from '@/api/_utils/getPrismaClient/get-prisma-client'
import { validateStrict } from '@/utils/validator/strictValidator';
import {
Block,
createBlockByQuestionDto,
CreateBlockDto,
DeleteBlockByIdDto,
DeleteBlockDto,
GetBlockListReqDto,
SearchBlockListReqDto,
Expand Down Expand Up @@ -56,14 +58,47 @@ export class BlockingService {
return sendApiError(400, 'Bad Request. User not found');
}
try {
await this.createBlock(targetUser.handle, user.handle, false);
const b = await this.createBlock(targetUser.handle, user.handle, false);
this.logger.debug(`New Block created, hidden: ${b.hidden}, target: ${b.blockeeTarget}`);
} catch (err) {
return sendApiError(500, JSON.stringify(err));
}

return NextResponse.json({}, { status: 200 });
}

@Auth()
@RateLimit({ bucket_time: 300, req_limit: 60 }, 'user')
public async createBlockByQuestionApi(
req: NextRequest,
@JwtPayload tokenBody?: jwtPayloadType,
): Promise<NextResponse> {
let data;
try {
data = await validateStrict(createBlockByQuestionDto, await req.json());
} catch (err) {
return sendApiError(400, `Bad request ${String(err)}`);
}
try {
const q = await this.prisma.question.findUnique({ where: { id: data.questionId } });
if (!q) {
return sendApiError(400, 'questionId not found!');
}
if (q.questioneeHandle !== tokenBody?.handle) {
return sendApiError(403, 'Not your question!');
}
if (q.questioner) {
const b = await this.createBlock(q.questioner, tokenBody.handle, false, q.isAnonymous);
this.logger.debug(`New Block created by Question ${q.id}, hidden: ${b.hidden}, target: ${b.blockeeTarget}`);
return NextResponse.json(`OK. block created!`, { status: 201 });
} else {
return NextResponse.json(`Block not created! (questioner is null)`, { status: 200 });
}
} catch (err) {
return sendApiError(500, 'ERROR!' + String(err));
}
}

@Auth()
@RateLimit({ bucket_time: 300, req_limit: 150 }, 'user')
public async getBlockList(req: NextRequest, @JwtPayload tokenBody?: jwtPayloadType) {
Expand All @@ -86,7 +121,6 @@ export class BlockingService {
...(data.sinceId ? { gt: data.sinceId } : {}),
...(data.untilId ? { lt: data.untilId } : {}),
},
hidden: false,
},
orderBy: { id: orderBy },
take: data.limit ?? 10,
Expand All @@ -95,7 +129,7 @@ export class BlockingService {
const return_data = blockList.map((v) => {
const d: Block = {
id: v.id,
targetHandle: v.blockeeHandle,
targetHandle: v.hidden ? `익명의 질문자 ${v.id}` : v.blockeeTarget,
blockedAt: v.createdAt,
};
return d;
Expand Down Expand Up @@ -125,7 +159,7 @@ export class BlockingService {
const r = await this.prisma.blocking.findMany({
where: {
blockerHandle: tokenBody.handle,
blockeeHandle: data.targetHandle,
blockeeTarget: data.targetHandle,
hidden: false,
},
});
Expand All @@ -139,23 +173,44 @@ export class BlockingService {
public async deleteBlock(req: NextRequest, @JwtPayload tokenBody?: jwtPayloadType) {
let data;
try {
data = await validateStrict(DeleteBlockDto, await req.json());
} catch {
return sendApiError(400, 'Bad Request');
const reqJson = await req.json();
if (reqJson.targetId) {
data = await validateStrict(DeleteBlockByIdDto, reqJson);
} else {
data = await validateStrict(DeleteBlockDto, reqJson);
}
} catch (err) {
return sendApiError(400, `Bad Request ${String(err)}`);
}

const user = await this.prisma.user.findUniqueOrThrow({ where: { handle: tokenBody!.handle } });

try {
const r = await this.prisma.blocking.deleteMany({
where: {
blockeeHandle: data.targetHandle,
blockerHandle: user.handle,
hidden: false,
},
});
this.logger.debug(`${r.count} block deleted`);
const deleteById = (data as DeleteBlockByIdDto).targetId ? (data as DeleteBlockByIdDto) : null;
const deleteByHandle = (data as DeleteBlockDto).targetHandle ? (data as DeleteBlockDto) : null;
if (deleteById) {
const r = await this.prisma.blocking.deleteMany({
where: {
id: deleteById.targetId,
blockerHandle: user.handle,
},
});
this.logger.debug(`${r.count} block deleted (by id ${deleteById.targetId})`);
return NextResponse.json({ message: `${r.count} block deleted (by id ${deleteById.targetId})` });
}
if (deleteByHandle) {
const r = await this.prisma.blocking.deleteMany({
where: {
blockeeTarget: deleteByHandle.targetHandle,
blockerHandle: user.handle,
hidden: false,
},
});
this.logger.debug(`${r.count} block deleted`);
return NextResponse.json({ message: `${r.count} block deleted` });
}
} catch {
return sendApiError(400, '이미 차단 해제된 사용자입니다!');
return sendApiError(500, '차단 해제 오류');
}

await this.redisKvService.drop(`block-${user.handle}`);
Expand All @@ -181,23 +236,23 @@ export class BlockingService {

/**
* 블락을 생성하고, 미답변 질문중 블락대상의 것은 삭제
* @param blockeeHandle 블락될 대상의 핸들
* @param blockeeTarget 블락될 대상의 핸들이나 ipHash
* @param myHandle 내 핸들
* @param imported ImportBlock에 의해서 가져온 Block인 경우 true
* @param isHidden 익명질문의 유저를 차단하는 경우 true (구현 예정)
* @param isHidden 익명질문의 유저를 차단하는 경우 true
*/
public async createBlock(blockeeHandle: string, myHandle: string, imported?: boolean, isHidden?: boolean) {
public async createBlock(blockeeTarget: string, myHandle: string, imported?: boolean, isHidden?: boolean) {
const dbData = {
blockeeHandle: blockeeHandle,
blockeeTarget: blockeeTarget,
blockerHandle: myHandle,
hidden: isHidden ? true : false,
imported: imported ? true : false,
createdAt: new Date(Date.now()),
};
await this.prisma.blocking.upsert({
const b = await this.prisma.blocking.upsert({
where: {
blockeeHandle_blockerHandle_hidden_imported: {
blockeeHandle: dbData.blockeeHandle,
blockeeTarget_blockerHandle_hidden_imported: {
blockeeTarget: dbData.blockeeTarget,
blockerHandle: dbData.blockerHandle,
hidden: dbData.hidden,
imported: dbData.imported,
Expand All @@ -209,7 +264,7 @@ export class BlockingService {

//기존 질문의 필터링
const question_list = await this.prisma.question.findMany({ where: { questioneeHandle: myHandle } });
const remove_list = question_list.filter((q) => q.questioner === blockeeHandle);
const remove_list = question_list.filter((q) => q.questioner === blockeeTarget);
remove_list.forEach(async (r) => {
await this.prisma.question.delete({ where: { id: r.id } });
const ev_data: QuestionDeletedPayload = {
Expand All @@ -220,5 +275,7 @@ export class BlockingService {
await this.redisPubsubService.pub<QuestionDeletedPayload>('question-deleted-event', ev_data);
});
await this.redisKvService.drop(`block-${myHandle}`);

return b;
}
}
14 changes: 7 additions & 7 deletions src/app/api/_service/question/question-service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -81,13 +81,13 @@ export class QuestionService {
return sendApiError(403, 'User stops NewQuestion');
}
// 블락 여부 검사
if (tokenPayload?.handle) {
const blocked = await this.prisma.blocking.findFirst({
where: { blockeeHandle: tokenPayload.handle, blockerHandle: questionee_user.handle },
});
if (blocked) {
return sendApiError(403, '이 사용자에게 질문을 보낼 수 없습니다!');
}

const blockeeTarget = tokenPayload?.handle ?? getIpHash(getIpFromRequest(req));
const blocked = await this.prisma.blocking.findFirst({
where: { blockeeTarget: blockeeTarget, blockerHandle: questionee_user.handle },
});
if (blocked) {
return sendApiError(403, '이 사용자에게 질문을 보낼 수 없습니다!');
}

if (!data.isAnonymous && !tokenPayload?.handle) {
Expand Down
6 changes: 3 additions & 3 deletions src/app/api/_service/websocket/websocket-service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -275,7 +275,7 @@ export class WebsocketService {
const existList = [];
for (const block of all_blockList) {
const exist = await this.prisma.user.findUnique({
where: { handle: block.blockeeHandle },
where: { handle: block.blockeeTarget },
});
if (exist) {
existList.push(block);
Expand All @@ -287,9 +287,9 @@ export class WebsocketService {
key: `block-${target_handle}`,
ttl: 600,
});
const blockedList = await this.prisma.blocking.findMany({ where: { blockeeHandle: target_handle, hidden: false } });
const blockedList = await this.prisma.blocking.findMany({ where: { blockeeTarget: target_handle, hidden: false } });
const filteredClients = client_list.filter((c) => {
if (blockList.find((b) => b.blockeeHandle === c.user_handle)) {
if (blockList.find((b) => b.blockeeTarget === c.user_handle)) {
return false;
}
if (blockedList.find((b) => b.blockerHandle === c.user_handle)) {
Expand Down
7 changes: 7 additions & 0 deletions src/app/api/user/blocking/create-by-question/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import { NextRequest } from 'next/server';
import { BlockingService } from '@/app/api/_service/blocking/blocking-service';

export async function POST(req: NextRequest) {
const service = BlockingService.get();
return await service.createBlockByQuestionApi(req);
}
Loading

0 comments on commit e4c7821

Please sign in to comment.