feat: implement notification system

This commit is contained in:
Face 2025-06-11 18:37:03 +03:00
parent de3f8a4929
commit e61c41e414
19 changed files with 883 additions and 3196 deletions

View file

@ -3,6 +3,7 @@ import { sql } from "drizzle-orm";
export const transactionTypeEnum = pgEnum('transaction_type', ['BUY', 'SELL', 'TRANSFER_IN', 'TRANSFER_OUT']);
export const predictionMarketEnum = pgEnum('prediction_market_status', ['ACTIVE', 'RESOLVED', 'CANCELLED']);
export const notificationTypeEnum = pgEnum('notification_type', ['HOPIUM', 'SYSTEM', 'TRANSFER', 'RUG_PULL']);
export const user = pgTable("user", {
id: serial("id").primaryKey(),
@ -226,4 +227,21 @@ export const accountDeletionRequest = pgTable("account_deletion_request", {
.on(table.userId)
.where(sql`is_processed = false`),
};
});
export const notifications = pgTable("notification", {
id: serial("id").primaryKey(),
userId: integer("user_id").notNull().references(() => user.id, { onDelete: "cascade" }),
type: notificationTypeEnum("type").notNull(),
title: varchar("title", { length: 200 }).notNull(),
message: text("message").notNull(),
isRead: boolean("is_read").notNull().default(false),
createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
}, (table) => {
return {
userIdIdx: index("notification_user_id_idx").on(table.userId),
typeIdx: index("notification_type_idx").on(table.type),
isReadIdx: index("notification_is_read_idx").on(table.isRead),
createdAtIdx: index("notification_created_at_idx").on(table.createdAt),
};
});

View file

@ -2,6 +2,8 @@ import { db } from '$lib/server/db';
import { predictionQuestion, predictionBet, user, accountDeletionRequest, session, account, promoCodeRedemption, userPortfolio, commentLike, comment, transaction, coin } from '$lib/server/db/schema';
import { eq, and, lte, isNull } from 'drizzle-orm';
import { resolveQuestion, getRugplayData } from '$lib/server/ai';
import { createNotification } from '$lib/server/notification';
import { formatValue } from '$lib/utils';
export async function resolveExpiredQuestions() {
const now = new Date();
@ -68,6 +70,13 @@ export async function resolveExpiredQuestions() {
? Number(question.totalYesAmount)
: Number(question.totalNoAmount);
const notificationsToCreate: Array<{
userId: number;
amount: number;
winnings: number;
won: boolean;
}> = [];
for (const bet of bets) {
const won = bet.side === resolution.resolution;
@ -101,6 +110,32 @@ export async function resolveExpiredQuestions() {
.where(eq(user.id, bet.userId));
}
}
if (bet.userId !== null) {
notificationsToCreate.push({
userId: bet.userId,
amount: Number(bet.amount),
winnings,
won
});
}
}
// Create notifications for all users who had bets
for (const notifData of notificationsToCreate) {
const { userId, amount, winnings, won } = notifData;
const title = won ? 'Prediction won! 🎉' : 'Prediction lost ;(';
const message = won
? `You won ${formatValue(winnings)} on "${question.question}"`
: `You lost ${formatValue(amount)} on "${question.question}"`;
await createNotification(
userId.toString(),
'HOPIUM',
title,
message,
);
}
});

View file

@ -0,0 +1,36 @@
import { db } from './db';
import { notifications, notificationTypeEnum } from './db/schema';
import { redis } from './redis';
export type NotificationType = typeof notificationTypeEnum.enumValues[number];
export async function createNotification(
userId: string,
type: NotificationType,
title: string,
message: string,
): Promise<void> {
await db.insert(notifications).values({
userId: parseInt(userId),
type,
title,
message
});
try {
const channel = `notifications:${userId}`;
const payload = {
type: 'notification',
timestamp: new Date().toISOString(),
userId,
notificationType: type,
title,
message,
};
await redis.publish(channel, JSON.stringify(payload));
} catch (error) {
console.error('Failed to send notification via Redis:', error);
}
}