Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2,417 changes: 1,699 additions & 718 deletions openapi/soundlog-api.yaml

Large diffs are not rendered by default.

111 changes: 111 additions & 0 deletions prisma/migrations/20260707000000_community_features/migration.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
CREATE TABLE "TravelRoom" (
"id" TEXT NOT NULL,
"ownerId" TEXT NOT NULL,
"title" TEXT NOT NULL,
"inviteCode" TEXT NOT NULL,
"sessionId" TEXT,
"visibility" TEXT NOT NULL DEFAULT 'invite_only',
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,

CONSTRAINT "TravelRoom_pkey" PRIMARY KEY ("id")
);

CREATE TABLE "TravelRoomMember" (
"id" TEXT NOT NULL,
"roomId" TEXT NOT NULL,
"userId" TEXT NOT NULL,
"role" TEXT NOT NULL,
"displayName" TEXT,
"joinedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,

CONSTRAINT "TravelRoomMember_pkey" PRIMARY KEY ("id")
);

CREATE TABLE "TravelRoomMoment" (
"id" TEXT NOT NULL,
"roomId" TEXT NOT NULL,
"userId" TEXT NOT NULL,
"momentLogId" TEXT,
"placeName" TEXT,
"note" TEXT,
"status" TEXT NOT NULL DEFAULT 'candidate',
"trackSnapshot" JSONB,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,

CONSTRAINT "TravelRoomMoment_pkey" PRIMARY KEY ("id")
);

CREATE TABLE "SoundMapPin" (
"id" TEXT NOT NULL,
"userId" TEXT NOT NULL,
"sessionId" TEXT,
"visibility" TEXT NOT NULL,
"lat" DOUBLE PRECISION NOT NULL,
"lng" DOUBLE PRECISION NOT NULL,
"approxLat" DOUBLE PRECISION NOT NULL,
"approxLng" DOUBLE PRECISION NOT NULL,
"travelMode" TEXT,
"moodTags" TEXT[],
"placeName" TEXT,
"trackSnapshot" JSONB,
"expiresAt" TIMESTAMP(3) NOT NULL,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,

CONSTRAINT "SoundMapPin_pkey" PRIMARY KEY ("id")
);

CREATE TABLE "TravelMateRequest" (
"id" TEXT NOT NULL,
"requesterId" TEXT NOT NULL,
"targetUserId" TEXT NOT NULL,
"targetPinId" TEXT,
"messageTemplate" TEXT NOT NULL,
"status" TEXT NOT NULL,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,

CONSTRAINT "TravelMateRequest_pkey" PRIMARY KEY ("id")
);

CREATE TABLE "CommunityBlock" (
"id" TEXT NOT NULL,
"blockerId" TEXT NOT NULL,
"blockedUserId" TEXT NOT NULL,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,

CONSTRAINT "CommunityBlock_pkey" PRIMARY KEY ("id")
);

CREATE TABLE "CommunityReport" (
"id" TEXT NOT NULL,
"reporterId" TEXT NOT NULL,
"targetUserId" TEXT,
"targetPinId" TEXT,
"requestId" TEXT,
"reason" TEXT NOT NULL,
"details" TEXT,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,

CONSTRAINT "CommunityReport_pkey" PRIMARY KEY ("id")
);

CREATE UNIQUE INDEX "TravelRoom_inviteCode_key" ON "TravelRoom"("inviteCode");
CREATE UNIQUE INDEX "TravelRoomMember_roomId_userId_key" ON "TravelRoomMember"("roomId", "userId");
CREATE UNIQUE INDEX "SoundMapPin_userId_key" ON "SoundMapPin"("userId");
CREATE UNIQUE INDEX "CommunityBlock_blockerId_blockedUserId_key" ON "CommunityBlock"("blockerId", "blockedUserId");

