대표 이미지 저장 기능 구현

This commit is contained in:
dohyeons 2025-11-05 15:50:29 +09:00
parent 9429033e2c
commit df779ac04c
4 changed files with 108 additions and 37 deletions

View File

@ -616,6 +616,7 @@ export const getComponentFiles = async (
regdate: file.regdate?.toISOString(),
status: file.status,
isTemplate, // 템플릿 파일 여부 표시
isRepresentative: file.is_representative || false, // 대표 파일 여부
});
const formattedTemplateFiles = templateFiles.map((file) =>
@ -1088,5 +1089,68 @@ export const getFileByToken = async (req: Request, res: Response) => {
}
};
/**
*
*/
export const setRepresentativeFile = async (
req: AuthenticatedRequest,
res: Response
): Promise<void> => {
try {
const { objid } = req.params;
const companyCode = req.user?.companyCode;
// 파일 존재 여부 및 권한 확인
const fileRecord = await queryOne<any>(
`SELECT * FROM attach_file_info WHERE objid = $1 AND status = $2`,
[parseInt(objid), "ACTIVE"]
);
if (!fileRecord) {
res.status(404).json({
success: false,
message: "파일을 찾을 수 없습니다.",
});
return;
}
// 멀티테넌시: 회사 코드 확인
if (companyCode !== "*" && fileRecord.company_code !== companyCode) {
res.status(403).json({
success: false,
message: "접근 권한이 없습니다.",
});
return;
}
// 같은 target_objid의 다른 파일들의 is_representative를 false로 설정
await query<any>(
`UPDATE attach_file_info
SET is_representative = false
WHERE target_objid = $1 AND objid != $2`,
[fileRecord.target_objid, parseInt(objid)]
);
// 선택한 파일을 대표 파일로 설정
await query<any>(
`UPDATE attach_file_info
SET is_representative = true
WHERE objid = $1`,
[parseInt(objid)]
);
res.json({
success: true,
message: "대표 파일이 설정되었습니다.",
});
} catch (error) {
console.error("대표 파일 설정 오류:", error);
res.status(500).json({
success: false,
message: "대표 파일 설정 중 오류가 발생했습니다.",
});
}
};
// Multer 미들웨어 export
export const uploadMiddleware = upload.array("files", 10); // 최대 10개 파일

View File

@ -10,6 +10,7 @@ import {
uploadMiddleware,
generateTempToken,
getFileByToken,
setRepresentativeFile,
} from "../controllers/fileController";
import { authenticateToken } from "../middleware/authMiddleware";
@ -84,4 +85,11 @@ router.get("/download/:objid", downloadFile);
*/
router.post("/temp-token/:objid", generateTempToken);
/**
* @route PUT /api/files/representative/:objid
* @desc
* @access Private
*/
router.put("/representative/:objid", setRepresentativeFile);
export default router;

View File

@ -249,3 +249,19 @@ export const getDirectFileUrl = (filePath: string): string => {
const baseUrl = process.env.NEXT_PUBLIC_API_URL?.replace("/api", "") || "";
return `${baseUrl}${filePath}`;
};
/**
*
*/
export const setRepresentativeFile = async (objid: string): Promise<{
success: boolean;
message: string;
}> => {
try {
const response = await apiClient.put(`/files/representative/${objid}`);
return response.data;
} catch (error) {
console.error("대표 파일 설정 오류:", error);
throw new Error("대표 파일 설정에 실패했습니다.");
}
};

View File

@ -802,7 +802,13 @@ const FileUploadComponent: React.FC<FileUploadComponentProps> = ({
// 대표 이미지 설정 핸들러
const handleSetRepresentative = useCallback(
(file: FileInfo) => {
async (file: FileInfo) => {
try {
// API 호출하여 DB에 대표 파일 설정
const { setRepresentativeFile } = await import("@/lib/api/file");
await setRepresentativeFile(file.objid);
// 상태 업데이트
const updatedFiles = uploadedFiles.map((f) => ({
...f,
isRepresentative: f.objid === file.objid,
@ -813,39 +819,16 @@ const FileUploadComponent: React.FC<FileUploadComponentProps> = ({
// 대표 이미지 로드
loadRepresentativeImage(file);
// localStorage 백업
try {
const backupKey = `fileUpload_${component.id}`;
localStorage.setItem(backupKey, JSON.stringify(updatedFiles));
console.log("📌 대표 파일 설정 완료:", {
console.log("✅ 대표 파일 설정 완료:", {
componentId: component.id,
representativeFile: file.realFileName,
objid: file.objid,
});
} catch (e) {
console.warn("localStorage 저장 실패:", e);
console.error("❌ 대표 파일 설정 실패:", e);
}
// 전역 상태 동기화
if (typeof window !== "undefined") {
(window as any).globalFileState = {
...(window as any).globalFileState,
[component.id]: updatedFiles,
};
// 실시간 동기화 이벤트 발송
const syncEvent = new CustomEvent("fileStateChanged", {
detail: {
componentId: component.id,
files: updatedFiles,
action: "setRepresentative",
},
});
window.dispatchEvent(syncEvent);
}
toast.success(`${file.realFileName}을(를) 대표 파일로 설정했습니다.`);
},
[uploadedFiles, component.id, loadRepresentativeImage],
[uploadedFiles, component.id, loadRepresentativeImage]
);
// uploadedFiles 변경 시 대표 이미지 로드