Skip to content
This repository was archived by the owner on May 29, 2026. It is now read-only.

feat: dashboard sharing, resource uploads, SVG sanitiser, config menu#73

Closed
rubenvdlinde wants to merge 16 commits into
developmentfrom
feature/replica-spec-proposals
Closed

feat: dashboard sharing, resource uploads, SVG sanitiser, config menu#73
rubenvdlinde wants to merge 16 commits into
developmentfrom
feature/replica-spec-proposals

Conversation

@rubenvdlinde

Copy link
Copy Markdown
Contributor

Summary

Lands the next batch of dashboard work in 7 logically-split commits:

  • Dashboard sharing — user/group shares with view/edit permissions, notifications (dashboard_shared, dashboard_ownership_transferred), and a UserDeletedListener that cascades cleanup + admin-retention transfer per REQ-SHARE-011/012/013. Includes migration Version001005Date20260430120000 (table oc_mydash_dashboard_shares).
  • Resource uploadsResourceController + ResourceService with PUT/PATCH multipart support (via ResourceUploadRequestParser), MIME parity validation (ImageMimeValidator), and a typed exception hierarchy.
  • SVG sanitiser — strips active content (script, on*, javascript:, external entities) before storage; integrated into the resource pipeline.
  • UIDashboardConfigMenu, DashboardConfigModal, WidgetPickerModal + Conduction/Sendent brand assets.
  • Core wiring — share/resource/sharee/revoke routes, share-aware DashboardService + PermissionService + frontend store/api updates; appinfo bumped to 1.0.4-unstable.0.
  • Chore.gitignore now excludes the local sendent vendor extract; openspec proposals scrubbed of explicit competitor name (kept the technical content).
  • Docsmydash-walkthrough/findings.md.

Test plan

  • composer check:strict passes (PHPCS, PHPMD, Psalm, PHPStan)
  • composer test:unit green (new tests for SvgSanitiser, ImageMimeValidator, ResourceService, ResourceServiceSvgIntegrationTest, DashboardShareServiceFollowupsTest, UserDeletedListenerTest)
  • Migration Version001005Date20260430120000 applies cleanly on a fresh DB and is idempotent on re-run
  • Manual: share a dashboard with a user, confirm notification rendered + recipient sees the share
  • Manual: delete a user — owned shares cascade, admin-retained dashboards transfer
  • Manual: upload PNG/JPG/SVG via resource endpoint; SVG with <script> is stripped; mismatched MIME rejected
  • Manual: open DashboardConfigMenu and WidgetPickerModal from the dashboard view

rubenvdlinde and others added 16 commits April 30, 2026 07:20
Walkthrough of all 9 archived OpenSpec features uncovered three critical
runtime bugs in features marked "implemented". This commit restores them
and clears the supporting quality stack.

Critical fixes
--------------
- ConditionalRule.createdAt: switch ?DateTime to ?string ('c' format) to
  match sibling entities. Doctrine DBAL cannot bind a DateTime without
  an explicit addType registration; INSERT now succeeds. Same change in
  AdminSetting.updatedAt.
- AdminTemplateService + TemplateService: drop the ramsey/uuid require
  and inline the random_bytes(16) UUID generator already used by
  DashboardFactory. The ramsey class was never autoloaded at runtime
  (Application.php does not require vendor/autoload.php), so admin
  template creation and template-based dashboard provisioning both
  threw 500.
- DashboardService: stop hardcoding PERMISSION_FULL when auto-creating
  user dashboards. Read defaultPermissionLevel and defaultGridColumns
  from AdminSettingMapper and pass them to DashboardFactory.

Quality
-------
- NamedParametersSniff: skip Entity-magic accessors (set*/get*/is*) on
  classes extending OCP\AppFramework\Db\Entity. Entity::__call uses
  args[0], so named arguments break those calls. Eliminates 4 false
  positives.
- DashboardService: add the missing WidgetPlacement import (PHPMD).
- PageController: replace \OC::server->get(IManager::class) with proper
  DI (Psalm).
- UserAttributeResolver: drop the non-existent IUser::getLanguage() call
  and read 'core/lang' from IConfig instead (Psalm).

Frontend warnings
-----------------
- TileEditor + WidgetStyleEditor: NcSelect was passing 'Icon' as the
  vue-select label key (option.Icon does not exist). Use input-label
  for the visible label and label="label" for the option key.
