Commit Graph

820 Commits

Author SHA1 Message Date
jubnl 07de2d3e5a test(server): plan 4 — budget calc suite onto real repositories (3e carry) 2026-09-24 07:04:32 +02:00
jubnl cb66ff33d3 test(server): plan 4 — dedupe overlapping listPlanDays/isTrackInTrip parity blocks
235f03665 landed two versions of the same DaysRepository.listPlanDays and
PlacesRepository.isTrackInTrip parity coverage side by side (a merge
artifact from concurrent work on the same carry item). Keeps one block per
method, drops the duplicate describe/it blocks and their colliding test ids.
2026-09-24 07:04:15 +02:00
jubnl 235f036657 test(server): plan 4 — full-key parity for Days/DayAssignments/Places roadtrip reads (3d carry)
Task 8b-2 item 1 (remainder): the 3 non-share/public-api reads from the 3d
Task 7 review's missing-parity list — DaysRepository.listPlanDays (RPL1),
DayAssignmentsRepository.listRoadtripVisits (RPL2, the correlated-subquery
LEFT JOIN), and PlacesRepository.isTrackInTrip (RT13, the review's
"findTrackInTrip" — the actual method name). Completes the 11-file carry
together with 434113937's 8 share/public-api reads.
2026-09-24 07:00:30 +02:00
jubnl f0816e557e test(server): plan 4 — concurrency pins for boundaries-upsert and token-mint races (3d carry)
Task 8b-2 item 2: R7's two remaining un-transacted-statement races flagged
by the 3d Task 7 review (L4) but never pinned — RoadtripDayBoundariesService
.save's belongs-check-then-upsert, and FeedsService.generateTripToken's
read-then-write. Both tests pin today's actual outcome (an atomic ON
CONFLICT upsert survives cleanly; the token mint has a last-write-wins
loser) with the same concurrent-request-shape Promise.all/allSettled
pattern roadtrip.service.test.ts's existing R7 vias pin uses. Not a fix.
2026-09-24 06:57:28 +02:00
jubnl 4341139374 test(server): plan 4 — full-key parity for Reservations share/public-api reads (3d carry)
Task 8b-2 item 1 (partial): the 8 share.service.ts/public-api.service.ts
reads on ReservationsRepository flagged by the 3d Task 7 review as having
no repository-level toEqual(<legacy raw>) coverage (listEndpointsForShare,
listDayPositionsForShare, listPublicForShare, listPublicAccommodationsForShare,
listScheduledForPublicApi, listUnscheduledForPublicApi,
listAccommodationsForPublicApi, listUnplannedPlacesForPublicApi). 12 new
cases against the legacy statement run raw on the same connection.
2026-09-24 06:57:22 +02:00
jubnl a4fe3241b2 test(server): plan 4 — full-key parity tests for ShareTokensRepository (3h L4)
3h L4's carry: ShareTokensRepository (SH1-SH6/SH17/UC6) had zero tests.
17 cases pinning every read against the legacy statement run raw on the
same connection — findRawByTrip/findTokenByTrip with every nullable
column both null and set, findValidByToken/findTripAndShareMapByToken/
findTripIdByToken's shared token+expiry predicate (valid, future-expiry,
past-expiry, unknown token), plus insertNew/updateFlagsByTrip/deleteByTrip/
deleteByCreator's write correctness (deleteByCreator's cross-trip erasure
kept distinct from deleteByTrip's single-trip scope, per the repository's
own docstring). The listPublicForShare family (the other half of 3h L4)
is a separate, larger carry — not in this file.
2026-09-24 06:49:24 +02:00
jubnl 8f8e5af632 refactor(server): plan 4 — narrow TripMembersRepository.remove/TripsRepository.setOwner to number
Both accepted trip_id: number | string per 3c's carry note (their "stays
loose for other callers" reasoning), pending the guard-level single parse
landing first. Re-checked every caller directly: setOwner's one production
caller (trip-members.service.ts#transferOwnership) already passes trip.id,
a real number resolved earlier in the same transaction; remove's callers
are trips.rpc.ts (num(), already a validated number), trips.mcp.ts (Zod-
typed tool input, already a number) and TripMembersController.removeMember
(the one genuine raw-string caller, :id off the route with no class-level
TripAccessGuard on this controller by design).

TripMembersController.removeMember now parses :id once (toRowId, not
Number() — rule 15's NaN-into-SQL trap) and threads the parsed number to
both its own access check and removeMember, instead of passing the raw
string through twice. TripMembersService.removeMember and both repository
methods narrow to number accordingly; setOwner's WHERE also moves off its
raw-bind escape hatch onto nativeUpdate now that trip_id is typed, and
remove's trip_id half moves onto a typed .where({ trip: trip_id }) filter
(user_id keeps its own raw bind — a different, still-live NaN-safety seam,
unrelated to this narrowing).

TRIPREPO-018 and TMEMREPO-023, which pinned the retired string-bind seam
on setOwner/remove, no longer typecheck against the narrowed signatures
and are removed with an explanatory comment in their place.
2026-09-24 06:37:40 +02:00
jubnl 40ede38ea7 refactor(server): plan 4 — unify the two user-data-erasure enqueue paths
UserCleanupService.erasePluginUserData (the account-deletion transaction's
own UC2/UC3 enqueue) and PluginRuntimeService.enqueueUserErasure (the
emitUserDeleted sink's post-commit enqueue) independently walked the
identical SELECT id, permissions FROM plugins + JSON.parse + hook:user-data
filter + INSERT OR IGNORE — and both genuinely run for every account
deletion, since every emitUserDeleted call site (auth.service.ts,
admin.service.ts, trip-members.service.ts's guest removal) fires
immediately after deleteUserCompletely.

Extracted the shared filter into enqueueHookUserDataErasures
(nest/plugins/user-erasure-enqueue.ts) and pointed both call sites at it.
UC2/UC3 also come off DatabaseService onto PluginsRepository/
PluginUserErasureQueueRepository in the same change (they were the last
raw statements standing in the way of sharing one implementation) — this
narrows the Plan 3b Task 5 "stays raw, owned by Plan 3j" ruling to UC1
only (plugin_user_config/plugin_oauth_tokens/plugin_oauth_state), which
still stays raw, unaffected. UserCleanupService's orphan-data-directory
scan (a plugin uninstalled with data retained) has no equivalent on the
enqueueUserErasure side and stays there, now via the same repository.

Both enqueue calls still run per deletion (INSERT OR IGNORE keeps this
idempotent) — this unifies the IMPLEMENTATION, not the call count; not
touching the emitUserDeleted event-sink wiring itself was a deliberate,
smaller-blast-radius choice for this task.
2026-09-24 06:36:43 +02:00
jubnl 78a0952a96 refactor(server): plan 4 — relocate JS7/10/13/14 off the journey-share fallback stub
JourneyShareTokensRepository's own JS7/JS10/JS13/JS14 Kysely reads were a
3g Task 3 fallback (Task 2's JourneyPhotosRepository/JourneyEntriesRepository/
JourneyEntryPhotosRepository were still mid-flight). Now that those
repositories are stable, relocate: JS7/JS10 (photo/asset validation joins)
onto JourneyPhotosRepository (already injected into JourneyShareService as
photosRepo, no constructor change); JS13 (public entry list) onto
JourneyEntriesRepository.listPublicEntries; JS14 (public per-entry photo
read) onto JourneyEntryPhotosRepository.listForPublicJourney, deduped
against that repository's own JP_COLUMNS select list instead of a
hand-duplicated copy. JourneyShareService gains two new @InjectRepository
params (both already registered in journey-domain.module.ts's forFeature),
rippled through mcp-test-controllers.ts and journey-share.service.test.ts
(P04/P05 parity tests now pin the new homes).
2026-09-24 06:31:34 +02:00
jubnl 3e37474a2c test(server): plan 4 — constructor ripple for the rosterUserIds/getPlaceWithTags inline
Hand-built BudgetService/BudgetMcp/TodoService call sites drop the removed
DatabaseService constructor arg (budgetRepoArgs() now appends a
TripMembersRepository so most spread call sites need no other change);
budget.service.test.ts's rosterHas() helper and svc() factory move from a
spied DatabaseService to a stubbed TripMembersRepository. The
AccommodationsService helper (tests/helpers/accommodations-service.ts) and
its two direct callers drop the same arg. PackingService call sites are
untouched (its new param is optional, so every existing hand-built instance
still resolves rosterUserIds through the DatabaseService fallback).
2026-09-24 06:18:32 +02:00
jubnl 1853156da3 fix(server): plan 4 — plugin discovery's delete+reinsert pairs are atomic
install/discovery.ts#upsert refreshed a plugin's actions and settings-field
descriptors as two independent delete-then-reinsert pairs (DI5-DI8) with no
transaction spanning either pair, let alone both: a crash between a delete
and its own reinsert left that plugin with an EMPTY actions or
settings-field set until the next discovery run, instead of its previous
still-valid rows. Wraps both pairs in one uow.transactional, so a discovery
refresh for a plugin's descriptors is now all-or-nothing.

Deliberate behaviour change, named for the user per the task brief.

DiscoveryRepos grows a UnitOfWork; PluginRegistryService (the other
production caller, registry.service.ts) picks it up the same @Optional()
way PluginRuntimeService already does, with the same guard-throw. Updates
the two hand-constructed test call sites that actually exercise discovery
(discovery.test.ts, registry.test.ts) to pass a real UnitOfWork.
2026-09-24 06:17:23 +02:00
jubnl 652aa568e4 refactor(server): plan 4 — trek-photos.repository.ts finishes its rename
3e Task 6 renamed the CLASS (TrekPhotosRepository -> TrekPhotoRegistrationService,
since the name collided with the generated ORM TrekPhotos.repository.ts) but
left the FILE and its test file's names unchanged. Renames both to
trek-photo-registration.service.ts / .test.ts.

The old path is kept as a thin re-export stub (the src/websocket.ts shape)
rather than repointed at every one of its ~20 importers in this pass: several
of those files sit in two sibling tasks' exclusively-owned windows on this
shared branch (Task 3's budget/packing/todo/accommodations/places ripple,
reached through tests/helpers/plugin-host.ts and mcp-test-controllers.ts;
Task 5c's held-back tests/unit/services/{trekPhotoMedia,conflictUpdate}.test.ts)
and are off limits here. Flagged for a follow-up once those tasks land: delete
the stub and repoint every remaining importer at ./trek-photo-registration.service.
2026-09-24 06:13:33 +02:00
jubnl d499b4505f refactor(server): plan 4 — retire the safely-convertible string-form where conditions (rule 23)
Places.repository.ts's existsInTrip/findInTrip/findRaw/reclaimInputs/
listByTripAndIds and Trips.repository.ts's findPublicForShare all took a
raw `.where('col = ?', [v])` string condition even though their id/trip_id
parameters were already plain `number` (or, for findPublicForShare,
narrowed to `number` here after confirming its one caller,
share.service.ts#getSharedTripData, only ever passes a number sourced
from a Kysely-typed share_tokens row, never an unconverted URL param) —
converted all six to MikroORM's typed `.where({...})` filter object.

The remaining raw string-form conditions in src/db/repositories/** are
NOT converted here: they are the `number | string` raw-bind seam
findAccessible/isOwner's own docstrings describe (trip_id/id threaded
through unconverted from req.params), which structurally requires the
deferred guard-level single toRowId parse to land first — narrowing any
of those methods' signatures now would either break the '0x10'/'007' id
parity those docstrings warn about, or duplicate the deferred item's own
work ahead of it landing.
2026-09-24 06:10:33 +02:00
jubnl e62f463296 refactor(server): plan 4 — unify the trip-access predicate's Kysely copies
TripsRepository.findForViewer/listForUser/activeTrip each hand-wrote the
identical trip-access boolean (t.user_id = ? OR m.user_id IS NOT NULL)
through the Kysely expression builder. Task 7's security review (M1)
evaluated a shared helper and set it aside for time, falling back to the
TRIPREPO-041 cross-method parity test as a regression guard instead.

Extracted the boolean into one generic helper, tripAccessExpr
(_shared/trip-access.ts), following the reservation-visibility.ts
publicReservationExpr precedent: fully generic over <DB, TB> since the
predicate has no correlated subquery, so it composes across
TripSelectKyselyDB and ActiveTripKyselyDB without needing a narrower
fixed-alias shape. The LEFT JOIN itself stays hand-written at each call
site — tripSelectQuery's own docstring already explains why a shared join
helper can't be typed across these differently-joined outer queries.

feeds.service.ts and memories-access.service.ts no longer duplicate this
predicate at all (Task 2's facade-inline sweep already converted both onto
TripsRepository.findAccessible/getFeedTokenIfReachable/
listReachableActiveTrips, which read through the canonical MikroORM
QueryBuilder form, accessibleTripsQuery) — the remaining duplication this
task closes is the three Kysely copies inside this file.

TRIPREPO-041 (64/64 in the file) stays green as the unification's own
regression guard.
2026-09-24 06:05:30 +02:00
jubnl 7f61d69add fix(server): plan 4 — RULE12 paren-count scanner ignores string literals
The entity generator's RULE12_fixGarbledCheckExpressions CHECK-expression
paren-balance scanner counted every `(`/`)` character regardless of
whether it fell inside a single-quoted SQL string literal. A CHECK
expression like `status IN ('confirmed)', 'tentative')` has a literal `)`
inside its first string value; the scanner would hit depth 0 at that
in-string character and truncate the expression, losing its own real
closing paren. The scanner now tracks single-quote string state (handling
the doubled `''` SQLite escape) and skips parens while inside one.

Latent-bug fix — no shipped entity's CHECK expression has hit this shape
yet (3d's ledger: "nothing affected today").
2026-09-24 06:05:14 +02:00
jubnl ea7b93de5d refactor(server): plan 4 — inline canAccessTrip/isOwner off DatabaseService (plugins + MCP surfaces) 2026-09-24 05:47:29 +02:00
jubnl 51327e75c0 refactor(server): plan 4 — inline canAccessTrip/isOwner off DatabaseService (trip-domain services) 2026-09-24 05:47:21 +02:00
jubnl 8f825e445e fix(server): plan 4 — un-skip PARITY-009 (FK deleteRule and index-name parity) 2026-09-24 05:22:31 +02:00
jubnl 88fa13d55a refactor(server): plan 4 — convert the program's orphaned raw-SQL callers 2026-09-24 04:59:54 +02:00
jubnl fe69a3b9a7 fix(server): plan 3j close-out — serialized audit-chain appends, atomic metadata and scheduler limits, rollback and race proofs for the plugin repositories 2026-09-24 04:52:18 +02:00
jubnl 6ff45be032 test(server): plan 4 — collapse nest service test harnesses onto the ORM snapshot 2026-09-24 04:47:29 +02:00
jubnl 9bcee484d4 test(server): plan 4 — collapse mcp tool test harnesses onto the ORM snapshot 2026-09-24 04:38:01 +02:00
jubnl b6de35a2ce refactor(plugins): contribution-hook controllers, the small plugin controllers and the plugins-table survivors through repositories 2026-09-24 03:53:44 +02:00
jubnl d522882590 refactor(plugins): install-time gates, the capability-audit hash chain and the registry through repositories 2026-09-24 03:24:23 +02:00
jubnl f29216c5af refactor(plugins): outbound OAuth tokens and per-user plugin settings through repositories 2026-09-24 03:13:09 +02:00
jubnl 2ba5534c65 fix(server): plan 3i close-out — DemoModule imported where its service is consumed (e2e restored); admin addons list selects the provider relation; saveDemoBaseline awaited; backup tests on the maintenance repository 2026-09-24 02:58:37 +02:00
jubnl ab63eaf9ad refactor(plugins): runtime lifecycle, config CRUD and the uninstall cascade through repositories 2026-09-24 02:46:01 +02:00
jubnl f337f116d9 fix(server): plan 3h close-out — new-value-wins COALESCE restores stay/reservation propagation; doc-sync state filter truthiness; request-context and parity ratchets for share, doc-sync and the integrations 2026-09-24 02:39:30 +02:00
jubnl 915ab84faf refactor(demo,backup): seed and reset through DemoRepository, live-connection backup statements through MaintenanceRepository, isDemoUserId through an injected DemoService 2026-09-24 02:04:32 +02:00
jubnl e76818627b refactor(plugins): the trip-write role gate through a repository, in isolation 2026-09-24 01:58:22 +02:00
jubnl 240c7ed2a9 test(admin): pin createUser's pre-existing non-transactional uniqueness-check race (AD2-4) 2026-09-24 01:47:38 +02:00
jubnl 862799062a refactor(admin): user CRUD, stats, audit log, version check and addon config through repositories 2026-09-24 01:46:09 +02:00
jubnl 4c22d83970 feat(server): plan 3j — the withCamelCase utility, unwired, pending a contract decision 2026-09-24 01:34:10 +02:00
jubnl 425d889983 refactor(share): public share links, the pre-init photo fallback, and collection-places survivors through repositories 2026-09-24 01:24:21 +02:00
jubnl 36e178c085 refactor(storage): backend/category config and usage stats through AppSettingsRepository; fix the seed-file test race and the shared-replica migration-flip gap 2026-09-24 01:20:46 +02:00
jubnl 14721e9658 feat(server): plan 3i groundwork — AuditLogRepository's reserved methods, the demo connection-swap spike, repository-fitness confirmations 2026-09-24 00:56:52 +02:00
jubnl a5665b5d2d fix(server): plan 3g close-out — transactional book first-save, skeleton reconcile and share-link create; unknown-key entry patches are no-ops again; parity and authorization pins for the journey repositories 2026-09-24 00:49:55 +02:00
jubnl aad7605071 test(integrations): add AirTrail saveSettings tx-asymmetry ratchet (Plan 3h Task 4 addendum) 2026-09-24 00:46:19 +02:00
jubnl 8bcfd92142 refactor(doc-sync): connections, reconciler and controller-SQL relocation through repositories 2026-09-24 00:40:23 +02:00
jubnl 182744e416 refactor(integrations,booking-import,airports,maps,route-usage): airtrail, booking import, airport backfill, maps cache and route-usage counters through repositories 2026-09-24 00:39:08 +02:00
jubnl d79f4cc605 refactor(collections): saved places, copy-to-trip, labels and invitations through repositories 2026-09-24 00:11:01 +02:00
jubnl 1d72c6909b refactor(integrations): dawarich connection, sync, tracks and review-flow through repositories 2026-09-23 23:55:52 +02:00
jubnl 3a4646a7bb refactor(server): journey-table survivors in trips, photos, memories, auth-cleanup and plugin-contributions through repositories 2026-09-23 23:37:01 +02:00
jubnl 992f9ae4a7 refactor(collections): access model, hydration, export/import and list CRUD through repositories 2026-09-23 23:29:15 +02:00
jubnl 407e50c295 refactor(journey): public share links, TREK Studio books and the journal facades through repositories 2026-09-23 23:09:13 +02:00
jubnl 24ad08a014 refactor(journey): journey stats, entries, photos, contributors and suggestions through repositories 2026-09-23 23:05:59 +02:00
jubnl 353edd7a48 fix(server): plan 3f close-out — ntfy operator-scope test on the live send path; vacay date-offset and shift-order parity; todo reminder window; coverage and parity ratchets for the atlas and vacay repositories 2026-09-23 22:58:03 +02:00
jubnl 9e75aea73f feat(server): plan 3h groundwork — date-offset dialect helpers, entity/repository fitness confirmations, pre-init and encryption pattern confirmations, survivor re-grep 2026-09-23 22:34:31 +02:00
jubnl 6486159289 refactor(journey): journey and trip access control, CRUD and the trip-sync engine through repositories 2026-09-23 22:29:55 +02:00
jubnl 71ebf5588b feat(server): plan 3g groundwork — GALLERY_CHRONOLOGICAL_ORDER Kysely ordering, unixEpochToIsoKysely helper, composite-PK DDL pins, JB5/R7 confirmations 2026-09-23 21:57:16 +02:00