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
12 changes: 1 addition & 11 deletions src/api/feeds/createFeed.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import { apiClient } from '../index';

/** 서버에 보낼 request JSON 페이로드 */
export interface CreateFeedBody {
isbn: string;
contentBody: string;
Expand All @@ -9,7 +8,6 @@ export interface CreateFeedBody {
imageUrls?: string[];
}

/** 성공 응답 */
export interface CreateFeedSuccess {
isSuccess: true;
code: number;
Expand All @@ -19,7 +17,6 @@ export interface CreateFeedSuccess {
};
}

/** 실패 응답 */
export interface CreateFeedFail {
isSuccess: false;
code: number;
Expand All @@ -28,14 +25,7 @@ export interface CreateFeedFail {

export type CreateFeedResponse = CreateFeedSuccess | CreateFeedFail;

/**
* 피드 작성 API
* - application/json (presigned URL 방식)
* - imageUrls: 미리 S3에 업로드한 이미지의 CloudFront URL 목록
*/
export const createFeed = async (
body: CreateFeedBody,
): Promise<CreateFeedResponse> => {
export const createFeed = async (body: CreateFeedBody): Promise<CreateFeedResponse> => {
const { data } = await apiClient.post<CreateFeedResponse>('/feeds', body);
return data;
};
16 changes: 0 additions & 16 deletions src/api/feeds/getWriteInfo.ts
Original file line number Diff line number Diff line change
@@ -1,38 +1,22 @@
import { apiClient } from '../index';

// 카테고리 및 태그 데이터 타입
export interface CategoryData {
category: string;
tagList: string[];
}

// API 응답 데이터 타입
export interface WriteInfoData {
categoryList: CategoryData[];
}

// API 응답 타입
export interface GetWriteInfoResponse {
isSuccess: boolean;
code: number;
message: string;
data: WriteInfoData;
}

// 새 글 작성을 위한 카테고리 및 태그 조회 API 함수
export const getWriteInfo = async () => {
const response = await apiClient.get<GetWriteInfoResponse>('/feeds/write-info');
return response.data;
};

/*
사용 예시:
const writeInfo = await getWriteInfo();
console.log(writeInfo.data.categoryList); // CategoryData[]

// 카테고리별 태그 접근
writeInfo.data.categoryList.forEach(category => {
console.log(`카테고리: ${category.category}`);
console.log(`태그: ${category.tagList.join(', ')}`);
});
*/
43 changes: 0 additions & 43 deletions src/api/feeds/updateFeed.ts
Original file line number Diff line number Diff line change
@@ -1,21 +1,17 @@
import { apiClient } from '../index';

/** 피드 수정 요청 바디 타입 */
export interface UpdateFeedBody {
contentBody: string;
isPublic: boolean;
tagList?: string[];
remainImageUrls?: string[];
}

/** 성공 응답 */
export interface UpdateFeedSuccess {
isSuccess: true;
code: number;
message: string;
}

/** 실패 응답 */
export interface UpdateFeedFail {
isSuccess: false;
code: number;
Expand All @@ -24,12 +20,6 @@ export interface UpdateFeedFail {

export type UpdateFeedResponse = UpdateFeedSuccess | UpdateFeedFail;

/**
* 피드 수정 API
* - multipart/form-data
* - request: application/json (Blob로 감싸 전송)
* - 이미지 추가는 불가능, 기존 이미지 삭제만 가능
*/
export const updateFeed = async (
feedId: number,
body: UpdateFeedBody,
Expand All @@ -53,36 +43,3 @@ export const updateFeed = async (
return data;
}
};

/*
사용 예시:

// 기존 이미지 일부 유지 (새 이미지 추가는 불가)
const updateBody: UpdateFeedBody = {
contentBody: "수정된 글 내용입니다!",
isPublic: true,
tagList: ["한국소설", "책추천", "역사"],
remainImageUrls: ["https://img.domain.com/1.jpg"] // 기존 이미지 중 유지할 것들
};

try {
const result = await updateFeed(123, updateBody);
if (result.isSuccess) {
console.log('피드 수정 성공:', result.message);
} else {
console.error('피드 수정 실패:', result.message);
}
} catch (error) {
console.error('네트워크 오류:', error);
}

// 모든 이미지 삭제 후 텍스트만 수정
const textOnlyUpdate: UpdateFeedBody = {
contentBody: "텍스트만 수정",
isPublic: false,
tagList: [],
remainImageUrls: [] // 모든 기존 이미지 삭제
};

const result = await updateFeed(123, textOnlyUpdate);
*/
45 changes: 1 addition & 44 deletions src/api/images/uploadImage.ts
Original file line number Diff line number Diff line change
@@ -1,45 +1,35 @@
import { apiClient } from '../index';

/** 단일 이미지 업로드 성공 시 데이터 */
export interface UploadImageData {
imageUrl: string;
}

/** 서버 공통 응답 타입 */
export interface UploadImageResponse {
isSuccess: boolean;
code: number;
message: string;
data?: UploadImageData; // 성공 시에만 존재
data?: UploadImageData;
}

/** 내부 유틸: 허용 확장자 */
const IMAGE_EXT_REGEX = /\.(jpe?g|png|gif)$/i;
/** 가이드 최대 업로드 개수 (서버는 FEED 생성 시 최대 3장 제약) */
export const MAX_IMAGES = 3;

/** 파일 사전 검증: 빈 파일 / 확장자 */
function validateFile(file: File) {
if (!file || file.size === 0) {
// 서버 코드 170001과 의미 일치
throw new Error('업로드하려는 이미지가 비어있습니다.');
}
if (!IMAGE_EXT_REGEX.test(file.name)) {
// 서버 코드 170003과 의미 일치
throw new Error('파일 형식은 jpg, jpeg, png, gif만 가능합니다.');
}
}

/** 단일 이미지 업로드 */
export const uploadImage = async (
file: File,
options?: { signal?: AbortSignal },
): Promise<UploadImageResponse> => {
// 사전 검증
validateFile(file);

const formData = new FormData();
// 서버가 단일 업로드에서 기대하는 필드명이 image라면 유지
formData.append('image', file);

const { data } = await apiClient.post<UploadImageResponse>('/images/upload', formData, {
Expand All @@ -50,24 +40,16 @@ export const uploadImage = async (
return data;
};

/**
* 다중 이미지 업로드
* - 전부 성공하면 URL 배열 반환
* - 하나라도 실패하면 실패 내역을 포함해 throw
*/
export const uploadMultipleImages = async (
files: File[],
options?: { signal?: AbortSignal; enforceMax?: boolean },
): Promise<string[]> => {
// 개수 제한(선택) – FEED 생성 정책에 맞춰 사전 차단하고 싶을 때 사용
if (options?.enforceMax && files.length > MAX_IMAGES) {
throw new Error(`이미지는 최대 ${MAX_IMAGES}장까지 업로드할 수 있습니다.`);
}

// 파일별 사전 검증
files.forEach(validateFile);

// 병렬 업로드 (각 요청 독립)
const results = await Promise.allSettled(
files.map(file => uploadImage(file, { signal: options?.signal })),
);
Expand Down Expand Up @@ -95,34 +77,9 @@ export const uploadMultipleImages = async (
});

if (failures.length > 0) {
// 어떤 항목이 왜 실패했는지 상세 메시지
const detail = failures.map(f => `#${f.index + 1}: ${f.reason}`).join(' / ');
throw new Error(`일부 이미지 업로드에 실패했습니다. (${detail})`);
}

return successUrls;
};

/*
사용 예시:

// 단일
try {
const res = await uploadImage(file);
if (res.isSuccess) {
console.log('업로드된 URL:', res.data?.imageUrl);
} else {
console.error('실패:', res.message);
}
} catch (e) {
console.error('오류:', e);
}

// 다중
try {
const urls = await uploadMultipleImages(files, { enforceMax: true });
console.log('업로드된 URL들:', urls);
} catch (e) {
console.error('다중 업로드 실패:', e);
}
*/
57 changes: 2 additions & 55 deletions src/api/memory/getMemoryPosts.ts
Original file line number Diff line number Diff line change
@@ -1,40 +1,35 @@
import { apiClient } from '../index';
import type { GetMemoryPostsParams, GetMemoryPostsResponse } from '@/types/memory';

// 기록장 조회 API 함수
export const getMemoryPosts = async (
params: GetMemoryPostsParams,
): Promise<GetMemoryPostsResponse> => {
const { roomId, ...queryParams } = params;

// 쿼리 파라미터 생성
const searchParams = new URLSearchParams();

// 기본값 적용
searchParams.append('type', queryParams.type || 'group');

// type이 group인 경우만 sort 파라미터 추가
if ((queryParams.type || 'group') === 'group' && queryParams.sort) {
searchParams.append('sort', queryParams.sort);
}

// 페이지 필터 파라미터
if (queryParams.pageStart !== undefined && queryParams.pageStart !== null) {
searchParams.append('pageStart', queryParams.pageStart.toString());
}

if (queryParams.pageEnd !== undefined && queryParams.pageEnd !== null) {
searchParams.append('pageEnd', queryParams.pageEnd.toString());
}

// 필터 파라미터
if (queryParams.isOverview !== undefined) {
searchParams.append('isOverview', queryParams.isOverview.toString());
}

if (queryParams.isPageFilter !== undefined) {
searchParams.append('isPageFilter', queryParams.isPageFilter.toString());
}

// 커서 파라미터
if (queryParams.cursor) {
searchParams.append('cursor', queryParams.cursor);
}
Expand All @@ -49,51 +44,3 @@ export const getMemoryPosts = async (
throw error;
}
};

/*
사용 예시:

// 그룹 기록 전체 조회 (기본)
const groupPosts = await getMemoryPosts({
roomId: 1
});

// 내 기록만 조회
const myPosts = await getMemoryPosts({
roomId: 1,
type: 'mine'
});

// 그룹 기록 인기순 정렬
const popularPosts = await getMemoryPosts({
roomId: 1,
type: 'group',
sort: 'like'
});

// 페이지 필터 적용 (10-20페이지)
const pagePosts = await getMemoryPosts({
roomId: 1,
type: 'group',
pageStart: 10,
pageEnd: 20,
isPageFilter: true
});

// 총평 보기 필터
const overviewPosts = await getMemoryPosts({
roomId: 1,
type: 'group',
isOverview: true
});

// 페이지네이션 (다음 페이지)
const nextPagePosts = await getMemoryPosts({
roomId: 1,
cursor: 'some-cursor-value'
});

console.log('Posts:', groupPosts.data.postList);
console.log('Next cursor:', groupPosts.data.nextCursor);
console.log('Is last page:', groupPosts.data.isLast);
*/
20 changes: 0 additions & 20 deletions src/api/record/createAiReview.ts
Original file line number Diff line number Diff line change
@@ -1,31 +1,11 @@
import { apiClient } from '../index';
import type { CreateAiReviewData, ApiResponse } from '@/types/record';

// API 응답 타입
export type CreateAiReviewResponse = ApiResponse<CreateAiReviewData>;

// AI 독서감상문 생성 API 함수
export const createAiReview = async (roomId: number) => {
const response = await apiClient.post<CreateAiReviewResponse>(
`/rooms/${roomId}/record/ai-review`,
);
return response.data;
};

/*
사용 예시:
try {
const result = await createAiReview(1);
if (result.isSuccess) {
console.log("생성된 독서감상문:", result.data.content);
console.log("잔여 이용 횟수:", result.data.count);
// 성공 처리 로직
} else {
console.error("AI 독서감상문 생성 실패:", result.message);
// 실패 처리 로직
}
} catch (error) {
console.error("API 호출 오류:", error);
// 에러 처리 로직
}
*/
Loading