first commit

This commit is contained in:
DerJesen
2025-12-11 09:49:24 +01:00
parent 212808529d
commit 5eb1c8550e
9 changed files with 107 additions and 24 deletions

12
.env.example Normal file
View File

@@ -0,0 +1,12 @@
# Database Connection
DATABASE_URL="postgres://postgres:"
# Security
JWT_SECRET="change_this_to_a_secure_secret"
# SMTP Configuration
SMTP_HOST="smtp.example.com"
SMTP_PORT=587
SMTP_USER="user@example.com"
SMTP_PASS="password"
SMTP_FROM="noreply@smartes-klassenzimmer.de"

View File

@@ -9,17 +9,10 @@ async function bootstrap() {
// CORS Konfiguration
app.enableCors({
origin: [
'http://localhost:5173', // Vite Dev Server
'http://127.0.0.1:5173', // Vite Dev Server (Alternative)
'http://localhost:5500',
'http://127.0.0.1:5500',
'http://localhost',
'*'
],
origin: true,
credentials: true,
methods: ['GET', 'POST', 'PUT', 'DELETE', 'PATCH', 'OPTIONS'],
allowedHeaders: ['Content-Type', 'Authorization'],
allowedHeaders: ['Content-Type', 'Authorization', 'Accept'],
});
// Globales Präfix für alle Routes
@@ -37,6 +30,6 @@ async function bootstrap() {
}),
);
await app.listen(process.env.PORT ?? 3000);
await app.listen(process.env.PORT ?? 3000, '0.0.0.0');
}
bootstrap();

View File

