Skip to content

Commit

Permalink
refactor(database): ♻️ Move Notifications to their own ORM abstractions
Browse files Browse the repository at this point in the history
  • Loading branch information
CPlusPatch committed Nov 4, 2024
1 parent 14ace17 commit e732a3d
Show file tree
Hide file tree
Showing 16 changed files with 439 additions and 400 deletions.
20 changes: 11 additions & 9 deletions api/api/v1/notifications/:id/dismiss.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
import { apiRoute, applyConfig, auth } from "@/api";
import { createRoute } from "@hono/zod-openapi";
import { db } from "@versia/kit/db";
import { Notifications, RolePermissions } from "@versia/kit/tables";
import { eq } from "drizzle-orm";
import { Notification } from "@versia/kit/db";
import { RolePermissions } from "@versia/kit/tables";
import { z } from "zod";
import { ErrorSchema } from "~/types/api";

Expand Down Expand Up @@ -59,12 +58,15 @@ export default apiRoute((app) =>
return context.json({ error: "Unauthorized" }, 401);
}

await db
.update(Notifications)
.set({
dismissed: true,
})
.where(eq(Notifications.id, id));
const notification = await Notification.fromId(id);

if (!notification) {
return context.json({ error: "Notification not found" }, 404);
}

await notification.update({
dismissed: true,
});

return context.newResponse(null, 200);
}),
Expand Down
20 changes: 3 additions & 17 deletions api/api/v1/notifications/:id/index.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,8 @@
import { apiRoute, applyConfig, auth } from "@/api";
import { createRoute } from "@hono/zod-openapi";
import { Note, User } from "@versia/kit/db";
import { Note, Notification, User } from "@versia/kit/db";
import { RolePermissions } from "@versia/kit/tables";
import type { SQL } from "drizzle-orm";
import { z } from "zod";
import {
findManyNotifications,
notificationToApi,
} from "~/classes/functions/notification";
import { ErrorSchema } from "~/types/api";

export const meta = applyConfig({
Expand Down Expand Up @@ -101,21 +96,12 @@ export default apiRoute((app) =>
return context.json({ error: "Unauthorized" }, 401);
}

const notification = (
await findManyNotifications(
{
where: (notification, { eq }): SQL | undefined =>
eq(notification.id, id),
limit: 1,
},
user.id,
)
)[0];
const notification = await Notification.fromId(id, user.id);

if (!notification) {
return context.json({ error: "Notification not found" }, 404);
}

return context.json(await notificationToApi(notification), 200);
return context.json(await notification.toApi(), 200);
}),
);
11 changes: 2 additions & 9 deletions api/api/v1/notifications/clear/index.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
import { apiRoute, applyConfig, auth } from "@/api";
import { createRoute } from "@hono/zod-openapi";
import { db } from "@versia/kit/db";
import { Notifications, RolePermissions } from "@versia/kit/tables";
import { eq } from "drizzle-orm";
import { RolePermissions } from "@versia/kit/tables";
import { ErrorSchema } from "~/types/api";

export const meta = applyConfig({
Expand Down Expand Up @@ -47,12 +45,7 @@ export default apiRoute((app) =>
return context.json({ error: "Unauthorized" }, 401);
}

await db
.update(Notifications)
.set({
dismissed: true,
})
.where(eq(Notifications.notifiedId, user.id));
await user.clearAllNotifications();

return context.newResponse(null, 200);
}),
Expand Down
16 changes: 2 additions & 14 deletions api/api/v1/notifications/destroy_multiple/index.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
import { apiRoute, applyConfig, auth } from "@/api";
import { createRoute } from "@hono/zod-openapi";
import { db } from "@versia/kit/db";
import { Notifications, RolePermissions } from "@versia/kit/tables";
import { and, eq, inArray } from "drizzle-orm";
import { RolePermissions } from "@versia/kit/tables";
import { z } from "zod";
import { ErrorSchema } from "~/types/api";

