Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 24 additions & 0 deletions app-server/src/notifications/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ use crate::cache::{Cache, CacheTrait, keys::USAGE_WARNING_SEND_LOCK_KEY};
use crate::ch::notifications::CHNotification;
use crate::ch::service::ClickhouseService;
use crate::db::DB;
use crate::mq::utils::mq_max_payload;
use crate::mq::{MessageQueue, MessageQueueTrait};
use crate::reports::email_template::NoteworthyEvent;
use crate::worker::{HandlerError, MessageHandler};
Expand Down Expand Up @@ -288,6 +289,29 @@ impl MessageHandler for NotificationHandler {
notifications: message.notifications.clone(),
};

let serialized_size = match serde_json::to_vec(&delivery) {
Ok(v) => v.len(),
Err(e) => {
failures += 1;
log::error!(
"[Notifications] Failed to serialize delivery message: {:?}, target: {:?}",
e,
delivery.target,
);
continue;
}
};
if serialized_size >= mq_max_payload() {
failures += 1;
log::error!(
"[Notifications] MQ payload limit exceeded for delivery message. \
payload size: [{}], target: {:?}",
serialized_size,
delivery.target,
);
continue;
}

if let Err(e) = push_to_deliveries_queue(delivery, self.queue.clone()).await {
failures += 1;
log::error!("[Notifications] Failed to push delivery message: {:?}", e);
Expand Down
61 changes: 61 additions & 0 deletions frontend/app/api/workspaces/[workspaceId]/notifications/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import { type NextRequest, NextResponse } from "next/server";
import { getServerSession } from "next-auth";

import { getWebNotifications, markNotificationAsRead } from "@/lib/actions/notifications";
import { authOptions } from "@/lib/auth";
import { isProjectInWorkspace } from "@/lib/authorization";

export async function GET(request: NextRequest, props: { params: Promise<{ workspaceId: string }> }) {
const { workspaceId } = await props.params;

try {
const session = await getServerSession(authOptions);
const userId = session?.user?.id;
if (!userId) {
return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
}
const projectId = request.nextUrl.searchParams.get("projectId");
if (!projectId) {
return NextResponse.json({ error: "projectId query parameter is required" }, { status: 400 });
}
if (!(await isProjectInWorkspace(projectId, workspaceId))) {
return NextResponse.json({ error: "Project does not belong to this workspace" }, { status: 400 });
}
const result = await getWebNotifications({ userId, projectId });
return NextResponse.json(result);
} catch (error) {
console.error(error);
return NextResponse.json(
{ error: error instanceof Error ? error.message : "Failed to fetch notifications." },
{ status: 500 }
);
}
}

export async function POST(request: NextRequest, props: { params: Promise<{ workspaceId: string }> }) {
const { workspaceId } = await props.params;

try {
const session = await getServerSession(authOptions);
const userId = session?.user?.id;
if (!userId) {
return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
}
const body = await request.json();
const { notificationId, projectId } = body;
if (!notificationId || !projectId) {
return NextResponse.json({ error: "notificationId and projectId are required" }, { status: 400 });
}
if (!(await isProjectInWorkspace(projectId, workspaceId))) {
return NextResponse.json({ error: "Project does not belong to this workspace" }, { status: 400 });
}
await markNotificationAsRead({ userId, notificationId, projectId });
return NextResponse.json({ success: true });
} catch (error) {
console.error(error);
return NextResponse.json(
{ error: error instanceof Error ? error.message : "Failed to mark notification as read." },
{ status: 500 }
);
}
}
4 changes: 3 additions & 1 deletion frontend/app/project/[projectId]/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { type ReactNode } from "react";
import PostHogClient from "@/app/posthog";
import PostHogIdentifier from "@/app/posthog-identifier";
import SessionSyncProvider from "@/components/auth/session-sync-provider";
import NotificationPanel from "@/components/notifications/notification-panel";
import ProjectSidebar from "@/components/project/sidebar";
import ProjectUsageBanner from "@/components/project/usage-banner";
import { SidebarInset, SidebarProvider } from "@/components/ui/sidebar";
Expand Down Expand Up @@ -56,7 +57,8 @@ export default async function ProjectIdLayout(props: { children: ReactNode; para
<div className="fixed inset-0 flex overflow-clip md:pt-2 bg-sidebar">
<SidebarProvider cookieName={projectSidebarCookieName} className="bg-sidebar" defaultOpen={defaultOpen}>
<ProjectSidebar details={projectDetails} />
<SidebarInset className="flex flex-col h-[calc(100%-8px)]! border-l border-t flex-1 md:rounded-tl-lg overflow-hidden">
<SidebarInset className="relative flex flex-col h-[calc(100%-8px)]! border-l border-t flex-1 md:rounded-tl-lg overflow-hidden">
<NotificationPanel />
{showBanner && <ProjectUsageBanner details={projectDetails} />}
{children}
</SidebarInset>
Expand Down
Loading
Loading