@@ -2,7 +2,7 @@ import { browser } from '$app/environment';
// API Client für Backend-Kommunikation
const API_BASE_URL = browser
? (import.meta.env.VITE_API_BASE_URL || 'http://localhost:3000/api')
? (import.meta.env.VITE_API_BASE_URL || `http://${window.location.hostname}:3000/api`)
: (import.meta.env.VITE_INTERNAL_API_URL || 'http://backend:3000/api');
interface ApiResponse<T> {

View File

@@ -28,7 +28,7 @@ class ClassroomStore {
if (this.socket?.connected) return;
if (!authStore.user) return;
this.socket = io('http://localhost:3000/api/classroom', {
this.socket = io(`http://${window.location.hostname}:3000/api/classroom`, {
withCredentials: true,
transports: ['websocket'],
});

View File

@@ -39,8 +39,9 @@ class WhiteboardStore {
this.whiteboardId = whiteboardId;
this.userId = userId;
// WebSocket-Verbindung herstellen
this.socket = io('http://localhost:3000/whiteboard', {
if (this.socket) return;
this.socket = io(`http://${window.location.hostname}:3000/whiteboard`, {
withCredentials: true,
transports: ['websocket', 'polling']
});

View File

@@ -1,5 +1,6 @@
import { redirect } from '@sveltejs/kit';
import type { LayoutLoad } from './$types';
import { browser } from '$app/environment';
// Routen die NICHT authentifiziert sein müssen
const PUBLIC_ROUTES = ['/', '/login', '/register'];
@@ -10,9 +11,13 @@ export const load: LayoutLoad = async ({ url, fetch }) => {
return {};
}
const apiBase = browser
? `http://${window.location.hostname}:3000/api`
: 'http://localhost:3000/api';
// Versuche den User zu laden
try {
const response = await fetch('http://localhost:3000/api/auth/me', {
const response = await fetch(`${apiBase}/auth/me`, {
credentials: 'include'
});

View File

@@ -0,0 +1,51 @@
import { env } from '$env/dynamic/private';
import type { RequestHandler } from './$types';
// Fallback, falls die Variable nicht gesetzt ist (für lokale Entwicklung ohne Docker)
const TARGET_URL = env.VITE_INTERNAL_API_URL || 'http://localhost:3000/api';
const proxy: RequestHandler = async ({ request, params, url }) => {
// Der Pfad hinter /api/ (z.B. "auth/me")
const path = params.path;
const query = url.search;
// Ziel-URL zusammenbauen
const targetUrl = `${TARGET_URL}/${path}${query}`;
try {
// Request-Body und Header kopieren
const headers = new Headers(request.headers);
// Host-Header entfernen, damit das Backend nicht verwirrt ist
headers.delete('host');
headers.delete('connection');
// Den ursprünglichen Request klonen und an das Ziel senden
const response = await fetch(targetUrl, {
method: request.method,
headers,
body: request.body,
// Wichtig für Cookies/Auth, falls nötig
// duplex: 'half' // Node 18+ fetch requirement für streams, falls verwendet
// @ts-ignore - sveltekit fetch wrapper handles this usually, but native fetch might need it
} as RequestInit);
// Die Antwort vom Backend an den Client zurückgeben
return new Response(response.body, {
status: response.status,
statusText: response.statusText,
headers: response.headers
});
} catch (err) {
console.error('Proxy Error:', err);
return new Response(JSON.stringify({ error: 'Proxy Error', details: String(err) }), {
status: 502,
headers: { 'Content-Type': 'application/json' }
});
}
};
export const GET: RequestHandler = proxy;
export const POST: RequestHandler = proxy;
export const PUT: RequestHandler = proxy;
export const DELETE: RequestHandler = proxy;
export const PATCH: RequestHandler = proxy;

View File

@@ -59,6 +59,9 @@
let showMembersModal = $state(false);
let allUsers = $state<User[]>([]);
let selectedUserIds = $state<number[]>([]);
// Dynamic Base URL
const baseUrl = `http://${window.location.hostname}:3000`;
$effect(() => {
if (!authStore.isLoading) {
@@ -82,7 +85,7 @@
async function loadGroups() {
try {
const response = await fetch('http://localhost:3000/api/live/groups/my', {
const response = await fetch(`${baseUrl}/api/live/groups/my`, {
credentials: 'include'
});
@@ -110,7 +113,7 @@
try {
// Nachrichten-Historie laden
const response = await fetch(`http://localhost:3000/api/live/groups/${group.id}/messages`, {
const response = await fetch(`${baseUrl}/api/live/groups/${group.id}/messages`, {
credentials: 'include'
});
@@ -151,7 +154,7 @@
console.log('User authenticated:', authStore.isAuthenticated);
// Socket.io Verbindung - das httpOnly Cookie wird automatisch mitgesendet
socket = io('http://localhost:3000/api/live', {
socket = io(`${baseUrl}/api/live`, {
withCredentials: true,
transports: ['websocket', 'polling']
});
@@ -233,7 +236,7 @@
payload.description = newGroupDescription.trim();
}
const response = await fetch('http://localhost:3000/api/live/groups', {
const response = await fetch(`${baseUrl}/api/live/groups`, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
@@ -262,7 +265,7 @@
if (!confirm('Möchten Sie diese Gruppe wirklich löschen?')) return;
try {
const response = await fetch(`http://localhost:3000/api/live/groups/${groupId}`, {
const response = await fetch(`${baseUrl}/api/live/groups/${groupId}`, {
method: 'DELETE',
credentials: 'include'
});
@@ -305,7 +308,7 @@
async function loadAllUsers() {
try {
const response = await fetch('http://localhost:3000/api/users', {
const response = await fetch(`${baseUrl}/api/users`, {
credentials: 'include'
});
@@ -328,7 +331,7 @@
if (!currentGroup || selectedUserIds.length === 0) return;
try {
const response = await fetch(`http://localhost:3000/api/live/groups/${currentGroup.id}/members`, {
const response = await fetch(`${baseUrl}/api/live/groups/${currentGroup.id}/members`, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
@@ -360,7 +363,7 @@
if (!confirm('Möchten Sie dieses Mitglied wirklich entfernen?')) return;
try {
const response = await fetch(`http://localhost:3000/api/live/groups/${currentGroup.id}/members/${userId}`, {
const response = await fetch(`${baseUrl}/api/live/groups/${currentGroup.id}/members/${userId}`, {
method: 'DELETE',
credentials: 'include'
});

View File

@@ -2,5 +2,23 @@ import { sveltekit } from '@sveltejs/kit/vite';
import { defineConfig } from 'vite';
export default defineConfig({
plugins: [sveltekit()]
plugins: [sveltekit()],
server: {
host: 'localhost',
port: 5173,
proxy: {
'/api': {
target: 'http://10.77.48.43:3000',
changeOrigin: true,
configure: (proxy, _options) => {
proxy.on('proxyReq', (proxyReq, req, _res) => {
if (req.headers.cookie) {
proxyReq.setHeader('Cookie', req.headers.cookie);
}
console.log('[Backend Proxy]', req.method, req.url, '→ Cookies:', !!req.headers.cookie);
});
}
}
}
}
});