ALTER TABLE "TravelRoom" ADD CONSTRAINT "TravelRoom_ownerId_fkey" FOREIGN KEY ("ownerId") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE;
ALTER TABLE "TravelRoomMember" ADD CONSTRAINT "TravelRoomMember_roomId_fkey" FOREIGN KEY ("roomId") REFERENCES "TravelRoom"("id") ON DELETE CASCADE ON UPDATE CASCADE;
ALTER TABLE "TravelRoomMember" ADD CONSTRAINT "TravelRoomMember_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE;
ALTER TABLE "TravelRoomMoment" ADD CONSTRAINT "TravelRoomMoment_roomId_fkey" FOREIGN KEY ("roomId") REFERENCES "TravelRoom"("id") ON DELETE CASCADE ON UPDATE CASCADE;
ALTER TABLE "TravelRoomMoment" ADD CONSTRAINT "TravelRoomMoment_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE;
ALTER TABLE "SoundMapPin" ADD CONSTRAINT "SoundMapPin_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE;
ALTER TABLE "TravelMateRequest" ADD CONSTRAINT "TravelMateRequest_requesterId_fkey" FOREIGN KEY ("requesterId") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE;
ALTER TABLE "TravelMateRequest" ADD CONSTRAINT "TravelMateRequest_targetUserId_fkey" FOREIGN KEY ("targetUserId") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE;
ALTER TABLE "CommunityBlock" ADD CONSTRAINT "CommunityBlock_blockerId_fkey" FOREIGN KEY ("blockerId") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE;
ALTER TABLE "CommunityBlock" ADD CONSTRAINT "CommunityBlock_blockedUserId_fkey" FOREIGN KEY ("blockedUserId") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE;
ALTER TABLE "CommunityReport" ADD CONSTRAINT "CommunityReport_reporterId_fkey" FOREIGN KEY ("reporterId") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE;
ALTER TABLE "CommunityReport" ADD CONSTRAINT "CommunityReport_targetUserId_fkey" FOREIGN KEY ("targetUserId") REFERENCES "User"("id") ON DELETE SET NULL ON UPDATE CASCADE;
ALTER TABLE "CommunityReport" ADD CONSTRAINT "CommunityReport_requestId_fkey" FOREIGN KEY ("requestId") REFERENCES "TravelMateRequest"("id") ON DELETE SET NULL ON UPDATE CASCADE;
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
-- CreateTable
CREATE TABLE "TravelRoomMomentComment" (
"id" TEXT NOT NULL,
"momentId" TEXT NOT NULL,
"userId" TEXT NOT NULL,
"body" TEXT NOT NULL,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,

CONSTRAINT "TravelRoomMomentComment_pkey" PRIMARY KEY ("id")
);

-- AddForeignKey
ALTER TABLE "TravelRoomMomentComment" ADD CONSTRAINT "TravelRoomMomentComment_momentId_fkey" FOREIGN KEY ("momentId") REFERENCES "TravelRoomMoment"("id") ON DELETE CASCADE ON UPDATE CASCADE;

-- AddForeignKey
ALTER TABLE "TravelRoomMomentComment" ADD CONSTRAINT "TravelRoomMomentComment_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE;
45 changes: 28 additions & 17 deletions prisma/models/auth.prisma
Original file line number Diff line number Diff line change
@@ -1,21 +1,32 @@
model User {
id String @id @default(cuid())
provider String
providerUserId String
displayName String?
passwordHash String?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
profile UserProfile?
musicPlatform MusicPlatform?
refreshTokens RefreshToken[]
libraryTrackStates LibraryTrackState[]
momentLogs MomentLog[]
recommendationEvents RecommendationEvent[]
recaps Recap[]
travelSessions TravelSession[]
recapShareEvents RecapShareEvent[]
idempotencyRecords IdempotencyRecord[]
id String @id @default(cuid())
provider String
providerUserId String
displayName String?
passwordHash String?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
profile UserProfile?
musicPlatform MusicPlatform?
refreshTokens RefreshToken[]
libraryTrackStates LibraryTrackState[]
momentLogs MomentLog[]
recommendationEvents RecommendationEvent[]
recaps Recap[]
travelSessions TravelSession[]
recapShareEvents RecapShareEvent[]
idempotencyRecords IdempotencyRecord[]
ownedTravelRooms TravelRoom[] @relation("TravelRoomOwner")
travelRoomMembers TravelRoomMember[]
travelRoomMoments TravelRoomMoment[]
travelRoomMomentComments TravelRoomMomentComment[]
soundMapPins SoundMapPin[]
sentMateRequests TravelMateRequest[] @relation("TravelMateRequester")
receivedMateRequests TravelMateRequest[] @relation("TravelMateTarget")
communityBlocksMade CommunityBlock[] @relation("CommunityBlocker")
communityBlocksTaken CommunityBlock[] @relation("CommunityBlocked")
communityReports CommunityReport[] @relation("CommunityReporter")
communityReportsOn CommunityReport[] @relation("CommunityReportedUser")

@@unique([provider, providerUserId])
}
Expand Down
109 changes: 109 additions & 0 deletions prisma/models/community.prisma
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
model TravelRoom {
id String @id
ownerId String
title String
inviteCode String @unique
sessionId String?
visibility String @default("invite_only")
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
owner User @relation("TravelRoomOwner", fields: [ownerId], references: [id], onDelete: Cascade)
members TravelRoomMember[]
moments TravelRoomMoment[]
}

model TravelRoomMember {
id String @id @default(cuid())
roomId String
userId String
role String
displayName String?
joinedAt DateTime @default(now())
room TravelRoom @relation(fields: [roomId], references: [id], onDelete: Cascade)
user User @relation(fields: [userId], references: [id], onDelete: Cascade)

@@unique([roomId, userId])
}

model TravelRoomMoment {
id String @id
roomId String
userId String
momentLogId String?
placeName String?
note String?
status String @default("candidate")
trackSnapshot Json?
createdAt DateTime @default(now())
room TravelRoom @relation(fields: [roomId], references: [id], onDelete: Cascade)
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
comments TravelRoomMomentComment[]
}

model TravelRoomMomentComment {
id String @id @default(cuid())
momentId String
userId String
body String
createdAt DateTime @default(now())
moment TravelRoomMoment @relation(fields: [momentId], references: [id], onDelete: Cascade)
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
}

model SoundMapPin {
id String @id
userId String @unique
sessionId String?
visibility String
lat Float
lng Float
approxLat Float
approxLng Float
travelMode String?
moodTags String[]
placeName String?
trackSnapshot Json?
expiresAt DateTime
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
}

model TravelMateRequest {
id String @id
requesterId String
targetUserId String
targetPinId String?
messageTemplate String
status String
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
requester User @relation("TravelMateRequester", fields: [requesterId], references: [id], onDelete: Cascade)
targetUser User @relation("TravelMateTarget", fields: [targetUserId], references: [id], onDelete: Cascade)
reports CommunityReport[]
}

model CommunityBlock {
id String @id @default(cuid())
blockerId String
blockedUserId String
createdAt DateTime @default(now())
blocker User @relation("CommunityBlocker", fields: [blockerId], references: [id], onDelete: Cascade)
blockedUser User @relation("CommunityBlocked", fields: [blockedUserId], references: [id], onDelete: Cascade)

@@unique([blockerId, blockedUserId])
}

model CommunityReport {
id String @id @default(cuid())
reporterId String
targetUserId String?
targetPinId String?
requestId String?
reason String
details String?
createdAt DateTime @default(now())
reporter User @relation("CommunityReporter", fields: [reporterId], references: [id], onDelete: Cascade)
targetUser User? @relation("CommunityReportedUser", fields: [targetUserId], references: [id], onDelete: SetNull)
mateRequest TravelMateRequest? @relation(fields: [requestId], references: [id], onDelete: SetNull)
}
8 changes: 8 additions & 0 deletions prisma/seed.ts
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,14 @@ export async function seedPublicCatalog() {
}

