mirror of
https://github.com/versia-pub/server.git
synced 2025-12-06 16:38:19 +01:00
refactor(api): 🎨 Simplify expressions
This commit is contained in:
parent
36d70fb612
commit
98f3ab23d8
|
|
@ -6,9 +6,7 @@ import { config } from "config-manager";
|
||||||
import { and, eq } from "drizzle-orm";
|
import { and, eq } from "drizzle-orm";
|
||||||
import type { Hono } from "hono";
|
import type { Hono } from "hono";
|
||||||
import ISO6391 from "iso-639-1";
|
import ISO6391 from "iso-639-1";
|
||||||
import { MediaBackendType } from "media-manager";
|
import { MediaBackend } from "media-manager";
|
||||||
import type { MediaBackend } from "media-manager";
|
|
||||||
import { LocalMediaBackend, S3MediaBackend } from "media-manager";
|
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
import { getUrl } from "~/database/entities/attachment";
|
import { getUrl } from "~/database/entities/attachment";
|
||||||
import { parseEmojis } from "~/database/entities/emoji";
|
import { parseEmojis } from "~/database/entities/emoji";
|
||||||
|
|
@ -127,19 +125,10 @@ export default (app: Hono) =>
|
||||||
display_name ?? "",
|
display_name ?? "",
|
||||||
);
|
);
|
||||||
|
|
||||||
let mediaManager: MediaBackend;
|
const mediaManager = await MediaBackend.fromBackendType(
|
||||||
|
config.media.backend,
|
||||||
switch (config.media.backend as MediaBackendType) {
|
config,
|
||||||
case MediaBackendType.Local:
|
);
|
||||||
mediaManager = new LocalMediaBackend(config);
|
|
||||||
break;
|
|
||||||
case MediaBackendType.S3:
|
|
||||||
mediaManager = new S3MediaBackend(config);
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
// TODO: Replace with logger
|
|
||||||
throw new Error("Invalid media backend");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (display_name) {
|
if (display_name) {
|
||||||
// Check if display name doesnt match filters
|
// Check if display name doesnt match filters
|
||||||
|
|
|
||||||
|
|
@ -3,9 +3,7 @@ import { errorResponse, jsonResponse, response } from "@/response";
|
||||||
import { zValidator } from "@hono/zod-validator";
|
import { zValidator } from "@hono/zod-validator";
|
||||||
import { config } from "config-manager";
|
import { config } from "config-manager";
|
||||||
import type { Hono } from "hono";
|
import type { Hono } from "hono";
|
||||||
import type { MediaBackend } from "media-manager";
|
import { MediaBackend } from "media-manager";
|
||||||
import { MediaBackendType } from "media-manager";
|
|
||||||
import { LocalMediaBackend, S3MediaBackend } from "media-manager";
|
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
import { getUrl } from "~/database/entities/attachment";
|
import { getUrl } from "~/database/entities/attachment";
|
||||||
import { RolePermissions } from "~/drizzle/schema";
|
import { RolePermissions } from "~/drizzle/schema";
|
||||||
|
|
@ -74,19 +72,10 @@ export default (app: Hono) =>
|
||||||
|
|
||||||
let thumbnailUrl = attachment.data.thumbnailUrl;
|
let thumbnailUrl = attachment.data.thumbnailUrl;
|
||||||
|
|
||||||
let mediaManager: MediaBackend;
|
const mediaManager = await MediaBackend.fromBackendType(
|
||||||
|
config.media.backend,
|
||||||
switch (config.media.backend as MediaBackendType) {
|
config,
|
||||||
case MediaBackendType.Local:
|
);
|
||||||
mediaManager = new LocalMediaBackend(config);
|
|
||||||
break;
|
|
||||||
case MediaBackendType.S3:
|
|
||||||
mediaManager = new S3MediaBackend(config);
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
// TODO: Replace with logger
|
|
||||||
throw new Error("Invalid media backend");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (thumbnail) {
|
if (thumbnail) {
|
||||||
const { path } = await mediaManager.addFile(thumbnail);
|
const { path } = await mediaManager.addFile(thumbnail);
|
||||||
|
|
|
||||||
|
|
@ -4,9 +4,7 @@ import { zValidator } from "@hono/zod-validator";
|
||||||
import { encode } from "blurhash";
|
import { encode } from "blurhash";
|
||||||
import { config } from "config-manager";
|
import { config } from "config-manager";
|
||||||
import type { Hono } from "hono";
|
import type { Hono } from "hono";
|
||||||
import { MediaBackendType } from "media-manager";
|
import { MediaBackend } from "media-manager";
|
||||||
import type { MediaBackend } from "media-manager";
|
|
||||||
import { LocalMediaBackend, S3MediaBackend } from "media-manager";
|
|
||||||
import sharp from "sharp";
|
import sharp from "sharp";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
import { getUrl } from "~/database/entities/attachment";
|
import { getUrl } from "~/database/entities/attachment";
|
||||||
|
|
@ -101,19 +99,10 @@ export default (app: Hono) =>
|
||||||
|
|
||||||
let url = "";
|
let url = "";
|
||||||
|
|
||||||
let mediaManager: MediaBackend;
|
const mediaManager = await MediaBackend.fromBackendType(
|
||||||
|
config.media.backend,
|
||||||
switch (config.media.backend as MediaBackendType) {
|
config,
|
||||||
case MediaBackendType.Local:
|
);
|
||||||
mediaManager = new LocalMediaBackend(config);
|
|
||||||
break;
|
|
||||||
case MediaBackendType.S3:
|
|
||||||
mediaManager = new S3MediaBackend(config);
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
// TODO: Replace with logger
|
|
||||||
throw new Error("Invalid media backend");
|
|
||||||
}
|
|
||||||
|
|
||||||
const { path } = await mediaManager.addFile(file);
|
const { path } = await mediaManager.addFile(file);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -12,8 +12,8 @@ import type { Hono } from "hono";
|
||||||
import ISO6391 from "iso-639-1";
|
import ISO6391 from "iso-639-1";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
import { undoFederationRequest } from "~/database/entities/federation";
|
import { undoFederationRequest } from "~/database/entities/federation";
|
||||||
import { db } from "~/drizzle/db";
|
|
||||||
import { RolePermissions } from "~/drizzle/schema";
|
import { RolePermissions } from "~/drizzle/schema";
|
||||||
|
import { Attachment } from "~/packages/database-interface/attachment";
|
||||||
import { Note } from "~/packages/database-interface/note";
|
import { Note } from "~/packages/database-interface/note";
|
||||||
|
|
||||||
export const meta = applyConfig({
|
export const meta = applyConfig({
|
||||||
|
|
@ -40,43 +40,58 @@ export const schemas = {
|
||||||
param: z.object({
|
param: z.object({
|
||||||
id: z.string().regex(idValidator),
|
id: z.string().regex(idValidator),
|
||||||
}),
|
}),
|
||||||
form: z.object({
|
form: z
|
||||||
status: z.string().max(config.validation.max_note_size).optional(),
|
.object({
|
||||||
content_type: z.string().optional().default("text/plain"),
|
status: z
|
||||||
media_ids: z
|
.string()
|
||||||
.array(z.string().regex(idValidator))
|
.max(config.validation.max_note_size)
|
||||||
.max(config.validation.max_media_attachments)
|
.refine(
|
||||||
.optional(),
|
(s) =>
|
||||||
spoiler_text: z.string().max(255).optional(),
|
!config.filters.note_content.some((filter) =>
|
||||||
sensitive: z
|
s.match(filter),
|
||||||
.string()
|
),
|
||||||
.transform((v) => ["true", "1", "on"].includes(v.toLowerCase()))
|
"Status contains blocked words",
|
||||||
.or(z.boolean())
|
)
|
||||||
.optional(),
|
.optional(),
|
||||||
language: z
|
content_type: z.string().optional().default("text/plain"),
|
||||||
.enum(ISO6391.getAllCodes() as [string, ...string[]])
|
media_ids: z
|
||||||
.optional(),
|
.array(z.string().regex(idValidator))
|
||||||
"poll[options]": z
|
.max(config.validation.max_media_attachments)
|
||||||
.array(z.string().max(config.validation.max_poll_option_size))
|
.default([]),
|
||||||
.max(config.validation.max_poll_options)
|
spoiler_text: z.string().max(255).optional(),
|
||||||
.optional(),
|
sensitive: z
|
||||||
"poll[expires_in]": z.coerce
|
.string()
|
||||||
.number()
|
.transform((v) => ["true", "1", "on"].includes(v.toLowerCase()))
|
||||||
.int()
|
.or(z.boolean())
|
||||||
.min(config.validation.min_poll_duration)
|
.optional(),
|
||||||
.max(config.validation.max_poll_duration)
|
language: z
|
||||||
.optional(),
|
.enum(ISO6391.getAllCodes() as [string, ...string[]])
|
||||||
"poll[multiple]": z
|
.optional(),
|
||||||
.string()
|
"poll[options]": z
|
||||||
.transform((v) => ["true", "1", "on"].includes(v.toLowerCase()))
|
.array(z.string().max(config.validation.max_poll_option_size))
|
||||||
.or(z.boolean())
|
.max(config.validation.max_poll_options)
|
||||||
.optional(),
|
.optional(),
|
||||||
"poll[hide_totals]": z
|
"poll[expires_in]": z.coerce
|
||||||
.string()
|
.number()
|
||||||
.transform((v) => ["true", "1", "on"].includes(v.toLowerCase()))
|
.int()
|
||||||
.or(z.boolean())
|
.min(config.validation.min_poll_duration)
|
||||||
.optional(),
|
.max(config.validation.max_poll_duration)
|
||||||
}),
|
.optional(),
|
||||||
|
"poll[multiple]": z
|
||||||
|
.string()
|
||||||
|
.transform((v) => ["true", "1", "on"].includes(v.toLowerCase()))
|
||||||
|
.or(z.boolean())
|
||||||
|
.optional(),
|
||||||
|
"poll[hide_totals]": z
|
||||||
|
.string()
|
||||||
|
.transform((v) => ["true", "1", "on"].includes(v.toLowerCase()))
|
||||||
|
.or(z.boolean())
|
||||||
|
.optional(),
|
||||||
|
})
|
||||||
|
.refine(
|
||||||
|
(obj) => !(obj.media_ids.length > 0 && obj["poll[options]"]),
|
||||||
|
"Cannot attach poll to media",
|
||||||
|
),
|
||||||
};
|
};
|
||||||
|
|
||||||
export default (app: Hono) =>
|
export default (app: Hono) =>
|
||||||
|
|
@ -91,91 +106,65 @@ export default (app: Hono) =>
|
||||||
const { id } = context.req.valid("param");
|
const { id } = context.req.valid("param");
|
||||||
const { user } = context.req.valid("header");
|
const { user } = context.req.valid("header");
|
||||||
|
|
||||||
const foundStatus = await Note.fromId(id, user?.id);
|
|
||||||
|
|
||||||
if (!foundStatus?.isViewableByUser(user)) {
|
|
||||||
return errorResponse("Record not found", 404);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (context.req.method === "GET") {
|
|
||||||
return jsonResponse(await foundStatus.toApi(user));
|
|
||||||
}
|
|
||||||
if (context.req.method === "DELETE") {
|
|
||||||
if (foundStatus.author.id !== user?.id) {
|
|
||||||
return errorResponse("Unauthorized", 401);
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: Delete and redraft
|
|
||||||
|
|
||||||
await foundStatus.delete();
|
|
||||||
|
|
||||||
await user.federateToFollowers(
|
|
||||||
undoFederationRequest(user, foundStatus.getUri()),
|
|
||||||
);
|
|
||||||
|
|
||||||
return jsonResponse(await foundStatus.toApi(user), 200);
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: Polls
|
// TODO: Polls
|
||||||
const {
|
const {
|
||||||
status: statusText,
|
status: statusText,
|
||||||
content_type,
|
content_type,
|
||||||
"poll[options]": options,
|
|
||||||
media_ids,
|
media_ids,
|
||||||
spoiler_text,
|
spoiler_text,
|
||||||
sensitive,
|
sensitive,
|
||||||
} = context.req.valid("form");
|
} = context.req.valid("form");
|
||||||
|
|
||||||
if (!(statusText || (media_ids && media_ids.length > 0))) {
|
const note = await Note.fromId(id, user?.id);
|
||||||
return errorResponse(
|
|
||||||
"Status is required unless media is attached",
|
if (!note?.isViewableByUser(user)) {
|
||||||
422,
|
return errorResponse("Record not found", 404);
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (media_ids && media_ids.length > 0 && options) {
|
switch (context.req.method) {
|
||||||
return errorResponse(
|
case "GET": {
|
||||||
"Cannot attach poll to post with media",
|
return jsonResponse(await note.toApi(user));
|
||||||
422,
|
}
|
||||||
);
|
case "DELETE": {
|
||||||
}
|
if (note.author.id !== user?.id) {
|
||||||
|
return errorResponse("Unauthorized", 401);
|
||||||
|
}
|
||||||
|
|
||||||
if (
|
// TODO: Delete and redraft
|
||||||
config.filters.note_content.some((filter) =>
|
|
||||||
statusText?.match(filter),
|
|
||||||
)
|
|
||||||
) {
|
|
||||||
return errorResponse("Status contains blocked words", 422);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (media_ids && media_ids.length > 0) {
|
await note.delete();
|
||||||
const foundAttachments = await db.query.Attachments.findMany({
|
|
||||||
where: (attachment, { inArray }) =>
|
|
||||||
inArray(attachment.id, media_ids),
|
|
||||||
});
|
|
||||||
|
|
||||||
if (foundAttachments.length !== (media_ids ?? []).length) {
|
await user.federateToFollowers(
|
||||||
return errorResponse("Invalid media IDs", 422);
|
undoFederationRequest(user, note.getUri()),
|
||||||
|
);
|
||||||
|
|
||||||
|
return jsonResponse(await note.toApi(user), 200);
|
||||||
|
}
|
||||||
|
case "PUT": {
|
||||||
|
if (media_ids.length > 0) {
|
||||||
|
const foundAttachments =
|
||||||
|
await Attachment.fromIds(media_ids);
|
||||||
|
|
||||||
|
if (foundAttachments.length !== media_ids.length) {
|
||||||
|
return errorResponse("Invalid media IDs", 422);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const newNote = await note.updateFromData({
|
||||||
|
content: statusText
|
||||||
|
? {
|
||||||
|
[content_type]: {
|
||||||
|
content: statusText,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
: undefined,
|
||||||
|
isSensitive: sensitive,
|
||||||
|
spoilerText: spoiler_text,
|
||||||
|
mediaAttachments: media_ids,
|
||||||
|
});
|
||||||
|
|
||||||
|
return jsonResponse(await newNote.toApi(user));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const newNote = await foundStatus.updateFromData({
|
|
||||||
content: statusText
|
|
||||||
? {
|
|
||||||
[content_type]: {
|
|
||||||
content: statusText,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
: undefined,
|
|
||||||
isSensitive: sensitive,
|
|
||||||
spoilerText: spoiler_text,
|
|
||||||
mediaAttachments: media_ids,
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!newNote) {
|
|
||||||
return errorResponse("Failed to update status", 500);
|
|
||||||
}
|
|
||||||
|
|
||||||
return jsonResponse(await newNote.toApi(user));
|
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -28,7 +28,9 @@ describe(meta.route, () => {
|
||||||
const response = await sendTestRequest(
|
const response = await sendTestRequest(
|
||||||
new Request(new URL(meta.route, config.http.base_url), {
|
new Request(new URL(meta.route, config.http.base_url), {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
body: new URLSearchParams(),
|
body: new URLSearchParams({
|
||||||
|
status: "Hello, world!",
|
||||||
|
}),
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -6,8 +6,8 @@ import type { Hono } from "hono";
|
||||||
import ISO6391 from "iso-639-1";
|
import ISO6391 from "iso-639-1";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
import { federateNote } from "~/database/entities/status";
|
import { federateNote } from "~/database/entities/status";
|
||||||
import { db } from "~/drizzle/db";
|
|
||||||
import { RolePermissions } from "~/drizzle/schema";
|
import { RolePermissions } from "~/drizzle/schema";
|
||||||
|
import { Attachment } from "~/packages/database-interface/attachment";
|
||||||
import { Note } from "~/packages/database-interface/note";
|
import { Note } from "~/packages/database-interface/note";
|
||||||
|
|
||||||
export const meta = applyConfig({
|
export const meta = applyConfig({
|
||||||
|
|
@ -26,61 +26,81 @@ export const meta = applyConfig({
|
||||||
});
|
});
|
||||||
|
|
||||||
export const schemas = {
|
export const schemas = {
|
||||||
form: z.object({
|
form: z
|
||||||
status: z
|
.object({
|
||||||
.string()
|
status: z
|
||||||
.max(config.validation.max_note_size)
|
.string()
|
||||||
.trim()
|
.max(config.validation.max_note_size)
|
||||||
.optional(),
|
.trim()
|
||||||
// TODO: Add regex to validate
|
.refine(
|
||||||
content_type: z.string().optional().default("text/plain"),
|
(s) =>
|
||||||
media_ids: z
|
!config.filters.note_content.some((filter) =>
|
||||||
.array(z.string().uuid())
|
s.match(filter),
|
||||||
.max(config.validation.max_media_attachments)
|
),
|
||||||
.optional(),
|
"Status contains blocked words",
|
||||||
spoiler_text: z.string().max(255).trim().optional(),
|
)
|
||||||
sensitive: z
|
.optional(),
|
||||||
.string()
|
// TODO: Add regex to validate
|
||||||
.transform((v) => ["true", "1", "on"].includes(v.toLowerCase()))
|
content_type: z.string().optional().default("text/plain"),
|
||||||
.or(z.boolean())
|
media_ids: z
|
||||||
.optional(),
|
.array(z.string().uuid())
|
||||||
language: z
|
.max(config.validation.max_media_attachments)
|
||||||
.enum(ISO6391.getAllCodes() as [string, ...string[]])
|
.default([]),
|
||||||
.optional(),
|
spoiler_text: z.string().max(255).trim().optional(),
|
||||||
"poll[options]": z
|
sensitive: z
|
||||||
.array(z.string().max(config.validation.max_poll_option_size))
|
.string()
|
||||||
.max(config.validation.max_poll_options)
|
.transform((v) => ["true", "1", "on"].includes(v.toLowerCase()))
|
||||||
.optional(),
|
.or(z.boolean())
|
||||||
"poll[expires_in]": z.coerce
|
.optional(),
|
||||||
.number()
|
language: z
|
||||||
.int()
|
.enum(ISO6391.getAllCodes() as [string, ...string[]])
|
||||||
.min(config.validation.min_poll_duration)
|
.optional(),
|
||||||
.max(config.validation.max_poll_duration)
|
"poll[options]": z
|
||||||
.optional(),
|
.array(z.string().max(config.validation.max_poll_option_size))
|
||||||
"poll[multiple]": z
|
.max(config.validation.max_poll_options)
|
||||||
.string()
|
.optional(),
|
||||||
.transform((v) => ["true", "1", "on"].includes(v.toLowerCase()))
|
"poll[expires_in]": z.coerce
|
||||||
.or(z.boolean())
|
.number()
|
||||||
.optional(),
|
.int()
|
||||||
"poll[hide_totals]": z
|
.min(config.validation.min_poll_duration)
|
||||||
.string()
|
.max(config.validation.max_poll_duration)
|
||||||
.transform((v) => ["true", "1", "on"].includes(v.toLowerCase()))
|
.optional(),
|
||||||
.or(z.boolean())
|
"poll[multiple]": z
|
||||||
.optional(),
|
.string()
|
||||||
in_reply_to_id: z.string().uuid().optional().nullable(),
|
.transform((v) => ["true", "1", "on"].includes(v.toLowerCase()))
|
||||||
quote_id: z.string().uuid().optional().nullable(),
|
.or(z.boolean())
|
||||||
visibility: z
|
.optional(),
|
||||||
.enum(["public", "unlisted", "private", "direct"])
|
"poll[hide_totals]": z
|
||||||
.optional()
|
.string()
|
||||||
.default("public"),
|
.transform((v) => ["true", "1", "on"].includes(v.toLowerCase()))
|
||||||
scheduled_at: z.string().optional().nullable(),
|
.or(z.boolean())
|
||||||
local_only: z
|
.optional(),
|
||||||
.string()
|
in_reply_to_id: z.string().uuid().optional().nullable(),
|
||||||
.transform((v) => ["true", "1", "on"].includes(v.toLowerCase()))
|
quote_id: z.string().uuid().optional().nullable(),
|
||||||
.or(z.boolean())
|
visibility: z
|
||||||
.optional()
|
.enum(["public", "unlisted", "private", "direct"])
|
||||||
.default(false),
|
.optional()
|
||||||
}),
|
.default("public"),
|
||||||
|
scheduled_at: z.coerce
|
||||||
|
.date()
|
||||||
|
.min(new Date(), "Scheduled time must be in the future")
|
||||||
|
.optional()
|
||||||
|
.nullable(),
|
||||||
|
local_only: z
|
||||||
|
.string()
|
||||||
|
.transform((v) => ["true", "1", "on"].includes(v.toLowerCase()))
|
||||||
|
.or(z.boolean())
|
||||||
|
.optional()
|
||||||
|
.default(false),
|
||||||
|
})
|
||||||
|
.refine(
|
||||||
|
(obj) => obj.status || obj.media_ids.length > 0,
|
||||||
|
"Status is required unless media is attached",
|
||||||
|
)
|
||||||
|
.refine(
|
||||||
|
(obj) => !(obj.media_ids.length > 0 && obj["poll[options]"]),
|
||||||
|
"Cannot attach poll to media",
|
||||||
|
),
|
||||||
};
|
};
|
||||||
|
|
||||||
export default (app: Hono) =>
|
export default (app: Hono) =>
|
||||||
|
|
@ -100,10 +120,8 @@ export default (app: Hono) =>
|
||||||
const {
|
const {
|
||||||
status,
|
status,
|
||||||
media_ids,
|
media_ids,
|
||||||
"poll[options]": options,
|
|
||||||
in_reply_to_id,
|
in_reply_to_id,
|
||||||
quote_id,
|
quote_id,
|
||||||
scheduled_at,
|
|
||||||
sensitive,
|
sensitive,
|
||||||
spoiler_text,
|
spoiler_text,
|
||||||
visibility,
|
visibility,
|
||||||
|
|
@ -111,68 +129,22 @@ export default (app: Hono) =>
|
||||||
local_only,
|
local_only,
|
||||||
} = context.req.valid("form");
|
} = context.req.valid("form");
|
||||||
|
|
||||||
// Validate status
|
|
||||||
if (!(status || (media_ids && media_ids.length > 0))) {
|
|
||||||
return errorResponse(
|
|
||||||
"Status is required unless media is attached",
|
|
||||||
422,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (media_ids && media_ids.length > 0 && options) {
|
|
||||||
// Disallow poll
|
|
||||||
return errorResponse("Cannot attach poll to media", 422);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (scheduled_at) {
|
|
||||||
if (
|
|
||||||
Number.isNaN(new Date(scheduled_at).getTime()) ||
|
|
||||||
new Date(scheduled_at).getTime() < Date.now()
|
|
||||||
) {
|
|
||||||
return errorResponse(
|
|
||||||
"Scheduled time must be in the future",
|
|
||||||
422,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check if status body doesnt match filters
|
|
||||||
if (
|
|
||||||
config.filters.note_content.some((filter) =>
|
|
||||||
status?.match(filter),
|
|
||||||
)
|
|
||||||
) {
|
|
||||||
return errorResponse("Status contains blocked words", 422);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check if media attachments are all valid
|
// Check if media attachments are all valid
|
||||||
if (media_ids && media_ids.length > 0) {
|
if (media_ids.length > 0) {
|
||||||
const foundAttachments = await db.query.Attachments.findMany({
|
const foundAttachments = await Attachment.fromIds(media_ids);
|
||||||
where: (attachment, { inArray }) =>
|
|
||||||
inArray(attachment.id, media_ids),
|
|
||||||
}).catch(() => []);
|
|
||||||
|
|
||||||
if (foundAttachments.length !== (media_ids ?? []).length) {
|
if (foundAttachments.length !== media_ids.length) {
|
||||||
return errorResponse("Invalid media IDs", 422);
|
return errorResponse("Invalid media IDs", 422);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check that in_reply_to_id and quote_id are real posts if provided
|
// Check that in_reply_to_id and quote_id are real posts if provided
|
||||||
if (in_reply_to_id) {
|
if (in_reply_to_id && !(await Note.fromId(in_reply_to_id))) {
|
||||||
const foundReply = await Note.fromId(in_reply_to_id);
|
return errorResponse("Invalid in_reply_to_id (not found)", 422);
|
||||||
if (!foundReply) {
|
|
||||||
return errorResponse(
|
|
||||||
"Invalid in_reply_to_id (not found)",
|
|
||||||
422,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (quote_id) {
|
if (quote_id && !(await Note.fromId(quote_id))) {
|
||||||
const foundQuote = await Note.fromId(quote_id);
|
return errorResponse("Invalid quote_id (not found)", 422);
|
||||||
if (!foundQuote) {
|
|
||||||
return errorResponse("Invalid quote_id (not found)", 422);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const newNote = await Note.fromData({
|
const newNote = await Note.fromData({
|
||||||
|
|
@ -191,10 +163,6 @@ export default (app: Hono) =>
|
||||||
application: application ?? undefined,
|
application: application ?? undefined,
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!newNote) {
|
|
||||||
return errorResponse("Failed to create status", 500);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!local_only) {
|
if (!local_only) {
|
||||||
await federateNote(newNote);
|
await federateNote(newNote);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,9 +4,7 @@ import { zValidator } from "@hono/zod-validator";
|
||||||
import { encode } from "blurhash";
|
import { encode } from "blurhash";
|
||||||
import { config } from "config-manager";
|
import { config } from "config-manager";
|
||||||
import type { Hono } from "hono";
|
import type { Hono } from "hono";
|
||||||
import type { MediaBackend } from "media-manager";
|
import { MediaBackend } from "media-manager";
|
||||||
import { MediaBackendType } from "media-manager";
|
|
||||||
import { LocalMediaBackend, S3MediaBackend } from "media-manager";
|
|
||||||
import sharp from "sharp";
|
import sharp from "sharp";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
import { getUrl } from "~/database/entities/attachment";
|
import { getUrl } from "~/database/entities/attachment";
|
||||||
|
|
@ -101,19 +99,10 @@ export default (app: Hono) =>
|
||||||
|
|
||||||
let url = "";
|
let url = "";
|
||||||
|
|
||||||
let mediaManager: MediaBackend;
|
const mediaManager = await MediaBackend.fromBackendType(
|
||||||
|
config.media.backend,
|
||||||
switch (config.media.backend as MediaBackendType) {
|
config,
|
||||||
case MediaBackendType.Local:
|
);
|
||||||
mediaManager = new LocalMediaBackend(config);
|
|
||||||
break;
|
|
||||||
case MediaBackendType.S3:
|
|
||||||
mediaManager = new S3MediaBackend(config);
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
// TODO: Replace with logger
|
|
||||||
throw new Error("Invalid media backend");
|
|
||||||
}
|
|
||||||
|
|
||||||
const { path } = await mediaManager.addFile(file);
|
const { path } = await mediaManager.addFile(file);
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue