mirror of
https://github.com/versia-pub/server.git
synced 2025-12-06 08:28:19 +01:00
perf(database): ⚡ Improve performance when fetching timelines by fetching all data in a single SQL query
This commit is contained in:
parent
26dfd14aaf
commit
e48f57a3d8
|
|
@ -21,6 +21,7 @@ export type NotificationWithRelations = Notification & {
|
||||||
|
|
||||||
export const findManyNotifications = async (
|
export const findManyNotifications = async (
|
||||||
query: Parameters<typeof db.query.Notifications.findMany>[0],
|
query: Parameters<typeof db.query.Notifications.findMany>[0],
|
||||||
|
userId?: string,
|
||||||
): Promise<NotificationWithRelations[]> => {
|
): Promise<NotificationWithRelations[]> => {
|
||||||
const output = await db.query.Notifications.findMany({
|
const output = await db.query.Notifications.findMany({
|
||||||
...query,
|
...query,
|
||||||
|
|
@ -42,7 +43,8 @@ export const findManyNotifications = async (
|
||||||
output.map(async (notif) => ({
|
output.map(async (notif) => ({
|
||||||
...notif,
|
...notif,
|
||||||
account: transformOutputToUserWithRelations(notif.account),
|
account: transformOutputToUserWithRelations(notif.account),
|
||||||
status: (await Note.fromId(notif.noteId))?.getStatus() ?? null,
|
status:
|
||||||
|
(await Note.fromId(notif.noteId, userId))?.getStatus() ?? null,
|
||||||
})),
|
})),
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -61,6 +61,10 @@ export type StatusWithRelations = Status & {
|
||||||
reblogCount: number;
|
reblogCount: number;
|
||||||
likeCount: number;
|
likeCount: number;
|
||||||
replyCount: number;
|
replyCount: number;
|
||||||
|
pinned: boolean;
|
||||||
|
reblogged: boolean;
|
||||||
|
muted: boolean;
|
||||||
|
liked: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type StatusWithoutRecursiveRelations = Omit<
|
export type StatusWithoutRecursiveRelations = Omit<
|
||||||
|
|
@ -75,6 +79,7 @@ export type StatusWithoutRecursiveRelations = Omit<
|
||||||
*/
|
*/
|
||||||
export const findManyNotes = async (
|
export const findManyNotes = async (
|
||||||
query: Parameters<typeof db.query.Notes.findMany>[0],
|
query: Parameters<typeof db.query.Notes.findMany>[0],
|
||||||
|
userId?: string,
|
||||||
): Promise<StatusWithRelations[]> => {
|
): Promise<StatusWithRelations[]> => {
|
||||||
const output = await db.query.Notes.findMany({
|
const output = await db.query.Notes.findMany({
|
||||||
...query,
|
...query,
|
||||||
|
|
@ -149,6 +154,26 @@ export const findManyNotes = async (
|
||||||
sql`(SELECT COUNT(*) FROM "Notes" WHERE "Notes"."replyId" = "Notes_reblog".id)`.as(
|
sql`(SELECT COUNT(*) FROM "Notes" WHERE "Notes"."replyId" = "Notes_reblog".id)`.as(
|
||||||
"reply_count",
|
"reply_count",
|
||||||
),
|
),
|
||||||
|
pinned: userId
|
||||||
|
? sql`EXISTS (SELECT 1 FROM "UserToPinnedNotes" WHERE "UserToPinnedNotes"."noteId" = "Notes_reblog".id AND "UserToPinnedNotes"."userId" = ${userId})`.as(
|
||||||
|
"pinned",
|
||||||
|
)
|
||||||
|
: sql`false`.as("pinned"),
|
||||||
|
reblogged: userId
|
||||||
|
? sql`EXISTS (SELECT 1 FROM "Notes" WHERE "Notes"."authorId" = ${userId} AND "Notes"."reblogId" = "Notes_reblog".id)`.as(
|
||||||
|
"reblogged",
|
||||||
|
)
|
||||||
|
: sql`false`.as("reblogged"),
|
||||||
|
muted: userId
|
||||||
|
? sql`EXISTS (SELECT 1 FROM "Relationships" WHERE "Relationships"."ownerId" = ${userId} AND "Relationships"."subjectId" = "Notes_reblog"."authorId" AND "Relationships"."muting" = true)`.as(
|
||||||
|
"muted",
|
||||||
|
)
|
||||||
|
: sql`false`.as("muted"),
|
||||||
|
liked: userId
|
||||||
|
? sql`EXISTS (SELECT 1 FROM "Likes" WHERE "Likes"."likedId" = "Notes_reblog".id AND "Likes"."likerId" = ${userId})`.as(
|
||||||
|
"liked",
|
||||||
|
)
|
||||||
|
: sql`false`.as("liked"),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
reply: true,
|
reply: true,
|
||||||
|
|
@ -167,6 +192,26 @@ export const findManyNotes = async (
|
||||||
sql`(SELECT COUNT(*) FROM "Notes" WHERE "Notes"."replyId" = "Notes".id)`.as(
|
sql`(SELECT COUNT(*) FROM "Notes" WHERE "Notes"."replyId" = "Notes".id)`.as(
|
||||||
"reply_count",
|
"reply_count",
|
||||||
),
|
),
|
||||||
|
pinned: userId
|
||||||
|
? sql`EXISTS (SELECT 1 FROM "UserToPinnedNotes" WHERE "UserToPinnedNotes"."noteId" = "Notes".id AND "UserToPinnedNotes"."userId" = ${userId})`.as(
|
||||||
|
"pinned",
|
||||||
|
)
|
||||||
|
: sql`false`.as("pinned"),
|
||||||
|
reblogged: userId
|
||||||
|
? sql`EXISTS (SELECT 1 FROM "Notes" WHERE "Notes"."authorId" = ${userId} AND "Notes"."reblogId" = "Notes".id)`.as(
|
||||||
|
"reblogged",
|
||||||
|
)
|
||||||
|
: sql`false`.as("reblogged"),
|
||||||
|
muted: userId
|
||||||
|
? sql`EXISTS (SELECT 1 FROM "Relationships" WHERE "Relationships"."ownerId" = ${userId} AND "Relationships"."subjectId" = "Notes"."authorId" AND "Relationships"."muting" = true)`.as(
|
||||||
|
"muted",
|
||||||
|
)
|
||||||
|
: sql`false`.as("muted"),
|
||||||
|
liked: userId
|
||||||
|
? sql`EXISTS (SELECT 1 FROM "Likes" WHERE "Likes"."likedId" = "Notes".id AND "Likes"."likerId" = ${userId})`.as(
|
||||||
|
"liked",
|
||||||
|
)
|
||||||
|
: sql`false`.as("liked"),
|
||||||
...query?.extras,
|
...query?.extras,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
@ -190,10 +235,18 @@ export const findManyNotes = async (
|
||||||
reblogCount: Number(post.reblog.reblogCount),
|
reblogCount: Number(post.reblog.reblogCount),
|
||||||
likeCount: Number(post.reblog.likeCount),
|
likeCount: Number(post.reblog.likeCount),
|
||||||
replyCount: Number(post.reblog.replyCount),
|
replyCount: Number(post.reblog.replyCount),
|
||||||
|
pinned: Boolean(post.reblog.pinned),
|
||||||
|
reblogged: Boolean(post.reblog.reblogged),
|
||||||
|
muted: Boolean(post.reblog.muted),
|
||||||
|
liked: Boolean(post.reblog.liked),
|
||||||
},
|
},
|
||||||
reblogCount: Number(post.reblogCount),
|
reblogCount: Number(post.reblogCount),
|
||||||
likeCount: Number(post.likeCount),
|
likeCount: Number(post.likeCount),
|
||||||
replyCount: Number(post.replyCount),
|
replyCount: Number(post.replyCount),
|
||||||
|
pinned: Boolean(post.pinned),
|
||||||
|
reblogged: Boolean(post.reblogged),
|
||||||
|
muted: Boolean(post.muted),
|
||||||
|
liked: Boolean(post.liked),
|
||||||
}));
|
}));
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -54,25 +54,38 @@ import { User } from "./user";
|
||||||
export class Note {
|
export class Note {
|
||||||
private constructor(private status: StatusWithRelations) {}
|
private constructor(private status: StatusWithRelations) {}
|
||||||
|
|
||||||
static async fromId(id: string | null): Promise<Note | null> {
|
static async fromId(
|
||||||
|
id: string | null,
|
||||||
|
userId?: string,
|
||||||
|
): Promise<Note | null> {
|
||||||
if (!id) return null;
|
if (!id) return null;
|
||||||
|
|
||||||
return await Note.fromSql(eq(Notes.id, id));
|
return await Note.fromSql(eq(Notes.id, id), undefined, userId);
|
||||||
}
|
}
|
||||||
|
|
||||||
static async fromIds(ids: string[]): Promise<Note[]> {
|
static async fromIds(ids: string[], userId?: string): Promise<Note[]> {
|
||||||
return await Note.manyFromSql(inArray(Notes.id, ids));
|
return await Note.manyFromSql(
|
||||||
|
inArray(Notes.id, ids),
|
||||||
|
undefined,
|
||||||
|
undefined,
|
||||||
|
undefined,
|
||||||
|
userId,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
static async fromSql(
|
static async fromSql(
|
||||||
sql: SQL<unknown> | undefined,
|
sql: SQL<unknown> | undefined,
|
||||||
orderBy: SQL<unknown> | undefined = desc(Notes.id),
|
orderBy: SQL<unknown> | undefined = desc(Notes.id),
|
||||||
|
userId?: string,
|
||||||
) {
|
) {
|
||||||
const found = await findManyNotes({
|
const found = await findManyNotes(
|
||||||
where: sql,
|
{
|
||||||
orderBy,
|
where: sql,
|
||||||
limit: 1,
|
orderBy,
|
||||||
});
|
limit: 1,
|
||||||
|
},
|
||||||
|
userId,
|
||||||
|
);
|
||||||
|
|
||||||
if (!found[0]) return null;
|
if (!found[0]) return null;
|
||||||
return new Note(found[0]);
|
return new Note(found[0]);
|
||||||
|
|
@ -83,13 +96,17 @@ export class Note {
|
||||||
orderBy: SQL<unknown> | undefined = desc(Notes.id),
|
orderBy: SQL<unknown> | undefined = desc(Notes.id),
|
||||||
limit?: number,
|
limit?: number,
|
||||||
offset?: number,
|
offset?: number,
|
||||||
|
userId?: string,
|
||||||
) {
|
) {
|
||||||
const found = await findManyNotes({
|
const found = await findManyNotes(
|
||||||
where: sql,
|
{
|
||||||
orderBy,
|
where: sql,
|
||||||
limit,
|
orderBy,
|
||||||
offset,
|
limit,
|
||||||
});
|
offset,
|
||||||
|
},
|
||||||
|
userId,
|
||||||
|
);
|
||||||
|
|
||||||
return found.map((s) => new Note(s));
|
return found.map((s) => new Note(s));
|
||||||
}
|
}
|
||||||
|
|
@ -176,8 +193,14 @@ export class Note {
|
||||||
)[0].count;
|
)[0].count;
|
||||||
}
|
}
|
||||||
|
|
||||||
async getReplyChildren() {
|
async getReplyChildren(userId?: string) {
|
||||||
return await Note.manyFromSql(eq(Notes.replyId, this.status.id));
|
return await Note.manyFromSql(
|
||||||
|
eq(Notes.replyId, this.status.id),
|
||||||
|
undefined,
|
||||||
|
undefined,
|
||||||
|
undefined,
|
||||||
|
userId,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
static async insert(values: InferInsertModel<typeof Notes>) {
|
static async insert(values: InferInsertModel<typeof Notes>) {
|
||||||
|
|
@ -275,7 +298,7 @@ export class Note {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return await Note.fromId(newNote.id);
|
return await Note.fromId(newNote.id, newNote.authorId);
|
||||||
}
|
}
|
||||||
|
|
||||||
async updateFromData(
|
async updateFromData(
|
||||||
|
|
@ -358,7 +381,7 @@ export class Note {
|
||||||
.where(inArray(Attachments.id, media_attachments));
|
.where(inArray(Attachments.id, media_attachments));
|
||||||
}
|
}
|
||||||
|
|
||||||
return await Note.fromId(newNote.id);
|
return await Note.fromId(newNote.id, newNote.authorId);
|
||||||
}
|
}
|
||||||
|
|
||||||
async delete() {
|
async delete() {
|
||||||
|
|
@ -414,47 +437,6 @@ export class Note {
|
||||||
async toAPI(userFetching?: User | null): Promise<APIStatus> {
|
async toAPI(userFetching?: User | null): Promise<APIStatus> {
|
||||||
const data = this.getStatus();
|
const data = this.getStatus();
|
||||||
|
|
||||||
const [pinnedByUser, rebloggedByUser, mutedByUser, likedByUser] = (
|
|
||||||
await Promise.all([
|
|
||||||
userFetching
|
|
||||||
? db.query.UserToPinnedNotes.findFirst({
|
|
||||||
where: (relation, { and, eq }) =>
|
|
||||||
and(
|
|
||||||
eq(relation.noteId, data.id),
|
|
||||||
eq(relation.userId, userFetching?.id),
|
|
||||||
),
|
|
||||||
})
|
|
||||||
: false,
|
|
||||||
userFetching
|
|
||||||
? Note.fromSql(
|
|
||||||
and(
|
|
||||||
eq(Notes.authorId, userFetching?.id),
|
|
||||||
eq(Notes.reblogId, data.id),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
: false,
|
|
||||||
userFetching
|
|
||||||
? db.query.Relationships.findFirst({
|
|
||||||
where: (relationship, { and, eq }) =>
|
|
||||||
and(
|
|
||||||
eq(relationship.ownerId, userFetching.id),
|
|
||||||
eq(relationship.subjectId, data.authorId),
|
|
||||||
eq(relationship.muting, true),
|
|
||||||
),
|
|
||||||
})
|
|
||||||
: false,
|
|
||||||
userFetching
|
|
||||||
? db.query.Likes.findFirst({
|
|
||||||
where: (like, { and, eq }) =>
|
|
||||||
and(
|
|
||||||
eq(like.likedId, data.id),
|
|
||||||
eq(like.likerId, userFetching.id),
|
|
||||||
),
|
|
||||||
})
|
|
||||||
: false,
|
|
||||||
])
|
|
||||||
).map((r) => !!r);
|
|
||||||
|
|
||||||
// Convert mentions of local users from @username@host to @username
|
// Convert mentions of local users from @username@host to @username
|
||||||
const mentionedLocalUsers = data.mentions.filter(
|
const mentionedLocalUsers = data.mentions.filter(
|
||||||
(mention) => mention.instanceId === null,
|
(mention) => mention.instanceId === null,
|
||||||
|
|
@ -488,7 +470,7 @@ export class Note {
|
||||||
card: null,
|
card: null,
|
||||||
content: replacedContent,
|
content: replacedContent,
|
||||||
emojis: data.emojis.map((emoji) => emojiToAPI(emoji)),
|
emojis: data.emojis.map((emoji) => emojiToAPI(emoji)),
|
||||||
favourited: likedByUser,
|
favourited: data.liked,
|
||||||
favourites_count: data.likeCount,
|
favourites_count: data.likeCount,
|
||||||
media_attachments: (data.attachments ?? []).map(
|
media_attachments: (data.attachments ?? []).map(
|
||||||
(a) => attachmentToAPI(a) as APIAttachment,
|
(a) => attachmentToAPI(a) as APIAttachment,
|
||||||
|
|
@ -504,8 +486,8 @@ export class Note {
|
||||||
username: mention.username,
|
username: mention.username,
|
||||||
})),
|
})),
|
||||||
language: null,
|
language: null,
|
||||||
muted: mutedByUser,
|
muted: data.muted,
|
||||||
pinned: pinnedByUser,
|
pinned: data.pinned,
|
||||||
// TODO: Add polls
|
// TODO: Add polls
|
||||||
poll: null,
|
poll: null,
|
||||||
reblog: data.reblog
|
reblog: data.reblog
|
||||||
|
|
@ -513,7 +495,7 @@ export class Note {
|
||||||
data.reblog as StatusWithRelations,
|
data.reblog as StatusWithRelations,
|
||||||
).toAPI(userFetching)
|
).toAPI(userFetching)
|
||||||
: null,
|
: null,
|
||||||
reblogged: rebloggedByUser,
|
reblogged: data.reblogged,
|
||||||
reblogs_count: data.reblogCount,
|
reblogs_count: data.reblogCount,
|
||||||
replies_count: data.replyCount,
|
replies_count: data.replyCount,
|
||||||
sensitive: data.sensitive,
|
sensitive: data.sensitive,
|
||||||
|
|
@ -525,8 +507,8 @@ export class Note {
|
||||||
bookmarked: false,
|
bookmarked: false,
|
||||||
// @ts-expect-error Glitch-SOC extension
|
// @ts-expect-error Glitch-SOC extension
|
||||||
quote: data.quotingId
|
quote: data.quotingId
|
||||||
? (await Note.fromId(data.quotingId).then((n) =>
|
? (await Note.fromId(data.quotingId, userFetching?.id).then(
|
||||||
n?.toAPI(userFetching),
|
(n) => n?.toAPI(userFetching),
|
||||||
)) ?? null
|
)) ?? null
|
||||||
: null,
|
: null,
|
||||||
quote_id: data.quotingId || undefined,
|
quote_id: data.quotingId || undefined,
|
||||||
|
|
@ -589,7 +571,10 @@ export class Note {
|
||||||
let currentStatus: Note = this;
|
let currentStatus: Note = this;
|
||||||
|
|
||||||
while (currentStatus.getStatus().replyId) {
|
while (currentStatus.getStatus().replyId) {
|
||||||
const parent = await Note.fromId(currentStatus.getStatus().replyId);
|
const parent = await Note.fromId(
|
||||||
|
currentStatus.getStatus().replyId,
|
||||||
|
fetcher?.id,
|
||||||
|
);
|
||||||
|
|
||||||
if (!parent) {
|
if (!parent) {
|
||||||
break;
|
break;
|
||||||
|
|
@ -612,7 +597,7 @@ export class Note {
|
||||||
*/
|
*/
|
||||||
async getDescendants(fetcher: User | null, depth = 0) {
|
async getDescendants(fetcher: User | null, depth = 0) {
|
||||||
const descendants: Note[] = [];
|
const descendants: Note[] = [];
|
||||||
for (const child of await this.getReplyChildren()) {
|
for (const child of await this.getReplyChildren(fetcher?.id)) {
|
||||||
descendants.push(child);
|
descendants.push(child);
|
||||||
|
|
||||||
if (depth < 20) {
|
if (depth < 20) {
|
||||||
|
|
|
||||||
|
|
@ -16,11 +16,13 @@ export class Timeline {
|
||||||
sql: SQL<unknown> | undefined,
|
sql: SQL<unknown> | undefined,
|
||||||
limit: number,
|
limit: number,
|
||||||
url: string,
|
url: string,
|
||||||
|
userId?: string,
|
||||||
) {
|
) {
|
||||||
return new Timeline(TimelineType.NOTE).fetchTimeline<Note>(
|
return new Timeline(TimelineType.NOTE).fetchTimeline<Note>(
|
||||||
sql,
|
sql,
|
||||||
limit,
|
limit,
|
||||||
url,
|
url,
|
||||||
|
userId,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -40,13 +42,22 @@ export class Timeline {
|
||||||
sql: SQL<unknown> | undefined,
|
sql: SQL<unknown> | undefined,
|
||||||
limit: number,
|
limit: number,
|
||||||
url: string,
|
url: string,
|
||||||
|
userId?: string,
|
||||||
) {
|
) {
|
||||||
const notes: Note[] = [];
|
const notes: Note[] = [];
|
||||||
const users: User[] = [];
|
const users: User[] = [];
|
||||||
|
|
||||||
switch (this.type) {
|
switch (this.type) {
|
||||||
case TimelineType.NOTE:
|
case TimelineType.NOTE:
|
||||||
notes.push(...(await Note.manyFromSql(sql, undefined, limit)));
|
notes.push(
|
||||||
|
...(await Note.manyFromSql(
|
||||||
|
sql,
|
||||||
|
undefined,
|
||||||
|
limit,
|
||||||
|
undefined,
|
||||||
|
userId,
|
||||||
|
)),
|
||||||
|
);
|
||||||
break;
|
break;
|
||||||
case TimelineType.USER:
|
case TimelineType.USER:
|
||||||
users.push(...(await User.manyFromSql(sql, undefined, limit)));
|
users.push(...(await User.manyFromSql(sql, undefined, limit)));
|
||||||
|
|
|
||||||
|
|
@ -62,6 +62,7 @@ export default (app: Hono) =>
|
||||||
auth(meta.auth),
|
auth(meta.auth),
|
||||||
async (context) => {
|
async (context) => {
|
||||||
const { id } = context.req.valid("param");
|
const { id } = context.req.valid("param");
|
||||||
|
const { user } = context.req.valid("header");
|
||||||
|
|
||||||
const otherUser = await User.fromId(id);
|
const otherUser = await User.fromId(id);
|
||||||
|
|
||||||
|
|
@ -95,6 +96,7 @@ export default (app: Hono) =>
|
||||||
),
|
),
|
||||||
limit,
|
limit,
|
||||||
context.req.url,
|
context.req.url,
|
||||||
|
user?.id,
|
||||||
);
|
);
|
||||||
|
|
||||||
return jsonResponse(
|
return jsonResponse(
|
||||||
|
|
|
||||||
|
|
@ -52,6 +52,7 @@ export default (app: Hono) =>
|
||||||
),
|
),
|
||||||
limit,
|
limit,
|
||||||
context.req.url,
|
context.req.url,
|
||||||
|
user?.id,
|
||||||
);
|
);
|
||||||
|
|
||||||
return jsonResponse(
|
return jsonResponse(
|
||||||
|
|
|
||||||
|
|
@ -37,10 +37,14 @@ export default (app: Hono) =>
|
||||||
if (!user) return errorResponse("Unauthorized", 401);
|
if (!user) return errorResponse("Unauthorized", 401);
|
||||||
|
|
||||||
const notification = (
|
const notification = (
|
||||||
await findManyNotifications({
|
await findManyNotifications(
|
||||||
where: (notification, { eq }) => eq(notification.id, id),
|
{
|
||||||
limit: 1,
|
where: (notification, { eq }) =>
|
||||||
})
|
eq(notification.id, id),
|
||||||
|
limit: 1,
|
||||||
|
},
|
||||||
|
user.id,
|
||||||
|
)
|
||||||
)[0];
|
)[0];
|
||||||
|
|
||||||
if (!notification)
|
if (!notification)
|
||||||
|
|
|
||||||
|
|
@ -174,6 +174,7 @@ export default (app: Hono) =>
|
||||||
desc(notification.id),
|
desc(notification.id),
|
||||||
},
|
},
|
||||||
context.req.raw,
|
context.req.raw,
|
||||||
|
user.id,
|
||||||
);
|
);
|
||||||
|
|
||||||
return jsonResponse(
|
return jsonResponse(
|
||||||
|
|
|
||||||
|
|
@ -34,7 +34,7 @@ export default (app: Hono) =>
|
||||||
|
|
||||||
const { user } = context.req.valid("header");
|
const { user } = context.req.valid("header");
|
||||||
|
|
||||||
const foundStatus = await Note.fromId(id);
|
const foundStatus = await Note.fromId(id, user?.id);
|
||||||
|
|
||||||
if (!foundStatus) return errorResponse("Record not found", 404);
|
if (!foundStatus) return errorResponse("Record not found", 404);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -39,7 +39,7 @@ export default (app: Hono) =>
|
||||||
|
|
||||||
if (!user) return errorResponse("Unauthorized", 401);
|
if (!user) return errorResponse("Unauthorized", 401);
|
||||||
|
|
||||||
const note = await Note.fromId(id);
|
const note = await Note.fromId(id, user?.id);
|
||||||
|
|
||||||
if (!note?.isViewableByUser(user))
|
if (!note?.isViewableByUser(user))
|
||||||
return errorResponse("Record not found", 404);
|
return errorResponse("Record not found", 404);
|
||||||
|
|
|
||||||
|
|
@ -48,7 +48,7 @@ export default (app: Hono) =>
|
||||||
|
|
||||||
if (!user) return errorResponse("Unauthorized", 401);
|
if (!user) return errorResponse("Unauthorized", 401);
|
||||||
|
|
||||||
const status = await Note.fromId(id);
|
const status = await Note.fromId(id, user?.id);
|
||||||
|
|
||||||
if (!status?.isViewableByUser(user))
|
if (!status?.isViewableByUser(user))
|
||||||
return errorResponse("Record not found", 404);
|
return errorResponse("Record not found", 404);
|
||||||
|
|
|
||||||
|
|
@ -72,7 +72,7 @@ export default (app: Hono) =>
|
||||||
const { id } = context.req.valid("param");
|
const { id } = context.req.valid("param");
|
||||||
const { user } = context.req.valid("header");
|
const { user } = context.req.valid("header");
|
||||||
|
|
||||||
const foundStatus = await Note.fromId(id);
|
const foundStatus = await Note.fromId(id, user?.id);
|
||||||
|
|
||||||
if (!foundStatus?.isViewableByUser(user))
|
if (!foundStatus?.isViewableByUser(user))
|
||||||
return errorResponse("Record not found", 404);
|
return errorResponse("Record not found", 404);
|
||||||
|
|
|
||||||
|
|
@ -36,7 +36,7 @@ export default (app: Hono) =>
|
||||||
|
|
||||||
if (!user) return errorResponse("Unauthorized", 401);
|
if (!user) return errorResponse("Unauthorized", 401);
|
||||||
|
|
||||||
const foundStatus = await Note.fromId(id);
|
const foundStatus = await Note.fromId(id, user?.id);
|
||||||
|
|
||||||
if (!foundStatus) return errorResponse("Record not found", 404);
|
if (!foundStatus) return errorResponse("Record not found", 404);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -43,7 +43,7 @@ export default (app: Hono) =>
|
||||||
|
|
||||||
if (!user) return errorResponse("Unauthorized", 401);
|
if (!user) return errorResponse("Unauthorized", 401);
|
||||||
|
|
||||||
const foundStatus = await Note.fromId(id);
|
const foundStatus = await Note.fromId(id, user.id);
|
||||||
|
|
||||||
if (!foundStatus?.isViewableByUser(user))
|
if (!foundStatus?.isViewableByUser(user))
|
||||||
return errorResponse("Record not found", 404);
|
return errorResponse("Record not found", 404);
|
||||||
|
|
@ -72,7 +72,7 @@ export default (app: Hono) =>
|
||||||
return errorResponse("Failed to reblog", 500);
|
return errorResponse("Failed to reblog", 500);
|
||||||
}
|
}
|
||||||
|
|
||||||
const finalNewReblog = await Note.fromId(newReblog.id);
|
const finalNewReblog = await Note.fromId(newReblog.id, user?.id);
|
||||||
|
|
||||||
if (!finalNewReblog) {
|
if (!finalNewReblog) {
|
||||||
return errorResponse("Failed to reblog", 500);
|
return errorResponse("Failed to reblog", 500);
|
||||||
|
|
|
||||||
|
|
@ -47,7 +47,7 @@ export default (app: Hono) =>
|
||||||
|
|
||||||
if (!user) return errorResponse("Unauthorized", 401);
|
if (!user) return errorResponse("Unauthorized", 401);
|
||||||
|
|
||||||
const status = await Note.fromId(id);
|
const status = await Note.fromId(id, user.id);
|
||||||
|
|
||||||
if (!status?.isViewableByUser(user))
|
if (!status?.isViewableByUser(user))
|
||||||
return errorResponse("Record not found", 404);
|
return errorResponse("Record not found", 404);
|
||||||
|
|
|
||||||
|
|
@ -36,7 +36,7 @@ export default (app: Hono) =>
|
||||||
|
|
||||||
if (!user) return errorResponse("Unauthorized", 401);
|
if (!user) return errorResponse("Unauthorized", 401);
|
||||||
|
|
||||||
const status = await Note.fromId(id);
|
const status = await Note.fromId(id, user.id);
|
||||||
|
|
||||||
if (!status?.isViewableByUser(user))
|
if (!status?.isViewableByUser(user))
|
||||||
return errorResponse("Record not found", 404);
|
return errorResponse("Record not found", 404);
|
||||||
|
|
|
||||||
|
|
@ -37,7 +37,7 @@ export default (app: Hono) =>
|
||||||
|
|
||||||
if (!user) return errorResponse("Unauthorized", 401);
|
if (!user) return errorResponse("Unauthorized", 401);
|
||||||
|
|
||||||
const note = await Note.fromId(id);
|
const note = await Note.fromId(id, user.id);
|
||||||
|
|
||||||
if (!note?.isViewableByUser(user))
|
if (!note?.isViewableByUser(user))
|
||||||
return errorResponse("Record not found", 404);
|
return errorResponse("Record not found", 404);
|
||||||
|
|
|
||||||
|
|
@ -35,7 +35,7 @@ export default (app: Hono) =>
|
||||||
|
|
||||||
if (!user) return errorResponse("Unauthorized", 401);
|
if (!user) return errorResponse("Unauthorized", 401);
|
||||||
|
|
||||||
const status = await Note.fromId(id);
|
const status = await Note.fromId(id, user.id);
|
||||||
|
|
||||||
if (!status) return errorResponse("Record not found", 404);
|
if (!status) return errorResponse("Record not found", 404);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -38,9 +38,7 @@ export default (app: Hono) =>
|
||||||
|
|
||||||
if (!user) return errorResponse("Unauthorized", 401);
|
if (!user) return errorResponse("Unauthorized", 401);
|
||||||
|
|
||||||
if (!user) return errorResponse("Unauthorized", 401);
|
const foundStatus = await Note.fromId(id, user.id);
|
||||||
|
|
||||||
const foundStatus = await Note.fromId(id);
|
|
||||||
|
|
||||||
// Check if user is authorized to view this status (if it's private)
|
// Check if user is authorized to view this status (if it's private)
|
||||||
if (!foundStatus?.isViewableByUser(user))
|
if (!foundStatus?.isViewableByUser(user))
|
||||||
|
|
@ -51,6 +49,8 @@ export default (app: Hono) =>
|
||||||
eq(Notes.authorId, user.id),
|
eq(Notes.authorId, user.id),
|
||||||
eq(Notes.reblogId, foundStatus.getStatus().id),
|
eq(Notes.reblogId, foundStatus.getStatus().id),
|
||||||
),
|
),
|
||||||
|
undefined,
|
||||||
|
user?.id,
|
||||||
);
|
);
|
||||||
|
|
||||||
if (!existingReblog) {
|
if (!existingReblog) {
|
||||||
|
|
|
||||||
|
|
@ -58,6 +58,7 @@ export default (app: Hono) =>
|
||||||
),
|
),
|
||||||
limit,
|
limit,
|
||||||
context.req.url,
|
context.req.url,
|
||||||
|
user.id,
|
||||||
);
|
);
|
||||||
|
|
||||||
return jsonResponse(
|
return jsonResponse(
|
||||||
|
|
|
||||||
|
|
@ -79,6 +79,7 @@ export default (app: Hono) =>
|
||||||
),
|
),
|
||||||
limit,
|
limit,
|
||||||
context.req.url,
|
context.req.url,
|
||||||
|
user?.id,
|
||||||
);
|
);
|
||||||
|
|
||||||
return jsonResponse(
|
return jsonResponse(
|
||||||
|
|
|
||||||
|
|
@ -186,6 +186,10 @@ export default (app: Hono) =>
|
||||||
})`
|
})`
|
||||||
: undefined,
|
: undefined,
|
||||||
),
|
),
|
||||||
|
undefined,
|
||||||
|
undefined,
|
||||||
|
undefined,
|
||||||
|
self?.id,
|
||||||
);
|
);
|
||||||
|
|
||||||
return jsonResponse({
|
return jsonResponse({
|
||||||
|
|
|
||||||
|
|
@ -106,6 +106,9 @@ export const getTestStatuses = async (
|
||||||
statuses.map((s) => s.id),
|
statuses.map((s) => s.id),
|
||||||
),
|
),
|
||||||
asc(Notes.id),
|
asc(Notes.id),
|
||||||
|
undefined,
|
||||||
|
undefined,
|
||||||
|
user.id,
|
||||||
)
|
)
|
||||||
).map((n) => n.getStatus());
|
).map((n) => n.getStatus());
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -17,11 +17,12 @@ export async function fetchTimeline<T extends UserType | Status | Notification>(
|
||||||
| Parameters<typeof findManyUsers>[0]
|
| Parameters<typeof findManyUsers>[0]
|
||||||
| Parameters<typeof db.query.Notifications.findMany>[0],
|
| Parameters<typeof db.query.Notifications.findMany>[0],
|
||||||
req: Request,
|
req: Request,
|
||||||
|
userId?: string,
|
||||||
) {
|
) {
|
||||||
// BEFORE: Before in a top-to-bottom order, so the most recent posts
|
// BEFORE: Before in a top-to-bottom order, so the most recent posts
|
||||||
// AFTER: After in a top-to-bottom order, so the oldest posts
|
// AFTER: After in a top-to-bottom order, so the oldest posts
|
||||||
// @ts-expect-error This is a hack to get around the fact that Prisma doesn't have a common base type for all models
|
// @ts-expect-error This is a hack to get around the fact that Prisma doesn't have a common base type for all models
|
||||||
const objects = (await model(args)) as T[];
|
const objects = (await model(args, userId)) as T[];
|
||||||
|
|
||||||
// Constuct HTTP Link header (next and prev) only if there are more statuses
|
// Constuct HTTP Link header (next and prev) only if there are more statuses
|
||||||
const linkHeader = [];
|
const linkHeader = [];
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue