From a2f40dac94de783b8a728717dbb79c4868edfb8d Mon Sep 17 00:00:00 2001 From: Hwigeon Date: Sat, 29 Nov 2025 06:18:32 +0900 Subject: [PATCH] =?UTF-8?q?feat:=20=EC=97=85=EB=8D=B0=EC=9D=B4=ED=8A=B8?= =?UTF-8?q?=EB=90=9C=20API=20=EC=A0=81=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../api/endpoints/voteboard/voteboard.ts | 120 ++++++++++++++++-- apps/web/src/generated/api/models/index.ts | 4 + .../api/models/votePostCreateRequest.ts | 15 ++- .../models/votePostCreateRequestCategory.ts | 23 ++++ .../api/models/votePostDetailResponse.ts | 20 ++- .../models/votePostDetailResponseCategory.ts | 23 ++++ .../api/models/votePostSummaryResponse.ts | 11 +- .../models/votePostSummaryResponseCategory.ts | 23 ++++ .../api/models/votePostUpdateRequest.ts | 11 +- .../models/votePostUpdateRequestCategory.ts | 23 ++++ 10 files changed, 242 insertions(+), 31 deletions(-) create mode 100644 apps/web/src/generated/api/models/votePostCreateRequestCategory.ts create mode 100644 apps/web/src/generated/api/models/votePostDetailResponseCategory.ts create mode 100644 apps/web/src/generated/api/models/votePostSummaryResponseCategory.ts create mode 100644 apps/web/src/generated/api/models/votePostUpdateRequestCategory.ts diff --git a/apps/web/src/generated/api/endpoints/voteboard/voteboard.ts b/apps/web/src/generated/api/endpoints/voteboard/voteboard.ts index 8ae710da..82bb8ce7 100644 --- a/apps/web/src/generated/api/endpoints/voteboard/voteboard.ts +++ b/apps/web/src/generated/api/endpoints/voteboard/voteboard.ts @@ -35,7 +35,28 @@ import type { import { customInstance } from '../../../../lib/api-client'; /** - * 투표 게시글의 상세 정보를 조회합니다. 비로그인 사용자도 조회 가능합니다. + * 투표 게시글의 상세 정보를 조회합니다. + +**특징:** +- 조회 시 조회수 자동 증가 +- 인증/비인증 사용자 모두 조회 가능 +- 인증 여부에 따라 isLiked, canEdit, canDelete 값 변경 +- 투표 참여 여부에 따라 selectedOptionIds 값 변경 + +**인증 사용자:** +- isAuthorized: true +- isLiked: boolean (좋아요 상태) +- canEdit: boolean (수정 권한 - 작성자인 경우 true) +- canDelete: boolean (삭제 권한 - 작성자인 경우 true) +- selectedOptionIds: 투표한 옵션 ID 목록 (투표 전이면 빈 배열) + +**비인증 사용자:** +- isAuthorized: false +- isLiked: null +- canEdit: null +- canDelete: null +- selectedOptionIds: 빈 배열 + * @summary 투표 게시글 상세 조회 */ export const getVotePost = ( @@ -203,17 +224,62 @@ export function useGetVotePost< /** * 투표 게시글을 수정합니다. 투표 옵션은 수정할 수 없습니다. + +**수정 가능 항목:** +- 카테고리 +- 제목, 내용 +- 이미지 (추가/삭제) +- 투표 설정 (투표 시작 전에만 가능) + +**Multipart 업로드** + * @summary 투표 게시글 수정 */ export const updateVotePost = ( votesboardId: number, votePostUpdateRequest: VotePostUpdateRequest, ) => { + const formData = new FormData(); + if (votePostUpdateRequest.category !== undefined) { + formData.append(`category`, votePostUpdateRequest.category); + } + if (votePostUpdateRequest.title !== undefined) { + formData.append(`title`, votePostUpdateRequest.title); + } + if (votePostUpdateRequest.content !== undefined) { + formData.append(`content`, votePostUpdateRequest.content); + } + if (votePostUpdateRequest.images !== undefined) { + votePostUpdateRequest.images.forEach((value) => + formData.append(`images`, value), + ); + } + if (votePostUpdateRequest.deleteImageIds !== undefined) { + votePostUpdateRequest.deleteImageIds.forEach((value) => + formData.append(`deleteImageIds`, value.toString()), + ); + } + if (votePostUpdateRequest.endTime !== undefined) { + formData.append(`endTime`, votePostUpdateRequest.endTime); + } + if (votePostUpdateRequest.allowRevote !== undefined) { + formData.append( + `allowRevote`, + votePostUpdateRequest.allowRevote.toString(), + ); + } + if (votePostUpdateRequest.allowMultipleChoice !== undefined) { + formData.append( + `allowMultipleChoice`, + votePostUpdateRequest.allowMultipleChoice.toString(), + ); + } + return customInstance({ url: `/community/votesboard/${votesboardId}`, method: 'PUT', - headers: { 'Content-Type': 'application/json' }, - data: votePostUpdateRequest, + headers: { 'Content-Type': 'multipart/form-data' }, + data: formData, }); }; @@ -832,14 +898,17 @@ export function useGetVotePostsByCursor< /** * 새로운 투표 게시글을 작성합니다. -**제약사항:** -- 투표 옵션: 최소 2개, 최대 5개 -- 제목: 최대 100자 -- 내용: 최대 5000자 -- 이미지: 최대 5개 -- 마감 시간: 현재 시간보다 미래여야 함 +**특징:** +- 2-5개의 투표 옵션 필수 +- 이미지 업로드 지원 (최대 4장) +- 투표 마감 시간 설정 필수 +- 재투표 허용 여부 설정 +- 중복 선택 허용 여부 설정 +- Multipart 방식으로 이미지와 텍스트 데이터를 함께 전송 +- 카테고리 필수 선택 -**권한:** 로그인 사용자만 가능 +**지원 파일 형식:** jpg, jpeg, png, gif, webp +**최대 파일 크기:** 5MB per image * @summary 투표 게시글 작성 */ @@ -847,17 +916,39 @@ export const createVotePost = ( votePostCreateRequest: VotePostCreateRequest, signal?: AbortSignal, ) => { + const formData = new FormData(); + formData.append(`category`, votePostCreateRequest.category); + formData.append(`title`, votePostCreateRequest.title); + formData.append(`content`, votePostCreateRequest.content); + votePostCreateRequest.voteOptions.forEach((value) => + formData.append(`voteOptions`, JSON.stringify(value)), + ); + formData.append(`endTime`, votePostCreateRequest.endTime); + formData.append( + `allowRevote`, + votePostCreateRequest.allowRevote.toString(), + ); + formData.append( + `allowMultipleChoice`, + votePostCreateRequest.allowMultipleChoice.toString(), + ); + if (votePostCreateRequest.images !== undefined) { + votePostCreateRequest.images.forEach((value) => + formData.append(`images`, value), + ); + } + return customInstance({ url: `/community/votesboard`, method: 'POST', - headers: { 'Content-Type': 'application/json' }, - data: votePostCreateRequest, + headers: { 'Content-Type': 'multipart/form-data' }, + data: formData, signal, }); }; export const getCreateVotePostMutationOptions = < - TError = ErrorResponse | ErrorResponse, + TError = ErrorResponse | ErrorResponse | ErrorResponse, TContext = unknown, >(options?: { mutation?: UseMutationOptions< @@ -898,6 +989,7 @@ export type CreateVotePostMutationResult = NonNullable< >; export type CreateVotePostMutationBody = VotePostCreateRequest; export type CreateVotePostMutationError = + | ErrorResponse | ErrorResponse | ErrorResponse; @@ -905,7 +997,7 @@ export type CreateVotePostMutationError = * @summary 투표 게시글 작성 */ export const useCreateVotePost = < - TError = ErrorResponse | ErrorResponse, + TError = ErrorResponse | ErrorResponse | ErrorResponse, TContext = unknown, >( options?: { diff --git a/apps/web/src/generated/api/models/index.ts b/apps/web/src/generated/api/models/index.ts index 2a27f3fc..99a93b06 100644 --- a/apps/web/src/generated/api/models/index.ts +++ b/apps/web/src/generated/api/models/index.ts @@ -93,13 +93,17 @@ export * from './userTypeRequestUserType'; export * from './voteOptionRequest'; export * from './voteOptionResponse'; export * from './votePostCreateRequest'; +export * from './votePostCreateRequestCategory'; export * from './votePostDetailResponse'; +export * from './votePostDetailResponseCategory'; export * from './votePostDetailResponseVoteStatus'; export * from './votePostIdResponse'; export * from './votePostListResponse'; export * from './votePostSummaryResponse'; +export * from './votePostSummaryResponseCategory'; export * from './votePostSummaryResponseVoteStatus'; export * from './votePostUpdateRequest'; +export * from './votePostUpdateRequestCategory'; export * from './voteRequest'; export * from './voteboardCommentCreateRequest'; export * from './voteboardCommentCreateResponse'; diff --git a/apps/web/src/generated/api/models/votePostCreateRequest.ts b/apps/web/src/generated/api/models/votePostCreateRequest.ts index ddd0f284..4b0b04e9 100644 --- a/apps/web/src/generated/api/models/votePostCreateRequest.ts +++ b/apps/web/src/generated/api/models/votePostCreateRequest.ts @@ -5,12 +5,15 @@ * 소소한 아이디어 공유 플랫폼의 백엔드 API 문서입니다. * OpenAPI spec version: v1.0.0 */ +import type { VotePostCreateRequestCategory } from './votePostCreateRequestCategory'; import type { VoteOptionRequest } from './voteOptionRequest'; /** * 투표 게시글 생성 요청 */ export interface VotePostCreateRequest { + /** 게시글 카테고리 */ + category: VotePostCreateRequestCategory; /** * 게시글 제목 * @minLength 0 @@ -23,12 +26,6 @@ export interface VotePostCreateRequest { * @maxLength 5000 */ content: string; - /** - * 이미지 URL 목록 (최대 5개) - * @minItems 0 - * @maxItems 5 - */ - imageUrls?: string[]; /** * 투표 옵션 목록 (2-5개) * @minItems 2 @@ -41,4 +38,10 @@ export interface VotePostCreateRequest { allowRevote: boolean; /** 중복 선택 허용 여부 (true: 여러 옵션 동시 선택 가능, 최대 n-1개 / false: 하나의 옵션만 선택 가능) */ allowMultipleChoice: boolean; + /** + * 첨부 이미지 파일들 (최대 4장) + * @minItems 0 + * @maxItems 4 + */ + images?: Blob[]; } diff --git a/apps/web/src/generated/api/models/votePostCreateRequestCategory.ts b/apps/web/src/generated/api/models/votePostCreateRequestCategory.ts new file mode 100644 index 00000000..4818a85f --- /dev/null +++ b/apps/web/src/generated/api/models/votePostCreateRequestCategory.ts @@ -0,0 +1,23 @@ +/** + * Generated by orval v7.13.0 🍺 + * Do not edit manually. + * SoSo API 명세서 + * 소소한 아이디어 공유 플랫폼의 백엔드 API 문서입니다. + * OpenAPI spec version: v1.0.0 + */ + +/** + * 게시글 카테고리 + */ +export type VotePostCreateRequestCategory = + (typeof VotePostCreateRequestCategory)[keyof typeof VotePostCreateRequestCategory]; + +// eslint-disable-next-line @typescript-eslint/no-redeclare +export const VotePostCreateRequestCategory = { + 'daily-hobby': 'daily-hobby', + restaurant: 'restaurant', + 'living-convenience': 'living-convenience', + 'neighborhood-news': 'neighborhood-news', + startup: 'startup', + others: 'others', +} as const; diff --git a/apps/web/src/generated/api/models/votePostDetailResponse.ts b/apps/web/src/generated/api/models/votePostDetailResponse.ts index 9d3f99fc..0a3c2dbb 100644 --- a/apps/web/src/generated/api/models/votePostDetailResponse.ts +++ b/apps/web/src/generated/api/models/votePostDetailResponse.ts @@ -6,6 +6,8 @@ * OpenAPI spec version: v1.0.0 */ import type { UserSummaryResponse } from './userSummaryResponse'; +import type { VotePostDetailResponseCategory } from './votePostDetailResponseCategory'; +import type { ImageInfo } from './imageInfo'; import type { VoteOptionResponse } from './voteOptionResponse'; import type { VotePostDetailResponseVoteStatus } from './votePostDetailResponseVoteStatus'; @@ -15,14 +17,16 @@ import type { VotePostDetailResponseVoteStatus } from './votePostDetailResponseV export interface VotePostDetailResponse { /** 게시글 ID */ id: number; + /** 작성자 정보 */ + author: UserSummaryResponse; + /** 카테고리 */ + category: VotePostDetailResponseCategory; /** 게시글 제목 */ title: string; /** 게시글 내용 */ content: string; - /** 작성자 정보 */ - author: UserSummaryResponse; - /** 이미지 URL 목록 */ - imageUrls?: string[]; + /** 첨부된 이미지 정보 목록 */ + images: ImageInfo[]; /** 투표 옵션 목록 */ voteOptions: VoteOptionResponse[]; /** 현재 사용자가 선택한 옵션 ID 목록 (미투표 시 빈 리스트) */ @@ -43,11 +47,15 @@ export interface VotePostDetailResponse { commentCount: number; /** 좋아요 수 */ likeCount: number; + /** 현재 사용자가 게시글 수정 권한이 있는지 여부 (비인증 사용자인 경우 null, 작성자인 경우 true) */ + canEdit: boolean; + /** 현재 사용자가 게시글 삭제 권한이 있는지 여부 (비인증 사용자인 경우 null, 작성자인 경우 true) */ + canDelete: boolean; /** 생성일시 */ createdDate: string; /** 수정일시 */ lastModifiedDate: string; - liked?: boolean; - /** 현재 사용자의 좋아요 여부 (비로그인 시 false) */ + authorized?: boolean; + /** 현재 사용자의 좋아요 여부 (비인증 사용자인 경우 null) */ isLiked: boolean; } diff --git a/apps/web/src/generated/api/models/votePostDetailResponseCategory.ts b/apps/web/src/generated/api/models/votePostDetailResponseCategory.ts new file mode 100644 index 00000000..1df7e04a --- /dev/null +++ b/apps/web/src/generated/api/models/votePostDetailResponseCategory.ts @@ -0,0 +1,23 @@ +/** + * Generated by orval v7.13.0 🍺 + * Do not edit manually. + * SoSo API 명세서 + * 소소한 아이디어 공유 플랫폼의 백엔드 API 문서입니다. + * OpenAPI spec version: v1.0.0 + */ + +/** + * 카테고리 + */ +export type VotePostDetailResponseCategory = + (typeof VotePostDetailResponseCategory)[keyof typeof VotePostDetailResponseCategory]; + +// eslint-disable-next-line @typescript-eslint/no-redeclare +export const VotePostDetailResponseCategory = { + 'daily-hobby': 'daily-hobby', + restaurant: 'restaurant', + 'living-convenience': 'living-convenience', + 'neighborhood-news': 'neighborhood-news', + startup: 'startup', + others: 'others', +} as const; diff --git a/apps/web/src/generated/api/models/votePostSummaryResponse.ts b/apps/web/src/generated/api/models/votePostSummaryResponse.ts index b45cd74e..61462543 100644 --- a/apps/web/src/generated/api/models/votePostSummaryResponse.ts +++ b/apps/web/src/generated/api/models/votePostSummaryResponse.ts @@ -6,6 +6,7 @@ * OpenAPI spec version: v1.0.0 */ import type { UserSummaryResponse } from './userSummaryResponse'; +import type { VotePostSummaryResponseCategory } from './votePostSummaryResponseCategory'; import type { VotePostSummaryResponseVoteStatus } from './votePostSummaryResponseVoteStatus'; import type { VoteOptionResponse } from './voteOptionResponse'; @@ -15,10 +16,16 @@ import type { VoteOptionResponse } from './voteOptionResponse'; export interface VotePostSummaryResponse { /** 게시글 ID */ id: number; - /** 게시글 제목 */ - title: string; /** 작성자 정보 */ author: UserSummaryResponse; + /** 카테고리 */ + category: VotePostSummaryResponseCategory; + /** 게시글 제목 */ + title: string; + /** 첫 번째 이미지 URL (썸네일용) */ + thumbnailUrl?: string; + /** 이미지 개수 */ + imageCount: number; /** 조회수 */ viewCount: number; /** 댓글 수 */ diff --git a/apps/web/src/generated/api/models/votePostSummaryResponseCategory.ts b/apps/web/src/generated/api/models/votePostSummaryResponseCategory.ts new file mode 100644 index 00000000..4959db2b --- /dev/null +++ b/apps/web/src/generated/api/models/votePostSummaryResponseCategory.ts @@ -0,0 +1,23 @@ +/** + * Generated by orval v7.13.0 🍺 + * Do not edit manually. + * SoSo API 명세서 + * 소소한 아이디어 공유 플랫폼의 백엔드 API 문서입니다. + * OpenAPI spec version: v1.0.0 + */ + +/** + * 카테고리 + */ +export type VotePostSummaryResponseCategory = + (typeof VotePostSummaryResponseCategory)[keyof typeof VotePostSummaryResponseCategory]; + +// eslint-disable-next-line @typescript-eslint/no-redeclare +export const VotePostSummaryResponseCategory = { + 'daily-hobby': 'daily-hobby', + restaurant: 'restaurant', + 'living-convenience': 'living-convenience', + 'neighborhood-news': 'neighborhood-news', + startup: 'startup', + others: 'others', +} as const; diff --git a/apps/web/src/generated/api/models/votePostUpdateRequest.ts b/apps/web/src/generated/api/models/votePostUpdateRequest.ts index 2388af16..c8c6eb41 100644 --- a/apps/web/src/generated/api/models/votePostUpdateRequest.ts +++ b/apps/web/src/generated/api/models/votePostUpdateRequest.ts @@ -5,11 +5,14 @@ * 소소한 아이디어 공유 플랫폼의 백엔드 API 문서입니다. * OpenAPI spec version: v1.0.0 */ +import type { VotePostUpdateRequestCategory } from './votePostUpdateRequestCategory'; /** * 투표 게시글 수정 요청 (투표 옵션 수정 불가) */ export interface VotePostUpdateRequest { + /** 수정할 카테고리 */ + category?: VotePostUpdateRequestCategory; /** * 게시글 제목 * @minLength 0 @@ -23,11 +26,13 @@ export interface VotePostUpdateRequest { */ content?: string; /** - * 이미지 URL 목록 (최대 5개) + * 새로운 이미지 파일들 (기존 이미지 대체, 최대 4장) * @minItems 0 - * @maxItems 5 + * @maxItems 4 */ - imageUrls?: string[]; + images?: Blob[]; + /** 삭제할 기존 이미지 ID 목록 */ + deleteImageIds?: number[]; /** 투표 마감 시간 */ endTime?: string; /** 재투표 허용 여부 (투표 후 변경 가능 여부) */ diff --git a/apps/web/src/generated/api/models/votePostUpdateRequestCategory.ts b/apps/web/src/generated/api/models/votePostUpdateRequestCategory.ts new file mode 100644 index 00000000..99884880 --- /dev/null +++ b/apps/web/src/generated/api/models/votePostUpdateRequestCategory.ts @@ -0,0 +1,23 @@ +/** + * Generated by orval v7.13.0 🍺 + * Do not edit manually. + * SoSo API 명세서 + * 소소한 아이디어 공유 플랫폼의 백엔드 API 문서입니다. + * OpenAPI spec version: v1.0.0 + */ + +/** + * 수정할 카테고리 + */ +export type VotePostUpdateRequestCategory = + (typeof VotePostUpdateRequestCategory)[keyof typeof VotePostUpdateRequestCategory]; + +// eslint-disable-next-line @typescript-eslint/no-redeclare +export const VotePostUpdateRequestCategory = { + 'daily-hobby': 'daily-hobby', + restaurant: 'restaurant', + 'living-convenience': 'living-convenience', + 'neighborhood-news': 'neighborhood-news', + startup: 'startup', + others: 'others', +} as const;