iceshrimp-161sh/packages/backend/src/server/activitypub/following.ts
ThatOneCalculator 2aab2de38d refactor: 🎨 rome
2023-01-12 20:40:33 -08:00

120 lines
3.1 KiB
TypeScript

import { LessThan, IsNull } from "typeorm";
import config from "@/config/index.js";
import * as url from "@/prelude/url.js";
import { renderActivity } from "@/remote/activitypub/renderer/index.js";
import renderOrderedCollection from "@/remote/activitypub/renderer/ordered-collection.js";
import renderOrderedCollectionPage from "@/remote/activitypub/renderer/ordered-collection-page.js";
import renderFollowUser from "@/remote/activitypub/renderer/follow-user.js";
import { Users, Followings, UserProfiles } from "@/models/index.js";
import type { Following } from "@/models/entities/following.js";
import { checkFetch } from "@/remote/activitypub/check-fetch.js";
import { fetchMeta } from "@/misc/fetch-meta.js";
import { setResponseType } from "../activitypub.js";
import type { FindOptionsWhere } from "typeorm";
import type Router from "@koa/router";
export default async (ctx: Router.RouterContext) => {
const verify = await checkFetch(ctx.req);
if (verify !== 200) {
ctx.status = verify;
return;
}
const userId = ctx.params.user;
const cursor = ctx.request.query.cursor;
if (cursor != null && typeof cursor !== "string") {
ctx.status = 400;
return;
}
const page = ctx.request.query.page === "true";
const user = await Users.findOneBy({
id: userId,
host: IsNull(),
});
if (user == null) {
ctx.status = 404;
return;
}
//#region Check ff visibility
const profile = await UserProfiles.findOneByOrFail({ userId: user.id });
if (profile.ffVisibility === "private") {
ctx.status = 403;
ctx.set("Cache-Control", "public, max-age=30");
return;
} else if (profile.ffVisibility === "followers") {
ctx.status = 403;
ctx.set("Cache-Control", "public, max-age=30");
return;
}
//#endregion
const limit = 10;
const partOf = `${config.url}/users/${userId}/following`;
if (page) {
const query = {
followerId: user.id,
} as FindOptionsWhere<Following>;
// If a cursor is specified
if (cursor) {
query.id = LessThan(cursor);
}
// Get followings
const followings = await Followings.find({
where: query,
take: limit + 1,
order: { id: -1 },
});
// Whether there is a "next page" or not
const inStock = followings.length === limit + 1;
if (inStock) followings.pop();
const renderedFollowees = await Promise.all(
followings.map((following) => renderFollowUser(following.followeeId)),
);
const rendered = renderOrderedCollectionPage(
`${partOf}?${url.query({
page: "true",
cursor,
})}`,
user.followingCount,
renderedFollowees,
partOf,
undefined,
inStock
? `${partOf}?${url.query({
page: "true",
cursor: followings[followings.length - 1].id,
})}`
: undefined,
);
ctx.body = renderActivity(rendered);
setResponseType(ctx);
} else {
// index page
const rendered = renderOrderedCollection(
partOf,
user.followingCount,
`${partOf}?page=true`,
);
ctx.body = renderActivity(rendered);
setResponseType(ctx);
}
const meta = await fetchMeta();
if (meta.secureMode || meta.privateMode) {
ctx.set("Cache-Control", "private, max-age=0, must-revalidate");
} else {
ctx.set("Cache-Control", "public, max-age=180");
}
};