From 29918f2a8ae16f8dbce87f742eb65a340caa22e7 Mon Sep 17 00:00:00 2001 From: KNSO Date: Sat, 23 May 2026 00:21:31 +0200 Subject: [PATCH] fix2 --- server/index.js | 12 ++- server/routes/features.js | 4 +- server/routes/projects.js | 4 +- server/services/appwriteAdmin.js | 135 +++++++++++++++++++++--------- server/services/appwriteClient.js | 117 +++++++++++++++++--------- 5 files changed, 187 insertions(+), 85 deletions(-) diff --git a/server/index.js b/server/index.js index 101ba5d..a382f1f 100644 --- a/server/index.js +++ b/server/index.js @@ -45,6 +45,16 @@ app.get('/', (_req, res) => { res.redirect('/login.html') }) -app.listen(config.port, () => { +const server = app.listen(config.port, () => { console.log(`Webklar Kundenbereich läuft auf Port ${config.port}`) }) + +server.on('error', (err) => { + if (err.code === 'EADDRINUSE') { + console.error( + `[server] Port ${config.port} ist bereits belegt. Alten Prozess beenden: lsof -i :${config.port} dann kill , oder PORT=3001 in .env setzen.` + ) + process.exit(1) + } + throw err +}) diff --git a/server/routes/features.js b/server/routes/features.js index 212e159..a252186 100644 --- a/server/routes/features.js +++ b/server/routes/features.js @@ -1,7 +1,5 @@ import { Router } from 'express' -import { Query } from 'node-appwrite' -import { config } from '../config.js' -import { listDocuments } from '../services/appwriteAdmin.js' +import { listDocuments, Query } from '../services/appwriteAdmin.js' import { getSessionCustomerId, requireSession } from '../middleware/session.js' const router = Router() diff --git a/server/routes/projects.js b/server/routes/projects.js index d4f50fd..c08e5c0 100644 --- a/server/routes/projects.js +++ b/server/routes/projects.js @@ -1,7 +1,5 @@ import { Router } from 'express' -import { Query } from 'node-appwrite' -import { config } from '../config.js' -import { listDocuments } from '../services/appwriteAdmin.js' +import { listDocuments, Query } from '../services/appwriteAdmin.js' import { getSessionCustomerId, requireSession } from '../middleware/session.js' const router = Router() diff --git a/server/services/appwriteAdmin.js b/server/services/appwriteAdmin.js index 14bee2d..8c024d0 100644 --- a/server/services/appwriteAdmin.js +++ b/server/services/appwriteAdmin.js @@ -1,37 +1,90 @@ -import { Client, Account, Databases, ID, Query } from 'node-appwrite' -import { config } from '../config.js' +import { randomUUID } from 'node:crypto' +import { config, WOMS_DATABASE_ID } from '../config.js' -export function createAdminClient() { - const client = new Client() - .setEndpoint(config.appwrite.endpoint) - .setProject(config.appwrite.projectId) - .setKey(config.appwrite.apiKey) +function buildQueries(queries = []) { + return queries.map((q) => { + if (typeof q === 'string') return q + return JSON.stringify(q) + }) +} +function adminHeaders() { return { - client, - databases: new Databases(client), + 'Content-Type': 'application/json', + 'X-Appwrite-Project': config.appwrite.projectId, + 'X-Appwrite-Key': config.appwrite.apiKey, } } -export function createUserClient() { - const client = new Client() - .setEndpoint(config.appwrite.endpoint) - .setProject(config.appwrite.projectId) - - return { - client, - account: new Account(client), +async function adminFetch(path, { method = 'GET', body, queries = [] } = {}) { + if (!config.appwrite.apiKey) { + const error = new Error('APPWRITE_API_KEY fehlt in .env') + error.status = 500 + throw error } + + const url = new URL(`${config.appwrite.endpoint}${path}`) + for (const q of buildQueries(queries)) { + url.searchParams.append('queries[]', q) + } + + const response = await fetch(url.toString(), { + method, + headers: adminHeaders(), + body: body ? JSON.stringify(body) : undefined, + }) + + const text = await response.text() + let data = null + 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 >= 500 ? 500 : response.status + error.code = data?.code + throw error + } + + return data +} + +/** Appwrite Query-Helper (kompatibel zu bisherigen Aufrufen) */ +export const Query = { + equal: (attribute, value) => ({ + method: 'equal', + attribute, + values: Array.isArray(value) ? value : [value], + }), + limit: (n) => ({ method: 'limit', values: [n] }), + orderDesc: (attribute) => ({ method: 'orderDesc', attribute }), + orderAsc: (attribute) => ({ method: 'orderAsc', attribute }), +} + +export const ID = { + unique: () => randomUUID(), +} + +function collectionPath(collectionId) { + return `/databases/${config.appwrite.databaseId}/collections/${collectionId}/documents` +} + +export async function getUserById(userId) { + return adminFetch(`/users/${userId}`) +} + +export async function deleteUserSession(userId, sessionId) { + return adminFetch(`/users/${userId}/sessions/${sessionId}`, { method: 'DELETE' }) } export async function listDocuments(collectionId, queries = []) { - const { databases } = createAdminClient() - const response = await databases.listDocuments( - config.appwrite.databaseId, - collectionId, - queries - ) - return response.documents + const result = await adminFetch(collectionPath(collectionId), { queries }) + return result.documents } export async function getCustomerByAppwriteUserId(appwriteUserId) { @@ -51,17 +104,13 @@ export async function getPortalAccessByCustomerId(customerId) { } export async function updateDocument(collectionId, documentId, data) { - const { databases } = createAdminClient() - return databases.updateDocument( - config.appwrite.databaseId, - collectionId, - documentId, - data - ) + return adminFetch(`${collectionPath(collectionId)}/${documentId}`, { + method: 'PATCH', + body: data, + }) } export async function upsertWebsiteProjectByRepo(repoFullName, data) { - const { databases } = createAdminClient() const existing = await listDocuments(config.collections.websiteProjects, [ Query.equal('repoFullName', repoFullName), Query.limit(1), @@ -71,18 +120,24 @@ export async function upsertWebsiteProjectByRepo(repoFullName, data) { const payload = { ...data, updatedAt: now } if (existing[0]) { - return databases.updateDocument( - config.appwrite.databaseId, + return updateDocument( config.collections.websiteProjects, existing[0].$id, payload ) } - return databases.createDocument( - config.appwrite.databaseId, - config.collections.websiteProjects, - ID.unique(), - { ...payload, createdAt: now } - ) + return adminFetch(collectionPath(config.collections.websiteProjects), { + method: 'POST', + body: { ...payload, createdAt: now, documentId: ID.unique() }, + }) +} + +/** @deprecated Nur für Kompatibilität – nutzt native fetch */ +export function createAdminClient() { + return { usesNativeFetch: true, databaseId: WOMS_DATABASE_ID } +} + +export function createUserClient() { + return { usesNativeFetch: true } } diff --git a/server/services/appwriteClient.js b/server/services/appwriteClient.js index c68df74..933fbe8 100644 --- a/server/services/appwriteClient.js +++ b/server/services/appwriteClient.js @@ -1,4 +1,5 @@ -import { createUserClient } from './appwriteAdmin.js' +import { config } from '../config.js' +import { deleteUserSession, getUserById } from './appwriteAdmin.js' const DEBUG_LOG = (location, message, data, hypothesisId) => { // #region agent log @@ -17,54 +18,94 @@ const DEBUG_LOG = (location, message, data, hypothesisId) => { // #endregion } -export async function loginWithAppwrite(email, password) { - const { client, account } = createUserClient() +function appwriteHeaders() { + return { + 'Content-Type': 'application/json', + 'X-Appwrite-Project': config.appwrite.projectId, + } +} - let session - try { - session = await account.createEmailPasswordSession(email, password) - DEBUG_LOG('appwriteClient.js:session', 'createEmailPasswordSession ok', { - hasSecret: Boolean(session?.secret), - sessionId: session?.$id || null, - }, 'H1') - } catch (err) { - DEBUG_LOG('appwriteClient.js:session', 'createEmailPasswordSession fail', { - code: err?.code, - type: err?.type, - message: err?.message?.slice(0, 120), - }, 'H1') - const message = err?.message || 'Anmeldung fehlgeschlagen' - const error = new Error(message) - error.status = 401 +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 === 401 ? 401 : response.status >= 500 ? 500 : 401 + error.code = data?.code + error.type = data?.type throw error } - if (session?.secret) { - client.setSession(session.secret) - DEBUG_LOG('appwriteClient.js:setSession', 'setSession applied', { hasSessionHeader: true }, 'H2') - } else { - DEBUG_LOG('appwriteClient.js:setSession', 'no session.secret', {}, 'H2') + return data +} + +/** + * Appwrite Auth per native fetch (Node 26 + node-appwrite-Agent ist inkompatibel). + * Session.secret wird serverseitig oft nicht zurückgegeben – userId aus Session nutzen. + */ +export async function loginWithAppwrite(email, password) { + let session + try { + session = await appwriteFetch('/account/sessions/email', { + method: 'POST', + body: { email, password }, + }) + DEBUG_LOG('appwriteClient.js:session', 'createEmailPasswordSession ok', { + hasSecret: Boolean(session?.secret), + hasUserId: Boolean(session?.userId), + sessionId: session?.$id || null, + }, 'H6') + } catch (err) { + DEBUG_LOG('appwriteClient.js:session', 'createEmailPasswordSession fail', { + message: err?.message?.slice(0, 120), + code: err?.code, + }, 'H1') + const error = new Error(err.message || 'Anmeldung fehlgeschlagen') + error.status = err.status || 401 + throw error + } + + if (!session?.userId) { + DEBUG_LOG('appwriteClient.js:session', 'no userId in session', { + sessionKeys: session ? Object.keys(session).filter((k) => !k.startsWith('provider')) : [], + }, 'H6') + const error = new Error('Appwrite-Session ohne userId.') + error.status = 500 + throw error } let user try { - user = await account.get() - DEBUG_LOG('appwriteClient.js:get', 'account.get ok', { userId: user?.$id || null }, 'H2') + user = await getUserById(session.userId) + DEBUG_LOG('appwriteClient.js:getUser', 'users.get ok', { userId: user?.$id || null }, 'H6') } catch (err) { - DEBUG_LOG('appwriteClient.js:get', 'account.get fail', { - code: err?.code, - message: err?.message?.slice(0, 120), - }, 'H2') - const message = err?.message || 'Anmeldung fehlgeschlagen' - const error = new Error(message) - error.status = err?.message?.includes('scopes') ? 401 : 500 - throw error + DEBUG_LOG('appwriteClient.js:getUser', 'users.get fail, fallback', { + message: err?.message?.slice(0, 80), + }, 'H6') + user = { $id: session.userId, email, name: '' } } - try { - await account.deleteSession('current') - } catch { - // Portal nutzt eigene Session; Appwrite-Session wird nicht persistiert + if (session.$id) { + try { + await deleteUserSession(session.userId, session.$id) + } catch { + // Portal nutzt eigene Session; Appwrite-Session optional aufräumen + } } return user