Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
c75429c
#213 feat(fe): next.config.mjs파일 s3 접근 호환 로직 구현
KimKyuHoi Feb 22, 2025
58ad07b
#213 fix(fe): 낙관적인 업데이트 함수 제거
KimKyuHoi Feb 22, 2025
077aae3
#213 feat(fe): 파일 사이즈 타입 검증 유틸함수 구현
KimKyuHoi Feb 22, 2025
460d682
#213 feat(fe): GET 요청 중 instance of 조건문 추가
KimKyuHoi Feb 22, 2025
013825f
#213 feat(fe): 10mb 이하일경우 파일 업로드 api 업로드 구현
KimKyuHoi Feb 22, 2025
8a57d4c
#213 refactor(fe): 비디오 썸네일 생성로직 개선
KimKyuHoi Feb 22, 2025
62d4ce4
#213 feat(fe): network latency에 따른 청크파일 동적 조정 유틸함수 구현
KimKyuHoi Feb 22, 2025
1cc8a56
#213 feat(fe): 청크 업로드 api 구현
KimKyuHoi Feb 22, 2025
39beaca
#213 feat(fe): 파일 업로드 후 썸네일 업로드 로직 구현
KimKyuHoi Feb 22, 2025
7364239
#213 feat(fe): 파일 타입 설정
KimKyuHoi Feb 22, 2025
3f28d15
#213 feat(fe): 파일 업로드 로직 훅 구현
KimKyuHoi Feb 22, 2025
8238868
#213 feat(fe): 파일 미리보기 모달 창 구현
KimKyuHoi Feb 22, 2025
5b6960d
#213 feat(fe): 파일 업로드 후 선택 삭제 및 레이아웃 구현
KimKyuHoi Feb 22, 2025
95cc686
#213 fix(fe): overflow 레이아웃 수정
KimKyuHoi Feb 22, 2025
b575fb7
#213 feat(fe): 파일 로딩시 동적으로 로딩 처리
KimKyuHoi Feb 22, 2025
12d1c48
#213 fix(fe): 파일 명 수정
KimKyuHoi Feb 22, 2025
0fc47ac
#213 feat(fe): file toggle group 로직 추가
KimKyuHoi Feb 22, 2025
4450b5b
#213 build(fe): id Index값 추가
KimKyuHoi Feb 23, 2025
05e963c
Merge branch 'dev' of https://github.com/sgdevcamp2025/c_sharp into f…
KimKyuHoi Feb 24, 2025
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
5 changes: 5 additions & 0 deletions src/frontend/apps/web/src/features/chat/api/get-ping.api.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import { getRequest } from '@/src/shared/services';

export async function getPing() {
return getRequest<string>('file', '/api/v1/files/test');
}
5 changes: 5 additions & 0 deletions src/frontend/apps/web/src/features/chat/api/index.ts
Original file line number Diff line number Diff line change
@@ -1 +1,6 @@
export { getPing } from './get-ping.api';
export { uploadFiles } from './upload-file.api';
export { uploadSmallFiles } from './upload-smallfile.api';
export { uploadChunksQueue } from './upload-chunks-queue.api';
export { uploadThumbnail } from './upload-thumbnail.api';
export { getHistoryChat } from './get-history-chat.api';
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
import { ResponseChunkFileData } from '../model';
import { uploadFiles } from './upload-file.api';

