Уведомления: persistent + live (push-to-invalidate → pull)
Новая система уведомлений поверх готовой SSE-шины: события пишутся в БД,
живут 72 часа и чистятся (при чтении списка + фоновой задачей), всплывают
сверху экрана в момент прихода, доступны через колокольчик в правом верхнем
углу (бейдж непрочитанных + панель).
Типы: приглашение в группу (→ /group), старт/финиш партии участникам кроме
инициатора (→ /match/{id}). Титулы — готовый хелпер-задел (не подключён, т.к.
выдача титулов игрокам ещё не реализована).
Бэкенд: модель Notification + миграция 0008 (идемпотентная), notification_service,
notify.notifications_changed, роутер /api/notifications (GET + /read), триггеры
в groups/matches, фоновая чистка в lifespan, защита hub.publish от закрытого loop.
Фронт: useNotifications/useMarkNotificationsRead, NotificationBell/Panel/Toaster,
перекомпоновка top-bar, стили; useServerEvents знает тип notifications.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -16,6 +16,7 @@ export const qk = {
|
||||
match: (id: number) => ["match", id] as const,
|
||||
authConfig: ["authConfig"] as const,
|
||||
invitations: ["invitations"] as const,
|
||||
notifications: ["notifications"] as const,
|
||||
devUsers: ["devUsers"] as const,
|
||||
adminUsers: ["adminUsers"] as const,
|
||||
adminGroups: ["adminGroups"] as const,
|
||||
|
||||
Vendored
+116
@@ -662,6 +662,40 @@ export interface paths {
|
||||
patch?: never;
|
||||
trace?: never;
|
||||
};
|
||||
"/api/notifications": {
|
||||
parameters: {
|
||||
query?: never;
|
||||
header?: never;
|
||||
path?: never;
|
||||
cookie?: never;
|
||||
};
|
||||
/** My Notifications */
|
||||
get: operations["my_notifications_api_notifications_get"];
|
||||
put?: never;
|
||||
post?: never;
|
||||
delete?: never;
|
||||
options?: never;
|
||||
head?: never;
|
||||
patch?: never;
|
||||
trace?: never;
|
||||
};
|
||||
"/api/notifications/read": {
|
||||
parameters: {
|
||||
query?: never;
|
||||
header?: never;
|
||||
path?: never;
|
||||
cookie?: never;
|
||||
};
|
||||
get?: never;
|
||||
put?: never;
|
||||
/** Mark Read */
|
||||
post: operations["mark_read_api_notifications_read_post"];
|
||||
delete?: never;
|
||||
options?: never;
|
||||
head?: never;
|
||||
patch?: never;
|
||||
trace?: never;
|
||||
};
|
||||
"/api/admin/auth/login": {
|
||||
parameters: {
|
||||
query?: never;
|
||||
@@ -1725,6 +1759,35 @@ export interface components {
|
||||
/** Nickname */
|
||||
nickname: string;
|
||||
};
|
||||
/** NotificationList */
|
||||
NotificationList: {
|
||||
/** Items */
|
||||
items: components["schemas"]["NotificationRead"][];
|
||||
/** Unread Count */
|
||||
unread_count: number;
|
||||
};
|
||||
/** NotificationMarkRead */
|
||||
NotificationMarkRead: {
|
||||
/** Ids */
|
||||
ids?: number[] | null;
|
||||
};
|
||||
/** NotificationRead */
|
||||
NotificationRead: {
|
||||
/** Id */
|
||||
id: number;
|
||||
/** Type */
|
||||
type: string;
|
||||
/** Title */
|
||||
title: string;
|
||||
/** Body */
|
||||
body?: string | null;
|
||||
/** Link */
|
||||
link?: string | null;
|
||||
/** Read At */
|
||||
read_at?: string | null;
|
||||
/** Created At */
|
||||
created_at: string;
|
||||
};
|
||||
/** OkResponse */
|
||||
OkResponse: {
|
||||
/**
|
||||
@@ -3232,6 +3295,59 @@ export interface operations {
|
||||
};
|
||||
};
|
||||
};
|
||||
my_notifications_api_notifications_get: {
|
||||
parameters: {
|
||||
query?: never;
|
||||
header?: never;
|
||||
path?: never;
|
||||
cookie?: never;
|
||||
};
|
||||
requestBody?: never;
|
||||
responses: {
|
||||
/** @description Successful Response */
|
||||
200: {
|
||||
headers: {
|
||||
[name: string]: unknown;
|
||||
};
|
||||
content: {
|
||||
"application/json": components["schemas"]["NotificationList"];
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
mark_read_api_notifications_read_post: {
|
||||
parameters: {
|
||||
query?: never;
|
||||
header?: never;
|
||||
path?: never;
|
||||
cookie?: never;
|
||||
};
|
||||
requestBody: {
|
||||
content: {
|
||||
"application/json": components["schemas"]["NotificationMarkRead"];
|
||||
};
|
||||
};
|
||||
responses: {
|
||||
/** @description Successful Response */
|
||||
200: {
|
||||
headers: {
|
||||
[name: string]: unknown;
|
||||
};
|
||||
content: {
|
||||
"application/json": components["schemas"]["OkResponse"];
|
||||
};
|
||||
};
|
||||
/** @description Validation Error */
|
||||
422: {
|
||||
headers: {
|
||||
[name: string]: unknown;
|
||||
};
|
||||
content: {
|
||||
"application/json": components["schemas"]["HTTPValidationError"];
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
admin_login_api_admin_auth_login_post: {
|
||||
parameters: {
|
||||
query?: never;
|
||||
|
||||
Reference in New Issue
Block a user