Kundenansicht: Protokoll-Collection customerViewLog mit Schreiben, Lesen und Loeschen nach 12 Monaten
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
772095ab19
commit
fdf594706d
6 files changed
+157
-1
No files matched your search
@@ -154,6 +154,7 @@ export const config = {
|
||||
aboOffers: process.env.APPWRITE_COLLECTION_ABO_OFFERS || 'aboOffers',
|
||||
contractEvents: process.env.APPWRITE_COLLECTION_CONTRACT_EVENTS || 'contractEvents',
|
||||
newsPosts: process.env.APPWRITE_COLLECTION_NEWS_POSTS || 'newsPosts',
|
||||
customerViewLog: process.env.APPWRITE_COLLECTION_CUSTOMER_VIEW_LOG || 'customerViewLog',
|
||||
// Finanzen (Bankkonto, Buchungen, Einstellungen, Fixkosten)
|
||||
bankConnections: process.env.APPWRITE_COLLECTION_BANK_CONNECTIONS || 'bankConnections',
|
||||
bankTransactions: process.env.APPWRITE_COLLECTION_BANK_TRANSACTIONS || 'bankTransactions',
|
||||
|
||||
@@ -20,6 +20,7 @@ import funktionenRoutes from './routes/funktionen.js'
|
||||
import takeoverCreditRoutes from './routes/takeoverCredits.js'
|
||||
import { purgeExpiredUploads } from './services/takeoverCredits.js'
|
||||
import { purgePendingChatUploads } from './services/chatAttachments.js'
|
||||
import { purgeViewLog } from './services/customerViewLog.js'
|
||||
import { purgeUnclaimedNewsImages } from './services/newsImages.js'
|
||||
import { runActivation } from './services/siteFeatures.js'
|
||||
import { runAboCheck, runAnalyseReconcile } from './services/analyse.js'
|
||||
@@ -145,6 +146,14 @@ const server = app.listen(config.port, '0.0.0.0', () => {
|
||||
setTimeout(purge, 60 * 1000).unref()
|
||||
setInterval(purge, 24 * 60 * 60 * 1000).unref()
|
||||
|
||||
// Protokoll der Kundenansicht: Eintraege aelter als 12 Monate loeschen (taeglich)
|
||||
const purgeViewLogJob = () =>
|
||||
purgeViewLog()
|
||||
.then((r) => r.deleted && console.log(`[kundenansicht] ${r.deleted} Protokolleinträge gelöscht`))
|
||||
.catch((err) => console.warn('[kundenansicht] Aufräumen fehlgeschlagen:', err.message))
|
||||
setTimeout(purgeViewLogJob, 150 * 1000).unref()
|
||||
setInterval(purgeViewLogJob, 24 * 60 * 60 * 1000).unref()
|
||||
|
||||
// Chat-Anhaenge, die nie abgeschickt wurden, nach config.chat.pendingTtlHours loeschen (stuendlich)
|
||||
const purgeChat = () =>
|
||||
purgePendingChatUploads()
|
||||
|
||||
@@ -76,6 +76,7 @@ export const Query = {
|
||||
}),
|
||||
limit: (n) => ({ method: 'limit', values: [n] }),
|
||||
greaterThan: (attribute, value) => ({ method: 'greaterThan', attribute, values: [value] }),
|
||||
lessThan: (attribute, value) => ({ method: 'lessThan', attribute, values: [value] }),
|
||||
orderDesc: (attribute) => ({ method: 'orderDesc', attribute }),
|
||||
orderAsc: (attribute) => ({ method: 'orderAsc', attribute }),
|
||||
}
|
||||
|
||||
@@ -0,0 +1,54 @@
|
||||
import { config } from '../config.js'
|
||||
import { createDocument, deleteDocument, listDocuments, Query } from './appwriteAdmin.js'
|
||||
|
||||
/**
|
||||
* Protokoll der Kundenansicht (Datenschutzerklaerung Abschnitt 6): wer wann das Portal eines Kunden geoeffnet
|
||||
* oder darin etwas geaendert hat. Schreiben nur ueber den Server-Schluessel; Eintraege leben 12 Monate.
|
||||
*/
|
||||
export const VIEW_ACTIONS = ['geoeffnet', 'beendet', 'abgelaufen', 'news_angelegt', 'news_bild', 'news_geaendert', 'news_geloescht']
|
||||
|
||||
const DETAIL_MAX = 300
|
||||
const RETENTION_MS = 365 * 24 * 60 * 60 * 1000
|
||||
const PAGE = 100
|
||||
|
||||
const COLLECTION = () => config.collections.customerViewLog
|
||||
|
||||
/** Eintrag schreiben; wirft nie (die protokollierte Aktion selbst bleibt gueltig). */
|
||||
export async function logViewAction({ customerId, staffUserId, staffName, action, detail = '' }) {
|
||||
try {
|
||||
await createDocument(COLLECTION(), {
|
||||
customerId: String(customerId || ''),
|
||||
staffUserId: String(staffUserId || ''),
|
||||
staffName: String(staffName || ''),
|
||||
action: String(action || ''),
|
||||
detail: String(detail || '').slice(0, DETAIL_MAX),
|
||||
createdAt: new Date().toISOString(),
|
||||
})
|
||||
} catch (err) {
|
||||
console.warn(`[kundenansicht] Protokoll (${action}, Kunde ${customerId}) nicht geschrieben:`, err.message)
|
||||
}
|
||||
}
|
||||
|
||||
/** Eintraege eines Kunden, neueste zuerst. */
|
||||
export async function listViewLog(customerId, { limit = 200 } = {}) {
|
||||
const docs = await listDocuments(COLLECTION(), [
|
||||
Query.equal('customerId', customerId),
|
||||
Query.orderDesc('createdAt'),
|
||||
Query.limit(limit),
|
||||
])
|
||||
return docs.map((d) => ({ id: d.$id, at: d.createdAt, staffName: d.staffName || '', action: d.action || '', detail: d.detail || '' }))
|
||||
}
|
||||
|
||||
/** Eintraege aelter als 12 Monate loeschen (taeglicher Lauf), seitenweise bis keine mehr uebrig sind. */
|
||||
export async function purgeViewLog({ now = new Date() } = {}) {
|
||||
const cutoff = new Date(now.getTime() - RETENTION_MS).toISOString()
|
||||
let deleted = 0
|
||||
for (;;) {
|
||||
const docs = await listDocuments(COLLECTION(), [Query.lessThan('createdAt', cutoff), Query.limit(PAGE)])
|
||||
for (const doc of docs) {
|
||||
await deleteDocument(COLLECTION(), doc.$id)
|
||||
deleted += 1
|
||||
}
|
||||
if (docs.length < PAGE) return { deleted }
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user