mirror of
https://iceshrimp.dev/limepotato/jormungandr-bite.git
synced 2024-11-22 09:57:29 -07:00
[mastodon-client] Code cleanup & reformat
This commit is contained in:
parent
8bc7bf373e
commit
afd9e236a3
21 changed files with 168 additions and 157 deletions
|
@ -38,7 +38,7 @@ export class NoteConverter {
|
||||||
host,
|
host,
|
||||||
));
|
));
|
||||||
|
|
||||||
const reactionCount = NoteReactions.countBy({noteId: note.id});
|
const reactionCount = NoteReactions.countBy({ noteId: note.id });
|
||||||
|
|
||||||
const reaction = user ? NoteReactions.findOneBy({
|
const reaction = user ? NoteReactions.findOneBy({
|
||||||
userId: user.id,
|
userId: user.id,
|
||||||
|
@ -87,7 +87,7 @@ export class NoteConverter {
|
||||||
});
|
});
|
||||||
|
|
||||||
const isPinned = user && note.userId === user.id
|
const isPinned = user && note.userId === user.id
|
||||||
? UserNotePinings.exist({where: {userId: user.id, noteId: note.id}})
|
? UserNotePinings.exist({ where: { userId: user.id, noteId: note.id } })
|
||||||
: undefined;
|
: undefined;
|
||||||
|
|
||||||
const tags = note.tags.map(tag => {
|
const tags = note.tags.map(tag => {
|
||||||
|
|
|
@ -27,14 +27,14 @@ export class UserConverter {
|
||||||
acct = `${u.username}@${u.host}`;
|
acct = `${u.username}@${u.host}`;
|
||||||
acctUrl = `https://${u.host}/@${u.username}`;
|
acctUrl = `https://${u.host}/@${u.username}`;
|
||||||
}
|
}
|
||||||
const profile = UserProfiles.findOneBy({userId: u.id});
|
const profile = UserProfiles.findOneBy({ userId: u.id });
|
||||||
const bio = profile.then(profile => MfmHelpers.toHtml(mfm.parse(profile?.description ?? "")) ?? escapeMFM(profile?.description ?? ""));
|
const bio = profile.then(profile => MfmHelpers.toHtml(mfm.parse(profile?.description ?? "")) ?? escapeMFM(profile?.description ?? ""));
|
||||||
const avatar = u.avatarId
|
const avatar = u.avatarId
|
||||||
? (DriveFiles.findOneBy({id: u.avatarId}))
|
? (DriveFiles.findOneBy({ id: u.avatarId }))
|
||||||
.then(p => p?.url ?? Users.getIdenticonUrl(u.id))
|
.then(p => p?.url ?? Users.getIdenticonUrl(u.id))
|
||||||
: Users.getIdenticonUrl(u.id);
|
: Users.getIdenticonUrl(u.id);
|
||||||
const banner = u.bannerId
|
const banner = u.bannerId
|
||||||
? (DriveFiles.findOneBy({id: u.bannerId}))
|
? (DriveFiles.findOneBy({ id: u.bannerId }))
|
||||||
.then(p => p?.url ?? `${config.url}/static-assets/transparent.png`)
|
.then(p => p?.url ?? `${config.url}/static-assets/transparent.png`)
|
||||||
: `${config.url}/static-assets/transparent.png`;
|
: `${config.url}/static-assets/transparent.png`;
|
||||||
const followersCount = profile.then(profile => {
|
const followersCount = profile.then(profile => {
|
||||||
|
|
|
@ -44,7 +44,7 @@ export function setupEndpointsList(router: Router): void {
|
||||||
auth(true, ['write:lists']),
|
auth(true, ['write:lists']),
|
||||||
async (ctx, reply) => {
|
async (ctx, reply) => {
|
||||||
const id = convertId(ctx.params.id, IdType.IceshrimpId);
|
const id = convertId(ctx.params.id, IdType.IceshrimpId);
|
||||||
const list = await UserLists.findOneBy({userId: ctx.user.id, id: id});
|
const list = await UserLists.findOneBy({ userId: ctx.user.id, id: id });
|
||||||
if (!list) throw new MastoApiError(404);
|
if (!list) throw new MastoApiError(404);
|
||||||
|
|
||||||
const body = ctx.request.body as any;
|
const body = ctx.request.body as any;
|
||||||
|
@ -58,7 +58,7 @@ export function setupEndpointsList(router: Router): void {
|
||||||
auth(true, ['write:lists']),
|
auth(true, ['write:lists']),
|
||||||
async (ctx, reply) => {
|
async (ctx, reply) => {
|
||||||
const id = convertId(ctx.params.id, IdType.IceshrimpId);
|
const id = convertId(ctx.params.id, IdType.IceshrimpId);
|
||||||
const list = await UserLists.findOneBy({userId: ctx.user.id, id: id});
|
const list = await UserLists.findOneBy({ userId: ctx.user.id, id: id });
|
||||||
if (!list) throw new MastoApiError(404);
|
if (!list) throw new MastoApiError(404);
|
||||||
|
|
||||||
await ListHelpers.deleteList(ctx.user, list);
|
await ListHelpers.deleteList(ctx.user, list);
|
||||||
|
@ -83,7 +83,7 @@ export function setupEndpointsList(router: Router): void {
|
||||||
auth(true, ['write:lists']),
|
auth(true, ['write:lists']),
|
||||||
async (ctx, reply) => {
|
async (ctx, reply) => {
|
||||||
const id = convertId(ctx.params.id, IdType.IceshrimpId);
|
const id = convertId(ctx.params.id, IdType.IceshrimpId);
|
||||||
const list = await UserLists.findOneBy({userId: ctx.user.id, id: id});
|
const list = await UserLists.findOneBy({ userId: ctx.user.id, id: id });
|
||||||
if (!list) throw new MastoApiError(404);
|
if (!list) throw new MastoApiError(404);
|
||||||
|
|
||||||
const body = ctx.request.body as any;
|
const body = ctx.request.body as any;
|
||||||
|
@ -100,7 +100,7 @@ export function setupEndpointsList(router: Router): void {
|
||||||
auth(true, ['write:lists']),
|
auth(true, ['write:lists']),
|
||||||
async (ctx, reply) => {
|
async (ctx, reply) => {
|
||||||
const id = convertId(ctx.params.id, IdType.IceshrimpId);
|
const id = convertId(ctx.params.id, IdType.IceshrimpId);
|
||||||
const list = await UserLists.findOneBy({userId: ctx.user.id, id: id});
|
const list = await UserLists.findOneBy({ userId: ctx.user.id, id: id });
|
||||||
if (!list) throw new MastoApiError(404);
|
if (!list) throw new MastoApiError(404);
|
||||||
|
|
||||||
const body = ctx.request.body as any;
|
const body = ctx.request.body as any;
|
||||||
|
|
|
@ -34,7 +34,7 @@ export function setupEndpointsMisc(router: Router): void {
|
||||||
auth(true, ['write:accounts']),
|
auth(true, ['write:accounts']),
|
||||||
async (ctx) => {
|
async (ctx) => {
|
||||||
const id = convertId(ctx.params.id, IdType.IceshrimpId);
|
const id = convertId(ctx.params.id, IdType.IceshrimpId);
|
||||||
const announcement = await Announcements.findOneBy({id: id});
|
const announcement = await Announcements.findOneBy({ id: id });
|
||||||
if (!announcement) throw new MastoApiError(404);
|
if (!announcement) throw new MastoApiError(404);
|
||||||
|
|
||||||
await MiscHelpers.dismissAnnouncement(announcement, ctx.user);
|
await MiscHelpers.dismissAnnouncement(announcement, ctx.user);
|
||||||
|
|
|
@ -1,6 +1,12 @@
|
||||||
import Router from "@koa/router";
|
import Router from "@koa/router";
|
||||||
import { convertId, IdType } from "../../index.js";
|
import { convertId, IdType } from "../../index.js";
|
||||||
import { convertAccountId, convertPollId, convertStatusIds, convertStatusEditIds, convertStatusSourceId, } from "../converters.js";
|
import {
|
||||||
|
convertAccountId,
|
||||||
|
convertPollId,
|
||||||
|
convertStatusEditIds,
|
||||||
|
convertStatusIds,
|
||||||
|
convertStatusSourceId,
|
||||||
|
} from "../converters.js";
|
||||||
import { NoteConverter } from "@/server/api/mastodon/converters/note.js";
|
import { NoteConverter } from "@/server/api/mastodon/converters/note.js";
|
||||||
import { NoteHelpers } from "@/server/api/mastodon/helpers/note.js";
|
import { NoteHelpers } from "@/server/api/mastodon/helpers/note.js";
|
||||||
import { convertPaginationArgsIds, limitToInt, normalizeUrlQuery } from "@/server/api/mastodon/endpoints/timeline.js";
|
import { convertPaginationArgsIds, limitToInt, normalizeUrlQuery } from "@/server/api/mastodon/endpoints/timeline.js";
|
||||||
|
@ -29,7 +35,7 @@ export function setupEndpointsStatus(router: Router): void {
|
||||||
.then(p => NoteConverter.encode(p, ctx.user))
|
.then(p => NoteConverter.encode(p, ctx.user))
|
||||||
.then(p => convertStatusIds(p));
|
.then(p => convertStatusIds(p));
|
||||||
|
|
||||||
if (key !== null) NoteHelpers.postIdempotencyCache.set(key, {status: ctx.body});
|
if (key !== null) NoteHelpers.postIdempotencyCache.set(key, { status: ctx.body });
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
router.put("/v1/statuses/:id",
|
router.put("/v1/statuses/:id",
|
||||||
|
@ -276,7 +282,7 @@ export function setupEndpointsStatus(router: Router): void {
|
||||||
const choices = toArray(body.choices ?? []).map(p => parseInt(p));
|
const choices = toArray(body.choices ?? []).map(p => parseInt(p));
|
||||||
if (choices.length < 1) {
|
if (choices.length < 1) {
|
||||||
ctx.status = 400;
|
ctx.status = 400;
|
||||||
ctx.body = {error: 'Must vote for at least one option'};
|
ctx.body = { error: 'Must vote for at least one option' };
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -103,7 +103,7 @@ export function setupEndpointsTimeline(router: Router): void {
|
||||||
auth(true, ['read:lists']),
|
auth(true, ['read:lists']),
|
||||||
async (ctx, reply) => {
|
async (ctx, reply) => {
|
||||||
const listId = convertId(ctx.params.listId, IdType.IceshrimpId);
|
const listId = convertId(ctx.params.listId, IdType.IceshrimpId);
|
||||||
const list = await UserLists.findOneBy({userId: ctx.user.id, id: listId});
|
const list = await UserLists.findOneBy({ userId: ctx.user.id, id: listId });
|
||||||
if (!list) throw new MastoApiError(404);
|
if (!list) throw new MastoApiError(404);
|
||||||
|
|
||||||
const args = normalizeUrlQuery(convertPaginationArgsIds(limitToInt(ctx.query)));
|
const args = normalizeUrlQuery(convertPaginationArgsIds(limitToInt(ctx.query)));
|
||||||
|
|
|
@ -10,7 +10,7 @@ import { MastoApiError } from "@/server/api/mastodon/middleware/catch-errors.js"
|
||||||
|
|
||||||
export class ListHelpers {
|
export class ListHelpers {
|
||||||
public static async getLists(user: ILocalUser): Promise<MastodonEntity.List[]> {
|
public static async getLists(user: ILocalUser): Promise<MastodonEntity.List[]> {
|
||||||
return UserLists.findBy({userId: user.id}).then(p => p.map(list => {
|
return UserLists.findBy({ userId: user.id }).then(p => p.map(list => {
|
||||||
return {
|
return {
|
||||||
id: list.id,
|
id: list.id,
|
||||||
title: list.name
|
title: list.name
|
||||||
|
@ -19,7 +19,7 @@ export class ListHelpers {
|
||||||
}
|
}
|
||||||
|
|
||||||
public static async getList(user: ILocalUser, id: string): Promise<MastodonEntity.List> {
|
public static async getList(user: ILocalUser, id: string): Promise<MastodonEntity.List> {
|
||||||
return UserLists.findOneByOrFail({userId: user.id, id: id}).then(list => {
|
return UserLists.findOneByOrFail({ userId: user.id, id: id }).then(list => {
|
||||||
return {
|
return {
|
||||||
id: list.id,
|
id: list.id,
|
||||||
title: list.name
|
title: list.name
|
||||||
|
@ -32,9 +32,10 @@ export class ListHelpers {
|
||||||
throw new MastoApiError(404);
|
throw new MastoApiError(404);
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
public static async getListUsers(user: ILocalUser, id: string, maxId: string | undefined, sinceId: string | undefined, minId: string | undefined, limit: number = 40): Promise<LinkPaginationObject<User[]>> {
|
public static async getListUsers(user: ILocalUser, id: string, maxId: string | undefined, sinceId: string | undefined, minId: string | undefined, limit: number = 40): Promise<LinkPaginationObject<User[]>> {
|
||||||
if (limit > 80) limit = 80;
|
if (limit > 80) limit = 80;
|
||||||
const list = await UserLists.findOneBy({userId: user.id, id: id});
|
const list = await UserLists.findOneBy({ userId: user.id, id: id });
|
||||||
if (!list) throw new MastoApiError(404);
|
if (!list) throw new MastoApiError(404);
|
||||||
const query = PaginationHelpers.makePaginationQuery(
|
const query = PaginationHelpers.makePaginationQuery(
|
||||||
UserListJoinings.createQueryBuilder('member'),
|
UserListJoinings.createQueryBuilder('member'),
|
||||||
|
@ -42,7 +43,7 @@ export class ListHelpers {
|
||||||
maxId,
|
maxId,
|
||||||
minId
|
minId
|
||||||
)
|
)
|
||||||
.andWhere("member.userListId = :listId", {listId: list.id})
|
.andWhere("member.userListId = :listId", { listId: list.id })
|
||||||
.innerJoinAndSelect("member.user", "user");
|
.innerJoinAndSelect("member.user", "user");
|
||||||
|
|
||||||
return query.take(limit).getMany().then(async p => {
|
return query.take(limit).getMany().then(async p => {
|
||||||
|
@ -125,9 +126,9 @@ export class ListHelpers {
|
||||||
if (title.length < 1) throw new MastoApiError(400, "Title must not be empty");
|
if (title.length < 1) throw new MastoApiError(400, "Title must not be empty");
|
||||||
if (user.id != list.userId) throw new Error("List is not owned by user");
|
if (user.id != list.userId) throw new Error("List is not owned by user");
|
||||||
|
|
||||||
const partial = {name: title};
|
const partial = { name: title };
|
||||||
const result = await UserLists.update(list.id, partial)
|
const result = await UserLists.update(list.id, partial)
|
||||||
.then(async _ => await UserLists.findOneByOrFail({id: list.id}));
|
.then(async _ => await UserLists.findOneByOrFail({ id: list.id }));
|
||||||
|
|
||||||
return {
|
return {
|
||||||
id: result.id,
|
id: result.id,
|
||||||
|
@ -140,9 +141,9 @@ export class ListHelpers {
|
||||||
.select("member.userListId")
|
.select("member.userListId")
|
||||||
.where("member.userId = :memberId");
|
.where("member.userId = :memberId");
|
||||||
const query = UserLists.createQueryBuilder('list')
|
const query = UserLists.createQueryBuilder('list')
|
||||||
.where("list.userId = :userId", {userId: user.id})
|
.where("list.userId = :userId", { userId: user.id })
|
||||||
.andWhere(`list.id IN (${joinQuery.getQuery()})`)
|
.andWhere(`list.id IN (${joinQuery.getQuery()})`)
|
||||||
.setParameters({memberId: member.id});
|
.setParameters({ memberId: member.id });
|
||||||
|
|
||||||
return query.getMany()
|
return query.getMany()
|
||||||
.then(results => results.map(result => {
|
.then(results => results.map(result => {
|
||||||
|
|
|
@ -34,7 +34,7 @@ export class MediaHelpers {
|
||||||
comment: body?.description ?? undefined
|
comment: body?.description ?? undefined
|
||||||
});
|
});
|
||||||
|
|
||||||
return DriveFiles.findOneByOrFail({id: file.id, userId: user.id})
|
return DriveFiles.findOneByOrFail({ id: file.id, userId: user.id })
|
||||||
.then(p => DriveFiles.pack(p));
|
.then(p => DriveFiles.pack(p));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -51,7 +51,7 @@ export class MediaHelpers {
|
||||||
}
|
}
|
||||||
|
|
||||||
public static async getMedia(user: ILocalUser, id: string): Promise<DriveFile | null> {
|
public static async getMedia(user: ILocalUser, id: string): Promise<DriveFile | null> {
|
||||||
return DriveFiles.findOneBy({id: id, userId: user.id});
|
return DriveFiles.findOneBy({ id: id, userId: user.id });
|
||||||
}
|
}
|
||||||
|
|
||||||
public static async getMediaOr404(user: ILocalUser, id: string): Promise<DriveFile> {
|
public static async getMediaOr404(user: ILocalUser, id: string): Promise<DriveFile> {
|
||||||
|
|
|
@ -13,7 +13,7 @@ export class MfmHelpers {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const {window} = new JSDOM("");
|
const { window } = new JSDOM("");
|
||||||
|
|
||||||
const doc = window.document;
|
const doc = window.document;
|
||||||
|
|
||||||
|
@ -122,7 +122,7 @@ export class MfmHelpers {
|
||||||
|
|
||||||
mention(node) {
|
mention(node) {
|
||||||
const a = doc.createElement("a");
|
const a = doc.createElement("a");
|
||||||
const {username, host, acct} = node.props;
|
const { username, host, acct } = node.props;
|
||||||
const remoteUserInfo = mentionedRemoteUsers.find(
|
const remoteUserInfo = mentionedRemoteUsers.find(
|
||||||
(remoteUser) =>
|
(remoteUser) =>
|
||||||
remoteUser.username === username && remoteUser.host === host,
|
remoteUser.username === username && remoteUser.host === host,
|
||||||
|
|
|
@ -7,11 +7,10 @@ import { awaitAll } from "@/prelude/await-all.js";
|
||||||
import { UserConverter } from "@/server/api/mastodon/converters/user.js";
|
import { UserConverter } from "@/server/api/mastodon/converters/user.js";
|
||||||
import { convertAccountId } from "@/server/api/mastodon/converters.js";
|
import { convertAccountId } from "@/server/api/mastodon/converters.js";
|
||||||
import { Announcement } from "@/models/entities/announcement.js";
|
import { Announcement } from "@/models/entities/announcement.js";
|
||||||
import { ILocalUser } from "@/models/entities/user.js";
|
import { ILocalUser, User } from "@/models/entities/user.js";
|
||||||
import { AnnouncementConverter } from "@/server/api/mastodon/converters/announcement.js";
|
import { AnnouncementConverter } from "@/server/api/mastodon/converters/announcement.js";
|
||||||
import { genId } from "@/misc/gen-id.js";
|
import { genId } from "@/misc/gen-id.js";
|
||||||
import * as Acct from "@/misc/acct.js";
|
import * as Acct from "@/misc/acct.js";
|
||||||
import { User } from "@/models/entities/user.js";
|
|
||||||
import { UserHelpers } from "@/server/api/mastodon/helpers/user.js";
|
import { UserHelpers } from "@/server/api/mastodon/helpers/user.js";
|
||||||
import { generateMutedUserQueryForUsers } from "@/server/api/common/generate-muted-user-query.js";
|
import { generateMutedUserQueryForUsers } from "@/server/api/common/generate-muted-user-query.js";
|
||||||
import { generateBlockQueryForUsers } from "@/server/api/common/generate-block-query.js";
|
import { generateBlockQueryForUsers } from "@/server/api/common/generate-block-query.js";
|
||||||
|
@ -23,8 +22,8 @@ import { VisibilityConverter } from "@/server/api/mastodon/converters/visibility
|
||||||
|
|
||||||
export class MiscHelpers {
|
export class MiscHelpers {
|
||||||
public static async getInstance(): Promise<MastodonEntity.Instance> {
|
public static async getInstance(): Promise<MastodonEntity.Instance> {
|
||||||
const userCount = Users.count({where: {host: IsNull()}});
|
const userCount = Users.count({ where: { host: IsNull() } });
|
||||||
const noteCount = Notes.count({where: {userHost: IsNull()}});
|
const noteCount = Notes.count({ where: { userHost: IsNull() } });
|
||||||
const instanceCount = Instances.count({ cache: 3600000 });
|
const instanceCount = Instances.count({ cache: 3600000 });
|
||||||
const contact = await Users.findOne({
|
const contact = await Users.findOne({
|
||||||
where: {
|
where: {
|
||||||
|
@ -33,7 +32,7 @@ export class MiscHelpers {
|
||||||
isDeleted: false,
|
isDeleted: false,
|
||||||
isSuspended: false,
|
isSuspended: false,
|
||||||
},
|
},
|
||||||
order: {id: "ASC"},
|
order: { id: "ASC" },
|
||||||
})
|
})
|
||||||
.then(p => p ? UserConverter.encode(p) : null)
|
.then(p => p ? UserConverter.encode(p) : null)
|
||||||
.then(p => p ? convertAccountId(p) : null);
|
.then(p => p ? convertAccountId(p) : null);
|
||||||
|
@ -100,9 +99,9 @@ export class MiscHelpers {
|
||||||
if (includeRead) {
|
if (includeRead) {
|
||||||
const [announcements, reads] = await Promise.all([
|
const [announcements, reads] = await Promise.all([
|
||||||
Announcements.createQueryBuilder("announcement")
|
Announcements.createQueryBuilder("announcement")
|
||||||
.orderBy({"announcement.id": "DESC"})
|
.orderBy({ "announcement.id": "DESC" })
|
||||||
.getMany(),
|
.getMany(),
|
||||||
AnnouncementReads.findBy({userId: user.id})
|
AnnouncementReads.findBy({ userId: user.id })
|
||||||
.then(p => p.map(x => x.announcementId))
|
.then(p => p.map(x => x.announcementId))
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
@ -115,7 +114,7 @@ export class MiscHelpers {
|
||||||
|
|
||||||
const query = Announcements.createQueryBuilder("announcement")
|
const query = Announcements.createQueryBuilder("announcement")
|
||||||
.where(`announcement.id NOT IN (${sq.getQuery()})`)
|
.where(`announcement.id NOT IN (${sq.getQuery()})`)
|
||||||
.orderBy({"announcement.id": "DESC"})
|
.orderBy({ "announcement.id": "DESC" })
|
||||||
.setParameter("userId", user.id);
|
.setParameter("userId", user.id);
|
||||||
|
|
||||||
return query.getMany()
|
return query.getMany()
|
||||||
|
@ -123,7 +122,7 @@ export class MiscHelpers {
|
||||||
}
|
}
|
||||||
|
|
||||||
public static async dismissAnnouncement(announcement: Announcement, user: ILocalUser): Promise<void> {
|
public static async dismissAnnouncement(announcement: Announcement, user: ILocalUser): Promise<void> {
|
||||||
const exists = await AnnouncementReads.exist({where: {userId: user.id, announcementId: announcement.id}});
|
const exists = await AnnouncementReads.exist({ where: { userId: user.id, announcementId: announcement.id } });
|
||||||
if (!exists) {
|
if (!exists) {
|
||||||
await AnnouncementReads.insert({
|
await AnnouncementReads.insert({
|
||||||
id: genId(),
|
id: genId(),
|
||||||
|
@ -150,7 +149,7 @@ export class MiscHelpers {
|
||||||
.then(p => p.filter(x => !!x) as User[])
|
.then(p => p.filter(x => !!x) as User[])
|
||||||
.then(p => UserConverter.encodeMany(p, cache))
|
.then(p => UserConverter.encodeMany(p, cache))
|
||||||
.then(p => p.map(x => {
|
.then(p => p.map(x => {
|
||||||
return {source: "staff", account: x} as MastodonEntity.SuggestedAccount
|
return { source: "staff", account: x } as MastodonEntity.SuggestedAccount
|
||||||
}))
|
}))
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -170,7 +169,7 @@ export class MiscHelpers {
|
||||||
.getMany()
|
.getMany()
|
||||||
.then(p => UserConverter.encodeMany(p, cache))
|
.then(p => UserConverter.encodeMany(p, cache))
|
||||||
.then(p => p.map(x => {
|
.then(p => p.map(x => {
|
||||||
return {source: "global", account: x} as MastodonEntity.SuggestedAccount
|
return { source: "global", account: x } as MastodonEntity.SuggestedAccount
|
||||||
}));
|
}));
|
||||||
|
|
||||||
results.push(pinned);
|
results.push(pinned);
|
||||||
|
@ -192,7 +191,8 @@ export class MiscHelpers {
|
||||||
cache: {
|
cache: {
|
||||||
id: "meta_emojis",
|
id: "meta_emojis",
|
||||||
milliseconds: 3600000, // 1 hour
|
milliseconds: 3600000, // 1 hour
|
||||||
}}
|
}
|
||||||
|
}
|
||||||
)
|
)
|
||||||
.then(dbRes => populateEmojis(dbRes.map(p => p.name), null)
|
.then(dbRes => populateEmojis(dbRes.map(p => p.name), null)
|
||||||
.then(p => p.map(x => EmojiConverter.encode(x))
|
.then(p => p.map(x => EmojiConverter.encode(x))
|
||||||
|
@ -230,7 +230,7 @@ export class MiscHelpers {
|
||||||
}
|
}
|
||||||
|
|
||||||
public static getPreferences(user: ILocalUser): Promise<MastodonEntity.Preferences> {
|
public static getPreferences(user: ILocalUser): Promise<MastodonEntity.Preferences> {
|
||||||
const profile = UserProfiles.findOneByOrFail({userId: user.id});
|
const profile = UserProfiles.findOneByOrFail({ userId: user.id });
|
||||||
const sensitive = profile.then(p => p.alwaysMarkNsfw);
|
const sensitive = profile.then(p => p.alwaysMarkNsfw);
|
||||||
const language = profile.then(p => p.lang);
|
const language = profile.then(p => p.lang);
|
||||||
const privacy = UserHelpers.getDefaultNoteVisibility(user)
|
const privacy = UserHelpers.getDefaultNoteVisibility(user)
|
||||||
|
|
|
@ -147,7 +147,7 @@ export class NoteHelpers {
|
||||||
maxId,
|
maxId,
|
||||||
minId
|
minId
|
||||||
)
|
)
|
||||||
.andWhere("reaction.noteId = :noteId", {noteId: note.id})
|
.andWhere("reaction.noteId = :noteId", { noteId: note.id })
|
||||||
.innerJoinAndSelect("reaction.user", "user");
|
.innerJoinAndSelect("reaction.user", "user");
|
||||||
|
|
||||||
return query.take(limit).getMany().then(async p => {
|
return query.take(limit).getMany().then(async p => {
|
||||||
|
@ -168,7 +168,7 @@ export class NoteHelpers {
|
||||||
const cache = UserHelpers.getFreshAccountCache();
|
const cache = UserHelpers.getFreshAccountCache();
|
||||||
const account = Promise.resolve(note.user ?? await UserHelpers.getUserCached(note.userId, cache))
|
const account = Promise.resolve(note.user ?? await UserHelpers.getUserCached(note.userId, cache))
|
||||||
.then(p => UserConverter.encode(p, cache));
|
.then(p => UserConverter.encode(p, cache));
|
||||||
const edits = await NoteEdits.find({where: {noteId: note.id}, order: {id: "ASC"}});
|
const edits = await NoteEdits.find({ where: { noteId: note.id }, order: { id: "ASC" } });
|
||||||
const history: Promise<MastodonEntity.StatusEdit>[] = [];
|
const history: Promise<MastodonEntity.StatusEdit>[] = [];
|
||||||
|
|
||||||
const curr = {
|
const curr = {
|
||||||
|
@ -219,7 +219,7 @@ export class NoteHelpers {
|
||||||
maxId,
|
maxId,
|
||||||
minId
|
minId
|
||||||
)
|
)
|
||||||
.andWhere("note.renoteId = :noteId", {noteId: note.id})
|
.andWhere("note.renoteId = :noteId", { noteId: note.id })
|
||||||
.andWhere("note.text IS NULL") // We don't want to count quotes as renotes
|
.andWhere("note.text IS NULL") // We don't want to count quotes as renotes
|
||||||
.innerJoinAndSelect("note.user", "user");
|
.innerJoinAndSelect("note.user", "user");
|
||||||
|
|
||||||
|
@ -244,7 +244,7 @@ export class NoteHelpers {
|
||||||
const query = makePaginationQuery(Notes.createQueryBuilder("note"))
|
const query = makePaginationQuery(Notes.createQueryBuilder("note"))
|
||||||
.andWhere(
|
.andWhere(
|
||||||
"note.id IN (SELECT id FROM note_replies(:noteId, :depth, :limit))",
|
"note.id IN (SELECT id FROM note_replies(:noteId, :depth, :limit))",
|
||||||
{noteId, depth, limit},
|
{ noteId, depth, limit },
|
||||||
);
|
);
|
||||||
|
|
||||||
generateVisibilityQuery(query, user);
|
generateVisibilityQuery(query, user);
|
||||||
|
|
|
@ -2,7 +2,7 @@ import { ILocalUser } from "@/models/entities/user.js";
|
||||||
import { Notes, Notifications } from "@/models/index.js";
|
import { Notes, Notifications } from "@/models/index.js";
|
||||||
import { PaginationHelpers } from "@/server/api/mastodon/helpers/pagination.js";
|
import { PaginationHelpers } from "@/server/api/mastodon/helpers/pagination.js";
|
||||||
import { Notification } from "@/models/entities/notification.js";
|
import { Notification } from "@/models/entities/notification.js";
|
||||||
import {MastoApiError} from "@/server/api/mastodon/middleware/catch-errors.js";
|
import { MastoApiError } from "@/server/api/mastodon/middleware/catch-errors.js";
|
||||||
|
|
||||||
export class NotificationHelpers {
|
export class NotificationHelpers {
|
||||||
public static async getNotifications(user: ILocalUser, maxId: string | undefined, sinceId: string | undefined, minId: string | undefined, limit: number = 40, types: string[] | undefined, excludeTypes: string[] | undefined, accountId: string | undefined): Promise<Notification[]> {
|
public static async getNotifications(user: ILocalUser, maxId: string | undefined, sinceId: string | undefined, minId: string | undefined, limit: number = 40, types: string[] | undefined, excludeTypes: string[] | undefined, accountId: string | undefined): Promise<Notification[]> {
|
||||||
|
@ -24,11 +24,11 @@ export class NotificationHelpers {
|
||||||
maxId,
|
maxId,
|
||||||
minId
|
minId
|
||||||
)
|
)
|
||||||
.andWhere("notification.notifieeId = :userId", {userId: user.id})
|
.andWhere("notification.notifieeId = :userId", { userId: user.id })
|
||||||
.andWhere("notification.type IN (:...types)", {types: requestedTypes});
|
.andWhere("notification.type IN (:...types)", { types: requestedTypes });
|
||||||
|
|
||||||
if (accountId !== undefined)
|
if (accountId !== undefined)
|
||||||
query.andWhere("notification.notifierId = :notifierId", {notifierId: accountId});
|
query.andWhere("notification.notifierId = :notifierId", { notifierId: accountId });
|
||||||
|
|
||||||
query.leftJoinAndSelect("notification.note", "note");
|
query.leftJoinAndSelect("notification.note", "note");
|
||||||
|
|
||||||
|
@ -36,7 +36,7 @@ export class NotificationHelpers {
|
||||||
}
|
}
|
||||||
|
|
||||||
public static async getNotification(id: string, user: ILocalUser): Promise<Notification | null> {
|
public static async getNotification(id: string, user: ILocalUser): Promise<Notification | null> {
|
||||||
return Notifications.findOneBy({id: id, notifieeId: user.id});
|
return Notifications.findOneBy({ id: id, notifieeId: user.id });
|
||||||
}
|
}
|
||||||
|
|
||||||
public static async getNotificationOr404(id: string, user: ILocalUser): Promise<Notification> {
|
public static async getNotificationOr404(id: string, user: ILocalUser): Promise<Notification> {
|
||||||
|
@ -47,11 +47,11 @@ export class NotificationHelpers {
|
||||||
}
|
}
|
||||||
|
|
||||||
public static async dismissNotification(id: string, user: ILocalUser): Promise<void> {
|
public static async dismissNotification(id: string, user: ILocalUser): Promise<void> {
|
||||||
const result = await Notifications.update({id: id, notifieeId: user.id}, {isRead: true});
|
const result = await Notifications.update({ id: id, notifieeId: user.id }, { isRead: true });
|
||||||
}
|
}
|
||||||
|
|
||||||
public static async clearAllNotifications(user: ILocalUser): Promise<void> {
|
public static async clearAllNotifications(user: ILocalUser): Promise<void> {
|
||||||
await Notifications.update({notifieeId: user.id}, {isRead: true});
|
await Notifications.update({ notifieeId: user.id }, { isRead: true });
|
||||||
}
|
}
|
||||||
|
|
||||||
public static async markConversationAsRead(id: string, user: ILocalUser): Promise<void> {
|
public static async markConversationAsRead(id: string, user: ILocalUser): Promise<void> {
|
||||||
|
@ -68,7 +68,7 @@ export class NotificationHelpers {
|
||||||
.setParameter("conversationId", id)
|
.setParameter("conversationId", id)
|
||||||
.setParameter("types", ['reply', 'mention'])
|
.setParameter("types", ['reply', 'mention'])
|
||||||
.update()
|
.update()
|
||||||
.set({isRead: true})
|
.set({ isRead: true })
|
||||||
.execute();
|
.execute();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -13,22 +13,22 @@ export class PaginationHelpers {
|
||||||
if (sinceId && minId) throw new Error("Can't user both sinceId and minId params");
|
if (sinceId && minId) throw new Error("Can't user both sinceId and minId params");
|
||||||
|
|
||||||
if (sinceId && maxId) {
|
if (sinceId && maxId) {
|
||||||
q.andWhere(`${idField} > :sinceId`, {sinceId: sinceId});
|
q.andWhere(`${idField} > :sinceId`, { sinceId: sinceId });
|
||||||
q.andWhere(`${idField} < :maxId`, {maxId: maxId});
|
q.andWhere(`${idField} < :maxId`, { maxId: maxId });
|
||||||
q.orderBy(`${idField}`, "DESC");
|
q.orderBy(`${idField}`, "DESC");
|
||||||
}
|
}
|
||||||
if (minId && maxId) {
|
if (minId && maxId) {
|
||||||
q.andWhere(`${idField} > :minId`, {minId: minId});
|
q.andWhere(`${idField} > :minId`, { minId: minId });
|
||||||
q.andWhere(`${idField} < :maxId`, {maxId: maxId});
|
q.andWhere(`${idField} < :maxId`, { maxId: maxId });
|
||||||
q.orderBy(`${idField}`, "ASC");
|
q.orderBy(`${idField}`, "ASC");
|
||||||
} else if (sinceId) {
|
} else if (sinceId) {
|
||||||
q.andWhere(`${idField} > :sinceId`, {sinceId: sinceId});
|
q.andWhere(`${idField} > :sinceId`, { sinceId: sinceId });
|
||||||
q.orderBy(`${idField}`, "DESC");
|
q.orderBy(`${idField}`, "DESC");
|
||||||
} else if (minId) {
|
} else if (minId) {
|
||||||
q.andWhere(`${idField} > :minId`, {minId: minId});
|
q.andWhere(`${idField} > :minId`, { minId: minId });
|
||||||
q.orderBy(`${idField}`, "ASC");
|
q.orderBy(`${idField}`, "ASC");
|
||||||
} else if (maxId) {
|
} else if (maxId) {
|
||||||
q.andWhere(`${idField} < :maxId`, {maxId: maxId});
|
q.andWhere(`${idField} < :maxId`, { maxId: maxId });
|
||||||
q.orderBy(`${idField}`, "DESC");
|
q.orderBy(`${idField}`, "DESC");
|
||||||
} else {
|
} else {
|
||||||
q.orderBy(`${idField}`, "DESC");
|
q.orderBy(`${idField}`, "DESC");
|
||||||
|
|
|
@ -10,7 +10,7 @@ import { deliver } from "@/queue/index.js";
|
||||||
import { renderActivity } from "@/remote/activitypub/renderer/index.js";
|
import { renderActivity } from "@/remote/activitypub/renderer/index.js";
|
||||||
import renderVote from "@/remote/activitypub/renderer/vote.js";
|
import renderVote from "@/remote/activitypub/renderer/vote.js";
|
||||||
import { Not } from "typeorm";
|
import { Not } from "typeorm";
|
||||||
import {MastoApiError} from "@/server/api/mastodon/middleware/catch-errors.js";
|
import { MastoApiError } from "@/server/api/mastodon/middleware/catch-errors.js";
|
||||||
|
|
||||||
export class PollHelpers {
|
export class PollHelpers {
|
||||||
public static async getPoll(note: Note, user: ILocalUser | null): Promise<MastodonEntity.Poll> {
|
public static async getPoll(note: Note, user: ILocalUser | null): Promise<MastodonEntity.Poll> {
|
||||||
|
@ -34,7 +34,7 @@ export class PollHelpers {
|
||||||
if (block) throw new Error('You are blocked by the poll author');
|
if (block) throw new Error('You are blocked by the poll author');
|
||||||
}
|
}
|
||||||
|
|
||||||
const poll = await Polls.findOneByOrFail({noteId: note.id});
|
const poll = await Polls.findOneByOrFail({ noteId: note.id });
|
||||||
|
|
||||||
if (poll.expiresAt && poll.expiresAt < createdAt) throw new Error('Poll is expired');
|
if (poll.expiresAt && poll.expiresAt < createdAt) throw new Error('Poll is expired');
|
||||||
|
|
||||||
|
|
|
@ -63,7 +63,10 @@ export class SearchHelpers {
|
||||||
if (!match) match = q.match(/^@(?<user>[a-zA-Z0-9_]+)$/)
|
if (!match) match = q.match(/^@(?<user>[a-zA-Z0-9_]+)$/)
|
||||||
if (match) {
|
if (match) {
|
||||||
// check if user is already in database
|
// check if user is already in database
|
||||||
const dbResult = await Users.findOneBy({usernameLower: match.groups!.user.toLowerCase(), host: match.groups?.host ?? IsNull()});
|
const dbResult = await Users.findOneBy({
|
||||||
|
usernameLower: match.groups!.user.toLowerCase(),
|
||||||
|
host: match.groups?.host ?? IsNull()
|
||||||
|
});
|
||||||
if (dbResult) return [dbResult];
|
if (dbResult) return [dbResult];
|
||||||
|
|
||||||
const result = await resolveUser(match.groups!.user.toLowerCase(), match.groups?.host ?? null);
|
const result = await resolveUser(match.groups!.user.toLowerCase(), match.groups?.host ?? null);
|
||||||
|
@ -89,23 +92,23 @@ export class SearchHelpers {
|
||||||
if (following) {
|
if (following) {
|
||||||
const followingQuery = Followings.createQueryBuilder("following")
|
const followingQuery = Followings.createQueryBuilder("following")
|
||||||
.select("following.followeeId")
|
.select("following.followeeId")
|
||||||
.where("following.followerId = :followerId", {followerId: user.id});
|
.where("following.followerId = :followerId", { followerId: user.id });
|
||||||
|
|
||||||
query.andWhere(
|
query.andWhere(
|
||||||
new Brackets((qb) => {
|
new Brackets((qb) => {
|
||||||
qb.where(`user.id IN (${followingQuery.getQuery()} UNION ALL VALUES (:meId))`, {meId: user.id});
|
qb.where(`user.id IN (${followingQuery.getQuery()} UNION ALL VALUES (:meId))`, { meId: user.id });
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
query.andWhere(
|
query.andWhere(
|
||||||
new Brackets((qb) => {
|
new Brackets((qb) => {
|
||||||
qb.where("user.name ILIKE :q", {q: `%${sqlLikeEscape(q)}%`});
|
qb.where("user.name ILIKE :q", { q: `%${sqlLikeEscape(q)}%` });
|
||||||
qb.orWhere("user.usernameLower ILIKE :q", {q: `%${sqlLikeEscape(q)}%`});
|
qb.orWhere("user.usernameLower ILIKE :q", { q: `%${sqlLikeEscape(q)}%` });
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
query.orderBy({'user.notesCount': 'DESC'});
|
query.orderBy({ 'user.notesCount': 'DESC' });
|
||||||
|
|
||||||
return query.skip(offset ?? 0).take(limit).getMany().then(p => minId ? p.reverse() : p);
|
return query.skip(offset ?? 0).take(limit).getMany().then(p => minId ? p.reverse() : p);
|
||||||
}
|
}
|
||||||
|
@ -184,8 +187,8 @@ export class SearchHelpers {
|
||||||
const chunk = ids.slice(start, start + chunkSize);
|
const chunk = ids.slice(start, start + chunkSize);
|
||||||
|
|
||||||
const query = Notes.createQueryBuilder("note")
|
const query = Notes.createQueryBuilder("note")
|
||||||
.where({id: In(chunk)})
|
.where({ id: In(chunk) })
|
||||||
.orderBy({id: "DESC"})
|
.orderBy({ id: "DESC" })
|
||||||
|
|
||||||
generateVisibilityQuery(query, user);
|
generateVisibilityQuery(query, user);
|
||||||
|
|
||||||
|
@ -197,11 +200,11 @@ export class SearchHelpers {
|
||||||
if (following) {
|
if (following) {
|
||||||
const followingQuery = Followings.createQueryBuilder("following")
|
const followingQuery = Followings.createQueryBuilder("following")
|
||||||
.select("following.followeeId")
|
.select("following.followeeId")
|
||||||
.where("following.followerId = :followerId", {followerId: user.id});
|
.where("following.followerId = :followerId", { followerId: user.id });
|
||||||
|
|
||||||
query.andWhere(
|
query.andWhere(
|
||||||
new Brackets((qb) => {
|
new Brackets((qb) => {
|
||||||
qb.where(`note.userId IN (${followingQuery.getQuery()} UNION ALL VALUES (:meId))`, {meId: user.id});
|
qb.where(`note.userId IN (${followingQuery.getQuery()} UNION ALL VALUES (:meId))`, { meId: user.id });
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -267,8 +270,8 @@ export class SearchHelpers {
|
||||||
const chunk = ids.slice(start, start + chunkSize);
|
const chunk = ids.slice(start, start + chunkSize);
|
||||||
|
|
||||||
const query = Notes.createQueryBuilder("note")
|
const query = Notes.createQueryBuilder("note")
|
||||||
.where({id: In(chunk)})
|
.where({ id: In(chunk) })
|
||||||
.orderBy({id: "DESC"})
|
.orderBy({ id: "DESC" })
|
||||||
|
|
||||||
generateVisibilityQuery(query, user);
|
generateVisibilityQuery(query, user);
|
||||||
|
|
||||||
|
@ -356,23 +359,23 @@ export class SearchHelpers {
|
||||||
);
|
);
|
||||||
|
|
||||||
if (accountId) {
|
if (accountId) {
|
||||||
query.andWhere("note.userId = :userId", {userId: accountId});
|
query.andWhere("note.userId = :userId", { userId: accountId });
|
||||||
}
|
}
|
||||||
|
|
||||||
if (following) {
|
if (following) {
|
||||||
const followingQuery = Followings.createQueryBuilder("following")
|
const followingQuery = Followings.createQueryBuilder("following")
|
||||||
.select("following.followeeId")
|
.select("following.followeeId")
|
||||||
.where("following.followerId = :followerId", {followerId: user.id});
|
.where("following.followerId = :followerId", { followerId: user.id });
|
||||||
|
|
||||||
query.andWhere(
|
query.andWhere(
|
||||||
new Brackets((qb) => {
|
new Brackets((qb) => {
|
||||||
qb.where(`note.userId IN (${followingQuery.getQuery()} UNION ALL VALUES (:meId))`, {meId: user.id});
|
qb.where(`note.userId IN (${followingQuery.getQuery()} UNION ALL VALUES (:meId))`, { meId: user.id });
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
query
|
query
|
||||||
.andWhere("note.text ILIKE :q", {q: `%${sqlLikeEscape(q)}%`})
|
.andWhere("note.text ILIKE :q", { q: `%${sqlLikeEscape(q)}%` })
|
||||||
.leftJoinAndSelect("note.renote", "renote");
|
.leftJoinAndSelect("note.renote", "renote");
|
||||||
|
|
||||||
|
|
||||||
|
@ -390,8 +393,8 @@ export class SearchHelpers {
|
||||||
const tags = Hashtags.createQueryBuilder('tag')
|
const tags = Hashtags.createQueryBuilder('tag')
|
||||||
.select('tag.name')
|
.select('tag.name')
|
||||||
.distinctOn(['tag.name'])
|
.distinctOn(['tag.name'])
|
||||||
.where("tag.name ILIKE :q", {q: `%${sqlLikeEscape(q)}%`})
|
.where("tag.name ILIKE :q", { q: `%${sqlLikeEscape(q)}%` })
|
||||||
.orderBy({'tag.name': 'ASC'})
|
.orderBy({ 'tag.name': 'ASC' })
|
||||||
.skip(offset ?? 0).take(limit).getMany();
|
.skip(offset ?? 0).take(limit).getMany();
|
||||||
|
|
||||||
return tags.then(p => p.map(tag => {
|
return tags.then(p => p.map(tag => {
|
||||||
|
|
|
@ -17,7 +17,7 @@ import { UserConverter } from "@/server/api/mastodon/converters/user.js";
|
||||||
import { NoteConverter } from "@/server/api/mastodon/converters/note.js";
|
import { NoteConverter } from "@/server/api/mastodon/converters/note.js";
|
||||||
import { awaitAll } from "@/prelude/await-all.js";
|
import { awaitAll } from "@/prelude/await-all.js";
|
||||||
import { unique } from "@/prelude/array.js";
|
import { unique } from "@/prelude/array.js";
|
||||||
import {MastoApiError} from "@/server/api/mastodon/middleware/catch-errors.js";
|
import { MastoApiError } from "@/server/api/mastodon/middleware/catch-errors.js";
|
||||||
|
|
||||||
export class TimelineHelpers {
|
export class TimelineHelpers {
|
||||||
public static async getHomeTimeline(user: ILocalUser, maxId: string | undefined, sinceId: string | undefined, minId: string | undefined, limit: number = 20): Promise<Note[]> {
|
public static async getHomeTimeline(user: ILocalUser, maxId: string | undefined, sinceId: string | undefined, minId: string | undefined, limit: number = 20): Promise<Note[]> {
|
||||||
|
@ -25,7 +25,7 @@ export class TimelineHelpers {
|
||||||
|
|
||||||
const followingQuery = Followings.createQueryBuilder("following")
|
const followingQuery = Followings.createQueryBuilder("following")
|
||||||
.select("following.followeeId")
|
.select("following.followeeId")
|
||||||
.where("following.followerId = :followerId", {followerId: user.id});
|
.where("following.followerId = :followerId", { followerId: user.id });
|
||||||
|
|
||||||
const query = PaginationHelpers.makePaginationQuery(
|
const query = PaginationHelpers.makePaginationQuery(
|
||||||
Notes.createQueryBuilder("note"),
|
Notes.createQueryBuilder("note"),
|
||||||
|
@ -35,7 +35,7 @@ export class TimelineHelpers {
|
||||||
)
|
)
|
||||||
.andWhere(
|
.andWhere(
|
||||||
new Brackets((qb) => {
|
new Brackets((qb) => {
|
||||||
qb.where(`note.userId IN (${followingQuery.getQuery()} UNION ALL VALUES (:meId))`, {meId: user.id});
|
qb.where(`note.userId IN (${followingQuery.getQuery()} UNION ALL VALUES (:meId))`, { meId: user.id });
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
.leftJoinAndSelect("note.renote", "renote");
|
.leftJoinAndSelect("note.renote", "renote");
|
||||||
|
@ -114,7 +114,7 @@ export class TimelineHelpers {
|
||||||
.andWhere(`note.userId IN (${listQuery.getQuery()})`)
|
.andWhere(`note.userId IN (${listQuery.getQuery()})`)
|
||||||
.andWhere("note.visibility != 'specified'")
|
.andWhere("note.visibility != 'specified'")
|
||||||
.leftJoinAndSelect("note.renote", "renote")
|
.leftJoinAndSelect("note.renote", "renote")
|
||||||
.setParameters({listId: list.id});
|
.setParameters({ listId: list.id });
|
||||||
|
|
||||||
generateVisibilityQuery(query, user);
|
generateVisibilityQuery(query, user);
|
||||||
|
|
||||||
|
@ -137,11 +137,11 @@ export class TimelineHelpers {
|
||||||
minId
|
minId
|
||||||
)
|
)
|
||||||
.andWhere("note.visibility = 'public'")
|
.andWhere("note.visibility = 'public'")
|
||||||
.andWhere("note.tags @> array[:tag]::varchar[]", {tag: tag});
|
.andWhere("note.tags @> array[:tag]::varchar[]", { tag: tag });
|
||||||
|
|
||||||
if (any.length > 0) query.andWhere("note.tags && array[:...any]::varchar[]", {any: any});
|
if (any.length > 0) query.andWhere("note.tags && array[:...any]::varchar[]", { any: any });
|
||||||
if (all.length > 0) query.andWhere("note.tags @> array[:...all]::varchar[]", {all: all});
|
if (all.length > 0) query.andWhere("note.tags @> array[:...all]::varchar[]", { all: all });
|
||||||
if (none.length > 0) query.andWhere("NOT(note.tags @> array[:...none]::varchar[])", {none: none});
|
if (none.length > 0) query.andWhere("NOT(note.tags @> array[:...none]::varchar[])", { none: none });
|
||||||
|
|
||||||
if (remote) query.andWhere("note.userHost IS NOT NULL");
|
if (remote) query.andWhere("note.userHost IS NOT NULL");
|
||||||
if (local) query.andWhere("note.userHost IS NULL");
|
if (local) query.andWhere("note.userHost IS NULL");
|
||||||
|
@ -168,7 +168,7 @@ export class TimelineHelpers {
|
||||||
.select("COALESCE(note.threadId, note.id)", "conversationId")
|
.select("COALESCE(note.threadId, note.id)", "conversationId")
|
||||||
.addSelect("note.id", "latest")
|
.addSelect("note.id", "latest")
|
||||||
.distinctOn(["COALESCE(note.threadId, note.id)"])
|
.distinctOn(["COALESCE(note.threadId, note.id)"])
|
||||||
.orderBy({"COALESCE(note.threadId, note.id)": minId ? "ASC" : "DESC", "note.id": "DESC"})
|
.orderBy({ "COALESCE(note.threadId, note.id)": minId ? "ASC" : "DESC", "note.id": "DESC" })
|
||||||
.andWhere("note.visibility = 'specified'")
|
.andWhere("note.visibility = 'specified'")
|
||||||
.andWhere(
|
.andWhere(
|
||||||
new Brackets(qb => {
|
new Brackets(qb => {
|
||||||
|
@ -183,7 +183,7 @@ export class TimelineHelpers {
|
||||||
minId
|
minId
|
||||||
)
|
)
|
||||||
.innerJoin(`(${sq.getQuery()})`, "sq", "note.id = sq.latest")
|
.innerJoin(`(${sq.getQuery()})`, "sq", "note.id = sq.latest")
|
||||||
.setParameters({userId: user.id})
|
.setParameters({ userId: user.id })
|
||||||
|
|
||||||
return query.take(limit).getMany().then(p => {
|
return query.take(limit).getMany().then(p => {
|
||||||
if (minId !== undefined) p = p.reverse();
|
if (minId !== undefined) p = p.reverse();
|
||||||
|
|
|
@ -67,8 +67,8 @@ type RelationshipType = 'followers' | 'following';
|
||||||
export class UserHelpers {
|
export class UserHelpers {
|
||||||
public static async followUser(target: User, localUser: ILocalUser, reblogs: boolean, notify: boolean): Promise<MastodonEntity.Relationship> {
|
public static async followUser(target: User, localUser: ILocalUser, reblogs: boolean, notify: boolean): Promise<MastodonEntity.Relationship> {
|
||||||
//FIXME: implement reblogs & notify params
|
//FIXME: implement reblogs & notify params
|
||||||
const following = await Followings.exist({where: {followerId: localUser.id, followeeId: target.id}});
|
const following = await Followings.exist({ where: { followerId: localUser.id, followeeId: target.id } });
|
||||||
const requested = await FollowRequests.exist({where: {followerId: localUser.id, followeeId: target.id}});
|
const requested = await FollowRequests.exist({ where: { followerId: localUser.id, followeeId: target.id } });
|
||||||
if (!following && !requested)
|
if (!following && !requested)
|
||||||
await createFollowing(localUser, target);
|
await createFollowing(localUser, target);
|
||||||
|
|
||||||
|
@ -76,8 +76,8 @@ export class UserHelpers {
|
||||||
}
|
}
|
||||||
|
|
||||||
public static async unfollowUser(target: User, localUser: ILocalUser): Promise<MastodonEntity.Relationship> {
|
public static async unfollowUser(target: User, localUser: ILocalUser): Promise<MastodonEntity.Relationship> {
|
||||||
const following = await Followings.exist({where: {followerId: localUser.id, followeeId: target.id}});
|
const following = await Followings.exist({ where: { followerId: localUser.id, followeeId: target.id } });
|
||||||
const requested = await FollowRequests.exist({where: {followerId: localUser.id, followeeId: target.id}});
|
const requested = await FollowRequests.exist({ where: { followerId: localUser.id, followeeId: target.id } });
|
||||||
if (following)
|
if (following)
|
||||||
await deleteFollowing(localUser, target);
|
await deleteFollowing(localUser, target);
|
||||||
if (requested)
|
if (requested)
|
||||||
|
@ -87,7 +87,7 @@ export class UserHelpers {
|
||||||
}
|
}
|
||||||
|
|
||||||
public static async blockUser(target: User, localUser: ILocalUser): Promise<MastodonEntity.Relationship> {
|
public static async blockUser(target: User, localUser: ILocalUser): Promise<MastodonEntity.Relationship> {
|
||||||
const blocked = await Blockings.exist({where: {blockerId: localUser.id, blockeeId: target.id}});
|
const blocked = await Blockings.exist({ where: { blockerId: localUser.id, blockeeId: target.id } });
|
||||||
if (!blocked)
|
if (!blocked)
|
||||||
await createBlocking(localUser, target);
|
await createBlocking(localUser, target);
|
||||||
|
|
||||||
|
@ -95,7 +95,7 @@ export class UserHelpers {
|
||||||
}
|
}
|
||||||
|
|
||||||
public static async unblockUser(target: User, localUser: ILocalUser): Promise<MastodonEntity.Relationship> {
|
public static async unblockUser(target: User, localUser: ILocalUser): Promise<MastodonEntity.Relationship> {
|
||||||
const blocked = await Blockings.exist({where: {blockerId: localUser.id, blockeeId: target.id}});
|
const blocked = await Blockings.exist({ where: { blockerId: localUser.id, blockeeId: target.id } });
|
||||||
if (blocked)
|
if (blocked)
|
||||||
await deleteBlocking(localUser, target);
|
await deleteBlocking(localUser, target);
|
||||||
|
|
||||||
|
@ -104,7 +104,7 @@ export class UserHelpers {
|
||||||
|
|
||||||
public static async muteUser(target: User, localUser: ILocalUser, notifications: boolean = true, duration: number = 0): Promise<MastodonEntity.Relationship> {
|
public static async muteUser(target: User, localUser: ILocalUser, notifications: boolean = true, duration: number = 0): Promise<MastodonEntity.Relationship> {
|
||||||
//FIXME: respect notifications parameter
|
//FIXME: respect notifications parameter
|
||||||
const muted = await Mutings.exist({where: {muterId: localUser.id, muteeId: target.id}});
|
const muted = await Mutings.exist({ where: { muterId: localUser.id, muteeId: target.id } });
|
||||||
if (!muted) {
|
if (!muted) {
|
||||||
await Mutings.insert({
|
await Mutings.insert({
|
||||||
id: genId(),
|
id: genId(),
|
||||||
|
@ -126,7 +126,7 @@ export class UserHelpers {
|
||||||
}
|
}
|
||||||
|
|
||||||
public static async unmuteUser(target: User, localUser: ILocalUser): Promise<MastodonEntity.Relationship> {
|
public static async unmuteUser(target: User, localUser: ILocalUser): Promise<MastodonEntity.Relationship> {
|
||||||
const muting = await Mutings.findOneBy({muterId: localUser.id, muteeId: target.id});
|
const muting = await Mutings.findOneBy({ muterId: localUser.id, muteeId: target.id });
|
||||||
if (muting) {
|
if (muting) {
|
||||||
await Mutings.delete({
|
await Mutings.delete({
|
||||||
id: muting.id,
|
id: muting.id,
|
||||||
|
@ -139,14 +139,14 @@ export class UserHelpers {
|
||||||
}
|
}
|
||||||
|
|
||||||
public static async acceptFollowRequest(target: User, localUser: ILocalUser): Promise<MastodonEntity.Relationship> {
|
public static async acceptFollowRequest(target: User, localUser: ILocalUser): Promise<MastodonEntity.Relationship> {
|
||||||
const pending = await FollowRequests.exist({where: {followerId: target.id, followeeId: localUser.id}});
|
const pending = await FollowRequests.exist({ where: { followerId: target.id, followeeId: localUser.id } });
|
||||||
if (pending)
|
if (pending)
|
||||||
await acceptFollowRequest(localUser, target);
|
await acceptFollowRequest(localUser, target);
|
||||||
return this.getUserRelationshipTo(target.id, localUser.id);
|
return this.getUserRelationshipTo(target.id, localUser.id);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static async rejectFollowRequest(target: User, localUser: ILocalUser): Promise<MastodonEntity.Relationship> {
|
public static async rejectFollowRequest(target: User, localUser: ILocalUser): Promise<MastodonEntity.Relationship> {
|
||||||
const pending = await FollowRequests.exist({where: {followerId: target.id, followeeId: localUser.id}});
|
const pending = await FollowRequests.exist({ where: { followerId: target.id, followeeId: localUser.id } });
|
||||||
if (pending)
|
if (pending)
|
||||||
await rejectFollowRequest(localUser, target);
|
await rejectFollowRequest(localUser, target);
|
||||||
return this.getUserRelationshipTo(target.id, localUser.id);
|
return this.getUserRelationshipTo(target.id, localUser.id);
|
||||||
|
@ -193,7 +193,7 @@ export class UserHelpers {
|
||||||
|
|
||||||
public static async verifyCredentials(user: ILocalUser): Promise<MastodonEntity.Account> {
|
public static async verifyCredentials(user: ILocalUser): Promise<MastodonEntity.Account> {
|
||||||
const acct = UserConverter.encode(user);
|
const acct = UserConverter.encode(user);
|
||||||
const profile = UserProfiles.findOneByOrFail({userId: user.id});
|
const profile = UserProfiles.findOneByOrFail({ userId: user.id });
|
||||||
const privacy = this.getDefaultNoteVisibility(user);
|
const privacy = this.getDefaultNoteVisibility(user);
|
||||||
const fields = profile.then(profile => profile.fields.map(field => {
|
const fields = profile.then(profile => profile.fields.map(field => {
|
||||||
return {
|
return {
|
||||||
|
@ -222,7 +222,7 @@ export class UserHelpers {
|
||||||
public static async getUserFromAcct(acct: string): Promise<User> {
|
public static async getUserFromAcct(acct: string): Promise<User> {
|
||||||
const split = acct.toLowerCase().split('@');
|
const split = acct.toLowerCase().split('@');
|
||||||
if (split.length > 2) throw new Error('Invalid acct');
|
if (split.length > 2) throw new Error('Invalid acct');
|
||||||
return Users.findOneBy({usernameLower: split[0], host: split[1] ?? IsNull()})
|
return Users.findOneBy({ usernameLower: split[0], host: split[1] ?? IsNull() })
|
||||||
.then(p => {
|
.then(p => {
|
||||||
if (p) return p;
|
if (p) return p;
|
||||||
throw new MastoApiError(404);
|
throw new MastoApiError(404);
|
||||||
|
@ -239,7 +239,7 @@ export class UserHelpers {
|
||||||
minId
|
minId
|
||||||
);
|
);
|
||||||
|
|
||||||
query.andWhere("muting.muterId = :userId", {userId: user.id})
|
query.andWhere("muting.muterId = :userId", { userId: user.id })
|
||||||
.innerJoinAndSelect("muting.mutee", "mutee");
|
.innerJoinAndSelect("muting.mutee", "mutee");
|
||||||
|
|
||||||
return query.take(limit).getMany().then(async p => {
|
return query.take(limit).getMany().then(async p => {
|
||||||
|
@ -275,7 +275,7 @@ export class UserHelpers {
|
||||||
minId
|
minId
|
||||||
);
|
);
|
||||||
|
|
||||||
query.andWhere("blocking.blockerId = :userId", {userId: user.id})
|
query.andWhere("blocking.blockerId = :userId", { userId: user.id })
|
||||||
.innerJoinAndSelect("blocking.blockee", "blockee");
|
.innerJoinAndSelect("blocking.blockee", "blockee");
|
||||||
|
|
||||||
return query.take(limit).getMany().then(p => {
|
return query.take(limit).getMany().then(p => {
|
||||||
|
@ -302,7 +302,7 @@ export class UserHelpers {
|
||||||
minId
|
minId
|
||||||
);
|
);
|
||||||
|
|
||||||
query.andWhere("request.followeeId = :userId", {userId: user.id})
|
query.andWhere("request.followeeId = :userId", { userId: user.id })
|
||||||
.innerJoinAndSelect("request.follower", "follower");
|
.innerJoinAndSelect("request.follower", "follower");
|
||||||
|
|
||||||
return query.take(limit).getMany().then(p => {
|
return query.take(limit).getMany().then(p => {
|
||||||
|
@ -356,7 +356,7 @@ export class UserHelpers {
|
||||||
new Brackets(qb => {
|
new Brackets(qb => {
|
||||||
qb.where("note.replyId IS NULL")
|
qb.where("note.replyId IS NULL")
|
||||||
.orWhere(new Brackets(qb => {
|
.orWhere(new Brackets(qb => {
|
||||||
qb.where('note.mentions = :mentions', {mentions: []})
|
qb.where('note.mentions = :mentions', { mentions: [] })
|
||||||
.andWhere('thread.userId = :userId')
|
.andWhere('thread.userId = :userId')
|
||||||
}));
|
}));
|
||||||
}));
|
}));
|
||||||
|
@ -375,7 +375,7 @@ export class UserHelpers {
|
||||||
query.andWhere("note.visibility != 'hidden'");
|
query.andWhere("note.visibility != 'hidden'");
|
||||||
query.andWhere("note.visibility != 'specified'");
|
query.andWhere("note.visibility != 'specified'");
|
||||||
|
|
||||||
query.setParameters({userId: user.id});
|
query.setParameters({ userId: user.id });
|
||||||
|
|
||||||
return PaginationHelpers.execQuery(query, limit, minId !== undefined);
|
return PaginationHelpers.execQuery(query, limit, minId !== undefined);
|
||||||
}
|
}
|
||||||
|
@ -389,7 +389,7 @@ export class UserHelpers {
|
||||||
maxId,
|
maxId,
|
||||||
minId
|
minId
|
||||||
)
|
)
|
||||||
.andWhere("favorite.userId = :meId", {meId: localUser.id})
|
.andWhere("favorite.userId = :meId", { meId: localUser.id })
|
||||||
.leftJoinAndSelect("favorite.note", "note");
|
.leftJoinAndSelect("favorite.note", "note");
|
||||||
|
|
||||||
generateVisibilityQuery(query, localUser);
|
generateVisibilityQuery(query, localUser);
|
||||||
|
@ -413,7 +413,7 @@ export class UserHelpers {
|
||||||
maxId,
|
maxId,
|
||||||
minId
|
minId
|
||||||
)
|
)
|
||||||
.andWhere("reaction.userId = :meId", {meId: localUser.id})
|
.andWhere("reaction.userId = :meId", { meId: localUser.id })
|
||||||
.leftJoinAndSelect("reaction.note", "note");
|
.leftJoinAndSelect("reaction.note", "note");
|
||||||
|
|
||||||
generateVisibilityQuery(query, localUser);
|
generateVisibilityQuery(query, localUser);
|
||||||
|
@ -431,11 +431,11 @@ export class UserHelpers {
|
||||||
private static async getUserRelationships(type: RelationshipType, user: User, localUser: ILocalUser | null, maxId: string | undefined, sinceId: string | undefined, minId: string | undefined, limit: number = 40): Promise<LinkPaginationObject<User[]>> {
|
private static async getUserRelationships(type: RelationshipType, user: User, localUser: ILocalUser | null, maxId: string | undefined, sinceId: string | undefined, minId: string | undefined, limit: number = 40): Promise<LinkPaginationObject<User[]>> {
|
||||||
if (limit > 80) limit = 80;
|
if (limit > 80) limit = 80;
|
||||||
|
|
||||||
const profile = await UserProfiles.findOneByOrFail({userId: user.id});
|
const profile = await UserProfiles.findOneByOrFail({ userId: user.id });
|
||||||
if (profile.ffVisibility === "private") {
|
if (profile.ffVisibility === "private") {
|
||||||
if (!localUser || user.id !== localUser.id) return {data: []};
|
if (!localUser || user.id !== localUser.id) return { data: [] };
|
||||||
} else if (profile.ffVisibility === "followers") {
|
} else if (profile.ffVisibility === "followers") {
|
||||||
if (!localUser) return {data: []};
|
if (!localUser) return { data: [] };
|
||||||
if (user.id !== localUser.id) {
|
if (user.id !== localUser.id) {
|
||||||
const isFollowed = await Followings.exist({
|
const isFollowed = await Followings.exist({
|
||||||
where: {
|
where: {
|
||||||
|
@ -443,7 +443,7 @@ export class UserHelpers {
|
||||||
followerId: localUser.id,
|
followerId: localUser.id,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
if (!isFollowed) return {data: []};
|
if (!isFollowed) return { data: [] };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -455,10 +455,10 @@ export class UserHelpers {
|
||||||
);
|
);
|
||||||
|
|
||||||
if (type === "followers") {
|
if (type === "followers") {
|
||||||
query.andWhere("following.followeeId = :userId", {userId: user.id})
|
query.andWhere("following.followeeId = :userId", { userId: user.id })
|
||||||
.innerJoinAndSelect("following.follower", "follower");
|
.innerJoinAndSelect("following.follower", "follower");
|
||||||
} else {
|
} else {
|
||||||
query.andWhere("following.followerId = :userId", {userId: user.id})
|
query.andWhere("following.followerId = :userId", { userId: user.id })
|
||||||
.innerJoinAndSelect("following.followee", "followee");
|
.innerJoinAndSelect("following.followee", "followee");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -538,6 +538,11 @@ export class UserHelpers {
|
||||||
}
|
}
|
||||||
|
|
||||||
public static async getDefaultNoteVisibility(user: ILocalUser): Promise<IceshrimpVisibility> {
|
public static async getDefaultNoteVisibility(user: ILocalUser): Promise<IceshrimpVisibility> {
|
||||||
return RegistryItems.findOneBy({domain: IsNull(), userId: user.id, key: 'defaultNoteVisibility', scope: '{client,base}'}).then(p => p?.value ?? 'public')
|
return RegistryItems.findOneBy({
|
||||||
|
domain: IsNull(),
|
||||||
|
userId: user.id,
|
||||||
|
key: 'defaultNoteVisibility',
|
||||||
|
scope: '{client,base}'
|
||||||
|
}).then(p => p?.value ?? 'public')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -22,9 +22,8 @@ export function auth(required: boolean, scopes: string[] = []) {
|
||||||
if (!AuthConverter.decode(scopes).every(p => ctx.scopes.includes(p))) {
|
if (!AuthConverter.decode(scopes).every(p => ctx.scopes.includes(p))) {
|
||||||
if (required) {
|
if (required) {
|
||||||
ctx.status = 403;
|
ctx.status = 403;
|
||||||
ctx.body = {error: "This action is outside the authorized scopes"};
|
ctx.body = { error: "This action is outside the authorized scopes" };
|
||||||
}
|
} else {
|
||||||
else {
|
|
||||||
ctx.user = null;
|
ctx.user = null;
|
||||||
ctx.scopes = [];
|
ctx.scopes = [];
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,9 +1,10 @@
|
||||||
import { MastoContext, logger } from "@/server/api/mastodon/index.js";
|
import { logger, MastoContext } from "@/server/api/mastodon/index.js";
|
||||||
import { IdentifiableError } from "@/misc/identifiable-error.js";
|
import { IdentifiableError } from "@/misc/identifiable-error.js";
|
||||||
import { ApiError } from "@/server/api/error.js";
|
import { ApiError } from "@/server/api/error.js";
|
||||||
|
|
||||||
export class MastoApiError extends Error {
|
export class MastoApiError extends Error {
|
||||||
statusCode: number;
|
statusCode: number;
|
||||||
|
|
||||||
constructor(statusCode: number, message?: string) {
|
constructor(statusCode: number, message?: string) {
|
||||||
if (message == null) {
|
if (message == null) {
|
||||||
switch (statusCode) {
|
switch (statusCode) {
|
||||||
|
@ -26,20 +27,16 @@ export async function CatchErrorsMiddleware(ctx: MastoContext, next: () => Promi
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
if (e instanceof MastoApiError) {
|
if (e instanceof MastoApiError) {
|
||||||
ctx.status = e.statusCode;
|
ctx.status = e.statusCode;
|
||||||
}
|
} else if (e instanceof IdentifiableError) {
|
||||||
else if (e instanceof IdentifiableError) {
|
|
||||||
ctx.status = 400;
|
ctx.status = 400;
|
||||||
}
|
} else if (e instanceof ApiError) {
|
||||||
else if (e instanceof ApiError) {
|
|
||||||
ctx.status = e.httpStatusCode ?? 500;
|
ctx.status = e.httpStatusCode ?? 500;
|
||||||
}
|
} else {
|
||||||
else {
|
|
||||||
logger.error(`Error occured in ${ctx.method} ${ctx.path}:`);
|
logger.error(`Error occured in ${ctx.method} ${ctx.path}:`);
|
||||||
if (e instanceof Error) {
|
if (e instanceof Error) {
|
||||||
if (e.stack) logger.error(e.stack);
|
if (e.stack) logger.error(e.stack);
|
||||||
else logger.error(`${e.name}: ${e.message}`);
|
else logger.error(`${e.name}: ${e.message}`);
|
||||||
}
|
} else {
|
||||||
else {
|
|
||||||
logger.error(e);
|
logger.error(e);
|
||||||
}
|
}
|
||||||
ctx.status = 500;
|
ctx.status = 500;
|
||||||
|
|
|
@ -5,7 +5,7 @@ export async function NormalizeQueryMiddleware(ctx: MastoContext, next: () => Pr
|
||||||
if (!ctx.request.body || Object.keys(ctx.request.body).length === 0) {
|
if (!ctx.request.body || Object.keys(ctx.request.body).length === 0) {
|
||||||
ctx.request.body = ctx.request.query;
|
ctx.request.body = ctx.request.query;
|
||||||
} else {
|
} else {
|
||||||
ctx.request.body = {...ctx.request.body, ...ctx.request.query};
|
ctx.request.body = { ...ctx.request.body, ...ctx.request.query };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
await next();
|
await next();
|
||||||
|
|
Loading…
Reference in a new issue