first commit
This commit is contained in:
12
.env.example
Normal file
12
.env.example
Normal 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"
|
||||||
@@ -9,17 +9,10 @@ async function bootstrap() {
|
|||||||
|
|
||||||
// CORS Konfiguration
|
// CORS Konfiguration
|
||||||
app.enableCors({
|
app.enableCors({
|
||||||
origin: [
|
origin: true,
|
||||||
'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',
|
|
||||||
'*'
|
|
||||||
],
|
|
||||||
credentials: true,
|
credentials: true,
|
||||||
methods: ['GET', 'POST', 'PUT', 'DELETE', 'PATCH', 'OPTIONS'],
|
methods: ['GET', 'POST', 'PUT', 'DELETE', 'PATCH', 'OPTIONS'],
|
||||||
allowedHeaders: ['Content-Type', 'Authorization'],
|
allowedHeaders: ['Content-Type', 'Authorization', 'Accept'],
|
||||||
});
|
});
|
||||||
|
|
||||||
// Globales Präfix für alle Routes
|
// 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();
|
bootstrap();
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import { browser } from '$app/environment';
|
|||||||
|
|
||||||
// API Client für Backend-Kommunikation
|
// API Client für Backend-Kommunikation
|
||||||
const API_BASE_URL = browser
|
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');
|
: (import.meta.env.VITE_INTERNAL_API_URL || 'http://backend:3000/api');
|
||||||
|
|
||||||
interface ApiResponse<T> {
|
interface ApiResponse<T> {
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ class ClassroomStore {
|
|||||||
if (this.socket?.connected) return;
|
if (this.socket?.connected) return;
|
||||||
if (!authStore.user) 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,
|
withCredentials: true,
|
||||||
transports: ['websocket'],
|
transports: ['websocket'],
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -39,8 +39,9 @@ class WhiteboardStore {
|
|||||||
this.whiteboardId = whiteboardId;
|
this.whiteboardId = whiteboardId;
|
||||||
this.userId = userId;
|
this.userId = userId;
|
||||||
|
|
||||||
// WebSocket-Verbindung herstellen
|
if (this.socket) return;
|
||||||
this.socket = io('http://localhost:3000/whiteboard', {
|
|
||||||
|
this.socket = io(`http://${window.location.hostname}:3000/whiteboard`, {
|
||||||
withCredentials: true,
|
withCredentials: true,
|
||||||
transports: ['websocket', 'polling']
|
transports: ['websocket', 'polling']
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { redirect } from '@sveltejs/kit';
|
import { redirect } from '@sveltejs/kit';
|
||||||
import type { LayoutLoad } from './$types';
|
import type { LayoutLoad } from './$types';
|
||||||
|
import { browser } from '$app/environment';
|
||||||
|
|
||||||
// Routen die NICHT authentifiziert sein müssen
|
// Routen die NICHT authentifiziert sein müssen
|
||||||
const PUBLIC_ROUTES = ['/', '/login', '/register'];
|
const PUBLIC_ROUTES = ['/', '/login', '/register'];
|
||||||
@@ -10,9 +11,13 @@ export const load: LayoutLoad = async ({ url, fetch }) => {
|
|||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const apiBase = browser
|
||||||
|
? `http://${window.location.hostname}:3000/api`
|
||||||
|
: 'http://localhost:3000/api';
|
||||||
|
|
||||||
// Versuche den User zu laden
|
// Versuche den User zu laden
|
||||||
try {
|
try {
|
||||||
const response = await fetch('http://localhost:3000/api/auth/me', {
|
const response = await fetch(`${apiBase}/auth/me`, {
|
||||||
credentials: 'include'
|
credentials: 'include'
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -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;
|
||||||
@@ -59,6 +59,9 @@
|
|||||||
let showMembersModal = $state(false);
|
let showMembersModal = $state(false);
|
||||||
let allUsers = $state<User[]>([]);
|
let allUsers = $state<User[]>([]);
|
||||||
let selectedUserIds = $state<number[]>([]);
|
let selectedUserIds = $state<number[]>([]);
|
||||||
|
|
||||||
|
// Dynamic Base URL
|
||||||
|
const baseUrl = `http://${window.location.hostname}:3000`;
|
||||||
|
|
||||||
$effect(() => {
|
$effect(() => {
|
||||||
if (!authStore.isLoading) {
|
if (!authStore.isLoading) {
|
||||||
@@ -82,7 +85,7 @@
|
|||||||
|
|
||||||
async function loadGroups() {
|
async function loadGroups() {
|
||||||
try {
|
try {
|
||||||
const response = await fetch('http://localhost:3000/api/live/groups/my', {
|
const response = await fetch(`${baseUrl}/api/live/groups/my`, {
|
||||||
credentials: 'include'
|
credentials: 'include'
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -110,7 +113,7 @@
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
// Nachrichten-Historie laden
|
// 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'
|
credentials: 'include'
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -151,7 +154,7 @@
|
|||||||
console.log('User authenticated:', authStore.isAuthenticated);
|
console.log('User authenticated:', authStore.isAuthenticated);
|
||||||
|
|
||||||
// Socket.io Verbindung - das httpOnly Cookie wird automatisch mitgesendet
|
// Socket.io Verbindung - das httpOnly Cookie wird automatisch mitgesendet
|
||||||
socket = io('http://localhost:3000/api/live', {
|
socket = io(`${baseUrl}/api/live`, {
|
||||||
withCredentials: true,
|
withCredentials: true,
|
||||||
transports: ['websocket', 'polling']
|
transports: ['websocket', 'polling']
|
||||||
});
|
});
|
||||||
@@ -233,7 +236,7 @@
|
|||||||
payload.description = newGroupDescription.trim();
|
payload.description = newGroupDescription.trim();
|
||||||
}
|
}
|
||||||
|
|
||||||
const response = await fetch('http://localhost:3000/api/live/groups', {
|
const response = await fetch(`${baseUrl}/api/live/groups`, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'application/json'
|
'Content-Type': 'application/json'
|
||||||
@@ -262,7 +265,7 @@
|
|||||||
if (!confirm('Möchten Sie diese Gruppe wirklich löschen?')) return;
|
if (!confirm('Möchten Sie diese Gruppe wirklich löschen?')) return;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const response = await fetch(`http://localhost:3000/api/live/groups/${groupId}`, {
|
const response = await fetch(`${baseUrl}/api/live/groups/${groupId}`, {
|
||||||
method: 'DELETE',
|
method: 'DELETE',
|
||||||
credentials: 'include'
|
credentials: 'include'
|
||||||
});
|
});
|
||||||
@@ -305,7 +308,7 @@
|
|||||||
|
|
||||||
async function loadAllUsers() {
|
async function loadAllUsers() {
|
||||||
try {
|
try {
|
||||||
const response = await fetch('http://localhost:3000/api/users', {
|
const response = await fetch(`${baseUrl}/api/users`, {
|
||||||
credentials: 'include'
|
credentials: 'include'
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -328,7 +331,7 @@
|
|||||||
if (!currentGroup || selectedUserIds.length === 0) return;
|
if (!currentGroup || selectedUserIds.length === 0) return;
|
||||||
|
|
||||||
try {
|
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',
|
method: 'POST',
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'application/json'
|
'Content-Type': 'application/json'
|
||||||
@@ -360,7 +363,7 @@
|
|||||||
if (!confirm('Möchten Sie dieses Mitglied wirklich entfernen?')) return;
|
if (!confirm('Möchten Sie dieses Mitglied wirklich entfernen?')) return;
|
||||||
|
|
||||||
try {
|
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',
|
method: 'DELETE',
|
||||||
credentials: 'include'
|
credentials: 'include'
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -2,5 +2,23 @@ import { sveltekit } from '@sveltejs/kit/vite';
|
|||||||
import { defineConfig } from 'vite';
|
import { defineConfig } from 'vite';
|
||||||
|
|
||||||
export default defineConfig({
|
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);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user