native-ified the notifications domain
This commit is contained in:
11
apps/main/src/lib/core/server.utils.ts
Normal file
11
apps/main/src/lib/core/server.utils.ts
Normal file
@@ -0,0 +1,11 @@
|
||||
import type { FlowExecCtx } from "@pkg/logic/core/flow.execution.context";
|
||||
|
||||
export async function getFlowExecCtxForRemoteFuncs(
|
||||
locals: App.Locals,
|
||||
): Promise<FlowExecCtx> {
|
||||
return {
|
||||
flowId: locals.flowId || crypto.randomUUID(),
|
||||
userId: locals.user?.id,
|
||||
sessionId: locals.session?.id,
|
||||
};
|
||||
}
|
||||
22
apps/main/src/lib/domains/account/account.remote.ts
Normal file
22
apps/main/src/lib/domains/account/account.remote.ts
Normal file
@@ -0,0 +1,22 @@
|
||||
import { ensureAccountExistsSchema } from "@pkg/logic/domains/user/data";
|
||||
import { command, getRequestEvent, query } from "$app/server";
|
||||
|
||||
export const getMyInfoSQ = query(async () => {
|
||||
const event = getRequestEvent();
|
||||
// .... do stuff usually done in a router here
|
||||
|
||||
return { data: "testing" };
|
||||
});
|
||||
|
||||
export const getUsersInfoByIdSQ = query(async () => {
|
||||
// .... do stuff usually done in a router here
|
||||
return { data: "testing" };
|
||||
});
|
||||
|
||||
export const ensureAccountExistsSQ = command(
|
||||
ensureAccountExistsSchema,
|
||||
async (payload) => {
|
||||
// .... do stuff usually done in a router here
|
||||
return { data: "testing" };
|
||||
},
|
||||
);
|
||||
@@ -3,18 +3,25 @@ import type {
|
||||
ClientPaginationState,
|
||||
Notifications,
|
||||
} from "@pkg/logic/domains/notifications/data";
|
||||
import { apiClient, user } from "$lib/global.stores";
|
||||
import {
|
||||
archiveSC,
|
||||
deleteNotificationsSC,
|
||||
getNotificationsSQ,
|
||||
getUnreadCountSQ,
|
||||
markAllReadSC,
|
||||
markReadSC,
|
||||
markUnreadSC,
|
||||
unarchiveSC,
|
||||
} from "./notifications.remote";
|
||||
import { user } from "$lib/global.stores";
|
||||
import { toast } from "svelte-sonner";
|
||||
import { get } from "svelte/store";
|
||||
import { ResultAsync, errAsync, okAsync } from "neverthrow";
|
||||
import type { Err } from "@pkg/result";
|
||||
|
||||
class NotificationViewModel {
|
||||
notifications = $state([] as Notifications);
|
||||
loading = $state(false);
|
||||
selectedIds = $state(new Set<number>());
|
||||
|
||||
// Pagination state
|
||||
pagination = $state<ClientPaginationState>({
|
||||
page: 1,
|
||||
pageSize: 20,
|
||||
@@ -24,465 +31,157 @@ class NotificationViewModel {
|
||||
sortOrder: "desc",
|
||||
});
|
||||
|
||||
// Filter state
|
||||
filters = $state<ClientNotificationFilters>({
|
||||
userId: get(user)?.id!,
|
||||
isArchived: false, // Default to showing non-archived
|
||||
isArchived: false,
|
||||
});
|
||||
|
||||
// Stats
|
||||
unreadCount = $state(0);
|
||||
|
||||
private getFetchQueryInput() {
|
||||
return {
|
||||
filters: {
|
||||
userId: this.filters.userId,
|
||||
isRead: this.filters.isRead,
|
||||
isArchived: this.filters.isArchived,
|
||||
type: this.filters.type,
|
||||
category: this.filters.category,
|
||||
priority: this.filters.priority,
|
||||
search: this.filters.search,
|
||||
},
|
||||
pagination: {
|
||||
page: this.pagination.page,
|
||||
pageSize: this.pagination.pageSize,
|
||||
sortBy: this.pagination.sortBy,
|
||||
sortOrder: this.pagination.sortOrder,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
private async runCommand(
|
||||
fn: (payload: { notificationIds: number[] }) => Promise<any>,
|
||||
notificationIds: number[],
|
||||
errorMessage: string,
|
||||
after: Array<() => Promise<void>>,
|
||||
) {
|
||||
try {
|
||||
const result = await fn({ notificationIds });
|
||||
if (result?.error) {
|
||||
toast.error(result.error.message || errorMessage, {
|
||||
description: result.error.description || "Please try again later",
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
for (const action of after) {
|
||||
await action();
|
||||
}
|
||||
} catch (error) {
|
||||
toast.error(errorMessage, {
|
||||
description:
|
||||
error instanceof Error ? error.message : "Please try again later",
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
async fetchNotifications() {
|
||||
this.loading = true;
|
||||
|
||||
const params = new URLSearchParams();
|
||||
|
||||
// Add pagination params
|
||||
params.append("page", this.pagination.page.toString());
|
||||
params.append("pageSize", this.pagination.pageSize.toString());
|
||||
params.append("sortBy", this.pagination.sortBy);
|
||||
params.append("sortOrder", this.pagination.sortOrder);
|
||||
|
||||
// Add filter params
|
||||
if (this.filters.isRead !== undefined) {
|
||||
params.append("isRead", this.filters.isRead.toString());
|
||||
}
|
||||
if (this.filters.isArchived !== undefined) {
|
||||
params.append("isArchived", this.filters.isArchived.toString());
|
||||
}
|
||||
if (this.filters.type) {
|
||||
params.append("type", this.filters.type);
|
||||
}
|
||||
if (this.filters.category) {
|
||||
params.append("category", this.filters.category);
|
||||
}
|
||||
if (this.filters.priority) {
|
||||
params.append("priority", this.filters.priority);
|
||||
}
|
||||
if (this.filters.search) {
|
||||
params.append("search", this.filters.search);
|
||||
}
|
||||
|
||||
const result = await ResultAsync.fromPromise(
|
||||
get(apiClient).notifications.$get({
|
||||
query: Object.fromEntries(params.entries()),
|
||||
}),
|
||||
(error): Err => ({
|
||||
code: "NETWORK_ERROR",
|
||||
message: "Failed to fetch notifications",
|
||||
description: "Network request failed",
|
||||
detail: error instanceof Error ? error.message : String(error),
|
||||
}),
|
||||
)
|
||||
.andThen((response) => {
|
||||
if (!response.ok) {
|
||||
return errAsync({
|
||||
code: "API_ERROR",
|
||||
message: "Failed to fetch notifications",
|
||||
description: `Response failed with status ${response.status}`,
|
||||
detail: `HTTP ${response.status}`,
|
||||
});
|
||||
}
|
||||
return ResultAsync.fromPromise(
|
||||
response.json(),
|
||||
(error): Err => ({
|
||||
code: "PARSING_ERROR",
|
||||
message: "Failed to parse response",
|
||||
description: "Invalid response format",
|
||||
detail: error instanceof Error ? error.message : String(error),
|
||||
}),
|
||||
);
|
||||
})
|
||||
.andThen((apiResult: any) => {
|
||||
if (apiResult.error || !apiResult.data) {
|
||||
return errAsync(
|
||||
apiResult.error || {
|
||||
code: "API_ERROR",
|
||||
message: "Failed to fetch notifications",
|
||||
description: "Invalid response data",
|
||||
detail: "Missing data in response",
|
||||
try {
|
||||
const result = await getNotificationsSQ(this.getFetchQueryInput());
|
||||
if (result?.error || !result?.data) {
|
||||
toast.error(
|
||||
result?.error?.message || "Failed to fetch notifications",
|
||||
{
|
||||
description:
|
||||
result?.error?.description || "Please try again later",
|
||||
},
|
||||
);
|
||||
return;
|
||||
}
|
||||
return okAsync(apiResult.data);
|
||||
});
|
||||
|
||||
result.match(
|
||||
(data) => {
|
||||
this.notifications = data.data as any;
|
||||
this.pagination.total = data.total;
|
||||
this.pagination.totalPages = data.totalPages;
|
||||
},
|
||||
(error) => {
|
||||
const errorMessage = error.message || "Failed to fetch notifications";
|
||||
toast.error(errorMessage, {
|
||||
description: error.description || "Please try again later",
|
||||
this.notifications = result.data.data as Notifications;
|
||||
this.pagination.total = result.data.total;
|
||||
this.pagination.totalPages = result.data.totalPages;
|
||||
} catch (error) {
|
||||
toast.error("Failed to fetch notifications", {
|
||||
description:
|
||||
error instanceof Error ? error.message : "Please try again later",
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
} finally {
|
||||
this.loading = false;
|
||||
}
|
||||
}
|
||||
|
||||
async markAsRead(notificationIds: number[]) {
|
||||
const result = await ResultAsync.fromPromise(
|
||||
get(apiClient).notifications["mark-read"].$put({
|
||||
json: { notificationIds },
|
||||
}),
|
||||
(error): Err => ({
|
||||
code: "NETWORK_ERROR",
|
||||
message: "Failed to mark as read",
|
||||
description: "Network request failed",
|
||||
detail: error instanceof Error ? error.message : String(error),
|
||||
}),
|
||||
)
|
||||
.andThen((response) => {
|
||||
if (!response.ok) {
|
||||
return errAsync({
|
||||
code: "API_ERROR",
|
||||
message: "Failed to mark as read",
|
||||
description: `Response failed with status ${response.status}`,
|
||||
detail: `HTTP ${response.status}`,
|
||||
});
|
||||
}
|
||||
return ResultAsync.fromPromise(
|
||||
response.json(),
|
||||
(error): Err => ({
|
||||
code: "PARSING_ERROR",
|
||||
message: "Failed to parse response",
|
||||
description: "Invalid response format",
|
||||
detail: error instanceof Error ? error.message : String(error),
|
||||
}),
|
||||
);
|
||||
})
|
||||
.andThen((apiResult: any) => {
|
||||
if (apiResult.error) {
|
||||
return errAsync(apiResult.error);
|
||||
}
|
||||
return okAsync(apiResult.data);
|
||||
});
|
||||
|
||||
result.match(
|
||||
async () => {
|
||||
await this.fetchNotifications();
|
||||
await this.fetchUnreadCount();
|
||||
},
|
||||
(error) => {
|
||||
const errorMessage = error.message || "Failed to mark as read";
|
||||
toast.error(errorMessage, {
|
||||
description: error.description || "Please try again later",
|
||||
});
|
||||
},
|
||||
);
|
||||
await this.runCommand(markReadSC, notificationIds, "Failed to mark as read", [
|
||||
() => this.fetchNotifications(),
|
||||
() => this.fetchUnreadCount(),
|
||||
]);
|
||||
}
|
||||
|
||||
async markAsUnread(notificationIds: number[]) {
|
||||
const result = await ResultAsync.fromPromise(
|
||||
get(apiClient).notifications["mark-unread"].$put({
|
||||
json: { notificationIds },
|
||||
}),
|
||||
(error): Err => ({
|
||||
code: "NETWORK_ERROR",
|
||||
message: "Failed to mark as unread",
|
||||
description: "Network request failed",
|
||||
detail: error instanceof Error ? error.message : String(error),
|
||||
}),
|
||||
)
|
||||
.andThen((response) => {
|
||||
if (!response.ok) {
|
||||
return errAsync({
|
||||
code: "API_ERROR",
|
||||
message: "Failed to mark as unread",
|
||||
description: `Response failed with status ${response.status}`,
|
||||
detail: `HTTP ${response.status}`,
|
||||
});
|
||||
}
|
||||
return ResultAsync.fromPromise(
|
||||
response.json(),
|
||||
(error): Err => ({
|
||||
code: "PARSING_ERROR",
|
||||
message: "Failed to parse response",
|
||||
description: "Invalid response format",
|
||||
detail: error instanceof Error ? error.message : String(error),
|
||||
}),
|
||||
);
|
||||
})
|
||||
.andThen((apiResult: any) => {
|
||||
if (apiResult.error) {
|
||||
return errAsync(apiResult.error);
|
||||
}
|
||||
return okAsync(apiResult.data);
|
||||
});
|
||||
|
||||
result.match(
|
||||
async () => {
|
||||
await this.fetchNotifications();
|
||||
await this.fetchUnreadCount();
|
||||
},
|
||||
(error) => {
|
||||
const errorMessage = error.message || "Failed to mark as unread";
|
||||
toast.error(errorMessage, {
|
||||
description: error.description || "Please try again later",
|
||||
});
|
||||
},
|
||||
await this.runCommand(
|
||||
markUnreadSC,
|
||||
notificationIds,
|
||||
"Failed to mark as unread",
|
||||
[() => this.fetchNotifications(), () => this.fetchUnreadCount()],
|
||||
);
|
||||
}
|
||||
|
||||
async archive(notificationIds: number[]) {
|
||||
const result = await ResultAsync.fromPromise(
|
||||
get(apiClient).notifications.archive.$put({
|
||||
json: { notificationIds },
|
||||
}),
|
||||
(error): Err => ({
|
||||
code: "NETWORK_ERROR",
|
||||
message: "Failed to archive",
|
||||
description: "Network request failed",
|
||||
detail: error instanceof Error ? error.message : String(error),
|
||||
}),
|
||||
)
|
||||
.andThen((response) => {
|
||||
if (!response.ok) {
|
||||
return errAsync({
|
||||
code: "API_ERROR",
|
||||
message: "Failed to archive",
|
||||
description: `Response failed with status ${response.status}`,
|
||||
detail: `HTTP ${response.status}`,
|
||||
});
|
||||
}
|
||||
return ResultAsync.fromPromise(
|
||||
response.json(),
|
||||
(error): Err => ({
|
||||
code: "PARSING_ERROR",
|
||||
message: "Failed to parse response",
|
||||
description: "Invalid response format",
|
||||
detail: error instanceof Error ? error.message : String(error),
|
||||
}),
|
||||
);
|
||||
})
|
||||
.andThen((apiResult: any) => {
|
||||
if (apiResult.error) {
|
||||
return errAsync(apiResult.error);
|
||||
}
|
||||
return okAsync(apiResult.data);
|
||||
});
|
||||
|
||||
result.match(
|
||||
async () => {
|
||||
await this.fetchNotifications();
|
||||
},
|
||||
(error) => {
|
||||
const errorMessage = error.message || "Failed to archive";
|
||||
toast.error(errorMessage, {
|
||||
description: error.description || "Please try again later",
|
||||
});
|
||||
},
|
||||
);
|
||||
await this.runCommand(archiveSC, notificationIds, "Failed to archive", [
|
||||
() => this.fetchNotifications(),
|
||||
]);
|
||||
}
|
||||
|
||||
async unarchive(notificationIds: number[]) {
|
||||
const result = await ResultAsync.fromPromise(
|
||||
get(apiClient).notifications.unarchive.$put({
|
||||
json: { notificationIds },
|
||||
}),
|
||||
(error): Err => ({
|
||||
code: "NETWORK_ERROR",
|
||||
message: "Failed to unarchive",
|
||||
description: "Network request failed",
|
||||
detail: error instanceof Error ? error.message : String(error),
|
||||
}),
|
||||
)
|
||||
.andThen((response) => {
|
||||
if (!response.ok) {
|
||||
return errAsync({
|
||||
code: "API_ERROR",
|
||||
message: "Failed to unarchive",
|
||||
description: `Response failed with status ${response.status}`,
|
||||
detail: `HTTP ${response.status}`,
|
||||
});
|
||||
}
|
||||
return ResultAsync.fromPromise(
|
||||
response.json(),
|
||||
(error): Err => ({
|
||||
code: "PARSING_ERROR",
|
||||
message: "Failed to parse response",
|
||||
description: "Invalid response format",
|
||||
detail: error instanceof Error ? error.message : String(error),
|
||||
}),
|
||||
);
|
||||
})
|
||||
.andThen((apiResult: any) => {
|
||||
if (apiResult.error) {
|
||||
return errAsync(apiResult.error);
|
||||
}
|
||||
return okAsync(apiResult.data);
|
||||
});
|
||||
|
||||
result.match(
|
||||
async () => {
|
||||
await this.fetchNotifications();
|
||||
},
|
||||
(error) => {
|
||||
const errorMessage = error.message || "Failed to unarchive";
|
||||
toast.error(errorMessage, {
|
||||
description: error.description || "Please try again later",
|
||||
});
|
||||
},
|
||||
);
|
||||
await this.runCommand(unarchiveSC, notificationIds, "Failed to unarchive", [
|
||||
() => this.fetchNotifications(),
|
||||
]);
|
||||
}
|
||||
|
||||
async deleteNotifications(notificationIds: number[]) {
|
||||
const result = await ResultAsync.fromPromise(
|
||||
get(apiClient).notifications.delete.$delete({
|
||||
json: { notificationIds },
|
||||
}),
|
||||
(error): Err => ({
|
||||
code: "NETWORK_ERROR",
|
||||
message: "Failed to delete",
|
||||
description: "Network request failed",
|
||||
detail: error instanceof Error ? error.message : String(error),
|
||||
}),
|
||||
)
|
||||
.andThen((response) => {
|
||||
if (!response.ok) {
|
||||
return errAsync({
|
||||
code: "API_ERROR",
|
||||
message: "Failed to delete",
|
||||
description: `Response failed with status ${response.status}`,
|
||||
detail: `HTTP ${response.status}`,
|
||||
});
|
||||
}
|
||||
return ResultAsync.fromPromise(
|
||||
response.json(),
|
||||
(error): Err => ({
|
||||
code: "PARSING_ERROR",
|
||||
message: "Failed to parse response",
|
||||
description: "Invalid response format",
|
||||
detail: error instanceof Error ? error.message : String(error),
|
||||
}),
|
||||
);
|
||||
})
|
||||
.andThen((apiResult: any) => {
|
||||
if (apiResult.error) {
|
||||
return errAsync(apiResult.error);
|
||||
}
|
||||
return okAsync(apiResult.data);
|
||||
});
|
||||
|
||||
result.match(
|
||||
async () => {
|
||||
await this.fetchNotifications();
|
||||
await this.fetchUnreadCount();
|
||||
},
|
||||
(error) => {
|
||||
const errorMessage = error.message || "Failed to delete";
|
||||
toast.error(errorMessage, {
|
||||
description: error.description || "Please try again later",
|
||||
});
|
||||
},
|
||||
await this.runCommand(
|
||||
deleteNotificationsSC,
|
||||
notificationIds,
|
||||
"Failed to delete",
|
||||
[() => this.fetchNotifications(), () => this.fetchUnreadCount()],
|
||||
);
|
||||
}
|
||||
|
||||
async markAllAsRead() {
|
||||
const result = await ResultAsync.fromPromise(
|
||||
get(apiClient).notifications["mark-all-read"].$put(),
|
||||
(error): Err => ({
|
||||
code: "NETWORK_ERROR",
|
||||
message: "Failed to mark all as read",
|
||||
description: "Network request failed",
|
||||
detail: error instanceof Error ? error.message : String(error),
|
||||
}),
|
||||
)
|
||||
.andThen((response) => {
|
||||
if (!response.ok) {
|
||||
return errAsync({
|
||||
code: "API_ERROR",
|
||||
message: "Failed to mark all as read",
|
||||
description: `Response failed with status ${response.status}`,
|
||||
detail: `HTTP ${response.status}`,
|
||||
try {
|
||||
const result = await markAllReadSC({});
|
||||
if (result?.error) {
|
||||
toast.error(result.error.message || "Failed to mark all as read", {
|
||||
description: result.error.description || "Please try again later",
|
||||
});
|
||||
return;
|
||||
}
|
||||
return ResultAsync.fromPromise(
|
||||
response.json(),
|
||||
(error): Err => ({
|
||||
code: "PARSING_ERROR",
|
||||
message: "Failed to parse response",
|
||||
description: "Invalid response format",
|
||||
detail: error instanceof Error ? error.message : String(error),
|
||||
}),
|
||||
);
|
||||
})
|
||||
.andThen((apiResult: any) => {
|
||||
if (apiResult.error) {
|
||||
return errAsync(apiResult.error);
|
||||
}
|
||||
return okAsync(apiResult.data);
|
||||
});
|
||||
|
||||
result.match(
|
||||
async () => {
|
||||
await this.fetchNotifications();
|
||||
await this.fetchUnreadCount();
|
||||
},
|
||||
(error) => {
|
||||
const errorMessage = error.message || "Failed to mark all as read";
|
||||
toast.error(errorMessage, {
|
||||
description: error.description || "Please try again later",
|
||||
} catch (error) {
|
||||
toast.error("Failed to mark all as read", {
|
||||
description:
|
||||
error instanceof Error ? error.message : "Please try again later",
|
||||
});
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
async fetchUnreadCount() {
|
||||
const result = await ResultAsync.fromPromise(
|
||||
get(apiClient).notifications["unread-count"].$get(),
|
||||
(error): Err => ({
|
||||
code: "NETWORK_ERROR",
|
||||
message: "Failed to fetch unread count",
|
||||
description: "Network request failed",
|
||||
detail: error instanceof Error ? error.message : String(error),
|
||||
}),
|
||||
)
|
||||
.andThen((response) => {
|
||||
if (!response.ok) {
|
||||
return errAsync({
|
||||
code: "API_ERROR",
|
||||
message: "Failed to fetch unread count",
|
||||
description: `Response failed with status ${response.status}`,
|
||||
detail: `HTTP ${response.status}`,
|
||||
});
|
||||
try {
|
||||
const result = await getUnreadCountSQ();
|
||||
if (result?.error) {
|
||||
return;
|
||||
}
|
||||
return ResultAsync.fromPromise(
|
||||
response.json(),
|
||||
(error): Err => ({
|
||||
code: "PARSING_ERROR",
|
||||
message: "Failed to parse response",
|
||||
description: "Invalid response format",
|
||||
detail: error instanceof Error ? error.message : String(error),
|
||||
}),
|
||||
);
|
||||
})
|
||||
.andThen((apiResult: any) => {
|
||||
if (apiResult.error) {
|
||||
return errAsync(apiResult.error);
|
||||
if (result?.data !== undefined && result?.data !== null) {
|
||||
this.unreadCount = result.data as number;
|
||||
}
|
||||
return okAsync(apiResult.data);
|
||||
});
|
||||
|
||||
result.match(
|
||||
(data) => {
|
||||
if (data !== undefined && data !== null) {
|
||||
this.unreadCount = data as number;
|
||||
} catch {
|
||||
// Intentionally silent - unread count is non-critical UI data.
|
||||
}
|
||||
},
|
||||
(error) => {
|
||||
// Silently fail for unread count - don't show toast as it's not critical
|
||||
console.error("Failed to fetch unread count:", error);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
// Helper methods
|
||||
toggleSelection(id: number) {
|
||||
if (this.selectedIds.has(id)) {
|
||||
this.selectedIds.delete(id);
|
||||
@@ -506,7 +205,7 @@ class NotificationViewModel {
|
||||
|
||||
setPageSize(pageSize: number) {
|
||||
this.pagination.pageSize = pageSize;
|
||||
this.pagination.page = 1; // Reset to first page
|
||||
this.pagination.page = 1;
|
||||
this.fetchNotifications();
|
||||
}
|
||||
|
||||
@@ -516,13 +215,13 @@ class NotificationViewModel {
|
||||
) {
|
||||
this.pagination.sortBy = sortBy;
|
||||
this.pagination.sortOrder = sortOrder;
|
||||
this.pagination.page = 1; // Reset to first page
|
||||
this.pagination.page = 1;
|
||||
this.fetchNotifications();
|
||||
}
|
||||
|
||||
setFilters(newFilters: Partial<ClientNotificationFilters>) {
|
||||
this.filters = { ...this.filters, ...newFilters };
|
||||
this.pagination.page = 1; // Reset to first page
|
||||
this.pagination.page = 1;
|
||||
this.fetchNotifications();
|
||||
}
|
||||
|
||||
|
||||
167
apps/main/src/lib/domains/notifications/notifications.remote.ts
Normal file
167
apps/main/src/lib/domains/notifications/notifications.remote.ts
Normal file
@@ -0,0 +1,167 @@
|
||||
import {
|
||||
bulkNotificationIdsSchema,
|
||||
getNotificationsSchema,
|
||||
} from "@pkg/logic/domains/notifications/data";
|
||||
import { getNotificationController } from "@pkg/logic/domains/notifications/controller";
|
||||
import type { FlowExecCtx } from "@pkg/logic/core/flow.execution.context";
|
||||
import { getFlowExecCtxForRemoteFuncs } from "$lib/core/server.utils";
|
||||
import { command, getRequestEvent, query } from "$app/server";
|
||||
import type { Err } from "@pkg/result";
|
||||
import * as v from "valibot";
|
||||
|
||||
const nc = getNotificationController();
|
||||
|
||||
export async function unauthorized(fctx: FlowExecCtx) {
|
||||
return {
|
||||
data: null,
|
||||
error: {
|
||||
flowId: fctx.flowId,
|
||||
code: "UNAUTHORIZED",
|
||||
message: "User not authenticated",
|
||||
description: "Please log in",
|
||||
detail: "No user found in request locals",
|
||||
} as Err,
|
||||
};
|
||||
}
|
||||
|
||||
export const getNotificationsSQ = query(
|
||||
getNotificationsSchema,
|
||||
async (input) => {
|
||||
const event = getRequestEvent();
|
||||
const fctx = await getFlowExecCtxForRemoteFuncs(event.locals);
|
||||
if (!fctx.userId) {
|
||||
return unauthorized(fctx);
|
||||
}
|
||||
const res = await nc.getNotifications(
|
||||
fctx,
|
||||
{ ...input.filters, userId: fctx.userId },
|
||||
input.pagination,
|
||||
);
|
||||
return res.isOk()
|
||||
? { data: res.value, error: null }
|
||||
: { data: null, error: res.error };
|
||||
},
|
||||
);
|
||||
|
||||
export const markReadSC = command(
|
||||
bulkNotificationIdsSchema,
|
||||
async (payload) => {
|
||||
const event = getRequestEvent();
|
||||
const fctx = await getFlowExecCtxForRemoteFuncs(event.locals);
|
||||
if (!fctx.userId) {
|
||||
return unauthorized(fctx);
|
||||
}
|
||||
|
||||
const res = await nc.markAsRead(
|
||||
fctx,
|
||||
[...payload.notificationIds],
|
||||
fctx.userId,
|
||||
);
|
||||
return res.isOk()
|
||||
? { data: res.value, error: null }
|
||||
: { data: null, error: res.error };
|
||||
},
|
||||
);
|
||||
|
||||
export const markUnreadSC = command(
|
||||
bulkNotificationIdsSchema,
|
||||
async (payload) => {
|
||||
const event = getRequestEvent();
|
||||
const fctx = await getFlowExecCtxForRemoteFuncs(event.locals);
|
||||
if (!fctx.userId) {
|
||||
return unauthorized(fctx);
|
||||
}
|
||||
|
||||
const res = await nc.markAsUnread(
|
||||
fctx,
|
||||
[...payload.notificationIds],
|
||||
fctx.userId,
|
||||
);
|
||||
return res.isOk()
|
||||
? { data: res.value, error: null }
|
||||
: { data: null, error: res.error };
|
||||
},
|
||||
);
|
||||
|
||||
export const archiveSC = command(bulkNotificationIdsSchema, async (payload) => {
|
||||
const event = getRequestEvent();
|
||||
const fctx = await getFlowExecCtxForRemoteFuncs(event.locals);
|
||||
if (!fctx.userId) {
|
||||
return unauthorized(fctx);
|
||||
}
|
||||
|
||||
const res = await nc.archive(
|
||||
fctx,
|
||||
[...payload.notificationIds],
|
||||
fctx.userId,
|
||||
);
|
||||
return res.isOk()
|
||||
? { data: res.value, error: null }
|
||||
: { data: null, error: res.error };
|
||||
});
|
||||
|
||||
export const unarchiveSC = command(
|
||||
bulkNotificationIdsSchema,
|
||||
async (payload) => {
|
||||
const event = getRequestEvent();
|
||||
const fctx = await getFlowExecCtxForRemoteFuncs(event.locals);
|
||||
if (!fctx.userId) {
|
||||
return unauthorized(fctx);
|
||||
}
|
||||
|
||||
const res = await nc.unarchive(
|
||||
fctx,
|
||||
[...payload.notificationIds],
|
||||
fctx.userId,
|
||||
);
|
||||
return res.isOk()
|
||||
? { data: res.value, error: null }
|
||||
: { data: null, error: res.error };
|
||||
},
|
||||
);
|
||||
|
||||
export const deleteNotificationsSC = command(
|
||||
bulkNotificationIdsSchema,
|
||||
async (payload) => {
|
||||
const event = getRequestEvent();
|
||||
const fctx = await getFlowExecCtxForRemoteFuncs(event.locals);
|
||||
if (!fctx.userId) {
|
||||
return unauthorized(fctx);
|
||||
}
|
||||
|
||||
const res = await nc.deleteNotifications(
|
||||
fctx,
|
||||
[...payload.notificationIds],
|
||||
fctx.userId,
|
||||
);
|
||||
return res.isOk()
|
||||
? { data: res.value, error: null }
|
||||
: { data: null, error: res.error };
|
||||
},
|
||||
);
|
||||
|
||||
export const markAllReadSC = command(v.object({}), async () => {
|
||||
const event = getRequestEvent();
|
||||
const fctx = await getFlowExecCtxForRemoteFuncs(event.locals);
|
||||
if (!fctx.userId) {
|
||||
return unauthorized(fctx);
|
||||
}
|
||||
|
||||
const res = await nc.markAllAsRead(fctx, fctx.userId);
|
||||
return res.isOk()
|
||||
? { data: res.value, error: null }
|
||||
: { data: null, error: res.error };
|
||||
});
|
||||
|
||||
export const getUnreadCountSQ = query(async () => {
|
||||
const event = getRequestEvent();
|
||||
const fctx = await getFlowExecCtxForRemoteFuncs(event.locals);
|
||||
if (!fctx.userId) {
|
||||
return unauthorized(fctx);
|
||||
}
|
||||
|
||||
const res = await nc.getUnreadCount(fctx, fctx.userId);
|
||||
return res.isOk()
|
||||
? { data: res.value, error: null }
|
||||
: { data: null, error: res.error };
|
||||
});
|
||||
@@ -39,6 +39,19 @@ export type NotificationFilters = v.InferOutput<
|
||||
typeof notificationFiltersSchema
|
||||
>;
|
||||
|
||||
export type NotificationsQueryInput = {
|
||||
isRead?: boolean;
|
||||
isArchived?: boolean;
|
||||
type?: string;
|
||||
category?: string;
|
||||
priority?: string;
|
||||
search?: string;
|
||||
page?: number;
|
||||
pageSize?: number;
|
||||
sortBy?: string;
|
||||
sortOrder?: string;
|
||||
};
|
||||
|
||||
// Pagination options schema
|
||||
export const paginationOptionsSchema = v.object({
|
||||
page: v.pipe(v.number(), v.integer()),
|
||||
|
||||
Reference in New Issue
Block a user