refactor(api): ♻️ Group note/account fetching code in some routes

This commit is contained in:
Jesse Wierzbinski 2024-12-30 21:30:10 +01:00
parent 16f302c2dc
commit 82da70bcac
No known key found for this signature in database
32 changed files with 210 additions and 553 deletions

View file

@ -1,10 +1,8 @@
import { apiRoute, auth } from "@/api";
import { apiRoute, auth, withUserParam } from "@/api";
import { createRoute } from "@hono/zod-openapi";
import { Relationship, User } from "@versia/kit/db";
import { Relationship } from "@versia/kit/db";
import { RolePermissions } from "@versia/kit/tables";
import { z } from "zod";
import { ApiError } from "~/classes/errors/api-error";
import { ErrorSchema } from "~/types/api";
const route = createRoute({
method: "post",
@ -20,6 +18,7 @@ const route = createRoute({
RolePermissions.ViewAccounts,
],
}),
withUserParam,
] as const,
responses: {
200: {
@ -30,14 +29,6 @@ const route = createRoute({
},
},
},
404: {
description: "User not found",
content: {
"application/json": {
schema: ErrorSchema,
},
},
},
},
request: {
params: z.object({
@ -48,14 +39,8 @@ const route = createRoute({
export default apiRoute((app) =>
app.openapi(route, async (context) => {
const { id } = context.req.valid("param");
const { user } = context.get("auth");
const otherUser = await User.fromId(id);
if (!otherUser) {
throw new ApiError(404, "User not found");
}
const otherUser = context.get("user");
const foundRelationship = await Relationship.fromOwnerAndSubject(
user,

View file

@ -1,11 +1,9 @@
import { apiRoute, auth } from "@/api";
import { apiRoute, auth, withUserParam } from "@/api";
import { createRoute } from "@hono/zod-openapi";
import { Relationship, User } from "@versia/kit/db";
import { Relationship } from "@versia/kit/db";
import { RolePermissions } from "@versia/kit/tables";
import ISO6391 from "iso-639-1";
import { z } from "zod";
import { ApiError } from "~/classes/errors/api-error";
import { ErrorSchema } from "~/types/api";
const schemas = {
param: z.object({
@ -37,6 +35,7 @@ const route = createRoute({
RolePermissions.ViewAccounts,
],
}),
withUserParam,
] as const,
responses: {
200: {
@ -47,14 +46,6 @@ const route = createRoute({
},
},
},
404: {
description: "User not found",
content: {
"application/json": {
schema: ErrorSchema,
},
},
},
},
request: {
params: schemas.param,
@ -70,15 +61,9 @@ const route = createRoute({
export default apiRoute((app) =>
app.openapi(route, async (context) => {
const { id } = context.req.valid("param");
const { user } = context.get("auth");
const { reblogs, notify, languages } = context.req.valid("json");
const otherUser = await User.fromId(id);
if (!otherUser) {
throw new ApiError(404, "User not found");
}
const otherUser = context.get("user");
let relationship = await Relationship.fromOwnerAndSubject(
user,

View file

@ -1,11 +1,9 @@
import { apiRoute, auth } from "@/api";
import { apiRoute, auth, withUserParam } from "@/api";
import { createRoute } from "@hono/zod-openapi";
import { Timeline, User } from "@versia/kit/db";
import { RolePermissions, Users } from "@versia/kit/tables";
import { and, gt, gte, lt, sql } from "drizzle-orm";
import { z } from "zod";
import { ApiError } from "~/classes/errors/api-error";
import { ErrorSchema } from "~/types/api";
const schemas = {
query: z.object({
@ -34,6 +32,7 @@ const route = createRoute({
RolePermissions.ViewAccounts,
],
}),
withUserParam,
] as const,
request: {
params: schemas.param,
@ -53,30 +52,15 @@ const route = createRoute({
},
},
},
404: {
description: "The specified account was not found",
content: {
"application/json": {
schema: ErrorSchema,
},
},
},
},
});
export default apiRoute((app) =>
app.openapi(route, async (context) => {
const { id } = context.req.valid("param");
const { max_id, since_id, min_id, limit } = context.req.valid("query");
const otherUser = await User.fromId(id);
const otherUser = context.get("user");
// TODO: Add follower/following privacy settings
if (!otherUser) {
throw new ApiError(404, "User not found");
}
const { objects, link } = await Timeline.getUserTimeline(
and(
max_id ? lt(Users.id, max_id) : undefined,

View file

@ -1,11 +1,9 @@
import { apiRoute, auth } from "@/api";
import { apiRoute, auth, withUserParam } from "@/api";
import { createRoute } from "@hono/zod-openapi";
import { Timeline, User } from "@versia/kit/db";
import { RolePermissions, Users } from "@versia/kit/tables";
import { and, gt, gte, lt, sql } from "drizzle-orm";
import { z } from "zod";
import { ApiError } from "~/classes/errors/api-error";
import { ErrorSchema } from "~/types/api";
const schemas = {
query: z.object({
@ -34,6 +32,7 @@ const route = createRoute({
RolePermissions.ViewAccounts,
],
}),
withUserParam,
] as const,
request: {
params: schemas.param,
@ -54,27 +53,13 @@ const route = createRoute({
},
},
},
404: {
description: "User not found",
content: {
"application/json": {
schema: ErrorSchema,
},
},
},
},
});
export default apiRoute((app) =>
app.openapi(route, async (context) => {
const { id } = context.req.valid("param");
const { max_id, since_id, min_id } = context.req.valid("query");
const otherUser = await User.fromId(id);
if (!otherUser) {
throw new ApiError(404, "User not found");
}
const otherUser = context.get("user");
// TODO: Add follower/following privacy settings

View file

@ -1,10 +1,8 @@
import { apiRoute, auth } from "@/api";
import { apiRoute, auth, withUserParam } from "@/api";
import { createRoute } from "@hono/zod-openapi";
import { User } from "@versia/kit/db";
import { RolePermissions } from "@versia/kit/tables";
import { z } from "zod";
import { ApiError } from "~/classes/errors/api-error";
import { ErrorSchema } from "~/types/api";
const route = createRoute({
method: "get",
@ -16,6 +14,7 @@ const route = createRoute({
auth: false,
permissions: [RolePermissions.ViewAccounts],
}),
withUserParam,
] as const,
request: {
params: z.object({
@ -31,28 +30,14 @@ const route = createRoute({
},
},
},
404: {
description: "User not found",
content: {
"application/json": {
schema: ErrorSchema,
},
},
},
},
});
export default apiRoute((app) =>
app.openapi(route, async (context) => {
const { id } = context.req.valid("param");
app.openapi(route, (context) => {
const { user } = context.get("auth");
const otherUser = context.get("user");
const foundUser = await User.fromId(id);
if (!foundUser) {
throw new ApiError(404, "User not found");
}
return context.json(foundUser.toApi(user?.id === foundUser.id), 200);
return context.json(otherUser.toApi(user?.id === otherUser.id), 200);
}),
);

View file

@ -1,10 +1,8 @@
import { apiRoute, auth } from "@/api";
import { apiRoute, auth, withUserParam } from "@/api";
import { createRoute } from "@hono/zod-openapi";
import { Relationship, User } from "@versia/kit/db";
import { Relationship } from "@versia/kit/db";
import { RolePermissions } from "@versia/kit/tables";
import { z } from "zod";
import { ApiError } from "~/classes/errors/api-error";
import { ErrorSchema } from "~/types/api";
const schemas = {
param: z.object({
@ -35,6 +33,7 @@ const route = createRoute({
RolePermissions.ViewAccounts,
],
}),
withUserParam,
] as const,
request: {
params: schemas.param,
@ -55,29 +54,15 @@ const route = createRoute({
},
},
},
404: {
description: "User not found",
content: {
"application/json": {
schema: ErrorSchema,
},
},
},
},
});
export default apiRoute((app) =>
app.openapi(route, async (context) => {
const { id } = context.req.valid("param");
const { user } = context.get("auth");
// TODO: Add duration support
const { notifications } = context.req.valid("json");
const otherUser = await User.fromId(id);
if (!otherUser) {
throw new ApiError(404, "User not found");
}
const otherUser = context.get("user");
const foundRelationship = await Relationship.fromOwnerAndSubject(
user,

View file

@ -1,10 +1,8 @@
import { apiRoute, auth } from "@/api";
import { apiRoute, auth, withUserParam } from "@/api";
import { createRoute } from "@hono/zod-openapi";
import { Relationship, User } from "@versia/kit/db";
import { Relationship } from "@versia/kit/db";
import { RolePermissions } from "@versia/kit/tables";
import { z } from "zod";
import { ApiError } from "~/classes/errors/api-error";
import { ErrorSchema } from "~/types/api";
const schemas = {
param: z.object({
@ -29,6 +27,7 @@ const route = createRoute({
RolePermissions.ViewAccounts,
],
}),
withUserParam,
] as const,
request: {
params: schemas.param,
@ -49,28 +48,14 @@ const route = createRoute({
},
},
},
404: {
description: "User not found",
content: {
"application/json": {
schema: ErrorSchema,
},
},
},
},
});
export default apiRoute((app) =>
app.openapi(route, async (context) => {
const { id } = context.req.valid("param");
const { user } = context.get("auth");
const { comment } = context.req.valid("json");
const otherUser = await User.fromId(id);
if (!otherUser) {
throw new ApiError(404, "User not found");
}
const otherUser = context.get("user");
const foundRelationship = await Relationship.fromOwnerAndSubject(
user,

View file

@ -1,10 +1,8 @@
import { apiRoute, auth } from "@/api";
import { apiRoute, auth, withUserParam } from "@/api";
import { createRoute } from "@hono/zod-openapi";
import { Relationship, User } from "@versia/kit/db";
import { Relationship } from "@versia/kit/db";
import { RolePermissions } from "@versia/kit/tables";
import { z } from "zod";
import { ApiError } from "~/classes/errors/api-error";
import { ErrorSchema } from "~/types/api";
const route = createRoute({
method: "post",
@ -20,6 +18,7 @@ const route = createRoute({
RolePermissions.ViewAccounts,
],
}),
withUserParam,
] as const,
request: {
params: z.object({
@ -35,27 +34,13 @@ const route = createRoute({
},
},
},
404: {
description: "User not found",
content: {
"application/json": {
schema: ErrorSchema,
},
},
},
},
});
export default apiRoute((app) =>
app.openapi(route, async (context) => {
const { id } = context.req.valid("param");
const { user } = context.get("auth");
const otherUser = await User.fromId(id);
if (!otherUser) {
throw new ApiError(404, "User not found");
}
const otherUser = context.get("user");
const foundRelationship = await Relationship.fromOwnerAndSubject(
user,

View file

@ -1,4 +1,4 @@
import { apiRoute, auth } from "@/api";
import { apiRoute, auth, withUserParam } from "@/api";
import { createRoute } from "@hono/zod-openapi";
import { User } from "@versia/kit/db";
import { RolePermissions } from "@versia/kit/tables";
@ -17,6 +17,7 @@ const route = createRoute({
scopes: ["write:accounts"],
permissions: [RolePermissions.ViewAccounts],
}),
withUserParam,
] as const,
request: {
params: z.object({
@ -32,14 +33,6 @@ const route = createRoute({
},
},
},
404: {
description: "User not found",
content: {
"application/json": {
schema: ErrorSchema,
},
},
},
400: {
description: "User is local",
content: {
@ -53,13 +46,7 @@ const route = createRoute({
export default apiRoute((app) =>
app.openapi(route, async (context) => {
const { id } = context.req.valid("param");
const otherUser = await User.fromId(id);
if (!otherUser) {
throw new ApiError(404, "User not found");
}
const otherUser = context.get("user");
if (otherUser.isLocal()) {
throw new ApiError(400, "Cannot refetch a local user");

View file

@ -1,10 +1,8 @@
import { apiRoute, auth } from "@/api";
import { apiRoute, auth, withUserParam } from "@/api";
import { createRoute } from "@hono/zod-openapi";
import { Relationship, User } from "@versia/kit/db";
import { Relationship } from "@versia/kit/db";
import { RolePermissions } from "@versia/kit/tables";
import { z } from "zod";
import { ApiError } from "~/classes/errors/api-error";
import { ErrorSchema } from "~/types/api";
const route = createRoute({
method: "post",
@ -20,6 +18,7 @@ const route = createRoute({
RolePermissions.ViewAccounts,
],
}),
withUserParam,
] as const,
request: {
params: z.object({
@ -35,27 +34,13 @@ const route = createRoute({
},
},
},
404: {
description: "User not found",
content: {
"application/json": {
schema: ErrorSchema,
},
},
},
},
});
export default apiRoute((app) =>
app.openapi(route, async (context) => {
const { id } = context.req.valid("param");
const { user } = context.get("auth");
const otherUser = await User.fromId(id);
if (!otherUser) {
throw new ApiError(404, "User not found");
}
const otherUser = context.get("user");
const oppositeRelationship = await Relationship.fromOwnerAndSubject(
otherUser,

View file

@ -1,6 +1,6 @@
import { apiRoute, auth } from "@/api";
import { apiRoute, auth, withUserParam } from "@/api";
import { createRoute } from "@hono/zod-openapi";
import { Role, User } from "@versia/kit/db";
import { Role } from "@versia/kit/db";
import { RolePermissions } from "@versia/kit/tables";
import { z } from "zod";
import { ApiError } from "~/classes/errors/api-error";
@ -22,6 +22,7 @@ const routePost = createRoute({
auth: true,
permissions: [RolePermissions.ManageRoles],
}),
withUserParam,
] as const,
request: {
params: schemas.param,
@ -30,9 +31,8 @@ const routePost = createRoute({
204: {
description: "Role assigned",
},
404: {
description: "User or role not found",
description: "Role not found",
content: {
"application/json": {
schema: ErrorSchema,
@ -59,6 +59,7 @@ const routeDelete = createRoute({
auth: true,
permissions: [RolePermissions.ManageRoles],
}),
withUserParam,
] as const,
request: {
params: schemas.param,
@ -67,9 +68,8 @@ const routeDelete = createRoute({
204: {
description: "Role removed",
},
404: {
description: "User or role not found",
description: "Role not found",
content: {
"application/json": {
schema: ErrorSchema,
@ -90,19 +90,14 @@ const routeDelete = createRoute({
export default apiRoute((app) => {
app.openapi(routePost, async (context) => {
const { user } = context.get("auth");
const { id, role_id } = context.req.valid("param");
const { role_id } = context.req.valid("param");
const targetUser = context.get("user");
const targetUser = await User.fromId(id);
const role = await Role.fromId(role_id);
if (!role) {
throw new ApiError(404, "Role not found");
}
if (!targetUser) {
throw new ApiError(404, "User not found");
}
// Priority check
const userRoles = await Role.getUserRoles(user.id, user.data.isAdmin);
@ -125,19 +120,15 @@ export default apiRoute((app) => {
app.openapi(routeDelete, async (context) => {
const { user } = context.get("auth");
const { id, role_id } = context.req.valid("param");
const { role_id } = context.req.valid("param");
const targetUser = context.get("user");
const targetUser = await User.fromId(id);
const role = await Role.fromId(role_id);
if (!role) {
throw new ApiError(404, "Role not found");
}
if (!targetUser) {
throw new ApiError(404, "User not found");
}
// Priority check
const userRoles = await Role.getUserRoles(user.id, user.data.isAdmin);

View file

@ -1,9 +1,7 @@
import { apiRoute, auth } from "@/api";
import { apiRoute, auth, withUserParam } from "@/api";
import { createRoute } from "@hono/zod-openapi";
import { Role, User } from "@versia/kit/db";
import { Role } from "@versia/kit/db";
import { z } from "zod";
import { ApiError } from "~/classes/errors/api-error";
import { ErrorSchema } from "~/types/api";
const route = createRoute({
method: "get",
@ -13,6 +11,7 @@ const route = createRoute({
auth({
auth: false,
}),
withUserParam,
] as const,
request: {
params: z.object({
@ -28,26 +27,12 @@ const route = createRoute({
},
},
},
404: {
description: "User not found",
content: {
"application/json": {
schema: ErrorSchema,
},
},
},
},
});
export default apiRoute((app) => {
app.openapi(route, async (context) => {
const { id } = context.req.valid("param");
const targetUser = await User.fromId(id);
if (!targetUser) {
throw new ApiError(404, "User not found");
}
const targetUser = context.get("user");
const roles = await Role.getUserRoles(
targetUser.id,

View file

@ -1,11 +1,9 @@
import { apiRoute, auth } from "@/api";
import { apiRoute, auth, withUserParam } from "@/api";
import { createRoute } from "@hono/zod-openapi";
import { Note, Timeline, User } from "@versia/kit/db";
import { Note, Timeline } from "@versia/kit/db";
import { Notes, RolePermissions } from "@versia/kit/tables";
import { and, eq, gt, gte, inArray, isNull, lt, or, sql } from "drizzle-orm";
import { z } from "zod";
import { ApiError } from "~/classes/errors/api-error";
import { ErrorSchema } from "~/types/api";
const schemas = {
param: z.object({
@ -53,6 +51,7 @@ const route = createRoute({
],
scopes: ["read:statuses"],
}),
withUserParam,
] as const,
request: {
params: schemas.param,
@ -72,27 +71,13 @@ const route = createRoute({
},
},
},
404: {
description: "User not found",
content: {
"application/json": {
schema: ErrorSchema,
},
},
},
},
});
export default apiRoute((app) =>
app.openapi(route, async (context) => {
const { id } = context.req.valid("param");
const { user } = context.get("auth");
const otherUser = await User.fromId(id);
if (!otherUser) {
throw new ApiError(404, "User not found");
}
const otherUser = context.get("user");
const {
max_id,
@ -110,7 +95,7 @@ export default apiRoute((app) =>
max_id ? lt(Notes.id, max_id) : undefined,
since_id ? gte(Notes.id, since_id) : undefined,
min_id ? gt(Notes.id, min_id) : undefined,
eq(Notes.authorId, id),
eq(Notes.authorId, otherUser.id),
only_media
? sql`EXISTS (SELECT 1 FROM "Attachments" WHERE "Attachments"."noteId" = ${Notes.id})`
: undefined,

View file

@ -1,10 +1,8 @@
import { apiRoute, auth } from "@/api";
import { apiRoute, auth, withUserParam } from "@/api";
import { createRoute } from "@hono/zod-openapi";
import { Relationship, User } from "@versia/kit/db";
import { Relationship } from "@versia/kit/db";
import { RolePermissions } from "@versia/kit/tables";
import { z } from "zod";
import { ApiError } from "~/classes/errors/api-error";
import { ErrorSchema } from "~/types/api";
const route = createRoute({
method: "post",
@ -20,6 +18,7 @@ const route = createRoute({
RolePermissions.ViewAccounts,
],
}),
withUserParam,
] as const,
request: {
params: z.object({
@ -35,27 +34,13 @@ const route = createRoute({
},
},
},
404: {
description: "User not found",
content: {
"application/json": {
schema: ErrorSchema,
},
},
},
},
});
export default apiRoute((app) =>
app.openapi(route, async (context) => {
const { id } = context.req.valid("param");
const { user } = context.get("auth");
const otherUser = await User.fromId(id);
if (!otherUser) {
throw new ApiError(404, "User not found");
}
const otherUser = context.get("user");
const foundRelationship = await Relationship.fromOwnerAndSubject(
user,

View file

@ -1,9 +1,8 @@
import { apiRoute, auth } from "@/api";
import { apiRoute, auth, withUserParam } from "@/api";
import { createRoute } from "@hono/zod-openapi";
import { Relationship, User } from "@versia/kit/db";
import { Relationship } from "@versia/kit/db";
import { RolePermissions } from "@versia/kit/tables";
import { z } from "zod";
import { ApiError } from "~/classes/errors/api-error";
import { ErrorSchema } from "~/types/api";
const route = createRoute({
@ -20,6 +19,7 @@ const route = createRoute({
RolePermissions.ViewAccounts,
],
}),
withUserParam,
] as const,
request: {
params: z.object({
@ -35,15 +35,6 @@ const route = createRoute({
},
},
},
404: {
description: "User not found",
content: {
"application/json": {
schema: ErrorSchema,
},
},
},
500: {
description: "Failed to unfollow user during federation",
content: {
@ -57,14 +48,8 @@ const route = createRoute({
export default apiRoute((app) =>
app.openapi(route, async (context) => {
const { id } = context.req.valid("param");
const { user } = context.get("auth");
const otherUser = await User.fromId(id);
if (!otherUser) {
throw new ApiError(404, "User not found");
}
const otherUser = context.get("user");
const foundRelationship = await Relationship.fromOwnerAndSubject(
user,

View file

@ -1,10 +1,8 @@
import { apiRoute, auth } from "@/api";
import { apiRoute, auth, withUserParam } from "@/api";
import { createRoute } from "@hono/zod-openapi";
import { Relationship, User } from "@versia/kit/db";
import { Relationship } from "@versia/kit/db";
import { RolePermissions } from "@versia/kit/tables";
import { z } from "zod";
import { ApiError } from "~/classes/errors/api-error";
import { ErrorSchema } from "~/types/api";
const route = createRoute({
method: "post",
@ -20,6 +18,7 @@ const route = createRoute({
RolePermissions.ViewAccounts,
],
}),
withUserParam,
] as const,
request: {
params: z.object({
@ -35,28 +34,13 @@ const route = createRoute({
},
},
},
404: {
description: "User not found",
content: {
"application/json": {
schema: ErrorSchema,
},
},
},
},
});
export default apiRoute((app) =>
app.openapi(route, async (context) => {
const { id } = context.req.valid("param");
const { user } = context.get("auth");
const otherUser = await User.fromId(id);
if (!otherUser) {
throw new ApiError(404, "User not found");
}
const otherUser = context.get("user");
const foundRelationship = await Relationship.fromOwnerAndSubject(
user,

View file

@ -1,10 +1,8 @@
import { apiRoute, auth } from "@/api";
import { apiRoute, auth, withUserParam } from "@/api";
import { createRoute } from "@hono/zod-openapi";
import { Relationship, User } from "@versia/kit/db";
import { Relationship } from "@versia/kit/db";
import { RolePermissions } from "@versia/kit/tables";
import { z } from "zod";
import { ApiError } from "~/classes/errors/api-error";
import { ErrorSchema } from "~/types/api";
const route = createRoute({
method: "post",
@ -20,6 +18,7 @@ const route = createRoute({
RolePermissions.ViewAccounts,
],
}),
withUserParam,
] as const,
request: {
params: z.object({
@ -35,28 +34,13 @@ const route = createRoute({
},
},
},
404: {
description: "User not found",
content: {
"application/json": {
schema: ErrorSchema,
},
},
},
},
});
export default apiRoute((app) =>
app.openapi(route, async (context) => {
const { id } = context.req.valid("param");
const { user } = context.get("auth");
const otherUser = await User.fromId(id);
if (!otherUser) {
throw new ApiError(404, "User not found");
}
const otherUser = context.get("user");
const foundRelationship = await Relationship.fromOwnerAndSubject(
user,

View file

@ -1,9 +1,8 @@
import { apiRoute, auth } from "@/api";
import { apiRoute, auth, withNoteParam } from "@/api";
import { createRoute } from "@hono/zod-openapi";
import { Note } from "@versia/kit/db";
import { RolePermissions } from "@versia/kit/tables";
import { z } from "zod";
import { ApiError } from "~/classes/errors/api-error";
import { ErrorSchema } from "~/types/api";
const route = createRoute({
@ -14,6 +13,7 @@ const route = createRoute({
auth: false,
permissions: [RolePermissions.ViewNotes],
}),
withNoteParam,
] as const,
summary: "Get status context",
request: {
@ -46,19 +46,12 @@ const route = createRoute({
export default apiRoute((app) =>
app.openapi(route, async (context) => {
const { id } = context.req.valid("param");
const { user } = context.get("auth");
const note = context.get("note");
const foundStatus = await Note.fromId(id, user?.id);
const ancestors = await note.getAncestors(user ?? null);
if (!foundStatus) {
throw new ApiError(404, "Note not found");
}
const ancestors = await foundStatus.getAncestors(user ?? null);
const descendants = await foundStatus.getDescendants(user ?? null);
const descendants = await note.getDescendants(user ?? null);
return context.json(
{

View file

@ -32,7 +32,6 @@ describe("/api/v1/statuses/:id/favourite", () => {
},
},
);
expect(response.status).toBe(200);
const json = (await response.json()) as ApiStatus;

View file

@ -1,10 +1,8 @@
import { apiRoute, auth } from "@/api";
import { apiRoute, auth, withNoteParam } from "@/api";
import { createRoute } from "@hono/zod-openapi";
import { Note } from "@versia/kit/db";
import { RolePermissions } from "@versia/kit/tables";
import { z } from "zod";
import { ApiError } from "~/classes/errors/api-error";
import { ErrorSchema } from "~/types/api";
const route = createRoute({
method: "post",
@ -18,6 +16,7 @@ const route = createRoute({
RolePermissions.ViewNotes,
],
}),
withNoteParam,
] as const,
request: {
params: z.object({
@ -33,29 +32,13 @@ const route = createRoute({
},
},
},
404: {
description: "Record not found",
content: {
"application/json": {
schema: ErrorSchema,
},
},
},
},
});
export default apiRoute((app) =>
app.openapi(route, async (context) => {
const { id } = context.req.valid("param");
const { user } = context.get("auth");
const note = await Note.fromId(id, user?.id);
if (!(note && (await note?.isViewableByUser(user)))) {
throw new ApiError(404, "Note not found");
}
const note = context.get("note");
await user.like(note);

View file

@ -1,11 +1,9 @@
import { apiRoute, auth } from "@/api";
import { apiRoute, auth, withNoteParam } from "@/api";
import { createRoute } from "@hono/zod-openapi";
import { Note, Timeline, User } from "@versia/kit/db";
import { Timeline, User } from "@versia/kit/db";
import { RolePermissions, Users } from "@versia/kit/tables";
import { and, gt, gte, lt, sql } from "drizzle-orm";
import { z } from "zod";
import { ApiError } from "~/classes/errors/api-error";
import { ErrorSchema } from "~/types/api";
const schemas = {
query: z.object({
@ -31,6 +29,7 @@ const route = createRoute({
RolePermissions.ViewNoteLikes,
],
}),
withNoteParam,
] as const,
request: {
params: schemas.param,
@ -45,30 +44,13 @@ const route = createRoute({
},
},
},
404: {
description: "Record not found",
content: {
"application/json": {
schema: ErrorSchema,
},
},
},
},
});
export default apiRoute((app) =>
app.openapi(route, async (context) => {
const { max_id, since_id, min_id, limit } = context.req.valid("query");
const { id } = context.req.valid("param");
const { user } = context.get("auth");
const note = await Note.fromId(id, user?.id);
if (!(note && (await note?.isViewableByUser(user)))) {
throw new ApiError(404, "Note not found");
}
const note = context.get("note");
const { objects, link } = await Timeline.getUserTimeline(
and(

View file

@ -1,4 +1,4 @@
import { apiRoute, auth, jsonOrForm } from "@/api";
import { apiRoute, auth, jsonOrForm, withNoteParam } from "@/api";
import { createRoute } from "@hono/zod-openapi";
import { Attachment, Note } from "@versia/kit/db";
import { RolePermissions } from "@versia/kit/tables";
@ -75,6 +75,7 @@ const routeGet = createRoute({
auth: false,
permissions: [RolePermissions.ViewNotes],
}),
withNoteParam,
] as const,
request: {
params: schemas.param,
@ -111,6 +112,7 @@ const routeDelete = createRoute({
RolePermissions.ViewNotes,
],
}),
withNoteParam,
] as const,
request: {
params: schemas.param,
@ -156,6 +158,7 @@ const routePut = createRoute({
],
}),
jsonOrForm(),
withNoteParam,
] as const,
request: {
params: schemas.param,
@ -190,14 +193,6 @@ const routePut = createRoute({
},
},
},
404: {
description: "Record not found",
content: {
"application/json": {
schema: ErrorSchema,
},
},
},
422: {
description: "Invalid media IDs",
content: {
@ -211,27 +206,15 @@ const routePut = createRoute({
export default apiRoute((app) => {
app.openapi(routeGet, async (context) => {
const { id } = context.req.valid("param");
const { user } = context.get("auth");
const note = await Note.fromId(id, user?.id);
if (!(note && (await note?.isViewableByUser(user)))) {
throw new ApiError(404, "Note not found");
}
const note = context.get("note");
return context.json(await note.toApi(user), 200);
});
app.openapi(routeDelete, async (context) => {
const { id } = context.req.valid("param");
const { user } = context.get("auth");
const note = await Note.fromId(id, user?.id);
if (!(note && (await note?.isViewableByUser(user)))) {
throw new ApiError(404, "Note not found");
}
const note = context.get("note");
if (note.author.id !== user.id) {
throw new ApiError(401, "Unauthorized");
@ -246,14 +229,8 @@ export default apiRoute((app) => {
});
app.openapi(routePut, async (context) => {
const { id } = context.req.valid("param");
const { user } = context.get("auth");
const note = await Note.fromId(id, user?.id);
if (!(note && (await note?.isViewableByUser(user)))) {
throw new ApiError(404, "Note not found");
}
const note = context.get("note");
if (note.author.id !== user.id) {
throw new ApiError(401, "Unauthorized");

View file

@ -1,4 +1,4 @@
import { apiRoute, auth } from "@/api";
import { apiRoute, auth, withNoteParam } from "@/api";
import { createRoute } from "@hono/zod-openapi";
import { Note, db } from "@versia/kit/db";
import { RolePermissions } from "@versia/kit/tables";
@ -7,12 +7,6 @@ import { z } from "zod";
import { ApiError } from "~/classes/errors/api-error";
import { ErrorSchema } from "~/types/api";
const schemas = {
param: z.object({
id: z.string().uuid(),
}),
};
const route = createRoute({
method: "post",
path: "/api/v1/statuses/{id}/pin",
@ -25,9 +19,12 @@ const route = createRoute({
RolePermissions.ViewNotes,
],
}),
withNoteParam,
] as const,
request: {
params: schemas.param,
params: z.object({
id: z.string().uuid(),
}),
},
responses: {
200: {
@ -46,14 +43,6 @@ const route = createRoute({
},
},
},
404: {
description: "Record not found",
content: {
"application/json": {
schema: ErrorSchema,
},
},
},
422: {
description: "Already pinned",
content: {
@ -67,16 +56,10 @@ const route = createRoute({
export default apiRoute((app) =>
app.openapi(route, async (context) => {
const { id } = context.req.valid("param");
const { user } = context.get("auth");
const note = context.get("note");
const foundStatus = await Note.fromId(id, user?.id);
if (!foundStatus) {
throw new ApiError(404, "Note not found");
}
if (foundStatus.author.id !== user.id) {
if (note.author.id !== user.id) {
throw new ApiError(401, "Unauthorized");
}
@ -84,7 +67,7 @@ export default apiRoute((app) =>
await db.query.UserToPinnedNotes.findFirst({
where: (userPinnedNote, { and, eq }): SQL | undefined =>
and(
eq(userPinnedNote.noteId, foundStatus.data.id),
eq(userPinnedNote.noteId, note.data.id),
eq(userPinnedNote.userId, user.id),
),
})
@ -92,8 +75,8 @@ export default apiRoute((app) =>
throw new ApiError(422, "Already pinned");
}
await user.pin(foundStatus);
await user.pin(note);
return context.json(await foundStatus.toApi(user), 200);
return context.json(await note.toApi(user), 200);
}),
);

View file

@ -1,4 +1,4 @@
import { apiRoute, auth, jsonOrForm } from "@/api";
import { apiRoute, auth, jsonOrForm, withNoteParam } from "@/api";
import { createRoute } from "@hono/zod-openapi";
import { Note } from "@versia/kit/db";
import { Notes, RolePermissions } from "@versia/kit/tables";
@ -29,6 +29,7 @@ const route = createRoute({
],
}),
jsonOrForm(),
withNoteParam,
] as const,
request: {
params: schemas.param,
@ -55,15 +56,6 @@ const route = createRoute({
},
},
},
404: {
description: "Record not found",
content: {
"application/json": {
schema: ErrorSchema,
},
},
},
422: {
description: "Already reblogged",
content: {
@ -85,15 +77,9 @@ const route = createRoute({
export default apiRoute((app) =>
app.openapi(route, async (context) => {
const { id } = context.req.valid("param");
const { visibility } = context.req.valid("json");
const { user } = context.get("auth");
const note = await Note.fromId(id, user.id);
if (!(note && (await note?.isViewableByUser(user)))) {
throw new ApiError(404, "Note not found");
}
const note = context.get("note");
const existingReblog = await Note.fromSql(
and(eq(Notes.authorId, user.id), eq(Notes.reblogId, note.data.id)),

View file

@ -1,11 +1,9 @@
import { apiRoute, auth } from "@/api";
import { apiRoute, auth, withNoteParam } from "@/api";
import { createRoute } from "@hono/zod-openapi";
import { Note, Timeline, User } from "@versia/kit/db";
import { Timeline, User } from "@versia/kit/db";
import { RolePermissions, Users } from "@versia/kit/tables";
import { and, gt, gte, lt, sql } from "drizzle-orm";
import { z } from "zod";
import { ApiError } from "~/classes/errors/api-error";
import { ErrorSchema } from "~/types/api";
const schemas = {
param: z.object({
@ -31,6 +29,7 @@ const route = createRoute({
RolePermissions.ViewNoteBoosts,
],
}),
withNoteParam,
] as const,
request: {
params: schemas.param,
@ -45,29 +44,13 @@ const route = createRoute({
},
},
},
404: {
description: "Record not found",
content: {
"application/json": {
schema: ErrorSchema,
},
},
},
},
});
export default apiRoute((app) =>
app.openapi(route, async (context) => {
const { id } = context.req.valid("param");
const { max_id, min_id, since_id, limit } = context.req.valid("query");
const { user } = context.get("auth");
const note = await Note.fromId(id, user.id);
if (!(note && (await note?.isViewableByUser(user)))) {
throw new ApiError(404, "Note not found");
}
const note = context.get("note");
const { objects, link } = await Timeline.getUserTimeline(
and(

View file

@ -1,11 +1,8 @@
import { apiRoute, auth } from "@/api";
import { apiRoute, auth, withNoteParam } from "@/api";
import { createRoute } from "@hono/zod-openapi";
import type { StatusSource as ApiStatusSource } from "@versia/client/types";
import { Note } from "@versia/kit/db";
import { RolePermissions } from "@versia/kit/tables";
import { z } from "zod";
import { ApiError } from "~/classes/errors/api-error";
import { ErrorSchema } from "~/types/api";
const route = createRoute({
method: "get",
@ -19,6 +16,7 @@ const route = createRoute({
RolePermissions.ViewNotes,
],
}),
withNoteParam,
] as const,
request: {
params: z.object({
@ -38,28 +36,12 @@ const route = createRoute({
},
},
},
404: {
description: "Record not found",
content: {
"application/json": {
schema: ErrorSchema,
},
},
},
},
});
export default apiRoute((app) =>
app.openapi(route, async (context) => {
const { id } = context.req.valid("param");
const { user } = context.get("auth");
const note = await Note.fromId(id, user.id);
if (!(note && (await note?.isViewableByUser(user)))) {
throw new ApiError(404, "Note not found");
}
app.openapi(route, (context) => {
const note = context.get("note");
return context.json(
{

View file

@ -1,10 +1,8 @@
import { apiRoute, auth } from "@/api";
import { apiRoute, auth, withNoteParam } from "@/api";
import { createRoute } from "@hono/zod-openapi";
import { Note } from "@versia/kit/db";
import { RolePermissions } from "@versia/kit/tables";
import { z } from "zod";
import { ApiError } from "~/classes/errors/api-error";
import { ErrorSchema } from "~/types/api";
const route = createRoute({
method: "post",
@ -18,6 +16,7 @@ const route = createRoute({
RolePermissions.ViewNotes,
],
}),
withNoteParam,
] as const,
request: {
params: z.object({
@ -33,28 +32,13 @@ const route = createRoute({
},
},
},
404: {
description: "Record not found",
content: {
"application/json": {
schema: ErrorSchema,
},
},
},
},
});
export default apiRoute((app) =>
app.openapi(route, async (context) => {
const { id } = context.req.valid("param");
const { user } = context.get("auth");
const note = await Note.fromId(id, user.id);
if (!(note && (await note?.isViewableByUser(user)))) {
throw new ApiError(404, "Note not found");
}
const note = context.get("note");
await user.unlike(note);

View file

@ -1,4 +1,4 @@
import { apiRoute, auth } from "@/api";
import { apiRoute, auth, withNoteParam } from "@/api";
import { createRoute } from "@hono/zod-openapi";
import { Note } from "@versia/kit/db";
import { RolePermissions } from "@versia/kit/tables";
@ -18,6 +18,7 @@ const route = createRoute({
RolePermissions.ViewNotes,
],
}),
withNoteParam,
] as const,
request: {
params: z.object({
@ -41,38 +42,24 @@ const route = createRoute({
},
},
},
404: {
description: "Record not found",
content: {
"application/json": {
schema: ErrorSchema,
},
},
},
},
});
export default apiRoute((app) =>
app.openapi(route, async (context) => {
const { id } = context.req.valid("param");
const { user } = context.get("auth");
const note = context.get("note");
const status = await Note.fromId(id, user.id);
if (!status) {
throw new ApiError(404, "Note not found");
}
if (status.author.id !== user.id) {
if (note.author.id !== user.id) {
throw new ApiError(401, "Unauthorized");
}
await user.unpin(status);
await user.unpin(note);
if (!status) {
if (!note) {
throw new ApiError(404, "Note not found");
}
return context.json(await status.toApi(user), 200);
return context.json(await note.toApi(user), 200);
}),
);

View file

@ -1,4 +1,4 @@
import { apiRoute, auth } from "@/api";
import { apiRoute, auth, withNoteParam } from "@/api";
import { createRoute } from "@hono/zod-openapi";
import { Note } from "@versia/kit/db";
import { Notes, RolePermissions } from "@versia/kit/tables";
@ -19,6 +19,7 @@ const route = createRoute({
RolePermissions.ViewNotes,
],
}),
withNoteParam,
] as const,
request: {
params: z.object({
@ -34,15 +35,6 @@ const route = createRoute({
},
},
},
404: {
description: "Record not found",
content: {
"application/json": {
schema: ErrorSchema,
},
},
},
422: {
description: "Not already reblogged",
content: {
@ -58,13 +50,7 @@ export default apiRoute((app) =>
app.openapi(route, async (context) => {
const { id } = context.req.valid("param");
const { user } = context.get("auth");
const note = await Note.fromId(id, user.id);
// Check if user is authorized to view this status (if it's private)
if (!(note && (await note?.isViewableByUser(user)))) {
throw new ApiError(404, "Note not found");
}
const note = context.get("note");
const existingReblog = await Note.fromSql(
and(eq(Notes.authorId, user.id), eq(Notes.reblogId, note.data.id)),

BIN
bun.lockb

Binary file not shown.

View file

@ -113,6 +113,7 @@
"@hono/prometheus": "^1.0.1",
"@hono/swagger-ui": "^0.5.0",
"@hono/zod-openapi": "0.18.3",
"@hono/zod-validator": "^0.4.2",
"@inquirer/confirm": "^5.1.1",
"@inquirer/input": "^4.1.1",
"@json2csv/plainjs": "^7.0.6",

View file

@ -1,11 +1,13 @@
import type { OpenAPIHono } from "@hono/zod-openapi";
import { zValidator } from "@hono/zod-validator";
import { getLogger } from "@logtape/logtape";
import { Application, Token, db } from "@versia/kit/db";
import { Application, 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";
import { type SQL, eq } from "drizzle-orm";
import type { Context, MiddlewareHandler } from "hono";
import { every } from "hono/combine";
import { createMiddleware } from "hono/factory";
import {
anyOf,
@ -22,7 +24,7 @@ import {
oneOrMore,
} from "magic-regexp";
import { type ParsedQs, parse } from "qs";
import type { z } from "zod";
import { z } from "zod";
import { fromZodError } from "zod-validation-error";
import { ApiError } from "~/classes/errors/api-error";
import type { AuthData } from "~/classes/functions/user";
@ -292,6 +294,85 @@ export const auth = <AuthRequired extends boolean>(options: {
});
};
type WithIdParam = {
in: { param: { id: string } };
out: { param: { id: string } };
};
/**
* Middleware to check if a note exists and is viewable by the user.
*
* Useful in /api/v1/statuses/:id/* routes
* @returns MiddlewareHandler
*/
export const withNoteParam = every(
zValidator("param", z.object({ id: z.string().uuid() }), handleZodError),
createMiddleware<
HonoEnv & {
Variables: {
note: Note;
};
},
string,
WithIdParam
>(async (context, next) => {
const { id } = context.req.valid("param");
const { user } = context.get("auth");
const note = await Note.fromId(id, user?.id);
if (!(note && (await note.isViewableByUser(user)))) {
throw new ApiError(404, "Note not found");
}
context.set("note", note);
await next();
}),
) as MiddlewareHandler<
HonoEnv & {
Variables: {
note: Note;
};
}
>;
/**
* Middleware to check if a user exists
*
* Useful in /api/v1/accounts/:id/* routes
* @returns MiddlewareHandler
*/
export const withUserParam = every(
zValidator("param", z.object({ id: z.string().uuid() }), handleZodError),
createMiddleware<
HonoEnv & {
Variables: {
user: User;
};
},
string,
WithIdParam
>(async (context, next) => {
const { id } = context.req.valid("param");
const user = await User.fromId(id);
if (!user) {
throw new ApiError(404, "User not found");
}
context.set("user", user);
await next();
}),
) as MiddlewareHandler<
HonoEnv & {
Variables: {
user: User;
};
}
>;
// Helper function to parse form data
async function parseFormData(context: Context): Promise<{
parsed: ParsedQs;