v3: allow users to edit dump title
All checks were successful
Build and Publish Docker Image / build-and-push (push) Successful in 2m54s

This commit is contained in:
khannurien
2026-06-21 12:57:46 +00:00
parent 34933a3d4f
commit 888aae45cf
12 changed files with 322 additions and 51 deletions

View File

@@ -420,6 +420,7 @@ export function isCreateUrlDumpRequest(
export interface UpdateDumpRequest {
url?: string;
title?: string;
comment?: string;
isPrivate?: boolean;
}
@@ -428,6 +429,11 @@ export function isUpdateDumpRequest(obj: unknown): obj is UpdateDumpRequest {
if (!obj || typeof obj !== "object") return false;
const o = obj as Record<string, unknown>;
if ("url" in o && typeof o.url !== "string" && o.url !== null) return false;
if ("title" in o) {
if (typeof o.title !== "string") return false;
const trimmed = (o.title as string).trim();
if (!trimmed || trimmed.length > VALIDATION.DUMP_TITLE_MAX) return false;
}
if (
"comment" in o && typeof o.comment !== "string" && o.comment !== null
) return false;

View File

@@ -39,6 +39,7 @@ router.post(
const formData = await ctx.request.body.formData();
const file = formData.get("file");
const comment = formData.get("comment");
const title = formData.get("title");
const isPrivate = formData.get("isPrivate") === "true";
if (!(file instanceof File)) {
@@ -54,6 +55,7 @@ router.post(
typeof comment === "string" && comment ? comment : undefined,
userId,
isPrivate,
typeof title === "string" && title ? title : undefined,
);
} else {
const body = await ctx.request.body.json();
@@ -109,6 +111,7 @@ router.put("/:dumpId/file", authMiddleware, async (ctx) => {
const formData = await ctx.request.body.formData();
const file = formData.get("file");
const comment = formData.get("comment");
const title = formData.get("title");
if (!(file instanceof File)) {
throw new APIException(
@@ -122,6 +125,7 @@ router.put("/:dumpId/file", authMiddleware, async (ctx) => {
dumpId,
file,
typeof comment === "string" && comment ? comment : undefined,
typeof title === "string" && title ? title : undefined,
);
const responseBody: APIResponse<Dump> = { success: true, data: updatedDump };
ctx.response.body = responseBody;

View File

@@ -109,6 +109,7 @@ export async function createFileDump(
comment: string | undefined,
userId: string,
isPrivate = false,
title?: string,
): Promise<Dump> {
if (!isAllowedMime(file.type)) {
throw new APIException(
@@ -127,7 +128,8 @@ export async function createFileDump(
const dumpId = crypto.randomUUID();
const createdAt = new Date();
const slug = makeSlug(file.name, dumpId);
const finalTitle = title?.trim() || file.name;
const slug = makeSlug(finalTitle, dumpId);
await Deno.mkdir(DUMPS_DIR, { recursive: true });
const data = new Uint8Array(await file.arrayBuffer());
@@ -141,7 +143,7 @@ export async function createFileDump(
).run(
dumpId,
"file",
file.name,
finalTitle,
slug,
comment ?? null,
userId,
@@ -160,7 +162,7 @@ export async function createFileDump(
const dump: Dump = {
id: dumpId,
kind: "file",
title: file.name,
title: finalTitle,
slug,
comment,
userId,
@@ -174,10 +176,10 @@ export async function createFileDump(
};
if (!isPrivate) {
broadcastNewDump(dump);
notifyUserFollowersNewDump(userId, dumpId, file.name);
notifyUserFollowersNewDump(userId, dumpId, finalTitle);
}
if (comment) {
notifyMentions(userId, comment, "dump", dumpId, file.name);
notifyMentions(userId, comment, "dump", dumpId, finalTitle);
linkAttachments(comment, dumpId);
}
return dump;
@@ -285,10 +287,14 @@ export async function updateDump(
const now = new Date();
// File dumps: only comment and isPrivate are editable
// File dumps: title, comment and isPrivate are editable
if (dump.kind === "file") {
const title = request.title?.trim() || dump.title;
const slug = title !== dump.title ? makeSlug(title, dumpId) : dump.slug;
const updatedDump: Dump = {
...dump,
title,
slug,
comment: "comment" in request
? (request.comment ?? undefined)
: dump.comment,
@@ -298,8 +304,10 @@ export async function updateDump(
updatedAt: now,
};
db.prepare(
`UPDATE dumps SET comment = ?, is_private = ?, updated_at = ? WHERE id = ?;`,
`UPDATE dumps SET title = ?, slug = ?, comment = ?, is_private = ?, updated_at = ? WHERE id = ?;`,
).run(
updatedDump.title,
updatedDump.slug ?? null,
updatedDump.comment ?? null,
updatedDump.isPrivate ? 1 : 0,
now.toISOString(),
@@ -334,7 +342,11 @@ export async function updateDump(
title = richContent?.title ?? titleFromUrl(newUrl);
}
const newSlug = makeSlug(title, dumpId);
if (request.title?.trim()) {
title = request.title.trim();
}
const newSlug = title !== dump.title ? makeSlug(title, dumpId) : dump.slug;
const updatedDump: Dump = {
...dump,
title,
@@ -387,6 +399,7 @@ export async function replaceFileDump(
dumpId: string,
file: File,
comment: string | undefined,
title?: string,
): Promise<Dump> {
if (!isAllowedMime(file.type)) {
throw new APIException(
@@ -415,13 +428,16 @@ export async function replaceFileDump(
await Deno.writeFile(filePath, data);
const now = new Date();
const newSlug = makeSlug(file.name, dumpId);
const finalTitle = title?.trim() || dump.title;
const newSlug = finalTitle !== dump.title
? makeSlug(finalTitle, dumpId)
: dump.slug;
try {
db.prepare(
`UPDATE dumps SET title = ?, slug = ?, file_name = ?, file_mime = ?, file_size = ?, comment = ?, updated_at = ? WHERE id = ?;`,
).run(
file.name,
newSlug,
finalTitle,
newSlug ?? null,
file.name,
file.type,
file.size,
@@ -437,12 +453,12 @@ export async function replaceFileDump(
}
if (comment) {
notifyMentions(dump.userId, comment, "dump", dumpId, file.name);
notifyMentions(dump.userId, comment, "dump", dumpId, finalTitle);
linkAttachments(comment, dumpId);
}
return {
...dump,
title: file.name,
title: finalTitle,
slug: newSlug,
fileName: file.name,
fileMime: file.type,

View File

@@ -167,7 +167,7 @@ export async function updateUser(
);
if (userResult.changes === 0) {
throw new APIException(APIErrorCode.NOT_FOUND, 404, "Dump not found");
throw new APIException(APIErrorCode.NOT_FOUND, 404, "User not found");
}
if (updatedUser.description) {