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.
This commit is contained in:
jubnl
2026-09-24 06:10:33 +02:00
parent 5e4c5640a1
commit d499b4505f
4 changed files with 16 additions and 7 deletions
@@ -231,7 +231,7 @@ export class PlacesRepository extends TrekRepository<Places> {
async existsInTrip(id: number, trip_id: number): Promise<boolean> {
const row = await this.qb('p')
.select(['p.id'])
.where('p.id = ? AND p.trip_id = ?', [id, trip_id])
.where({ 'p.id': id, 'p.trip_id': trip_id })
.execute<{ id: number } | undefined>('get', false);
return !!row;
}
@@ -252,7 +252,7 @@ export class PlacesRepository extends TrekRepository<Places> {
async findInTrip(id: number, trip_id: number): Promise<PlaceRow | undefined> {
return this.qb('p')
.select(['p.*'])
.where('p.id = ? AND p.trip_id = ?', [id, trip_id])
.where({ 'p.id': id, 'p.trip_id': trip_id })
.execute<PlaceRow | undefined>('get', false);
}
@@ -267,7 +267,7 @@ export class PlacesRepository extends TrekRepository<Places> {
async findRaw(id: number): Promise<PlaceRow | undefined> {
return this.qb('p')
.select(['p.*'])
.where('p.id = ?', [id])
.where({ 'p.id': id })
.execute<PlaceRow | undefined>('get', false);
}
@@ -281,7 +281,7 @@ export class PlacesRepository extends TrekRepository<Places> {
async reclaimInputs(id: number, trip_id: number): Promise<{ google_place_id: string | null; image_url: string | null } | undefined> {
return this.qb('p')
.select(['p.google_place_id', 'p.image_url'])
.where('p.id = ? AND p.trip_id = ?', [id, trip_id])
.where({ 'p.id': id, 'p.trip_id': trip_id })
.execute<{ google_place_id: string | null; image_url: string | null } | undefined>('get', false);
}
@@ -1144,7 +1144,7 @@ export class PlacesRepository extends TrekRepository<Places> {
if (ids.length === 0) return [];
return await this.qb('p')
.select(['p.id', 'p.name', 'p.lat', 'p.lng', 'p.google_place_id', 'p.google_ftid', 'p.osm_id'])
.where('p.trip_id = ?', [trip_id])
.where({ 'p.trip_id': trip_id })
.andWhere({ id: { $in: ids } })
.execute<PlaceMatchRow[]>('all', false);
}
+11 -2
View File
@@ -750,11 +750,20 @@ export class TripsRepository extends TrekRepository<Trips> {
* `updated_at` out — a public share-link viewer never sees either). No
* access check here, matching the legacy statement — the caller's own
* `verifyTripAccess`/token validity gates this read.
*
* `id: number` (Plan 4 Task 8a, rule 23): unlike `findAccessible`/
* `isOwner`, this is never on the guard's raw-`req.params` path — its one
* caller (`share.service.ts#getSharedTripData`) passes `shareRow.trip_id`,
* a `number` straight off a Kysely-typed `share_tokens` row, never an
* unconverted URL param — so the `number | string` raw-bind seam those
* two methods' own docstrings describe does not apply here, and this one
* narrows to a typed filter now rather than waiting on that seam's guard-
* level parse (deferred, out of this task's scope).
*/
async findPublicForShare(id: number | string): Promise<TripPublicShareRow | undefined> {
async findPublicForShare(id: number): Promise<TripPublicShareRow | undefined> {
return await this.qb('t')
.select(['t.id', 't.title', 't.description', 't.start_date', 't.end_date', 't.cover_image', 't.currency'])
.where('t.id = ?', [id])
.where({ 't.id': id })
.execute<TripPublicShareRow | undefined>('get', false);
}