feat: Group chat and push notifications

- Add group chat tables (chat_groups, chat_group_members, group_messages)
- Add push subscriptions table for web push notifications
- Add group service, controller, routes
- Add push service, controller, routes
- Integrate push notifications with todos, messages, group messages

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
richardtekula
2026-01-20 07:27:13 +01:00
parent 73a3c6bf95
commit d9f16ad0a6
15 changed files with 1233 additions and 4 deletions

View File

@@ -0,0 +1,88 @@
import express from 'express';
import * as groupController from '../controllers/group.controller.js';
import { authenticate } from '../middlewares/auth/authMiddleware.js';
import { validateBody, validateParams } from '../middlewares/security/validateInput.js';
import { z } from 'zod';
const router = express.Router();
// All group routes require authentication
router.use(authenticate);
// Create a new group
router.post(
'/',
validateBody(
z.object({
name: z.string().min(1, 'Názov skupiny je povinný').max(100, 'Názov je príliš dlhý'),
memberIds: z.array(z.string().uuid()).min(1, 'Vyberte aspoň jedného člena'),
})
),
groupController.createGroup
);
// Get all groups for current user
router.get('/', groupController.getUserGroups);
// Get group details
router.get(
'/:groupId',
validateParams(z.object({ groupId: z.string().uuid() })),
groupController.getGroupDetails
);
// Update group name
router.patch(
'/:groupId',
validateParams(z.object({ groupId: z.string().uuid() })),
validateBody(z.object({ name: z.string().min(1).max(100) })),
groupController.updateGroupName
);
// Delete group
router.delete(
'/:groupId',
validateParams(z.object({ groupId: z.string().uuid() })),
groupController.deleteGroup
);
// Get group messages
router.get(
'/:groupId/messages',
validateParams(z.object({ groupId: z.string().uuid() })),
groupController.getGroupMessages
);
// Send message to group
router.post(
'/:groupId/messages',
validateParams(z.object({ groupId: z.string().uuid() })),
validateBody(
z.object({
content: z.string().min(1, 'Správa nemôže byť prázdna').max(5000, 'Správa je príliš dlhá'),
})
),
groupController.sendGroupMessage
);
// Add member to group
router.post(
'/:groupId/members',
validateParams(z.object({ groupId: z.string().uuid() })),
validateBody(z.object({ userId: z.string().uuid() })),
groupController.addGroupMember
);
// Remove member from group
router.delete(
'/:groupId/members/:userId',
validateParams(
z.object({
groupId: z.string().uuid(),
userId: z.string().uuid(),
})
),
groupController.removeGroupMember
);
export default router;

49
src/routes/push.routes.js Normal file
View File

@@ -0,0 +1,49 @@
import express from 'express';
import * as pushController from '../controllers/push.controller.js';
import { authenticate } from '../middlewares/auth/authMiddleware.js';
import { validateBody } from '../middlewares/security/validateInput.js';
import { z } from 'zod';
const router = express.Router();
// Get VAPID public key (no auth required)
router.get('/vapid-public-key', pushController.getVapidPublicKey);
// All other routes require authentication
router.use(authenticate);
// Subscribe to push notifications
router.post(
'/subscribe',
validateBody(
z.object({
subscription: z.object({
endpoint: z.string().url(),
keys: z.object({
p256dh: z.string(),
auth: z.string(),
}),
}),
})
),
pushController.subscribe
);
// Unsubscribe from push notifications
router.post(
'/unsubscribe',
validateBody(
z.object({
endpoint: z.string().url().optional(),
}).optional()
),
pushController.unsubscribe
);
// Get subscription status
router.get('/status', pushController.getStatus);
// Test push notification
router.post('/test', pushController.testPush);
export default router;