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

+92
View File
@@ -6,6 +6,7 @@ import {
Attendance,
Recommendation,
FitnessGoal,
Notification,
DatabaseConfig,
} from "./types";
import {
@@ -16,6 +17,7 @@ import {
attendance,
recommendations,
fitnessGoals,
notifications,
eq,
and,
desc,
@@ -1419,4 +1421,94 @@ export class DrizzleDatabase implements IDatabase {
updatedAt: new Date(row.updatedAt as number | Date),
};
}
// Notification operations
async createNotification(
notification: Omit<Notification, "createdAt">,
): Promise<Notification> {
const newNotification = {
...notification,
createdAt: new Date(),
};
const result = await this.db.insert(notifications).values(newNotification);
log.debug("Notification created", { id: notification.id });
return newNotification;
}
async getNotificationsByUserId(userId: string): Promise<Notification[]> {
const rows = await this.db
.select()
.from(notifications)
.where(eq(notifications.userId, userId))
.orderBy(desc(notifications.createdAt))
.all();
return rows.map((row) => this.mapNotification(row));
}
async getUnreadNotificationCount(userId: string): Promise<number> {
const result = await this.db
.select({ count: sql<number>`count(*)` })
.from(notifications)
.where(
and(eq(notifications.userId, userId), eq(notifications.read, false)),
);
return Number(result[0]?.count || 0);
}
async markNotificationAsRead(id: string): Promise<Notification | null> {
await this.db
.update(notifications)
.set({ read: true })
.where(eq(notifications.id, id));
const rows = await this.db
.select()
.from(notifications)
.where(eq(notifications.id, id));
if (rows.length === 0) return null;
return this.mapNotification(rows[0]);
}
async markAllNotificationsAsRead(userId: string): Promise<void> {
await this.db
.update(notifications)
.set({ read: true })
.where(
and(eq(notifications.userId, userId), eq(notifications.read, false)),
);
log.debug("All notifications marked as read", { userId });
}
async deleteNotification(id: string): Promise<boolean> {
const result = await this.db
.delete(notifications)
.where(eq(notifications.id, id));
return result.changes > 0;
}
private mapNotification(row: Record<string, unknown>): Notification {
const createdAtValue = row.createdAt as number | Date;
const createdAt =
typeof createdAtValue === "number"
? new Date(createdAtValue * 1000) // SQLite stores in seconds, convert to milliseconds
: createdAtValue;
return {
id: String(row.id),
userId: String(row.userId || row.user_id),
title: String(row.title),
message: String(row.message),
type: String(row.type) as Notification["type"],
read: Boolean(row.read),
createdAt,
};
}
}
+19 -1
View File
@@ -5,6 +5,7 @@ import {
Attendance,
Recommendation,
FitnessGoal,
Notification,
} from "@fitai/shared";
import type { SortConfig, FilterCondition } from "../filtering";
@@ -15,7 +16,14 @@ export interface User extends SharedUser {
password: string;
}
export type { Client, FitnessProfile, Attendance, Recommendation, FitnessGoal };
export type {
Client,
FitnessProfile,
Attendance,
Recommendation,
FitnessGoal,
Notification,
};
// Database Interface - allows us to swap implementations
export interface IDatabase {
@@ -163,6 +171,16 @@ export interface IDatabase {
): Promise<FitnessGoal | null>;
completeGoal(id: string): Promise<FitnessGoal | null>;
// Notification operations
createNotification(
notification: Omit<Notification, "createdAt">,
): Promise<Notification>;
getNotificationsByUserId(userId: string): Promise<Notification[]>;
getUnreadNotificationCount(userId: string): Promise<number>;
markNotificationAsRead(id: string): Promise<Notification | null>;
markAllNotificationsAsRead(userId: string): Promise<void>;
deleteNotification(id: string): Promise<boolean>;
// Dashboard operations
getDashboardStats(): Promise<{
totalUsers: number;