feat(notifications): add real-time notification system
Add database tables for notifications and WebSocket tickets Implement REST API and WebSocket server for real-time delivery Add NotificationBell component with dropdown and unread badge Trigger alerts for comments, reactions, and AI moderation results
This commit is contained in:
@@ -1214,6 +1214,70 @@ ALTER TABLE entity_discussion_comments
|
||||
entity_type IN ('pokemon', 'items', 'recipes', 'habitats', 'ancient-artifacts')
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS notifications (
|
||||
id integer GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
|
||||
recipient_user_id integer NOT NULL REFERENCES users(id) ON DELETE CASCADE,
|
||||
actor_user_id integer REFERENCES users(id) ON DELETE SET NULL,
|
||||
type text NOT NULL CHECK (
|
||||
type IN (
|
||||
'life_post_comment',
|
||||
'life_comment_reply',
|
||||
'discussion_comment_reply',
|
||||
'life_post_reaction',
|
||||
'moderation_result'
|
||||
)
|
||||
),
|
||||
life_post_id integer REFERENCES life_posts(id) ON DELETE CASCADE,
|
||||
life_comment_id integer REFERENCES life_post_comments(id) ON DELETE CASCADE,
|
||||
parent_life_comment_id integer REFERENCES life_post_comments(id) ON DELETE SET NULL,
|
||||
discussion_comment_id integer REFERENCES entity_discussion_comments(id) ON DELETE CASCADE,
|
||||
parent_discussion_comment_id integer REFERENCES entity_discussion_comments(id) ON DELETE SET NULL,
|
||||
entity_type text CHECK (
|
||||
entity_type IS NULL OR entity_type IN ('pokemon', 'items', 'recipes', 'habitats', 'ancient-artifacts')
|
||||
),
|
||||
entity_id integer,
|
||||
reaction_type text CHECK (reaction_type IS NULL OR reaction_type IN ('like', 'helpful', 'fun', 'thanks')),
|
||||
moderation_status text CHECK (moderation_status IS NULL OR moderation_status IN ('approved', 'rejected', 'failed')),
|
||||
read_at timestamptz,
|
||||
created_at timestamptz NOT NULL DEFAULT now(),
|
||||
updated_at timestamptz NOT NULL DEFAULT now()
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS notifications_recipient_created_idx
|
||||
ON notifications(recipient_user_id, created_at DESC, id DESC);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS notifications_recipient_unread_idx
|
||||
ON notifications(recipient_user_id, created_at DESC, id DESC)
|
||||
WHERE read_at IS NULL;
|
||||
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS notifications_life_post_comment_unique_idx
|
||||
ON notifications(recipient_user_id, life_comment_id)
|
||||
WHERE type = 'life_post_comment' AND life_comment_id IS NOT NULL;
|
||||
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS notifications_life_comment_reply_unique_idx
|
||||
ON notifications(recipient_user_id, life_comment_id)
|
||||
WHERE type = 'life_comment_reply' AND life_comment_id IS NOT NULL;
|
||||
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS notifications_discussion_comment_reply_unique_idx
|
||||
ON notifications(recipient_user_id, discussion_comment_id)
|
||||
WHERE type = 'discussion_comment_reply' AND discussion_comment_id IS NOT NULL;
|
||||
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS notifications_life_post_reaction_unique_idx
|
||||
ON notifications(recipient_user_id, actor_user_id, life_post_id)
|
||||
WHERE type = 'life_post_reaction' AND actor_user_id IS NOT NULL AND life_post_id IS NOT NULL;
|
||||
|
||||
CREATE TABLE IF NOT EXISTS notification_ws_tickets (
|
||||
id integer GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
|
||||
user_id integer NOT NULL REFERENCES users(id) ON DELETE CASCADE,
|
||||
token_hash text NOT NULL UNIQUE,
|
||||
expires_at timestamptz NOT NULL,
|
||||
used_at timestamptz,
|
||||
created_at timestamptz NOT NULL DEFAULT now()
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS notification_ws_tickets_user_idx
|
||||
ON notification_ws_tickets(user_id, expires_at DESC);
|
||||
|
||||
ALTER TABLE life_tags
|
||||
ADD COLUMN IF NOT EXISTS is_rateable boolean NOT NULL DEFAULT false;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user