import { config } from '../config.js' import { berlinDateTime } from './berlinTime.js' import { withLocks } from './keyedLock.js' import { listDocuments, getDocument, createDocument, Query, ID, } from './appwriteAdmin.js' const WSID_START = 100000 // worksheets.details ist auf 4000 Zeichen begrenzt const DETAILS_MAX_LENGTH = 3900 /** Pusht das System-Konto selbst (automatische Commits) - dafuer kein Arbeitsblatt. */ export function isSystemPusher(pusher) { const name = pusher?.username || pusher?.login || '' return name.toLowerCase() === config.gitea.systemUser.toLowerCase() } async function generateNextWsid() { const docs = await listDocuments(config.collections.worksheets, [ Query.orderDesc('wsid'), Query.limit(1), ]) const highest = docs[0]?.wsid ? parseInt(docs[0].wsid, 10) : WSID_START - 1 return String(Math.max(WSID_START, highest + 1)) } async function gitWorksheetExists(woid, commitSha) { if (!commitSha) return false const docs = await listDocuments(config.collections.worksheets, [ Query.equal('woid', woid), Query.limit(100), ]) const marker = `Commit: ${commitSha.slice(0, 7)}` return docs.some((doc) => doc.details?.includes(marker) || doc.details?.includes(commitSha)) } function formatCommitLines(commits, repoFullName) { if (!Array.isArray(commits) || commits.length === 0) { return '- (keine Commit-Details im Webhook)' } return commits .map((commit) => { const sha = (commit.id || commit.sha || '').slice(0, 7) // Commit-Titel + Beschreibung (Body) uebernehmen const [title, ...bodyLines] = String(commit.message || '').trim().split('\n') const author = commit.author?.name || commit.committer?.name || 'Unbekannt' const url = commit.url || `${config.gitea.baseUrl}/${repoFullName}/commit/${commit.id || commit.sha}` const body = bodyLines .map((line) => line.trim()) .filter(Boolean) .map((line) => ` ${line}`) .join('\n') return `- [${sha}](${url}): ${title} (${author})${body ? `\n${body}` : ''}` }) .join('\n') } export async function createGitPushWorksheet({ ticketId, repoFullName, branch, commits = [], pusher, commitSha, pushedAt = new Date(), }) { if (!ticketId) return null if (isSystemPusher(pusher)) { return { skipped: true, reason: 'system_pusher' } } let workorder try { workorder = await getDocument(config.collections.workorders, ticketId) } catch { console.warn('[gitWorksheet] Workorder nicht gefunden:', ticketId) return null } const woid = workorder.woid if (!woid) return null // Gesperrt je Ticket+Commit, damit zwei gleichzeitige Zustellungen desselben Webhooks // (Gitea liefert bei Bedarf erneut zu) nicht zweimal denselben Duplikat-Check bestehen // und zwei Arbeitsblaetter anlegen. const lockKey = `ws:${woid}:${(commitSha || '').slice(0, 7)}` return withLocks([lockKey], async () => { if (await gitWorksheetExists(woid, commitSha)) { return { skipped: true, reason: 'duplicate_commit' } } const wsid = await generateNextWsid() const pusherName = pusher?.full_name || pusher?.username || pusher?.login || 'Gitea' const latestCommit = commits[commits.length - 1] || commits[0] let details = [ `Git Push auf \`${repoFullName}\` (Branch: ${branch})`, `Commit: ${commitSha?.slice(0, 7) || latestCommit?.id?.slice(0, 7) || '?'}`, '', formatCommitLines(commits, repoFullName), ].join('\n') if (details.length > DETAILS_MAX_LENGTH) { details = `${details.slice(0, DETAILS_MAX_LENGTH)}\n… (gekuerzt)` } // Endzeit = Push-Zeitpunkt in deutscher Zeit (MESZ/MEZ). Startzeit bleibt leer und wird // im Ticketsystem nachgetragen ("Zeit nachtragen") - erst dann zaehlt die Arbeitszeit. const { date, time } = berlinDateTime(pushedAt) const worksheet = await createDocument( config.collections.worksheets, { wsid, woid: String(woid), workorderId: ticketId, employeeId: 'gitea', employeeName: pusherName, employeeShort: 'GIT', serviceType: 'GIT', oldStatus: workorder.status || '', newStatus: workorder.status || '', oldResponseLevel: workorder.response || '', newResponseLevel: workorder.response || '', totalTime: 0, startDate: date, startTime: '', endDate: date, endTime: time, details, isComment: true, createdAt: new Date().toISOString(), }, ID.unique() ) // ticket + worksheet fuer die Push-Zeitmail (routes/webhook/gitea.js), nicht fuer die Webhook-Antwort return { created: true, wsid, worksheetId: worksheet.$id, ticket: workorder, worksheet } }) }