feat: export posts and sources
This commit is contained in:
@@ -0,0 +1,21 @@
|
||||
import { NextResponse } from "next/server";
|
||||
import { requireUser } from "@/lib/auth";
|
||||
import { db } from "@/lib/db";
|
||||
|
||||
function markdown(post: any) {
|
||||
const tags = (() => { try { return JSON.parse(post.tags_json || "[]"); } catch { return []; } })();
|
||||
const attachments = (() => { try { return JSON.parse(post.attachments_json || "[]"); } catch { return []; } })();
|
||||
const quote = (value: unknown) => JSON.stringify(value ?? "");
|
||||
const attachmentList = attachments.length ? `\n\n## Attachments\n${attachments.map((item: any) => `- [${item.filename || item.name || "attachment"}](${item.url || item.externalLink || ""})`).join("\n")}` : "";
|
||||
return `---\nid: ${post.id}\norigin: ${quote(post.origin)}\nvisibility: ${quote(post.visibility)}\npublished_at: ${quote(post.remote_created_at || post.created_at)}\ntags: ${JSON.stringify(tags)}\nremote_url: ${quote(post.remote_url)}\n---\n\n${post.content}${attachmentList}\n`;
|
||||
}
|
||||
|
||||
export async function GET(request: Request, { params }: { params: Promise<{ id: string }> }) {
|
||||
const user = await requireUser(); const { id: rawId } = await params; const post = db.prepare("SELECT p.*,s.name AS source_name FROM posts p LEFT JOIN sources s ON s.id=p.source_id WHERE p.id=?").get(Number(rawId)) as any;
|
||||
if (!post) return NextResponse.json({ error: "Not found" }, { status: 404 });
|
||||
const permitted = post.author_id === user.id || (post.source_id && db.prepare("SELECT 1 FROM source_members WHERE source_id=? AND user_id=?").get(post.source_id, user.id));
|
||||
if (!permitted) return NextResponse.json({ error: "Forbidden" }, { status: 403 });
|
||||
const format = new URL(request.url).searchParams.get("format") === "markdown" ? "markdown" : "json";
|
||||
const body = format === "markdown" ? markdown(post) : JSON.stringify({ version: 1, exportedAt: new Date().toISOString(), post: { ...post, tags: JSON.parse(post.tags_json || "[]"), attachments: JSON.parse(post.attachments_json || "[]") } }, null, 2);
|
||||
return new NextResponse(body, { headers: { "Content-Type": format === "markdown" ? "text/markdown; charset=utf-8" : "application/json; charset=utf-8", "Content-Disposition": `attachment; filename="mebbling-post-${post.id}.${format === "markdown" ? "md" : "json"}"` } });
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
import { NextResponse } from "next/server";
|
||||
import { requireUser } from "@/lib/auth";
|
||||
import { db } from "@/lib/db";
|
||||
|
||||
function postMarkdown(post: any) { return `## ${post.remote_created_at || post.created_at}\n\n${post.content}\n`; }
|
||||
|
||||
export async function GET(request: Request, { params }: { params: Promise<{ id: string }> }) {
|
||||
const user = await requireUser(); const { id: rawId } = await params; const id = Number(rawId); const source = db.prepare("SELECT id,name,base_url,integration_type,rss_feed_url,created_at FROM sources WHERE id=?").get(id) as any;
|
||||
if (!source) return NextResponse.json({ error: "Not found" }, { status: 404 });
|
||||
if (!db.prepare("SELECT 1 FROM source_members WHERE source_id=? AND user_id=?").get(id, user.id)) return NextResponse.json({ error: "Forbidden" }, { status: 403 });
|
||||
const posts = db.prepare("SELECT id,content,visibility,tags_json,attachments_json,origin,remote_created_at,remote_updated_at,remote_url,created_at,updated_at FROM posts WHERE source_id=? ORDER BY COALESCE(remote_created_at,created_at)").all(id) as any[];
|
||||
const format = new URL(request.url).searchParams.get("format") === "markdown" ? "markdown" : "json";
|
||||
const body = format === "markdown" ? `# ${source.name}\n\n${posts.map(postMarkdown).join("\n---\n\n")}` : JSON.stringify({ version: 1, exportedAt: new Date().toISOString(), source, posts: posts.map((post) => ({ ...post, tags: JSON.parse(post.tags_json || "[]"), attachments: JSON.parse(post.attachments_json || "[]") })) }, null, 2);
|
||||
return new NextResponse(body, { headers: { "Content-Type": format === "markdown" ? "text/markdown; charset=utf-8" : "application/json; charset=utf-8", "Content-Disposition": `attachment; filename="mebbling-source-${id}.${format === "markdown" ? "md" : "json"}"` } });
|
||||
}
|
||||
@@ -7,8 +7,9 @@ import { requireSameOrigin } from "@/lib/security";
|
||||
export async function POST(request: Request, { params }: { params: Promise<{ id: string }> }) {
|
||||
try {
|
||||
requireSameOrigin(request); const user = await requireUser(); const { id: rawId } = await params; const id = Number(rawId);
|
||||
const source = db.prepare("SELECT id FROM sources WHERE id=? AND user_id=?").get(id, user.id);
|
||||
const source = db.prepare("SELECT id,webhook_mode FROM sources WHERE id=? AND user_id=?").get(id, user.id) as { id: number; webhook_mode: string } | undefined;
|
||||
if (!source) return NextResponse.json({ error: "Not found" }, { status: 404 });
|
||||
if (source.webhook_mode === "signed") return NextResponse.json({ error: "這個 Webhook 已由 Memos 自動管理;請重新連接來源以重新建立。" }, { status: 409 });
|
||||
const secret = createWebhookSecret();
|
||||
db.prepare("UPDATE sources SET webhook_secret_hash=? WHERE id=?").run(webhookSecretHash(secret), id);
|
||||
const publicOrigin = (process.env.NEXT_PUBLIC_APP_URL || new URL(request.url).origin).replace(/\/$/, "");
|
||||
|
||||
Reference in New Issue
Block a user