Umsetzungspläne: Kundenportal, Ticketsystem, Lead-Automatik
Plan 1 Kundenportal (Gitea-Grundlage, Umzug, Nachtragen, Livegang), Plan 2 Ticketsystem (Dialog, Knöpfe), Plan 3 Lead-Automatik (neue Lead-Repos in WEBklar über das System-Konto). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
3edb5f8bb2
commit
a3faa1aff1
3 files changed
+1804
No files matched your search
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,344 @@
|
||||
# Plan 2 – Ticketsystem: neue Repos sehen, Zuweisen mit Umzug, Preview und Pushes per Knopf
|
||||
|
||||
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
|
||||
|
||||
**Ziel:** Der Dialog „Projekte bearbeiten“ zeigt neue Gitea-Repos sofort. „Zuweisen“ läuft über das Portal, das das Repo verschiebt und Pushes nachträgt, und zeigt danach eine Zusammenfassung. Zugewiesene Projekte bekommen die Knöpfe „Preview einschalten“ und „Pushes nachtragen“. „Kunde zuordnen“ auf der Projekte-Seite nutzt denselben Weg.
|
||||
|
||||
**Aufbau:**
|
||||
|
||||
- **Reine Logik** in zwei neuen Dateien:
|
||||
- `src/lib/subdomain.js`: Subdomain-Vorschlag,
|
||||
- `src/lib/projectAssign.js`: Organisation, Texte der Zusammenfassung, Zuweisen nacheinander.
|
||||
Beide werden mit `node --test` geprüft, geladen über esbuild. Das Ticketsystem hat bisher keinen Testlauf.
|
||||
- **Portal-Aufrufe** kommen nach `src/lib/projectAdminApi.js`.
|
||||
- **Oberfläche:** Der Hook `useWebsiteProjects` und drei Komponenten nutzen das.
|
||||
- **Oberflächen-Prüfung:** Playwright gegen einen echten vite-Build mit nachgebautem Appwrite und Portal. Sie liegt im Scratchpad und wird nicht committet.
|
||||
|
||||
**Technik:**
|
||||
|
||||
- React 18, Vite 5
|
||||
- `appwrite` 13 (Web-SDK)
|
||||
- Node 24 auf dem Host für `node --test`
|
||||
- esbuild aus `node_modules`, genutzt über die JS-Schnittstelle, nicht über `bin/esbuild`
|
||||
|
||||
**Spec:** `docs/superpowers/specs/2026-09-27-gitea-organisation-design.md` im Kundenportal-Repo. Maßgeblich sind die Abschnitte 5.7, 6 und 11.
|
||||
|
||||
**Voraussetzung:** Plan 1 ist live (dort Livegang L5), sonst fehlen die Schnittstellen. Der Dialog muss trotzdem bedienbar bleiben, wenn `import-repos` scheitert.
|
||||
|
||||
## Global Constraints
|
||||
|
||||
**Werte und Texte, wörtlich**
|
||||
|
||||
- Organisation `WEBklar`, in `src/lib/projectAssign.js` als `GITEA_ORG` festgelegt.
|
||||
- Portal-Schnittstellen, jeweils mit `Authorization: Bearer <Appwrite-JWT>` über das vorhandene `adminFetch`:
|
||||
- `POST /api/admin/gitea/import-repos` → `{ ok, created }`
|
||||
- `POST /api/admin/website-projects/:id/assign` mit `{ customerId, ticketId }` → `{ ok, project, moved, backfilled, warnings }`
|
||||
- `POST /api/admin/website-projects/:id/pushes` → `{ ok, moved, backfilled, warnings }`
|
||||
- `PATCH /api/admin/website-projects/:id` mit `{ projectType: 'preview', subdomain }` → `{ ok, previewUrl, … }`
|
||||
- Texte:
|
||||
- `Suche neue Repos …`
|
||||
- `wird beim Zuweisen nach WEBklar verschoben`
|
||||
- `Preview einschalten`, `Einschalten`, `Pushes nachtragen`
|
||||
- `` `Preview läuft: ${previewUrl}` ``
|
||||
- `` `Neue Repos konnten nicht geladen werden (${message}).` ``
|
||||
- `` `${projectName}: Zuweisen fehlgeschlagen – ${error}` ``
|
||||
- Die Zusammenfassung (`assignSummary`) besteht aus zwei Teilen, getrennt durch ` · `:
|
||||
- „nach WEBklar verschoben“, wenn verschoben wurde,
|
||||
- danach „keine neuen Pushes“, „1 Push nachgetragen“ oder `` `${n} Pushes nachgetragen` ``.
|
||||
- Beispiel aus dem Spec: `Döner Nation: nach WEBklar verschoben · 1 Push nachgetragen`.
|
||||
- **Subdomain-Vorschlag:**
|
||||
1. Grundlage ist der Kundenname des Tickets, sonst der Projektname.
|
||||
2. Kleinschreibung.
|
||||
3. ä→ae, ö→oe, ü→ue, ß→ss.
|
||||
4. Akzente entfernen (NFD).
|
||||
5. Apostrophe `'` `’` `‘` `` ` `` entfernen.
|
||||
6. `[^a-z0-9]+` → `-`, Bindestriche am Rand entfernen.
|
||||
7. Höchstens 63 Zeichen, danach nochmals Bindestriche am Ende entfernen.
|
||||
- **„Preview einschalten“** erscheint, wenn `!project.previewUrl || project.status !== 'deployed'`, und nur, wenn die Karte `showActions` hat (also im Dialog).
|
||||
|
||||
**Git und Deploy – wichtig**
|
||||
|
||||
- Das Deploy-Webhook des Ticketsystems setzt **jeden** gepushten Branch live. Den Feature-Branch deshalb niemals pushen. Live geht es nur mit OK des Nutzers per `git push origin feature/gitea-organisation:test`.
|
||||
- `node_modules` ist im Repo **versioniert** (etwa 8900 Dateien), und im Worktree ist es ein Symlink. Deshalb nur `git add <Dateien>`, niemals `-A` oder `.`.
|
||||
- Eine andere Sitzung arbeitet parallel in `dev-entschlackung`, gerade an `App.jsx`, `InvoicePanel.jsx`, `useInvoices.js`, `integrationsApi.js` und `FinancePage.jsx`. Diese Dateien fasst Plan 2 nicht an. Vor dem Push `git rebase origin/test`.
|
||||
- Neue Texte werden in UTF-8 mit echten Umlauten geschrieben. Die berührten Dateien sind bisher reines ASCII und bleiben gültiges UTF-8.
|
||||
|
||||
## Review Focus
|
||||
|
||||
1. **Das Portal ist noch nicht aktualisiert.** `import-repos` antwortet dann mit 404 oder 500. Der Dialog zeigt nur den Hinweis, und die Listen laden trotzdem. Test: Task 4, Prüfung 7.
|
||||
2. **Beim Zuweisen mehrerer Projekte scheitert eines.** Die anderen werden trotzdem zugewiesen, und die Zusammenfassung nennt den Fehler beim betroffenen Projekt. Test: Task 1, „nacheinander und weiter nach Fehler“.
|
||||
3. **Das Ticket hat keinen Kundennamen.** Der Vorschlag nimmt dann den Projektnamen. Sind beide leer, bleibt das Feld leer, und „Einschalten“ ist gesperrt, bis etwas eingetragen ist. Tests: Task 1 (`suggestSubdomain('', '')`) und Task 4, Prüfung 4.
|
||||
4. **Namen mit Zeichen wie „&“, „+“, „ß“ oder „é“.** Das ergibt eine gültige DNS-Subdomain ohne doppelte Bindestriche. Test: Task 1, „Sonderzeichen“.
|
||||
5. **Die Subdomain ist schon vergeben (409 vom Portal).** Die Meldung des Portals erscheint, und die Eingabe bleibt stehen. Test: Task 4, Prüfung 5.
|
||||
|
||||
---
|
||||
|
||||
## Dateien
|
||||
|
||||
| Datei | Aufgabe |
|
||||
|---|---|
|
||||
| `src/lib/subdomain.js` (neu) | `suggestSubdomain` |
|
||||
| `src/lib/projectAssign.js` (neu) | `GITEA_ORG`, `isOutsideOrg`, `needsPreviewButton`, `assignSummary`, `assignSequentially` |
|
||||
| `src/lib/projectAdminApi.js` | `importNewRepos`, `assignWebsiteProject`, `backfillProjectPushes` |
|
||||
| `src/hooks/useWebsiteProjects.js` | `assignProjects` über das Portal, nacheinander |
|
||||
| `src/components/TicketProjectsModal.jsx` | Import beim Öffnen, Hinweis, Zusammenfassung, Daten für die Karte |
|
||||
| `src/components/AssignedProjectCard.jsx` | Knöpfe „Preview einschalten“ und „Pushes nachtragen“ (nur mit `showActions`) |
|
||||
| `src/pages/ProjectsPage.jsx` | „Kunde zuordnen“ zeigt die Zusammenfassung |
|
||||
| `package.json` | Skript `"test": "node --test tests/unit/*.test.mjs"` (Muster statt Ordner, damit Node 20 und 24 gleich laufen) |
|
||||
| `tests/unit/load.mjs`, `tests/unit/*.test.mjs` (neu) | Laden der Quellen über esbuild, Tests |
|
||||
|
||||
---
|
||||
|
||||
### Task 0: Worktree anlegen
|
||||
|
||||
- [ ] **Step 1: Create the worktree from the current `test`**
|
||||
|
||||
```bash
|
||||
cd /home/webklar/apps/ticket.webklar.com/build && git fetch -q origin
|
||||
git worktree add -b feature/gitea-organisation ../dev-gitea-org origin/test
|
||||
cd ../dev-gitea-org && git branch --unset-upstream; rm -rf node_modules && ln -s ../build/node_modules node_modules
|
||||
git log --oneline -1
|
||||
```
|
||||
|
||||
Expected: HEAD = `origin/test`. `git status --short | grep -v node_modules` shows nothing.
|
||||
|
||||
---
|
||||
|
||||
### Task 1: Reine Logik mit Tests
|
||||
|
||||
**Files:**
|
||||
- Create: `src/lib/subdomain.js`, `src/lib/projectAssign.js`, `tests/unit/load.mjs`, `tests/unit/subdomain.test.mjs`, `tests/unit/projectAssign.test.mjs`
|
||||
- Modify: `package.json` (only `scripts.test`)
|
||||
|
||||
**Interfaces:**
|
||||
- Produces:
|
||||
- `suggestSubdomain(customerName?: string, projectName?: string): string`
|
||||
- `GITEA_ORG = 'WEBklar'`
|
||||
- `isOutsideOrg(repoFullName: string): boolean`. It is `false` for an empty name, and the comparison ignores case.
|
||||
- `needsPreviewButton(project): boolean`
|
||||
- `assignSummary(projectName: string, result: { moved, backfilled, warnings } | { error: string }): { text: string, warnings: string[] }`
|
||||
- `assignSequentially(projectIds: string[], ctx, assignFn: (id, ctx) => Promise<object>): Promise<Array<{ projectId, ok: boolean, result?, error? }>>`
|
||||
- `loadModule(relPathFromRepoRoot): Promise<module>` in `tests/unit/load.mjs`. It uses esbuild `build({ bundle: true, format: 'esm', write: false, platform: 'neutral' })` and `import()` from a `data:` URL.
|
||||
|
||||
- [ ] **Step 1: Write the failing tests**
|
||||
|
||||
```js
|
||||
// tests/unit/subdomain.test.mjs
|
||||
const { suggestSubdomain } = await loadModule('src/lib/subdomain.js')
|
||||
test('Kundenname vor Projektname', () => assert.equal(suggestSubdomain('Döner Nation', 'D-ner-Nation'), 'doener-nation'))
|
||||
test('Apostroph', () => assert.equal(suggestSubdomain("Emma's Schatzkiste", ''), 'emmas-schatzkiste'))
|
||||
test('Sonderzeichen', () => assert.equal(suggestSubdomain('', 'Café Müller & Söhne + Straße'), 'cafe-mueller-soehne-strasse'))
|
||||
test('leer', () => assert.equal(suggestSubdomain('', ''), ''))
|
||||
test('63 Zeichen ohne Bindestrich am Ende', () => assert.equal(suggestSubdomain('a'.repeat(62) + ' b', ''), 'a'.repeat(62)))
|
||||
|
||||
// tests/unit/projectAssign.test.mjs
|
||||
test('isOutsideOrg', () => {
|
||||
assert.equal(isOutsideOrg('JUSN/D-ner-Nation'), true)
|
||||
assert.equal(isOutsideOrg('webklar/x'), false)
|
||||
assert.equal(isOutsideOrg(''), false)
|
||||
})
|
||||
test('needsPreviewButton', () => {
|
||||
assert.equal(needsPreviewButton({ previewUrl: 'https://x', status: 'deployed' }), false)
|
||||
assert.equal(needsPreviewButton({ previewUrl: '', status: 'synced' }), true)
|
||||
assert.equal(needsPreviewButton({ previewUrl: 'https://x', status: 'pending' }), true)
|
||||
})
|
||||
test('assignSummary', () => {
|
||||
assert.deepEqual(assignSummary('Döner Nation', { moved: { from: 'JUSN/D-ner-Nation', to: 'WEBklar/D-ner-Nation' }, backfilled: 1, warnings: [] }),
|
||||
{ text: 'Döner Nation: nach WEBklar verschoben · 1 Push nachgetragen', warnings: [] })
|
||||
assert.deepEqual(assignSummary('X', { moved: null, backfilled: 0, warnings: ['Internes Repo, bleibt wo es ist.'] }),
|
||||
{ text: 'X: keine neuen Pushes', warnings: ['Internes Repo, bleibt wo es ist.'] })
|
||||
assert.equal(assignSummary('X', { moved: null, backfilled: 3, warnings: [] }).text, 'X: 3 Pushes nachgetragen')
|
||||
assert.deepEqual(assignSummary('X', { error: 'boom' }), { text: 'X: Zuweisen fehlgeschlagen – boom', warnings: [] })
|
||||
})
|
||||
test('nacheinander und weiter nach Fehler', async () => {
|
||||
const log = []
|
||||
const fn = async (id) => { log.push(id + ':start'); await sleep(10); log.push(id + ':end'); if (id === 'b') throw new Error('boom'); return { backfilled: 0 } }
|
||||
const r = await assignSequentially(['a', 'b', 'c'], { ticketId: 't1' }, fn)
|
||||
assert.deepEqual(log, ['a:start', 'a:end', 'b:start', 'b:end', 'c:start', 'c:end'])
|
||||
assert.deepEqual(r.map((x) => [x.projectId, x.ok, x.error]), [['a', true, undefined], ['b', false, 'boom'], ['c', true, undefined]])
|
||||
})
|
||||
```
|
||||
|
||||
- [ ] **Step 2: Run to verify it fails**
|
||||
|
||||
Run: `npm test`
|
||||
Expected: FAIL, because the modules are missing. The script is already added.
|
||||
|
||||
- [ ] **Step 3: Implement both modules**
|
||||
|
||||
Follow the rules in Global Constraints. `assignSequentially` runs the calls strictly one after another. It catches every error as `{ ok: false, error: err.message }` and continues with the next ID.
|
||||
|
||||
- [ ] **Step 4: Run to verify it passes**
|
||||
|
||||
Run: `npm test`
|
||||
Expected: PASS
|
||||
|
||||
- [ ] **Step 5: Commit**
|
||||
|
||||
```bash
|
||||
git add src/lib/subdomain.js src/lib/projectAssign.js tests/unit/load.mjs tests/unit/subdomain.test.mjs tests/unit/projectAssign.test.mjs package.json
|
||||
git commit -m "Subdomain-Vorschlag und Zuweisungs-Logik mit Tests"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Task 2: Portal-Aufrufe und Hook
|
||||
|
||||
**Files:**
|
||||
- Modify: `src/lib/projectAdminApi.js`, `src/hooks/useWebsiteProjects.js`
|
||||
|
||||
**Interfaces:**
|
||||
- Consumes: `assignSequentially` (Task 1)
|
||||
- Produces:
|
||||
- `importNewRepos(): Promise<{ ok, created }>`
|
||||
- `assignWebsiteProject(id, { customerId, ticketId }): Promise<{ ok, project, moved, backfilled, warnings }>`
|
||||
- `backfillProjectPushes(id): Promise<{ ok, moved, backfilled, warnings }>`
|
||||
- `useWebsiteProjects().assignProjects(projectIds, { customerId, ticketId })` returns `{ success: boolean, error?: string, results }`.
|
||||
- `results` comes from `assignSequentially` with `assignWebsiteProject`.
|
||||
- `success` is true only if every result is `ok`.
|
||||
- `error` is the first error text.
|
||||
- `unassignProject` stays as it is, directly in Appwrite.
|
||||
|
||||
- [ ] **Step 1: Implement**
|
||||
|
||||
Use the existing `adminFetch` pattern, with method POST and body `JSON.stringify(...)`. `assignProject`, the single direct Appwrite update, is removed from the hook. It has no other callers: check with `git grep -n "assignProject\b" src`.
|
||||
|
||||
- [ ] **Step 2: Build check**
|
||||
|
||||
Run:
|
||||
|
||||
```bash
|
||||
node -e "require('esbuild').build({entryPoints:['src/hooks/useWebsiteProjects.js'],bundle:true,write:false,format:'esm',platform:'browser',external:['react','appwrite'],define:{'import.meta.env.VITE_PROJECT_ADMIN_URL':'\"https://project.webklar.com\"'},loader:{'.js':'jsx','.css':'empty','.ttf':'empty','.woff2':'empty','.png':'empty','.svg':'empty'}}).then(()=>console.log('ok'))"
|
||||
```
|
||||
|
||||
Expected: `ok`. `npm test` stays green.
|
||||
|
||||
- [ ] **Step 3: Commit**
|
||||
|
||||
```bash
|
||||
git add src/lib/projectAdminApi.js src/hooks/useWebsiteProjects.js
|
||||
git commit -m "Zuweisen über das Portal, Import neuer Repos, Pushes nachtragen"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Task 3: Dialog, Projektkarte, Projekte-Seite
|
||||
|
||||
**Files:**
|
||||
- Modify: `src/components/TicketProjectsModal.jsx`, `src/components/AssignedProjectCard.jsx`, `src/pages/ProjectsPage.jsx`
|
||||
|
||||
**Interfaces:**
|
||||
- Consumes: Tasks 1 and 2; `updateWebsiteProject` (already in `projectAdminApi.js`)
|
||||
- Produces:
|
||||
- `AssignedProjectCard` gets the new optional props `customerName` and `onChanged()`. Without `showActions` it looks exactly as before.
|
||||
|
||||
- [ ] **Step 1: Implement `TicketProjectsModal`**
|
||||
|
||||
1. **When the dialog opens:**
|
||||
- set `importing = true` and show `Suche neue Repos …`,
|
||||
- call `await importNewRepos()`; on an error, set `importHint` to `` `Neue Repos konnten nicht geladen werden (${err.message}).` `` and carry on,
|
||||
- then call `loadProjects()`.
|
||||
2. **List entries:** For `isOutsideOrg(p.repoFullName)`, add a grey note to the entry: `wird beim Zuweisen nach WEBklar verschoben`.
|
||||
3. **`handleAssign`:**
|
||||
- call `assignProjects(selectedIds, { customerId: ticket.customerId || '', ticketId: ticket.$id })`,
|
||||
- build a list of `assignSummary(projectName, result.ok ? result.result : { error: result.error })` from `results` and show it under the button: one line per project, warnings indented,
|
||||
- clear the selection, then `loadProjects()` and `onUpdated?.()`.
|
||||
4. **Cards:** Pass `customerName={ticket.customerName || ''}` and `onChanged={() => { loadProjects(); onUpdated?.() }}` to `AssignedProjectCard`.
|
||||
|
||||
- [ ] **Step 2: Implement `AssignedProjectCard`**
|
||||
|
||||
These controls appear only with `showActions`:
|
||||
|
||||
- **„Preview einschalten“** appears if `needsPreviewButton(project)`.
|
||||
- A click shows an input prefilled with `suggestSubdomain(customerName, project.projectName)` and the button `Einschalten`. It is disabled while the input is empty or a request is running.
|
||||
- `Einschalten` calls `updateWebsiteProject(project.$id, { projectType: 'preview', subdomain })`.
|
||||
- On success: the message `` `Preview läuft: ${r.previewUrl}` `` and `onChanged()`.
|
||||
- On an error: `err.message`, and the input stays.
|
||||
- **„Pushes nachtragen“** calls `backfillProjectPushes(project.$id)` and shows `assignSummary(project.projectName, r)`, then `onChanged()`.
|
||||
- Messages appear inside the card, below the status line. Buttons use `className="btn btn-sm"`, like the existing trash button.
|
||||
|
||||
- [ ] **Step 3: Implement `ProjectsPage`**
|
||||
|
||||
`handleAssignCustomer`:
|
||||
|
||||
- With a customer: the result of `assignProjects([project.$id], …)` shows as `setNotice(assignSummary(project.projectName, r.results[0].ok ? r.results[0].result : { error: r.results[0].error }).text)`. Warnings are appended with ` – `.
|
||||
- Without a customer: unchanged.
|
||||
|
||||
- [ ] **Step 4: Build check**
|
||||
|
||||
Run the same esbuild call as in Task 2 Step 2, once each for `src/components/TicketProjectsModal.jsx` and `src/pages/ProjectsPage.jsx`, with `external:['react','react-dom','react-icons/*','appwrite','react-router-dom','react-markdown','@tabler/icons-react']`.
|
||||
|
||||
Expected: `ok` both times. `npm test` stays green.
|
||||
|
||||
- [ ] **Step 5: Commit**
|
||||
|
||||
```bash
|
||||
git add src/components/TicketProjectsModal.jsx src/components/AssignedProjectCard.jsx src/pages/ProjectsPage.jsx
|
||||
git commit -m "Dialog: neue Repos, Zuweisen mit Umzug, Preview einschalten, Pushes nachtragen"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Task 4: Oberflächen-Prüfung (Scratchpad, nicht committen)
|
||||
|
||||
**Files:** Scratchpad `…/scratchpad/woms-ui/` (build script, `ui.mjs`)
|
||||
|
||||
**Setup:**
|
||||
|
||||
- **vite-Build** like the deploy:
|
||||
|
||||
```bash
|
||||
git archive HEAD | tar -x -C <tmp>
|
||||
docker run --rm -v <tmp>:/w -w /w node:20-alpine sh -c "rm -rf node_modules && npm install && npm run build"
|
||||
```
|
||||
|
||||
- **Browser:** `playwright-core` from `/root/.npm/_npx/db89d7302a373f10/node_modules/playwright-core` (load it via `createRequire`) and Chromium at `~/.cache/ms-playwright/chromium-1234/chrome-linux64/chrome`. Run it under `systemd-run --scope -p MemoryMax=1200M -p MemorySwapMax=0`, and check `free -m` beforehand.
|
||||
- **Page:** served from `https://ticket.test` via `page.route`.
|
||||
- **Appwrite mock:**
|
||||
- a logged-in admin session and `POST /v1/account/jwts` → `{ jwt: 'jwt-admin' }`,
|
||||
- `listDocuments` for workorders: ticket 100115, customer name „Döner Nation“,
|
||||
- `websiteProjects`:
|
||||
- `JUSN/Ben-Breidt` and `WEBklar/sauber`, both available,
|
||||
- `WEBklar/D-ner-Nation`, assigned to ticket 100115, without `previewUrl`, status `synced`,
|
||||
- plus employees and customers.
|
||||
- **Portal mock** under `https://project.webklar.com/api/admin/**`. It records every request with its start and end time.
|
||||
- If the Entschlackung harness still exists (`appsmoke.mjs` in scratchpad `c07881f0…/wstimes`), reuse its Appwrite mock.
|
||||
|
||||
**Prüfungen** (all must pass):
|
||||
|
||||
1. Opening „Projekte zuweisen / bearbeiten“ on ticket 100115 sends exactly one `POST /api/admin/gitea/import-repos`. While the answer is pending (delayed by 500 ms), the dialog shows `Suche neue Repos …`.
|
||||
2. `JUSN/Ben-Breidt` carries `wird beim Zuweisen nach WEBklar verschoben`. `WEBklar/sauber` does not.
|
||||
3. Selecting both and clicking „Zuweisen“ sends two `…/assign` requests. The second starts only after the first has answered. Two summary lines appear. The first is `Ben Breidt: nach WEBklar verschoben · 1 Push nachgetragen`, with the mock answer to match.
|
||||
4. The card of `D-ner-Nation` has „Preview einschalten“.
|
||||
- The input shows `doener-nation`.
|
||||
- Changed to `doener-test` and sent with „Einschalten“, it produces `PATCH …/website-projects/<id>` with the body `{"projectType":"preview","subdomain":"doener-test"}`.
|
||||
- The card then shows `Preview läuft: https://doener-test.project.webklar.com`.
|
||||
- Once the field is cleared, „Einschalten“ is disabled.
|
||||
5. When the PATCH answers `409 { error: 'Subdomain "doener-nation" ist bereits vergeben' }`, exactly that text appears and the input stays.
|
||||
6. „Pushes nachtragen“ sends `POST …/pushes` and shows `Döner Nation: keine neuen Pushes`.
|
||||
7. When `import-repos` answers 404, the dialog shows `Neue Repos konnten nicht geladen werden (…)`, and both lists still appear.
|
||||
8. On the Projekte page, „Kunde zuordnen“ sends `…/assign` with `{"customerId":"<id>","ticketId":""}`, and the notice shows the summary.
|
||||
9. The ticket's Details tab (`TicketAssignedProjectsPanel`) and the customer page show the card without the new buttons.
|
||||
|
||||
Expected: the script prints `9/9 ok`.
|
||||
|
||||
---
|
||||
|
||||
## Livegang – nur mit OK des Nutzers, erst nach Plan 1 L5
|
||||
|
||||
1. Update and test:
|
||||
|
||||
```bash
|
||||
git -C /home/webklar/apps/ticket.webklar.com/dev-gitea-org fetch origin && git -C … rebase origin/test
|
||||
```
|
||||
|
||||
Then run `npm test`, and the Task 4 check once more against the rebased state.
|
||||
2. Deploy-like check: `git archive` + `docker run node:20-alpine` (`rm -rf node_modules; npm install; npm run build`) finishes without errors.
|
||||
3. Push: `git -C /home/webklar/apps/ticket.webklar.com/dev-gitea-org push origin feature/gitea-organisation:test`.
|
||||
4. Check:
|
||||
- the `out/` bundle and the live https://ticket.webklar.com contain the string `Suche neue Repos`,
|
||||
- the deploy log shows `npm run build` without errors,
|
||||
- opening the dialog in the live ticket 100115 lists `JUSN/Ben-Breidt` and similar repos. Doing this is only reading.
|
||||
5. Update the memory note `woms-ticketsystem`.
|
||||
|
||||
**Rückweg:** `git revert` the Plan 2 commits on a branch, then push to `test` after the OK.
|
||||
@@ -0,0 +1,343 @@
|
||||
# Plan 3 – Lead-Automatik: neue Lead-Repos in WEBklar, Zugang über das System-Konto
|
||||
|
||||
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
|
||||
|
||||
**Ziel:** Die Lead-Automatik legt neue Lead-Previews in der Organisation WEBklar an und arbeitet mit dem Schlüssel `lead-automatik` des Kontos `webklar-system`. Bestehende Lead-Repos bei knso werden weiter unter ihrem bisherigen Pfad bedient.
|
||||
|
||||
**Aufbau:**
|
||||
|
||||
- **Neues Modul `repo_paths.py`:** reine Funktionen, die entscheiden:
|
||||
- wem ein Lead-Repo gehört,
|
||||
- wie Pfad, Web-Adresse und Git-Adresse lauten,
|
||||
- über welche Gitea-Schnittstelle ein Repo angelegt wird,
|
||||
- ob es einen Einzel-Webhook braucht.
|
||||
|
||||
Es wird mit pytest geprüft.
|
||||
- **Bestehende Dateien:** `worker.py`, `processor.py`, `enrich.py`, `backfill_leads.py` und `migrate_previews.py` nutzen diese Funktionen statt `GITEA_USER/<repo_name>`.
|
||||
- **Unveränderter Start:** Ohne neue Einstellungen verhält sich alles wie heute. Der Code geht deshalb zuerst live, die Umstellung folgt danach über `config.env`.
|
||||
|
||||
**Technik:**
|
||||
|
||||
- Python 3 (System), pytest 6.2.5
|
||||
- systemd-Dienste `webklar-automation-hook` und `webklar-automation-processor`
|
||||
- Cron-Lauf `worker.py` um 06:30 Uhr (Europe/Berlin)
|
||||
|
||||
**Spec:** `docs/superpowers/specs/2026-09-27-gitea-organisation-design.md` im Kundenportal-Repo. Maßgeblich sind die Abschnitte 8, 9, 11 und 12.
|
||||
|
||||
**Voraussetzung:** Plan 1 ist live (dort L5). Erst dann erzeugen Pushes des System-Kontos keine Arbeitsblätter mehr. Mit dem alten Portal-Code bekämen Lead-Tickets sonst Arbeitsblätter auf „webklar-system“.
|
||||
|
||||
## Global Constraints
|
||||
|
||||
**Werte, die wörtlich gelten**
|
||||
|
||||
- **Neue Schlüssel in `config.env`:**
|
||||
- `GITEA_AUTH_USER=webklar-system`. Das ist der Benutzer in Git-Adressen.
|
||||
- `GITEA_NEW_REPO_OWNER=WEBklar`
|
||||
- **Unveränderte Schlüssel:** `GITEA_USER=knso` bleibt, als Besitzer der bestehenden Lead-Repos. `TEMPLATE_OWNER` und `TEMPLATE_REPO` bleiben ebenfalls.
|
||||
- **`GITEA_TOKEN`** wird der Schlüssel `lead-automatik` des Kontos `webklar-system`, mit den Rechten `write:repository,write:organization,read:user`.
|
||||
- **Voreinstellungen:**
|
||||
- Fehlt `GITEA_AUTH_USER`, gilt `GITEA_USER`.
|
||||
- Fehlt `GITEA_NEW_REPO_OWNER`, gilt `GITEA_USER`.
|
||||
- Ohne die neuen Schlüssel ist das Verhalten also genau wie heute.
|
||||
- **Besitzer eines Lead-Repos** (`lead_repo_owner`), in dieser Reihenfolge:
|
||||
1. `lead["repo_owner"]`, falls gesetzt,
|
||||
2. sonst der Besitzer aus `lead["repo_url"]` (`https://host/<besitzer>/<repo>`),
|
||||
3. sonst `GITEA_NEW_REPO_OWNER`.
|
||||
|
||||
Das präzisiert Spec 8 („bestehende Leads gelten als knso“): Alle bestehenden Leads mit Repo tragen `repo_url` auf knso, und nur Leads ohne Repo kommen neu nach WEBklar.
|
||||
- **Einzel-Webhook:** Er wird nur dann **nicht** angelegt, wenn der Besitzer `GITEA_NEW_REPO_OWNER` ist und dieser von `GITEA_USER` abweicht. Das ist der Fall der Organisation, in dem ihr eigener Webhook greift.
|
||||
|
||||
**Sicherheit und Betrieb**
|
||||
|
||||
- Schlüssel stehen nie in Logs, Tests oder Ausgaben.
|
||||
- `config.env` wird nie committet.
|
||||
- Der Verarbeitungsdienst arbeitet alle 5 Minuten. Umgestellt wird darum nur, während er gestoppt ist (siehe Livegang).
|
||||
- Nach jeder Code-Änderung werden beide Dienste neu gestartet, weil sie die Module beim Start laden.
|
||||
|
||||
**Git**
|
||||
|
||||
- `/opt/webklar-automation` ist bisher kein Git-Repo. Task 1 legt ein **lokales** Repo ohne Remote an, nur für Nachvollziehbarkeit und Review. `.gitignore` schützt Geheimnisse und Daten.
|
||||
- Immer `git add <Dateien>`, nie `-A`.
|
||||
|
||||
## Review Focus
|
||||
|
||||
1. **Alter Lead mit `repo_url` auf knso, ohne `repo_owner`.** Weitere Pushes, etwa aus der Anreicherung, gehen weiter nach `knso/<repo>`. Test: Task 2, „alter Lead bleibt bei knso“.
|
||||
2. **Ein neuer Lead wird erneut verarbeitet, und sein Repo existiert schon in WEBklar (409).** Das Repo wird weiterverwendet, ohne Einzel-Webhook. Tests: Task 2 (`needs_repo_webhook`) und Task 3 (Prüfschritt).
|
||||
3. **Git-Adresse.** Benutzer ist `GITEA_AUTH_USER`, der Pfad kommt aber vom Besitzer des Leads, nicht vom Benutzer. Test: Task 2, `git_url`.
|
||||
4. **Ohne die neuen Einstellungen** bleiben Pfade, Anlage über `/user/repos` und Einzel-Webhook genau wie heute. Test: Task 2, „ohne neue Einstellungen wie heute“.
|
||||
5. **Die Vorlage liegt bei knso** (`TEMPLATE_OWNER`) und wird mit dem System-Konto geklont. Geprüft wird das im Livegang, Probelauf L3.
|
||||
|
||||
---
|
||||
|
||||
## Dateien
|
||||
|
||||
| Datei | Aufgabe |
|
||||
|---|---|
|
||||
| `.gitignore` (neu) | schließt `config.env*`, Daten, Logs und Sicherungen aus |
|
||||
| `repo_paths.py` (neu) | `lead_repo_owner`, `repo_full`, `web_url`, `git_url`, `repo_create_path`, `needs_repo_webhook` |
|
||||
| `tests/test_repo_paths.py` (neu) | Tests |
|
||||
| `worker.py` | Einstellungen, `ensure_repo(repo_name, firma, owner)`, Git- und Web-Adressen, Lookups, `_mark_processed(lead_id, repo_url, repo_owner)` |
|
||||
| `processor.py`, `enrich.py` | Adressen mit Besitzer |
|
||||
| `backfill_leads.py`, `migrate_previews.py` | Pfade über `W.lead_repo_full(lead)` |
|
||||
|
||||
---
|
||||
|
||||
### Task 1: Lokales Git-Repo und Ausgangsstand
|
||||
|
||||
- [ ] **Step 1: Write `.gitignore`**
|
||||
|
||||
```
|
||||
config.env*
|
||||
*.bak*
|
||||
logs/
|
||||
queue/
|
||||
status/
|
||||
tmp/
|
||||
backups/
|
||||
master*.json
|
||||
master*.xlsx
|
||||
standorte.*
|
||||
__pycache__/
|
||||
.pytest_cache/
|
||||
```
|
||||
|
||||
- [ ] **Step 2: Create the repo and commit the baseline**
|
||||
|
||||
```bash
|
||||
cd /opt/webklar-automation && git init -q
|
||||
git add .gitignore *.py DOKUMENTATION.md ROUTINE_ANREICHERUNG_PROMPT.txt fallback-assets
|
||||
git -c user.name=Claude -c user.email=claude@webklar.local commit -qm "Ausgangsstand Lead-Automatik"
|
||||
git ls-files | grep -c "config.env\|master\|\.bak"
|
||||
```
|
||||
|
||||
Expected: `0`. `git status --short` shows no `.py` file as untracked.
|
||||
|
||||
---
|
||||
|
||||
### Task 2: Pfad-Regeln als reine Funktionen
|
||||
|
||||
**Files:**
|
||||
- Create: `repo_paths.py`, `tests/test_repo_paths.py`
|
||||
|
||||
**Interfaces:**
|
||||
- Produces:
|
||||
- `lead_repo_owner(lead: dict, legacy_owner: str, new_owner: str) -> str`
|
||||
- `repo_full(owner: str, repo_name: str) -> str`
|
||||
- `web_url(base_url: str, owner: str, repo_name: str) -> str`
|
||||
- `git_url(base_url: str, auth_user: str, token: str, owner: str, repo_name: str) -> str`
|
||||
- `repo_create_path(owner: str, auth_user: str, legacy_owner: str) -> str | None`. The cases, checked in this order and ignoring case:
|
||||
1. `owner == auth_user` → `'/user/repos'`,
|
||||
2. `owner == legacy_owner` → `None`, meaning the repo must already exist,
|
||||
3. otherwise → `'/orgs/<owner>/repos'`.
|
||||
- `needs_repo_webhook(owner: str, new_owner: str, legacy_owner: str) -> bool`
|
||||
|
||||
- [ ] **Step 1: Write the failing tests**
|
||||
|
||||
```python
|
||||
B = "https://git.webklar.com"
|
||||
def test_alter_lead_bleibt_bei_knso():
|
||||
lead = {"repo_name": "preview-x", "repo_url": B + "/knso/preview-x"}
|
||||
assert lead_repo_owner(lead, "knso", "WEBklar") == "knso"
|
||||
def test_repo_owner_hat_vorrang():
|
||||
assert lead_repo_owner({"repo_owner": "WEBklar", "repo_url": B + "/knso/x"}, "knso", "WEBklar") == "WEBklar"
|
||||
def test_neuer_lead_kommt_nach_webklar():
|
||||
assert lead_repo_owner({"repo_name": "preview-neu", "repo_url": ""}, "knso", "WEBklar") == "WEBklar"
|
||||
def test_ohne_neue_einstellungen_wie_heute():
|
||||
assert lead_repo_owner({"repo_name": "preview-neu"}, "knso", "knso") == "knso"
|
||||
assert repo_create_path("knso", "knso", "knso") == "/user/repos"
|
||||
assert needs_repo_webhook("knso", "knso", "knso") is True
|
||||
def test_adressen():
|
||||
assert repo_full("WEBklar", "preview-x") == "WEBklar/preview-x"
|
||||
assert web_url(B, "WEBklar", "preview-x") == B + "/WEBklar/preview-x"
|
||||
assert git_url(B, "webklar-system", "TOKEN", "knso", "preview-x") == "https://webklar-system:TOKEN@git.webklar.com/knso/preview-x.git"
|
||||
def test_anlegen():
|
||||
assert repo_create_path("WEBklar", "webklar-system", "knso") == "/orgs/WEBklar/repos"
|
||||
assert repo_create_path("knso", "webklar-system", "knso") is None
|
||||
assert repo_create_path("Webklar-System", "webklar-system", "knso") == "/user/repos"
|
||||
def test_einzel_webhook():
|
||||
assert needs_repo_webhook("WEBklar", "WEBklar", "knso") is False
|
||||
assert needs_repo_webhook("webklar", "WEBklar", "knso") is False
|
||||
assert needs_repo_webhook("knso", "WEBklar", "knso") is True
|
||||
```
|
||||
|
||||
- [ ] **Step 2: Run to verify it fails**
|
||||
|
||||
Run: `cd /opt/webklar-automation && python3 -m pytest -q tests`
|
||||
Expected: FAIL (`ModuleNotFoundError: repo_paths`)
|
||||
|
||||
- [ ] **Step 3: Implement `repo_paths.py`**
|
||||
|
||||
- It uses only the standard library and loads no configuration.
|
||||
- `git_url` builds the host from `base_url` without the scheme.
|
||||
- The owner is extracted from `repo_url` with `^https?://[^/]+/([^/]+)/[^/]+/?$`.
|
||||
|
||||
- [ ] **Step 4: Run to verify it passes**
|
||||
|
||||
Run: `python3 -m pytest -q tests`
|
||||
Expected: `7 passed`
|
||||
|
||||
- [ ] **Step 5: Commit**
|
||||
|
||||
```bash
|
||||
git add repo_paths.py tests/test_repo_paths.py && git -c user.name=Claude -c user.email=claude@webklar.local commit -qm "Pfad-Regeln für Lead-Repos mit Tests"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Task 3: worker.py umstellen
|
||||
|
||||
**Files:**
|
||||
- Modify: `worker.py`
|
||||
|
||||
**Interfaces:**
|
||||
- Consumes: `repo_paths` (Task 2)
|
||||
- Produces, for the other files:
|
||||
- `W.GITEA_AUTH_USER`
|
||||
- `W.NEW_REPO_OWNER`
|
||||
- `W.lead_repo_owner(lead) -> str`
|
||||
- `W.lead_repo_full(lead) -> str`
|
||||
- `W.lead_git_url(owner, repo_name) -> str`
|
||||
- `W.lead_web_url(owner, repo_name) -> str`
|
||||
- `W.ensure_repo(repo_name, firma, owner)`
|
||||
- `W._mark_processed(lead_id, repo_url, repo_owner="")`
|
||||
|
||||
- [ ] **Step 1: Implement**
|
||||
|
||||
1. **Settings:** `GITEA_AUTH_USER = cfg.get("GITEA_AUTH_USER") or GITEA_USER` and `NEW_REPO_OWNER = cfg.get("GITEA_NEW_REPO_OWNER") or GITEA_USER`. Add the four `lead_*` wrappers, which fill the `repo_paths` functions with these values.
|
||||
2. **`ensure_repo(repo_name, firma, owner)`:**
|
||||
- `path = repo_create_path(owner, GITEA_AUTH_USER, GITEA_USER)`.
|
||||
- If `path` is `None`, check `GET /repos/<owner>/<repo_name>`. On 200, reuse the repo; otherwise raise `RuntimeError("Repo %s/%s fehlt und kann dort nicht angelegt werden" % (owner, repo_name))`.
|
||||
- Otherwise POST to `path`. As today, 409 means reuse.
|
||||
- Only call `ensure_repo_webhook(repo_full(owner, repo_name))` if `needs_repo_webhook(owner, NEW_REPO_OWNER, GITEA_USER)`.
|
||||
3. **Git and web addresses:**
|
||||
- The push URL in the fill step (today line 531) becomes `lead_git_url(owner, repo_name)`, so that function gets `owner` as a parameter.
|
||||
- `get_template_cache` uses `GITEA_AUTH_USER` as the user; owner and repo stay `TEMPLATE_OWNER`/`TEMPLATE_REPO`.
|
||||
- Every `"%s/%s/%s" % (GITEA_URL, GITEA_USER, repo_name)` becomes `lead_web_url(owner, repo_name)`.
|
||||
4. **Lookups:** In the three functions at the former lines 331, 373 and 415, use `lead_repo_full(lead)`.
|
||||
5. **`process_lead`:**
|
||||
- `owner = lead_repo_owner(lead)`, set `lead["repo_owner"] = owner`, then `ensure_repo(repo_name, firma, owner)`.
|
||||
- `_mark_processed(lead_id, repo_url, owner)` also stores `repo_owner` in `master.json`.
|
||||
- The caller around the former line 621 passes the owner along.
|
||||
|
||||
- [ ] **Step 2: Check**
|
||||
|
||||
```bash
|
||||
python3 -m py_compile worker.py && python3 -m pytest -q tests
|
||||
grep -n "GITEA_USER, repo_name\|GITEA_USER, (lead" worker.py
|
||||
```
|
||||
|
||||
Expected: compiles, `7 passed`, and grep finds nothing.
|
||||
|
||||
- [ ] **Step 3: Commit**
|
||||
|
||||
```bash
|
||||
git add worker.py && git -c user.name=Claude -c user.email=claude@webklar.local commit -qm "worker: Repo-Besitzer pro Lead, System-Konto für Git-Adressen"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Task 4: processor.py, enrich.py und Wartungsskripte umstellen
|
||||
|
||||
**Files:**
|
||||
- Modify: `processor.py`, `enrich.py`, `backfill_leads.py`, `migrate_previews.py`
|
||||
|
||||
**Interfaces:**
|
||||
- Consumes: the `W.*` functions from Task 3
|
||||
|
||||
- [ ] **Step 1: Implement**
|
||||
|
||||
- **`processor.py`:**
|
||||
- `_repo_url(owner, repo_name)` returns `worker.lead_git_url(owner, repo_name)`.
|
||||
- `_clone_repo(owner, repo_name)`.
|
||||
- In `process_lead`: `owner = worker.lead_repo_owner(lead)`, then `worker.ensure_repo(repo_name, firma, owner)`, `worker._mark_processed(lead_id, repo_url, owner)`, and `repo_url = worker.lead_web_url(owner, repo_name)`.
|
||||
- **`enrich.py`:**
|
||||
- `_repo_push_url(owner, repo_name)` and `_clone_repo(owner, repo_name)`.
|
||||
- `_apply_to_repo(owner, repo_name, p, ctx=None)`. The caller determines `owner = worker.lead_repo_owner(lead)` inside the `master_lock` block, together with `repo_name`.
|
||||
- In `get_file`, the API path uses `worker.lead_repo_owner(lead)` instead of `worker.GITEA_USER`.
|
||||
- `repo_url` (the former line 1049) becomes `worker.lead_web_url(owner, repo_name)`.
|
||||
- **`backfill_leads.py` and `migrate_previews.py`:** `W.lead_repo_full(lead)` instead of `"%s/%s" % (W.GITEA_USER, repo_name)`.
|
||||
|
||||
- [ ] **Step 2: Check**
|
||||
|
||||
```bash
|
||||
python3 -m py_compile processor.py enrich.py backfill_leads.py migrate_previews.py hook_server.py && python3 -m pytest -q tests
|
||||
grep -n "GITEA_USER" processor.py enrich.py backfill_leads.py migrate_previews.py
|
||||
```
|
||||
|
||||
Expected: compiles, `7 passed`. `GITEA_USER` appears in these four files nowhere, or only in comments.
|
||||
|
||||
- [ ] **Step 3: Restart with unchanged `config.env` (the code alone changes nothing)**
|
||||
|
||||
With the user's OK:
|
||||
|
||||
```bash
|
||||
systemctl restart webklar-automation-hook webklar-automation-processor
|
||||
systemctl is-active webklar-automation-hook webklar-automation-processor
|
||||
```
|
||||
|
||||
Expected: `active` twice, and the log files in `logs/` show no traceback in the next 10 minutes.
|
||||
|
||||
- [ ] **Step 4: Commit**
|
||||
|
||||
```bash
|
||||
git add processor.py enrich.py backfill_leads.py migrate_previews.py && git -c user.name=Claude -c user.email=claude@webklar.local commit -qm "processor/enrich: Adressen mit Repo-Besitzer des Leads"
|
||||
```
|
||||
|
||||
- [ ] **Step 5: Review**
|
||||
|
||||
Run a fresh code review of `git diff <Ausgangsstand>..HEAD` against Spec 8 and this plan (`superpowers:requesting-code-review`).
|
||||
|
||||
---
|
||||
|
||||
## Livegang – nur mit OK des Nutzers, erst nach Plan 1 L5
|
||||
|
||||
**L0 Sicherung**
|
||||
|
||||
```bash
|
||||
cp /opt/webklar-automation/config.env /opt/webklar-automation/config.env.bak-gitea-org
|
||||
```
|
||||
|
||||
**L1 Schlüssel und Einstellungen.** The processor is stopped, so no lead is in the middle of processing:
|
||||
|
||||
```bash
|
||||
systemctl stop webklar-automation-processor
|
||||
docker exec -u git webklar-gitea gitea admin user generate-access-token --username webklar-system --token-name lead-automatik --scopes write:repository,write:organization,read:user --raw \
|
||||
| python3 -c 'import re,sys; t=sys.stdin.read().strip(); p="/opt/webklar-automation/config.env"; s=open(p).read(); s=re.sub(r"^GITEA_TOKEN=.*$", "GITEA_TOKEN="+t, s, flags=re.M); open(p,"w").write(s.rstrip("\n")+"\nGITEA_AUTH_USER=webklar-system\nGITEA_NEW_REPO_OWNER=WEBklar\n")'
|
||||
grep -c "^GITEA_AUTH_USER=webklar-system$\|^GITEA_NEW_REPO_OWNER=WEBklar$" /opt/webklar-automation/config.env
|
||||
systemctl start webklar-automation-processor && systemctl restart webklar-automation-hook
|
||||
```
|
||||
|
||||
Expected: `2`, and both services `active`. The token only passes through the pipe.
|
||||
|
||||
**L2 Zugang prüfen:**
|
||||
|
||||
```bash
|
||||
cd /opt/webklar-automation && python3 -c "import worker as W; c,u=W.gitea_api('GET','/user'); print(c,u.get('login'),u.get('is_admin'))"
|
||||
```
|
||||
|
||||
Expected: `200 webklar-system True`.
|
||||
|
||||
**L3 Probelauf mit Wegwerf-Repo.** Only with explicit OK:
|
||||
|
||||
1. With the worker functions, create `WEBklar/preview-probe-gitea-org` (`W.ensure_repo('preview-probe-gitea-org', 'Probe', W.NEW_REPO_OWNER)`), then `W.set_repo_file('WEBklar/preview-probe-gitea-org', 'index.html', '<h1>Probe</h1>', 'Probe')`.
|
||||
2. Clone the template once with `W.get_template_cache()`. This checks that the system account can read `knso/<TEMPLATE_REPO>`.
|
||||
3. Expected:
|
||||
- the repo exists in WEBklar,
|
||||
- `GET /repos/WEBklar/preview-probe-gitea-org/hooks` returns `[]`,
|
||||
- within a minute the portal has a `websiteProjects` entry `WEBklar/preview-probe-gitea-org` with status `synced`, created by the organisation webhook,
|
||||
- no new arbeitsblatt.
|
||||
4. Clean up:
|
||||
- `DELETE /repos/WEBklar/preview-probe-gitea-org`,
|
||||
- delete the `websiteProjects` entry with a script in the portal container,
|
||||
- check that both are gone.
|
||||
|
||||
**L4 Beobachten.** Check the next real lead: its repo is created in WEBklar, the preview deploys, and there is no per-repo webhook. Update the memory note.
|
||||
|
||||
**Rückweg:**
|
||||
|
||||
```bash
|
||||
cp /opt/webklar-automation/config.env.bak-gitea-org /opt/webklar-automation/config.env
|
||||
systemctl restart webklar-automation-hook webklar-automation-processor
|
||||
```
|
||||
|
||||
The code can stay: without the new keys it behaves as it does today. Remove the `lead-automatik` token in Gitea under Settings → Applications of the account, as an admin.
|
||||
Reference in new issue
Block a user