Commit Graph

10 Commits

Author SHA1 Message Date
Matthieu a18e1f575f refactor(client-portal) : remove client portal feature entirely
Pull Request — Quality gate / Backend (PHP CS + PHPUnit) (pull_request) Successful in 1m11s
Pull Request — Quality gate / Frontend (build) (pull_request) Successful in 1m17s
- drop ClientPortal module, ClientTicket entity, ROLE_CLIENT and all couplings (Task, TaskDocument, User, Notification) back to an internal-only model

- migration drops client_ticket / user_allowed_projects / related FK columns and removes leftover external client accounts (would otherwise be promoted to ROLE_USER)

- remove client-portal frontend module, admin tickets tab, user portal section, portal nav item and portal/clientTicket i18n keys

- fix directory nav icon (invalid mdi:contact-multiple-outline -> mdi:card-account-details-outline)

- add 'make sync-permissions' target, wire it into install/db-reset and the prod deploy script
2026-06-22 09:49:44 +02:00
Matthieu 987df54175 fix(project-management) : skip caldav sync when unconfigured and require task project 2026-06-22 09:05:16 +02:00
Matthieu 96ef1bf436 fix(security) : harden ROLE_CLIENT isolation + tighten cross-module contracts
Findings from the post-migration code review. The arrival of ROLE_CLIENT
exposed internal endpoints still guarded only by IS_AUTHENTICATED_FULLY (or no
security), reachable by a client. Verified by re-running a multi-role smoke
test (client -> 403, internal roles -> 200).

Security (closed real client-isolation holes):
- TaskDocumentDownloadController: add ownership check (admin all / client only
  own clientTicket docs / user only task-linked docs) — the custom download
  bypassed the cloistered provider.
- Share browse/download/search/status controllers: IS_AUTHENTICATED_FULLY ->
  ROLE_USER (SMB share is internal).
- User Get/GetCollection: add security ROLE_USER (was exposing the internal
  directory to clients).
- BookStackLink GetCollection/Post/Delete: IS_AUTHENTICATED_FULLY -> ROLE_USER.

Contracts / robustness:
- TaskInterface gains getProject(): ?ProjectInterface; TimeTracking export
  controller/service drop concrete cross-module entities for repo interfaces.
- Shared MCP Serializer signatures widened to the contracts (user/projectRef/
  taskRef/tags/users); project()/userFull()/etc. kept concrete (use getters
  outside the contracts).
- RecurrenceHandler: null-guard before findMaxNumberByProjectForUpdate().

180 tests green, cs-fixer clean, routes unchanged.
2026-06-21 19:31:09 +02:00
Matthieu da3d190216 refactor(core) : final legacy cleanup — app is 100% modular
LST-60 (3.3). Closes the modular-monolith migration. src/Entity was already
empty; this removes the last legacy residue.

- Doctrine: drop the legacy "App" mapping (empty src/Entity). resolve_target_
  entities already targets modules only.
- MCP User tools (Reference/) -> Core/Infrastructure/Mcp/Tool; MCP Serializer
  -> Shared/Infrastructure/Mcp (33 usages repointed).
- Controllers (mark-all-read, notification unread-count, regenerate-api-token,
  user-avatar) -> Core/Infrastructure/Controller. TokenEncryptor -> Shared/
  Infrastructure/Service (11 usages). AppVersion resource+provider -> Shared.
  ContractType enum -> Core/Domain/Enum.
- src/{Entity,State,Controller,Service,Enum,ApiResource} now empty; routes,
  MCP tool names and public API unchanged.

180 tests green, mapping valid, no route regression, cs-fixer clean.
Note: final Malio visual harmonisation (subjective) left to the PO.
2026-06-21 01:25:19 +02:00
Matthieu a2bbc8311d fix(client-portal) : forbid SMB share-link document creation for client users
Security hardening on the document POST that phase 1 widened to ROLE_CLIENT:
a client user could reach the share-link path (arbitrary SMB file reference)
instead of an upload. Now the sharePath branch is admin-only — client users
must upload. attachTarget already scopes documents to the client's own ticket.

178 tests green.
2026-06-21 01:03:24 +02:00
Matthieu 808a290845 feat(client-portal) : phase 1 foundations — ROLE_CLIENT hardening + ClientTicket (back)
LST-69 (3.2) phase 1. New ClientPortal module + security foundations for the
client portal (spec docs/superpowers/specs/2026-03-15-client-portal-design.md).

- Security: User::getRoles() no longer adds ROLE_USER to ROLE_CLIENT users;
  role_hierarchy ROLE_ADMIN: [ROLE_USER, ROLE_CLIENT]. Existing Task/Project/
  Client/TimeEntry/metadata endpoints already required ROLE_USER -> a pure
  ROLE_CLIENT is walled off (verified: 403).
- User (Core): client (ManyToOne ClientInterface, SET NULL) + allowedProjects
  (ManyToMany ProjectInterface). UserInterface extended (getClient/
  getAllowedProjects).
- New ClientTicket entity (module ClientPortal) + enums + repository + API with
  per-client isolation (ClientTicketProvider: own tickets ∩ allowedProjects),
  per-project numbering under advisory lock (rejects if user.client null),
  status transition rules. ClientTicketInterface contract for Task/TaskDocument.
