feat: add system monitor component and API for performance metrics
feat: implement conversations API with message retrieval and posting feat: add avatar URL handling for users and update user role definitions feat: create chat system tables in the database with initial seed data fix: update user roles from "sales_user" to "sales" for consistency chore: add middleware for system API route access fixed fuckups on john's side, added a benchmark Made Graphs actualy load from database and realtime data, graphs will update and percentages will be mathematically made, and made realtime added chatcart edits, made graphs glow. added in some little small home feeling fuctions added in a slide show, in login with qoutes from creators because i want to leave a print on it saying we made this shit, we built it brick for brick login page added qoutes some random, and made them shuffle from left to right one dissapears other come in adding shape to the textbox for the qoutes Fixed my chat fuckups when it comes to chats
This commit is contained in:
@@ -0,0 +1,115 @@
|
||||
import { NextRequest, NextResponse } from "next/server"
|
||||
import { getSessionUser } from "@/lib/auth"
|
||||
import { query } from "@/lib/db"
|
||||
|
||||
export async function GET(
|
||||
_request: NextRequest,
|
||||
{ params }: { params: Promise<{ id: string }> },
|
||||
) {
|
||||
try {
|
||||
const user = await getSessionUser()
|
||||
if (!user) return NextResponse.json({ error: "Unauthorized" }, { status: 401 })
|
||||
|
||||
const { id } = await params
|
||||
|
||||
const [msgResult, otherReadResult] = await Promise.all([
|
||||
query(
|
||||
`SELECT m.id, m.sender_id, m.content, m.created_at, m.updated_at, m.deleted_at,
|
||||
u.first_name || ' ' || u.last_name AS sender_name,
|
||||
u.email AS sender_email,
|
||||
u.avatar_url AS sender_avatar_url
|
||||
FROM messages m
|
||||
JOIN users u ON u.id = m.sender_id
|
||||
WHERE m.conversation_id = $1 AND m.deleted_at IS NULL
|
||||
ORDER BY m.created_at ASC`,
|
||||
[id],
|
||||
),
|
||||
query(
|
||||
`SELECT last_read_at FROM conversation_participants
|
||||
WHERE conversation_id = $1 AND user_id != $2`,
|
||||
[id, user.id],
|
||||
),
|
||||
])
|
||||
|
||||
const otherLastReadAt = otherReadResult.rows[0]?.last_read_at
|
||||
? new Date(otherReadResult.rows[0].last_read_at).getTime()
|
||||
: 0
|
||||
|
||||
const messages = msgResult.rows.map((row: any) => ({
|
||||
id: row.id,
|
||||
conversationId: id,
|
||||
senderId: row.sender_id,
|
||||
senderName: row.sender_name,
|
||||
senderAvatar: row.sender_avatar_url || `https://ui-avatars.com/api/?name=${encodeURIComponent(row.sender_name)}&background=1d4ed8&color=fff&size=128`,
|
||||
content: row.content,
|
||||
timestamp: formatTime(new Date(row.created_at)),
|
||||
createdAt: row.created_at,
|
||||
read: row.sender_id === user.id
|
||||
? new Date(row.created_at).getTime() <= otherLastReadAt
|
||||
: true,
|
||||
}))
|
||||
|
||||
return NextResponse.json({ messages })
|
||||
} catch (error) {
|
||||
console.error("Messages error:", error)
|
||||
return NextResponse.json({ error: "Failed to load messages" }, { status: 500 })
|
||||
}
|
||||
}
|
||||
|
||||
export async function POST(
|
||||
request: NextRequest,
|
||||
{ params }: { params: Promise<{ id: string }> },
|
||||
) {
|
||||
try {
|
||||
const user = await getSessionUser()
|
||||
if (!user) return NextResponse.json({ error: "Unauthorized" }, { status: 401 })
|
||||
|
||||
const { id } = await params
|
||||
const { content } = await request.json()
|
||||
|
||||
if (!content?.trim()) {
|
||||
return NextResponse.json({ error: "Message content is required" }, { status: 400 })
|
||||
}
|
||||
|
||||
const result = await query(
|
||||
`INSERT INTO messages (conversation_id, sender_id, content)
|
||||
VALUES ($1, $2, $3)
|
||||
RETURNING id, created_at`,
|
||||
[id, user.id, content.trim()],
|
||||
)
|
||||
|
||||
await query(
|
||||
`UPDATE conversations SET updated_at = NOW() WHERE id = $1`,
|
||||
[id],
|
||||
)
|
||||
|
||||
const msg = result.rows[0]
|
||||
|
||||
return NextResponse.json({
|
||||
message: {
|
||||
id: msg.id,
|
||||
conversationId: id,
|
||||
senderId: user.id,
|
||||
senderName: `${user.firstName} ${user.lastName}`,
|
||||
senderAvatar: user.avatar,
|
||||
content: content.trim(),
|
||||
timestamp: formatTime(new Date(msg.created_at)),
|
||||
createdAt: msg.created_at,
|
||||
read: false,
|
||||
},
|
||||
})
|
||||
} catch (error) {
|
||||
console.error("Send message error:", error)
|
||||
return NextResponse.json({ error: "Failed to send message" }, { status: 500 })
|
||||
}
|
||||
}
|
||||
|
||||
function formatTime(date: Date): string {
|
||||
const now = new Date()
|
||||
const isToday = date.toDateString() === now.toDateString()
|
||||
if (isToday) {
|
||||
return date.toLocaleTimeString([], { hour: "2-digit", minute: "2-digit" })
|
||||
}
|
||||
return date.toLocaleDateString([], { month: "short", day: "numeric" }) + " " +
|
||||
date.toLocaleTimeString([], { hour: "2-digit", minute: "2-digit" })
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
import { NextRequest, NextResponse } from "next/server"
|
||||
import { getSessionUser } from "@/lib/auth"
|
||||
import { query } from "@/lib/db"
|
||||
|
||||
export async function POST(
|
||||
_request: NextRequest,
|
||||
{ params }: { params: Promise<{ id: string }> },
|
||||
) {
|
||||
try {
|
||||
const user = await getSessionUser()
|
||||
if (!user) return NextResponse.json({ error: "Unauthorized" }, { status: 401 })
|
||||
|
||||
const { id } = await params
|
||||
|
||||
await query(
|
||||
`UPDATE conversation_participants
|
||||
SET last_read_at = NOW()
|
||||
WHERE conversation_id = $1 AND user_id = $2`,
|
||||
[id, user.id],
|
||||
)
|
||||
|
||||
return NextResponse.json({ success: true })
|
||||
} catch (error) {
|
||||
console.error("Mark read error:", error)
|
||||
return NextResponse.json({ error: "Failed to mark as read" }, { status: 500 })
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,129 @@
|
||||
import { NextRequest, NextResponse } from "next/server"
|
||||
import { getSessionUser } from "@/lib/auth"
|
||||
import { query } from "@/lib/db"
|
||||
|
||||
export async function GET() {
|
||||
try {
|
||||
const user = await getSessionUser()
|
||||
if (!user) return NextResponse.json({ error: "Unauthorized" }, { status: 401 })
|
||||
|
||||
const result = await query(
|
||||
`SELECT
|
||||
c.id,
|
||||
c.updated_at,
|
||||
cp_me.last_read_at,
|
||||
u.id AS other_user_id,
|
||||
u.first_name || ' ' || u.last_name AS other_user_name,
|
||||
u.email AS other_user_email,
|
||||
u.avatar_url AS other_user_avatar_url,
|
||||
(SELECT content FROM messages WHERE conversation_id = c.id ORDER BY created_at DESC LIMIT 1) AS last_message,
|
||||
(SELECT created_at FROM messages WHERE conversation_id = c.id ORDER BY created_at DESC LIMIT 1) AS last_message_time,
|
||||
(SELECT count(*) FROM messages WHERE conversation_id = c.id AND sender_id != $1 AND created_at > COALESCE(cp_me.last_read_at, '1970-01-01')) AS unread
|
||||
FROM conversations c
|
||||
JOIN conversation_participants cp_me ON cp_me.conversation_id = c.id AND cp_me.user_id = $1
|
||||
JOIN conversation_participants cp ON cp.conversation_id = c.id
|
||||
JOIN users u ON u.id = cp.user_id AND u.id != $1
|
||||
WHERE c.id IN (
|
||||
SELECT conversation_id FROM conversation_participants WHERE user_id = $1
|
||||
)
|
||||
ORDER BY c.updated_at DESC`,
|
||||
[user.id],
|
||||
)
|
||||
|
||||
const conversations = result.rows.map((row: any) => ({
|
||||
id: row.id,
|
||||
updatedAt: row.updated_at,
|
||||
otherUser: {
|
||||
id: row.other_user_id,
|
||||
name: row.other_user_name,
|
||||
email: row.other_user_email,
|
||||
avatar: row.other_user_avatar_url || `https://ui-avatars.com/api/?name=${encodeURIComponent(row.other_user_name)}&background=1d4ed8&color=fff&size=128`,
|
||||
},
|
||||
lastMessage: row.last_message || "",
|
||||
lastMessageTime: row.last_message_time ? timeAgo(new Date(row.last_message_time)) : "",
|
||||
unread: parseInt(row.unread) || 0,
|
||||
}))
|
||||
|
||||
return NextResponse.json({ conversations })
|
||||
} catch (error) {
|
||||
console.error("Conversations error:", error)
|
||||
return NextResponse.json({ error: "Failed to load conversations" }, { status: 500 })
|
||||
}
|
||||
}
|
||||
|
||||
export async function POST(request: NextRequest) {
|
||||
try {
|
||||
const user = await getSessionUser()
|
||||
if (!user) return NextResponse.json({ error: "Unauthorized" }, { status: 401 })
|
||||
|
||||
const { userId } = await request.json()
|
||||
if (!userId) {
|
||||
return NextResponse.json({ error: "userId is required" }, { status: 400 })
|
||||
}
|
||||
|
||||
if (userId === user.id) {
|
||||
return NextResponse.json({ error: "Cannot start a conversation with yourself" }, { status: 400 })
|
||||
}
|
||||
|
||||
const existing = await query(
|
||||
`SELECT c.id FROM conversations c
|
||||
JOIN conversation_participants cp1 ON cp1.conversation_id = c.id AND cp1.user_id = $1
|
||||
JOIN conversation_participants cp2 ON cp2.conversation_id = c.id AND cp2.user_id = $2
|
||||
LIMIT 1`,
|
||||
[user.id, userId],
|
||||
)
|
||||
|
||||
if (existing.rows.length > 0) {
|
||||
return NextResponse.json({ conversationId: existing.rows[0].id })
|
||||
}
|
||||
|
||||
const convResult = await query(
|
||||
`INSERT INTO conversations DEFAULT VALUES RETURNING id`,
|
||||
)
|
||||
const conversationId = convResult.rows[0].id
|
||||
|
||||
await query(
|
||||
`INSERT INTO conversation_participants (conversation_id, user_id, last_read_at) VALUES ($1, $2, NOW()), ($1, $3, NOW())`,
|
||||
[conversationId, user.id, userId],
|
||||
)
|
||||
|
||||
const otherUser = await query(
|
||||
`SELECT id, first_name || ' ' || last_name AS name, email, avatar_url
|
||||
FROM users WHERE id = $1`,
|
||||
[userId],
|
||||
)
|
||||
|
||||
const other = otherUser.rows[0]
|
||||
|
||||
return NextResponse.json({
|
||||
conversation: {
|
||||
id: conversationId,
|
||||
updatedAt: new Date().toISOString(),
|
||||
otherUser: {
|
||||
id: other.id,
|
||||
name: other.name,
|
||||
email: other.email,
|
||||
avatar: other.avatar_url || `https://ui-avatars.com/api/?name=${encodeURIComponent(other.name)}&background=1d4ed8&color=fff&size=128`,
|
||||
},
|
||||
lastMessage: "",
|
||||
lastMessageTime: "",
|
||||
unread: 0,
|
||||
},
|
||||
})
|
||||
} catch (error) {
|
||||
console.error("Create conversation error:", error)
|
||||
return NextResponse.json({ error: "Failed to create conversation" }, { status: 500 })
|
||||
}
|
||||
}
|
||||
|
||||
function timeAgo(date: Date): string {
|
||||
const seconds = Math.floor((Date.now() - date.getTime()) / 1000)
|
||||
if (seconds < 60) return "now"
|
||||
const minutes = Math.floor(seconds / 60)
|
||||
if (minutes < 60) return `${minutes}m ago`
|
||||
const hours = Math.floor(minutes / 60)
|
||||
if (hours < 24) return `${hours}h ago`
|
||||
const days = Math.floor(hours / 24)
|
||||
if (days < 7) return `${days}d ago`
|
||||
return date.toLocaleDateString()
|
||||
}
|
||||
Reference in New Issue
Block a user