export async function seedDatabase() {
await prisma.communityReport.deleteMany({});
await prisma.communityBlock.deleteMany({});
await prisma.travelMateRequest.deleteMany({});
await prisma.soundMapPin.deleteMany({});
await prisma.travelRoomMoment.deleteMany({});
await prisma.travelRoomMember.deleteMany({});
await prisma.travelRoom.deleteMany({});

const user = await prisma.user.upsert({
where: {
provider_providerUserId: defaultUser,
Expand Down
12 changes: 12 additions & 0 deletions src/constants/error.constants.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
export const ERROR_CODES = {
BAD_REQUEST: 'BAD_REQUEST',
DATABASE_ERROR: 'DATABASE_ERROR',
FORBIDDEN: 'FORBIDDEN',
INTERNAL_SERVER_ERROR: 'INTERNAL_SERVER_ERROR',
NOT_FOUND: 'NOT_FOUND',
UNAUTHORIZED: 'UNAUTHORIZED',
Expand All @@ -11,6 +12,7 @@ export const ERROR_MESSAGES = {
DATABASE_ERROR: '데이터 처리 중 오류가 발생했습니다.',
ENDED_TRAVEL_SESSION_CANNOT_ACTIVATE: '종료된 여행 세션은 다시 활성화할 수 없습니다.',
FILE_UPLOAD_INVALID: '파일 업로드 요청이 올바르지 않습니다.',
FORBIDDEN: '요청한 작업을 수행할 권한이 없습니다.',
IDEMPOTENCY_IN_PROGRESS: '동일한 요청이 아직 처리 중입니다.',
INTERNAL_SERVER_ERROR: '서버 오류가 발생했습니다.',
INVALID_EMAIL_OR_PASSWORD: '이메일 또는 비밀번호가 올바르지 않습니다.',
Expand All @@ -19,6 +21,7 @@ export const ERROR_MESSAGES = {
INVALID_TOKEN: '유효하지 않은 토큰입니다.',
PHOTO_REQUIRED: 'photo 파일이 필요합니다.',
PLAYLIST_NOT_FOUND: '플레이리스트를 찾을 수 없습니다.',
MOMENT_LOG_NOT_FOUND: 'Moment Log를 찾을 수 없습니다.',
USER_ALREADY_EXISTS: '이미 가입된 이메일입니다.',
RECAP_NOT_FOUND: '리캡을 찾을 수 없습니다.',
RECOMMENDATION_TRACK_NOT_FOUND: '추천 트랙을 찾을 수 없습니다.',
Expand All @@ -27,5 +30,14 @@ export const ERROR_MESSAGES = {
RESOURCE_NOT_FOUND: '요청한 리소스를 찾을 수 없습니다.',
ROUTE_NOT_FOUND: '요청한 API 경로를 찾을 수 없습니다.',
TRACK_NOT_FOUND: '트랙을 찾을 수 없습니다.',
TRAVEL_MATE_REQUEST_ACTION_NOT_ALLOWED: '이 동행 매칭 요청을 처리할 권한이 없습니다.',
TRAVEL_MATE_REQUEST_NOT_PENDING: '대기 중인 동행 매칭 요청만 변경할 수 있습니다.',
TRAVEL_MATE_REQUEST_NOT_FOUND: '동행 매칭 요청을 찾을 수 없습니다.',
TRAVEL_MATE_SELF_REQUEST_NOT_ALLOWED: '자기 자신에게는 동행 매칭 요청을 보낼 수 없습니다.',
TRAVEL_MATE_TARGET_REQUIRED: '동행 매칭 대상이 필요합니다.',
TRAVEL_ROOM_INVITE_CODE_INVALID: '여행방 초대 코드가 올바르지 않습니다.',
TRAVEL_ROOM_MOMENT_NOT_FOUND: '공동 Recap 후보 Moment를 찾을 수 없습니다.',
TRAVEL_ROOM_NOT_FOUND: '여행방을 찾을 수 없습니다.',
TRAVEL_SESSION_ACTIVE_REQUIRED: '여행 모드가 켜진 활성 세션에서만 사용할 수 있습니다.',
TRAVEL_SESSION_NOT_FOUND: '여행 세션을 찾을 수 없습니다.',
} as const;
Loading
Loading