mirror of
https://git.coastit.co.za/caitlin/CRM_ENVR.git
synced 2026-07-10 19:17:16 +02:00
1adc4806fa
Security Fixes
File Change
.env.local Placeholder JWT secret (rotate to random value: openssl rand -base64 64)
src/middleware.ts Removed /api/auth/me bypass; API routes return JSON 401 (not 302 redirect)
src/lib/auth.ts sameSite: "lax" → "strict" (create + destroy cookie); SVG name chars are pre-computed (no injection)
src/lib/avatar.ts Added sanitizeName() — strips non-alphanumeric/safe chars, max 50 chars
src/app/api/users/route.ts POST restricted to super_admin; validates role against whitelist ["sales","admin","super_admin","dev"]; validates active defaults to true
src/app/api/users/[id]/route.ts DELETE restricted to super_admin; blocks self-deletion
src/app/api/leads/[id]/route.ts GET: non-admin users filtered by assigned_to; PATCH: ownership check + score validated 0-100 range
src/app/api/leads/route.ts DELETE: ownership + admin check; GET: ownership filter for non-admin users
src/app/api/conversations/[id]/messages/route.ts GET/POST: verify user is a conversation_participant before access
rust-ai/src/main.rs CORS: AllowOrigin::list(["localhost:3006", "127.0.0.1:3006"]) (was Any)
Performance Fixes
File Change
src/app/api/leads/route.ts Added limit (default 50), offset query params; ownership filter in SQL
src/app/api/users/route.ts Added limit (default 50), offset query params
src/app/api/conversations/route.ts Added LIMIT 50
src/app/api/leads/[id]/notes/route.ts Added limit (default 50), offset query params
src/app/api/conversations/[id]/messages/route.ts Added limit (default 100), offset, before query params
src/app/(dashboard)/chats/page.tsx Pruned to 5 cached conversations; useMemo wraps messages/conversation/filteredConversations; otherParticipant moved outside component; added 10MB file size limit; recordingChunksRef cleared on discard
src/app/(dashboard)/leads/[id]/page.tsx Optimistic status update rolls back on fetch failure
database/migrations/003_chat.sql Added composite index idx_messages_conversation_created on (conversation_id, created_at DESC)
Code Quality Fixes
File Change
src/lib/ai.ts Removed dead getInstructions()/updateInstructions() (called nonexistent /ai/instructions)
src/lib/auth.ts bcrypt.hash(password, 12) — kept (acceptable, OWASP-recommended)
src/app/login/page.tsx "Forgot password?" button now shows alert to contact admin (was no-op)
src/components/users/user-form-dialog.tsx Password min 6 → 8 chars
26 silent catch {} blocks across 16 files (see below) Added console.warn("...") context messages
83 lines
4.7 KiB
SQL
83 lines
4.7 KiB
SQL
-- ============================================================================
|
|
-- Chat System: conversations, participants, and messages
|
|
-- ============================================================================
|
|
|
|
CREATE TABLE IF NOT EXISTS conversations (
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
|
);
|
|
|
|
CREATE TABLE IF NOT EXISTS conversation_participants (
|
|
conversation_id UUID NOT NULL REFERENCES conversations(id) ON DELETE CASCADE,
|
|
user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE,
|
|
joined_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
PRIMARY KEY (conversation_id, user_id)
|
|
);
|
|
|
|
CREATE TABLE IF NOT EXISTS messages (
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
conversation_id UUID NOT NULL REFERENCES conversations(id) ON DELETE CASCADE,
|
|
sender_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE,
|
|
content TEXT NOT NULL,
|
|
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
updated_at TIMESTAMPTZ,
|
|
deleted_at TIMESTAMPTZ
|
|
);
|
|
|
|
CREATE INDEX IF NOT EXISTS idx_messages_conversation_id ON messages(conversation_id);
|
|
CREATE INDEX IF NOT EXISTS idx_messages_created_at ON messages(created_at);
|
|
CREATE INDEX IF NOT EXISTS idx_conversation_participants_user_id ON conversation_participants(user_id);
|
|
|
|
CREATE INDEX IF NOT EXISTS idx_messages_conversation_created ON messages(conversation_id, created_at DESC);
|
|
|
|
-- Seed conversations between superadmin and other users
|
|
INSERT INTO conversations (id, created_at) VALUES
|
|
('c0000000-0000-0000-0000-000000000001', NOW() - INTERVAL '2 hours'),
|
|
('c0000000-0000-0000-0000-000000000002', NOW() - INTERVAL '1 hour'),
|
|
('c0000000-0000-0000-0000-000000000003', NOW() - INTERVAL '30 minutes')
|
|
ON CONFLICT DO NOTHING;
|
|
|
|
-- Add participants (superadmin + each sales/dev user)
|
|
INSERT INTO conversation_participants (conversation_id, user_id) VALUES
|
|
('c0000000-0000-0000-0000-000000000001', '00000000-0000-0000-0000-000000000001'),
|
|
('c0000000-0000-0000-0000-000000000001', '00000000-0000-0000-0000-000000000003'),
|
|
('c0000000-0000-0000-0000-000000000002', '00000000-0000-0000-0000-000000000001'),
|
|
('c0000000-0000-0000-0000-000000000002', '00000000-0000-0000-0000-000000000004'),
|
|
('c0000000-0000-0000-0000-000000000003', '00000000-0000-0000-0000-000000000001'),
|
|
('c0000000-0000-0000-0000-000000000003', '00000000-0000-0000-0000-000000000002')
|
|
ON CONFLICT DO NOTHING;
|
|
|
|
-- Seed some messages
|
|
INSERT INTO messages (id, conversation_id, sender_id, content, created_at) VALUES
|
|
('00000000-0000-0000-0000-000000000101', 'c0000000-0000-0000-0000-000000000001',
|
|
'00000000-0000-0000-0000-000000000003', 'Hey! Just finalized the TechCorp deal.',
|
|
NOW() - INTERVAL '2 hours'),
|
|
('00000000-0000-0000-0000-000000000102', 'c0000000-0000-0000-0000-000000000001',
|
|
'00000000-0000-0000-0000-000000000001', 'Great work! What were the final terms?',
|
|
NOW() - INTERVAL '105 minutes'),
|
|
('00000000-0000-0000-0000-000000000103', 'c0000000-0000-0000-0000-000000000001',
|
|
'00000000-0000-0000-0000-000000000003', '3-year enterprise license, 15% discount. They signed this morning.',
|
|
NOW() - INTERVAL '100 minutes'),
|
|
('00000000-0000-0000-0000-000000000104', 'c0000000-0000-0000-0000-000000000002',
|
|
'00000000-0000-0000-0000-000000000004', 'The API integration tests are passing on staging.',
|
|
NOW() - INTERVAL '50 minutes'),
|
|
('00000000-0000-0000-0000-000000000105', 'c0000000-0000-0000-0000-000000000002',
|
|
'00000000-0000-0000-0000-000000000001', 'Great, when can we deploy to production?',
|
|
NOW() - INTERVAL '45 minutes'),
|
|
('00000000-0000-0000-0000-000000000106', 'c0000000-0000-0000-0000-000000000002',
|
|
'00000000-0000-0000-0000-000000000004', 'Tomorrow morning after final review.',
|
|
NOW() - INTERVAL '40 minutes'),
|
|
('00000000-0000-0000-0000-000000000107', 'c0000000-0000-0000-0000-000000000003',
|
|
'00000000-0000-0000-0000-000000000002', 'New report shows 23% increase in lead conversion this quarter.',
|
|
NOW() - INTERVAL '25 minutes'),
|
|
('00000000-0000-0000-0000-000000000108', 'c0000000-0000-0000-0000-000000000003',
|
|
'00000000-0000-0000-0000-000000000001', 'That is excellent! Let us discuss in the next team meeting.',
|
|
NOW() - INTERVAL '20 minutes')
|
|
ON CONFLICT DO NOTHING;
|
|
|
|
-- Update conversation timestamps
|
|
UPDATE conversations SET updated_at = NOW() - INTERVAL '20 minutes' WHERE id = 'c0000000-0000-0000-0000-000000000003';
|
|
UPDATE conversations SET updated_at = NOW() - INTERVAL '40 minutes' WHERE id = 'c0000000-0000-0000-0000-000000000002';
|
|
UPDATE conversations SET updated_at = NOW() - INTERVAL '100 minutes' WHERE id = 'c0000000-0000-0000-0000-000000000001';
|