mirror of
https://github.com/ceratic/project_vollidioten_website.git
synced 2026-05-14 00:16:47 +02:00
feat: Add DatabaseManager and LinkPlayer components, implement authentication and linking logic
- Created DatabaseManager component for managing database access via phpMyAdmin. - Developed LinkPlayer component to link Discord accounts with game characters, including user authentication and error handling. - Added mock data files for players, organizations, and projects to handle backend unavailability. - Implemented AuthService for managing user authentication and session checks. - Created DatabaseService to fetch and manage player, organization, and project data with fallback to mock data. - Added HTML page for handling authentication unavailability. - Developed a test script for validating Docker setup and required files.
This commit is contained in:
54
services/AuthService.ts
Normal file
54
services/AuthService.ts
Normal file
@@ -0,0 +1,54 @@
|
||||
import { DiscordUser } from '../types';
|
||||
|
||||
// Points to the production backend via Traefik
|
||||
const API_URL = 'https://vollidioten.ceraticsoft.de';
|
||||
|
||||
class AuthService {
|
||||
private user: DiscordUser | null = null;
|
||||
private listeners: ((user: DiscordUser | null) => void)[] = [];
|
||||
|
||||
constructor() {
|
||||
this.checkSession();
|
||||
}
|
||||
|
||||
getUser(): DiscordUser | null {
|
||||
return this.user;
|
||||
}
|
||||
|
||||
// Check if session cookie exists and is valid
|
||||
async checkSession() {
|
||||
try {
|
||||
const res = await fetch(`${API_URL}/auth/me`, { credentials: 'include' });
|
||||
if (res.ok) {
|
||||
const data = await res.json();
|
||||
this.user = data;
|
||||
this.notifyListeners();
|
||||
}
|
||||
} catch (e) {
|
||||
console.log("Auth check failed (Backend might be offline)", e);
|
||||
}
|
||||
}
|
||||
|
||||
// Redirects to Discord OAuth
|
||||
async login(): Promise<void> {
|
||||
window.location.href = `${API_URL}/auth/discord`;
|
||||
}
|
||||
|
||||
logout() {
|
||||
window.location.href = `${API_URL}/auth/logout`;
|
||||
}
|
||||
|
||||
subscribe(listener: (user: DiscordUser | null) => void) {
|
||||
this.listeners.push(listener);
|
||||
listener(this.user);
|
||||
return () => {
|
||||
this.listeners = this.listeners.filter(l => l !== listener);
|
||||
};
|
||||
}
|
||||
|
||||
private notifyListeners() {
|
||||
this.listeners.forEach(l => l(this.user));
|
||||
}
|
||||
}
|
||||
|
||||
export const authService = new AuthService();
|
||||
169
services/DatabaseService.ts
Normal file
169
services/DatabaseService.ts
Normal file
@@ -0,0 +1,169 @@
|
||||
import { MOCK_PLAYERS, MOCK_ORGS, MOCK_PROJECTS } from '../constants';
|
||||
import { Player, Organization, Project } from '../types';
|
||||
|
||||
const API_URL = 'https://vollidioten.ceraticsoft.de/api';
|
||||
const MOCK_DATA_HEADER = 'X-Mock-Data';
|
||||
|
||||
class DatabaseService {
|
||||
private players: Player[] = MOCK_PLAYERS;
|
||||
private orgs: Organization[] = MOCK_ORGS;
|
||||
private projects: Project[] = MOCK_PROJECTS;
|
||||
private listeners: Function[] = [];
|
||||
|
||||
constructor() {
|
||||
this.fetchAll();
|
||||
}
|
||||
|
||||
// Try to fetch real data from backend
|
||||
async fetchAll() {
|
||||
try {
|
||||
console.log("Fetching data from API...");
|
||||
const [pRes, oRes, prRes] = await Promise.all([
|
||||
fetch(`${API_URL}/players`),
|
||||
fetch(`${API_URL}/orgs`),
|
||||
fetch(`${API_URL}/projects`)
|
||||
]);
|
||||
|
||||
// Check if we're getting mock data from nginx fallback
|
||||
const isMockData = pRes.headers.get(MOCK_DATA_HEADER) === 'true' ||
|
||||
oRes.headers.get(MOCK_DATA_HEADER) === 'true' ||
|
||||
prRes.headers.get(MOCK_DATA_HEADER) === 'true';
|
||||
|
||||
if (pRes.ok && oRes.ok && prRes.ok) {
|
||||
const playersData = await pRes.json();
|
||||
const orgsData = await oRes.json();
|
||||
const projectsData = await prRes.json();
|
||||
|
||||
console.log(`Loaded ${playersData.length} players, ${orgsData.length} orgs, ${projectsData.length} projects from API`);
|
||||
|
||||
this.players = playersData;
|
||||
this.orgs = orgsData;
|
||||
this.projects = projectsData;
|
||||
this.notify();
|
||||
|
||||
if (isMockData) {
|
||||
console.warn("Backend unavailable - using mock data from nginx fallback");
|
||||
} else {
|
||||
console.log("✅ Connected to Backend Database - using real data");
|
||||
}
|
||||
} else {
|
||||
console.warn(`API returned errors: players=${pRes.status}, orgs=${oRes.status}, projects=${prRes.status}`);
|
||||
console.warn("Using built-in mock data");
|
||||
// Data is already set to mock data in constructor
|
||||
}
|
||||
} catch (e) {
|
||||
console.warn("Backend not available, using built-in Mock Data.", e);
|
||||
// Fallback is already set in constructor
|
||||
}
|
||||
}
|
||||
|
||||
subscribe(cb: Function) {
|
||||
this.listeners.push(cb);
|
||||
return () => {
|
||||
this.listeners = this.listeners.filter(l => l !== cb);
|
||||
};
|
||||
}
|
||||
|
||||
notify() {
|
||||
this.listeners.forEach(cb => cb());
|
||||
}
|
||||
|
||||
// --- READ ---
|
||||
|
||||
getPlayers(): Player[] { return this.players; }
|
||||
getOrgs(): Organization[] { return this.orgs; }
|
||||
getProjects(): Project[] { return this.projects; }
|
||||
|
||||
getPlayer(uuid: string): Player | undefined {
|
||||
return this.players.find(p => p.uuid === uuid);
|
||||
}
|
||||
|
||||
getOrg(id: string): Organization | undefined {
|
||||
return this.orgs.find(o => o.id === id);
|
||||
}
|
||||
|
||||
getProject(id: string): Project | undefined {
|
||||
return this.projects.find(p => p.id === id);
|
||||
}
|
||||
|
||||
// --- WRITE (Optimistic UI + API Call) ---
|
||||
|
||||
async updatePlayer(uuid: string, updates: Partial<Player>) {
|
||||
// 1. Optimistic Update
|
||||
this.players = this.players.map(p => p.uuid === uuid ? { ...p, ...updates } : p);
|
||||
this.notify();
|
||||
|
||||
// 2. API Call
|
||||
try {
|
||||
await fetch(`${API_URL}/players/${uuid}`, {
|
||||
method: 'PUT',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(updates),
|
||||
credentials: 'include' // Sends session cookie
|
||||
});
|
||||
} catch (e) {
|
||||
console.error("Failed to save player update", e);
|
||||
}
|
||||
}
|
||||
|
||||
async updateProject(id: string, updates: Partial<Project>) {
|
||||
this.projects = this.projects.map(p => p.id === id ? { ...p, ...updates } : p);
|
||||
this.notify();
|
||||
|
||||
try {
|
||||
await fetch(`${API_URL}/projects/${id}`, {
|
||||
method: 'PUT',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(updates),
|
||||
credentials: 'include'
|
||||
});
|
||||
} catch (e) {
|
||||
console.error("Failed to save project update", e);
|
||||
}
|
||||
}
|
||||
|
||||
// --- PROJECT MANAGEMENT ---
|
||||
async createProject(projectData: { title: string; description: string; category: Project['category'] }): Promise<boolean> {
|
||||
try {
|
||||
const response = await fetch(`${API_URL}/projects`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
credentials: 'include',
|
||||
body: JSON.stringify(projectData),
|
||||
});
|
||||
|
||||
if (response.ok) {
|
||||
// Reload data to include the new project
|
||||
await this.fetchAll();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
} catch (e) {
|
||||
console.error("Failed to create project", e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// --- LINKING ---
|
||||
async linkPlayer(playerUuid: string): Promise<boolean> {
|
||||
try {
|
||||
const res = await fetch(`${API_URL}/link-user`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ playerUuid }),
|
||||
credentials: 'include'
|
||||
});
|
||||
if (res.ok) {
|
||||
// Reload data to reflect linking (might unlock edit buttons)
|
||||
await this.fetchAll();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
} catch(e) {
|
||||
console.error("Link failed", e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export const dbService = new DatabaseService();
|
||||
Reference in New Issue
Block a user