notification system implemented
need refinements
This commit is contained in:
1 parent
612259f020
commit
1143f8ca02
33 files changed
+2221
-222
No files matched your search
@@ -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,
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
@@ -83,7 +83,7 @@ export const prioritySchema = z.enum(["low", "medium", "high"]);
|
||||
export const goalStatusSchema = z.enum(["active", "completed", "abandoned"]);
|
||||
|
||||
export const fitnessGoalSchema = z.object({
|
||||
userId: z.string().min(1, "User ID is required"),
|
||||
userId: z.string().min(1, "User ID is required").optional(), // Optional for authenticated requests
|
||||
goalType: goalTypeSchema,
|
||||
title: z.string().min(1, "Title is required").max(100),
|
||||
description: z.string().max(500).optional(),
|
||||
|
||||
Reference in new issue
Block a user