- TaskDocument generalized: task nullable + clientTicket (CASCADE) + CHECK;
  per-role access. Task.clientTicket exposed in task:read.
- Additive migration; demo client fixtures.
- Tenancy tests assert the isolation invariant (a client never sees another
  client's tickets) rather than brittle absolute counts (shared test DB).

178 tests green, mapping valid, cs-fixer clean.
2026-06-21 00:46:26 +02:00
Matthieu 90682e809c feat(integration) : migrate Gitea/BookStack/Zimbra/Share into module (back)
LST-68 (2.6) backend. Behaviour-preserving move of the external integrations
into src/Module/Integration/. All 26 routes and securities unchanged.

- 5 entities (4 *Configuration singletons + TaskBookStackLink) + 5 repositories
  (Domain interfaces + Doctrine impls, bound). TaskBookStackLink.task now
  references TaskInterface (contract).
- Domain (FileSource interface, SharePathResolver, share DTOs + exceptions);
  Infrastructure (GiteaApiService, BookStackApiService, SmbFileSource, 15
  ApiResources, 21 State, 4 Share controllers).
- Cross-module couplings via abstractions: CalDavService (PM) injects
  ZimbraConfigurationRepositoryInterface; PM TaskDocument consumers repointed
  to the module's FileSource/SharePathResolver; Gitea/BookStack State load
  tasks via TaskRepositoryInterface (concrete Project read for integration
  fields — documented). ZimbraTestConnection keeps CalDavService (no build
  cycle). TokenEncryptor stays shared.
- IntegrationModule registered; doctrine mapping added.
- #[Auditable] + Timestampable on the 4 Configuration entities (additive
  migration on the 4 *_configuration tables).

163 tests green, container compiles (no cycle), no route regression, cs-fixer clean.
2026-06-20 20:16:20 +02:00
Matthieu c5738d269b feat(directory) : migrate Client into Directory module (back)
LST-58 (2.4), part 1/2 — Client move. Prospect + repertoire front are pending
the product spec and will be added on this branch afterward.

- Client entity moved to src/Module/Directory/Domain/Entity; repository split
  into Domain/Repository/ClientRepositoryInterface + Doctrine impl (bound in
  services.yaml). 5 client MCP tools moved to Infrastructure/Mcp/Tool, now
  injecting the interface.
- resolve_target_entities ClientInterface repointed to Directory\Client;
  Directory mapping added; DirectoryModule registered (id directory, 2 RBAC
  perms). Client.projects relation now uses ProjectInterface -> Directory no
  longer depends on ProjectManagement.
- ProjectManagement Create/UpdateProjectTool inject Directory's
  ClientRepositoryInterface; Serializer and fixtures repointed.
- Garde-fous: #[Auditable] + Timestampable/Blamable on Client (additive
  migration: created_at/updated_at + created_by/updated_by FK ON DELETE SET
  NULL + COMMENT).

161 tests green, mapping valid, no API route regression, cs-fixer clean.
2026-06-20 18:51:49 +02:00
Matthieu c90d91d6c4 feat(project-management) : add timestampable/blamable to Task and Project (additive)
Tranche 3 of LST-65. Task and Project adopt TimestampableBlamableTrait.

- Additive migration on task and project: created_at/updated_at (nullable),
  created_by/updated_by (nullable INT, FK to "user" ON DELETE SET NULL) +
  indexes + COMMENT ON COLUMN. down() drops only the added objects.
- Trait fields stay out of the existing API groups (trait carries its own).
- Functional test (TaskTimestampableTest) confirms created_at on persist and
  updated_at refresh on update.

161 tests green, no destructive migration.
2026-06-20 17:05:47 +02:00
Matthieu 23809f165e feat(project-management) : migrate core Projects/Tasks domain into module (back)
Tranche 2 of LST-65. Mechanical, behaviour-preserving move of the core
business domain into src/Module/ProjectManagement/. API operations,
securities, uriTemplates and the 38 MCP tool names are all unchanged.

- 10 entities + 2 enums moved to Domain/{Entity,Enum}; intra-module
  relations stay concrete, cross-module relations go through contracts
  (Project.client -> ClientInterface, Task/TaskDocument users ->
  UserInterface).
- 9 repositories split into Domain/Repository interfaces + Doctrine impls,
  bound in services.yaml; consumers inject the interfaces. find() kept off
  the interfaces (ServiceEntityRepository ?object compat) -> findById().
- State (7), MCP tools (38), controller, CalDavService/RecurrenceCalculator,
  3 Doctrine listeners and SwitchWorkflowOutput moved under Infrastructure/.
- doctrine.yaml: ProjectManagement mapping + resolve_target_entities of the
  3 module contracts repointed to the module (ClientInterface stays legacy).
- ProjectManagementModule registered (id project-management, 4 RBAC perms,
  not re-wired); sidebar my-tasks/projects gated by the module.
- Legacy not-yet-modularised consumers (Mail/Gitea/BookStack, Serializer,
  fixtures, tests) swapped to the module FQCN — transitional coupling to be
  cleaned in 2.4/2.5/2.6.

159 tests green, mapping valid, no API route regression, cs-fixer clean.
2026-06-20 16:54:59 +02:00