Files
Webklar-Kundenbereich/server/config.js
T
ClaudeandClaude Opus 5.5 48b13f4488 Abo: Uebernahmegutschrift fuer beim Altanbieter bezahlte Leistungen
- Kunde (Dashboard, nur mit laufendem Abo): Antrag mit Leistung, Anbieter,
  Domain, Rechnungsbetrag/-zeitraum, Grund, Rechnungsnachweis (PDF/JPG/PNG)
  und E-Mail fuer die Antwort; Eingangsbestaetigung per Mail, Status und
  Antwort auf Rueckfragen im Portal
- Admin-Mail mit Link admin.html?gutschrift=<id> (Login leitet zurueck)
- Admin-Tab "Gutschriften": Kunde und Vertrag, Nachweise, Verlauf,
  automatische Pruefungen (Duplikate per Datei-Hash/Domain, Abo, Zeitraum,
  DNS-Abgleich Nameserver/MX mit Anbieter), Pflicht-Checkliste, Rechner
  (tagesgenau anteilig), Genehmigen/Rueckfrage/Ablehnen mit Kundenmail
- Genehmigte Gutschrift als Stripe-Kundenguthaben (idempotent, Nachbuchen
  bei Stripe-Stoerung)
- Nachweise in upload-data/ (gitignored), Loeschung abgelehnter nach 6 Monaten
- Stundenkontingent auch fuer Monate, die ein Guthaben komplett deckt
  (invoice.total statt amount_paid)
- Datenschutzerklaerung Abschnitt 7 ergaenzt, PDF neu erzeugt
- Collection takeoverCredits in setup-abo-collections.mjs

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-25 19:34:33 +00:00

