From 24212bd2a12c58226eb09f9369ec87b9a037b545 Mon Sep 17 00:00:00 2001 From: Ruben van der Linde Date: Tue, 18 Aug 2026 15:57:07 +0200 Subject: [PATCH 01/70] =?UTF-8?q?docs(openspec):=20change=20proposals=20?= =?UTF-8?q?=E2=80=94=20adopt-integration-leaves=20mcp-full-action-surface?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Artifacts only. Every task box is unticked; nothing here is wired to anything yet, and each change is picked up by `/opsx-apply` when it is scheduled. Committed because these were sitting UNTRACKED in the shared checkout across ten apps at once. An untracked directory is one file-sweep away from being swept into an unrelated commit and one branch switch away from being lost, and these carry the design reasoning rather than just a title. --- .../adopt-integration-leaves/.openspec.yaml | 2 + .../adopt-integration-leaves/design.md | 110 +++++++++ .../adopt-integration-leaves/proposal.md | 96 ++++++++ .../specs/catalog-integration-leaves/spec.md | 144 ++++++++++++ .../changes/adopt-integration-leaves/tasks.md | 80 +++++++ .../mcp-full-action-surface/.openspec.yaml | 2 + .../changes/mcp-full-action-surface/design.md | 212 ++++++++++++++++++ .../mcp-full-action-surface/proposal.md | 120 ++++++++++ .../specs/mcp-tool-surface/spec.md | 191 ++++++++++++++++ .../changes/mcp-full-action-surface/tasks.md | 113 ++++++++++ 10 files changed, 1070 insertions(+) create mode 100644 openspec/changes/adopt-integration-leaves/.openspec.yaml create mode 100644 openspec/changes/adopt-integration-leaves/design.md create mode 100644 openspec/changes/adopt-integration-leaves/proposal.md create mode 100644 openspec/changes/adopt-integration-leaves/specs/catalog-integration-leaves/spec.md create mode 100644 openspec/changes/adopt-integration-leaves/tasks.md create mode 100644 openspec/changes/mcp-full-action-surface/.openspec.yaml create mode 100644 openspec/changes/mcp-full-action-surface/design.md create mode 100644 openspec/changes/mcp-full-action-surface/proposal.md create mode 100644 openspec/changes/mcp-full-action-surface/specs/mcp-tool-surface/spec.md create mode 100644 openspec/changes/mcp-full-action-surface/tasks.md diff --git a/openspec/changes/adopt-integration-leaves/.openspec.yaml b/openspec/changes/adopt-integration-leaves/.openspec.yaml new file mode 100644 index 00000000..95672402 --- /dev/null +++ b/openspec/changes/adopt-integration-leaves/.openspec.yaml @@ -0,0 +1,2 @@ +schema: spec-driven +created: 2026-08-18 diff --git a/openspec/changes/adopt-integration-leaves/design.md b/openspec/changes/adopt-integration-leaves/design.md new file mode 100644 index 00000000..be99cf97 --- /dev/null +++ b/openspec/changes/adopt-integration-leaves/design.md @@ -0,0 +1,110 @@ +# Design — adopt-integration-leaves + +## 1. Leaf-per-schema mapping and why each pairing is the right one + +| Schema | Leaf | Grounding (real fields / services) | +|---|---|---| +| `contactPerson` | `contacts` | `contactsUid` — "Verwijzing (UID) naar de Nextcloud-contactpersoon in het adresboek (OCP\Contacts\IManager)". The catalogue record is explicitly only the ROLE; identity lives in NC Contacts. The leaf makes the vCard (display name, email, avatar) visible and linkable on `ContactpersoonDetail` instead of a bare UID string. | +| `organization` | `contacts` | `organization.contactsUid` — same convention, "contactpersoon van het type organisatie". | +| `contract` | `calendar` | `contract.startDate` / `contract.endDate` ("De einddatum van het contract (indien van toepassing)"). End dates drive renewal planning; the leaf gives every contract a Meetings/Events tab plus the synced end-date event (section 3). | +| `moduleVersion` | `calendar` | `dateEndSupport` ("Startdatum einde ondersteuning") and `dateWithdrawn`. `dateEndSupport` is machine-maintained by the EOL sync (`eolSource`, `eolUpdatedOn` — "Alleen gezet door de EOL-matcher", `EolSyncService::run()`), so the synced event tracks upstream endoflife.date data. | +| `assessment` | `deck` | Reviews carry moderation state (`status` pending/approved/rejected, forced to `pending` server-side by `ReviewService::submit()`, transitioned only by `ModerationService::approve()/reject()` — see `register.d/catalog-ratings.json`). Follow-ups ("moderate this review", "discuss rating 2/10 for module X with the vendor") are card-shaped work; `DeckProvider` supports both link-existing (`{cardId}`) and create-and-link (`{boardId, stackId, title}`). | +| `module` | `bookmarks` | `module.website` is one URL; real applications have docs, changelog, security advisories, pricing pages. `BookmarksProvider` stores links in OR's own `openregister_bookmark_links` table (survives Bookmarks tag edits, caches title/url for the sidebar). | +| `service` | `bookmarks` | `service.website` — same reasoning for supplier service offerings. | + +Leaf ids verified against openregister at HEAD: +`LinkedEntityService::legacyLinkedTypeIds()` = `files`, `mail`, `contacts`, +`notes`, `todos`, `calendar`, `talk`, `deck`; registered +`IntegrationProvider::getId()` values include `contacts`, `calendar`, +`deck`, `bookmarks` (`openregister/lib/Service/Integration/Providers/`). +`LinkedEntityService::validateType()` throws on anything else, and +`Repair/LogDanglingLinkedTypes` logs schemas whose `linkedTypes` name an +unregistered integration — both act as loud guards against a typo in the +fragment. + +## 2. Fragment mechanics — the `contract` array hazard + +`contract` is the ONE schema that already carries `linkedTypes` +(`["decidesk-decisions"]`, in the monolith). ADR-037 fragments are +deep-merged by `SettingsService::loadSettings()` (`deepMergeConfig()`); for +scalar/object keys the merge is a union, but array-of-scalar semantics +(union vs replace) must not be assumed. The fragment therefore declares the +FULL intended array — `["decidesk-decisions", "calendar"]` — which is +correct under either semantic: + +- replace → the merged value is exactly the full array; +- union → `decidesk-decisions` deduplicates, `calendar` is added. + +Task 1.3 verifies the merged output (`/api/settings/load`) contains both +entries exactly once. Losing `decidesk-decisions` would silently break the +ContractApprovalPanel's decision leaf — this is the highest-risk line of the +whole change, hence its own task and scenario. + +## 3. Lifecycle-date calendar sync + +`CalendarProvider` is a read/render surface: it lists CalDAV VEVENTs that +carry `X-OPENREGISTER-*` properties identifying the owning object +(persistence is owned by the Calendar app; creation flows via OR's +`CalendarEventService`). Declaring `calendar` in `linkedTypes` gives the +tab and manual link/create, but nobody will hand-create "contract ends" +events for every contract — so this change adds a thin app-side sync: + +- `lib/Service/LifecycleCalendarService.php` — `syncContract(objectData)` + and `syncModuleVersion(objectData)`; upserts (creates, moves, or + deletes) one all-day linked event per tracked date field via OR's + calendar link path. Event titles are English per fleet convention + (`feedback_english-code`): "Contract ends: {contractNumber}" / + "End of support: {module name} {version}". +- `lib/Listener/LifecycleCalendarListener.php` — subscribes to + OpenRegister's object-saved event for the voorzieningen register, + filters on the `contract` / `moduleVersion` schema slugs resolved + through `SettingsService` (never hard-coded register ids), and delegates + to the service. Deletion of the object removes the linked event (OR's + `ObjectCleanupListener` already unlinks leaf rows; the listener only + needs to handle date-cleared-on-save). +- Idempotency: the event is looked up by its object link + a + deterministic marker (one tracked field = one event), so re-saves and + EOL re-stamps move the single event instead of accumulating duplicates. +- Fail-soft: calendar unavailable (app disabled, no writable calendar) is + logged and never blocks the object save — same graceful-degradation + posture the register's other integrations use. + +**Which calendar?** The events are personal CalDAV objects; the sync runs +in the saving user's session and writes to that user's default calendar +(the same calendar OR's create-event leaf flow targets). A shared +"portfolio calendar" is a legitimate future improvement, deferred — it +needs an ownership/config decision (`declared-config-enforced-nowhere` is +the failure mode to avoid: no config key is introduced here until +something reads it). + +## 4. Deferred (explicitly out of scope) + +- `connection.dateEndSupport` / `dateWithdrawn` — same calendar shape as + `moduleVersion`, deferred until the koppeling detail surface is + reviewed; adding it later is one fragment line + one listener case. +- `compliancy.url` / `evidenceReference` as bookmarks — compliance + evidence is file/reference-shaped and already has `allowFiles: true` + + `evidenceReference`; forcing it into bookmarks would duplicate an + existing surface. +- `usage` deck leaf (TIME-classification review follow-ups via + `timeReviewDate`) — plausible, but the assessment leaf should prove the + pattern first. +- NC Mail (`configuration.linkedTypes: ["mail"]` sidebar target + + `mailObjectTemplate`) — a separate comms-rule discussion; the manifest + `_note`s document a deliberate "comms hard-rule" that email widgets stay + off these detail pages, and this change does not reopen it. + +## 5. Manifest touch-points + +`src/manifest.json` detail pages affected: `ContactpersoonDetail`, +`ContractDetail`, `ModuleDetail`, `Diensten`/`DienstDetail` equivalent, +`ModuleversieDetail`, `ReviewDetail`, `OrganisatieDetail`. The leaf tabs +render from schema `linkedTypes` via the shared detail-page sidebar +(`CnObjectSidebar` — "so the CnObjectSidebar and dashboard widgets can +render a … tab without per-app glue", per `CalendarProvider`'s own +docblock); no per-page widget wiring is expected, but the two `_note` +strings that assert "declares NO email/calendar linkedType" become false +for `contract`/`contactPerson` and MUST be rewritten to describe the new +state, so the next audit doesn't read a stale premise +(`reference_design-system-adoption-silent-failures`: notes that lie are +worse than no notes). diff --git a/openspec/changes/adopt-integration-leaves/proposal.md b/openspec/changes/adopt-integration-leaves/proposal.md new file mode 100644 index 00000000..cb5a77c8 --- /dev/null +++ b/openspec/changes/adopt-integration-leaves/proposal.md @@ -0,0 +1,96 @@ +--- +kind: code +depends_on: [] +--- + +# softwarecatalog — adopt OpenRegister integration leaves (contacts, calendar, deck, bookmarks) + +## Why + +OpenRegister ships an app-agnostic integration-leaf registry +(`openregister/lib/Service/Integration/IntegrationRegistry.php` + +`Providers/`): a schema that declares a leaf id in +`configuration.linkedTypes` gets that Nextcloud app's link surface (sidebar +tab / widgets on the object detail page) with zero per-app glue — +`ContactsProvider` (id `contacts`), `CalendarProvider` (`calendar`), +`DeckProvider` (`deck`), `BookmarksProvider` (`bookmarks`) all exist today, +alongside the legacy allow-list ids in +`LinkedEntityService::legacyLinkedTypeIds()` (`files`, `mail`, `contacts`, +`notes`, `todos`, `calendar`, `talk`, `deck`). + +Software Catalog consumes almost none of this. Verified against +`lib/Settings/softwarecatalogus_register.json` at HEAD: + +- `allowFiles: true` on exactly 6 schemas (`suite`, `service`, + `organization`, `usage`, `module`, `compliancy`) — the files leaf. +- `linkedTypes` on exactly one schema: `contract` → + `["decidesk-decisions"]` (the ADR-066 approval projection). +- No schema declares `contacts`, `calendar`, `deck`, or `bookmarks`. + +That leaves four gaps the domain data is already shaped for: + +1. **Contacts** — `contactPerson.contactsUid` is literally "Verwijzing + (UID) naar de Nextcloud-contactpersoon in het adresboek + (`OCP\Contacts\IManager`)", and `organization.contactsUid` mirrors it. + The identity IS a Nextcloud contact by design (the + `ContactpersoonDetail` manifest note says communication happens + "through the linked Nextcloud contact via contactsUid"), yet the detail + page renders no contacts leaf — the vCard link exists only as a bare + string property. +2. **Calendar** — `contract.endDate` ("De einddatum van het contract") and + `moduleVersion.dateEndSupport` ("Startdatum einde ondersteuning", + stamped by the EOL matcher per `eolSource`/`eolUpdatedOn`) are the two + dates portfolio managers plan around, and neither is visible in any + calendar. The `ContractDetail` manifest `_note` even hard-codes the + current state: "The contract schema declares NO email/calendar + linkedType". +3. **Deck** — `assessment` records (reviews, live since the + `catalog-ratings` fragment added `auteur` + moderation `status` + pending/approved/rejected, enforced by `ReviewService::submit()` and + `ModerationService::approve()/reject()`) generate follow-up work + (moderate a pending review, chase a vendor about a bad rating) that has + no task surface. +4. **Bookmarks** — `module.website` ("Een URL naar uw applicatie") and + `service.website` are single URL strings; vendors accumulate more than + one relevant link (docs, changelog, status page, pricing) and today + have nowhere structured to put them. + +## What Changes + +- Add a new ADR-037 register fragment + `lib/Settings/register.d/catalog-integration-leaves.json` (never editing + the `softwarecatalogus_register.json` monolith) that declares + `configuration.linkedTypes`: + - `contactPerson`: `["contacts"]` + - `organization`: `["contacts"]` + - `contract`: `["decidesk-decisions", "calendar"]` — restating the + existing `decidesk-decisions` entry so the merged array is correct + regardless of whether the ADR-037 deep-merge unions or replaces + arrays (verified behaviour recorded in `design.md`). + - `moduleVersion`: `["calendar"]` + - `assessment`: `["deck"]` + - `module`: `["bookmarks"]` + - `service`: `["bookmarks"]` +- Add a lifecycle-date calendar sync (`lib/Service/LifecycleCalendarService.php` + + an OR object-saved listener): when `contract.endDate` or + `moduleVersion.dateEndSupport` is set or changed, upsert a linked + all-day VEVENT through OpenRegister's calendar link path (the same + `X-OPENREGISTER-*`-marked events `CalendarProvider::list()` renders), + so the calendar leaf tab shows the end-of-contract / end-of-support + event without manual linking; remove the event when the date is + cleared. EOL-matcher re-stamps of `dateEndSupport` + (`EolSyncService::run()` → `EolMatcherService`) move the event. +- Update the stale `src/manifest.json` detail-page `_note` prose on + `ContractDetail` and `ContactpersoonDetail` (both currently assert "NO + email/calendar linkedType" as the reason no comms widgets are placed) + and verify the leaf tabs render on the `ContractDetail`, + `ContactpersoonDetail`, `ModuleversieDetail`, `ReviewDetail`, + `ModuleDetail`, and `Diensten` detail surfaces. +- No new leaf providers and no OpenRegister changes: everything consumed + here (`contacts`, `calendar`, `deck`, `bookmarks`) is already a + registered `IntegrationProvider` at openregister HEAD. + +Not BREAKING: purely additive configuration plus one new sync service; no +existing route, response shape, or schema property changes. The +`connection.dateEndSupport` and `compliancy.url` fields are deliberately +out of scope (see `design.md` deferrals). diff --git a/openspec/changes/adopt-integration-leaves/specs/catalog-integration-leaves/spec.md b/openspec/changes/adopt-integration-leaves/specs/catalog-integration-leaves/spec.md new file mode 100644 index 00000000..813c6ef2 --- /dev/null +++ b/openspec/changes/adopt-integration-leaves/specs/catalog-integration-leaves/spec.md @@ -0,0 +1,144 @@ +## ADDED Requirements + +### Requirement: Contact persons and organisations MUST expose the contacts leaf +The `contactPerson` and `organization` schemas SHALL declare `contacts` in +`configuration.linkedTypes`, so the detail pages render OpenRegister's +contacts leaf (vCard link rows with role, backed by +`openregister_contact_links` + `X-OPENREGISTER-*` vCard properties) for +the identity that today exists only as the bare `contactsUid` string. + +#### Scenario: Contact role detail shows the linked Nextcloud contact +- GIVEN a `contactPerson` object whose `contactsUid` references an existing + address-book contact +- WHEN a user opens `ContactpersoonDetail` (`/contactpersonen/:id`) +- THEN a contacts leaf tab MUST be present in the object sidebar +- AND it MUST list the linked contact with its display name (not the raw UID) +- @e2e Playwright: seed a contactPerson with a linked contact, open the + detail page, assert the contacts tab and the contact's display name + +#### Scenario: Organisation detail offers link-existing contact +- GIVEN an `organization` object with no linked contact +- WHEN a user opens the organisation detail page and uses the contacts leaf +- THEN the leaf MUST offer linking an existing address-book contact +- AND after linking, the contact MUST appear in the leaf list +- @e2e exclude Link-picker flow is owned and e2e-covered by OpenRegister's + integration-contacts suite; this app only declares the linkedType + +### Requirement: Contract end dates and version end-of-support dates MUST surface as calendar leaf events +The `contract` and `moduleVersion` schemas SHALL declare `calendar` in +`configuration.linkedTypes`, and the app SHALL maintain one linked all-day +calendar event per tracked date field — `contract.endDate` and +`moduleVersion.dateEndSupport` — created, moved, and removed by +`LifecycleCalendarService` when the field is set, changed (including EOL +re-stamps by `EolSyncService`/`EolMatcherService`), or cleared. Sync +failures MUST be logged and MUST NOT block the object save. + +#### Scenario: Setting a contract end date creates the leaf event +- GIVEN a contract whose `endDate` is empty +- WHEN a user saves the contract with `endDate = 2027-03-31` +- THEN a linked all-day event on 2027-03-31 MUST exist for that contract +- AND it MUST be listed in the calendar leaf tab on `ContractDetail` +- @e2e Playwright: set an endDate through the contract modal, open the + detail page, assert the calendar tab lists the end-date event + +#### Scenario: EOL matcher re-stamp moves the end-of-support event +- GIVEN a `moduleVersion` with a synced end-of-support event on 2026-12-01 +- WHEN the EOL sync (`EolSyncService::run()`) re-stamps `dateEndSupport` + to 2027-06-01 +- THEN the SAME linked event MUST now be on 2027-06-01 +- AND no duplicate end-of-support event MUST exist for that version +- @e2e exclude Background-job path with an external-feed dependency; + asserted by a PHPUnit test on `LifecycleCalendarService` upsert idempotency + +#### Scenario: Clearing the date removes the event without failing the save +- GIVEN a contract with a synced end-date event +- WHEN the contract is saved with `endDate` cleared +- THEN the save MUST succeed +- AND the linked end-date event MUST be removed +- @e2e exclude Deletion side-effect; asserted by PHPUnit on the listener + +#### Scenario: Calendar unavailable degrades gracefully +- GIVEN the Calendar app is disabled on the instance +- WHEN a contract with an `endDate` is saved +- THEN the save MUST succeed (HTTP 200 on the object write) +- AND the condition MUST be logged, not thrown +- @e2e exclude Requires disabling a server app mid-suite; asserted by + PHPUnit with a throwing calendar-service double + +### Requirement: Assessments MUST expose the deck leaf for follow-up work +The `assessment` schema SHALL declare `deck` in +`configuration.linkedTypes`, so review follow-ups (moderating a `pending` +review, acting on a low rating) can be tracked as Deck cards linked to the +assessment — supporting both `DeckProvider` create payload shapes +(`{cardId}` link-existing and `{boardId, stackId, title}` create-and-link). + +#### Scenario: A pending review gets a follow-up card +- GIVEN an `assessment` with moderation `status = pending` +- WHEN a moderator opens `ReviewDetail` and creates a card from the deck leaf +- THEN a Deck card linked to that assessment MUST be created +- AND the deck leaf tab MUST list it with its board/stack context +- @e2e Playwright: open a seeded pending review, create a card via the + deck leaf, assert it appears in the tab + +### Requirement: Applications and services MUST expose the bookmarks leaf for vendor and documentation links +The `module` and `service` schemas SHALL declare `bookmarks` in +`configuration.linkedTypes`, complementing the single `website` property +each schema carries with a structured, multi-link surface backed by +`openregister_bookmark_links`. + +#### Scenario: An application accumulates documentation links +- GIVEN a `module` object whose `website` property is set +- WHEN a user links two bookmarks (documentation, changelog) via the leaf + on `ModuleDetail` +- THEN both bookmarks MUST be listed in the bookmarks leaf tab with their + cached titles and URLs +- AND the `website` property MUST be unchanged +- @e2e Playwright: link a bookmark on a module detail page and assert the + tab renders title + URL + +#### Scenario: Bookmarks app uninstalled yields an empty leaf, not an error +- GIVEN the Bookmarks app is not installed +- WHEN a user opens `ModuleDetail` +- THEN the page MUST render without error +- AND the bookmarks leaf MUST present an empty/unavailable state +- @e2e exclude Requires uninstalling a server app; covered by + `BookmarksProvider`'s own contract (returns empty list when uninstalled) + +### Requirement: Leaf declarations MUST live in a register fragment and MUST preserve the contract's decidesk leaf +All `linkedTypes` additions SHALL be declared in a new +`lib/Settings/register.d/catalog-integration-leaves.json` fragment +(ADR-037); `lib/Settings/softwarecatalogus_register.json` MUST NOT be +modified. The fragment MUST declare `contract.configuration.linkedTypes` +as the full array `["decidesk-decisions", "calendar"]` so the existing +`decidesk-decisions` entry survives either array-merge semantic, and every +declared leaf id MUST be one that +`LinkedEntityService::validateType()` accepts at openregister HEAD. + +#### Scenario: Merged contract linkedTypes contain both leaves exactly once +- GIVEN the monolith declaring `contract.linkedTypes = ["decidesk-decisions"]` + and this change's fragment applied +- WHEN the merged settings are read (`GET /api/settings/load`) +- THEN `contract.configuration.linkedTypes` MUST contain + `decidesk-decisions` and `calendar`, each exactly once +- @e2e exclude Config-merge assertion; asserted by a PHPUnit test on + `SettingsService::loadSettings()` output + +#### Scenario: No dangling linked type is introduced +- GIVEN the fragment applied on an instance at openregister HEAD +- WHEN the `LogDanglingLinkedTypes` repair step runs +- THEN it MUST report zero schemas whose `linkedTypes` reference an + unregistered integration +- @e2e exclude Repair-step log assertion; verified via occ output in CI + +### Requirement: Stale manifest notes MUST be corrected +The `src/manifest.json` `_note` strings on `ContractDetail` and +`ContactpersoonDetail` that assert the schemas declare "NO email/calendar +linkedType" SHALL be rewritten to describe the post-change state, keeping +the documented comms hard-rule (no email widgets) intact and accurate. + +#### Scenario: Manifest notes no longer contradict the register +- GIVEN this change applied +- WHEN `src/manifest.json` is searched for "declares NO email/calendar linkedType" +- THEN no detail page whose schema now declares `calendar` or `contacts` + MUST carry that assertion +- @e2e exclude Documentation-string assertion; checked by grep in review diff --git a/openspec/changes/adopt-integration-leaves/tasks.md b/openspec/changes/adopt-integration-leaves/tasks.md new file mode 100644 index 00000000..b20cd1be --- /dev/null +++ b/openspec/changes/adopt-integration-leaves/tasks.md @@ -0,0 +1,80 @@ +# Tasks — adopt-integration-leaves + +## 1. Register fragment + +- [ ] 1.1 Add `lib/Settings/register.d/catalog-integration-leaves.json` + declaring `configuration.linkedTypes` on: `contactPerson` + + `organization` (`["contacts"]`), `contract` + (`["decidesk-decisions", "calendar"]` — full array, see design.md §2), + `moduleVersion` (`["calendar"]`), `assessment` (`["deck"]`), `module` + + `service` (`["bookmarks"]`). Validate with `python3 -m json.tool`. +- [ ] 1.2 Cross-check every leaf id against openregister HEAD: + `LinkedEntityService::legacyLinkedTypeIds()` plus + `IntegrationRegistry::listIds()` must accept all of `contacts`, + `calendar`, `deck`, `bookmarks` (they do at time of writing — re-verify + at apply time, the gate suite changes under you). +- [ ] 1.3 PHPUnit on `SettingsService::loadSettings()` merged output: + `contract.configuration.linkedTypes` contains `decidesk-decisions` AND + `calendar` exactly once each (guards the array-merge semantic either way); + all seven schemas carry their declared leaf. +- [ ] 1.4 Re-import on the dev instance (fragment signature change triggers + re-import) and confirm `Repair/LogDanglingLinkedTypes` reports zero + dangling entries. + +## 2. Lifecycle-date calendar sync + +- [ ] 2.1 Add `lib/Service/LifecycleCalendarService.php` with + `syncContract(array $object): void` and + `syncModuleVersion(array $object): void` — upsert one linked all-day + event per tracked field (`contract.endDate`, + `moduleVersion.dateEndSupport`) through OpenRegister's calendar link + path (the `X-OPENREGISTER-*` event surface `CalendarProvider::list()` + renders); move on change, delete on clear, never duplicate (one tracked + field = one event, deterministic marker). +- [ ] 2.2 Add `lib/Listener/LifecycleCalendarListener.php` subscribed to + OpenRegister's object-saved event; filter to the voorzieningen register + and the `contract`/`moduleVersion` schemas resolved via + `SettingsService` (no hard-coded register/schema ids); register the + listener in `lib/AppInfo/Application.php` alongside the existing OR + event listeners. +- [ ] 2.3 Fail-soft: wrap calendar interaction so an unavailable Calendar + app (or no writable calendar) logs a warning and the object save still + succeeds — mirror the graceful-degradation posture of the other + integrations. +- [ ] 2.4 Event titles in English per fleet convention: + `Contract ends: {contractNumber}`, + `End of support: {module name} {version}`. + +## 3. Frontend / manifest + +- [ ] 3.1 Verify the leaf tabs render from schema `linkedTypes` on the + affected detail pages (`ContactpersoonDetail`, `OrganisatieDetail`, + `ContractDetail`, `ModuleversieDetail`, `ReviewDetail`, `ModuleDetail`, + service detail) — expected zero per-page wiring via the shared object + sidebar; if a page suppresses sidebar tabs, wire it there. +- [ ] 3.2 Rewrite the `_note` strings on `ContractDetail` and + `ContactpersoonDetail` in `src/manifest.json` that currently assert + "declares NO email/calendar linkedType" — keep the comms hard-rule (no + email widgets) documented, describe the new calendar/contacts leaves. + +## 4. Tests + +- [ ] 4.1 PHPUnit `LifecycleCalendarServiceTest`: create-on-set, + move-on-change (including a simulated EOL re-stamp of + `dateEndSupport`), delete-on-clear, idempotent double-save, and + fail-soft when the calendar double throws. +- [ ] 4.2 PHPUnit `LifecycleCalendarListenerTest`: fires only for + `contract`/`moduleVersion` saves in the voorzieningen register; ignores + other schemas. +- [ ] 4.3 Playwright: contacts tab on a seeded contactPerson + (display name, not raw UID); calendar tab on a contract after setting + `endDate`; deck card create-and-link on a pending review; bookmark + link + render on a module detail page — per the @e2e-tagged scenarios + (gate-19 traceability). + +## 5. Spec + docs + +- [ ] 5.1 Sync this change's spec delta into + `openspec/specs/catalog-integration-leaves/spec.md` on archive. +- [ ] 5.2 CHANGELOG entry under Unreleased: contacts/calendar/deck/bookmarks + leaf adoption + lifecycle-date calendar sync. diff --git a/openspec/changes/mcp-full-action-surface/.openspec.yaml b/openspec/changes/mcp-full-action-surface/.openspec.yaml new file mode 100644 index 00000000..95672402 --- /dev/null +++ b/openspec/changes/mcp-full-action-surface/.openspec.yaml @@ -0,0 +1,2 @@ +schema: spec-driven +created: 2026-08-18 diff --git a/openspec/changes/mcp-full-action-surface/design.md b/openspec/changes/mcp-full-action-surface/design.md new file mode 100644 index 00000000..1b8d3cd3 --- /dev/null +++ b/openspec/changes/mcp-full-action-surface/design.md @@ -0,0 +1,212 @@ +# Design — mcp-full-action-surface + +## 1. Positioning against `softwarecatalog-mcp-adoption` + +That change (active, unimplemented, `.openspec.yaml: schema: conduction`, +created 2026-07-13) is the read-only declarative half of this surface. Its +reasoning is kept; its artefacts cannot be applied as written: + +| Its assumption | State at HEAD | Consequence | +|---|---|---| +| Slugs `moduleVersie`, `dienst`, `organisatie`, `contactpersoon`, `koppeling`, `gebruik` | Renamed to `moduleVersion`, `service`, `organization`, `contactPerson`, `connection`, `usage` | Its fragment would deep-merge 8 orphan schemas into the register (ADR-037 creates keys it cannot match) — worse than failing loudly | +| Dutch filter names (`naam`, `aanbieder`, `licentietype`, `standaardGemma`, `afnemer`…) | Properties are English (`name`, `provider`, `licentietype` DOES survive on `module`, but e.g. `naam` → `name`, `standaardGemma` → `standardGemma`, `afnemer` → `consumer`) | `McpAnnotationValidator` would reject — every filter list must be re-derived from the HEAD `properties` maps | +| `kwetsbaarheid`/`beoordeeling` excluded as "dead schemas" | `vulnerability` and `assessment` are live: manifest pages `Kwetsbaarheden`/`KwetsbaarheidDetail`, `Reviews`/`ReviewDetail`; `ReviewService`/`ModerationService`; `register.d/catalog-ratings.json` moderation fields | Both belong in the read surface; `vulnerability` is also the safe derived-write candidate | +| Write tools deferred (`DEFERRED_QUESTIONS`) | The concrete need now exists (hermiq grant model + chat commanding) | This change is the deferred `kind: code` follow-up it named | + +**Disposition:** this change supersedes it. Archive +`softwarecatalog-mcp-adoption` as superseded-by `mcp-full-action-surface` +when this lands; do not apply its fragment first. + +## 2. Architecture + +``` +hermiq agent (default-deny grants, scope × reach, approval gate, audit) + -> OpenRegister /api/mcp (JSON-RPC) / chat facade + -> SchemaDerivedToolProvider <- register.d/mcp-full-action-surface.json (layer A+B) + -> IMcpToolProvider::softwarecatalog <- lib/Mcp/SoftwareCatalogToolProvider.php (layer C) + dispatcher only; per tool: + McpArgumentValidator -> per-object gate -> existing workflow service +``` + +Fleet reference: `decidesk/lib/Mcp/` — `DecideskToolProvider` (dispatcher +with a `TOOL_DESCRIPTORS` constant so unit tests assert the catalogue as a +fixture), `McpArgumentValidator`, `McpMeetingGate` (the single +"load object, prove the caller may touch it" ladder: argument validation → +load → not_found → authorise, auth helpers that return real booleans and +are never wrapped in `catch(\Throwable)`), `McpMeetingScopeResolver`. +Software Catalog ports the shape: `McpContractGate` (wraps the existing +`ContractApprovalService::authorizeSubmit(contractUuid, groupNames, +activeOrgUuid)` — the IDOR guard from `contract-approval-ownership-guard`), +`McpPublicationGate` (wraps `PublicationController::authorizeEntry()` +semantics via `PublicationService::resolveEntry()` + the admin / +`aanbod-beheerder` organisation match), and admin checks via +`IGroupManager::isAdmin()`. **Rule: the MCP layer adds no new authority — +every tool runs exactly the guard its REST twin runs.** + +DI alias, mirroring `decidesk/lib/AppInfo/Registrar/DomainServiceRegistrar.php:121`: + +```php +$context->registerServiceAlias( + 'OCA\\OpenRegister\\Mcp\\IMcpToolProvider::softwarecatalog', + SoftwareCatalogToolProvider::class +); +``` + +## 3. Layer A — derived read tools (14 schemas × search/get = 28 tools) + +`register.d/mcp-full-action-surface.json`, `configuration.x-openregister-mcp`, +`enabled: true`, verbs `search` + `get`, `scope: "read"`, +`readOnlyHint: true`, `destructiveHint: false`, `idempotentHint: true`, +implicit `reach: user` (hermiq infers `user` for 3-segment `{app}.{schema}.{search|get}` +ids; we declare it anyway — see §5). Filters below are cross-checked +against the HEAD `properties` maps (every name verified present): + +| Schema | search filters (all real properties) | +|---|---| +| `module` | `name`, `type`, `provider`, `licentietype`, `hostingJurisdiction` | +| `moduleVersion` | `module`, `status`, `dateEndSupport` | +| `service` | `name`, `provider` | +| `organization` | `name`, `type`, `status`, `registrationStatus` | +| `contactPerson` | `organization`, `role` | +| `connection` | `type`, `status`, `integrationType`, `provider` | +| `compliancy` | `module`, `standardGemma` | +| `usage` | `consumer`, `provider`, `status`, `module`, `timeClassification` | +| `contract` | `status`, `contractType`, `service`, `usage`, `endDate` | +| `suite` | `name` | +| `vulnerability` | `name`, `cveCode`, `cvssScore`, `modules` | +| `assessment` | `status`, `rating`, `modules`, `usage` | +| `bioMeasure` | `code`, `name`, `bbnLevel` | +| `sbomComponent` | `name`, `moduleVersion`, `purl`, `vexCveIds` | + +Excluded from derivation, reasoning inherited from the superseded change: +`sector` (2-field taxonomy), `element`/`view`/`model`/ +`property-definition`/`relation` (AMEF bulk-import artifacts; `element` +alone has 80+ properties). Note `view` data IS reachable through curated +`listViews`/`getView` provider tools (layer C), which return the enriched +projection the `ViewController` API serves rather than raw AMEF XML. + +## 4. Layer B — derived writes: `vulnerability` only + +`vulnerability.create` (`scope: create`) and `vulnerability.update` +(`scope: update`), both `reach: instance`, `destructiveHint: false`. +Justification: it is the only live schema with (a) no +`x-openregister-lifecycle` state machine, (b) no decidesk projection +fields, (c) no dedicated workflow service — the app's own UI authors it +via generic OR object CRUD, so a derived MCP write matches the app's +existing authority model exactly (OR RBAC at invoke time). `delete` is +withheld (destructive; no current UI story). Every OTHER schema keeps the +superseded change's "no raw writes" rule: `contract.status = Actief` is a +decidesk projection ("softwarecatalog NEVER sets `status = Actief` on its +own authority" — `register.d/contracts-to-decidesk.json`), and +`moduleVersion`/`connection`/`organization`/`usage`/`contract` carry +lifecycle state machines a raw `update` would bypass. + +## 5. Layer C — curated provider tools (grant-matrix table) + +Reach follows `hermiq/openspec/specs/agent-capability-reach/spec.md`: +`self` < `user` < `instance` < `external`; reach = widest principal set an +invocation can AFFECT or DISCLOSE TO; a read that leaves the instance is +`external`; undeclared reach fail-closes to `external`, so every +descriptor declares one explicitly. + +### Read tools (scope: read) + +| Tool id | Delegates to | Reach | Notes | +|---|---|---|---| +| `softwarecatalog.getMyContactProfile` | `ContactpersonenController::getMe` path (`/api/me` resolution) | user | The caller's own contactPerson + organisation context | +| `softwarecatalog.listOffers` | `AanbodService::getAanbod()` | user | Offers pending for the caller's active organisation | +| `softwarecatalog.listOfferedUsages` | `AangebodenGebruikService::getGebruiksWhereAfnemer()` / `getGebruiksWhereDeelnemers()` | user | Usage records offered to / shared with the caller's organisation | +| `softwarecatalog.getPortfolioReport` | `PortfolioReportService::buildReport(organisationUuid)` | user | Caller's organisation only; gate rejects foreign org uuids for non-admins | +| `softwarecatalog.listPendingModerations` | `ModerationService::listPending()` | user | Admin-gated (same as `moderation#pending`) | +| `softwarecatalog.getReviewAggregate` | `ReviewAggregateService` (`review#aggregate`) | user | Public aggregate numbers | +| `softwarecatalog.getContractApprovalConfig` | `ContractApprovalService::isDelegationConfigured()` (`contractApproval#config`) | user | Lets an agent know whether submit tools can work | +| `softwarecatalog.getSbomImportStatus` | `SbomImportService::getStatus(moduleVersionUuid)` | user | Behind `SbomImportService::userCanReadModule()` | +| `softwarecatalog.listViews` / `softwarecatalog.getView` | `ViewService` (`view#getAllViews` / `#getView`) | user | Enriched ArchiMate view projection, incl. enrichment params | +| `softwarecatalog.previewOrganisationMerge` | `MergeOrganisatieService::dryRun(source, target)` | user | Admin-gated; read-only preview of `mergeOrganisations` | +| `softwarecatalog.getEolSyncStatus` | `EolSyncService::getStatus()` | user | Read of last-run metadata only | + +### Write tools (scope as listed; hermiq default-deny, human approval gate) + +| Tool id | Delegates to | Scope | Reach | Why that reach | +|---|---|---|---|---| +| `softwarecatalog.submitContractApproval` | `ContractApprovalService::submitForApproval(uuid, false)` behind `authorizeSubmit()` | update | instance | Raises a decidesk Decision other users see; flips `approvalState` | +| `softwarecatalog.submitContractRenewal` | `submitForApproval(uuid, true)` | update | instance | Same seam, renewal flavour | +| `softwarecatalog.publishObject` | `PublicationService::publish(objectType, uuid)` | update | external | Sets `publicationDate` → anonymous open-data readers see the record; effect leaves the authenticated instance surface | +| `softwarecatalog.depublishObject` | `PublicationService::depublish(objectType, uuid)` | update | external | Withdraws from the public surface — same boundary | +| `softwarecatalog.approveRegistration` | `ModerationService::approve(uuid, type)` | update | instance | Admits an organisation/review; visible to all users | +| `softwarecatalog.rejectRegistration` | `ModerationService::reject(uuid, type)` | update | instance | | +| `softwarecatalog.submitReview` | `ReviewService::submit(payload, subjectType, subjectId)` | create | instance | Forced to `status: pending` server-side; moderators observe it | +| `softwarecatalog.acceptOffer` | `AanbodService::acceptAanbod(aanbodId)` | update | instance | | +| `softwarecatalog.declineOffer` | `AanbodService::denyAanbod(aanbodId)` | delete | instance | REST twin is a DELETE verb | +| `softwarecatalog.claimUsage` | `AangebodenGebruikService::setGebruikSelfToActiveOrg(gebruikId)` | update | instance | | +| `softwarecatalog.declineUsage` | `AangebodenGebruikService::deleteGebruikAsAfnemer(gebruikId)` | delete | instance | | +| `softwarecatalog.grantOrganisationMembership` | `OrganisationMembersController::grant(uuid, userId)` logic (extract to service if needed) | update | instance | Changes another user's permission set | +| `softwarecatalog.revokeOrganisationMembership` | `::revoke(uuid, userId)` logic | update | instance | | +| `softwarecatalog.mergeOrganisations` | `MergeOrganisatieService::execute(source, target, actorUid)` | update | instance | Admin-gated; tombstones the source (`mergedInto`) | +| `softwarecatalog.registerOrganisation` | `IntakeService::submit(payload)` (+ `validate()`) | create | instance | Creates a `pending` registration for moderators | +| `softwarecatalog.importSbom` | `SbomImportService::importForModuleVersie(...)` | create | instance | Content passed inline (SBOM JSON/XML string), not a file upload | +| `softwarecatalog.triggerEolSync` | `EolSyncService::run()` | update | external | Outbound HTTP to endoflife.date — per hermiq's rule, anything issuing external requests is `external` regardless of verb | + +Descriptor hints: every write tool sets `readOnlyHint: false`; +`destructiveHint: true` only on `declineOffer`, `declineUsage`, +`revokeOrganisationMembership`, and `mergeOrganisations` (tombstoning); +`idempotentHint` per delegate semantics (e.g. `publish` idempotent, +`submitReview` not). + +### Named exclusions (auditable "full coverage" boundary) + +| Surface | Why not a tool | +|---|---| +| `settings#*` config get/set (~50 endpoints: general/sync/AMEF/voorzieningen/email/cronjob/user-group config, auto-configure, force-update, clear-cache, debug, heartbeat) | App configuration, not catalogue operation. An agent misconfiguring register bindings can brick the app for everyone; nothing in the PO intent ("command the app from chat") needs it. Deferred, not denied forever. | +| `contactpersonen#convertToUser`, `changePassword`, `disableUser`, `enableUser`, `updateUserGroups` | Identity/credential administration. Password and account-state changes are outside any sane agent grant in v1. | +| `settings#importArchiMate` / `exportArchiMate` / `downloadArchiMate` + progress streaming | File-upload/-download shaped with an async progress protocol; MCP tool-call ergonomics don't fit yet. `importSbom` is included instead because its payload is inline text. | +| `federation#addPeer/removePeer/pull` | Instance-topology administration touching remote instances; needs its own security review before any agent reach. | +| `dashboard#*`, `preferences#*`, `facet#getFacets`, `settings#getObjectsCounts/Statistics` | UI plumbing; derived `search` covers the data need. | + +## 6. Chat scenarios the surface must support (grounded end-to-end) + +1. **"Which contracts expire this quarter?"** → + `softwarecatalog.contract.search` with a `endDate` range filter + (real property: `contract.endDate`, "De einddatum van het contract"); + scope read / reach user — grantable without approval friction. +2. **"Log a vulnerability against application X."** → + `softwarecatalog.module.search {name: X}` then + `softwarecatalog.vulnerability.create {name, cveCode, cvssScore, + modules: [moduleId]}` (all real `vulnerability` properties); write → + default-deny, first use prompts a grant, invocation passes the human + approval gate and lands in the audit trail. +3. **"Submit contract 2025-0042 for renewal approval."** → + `contract.search {contractNumber}` then + `softwarecatalog.submitContractRenewal {contractUuid}`; the gate runs + `ContractApprovalService::authorizeSubmit()` — a caller whose active + organisation doesn't own the contract gets the same 403-equivalent + `forbidden` error the REST path returns, agent or not. + +## 7. Risks / trade-offs + +- [Risk] The superseded change is applied first with Dutch slugs → + orphan schemas polluting the register. Mitigation: proposal recommends + archiving it as superseded; task 1.1 asserts the fragment only names + slugs present in the HEAD monolith (fails the build otherwise). +- [Risk] MCP write tool drifts from its REST twin's guard (an MCP-only + IDOR). Mitigation: the delegation rule is a spec requirement with + per-gate unit tests mirroring `contract-approval-ownership-guard`'s + 403 cases; the gates REUSE the service-level guards rather than + reimplementing them. +- [Trade-off] `OrganisationMembersController::grant/revoke` logic lives + in the controller today; the provider either extracts it into a small + service (preferred, one-time refactor) or is deferred for those two + tools — decided at apply time, recorded in tasks 5.4. +- [Trade-off] No `delete` tools for catalogue records at all (beyond the + decline/revoke workflow verbs). Deliberate: destructive deletes have no + workflow service and no agent story; bias to fewer. + +## 8. Deferred + +- Curated tools over the excluded admin surfaces (config, ArchiMate, + federation) once hermiq has an "operator agent" grant tier. +- `x-openregister-mcp` on a trimmed AMEF projection schema (inherited + deferral). +- `assessment` derived writes — review submission must stay behind + `softwarecatalog.submitReview` so the server-side `pending` forcing and + `auteur` stamping are never bypassed. diff --git a/openspec/changes/mcp-full-action-surface/proposal.md b/openspec/changes/mcp-full-action-surface/proposal.md new file mode 100644 index 00000000..0abbeca4 --- /dev/null +++ b/openspec/changes/mcp-full-action-surface/proposal.md @@ -0,0 +1,120 @@ +--- +kind: code +depends_on: [] +--- + +# softwarecatalog — full MCP action surface for hermiq (chat-drivable catalogue) + +## Why + +**Product intent:** every Conduction app should expose MCP tooling for ALL +of its user actions, so any action can in principle be automated by an AI +agent — with the user granting rights per agent, granularly, on hermiq's +two-axis grant model (`scope` × `reach`, default-deny for writes, human +approval gates, audit trail — `hermiq/openspec/specs/agent-tool-governance/` +and `agent-capability-reach/spec.md`). Even without automation, a user +should be able to command the app from chat: "which contracts expire this +quarter?" answered, "submit contract 2025-0042 for renewal" queued behind +an approval gate. + +**Current state (verified at HEAD):** Software Catalog has zero MCP +surface. `grep -rn "IMcpToolProvider\|McpTool\|x-openregister-mcp" lib/ +src/ appinfo/` returns nothing outside openspec prose. The mechanism is +proven elsewhere: decidesk ships the fleet reference implementation +(`decidesk/lib/Mcp/DecideskToolProvider.php` — dispatcher + +`TOOL_DESCRIPTORS` catalogue, `McpArgumentValidator`, `McpMeetingGate` +per-object authorisation, `McpMeetingScopeResolver`), registered via the +DI alias `OCA\OpenRegister\Mcp\IMcpToolProvider::decidesk` +(`decidesk/lib/AppInfo/Registrar/DomainServiceRegistrar.php:121`), and +OpenRegister derives CRUD tools from `x-openregister-mcp` schema blocks +(`openregister/lib/Mcp/`). + +**Relationship to `softwarecatalog-mcp-adoption` (active change, +2026-07-13, `schema: conduction`):** that change specifies the read-only +half — derived `search`/`get` tools on 9 curated schemas via a +`register.d` fragment — and explicitly defers every write/action tool +("A future `kind: code` change could promote … a `#[McpTool]` once +there's a concrete agent workflow need", its `DEFERRED_QUESTIONS`). This +change is that deferred follow-up, and it also has to correct the ground +under it: **the register was since migrated to English slugs** and +`softwarecatalog-mcp-adoption`'s fragment is written against schema names +that no longer exist. Verified against +`lib/Settings/softwarecatalogus_register.json` at HEAD: the register +contains `module`, `moduleVersion`, `service`, `organization`, +`contactPerson`, `connection`, `compliancy`, `usage`, `contract`, +`suite`, `sector`, `vulnerability`, `assessment`, `bioMeasure`, +`sbomComponent` + the 5 AMEF schemas — there is no `moduleVersie`, +`dienst`, `organisatie`, `contactpersoon`, `koppeling`, `gebruik`, +`kwetsbaarheid`, or `beoordeeling`. Applying that change's JSON as-is +would deep-merge eight ORPHAN schemas into the register (ADR-037 creates +what it cannot match) instead of annotating the real ones. Two of its +exclusions are also stale: `vulnerability` and `assessment` are live +surfaces now (`Kwetsbaarheden`/`KwetsbaarheidDetail` and +`Reviews`/`ReviewDetail` manifest pages; `ReviewService`, +`ModerationService`, the `catalog-ratings` fragment), despite the +monolith's leftover "niet daadwerkelijk gebruikt" description. + +**This change therefore supersedes `softwarecatalog-mcp-adoption`**: it +retains that change's curation reasoning (read-only derived tools, honest +hints, filters cross-checked against real properties, AMEF exclusion, no +raw writes on lifecycle-governed schemas) and re-grounds it on the English +slugs, then adds the full action layer on top. Recommend archiving +`softwarecatalog-mcp-adoption` as superseded when this change lands. + +## What Changes + +1. **Derived read layer (config)** — new + `lib/Settings/register.d/mcp-full-action-surface.json` fragment + declaring `configuration.x-openregister-mcp` (`search` + `get`, + `scope: read`, `readOnlyHint: true`) on 14 schemas: the 9 from the + superseded change under their current slugs (`module`, `moduleVersion`, + `service`, `organization`, `contactPerson`, `connection`, `compliancy`, + `usage`, `contract`) plus `suite`, `vulnerability`, `assessment`, + `bioMeasure`, `sbomComponent` (all now live surfaces). AMEF schemas + (`element`, `view`, `model`, `property-definition`, `relation`) and + `sector` stay excluded — reasoning inherited, see design.md. +2. **Derived write verbs on `vulnerability` only** — `create`/`update` + (`scope` accordingly, `reach: instance`): the one live schema with no + lifecycle state machine, no projection fields, and no workflow + service; the app's own UI writes it through generic OR object CRUD. + Every other schema's writes stay workflow-only (below). +3. **Hand-written provider (code)** — + `lib/Mcp/SoftwareCatalogToolProvider.php` + (`OCA\SoftwareCatalog\Mcp`, implements + `OCA\OpenRegister\Mcp\IMcpToolProvider`), registered under the DI + alias `OCA\OpenRegister\Mcp\IMcpToolProvider::softwarecatalog`, tool + ids `softwarecatalog.{toolName}`. Dispatcher-only, decidesk-style: + argument validation (`McpArgumentValidator` port) → per-object + authorisation gate → delegation to the EXISTING workflow service. + 12 curated read tools and 17 write tools covering every real + user-facing workflow action found in `lib/Controller/` + + `lib/Service/` — contract approval/renewal + (`ContractApprovalService::submitForApproval()` behind + `authorizeSubmit()`), publish/depublish (`PublicationService`), + moderation (`ModerationService::listPending/approve/reject`), reviews + (`ReviewService::submit`), offers (`AanbodService::getAanbod/ + acceptAanbod/denyAanbod`), offered-usage claim/decline + (`AangebodenGebruikService`), organisation membership + (`OrganisationMembersController` logic), organisation merge + (`MergeOrganisatieService::dryRun/execute`), intake + (`IntakeService::submit`), SBOM import (`SbomImportService`), EOL sync + (`EolSyncService::run`), portfolio report + (`PortfolioReportService::buildReport`). Full catalogue table with + per-tool `scope` and `reach` in design.md. +4. **Grant-matrix annotations** — every descriptor declares `scope` + (read/create/update/delete) AND `reach` (self/user/instance/external) + from hermiq's closed vocabularies, because hermiq fail-closes an + undeclared reach to `external` (its most-restricted class) and we want + reads grantable at `user` reach. Publication tools are honestly + `reach: external` (they alter the anonymous open-data surface), as is + the EOL sync trigger (outbound HTTP to endoflife.date). +5. **Named exclusions, not silent ones** — admin configuration plumbing + (the ~50 `settings#*` config get/set endpoints, email templates, + cronjob config, user-group config), identity/credential operations + (`contactpersonen#convertToUser/changePassword/disable/enable`), + ArchiMate import/export (file-transfer shaped), and federation peer + management are deliberately NOT tools in this change — each with its + rationale recorded in design.md so the coverage claim is auditable. + +Not BREAKING: purely additive — no existing route, controller, or schema +property changes; REST surface untouched. diff --git a/openspec/changes/mcp-full-action-surface/specs/mcp-tool-surface/spec.md b/openspec/changes/mcp-full-action-surface/specs/mcp-tool-surface/spec.md new file mode 100644 index 00000000..d27bcf8b --- /dev/null +++ b/openspec/changes/mcp-full-action-surface/specs/mcp-tool-surface/spec.md @@ -0,0 +1,191 @@ +## ADDED Requirements + +### Requirement: Software Catalog MUST register a hand-written MCP tool provider +The app SHALL ship `OCA\SoftwareCatalog\Mcp\SoftwareCatalogToolProvider` +implementing `OCA\OpenRegister\Mcp\IMcpToolProvider`, registered under the +DI alias `OCA\OpenRegister\Mcp\IMcpToolProvider::softwarecatalog` +(mirroring decidesk's registrar at +`decidesk/lib/AppInfo/Registrar/DomainServiceRegistrar.php:121`). The +provider MUST be a dispatcher only: it owns the tool catalogue (a constant +descriptor table unit tests can assert as a fixture, per +`DecideskToolProvider::TOOL_DESCRIPTORS`) and routes tool ids to handler +classes; it MUST NOT contain business logic. Every tool id MUST be +namespaced `softwarecatalog.{toolName}`. + +#### Scenario: The provider is discoverable through OpenRegister +- GIVEN this change applied and the app enabled +- WHEN OpenRegister resolves registered `IMcpToolProvider` aliases +- THEN `IMcpToolProvider::softwarecatalog` MUST resolve to + `SoftwareCatalogToolProvider` +- AND its listed tools MUST all carry ids starting with `softwarecatalog.` +- @e2e exclude DI-resolution assertion; asserted by PHPUnit bootstrapping + the container + +### Requirement: Every tool descriptor MUST declare scope and reach from hermiq's closed vocabularies +Every descriptor — derived and curated — SHALL declare `scope` (one of +`read`, `create`, `update`, `delete`) and `reach` (one of `self`, `user`, +`instance`, `external`, per +`hermiq/openspec/specs/agent-capability-reach/spec.md`), plus honest +`readOnlyHint`/`destructiveHint`/`idempotentHint` values. Reach MUST be +declared explicitly (hermiq fail-closes an undeclared reach to +`external`). A tool whose invocation issues an outbound HTTP request +(`softwarecatalog.triggerEolSync` → endoflife.date) or alters the +anonymous open-data surface (`publishObject`/`depublishObject`) MUST +declare `reach: external` regardless of its verb. + +#### Scenario: No descriptor ships without both axes +- GIVEN the provider's descriptor table and the derived-tool fragment +- WHEN every entry is inspected +- THEN each MUST carry a `scope` and a `reach` from the closed vocabularies +- AND no read tool MUST carry `readOnlyHint: false` +- @e2e exclude Descriptor-shape fixture assertion; PHPUnit over the + descriptor constant + +#### Scenario: Publication tools are classified as external reach +- GIVEN the descriptors for `softwarecatalog.publishObject` and + `softwarecatalog.depublishObject` +- WHEN their `reach` is read +- THEN it MUST be `external` +- AND their `scope` MUST be `update` +- @e2e exclude Fixture assertion; PHPUnit + +### Requirement: Read tools MUST be side-effect free and separated from write tools +Curated read tools (`getMyContactProfile`, `listOffers`, +`listOfferedUsages`, `getPortfolioReport`, `listPendingModerations`, +`getReviewAggregate`, `getContractApprovalConfig`, `getSbomImportStatus`, +`listViews`, `getView`, `previewOrganisationMerge`, `getEolSyncStatus`) +SHALL delegate only to read paths of the existing services and MUST NOT +persist anything. `previewOrganisationMerge` MUST delegate to +`MergeOrganisatieService::dryRun()` and MUST NOT be able to reach +`execute()`. + +#### Scenario: Merge preview never mutates +- GIVEN two organisation uuids +- WHEN `softwarecatalog.previewOrganisationMerge` is invoked +- THEN the response MUST contain the dry-run impact summary +- AND no object write MUST occur (asserted via a mocked + `MergeOrganisatieService` expecting `dryRun()` once and `execute()` never) +- @e2e exclude MCP JSON-RPC path; PHPUnit on the handler + +### Requirement: Every write tool MUST delegate to the existing workflow service behind its existing guard +Each write tool SHALL delegate to the named workflow method — +`ContractApprovalService::submitForApproval()`, +`PublicationService::publish()/depublish()`, +`ModerationService::approve()/reject()`, `ReviewService::submit()`, +`AanbodService::acceptAanbod()/denyAanbod()`, +`AangebodenGebruikService::setGebruikSelfToActiveOrg()/deleteGebruikAsAfnemer()`, +organisation-membership grant/revoke, `MergeOrganisatieService::execute()`, +`IntakeService::submit()`, `SbomImportService::importForModuleVersie()`, +`EolSyncService::run()` — and MUST run per-object authorisation before the +delegate, structured as the decidesk ladder (argument validation → load → +not_found → authorise → delegate, per `decidesk/lib/Mcp/McpMeetingGate.php`). +The MCP layer MUST NOT grant authority the REST twin denies: in particular +`submitContractApproval`/`submitContractRenewal` MUST pass +`ContractApprovalService::authorizeSubmit()` and fail closed exactly like +the REST 403 path. Raw object writes on lifecycle-governed schemas +(`contract`, `usage`, `organization`, `moduleVersion`, `connection`) MUST +NOT be exposed as MCP tools. + +#### Scenario: A non-owning caller cannot submit a contract via MCP +- GIVEN a contract owned by organisation A +- AND an authenticated caller whose active organisation is B and who is + not an instance admin +- WHEN `softwarecatalog.submitContractApproval` is invoked for that contract +- THEN the tool MUST return a forbidden error +- AND `ContractApprovalService::submitForApproval()` MUST NOT be invoked +- AND no `DecisionRequestedEvent` MUST be dispatched +- @e2e exclude Mirrors the REST 403 cases of + `contract-approval-ownership-guard`; PHPUnit with mocked dispatcher + +#### Scenario: Review submission cannot bypass moderation +- GIVEN any caller +- WHEN `softwarecatalog.submitReview` is invoked with a payload declaring + `status: approved` +- THEN the persisted assessment MUST have `status: pending` (forced + server-side by `ReviewService::submit()`) +- AND the response MUST reflect the pending state +- @e2e exclude Server-side forcing assertion; PHPUnit on the handler + + service + +#### Scenario: Argument validation precedes authorisation and business logic +- GIVEN an invocation of any curated tool with a missing required argument +- WHEN the provider dispatches it +- THEN the tool MUST return a validation error naming the argument +- AND no service method MUST have been called +- @e2e exclude Validator-ladder assertion; PHPUnit + +### Requirement: Derived read tools MUST cover the 14 live catalogue schemas under their current English slugs +`lib/Settings/register.d/mcp-full-action-surface.json` SHALL declare +`configuration.x-openregister-mcp` with `search` + `get` (`scope: read`, +`readOnlyHint: true`) on exactly: `module`, `moduleVersion`, `service`, +`organization`, `contactPerson`, `connection`, `compliancy`, `usage`, +`contract`, `suite`, `vulnerability`, `assessment`, `bioMeasure`, +`sbomComponent`. Every schema name and every `search.filters` entry MUST +exist in the HEAD `softwarecatalogus_register.json` (`McpAnnotationValidator` +must report zero unknown-filter errors, and the fragment MUST NOT +introduce any schema key absent from the monolith). The AMEF schemas +(`element`, `view`, `model`, `property-definition`, `relation`) and +`sector` MUST NOT be annotated. `lib/Settings/softwarecatalogus_register.json` +MUST NOT be modified. + +#### Scenario: Contracts are searchable by end date from chat +- GIVEN the fragment imported and contracts with `endDate` values in Q4 +- WHEN an agent invokes `softwarecatalog.contract.search` with an + `endDate` range filter for the quarter +- THEN the result MUST contain exactly the contracts whose `endDate` + falls in the range the caller may read under OR RBAC +- @e2e exclude MCP JSON-RPC query; covered by OpenRegister's derived-tool + suite plus an app-side import assertion + +#### Scenario: No orphan schema is merged into the register +- GIVEN the fragment applied +- WHEN the merged register is diffed against the monolith's schema key set +- THEN the set of schema keys MUST be unchanged (annotations only, no new + schemas — in particular none of the retired Dutch slugs `moduleVersie`, + `dienst`, `organisatie`, `contactpersoon`, `koppeling`, `gebruik`, + `kwetsbaarheid`, `beoordeeling`) +- @e2e exclude Config-merge assertion; PHPUnit on + `SettingsService::loadSettings()` + +### Requirement: Derived write verbs MUST exist on vulnerability and nowhere else +The fragment SHALL additionally declare `create` and `update` (with +matching `scope`, `reach: instance`, `readOnlyHint: false`) on the +`vulnerability` schema only — the one live schema with no lifecycle state +machine, no projection fields, and no workflow service. No other schema in +the fragment MUST carry a `create`, `update`, or `delete` verb, and +`vulnerability` MUST NOT carry `delete`. + +#### Scenario: An agent logs a vulnerability against an application +- GIVEN an agent granted `softwarecatalog.vulnerability.create` (a write — + hermiq default-denies it until granted, and the invocation passes the + human approval gate) +- WHEN it invokes the tool with `name`, `cveCode`, `cvssScore`, and + `modules` referencing an existing module id +- THEN a `vulnerability` object MUST be created with those values under + the caller's OR RBAC authority +- AND the invocation MUST appear in hermiq's audit trail +- @e2e exclude Cross-app hermiq grant flow; covered by hermiq's + agent-tool-governance suite; app-side PHPUnit asserts the fragment shape + +#### Scenario: Writes on lifecycle-governed schemas stay impossible +- GIVEN the imported merged register +- WHEN the derived tool list for `softwarecatalog` is enumerated +- THEN no `contract.*`, `usage.*`, `organization.*`, `moduleVersion.*`, + or `connection.*` tool with scope `create`, `update`, or `delete` MUST + exist +- @e2e exclude Tool-listing assertion; import check in CI + +### Requirement: This change supersedes softwarecatalog-mcp-adoption +The change SHALL be applied instead of, never after or alongside, the +`softwarecatalog-mcp-adoption` fragment: that change's +`register.d/softwarecatalog-mcp-adoption.json` (Dutch slugs) MUST NOT be +created, and on landing this change the `softwarecatalog-mcp-adoption` +change MUST be archived as superseded with a pointer to +`mcp-full-action-surface`. + +#### Scenario: The stale fragment never lands +- GIVEN this change applied +- WHEN `lib/Settings/register.d/` is listed +- THEN it MUST contain `mcp-full-action-surface.json` +- AND it MUST NOT contain `softwarecatalog-mcp-adoption.json` +- @e2e exclude File-presence assertion; checked in review/CI diff --git a/openspec/changes/mcp-full-action-surface/tasks.md b/openspec/changes/mcp-full-action-surface/tasks.md new file mode 100644 index 00000000..d0853ea6 --- /dev/null +++ b/openspec/changes/mcp-full-action-surface/tasks.md @@ -0,0 +1,113 @@ +# Tasks — mcp-full-action-surface + +## 1. Derived layer (register fragment) + +- [ ] 1.1 Add `lib/Settings/register.d/mcp-full-action-surface.json`: + `configuration.x-openregister-mcp` with `search`/`get` (`scope: read`, + honest hints, explicit `reach: user`) on the 14 schemas in design.md §3, + plus `create`/`update` (`reach: instance`) on `vulnerability` only. + Assert (script or PHPUnit) that every schema key in the fragment exists + in the HEAD monolith — none of the retired Dutch slugs — and validate + with `python3 -m json.tool`. +- [ ] 1.2 Re-derive every `search.filters` list from the HEAD `properties` + maps (design.md table — every name verified against HEAD at + proposal time; re-verify at apply time); the merged register must pass + `McpAnnotationValidator` with zero unknown-filter errors. +- [ ] 1.3 Agent-facing English `description` prose per verb per schema + (what the LLM reads to choose the tool), reusing the superseded + change's descriptions where the schema survived the rename. +- [ ] 1.4 Import on the dev instance and verify the derived tool listing: + 28 read tools + `vulnerability.create`/`.update`, and no write tool on + any lifecycle-governed schema. + +## 2. Provider skeleton + +- [ ] 2.1 Add `lib/Mcp/SoftwareCatalogToolProvider.php` + (`OCA\SoftwareCatalog\Mcp`, implements + `OCA\OpenRegister\Mcp\IMcpToolProvider`): descriptor constant + (id, name, description, inputSchema, scope, reach, hints) + dispatch + table; no business logic (decidesk `DecideskToolProvider` shape). +- [ ] 2.2 Register the DI alias + `OCA\OpenRegister\Mcp\IMcpToolProvider::softwarecatalog` in + `lib/AppInfo/Application.php` (mirror + `decidesk/lib/AppInfo/Registrar/DomainServiceRegistrar.php:121`). +- [ ] 2.3 Add `lib/Mcp/McpArgumentValidator.php` (port of decidesk's: + typed required/optional argument checking, validation error before any + service call). + +## 3. Authorisation gates + +- [ ] 3.1 Add `lib/Mcp/McpContractGate.php`: load contract → + `not_found` → `ContractApprovalService::authorizeSubmit(contractUuid, + groupNames, activeOrgUuid)`; helpers return real booleans, never + wrapped in `catch(\Throwable)` (decidesk `McpMeetingGate` rules). +- [ ] 3.2 Add `lib/Mcp/McpPublicationGate.php` reusing the + `PublicationController::authorizeEntry()` semantics via + `PublicationService::resolveEntry()` (admin OR owning + `aanbod-beheerder`). +- [ ] 3.3 Admin gates for moderation/merge/EOL/membership tools via + `IGroupManager::isAdmin()` — identical posture to the REST twins. + +## 4. Read tools + +- [ ] 4.1 Implement the curated read handlers (design.md §5 read table): + `getMyContactProfile`, `listOffers`, `listOfferedUsages`, + `getPortfolioReport` (reject foreign org uuid for non-admins), + `listPendingModerations`, `getReviewAggregate`, + `getContractApprovalConfig`, `getSbomImportStatus` + (`userCanReadModule()` gate), `listViews`, `getView`, + `previewOrganisationMerge` (dryRun only), `getEolSyncStatus`. + +## 5. Write tools + +- [ ] 5.1 Contract seam: `submitContractApproval`, `submitContractRenewal` + → `ContractApprovalService::submitForApproval()` behind + `McpContractGate`. +- [ ] 5.2 Publication seam: `publishObject`, `depublishObject` → + `PublicationService::publish()/depublish()` behind + `McpPublicationGate`; descriptors declare `reach: external`. +- [ ] 5.3 Moderation/review/intake: `approveRegistration`, + `rejectRegistration` (`ModerationService`), `submitReview` + (`ReviewService::submit()` — pending forced server-side), + `registerOrganisation` (`IntakeService::validate()` + `submit()`). +- [ ] 5.4 Offers/usages/membership/merge: `acceptOffer`, `declineOffer` + (`AanbodService`), `claimUsage`, `declineUsage` + (`AangebodenGebruikService`), `grantOrganisationMembership`, + `revokeOrganisationMembership` (extract the + `OrganisationMembersController` grant/revoke logic into a small + service, or defer these two tools — record the decision here), + `mergeOrganisations` (`MergeOrganisatieService::execute()`, + admin-gated, `destructiveHint: true`). +- [ ] 5.5 Ops: `importSbom` (`SbomImportService::importForModuleVersie()`, + inline payload), `triggerEolSync` (`EolSyncService::run()`, + `reach: external`). + +## 6. Tests + +- [ ] 6.1 PHPUnit descriptor fixture: every entry has `scope` + `reach` + from the closed vocabularies; reads are `readOnlyHint: true`; + publication + EOL tools are `reach: external`; write set matches + design.md exactly. +- [ ] 6.2 PHPUnit per gate: non-owning caller → forbidden AND delegate + never called AND no `DecisionRequestedEvent` (mirror + `contract-approval-ownership-guard`'s cases at the MCP seam); + owning/admin caller passes through. +- [ ] 6.3 PHPUnit validator ladder: missing/badly-typed argument → + validation error, zero service calls. +- [ ] 6.4 PHPUnit `submitReview` pending-forcing; + `previewOrganisationMerge` never reaches `execute()`. +- [ ] 6.5 PHPUnit fragment/merge: schema key set unchanged after merge + (no orphan Dutch slugs); vulnerability the only schema with write verbs. +- [ ] 6.6 `composer check:strict` clean (PHPCS, PHPMD, Psalm, PHPStan) — + fix pre-existing issues encountered in touched files. + +## 7. Supersession + spec/docs + +- [ ] 7.1 Archive `softwarecatalog-mcp-adoption` as superseded by this + change (pointer in its archive note); its + `register.d/softwarecatalog-mcp-adoption.json` is never created. +- [ ] 7.2 Sync this change's spec delta into + `openspec/specs/mcp-tool-surface/spec.md` on archive. +- [ ] 7.3 CHANGELOG entry under Unreleased: full MCP action surface + (28 derived read tools, vulnerability writes, ~29 curated tools) for + hermiq consumption. From 95ff8c3652c2279ed9bb0e0563dba85644729602 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Thu, 20 Aug 2026 13:02:29 +0000 Subject: [PATCH 02/70] chore(release): 0.1.141-unstable.20260820130046 [skip ci] --- appinfo/info.xml | 2 +- openapi.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/appinfo/info.xml b/appinfo/info.xml index 327d81b8..2b99f3e5 100644 --- a/appinfo/info.xml +++ b/appinfo/info.xml @@ -50,7 +50,7 @@ Vrij en open source onder de EUPL-licentie. **Ondersteuning:** Voor ondersteuning, neem contact op via support@conduction.nl. Voor een Service Level Agreement (SLA), neem contact op via sales@conduction.nl. ]]> - 0.1.141-unstable.20260820125107 + 0.1.141-unstable.20260820130046 EUPL-1.2 Conduction SoftwareCatalog diff --git a/openapi.json b/openapi.json index 58b9fb46..4cab2be9 100644 --- a/openapi.json +++ b/openapi.json @@ -2,7 +2,7 @@ "openapi": "3.0.3", "info": { "title": "softwarecatalog", - "version": "0.1.141-unstable.20260820125107", + "version": "0.1.141-unstable.20260820130046", "description": "Software Catalog", "license": { "name": "agpl" From 35a943c2e0cfe554b9064e27d9ec3b5b38b1552c Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Thu, 20 Aug 2026 20:20:04 +0000 Subject: [PATCH 03/70] chore(release): 0.1.141-unstable.20260820201847 [skip ci] --- appinfo/info.xml | 2 +- openapi.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/appinfo/info.xml b/appinfo/info.xml index 2b99f3e5..5b64bd7e 100644 --- a/appinfo/info.xml +++ b/appinfo/info.xml @@ -50,7 +50,7 @@ Vrij en open source onder de EUPL-licentie. **Ondersteuning:** Voor ondersteuning, neem contact op via support@conduction.nl. Voor een Service Level Agreement (SLA), neem contact op via sales@conduction.nl. ]]> - 0.1.141-unstable.20260820130046 + 0.1.141-unstable.20260820201847 EUPL-1.2 Conduction SoftwareCatalog diff --git a/openapi.json b/openapi.json index 4cab2be9..367bb591 100644 --- a/openapi.json +++ b/openapi.json @@ -2,7 +2,7 @@ "openapi": "3.0.3", "info": { "title": "softwarecatalog", - "version": "0.1.141-unstable.20260820130046", + "version": "0.1.141-unstable.20260820201847", "description": "Software Catalog", "license": { "name": "agpl" From 6a2d1725ceb54203b1537584343f279d676d883e Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Thu, 20 Aug 2026 20:40:57 +0000 Subject: [PATCH 04/70] chore(release): 0.1.141-unstable.20260820203911 [skip ci] --- appinfo/info.xml | 2 +- openapi.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/appinfo/info.xml b/appinfo/info.xml index 5b64bd7e..a7452b9a 100644 --- a/appinfo/info.xml +++ b/appinfo/info.xml @@ -50,7 +50,7 @@ Vrij en open source onder de EUPL-licentie. **Ondersteuning:** Voor ondersteuning, neem contact op via support@conduction.nl. Voor een Service Level Agreement (SLA), neem contact op via sales@conduction.nl. ]]> - 0.1.141-unstable.20260820201847 + 0.1.141-unstable.20260820203911 EUPL-1.2 Conduction SoftwareCatalog diff --git a/openapi.json b/openapi.json index 367bb591..1a8f7317 100644 --- a/openapi.json +++ b/openapi.json @@ -2,7 +2,7 @@ "openapi": "3.0.3", "info": { "title": "softwarecatalog", - "version": "0.1.141-unstable.20260820201847", + "version": "0.1.141-unstable.20260820203911", "description": "Software Catalog", "license": { "name": "agpl" From 5689d7eeb0f2ec600ee2c978d7b6691b0d2258f2 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Thu, 20 Aug 2026 20:57:37 +0000 Subject: [PATCH 05/70] chore(release): 0.1.141-unstable.20260820205557 [skip ci] --- appinfo/info.xml | 2 +- openapi.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/appinfo/info.xml b/appinfo/info.xml index a7452b9a..0a415e45 100644 --- a/appinfo/info.xml +++ b/appinfo/info.xml @@ -50,7 +50,7 @@ Vrij en open source onder de EUPL-licentie. **Ondersteuning:** Voor ondersteuning, neem contact op via support@conduction.nl. Voor een Service Level Agreement (SLA), neem contact op via sales@conduction.nl. ]]> - 0.1.141-unstable.20260820203911 + 0.1.141-unstable.20260820205557 EUPL-1.2 Conduction SoftwareCatalog diff --git a/openapi.json b/openapi.json index 1a8f7317..48ac54f1 100644 --- a/openapi.json +++ b/openapi.json @@ -2,7 +2,7 @@ "openapi": "3.0.3", "info": { "title": "softwarecatalog", - "version": "0.1.141-unstable.20260820203911", + "version": "0.1.141-unstable.20260820205557", "description": "Software Catalog", "license": { "name": "agpl" From 743ba4b77b6542647cdf67a3957ab2bffe227312 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Thu, 20 Aug 2026 21:22:46 +0000 Subject: [PATCH 06/70] chore(release): 0.1.141-unstable.20260820212102 [skip ci] --- appinfo/info.xml | 2 +- openapi.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/appinfo/info.xml b/appinfo/info.xml index 0a415e45..c629c83b 100644 --- a/appinfo/info.xml +++ b/appinfo/info.xml @@ -50,7 +50,7 @@ Vrij en open source onder de EUPL-licentie. **Ondersteuning:** Voor ondersteuning, neem contact op via support@conduction.nl. Voor een Service Level Agreement (SLA), neem contact op via sales@conduction.nl. ]]> - 0.1.141-unstable.20260820205557 + 0.1.141-unstable.20260820212102 EUPL-1.2 Conduction SoftwareCatalog diff --git a/openapi.json b/openapi.json index 48ac54f1..6758d9a2 100644 --- a/openapi.json +++ b/openapi.json @@ -2,7 +2,7 @@ "openapi": "3.0.3", "info": { "title": "softwarecatalog", - "version": "0.1.141-unstable.20260820205557", + "version": "0.1.141-unstable.20260820212102", "description": "Software Catalog", "license": { "name": "agpl" From 4f03776004229ece83c1e1e6d291b9d825abea94 Mon Sep 17 00:00:00 2001 From: Ruben van der Linde Date: Thu, 20 Aug 2026 23:28:37 +0200 Subject: [PATCH 07/70] fix(manifest): process and audit fields are no longer editable inputs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `CnObjectDataWidget.editable` defaults to TRUE, so every property named in a data widget's `include` list becomes a text box the user can type into. That put lifecycle state and audit stamps — `status`, `lifecycle`, `submittedAt`, `approvedBy`, `openedAt`, `closedAt`, `publishedAt`, `enactedAt` — in front of users as editable fields. These are written by the backend when a transition lands (`TransitionEngine` stamps them through `saveObject()`), so an input for them is a control that can only ever fail or confuse: the guarded path is the lifecycle buttons, and `LifecycleValidationListener` rejects anything that is not a legal transition. Locked with per-field `overrides..editable: false` rather than `editable: false` on the widget: these panels mix process state with fields the user legitimately edits, and a blanket lock would make those read-only too. NOT fixed here: widgets that declare no `include` at all render EVERY schema property, and enumerating their fields in the manifest would drift the moment the schema changes. 52 such widgets fleet-wide expose 124 process fields. Closing those needs a server-side "system-owned" marker, which OpenRegister does not have — `readOnly:true` has no bypass for backend callers and would break the transition that writes the field. Filed as ConductionNL/openregister#2644. Inserted textually, one compact line per widget, so the diff is the change and nothing else: a full JSON re-serialisation reflowed hand-compacted lines and turned this into thousands of lines of churn. A verifier re-parses both files and asserts the only structural difference is the added overrides, and that no non-process field was locked. --- src/manifest.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/manifest.json b/src/manifest.json index b190a8d0..299009d1 100644 --- a/src/manifest.json +++ b/src/manifest.json @@ -195,7 +195,7 @@ "schema": "organization", "_note": "ADR-062 rollout (round 2): this page did not exist before — organisatie objects (vendors, municipalities, collaborations) were only reachable as index cards with no detail view (codeberg softwarecatalog#76). Vendor/organisation archetype: the organisatie schema itself carries only catalogue-role fields (contactsUid, type, status, registratiestatus, samenwerkingtype, geregistreerdDoor, publicatie dates) because identity (name, website, e-mail, KvK) lives in the linked Nextcloud contact via contactsUid, not on this OR object — so the body leads with those 8 catalogue fields (2-col) rather than name/address. Top-right: a stats-block KPI card counting the organisation's own diensten, modules and contactpersonen (all FK-scoped via aanbieder/organisatie = @objectId) so a reader sees portfolio size at a glance without opening any list. Below: three FK object-lists — Services (dienst.aanbieder) and Applications (module.aanbieder) are the org's supply-side offerings; Contact persons (contactpersoon.organisatie) links to the existing ContactpersoonDetail page. Services still have no dedicated index/detail page (a real fleet gap, follow-up remains). Applications now do — bio-compliance-assessment added Modules/ModuleDetail (BBN level and DPIA tracking needed somewhere to live), so org-modules now carries `rowRoute: ModuleDetail` and a bbnLevel column; org-diensten's rowRoute stays intentionally omitted. An organisation does not communicate itself (contact happens through the linked NC contact), so per the comms hard-rule NO Emails/Meetings widgets appear. Audit trail stays a sidebar tab. Card-click navigation from the Organisaties index required a matching fix in OrganisatieCard.vue (the custom cardComponent never emitted the `click` event CnCardGrid/CnPageRenderer listen on for register+schema route resolution — clicking a card was previously a no-op).", "widgets": [ - { "id": "org-data", "type": "data", "title": "Organisation", "icon": "OfficeBuilding", "content": { "columns": 2, "include": [ "contactsUid", "type", "status", "registrationStatus", "samenwerkingtype", "registeredBy", "publicationDate", "depublicationDate" ] } }, + { "id": "org-data", "type": "data", "title": "Organisation", "icon": "OfficeBuilding", "content": { "columns": 2, "include": [ "contactsUid", "type", "status", "registrationStatus", "samenwerkingtype", "registeredBy", "publicationDate", "depublicationDate" ], "overrides": { "status": { "editable": false }, "registeredBy": { "editable": false } } } }, { "id": "org-stats-services", "type": "stats-block", "title": "Services", "icon": "ChartBar", "content": { "entries": [ { "title": "Services", "register": "@resolve:voorzieningen_register", "schema": "service", "metric": "count", "filter": { "provider": "@objectId" } } ] } }, { "id": "org-stats-applications", "type": "stats-block", "title": "Applications", "icon": "ChartBar", "content": { "entries": [ { "title": "Applications", "register": "@resolve:voorzieningen_register", "schema": "module", "metric": "count", "filter": { "provider": "@objectId" } } ] } }, { "id": "org-stats-contact-persons", "type": "stats-block", "title": "Contact persons", "icon": "ChartBar", "content": { "entries": [ { "title": "Contact persons", "register": "@resolve:voorzieningen_register", "schema": "contactPerson", "metric": "count", "filter": { "organization": "@objectId" } } ] } }, From d0bdc09c06dab946bcb2882f754e0be274a76e8c Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Thu, 20 Aug 2026 21:44:56 +0000 Subject: [PATCH 08/70] chore(release): 0.1.141-unstable.20260820214311 [skip ci] --- appinfo/info.xml | 2 +- openapi.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/appinfo/info.xml b/appinfo/info.xml index c629c83b..1876dfc2 100644 --- a/appinfo/info.xml +++ b/appinfo/info.xml @@ -50,7 +50,7 @@ Vrij en open source onder de EUPL-licentie. **Ondersteuning:** Voor ondersteuning, neem contact op via support@conduction.nl. Voor een Service Level Agreement (SLA), neem contact op via sales@conduction.nl. ]]> - 0.1.141-unstable.20260820212102 + 0.1.141-unstable.20260820214311 EUPL-1.2 Conduction SoftwareCatalog diff --git a/openapi.json b/openapi.json index 6758d9a2..9a60e73a 100644 --- a/openapi.json +++ b/openapi.json @@ -2,7 +2,7 @@ "openapi": "3.0.3", "info": { "title": "softwarecatalog", - "version": "0.1.141-unstable.20260820212102", + "version": "0.1.141-unstable.20260820214311", "description": "Software Catalog", "license": { "name": "agpl" From 9a6e9e57d87f7e1884b39decc8e8eb8ac7c63876 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Thu, 20 Aug 2026 22:06:27 +0000 Subject: [PATCH 09/70] chore(release): 0.1.141-unstable.20260820220515 [skip ci] --- appinfo/info.xml | 2 +- openapi.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/appinfo/info.xml b/appinfo/info.xml index 1876dfc2..02e07ebb 100644 --- a/appinfo/info.xml +++ b/appinfo/info.xml @@ -50,7 +50,7 @@ Vrij en open source onder de EUPL-licentie. **Ondersteuning:** Voor ondersteuning, neem contact op via support@conduction.nl. Voor een Service Level Agreement (SLA), neem contact op via sales@conduction.nl. ]]> - 0.1.141-unstable.20260820214311 + 0.1.141-unstable.20260820220515 EUPL-1.2 Conduction SoftwareCatalog diff --git a/openapi.json b/openapi.json index 9a60e73a..7bb60eb4 100644 --- a/openapi.json +++ b/openapi.json @@ -2,7 +2,7 @@ "openapi": "3.0.3", "info": { "title": "softwarecatalog", - "version": "0.1.141-unstable.20260820214311", + "version": "0.1.141-unstable.20260820220515", "description": "Software Catalog", "license": { "name": "agpl" From 71cb9a0c1f34cb31a1772fbffd56497579f0c765 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Thu, 20 Aug 2026 22:33:12 +0000 Subject: [PATCH 10/70] chore(release): 0.1.141-unstable.20260820223149 [skip ci] --- appinfo/info.xml | 2 +- openapi.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/appinfo/info.xml b/appinfo/info.xml index 02e07ebb..3e0b375b 100644 --- a/appinfo/info.xml +++ b/appinfo/info.xml @@ -50,7 +50,7 @@ Vrij en open source onder de EUPL-licentie. **Ondersteuning:** Voor ondersteuning, neem contact op via support@conduction.nl. Voor een Service Level Agreement (SLA), neem contact op via sales@conduction.nl. ]]> - 0.1.141-unstable.20260820220515 + 0.1.141-unstable.20260820223149 EUPL-1.2 Conduction SoftwareCatalog diff --git a/openapi.json b/openapi.json index 7bb60eb4..6cb09eb5 100644 --- a/openapi.json +++ b/openapi.json @@ -2,7 +2,7 @@ "openapi": "3.0.3", "info": { "title": "softwarecatalog", - "version": "0.1.141-unstable.20260820220515", + "version": "0.1.141-unstable.20260820223149", "description": "Software Catalog", "license": { "name": "agpl" From 0c1e103cdf6abd34c24caf54b3366482c5b130c0 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Thu, 20 Aug 2026 22:50:27 +0000 Subject: [PATCH 11/70] chore(release): 0.1.141-unstable.20260820224848 [skip ci] --- appinfo/info.xml | 2 +- openapi.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/appinfo/info.xml b/appinfo/info.xml index 3e0b375b..1c87434f 100644 --- a/appinfo/info.xml +++ b/appinfo/info.xml @@ -50,7 +50,7 @@ Vrij en open source onder de EUPL-licentie. **Ondersteuning:** Voor ondersteuning, neem contact op via support@conduction.nl. Voor een Service Level Agreement (SLA), neem contact op via sales@conduction.nl. ]]> - 0.1.141-unstable.20260820223149 + 0.1.141-unstable.20260820224848 EUPL-1.2 Conduction SoftwareCatalog diff --git a/openapi.json b/openapi.json index 6cb09eb5..e0a73e11 100644 --- a/openapi.json +++ b/openapi.json @@ -2,7 +2,7 @@ "openapi": "3.0.3", "info": { "title": "softwarecatalog", - "version": "0.1.141-unstable.20260820223149", + "version": "0.1.141-unstable.20260820224848", "description": "Software Catalog", "license": { "name": "agpl" From c74a3a9c8894a7f12f256f135274f8fbeb0096cc Mon Sep 17 00:00:00 2001 From: Ruben van der Linde Date: Fri, 21 Aug 2026 01:11:44 +0200 Subject: [PATCH 12/70] build(deps-dev): move the whole stylelint family to 17 as one upgrade MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit stylelint 15.11.0 -> 17.14.1 with every package that peers on it: @nextcloud/stylelint-config ^2.4.0 -> ^3.2.2 stylelint-config-recommended-scss ^13.1.0 -> ^17.0.1 stylelint-config-recommended-vue ^1.6.1 -> ^2.0.0 postcss-html ^1.8.1 -> ^2.0.0 stylelint-config-html (absent) -> ^2.0.0 None can move alone: vue-config@2 peers 'postcss-html ^2.0.0' and 'stylelint-config-html >=2.0.0'; postcss-html@2 breaks vue-config@1.6.1's own '^1.0.0' peer; scss-config@17 peers 'stylelint ^17'. @nextcloud/stylelint-config is the member that decides it — v2.4.0 still declares indentation / string-quotes / number-leading-zero / selector-list-comma-newline-after, all removed in stylelint 16. Then 10 real errors, in 4 files: 8x word-break: break-word -> overflow-wrap: break-word 1x word-wrap: break-word -> overflow-wrap: break-word 1x clip: rect(0,0,0,0) -> clip-path: inset(50%) Fixed by hand, NOT with --fix. The autofix also rewrites the 86 advisory csstools/use-logical warnings (text-align: left -> start, padding-left -> padding-inline-start, ...) across 28 files. Those change how the UI lays out under RTL and have nothing to do with this bump; openbuild, scholiq and hermiq all carry the same warnings unfixed. A dependency upgrade should not smuggle in a directional-CSS change. Result: 10 errors -> 0, exit 0. The 86 warnings are unchanged and non-blocking. --- package-lock.json | 1379 +++++++---------- package.json | 11 +- src/modals/object/ObjectModal.vue | 4 +- src/modals/object/ViewObject.vue | 10 +- .../sections/ArchiMateImportExport.vue | 4 +- .../settings/sections/ModerationQueue.vue | 2 +- 6 files changed, 609 insertions(+), 801 deletions(-) diff --git a/package-lock.json b/package-lock.json index 3ed17dc5..02e8c784 100644 --- a/package-lock.json +++ b/package-lock.json @@ -54,7 +54,7 @@ "@nextcloud/browserslist-config": "^3.0.1", "@nextcloud/eslint-config": "^9.0.1", "@nextcloud/prettier-config": "^1.2.0", - "@nextcloud/stylelint-config": "^2.4.0", + "@nextcloud/stylelint-config": "^3.2.2", "@nextcloud/webpack-vue-config": "^7.0.2", "@pinia/testing": "^1.0.2", "@playwright/test": "^1.60.0", @@ -79,11 +79,12 @@ "jest-environment-jsdom": "^29.7.0", "jest-transform-stub": "^2.0.0", "jsdom": "^29.1.1", - "postcss-html": "^1.8.1", + "postcss-html": "^2.0.0", "prettier": "^3.9.6", - "stylelint": "^15.11.0", - "stylelint-config-recommended-scss": "^13.1.0", - "stylelint-config-recommended-vue": "^1.6.1", + "stylelint": "^17.14.1", + "stylelint-config-html": "^2.0.0", + "stylelint-config-recommended-scss": "^17.0.1", + "stylelint-config-recommended-vue": "^2.0.0", "stylelint-webpack-plugin": "^5.0.1", "ts-jest": "^29.2.3", "ts-loader": "^9.5.1", @@ -127,49 +128,6 @@ "node": "^20.19.0 || ^22.12.0 || >=24.0.0" } }, - "node_modules/@asamuzakjp/css-color/node_modules/@csstools/css-parser-algorithms": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/@csstools/css-parser-algorithms/-/css-parser-algorithms-4.0.0.tgz", - "integrity": "sha512-+B87qS7fIG3L5h3qwJ/IFbjoVoOe/bpOdh9hAjXbvx0o8ImEmUsGXN0inFOnk2ChCFgqkkGFQ+TpM5rbhkKe4w==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "license": "MIT", - "engines": { - "node": ">=20.19.0" - }, - "peerDependencies": { - "@csstools/css-tokenizer": "^4.0.0" - } - }, - "node_modules/@asamuzakjp/css-color/node_modules/@csstools/css-tokenizer": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/@csstools/css-tokenizer/-/css-tokenizer-4.0.0.tgz", - "integrity": "sha512-QxULHAm7cNu72w97JUNCBFODFaXpbDg+dP8b/oWFAZ2MTRppA3U00Y2L1HqaS4J6yBqxwa/Y3nMBaxVKbB/NsA==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "license": "MIT", - "engines": { - "node": ">=20.19.0" - } - }, "node_modules/@asamuzakjp/dom-selector": { "version": "7.1.1", "resolved": "https://registry.npmjs.org/@asamuzakjp/dom-selector/-/dom-selector-7.1.1.tgz", @@ -2023,6 +1981,50 @@ "node-fetch": "^3.3.0" } }, + "node_modules/@cacheable/memory": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@cacheable/memory/-/memory-2.2.0.tgz", + "integrity": "sha512-CTLKqLItRCEixEAewD3/j9DB3/o96gpTPD4eJ1v+DGOlxZRZncRQkGYqqnAGCscYd6RNeXfGeiuCphsPtqyIfQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@cacheable/utils": "^2.5.0", + "@keyv/bigmap": "^1.3.1", + "hookified": "^1.15.1", + "keyv": "^5.6.0" + } + }, + "node_modules/@cacheable/memory/node_modules/keyv": { + "version": "5.6.0", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-5.6.0.tgz", + "integrity": "sha512-CYDD3SOtsHtyXeEORYRx2qBtpDJFjRTGXUtmNEMGyzYOKj1TE3tycdlho7kA1Ufx9OYWZzg52QFBGALTirzDSw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@keyv/serialize": "^1.1.1" + } + }, + "node_modules/@cacheable/utils": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/@cacheable/utils/-/utils-2.5.0.tgz", + "integrity": "sha512-buipgOVDkkPXNR5+xBpDw7Zk2n1EvU7qBJCNUcL7rhQ//kfpOXPAvQ511Os0vpLYJ1pZnvudNytkQt2hst3wqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "hashery": "^1.5.1", + "keyv": "^5.6.0" + } + }, + "node_modules/@cacheable/utils/node_modules/keyv": { + "version": "5.6.0", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-5.6.0.tgz", + "integrity": "sha512-CYDD3SOtsHtyXeEORYRx2qBtpDJFjRTGXUtmNEMGyzYOKj1TE3tycdlho7kA1Ufx9OYWZzg52QFBGALTirzDSw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@keyv/serialize": "^1.1.1" + } + }, "node_modules/@ckpack/vue-color": { "version": "1.6.0", "resolved": "https://registry.npmjs.org/@ckpack/vue-color/-/vue-color-1.6.0.tgz", @@ -2354,9 +2356,9 @@ } }, "node_modules/@csstools/css-parser-algorithms": { - "version": "2.7.1", - "resolved": "https://registry.npmjs.org/@csstools/css-parser-algorithms/-/css-parser-algorithms-2.7.1.tgz", - "integrity": "sha512-2SJS42gxmACHgikc1WGesXLIT8d/q2l0UFM7TaEeIzdFCE/FPMtTiizcPGGJtlPo2xuQzY09OhrLTzRxqJqwGw==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@csstools/css-parser-algorithms/-/css-parser-algorithms-4.0.0.tgz", + "integrity": "sha512-+B87qS7fIG3L5h3qwJ/IFbjoVoOe/bpOdh9hAjXbvx0o8ImEmUsGXN0inFOnk2ChCFgqkkGFQ+TpM5rbhkKe4w==", "dev": true, "funding": [ { @@ -2370,10 +2372,10 @@ ], "license": "MIT", "engines": { - "node": "^14 || ^16 || >=18" + "node": ">=20.19.0" }, "peerDependencies": { - "@csstools/css-tokenizer": "^2.4.1" + "@csstools/css-tokenizer": "^4.0.0" } }, "node_modules/@csstools/css-syntax-patches-for-csstree": { @@ -2402,9 +2404,9 @@ } }, "node_modules/@csstools/css-tokenizer": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/@csstools/css-tokenizer/-/css-tokenizer-2.4.1.tgz", - "integrity": "sha512-eQ9DIktFJBhGjioABJRtUucoWR2mwllurfnM8LuNGAqX3ViZXaUchqk+1s7jjtkFiT9ySdACsFEA3etErkALUg==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@csstools/css-tokenizer/-/css-tokenizer-4.0.0.tgz", + "integrity": "sha512-QxULHAm7cNu72w97JUNCBFODFaXpbDg+dP8b/oWFAZ2MTRppA3U00Y2L1HqaS4J6yBqxwa/Y3nMBaxVKbB/NsA==", "dev": true, "funding": [ { @@ -2418,13 +2420,13 @@ ], "license": "MIT", "engines": { - "node": "^14 || ^16 || >=18" + "node": ">=20.19.0" } }, "node_modules/@csstools/media-query-list-parser": { - "version": "2.1.13", - "resolved": "https://registry.npmjs.org/@csstools/media-query-list-parser/-/media-query-list-parser-2.1.13.tgz", - "integrity": "sha512-XaHr+16KRU9Gf8XLi3q8kDlI18d5vzKSKCY510Vrtc9iNR0NJzbY9hhTmwhzYZj/ZwGL4VmB3TA9hJW0Um2qFA==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@csstools/media-query-list-parser/-/media-query-list-parser-5.0.0.tgz", + "integrity": "sha512-T9lXmZOfnam3eMERPsszjY5NK0jX8RmThmmm99FZ8b7z8yMaFZWKwLWGZuTwdO3ddRY5fy13GmmEYZXB4I98Eg==", "dev": true, "funding": [ { @@ -2438,17 +2440,40 @@ ], "license": "MIT", "engines": { - "node": "^14 || ^16 || >=18" + "node": ">=20.19.0" + }, + "peerDependencies": { + "@csstools/css-parser-algorithms": "^4.0.0", + "@csstools/css-tokenizer": "^4.0.0" + } + }, + "node_modules/@csstools/selector-resolve-nested": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@csstools/selector-resolve-nested/-/selector-resolve-nested-4.0.1.tgz", + "integrity": "sha512-j3vdQu0XwLME5qOTWxm8cnmvsf423R2YL6DbKklCHZwkDm7UdKNu6RPlw4REIJhSlKBICY3B70/7QZdicLqZgg==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "engines": { + "node": ">=20.19.0" }, "peerDependencies": { - "@csstools/css-parser-algorithms": "^2.7.1", - "@csstools/css-tokenizer": "^2.4.1" + "postcss-selector-parser": "^7.1.1" } }, "node_modules/@csstools/selector-specificity": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/@csstools/selector-specificity/-/selector-specificity-3.1.1.tgz", - "integrity": "sha512-a7cxGcJ2wIlMFLlh8z2ONm+715QkPHiyJcxwQlKOz/03GPw1COpfhcmC9wm4xlZfp//jWHNNMwzjtqHXVWU9KA==", + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/@csstools/selector-specificity/-/selector-specificity-6.0.0.tgz", + "integrity": "sha512-4sSgl78OtOXEX/2d++8A83zHNTgwCJMaR24FvsYL7Uf/VS8HZk9PTwR51elTbGqMuwH3szLvvOXEaVnqn0Z3zA==", "dev": true, "funding": [ { @@ -2462,10 +2487,10 @@ ], "license": "MIT-0", "engines": { - "node": "^14 || ^16 || >=18" + "node": ">=20.19.0" }, "peerDependencies": { - "postcss-selector-parser": "^6.0.13" + "postcss-selector-parser": "^7.1.1" } }, "node_modules/@ctrl/tinycolor": { @@ -4370,6 +4395,30 @@ "@jridgewell/sourcemap-codec": "^1.4.14" } }, + "node_modules/@keyv/bigmap": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/@keyv/bigmap/-/bigmap-1.3.1.tgz", + "integrity": "sha512-WbzE9sdmQtKy8vrNPa9BRnwZh5UF4s1KTmSK0KUVLo3eff5BlQNNWDnFOouNpKfPKDnms9xynJjsMYjMaT/aFQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "hashery": "^1.4.0", + "hookified": "^1.15.0" + }, + "engines": { + "node": ">= 18" + }, + "peerDependencies": { + "keyv": "^5.6.0" + } + }, + "node_modules/@keyv/serialize": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@keyv/serialize/-/serialize-1.1.1.tgz", + "integrity": "sha512-dXn3FZhPv0US+7dtJsIi2R+c7qWYiReoEh5zUntWCf4oSpMNib8FDhSoed6m3QyZdx5hK7iLFkYk3rNxwt8vTA==", + "dev": true, + "license": "MIT" + }, "node_modules/@lezer/common": { "version": "1.5.2", "resolved": "https://registry.npmjs.org/@lezer/common/-/common-1.5.2.tgz", @@ -4954,19 +5003,21 @@ } }, "node_modules/@nextcloud/stylelint-config": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/@nextcloud/stylelint-config/-/stylelint-config-2.4.0.tgz", - "integrity": "sha512-S/q/offcs9pwnkjSrnfvsONryCOe6e1lfK2sszN6ZtkYyXvaqi8EbQuuhaGlxCstn9oXwbXfAI6O3Y8lGrjdFg==", + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/@nextcloud/stylelint-config/-/stylelint-config-3.2.2.tgz", + "integrity": "sha512-5rr77fGK+zoa8yN+8zR43XbqyuN/yB2wSAy4vKE2F+hgAeOhpUAyChV+pfySDbP20t4s22DHgn7BDiZKsbNE3Q==", "dev": true, "license": "AGPL-3.0-or-later", + "dependencies": { + "stylelint-use-logical": "^2.1.3" + }, "engines": { - "node": "^20.0.0", - "npm": "^10.0.0" + "node": "^20.19 || ^22 || ^24" }, "peerDependencies": { - "stylelint": "^15.6.0", - "stylelint-config-recommended-scss": "^13.1.0", - "stylelint-config-recommended-vue": "^1.1.0" + "stylelint": "^17.9.1", + "stylelint-config-recommended-scss": "^17.0.1", + "stylelint-config-recommended-vue": "^1.6.1" } }, "node_modules/@nextcloud/typings": { @@ -6184,6 +6235,19 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/@sindresorhus/merge-streams": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@sindresorhus/merge-streams/-/merge-streams-4.0.0.tgz", + "integrity": "sha512-tlqY9xq5ukxTUZBmoOp+m61cqwQD5pHJtFY3Mn8CA8ps6yghLH/Hw8UPdqg4OLmFW3IFlcXnQNmo/dh8HzXYIQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/@sinonjs/commons": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-3.0.1.tgz", @@ -6597,13 +6661,6 @@ "@types/unist": "*" } }, - "node_modules/@types/minimist": { - "version": "1.2.5", - "resolved": "https://registry.npmjs.org/@types/minimist/-/minimist-1.2.5.tgz", - "integrity": "sha512-hov8bUuiLiyFPGyFPE1lwWhmzYbirOXQNNo40+y3zow8aFVTeyn3VWL0VFFfdNddA8S4Vf0Tc062rzyNr7Paag==", - "dev": true, - "license": "MIT" - }, "node_modules/@types/ms": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/@types/ms/-/ms-2.1.0.tgz", @@ -6619,13 +6676,6 @@ "undici-types": "~6.21.0" } }, - "node_modules/@types/normalize-package-data": { - "version": "2.4.4", - "resolved": "https://registry.npmjs.org/@types/normalize-package-data/-/normalize-package-data-2.4.4.tgz", - "integrity": "sha512-37i+OaWTh9qeK4LSHPsyRC7NahnGotNuZvjLSgcPzblpHB3rrCJxAOgI5gCdKm7coonsaX1Of0ILiTcnZjbfxA==", - "dev": true, - "license": "MIT" - }, "node_modules/@types/prop-types": { "version": "15.7.15", "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.15.tgz", @@ -8049,16 +8099,6 @@ "node": ">=8" } }, - "node_modules/arrify": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/arrify/-/arrify-1.0.1.tgz", - "integrity": "sha512-3CYzex9M9FGQjCGMGyi6/31c8GJbgb0qGyrx5HWxPd0aCwh4cB2YjMb2Xf9UuoogrMrlO9cTqnB5rI5GHZTcUA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/asn1.js": { "version": "4.10.1", "resolved": "https://registry.npmjs.org/asn1.js/-/asn1.js-4.10.1.tgz", @@ -8947,6 +8987,30 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/cacheable": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/cacheable/-/cacheable-2.5.0.tgz", + "integrity": "sha512-60cyAOytib/OzBw1JNSoSV/boK1AtHryDIjvVBk7XbN4ugfkM3+Sry7fEjNgPMGgOjuaZPAp8ruZ0Cxafwyq9g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@cacheable/memory": "^2.2.0", + "@cacheable/utils": "^2.5.0", + "hookified": "^1.15.0", + "keyv": "^5.6.0", + "qified": "^0.10.1" + } + }, + "node_modules/cacheable/node_modules/keyv": { + "version": "5.6.0", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-5.6.0.tgz", + "integrity": "sha512-CYDD3SOtsHtyXeEORYRx2qBtpDJFjRTGXUtmNEMGyzYOKj1TE3tycdlho7kA1Ufx9OYWZzg52QFBGALTirzDSw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@keyv/serialize": "^1.1.1" + } + }, "node_modules/call-bind": { "version": "1.0.9", "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.9.tgz", @@ -9014,51 +9078,6 @@ "node": ">=6" } }, - "node_modules/camelcase-keys": { - "version": "7.0.2", - "resolved": "https://registry.npmjs.org/camelcase-keys/-/camelcase-keys-7.0.2.tgz", - "integrity": "sha512-Rjs1H+A9R+Ig+4E/9oyB66UC5Mj9Xq3N//vcLf2WzgdTi/3gUu3Z9KoqmlrEG4VuuLK8wJHofxzdQXz/knhiYg==", - "dev": true, - "license": "MIT", - "dependencies": { - "camelcase": "^6.3.0", - "map-obj": "^4.1.0", - "quick-lru": "^5.1.1", - "type-fest": "^1.2.1" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/camelcase-keys/node_modules/camelcase": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", - "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/camelcase-keys/node_modules/type-fest": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-1.4.0.tgz", - "integrity": "sha512-yGSza74xk0UG8k+pLh5oeoYirvIiWo5t0/o3zHHAO2tRDiZcxWP7fywNlXhqb6/r6sWvwi+RsyQMWhVLe4BVuA==", - "dev": true, - "license": "(MIT OR CC0-1.0)", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/cancelable-promise": { "version": "4.3.1", "resolved": "https://registry.npmjs.org/cancelable-promise/-/cancelable-promise-4.3.1.tgz", @@ -9504,16 +9523,16 @@ "license": "MIT" }, "node_modules/cosmiconfig": { - "version": "8.3.6", - "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-8.3.6.tgz", - "integrity": "sha512-kcZ6+W5QzcJ3P1Mt+83OUv/oHFqZHIx8DuxG6eZ5RGMERoLqp4BuGjhHLYGK+Kf5XVkQvqBSmAy/nGWN3qDgEA==", + "version": "9.0.2", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-9.0.2.tgz", + "integrity": "sha512-gtTZxTDau1wL7Y7zifc2dd8jHSK/k6BTx/2Xp/BpdlAdnlYWFVt7qhJqgwi7637yRwRQ3qL4ZidbB4I8tA5VOg==", "dev": true, "license": "MIT", "dependencies": { + "env-paths": "^2.2.1", "import-fresh": "^3.3.0", "js-yaml": "^4.1.0", - "parse-json": "^5.2.0", - "path-type": "^4.0.0" + "parse-json": "^5.2.0" }, "engines": { "node": ">=14" @@ -9910,56 +9929,6 @@ } } }, - "node_modules/decamelize": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-5.0.1.tgz", - "integrity": "sha512-VfxadyCECXgQlkoEAjeghAr5gY3Hf+IKjKb+X8tGVDtveCjN+USwprd2q3QXBR9T1+x2DG0XZF5/w+7HAtSaXA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/decamelize-keys": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/decamelize-keys/-/decamelize-keys-1.1.1.tgz", - "integrity": "sha512-WiPxgEirIV0/eIOMcnFBA3/IJZAZqKnwAwWyvvdi4lsr1WCN22nhdf/3db3DoZcUjTV2SqfzIwNyp6y2xs3nmg==", - "dev": true, - "license": "MIT", - "dependencies": { - "decamelize": "^1.1.0", - "map-obj": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/decamelize-keys/node_modules/decamelize": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", - "integrity": "sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/decamelize-keys/node_modules/map-obj": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/map-obj/-/map-obj-1.0.1.tgz", - "integrity": "sha512-7N/q3lyZ+LVCp7PzuxrJr4KMbBE2hW7BT7YNia330OFxIf4d3r5zVpicP2650l7CPN6RM9zOJRl3NGpqSiw3Eg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/decimal.js": { "version": "10.6.0", "resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.6.0.tgz", @@ -10531,7 +10500,6 @@ "integrity": "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==", "dev": true, "license": "MIT", - "optional": true, "engines": { "node": ">=6" } @@ -11126,20 +11094,6 @@ } } }, - "node_modules/eslint-plugin-vue/node_modules/postcss-selector-parser": { - "version": "7.1.5", - "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.5.tgz", - "integrity": "sha512-KvvtD7SrlBP7dlgkBghEE3r84CABm5SmV2aNcG4oCA+qDnJ/tvKonFVvwWAyyWUEwxuNawdfEAZKP9zM3oZ2Uw==", - "dev": true, - "license": "MIT", - "dependencies": { - "cssesc": "^3.0.0", - "util-deprecate": "^1.0.2" - }, - "engines": { - "node": ">=4" - } - }, "node_modules/eslint-plugin-vue/node_modules/semver": { "version": "7.8.5", "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz", @@ -11807,18 +11761,15 @@ } }, "node_modules/flat-cache": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.2.0.tgz", - "integrity": "sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw==", + "version": "6.1.23", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-6.1.23.tgz", + "integrity": "sha512-f++BY9pTk+983xK1FLzlLpmM0i0z+jHmx3QESGkURMXujQZz1k5wzwX6hjnQ8goaD0B+sYnDK1yZ6MTyZfUaqA==", "dev": true, "license": "MIT", "dependencies": { - "flatted": "^3.2.9", - "keyv": "^4.5.3", - "rimraf": "^3.0.2" - }, - "engines": { - "node": "^10.12.0 || >=12.0.0" + "cacheable": "^2.5.0", + "flatted": "^3.4.2", + "hookified": "^1.15.0" } }, "node_modules/flatted": { @@ -12039,6 +11990,19 @@ "node": "6.* || 8.* || >= 10.*" } }, + "node_modules/get-east-asian-width": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/get-east-asian-width/-/get-east-asian-width-1.6.0.tgz", + "integrity": "sha512-QRbvDIbx6YklUe6RxeTeleMR0yv3cYH6PsPZHcnVn7xv7zO1BHN8r0XETu8n6Ye3Q+ahtSarc3WgtNWmehIBfA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/get-intrinsic": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", @@ -12287,16 +12251,6 @@ "node": ">=0.10.0" } }, - "node_modules/hard-rejection": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/hard-rejection/-/hard-rejection-2.1.0.tgz", - "integrity": "sha512-VIZB+ibDhx7ObhAe7OVtoEbuP4h/MuOTHJ+J8h/eBXotJYl0fBgR72xDFCKgIh22OJZIOVNxBMWuhAr10r8HdA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, "node_modules/has-flag": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", @@ -12375,6 +12329,19 @@ "minimalistic-assert": "^1.0.1" } }, + "node_modules/hashery": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/hashery/-/hashery-1.5.1.tgz", + "integrity": "sha512-iZyKG96/JwPz1N55vj2Ie2vXbhu440zfUfJvSwEqEbeLluk7NnapfGqa7LH0mOsnDxTF85Mx8/dyR6HfqcbmbQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "hookified": "^1.15.0" + }, + "engines": { + "node": ">=20" + } + }, "node_modules/hasown": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.4.tgz", @@ -12482,6 +12449,13 @@ "integrity": "sha512-Yc+BQe8SvoXH1643Qez1zqLRmbA5rCL+sSmk6TVos0LWVfNIB7PGncdlId77WzLGSIB5KaWgTaNTs2lNVEI6VQ==", "license": "MIT" }, + "node_modules/hookified": { + "version": "1.15.1", + "resolved": "https://registry.npmjs.org/hookified/-/hookified-1.15.1.tgz", + "integrity": "sha512-MvG/clsADq1GPM2KGo2nyfaWVyn9naPiXrqIe4jYjXNZQt238kWyOGrsyc/DmRAQ+Re6yeo6yX/yoNCG5KAEVg==", + "dev": true, + "license": "MIT" + }, "node_modules/hosted-git-info": { "version": "9.0.3", "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-9.0.3.tgz", @@ -12549,22 +12523,22 @@ "license": "MIT" }, "node_modules/html-tags": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/html-tags/-/html-tags-3.3.1.tgz", - "integrity": "sha512-ztqyC3kLto0e9WbNp0aeP+M3kTt+nbaIveGmUxAtZa+8iFgKLUOD4YKM5j+f3QD89bra7UeumolZHKuOXnTmeQ==", + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/html-tags/-/html-tags-5.1.0.tgz", + "integrity": "sha512-n6l5uca7/y5joxZ3LUePhzmBFUJ+U2YWzhMa8XUTecSeSlQiZdF5XAd/Q3/WUl0VsXgUwWi8I7CNIwdI5WN1SQ==", "dev": true, "license": "MIT", "engines": { - "node": ">=8" + "node": ">=20.10" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/htmlparser2": { - "version": "8.0.2", - "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-8.0.2.tgz", - "integrity": "sha512-GYdjWKDkbRLkZ5geuHs5NY1puJ+PXwP7+fHPRz06Eirsb9ugf6d8kkXav6ADhcODhFFPMIXyxkxSuMf3D6NCFA==", + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-9.1.0.tgz", + "integrity": "sha512-5zfg6mHUoaer/97TxnGpxmbR7zJtPwIYFMZ/H5ucTlPZhKvtum05yiPK3Mgai3a0DyVxv7qYqoweaEd2nrYQzQ==", "dev": true, "funding": [ "https://github.com/fb55/htmlparser2?sponsor=1", @@ -12577,8 +12551,8 @@ "dependencies": { "domelementtype": "^2.3.0", "domhandler": "^5.0.3", - "domutils": "^3.0.1", - "entities": "^4.4.0" + "domutils": "^3.1.0", + "entities": "^4.5.0" } }, "node_modules/htmlparser2/node_modules/entities": { @@ -12724,16 +12698,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/import-lazy": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/import-lazy/-/import-lazy-4.0.0.tgz", - "integrity": "sha512-rKtvo6a868b5Hu3heneU+L4yEQ4jYKLtjpnPeUdK7h0yzXGmyBTypknlkCvHFBqfX9YlorEiMM6Dnq/5atfHkw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, "node_modules/import-local": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/import-local/-/import-local-3.2.0.tgz", @@ -12753,6 +12717,17 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/import-meta-resolve": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/import-meta-resolve/-/import-meta-resolve-4.2.0.tgz", + "integrity": "sha512-Iqv2fzaTQN28s/FwZAoFq0ZSs/7hMAHJVX+w8PZl3cY19Pxk6jFFalxQoIfW2826i/fDLXv8IiEZRIT0lDuWcg==", + "dev": true, + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/imurmurhash": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", @@ -12763,19 +12738,6 @@ "node": ">=0.8.19" } }, - "node_modules/indent-string": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-5.0.0.tgz", - "integrity": "sha512-m6FAo/spmsW2Ab2fU35JTYwtOKa2yAwXSwgjSv1TJzh4Mh7mC3lzAOVLBprb72XsTrgkEIsl7YrFNAiDiRhIGg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/inflight": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", @@ -13027,14 +12989,17 @@ "node": ">=0.12.0" } }, - "node_modules/is-plain-obj": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-1.1.0.tgz", - "integrity": "sha512-yvkRyxmFKEOQ4pNXCmJG5AEQNlXJS5LaONXo5/cLdTZdWvsZ1ioJEonLGAosKlMWE8lwUy/bJzMjcw8az73+Fg==", + "node_modules/is-path-inside": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-4.0.0.tgz", + "integrity": "sha512-lJJV/5dYS+RcL8uQdBDW9c9uWFLLBNRyFhnAKXw5tVqLlKZ4RMGZKv+YQ/IA3OhD+RpbJa1LLFM1FQPGyIXvOA==", "dev": true, "license": "MIT", "engines": { - "node": ">=0.10.0" + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/is-plain-object": { @@ -15452,9 +15417,9 @@ } }, "node_modules/known-css-properties": { - "version": "0.29.0", - "resolved": "https://registry.npmjs.org/known-css-properties/-/known-css-properties-0.29.0.tgz", - "integrity": "sha512-Ne7wqW7/9Cz54PDt4I3tcV+hAyat8ypyOGzYRJQfdxnnjeWsTxt1cy8pjvvKeI5kfXuyvULyeeAvwvvtAX3ayQ==", + "version": "0.37.0", + "resolved": "https://registry.npmjs.org/known-css-properties/-/known-css-properties-0.37.0.tgz", + "integrity": "sha512-JCDrsP4Z1Sb9JwG0aJ8Eo2r7k4Ou5MwmThS/6lcIe1ICyb7UBJKGRIUUdqc2ASdE/42lgz6zFUnzAIhtXnBVrQ==", "dev": true, "license": "MIT" }, @@ -15723,19 +15688,6 @@ "tmpl": "1.0.5" } }, - "node_modules/map-obj": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/map-obj/-/map-obj-4.3.0.tgz", - "integrity": "sha512-hdN1wVrZbb29eBGiGjJbeP8JbKjq1urkHJ/LIP/NY48MZ1QVXUsQBV1G1zvYFHn1XE06cwjBsOI2K3Ulnj1YXQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/marked": { "version": "12.0.2", "resolved": "https://registry.npmjs.org/marked/-/marked-12.0.2.tgz", @@ -15764,9 +15716,9 @@ } }, "node_modules/mathml-tag-names": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/mathml-tag-names/-/mathml-tag-names-2.1.3.tgz", - "integrity": "sha512-APMBEanjybaPzUrfqU0IMU5I0AswKMH7k8OTLs0vvV4KZpExkTkY87nR/zpbuTPj+gARop7aGUbl11pnDfW6xg==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/mathml-tag-names/-/mathml-tag-names-4.0.0.tgz", + "integrity": "sha512-aa6AU2Pcx0VP/XWnh8IGL0SYSgQHDT6Ucror2j2mXeFAlN3ahaNs8EZtG1YiticMkSLj3Gt6VPFfZogt7G5iFQ==", "dev": true, "license": "MIT", "funding": { @@ -16013,107 +15965,18 @@ "license": "CC0-1.0" }, "node_modules/meow": { - "version": "10.1.5", - "resolved": "https://registry.npmjs.org/meow/-/meow-10.1.5.tgz", - "integrity": "sha512-/d+PQ4GKmGvM9Bee/DPa8z3mXs/pkvJE2KEThngVNOqtmljC6K7NMPxtc2JeZYTmpWb9k/TmxjeL18ez3h7vCw==", + "version": "14.1.0", + "resolved": "https://registry.npmjs.org/meow/-/meow-14.1.0.tgz", + "integrity": "sha512-EDYo6VlmtnumlcBCbh1gLJ//9jvM/ndXHfVXIFrZVr6fGcwTUyCTFNTLCKuY3ffbK8L/+3Mzqnd58RojiZqHVw==", "dev": true, "license": "MIT", - "dependencies": { - "@types/minimist": "^1.2.2", - "camelcase-keys": "^7.0.0", - "decamelize": "^5.0.0", - "decamelize-keys": "^1.1.0", - "hard-rejection": "^2.1.0", - "minimist-options": "4.1.0", - "normalize-package-data": "^3.0.2", - "read-pkg-up": "^8.0.0", - "redent": "^4.0.0", - "trim-newlines": "^4.0.2", - "type-fest": "^1.2.2", - "yargs-parser": "^20.2.9" - }, - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/meow/node_modules/hosted-git-info": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-4.1.0.tgz", - "integrity": "sha512-kyCuEOWjJqZuDbRHzL8V93NzQhwIB71oFWSyzVo+KPZI+pnQPPxucdkrOZvkLRnrf5URsQM+IJ09Dw29cRALIA==", - "dev": true, - "license": "ISC", - "dependencies": { - "lru-cache": "^6.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/meow/node_modules/lru-cache": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", - "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "dev": true, - "license": "ISC", - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/meow/node_modules/normalize-package-data": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-3.0.3.tgz", - "integrity": "sha512-p2W1sgqij3zMMyRC067Dg16bfzVH+w7hyegmpIvZ4JNjqtGOVAIvLmjBx3yP7YTe9vKJgkoNOPjwQGogDoMXFA==", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "hosted-git-info": "^4.0.1", - "is-core-module": "^2.5.0", - "semver": "^7.3.4", - "validate-npm-package-license": "^3.0.1" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/meow/node_modules/semver": { - "version": "7.8.5", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz", - "integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/meow/node_modules/type-fest": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-1.4.0.tgz", - "integrity": "sha512-yGSza74xk0UG8k+pLh5oeoYirvIiWo5t0/o3zHHAO2tRDiZcxWP7fywNlXhqb6/r6sWvwi+RsyQMWhVLe4BVuA==", - "dev": true, - "license": "(MIT OR CC0-1.0)", "engines": { - "node": ">=10" + "node": ">=20" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/meow/node_modules/yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true, - "license": "ISC" - }, "node_modules/merge-stream": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", @@ -16685,21 +16548,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/minimist-options": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/minimist-options/-/minimist-options-4.1.0.tgz", - "integrity": "sha512-Q4r8ghd80yhO/0j1O3B2BjweX3fiHg9cdOwjJd2J76Q135c+NDxGCqdYKQ1SKBuFfgWbAUzBfvYjPUEeNgqN1A==", - "dev": true, - "license": "MIT", - "dependencies": { - "arrify": "^1.0.1", - "is-plain-obj": "^1.1.0", - "kind-of": "^6.0.3" - }, - "engines": { - "node": ">= 6" - } - }, "node_modules/minimizer-webpack-plugin": { "version": "5.6.1", "resolved": "https://registry.npmjs.org/minimizer-webpack-plugin/-/minimizer-webpack-plugin-5.6.1.tgz", @@ -18158,19 +18006,21 @@ } }, "node_modules/postcss-html": { - "version": "1.8.1", - "resolved": "https://registry.npmjs.org/postcss-html/-/postcss-html-1.8.1.tgz", - "integrity": "sha512-OLF6P7qctfAWayOhLpcVnTGqVeJzu2W3WpIYelfz2+JV5oGxfkcEvweN9U4XpeqE0P98dcD9ssusGwlF0TK0uQ==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/postcss-html/-/postcss-html-2.0.0.tgz", + "integrity": "sha512-f2Rvw5FCollEfVj3wfN7JdQb7n2rNIthW+epw2EByio7M6P7RH0BTj8a/ODHrUXd0cmO7ychb6YniymV93182Q==", "dev": true, "license": "MIT", "dependencies": { - "htmlparser2": "^8.0.0", + "htmlparser2": "^9.1.0", "js-tokens": "^9.0.0", - "postcss": "^8.5.0", - "postcss-safe-parser": "^6.0.0" + "postcss-safe-parser": "^7.0.1" }, "engines": { - "node": "^12 || >=14" + "node": "^22.12 || >=24" + }, + "peerDependencies": { + "postcss": "^8.5.0" } }, "node_modules/postcss-html/node_modules/js-tokens": { @@ -18216,19 +18066,6 @@ "postcss": "^8.1.0" } }, - "node_modules/postcss-modules-local-by-default/node_modules/postcss-selector-parser": { - "version": "7.1.4", - "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.4.tgz", - "integrity": "sha512-HeP7D2wyhkR+XaK6v4W8oRF62Dsz4flyuczALJp61GckGm42u1saSSJ/0auvcBqxs3jMRFEcPK34At/0JBKdOg==", - "license": "MIT", - "dependencies": { - "cssesc": "^3.0.0", - "util-deprecate": "^1.0.2" - }, - "engines": { - "node": ">=4" - } - }, "node_modules/postcss-modules-scope": { "version": "3.2.1", "resolved": "https://registry.npmjs.org/postcss-modules-scope/-/postcss-modules-scope-3.2.1.tgz", @@ -18244,19 +18081,6 @@ "postcss": "^8.1.0" } }, - "node_modules/postcss-modules-scope/node_modules/postcss-selector-parser": { - "version": "7.1.4", - "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.4.tgz", - "integrity": "sha512-HeP7D2wyhkR+XaK6v4W8oRF62Dsz4flyuczALJp61GckGm42u1saSSJ/0auvcBqxs3jMRFEcPK34At/0JBKdOg==", - "license": "MIT", - "dependencies": { - "cssesc": "^3.0.0", - "util-deprecate": "^1.0.2" - }, - "engines": { - "node": ">=4" - } - }, "node_modules/postcss-modules-values": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/postcss-modules-values/-/postcss-modules-values-4.0.0.tgz", @@ -18280,20 +18104,30 @@ "license": "MIT" }, "node_modules/postcss-safe-parser": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/postcss-safe-parser/-/postcss-safe-parser-6.0.0.tgz", - "integrity": "sha512-FARHN8pwH+WiS2OPCxJI8FuRJpTVnn6ZNFiqAM2aeW2LwTHWWmWgIyKC6cUo0L8aeKiF/14MNvnpls6R2PBeMQ==", + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/postcss-safe-parser/-/postcss-safe-parser-7.0.1.tgz", + "integrity": "sha512-0AioNCJZ2DPYz5ABT6bddIqlhgwhpHZ/l65YAYo0BCIn0xiDpsnTHz0gnoTGk0OXZW0JRs+cDwL8u/teRdz+8A==", "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss-safe-parser" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], "license": "MIT", "engines": { - "node": ">=12.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/postcss/" + "node": ">=18.0" }, "peerDependencies": { - "postcss": "^8.3.3" + "postcss": "^8.4.31" } }, "node_modules/postcss-scss": { @@ -18324,10 +18158,9 @@ } }, "node_modules/postcss-selector-parser": { - "version": "6.1.4", - "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.1.4.tgz", - "integrity": "sha512-bIoJLOmjCO1S9XdY/DcnR5hJxvrDir1PbGChrzXG3vw0/FOliy/fA3dmdhQ441kah4gKv+TwckGzex6wNS5cnQ==", - "dev": true, + "version": "7.1.5", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.5.tgz", + "integrity": "sha512-KvvtD7SrlBP7dlgkBghEE3r84CABm5SmV2aNcG4oCA+qDnJ/tvKonFVvwWAyyWUEwxuNawdfEAZKP9zM3oZ2Uw==", "license": "MIT", "dependencies": { "cssesc": "^3.0.0", @@ -18680,6 +18513,26 @@ ], "license": "MIT" }, + "node_modules/qified": { + "version": "0.10.1", + "resolved": "https://registry.npmjs.org/qified/-/qified-0.10.1.tgz", + "integrity": "sha512-+Owyggi9IxT1ePKGafcI87ubSmxol6smwJ+RAHDQlx9+9cPwFWDiKFFCPuWhr9ignlGpZ9vDQLw67N4dcTVFEA==", + "dev": true, + "license": "MIT", + "dependencies": { + "hookified": "^2.1.1" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/qified/node_modules/hookified": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/hookified/-/hookified-2.2.0.tgz", + "integrity": "sha512-p/LgFzRN5FeoD3DLS6bkUapeye6E4SI6yJs6KetENd18S+FBthqYq2amJUWpt5z0EQwwHemidjY5OqJGEKm5uA==", + "dev": true, + "license": "MIT" + }, "node_modules/qs": { "version": "6.15.3", "resolved": "https://registry.npmjs.org/qs/-/qs-6.15.3.tgz", @@ -18747,19 +18600,6 @@ ], "license": "MIT" }, - "node_modules/quick-lru": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-5.1.1.tgz", - "integrity": "sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/railroad-diagrams": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/railroad-diagrams/-/railroad-diagrams-1.0.0.tgz", @@ -18837,185 +18677,43 @@ "dev": true, "license": "MIT" }, - "node_modules/read-pkg": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-6.0.0.tgz", - "integrity": "sha512-X1Fu3dPuk/8ZLsMhEj5f4wFAF0DWoK7qhGJvgaijocXxBmSToKfbFtqbxMO7bVjNA1dmE5huAzjXj/ey86iw9Q==", - "dev": true, + "node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", "license": "MIT", "dependencies": { - "@types/normalize-package-data": "^2.4.0", - "normalize-package-data": "^3.0.2", - "parse-json": "^5.2.0", - "type-fest": "^1.0.1" + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" }, "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": ">= 6" } }, - "node_modules/read-pkg-up": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-8.0.0.tgz", - "integrity": "sha512-snVCqPczksT0HS2EC+SxUndvSzn6LRCwpfSvLrIfR5BKDQQZMaI6jPRC9dYvYFDRAuFEAnkwww8kBBNE/3VvzQ==", - "dev": true, + "node_modules/readdirp": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-5.0.0.tgz", + "integrity": "sha512-9u/XQ1pvrQtYyMpZe7DXKv2p5CNvyVwzUB6uhLAnQwHMSgKMBR62lc7AHljaeteeHXn11XTAaLLUVZYVZyuRBQ==", "license": "MIT", - "dependencies": { - "find-up": "^5.0.0", - "read-pkg": "^6.0.0", - "type-fest": "^1.0.1" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/read-pkg-up/node_modules/type-fest": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-1.4.0.tgz", - "integrity": "sha512-yGSza74xk0UG8k+pLh5oeoYirvIiWo5t0/o3zHHAO2tRDiZcxWP7fywNlXhqb6/r6sWvwi+RsyQMWhVLe4BVuA==", - "dev": true, - "license": "(MIT OR CC0-1.0)", "engines": { - "node": ">=10" + "node": ">= 20.19.0" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "type": "individual", + "url": "https://paulmillr.com/funding/" } }, - "node_modules/read-pkg/node_modules/hosted-git-info": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-4.1.0.tgz", - "integrity": "sha512-kyCuEOWjJqZuDbRHzL8V93NzQhwIB71oFWSyzVo+KPZI+pnQPPxucdkrOZvkLRnrf5URsQM+IJ09Dw29cRALIA==", - "dev": true, - "license": "ISC", + "node_modules/rechoir": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/rechoir/-/rechoir-0.8.0.tgz", + "integrity": "sha512-/vxpCXddiX8NGfGO/mTafwjq4aFa/71pvamip0++IQk3zG8cbCj0fifNPrjjF1XMXUne91jL9OoxmdykoEtifQ==", + "license": "MIT", "dependencies": { - "lru-cache": "^6.0.0" + "resolve": "^1.20.0" }, "engines": { - "node": ">=10" - } - }, - "node_modules/read-pkg/node_modules/lru-cache": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", - "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "dev": true, - "license": "ISC", - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/read-pkg/node_modules/normalize-package-data": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-3.0.3.tgz", - "integrity": "sha512-p2W1sgqij3zMMyRC067Dg16bfzVH+w7hyegmpIvZ4JNjqtGOVAIvLmjBx3yP7YTe9vKJgkoNOPjwQGogDoMXFA==", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "hosted-git-info": "^4.0.1", - "is-core-module": "^2.5.0", - "semver": "^7.3.4", - "validate-npm-package-license": "^3.0.1" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/read-pkg/node_modules/semver": { - "version": "7.8.5", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz", - "integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/read-pkg/node_modules/type-fest": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-1.4.0.tgz", - "integrity": "sha512-yGSza74xk0UG8k+pLh5oeoYirvIiWo5t0/o3zHHAO2tRDiZcxWP7fywNlXhqb6/r6sWvwi+RsyQMWhVLe4BVuA==", - "dev": true, - "license": "(MIT OR CC0-1.0)", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/read-pkg/node_modules/yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true, - "license": "ISC" - }, - "node_modules/readable-stream": { - "version": "3.6.2", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", - "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", - "license": "MIT", - "dependencies": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/readdirp": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-5.0.0.tgz", - "integrity": "sha512-9u/XQ1pvrQtYyMpZe7DXKv2p5CNvyVwzUB6uhLAnQwHMSgKMBR62lc7AHljaeteeHXn11XTAaLLUVZYVZyuRBQ==", - "license": "MIT", - "engines": { - "node": ">= 20.19.0" - }, - "funding": { - "type": "individual", - "url": "https://paulmillr.com/funding/" - } - }, - "node_modules/rechoir": { - "version": "0.8.0", - "resolved": "https://registry.npmjs.org/rechoir/-/rechoir-0.8.0.tgz", - "integrity": "sha512-/vxpCXddiX8NGfGO/mTafwjq4aFa/71pvamip0++IQk3zG8cbCj0fifNPrjjF1XMXUne91jL9OoxmdykoEtifQ==", - "license": "MIT", - "dependencies": { - "resolve": "^1.20.0" - }, - "engines": { - "node": ">= 10.13.0" - } - }, - "node_modules/redent": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/redent/-/redent-4.0.0.tgz", - "integrity": "sha512-tYkDkVVtYkSVhuQ4zBgfvciymHaeuel+zFKXShfDnFP5SyVEP7qo70Rf1jTOTCx3vGNAbnEi/xFkcfQVMIBWag==", - "dev": true, - "license": "MIT", - "dependencies": { - "indent-string": "^5.0.0", - "strip-indent": "^4.0.0" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": ">= 10.13.0" } }, "node_modules/regenerate": { @@ -19324,23 +19022,6 @@ "integrity": "sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==", "license": "MIT" }, - "node_modules/rimraf": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", - "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", - "deprecated": "Rimraf versions prior to v4 are no longer supported", - "dev": true, - "license": "ISC", - "dependencies": { - "glob": "^7.1.3" - }, - "bin": { - "rimraf": "bin.js" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, "node_modules/ripemd160": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/ripemd160/-/ripemd160-2.0.3.tgz", @@ -20413,19 +20094,6 @@ "node": ">=6" } }, - "node_modules/strip-indent": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-4.1.1.tgz", - "integrity": "sha512-SlyRoSkdh1dYP0PzclLE7r0M9sgbFKKMFXpFRUMNuKhQSbC6VQIGzq3E0qsfvGJaUFJPGv6Ws1NZ/haTAjfbMA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/strip-json-comments": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", @@ -20518,13 +20186,6 @@ "integrity": "sha512-i/n8VsZydrugj3Iuzll8+x/00GH2vnYsk1eomD8QiRrSAeW6ItbCQDtfXCeJHd0iwiNagqjQkvpvREEPtW3IoQ==", "license": "MIT" }, - "node_modules/style-search": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/style-search/-/style-search-0.1.0.tgz", - "integrity": "sha512-Dj1Okke1C3uKKwQcetra4jSuk0DqbzbYtXipzFlFMZtowbF1x7BKJwB9AayVMyFARvU8EDrZdcax4At/452cAg==", - "dev": true, - "license": "ISC" - }, "node_modules/style-to-js": { "version": "1.1.21", "resolved": "https://registry.npmjs.org/style-to-js/-/style-to-js-1.1.21.tgz", @@ -20544,62 +20205,63 @@ } }, "node_modules/stylelint": { - "version": "15.11.0", - "resolved": "https://registry.npmjs.org/stylelint/-/stylelint-15.11.0.tgz", - "integrity": "sha512-78O4c6IswZ9TzpcIiQJIN49K3qNoXTM8zEJzhaTE/xRTCZswaovSEVIa/uwbOltZrk16X4jAxjaOhzz/hTm1Kw==", + "version": "17.14.1", + "resolved": "https://registry.npmjs.org/stylelint/-/stylelint-17.14.1.tgz", + "integrity": "sha512-xVQwyiuxALUBNB2fBe0tmNemg9KqLtdj3T64mioFDar79B2cU8LIyz+3KL6LdiHs9NkeNfwxpKSaIVOY8f112g==", "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/stylelint" + }, + { + "type": "github", + "url": "https://github.com/sponsors/stylelint" + } + ], "license": "MIT", "dependencies": { - "@csstools/css-parser-algorithms": "^2.3.1", - "@csstools/css-tokenizer": "^2.2.0", - "@csstools/media-query-list-parser": "^2.1.4", - "@csstools/selector-specificity": "^3.0.0", - "balanced-match": "^2.0.0", + "@csstools/css-calc": "^3.2.1", + "@csstools/css-parser-algorithms": "^4.0.0", + "@csstools/css-syntax-patches-for-csstree": "^1.1.6", + "@csstools/css-tokenizer": "^4.0.0", + "@csstools/media-query-list-parser": "^5.0.0", + "@csstools/selector-resolve-nested": "^4.0.0", + "@csstools/selector-specificity": "^6.0.0", "colord": "^2.9.3", - "cosmiconfig": "^8.2.0", - "css-functions-list": "^3.2.1", - "css-tree": "^2.3.1", - "debug": "^4.3.4", - "fast-glob": "^3.3.1", + "cosmiconfig": "^9.0.2", + "css-functions-list": "^3.3.3", + "css-tree": "^3.2.1", + "debug": "^4.4.3", + "fast-glob": "^3.3.3", "fastest-levenshtein": "^1.0.16", - "file-entry-cache": "^7.0.0", + "file-entry-cache": "^11.1.5", "global-modules": "^2.0.0", - "globby": "^11.1.0", + "globby": "^16.2.1", "globjoin": "^0.1.4", - "html-tags": "^3.3.1", - "ignore": "^5.2.4", - "import-lazy": "^4.0.0", - "imurmurhash": "^0.1.4", - "is-plain-object": "^5.0.0", - "known-css-properties": "^0.29.0", - "mathml-tag-names": "^2.1.3", - "meow": "^10.1.5", - "micromatch": "^4.0.5", + "html-tags": "^5.1.0", + "ignore": "^7.0.5", + "import-meta-resolve": "^4.2.0", + "mathml-tag-names": "^4.0.0", + "meow": "^14.1.0", + "micromatch": "^4.0.8", "normalize-path": "^3.0.0", - "picocolors": "^1.0.0", - "postcss": "^8.4.28", - "postcss-resolve-nested-selector": "^0.1.1", - "postcss-safe-parser": "^6.0.0", - "postcss-selector-parser": "^6.0.13", + "picocolors": "^1.1.1", + "postcss": "^8.5.16", + "postcss-safe-parser": "^7.0.1", + "postcss-selector-parser": "^7.1.4", "postcss-value-parser": "^4.2.0", - "resolve-from": "^5.0.0", - "string-width": "^4.2.3", - "strip-ansi": "^6.0.1", - "style-search": "^0.1.0", - "supports-hyperlinks": "^3.0.0", + "string-width": "^8.2.1", + "supports-hyperlinks": "^4.5.0", "svg-tags": "^1.0.0", - "table": "^6.8.1", - "write-file-atomic": "^5.0.1" + "table": "^6.9.0", + "write-file-atomic": "^7.0.1" }, "bin": { "stylelint": "bin/stylelint.mjs" }, "engines": { - "node": "^14.13.1 || >=16.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/stylelint" + "node": ">=20.19.0" } }, "node_modules/stylelint-config-html": { @@ -20620,32 +20282,45 @@ } }, "node_modules/stylelint-config-recommended": { - "version": "13.0.0", - "resolved": "https://registry.npmjs.org/stylelint-config-recommended/-/stylelint-config-recommended-13.0.0.tgz", - "integrity": "sha512-EH+yRj6h3GAe/fRiyaoO2F9l9Tgg50AOFhaszyfov9v6ayXJ1IkSHwTxd7lB48FmOeSGDPLjatjO11fJpmarkQ==", + "version": "18.0.0", + "resolved": "https://registry.npmjs.org/stylelint-config-recommended/-/stylelint-config-recommended-18.0.0.tgz", + "integrity": "sha512-mxgT2XY6YZ3HWWe3Di8umG6aBmWmHTblTgu/f10rqFXnyWxjKWwNdjSWkgkwCtxIKnqjSJzvFmPT5yabVIRxZg==", "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/stylelint" + }, + { + "type": "github", + "url": "https://github.com/sponsors/stylelint" + } + ], "license": "MIT", "engines": { - "node": "^14.13.1 || >=16.0.0" + "node": ">=20.19.0" }, "peerDependencies": { - "stylelint": "^15.10.0" + "stylelint": "^17.0.0" } }, "node_modules/stylelint-config-recommended-scss": { - "version": "13.1.0", - "resolved": "https://registry.npmjs.org/stylelint-config-recommended-scss/-/stylelint-config-recommended-scss-13.1.0.tgz", - "integrity": "sha512-8L5nDfd+YH6AOoBGKmhH8pLWF1dpfY816JtGMePcBqqSsLU+Ysawx44fQSlMOJ2xTfI9yTGpup5JU77c17w1Ww==", + "version": "17.0.1", + "resolved": "https://registry.npmjs.org/stylelint-config-recommended-scss/-/stylelint-config-recommended-scss-17.0.1.tgz", + "integrity": "sha512-x5DVehzJudcwF0od3sGpgkln2PLLranFE7twwbp7dqDINCyZvwzFkMc6TLhNOvazRiVBJYATQLouJY0xPGB8WA==", "dev": true, "license": "MIT", "dependencies": { "postcss-scss": "^4.0.9", - "stylelint-config-recommended": "^13.0.0", - "stylelint-scss": "^5.3.0" + "stylelint-config-recommended": "^18.0.0", + "stylelint-scss": "^7.0.0" + }, + "engines": { + "node": ">=20" }, "peerDependencies": { "postcss": "^8.3.3", - "stylelint": "^15.10.0" + "stylelint": "^17.0.0" }, "peerDependenciesMeta": { "postcss": { @@ -20654,25 +20329,31 @@ } }, "node_modules/stylelint-config-recommended-vue": { - "version": "1.6.1", - "resolved": "https://registry.npmjs.org/stylelint-config-recommended-vue/-/stylelint-config-recommended-vue-1.6.1.tgz", - "integrity": "sha512-lLW7hTIMBiTfjenGuDq2kyHA6fBWd/+Df7MO4/AWOxiFeXP9clbpKgg27kHfwA3H7UNMGC7aeP3mNlZB5LMmEQ==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/stylelint-config-recommended-vue/-/stylelint-config-recommended-vue-2.0.0.tgz", + "integrity": "sha512-SrGBfxgX+CmxRoFOl6HHhfKrp+6YvGnuiHj/N+/deI310eGNhix8aZOtPHG+OeqB6+5Si5BbjsZiC+kULUO1DQ==", "dev": true, "license": "MIT", "dependencies": { - "semver": "^7.3.5", - "stylelint-config-html": ">=1.0.0", - "stylelint-config-recommended": ">=6.0.0" + "semver": "^7.3.5" }, "engines": { - "node": "^12 || >=14" + "node": "^22.12 || >=24" }, "funding": { "url": "https://github.com/sponsors/ota-meshi" }, "peerDependencies": { - "postcss-html": "^1.0.0", - "stylelint": ">=14.0.0" + "postcss-html": "^2.0.0", + "stylelint": ">=16.0.0", + "stylelint-config-html": ">=2.0.0", + "stylelint-config-recommended": ">=14.0.0", + "stylelint-config-recommended-scss": ">=14.0.0" + }, + "peerDependenciesMeta": { + "stylelint-config-recommended-scss": { + "optional": true + } } }, "node_modules/stylelint-config-recommended-vue/node_modules/semver": { @@ -20689,20 +20370,63 @@ } }, "node_modules/stylelint-scss": { - "version": "5.3.2", - "resolved": "https://registry.npmjs.org/stylelint-scss/-/stylelint-scss-5.3.2.tgz", - "integrity": "sha512-4LzLaayFhFyneJwLo0IUa8knuIvj+zF0vBFueQs4e3tEaAMIQX8q5th8ziKkgOavr6y/y9yoBe+RXN/edwLzsQ==", + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/stylelint-scss/-/stylelint-scss-7.2.0.tgz", + "integrity": "sha512-6E79Bachv0Iz0gqRUZgdqdXCsiq26DWBWIBNHYtjTmAp3wJu6cp/I37VfW7BPntmh2puF3bY09XWl4HZGrLhzw==", "dev": true, "license": "MIT", "dependencies": { - "known-css-properties": "^0.29.0", + "@csstools/css-calc": "^3.2.1", + "@csstools/css-parser-algorithms": "^4.0.0", + "@csstools/css-syntax-patches-for-csstree": "^1.1.4", + "@csstools/css-tokenizer": "^4.0.0", + "css-tree": "^3.2.1", + "is-plain-object": "^5.0.0", + "known-css-properties": "^0.37.0", "postcss-media-query-parser": "^0.2.3", - "postcss-resolve-nested-selector": "^0.1.1", - "postcss-selector-parser": "^6.0.13", + "postcss-resolve-nested-selector": "^0.1.6", + "postcss-selector-parser": "^7.1.1", "postcss-value-parser": "^4.2.0" }, + "engines": { + "node": ">=20.19.0" + }, + "peerDependencies": { + "stylelint": "^16.8.2 || ^17.0.0" + } + }, + "node_modules/stylelint-scss/node_modules/css-tree": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-3.2.1.tgz", + "integrity": "sha512-X7sjQzceUhu1u7Y/ylrRZFU2FS6LRiFVp6rKLPg23y3x3c3DOKAwuXGDp+PAGjh6CSnCjYeAul8pcT8bAl+lSA==", + "dev": true, + "license": "MIT", + "dependencies": { + "mdn-data": "2.27.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12.20.0 || ^14.13.0 || >=15.0.0" + } + }, + "node_modules/stylelint-scss/node_modules/mdn-data": { + "version": "2.27.1", + "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.27.1.tgz", + "integrity": "sha512-9Yubnt3e8A0OKwxYSXyhLymGW4sCufcLG6VdiDdUGVkPhpqLxlvP5vl1983gQjJl3tqbrM731mjaZaP68AgosQ==", + "dev": true, + "license": "CC0-1.0" + }, + "node_modules/stylelint-use-logical": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/stylelint-use-logical/-/stylelint-use-logical-2.1.3.tgz", + "integrity": "sha512-haPkgxKre+eSqr4IZJnHwNT/9/wICykeFZIaz7rZbe4SohTHkw7vBahMOrZJZpdny/EBVHAcPH2IBeoiUcZWWw==", + "dev": true, + "license": "CC0-1.0", + "engines": { + "node": ">=14.0.0" + }, "peerDependencies": { - "stylelint": "^14.5.1 || ^15.0.0" + "stylelint": ">= 11 < 18" } }, "node_modules/stylelint-webpack-plugin": { @@ -20730,36 +20454,81 @@ "webpack": "^5.0.0" } }, - "node_modules/stylelint/node_modules/balanced-match": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-2.0.0.tgz", - "integrity": "sha512-1ugUSr8BHXRnK23KfuYS+gVMC3LB8QGH9W1iGtDPsNWoQbgtXSExkBu2aDR4epiGWZOjZsj6lDl/N/AqqTC3UA==", + "node_modules/stylelint/node_modules/ansi-regex": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.3.0.tgz", + "integrity": "sha512-WpDfL7NO6j7tH88IDBNVdUJxDh9nmCteAVW9dsep846XdwF4naCBK+/tGLX3KJgcpgMRXCFlTM2hKGoK9FsdrQ==", "dev": true, - "license": "MIT" + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/stylelint/node_modules/css-tree": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-3.2.1.tgz", + "integrity": "sha512-X7sjQzceUhu1u7Y/ylrRZFU2FS6LRiFVp6rKLPg23y3x3c3DOKAwuXGDp+PAGjh6CSnCjYeAul8pcT8bAl+lSA==", + "dev": true, + "license": "MIT", + "dependencies": { + "mdn-data": "2.27.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12.20.0 || ^14.13.0 || >=15.0.0" + } }, "node_modules/stylelint/node_modules/file-entry-cache": { - "version": "7.0.2", - "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-7.0.2.tgz", - "integrity": "sha512-TfW7/1iI4Cy7Y8L6iqNdZQVvdXn0f8B4QcIXmkIbtTIe/Okm/nSlHb4IwGzRVOd3WfSieCgvf5cMzEfySAIl0g==", + "version": "11.1.5", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-11.1.5.tgz", + "integrity": "sha512-+PFTHITI08JIGhnNpGNI8T8inUpgZfk3GNEqfT9R2zZV2iFXg3CvqzSl/uEhs7TSGujYRELEANyDvS8Fj7+S7Q==", "dev": true, "license": "MIT", "dependencies": { - "flat-cache": "^3.2.0" + "flat-cache": "^6.1.23" + } + }, + "node_modules/stylelint/node_modules/globby": { + "version": "16.2.3", + "resolved": "https://registry.npmjs.org/globby/-/globby-16.2.3.tgz", + "integrity": "sha512-VZX7TV7jmd/pn71vdnLKtgwy1IWqc3KjI9x1/UtPkwoKk5fKrNLY30ltDe3cAM5xruIN7YuuaulFt133jRrKZg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@sindresorhus/merge-streams": "^4.0.0", + "fast-glob": "^3.3.3", + "ignore": "^7.0.5", + "is-path-inside": "^4.0.0", + "slash": "^5.1.0", + "unicorn-magic": "^0.4.0" }, "engines": { - "node": ">=12.0.0" + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/stylelint/node_modules/resolve-from": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", - "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", + "node_modules/stylelint/node_modules/ignore": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.6.tgz", + "integrity": "sha512-BAg6QkE8W+TuQLrrw0Ugr7HegXduRuuj8/ti2kSOc+jz1dmx8/WNcjr6XGnq5YpDWxFwwaavqD0+jIUOKelTsw==", "dev": true, "license": "MIT", "engines": { - "node": ">=8" + "node": ">= 4" } }, + "node_modules/stylelint/node_modules/mdn-data": { + "version": "2.27.1", + "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.27.1.tgz", + "integrity": "sha512-9Yubnt3e8A0OKwxYSXyhLymGW4sCufcLG6VdiDdUGVkPhpqLxlvP5vl1983gQjJl3tqbrM731mjaZaP68AgosQ==", + "dev": true, + "license": "CC0-1.0" + }, "node_modules/stylelint/node_modules/signal-exit": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", @@ -20773,18 +20542,63 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/stylelint/node_modules/slash": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-5.1.0.tgz", + "integrity": "sha512-ZA6oR3T/pEyuqwMgAKT0/hAv8oAXckzbkmR0UkUosQ+Mc4RxGoJkRmwHgHufaenlyAgE1Mxgpdcrf75y6XcnDg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/stylelint/node_modules/string-width": { + "version": "8.2.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-8.2.2.tgz", + "integrity": "sha512-GaPUh5gfdrYzqeVNZvUfT23vYYxXzKYidUcnMtJg/3rxRV63EFZy3k6xfKlmfeJD0176lnUV/Usr3XcwSvFzpg==", + "dev": true, + "license": "MIT", + "dependencies": { + "get-east-asian-width": "^1.5.0", + "strip-ansi": "^7.1.2" + }, + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/stylelint/node_modules/strip-ansi": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.2.0.tgz", + "integrity": "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.2.2" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, "node_modules/stylelint/node_modules/write-file-atomic": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-5.0.1.tgz", - "integrity": "sha512-+QU2zd6OTD8XWIJCbffaiQeH9U73qIqafo1x6V1snCWYGJf6cVE0cDR4D8xRzcEnfI21IFrUPzPGtcPf8AC+Rw==", + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-7.0.1.tgz", + "integrity": "sha512-OTIk8iR8/aCRWBqvxrzxR0hgxWpnYBblY1S5hDWBQfk/VFmJwzmJgQFN3WsoUKHISv2eAwe+PpbUzyL1CKTLXg==", "dev": true, "license": "ISC", "dependencies": { - "imurmurhash": "^0.1.4", "signal-exit": "^4.0.1" }, "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + "node": "^20.17.0 || >=22.9.0" } }, "node_modules/superjson": { @@ -20813,43 +20627,46 @@ } }, "node_modules/supports-hyperlinks": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/supports-hyperlinks/-/supports-hyperlinks-3.2.0.tgz", - "integrity": "sha512-zFObLMyZeEwzAoKCyu1B91U79K2t7ApXuQfo8OuxwXLDgcKxuwM+YvcbIhm6QWqz7mHUH1TVytR1PwVVjEuMig==", + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/supports-hyperlinks/-/supports-hyperlinks-4.5.0.tgz", + "integrity": "sha512-ZW2OvfeCXrNTbLakPUzjQG922EeGCOteFSVoek5DKStTh898wf7zgtuFlzQN8HfZCxC3Eh02yJVrRW51hADf+w==", "dev": true, "license": "MIT", "dependencies": { - "has-flag": "^4.0.0", - "supports-color": "^7.0.0" + "has-flag": "^5.0.1", + "supports-color": "^10.2.2" }, "engines": { - "node": ">=14.18" + "node": ">=20" }, "funding": { "url": "https://github.com/chalk/supports-hyperlinks?sponsor=1" } }, "node_modules/supports-hyperlinks/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-5.0.1.tgz", + "integrity": "sha512-CsNUt5x9LUdx6hnk/E2SZLsDyvfqANZSUq4+D3D8RzDJ2M+HDTIkF60ibS1vHaK55vzgiZw1bEPFG9yH7l33wA==", "dev": true, "license": "MIT", "engines": { - "node": ">=8" + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/supports-hyperlinks/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "version": "10.2.2", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-10.2.2.tgz", + "integrity": "sha512-SS+jx45GF1QjgEXQx4NJZV9ImqmO2NPz5FNsIHrsDjh2YsHnawpan7SNQ1o8NuhrbHZy9AZhIoCUiCeaW/C80g==", "dev": true, "license": "MIT", - "dependencies": { - "has-flag": "^4.0.0" - }, "engines": { - "node": ">=8" + "node": ">=18" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" } }, "node_modules/supports-preserve-symlinks-flag": { @@ -21341,19 +21158,6 @@ "url": "https://github.com/sponsors/wooorm" } }, - "node_modules/trim-newlines": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/trim-newlines/-/trim-newlines-4.1.1.tgz", - "integrity": "sha512-jRKj0n0jXWo6kh62nA5TEh3+4igKDXLvzBJcPpiizP7oOolUrYIxmVBG9TOtHYFHoddUk6YvAkGeGoSVTXfQXQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/trough": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/trough/-/trough-2.2.0.tgz", @@ -22057,6 +21861,19 @@ "node": ">=4" } }, + "node_modules/unicorn-magic": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/unicorn-magic/-/unicorn-magic-0.4.0.tgz", + "integrity": "sha512-wH590V9VNgYH9g3lH9wWjTrUoKsjLF6sGLjhR4sH1LWpLmCOH0Zf7PukhDA8BiS7KHe4oPNkcTHqYkj7SOGUOw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/unified": { "version": "11.0.5", "resolved": "https://registry.npmjs.org/unified/-/unified-11.0.5.tgz", @@ -23655,16 +23472,6 @@ "node": ">=12" } }, - "node_modules/yargs-parser": { - "version": "20.2.9", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz", - "integrity": "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==", - "dev": true, - "license": "ISC", - "engines": { - "node": ">=10" - } - }, "node_modules/yargs/node_modules/yargs-parser": { "version": "21.1.1", "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", diff --git a/package.json b/package.json index 46441455..612f1355 100644 --- a/package.json +++ b/package.json @@ -90,7 +90,7 @@ "@nextcloud/browserslist-config": "^3.0.1", "@nextcloud/eslint-config": "^9.0.1", "@nextcloud/prettier-config": "^1.2.0", - "@nextcloud/stylelint-config": "^2.4.0", + "@nextcloud/stylelint-config": "^3.2.2", "@nextcloud/webpack-vue-config": "^7.0.2", "@pinia/testing": "^1.0.2", "@playwright/test": "^1.60.0", @@ -115,11 +115,12 @@ "jest-environment-jsdom": "^29.7.0", "jest-transform-stub": "^2.0.0", "jsdom": "^29.1.1", - "postcss-html": "^1.8.1", + "postcss-html": "^2.0.0", "prettier": "^3.9.6", - "stylelint": "^15.11.0", - "stylelint-config-recommended-scss": "^13.1.0", - "stylelint-config-recommended-vue": "^1.6.1", + "stylelint": "^17.14.1", + "stylelint-config-html": "^2.0.0", + "stylelint-config-recommended-scss": "^17.0.1", + "stylelint-config-recommended-vue": "^2.0.0", "stylelint-webpack-plugin": "^5.0.1", "ts-jest": "^29.2.3", "ts-loader": "^9.5.1", diff --git a/src/modals/object/ObjectModal.vue b/src/modals/object/ObjectModal.vue index 9a386038..88f3912c 100644 --- a/src/modals/object/ObjectModal.vue +++ b/src/modals/object/ObjectModal.vue @@ -965,11 +965,11 @@ export default { } .detail-value { - word-break: break-word; + overflow-wrap: break-word; } .sub-detail-value { - word-break: break-word; + overflow-wrap: break-word; font-size: 0.8rem; color: var(--color-text-maxcontrast); } diff --git a/src/modals/object/ViewObject.vue b/src/modals/object/ViewObject.vue index 65442392..d5dcb5b5 100644 --- a/src/modals/object/ViewObject.vue +++ b/src/modals/object/ViewObject.vue @@ -5453,7 +5453,7 @@ export default { text-align: left; align-items: center; white-space: normal; - word-break: break-word; + overflow-wrap: break-word; } .json-value { @@ -5712,7 +5712,7 @@ export default { min-height: 100px; resize: vertical; white-space: pre-wrap; - word-break: break-word; + overflow-wrap: break-word; overflow-wrap: anywhere; } @@ -5862,7 +5862,7 @@ export default { .viewObjectDialog .viewTable th, .viewObjectDialog .viewTable td { white-space: normal; - word-break: break-word; + overflow-wrap: break-word; } .viewObjectDialog .viewTable td.td-labels { @@ -5884,7 +5884,7 @@ export default { .viewObjectDialog .viewTable td.table-row-title { flex: 1; white-space: normal; - word-break: break-word; + overflow-wrap: break-word; } .short-column { @@ -5900,7 +5900,7 @@ export default { width: 100%; max-width: initial; white-space: normal; - word-break: break-word; + overflow-wrap: break-word; } .table-row-type { diff --git a/src/views/settings/sections/ArchiMateImportExport.vue b/src/views/settings/sections/ArchiMateImportExport.vue index ab98cc8d..182c3b5d 100644 --- a/src/views/settings/sections/ArchiMateImportExport.vue +++ b/src/views/settings/sections/ArchiMateImportExport.vue @@ -1305,7 +1305,7 @@ export default { padding: 0; margin: -1px; overflow: hidden; - clip: rect(0, 0, 0, 0); + clip-path: inset(50%); white-space: nowrap; border: 0; } @@ -2036,7 +2036,7 @@ button.omschrijving-item:disabled { font-weight: 500; color: var(--color-main-text); margin-bottom: 0.5rem; - word-wrap: break-word; + overflow-wrap: break-word; } .error-meta { diff --git a/src/views/settings/sections/ModerationQueue.vue b/src/views/settings/sections/ModerationQueue.vue index 04b229f4..e0d2e2fd 100644 --- a/src/views/settings/sections/ModerationQueue.vue +++ b/src/views/settings/sections/ModerationQueue.vue @@ -333,7 +333,7 @@ export default defineComponent({ .moderation-title { font-weight: 600; - word-break: break-word; + overflow-wrap: break-word; } .moderation-actions { From 4f756706fa0b1551af89a3881ca8fa63a6cab734 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Thu, 20 Aug 2026 23:12:30 +0000 Subject: [PATCH 13/70] chore(release): 0.1.141-unstable.20260820231107 [skip ci] --- appinfo/info.xml | 2 +- openapi.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/appinfo/info.xml b/appinfo/info.xml index 1c87434f..04cfd4bb 100644 --- a/appinfo/info.xml +++ b/appinfo/info.xml @@ -50,7 +50,7 @@ Vrij en open source onder de EUPL-licentie. **Ondersteuning:** Voor ondersteuning, neem contact op via support@conduction.nl. Voor een Service Level Agreement (SLA), neem contact op via sales@conduction.nl. ]]> - 0.1.141-unstable.20260820224848 + 0.1.141-unstable.20260820231107 EUPL-1.2 Conduction SoftwareCatalog diff --git a/openapi.json b/openapi.json index e0a73e11..a805512c 100644 --- a/openapi.json +++ b/openapi.json @@ -2,7 +2,7 @@ "openapi": "3.0.3", "info": { "title": "softwarecatalog", - "version": "0.1.141-unstable.20260820224848", + "version": "0.1.141-unstable.20260820231107", "description": "Software Catalog", "license": { "name": "agpl" From 3b98ca49e4bb4e07a3fde3c2e325a6f412acfea3 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Thu, 20 Aug 2026 23:41:33 +0000 Subject: [PATCH 14/70] chore(release): 0.1.141-unstable.20260820233952 [skip ci] --- appinfo/info.xml | 2 +- openapi.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/appinfo/info.xml b/appinfo/info.xml index 04cfd4bb..06e58ae2 100644 --- a/appinfo/info.xml +++ b/appinfo/info.xml @@ -50,7 +50,7 @@ Vrij en open source onder de EUPL-licentie. **Ondersteuning:** Voor ondersteuning, neem contact op via support@conduction.nl. Voor een Service Level Agreement (SLA), neem contact op via sales@conduction.nl. ]]> - 0.1.141-unstable.20260820231107 + 0.1.141-unstable.20260820233952 EUPL-1.2 Conduction SoftwareCatalog diff --git a/openapi.json b/openapi.json index a805512c..7a49f544 100644 --- a/openapi.json +++ b/openapi.json @@ -2,7 +2,7 @@ "openapi": "3.0.3", "info": { "title": "softwarecatalog", - "version": "0.1.141-unstable.20260820231107", + "version": "0.1.141-unstable.20260820233952", "description": "Software Catalog", "license": { "name": "agpl" From fa4ecea428c731c7d2d70caf8415e23a7abd033f Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Fri, 21 Aug 2026 00:03:11 +0000 Subject: [PATCH 15/70] chore(release): 0.1.141-unstable.20260821000125 [skip ci] --- appinfo/info.xml | 2 +- openapi.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/appinfo/info.xml b/appinfo/info.xml index 06e58ae2..5abe3e6b 100644 --- a/appinfo/info.xml +++ b/appinfo/info.xml @@ -50,7 +50,7 @@ Vrij en open source onder de EUPL-licentie. **Ondersteuning:** Voor ondersteuning, neem contact op via support@conduction.nl. Voor een Service Level Agreement (SLA), neem contact op via sales@conduction.nl. ]]> - 0.1.141-unstable.20260820233952 + 0.1.141-unstable.20260821000125 EUPL-1.2 Conduction SoftwareCatalog diff --git a/openapi.json b/openapi.json index 7a49f544..d2942f35 100644 --- a/openapi.json +++ b/openapi.json @@ -2,7 +2,7 @@ "openapi": "3.0.3", "info": { "title": "softwarecatalog", - "version": "0.1.141-unstable.20260820233952", + "version": "0.1.141-unstable.20260821000125", "description": "Software Catalog", "license": { "name": "agpl" From 1ef96a2405247a2fd850dcc99606668b045cc6b1 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Fri, 21 Aug 2026 00:24:12 +0000 Subject: [PATCH 16/70] chore(release): 0.1.141-unstable.20260821002227 [skip ci] --- appinfo/info.xml | 2 +- openapi.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/appinfo/info.xml b/appinfo/info.xml index 5abe3e6b..48d1ff69 100644 --- a/appinfo/info.xml +++ b/appinfo/info.xml @@ -50,7 +50,7 @@ Vrij en open source onder de EUPL-licentie. **Ondersteuning:** Voor ondersteuning, neem contact op via support@conduction.nl. Voor een Service Level Agreement (SLA), neem contact op via sales@conduction.nl. ]]> - 0.1.141-unstable.20260821000125 + 0.1.141-unstable.20260821002227 EUPL-1.2 Conduction SoftwareCatalog diff --git a/openapi.json b/openapi.json index d2942f35..0a5f696a 100644 --- a/openapi.json +++ b/openapi.json @@ -2,7 +2,7 @@ "openapi": "3.0.3", "info": { "title": "softwarecatalog", - "version": "0.1.141-unstable.20260821000125", + "version": "0.1.141-unstable.20260821002227", "description": "Software Catalog", "license": { "name": "agpl" From ed1a773f26836412173d7ab9f4a66b2140973bd1 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Fri, 21 Aug 2026 00:48:23 +0000 Subject: [PATCH 17/70] chore(release): 0.1.141-unstable.20260821004639 [skip ci] --- appinfo/info.xml | 2 +- openapi.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/appinfo/info.xml b/appinfo/info.xml index 48d1ff69..5a395542 100644 --- a/appinfo/info.xml +++ b/appinfo/info.xml @@ -50,7 +50,7 @@ Vrij en open source onder de EUPL-licentie. **Ondersteuning:** Voor ondersteuning, neem contact op via support@conduction.nl. Voor een Service Level Agreement (SLA), neem contact op via sales@conduction.nl. ]]> - 0.1.141-unstable.20260821002227 + 0.1.141-unstable.20260821004639 EUPL-1.2 Conduction SoftwareCatalog diff --git a/openapi.json b/openapi.json index 0a5f696a..7e91d294 100644 --- a/openapi.json +++ b/openapi.json @@ -2,7 +2,7 @@ "openapi": "3.0.3", "info": { "title": "softwarecatalog", - "version": "0.1.141-unstable.20260821002227", + "version": "0.1.141-unstable.20260821004639", "description": "Software Catalog", "license": { "name": "agpl" From de3924fea6bdf8e47cb45eaf8eea6216e3ea81d0 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Fri, 21 Aug 2026 01:02:04 +0000 Subject: [PATCH 18/70] chore(release): 0.1.141-unstable.20260821010037 [skip ci] --- appinfo/info.xml | 2 +- openapi.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/appinfo/info.xml b/appinfo/info.xml index 5a395542..8d6115ab 100644 --- a/appinfo/info.xml +++ b/appinfo/info.xml @@ -50,7 +50,7 @@ Vrij en open source onder de EUPL-licentie. **Ondersteuning:** Voor ondersteuning, neem contact op via support@conduction.nl. Voor een Service Level Agreement (SLA), neem contact op via sales@conduction.nl. ]]> - 0.1.141-unstable.20260821004639 + 0.1.141-unstable.20260821010037 EUPL-1.2 Conduction SoftwareCatalog diff --git a/openapi.json b/openapi.json index 7e91d294..d56aa8be 100644 --- a/openapi.json +++ b/openapi.json @@ -2,7 +2,7 @@ "openapi": "3.0.3", "info": { "title": "softwarecatalog", - "version": "0.1.141-unstable.20260821004639", + "version": "0.1.141-unstable.20260821010037", "description": "Software Catalog", "license": { "name": "agpl" From 228c60b97f209f501a3cbf91dca01f18cad10e90 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Fri, 21 Aug 2026 01:16:06 +0000 Subject: [PATCH 19/70] chore(release): 0.1.141-unstable.20260821011425 [skip ci] --- appinfo/info.xml | 2 +- openapi.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/appinfo/info.xml b/appinfo/info.xml index 8d6115ab..9e21c1f2 100644 --- a/appinfo/info.xml +++ b/appinfo/info.xml @@ -50,7 +50,7 @@ Vrij en open source onder de EUPL-licentie. **Ondersteuning:** Voor ondersteuning, neem contact op via support@conduction.nl. Voor een Service Level Agreement (SLA), neem contact op via sales@conduction.nl. ]]> - 0.1.141-unstable.20260821010037 + 0.1.141-unstable.20260821011425 EUPL-1.2 Conduction SoftwareCatalog diff --git a/openapi.json b/openapi.json index d56aa8be..3b3b716c 100644 --- a/openapi.json +++ b/openapi.json @@ -2,7 +2,7 @@ "openapi": "3.0.3", "info": { "title": "softwarecatalog", - "version": "0.1.141-unstable.20260821010037", + "version": "0.1.141-unstable.20260821011425", "description": "Software Catalog", "license": { "name": "agpl" From 003cdf5134293b41b0903fc02db93c4d3b968976 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Fri, 21 Aug 2026 01:29:33 +0000 Subject: [PATCH 20/70] chore(release): 0.1.141-unstable.20260821012736 [skip ci] --- appinfo/info.xml | 2 +- openapi.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/appinfo/info.xml b/appinfo/info.xml index 9e21c1f2..046458c2 100644 --- a/appinfo/info.xml +++ b/appinfo/info.xml @@ -50,7 +50,7 @@ Vrij en open source onder de EUPL-licentie. **Ondersteuning:** Voor ondersteuning, neem contact op via support@conduction.nl. Voor een Service Level Agreement (SLA), neem contact op via sales@conduction.nl. ]]> - 0.1.141-unstable.20260821011425 + 0.1.141-unstable.20260821012736 EUPL-1.2 Conduction SoftwareCatalog diff --git a/openapi.json b/openapi.json index 3b3b716c..c76e9f39 100644 --- a/openapi.json +++ b/openapi.json @@ -2,7 +2,7 @@ "openapi": "3.0.3", "info": { "title": "softwarecatalog", - "version": "0.1.141-unstable.20260821011425", + "version": "0.1.141-unstable.20260821012736", "description": "Software Catalog", "license": { "name": "agpl" From 231bfe22d09d154e2daca6f41c92f8c17bf5cb80 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Fri, 21 Aug 2026 01:47:13 +0000 Subject: [PATCH 21/70] chore(release): 0.1.141-unstable.20260821014525 [skip ci] --- appinfo/info.xml | 2 +- openapi.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/appinfo/info.xml b/appinfo/info.xml index 046458c2..a8c5f3de 100644 --- a/appinfo/info.xml +++ b/appinfo/info.xml @@ -50,7 +50,7 @@ Vrij en open source onder de EUPL-licentie. **Ondersteuning:** Voor ondersteuning, neem contact op via support@conduction.nl. Voor een Service Level Agreement (SLA), neem contact op via sales@conduction.nl. ]]> - 0.1.141-unstable.20260821012736 + 0.1.141-unstable.20260821014525 EUPL-1.2 Conduction SoftwareCatalog diff --git a/openapi.json b/openapi.json index c76e9f39..0788bb27 100644 --- a/openapi.json +++ b/openapi.json @@ -2,7 +2,7 @@ "openapi": "3.0.3", "info": { "title": "softwarecatalog", - "version": "0.1.141-unstable.20260821012736", + "version": "0.1.141-unstable.20260821014525", "description": "Software Catalog", "license": { "name": "agpl" From 249246c43140501a1cd1ab3284a80760e686f3e0 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Fri, 21 Aug 2026 01:56:23 +0000 Subject: [PATCH 22/70] chore(release): 0.1.141-unstable.20260821015458 [skip ci] --- appinfo/info.xml | 2 +- openapi.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/appinfo/info.xml b/appinfo/info.xml index a8c5f3de..6cf92c11 100644 --- a/appinfo/info.xml +++ b/appinfo/info.xml @@ -50,7 +50,7 @@ Vrij en open source onder de EUPL-licentie. **Ondersteuning:** Voor ondersteuning, neem contact op via support@conduction.nl. Voor een Service Level Agreement (SLA), neem contact op via sales@conduction.nl. ]]> - 0.1.141-unstable.20260821014525 + 0.1.141-unstable.20260821015458 EUPL-1.2 Conduction SoftwareCatalog diff --git a/openapi.json b/openapi.json index 0788bb27..efe67a63 100644 --- a/openapi.json +++ b/openapi.json @@ -2,7 +2,7 @@ "openapi": "3.0.3", "info": { "title": "softwarecatalog", - "version": "0.1.141-unstable.20260821014525", + "version": "0.1.141-unstable.20260821015458", "description": "Software Catalog", "license": { "name": "agpl" From c33308f3debd83309f634f6e751aa4abb1948543 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Fri, 21 Aug 2026 02:05:10 +0000 Subject: [PATCH 23/70] chore(release): 0.1.141-unstable.20260821020326 [skip ci] --- appinfo/info.xml | 2 +- openapi.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/appinfo/info.xml b/appinfo/info.xml index 6cf92c11..83a3f2bd 100644 --- a/appinfo/info.xml +++ b/appinfo/info.xml @@ -50,7 +50,7 @@ Vrij en open source onder de EUPL-licentie. **Ondersteuning:** Voor ondersteuning, neem contact op via support@conduction.nl. Voor een Service Level Agreement (SLA), neem contact op via sales@conduction.nl. ]]> - 0.1.141-unstable.20260821015458 + 0.1.141-unstable.20260821020326 EUPL-1.2 Conduction SoftwareCatalog diff --git a/openapi.json b/openapi.json index efe67a63..14b1e210 100644 --- a/openapi.json +++ b/openapi.json @@ -2,7 +2,7 @@ "openapi": "3.0.3", "info": { "title": "softwarecatalog", - "version": "0.1.141-unstable.20260821015458", + "version": "0.1.141-unstable.20260821020326", "description": "Software Catalog", "license": { "name": "agpl" From 340ed2acd58c28b5a93897afb97180abb4b0ec56 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Fri, 21 Aug 2026 02:24:32 +0000 Subject: [PATCH 24/70] chore(release): 0.1.141-unstable.20260821022249 [skip ci] --- appinfo/info.xml | 2 +- openapi.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/appinfo/info.xml b/appinfo/info.xml index 83a3f2bd..d608a76e 100644 --- a/appinfo/info.xml +++ b/appinfo/info.xml @@ -50,7 +50,7 @@ Vrij en open source onder de EUPL-licentie. **Ondersteuning:** Voor ondersteuning, neem contact op via support@conduction.nl. Voor een Service Level Agreement (SLA), neem contact op via sales@conduction.nl. ]]> - 0.1.141-unstable.20260821020326 + 0.1.141-unstable.20260821022249 EUPL-1.2 Conduction SoftwareCatalog diff --git a/openapi.json b/openapi.json index 14b1e210..87990c48 100644 --- a/openapi.json +++ b/openapi.json @@ -2,7 +2,7 @@ "openapi": "3.0.3", "info": { "title": "softwarecatalog", - "version": "0.1.141-unstable.20260821020326", + "version": "0.1.141-unstable.20260821022249", "description": "Software Catalog", "license": { "name": "agpl" From 7c737b5149b4995efc734c3bf6392a61fc9d7ab2 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Fri, 21 Aug 2026 02:44:02 +0000 Subject: [PATCH 25/70] chore(release): 0.1.141-unstable.20260821024223 [skip ci] --- appinfo/info.xml | 2 +- openapi.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/appinfo/info.xml b/appinfo/info.xml index d608a76e..ce870203 100644 --- a/appinfo/info.xml +++ b/appinfo/info.xml @@ -50,7 +50,7 @@ Vrij en open source onder de EUPL-licentie. **Ondersteuning:** Voor ondersteuning, neem contact op via support@conduction.nl. Voor een Service Level Agreement (SLA), neem contact op via sales@conduction.nl. ]]> - 0.1.141-unstable.20260821022249 + 0.1.141-unstable.20260821024223 EUPL-1.2 Conduction SoftwareCatalog diff --git a/openapi.json b/openapi.json index 87990c48..08120140 100644 --- a/openapi.json +++ b/openapi.json @@ -2,7 +2,7 @@ "openapi": "3.0.3", "info": { "title": "softwarecatalog", - "version": "0.1.141-unstable.20260821022249", + "version": "0.1.141-unstable.20260821024223", "description": "Software Catalog", "license": { "name": "agpl" From 6933bbb93d3229e1a306cea5fcfd8f3fb0eda712 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Fri, 21 Aug 2026 02:54:59 +0000 Subject: [PATCH 26/70] chore(release): 0.1.141-unstable.20260821025313 [skip ci] --- appinfo/info.xml | 2 +- openapi.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/appinfo/info.xml b/appinfo/info.xml index ce870203..6453474f 100644 --- a/appinfo/info.xml +++ b/appinfo/info.xml @@ -50,7 +50,7 @@ Vrij en open source onder de EUPL-licentie. **Ondersteuning:** Voor ondersteuning, neem contact op via support@conduction.nl. Voor een Service Level Agreement (SLA), neem contact op via sales@conduction.nl. ]]> - 0.1.141-unstable.20260821024223 + 0.1.141-unstable.20260821025313 EUPL-1.2 Conduction SoftwareCatalog diff --git a/openapi.json b/openapi.json index 08120140..b6664db8 100644 --- a/openapi.json +++ b/openapi.json @@ -2,7 +2,7 @@ "openapi": "3.0.3", "info": { "title": "softwarecatalog", - "version": "0.1.141-unstable.20260821024223", + "version": "0.1.141-unstable.20260821025313", "description": "Software Catalog", "license": { "name": "agpl" From 8569683d666f71a9a500ac69edf6fb340ad34228 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Fri, 21 Aug 2026 03:06:21 +0000 Subject: [PATCH 27/70] chore(release): 0.1.141-unstable.20260821030426 [skip ci] --- appinfo/info.xml | 2 +- openapi.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/appinfo/info.xml b/appinfo/info.xml index 6453474f..dd0d0cad 100644 --- a/appinfo/info.xml +++ b/appinfo/info.xml @@ -50,7 +50,7 @@ Vrij en open source onder de EUPL-licentie. **Ondersteuning:** Voor ondersteuning, neem contact op via support@conduction.nl. Voor een Service Level Agreement (SLA), neem contact op via sales@conduction.nl. ]]> - 0.1.141-unstable.20260821025313 + 0.1.141-unstable.20260821030426 EUPL-1.2 Conduction SoftwareCatalog diff --git a/openapi.json b/openapi.json index b6664db8..b32c8fdf 100644 --- a/openapi.json +++ b/openapi.json @@ -2,7 +2,7 @@ "openapi": "3.0.3", "info": { "title": "softwarecatalog", - "version": "0.1.141-unstable.20260821025313", + "version": "0.1.141-unstable.20260821030426", "description": "Software Catalog", "license": { "name": "agpl" From 22f0241c13d49e42964480165ec7479275c1bc3d Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Fri, 21 Aug 2026 03:19:08 +0000 Subject: [PATCH 28/70] chore(release): 0.1.141-unstable.20260821031723 [skip ci] --- appinfo/info.xml | 2 +- openapi.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/appinfo/info.xml b/appinfo/info.xml index dd0d0cad..d7514931 100644 --- a/appinfo/info.xml +++ b/appinfo/info.xml @@ -50,7 +50,7 @@ Vrij en open source onder de EUPL-licentie. **Ondersteuning:** Voor ondersteuning, neem contact op via support@conduction.nl. Voor een Service Level Agreement (SLA), neem contact op via sales@conduction.nl. ]]> - 0.1.141-unstable.20260821030426 + 0.1.141-unstable.20260821031723 EUPL-1.2 Conduction SoftwareCatalog diff --git a/openapi.json b/openapi.json index b32c8fdf..c1ea6576 100644 --- a/openapi.json +++ b/openapi.json @@ -2,7 +2,7 @@ "openapi": "3.0.3", "info": { "title": "softwarecatalog", - "version": "0.1.141-unstable.20260821030426", + "version": "0.1.141-unstable.20260821031723", "description": "Software Catalog", "license": { "name": "agpl" From 91062c29ccd25751cbfc614cda769f4c8d6bcd8c Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Fri, 21 Aug 2026 03:32:26 +0000 Subject: [PATCH 29/70] chore(release): 0.1.141-unstable.20260821033058 [skip ci] --- appinfo/info.xml | 2 +- openapi.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/appinfo/info.xml b/appinfo/info.xml index d7514931..e5be6c05 100644 --- a/appinfo/info.xml +++ b/appinfo/info.xml @@ -50,7 +50,7 @@ Vrij en open source onder de EUPL-licentie. **Ondersteuning:** Voor ondersteuning, neem contact op via support@conduction.nl. Voor een Service Level Agreement (SLA), neem contact op via sales@conduction.nl. ]]> - 0.1.141-unstable.20260821031723 + 0.1.141-unstable.20260821033058 EUPL-1.2 Conduction SoftwareCatalog diff --git a/openapi.json b/openapi.json index c1ea6576..19fb3fdf 100644 --- a/openapi.json +++ b/openapi.json @@ -2,7 +2,7 @@ "openapi": "3.0.3", "info": { "title": "softwarecatalog", - "version": "0.1.141-unstable.20260821031723", + "version": "0.1.141-unstable.20260821033058", "description": "Software Catalog", "license": { "name": "agpl" From c69f4ee4d0c06e89bc63bcacb3697c703041c010 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Fri, 21 Aug 2026 03:44:04 +0000 Subject: [PATCH 30/70] chore(release): 0.1.141-unstable.20260821034217 [skip ci] --- appinfo/info.xml | 2 +- openapi.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/appinfo/info.xml b/appinfo/info.xml index e5be6c05..55c06172 100644 --- a/appinfo/info.xml +++ b/appinfo/info.xml @@ -50,7 +50,7 @@ Vrij en open source onder de EUPL-licentie. **Ondersteuning:** Voor ondersteuning, neem contact op via support@conduction.nl. Voor een Service Level Agreement (SLA), neem contact op via sales@conduction.nl. ]]> - 0.1.141-unstable.20260821033058 + 0.1.141-unstable.20260821034217 EUPL-1.2 Conduction SoftwareCatalog diff --git a/openapi.json b/openapi.json index 19fb3fdf..e7d23dc0 100644 --- a/openapi.json +++ b/openapi.json @@ -2,7 +2,7 @@ "openapi": "3.0.3", "info": { "title": "softwarecatalog", - "version": "0.1.141-unstable.20260821033058", + "version": "0.1.141-unstable.20260821034217", "description": "Software Catalog", "license": { "name": "agpl" From 692b859c37058c5074280b335fa87389d65abedf Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Fri, 21 Aug 2026 03:55:25 +0000 Subject: [PATCH 31/70] chore(release): 0.1.141-unstable.20260821035333 [skip ci] --- appinfo/info.xml | 2 +- openapi.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/appinfo/info.xml b/appinfo/info.xml index 55c06172..5bf435cb 100644 --- a/appinfo/info.xml +++ b/appinfo/info.xml @@ -50,7 +50,7 @@ Vrij en open source onder de EUPL-licentie. **Ondersteuning:** Voor ondersteuning, neem contact op via support@conduction.nl. Voor een Service Level Agreement (SLA), neem contact op via sales@conduction.nl. ]]> - 0.1.141-unstable.20260821034217 + 0.1.141-unstable.20260821035333 EUPL-1.2 Conduction SoftwareCatalog diff --git a/openapi.json b/openapi.json index e7d23dc0..370b3fa6 100644 --- a/openapi.json +++ b/openapi.json @@ -2,7 +2,7 @@ "openapi": "3.0.3", "info": { "title": "softwarecatalog", - "version": "0.1.141-unstable.20260821034217", + "version": "0.1.141-unstable.20260821035333", "description": "Software Catalog", "license": { "name": "agpl" From c25af32fab3716eb46aa36d90cf889be4380aa27 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Fri, 21 Aug 2026 04:06:55 +0000 Subject: [PATCH 32/70] chore(release): 0.1.141-unstable.20260821040505 [skip ci] --- appinfo/info.xml | 2 +- openapi.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/appinfo/info.xml b/appinfo/info.xml index 5bf435cb..b7131462 100644 --- a/appinfo/info.xml +++ b/appinfo/info.xml @@ -50,7 +50,7 @@ Vrij en open source onder de EUPL-licentie. **Ondersteuning:** Voor ondersteuning, neem contact op via support@conduction.nl. Voor een Service Level Agreement (SLA), neem contact op via sales@conduction.nl. ]]> - 0.1.141-unstable.20260821035333 + 0.1.141-unstable.20260821040505 EUPL-1.2 Conduction SoftwareCatalog diff --git a/openapi.json b/openapi.json index 370b3fa6..c35c629c 100644 --- a/openapi.json +++ b/openapi.json @@ -2,7 +2,7 @@ "openapi": "3.0.3", "info": { "title": "softwarecatalog", - "version": "0.1.141-unstable.20260821035333", + "version": "0.1.141-unstable.20260821040505", "description": "Software Catalog", "license": { "name": "agpl" From 5c616934de3fb269401fcc231623d2018fba71d6 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Fri, 21 Aug 2026 04:18:41 +0000 Subject: [PATCH 33/70] chore(release): 0.1.141-unstable.20260821041656 [skip ci] --- appinfo/info.xml | 2 +- openapi.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/appinfo/info.xml b/appinfo/info.xml index b7131462..1b61c57a 100644 --- a/appinfo/info.xml +++ b/appinfo/info.xml @@ -50,7 +50,7 @@ Vrij en open source onder de EUPL-licentie. **Ondersteuning:** Voor ondersteuning, neem contact op via support@conduction.nl. Voor een Service Level Agreement (SLA), neem contact op via sales@conduction.nl. ]]> - 0.1.141-unstable.20260821040505 + 0.1.141-unstable.20260821041656 EUPL-1.2 Conduction SoftwareCatalog diff --git a/openapi.json b/openapi.json index c35c629c..88eba791 100644 --- a/openapi.json +++ b/openapi.json @@ -2,7 +2,7 @@ "openapi": "3.0.3", "info": { "title": "softwarecatalog", - "version": "0.1.141-unstable.20260821040505", + "version": "0.1.141-unstable.20260821041656", "description": "Software Catalog", "license": { "name": "agpl" From 4ca2fed33f77ced8c03694c1b95a9ec2d4cfcea0 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Fri, 21 Aug 2026 04:30:29 +0000 Subject: [PATCH 34/70] chore(release): 0.1.141-unstable.20260821042832 [skip ci] --- appinfo/info.xml | 2 +- openapi.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/appinfo/info.xml b/appinfo/info.xml index 1b61c57a..75dd50d9 100644 --- a/appinfo/info.xml +++ b/appinfo/info.xml @@ -50,7 +50,7 @@ Vrij en open source onder de EUPL-licentie. **Ondersteuning:** Voor ondersteuning, neem contact op via support@conduction.nl. Voor een Service Level Agreement (SLA), neem contact op via sales@conduction.nl. ]]> - 0.1.141-unstable.20260821041656 + 0.1.141-unstable.20260821042832 EUPL-1.2 Conduction SoftwareCatalog diff --git a/openapi.json b/openapi.json index 88eba791..4fe5a05d 100644 --- a/openapi.json +++ b/openapi.json @@ -2,7 +2,7 @@ "openapi": "3.0.3", "info": { "title": "softwarecatalog", - "version": "0.1.141-unstable.20260821041656", + "version": "0.1.141-unstable.20260821042832", "description": "Software Catalog", "license": { "name": "agpl" From 24a0095679b3144de4ddf2a214f7761f3dadd5ec Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Fri, 21 Aug 2026 04:47:34 +0000 Subject: [PATCH 35/70] chore(release): 0.1.141-unstable.20260821044543 [skip ci] --- appinfo/info.xml | 2 +- openapi.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/appinfo/info.xml b/appinfo/info.xml index 75dd50d9..82adbd02 100644 --- a/appinfo/info.xml +++ b/appinfo/info.xml @@ -50,7 +50,7 @@ Vrij en open source onder de EUPL-licentie. **Ondersteuning:** Voor ondersteuning, neem contact op via support@conduction.nl. Voor een Service Level Agreement (SLA), neem contact op via sales@conduction.nl. ]]> - 0.1.141-unstable.20260821042832 + 0.1.141-unstable.20260821044543 EUPL-1.2 Conduction SoftwareCatalog diff --git a/openapi.json b/openapi.json index 4fe5a05d..6ca5090a 100644 --- a/openapi.json +++ b/openapi.json @@ -2,7 +2,7 @@ "openapi": "3.0.3", "info": { "title": "softwarecatalog", - "version": "0.1.141-unstable.20260821042832", + "version": "0.1.141-unstable.20260821044543", "description": "Software Catalog", "license": { "name": "agpl" From 29f588e4bc4bab49cdce8729db894ef0910f03a5 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Fri, 21 Aug 2026 05:09:34 +0000 Subject: [PATCH 36/70] chore(release): 0.1.141-unstable.20260821050743 [skip ci] --- appinfo/info.xml | 2 +- openapi.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/appinfo/info.xml b/appinfo/info.xml index 82adbd02..2db8dba5 100644 --- a/appinfo/info.xml +++ b/appinfo/info.xml @@ -50,7 +50,7 @@ Vrij en open source onder de EUPL-licentie. **Ondersteuning:** Voor ondersteuning, neem contact op via support@conduction.nl. Voor een Service Level Agreement (SLA), neem contact op via sales@conduction.nl. ]]> - 0.1.141-unstable.20260821044543 + 0.1.141-unstable.20260821050743 EUPL-1.2 Conduction SoftwareCatalog diff --git a/openapi.json b/openapi.json index 6ca5090a..31566bb6 100644 --- a/openapi.json +++ b/openapi.json @@ -2,7 +2,7 @@ "openapi": "3.0.3", "info": { "title": "softwarecatalog", - "version": "0.1.141-unstable.20260821044543", + "version": "0.1.141-unstable.20260821050743", "description": "Software Catalog", "license": { "name": "agpl" From 1c0b9cb376ada8c1a09285e19ef9fd2daebd0d6c Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Fri, 21 Aug 2026 05:26:08 +0000 Subject: [PATCH 37/70] chore(release): 0.1.141-unstable.20260821052417 [skip ci] --- appinfo/info.xml | 2 +- openapi.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/appinfo/info.xml b/appinfo/info.xml index 2db8dba5..70c3d5ef 100644 --- a/appinfo/info.xml +++ b/appinfo/info.xml @@ -50,7 +50,7 @@ Vrij en open source onder de EUPL-licentie. **Ondersteuning:** Voor ondersteuning, neem contact op via support@conduction.nl. Voor een Service Level Agreement (SLA), neem contact op via sales@conduction.nl. ]]> - 0.1.141-unstable.20260821050743 + 0.1.141-unstable.20260821052417 EUPL-1.2 Conduction SoftwareCatalog diff --git a/openapi.json b/openapi.json index 31566bb6..9ec9d64e 100644 --- a/openapi.json +++ b/openapi.json @@ -2,7 +2,7 @@ "openapi": "3.0.3", "info": { "title": "softwarecatalog", - "version": "0.1.141-unstable.20260821050743", + "version": "0.1.141-unstable.20260821052417", "description": "Software Catalog", "license": { "name": "agpl" From 08ed7cf3bbb202a2d9e26fcc3f6462abd401afcd Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Fri, 21 Aug 2026 05:38:52 +0000 Subject: [PATCH 38/70] chore(release): 0.1.141-unstable.20260821053703 [skip ci] --- appinfo/info.xml | 2 +- openapi.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/appinfo/info.xml b/appinfo/info.xml index 70c3d5ef..b6305aa2 100644 --- a/appinfo/info.xml +++ b/appinfo/info.xml @@ -50,7 +50,7 @@ Vrij en open source onder de EUPL-licentie. **Ondersteuning:** Voor ondersteuning, neem contact op via support@conduction.nl. Voor een Service Level Agreement (SLA), neem contact op via sales@conduction.nl. ]]> - 0.1.141-unstable.20260821052417 + 0.1.141-unstable.20260821053703 EUPL-1.2 Conduction SoftwareCatalog diff --git a/openapi.json b/openapi.json index 9ec9d64e..32f1ef47 100644 --- a/openapi.json +++ b/openapi.json @@ -2,7 +2,7 @@ "openapi": "3.0.3", "info": { "title": "softwarecatalog", - "version": "0.1.141-unstable.20260821052417", + "version": "0.1.141-unstable.20260821053703", "description": "Software Catalog", "license": { "name": "agpl" From bcece50a890e103efe01057c8cacaaaef731dfb8 Mon Sep 17 00:00:00 2001 From: Ruben van der Linde Date: Fri, 21 Aug 2026 14:16:39 +0200 Subject: [PATCH 39/70] chore(deps): refresh the shared Conduction locks (#692) hydra-gates v1.8.1 -> v1.8.2 nc-vue 2.8.2 -> 2.9.2 Lock-only: both packages are already declared with caret ranges that permit these versions, so nothing about what this app ACCEPTS changes - only what it currently resolves to. Opened by the weekly fleet shared-dependency bump, because a lock nobody re-resolves is a pin nobody chose. Merging is gated by this repository's own suite, deliberately: taking hydra-gates v1.8.1 added patchObject() to a published interface, which is a load-time fatal for any concrete double that implements it without the method. CI is the only thing that can tell a safe bump from that. Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> --- composer.lock | 12 ++++++------ package-lock.json | 6 +++--- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/composer.lock b/composer.lock index e5b93042..d38884fd 100644 --- a/composer.lock +++ b/composer.lock @@ -2792,16 +2792,16 @@ }, { "name": "conduction/hydra-gates", - "version": "v1.8.1", + "version": "v1.8.2", "source": { "type": "git", "url": "https://github.com/ConductionNL/.github.git", - "reference": "8e0e9857e54d6c680e157939e78a468e58d3751a" + "reference": "3dfcd1e56d27bd06eaa98a9a66e377e7e14fe491" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/ConductionNL/.github/zipball/8e0e9857e54d6c680e157939e78a468e58d3751a", - "reference": "8e0e9857e54d6c680e157939e78a468e58d3751a", + "url": "https://api.github.com/repos/ConductionNL/.github/zipball/3dfcd1e56d27bd06eaa98a9a66e377e7e14fe491", + "reference": "3dfcd1e56d27bd06eaa98a9a66e377e7e14fe491", "shasum": "" }, "require": { @@ -2845,9 +2845,9 @@ "support": { "docs": "https://github.com/ConductionNL/.github/blob/main/hydra-gates/README.md", "issues": "https://github.com/ConductionNL/.github/issues", - "source": "https://github.com/ConductionNL/.github/tree/v1.8.1" + "source": "https://github.com/ConductionNL/.github/tree/v1.8.2" }, - "time": "2026-08-20T05:07:22+00:00" + "time": "2026-08-20T09:37:12+00:00" }, { "name": "consolidation/annotated-command", diff --git a/package-lock.json b/package-lock.json index 02e8c784..53dc9131 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2192,9 +2192,9 @@ } }, "node_modules/@conduction/nextcloud-vue": { - "version": "2.8.2", - "resolved": "https://registry.npmjs.org/@conduction/nextcloud-vue/-/nextcloud-vue-2.8.2.tgz", - "integrity": "sha512-kqzqQ2uFyzpHUL6VxzNsfJ5iDOsy/S1iQ9UVn7W0w3CdlVb4RxWz5AFym/onB1eKewF2WtF+9vzBM5CHWsaHTQ==", + "version": "2.9.2", + "resolved": "https://registry.npmjs.org/@conduction/nextcloud-vue/-/nextcloud-vue-2.9.2.tgz", + "integrity": "sha512-79AFgzsNiTU9ltg4bEquumR0CFm7b4ed/jW5qEncPPSPWO82HEDwIIe0zm6x38oOegE2ESADiRDn02TW/qXeIQ==", "license": "EUPL-1.2", "dependencies": { "@ckpack/vue-color": "^1.6.0", From 3f6beb9ec173ef4c5cabe5e4052c17047225e29e Mon Sep 17 00:00:00 2001 From: Ruben van der Linde Date: Fri, 21 Aug 2026 16:02:53 +0200 Subject: [PATCH 40/70] chore(deps): refresh the shared Conduction locks (#694) hydra-gates v1.8.2 -> v1.8.2 nc-vue 2.9.2 -> 2.10.1 Lock-only: both packages are already declared with caret ranges that permit these versions, so nothing about what this app ACCEPTS changes - only what it currently resolves to. Opened by the weekly fleet shared-dependency bump, because a lock nobody re-resolves is a pin nobody chose. Merging is gated by this repository's own suite, deliberately: taking hydra-gates v1.8.1 added patchObject() to a published interface, which is a load-time fatal for any concrete double that implements it without the method. CI is the only thing that can tell a safe bump from that. Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> --- package-lock.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index 53dc9131..4a7dfbb4 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2192,9 +2192,9 @@ } }, "node_modules/@conduction/nextcloud-vue": { - "version": "2.9.2", - "resolved": "https://registry.npmjs.org/@conduction/nextcloud-vue/-/nextcloud-vue-2.9.2.tgz", - "integrity": "sha512-79AFgzsNiTU9ltg4bEquumR0CFm7b4ed/jW5qEncPPSPWO82HEDwIIe0zm6x38oOegE2ESADiRDn02TW/qXeIQ==", + "version": "2.10.1", + "resolved": "https://registry.npmjs.org/@conduction/nextcloud-vue/-/nextcloud-vue-2.10.1.tgz", + "integrity": "sha512-4S2X+Bv6mGzQMfxZW8XJheJ8iFis+iJdrl3+hlchYl50qQ77TDWy2xsp9Dog+ggfOikfngzmJseF5kz2MHZt4A==", "license": "EUPL-1.2", "dependencies": { "@ckpack/vue-color": "^1.6.0", From 646c301763d94a730a16c5bd777f5e6307372264 Mon Sep 17 00:00:00 2001 From: Ruben van der Linde Date: Sat, 22 Aug 2026 02:16:44 +0200 Subject: [PATCH 41/70] =?UTF-8?q?fix(tests):=20opt=20into=20the=20OpenRegi?= =?UTF-8?q?ster=20contract=20=E2=80=94=20fixes=20130=20standalone=20unit?= =?UTF-8?q?=20errors=20(#696)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Prepares this app for ConductionNL/.github#531, which drops `OCA\OpenRegister\Contract\` from conduction/hydra-gates' RUNTIME psr-4 autoload. That prefix is LONGER than openregister's own `OCA\OpenRegister\` -> `lib/`, and PSR-4 is longest-prefix-wins, so whichever app's autoloader registers first defines OpenRegister's contract for the whole process. IT ALSO FIXES A LIVE PROBLEM HERE, WHICH THE OTHER APPS IN THIS SWEEP DID NOT HAVE. tests/bootstrap-unit.php registers its OpenRegister stubs through a manual spl_autoload_register prefix map covering `OCA\OpenRegister\Db\` and `...\Service\`. Neither covers `...\Contract\`, so the standalone unit suite was already failing on it: before Tests: 715, Assertions: 2573, Errors: 131, Failures: 1, Skipped: 25 after Tests: 715, Assertions: 2993, Errors: 1, Failures: 0, Skipped: 24 The single remaining error is unrelated — Symfony\Component\HttpFoundation\ HeaderUtils is absent from the standalone environment, reached via OCP's DownloadResponse. Added to BOTH bootstraps deliberately: phpunit.xml loads tests/bootstrap.php and phpunit-unit.xml loads tests/bootstrap-unit.php, and both reach code that needs the contract. The full phpunit.xml path cannot be measured outside a Nextcloud tree (its bootstrap fatals on `Class "OC_App" not found`), so that half is verified by CI rather than locally, and is a no-op there while the prefix still exists. interface_exists() is order-independent: it asks whether the interface is RESOLVABLE rather than who registered first. Appending a fallback autoloader does not work, because spl_autoload_register appends relative to registration order and that order across independently loaded apps is what nobody controls. Co-authored-by: Conduction Release Bot --- tests/bootstrap-unit.php | 25 +++++++++++++++++++++++++ tests/bootstrap.php | 26 ++++++++++++++++++++++++++ 2 files changed, 51 insertions(+) diff --git a/tests/bootstrap-unit.php b/tests/bootstrap-unit.php index c1939566..7001ac87 100644 --- a/tests/bootstrap-unit.php +++ b/tests/bootstrap-unit.php @@ -23,6 +23,31 @@ // Include Composer's autoloader. require_once __DIR__ . '/../vendor/autoload.php'; +// THE OpenRegister CONTRACT INTERFACES, OPTED INTO RATHER THAN AUTOLOADED. +// +// conduction/hydra-gates claims `OCA\OpenRegister\Contract\` as a RUNTIME psr-4 +// prefix, so consumers get these interfaces implicitly. That prefix is LONGER +// than openregister's own `OCA\OpenRegister\` -> `lib/`, and PSR-4 is +// longest-prefix-wins, so whichever app's autoloader registers first defines +// OpenRegister's contract for the whole process (ConductionNL/.github#531). +// +// Note the stub prefixes registered just below are `OCA\OpenRegister\Db\` and +// `...\Service\` — neither covers `...\Contract\`, so once hydra-gates stops +// declaring it nothing else in this app resolves it. +// +// interface_exists() is order-independent — it asks whether the interface is +// RESOLVABLE, not who registered first. Appending a fallback autoloader does +// NOT work: spl_autoload_register appends relative to registration order, and +// that order across independently loaded apps is what nobody controls. +foreach (['ObjectEntityInterface', 'ObjectServiceInterface'] as $contract) { + if (interface_exists('\\OCA\\OpenRegister\\Contract\\' . $contract) === false) { + $shipped = __DIR__ . '/../vendor/conduction/hydra-gates/hydra-gates/contracts/' . $contract . '.php'; + if (file_exists($shipped) === true) { + require_once $shipped; + } + } +} + // Register OCP/NCU classes from nextcloud/ocp package. // nextcloud/ocp has no autoload section in its composer.json, so we register it manually. spl_autoload_register(function (string $class): void { diff --git a/tests/bootstrap.php b/tests/bootstrap.php index 4174546e..7783ea29 100644 --- a/tests/bootstrap.php +++ b/tests/bootstrap.php @@ -23,6 +23,32 @@ // Include Composer's autoloader require_once __DIR__ . '/../vendor/autoload.php'; +// THE OpenRegister CONTRACT INTERFACES, OPTED INTO RATHER THAN AUTOLOADED. +// +// conduction/hydra-gates claims `OCA\OpenRegister\Contract\` as a RUNTIME psr-4 +// prefix, so consumers get these interfaces implicitly. That prefix is LONGER +// than openregister's own `OCA\OpenRegister\` -> `lib/`, and PSR-4 is +// longest-prefix-wins, so whichever app's autoloader registers first defines +// OpenRegister's contract for the whole process (ConductionNL/.github#531). +// +// Loaded here, immediately after the autoloader and before the OpenRegister +// stubs below, for the same reason those stubs are loaded early: what is +// declared first wins, and the contract must exist before anything implementing +// it is declared. +// +// interface_exists() is order-independent — it asks whether the interface is +// RESOLVABLE, not who registered first. Appending a fallback autoloader does +// NOT work: spl_autoload_register appends relative to registration order, and +// that order across independently loaded apps is what nobody controls. +foreach (['ObjectEntityInterface', 'ObjectServiceInterface'] as $contract) { + if (interface_exists('\\OCA\\OpenRegister\\Contract\\' . $contract) === false) { + $shipped = __DIR__ . '/../vendor/conduction/hydra-gates/hydra-gates/contracts/' . $contract . '.php'; + if (file_exists($shipped) === true) { + require_once $shipped; + } + } +} + // OpenRegister test stubs. The real OCA\OpenRegister\Db\ObjectEntity has // __call magic getters that PHPUnit cannot configure on a mock, so the unit // tests use the explicit stub in tests/Stubs/. It is loaded HERE, BEFORE From 53692a1616a85c55e33e1353989bf79022c95a02 Mon Sep 17 00:00:00 2001 From: Ruben van der Linde Date: Sat, 22 Aug 2026 02:52:11 +0200 Subject: [PATCH 42/70] =?UTF-8?q?chore(quality):=20migrate=20to=20PHPStan?= =?UTF-8?q?=202=20=E2=80=94=2035=20findings=20to=20zero,=20plus=20a=20miss?= =?UTF-8?q?ing-manager=20bug=20(#697)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * chore(quality): migrate to PHPStan 2 — 35 findings to zero, plus a real bug Bumps `phpstan/phpstan` to ^2.0 and `conduction/hydra-gates` to ^1.8.2, and clears every finding the new major surfaces. ## A missing manager was never detected ContactPersonHandler::setUserManager(): $user = $this->_userManager->get($username); $manager = $this->_userManager->get($managerUsername); if ($user === null || $manager === false) { ...warn and return... } IUserManager::get() returns `?IUser` — it signals "no such user" with NULL and never returns false. So the second half of that guard could never fire: calling setUserManager() with a manager username that does not exist skipped the warning entirely and carried on as if the manager were real. Fixed to `=== null`. PHPStan found it from the other end: inside that branch `$user !== null` was reported as always FALSE, because the only reachable way in was the first clause. ## Dead guards (28) Mostly in the ArchiMate import/export pair, which are near-copies of each other, so nearly every finding came in twos: - `self::PERFORMANCE_OPTIMIZATIONS['memory_cleanup'] !== false` and `['parallel_processing'] === true` — both are class constants set to true, so neither was ever conditional. - `$identifier !== false`, `$versionId !== false`, `$refCompId !== false`, `$amefKey !== false` — all strings; none can be false. - `is_array($sectionData) === false` — the parameter is declared `array`, so PHP rejects anything else at the call boundary first. - `isset($statistics[$sectionKey]) === false` — the branch above pins $sectionKey to a key $statistics always has. - `if ($section !== 'omschrijving')` with the comment "Skip summary section itself" — `omschrijving` is assigned to $statistics on the line AFTER the loop, so the loop can never see it. - `method_exists($this->archiMateService, '...Optimized')` — the method is declared on the class. - Several `isset() && !== null` pairs and non-nullable-entity null tests. ## One scoped ignore OrganizationSyncService's `if ($contactObject !== null)` is provably true — the code a few lines above already dereferences $contactObject unconditionally. It is left in place because the block it wraps is 243 lines: removing the `if` is a pure re-indentation of a quarter of the method, a large review-hostile diff for zero behaviour change. The comment says so and marks it for the next real edit to that method. ## Verification phpstan 0, phpcs clean, phpmd clean. PHPUnit is NOT part of this evidence: the bootstrap requires a booted Nextcloud (`Class "OC_App" not found`) and cannot run standalone. Checked that this is pre-existing by stashing every change in this commit and re-running — byte-identical failure. CI runs the suite inside the container. * style(quality): satisfy phpcs on the comments this branch added CI's phpcs step runs `--warning-severity=0`, and it failed on every one of the three PRat in this series for the same reason: comments I wrote. Two sniffs: - Squiz.Commenting.InlineComment.NotCapital — many of my new comments open with a lowercase function name ("// find() throws rather than ..."). Rephrased so the first word is a real capitalised word. - Generic.Commenting.DocComment.TagsNotGrouped — the `@param-out` tags I added were interleaved between `@param` tags, splitting the group. Moved below the last `@param`. One of those inserts had also orphaned a continuation line off the `@param` above it; that is rejoined. Where PHPStan genuinely needs a `/** @var */` inline doc-block (which Squiz.Commenting.InlineComment.DocBlock rejects), the line now carries a targeted `phpcs:ignore` naming that sniff and saying why, rather than dropping the annotation and leaving the type wrong. I should have caught this locally. I did run phpcs, but with `--report=summary | tail -3`, which prints only the timing line — so I read an empty tail as "clean" when the error count was two lines above the cut. Re-verified here with CI's exact invocation. --------- Co-authored-by: Conduction Release Bot --- composer.json | 4 +- composer.lock | 23 +++++-- .../OrganisationMembersController.php | 4 +- lib/Controller/SettingsController.php | 8 ++- lib/Service/ArchiMateExportService.php | 2 +- lib/Service/ArchiMateImportService.php | 65 +++++++++---------- lib/Service/ArchiMateService.php | 45 ++++++------- lib/Service/OrganisatieService.php | 18 ++--- lib/Service/OrganizationSyncService.php | 5 ++ lib/Service/SettingsService.php | 19 ++---- .../ContactPersonHandler.php | 6 +- .../SoftwareCatalogue/OrganizationHandler.php | 47 +++++++------- lib/Service/SoftwareCatalogueService.php | 3 +- phpstan.neon | 16 +++++ 14 files changed, 148 insertions(+), 117 deletions(-) diff --git a/composer.json b/composer.json index 289381fa..880aa7be 100644 --- a/composer.json +++ b/composer.json @@ -80,7 +80,7 @@ }, "require-dev": { "conduction/coding-standard": "^1.0", - "conduction/hydra-gates": "^1.0", + "conduction/hydra-gates": "^1.8.2", "cyclonedx/cyclonedx-php-composer": "^6.2", "edgedesign/phpqa": "^1.27", "guzzlehttp/guzzle": "^7.8", @@ -88,7 +88,7 @@ "phpcsstandards/phpcsextra": "^1.4", "phpmd/phpmd": "^2.15", "phpmetrics/phpmetrics": "^2.8", - "phpstan/phpstan": "^1.10", + "phpstan/phpstan": "^2.0", "phpunit/phpunit": "^10.5", "roave/security-advisories": "dev-latest", "squizlabs/php_codesniffer": "^3.9", diff --git a/composer.lock b/composer.lock index d38884fd..9bfbbdf7 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "b1b59434167398888c8cfc94e8cf1f7d", + "content-hash": "8ec5b262f02d9a521ddced66fe163f6a", "packages": [ { "name": "adbario/php-dot-notation", @@ -5836,15 +5836,15 @@ }, { "name": "phpstan/phpstan", - "version": "1.12.33", + "version": "2.2.8", "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan/zipball/37982d6fc7cbb746dda7773530cda557cdf119e1", - "reference": "37982d6fc7cbb746dda7773530cda557cdf119e1", + "url": "https://api.github.com/repos/phpstan/phpstan/zipball/e285254e60f33c21902efef4a926ca0987c06804", + "reference": "e285254e60f33c21902efef4a926ca0987c06804", "shasum": "" }, "require": { - "php": "^7.2|^8.0" + "php": "^7.4|^8.0" }, "conflict": { "phpstan/phpstan-shim": "*" @@ -5863,6 +5863,17 @@ "license": [ "MIT" ], + "authors": [ + { + "name": "Ondřej Mirtes" + }, + { + "name": "Markus Staab" + }, + { + "name": "Vincent Langlet" + } + ], "description": "PHPStan - PHP Static Analysis Tool", "keywords": [ "dev", @@ -5885,7 +5896,7 @@ "type": "github" } ], - "time": "2026-02-28T20:30:03+00:00" + "time": "2026-08-04T22:21:45+00:00" }, { "name": "phpunit/php-code-coverage", diff --git a/lib/Controller/OrganisationMembersController.php b/lib/Controller/OrganisationMembersController.php index 92fa3893..cc752432 100644 --- a/lib/Controller/OrganisationMembersController.php +++ b/lib/Controller/OrganisationMembersController.php @@ -261,7 +261,9 @@ private function authorizeMaintainer(string $organisationUuid): ?JSONResponse { * * @return \OCA\OpenRegister\Service\OrganisationService The service instance. * - * @throws \Throwable When OpenRegister is unavailable. + * No `@throws`: the body is a plain property read. If OpenRegister is + * unavailable the failure happens in the container while CONSTRUCTING this + * controller, not here. */ private function getOrganisationService(): \OCA\OpenRegister\Service\OrganisationService { return $this->organisationService; diff --git a/lib/Controller/SettingsController.php b/lib/Controller/SettingsController.php index e3eb3e89..5bc0f6af 100644 --- a/lib/Controller/SettingsController.php +++ b/lib/Controller/SettingsController.php @@ -264,7 +264,7 @@ public function index(): JSONResponse { try { $user = $this->userSession->getUser(); - $isAdmin = $user !== null && $this->groupManager->isAdmin($user->getUID()); + $isAdmin = $this->groupManager->isAdmin($user->getUID()); // Delegate all business logic to service. $data = $this->settingsService->getAllSettings(); @@ -1577,10 +1577,12 @@ private function parseArchiMateFileUpload(): ?array { * @spec openspec/changes/method-decomposition/tasks.md#task-3 */ private function resolveArchiMateMethod(array $options): array { + // No method_exists() probe: ArchiMateService declares + // importArchiMateFileFromPathOptimized(), so only the request parameter + // decides which path runs. $useOptimized = $this->request->getParam('useOptimized', 'true') === 'true'; - $hasOptimized = method_exists($this->archiMateService, 'importArchiMateFileFromPathOptimized'); - if ($useOptimized === true && $hasOptimized === true) { + if ($useOptimized === true) { $this->logger->info('Using OPTIMIZED ArchiMate import method.'); return $this->archiMateService->importArchiMateFileFromPathOptimized($options); } diff --git a/lib/Service/ArchiMateExportService.php b/lib/Service/ArchiMateExportService.php index a061ec4b..38d2be00 100644 --- a/lib/Service/ArchiMateExportService.php +++ b/lib/Service/ArchiMateExportService.php @@ -1209,7 +1209,7 @@ private function addObjectDirectlyToXmlWithProperties( $xmlData = $this->cleanObjectDataForXml(object: $object, propDefMap: $propertyDefinitionMap); } - if (is_array($xmlData) === true && empty($xmlData) === false) { + if (empty($xmlData) === false) { if ($sectionName === 'views') { $this->addViewDataToXmlNode(viewNode: $objectNode, viewData: $xmlData); } else { diff --git a/lib/Service/ArchiMateImportService.php b/lib/Service/ArchiMateImportService.php index c7d5fb88..d5e41407 100644 --- a/lib/Service/ArchiMateImportService.php +++ b/lib/Service/ArchiMateImportService.php @@ -368,11 +368,11 @@ public function importArchiMateFileFromPathOptimized(array $options = []): array // PERFORMANCE OPTIMIZATION: Clean up memory after XML parsing. $memoryCleanupTime = 0; - if (self::PERFORMANCE_OPTIMIZATIONS['memory_cleanup'] !== false) { - $memCleanupStart = microtime(true); - $this->cleanupMemory(); - $memoryCleanupTime = microtime(true) - $memCleanupStart; - } + // PERFORMANCE_OPTIMIZATIONS['memory_cleanup'] is a class constant set + // to true, so this was never conditional. + $memCleanupStart = microtime(true); + $this->cleanupMemory(); + $memoryCleanupTime = microtime(true) - $memCleanupStart; // STEP 2: Extract model identifier. $modelIdStartTime = microtime(true); @@ -1254,7 +1254,7 @@ private function createSectionObject(string $section, string $identifier, array // Fallback: Use AMEF identifier as both ID and extract clean UUID for slug. $objectId = $identifier; // Extract clean UUID from AMEF identifier (remove "id-" prefix if present). - if ($identifier !== false && str_starts_with($identifier, 'id-') === true) { + if (str_starts_with($identifier, 'id-') === true) { $slug = substr($identifier, 3); // Remove "id-" prefix. } else { @@ -1735,9 +1735,10 @@ private function saveObjectsInParallelBatches(array $objects, ObjectServiceInter }//end try // Memory cleanup between chunks. - if (self::PERFORMANCE_OPTIMIZATIONS['memory_cleanup'] !== false) { - $this->cleanupMemory(); - } + // PERFORMANCE_OPTIMIZATIONS['memory_cleanup'] is a class constant set + // to true, so this was never conditional. Flip the constant and the + // compiler will point you back here. + $this->cleanupMemory(); }//end foreach // Store the aggregated result for statistics calculation. @@ -2047,7 +2048,7 @@ private function getAmefRegisterId(): ?int { } // Validate and normalize to positive int. - if ($rawRegisterId !== null && $rawRegisterId !== '' && is_numeric((string)$rawRegisterId) === true) { + if ($rawRegisterId !== '' && is_numeric((string)$rawRegisterId) === true) { $registerId = (int)$rawRegisterId; if ($registerId > 0) { return $registerId; @@ -2549,10 +2550,8 @@ private function findItemsInSection(array $sectionData, string $sectionName): ar // OPTIMIZATION: Removed debug logging from section processing. $items = []; - // Safety check: ensure sectionData is an array. - if (is_array($sectionData) === false) { - return []; - } + // No is_array() safety check: $sectionData is declared array, so PHP + // rejects anything else at the call boundary before this could run. // Get section structure configuration from AMEF config. $config = $this->getSectionStructureConfig(sectionName: $sectionName); @@ -4033,7 +4032,7 @@ private function processStandardVersionRelationship( $standardId = $source; } - if ($versionId !== false && $standardId === true) { + if ($standardId === true) { $stdVersionRelMap[$versionId] = $standardId; } }//end processStandaardVersieRelationship() @@ -4084,7 +4083,7 @@ private function processRelationshipImmediate( $standardId = $source; } - if ($refCompId !== false && $standardId === true) { + if ($standardId === true) { // Initialize arrays if not exists. if (isset($gemmaRelationshipMap[$refCompId]) === false) { $gemmaRelationshipMap[$refCompId] = [ @@ -4800,7 +4799,7 @@ private function transformSectionObjectsBatch( // AMEF identifier becomes slug. } else { // Fallback: extract clean UUID from AMEF identifier for slug. - if ($identifier !== false && str_starts_with($identifier, 'id-') === true) { + if (str_starts_with($identifier, 'id-') === true) { $object['@self']['slug'] = substr($identifier, 3); // Remove "id-" prefix. } else { @@ -4809,7 +4808,7 @@ private function transformSectionObjectsBatch( } } else { // No properties to flatten, use AMEF identifier logic. - if ($identifier !== false && str_starts_with($identifier, 'id-') === true) { + if (str_starts_with($identifier, 'id-') === true) { $object['@self']['slug'] = substr($identifier, 3); // Remove "id-" prefix. } else { @@ -4988,7 +4987,9 @@ private function flattenPropertiesBatch(array &$object, array $properties, array continue; } - if ($value !== null && isset($propDefMap[$defRef]) === true) { + // No isset($propDefMap[$defRef]) re-check: the loop above only + // reaches here for a $defRef the map already has. + if ($value !== null) { $propertyName = $propDefMap[$defRef]; $camelCaseName = $this->convertToCamelCase(propertyName: $propertyName); $object[$camelCaseName] = $value; @@ -5022,13 +5023,14 @@ private function flattenPropertiesBatch(array &$object, array $properties, array ); } } else { + // 'mapping_exists' is always true here — the map lookup already + // succeeded, so a null $value is the only way into this branch. $this->logger->warning( - 'Property value is null or mapping missing', + 'Property value is null', [ 'object_id' => $object['identifier'] ?? 'unknown', 'property_def_ref' => $defRef, 'value' => $value, - 'mapping_exists' => isset($propDefMap[$defRef]) === true, ] ); }//end if @@ -5720,10 +5722,8 @@ private function calculateObjectStatistics(array $normalizedData): array { $sectionKey = 'elements'; }//end if - if (isset($statistics[$sectionKey]) === false) { - continue; - // Skip unknown section types. - } + // No "skip unknown section types" guard: the branch above pins + // $sectionKey to a key $statistics always has, so it never fired. // Determine if this object was created, updated, or had errors. $objectId = $object['@self']['id'] ?? $object['identifier'] ?? null; @@ -5809,14 +5809,13 @@ private function calculateObjectStatistics(array $normalizedData): array { 'total_errors' => 0, ]; - foreach ($statistics as $section => $sectionStats) { - if ($section !== 'omschrijving') { - // Skip summary section itself. - $summary['total_objects_created'] += $sectionStats['created']; - $summary['total_objects_updated'] += $sectionStats['updated']; - $summary['total_objects_unchanged'] += $sectionStats['unchanged']; - $summary['total_errors'] += count($sectionStats['errors']); - } + // No "skip the summary section" guard: `omschrijving` is written into + // $statistics on the line AFTER this loop, so the loop can never see it. + foreach ($statistics as $sectionStats) { + $summary['total_objects_created'] += $sectionStats['created']; + $summary['total_objects_updated'] += $sectionStats['updated']; + $summary['total_objects_unchanged'] += $sectionStats['unchanged']; + $summary['total_errors'] += count($sectionStats['errors']); } $statistics['omschrijving'] = $summary; diff --git a/lib/Service/ArchiMateService.php b/lib/Service/ArchiMateService.php index 1a123163..d9376a5d 100644 --- a/lib/Service/ArchiMateService.php +++ b/lib/Service/ArchiMateService.php @@ -642,10 +642,8 @@ private function findItemsInSection(array $sectionData, string $sectionName): ar // OPTIMIZATION: Removed debug logging from section processing. $items = []; - // Safety check: ensure sectionData is an array. - if (is_array($sectionData) === false) { - return []; - } + // No is_array() safety check: $sectionData is declared array, so PHP + // rejects anything else at the call boundary before this could run. // Get section structure configuration from AMEF config. $config = $this->getSectionStructureConfig(sectionName: $sectionName); @@ -974,7 +972,7 @@ private function createSectionObject(string $section, string $identifier, array } elseif (isset($data['Object ID']) === true) { // Check if we have "Object ID" property directly. $slug = $data['Object ID']; - } elseif ($identifier !== false && str_starts_with($identifier, 'id-') === true) { + } elseif (str_starts_with($identifier, 'id-') === true) { // Fallback: extract from identifier (remove "id-" prefix if present). $slug = substr($identifier, 3); } @@ -1027,9 +1025,9 @@ private function saveObjectsToDatabase(array $objects): array { // PERFORMANCE OPTIMIZATION: Use parallel batch processing for large datasets. $batchProcessingStartTime = microtime(true); - if (self::PERFORMANCE_OPTIMIZATIONS['parallel_processing'] === true - && count($objects) > self::PERFORMANCE_OPTIMIZATIONS['batch_size'] - ) { + // PERFORMANCE_OPTIMIZATIONS['parallel_processing'] is a class constant set + // to true, so only the batch-size threshold decides this. + if (count($objects) > self::PERFORMANCE_OPTIMIZATIONS['batch_size']) { $result = $this->saveObjectsInParallelBatches( objects: $objects, objectService: $objectService, @@ -1169,9 +1167,9 @@ private function saveObjectsInParallelBatches(array $objects, ObjectServiceInter }//end try // Memory cleanup between chunks. - if (self::PERFORMANCE_OPTIMIZATIONS['memory_cleanup'] !== false) { - $this->cleanupMemory(); - } + // PERFORMANCE_OPTIMIZATIONS['memory_cleanup'] is a class constant set + // to true, so this was never conditional. + $this->cleanupMemory(); }//end foreach // Store the aggregated result for statistics calculation. @@ -1767,7 +1765,7 @@ private function getAmefRegisterId(): ?int { } // Validate and normalize to positive int. - if ($rawRegisterId !== null && $rawRegisterId !== '' && is_numeric((string)$rawRegisterId) === true) { + if ($rawRegisterId !== '' && is_numeric((string)$rawRegisterId) === true) { $registerId = (int)$rawRegisterId; if ($registerId > 0) { return $registerId; @@ -2258,10 +2256,8 @@ private function calculateObjectStatistics(array $normalizedData): array { // Default fallback. }; - if (isset($statistics[$sectionKey]) === false) { - continue; - // Skip unknown section types. - } + // No "skip unknown section types" guard: the branch above pins + // $sectionKey to a key $statistics always has, so it never fired. // Determine if this object was created, updated, or had errors. $objectId = $object['@self']['id'] ?? $object['identifier'] ?? null; @@ -2346,14 +2342,13 @@ private function calculateObjectStatistics(array $normalizedData): array { 'total_errors' => 0, ]; - foreach ($statistics as $section => $sectionStats) { - if ($section !== 'omschrijving') { - // Skip summary section itself. - $summary['total_objects_created'] += $sectionStats['created']; - $summary['total_objects_updated'] += $sectionStats['updated']; - $summary['total_objects_skipped'] += $sectionStats['skipped']; - $summary['total_errors'] += count($sectionStats['errors']); - } + // No "skip the summary section" guard: `omschrijving` is written into + // $statistics on the line AFTER this loop, so the loop can never see it. + foreach ($statistics as $sectionStats) { + $summary['total_objects_created'] += $sectionStats['created']; + $summary['total_objects_updated'] += $sectionStats['updated']; + $summary['total_objects_skipped'] += $sectionStats['skipped']; + $summary['total_errors'] += count($sectionStats['errors']); } $statistics['omschrijving'] = $summary; @@ -3061,7 +3056,7 @@ private function processRelationshipImmediate( $standardId = $source; } - if ($refCompId !== false && $standardId === true) { + if ($standardId === true) { // Initialize arrays if not exists. if (isset($gemmaRelationshipMap[$refCompId]) === false) { $gemmaRelationshipMap[$refCompId] = [ diff --git a/lib/Service/OrganisatieService.php b/lib/Service/OrganisatieService.php index 8dc86c08..9a1029ac 100644 --- a/lib/Service/OrganisatieService.php +++ b/lib/Service/OrganisatieService.php @@ -106,15 +106,15 @@ public function createOrganisationInOpenRegister(array $objectData): ?object { organizationUuid: $organizationUuid ); - if ($organisationEntity !== null) { - $this->logger->info( - 'OrganisatieService: Successfully created organization entity', - [ - 'organizationUuid' => $organizationUuid, - 'entityId' => $organisationEntity->getId(), - ] - ); - } + // The createOrganisationEntityInternal() helper is declared non-nullable and + // throws on failure — the catch below is the real failure path. + $this->logger->info( + 'OrganisatieService: Successfully created organization entity', + [ + 'organizationUuid' => $organizationUuid, + 'entityId' => $organisationEntity->getId(), + ] + ); return $organisationEntity; } catch (\Exception $e) { diff --git a/lib/Service/OrganizationSyncService.php b/lib/Service/OrganizationSyncService.php index 9b510cfa..d598cf2b 100644 --- a/lib/Service/OrganizationSyncService.php +++ b/lib/Service/OrganizationSyncService.php @@ -1404,6 +1404,11 @@ private function processContactPerson(object $contactPerson, array &$stats): ?st private function updateOrganisationEntityUsers(object $organisationEntity, array $usernames, array &$stats): void { try { $organisationUuid = $organisationEntity->getUuid(); + // OpenRegister is not on the analysis path, so the getUsers() call has no + // resolvable return type and sort() below cannot be checked without + // this. It is a list of usernames. + // phpcs:ignore Squiz.Commenting.InlineComment.DocBlock -- PHPStan only reads @var from a /** */ block. + /** @var array $currentUsers */ $currentUsers = ($organisationEntity->getUsers() ?? []); // Add admin users to ensure they're always included. diff --git a/lib/Service/SettingsService.php b/lib/Service/SettingsService.php index b08f7d58..d354a501 100644 --- a/lib/Service/SettingsService.php +++ b/lib/Service/SettingsService.php @@ -872,7 +872,7 @@ public function getSchemaIdForObjectType(string $objectType): ?int { $amefKey = $amefKeyMap[$objectType] ?? null; - if ($amefKey !== false && isset($decodedAmefConfig[$amefKey]) === true) { + if (isset($decodedAmefConfig[$amefKey]) === true) { $schemaId = $decodedAmefConfig[$amefKey]; if (empty($schemaId) === false) { $result = (int)$schemaId; @@ -920,9 +920,7 @@ public function getSchemaIdForObjectType(string $objectType): ?int { // Only check voorzieningen config if object type exists in the key map. if ($result === null && isset($voorzieningenKeyMap[$objectType]) === true) { $voorzieningenKey = $voorzieningenKeyMap[$objectType]; - if (isset($voorzieningenConfig[$voorzieningenKey]) === true - && $voorzieningenConfig[$voorzieningenKey] !== null - ) { + if (isset($voorzieningenConfig[$voorzieningenKey]) === true) { $result = (int)$voorzieningenConfig[$voorzieningenKey]; } } @@ -5997,11 +5995,10 @@ public function getEmailConfigFocused(): array { */ public function updateEmailConfig(array $config): array { try { - if (isset($config) === true) { - $result = $this->updateEmailSettings(emailSettings: $config); - if ($result['success'] === false) { - return $result; - } + // No isset($config) guard: it is a required, non-nullable parameter. + $result = $this->updateEmailSettings(emailSettings: $config); + if ($result['success'] === false) { + return $result; } return [ @@ -6070,9 +6067,7 @@ public function updateAmefConfig(array $config): array { // Load existing config to allow merging. $existing = $this->getAmefConfig(); - if (is_array($existing) === false) { - $existing = []; - } + // No is_array() fallback: getAmefConfig() is declared to return array. // Determine target register id. if (isset($config['register']) === true) { diff --git a/lib/Service/SoftwareCatalogue/ContactPersonHandler.php b/lib/Service/SoftwareCatalogue/ContactPersonHandler.php index 095f84a1..cae6758a 100644 --- a/lib/Service/SoftwareCatalogue/ContactPersonHandler.php +++ b/lib/Service/SoftwareCatalogue/ContactPersonHandler.php @@ -1454,7 +1454,11 @@ public function setUserManager(string $username, string $managerUsername): void $user = $this->_userManager->get($username); $manager = $this->_userManager->get($managerUsername); - if ($user === null || $manager === false) { + // `$manager === null`, not `=== false`: IUserManager::get() returns + // ?IUser and signals "no such user" with null. Comparing against + // false meant a MISSING MANAGER was never detected — the guard fell + // through and the method carried on as if the manager existed. + if ($user === null || $manager === null) { $this->_logger->warning( 'Cannot set manager - user or manager not found', [ diff --git a/lib/Service/SoftwareCatalogue/OrganizationHandler.php b/lib/Service/SoftwareCatalogue/OrganizationHandler.php index 747e6a4b..25533b53 100644 --- a/lib/Service/SoftwareCatalogue/OrganizationHandler.php +++ b/lib/Service/SoftwareCatalogue/OrganizationHandler.php @@ -437,30 +437,31 @@ public function processContactpersonen(object $organizationObject): array { ); } - if ($contactgegevensObject !== null) { - $processedContacts[] = $contactgegevensObject; - - $actionLogMessage = 'Created new contactgegevens from contactpersoon'; - $actionValue = 'create'; - if ($existingContactgegevens !== null) { - $actionLogMessage = 'Updated existing contactgegevens from contactpersoon'; - $actionValue = 'update'; - } + // No null guard: saveObject() returns a non-nullable + // ObjectEntityInterface and throws on failure, which the + // catch below handles. + $processedContacts[] = $contactgegevensObject; - $this->_logger->info( - $actionLogMessage, - [ - 'organizationId' => $organizationUuid, - // UUID, not getId(): `getId()` is not on - // ObjectEntityInterface (ADR-084), and the UUID is - // the identifier every other log line here carries. - 'contactgegevensId' => $contactgegevensObject->getUuid(), - 'contactpersoonIndex' => $index, - 'email' => $contactgegevensData['email'], - 'action' => $actionValue, - ] - ); - }//end if + $actionLogMessage = 'Created new contactgegevens from contactpersoon'; + $actionValue = 'create'; + if ($existingContactgegevens !== null) { + $actionLogMessage = 'Updated existing contactgegevens from contactpersoon'; + $actionValue = 'update'; + } + + $this->_logger->info( + $actionLogMessage, + [ + 'organizationId' => $organizationUuid, + // UUID, not getId(): `getId()` is not on + // ObjectEntityInterface (ADR-084), and the UUID is + // the identifier every other log line here carries. + 'contactgegevensId' => $contactgegevensObject->getUuid(), + 'contactpersoonIndex' => $index, + 'email' => $contactgegevensData['email'], + 'action' => $actionValue, + ] + ); } catch (\Exception $e) { $this->_logger->error( 'Failed to process contactPerson: ' . $e->getMessage(), diff --git a/lib/Service/SoftwareCatalogueService.php b/lib/Service/SoftwareCatalogueService.php index 45c25c4a..307d78f2 100644 --- a/lib/Service/SoftwareCatalogueService.php +++ b/lib/Service/SoftwareCatalogueService.php @@ -1526,7 +1526,8 @@ private function createOrganisationInOpenRegisterInternal( $this->_logger->info( 'SoftwareCatalogueService: STEP 2 - Checking user context', [ - 'hasUserSession' => $userSession !== null, + // Always true: $userSession is an injected, non-nullable IUserSession. + 'hasUserSession' => true, 'currentUser' => $currentUserValue, 'isAnonymous' => $currentUser === null, ] diff --git a/phpstan.neon b/phpstan.neon index a805d1e8..ecc4397b 100644 --- a/phpstan.neon +++ b/phpstan.neon @@ -22,3 +22,19 @@ parameters: # class` errors that a bare ignore pattern cannot fix. Analysis-only; # never loaded at runtime or by PHPUnit. - tests/analysis-stubs/decidesk-events.stub.php + + ignoreErrors: + # OrganizationSyncService's `if ($contactObject !== null)` at the top of + # the contact-person loop. saveObject() returns a non-nullable + # ObjectEntityInterface, so the guard is provably true — the code a few + # lines ABOVE it already dereferences $contactObject unconditionally, + # which is the giveaway. + # + # Left in place rather than removed because the block it wraps is 243 + # lines: deleting the `if` is a pure re-indentation of a quarter of the + # method, which is a large, review-hostile diff for zero behaviour + # change. Worth doing when that method is next touched for real. + - + message: '#Strict comparison using !== between OCA\\OpenRegister\\Contract\\ObjectEntityInterface and null will always evaluate to true#' + identifier: notIdentical.alwaysTrue + path: lib/Service/OrganizationSyncService.php From fe821988a5575406f850b60f061efba306c16c59 Mon Sep 17 00:00:00 2001 From: Ruben van der Linde Date: Sat, 22 Aug 2026 10:24:58 +0200 Subject: [PATCH 43/70] chore(deps): refresh the shared Conduction locks (#706) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * chore(deps): refresh the shared Conduction locks hydra-gates v1.8.2 -> v1.9.0 nc-vue 2.10.1 -> 2.11.1 Lock-only: both packages are already declared with caret ranges that permit these versions, so nothing about what this app ACCEPTS changes - only what it currently resolves to. Opened by the weekly fleet shared-dependency bump, because a lock nobody re-resolves is a pin nobody chose. Merging is gated by this repository's own suite, deliberately: taking hydra-gates v1.8.1 added patchObject() to a published interface, which is a load-time fatal for any concrete double that implements it without the method. CI is the only thing that can tell a safe bump from that. * fix(psalm): stub OpenRegister's contract, which v1.9.0 stopped autoloading hydra-gates v1.9.0 removed `OCA\OpenRegister\Contract\` from its runtime psr-4 autoload (ConductionNL/.github#531). The removal was correct — that prefix is longer than openregister's own, so a vendored copy in ANY app defined the contract for the whole process — but it was verified against PHPUnit only. Psalm never runs the test bootstrap; it resolves types through the composer autoload map. So the guarded require added to this app's bootstrap does nothing for it, and 213 UndefinedClass errors appeared for a class the app typehints but does not own. This is the same situation as the decidesk event stubs already in this file: a sibling Nextcloud app supplies the type at runtime, so it is absent from the analysis path and gets declared here. A stub teaches the analyser the shape without putting the class back into the runtime autoloader, which is what caused the original defect. Measured in this checkout on the real v1.9.0: 213 errors before, 0 after — "No errors found!", psalm exit 0. * fix(metadata): point shipped URLs at GitHub, not the retired Codeberg host gate-94 (retired-git-host-metadata, ConductionNL/.github#546) flags the shipped URLs in this app's metadata — website, bugs, repository and screenshots — as pointing at codeberg.org. GitHub is the only host. Two things changed together, and only one of them is the host: codeberg.org/Conduction/softwarecatalog -> github.com/ConductionNL/stackiq The Codeberg URLs still carried the app's PRE-RENAME name. Swapping only the host would have produced github.com/ConductionNL/softwarecatalog, which resolves today purely because GitHub redirects a renamed repo — and stops the moment anyone creates a repo at the old path. Screenshots move to raw.githubusercontent.com rather than a github.com/raw redirect, and every one was fetched rather than assumed: HTTP 200 each. A green gate with dead image URLs would be worse than the finding it silenced. Not caused by this branch's lock bump — gate-94 landed at 02:18 UTC, after this app's last development run, so the PR runs are simply the first measured against it. Pre-existing debt, newly visible. --------- Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: Conduction Release Bot --- appinfo/info.xml | 18 +++++++++--------- composer.lock | 17 ++++++----------- package-lock.json | 6 +++--- psalm.xml | 16 ++++++++++++++++ 4 files changed, 34 insertions(+), 23 deletions(-) diff --git a/appinfo/info.xml b/appinfo/info.xml index b6305aa2..0c68b0f6 100644 --- a/appinfo/info.xml +++ b/appinfo/info.xml @@ -55,20 +55,20 @@ Vrij en open source onder de EUPL-licentie. Conduction SoftwareCatalog - https://codeberg.org/Conduction/softwarecatalog - https://codeberg.org/Conduction/softwarecatalog - https://codeberg.org/Conduction/softwarecatalog + https://github.com/ConductionNL/stackiq + https://github.com/ConductionNL/stackiq + https://github.com/ConductionNL/stackiq organization tools integration - https://codeberg.org/Conduction/softwarecatalog - https://codeberg.org/Conduction/softwarecatalog/issues - https://codeberg.org/Conduction/softwarecatalog + https://github.com/ConductionNL/stackiq + https://github.com/ConductionNL/stackiq/issues + https://github.com/ConductionNL/stackiq - https://codeberg.org/Conduction/softwarecatalog/raw/branch/main/img/screenshot-dashboard.png - https://codeberg.org/Conduction/softwarecatalog/raw/branch/main/img/screenshot-applications.png - https://codeberg.org/Conduction/softwarecatalog/raw/branch/main/img/screenshot-connections.png + https://raw.githubusercontent.com/ConductionNL/stackiq/main/img/screenshot-dashboard.png + https://raw.githubusercontent.com/ConductionNL/stackiq/main/img/screenshot-applications.png + https://raw.githubusercontent.com/ConductionNL/stackiq/main/img/screenshot-connections.png + + ](https://apps.nextcloud.com/apps/openregister) @@ -129,11 +129,11 @@ softwarecatalog/ ```bash cd /var/www/html/custom_apps -git clone https://codeberg.org/Conduction/softwarecatalog.git -cd softwarecatalog +git clone https://github.com/ConductionNL/stackiq.git +cd stackiq npm install npm run build -php occ app:enable softwarecatalog +php occ app:enable stackiq ``` ## Development @@ -147,7 +147,7 @@ docker compose -f openregister/docker-compose.yml up -d ### Frontend development ```bash -cd softwarecatalog +cd stackiq npm install npm run dev # Watch mode npm run build # Production build @@ -180,7 +180,7 @@ npm run stylelint # CSS linting ## Documentation -Full documentation is available at **[softwarecatalog.app](https://softwarecatalog.app)** +Full documentation is available at **[softwarecatalog.conduction.nl](https://softwarecatalog.conduction.nl)** | Page | Description | |------|-------------| @@ -191,11 +191,11 @@ Full documentation is available at **[softwarecatalog.app](https://softwarecatal ## Testing -Software Catalogus is tested through three complementary layers that together provide comprehensive quality assurance. +Stackiq is tested through three complementary layers that together provide comprehensive quality assurance. ### Code Quality (Conduction Quality Workflow) -Every commit runs through the [Conduction quality workflow](https://codeberg.org/Conduction/softwarecatalog/actions) — a strict CI/CD pipeline that enforces: +Every commit runs through the [Conduction quality workflow](https://github.com/ConductionNL/stackiq/actions) — a strict CI/CD pipeline that enforces: - **PHP Lint** — syntax validation - **PHPCS** — coding standards (PEAR + PSR-12 + custom Conduction rules, including forbidden functions and named parameter enforcement) @@ -263,7 +263,7 @@ The master file `issues.md` tracks all 137 IGS (In Review/Scoped) issues with th ## Required Repositories -The Softwarecatalogus is not a standalone application — it runs as a Nextcloud app backed by several other apps, with a separate React-based public frontend. +Stackiq is not a standalone application — it runs as a Nextcloud app backed by several other apps, with a separate React-based public frontend. | Repository | Role | Required | |-----------|------|----------| @@ -305,8 +305,8 @@ docker exec -u www-data nextcloud php occ app:enable opencatalogi # 3. NL Design — theming (no hard dependencies, but should be early) docker exec -u www-data nextcloud php occ app:enable nldesign -# 4. Software Catalogus — depends on OpenRegister and OpenCatalogi -docker exec -u www-data nextcloud php occ app:enable softwarecatalog +# 4. Stackiq — depends on OpenRegister and OpenCatalogi +docker exec -u www-data nextcloud php occ app:enable stackiq # 5. LaunchPad — optional, for dashboard widgets docker exec -u www-data nextcloud php occ app:enable launchpad @@ -314,7 +314,7 @@ docker exec -u www-data nextcloud php occ app:enable launchpad ### 3. Import data -The Softwarecatalogus requires register schemas and seed data to function. Import the configurations via the OpenRegister Magic Mapper: +Stackiq requires register schemas and seed data to function. Import the configurations via the OpenRegister Magic Mapper: ```bash # Import the softwarecatalogus register configuration @@ -323,13 +323,13 @@ The Softwarecatalogus requires register schemas and seed data to function. Impor curl -X POST "http://localhost:8080/index.php/apps/openregister/api/configurations?force=true" \ -u admin:admin \ -H "Content-Type: application/json" \ - -d @softwarecatalog/configurations/softwarecatalogus_register.json + -d @stackiq/lib/Settings/softwarecatalogus_register.json ``` For a complete test environment with users, organizations, and sample data: ```bash -bash softwarecatalog/test-setup.sh +bash stackiq/test-setup.sh ``` This creates 7 test users across 4 organizations (leverancier, gemeente, samenwerking, admin), seeds contact persons and sample applications, and verifies RBAC scoping. @@ -338,7 +338,7 @@ This creates 7 test users across 4 organizations (leverancier, gemeente, samenwe ```bash # Nextcloud app frontend (Vue 2) -cd softwarecatalog && npm install && npm run build +cd stackiq && npm install && npm run build # Public frontend (React) — only needed if not using Docker cd tilburg-woo-ui && yarn install && yarn build diff --git a/README_DEBUG.md b/README_DEBUG.md index 3d67bf22..1367d5d1 100644 --- a/README_DEBUG.md +++ b/README_DEBUG.md @@ -112,7 +112,7 @@ $ occ user:info test.fixed ## Key Fixes Applied ### 1. Username Generation Fix -**File**: `lib/Service/SoftwareCatalogue/ContactPersonHandler.php` +**File**: `lib/Service/Stackiq/ContactPersonHandler.php` **Issue**: `??` operator not working properly **Fix**: Explicit username assignment logic @@ -127,7 +127,7 @@ $ occ user:info test.fixed **Fix**: Changed to use correct `find()` method with register/schema context ### 4. Organization Group Assignment Fix ⭐ **KEY FIX** -**File**: `lib/Service/SoftwareCatalogue/ContactPersonHandler.php` +**File**: `lib/Service/Stackiq/ContactPersonHandler.php` **Lines**: 814, 1175 **Issue**: `$objectService->getObject($organizationId)` method doesn't exist **Fix**: @@ -144,17 +144,17 @@ $organizationObject = $objectService->find($organizationId, [], false, 6, 35); The following debug logging is currently active and should be removed after successful acceptance testing: ### 1. ContactPersonHandler Debug Logs -**File**: `lib/Service/SoftwareCatalogue/ContactPersonHandler.php` +**File**: `lib/Service/Stackiq/ContactPersonHandler.php` **Lines**: ~470-490, ~800-850 **Purpose**: Track user group assignment and organization lookup ### 2. OrganizationHandler Debug Logs -**File**: `lib/Service/SoftwareCatalogue/OrganizationHandler.php` +**File**: `lib/Service/Stackiq/OrganizationHandler.php` **Lines**: Various **Purpose**: Track contactgegevens creation process ### 3. EventListener Debug Logs -**File**: `lib/EventListener/SoftwareCatalogEventListener.php` +**File**: `lib/EventListener/StackiqEventListener.php` **Lines**: Various **Purpose**: Track event processing flow diff --git a/SECURITY.md b/SECURITY.md index 040d94c4..0c0df29b 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -46,16 +46,16 @@ For every app `` under [ConductionNL](https://github.com/ConductionNL), two | **Always-latest released SBOM** (auto-redirects to newest release) | `https://github.com/ConductionNL//releases/latest/download/sbom.cdx.json` | | **Specific release SBOM** (pinned, for compliance archives) | `https://github.com/ConductionNL//releases/download//sbom.cdx.json` | -Example — fetch the latest softwarecatalog SBOM: +Example — fetch the latest stackiq SBOM: ```bash -curl -sL https://github.com/ConductionNL/softwarecatalog/releases/latest/download/sbom.cdx.json | jq . +curl -sL https://github.com/ConductionNL/stackiq/releases/latest/download/sbom.cdx.json | jq . ``` Example — fetch the SBOM for a specific historical release: ```bash -curl -sL https://github.com/ConductionNL/softwarecatalog/releases/download/v1.0.0/sbom.cdx.json | jq . +curl -sL https://github.com/ConductionNL/stackiq/releases/download/v1.0.0/sbom.cdx.json | jq . ``` ### Update cadence diff --git a/aanvullende-informatie.md b/aanvullende-informatie.md index f9f97357..a59c6557 100644 --- a/aanvullende-informatie.md +++ b/aanvullende-informatie.md @@ -33,8 +33,8 @@ Het GEMMA ArchiMate Exchange Format bestand bevat het volledige architectuurmode | Bestand | Omschrijving | Pad | |---------|-------------|-----| -| `GEMMA release.xml` | Volledig GEMMA ArchiMate model (13.3 MB) | `softwarecatalog/data/GEMMA release.xml` | -| `GEMMA_release.xml` | Kopie in Settings directory (13.4 MB) | `softwarecatalog/lib/Settings/GEMMA_release.xml` | +| `GEMMA release.xml` | Volledig GEMMA ArchiMate model (13.3 MB) | `stackiq/data/GEMMA release.xml` | +| `GEMMA_release.xml` | Kopie in Settings directory (13.4 MB) | `stackiq/lib/Settings/GEMMA_release.xml` | | Turfbrug test model | VNG Realisatie test-export (15 MB) | `Softwarecatalogus/docs/examples/02-04-2025_GEMMA 2_Turfbrug (test VNG Realisatie)_ameff_model.xml` | ### Analyse: Orphaned buitengemeentelijkVoorziening referenties in koppelingen @@ -264,7 +264,7 @@ Softwarecatalogus/reacties/screenshots/{nummer}-{beschrijving}.png | Frontend URL | http://localhost:3000 | | Backend URL | http://localhost:8080 | | Admin credentials | admin / admin | -| Test-gebruikers | Zie `softwarecatalog/.claude/skills/test-softwarecatalog.md` | +| Test-gebruikers | Zie `stackiq/.claude/skills/test-stackiq.md` | ### Browsergebruik - Gebruik de toegewezen browser (zie browser-pool in CLAUDE.md) @@ -292,7 +292,7 @@ awk -F',' '{if ($NF == "" || $NF == "\"\"") print}' Softwarecatalogus/data/conta ``` ### RBAC-referentie voor agents -De RBAC-regels staan in: `softwarecatalog/lib/Settings/softwarecatalogus_register.json` +De RBAC-regels staan in: `stackiq/lib/Settings/softwarecatalogus_register.json` Raadpleeg dit bestand wanneer een issue gaat over zichtbaarheid, toegang, of organisatie-scoping. ### Voortgang bijhouden diff --git a/appinfo/info.xml b/appinfo/info.xml index 0c68b0f6..9935cc76 100644 --- a/appinfo/info.xml +++ b/appinfo/info.xml @@ -1,14 +1,14 @@ - softwarecatalog - Software Catalogus - Software Catalogus + stackiq + Stackiq + Stackiq Manage your software portfolio with applications, modules, and connections Beheer je softwareportfolio met applicaties, modules en koppelingen - ⚠️ **Active development — not for production use yet.** This app is under active development. Although it may carry a stable release status, **please do not use it in production environments before 17 June 2026.** See the [app page on conduction.nl](https://conduction.nl/apps/softwarecatalog) for release planning and what this app does. + ⚠️ **Active development — not for production use yet.** This app is under active development. Although it may carry a stable release status, **please do not use it in production environments before 17 June 2026.** See the [app page on conduction.nl](https://conduction.nl/apps/stackiq) for release planning and what this app does. -Software Catalogus brings structured software portfolio management to Nextcloud. Keep track of all the applications, modules, and connections in your organization — and share them across a federated open data network. +Stackiq brings structured software portfolio management to Nextcloud. Keep track of all the applications, modules, and connections in your organization — and share them across a federated open data network. **Key Features** @@ -18,7 +18,7 @@ Software Catalogus brings structured software portfolio management to Nextcloud. - **Contract & license administration** — Track contracts with an approval workflow, renewal status, and expiry alerts - **GEMMA/ArchiMate standards & compliance** — Maintain a standards register, record compliance claims with evidence, cross-check modules against standards in a compliance matrix, and import/export ArchiMate models - **Portfolio roadmap** — See applications in use grouped by lifecycle phase, with end-of-support and phase-out warnings -- **Federated synchronization** — Share and sync catalog data with other Software Catalogus instances via OpenCatalogi's directory network (optional; degrades gracefully if OpenCatalogi is not installed) +- **Federated synchronization** — Share and sync catalog data with other Stackiq instances via OpenCatalogi's directory network (optional; degrades gracefully if OpenCatalogi is not installed) - **Automatic user provisioning** — Sync contacts and organizations from your register into Nextcloud accounts and groups - **Open data publishing** — Publish selected catalog entries for transparency and reuse, with anonymous self-registration and moderation for new organizations @@ -28,9 +28,9 @@ Free and open source under the EUPL license. **Support:** For support, contact support@conduction.nl. For a Service Level Agreement (SLA), contact sales@conduction.nl. ]]> - ⚠️ **In actieve ontwikkeling — nog niet voor productiegebruik.** Deze app is volop in ontwikkeling. Hoewel de app een stabiele release-status kan hebben, **gebruik deze nog niet in productieomgevingen vóór 17 juni 2026.** Zie de [app-pagina op conduction.nl](https://conduction.nl/apps/softwarecatalog) voor release-planning en het doel van deze app. + ⚠️ **In actieve ontwikkeling — nog niet voor productiegebruik.** Deze app is volop in ontwikkeling. Hoewel de app een stabiele release-status kan hebben, **gebruik deze nog niet in productieomgevingen vóór 17 juni 2026.** Zie de [app-pagina op conduction.nl](https://conduction.nl/apps/stackiq) voor release-planning en het doel van deze app. -Software Catalogus brengt gestructureerd softwareportfoliobeheer naar Nextcloud. Houd al je applicaties, modules en koppelingen bij — en deel ze via een gefedereerd open data netwerk. +Stackiq brengt gestructureerd softwareportfoliobeheer naar Nextcloud. Houd al je applicaties, modules en koppelingen bij — en deel ze via een gefedereerd open data netwerk. **Belangrijkste functies** @@ -40,7 +40,7 @@ Software Catalogus brengt gestructureerd softwareportfoliobeheer naar Nextcloud. - **Contract- en licentiebeheer** — Volg contracten met een goedkeuringsworkflow, verlengingsstatus en verloopmeldingen - **GEMMA/ArchiMate-standaarden & compliance** — Beheer een standaardenregister, leg compliance-claims met bewijs vast, toets modules tegen standaarden in een compliance-matrix en importeer/exporteer ArchiMate-modellen - **Portfolioroadmap** — Zie applicaties in gebruik gegroepeerd op levenscyclusfase, met waarschuwingen voor einde ondersteuning en uitfasering -- **Gefedereerde synchronisatie** — Deel en synchroniseer catalogusdata met andere Software Catalogus-instanties via het directorynetwerk van OpenCatalogi (optioneel; werkt zonder OpenCatalogi met beperkte functionaliteit) +- **Gefedereerde synchronisatie** — Deel en synchroniseer catalogusdata met andere Stackiq-instanties via het directorynetwerk van OpenCatalogi (optioneel; werkt zonder OpenCatalogi met beperkte functionaliteit) - **Automatische gebruikersaanmaak** — Synchroniseer contacten en organisaties vanuit je register naar Nextcloud-accounts en -groepen - **Open data publicatie** — Publiceer geselecteerde catalogusitems voor transparantie en hergebruik, met anonieme zelfregistratie en moderatie voor nieuwe organisaties @@ -53,7 +53,7 @@ Vrij en open source onder de EUPL-licentie. 0.1.141-unstable.20260821053703 EUPL-1.2 Conduction - SoftwareCatalog + Stackiq https://github.com/ConductionNL/stackiq https://github.com/ConductionNL/stackiq @@ -73,7 +73,7 @@ Vrij en open source onder de EUPL-licentie. + OCA\Stackiq\Repair\MigrateAppConfigKeys + OCA\Stackiq\Repair\MigrateUserPreferences + + OCA\Stackiq\Repair\MigrateBackgroundJobClasses + - OCA\SoftwareCatalog\Repair\RenameDutchSchemaSlugs - OCA\SoftwareCatalog\Repair\InitializeSettings - OCA\SoftwareCatalog\Repair\MigrateContactsToNc - OCA\SoftwareCatalog\Repair\BackfillContractApprovalState + OCA\Stackiq\Repair\RenameDutchSchemaSlugs + OCA\Stackiq\Repair\InitializeSettings + OCA\Stackiq\Repair\MigrateContactsToNc + OCA\Stackiq\Repair\BackfillContractApprovalState - OCA\SoftwareCatalog\Repair\RenameDutchCatalogColumns + OCA\Stackiq\Repair\RenameDutchCatalogColumns - OCA\SoftwareCatalog\Repair\RenameDutchCatalogValues + OCA\Stackiq\Repair\RenameDutchCatalogValues - OCA\SoftwareCatalog\Repair\InitializeSettings + OCA\Stackiq\Repair\MigrateAppConfigKeys + OCA\Stackiq\Repair\MigrateUserPreferences + OCA\Stackiq\Repair\MigrateBackgroundJobClasses + OCA\Stackiq\Repair\InitializeSettings - OCA\SoftwareCatalog\Settings\SoftwareCatalogAdmin - OCA\SoftwareCatalog\Sections\SoftwareCatalogAdmin + OCA\Stackiq\Settings\StackiqAdmin + OCA\Stackiq\Sections\StackiqAdmin - softwarecatalog - Software Catalogs - softwarecatalog.dashboard.page + stackiq + Stackiq + stackiq.dashboard.page app.svg diff --git a/appinfo/routes.php b/appinfo/routes.php index cabad721..f3034733 100644 --- a/appinfo/routes.php +++ b/appinfo/routes.php @@ -3,16 +3,16 @@ declare(strict_types=1); /** - * SoftwareCatalog Routes Configuration + * Stackiq Routes Configuration * - * This file defines the API routes for the SoftwareCatalog application. + * This file defines the API routes for the Stackiq application. * * @category Configuration - * @package OCA\SoftwareCatalog + * @package OCA\Stackiq * @version 1.0.0 * @author Conduction b.v. * @license EUPL-1.2 https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12 - * @link https://codeberg.org/Conduction/SoftwareCatalog + * @link https://github.com/ConductionNL/stackiq */ return [ @@ -27,7 +27,7 @@ // Contract approval delegation to decidesk (in-process IEventDispatcher; fail-closed). // Outcome is projected by DecisionConcludedListener, not an HTTP callback. - // @spec openspec/changes/softwarecatalog-delegation-via-events/specs/contract-decision-delegation/spec.md + // @spec openspec/changes/stackiq-delegation-via-events/specs/contract-decision-delegation/spec.md ['name' => 'contractApproval#config', 'url' => '/api/contracts/approval/config', 'verb' => 'GET'], ['name' => 'contractApproval#submit', 'url' => '/api/contracts/{contractUuid}/approval/submit', 'verb' => 'POST'], ['name' => 'contractApproval#submitRenewal', 'url' => '/api/contracts/{contractUuid}/approval/renewal', 'verb' => 'POST'], @@ -212,7 +212,7 @@ ['name' => 'moderation#approve', 'url' => '/api/moderation/{uuid}/approve', 'verb' => 'POST'], ['name' => 'moderation#reject', 'url' => '/api/moderation/{uuid}/reject', 'verb' => 'POST'], - // CATALOG RATINGS (softwarecatalog#375) — authenticated review + // CATALOG RATINGS (stackiq#375) — authenticated review // submission (author/status always server-stamped, never from the // client) + public approved-only aggregate for module/dienst detail. ['name' => 'review#submit', 'url' => '/api/reviews', 'verb' => 'POST'], @@ -298,7 +298,7 @@ // `dashboard#page` route above: both entries target the same // controller#method, so without a postfix they generate the same // internal route name and the later one silently displaces the first — - // which 404'd the app's own entry point (`/apps/softwarecatalog/`) for + // which 404'd the app's own entry point (`/apps/stackiq/`) for // every user, because this route's `path` requirement ('.+') can never // match an empty path. ['name' => 'dashboard#page', 'url' => '/{path}', 'verb' => 'GET', 'requirements' => ['path' => '.+'], 'defaults' => ['path' => ''], 'postfix' => 'spa'], diff --git a/check_db.php b/check_db.php index b9c94240..3e15716e 100644 --- a/check_db.php +++ b/check_db.php @@ -4,7 +4,7 @@ $db = \OC::$server->getDatabaseConnection(); $query = $db->getQueryBuilder(); $query->select('id', 'slug', 'object') - ->from('softwarecatalog_objects') + ->from('stackiq_objects') ->where($query->expr()->like('id', $query->createNamedParameter('%d4572e2e%'))) ->setMaxResults(1); $result = $query->execute(); diff --git a/check_openregister.php b/check_openregister.php index 70a65895..aa3fa3b2 100644 --- a/check_openregister.php +++ b/check_openregister.php @@ -9,7 +9,7 @@ 'oc_openregister_object', 'openregister_objects', 'objects', - 'oc_softwarecatalog_objects' + 'oc_stackiq_objects' ]; foreach ($possibleTables as $tableName) { diff --git a/check_tables.php b/check_tables.php index f6f4de86..0042fcec 100644 --- a/check_tables.php +++ b/check_tables.php @@ -3,13 +3,13 @@ $db = \OC::$server->getDatabaseConnection(); -// Get all tables that contain 'softwarecatalog' or 'catalog' +// Get all tables that contain 'stackiq' or 'catalog' $query = $db->getQueryBuilder(); $query->select('TABLE_NAME') ->from('information_schema.TABLES') ->where($query->expr()->eq('TABLE_SCHEMA', $query->createNamedParameter('nextcloud'))) ->andWhere($query->expr()->orX( - $query->expr()->like('TABLE_NAME', $query->createNamedParameter('%softwarecatalog%')), + $query->expr()->like('TABLE_NAME', $query->createNamedParameter('%stackiq%')), $query->expr()->like('TABLE_NAME', $query->createNamedParameter('%catalog%')) )); @@ -21,7 +21,7 @@ } if (empty($tables)) { - echo "No tables found containing 'softwarecatalog' or 'catalog'" . PHP_EOL; + echo "No tables found containing 'stackiq' or 'catalog'" . PHP_EOL; // Let's check for any tables that might be related $query2 = $db->getQueryBuilder(); diff --git a/compare_archimate.php b/compare_archimate.php index 15237fe5..ab0ad79c 100644 --- a/compare_archimate.php +++ b/compare_archimate.php @@ -616,13 +616,13 @@ private function generateReport(): void try { $comparator = new ArchiMateComparator(); - $originalFile = '/var/www/html/apps-extra/softwarecatalog/lib/Settings/GEMMA_release.xml'; + $originalFile = '/var/www/html/apps-extra/stackiq/lib/Settings/GEMMA_release.xml'; $exportedFile = '/tmp/archimate_export_latest.xml'; // First, generate a fresh export echo "Generating fresh export...\n"; $ch = curl_init(); - curl_setopt($ch, CURLOPT_URL, 'http://localhost/index.php/apps/softwarecatalog/api/archimate/export'); + curl_setopt($ch, CURLOPT_URL, 'http://localhost/index.php/apps/stackiq/api/archimate/export'); curl_setopt($ch, CURLOPT_POST, true); curl_setopt($ch, CURLOPT_POSTFIELDS, '{}'); curl_setopt($ch, CURLOPT_HTTPHEADER, ['Content-Type: application/json']); diff --git a/composer.json b/composer.json index 880aa7be..499a657b 100644 --- a/composer.json +++ b/composer.json @@ -1,5 +1,5 @@ { - "name": "conductionnl/softwarecatalog", + "name": "conductionnl/stackiq", "description": "Quickly build data registers based on schema.json", "license": "EUPL-1.2", "authors": [ @@ -11,7 +11,7 @@ ], "autoload": { "psr-4": { - "OCA\\SoftwareCatalog\\": "lib/" + "OCA\\Stackiq\\": "lib/" } }, "scripts": { diff --git a/css/dashboardWidgets.css b/css/dashboardWidgets.css index adfbc9bc..0dd5005a 100644 --- a/css/dashboardWidgets.css +++ b/css/dashboardWidgets.css @@ -1,8 +1,8 @@ -.icon-softwarecatalog-widget { +.icon-stackiq-widget { background-image: url('../img/app-dark.svg'); filter: var(--background-invert-if-dark); } -body.theme--dark .icon-softwarecatalog-widget { +body.theme--dark .icon-stackiq-widget { background-image: url('../img/app.svg'); } diff --git a/docs/ANONYMOUS_USER_REGISTRATION_USECASE.md b/docs/ANONYMOUS_USER_REGISTRATION_USECASE.md index d4df56f2..e715f5f8 100644 --- a/docs/ANONYMOUS_USER_REGISTRATION_USECASE.md +++ b/docs/ANONYMOUS_USER_REGISTRATION_USECASE.md @@ -243,7 +243,7 @@ curl -u 'admin:admin' 'http://localhost/index.php/apps/openregister/api/organisa docker-compose exec -u 33 nextcloud php /var/www/html/occ user:info {username} # Check logs -docker-compose exec nextcloud tail -f /var/www/html/data/nextcloud.log | grep -E "SoftwareCatalogue|ownership|UUID" +docker-compose exec nextcloud tail -f /var/www/html/data/nextcloud.log | grep -E "Stackiq|ownership|UUID" ``` ## Implementation Notes diff --git a/docs/API_REFERENCE.md b/docs/API_REFERENCE.md index ee66d8e8..9c1d0eae 100644 --- a/docs/API_REFERENCE.md +++ b/docs/API_REFERENCE.md @@ -8,7 +8,7 @@ The Software Catalog app provides a REST API for configuration management and pr All API endpoints are relative to the Nextcloud base URL: ``` -https://your-nextcloud-domain/index.php/apps/softwarecatalog/api/ +https://your-nextcloud-domain/index.php/apps/stackiq/api/ ``` ## Authentication @@ -93,7 +93,7 @@ Loads configuration from register-specific JSON files. ## Service Classes API -### SoftwareCatalogueService +### StackiqService #### processContactgegevens() @@ -114,7 +114,7 @@ public function processContactgegevens(object $contactgegevensObject): bool **Usage:** ```php -$service = \OC::$server->get(SoftwareCatalogueService::class); +$service = \OC::$server->get(StackiqService::class); $result = $service->processContactgegevens($contactgegevensObject); ``` @@ -172,7 +172,7 @@ public function getUserManager(string $username): ?string **Usage:** ```php -$service = \OC::$server->get(SoftwareCatalogueService::class); +$service = \OC::$server->get(StackiqService::class); $manager = $service->getUserManager('john.doe'); ``` @@ -320,10 +320,10 @@ try { ### Programmatic User Creation ```php -use OCA\SoftwareCatalog\Service\SoftwareCatalogueService; +use OCA\Stackiq\Service\StackiqService; // Get the service -$service = \OC::$server->get(SoftwareCatalogueService::class); +$service = \OC::$server->get(StackiqService::class); // Create contactgegevens object data $contactData = [ @@ -352,9 +352,9 @@ try { ### Check User Manager ```php -use OCA\SoftwareCatalog\Service\SoftwareCatalogueService; +use OCA\Stackiq\Service\StackiqService; -$service = \OC::$server->get(SoftwareCatalogueService::class); +$service = \OC::$server->get(StackiqService::class); $username = 'john.doe'; $manager = $service->getUserManager($username); @@ -457,7 +457,7 @@ Cache is automatically invalidated on: ```php // Unit test example public function testProcessContactgegevens() { - $service = new SoftwareCatalogueService(/* dependencies */); + $service = new StackiqService(/* dependencies */); $contactObject = $this->createMockContactObject(); $result = $service->processContactgegevens($contactObject); diff --git a/docs/API_TESTING.md b/docs/API_TESTING.md index 5d4a9e20..3d09e1c3 100644 --- a/docs/API_TESTING.md +++ b/docs/API_TESTING.md @@ -1,7 +1,7 @@ -# API Testing Guide for SoftwareCatalog Integration +# API Testing Guide for Stackiq Integration **Date:** July 24, 2025 -**App:** SoftwareCatalog +**App:** Stackiq **Purpose:** Repeatable local API testing for organization synchronization ## 🚀 Recommended Testing Tools @@ -44,11 +44,11 @@ npm install -g newman-reporter-cli ## 📁 Project Structure ``` -softwarecatalog/ +stackiq/ ├── tests/ │ ├── api/ │ │ ├── postman/ -│ │ │ ├── SoftwareCatalog_API_Tests.postman_collection.json +│ │ │ ├── Stackiq_API_Tests.postman_collection.json │ │ │ ├── Local_Environment.postman_environment.json │ │ │ └── Docker_Environment.postman_environment.json │ │ ├── scripts/ @@ -67,7 +67,7 @@ softwarecatalog/ ### Collection Structure ``` -SoftwareCatalog API Tests/ +Stackiq API Tests/ ├── Setup/ │ ├── Check Configuration │ └── Verify Services @@ -113,16 +113,16 @@ SoftwareCatalog API Tests/ ### Basic Test Execution ```bash # Run all tests -newman run tests/api/postman/SoftwareCatalog_API_Tests.postman_collection.json \ +newman run tests/api/postman/Stackiq_API_Tests.postman_collection.json \ -e tests/api/postman/Local_Environment.postman_environment.json # Run specific folder -newman run tests/api/postman/SoftwareCatalog_API_Tests.postman_collection.json \ +newman run tests/api/postman/Stackiq_API_Tests.postman_collection.json \ -e tests/api/postman/Local_Environment.postman_environment.json \ --folder "Anonymous Registration" # Run with detailed reporting -newman run tests/api/postman/SoftwareCatalog_API_Tests.postman_collection.json \ +newman run tests/api/postman/Stackiq_API_Tests.postman_collection.json \ -e tests/api/postman/Local_Environment.postman_environment.json \ --reporters cli,htmlextra \ --reporter-htmlextra-export tests/api/results/newman-reports/ @@ -131,11 +131,11 @@ newman run tests/api/postman/SoftwareCatalog_API_Tests.postman_collection.json \ ### Test with Different Environments ```bash # Local development -newman run tests/api/postman/SoftwareCatalog_API_Tests.postman_collection.json \ +newman run tests/api/postman/Stackiq_API_Tests.postman_collection.json \ -e tests/api/postman/Local_Environment.postman_environment.json # Docker environment -newman run tests/api/postman/SoftwareCatalog_API_Tests.postman_collection.json \ +newman run tests/api/postman/Stackiq_API_Tests.postman_collection.json \ -e tests/api/postman/Docker_Environment.postman_environment.json ``` @@ -148,7 +148,7 @@ newman run tests/api/postman/SoftwareCatalog_API_Tests.postman_collection.json \ set -e -echo "🚀 Starting SoftwareCatalog API Tests..." +echo "🚀 Starting Stackiq API Tests..." # Configuration BASE_URL="http://localhost" @@ -461,11 +461,11 @@ curl -u 'admin:admin' \ ### Newman HTML Report ```bash # Generate detailed HTML report -newman run tests/api/postman/SoftwareCatalog_API_Tests.postman_collection.json \ +newman run tests/api/postman/Stackiq_API_Tests.postman_collection.json \ -e tests/api/postman/Local_Environment.postman_environment.json \ --reporters htmlextra \ --reporter-htmlextra-export tests/api/results/newman-reports/ \ - --reporter-htmlextra-title "SoftwareCatalog API Test Report" + --reporter-htmlextra-title "Stackiq API Test Report" ``` ### Shell Script Logging @@ -503,7 +503,7 @@ jobs: - name: Run API Tests run: | - newman run tests/api/postman/SoftwareCatalog_API_Tests.postman_collection.json \ + newman run tests/api/postman/Stackiq_API_Tests.postman_collection.json \ -e tests/api/postman/Docker_Environment.postman_environment.json \ --reporters cli,htmlextra \ --reporter-htmlextra-export tests/api/results/newman-reports/ @@ -537,13 +537,13 @@ curl -u 'admin:admin' 'http://localhost/index.php/apps/openregister/api/objects/ #### 3. Schema Configuration Issues ```bash # Check configuration -docker-compose exec -u 33 nextcloud php /var/www/html/occ config:app:get softwarecatalog voorzieningen_organisatie_schema +docker-compose exec -u 33 nextcloud php /var/www/html/occ config:app:get stackiq voorzieningen_organisatie_schema ``` ### Debug Mode ```bash # Run with verbose output -newman run tests/api/postman/SoftwareCatalog_API_Tests.postman_collection.json \ +newman run tests/api/postman/Stackiq_API_Tests.postman_collection.json \ -e tests/api/postman/Local_Environment.postman_environment.json \ --verbose @@ -605,4 +605,4 @@ scenarios: - [curl Documentation](https://curl.se/docs/) - [jq Documentation](https://stedolan.github.io/jq/manual/) -This comprehensive API testing setup provides repeatable, automated testing for all SoftwareCatalog integration scenarios, from basic CRUD operations to complex anonymous user registration flows. \ No newline at end of file +This comprehensive API testing setup provides repeatable, automated testing for all Stackiq integration scenarios, from basic CRUD operations to complex anonymous user registration flows. \ No newline at end of file diff --git a/docs/ARCHIMATE_QUICK_TEST.md b/docs/ARCHIMATE_QUICK_TEST.md index 7e60047b..b9c46405 100644 --- a/docs/ARCHIMATE_QUICK_TEST.md +++ b/docs/ARCHIMATE_QUICK_TEST.md @@ -3,7 +3,7 @@ ## 🚀 One-Line Full Test ```bash -cd /home/rubenlinde/nextcloud-docker-dev && docker-compose exec nextcloud php /var/www/html/apps-extra/softwarecatalog/compare_archimate.php +cd /home/rubenlinde/nextcloud-docker-dev && docker-compose exec nextcloud php /var/www/html/apps-extra/stackiq/compare_archimate.php ``` This command will: @@ -17,22 +17,22 @@ This command will: ### Import Test ```bash -docker-compose exec nextcloud curl -X POST "http://localhost/index.php/apps/softwarecatalog/api/archimate/import" -H "Content-Type: application/json" -u admin:admin -d '{"file_path": "/var/www/html/apps-extra/softwarecatalog/lib/Settings/GEMMA_release.xml"}' +docker-compose exec nextcloud curl -X POST "http://localhost/index.php/apps/stackiq/api/archimate/import" -H "Content-Type: application/json" -u admin:admin -d '{"file_path": "/var/www/html/apps-extra/stackiq/lib/Settings/GEMMA_release.xml"}' ``` ### Export Test ```bash -docker-compose exec nextcloud curl -X POST "http://localhost/index.php/apps/softwarecatalog/api/archimate/export" -H "Content-Type: application/json" -u admin:admin -d '{}' > /tmp/test_export.xml +docker-compose exec nextcloud curl -X POST "http://localhost/index.php/apps/stackiq/api/archimate/export" -H "Content-Type: application/json" -u admin:admin -d '{}' > /tmp/test_export.xml ``` ### Database Inspection ```bash -docker-compose exec nextcloud php /var/www/html/apps-extra/softwarecatalog/debug_db.php +docker-compose exec nextcloud php /var/www/html/apps-extra/stackiq/debug_db.php ``` ### Clear Status ```bash -docker-compose exec nextcloud curl -X POST "http://localhost/index.php/apps/softwarecatalog/api/archimate/import/cancel" -u admin:admin +docker-compose exec nextcloud curl -X POST "http://localhost/index.php/apps/stackiq/api/archimate/import/cancel" -u admin:admin ``` ## 🎯 Expected Results @@ -67,12 +67,12 @@ Folders compared: X ## 🔧 Troubleshooting ### If Import Fails -1. Check file exists: `docker-compose exec nextcloud ls -la /var/www/html/apps-extra/softwarecatalog/lib/Settings/GEMMA_release.xml` -2. Clear status: `docker-compose exec nextcloud curl -X POST "http://localhost/index.php/apps/softwarecatalog/api/archimate/import/cancel" -u admin:admin` +1. Check file exists: `docker-compose exec nextcloud ls -la /var/www/html/apps-extra/stackiq/lib/Settings/GEMMA_release.xml` +2. Clear status: `docker-compose exec nextcloud curl -X POST "http://localhost/index.php/apps/stackiq/api/archimate/import/cancel" -u admin:admin` 3. Check logs: `docker-compose exec nextcloud tail -n 50 /var/www/html/data/nextcloud.log` ### If Export is Empty -1. Verify import worked: `docker-compose exec nextcloud php /var/www/html/apps-extra/softwarecatalog/debug_db.php` +1. Verify import worked: `docker-compose exec nextcloud php /var/www/html/apps-extra/stackiq/debug_db.php` 2. Check object counts in database 3. Re-run import if needed diff --git a/docs/ARCHIMATE_TESTING_GUIDE.md b/docs/ARCHIMATE_TESTING_GUIDE.md index fc7ab3c1..f14ed40f 100644 --- a/docs/ARCHIMATE_TESTING_GUIDE.md +++ b/docs/ARCHIMATE_TESTING_GUIDE.md @@ -1,11 +1,11 @@ # ArchiMate Import/Export Testing Guide -This guide provides comprehensive instructions for testing the ArchiMate import/export functionality in the SoftwareCatalog application, including full round-trip testing to ensure data integrity. +This guide provides comprehensive instructions for testing the ArchiMate import/export functionality in the Stackiq application, including full round-trip testing to ensure data integrity. ## Prerequisites - Docker Compose setup with Nextcloud container running -- SoftwareCatalog and OpenRegister apps enabled +- Stackiq and OpenRegister apps enabled - Admin credentials (default: admin:admin) - Sample ArchiMate XML file (GEMMA_release.xml is included) @@ -17,10 +17,10 @@ This guide provides comprehensive instructions for testing the ArchiMate import/ # Import the sample GEMMA file cd /home/rubenlinde/nextcloud-docker-dev docker-compose exec nextcloud curl -X POST \ - "http://localhost/index.php/apps/softwarecatalog/api/archimate/import" \ + "http://localhost/index.php/apps/stackiq/api/archimate/import" \ -H "Content-Type: application/json" \ -u admin:admin \ - -d '{"file_path": "/var/www/html/apps-extra/softwarecatalog/lib/Settings/GEMMA_release.xml"}' + -d '{"file_path": "/var/www/html/apps-extra/stackiq/lib/Settings/GEMMA_release.xml"}' ``` Expected response includes statistics like: @@ -42,7 +42,7 @@ Expected response includes statistics like: ```bash # Export the imported data docker-compose exec nextcloud curl -X POST \ - "http://localhost/index.php/apps/softwarecatalog/api/archimate/export" \ + "http://localhost/index.php/apps/stackiq/api/archimate/export" \ -H "Content-Type: application/json" \ -u admin:admin \ -d '{}' > /tmp/exported_archimate.xml @@ -56,7 +56,7 @@ The application includes a comprehensive comparison script that automatically te ```bash # Run the automated comparison -docker-compose exec nextcloud php /var/www/html/apps-extra/softwarecatalog/compare_archimate.php +docker-compose exec nextcloud php /var/www/html/apps-extra/stackiq/compare_archimate.php ``` This script will: @@ -72,12 +72,12 @@ This script will: ```bash # Clear any existing import status docker-compose exec nextcloud curl -X POST \ - "http://localhost/index.php/apps/softwarecatalog/api/archimate/import/cancel" \ + "http://localhost/index.php/apps/stackiq/api/archimate/import/cancel" \ -u admin:admin # Check that the system is ready docker-compose exec nextcloud curl -X GET \ - "http://localhost/index.php/apps/softwarecatalog/api/settings/status" \ + "http://localhost/index.php/apps/stackiq/api/settings/status" \ -u admin:admin ``` @@ -86,10 +86,10 @@ docker-compose exec nextcloud curl -X GET \ ```bash # Import the original GEMMA file docker-compose exec nextcloud curl -X POST \ - "http://localhost/index.php/apps/softwarecatalog/api/archimate/import" \ + "http://localhost/index.php/apps/stackiq/api/archimate/import" \ -H "Content-Type: application/json" \ -u admin:admin \ - -d '{"file_path": "/var/www/html/apps-extra/softwarecatalog/lib/Settings/GEMMA_release.xml"}' + -d '{"file_path": "/var/www/html/apps-extra/stackiq/lib/Settings/GEMMA_release.xml"}' ``` #### Step 3: Export Imported Data @@ -97,13 +97,13 @@ docker-compose exec nextcloud curl -X POST \ ```bash # Export the data to a new file docker-compose exec nextcloud curl -X POST \ - "http://localhost/index.php/apps/softwarecatalog/api/archimate/export" \ + "http://localhost/index.php/apps/stackiq/api/archimate/export" \ -H "Content-Type: application/json" \ -u admin:admin \ -d '{}' > /tmp/round_trip_export.xml # Check the export file size (should be similar to original) -docker-compose exec nextcloud ls -lh /var/www/html/apps-extra/softwarecatalog/lib/Settings/GEMMA_release.xml +docker-compose exec nextcloud ls -lh /var/www/html/apps-extra/stackiq/lib/Settings/GEMMA_release.xml docker-compose exec nextcloud ls -lh /tmp/round_trip_export.xml ``` @@ -111,7 +111,7 @@ docker-compose exec nextcloud ls -lh /tmp/round_trip_export.xml ```bash # Run the detailed comparison -docker-compose exec nextcloud php /var/www/html/apps-extra/softwarecatalog/compare_archimate.php +docker-compose exec nextcloud php /var/www/html/apps-extra/stackiq/compare_archimate.php # For manual inspection, check specific sections docker-compose exec nextcloud head -n 50 /tmp/round_trip_export.xml @@ -124,7 +124,7 @@ docker-compose exec nextcloud head -n 50 /tmp/round_trip_export.xml Use the included debug script to inspect what's stored in the database: ```bash -docker-compose exec nextcloud php /var/www/html/apps-extra/softwarecatalog/debug_db.php +docker-compose exec nextcloud php /var/www/html/apps-extra/stackiq/debug_db.php ``` This will show: @@ -159,7 +159,7 @@ foreach ($query->fetchAll() as $row) { ```bash # Check a specific element docker-compose exec nextcloud curl -X GET \ - "http://localhost/index.php/apps/softwarecatalog/api/archimate/export" \ + "http://localhost/index.php/apps/stackiq/api/archimate/export" \ -u admin:admin | grep -A 10 "id-009fa62f25844aa3a87d252bf2b6bb0c" ``` @@ -173,7 +173,7 @@ Expected output should include: ```bash # Check relationships structure docker-compose exec nextcloud curl -X GET \ - "http://localhost/index.php/apps/softwarecatalog/api/archimate/export" \ + "http://localhost/index.php/apps/stackiq/api/archimate/export" \ -u admin:admin | grep -A 5 -B 2 'schemaValidate("/path/to/archimate.xsd")) { ```bash # Count elements in original vs export echo "Original elements:" -docker-compose exec nextcloud grep -c 'get(ContactpersoonService::class); @@ -197,7 +197,7 @@ The method provides detailed logging at different levels: ## Related Files -- `softwarecatalog/lib/Service/ContactpersoonService.php` - Main service implementation -- `softwarecatalog/lib/Controller/ContactpersonenController.php` - API controller -- `softwarecatalog/appinfo/routes.php` - API route definition -- `softwarecatalog/lib/Examples/ContactpersoonServiceExample.php` - Usage examples +- `stackiq/lib/Service/ContactpersoonService.php` - Main service implementation +- `stackiq/lib/Controller/ContactpersonenController.php` - API controller +- `stackiq/appinfo/routes.php` - API route definition +- `stackiq/lib/Examples/ContactpersoonServiceExample.php` - Usage examples diff --git a/docs/FRONTEND_INTEGRATION.md b/docs/FRONTEND_INTEGRATION.md index 86830eec..72a1adee 100644 --- a/docs/FRONTEND_INTEGRATION.md +++ b/docs/FRONTEND_INTEGRATION.md @@ -13,7 +13,7 @@ Added a new method `fetchContactPersonsWithUserDetails()` to the Pinia store: ```javascript async fetchContactPersonsWithUserDetails(organizationUuid) { try { - const url = generateUrl(`/apps/softwarecatalog/api/contactpersonen/organisation/${organizationUuid}/with-user-details`) + const url = generateUrl(`/apps/stackiq/api/contactpersonen/organisation/${organizationUuid}/with-user-details`) const response = await fetch(url, { method: 'GET', @@ -201,7 +201,7 @@ To test the integration: ## Related Files -- `softwarecatalog/src/store/modules/organisatie.js` - Store with new API method -- `softwarecatalog/src/components/ContactpersonenList.vue` - Component with enhanced data loading -- `softwarecatalog/lib/Service/ContactpersoonService.php` - Backend service -- `softwarecatalog/lib/Controller/ContactpersonenController.php` - API controller +- `stackiq/src/store/modules/organisatie.js` - Store with new API method +- `stackiq/src/components/ContactpersonenList.vue` - Component with enhanced data loading +- `stackiq/lib/Service/ContactpersoonService.php` - Backend service +- `stackiq/lib/Controller/ContactpersonenController.php` - API controller diff --git a/docs/GROUP_MANAGEMENT.md b/docs/GROUP_MANAGEMENT.md index 0780663e..8d6a8c74 100644 --- a/docs/GROUP_MANAGEMENT.md +++ b/docs/GROUP_MANAGEMENT.md @@ -118,7 +118,7 @@ Every user automatically gets a manager assigned: ### Manager Storage Manager relationships are stored in Nextcloud user preferences: -- **App**: 'softwarecatalog' +- **App**: 'stackiq' - **Key**: 'manager' - **Value**: Manager's username @@ -195,7 +195,7 @@ All operations are logged with appropriate detail levels: ### Adding New Role-Based Groups -To add new role-based groups, update the '_defaultGroups' array in SoftwareCatalogueService: +To add new role-based groups, update the '_defaultGroups' array in StackiqueService: ```php private array $_defaultGroups = [ diff --git a/docs/MODULE_COMPLIANCE_SUBSCRIBER.md b/docs/MODULE_COMPLIANCE_SUBSCRIBER.md index 2b912a79..96bd2fb2 100644 --- a/docs/MODULE_COMPLIANCE_SUBSCRIBER.md +++ b/docs/MODULE_COMPLIANCE_SUBSCRIBER.md @@ -2,7 +2,7 @@ ## Overzicht -De Module Compliance Subscriber is een nieuwe functionaliteit in de SoftwareCatalog app die automatisch de `standaarden` property van module objecten synchroniseert op basis van gekoppelde compliance objecten. +De Module Compliance Subscriber is een nieuwe functionaliteit in de Stackiq app die automatisch de `standaarden` property van module objecten synchroniseert op basis van gekoppelde compliance objecten. ## Functionaliteit @@ -55,8 +55,8 @@ $context->registerEventListener(ObjectCreatedEvent::class, ModuleComplianceSubsc $context->registerEventListener(ObjectUpdatedEvent::class, ModuleComplianceSubscriber::class); // Service registratie -$context->registerService(\OCA\SoftwareCatalog\Service\ModuleComplianceService::class, function ($container) { - return new \OCA\SoftwareCatalog\Service\ModuleComplianceService( +$context->registerService(\OCA\Stackiq\Service\ModuleComplianceService::class, function ($container) { + return new \OCA\Stackiq\Service\ModuleComplianceService( $container, $container->get(SettingsService::class), $container->get('Psr\Log\LoggerInterface') @@ -99,10 +99,10 @@ Voor het testen van de functionaliteit is een test script beschikbaar: ```bash # Eenvoudige service test -docker exec -u 33 master-nextcloud-1 php /var/www/html/apps-extra/softwarecatalog/test_module_compliance_simple.php +docker exec -u 33 master-nextcloud-1 php /var/www/html/apps-extra/stackiq/test_module_compliance_simple.php # Volledige functionaliteit test (vereist geconfigureerde schemas) -docker exec -u 33 master-nextcloud-1 php /var/www/html/apps-extra/softwarecatalog/test_module_compliance.php +docker exec -u 33 master-nextcloud-1 php /var/www/html/apps-extra/stackiq/test_module_compliance.php ``` ## Logging diff --git a/docs/ORGANIZATION_SYNC_USECASES.md b/docs/ORGANIZATION_SYNC_USECASES.md index 1dad567b..7911f9c0 100644 --- a/docs/ORGANIZATION_SYNC_USECASES.md +++ b/docs/ORGANIZATION_SYNC_USECASES.md @@ -1,7 +1,7 @@ # Organization Synchronization Use Cases and Testing **Date:** July 24, 2025 -**App:** SoftwareCatalog +**App:** Stackiq **Feature:** Organization Synchronization with OpenRegister ## 🚨 QUICK REFERENCE FOR NEW CONVERSATION @@ -31,10 +31,10 @@ docker-compose exec -u 33 nextcloud php /var/www/html/occ user:list docker-compose exec -u 33 nextcloud php /var/www/html/occ user:info {username} # Log monitoring -docker-compose exec nextcloud tail -f /var/www/html/data/nextcloud.log | grep -i "softwarecatalog" +docker-compose exec nextcloud tail -f /var/www/html/data/nextcloud.log | grep -i "stackiq" # Configuration -docker-compose exec -u 33 nextcloud php /var/www/html/occ config:app:get softwarecatalog voorzieningen_organisatie_schema +docker-compose exec -u 33 nextcloud php /var/www/html/occ config:app:get stackiq voorzieningen_organisatie_schema ``` ### Architecture Summary @@ -51,13 +51,13 @@ docker-compose exec -u 33 nextcloud php /var/www/html/occ config:app:get softwar ## Overview -This document outlines the use cases and testing scenarios for the new organization synchronization functionality in the Software Catalog app. The feature enables automatic synchronization between SoftwareCatalog organizations and OpenRegister, along with user status management based on organization status. +This document outlines the use cases and testing scenarios for the new organization synchronization functionality in the Software Catalog app. The feature enables automatic synchronization between Stackiq organizations and OpenRegister, along with user status management based on organization status. ## Use Cases ### 1. Organization Creation Synchronization -**Scenario:** A new organization is created in SoftwareCatalog +**Scenario:** A new organization is created in Stackiq **Expected Behavior:** - Organization data is automatically synced to OpenRegister @@ -66,7 +66,7 @@ This document outlines the use cases and testing scenarios for the new organizat - Contactpersonen are processed and users are created (inactive initially) **Test Steps:** -1. Create a new organization in SoftwareCatalog with status `actief` +1. Create a new organization in Stackiq with status `actief` 2. Verify organization appears in OpenRegister with `active: true` 3. Verify organization UUID is identical in both systems 4. Create contactpersonen for the organization @@ -109,7 +109,7 @@ This document outlines the use cases and testing scenarios for the new organizat ### 4. Organization Deletion -**Scenario:** Organization is deleted from SoftwareCatalog +**Scenario:** Organization is deleted from Stackiq **Expected Behavior:** - All users in the organization are deactivated @@ -134,7 +134,7 @@ This document outlines the use cases and testing scenarios for the new organizat - Organization membership is properly established **Test Steps:** -1. Create organization in SoftwareCatalog +1. Create organization in Stackiq 2. Create contactpersoon for the organization 3. Verify user account is created 4. Verify username is added to organization's users list @@ -164,7 +164,7 @@ This document outlines the use cases and testing scenarios for the new organizat **Scenario:** Organizations and contactpersonen use UUIDs across systems **Expected Behavior:** -- Organization UUIDs are identical in SoftwareCatalog and OpenRegister +- Organization UUIDs are identical in Stackiq and OpenRegister - Contactpersoon UUIDs are preserved during processing - UUID-based lookups work correctly in both systems @@ -175,9 +175,9 @@ This document outlines the use cases and testing scenarios for the new organizat 4. Verify UUID is preserved during user creation 5. Test UUID-based lookups in both systems -### 8. SoftwareCatalog-Specific User Status Management +### 8. Stackiq-Specific User Status Management -**Scenario:** Organization status changes affect only SoftwareCatalog-specific users (from contactpersoon objects), while admin group users remain protected. +**Scenario:** Organization status changes affect only Stackiq-specific users (from contactpersoon objects), while admin group users remain protected. **Expected Behavior:** - When organization becomes `inactief`: Only contactpersoon users are deactivated @@ -207,7 +207,7 @@ This document outlines the use cases and testing scenarios for the new organizat - User can be activated/deactivated with organization status changes **Test Steps:** -1. Create organization in SoftwareCatalog +1. Create organization in Stackiq 2. Create contact person with organization reference 3. Verify user account is created 4. Verify username is added to organization's users list @@ -222,7 +222,7 @@ This document outlines the use cases and testing scenarios for the new organizat - Admin group users are always added to organizations - Admin group users are never deactivated when organization becomes inactive - Admin group users remain active regardless of organization status -- Only SoftwareCatalog-specific users (contactpersonen) are affected by status changes +- Only Stackiq-specific users (contactpersonen) are affected by status changes **Test Steps:** 1. Create organization with admin users present @@ -350,7 +350,7 @@ This document outlines the use cases and testing scenarios for the new organizat ### Test Environment Setup **Prerequisites:** -- SoftwareCatalog app enabled +- Stackiq app enabled - OpenRegister app enabled - Proper schema and register configuration - Test organization and contactpersoon schemas configured @@ -400,7 +400,7 @@ This document outlines the use cases and testing scenarios for the new organizat // Test organization creation and OpenRegister sync public function testOrganizationCreationSync(): void { - // 1. Create organization in SoftwareCatalog + // 1. Create organization in Stackiq $organizationData = $this->createTestOrganization(); $organization = $this->createOrganization($organizationData); @@ -540,19 +540,19 @@ public function testOrganizationMembership(): void ### Debug Commands ```bash # Check organization sync status -docker exec -u 33 master-nextcloud-1 php /var/www/html/occ softwarecatalog:debug:organization-sync +docker exec -u 33 master-nextcloud-1 php /var/www/html/occ stackiq:debug:organization-sync # Check user status for organization -docker exec -u 33 master-nextcloud-1 php /var/www/html/occ softwarecatalog:debug:user-status --organization=UUID +docker exec -u 33 master-nextcloud-1 php /var/www/html/occ stackiq:debug:user-status --organization=UUID # Check organization membership -docker exec -u 33 master-nextcloud-1 php /var/www/html/occ softwarecatalog:debug:organization-membership --organization=UUID +docker exec -u 33 master-nextcloud-1 php /var/www/html/occ stackiq:debug:organization-membership --organization=UUID ``` ## Future Enhancements ### Planned Features -- Bidirectional sync (OpenRegister → SoftwareCatalog) +- Bidirectional sync (OpenRegister → Stackiq) - Bulk organization operations - Advanced status mapping - Custom field synchronization @@ -567,6 +567,6 @@ docker exec -u 33 master-nextcloud-1 php /var/www/html/occ softwarecatalog:debug ## Conclusion -The organization synchronization feature provides seamless integration between SoftwareCatalog and OpenRegister, ensuring data consistency and proper user management. The comprehensive testing framework ensures reliable operation across various scenarios and edge cases. +The organization synchronization feature provides seamless integration between Stackiq and OpenRegister, ensuring data consistency and proper user management. The comprehensive testing framework ensures reliable operation across various scenarios and edge cases. -For questions or issues, refer to the SoftwareCatalog documentation or contact the development team. \ No newline at end of file +For questions or issues, refer to the Stackiq documentation or contact the development team. \ No newline at end of file diff --git a/docs/README.md b/docs/README.md index 17a9a7f9..31676902 100644 --- a/docs/README.md +++ b/docs/README.md @@ -52,7 +52,7 @@ Welcome to the Software Catalog app documentation. This app provides comprehensi ## System Architecture ``` -OpenRegister Events → SoftwareCatalogEventListener → SoftwareCatalogueService +OpenRegister Events → StackiqEventListener → StackiqService ↓ User Creation ← Group Assignment ← Organization Processing ← Manager Assignment ↓ ↓ ↓ ↓ diff --git a/docs/TESTING_ORGANIZATION_SYNC.md b/docs/TESTING_ORGANIZATION_SYNC.md index 0ff78ea9..2bd7fca4 100644 --- a/docs/TESTING_ORGANIZATION_SYNC.md +++ b/docs/TESTING_ORGANIZATION_SYNC.md @@ -1,7 +1,7 @@ # Organization Synchronization Testing Guide **Date:** July 24, 2025 -**App:** SoftwareCatalog +**App:** Stackiq **Feature:** Organization Synchronization with OpenRegister ## 🚨 ESSENTIAL INFORMATION FOR NEW CONVERSATION @@ -15,16 +15,16 @@ ### Critical API Endpoints -#### SoftwareCatalog Sync API (NEW) +#### Stackiq Sync API (NEW) ```bash # Manual sync trigger -curl -u 'admin:admin' -X POST 'http://localhost/index.php/apps/softwarecatalog/api/settings/sync' +curl -u 'admin:admin' -X POST 'http://localhost/index.php/apps/stackiq/api/settings/sync' # Get sync status -curl -u 'admin:admin' 'http://localhost/index.php/apps/softwarecatalog/api/settings/sync-status' +curl -u 'admin:admin' 'http://localhost/index.php/apps/stackiq/api/settings/sync-status' # Check sync configuration -curl -u 'admin:admin' 'http://localhost/index.php/apps/softwarecatalog/api/settings' +curl -u 'admin:admin' 'http://localhost/index.php/apps/stackiq/api/settings' ``` #### OpenRegister API (Authenticated) @@ -94,10 +94,10 @@ docker-compose exec -u 33 nextcloud php /var/www/html/occ user:info {username} | #### Configuration ```bash -# Check SoftwareCatalog configuration -docker-compose exec -u 33 nextcloud php /var/www/html/occ config:app:get softwarecatalog voorzieningen_organisatie_schema -docker-compose exec -u 33 nextcloud php /var/www/html/occ config:app:get softwarecatalog voorzieningen_contactpersoon_schema -docker-compose exec -u 33 nextcloud php /var/www/html/occ config:app:get softwarecatalog voorzieningen_register +# Check Stackiq configuration +docker-compose exec -u 33 nextcloud php /var/www/html/occ config:app:get stackiq voorzieningen_organisatie_schema +docker-compose exec -u 33 nextcloud php /var/www/html/occ config:app:get stackiq voorzieningen_contactpersoon_schema +docker-compose exec -u 33 nextcloud php /var/www/html/occ config:app:get stackiq voorzieningen_register ``` ### Log Reading @@ -110,8 +110,8 @@ docker-compose exec nextcloud tail -f /var/www/html/data/nextcloud.log # Filter for OrganizationSyncService events (NEW) docker-compose exec nextcloud tail -f /var/www/html/data/nextcloud.log | grep -i "organizationsyncservice" -# Filter for SoftwareCatalog events -docker-compose exec nextcloud tail -f /var/www/html/data/nextcloud.log | grep -i "softwarecatalog" +# Filter for Stackiq events +docker-compose exec nextcloud tail -f /var/www/html/data/nextcloud.log | grep -i "stackiq" # Filter for specific organization UUID docker-compose exec nextcloud tail -f /var/www/html/data/nextcloud.log | grep "{UUID}" @@ -166,7 +166,7 @@ docker-compose exec nextcloud grep -i "organizationcontactsyncjob" /var/www/html #### 1. Cron-Based Synchronization (PRIORITY) - **Status**: ✅ Code implemented, 🔄 Testing needed - **Background Job**: `OrganizationContactSyncJob` runs every 5 minutes -- **Manual Trigger**: `POST /apps/softwarecatalog/api/settings/sync` +- **Manual Trigger**: `POST /apps/stackiq/api/settings/sync` - **Expected**: Organizations synchronized, entities created, users managed - **Logging**: Comprehensive step-by-step logging in `OrganizationSyncService` @@ -184,7 +184,7 @@ docker-compose exec nextcloud grep -i "organizationcontactsyncjob" /var/www/html #### 3. User Status Management - **Status**: ✅ Implemented, 🔄 Testing needed - **Test**: Change organization `beoordeling` from `actief` to `inactief` -- **Expected**: Only SoftwareCatalog users deactivated, admin users protected +- **Expected**: Only Stackiq users deactivated, admin users protected ### Known Issues and Solutions @@ -206,7 +206,7 @@ docker-compose exec nextcloud grep -i "organizationcontactsyncjob" /var/www/html #### 4. UUID Format Mismatch (CURRENT ISSUE) - **Problem**: Organization object UUIDs use standard format (with hyphens: `ddaf232b-acbc-4396-946d-f80ccc2d3eb1`) but OpenRegister expects 32-character hex strings (without hyphens: `ddaf232bacbc4396946df80ccc2d3eb1`) - **Error**: `"Field 'uuid' doesn't have a default value"` when creating organization entities -- **Root Cause**: OpenConnector tries to create organization entity immediately, before SoftwareCatalog event listener can process it +- **Root Cause**: OpenConnector tries to create organization entity immediately, before Stackiq event listener can process it - **Solution**: ✅ Implemented UUID format conversion in `createOrganisationInOpenRegister()` method - **Status**: 🔄 **PARTIALLY FIXED** - Works for authenticated API calls, but OpenConnector still has the issue - **Next Steps**: Need to fix OpenConnector's `ObjectService::createFromArray()` method to handle UUID format conversion @@ -244,17 +244,17 @@ docker-compose exec nextcloud curl -s -u 'admin:admin' -H 'Content-Type: applica }' ``` -This will trigger the same SoftwareCatalog event listener and test our UUID fix without the OpenConnector issue. +This will trigger the same Stackiq event listener and test our UUID fix without the OpenConnector issue. ### Debugging Commands #### Synchronization Testing (NEW) ```bash # Test manual synchronization -curl -u 'admin:admin' -X POST 'http://localhost/index.php/apps/softwarecatalog/api/settings/sync' +curl -u 'admin:admin' -X POST 'http://localhost/index.php/apps/stackiq/api/settings/sync' # Check sync status -curl -u 'admin:admin' 'http://localhost/index.php/apps/softwarecatalog/api/settings/sync-status' +curl -u 'admin:admin' 'http://localhost/index.php/apps/stackiq/api/settings/sync-status' # Monitor synchronization logs docker-compose exec nextcloud tail -f /var/www/html/data/nextcloud.log | grep -i "organizationsyncservice" @@ -279,8 +279,8 @@ docker-compose exec nextcloud tail -f /var/www/html/data/nextcloud.log | grep -E ``` ### File Locations -- **Main Service**: `/var/www/html/apps-extra/softwarecatalog/lib/Service/SoftwareCatalogueService.php` -- **Event Listener**: `/var/www/html/apps-extra/softwarecatalog/lib/EventListener/SoftwareCatalogEventListener.php` +- **Main Service**: `/var/www/html/apps-extra/stackiq/lib/Service/StackiqueService.php` +- **Event Listener**: `/var/www/html/apps-extra/stackiq/lib/EventListener/StackiqEventListener.php` - **Logs**: `/var/www/html/data/nextcloud.log` - **Configuration**: `/var/www/html/config/config.php` @@ -300,7 +300,7 @@ This document provides comprehensive testing scenarios for the organization sync ### Configuration Verification Before testing, verify the Software Catalog configuration: ```bash -docker exec -it -u 33 master-nextcloud-1 bash -c "curl -u 'admin:admin' 'http://localhost/index.php/apps/softwarecatalog/api/settings'" +docker exec -it -u 33 master-nextcloud-1 bash -c "curl -u 'admin:admin' 'http://localhost/index.php/apps/stackiq/api/settings'" ``` Expected configuration: @@ -318,7 +318,7 @@ Expected configuration: #### 0.1 Manual Synchronization Test 1. Trigger manual synchronization: ```bash -curl -u 'admin:admin' -X POST 'http://localhost/index.php/apps/softwarecatalog/api/settings/sync' +curl -u 'admin:admin' -X POST 'http://localhost/index.php/apps/stackiq/api/settings/sync' ``` 2. Monitor the logs for detailed execution steps: @@ -328,7 +328,7 @@ docker-compose exec nextcloud tail -f /var/www/html/data/nextcloud.log | grep -i 3. Check sync status: ```bash -curl -u 'admin:admin' 'http://localhost/index.php/apps/softwarecatalog/api/settings/sync-status' +curl -u 'admin:admin' 'http://localhost/index.php/apps/stackiq/api/settings/sync-status' ``` **Expected Log Output**: @@ -493,9 +493,9 @@ docker exec -it -u 33 master-nextcloud-1 bash -c "curl -u 'admin:admin' 'http:// - All users in organization follow organization status - User accounts properly activated/deactivated in Nextcloud -### 7. SoftwareCatalog-Specific User Activation/Deactivation Test +### 7. Stackiq-Specific User Activation/Deactivation Test -**Objective**: Verify that when an organization status changes, only SoftwareCatalog-specific users (from contactpersoon objects) are activated/deactivated, while admin group users remain unaffected. +**Objective**: Verify that when an organization status changes, only Stackiq-specific users (from contactpersoon objects) are activated/deactivated, while admin group users remain unaffected. **Test Steps**: @@ -529,7 +529,7 @@ docker exec -u 33 master-nextcloud-1 php /var/www/html/occ user:list | grep -E " docker exec -it -u 33 master-nextcloud-1 bash -c "curl -u 'admin:admin' -H 'Content-Type: application/json' -X PUT -d '{\"naam\":\"Test Org with Users\",\"website\":\"https://test-users.org\",\"type\":\"Leverancier\",\"beoordeling\":\"inactief\"}' 'http://localhost/index.php/apps/openregister/api/objects/6/35/{ORGANIZATION_ID}'" ``` -5. Verify SoftwareCatalog users are deactivated but admin users remain active: +5. Verify Stackiq users are deactivated but admin users remain active: ```bash # Check user status after deactivation docker exec -u 33 master-nextcloud-1 php /var/www/html/occ user:list | grep -E "john.doe|jane.smith|admin" @@ -539,7 +539,7 @@ docker exec -u 33 master-nextcloud-1 php /var/www/html/occ user:info admin ``` **Expected Results**: -- SoftwareCatalog users (john.doe, jane.smith) are deactivated +- Stackiq users (john.doe, jane.smith) are deactivated - Admin group users remain active and unaffected - Organization status shows as "inactief" @@ -549,7 +549,7 @@ docker exec -u 33 master-nextcloud-1 php /var/www/html/occ user:info admin docker exec -it -u 33 master-nextcloud-1 bash -c "curl -u 'admin:admin' -H 'Content-Type: application/json' -X PUT -d '{\"naam\":\"Test Org with Users\",\"website\":\"https://test-users.org\",\"type\":\"Leverancier\",\"beoordeling\":\"actief\"}' 'http://localhost/index.php/apps/openregister/api/objects/6/35/{ORGANIZATION_ID}'" ``` -2. Verify SoftwareCatalog users are reactivated: +2. Verify Stackiq users are reactivated: ```bash # Check user status after reactivation docker exec -u 33 master-nextcloud-1 php /var/www/html/occ user:list | grep -E "john.doe|jane.smith|admin" @@ -561,7 +561,7 @@ docker exec -u 33 master-nextcloud-1 php /var/www/html/occ user:info admin ``` **Expected Results**: -- SoftwareCatalog users (john.doe, jane.smith) are reactivated +- Stackiq users (john.doe, jane.smith) are reactivated - Admin group users remain active - Organization status shows as "actief" @@ -637,7 +637,7 @@ docker exec -u 33 master-nextcloud-1 php /var/www/html/occ user:info regular.use **Expected Results**: - Admin users (admin, testadmin) remain active -- Regular SoftwareCatalog user (regular.user) is deactivated +- Regular Stackiq user (regular.user) is deactivated - Admin group users are completely protected from organization status changes ### 10. Bulk User Management Test @@ -936,8 +936,8 @@ docker-compose exec nextcloud curl -X POST "http://localhost/index.php/apps/open **Current Status**: - ✅ **Endpoint accessible**: The OpenConnector endpoint is working and accessible from within the Docker container - ❌ **UUID Issue**: Currently getting "Field 'uuid' doesn't have a default value" error when creating organization entities -- 🔄 **Fix in progress**: UUID format conversion implemented in SoftwareCatalog service (standard UUID with hyphens → 32-char hex string) -- ⚠️ **OpenConnector Issue**: The error occurs in OpenConnector before SoftwareCatalog event listener can process it +- 🔄 **Fix in progress**: UUID format conversion implemented in Stackiq service (standard UUID with hyphens → 32-char hex string) +- ⚠️ **OpenConnector Issue**: The error occurs in OpenConnector before Stackiq event listener can process it **Expected Results** (once UUID issue is resolved): - Organization object created successfully via OpenConnector @@ -1010,17 +1010,17 @@ docker exec -u 33 master-nextcloud-1 php /var/www/html/occ user:info anonymous.c ### Check Event Logs ```bash -docker logs master-nextcloud-1 --since 10m | grep -E "\[SoftwareCatalog\]|\[ObjectCreatedEvent\]|\[ObjectUpdatedEvent\]|\[ObjectDeletedEvent\]" +docker logs master-nextcloud-1 --since 10m | grep -E "\[Stackiq\]|\[ObjectCreatedEvent\]|\[ObjectUpdatedEvent\]|\[ObjectDeletedEvent\]" ``` ### Check App Status ```bash -docker exec -u 33 master-nextcloud-1 php /var/www/html/occ app:list | grep softwarecatalog +docker exec -u 33 master-nextcloud-1 php /var/www/html/occ app:list | grep stackiq ``` ### Enable App if Needed ```bash -docker exec -u 33 master-nextcloud-1 php /var/www/html/occ app:enable softwarecatalog +docker exec -u 33 master-nextcloud-1 php /var/www/html/occ app:enable stackiq ``` ### Check Nextcloud Logs diff --git a/docs/USER_GUIDE.md b/docs/USER_GUIDE.md index 266bc4df..86748cdf 100644 --- a/docs/USER_GUIDE.md +++ b/docs/USER_GUIDE.md @@ -179,7 +179,7 @@ SoftwareCatalog: Set user manager: john.smith → jane.doe To add new role-based groups: -1. **Code Modification**: Update SoftwareCatalogueService +1. **Code Modification**: Update StackiqService 2. **Documentation**: Document new role meanings 3. **Testing**: Verify assignment works correctly diff --git a/docs/View_API.md b/docs/View_API.md index 584301ce..7e00d381 100644 --- a/docs/View_API.md +++ b/docs/View_API.md @@ -283,15 +283,15 @@ The API is designed to support future enhancements: ```bash # Test basic functionality -curl -X GET "http://localhost/index.php/apps/softwarecatalog/api/views" \\ +curl -X GET "http://localhost/index.php/apps/stackiq/api/views" \\ -H "Content-Type: application/json" # Test with enrichment -curl -X GET "http://localhost/index.php/apps/softwarecatalog/api/views?include_products=true" \\ +curl -X GET "http://localhost/index.php/apps/stackiq/api/views?include_products=true" \\ -H "Content-Type: application/json" # Test specific view -curl -X GET "http://localhost/index.php/apps/softwarecatalog/api/views/view-lv01" \\ +curl -X GET "http://localhost/index.php/apps/stackiq/api/views/view-lv01" \\ -H "Content-Type: application/json" ``` diff --git a/docs/docusaurus.config.js b/docs/docusaurus.config.js index c9951d1c..d0fec761 100644 --- a/docs/docusaurus.config.js +++ b/docs/docusaurus.config.js @@ -1,12 +1,12 @@ // @ts-check /** - * SoftwareCatalog documentation site. + * Stackiq documentation site. * * Built on @conduction/docusaurus-preset for brand defaults (tokens, * theme swizzles for Navbar / Footer, four-locale i18n scaffolding, * KvK / BTW copyright). Site-specific overrides — locales, sidebar - * path, mermaid theme, custom prism themes, softwarecatalog-only + * path, mermaid theme, custom prism themes, stackiq-only * navbar items — are passed through createConfig() opts. */ @@ -19,16 +19,16 @@ const { createConfig, baseFooterLinks } = require('@conduction/docusaurus-preset const BRAND_THEME = require.resolve('@conduction/docusaurus-preset/theme'); const config = createConfig({ - title: 'SoftwareCatalog', + title: 'Stackiq', tagline: 'IT-asset management on Nextcloud. Software inventory, licenses, contracts, dependencies. One register, every install.', url: 'https://softwarecatalog.conduction.nl', baseUrl: '/', organizationName: 'ConductionNL', - projectName: 'softwarecatalog', + projectName: 'stackiq', /* The brand preset's default i18n block (nl/en/de/fr) is replaced - wholesale here. SoftwareCatalog docs ship with NL + EN translation + wholesale here. Stackiq docs ship with NL + EN translation surfaces; keep both. */ i18n: { defaultLocale: 'en', @@ -39,10 +39,10 @@ const config = createConfig({ }, }, - /* The softwarecatalog docs source lives at the repo root of `docs/` + /* The stackiq docs source lives at the repo root of `docs/` rather than under a `docs/` subfolder, so we override the preset's default `presets:` block to point `docs.path` at './' and disable - the blog plugin. customCss carries softwarecatalog-specific CSS + the blog plugin. customCss carries stackiq-specific CSS only — brand tokens and the theme swizzles are auto-loaded by the brand theme entry in `themes:` below. */ presets: [ @@ -57,7 +57,7 @@ const config = createConfig({ plus the standard node_modules bucket. */ exclude: ['**/node_modules/**', 'src/**'], sidebarPath: require.resolve('./sidebars.js'), - editUrl: 'https://codeberg.org/Conduction/softwarecatalog/src/branch/main/docs/', + editUrl: 'https://codeberg.org/Conduction/stackiq/src/branch/main/docs/', }, blog: false, theme: { @@ -70,7 +70,7 @@ const config = createConfig({ themes: [BRAND_THEME, '@docusaurus/theme-mermaid'], /* Brand navbar provides locale dropdown + GitHub by default; we - replace items[] with softwarecatalog's own (Documentation sidebar + replace items[] with stackiq's own (Documentation sidebar link, GitHub link, locale dropdown). */ navbar: { items: [ @@ -81,7 +81,7 @@ const config = createConfig({ label: 'Documentation', }, { - href: 'https://codeberg.org/Conduction/softwarecatalog', + href: 'https://codeberg.org/Conduction/stackiq', label: 'GitHub', position: 'right', }, @@ -107,7 +107,7 @@ const config = createConfig({ /* themeConfig is shallow-merged into the preset's defaults (colorMode + navbar + footer). prism + mermaid land alongside. */ themeConfig: { - image: 'img/og-softwarecatalog.png', + image: 'img/og-stackiq.png', prism: { theme: require('prism-react-renderer/themes/github'), darkTheme: require('prism-react-renderer/themes/dracula'), diff --git a/docs/features/README.md b/docs/features/README.md index c6a7fc74..82747cae 100644 --- a/docs/features/README.md +++ b/docs/features/README.md @@ -50,7 +50,7 @@ The **Applications** (`/modules`) and **Services** (`/diensten`) index pages off **Key service:** `lib/Service/FacetService.php` **Controller:** `lib/Controller/FacetController.php` -**Endpoint:** `GET /apps/softwarecatalog/api/facets/{schema}` (`schema`: `module` or `dienst`) +**Endpoint:** `GET /apps/stackiq/api/facets/{schema}` (`schema`: `module` or `dienst`) ## Module Tracking @@ -120,8 +120,8 @@ Software Catalogus can synchronise catalogue data across organisations via the o This enables a federated, collaborative catalogue across Dutch municipalities without a central authority. -**Key service:** `lib/Service/SoftwareCatalogueService.php` -**Subservices:** `lib/Service/SoftwareCatalogue/` (dedicated handlers per sync scenario) +**Key service:** `lib/Service/StackiqService.php` +**Subservices:** `lib/Service/Stackiq/` (dedicated handlers per sync scenario) ## Automatic User Provisioning diff --git a/docs/features/catalog-ratings.md b/docs/features/catalog-ratings.md index 1a9d05af..a3c194e1 100644 --- a/docs/features/catalog-ratings.md +++ b/docs/features/catalog-ratings.md @@ -10,7 +10,7 @@ Turns the previously dormant `beoordeeling` (review) schema into a working, and closes the authorization hole it shipped with (world-readable, no create/update/delete rules, no attributable author). See [VNG Softwarecatalogus issue #49](https://github.com/VNG-Realisatie/Softwarecatalogus/issues/49) -and softwarecatalog#375. +and stackiq#375. Specification: [`openspec/specs/catalog-ratings/spec.md`](../../openspec/specs/catalog-ratings/spec.md). @@ -34,7 +34,7 @@ session, bound server-side by `ReviewService`; anything the client sends for `auteur` is discarded. ``` -POST /apps/softwarecatalog/api/reviews +POST /apps/stackiq/api/reviews { "review": {"naam": "Solid intake flow", "waardering": 9, "beschrijvingLang": "..."}, "subjectType": "module", "subjectId": "" } ``` @@ -92,7 +92,7 @@ approved reviews shows a null average / zero count rather than an error. - **No `DienstDetail` page yet.** The submit/aggregate backend is subject-type-agnostic (`module` or `dienst`), and `beoordeeling` already - supports a `diensten` relation, but the softwarecatalog manifest has no + supports a `diensten` relation, but the stackiq manifest has no `/diensten/:id` detail route today (`Diensten` is a `type: custom` faceted index with no per-row detail page) — that is a pre-existing gap unrelated to the authorization fix this change makes. Filed as a follow-up to wire diff --git a/docs/features/eol-feed-integration.md b/docs/features/eol-feed-integration.md index e47be549..e1443e45 100644 --- a/docs/features/eol-feed-integration.md +++ b/docs/features/eol-feed-integration.md @@ -16,7 +16,7 @@ what populates the field they already read. Specification: [`openspec/specs/eol-feed-integration/spec.md`](../../openspec/specs/eol-feed-integration/spec.md). -## Architecture: softwarecatalog never calls endoflife.date +## Architecture: stackiq never calls endoflife.date All fetching of endoflife.date data happens in the sibling **openconnector** `endoflife-date-source` change — a Source + Synchronization + Mapping that @@ -33,7 +33,7 @@ openconnector (sibling repo, optional) endoflife-date-source: fetches endoflife.date → eolProduct/eolCycle objects │ read-only, via ObjectService — NO HTTP here ▼ -softwarecatalog (this feature) +stackiq (this feature) module.eolProductSlug ──┐ (mapping config, per product) │ EolSyncJob (scheduled) ─► EolSyncService ─► EolMatcherService @@ -120,7 +120,7 @@ Reason codes surfaced in the settings status panel: - **Register slug** / **eolProduct schema slug** / **eolCycle schema slug** — pre-filled with the names the openconnector `endoflife-date-source` change provisions (`openconnector` / `eolProduct` / `eolCycle`). Editable - without a code change, since openconnector and softwarecatalog are + without a code change, since openconnector and stackiq are separate release trains and the provisioned names could differ. - **Sync interval (minutes)** — how often the scheduled job re-runs (minimum enforced: 5 minutes). @@ -131,10 +131,10 @@ Reason codes surfaced in the settings status panel: ## API ``` -GET /apps/softwarecatalog/api/eol-sync/config — current configuration -POST /apps/softwarecatalog/api/eol-sync/config — update configuration -POST /apps/softwarecatalog/api/eol-sync/trigger — run a sync now, returns status -GET /apps/softwarecatalog/api/eol-sync/status — last-recorded status +GET /apps/stackiq/api/eol-sync/config — current configuration +POST /apps/stackiq/api/eol-sync/config — update configuration +POST /apps/stackiq/api/eol-sync/trigger — run a sync now, returns status +GET /apps/stackiq/api/eol-sync/status — last-recorded status ``` All four endpoints require Nextcloud admin-group authorization (the default diff --git a/docs/features/multi-org-membership.md b/docs/features/multi-org-membership.md index 069c3eab..0bb3456c 100644 --- a/docs/features/multi-org-membership.md +++ b/docs/features/multi-org-membership.md @@ -18,7 +18,7 @@ gemeentelijke herindeling. See Specification: [`openspec/specs/multi-org-membership/spec.md`](../../openspec/specs/multi-org-membership/spec.md). Everything in this feature is built on OpenRegister's own, already-shipped -`OrganisationService`/`OrganisationController` — SoftwareCatalog does not +`OrganisationService`/`OrganisationController` — Stackiq does not store a separate membership record anywhere. ## Switching your active organisation @@ -29,7 +29,7 @@ organisation's name. Opening it lists every organisation the user belongs to; picking a different one: 1. Calls OpenRegister's own `POST /apps/openregister/api/organisations/{uuid}/set-active` - directly — SoftwareCatalog does not proxy this call. + directly — Stackiq does not proxy this call. 2. OpenRegister verifies, server-side, that the caller is actually a member of that organisation (`Organisation::hasUser()`) before changing anything. A switch to an organisation the user does not belong to is @@ -58,11 +58,11 @@ header switcher, opening a dialog for their currently-active organisation: flow, and never creates a Nextcloud account. - **Revoke access** — remove a member from the list. -Both actions are authorised server-side by a new, SoftwareCatalog-specific +Both actions are authorised server-side by a new, Stackiq-specific check (`OrganisationMembersController::authorizeBeheerder()`) that OpenRegister's own membership endpoints don't perform (OpenRegister's `join`/`leave` only recognise a Nextcloud admin or the organisation's single `owner` field as -allowed to manage another user's membership — SoftwareCatalog's `beheerder` +allowed to manage another user's membership — Stackiq's `beheerder` role is a separate, broader concept). The check requires **both**: 1. The caller is authenticated and in the global `beheerder` Nextcloud group. @@ -77,10 +77,10 @@ A beheerder of one organisation cannot grant or revoke access to a role. ``` -POST /apps/softwarecatalog/api/organisations/{uuid}/members +POST /apps/stackiq/api/organisations/{uuid}/members { "userId": "j.devries" } -DELETE /apps/softwarecatalog/api/organisations/{uuid}/members/{userId} +DELETE /apps/stackiq/api/organisations/{uuid}/members/{userId} ``` ## What this does not do @@ -93,7 +93,7 @@ DELETE /apps/softwarecatalog/api/organisations/{uuid}/members/{userId} govern what each role may read within that organisation are unchanged (`vendor-visibility-rbac`). - **No cross-organisation data merge.** That is a different capability - ([organisation merge](organisation-merge.md), softwarecatalog#370). + ([organisation merge](organisation-merge.md), stackiq#370). ## Screenshots diff --git a/docs/features/organisation-merge.md b/docs/features/organisation-merge.md index 0469ea1d..ee42db62 100644 --- a/docs/features/organisation-merge.md +++ b/docs/features/organisation-merge.md @@ -37,7 +37,7 @@ every object above and returns a count per relation type, without writing anything: ``` -POST /apps/softwarecatalog/api/organisaties/{sourceUuid}/merge/dry-run +POST /apps/stackiq/api/organisaties/{sourceUuid}/merge/dry-run { "targetUuid": "" } ``` @@ -59,7 +59,7 @@ UUID) and execute will refuse it too, with the same validation. ## Executing a merge ``` -POST /apps/softwarecatalog/api/organisaties/{sourceUuid}/merge +POST /apps/stackiq/api/organisaties/{sourceUuid}/merge { "targetUuid": "", "confirm": true } ``` diff --git a/docs/features/portfolio-rationalization-time.md b/docs/features/portfolio-rationalization-time.md index 7875f167..243f2c85 100644 --- a/docs/features/portfolio-rationalization-time.md +++ b/docs/features/portfolio-rationalization-time.md @@ -58,7 +58,7 @@ never fork into two competing sources of the same fact. ## The portfolio rationalization report -`GET /apps/softwarecatalog/api/portfolio-report?organisation={uuid}` +`GET /apps/stackiq/api/portfolio-report?organisation={uuid}` Returns a bounded, organisation-scoped aggregate: @@ -139,7 +139,7 @@ The controller resolves and checks the caller's organisation access ### CSV export -`GET /apps/softwarecatalog/api/portfolio-report?organisation={uuid}&format=csv` +`GET /apps/stackiq/api/portfolio-report?organisation={uuid}&format=csv` The **same** bounded, RBAC-scoped row set as the JSON report, serialised as CSV (one row per gebruik: organisation, module, TIME classification, diff --git a/docs/features/sbom-import.md b/docs/features/sbom-import.md index 017c79d6..3ece825c 100644 --- a/docs/features/sbom-import.md +++ b/docs/features/sbom-import.md @@ -21,7 +21,7 @@ Choose a format (CycloneDX or SPDX, both JSON) and a file, then **Import SBOM**: ``` -POST /apps/softwarecatalog/api/moduleversies/{moduleVersieUuid}/sbom +POST /apps/stackiq/api/moduleversies/{moduleVersieUuid}/sbom multipart/form-data: sbomFile=, format=cyclonedx-json|spdx-json ``` diff --git a/docs/features/suite-wizard.md b/docs/features/suite-wizard.md index f97afba4..ad74ff90 100644 --- a/docs/features/suite-wizard.md +++ b/docs/features/suite-wizard.md @@ -10,7 +10,7 @@ product made up of one or more existing applications, e.g. "Centric Leefomgeving" — and attach its member applications in one guided pass. This replaces the retired incumbent "product" concept per [VNG Softwarecatalogus issue #242](https://github.com/VNG-Realisatie/Softwarecatalogus/issues/242) -and softwarecatalog#372. +and stackiq#372. Specification: [`openspec/specs/suite-wizard/spec.md`](../../openspec/specs/suite-wizard/spec.md). diff --git a/docs/gmail-configuration-manual.md b/docs/gmail-configuration-manual.md index b13c3bf4..0f1b6b29 100644 --- a/docs/gmail-configuration-manual.md +++ b/docs/gmail-configuration-manual.md @@ -68,7 +68,7 @@ Fill in the following settings: | Setting | Value | Example | |---------|--------|---------| -| **Sender Email** | `your-email@gmail.com` | `softwarecatalog@yourorg.com` | +| **Sender Email** | `your-email@gmail.com` | `stackiq@yourorg.com` | | **Sender Name** | `Your Organization Name` | `Software Catalog Team` | ### Configure Email Types (Optional) diff --git a/docs/i18n/nl/docusaurus-theme-classic/footer.json b/docs/i18n/nl/docusaurus-theme-classic/footer.json index 1056ef3f..1c75a88c 100644 --- a/docs/i18n/nl/docusaurus-theme-classic/footer.json +++ b/docs/i18n/nl/docusaurus-theme-classic/footer.json @@ -13,7 +13,7 @@ }, "link.item.label.GitHub": { "message": "GitHub", - "description": "The label of footer link with label=GitHub linking to https://codeberg.org/Conduction/softwarecatalog" + "description": "The label of footer link with label=GitHub linking to https://codeberg.org/Conduction/stackiq" }, "copyright": { "message": "Copyright © 2026 for Open Webconcept by Conduction B.V.", diff --git a/docs/package-lock.json b/docs/package-lock.json index 39a3f6bd..46eb5d68 100644 --- a/docs/package-lock.json +++ b/docs/package-lock.json @@ -1,11 +1,11 @@ { - "name": "softwarecatalog-docs", + "name": "stackiq-docs", "version": "0.0.0", "lockfileVersion": 3, "requires": true, "packages": { "": { - "name": "softwarecatalog-docs", + "name": "stackiq-docs", "version": "0.0.0", "dependencies": { "@conduction/docusaurus-preset": "^3.26.0", diff --git a/docs/package.json b/docs/package.json index 6bb31b55..26af351e 100644 --- a/docs/package.json +++ b/docs/package.json @@ -1,5 +1,5 @@ { - "name": "softwarecatalog-docs", + "name": "stackiq-docs", "version": "0.0.0", "private": true, "scripts": { diff --git a/docs/security/vendor-visibility-rbac.md b/docs/security/vendor-visibility-rbac.md index ff9e3a38..21dee488 100644 --- a/docs/security/vendor-visibility-rbac.md +++ b/docs/security/vendor-visibility-rbac.md @@ -6,7 +6,7 @@ or `contract` OpenRegister object, enumerated with its authorization posture and the test(s) that cover it, per [REQ-007](../../openspec/specs/vendor-visibility-rbac/spec.md#requirement-every-route-touching-gebruik-koppeling-or-contract-objects-must-have-a-documented-tested-authorization-posture-req-007). -**Updated by `schema-rbac-hardening`** (softwarecatalog #379, #390, #378): +**Updated by `schema-rbac-hardening`** (stackiq #379, #390, #378): closed the two follow-up gaps this audit originally flagged below — the `gebruik`/`koppeling`/`organisatie` schema-level RBAC gap and the `AanbodController::getAanbod()` implicit-guard gap — and extended the @@ -43,14 +43,14 @@ controllers named in the proposal's discovery phase. | `PUT /api/publication/{objectType}/{uuid}/publish`, `DELETE /api/publication/{objectType}/{uuid}/depublish` | `PublicationController::publish` / `depublish` | dienst/module/koppeling/organisatie | Write path only (sets `publicatiedatum`/`depublicatiedatum`); not a bulk read. Already has a per-object ownership guard (admin, or aanbod-beheerder whose org owns the entry). Out of scope per proposal ("Changes to the open-data publishing mechanism ... out of scope"). | Pre-existing | | `GET /api/contracts/approval/config` | `ContractApprovalController::config` | — | Authenticated-only; returns a boolean config flag, no contract data. Not a contract read. | N/A | | `POST /api/contracts/{contractUuid}/approval/submit`, `POST /api/contracts/{contractUuid}/approval/renewal` | `ContractApprovalController::submit` / `submitRenewal` | contract | Write (delegation) path only — no contract data returned. Per-object ownership guard confirmed present (`authorizeContract()` → `ContractApprovalService::authorizeSubmit()`, `_organisation`-matched). Confirmed correct, unaffected by this change. | Pre-existing `ContractApprovalControllerTest` | -| *(no app-local route)* | Contract object reads (list/detail) | contract | Contract CRUD runs entirely through the OpenRegister object store (ADR-022, `contract-administration`) via the manifest renderer — there is no SoftwareCatalog controller for contract reads. Visibility is governed exclusively by the OpenRegister `contract` schema's own `authorization.read` RBAC rule. **Fixed (REQ-006):** removed the blanket `"public"` grant and the unscoped `"aanbod-beheerder"` grant; `aanbod-beheerder` is now match-scoped to `_organisation == $organisation` in `lib/Settings/softwarecatalogus_register.json`. **Extended (REQ-006, `schema-rbac-hardening`, #390):** every remaining bare role (`functioneel-beheerder`, `gebruik-beheerder`, `vng-raadpleger`, `software-catalog-users`, `organisatie-beheerder`, `organisaties-beheerder`, `gebruik-raadpleger`) is now match-scoped the same way; `ambtenaar` and `software-catalog-admins` (the app's super-user group) remain deliberately unscoped — see `design.md` Decision 4. | `ContractRbacTest` | -| *(no app-local route)* | Koppeling object reads (list/detail) | koppeling | No SoftwareCatalog controller reads koppeling through the generic OpenRegister object API. Visibility is governed exclusively by the `koppeling` schema's `authorization.read` rule. **Fixed (REQ-008, `schema-rbac-hardening`, #379):** the bare unscoped `gebruik-beheerder` grant is now match-scoped to `_organisation == $organisation`. Every app-local `koppeling` read (see `AangebodenGebruikController`/`AanbodController` rows above) already bypasses schema RBAC with `_rbac:false` and does its own scoping, so this was not a live leak through those routes — but was live-exploitable via any generic OpenRegister object-API read outside this app. | `SchemaRbacTest` | -| *(no app-local route)* | Organisatie object reads (list/detail) | organisatie | No SoftwareCatalog controller reads organisatie through the generic OpenRegister object API. Visibility is governed exclusively by the `organisatie` schema's `authorization.read` rule (plus its three pre-existing `public` match rules for active organisaties, unaffected by this change). **Fixed (REQ-008, `schema-rbac-hardening`, #379):** the bare unscoped `gebruik-beheerder` grant is now match-scoped to `_organisation == $organisation`; only non-public/inactive organisatie records were affected. | `SchemaRbacTest` | +| *(no app-local route)* | Contract object reads (list/detail) | contract | Contract CRUD runs entirely through the OpenRegister object store (ADR-022, `contract-administration`) via the manifest renderer — there is no Stackiq controller for contract reads. Visibility is governed exclusively by the OpenRegister `contract` schema's own `authorization.read` RBAC rule. **Fixed (REQ-006):** removed the blanket `"public"` grant and the unscoped `"aanbod-beheerder"` grant; `aanbod-beheerder` is now match-scoped to `_organisation == $organisation` in `lib/Settings/softwarecatalogus_register.json`. **Extended (REQ-006, `schema-rbac-hardening`, #390):** every remaining bare role (`functioneel-beheerder`, `gebruik-beheerder`, `vng-raadpleger`, `software-catalog-users`, `organisatie-beheerder`, `organisaties-beheerder`, `gebruik-raadpleger`) is now match-scoped the same way; `ambtenaar` and `software-catalog-admins` (the app's super-user group) remain deliberately unscoped — see `design.md` Decision 4. | `ContractRbacTest` | +| *(no app-local route)* | Koppeling object reads (list/detail) | koppeling | No Stackiq controller reads koppeling through the generic OpenRegister object API. Visibility is governed exclusively by the `koppeling` schema's `authorization.read` rule. **Fixed (REQ-008, `schema-rbac-hardening`, #379):** the bare unscoped `gebruik-beheerder` grant is now match-scoped to `_organisation == $organisation`. Every app-local `koppeling` read (see `AangebodenGebruikController`/`AanbodController` rows above) already bypasses schema RBAC with `_rbac:false` and does its own scoping, so this was not a live leak through those routes — but was live-exploitable via any generic OpenRegister object-API read outside this app. | `SchemaRbacTest` | +| *(no app-local route)* | Organisatie object reads (list/detail) | organisatie | No Stackiq controller reads organisatie through the generic OpenRegister object API. Visibility is governed exclusively by the `organisatie` schema's `authorization.read` rule (plus its three pre-existing `public` match rules for active organisaties, unaffected by this change). **Fixed (REQ-008, `schema-rbac-hardening`, #379):** the bare unscoped `gebruik-beheerder` grant is now match-scoped to `_organisation == $organisation`; only non-public/inactive organisatie records were affected. | `SchemaRbacTest` | ## Findings summary - **Fixed by `vendor-visibility-rbac`:** `GET /api/gebruik` (gebruik-beheerder cross-org leak, discovery.md finding 2), `GET /api/aangeboden-gebruik/afnemer` (implicit-only auth), OpenRegister `contract` schema RBAC read rule (blanket `public` + unscoped `aanbod-beheerder`). -- **Fixed by `schema-rbac-hardening`** (softwarecatalog #379, #390, #378): `koppeling` and `organisatie` schema RBAC (`gebruik-beheerder` unscoped grant), the remaining bare roles on `contract` schema RBAC beyond `aanbod-beheerder`, and `AanbodController::getAanbod()`'s implicit-only auth guard. See REQ-008/REQ-009 and the "Schema-level RBAC layer" section below. +- **Fixed by `schema-rbac-hardening`** (stackiq #379, #390, #378): `koppeling` and `organisatie` schema RBAC (`gebruik-beheerder` unscoped grant), the remaining bare roles on `contract` schema RBAC beyond `aanbod-beheerder`, and `AanbodController::getAanbod()`'s implicit-only auth guard. See REQ-008/REQ-009 and the "Schema-level RBAC layer" section below. - **Confirmed correct, now regression-tested:** `GET /api/koppelingen-gebruik/{uuid}`, `GET /api/aangeboden-gebruik/{afnemer,deelnemers}`, `GET /api/gebruik/deelnemer`. - **Confirmed correct, unaffected (already had their own field-scoping or role guard):** `GET /api/views` (gebruik/deelnames-gebruik enrichment), `GET/POST /api/aangeboden-gebruik/ambtenaar*`, all `PublicationController`/`ContractApprovalController`/`AangebodenGebruikController` write paths. - **Documented accepted residual (not fixed, not silently dropped):** the `gebruik.deelnemers` array-membership sharing case — see below. @@ -101,7 +101,7 @@ implemented ad hoc in an app-level register config. ## Deployment caveat: silent no-op until #391 lands `schema-rbac-hardening`'s register-JSON fixes have **no runtime effect on -any already-installed instance** until softwarecatalog #391 +any already-installed instance** until stackiq #391 (`register-import-reliability`) lands — the repair-step importer currently no-ops when a register/schema it has already imported once is edited again. This change must ship after, or together with, #391, and the fix diff --git a/docs/src/pages/index.js b/docs/src/pages/index.js index 501a364b..964f5783 100644 --- a/docs/src/pages/index.js +++ b/docs/src/pages/index.js @@ -1,9 +1,9 @@ /** - * SoftwareCatalog landing page. + * Stackiq landing page. * * Composes the brand + from * @conduction/docusaurus-preset/components, mirroring the connext page - * at sites/www/src/pages/apps/softwarecatalog.mdx. + * at sites/www/src/pages/apps/stackiq.mdx. * * Written as .js (not .mdx) because the docs site has the docs plugin * pointed at `path: './'`, and an MDX file in src/pages/ trips the @@ -25,7 +25,7 @@ import { the tile-grid motif reads as "rows in a register" for both apps (catalog of items vs. catalog of widgets) and ties the two product surfaces together visually. Cited from the connext detail page at - sites/www/src/pages/apps/softwarecatalog.mdx. */ + sites/www/src/pages/apps/stackiq.mdx. */ const SOFTWARECATALOG_ICON = ( @@ -294,37 +294,37 @@ const WIDGETS = [ export default function Home() { return (
} + illustration={} />
diff --git a/docs/static/llms.txt b/docs/static/llms.txt index d7006aed..341e282c 100644 --- a/docs/static/llms.txt +++ b/docs/static/llms.txt @@ -1,8 +1,8 @@ -# SoftwareCatalog +# Stackiq -> SoftwareCatalog is an open-source IT-asset-management app for the Nextcloud workspace. +> Stackiq is an open-source IT-asset-management app for the Nextcloud workspace. -SoftwareCatalog is an open-source IT-asset-management app for the Nextcloud workspace. It keeps a central register of every application, licence, contract, and dependency the organisation runs, with renewal alerts before the auto-renewal date. Dashboard widgets surface upcoming renewals, an inventory snapshot by category, and discovery deltas showing newly added or removed apps. Built for the IT department that needs to know what runs where, without a separate asset database or second login. Released under EUPL-1.2 and maintained by Conduction since 2019. +Stackiq is an open-source IT-asset-management app for the Nextcloud workspace. It keeps a central register of every application, licence, contract, and dependency the organisation runs, with renewal alerts before the auto-renewal date. Dashboard widgets surface upcoming renewals, an inventory snapshot by category, and discovery deltas showing newly added or removed apps. Built for the IT department that needs to know what runs where, without a separate asset database or second login. Released under EUPL-1.2 and maintained by Conduction since 2019. ## Docs @@ -12,8 +12,8 @@ SoftwareCatalog is an open-source IT-asset-management app for the Nextcloud work ## Optional - [Install](https://www.conduction.nl/install): self-host on your Nextcloud instance. -- [Source code](https://codeberg.org/Conduction/softwarecatalog): repository and issue tracker. -- [App page](https://www.conduction.nl/apps/softwarecatalog): product positioning on conduction.nl. +- [Source code](https://codeberg.org/Conduction/stackiq): repository and issue tracker. +- [App page](https://www.conduction.nl/apps/stackiq): product positioning on conduction.nl. ## Contact diff --git a/eslint-suppressions.json b/eslint-suppressions.json index d8594712..855f5d7d 100644 --- a/eslint-suppressions.json +++ b/eslint-suppressions.json @@ -394,7 +394,7 @@ "count": 2 } }, - "src/store/plugins/softwarecatalogPlugin.js": { + "src/store/plugins/stackiqPlugin.js": { "no-console": { "count": 5 }, @@ -474,7 +474,7 @@ "count": 1 } }, - "src/views/settings/SoftwareCatalogSettings.vue": { + "src/views/settings/StackiqSettings.vue": { "jsdoc/require-param-type": { "count": 3 }, diff --git a/issues.md b/issues.md index f44b74ef..10a19085 100644 --- a/issues.md +++ b/issues.md @@ -223,7 +223,7 @@ Previously closed (2026-02-22): #185, #266, #267, #286, #294, #300, #302, #307, **Summary:** As a gebruik-beheerder, we want suppliers (aanbod-beheerder) to not see our application landscapes and connections. The RBAC model scopes data visibility per organization — the page itself may be accessible, but aanbod-beheerder should only see their own organization's data. -**RBAC Reference:** See `softwarecatalog/lib/Settings/softwarecatalogus_register.json`: +**RBAC Reference:** See `stackiq/lib/Settings/softwarecatalogus_register.json`: - `module` (applicatie) schema → `authorization.read`: `{ "group": "aanbod-beheerder", "match": { "_organisation": "$organisation" } }` — own org only - `koppeling` schema → `authorization.read`: `{ "group": "aanbod-beheerder", "match": { "_organisation": "$organisation" } }` — own org only @@ -1778,7 +1778,7 @@ Previously closed (2026-02-22): #185, #266, #267, #286, #294, #300, #302, #307, **Summary:** Contact persons of **gemeenten** (municipalities) are publicly visible but should NOT be. Note: contact persons of **leveranciers** (vendors) ARE expected to be publicly visible — only gemeente/samenwerking contact persons should be hidden. -**RBAC Reference:** See `softwarecatalog/lib/Settings/softwarecatalogus_register.json` → `contactpersoon` schema → `authorization` block. The `contactpersoon` schema does NOT have `public` read access. Leverancier contact persons are exposed via **publications** (which extend contactpersonen), not via direct public access to the contactpersoon schema. +**RBAC Reference:** See `stackiq/lib/Settings/softwarecatalogus_register.json` → `contactpersoon` schema → `authorization` block. The `contactpersoon` schema does NOT have `public` read access. Leverancier contact persons are exposed via **publications** (which extend contactpersonen), not via direct public access to the contactpersoon schema. **Acceptance Criteria:** - [x] [API] Contact persons of **leveranciers** ARE visible on public pages (this is expected/correct behavior) diff --git a/l10n/be.js b/l10n/be.js index 06f6fe51..18cdffbf 100644 --- a/l10n/be.js +++ b/l10n/be.js @@ -1,5 +1,5 @@ OC.L10N.register( - "softwarecatalog", + "stackiq", { "Acquisition" : "Acquisition", "Applications in use for an organisation, grouped by lifecycle phase and ordered by nearest urgency (end-of-support, phase-out or planned replacement)." : "Applications in use for an organisation, grouped by lifecycle phase and ordered by nearest urgency (end-of-support, phase-out or planned replacement).", diff --git a/l10n/bg.js b/l10n/bg.js index fd8a5bfa..d93693a1 100644 --- a/l10n/bg.js +++ b/l10n/bg.js @@ -1,5 +1,5 @@ OC.L10N.register( - "softwarecatalog", + "stackiq", { "Acquisition" : "Acquisition", "Applications in use for an organisation, grouped by lifecycle phase and ordered by nearest urgency (end-of-support, phase-out or planned replacement)." : "Applications in use for an organisation, grouped by lifecycle phase and ordered by nearest urgency (end-of-support, phase-out or planned replacement).", diff --git a/l10n/bs.js b/l10n/bs.js index 6d165269..9ca1053a 100644 --- a/l10n/bs.js +++ b/l10n/bs.js @@ -1,5 +1,5 @@ OC.L10N.register( - "softwarecatalog", + "stackiq", { "Acquisition" : "Acquisition", "Applications in use for an organisation, grouped by lifecycle phase and ordered by nearest urgency (end-of-support, phase-out or planned replacement)." : "Applications in use for an organisation, grouped by lifecycle phase and ordered by nearest urgency (end-of-support, phase-out or planned replacement).", diff --git a/l10n/ca.js b/l10n/ca.js index 9c4c9cc5..a3e7bc15 100644 --- a/l10n/ca.js +++ b/l10n/ca.js @@ -1,5 +1,5 @@ OC.L10N.register( - "softwarecatalog", + "stackiq", { "Acquisition" : "Acquisition", "Applications in use for an organisation, grouped by lifecycle phase and ordered by nearest urgency (end-of-support, phase-out or planned replacement)." : "Applications in use for an organisation, grouped by lifecycle phase and ordered by nearest urgency (end-of-support, phase-out or planned replacement).", diff --git a/l10n/cs.js b/l10n/cs.js index cb456d33..16de9025 100644 --- a/l10n/cs.js +++ b/l10n/cs.js @@ -1,5 +1,5 @@ OC.L10N.register( - "softwarecatalog", + "stackiq", { "Acquisition" : "Acquisition", "Applications in use for an organisation, grouped by lifecycle phase and ordered by nearest urgency (end-of-support, phase-out or planned replacement)." : "Applications in use for an organisation, grouped by lifecycle phase and ordered by nearest urgency (end-of-support, phase-out or planned replacement).", diff --git a/l10n/da.js b/l10n/da.js index d55f3f96..3f96efc2 100644 --- a/l10n/da.js +++ b/l10n/da.js @@ -1,5 +1,5 @@ OC.L10N.register( - "softwarecatalog", + "stackiq", { "Acquisition" : "Acquisition", "Applications in use for an organisation, grouped by lifecycle phase and ordered by nearest urgency (end-of-support, phase-out or planned replacement)." : "Applications in use for an organisation, grouped by lifecycle phase and ordered by nearest urgency (end-of-support, phase-out or planned replacement).", diff --git a/l10n/de.js b/l10n/de.js index 98910613..d8b6c5ae 100644 --- a/l10n/de.js +++ b/l10n/de.js @@ -1,5 +1,5 @@ OC.L10N.register( - "softwarecatalog", + "stackiq", { "Acquisition" : "Acquisition", "Applications in use for an organisation, grouped by lifecycle phase and ordered by nearest urgency (end-of-support, phase-out or planned replacement)." : "Applications in use for an organisation, grouped by lifecycle phase and ordered by nearest urgency (end-of-support, phase-out or planned replacement).", diff --git a/l10n/el.js b/l10n/el.js index 3722bee0..2b107591 100644 --- a/l10n/el.js +++ b/l10n/el.js @@ -1,5 +1,5 @@ OC.L10N.register( - "softwarecatalog", + "stackiq", { "Acquisition" : "Acquisition", "Applications in use for an organisation, grouped by lifecycle phase and ordered by nearest urgency (end-of-support, phase-out or planned replacement)." : "Applications in use for an organisation, grouped by lifecycle phase and ordered by nearest urgency (end-of-support, phase-out or planned replacement).", diff --git a/l10n/en.js b/l10n/en.js index 2295bce9..2e40fbc7 100644 --- a/l10n/en.js +++ b/l10n/en.js @@ -1,5 +1,5 @@ OC.L10N.register( - "softwarecatalog", + "stackiq", { "AMEF elements" : "AMEF elements", "AMEF standards" : "AMEF standards", diff --git a/l10n/en.json b/l10n/en.json index 3b34a715..ee3f047d 100644 --- a/l10n/en.json +++ b/l10n/en.json @@ -537,7 +537,6 @@ "Last run: {matched} matched, {skipped} skipped, at {time}.": "Last run: {matched} matched, {skipped} skipped, at {time}.", "Loading EOL sync configuration…": "Loading EOL sync configuration…", "Loading merge status": "Loading merge status", - "Match catalog products to endoflife.date product cycles ingested via OpenConnector, to keep end-of-support dates data-driven. Softwarecatalog never calls endoflife.date directly.": "Match catalog products to endoflife.date product cycles ingested via OpenConnector, to keep end-of-support dates data-driven. Softwarecatalog never calls endoflife.date directly.", "Merge organisation": "Merge organisation", "Merge organisations": "Merge organisations", "never": "never", @@ -688,6 +687,8 @@ "Could not update the {label}": "Could not update the {label}", "Add a new contact person to organisation: {name}": "Add a new contact person to organisation: {name}", "Failed to add contact person: {error}": "Failed to add contact person: {error}", - "Invalid contact person data structure": "Invalid contact person data structure" + "Invalid contact person data structure": "Invalid contact person data structure", + "Match catalog products to endoflife.date product cycles ingested via OpenConnector, to keep end-of-support dates data-driven. Stackiq never calls endoflife.date directly.": "Match catalog products to endoflife.date product cycles ingested via OpenConnector, to keep end-of-support dates data-driven. Stackiq never calls endoflife.date directly.", + "Stackiq Location URL": "Stackiq Location URL" } } diff --git a/l10n/en_US.js b/l10n/en_US.js index e160e615..6765f0ac 100644 --- a/l10n/en_US.js +++ b/l10n/en_US.js @@ -1,5 +1,5 @@ OC.L10N.register( - "softwarecatalog", + "stackiq", { "Acquisition" : "Acquisition", "Applications in use for an organisation, grouped by lifecycle phase and ordered by nearest urgency (end-of-support, phase-out or planned replacement)." : "Applications in use for an organisation, grouped by lifecycle phase and ordered by nearest urgency (end-of-support, phase-out or planned replacement).", @@ -260,7 +260,7 @@ OC.L10N.register( "Group members" : "Group members", "Merge organisations" : "Merge organisations", "End-of-life feed sync" : "End-of-life feed sync", - "Match catalog products to endoflife.date product cycles ingested via OpenConnector, to keep end-of-support dates data-driven. Softwarecatalog never calls endoflife.date directly." : "Match catalog products to endoflife.date product cycles ingested via OpenConnector, to keep end-of-support dates data-driven. Softwarecatalog never calls endoflife.date directly.", + "Match catalog products to endoflife.date product cycles ingested via OpenConnector, to keep end-of-support dates data-driven. Stackiq never calls endoflife.date directly." : "Match catalog products to endoflife.date product cycles ingested via OpenConnector, to keep end-of-support dates data-driven. Stackiq never calls endoflife.date directly.", "Loading EOL sync configuration…" : "Loading EOL sync configuration…", "Save EOL sync settings" : "Save EOL sync settings", "Sync now" : "Sync now", diff --git a/l10n/en_US.json b/l10n/en_US.json index 8fee0daa..3693bd60 100644 --- a/l10n/en_US.json +++ b/l10n/en_US.json @@ -302,7 +302,6 @@ "Group members": "Group members", "Merge organisations": "Merge organisations", "End-of-life feed sync": "End-of-life feed sync", - "Match catalog products to endoflife.date product cycles ingested via OpenConnector, to keep end-of-support dates data-driven. Softwarecatalog never calls endoflife.date directly.": "Match catalog products to endoflife.date product cycles ingested via OpenConnector, to keep end-of-support dates data-driven. Softwarecatalog never calls endoflife.date directly.", "Loading EOL sync configuration…": "Loading EOL sync configuration…", "Save EOL sync settings": "Save EOL sync settings", "Sync now": "Sync now", @@ -376,7 +375,6 @@ "Import a CycloneDX or SPDX SBOM to see this version's components, licenses and any matching known vulnerabilities.": "Import a CycloneDX or SPDX SBOM to see this version's components, licenses and any matching known vulnerabilities.", "Confirmed match": "Confirmed match", "Possible match": "Possible match", - "Name": "Name", "Version": "Version", "Package URL": "Package URL", "Licenses": "Licenses", @@ -429,15 +427,6 @@ "Could not load applications. Please try again.": "Could not load applications. Please try again.", "Applications ({count})": "Applications ({count})", "No applications attached yet.": "No applications attached yet.", - "Approve": "Approve", - "Reject": "Reject", - "Nothing to moderate": "Nothing to moderate", - "Refresh queue": "Refresh queue", - "Registration moderation": "Registration moderation", - "Review anonymous catalog registrations. Approving an entry publishes it; rejecting leaves it hidden.": "Review anonymous catalog registrations. Approving an entry publishes it; rejecting leaves it hidden.", - "Loading pending registrations…": "Loading pending registrations…", - "There are no pending registrations right now.": "There are no pending registrations right now.", - "Could not load the moderation queue": "Could not load the moderation queue", "{label} approved and published": "{label} approved and published", "{label} rejected": "{label} rejected", "{label} has no identifier": "{label} has no identifier", @@ -472,6 +461,7 @@ "Current members": "Current members", "No members yet.": "No members yet.", "Revoke access": "Revoke access", - "Close": "Close" + "Close": "Close", + "Match catalog products to endoflife.date product cycles ingested via OpenConnector, to keep end-of-support dates data-driven. Stackiq never calls endoflife.date directly.": "Match catalog products to endoflife.date product cycles ingested via OpenConnector, to keep end-of-support dates data-driven. Stackiq never calls endoflife.date directly." } } diff --git a/l10n/es.js b/l10n/es.js index ff91aaf6..8eb29407 100644 --- a/l10n/es.js +++ b/l10n/es.js @@ -1,5 +1,5 @@ OC.L10N.register( - "softwarecatalog", + "stackiq", { "Acquisition" : "Acquisition", "Applications in use for an organisation, grouped by lifecycle phase and ordered by nearest urgency (end-of-support, phase-out or planned replacement)." : "Applications in use for an organisation, grouped by lifecycle phase and ordered by nearest urgency (end-of-support, phase-out or planned replacement).", diff --git a/l10n/et.js b/l10n/et.js index d7e932b3..21daf809 100644 --- a/l10n/et.js +++ b/l10n/et.js @@ -1,5 +1,5 @@ OC.L10N.register( - "softwarecatalog", + "stackiq", { "Acquisition" : "Acquisition", "Applications in use for an organisation, grouped by lifecycle phase and ordered by nearest urgency (end-of-support, phase-out or planned replacement)." : "Applications in use for an organisation, grouped by lifecycle phase and ordered by nearest urgency (end-of-support, phase-out or planned replacement).", diff --git a/l10n/fi.js b/l10n/fi.js index 522b806d..59474e4e 100644 --- a/l10n/fi.js +++ b/l10n/fi.js @@ -1,5 +1,5 @@ OC.L10N.register( - "softwarecatalog", + "stackiq", { "Acquisition" : "Acquisition", "Applications in use for an organisation, grouped by lifecycle phase and ordered by nearest urgency (end-of-support, phase-out or planned replacement)." : "Applications in use for an organisation, grouped by lifecycle phase and ordered by nearest urgency (end-of-support, phase-out or planned replacement).", diff --git a/l10n/fr.js b/l10n/fr.js index da58d6b8..bef79b41 100644 --- a/l10n/fr.js +++ b/l10n/fr.js @@ -1,5 +1,5 @@ OC.L10N.register( - "softwarecatalog", + "stackiq", { "Acquisition" : "Acquisition", "Applications in use for an organisation, grouped by lifecycle phase and ordered by nearest urgency (end-of-support, phase-out or planned replacement)." : "Applications in use for an organisation, grouped by lifecycle phase and ordered by nearest urgency (end-of-support, phase-out or planned replacement).", diff --git a/l10n/ga.js b/l10n/ga.js index 5336188a..9eccb9db 100644 --- a/l10n/ga.js +++ b/l10n/ga.js @@ -1,5 +1,5 @@ OC.L10N.register( - "softwarecatalog", + "stackiq", { "Acquisition" : "Acquisition", "Applications in use for an organisation, grouped by lifecycle phase and ordered by nearest urgency (end-of-support, phase-out or planned replacement)." : "Applications in use for an organisation, grouped by lifecycle phase and ordered by nearest urgency (end-of-support, phase-out or planned replacement).", diff --git a/l10n/hr.js b/l10n/hr.js index 7b5ab0dc..cd772827 100644 --- a/l10n/hr.js +++ b/l10n/hr.js @@ -1,5 +1,5 @@ OC.L10N.register( - "softwarecatalog", + "stackiq", { "Acquisition" : "Acquisition", "Applications in use for an organisation, grouped by lifecycle phase and ordered by nearest urgency (end-of-support, phase-out or planned replacement)." : "Applications in use for an organisation, grouped by lifecycle phase and ordered by nearest urgency (end-of-support, phase-out or planned replacement).", diff --git a/l10n/hu.js b/l10n/hu.js index 68d25d84..621d9d3e 100644 --- a/l10n/hu.js +++ b/l10n/hu.js @@ -1,5 +1,5 @@ OC.L10N.register( - "softwarecatalog", + "stackiq", { "Acquisition" : "Acquisition", "Applications in use for an organisation, grouped by lifecycle phase and ordered by nearest urgency (end-of-support, phase-out or planned replacement)." : "Applications in use for an organisation, grouped by lifecycle phase and ordered by nearest urgency (end-of-support, phase-out or planned replacement).", diff --git a/l10n/is.js b/l10n/is.js index e9d7d694..34fbd291 100644 --- a/l10n/is.js +++ b/l10n/is.js @@ -1,5 +1,5 @@ OC.L10N.register( - "softwarecatalog", + "stackiq", { "Acquisition" : "Acquisition", "Applications in use for an organisation, grouped by lifecycle phase and ordered by nearest urgency (end-of-support, phase-out or planned replacement)." : "Applications in use for an organisation, grouped by lifecycle phase and ordered by nearest urgency (end-of-support, phase-out or planned replacement).", diff --git a/l10n/it.js b/l10n/it.js index 81281724..868e5787 100644 --- a/l10n/it.js +++ b/l10n/it.js @@ -1,5 +1,5 @@ OC.L10N.register( - "softwarecatalog", + "stackiq", { "Acquisition" : "Acquisition", "Applications in use for an organisation, grouped by lifecycle phase and ordered by nearest urgency (end-of-support, phase-out or planned replacement)." : "Applications in use for an organisation, grouped by lifecycle phase and ordered by nearest urgency (end-of-support, phase-out or planned replacement).", diff --git a/l10n/lb.js b/l10n/lb.js index 29c443a6..1327853c 100644 --- a/l10n/lb.js +++ b/l10n/lb.js @@ -1,5 +1,5 @@ OC.L10N.register( - "softwarecatalog", + "stackiq", { "Acquisition" : "Acquisition", "Applications in use for an organisation, grouped by lifecycle phase and ordered by nearest urgency (end-of-support, phase-out or planned replacement)." : "Applications in use for an organisation, grouped by lifecycle phase and ordered by nearest urgency (end-of-support, phase-out or planned replacement).", diff --git a/l10n/lt.js b/l10n/lt.js index 9f916475..0e00640d 100644 --- a/l10n/lt.js +++ b/l10n/lt.js @@ -1,5 +1,5 @@ OC.L10N.register( - "softwarecatalog", + "stackiq", { "Acquisition" : "Acquisition", "Applications in use for an organisation, grouped by lifecycle phase and ordered by nearest urgency (end-of-support, phase-out or planned replacement)." : "Applications in use for an organisation, grouped by lifecycle phase and ordered by nearest urgency (end-of-support, phase-out or planned replacement).", diff --git a/l10n/lv.js b/l10n/lv.js index d257f7b4..125c8a72 100644 --- a/l10n/lv.js +++ b/l10n/lv.js @@ -1,5 +1,5 @@ OC.L10N.register( - "softwarecatalog", + "stackiq", { "Acquisition" : "Acquisition", "Applications in use for an organisation, grouped by lifecycle phase and ordered by nearest urgency (end-of-support, phase-out or planned replacement)." : "Applications in use for an organisation, grouped by lifecycle phase and ordered by nearest urgency (end-of-support, phase-out or planned replacement).", diff --git a/l10n/mk.js b/l10n/mk.js index 9b7c4304..585e9c48 100644 --- a/l10n/mk.js +++ b/l10n/mk.js @@ -1,5 +1,5 @@ OC.L10N.register( - "softwarecatalog", + "stackiq", { "Acquisition" : "Acquisition", "Applications in use for an organisation, grouped by lifecycle phase and ordered by nearest urgency (end-of-support, phase-out or planned replacement)." : "Applications in use for an organisation, grouped by lifecycle phase and ordered by nearest urgency (end-of-support, phase-out or planned replacement).", diff --git a/l10n/mt.js b/l10n/mt.js index a6dacbe0..a18df4e6 100644 --- a/l10n/mt.js +++ b/l10n/mt.js @@ -1,5 +1,5 @@ OC.L10N.register( - "softwarecatalog", + "stackiq", { "Acquisition" : "Acquisition", "Applications in use for an organisation, grouped by lifecycle phase and ordered by nearest urgency (end-of-support, phase-out or planned replacement)." : "Applications in use for an organisation, grouped by lifecycle phase and ordered by nearest urgency (end-of-support, phase-out or planned replacement).", diff --git a/l10n/nb.js b/l10n/nb.js index a79ec9b9..44ca59ed 100644 --- a/l10n/nb.js +++ b/l10n/nb.js @@ -1,5 +1,5 @@ OC.L10N.register( - "softwarecatalog", + "stackiq", { "Acquisition" : "Acquisition", "Applications in use for an organisation, grouped by lifecycle phase and ordered by nearest urgency (end-of-support, phase-out or planned replacement)." : "Applications in use for an organisation, grouped by lifecycle phase and ordered by nearest urgency (end-of-support, phase-out or planned replacement).", diff --git a/l10n/nl.js b/l10n/nl.js index 25a15fe2..068121e8 100644 --- a/l10n/nl.js +++ b/l10n/nl.js @@ -1,5 +1,5 @@ OC.L10N.register( - "softwarecatalog", + "stackiq", { "AMEF elements" : "Amef elementen", "AMEF standards" : "Standaarden AMEF", @@ -251,7 +251,7 @@ OC.L10N.register( "Short Description" : "Korte beschrijving", "Short description" : "Korte beschrijving", "Showing {showing} of {total} {type}" : "{showing} van {total} {type} weergegeven", - "Software Catalog Location URL" : "Softwarecatalogus locatie-URL", + "Stackiq Location URL" : "Stackiq locatie-URL", "Software Catalogus needs the OpenRegister app to store and manage data. Please install OpenRegister from the app store to get started." : "De Softwarecatalogus heeft de OpenRegister-app nodig om gegevens op te slaan en te beheren. Installeer OpenRegister vanuit de app store om te beginnen.", "Start date" : "Startdatum", "Status" : "Status", @@ -380,7 +380,7 @@ OC.L10N.register( "Group members" : "Groepsleden", "Merge organisations" : "Organisaties samenvoegen", "End-of-life feed sync" : "Einde-ondersteuning-feedsynchronisatie", - "Match catalog products to endoflife.date product cycles ingested via OpenConnector, to keep end-of-support dates data-driven. Softwarecatalog never calls endoflife.date directly." : "Koppel catalogusproducten aan endoflife.date-productcycli die via OpenConnector zijn binnengehaald, zodat einde-ondersteuningsdata datagedreven blijft. Softwarecatalogus roept endoflife.date nooit rechtstreeks aan.", + "Match catalog products to endoflife.date product cycles ingested via OpenConnector, to keep end-of-support dates data-driven. Stackiq never calls endoflife.date directly." : "Koppel catalogusproducten aan endoflife.date-productcycli die via OpenConnector zijn binnengehaald, zodat einde-ondersteuningsdata datagedreven blijft. Stackiq roept endoflife.date nooit rechtstreeks aan.", "Loading EOL sync configuration…" : "EOL-synchronisatieconfiguratie laden…", "Save EOL sync settings" : "EOL-synchronisatie-instellingen opslaan", "Sync now" : "Nu synchroniseren", diff --git a/l10n/nl.json b/l10n/nl.json index d373e299..f57f73e5 100644 --- a/l10n/nl.json +++ b/l10n/nl.json @@ -303,7 +303,6 @@ "Short Description": "Korte beschrijving", "Short description": "Korte beschrijving", "Showing {showing} of {total} {type}": "{showing} van {total} {type} weergegeven", - "Software Catalog Location URL": "Softwarecatalogus locatie-URL", "Software Catalogus needs the OpenRegister app to store and manage data. Please install OpenRegister from the app store to get started.": "De Softwarecatalogus heeft de OpenRegister-app nodig om gegevens op te slaan en te beheren. Installeer OpenRegister vanuit de app store om te beginnen.", "Some compliancy records only reference a standard by name and could not be matched to a standard version. They are excluded from the matrix.": "Sommige compliancy-records verwijzen alleen via een naam naar een standaard en konden niet aan een standaardversie worden gekoppeld. Deze worden uitgesloten van de matrix.", "Standards": "Standaarden", @@ -362,12 +361,12 @@ "Add a federation peer": "Add a federation peer", "Add a peer catalog URL above to start federating.": "Add a peer catalog URL above to start federating.", "Add peer": "Add peer", - "Approve": "Approve", + "Approve": "Goedkeuren", "Blocked by SSRF guard": "Blocked by SSRF guard", "Catalog federation": "Catalog federation", "Could not add peer": "Could not add peer", "Could not load federation status": "Could not load federation status", - "Could not load the moderation queue": "Could not load the moderation queue", + "Could not load the moderation queue": "Kon de moderatiewachtrij niet laden", "Could not remove peer": "Could not remove peer", "Could not update the registration": "Could not update the registration", "Directory": "Directory", @@ -377,29 +376,29 @@ "Federation pull failed": "Federation pull failed", "Healthy": "Healthy", "Loading federation status…": "Loading federation status…", - "Loading pending registrations…": "Loading pending registrations…", + "Loading pending registrations…": "Openstaande registraties laden…", "No directory configured": "No directory configured", "No peers subscribed": "No peers subscribed", - "Nothing to moderate": "Nothing to moderate", + "Nothing to moderate": "Niets te modereren", "Peer added": "Peer added", "Peer catalog URL": "Peer catalog URL", "Peer removed": "Peer removed", "Private and loopback hosts are blocked unless explicitly allowlisted via the local_federation_hosts setting.": "Private and loopback hosts are blocked unless explicitly allowlisted via the local_federation_hosts setting.", "Pull now": "Pull now", "Pulled {count} peer(s).": "Pulled {count} peer(s).", - "Refresh queue": "Refresh queue", + "Refresh queue": "Wachtrij vernieuwen", "Refresh status": "Refresh status", "Registration approved and published": "Registration approved and published", "Registration has no identifier": "Registration has no identifier", - "Registration moderation": "Registration moderation", + "Registration moderation": "Registratiemoderatie", "Registration rejected": "Registration rejected", - "Reject": "Reject", + "Reject": "Afwijzen", "Remove peer": "Remove peer", - "Review anonymous catalog registrations. Approving an entry publishes it; rejecting leaves it hidden.": "Review anonymous catalog registrations. Approving an entry publishes it; rejecting leaves it hidden.", + "Review anonymous catalog registrations. Approving an entry publishes it; rejecting leaves it hidden.": "Beoordeel anonieme catalogusregistraties. Goedkeuren publiceert een item; afwijzen houdt het verborgen.", "Stale": "Stale", "Subscribe to peer catalogs and pull their published entries into this instance.": "Subscribe to peer catalogs and pull their published entries into this instance.", "Subscribed peers": "Subscribed peers", - "There are no pending registrations right now.": "There are no pending registrations right now.", + "There are no pending registrations right now.": "Er zijn op dit moment geen openstaande registraties.", "Publication": "Publicatie", "Publications will be soft deleted and moved to the": "Publicaties worden zacht verwijderd en verplaatst naar de", "deleted publications section": "sectie verwijderde publicaties", @@ -529,7 +528,6 @@ "Save view": "Weergave opslaan", "Approval": "Goedkeuring", "End-of-life feed sync": "Einde-ondersteuning-feedsynchronisatie", - "Match catalog products to endoflife.date product cycles ingested via OpenConnector, to keep end-of-support dates data-driven. Softwarecatalog never calls endoflife.date directly.": "Koppel catalogusproducten aan endoflife.date-productcycli die via OpenConnector zijn binnengehaald, zodat einde-ondersteuningsdata datagedreven blijft. Softwarecatalogus roept endoflife.date nooit rechtstreeks aan.", "Loading EOL sync configuration…": "EOL-synchronisatieconfiguratie laden…", "Save EOL sync settings": "EOL-synchronisatie-instellingen opslaan", "Sync now": "Nu synchroniseren", @@ -590,7 +588,6 @@ "TIME quadrant counts": "TIME-kwadrantaantallen", "Tolerate": "Tolereren", "Unclassified": "Ongeclassificeerd", - "Approval": "Goedkeuring", "Loading components": "Componenten laden", "Components": "Componenten", "Distinct licenses": "Unieke licenties", @@ -602,7 +599,6 @@ "Import a CycloneDX or SPDX SBOM to see this version's components, licenses and any matching known vulnerabilities.": "Importeer een CycloneDX- of SPDX-SBOM om de componenten, licenties en eventuele overeenkomende bekende kwetsbaarheden van deze versie te zien.", "Confirmed match": "Bevestigde overeenkomst", "Possible match": "Mogelijke overeenkomst", - "Name": "Naam", "Version": "Versie", "Package URL": "Package-URL", "Licenses": "Licenties", @@ -612,7 +608,6 @@ "SBOM import failed": "SBOM-import mislukt", "Imported {count} components.": "{count} componenten geïmporteerd.", "Last imported {date} from {file} ({format})": "Laatst geïmporteerd op {date} vanuit {file} ({format})", - "Approval": "Goedkeuring", "All applications": "Alle applicaties", "BBN level": "BBN-niveau", "BIO measures": "BIO-maatregelen", @@ -656,15 +651,6 @@ "Could not load applications. Please try again.": "Kon de applicaties niet laden. Probeer het opnieuw.", "Applications ({count})": "Applicaties ({count})", "No applications attached yet.": "Nog geen applicaties gekoppeld.", - "Approve": "Goedkeuren", - "Reject": "Afwijzen", - "Nothing to moderate": "Niets te modereren", - "Refresh queue": "Wachtrij vernieuwen", - "Registration moderation": "Registratiemoderatie", - "Review anonymous catalog registrations. Approving an entry publishes it; rejecting leaves it hidden.": "Beoordeel anonieme catalogusregistraties. Goedkeuren publiceert een item; afwijzen houdt het verborgen.", - "Loading pending registrations…": "Openstaande registraties laden…", - "There are no pending registrations right now.": "Er zijn op dit moment geen openstaande registraties.", - "Could not load the moderation queue": "Kon de moderatiewachtrij niet laden", "{label} approved and published": "{label} goedgekeurd en gepubliceerd", "{label} rejected": "{label} afgewezen", "{label} has no identifier": "{label} heeft geen identificatie", @@ -699,10 +685,11 @@ "Current members": "Huidige leden", "No members yet.": "Nog geen leden.", "Revoke access": "Toegang intrekken", - "Close": "Sluiten", "Vulnerability": "Kwetsbaarheid", "Connection": "Koppeling", "Assessment": "Beoordeling", - "SBOM component": "SBOM-component" + "SBOM component": "SBOM-component", + "Stackiq Location URL": "Stackiq locatie-URL", + "Match catalog products to endoflife.date product cycles ingested via OpenConnector, to keep end-of-support dates data-driven. Stackiq never calls endoflife.date directly.": "Koppel catalogusproducten aan endoflife.date-productcycli die via OpenConnector zijn binnengehaald, zodat einde-ondersteuningsdata datagedreven blijft. Stackiq roept endoflife.date nooit rechtstreeks aan." } } diff --git a/l10n/pl.js b/l10n/pl.js index 6f2b1fde..e795c225 100644 --- a/l10n/pl.js +++ b/l10n/pl.js @@ -1,5 +1,5 @@ OC.L10N.register( - "softwarecatalog", + "stackiq", { "Acquisition" : "Acquisition", "Applications in use for an organisation, grouped by lifecycle phase and ordered by nearest urgency (end-of-support, phase-out or planned replacement)." : "Applications in use for an organisation, grouped by lifecycle phase and ordered by nearest urgency (end-of-support, phase-out or planned replacement).", diff --git a/l10n/pt.js b/l10n/pt.js index 9fe86cef..76bc51eb 100644 --- a/l10n/pt.js +++ b/l10n/pt.js @@ -1,5 +1,5 @@ OC.L10N.register( - "softwarecatalog", + "stackiq", { "Acquisition" : "Acquisition", "Applications in use for an organisation, grouped by lifecycle phase and ordered by nearest urgency (end-of-support, phase-out or planned replacement)." : "Applications in use for an organisation, grouped by lifecycle phase and ordered by nearest urgency (end-of-support, phase-out or planned replacement).", diff --git a/l10n/rm.js b/l10n/rm.js index daf44b83..5373610d 100644 --- a/l10n/rm.js +++ b/l10n/rm.js @@ -1,5 +1,5 @@ OC.L10N.register( - "softwarecatalog", + "stackiq", { "Acquisition" : "Acquisition", "Applications in use for an organisation, grouped by lifecycle phase and ordered by nearest urgency (end-of-support, phase-out or planned replacement)." : "Applications in use for an organisation, grouped by lifecycle phase and ordered by nearest urgency (end-of-support, phase-out or planned replacement).", diff --git a/l10n/ro.js b/l10n/ro.js index bf2dad4d..a6d4cae0 100644 --- a/l10n/ro.js +++ b/l10n/ro.js @@ -1,5 +1,5 @@ OC.L10N.register( - "softwarecatalog", + "stackiq", { "Acquisition" : "Acquisition", "Applications in use for an organisation, grouped by lifecycle phase and ordered by nearest urgency (end-of-support, phase-out or planned replacement)." : "Applications in use for an organisation, grouped by lifecycle phase and ordered by nearest urgency (end-of-support, phase-out or planned replacement).", diff --git a/l10n/ru.js b/l10n/ru.js index 0110c294..199b74fb 100644 --- a/l10n/ru.js +++ b/l10n/ru.js @@ -1,5 +1,5 @@ OC.L10N.register( - "softwarecatalog", + "stackiq", { "Acquisition" : "Acquisition", "Applications in use for an organisation, grouped by lifecycle phase and ordered by nearest urgency (end-of-support, phase-out or planned replacement)." : "Applications in use for an organisation, grouped by lifecycle phase and ordered by nearest urgency (end-of-support, phase-out or planned replacement).", diff --git a/l10n/sk.js b/l10n/sk.js index 2d9de754..e36ccfe4 100644 --- a/l10n/sk.js +++ b/l10n/sk.js @@ -1,5 +1,5 @@ OC.L10N.register( - "softwarecatalog", + "stackiq", { "Acquisition" : "Acquisition", "Applications in use for an organisation, grouped by lifecycle phase and ordered by nearest urgency (end-of-support, phase-out or planned replacement)." : "Applications in use for an organisation, grouped by lifecycle phase and ordered by nearest urgency (end-of-support, phase-out or planned replacement).", diff --git a/l10n/sl.js b/l10n/sl.js index 7a2df0d4..db606bea 100644 --- a/l10n/sl.js +++ b/l10n/sl.js @@ -1,5 +1,5 @@ OC.L10N.register( - "softwarecatalog", + "stackiq", { "Acquisition" : "Acquisition", "Applications in use for an organisation, grouped by lifecycle phase and ordered by nearest urgency (end-of-support, phase-out or planned replacement)." : "Applications in use for an organisation, grouped by lifecycle phase and ordered by nearest urgency (end-of-support, phase-out or planned replacement).", diff --git a/l10n/sq.js b/l10n/sq.js index cc5e15b5..9f596431 100644 --- a/l10n/sq.js +++ b/l10n/sq.js @@ -1,5 +1,5 @@ OC.L10N.register( - "softwarecatalog", + "stackiq", { "Acquisition" : "Acquisition", "Applications in use for an organisation, grouped by lifecycle phase and ordered by nearest urgency (end-of-support, phase-out or planned replacement)." : "Applications in use for an organisation, grouped by lifecycle phase and ordered by nearest urgency (end-of-support, phase-out or planned replacement).", diff --git a/l10n/sr.js b/l10n/sr.js index 264246e4..8c0808d8 100644 --- a/l10n/sr.js +++ b/l10n/sr.js @@ -1,5 +1,5 @@ OC.L10N.register( - "softwarecatalog", + "stackiq", { "Acquisition" : "Acquisition", "Applications in use for an organisation, grouped by lifecycle phase and ordered by nearest urgency (end-of-support, phase-out or planned replacement)." : "Applications in use for an organisation, grouped by lifecycle phase and ordered by nearest urgency (end-of-support, phase-out or planned replacement).", diff --git a/l10n/sv.js b/l10n/sv.js index 97866bb0..ccf03735 100644 --- a/l10n/sv.js +++ b/l10n/sv.js @@ -1,5 +1,5 @@ OC.L10N.register( - "softwarecatalog", + "stackiq", { "Acquisition" : "Acquisition", "Applications in use for an organisation, grouped by lifecycle phase and ordered by nearest urgency (end-of-support, phase-out or planned replacement)." : "Applications in use for an organisation, grouped by lifecycle phase and ordered by nearest urgency (end-of-support, phase-out or planned replacement).", diff --git a/l10n/tr.js b/l10n/tr.js index 50a64146..d6e9d555 100644 --- a/l10n/tr.js +++ b/l10n/tr.js @@ -1,5 +1,5 @@ OC.L10N.register( - "softwarecatalog", + "stackiq", { "Acquisition" : "Acquisition", "Applications in use for an organisation, grouped by lifecycle phase and ordered by nearest urgency (end-of-support, phase-out or planned replacement)." : "Applications in use for an organisation, grouped by lifecycle phase and ordered by nearest urgency (end-of-support, phase-out or planned replacement).", diff --git a/l10n/uk.js b/l10n/uk.js index 6d1a9913..27bcd4e1 100644 --- a/l10n/uk.js +++ b/l10n/uk.js @@ -1,5 +1,5 @@ OC.L10N.register( - "softwarecatalog", + "stackiq", { "Acquisition" : "Acquisition", "Applications in use for an organisation, grouped by lifecycle phase and ordered by nearest urgency (end-of-support, phase-out or planned replacement)." : "Applications in use for an organisation, grouped by lifecycle phase and ordered by nearest urgency (end-of-support, phase-out or planned replacement).", diff --git a/lib/AppInfo/Application.php b/lib/AppInfo/Application.php index e41a15d5..98a84ed2 100644 --- a/lib/AppInfo/Application.php +++ b/lib/AppInfo/Application.php @@ -1,24 +1,24 @@ * @copyright 2024 Conduction B.V. * @license EUPL-1.2 https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12 * @version GIT: - * @link https://codeberg.org/Conduction/SoftwareCatalog + * @link https://github.com/ConductionNL/stackiq * * @spec openspec/specs/method-decomposition/spec.md */ declare(strict_types=1); -namespace OCA\SoftwareCatalog\AppInfo; +namespace OCA\Stackiq\AppInfo; use OCA\Decidesk\Event\DecisionConcludedEvent; use OCA\OpenRegister\Contract\ObjectServiceInterface; @@ -26,53 +26,53 @@ use OCA\OpenRegister\Event\ObjectUpdatedEvent; use OCA\OpenRegister\Event\UserProfileUpdatedEvent; use OCA\OpenRegister\Service\OrganisationService as OpenRegisterOrganisationService; -use OCA\SoftwareCatalog\BackgroundJob\ContractStatusJob; -use OCA\SoftwareCatalog\BackgroundJob\EolSyncJob; -use OCA\SoftwareCatalog\BackgroundJob\FederationSyncJob; -use OCA\SoftwareCatalog\BackgroundJob\OrganizationContactSyncJob; -use OCA\SoftwareCatalog\Controller\ContactpersonenController; -use OCA\SoftwareCatalog\Dashboard\ConceptOrganisatiesWidget; -use OCA\SoftwareCatalog\EventListener\DecisionConcludedListener; -use OCA\SoftwareCatalog\EventListener\ModuleComplianceSubscriber; -use OCA\SoftwareCatalog\EventListener\ModuleRegistrationSubscriber; -use OCA\SoftwareCatalog\EventListener\TestEventListener; -use OCA\SoftwareCatalog\EventListener\UserProfileUpdatedEventListener; -use OCA\SoftwareCatalog\Service\ArchiMateExportService; -use OCA\SoftwareCatalog\Service\ArchiMateImportService; -use OCA\SoftwareCatalog\Service\ArchiMateService; -use OCA\SoftwareCatalog\Service\ContactpersoonService; -use OCA\SoftwareCatalog\Service\ContractApprovalService; -use OCA\SoftwareCatalog\Service\ContractStatusService; -use OCA\SoftwareCatalog\Service\EolMatcherService; -use OCA\SoftwareCatalog\Service\EolSyncService; -use OCA\SoftwareCatalog\Service\FacetService; -use OCA\SoftwareCatalog\Service\Federation\FederationConfig; -use OCA\SoftwareCatalog\Service\Federation\FederationMerger; -use OCA\SoftwareCatalog\Service\Federation\FederationService; -use OCA\SoftwareCatalog\Service\GebruikSyncService; -use OCA\SoftwareCatalog\Service\IntakeService; -use OCA\SoftwareCatalog\Service\MergeOrganisatieService; -use OCA\SoftwareCatalog\Service\ModerationService; -use OCA\SoftwareCatalog\Service\ModuleComplianceService; -use OCA\SoftwareCatalog\Service\ModuleRegistrationService; -use OCA\SoftwareCatalog\Service\ModuleVersionService; -use OCA\SoftwareCatalog\Service\OrganisatieService; -use OCA\SoftwareCatalog\Service\OrganizationSyncService; -use OCA\SoftwareCatalog\Service\ProgressTracker; -use OCA\SoftwareCatalog\Service\PublicationService; -use OCA\SoftwareCatalog\Service\ReviewAggregateService; -use OCA\SoftwareCatalog\Service\ReviewService; -use OCA\SoftwareCatalog\Service\SbomImportService; -use OCA\SoftwareCatalog\Service\SbomParserService; -use OCA\SoftwareCatalog\Service\SettingsService; -use OCA\SoftwareCatalog\Service\SoftwareCatalogContactSyncService; -use OCA\SoftwareCatalog\Service\SoftwareCatalogue\ContactPersonHandler; -use OCA\SoftwareCatalog\Service\SoftwareCatalogue\GroupHandler; -use OCA\SoftwareCatalog\Service\SoftwareCatalogue\HierarchyHandler; -use OCA\SoftwareCatalog\Service\SoftwareCatalogue\OrganizationHandler; -use OCA\SoftwareCatalog\Service\SymfonyEmailService; -use OCA\SoftwareCatalog\Service\ViewQueryBuilder; -use OCA\SoftwareCatalog\Service\ViewService; +use OCA\Stackiq\BackgroundJob\ContractStatusJob; +use OCA\Stackiq\BackgroundJob\EolSyncJob; +use OCA\Stackiq\BackgroundJob\FederationSyncJob; +use OCA\Stackiq\BackgroundJob\OrganizationContactSyncJob; +use OCA\Stackiq\Controller\ContactpersonenController; +use OCA\Stackiq\Dashboard\ConceptOrganisatiesWidget; +use OCA\Stackiq\EventListener\DecisionConcludedListener; +use OCA\Stackiq\EventListener\ModuleComplianceSubscriber; +use OCA\Stackiq\EventListener\ModuleRegistrationSubscriber; +use OCA\Stackiq\EventListener\TestEventListener; +use OCA\Stackiq\EventListener\UserProfileUpdatedEventListener; +use OCA\Stackiq\Service\ArchiMateExportService; +use OCA\Stackiq\Service\ArchiMateImportService; +use OCA\Stackiq\Service\ArchiMateService; +use OCA\Stackiq\Service\ContactpersoonService; +use OCA\Stackiq\Service\ContractApprovalService; +use OCA\Stackiq\Service\ContractStatusService; +use OCA\Stackiq\Service\EolMatcherService; +use OCA\Stackiq\Service\EolSyncService; +use OCA\Stackiq\Service\FacetService; +use OCA\Stackiq\Service\Federation\FederationConfig; +use OCA\Stackiq\Service\Federation\FederationMerger; +use OCA\Stackiq\Service\Federation\FederationService; +use OCA\Stackiq\Service\GebruikSyncService; +use OCA\Stackiq\Service\IntakeService; +use OCA\Stackiq\Service\MergeOrganisatieService; +use OCA\Stackiq\Service\ModerationService; +use OCA\Stackiq\Service\ModuleComplianceService; +use OCA\Stackiq\Service\ModuleRegistrationService; +use OCA\Stackiq\Service\ModuleVersionService; +use OCA\Stackiq\Service\OrganisatieService; +use OCA\Stackiq\Service\OrganizationSyncService; +use OCA\Stackiq\Service\ProgressTracker; +use OCA\Stackiq\Service\PublicationService; +use OCA\Stackiq\Service\ReviewAggregateService; +use OCA\Stackiq\Service\ReviewService; +use OCA\Stackiq\Service\SbomImportService; +use OCA\Stackiq\Service\SbomParserService; +use OCA\Stackiq\Service\SettingsService; +use OCA\Stackiq\Service\Stackiq\ContactPersonHandler; +use OCA\Stackiq\Service\Stackiq\GroupHandler; +use OCA\Stackiq\Service\Stackiq\HierarchyHandler; +use OCA\Stackiq\Service\Stackiq\OrganizationHandler; +use OCA\Stackiq\Service\StackiqContactSyncService; +use OCA\Stackiq\Service\SymfonyEmailService; +use OCA\Stackiq\Service\ViewQueryBuilder; +use OCA\Stackiq\Service\ViewService; use OCP\App\IAppManager; use OCP\AppFramework\App; use OCP\AppFramework\Bootstrap\IBootContext; @@ -92,14 +92,14 @@ use Psr\Log\LoggerInterface; /** - * Main Application class for SoftwareCatalog + * Main Application class for Stackiq * * @category Application - * @package OCA\SoftwareCatalog\AppInfo + * @package OCA\Stackiq\AppInfo * @author Conduction b.v. * @license EUPL-1.2 https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12 * @version GIT: - * @link https://codeberg.org/Conduction/SoftwareCatalog + * @link https://github.com/ConductionNL/stackiq * * @SuppressWarnings(PHPMD.CouplingBetweenObjects) * @@ -109,7 +109,7 @@ class Application extends App implements IBootstrap { /** * The application ID */ - public const APP_ID = 'softwarecatalog'; + public const APP_ID = 'stackiq'; /** * Application constructor @@ -157,7 +157,7 @@ public function register(IRegistrationContext $context): void { }//end register() /** - * Wire the four SoftwareCatalogue handler services as DI bindings. + * Wire the four Stackiq handler services as DI bindings. * * Single-responsibility helper extracted from `register()` per * `openspec/changes/method-decomposition/tasks.md` task 9.1. @@ -170,7 +170,7 @@ public function register(IRegistrationContext $context): void { */ private function registerHandlerServices(IRegistrationContext $context): void { $context->registerService( - 'OCA\SoftwareCatalog\Service\SoftwareCatalogue\OrganizationHandler', + 'OCA\Stackiq\Service\Stackiq\OrganizationHandler', function (ContainerInterface $c) { return new OrganizationHandler( _groupManager: $c->get(IGroupManager::class), @@ -183,7 +183,7 @@ function (ContainerInterface $c) { ); $context->registerService( - 'OCA\SoftwareCatalog\Service\SoftwareCatalogue\ContactPersonHandler', + 'OCA\Stackiq\Service\Stackiq\ContactPersonHandler', function (ContainerInterface $c) { return new ContactPersonHandler( _userManager: $c->get(IUserManager::class), @@ -200,7 +200,7 @@ function (ContainerInterface $c) { ); $context->registerService( - 'OCA\SoftwareCatalog\Service\SoftwareCatalogue\GroupHandler', + 'OCA\Stackiq\Service\Stackiq\GroupHandler', function (ContainerInterface $c) { return new GroupHandler( _groupManager: $c->get(IGroupManager::class), @@ -214,11 +214,11 @@ function (ContainerInterface $c) { ); $context->registerService( - 'OCA\SoftwareCatalog\Service\SoftwareCatalogue\HierarchyHandler', + 'OCA\Stackiq\Service\Stackiq\HierarchyHandler', function (ContainerInterface $c) { return new HierarchyHandler( - _organizationHandler: $c->get('OCA\SoftwareCatalog\Service\SoftwareCatalogue\OrganizationHandler'), - _contactPersonHandler: $c->get('OCA\SoftwareCatalog\Service\SoftwareCatalogue\ContactPersonHandler'), + _organizationHandler: $c->get('OCA\Stackiq\Service\Stackiq\OrganizationHandler'), + _contactPersonHandler: $c->get('OCA\Stackiq\Service\Stackiq\ContactPersonHandler'), _logger: $c->get(LoggerInterface::class), _userManager: $c->get(IUserManager::class), _groupManager: $c->get(IGroupManager::class) @@ -361,9 +361,9 @@ function ($container) { // Register the Nextcloud-Contacts bridge (identity → NC addressbook, // relationship records keyed by contactsUid; ADR-019/ADR-022). $context->registerService( - SoftwareCatalogContactSyncService::class, + StackiqContactSyncService::class, function ($container) { - return new SoftwareCatalogContactSyncService( + return new StackiqContactSyncService( contactsManager: $container->get('OCP\Contacts\IManager'), logger: $container->get('Psr\Log\LoggerInterface') ); @@ -434,7 +434,7 @@ function ($container) { ); // Register the registration/review moderation/approval-queue service - // (generalised to also moderate beoordeeling — softwarecatalog#375). + // (generalised to also moderate beoordeeling — stackiq#375). $context->registerService( ModerationService::class, function ($container) { @@ -447,7 +447,7 @@ function ($container) { ); // Register the authenticated review-submission service (catalog-ratings, - // softwarecatalog#375). Author identity comes from IUserSession, never + // stackiq#375). Author identity comes from IUserSession, never // from client input. $context->registerService( ReviewService::class, @@ -462,7 +462,7 @@ function ($container) { ); // Register the public approved-only review aggregate/read service - // (catalog-ratings, softwarecatalog#375) — split from ReviewService + // (catalog-ratings, stackiq#375) — split from ReviewService // to keep each class under the complexity budget. $context->registerService( ReviewAggregateService::class, @@ -607,7 +607,7 @@ function ($container) { return new OrganizationContactSyncJob( timeFactory: $container->get('OCP\AppFramework\Utility\ITimeFactory'), orgSyncService: $container->get(OrganizationSyncService::class), - contactSync: $container->get(SoftwareCatalogContactSyncService::class), + contactSync: $container->get(StackiqContactSyncService::class), settingsService: $container->get(SettingsService::class), appManager: $container->get(IAppManager::class), logger: $container->get(LoggerInterface::class) @@ -731,7 +731,7 @@ function ($container) { request: $container->get('OCP\IRequest'), settingsService: $container->get(SettingsService::class), contactPersonHandler: $container->get( - 'OCA\SoftwareCatalog\Service\SoftwareCatalogue\ContactPersonHandler' + 'OCA\Stackiq\Service\Stackiq\ContactPersonHandler' ), contactSvc: $container->get(ContactpersoonService::class), userManager: $container->get('OCP\IUserManager'), @@ -773,7 +773,7 @@ private function registerEventListeners(IRegistrationContext $context): void { $context->registerEventListener(UserLoggedInEvent::class, TestEventListener::class); // OpenRegister object lifecycle events are NO LONGER broadcast to - // SoftwareCatalogEventListener. + // StackiqEventListener. // // That listener's own docblock has said "DISABLED: All processing is now // handled by cron-based OrganizationSyncService to avoid race conditions" @@ -784,7 +784,7 @@ private function registerEventListeners(IRegistrationContext $context): void { // then discarded the result. // // The cost was not theoretical. Importing OpenCatalogi's configuration — - // twelve seeded objects — produced 657 SoftwareCatalog event handlings. + // twelve seeded objects — produced 657 Stackiq event handlings. // Each one resolved three services from the container and wrote six log // lines BEFORE reaching the schema check that decides the event is not // ours. `occ maintenance:repair` reached 119 of 120 steps and then sat in diff --git a/lib/BackgroundJob/ContractStatusJob.php b/lib/BackgroundJob/ContractStatusJob.php index 171d3ca9..45a8e1c9 100644 --- a/lib/BackgroundJob/ContractStatusJob.php +++ b/lib/BackgroundJob/ContractStatusJob.php @@ -12,11 +12,11 @@ * correct mechanism (tasks.md 2.1 → 2.2). * * @category BackgroundJob - * @package OCA\SoftwareCatalog\BackgroundJob + * @package OCA\Stackiq\BackgroundJob * @author Conduction b.v. * @copyright 2026 Conduction B.V. * @license EUPL-1.2 https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12 - * @link https://codeberg.org/Conduction/SoftwareCatalog + * @link https://github.com/ConductionNL/stackiq * * @spec openspec/specs/contract-administration/spec.md * @@ -26,9 +26,9 @@ declare(strict_types=1); -namespace OCA\SoftwareCatalog\BackgroundJob; +namespace OCA\Stackiq\BackgroundJob; -use OCA\SoftwareCatalog\Service\ContractStatusService; +use OCA\Stackiq\Service\ContractStatusService; use OCP\App\IAppManager; use OCP\AppFramework\Utility\ITimeFactory; use OCP\BackgroundJob\TimedJob; diff --git a/lib/BackgroundJob/EolSyncJob.php b/lib/BackgroundJob/EolSyncJob.php index 38ee9ec8..83d510c7 100644 --- a/lib/BackgroundJob/EolSyncJob.php +++ b/lib/BackgroundJob/EolSyncJob.php @@ -14,11 +14,11 @@ * sharing it. * * @category BackgroundJob - * @package OCA\SoftwareCatalog\BackgroundJob + * @package OCA\Stackiq\BackgroundJob * @author Conduction b.v. * @copyright 2026 Conduction B.V. * @license EUPL-1.2 https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12 - * @link https://codeberg.org/Conduction/SoftwareCatalog + * @link https://github.com/ConductionNL/stackiq * * @spec openspec/specs/eol-feed-integration/spec.md#requirement-eol-sync-runs-on-a-schedule-with-a-manual-trigger * @@ -28,9 +28,9 @@ declare(strict_types=1); -namespace OCA\SoftwareCatalog\BackgroundJob; +namespace OCA\Stackiq\BackgroundJob; -use OCA\SoftwareCatalog\Service\EolSyncService; +use OCA\Stackiq\Service\EolSyncService; use OCP\AppFramework\Utility\ITimeFactory; use OCP\BackgroundJob\TimedJob; use Psr\Log\LoggerInterface; @@ -58,7 +58,7 @@ public function __construct( ) { parent::__construct(time: $timeFactory); - // Floor at 300s (the shortest interval any existing SoftwareCatalog + // Floor at 300s (the shortest interval any existing Stackiq // background job runs at — OrganizationContactSyncJob) so a // mistyped admin value can never schedule a tighter loop than the // rest of the app's cron surface. diff --git a/lib/BackgroundJob/FederationSyncJob.php b/lib/BackgroundJob/FederationSyncJob.php index 265c21de..b3a701a1 100644 --- a/lib/BackgroundJob/FederationSyncJob.php +++ b/lib/BackgroundJob/FederationSyncJob.php @@ -9,11 +9,11 @@ * federation is disabled — so the job is always safe to run. * * @category BackgroundJob - * @package OCA\SoftwareCatalog\BackgroundJob + * @package OCA\Stackiq\BackgroundJob * @author Conduction b.v. * @copyright 2026 Conduction B.V. * @license EUPL-1.2 https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12 - * @link https://codeberg.org/Conduction/SoftwareCatalog + * @link https://github.com/ConductionNL/stackiq * * @spec openspec/specs/federated-catalog-sync/spec.md * @@ -23,10 +23,10 @@ declare(strict_types=1); -namespace OCA\SoftwareCatalog\BackgroundJob; +namespace OCA\Stackiq\BackgroundJob; -use OCA\SoftwareCatalog\Service\Federation\FederationConfig; -use OCA\SoftwareCatalog\Service\Federation\FederationService; +use OCA\Stackiq\Service\Federation\FederationConfig; +use OCA\Stackiq\Service\Federation\FederationService; use OCP\AppFramework\Utility\ITimeFactory; use OCP\BackgroundJob\TimedJob; use Psr\Log\LoggerInterface; diff --git a/lib/BackgroundJob/OrganizationContactSyncJob.php b/lib/BackgroundJob/OrganizationContactSyncJob.php index d2e14e10..575107af 100644 --- a/lib/BackgroundJob/OrganizationContactSyncJob.php +++ b/lib/BackgroundJob/OrganizationContactSyncJob.php @@ -4,24 +4,24 @@ * Organization Contact Synchronization Background Job * * This file contains the background job class for synchronizing organizations and contact persons - * between SoftwareCatalog objects and OpenRegister entities. + * between Stackiq objects and OpenRegister entities. * * @category BackgroundJob - * @package OCA\SoftwareCatalog\BackgroundJob + * @package OCA\Stackiq\BackgroundJob * @author Conduction b.v. * @copyright 2024 Conduction B.V. * @license EUPL-1.2 https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12 * @version GIT: 1.0.0 - * @link https://codeberg.org/Conduction/SoftwareCatalog + * @link https://github.com/ConductionNL/stackiq */ declare(strict_types=1); -namespace OCA\SoftwareCatalog\BackgroundJob; +namespace OCA\Stackiq\BackgroundJob; -use OCA\SoftwareCatalog\Service\OrganizationSyncService; -use OCA\SoftwareCatalog\Service\SettingsService; -use OCA\SoftwareCatalog\Service\SoftwareCatalogContactSyncService; +use OCA\Stackiq\Service\OrganizationSyncService; +use OCA\Stackiq\Service\SettingsService; +use OCA\Stackiq\Service\StackiqContactSyncService; use OCP\App\IAppManager; use OCP\AppFramework\Utility\ITimeFactory; use OCP\BackgroundJob\TimedJob; @@ -30,7 +30,7 @@ /** * Background job for comprehensive organization and contact person synchronization * - * This job runs every 5 minutes to ensure data consistency between SoftwareCatalog objects + * This job runs every 5 minutes to ensure data consistency between Stackiq objects * and OpenRegister entities using full sync (all organizations). All business logic is * delegated to the OrganizationSyncService. * @@ -38,11 +38,11 @@ * system-level background job that needs unrestricted access to all objects. * * @category BackgroundJob - * @package OCA\SoftwareCatalog\BackgroundJob + * @package OCA\Stackiq\BackgroundJob * @author Conduction b.v. * @license EUPL-1.2 https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12 * @version GIT: 1.0.0 - * @link https://codeberg.org/Conduction/SoftwareCatalog + * @link https://github.com/ConductionNL/stackiq */ class OrganizationContactSyncJob extends TimedJob { @@ -58,7 +58,7 @@ class OrganizationContactSyncJob extends TimedJob { * * @param ITimeFactory $timeFactory The time factory for job scheduling * @param OrganizationSyncService $orgSyncService The sync service - * @param SoftwareCatalogContactSyncService $contactSync The Nextcloud-Contacts bridge + * @param StackiqContactSyncService $contactSync The Nextcloud-Contacts bridge * @param SettingsService $settingsService The settings service (register/schema id resolution) * @param IAppManager $appManager The Nextcloud app manager * @param LoggerInterface $logger The logger @@ -66,7 +66,7 @@ class OrganizationContactSyncJob extends TimedJob { public function __construct( ITimeFactory $timeFactory, OrganizationSyncService $orgSyncService, - private readonly SoftwareCatalogContactSyncService $contactSync, + private readonly StackiqContactSyncService $contactSync, private readonly SettingsService $settingsService, private readonly IAppManager $appManager, private readonly LoggerInterface $logger, @@ -83,7 +83,7 @@ public function __construct( * Delegates organisation/contact synchronisation to the * OrganizationSyncService, then keeps every catalog relationship record's * `contactsUid` link to the Nextcloud addressbook fresh via - * SoftwareCatalogContactSyncService. Per cross-app interface contract #2, + * StackiqContactSyncService. Per cross-app interface contract #2, * identity lives in Nextcloud Contacts — this job refreshes the link, it * does NOT mirror identity into OpenRegister. * @@ -135,7 +135,7 @@ protected function run($argument): void { * Refresh the `contactsUid` link on every contactpersoon/organisatie record. * * For each record this (re)resolves its Nextcloud Contact through - * SoftwareCatalogContactSyncService and writes back the UID only when it is + * StackiqContactSyncService and writes back the UID only when it is * missing or has changed. Never mirrors identity into OpenRegister and * never deletes a source object — a record that cannot be resolved is left * intact for a later pass. diff --git a/lib/Controller/AanbodController.php b/lib/Controller/AanbodController.php index f26a6501..9d1d8ed1 100644 --- a/lib/Controller/AanbodController.php +++ b/lib/Controller/AanbodController.php @@ -1,25 +1,25 @@ * @copyright 2024 Conduction B.V. * @license EUPL-1.2 https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12 * @version GIT: - * @link https://codeberg.org/Conduction/SoftwareCatalog + * @link https://github.com/ConductionNL/stackiq */ declare(strict_types=1); -namespace OCA\SoftwareCatalog\Controller; +namespace OCA\Stackiq\Controller; -use OCA\SoftwareCatalog\Service\AanbodService; +use OCA\Stackiq\Service\AanbodService; use OCP\AppFramework\Controller; use OCP\AppFramework\Http; use OCP\AppFramework\Http\JSONResponse; @@ -35,12 +35,12 @@ * (provider), and for accepting or denying these offers. * * @category Controller - * @package OCA\SoftwareCatalog\Controller + * @package OCA\Stackiq\Controller * @author Conduction b.v. * @copyright 2024 Conduction B.V. * @license EUPL-1.2 https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12 * @version GIT: - * @link https://codeberg.org/Conduction/SoftwareCatalog + * @link https://github.com/ConductionNL/stackiq */ class AanbodController extends Controller { /** diff --git a/lib/Controller/AangebodenGebruikController.php b/lib/Controller/AangebodenGebruikController.php index 521b0ffc..a62f8abe 100644 --- a/lib/Controller/AangebodenGebruikController.php +++ b/lib/Controller/AangebodenGebruikController.php @@ -1,26 +1,26 @@ * @copyright 2024 Conduction B.V. * @license EUPL-1.2 https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12 * @version GIT: - * @link https://codeberg.org/Conduction/SoftwareCatalog + * @link https://github.com/ConductionNL/stackiq */ declare(strict_types=1); -namespace OCA\SoftwareCatalog\Controller; +namespace OCA\Stackiq\Controller; -use OCA\SoftwareCatalog\Service\AangebodenGebruikService; +use OCA\Stackiq\Service\AangebodenGebruikService; use OCP\AppFramework\Controller; use OCP\AppFramework\Http; use OCP\AppFramework\Http\Attribute\AnonRateLimit; @@ -38,12 +38,12 @@ * (participants), and for updating the @self property of gebruiks objects. * * @category Controller - * @package OCA\SoftwareCatalog\Controller + * @package OCA\Stackiq\Controller * @author Conduction b.v. * @copyright 2024 Conduction B.V. * @license EUPL-1.2 https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12 * @version GIT: - * @link https://codeberg.org/Conduction/SoftwareCatalog + * @link https://github.com/ConductionNL/stackiq * * @SuppressWarnings(PHPMD.ExcessiveClassLength) * @SuppressWarnings(PHPMD.ExcessiveClassComplexity) @@ -866,7 +866,7 @@ function ($key) { public function getApiDocumentation(): JSONResponse { $documentation = [ 'api_version' => '2.0.0', - 'description' => 'SoftwareCatalog AangebodenGebruik API', + 'description' => 'Stackiq AangebodenGebruik API', 'base_url' => '/api/aangeboden-gebruik', 'endpoints' => [ [ diff --git a/lib/Controller/ContactpersonenController.php b/lib/Controller/ContactpersonenController.php index 14c7e159..894356e2 100644 --- a/lib/Controller/ContactpersonenController.php +++ b/lib/Controller/ContactpersonenController.php @@ -1,18 +1,18 @@ * @copyright 2024 Conduction B.V. * @license EUPL-1.2 https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12 * @version GIT: - * @link https://codeberg.org/Conduction/SoftwareCatalog + * @link https://github.com/ConductionNL/stackiq * * SPDX-FileCopyrightText: 2026 Conduction B.V. * SPDX-License-Identifier: EUPL-1.2 @@ -20,11 +20,13 @@ declare(strict_types=1); -namespace OCA\SoftwareCatalog\Controller; +namespace OCA\Stackiq\Controller; -use OCA\SoftwareCatalog\Service\ContactpersoonService; -use OCA\SoftwareCatalog\Service\SettingsService; -use OCA\SoftwareCatalog\Service\SoftwareCatalogue\ContactPersonHandler; +use OCA\OpenRegister\Contract\ObjectServiceInterface; +use OCA\OpenRegister\Service\OrganisationService; +use OCA\Stackiq\Service\ContactpersoonService; +use OCA\Stackiq\Service\SettingsService; +use OCA\Stackiq\Service\Stackiq\ContactPersonHandler; use OCP\AppFramework\Controller; use OCP\AppFramework\Http; use OCP\AppFramework\Http\JSONResponse; @@ -34,8 +36,6 @@ use OCP\IUserSession; use OCP\Security\ISecureRandom; use Psr\Log\LoggerInterface; -use OCA\OpenRegister\Contract\ObjectServiceInterface; -use OCA\OpenRegister\Service\OrganisationService; /** * Controller for managing contactpersonen and their user accounts. @@ -46,12 +46,12 @@ * - Managing user group memberships * * @category Controller - * @package OCA\SoftwareCatalog\Controller + * @package OCA\Stackiq\Controller * @author Conduction b.v. * @copyright 2024 Conduction B.V. * @license EUPL-1.2 https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12 * @version GIT: - * @link https://codeberg.org/Conduction/SoftwareCatalog + * @link https://github.com/ConductionNL/stackiq * * @SuppressWarnings(PHPMD.ExcessiveClassLength) * @SuppressWarnings(PHPMD.ExcessiveClassComplexity) diff --git a/lib/Controller/ContractApprovalController.php b/lib/Controller/ContractApprovalController.php index ed1075e4..972ba8c7 100644 --- a/lib/Controller/ContractApprovalController.php +++ b/lib/Controller/ContractApprovalController.php @@ -1,7 +1,7 @@ * @copyright 2026 Conduction B.V. @@ -32,7 +32,7 @@ * * @version GIT: * - * @link https://codeberg.org/Conduction/softwarecatalog + * @link https://github.com/ConductionNL/stackiq * * @spec openspec/specs/contract-decision-delegation/spec.md * @@ -42,10 +42,10 @@ declare(strict_types=1); -namespace OCA\SoftwareCatalog\Controller; +namespace OCA\Stackiq\Controller; -use OCA\SoftwareCatalog\AppInfo\Application; -use OCA\SoftwareCatalog\Service\ContractApprovalService; +use OCA\Stackiq\AppInfo\Application; +use OCA\Stackiq\Service\ContractApprovalService; use OCP\AppFramework\Controller; use OCP\AppFramework\Http; use OCP\AppFramework\Http\Attribute\NoAdminRequired; diff --git a/lib/Controller/DashboardController.php b/lib/Controller/DashboardController.php index 8890ba43..cebdb5c2 100644 --- a/lib/Controller/DashboardController.php +++ b/lib/Controller/DashboardController.php @@ -1,18 +1,18 @@ * @copyright 2024 Conduction B.V. * @license EUPL-1.2 https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12 * @version GIT: - * @link https://codeberg.org/Conduction/SoftwareCatalog + * @link https://github.com/ConductionNL/stackiq */ -namespace OCA\SoftwareCatalog\Controller; +namespace OCA\Stackiq\Controller; use OCP\AppFramework\Controller; use OCP\AppFramework\Http; diff --git a/lib/Controller/FacetController.php b/lib/Controller/FacetController.php index 16475333..c453a6d6 100644 --- a/lib/Controller/FacetController.php +++ b/lib/Controller/FacetController.php @@ -1,16 +1,16 @@ * @copyright 2026 Conduction B.V. * @license EUPL-1.2 https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12 - * @link https://codeberg.org/Conduction/SoftwareCatalog + * @link https://github.com/ConductionNL/stackiq * * SPDX-FileCopyrightText: 2026 Conduction B.V. * SPDX-License-Identifier: EUPL-1.2 @@ -20,9 +20,9 @@ declare(strict_types=1); -namespace OCA\SoftwareCatalog\Controller; +namespace OCA\Stackiq\Controller; -use OCA\SoftwareCatalog\Service\FacetService; +use OCA\Stackiq\Service\FacetService; use OCP\AppFramework\Controller; use OCP\AppFramework\Http\JSONResponse; use OCP\IRequest; @@ -32,7 +32,7 @@ * Controller for the GEMMA-dimension facet aggregation endpoint. * * @category Controller - * @package OCA\SoftwareCatalog\Controller + * @package OCA\Stackiq\Controller * * @spec openspec/specs/gemma-faceted-search/spec.md#requirement-facet-aggregation-endpoint-returns-gemma-dimension-counts */ diff --git a/lib/Controller/FederationController.php b/lib/Controller/FederationController.php index d59aff77..e57f5b38 100644 --- a/lib/Controller/FederationController.php +++ b/lib/Controller/FederationController.php @@ -9,7 +9,7 @@ * of all configured peers. All write/read paths delegate to FederationService; * no bespoke federation logic lives here. * - * AUTH (ADR-005): every method is `#[AuthorizedAdminSetting(SoftwareCatalogAdmin::class)]` + * AUTH (ADR-005): every method is `#[AuthorizedAdminSetting(StackiqAdmin::class)]` * — Nextcloud's admin-settings middleware rejects any non-admin caller before * the controller body runs (matching the moderation controller's posture and the * service's admin-only intent), so an authenticated non-admin can never reach @@ -17,11 +17,11 @@ * OWASP A01:2021). * * @category Controller - * @package OCA\SoftwareCatalog\Controller + * @package OCA\Stackiq\Controller * @author Conduction b.v. * @copyright 2026 Conduction B.V. * @license EUPL-1.2 https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12 - * @link https://codeberg.org/Conduction/SoftwareCatalog + * @link https://github.com/ConductionNL/stackiq * * @spec openspec/specs/federated-catalog-sync/spec.md * @@ -31,11 +31,11 @@ declare(strict_types=1); -namespace OCA\SoftwareCatalog\Controller; +namespace OCA\Stackiq\Controller; -use OCA\SoftwareCatalog\AppInfo\Application; -use OCA\SoftwareCatalog\Service\Federation\FederationService; -use OCA\SoftwareCatalog\Settings\SoftwareCatalogAdmin; +use OCA\Stackiq\AppInfo\Application; +use OCA\Stackiq\Service\Federation\FederationService; +use OCA\Stackiq\Settings\StackiqAdmin; use OCP\AppFramework\Controller; use OCP\AppFramework\Http; use OCP\AppFramework\Http\Attribute\AuthorizedAdminSetting; @@ -64,10 +64,10 @@ public function __construct( * * @return JSONResponse `{available, enabled, directoryUrl, peers, staleAfter, message}`. * - * @AuthorizedAdminSetting(settings=OCA\SoftwareCatalog\Settings\SoftwareCatalogAdmin) + * @AuthorizedAdminSetting(settings=OCA\Stackiq\Settings\StackiqAdmin) * @spec openspec/specs/federated-catalog-sync/spec.md */ - #[AuthorizedAdminSetting(settings: SoftwareCatalogAdmin::class)] + #[AuthorizedAdminSetting(settings: StackiqAdmin::class)] public function status(): JSONResponse { return new JSONResponse(data: $this->federation->getStatus()); }//end status() @@ -79,10 +79,10 @@ public function status(): JSONResponse { * * @return JSONResponse `{ok, reason}` or a 400 when the peer is rejected. * - * @AuthorizedAdminSetting(settings=OCA\SoftwareCatalog\Settings\SoftwareCatalogAdmin) + * @AuthorizedAdminSetting(settings=OCA\Stackiq\Settings\StackiqAdmin) * @spec openspec/specs/federated-catalog-sync/spec.md */ - #[AuthorizedAdminSetting(settings: SoftwareCatalogAdmin::class)] + #[AuthorizedAdminSetting(settings: StackiqAdmin::class)] public function addPeer(string $peerUrl = ''): JSONResponse { $result = $this->federation->addPeer($peerUrl); if ($result['ok'] === false) { @@ -99,10 +99,10 @@ public function addPeer(string $peerUrl = ''): JSONResponse { * * @return JSONResponse `{ok, reason}` or a 400 when the peer is unknown. * - * @AuthorizedAdminSetting(settings=OCA\SoftwareCatalog\Settings\SoftwareCatalogAdmin) + * @AuthorizedAdminSetting(settings=OCA\Stackiq\Settings\StackiqAdmin) * @spec openspec/specs/federated-catalog-sync/spec.md */ - #[AuthorizedAdminSetting(settings: SoftwareCatalogAdmin::class)] + #[AuthorizedAdminSetting(settings: StackiqAdmin::class)] public function removePeer(string $peerUrl = ''): JSONResponse { $result = $this->federation->removePeer($peerUrl); if ($result['ok'] === false) { @@ -117,10 +117,10 @@ public function removePeer(string $peerUrl = ''): JSONResponse { * * @return JSONResponse `{ok, reason, peers}` or a 400 when federation is off/unavailable. * - * @AuthorizedAdminSetting(settings=OCA\SoftwareCatalog\Settings\SoftwareCatalogAdmin) + * @AuthorizedAdminSetting(settings=OCA\Stackiq\Settings\StackiqAdmin) * @spec openspec/specs/federated-catalog-sync/spec.md */ - #[AuthorizedAdminSetting(settings: SoftwareCatalogAdmin::class)] + #[AuthorizedAdminSetting(settings: StackiqAdmin::class)] public function pull(): JSONResponse { $result = $this->federation->pullAllPeers(); if ($result['ok'] === false) { diff --git a/lib/Controller/GebruikController.php b/lib/Controller/GebruikController.php index 0ae801b1..7f07267c 100644 --- a/lib/Controller/GebruikController.php +++ b/lib/Controller/GebruikController.php @@ -1,26 +1,26 @@ * @copyright 2024 Conduction B.V. * @license EUPL-1.2 https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12 * @version GIT: - * @link https://github.com/nextcloud/softwarecatalog + * @link https://github.com/ConductionNL/stackiq * * @spec openspec/specs/method-decomposition/spec.md */ -namespace OCA\SoftwareCatalog\Controller; +namespace OCA\Stackiq\Controller; use Exception; -use OCA\SoftwareCatalog\Service\GebruikService; +use OCA\Stackiq\Service\GebruikService; use OCP\AppFramework\Controller; use OCP\AppFramework\Http; use OCP\AppFramework\Http\Attribute\AnonRateLimit; @@ -38,12 +38,12 @@ * with role-based access for gebruik-beheerder and aanbod-beheerder users. * * @category Controller - * @package OCA\SoftwareCatalog\Controller + * @package OCA\Stackiq\Controller * @author Conduction b.v. * @copyright 2024 Conduction B.V. * @license EUPL-1.2 https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12 * @version GIT: - * @link https://github.com/nextcloud/softwarecatalog + * @link https://github.com/ConductionNL/stackiq * * @spec openspec/changes/vendor-visibility-rbac/tasks.md#task-2 */ diff --git a/lib/Controller/IntakeController.php b/lib/Controller/IntakeController.php index f66b6815..7fbae2cf 100644 --- a/lib/Controller/IntakeController.php +++ b/lib/Controller/IntakeController.php @@ -17,11 +17,11 @@ * anonymous client has no CSRF token. * * @category Controller - * @package OCA\SoftwareCatalog\Controller + * @package OCA\Stackiq\Controller * @author Conduction b.v. * @copyright 2026 Conduction B.V. * @license EUPL-1.2 https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12 - * @link https://codeberg.org/Conduction/SoftwareCatalog + * @link https://github.com/ConductionNL/stackiq * * @spec openspec/specs/open-data-publishing/spec.md * @@ -31,10 +31,10 @@ declare(strict_types=1); -namespace OCA\SoftwareCatalog\Controller; +namespace OCA\Stackiq\Controller; -use OCA\SoftwareCatalog\AppInfo\Application; -use OCA\SoftwareCatalog\Service\IntakeService; +use OCA\Stackiq\AppInfo\Application; +use OCA\Stackiq\Service\IntakeService; use OCP\AppFramework\Controller; use OCP\AppFramework\Http; use OCP\AppFramework\Http\Attribute\AnonRateLimit; diff --git a/lib/Controller/MergeController.php b/lib/Controller/MergeController.php index 2cb7ef15..63a20766 100644 --- a/lib/Controller/MergeController.php +++ b/lib/Controller/MergeController.php @@ -1,7 +1,7 @@ * @copyright 2026 Conduction B.V. * @license EUPL-1.2 https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12 - * @link https://codeberg.org/Conduction/SoftwareCatalog + * @link https://github.com/ConductionNL/stackiq * * @spec openspec/specs/organisation-merge/spec.md#requirement-both-merge-endpoints-must-be-admin-only-with-an-explicit-per-object-authorization-guard * @@ -29,10 +29,10 @@ declare(strict_types=1); -namespace OCA\SoftwareCatalog\Controller; +namespace OCA\Stackiq\Controller; -use OCA\SoftwareCatalog\AppInfo\Application; -use OCA\SoftwareCatalog\Service\MergeOrganisatieService; +use OCA\Stackiq\AppInfo\Application; +use OCA\Stackiq\Service\MergeOrganisatieService; use OCP\AppFramework\Controller; use OCP\AppFramework\Http; use OCP\AppFramework\Http\Attribute\NoAdminRequired; diff --git a/lib/Controller/ModerationController.php b/lib/Controller/ModerationController.php index d233f474..368bce09 100644 --- a/lib/Controller/ModerationController.php +++ b/lib/Controller/ModerationController.php @@ -13,7 +13,7 @@ * to `approved`/`rejected` — the schema's own `status`-conditioned public * RBAC rule does the visibility job `publicatiedatum` does for organisatie. * - * AUTH (ADR-005): every method is `#[AuthorizedAdminSetting(SoftwareCatalogAdmin::class)]` + * AUTH (ADR-005): every method is `#[AuthorizedAdminSetting(StackiqAdmin::class)]` * — Nextcloud's admin-settings middleware rejects any non-admin caller before * the controller body runs, so an authenticated non-admin can never reach the * approve/publish path (no privilege escalation / IDOR — OWASP A01:2021). The @@ -21,11 +21,11 @@ * currently `pending` and on peer-sourced (federated) mirrors. * * @category Controller - * @package OCA\SoftwareCatalog\Controller + * @package OCA\Stackiq\Controller * @author Conduction b.v. * @copyright 2026 Conduction B.V. * @license EUPL-1.2 https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12 - * @link https://codeberg.org/Conduction/SoftwareCatalog + * @link https://github.com/ConductionNL/stackiq * * @spec openspec/specs/open-data-publishing/spec.md * @spec openspec/specs/catalog-ratings/spec.md#requirement-review-moderation-must-reuse-the-existing-moderation-queue-mechanism-not-a-second-one @@ -36,11 +36,11 @@ declare(strict_types=1); -namespace OCA\SoftwareCatalog\Controller; +namespace OCA\Stackiq\Controller; -use OCA\SoftwareCatalog\AppInfo\Application; -use OCA\SoftwareCatalog\Service\ModerationService; -use OCA\SoftwareCatalog\Settings\SoftwareCatalogAdmin; +use OCA\Stackiq\AppInfo\Application; +use OCA\Stackiq\Service\ModerationService; +use OCA\Stackiq\Settings\StackiqAdmin; use OCP\AppFramework\Controller; use OCP\AppFramework\Http; use OCP\AppFramework\Http\Attribute\AuthorizedAdminSetting; @@ -74,11 +74,11 @@ public function __construct( * * @return JSONResponse `{ok, items}` or a 400. * - * @AuthorizedAdminSetting(settings=OCA\SoftwareCatalog\Settings\SoftwareCatalogAdmin) + * @AuthorizedAdminSetting(settings=OCA\Stackiq\Settings\StackiqAdmin) * @spec openspec/specs/open-data-publishing/spec.md * @spec openspec/specs/catalog-ratings/spec.md#requirement-review-moderation-must-reuse-the-existing-moderation-queue-mechanism-not-a-second-one */ - #[AuthorizedAdminSetting(settings: SoftwareCatalogAdmin::class)] + #[AuthorizedAdminSetting(settings: StackiqAdmin::class)] public function pending(string $type = ModerationService::MODERATED_TYPE): JSONResponse { $result = $this->moderation->listPending(type: $type); if ($result['ok'] === false) { @@ -96,11 +96,11 @@ public function pending(string $type = ModerationService::MODERATED_TYPE): JSONR * * @return JSONResponse `{ok, status}` or a 400. * - * @AuthorizedAdminSetting(settings=OCA\SoftwareCatalog\Settings\SoftwareCatalogAdmin) + * @AuthorizedAdminSetting(settings=OCA\Stackiq\Settings\StackiqAdmin) * @spec openspec/specs/open-data-publishing/spec.md * @spec openspec/specs/catalog-ratings/spec.md#requirement-a-newly-submitted-review-must-require-moderation-approval-before-becoming-public */ - #[AuthorizedAdminSetting(settings: SoftwareCatalogAdmin::class)] + #[AuthorizedAdminSetting(settings: StackiqAdmin::class)] public function approve(string $uuid, string $type = ModerationService::MODERATED_TYPE): JSONResponse { $result = $this->moderation->approve($uuid, type: $type); if ($result['ok'] === false) { @@ -118,11 +118,11 @@ public function approve(string $uuid, string $type = ModerationService::MODERATE * * @return JSONResponse `{ok, status}` or a 400. * - * @AuthorizedAdminSetting(settings=OCA\SoftwareCatalog\Settings\SoftwareCatalogAdmin) + * @AuthorizedAdminSetting(settings=OCA\Stackiq\Settings\StackiqAdmin) * @spec openspec/specs/open-data-publishing/spec.md * @spec openspec/specs/catalog-ratings/spec.md#requirement-a-newly-submitted-review-must-require-moderation-approval-before-becoming-public */ - #[AuthorizedAdminSetting(settings: SoftwareCatalogAdmin::class)] + #[AuthorizedAdminSetting(settings: StackiqAdmin::class)] public function reject(string $uuid, string $type = ModerationService::MODERATED_TYPE): JSONResponse { $result = $this->moderation->reject($uuid, type: $type); if ($result['ok'] === false) { diff --git a/lib/Controller/OrganisationMembersController.php b/lib/Controller/OrganisationMembersController.php index cc752432..2cc5304c 100644 --- a/lib/Controller/OrganisationMembersController.php +++ b/lib/Controller/OrganisationMembersController.php @@ -1,7 +1,7 @@ * @copyright 2026 Conduction B.V. * @license EUPL-1.2 https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12 - * @link https://codeberg.org/Conduction/SoftwareCatalog + * @link https://github.com/ConductionNL/stackiq * * @spec openspec/specs/multi-org-membership/spec.md#requirement-granting-or-revoking-organisation-access-must-be-restricted-to-a-beheerder-of-that-organisation-req-004 * @@ -40,9 +40,10 @@ declare(strict_types=1); -namespace OCA\SoftwareCatalog\Controller; +namespace OCA\Stackiq\Controller; -use OCA\SoftwareCatalog\AppInfo\Application; +use OCA\OpenRegister\Service\OrganisationService; +use OCA\Stackiq\AppInfo\Application; use OCP\AppFramework\Controller; use OCP\AppFramework\Http; use OCP\AppFramework\Http\Attribute\NoAdminRequired; @@ -53,7 +54,6 @@ use OCP\IUserManager; use OCP\IUserSession; use Psr\Log\LoggerInterface; -use OCA\OpenRegister\Service\OrganisationService; /** * Beheerder-gated grant/revoke of organisation membership for an existing diff --git a/lib/Controller/PortfolioReportController.php b/lib/Controller/PortfolioReportController.php index fbde6b7f..bde7ad93 100644 --- a/lib/Controller/PortfolioReportController.php +++ b/lib/Controller/PortfolioReportController.php @@ -1,7 +1,7 @@ * @copyright 2026 Conduction B.V. * @license EUPL-1.2 https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12 - * @link https://codeberg.org/Conduction/SoftwareCatalog + * @link https://github.com/ConductionNL/stackiq * * @spec openspec/changes/portfolio-rationalization-time/specs/portfolio-rationalization-time/spec.md#requirement-report-and-csv-export-are-scoped-to-the-requesters-authorised-organisations * @@ -23,10 +23,10 @@ declare(strict_types=1); -namespace OCA\SoftwareCatalog\Controller; +namespace OCA\Stackiq\Controller; use Exception; -use OCA\SoftwareCatalog\Service\PortfolioReportService; +use OCA\Stackiq\Service\PortfolioReportService; use OCP\AppFramework\Controller; use OCP\AppFramework\Http; use OCP\AppFramework\Http\DataDownloadResponse; diff --git a/lib/Controller/PreferencesController.php b/lib/Controller/PreferencesController.php index b813742b..f5672a18 100644 --- a/lib/Controller/PreferencesController.php +++ b/lib/Controller/PreferencesController.php @@ -1,7 +1,7 @@ * @copyright 2024 Conduction B.V. @@ -17,14 +17,14 @@ * * @version GIT: * - * @link https://codeberg.org/Conduction/softwarecatalog + * @link https://github.com/ConductionNL/stackiq */ declare(strict_types=1); -namespace OCA\SoftwareCatalog\Controller; +namespace OCA\Stackiq\Controller; -use OCA\SoftwareCatalog\AppInfo\Application; +use OCA\Stackiq\AppInfo\Application; use OCP\AppFramework\Controller; use OCP\AppFramework\Http; use OCP\AppFramework\Http\JSONResponse; diff --git a/lib/Controller/PublicationController.php b/lib/Controller/PublicationController.php index dc908a7b..b6e4fe30 100644 --- a/lib/Controller/PublicationController.php +++ b/lib/Controller/PublicationController.php @@ -1,7 +1,7 @@ * @copyright 2026 Conduction B.V. * @license EUPL-1.2 https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12 - * @link https://codeberg.org/Conduction/SoftwareCatalog + * @link https://github.com/ConductionNL/stackiq * * @spec openspec/specs/open-data-publishing/spec.md * @@ -31,10 +31,10 @@ declare(strict_types=1); -namespace OCA\SoftwareCatalog\Controller; +namespace OCA\Stackiq\Controller; -use OCA\SoftwareCatalog\AppInfo\Application; -use OCA\SoftwareCatalog\Service\PublicationService; +use OCA\Stackiq\AppInfo\Application; +use OCA\Stackiq\Service\PublicationService; use OCP\AppFramework\Controller; use OCP\AppFramework\Http; use OCP\AppFramework\Http\Attribute\NoAdminRequired; diff --git a/lib/Controller/ReviewController.php b/lib/Controller/ReviewController.php index f9312948..becbe76c 100644 --- a/lib/Controller/ReviewController.php +++ b/lib/Controller/ReviewController.php @@ -14,11 +14,11 @@ * anonymous module/dienst detail page view, mirroring `FacetController`. * * @category Controller - * @package OCA\SoftwareCatalog\Controller + * @package OCA\Stackiq\Controller * @author Conduction b.v. * @copyright 2026 Conduction B.V. * @license EUPL-1.2 https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12 - * @link https://codeberg.org/Conduction/SoftwareCatalog + * @link https://github.com/ConductionNL/stackiq * * @spec openspec/specs/catalog-ratings/spec.md * @@ -28,15 +28,15 @@ declare(strict_types=1); -namespace OCA\SoftwareCatalog\Controller; +namespace OCA\Stackiq\Controller; -use OCA\SoftwareCatalog\AppInfo\Application; -use OCA\SoftwareCatalog\Service\ReviewAggregateService; -use OCA\SoftwareCatalog\Service\ReviewService; +use OCA\Stackiq\AppInfo\Application; +use OCA\Stackiq\Service\ReviewAggregateService; +use OCA\Stackiq\Service\ReviewService; use OCP\AppFramework\Controller; use OCP\AppFramework\Http; -use OCP\AppFramework\Http\Attribute\NoAdminRequired; use OCP\AppFramework\Http\Attribute\AnonRateLimit; +use OCP\AppFramework\Http\Attribute\NoAdminRequired; use OCP\AppFramework\Http\Attribute\NoCSRFRequired; use OCP\AppFramework\Http\Attribute\PublicPage; use OCP\AppFramework\Http\JSONResponse; diff --git a/lib/Controller/SbomController.php b/lib/Controller/SbomController.php index 2bebb47c..1cfb557a 100644 --- a/lib/Controller/SbomController.php +++ b/lib/Controller/SbomController.php @@ -1,7 +1,7 @@ * @copyright 2026 Conduction B.V. * @license EUPL-1.2 https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12 - * @link https://codeberg.org/Conduction/SoftwareCatalog + * @link https://github.com/ConductionNL/stackiq * * @spec openspec/specs/sbom-import/spec.md * @@ -36,11 +36,11 @@ declare(strict_types=1); -namespace OCA\SoftwareCatalog\Controller; +namespace OCA\Stackiq\Controller; -use OCA\SoftwareCatalog\AppInfo\Application; -use OCA\SoftwareCatalog\Exception\UnsupportedSbomFormatException; -use OCA\SoftwareCatalog\Service\SbomImportService; +use OCA\Stackiq\AppInfo\Application; +use OCA\Stackiq\Exception\UnsupportedSbomFormatException; +use OCA\Stackiq\Service\SbomImportService; use OCP\AppFramework\Controller; use OCP\AppFramework\Db\DoesNotExistException; use OCP\AppFramework\Http; @@ -69,6 +69,12 @@ class SbomController extends Controller { * group membership OR manage-ACL on the target moduleVersie's parent * module"). * + * `software-catalog-admins` is FROZEN across the stackiq -> stackiq + * rename: it is a Nextcloud group id, and membership is stored against that + * literal in `oc_group_user`. Renaming it makes every membership check miss + * without raising anything, so the app would silently drop the permissions + * of everyone currently in the group. + * * @var array */ private const MANAGE_GROUPS = ['software-catalog-admins', 'aanbod-beheerder', 'functioneel-beheerder']; diff --git a/lib/Controller/Settings/ModuleRegistrationHandler.php b/lib/Controller/Settings/ModuleRegistrationHandler.php index 0c13d806..31eec685 100644 --- a/lib/Controller/Settings/ModuleRegistrationHandler.php +++ b/lib/Controller/Settings/ModuleRegistrationHandler.php @@ -7,11 +7,11 @@ * and CouplingBetweenObjects on that controller. * * @category Handler - * @package OCA\SoftwareCatalog\Controller\Settings + * @package OCA\Stackiq\Controller\Settings * @author Conduction b.v. * @copyright 2024 Conduction B.V. * @license EUPL-1.2 https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12 - * @link https://codeberg.org/Conduction/SoftwareCatalog + * @link https://github.com/ConductionNL/stackiq * * @spec openspec/changes/method-decomposition/tasks.md#task-3 * @@ -21,9 +21,9 @@ declare(strict_types=1); -namespace OCA\SoftwareCatalog\Controller\Settings; +namespace OCA\Stackiq\Controller\Settings; -use OCA\SoftwareCatalog\Service\ModuleRegistrationService; +use OCA\Stackiq\Service\ModuleRegistrationService; use Psr\Log\LoggerInterface; /** diff --git a/lib/Controller/Settings/SyncHandler.php b/lib/Controller/Settings/SyncHandler.php index 77971911..e96ffe0c 100644 --- a/lib/Controller/Settings/SyncHandler.php +++ b/lib/Controller/Settings/SyncHandler.php @@ -7,11 +7,11 @@ * and CouplingBetweenObjects on that controller. * * @category Handler - * @package OCA\SoftwareCatalog\Controller\Settings + * @package OCA\Stackiq\Controller\Settings * @author Conduction b.v. * @copyright 2024 Conduction B.V. * @license EUPL-1.2 https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12 - * @link https://codeberg.org/Conduction/SoftwareCatalog + * @link https://github.com/ConductionNL/stackiq * * @spec openspec/changes/method-decomposition/tasks.md#task-3 * @@ -21,9 +21,9 @@ declare(strict_types=1); -namespace OCA\SoftwareCatalog\Controller\Settings; +namespace OCA\Stackiq\Controller\Settings; -use OCA\SoftwareCatalog\Service\OrganizationSyncService; +use OCA\Stackiq\Service\OrganizationSyncService; use Psr\Log\LoggerInterface; /** diff --git a/lib/Controller/SettingsController.php b/lib/Controller/SettingsController.php index 5bc0f6af..2064ade2 100644 --- a/lib/Controller/SettingsController.php +++ b/lib/Controller/SettingsController.php @@ -22,15 +22,15 @@ * SPDX-License-Identifier: EUPL-1.2 */ -namespace OCA\SoftwareCatalog\Controller; +namespace OCA\Stackiq\Controller; -use OCA\OpenRegister\Service\ConfigurationService; use OCA\OpenRegister\Contract\ObjectServiceInterface; -use OCA\SoftwareCatalog\Service\ArchiMateService; -use OCA\SoftwareCatalog\Service\EolSyncService; -use OCA\SoftwareCatalog\Service\OrganizationSyncService; -use OCA\SoftwareCatalog\Service\ProgressTracker; -use OCA\SoftwareCatalog\Service\SettingsService; +use OCA\OpenRegister\Service\ConfigurationService; +use OCA\Stackiq\Service\ArchiMateService; +use OCA\Stackiq\Service\EolSyncService; +use OCA\Stackiq\Service\OrganizationSyncService; +use OCA\Stackiq\Service\ProgressTracker; +use OCA\Stackiq\Service\SettingsService; use OCP\App\IAppManager; use OCP\AppFramework\Controller; use OCP\AppFramework\Http; @@ -578,7 +578,7 @@ public function load(): JSONResponse { }//end load() /** - * Initialize the SoftwareCatalog settings + * Initialize the Stackiq settings * * @return JSONResponse JSON response containing the initialization results * @@ -629,7 +629,7 @@ public function status(): JSONResponse { 'versionInfo' => $versionInfo, 'timestamp' => time(), 'autoConfigCompleted' => $this->config->getValueString( - 'softwarecatalog', + 'stackiq', 'auto_config_completed', 'false' ) === 'true', @@ -801,7 +801,7 @@ public function sendTestEmail(): JSONResponse { ); } catch (\Exception $e) { $this->logger->error( - 'SoftwareCatalog: Failed to send test email in controller', + 'Stackiq: Failed to send test email in controller', [ 'exception_class' => get_class($e), 'exception_message' => $e->getMessage(), @@ -1915,14 +1915,14 @@ public function testEmailConnection(): JSONResponse { return new JSONResponse(['message' => 'Not authenticated'], Http::STATUS_UNAUTHORIZED); } - $this->logger->info('SoftwareCatalog: Email connection test endpoint called'); + $this->logger->info('Stackiq: Email connection test endpoint called'); try { $data = $this->request->getParams(); $emailSettings = $data['emailSettings'] ?? $data ?? []; $this->logger->info( - 'SoftwareCatalog: Email connection test request data', + 'Stackiq: Email connection test request data', [ 'has_email_settings' => empty($emailSettings) === false, 'transport_type' => $emailSettings['transportType'] ?? 'not specified', @@ -1933,7 +1933,7 @@ public function testEmailConnection(): JSONResponse { $result = $this->settingsService->testEmailConnection($emailSettings); $this->logger->info( - 'SoftwareCatalog: Email connection test result from service', + 'Stackiq: Email connection test result from service', [ 'success' => $result['success'], 'message' => $result['message'] ?? 'no message', @@ -1949,7 +1949,7 @@ public function testEmailConnection(): JSONResponse { ); } catch (\Exception $e) { $this->logger->error( - 'SoftwareCatalog: Failed to test email connection', + 'Stackiq: Failed to test email connection', [ 'exception_class' => get_class($e), 'exception_message' => $e->getMessage(), @@ -2876,13 +2876,13 @@ public function testArchiMateRoundTrip(): JSONResponse { } try { - $this->logger->info('SoftwareCatalog: ArchiMate round-trip test started'); + $this->logger->info('Stackiq: ArchiMate round-trip test started'); // Call the ArchiMate service to perform round-trip test. $result = $this->archiMateService->testRoundTrip(); $this->logger->info( - 'SoftwareCatalog: ArchiMate round-trip test completed', + 'Stackiq: ArchiMate round-trip test completed', [ 'success' => $result['success'], 'message' => $result['message'] ?? 'no message', @@ -2899,7 +2899,7 @@ public function testArchiMateRoundTrip(): JSONResponse { ); } catch (\Exception $e) { $this->logger->error( - 'SoftwareCatalog: ArchiMate round-trip test failed', + 'Stackiq: ArchiMate round-trip test failed', [ 'exception_class' => get_class($e), 'exception_message' => $e->getMessage(), @@ -3596,7 +3596,7 @@ public function bulkSyncStandards(): JSONResponse { $this->logger->info('SettingsController: Starting bulk sync of module standards.'); // Get the ModuleComplianceService from the container. - $moduleComplianceService = $this->container->get(\OCA\SoftwareCatalog\Service\ModuleComplianceService::class); + $moduleComplianceService = $this->container->get(\OCA\Stackiq\Service\ModuleComplianceService::class); // Perform the bulk sync. $results = $moduleComplianceService->bulkSyncModuleStandards(); diff --git a/lib/Controller/ViewController.php b/lib/Controller/ViewController.php index f39199ce..1b84d49e 100644 --- a/lib/Controller/ViewController.php +++ b/lib/Controller/ViewController.php @@ -1,25 +1,25 @@ * @copyright 2024 Conduction B.V. * @license EUPL-1.2 https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12 * @version GIT: - * @link https://github.com/nextcloud/softwarecatalog + * @link https://github.com/ConductionNL/stackiq * * @spec openspec/specs/method-decomposition/spec.md */ -namespace OCA\SoftwareCatalog\Controller; +namespace OCA\Stackiq\Controller; -use OCA\SoftwareCatalog\Service\ViewService; +use OCA\Stackiq\Service\ViewService; use OCP\AppFramework\Controller; use OCP\AppFramework\Http; use OCP\AppFramework\Http\JSONResponse; @@ -34,12 +34,12 @@ * with optional enrichment capabilities for products, usage data (gebruik), and related information. * * @category Controller - * @package OCA\SoftwareCatalog\Controller + * @package OCA\Stackiq\Controller * @author Conduction b.v. * @copyright 2024 Conduction B.V. * @license EUPL-1.2 https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12 * @version GIT: - * @link https://github.com/nextcloud/softwarecatalog + * @link https://github.com/ConductionNL/stackiq */ class ViewController extends Controller { /** @@ -377,7 +377,7 @@ public function getApiDocumentation(): JSONResponse { $documentation = [ 'api_version' => '1.0.0', - 'description' => 'SoftwareCatalog View API - Query and enrich ArchiMate views', + 'description' => 'Stackiq View API - Query and enrich ArchiMate views', 'base_url' => '/api/views', 'endpoints' => [ [ diff --git a/lib/Dashboard/ConceptOrganisatiesWidget.php b/lib/Dashboard/ConceptOrganisatiesWidget.php index d2f8f1d7..2c7d7297 100644 --- a/lib/Dashboard/ConceptOrganisatiesWidget.php +++ b/lib/Dashboard/ConceptOrganisatiesWidget.php @@ -4,17 +4,17 @@ * Concept Organisaties Dashboard Widget. * * @category Dashboard - * @package OCA\SoftwareCatalog\Dashboard + * @package OCA\Stackiq\Dashboard * @author Conduction b.v. * @copyright 2024 Conduction B.V. * @license EUPL-1.2 https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12 * @version GIT: - * @link https://codeberg.org/Conduction/SoftwareCatalog + * @link https://github.com/ConductionNL/stackiq */ -namespace OCA\SoftwareCatalog\Dashboard; +namespace OCA\Stackiq\Dashboard; -use OCA\SoftwareCatalog\AppInfo\Application; +use OCA\Stackiq\AppInfo\Application; use OCP\Dashboard\IWidget; use OCP\IL10N; use OCP\IURLGenerator; @@ -39,7 +39,13 @@ public function __construct( * @return string The widget ID */ public function getId(): string { - return 'softwarecatalog_concept_organisaties_widget'; + // FROZEN across the stackiq -> stackiq rename. The Dashboard app + // stores each user's chosen widgets BY WIDGET ID, in its own `dashboard` + // appid namespace in `oc_preferences` — data this app's repair steps + // cannot reach. Renaming this id therefore does not error: the widget + // simply stops matching the stored selection and silently vanishes from + // every dashboard that had it. + return 'stackiq_concept_organisaties_widget'; }//end getId() /** @@ -66,7 +72,7 @@ public function getOrder(): int { * @return string The icon CSS class name */ public function getIconClass(): string { - return 'icon-softwarecatalog-widget'; + return 'icon-stackiq-widget'; }//end getIconClass() /** diff --git a/lib/EventListener/DecisionConcludedListener.php b/lib/EventListener/DecisionConcludedListener.php index c9fa995b..682747be 100644 --- a/lib/EventListener/DecisionConcludedListener.php +++ b/lib/EventListener/DecisionConcludedListener.php @@ -1,24 +1,24 @@ Actief` transition is reached ONLY here, as * a projection of an `approved` decidesk outcome — never on local authority. * * @category EventListener - * @package OCA\SoftwareCatalog\EventListener + * @package OCA\Stackiq\EventListener * @author Conduction b.v. * @copyright 2026 Conduction B.V. * @license EUPL-1.2 https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12 - * @link https://codeberg.org/Conduction/SoftwareCatalog + * @link https://github.com/ConductionNL/stackiq * * @spec openspec/specs/contract-decision-delegation/spec.md * @@ -28,10 +28,10 @@ declare(strict_types=1); -namespace OCA\SoftwareCatalog\EventListener; +namespace OCA\Stackiq\EventListener; use OCA\Decidesk\Event\DecisionConcludedEvent; -use OCA\SoftwareCatalog\Service\ContractApprovalService; +use OCA\Stackiq\Service\ContractApprovalService; use OCP\EventDispatcher\Event; use OCP\EventDispatcher\IEventListener; use Psr\Log\LoggerInterface; @@ -59,7 +59,7 @@ public function __construct( /** * Handle a concluded decidesk Decision. * - * Only `DecisionConcludedEvent`s whose `sourceApp` is softwarecatalog are + * Only `DecisionConcludedEvent`s whose `sourceApp` is stackiq are * acted on; everything else is ignored. The carried `decisionId` is * IDOR-checked against the contract's stored `approvalDecisionId` inside * `resolveContractForOutcome()` before any projection is written. diff --git a/lib/EventListener/ModuleComplianceSubscriber.php b/lib/EventListener/ModuleComplianceSubscriber.php index 478beb70..d751ad60 100644 --- a/lib/EventListener/ModuleComplianceSubscriber.php +++ b/lib/EventListener/ModuleComplianceSubscriber.php @@ -4,26 +4,26 @@ * Module Compliance Subscriber. * * This file contains the subscriber class for handling module compliance updates - * in the SoftwareCatalog application. + * in the Stackiq application. * * @category EventListener - * @package OCA\SoftwareCatalog\EventListener + * @package OCA\Stackiq\EventListener * @author Conduction b.v. * @copyright 2024 Conduction B.V. * @license EUPL-1.2 https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12 * @version GIT: - * @link https://codeberg.org/Conduction/SoftwareCatalog + * @link https://github.com/ConductionNL/stackiq */ declare(strict_types=1); -namespace OCA\SoftwareCatalog\EventListener; +namespace OCA\Stackiq\EventListener; use OCA\OpenRegister\Event\ObjectCreatedEvent; use OCA\OpenRegister\Event\ObjectUpdatedEvent; -use OCA\SoftwareCatalog\Service\ModuleComplianceService; -use OCA\SoftwareCatalog\Service\ModuleVersionService; -use OCA\SoftwareCatalog\Service\SettingsService; +use OCA\Stackiq\Service\ModuleComplianceService; +use OCA\Stackiq\Service\ModuleVersionService; +use OCA\Stackiq\Service\SettingsService; use OCP\EventDispatcher\Event; use OCP\EventDispatcher\IEventListener; use Psr\Container\ContainerInterface; @@ -36,11 +36,11 @@ * synchronizes the 'standards' property based on linked compliance objects. * * @category EventListener - * @package OCA\SoftwareCatalog\EventListener + * @package OCA\Stackiq\EventListener * @author Conduction b.v. * @license EUPL-1.2 https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12 * @version GIT: - * @link https://codeberg.org/Conduction/SoftwareCatalog + * @link https://github.com/ConductionNL/stackiq */ class ModuleComplianceSubscriber implements IEventListener { /** diff --git a/lib/EventListener/ModuleRegistrationSubscriber.php b/lib/EventListener/ModuleRegistrationSubscriber.php index 267ed084..ec8612ae 100644 --- a/lib/EventListener/ModuleRegistrationSubscriber.php +++ b/lib/EventListener/ModuleRegistrationSubscriber.php @@ -7,22 +7,22 @@ * based on the owning organisation's type. * * @category EventListener - * @package OCA\SoftwareCatalog\EventListener + * @package OCA\Stackiq\EventListener * @author Conduction b.v. * @copyright 2024 Conduction B.V. * @license EUPL-1.2 https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12 * @version GIT: - * @link https://codeberg.org/Conduction/SoftwareCatalog + * @link https://github.com/ConductionNL/stackiq */ declare(strict_types=1); -namespace OCA\SoftwareCatalog\EventListener; +namespace OCA\Stackiq\EventListener; use OCA\OpenRegister\Event\ObjectCreatedEvent; use OCA\OpenRegister\Event\ObjectUpdatedEvent; -use OCA\SoftwareCatalog\Service\ModuleRegistrationService; -use OCA\SoftwareCatalog\Service\SettingsService; +use OCA\Stackiq\Service\ModuleRegistrationService; +use OCA\Stackiq\Service\SettingsService; use OCP\EventDispatcher\Event; use OCP\EventDispatcher\IEventListener; use Psr\Container\ContainerInterface; @@ -33,7 +33,7 @@ * based on the owning organisation's type. * * @category EventListener - * @package OCA\SoftwareCatalog\EventListener + * @package OCA\Stackiq\EventListener */ class ModuleRegistrationSubscriber implements IEventListener { /** diff --git a/lib/EventListener/OpenRegisterEventsDebugListener.php b/lib/EventListener/OpenRegisterEventsDebugListener.php index 87118c31..4416542e 100644 --- a/lib/EventListener/OpenRegisterEventsDebugListener.php +++ b/lib/EventListener/OpenRegisterEventsDebugListener.php @@ -1,14 +1,14 @@ * @copyright 2024 Conduction B.V. @@ -16,14 +16,14 @@ * * @version GIT: * - * @link https://SoftwareCatalog.app + * @link https://github.com/ConductionNL/stackiq * * @spec openspec/specs/method-decomposition/spec.md */ declare(strict_types=1); -namespace OCA\SoftwareCatalog\EventListener; +namespace OCA\Stackiq\EventListener; use OCA\OpenRegister\Event\ObjectCreatedEvent; use OCA\OpenRegister\Event\ObjectDeletedEvent; @@ -43,10 +43,10 @@ use Psr\Log\LoggerInterface; /** - * Debug event listener for all OpenRegister events in SoftwareCatalog + * Debug event listener for all OpenRegister events in Stackiq * * This listener provides comprehensive debugging information for all OpenRegister events - * received by the SoftwareCatalog app. It logs event details at info level and can be + * received by the Stackiq app. It logs event details at info level and can be * easily enabled/disabled. * * @template T of Event @@ -111,7 +111,7 @@ public function handle(Event $event): void { $this->logger->debug( 'OpenRegister debug listener triggered', [ - 'app' => 'softwarecatalog', + 'app' => 'stackiq', 'eventType' => $eventType, 'eventClass' => $eventClass, 'debugEnabled' => $this->debugEnabled, @@ -119,7 +119,7 @@ public function handle(Event $event): void { ); if ($this->debugEnabled === false) { - $this->logger->warning('SoftwareCatalog OpenRegister Debug: Debug disabled, skipping detailed logging.'); + $this->logger->warning('Stackiq OpenRegister Debug: Debug disabled, skipping detailed logging.'); return; } @@ -128,9 +128,9 @@ public function handle(Event $event): void { // Log comprehensive debug information. $this->logger->info( - '[SoftwareCatalog] OPENREGISTER EVENT: {eventType} received from OpenRegister', + '[Stackiq] OPENREGISTER EVENT: {eventType} received from OpenRegister', [ - 'app' => 'softwarecatalog', + 'app' => 'stackiq', 'eventType' => $eventType, 'eventClass' => $eventClass, 'listenerClass' => self::class, @@ -192,7 +192,7 @@ private function extractEventData(Event $event): array { } $data['eventType'] = 'Unknown'; - $data['note'] = 'Event type not specifically handled by SoftwareCatalog debug listener'; + $data['note'] = 'Event type not specifically handled by Stackiq debug listener'; return $data; }//end extractEventData() diff --git a/lib/EventListener/SoftwareCatalogEventListener.php b/lib/EventListener/StackiqEventListener.php similarity index 87% rename from lib/EventListener/SoftwareCatalogEventListener.php rename to lib/EventListener/StackiqEventListener.php index 73d18ea2..5f2cf672 100644 --- a/lib/EventListener/SoftwareCatalogEventListener.php +++ b/lib/EventListener/StackiqEventListener.php @@ -1,13 +1,13 @@ * @copyright 2024 Conduction B.V. * @license EUPL-1.2 https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12 @@ -19,7 +19,7 @@ declare(strict_types=1); -namespace OCA\SoftwareCatalog\EventListener; +namespace OCA\Stackiq\EventListener; use OCA\OpenRegister\Event\ObjectCreatedEvent; use OCA\OpenRegister\Event\ObjectDeletedEvent; @@ -27,9 +27,9 @@ use OCA\OpenRegister\Event\ObjectRevertedEvent; use OCA\OpenRegister\Event\ObjectUnlockedEvent; use OCA\OpenRegister\Event\ObjectUpdatedEvent; -use OCA\SoftwareCatalog\Service\ContactpersoonService; -use OCA\SoftwareCatalog\Service\GebruikSyncService; -use OCA\SoftwareCatalog\Service\SettingsService; +use OCA\Stackiq\Service\ContactpersoonService; +use OCA\Stackiq\Service\GebruikSyncService; +use OCA\Stackiq\Service\SettingsService; use OCP\EventDispatcher\Event; use OCP\EventDispatcher\IEventListener; use Psr\Container\ContainerInterface; @@ -43,7 +43,7 @@ * user blocking/unblocking functionality. * * @category EventListener - * @package OCA\SoftwareCatalog\EventListener + * @package OCA\Stackiq\EventListener * @author Conduction b.v. * @license EUPL-1.2 https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12 * @version GIT: @@ -53,9 +53,9 @@ * @SuppressWarnings(PHPMD.ExcessiveClassComplexity) * @SuppressWarnings(PHPMD.CouplingBetweenObjects) */ -class SoftwareCatalogEventListener implements IEventListener { +class StackiqEventListener implements IEventListener { /** - * Constructor for SoftwareCatalogEventListener + * Constructor for StackiqEventListener * * @param ContainerInterface $container DI container for lazy service resolution. */ @@ -83,7 +83,7 @@ public function handle(Event $event): void { $settingsService = $this->container->get(SettingsService::class); $logger->info( - 'SoftwareCatalog: Processing event', + 'Stackiq: Processing event', [ 'eventType' => get_class($event), 'timestamp' => date('Y-m-d H:i:s'), @@ -100,7 +100,7 @@ public function handle(Event $event): void { try { $logger = $this->container->get(LoggerInterface::class); $logger->error( - 'SoftwareCatalog: Error in event handler', + 'Stackiq: Error in event handler', [ 'eventType' => get_class($event), 'exception' => $e->getMessage(), @@ -171,7 +171,7 @@ private function dispatchEvent( || $event instanceof ObjectRevertedEvent ) { $logger->debug( - 'SoftwareCatalog: Ignoring object lifecycle event', + 'Stackiq: Ignoring object lifecycle event', [ 'eventType' => get_class($event), ] @@ -261,10 +261,10 @@ private function runOrganizationSync( ): void { $objectId = $object->getUuid(); try { - $orgSyncService = $this->container->get('OCA\SoftwareCatalog\Service\OrganizationSyncService'); + $orgSyncService = $this->container->get('OCA\Stackiq\Service\OrganizationSyncService'); $result = $orgSyncService->processSpecificOrganization($object); $logger->info( - 'SoftwareCatalog: Successfully processed organization ' . $phase, + 'Stackiq: Successfully processed organization ' . $phase, [ 'objectId' => $objectId, 'processResult' => $result, @@ -272,7 +272,7 @@ private function runOrganizationSync( ); } catch (\Exception $e) { $logger->error( - 'SoftwareCatalog: Failed to process organization ' . $phase, + 'Stackiq: Failed to process organization ' . $phase, [ 'objectId' => $objectId, 'exception' => $e->getMessage(), @@ -309,7 +309,7 @@ private function runGebruikSync( $gebruikSyncService = $this->container->get(GebruikSyncService::class); $result = $gebruikSyncService->processSpecificGebruik($object); $logger->info( - 'SoftwareCatalog: Successfully processed gebruik ' . $phase, + 'Stackiq: Successfully processed gebruik ' . $phase, [ 'objectId' => $objectId, 'processResult' => $result, @@ -317,7 +317,7 @@ private function runGebruikSync( ); } catch (\Exception $e) { $logger->error( - 'SoftwareCatalog: Failed to process gebruik ' . $phase, + 'Stackiq: Failed to process gebruik ' . $phase, [ 'objectId' => $objectId, 'exception' => $e->getMessage(), @@ -366,7 +366,7 @@ private function refetchOrganizationWithContactpersonen( ); $logger->info( - 'SoftwareCatalog: Refetched organization with contactpersonen', + 'Stackiq: Refetched organization with contactpersonen', [ 'objectId' => $objectId, 'contactpersonenCount' => count( @@ -378,7 +378,7 @@ private function refetchOrganizationWithContactpersonen( return $orgWithContacts; } catch (\Exception $e) { $logger->error( - 'SoftwareCatalog: Failed to refetch organization with contactpersonen', + 'Stackiq: Failed to refetch organization with contactpersonen', [ 'objectId' => $objectId, 'exception' => $e->getMessage(), @@ -414,7 +414,7 @@ private function handleObjectCreated( ): void { $object = $event->getObject(); if ($object === null) { - $logger->warning('SoftwareCatalog: ObjectCreatedEvent received with null object'); + $logger->warning('Stackiq: ObjectCreatedEvent received with null object'); return; } @@ -426,7 +426,7 @@ private function handleObjectCreated( $objectSchemaIdInt = (int)$objectSchemaId; $logger->info( - 'SoftwareCatalog: Processing object creation', + 'Stackiq: Processing object creation', [ 'objectId' => $objectId, 'schemaId' => $objectSchemaId, @@ -444,7 +444,7 @@ private function handleObjectCreated( $gebruikSchemaId = $catalogSchemaIds['usage']; $logger->debug( - 'SoftwareCatalog: Configuration lookup results', + 'Stackiq: Configuration lookup results', [ 'organisatieSchemaId' => $organisationSchemaId, 'contactpersoonSchemaId' => $contactSchemaId, @@ -462,7 +462,7 @@ private function handleObjectCreated( // Only process active organizations. if ($this->isActiveStatus(status: $status) === false) { $logger->debug( - 'SoftwareCatalog: Skipping non-active organization creation', + 'Stackiq: Skipping non-active organization creation', [ 'objectId' => $objectId, 'status' => $status, @@ -472,7 +472,7 @@ private function handleObjectCreated( } $logger->info( - 'SoftwareCatalog: Processing active organization creation', + 'Stackiq: Processing active organization creation', [ 'objectId' => $objectId, 'status' => $status, @@ -485,28 +485,28 @@ private function handleObjectCreated( // Check if this is a contactpersoon object. if ($this->matchesSchema(objectSchemaIdInt: $objectSchemaIdInt, configured: $contactSchemaId) === true) { - $logger->info('SoftwareCatalog: Processing contactpersoon creation', ['objectId' => $objectId]); + $logger->info('Stackiq: Processing contactpersoon creation', ['objectId' => $objectId]); $contactSvc->processContactpersoon($object); return; } // Check if this is a contactgegevens object (deprecated - use contactpersoon instead). if ($this->matchesSchema(objectSchemaIdInt: $objectSchemaIdInt, configured: $contactInfoSchemaId) === true) { - $logger->info('SoftwareCatalog: Processing contactgegevens creation (deprecated)', ['objectId' => $objectId]); + $logger->info('Stackiq: Processing contactgegevens creation (deprecated)', ['objectId' => $objectId]); // Contactgegevens is deprecated, use contactpersoon instead. return; } // Check if this is a gebruik object. if ($this->matchesSchema(objectSchemaIdInt: $objectSchemaIdInt, configured: $gebruikSchemaId) === true) { - $logger->info('SoftwareCatalog: Processing gebruik creation', ['objectId' => $objectId]); + $logger->info('Stackiq: Processing gebruik creation', ['objectId' => $objectId]); $this->runGebruikSync(object: $object, phase: 'creation', logger: $logger); return; }//end if // Log unhandled object types. $logger->debug( - 'SoftwareCatalog: Object creation not handled - not a supported object type', + 'Stackiq: Object creation not handled - not a supported object type', [ 'objectId' => $objectId, 'schemaId' => $objectSchemaIdInt, @@ -547,7 +547,7 @@ private function handleObjectUpdated( $oldObject = $event->getOldObject(); if ($object === null) { - $logger->warning('SoftwareCatalog: ObjectUpdatedEvent received with null object'); + $logger->warning('Stackiq: ObjectUpdatedEvent received with null object'); return; } @@ -559,7 +559,7 @@ private function handleObjectUpdated( $objectSchemaIdInt = (int)$objectSchemaId; $logger->info( - 'SoftwareCatalog: Processing object update', + 'Stackiq: Processing object update', [ 'objectId' => $objectId, 'schemaId' => $objectSchemaId, @@ -576,7 +576,7 @@ private function handleObjectUpdated( $logger->debug( 'Got organisation schema ID', [ - 'app' => 'softwarecatalog', + 'app' => 'stackiq', 'organisatieSchemaId' => $organisationSchemaId, 'organisatieSchemaIdInt' => $orgSchemaIdInt, ] @@ -585,7 +585,7 @@ private function handleObjectUpdated( $logger->debug( 'Organization schema check', [ - 'app' => 'softwarecatalog', + 'app' => 'stackiq', 'objectSchemaId' => $objectSchemaId, 'objectSchemaIdInt' => $objectSchemaIdInt, 'organisatieSchemaId' => $organisationSchemaId, @@ -607,7 +607,7 @@ private function handleObjectUpdated( $logger->debug( 'Organization status check', [ - 'app' => 'softwarecatalog', + 'app' => 'stackiq', 'objectId' => $objectId, 'status' => $status, 'oldStatus' => $oldStatus, @@ -621,7 +621,7 @@ private function handleObjectUpdated( // Only process active organizations. if ($this->isActiveStatus(status: $status) === true && $status !== $oldStatus) { $logger->info( - 'SoftwareCatalog: Processing active organization update', + 'Stackiq: Processing active organization update', [ 'objectId' => $objectId, 'status' => $status, @@ -641,7 +641,7 @@ private function handleObjectUpdated( if ($this->isActiveStatus(status: $status) === false || $status === $oldStatus) { $logger->debug( - 'SoftwareCatalog: Skipping non-active organization update', + 'Stackiq: Skipping non-active organization update', [ 'objectId' => $objectId, 'status' => $status, @@ -659,7 +659,7 @@ private function handleObjectUpdated( if ($contactSchemaId !== null && $objectSchemaIdInt === $cntSchemaIdInt) { $logger->info( - 'SoftwareCatalog: Matched contactpersoon schema - processing update', + 'Stackiq: Matched contactpersoon schema - processing update', [ 'objectId' => $objectId, 'schemaId' => $objectSchemaId, @@ -674,7 +674,7 @@ private function handleObjectUpdated( ); $logger->info( - 'SoftwareCatalog: Successfully processed contactpersoon update', + 'Stackiq: Successfully processed contactpersoon update', [ 'objectId' => $objectId, 'timestamp' => date('Y-m-d H:i:s'), @@ -682,7 +682,7 @@ private function handleObjectUpdated( ); } catch (\Exception $e) { $logger->error( - 'SoftwareCatalog: Failed to process contactpersoon update', + 'Stackiq: Failed to process contactpersoon update', [ 'objectId' => $objectId, 'exception' => $e->getMessage(), @@ -702,7 +702,7 @@ private function handleObjectUpdated( if ($contactInfoSchemaId !== null && $objectSchemaIdInt === $infoSchemaIdInt) { $logger->info( - 'SoftwareCatalog: Matched contactgegevens schema - processing update (backward compatibility)', + 'Stackiq: Matched contactgegevens schema - processing update (backward compatibility)', [ 'objectId' => $objectId, 'schemaId' => $objectSchemaId, @@ -718,7 +718,7 @@ private function handleObjectUpdated( ); $logger->info( - 'SoftwareCatalog: Successfully processed contactgegevens update (as contactpersoon)', + 'Stackiq: Successfully processed contactgegevens update (as contactpersoon)', [ 'objectId' => $objectId, 'timestamp' => date('Y-m-d H:i:s'), @@ -726,7 +726,7 @@ private function handleObjectUpdated( ); } catch (\Exception $e) { $logger->error( - 'SoftwareCatalog: Failed to process contactgegevens update', + 'Stackiq: Failed to process contactgegevens update', [ 'objectId' => $objectId, 'exception' => $e->getMessage(), @@ -746,7 +746,7 @@ private function handleObjectUpdated( if ($gebruikSchemaId !== null && $objectSchemaIdInt === $gebruikSchemaIdInt) { $logger->info( - 'SoftwareCatalog: Matched gebruik schema - processing update', + 'Stackiq: Matched gebruik schema - processing update', [ 'objectId' => $objectId, 'schemaId' => $objectSchemaId, @@ -760,7 +760,7 @@ private function handleObjectUpdated( // Log if we don't handle this schema type. $logger->debug( - 'SoftwareCatalog: Object update not handled - focusing only on organisatie, contactpersonen, and gebruik', + 'Stackiq: Object update not handled - focusing only on organisatie, contactpersonen, and gebruik', [ 'objectId' => $objectId, 'schemaId' => $objectSchemaId, @@ -801,7 +801,7 @@ private function handleObjectDeleted( ): void { $object = $event->getObject(); if ($object === null) { - $logger->warning('SoftwareCatalog: ObjectDeletedEvent received with null object'); + $logger->warning('Stackiq: ObjectDeletedEvent received with null object'); return; } @@ -810,7 +810,7 @@ private function handleObjectDeleted( $objectRegisterId = $object->getRegister(); $logger->info( - 'SoftwareCatalog: Processing object deletion', + 'Stackiq: Processing object deletion', [ 'objectId' => $objectId, 'schemaId' => $objectSchemaId, @@ -825,7 +825,7 @@ private function handleObjectDeleted( $objectSchemaIdInt = (int)$objectSchemaId; if ($organisationSchemaId !== null && $objectSchemaIdInt === $orgSchemaIdInt) { - $logger->info('SoftwareCatalog: Processing organization deletion', ['objectId' => $objectId]); + $logger->info('Stackiq: Processing organization deletion', ['objectId' => $objectId]); $this->runOrganizationSync(object: $object, phase: 'deletion', logger: $logger); return; }//end if @@ -836,7 +836,7 @@ private function handleObjectDeleted( if ($contactSchemaId !== null && $objectSchemaIdInt === $cntSchemaIdInt) { $logger->info( - 'SoftwareCatalog: Matched contactpersoon schema - processing deletion', + 'Stackiq: Matched contactpersoon schema - processing deletion', [ 'objectId' => $objectId, 'schemaId' => $objectSchemaId, @@ -848,7 +848,7 @@ private function handleObjectDeleted( $contactSvc->handleContactDeletion($object); $logger->info( - 'SoftwareCatalog: Successfully processed contactpersoon deletion', + 'Stackiq: Successfully processed contactpersoon deletion', [ 'objectId' => $objectId, 'timestamp' => date('Y-m-d H:i:s'), @@ -856,7 +856,7 @@ private function handleObjectDeleted( ); } catch (\Exception $e) { $logger->error( - 'SoftwareCatalog: Failed to process contactpersoon deletion', + 'Stackiq: Failed to process contactpersoon deletion', [ 'objectId' => $objectId, 'exception' => $e->getMessage(), @@ -876,7 +876,7 @@ private function handleObjectDeleted( if ($contactInfoSchemaId !== null && $objectSchemaIdInt === $infoSchemaIdInt) { $logger->info( - 'SoftwareCatalog: Matched contactgegevens schema - processing deletion (backward compatibility)', + 'Stackiq: Matched contactgegevens schema - processing deletion (backward compatibility)', [ 'objectId' => $objectId, 'schemaId' => $objectSchemaId, @@ -888,7 +888,7 @@ private function handleObjectDeleted( $contactSvc->handleContactDeletion($object); $logger->info( - 'SoftwareCatalog: Successfully processed contactgegevens deletion', + 'Stackiq: Successfully processed contactgegevens deletion', [ 'objectId' => $objectId, 'timestamp' => date('Y-m-d H:i:s'), @@ -896,7 +896,7 @@ private function handleObjectDeleted( ); } catch (\Exception $e) { $logger->error( - 'SoftwareCatalog: Failed to process contactgegevens deletion', + 'Stackiq: Failed to process contactgegevens deletion', [ 'objectId' => $objectId, 'exception' => $e->getMessage(), @@ -918,7 +918,7 @@ private function handleObjectDeleted( $objectData = $object->getObject(); $logger->info( - 'SoftwareCatalog: Matched gebruik schema - processing deletion', + 'Stackiq: Matched gebruik schema - processing deletion', [ 'objectId' => $objectId, 'schemaId' => $objectSchemaId, @@ -931,7 +931,7 @@ private function handleObjectDeleted( // For deletions, we mainly log the event since the object is being removed. // No specific cleanup needed for gebruik objects currently. $logger->info( - 'SoftwareCatalog: Gebruik object deleted - no specific cleanup required', + 'Stackiq: Gebruik object deleted - no specific cleanup required', [ 'objectId' => $objectId, 'timestamp' => date('Y-m-d H:i:s'), @@ -942,7 +942,7 @@ private function handleObjectDeleted( // Log if we don't handle this schema type. $logger->debug( - 'SoftwareCatalog: Object deletion not handled - focusing only on organisatie, contactpersonen, and gebruik', + 'Stackiq: Object deletion not handled - focusing only on organisatie, contactpersonen, and gebruik', [ 'objectId' => $objectId, 'schemaId' => $objectSchemaId, diff --git a/lib/EventListener/TestEventListener.php b/lib/EventListener/TestEventListener.php index 83ed303e..c55d0250 100644 --- a/lib/EventListener/TestEventListener.php +++ b/lib/EventListener/TestEventListener.php @@ -1,13 +1,13 @@ * @copyright 2024 Conduction B.V. * @license EUPL-1.2 https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12 @@ -17,7 +17,7 @@ declare(strict_types=1); -namespace OCA\SoftwareCatalog\EventListener; +namespace OCA\Stackiq\EventListener; use OCP\EventDispatcher\Event; use OCP\EventDispatcher\IEventListener; @@ -32,7 +32,7 @@ * triggered for testing purposes. * * @category EventListener - * @package OCA\SoftwareCatalog\EventListener + * @package OCA\Stackiq\EventListener * @author Conduction b.v. * @license EUPL-1.2 https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12 * @version GIT: @@ -63,7 +63,7 @@ public function __construct( public function handle(Event $event): void { // Log that we received ANY event first. $this->logger->info( - 'SoftwareCatalog TestEventListener: Event received!', + 'Stackiq TestEventListener: Event received!', [ 'eventClass' => get_class($event), 'timestamp' => date('Y-m-d H:i:s'), @@ -75,7 +75,7 @@ public function handle(Event $event): void { if (($event instanceof UserLoggedInEvent) === false) { // Log other events we might receive. $this->logger->debug( - 'SoftwareCatalog TestEventListener: Received unhandled event', + 'Stackiq TestEventListener: Received unhandled event', [ 'eventClass' => get_class($event), 'timestamp' => date('Y-m-d H:i:s'), @@ -87,7 +87,7 @@ public function handle(Event $event): void { $user = $event->getUser(); $this->logger->info( - 'SoftwareCatalog TestEventListener: User logged in successfully!', + 'Stackiq TestEventListener: User logged in successfully!', [ 'userId' => $user->getUID(), 'userDisplayName' => $user->getDisplayName(), @@ -100,7 +100,7 @@ public function handle(Event $event): void { // Test that we can access Nextcloud services. try { $this->logger->debug( - 'SoftwareCatalog TestEventListener: Event listener is working correctly!', + 'Stackiq TestEventListener: Event listener is working correctly!', [ 'message' => 'This confirms that event listeners are properly registered and triggered', 'userId' => $user->getUID(), @@ -109,7 +109,7 @@ public function handle(Event $event): void { ); } catch (\Exception $e) { $this->logger->error( - 'SoftwareCatalog TestEventListener: Error in event processing', + 'Stackiq TestEventListener: Error in event processing', [ 'exception' => $e->getMessage(), 'trace' => $e->getTraceAsString(), diff --git a/lib/EventListener/UserProfileUpdatedEventListener.php b/lib/EventListener/UserProfileUpdatedEventListener.php index 6bbb4c42..a20c2e36 100644 --- a/lib/EventListener/UserProfileUpdatedEventListener.php +++ b/lib/EventListener/UserProfileUpdatedEventListener.php @@ -7,7 +7,7 @@ * the changed fields back to the corresponding contactpersoon object. * * @category EventListener - * @package OCA\SoftwareCatalog\EventListener + * @package OCA\Stackiq\EventListener * @author Conduction b.v. * @copyright 2024 Conduction B.V. * @license EUPL-1.2 https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12 @@ -17,15 +17,15 @@ declare(strict_types=1); -namespace OCA\SoftwareCatalog\EventListener; +namespace OCA\Stackiq\EventListener; +use OCA\OpenRegister\Contract\ObjectServiceInterface; use OCA\OpenRegister\Event\UserProfileUpdatedEvent; -use OCA\SoftwareCatalog\Service\SettingsService; +use OCA\Stackiq\Service\SettingsService; use OCP\EventDispatcher\Event; use OCP\EventDispatcher\IEventListener; use Psr\Container\ContainerInterface; use Psr\Log\LoggerInterface; -use OCA\OpenRegister\Contract\ObjectServiceInterface; /** * Syncs user profile changes to the corresponding contactpersoon object. diff --git a/lib/Examples/ContactpersoonServiceExample.php b/lib/Examples/ContactpersoonServiceExample.php index 5c82ddc0..ce3b5420 100644 --- a/lib/Examples/ContactpersoonServiceExample.php +++ b/lib/Examples/ContactpersoonServiceExample.php @@ -7,30 +7,30 @@ * method to retrieve contact persons for an organization with their user details spliced in. * * @category Example - * @package OCA\SoftwareCatalog\Examples + * @package OCA\Stackiq\Examples * @author Conduction b.v. * @copyright 2024 Conduction B.V. * @license EUPL-1.2 https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12 * @version GIT: - * @link https://codeberg.org/Conduction/SoftwareCatalog + * @link https://github.com/ConductionNL/stackiq */ declare(strict_types=1); -namespace OCA\SoftwareCatalog\Examples; +namespace OCA\Stackiq\Examples; -use OCA\SoftwareCatalog\Service\ContactpersoonService; +use OCA\Stackiq\Service\ContactpersoonService; use Psr\Log\LoggerInterface; /** * Example class demonstrating ContactpersoonService usage. * * @category Example - * @package OCA\SoftwareCatalog\Examples + * @package OCA\Stackiq\Examples * @author Conduction b.v. * @license EUPL-1.2 https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12 * @version GIT: - * @link https://codeberg.org/Conduction/SoftwareCatalog + * @link https://github.com/ConductionNL/stackiq */ class ContactpersoonServiceExample { /** diff --git a/lib/Exception/UnsupportedSbomFormatException.php b/lib/Exception/UnsupportedSbomFormatException.php index dac44031..9ae16c17 100644 --- a/lib/Exception/UnsupportedSbomFormatException.php +++ b/lib/Exception/UnsupportedSbomFormatException.php @@ -3,7 +3,7 @@ /** * UnsupportedSbomFormatException. * - * Thrown by {@see \OCA\SoftwareCatalog\Service\SbomParserService} when an + * Thrown by {@see \OCA\Stackiq\Service\SbomParserService} when an * uploaded document's `bomFormat`/`specVersion` (CycloneDX) or `spdxVersion` * (SPDX) is not one this app supports. Carries the offending format/version * in its message so the controller can surface a precise 422 rather than a @@ -11,11 +11,11 @@ * this is thrown (fail-fast, not a silent partial parse). * * @category Exception - * @package OCA\SoftwareCatalog\Exception + * @package OCA\Stackiq\Exception * @author Conduction b.v. * @copyright 2026 Conduction B.V. * @license EUPL-1.2 https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12 - * @link https://codeberg.org/Conduction/SoftwareCatalog + * @link https://github.com/ConductionNL/stackiq * * @spec openspec/specs/sbom-import/spec.md#requirement-cyclonedx-sbom-files-are-parsed-into-a-normalized-component-list * @@ -25,7 +25,7 @@ declare(strict_types=1); -namespace OCA\SoftwareCatalog\Exception; +namespace OCA\Stackiq\Exception; /** * Thrown when an uploaded SBOM document's format/spec-version is not supported. diff --git a/lib/Portal/PortalContributionProvider.php b/lib/Portal/PortalContributionProvider.php index d3e68dfb..eb68b033 100644 --- a/lib/Portal/PortalContributionProvider.php +++ b/lib/Portal/PortalContributionProvider.php @@ -1,9 +1,9 @@ * @copyright 2026 Conduction B.V. @@ -42,10 +42,10 @@ declare(strict_types=1); -namespace OCA\SoftwareCatalog\Portal; +namespace OCA\Stackiq\Portal; /** - * Declares what an external portal subject may see in the Software Catalog. + * Declares what an external portal subject may see in Stackiq. * * The contribution is a declarative manifest (pure data — no I/O, no * callbacks). All subject identity (subjectRef, audience, organisation, trust) @@ -82,7 +82,7 @@ class PortalContributionProvider { /** * The audiences this provider contributes to (contract v2, preferred). * - * The registry probes for this method first. Software Catalog serves + * The registry probes for this method first. Stackiq serves * software suppliers (`vendor-org`, organisatie.type "Supplier") and the * municipalities/collaborations that consume that software (`participant-org`, * organisatie.type "Municipality" / "Collaboration" / "Community"). The two @@ -116,7 +116,7 @@ public function getAudience(): string { * * The subject array is server-derived by portaliq (subjectRef UUID, * audience, organisation, trust level low|substantial|high). Returns null - * for any audience Software Catalog does not serve (fail-closed; the registry + * for any audience Stackiq does not serve (fail-closed; the registry * already filters by audience, but a provider must not rely on that). This * wave declares READ collections only — no create-actions and no endpoint * actions (see design.md for the deferral rationale). @@ -159,7 +159,7 @@ public function getContribution(array $subject): ?array { */ private function vendorContribution(): array { return [ - 'label' => 'Software Catalog', + 'label' => 'Stackiq', 'collections' => [ [ 'id' => 'vendorDiensten', @@ -262,7 +262,7 @@ private function vendorContribution(): array { */ private function participantContribution(): array { return [ - 'label' => 'Software Catalog', + 'label' => 'Stackiq', 'collections' => [ [ 'id' => 'participantGebruik', diff --git a/lib/Repair/BackfillContractApprovalState.php b/lib/Repair/BackfillContractApprovalState.php index 8f85020e..73d4b36e 100644 --- a/lib/Repair/BackfillContractApprovalState.php +++ b/lib/Repair/BackfillContractApprovalState.php @@ -18,12 +18,12 @@ * named-argument convention as in MigrateContactsToNc. * * @category Repair - * @package OCA\SoftwareCatalog\Repair + * @package OCA\Stackiq\Repair * @author Conduction b.v. * @copyright 2026 Conduction B.V. * @license EUPL-1.2 https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12 * @version GIT: - * @link https://codeberg.org/Conduction/SoftwareCatalog + * @link https://github.com/ConductionNL/stackiq * * @spec openspec/specs/contract-decision-delegation/spec.md * @@ -33,10 +33,10 @@ declare(strict_types=1); -namespace OCA\SoftwareCatalog\Repair; +namespace OCA\Stackiq\Repair; -use OCA\SoftwareCatalog\Service\ContractApprovalService; -use OCA\SoftwareCatalog\Service\SettingsService; +use OCA\Stackiq\Service\ContractApprovalService; +use OCA\Stackiq\Service\SettingsService; use OCP\App\IAppManager; use OCP\Migration\IOutput; use OCP\Migration\IRepairStep; diff --git a/lib/Repair/InitializeSettings.php b/lib/Repair/InitializeSettings.php index ac4914de..9dad2e9a 100644 --- a/lib/Repair/InitializeSettings.php +++ b/lib/Repair/InitializeSettings.php @@ -1,23 +1,23 @@ * @copyright 2024 Conduction B.V. * @license EUPL-1.2 https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12 * @version GIT: - * @link https://codeberg.org/Conduction/SoftwareCatalog + * @link https://github.com/ConductionNL/stackiq */ declare(strict_types=1); -namespace OCA\SoftwareCatalog\Repair; +namespace OCA\Stackiq\Repair; -use OCA\SoftwareCatalog\AppInfo\Application; -use OCA\SoftwareCatalog\Service\SettingsService; +use OCA\Stackiq\AppInfo\Application; +use OCA\Stackiq\Service\SettingsService; use OCP\App\IAppManager; use OCP\IAppConfig; use OCP\Migration\IOutput; @@ -26,12 +26,12 @@ use Psr\Log\LoggerInterface; /** - * Repair step that initializes SoftwareCatalog settings on install/upgrade. + * Repair step that initializes Stackiq settings on install/upgrade. * * This runs only during app install or upgrade, not on every request. * * @category Repair - * @package OCA\SoftwareCatalog\Repair + * @package OCA\Stackiq\Repair * * @spec openspec/specs/repair-init/spec.md */ @@ -60,7 +60,7 @@ public function __construct( * @spec openspec/specs/repair-init/spec.md */ public function getName(): string { - return 'Initialize SoftwareCatalog settings'; + return 'Initialize Stackiq settings'; }//end getName() /** @@ -95,7 +95,7 @@ public function run(IOutput $output): void { } $output->info('Initializing settings for version ' . $currentAppVersion); - $this->logger->info('SoftwareCatalog repair: Starting initialization for version ' . $currentAppVersion); + $this->logger->info('Stackiq repair: Starting initialization for version ' . $currentAppVersion); // @spec openspec/specs/contract-administration/spec.md // Seed window defaults only when the admin has not set them, so @@ -145,18 +145,18 @@ public function run(IOutput $output): void { if (empty($result['errors']) === false) { foreach ($result['errors'] as $error) { $output->warning('Initialization warning: ' . $error); - $this->logger->warning('SoftwareCatalog repair: ' . $error); + $this->logger->warning('Stackiq repair: ' . $error); } } $output->info('Settings initialization completed'); - $this->logger->info('SoftwareCatalog repair: Initialization completed', ['result' => $result]); + $this->logger->info('Stackiq repair: Initialization completed', ['result' => $result]); } catch (\Exception $e) { // Still mark as initialized to prevent repeated failures. $currentAppVersion = $this->appManager->getAppVersion(Application::APP_ID); $this->config->setValueString(Application::APP_ID, 'last_initialized_version', $currentAppVersion); $output->warning('Settings initialization failed: ' . $e->getMessage()); - $this->logger->error('SoftwareCatalog repair: Initialization failed', ['exception' => $e->getMessage()]); + $this->logger->error('Stackiq repair: Initialization failed', ['exception' => $e->getMessage()]); }//end try $output->advance(1); diff --git a/lib/Repair/MigrateAppConfigKeys.php b/lib/Repair/MigrateAppConfigKeys.php new file mode 100644 index 00000000..83e8691e --- /dev/null +++ b/lib/Repair/MigrateAppConfigKeys.php @@ -0,0 +1,230 @@ +` and `` and MUST stay + * there. `InitializeSettings` writes app config itself; if it ran first, every + * key it touched would look "already present" here and the operator's real + * value would stay stranded in the old namespace forever. + * + * All OCP service calls use POSITIONAL arguments (named args are FATAL on + * `occ upgrade`). + * + * @category Repair + * @package OCA\Stackiq\Repair + * @author Conduction b.v. + * @copyright 2026 Conduction B.V. + * @license EUPL-1.2 https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12 + * @version GIT: + * @link https://github.com/ConductionNL/stackiq + * + * @spec openspec/changes/rename-app-id-to-stackiq/specs/app-id-rename/spec.md#requirement-stored-app-config-survives-the-rename + * + * SPDX-FileCopyrightText: 2026 Conduction B.V. + * SPDX-License-Identifier: EUPL-1.2 + */ + +declare(strict_types=1); + +namespace OCA\Stackiq\Repair; + +use OCA\Stackiq\AppInfo\Application; +use OCP\IAppConfig; +use OCP\Migration\IOutput; +use OCP\Migration\IRepairStep; +use Psr\Log\LoggerInterface; +use Throwable; + +/** + * Copies app config from the legacy `stackiq` app id to `stackiq`. + * + * @spec openspec/changes/rename-app-id-to-stackiq/specs/app-id-rename/spec.md#requirement-stored-app-config-survives-the-rename + */ +class MigrateAppConfigKeys implements IRepairStep { + /** + * The app id every stored row was written under before the rename. + */ + public const LEGACY_APP_ID = 'softwarecatalog'; + + /** + * Keys Nextcloud owns in every app's namespace. These MUST NOT be copied. + * + * `enabled` is the dangerous one. `AppManager::enableApp()` writes it as + * type MIXED; copying it with `setValueString()` stores it as STRING, and + * the next `occ app:enable` then fails permanently with + * `AppConfigTypeConflictException` — a conflict that is hit BEFORE the app + * can run anything that would repair it. `installed_version` and `types` + * are Nextcloud's own bookkeeping for the new id and are already correct. + */ + private const RESERVED_KEYS = [ + 'enabled', + 'installed_version', + 'types', + ]; + + /** + * Constructor. + * + * @param IAppConfig $appConfig The typed app config service. + * @param LoggerInterface $logger The logger. + */ + public function __construct( + private readonly IAppConfig $appConfig, + private readonly LoggerInterface $logger, + ) { + }//end __construct() + + /** + * Returns the name of this repair step. + * + * @return string The repair step name. + * + * @spec openspec/changes/rename-app-id-to-stackiq/specs/app-id-rename/spec.md#requirement-stored-app-config-survives-the-rename + */ + public function getName(): string { + return 'Migrate app config from the stackiq app id to stackiq'; + }//end getName() + + /** + * Copy every non-reserved app config key from the legacy app id. + * + * Both the READS and the WRITE sit inside the try. This step runs under + * `` — the only hook that fires on the fresh install an app-id + * rename performs — so an escaping throw aborts the install and the app + * never enables at all. A config key that fails to copy is a reverted + * setting; a failed install is no app. + * + * @param IOutput $output The output interface for progress reporting. + * + * @return void + * + * @spec openspec/changes/rename-app-id-to-stackiq/specs/app-id-rename/spec.md#requirement-stored-app-config-survives-the-rename + */ + public function run(IOutput $output): void { + try { + // Exhaustive enumeration. getKeys() lists every key stored under the + // old app id regardless of value — the alternative shapes + // (searchValues, getUsersForUserValue) all match on a VALUE, so over + // an open value set they migrate nothing and report success. + $keys = $this->appConfig->getKeys(self::LEGACY_APP_ID); + + // Values come from getAllValues() rather than the typed getters + // because it returns each row in its STORED type without asserting + // one. getValueString() on a key stored as MIXED or INT raises + // AppConfigTypeConflictException, which would abort the whole copy + // on the first typed key it met. + $values = $this->appConfig->getAllValues(self::LEGACY_APP_ID); + + $copied = 0; + $skipped = 0; + + foreach ($keys as $key) { + if (in_array($key, self::RESERVED_KEYS, true) === true) { + $skipped++; + continue; + } + + // Never clobber a value the new namespace already holds — either + // a previous run copied it, or an admin has since changed it. + if ($this->appConfig->hasKey(Application::APP_ID, $key) === true) { + $skipped++; + continue; + } + + if (array_key_exists($key, $values) === false) { + $skipped++; + continue; + } + + if ($this->copyValue(key: $key, value: $values[$key]) === true) { + $copied++; + continue; + } + + $skipped++; + } + + $output->info( + sprintf( + 'Stackiq: migrated %d app config key(s) from "%s" (skipped %d)', + $copied, + self::LEGACY_APP_ID, + $skipped + ) + ); + } catch (Throwable $e) { + // Swallowed deliberately — see the docblock. Logged at error level so + // the reverted-settings symptom has a cause to find. + $this->logger->error( + 'Stackiq: failed to migrate app config from the legacy app id: ' . $e->getMessage(), + [ + 'app' => Application::APP_ID, + 'exception' => $e, + ] + ); + $output->warning('Stackiq: app config migration failed; see the log. Settings may have reverted to defaults.'); + }//end try + }//end run() + + /** + * Write one legacy value into the new namespace in its own type. + * + * Empty strings and empty arrays are treated as "nothing stored" and are + * not copied — an empty value is indistinguishable from the default every + * reader already supplies, so copying it adds a row and changes nothing. + * Scalars are copied as-is: `false` and `0` are real, chosen values. + * + * @param string $key The config key. + * @param mixed $value The value as stored under the legacy app id. + * + * @return bool True when a value was written. + * + * @spec openspec/changes/rename-app-id-to-stackiq/specs/app-id-rename/spec.md#requirement-stored-app-config-survives-the-rename + */ + protected function copyValue(string $key, mixed $value): bool { + if (is_bool($value) === true) { + $this->appConfig->setValueBool(Application::APP_ID, $key, $value); + return true; + } + + if (is_int($value) === true) { + $this->appConfig->setValueInt(Application::APP_ID, $key, $value); + return true; + } + + if (is_float($value) === true) { + $this->appConfig->setValueFloat(Application::APP_ID, $key, $value); + return true; + } + + if (is_array($value) === true) { + if ($value === []) { + return false; + } + + $this->appConfig->setValueArray(Application::APP_ID, $key, $value); + return true; + } + + $string = (string)$value; + if ($string === '') { + return false; + } + + $this->appConfig->setValueString(Application::APP_ID, $key, $string); + return true; + }//end copyValue() +}//end class diff --git a/lib/Repair/MigrateBackgroundJobClasses.php b/lib/Repair/MigrateBackgroundJobClasses.php new file mode 100644 index 00000000..09e1fc10 --- /dev/null +++ b/lib/Repair/MigrateBackgroundJobClasses.php @@ -0,0 +1,145 @@ + + * @copyright 2026 Conduction B.V. + * @license EUPL-1.2 https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12 + * @version GIT: + * @link https://github.com/ConductionNL/stackiq + * + * @spec openspec/changes/rename-app-id-to-stackiq/specs/app-id-rename/spec.md#requirement-stored-background-job-classes-survive-the-rename + * + * SPDX-FileCopyrightText: 2026 Conduction B.V. + * SPDX-License-Identifier: EUPL-1.2 + */ + +declare(strict_types=1); + +namespace OCA\Stackiq\Repair; + +use OCA\Stackiq\AppInfo\Application; +use OCP\BackgroundJob\IJobList; +use OCP\Migration\IOutput; +use OCP\Migration\IRepairStep; +use Psr\Log\LoggerInterface; +use Throwable; + +/** + * Removes background job registrations left behind by the namespace rename. + * + * @spec openspec/changes/rename-app-id-to-stackiq/specs/app-id-rename/spec.md#requirement-stored-background-job-classes-survive-the-rename + */ +class MigrateBackgroundJobClasses implements IRepairStep { + /** + * The exact job class strings written to `oc_jobs` before the rename. + * + * Written out in full rather than derived from the current class names, + * so that a later namespace change cannot silently widen what this step + * deletes. These four are the complete `` list as it stood + * in `appinfo/info.xml` before the rename. + */ + public const LEGACY_JOB_CLASSES = [ + 'OCA\SoftwareCatalog\BackgroundJob\OrganizationContactSyncJob', + 'OCA\SoftwareCatalog\BackgroundJob\ContractStatusJob', + 'OCA\SoftwareCatalog\BackgroundJob\FederationSyncJob', + 'OCA\SoftwareCatalog\BackgroundJob\EolSyncJob', + ]; + + /** + * Constructor. + * + * @param IJobList $jobList The background job list. + * @param LoggerInterface $logger The logger. + */ + public function __construct( + private readonly IJobList $jobList, + private readonly LoggerInterface $logger, + ) { + }//end __construct() + + /** + * Returns the name of this repair step. + * + * @return string The repair step name. + * + * @spec openspec/changes/rename-app-id-to-stackiq/specs/app-id-rename/spec.md#requirement-stored-background-job-classes-survive-the-rename + */ + public function getName(): string { + return 'Deregister background jobs orphaned by the OCA\\SoftwareCatalog namespace rename'; + }//end getName() + + /** + * Remove every legacy job registration. + * + * The whole body sits inside the try. This step runs under `` — + * the only hook that fires on the fresh install an app-id rename performs — + * so an escaping throw aborts the install and the app never enables at all. + * + * @param IOutput $output The output interface for progress reporting. + * + * @return void + * + * @spec openspec/changes/rename-app-id-to-stackiq/specs/app-id-rename/spec.md#requirement-stored-background-job-classes-survive-the-rename + */ + public function run(IOutput $output): void { + try { + $removed = 0; + + foreach (self::LEGACY_JOB_CLASSES as $legacyClass) { + // The remove() call is a no-op when the row is absent, so has() is + // only an accounting call — it keeps the reported count honest on a + // re-run rather than claiming work that did not happen. + // PHPStan wants a class-string here, which this method can + // never supply: the whole point is to deregister jobs whose class + // NO LONGER EXISTS after the namespace rename. A loadable + // class-string would mean there was nothing to clean up. The + // stored rows are matched by their literal class name, which is + // exactly what IJobList compares against. + // @phpstan-ignore argument.type + if ($this->jobList->has($legacyClass, null) === false) { + continue; + } + + // @phpstan-ignore argument.type + $this->jobList->remove($legacyClass, null); + $removed++; + } + + $output->info( + sprintf('Stackiq: deregistered %d orphaned background job(s) from the legacy namespace', $removed) + ); + } catch (Throwable $e) { + // Swallowed deliberately — see the docblock. + $this->logger->error( + 'Stackiq: failed to deregister legacy background jobs: ' . $e->getMessage(), + [ + 'app' => Application::APP_ID, + 'exception' => $e, + ] + ); + $output->warning('Stackiq: legacy background job cleanup failed; see the log.'); + }//end try + }//end run() +}//end class diff --git a/lib/Repair/MigrateContactsToNc.php b/lib/Repair/MigrateContactsToNc.php index 3821e522..663accdc 100644 --- a/lib/Repair/MigrateContactsToNc.php +++ b/lib/Repair/MigrateContactsToNc.php @@ -1,7 +1,7 @@ * @copyright 2026 Conduction B.V. * @license EUPL-1.2 https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12 * @version GIT: - * @link https://codeberg.org/Conduction/SoftwareCatalog + * @link https://github.com/ConductionNL/stackiq * * @spec openspec/specs/softwarecatalog-contacts-to-nc/spec.md */ declare(strict_types=1); -namespace OCA\SoftwareCatalog\Repair; +namespace OCA\Stackiq\Repair; -use OCA\SoftwareCatalog\AppInfo\Application; -use OCA\SoftwareCatalog\Service\SettingsService; -use OCA\SoftwareCatalog\Service\SoftwareCatalogContactSyncService; +use OCA\Stackiq\AppInfo\Application; +use OCA\Stackiq\Service\SettingsService; +use OCA\Stackiq\Service\StackiqContactSyncService; use OCP\App\IAppManager; use OCP\IAppConfig; use OCP\Migration\IOutput; @@ -44,11 +44,11 @@ * Idempotent, fail-safe migration of identity to Nextcloud Contacts. * * @category Repair - * @package OCA\SoftwareCatalog\Repair + * @package OCA\Stackiq\Repair * @author Conduction b.v. * @license EUPL-1.2 https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12 * @version GIT: - * @link https://codeberg.org/Conduction/SoftwareCatalog + * @link https://github.com/ConductionNL/stackiq * * @spec openspec/specs/softwarecatalog-contacts-to-nc/spec.md */ @@ -69,7 +69,7 @@ class MigrateContactsToNc implements IRepairStep { * * @param IAppManager $appManager The app manager. * @param SettingsService $settingsService The settings service (register/schema id resolution). - * @param SoftwareCatalogContactSyncService $contactSync The contacts bridge. + * @param StackiqContactSyncService $contactSync The contacts bridge. * @param IAppConfig $appConfig The app config (convergence marker). * @param LoggerInterface $logger The logger. * @@ -78,7 +78,7 @@ class MigrateContactsToNc implements IRepairStep { public function __construct( private readonly IAppManager $appManager, private readonly SettingsService $settingsService, - private readonly SoftwareCatalogContactSyncService $contactSync, + private readonly StackiqContactSyncService $contactSync, private readonly IAppConfig $appConfig, private readonly LoggerInterface $logger, ) { @@ -92,7 +92,7 @@ public function __construct( * @spec openspec/specs/softwarecatalog-contacts-to-nc/spec.md */ public function getName(): string { - return 'Migrate SoftwareCatalog contacts/organisations to the Nextcloud addressbook'; + return 'Migrate Stackiq contacts/organisations to the Nextcloud addressbook'; }//end getName() /** diff --git a/lib/Repair/MigrateUserPreferences.php b/lib/Repair/MigrateUserPreferences.php new file mode 100644 index 00000000..96d89639 --- /dev/null +++ b/lib/Repair/MigrateUserPreferences.php @@ -0,0 +1,195 @@ + + * @copyright 2026 Conduction B.V. + * @license EUPL-1.2 https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12 + * @version GIT: + * @link https://github.com/ConductionNL/stackiq + * + * @spec openspec/changes/rename-app-id-to-stackiq/specs/app-id-rename/spec.md#requirement-stored-user-preferences-survive-the-rename + * + * SPDX-FileCopyrightText: 2026 Conduction B.V. + * SPDX-License-Identifier: EUPL-1.2 + */ + +declare(strict_types=1); + +namespace OCA\Stackiq\Repair; + +use Closure; +use OCA\Stackiq\AppInfo\Application; +use OCP\IConfig; +use OCP\IUser; +use OCP\IUserManager; +use OCP\Migration\IOutput; +use OCP\Migration\IRepairStep; +use Psr\Log\LoggerInterface; +use Throwable; + +/** + * Copies per-user preferences from the legacy `stackiq` app id to `stackiq`. + * + * @spec openspec/changes/rename-app-id-to-stackiq/specs/app-id-rename/spec.md#requirement-stored-user-preferences-survive-the-rename + */ +class MigrateUserPreferences implements IRepairStep { + /** + * The app id every stored preference was written under before the rename. + */ + public const LEGACY_APP_ID = 'softwarecatalog'; + + /** + * Number of preference values copied during the current run. + * + * @var int + */ + private int $copied = 0; + + /** + * Number of users that carried at least one legacy preference. + * + * @var int + */ + private int $users = 0; + + /** + * Constructor. + * + * @param IConfig $config The user config service. + * @param IUserManager $userManager The user manager. + * @param LoggerInterface $logger The logger. + */ + public function __construct( + private readonly IConfig $config, + private readonly IUserManager $userManager, + private readonly LoggerInterface $logger, + ) { + }//end __construct() + + /** + * Returns the name of this repair step. + * + * @return string The repair step name. + * + * @spec openspec/changes/rename-app-id-to-stackiq/specs/app-id-rename/spec.md#requirement-stored-user-preferences-survive-the-rename + */ + public function getName(): string { + return 'Migrate user preferences from the stackiq app id to stackiq'; + }//end getName() + + /** + * Copy every legacy preference for every seen user. + * + * Both the READS and the WRITES sit inside the try. This step runs under + * `` — the only hook that fires on the fresh install an app-id + * rename performs — so an escaping throw aborts the install and the app + * never enables at all. + * + * @param IOutput $output The output interface for progress reporting. + * + * @return void + * + * @spec openspec/changes/rename-app-id-to-stackiq/specs/app-id-rename/spec.md#requirement-stored-user-preferences-survive-the-rename + */ + public function run(IOutput $output): void { + $this->copied = 0; + $this->users = 0; + + try { + // The callback must return bool|null, not void: IUserManager treats a + // `false` return as "stop iterating", so null means "keep going". + // A void closure does not satisfy that contract. + $this->userManager->callForSeenUsers( + function (IUser $user): ?bool { + $this->migrateUser(user: $user); + return null; + } + ); + + $output->info( + sprintf( + 'Stackiq: migrated %d user preference value(s) for %d user(s) from "%s"', + $this->copied, + $this->users, + self::LEGACY_APP_ID + ) + ); + } catch (Throwable $e) { + // Swallowed deliberately — see the docblock. + $this->logger->error( + 'Stackiq: failed to migrate user preferences from the legacy app id: ' . $e->getMessage(), + [ + 'app' => Application::APP_ID, + 'exception' => $e, + ] + ); + $output->warning('Stackiq: user preference migration failed; see the log. Users may see default view settings.'); + }//end try + }//end run() + + /** + * Copy one user's legacy preferences into the new namespace. + * + * @param IUser $user The user to migrate. + * + * @return void + * + * @spec openspec/changes/rename-app-id-to-stackiq/specs/app-id-rename/spec.md#requirement-stored-user-preferences-survive-the-rename + */ + protected function migrateUser(IUser $user): void { + $userId = $user->getUID(); + + // Exhaustive per-user enumeration. getUserKeys() lists the keys this + // user actually holds under the old app id — it needs no value to match, + // which is exactly why getUsersForUserValue() is unusable here. + $keys = $this->config->getUserKeys($userId, self::LEGACY_APP_ID); + if ($keys === []) { + return; + } + + $touched = false; + + foreach ($keys as $key) { + $legacy = $this->config->getUserValue($userId, self::LEGACY_APP_ID, $key, ''); + if ($legacy === '') { + continue; + } + + // Never clobber a value the new namespace already holds. + $current = $this->config->getUserValue($userId, Application::APP_ID, $key, ''); + if ($current !== '') { + continue; + } + + $this->config->setUserValue($userId, Application::APP_ID, $key, $legacy); + $this->copied++; + $touched = true; + } + + if ($touched === true) { + $this->users++; + } + }//end migrateUser() +}//end class diff --git a/lib/Repair/RenameDutchCatalogColumns.php b/lib/Repair/RenameDutchCatalogColumns.php index 42cd013b..b3d78e92 100644 --- a/lib/Repair/RenameDutchCatalogColumns.php +++ b/lib/Repair/RenameDutchCatalogColumns.php @@ -1,7 +1,7 @@ * @copyright 2026 Conduction B.V. @@ -103,7 +103,7 @@ declare(strict_types=1); -namespace OCA\SoftwareCatalog\Repair; +namespace OCA\Stackiq\Repair; use OCP\DB\Exception; use OCP\IDBConnection; @@ -122,7 +122,7 @@ class RenameDutchCatalogColumns implements IRepairStep { * * @var string */ - private const REGISTER_SLUG = 'softwarecatalog'; + private const REGISTER_SLUG = 'stackiq'; /** * Schema slugs holding externally-standardised field names, which are @@ -434,7 +434,7 @@ private function migrateTable(string $table, array $declared): array { * Read from `oc_openregister_schemas.properties`, which is the shape * MagicMapper materialises columns from — verified first-hand against a live * instance rather than inferred: the column is `json`, `jsonb_typeof` is - * `object` for all 21 softwarecatalog schemas, and it is keyed by the + * `object` for all 21 stackiq schemas, and it is keyed by the * camelCase property name (`properties::jsonb ? 'name'` is true on exactly * the schemas the register JSON declares `naam` on). * diff --git a/lib/Repair/RenameDutchCatalogDecisions.php b/lib/Repair/RenameDutchCatalogDecisions.php index 5ad22487..fe8cba10 100644 --- a/lib/Repair/RenameDutchCatalogDecisions.php +++ b/lib/Repair/RenameDutchCatalogDecisions.php @@ -12,7 +12,7 @@ * was already sitting on before the candidate-target support was added. * * @category Repair - * @package OCA\\SoftwareCatalog\\Repair + * @package OCA\\Stackiq\\Repair * @author Conduction B.V. * @copyright 2026 Conduction B.V. * @license EUPL-1.2 https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12 @@ -24,13 +24,13 @@ declare(strict_types=1); -namespace OCA\SoftwareCatalog\Repair; +namespace OCA\Stackiq\Repair; /** * Pure predicates for the Dutch-to-English column migration. * * @category Repair - * @package OCA\\SoftwareCatalog\\Repair + * @package OCA\\Stackiq\\Repair * @author Conduction B.V. * @copyright 2026 Conduction B.V. * @license EUPL-1.2 https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12 @@ -70,7 +70,7 @@ public function sanitizeColumnName(string $name): string { /** * Whether moving `$old` to `$new` is safe for a schema declaring `$declared`. * - * This is the softwarecatalog#492 guard, and it is deliberately BOTH halves: + * This is the stackiq#492 guard, and it is deliberately BOTH halves: * * - the destination MUST be declared — otherwise the data lands in a * column nothing reads, and MagicMapper will re-add the Dutch one empty; @@ -139,7 +139,7 @@ public function firstSafeTarget(string $old, array $candidates, array $declared) * issuing an UPDATE against a missing column is an error rather than a no-op. * * @param array> $valueMap Property => old => new. - * @param array $columns Columns the table has. + * @param array $columns Columns the table has. * * @return array */ diff --git a/lib/Repair/RenameDutchCatalogValues.php b/lib/Repair/RenameDutchCatalogValues.php index 03e41d44..3e25caae 100644 --- a/lib/Repair/RenameDutchCatalogValues.php +++ b/lib/Repair/RenameDutchCatalogValues.php @@ -18,7 +18,7 @@ * Idempotent: an already-migrated row simply matches no WHERE clause. * * @category Repair - * @package OCA\SoftwareCatalog\Repair + * @package OCA\Stackiq\Repair * @author Conduction B.V. * @copyright 2026 Conduction B.V. * @license EUPL-1.2 https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12 @@ -30,7 +30,7 @@ declare(strict_types=1); -namespace OCA\SoftwareCatalog\Repair; +namespace OCA\Stackiq\Repair; use OCP\DB\Exception; use OCP\IDBConnection; @@ -138,9 +138,9 @@ class RenameDutchCatalogValues implements IRepairStep { /** * Constructor. * - * @param IDBConnection $db Database connection. - * @param LoggerInterface $logger Logger. - * @param RenameDutchCatalogDecisions $decisions Column-name predicates. + * @param IDBConnection $db Database connection. + * @param LoggerInterface $logger Logger. + * @param RenameDutchCatalogDecisions $decisions Column-name predicates. */ public function __construct( private readonly IDBConnection $db, @@ -155,7 +155,7 @@ public function __construct( * @return string */ public function getName(): string { - return 'Translate stored Dutch SoftwareCatalog enum values'; + return 'Translate stored Dutch Stackiq enum values'; }//end getName() /** @@ -164,11 +164,13 @@ public function getName(): string { * @param IOutput $output Repair output. * * @return void + * + * @spec openspec/specs/english-vocabulary-migration/spec.md#requirement-the-migration-is-non-destructive-and-idempotent */ public function run(IOutput $output): void { $tables = $this->shardTables(); if ($tables === []) { - $output->info('RenameDutchCatalogValues: no SoftwareCatalog shard tables on this install; nothing to do.'); + $output->info('RenameDutchCatalogValues: no Stackiq shard tables on this install; nothing to do.'); return; } @@ -195,10 +197,10 @@ public function run(IOutput $output): void { /** * Rewrite one value in one column. * - * @param string $table Shard table. + * @param string $table Shard table. * @param string $column Column name. - * @param string $old Stored Dutch value. - * @param string $new English replacement. + * @param string $old Stored Dutch value. + * @param string $new English replacement. * * @return int Rows affected. */ diff --git a/lib/Repair/RenameDutchSchemaSlugDecisions.php b/lib/Repair/RenameDutchSchemaSlugDecisions.php index 9fadb66a..5e31f17a 100644 --- a/lib/Repair/RenameDutchSchemaSlugDecisions.php +++ b/lib/Repair/RenameDutchSchemaSlugDecisions.php @@ -13,7 +13,7 @@ * can be decided before touching it is decided here. * * @category Repair - * @package OCA\SoftwareCatalog\Repair + * @package OCA\Stackiq\Repair * @author Conduction B.V. * @copyright 2026 Conduction B.V. * @license EUPL-1.2 https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12 @@ -25,7 +25,7 @@ declare(strict_types=1); -namespace OCA\SoftwareCatalog\Repair; +namespace OCA\Stackiq\Repair; /** * Pure predicates for the Dutch-to-English schema slug migration. @@ -44,8 +44,8 @@ class RenameDutchSchemaSlugDecisions { * is visible to the collision check of a later one — otherwise two entries * targeting the same name would both look safe. * - * @param array $map Old slug => new slug. - * @param array $existing Slugs currently present. + * @param array $map Old slug => new slug. + * @param array $existing Slugs currently present. * * @return array{renames: array, refused: array} */ @@ -130,7 +130,7 @@ public function schemaIdsFrom(array $rows): array { * would make an empty schema look occupied and refuse a safe merge. * * @param string $tableName The candidate table name. - * @param int $schemaId The schema id. + * @param int $schemaId The schema id. * * @return bool True when the table belongs to that schema. */ diff --git a/lib/Repair/RenameDutchSchemaSlugs.php b/lib/Repair/RenameDutchSchemaSlugs.php index 3882b377..0182661b 100644 --- a/lib/Repair/RenameDutchSchemaSlugs.php +++ b/lib/Repair/RenameDutchSchemaSlugs.php @@ -28,7 +28,7 @@ * Dutch keys, so resolution is unaffected by this step. * * @category Repair - * @package OCA\SoftwareCatalog\Repair + * @package OCA\Stackiq\Repair * @author Conduction B.V. * @copyright 2026 Conduction B.V. * @license EUPL-1.2 https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12 @@ -40,7 +40,7 @@ declare(strict_types=1); -namespace OCA\SoftwareCatalog\Repair; +namespace OCA\Stackiq\Repair; use OCP\DB\Exception; use OCP\IDBConnection; @@ -101,8 +101,8 @@ class RenameDutchSchemaSlugs implements IRepairStep { /** * Constructor. * - * @param IDBConnection $db Database connection. - * @param LoggerInterface $logger Logger. + * @param IDBConnection $db Database connection. + * @param LoggerInterface $logger Logger. * @param RenameDutchSchemaSlugDecisions $decisions The pure predicates. */ public function __construct( @@ -118,7 +118,7 @@ public function __construct( * @return string */ public function getName(): string { - return 'Rename Dutch SoftwareCatalog schema slugs'; + return 'Rename Dutch Stackiq schema slugs'; }//end getName() /** @@ -127,11 +127,13 @@ public function getName(): string { * @param IOutput $output Repair output. * * @return void + * + * @spec openspec/specs/english-vocabulary-migration/spec.md#requirement-renaming-a-stored-property-ships-a-data-migration */ public function run(IOutput $output): void { $schemaIds = $this->inScopeSchemaIds(); if ($schemaIds === []) { - $output->info('RenameDutchSchemaSlugs: no SoftwareCatalog registers on this install; nothing to do.'); + $output->info('RenameDutchSchemaSlugs: no Stackiq registers on this install; nothing to do.'); return; } @@ -180,7 +182,7 @@ public function run(IOutput $output): void { * the fact. So it refuses, loudly, and leaves both schemas alone. * * @param array $schemaIds Schema ids in scope. - * @param IOutput $output Repair output. + * @param IOutput $output Repair output. * * @return void */ @@ -365,8 +367,8 @@ private function slugsOf(array $schemaIds): array { /** * Rename one slug, scoped to this app's schemas. * - * @param string $old Current slug. - * @param string $new Replacement slug. + * @param string $old Current slug. + * @param string $new Replacement slug. * @param array $schemaIds Schema ids in scope. * * @return bool True when the row was updated. diff --git a/lib/Sections/SoftwareCatalogAdmin.php b/lib/Sections/StackiqAdmin.php similarity index 77% rename from lib/Sections/SoftwareCatalogAdmin.php rename to lib/Sections/StackiqAdmin.php index 5e65a48f..def9678d 100644 --- a/lib/Sections/SoftwareCatalogAdmin.php +++ b/lib/Sections/StackiqAdmin.php @@ -1,24 +1,24 @@ * @copyright 2024 Conduction B.V. * @license EUPL-1.2 https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12 * @version GIT: - * @link https://codeberg.org/Conduction/SoftwareCatalog + * @link https://github.com/ConductionNL/stackiq */ -namespace OCA\SoftwareCatalog\Sections; +namespace OCA\Stackiq\Sections; use OCP\IL10N; use OCP\IURLGenerator; use OCP\Settings\IIconSection; -class SoftwareCatalogAdmin implements IIconSection { +class StackiqAdmin implements IIconSection { /** * The localization service. @@ -35,7 +35,7 @@ class SoftwareCatalogAdmin implements IIconSection { private IURLGenerator $urlGenerator; /** - * Constructor for SoftwareCatalogAdmin section. + * Constructor for StackiqAdmin section. * * @param IL10N $l10n The localization service * @param IURLGenerator $urlGenerator The URL generator service @@ -52,7 +52,7 @@ public function __construct(IL10N $l10n, IURLGenerator $urlGenerator) { */ public function getIcon(): string { // phpcs:ignore -- named parameters unsafe for Nextcloud core methods (param names vary by NC version) - return $this->urlGenerator->imagePath('softwarecatalog', 'app-dark.svg'); + return $this->urlGenerator->imagePath('stackiq', 'app-dark.svg'); }//end getIcon() /** @@ -61,7 +61,7 @@ public function getIcon(): string { * @return string The section ID */ public function getID(): string { - return 'softwarecatalog'; + return 'stackiq'; }//end getID() /** @@ -70,7 +70,7 @@ public function getID(): string { * @return string The translated section name */ public function getName(): string { - return $this->l10n->t('Software Catalog'); + return $this->l10n->t('Stackiq'); }//end getName() /** diff --git a/lib/Service/AanbodService.php b/lib/Service/AanbodService.php index 68bddf92..1842300b 100644 --- a/lib/Service/AanbodService.php +++ b/lib/Service/AanbodService.php @@ -1,26 +1,26 @@ * @copyright 2024 Conduction B.V. * @license EUPL-1.2 https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12 * @version GIT: - * @link https://codeberg.org/Conduction/SoftwareCatalog + * @link https://github.com/ConductionNL/stackiq * * @spec openspec/specs/method-decomposition/spec.md */ declare(strict_types=1); -namespace OCA\SoftwareCatalog\Service; +namespace OCA\Stackiq\Service; use Exception; use OCA\OpenRegister\Contract\ObjectServiceInterface; @@ -38,12 +38,12 @@ * afnemer (consumer) or aanbieder (provider), and for accepting or denying these offers. * * @category Service - * @package OCA\SoftwareCatalog\Service + * @package OCA\Stackiq\Service * @author Conduction b.v. * @copyright 2024 Conduction B.V. * @license EUPL-1.2 https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12 * @version GIT: - * @link https://codeberg.org/Conduction/SoftwareCatalog + * @link https://github.com/ConductionNL/stackiq * * @SuppressWarnings(PHPMD.ExcessiveClassComplexity) * @SuppressWarnings(PHPMD.CyclomaticComplexity) diff --git a/lib/Service/AangebodenGebruik/GebruikBulkHandler.php b/lib/Service/AangebodenGebruik/GebruikBulkHandler.php index 76dcf196..9661df92 100644 --- a/lib/Service/AangebodenGebruik/GebruikBulkHandler.php +++ b/lib/Service/AangebodenGebruik/GebruikBulkHandler.php @@ -1,17 +1,17 @@ * @copyright 2024 Conduction B.V. * @license EUPL-1.2 https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12 - * @link https://codeberg.org/Conduction/SoftwareCatalog + * @link https://github.com/ConductionNL/stackiq * * @spec openspec/changes/method-decomposition/tasks.md#task-7 * @@ -21,7 +21,7 @@ declare(strict_types=1); -namespace OCA\SoftwareCatalog\Service\AangebodenGebruik; +namespace OCA\Stackiq\Service\AangebodenGebruik; use OCA\OpenRegister\Contract\ObjectServiceInterface; use Psr\Log\LoggerInterface; diff --git a/lib/Service/AangebodenGebruikService.php b/lib/Service/AangebodenGebruikService.php index ac760da7..b3006f95 100644 --- a/lib/Service/AangebodenGebruikService.php +++ b/lib/Service/AangebodenGebruikService.php @@ -1,24 +1,24 @@ * @copyright 2024 Conduction B.V. * @license EUPL-1.2 https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12 * @version GIT: - * @link https://codeberg.org/Conduction/SoftwareCatalog + * @link https://github.com/ConductionNL/stackiq */ declare(strict_types=1); -namespace OCA\SoftwareCatalog\Service; +namespace OCA\Stackiq\Service; use Exception; use OCA\OpenRegister\Contract\ObjectServiceInterface; @@ -36,12 +36,12 @@ * (participants) array, and for updating the @self property of gebruiks objects. * * @category Service - * @package OCA\SoftwareCatalog\Service + * @package OCA\Stackiq\Service * @author Conduction b.v. * @copyright 2024 Conduction B.V. * @license EUPL-1.2 https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12 * @version GIT: - * @link https://codeberg.org/Conduction/SoftwareCatalog + * @link https://github.com/ConductionNL/stackiq * * @SuppressWarnings(PHPMD.ExcessiveClassLength) * @SuppressWarnings(PHPMD.ExcessiveClassComplexity) @@ -241,7 +241,7 @@ public function getGebruiksWhereAfnemer(array $options = []): array { // Build next/previous links. $nextLink = null; $prevLink = null; - $consumerPath = '/index.php/apps/softwarecatalog/api/aangeboden-gebruik/afnemer'; + $consumerPath = '/index.php/apps/stackiq/api/aangeboden-gebruik/afnemer'; if ($currentPage < $totalPages) { $nextPage = $currentPage + 1; $nextLink = "{$consumerPath}?_limit={$requestedLimit}&_source=database&page={$nextPage}"; diff --git a/lib/Service/ArchiMate/ArchiMateContext.php b/lib/Service/ArchiMate/ArchiMateContext.php index e31edf57..ea5b435b 100644 --- a/lib/Service/ArchiMate/ArchiMateContext.php +++ b/lib/Service/ArchiMate/ArchiMateContext.php @@ -1,17 +1,17 @@ * @copyright 2024 Conduction B.V. * @license EUPL-1.2 https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12 - * @link https://codeberg.org/Conduction/SoftwareCatalog + * @link https://github.com/ConductionNL/stackiq * * @spec openspec/changes/method-decomposition/tasks.md#task-4 * @@ -21,10 +21,10 @@ declare(strict_types=1); -namespace OCA\SoftwareCatalog\Service\ArchiMate; +namespace OCA\Stackiq\Service\ArchiMate; use OCA\OpenRegister\Contract\ObjectServiceInterface; -use OCA\SoftwareCatalog\Service\SettingsService; +use OCA\Stackiq\Service\SettingsService; use Psr\Log\LoggerInterface; /** @@ -41,7 +41,7 @@ class ArchiMateContext { * Constructor. * * @param ObjectServiceInterface $objectService The OpenRegister object service. - * @param SettingsService $settingsService The SoftwareCatalog settings service. + * @param SettingsService $settingsService The Stackiq settings service. * @param LoggerInterface $logger The application logger. * * @spec openspec/changes/method-decomposition/tasks.md#task-4 diff --git a/lib/Service/ArchiMateExportService.php b/lib/Service/ArchiMateExportService.php index 38d2be00..ac365400 100644 --- a/lib/Service/ArchiMateExportService.php +++ b/lib/Service/ArchiMateExportService.php @@ -4,7 +4,7 @@ * ArchiMate Export Service. * * @category Service - * @package OCA\SoftwareCatalog\Service + * @package OCA\Stackiq\Service * @author Conduction b.v. * @copyright 2024 Conduction B.V. * @license EUPL-1.2 https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12 @@ -14,19 +14,19 @@ */ /** - * ArchiMate Export Service for the SoftwareCatalog app + * ArchiMate Export Service for the Stackiq app * * @category Service - * @package OCA\SoftwareCatalog\Service + * @package OCA\Stackiq\Service * @author Conduction b.v. * @copyright 2024 Conduction B.V. * @license EUPL-1.2 https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12 - * @link https://codeberg.org/Conduction/SoftwareCatalog + * @link https://github.com/ConductionNL/stackiq */ declare(strict_types=1); -namespace OCA\SoftwareCatalog\Service; +namespace OCA\Stackiq\Service; use Psr\Log\LoggerInterface; @@ -2878,7 +2878,7 @@ private function getViewName(array $viewData): string { }//end getViewName() /** - * Add Bron=Softwarecatalogus property to an XML data array. + * Add Bron=Stackiq property to an XML data array. * * @param array $data The data array. * @param string $sourcePropDefId The Bron property definition ID. @@ -2888,7 +2888,7 @@ private function getViewName(array $viewData): string { private function addSourceProperty(array $data, string $sourcePropDefId): array { $sourceProp = [ '_propertyDefinitionRef' => $sourcePropDefId, - 'value' => ['_value' => 'Softwarecatalogus'], + 'value' => ['_value' => 'Stackiq'], ]; if (isset($data['properties']) === false) { @@ -3071,7 +3071,7 @@ private function buildSwcOrganizationFolders( } $folders[] = [ - 'label' => ['_value' => 'Gebruikt (Softwarecatalogus)'], + 'label' => ['_value' => 'Gebruikt (Stackiq)'], 'items' => $items, ]; } @@ -3083,7 +3083,7 @@ private function buildSwcOrganizationFolders( } $folders[] = [ - 'label' => ['_value' => 'Deelnames (Softwarecatalogus)'], + 'label' => ['_value' => 'Deelnames (Stackiq)'], 'items' => $items, ]; } @@ -3096,7 +3096,7 @@ private function buildSwcOrganizationFolders( } $folders[] = [ - 'label' => ['_value' => 'Relaties (Softwarecatalogus)'], + 'label' => ['_value' => 'Relaties (Stackiq)'], 'items' => $relItems, ]; } @@ -3108,7 +3108,7 @@ private function buildSwcOrganizationFolders( } $folders[] = [ - 'label' => ['_value' => 'Views (Softwarecatalogus)'], + 'label' => ['_value' => 'Views (Stackiq)'], 'items' => $viewItems, ]; } @@ -3146,7 +3146,7 @@ private function assembleOrganizationXml( $xml = $this->createCleanArchiMateXml(modelMetadata: $modelMetadata); // Override model name. - $modelName = 'Softwarecatalogus ' . $orgName; + $modelName = 'Stackiq ' . $orgName; // Remove existing name children and add new one. foreach ($xml->children() as $child) { if ($child->getName() === 'name') { @@ -3203,7 +3203,7 @@ private function assembleOrganizationXml( $propsEl = $elNode->addChild('properties'); $propEl = $propsEl->addChild('property'); $propEl->addAttribute('propertyDefinitionRef', $appEl['bronPropDefId']); - $propEl->addChild('value', 'Softwarecatalogus'); + $propEl->addChild('value', 'Stackiq'); } // --- Relationships section ---. @@ -3235,7 +3235,7 @@ private function assembleOrganizationXml( $propsEl = $relNode->addChild('properties'); $propEl = $propsEl->addChild('property'); $propEl->addAttribute('propertyDefinitionRef', $rel['bronPropDefId']); - $propEl->addChild('value', 'Softwarecatalogus'); + $propEl->addChild('value', 'Stackiq'); } // --- Property Definitions section ---. diff --git a/lib/Service/ArchiMateImportService.php b/lib/Service/ArchiMateImportService.php index d5e41407..a1a4a157 100644 --- a/lib/Service/ArchiMateImportService.php +++ b/lib/Service/ArchiMateImportService.php @@ -1,24 +1,24 @@ + * @package OCA\Stackiq\Service + * @author Stackiq Team * @copyright 2024 Conduction B.V. * @license EUPL-1.2 https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12 - * @link https://github.com/nextcloud/softwarecatalog + * @link https://github.com/nextcloud/stackiq * * @spec openspec/specs/method-decomposition/spec.md */ declare(strict_types=1); -namespace OCA\SoftwareCatalog\Service; +namespace OCA\Stackiq\Service; use OCA\OpenRegister\Contract\ObjectServiceInterface; use OCA\OpenRegister\Service\ObjectService; @@ -43,11 +43,11 @@ * 5. Save objects using ObjectService::saveObjects * * @category Service - * @package OCA\SoftwareCatalog\Service - * @author SoftwareCatalog Team + * @package OCA\Stackiq\Service + * @author Stackiq Team * @copyright 2024 Conduction B.V. * @license EUPL-1.2 https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12 - * @link https://github.com/nextcloud/softwarecatalog + * @link https://github.com/nextcloud/stackiq * * @SuppressWarnings(PHPMD.ExcessiveClassLength) * @SuppressWarnings(PHPMD.ExcessiveClassComplexity) @@ -1960,7 +1960,7 @@ private function getCurrentOrganisation(): string { * @spec openspec/specs/archimate-import/spec.md */ private function resolveConfiguredId(string $key): ?string { - $value = $this->config->getValueString('softwarecatalog', $key, ''); + $value = $this->config->getValueString('stackiq', $key, ''); if (trim($value) === '') { $this->logger->warning( 'ArchiMate configuration is incomplete — this id is not configured, so it is omitted rather than passed on as an empty string', @@ -1984,7 +1984,7 @@ public function getAmefConfig(): array { try { // Get configuration from app config using the correct method. - $config = $this->config->getValueString('softwarecatalog', 'amef_config', '{}'); + $config = $this->config->getValueString('stackiq', 'amef_config', '{}'); $decoded = json_decode($config, true); if (is_array($decoded) === false) { @@ -2041,9 +2041,9 @@ private function getAmefRegisterId(): ?int { // Fallback to legacy individual app config keys if not present in JSON. if ($rawRegisterId === null || $rawRegisterId === '') { - $rawRegisterId = $this->config->getValueString('softwarecatalog', 'amef_register_id', ''); - if ($this->config->getValueString('softwarecatalog', 'amef_register', '') !== '') { - $rawRegisterId = $this->config->getValueString('softwarecatalog', 'amef_register', ''); + $rawRegisterId = $this->config->getValueString('stackiq', 'amef_register_id', ''); + if ($this->config->getValueString('stackiq', 'amef_register', '') !== '') { + $rawRegisterId = $this->config->getValueString('stackiq', 'amef_register', ''); } } @@ -2113,9 +2113,9 @@ private function getAmefSchemaIdForType(string $archiMateType): ?int { // Fallback to legacy individual app config keys if not present in JSON. foreach ($candidates as $key) { - $raw = $this->config->getValueString('softwarecatalog', $key, ''); - if ($this->config->getValueString('softwarecatalog', 'amef_' . $key, '') !== '') { - $raw = $this->config->getValueString('softwarecatalog', 'amef_' . $key, ''); + $raw = $this->config->getValueString('stackiq', $key, ''); + if ($this->config->getValueString('stackiq', 'amef_' . $key, '') !== '') { + $raw = $this->config->getValueString('stackiq', 'amef_' . $key, ''); } if ($raw !== '' && is_numeric((string)$raw) === true) { diff --git a/lib/Service/ArchiMateService.php b/lib/Service/ArchiMateService.php index d9376a5d..e08c4617 100644 --- a/lib/Service/ArchiMateService.php +++ b/lib/Service/ArchiMateService.php @@ -1,25 +1,25 @@ + * @package OCA\Stackiq\Service + * @author Stackiq Team * @copyright 2024 Conduction B.V. * @license EUPL-1.2 https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12 - * @link https://github.com/nextcloud/softwarecatalog + * @link https://github.com/nextcloud/stackiq * * @spec openspec/specs/method-decomposition/spec.md */ declare(strict_types=1); -namespace OCA\SoftwareCatalog\Service; +namespace OCA\Stackiq\Service; use OCA\OpenRegister\Contract\ObjectServiceInterface; use OCA\OpenRegister\Service\ObjectService; @@ -39,11 +39,11 @@ * 3. Export: Reconstruct exact XML from stored JSON blobs * * @category Service - * @package OCA\SoftwareCatalog\Service - * @author SoftwareCatalog Team + * @package OCA\Stackiq\Service + * @author Stackiq Team * @copyright 2024 Conduction B.V. * @license EUPL-1.2 https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12 - * @link https://github.com/nextcloud/softwarecatalog + * @link https://github.com/nextcloud/stackiq * * @SuppressWarnings(PHPMD.ExcessiveClassLength) * @SuppressWarnings(PHPMD.ExcessiveClassComplexity) @@ -475,8 +475,8 @@ public function exportOrgArchiMate(string $organizationUuid, array $options = [] $options ); - // Generate file name: DD-MM-YYYY_Softwarecatalogus_AMEFF_export_OrgName.xml. - $fileName = date('d-m-Y') . '_Softwarecatalogus_AMEFF_export_' . str_replace(' ', '_', $orgName) . '.xml'; + // Generate file name: DD-MM-YYYY_Stackiq_AMEFF_export_OrgName.xml. + $fileName = date('d-m-Y') . '_Stackiq_AMEFF_export_' . str_replace(' ', '_', $orgName) . '.xml'; return [ 'success' => true, @@ -1392,7 +1392,7 @@ private function validateRequiredConfiguration(): void { $errorMessage .= "- {$item}\n"; } - $settingsHint = 'in the SoftwareCatalog settings before importing.'; + $settingsHint = 'in the Stackiq settings before importing.'; $errorMessage .= "\nPlease configure the AMEF register and all required schema IDs $settingsHint"; $manualHint = 'or set them manually via the admin interface.'; $errorMessage .= "\nYou can use the auto-configuration feature $manualHint"; @@ -1615,7 +1615,7 @@ private function createTempFile(string $content): string { * @spec openspec/specs/archimate-import/spec.md */ private function resolveConfiguredId(string $key): ?string { - $value = $this->config->getValueString('softwarecatalog', $key, ''); + $value = $this->config->getValueString('stackiq', $key, ''); if (trim($value) === '') { $this->logger->warning( 'ArchiMate configuration is incomplete — this id is not configured, so it is omitted rather than passed on as an empty string', @@ -1639,7 +1639,7 @@ public function getAmefConfig(): array { try { // Get configuration from app config using the correct method. - $config = $this->config->getValueString('softwarecatalog', 'amef_config', '{}'); + $config = $this->config->getValueString('stackiq', 'amef_config', '{}'); $decoded = json_decode($config, true); if (is_array($decoded) === false) { @@ -1757,10 +1757,10 @@ private function getAmefRegisterId(): ?int { // Fallback to legacy individual app config keys if not present in JSON. if ($rawRegisterId === null || $rawRegisterId === '') { - $rawRegisterId = $this->config->getValueString('softwarecatalog', 'amef_register_id', ''); - if ($this->config->getValueString('softwarecatalog', 'amef_register', '') !== '') { + $rawRegisterId = $this->config->getValueString('stackiq', 'amef_register_id', ''); + if ($this->config->getValueString('stackiq', 'amef_register', '') !== '') { // If only the plain register key is configured, use it as the ID. - $rawRegisterId = $this->config->getValueString('softwarecatalog', 'amef_register', ''); + $rawRegisterId = $this->config->getValueString('stackiq', 'amef_register', ''); } } diff --git a/lib/Service/Contactpersoon/ContactValidator.php b/lib/Service/Contactpersoon/ContactValidator.php index fe909c68..eb8e7e66 100644 --- a/lib/Service/Contactpersoon/ContactValidator.php +++ b/lib/Service/Contactpersoon/ContactValidator.php @@ -1,17 +1,17 @@ * @copyright 2024 Conduction B.V. * @license EUPL-1.2 https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12 - * @link https://codeberg.org/Conduction/SoftwareCatalog + * @link https://github.com/ConductionNL/stackiq * * @spec openspec/changes/method-decomposition/tasks.md#task-7 * @@ -21,7 +21,7 @@ declare(strict_types=1); -namespace OCA\SoftwareCatalog\Service\Contactpersoon; +namespace OCA\Stackiq\Service\Contactpersoon; use InvalidArgumentException; diff --git a/lib/Service/ContactpersoonService.php b/lib/Service/ContactpersoonService.php index 44b1f9ba..6df8a9a2 100644 --- a/lib/Service/ContactpersoonService.php +++ b/lib/Service/ContactpersoonService.php @@ -4,26 +4,26 @@ * Contactpersoon Service * * This file contains the service class for handling contact person-specific operations - * in the SoftwareCatalog application. + * in the Stackiq application. * * @category Service - * @package OCA\SoftwareCatalog\Service + * @package OCA\Stackiq\Service * @author Conduction b.v. * @copyright 2024 Conduction B.V. * @license EUPL-1.2 https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12 * @version GIT: - * @link https://codeberg.org/Conduction/SoftwareCatalog + * @link https://github.com/ConductionNL/stackiq * * @spec openspec/specs/method-decomposition/spec.md */ declare(strict_types=1); -namespace OCA\SoftwareCatalog\Service; +namespace OCA\Stackiq\Service; -use OCA\SoftwareCatalog\Service\SoftwareCatalogue\ContactPersonHandler; -use OCA\SoftwareCatalog\Service\SoftwareCatalogue\GroupHandler; -use OCA\SoftwareCatalog\Service\SoftwareCatalogue\HierarchyHandler; +use OCA\Stackiq\Service\Stackiq\ContactPersonHandler; +use OCA\Stackiq\Service\Stackiq\GroupHandler; +use OCA\Stackiq\Service\Stackiq\HierarchyHandler; use OCP\App\IAppManager; use OCP\AppFramework\Db\DoesNotExistException; use OCP\IAppConfig; @@ -37,11 +37,11 @@ * user account creation, and group management. * * @category Service - * @package OCA\SoftwareCatalog\Service + * @package OCA\Stackiq\Service * @author Conduction b.v. * @license EUPL-1.2 https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12 * @version GIT: - * @link https://codeberg.org/Conduction/SoftwareCatalog + * @link https://github.com/ConductionNL/stackiq * * @SuppressWarnings(PHPMD.ExcessiveClassLength) * @SuppressWarnings(PHPMD.ExcessiveClassComplexity) @@ -173,7 +173,7 @@ public function processContactpersoon(object $contactPersonObject, bool $isUpdat ); try { $objectService = $this->container->get('OCA\OpenRegister\Service\ObjectService'); - $settingsService = $this->container->get('OCA\SoftwareCatalog\Service\SettingsService'); + $settingsService = $this->container->get('OCA\Stackiq\Service\SettingsService'); $voorzieningenConfig = $settingsService->getVoorzieningenConfig(); $orgObject = $objectService->find( id: $organizationUuid, @@ -186,7 +186,7 @@ public function processContactpersoon(object $contactPersonObject, bool $isUpdat $orgData = $orgObject->getObject(); $orgStatus = strtolower(($orgData['status'] ?? '')); if (in_array(needle: $orgStatus, haystack: ['actief', 'active']) === true) { - $syncServiceClass = 'OCA\SoftwareCatalog\Service\OrganizationSyncService'; + $syncServiceClass = 'OCA\Stackiq\Service\OrganizationSyncService'; $organizationSyncService = $this->container->get($syncServiceClass); $backupStats = [ 'entitiesCreated' => 0, diff --git a/lib/Service/ContractApprovalService.php b/lib/Service/ContractApprovalService.php index 231240df..8738993f 100644 --- a/lib/Service/ContractApprovalService.php +++ b/lib/Service/ContractApprovalService.php @@ -8,7 +8,7 @@ * expiring/`Verlopen` contract) to decidesk — the canonical fleet decision * authority (cross-app interface contract #1) — through the in-process * `IEventDispatcher` event contract (`OCA\Decidesk\Event\DecisionRequestedEvent` - * / `DecisionConcludedEvent`). softwarecatalog keeps the contract RECORD locally + * / `DecisionConcludedEvent`). stackiq keeps the contract RECORD locally * and PROJECTS the decidesk outcome onto two catalog-local fields * (`approvalDecisionId`, `approvalState`). * @@ -21,11 +21,11 @@ * never set to `Actief` on local authority. * * @category Service - * @package OCA\SoftwareCatalog\Service + * @package OCA\Stackiq\Service * @author Conduction b.v. * @copyright 2026 Conduction B.V. * @license EUPL-1.2 https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12 - * @link https://codeberg.org/Conduction/SoftwareCatalog + * @link https://github.com/ConductionNL/stackiq * * @spec openspec/specs/contract-decision-delegation/spec.md * @@ -35,7 +35,7 @@ declare(strict_types=1); -namespace OCA\SoftwareCatalog\Service; +namespace OCA\Stackiq\Service; use OCA\OpenRegister\Contract\ObjectServiceInterface; use OCP\EventDispatcher\IEventDispatcher; @@ -65,8 +65,21 @@ class ContractApprovalService { /** * This consumer app id, stamped on the request event as `sourceApp` and * used by the conclusion listener to filter inbound events. + * + * FROZEN at `stackiq` through the app-id rename. This value is not + * a name we own at read time — it is PERSISTED on decidesk's Decision rows + * when the approval is raised, and `DecisionConcludedListener` matches + * inbound conclusions against it. Every decision already open was stamped + * `stackiq`; moving the constant makes the filter miss them, so + * their outcomes are dropped on the floor and the contract never leaves + * `In onderhandeling`. Nothing errors — a filtered-out event looks exactly + * like an event that was never sent. + * + * It can only move in a coordinated change that also rewrites the stored + * `sourceApp` on decidesk's existing rows, or that accepts both spellings + * on the read side first. */ - public const SOURCE_APP = 'softwarecatalog'; + public const SOURCE_APP = 'stackiq'; /** * The decisionType raised for a first activation of an `In onderhandeling` diff --git a/lib/Service/ContractStatusService.php b/lib/Service/ContractStatusService.php index 3e1f67d4..0e511ad7 100644 --- a/lib/Service/ContractStatusService.php +++ b/lib/Service/ContractStatusService.php @@ -10,11 +10,11 @@ * without an end date, or any manually-set status in another direction. * * @category Service - * @package OCA\SoftwareCatalog\Service + * @package OCA\Stackiq\Service * @author Conduction b.v. * @copyright 2026 Conduction B.V. * @license EUPL-1.2 https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12 - * @link https://codeberg.org/Conduction/SoftwareCatalog + * @link https://github.com/ConductionNL/stackiq * * @spec openspec/specs/contract-administration/spec.md * @@ -24,7 +24,7 @@ declare(strict_types=1); -namespace OCA\SoftwareCatalog\Service; +namespace OCA\Stackiq\Service; use DateTimeImmutable; use OCA\OpenRegister\Contract\ObjectServiceInterface; diff --git a/lib/Service/EolMatcherService.php b/lib/Service/EolMatcherService.php index c55b78fe..112b4128 100644 --- a/lib/Service/EolMatcherService.php +++ b/lib/Service/EolMatcherService.php @@ -15,11 +15,11 @@ * nulled), plus the two provenance fields. * * @category Service - * @package OCA\SoftwareCatalog\Service + * @package OCA\Stackiq\Service * @author Conduction b.v. * @copyright 2026 Conduction B.V. * @license EUPL-1.2 https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12 - * @link https://codeberg.org/Conduction/SoftwareCatalog + * @link https://github.com/ConductionNL/stackiq * * @spec openspec/specs/eol-feed-integration/spec.md#requirement-version-matching-is-conservative-and-unambiguous-only * @spec openspec/specs/eol-feed-integration/spec.md#requirement-stamping-preserves-every-other-field-and-records-provenance @@ -30,7 +30,7 @@ declare(strict_types=1); -namespace OCA\SoftwareCatalog\Service; +namespace OCA\Stackiq\Service; /** * Conservative version-prefix matcher between `moduleVersie.versie` and diff --git a/lib/Service/EolSyncService.php b/lib/Service/EolSyncService.php index 2d21cb63..ae919b91 100644 --- a/lib/Service/EolSyncService.php +++ b/lib/Service/EolSyncService.php @@ -15,11 +15,11 @@ * "sync now" admin endpoint, so the two trigger paths can never drift. * * @category Service - * @package OCA\SoftwareCatalog\Service + * @package OCA\Stackiq\Service * @author Conduction b.v. * @copyright 2026 Conduction B.V. * @license EUPL-1.2 https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12 - * @link https://codeberg.org/Conduction/SoftwareCatalog + * @link https://github.com/ConductionNL/stackiq * * @spec openspec/specs/eol-feed-integration/spec.md#requirement-eol-sync-runs-on-a-schedule-with-a-manual-trigger * @spec openspec/specs/eol-feed-integration/spec.md#requirement-the-feature-degrades-gracefully-when-the-feed-is-unavailable @@ -31,7 +31,7 @@ declare(strict_types=1); -namespace OCA\SoftwareCatalog\Service; +namespace OCA\Stackiq\Service; use OCA\OpenRegister\Contract\ObjectServiceInterface; use OCP\AppFramework\Utility\ITimeFactory; @@ -352,7 +352,7 @@ private function fetchCycles(ObjectServiceInterface $objectService, array $confi * Fetch the `moduleVersie` rows belonging to one module. * * @param ObjectServiceInterface $objectService The OpenRegister object service. - * @param int $moduleRegisterId The (softwarecatalog) module register id. + * @param int $moduleRegisterId The (stackiq) module register id. * @param int $versionSchemaId The moduleVersie schema id. * @param string $moduleUuid The owning module's uuid. * diff --git a/lib/Service/FacetService.php b/lib/Service/FacetService.php index a3748455..48bb6b1e 100644 --- a/lib/Service/FacetService.php +++ b/lib/Service/FacetService.php @@ -1,7 +1,7 @@ * @copyright 2026 Conduction B.V. * @license EUPL-1.2 https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12 - * @link https://codeberg.org/Conduction/SoftwareCatalog + * @link https://github.com/ConductionNL/stackiq * * SPDX-FileCopyrightText: 2026 Conduction B.V. * SPDX-License-Identifier: EUPL-1.2 @@ -23,7 +23,7 @@ declare(strict_types=1); -namespace OCA\SoftwareCatalog\Service; +namespace OCA\Stackiq\Service; use InvalidArgumentException; use OCA\OpenRegister\Contract\ObjectServiceInterface; @@ -46,7 +46,7 @@ * by its own selection") facet counts over that map. * * @category Service - * @package OCA\SoftwareCatalog\Service + * @package OCA\Stackiq\Service * * @spec openspec/specs/gemma-faceted-search/spec.md#requirement-facet-aggregation-endpoint-returns-gemma-dimension-counts * @@ -130,7 +130,7 @@ public function __construct( private readonly LoggerInterface $logger, ICacheFactory $cacheFactory, ) { - $this->facetsCache = $cacheFactory->createDistributed(prefix: 'softwarecatalog_facets'); + $this->facetsCache = $cacheFactory->createDistributed(prefix: 'stackiq_facets'); }//end __construct() diff --git a/lib/Service/Federation/FederationConfig.php b/lib/Service/Federation/FederationConfig.php index 6125fbc5..fc31c52d 100644 --- a/lib/Service/Federation/FederationConfig.php +++ b/lib/Service/Federation/FederationConfig.php @@ -3,17 +3,17 @@ /** * Federation configuration value object. * - * Reads the softwarecatalog federation app-config keys via IAppConfig and + * Reads the stackiq federation app-config keys via IAppConfig and * exposes them as a small immutable value object so callers never touch raw * config keys. Defaults match the spec (directory.opencatalogi.nl, federation * disabled, no peers, hourly sync). * * @category Service - * @package OCA\SoftwareCatalog\Service\Federation + * @package OCA\Stackiq\Service\Federation * @author Conduction b.v. * @copyright 2026 Conduction B.V. * @license EUPL-1.2 https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12 - * @link https://codeberg.org/Conduction/SoftwareCatalog + * @link https://github.com/ConductionNL/stackiq * * @spec openspec/specs/federated-catalog-sync/spec.md * @@ -23,9 +23,9 @@ declare(strict_types=1); -namespace OCA\SoftwareCatalog\Service\Federation; +namespace OCA\Stackiq\Service\Federation; -use OCA\SoftwareCatalog\AppInfo\Application; +use OCA\Stackiq\AppInfo\Application; use OCP\IAppConfig; /** diff --git a/lib/Service/Federation/FederationMerger.php b/lib/Service/Federation/FederationMerger.php index 8850261d..470fc7b5 100644 --- a/lib/Service/Federation/FederationMerger.php +++ b/lib/Service/Federation/FederationMerger.php @@ -16,11 +16,11 @@ * therefore treated as read-only by every local write path (isPeerSourced()). * * @category Service - * @package OCA\SoftwareCatalog\Service\Federation + * @package OCA\Stackiq\Service\Federation * @author Conduction b.v. * @copyright 2026 Conduction B.V. * @license EUPL-1.2 https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12 - * @link https://codeberg.org/Conduction/SoftwareCatalog + * @link https://github.com/ConductionNL/stackiq * * @spec openspec/specs/federated-catalog-sync/spec.md * @@ -30,7 +30,7 @@ declare(strict_types=1); -namespace OCA\SoftwareCatalog\Service\Federation; +namespace OCA\Stackiq\Service\Federation; /** * Reconciles a peer's published entries with the locally-stored peer mirrors. diff --git a/lib/Service/Federation/FederationService.php b/lib/Service/Federation/FederationService.php index c63acc95..2bd60c10 100644 --- a/lib/Service/Federation/FederationService.php +++ b/lib/Service/Federation/FederationService.php @@ -5,17 +5,17 @@ * * Cross-instance catalog federation by DELEGATING to OpenCatalogi's proven * federation stack (DirectoryService / BroadcastService) — never a bespoke - * wire protocol (design constraint). softwarecatalog contributes only its + * wire protocol (design constraint). stackiq contributes only its * schema mapping, the merge/provenance semantics, the sync schedule, and the * admin controls. When OpenCatalogi is not installed, every entry point * degrades to a clean disabled state with a clear message — it never errors. * * @category Service - * @package OCA\SoftwareCatalog\Service\Federation + * @package OCA\Stackiq\Service\Federation * @author Conduction b.v. * @copyright 2026 Conduction B.V. * @license EUPL-1.2 https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12 - * @link https://codeberg.org/Conduction/SoftwareCatalog + * @link https://github.com/ConductionNL/stackiq * * @spec openspec/specs/federated-catalog-sync/spec.md * @@ -25,9 +25,9 @@ declare(strict_types=1); -namespace OCA\SoftwareCatalog\Service\Federation; +namespace OCA\Stackiq\Service\Federation; -use OCA\SoftwareCatalog\Service\SettingsService; +use OCA\Stackiq\Service\SettingsService; use OCP\App\IAppManager; use Psr\Container\ContainerInterface; use Psr\Log\LoggerInterface; @@ -384,7 +384,7 @@ public function pullPeer(string $peerUrl): array { /** * Fetch a peer's published catalog entries via OpenCatalogi's DirectoryService. * - * Delegates the wire to OpenCatalogi — softwarecatalog never speaks a bespoke + * Delegates the wire to OpenCatalogi — stackiq never speaks a bespoke * federation protocol. Only published entries are returned (the peer's own * `publicatiedatum<=$now` public RBAC gate governs that surface). * diff --git a/lib/Service/GebruikService.php b/lib/Service/GebruikService.php index 24cb777b..ba7aeb4c 100644 --- a/lib/Service/GebruikService.php +++ b/lib/Service/GebruikService.php @@ -6,15 +6,15 @@ * Service for retrieving and managing Gebruik (usage) objects. * * @category Service - * @package OCA\SoftwareCatalog\Service + * @package OCA\Stackiq\Service * @author Conduction b.v. * @copyright 2024 Conduction B.V. * @license EUPL-1.2 https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12 * @version GIT: - * @link https://codeberg.org/Conduction/SoftwareCatalog + * @link https://github.com/ConductionNL/stackiq */ -namespace OCA\SoftwareCatalog\Service; +namespace OCA\Stackiq\Service; use Exception; use OCA\OpenRegister\Contract\ObjectServiceInterface; diff --git a/lib/Service/GebruikSyncService.php b/lib/Service/GebruikSyncService.php index f581203f..c0931b96 100644 --- a/lib/Service/GebruikSyncService.php +++ b/lib/Service/GebruikSyncService.php @@ -10,7 +10,7 @@ * - Auto-updating status based on date fields * * @category Service - * @package OCA\SoftwareCatalog\Service + * @package OCA\Stackiq\Service * @author Ruben van der Linde * @copyright 2024 Conduction B.V. * @license EUPL-1.2 https://joinup.ec.europa.eu/collection/eupl @@ -22,19 +22,19 @@ declare(strict_types=1); -namespace OCA\SoftwareCatalog\Service; +namespace OCA\Stackiq\Service; use DateTime; use Exception; +use OCA\OpenRegister\Contract\ObjectServiceInterface; use OCA\OpenRegister\Db\ObjectEntity; use Psr\Log\LoggerInterface; -use OCA\OpenRegister\Contract\ObjectServiceInterface; /** * Service for synchronizing and processing Gebruik (Usage) objects. * * @category Service - * @package OCA\SoftwareCatalog\Service + * @package OCA\Stackiq\Service * @author Ruben van der Linde * @license EUPL-1.2 https://joinup.ec.europa.eu/collection/eupl * @version GIT: @@ -113,7 +113,7 @@ public function processSpecificGebruik(ObjectEntity $gebruikObject): array { $this->logger->debug( 'Processing gebruik object', [ - 'app' => 'softwarecatalog', + 'app' => 'stackiq', 'gebruikId' => $gebruikUuid, 'currentStatus' => $gebruikData['status'] ?? 'Unknown', ] @@ -135,7 +135,7 @@ public function processSpecificGebruik(ObjectEntity $gebruikObject): array { $this->logger->critical( 'GEBRUIK PROCESSING COMPLETED', [ - 'app' => 'softwarecatalog', + 'app' => 'stackiq', 'gebruikId' => $gebruikUuid, 'stats' => $stats, 'processingTime' => $stats['duration'] . 's', @@ -150,7 +150,7 @@ public function processSpecificGebruik(ObjectEntity $gebruikObject): array { $this->logger->error( 'GEBRUIK PROCESSING ERROR', [ - 'app' => 'softwarecatalog', + 'app' => 'stackiq', 'gebruikId' => $gebruikObject->getUuid(), 'exception' => $e->getMessage(), 'file' => $e->getFile(), @@ -199,7 +199,7 @@ private function processAmefElements(ObjectEntity $gebruikObject): array { $this->logger->debug( 'Processing referentiecomponenten', [ - 'app' => 'softwarecatalog', + 'app' => 'stackiq', 'gebruikId' => $gebruikUuid, 'referentieComponentenCount' => count($referenceComponents), ] @@ -233,7 +233,7 @@ private function processAmefElements(ObjectEntity $gebruikObject): array { $this->logger->error( 'AMEF configuration missing', [ - 'app' => 'softwarecatalog', + 'app' => 'stackiq', 'amefRegister' => $amefRegister, 'elementSchema' => $elementSchema, ] @@ -278,7 +278,7 @@ private function processAmefElements(ObjectEntity $gebruikObject): array { $this->logger->critical( 'AMEF ELEMENTS UPDATED', [ - 'app' => 'softwarecatalog', + 'app' => 'stackiq', 'gebruikId' => $gebruikUuid, 'amefSlugs' => $amefSlugs, 'amefElementsCount' => count($amefSlugs), @@ -292,7 +292,7 @@ private function processAmefElements(ObjectEntity $gebruikObject): array { $this->logger->error( 'AMEF PROCESSING ERROR', [ - 'app' => 'softwarecatalog', + 'app' => 'stackiq', 'gebruikId' => $gebruikObject->getUuid(), 'exception' => $e->getMessage(), ] @@ -337,7 +337,7 @@ private function searchAmefElementsByIds(array $ids, string $register, string $s $this->logger->warning( 'Failed to search for AMEF element', [ - 'app' => 'softwarecatalog', + 'app' => 'stackiq', 'id' => $id, 'error' => $e->getMessage(), ] @@ -348,7 +348,7 @@ private function searchAmefElementsByIds(array $ids, string $register, string $s $this->logger->info( 'AMEF elements search completed', [ - 'app' => 'softwarecatalog', + 'app' => 'stackiq', 'searchedIds' => $ids, 'foundElementsCount' => count($foundElements), ] @@ -382,7 +382,7 @@ private function updateStatusBasedOnDates(ObjectEntity $gebruikObject): array { $this->logger->info( 'CHECKING STATUS DATES', [ - 'app' => 'softwarecatalog', + 'app' => 'stackiq', 'gebruikId' => $gebruikUuid, 'currentStatus' => $currentStatus, 'statusDates' => $statusDates, @@ -402,7 +402,7 @@ private function updateStatusBasedOnDates(ObjectEntity $gebruikObject): array { $this->logger->critical( 'STATUS AUTO-UPDATED', [ - 'app' => 'softwarecatalog', + 'app' => 'stackiq', 'gebruikId' => $gebruikUuid, 'oldStatus' => $currentStatus, 'newStatus' => $targetStatus, @@ -417,7 +417,7 @@ private function updateStatusBasedOnDates(ObjectEntity $gebruikObject): array { $this->logger->error( 'STATUS UPDATE ERROR', [ - 'app' => 'softwarecatalog', + 'app' => 'stackiq', 'gebruikId' => $gebruikObject->getUuid(), 'exception' => $e->getMessage(), ] @@ -471,7 +471,7 @@ private function resolveLatestEligibleStatus(array $statusDates, string $gebruik $this->logger->warning( 'Invalid date format', [ - 'app' => 'softwarecatalog', + 'app' => 'stackiq', 'gebruikId' => $gebruikUuid, 'status' => $status, 'dateString' => $dateString, @@ -526,7 +526,7 @@ private function updateGebruikObject(ObjectEntity $gebruikObject, array $updated $this->logger->info( 'Gebruik object updated successfully', [ - 'app' => 'softwarecatalog', + 'app' => 'stackiq', 'gebruikId' => $gebruikObject->getUuid(), ] ); @@ -534,7 +534,7 @@ private function updateGebruikObject(ObjectEntity $gebruikObject, array $updated $this->logger->error( 'Failed to update gebruik object', [ - 'app' => 'softwarecatalog', + 'app' => 'stackiq', 'gebruikId' => $gebruikObject->getUuid(), 'error' => $e->getMessage(), ] diff --git a/lib/Service/IntakeService.php b/lib/Service/IntakeService.php index b0792ee8..83adf120 100644 --- a/lib/Service/IntakeService.php +++ b/lib/Service/IntakeService.php @@ -17,11 +17,11 @@ * admin-gated step (ModerationService). * * @category Service - * @package OCA\SoftwareCatalog\Service + * @package OCA\Stackiq\Service * @author Conduction b.v. * @copyright 2026 Conduction B.V. * @license EUPL-1.2 https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12 - * @link https://codeberg.org/Conduction/SoftwareCatalog + * @link https://github.com/ConductionNL/stackiq * * @spec openspec/specs/open-data-publishing/spec.md * @@ -31,7 +31,7 @@ declare(strict_types=1); -namespace OCA\SoftwareCatalog\Service; +namespace OCA\Stackiq\Service; use Psr\Container\ContainerInterface; use Psr\Log\LoggerInterface; @@ -270,7 +270,7 @@ private function resolveTarget(): ?array { * FALSE, and because an object is not an array the array arm below cannot * rescue it — so this method used to return `null` for EVERY real save, * putting `uuid: null` in the submit response and the audit log - * (softwarecatalog#490). `property_exists()` is the instrument + * (stackiq#490). `property_exists()` is the instrument * `Entity::getter()` itself decides on; `method_exists()` is kept as the * second arm for genuinely concrete accessors, and the call is wrapped * because neither probe guarantees the other object's shape. diff --git a/lib/Service/MergeOrganisatieService.php b/lib/Service/MergeOrganisatieService.php index b9200008..9bf9ad06 100644 --- a/lib/Service/MergeOrganisatieService.php +++ b/lib/Service/MergeOrganisatieService.php @@ -46,11 +46,11 @@ * - compliancy: `@self.organisation` (system-level owning organisation). * * @category Service - * @package OCA\SoftwareCatalog\Service + * @package OCA\Stackiq\Service * @author Conduction b.v. * @copyright 2026 Conduction B.V. * @license EUPL-1.2 https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12 - * @link https://codeberg.org/Conduction/SoftwareCatalog + * @link https://github.com/ConductionNL/stackiq * * @spec openspec/specs/organisation-merge/spec.md * @@ -60,10 +60,10 @@ declare(strict_types=1); -namespace OCA\SoftwareCatalog\Service; +namespace OCA\Stackiq\Service; use OCA\OpenRegister\Contract\ObjectServiceInterface; -use OCA\SoftwareCatalog\Service\SoftwareCatalogue\OrganizationHandler; +use OCA\Stackiq\Service\Stackiq\OrganizationHandler; use OCP\App\IAppManager; use OCP\EventDispatcher\IEventDispatcher; use OCP\IGroupManager; @@ -75,10 +75,10 @@ * Service orchestrating organisation-merge dry-run and execute. * * @category Service - * @package OCA\SoftwareCatalog\Service + * @package OCA\Stackiq\Service * @author Conduction b.v. * @license EUPL-1.2 https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12 - * @link https://codeberg.org/Conduction/SoftwareCatalog + * @link https://github.com/ConductionNL/stackiq * * @spec openspec/specs/organisation-merge/spec.md * @@ -472,7 +472,7 @@ private function repointBySelfOrganisation(string $objectType, string $source, s * wrong here, and both fail silently: * * - `method_exists()` is **false** for every such accessor. That was - * softwarecatalog#490: the caller's re-point branch never ran, so a merge + * stackiq#490: the caller's re-point branch never ran, so a merge * re-pointed nothing for `contract`/`compliancy` while still tombstoning * the source organisation. * - `is_callable()` is **true** for ANY name on a class with `__call()`, so diff --git a/lib/Service/ModerationService.php b/lib/Service/ModerationService.php index 48565f88..62dfac8a 100644 --- a/lib/Service/ModerationService.php +++ b/lib/Service/ModerationService.php @@ -5,7 +5,7 @@ * * The admin-only counterpart to IntakeService (organisatie) and * ReviewService (beoordeeling) — a single generalised mechanism, not two - * parallel ones (catalog-ratings, softwarecatalog#375: "reuse the + * parallel ones (catalog-ratings, stackiq#375: "reuse the * ModerationQueue.vue pattern... do not invent a second moderation * mechanism"). Every method takes an explicit `$type` selecting which * moderated type/field/values to operate on; it defaults to the original @@ -28,11 +28,11 @@ * ModerationController. * * @category Service - * @package OCA\SoftwareCatalog\Service + * @package OCA\Stackiq\Service * @author Conduction b.v. * @copyright 2026 Conduction B.V. * @license EUPL-1.2 https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12 - * @link https://codeberg.org/Conduction/SoftwareCatalog + * @link https://github.com/ConductionNL/stackiq * * @spec openspec/specs/open-data-publishing/spec.md * @spec openspec/specs/catalog-ratings/spec.md#requirement-review-moderation-must-reuse-the-existing-moderation-queue-mechanism-not-a-second-one @@ -43,7 +43,7 @@ declare(strict_types=1); -namespace OCA\SoftwareCatalog\Service; +namespace OCA\Stackiq\Service; use Psr\Container\ContainerInterface; use Psr\Log\LoggerInterface; diff --git a/lib/Service/ModuleComplianceService.php b/lib/Service/ModuleComplianceService.php index dfb181c8..5b18c436 100644 --- a/lib/Service/ModuleComplianceService.php +++ b/lib/Service/ModuleComplianceService.php @@ -4,22 +4,22 @@ * Module Compliance Service * * This file contains the service class for handling module compliance logic - * in the SoftwareCatalog application. + * in the Stackiq application. * * @category Service - * @package OCA\SoftwareCatalog\Service + * @package OCA\Stackiq\Service * @author Conduction b.v. * @copyright 2024 Conduction B.V. * @license EUPL-1.2 https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12 * @version GIT: - * @link https://codeberg.org/Conduction/SoftwareCatalog + * @link https://github.com/ConductionNL/stackiq * * @spec openspec/specs/method-decomposition/spec.md */ declare(strict_types=1); -namespace OCA\SoftwareCatalog\Service; +namespace OCA\Stackiq\Service; use OCA\OpenRegister\Contract\ObjectServiceInterface; use Psr\Container\ContainerInterface; @@ -32,11 +32,11 @@ * property based on linked compliance objects and their standaardversie references. * * @category Service - * @package OCA\SoftwareCatalog\Service + * @package OCA\Stackiq\Service * @author Conduction b.v. * @license EUPL-1.2 https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12 * @version GIT: - * @link https://codeberg.org/Conduction/SoftwareCatalog + * @link https://github.com/ConductionNL/stackiq * * @SuppressWarnings(PHPMD.ExcessiveClassComplexity) * @SuppressWarnings(PHPMD.CyclomaticComplexity) diff --git a/lib/Service/ModuleEventProcessor.php b/lib/Service/ModuleEventProcessor.php index dc943e4d..e90af878 100644 --- a/lib/Service/ModuleEventProcessor.php +++ b/lib/Service/ModuleEventProcessor.php @@ -1,17 +1,17 @@ * @copyright 2024 Conduction B.V. * @license EUPL-1.2 https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12 - * @link https://codeberg.org/Conduction/SoftwareCatalog + * @link https://github.com/ConductionNL/stackiq * * @spec openspec/changes/method-decomposition/tasks.md#task-6 * @@ -21,13 +21,13 @@ declare(strict_types=1); -namespace OCA\SoftwareCatalog\Service; +namespace OCA\Stackiq\Service; use Psr\Container\ContainerInterface; use Psr\Log\LoggerInterface; /** - * Handles shared module-event processing logic extracted from SoftwareCatalogEventListener. + * Handles shared module-event processing logic extracted from StackiqEventListener. * * This processor centralises the schema-ID lookup, early-guard, and delegation * steps that were duplicated across handleObjectCreated / handleObjectUpdated / @@ -93,18 +93,18 @@ public function processOrganisatieCreated(object $object, array $schemaIds): voi if (in_array(needle: $status, haystack: ['actief', 'active']) !== true) { $this->logger->debug( - 'SoftwareCatalog: Skipping non-active organization creation', + 'Stackiq: Skipping non-active organization creation', ['objectId' => $object->getUuid(), 'status' => $status] ); return; } try { - $orgSyncService = $this->container->get('OCA\SoftwareCatalog\Service\OrganizationSyncService'); + $orgSyncService = $this->container->get('OCA\Stackiq\Service\OrganizationSyncService'); $orgSyncService->processSpecificOrganization($object); } catch (\Exception $e) { $this->logger->error( - 'SoftwareCatalog: Failed to process organization creation', + 'Stackiq: Failed to process organization creation', ['objectId' => $object->getUuid(), 'exception' => $e->getMessage()] ); } @@ -164,16 +164,16 @@ public function processOrganisatieDeleted(object $object, array $schemaIds): boo } $this->logger->info( - 'SoftwareCatalog: Processing organization deletion', + 'Stackiq: Processing organization deletion', ['objectId' => $object->getUuid()] ); try { - $orgSyncService = $this->container->get('OCA\SoftwareCatalog\Service\OrganizationSyncService'); + $orgSyncService = $this->container->get('OCA\Stackiq\Service\OrganizationSyncService'); $orgSyncService->processSpecificOrganization($object); } catch (\Exception $e) { $this->logger->error( - 'SoftwareCatalog: Failed to process organization deletion', + 'Stackiq: Failed to process organization deletion', ['objectId' => $object->getUuid(), 'exception' => $e->getMessage()] ); } @@ -193,7 +193,7 @@ private function processActiveOrganisationUpdate(object $object, string $status) $objectId = $object->getUuid(); $this->logger->info( - 'SoftwareCatalog: Processing active organization update', + 'Stackiq: Processing active organization update', ['objectId' => $objectId, 'status' => $status] ); @@ -212,11 +212,11 @@ private function processActiveOrganisationUpdate(object $object, string $status) _multitenancy: false ); - $orgSyncService = $this->container->get('OCA\SoftwareCatalog\Service\OrganizationSyncService'); + $orgSyncService = $this->container->get('OCA\Stackiq\Service\OrganizationSyncService'); $orgSyncService->processSpecificOrganization($orgWithContacts); } catch (\Exception $e) { $this->logger->error( - 'SoftwareCatalog: Failed to process organization update', + 'Stackiq: Failed to process organization update', ['objectId' => $objectId, 'exception' => $e->getMessage()] ); }//end try diff --git a/lib/Service/ModuleRegistrationService.php b/lib/Service/ModuleRegistrationService.php index 43606e83..5fede71c 100644 --- a/lib/Service/ModuleRegistrationService.php +++ b/lib/Service/ModuleRegistrationService.php @@ -7,19 +7,19 @@ * based on the owning organisation's type. * * @category Service - * @package OCA\SoftwareCatalog\Service + * @package OCA\Stackiq\Service * @author Conduction b.v. * @copyright 2024 Conduction B.V. * @license EUPL-1.2 https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12 * @version GIT: - * @link https://codeberg.org/Conduction/SoftwareCatalog + * @link https://github.com/ConductionNL/stackiq * * @spec openspec/specs/method-decomposition/spec.md */ declare(strict_types=1); -namespace OCA\SoftwareCatalog\Service; +namespace OCA\Stackiq\Service; use OCA\OpenRegister\Contract\ObjectServiceInterface; use Psr\Container\ContainerInterface; @@ -30,7 +30,7 @@ * based on the owning organisation's type. * * @category Service - * @package OCA\SoftwareCatalog\Service + * @package OCA\Stackiq\Service */ class ModuleRegistrationService { /** diff --git a/lib/Service/ModuleVersionService.php b/lib/Service/ModuleVersionService.php index 4efb3d02..d00ed17c 100644 --- a/lib/Service/ModuleVersionService.php +++ b/lib/Service/ModuleVersionService.php @@ -8,19 +8,19 @@ * the module's name and description. * * @category Service - * @package OCA\SoftwareCatalog\Service + * @package OCA\Stackiq\Service * @author Conduction b.v. * @copyright 2024 Conduction B.V. * @license EUPL-1.2 https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12 * @version GIT: 1.0.0 - * @link https://codeberg.org/Conduction/SoftwareCatalog + * @link https://github.com/ConductionNL/stackiq * * @spec openspec/specs/method-decomposition/spec.md */ declare(strict_types=1); -namespace OCA\SoftwareCatalog\Service; +namespace OCA\Stackiq\Service; use OCA\OpenRegister\Contract\ObjectServiceInterface; use Psr\Container\ContainerInterface; @@ -35,7 +35,7 @@ * (`fetchVersionData()`, `compareVersions()`, `updateVersionRecord()`). * * @category Service - * @package OCA\SoftwareCatalog\Service + * @package OCA\Stackiq\Service * * @spec openspec/changes/method-decomposition/tasks.md#task-9-5 */ diff --git a/lib/Service/OrganisatieService.php b/lib/Service/OrganisatieService.php index 9a1029ac..7c77a9a2 100644 --- a/lib/Service/OrganisatieService.php +++ b/lib/Service/OrganisatieService.php @@ -4,22 +4,22 @@ * Organisatie Service. * * This file contains the service class for handling organization-specific operations - * in the SoftwareCatalog application. + * in the Stackiq application. * * @category Service - * @package OCA\SoftwareCatalog\Service + * @package OCA\Stackiq\Service * @author Conduction b.v. * @copyright 2024 Conduction B.V. * @license EUPL-1.2 https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12 * @version GIT: - * @link https://codeberg.org/Conduction/SoftwareCatalog + * @link https://github.com/ConductionNL/stackiq */ declare(strict_types=1); -namespace OCA\SoftwareCatalog\Service; +namespace OCA\Stackiq\Service; -use OCA\SoftwareCatalog\Service\SoftwareCatalogue\OrganizationHandler; +use OCA\Stackiq\Service\Stackiq\OrganizationHandler; use OCP\App\IAppManager; use OCP\IAppConfig; use OCP\IUserManager; @@ -33,11 +33,11 @@ * status management, and integration with OpenRegister. * * @category Service - * @package OCA\SoftwareCatalog\Service + * @package OCA\Stackiq\Service * @author Conduction b.v. * @license EUPL-1.2 https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12 * @version GIT: - * @link https://codeberg.org/Conduction/SoftwareCatalog + * @link https://github.com/ConductionNL/stackiq * * @SuppressWarnings(PHPMD.UnusedPrivateMethod) */ @@ -153,7 +153,7 @@ public function updateOrganizationStatus(string $organizationUuid, array $object $organisationMapper = $this->container->get('OCA\OpenRegister\Db\OrganisationMapper'); $organisationEntity = $organisationMapper->findByUuid($organizationUuid); - // Map status from SoftwareCatalog to OpenRegister. + // Map status from Stackiq to OpenRegister. $active = $this->mapStatus(status: $objectData['beoordeling'] ?? 'actief'); // Update the entity. @@ -200,7 +200,7 @@ private function getOrganisationService(): ?\OCA\OpenRegister\Service\Organisati }//end getOrganisationService() /** - * Maps organization data from Software Catalog object to OpenRegister format. + * Maps organization data from Stackiq object to OpenRegister format. * * @param array $objectData The organization object data. * @@ -229,9 +229,9 @@ private function mapOrganizationDataForOpenRegister(array $objectData): array { }//end mapOrganizationDataForOpenRegister() /** - * Maps status from Software Catalog to OpenRegister format. + * Maps status from Stackiq to OpenRegister format. * - * @param string $status The status from Software Catalog + * @param string $status The status from Stackiq * * @return bool The mapped active status for OpenRegister * diff --git a/lib/Service/OrganizationSyncService.php b/lib/Service/OrganizationSyncService.php index d598cf2b..8ae3062e 100644 --- a/lib/Service/OrganizationSyncService.php +++ b/lib/Service/OrganizationSyncService.php @@ -4,41 +4,41 @@ * Organization Synchronization Service * * This file contains the service class for synchronizing organizations and contact persons - * between SoftwareCatalog objects and OpenRegister entities. + * between Stackiq objects and OpenRegister entities. * * @category Service - * @package OCA\SoftwareCatalog\Service + * @package OCA\Stackiq\Service * @author Conduction b.v. * @copyright 2024 Conduction B.V. * @license EUPL-1.2 https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12 - * @link https://codeberg.org/Conduction/SoftwareCatalog + * @link https://github.com/ConductionNL/stackiq * * @spec openspec/specs/method-decomposition/spec.md */ declare(strict_types=1); -namespace OCA\SoftwareCatalog\Service; +namespace OCA\Stackiq\Service; use OCA\OpenRegister\Contract\ObjectServiceInterface; -use OCA\SoftwareCatalog\Service\SoftwareCatalogue\ContactPersonHandler; +use OCA\OpenRegister\Db\OrganisationMapper; +use OCA\Stackiq\Service\Stackiq\ContactPersonHandler; use OCP\IAppConfig; use OCP\IDBConnection; use Psr\Container\ContainerInterface; use Psr\Log\LoggerInterface; -use OCA\OpenRegister\Db\OrganisationMapper; /** * Service for synchronizing organizations and contact persons. * - * This service provides comprehensive synchronization between SoftwareCatalog objects + * This service provides comprehensive synchronization between Stackiq objects * and OpenRegister entities, ensuring data consistency and proper user management. * * @category Service - * @package OCA\SoftwareCatalog\Service + * @package OCA\Stackiq\Service * @author Conduction b.v. * @license EUPL-1.2 https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12 - * @link https://codeberg.org/Conduction/SoftwareCatalog + * @link https://github.com/ConductionNL/stackiq * * @SuppressWarnings(PHPMD.ExcessiveClassLength) * @SuppressWarnings(PHPMD.ExcessiveClassComplexity) @@ -995,7 +995,7 @@ private function ensureOrganisationEntity(object $organisationObject, array &$st $this->logger->debug( 'Ensuring organisation entity', [ - 'app' => 'softwarecatalog', + 'app' => 'stackiq', 'organisatieId' => $organisationId, 'name' => ($objectData['name'] ?? 'Unknown'), 'status' => ($objectData['status'] ?? 'Unknown'), @@ -1019,7 +1019,7 @@ private function ensureOrganisationEntity(object $organisationObject, array &$st $this->logger->debug( 'Existing entity found', [ - 'app' => 'softwarecatalog', + 'app' => 'stackiq', 'organisatieId' => $organisationId, 'entityId' => $organisationEntity->getId(), 'shouldBeActive' => $shouldBeActive, @@ -1031,7 +1031,7 @@ private function ensureOrganisationEntity(object $organisationObject, array &$st $this->logger->debug( 'Updating entity status', [ - 'app' => 'softwarecatalog', + 'app' => 'stackiq', 'organisatieId' => $organisationId, 'oldActive' => $organisationEntity->getActive(), 'newActive' => $shouldBeActive, @@ -1087,7 +1087,7 @@ private function ensureOrganisationEntity(object $organisationObject, array &$st $this->logger->info( 'OrganizationSyncService: Found existing entity by slug, updating UUID to match object', [ - 'app' => 'softwarecatalog', + 'app' => 'stackiq', 'organisatieId' => $organisationId, 'oldEntityUuid' => $organisationEntity->getUuid(), 'slug' => $slug, @@ -1116,7 +1116,7 @@ private function ensureOrganisationEntity(object $organisationObject, array &$st $this->logger->debug( 'Creating new organisation entity', [ - 'app' => 'softwarecatalog', + 'app' => 'stackiq', 'organisatieId' => $organisationId, 'name' => ($objectData['name'] ?? 'Unknown'), ] @@ -1128,7 +1128,7 @@ private function ensureOrganisationEntity(object $organisationObject, array &$st $this->logger->debug( 'New organisation entity created', [ - 'app' => 'softwarecatalog', + 'app' => 'stackiq', 'organisatieId' => $organisationId, 'entityId' => $organisationEntity->getId(), 'active' => $organisationEntity->getActive(), @@ -1171,7 +1171,7 @@ private function ensureOrganisationEntity(object $organisationObject, array &$st $this->logger->error( '❌ ORGANISATION ENTITY CREATION FAILED', [ - 'app' => 'softwarecatalog', + 'app' => 'stackiq', 'organisatieId' => $organisationId, ] ); @@ -1183,7 +1183,7 @@ private function ensureOrganisationEntity(object $organisationObject, array &$st $this->logger->error( '💥 ENSURE ORGANISATION ENTITY EXCEPTION', [ - 'app' => 'softwarecatalog', + 'app' => 'stackiq', 'organisatieId' => $organisationObject->getId(), 'exception' => $e->getMessage(), 'file' => $e->getFile(), @@ -1606,7 +1606,7 @@ public function getSyncStatus(int $minutesBack = 10): array { // Configuration. 'contactSchemaConfigured' => empty($contactSchema) === false, - 'lastSyncTime' => $this->config->getValueString('softwarecatalog', 'last_sync_time', 'Never'), + 'lastSyncTime' => $this->config->getValueString('stackiq', 'last_sync_time', 'Never'), // Email configuration status. 'emailStatus' => $this->getEmailConfigurationStatus(), @@ -1671,7 +1671,7 @@ private function formatNumber(int $number): string { * @spec openspec/specs/organization-sync/spec.md */ public function recordSyncTime(): void { - $this->config->setValueString('softwarecatalog', 'last_sync_time', date('Y-m-d H:i:s')); + $this->config->setValueString('stackiq', 'last_sync_time', date('Y-m-d H:i:s')); }//end recordSyncTime() @@ -1705,7 +1705,7 @@ public function processSpecificOrganization($organizationObject): array { $this->logger->info( '🏢 ORGANIZATION PROCESSING STARTED', [ - 'app' => 'softwarecatalog', + 'app' => 'stackiq', 'trigger' => 'ObjectCreatedEvent', 'organizationId' => $organizationUuid, 'organizationName' => ($objectData['name'] ?? 'Unknown'), @@ -1732,7 +1732,7 @@ public function processSpecificOrganization($organizationObject): array { $this->logger->info( '✅ ORGANISATION ENTITY CREATED/UPDATED', [ - 'app' => 'softwarecatalog', + 'app' => 'stackiq', 'organizationUuid' => $organizationUuid, 'entityId' => $organisationEntity->getId(), 'entityActive' => $organisationEntity->getActive(), @@ -1772,7 +1772,7 @@ public function processSpecificOrganization($organizationObject): array { $this->logger->error( '❌ ORGANISATION ENTITY FAILED', [ - 'app' => 'softwarecatalog', + 'app' => 'stackiq', 'organizationUuid' => $organizationUuid, 'error' => 'Failed to create/update organisation entity', ] @@ -1786,7 +1786,7 @@ public function processSpecificOrganization($organizationObject): array { $this->logger->info( '🏁 ORGANIZATION PROCESSING COMPLETED', [ - 'app' => 'softwarecatalog', + 'app' => 'stackiq', 'organizationId' => $organizationUuid, 'stats' => $stats, 'processingTime' => $stats['duration'] . 's', @@ -1799,7 +1799,7 @@ public function processSpecificOrganization($organizationObject): array { $this->logger->error( '💥 ORGANIZATION PROCESSING EXCEPTION', [ - 'app' => 'softwarecatalog', + 'app' => 'stackiq', 'organizationId' => $organizationObject->getUuid(), 'exception' => $e->getMessage(), 'file' => $e->getFile(), @@ -1840,7 +1840,7 @@ private function processNestedContactPersons($organizationObject, array &$stats) $this->logger->info( '👥 PROCESSING NESTED CONTACT PERSONS', [ - 'app' => 'softwarecatalog', + 'app' => 'stackiq', 'organizationId' => $organizationUuid, 'contactCount' => count($contactPersons), ] @@ -1968,7 +1968,7 @@ private function processRelatedContactPersons(string $organizationUuid, $organiz $this->logger->debug( 'Finding related contact persons', [ - 'app' => 'softwarecatalog', + 'app' => 'stackiq', 'organizationId' => $organizationUuid, ] ); @@ -2121,7 +2121,7 @@ private function processRelatedContactPersons(string $organizationUuid, $organiz $this->logger->info( '👥 PROCESSING RELATED CONTACT PERSONS', [ - 'app' => 'softwarecatalog', + 'app' => 'stackiq', 'organizationId' => $organizationUuid, 'contactCount' => count($relatedContacts), ] @@ -2284,7 +2284,7 @@ private function createOrUpdateContactPersonObject( $this->logger->info( '📧 FETCHING EXISTING CONTACT PERSON', [ - 'app' => 'softwarecatalog', + 'app' => 'stackiq', 'organizationId' => $organizationUuid, 'contactId' => $existingContactId, 'email' => $email, @@ -2316,7 +2316,7 @@ private function createOrUpdateContactPersonObject( $this->logger->info( '📧 CREATING NEW CONTACT PERSON OBJECT', [ - 'app' => 'softwarecatalog', + 'app' => 'stackiq', 'organizationId' => $organizationUuid, 'email' => $email, 'name' => ($contactData['voornaam'] ?? '') . ' ' . ($contactData['achternaam'] ?? ''), @@ -2398,7 +2398,7 @@ private function createOrUpdateContactPersonObject( $this->logger->info( '✅ CONTACT PERSON OBJECT READY', [ - 'app' => 'softwarecatalog', + 'app' => 'stackiq', 'organizationId' => $organizationUuid, 'contactId' => $contactObject->getUuid(), 'email' => $email, @@ -2417,7 +2417,7 @@ private function createOrUpdateContactPersonObject( $this->logger->info( 'Organisation entity missing for ' . $organizationUuid . ', creating backup entity', [ - 'app' => 'softwarecatalog', + 'app' => 'stackiq', 'contactId' => $contactObject->getUuid(), ] ); @@ -2442,7 +2442,7 @@ private function createOrUpdateContactPersonObject( $this->logger->error( 'Backup org entity creation failed', [ - 'app' => 'softwarecatalog', + 'app' => 'stackiq', 'organizationId' => $organizationUuid, 'error' => $backupEx->getMessage(), ] @@ -2461,7 +2461,7 @@ private function createOrUpdateContactPersonObject( $this->logger->info( 'Creating user account for contact person (org is active)', [ - 'app' => 'softwarecatalog', + 'app' => 'stackiq', 'contactId' => $contactObject->getUuid(), 'organizationId' => $organizationUuid, 'email' => $email, @@ -2496,7 +2496,7 @@ private function createOrUpdateContactPersonObject( $this->logger->debug( 'Saving contact with username via direct mapper update', [ - 'app' => 'softwarecatalog', + 'app' => 'stackiq', 'contactId' => $contactObject->getUuid(), 'username' => $user->getUID(), 'hasOrganisatie' => isset($contactObjectData['organization']) === true, @@ -2519,7 +2519,7 @@ private function createOrUpdateContactPersonObject( $this->logger->info( 'Contact saved with username', [ - 'app' => 'softwarecatalog', + 'app' => 'stackiq', 'contactId' => $contactObject->getUuid(), 'username' => $user->getUID(), ] @@ -2528,7 +2528,7 @@ private function createOrUpdateContactPersonObject( $this->logger->warning( 'Failed to save username to contact object (user was created)', [ - 'app' => 'softwarecatalog', + 'app' => 'stackiq', 'contactId' => $contactObject->getUuid(), 'username' => $user->getUID(), 'error' => $saveEx->getMessage(), @@ -2555,7 +2555,7 @@ private function createOrUpdateContactPersonObject( $this->logger->info( 'User account created', [ - 'app' => 'softwarecatalog', + 'app' => 'stackiq', 'contactId' => $contactObject->getUuid(), 'username' => $user->getUID(), ] @@ -2566,7 +2566,7 @@ private function createOrUpdateContactPersonObject( $this->logger->error( 'User account creation failed', [ - 'app' => 'softwarecatalog', + 'app' => 'stackiq', 'contactId' => $contactObject->getUuid(), 'email' => $email, ] @@ -2684,7 +2684,7 @@ public function processSpecificContactPerson($contactObject): array { $this->logger->info( '[EVENT] Organisation entity missing for ' . $organizationUuid . ', creating backup entity', [ - 'app' => 'softwarecatalog', + 'app' => 'stackiq', 'contactId' => $contactObject->getUuid(), ] ); @@ -2709,7 +2709,7 @@ public function processSpecificContactPerson($contactObject): array { $this->logger->error( 'Backup org entity creation failed', [ - 'app' => 'softwarecatalog', + 'app' => 'stackiq', 'organizationId' => $organizationUuid, 'error' => $backupEx->getMessage(), ] @@ -2798,7 +2798,7 @@ public function processSpecificContactPerson($contactObject): array { $this->logger->debug( '[EVENT] Skipping contact - user account creation failed (likely no email)', [ - 'app' => 'softwarecatalog', + 'app' => 'stackiq', 'contactId' => $contactObject->getUuid(), ] ); diff --git a/lib/Service/PortfolioReportDerivation.php b/lib/Service/PortfolioReportDerivation.php index 00e7c1a5..324079fe 100644 --- a/lib/Service/PortfolioReportDerivation.php +++ b/lib/Service/PortfolioReportDerivation.php @@ -13,11 +13,11 @@ * (design.md Decision 3). * * @category Service - * @package OCA\SoftwareCatalog\Service + * @package OCA\Stackiq\Service * @author Conduction b.v. * @copyright 2026 Conduction B.V. * @license EUPL-1.2 https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12 - * @link https://codeberg.org/Conduction/SoftwareCatalog + * @link https://github.com/ConductionNL/stackiq * * @spec openspec/changes/portfolio-rationalization-time/specs/portfolio-rationalization-time/spec.md * @@ -27,7 +27,7 @@ declare(strict_types=1); -namespace OCA\SoftwareCatalog\Service; +namespace OCA\Stackiq\Service; use DateTimeImmutable; diff --git a/lib/Service/PortfolioReportService.php b/lib/Service/PortfolioReportService.php index 2972a813..21612b3a 100644 --- a/lib/Service/PortfolioReportService.php +++ b/lib/Service/PortfolioReportService.php @@ -16,7 +16,7 @@ * {@see PortfolioReportDerivation}. * * @category Service - * @package OCA\SoftwareCatalog\Service + * @package OCA\Stackiq\Service * @author Conduction b.v. * @copyright 2026 Conduction B.V. * @license EUPL-1.2 https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12 @@ -30,12 +30,12 @@ declare(strict_types=1); -namespace OCA\SoftwareCatalog\Service; +namespace OCA\Stackiq\Service; use DateTimeImmutable; use Exception; use OCA\OpenRegister\Contract\ObjectServiceInterface; -use OCA\SoftwareCatalog\AppInfo\Application; +use OCA\Stackiq\AppInfo\Application; use OCP\App\IAppManager; use OCP\IAppConfig; use Psr\Container\ContainerInterface; diff --git a/lib/Service/ProfileFieldMapper.php b/lib/Service/ProfileFieldMapper.php index 3e106f07..26928b40 100644 --- a/lib/Service/ProfileFieldMapper.php +++ b/lib/Service/ProfileFieldMapper.php @@ -1,17 +1,17 @@ * @copyright 2024 Conduction B.V. * @license EUPL-1.2 https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12 - * @link https://codeberg.org/Conduction/SoftwareCatalog + * @link https://github.com/ConductionNL/stackiq * * @spec openspec/changes/method-decomposition/tasks.md#task-8 * @@ -21,10 +21,10 @@ declare(strict_types=1); -namespace OCA\SoftwareCatalog\Service; +namespace OCA\Stackiq\Service; /** - * Maps Nextcloud user profile field keys to SoftwareCatalog contactpersoon field names. + * Maps Nextcloud user profile field keys to Stackiq contactpersoon field names. * * Used by UserProfileUpdatedEventListener to delegate field-name resolution * without bloating the event-handler method. diff --git a/lib/Service/ProgressTracker.php b/lib/Service/ProgressTracker.php index 066546d2..3e90f642 100644 --- a/lib/Service/ProgressTracker.php +++ b/lib/Service/ProgressTracker.php @@ -7,17 +7,17 @@ * Supports real-time streaming via Server-Sent Events. * * @category Service - * @package OCA\SoftwareCatalog\Service + * @package OCA\Stackiq\Service * @author Conduction b.v. * @copyright 2024 Conduction B.V. * @license EUPL-1.2 https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12 * @version GIT: 1.0.0 - * @link https://codeberg.org/Conduction/SoftwareCatalog + * @link https://github.com/ConductionNL/stackiq */ declare(strict_types=1); -namespace OCA\SoftwareCatalog\Service; +namespace OCA\Stackiq\Service; use OCP\ISession; use Psr\Log\LoggerInterface; @@ -26,12 +26,12 @@ * Service for tracking and reporting progress of long-running operations * * @category Service - * @package OCA\SoftwareCatalog\Service + * @package OCA\Stackiq\Service * @author Conduction b.v. * @copyright 2024 Conduction B.V. * @license EUPL-1.2 https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12 * @version GIT: 1.0.0 - * @link https://codeberg.org/Conduction/SoftwareCatalog + * @link https://github.com/ConductionNL/stackiq */ class ProgressTracker { /** diff --git a/lib/Service/PublicationService.php b/lib/Service/PublicationService.php index 73829b55..046a1384 100644 --- a/lib/Service/PublicationService.php +++ b/lib/Service/PublicationService.php @@ -18,11 +18,11 @@ * (public + federation) read surface. * * @category Service - * @package OCA\SoftwareCatalog\Service + * @package OCA\Stackiq\Service * @author Conduction b.v. * @copyright 2026 Conduction B.V. * @license EUPL-1.2 https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12 - * @link https://codeberg.org/Conduction/SoftwareCatalog + * @link https://github.com/ConductionNL/stackiq * * @spec openspec/specs/open-data-publishing/spec.md * @@ -32,7 +32,7 @@ declare(strict_types=1); -namespace OCA\SoftwareCatalog\Service; +namespace OCA\Stackiq\Service; use OCA\OpenRegister\Contract\ObjectServiceInterface; use Psr\Container\ContainerInterface; diff --git a/lib/Service/ReviewAggregateService.php b/lib/Service/ReviewAggregateService.php index 60ec30de..8e13a1d7 100644 --- a/lib/Service/ReviewAggregateService.php +++ b/lib/Service/ReviewAggregateService.php @@ -2,7 +2,7 @@ /** * Public approved-only review aggregate/read path (catalog-ratings, - * softwarecatalog#375) — split out of ReviewService (which owns the + * stackiq#375) — split out of ReviewService (which owns the * authenticated write path) to keep each class under the * ExcessiveClassComplexity budget. * @@ -15,11 +15,11 @@ * matching — computing it here is fully unit-testable regardless of that. * * @category Service - * @package OCA\SoftwareCatalog\Service + * @package OCA\Stackiq\Service * @author Conduction b.v. * @copyright 2026 Conduction B.V. * @license EUPL-1.2 https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12 - * @link https://codeberg.org/Conduction/SoftwareCatalog + * @link https://github.com/ConductionNL/stackiq * * @spec openspec/specs/catalog-ratings/spec.md#requirement-module-and-dienst-detail-pages-must-display-an-aggregate-rating-computed-only-from-approved-reviews * @@ -29,7 +29,7 @@ declare(strict_types=1); -namespace OCA\SoftwareCatalog\Service; +namespace OCA\Stackiq\Service; use Psr\Container\ContainerInterface; use Psr\Log\LoggerInterface; diff --git a/lib/Service/ReviewService.php b/lib/Service/ReviewService.php index d3183d46..3bc2e1c8 100644 --- a/lib/Service/ReviewService.php +++ b/lib/Service/ReviewService.php @@ -5,7 +5,7 @@ * ReviewAggregateService for the approved-only read/aggregate path; split * to keep each class under the ExcessiveClassComplexity budget). * - * The write path for the `catalog-ratings` feature (softwarecatalog#375). + * The write path for the `catalog-ratings` feature (stackiq#375). * Unlike `IntakeService` (anonymous organisation registration), a review * submission REQUIRES an authenticated Nextcloud session — anonymous public * review submission is explicitly out of scope. The author identity is never @@ -17,11 +17,11 @@ * organisatie moderation pattern) may change that. * * @category Service - * @package OCA\SoftwareCatalog\Service + * @package OCA\Stackiq\Service * @author Conduction b.v. * @copyright 2026 Conduction B.V. * @license EUPL-1.2 https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12 - * @link https://codeberg.org/Conduction/SoftwareCatalog + * @link https://github.com/ConductionNL/stackiq * * @spec openspec/specs/catalog-ratings/spec.md * @@ -31,7 +31,7 @@ declare(strict_types=1); -namespace OCA\SoftwareCatalog\Service; +namespace OCA\Stackiq\Service; use OCP\IUser; use OCP\IUserSession; @@ -359,7 +359,7 @@ private function resolveTarget(): ?array { * FALSE, and because an object is not an array the array arm below cannot * rescue it — so this method used to return `null` for EVERY real save, * putting `uuid: null` in the submit response and the audit log - * (softwarecatalog#490). `property_exists()` is the instrument + * (stackiq#490). `property_exists()` is the instrument * `Entity::getter()` itself decides on; `method_exists()` is kept as the * second arm for genuinely concrete accessors, and the call is wrapped * because neither probe guarantees the other object's shape. diff --git a/lib/Service/SbomImportService.php b/lib/Service/SbomImportService.php index 727f75be..3cdbbfe7 100644 --- a/lib/Service/SbomImportService.php +++ b/lib/Service/SbomImportService.php @@ -22,11 +22,11 @@ * set rather than a mixed old/new set; re-running the import starts clean. * * @category Service - * @package OCA\SoftwareCatalog\Service + * @package OCA\Stackiq\Service * @author Conduction b.v. * @copyright 2026 Conduction B.V. * @license EUPL-1.2 https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12 - * @link https://codeberg.org/Conduction/SoftwareCatalog + * @link https://github.com/ConductionNL/stackiq * * @spec openspec/specs/sbom-import/spec.md#requirement-re-import-replaces-the-previous-component-set-and-is-soft-delete-aware * @@ -36,7 +36,7 @@ declare(strict_types=1); -namespace OCA\SoftwareCatalog\Service; +namespace OCA\Stackiq\Service; use DateTime; use OCA\OpenRegister\Contract\ObjectServiceInterface; @@ -101,9 +101,9 @@ public function __construct( * * @return array Import result summary. * - * @throws \OCA\SoftwareCatalog\Exception\UnsupportedSbomFormatException When the - * document's format/version is not supported. - * No component is written in that case. + * @throws \OCA\Stackiq\Exception\UnsupportedSbomFormatException When the + * document's format/version is not supported. + * No component is written in that case. * @throws RuntimeException When the target `moduleVersie` cannot be * resolved, or required configuration is missing. * @@ -215,8 +215,8 @@ public function importForModuleVersie( * * @return array{components: array>, vulnerabilities: array} * - * @throws \OCA\SoftwareCatalog\Exception\UnsupportedSbomFormatException When - * the document's format/version is not supported. + * @throws \OCA\Stackiq\Exception\UnsupportedSbomFormatException When + * the document's format/version is not supported. * * @spec openspec/specs/sbom-import/spec.md#requirement-cyclonedx-sbom-files-are-parsed-into-a-normalized-component-list */ diff --git a/lib/Service/SbomParserService.php b/lib/Service/SbomParserService.php index 5b39319a..6dc19b69 100644 --- a/lib/Service/SbomParserService.php +++ b/lib/Service/SbomParserService.php @@ -20,11 +20,11 @@ * "Alternative considered"). * * @category Service - * @package OCA\SoftwareCatalog\Service + * @package OCA\Stackiq\Service * @author Conduction b.v. * @copyright 2026 Conduction B.V. * @license EUPL-1.2 https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12 - * @link https://codeberg.org/Conduction/SoftwareCatalog + * @link https://github.com/ConductionNL/stackiq * * @spec openspec/specs/sbom-import/spec.md#requirement-cyclonedx-sbom-files-are-parsed-into-a-normalized-component-list * @@ -34,9 +34,9 @@ declare(strict_types=1); -namespace OCA\SoftwareCatalog\Service; +namespace OCA\Stackiq\Service; -use OCA\SoftwareCatalog\Exception\UnsupportedSbomFormatException; +use OCA\Stackiq\Exception\UnsupportedSbomFormatException; /** * Pure CycloneDX 1.5/1.6 (+ optional SPDX 2.x) SBOM parser. diff --git a/lib/Service/Settings/ModuleSettingsHandler.php b/lib/Service/Settings/ModuleSettingsHandler.php index 336bd31a..cb6323ab 100644 --- a/lib/Service/Settings/ModuleSettingsHandler.php +++ b/lib/Service/Settings/ModuleSettingsHandler.php @@ -1,17 +1,17 @@ * @copyright 2024 Conduction B.V. * @license EUPL-1.2 https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12 - * @link https://codeberg.org/Conduction/SoftwareCatalog + * @link https://github.com/ConductionNL/stackiq * * @spec openspec/changes/method-decomposition/tasks.md#task-1 * @@ -21,7 +21,7 @@ declare(strict_types=1); -namespace OCA\SoftwareCatalog\Service\Settings; +namespace OCA\Stackiq\Service\Settings; use InvalidArgumentException; use OCP\IAppConfig; @@ -42,7 +42,7 @@ class ModuleSettingsHandler { * * @var string */ - private const APP_NAME = 'softwarecatalog'; + private const APP_NAME = 'stackiq'; /** * Constructor. diff --git a/lib/Service/Settings/OrganizationSettingsHandler.php b/lib/Service/Settings/OrganizationSettingsHandler.php index b36a0603..90520263 100644 --- a/lib/Service/Settings/OrganizationSettingsHandler.php +++ b/lib/Service/Settings/OrganizationSettingsHandler.php @@ -1,17 +1,17 @@ * @copyright 2024 Conduction B.V. * @license EUPL-1.2 https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12 - * @link https://codeberg.org/Conduction/SoftwareCatalog + * @link https://github.com/ConductionNL/stackiq * * @spec openspec/changes/method-decomposition/tasks.md#task-1 * @@ -21,7 +21,7 @@ declare(strict_types=1); -namespace OCA\SoftwareCatalog\Service\Settings; +namespace OCA\Stackiq\Service\Settings; use InvalidArgumentException; use OCP\IAppConfig; @@ -42,7 +42,7 @@ class OrganizationSettingsHandler { * * @var string */ - private const APP_NAME = 'softwarecatalog'; + private const APP_NAME = 'stackiq'; /** * Constructor. diff --git a/lib/Service/Settings/SyncSettingsHandler.php b/lib/Service/Settings/SyncSettingsHandler.php index 05943dc8..53c806ef 100644 --- a/lib/Service/Settings/SyncSettingsHandler.php +++ b/lib/Service/Settings/SyncSettingsHandler.php @@ -1,13 +1,13 @@ * @copyright 2024 Conduction B.V. * @license EUPL-1.2 https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12 @@ -21,7 +21,7 @@ declare(strict_types=1); -namespace OCA\SoftwareCatalog\Service\Settings; +namespace OCA\Stackiq\Service\Settings; use InvalidArgumentException; use OCP\IAppConfig; @@ -42,7 +42,7 @@ class SyncSettingsHandler { * * @var string */ - private const APP_NAME = 'softwarecatalog'; + private const APP_NAME = 'stackiq'; /** * Constructor. diff --git a/lib/Service/SettingsService.php b/lib/Service/SettingsService.php index d354a501..a3ea1b0e 100644 --- a/lib/Service/SettingsService.php +++ b/lib/Service/SettingsService.php @@ -1,10 +1,10 @@ * @copyright 2024 Conduction B.V. * @license EUPL-1.2 https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12 @@ -16,7 +16,7 @@ declare(strict_types=1); -namespace OCA\SoftwareCatalog\Service; +namespace OCA\Stackiq\Service; use OCP\App\IAppManager; use OCP\IAppConfig; @@ -31,13 +31,13 @@ use Symfony\Component\Mime\Email; /** - * Service for handling settings-related operations in the SoftwareCatalog. + * Service for handling settings-related operations in the Stackiq. * * Provides functionality for retrieving, saving, and loading settings, * as well as managing configuration for different object types. * * @category Service - * @package OCA\SoftwareCatalog\Service + * @package OCA\Stackiq\Service * @author Conduction b.v. * @copyright 2024 Conduction B.V. * @license EUPL-1.2 https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12 @@ -152,7 +152,7 @@ public function __construct( private readonly IGroupManager $groupManager, private readonly IL10N $l10n, ) { - $this->appName = 'softwarecatalog'; + $this->appName = 'stackiq'; }//end __construct() /** @@ -1449,7 +1449,7 @@ public function initialize(?string $minOpenRegisterVersion = self::MIN_OPENREGIS $this->logger->info('SettingsService: Loading settings from file'); $loadResult = $this->loadSettings(); $results['settingsLoaded'] = true; - $results['configurationImported'] = empty($loadResult['softwarecatalog_imported']) === false; + $results['configurationImported'] = empty($loadResult['stackiq_imported']) === false; $this->logger->info( 'SettingsService: Settings loaded successfully', [ @@ -1623,7 +1623,7 @@ public function loadSettings(bool $force = false): array { } }//end if - $results['softwarecatalog'] = $softwareCatalogSettings; + $results['stackiq'] = $softwareCatalogSettings; // Import via configuration service if available with version checking. try { @@ -1647,7 +1647,7 @@ public function loadSettings(bool $force = false): array { fragmentSig: $fragmentSig ); - $appId = \OCA\SoftwareCatalog\AppInfo\Application::APP_ID; + $appId = \OCA\Stackiq\AppInfo\Application::APP_ID; // Force-when-stale workaround (register-import-reliability, // https://github.com/ConductionNL/openregister/issues/2075): @@ -1724,7 +1724,7 @@ public function loadSettings(bool $force = false): array { ] ); - $results['softwarecatalog_imported'] = true; + $results['stackiq_imported'] = true; $results['import_result'] = $importResult; // Post-import verification (register-import-reliability): a version-gate @@ -1739,15 +1739,15 @@ public function loadSettings(bool $force = false): array { $results['registerVerification'] = $verification; $this->persistRegisterVerificationStatus(verification: $verification); } catch (\Exception $e) { - $results['softwarecatalog_import_error'] = $e->getMessage(); + $results['stackiq_import_error'] = $e->getMessage(); $this->logger->error( - 'Failed to import softwarecatalog settings: ' . $e->getMessage(), + 'Failed to import stackiq settings: ' . $e->getMessage(), [ 'exception' => $e, 'trace' => $e->getTraceAsString(), 'force_flag' => $force, 'effective_force' => $effectiveForce, - 'app_id' => \OCA\SoftwareCatalog\AppInfo\Application::APP_ID, + 'app_id' => \OCA\Stackiq\AppInfo\Application::APP_ID, ] ); @@ -2477,7 +2477,7 @@ public function redactEmailSecrets(array $settings): array { * @spec openspec/specs/settings-service/spec.md */ public function getEmailSettings(): array { - $this->logger->debug('SoftwareCatalog: Loading email settings from configuration'); + $this->logger->debug('Stackiq: Loading email settings from configuration'); $app = $this->appName; $settings = [ @@ -2620,7 +2620,7 @@ public function getEmailSettings(): array { ]; $this->logger->info( - 'SoftwareCatalog: Email settings loaded from configuration', + 'Stackiq: Email settings loaded from configuration', [ 'enabled' => $settings['enabled'], 'transport_type' => $settings['transportType'], @@ -2951,7 +2951,7 @@ public function getDebugInfo(): array { public function sendTestEmail(string $email, array $emailSettings = []): array { // Validate email address first (business logic moved from controller). if (empty($email) === true) { - $this->logger->warning('SoftwareCatalog: Test email request missing email address'); + $this->logger->warning('Stackiq: Test email request missing email address'); return [ 'success' => false, 'message' => 'Email address is required', @@ -2959,7 +2959,7 @@ public function sendTestEmail(string $email, array $emailSettings = []): array { } $this->logger->info( - 'SoftwareCatalog: Starting sendTestEmail process', + 'Stackiq: Starting sendTestEmail process', [ 'recipient' => $email, 'has_email_settings' => empty($emailSettings) === false, @@ -2969,19 +2969,19 @@ public function sendTestEmail(string $email, array $emailSettings = []): array { try { // Ensure vendor autoloader is loaded. include_once __DIR__ . '/../../vendor/autoload.php'; - $this->logger->debug('SoftwareCatalog: Vendor autoloader loaded'); + $this->logger->debug('Stackiq: Vendor autoloader loaded'); // Use provided settings or fall back to stored settings. if (empty($emailSettings) === true) { $emailSettings = $this->getEmailSettings(); - $this->logger->info('SoftwareCatalog: Loaded email settings from storage'); + $this->logger->info('Stackiq: Loaded email settings from storage'); } else { - $this->logger->info('SoftwareCatalog: Using provided email settings'); + $this->logger->info('Stackiq: Using provided email settings'); } // Log the email configuration (without sensitive data). $this->logger->info( - 'SoftwareCatalog: Email configuration', + 'Stackiq: Email configuration', [ 'enabled' => $emailSettings['enabled'] ?? false, 'transport_type' => $emailSettings['transportType'] ?? 'unknown', @@ -2994,7 +2994,7 @@ public function sendTestEmail(string $email, array $emailSettings = []): array { // Check if email is enabled. if (($emailSettings['enabled'] ?? false) === false) { - $this->logger->warning('SoftwareCatalog: Email notifications are disabled'); + $this->logger->warning('Stackiq: Email notifications are disabled'); return [ 'success' => false, 'message' => 'Email notifications are disabled', @@ -3004,7 +3004,7 @@ public function sendTestEmail(string $email, array $emailSettings = []): array { // Use test receiver override if configured. $recipient = $emailSettings['testReceiverOverride'] ?? $email; $this->logger->info( - 'SoftwareCatalog: Final recipient determined', + 'Stackiq: Final recipient determined', [ 'original_recipient' => $email, 'final_recipient' => $recipient, @@ -3013,12 +3013,12 @@ public function sendTestEmail(string $email, array $emailSettings = []): array { ); // Create transport based on configuration. - $this->logger->info('SoftwareCatalog: Creating email transport'); + $this->logger->info('Stackiq: Creating email transport'); $transport = $this->createEmailTransport(emailSettings: $emailSettings); - $this->logger->info('SoftwareCatalog: Email transport created successfully'); + $this->logger->info('Stackiq: Email transport created successfully'); $mailer = new Mailer($transport); - $this->logger->info('SoftwareCatalog: Mailer instance created'); + $this->logger->info('Stackiq: Mailer instance created'); // Create test email. $senderEmail = $emailSettings['senderEmail'] ?? 'noreply@softwarecatalogus.nl'; @@ -3026,7 +3026,7 @@ public function sendTestEmail(string $email, array $emailSettings = []): array { $transportType = $emailSettings['transportType'] ?? 'smtp'; $this->logger->info( - 'SoftwareCatalog: Creating email message', + 'Stackiq: Creating email message', [ 'sender_email' => $senderEmail, 'sender_name' => $senderName, @@ -3050,13 +3050,13 @@ public function sendTestEmail(string $email, array $emailSettings = []): array { ' ); - $this->logger->info('SoftwareCatalog: Email message created, attempting to send'); + $this->logger->info('Stackiq: Email message created, attempting to send'); // Send the email. $mailer->send($email); $this->logger->info( - 'SoftwareCatalog: Email sent successfully via Symfony Mailer', + 'Stackiq: Email sent successfully via Symfony Mailer', [ 'recipient' => $recipient, 'transport' => $transportType, @@ -3070,7 +3070,7 @@ public function sendTestEmail(string $email, array $emailSettings = []): array { ]; } catch (\Exception $e) { $this->logger->error( - 'SoftwareCatalog: Failed to send test email', + 'Stackiq: Failed to send test email', [ 'recipient' => $email, 'exception_class' => get_class($e), @@ -3096,7 +3096,7 @@ public function sendTestEmail(string $email, array $emailSettings = []): array { */ public function testEmailConnection(array $emailSettings = []): array { $this->logger->info( - 'SoftwareCatalog: Starting email connection test', + 'Stackiq: Starting email connection test', [ 'has_email_settings' => empty($emailSettings) === false, ] @@ -3105,19 +3105,19 @@ public function testEmailConnection(array $emailSettings = []): array { try { // Ensure vendor autoloader is loaded. include_once __DIR__ . '/../../vendor/autoload.php'; - $this->logger->debug('SoftwareCatalog: Vendor autoloader loaded'); + $this->logger->debug('Stackiq: Vendor autoloader loaded'); // Use provided settings or fall back to stored settings. if (empty($emailSettings) === true) { $emailSettings = $this->getEmailSettings(); - $this->logger->info('SoftwareCatalog: Loaded email settings from storage'); + $this->logger->info('Stackiq: Loaded email settings from storage'); } else { - $this->logger->info('SoftwareCatalog: Using provided email settings'); + $this->logger->info('Stackiq: Using provided email settings'); } // Log the email configuration (without sensitive data). $this->logger->info( - 'SoftwareCatalog: Email configuration for connection test', + 'Stackiq: Email configuration for connection test', [ 'enabled' => $emailSettings['enabled'] ?? false, 'transport_type' => $emailSettings['transportType'] ?? 'unknown', @@ -3129,7 +3129,7 @@ public function testEmailConnection(array $emailSettings = []): array { // Check if email is enabled. if (($emailSettings['enabled'] ?? false) === false) { - $this->logger->warning('SoftwareCatalog: Email notifications are disabled'); + $this->logger->warning('Stackiq: Email notifications are disabled'); return [ 'success' => false, 'message' => 'Email notifications are disabled', @@ -3148,19 +3148,19 @@ public function testEmailConnection(array $emailSettings = []): array { } // Create transport based on configuration (this tests the connection). - $this->logger->info('SoftwareCatalog: Creating email transport for connection test'); + $this->logger->info('Stackiq: Creating email transport for connection test'); $transport = $this->createEmailTransport(emailSettings: $emailSettings); - $this->logger->info('SoftwareCatalog: Email transport created successfully'); + $this->logger->info('Stackiq: Email transport created successfully'); // Test the connection by creating a mailer instance. $mailer = new Mailer($transport); - $this->logger->info('SoftwareCatalog: Mailer instance created for connection test'); + $this->logger->info('Stackiq: Mailer instance created for connection test'); // For some transports, we can test the connection more directly. $connectionDetails = $this->getConnectionDetails(emailSettings: $emailSettings); $this->logger->info( - 'SoftwareCatalog: Email connection test completed successfully', + 'Stackiq: Email connection test completed successfully', [ 'transport' => $transportType, 'sender' => $senderEmail, @@ -3174,7 +3174,7 @@ public function testEmailConnection(array $emailSettings = []): array { ]; } catch (\Exception $e) { $this->logger->error( - 'SoftwareCatalog: Email connection test failed', + 'Stackiq: Email connection test failed', [ 'exception_class' => get_class($e), 'exception_message' => $e->getMessage(), @@ -3289,7 +3289,7 @@ private function createEmailTransport(array $emailSettings): \Symfony\Component\ $transportType = $emailSettings['transportType'] ?? 'smtp'; $this->logger->info( - 'SoftwareCatalog: Creating transport', + 'Stackiq: Creating transport', [ 'transport_type' => $transportType, ] @@ -3297,14 +3297,14 @@ private function createEmailTransport(array $emailSettings): \Symfony\Component\ switch ($transportType) { case 'mailjet': - $this->logger->info('SoftwareCatalog: Creating Mailjet transport'); + $this->logger->info('Stackiq: Creating Mailjet transport'); return $this->createMailjetTransport(settings: $emailSettings); case 'smtp': - $this->logger->info('SoftwareCatalog: Creating SMTP transport'); + $this->logger->info('Stackiq: Creating SMTP transport'); return $this->createSmtpTransport(settings: $emailSettings); default: $this->logger->error( - 'SoftwareCatalog: Unsupported transport type', + 'Stackiq: Unsupported transport type', [ 'transport_type' => $transportType, ] @@ -3325,7 +3325,7 @@ private function createMailjetTransport(array $settings): \Symfony\Component\Mai $secretKey = $settings['mailjetSecretKey'] ?? ''; $this->logger->info( - 'SoftwareCatalog: Mailjet transport configuration', + 'Stackiq: Mailjet transport configuration', [ 'has_api_key' => empty($apiKey) === false, 'api_key_length' => strlen($apiKey), @@ -3336,7 +3336,7 @@ private function createMailjetTransport(array $settings): \Symfony\Component\Mai if (empty($apiKey) === true || empty($secretKey) === true) { $this->logger->error( - 'SoftwareCatalog: Mailjet API key and secret key are required', + 'Stackiq: Mailjet API key and secret key are required', [ 'api_key_empty' => empty($apiKey) === true, 'secret_key_empty' => empty($secretKey) === true, @@ -3352,7 +3352,7 @@ private function createMailjetTransport(array $settings): \Symfony\Component\Mai ); $this->logger->info( - 'SoftwareCatalog: Creating Mailjet transport with DSN', + 'Stackiq: Creating Mailjet transport with DSN', [ 'dsn_pattern' => 'mailjet+api://***:***@default', ] @@ -3361,7 +3361,7 @@ private function createMailjetTransport(array $settings): \Symfony\Component\Mai try { $transport = Transport::fromDsn($dsn); $this->logger->info( - 'SoftwareCatalog: Mailjet transport created successfully', + 'Stackiq: Mailjet transport created successfully', [ 'transport_class' => get_class($transport), ] @@ -3369,7 +3369,7 @@ private function createMailjetTransport(array $settings): \Symfony\Component\Mai return $transport; } catch (\Exception $e) { $this->logger->error( - 'SoftwareCatalog: Failed to create Mailjet transport', + 'Stackiq: Failed to create Mailjet transport', [ 'exception_class' => get_class($e), 'exception_message' => $e->getMessage(), @@ -3394,7 +3394,7 @@ private function createSmtpTransport(array $settings): \Symfony\Component\Mailer $password = $settings['smtpPassword'] ?? ''; $this->logger->info( - 'SoftwareCatalog: SMTP transport configuration', + 'Stackiq: SMTP transport configuration', [ 'host' => $host, 'port' => $port, @@ -3425,7 +3425,7 @@ private function createSmtpTransport(array $settings): \Symfony\Component\Mailer $dsnPattern = sprintf('smtp://***:***@%s:%d%s', $host, $port, $encSuffix); $this->logger->info( - 'SoftwareCatalog: Creating SMTP transport with DSN', + 'Stackiq: Creating SMTP transport with DSN', [ 'dsn_pattern' => $dsnPattern, ] @@ -3434,7 +3434,7 @@ private function createSmtpTransport(array $settings): \Symfony\Component\Mailer try { $transport = Transport::fromDsn($dsn); $this->logger->info( - 'SoftwareCatalog: SMTP transport created successfully', + 'Stackiq: SMTP transport created successfully', [ 'transport_class' => get_class($transport), ] @@ -3442,7 +3442,7 @@ private function createSmtpTransport(array $settings): \Symfony\Component\Mailer return $transport; } catch (\Exception $e) { $this->logger->error( - 'SoftwareCatalog: Failed to create SMTP transport', + 'Stackiq: Failed to create SMTP transport', [ 'exception_class' => get_class($e), 'exception_message' => $e->getMessage(), @@ -3506,7 +3506,7 @@ private function shouldLoadSettings(): bool { public function getVersionInfo(): array { try { // Get the current app version. - $currentAppVersion = $this->appManager->getAppVersion(\OCA\SoftwareCatalog\AppInfo\Application::APP_ID); + $currentAppVersion = $this->appManager->getAppVersion(\OCA\Stackiq\AppInfo\Application::APP_ID); $this->logger->debug( 'SettingsService: Getting version information', @@ -3520,7 +3520,7 @@ public function getVersionInfo(): array { $storedConfigVersion = null; try { - $appId = \OCA\SoftwareCatalog\AppInfo\Application::APP_ID; + $appId = \OCA\Stackiq\AppInfo\Application::APP_ID; $storedConfigVersion = $configurationService->getConfiguredAppVersion($appId); } catch (\Exception $e) { $this->logger->warning( @@ -3550,7 +3550,7 @@ public function getVersionInfo(): array { } $versionInfo = [ - 'appName' => 'SoftwareCatalog', + 'appName' => 'Stackiq', 'appVersion' => $currentAppVersion, 'configuredVersion' => $storedConfigVersion, 'versionsMatch' => $versionsMatch, @@ -4612,7 +4612,7 @@ private function normalizeVoorzieningenConfig(array $input): array { public function getAmefConfig(): array { try { // Get ArchiMateService from container to avoid circular dependency. - $archiMateService = $this->container->get(\OCA\SoftwareCatalog\Service\ArchiMateService::class); + $archiMateService = $this->container->get(\OCA\Stackiq\Service\ArchiMateService::class); // Use reflection to access the private getAmefConfig method. // setAccessible() is unnecessary on PHP 8.1+ — private methods are @@ -4756,7 +4756,7 @@ public function setEmailConfig(array $config): void { public function getArchiMateStatus(): array { try { // Get ArchiMateService from container to avoid circular dependency. - $archiMateService = $this->container->get(\OCA\SoftwareCatalog\Service\ArchiMateService::class); + $archiMateService = $this->container->get(\OCA\Stackiq\Service\ArchiMateService::class); return $archiMateService->getArchiMateStatus(); } catch (\Exception $e) { @@ -4936,7 +4936,7 @@ private function getVoorzieningenObjectCounts(): array { private function getAmefObjectCounts(): array { try { // Get ArchiMateService from container to avoid circular dependency. - $archiMateService = $this->container->get(\OCA\SoftwareCatalog\Service\ArchiMateService::class); + $archiMateService = $this->container->get(\OCA\Stackiq\Service\ArchiMateService::class); // Get object counts using ArchiMateService methods. $elementObjects = $archiMateService->getElementObjects(); @@ -4997,7 +4997,7 @@ private function getAmefObjectCounts(): array { public function setArchiMateImportStatus(array $status): void { try { // Get ArchiMateService from container to avoid circular dependency. - $archiMateService = $this->container->get(\OCA\SoftwareCatalog\Service\ArchiMateService::class); + $archiMateService = $this->container->get(\OCA\Stackiq\Service\ArchiMateService::class); $archiMateService->setArchiMateImportStatus($status); } catch (\Exception $e) { @@ -5029,7 +5029,7 @@ public function setArchiMateImportStatus(array $status): void { public function setArchiMateExportStatus(array $status): void { try { // Get ArchiMateService from container to avoid circular dependency. - $archiMateService = $this->container->get(\OCA\SoftwareCatalog\Service\ArchiMateService::class); + $archiMateService = $this->container->get(\OCA\Stackiq\Service\ArchiMateService::class); $archiMateService->setArchiMateExportStatus($status); } catch (\Exception $e) { @@ -5059,7 +5059,7 @@ public function setArchiMateExportStatus(array $status): void { public function clearArchiMateImportStatus(): array { try { // Get ArchiMateService from container to avoid circular dependency. - $archiMateService = $this->container->get(\OCA\SoftwareCatalog\Service\ArchiMateService::class); + $archiMateService = $this->container->get(\OCA\Stackiq\Service\ArchiMateService::class); return $archiMateService->clearArchiMateImportStatus(); } catch (\Exception $e) { @@ -5097,7 +5097,7 @@ public function clearArchiMateImportStatus(): array { public function killArchiMateImport(): array { try { // Get ArchiMateService from container to avoid circular dependency. - $archiMateService = $this->container->get(\OCA\SoftwareCatalog\Service\ArchiMateService::class); + $archiMateService = $this->container->get(\OCA\Stackiq\Service\ArchiMateService::class); return $archiMateService->clearArchiMateImportStatus(true); // KillProcess = true. @@ -5135,7 +5135,7 @@ public function killArchiMateImport(): array { public function cancelArchiMateImport(): array { try { // Get ArchiMateService from container to avoid circular dependency. - $archiMateService = $this->container->get(\OCA\SoftwareCatalog\Service\ArchiMateService::class); + $archiMateService = $this->container->get(\OCA\Stackiq\Service\ArchiMateService::class); return $archiMateService->cancelArchiMateImport(); } catch (\Exception $e) { @@ -5174,7 +5174,7 @@ public function cancelArchiMateImport(): array { public function clearArchiMateExportStatus(): void { try { // Get ArchiMateService from container to avoid circular dependency. - $archiMateService = $this->container->get(\OCA\SoftwareCatalog\Service\ArchiMateService::class); + $archiMateService = $this->container->get(\OCA\Stackiq\Service\ArchiMateService::class); $archiMateService->clearArchiMateExportStatus(); } catch (\Exception $e) { @@ -6819,10 +6819,10 @@ private function getAvailableCronjobs(): array { return [ 'organization_contact_sync' => [ 'name' => 'Organization Contact Sync', - 'description' => 'Syncs organizations and contacts between SoftwareCatalog and OpenRegister.', + 'description' => 'Syncs organizations and contacts between Stackiq and OpenRegister.', 'interval' => 300, // 5 minutes. - 'class' => 'OCA\\SoftwareCatalog\\BackgroundJob\\OrganizationContactSyncJob', + 'class' => 'OCA\\Stackiq\\BackgroundJob\\OrganizationContactSyncJob', ], ]; }//end getAvailableCronjobs() @@ -7234,7 +7234,7 @@ public function setEolSyncStatus(array $status): void { * merged by key union (recursing on shared keys); list arrays are concatenated; * scalars in the fragment overwrite the base. Disjoint fragments never collide. * - * EXCEPTION (catalog-ratings, softwarecatalog#375): any key literally named + * EXCEPTION (catalog-ratings, stackiq#375): any key literally named * `authorization` switches its entire subtree to REPLACE semantics for list * values, instead of the general concatenation above. Concatenating an RBAC * rule list is a fail-OPEN trap: if the base already carries an unconditional @@ -7262,7 +7262,7 @@ public function setEolSyncStatus(array $status): void { * an `authorization` key has been crossed, and it stays true for that whole subtree. Turning * it into two methods would mean duplicating the merge for the sole purpose of removing a * parameter that no external caller ever passes, and would make the fail-open trap this flag - * exists to close (softwarecatalog#375) easier to reintroduce. + * exists to close (stackiq#375) easier to reintroduce. */ private static function deepMergeConfig(array $base, array $overlay, bool $replaceLists = false): array { foreach ($overlay as $key => $value) { diff --git a/lib/Service/SoftwareCatalogue/ApiClient.php b/lib/Service/Stackiq/ApiClient.php similarity index 91% rename from lib/Service/SoftwareCatalogue/ApiClient.php rename to lib/Service/Stackiq/ApiClient.php index baad662f..f46597a6 100644 --- a/lib/Service/SoftwareCatalogue/ApiClient.php +++ b/lib/Service/Stackiq/ApiClient.php @@ -1,13 +1,13 @@ * @copyright 2024 Conduction B.V. * @license EUPL-1.2 https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12 @@ -21,15 +21,15 @@ declare(strict_types=1); -namespace OCA\SoftwareCatalog\Service\SoftwareCatalogue; +namespace OCA\Stackiq\Service\Stackiq; use OCA\OpenRegister\Contract\ObjectServiceInterface; use Psr\Log\LoggerInterface; /** - * Handles all API communication for the SoftwareCatalogue domain. + * Handles all API communication for the Stackiq domain. * - * SoftwareCatalogueService delegates API fetch operations to this class, + * StackiqService delegates API fetch operations to this class, * keeping its own constructor coupling below the PHPMD CouplingBetweenObjects * threshold and keeping its methods below ExcessiveMethodLength. * diff --git a/lib/Service/SoftwareCatalogue/ConflictResolver.php b/lib/Service/Stackiq/ConflictResolver.php similarity index 92% rename from lib/Service/SoftwareCatalogue/ConflictResolver.php rename to lib/Service/Stackiq/ConflictResolver.php index 92506ff3..0a044f64 100644 --- a/lib/Service/SoftwareCatalogue/ConflictResolver.php +++ b/lib/Service/Stackiq/ConflictResolver.php @@ -1,13 +1,13 @@ * @copyright 2024 Conduction B.V. * @license EUPL-1.2 https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12 @@ -21,14 +21,14 @@ declare(strict_types=1); -namespace OCA\SoftwareCatalog\Service\SoftwareCatalogue; +namespace OCA\Stackiq\Service\Stackiq; use Psr\Log\LoggerInterface; /** - * Resolves conflicts and deduplicates entries in the SoftwareCatalogue domain. + * Resolves conflicts and deduplicates entries in the Stackiq domain. * - * SoftwareCatalogueService delegates all conflict detection and resolution + * StackiqService delegates all conflict detection and resolution * methods to this class, shrinking its own complexity metrics. * * @spec openspec/changes/method-decomposition/tasks.md#task-2 diff --git a/lib/Service/SoftwareCatalogue/ContactPersonHandler.php b/lib/Service/Stackiq/ContactPersonHandler.php similarity index 98% rename from lib/Service/SoftwareCatalogue/ContactPersonHandler.php rename to lib/Service/Stackiq/ContactPersonHandler.php index cae6758a..3e0395cd 100644 --- a/lib/Service/SoftwareCatalogue/ContactPersonHandler.php +++ b/lib/Service/Stackiq/ContactPersonHandler.php @@ -7,7 +7,7 @@ * contact processing, and organizational hierarchy management. * * @category Handler - * @package OCA\SoftwareCatalog\Service\SoftwareCatalogue + * @package OCA\Stackiq\Service\Stackiq * @author Conduction b.v. * @copyright 2024 Conduction B.V. * @license EUPL-1.2 https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12 @@ -16,9 +16,9 @@ declare(strict_types=1); -namespace OCA\SoftwareCatalog\Service\SoftwareCatalogue; +namespace OCA\Stackiq\Service\Stackiq; -use OCA\SoftwareCatalog\Service\SymfonyEmailService; +use OCA\Stackiq\Service\SymfonyEmailService; use OCP\App\IAppManager; use OCP\IAppConfig; use OCP\IConfig; @@ -33,7 +33,7 @@ * Handler for contact person-related operations. * * @category Handler - * @package OCA\SoftwareCatalog\Service\SoftwareCatalogue + * @package OCA\Stackiq\Service\Stackiq * @author Conduction b.v. * @copyright 2024 Conduction B.V. * @license EUPL-1.2 https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12 @@ -302,7 +302,7 @@ public function createUserAccount(object $contactPersonObject, bool $isFirstCont $this->_logger->debug( 'User account creation started', [ - 'app' => 'softwarecatalog', + 'app' => 'stackiq', 'contactId' => $contactId, 'email' => $email, 'organizationUuid' => $organizationUuid, @@ -314,7 +314,7 @@ public function createUserAccount(object $contactPersonObject, bool $isFirstCont $this->_logger->error( '❌ USER CREATION FAILED - NO EMAIL', [ - 'app' => 'softwarecatalog', + 'app' => 'stackiq', 'contactpersoonId' => $contactId, ] ); @@ -335,7 +335,7 @@ public function createUserAccount(object $contactPersonObject, bool $isFirstCont $this->_logger->critical( '📝 USERNAME GENERATED', [ - 'app' => 'softwarecatalog', + 'app' => 'stackiq', 'contactId' => $contactId, 'generatedUsername' => $username, 'email' => $email, @@ -354,7 +354,7 @@ public function createUserAccount(object $contactPersonObject, bool $isFirstCont $this->_logger->critical( '♻️ USER EXISTS BY EMAIL', [ - 'app' => 'softwarecatalog', + 'app' => 'stackiq', 'email' => $email, 'contactpersoonId' => $contactId, ] @@ -376,7 +376,7 @@ public function createUserAccount(object $contactPersonObject, bool $isFirstCont $this->_logger->critical( '✅ EXISTING USER UPDATED', [ - 'app' => 'softwarecatalog', + 'app' => 'stackiq', 'username' => $existingUser->getUID(), 'email' => $email, 'organizationUuid' => $organizationUuid, @@ -399,7 +399,7 @@ public function createUserAccount(object $contactPersonObject, bool $isFirstCont $this->_logger->critical( '♻️ USER EXISTS BY USERNAME', [ - 'app' => 'softwarecatalog', + 'app' => 'stackiq', 'username' => $username, 'contactpersoonId' => $contactId, ] @@ -423,7 +423,7 @@ public function createUserAccount(object $contactPersonObject, bool $isFirstCont $this->_logger->critical( '✅ EXISTING USER UPDATED BY USERNAME', [ - 'app' => 'softwarecatalog', + 'app' => 'stackiq', 'username' => $username, 'email' => $existingUserByUsername->getEMailAddress(), 'organizationUuid' => $organizationUuid, @@ -437,7 +437,7 @@ public function createUserAccount(object $contactPersonObject, bool $isFirstCont $this->_logger->critical( '🚀 CREATING NEW USER ACCOUNT', [ - 'app' => 'softwarecatalog', + 'app' => 'stackiq', 'username' => $username, 'email' => $email, 'contactId' => $contactId, @@ -455,7 +455,7 @@ public function createUserAccount(object $contactPersonObject, bool $isFirstCont $this->_logger->critical( '🎊 NEW USER ACCOUNT CREATED', [ - 'app' => 'softwarecatalog', + 'app' => 'stackiq', 'username' => $username, 'email' => $email, 'contactId' => $contactId, @@ -485,7 +485,7 @@ public function createUserAccount(object $contactPersonObject, bool $isFirstCont $this->_logger->critical( '📋 USER DETAILS SET', [ - 'app' => 'softwarecatalog', + 'app' => 'stackiq', 'username' => $username, 'email' => $email, 'displayName' => $displayName, @@ -558,7 +558,7 @@ public function createUserAccount(object $contactPersonObject, bool $isFirstCont $this->_logger->critical( '🎉 USER ACCOUNT CREATION COMPLETED', [ - 'app' => 'softwarecatalog', + 'app' => 'stackiq', 'contactpersoonId' => $contactId, 'username' => $username, 'email' => $email, @@ -574,7 +574,7 @@ public function createUserAccount(object $contactPersonObject, bool $isFirstCont $this->_logger->error( '❌ USER CREATION RETURNED NULL', [ - 'app' => 'softwarecatalog', + 'app' => 'stackiq', 'username' => $username, 'email' => $email, 'contactpersoonId' => $contactId, @@ -587,7 +587,7 @@ public function createUserAccount(object $contactPersonObject, bool $isFirstCont $this->_logger->error( '💥 USER CREATION EXCEPTION', [ - 'app' => 'softwarecatalog', + 'app' => 'stackiq', 'contactpersoonId' => $contactPersonObject->getId(), 'email' => $objectData['email'] ?? $objectData['e-mailadres'] ?? 'unknown', 'username' => $username ?? 'unknown', @@ -640,7 +640,7 @@ private function assignUserGroups(\OCP\IUser $user, array $objectData, bool $isF } // Get the settings service to access group configurations. - $settingsService = $this->_container->get('OCA\SoftwareCatalog\Service\SettingsService'); + $settingsService = $this->_container->get('OCA\Stackiq\Service\SettingsService'); // Add user to organization admin groups if this is the first contact. if ($isFirstContact === true) { @@ -995,7 +995,7 @@ private function getOrganizationGroup(string $organizationId): ?\OCP\IGroup { } // Get register and schema IDs dynamically from configuration. - $settingsService = $this->_container->get('OCA\SoftwareCatalog\Service\SettingsService'); + $settingsService = $this->_container->get('OCA\Stackiq\Service\SettingsService'); $registerId = $settingsService->getVoorzieningenRegisterId(); $organisationSchemaId = $settingsService->getSchemaIdForObjectType('organization'); @@ -1046,7 +1046,7 @@ public function isFirstContactForOrganization(object $contactObject, array $obje $this->_logger->info( 'isFirstContactForOrganization: Defaulting to true (simplified)', [ - 'app' => 'softwarecatalog', + 'app' => 'stackiq', 'contactId' => $contactObject->getId(), 'contactUuid' => $contactObject->getUuid(), ] @@ -1475,7 +1475,7 @@ public function setUserManager(string $username, string $managerUsername): void // Since there's no built-in manager field, we'll use preferences. $this->config->setUserValue( $username, - 'softwarecatalog', + 'stackiq', 'manager', $managerUsername ); @@ -1511,7 +1511,7 @@ public function getUserManager(string $username): ?string { try { $manager = $this->config->getUserValue( $username, - 'softwarecatalog', + 'stackiq', 'manager', '' ); @@ -1553,7 +1553,7 @@ private function getOrganizationType(string $organizationId): string { ); // Get voorzieningen config for register and schema. - $settingsService = $this->_container->get('OCA\SoftwareCatalog\Service\SettingsService'); + $settingsService = $this->_container->get('OCA\Stackiq\Service\SettingsService'); $voorzieningenConfig = $settingsService->getVoorzieningenConfig(); $register = $voorzieningenConfig['register'] ?? ''; $organizationSchema = $voorzieningenConfig['organisatie_schema'] ?? ''; @@ -1667,7 +1667,7 @@ private function sendUserCreationEmail(\OCP\IUser $user, array $objectData): voi try { $objectService = $this->getObjectService(); // Get register and schema IDs dynamically from configuration. - $settingsService = $this->_container->get('OCA\SoftwareCatalog\Service\SettingsService'); + $settingsService = $this->_container->get('OCA\Stackiq\Service\SettingsService'); $registerId = $settingsService->getVoorzieningenRegisterId(); $organisationSchemaId = $settingsService->getSchemaIdForObjectType('organization'); @@ -2116,7 +2116,7 @@ public function shouldAddContactpersoonToOrganization(object $contactPersonObjec } // Get the organization object. - $settingsService = $this->_container->get('OCA\SoftwareCatalog\Service\SettingsService'); + $settingsService = $this->_container->get('OCA\Stackiq\Service\SettingsService'); $registerId = $settingsService->getVoorzieningenRegisterId(); $organisationSchemaId = $settingsService->getSchemaIdForObjectType('organization'); @@ -2199,7 +2199,7 @@ public function addContactpersoonToOrganization(object $contactPersonObject): bo } // Get the organization object. - $settingsService = $this->_container->get('OCA\SoftwareCatalog\Service\SettingsService'); + $settingsService = $this->_container->get('OCA\Stackiq\Service\SettingsService'); $registerId = $settingsService->getVoorzieningenRegisterId(); $organisationSchemaId = $settingsService->getSchemaIdForObjectType('organization'); @@ -2502,7 +2502,7 @@ private function ensureOrganizationEntity(string $organizationUuid): ?\OCA\OpenR $objectService = $this->getObjectService(); // Get voorzieningen config for register and schema. - $settingsService = $this->_container->get('OCA\SoftwareCatalog\Service\SettingsService'); + $settingsService = $this->_container->get('OCA\Stackiq\Service\SettingsService'); $voorzieningenConfig = $settingsService->getVoorzieningenConfig(); $register = $voorzieningenConfig['register'] ?? ''; $organizationSchema = $voorzieningenConfig['organisatie_schema'] ?? ''; diff --git a/lib/Service/SoftwareCatalogue/DataMapper.php b/lib/Service/Stackiq/DataMapper.php similarity index 93% rename from lib/Service/SoftwareCatalogue/DataMapper.php rename to lib/Service/Stackiq/DataMapper.php index d5049240..ead69b56 100644 --- a/lib/Service/SoftwareCatalogue/DataMapper.php +++ b/lib/Service/Stackiq/DataMapper.php @@ -1,13 +1,13 @@ * @copyright 2024 Conduction B.V. * @license EUPL-1.2 https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12 @@ -21,14 +21,14 @@ declare(strict_types=1); -namespace OCA\SoftwareCatalog\Service\SoftwareCatalogue; +namespace OCA\Stackiq\Service\Stackiq; use Psr\Log\LoggerInterface; /** - * Maps and transforms data between external API formats and the SoftwareCatalogue domain model. + * Maps and transforms data between external API formats and the Stackiq domain model. * - * SoftwareCatalogueService delegates all data transformation methods to this class, + * StackiqService delegates all data transformation methods to this class, * shrinking its own method bodies below ExcessiveMethodLength. * * @spec openspec/changes/method-decomposition/tasks.md#task-2 diff --git a/lib/Service/SoftwareCatalogue/GroupHandler.php b/lib/Service/Stackiq/GroupHandler.php similarity index 98% rename from lib/Service/SoftwareCatalogue/GroupHandler.php rename to lib/Service/Stackiq/GroupHandler.php index aa48e2f3..1bc340a4 100644 --- a/lib/Service/SoftwareCatalogue/GroupHandler.php +++ b/lib/Service/Stackiq/GroupHandler.php @@ -7,7 +7,7 @@ * and ensures all required groups exist and are properly configured. * * @category Handler - * @package OCA\SoftwareCatalog\Service\SoftwareCatalogue + * @package OCA\Stackiq\Service\Stackiq * @author Conduction b.v. * @copyright 2024 Conduction B.V. * @license EUPL-1.2 https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12 @@ -19,7 +19,7 @@ declare(strict_types=1); -namespace OCA\SoftwareCatalog\Service\SoftwareCatalogue; +namespace OCA\Stackiq\Service\Stackiq; use OCA\OpenRegister\Contract\ObjectServiceInterface; use OCP\App\IAppManager; @@ -36,7 +36,7 @@ * Handler for group management operations * * @category Handler - * @package OCA\SoftwareCatalog\Service\SoftwareCatalogue + * @package OCA\Stackiq\Service\Stackiq * @author Conduction b.v. * @copyright 2024 Conduction B.V. * @license EUPL-1.2 https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12 @@ -57,7 +57,7 @@ class GroupHandler { * * @var string */ - private const APP_NAME = 'softwarecatalog'; + private const APP_NAME = 'stackiq'; /** * GroupHandler constructor @@ -396,7 +396,7 @@ public function updateOrganizationGroups(IUser $user, array $objectData): void { */ private function resolveOrganisationData(string $organisationUuid): ?array { $objectService = $this->getObjectService(); - $settingsService = $this->_container->get('OCA\SoftwareCatalog\Service\SettingsService'); + $settingsService = $this->_container->get('OCA\Stackiq\Service\SettingsService'); $registerId = $settingsService->getVoorzieningenRegisterId(); $organisationSchemaId = $settingsService->getSchemaIdForObjectType('organization'); diff --git a/lib/Service/SoftwareCatalogue/HierarchyHandler.php b/lib/Service/Stackiq/HierarchyHandler.php similarity index 98% rename from lib/Service/SoftwareCatalogue/HierarchyHandler.php rename to lib/Service/Stackiq/HierarchyHandler.php index efd6eb00..86916186 100644 --- a/lib/Service/SoftwareCatalogue/HierarchyHandler.php +++ b/lib/Service/Stackiq/HierarchyHandler.php @@ -7,7 +7,7 @@ * and manager relationships within organizations. * * @category Handler - * @package OCA\SoftwareCatalog\Service\SoftwareCatalogue + * @package OCA\Stackiq\Service\Stackiq * @author Conduction b.v. * @copyright 2024 Conduction B.V. * @license EUPL-1.2 https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12 @@ -19,7 +19,7 @@ declare(strict_types=1); -namespace OCA\SoftwareCatalog\Service\SoftwareCatalogue; +namespace OCA\Stackiq\Service\Stackiq; use OCP\IGroupManager; use OCP\IUserManager; @@ -29,7 +29,7 @@ * Handler for organizational hierarchy management * * @category Handler - * @package OCA\SoftwareCatalog\Service\SoftwareCatalogue + * @package OCA\Stackiq\Service\Stackiq * @author Conduction b.v. * @copyright 2024 Conduction B.V. * @license EUPL-1.2 https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12 diff --git a/lib/Service/SoftwareCatalogue/OrganizationHandler.php b/lib/Service/Stackiq/OrganizationHandler.php similarity index 98% rename from lib/Service/SoftwareCatalogue/OrganizationHandler.php rename to lib/Service/Stackiq/OrganizationHandler.php index 25533b53..9520223f 100644 --- a/lib/Service/SoftwareCatalogue/OrganizationHandler.php +++ b/lib/Service/Stackiq/OrganizationHandler.php @@ -7,7 +7,7 @@ * organization processing, and hierarchy management. * * @category Handler - * @package OCA\SoftwareCatalog\Service\SoftwareCatalogue + * @package OCA\Stackiq\Service\Stackiq * @author Conduction b.v. * @copyright 2024 Conduction B.V. * @license EUPL-1.2 https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12 @@ -19,7 +19,7 @@ declare(strict_types=1); -namespace OCA\SoftwareCatalog\Service\SoftwareCatalogue; +namespace OCA\Stackiq\Service\Stackiq; use OCP\App\IAppManager; use OCP\IGroup; @@ -33,7 +33,7 @@ * Handler for organization-related operations. * * @category Handler - * @package OCA\SoftwareCatalog\Service\SoftwareCatalogue + * @package OCA\Stackiq\Service\Stackiq * @author Conduction b.v. * @copyright 2024 Conduction B.V. * @license EUPL-1.2 https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12 @@ -181,7 +181,7 @@ public function ensureOrganizationGroup(object $organizationObject, array &$obje // Save the updated organization with correct register/schema IDs. $objectService = $this->getObjectService(); - $settingsService = $this->_container->get('OCA\SoftwareCatalog\Service\SettingsService'); + $settingsService = $this->_container->get('OCA\Stackiq\Service\SettingsService'); $registerId = $settingsService->getVoorzieningenRegisterId(); $organizationSchemaId = $settingsService->getSchemaIdForObjectType('organization'); @@ -347,7 +347,7 @@ public function processContactpersonen(object $organizationObject): array { foreach ($contactpersonen as $index => $contactPerson) { try { // Get the contactgegevens schema ID from settings. - $settingsService = $this->_container->get('OCA\SoftwareCatalog\Service\SettingsService'); + $settingsService = $this->_container->get('OCA\Stackiq\Service\SettingsService'); $contactgegevensSchemaId = $settingsService->getSchemaIdForObjectType('contactgegevens'); $registerId = $settingsService->getVoorzieningenRegisterId(); diff --git a/lib/Service/SoftwareCatalogContactSyncService.php b/lib/Service/StackiqContactSyncService.php similarity index 94% rename from lib/Service/SoftwareCatalogContactSyncService.php rename to lib/Service/StackiqContactSyncService.php index 324a3606..6e2cc4e7 100644 --- a/lib/Service/SoftwareCatalogContactSyncService.php +++ b/lib/Service/StackiqContactSyncService.php @@ -1,9 +1,9 @@ * @copyright 2026 Conduction B.V. * @license EUPL-1.2 https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12 @@ -25,32 +25,31 @@ declare(strict_types=1); -namespace OCA\SoftwareCatalog\Service; +namespace OCA\Stackiq\Service; use OCP\Constants; use OCP\Contacts\IManager as IContactsManager; use Psr\Log\LoggerInterface; -use RuntimeException; /** - * Search, import and create Nextcloud contacts for softwarecatalog + * Search, import and create Nextcloud contacts for stackiq * relationship/role records. * * @category Service - * @package OCA\SoftwareCatalog\Service + * @package OCA\Stackiq\Service * @author Conduction b.v. * @license EUPL-1.2 https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12 * @version GIT: * @link https://codeberg.org/Conduction/SoftwareCatalog * * @SuppressWarnings(PHPMD.ExcessiveClassComplexity) Overall complexity 56 (threshold 50). The - * class maps several distinct softwarecatalog relationship types (organisation, contactpersoon, + * class maps several distinct stackiq relationship types (organisation, contactpersoon, * …) onto Nextcloud's vCard contact model, and each mapping needs per-field presence checks * because the legacy identity fields are optional and inconsistently populated across records. * The Contacts API is also an optional dependency, so every entry point carries an availability * guard. Both are breadth of mapping rather than depth of logic. */ -class SoftwareCatalogContactSyncService { +class StackiqContactSyncService { /** * Constructor. * @@ -143,7 +142,7 @@ public function searchContacts(string $query): array { */ public function syncToContacts(string $objectType, array $record): ?string { if ($this->isAvailable() === false) { - $this->logger->info('[SoftwareCatalogContactSync] Contacts disabled, cannot resolve UID', ['objectType' => $objectType]); + $this->logger->info('[StackiqContactSync] Contacts disabled, cannot resolve UID', ['objectType' => $objectType]); return null; } @@ -257,7 +256,7 @@ public function createContactForRecord(string $objectType, array $record): ?stri $addressBookKey = $this->firstWritableAddressBookKey(); if ($addressBookKey === null) { $this->logger->warning( - '[SoftwareCatalogContactSync] No writable addressbook available; cannot create contact', + '[StackiqContactSync] No writable addressbook available; cannot create contact', ['objectType' => $objectType] ); return null; @@ -265,7 +264,7 @@ public function createContactForRecord(string $objectType, array $record): ?stri $properties = $this->recordToVCard(objectType: $objectType, record: $record); if (($properties['FN'] ?? '') === '') { - $this->logger->warning('[SoftwareCatalogContactSync] Record has no identity to create a contact from', ['objectType' => $objectType]); + $this->logger->warning('[StackiqContactSync] Record has no identity to create a contact from', ['objectType' => $objectType]); return null; } diff --git a/lib/Service/SoftwareCatalogueService.php b/lib/Service/StackiqService.php similarity index 83% rename from lib/Service/SoftwareCatalogueService.php rename to lib/Service/StackiqService.php index 307d78f2..bf035ad0 100644 --- a/lib/Service/SoftwareCatalogueService.php +++ b/lib/Service/StackiqService.php @@ -7,7 +7,7 @@ * user management, contact processing, and object lifecycle management. * * @category Service - * @package OCA\SoftwareCatalog\Service + * @package OCA\Stackiq\Service * @author Conduction b.v. * @copyright 2024 Conduction B.V. * @license EUPL-1.2 https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12 @@ -18,12 +18,12 @@ declare(strict_types=1); -namespace OCA\SoftwareCatalog\Service; +namespace OCA\Stackiq\Service; -use OCA\SoftwareCatalog\Service\SoftwareCatalogue\ContactPersonHandler; -use OCA\SoftwareCatalog\Service\SoftwareCatalogue\GroupHandler; -use OCA\SoftwareCatalog\Service\SoftwareCatalogue\HierarchyHandler; -use OCA\SoftwareCatalog\Service\SoftwareCatalogue\OrganizationHandler; +use OCA\Stackiq\Service\Stackiq\ContactPersonHandler; +use OCA\Stackiq\Service\Stackiq\GroupHandler; +use OCA\Stackiq\Service\Stackiq\HierarchyHandler; +use OCA\Stackiq\Service\Stackiq\OrganizationHandler; use OCP\App\IAppManager; use OCP\IGroupManager; use OCP\IUserManager; @@ -39,7 +39,7 @@ * email notifications, and object lifecycle management. * * @category Service - * @package OCA\SoftwareCatalog\Service + * @package OCA\Stackiq\Service * @author Conduction b.v. * @copyright 2024 Conduction B.V. * @license EUPL-1.2 https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12 @@ -65,7 +65,7 @@ * @SuppressWarnings(PHPMD.UndefinedVariable) * @SuppressWarnings(PHPMD.CountInLoopExpression) */ -class SoftwareCatalogueService { +class StackiqService { /** * The name of the app @@ -75,7 +75,7 @@ class SoftwareCatalogueService { private string $appName; /** - * SoftwareCatalogueService constructor + * StackiqService constructor * * @param OrganizationHandler $_organizationHandler Organization handler. * @param ContactPersonHandler $_contactPersonHandler Contact person handler. @@ -102,7 +102,7 @@ public function __construct( private readonly IUserManager $_userManager, private readonly IGroupManager $_groupManager, ) { - $this->appName = 'softwarecatalog'; + $this->appName = 'stackiq'; }//end __construct() /** @@ -190,7 +190,7 @@ public function processContactpersoon(object $contactPersonObject, bool $isUpdat $objectData = $contactPersonObject->getObject(); $this->_logger->info( - 'SoftwareCatalogueService: Starting contactpersoon processing', + 'StackiqService: Starting contactpersoon processing', [ 'objectId' => $objectId, 'objectData' => $objectData, @@ -200,7 +200,7 @@ public function processContactpersoon(object $contactPersonObject, bool $isUpdat // Delegate to contact person handler. $this->_logger->debug( - 'SoftwareCatalogueService: Delegating to ContactPersonHandler for contactpersoon processing', + 'StackiqService: Delegating to ContactPersonHandler for contactpersoon processing', [ 'objectId' => $objectId, ] @@ -209,7 +209,7 @@ public function processContactpersoon(object $contactPersonObject, bool $isUpdat $result = $this->_contactPersonHandler->processContactpersoon($contactPersonObject, $isUpdate); $this->_logger->info( - 'SoftwareCatalogueService: ContactPersonHandler processing completed', + 'StackiqService: ContactPersonHandler processing completed', [ 'objectId' => $objectId, 'result' => $result, @@ -223,7 +223,7 @@ public function processContactpersoon(object $contactPersonObject, bool $isUpdat $username = $updatedObjectData['username'] ?? ''; $this->_logger->info( - 'SoftwareCatalogueService: Username extracted from processed object', + 'StackiqService: Username extracted from processed object', [ 'objectId' => $objectId, 'username' => $username, @@ -237,7 +237,7 @@ public function processContactpersoon(object $contactPersonObject, bool $isUpdat // as it would overwrite the correct group assignments. // Ensure organization has beheerder and set up manager relationships. $this->_logger->debug( - 'SoftwareCatalogueService: Ensuring organization beheerder', + 'StackiqService: Ensuring organization beheerder', [ 'objectId' => $objectId, 'username' => $username, @@ -248,7 +248,7 @@ public function processContactpersoon(object $contactPersonObject, bool $isUpdat // Set user to inactive initially. $this->_logger->debug( - 'SoftwareCatalogueService: Setting user to inactive', + 'StackiqService: Setting user to inactive', [ 'objectId' => $objectId, 'username' => $username, @@ -258,7 +258,7 @@ public function processContactpersoon(object $contactPersonObject, bool $isUpdat $this->_contactPersonHandler->setUserInactive($username); $this->_logger->info( - 'SoftwareCatalogueService: User setup completed', + 'StackiqService: User setup completed', [ 'objectId' => $objectId, 'username' => $username, @@ -270,7 +270,7 @@ public function processContactpersoon(object $contactPersonObject, bool $isUpdat $organization = $objectData['organization'] ?? null; if (empty($organization) === false) { $this->_logger->info( - 'SoftwareCatalogueService: Adding user to organization entity', + 'StackiqService: Adding user to organization entity', [ 'objectId' => $objectId, 'username' => $username, @@ -282,7 +282,7 @@ public function processContactpersoon(object $contactPersonObject, bool $isUpdat $organisationMapper = $this->getOrganisationMapper(); if ($organisationMapper === null) { $this->_logger->warning( - 'SoftwareCatalogueService: OpenRegister OrganisationMapper not available, skipping organization membership', + 'StackiqService: OpenRegister OrganisationMapper not available, skipping organization membership', [ 'objectId' => $objectId, 'username' => $username, @@ -304,7 +304,7 @@ public function processContactpersoon(object $contactPersonObject, bool $isUpdat $organisationMapper->save($organisation); $this->_logger->info( - 'SoftwareCatalogueService: Successfully added user to organization entity', + 'StackiqService: Successfully added user to organization entity', [ 'objectId' => $objectId, 'username' => $username, @@ -314,7 +314,7 @@ public function processContactpersoon(object $contactPersonObject, bool $isUpdat ); } else { $this->_logger->info( - 'SoftwareCatalogueService: User already in organization entity', + 'StackiqService: User already in organization entity', [ 'objectId' => $objectId, 'username' => $username, @@ -324,7 +324,7 @@ public function processContactpersoon(object $contactPersonObject, bool $isUpdat }//end if } else { $this->_logger->warning( - 'SoftwareCatalogueService: Organization entity not found', + 'StackiqService: Organization entity not found', [ 'objectId' => $objectId, 'username' => $username, @@ -334,7 +334,7 @@ public function processContactpersoon(object $contactPersonObject, bool $isUpdat }//end if } catch (\Exception $e) { $this->_logger->error( - 'SoftwareCatalogueService: Failed to add user to organization entity', + 'StackiqService: Failed to add user to organization entity', [ 'objectId' => $objectId, 'username' => $username, @@ -345,7 +345,7 @@ public function processContactpersoon(object $contactPersonObject, bool $isUpdat }//end try } else { $this->_logger->warning( - 'SoftwareCatalogueService: No organisation reference found for contact person', + 'StackiqService: No organisation reference found for contact person', [ 'objectId' => $objectId, 'username' => $username, @@ -354,7 +354,7 @@ public function processContactpersoon(object $contactPersonObject, bool $isUpdat }//end if } else { $this->_logger->warning( - 'SoftwareCatalogueService: No username generated for contactpersoon', + 'StackiqService: No username generated for contactpersoon', [ 'objectId' => $objectId, 'objectData' => $updatedObjectData, @@ -363,7 +363,7 @@ public function processContactpersoon(object $contactPersonObject, bool $isUpdat }//end if } else { $this->_logger->warning( - 'SoftwareCatalogueService: ContactPersonHandler returned false', + 'StackiqService: ContactPersonHandler returned false', [ 'objectId' => $objectId, 'processingTime' => round((microtime(true) - $startTime) * 1000, 2) . 'ms', @@ -374,7 +374,7 @@ public function processContactpersoon(object $contactPersonObject, bool $isUpdat return $result; } catch (\Exception $e) { $this->_logger->error( - 'SoftwareCatalogueService: Failed to process contactpersoon object: ' . $e->getMessage(), + 'StackiqService: Failed to process contactpersoon object: ' . $e->getMessage(), [ 'exception' => $e->getMessage(), 'file' => $e->getFile(), @@ -483,7 +483,7 @@ public function getUserManager(string $username): ?string { public function handleNewOrganization(object $organizationObject): void { try { $this->_logger->info( - 'SoftwareCatalogueService: Handling new organization', + 'StackiqService: Handling new organization', [ 'objectId' => $organizationObject->getId(), ] @@ -494,7 +494,7 @@ public function handleNewOrganization(object $organizationObject): void { if ($syncResult === true) { $this->_logger->info( - 'SoftwareCatalogueService: Successfully synced organization with OpenRegister', + 'StackiqService: Successfully synced organization with OpenRegister', [ 'objectId' => $organizationObject->getId(), ] @@ -504,7 +504,7 @@ public function handleNewOrganization(object $organizationObject): void { $this->updateOrganizationReferences(organizationObject: $organizationObject); } else { $this->_logger->warning( - 'SoftwareCatalogueService: Failed to sync organization with OpenRegister', + 'StackiqService: Failed to sync organization with OpenRegister', [ 'objectId' => $organizationObject->getId(), ] @@ -554,7 +554,7 @@ public function handleNewOrganization(object $organizationObject): void { $contactpersonen = $objectData['contactpersonen'] ?? []; if (empty($contactpersonen) === false) { $this->_logger->info( - 'SoftwareCatalogueService: Processing nested contact persons', + 'StackiqService: Processing nested contact persons', [ 'objectId' => $organizationObject->getId(), 'contactPersonCount' => count($contactpersonen), @@ -565,19 +565,19 @@ public function handleNewOrganization(object $organizationObject): void { $objectService = $this->getObjectService(); if (empty($objectService) === false) { - $settingsService = $this->_container->get('OCA\SoftwareCatalog\Service\SettingsService'); + $settingsService = $this->_container->get('OCA\Stackiq\Service\SettingsService'); $voorzieningenConfig = $settingsService->getVoorzieningenConfig(); $contactSchemaId = $voorzieningenConfig['contactpersoon_schema'] ?? null; if ($contactSchemaId === null) { - $this->_logger->warning('SoftwareCatalogueService: Missing contactpersoon schema configuration'); + $this->_logger->warning('StackiqService: Missing contactpersoon schema configuration'); return; } $organisationMapper = $this->getOrganisationMapper(); if ($organisationMapper === null) { $this->_logger->warning( - 'SoftwareCatalogueService: OpenRegister OrganisationMapper not available, skipping contact person membership' + 'StackiqService: OpenRegister OrganisationMapper not available, skipping contact person membership' ); return; } @@ -599,7 +599,7 @@ public function handleNewOrganization(object $organizationObject): void { $addedUsers[] = $email; $this->_logger->info( - 'SoftwareCatalogueService: Added nested contact person user to organization', + 'StackiqService: Added nested contact person user to organization', [ 'objectId' => $organizationObject->getId(), 'contactPersonId' => $contactPersonId, @@ -610,7 +610,7 @@ public function handleNewOrganization(object $organizationObject): void { } } catch (\Exception $e) { $this->_logger->warning( - 'SoftwareCatalogueService: Failed to process nested contact person', + 'StackiqService: Failed to process nested contact person', [ 'objectId' => $organizationObject->getId(), 'contactPersonId' => $contactPersonId, @@ -625,7 +625,7 @@ public function handleNewOrganization(object $organizationObject): void { $organisationMapper->save($organisation); $this->_logger->info( - 'SoftwareCatalogueService: Updated org with nested contact person users', + 'StackiqService: Updated org with nested contact person users', [ 'objectId' => $organizationObject->getId(), 'organizationUuid' => $organizationUuid, @@ -645,7 +645,7 @@ public function handleNewOrganization(object $organizationObject): void { $this->syncContactPersonUsernamesWithOrganization(organizationUuid: $organizationUuid); $this->_logger->info( - 'SoftwareCatalogueService: Completed final contact person synchronization for new organization', + 'StackiqService: Completed final contact person synchronization for new organization', [ 'objectId' => $organizationObject->getId(), 'organizationUuid' => $organizationUuid, @@ -653,7 +653,7 @@ public function handleNewOrganization(object $organizationObject): void { ); } catch (\Exception $e) { $this->_logger->error( - 'SoftwareCatalogueService: Failed to handle new organization: ' . $e->getMessage(), + 'StackiqService: Failed to handle new organization: ' . $e->getMessage(), [ 'objectId' => $organizationObject->getId(), 'exception' => $e->getMessage(), @@ -677,7 +677,7 @@ public function handleNewOrganization(object $organizationObject): void { public function handleOrganizationUpdate(object $organizationObject, object $oldOrganizationObject): void { try { $this->_logger->info( - 'SoftwareCatalogueService: Handling organization update', + 'StackiqService: Handling organization update', [ 'objectId' => $organizationObject->getId(), ] @@ -695,14 +695,14 @@ public function handleOrganizationUpdate(object $organizationObject, object $old if ($syncResult === true) { $this->_logger->info( - 'SoftwareCatalogueService: Successfully synced organization with OpenRegister', + 'StackiqService: Successfully synced organization with OpenRegister', [ 'objectId' => $organizationObject->getId(), ] ); } else { $this->_logger->warning( - 'SoftwareCatalogueService: Failed to sync organization with OpenRegister', + 'StackiqService: Failed to sync organization with OpenRegister', [ 'objectId' => $organizationObject->getId(), ] @@ -736,7 +736,7 @@ public function handleOrganizationUpdate(object $organizationObject, object $old $organizationUuid = $newData['id'] ?? $organizationObject->getId(); $this->_logger->info( - 'SoftwareCatalogueService: Organization became active - creating users from contactpersonen', + 'StackiqService: Organization became active - creating users from contactpersonen', [ 'organizationUuid' => $organizationUuid, ] @@ -747,8 +747,8 @@ public function handleOrganizationUpdate(object $organizationObject, object $old // and contactpersonen were added before activation. $this->processOrganization(organizationObject: $organizationObject); - // Activate SoftwareCatalog-specific users in this organization. - $this->activateSoftwareCatalogUsersForOrganization(organizationUuid: $organizationUuid); + // Activate Stackiq-specific users in this organization. + $this->activateStackiqUsersForOrganization(organizationUuid: $organizationUuid); // Send activation email. try { @@ -792,14 +792,14 @@ public function handleOrganizationUpdate(object $organizationObject, object $old ); if ($becameInactive === true) { - // Deactivate SoftwareCatalog-specific users in this organization. + // Deactivate Stackiq-specific users in this organization. $organizationUuid = $newData['id'] ?? $organizationObject->getId(); - $this->deactivateSoftwareCatalogUsersForOrganization(organizationUuid: $organizationUuid); + $this->deactivateStackiqUsersForOrganization(organizationUuid: $organizationUuid); } }//end if } catch (\Exception $e) { $this->_logger->error( - 'SoftwareCatalogueService: Failed to handle organization update: ' . $e->getMessage(), + 'StackiqService: Failed to handle organization update: ' . $e->getMessage(), [ 'objectId' => $organizationObject->getId(), 'exception' => $e->getMessage(), @@ -892,7 +892,7 @@ public function handleNewGebruiker(object $userObject): void { * * Its whole body was a `logger->info('Sending gebruiker welcome email')` * — it never sent anything — and no caller reached it: - * `SoftwareCatalogEventListener` does not invoke it on the + * `StackiqEventListener` does not invoke it on the * gebruiker-created path. Wiring a method that sends no mail would have * bought nothing; implementing one is a feature, not dead-code removal. */ @@ -1002,7 +1002,7 @@ public function restoreUserAccessForGebruiker(object $userObject): void { * `syncUserWithRevertedContact()` and `updateUserFromRevertedGebruiker()` * were the same shape as `sendGebruikerWelcomeEmail()` above: a single * `logger->info()` and nothing else, with no caller — - * `SoftwareCatalogEventListener` handles `ObjectRevertedEvent` without + * `StackiqEventListener` handles `ObjectRevertedEvent` without * touching this service. They named a capability (reconcile the Nextcloud * user after an object revert) that has never been implemented; a log line * is not that capability, and wiring one in would have made the gap @@ -1076,7 +1076,7 @@ public function handleContactpersoonUpdate(object $contactPersonObject, ?object try { $objectId = $contactPersonObject->getId(); $this->_logger->info( - 'SoftwareCatalogueService: Starting contactpersoon update handling', + 'StackiqService: Starting contactpersoon update handling', [ 'objectId' => $objectId, 'hasOldObject' => $oldContactPersonObject !== null, @@ -1095,7 +1095,7 @@ public function handleContactpersoonUpdate(object $contactPersonObject, ?object $oldRoles = $oldData['roles'] ?? []; $this->_logger->debug( - 'SoftwareCatalogueService: Comparing roles for contactpersoon update', + 'StackiqService: Comparing roles for contactpersoon update', [ 'objectId' => $objectId, 'newRoles' => $newRoles, @@ -1109,7 +1109,7 @@ public function handleContactpersoonUpdate(object $contactPersonObject, ?object if (is_array($newRoles) === false) { $newRoles = [$newRoles]; $this->_logger->debug( - 'SoftwareCatalogueService: Converted newRoles to array', + 'StackiqService: Converted newRoles to array', [ 'objectId' => $objectId, 'newRoles' => $newRoles, @@ -1120,7 +1120,7 @@ public function handleContactpersoonUpdate(object $contactPersonObject, ?object if (is_array($oldRoles) === false) { $oldRoles = [$oldRoles]; $this->_logger->debug( - 'SoftwareCatalogueService: Converted oldRoles to array', + 'StackiqService: Converted oldRoles to array', [ 'objectId' => $objectId, 'oldRoles' => $oldRoles, @@ -1132,7 +1132,7 @@ public function handleContactpersoonUpdate(object $contactPersonObject, ?object if ($newRoles !== $oldRoles) { // Roles changed - use role-based group assignment instead of generic group assignment. $this->_logger->info( - 'SoftwareCatalogueService: Roles changed for contactpersoon, using role-based group assignment', + 'StackiqService: Roles changed for contactpersoon, using role-based group assignment', [ 'contactpersoonId' => $objectId, 'oldRoles' => $oldRoles, @@ -1161,7 +1161,7 @@ public function handleContactpersoonUpdate(object $contactPersonObject, ?object $this->_contactPersonHandler->updateUserGroupsFromContactData($user, $contactData); $this->_logger->info( - 'SoftwareCatalogueService: Organization type-based group updates completed', + 'StackiqService: Organization type-based group updates completed', [ 'username' => $username, 'objectId' => $objectId, @@ -1170,7 +1170,7 @@ public function handleContactpersoonUpdate(object $contactPersonObject, ?object ); } else { $this->_logger->warning( - 'SoftwareCatalogueService: User not found for role-based group updates', + 'StackiqService: User not found for role-based group updates', [ 'username' => $username, 'objectId' => $objectId, @@ -1179,7 +1179,7 @@ public function handleContactpersoonUpdate(object $contactPersonObject, ?object }//end if } else { $this->_logger->warning( - 'SoftwareCatalogueService: No username available for role-based group updates', + 'StackiqService: No username available for role-based group updates', [ 'objectId' => $objectId, 'newData' => $newData, @@ -1189,7 +1189,7 @@ public function handleContactpersoonUpdate(object $contactPersonObject, ?object } else { // No role changes - use standard processing (assigns generic groups). $this->_logger->debug( - 'SoftwareCatalogueService: No role changes, using standard contactpersoon processing', + 'StackiqService: No role changes, using standard contactpersoon processing', [ 'objectId' => $objectId, 'roles' => $newRoles, @@ -1199,7 +1199,7 @@ public function handleContactpersoonUpdate(object $contactPersonObject, ?object $result = $this->processContactpersoon(contactPersonObject: $contactPersonObject, isUpdate: true); $this->_logger->info( - 'SoftwareCatalogueService: Standard contactpersoon processing completed', + 'StackiqService: Standard contactpersoon processing completed', [ 'objectId' => $objectId, 'result' => $result, @@ -1209,7 +1209,7 @@ public function handleContactpersoonUpdate(object $contactPersonObject, ?object }//end if $this->_logger->info( - 'SoftwareCatalogueService: Contactpersoon update handling completed', + 'StackiqService: Contactpersoon update handling completed', [ 'objectId' => $objectId, 'totalProcessingTime' => round((microtime(true) - $startTime) * 1000, 2) . 'ms', @@ -1217,7 +1217,7 @@ public function handleContactpersoonUpdate(object $contactPersonObject, ?object ); } catch (\Exception $e) { $this->_logger->error( - 'SoftwareCatalogueService: Failed to handle contactpersoon update: ' . $e->getMessage(), + 'StackiqService: Failed to handle contactpersoon update: ' . $e->getMessage(), [ 'objectId' => $contactPersonObject->getId(), 'exception' => $e->getMessage(), @@ -1241,7 +1241,7 @@ public function handleContactpersoonUpdate(object $contactPersonObject, ?object public function handleOrganizationDeletion(object $organizationObject): void { try { $this->_logger->info( - 'SoftwareCatalogueService: Handling organization deletion', + 'StackiqService: Handling organization deletion', [ 'objectId' => $organizationObject->getId(), ] @@ -1254,7 +1254,7 @@ public function handleOrganizationDeletion(object $organizationObject): void { $this->deactivateUsersForOrganization(organizationUuid: $organizationUuid); $this->_logger->info( - 'SoftwareCatalogueService: Successfully handled organization deletion', + 'StackiqService: Successfully handled organization deletion', [ 'organizationId' => $organizationUuid, 'timestamp' => date('Y-m-d H:i:s'), @@ -1262,7 +1262,7 @@ public function handleOrganizationDeletion(object $organizationObject): void { ); } catch (\Exception $e) { $this->_logger->error( - 'SoftwareCatalogueService: Failed to handle organization deletion: ' . $e->getMessage(), + 'StackiqService: Failed to handle organization deletion: ' . $e->getMessage(), [ 'objectId' => $organizationObject->getId(), 'exception' => $e->getMessage(), @@ -1285,7 +1285,7 @@ public function handleOrganizationDeletion(object $organizationObject): void { public function syncOrganizationWithOpenRegister(object $organizationObject): bool { try { $this->_logger->info( - 'SoftwareCatalogueService: SYNC_STEP_1 - Starting syncOrganizationWithOpenRegister', + 'StackiqService: SYNC_STEP_1 - Starting syncOrganizationWithOpenRegister', [ 'objectId' => $organizationObject->getId(), 'objectClass' => get_class($organizationObject), @@ -1296,7 +1296,7 @@ public function syncOrganizationWithOpenRegister(object $organizationObject): bo $organizationUuid = $objectData['id'] ?? $organizationObject->getId(); $this->_logger->info( - 'SoftwareCatalogueService: SYNC_STEP_2 - Extracted organization data', + 'StackiqService: SYNC_STEP_2 - Extracted organization data', [ 'organizationUuid' => $organizationUuid, 'objectDataKeys' => array_keys($objectData), @@ -1305,24 +1305,24 @@ public function syncOrganizationWithOpenRegister(object $organizationObject): bo ); // Get OpenRegister OrganisationService for proper organization entity management. - $this->_logger->info('SoftwareCatalogueService: SYNC_STEP_3 - Getting OrganisationService'); + $this->_logger->info('StackiqService: SYNC_STEP_3 - Getting OrganisationService'); $organisationService = $this->getOrganisationService(); if ($organisationService === null) { $this->_logger->error( - 'SoftwareCatalogueService: SYNC_STEP_3 - OpenRegister OrganisationService not available' + 'StackiqService: SYNC_STEP_3 - OpenRegister OrganisationService not available' ); return false; } $this->_logger->info( - 'SoftwareCatalogueService: SYNC_STEP_3 - OrganisationService retrieved', + 'StackiqService: SYNC_STEP_3 - OrganisationService retrieved', [ 'serviceClass' => get_class($organisationService), ] ); $this->_logger->info( - 'SoftwareCatalogueService: SYNC_STEP_4 - OpenRegister configuration', + 'StackiqService: SYNC_STEP_4 - OpenRegister configuration', [ 'organizationUuid' => $organizationUuid, 'organizationName' => $objectData['name'] ?? 'Unknown', @@ -1330,19 +1330,19 @@ public function syncOrganizationWithOpenRegister(object $organizationObject): bo ); // Check if organization already exists in OpenRegister. - $this->_logger->info('SoftwareCatalogueService: SYNC_STEP_5 - Checking if organization exists in OpenRegister'); + $this->_logger->info('StackiqService: SYNC_STEP_5 - Checking if organization exists in OpenRegister'); try { - $this->_logger->info('SoftwareCatalogueService: SYNC_STEP_5A - Getting OrganisationMapper for lookup'); + $this->_logger->info('StackiqService: SYNC_STEP_5A - Getting OrganisationMapper for lookup'); $organisationMapper = $this->getOrganisationMapper(); if ($organisationMapper === null) { $this->_logger->error( - 'SoftwareCatalogueService: OpenRegister OrganisationMapper not available, cannot sync organization' + 'StackiqService: OpenRegister OrganisationMapper not available, cannot sync organization' ); return false; } $this->_logger->info( - 'SoftwareCatalogueService: SYNC_STEP_5B - Calling findByUuid', + 'StackiqService: SYNC_STEP_5B - Calling findByUuid', [ 'uuid' => $organizationUuid, ] @@ -1351,15 +1351,15 @@ public function syncOrganizationWithOpenRegister(object $organizationObject): bo // Organization exists - update it. $this->_logger->info( - 'SoftwareCatalogueService: SYNC_STEP_6 - Organization exists in OpenRegister, updating', + 'StackiqService: SYNC_STEP_6 - Organization exists in OpenRegister, updating', [ 'organizationId' => $organizationUuid, 'existingOrganisationClass' => get_class($existingOrganisation), ] ); - // Map status from SoftwareCatalog to OpenRegister. - $this->_logger->info('SoftwareCatalogueService: SYNC_STEP_7 - Mapping organization data'); + // Map status from Stackiq to OpenRegister. + $this->_logger->info('StackiqService: SYNC_STEP_7 - Mapping organization data'); $mappedData = $this->mapOrganizationDataForOpenRegister(objectData: $objectData); // Update the organization using OrganisationService. @@ -1370,7 +1370,7 @@ public function syncOrganizationWithOpenRegister(object $organizationObject): bo ); $this->_logger->info( - 'SoftwareCatalogueService: Successfully updated organization in OpenRegister', + 'StackiqService: Successfully updated organization in OpenRegister', [ 'organizationId' => $organizationUuid, 'openRegisterId' => $updatedOrganisation->getUuid(), @@ -1381,19 +1381,19 @@ public function syncOrganizationWithOpenRegister(object $organizationObject): bo } catch (\OCP\AppFramework\Db\DoesNotExistException $e) { // Organization doesn't exist - create it. $this->_logger->info( - 'SoftwareCatalogueService: SYNC_STEP_8 - Organization not found in OpenRegister, creating', + 'StackiqService: SYNC_STEP_8 - Organization not found in OpenRegister, creating', [ 'organizationId' => $organizationUuid, 'exception' => $e->getMessage(), ] ); - // Map status from SoftwareCatalog to OpenRegister. - $this->_logger->info('SoftwareCatalogueService: SYNC_STEP_9 - Mapping organization data for creation'); + // Map status from Stackiq to OpenRegister. + $this->_logger->info('StackiqService: SYNC_STEP_9 - Mapping organization data for creation'); $mappedData = $this->mapOrganizationDataForOpenRegister(objectData: $objectData); // Create the organization using OrganisationService. - $this->_logger->info('SoftwareCatalogueService: SYNC_STEP_10 - Calling createOrganisationInOpenRegister'); + $this->_logger->info('StackiqService: SYNC_STEP_10 - Calling createOrganisationInOpenRegister'); $createdOrganisation = $this->createOrganisationInOpenRegisterInternal( organisationService: $organisationService, mappedData: $mappedData, @@ -1401,7 +1401,7 @@ public function syncOrganizationWithOpenRegister(object $organizationObject): bo ); $this->_logger->info( - 'SoftwareCatalogueService: SYNC_STEP_11 - Successfully created organization in OpenRegister', + 'StackiqService: SYNC_STEP_11 - Successfully created organization in OpenRegister', [ 'organizationId' => $organizationUuid, 'openRegisterId' => $createdOrganisation->getUuid(), @@ -1413,7 +1413,7 @@ public function syncOrganizationWithOpenRegister(object $organizationObject): bo }//end try } catch (\Exception $e) { $this->_logger->error( - 'SoftwareCatalogueService: Failed to sync organization with OpenRegister: ' . $e->getMessage(), + 'StackiqService: Failed to sync organization with OpenRegister: ' . $e->getMessage(), [ 'objectId' => $organizationObject->getId(), 'exception' => $e->getMessage(), @@ -1438,7 +1438,7 @@ public function createOrganisationInOpenRegister(array $objectData): ?object { try { $organizationUuid = $objectData['id'] ?? null; if ($organizationUuid === null) { - $this->_logger->error('SoftwareCatalogueService: No organization UUID provided for creation'); + $this->_logger->error('StackiqService: No organization UUID provided for creation'); return null; } @@ -1462,7 +1462,7 @@ public function createOrganisationInOpenRegister(array $objectData): ?object { ); } catch (\Exception $e) { $this->_logger->error( - 'SoftwareCatalogueService: Error in public createOrganisationInOpenRegister', + 'StackiqService: Error in public createOrganisationInOpenRegister', [ 'error' => $e->getMessage(), 'objectData' => $objectData, @@ -1506,7 +1506,7 @@ private function createOrganisationInOpenRegisterInternal( string $organizationUuid, ): \OCA\OpenRegister\Db\Organisation { $this->_logger->info( - 'SoftwareCatalogueService: STEP 1 - Starting createOrganisationInOpenRegister', + 'StackiqService: STEP 1 - Starting createOrganisationInOpenRegister', [ 'organizationUuid' => $organizationUuid, 'name' => $mappedData['name'] ?? 'Unknown', @@ -1524,7 +1524,7 @@ private function createOrganisationInOpenRegisterInternal( } $this->_logger->info( - 'SoftwareCatalogueService: STEP 2 - Checking user context', + 'StackiqService: STEP 2 - Checking user context', [ // Always true: $userSession is an injected, non-nullable IUserSession. 'hasUserSession' => true, @@ -1535,7 +1535,7 @@ private function createOrganisationInOpenRegisterInternal( if ($currentUser === null) { $this->_logger->info( - 'SoftwareCatalogueService: STEP 3A - Anonymous path: No user, creating org directly via mapper', + 'StackiqService: STEP 3A - Anonymous path: No user, creating org directly via mapper', [ 'organizationUuid' => $organizationUuid, ] @@ -1543,14 +1543,14 @@ private function createOrganisationInOpenRegisterInternal( // Keep the original UUID format - no conversion needed. $this->_logger->info( - 'SoftwareCatalogueService: STEP 3B - Using original UUID format for OpenRegister (anonymous)', + 'StackiqService: STEP 3B - Using original UUID format for OpenRegister (anonymous)', [ 'organizationUuid' => $organizationUuid, ] ); // Create organization directly via mapper to avoid user context requirements. - $this->_logger->info('SoftwareCatalogueService: STEP 3C - Getting OrganisationMapper from container'); + $this->_logger->info('StackiqService: STEP 3C - Getting OrganisationMapper from container'); $organisationMapper = $this->getOrganisationMapper(); if ($organisationMapper === null) { // This method's return type is non-nullable and its caller already @@ -1560,18 +1560,18 @@ private function createOrganisationInOpenRegisterInternal( } $this->_logger->info( - 'SoftwareCatalogueService: STEP 3D - OrganisationMapper retrieved', + 'StackiqService: STEP 3D - OrganisationMapper retrieved', [ 'mapperClass' => get_class($organisationMapper), ] ); // Create a new Organisation entity. - $this->_logger->info('SoftwareCatalogueService: STEP 3E - Creating new Organisation entity'); + $this->_logger->info('StackiqService: STEP 3E - Creating new Organisation entity'); $organisation = new \OCA\OpenRegister\Db\Organisation(); $this->_logger->info( - 'SoftwareCatalogueService: STEP 3F - Setting organisation properties', + 'StackiqService: STEP 3F - Setting organisation properties', [ 'name' => $mappedData['name'] ?? 'Unknown Organization', 'description' => $mappedData['website'] ?? '', @@ -1590,7 +1590,7 @@ private function createOrganisationInOpenRegisterInternal( $allUsernames = array_unique($allUsernames); $this->_logger->info( - 'SoftwareCatalogueService: STEP 3F_2 - Collected usernames for organization', + 'StackiqService: STEP 3F_2 - Collected usernames for organization', [ 'organizationUuid' => $organizationUuid, 'totalUsernames' => count($allUsernames), @@ -1609,7 +1609,7 @@ private function createOrganisationInOpenRegisterInternal( // Set active status based on organization beoordeling. // Debug: Check if UUID was set correctly. $this->_logger->info( - 'SoftwareCatalogueService: STEP 3G - Debug - UUID before save', + 'StackiqService: STEP 3G - Debug - UUID before save', [ 'setUuid' => $organizationUuid, 'getUuid' => $organisation->getUuid(), @@ -1619,15 +1619,15 @@ private function createOrganisationInOpenRegisterInternal( ); // Save the organization. - $this->_logger->info('SoftwareCatalogueService: STEP 3H - Calling organisationMapper->save()'); + $this->_logger->info('StackiqService: STEP 3H - Calling organisationMapper->save()'); try { $savedOrganisation = $organisationMapper->save($organisation); $this->_logger->info( - 'SoftwareCatalogueService: STEP 3I - organisationMapper->save() completed' + 'StackiqService: STEP 3I - organisationMapper->save() completed' ); } catch (\Exception $e) { $this->_logger->error( - 'SoftwareCatalogueService: STEP 3I - organisationMapper->save() failed', + 'StackiqService: STEP 3I - organisationMapper->save() failed', [ 'error' => $e->getMessage(), 'errorClass' => get_class($e), @@ -1638,7 +1638,7 @@ private function createOrganisationInOpenRegisterInternal( } $this->_logger->info( - 'SoftwareCatalogueService: Successfully created organization in OpenRegister via mapper', + 'StackiqService: Successfully created organization in OpenRegister via mapper', [ 'organizationUuid' => $organizationUuid, 'openRegisterId' => $savedOrganisation->getUuid(), @@ -1650,7 +1650,7 @@ private function createOrganisationInOpenRegisterInternal( // Verify the UUID was preserved. if ($savedOrganisation->getUuid() !== $organizationUuid) { $this->_logger->warning( - 'SoftwareCatalogueService: UUID mismatch after saving organization', + 'StackiqService: UUID mismatch after saving organization', [ 'expectedUuid' => $organizationUuid, 'actualUuid' => $savedOrganisation->getUuid(), @@ -1660,7 +1660,7 @@ private function createOrganisationInOpenRegisterInternal( }//end if $this->_logger->info( - 'SoftwareCatalogueService: STEP 4A - Auth path: User logged in, creating org via mapper', + 'StackiqService: STEP 4A - Auth path: User logged in, creating org via mapper', [ 'organizationUuid' => $organizationUuid, 'currentUser' => $currentUser->getUID(), @@ -1669,14 +1669,14 @@ private function createOrganisationInOpenRegisterInternal( // Keep the original UUID format - no conversion needed. $this->_logger->info( - 'SoftwareCatalogueService: STEP 4B - Using original UUID format for OpenRegister', + 'StackiqService: STEP 4B - Using original UUID format for OpenRegister', [ 'organizationUuid' => $organizationUuid, ] ); // Create organization directly via mapper to avoid service issues. - $this->_logger->info('SoftwareCatalogueService: STEP 4C - Getting OrganisationMapper from container'); + $this->_logger->info('StackiqService: STEP 4C - Getting OrganisationMapper from container'); $organisationMapper = $this->getOrganisationMapper(); if ($organisationMapper === null) { // Non-nullable return type, same reasoning as the anonymous branch above. @@ -1684,7 +1684,7 @@ private function createOrganisationInOpenRegisterInternal( } $this->_logger->info( - 'SoftwareCatalogueService: STEP 4D - OrganisationMapper retrieved', + 'StackiqService: STEP 4D - OrganisationMapper retrieved', [ 'mapperClass' => get_class($organisationMapper), ] @@ -1702,7 +1702,7 @@ private function createOrganisationInOpenRegisterInternal( $allUsernames = array_unique($allUsernames); $this->_logger->info( - 'SoftwareCatalogueService: STEP 4E - Debug - UUID before createWithUuid', + 'StackiqService: STEP 4E - Debug - UUID before createWithUuid', [ 'organizationUuid' => $organizationUuid, 'uuidLength' => strlen($organizationUuid), @@ -1715,11 +1715,11 @@ private function createOrganisationInOpenRegisterInternal( ] ); - $this->_logger->info('SoftwareCatalogueService: STEP 4F - Calling organisationMapper->createWithUuid()'); + $this->_logger->info('StackiqService: STEP 4F - Calling organisationMapper->createWithUuid()'); try { // Debug: Log the exact parameters being passed. $this->_logger->info( - 'SoftwareCatalogueService: STEP 4F_DEBUG - Parameters for createWithUuid', + 'StackiqService: STEP 4F_DEBUG - Parameters for createWithUuid', [ 'name' => $mappedData['name'] ?? 'Unknown Organization', 'description' => $mappedData['website'] ?? '', @@ -1746,11 +1746,11 @@ private function createOrganisationInOpenRegisterInternal( // Not default. ); $this->_logger->info( - 'SoftwareCatalogueService: STEP 4G - organisationMapper->createWithUuid() completed' + 'StackiqService: STEP 4G - organisationMapper->createWithUuid() completed' ); } catch (\Exception $e) { $this->_logger->error( - 'SoftwareCatalogueService: STEP 4G - organisationMapper->createWithUuid() failed', + 'StackiqService: STEP 4G - organisationMapper->createWithUuid() failed', [ 'error' => $e->getMessage(), 'errorClass' => get_class($e), @@ -1761,9 +1761,9 @@ private function createOrganisationInOpenRegisterInternal( }//end try // Note: OpenRegister Organisation entity doesn't have status or type fields. - // These are managed in the SoftwareCatalog object, not in the OpenRegister organisation. + // These are managed in the Stackiq object, not in the OpenRegister organisation. $this->_logger->info( - 'SoftwareCatalogueService: Successfully created organization in OpenRegister via service', + 'StackiqService: Successfully created organization in OpenRegister via service', [ 'organizationUuid' => $organizationUuid, 'openRegisterId' => $organisation->getUuid(), @@ -1790,7 +1790,7 @@ private function updateOrganisationInOpenRegister( array $mappedData, ): \OCA\OpenRegister\Db\Organisation { $this->_logger->info( - 'SoftwareCatalogueService: Updating organization in OpenRegister', + 'StackiqService: Updating organization in OpenRegister', [ 'organizationUuid' => $existingOrganisation->getUuid(), 'name' => $mappedData['name'] ?? 'Unknown', @@ -1807,7 +1807,7 @@ private function updateOrganisationInOpenRegister( } // Note: OpenRegister Organisation entity doesn't have status or type fields. - // These are managed in the SoftwareCatalog object, not in the OpenRegister organisation. + // These are managed in the Stackiq object, not in the OpenRegister organisation. // Save the updated organization. $organisationMapper = $this->getOrganisationMapper(); if ($organisationMapper === null) { @@ -1819,7 +1819,7 @@ private function updateOrganisationInOpenRegister( $updatedOrganisation = $organisationMapper->save($existingOrganisation); $this->_logger->info( - 'SoftwareCatalogueService: Successfully updated organization in OpenRegister', + 'StackiqService: Successfully updated organization in OpenRegister', [ 'organizationUuid' => $existingOrganisation->getUuid(), 'openRegisterId' => $updatedOrganisation->getUuid(), @@ -1844,7 +1844,7 @@ private function collectContactPersonUsernames(string $organizationUuid, array $ // These are available immediately when the organization is created. $nestedContactPersons = $objectData['contactpersonen'] ?? []; $this->_logger->info( - 'SoftwareCatalogueService: Processing nested contact persons', + 'StackiqService: Processing nested contact persons', [ 'organizationUuid' => $organizationUuid, 'nestedContactPersonCount' => count($nestedContactPersons), @@ -1855,7 +1855,7 @@ private function collectContactPersonUsernames(string $organizationUuid, array $ if (is_array($contactPerson) === true && isset($contactPerson['email']) === true) { $usernames[] = $contactPerson['email']; $this->_logger->info( - 'SoftwareCatalogueService: Added nested contact person username', + 'StackiqService: Added nested contact person username', [ 'username' => $contactPerson['email'], 'contactPersonData' => $contactPerson, @@ -1868,13 +1868,13 @@ private function collectContactPersonUsernames(string $organizationUuid, array $ // This is useful for updates or when contact persons were created separately. $objectService = $this->getObjectService(); if (empty($objectService) === false) { - $settingsService = $this->_container->get('OCA\SoftwareCatalog\Service\SettingsService'); + $settingsService = $this->_container->get('OCA\Stackiq\Service\SettingsService'); $voorzieningenConfig = $settingsService->getVoorzieningenConfig(); $contactSchemaId = $voorzieningenConfig['contactpersoon_schema'] ?? null; if ($contactSchemaId === null) { $this->_logger->warning( - 'SoftwareCatalogueService: Missing contactpersoon schema config for username extraction' + 'StackiqService: Missing contactpersoon schema config for username extraction' ); return $usernames; } @@ -1896,7 +1896,7 @@ private function collectContactPersonUsernames(string $organizationUuid, array $ ); } catch (\Exception $e) { $this->_logger->info( - 'SoftwareCatalogueService: Approach 1 failed, trying approach 2', + 'StackiqService: Approach 1 failed, trying approach 2', [ 'organizationUuid' => $organizationUuid, 'error' => $e->getMessage(), @@ -1925,7 +1925,7 @@ private function collectContactPersonUsernames(string $organizationUuid, array $ } } catch (\Exception $e) { $this->_logger->info( - 'SoftwareCatalogueService: Approach 2 also failed', + 'StackiqService: Approach 2 also failed', [ 'organizationUuid' => $organizationUuid, 'error' => $e->getMessage(), @@ -1935,7 +1935,7 @@ private function collectContactPersonUsernames(string $organizationUuid, array $ }//end if $this->_logger->info( - 'SoftwareCatalogueService: Found existing contact persons for organization', + 'StackiqService: Found existing contact persons for organization', [ 'organizationUuid' => $organizationUuid, 'contactPersonCount' => count($contactPersons), @@ -1954,7 +1954,7 @@ function ($cp) { if (empty($email) === false) { $usernames[] = $email; $this->_logger->info( - 'SoftwareCatalogueService: Added existing contact person username', + 'StackiqService: Added existing contact person username', [ 'username' => $email, 'contactPersonId' => $contactPerson->getId(), @@ -1964,7 +1964,7 @@ function ($cp) { } } catch (\Exception $e) { $this->_logger->error( - 'SoftwareCatalogueService: Error collecting existing contact person usernames', + 'StackiqService: Error collecting existing contact person usernames', [ 'organizationUuid' => $organizationUuid, 'error' => $e->getMessage(), @@ -1976,7 +1976,7 @@ function ($cp) { // Remove duplicates and return. $uniqueUsernames = array_unique($usernames); $this->_logger->info( - 'SoftwareCatalogueService: Collected contact person usernames', + 'StackiqService: Collected contact person usernames', [ 'organizationUuid' => $organizationUuid, 'totalUsernames' => count($uniqueUsernames), @@ -1988,9 +1988,9 @@ function ($cp) { }//end collectContactPersonUsernames() /** - * Maps organization data from SoftwareCatalog format to OpenRegister format + * Maps organization data from Stackiq format to OpenRegister format * - * @param array $objectData The organization data from SoftwareCatalog + * @param array $objectData The organization data from Stackiq * * @return array The mapped data for OpenRegister */ @@ -2005,7 +2005,7 @@ private function mapOrganizationDataForOpenRegister(array $objectData): array { 'participants' => [], ]; - // Map status from SoftwareCatalog to OpenRegister. + // Map status from Stackiq to OpenRegister. $assessment = strtolower($objectData['beoordeling'] ?? ''); if ($assessment === 'actief') { $mappedData['active'] = true; @@ -2047,7 +2047,7 @@ private function mapOrganizationDataForOpenRegister(array $objectData): array { private function activateUsersForOrganization(string $organizationUuid): void { try { $this->_logger->info( - 'SoftwareCatalogueService: Activating users for organization', + 'StackiqService: Activating users for organization', [ 'organizationUuid' => $organizationUuid, ] @@ -2055,7 +2055,7 @@ private function activateUsersForOrganization(string $organizationUuid): void { $objectService = $this->getObjectService(); if ($objectService === null) { - $this->_logger->error('SoftwareCatalogueService: OpenRegister ObjectService not available'); + $this->_logger->error('StackiqService: OpenRegister ObjectService not available'); return; } @@ -2097,7 +2097,7 @@ private function activateUsersForOrganization(string $organizationUuid): void { $activatedCount++; $this->_logger->info( - 'SoftwareCatalogueService: Activated user for organization', + 'StackiqService: Activated user for organization', [ 'username' => $username, 'organizationUuid' => $organizationUuid, @@ -2108,7 +2108,7 @@ private function activateUsersForOrganization(string $organizationUuid): void { } $this->_logger->info( - 'SoftwareCatalogueService: Completed user activation for organization', + 'StackiqService: Completed user activation for organization', [ 'organizationUuid' => $organizationUuid, 'totalContactpersonen' => count($contactpersonen), @@ -2117,7 +2117,7 @@ private function activateUsersForOrganization(string $organizationUuid): void { ); } catch (\Exception $e) { $this->_logger->error( - 'SoftwareCatalogueService: Failed to activate users for organization: ' . $e->getMessage(), + 'StackiqService: Failed to activate users for organization: ' . $e->getMessage(), [ 'organizationUuid' => $organizationUuid, 'exception' => $e->getMessage(), @@ -2139,7 +2139,7 @@ private function activateUsersForOrganization(string $organizationUuid): void { private function deactivateUsersForOrganization(string $organizationUuid): void { try { $this->_logger->info( - 'SoftwareCatalogueService: Deactivating users for organization', + 'StackiqService: Deactivating users for organization', [ 'organizationUuid' => $organizationUuid, ] @@ -2147,7 +2147,7 @@ private function deactivateUsersForOrganization(string $organizationUuid): void $objectService = $this->getObjectService(); if ($objectService === null) { - $this->_logger->error('SoftwareCatalogueService: OpenRegister ObjectService not available'); + $this->_logger->error('StackiqService: OpenRegister ObjectService not available'); return; } @@ -2189,7 +2189,7 @@ private function deactivateUsersForOrganization(string $organizationUuid): void $deactivatedCount++; $this->_logger->info( - 'SoftwareCatalogueService: Deactivated user for organization', + 'StackiqService: Deactivated user for organization', [ 'username' => $username, 'organizationUuid' => $organizationUuid, @@ -2200,7 +2200,7 @@ private function deactivateUsersForOrganization(string $organizationUuid): void } $this->_logger->info( - 'SoftwareCatalogueService: Completed user deactivation for organization', + 'StackiqService: Completed user deactivation for organization', [ 'organizationUuid' => $organizationUuid, 'totalContactpersonen' => count($contactpersonen), @@ -2209,7 +2209,7 @@ private function deactivateUsersForOrganization(string $organizationUuid): void ); } catch (\Exception $e) { $this->_logger->error( - 'SoftwareCatalogueService: Failed to deactivate users for organization: ' . $e->getMessage(), + 'StackiqService: Failed to deactivate users for organization: ' . $e->getMessage(), [ 'organizationUuid' => $organizationUuid, 'exception' => $e->getMessage(), @@ -2222,28 +2222,28 @@ private function deactivateUsersForOrganization(string $organizationUuid): void }//end deactivateUsersForOrganization() /** - * Activates SoftwareCatalog-specific users for an organization + * Activates Stackiq-specific users for an organization * Only affects users from contactpersoon objects, not admin group users * * @param string $organizationUuid The organization UUID * * @return void */ - private function activateSoftwareCatalogUsersForOrganization(string $organizationUuid): void { + private function activateStackiqUsersForOrganization(string $organizationUuid): void { try { $this->_logger->info( - 'SoftwareCatalogueService: Activating SoftwareCatalog users for organization', + 'StackiqService: Activating Stackiq users for organization', [ 'organizationUuid' => $organizationUuid, ] ); - // Get SoftwareCatalog-specific users (from contactpersonen). - $softwareCatalogUsers = $this->getSoftwareCatalogUsersForOrganization(organizationUuid: $organizationUuid); + // Get Stackiq-specific users (from contactpersonen). + $softwareCatalogUsers = $this->getStackiqUsersForOrganization(organizationUuid: $organizationUuid); if (empty($softwareCatalogUsers) === true) { $this->_logger->info( - 'SoftwareCatalogueService: No SoftwareCatalog users found for organization', + 'StackiqService: No Stackiq users found for organization', [ 'organizationUuid' => $organizationUuid, ] @@ -2252,7 +2252,7 @@ private function activateSoftwareCatalogUsersForOrganization(string $organizatio } $this->_logger->info( - 'SoftwareCatalogueService: Found SoftwareCatalog users to activate', + 'StackiqService: Found Stackiq users to activate', [ 'organizationUuid' => $organizationUuid, 'userCount' => count($softwareCatalogUsers), @@ -2272,7 +2272,7 @@ private function activateSoftwareCatalogUsersForOrganization(string $organizatio $user->setEnabled(true); $activatedUsers[] = $username; $this->_logger->debug( - 'SoftwareCatalogueService: Activated SoftwareCatalog user', + 'StackiqService: Activated Stackiq user', [ 'organizationUuid' => $organizationUuid, 'username' => $username, @@ -2280,7 +2280,7 @@ private function activateSoftwareCatalogUsersForOrganization(string $organizatio ); } elseif ($user !== false && $user->isEnabled() === true) { $this->_logger->debug( - 'SoftwareCatalogueService: SoftwareCatalog user already active', + 'StackiqService: Stackiq user already active', [ 'organizationUuid' => $organizationUuid, 'username' => $username, @@ -2289,7 +2289,7 @@ private function activateSoftwareCatalogUsersForOrganization(string $organizatio } else { $failedUsers[] = $username; $this->_logger->warning( - 'SoftwareCatalogueService: SoftwareCatalog user not found', + 'StackiqService: Stackiq user not found', [ 'organizationUuid' => $organizationUuid, 'username' => $username, @@ -2299,7 +2299,7 @@ private function activateSoftwareCatalogUsersForOrganization(string $organizatio } catch (\Exception $e) { $failedUsers[] = $username; $this->_logger->error( - 'SoftwareCatalogueService: Failed to activate SoftwareCatalog user', + 'StackiqService: Failed to activate Stackiq user', [ 'organizationUuid' => $organizationUuid, 'username' => $username, @@ -2310,7 +2310,7 @@ private function activateSoftwareCatalogUsersForOrganization(string $organizatio }//end foreach $this->_logger->info( - 'SoftwareCatalogueService: SoftwareCatalog user activation complete', + 'StackiqService: Stackiq user activation complete', [ 'organizationUuid' => $organizationUuid, 'activatedUsers' => $activatedUsers, @@ -2320,38 +2320,38 @@ private function activateSoftwareCatalogUsersForOrganization(string $organizatio ); } catch (\Exception $e) { $this->_logger->error( - 'SoftwareCatalogueService: Error activating SoftwareCatalog users for organization', + 'StackiqService: Error activating Stackiq users for organization', [ 'organizationUuid' => $organizationUuid, 'error' => $e->getMessage(), ] ); }//end try - }//end activateSoftwareCatalogUsersForOrganization() + }//end activateStackiqUsersForOrganization() /** - * Deactivates SoftwareCatalog-specific users for an organization + * Deactivates Stackiq-specific users for an organization * Only affects users from contactpersoon objects, not admin group users * * @param string $organizationUuid The organization UUID * * @return void */ - private function deactivateSoftwareCatalogUsersForOrganization(string $organizationUuid): void { + private function deactivateStackiqUsersForOrganization(string $organizationUuid): void { try { $this->_logger->info( - 'SoftwareCatalogueService: Deactivating SoftwareCatalog users for organization', + 'StackiqService: Deactivating Stackiq users for organization', [ 'organizationUuid' => $organizationUuid, ] ); - // Get SoftwareCatalog-specific users (from contactpersonen). - $softwareCatalogUsers = $this->getSoftwareCatalogUsersForOrganization(organizationUuid: $organizationUuid); + // Get Stackiq-specific users (from contactpersonen). + $softwareCatalogUsers = $this->getStackiqUsersForOrganization(organizationUuid: $organizationUuid); if (empty($softwareCatalogUsers) === true) { $this->_logger->info( - 'SoftwareCatalogueService: No SoftwareCatalog users found for organization', + 'StackiqService: No Stackiq users found for organization', [ 'organizationUuid' => $organizationUuid, ] @@ -2360,7 +2360,7 @@ private function deactivateSoftwareCatalogUsersForOrganization(string $organizat } $this->_logger->info( - 'SoftwareCatalogueService: Found SoftwareCatalog users to deactivate', + 'StackiqService: Found Stackiq users to deactivate', [ 'organizationUuid' => $organizationUuid, 'userCount' => count($softwareCatalogUsers), @@ -2380,7 +2380,7 @@ private function deactivateSoftwareCatalogUsersForOrganization(string $organizat $user->setEnabled(false); $deactivatedUsers[] = $username; $this->_logger->debug( - 'SoftwareCatalogueService: Deactivated SoftwareCatalog user', + 'StackiqService: Deactivated Stackiq user', [ 'organizationUuid' => $organizationUuid, 'username' => $username, @@ -2388,7 +2388,7 @@ private function deactivateSoftwareCatalogUsersForOrganization(string $organizat ); } elseif ($user !== false && $user->isEnabled() === false) { $this->_logger->debug( - 'SoftwareCatalogueService: SoftwareCatalog user already inactive', + 'StackiqService: Stackiq user already inactive', [ 'organizationUuid' => $organizationUuid, 'username' => $username, @@ -2397,7 +2397,7 @@ private function deactivateSoftwareCatalogUsersForOrganization(string $organizat } else { $failedUsers[] = $username; $this->_logger->warning( - 'SoftwareCatalogueService: SoftwareCatalog user not found', + 'StackiqService: Stackiq user not found', [ 'organizationUuid' => $organizationUuid, 'username' => $username, @@ -2407,7 +2407,7 @@ private function deactivateSoftwareCatalogUsersForOrganization(string $organizat } catch (\Exception $e) { $failedUsers[] = $username; $this->_logger->error( - 'SoftwareCatalogueService: Failed to deactivate SoftwareCatalog user', + 'StackiqService: Failed to deactivate Stackiq user', [ 'organizationUuid' => $organizationUuid, 'username' => $username, @@ -2418,7 +2418,7 @@ private function deactivateSoftwareCatalogUsersForOrganization(string $organizat }//end foreach $this->_logger->info( - 'SoftwareCatalogueService: SoftwareCatalog user deactivation complete', + 'StackiqService: Stackiq user deactivation complete', [ 'organizationUuid' => $organizationUuid, 'deactivatedUsers' => $deactivatedUsers, @@ -2428,27 +2428,27 @@ private function deactivateSoftwareCatalogUsersForOrganization(string $organizat ); } catch (\Exception $e) { $this->_logger->error( - 'SoftwareCatalogueService: Error deactivating SoftwareCatalog users for organization', + 'StackiqService: Error deactivating Stackiq users for organization', [ 'organizationUuid' => $organizationUuid, 'error' => $e->getMessage(), ] ); }//end try - }//end deactivateSoftwareCatalogUsersForOrganization() + }//end deactivateStackiqUsersForOrganization() /** - * Gets SoftwareCatalog-specific users for an organization + * Gets Stackiq-specific users for an organization * These are users from contactpersoon objects, excluding admin group users * * @param string $organizationUuid The organization UUID * * @return array Array of usernames */ - private function getSoftwareCatalogUsersForOrganization(string $organizationUuid): array { + private function getStackiqUsersForOrganization(string $organizationUuid): array { try { $this->_logger->debug( - 'SoftwareCatalogueService: Getting SoftwareCatalog users for organization', + 'StackiqService: Getting Stackiq users for organization', [ 'organizationUuid' => $organizationUuid, ] @@ -2458,7 +2458,7 @@ private function getSoftwareCatalogUsersForOrganization(string $organizationUuid $objectService = $this->getObjectService(); if ($objectService === null) { $this->_logger->error( - 'SoftwareCatalogueService: ObjectService not available for getting SoftwareCatalog users' + 'StackiqService: ObjectService not available for getting Stackiq users' ); return []; } @@ -2491,7 +2491,7 @@ private function getSoftwareCatalogUsersForOrganization(string $organizationUuid if ($username !== false && in_array($username, $adminGroupUsers) === false) { $softwareCatalogUsers[] = $username; $this->_logger->debug( - 'SoftwareCatalogueService: Found SoftwareCatalog user', + 'StackiqService: Found Stackiq user', [ 'organizationUuid' => $organizationUuid, 'username' => $username, @@ -2503,7 +2503,7 @@ private function getSoftwareCatalogUsersForOrganization(string $organizationUuid }//end foreach $this->_logger->info( - 'SoftwareCatalogueService: Found SoftwareCatalog users for organization', + 'StackiqService: Found Stackiq users for organization', [ 'organizationUuid' => $organizationUuid, 'userCount' => count($softwareCatalogUsers), @@ -2514,7 +2514,7 @@ private function getSoftwareCatalogUsersForOrganization(string $organizationUuid return $softwareCatalogUsers; } catch (\Exception $e) { $this->_logger->error( - 'SoftwareCatalogueService: Error getting SoftwareCatalog users for organization', + 'StackiqService: Error getting Stackiq users for organization', [ 'organizationUuid' => $organizationUuid, 'error' => $e->getMessage(), @@ -2522,7 +2522,7 @@ private function getSoftwareCatalogUsersForOrganization(string $organizationUuid ); return []; }//end try - }//end getSoftwareCatalogUsersForOrganization() + }//end getStackiqUsersForOrganization() /** * Gets all usernames from the admin group @@ -2535,7 +2535,7 @@ private function getAdminGroupUsernames(): array { $adminGroup = $groupManager->get('admin'); if ($adminGroup === null) { - $this->_logger->warning('SoftwareCatalogueService: Admin group not found'); + $this->_logger->warning('StackiqService: Admin group not found'); return []; } @@ -2547,7 +2547,7 @@ private function getAdminGroupUsernames(): array { } $this->_logger->debug( - 'SoftwareCatalogueService: Found admin group users', + 'StackiqService: Found admin group users', [ 'adminUserCount' => count($adminUsernames), 'adminUsers' => $adminUsernames, @@ -2557,7 +2557,7 @@ private function getAdminGroupUsernames(): array { return $adminUsernames; } catch (\Exception $e) { $this->_logger->error( - 'SoftwareCatalogueService: Error getting admin group usernames', + 'StackiqService: Error getting admin group usernames', [ 'error' => $e->getMessage(), ] @@ -2576,7 +2576,7 @@ private function getAdminGroupUsernames(): array { private function addAdminGroupUsersToOrganization(string $organizationUuid): void { try { $this->_logger->info( - 'SoftwareCatalogueService: Adding admin group users to organization entity', + 'StackiqService: Adding admin group users to organization entity', [ 'organizationUuid' => $organizationUuid, ] @@ -2587,13 +2587,13 @@ private function addAdminGroupUsersToOrganization(string $organizationUuid): voi $adminGroup = $groupManager->get('admin'); if ($adminGroup === null) { - $this->_logger->warning('SoftwareCatalogueService: Admin group not found'); + $this->_logger->warning('StackiqService: Admin group not found'); return; } $adminUsers = $adminGroup->getUsers(); $this->_logger->info( - 'SoftwareCatalogueService: Found admin group users', + 'StackiqService: Found admin group users', [ 'organizationUuid' => $organizationUuid, 'adminUserCount' => count($adminUsers), @@ -2603,13 +2603,13 @@ private function addAdminGroupUsersToOrganization(string $organizationUuid): voi // Get the organization entity (not object) to update its users list. $organisationMapper = $this->_container->get('OCA\\OpenRegister\\Db\\OrganisationMapper'); if ($organisationMapper === null) { - $this->_logger->error('SoftwareCatalogueService: OrganisationMapper not available for adding admin users'); + $this->_logger->error('StackiqService: OrganisationMapper not available for adding admin users'); return; } // Find the organization entity by UUID. $this->_logger->info( - 'SoftwareCatalogueService: Searching for organization entity', + 'StackiqService: Searching for organization entity', [ 'organizationUuid' => $organizationUuid, ] @@ -2619,7 +2619,7 @@ private function addAdminGroupUsersToOrganization(string $organizationUuid): voi $targetOrganisation = $organisationMapper->findByUuid($organizationUuid); $this->_logger->info( - 'SoftwareCatalogueService: Found target organization entity', + 'StackiqService: Found target organization entity', [ 'organizationUuid' => $organizationUuid, 'entityId' => $targetOrganisation->getId(), @@ -2627,7 +2627,7 @@ private function addAdminGroupUsersToOrganization(string $organizationUuid): voi ); } catch (\OCP\AppFramework\Db\DoesNotExistException $e) { $this->_logger->warning( - 'SoftwareCatalogueService: Organization entity not found for adding admin users', + 'StackiqService: Organization entity not found for adding admin users', [ 'organizationUuid' => $organizationUuid, ] @@ -2639,7 +2639,7 @@ private function addAdminGroupUsersToOrganization(string $organizationUuid): voi $currentUsers = $targetOrganisation->getUsers() ?? []; $this->_logger->info( - 'SoftwareCatalogueService: Current organization entity users', + 'StackiqService: Current organization entity users', [ 'organizationUuid' => $organizationUuid, 'currentUsers' => $currentUsers, @@ -2656,7 +2656,7 @@ private function addAdminGroupUsersToOrganization(string $organizationUuid): voi $updatedUsers[] = $adminUsername; $addedUsers[] = $adminUsername; $this->_logger->debug( - 'SoftwareCatalogueService: Added admin user to organization entity', + 'StackiqService: Added admin user to organization entity', [ 'organizationUuid' => $organizationUuid, 'adminUsername' => $adminUsername, @@ -2666,7 +2666,7 @@ private function addAdminGroupUsersToOrganization(string $organizationUuid): voi } $this->_logger->info( - 'SoftwareCatalogueService: Admin users processing complete', + 'StackiqService: Admin users processing complete', [ 'organizationUuid' => $organizationUuid, 'addedUsers' => $addedUsers, @@ -2677,7 +2677,7 @@ private function addAdminGroupUsersToOrganization(string $organizationUuid): voi // Update the organization entity with the new users list. if (count($updatedUsers) > count($currentUsers)) { $this->_logger->info( - 'SoftwareCatalogueService: Updating organization entity with new users', + 'StackiqService: Updating organization entity with new users', [ 'organizationUuid' => $organizationUuid, 'entityId' => $targetOrganisation->getId(), @@ -2689,7 +2689,7 @@ private function addAdminGroupUsersToOrganization(string $organizationUuid): voi $targetOrganisation->setUsers($updatedUsers); $this->_logger->info( - 'SoftwareCatalogueService: Saving updated organization entity', + 'StackiqService: Saving updated organization entity', [ 'organizationUuid' => $organizationUuid, 'entityId' => $targetOrganisation->getId(), @@ -2701,7 +2701,7 @@ private function addAdminGroupUsersToOrganization(string $organizationUuid): voi $savedOrganisation = $organisationMapper->save($targetOrganisation); $this->_logger->info( - 'SoftwareCatalogueService: Successfully added admin users to organization entity', + 'StackiqService: Successfully added admin users to organization entity', [ 'organizationUuid' => $organizationUuid, 'addedUsers' => count($updatedUsers) - count($currentUsers), @@ -2710,7 +2710,7 @@ private function addAdminGroupUsersToOrganization(string $organizationUuid): voi ); } else { $this->_logger->info( - 'SoftwareCatalogueService: All admin users already in organization entity', + 'StackiqService: All admin users already in organization entity', [ 'organizationUuid' => $organizationUuid, 'totalUsers' => count($updatedUsers), @@ -2719,7 +2719,7 @@ private function addAdminGroupUsersToOrganization(string $organizationUuid): voi }//end if } catch (\Exception $e) { $this->_logger->error( - 'SoftwareCatalogueService: Failed to add admin users to organization entity: ' . $e->getMessage(), + 'StackiqService: Failed to add admin users to organization entity: ' . $e->getMessage(), [ 'organizationUuid' => $organizationUuid, 'exception' => $e, @@ -2769,7 +2769,7 @@ public function shouldAddContactpersoonToOrganization(object $contactPersonObjec if (is_array($organizationUsers) === true && in_array($username, $organizationUsers) === false) { $this->_logger->info( - 'SoftwareCatalogueService: Contactpersoon should be added to organization', + 'StackiqService: Contactpersoon should be added to organization', [ 'username' => $username, 'organizationUuid' => $organizationUuid, @@ -2783,7 +2783,7 @@ public function shouldAddContactpersoonToOrganization(object $contactPersonObjec } catch (\OCP\AppFramework\Db\DoesNotExistException $e) { // Organization doesn't exist, so we can't add the user. $this->_logger->warning( - 'SoftwareCatalogueService: Organization not found for contactpersoon', + 'StackiqService: Organization not found for contactpersoon', [ 'username' => $username, 'organizationUuid' => $organizationUuid, @@ -2793,7 +2793,7 @@ public function shouldAddContactpersoonToOrganization(object $contactPersonObjec }//end try } catch (\Exception $e) { $this->_logger->error( - 'SoftwareCatalogueService: Failed to check contactpersoon addition to org: ' . $e->getMessage(), + 'StackiqService: Failed to check contactpersoon addition to org: ' . $e->getMessage(), [ 'objectId' => $contactPersonObject->getId(), 'exception' => $e->getMessage(), @@ -2819,7 +2819,7 @@ public function addContactpersoonToOrganization(object $contactPersonObject): bo if (empty($username) === true || empty($organizationUuid) === true) { $this->_logger->warning( - 'SoftwareCatalogueService: Cannot add contactpersoon to org - missing username or org', + 'StackiqService: Cannot add contactpersoon to org - missing username or org', [ 'username' => $username, 'organizationUuid' => $organizationUuid, @@ -2830,7 +2830,7 @@ public function addContactpersoonToOrganization(object $contactPersonObject): bo $objectService = $this->getObjectService(); if ($objectService === null) { - $this->_logger->error('SoftwareCatalogueService: OpenRegister ObjectService not available'); + $this->_logger->error('StackiqService: OpenRegister ObjectService not available'); return false; } @@ -2867,7 +2867,7 @@ public function addContactpersoonToOrganization(object $contactPersonObject): bo ); $this->_logger->info( - 'SoftwareCatalogueService: Successfully added contactpersoon to organization', + 'StackiqService: Successfully added contactpersoon to organization', [ 'username' => $username, 'organizationUuid' => $organizationUuid, @@ -2877,7 +2877,7 @@ public function addContactpersoonToOrganization(object $contactPersonObject): bo }//end if $this->_logger->debug( - 'SoftwareCatalogueService: Contactpersoon already in organization', + 'StackiqService: Contactpersoon already in organization', [ 'username' => $username, 'organizationUuid' => $organizationUuid, @@ -2887,7 +2887,7 @@ public function addContactpersoonToOrganization(object $contactPersonObject): bo // Already there, consider it successful. } catch (\OCP\AppFramework\Db\DoesNotExistException $e) { $this->_logger->error( - 'SoftwareCatalogueService: Organization not found for contactpersoon', + 'StackiqService: Organization not found for contactpersoon', [ 'username' => $username, 'organizationUuid' => $organizationUuid, @@ -2897,7 +2897,7 @@ public function addContactpersoonToOrganization(object $contactPersonObject): bo }//end try } catch (\Exception $e) { $this->_logger->error( - 'SoftwareCatalogueService: Failed to add contact person to organization: ' . $e->getMessage(), + 'StackiqService: Failed to add contact person to organization: ' . $e->getMessage(), [ 'objectId' => $contactPersonObject->getId(), 'exception' => $e->getMessage(), @@ -2920,7 +2920,7 @@ public function addContactpersoonToOrganization(object $contactPersonObject): bo private function handleOwnershipAssignment(object $organizationObject): void { try { $this->_logger->info( - 'SoftwareCatalogueService: Handling ownership assignment for organization', + 'StackiqService: Handling ownership assignment for organization', [ 'objectId' => $organizationObject->getId(), ] @@ -2932,7 +2932,7 @@ private function handleOwnershipAssignment(object $organizationObject): void { if (empty($contactpersonen) === true) { $this->_logger->info( - 'SoftwareCatalogueService: No contact persons found for ownership assignment', + 'StackiqService: No contact persons found for ownership assignment', [ 'organizationUuid' => $organizationUuid, ] @@ -2946,7 +2946,7 @@ private function handleOwnershipAssignment(object $organizationObject): void { $objectService = $this->getObjectService(); if ($objectService === null) { $this->_logger->error( - 'SoftwareCatalogueService: OpenRegister ObjectService not available for ownership assignment' + 'StackiqService: OpenRegister ObjectService not available for ownership assignment' ); return; } @@ -2959,7 +2959,7 @@ private function handleOwnershipAssignment(object $organizationObject): void { if ($registerId === null || $contactPersonSchemaId === null || $organisationSchemaId === false) { $this->_logger->error( - 'SoftwareCatalogueService: Register or schema not configured for contactpersoon/organisatie' + 'StackiqService: Register or schema not configured for contactpersoon/organisatie' ); return; } @@ -2983,7 +2983,7 @@ private function handleOwnershipAssignment(object $organizationObject): void { if (empty($primaryUsername) === true) { if ($retry < $maxRetries - 1) { $this->_logger->info( - 'SoftwareCatalogueService: Primary contact no username, retry in ' . $retryDelay . 's', + 'StackiqService: Primary contact no username, retry in ' . $retryDelay . 's', [ 'contactUuid' => $primaryContactUuid, 'organizationUuid' => $organizationUuid, @@ -2995,7 +2995,7 @@ private function handleOwnershipAssignment(object $organizationObject): void { } $this->_logger->warning( - 'SoftwareCatalogueService: Primary contact person still has no username after retries', + 'StackiqService: Primary contact person still has no username after retries', [ 'contactUuid' => $primaryContactUuid, 'organizationUuid' => $organizationUuid, @@ -3030,7 +3030,7 @@ private function handleOwnershipAssignment(object $organizationObject): void { } } catch (\Exception $e) { $this->_logger->warning( - 'SoftwareCatalogueService: Failed to add contact person to organization entity', + 'StackiqService: Failed to add contact person to organization entity', [ 'contactUuid' => $contactUuid, 'error' => $e->getMessage(), @@ -3043,7 +3043,7 @@ private function handleOwnershipAssignment(object $organizationObject): void { $organisationMapper->save($organisationEntity); $this->_logger->info( - 'SoftwareCatalogueService: Successfully added users to organization entity', + 'StackiqService: Successfully added users to organization entity', [ 'organizationUuid' => $organisationEntityUuid, 'userCount' => count($organisationEntity->getUserIds()), @@ -3051,7 +3051,7 @@ private function handleOwnershipAssignment(object $organizationObject): void { ); } catch (\OCP\AppFramework\Db\DoesNotExistException $e) { $this->_logger->error( - 'SoftwareCatalogueService: Organization entity not found for adding users', + 'StackiqService: Organization entity not found for adding users', [ 'organizationUuid' => $organisationEntityUuid, ] @@ -3110,7 +3110,7 @@ private function handleOwnershipAssignment(object $organizationObject): void { } } catch (\Exception $e) { $this->_logger->warning( - 'SoftwareCatalogueService: Failed to update contact person ownership', + 'StackiqService: Failed to update contact person ownership', [ 'contactUuid' => $contactUuid, 'error' => $e->getMessage(), @@ -3120,7 +3120,7 @@ private function handleOwnershipAssignment(object $organizationObject): void { }//end for $this->_logger->info( - 'SoftwareCatalogueService: Successfully assigned ownership for organization', + 'StackiqService: Successfully assigned ownership for organization', [ 'organizationUuid' => $organizationUuid, 'primaryOwner' => $primaryUsername, @@ -3135,7 +3135,7 @@ private function handleOwnershipAssignment(object $organizationObject): void { } catch (\OCP\AppFramework\Db\DoesNotExistException $e) { if ($retry < $maxRetries - 1) { $this->_logger->info( - 'SoftwareCatalogueService: Primary contact not found, retrying in ' . $retryDelay . ' seconds', + 'StackiqService: Primary contact not found, retrying in ' . $retryDelay . ' seconds', [ 'contactUuid' => $primaryContactUuid, 'organizationUuid' => $organizationUuid, @@ -3147,7 +3147,7 @@ private function handleOwnershipAssignment(object $organizationObject): void { } $this->_logger->error( - 'SoftwareCatalogueService: Primary contact person not found after retries', + 'StackiqService: Primary contact person not found after retries', [ 'contactUuid' => $primaryContactUuid, 'organizationUuid' => $organizationUuid, @@ -3158,7 +3158,7 @@ private function handleOwnershipAssignment(object $organizationObject): void { }//end for } catch (\Exception $e) { $this->_logger->error( - 'SoftwareCatalogueService: Error handling ownership assignment', + 'StackiqService: Error handling ownership assignment', [ 'objectId' => $organizationObject->getId(), 'error' => $e->getMessage(), @@ -3181,7 +3181,7 @@ private function handleOwnershipAssignment(object $organizationObject): void { */ public function syncContactPersonUsernamesWithOrganization(string $organizationUuid): void { $this->_logger->info( - 'SoftwareCatalogueService: Starting contact person username synchronization', + 'StackiqService: Starting contact person username synchronization', [ 'organizationUuid' => $organizationUuid, ] @@ -3190,19 +3190,19 @@ public function syncContactPersonUsernamesWithOrganization(string $organizationU // Get the ObjectService to find contact persons. $objectService = $this->getObjectService(); if ($objectService === null) { - $this->_logger->error('SoftwareCatalogueService: ObjectService not available for username synchronization'); + $this->_logger->error('StackiqService: ObjectService not available for username synchronization'); return; } // Get the contact person schema ID from configuration. - $settingsService = $this->_container->get('OCA\SoftwareCatalog\Service\SettingsService'); + $settingsService = $this->_container->get('OCA\Stackiq\Service\SettingsService'); $voorzieningenConfig = $settingsService->getVoorzieningenConfig(); $contactSchemaId = $voorzieningenConfig['contactpersoon_schema'] ?? null; $registerId = $voorzieningenConfig['register'] ?? null; if ($contactSchemaId === null || $registerId === false) { $this->_logger->warning( - 'SoftwareCatalogueService: Missing Voorzieningen configuration for contact person sync', + 'StackiqService: Missing Voorzieningen configuration for contact person sync', [ 'organizationUuid' => $organizationUuid, 'contactSchemaId' => $contactSchemaId, @@ -3225,7 +3225,7 @@ public function syncContactPersonUsernamesWithOrganization(string $organizationU ); $this->_logger->info( - 'SoftwareCatalogueService: Found contact persons for synchronization', + 'StackiqService: Found contact persons for synchronization', [ 'organizationUuid' => $organizationUuid, 'contactPersonCount' => count($contactPersons), @@ -3240,7 +3240,7 @@ public function syncContactPersonUsernamesWithOrganization(string $organizationU if (empty($email) === false) { $contactPersonUsernames[] = $email; $this->_logger->info( - 'SoftwareCatalogueService: Found contact person username', + 'StackiqService: Found contact person username', [ 'username' => $email, 'contactPersonId' => $contactPerson->getId(), @@ -3253,7 +3253,7 @@ public function syncContactPersonUsernamesWithOrganization(string $organizationU $organisationMapper = $this->getOrganisationMapper(); if ($organisationMapper === null) { $this->_logger->error( - 'SoftwareCatalogueService: OpenRegister OrganisationMapper not available for synchronization', + 'StackiqService: OpenRegister OrganisationMapper not available for synchronization', [ 'organizationUuid' => $organizationUuid, ] @@ -3265,7 +3265,7 @@ public function syncContactPersonUsernamesWithOrganization(string $organizationU if ($organisation === null) { $this->_logger->error( - 'SoftwareCatalogueService: Organization entity not found for synchronization', + 'StackiqService: Organization entity not found for synchronization', [ 'organizationUuid' => $organizationUuid, ] @@ -3279,7 +3279,7 @@ public function syncContactPersonUsernamesWithOrganization(string $organizationU $allUsers = array_unique($allUsers); $this->_logger->info( - 'SoftwareCatalogueService: Updating organization entity users', + 'StackiqService: Updating organization entity users', [ 'organizationUuid' => $organizationUuid, 'currentUsers' => $currentUsers, @@ -3293,7 +3293,7 @@ public function syncContactPersonUsernamesWithOrganization(string $organizationU $organisationMapper->save($organisation); $this->_logger->info( - 'SoftwareCatalogueService: Successfully synchronized contact person usernames', + 'StackiqService: Successfully synchronized contact person usernames', [ 'organizationUuid' => $organizationUuid, 'totalUsers' => count($allUsers), @@ -3303,7 +3303,7 @@ public function syncContactPersonUsernamesWithOrganization(string $organizationU // Organization entity doesn't exist yet - this can happen due to race conditions. // Log and return gracefully, the organization sync will handle this later. $this->_logger->warning( - 'SoftwareCatalogueService: Organization entity not found during username sync (race condition)', + 'StackiqService: Organization entity not found during username sync (race condition)', [ 'organizationUuid' => $organizationUuid, 'message' => 'Expected during anonymous registration - org entity created after contacts', @@ -3311,7 +3311,7 @@ public function syncContactPersonUsernamesWithOrganization(string $organizationU ); } catch (\Exception $e) { $this->_logger->error( - 'SoftwareCatalogueService: Error synchronizing contact person usernames', + 'StackiqService: Error synchronizing contact person usernames', [ 'organizationUuid' => $organizationUuid, 'error' => $e->getMessage(), @@ -3339,7 +3339,7 @@ private function ensureContactPersonInOrganization(object $contactPersonObject): if ($email === null || $organization === false) { $this->_logger->info( - 'SoftwareCatalogueService: Contact person missing email or organisation', + 'StackiqService: Contact person missing email or organisation', [ 'contactPersonId' => $contactPersonObject->getId(), 'hasEmail' => empty($email) === false, @@ -3353,7 +3353,7 @@ private function ensureContactPersonInOrganization(object $contactPersonObject): $owner = $contactPersonObject->getOwner(); if ($owner === 'system') { $this->_logger->info( - 'SoftwareCatalogueService: Skipping contact person owned by system', + 'StackiqService: Skipping contact person owned by system', [ 'contactPersonId' => $contactPersonObject->getId(), 'username' => $email, @@ -3363,7 +3363,7 @@ private function ensureContactPersonInOrganization(object $contactPersonObject): } $this->_logger->info( - 'SoftwareCatalogueService: Ensuring contact person in organization', + 'StackiqService: Ensuring contact person in organization', [ 'contactPersonId' => $contactPersonObject->getId(), 'username' => $email, @@ -3376,7 +3376,7 @@ private function ensureContactPersonInOrganization(object $contactPersonObject): $organisationMapper = $this->getOrganisationMapper(); if ($organisationMapper === null) { $this->_logger->error( - 'SoftwareCatalogueService: OpenRegister OrganisationMapper not available for contact person', + 'StackiqService: OpenRegister OrganisationMapper not available for contact person', [ 'contactPersonId' => $contactPersonObject->getId(), 'organization' => $organization, @@ -3389,7 +3389,7 @@ private function ensureContactPersonInOrganization(object $contactPersonObject): if ($organisation === null) { $this->_logger->error( - 'SoftwareCatalogueService: Organization entity not found for contact person', + 'StackiqService: Organization entity not found for contact person', [ 'contactPersonId' => $contactPersonObject->getId(), 'organization' => $organization, @@ -3402,7 +3402,7 @@ private function ensureContactPersonInOrganization(object $contactPersonObject): $currentUsers = $organisation->getUsers() ?? []; if (in_array($email, $currentUsers) === true) { $this->_logger->info( - 'SoftwareCatalogueService: Contact person already in organization', + 'StackiqService: Contact person already in organization', [ 'contactPersonId' => $contactPersonObject->getId(), 'username' => $email, @@ -3418,7 +3418,7 @@ private function ensureContactPersonInOrganization(object $contactPersonObject): $organisationMapper->save($organisation); $this->_logger->info( - 'SoftwareCatalogueService: Successfully added contact person to organization', + 'StackiqService: Successfully added contact person to organization', [ 'contactPersonId' => $contactPersonObject->getId(), 'username' => $email, @@ -3430,7 +3430,7 @@ private function ensureContactPersonInOrganization(object $contactPersonObject): // Organization entity doesn't exist yet - this can happen due to race conditions. // Log and return gracefully, the organization sync will handle this later. $this->_logger->warning( - 'SoftwareCatalogueService: Org entity not found (race condition), handled by org sync', + 'StackiqService: Org entity not found (race condition), handled by org sync', [ 'contactPersonId' => $contactPersonObject->getId(), 'username' => $email, @@ -3452,7 +3452,7 @@ private function ensureContactPersonInOrganization(object $contactPersonObject): private function updateOrganizationReferences(object $organizationObject): void { try { $this->_logger->info( - 'SoftwareCatalogueService: Updating organization references', + 'StackiqService: Updating organization references', [ 'objectId' => $organizationObject->getId(), ] @@ -3464,7 +3464,7 @@ private function updateOrganizationReferences(object $organizationObject): void // Get the ObjectService to update objects. $objectService = $this->getObjectService(); if ($objectService === null) { - $this->_logger->error('SoftwareCatalogueService: ObjectService not available for updating references'); + $this->_logger->error('StackiqService: ObjectService not available for updating references'); return; } @@ -3476,7 +3476,7 @@ private function updateOrganizationReferences(object $organizationObject): void $organisationEntityUuid = $organisationEntity->getUuid(); $this->_logger->info( - 'SoftwareCatalogueService: Found organization entity for reference update', + 'StackiqService: Found organization entity for reference update', [ 'organizationObjectUuid' => $organizationUuid, 'organizationEntityUuid' => $organisationEntityUuid, @@ -3484,7 +3484,7 @@ private function updateOrganizationReferences(object $organizationObject): void ); } catch (\OCP\AppFramework\Db\DoesNotExistException $e) { $this->_logger->error( - 'SoftwareCatalogueService: Organization entity not found for reference update', + 'StackiqService: Organization entity not found for reference update', [ 'organizationUuid' => $organizationUuid, ] @@ -3494,7 +3494,7 @@ private function updateOrganizationReferences(object $organizationObject): void // Update the organization object's @self.organisation field. $this->_logger->info( - 'SoftwareCatalogueService: Updating organization object reference', + 'StackiqService: Updating organization object reference', [ 'objectId' => $organizationObject->getId(), 'newOrganisationUuid' => $organisationEntityUuid, @@ -3518,7 +3518,7 @@ private function updateOrganizationReferences(object $organizationObject): void $contactpersonen = $objectData['contactpersonen'] ?? []; foreach ($contactpersonen as $contactUuid) { $this->_logger->info( - 'SoftwareCatalogueService: Updating contact person object reference', + 'StackiqService: Updating contact person object reference', [ 'contactUuid' => $contactUuid, 'newOrganisationUuid' => $organisationEntityUuid, @@ -3526,13 +3526,13 @@ private function updateOrganizationReferences(object $organizationObject): void ); // Get the contact person schema ID from configuration. - $settingsService = $this->_container->get('OCA\SoftwareCatalog\Service\SettingsService'); + $settingsService = $this->_container->get('OCA\Stackiq\Service\SettingsService'); $voorzieningenConfig = $settingsService->getVoorzieningenConfig(); $contactSchemaId = $voorzieningenConfig['contactpersoon_schema'] ?? null; if ($contactSchemaId === null) { $this->_logger->warning( - 'SoftwareCatalogueService: Missing contactpersoon schema configuration for object update', + 'StackiqService: Missing contactpersoon schema configuration for object update', [ 'contactUuid' => $contactUuid, ] @@ -3566,7 +3566,7 @@ private function updateOrganizationReferences(object $organizationObject): void } } catch (\Exception $e) { $this->_logger->error( - 'SoftwareCatalogueService: Failed to update contact person object', + 'StackiqService: Failed to update contact person object', [ 'contactUuid' => $contactUuid, 'error' => $e->getMessage(), @@ -3576,7 +3576,7 @@ private function updateOrganizationReferences(object $organizationObject): void }//end foreach $this->_logger->info( - 'SoftwareCatalogueService: Successfully updated organization references', + 'StackiqService: Successfully updated organization references', [ 'organizationUuid' => $organizationUuid, 'organizationEntityUuid' => $organisationEntityUuid, @@ -3585,7 +3585,7 @@ private function updateOrganizationReferences(object $organizationObject): void ); } catch (\Exception $e) { $this->_logger->error( - 'SoftwareCatalogueService: Failed to update organization references: ' . $e->getMessage(), + 'StackiqService: Failed to update organization references: ' . $e->getMessage(), [ 'objectId' => $organizationObject->getId(), 'exception' => $e->getMessage(), @@ -3611,7 +3611,7 @@ private function updateOrganizationReferences(object $organizationObject): void * log line so the per-method bodies no longer carry that boilerplate. * * W31 method-decomposition 2.6 — companion helper for the - * SoftwareCatalogue subservice wiring. + * Stackiq subservice wiring. * * @param string $schemaSlug Object-type slug as understood by * `SettingsService::getSchemaIdForObjectType()` @@ -3633,7 +3633,7 @@ private function resolveVoorzieningenContext(string $schemaSlug, string $logCont if ($registerId === null || $schemaId === null || $schemaId === false) { $this->_logger->error( - 'SoftwareCatalogueService: Register or schema not configured for ' . $logContext + 'StackiqService: Register or schema not configured for ' . $logContext ); return null; } diff --git a/lib/Service/SymfonyEmailService.php b/lib/Service/SymfonyEmailService.php index 8a92fdbd..1a171e9b 100644 --- a/lib/Service/SymfonyEmailService.php +++ b/lib/Service/SymfonyEmailService.php @@ -1,10 +1,10 @@ * @copyright 2024 Conduction B.V. * @license EUPL-1.2 https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12 @@ -16,7 +16,7 @@ declare(strict_types=1); -namespace OCA\SoftwareCatalog\Service; +namespace OCA\Stackiq\Service; use OCP\IAppConfig; use Psr\Log\LoggerInterface; @@ -34,7 +34,7 @@ * SendGrid, Mailgun, and other providers. * * @category Service - * @package OCA\SoftwareCatalog\Service + * @package OCA\Stackiq\Service * @author Conduction b.v. * @license EUPL-1.2 https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12 * @link https://codeberg.org/Conduction/SoftwareCatalog diff --git a/lib/Service/ViewQueryBuilder.php b/lib/Service/ViewQueryBuilder.php index ef349e55..705f520e 100644 --- a/lib/Service/ViewQueryBuilder.php +++ b/lib/Service/ViewQueryBuilder.php @@ -1,17 +1,17 @@ * @copyright 2024 Conduction B.V. * @license EUPL-1.2 https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12 - * @link https://codeberg.org/Conduction/SoftwareCatalog + * @link https://github.com/ConductionNL/stackiq * * @spec openspec/changes/method-decomposition/tasks.md#task-6 * @@ -21,7 +21,7 @@ declare(strict_types=1); -namespace OCA\SoftwareCatalog\Service; +namespace OCA\Stackiq\Service; /** * Builds and applies filters and sorting to view query option arrays. diff --git a/lib/Service/ViewService.php b/lib/Service/ViewService.php index 5104f1b1..21c0db91 100644 --- a/lib/Service/ViewService.php +++ b/lib/Service/ViewService.php @@ -1,25 +1,25 @@ * @copyright 2024 Conduction B.V. * @license EUPL-1.2 https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12 * @version GIT: - * @link https://codeberg.org/Conduction/SoftwareCatalog + * @link https://github.com/ConductionNL/stackiq * * @spec openspec/specs/method-decomposition/spec.md */ declare(strict_types=1); -namespace OCA\SoftwareCatalog\Service; +namespace OCA\Stackiq\Service; use OCA\OpenRegister\Contract\ObjectServiceInterface; use OCP\App\IAppManager; @@ -37,12 +37,12 @@ * data such as products, usage information (gebruik), and related data. * * @category Service - * @package OCA\SoftwareCatalog\Service + * @package OCA\Stackiq\Service * @author Conduction b.v. * @copyright 2024 Conduction B.V. * @license EUPL-1.2 https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12 * @version GIT: - * @link https://codeberg.org/Conduction/SoftwareCatalog + * @link https://github.com/ConductionNL/stackiq * * @SuppressWarnings(PHPMD.ExcessiveClassLength) * @SuppressWarnings(PHPMD.ExcessiveClassComplexity) @@ -93,7 +93,7 @@ public function __construct( private readonly IUserSession $userSession, ICacheFactory $cacheFactory, ) { - $this->viewsCache = $cacheFactory->createDistributed(prefix: 'softwarecatalog_views'); + $this->viewsCache = $cacheFactory->createDistributed(prefix: 'stackiq_views'); }//end __construct() /** diff --git a/lib/Settings/SoftwareCatalogAdmin.php b/lib/Settings/StackiqAdmin.php similarity index 87% rename from lib/Settings/SoftwareCatalogAdmin.php rename to lib/Settings/StackiqAdmin.php index bfb06895..c21e8db0 100644 --- a/lib/Settings/SoftwareCatalogAdmin.php +++ b/lib/Settings/StackiqAdmin.php @@ -1,18 +1,18 @@ * @copyright 2024 Conduction B.V. * @license EUPL-1.2 https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12 * @version GIT: - * @link https://codeberg.org/Conduction/SoftwareCatalog + * @link https://github.com/ConductionNL/stackiq */ -namespace OCA\SoftwareCatalog\Settings; +namespace OCA\Stackiq\Settings; use OCP\App\IAppManager; use OCP\AppFramework\Http\TemplateResponse; @@ -21,7 +21,7 @@ use OCP\IL10N; use OCP\Settings\IDelegatedSettings; -class SoftwareCatalogAdmin implements IDelegatedSettings { +class StackiqAdmin implements IDelegatedSettings { /** * The localization service. @@ -52,7 +52,7 @@ class SoftwareCatalogAdmin implements IDelegatedSettings { private IInitialState $initialState; /** - * Constructor for SoftwareCatalogAdmin settings. + * Constructor for StackiqAdmin settings. * * @param IAppConfig $config The application configuration service * @param IL10N $l10n The localization service @@ -72,9 +72,9 @@ public function __construct(IAppConfig $config, IL10N $l10n, IAppManager $appMan * @return TemplateResponse The template response for the settings form */ public function getForm(): TemplateResponse { - $this->initialState->provideInitialState('version', $this->appManager->getAppVersion('softwarecatalog')); + $this->initialState->provideInitialState('version', $this->appManager->getAppVersion('stackiq')); - return new TemplateResponse('softwarecatalog', 'settings/admin', []); + return new TemplateResponse('stackiq', 'settings/admin', []); }//end getForm() /** @@ -84,7 +84,7 @@ public function getForm(): TemplateResponse { */ public function getSection(): string { // Name of the previously created section. - $sectionName = 'softwarecatalog'; + $sectionName = 'stackiq'; return $sectionName; }//end getSection() @@ -117,7 +117,7 @@ public function getName(): ?string { /** * App config keys an authorized (delegated) admin may manage. * - * Returned as a map of appId => list of allowed config keys. SoftwareCatalog + * Returned as a map of appId => list of allowed config keys. Stackiq * exposes no delegatable sub-keys, so this is intentionally empty; the * `#[AuthorizedAdminSetting]` attribute still scopes the endpoints to full * admins (fail-closed). Required by IDelegatedSettings — its absence is a diff --git a/lib/Settings/register.d/contracts-to-decidesk.json b/lib/Settings/register.d/contracts-to-decidesk.json index c70c8e48..2097a43f 100644 --- a/lib/Settings/register.d/contracts-to-decidesk.json +++ b/lib/Settings/register.d/contracts-to-decidesk.json @@ -5,7 +5,7 @@ "properties": { "approvalDecisionId": { "type": "string", - "description": "PROJECTION FIELD. The id of the decidesk Decision (decisionType contract / contract-renewal) raised for this contract's approval/sign-off, resolved via the ADR-019 integration registry. Empty until the contract is submitted for approval. softwarecatalog never authors an approval decision locally; it raises it in decidesk and stores the returned id here for outcome reconciliation.", + "description": "PROJECTION FIELD. The id of the decidesk Decision (decisionType contract / contract-renewal) raised for this contract's approval/sign-off, resolved via the ADR-019 integration registry. Empty until the contract is submitted for approval. stackiq never authors an approval decision locally; it raises it in decidesk and stores the returned id here for outcome reconciliation.", "facetable": false, "title": "Approval decision id", "order": 20, @@ -20,7 +20,7 @@ "rejected" ], "default": "none", - "description": "PROJECTION of the decidesk outcome for the approval/renewal decision — distinct from the catalog lifecycle field `status`. `none` = no decision raised; `pending` = a decidesk Decision is open; `approved` = decidesk reported an adopting outcome (this is the ONLY thing that drives the `In onderhandeling -> Actief` transition on `status`); `rejected` = decidesk rejected/withdrew (status stays `In onderhandeling`). softwarecatalog NEVER sets `status = Actief` on its own authority — it is always a projection of an `approved` decidesk outcome. The date-driven `Actief -> Verlopen` expiry transition is catalog-local and is NOT a decision delegated to decidesk.", + "description": "PROJECTION of the decidesk outcome for the approval/renewal decision — distinct from the catalog lifecycle field `status`. `none` = no decision raised; `pending` = a decidesk Decision is open; `approved` = decidesk reported an adopting outcome (this is the ONLY thing that drives the `In onderhandeling -> Actief` transition on `status`); `rejected` = decidesk rejected/withdrew (status stays `In onderhandeling`). stackiq NEVER sets `status = Actief` on its own authority — it is always a projection of an `approved` decidesk outcome. The date-driven `Actief -> Verlopen` expiry transition is catalog-local and is NOT a decision delegated to decidesk.", "facetable": false, "title": "Approval state", "order": 21, diff --git a/lib/Settings/softwarecatalogus_register.json b/lib/Settings/softwarecatalogus_register.json index 39c8db05..ae9c8802 100644 --- a/lib/Settings/softwarecatalogus_register.json +++ b/lib/Settings/softwarecatalogus_register.json @@ -8,7 +8,7 @@ }, "x-openregister": { "type": "application", - "app": "softwarecatalog", + "app": "stackiq", "sourceType": "local", "sourceUrl": "lib/Settings/softwarecatalogus_register.json", "openregister": "^v0.2.10", diff --git a/openapi.json b/openapi.json index 32f1ef47..bd45fa77 100644 --- a/openapi.json +++ b/openapi.json @@ -1,9 +1,9 @@ { "openapi": "3.0.3", "info": { - "title": "softwarecatalog", + "title": "stackiq", "version": "0.1.141-unstable.20260821053703", - "description": "Software Catalog", + "description": "Stackiq", "license": { "name": "agpl" } diff --git a/openspec/changes/adopt-apphost/hydra.json b/openspec/changes/adopt-apphost/hydra.json index ca90dbb2..6dbdd96b 100644 --- a/openspec/changes/adopt-apphost/hydra.json +++ b/openspec/changes/adopt-apphost/hydra.json @@ -1,8 +1,8 @@ { "spec_slug": "adopt-apphost", "title": "Adopt OpenRegister AppHost (observability + boilerplate)", - "app": "softwarecatalog", - "repo": "https://codeberg.org/Conduction/softwarecatalog", + "app": "stackiq", + "repo": "https://codeberg.org/Conduction/stackiq", "depends_on": [ "apphost-observability-engine", "apphost-boilerplate-controllers" diff --git a/openspec/changes/adopt-apphost/proposal.md b/openspec/changes/adopt-apphost/proposal.md index d1633790..6762b723 100644 --- a/openspec/changes/adopt-apphost/proposal.md +++ b/openspec/changes/adopt-apphost/proposal.md @@ -2,11 +2,11 @@ kind: code --- -# Proposal: SoftwareCatalog Adopts OpenRegister AppHost +# Proposal: Stackiq Adopts OpenRegister AppHost ## Problem -SoftwareCatalog is one of the three fleet apps (with larpingapp and zaakafhandelapp) that has **no health or metrics endpoint at all** — a direct ADR-006 violation flagged in the 2026-06-12 fleet observability inventory. Worse, it pretends otherwise: `appinfo/routes.php` carries the comment `// Note: /api/health is served by settings#status above`, but: +Stackiq is one of the three fleet apps (with larpingapp and zaakafhandelapp) that has **no health or metrics endpoint at all** — a direct ADR-006 violation flagged in the 2026-06-12 fleet observability inventory. Worse, it pretends otherwise: `appinfo/routes.php` carries the comment `// Note: /api/health is served by settings#status above`, but: - There is **no `/api/health` route** in the file. The referenced route is `settings#status` at `/api/settings/status`. - `SettingsController::status()` requires an authenticated session (`@NoAdminRequired` + an explicit 401 on anonymous calls) — ADR-006 health must be public for probes. @@ -14,7 +14,7 @@ SoftwareCatalog is one of the three fleet apps (with larpingapp and zaakafhandel So the app's "health" is a pseudo-health: wrong URL, wrong auth posture, wrong shape, wrong semantics. There is no metrics endpoint of any kind. -Beyond observability, SoftwareCatalog carries the full boilerplate set the AppHost replaces: `DashboardController` (100 lines, SPA page + catch-all + trivial `index()` stub), `PreferencesController` (154 lines), the generic settings surface inside the 3,667-line `SettingsController`, `Repair/InitializeSettings`, `Sections/SoftwareCatalogAdmin` + `Settings/SoftwareCatalogAdmin`, a 592-line `Application.php`, and a 217-line hand-maintained `routes.php`. +Beyond observability, Stackiq carries the full boilerplate set the AppHost replaces: `DashboardController` (100 lines, SPA page + catch-all + trivial `index()` stub), `PreferencesController` (154 lines), the generic settings surface inside the 3,667-line `SettingsController`, `Repair/InitializeSettings`, `Sections/StackiqAdmin` + `Settings/StackiqAdmin`, a 592-line `Application.php`, and a 217-line hand-maintained `routes.php`. ## Proposed Change @@ -24,7 +24,7 @@ Adopt both halves of the OpenRegister AppHost (`apphost-observability-engine` + - Add an `observability` block to `src/manifest.json`: - **Health checks**: `database` + `orAvailable` (the app is fully OR-backed; if OR is down the app is down). Engine-owned posture: public, `statusCodePolicy: adr006` (503 on critical failure). - - **Metrics**: implicit `softwarecatalog_info` / `softwarecatalog_up`, plus one example descriptor `gebruik_total` — `objectCount` on register `voorzieningen`, schema `gebruik` (the app's main usage entity; both slugs verified against `lib/Settings/softwarecatalogus_register.json`). Engine-owned: admin-only, Prometheus text 0.0.4. + - **Metrics**: implicit `stackiq_info` / `stackiq_up`, plus one example descriptor `gebruik_total` — `objectCount` on register `voorzieningen`, schema `gebruik` (the app's main usage entity; both slugs verified against `lib/Settings/softwarecatalogus_register.json`). Engine-owned: admin-only, Prometheus text 0.0.4. - Route `/api/health` → `health#check` and `/api/metrics` → `metrics#index` to the AppHost generic controllers via the standard alias wiring. - **Delete the misleading routes.php comment.** `settings#status` keeps existing at `/api/settings/status` and reverts to being exactly what it is: an authenticated settings/configuration-status endpoint. Nothing else changes about it; it simply stops being claimed as health. @@ -36,14 +36,14 @@ Adopt both halves of the OpenRegister AppHost (`apphost-observability-engine` + - **`lib/Controller/SettingsController.php` (3,667 lines) — SCOPED, NOT deleted.** This controller is almost entirely DOMAIN: ~75 methods covering email + email templates, ArchiMate import/export/status/round-trip, organisation sync, user-group management, cronjob config, progress streaming, AMEF/voorzieningen/general/sync focused config endpoints, object counts/statistics, bulk standards sync, heartbeat, version/import/force-update management. **Only the generic settings surface moves**: `index()`, `create()`, and `load()` (the `GenericSettingsController` contract). The class becomes a subclass of `GenericSettingsController` (extension-first, per the AppHost design), deletes those three hand-written bodies, and keeps every domain method — including `status()`, which stays as the domain configuration-status endpoint. - **`lib/Service/SettingsService.php` (6,716 lines) — KEEP.** Same verdict: overwhelmingly domain (register auto-detection heuristics, sync, ArchiMate config). Only the register/schema config-resolution + OR-availability surface overlaps `AppHostSettingsService`; refactoring it to delegate is explicitly out of scope here (tracked as follow-up) to keep this change parity-verifiable. - **`lib/Repair/InitializeSettings.php` → one-line stub** extending `GenericInitializeSettings` (info.xml `` requires an app-namespace class; repair-step pattern preserved per the install-order constraint). -- **`lib/Sections/SoftwareCatalogAdmin.php` + `lib/Settings/SoftwareCatalogAdmin.php` → one-line stubs** extending `GenericSettingsSection` / `GenericAdminSettings` (IDelegatedSettings, #299 pattern). +- **`lib/Sections/StackiqAdmin.php` + `lib/Settings/StackiqAdmin.php` → one-line stubs** extending `GenericSettingsSection` / `GenericAdminSettings` (IDelegatedSettings, #299 pattern). - **No DeepLinkRegistrationListener exists in this app** — nothing to adopt there. -- **`lib/AppInfo/Application.php` (592 lines)**: replace the boilerplate registrations with `Bootstrap::register($context, self::APP_ID)`; the substantial domain wiring stays (SoftwareCatalogue handlers, domain services, OR event listeners, dashboard widget, the `boot()` manifest-sentinel initial-state provisioning). +- **`lib/AppInfo/Application.php` (592 lines)**: replace the boilerplate registrations with `Bootstrap::register($context, self::APP_ID)`; the substantial domain wiring stays (Stackique handlers, domain services, OR event listeners, dashboard widget, the `boot()` manifest-sentinel initial-state provisioning). - **`appinfo/routes.php` (217 lines)**: rebuild on `Routes::standard($extra)` — standard supplies dashboard page + catch-all, settings index/create/load, preferences, health, metrics; the large domain route set (settings domain endpoints, contactpersonen, views, aanbod, aangeboden-gebruik, gebruik, cronjobs) is appended via `$extra`. Route names/URLs/verbs unchanged for everything that exists today. ## Impact -- **New endpoints**: `GET /apps/softwarecatalog/api/health` (public, ADR-006) and `GET /apps/softwarecatalog/api/metrics` (admin, Prometheus) — closing a fleet-inventory compliance gap. +- **New endpoints**: `GET /apps/stackiq/api/health` (public, ADR-006) and `GET /apps/stackiq/api/metrics` (admin, Prometheus) — closing a fleet-inventory compliance gap. - **Deleted**: DashboardController + PreferencesController (~254 lines), 3 generic method bodies in SettingsController, boilerplate Application.php registrations, hand-written standard routes; repair/section/admin-settings classes shrink to stubs. - **Unchanged contracts**: all existing route names, URLs, verbs, response shapes; `settings#status` response is byte-identical; preferences keys keep resolving. - **Risk**: behavioural drift between old copies and the generic classes — mitigated by endpoint-level parity checks (baseline capture in tasks 0.x, OR Newman contract collection, existing e2e suite) before deletion lands. diff --git a/openspec/changes/adopt-apphost/specs/apphost-adoption/spec.md b/openspec/changes/adopt-apphost/specs/apphost-adoption/spec.md index 8597a6e5..fb1d2e90 100644 --- a/openspec/changes/adopt-apphost/specs/apphost-adoption/spec.md +++ b/openspec/changes/adopt-apphost/specs/apphost-adoption/spec.md @@ -2,11 +2,11 @@ status: proposed --- -# SoftwareCatalog AppHost Adoption +# Stackiq AppHost Adoption ## Purpose -SoftwareCatalog serves real ADR-006 observability endpoints (replacing the `settings#status` pseudo-health) and runs its app boilerplate (dashboard SPA serving, preferences, generic settings surface, install/admin plumbing) on the OpenRegister AppHost generics, with endpoint-level parity for everything that exists today. +Stackiq serves real ADR-006 observability endpoints (replacing the `settings#status` pseudo-health) and runs its app boilerplate (dashboard SPA serving, preferences, generic settings surface, install/admin plumbing) on the OpenRegister AppHost generics, with endpoint-level parity for everything that exists today. **Cross-references**: `openregister/openspec/changes/apphost-observability-engine/specs/apphost-observability/spec.md`, `openregister/openspec/changes/apphost-boilerplate-controllers/` @@ -16,65 +16,65 @@ SoftwareCatalog serves real ADR-006 observability endpoints (replacing the `sett ### Requirement: ADR-006 Health Endpoint -SoftwareCatalog SHALL serve `GET /apps/softwarecatalog/api/health` through the AppHost `GenericHealthController` — publicly accessible, executing the manifest-declared `database` and `orAvailable` checks, returning the fleet health shape and HTTP 503 when a critical check fails (`statusCodePolicy: adr006`). +Stackiq SHALL serve `GET /apps/stackiq/api/health` through the AppHost `GenericHealthController` — publicly accessible, executing the manifest-declared `database` and `orAvailable` checks, returning the fleet health shape and HTTP 503 when a critical check fails (`statusCodePolicy: adr006`). #### Scenario: Anonymous health probe on a healthy instance - **GIVEN** a healthy instance with OpenRegister enabled -- **WHEN** `GET /apps/softwarecatalog/api/health` is called without authentication -- **THEN** the response MUST be HTTP 200 with `status = "ok"`, `app = "softwarecatalog"`, and `checks.database = "ok"` and `checks.openregister = "ok"` in the standard `{status, app, version, checks}` shape +- **WHEN** `GET /apps/stackiq/api/health` is called without authentication +- **THEN** the response MUST be HTTP 200 with `status = "ok"`, `app = "stackiq"`, and `checks.database = "ok"` and `checks.openregister = "ok"` in the standard `{status, app, version, checks}` shape - @e2e exclude API-only endpoint — covered by the OR AppHost Newman contract collection #### Scenario: Critical check failure returns 503 - **GIVEN** OpenRegister is disabled or its ObjectService cannot be resolved -- **WHEN** `GET /apps/softwarecatalog/api/health` is called +- **WHEN** `GET /apps/stackiq/api/health` is called - **THEN** the response MUST be HTTP 503 with `status = "error"` and `checks.openregister` reporting `failed: ` without leaking exception details - @e2e exclude API-only endpoint — covered by the OR AppHost Newman contract collection ### Requirement: ADR-006 Metrics Endpoint -SoftwareCatalog SHALL serve `GET /apps/softwarecatalog/api/metrics` through the AppHost `GenericMetricsController` — admin-only, Prometheus text format 0.0.4 — emitting the implicit `softwarecatalog_info` and `softwarecatalog_up` metrics plus the declared `softwarecatalog_gebruik_total` gauge (`objectCount` on register `voorzieningen`, schema `gebruik`). +Stackiq SHALL serve `GET /apps/stackiq/api/metrics` through the AppHost `GenericMetricsController` — admin-only, Prometheus text format 0.0.4 — emitting the implicit `stackiq_info` and `stackiq_up` metrics plus the declared `stackiq_gebruik_total` gauge (`objectCount` on register `voorzieningen`, schema `gebruik`). #### Scenario: Admin scrapes metrics - **GIVEN** a seeded instance with gebruik objects in the voorzieningen register -- **WHEN** `GET /apps/softwarecatalog/api/metrics` is called by an admin -- **THEN** the response MUST be Prometheus text 0.0.4 with `# HELP`/`# TYPE` lines containing `softwarecatalog_info` (version, php_version, nextcloud_version labels), `softwarecatalog_up 1`, and `softwarecatalog_gebruik_total` matching the gebruik object count +- **WHEN** `GET /apps/stackiq/api/metrics` is called by an admin +- **THEN** the response MUST be Prometheus text 0.0.4 with `# HELP`/`# TYPE` lines containing `stackiq_info` (version, php_version, nextcloud_version labels), `stackiq_up 1`, and `stackiq_gebruik_total` matching the gebruik object count - @e2e exclude API-only endpoint — covered by the OR AppHost Newman contract collection #### Scenario: Non-admin is rejected - **GIVEN** an authenticated non-admin user -- **WHEN** `GET /apps/softwarecatalog/api/metrics` is called +- **WHEN** `GET /apps/stackiq/api/metrics` is called - **THEN** the request MUST be rejected (401/403) by the engine-owned admin posture - @e2e exclude API-only endpoint — covered by the OR AppHost Newman contract collection ### Requirement: Pseudo-Health Retirement -SoftwareCatalog SHALL remove the claim that `settings#status` serves health: the `// Note: /api/health is served by settings#status above` comment in `appinfo/routes.php` SHALL be deleted, while `GET /api/settings/status` SHALL keep serving its current authenticated configuration-status response unchanged (`{status, fullyConfigured, versionInfo, timestamp, autoConfigCompleted}`). +Stackiq SHALL remove the claim that `settings#status` serves health: the `// Note: /api/health is served by settings#status above` comment in `appinfo/routes.php` SHALL be deleted, while `GET /api/settings/status` SHALL keep serving its current authenticated configuration-status response unchanged (`{status, fullyConfigured, versionInfo, timestamp, autoConfigCompleted}`). #### Scenario: Settings status is unchanged and is not health - **GIVEN** an authenticated user on a configured instance -- **WHEN** `GET /apps/softwarecatalog/api/settings/status` is called +- **WHEN** `GET /apps/stackiq/api/settings/status` is called - **THEN** the response MUST be byte-identical to the pre-adoption baseline, and anonymous calls MUST still receive 401 — the endpoint is a settings endpoint, distinct from the public `/api/health` - @e2e exclude API-only endpoint — covered by the OR AppHost Newman contract collection ### Requirement: Boilerplate Runs on AppHost Generics -SoftwareCatalog SHALL serve its SPA (dashboard page + catch-all), per-user preferences, and the generic settings surface (`settings#index`, `settings#create`, `settings#load`) through the AppHost generic controllers via alias wiring (`Bootstrap::register`) and `Routes::standard($extra)`, deleting `DashboardController` and `PreferencesController` and the three generic `SettingsController` method bodies. All domain surfaces SHALL remain app-owned: `ViewController` (ArchiMate view-enrichment API), the ~75 domain methods of `SettingsController`, `SettingsService`, and all domain routes appended via `$extra` with unchanged names, URLs, and verbs. +Stackiq SHALL serve its SPA (dashboard page + catch-all), per-user preferences, and the generic settings surface (`settings#index`, `settings#create`, `settings#load`) through the AppHost generic controllers via alias wiring (`Bootstrap::register`) and `Routes::standard($extra)`, deleting `DashboardController` and `PreferencesController` and the three generic `SettingsController` method bodies. All domain surfaces SHALL remain app-owned: `ViewController` (ArchiMate view-enrichment API), the ~75 domain methods of `SettingsController`, `SettingsService`, and all domain routes appended via `$extra` with unchanged names, URLs, and verbs. #### Scenario: SPA still renders after dashboard aliasing - **GIVEN** the app is enabled and a user is logged in -- **WHEN** the user opens `/apps/softwarecatalog/` or deep-links to any frontend route such as `/apps/softwarecatalog/voorzieningen` +- **WHEN** the user opens `/apps/stackiq/` or deep-links to any frontend route such as `/apps/stackiq/voorzieningen` - **THEN** the Vue SPA MUST render via the generic dashboard controller serving `templates/index.php` with the preserved chunk-loading order, and in-app navigation MUST work as before #### Scenario: Preferences parity through the generic controller - **GIVEN** a user with a preference previously written by the hand-written controller -- **WHEN** `GET /apps/softwarecatalog/api/preferences/{key}` and `PUT` with a new value are called +- **WHEN** `GET /apps/stackiq/api/preferences/{key}` and `PUT` with a new value are called - **THEN** the stored value MUST resolve under the same key namespace and the response shapes MUST match the pre-adoption baseline - @e2e exclude API-only endpoint — covered by the OR AppHost Newman contract collection @@ -87,7 +87,7 @@ SoftwareCatalog SHALL serve its SPA (dashboard page + catch-all), per-user prefe ### Requirement: Install And Admin Plumbing Via Stubs -SoftwareCatalog SHALL keep `Repair/InitializeSettings`, `Sections/SoftwareCatalogAdmin`, and `Settings/SoftwareCatalogAdmin` only as one-line app-namespace stubs extending the AppHost generics (required by info.xml ``/`` registration), preserving the repair-step install pattern and the IDelegatedSettings (#299) admin-settings pattern. +Stackiq SHALL keep `Repair/InitializeSettings`, `Sections/StackiqAdmin`, and `Settings/StackiqAdmin` only as one-line app-namespace stubs extending the AppHost generics (required by info.xml ``/`` registration), preserving the repair-step install pattern and the IDelegatedSettings (#299) admin-settings pattern. #### Scenario: Register import still runs on install/upgrade diff --git a/openspec/changes/adopt-apphost/tasks.md b/openspec/changes/adopt-apphost/tasks.md index 24475377..8b9655f7 100644 --- a/openspec/changes/adopt-apphost/tasks.md +++ b/openspec/changes/adopt-apphost/tasks.md @@ -1,29 +1,29 @@ -# Tasks: SoftwareCatalog Adopts OpenRegister AppHost +# Tasks: Stackiq Adopts OpenRegister AppHost ## 0. Baseline -- [ ] 0.1 Capture baseline of the pseudo-health: `curl` (authenticated) `GET /apps/softwarecatalog/api/settings/status` on the dev instance; store the JSON (`{status, fullyConfigured, versionInfo, timestamp, autoConfigCompleted}`) as a parity fixture — this endpoint must be byte-identical after adoption +- [ ] 0.1 Capture baseline of the pseudo-health: `curl` (authenticated) `GET /apps/stackiq/api/settings/status` on the dev instance; store the JSON (`{status, fullyConfigured, versionInfo, timestamp, autoConfigCompleted}`) as a parity fixture — this endpoint must be byte-identical after adoption - [ ] 0.2 Record the current state for the change log: NO `/api/health` or `/api/metrics` route exists; `appinfo/routes.php` line ~69 carries the false comment `// Note: /api/health is served by settings#status above` - [ ] 0.3 Capture baseline responses of `dashboard#page` (SPA template), `dashboard#index`, and `preferences#getPreference`/`setPreference` for parity diff after generic-controller aliasing ## 1. Manifest observability block -- [ ] 1.1 Add `observability` to `src/manifest.json`: `health.checks = [{id: database, type: database}, {id: openregister, type: orAvailable}]` (default `statusCodePolicy: adr006`); `metrics = [{name: gebruik_total, type: gauge, help: "Gebruik (usage) records", source: {kind: objectCount, register: voorzieningen, schema: gebruik}}]` — implicit `softwarecatalog_info`/`softwarecatalog_up` come free +- [ ] 1.1 Add `observability` to `src/manifest.json`: `health.checks = [{id: database, type: database}, {id: openregister, type: orAvailable}]` (default `statusCodePolicy: adr006`); `metrics = [{name: gebruik_total, type: gauge, help: "Gebruik (usage) records", source: {kind: objectCount, register: voorzieningen, schema: gebruik}}]` — implicit `stackiq_info`/`stackiq_up` come free - [ ] 1.2 Validate the block via ManifestService diagnostics (no errors); confirm `voorzieningen`/`gebruik` slugs resolve against the imported register ## 2. Wiring, deletions, real health/metrics routes -- [ ] 2.1 `lib/AppInfo/Application.php`: add `Bootstrap::register($context, self::APP_ID)`; remove the boilerplate registrations it supersedes; KEEP all domain wiring (SoftwareCatalogue handlers, domain services, OR event listeners, ConceptOrganisatiesWidget, `boot()` initial-state sentinel provisioning) +- [ ] 2.1 `lib/AppInfo/Application.php`: add `Bootstrap::register($context, self::APP_ID)`; remove the boilerplate registrations it supersedes; KEEP all domain wiring (Stackique handlers, domain services, OR event listeners, ConceptOrganisatiesWidget, `boot()` initial-state sentinel provisioning) - [ ] 2.2 `appinfo/routes.php`: rebuild on `\OCA\OpenRegister\AppHost\Routes::standard($extra)` — standard provides dashboard page + SPA catch-all, settings index/create/load, preferences GET/PUT, **`/api/health` (public) and `/api/metrics` (admin)**; append all domain routes via `$extra` with names/URLs/verbs unchanged; DELETE the `// Note: /api/health is served by settings#status above` comment - [ ] 2.3 DELETE `lib/Controller/DashboardController.php` (SPA server → `GenericDashboardController` alias) and `lib/Controller/PreferencesController.php` (→ `GenericPreferencesController` alias) - [ ] 2.4 `lib/Controller/SettingsController.php`: extend `GenericSettingsController`; delete ONLY the hand-written `index()`, `create()`, `load()` bodies; keep all ~75 domain methods (email, ArchiMate, sync, user groups, cronjobs, progress, focused configs, counts/statistics, heartbeat, version/import management) — including `status()`, which reverts to being just the settings configuration-status endpoint -- [ ] 2.5 Shrink `lib/Repair/InitializeSettings.php`, `lib/Sections/SoftwareCatalogAdmin.php`, `lib/Settings/SoftwareCatalogAdmin.php` to one-line stubs extending the AppHost generics (info.xml ``/`` require app-namespace classes); confirm no DeepLinkRegistrationListener exists to adopt +- [ ] 2.5 Shrink `lib/Repair/InitializeSettings.php`, `lib/Sections/StackiqAdmin.php`, `lib/Settings/StackiqAdmin.php` to one-line stubs extending the AppHost generics (info.xml ``/`` require app-namespace classes); confirm no DeepLinkRegistrationListener exists to adopt - [ ] 2.6 KEEP `lib/Controller/ViewController.php` (domain ArchiMate view-enrichment API, not the SPA server) and `lib/Service/SettingsService.php` (domain; AppHostSettingsService delegation is a tracked follow-up) untouched - [ ] 2.7 Sweep references: unit tests, `@spec` tags, frontend callers of deleted controller methods (none expected — routes unchanged) ## 3. Verification -- [ ] 3.1 Run the OR AppHost Newman contract collection against softwarecatalog: `/api/health` anonymous 200 with `checks.database`/`checks.openregister`; 503 when a critical check fails; `/api/metrics` admin-only (401/403 for non-admin), Prometheus text 0.0.4 containing `softwarecatalog_info`, `softwarecatalog_up`, `softwarecatalog_gebruik_total` +- [ ] 3.1 Run the OR AppHost Newman contract collection against stackiq: `/api/health` anonymous 200 with `checks.database`/`checks.openregister`; 503 when a critical check fails; `/api/metrics` admin-only (401/403 for non-admin), Prometheus text 0.0.4 containing `stackiq_info`, `stackiq_up`, `stackiq_gebruik_total` - [ ] 3.2 Diff `settings#status`, dashboard, and preferences responses against the 0.x baselines — byte-identical (document any intentional delta; expected: none) - [ ] 3.3 Existing Playwright e2e suite green (SPA catch-all + deep links still render); PHPUnit green diff --git a/openspec/changes/adopt-integration-leaves/proposal.md b/openspec/changes/adopt-integration-leaves/proposal.md index cb5a77c8..569ff8aa 100644 --- a/openspec/changes/adopt-integration-leaves/proposal.md +++ b/openspec/changes/adopt-integration-leaves/proposal.md @@ -3,7 +3,7 @@ kind: code depends_on: [] --- -# softwarecatalog — adopt OpenRegister integration leaves (contacts, calendar, deck, bookmarks) +# stackiq — adopt OpenRegister integration leaves (contacts, calendar, deck, bookmarks) ## Why diff --git a/openspec/changes/beta-surface-alignment/proposal.md b/openspec/changes/beta-surface-alignment/proposal.md index 52e14ac0..55b2f2d4 100644 --- a/openspec/changes/beta-surface-alignment/proposal.md +++ b/openspec/changes/beta-surface-alignment/proposal.md @@ -2,14 +2,14 @@ kind: docs --- -# Proposal: Beta Cross-Surface Alignment — SoftwareCatalog +# Proposal: Beta Cross-Surface Alignment — Stackiq ## Problem -Four surfaces describe SoftwareCatalog (`appinfo/info.xml`, `src/manifest.json`, the conduction.nl product page, and `docs/`) and they disagreed badly enough to block a beta release: +Four surfaces describe Stackiq (`appinfo/info.xml`, `src/manifest.json`, the conduction.nl product page, and `docs/`) and they disagreed badly enough to block a beta release: 1. **License tag wrong.** `appinfo/info.xml` declared `agpl` even though the shipped `LICENSE` file is the European Union Public Licence v1.2 and the description text already said "EUPL". Many PHP/Vue/JS file headers also carry `SPDX-License-Identifier: AGPL-3.0-or-later` — a pre-existing, wider inconsistency noted below but not touched in this change (see "Deferred"). -2. **The product page (`conduction-website/src/pages/apps/softwarecatalog.mdx` + its NL translation) was substantially fabricated.** It described an app that pulls IT-asset inventory from Microsoft Intune/Jamf/GLPI/OCS Inventory via OpenConnector, computes a "dependency graph" with "deprecation impact" analysis, federates specifically to Forum Standaardisatie/data.overheid.nl, and ships dashboard widgets named "Renewals due", "Inventory snapshot" and "Discovery deltas". None of this exists in `lib/` or `src/`. The page also claimed version `v1.1` / status "Stable" against an actual `info.xml` version of `0.2.13`. +2. **The product page (`conduction-website/src/pages/apps/stackiq.mdx` + its NL translation) was substantially fabricated.** It described an app that pulls IT-asset inventory from Microsoft Intune/Jamf/GLPI/OCS Inventory via OpenConnector, computes a "dependency graph" with "deprecation impact" analysis, federates specifically to Forum Standaardisatie/data.overheid.nl, and ships dashboard widgets named "Renewals due", "Inventory snapshot" and "Discovery deltas". None of this exists in `lib/` or `src/`. The page also claimed version `v1.1` / status "Stable" against an actual `info.xml` version of `0.2.13`. 3. **`docs/FEATURES.md` was stale** (missing contracts, standards/compliance matrix, ArchiMate, portfolio roadmap, reviews, moderated self-registration — all real, shipped features) though not fabricated. 4. **`docs/GOVERNMENT-FEATURES.md`** (a VNG-style requirements checklist) was largely accurate and unusually self-critical, but repeated the wrong license (`AGPL`) and an outdated attribution (`GitHub` instead of Codeberg). @@ -43,9 +43,9 @@ Four surfaces describe SoftwareCatalog (`appinfo/info.xml`, `src/manifest.json`, ## Fixes Applied 1. `appinfo/info.xml`: `agpl` → `EUPL-1.2`; EN+NL description "Key Features" lists expanded to include contract administration, GEMMA/ArchiMate standards & compliance, portfolio roadmap, and moderated open-data publishing; federated-sync bullet reworded to name OpenCatalogi as the optional delegate. -2. `conduction-website/src/pages/apps/softwarecatalog.mdx` (EN) and the NL i18n copy: hero (version, status), intro, FeatureList, RotatingCards, WidgetShelf, Showcase, PairRow, and CtaBanner rewritten around the verified feature list; all fabricated claims removed. -3. `softwarecatalog/docs/FEATURES.md`: added Contract Administration, Standards/Compliance Matrix/ArchiMate, Application Lifecycle & Portfolio Roadmap, Reviews, and Open Data Publishing & Moderated Self-Registration sections; Federated Synchronization section reworded to name OpenCatalogi. -4. `softwarecatalog/docs/GOVERNMENT-FEATURES.md`: license line and open-source attribution corrected (AGPL→EUPL-1.2, GitHub→Codeberg). +2. `conduction-website/src/pages/apps/stackiq.mdx` (EN) and the NL i18n copy: hero (version, status), intro, FeatureList, RotatingCards, WidgetShelf, Showcase, PairRow, and CtaBanner rewritten around the verified feature list; all fabricated claims removed. +3. `stackiq/docs/FEATURES.md`: added Contract Administration, Standards/Compliance Matrix/ArchiMate, Application Lifecycle & Portfolio Roadmap, Reviews, and Open Data Publishing & Moderated Self-Registration sections; Federated Synchronization section reworded to name OpenCatalogi. +4. `stackiq/docs/GOVERNMENT-FEATURES.md`: license line and open-source attribution corrected (AGPL→EUPL-1.2, GitHub→Codeberg). 5. Icon (`img/app.svg`): checked against the brand convention (24×24 viewBox, single `#fff` fill) — already compliant, no change needed. ## Deferred (flagged, not fixed in this change) @@ -56,4 +56,4 @@ Four surfaces describe SoftwareCatalog (`appinfo/info.xml`, `src/manifest.json`, ## Note on Scope -softwarecatalog is a VNG client repository (`Softwarecatalogus/` external client is separate and untouched). All edits in this change are local — no push, no PR, per repo convention for this app. +stackiq is a VNG client repository (`Softwarecatalogus/` external client is separate and untouched). All edits in this change are local — no push, no PR, per repo convention for this app. diff --git a/openspec/changes/beta-surface-alignment/specs/beta-alignment/spec.md b/openspec/changes/beta-surface-alignment/specs/beta-alignment/spec.md index 770dc5af..ec41f323 100644 --- a/openspec/changes/beta-surface-alignment/specs/beta-alignment/spec.md +++ b/openspec/changes/beta-surface-alignment/specs/beta-alignment/spec.md @@ -2,11 +2,11 @@ status: proposed --- -# SoftwareCatalog Beta Cross-Surface Alignment +# Stackiq Beta Cross-Surface Alignment ## Purpose -SoftwareCatalog's code metadata (`appinfo/info.xml`), product page (conduction.nl), and docs (softwarecatalog.conduction.nl) SHALL describe the same, code-verified feature set and licence, so the app is beta-release-ready. +Stackiq's code metadata (`appinfo/info.xml`), product page (conduction.nl), and docs (softwarecatalog.conduction.nl) SHALL describe the same, code-verified feature set and licence, so the app is beta-release-ready. ## Requirements diff --git a/openspec/changes/beta-surface-alignment/tasks.md b/openspec/changes/beta-surface-alignment/tasks.md index 6dceeede..b422ac0f 100644 --- a/openspec/changes/beta-surface-alignment/tasks.md +++ b/openspec/changes/beta-surface-alignment/tasks.md @@ -1,4 +1,4 @@ -# Tasks: Beta Cross-Surface Alignment — SoftwareCatalog +# Tasks: Beta Cross-Surface Alignment — Stackiq ## 1. Derive canonical feature vocabulary @@ -16,14 +16,14 @@ ## 3. Reconcile product page (conduction-website) -- [x] 3.1 Rewrite `src/pages/apps/softwarecatalog.mdx` hero: version `v0.2` (was `v1.1`), status Beta (was Stable) +- [x] 3.1 Rewrite `src/pages/apps/stackiq.mdx` hero: version `v0.2` (was `v1.1`), status Beta (was Stable) - [x] 3.2 Rewrite intro paragraph and `FeatureList` around the verified feature list - [x] 3.3 Rewrite `RotatingCards` (Register / Assess / Federate) removing OpenConnector-discovery and Forum Standaardisatie claims - [x] 3.4 Rewrite `WidgetShelf` to the two real dashboard widgets (object statistics, management information) - [x] 3.5 Rewrite `Showcase` (contract approval, standards/ArchiMate, OpenCatalogi federation) - [x] 3.6 Rewrite `PairRow` — drop OpenConnector and LaunchPad (no code support), keep OpenRegister (hard dependency) + OpenCatalogi (optional) - [x] 3.7 Rewrite `CtaBanner` copy -- [x] 3.8 Mirror all of the above in the NL i18n page (`i18n/nl/docusaurus-plugin-content-pages/apps/softwarecatalog.mdx`) +- [x] 3.8 Mirror all of the above in the NL i18n page (`i18n/nl/docusaurus-plugin-content-pages/apps/stackiq.mdx`) ## 4. Reconcile docs diff --git a/openspec/changes/bound-unbounded-searchobjects-scans/proposal.md b/openspec/changes/bound-unbounded-searchobjects-scans/proposal.md index a53dad7c..9f52281b 100644 --- a/openspec/changes/bound-unbounded-searchobjects-scans/proposal.md +++ b/openspec/changes/bound-unbounded-searchobjects-scans/proposal.md @@ -3,7 +3,7 @@ kind: code depends_on: [] --- -# softwarecatalog — bound unbounded `searchObjects()` full-table scans +# stackiq — bound unbounded `searchObjects()` full-table scans ## Why @@ -17,7 +17,7 @@ opposite failure mode from the more common "default-limit truncates results" bug: here, forgetting `_limit` silently removes the safety net. A repo-wide audit of every non-test `searchObjects()` call site in -`softwarecatalog/lib/` found **25 of 29 call sites never set `_limit`** +`stackiq/lib/` found **25 of 29 call sites never set `_limit`** (verified by grepping the 20 lines preceding each call for `_limit`; the 4 exceptions are `ArchiMateService.php:378/397/412/435`, which do set an explicit `_limit`). These are full, unbounded register/schema scans on diff --git a/openspec/changes/bound-unbounded-searchobjects-scans/specs/query-performance/spec.md b/openspec/changes/bound-unbounded-searchobjects-scans/specs/query-performance/spec.md index 75f26c06..a5656bf1 100644 --- a/openspec/changes/bound-unbounded-searchobjects-scans/specs/query-performance/spec.md +++ b/openspec/changes/bound-unbounded-searchobjects-scans/specs/query-performance/spec.md @@ -1,7 +1,7 @@ ## ADDED Requirements ### Requirement: Every `searchObjects()` call MUST set an explicit `_limit` -Every call to `OCA\OpenRegister\Service\ObjectService::searchObjects()` from softwarecatalog's `lib/` MUST pass an explicit `_limit` key in its query array. +Every call to `OCA\OpenRegister\Service\ObjectService::searchObjects()` from stackiq's `lib/` MUST pass an explicit `_limit` key in its query array. Omitting `_limit` causes `MagicSearchHandler::searchObjects()` to call `setMaxResults(null)`, which removes the LIMIT clause entirely and fetches every row in the target register/schema table into PHP memory. diff --git a/openspec/changes/bundle-hygiene-apexcharts-lodash/proposal.md b/openspec/changes/bundle-hygiene-apexcharts-lodash/proposal.md index fc60ac43..a4018013 100644 --- a/openspec/changes/bundle-hygiene-apexcharts-lodash/proposal.md +++ b/openspec/changes/bundle-hygiene-apexcharts-lodash/proposal.md @@ -3,7 +3,7 @@ kind: code depends_on: [] --- -# softwarecatalog — drop unused own `apexcharts` dependency, scope `lodash` imports +# stackiq — drop unused own `apexcharts` dependency, scope `lodash` imports ## Why @@ -13,17 +13,17 @@ resolves to `3.54.1` (`node_modules/apexcharts/package.json`). A repo-wide grep for `apexcharts` under `src/` returns **zero matches** — the app never imports it directly, and `CnChartWidget` (nc-vue's chart component, which does depend on apexcharts) is never used either. Meanwhile -`@conduction/nextcloud-vue` — which softwarecatalog already depends on — +`@conduction/nextcloud-vue` — which stackiq already depends on — ships its own `apexcharts@^4.7.0` (`node_modules/@conduction/nextcloud-vue/package.json`), nested at `node_modules/@conduction/nextcloud-vue/node_modules/apexcharts` because the major-version ranges (`^3.50.0` vs `^4.7.0`) don't overlap and npm cannot dedupe them. Per the fleet convention ("apexcharts from nc-vue, not -duplicated"), softwarecatalog is shipping a second, unused, mismatched +duplicated"), stackiq is shipping a second, unused, mismatched copy of a large charting library (~500KB+ minified) in its own dependency tree for no functional benefit — pure bundle/install-size bloat. -**Unscoped `lodash` imports.** softwarecatalog depends on the full +**Unscoped `lodash` imports.** stackiq depends on the full `lodash` package (`package.json`) but only uses two functions from it: - `src/modals/object/ObjectModal.vue:226` — `import _ from 'lodash'`, @@ -38,7 +38,7 @@ single-function call sites. ## What Changes -- Remove `"apexcharts"` from softwarecatalog's own `package.json` +- Remove `"apexcharts"` from stackiq's own `package.json` dependencies. The app has no direct usage; if a future feature needs charting, it MUST use nc-vue's `CnChartWidget` (which already brings its own apexcharts) rather than re-adding a direct dependency. diff --git a/openspec/changes/bundle-hygiene-apexcharts-lodash/specs/frontend-bundle-hygiene/spec.md b/openspec/changes/bundle-hygiene-apexcharts-lodash/specs/frontend-bundle-hygiene/spec.md index 725ebd2a..52ec6339 100644 --- a/openspec/changes/bundle-hygiene-apexcharts-lodash/specs/frontend-bundle-hygiene/spec.md +++ b/openspec/changes/bundle-hygiene-apexcharts-lodash/specs/frontend-bundle-hygiene/spec.md @@ -1,25 +1,25 @@ ## ADDED Requirements ### Requirement: No duplicate charting library dependency -softwarecatalog's own `package.json` MUST NOT declare a direct dependency +stackiq's own `package.json` MUST NOT declare a direct dependency on `apexcharts` (or any other charting library already provided by `@conduction/nextcloud-vue`'s `CnChartWidget`) unless the app directly imports and uses it. Charting needs MUST be satisfied via nc-vue's shared component so only one version of the library is ever bundled fleet-wide. #### Scenario: No direct apexcharts usage exists -- GIVEN softwarecatalog's `src/` tree contains no direct `apexcharts` +- GIVEN stackiq's `src/` tree contains no direct `apexcharts` import and no usage of `CnChartWidget` - WHEN `package.json` is inspected - THEN it MUST NOT list `apexcharts` as a dependency #### Scenario: A future feature needs a chart -- GIVEN a developer wants to add a chart to softwarecatalog +- GIVEN a developer wants to add a chart to stackiq - WHEN they implement the widget - THEN they MUST use nc-vue's `CnChartWidget` (which supplies its own apexcharts dependency) - AND MUST NOT add a second, separately-versioned `apexcharts` dependency - to softwarecatalog's own `package.json` + to stackiq's own `package.json` ### Requirement: Utility library imports MUST be scoped to the function used Where only one or two functions from a large CJS utility library (e.g. `lodash`) are used, the import MUST reference the specific submodule (`lodash/cloneDeep`, `lodash/debounce`) rather than the package barrel. diff --git a/openspec/changes/bundle-hygiene-apexcharts-lodash/tasks.md b/openspec/changes/bundle-hygiene-apexcharts-lodash/tasks.md index 5b69e9d8..9b68cd9d 100644 --- a/openspec/changes/bundle-hygiene-apexcharts-lodash/tasks.md +++ b/openspec/changes/bundle-hygiene-apexcharts-lodash/tasks.md @@ -26,7 +26,7 @@ ## 3. Verification - [ ] 3.1 `npm run build` — confirm the production bundle no longer - includes a top-level `apexcharts` chunk from softwarecatalog's own + includes a top-level `apexcharts` chunk from stackiq's own dependency (only nc-vue's nested copy, if any chart widget pulls it in transitively). - [ ] 3.2 Run existing vitest suite (`npm run test:unit` or equivalent) to diff --git a/openspec/changes/contract-approval-ownership-guard/proposal.md b/openspec/changes/contract-approval-ownership-guard/proposal.md index 3be20c6f..cd3a9783 100644 --- a/openspec/changes/contract-approval-ownership-guard/proposal.md +++ b/openspec/changes/contract-approval-ownership-guard/proposal.md @@ -3,7 +3,7 @@ kind: code depends_on: [] --- -# softwarecatalog — contract approval per-object ownership guard (IDOR fix) +# stackiq — contract approval per-object ownership guard (IDOR fix) ## Why diff --git a/openspec/changes/i18n-wrap-hardcoded-object-modal-strings/proposal.md b/openspec/changes/i18n-wrap-hardcoded-object-modal-strings/proposal.md index 7928b508..cf76a576 100644 --- a/openspec/changes/i18n-wrap-hardcoded-object-modal-strings/proposal.md +++ b/openspec/changes/i18n-wrap-hardcoded-object-modal-strings/proposal.md @@ -3,12 +3,12 @@ kind: code depends_on: [] --- -# softwarecatalog — wrap hardcoded English UI strings in `t()` across object-action modals and ArchiMate settings +# stackiq — wrap hardcoded English UI strings in `t()` across object-action modals and ArchiMate settings ## Why ADR-004 requires ALL user-visible strings to go through -`t(appName, 'text')` so `l10n/nl.json` can translate them. softwarecatalog +`t(appName, 'text')` so `l10n/nl.json` can translate them. stackiq already runs its own translation-key tooling (`npm run test:l10n` → `tests/l10n/check-l10n.js` / `check-l10n-parity.js`), and that tooling reports 0 problems — but it only @@ -17,7 +17,7 @@ have full-locale parity. It cannot detect prose that was never wrapped in `t()` at all, so a large amount of user-visible English text ships untranslatable regardless of the user's chosen NC locale. -A repo-wide scan (`grep -c "t('softwarecatalog'" per .vue file`) found +A repo-wide scan (`grep -c "t('stackiq'" per .vue file`) found **31 `.vue` files over 50 lines with zero `t()` calls**. Spot-checks confirm these are not trivial wrapper components — they contain real, user-facing prose: @@ -62,7 +62,7 @@ prose: `CollapsibleSection.vue`, `StandardTabs.vue`, `Configuration.vue`, `DirectorySideBar.vue`, `SearchSideBar.vue`). -Net effect: a Dutch-locale user of softwarecatalog sees English text +Net effect: a Dutch-locale user of stackiq sees English text throughout the object-merge/upload/migration/mass-action modals and most of the admin settings screens — the exact silent English-fallback failure mode this sweep's i18n lens targets, just not via missing translation @@ -72,7 +72,7 @@ turned into a key at all. ## What Changes - Wrap every user-visible literal string in the files above in - `t('softwarecatalog', '…')` (interpolated values via the standard `t()` + `t('stackiq', '…')` (interpolated values via the standard `t()` placeholder syntax where needed), following the same convention already used correctly elsewhere in the app (e.g. `ComplianceMatrixView.vue`, `PaginationComponent.vue`). diff --git a/openspec/changes/i18n-wrap-hardcoded-object-modal-strings/specs/fe-object-modals/spec.md b/openspec/changes/i18n-wrap-hardcoded-object-modal-strings/specs/fe-object-modals/spec.md index 44c1898b..0605539a 100644 --- a/openspec/changes/i18n-wrap-hardcoded-object-modal-strings/specs/fe-object-modals/spec.md +++ b/openspec/changes/i18n-wrap-hardcoded-object-modal-strings/specs/fe-object-modals/spec.md @@ -1,7 +1,7 @@ ## ADDED Requirements ### Requirement: Object-action modal text MUST be translatable -Every user-visible string rendered by the object-action modal family (`src/modals/object/*.vue`, `src/modals/BulkSyncDialog.vue`) and the ArchiMate import/export settings section (`src/views/settings/sections/ArchiMateImportExport.vue`) MUST be wrapped in `t('softwarecatalog', '…')` with the English literal as the translation key, per ADR-004. +Every user-visible string rendered by the object-action modal family (`src/modals/object/*.vue`, `src/modals/BulkSyncDialog.vue`) and the ArchiMate import/export settings section (`src/views/settings/sections/ArchiMateImportExport.vue`) MUST be wrapped in `t('stackiq', '…')` with the English literal as the translation key, per ADR-004. No literal English prose (headings, table headers, button labels, empty-state text, placeholders) MAY be rendered directly in a `
+
blocks. The ESLint rule vue/enforce-style-attribute diff --git a/src/components/ContactpersonenList.vue b/src/components/ContactpersonenList.vue index 47a5f894..e6fd23a6 100644 --- a/src/components/ContactpersonenList.vue +++ b/src/components/ContactpersonenList.vue @@ -2,7 +2,7 @@
- {{ t('softwarecatalog', 'Loading contactpersonen...') }} + {{ t('stackiq', 'Loading contactpersonen...') }}
@@ -13,9 +13,9 @@
{{ contactpersoon.loading - ? t('softwarecatalog', 'Converting...') - : t('softwarecatalog', 'Convert to User') + ? t('stackiq', 'Converting...') + : t('stackiq', 'Convert to User') }} @@ -116,7 +116,7 @@ - {{ t('softwarecatalog', 'Change Password') }} + {{ t('stackiq', 'Change Password') }} @@ -127,7 +127,7 @@ - {{ t('softwarecatalog', 'Manage Groups') }} + {{ t('stackiq', 'Manage Groups') }} @@ -141,7 +141,7 @@ - {{ t('softwarecatalog', 'Disable User') }} + {{ t('stackiq', 'Disable User') }} @@ -155,7 +155,7 @@ - {{ t('softwarecatalog', 'Enable User') }} + {{ t('stackiq', 'Enable User') }} @@ -599,7 +599,7 @@ export default { if (contactIndex === -1) { showError( this.t( - 'softwarecatalog', + 'stackiq', 'Contactpersoon not found in organisation data', ), ) @@ -612,12 +612,7 @@ export default { contactObject.loading = true } else { console.error('Contactpersoon is not an object:', contactObject) - showError( - this.t( - 'softwarecatalog', - 'Invalid contact person data structure', - ), - ) + showError(this.t('stackiq', 'Invalid contact person data structure')) return } @@ -672,19 +667,13 @@ export default { console.info('Refreshing user info after successful conversion...') await this.refreshUserData() - showSuccess( - this.t('softwarecatalog', 'User account created successfully'), - ) + showSuccess(this.t('stackiq', 'User account created successfully')) } catch (error) { console.error('Error in convertToUser:', error) showError( - this.t( - 'softwarecatalog', - 'Failed to create user account: {error}', - { - error: error.message, - }, - ), + this.t('stackiq', 'Failed to create user account: {error}', { + error: error.message, + }), ) // Clear loading state on error - ensure it's an object first @@ -777,13 +766,13 @@ export default { async disableUser(contactpersoon) { try { await this.organisatieStore.disableUser(contactpersoon.id) - showSuccess(this.t('softwarecatalog', 'User disabled successfully')) + showSuccess(this.t('stackiq', 'User disabled successfully')) // Update the local contactpersoon data to reflect disabled status this.updateContactpersoonStatus(contactpersoon.id, true) } catch (error) { showError( - this.t('softwarecatalog', 'Failed to disable user: {error}', { + this.t('stackiq', 'Failed to disable user: {error}', { error: error.message, }), ) @@ -799,13 +788,13 @@ export default { async enableUser(contactpersoon) { try { await this.organisatieStore.enableUser(contactpersoon.id) - showSuccess(this.t('softwarecatalog', 'User enabled successfully')) + showSuccess(this.t('stackiq', 'User enabled successfully')) // Update the local contactpersoon data to reflect enabled status this.updateContactpersoonStatus(contactpersoon.id, false) } catch (error) { showError( - this.t('softwarecatalog', 'Failed to enable user: {error}', { + this.t('stackiq', 'Failed to enable user: {error}', { error: error.message, }), ) diff --git a/src/components/PaginationComponent.vue b/src/components/PaginationComponent.vue index a63bc924..c07b0f07 100644 --- a/src/components/PaginationComponent.vue +++ b/src/components/PaginationComponent.vue @@ -4,7 +4,7 @@
{{ - t('softwarecatalog', 'Page {current} of {total}', { + t('stackiq', 'Page {current} of {total}', { current: currentPage, total: totalPages, }) @@ -16,14 +16,14 @@
- {{ t('softwarecatalog', 'First') }} + {{ t('stackiq', 'First') }} - {{ t('softwarecatalog', 'Previous') }} + {{ t('stackiq', 'Previous') }} @@ -50,29 +50,27 @@ - {{ t('softwarecatalog', 'Next') }} + {{ t('stackiq', 'Next') }} - {{ t('softwarecatalog', 'Last') }} + {{ t('stackiq', 'Last') }}
- +
diff --git a/src/components/cards/OrganisatieCard.vue b/src/components/cards/OrganisatieCard.vue index 9dd9a61a..f5360c3c 100644 --- a/src/components/cards/OrganisatieCard.vue +++ b/src/components/cards/OrganisatieCard.vue @@ -1,8 +1,7 @@ /** * OrganisatieCard.vue * Custom card component for displaying organisatie objects -* @category Components * @package softwarecatalog * @author Ruben Linde * @copyright -2024 * @license EUPL-1.2 -https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12 * @version 1.0.0 * -@link https://github.com/opencatalogi/softwarecatalog */ +* @category Components * @package stackiq * @author Ruben Linde * @copyright 2024 * +@license EUPL-1.2 https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12 * +@version 1.0.0 * @link https://github.com/ConductionNL/stackiq */ - {{ t('softwarecatalog', 'Preview merge') }} + {{ t('stackiq', 'Preview merge') }} @@ -254,15 +246,13 @@ export default { const obj = data && data['@self'] !== undefined ? data : data.object || data this.sourceName = - obj.name - || obj.name - || t('softwarecatalog', 'Unknown organisation') + obj.name || obj.name || t('stackiq', 'Unknown organisation') this.status = obj.status || '' this.mergedInto = obj.mergedInto || '' } catch (e) { // Non-fatal — the panel still renders merge controls with an // empty source name rather than failing the whole detail page. - this.sourceName = t('softwarecatalog', 'Unknown organisation') + this.sourceName = t('stackiq', 'Unknown organisation') } finally { this.loading = false } @@ -310,10 +300,7 @@ export default { || String(org.id), })) } catch (e) { - this.error = t( - 'softwarecatalog', - 'Could not load target organisations.', - ) + this.error = t('stackiq', 'Could not load target organisations.') } finally { this.loadingTargets = false } @@ -347,7 +334,7 @@ export default { this.showConfirm = true } catch (e) { this.error = - e.message || t('softwarecatalog', 'Could not preview the merge.') + e.message || t('stackiq', 'Could not preview the merge.') } finally { this.previewing = false } @@ -374,13 +361,10 @@ export default { this.success = true this.status = 'merged' this.mergedInto = String(this.selectedTarget.value) - showSuccess( - t('softwarecatalog', 'Organisation successfully merged.'), - ) + showSuccess(t('stackiq', 'Organisation successfully merged.')) } catch (e) { this.confirmError = - e.message - || t('softwarecatalog', 'Could not merge the organisations.') + e.message || t('stackiq', 'Could not merge the organisations.') } finally { this.busy = false } diff --git a/src/components/organisations/OrganisationSwitcher.vue b/src/components/organisations/OrganisationSwitcher.vue index 2e8cd988..1c3a21ea 100644 --- a/src/components/organisations/OrganisationSwitcher.vue +++ b/src/components/organisations/OrganisationSwitcher.vue @@ -51,7 +51,7 @@ - {{ t('softwarecatalog', 'Manage members') }} + {{ t('stackiq', 'Manage members') }} - SPDX-License-Identifier: EUPL-1.2 - - - Ratings & reviews body widget (softwarecatalog#375) — registered via + - Ratings & reviews body widget (stackiq#375) — registered via - src/customComponents.js and placed on ModuleDetail's `bodyWidgets` - (same escape hatch as ContractApprovalPanel/OrganisationMergePanel). - Shows the approved-only aggregate (average + count) computed server-side @@ -17,7 +17,7 @@