From 6e65247fa974b5455d1bc7f20e6d3421f5a00161 Mon Sep 17 00:00:00 2001 From: taeyoung0524 Date: Sun, 10 Aug 2025 23:16:44 +0900 Subject: [PATCH 1/6] =?UTF-8?q?REFACTOR=20:=20=EC=86=8C=EA=B0=9C=20default?= =?UTF-8?q?=20=EA=B0=92=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../migrations/20250810141539_description_default/migration.sql | 2 ++ prisma/schema.prisma | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) create mode 100644 prisma/migrations/20250810141539_description_default/migration.sql diff --git a/prisma/migrations/20250810141539_description_default/migration.sql b/prisma/migrations/20250810141539_description_default/migration.sql new file mode 100644 index 0000000..29a28d3 --- /dev/null +++ b/prisma/migrations/20250810141539_description_default/migration.sql @@ -0,0 +1,2 @@ +-- AlterTable +ALTER TABLE `users` MODIFY `description` VARCHAR(1000) NULL DEFAULT '입력된 소개가 없습니다.'; diff --git a/prisma/schema.prisma b/prisma/schema.prisma index 8936d7c..11b30a1 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -35,7 +35,7 @@ model User { id BigInt @id @default(autoincrement()) nickname String @db.VarChar(50) accountId BigInt @map("account_id") @unique - description String? @db.VarChar(1000) + description String? @db.VarChar(1000) @default("입력된 소개가 없습니다.") profileImage String? @map("profile_image") @db.VarChar(255) createdAt DateTime @default(now()) @map("created_at") updatedAt DateTime @updatedAt @map("updated_at") From 50e1c47de057f6894bcfd96e566aedf3b74ad41e Mon Sep 17 00:00:00 2001 From: taeyoung0524 Date: Mon, 11 Aug 2025 01:03:26 +0900 Subject: [PATCH 2/6] =?UTF-8?q?FEAT=20:=20=EB=B1=83=EC=A7=80=20repository?= =?UTF-8?q?=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/commission.controller.js | 5 + src/request/controller/request.controller.js | 1 + src/user/repository/badge.repository.js | 246 ++++++++++++++++++ src/user/service/user.service.js | 4 +- 4 files changed, 255 insertions(+), 1 deletion(-) diff --git a/src/commission/controller/commission.controller.js b/src/commission/controller/commission.controller.js index ea88fc3..b9ea487 100644 --- a/src/commission/controller/commission.controller.js +++ b/src/commission/controller/commission.controller.js @@ -7,6 +7,7 @@ import SubmitCommissionRequestDto } from "../dto/commission.dto.js"; import { parseWithBigInt, stringifyWithBigInt } from "../../bigintJson.js"; +import { BadgeRepository } from "../../user/repository/badge.repository.js"; // 커미션 게시글 상세글 조회 export const getCommissionDetail = async (req, res, next) => { @@ -87,6 +88,8 @@ export const uploadRequestImage = async (req, res, next) => { export const submitCommissionRequest = async (req, res, next) => { try { const userId = req.user.userId; + const accountId = req.user.accountId; + const dto = new SubmitCommissionRequestDto({ commissionId: BigInt(req.params.commissionId), formAnswer: req.body.formAnswer @@ -95,6 +98,8 @@ export const submitCommissionRequest = async (req, res, next) => { const result = await CommissionService.submitCommissionRequest(userId, dto); const responseData = parseWithBigInt(stringifyWithBigInt(result)); + await BadgeRepository.GiveCommissionApplyBadges(userId, accountId); + res.status(StatusCodes.OK).success(responseData); } catch (err) { next(err); diff --git a/src/request/controller/request.controller.js b/src/request/controller/request.controller.js index d079408..9cfe2e9 100644 --- a/src/request/controller/request.controller.js +++ b/src/request/controller/request.controller.js @@ -9,6 +9,7 @@ import { GetRequestResultDto } from "../dto/request.dto.js"; import { parseWithBigInt, stringifyWithBigInt } from "../../bigintJson.js"; +import { BadgeRepository } from "../../user/repository/badge.repository.js"; // 신청 목록 조회 export const getRequestList = async (req, res, next) => { diff --git a/src/user/repository/badge.repository.js b/src/user/repository/badge.repository.js index 31bdf1e..5d3cf8e 100644 --- a/src/user/repository/badge.repository.js +++ b/src/user/repository/badge.repository.js @@ -58,6 +58,252 @@ export const BadgeRepository = { } }); } + , + // 팔로워 뱃지 부여하기 + async GiveFollowerBadges(artistId, accountId){ + const followerCount = await prisma.follow.count({ + where:{artistId} + }) + + if(followerCount >= 5){ + const existingBadge = await prisma.userBadge.findFirst({ + where:{ + accountId, + badgeId:1 + } + }); + + if(!existingBadge){ + await prisma.userBadge.create({ + data:{ + accountId, + badgeId:1, + earnedAt:new Date(), + } + }) + } + } + + if(followerCount >= 10){ + const existingBadge = await prisma.userBadge.findFirst({ + where:{ + accountId, + badgeId:2 + } + }); + + if(!existingBadge){ + await prisma.userBadge.create({ + data:{ + accountId, + badgeId:2, + earnedAt:new Date(), + } + }) + } + } + if(followerCount >= 20){ + const existingBadge = await prisma.userBadge.findFirst({ + where:{ + accountId, + badgeId:3 + } + }); + + if(!existingBadge){ + await prisma.userBadge.create({ + data:{ + accountId, + badgeId:3, + earnedAt:new Date(), + } + }) + } + } + if(followerCount >= 100){ + const existingBadge = await prisma.userBadge.findFirst({ + where:{ + accountId, + badgeId:4 + } + }); + + if(!existingBadge){ + await prisma.userBadge.create({ + data:{ + accountId, + badgeId:4, + earnedAt:new Date(), + } + }) + } + } + }, + // 커미션 신청 뱃지 부여 + async GiveCommissionApplyBadges(userId, accountId){ + const ApplyCommissionCount = await prisma.request.count({ + where:{ + userId + } + }) + + if(ApplyCommissionCount >= 1){ + const existingBadge = await prisma.userBadge.findFirst({ + where:{ + accountId, + badgeId:5 + } + }); + + if(!existingBadge){ + await prisma.userBadge.create({ + data:{ + accountId, + badgeId:5, + earnedAt:new Date(), + } + }) + } + } + + if(ApplyCommissionCount >= 5){ + const existingBadge = await prisma.userBadge.findFirst({ + where:{ + accountId, + badgeId:6 + } + }); + + if(!existingBadge){ + await prisma.userBadge.create({ + data:{ + accountId, + badgeId:6, + earnedAt:new Date(), + } + }) + } + } + if(ApplyCommissionCount >= 15){ + const existingBadge = await prisma.userBadge.findFirst({ + where:{ + accountId, + badgeId:7 + } + }); + + if(!existingBadge){ + await prisma.userBadge.create({ + data:{ + accountId, + badgeId:7, + earnedAt:new Date(), + } + }) + } + } + if(ApplyCommissionCount >= 50){ + const existingBadge = await prisma.userBadge.findFirst({ + where:{ + accountId, + badgeId:8 + } + }); + + if(!existingBadge){ + await prisma.userBadge.create({ + data:{ + accountId, + badgeId:8, + earnedAt:new Date(), + } + }) + } + } + }, + // 후기 작성 뱃지 부여 + async GiveReviewBadges(userId, accountId){ + const ReviewCount = await prisma.review.count({ + where:{ + userId + } + }) + + if(ReviewCount >= 1){ + const existingBadge = await prisma.userBadge.findFirst({ + where:{ + accountId, + badgeId:9 + } + }); + + if(!existingBadge){ + await prisma.userBadge.create({ + data:{ + accountId, + badgeId:9, + earnedAt:new Date(), + } + }) + } + } + + if(ReviewCount >= 5){ + const existingBadge = await prisma.userBadge.findFirst({ + where:{ + accountId, + badgeId:10 + } + }); + + if(!existingBadge){ + await prisma.userBadge.create({ + data:{ + accountId, + badgeId:10, + earnedAt:new Date(), + } + }) + } + } + if(ReviewCount >= 15){ + const existingBadge = await prisma.userBadge.findFirst({ + where:{ + accountId, + badgeId:11 + } + }); + + if(!existingBadge){ + await prisma.userBadge.create({ + data:{ + accountId, + badgeId:11, + earnedAt:new Date(), + } + }) + } + } + if(ReviewCount >= 50){ + const existingBadge = await prisma.userBadge.findFirst({ + where:{ + accountId, + badgeId:12 + } + }); + + if(!existingBadge){ + await prisma.userBadge.create({ + data:{ + accountId, + badgeId:12, + earnedAt:new Date(), + } + }) + } + } + } + }; diff --git a/src/user/service/user.service.js b/src/user/service/user.service.js index 85f2c3c..b873024 100644 --- a/src/user/service/user.service.js +++ b/src/user/service/user.service.js @@ -128,7 +128,7 @@ export const UserService = { earnedAt: userBadge.earnedAt, badge: userBadge.badge })); - + const reviews = (await UserRepository.UserReviewList(userId)) ?? []; @@ -276,6 +276,8 @@ export const UserService = { throw new UserAlreadyFollowArtist(); const result = await UserRepository.FollowArtist(accountId, artistId); + + await BadgeRepository.GiveFollowerBadges(artistId, accountId); return { message:"해당 작가 팔로우를 성공했습니다.", From 43d91830baaa330ce18ee6584303e8db089f6551 Mon Sep 17 00:00:00 2001 From: taeyoung0524 Date: Mon, 11 Aug 2025 23:54:24 +0900 Subject: [PATCH 3/6] =?UTF-8?q?REFACTOR=20:=20review=20=EB=B0=98=ED=99=98?= =?UTF-8?q?=EC=97=90=20image=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/user/service/user.service.js | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/src/user/service/user.service.js b/src/user/service/user.service.js index b873024..af82a59 100644 --- a/src/user/service/user.service.js +++ b/src/user/service/user.service.js @@ -4,7 +4,7 @@ import axios from "axios"; import { signJwt } from "../../jwt.config.js"; import { BadgeRepository } from "../repository/badge.repository.js"; import { CommissionRepository } from "../../commission/repository/commission.repository.js"; - +import { ReviewRepository } from "../../../src/request/repository/request.repository.js" export const UserService = { @@ -356,18 +356,23 @@ export const UserService = { throw new ArtistNotFound(); const rawReviews = await UserRepository.ArtistReviews(artistId); + - const reviews = rawReviews.map((r) => { + const reviews = rawReviews.map(async (r) => { + const start = r.request.inProgressAt ? new Date(r.request.inProgressAt) : null; const end = r.request.completedAt ? new Date(r.request.completedAt) : null; + let workingTime = null; if (start && end) { const diffMs = end - start; const hours = Math.floor(diffMs / (1000 * 60 * 60)); workingTime = hours < 24 ? `${hours}시간` : `${Math.floor(hours / 24)}일`; } - + + const images = await ReviewRepository.getImagesByTarget('review', r.id); + return { id: r.id, rate: r.rate, @@ -377,7 +382,8 @@ export const UserService = { workingTime: workingTime, writer: { nickname: r.user.nickname - } + }, + reviewImage: images }}); // 작가가 등록한 커미션 목록 From f0889954d6a3d02a2ad5cabbeaacb9fc5d825d5f Mon Sep 17 00:00:00 2001 From: taeyoung0524 Date: Tue, 12 Aug 2025 00:13:35 +0900 Subject: [PATCH 4/6] =?UTF-8?q?FIX=20:=20=EC=82=AC=EC=9A=A9=ED=95=98?= =?UTF-8?q?=EC=A7=80=20=EC=95=8A=EB=8A=94=20=EC=A4=84=20=EC=82=AD=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/request/controller/request.controller.js | 1 - 1 file changed, 1 deletion(-) diff --git a/src/request/controller/request.controller.js b/src/request/controller/request.controller.js index 9cfe2e9..d079408 100644 --- a/src/request/controller/request.controller.js +++ b/src/request/controller/request.controller.js @@ -9,7 +9,6 @@ import { GetRequestResultDto } from "../dto/request.dto.js"; import { parseWithBigInt, stringifyWithBigInt } from "../../bigintJson.js"; -import { BadgeRepository } from "../../user/repository/badge.repository.js"; // 신청 목록 조회 export const getRequestList = async (req, res, next) => { From a19eeb9259caacb23f91742058f2878c23edd3cc Mon Sep 17 00:00:00 2001 From: taeyoung0524 Date: Tue, 12 Aug 2025 00:16:09 +0900 Subject: [PATCH 5/6] =?UTF-8?q?REFACTOR=20:=20=EC=9E=91=EA=B0=80=20?= =?UTF-8?q?=ED=8C=94=EB=A1=9C=EC=9A=B0=ED=95=98=EA=B8=B0=EC=97=90=20?= =?UTF-8?q?=EB=B1=83=EC=A7=80=20=EB=B6=80=EC=97=AC=20repository=20?= =?UTF-8?q?=EC=97=B0=EA=B2=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/user/controller/user.controller.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/user/controller/user.controller.js b/src/user/controller/user.controller.js index b2ebad5..017c924 100644 --- a/src/user/controller/user.controller.js +++ b/src/user/controller/user.controller.js @@ -4,6 +4,7 @@ import { UserSignupRequestDto } from "../dto/user.dto.js"; import { UserLoginRequestDto } from "../dto/user.dto.js"; import { UpdateMyprofileDto } from "../dto/user.dto.js"; import { verifyJwt } from "../../jwt.config.js"; +import { BadgeRepository } from "../repository/badge.repository.js"; // 로그인 요청 @@ -122,6 +123,8 @@ export const FollowArtist = async(req, res, next) => { const result = await UserService.FollowArtist(accountId, artistId); + await BadgeRepository.GiveFollowerBadges(artistId, accountId); + res.status(StatusCodes.CREATED).success(result); } catch(err) { next(err); From a38887ea18a0c4f884482e7a9c7d797665672f67 Mon Sep 17 00:00:00 2001 From: taeyoung0524 Date: Tue, 12 Aug 2025 00:19:36 +0900 Subject: [PATCH 6/6] =?UTF-8?q?REFACTOR=20:=20=EB=A6=AC=EB=B7=B0=20?= =?UTF-8?q?=EC=9E=91=EC=84=B1=EC=97=90=20BadgeRepository=20=EC=97=B0?= =?UTF-8?q?=EA=B2=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/review/controller/review.controller.js | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/review/controller/review.controller.js b/src/review/controller/review.controller.js index 93e4376..4d51f46 100644 --- a/src/review/controller/review.controller.js +++ b/src/review/controller/review.controller.js @@ -14,6 +14,7 @@ import { ReviewUpdateDto } from '../dto/review.dto.js'; // DTO 클래스 import import reviewService from '../service/review.service.js'; +import { BadgeRepository } from "../../user/repository/badge.repository.js"; class ReviewController { @@ -60,6 +61,9 @@ class ReviewController { // 현재 로그인한 사용자 ID (BigInt 변환) const userId = BigInt(req.user.userId); + // accountId 조회 + const accountId = BigInt(req.user.accountId); + // 요청 본문 데이터를 DTO 클래스로 구조화 const reviewDto = new ReviewCreateDto(req.body); @@ -71,6 +75,9 @@ class ReviewController { // BigInt를 JSON 문자열로 변환 후 다시 파싱하여 일반 객체로 변환 const finalData = JSON.parse(stringifyWithBigInt(responseData)); + // 리뷰 뱃지 발급 + await BadgeRepository.GiveReviewBadges(userId, accountId); + // 클라이언트에 응답 전송 (201 Created) res.status(StatusCodes.CREATED).json({ resultType: "SUCCESS",