From fa35d5c30529a07a3d7ce15c5f84f4d3505e701f Mon Sep 17 00:00:00 2001 From: imnyang Date: Thu, 23 Apr 2026 18:50:15 +0900 Subject: [PATCH] wow --- apps/backend/src/routes/post.ts | 35 ++++++++++++++++++++++-------- apps/frontend/src/app/add/page.tsx | 7 ++++++ apps/frontend/src/app/page.tsx | 24 ++++++++++---------- 3 files changed, 46 insertions(+), 20 deletions(-) diff --git a/apps/backend/src/routes/post.ts b/apps/backend/src/routes/post.ts index 81a3a4f..b245264 100644 --- a/apps/backend/src/routes/post.ts +++ b/apps/backend/src/routes/post.ts @@ -18,6 +18,7 @@ type UploadResponse = { message: string; savedCount?: number; failedCount?: number; + ids?: string[]; }; type ExistsResponse = { @@ -120,13 +121,13 @@ async function uploadAndCreateWithRetry(options: { fileName: string; mediaUrl: string; mediaIndex: number; - createDocument: () => Promise; + createDocument: () => Promise; }) { const { fileName, mediaUrl, mediaIndex, createDocument } = options; const existingBefore = await MediaUpload.findOne({ s3Key: fileName, mediaIndex }); if (existingBefore) { - return { ok: true as const, created: false }; + return { ok: true as const, created: false, id: existingBefore._id.toString() }; } let lastError: unknown; @@ -138,16 +139,16 @@ async function uploadAndCreateWithRetry(options: { const existing = await MediaUpload.findOne({ s3Key: fileName, mediaIndex }); if (existing) { - return { ok: true as const, created: false }; + return { ok: true as const, created: false, id: existing._id.toString() }; } - await createDocument(); - return { ok: true as const, created: true }; + const doc = await createDocument(); + return { ok: true as const, created: true, id: doc._id.toString() }; } catch (error) { lastError = error; const existingAfterError = await MediaUpload.findOne({ s3Key: fileName, mediaIndex }); if (existingAfterError) { - return { ok: true as const, created: false }; + return { ok: true as const, created: false, id: existingAfterError._id.toString() }; } if (attempt < 2) { @@ -397,7 +398,11 @@ export default new Elysia({ prefix: "/post" }) const existingPost = await checkExistingPostByUrl(body.url); if (existingPost.exists) { - return uploadOk("이미 저장된 게시물입니다.", { savedCount: 0, failedCount: 0 }); + return uploadOk("이미 저장된 게시물입니다.", { + savedCount: 0, + failedCount: 0, + ids: existingPost.documentId ? [existingPost.documentId] : [], + }); } if (body.url.startsWith("https://www.pixiv.net/")) { @@ -428,6 +433,7 @@ export default new Elysia({ prefix: "/post" }) let failedCount = 0; const hasExplicitSelection = body.selected.length > 0; + const savedIds: string[] = []; for (const [index, mediaUrl] of mediaUrls.entries()) { const isSelected = hasExplicitSelection ? body.selected[index] === true @@ -446,7 +452,7 @@ export default new Elysia({ prefix: "/post" }) mediaUrl, mediaIndex: index, createDocument: async () => { - await MediaUpload.create({ + return await MediaUpload.create({ type: "pixiv", tweet: { id: illustId, @@ -477,6 +483,10 @@ export default new Elysia({ prefix: "/post" }) continue; } + if (result.id) { + savedIds.push(result.id); + } + if (result.created) { await saveTags(normalizedTags); savedCount += 1; @@ -523,6 +533,7 @@ export default new Elysia({ prefix: "/post" }) return uploadOk("업로드가 완료되었습니다.", { savedCount, failedCount, + ids: savedIds, }); } catch (error) { console.error(`[Pixiv upload aborted] requestId=${requestId} key=${uploadKey}`, error); @@ -551,6 +562,7 @@ export default new Elysia({ prefix: "/post" }) if (media.length > 0) { const mediaUrls = media.map((m: any) => m.url); const hasExplicitSelection = body.selected.length > 0; + const savedIds: string[] = []; for (const [index, url] of mediaUrls.entries()) { const isSelected = hasExplicitSelection ? body.selected[index] === true @@ -569,7 +581,7 @@ export default new Elysia({ prefix: "/post" }) mediaUrl: url, mediaIndex: index, createDocument: async () => { - await MediaUpload.create({ + return await MediaUpload.create({ type: "twitter", tweet: tweetWithoutMedia, mediaIndex: index, @@ -593,6 +605,10 @@ export default new Elysia({ prefix: "/post" }) continue; } + if (result.id) { + savedIds.push(result.id); + } + if (result.created) { await saveTags(normalizedTags); savedCount += 1; @@ -643,6 +659,7 @@ export default new Elysia({ prefix: "/post" }) return uploadOk("업로드가 완료되었습니다.", { savedCount, failedCount, + ids: savedIds, }); } catch (error) { console.error(`[Upload aborted] requestId=${requestId} key=${uploadKey}`, error); diff --git a/apps/frontend/src/app/add/page.tsx b/apps/frontend/src/app/add/page.tsx index 75e2f2f..17066eb 100644 --- a/apps/frontend/src/app/add/page.tsx +++ b/apps/frontend/src/app/add/page.tsx @@ -2,6 +2,7 @@ import { FormEvent, useEffect, useMemo, useState } from "react"; import Link from "next/link"; +import { useRouter } from "next/navigation"; import Header from "../../components/header"; type SourceType = "twitter" | "pixiv"; @@ -32,6 +33,7 @@ type UploadApiResponse = { message: string; savedCount?: number; failedCount?: number; + ids?: string[]; }; type ExistsApiResponse = { @@ -63,6 +65,7 @@ function splitTags(text: string) { } export default function AddPage() { + const router = useRouter(); const [url, setUrl] = useState(""); const [author, setAuthor] = useState(""); const [tagsText, setTagsText] = useState(""); @@ -313,6 +316,10 @@ export default function AddPage() { } setSuccess(message); + + if (data?.ids && data.ids.length > 0) { + router.push(`/detail/${data.ids[0]}`); + } } catch (submitError) { setError(submitError instanceof Error ? submitError.message : "업로드에 실패했습니다."); } finally { diff --git a/apps/frontend/src/app/page.tsx b/apps/frontend/src/app/page.tsx index 7581b12..52f7829 100644 --- a/apps/frontend/src/app/page.tsx +++ b/apps/frontend/src/app/page.tsx @@ -214,17 +214,19 @@ export default function App() { async function copyImage(photo: GalleryPhoto) { try { - if (typeof ClipboardItem !== "undefined" && navigator.clipboard?.write) { - const response = await fetch(photo.src, { cache: "no-store" }); - const blob = await response.blob(); - await navigator.clipboard.write([new ClipboardItem({ [blob.type || "image/png"]: blob })]); - } else if (navigator.clipboard?.writeText) { - await navigator.clipboard.writeText(photo.src); - } - } catch { - if (navigator.clipboard?.writeText) { - await navigator.clipboard.writeText(photo.src); - } + const response = await fetch(photo.src); + if (!response.ok) throw new Error("Failed to fetch image"); + + const blob = await response.blob(); + const clipboardItem = new ClipboardItem({ + [blob.type]: blob + }); + + await navigator.clipboard.write([clipboardItem]); + alert("이미지가 클립보드에 복사되었습니다!"); + } catch (error) { + console.error("Failed to copy image:", error); + alert("이미지 복사에 실패했습니다. 브라우저 호환성 문제일 수 있습니다."); } }