Rewrite every route to use the new framework

This commit is contained in:
Jesse Wierzbinski 2024-03-10 13:25:44 -10:00
parent 05140f0d6f
commit 852efaea50
No known key found for this signature in database
51 changed files with 413 additions and 707 deletions

View file

@ -1,14 +1,10 @@
import { errorResponse, jsonResponse } from "@response";
import type { MatchedRoute } from "bun";
import {
createNewRelationship,
relationshipToAPI,
} from "~database/entities/Relationship";
import {
getFromRequest,
getRelationshipToOtherUser,
} from "~database/entities/User";
import { applyConfig } from "@api";
import { getRelationshipToOtherUser } from "~database/entities/User";
import { apiRoute, applyConfig } from "@api";
import { client } from "~database/datasource";
export const meta = applyConfig({
@ -26,13 +22,10 @@ export const meta = applyConfig({
/**
* Blocks a user
*/
export default async (
req: Request,
matchedRoute: MatchedRoute
): Promise<Response> => {
export default apiRoute(async (req, matchedRoute, extraData) => {
const id = matchedRoute.params.id;
const { user: self } = await getFromRequest(req);
const { user: self } = extraData.auth;
if (!self) return errorResponse("Unauthorized", 401);
@ -84,4 +77,4 @@ export default async (
});
return jsonResponse(relationshipToAPI(relationship));
};
});

View file

@ -1,15 +1,10 @@
import { parseRequest } from "@request";
import { errorResponse, jsonResponse } from "@response";
import type { MatchedRoute } from "bun";
import {
createNewRelationship,
relationshipToAPI,
} from "~database/entities/Relationship";
import {
getFromRequest,
getRelationshipToOtherUser,
} from "~database/entities/User";
import { applyConfig } from "@api";
import { getRelationshipToOtherUser } from "~database/entities/User";
import { apiRoute, applyConfig } from "@api";
import { client } from "~database/datasource";
export const meta = applyConfig({
@ -27,21 +22,18 @@ export const meta = applyConfig({
/**
* Follow a user
*/
export default async (
req: Request,
matchedRoute: MatchedRoute
): Promise<Response> => {
export default apiRoute<{
reblogs?: boolean;
notify?: boolean;
languages?: string[];
}>(async (req, matchedRoute, extraData) => {
const id = matchedRoute.params.id;
const { user: self } = await getFromRequest(req);
const { user: self } = extraData.auth;
if (!self) return errorResponse("Unauthorized", 401);
const { languages, notify, reblogs } = await parseRequest<{
reblogs?: boolean;
notify?: boolean;
languages?: string[];
}>(req);
const { languages, notify, reblogs } = extraData.parsedRequest;
const user = await client.user.findUnique({
where: { id },
@ -103,4 +95,4 @@ export default async (
});
return jsonResponse(relationshipToAPI(relationship));
};
});

View file

@ -1,8 +1,7 @@
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
import { errorResponse, jsonResponse } from "@response";
import type { MatchedRoute } from "bun";
import { userRelations, userToAPI } from "~database/entities/User";
import { applyConfig } from "@api";
import { apiRoute, applyConfig } from "@api";
import { client } from "~database/datasource";
export const meta = applyConfig({
@ -20,24 +19,16 @@ export const meta = applyConfig({
/**
* Fetch all statuses for a user
*/
export default async (
req: Request,
matchedRoute: MatchedRoute
): Promise<Response> => {
export default apiRoute<{
max_id?: string;
since_id?: string;
min_id?: string;
limit?: number;
}>(async (req, matchedRoute, extraData) => {
const id = matchedRoute.params.id;
// TODO: Add pinned
const {
max_id,
min_id,
since_id,
limit = 20,
}: {
max_id?: string;
since_id?: string;
min_id?: string;
limit?: number;
} = matchedRoute.query;
const { max_id, min_id, since_id, limit = 20 } = extraData.parsedRequest;
const user = await client.user.findUnique({
where: { id },
@ -86,4 +77,4 @@ export default async (
Link: linkHeader.join(", "),
}
);
};
});

View file

@ -1,8 +1,7 @@
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
import { errorResponse, jsonResponse } from "@response";
import type { MatchedRoute } from "bun";
import { userRelations, userToAPI } from "~database/entities/User";
import { applyConfig } from "@api";
import { apiRoute, applyConfig } from "@api";
import { client } from "~database/datasource";
export const meta = applyConfig({
@ -20,24 +19,16 @@ export const meta = applyConfig({
/**
* Fetch all statuses for a user
*/
export default async (
req: Request,
matchedRoute: MatchedRoute
): Promise<Response> => {
export default apiRoute<{
max_id?: string;
since_id?: string;
min_id?: string;
limit?: number;
}>(async (req, matchedRoute, extraData) => {
const id = matchedRoute.params.id;
// TODO: Add pinned
const {
max_id,
min_id,
since_id,
limit = 20,
}: {
max_id?: string;
since_id?: string;
min_id?: string;
limit?: number;
} = matchedRoute.query;
const { max_id, min_id, since_id, limit = 20 } = extraData.parsedRequest;
const user = await client.user.findUnique({
where: { id },
@ -86,4 +77,4 @@ export default async (
Link: linkHeader.join(", "),
}
);
};
});

View file

@ -1,12 +1,7 @@
import { errorResponse, jsonResponse } from "@response";
import type { MatchedRoute } from "bun";
import type { UserWithRelations } from "~database/entities/User";
import {
getFromRequest,
userRelations,
userToAPI,
} from "~database/entities/User";
import { applyConfig } from "@api";
import { userRelations, userToAPI } from "~database/entities/User";
import { apiRoute, applyConfig } from "@api";
import { client } from "~database/datasource";
export const meta = applyConfig({
@ -24,17 +19,14 @@ export const meta = applyConfig({
/**
* Fetch a user
*/
export default async (
req: Request,
matchedRoute: MatchedRoute
): Promise<Response> => {
export default apiRoute(async (req, matchedRoute, extraData) => {
const id = matchedRoute.params.id;
// Check if ID is valid UUID
if (!id.match(/^[0-9a-fA-F]{24}$/)) {
return errorResponse("Invalid ID", 404);
}
const { user } = await getFromRequest(req);
const { user } = extraData.auth;
let foundUser: UserWithRelations | null;
try {
@ -49,4 +41,4 @@ export default async (
if (!foundUser) return errorResponse("User not found", 404);
return jsonResponse(userToAPI(foundUser, user?.id === foundUser.id));
};
});

View file

@ -1,15 +1,10 @@
import { parseRequest } from "@request";
import { errorResponse, jsonResponse } from "@response";
import type { MatchedRoute } from "bun";
import {
createNewRelationship,
relationshipToAPI,
} from "~database/entities/Relationship";
import {
getFromRequest,
getRelationshipToOtherUser,
} from "~database/entities/User";
import { applyConfig } from "@api";
import { getRelationshipToOtherUser } from "~database/entities/User";
import { apiRoute, applyConfig } from "@api";
import { client } from "~database/datasource";
export const meta = applyConfig({
@ -27,21 +22,18 @@ export const meta = applyConfig({
/**
* Mute a user
*/
export default async (
req: Request,
matchedRoute: MatchedRoute
): Promise<Response> => {
export default apiRoute<{
notifications: boolean;
duration: number;
}>(async (req, matchedRoute, extraData) => {
const id = matchedRoute.params.id;
const { user: self } = await getFromRequest(req);
const { user: self } = extraData.auth;
if (!self) return errorResponse("Unauthorized", 401);
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const { notifications, duration } = await parseRequest<{
notifications: boolean;
duration: number;
}>(req);
const { notifications, duration } = extraData.parsedRequest;
const user = await client.user.findUnique({
where: { id },
@ -97,4 +89,4 @@ export default async (
// TODO: Implement duration
return jsonResponse(relationshipToAPI(relationship));
};
});

View file

@ -1,15 +1,10 @@
import { parseRequest } from "@request";
import { errorResponse, jsonResponse } from "@response";
import type { MatchedRoute } from "bun";
import {
createNewRelationship,
relationshipToAPI,
} from "~database/entities/Relationship";
import {
getFromRequest,
getRelationshipToOtherUser,
} from "~database/entities/User";
import { applyConfig } from "@api";
import { getRelationshipToOtherUser } from "~database/entities/User";
import { apiRoute, applyConfig } from "@api";
import { client } from "~database/datasource";
export const meta = applyConfig({
@ -27,19 +22,16 @@ export const meta = applyConfig({
/**
* Sets a user note
*/
export default async (
req: Request,
matchedRoute: MatchedRoute
): Promise<Response> => {
export default apiRoute<{
comment: string;
}>(async (req, matchedRoute, extraData) => {
const id = matchedRoute.params.id;
const { user: self } = await getFromRequest(req);
const { user: self } = extraData.auth;
if (!self) return errorResponse("Unauthorized", 401);
const { comment } = await parseRequest<{
comment: string;
}>(req);
const { comment } = extraData.parsedRequest;
const user = await client.user.findUnique({
where: { id },
@ -87,4 +79,4 @@ export default async (
});
return jsonResponse(relationshipToAPI(relationship));
};
});

View file

@ -1,14 +1,10 @@
import { errorResponse, jsonResponse } from "@response";
import type { MatchedRoute } from "bun";
import {
createNewRelationship,
relationshipToAPI,
} from "~database/entities/Relationship";
import {
getFromRequest,
getRelationshipToOtherUser,
} from "~database/entities/User";
import { applyConfig } from "@api";
import { getRelationshipToOtherUser } from "~database/entities/User";
import { apiRoute, applyConfig } from "@api";
import { client } from "~database/datasource";
export const meta = applyConfig({
@ -26,13 +22,10 @@ export const meta = applyConfig({
/**
* Pin a user
*/
export default async (
req: Request,
matchedRoute: MatchedRoute
): Promise<Response> => {
export default apiRoute(async (req, matchedRoute, extraData) => {
const id = matchedRoute.params.id;
const { user: self } = await getFromRequest(req);
const { user: self } = extraData.auth;
if (!self) return errorResponse("Unauthorized", 401);
@ -84,4 +77,4 @@ export default async (
});
return jsonResponse(relationshipToAPI(relationship));
};
});

View file

@ -1,14 +1,10 @@
import { errorResponse, jsonResponse } from "@response";
import type { MatchedRoute } from "bun";
import {
createNewRelationship,
relationshipToAPI,
} from "~database/entities/Relationship";
import {
getFromRequest,
getRelationshipToOtherUser,
} from "~database/entities/User";
import { applyConfig } from "@api";
import { getRelationshipToOtherUser } from "~database/entities/User";
import { apiRoute, applyConfig } from "@api";
import { client } from "~database/datasource";
export const meta = applyConfig({
@ -26,13 +22,10 @@ export const meta = applyConfig({
/**
* Removes an account from your followers list
*/
export default async (
req: Request,
matchedRoute: MatchedRoute
): Promise<Response> => {
export default apiRoute(async (req, matchedRoute, extraData) => {
const id = matchedRoute.params.id;
const { user: self } = await getFromRequest(req);
const { user: self } = extraData.auth;
if (!self) return errorResponse("Unauthorized", 401);
@ -98,4 +91,4 @@ export default async (
}
return jsonResponse(relationshipToAPI(relationship));
};
});

View file

@ -1,9 +1,8 @@
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
import { errorResponse, jsonResponse } from "@response";
import type { MatchedRoute } from "bun";
import { statusAndUserRelations, statusToAPI } from "~database/entities/Status";
import { userRelations } from "~database/entities/User";
import { applyConfig } from "@api";
import { apiRoute, applyConfig } from "@api";
import { client } from "~database/datasource";
export const meta = applyConfig({
@ -21,10 +20,18 @@ export const meta = applyConfig({
/**
* Fetch all statuses for a user
*/
export default async (
req: Request,
matchedRoute: MatchedRoute
): Promise<Response> => {
export default apiRoute<{
max_id?: string;
since_id?: string;
min_id?: string;
limit?: string;
only_media?: boolean;
exclude_replies?: boolean;
exclude_reblogs?: boolean;
// TODO: Add with_muted
pinned?: boolean;
tagged?: string;
}>(async (req, matchedRoute, extraData) => {
const id = matchedRoute.params.id;
// TODO: Add pinned
@ -35,18 +42,7 @@ export default async (
limit = "20",
exclude_reblogs,
pinned,
}: {
max_id?: string;
since_id?: string;
min_id?: string;
limit?: string;
only_media?: boolean;
exclude_replies?: boolean;
exclude_reblogs?: boolean;
// TODO: Add with_muted
pinned?: boolean;
tagged?: string;
} = matchedRoute.query;
} = extraData.parsedRequest;
const user = await client.user.findUnique({
where: { id },
@ -131,4 +127,4 @@ export default async (
Link: linkHeader.join(", "),
}
);
};
});

View file

@ -1,14 +1,10 @@
import { errorResponse, jsonResponse } from "@response";
import type { MatchedRoute } from "bun";
import {
createNewRelationship,
relationshipToAPI,
} from "~database/entities/Relationship";
import {
getFromRequest,
getRelationshipToOtherUser,
} from "~database/entities/User";
import { applyConfig } from "@api";
import { getRelationshipToOtherUser } from "~database/entities/User";
import { apiRoute, applyConfig } from "@api";
import { client } from "~database/datasource";
export const meta = applyConfig({
@ -26,13 +22,10 @@ export const meta = applyConfig({
/**
* Blocks a user
*/
export default async (
req: Request,
matchedRoute: MatchedRoute
): Promise<Response> => {
export default apiRoute(async (req, matchedRoute, extraData) => {
const id = matchedRoute.params.id;
const { user: self } = await getFromRequest(req);
const { user: self } = extraData.auth;
if (!self) return errorResponse("Unauthorized", 401);
@ -84,4 +77,4 @@ export default async (
});
return jsonResponse(relationshipToAPI(relationship));
};
});

View file

@ -1,14 +1,10 @@
import { errorResponse, jsonResponse } from "@response";
import type { MatchedRoute } from "bun";
import {
createNewRelationship,
relationshipToAPI,
} from "~database/entities/Relationship";
import {
getFromRequest,
getRelationshipToOtherUser,
} from "~database/entities/User";
import { applyConfig } from "@api";
import { getRelationshipToOtherUser } from "~database/entities/User";
import { apiRoute, applyConfig } from "@api";
import { client } from "~database/datasource";
export const meta = applyConfig({
@ -26,13 +22,10 @@ export const meta = applyConfig({
/**
* Unfollows a user
*/
export default async (
req: Request,
matchedRoute: MatchedRoute
): Promise<Response> => {
export default apiRoute(async (req, matchedRoute, extraData) => {
const id = matchedRoute.params.id;
const { user: self } = await getFromRequest(req);
const { user: self } = extraData.auth;
if (!self) return errorResponse("Unauthorized", 401);
@ -84,4 +77,4 @@ export default async (
});
return jsonResponse(relationshipToAPI(relationship));
};
});

View file

@ -1,14 +1,10 @@
import { errorResponse, jsonResponse } from "@response";
import type { MatchedRoute } from "bun";
import {
createNewRelationship,
relationshipToAPI,
} from "~database/entities/Relationship";
import {
getFromRequest,
getRelationshipToOtherUser,
} from "~database/entities/User";
import { applyConfig } from "@api";
import { getRelationshipToOtherUser } from "~database/entities/User";
import { apiRoute, applyConfig } from "@api";
import { client } from "~database/datasource";
export const meta = applyConfig({
@ -26,13 +22,10 @@ export const meta = applyConfig({
/**
* Unmute a user
*/
export default async (
req: Request,
matchedRoute: MatchedRoute
): Promise<Response> => {
export default apiRoute(async (req, matchedRoute, extraData) => {
const id = matchedRoute.params.id;
const { user: self } = await getFromRequest(req);
const { user: self } = extraData.auth;
if (!self) return errorResponse("Unauthorized", 401);
@ -86,4 +79,4 @@ export default async (
});
return jsonResponse(relationshipToAPI(relationship));
};
});

View file

@ -1,14 +1,10 @@
import { errorResponse, jsonResponse } from "@response";
import type { MatchedRoute } from "bun";
import {
createNewRelationship,
relationshipToAPI,
} from "~database/entities/Relationship";
import {
getFromRequest,
getRelationshipToOtherUser,
} from "~database/entities/User";
import { applyConfig } from "@api";
import { getRelationshipToOtherUser } from "~database/entities/User";
import { apiRoute, applyConfig } from "@api";
import { client } from "~database/datasource";
export const meta = applyConfig({
@ -26,13 +22,10 @@ export const meta = applyConfig({
/**
* Unpin a user
*/
export default async (
req: Request,
matchedRoute: MatchedRoute
): Promise<Response> => {
export default apiRoute(async (req, matchedRoute, extraData) => {
const id = matchedRoute.params.id;
const { user: self } = await getFromRequest(req);
const { user: self } = extraData.auth;
if (!self) return errorResponse("Unauthorized", 401);
@ -84,4 +77,4 @@ export default async (
});
return jsonResponse(relationshipToAPI(relationship));
};
});

View file

@ -1,11 +1,6 @@
import { parseRequest } from "@request";
import { errorResponse, jsonResponse } from "@response";
import {
getFromRequest,
userRelations,
userToAPI,
} from "~database/entities/User";
import { applyConfig } from "@api";
import { userRelations, userToAPI } from "~database/entities/User";
import { apiRoute, applyConfig } from "@api";
import { client } from "~database/datasource";
export const meta = applyConfig({
@ -23,14 +18,14 @@ export const meta = applyConfig({
/**
* Find familiar followers (followers of a user that you also follow)
*/
export default async (req: Request): Promise<Response> => {
const { user: self } = await getFromRequest(req);
export default apiRoute<{
"id[]": string[];
}>(async (req, matchedRoute, extraData) => {
const { user: self } = extraData.auth;
if (!self) return errorResponse("Unauthorized", 401);
const { "id[]": ids } = await parseRequest<{
"id[]": string[];
}>(req);
const { "id[]": ids } = extraData.parsedRequest;
// Minimum id count 1, maximum 10
if (!ids || ids.length < 1 || ids.length > 10) {
@ -67,4 +62,4 @@ export default async (req: Request): Promise<Response> => {
});
return jsonResponse(output.map(o => userToAPI(o)));
};
});

View file

@ -1,11 +1,9 @@
import { getConfig } from "~classes/configmanager";
import { jsonResponse } from "@response";
import { tempmailDomains } from "@tempmail";
import { applyConfig } from "@api";
import { apiRoute, applyConfig } from "@api";
import { client } from "~database/datasource";
import { createNewLocalUser } from "~database/entities/User";
import ISO6391 from "iso-639-1";
import type { RouteHandler } from "~server/api/routes.type";
export const meta = applyConfig({
allowedMethods: ["POST"],
@ -19,19 +17,19 @@ export const meta = applyConfig({
},
});
const handler: RouteHandler<{
export default apiRoute<{
username: string;
email: string;
password: string;
agreement: boolean;
locale: string;
reason: string;
}> = async (req, matchedRoute, extraData) => {
}>(async (req, matchedRoute, extraData) => {
// TODO: Add Authorization check
const body = extraData.parsedRequest;
const config = getConfig();
const config = await extraData.configManager.getConfig();
if (!config.signups.registration) {
return jsonResponse(
@ -200,9 +198,4 @@ const handler: RouteHandler<{
return new Response("", {
status: 200,
});
};
/**
* Creates a new user
*/
export default handler;
});

View file

@ -1,11 +1,9 @@
import { parseRequest } from "@request";
import { errorResponse, jsonResponse } from "@response";
import {
createNewRelationship,
relationshipToAPI,
} from "~database/entities/Relationship";
import { getFromRequest } from "~database/entities/User";
import { applyConfig } from "@api";
import { apiRoute, applyConfig } from "@api";
import { client } from "~database/datasource";
export const meta = applyConfig({
@ -23,14 +21,14 @@ export const meta = applyConfig({
/**
* Find relationships
*/
export default async (req: Request): Promise<Response> => {
const { user: self } = await getFromRequest(req);
export default apiRoute<{
"id[]": string[];
}>(async (req, matchedRoute, extraData) => {
const { user: self } = extraData.auth;
if (!self) return errorResponse("Unauthorized", 401);
const { "id[]": ids } = await parseRequest<{
"id[]": string[];
}>(req);
const { "id[]": ids } = extraData.parsedRequest;
// Minimum id count 1, maximum 10
if (!ids || ids.length < 1 || ids.length > 10) {
@ -64,4 +62,4 @@ export default async (req: Request): Promise<Response> => {
);
return jsonResponse(relationships.map(r => relationshipToAPI(r)));
};
});

View file

@ -1,8 +1,7 @@
import { errorResponse, jsonResponse } from "@response";
import { userRelations, userToAPI } from "~database/entities/User";
import { applyConfig } from "@api";
import { apiRoute, applyConfig } from "@api";
import { client } from "~database/datasource";
import type { RouteHandler } from "~server/api/routes.type";
export const meta = applyConfig({
allowedMethods: ["GET"],
@ -16,13 +15,13 @@ export const meta = applyConfig({
},
});
const handler: RouteHandler<{
export default apiRoute<{
q?: string;
limit?: number;
offset?: number;
resolve?: boolean;
following?: boolean;
}> = async (req, matchedRoute, extraData) => {
}>(async (req, matchedRoute, extraData) => {
// TODO: Add checks for disabled or not email verified accounts
const { user } = extraData.auth;
@ -71,6 +70,4 @@ const handler: RouteHandler<{
});
return jsonResponse(accounts.map(acct => userToAPI(acct)));
};
export default handler;
});

View file

@ -1,7 +1,6 @@
import { getConfig } from "~classes/configmanager";
import { errorResponse, jsonResponse } from "@response";
import { userRelations, userToAPI } from "~database/entities/User";
import { applyConfig } from "@api";
import { apiRoute, applyConfig } from "@api";
import { sanitize } from "isomorphic-dompurify";
import { sanitizeHtml } from "@sanitization";
import { uploadFile } from "~classes/media";
@ -10,7 +9,6 @@ import { parseEmojis } from "~database/entities/Emoji";
import { client } from "~database/datasource";
import type { APISource } from "~types/entities/source";
import { convertTextToHtml } from "@formatting";
import type { RouteHandler } from "~server/api/routes.type";
export const meta = applyConfig({
allowedMethods: ["PATCH"],
@ -24,7 +22,7 @@ export const meta = applyConfig({
},
});
const handler: RouteHandler<{
export default apiRoute<{
display_name: string;
note: string;
avatar: File;
@ -35,12 +33,12 @@ const handler: RouteHandler<{
"source[privacy]": string;
"source[sensitive]": string;
"source[language]": string;
}> = async (req, matchedRoute, extraData) => {
}>(async (req, matchedRoute, extraData) => {
const { user } = extraData.auth;
if (!user) return errorResponse("Unauthorized", 401);
const config = getConfig();
const config = await extraData.configManager.getConfig();
const {
display_name,
@ -134,7 +132,7 @@ const handler: RouteHandler<{
);
}
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
// @ts-expect-error Prisma Typescript doesn't include relations
user.source.privacy = source_privacy;
}
@ -144,7 +142,7 @@ const handler: RouteHandler<{
return errorResponse("Sensitive must be a boolean", 422);
}
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
// @ts-expect-error Prisma Typescript doesn't include relations
user.source.sensitive = source_sensitive === "true";
}
@ -156,7 +154,7 @@ const handler: RouteHandler<{
);
}
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
// @ts-expect-error Prisma Typescript doesn't include relations
user.source.language = source_language;
}
@ -251,6 +249,4 @@ const handler: RouteHandler<{
});
return jsonResponse(userToAPI(output));
};
export default handler;
});

View file

@ -1,7 +1,6 @@
import { errorResponse, jsonResponse } from "@response";
import { userToAPI } from "~database/entities/User";
import { applyConfig } from "@api";
import type { RouteHandler } from "~server/api/routes.type";
import { apiRoute, applyConfig } from "@api";
export const meta = applyConfig({
allowedMethods: ["GET"],
@ -15,9 +14,7 @@ export const meta = applyConfig({
},
});
const handler: RouteHandler<> = (req, matchedRoute, extraData) => {};
const handler: RouteHandler<""> = (req, matchedRoute, extraData) => {
export default apiRoute((req, matchedRoute, extraData) => {
// TODO: Add checks for disabled or not email verified accounts
const { user } = extraData.auth;
@ -27,6 +24,4 @@ const handler: RouteHandler<""> = (req, matchedRoute, extraData) => {
return jsonResponse({
...userToAPI(user, true),
});
};
export default handler;
});

View file

@ -1,5 +1,4 @@
import { applyConfig } from "@api";
import { parseRequest } from "@request";
import { apiRoute, applyConfig } from "@api";
import { errorResponse, jsonResponse } from "@response";
import { randomBytes } from "crypto";
import { client } from "~database/datasource";
@ -19,13 +18,14 @@ export const meta = applyConfig({
/**
* Creates a new application to obtain OAuth 2 credentials
*/
export default async (req: Request): Promise<Response> => {
const { client_name, redirect_uris, scopes, website } = await parseRequest<{
client_name: string;
redirect_uris: string;
scopes: string;
website: string;
}>(req);
export default apiRoute<{
client_name: string;
redirect_uris: string;
scopes: string;
website: string;
}>(async (req, matchedRoute, extraData) => {
const { client_name, redirect_uris, scopes, website } =
extraData.parsedRequest;
// Check if redirect URI is a valid URI, and also an absolute URI
if (redirect_uris) {
@ -62,4 +62,4 @@ export default async (req: Request): Promise<Response> => {
redirect_uri: application.redirect_uris,
vapid_link: application.vapid_key,
});
};
});

View file

@ -1,7 +1,6 @@
import { applyConfig } from "@api";
import { apiRoute, applyConfig } from "@api";
import { errorResponse, jsonResponse } from "@response";
import { getFromToken } from "~database/entities/Application";
import { getFromRequest } from "~database/entities/User";
export const meta = applyConfig({
allowedMethods: ["GET"],
@ -18,8 +17,8 @@ export const meta = applyConfig({
/**
* Returns OAuth2 credentials
*/
export default async (req: Request): Promise<Response> => {
const { user, token } = await getFromRequest(req);
export default apiRoute(async (req, matchedRoute, extraData) => {
const { user, token } = extraData.auth;
const application = await getFromToken(token);
if (!user) return errorResponse("Unauthorized", 401);
@ -32,4 +31,4 @@ export default async (req: Request): Promise<Response> => {
redirect_uris: application.redirect_uris,
scopes: application.scopes,
});
};
});

View file

@ -1,10 +1,6 @@
import { errorResponse, jsonResponse } from "@response";
import {
getFromRequest,
userRelations,
userToAPI,
} from "~database/entities/User";
import { applyConfig } from "@api";
import { userRelations, userToAPI } from "~database/entities/User";
import { apiRoute, applyConfig } from "@api";
import { client } from "~database/datasource";
export const meta = applyConfig({
@ -19,8 +15,8 @@ export const meta = applyConfig({
},
});
export default async (req: Request): Promise<Response> => {
const { user } = await getFromRequest(req);
export default apiRoute(async (req, matchedRoute, extraData) => {
const { user } = extraData.auth;
if (!user) return errorResponse("Unauthorized", 401);
@ -37,4 +33,4 @@ export default async (req: Request): Promise<Response> => {
});
return jsonResponse(blocks.map(u => userToAPI(u)));
};
});

View file

@ -1,4 +1,4 @@
import { applyConfig } from "@api";
import { apiRoute, applyConfig } from "@api";
import { jsonResponse } from "@response";
import { client } from "~database/datasource";
import { emojiToAPI } from "~database/entities/Emoji";
@ -15,11 +15,7 @@ export const meta = applyConfig({
},
});
/**
* S
*/
// eslint-disable-next-line @typescript-eslint/require-await
export default async (): Promise<Response> => {
export default apiRoute(async () => {
const emojis = await client.emoji.findMany({
where: {
instanceId: null,
@ -29,4 +25,4 @@ export default async (): Promise<Response> => {
return jsonResponse(
await Promise.all(emojis.map(emoji => emojiToAPI(emoji)))
);
};
});

View file

@ -1,8 +1,6 @@
import { errorResponse, jsonResponse } from "@response";
import { getFromRequest } from "~database/entities/User";
import { applyConfig } from "@api";
import { apiRoute, applyConfig } from "@api";
import { client } from "~database/datasource";
import { parseRequest } from "@request";
import { statusAndUserRelations, statusToAPI } from "~database/entities/Status";
export const meta = applyConfig({
@ -17,20 +15,15 @@ export const meta = applyConfig({
},
});
export default async (req: Request): Promise<Response> => {
const { user } = await getFromRequest(req);
export default apiRoute<{
max_id?: string;
since_id?: string;
min_id?: string;
limit?: number;
}>(async (req, matchedRoute, extraData) => {
const { user } = extraData.auth;
const {
limit = 20,
max_id,
min_id,
since_id,
} = await parseRequest<{
max_id?: string;
since_id?: string;
min_id?: string;
limit?: number;
}>(req);
const { limit = 20, max_id, min_id, since_id } = extraData.parsedRequest;
if (limit < 1 || limit > 40) {
return errorResponse("Limit must be between 1 and 40", 400);
@ -77,4 +70,4 @@ export default async (req: Request): Promise<Response> => {
Link: linkHeader.join(", "),
}
);
};
});

View file

@ -1,8 +1,7 @@
import { errorResponse, jsonResponse } from "@response";
import { getFromRequest, userRelations } from "~database/entities/User";
import { applyConfig } from "@api";
import { userRelations } from "~database/entities/User";
import { apiRoute, applyConfig } from "@api";
import { client } from "~database/datasource";
import type { MatchedRoute } from "bun";
import {
checkForBidirectionalRelationships,
relationshipToAPI,
@ -20,11 +19,8 @@ export const meta = applyConfig({
},
});
export default async (
req: Request,
matchedRoute: MatchedRoute
): Promise<Response> => {
const { user } = await getFromRequest(req);
export default apiRoute(async (req, matchedRoute, extraData) => {
const { user } = extraData.auth;
if (!user) return errorResponse("Unauthorized", 401);
@ -76,4 +72,4 @@ export default async (
if (!relationship) return errorResponse("Relationship not found", 404);
return jsonResponse(relationshipToAPI(relationship));
};
});

View file

@ -1,8 +1,7 @@
import { errorResponse, jsonResponse } from "@response";
import { getFromRequest, userRelations } from "~database/entities/User";
import { applyConfig } from "@api";
import { userRelations } from "~database/entities/User";
import { apiRoute, applyConfig } from "@api";
import { client } from "~database/datasource";
import type { MatchedRoute } from "bun";
import {
checkForBidirectionalRelationships,
relationshipToAPI,
@ -20,11 +19,8 @@ export const meta = applyConfig({
},
});
export default async (
req: Request,
matchedRoute: MatchedRoute
): Promise<Response> => {
const { user } = await getFromRequest(req);
export default apiRoute(async (req, matchedRoute, extraData) => {
const { user } = extraData.auth;
if (!user) return errorResponse("Unauthorized", 401);
@ -64,4 +60,4 @@ export default async (
if (!relationship) return errorResponse("Relationship not found", 404);
return jsonResponse(relationshipToAPI(relationship));
};
});

View file

@ -1,12 +1,7 @@
import { errorResponse, jsonResponse } from "@response";
import {
getFromRequest,
userRelations,
userToAPI,
} from "~database/entities/User";
import { applyConfig } from "@api";
import { userRelations, userToAPI } from "~database/entities/User";
import { apiRoute, applyConfig } from "@api";
import { client } from "~database/datasource";
import { parseRequest } from "@request";
export const meta = applyConfig({
allowedMethods: ["GET"],
@ -20,20 +15,15 @@ export const meta = applyConfig({
},
});
export default async (req: Request): Promise<Response> => {
const { user } = await getFromRequest(req);
export default apiRoute<{
max_id?: string;
since_id?: string;
min_id?: string;
limit?: number;
}>(async (req, matchedRoute, extraData) => {
const { user } = extraData.auth;
const {
limit = 20,
max_id,
min_id,
since_id,
} = await parseRequest<{
max_id?: string;
since_id?: string;
min_id?: string;
limit?: number;
}>(req);
const { limit = 20, max_id, min_id, since_id } = extraData.parsedRequest;
if (limit < 1 || limit > 40) {
return errorResponse("Limit must be between 1 and 40", 400);
@ -79,4 +69,4 @@ export default async (req: Request): Promise<Response> => {
Link: linkHeader.join(", "),
}
);
};
});

View file

@ -1,5 +1,4 @@
import { applyConfig } from "@api";
import { getConfig } from "~classes/configmanager";
import { apiRoute, applyConfig } from "@api";
import { jsonResponse } from "@response";
import { client } from "~database/datasource";
import { userRelations, userToAPI } from "~database/entities/User";
@ -18,12 +17,8 @@ export const meta = applyConfig({
},
});
/**
* Creates a new user
*/
// eslint-disable-next-line @typescript-eslint/require-await
export default async (): Promise<Response> => {
const config = getConfig();
export default apiRoute(async (req, matchedRoute, extraData) => {
const config = await extraData.configManager.getConfig();
// Get software version from package.json
const version = manifest.version;
@ -159,4 +154,4 @@ export default async (): Promise<Response> => {
},
contact_account: contactAccount ? userToAPI(contactAccount) : null,
} as APIInstance);
};
});

View file

@ -1,13 +1,9 @@
import { applyConfig } from "@api";
import { apiRoute, applyConfig } from "@api";
import { errorResponse, jsonResponse } from "@response";
import { client } from "~database/datasource";
import { getFromRequest } from "~database/entities/User";
import type { APIRouteMeta } from "~types/api";
import { uploadFile } from "~classes/media";
import { getConfig } from "~classes/configmanager";
import { attachmentToAPI, getUrl } from "~database/entities/Attachment";
import type { MatchedRoute } from "bun";
import { parseRequest } from "@request";
export const meta: APIRouteMeta = applyConfig({
allowedMethods: ["GET", "PUT"],
@ -25,11 +21,12 @@ export const meta: APIRouteMeta = applyConfig({
/**
* Get media information
*/
export default async (
req: Request,
matchedRoute: MatchedRoute
): Promise<Response> => {
const { user } = await getFromRequest(req);
export default apiRoute<{
thumbnail?: File;
description?: string;
focus?: string;
}>(async (req, matchedRoute, extraData) => {
const { user } = extraData.auth;
if (!user) {
return errorResponse("Unauthorized", 401);
@ -47,7 +44,7 @@ export default async (
return errorResponse("Media not found", 404);
}
const config = getConfig();
const config = await extraData.configManager.getConfig();
switch (req.method) {
case "GET": {
@ -60,11 +57,7 @@ export default async (
}
}
case "PUT": {
const { description, thumbnail } = await parseRequest<{
thumbnail?: File;
description?: string;
focus?: string;
}>(req);
const { description, thumbnail } = extraData.parsedRequest;
let thumbnailUrl = attachment.thumbnail_url;
@ -101,4 +94,4 @@ export default async (
}
return errorResponse("Method not allowed", 405);
};
});

View file

@ -1,12 +1,10 @@
import { applyConfig } from "@api";
import { apiRoute, applyConfig } from "@api";
import { errorResponse, jsonResponse } from "@response";
import { client } from "~database/datasource";
import { encode } from "blurhash";
import { getFromRequest } from "~database/entities/User";
import type { APIRouteMeta } from "~types/api";
import sharp from "sharp";
import { uploadFile } from "~classes/media";
import { getConfig } from "~classes/configmanager";
import { attachmentToAPI, getUrl } from "~database/entities/Attachment";
export const meta: APIRouteMeta = applyConfig({
@ -25,27 +23,26 @@ export const meta: APIRouteMeta = applyConfig({
/**
* Upload new media
*/
export default async (req: Request): Promise<Response> => {
const { user } = await getFromRequest(req);
export default apiRoute<{
file: File;
thumbnail?: File;
description?: string;
// TODO: Add focus
focus?: string;
}>(async (req, matchedRoute, extraData) => {
const { user } = extraData.auth;
if (!user) {
return errorResponse("Unauthorized", 401);
}
const form = await req.formData();
const file = form.get("file") as unknown as File | undefined;
const thumbnail = form.get("thumbnail");
const description = form.get("description") as string | undefined;
// Floating point numbers from -1.0 to 1.0, comma delimited
// const focus = form.get("focus");
const { file, thumbnail, description } = extraData.parsedRequest;
if (!file) {
return errorResponse("No file provided", 400);
}
const config = getConfig();
const config = await extraData.configManager.getConfig();
if (file.size > config.validation.max_media_size) {
return errorResponse(
@ -86,7 +83,7 @@ export default async (req: Request): Promise<Response> => {
metadata?.height ?? 0,
4,
4
)
)
: null;
let url = "";
@ -120,4 +117,4 @@ export default async (req: Request): Promise<Response> => {
// TODO: Add job to process videos and other media
return jsonResponse(attachmentToAPI(newAttachment));
};
});

View file

@ -1,10 +1,6 @@
import { errorResponse, jsonResponse } from "@response";
import {
getFromRequest,
userRelations,
userToAPI,
} from "~database/entities/User";
import { applyConfig } from "@api";
import { userRelations, userToAPI } from "~database/entities/User";
import { apiRoute, applyConfig } from "@api";
import { client } from "~database/datasource";
export const meta = applyConfig({
@ -19,8 +15,8 @@ export const meta = applyConfig({
},
});
export default async (req: Request): Promise<Response> => {
const { user } = await getFromRequest(req);
export default apiRoute(async (req, matchedRoute, extraData) => {
const { user } = extraData.auth;
if (!user) return errorResponse("Unauthorized", 401);
@ -37,4 +33,4 @@ export default async (req: Request): Promise<Response> => {
});
return jsonResponse(blocks.map(u => userToAPI(u)));
};
});

View file

@ -1,9 +1,8 @@
import { errorResponse, jsonResponse } from "@response";
import { getFromRequest, userRelations } from "~database/entities/User";
import { applyConfig } from "@api";
import { userRelations } from "~database/entities/User";
import { apiRoute, applyConfig } from "@api";
import { client } from "~database/datasource";
import { statusAndUserRelations } from "~database/entities/Status";
import { parseRequest } from "@request";
import { notificationToAPI } from "~database/entities/Notification";
export const meta = applyConfig({
@ -18,8 +17,16 @@ export const meta = applyConfig({
},
});
export default async (req: Request): Promise<Response> => {
const { user } = await getFromRequest(req);
export default apiRoute<{
max_id?: string;
since_id?: string;
min_id?: string;
limit?: number;
exclude_types?: string[];
types?: string[];
account_id?: string;
}>(async (req, matchedRoute, extraData) => {
const { user } = extraData.auth;
if (!user) return errorResponse("Unauthorized", 401);
@ -31,15 +38,7 @@ export default async (req: Request): Promise<Response> => {
min_id,
since_id,
types,
} = await parseRequest<{
max_id?: string;
since_id?: string;
min_id?: string;
limit?: number;
exclude_types?: string[];
types?: string[];
account_id?: string;
}>(req);
} = extraData.parsedRequest;
if (limit > 30) return errorResponse("Limit too high", 400);
@ -85,8 +84,9 @@ export default async (req: Request): Promise<Response> => {
`<${urlWithoutQuery}?max_id=${objects[0].id}&limit=${limit}>; rel="next"`
);
linkHeader.push(
`<${urlWithoutQuery}?since_id=${objects.at(-1)
?.id}&limit=${limit}>; rel="prev"`
`<${urlWithoutQuery}?since_id=${
objects.at(-1)?.id
}&limit=${limit}>; rel="prev"`
);
}
@ -97,4 +97,4 @@ export default async (req: Request): Promise<Response> => {
Link: linkHeader.join(", "),
}
);
};
});

View file

@ -1,11 +1,7 @@
import { applyConfig } from "@api";
import { apiRoute, applyConfig } from "@api";
import { errorResponse, jsonResponse } from "@response";
import { client } from "~database/datasource";
import {
getFromRequest,
userRelations,
userToAPI,
} from "~database/entities/User";
import { userRelations, userToAPI } from "~database/entities/User";
import type { APIRouteMeta } from "~types/api";
export const meta: APIRouteMeta = applyConfig({
@ -23,8 +19,8 @@ export const meta: APIRouteMeta = applyConfig({
/**
* Deletes a user avatar
*/
export default async (req: Request): Promise<Response> => {
const { user } = await getFromRequest(req);
export default apiRoute(async (req, matchedRoute, extraData) => {
const { user } = extraData.auth;
if (!user) return errorResponse("Unauthorized", 401);
@ -40,4 +36,4 @@ export default async (req: Request): Promise<Response> => {
});
return jsonResponse(userToAPI(newUser));
};
});

View file

@ -1,11 +1,7 @@
import { applyConfig } from "@api";
import { apiRoute, applyConfig } from "@api";
import { errorResponse, jsonResponse } from "@response";
import { client } from "~database/datasource";
import {
getFromRequest,
userRelations,
userToAPI,
} from "~database/entities/User";
import { userRelations, userToAPI } from "~database/entities/User";
import type { APIRouteMeta } from "~types/api";
export const meta: APIRouteMeta = applyConfig({
@ -23,8 +19,8 @@ export const meta: APIRouteMeta = applyConfig({
/**
* Deletes a user header
*/
export default async (req: Request): Promise<Response> => {
const { user } = await getFromRequest(req);
export default apiRoute(async (req, matchedRoute, extraData) => {
const { user } = extraData.auth;
if (!user) return errorResponse("Unauthorized", 401);
@ -40,4 +36,4 @@ export default async (req: Request): Promise<Response> => {
});
return jsonResponse(userToAPI(newUser));
};
});

View file

@ -1,6 +1,5 @@
import { applyConfig } from "@api";
import { apiRoute, applyConfig } from "@api";
import { errorResponse, jsonResponse } from "@response";
import type { MatchedRoute } from "bun";
import { client } from "~database/datasource";
import {
getAncestors,
@ -8,7 +7,6 @@ import {
statusAndUserRelations,
statusToAPI,
} from "~database/entities/Status";
import { getFromRequest } from "~database/entities/User";
import type { APIRouteMeta } from "~types/api";
export const meta: APIRouteMeta = applyConfig({
@ -26,15 +24,12 @@ export const meta: APIRouteMeta = applyConfig({
/**
* Fetch a user
*/
export default async (
req: Request,
matchedRoute: MatchedRoute
): Promise<Response> => {
export default apiRoute(async (req, matchedRoute, extraData) => {
// Public for public statuses limited to 40 ancestors and 60 descendants with a maximum depth of 20.
// User token + read:statuses for up to 4,096 ancestors, 4,096 descendants, unlimited depth, and private statuses.
const id = matchedRoute.params.id;
const { user } = await getFromRequest(req);
const { user } = extraData.auth;
const foundStatus = await client.status.findUnique({
where: { id },
@ -55,4 +50,4 @@ export default async (
descendants.map(status => statusToAPI(status, user || undefined))
),
});
};
});

View file

@ -1,7 +1,6 @@
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
import { applyConfig } from "@api";
import { apiRoute, applyConfig } from "@api";
import { errorResponse, jsonResponse } from "@response";
import type { MatchedRoute } from "bun";
import { client } from "~database/datasource";
import { createLike } from "~database/entities/Like";
import {
@ -9,7 +8,6 @@ import {
statusAndUserRelations,
statusToAPI,
} from "~database/entities/Status";
import { getFromRequest } from "~database/entities/User";
import type { APIRouteMeta } from "~types/api";
import type { APIStatus } from "~types/entities/status";
@ -28,13 +26,10 @@ export const meta: APIRouteMeta = applyConfig({
/**
* Favourite a post
*/
export default async (
req: Request,
matchedRoute: MatchedRoute
): Promise<Response> => {
export default apiRoute(async (req, matchedRoute, extraData) => {
const id = matchedRoute.params.id;
const { user } = await getFromRequest(req);
const { user } = extraData.auth;
if (!user) return errorResponse("Unauthorized", 401);
@ -63,4 +58,4 @@ export default async (
favourited: true,
favourites_count: status._count.likes + 1,
} as APIStatus);
};
});

View file

@ -1,18 +1,11 @@
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
import { applyConfig } from "@api";
import { parseRequest } from "@request";
import { apiRoute, applyConfig } from "@api";
import { errorResponse, jsonResponse } from "@response";
import type { MatchedRoute } from "bun";
import { client } from "~database/datasource";
import {
isViewableByUser,
statusAndUserRelations,
} from "~database/entities/Status";
import {
getFromRequest,
userRelations,
userToAPI,
} from "~database/entities/User";
import { userRelations, userToAPI } from "~database/entities/User";
import type { APIRouteMeta } from "~types/api";
export const meta: APIRouteMeta = applyConfig({
@ -30,13 +23,15 @@ export const meta: APIRouteMeta = applyConfig({
/**
* Fetch users who favourited the post
*/
export default async (
req: Request,
matchedRoute: MatchedRoute
): Promise<Response> => {
export default apiRoute<{
max_id?: string;
min_id?: string;
since_id?: string;
limit?: number;
}>(async (req, matchedRoute, extraData) => {
const id = matchedRoute.params.id;
const { user } = await getFromRequest(req);
const { user } = extraData.auth;
const status = await client.status.findUnique({
where: { id },
@ -52,12 +47,7 @@ export default async (
min_id = null,
since_id = null,
limit = 40,
} = await parseRequest<{
max_id?: string;
min_id?: string;
since_id?: string;
limit?: number;
}>(req);
} = extraData.parsedRequest;
// Check for limit limits
if (limit > 80) return errorResponse("Invalid limit (maximum is 80)", 400);
@ -111,4 +101,4 @@ export default async (
Link: linkHeader.join(", "),
}
);
};
});

View file

@ -1,9 +1,6 @@
import { applyConfig } from "@api";
import { getConfig } from "~classes/configmanager";
import { parseRequest } from "@request";
import { apiRoute, applyConfig } from "@api";
import { errorResponse, jsonResponse } from "@response";
import { sanitizeHtml } from "@sanitization";
import type { MatchedRoute } from "bun";
import { parse } from "marked";
import { client } from "~database/datasource";
import {
@ -12,7 +9,6 @@ import {
statusAndUserRelations,
statusToAPI,
} from "~database/entities/Status";
import { getFromRequest } from "~database/entities/User";
import type { APIRouteMeta } from "~types/api";
export const meta: APIRouteMeta = applyConfig({
@ -31,20 +27,28 @@ export const meta: APIRouteMeta = applyConfig({
/**
* Fetch a user
*/
export default async (
req: Request,
matchedRoute: MatchedRoute
): Promise<Response> => {
export default apiRoute<{
status?: string;
spoiler_text?: string;
sensitive?: boolean;
language?: string;
content_type?: string;
"media_ids[]"?: string[];
"poll[options][]"?: string[];
"poll[expires_in]"?: number;
"poll[multiple]"?: boolean;
"poll[hide_totals]"?: boolean;
}>(async (req, matchedRoute, extraData) => {
const id = matchedRoute.params.id;
const { user } = await getFromRequest(req);
const { user } = extraData.auth;
const status = await client.status.findUnique({
where: { id },
include: statusAndUserRelations,
});
const config = getConfig();
const config = await extraData.configManager.getConfig();
// Check if user is authorized to view this status (if it's private)
if (!status || !isViewableByUser(status, user))
@ -89,18 +93,7 @@ export default async (
"media_ids[]": media_ids,
spoiler_text,
sensitive,
} = await parseRequest<{
status?: string;
spoiler_text?: string;
sensitive?: boolean;
language?: string;
content_type?: string;
"media_ids[]"?: string[];
"poll[options][]"?: string[];
"poll[expires_in]"?: number;
"poll[multiple]"?: boolean;
"poll[hide_totals]"?: boolean;
}>(req);
} = extraData.parsedRequest;
// TODO: Add Poll support
// Validate status
@ -171,11 +164,11 @@ export default async (
let sanitizedStatus: string;
if (content_type === "text/markdown") {
sanitizedStatus = await sanitizeHtml(parse(statusText ?? ""));
sanitizedStatus = await sanitizeHtml(await parse(statusText ?? ""));
} else if (content_type === "text/x.misskeymarkdown") {
// Parse as MFM
// TODO: Parse as MFM
sanitizedStatus = await sanitizeHtml(parse(statusText ?? ""));
sanitizedStatus = await sanitizeHtml(await parse(statusText ?? ""));
} else {
sanitizedStatus = await sanitizeHtml(statusText ?? "");
}
@ -189,8 +182,8 @@ export default async (
// Check if status body doesnt match filters
if (
config.filters.note_filters.some(
filter => statusText?.match(filter)
config.filters.note_filters.some(filter =>
statusText?.match(filter)
)
) {
return errorResponse("Status contains blocked words", 422);
@ -223,4 +216,4 @@ export default async (
}
return jsonResponse({});
};
});

View file

@ -1,10 +1,8 @@
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
import { applyConfig } from "@api";
import { apiRoute, applyConfig } from "@api";
import { errorResponse, jsonResponse } from "@response";
import type { MatchedRoute } from "bun";
import { client } from "~database/datasource";
import { statusAndUserRelations, statusToAPI } from "~database/entities/Status";
import { getFromRequest } from "~database/entities/User";
import type { APIRouteMeta } from "~types/api";
export const meta: APIRouteMeta = applyConfig({
@ -22,13 +20,10 @@ export const meta: APIRouteMeta = applyConfig({
/**
* Pin a post
*/
export default async (
req: Request,
matchedRoute: MatchedRoute
): Promise<Response> => {
export default apiRoute(async (req, matchedRoute, extraData) => {
const id = matchedRoute.params.id;
const { user } = await getFromRequest(req);
const { user } = extraData.auth;
if (!user) return errorResponse("Unauthorized", 401);
@ -62,4 +57,4 @@ export default async (
if (!status) return errorResponse("Record not found", 404);
return jsonResponse(statusToAPI(status, user));
};
});

View file

@ -1,19 +1,13 @@
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
import { applyConfig } from "@api";
import { getConfig } from "~classes/configmanager";
import { parseRequest } from "@request";
import { apiRoute, applyConfig } from "@api";
import { errorResponse, jsonResponse } from "@response";
import type { MatchedRoute } from "bun";
import { client } from "~database/datasource";
import {
isViewableByUser,
statusAndUserRelations,
statusToAPI,
} from "~database/entities/Status";
import {
getFromRequest,
type UserWithRelations,
} from "~database/entities/User";
import { type UserWithRelations } from "~database/entities/User";
import type { APIRouteMeta } from "~types/api";
export const meta: APIRouteMeta = applyConfig({
@ -31,20 +25,17 @@ export const meta: APIRouteMeta = applyConfig({
/**
* Reblogs a post
*/
export default async (
req: Request,
matchedRoute: MatchedRoute
): Promise<Response> => {
export default apiRoute<{
visibility: "public" | "unlisted" | "private";
}>(async (req, matchedRoute, extraData) => {
const id = matchedRoute.params.id;
const config = getConfig();
const config = await extraData.configManager.getConfig();
const { user } = await getFromRequest(req);
const { user } = extraData.auth;
if (!user) return errorResponse("Unauthorized", 401);
const { visibility = "public" } = await parseRequest<{
visibility: "public" | "unlisted" | "private";
}>(req);
const { visibility = "public" } = extraData.parsedRequest;
const status = await client.status.findUnique({
where: { id },
@ -107,4 +98,4 @@ export default async (
user
)
);
};
});

View file

@ -1,18 +1,11 @@
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
import { applyConfig } from "@api";
import { parseRequest } from "@request";
import { apiRoute, applyConfig } from "@api";
import { errorResponse, jsonResponse } from "@response";
import type { MatchedRoute } from "bun";
import { client } from "~database/datasource";
import {
isViewableByUser,
statusAndUserRelations,
} from "~database/entities/Status";
import {
getFromRequest,
userRelations,
userToAPI,
} from "~database/entities/User";
import { userRelations, userToAPI } from "~database/entities/User";
import type { APIRouteMeta } from "~types/api";
export const meta: APIRouteMeta = applyConfig({
@ -30,13 +23,15 @@ export const meta: APIRouteMeta = applyConfig({
/**
* Fetch users who reblogged the post
*/
export default async (
req: Request,
matchedRoute: MatchedRoute
): Promise<Response> => {
export default apiRoute<{
max_id?: string;
min_id?: string;
since_id?: string;
limit?: number;
}>(async (req, matchedRoute, extraData) => {
const id = matchedRoute.params.id;
const { user } = await getFromRequest(req);
const { user } = extraData.auth;
const status = await client.status.findUnique({
where: { id },
@ -52,12 +47,7 @@ export default async (
min_id = null,
since_id = null,
limit = 40,
} = await parseRequest<{
max_id?: string;
min_id?: string;
since_id?: string;
limit?: number;
}>(req);
} = extraData.parsedRequest;
// Check for limit limits
if (limit > 80) return errorResponse("Invalid limit (maximum is 80)", 400);
@ -112,4 +102,4 @@ export default async (
Link: linkHeader.join(", "),
}
);
};
});

View file

@ -1,17 +1,11 @@
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
import { applyConfig } from "@api";
import { errorResponse, jsonResponse } from "@response";
import type { MatchedRoute } from "bun";
import { apiRoute, applyConfig } from "@api";
import { errorResponse } from "@response";
import { client } from "~database/datasource";
import { createLike } from "~database/entities/Like";
import {
isViewableByUser,
statusAndUserRelations,
statusToAPI,
} from "~database/entities/Status";
import { getFromRequest } from "~database/entities/User";
import type { APIRouteMeta } from "~types/api";
import type { APIStatus } from "~types/entities/status";
export const meta: APIRouteMeta = applyConfig({
allowedMethods: ["GET"],
@ -28,13 +22,10 @@ export const meta: APIRouteMeta = applyConfig({
/**
* Favourite a post
*/
export default async (
req: Request,
matchedRoute: MatchedRoute
): Promise<Response> => {
export default apiRoute(async (req, matchedRoute, extraData) => {
const id = matchedRoute.params.id;
const { user } = await getFromRequest(req);
const { user } = extraData.auth;
if (!user) return errorResponse("Unauthorized", 401);
@ -46,4 +37,6 @@ export default async (
// Check if user is authorized to view this status (if it's private)
if (!status || !isViewableByUser(status, user))
return errorResponse("Record not found", 404);
};
return errorResponse("Not implemented yet");
});

View file

@ -1,7 +1,6 @@
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
import { applyConfig } from "@api";
import { apiRoute, applyConfig } from "@api";
import { errorResponse, jsonResponse } from "@response";
import type { MatchedRoute } from "bun";
import { client } from "~database/datasource";
import { deleteLike } from "~database/entities/Like";
import {
@ -9,7 +8,6 @@ import {
statusAndUserRelations,
statusToAPI,
} from "~database/entities/Status";
import { getFromRequest } from "~database/entities/User";
import type { APIRouteMeta } from "~types/api";
import type { APIStatus } from "~types/entities/status";
@ -28,13 +26,10 @@ export const meta: APIRouteMeta = applyConfig({
/**
* Unfavourite a post
*/
export default async (
req: Request,
matchedRoute: MatchedRoute
): Promise<Response> => {
export default apiRoute(async (req, matchedRoute, extraData) => {
const id = matchedRoute.params.id;
const { user } = await getFromRequest(req);
const { user } = extraData.auth;
if (!user) return errorResponse("Unauthorized", 401);
@ -54,4 +49,4 @@ export default async (
favourited: false,
favourites_count: status._count.likes - 1,
} as APIStatus);
};
});

View file

@ -1,10 +1,7 @@
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
import { applyConfig } from "@api";
import { apiRoute, applyConfig } from "@api";
import { errorResponse, jsonResponse } from "@response";
import type { MatchedRoute } from "bun";
import { client } from "~database/datasource";
import { statusAndUserRelations, statusToAPI } from "~database/entities/Status";
import { getFromRequest } from "~database/entities/User";
import type { APIRouteMeta } from "~types/api";
export const meta: APIRouteMeta = applyConfig({
@ -22,13 +19,10 @@ export const meta: APIRouteMeta = applyConfig({
/**
* Unpins a post
*/
export default async (
req: Request,
matchedRoute: MatchedRoute
): Promise<Response> => {
export default apiRoute(async (req, matchedRoute, extraData) => {
const id = matchedRoute.params.id;
const { user } = await getFromRequest(req);
const { user } = extraData.auth;
if (!user) return errorResponse("Unauthorized", 401);
@ -62,4 +56,4 @@ export default async (
if (!status) return errorResponse("Record not found", 404);
return jsonResponse(statusToAPI(status, user));
};
});

View file

@ -1,14 +1,11 @@
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
import { applyConfig } from "@api";
import { apiRoute, applyConfig } from "@api";
import { errorResponse, jsonResponse } from "@response";
import type { MatchedRoute } from "bun";
import { client } from "~database/datasource";
import {
isViewableByUser,
statusAndUserRelations,
statusToAPI,
} from "~database/entities/Status";
import { getFromRequest } from "~database/entities/User";
import type { APIRouteMeta } from "~types/api";
import type { APIStatus } from "~types/entities/status";
@ -27,13 +24,10 @@ export const meta: APIRouteMeta = applyConfig({
/**
* Unreblogs a post
*/
export default async (
req: Request,
matchedRoute: MatchedRoute
): Promise<Response> => {
export default apiRoute(async (req, matchedRoute, extraData) => {
const id = matchedRoute.params.id;
const { user } = await getFromRequest(req);
const { user } = extraData.auth;
if (!user) return errorResponse("Unauthorized", 401);
@ -66,4 +60,4 @@ export default async (
reblogged: false,
reblogs_count: status._count.reblogs - 1,
} as APIStatus);
};
});

View file

@ -1,12 +1,6 @@
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
/* eslint-disable @typescript-eslint/no-unsafe-argument */
/* eslint-disable @typescript-eslint/no-unused-vars */
import { applyConfig } from "@api";
import { getConfig } from "~classes/configmanager";
import { parseRequest } from "@request";
import { apiRoute, applyConfig } from "@api";
import { errorResponse, jsonResponse } from "@response";
import { sanitizeHtml } from "@sanitization";
import type { MatchedRoute } from "bun";
import { parse } from "marked";
import { client } from "~database/datasource";
import { getFromToken } from "~database/entities/Application";
@ -16,7 +10,7 @@ import {
statusAndUserRelations,
statusToAPI,
} from "~database/entities/Status";
import type { AuthData, UserWithRelations } from "~database/entities/User";
import type { UserWithRelations } from "~database/entities/User";
import type { APIRouteMeta } from "~types/api";
export const meta: APIRouteMeta = applyConfig({
@ -34,50 +28,46 @@ export const meta: APIRouteMeta = applyConfig({
/**
* Post new status
*/
export default async (
req: Request,
matchedRoute: MatchedRoute,
authData: AuthData
): Promise<Response> => {
const { user, token } = authData;
export default apiRoute<{
status: string;
media_ids?: string[];
"poll[options]"?: string[];
"poll[expires_in]"?: number;
"poll[multiple]"?: boolean;
"poll[hide_totals]"?: boolean;
in_reply_to_id?: string;
quote_id?: string;
sensitive?: boolean;
spoiler_text?: string;
visibility?: "public" | "unlisted" | "private" | "direct";
language?: string;
scheduled_at?: string;
local_only?: boolean;
content_type?: string;
}>(async (req, matchedRoute, extraData) => {
const { user, token } = extraData.auth;
const application = await getFromToken(token);
if (!user) return errorResponse("Unauthorized", 401);
const config = getConfig();
const config = await extraData.configManager.getConfig();
const {
status,
media_ids,
"poll[expires_in]": expires_in,
"poll[hide_totals]": hide_totals,
"poll[multiple]": multiple,
// "poll[hide_totals]": hide_totals,
// "poll[multiple]": multiple,
"poll[options]": options,
in_reply_to_id,
quote_id,
language,
// language,
scheduled_at,
sensitive,
spoiler_text,
visibility,
content_type,
} = await parseRequest<{
status: string;
media_ids?: string[];
"poll[options]"?: string[];
"poll[expires_in]"?: number;
"poll[multiple]"?: boolean;
"poll[hide_totals]"?: boolean;
in_reply_to_id?: string;
quote_id?: string;
sensitive?: boolean;
spoiler_text?: string;
visibility?: "public" | "unlisted" | "private" | "direct";
language?: string;
scheduled_at?: string;
local_only?: boolean;
content_type?: string;
}>(req);
} = extraData.parsedRequest;
// Validate status
if (!status && !(media_ids && media_ids.length > 0)) {
@ -246,7 +236,7 @@ export default async (
? {
user: replyUser,
status: replyStatus,
}
}
: undefined,
quote: quote || undefined,
});
@ -254,4 +244,4 @@ export default async (
// TODO: add database jobs to deliver the post
return jsonResponse(await statusToAPI(newStatus, user));
};
});

View file

@ -1,10 +1,7 @@
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
import { applyConfig } from "@api";
import { parseRequest } from "@request";
import { apiRoute, applyConfig } from "@api";
import { errorResponse, jsonResponse } from "@response";
import { client } from "~database/datasource";
import { statusAndUserRelations, statusToAPI } from "~database/entities/Status";
import { getFromRequest } from "~database/entities/User";
import type { APIRouteMeta } from "~types/api";
export const meta: APIRouteMeta = applyConfig({
@ -22,20 +19,15 @@ export const meta: APIRouteMeta = applyConfig({
/**
* Fetch home timeline statuses
*/
export default async (req: Request): Promise<Response> => {
const { user } = await getFromRequest(req);
export default apiRoute<{
max_id?: string;
since_id?: string;
min_id?: string;
limit?: number;
}>(async (req, matchedRoute, extraData) => {
const { user } = extraData.auth;
const {
limit = 20,
max_id,
min_id,
since_id,
} = await parseRequest<{
max_id?: string;
since_id?: string;
min_id?: string;
limit?: number;
}>(req);
const { limit = 20, max_id, min_id, since_id } = extraData.parsedRequest;
if (limit < 1 || limit > 40) {
return errorResponse("Limit must be between 1 and 40", 400);
@ -104,4 +96,4 @@ export default async (req: Request): Promise<Response> => {
Link: linkHeader.join(", "),
}
);
};
});

View file

@ -1,9 +1,7 @@
import { applyConfig } from "@api";
import { parseRequest } from "@request";
import { apiRoute, applyConfig } from "@api";
import { errorResponse, jsonResponse } from "@response";
import { client } from "~database/datasource";
import { statusAndUserRelations, statusToAPI } from "~database/entities/Status";
import { getFromRequest } from "~database/entities/User";
import type { APIRouteMeta } from "~types/api";
export const meta: APIRouteMeta = applyConfig({
@ -18,8 +16,16 @@ export const meta: APIRouteMeta = applyConfig({
},
});
export default async (req: Request): Promise<Response> => {
const { user } = await getFromRequest(req);
export default apiRoute<{
local?: boolean;
only_media?: boolean;
remote?: boolean;
max_id?: string;
since_id?: string;
min_id?: string;
limit?: number;
}>(async (req, matchedRoute, extraData) => {
const { user } = extraData.auth;
const {
local,
limit = 20,
@ -28,15 +34,7 @@ export default async (req: Request): Promise<Response> => {
// only_media,
remote,
since_id,
} = await parseRequest<{
local?: boolean;
only_media?: boolean;
remote?: boolean;
max_id?: string;
since_id?: string;
min_id?: string;
limit?: number;
}>(req);
} = extraData.parsedRequest;
if (limit < 1 || limit > 40) {
return errorResponse("Limit must be between 1 and 40", 400);
@ -56,10 +54,10 @@ export default async (req: Request): Promise<Response> => {
instanceId: remote
? {
not: null,
}
}
: local
? null
: undefined,
? null
: undefined,
},
include: statusAndUserRelations,
take: limit,
@ -87,4 +85,4 @@ export default async (req: Request): Promise<Response> => {
Link: linkHeader.join(", "),
}
);
};
});

View file

@ -1,12 +1,10 @@
import { applyConfig } from "@api";
import { apiRoute, applyConfig } from "@api";
import { errorResponse, jsonResponse } from "@response";
import { client } from "~database/datasource";
import { encode } from "blurhash";
import { getFromRequest } from "~database/entities/User";
import type { APIRouteMeta } from "~types/api";
import sharp from "sharp";
import { uploadFile } from "~classes/media";
import { getConfig } from "~classes/configmanager";
import { attachmentToAPI, getUrl } from "~database/entities/Attachment";
export const meta: APIRouteMeta = applyConfig({
@ -25,27 +23,26 @@ export const meta: APIRouteMeta = applyConfig({
/**
* Upload new media
*/
export default async (req: Request): Promise<Response> => {
const { user } = await getFromRequest(req);
export default apiRoute<{
file: File;
thumbnail: File;
description: string;
// TODO: Implement focus storage
focus: string;
}>(async (req, matchedRoute, extraData) => {
const { user } = extraData.auth;
if (!user) {
return errorResponse("Unauthorized", 401);
}
const form = await req.formData();
const file = form.get("file") as unknown as File | undefined;
const thumbnail = form.get("thumbnail");
const description = form.get("description") as string | undefined;
// Floating point numbers from -1.0 to 1.0, comma delimited
// const focus = form.get("focus");
const { file, thumbnail, description } = extraData.parsedRequest;
if (!file) {
return errorResponse("No file provided", 400);
}
const config = getConfig();
const config = await extraData.configManager.getConfig();
if (file.size > config.validation.max_media_size) {
return errorResponse(
@ -86,7 +83,7 @@ export default async (req: Request): Promise<Response> => {
metadata?.height ?? 0,
4,
4
)
)
: null;
let url = "";
@ -132,4 +129,4 @@ export default async (req: Request): Promise<Response> => {
202
);
}
};
});

View file

@ -1,15 +1,9 @@
import { applyConfig } from "@api";
import { getConfig } from "~classes/configmanager";
import { apiRoute, applyConfig } from "@api";
import { MeiliIndexType, meilisearch } from "@meilisearch";
import { parseRequest } from "@request";
import { errorResponse, jsonResponse } from "@response";
import { client } from "~database/datasource";
import { statusAndUserRelations, statusToAPI } from "~database/entities/Status";
import {
getFromRequest,
userRelations,
userToAPI,
} from "~database/entities/User";
import { userRelations, userToAPI } from "~database/entities/User";
import type { APIRouteMeta } from "~types/api";
export const meta: APIRouteMeta = applyConfig({
@ -28,8 +22,18 @@ export const meta: APIRouteMeta = applyConfig({
/**
* Upload new media
*/
export default async (req: Request): Promise<Response> => {
const { user } = await getFromRequest(req);
export default apiRoute<{
q?: string;
type?: string;
resolve?: boolean;
following?: boolean;
account_id?: string;
max_id?: string;
min_id?: string;
limit?: number;
offset?: number;
}>(async (req, matchedRoute, extraData) => {
const { user } = extraData.auth;
const {
q,
@ -41,19 +45,9 @@ export default async (req: Request): Promise<Response> => {
// min_id,
limit = 20,
offset,
} = await parseRequest<{
q?: string;
type?: string;
resolve?: boolean;
following?: boolean;
account_id?: string;
max_id?: string;
min_id?: string;
limit?: number;
offset?: number;
}>(req);
} = extraData.parsedRequest;
const config = getConfig();
const config = await extraData.configManager.getConfig();
if (!config.meilisearch.enabled) {
return errorResponse("Meilisearch is not enabled", 501);
@ -143,4 +137,4 @@ export default async (req: Request): Promise<Response> => {
),
hashtags: [],
});
};
});