fix2
This commit is contained in:
@@ -45,6 +45,16 @@ app.get('/', (_req, res) => {
|
|||||||
res.redirect('/login.html')
|
res.redirect('/login.html')
|
||||||
})
|
})
|
||||||
|
|
||||||
app.listen(config.port, () => {
|
const server = app.listen(config.port, () => {
|
||||||
console.log(`Webklar Kundenbereich läuft auf Port ${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 <PID>, oder PORT=3001 in .env setzen.`
|
||||||
|
)
|
||||||
|
process.exit(1)
|
||||||
|
}
|
||||||
|
throw err
|
||||||
|
})
|
||||||
|
|||||||
@@ -1,7 +1,5 @@
|
|||||||
import { Router } from 'express'
|
import { Router } from 'express'
|
||||||
import { Query } from 'node-appwrite'
|
import { listDocuments, Query } from '../services/appwriteAdmin.js'
|
||||||
import { config } from '../config.js'
|
|
||||||
import { listDocuments } from '../services/appwriteAdmin.js'
|
|
||||||
import { getSessionCustomerId, requireSession } from '../middleware/session.js'
|
import { getSessionCustomerId, requireSession } from '../middleware/session.js'
|
||||||
|
|
||||||
const router = Router()
|
const router = Router()
|
||||||
|
|||||||
@@ -1,7 +1,5 @@
|
|||||||
import { Router } from 'express'
|
import { Router } from 'express'
|
||||||
import { Query } from 'node-appwrite'
|
import { listDocuments, Query } from '../services/appwriteAdmin.js'
|
||||||
import { config } from '../config.js'
|
|
||||||
import { listDocuments } from '../services/appwriteAdmin.js'
|
|
||||||
import { getSessionCustomerId, requireSession } from '../middleware/session.js'
|
import { getSessionCustomerId, requireSession } from '../middleware/session.js'
|
||||||
|
|
||||||
const router = Router()
|
const router = Router()
|
||||||
|
|||||||
@@ -1,37 +1,90 @@
|
|||||||
import { Client, Account, Databases, ID, Query } from 'node-appwrite'
|
import { randomUUID } from 'node:crypto'
|
||||||
import { config } from '../config.js'
|
import { config, WOMS_DATABASE_ID } from '../config.js'
|
||||||
|
|
||||||
export function createAdminClient() {
|
function buildQueries(queries = []) {
|
||||||
const client = new Client()
|
return queries.map((q) => {
|
||||||
.setEndpoint(config.appwrite.endpoint)
|
if (typeof q === 'string') return q
|
||||||
.setProject(config.appwrite.projectId)
|
return JSON.stringify(q)
|
||||||
.setKey(config.appwrite.apiKey)
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function adminHeaders() {
|
||||||
return {
|
return {
|
||||||
client,
|
'Content-Type': 'application/json',
|
||||||
databases: new Databases(client),
|
'X-Appwrite-Project': config.appwrite.projectId,
|
||||||
|
'X-Appwrite-Key': config.appwrite.apiKey,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function createUserClient() {
|
async function adminFetch(path, { method = 'GET', body, queries = [] } = {}) {
|
||||||
const client = new Client()
|
if (!config.appwrite.apiKey) {
|
||||||
.setEndpoint(config.appwrite.endpoint)
|
const error = new Error('APPWRITE_API_KEY fehlt in .env')
|
||||||
.setProject(config.appwrite.projectId)
|
error.status = 500
|
||||||
|
throw error
|
||||||
return {
|
|
||||||
client,
|
|
||||||
account: new Account(client),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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 = []) {
|
export async function listDocuments(collectionId, queries = []) {
|
||||||
const { databases } = createAdminClient()
|
const result = await adminFetch(collectionPath(collectionId), { queries })
|
||||||
const response = await databases.listDocuments(
|
return result.documents
|
||||||
config.appwrite.databaseId,
|
|
||||||
collectionId,
|
|
||||||
queries
|
|
||||||
)
|
|
||||||
return response.documents
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getCustomerByAppwriteUserId(appwriteUserId) {
|
export async function getCustomerByAppwriteUserId(appwriteUserId) {
|
||||||
@@ -51,17 +104,13 @@ export async function getPortalAccessByCustomerId(customerId) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export async function updateDocument(collectionId, documentId, data) {
|
export async function updateDocument(collectionId, documentId, data) {
|
||||||
const { databases } = createAdminClient()
|
return adminFetch(`${collectionPath(collectionId)}/${documentId}`, {
|
||||||
return databases.updateDocument(
|
method: 'PATCH',
|
||||||
config.appwrite.databaseId,
|
body: data,
|
||||||
collectionId,
|
})
|
||||||
documentId,
|
|
||||||
data
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function upsertWebsiteProjectByRepo(repoFullName, data) {
|
export async function upsertWebsiteProjectByRepo(repoFullName, data) {
|
||||||
const { databases } = createAdminClient()
|
|
||||||
const existing = await listDocuments(config.collections.websiteProjects, [
|
const existing = await listDocuments(config.collections.websiteProjects, [
|
||||||
Query.equal('repoFullName', repoFullName),
|
Query.equal('repoFullName', repoFullName),
|
||||||
Query.limit(1),
|
Query.limit(1),
|
||||||
@@ -71,18 +120,24 @@ export async function upsertWebsiteProjectByRepo(repoFullName, data) {
|
|||||||
const payload = { ...data, updatedAt: now }
|
const payload = { ...data, updatedAt: now }
|
||||||
|
|
||||||
if (existing[0]) {
|
if (existing[0]) {
|
||||||
return databases.updateDocument(
|
return updateDocument(
|
||||||
config.appwrite.databaseId,
|
|
||||||
config.collections.websiteProjects,
|
config.collections.websiteProjects,
|
||||||
existing[0].$id,
|
existing[0].$id,
|
||||||
payload
|
payload
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
return databases.createDocument(
|
return adminFetch(collectionPath(config.collections.websiteProjects), {
|
||||||
config.appwrite.databaseId,
|
method: 'POST',
|
||||||
config.collections.websiteProjects,
|
body: { ...payload, createdAt: now, documentId: ID.unique() },
|
||||||
ID.unique(),
|
})
|
||||||
{ ...payload, createdAt: now }
|
}
|
||||||
)
|
|
||||||
|
/** @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 }
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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) => {
|
const DEBUG_LOG = (location, message, data, hypothesisId) => {
|
||||||
// #region agent log
|
// #region agent log
|
||||||
@@ -17,54 +18,94 @@ const DEBUG_LOG = (location, message, data, hypothesisId) => {
|
|||||||
// #endregion
|
// #endregion
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function loginWithAppwrite(email, password) {
|
function appwriteHeaders() {
|
||||||
const { client, account } = createUserClient()
|
return {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'X-Appwrite-Project': config.appwrite.projectId,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
let session
|
async function appwriteFetch(path, { method = 'GET', body } = {}) {
|
||||||
try {
|
const url = `${config.appwrite.endpoint}${path}`
|
||||||
session = await account.createEmailPasswordSession(email, password)
|
const response = await fetch(url, {
|
||||||
DEBUG_LOG('appwriteClient.js:session', 'createEmailPasswordSession ok', {
|
method,
|
||||||
hasSecret: Boolean(session?.secret),
|
headers: appwriteHeaders(),
|
||||||
sessionId: session?.$id || null,
|
body: body ? JSON.stringify(body) : undefined,
|
||||||
}, 'H1')
|
})
|
||||||
} catch (err) {
|
|
||||||
DEBUG_LOG('appwriteClient.js:session', 'createEmailPasswordSession fail', {
|
let data = null
|
||||||
code: err?.code,
|
const text = await response.text()
|
||||||
type: err?.type,
|
if (text) {
|
||||||
message: err?.message?.slice(0, 120),
|
try {
|
||||||
}, 'H1')
|
data = JSON.parse(text)
|
||||||
const message = err?.message || 'Anmeldung fehlgeschlagen'
|
} catch {
|
||||||
const error = new Error(message)
|
data = { message: text }
|
||||||
error.status = 401
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
throw error
|
||||||
}
|
}
|
||||||
|
|
||||||
if (session?.secret) {
|
return data
|
||||||
client.setSession(session.secret)
|
}
|
||||||
DEBUG_LOG('appwriteClient.js:setSession', 'setSession applied', { hasSessionHeader: true }, 'H2')
|
|
||||||
} else {
|
/**
|
||||||
DEBUG_LOG('appwriteClient.js:setSession', 'no session.secret', {}, 'H2')
|
* 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
|
let user
|
||||||
try {
|
try {
|
||||||
user = await account.get()
|
user = await getUserById(session.userId)
|
||||||
DEBUG_LOG('appwriteClient.js:get', 'account.get ok', { userId: user?.$id || null }, 'H2')
|
DEBUG_LOG('appwriteClient.js:getUser', 'users.get ok', { userId: user?.$id || null }, 'H6')
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
DEBUG_LOG('appwriteClient.js:get', 'account.get fail', {
|
DEBUG_LOG('appwriteClient.js:getUser', 'users.get fail, fallback', {
|
||||||
code: err?.code,
|
message: err?.message?.slice(0, 80),
|
||||||
message: err?.message?.slice(0, 120),
|
}, 'H6')
|
||||||
}, 'H2')
|
user = { $id: session.userId, email, name: '' }
|
||||||
const message = err?.message || 'Anmeldung fehlgeschlagen'
|
|
||||||
const error = new Error(message)
|
|
||||||
error.status = err?.message?.includes('scopes') ? 401 : 500
|
|
||||||
throw error
|
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
if (session.$id) {
|
||||||
await account.deleteSession('current')
|
try {
|
||||||
} catch {
|
await deleteUserSession(session.userId, session.$id)
|
||||||
// Portal nutzt eigene Session; Appwrite-Session wird nicht persistiert
|
} catch {
|
||||||
|
// Portal nutzt eigene Session; Appwrite-Session optional aufräumen
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return user
|
return user
|
||||||
|
|||||||
Reference in New Issue
Block a user