nordicstorium/src/app/api/conversations/[id]/route.ts

286 lines
10 KiB
TypeScript
Raw Normal View History

2026-02-02 18:25:30 +00:00
2026-02-02 15:09:01 +00:00
import { NextRequest, NextResponse } from 'next/server';
import pool from '@/lib/db';
import { verifyToken, TokenPayload } from '@/lib/auth';
import { RowDataPacket, ResultSetHeader } from 'mysql2';
import { sendEmail, sendMessageNotification } from '@/lib/email';
2026-02-02 15:09:01 +00:00
function getUserFromRequest(request: NextRequest): TokenPayload | null {
const authHeader = request.headers.get('authorization');
if (!authHeader?.startsWith('Bearer ')) return null;
const token = authHeader.substring(7);
try {
return verifyToken(token);
} catch {
return null;
}
}
interface RouteParams {
params: Promise<{ id: string }>;
}
// GET /api/conversations/[id] - Get conversation with messages
export async function GET(request: NextRequest, { params }: RouteParams) {
try {
const user = getUserFromRequest(request);
if (!user) {
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
}
const { id } = await params;
const [convRows] = await pool.query<RowDataPacket[]>(
`SELECT c.*, u.full_name as user_name, u.email as user_email, p.name as product_name
FROM conversations c
JOIN users u ON c.user_id = u.id
LEFT JOIN products p ON c.product_id = p.id
WHERE c.id = ?`,
[id]
);
if (convRows.length === 0) {
return NextResponse.json({ error: 'Conversation not found' }, { status: 404 });
}
const conversation = convRows[0];
if (user.role !== 'admin' && conversation.user_id !== user.userId) {
return NextResponse.json({ error: 'Forbidden' }, { status: 403 });
}
const [messages] = await pool.query<RowDataPacket[]>(
`SELECT m.*, u.full_name as sender_name
FROM messages m
JOIN users u ON m.sender_id = u.id
WHERE m.conversation_id = ?
ORDER BY m.created_at ASC`,
[id]
);
const readRole = user.role === 'admin' ? 'customer' : 'admin';
await pool.query(
`UPDATE messages SET is_read = TRUE
WHERE conversation_id = ? AND sender_role = ? AND is_read = FALSE`,
[id, readRole]
);
return NextResponse.json({ conversation, messages });
} catch (error) {
console.error('Error fetching conversation:', error);
return NextResponse.json({ error: 'Server error' }, { status: 500 });
}
}
// POST /api/conversations/[id] - Add message to conversation
export async function POST(request: NextRequest, { params }: RouteParams) {
try {
const user = getUserFromRequest(request);
if (!user) {
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
}
const { id } = await params;
const body = await request.json();
const { content } = body;
if (!content) {
return NextResponse.json({ error: 'Message content required' }, { status: 400 });
}
const [convRows] = await pool.query<RowDataPacket[]>(
`SELECT c.*, u.full_name as user_name, u.email as user_email
FROM conversations c
JOIN users u ON c.user_id = u.id
WHERE c.id = ?`,
[id]
);
if (convRows.length === 0) {
return NextResponse.json({ error: 'Conversation not found' }, { status: 404 });
}
const conversation = convRows[0];
if (user.role !== 'admin' && conversation.user_id !== user.userId) {
return NextResponse.json({ error: 'Forbidden' }, { status: 403 });
}
const senderRole = user.role === 'admin' ? 'admin' : 'customer';
await pool.query<ResultSetHeader>(
`INSERT INTO messages (conversation_id, sender_id, sender_role, content) VALUES (?, ?, ?, ?)`,
[id, user.userId, senderRole, content]
);
// Fetch sender's expanded details for the email
const [senderDetails] = await pool.query<RowDataPacket[]>(
`SELECT full_name, email, mobile, personnummer FROM users WHERE id = ?`,
[user.userId]
);
const sender = senderDetails[0];
2026-02-02 15:09:01 +00:00
await pool.query(
`UPDATE conversations SET updated_at = CURRENT_TIMESTAMP WHERE id = ?`,
[id]
);
// Send email notification based on recipient's online status
if (user.role === 'admin') {
// Admin replying to customer
const [sessions] = await pool.query<RowDataPacket[]>(
`SELECT COUNT(*) as count FROM user_sessions
WHERE user_id = ? AND expires_at > NOW()`,
[conversation.user_id]
);
const isCustomerOnline = sessions[0].count > 0;
if (!isCustomerOnline) {
await sendMessageNotification(
2026-02-02 15:09:01 +00:00
conversation.user_email,
`Nytt svar: ${conversation.subject}`,
'Nordic Storium Support',
process.env.SMTP_FROM || 'support@nordicstorium.com',
null,
null,
content,
`${process.env.NEXT_PUBLIC_APP_URL || 'http://localhost:3000'}/messages`,
'Se konversation'
2026-02-02 15:09:01 +00:00
);
}
} else {
// Customer replying to admins
const [admins] = await pool.query<RowDataPacket[]>(
`SELECT id, email FROM users WHERE role = 'admin'`
);
for (const admin of admins) {
const [sessions] = await pool.query<RowDataPacket[]>(
`SELECT COUNT(*) as count FROM user_sessions
WHERE user_id = ? AND expires_at > NOW()`,
[admin.id]
);
const isAdminOnline = sessions[0].count > 0;
if (!isAdminOnline) {
await sendMessageNotification(
2026-02-02 15:09:01 +00:00
admin.email,
`Nytt meddelande: ${conversation.subject}`,
sender.full_name,
sender.email,
sender.mobile,
sender.personnummer,
content,
2026-02-02 18:25:30 +00:00
`${process.env.NEXT_PUBLIC_APP_URL || 'http://localhost:3000'}/admin/messages`,
'Svara i Adminpanelen'
);
}
}
}
2026-02-02 18:25:30 +00:00
return NextResponse.json({ success: true });
} catch (error) {
console.error('Error sending message:', error);
return NextResponse.json({ error: 'Server error' }, { status: 500 });
}
}
2026-02-02 18:25:30 +00:00
// DELETE /api/conversations/[id] - Delete conversation
export async function DELETE(request: NextRequest, { params }: RouteParams) {
try {
const user = getUserFromRequest(request);
if (!user) {
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
}
2026-02-02 15:09:01 +00:00
2026-02-02 18:25:30 +00:00
const { id } = await params;
2026-02-02 15:09:01 +00:00
2026-02-02 18:25:30 +00:00
const [convRows] = await pool.query<RowDataPacket[]>(
`SELECT * FROM conversations WHERE id = ?`,
[id]
);
2026-02-02 15:09:01 +00:00
2026-02-02 18:25:30 +00:00
if (convRows.length === 0) {
return NextResponse.json({ error: 'Conversation not found' }, { status: 404 });
}
2026-02-02 15:09:01 +00:00
2026-02-02 18:25:30 +00:00
const conversation = convRows[0];
2026-02-02 15:09:01 +00:00
2026-02-02 18:25:30 +00:00
// Only admin can delete completely, customers can only delete their own
if (user.role !== 'admin' && conversation.user_id !== user.userId) {
return NextResponse.json({ error: 'Forbidden' }, { status: 403 });
}
2026-02-02 15:09:01 +00:00
2026-02-02 18:25:30 +00:00
// Delete the conversation (cascades to messages)
await pool.query(`DELETE FROM conversations WHERE id = ?`, [id]);
return NextResponse.json({ success: true, message: 'Conversation deleted' });
} catch (error) {
console.error('Error deleting conversation:', error);
return NextResponse.json({ error: 'Server error' }, { status: 500 });
}
}
// PATCH /api/conversations/[id] - Resolve conversation (admin only)
export async function PATCH(request: NextRequest, { params }: RouteParams) {
try {
const user = getUserFromRequest(request);
if (!user) {
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
}
if (user.role !== 'admin') {
return NextResponse.json({ error: 'Admin only' }, { status: 403 });
}
const { id } = await params;
const body = await request.json();
const { action } = body;
if (action === 'resolve') {
// Get conversation info for email
const [convRows] = await pool.query<RowDataPacket[]>(
`SELECT c.*, u.email as user_email, u.full_name as user_name
2026-02-02 15:09:01 +00:00
FROM conversations c
JOIN users u ON c.user_id = u.id
WHERE c.id = ?`,
2026-02-02 18:25:30 +00:00
[id]
);
if (convRows.length === 0) {
return NextResponse.json({ error: 'Conversation not found' }, { status: 404 });
}
const conversation = convRows[0];
// Update status to closed
await pool.query(
`UPDATE conversations SET status = 'closed' WHERE id = ?`,
[id]
);
// Send resolution email to customer
await sendEmail(
conversation.user_email,
`Ärende löst: ${conversation.subject}`,
`
2026-02-02 15:09:01 +00:00
<h2>Ditt ärende har markerats som löst</h2>
<p>Hej ${conversation.user_name || 'kund'},</p>
<p>Vi har markerat ditt ärende "<strong>${conversation.subject}</strong>" som löst.</p>
<p>Om du fortfarande har frågor eller känner att ärendet inte är helt löst, tveka inte att kontakta oss igen.</p>
<p>Med vänliga hälsningar,<br/>Nordic Storium Support</p>
`
2026-02-02 18:25:30 +00:00
);
2026-02-02 15:09:01 +00:00
2026-02-02 18:25:30 +00:00
return NextResponse.json({ success: true, message: 'Conversation resolved' });
}
2026-02-02 15:09:01 +00:00
2026-02-02 18:25:30 +00:00
return NextResponse.json({ error: 'Invalid action' }, { status: 400 });
} catch (error) {
console.error('Error resolving conversation:', error);
return NextResponse.json({ error: 'Server error' }, { status: 500 });
}
}