notification system implemented

need refinements
This commit is contained in:
dimitar committed 2026-03-11 06:07:16 +01:00
1 parent 612259f020
commit 1143f8ca02
33 files changed
+2221 -222

No files matched your search

@@ -0,0 +1,105 @@
import { NextRequest, NextResponse } from "next/server";
import { auth } from "@clerk/nextjs/server";
import { getDatabase } from "@/lib/database";
import log from "@/lib/logger";
/**
* PUT /api/notifications/[id]
* Mark a notification as read
*/
export async function PUT(
req: NextRequest,
{ params }: { params: Promise<{ id: string }> },
) {
try {
const { userId } = await auth();
if (!userId) {
return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
}
const { id } = await params;
const db = await getDatabase();
// Verify the notification belongs to the user
const notifications = await db.getNotificationsByUserId(userId);
const notification = notifications.find((n) => n.id === id);
if (!notification) {
return NextResponse.json(
{ error: "Notification not found" },
{ status: 404 },
);
}
const updatedNotification = await db.markNotificationAsRead(id);
return NextResponse.json({
success: true,
data: updatedNotification,
meta: {
timestamp: new Date().toISOString(),
},
});
} catch (error) {
log.error("Failed to mark notification as read", error);
return NextResponse.json(
{ error: "Internal server error" },
{ status: 500 },
);
}
}
/**
* DELETE /api/notifications/[id]
* Delete a notification
*/
export async function DELETE(
req: NextRequest,
{ params }: { params: Promise<{ id: string }> },
) {
try {
const { userId } = await auth();
if (!userId) {
return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
}
const { id } = await params;
const db = await getDatabase();
// Verify the notification belongs to the user
const notifications = await db.getNotificationsByUserId(userId);
const notification = notifications.find((n) => n.id === id);
if (!notification) {
return NextResponse.json(
{ error: "Notification not found" },
{ status: 404 },
);
}
const deleted = await db.deleteNotification(id);
if (!deleted) {
return NextResponse.json(
{ error: "Failed to delete notification" },
{ status: 500 },
);
}
return NextResponse.json({
success: true,
data: { id },
meta: {
timestamp: new Date().toISOString(),
},
});
} catch (error) {
log.error("Failed to delete notification", error);
return NextResponse.json(
{ error: "Internal server error" },
{ status: 500 },
);
}
}
@@ -0,0 +1,37 @@
import { NextRequest, NextResponse } from "next/server";
import { auth } from "@clerk/nextjs/server";
import { getDatabase } from "@/lib/database";
import log from "@/lib/logger";
/**
* POST /api/notifications/mark-all-read
* Mark all notifications as read for the authenticated user
*/
export async function POST(req: NextRequest) {
try {
const { userId } = await auth();
if (!userId) {
return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
}
const db = await getDatabase();
await db.markAllNotificationsAsRead(userId);
log.info("All notifications marked as read", { userId });
return NextResponse.json({
success: true,
data: { message: "All notifications marked as read" },
meta: {
timestamp: new Date().toISOString(),
},
});
} catch (error) {
log.error("Failed to mark all notifications as read", error);
return NextResponse.json(
{ error: "Internal server error" },
{ status: 500 },
);
}
}
@@ -0,0 +1,116 @@
import { NextRequest, NextResponse } from "next/server";
import { auth } from "@clerk/nextjs/server";
import { getDatabase } from "@/lib/database";
import log from "@/lib/logger";
/**
* GET /api/notifications
* Get all notifications for the authenticated user
*/
export async function GET(req: NextRequest) {
try {
const { userId } = await auth();
if (!userId) {
log.warn("Unauthorized notification fetch attempt");
return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
}
log.debug("Fetching notifications for user", { userId });
const db = await getDatabase();
const notifications = await db.getNotificationsByUserId(userId);
log.debug("Notifications fetched successfully", {
userId,
count: notifications.length,
});
return NextResponse.json({
success: true,
data: notifications,
meta: {
timestamp: new Date().toISOString(),
count: notifications.length,
},
});
} catch (error) {
log.error("Failed to fetch notifications", error);
return NextResponse.json(
{ error: "Internal server error" },
{ status: 500 },
);
}
}
/**
* POST /api/notifications
* Create a new notification (admin/system only)
*/
export async function POST(req: NextRequest) {
try {
const { userId } = await auth();
if (!userId) {
return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
}
const body = await req.json();
const { targetUserId, title, message, type } = body;
if (!targetUserId || !title || !message || !type) {
return NextResponse.json(
{
error: "Missing required fields: targetUserId, title, message, type",
},
{ status: 400 },
);
}
// Validate notification type
const validTypes = [
"payment_reminder",
"attendance",
"promotion",
"system",
];
if (!validTypes.includes(type)) {
return NextResponse.json(
{
error: `Invalid notification type. Must be one of: ${validTypes.join(", ")}`,
},
{ status: 400 },
);
}
const db = await getDatabase();
const notification = await db.createNotification({
id: crypto.randomUUID(),
userId: targetUserId,
title,
message,
type,
read: false,
});
log.info("Notification created", {
id: notification.id,
targetUserId,
type,
});
return NextResponse.json({
success: true,
data: notification,
meta: {
timestamp: new Date().toISOString(),
},
});
} catch (error) {
log.error("Failed to create notification", error);
return NextResponse.json(
{ error: "Internal server error" },
{ status: 500 },
);
}
}
@@ -0,0 +1,71 @@
import { NextRequest, NextResponse } from "next/server";
import { auth } from "@clerk/nextjs/server";
import { getDatabase } from "@/lib/database";
import log from "@/lib/logger";
/**
* POST /api/notifications/save-token
* Save the user's Expo push notification token
*/
export async function POST(req: NextRequest) {
try {
const { userId } = await auth();
if (!userId) {
return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
}
const body = await req.json();
const { expoPushToken, deviceType } = body;
if (!expoPushToken) {
return NextResponse.json(
{ error: "Missing required field: expoPushToken" },
{ status: 400 },
);
}
// Validate device type
if (deviceType && !["ios", "android"].includes(deviceType)) {
return NextResponse.json(
{ error: "Invalid deviceType. Must be 'ios' or 'android'" },
{ status: 400 },
);
}
const db = await getDatabase();
// Update user with push token
const updatedUser = await db.updateUser(userId, {
expoPushToken,
deviceType: deviceType || undefined,
});
if (!updatedUser) {
return NextResponse.json({ error: "User not found" }, { status: 404 });
}
log.info("Push token saved", {
userId,
deviceType,
tokenPrefix: expoPushToken.substring(0, 20) + "...",
});
return NextResponse.json({
success: true,
data: {
message: "Push token saved successfully",
deviceType: updatedUser.deviceType,
},
meta: {
timestamp: new Date().toISOString(),
},
});
} catch (error) {
log.error("Failed to save push token", error);
return NextResponse.json(
{ error: "Internal server error" },
{ status: 500 },
);
}
}
@@ -0,0 +1,35 @@
import { NextRequest, NextResponse } from "next/server";
import { auth } from "@clerk/nextjs/server";
import { getDatabase } from "@/lib/database";
import log from "@/lib/logger";
/**
* GET /api/notifications/unread-count
* Get count of unread notifications for the authenticated user
*/
export async function GET(req: NextRequest) {
try {
const { userId } = await auth();
if (!userId) {
return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
}
const db = await getDatabase();
const count = await db.getUnreadNotificationCount(userId);
return NextResponse.json({
success: true,
data: { count },
meta: {
timestamp: new Date().toISOString(),
},
});
} catch (error) {
log.error("Failed to fetch unread notification count", error);
return NextResponse.json(
{ error: "Internal server error" },
{ status: 500 },
);
}
}