286 lines
10 KiB
TypeScript
286 lines
10 KiB
TypeScript
|
|
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';
|
|
|
|
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];
|
|
|
|
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(
|
|
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'
|
|
);
|
|
}
|
|
} 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(
|
|
admin.email,
|
|
`Nytt meddelande: ${conversation.subject}`,
|
|
sender.full_name,
|
|
sender.email,
|
|
sender.mobile,
|
|
sender.personnummer,
|
|
content,
|
|
`${process.env.NEXT_PUBLIC_APP_URL || 'http://localhost:3000'}/admin/messages`,
|
|
'Svara i Adminpanelen'
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
return NextResponse.json({ success: true });
|
|
} catch (error) {
|
|
console.error('Error sending message:', error);
|
|
return NextResponse.json({ error: 'Server error' }, { status: 500 });
|
|
}
|
|
}
|
|
|
|
// 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 });
|
|
}
|
|
|
|
const { id } = await params;
|
|
|
|
const [convRows] = await pool.query<RowDataPacket[]>(
|
|
`SELECT * FROM conversations WHERE id = ?`,
|
|
[id]
|
|
);
|
|
|
|
if (convRows.length === 0) {
|
|
return NextResponse.json({ error: 'Conversation not found' }, { status: 404 });
|
|
}
|
|
|
|
const conversation = convRows[0];
|
|
|
|
// 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 });
|
|
}
|
|
|
|
// 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
|
|
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];
|
|
|
|
// 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}`,
|
|
`
|
|
<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>
|
|
`
|
|
);
|
|
|
|
return NextResponse.json({ success: true, message: 'Conversation resolved' });
|
|
}
|
|
|
|
return NextResponse.json({ error: 'Invalid action' }, { status: 400 });
|
|
} catch (error) {
|
|
console.error('Error resolving conversation:', error);
|
|
return NextResponse.json({ error: 'Server error' }, { status: 500 });
|
|
}
|
|
}
|