Expand Down Expand Up @@ -60,17 +58,7 @@ export default apiRoute((app) =>

const { "ids[]": ids } = context.req.valid("query");

await db
.update(Notifications)
.set({
dismissed: true,
})
.where(
and(
inArray(Notifications.id, ids),
eq(Notifications.notifiedId, user.id),
),
);
await user.clearSomeNotifications(ids);

return context.newResponse(null, 200);
}),
Expand Down
109 changes: 43 additions & 66 deletions api/api/v1/notifications/index.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,9 @@
import { apiRoute, applyConfig, auth, idValidator } from "@/api";
import { fetchTimeline } from "@/timelines";
import { createRoute } from "@hono/zod-openapi";
import { Note, User } from "@versia/kit/db";
import { RolePermissions } from "@versia/kit/tables";
import { type SQL, sql } from "drizzle-orm";
import { Note, Timeline, User } from "@versia/kit/db";
import { Notifications, RolePermissions } from "@versia/kit/tables";
import { and, eq, gt, gte, inArray, lt, not, sql } from "drizzle-orm";
import { z } from "zod";
import {
findManyNotifications,
notificationToApi,
} from "~/classes/functions/notification";
import type { NotificationWithRelations } from "~/classes/functions/notification";
import { ErrorSchema } from "~/types/api";

export const meta = applyConfig({
Expand Down Expand Up @@ -150,65 +144,48 @@ export default apiRoute((app) =>
types,
} = context.req.valid("query");

const { objects, link } =
await fetchTimeline<NotificationWithRelations>(
findManyNotifications,
{
where: (
// @ts-expect-error Yes I KNOW the types are wrong
notification,
// @ts-expect-error Yes I KNOW the types are wrong
{ lt, gte, gt, and, eq, not, inArray },
): SQL | undefined =>
and(
max_id ? lt(notification.id, max_id) : undefined,
since_id
? gte(notification.id, since_id)
: undefined,
min_id ? gt(notification.id, min_id) : undefined,
eq(notification.notifiedId, user.id),
eq(notification.dismissed, false),
account_id
? eq(notification.accountId, account_id)
: undefined,
not(eq(notification.accountId, user.id)),
types
? inArray(notification.type, types)
: undefined,
exclude_types
? not(inArray(notification.type, exclude_types))
: undefined,
// Don't show notes that have filtered words in them (via Notification.note.content via Notification.noteId)
// Filters in `Filters` table have keyword in `FilterKeywords` table (use LIKE)
// Filters table has a userId and a context which is an array
sql`NOT EXISTS (
SELECT 1
FROM "Filters"
WHERE "Filters"."userId" = ${user.id}
AND "Filters"."filter_action" = 'hide'
AND EXISTS (
SELECT 1
FROM "FilterKeywords", "Notifications" as "n_inner", "Notes"
WHERE "FilterKeywords"."filterId" = "Filters"."id"
AND "n_inner"."noteId" = "Notes"."id"
AND "Notes"."content" LIKE
'%' || "FilterKeywords"."keyword" || '%'
AND "n_inner"."id" = "Notifications"."id"
)
AND "Filters"."context" @> ARRAY['notifications']
)`,
),
limit,
// @ts-expect-error Yes I KNOW the types are wrong
orderBy: (notification, { desc }): SQL | undefined =>
desc(notification.id),
},
context.req.raw,
user.id,
);
const { objects, link } = await Timeline.getNotificationTimeline(
and(
max_id ? lt(Notifications.id, max_id) : undefined,
since_id ? gte(Notifications.id, since_id) : undefined,
min_id ? gt(Notifications.id, min_id) : undefined,
eq(Notifications.notifiedId, user.id),
eq(Notifications.dismissed, false),
account_id
? eq(Notifications.accountId, account_id)
: undefined,
not(eq(Notifications.accountId, user.id)),
types ? inArray(Notifications.type, types) : undefined,
exclude_types
? not(inArray(Notifications.type, exclude_types))
: undefined,
// Don't show notes that have filtered words in them (via Notification.note.content via Notification.noteId)
// Filters in `Filters` table have keyword in `FilterKeywords` table (use LIKE)
// Filters table has a userId and a context which is an array
sql`NOT EXISTS (
SELECT 1
FROM "Filters"
WHERE "Filters"."userId" = ${user.id}
AND "Filters"."filter_action" = 'hide'
AND EXISTS (
SELECT 1
FROM "FilterKeywords", "Notifications" as "n_inner", "Notes"
WHERE "FilterKeywords"."filterId" = "Filters"."id"
AND "n_inner"."noteId" = "Notes"."id"
AND "Notes"."content" LIKE
'%' || "FilterKeywords"."keyword" || '%'
AND "n_inner"."id" = "Notifications"."id"
)
AND "Filters"."context" @> ARRAY['notifications']
)`,
),
limit,
context.req.url,
user?.id,
);

return context.json(
await Promise.all(objects.map((n) => notificationToApi(n))),
await Promise.all(objects.map((n) => n.toApi())),
200,
{
Link: link,
Expand Down
6 changes: 3 additions & 3 deletions api/api/v1/statuses/:id/reblog.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { apiRoute, applyConfig, auth, jsonOrForm } from "@/api";
import { createRoute } from "@hono/zod-openapi";
import { Note, db } from "@versia/kit/db";
import { Notes, Notifications, RolePermissions } from "@versia/kit/tables";
import { Note, Notification } from "@versia/kit/db";
import { Notes, RolePermissions } from "@versia/kit/tables";
import { and, eq } from "drizzle-orm";
import { z } from "zod";
import { ErrorSchema } from "~/types/api";
Expand Down Expand Up @@ -141,7 +141,7 @@ export default apiRoute((app) =>
}

if (foundStatus.author.isLocal() && user.isLocal()) {
await db.insert(Notifications).values({
await Notification.insert({
accountId: user.id,
notifiedId: foundStatus.author.id,
type: "reblog",
Expand Down
16 changes: 15 additions & 1 deletion classes/database/like.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
import { RolePermission } from "@versia/client/types";
import type { Delete, LikeExtension } from "@versia/federation/types";
import { db } from "@versia/kit/db";
import { Likes } from "@versia/kit/tables";
import { Likes, Notifications } from "@versia/kit/tables";
import {
type InferInsertModel,
type InferSelectModel,
type SQL,
and,
desc,
eq,
inArray,
Expand Down Expand Up @@ -139,6 +140,19 @@ export class Like extends BaseInterface<typeof Likes, LikeType> {
return this.data.id;
}

public async clearRelatedNotifications(): Promise<void> {
await db
.delete(Notifications)
.where(
and(
eq(Notifications.accountId, this.id),
eq(Notifications.type, "favourite"),
eq(Notifications.notifiedId, this.data.liked.authorId),
eq(Notifications.noteId, this.data.liked.id),
),
);
}

public getUri(): URL {
return new URL(`/objects/${this.data.id}`, config.http.base_url);
}
Expand Down
5 changes: 2 additions & 3 deletions classes/database/note.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,12 @@ import type {
Delete as VersiaDelete,
Note as VersiaNote,
} from "@versia/federation/types";
import { db } from "@versia/kit/db";
import { Notification, db } from "@versia/kit/db";
import {
Attachments,
EmojiToNote,
NoteToMentions,
Notes,
Notifications,
Users,
} from "@versia/kit/tables";
import {
Expand Down Expand Up @@ -469,7 +468,7 @@ export class Note extends BaseInterface<typeof Notes, StatusWithRelations> {
// Send notifications for mentioned local users
for (const mention of parsedMentions ?? []) {
if (mention.isLocal()) {
await db.insert(Notifications).values({
await Notification.insert({
accountId: data.author.id,
notifiedId: mention.id,
type: "mention",
Expand Down
Loading

0 comments on commit e732a3d

Please sign in to comment.