Initial commit: Portfolio Website

Vollständige Next.js 15 Portfolio-Website mit:
- Blog-System mit 100+ Artikeln
- Supabase-Integration
- Responsive Design mit Tailwind CSS
- TypeScript-Konfiguration
- Testing-Setup mit Vitest und Playwright

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
2026-02-01 15:07:20 +01:00
co-authored by Claude Opus 4.5
commit e1bbe5455d
315 changed files with 94124 additions and 0 deletions
+262
View File
@@ -0,0 +1,262 @@
import { query, queryOne } from './postgres';
// Type definitions for chat tables
export type ChatRole = 'user' | 'assistant' | 'system';
export interface ChatSession {
id: string;
created_at: string;
updated_at: string;
visitor_id: string;
metadata: Record<string, unknown>;
}
export interface ChatMessage {
id: string;
session_id: string;
role: ChatRole;
content: string;
created_at: string;
}
// Input types for creating records
export interface CreateChatSessionInput {
visitor_id: string;
metadata?: Record<string, unknown>;
}
export interface CreateChatMessageInput {
session_id: string;
role: ChatRole;
content: string;
}
// ============================================
// Chat Session Operations
// ============================================
/**
* Create a new chat session for a visitor
*/
export async function createChatSession(
input: CreateChatSessionInput
): Promise<ChatSession> {
const result = await queryOne<ChatSession>(
`INSERT INTO chat_sessions (visitor_id, metadata)
VALUES ($1, $2)
RETURNING *`,
[input.visitor_id, JSON.stringify(input.metadata ?? {})]
);
if (!result) {
throw new Error('Failed to create chat session');
}
return result;
}
/**
* Get a chat session by ID
*/
export async function getChatSession(
sessionId: string
): Promise<ChatSession | null> {
return queryOne<ChatSession>(
'SELECT * FROM chat_sessions WHERE id = $1',
[sessionId]
);
}
/**
* Get or create a chat session for a visitor
* Useful for maintaining session continuity
*/
export async function getOrCreateChatSession(
visitorId: string,
metadata?: Record<string, unknown>
): Promise<ChatSession> {
// First, try to find an existing active session for this visitor
// Get the most recent session from the last 24 hours
const existingSession = await queryOne<ChatSession>(
`SELECT * FROM chat_sessions
WHERE visitor_id = $1
AND created_at >= NOW() - INTERVAL '24 hours'
ORDER BY created_at DESC
LIMIT 1`,
[visitorId]
);
if (existingSession) {
return existingSession;
}
// No existing session found, create a new one
return createChatSession({
visitor_id: visitorId,
metadata,
});
}
/**
* Update session metadata
*/
export async function updateSessionMetadata(
sessionId: string,
metadata: Record<string, unknown>
): Promise<ChatSession> {
const result = await queryOne<ChatSession>(
`UPDATE chat_sessions
SET metadata = $1
WHERE id = $2
RETURNING *`,
[JSON.stringify(metadata), sessionId]
);
if (!result) {
throw new Error('Failed to update session metadata');
}
return result;
}
// ============================================
// Chat Message Operations
// ============================================
/**
* Add a message to a chat session
*/
export async function addChatMessage(
input: CreateChatMessageInput
): Promise<ChatMessage> {
const result = await queryOne<ChatMessage>(
`INSERT INTO chat_messages (session_id, role, content)
VALUES ($1, $2, $3)
RETURNING *`,
[input.session_id, input.role, input.content]
);
if (!result) {
throw new Error('Failed to add chat message');
}
return result;
}
/**
* Get all messages for a chat session
* Returns messages in chronological order
*/
export async function getChatMessages(
sessionId: string
): Promise<ChatMessage[]> {
return query<ChatMessage>(
`SELECT * FROM chat_messages
WHERE session_id = $1
ORDER BY created_at ASC`,
[sessionId]
);
}
/**
* Get the last N messages for a session
* Useful for maintaining context window limits
*/
export async function getRecentMessages(
sessionId: string,
limit: number = 20
): Promise<ChatMessage[]> {
const messages = await query<ChatMessage>(
`SELECT * FROM chat_messages
WHERE session_id = $1
ORDER BY created_at DESC
LIMIT $2`,
[sessionId, limit]
);
// Reverse to get chronological order
return messages.reverse();
}
/**
* Delete all messages for a session (clear history)
*/
export async function clearChatMessages(sessionId: string): Promise<void> {
await query(
'DELETE FROM chat_messages WHERE session_id = $1',
[sessionId]
);
}
/**
* Delete a chat session and all its messages
* Messages are deleted automatically via ON DELETE CASCADE
*/
export async function deleteChatSession(sessionId: string): Promise<void> {
await query(
'DELETE FROM chat_sessions WHERE id = $1',
[sessionId]
);
}
// ============================================
// Utility Functions
// ============================================
/**
* Get conversation history formatted for AI context
* Converts database messages to the format expected by chat completion APIs
*/
export async function getConversationHistory(
sessionId: string,
maxMessages: number = 20
): Promise<Array<{ role: ChatRole; content: string }>> {
const messages = await getRecentMessages(sessionId, maxMessages);
return messages.map((msg) => ({
role: msg.role,
content: msg.content,
}));
}
/**
* Save a conversation turn (user message + assistant response)
* Convenience function for typical chat flow
*/
export async function saveConversationTurn(
sessionId: string,
userMessage: string,
assistantResponse: string
): Promise<{ userMsg: ChatMessage; assistantMsg: ChatMessage }> {
const userMsg = await addChatMessage({
session_id: sessionId,
role: 'user',
content: userMessage,
});
const assistantMsg = await addChatMessage({
session_id: sessionId,
role: 'assistant',
content: assistantResponse,
});
return { userMsg, assistantMsg };
}
// Default export for convenience
export default {
// Session operations
createChatSession,
getChatSession,
getOrCreateChatSession,
updateSessionMetadata,
deleteChatSession,
// Message operations
addChatMessage,
getChatMessages,
getRecentMessages,
clearChatMessages,
// Utility functions
getConversationHistory,
saveConversationTurn,
};