Skip to content

Bevindingen Remko#896

Open
rubenvdlinde wants to merge 81 commits intofeature/accept-validationfrom
feature/php-linting
Open

Bevindingen Remko#896
rubenvdlinde wants to merge 81 commits intofeature/accept-validationfrom
feature/php-linting

Conversation

@rubenvdlinde
Copy link
Contributor

Nieuwe Organisatie aanmaken en accepteren zet niet een gebruiker direct om.
De organisatie die aangemaakt word in openregister heeft niet het zelfd uuid als die in het voorzieningen register

Calls na eerste inlog zijn heel traag
Mijn account update contactpersoon niet. Contactpersoon update wel mijn account
Niet ingelogd geeft 404 bij het ophalen van logo's

rubenvdlinde and others added 8 commits February 22, 2026 11:31
…rience

Eliminates ~500ms overhead from every API request by moving background job
registration from boot() (runs every request) to info.xml (runs once at
install). Also optimizes OrganisationService with in-memory caching and
FileChangeListener with early path checks to reduce first-user login from
7.8s to 4.1s.

- Remove 8 redundant $jobList->has() DB queries from boot()
- Move 4 background jobs to declarative info.xml registration
- Add request-level in-memory cache to getUserOrganisations()
- Pass pre-loaded organisations to getActiveOrganisation() to avoid double lookup
- Only run admin/RBAC checks on org creation, not every lookup
- Cache extraction scope in FileChangeListener (1 DB read vs 128 per skeleton)
- Check file path before settings DB read for early skip

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Files uploaded by admin or non-admin users were stored under that user's
Nextcloud folder, but public/anonymous access (via @publicpage endpoint)
could not resolve the file because IRootFolder::getById() requires a user
context.

Added getFileViaKnownUsers() fallback in FilesController::show() that
tries the object owner, then the OpenRegister system user, then admin
to find the file by ID. Also improved FolderManagementHandler::getNodeById()
to fall back to root folder lookup.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Remove all catch-and-fallback-to-blob patterns that silently return
empty results from the unused openregister_objects table when
MagicMapper/UnifiedObjectMapper operations fail. Operations now
propagate errors instead of hiding them.

Changed: find(), tryMagicMapperFindAll(), insert(), update(),
deleteObjects(), publishObjects(), depublishObjects(),
shouldUseMagicMapper(), shouldUseMagicMapperForRegisterSchema()

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Register lookups for file/folder operations should not apply multitenancy
filtering, as registers are shared infrastructure that all users need to
access. This fixes non-admin users being unable to upload files to objects
because the register lookup was blocked by org-based filtering.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
rubenvdlinde and others added 21 commits February 22, 2026 19:32
When creating organisations, a unique constraint violation on the slug
column would crash the entire request. Now the service catches the
DbalException and returns the existing organisation instead.

Also adds findBySlug() to OrganisationMapper and downgrades the noisy
FilePropertyHandler isFileProperty log from warning to debug.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
When createOrganisation encounters a slug collision and finds an
existing entity by slug, update its UUID to match the requested
UUID if they differ. This ensures the entity UUID stays in sync
with the object UUID, preventing broken references in downstream
code that relies on UUID-based lookups.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Set SITE_TITLE=Softwarecatalogus on nginx frontend service
- Add 'extend' to reserved property list in MagicSearchHandler to
  prevent it from being treated as a search filter

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
… results

When findByRelationBatchInSchema() returned 0 results (e.g., empty magic
table or no matching objects), the cache was not populated due to a
`if (count > 0)` guard. This caused every entity to fall through to the
slow per-entity findByRelation() path, which does LIKE scans on the blob
table and iterates all magic mapper tables.

On the test environment with 3,093 organisations and an empty contactpersoon
magic table, this caused _extend[]=contactpersonen to take 10-13 seconds
instead of <1 second.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
OAS documentation generation was failing with 500 for org-bound
registers because RegisterMapper::find() applied multitenancy filtering.
Registers with an organisation UUID were inaccessible to users outside
that org, including for public API documentation.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…roperties

Linked diensten and koppelingen were not shown on applicatie pages because
resolveSchemaReference() and getSchema() applied multi-tenancy filtering
when looking up schemas internally. This caused $ref references like
"#/components/schemas/dienst" to fail resolution when the schema belonged
to a different organization than the current user, breaking both:
- The single-object API (_extend=diensten on show endpoint)
- The list API (batch preloadInverseRelationships path)

The fix bypasses RBAC and multi-tenancy for internal schema resolution
since schemas must always be accessible when resolving inversedBy
references regardless of the requesting user's organization.

Resolves: VNG-Realisatie/Softwarecatalogus#373

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The OAS specification endpoint was only returning 1 of 6 schemas because
findMultiple() applied multi-tenancy filtering by default. Schemas
belonging to organizations other than the current user's were excluded.

OAS generation is a system-level operation that needs access to all
schemas in a register, regardless of organization ownership.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The export endpoint returned 500 because registerMapper.find() applied
multi-tenancy filtering by default. Registers with an organisation set
(like voorzieningen) were excluded when the current user's org didn't
match. The lookup is only needed for filename generation — access
control is already handled by setRegister/setSchema above.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Replace Apache-2.0 LICENSE with EUPL-1.2. Update appinfo/info.xml,
composer.json, and package.json license fields.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- ObjectsController: Accept both 'format' and 'type' query parameters
  for export, so CSV downloads work from all endpoints consistently.
- UserService: Wrap getUserOrganisationStats() in try-catch to prevent
  500 errors on /api/user/me when organisation data is unavailable.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add schema title to the delete dialog heading and body text so users
