mirror of
https://github.com/versia-pub/server.git
synced 2025-12-06 08:28:19 +01:00
refactor(api): 🏷️ Port more misc endpoints to use new schemas
Some checks failed
Mirror to Codeberg / Mirror (push) Failing after 0s
Some checks failed
Mirror to Codeberg / Mirror (push) Failing after 0s
This commit is contained in:
parent
e3e285571e
commit
247a8fbce3
|
|
@ -1,4 +1,4 @@
|
|||
import { apiRoute, auth, jsonOrForm } from "@/api";
|
||||
import { apiRoute, auth, jsonOrForm, reusedResponses } from "@/api";
|
||||
import { tempmailDomains } from "@/tempmail";
|
||||
import { createRoute, z } from "@hono/zod-openapi";
|
||||
import { User } from "@versia/kit/db";
|
||||
|
|
@ -74,8 +74,9 @@ const route = createRoute({
|
|||
},
|
||||
responses: {
|
||||
200: {
|
||||
description: "Account created",
|
||||
description: "Token for the created account",
|
||||
},
|
||||
401: reusedResponses[401],
|
||||
422: {
|
||||
description: "Validation failed",
|
||||
content: {
|
||||
|
|
@ -346,9 +347,9 @@ export default apiRoute((app) =>
|
|||
}
|
||||
|
||||
await User.fromDataLocal({
|
||||
username: username ?? "",
|
||||
password: password ?? "",
|
||||
email: email ?? "",
|
||||
username: username,
|
||||
password: password,
|
||||
email: email,
|
||||
});
|
||||
|
||||
return context.text("", 200);
|
||||
|
|
|
|||
|
|
@ -1,62 +1,61 @@
|
|||
import { apiRoute, jsonOrForm } from "@/api";
|
||||
import { apiRoute, jsonOrForm, reusedResponses } from "@/api";
|
||||
import { randomString } from "@/math";
|
||||
import { createRoute, z } from "@hono/zod-openapi";
|
||||
import { Application } from "@versia/kit/db";
|
||||
|
||||
const schemas = {
|
||||
json: z.object({
|
||||
client_name: z.string().trim().min(1).max(100),
|
||||
redirect_uris: z
|
||||
.string()
|
||||
.min(0)
|
||||
.max(2000)
|
||||
.url()
|
||||
.or(z.literal("urn:ietf:wg:oauth:2.0:oob")),
|
||||
scopes: z.string().min(1).max(200),
|
||||
website: z
|
||||
.string()
|
||||
.min(0)
|
||||
.max(2000)
|
||||
.url()
|
||||
.optional()
|
||||
// Allow empty websites because Traewelling decides to give an empty
|
||||
// value instead of not providing anything at all
|
||||
.or(z.literal("").transform(() => undefined)),
|
||||
}),
|
||||
};
|
||||
import {
|
||||
Application as ApplicationSchema,
|
||||
CredentialApplication as CredentialApplicationSchema,
|
||||
} from "~/classes/schemas/application";
|
||||
|
||||
const route = createRoute({
|
||||
method: "post",
|
||||
path: "/api/v1/apps",
|
||||
summary: "Create app",
|
||||
description: "Create an OAuth2 app",
|
||||
summary: "Create an application",
|
||||
description: "Create a new application to obtain OAuth2 credentials.",
|
||||
externalDocs: {
|
||||
url: "https://docs.joinmastodon.org/methods/apps/#create",
|
||||
},
|
||||
tags: ["Apps"],
|
||||
middleware: [jsonOrForm()],
|
||||
request: {
|
||||
body: {
|
||||
content: {
|
||||
"application/json": {
|
||||
schema: schemas.json,
|
||||
schema: z.object({
|
||||
client_name: ApplicationSchema.shape.name,
|
||||
redirect_uris: ApplicationSchema.shape.redirect_uris.or(
|
||||
ApplicationSchema.shape.redirect_uri.transform(
|
||||
(u) => u.split("\n"),
|
||||
),
|
||||
),
|
||||
scopes: z
|
||||
.string()
|
||||
.default("read")
|
||||
.transform((s) => s.split(" "))
|
||||
.openapi({
|
||||
description: "Space separated list of scopes.",
|
||||
}),
|
||||
// Allow empty websites because Traewelling decides to give an empty
|
||||
// value instead of not providing anything at all
|
||||
website: ApplicationSchema.shape.website
|
||||
.optional()
|
||||
.or(z.literal("").transform(() => undefined)),
|
||||
}),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
responses: {
|
||||
200: {
|
||||
description: "App",
|
||||
description:
|
||||
"Store the client_id and client_secret in your cache, as these will be used to obtain OAuth tokens.",
|
||||
content: {
|
||||
"application/json": {
|
||||
schema: z.object({
|
||||
id: z.string().uuid(),
|
||||
name: z.string(),
|
||||
website: z.string().nullable(),
|
||||
client_id: z.string(),
|
||||
client_secret: z.string(),
|
||||
redirect_uri: z.string(),
|
||||
vapid_link: z.string().nullable(),
|
||||
}),
|
||||
schema: CredentialApplicationSchema,
|
||||
},
|
||||
},
|
||||
},
|
||||
422: reusedResponses[422],
|
||||
},
|
||||
});
|
||||
|
||||
|
|
@ -66,25 +65,14 @@ export default apiRoute((app) =>
|
|||
context.req.valid("json");
|
||||
|
||||
const app = await Application.insert({
|
||||
name: client_name || "",
|
||||
redirectUri: decodeURI(redirect_uris) || "",
|
||||
scopes: scopes || "read",
|
||||
website: website || null,
|
||||
name: client_name,
|
||||
redirectUri: redirect_uris.join("\n"),
|
||||
scopes: scopes.join(" "),
|
||||
website: website,
|
||||
clientId: randomString(32, "base64url"),
|
||||
secret: randomString(64, "base64url"),
|
||||
});
|
||||
|
||||
return context.json(
|
||||
{
|
||||
id: app.id,
|
||||
name: app.data.name,
|
||||
website: app.data.website,
|
||||
client_id: app.data.clientId,
|
||||
client_secret: app.data.secret,
|
||||
redirect_uri: app.data.redirectUri,
|
||||
vapid_link: app.data.vapidKey,
|
||||
},
|
||||
200,
|
||||
);
|
||||
return context.json(app.toApiCredential(), 200);
|
||||
}),
|
||||
);
|
||||
|
|
|
|||
|
|
@ -1,16 +1,19 @@
|
|||
import { apiRoute, auth } from "@/api";
|
||||
import { apiRoute, auth, reusedResponses } from "@/api";
|
||||
import { createRoute } from "@hono/zod-openapi";
|
||||
import { Application } from "@versia/kit/db";
|
||||
import { RolePermissions } from "@versia/kit/tables";
|
||||
import { ApiError } from "~/classes/errors/api-error";
|
||||
import { Application as ApplicationSchema } from "~/classes/schemas/application";
|
||||
import { ErrorSchema } from "~/types/api";
|
||||
|
||||
const route = createRoute({
|
||||
method: "get",
|
||||
path: "/api/v1/apps/verify_credentials",
|
||||
summary: "Verify credentials",
|
||||
description: "Get your own application information",
|
||||
summary: "Verify your app works",
|
||||
description: "Confirm that the app’s OAuth2 credentials work.",
|
||||
externalDocs: {
|
||||
url: "https://docs.joinmastodon.org/methods/apps/#verify_credentials",
|
||||
},
|
||||
tags: ["Apps"],
|
||||
middleware: [
|
||||
auth({
|
||||
auth: true,
|
||||
|
|
@ -19,21 +22,15 @@ const route = createRoute({
|
|||
] as const,
|
||||
responses: {
|
||||
200: {
|
||||
description: "Application",
|
||||
description:
|
||||
"If the Authorization header was provided with a valid token, you should see your app returned as an Application entity.",
|
||||
content: {
|
||||
"application/json": {
|
||||
schema: ApplicationSchema,
|
||||
},
|
||||
},
|
||||
},
|
||||
401: {
|
||||
description: "Unauthorized",
|
||||
content: {
|
||||
"application/json": {
|
||||
schema: ErrorSchema,
|
||||
},
|
||||
},
|
||||
},
|
||||
...reusedResponses,
|
||||
},
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -1,24 +1,19 @@
|
|||
import { apiRoute, auth } from "@/api";
|
||||
import { apiRoute, auth, reusedResponses } from "@/api";
|
||||
import { createRoute, z } from "@hono/zod-openapi";
|
||||
import { Timeline } from "@versia/kit/db";
|
||||
import { RolePermissions, Users } from "@versia/kit/tables";
|
||||
import { and, gt, gte, lt, sql } from "drizzle-orm";
|
||||
import { Account } from "~/classes/schemas/account";
|
||||
|
||||
const schemas = {
|
||||
query: z.object({
|
||||
max_id: z.string().uuid().optional(),
|
||||
since_id: z.string().uuid().optional(),
|
||||
min_id: z.string().uuid().optional(),
|
||||
limit: z.coerce.number().int().min(1).max(80).default(40),
|
||||
}),
|
||||
};
|
||||
import { Account as AccountSchema } from "~/classes/schemas/account";
|
||||
|
||||
const route = createRoute({
|
||||
method: "get",
|
||||
path: "/api/v1/blocks",
|
||||
summary: "Get blocks",
|
||||
description: "Get users you have blocked",
|
||||
summary: "View your blocks.",
|
||||
description: "View blocked users.",
|
||||
externalDocs: {
|
||||
url: "https://docs.joinmastodon.org/methods/blocks/#get",
|
||||
},
|
||||
tags: ["Blocks"],
|
||||
middleware: [
|
||||
auth({
|
||||
auth: true,
|
||||
|
|
@ -27,17 +22,49 @@ const route = createRoute({
|
|||
}),
|
||||
] as const,
|
||||
request: {
|
||||
query: schemas.query,
|
||||
query: z.object({
|
||||
max_id: AccountSchema.shape.id.optional().openapi({
|
||||
description:
|
||||
"All results returned will be lesser than this ID. In effect, sets an upper bound on results.",
|
||||
example: "8d35243d-b959-43e2-8bac-1a9d4eaea2aa",
|
||||
}),
|
||||
since_id: AccountSchema.shape.id.optional().openapi({
|
||||
description:
|
||||
"All results returned will be greater than this ID. In effect, sets a lower bound on results.",
|
||||
example: undefined,
|
||||
}),
|
||||
min_id: AccountSchema.shape.id.optional().openapi({
|
||||
description:
|
||||
"Returns results immediately newer than this ID. In effect, sets a cursor at this ID and paginates forward.",
|
||||
example: undefined,
|
||||
}),
|
||||
limit: z.coerce.number().int().min(1).max(80).default(40).openapi({
|
||||
description: "Maximum number of results to return.",
|
||||
}),
|
||||
}),
|
||||
},
|
||||
responses: {
|
||||
200: {
|
||||
description: "Blocks",
|
||||
description: "List of blocked users",
|
||||
content: {
|
||||
"application/json": {
|
||||
schema: z.array(Account),
|
||||
schema: z.array(AccountSchema),
|
||||
},
|
||||
},
|
||||
headers: z.object({
|
||||
link: z
|
||||
.string()
|
||||
.optional()
|
||||
.openapi({
|
||||
description: "Links to the next and previous pages",
|
||||
example: `<https://versia.social/api/v1/blocks?limit=2&max_id=359ae97f-78dd-43e7-8e13-1d8e1d7829b5>; rel="next", <https://versia.social/api/v1/blocks?limit=2&since_id=75e9f5a9-f455-48eb-8f60-435b4a088bc0>; rel="prev"`,
|
||||
externalDocs: {
|
||||
url: "https://docs.joinmastodon.org/api/guidelines/#pagination",
|
||||
},
|
||||
}),
|
||||
}),
|
||||
},
|
||||
...reusedResponses,
|
||||
},
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -1,15 +1,19 @@
|
|||
import { apiRoute, auth } from "@/api";
|
||||
import { apiRoute, auth, reusedResponses } from "@/api";
|
||||
import { createRoute, z } from "@hono/zod-openapi";
|
||||
import { Emoji } from "@versia/kit/db";
|
||||
import { Emojis, RolePermissions } from "@versia/kit/tables";
|
||||
import { and, eq, isNull, or } from "drizzle-orm";
|
||||
import { CustomEmoji } from "~/classes/schemas/emoji";
|
||||
import { CustomEmoji as CustomEmojiSchema } from "~/classes/schemas/emoji";
|
||||
|
||||
const route = createRoute({
|
||||
method: "get",
|
||||
path: "/api/v1/custom_emojis",
|
||||
summary: "Get custom emojis",
|
||||
description: "Get custom emojis",
|
||||
summary: "View all custom emoji",
|
||||
description: "Returns custom emojis that are available on the server.",
|
||||
externalDocs: {
|
||||
url: "https://docs.joinmastodon.org/methods/custom_emojis/#get",
|
||||
},
|
||||
tags: ["Emojis"],
|
||||
middleware: [
|
||||
auth({
|
||||
auth: false,
|
||||
|
|
@ -18,13 +22,14 @@ const route = createRoute({
|
|||
] as const,
|
||||
responses: {
|
||||
200: {
|
||||
description: "Emojis",
|
||||
description: "List of custom emojis",
|
||||
content: {
|
||||
"application/json": {
|
||||
schema: z.array(CustomEmoji),
|
||||
schema: z.array(CustomEmojiSchema),
|
||||
},
|
||||
},
|
||||
},
|
||||
422: reusedResponses[422],
|
||||
},
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -1,80 +1,73 @@
|
|||
import { apiRoute, auth, emojiValidator, jsonOrForm } from "@/api";
|
||||
import {
|
||||
apiRoute,
|
||||
auth,
|
||||
jsonOrForm,
|
||||
reusedResponses,
|
||||
withEmojiParam,
|
||||
} from "@/api";
|
||||
import { mimeLookup } from "@/content_types";
|
||||
import { createRoute, z } from "@hono/zod-openapi";
|
||||
import { Emoji } from "@versia/kit/db";
|
||||
import { RolePermissions } from "@versia/kit/tables";
|
||||
import { ApiError } from "~/classes/errors/api-error";
|
||||
import { CustomEmoji } from "~/classes/schemas/emoji";
|
||||
import { CustomEmoji as CustomEmojiSchema } from "~/classes/schemas/emoji";
|
||||
import { config } from "~/packages/config-manager";
|
||||
import { ErrorSchema } from "~/types/api";
|
||||
|
||||
const schemas = {
|
||||
param: z.object({
|
||||
id: z.string().uuid(),
|
||||
}),
|
||||
json: z
|
||||
.object({
|
||||
shortcode: z
|
||||
.string()
|
||||
.trim()
|
||||
.min(1)
|
||||
.max(config.validation.max_emoji_shortcode_size)
|
||||
.regex(
|
||||
emojiValidator,
|
||||
"Shortcode must only contain letters (any case), numbers, dashes or underscores.",
|
||||
),
|
||||
element: z
|
||||
.string()
|
||||
.trim()
|
||||
.min(1)
|
||||
.max(2000)
|
||||
.url()
|
||||
.transform((a) => new URL(a))
|
||||
.or(
|
||||
z
|
||||
.instanceof(File)
|
||||
.refine(
|
||||
(v) => v.size <= config.validation.max_emoji_size,
|
||||
`Emoji must be less than ${config.validation.max_emoji_size} bytes`,
|
||||
),
|
||||
),
|
||||
category: z.string().max(64).optional(),
|
||||
alt: z
|
||||
.string()
|
||||
.max(config.validation.max_emoji_description_size)
|
||||
.optional(),
|
||||
global: z
|
||||
.string()
|
||||
.transform((v) => ["true", "1", "on"].includes(v.toLowerCase()))
|
||||
.or(z.boolean())
|
||||
.optional(),
|
||||
})
|
||||
.partial(),
|
||||
};
|
||||
const schema = z
|
||||
.object({
|
||||
shortcode: CustomEmojiSchema.shape.shortcode,
|
||||
element: z
|
||||
.string()
|
||||
.url()
|
||||
.transform((a) => new URL(a))
|
||||
.openapi({
|
||||
description: "Emoji image URL",
|
||||
})
|
||||
.or(
|
||||
z
|
||||
.instanceof(File)
|
||||
.openapi({
|
||||
description:
|
||||
"Emoji image encoded using multipart/form-data",
|
||||
})
|
||||
.refine(
|
||||
(v) => v.size <= config.validation.max_emoji_size,
|
||||
`Emoji must be less than ${config.validation.max_emoji_size} bytes`,
|
||||
),
|
||||
),
|
||||
category: CustomEmojiSchema.shape.category.optional(),
|
||||
alt: CustomEmojiSchema.shape.description.optional(),
|
||||
global: CustomEmojiSchema.shape.global.default(false),
|
||||
})
|
||||
.partial();
|
||||
|
||||
const routeGet = createRoute({
|
||||
method: "get",
|
||||
path: "/api/v1/emojis/{id}",
|
||||
summary: "Get emoji data",
|
||||
summary: "Get emoji",
|
||||
description: "Retrieves a custom emoji from database by ID.",
|
||||
tags: ["Emojis"],
|
||||
middleware: [
|
||||
auth({
|
||||
auth: true,
|
||||
permissions: [RolePermissions.ViewEmojis],
|
||||
}),
|
||||
withEmojiParam,
|
||||
] as const,
|
||||
request: {
|
||||
params: schemas.param,
|
||||
params: z.object({
|
||||
id: CustomEmojiSchema.shape.id,
|
||||
}),
|
||||
},
|
||||
responses: {
|
||||
200: {
|
||||
description: "Emoji",
|
||||
content: {
|
||||
"application/json": {
|
||||
schema: CustomEmoji,
|
||||
schema: CustomEmojiSchema,
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
404: {
|
||||
description: "Emoji not found",
|
||||
content: {
|
||||
|
|
@ -83,6 +76,7 @@ const routeGet = createRoute({
|
|||
},
|
||||
},
|
||||
},
|
||||
...reusedResponses,
|
||||
},
|
||||
});
|
||||
|
||||
|
|
@ -90,6 +84,8 @@ const routePatch = createRoute({
|
|||
method: "patch",
|
||||
path: "/api/v1/emojis/{id}",
|
||||
summary: "Modify emoji",
|
||||
description: "Edit image or metadata of an emoji.",
|
||||
tags: ["Emojis"],
|
||||
middleware: [
|
||||
auth({
|
||||
auth: true,
|
||||
|
|
@ -99,19 +95,22 @@ const routePatch = createRoute({
|
|||
],
|
||||
}),
|
||||
jsonOrForm(),
|
||||
withEmojiParam,
|
||||
] as const,
|
||||
request: {
|
||||
params: schemas.param,
|
||||
params: z.object({
|
||||
id: CustomEmojiSchema.shape.id,
|
||||
}),
|
||||
body: {
|
||||
content: {
|
||||
"application/json": {
|
||||
schema: schemas.json,
|
||||
schema: schema,
|
||||
},
|
||||
"application/x-www-form-urlencoded": {
|
||||
schema: schemas.json,
|
||||
schema: schema,
|
||||
},
|
||||
"multipart/form-data": {
|
||||
schema: schemas.json,
|
||||
schema: schema,
|
||||
},
|
||||
},
|
||||
},
|
||||
|
|
@ -121,13 +120,12 @@ const routePatch = createRoute({
|
|||
description: "Emoji modified",
|
||||
content: {
|
||||
"application/json": {
|
||||
schema: CustomEmoji,
|
||||
schema: CustomEmojiSchema,
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
403: {
|
||||
description: "Insufficient credentials",
|
||||
description: "Insufficient permissions",
|
||||
content: {
|
||||
"application/json": {
|
||||
schema: ErrorSchema,
|
||||
|
|
@ -142,14 +140,7 @@ const routePatch = createRoute({
|
|||
},
|
||||
},
|
||||
},
|
||||
422: {
|
||||
description: "Invalid form data",
|
||||
content: {
|
||||
"application/json": {
|
||||
schema: ErrorSchema,
|
||||
},
|
||||
},
|
||||
},
|
||||
...reusedResponses,
|
||||
},
|
||||
});
|
||||
|
||||
|
|
@ -157,6 +148,8 @@ const routeDelete = createRoute({
|
|||
method: "delete",
|
||||
path: "/api/v1/emojis/{id}",
|
||||
summary: "Delete emoji",
|
||||
description: "Delete a custom emoji from the database.",
|
||||
tags: ["Emojis"],
|
||||
middleware: [
|
||||
auth({
|
||||
auth: true,
|
||||
|
|
@ -165,15 +158,17 @@ const routeDelete = createRoute({
|
|||
RolePermissions.ViewEmojis,
|
||||
],
|
||||
}),
|
||||
withEmojiParam,
|
||||
] as const,
|
||||
request: {
|
||||
params: schemas.param,
|
||||
params: z.object({
|
||||
id: CustomEmojiSchema.shape.id,
|
||||
}),
|
||||
},
|
||||
responses: {
|
||||
204: {
|
||||
description: "Emoji deleted",
|
||||
},
|
||||
|
||||
404: {
|
||||
description: "Emoji not found",
|
||||
content: {
|
||||
|
|
@ -186,15 +181,9 @@ const routeDelete = createRoute({
|
|||
});
|
||||
|
||||
export default apiRoute((app) => {
|
||||
app.openapi(routeGet, async (context) => {
|
||||
const { id } = context.req.valid("param");
|
||||
app.openapi(routeGet, (context) => {
|
||||
const { user } = context.get("auth");
|
||||
|
||||
const emoji = await Emoji.fromId(id);
|
||||
|
||||
if (!emoji) {
|
||||
throw new ApiError(404, "Emoji not found");
|
||||
}
|
||||
const emoji = context.get("emoji");
|
||||
|
||||
// Don't leak non-global emojis to non-admins
|
||||
if (
|
||||
|
|
@ -208,14 +197,8 @@ export default apiRoute((app) => {
|
|||
});
|
||||
|
||||
app.openapi(routePatch, async (context) => {
|
||||
const { id } = context.req.valid("param");
|
||||
const { user } = context.get("auth");
|
||||
|
||||
const emoji = await Emoji.fromId(id);
|
||||
|
||||
if (!emoji) {
|
||||
throw new ApiError(404, "Emoji not found");
|
||||
}
|
||||
const emoji = context.get("emoji");
|
||||
|
||||
// Check if user is admin
|
||||
if (
|
||||
|
|
@ -246,7 +229,7 @@ export default apiRoute((app) => {
|
|||
}
|
||||
|
||||
if (element) {
|
||||
// Check of emoji is an image
|
||||
// Check if emoji is an image
|
||||
const contentType =
|
||||
element instanceof File
|
||||
? element.type
|
||||
|
|
@ -283,14 +266,8 @@ export default apiRoute((app) => {
|
|||
});
|
||||
|
||||
app.openapi(routeDelete, async (context) => {
|
||||
const { id } = context.req.valid("param");
|
||||
const { user } = context.get("auth");
|
||||
|
||||
const emoji = await Emoji.fromId(id);
|
||||
|
||||
if (!emoji) {
|
||||
throw new ApiError(404, "Emoji not found");
|
||||
}
|
||||
const emoji = context.get("emoji");
|
||||
|
||||
// Check if user is admin
|
||||
if (
|
||||
|
|
|
|||
|
|
@ -1,58 +1,44 @@
|
|||
import { apiRoute, auth, emojiValidator, jsonOrForm } from "@/api";
|
||||
import { apiRoute, auth, jsonOrForm, reusedResponses } from "@/api";
|
||||
import { mimeLookup } from "@/content_types";
|
||||
import { createRoute, z } from "@hono/zod-openapi";
|
||||
import { Emoji, Media } from "@versia/kit/db";
|
||||
import { Emojis, RolePermissions } from "@versia/kit/tables";
|
||||
import { and, eq, isNull, or } from "drizzle-orm";
|
||||
import { ApiError } from "~/classes/errors/api-error";
|
||||
import { CustomEmoji } from "~/classes/schemas/emoji";
|
||||
import { CustomEmoji as CustomEmojiSchema } from "~/classes/schemas/emoji";
|
||||
import { config } from "~/packages/config-manager";
|
||||
import { ErrorSchema } from "~/types/api";
|
||||
|
||||
const schemas = {
|
||||
json: z.object({
|
||||
shortcode: z
|
||||
.string()
|
||||
.trim()
|
||||
.min(1)
|
||||
.max(config.validation.max_emoji_shortcode_size)
|
||||
.regex(
|
||||
emojiValidator,
|
||||
"Shortcode must only contain letters (any case), numbers, dashes or underscores.",
|
||||
),
|
||||
element: z
|
||||
.string()
|
||||
.trim()
|
||||
.min(1)
|
||||
.max(2000)
|
||||
.url()
|
||||
.transform((a) => new URL(a))
|
||||
.or(
|
||||
z
|
||||
.instanceof(File)
|
||||
.refine(
|
||||
(v) => v.size <= config.validation.max_emoji_size,
|
||||
`Emoji must be less than ${config.validation.max_emoji_size} bytes`,
|
||||
),
|
||||
),
|
||||
category: z.string().max(64).optional(),
|
||||
alt: z
|
||||
.string()
|
||||
.max(config.validation.max_emoji_description_size)
|
||||
.optional(),
|
||||
global: z
|
||||
.string()
|
||||
.transform((v) => ["true", "1", "on"].includes(v.toLowerCase()))
|
||||
.or(z.boolean())
|
||||
.optional(),
|
||||
}),
|
||||
};
|
||||
const schema = z.object({
|
||||
shortcode: CustomEmojiSchema.shape.shortcode,
|
||||
element: z
|
||||
.string()
|
||||
.url()
|
||||
.transform((a) => new URL(a))
|
||||
.openapi({
|
||||
description: "Emoji image URL",
|
||||
})
|
||||
.or(
|
||||
z
|
||||
.instanceof(File)
|
||||
.openapi({
|
||||
description:
|
||||
"Emoji image encoded using multipart/form-data",
|
||||
})
|
||||
.refine(
|
||||
(v) => v.size <= config.validation.max_emoji_size,
|
||||
`Emoji must be less than ${config.validation.max_emoji_size} bytes`,
|
||||
),
|
||||
),
|
||||
category: CustomEmojiSchema.shape.category.optional(),
|
||||
alt: CustomEmojiSchema.shape.description.optional(),
|
||||
global: CustomEmojiSchema.shape.global.default(false),
|
||||
});
|
||||
|
||||
const route = createRoute({
|
||||
method: "post",
|
||||
path: "/api/v1/emojis",
|
||||
summary: "Upload emoji",
|
||||
description: "Upload an emoji",
|
||||
description: "Upload a new emoji to the server.",
|
||||
middleware: [
|
||||
auth({
|
||||
auth: true,
|
||||
|
|
@ -67,13 +53,13 @@ const route = createRoute({
|
|||
body: {
|
||||
content: {
|
||||
"application/json": {
|
||||
schema: schemas.json,
|
||||
schema: schema,
|
||||
},
|
||||
"multipart/form-data": {
|
||||
schema: schemas.json,
|
||||
schema: schema,
|
||||
},
|
||||
"application/x-www-form-urlencoded": {
|
||||
schema: schemas.json,
|
||||
schema: schema,
|
||||
},
|
||||
},
|
||||
},
|
||||
|
|
@ -83,19 +69,11 @@ const route = createRoute({
|
|||
description: "Uploaded emoji",
|
||||
content: {
|
||||
"application/json": {
|
||||
schema: CustomEmoji,
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
422: {
|
||||
description: "Invalid data",
|
||||
content: {
|
||||
"application/json": {
|
||||
schema: ErrorSchema,
|
||||
schema: CustomEmojiSchema,
|
||||
},
|
||||
},
|
||||
},
|
||||
...reusedResponses,
|
||||
},
|
||||
});
|
||||
|
||||
|
|
@ -145,10 +123,10 @@ export default apiRoute((app) =>
|
|||
const media =
|
||||
element instanceof File
|
||||
? await Media.fromFile(element, {
|
||||
description: alt,
|
||||
description: alt ?? undefined,
|
||||
})
|
||||
: await Media.fromUrl(element, {
|
||||
description: alt,
|
||||
description: alt ?? undefined,
|
||||
});
|
||||
|
||||
const emoji = await Emoji.insert({
|
||||
|
|
|
|||
|
|
@ -1,23 +1,19 @@
|
|||
import { apiRoute, auth } from "@/api";
|
||||
import { apiRoute, auth, reusedResponses } from "@/api";
|
||||
import { createRoute, z } from "@hono/zod-openapi";
|
||||
import { Timeline } from "@versia/kit/db";
|
||||
import { Notes, RolePermissions } from "@versia/kit/tables";
|
||||
import { and, gt, gte, lt, sql } from "drizzle-orm";
|
||||
import { Status } from "~/classes/schemas/status";
|
||||
|
||||
const schemas = {
|
||||
query: z.object({
|
||||
max_id: z.string().uuid().optional(),
|
||||
since_id: z.string().uuid().optional(),
|
||||
min_id: z.string().uuid().optional(),
|
||||
limit: z.coerce.number().int().min(1).max(80).default(40),
|
||||
}),
|
||||
};
|
||||
import { Status as StatusSchema } from "~/classes/schemas/status";
|
||||
|
||||
const route = createRoute({
|
||||
method: "get",
|
||||
path: "/api/v1/favourites",
|
||||
summary: "Get favourites",
|
||||
summary: "View favourited statuses",
|
||||
description: "Statuses the user has favourited.",
|
||||
externalDocs: {
|
||||
url: "https://docs.joinmastodon.org/methods/favourites/#get",
|
||||
},
|
||||
tags: ["Favourites"],
|
||||
middleware: [
|
||||
auth({
|
||||
auth: true,
|
||||
|
|
@ -25,17 +21,49 @@ const route = createRoute({
|
|||
}),
|
||||
] as const,
|
||||
request: {
|
||||
query: schemas.query,
|
||||
query: z.object({
|
||||
max_id: StatusSchema.shape.id.optional().openapi({
|
||||
description:
|
||||
"All results returned will be lesser than this ID. In effect, sets an upper bound on results.",
|
||||
example: "8d35243d-b959-43e2-8bac-1a9d4eaea2aa",
|
||||
}),
|
||||
since_id: StatusSchema.shape.id.optional().openapi({
|
||||
description:
|
||||
"All results returned will be greater than this ID. In effect, sets a lower bound on results.",
|
||||
example: undefined,
|
||||
}),
|
||||
min_id: StatusSchema.shape.id.optional().openapi({
|
||||
description:
|
||||
"Returns results immediately newer than this ID. In effect, sets a cursor at this ID and paginates forward.",
|
||||
example: undefined,
|
||||
}),
|
||||
limit: z.coerce.number().int().min(1).max(80).default(40).openapi({
|
||||
description: "Maximum number of results to return.",
|
||||
}),
|
||||
}),
|
||||
},
|
||||
responses: {
|
||||
200: {
|
||||
description: "Favourites",
|
||||
description: "List of favourited statuses",
|
||||
content: {
|
||||
"application/json": {
|
||||
schema: z.array(Status),
|
||||
schema: z.array(StatusSchema),
|
||||
},
|
||||
},
|
||||
headers: z.object({
|
||||
link: z
|
||||
.string()
|
||||
.optional()
|
||||
.openapi({
|
||||
description: "Links to the next and previous pages",
|
||||
example: `<https://versia.social/api/v1/favourites?limit=2&max_id=359ae97f-78dd-43e7-8e13-1d8e1d7829b5>; rel="next", <https://versia.social/api/v1/favourites?limit=2&since_id=75e9f5a9-f455-48eb-8f60-435b4a088bc0>; rel="prev"`,
|
||||
externalDocs: {
|
||||
url: "https://docs.joinmastodon.org/api/guidelines/#pagination",
|
||||
},
|
||||
}),
|
||||
}),
|
||||
},
|
||||
...reusedResponses,
|
||||
},
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -1,21 +1,19 @@
|
|||
import { apiRoute, auth } from "@/api";
|
||||
import { accountNotFound, apiRoute, auth, reusedResponses } from "@/api";
|
||||
import { createRoute, z } from "@hono/zod-openapi";
|
||||
import { Relationship, User } from "@versia/kit/db";
|
||||
import { RolePermissions } from "@versia/kit/tables";
|
||||
import { ApiError } from "~/classes/errors/api-error";
|
||||
import { Account as AccountSchema } from "~/classes/schemas/account";
|
||||
import { Relationship as RelationshipSchema } from "~/classes/schemas/relationship";
|
||||
import { ErrorSchema } from "~/types/api";
|
||||
|
||||
const schemas = {
|
||||
param: z.object({
|
||||
account_id: z.string().uuid(),
|
||||
}),
|
||||
};
|
||||
|
||||
const route = createRoute({
|
||||
method: "post",
|
||||
path: "/api/v1/follow_requests/{account_id}/authorize",
|
||||
summary: "Authorize follow request",
|
||||
summary: "Accept follow request",
|
||||
externalDocs: {
|
||||
url: "https://docs.joinmastodon.org/methods/follow_requests/#accept",
|
||||
},
|
||||
tags: ["Follows"],
|
||||
middleware: [
|
||||
auth({
|
||||
auth: true,
|
||||
|
|
@ -23,26 +21,22 @@ const route = createRoute({
|
|||
}),
|
||||
] as const,
|
||||
request: {
|
||||
params: schemas.param,
|
||||
params: z.object({
|
||||
account_id: AccountSchema.shape.id,
|
||||
}),
|
||||
},
|
||||
responses: {
|
||||
200: {
|
||||
description: "Relationship",
|
||||
description:
|
||||
"Your Relationship with this account should be updated so that you are followed_by this account.",
|
||||
content: {
|
||||
"application/json": {
|
||||
schema: RelationshipSchema,
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
404: {
|
||||
description: "Account not found",
|
||||
content: {
|
||||
"application/json": {
|
||||
schema: ErrorSchema,
|
||||
},
|
||||
},
|
||||
},
|
||||
404: accountNotFound,
|
||||
...reusedResponses,
|
||||
},
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -1,21 +1,19 @@
|
|||
import { apiRoute, auth } from "@/api";
|
||||
import { accountNotFound, apiRoute, auth, reusedResponses } from "@/api";
|
||||
import { createRoute, z } from "@hono/zod-openapi";
|
||||
import { Relationship, User } from "@versia/kit/db";
|
||||
import { RolePermissions } from "@versia/kit/tables";
|
||||
import { ApiError } from "~/classes/errors/api-error";
|
||||
import { Account as AccountSchema } from "~/classes/schemas/account";
|
||||
import { Relationship as RelationshipSchema } from "~/classes/schemas/relationship";
|
||||
import { ErrorSchema } from "~/types/api";
|
||||
|
||||
const schemas = {
|
||||
param: z.object({
|
||||
account_id: z.string().uuid(),
|
||||
}),
|
||||
};
|
||||
|
||||
const route = createRoute({
|
||||
method: "post",
|
||||
path: "/api/v1/follow_requests/{account_id}/reject",
|
||||
summary: "Reject follow request",
|
||||
externalDocs: {
|
||||
url: "https://docs.joinmastodon.org/methods/follow_requests/#reject",
|
||||
},
|
||||
tags: ["Follows"],
|
||||
middleware: [
|
||||
auth({
|
||||
auth: true,
|
||||
|
|
@ -23,26 +21,22 @@ const route = createRoute({
|
|||
}),
|
||||
] as const,
|
||||
request: {
|
||||
params: schemas.param,
|
||||
params: z.object({
|
||||
account_id: AccountSchema.shape.id,
|
||||
}),
|
||||
},
|
||||
responses: {
|
||||
200: {
|
||||
description: "Relationship",
|
||||
description:
|
||||
"Your Relationship with this account should be unchanged.",
|
||||
content: {
|
||||
"application/json": {
|
||||
schema: RelationshipSchema,
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
404: {
|
||||
description: "Account not found",
|
||||
content: {
|
||||
"application/json": {
|
||||
schema: ErrorSchema,
|
||||
},
|
||||
},
|
||||
},
|
||||
404: accountNotFound,
|
||||
...reusedResponses,
|
||||
},
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -1,23 +1,19 @@
|
|||
import { apiRoute, auth } from "@/api";
|
||||
import { apiRoute, auth, reusedResponses } from "@/api";
|
||||
import { createRoute, z } from "@hono/zod-openapi";
|
||||
import { Timeline } from "@versia/kit/db";
|
||||
import { RolePermissions, Users } from "@versia/kit/tables";
|
||||
import { and, gt, gte, lt, sql } from "drizzle-orm";
|
||||
import { Account } from "~/classes/schemas/account";
|
||||
|
||||
const schemas = {
|
||||
query: z.object({
|
||||
max_id: z.string().uuid().optional(),
|
||||
since_id: z.string().uuid().optional(),
|
||||
min_id: z.string().uuid().optional(),
|
||||
limit: z.coerce.number().int().min(1).max(80).default(40),
|
||||
}),
|
||||
};
|
||||
import { Account as AccountSchema } from "~/classes/schemas/account";
|
||||
|
||||
const route = createRoute({
|
||||
method: "get",
|
||||
path: "/api/v1/follow_requests",
|
||||
summary: "Get follow requests",
|
||||
summary: "View pending follow requests",
|
||||
description: "Get a list of follow requests that the user has received.",
|
||||
externalDocs: {
|
||||
url: "https://docs.joinmastodon.org/methods/follow_requests/#get",
|
||||
},
|
||||
tags: ["Follows"],
|
||||
middleware: [
|
||||
auth({
|
||||
auth: true,
|
||||
|
|
@ -25,17 +21,50 @@ const route = createRoute({
|
|||
}),
|
||||
] as const,
|
||||
request: {
|
||||
query: schemas.query,
|
||||
query: z.object({
|
||||
max_id: AccountSchema.shape.id.optional().openapi({
|
||||
description:
|
||||
"All results returned will be lesser than this ID. In effect, sets an upper bound on results.",
|
||||
example: "8d35243d-b959-43e2-8bac-1a9d4eaea2aa",
|
||||
}),
|
||||
since_id: AccountSchema.shape.id.optional().openapi({
|
||||
description:
|
||||
"All results returned will be greater than this ID. In effect, sets a lower bound on results.",
|
||||
example: undefined,
|
||||
}),
|
||||
min_id: AccountSchema.shape.id.optional().openapi({
|
||||
description:
|
||||
"Returns results immediately newer than this ID. In effect, sets a cursor at this ID and paginates forward.",
|
||||
example: undefined,
|
||||
}),
|
||||
limit: z.coerce.number().int().min(1).max(80).default(40).openapi({
|
||||
description: "Maximum number of results to return.",
|
||||
}),
|
||||
}),
|
||||
},
|
||||
responses: {
|
||||
200: {
|
||||
description: "Follow requests",
|
||||
description:
|
||||
"List of accounts that have requested to follow the user",
|
||||
content: {
|
||||
"application/json": {
|
||||
schema: z.array(Account),
|
||||
schema: z.array(AccountSchema),
|
||||
},
|
||||
},
|
||||
headers: z.object({
|
||||
link: z
|
||||
.string()
|
||||
.optional()
|
||||
.openapi({
|
||||
description: "Links to the next and previous pages",
|
||||
example: `<https://versia.social/api/v1/follow_requests?limit=2&max_id=359ae97f-78dd-43e7-8e13-1d8e1d7829b5>; rel="next", <https://versia.social/api/v1/follow_requests?limit=2&since_id=75e9f5a9-f455-48eb-8f60-435b4a088bc0>; rel="prev"`,
|
||||
externalDocs: {
|
||||
url: "https://docs.joinmastodon.org/api/guidelines/#pagination",
|
||||
},
|
||||
}),
|
||||
}),
|
||||
},
|
||||
...reusedResponses,
|
||||
},
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -1,23 +1,19 @@
|
|||
import { apiRoute, auth } from "@/api";
|
||||
import { apiRoute, auth, reusedResponses } from "@/api";
|
||||
import { createRoute, z } from "@hono/zod-openapi";
|
||||
import { Timeline } from "@versia/kit/db";
|
||||
import { RolePermissions, Users } from "@versia/kit/tables";
|
||||
import { and, gt, gte, lt, sql } from "drizzle-orm";
|
||||
import { Account } from "~/classes/schemas/account";
|
||||
|
||||
const schemas = {
|
||||
query: z.object({
|
||||
max_id: z.string().uuid().optional(),
|
||||
since_id: z.string().uuid().optional(),
|
||||
min_id: z.string().uuid().optional(),
|
||||
limit: z.coerce.number().int().min(1).max(80).default(40),
|
||||
}),
|
||||
};
|
||||
import { Account as AccountSchema } from "~/classes/schemas/account";
|
||||
|
||||
const route = createRoute({
|
||||
method: "get",
|
||||
path: "/api/v1/mutes",
|
||||
summary: "Get muted users",
|
||||
summary: "View muted accounts",
|
||||
description: "View your mutes.",
|
||||
externalDocs: {
|
||||
url: "https://docs.joinmastodon.org/methods/mutes/#get",
|
||||
},
|
||||
tags: ["Mutes"],
|
||||
middleware: [
|
||||
auth({
|
||||
auth: true,
|
||||
|
|
@ -26,17 +22,49 @@ const route = createRoute({
|
|||
}),
|
||||
] as const,
|
||||
request: {
|
||||
query: schemas.query,
|
||||
query: z.object({
|
||||
max_id: AccountSchema.shape.id.optional().openapi({
|
||||
description:
|
||||
"All results returned will be lesser than this ID. In effect, sets an upper bound on results.",
|
||||
example: "8d35243d-b959-43e2-8bac-1a9d4eaea2aa",
|
||||
}),
|
||||
since_id: AccountSchema.shape.id.optional().openapi({
|
||||
description:
|
||||
"All results returned will be greater than this ID. In effect, sets a lower bound on results.",
|
||||
example: undefined,
|
||||
}),
|
||||
min_id: AccountSchema.shape.id.optional().openapi({
|
||||
description:
|
||||
"Returns results immediately newer than this ID. In effect, sets a cursor at this ID and paginates forward.",
|
||||
example: undefined,
|
||||
}),
|
||||
limit: z.coerce.number().int().min(1).max(80).default(40).openapi({
|
||||
description: "Maximum number of results to return.",
|
||||
}),
|
||||
}),
|
||||
},
|
||||
responses: {
|
||||
200: {
|
||||
description: "Muted users",
|
||||
description: "List of muted users",
|
||||
content: {
|
||||
"application/json": {
|
||||
schema: z.array(Account),
|
||||
schema: z.array(AccountSchema),
|
||||
},
|
||||
},
|
||||
headers: z.object({
|
||||
link: z
|
||||
.string()
|
||||
.optional()
|
||||
.openapi({
|
||||
description: "Links to the next and previous pages",
|
||||
example: `<https://versia.social/api/v1/mutes?limit=2&max_id=359ae97f-78dd-43e7-8e13-1d8e1d7829b5>; rel="next", <https://versia.social/api/v1/mutes?limit=2&since_id=75e9f5a9-f455-48eb-8f60-435b4a088bc0>; rel="prev"`,
|
||||
externalDocs: {
|
||||
url: "https://docs.joinmastodon.org/api/guidelines/#pagination",
|
||||
},
|
||||
}),
|
||||
}),
|
||||
},
|
||||
...reusedResponses,
|
||||
},
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -1,13 +1,18 @@
|
|||
import { z } from "@hono/zod-openapi";
|
||||
|
||||
export const Application = z.object({
|
||||
name: z.string().openapi({
|
||||
description: "The name of your application.",
|
||||
example: "Test Application",
|
||||
externalDocs: {
|
||||
url: "https://docs.joinmastodon.org/entities/Application/#name",
|
||||
},
|
||||
}),
|
||||
name: z
|
||||
.string()
|
||||
.trim()
|
||||
.min(1)
|
||||
.max(200)
|
||||
.openapi({
|
||||
description: "The name of your application.",
|
||||
example: "Test Application",
|
||||
externalDocs: {
|
||||
url: "https://docs.joinmastodon.org/entities/Application/#name",
|
||||
},
|
||||
}),
|
||||
website: z
|
||||
.string()
|
||||
.nullable()
|
||||
|
|
@ -18,19 +23,26 @@ export const Application = z.object({
|
|||
url: "https://docs.joinmastodon.org/entities/Application/#website",
|
||||
},
|
||||
}),
|
||||
scopes: z.array(z.string()).openapi({
|
||||
description:
|
||||
"The scopes for your application. This is the registered scopes string split on whitespace.",
|
||||
example: ["read", "write", "push"],
|
||||
externalDocs: {
|
||||
url: "https://docs.joinmastodon.org/entities/Application/#scopes",
|
||||
},
|
||||
}),
|
||||
scopes: z
|
||||
.array(z.string())
|
||||
.default(["read"])
|
||||
.openapi({
|
||||
description:
|
||||
"The scopes for your application. This is the registered scopes string split on whitespace.",
|
||||
example: ["read", "write", "push"],
|
||||
externalDocs: {
|
||||
url: "https://docs.joinmastodon.org/entities/Application/#scopes",
|
||||
},
|
||||
}),
|
||||
redirect_uris: z
|
||||
.array(
|
||||
z.string().url().openapi({
|
||||
description: "URL or 'urn:ietf:wg:oauth:2.0:oob'",
|
||||
}),
|
||||
z
|
||||
.string()
|
||||
.url()
|
||||
.or(z.literal("urn:ietf:wg:oauth:2.0:oob"))
|
||||
.openapi({
|
||||
description: "URL or 'urn:ietf:wg:oauth:2.0:oob'",
|
||||
}),
|
||||
)
|
||||
.openapi({
|
||||
description:
|
||||
|
|
@ -40,6 +52,7 @@ export const Application = z.object({
|
|||
},
|
||||
}),
|
||||
redirect_uri: z.string().openapi({
|
||||
deprecated: true,
|
||||
description:
|
||||
"The registered redirection URI(s) for your application. May contain \\n characters when multiple redirect URIs are registered.",
|
||||
externalDocs: {
|
||||
|
|
|
|||
|
|
@ -1,5 +1,7 @@
|
|||
import { emojiValidator } from "@/api.ts";
|
||||
import { z } from "@hono/zod-openapi";
|
||||
import { zBoolean } from "~/packages/config-manager/config.type";
|
||||
import { config } from "~/packages/config-manager/index.ts";
|
||||
import { Id } from "./common.ts";
|
||||
|
||||
export const CustomEmoji = z
|
||||
|
|
@ -9,13 +11,22 @@ export const CustomEmoji = z
|
|||
description: "ID of the custom emoji in the database.",
|
||||
example: "af9ccd29-c689-477f-aa27-d7d95fd8fb05",
|
||||
}),
|
||||
shortcode: z.string().openapi({
|
||||
description: "The name of the custom emoji.",
|
||||
example: "blobaww",
|
||||
externalDocs: {
|
||||
url: "https://docs.joinmastodon.org/entities/CustomEmoji/#shortcode",
|
||||
},
|
||||
}),
|
||||
shortcode: z
|
||||
.string()
|
||||
.trim()
|
||||
.min(1)
|
||||
.max(config.validation.max_emoji_shortcode_size)
|
||||
.regex(
|
||||
emojiValidator,
|
||||
"Shortcode must only contain letters (any case), numbers, dashes or underscores.",
|
||||
)
|
||||
.openapi({
|
||||
description: "The name of the custom emoji.",
|
||||
example: "blobaww",
|
||||
externalDocs: {
|
||||
url: "https://docs.joinmastodon.org/entities/CustomEmoji/#shortcode",
|
||||
},
|
||||
}),
|
||||
url: z
|
||||
.string()
|
||||
.url()
|
||||
|
|
@ -48,6 +59,8 @@ export const CustomEmoji = z
|
|||
}),
|
||||
category: z
|
||||
.string()
|
||||
.trim()
|
||||
.max(64)
|
||||
.nullable()
|
||||
.openapi({
|
||||
description: "Used for sorting custom emoji in the picker.",
|
||||
|
|
@ -64,6 +77,7 @@ export const CustomEmoji = z
|
|||
/* Versia Server API extension */
|
||||
description: z
|
||||
.string()
|
||||
.max(config.validation.max_emoji_description_size)
|
||||
.nullable()
|
||||
.openapi({
|
||||
description:
|
||||
|
|
|
|||
|
|
@ -49,13 +49,14 @@ describe("POST /api/v1/apps/", () => {
|
|||
const json = await response.json();
|
||||
|
||||
expect(json).toEqual({
|
||||
id: expect.any(String),
|
||||
name: "Test Application",
|
||||
website: "https://example.com",
|
||||
client_id: expect.any(String),
|
||||
client_secret: expect.any(String),
|
||||
client_secret_expires_at: "0",
|
||||
redirect_uri: "https://example.com",
|
||||
vapid_link: null,
|
||||
redirect_uris: ["https://example.com"],
|
||||
scopes: ["read", "write"],
|
||||
});
|
||||
|
||||
clientId = json.client_id;
|
||||
|
|
|
|||
39
utils/api.ts
39
utils/api.ts
|
|
@ -2,7 +2,7 @@ import type { OpenAPIHono } from "@hono/zod-openapi";
|
|||
import { z } from "@hono/zod-openapi";
|
||||
import { zValidator } from "@hono/zod-validator";
|
||||
import { getLogger } from "@logtape/logtape";
|
||||
import { Application, Note, Token, User, db } from "@versia/kit/db";
|
||||
import { Application, Emoji, Note, Token, User, db } from "@versia/kit/db";
|
||||
import { Challenges, type RolePermissions } from "@versia/kit/tables";
|
||||
import { extractParams, verifySolution } from "altcha-lib";
|
||||
import chalk from "chalk";
|
||||
|
|
@ -404,6 +404,43 @@ export const withUserParam = every(
|
|||
}
|
||||
>;
|
||||
|
||||
/**
|
||||
* Middleware to check if an emoji exists and is viewable by the user
|
||||
*
|
||||
* Useful in /api/v1/emojis/:id/* routes
|
||||
* @returns
|
||||
*/
|
||||
export const withEmojiParam = every(
|
||||
zValidator("param", z.object({ id: z.string().uuid() }), handleZodError),
|
||||
createMiddleware<
|
||||
HonoEnv & {
|
||||
Variables: {
|
||||
emoji: Emoji;
|
||||
};
|
||||
},
|
||||
string,
|
||||
WithIdParam
|
||||
>(async (context, next) => {
|
||||
const { id } = context.req.valid("param");
|
||||
|
||||
const emoji = await Emoji.fromId(id);
|
||||
|
||||
if (!emoji) {
|
||||
throw new ApiError(404, "Emoji not found");
|
||||
}
|
||||
|
||||
context.set("emoji", emoji);
|
||||
|
||||
await next();
|
||||
}),
|
||||
) as MiddlewareHandler<
|
||||
HonoEnv & {
|
||||
Variables: {
|
||||
emoji: Emoji;
|
||||
};
|
||||
}
|
||||
>;
|
||||
|
||||
// Helper function to parse form data
|
||||
async function parseFormData(context: Context): Promise<{
|
||||
parsed: ParsedQs;
|
||||
|
|
|
|||
Loading…
Reference in a new issue