& so it begins
This commit is contained in:
96
packages/logic/domains/notifications/controller.ts
Normal file
96
packages/logic/domains/notifications/controller.ts
Normal file
@@ -0,0 +1,96 @@
|
||||
import { FlowExecCtx } from "@/core/flow.execution.context";
|
||||
import { okAsync } from "neverthrow";
|
||||
import {
|
||||
NotificationFilters,
|
||||
PaginationOptions,
|
||||
} from "./data";
|
||||
import { NotificationRepository } from "./repository";
|
||||
import { db } from "@pkg/db";
|
||||
|
||||
export class NotificationController {
|
||||
constructor(private notifsRepo: NotificationRepository) {}
|
||||
|
||||
getNotifications(
|
||||
fctx: FlowExecCtx,
|
||||
filters: NotificationFilters,
|
||||
pagination: PaginationOptions,
|
||||
) {
|
||||
return this.notifsRepo.getNotifications(fctx, filters, pagination);
|
||||
}
|
||||
|
||||
markAsRead(
|
||||
fctx: FlowExecCtx,
|
||||
notificationIds: number[],
|
||||
userId: string,
|
||||
) {
|
||||
return this.notifsRepo.markAsRead(fctx, notificationIds, userId);
|
||||
}
|
||||
|
||||
markAsUnread(
|
||||
fctx: FlowExecCtx,
|
||||
notificationIds: number[],
|
||||
userId: string,
|
||||
) {
|
||||
return this.notifsRepo.markAsUnread(fctx, notificationIds, userId);
|
||||
}
|
||||
|
||||
archive(
|
||||
fctx: FlowExecCtx,
|
||||
notificationIds: number[],
|
||||
userId: string,
|
||||
) {
|
||||
return this.notifsRepo.archive(fctx, notificationIds, userId);
|
||||
}
|
||||
|
||||
unarchive(
|
||||
fctx: FlowExecCtx,
|
||||
notificationIds: number[],
|
||||
userId: string,
|
||||
) {
|
||||
return this.notifsRepo.unarchive(fctx, notificationIds, userId);
|
||||
}
|
||||
|
||||
deleteNotifications(
|
||||
fctx: FlowExecCtx,
|
||||
notificationIds: number[],
|
||||
userId: string,
|
||||
) {
|
||||
return this.notifsRepo.deleteNotifications(fctx, notificationIds, userId);
|
||||
}
|
||||
|
||||
getUnreadCount(
|
||||
fctx: FlowExecCtx,
|
||||
userId: string,
|
||||
) {
|
||||
return this.notifsRepo.getUnreadCount(fctx, userId);
|
||||
}
|
||||
|
||||
markAllAsRead(
|
||||
fctx: FlowExecCtx,
|
||||
userId: string,
|
||||
) {
|
||||
// Get all unread notification IDs for this user
|
||||
const filters: NotificationFilters = {
|
||||
userId,
|
||||
isRead: false,
|
||||
isArchived: false,
|
||||
};
|
||||
|
||||
// Get a large number to handle bulk operations
|
||||
const pagination: PaginationOptions = { page: 1, pageSize: 1000 };
|
||||
|
||||
return this.notifsRepo
|
||||
.getNotifications(fctx, filters, pagination)
|
||||
.map((paginated) => paginated.data.map((n) => n.id))
|
||||
.andThen((notificationIds) => {
|
||||
if (notificationIds.length === 0) {
|
||||
return okAsync(true);
|
||||
}
|
||||
return this.notifsRepo.markAsRead(fctx, notificationIds, userId);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export function getNotificationController(): NotificationController {
|
||||
return new NotificationController(new NotificationRepository(db));
|
||||
}
|
||||
102
packages/logic/domains/notifications/data.ts
Normal file
102
packages/logic/domains/notifications/data.ts
Normal file
@@ -0,0 +1,102 @@
|
||||
import * as v from "valibot";
|
||||
|
||||
// Notification schema
|
||||
export const notificationSchema = v.object({
|
||||
id: v.pipe(v.number(), v.integer()),
|
||||
title: v.string(),
|
||||
body: v.string(),
|
||||
priority: v.string(),
|
||||
type: v.string(),
|
||||
category: v.string(),
|
||||
isRead: v.boolean(),
|
||||
isArchived: v.boolean(),
|
||||
actionUrl: v.string(),
|
||||
actionType: v.string(),
|
||||
actionData: v.string(),
|
||||
icon: v.string(),
|
||||
userId: v.string(),
|
||||
sentAt: v.date(),
|
||||
readAt: v.nullable(v.date()),
|
||||
expiresAt: v.nullable(v.date()),
|
||||
createdAt: v.date(),
|
||||
updatedAt: v.date(),
|
||||
});
|
||||
|
||||
export type Notification = v.InferOutput<typeof notificationSchema>;
|
||||
export type Notifications = Notification[];
|
||||
|
||||
// Notification filters schema
|
||||
export const notificationFiltersSchema = v.object({
|
||||
userId: v.string(),
|
||||
isRead: v.optional(v.boolean()),
|
||||
isArchived: v.optional(v.boolean()),
|
||||
type: v.optional(v.string()),
|
||||
category: v.optional(v.string()),
|
||||
priority: v.optional(v.string()),
|
||||
search: v.optional(v.string()),
|
||||
});
|
||||
export type NotificationFilters = v.InferOutput<
|
||||
typeof notificationFiltersSchema
|
||||
>;
|
||||
|
||||
// Pagination options schema
|
||||
export const paginationOptionsSchema = v.object({
|
||||
page: v.pipe(v.number(), v.integer()),
|
||||
pageSize: v.pipe(v.number(), v.integer()),
|
||||
sortBy: v.optional(v.string()),
|
||||
sortOrder: v.optional(v.string()),
|
||||
});
|
||||
export type PaginationOptions = v.InferOutput<typeof paginationOptionsSchema>;
|
||||
|
||||
// Paginated notifications schema
|
||||
export const paginatedNotificationsSchema = v.object({
|
||||
data: v.array(notificationSchema),
|
||||
total: v.pipe(v.number(), v.integer()),
|
||||
page: v.pipe(v.number(), v.integer()),
|
||||
pageSize: v.pipe(v.number(), v.integer()),
|
||||
totalPages: v.pipe(v.number(), v.integer()),
|
||||
});
|
||||
export type PaginatedNotifications = v.InferOutput<
|
||||
typeof paginatedNotificationsSchema
|
||||
>;
|
||||
|
||||
// Get notifications schema
|
||||
export const getNotificationsSchema = v.object({
|
||||
filters: notificationFiltersSchema,
|
||||
pagination: paginationOptionsSchema,
|
||||
});
|
||||
export type GetNotifications = v.InferOutput<typeof getNotificationsSchema>;
|
||||
|
||||
// Bulk notification IDs schema
|
||||
export const bulkNotificationIdsSchema = v.object({
|
||||
notificationIds: v.array(v.pipe(v.number(), v.integer())),
|
||||
});
|
||||
export type BulkNotificationIds = v.InferOutput<
|
||||
typeof bulkNotificationIdsSchema
|
||||
>;
|
||||
|
||||
// View Model specific types
|
||||
export const clientNotificationFiltersSchema = v.object({
|
||||
userId: v.string(),
|
||||
isRead: v.optional(v.boolean()),
|
||||
isArchived: v.optional(v.boolean()),
|
||||
type: v.optional(v.string()),
|
||||
category: v.optional(v.string()),
|
||||
priority: v.optional(v.string()),
|
||||
search: v.optional(v.string()),
|
||||
});
|
||||
export type ClientNotificationFilters = v.InferOutput<
|
||||
typeof clientNotificationFiltersSchema
|
||||
>;
|
||||
|
||||
export const clientPaginationStateSchema = v.object({
|
||||
page: v.pipe(v.number(), v.integer()),
|
||||
pageSize: v.pipe(v.number(), v.integer()),
|
||||
total: v.pipe(v.number(), v.integer()),
|
||||
totalPages: v.pipe(v.number(), v.integer()),
|
||||
sortBy: v.picklist(["createdAt", "sentAt", "readAt", "priority"]),
|
||||
sortOrder: v.picklist(["asc", "desc"]),
|
||||
});
|
||||
export type ClientPaginationState = v.InferOutput<
|
||||
typeof clientPaginationStateSchema
|
||||
>;
|
||||
78
packages/logic/domains/notifications/errors.ts
Normal file
78
packages/logic/domains/notifications/errors.ts
Normal file
@@ -0,0 +1,78 @@
|
||||
import { FlowExecCtx } from "@/core/flow.execution.context";
|
||||
import { ERROR_CODES, type Err } from "@pkg/result";
|
||||
import { getError } from "@pkg/logger";
|
||||
|
||||
export const notificationErrors = {
|
||||
dbError: (fctx: FlowExecCtx, detail: string): Err =>
|
||||
getError({
|
||||
flowId: fctx.flowId,
|
||||
code: ERROR_CODES.DATABASE_ERROR,
|
||||
message: "Database operation failed",
|
||||
description: "Please try again later",
|
||||
detail,
|
||||
}),
|
||||
|
||||
getNotificationsFailed: (fctx: FlowExecCtx, detail: string): Err =>
|
||||
getError({
|
||||
flowId: fctx.flowId,
|
||||
code: ERROR_CODES.DATABASE_ERROR,
|
||||
message: "Failed to fetch notifications",
|
||||
description: "Please try again later",
|
||||
detail,
|
||||
}),
|
||||
|
||||
markAsReadFailed: (fctx: FlowExecCtx, detail: string): Err =>
|
||||
getError({
|
||||
flowId: fctx.flowId,
|
||||
code: ERROR_CODES.DATABASE_ERROR,
|
||||
message: "Failed to mark notifications as read",
|
||||
description: "Please try again later",
|
||||
detail,
|
||||
}),
|
||||
|
||||
markAsUnreadFailed: (fctx: FlowExecCtx, detail: string): Err =>
|
||||
getError({
|
||||
flowId: fctx.flowId,
|
||||
code: ERROR_CODES.DATABASE_ERROR,
|
||||
message: "Failed to mark notifications as unread",
|
||||
description: "Please try again later",
|
||||
detail,
|
||||
}),
|
||||
|
||||
archiveFailed: (fctx: FlowExecCtx, detail: string): Err =>
|
||||
getError({
|
||||
flowId: fctx.flowId,
|
||||
code: ERROR_CODES.DATABASE_ERROR,
|
||||
message: "Failed to archive notifications",
|
||||
description: "Please try again later",
|
||||
detail,
|
||||
}),
|
||||
|
||||
unarchiveFailed: (fctx: FlowExecCtx, detail: string): Err =>
|
||||
getError({
|
||||
flowId: fctx.flowId,
|
||||
code: ERROR_CODES.DATABASE_ERROR,
|
||||
message: "Failed to unarchive notifications",
|
||||
description: "Please try again later",
|
||||
detail,
|
||||
}),
|
||||
|
||||
deleteNotificationsFailed: (fctx: FlowExecCtx, detail: string): Err =>
|
||||
getError({
|
||||
flowId: fctx.flowId,
|
||||
code: ERROR_CODES.DATABASE_ERROR,
|
||||
message: "Failed to delete notifications",
|
||||
description: "Please try again later",
|
||||
detail,
|
||||
}),
|
||||
|
||||
getUnreadCountFailed: (fctx: FlowExecCtx, detail: string): Err =>
|
||||
getError({
|
||||
flowId: fctx.flowId,
|
||||
code: ERROR_CODES.DATABASE_ERROR,
|
||||
message: "Failed to get unread count",
|
||||
description: "Please try again later",
|
||||
detail,
|
||||
}),
|
||||
};
|
||||
|
||||
384
packages/logic/domains/notifications/repository.ts
Normal file
384
packages/logic/domains/notifications/repository.ts
Normal file
@@ -0,0 +1,384 @@
|
||||
import { and, asc, count, Database, desc, eq, like, or, sql } from "@pkg/db";
|
||||
import { notifications } from "@pkg/db/schema";
|
||||
import { ResultAsync } from "neverthrow";
|
||||
import { FlowExecCtx } from "@core/flow.execution.context";
|
||||
import type {
|
||||
Notification,
|
||||
NotificationFilters,
|
||||
PaginatedNotifications,
|
||||
PaginationOptions,
|
||||
} from "./data";
|
||||
import { type Err } from "@pkg/result";
|
||||
import { notificationErrors } from "./errors";
|
||||
import { logger } from "@pkg/logger";
|
||||
|
||||
export class NotificationRepository {
|
||||
constructor(private db: Database) {}
|
||||
|
||||
getNotifications(
|
||||
fctx: FlowExecCtx,
|
||||
filters: NotificationFilters,
|
||||
pagination: PaginationOptions,
|
||||
): ResultAsync<PaginatedNotifications, Err> {
|
||||
logger.info("Getting notifications with filters", { ...fctx, filters });
|
||||
|
||||
const { userId, isRead, isArchived, type, category, priority, search } =
|
||||
filters;
|
||||
const {
|
||||
page,
|
||||
pageSize,
|
||||
sortBy = "createdAt",
|
||||
sortOrder = "desc",
|
||||
} = pagination;
|
||||
|
||||
// Build WHERE conditions
|
||||
const conditions = [eq(notifications.userId, userId)];
|
||||
|
||||
if (isRead !== undefined) {
|
||||
conditions.push(eq(notifications.isRead, isRead));
|
||||
}
|
||||
|
||||
if (isArchived !== undefined) {
|
||||
conditions.push(eq(notifications.isArchived, isArchived));
|
||||
}
|
||||
|
||||
if (type) {
|
||||
conditions.push(eq(notifications.type, type));
|
||||
}
|
||||
|
||||
if (category) {
|
||||
conditions.push(eq(notifications.category, category));
|
||||
}
|
||||
|
||||
if (priority) {
|
||||
conditions.push(eq(notifications.priority, priority));
|
||||
}
|
||||
|
||||
if (search) {
|
||||
conditions.push(
|
||||
or(
|
||||
like(notifications.title, `%${search}%`),
|
||||
like(notifications.body, `%${search}%`),
|
||||
)!,
|
||||
);
|
||||
}
|
||||
|
||||
const whereClause = and(...conditions);
|
||||
|
||||
return ResultAsync.fromPromise(
|
||||
this.db.select({ count: count() }).from(notifications).where(whereClause),
|
||||
(error) => {
|
||||
logger.error("Failed to get notifications count", {
|
||||
...fctx,
|
||||
error,
|
||||
});
|
||||
return notificationErrors.getNotificationsFailed(
|
||||
fctx,
|
||||
error instanceof Error ? error.message : String(error),
|
||||
);
|
||||
},
|
||||
).andThen((totalResult) => {
|
||||
const total = totalResult[0]?.count || 0;
|
||||
const offset = (page - 1) * pageSize;
|
||||
|
||||
// Map sortBy to proper column
|
||||
const getOrderColumn = (sortBy: string) => {
|
||||
switch (sortBy) {
|
||||
case "createdAt":
|
||||
return notifications.createdAt;
|
||||
case "sentAt":
|
||||
return notifications.sentAt;
|
||||
case "readAt":
|
||||
return notifications.readAt;
|
||||
case "priority":
|
||||
return notifications.priority;
|
||||
default:
|
||||
return notifications.createdAt;
|
||||
}
|
||||
};
|
||||
|
||||
const orderColumn = getOrderColumn(sortBy);
|
||||
const orderFunc = sortOrder === "asc" ? asc : desc;
|
||||
|
||||
return ResultAsync.fromPromise(
|
||||
this.db
|
||||
.select()
|
||||
.from(notifications)
|
||||
.where(whereClause)
|
||||
.orderBy(orderFunc(orderColumn))
|
||||
.limit(pageSize)
|
||||
.offset(offset),
|
||||
(error) => {
|
||||
logger.error("Failed to get notifications data", {
|
||||
...fctx,
|
||||
error,
|
||||
});
|
||||
return notificationErrors.getNotificationsFailed(
|
||||
fctx,
|
||||
error instanceof Error ? error.message : String(error),
|
||||
);
|
||||
},
|
||||
).map((data) => {
|
||||
const totalPages = Math.ceil(total / pageSize);
|
||||
logger.info("Retrieved notifications", {
|
||||
...fctx,
|
||||
count: data.length,
|
||||
page,
|
||||
totalPages,
|
||||
});
|
||||
|
||||
return {
|
||||
data: data as Notification[],
|
||||
total,
|
||||
page,
|
||||
pageSize,
|
||||
totalPages,
|
||||
};
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
markAsRead(
|
||||
fctx: FlowExecCtx,
|
||||
notificationIds: number[],
|
||||
userId: string,
|
||||
): ResultAsync<boolean, Err> {
|
||||
logger.info("Marking notifications as read", {
|
||||
...fctx,
|
||||
notificationIds,
|
||||
userId,
|
||||
});
|
||||
|
||||
return ResultAsync.fromPromise(
|
||||
this.db
|
||||
.update(notifications)
|
||||
.set({
|
||||
isRead: true,
|
||||
readAt: new Date(),
|
||||
updatedAt: new Date(),
|
||||
})
|
||||
.where(
|
||||
and(
|
||||
eq(notifications.userId, userId),
|
||||
sql`${notifications.id} = ANY(${notificationIds})`,
|
||||
),
|
||||
),
|
||||
(error) => {
|
||||
logger.error("Failed to mark notifications as read", {
|
||||
...fctx,
|
||||
error,
|
||||
});
|
||||
return notificationErrors.markAsReadFailed(
|
||||
fctx,
|
||||
error instanceof Error ? error.message : String(error),
|
||||
);
|
||||
},
|
||||
).map(() => {
|
||||
logger.info("Notifications marked as read successfully", {
|
||||
...fctx,
|
||||
notificationIds,
|
||||
});
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
markAsUnread(
|
||||
fctx: FlowExecCtx,
|
||||
notificationIds: number[],
|
||||
userId: string,
|
||||
): ResultAsync<boolean, Err> {
|
||||
logger.info("Marking notifications as unread", {
|
||||
...fctx,
|
||||
notificationIds,
|
||||
userId,
|
||||
});
|
||||
|
||||
return ResultAsync.fromPromise(
|
||||
this.db
|
||||
.update(notifications)
|
||||
.set({
|
||||
isRead: false,
|
||||
readAt: null,
|
||||
updatedAt: new Date(),
|
||||
})
|
||||
.where(
|
||||
and(
|
||||
eq(notifications.userId, userId),
|
||||
sql`${notifications.id} = ANY(${notificationIds})`,
|
||||
),
|
||||
),
|
||||
(error) => {
|
||||
logger.error("Failed to mark notifications as unread", {
|
||||
...fctx,
|
||||
error,
|
||||
});
|
||||
return notificationErrors.markAsUnreadFailed(
|
||||
fctx,
|
||||
error instanceof Error ? error.message : String(error),
|
||||
);
|
||||
},
|
||||
).map(() => {
|
||||
logger.info("Notifications marked as unread successfully", {
|
||||
...fctx,
|
||||
notificationIds,
|
||||
});
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
archive(
|
||||
fctx: FlowExecCtx,
|
||||
notificationIds: number[],
|
||||
userId: string,
|
||||
): ResultAsync<boolean, Err> {
|
||||
logger.info("Archiving notifications", {
|
||||
...fctx,
|
||||
notificationIds,
|
||||
userId,
|
||||
});
|
||||
|
||||
return ResultAsync.fromPromise(
|
||||
this.db
|
||||
.update(notifications)
|
||||
.set({
|
||||
isArchived: true,
|
||||
updatedAt: new Date(),
|
||||
})
|
||||
.where(
|
||||
and(
|
||||
eq(notifications.userId, userId),
|
||||
sql`${notifications.id} = ANY(${notificationIds})`,
|
||||
),
|
||||
),
|
||||
(error) => {
|
||||
logger.error("Failed to archive notifications", {
|
||||
...fctx,
|
||||
error,
|
||||
});
|
||||
return notificationErrors.archiveFailed(
|
||||
fctx,
|
||||
error instanceof Error ? error.message : String(error),
|
||||
);
|
||||
},
|
||||
).map(() => {
|
||||
logger.info("Notifications archived successfully", {
|
||||
...fctx,
|
||||
notificationIds,
|
||||
});
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
unarchive(
|
||||
fctx: FlowExecCtx,
|
||||
notificationIds: number[],
|
||||
userId: string,
|
||||
): ResultAsync<boolean, Err> {
|
||||
logger.info("Unarchiving notifications", {
|
||||
...fctx,
|
||||
notificationIds,
|
||||
userId,
|
||||
});
|
||||
|
||||
return ResultAsync.fromPromise(
|
||||
this.db
|
||||
.update(notifications)
|
||||
.set({
|
||||
isArchived: false,
|
||||
updatedAt: new Date(),
|
||||
})
|
||||
.where(
|
||||
and(
|
||||
eq(notifications.userId, userId),
|
||||
sql`${notifications.id} = ANY(${notificationIds})`,
|
||||
),
|
||||
),
|
||||
(error) => {
|
||||
logger.error("Failed to unarchive notifications", {
|
||||
...fctx,
|
||||
error,
|
||||
});
|
||||
return notificationErrors.unarchiveFailed(
|
||||
fctx,
|
||||
error instanceof Error ? error.message : String(error),
|
||||
);
|
||||
},
|
||||
).map(() => {
|
||||
logger.info("Notifications unarchived successfully", {
|
||||
...fctx,
|
||||
notificationIds,
|
||||
});
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
deleteNotifications(
|
||||
fctx: FlowExecCtx,
|
||||
notificationIds: number[],
|
||||
userId: string,
|
||||
): ResultAsync<boolean, Err> {
|
||||
logger.info("Deleting notifications", {
|
||||
...fctx,
|
||||
notificationIds,
|
||||
userId,
|
||||
});
|
||||
|
||||
return ResultAsync.fromPromise(
|
||||
this.db
|
||||
.delete(notifications)
|
||||
.where(
|
||||
and(
|
||||
eq(notifications.userId, userId),
|
||||
sql`${notifications.id} = ANY(${notificationIds})`,
|
||||
),
|
||||
),
|
||||
(error) => {
|
||||
logger.error("Failed to delete notifications", {
|
||||
...fctx,
|
||||
error,
|
||||
});
|
||||
return notificationErrors.deleteNotificationsFailed(
|
||||
fctx,
|
||||
error instanceof Error ? error.message : String(error),
|
||||
);
|
||||
},
|
||||
).map(() => {
|
||||
logger.info("Notifications deleted successfully", {
|
||||
...fctx,
|
||||
notificationIds,
|
||||
});
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
getUnreadCount(
|
||||
fctx: FlowExecCtx,
|
||||
userId: string,
|
||||
): ResultAsync<number, Err> {
|
||||
logger.info("Getting unread count", { ...fctx, userId });
|
||||
|
||||
return ResultAsync.fromPromise(
|
||||
this.db
|
||||
.select({ count: count() })
|
||||
.from(notifications)
|
||||
.where(
|
||||
and(
|
||||
eq(notifications.userId, userId),
|
||||
eq(notifications.isRead, false),
|
||||
eq(notifications.isArchived, false),
|
||||
),
|
||||
),
|
||||
(error) => {
|
||||
logger.error("Failed to get unread count", { ...fctx, error });
|
||||
return notificationErrors.getUnreadCountFailed(
|
||||
fctx,
|
||||
error instanceof Error ? error.message : String(error),
|
||||
);
|
||||
},
|
||||
).map((result) => {
|
||||
const count = result[0]?.count || 0;
|
||||
logger.info("Retrieved unread count", { ...fctx, count });
|
||||
return count;
|
||||
});
|
||||
}
|
||||
}
|
||||
160
packages/logic/domains/notifications/router.ts
Normal file
160
packages/logic/domains/notifications/router.ts
Normal file
@@ -0,0 +1,160 @@
|
||||
import { bulkNotificationIdsSchema, getNotificationsSchema } from "./data";
|
||||
import { getNotificationController } from "./controller";
|
||||
import { sValidator } from "@hono/standard-validator";
|
||||
import { HonoContext } from "@core/hono.helpers";
|
||||
import { Hono } from "hono";
|
||||
|
||||
const nc = getNotificationController();
|
||||
|
||||
export const notificationsRouter = new Hono<HonoContext>()
|
||||
.get("/", async (c) => {
|
||||
const fctx = c.env.locals.fCtx;
|
||||
const userId = c.env.locals.user.id;
|
||||
const url = new URL(c.req.url);
|
||||
|
||||
const filters = {
|
||||
userId,
|
||||
isRead: url.searchParams.get("isRead")
|
||||
? url.searchParams.get("isRead") === "true"
|
||||
: undefined,
|
||||
isArchived: url.searchParams.get("isArchived")
|
||||
? url.searchParams.get("isArchived") === "true"
|
||||
: undefined,
|
||||
type: url.searchParams.get("type") || undefined,
|
||||
category: url.searchParams.get("category") || undefined,
|
||||
priority: url.searchParams.get("priority") || undefined,
|
||||
search: url.searchParams.get("search") || undefined,
|
||||
};
|
||||
|
||||
const pagination = {
|
||||
page: parseInt(url.searchParams.get("page") || "1"),
|
||||
pageSize: parseInt(url.searchParams.get("pageSize") || "20"),
|
||||
sortBy: url.searchParams.get("sortBy") || "createdAt",
|
||||
sortOrder: url.searchParams.get("sortOrder") || "desc",
|
||||
};
|
||||
|
||||
const res = await nc.getNotifications(fctx, filters, pagination);
|
||||
return c.json(
|
||||
res.isOk()
|
||||
? { data: res.value, error: null }
|
||||
: { data: null, error: res.error },
|
||||
res.isOk() ? 200 : 400,
|
||||
);
|
||||
})
|
||||
.post(
|
||||
"/get-notifications",
|
||||
sValidator("json", getNotificationsSchema),
|
||||
async (c) => {
|
||||
const fctx = c.env.locals.fCtx;
|
||||
const data = c.req.valid("json");
|
||||
const res = await nc.getNotifications(fctx, data.filters, data.pagination);
|
||||
return c.json(
|
||||
res.isOk()
|
||||
? { data: res.value, error: null }
|
||||
: { data: null, error: res.error },
|
||||
res.isOk() ? 200 : 400,
|
||||
);
|
||||
},
|
||||
)
|
||||
.put(
|
||||
"/mark-read",
|
||||
sValidator("json", bulkNotificationIdsSchema),
|
||||
async (c) => {
|
||||
const fctx = c.env.locals.fCtx;
|
||||
const data = c.req.valid("json");
|
||||
const userId = c.env.locals.user.id;
|
||||
const res = await nc.markAsRead(fctx, [...data.notificationIds], userId);
|
||||
return c.json(
|
||||
res.isOk()
|
||||
? { data: res.value, error: null }
|
||||
: { data: null, error: res.error },
|
||||
res.isOk() ? 200 : 400,
|
||||
);
|
||||
},
|
||||
)
|
||||
.put(
|
||||
"/mark-unread",
|
||||
sValidator("json", bulkNotificationIdsSchema),
|
||||
async (c) => {
|
||||
const fctx = c.env.locals.fCtx;
|
||||
const data = c.req.valid("json");
|
||||
const userId = c.env.locals.user.id;
|
||||
const res = await nc.markAsUnread(fctx, [...data.notificationIds], userId);
|
||||
return c.json(
|
||||
res.isOk()
|
||||
? { data: res.value, error: null }
|
||||
: { data: null, error: res.error },
|
||||
res.isOk() ? 200 : 400,
|
||||
);
|
||||
},
|
||||
)
|
||||
.put(
|
||||
"/archive",
|
||||
sValidator("json", bulkNotificationIdsSchema),
|
||||
async (c) => {
|
||||
const fctx = c.env.locals.fCtx;
|
||||
const data = c.req.valid("json");
|
||||
const userId = c.env.locals.user.id;
|
||||
const res = await nc.archive(fctx, [...data.notificationIds], userId);
|
||||
return c.json(
|
||||
res.isOk()
|
||||
? { data: res.value, error: null }
|
||||
: { data: null, error: res.error },
|
||||
res.isOk() ? 200 : 400,
|
||||
);
|
||||
},
|
||||
)
|
||||
.put(
|
||||
"/unarchive",
|
||||
sValidator("json", bulkNotificationIdsSchema),
|
||||
async (c) => {
|
||||
const fctx = c.env.locals.fCtx;
|
||||
const data = c.req.valid("json");
|
||||
const userId = c.env.locals.user.id;
|
||||
const res = await nc.unarchive(fctx, [...data.notificationIds], userId);
|
||||
return c.json(
|
||||
res.isOk()
|
||||
? { data: res.value, error: null }
|
||||
: { data: null, error: res.error },
|
||||
res.isOk() ? 200 : 400,
|
||||
);
|
||||
},
|
||||
)
|
||||
.delete(
|
||||
"/delete",
|
||||
sValidator("json", bulkNotificationIdsSchema),
|
||||
async (c) => {
|
||||
const fctx = c.env.locals.fCtx;
|
||||
const data = c.req.valid("json");
|
||||
const userId = c.env.locals.user.id;
|
||||
const res = await nc.deleteNotifications(fctx, [...data.notificationIds], userId);
|
||||
return c.json(
|
||||
res.isOk()
|
||||
? { data: res.value, error: null }
|
||||
: { data: null, error: res.error },
|
||||
res.isOk() ? 200 : 400,
|
||||
);
|
||||
},
|
||||
)
|
||||
.put("/mark-all-read", async (c) => {
|
||||
const fctx = c.env.locals.fCtx;
|
||||
const userId = c.env.locals.user.id;
|
||||
const res = await nc.markAllAsRead(fctx, userId);
|
||||
return c.json(
|
||||
res.isOk()
|
||||
? { data: res.value, error: null }
|
||||
: { data: null, error: res.error },
|
||||
res.isOk() ? 200 : 400,
|
||||
);
|
||||
})
|
||||
.get("/unread-count", async (c) => {
|
||||
const fctx = c.env.locals.fCtx;
|
||||
const userId = c.env.locals.user.id;
|
||||
const res = await nc.getUnreadCount(fctx, userId);
|
||||
return c.json(
|
||||
res.isOk()
|
||||
? { data: res.value, error: null }
|
||||
: { data: null, error: res.error },
|
||||
res.isOk() ? 200 : 400,
|
||||
);
|
||||
});
|
||||
Reference in New Issue
Block a user