can see what type of object (applicatie, dienst, etc.) they are about
to delete, instead of a generic confirmation message.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add PR checks for PHP quality (PHPCS, PHPMD, phpmetrics) and frontend
quality (ESLint, stylelint) that run on pull requests to main branches.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add OpenSpec config, change tracking for deep link registry and global
search features, and shared Nextcloud app specifications.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add DeepLinkRegistryService for cross-app entity linking, notes and
tasks controllers/services with DTOs, comments entity listener, object
cleanup listener, and fix global unified search across registers.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
rubenvdlinde and others added 30 commits March 2, 2026 00:24
The Nextcloud App Store schema does not accept EUPL-1.2 as a valid
licence value, causing all release uploads to fail with HTTP 400.
Revert to 'agpl' which is in the accepted set.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The unstable release workflow now derives the version from the branch's
own info.xml instead of from main, making version bumps self-contained.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…logging

OrganisationMapper now catches duplicate slug constraint violations and
updates the existing organisation instead of throwing. Also removed 5
verbose INFO-level log statements that dumped full entity state on every save.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…rning

- n8n_workflows config: use raw.githubusercontent.com instead of
  github.com/blob/ URL which returns HTML instead of JSON
- MagicMapper: remove owner from critical null-field warning since
  owner can legitimately be null for public/unauthenticated objects

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
When a background text extraction job runs for an object that was
deleted between queueing and execution, this is expected behavior
and should not produce a warning in the log.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Nextcloud's ControllerMethodReflector parses annotations matching
@[A-Z]\w+ and tries to split parenthesized values by '='. The
@SuppressWarnings(PHPMD.xxx) annotations don't contain '=' causing
PHP warning "Undefined array key 1" on every controller request.

Lowercasing to @SuppressWarnings makes the reflector skip them while
PHPMD still reads them correctly (case-insensitive).

Fixes ~12,000+ PHP warnings in the Nextcloud log.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
findExtendedBy() was re-fetching each schema via find() just to get
its uuid and slug, triggering 3 extra queries per schema (schema
SELECT + org preferences + org hierarchy CTE). With 67 schemas this
caused ~201 redundant queries per request.

Now accepts optional knownUuid/knownSlug parameters so the controller
can pass already-loaded values, reducing total queries from ~269 to ~68.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Batch operations and request-scoped caching to eliminate N+1 query
patterns. Query warnings dropped from 29 to 10 (65% reduction),
QueryBuilder warnings from 46 to 21 (54% reduction).

Changes:
- Add batch findAllExtendedBy(), getStatisticsGroupedBySchema() methods
- Batch file tag loading in RenderObject (2 queries instead of 2*N)
- Pre-load schemas in bulk for CacheHandler name resolution
- Pre-seed export name map from already-loaded objects
- Cache organisation stats across duplicate buildUserDataArray() calls
- Add request-scoped find() cache to SchemaMapper and RegisterMapper
- Fix processFilePropertiesWithRollback log level (error -> debug)

Resolved endpoints (0 warnings): create, export, index, schemas, publications
Reduced: updateMe 135->124, patch data-dependent

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
PHP converts ?_facets[]=foo&_facets[]=bar into a numerically-indexed
array; expand these by joining with comma before passing to
expandFacetConfig / expandFacetConfigFromAllSchemas. Also adds
CacheWarmupJob background job and app store images/screenshots.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…rash on UUID change

When a slug collision occurs during organisation creation, the save() path
sets a new UUID on the entity before calling update(). The previous update()
called findByUuid(entity->getUuid()) to retrieve the old state for event
dispatch — but the new UUID doesn't exist yet, causing a DoesNotExistException
and a 500 error from OrganisationController.

Fix: Find the old entity by database integer ID using a QueryBuilder query.
This is stable across UUID changes. Falls back to entity itself as old state
if the ID lookup fails.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ias error

Nextcloud's QueryBuilder registers the table with the prefix as alias
(e.g. *PREFIX*storages) on PostgreSQL, so ->join('storages', ...) fails
with "alias not part of FROM clause". Fix by using an explicit 's' alias
in both ->from() and ->join().

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Rename website/ → docusaurus/ (preserves git history)
- Move website/docs/ → docs/ at repository root
- Existing dev-notes from root docs/ merged into docs/development-notes/
- Update docusaurus.config.js: path '../docs', editUrl → docusaurus/
- Update documentation.yml: trigger development branch, cd docusaurus
- Update CONTRIBUTING.md copy destination to docs/development/
- Update logo.svg with blue hexagon app-store icon
- Create img/app-store.svg with blue hexagon connection icon

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Add Psalm (Static Analysis) step to code-quality.yml php-quality job

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Real bugs fixed:
- BulkController: add missing registerId arg to deleteObjectsBySchema()
- TablesController: replace non-existent findBySlug() with find()
- MagicMapper: fix @return void docblock to @return array on updateTableStructure()
- ImportHandler: declare missing static property $isDependencyCheckActive
- DeepLinkRegistration: strtr() positional args (replacePairs is not a valid param name)
- FilePropertyHandler: rename duplicate 'file' array key to 'filename'
- ValidationHandler: change $saveCallback type from callable to array
- EntityRecognitionHandler: add is_string() guards before json_decode() calls
- TaskService: change return types of createTask/updateTask to ?array (nullable)
- UnifiedObjectMapper: rename $_rbac/$_multitenancy params to $rbac/$multitenancy to match parent

Psalm config:
- Add suppressions for OC\Files\AppData\Factory, Doctrine\DBAL, OCA\DAV, Sabre, OC\DB
- Add InvalidThrow and UnsupportedPropertyReferenceUsage suppressors

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants