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") 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/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", 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); 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..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 = { @@ -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:"해당 작가 팔로우를 성공했습니다.", @@ -354,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, @@ -375,7 +382,8 @@ export const UserService = { workingTime: workingTime, writer: { nickname: r.user.nickname - } + }, + reviewImage: images }}); // 작가가 등록한 커미션 목록