172 lines
7.5 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 'dotenv/config'
import path from 'node:path'
/** Einzige erlaubte Datenbank (Ticketsystem). Keine separate Portal-DB mehr. */
export const WOMS_DATABASE_ID = 'woms-database'
const DEPRECATED_DATABASE_IDS = new Set([
'6943bf0e0003291f8c35', // frühere Portal-/Legacy-DB
])
function resolveDatabaseId() {
const fromEnv = (process.env.APPWRITE_DATABASE_ID || '').trim()
if (!fromEnv || fromEnv === WOMS_DATABASE_ID) return WOMS_DATABASE_ID
if (DEPRECATED_DATABASE_IDS.has(fromEnv)) {
console.warn(
`[config] Veraltete APPWRITE_DATABASE_ID "${fromEnv}" – es wird nur ${WOMS_DATABASE_ID} verwendet.`
)
return WOMS_DATABASE_ID
}
console.warn(
`[config] APPWRITE_DATABASE_ID="${fromEnv}" ignoriert – Portal nutzt ausschließlich ${WOMS_DATABASE_ID}.`
)
return WOMS_DATABASE_ID
}
export const config = {
port: Number(process.env.PORT) || 3000,
sessionSecret: process.env.SESSION_SECRET || '',
cookieName: process.env.SESSION_COOKIE_NAME || 'webklar_portal_session',
allowedCustomerStatuses: (process.env.ALLOWED_CUSTOMER_STATUSES || 'active')
.split(',')
.map((s) => s.trim())
.filter(Boolean),
appwrite: {
endpoint: process.env.APPWRITE_ENDPOINT || 'https://ticket.webklar.com/v1',
projectId: process.env.APPWRITE_PROJECT_ID || '6a1058610003c5a13a05',
databaseId: resolveDatabaseId(),
apiKey: process.env.APPWRITE_API_KEY || '',
},
collections: {
customers: process.env.APPWRITE_COLLECTION_CUSTOMERS || 'customers',
customerPortalAccess: process.env.APPWRITE_COLLECTION_CUSTOMER_PORTAL_ACCESS || 'customerPortalAccess',
websiteProjects: process.env.APPWRITE_COLLECTION_WEBSITE_PROJECTS || 'websiteProjects',
portalFeatures: process.env.APPWRITE_COLLECTION_PORTAL_FEATURES || 'portalFeatures',
workorders: process.env.APPWRITE_COLLECTION_WORKORDERS || 'workorders',
worksheets: process.env.APPWRITE_COLLECTION_WORKSHEETS || 'worksheets',
employees: process.env.APPWRITE_COLLECTION_EMPLOYEES || 'employees',
portalMessages: process.env.APPWRITE_COLLECTION_PORTAL_MESSAGES || 'portalMessages',
subscriptions: process.env.APPWRITE_COLLECTION_SUBSCRIPTIONS || 'subscriptions',
hourCredits: process.env.APPWRITE_COLLECTION_HOUR_CREDITS || 'hourCredits',
takeoverCredits: process.env.APPWRITE_COLLECTION_TAKEOVER_CREDITS || 'takeoverCredits',
},
publicBaseUrl: (process.env.PORTAL_PUBLIC_URL || 'https://project.webklar.com').replace(/\/$/, ''),
// Wartungs-Abo (Stripe Billing). Preis/Produkt legt der Server per lookup_key selbst an.
stripe: {
secretKey: process.env.STRIPE_SECRET_KEY || '',
webhookSecret: process.env.STRIPE_WEBHOOK_SECRET || '',
priceLookupKey: process.env.STRIPE_ABO_PRICE_LOOKUP_KEY || 'webklar_wartung_monatlich_5000',
priceAmountCents: 5000,
productName: 'WEBklar Wartungs-Abo',
productDescription:
'Betrieb und Pflege Ihrer Website bzw. Web-Anwendung auf unserem Server in Deutschland (Sicherheitsupdates, SSL, tägliche Datensicherung, Überwachung der Erreichbarkeit), 1 Domain inklusive Verwaltung, 1 E-Mail-Postfach (5 GB) und 1 Stunde Arbeitszeit pro Monat (12 Monate nutzbar). Mindestlaufzeit 12 Monate, danach monatlich kündbar. Gemäß § 19 UStG ohne Umsatzsteuer.',
statementDescriptor: 'WEBKLAR WARTUNG',
productImage: 'https://project.webklar.com/img/wartungs-abo-produkt.png',
// Kleinunternehmer: Pflichthinweis auf jeder Stripe-Rechnung
invoiceFooter:
process.env.STRIPE_INVOICE_FOOTER ||
'Gemäß § 19 UStG wird keine Umsatzsteuer berechnet (Kleinunternehmerregelung). WEBklar GbR, Am Schwimmbad 10, 67722 Winnweiler',
},
abo: {
// Bei jeder Textaenderung an Bedingungen oder Bestellseite hochziehen (wird mit der Zustimmung gespeichert)
termsVersion: '2026-09-25-v3',
// Mindestlaufzeit ab Leistungsbeginn; Kuendigung zum Ende bis 1 Monat vorher, danach monatlich
minTermMonths: 12,
minutesPerMonth: 60,
creditValidityMonths: 12,
usageRoundingMinutes: 15,
withdrawalDays: 14,
// "Beginn nach Ablauf der Widerrufsfrist": 15 Tage Probezeit ohne Leistung und ohne Abbuchung
lateStartTrialDays: 15,
// Pilotbetrieb: nur diese Kunden-E-Mails duerfen bestellen (leer = alle)
pilotEmails: (process.env.ABO_PILOT_EMAILS || '')
.split(',')
.map((s) => s.trim().toLowerCase())
.filter(Boolean),
// Testkonten zahlen nur den Stripe-Mindestbetrag (0,50 €) ueber einen Rabatt auf den echten Preis
testEmails: (process.env.ABO_TEST_EMAILS || '')
.split(',')
.map((s) => s.trim().toLowerCase())
.filter(Boolean),
testCouponId: 'WEBKLAR-TEST-050',
},
// Uebernahmegutschrift: beim bisherigen Anbieter bereits bezahlte Zeit wird mit den Abo-Gebuehren verrechnet
takeover: {
// Bei jeder Textaenderung an den Bedingungen der Gutschrift hochziehen (wird mit dem Antrag gespeichert)
conditionsVersion: '2026-09-25',
// Nachweise liegen ausserhalb von public/ (gitignored, ueberlebt Deploys wie preview-data/)
uploadDir: process.env.TAKEOVER_UPLOAD_DIR || path.join(process.cwd(), 'upload-data', 'takeover-credits'),
maxFiles: 3,
maxFileBytes: 8 * 1024 * 1024,
maxTotalBytes: 10 * 1024 * 1024,
// Plausibilitaetsgrenze fuer eine einzelne Gutschrift (Tippfehler abfangen)
maxAmountCents: 100000,
maxOpenPerCustomer: 3,
// Nachweise abgelehnter Antraege werden nach dieser Frist geloescht (Datenschutzerklaerung Abschnitt 7)
rejectedRetentionDays: 183,
},
smtp: {
host: process.env.SMTP_HOST || '',
port: Number(process.env.SMTP_PORT) || 587,
secure: (process.env.SMTP_SECURE || 'false') === 'true',
user: process.env.SMTP_USERNAME || '',
pass: process.env.SMTP_PASSWORD || '',
from: process.env.SMTP_FROM || process.env.SMTP_USERNAME || '',
},
chatNotify: {
emails: (process.env.CHAT_NOTIFY_EMAILS || 'kenso@webklar.com,andrej@webklar.com')
.split(',')
.map((s) => s.trim())
.filter(Boolean),
throttleMin: Number(process.env.CHAT_NOTIFY_THROTTLE_MIN) || 15,
},
gitea: {
webhookToken: process.env.GITEA_WEBHOOK_TOKEN || '',
baseUrl: process.env.GITEA_BASE_URL || 'https://git.webklar.com',
apiToken: process.env.GITEA_API_TOKEN || '',
templateOwner: process.env.GITEA_TEMPLATE_OWNER || 'knso',
templateRepo: process.env.GITEA_TEMPLATE_REPO || 'webklar-preview-template',
defaultOwner: process.env.GITEA_DEFAULT_OWNER || 'knso',
},
cors: {
allowedOrigins: (process.env.ADMIN_CORS_ORIGINS || 'https://ticket.webklar.com,http://localhost:5173')
.split(',')
.map((s) => s.trim())
.filter(Boolean),
},
preview: {
baseHost: process.env.PREVIEW_BASE_HOST || 'project.webklar.com',
deployRoot: process.env.PREVIEW_DEPLOY_ROOT || '',
traefikDynamicDir: process.env.TRAEFIK_DYNAMIC_DIR || '',
sessionSecret:
process.env.PREVIEW_SESSION_SECRET || process.env.SESSION_SECRET || '',
cookieDomain: process.env.PREVIEW_COOKIE_DOMAIN || '.project.webklar.com',
sessionMaxAge: Number(process.env.PREVIEW_SESSION_MAX_AGE || 60 * 60 * 24 * 7),
},
invoiceNinja: {
url: (process.env.INVOICE_NINJA_URL || 'https://invoice.webklar.com').replace(/\/$/, ''),
apiToken: process.env.INVOICE_NINJA_API_TOKEN || '',
},
}
export function assertServerConfig() {
if (!config.sessionSecret || config.sessionSecret.length < 32) {
console.warn('[config] SESSION_SECRET fehlt oder ist zu kurz (min. 32 Zeichen).')
}
if (!config.appwrite.apiKey) {
console.warn('[config] APPWRITE_API_KEY fehlt – DB-Zugriff und Webhook schlagen fehl.')
}
}