feat: consolidate PMS server PRs (PMS-4..PMS-131) #40
Loading…
Reference in a new issue
No description provided.
Delete branch "feat/pms-merge"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Consolidates the 19 open PMS server PRs (#20-#38) into a single branch so they can be reviewed and merged as one unit instead of 19 interdependent PRs.
What this branch contains
All 19 branches merged into
feat/pms-mergeoffmainwith real merge commits (git merge --no-ff), preserving every PR's individual commits:feat/auth-story-pms-4->feat/tickets-story-pms-11->feat/portal-story-pms-25were a PR stack; they merge naturally since each contains the previous.Merge conflicts resolved
Every story branch registers its module in the shared
src/api/router.rs, so 14 of the merges conflicted there (one also insrc/main.rs). All resolved as unions: each branch swaps its own stub route for real routes, so the final router wires all modules.src/main.rskeeps both the mailer (from PMS-4) and the encryption key (from PMS-33). The router was verified to register all 19 services with only/dispatchleft as a stub (no branch implemented it).Added CI + cleanup
mokosh-server had no PR check workflow. This branch adds
.forgejo/workflows/check.yml(fmt + clippy-D warnings+ compile + tests,SQLX_OFFLINE=true). To pass that strict gate it also runscargo fmt --alland resolves everycargo clippy --all-targets -- -D warningsfinding across the workspace (~80 warnings, most pre-existing inmainand themokosh-auth-*crates).Verification
Run in the local dev container (rust 1.94.1,
SQLX_OFFLINE=true):cargo check --all-targets- passescargo clippy --all-targets -- -D warnings- passescargo test --lib- 80 passedcargo fmt --all --check- cleanSupersedes
Replaces #20, #21, #22, #23, #24, #25, #26, #27, #28, #29, #30, #31, #32, #33, #34, #35, #36, #37, #38 - those PRs are closed in favour of this one.
Add `utils:📧:Mailer` with `LogMailer` (dev) and `SmtpMailer` (lettre). `MailerConfig::from_env` selects the implementation based on `SMTP_HOST`. `AuthService::request_password_reset` now dispatches the reset link through the configured mailer; the link is built from the new `frontend_base_url` field (sourced from `CLIENT_ORIGIN`). The mailer is constructed once at startup and threaded through `create_api_router` -> `AuthService::with_mailer`. Send failures are logged but do not change the public response shape, preserving the existing enumeration-resistant behaviour of `/auth/forgot-password`. #PMS-7 State Done`.nest("/companies", Router::new())` matched nothing, so `/api/v1/companies` returned a misleading 404 instead of either succeeding as an alias or returning a route-not-found from the parent. The canonical company endpoints already live under `/api/v1/contacts/companies/...`; clients pointing at the old path need to update. #PMS-20 State DoneThe `multi-tenant` / `single-tenant` cargo features have existed in `AppConfig::is_multi_tenant` since day one but were inert at the routing layer — both builds exposed `/api/v1/tenants/*`. In a single-tenant deployment that's a foot-gun: a CRUD endpoint that can spawn extra tenant rows breaks the deployment's load-bearing assumption. Now `.nest("/tenants", ...)` is only added under `#[cfg(feature = "multi-tenant")]`, and `TenantService` is constructed only in that mode. Verified both `--features multi-tenant` (default) and `--no-default-features --features single-tenant,server` builds compile clean. #PMS-24 State DoneReads `contact.tenant_id` + `contact.company_id` from the extractor and pages through `TicketService::list_portal_tickets`, which under the hood builds a `TicketFilter { company_id: Some(...), .. }` and shares the same JOINed SELECT as the agent-side list. Pagination piggybacks on `PaginationParams`. Company-wide visibility (not just `contact_id = self`) matches the typical helpdesk model where employees of company X can follow each other's tickets without admin glue. #PMS-28 State DoneCreates `mokosh-server::modules::billing` with `models.rs`, `service.rs`, and `routes.rs`. The module exposes the wire-shaped DTOs for invoices, payments, payment-gateway configs, and tax rates - all four tables already exist in `001_initial_schema.sql`, so the data layer just needs an HTTP face. `billing_routes` is wired into `create_api_router` via `merge` (not `nest`), replacing the prior `nest("/invoices", stub_routes())` and `nest("/payments", stub_routes())` placeholders. The router is empty in this commit; subsequent commits in the PMS-33 story add the endpoint families: - PMS-35..38 invoices CRUD - PMS-39 payments - PMS-40 payment gateway configs (encrypted at rest) - PMS-41 tax rates #PMS-34 State Done`src/modules/time_tracking/{mod.rs, models.rs, service.rs, routes.rs}` with full DTO coverage (work types, time entries, timesheet summaries, active timers, rounding rules). `time_tracking_routes` is wired via `merge` and replaces the `/time-entries` and `/timesheets` stubs. Router is empty in this commit; subsequent commits add endpoints. #PMS-43 State DoneAll six remaining sub-tasks in the PMS-42 story shipped in one batch because they share the same `service.rs` and `routes.rs` files; splitting per-task would have been arbitrary diff churn. - PMS-44 GET / POST `/api/v1/time-entries` with filter (user / ticket / project / date range) + pagination. Compute `duration_minutes` from start/end or accept it directly. Non-admins can only log time for themselves. - PMS-45 GET / PUT / DELETE `/api/v1/time-entries/:id`. Recompute `duration_minutes` and `total_amount` (rate * minutes / 60) on start/end/rate changes. - PMS-46 GET `/api/v1/timesheets` aggregates `time_entries` by `(user_id, DATE_TRUNC('week', date))`. Filter by user / week. - PMS-47 POST `/api/v1/timesheets/{user_id}/{week_start}/submit` flips every non-approved entry in the (user_id, week) window to `approval_status = pending` (the conventional "submitted, awaiting approval" state). Non-admins can only submit their own. - PMS-48 active timers: GET `/timers/active`, POST `/timers/start`, POST `/timers/{id}/stop`. UNIQUE(user_id) on `active_timers` is honoured; start rejects if a timer is already running. Stop atomically removes the timer row and creates a `time_entries` row with the elapsed window; falls back to the tenant's first active work type / inferred company when the timer didn't carry one. - PMS-49 time rounding rules CRUD (`/time-rounding-rules`). Setting `is_default = true` demotes the prior default in the same transaction. `rounding_method` validated against `up | down | nearest`. - PMS-50 work types CRUD (`/work-types`). Admin-only writes; reads available to any authenticated user. #PMS-44 State Done #PMS-45 State Done #PMS-46 State Done #PMS-47 State Done #PMS-48 State Done #PMS-49 State Done #PMS-50 State DoneBatches PMS-72..78: - PMS-72 scaffold + router wire - PMS-73 asset types CRUD (admin writes) - PMS-74 assets CRUD; create + update record an entry in `asset_audit_log` (`status_changed` when status differs from prior) - PMS-75 asset relationships (`/assets/:id/relationships`, `/asset-relationships/:id`) - PMS-76 configuration items - per-asset; `value_encrypted` AES-256-GCM at rest with the host `ENCRYPTION_KEY` - PMS-77 credential vault - per-asset; username/password/notes all encrypted at rest. Every list call writes a `synced` audit row tagged `{"event":"credential_read"}` for SOC2-style read auditing. - PMS-78 `GET /api/v1/assets/:id/audit-log` capped at 500 rows; admin-only. The encryption key plumbing reuses `BillingService::with_encryption_key`'s pattern. `AssetsService::new` defaults to a zero key for unit tests; the router constructs it via `with_encryption_key` in a follow-up commit once the host-config plumbing lands across stacked PRs. #PMS-72 State Done #PMS-73 State Done #PMS-74 State Done #PMS-75 State Done #PMS-76 State Done #PMS-77 State Done #PMS-78 State DoneBatches PMS-80..84: - PMS-80 scaffold + router wire - PMS-81 kb_categories CRUD (manager writes) - PMS-82 kb_articles CRUD: create seeds version 1; update snapshots a new version row when title or content change; view_count bumps on every read - PMS-83 article version history at `GET /kb/articles/:id/versions` (newest first) - PMS-84 portal feed at `GET /kb/articles/portal` returns `status = 'published'` AND `visibility IN ('public', 'client_specific')` rows; the portal-side reader (PMS-32) can call this through its own route once both PRs are in. #PMS-80 State Done #PMS-81 State Done #PMS-82 State Done #PMS-83 State Done #PMS-84 State DoneBatches PMS-107..112: - PMS-107 scaffold + router wire - PMS-108 SLA policies CRUD; setting `is_default` demotes the prior default in the same transaction - PMS-109 SLA targets: `(sla_policy_id, priority_id)` is unique per the schema, so `POST /policies/:id/targets` is upsert - PMS-110 business hours CRUD; `schedule` is JSONB (`{"mon": [{"start": "09:00", "end": "17:00"}], ...}`) - PMS-111 holiday calendars CRUD - PMS-112 evaluator: `SlaService::evaluate_for_ticket` reads the ticket's effective policy (assigned `sla_id` or tenant default), looks up the priority-keyed target, and stamps `first_response_due` / `resolution_due` / `sla_due_date`. v1 treats every window as 24x7; business-hours awareness lands as a follow-up. `POST /sla/tickets/:id/evaluate` is the manual trigger; ticket service hook lives in a stacked PR. #PMS-107 State Done #PMS-108 State Done #PMS-109 State Done #PMS-110 State Done #PMS-111 State Done #PMS-112 State Done