export const uploadChunkWithRetry = async (
chunk: Blob,
channelId: number,
workspaceId: number,
tempFileIdentifier: string,
totalChunks: number,
totalSize: number,
chunkIndex: number,
attempt: number = 0,
): Promise<ResponseChunkFileData> => {
const maxAttempts = 5;
try {
// console.log(
// '[uploadChunkWithRetry] Attempt:',
// attempt,
// 'ChunkIndex:',
// chunkIndex,
// );

const fileForChunk = new File([chunk], `chunk-${chunkIndex}`, {
type: 'application/octet-stream',
});

// console.log('[uploadChunkWithRetry] fileForChunk size:', fileForChunk.size);

const response = await uploadFiles({
channelId,
workspaceId,
tempFileIdentifier,
totalChunks,
totalSize,
chunkIndex,
chunk: fileForChunk,
});
// console.log(
// '[uploadChunkWithRetry] Upload success. chunkIndex:',
// chunkIndex,
// );
return response;
} catch (error) {
// console.error(
// '[uploadChunkWithRetry] Upload error. chunkIndex:',
// chunkIndex,
// error,
// );

if (attempt < maxAttempts) {
const delay = Math.pow(2, attempt) * 1000;
console.warn(
`Upload failed for chunk ${chunkIndex} (attempt ${attempt + 1}). Retrying in ${delay}ms...`,
);
await new Promise((resolve) => setTimeout(resolve, delay));
return uploadChunkWithRetry(
chunk,
channelId,
workspaceId,
tempFileIdentifier,
totalChunks,
totalSize,
chunkIndex,
attempt + 1,
);
} else {
console.error(
`Chunk ${chunkIndex} upload failed after ${maxAttempts} attempts.`,
);
throw error;
}
}
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
import { uploadChunkWithRetry } from './upload-chunk-retry-api';

import { ResponseChunkFileData } from '../model';
import { getUploadConcurrency } from '../lib/chunk-file.utils';

/**
* 파일 청크들을 네트워크 상태에 따른 병렬성으로 업로드하는 업로드 대기열 함수입니다.
* 각 청크는 바로 API로 전송됩니다.
* @param chunks 업로드할 청크 배열
* @param channelId 채널 ID
* @param workspaceId 워크스페이스 ID
* @param tempFileIdentifier 임시 파일 식별자
* @param chunkSize 청크 사이즈
* @returns {Promise<ResponseFileUploadItem[]>} 각 청크 업로드 결과 배열
*/
export const uploadChunksQueue = async (
chunks: Blob[],
channelId: number,
workspaceId: number,
tempFileIdentifier: string,
chunkSize: number,
): Promise<ResponseChunkFileData[]> => {
const totalChunk = chunks.length;
const concurrency = await getUploadConcurrency();
// console.log('Starting upload with concurrency:', concurrency);
const results: ResponseChunkFileData[] = new Array(totalChunk);
let currentIndex = 0;

async function worker(workerId: number) {
while (currentIndex < totalChunk) {
const index = currentIndex;
currentIndex++;
// console.log(`[worker ${workerId}] Uploading chunk index:`, index);

try {
results[index] = await uploadChunkWithRetry(
chunks[index],
channelId,
workspaceId,
tempFileIdentifier,
totalChunk,
chunkSize,
index + 1,
);
// console.log(
// `[worker ${workerId}] Upload success for chunk index:`,
// index,
// );
} catch (error) {
console.error(
`[worker ${workerId}] Upload failed for chunk index:`,
index,
error,
);
results[index] = {
code: '500',
status: 'error',
};
}
}
}

const workers = [];
for (let i = 0; i < concurrency; i++) {
workers.push(worker(i));
}
await Promise.all(workers);
return results;
};
44 changes: 44 additions & 0 deletions src/frontend/apps/web/src/features/chat/api/upload-file.api.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import { ChunkFileData, ResponseChunkFileData } from '../model';
import { postRequest } from '@/src/shared/services/apis';

/**
* 각 청크 데이터를 개별적으로 업로드하는 함수.
* 이 함수는 하나의 청크(ChunkInfo 객체)를 API로 전송합니다.
*/
export async function uploadFiles({
channelId,
workspaceId,
tempFileIdentifier,
totalChunks,
totalSize,
chunkIndex,
chunk,
}): Promise<ResponseChunkFileData> {
const formData = new FormData();

formData.append('channelId', channelId.toString());
formData.append('workspaceId', workspaceId.toString());
formData.append('tempFileIdentifier', tempFileIdentifier);
formData.append('totalChunks', totalChunks.toString());
formData.append('totalSize', totalSize.toString());
formData.append('chunkIndex', chunkIndex.toString());
formData.append('chunk', chunk);

// 디버깅용: formData의 모든 키-값 출력
// for (const [key, value] of formData.entries()) {
// console.log('debugging', key, value);
// }

try {
const response = await postRequest<ResponseChunkFileData, FormData>(
'file',
'/api/v1/files/chunk',
formData,
);
console.log('[uploadFiles] Response =>', response);
return response;
} catch (error) {
console.error('[uploadFiles] Request error =>', error);
throw error;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import { type ResponseChunkFileData } from '../model';
import { postRequest } from '@/src/shared/services/apis';

/**
* 각 청크 데이터를 개별적으로 업로드하는 함수.
* 이 함수는 하나의 청크(ChunkInfo 객체)를 API로 전송합니다.
*/
export async function uploadSmallFiles({
channelId,
workspaceId,
file,
}): Promise<ResponseChunkFileData> {
const formData = new FormData();

formData.append('channelId', channelId.toString());
formData.append('workspaceId', workspaceId.toString());
formData.append('file', file);

// 디버깅용: formData의 모든 키-값 출력
// for (const [key, value] of formData.entries()) {
// console.log('debugging', key, value);
// }

try {
const response = await postRequest<ResponseChunkFileData, FormData>(
'file',
'/api/v1/files/small',
formData,
);
console.log('[uploadFiles] Response =>', response);
return response;
} catch (error) {
console.error('[uploadFiles] Request error =>', error);
throw error;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import type { FileResponse } from '../model';
import { postRequest } from '@/src/shared/services/apis';

type ThumbnailData = {
fileId: number;
thumbnail?: File;
};

/**
* 각 청크 데이터를 개별적으로 업로드하는 함수.
* 이 함수는 하나의 청크(ChunkInfo 객체)를 API로 전송합니다.
*/
export async function uploadThumbnail({
fileId,
thumbnail,
}: ThumbnailData): Promise<FileResponse> {
const formData = new FormData();

formData.append('fileId', fileId.toString());
formData.append('thumbnail', thumbnail);

// 디버깅용: formData의 모든 키-값 출력
// for (const [key, value] of formData.entries()) {
// console.log('debugging', key, value);
// }

try {
const response = await postRequest<FileResponse, FormData>(
'file',
'/api/v1/files/thumbnail',
formData,
);
console.log('[uploadFiles] Response =>', response);
return response;
} catch (error) {
console.error('[uploadFiles] Request error =>', error);
throw error;
}
}
42 changes: 0 additions & 42 deletions src/frontend/apps/web/src/features/chat/api/uploadFile.api.ts

This file was deleted.

Loading
Loading