Files
Webklar-Kundenbereich/server/services/appwriteClient.js
Kenso Grimm 177ee6a13d feat: Admin-Bereich im Kundenportal (Ticketsystem-Login, Projektsuche, Kundenverwaltung)
- Login: Appwrite-User mit Label 'admin' (Ticketsystem-Mitarbeiter) bekommen
  eine Admin-Session und landen auf /admin.html
- Admin-Dashboard: Statistik-Uebersicht, Projektsuche ueber alle Projekte
  (inkl. Preview-/Gitea-Links, Vorschau ohne Kunden-Login), Kundenliste mit
  Portal-Status, letztem Login und Projektanzahl
- Kundennummer (code) direkt im Admin-Bereich pflegbar
- 'Als Kunde ansehen': Admin sieht das Portal exakt wie der Kunde (Banner
  mit Rueckweg zur Admin-Uebersicht)
- Kunden-Dashboard: neue Bereiche 'Meine Rechnungen' (InvoiceNinja inkl.
  Link ins Rechnungsportal) und 'Was wurde gemacht' (Tickets + Worksheets
  aus dem Ticketsystem, ohne interne Akquise-Tickets); Kundennummer im Header
- Admin-API /api/portal-admin/* (Session-basiert), Kunden-API /api/invoices
  und /api/activity
- appwriteClient: uebrig gebliebene Debug-Instrumentierung entfernt

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-11 17:09:39 +00:00

97 lines
2.4 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { config } from '../config.js'
function appwriteHeaders() {
return {
'Content-Type': 'application/json',
'X-Appwrite-Project': config.appwrite.projectId,
}
}
async function appwriteFetch(path, { method = 'GET', body } = {}) {
const url = `${config.appwrite.endpoint}${path}`
const response = await fetch(url, {
method,
headers: appwriteHeaders(),
body: body ? JSON.stringify(body) : undefined,
})
let data = null
const text = await response.text()
if (text) {
try {
data = JSON.parse(text)
} catch {
data = { message: text }
}
}
if (!response.ok) {
const error = new Error(data?.message || `Appwrite ${response.status}`)
error.status = response.status
error.code = data?.code
error.type = data?.type
if (response.status === 429 || data?.type === 'general_rate_limit_exceeded') {
error.message =
'Zu viele Anmeldeversuche. Bitte warte einige Minuten, bevor du es erneut versuchst.'
error.status = 429
}
throw error
}
return data
}
/**
* Login via Appwrite Auth REST. userId kommt aus der Session kein users.read nötig.
*/
let appwriteLoginBlockedUntil = 0
const APPWRITE_LOGIN_COOLDOWN_MS = 5 * 60 * 1000
const APPWRITE_RATE_LIMIT_COOLDOWN_MS = 15 * 60 * 1000
export function getLoginCooldownRemainingSec() {
const left = appwriteLoginBlockedUntil - Date.now()
return left > 0 ? Math.ceil(left / 1000) : 0
}
export function clearLoginCooldown() {
appwriteLoginBlockedUntil = 0
}
export async function loginWithAppwrite(email, password) {
const now = Date.now()
if (now < appwriteLoginBlockedUntil) {
const waitSec = Math.ceil((appwriteLoginBlockedUntil - now) / 1000)
const error = new Error(
`Zu viele Anmeldeversuche. Bitte warte noch ${waitSec} Sekunden.`
)
error.status = 429
throw error
}
let session
try {
session = await appwriteFetch('/account/sessions/email', {
method: 'POST',
body: { email, password },
})
} catch (err) {
if (err.status === 429) {
appwriteLoginBlockedUntil = Date.now() + APPWRITE_RATE_LIMIT_COOLDOWN_MS
}
const error = new Error(err.message || 'Anmeldung fehlgeschlagen')
error.status = err.status || 401
throw error
}
if (!session?.userId) {
const error = new Error('Appwrite-Session ohne userId.')
error.status = 500
throw error
}
const user = { $id: session.userId, email, name: '' }
clearLoginCooldown()
return user
}