- AdminSettings: replace HTML <label> + bare NcSelect with input-label
  prop, satisfying NcSelect's accessibility requirement.
- DashboardSwitcher / WidgetPicker / WidgetWrapper / TileEditor /
  WidgetStyleEditor: add aria-label or input-label to icon-only NcButton
  / unlabeled NcTextField / inline color-pick NcButton instances.

Tests: 106 / 106 passing (293 assertions). composer phpcs / phpmd /
psalm clean. End-to-end API verified for conditional rules, admin
templates, prometheus metrics.
…creenshots

PHPStan
-------
- Add autoload-dev mapping for nextcloud/ocp's OCP\* and NCU\* namespaces.
  The package ships PHP stubs but declares no autoload section, so PHPStan
  could not resolve any Nextcloud type and reported every OCP\IUser /
  IL10N / IURLGenerator etc. call as "unknown class" - 722 errors total.
  Registering the namespace in our autoload-dev makes the stubs reachable.
- Tighten phpstan.neon scanDirectories to point at the OCP/ and NCU/
  subdirectories (not the package root) and add focused ignoreErrors for
  Doctrine\DBAL\Schema\Table calls (provided by Nextcloud's 3rdparty
  bundle, not our app's vendor) and the JSONResponse strict-status union
  / Entity::jsonSerialize stub gaps.
- Result: 722 -> 0 errors. PHPStan is now meaningful CI signal.

Real type fixes surfaced once OCP became visible
------------------------------------------------
- AdminSettingMapper::setSetting(): pass formatted ISO string to
  setUpdatedAt() instead of a DateTime instance (the entity stores the
  timestamp as ?string).
- DashboardResolver, DashboardService, TemplateService: use 1 instead of
  true for setIsActive(int), and 0 instead of false for the
  getIsCompulsory()/getIsVisible() comparisons. Same coercion semantics
  at runtime, but PHPStan can verify the types.
- AdminTemplateService::createTemplate(): cast bool $isDefault to int
  before passing to the SMALLINT column setter.
- DashboardResolver::getEffectivePermissionLevel(): drop the unreachable
  null-fallback branch (Dashboard::permissionLevel is non-nullable with
  PERMISSION_FULL default) and the unused settingMapper / AdminSetting
  imports that only existed to feed it.
- ConditionalService / PermissionService: switch
  getIsVisible()/getIsCompulsory() === false to === 0 (the Entity stores
  these as int flags, not booleans).

phpunit-unit.xml rename
-----------------------
- git mv phpunit.xml -> phpunit-unit.xml to match the convention used
  across other Conduction apps. Composer scripts (test:unit, test:all,
  test:coverage, test) updated to pass --configuration explicitly so
  PHPUnit no longer needs to auto-discover.

Screenshots
-----------
- Add docs/screenshots/admin-settings.png, customize-panel-widgets.png,
  customize-panel-dashboards.png, template-create-modal.png to document
  the main customize / admin / template flows. Captured at 1440x1100.

Tests: 106 / 106 passing, 293 assertions.
PHPCS / PHPMD / Psalm / PHPStan: all clean.
…form

Adds 25 OpenSpec change proposals modelling a complete dashboard sharing
and runtime experience for MyDash. Specs are organised across 4 dependency
layers (foundation → extensions → UI surfaces → runtime shell):

Dashboards capability (4): multi-scope-dashboards, default-dashboard-flag,
active-dashboard-resolution, fork-current-as-personal — adds group_shared
type, default flag, 7-step resolution chain, fork-from-current action.

Admin (3): group-priority-order, group-routing, allow-personal-dashboards-flag
— admin-controlled group ordering, primary-group resolver, runtime gating.

Grid layout (2): responsive-grid-breakpoints, widget-collision-placement —
4 viewport breakpoints, moveScale reflow, deterministic add-widget placement.

Widgets (5 + 2 modal/menu): text-display-widget, label-widget, image-widget,
link-button-widget, nc-dashboard-widget-proxy, widget-add-edit-modal,
widget-context-menu — five widget types plus shared add/edit modal and
right-click menu.

Resources (3): resource-uploads, resource-serving, svg-sanitisation —
admin-only base64 upload pipeline, immutable-cached serve route, DOM-based
SVG whitelist.

Icons (2): dashboard-icons, custom-icon-upload-pattern — curated MDI
registry plus URL discriminator for uploaded icons.

UI (2): dashboard-switcher-sidebar, runtime-shell — slide-in 3-section
sidebar, page-level shell with canEdit gate.

Infra (1): initial-state-contract — typed PHP→JS bootstrap contract.

Sharing follow-ups (1): dashboard-sharing-followups — extends the existing
dashboard-sharing capability with notifications, bulk management, and
deletion cascade with admin-retention guard.

Includes the baseline dashboard-sharing capability spec under openspec/specs/.

All 25 changes pass openspec validate --strict. Implementation (opsx-apply)
is the next step for each.
- Add /sendent-workspace-main/ and /2026.*_sendent-workspace-main.zip
  to .gitignore (research-only vendor extract, not part of the app).
- Remove explicit "Sendent" mentions from image-widget and
  link-button-widget proposals; keep the technical content intact.
Strips active content (script tags, on* handlers, javascript: URLs,
external entities) from SVG payloads before they hit storage. Used by
the resource-upload pipeline to make user-supplied SVGs safe to serve
inline. New InvalidSvgException is thrown when the input cannot be
made safe.
- ResourceController exposes the upload + serve endpoints, with a
  dedicated ResourceUploadRequestParser to handle PUT/PATCH multipart
  payloads (PHP $_FILES doesn't populate for those verbs).
- ResourceService owns the upload pipeline and routes SVGs through
  SvgSanitiser before persisting.
- ImageMimeValidator enforces declared-vs-actual MIME parity to block
  smuggled-payload attacks (image/png claim with non-PNG bytes, etc.).
- New typed exceptions (ResourceException base + nine concrete kinds)
  produce predictable HTTP responses from the controller.
…cade

- DashboardShare entity, mapper, service, and API controller for
  user/group sharing with view/edit permission grants.
- Migration Version001005 adds the oc_mydash_dashboard_shares table
  via DashboardShareTableBuilder.
- Notifier renders dashboard_shared and dashboard_ownership_transferred
  notifications (REQ-SHARE-011).
- UserDeletedListener cascades share cleanup and transfers
  admin-retained dashboards on user deletion (REQ-SHARE-012, 013).
- New PersonalDashboardsDisabledException for the admin-disabled-personal
  scope path used by DashboardService.
- DashboardConfigMenu: top-bar entry with Conduction + Sendent brand
  links and access to the dashboard config modal.
- DashboardConfigModal: edit dashboard metadata, manage shares.
- WidgetPickerModal: dedicated picker UI for adding widgets.
- Add img/conduction-logo.png and img/sendent-logo.png brand assets
  used by the menu.
- Register share, resource, getById, sharee-search, and revoke routes
  in appinfo/routes.php.
- Extend DashboardApiController + DashboardService + DashboardFactory +
  DashboardMapper + Dashboard entity to surface share-aware lookups,
  ownership transfer, and the personal-disabled scope path.
- PermissionService gains share-derived view/edit checks.
- Frontend store + api.js learn to fetch shares, sharees, and to call
  the new endpoints; Views.vue mounts DashboardConfigMenu /
  DashboardConfigModal / WidgetPickerModal.
- Bump appinfo version to 1.0.4-unstable.0.
Captured walkthrough notes from a working session through the dashboard
flows; left as raw findings for follow-up tickets and spec refinement.
The DashboardShare entity declares an updatedAt field but the table
builder forgot to add the matching column, so updates would silently
fail to persist on a fresh schema. Adds the nullable DATETIME column.
@rubenvdlinde

Copy link
Copy Markdown
Contributor Author

Closing as duplicate.

This branch was built from a stale base and parallel-implements work that has already shipped to `development` through other PRs:

Both branches added the same files (`SvgSanitiser`, `ImageMimeValidator`, `DashboardShareService`, `ResourceController`, `DashboardShareApiController`, etc.) with different implementations, plus a duplicate migration version (`Version001005Date20260430120000` here vs `Version001005Date20260430000000` on dev) — merging would either overwrite shipped code or fail to apply the migration.

Resubmitting the truly unique deltas (walkthrough findings, screenshots, sendent-name scrub on the dev versions of the image-widget / link-button-widget proposals) as a smaller follow-up PR.

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant