This commit is contained in:
암냥 2026-04-23 18:50:15 +09:00
commit fa35d5c305
No known key found for this signature in database
3 changed files with 46 additions and 20 deletions

View file

@ -18,6 +18,7 @@ type UploadResponse = {
message: string; message: string;
savedCount?: number; savedCount?: number;
failedCount?: number; failedCount?: number;
ids?: string[];
}; };
type ExistsResponse = { type ExistsResponse = {
@ -120,13 +121,13 @@ async function uploadAndCreateWithRetry(options: {
fileName: string; fileName: string;
mediaUrl: string; mediaUrl: string;
mediaIndex: number; mediaIndex: number;
createDocument: () => Promise<void>; createDocument: () => Promise<any>;
}) { }) {
const { fileName, mediaUrl, mediaIndex, createDocument } = options; const { fileName, mediaUrl, mediaIndex, createDocument } = options;
const existingBefore = await MediaUpload.findOne({ s3Key: fileName, mediaIndex }); const existingBefore = await MediaUpload.findOne({ s3Key: fileName, mediaIndex });
if (existingBefore) { if (existingBefore) {
return { ok: true as const, created: false }; return { ok: true as const, created: false, id: existingBefore._id.toString() };
} }
let lastError: unknown; let lastError: unknown;
@ -138,16 +139,16 @@ async function uploadAndCreateWithRetry(options: {
const existing = await MediaUpload.findOne({ s3Key: fileName, mediaIndex }); const existing = await MediaUpload.findOne({ s3Key: fileName, mediaIndex });
if (existing) { if (existing) {
return { ok: true as const, created: false }; return { ok: true as const, created: false, id: existing._id.toString() };
} }
await createDocument(); const doc = await createDocument();
return { ok: true as const, created: true }; return { ok: true as const, created: true, id: doc._id.toString() };
} catch (error) { } catch (error) {
lastError = error; lastError = error;
const existingAfterError = await MediaUpload.findOne({ s3Key: fileName, mediaIndex }); const existingAfterError = await MediaUpload.findOne({ s3Key: fileName, mediaIndex });
if (existingAfterError) { if (existingAfterError) {
return { ok: true as const, created: false }; return { ok: true as const, created: false, id: existingAfterError._id.toString() };
} }
if (attempt < 2) { if (attempt < 2) {
@ -397,7 +398,11 @@ export default new Elysia({ prefix: "/post" })
const existingPost = await checkExistingPostByUrl(body.url); const existingPost = await checkExistingPostByUrl(body.url);
if (existingPost.exists) { 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/")) { if (body.url.startsWith("https://www.pixiv.net/")) {
@ -428,6 +433,7 @@ export default new Elysia({ prefix: "/post" })
let failedCount = 0; let failedCount = 0;
const hasExplicitSelection = body.selected.length > 0; const hasExplicitSelection = body.selected.length > 0;
const savedIds: string[] = [];
for (const [index, mediaUrl] of mediaUrls.entries()) { for (const [index, mediaUrl] of mediaUrls.entries()) {
const isSelected = hasExplicitSelection const isSelected = hasExplicitSelection
? body.selected[index] === true ? body.selected[index] === true
@ -446,7 +452,7 @@ export default new Elysia({ prefix: "/post" })
mediaUrl, mediaUrl,
mediaIndex: index, mediaIndex: index,
createDocument: async () => { createDocument: async () => {
await MediaUpload.create({ return await MediaUpload.create({
type: "pixiv", type: "pixiv",
tweet: { tweet: {
id: illustId, id: illustId,
@ -477,6 +483,10 @@ export default new Elysia({ prefix: "/post" })
continue; continue;
} }
if (result.id) {
savedIds.push(result.id);
}
if (result.created) { if (result.created) {
await saveTags(normalizedTags); await saveTags(normalizedTags);
savedCount += 1; savedCount += 1;
@ -523,6 +533,7 @@ export default new Elysia({ prefix: "/post" })
return uploadOk("업로드가 완료되었습니다.", { return uploadOk("업로드가 완료되었습니다.", {
savedCount, savedCount,
failedCount, failedCount,
ids: savedIds,
}); });
} catch (error) { } catch (error) {
console.error(`[Pixiv upload aborted] requestId=${requestId} key=${uploadKey}`, 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) { if (media.length > 0) {
const mediaUrls = media.map((m: any) => m.url); const mediaUrls = media.map((m: any) => m.url);
const hasExplicitSelection = body.selected.length > 0; const hasExplicitSelection = body.selected.length > 0;
const savedIds: string[] = [];
for (const [index, url] of mediaUrls.entries()) { for (const [index, url] of mediaUrls.entries()) {
const isSelected = hasExplicitSelection const isSelected = hasExplicitSelection
? body.selected[index] === true ? body.selected[index] === true
@ -569,7 +581,7 @@ export default new Elysia({ prefix: "/post" })
mediaUrl: url, mediaUrl: url,
mediaIndex: index, mediaIndex: index,
createDocument: async () => { createDocument: async () => {
await MediaUpload.create({ return await MediaUpload.create({
type: "twitter", type: "twitter",
tweet: tweetWithoutMedia, tweet: tweetWithoutMedia,
mediaIndex: index, mediaIndex: index,
@ -593,6 +605,10 @@ export default new Elysia({ prefix: "/post" })
continue; continue;
} }
if (result.id) {
savedIds.push(result.id);
}
if (result.created) { if (result.created) {
await saveTags(normalizedTags); await saveTags(normalizedTags);
savedCount += 1; savedCount += 1;
@ -643,6 +659,7 @@ export default new Elysia({ prefix: "/post" })
return uploadOk("업로드가 완료되었습니다.", { return uploadOk("업로드가 완료되었습니다.", {
savedCount, savedCount,
failedCount, failedCount,
ids: savedIds,
}); });
} catch (error) { } catch (error) {
console.error(`[Upload aborted] requestId=${requestId} key=${uploadKey}`, error); console.error(`[Upload aborted] requestId=${requestId} key=${uploadKey}`, error);

View file

@ -2,6 +2,7 @@
import { FormEvent, useEffect, useMemo, useState } from "react"; import { FormEvent, useEffect, useMemo, useState } from "react";
import Link from "next/link"; import Link from "next/link";
import { useRouter } from "next/navigation";
import Header from "../../components/header"; import Header from "../../components/header";
type SourceType = "twitter" | "pixiv"; type SourceType = "twitter" | "pixiv";
@ -32,6 +33,7 @@ type UploadApiResponse = {
message: string; message: string;
savedCount?: number; savedCount?: number;
failedCount?: number; failedCount?: number;
ids?: string[];
}; };
type ExistsApiResponse = { type ExistsApiResponse = {
@ -63,6 +65,7 @@ function splitTags(text: string) {
} }
export default function AddPage() { export default function AddPage() {
const router = useRouter();
const [url, setUrl] = useState(""); const [url, setUrl] = useState("");
const [author, setAuthor] = useState(""); const [author, setAuthor] = useState("");
const [tagsText, setTagsText] = useState(""); const [tagsText, setTagsText] = useState("");
@ -313,6 +316,10 @@ export default function AddPage() {
} }
setSuccess(message); setSuccess(message);
if (data?.ids && data.ids.length > 0) {
router.push(`/detail/${data.ids[0]}`);
}
} catch (submitError) { } catch (submitError) {
setError(submitError instanceof Error ? submitError.message : "업로드에 실패했습니다."); setError(submitError instanceof Error ? submitError.message : "업로드에 실패했습니다.");
} finally { } finally {

View file

@ -214,17 +214,19 @@ export default function App() {
async function copyImage(photo: GalleryPhoto) { async function copyImage(photo: GalleryPhoto) {
try { try {
if (typeof ClipboardItem !== "undefined" && navigator.clipboard?.write) { const response = await fetch(photo.src);
const response = await fetch(photo.src, { cache: "no-store" }); if (!response.ok) throw new Error("Failed to fetch image");
const blob = await response.blob(); const blob = await response.blob();
await navigator.clipboard.write([new ClipboardItem({ [blob.type || "image/png"]: blob })]); const clipboardItem = new ClipboardItem({
} else if (navigator.clipboard?.writeText) { [blob.type]: blob
await navigator.clipboard.writeText(photo.src); });
}
} catch { await navigator.clipboard.write([clipboardItem]);
if (navigator.clipboard?.writeText) { alert("이미지가 클립보드에 복사되었습니다!");
await navigator.clipboard.writeText(photo.src); } catch (error) {
} console.error("Failed to copy image:", error);
alert("이미지 복사에 실패했습니다. 브라우저 호환성 문제일 수 있습니다.");
} }
} }