feat : copy youtube studio
This commit is contained in:
+23
-4
@@ -16,6 +16,24 @@ type ParsedForm = ReturnType<typeof parseCommentForm>;
|
||||
type Upload = { id: string; objectKey: string; mimeType: string; byteSize: number };
|
||||
type Writer = Parameters<Parameters<ReturnType<typeof getDb>["transaction"]>[0]>[0];
|
||||
|
||||
async function uploadCommentImage(storage: Awaited<ReturnType<typeof getMediaStorage>>, image: Upload, bytes: Uint8Array) {
|
||||
for (let attempt = 0; ; attempt++) {
|
||||
try {
|
||||
// Retry the same object key, never the database mutation.
|
||||
await storage.write(image.objectKey, bytes, { type: image.mimeType, acl: "public-read", retry: 0 });
|
||||
return;
|
||||
} catch (cause) {
|
||||
const error = cause as { code?: string; name?: string; statusCode?: number } | null;
|
||||
const transient = error && (
|
||||
["ECONNRESET", "ECONNREFUSED", "EPIPE", "ETIMEDOUT", "ConnectionClosed", "SocketClosed", "InternalError", "SlowDown", "ServiceUnavailable", "RequestTimeout"].includes(error.code ?? "") ||
|
||||
error.name === "TimeoutError" || error.statusCode === 429 || (error.statusCode !== undefined && error.statusCode >= 500)
|
||||
);
|
||||
if (!transient || attempt === 2) throw new HttpError(503, "comment-image-upload-failed");
|
||||
await new Promise((resolve) => setTimeout(resolve, 200 * (attempt + 1)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function saveRevision(tx: Pick<Writer, "insert" | "select">, id: string, version: number, form: ParsedForm, uploaded: Upload[]) {
|
||||
if (form.keep.length) {
|
||||
const own = await tx.select({ id: commentAttachments.id }).from(commentAttachments)
|
||||
@@ -45,18 +63,19 @@ export async function publishComment(request: Request, viewer: CommentViewer, op
|
||||
const image = { id: crypto.randomUUID(), objectKey: `comments/${crypto.randomUUID()}`, mimeType: file.type, byteSize: file.size };
|
||||
uploaded.push(image);
|
||||
// Browser images load directly from the configured S3 public CDN.
|
||||
await storage!.write(image.objectKey, bytes, { type: file.type, acl: "public-read" });
|
||||
await uploadCommentImage(storage!, image, bytes);
|
||||
}
|
||||
if (options.id) {
|
||||
await withCommentLock(options.id, viewer, async (tx, context) => {
|
||||
const version = await withCommentLock(options.id, viewer, async (tx, context) => {
|
||||
const c = context.comment;
|
||||
if (c.authorId !== viewer.id) throw new HttpError(403, "not-comment-author");
|
||||
if (c.deletedAt || c.hidden || context.rootHidden) throw new HttpError(409, "comment-unavailable");
|
||||
if (c.version !== form.version) throw new HttpError(409, "comment-edited-reload");
|
||||
await saveRevision(tx, c.id, c.version + 1, form, uploaded);
|
||||
await tx.update(comments).set({ version: c.version + 1 }).where(eq(comments.id, c.id));
|
||||
return c.version + 1;
|
||||
});
|
||||
return { id: options.id };
|
||||
return { id: options.id, version };
|
||||
}
|
||||
const thread = await ensureCommentThread(options.target!, viewer);
|
||||
return await getDb().transaction(async (tx) => {
|
||||
@@ -72,7 +91,7 @@ export async function publishComment(request: Request, viewer: CommentViewer, op
|
||||
}
|
||||
const [comment] = await tx.insert(comments).values({ threadId: thread.id, authorId: viewer.id, rootId, replyToId: form.replyToId }).returning({ id: comments.id });
|
||||
await saveRevision(tx, comment.id, 1, form, uploaded);
|
||||
return { id: comment.id };
|
||||
return { id: comment.id, version: 1 };
|
||||
});
|
||||
} catch (cause) {
|
||||
if (uploaded.length) {
|
||||
|
||||
Reference in New Issue
Block a user