mirror of
https://github.com/mauriceboe/TREK.git
synced 2026-10-19 05:26:47 +00:00
refactor(trip-membership): domains ask TripAccessService who may see a trip
Thirty services, controllers, MCP tools and RPC handlers outside the trips domain injected TripsRepository for one call, findAccessible. They now inject TripAccessService from the trip-membership leaf, in the same constructor slot under the same field name; it answers with the same access row (or undefined) and binds the id exactly as given, so each caller keeps its own coercion and its own 404, 403 or tool error.
This commit is contained in:
@@ -12,6 +12,7 @@ import { Days } from '../../db/entities/Days.entity';
|
||||
import { RoadtripVias } from '../../db/entities/RoadtripVias.entity';
|
||||
import { Reservations } from '../../db/entities/Reservations.entity';
|
||||
import { Trips } from '../../db/entities/Trips.entity';
|
||||
import { TripMembershipModule } from '../trip-membership/trip-membership.module';
|
||||
|
||||
/**
|
||||
* The accommodations SERVICE, split from the controller/MCP/RPC surfaces (the
|
||||
@@ -38,7 +39,7 @@ import { Trips } from '../../db/entities/Trips.entity';
|
||||
* no entity added here for that.
|
||||
*/
|
||||
@Module({
|
||||
imports: [
|
||||
imports: [TripMembershipModule,
|
||||
PermissionsModule, RealtimeModule, AssignmentsDomainModule,
|
||||
MikroOrmModule.forFeature([DayAccommodations, DayAssignments, Places, Days, RoadtripVias, Reservations, BudgetItems, Trips]),
|
||||
],
|
||||
|
||||
@@ -14,8 +14,7 @@ import { Places } from '../../db/entities/Places.entity';
|
||||
import type { PlacesRepository, PlaceWithTagsRow as PlaceWithTags } from '../../db/repositories/Places.repository';
|
||||
import { Days } from '../../db/entities/Days.entity';
|
||||
import type { DaysRepository } from '../../db/repositories/Days.repository';
|
||||
import { Trips } from '../../db/entities/Trips.entity';
|
||||
import type { TripsRepository, TripAccess } from '../../db/repositories/Trips.repository';
|
||||
import type { TripAccess } from '../../db/repositories/Trips.repository';
|
||||
import { RoadtripVias } from '../../db/entities/RoadtripVias.entity';
|
||||
import type { RoadtripViasRepository } from '../../db/repositories/RoadtripVias.repository';
|
||||
import { Reservations } from '../../db/entities/Reservations.entity';
|
||||
@@ -26,6 +25,7 @@ import { carryViasWith, locatedStopIdsWith, reseatOwnStopWith, seatHolds, seatIn
|
||||
import type { User } from '../../types';
|
||||
import { RESERVATION_METADATA } from '../../db/json-columns';
|
||||
import { decodeJson, encodeJson } from '../../utils/json-column';
|
||||
import { TripAccessService } from '../trip-membership/trip-access.service';
|
||||
|
||||
type Trip = TripAccess;
|
||||
|
||||
@@ -115,8 +115,8 @@ export class AccommodationsService {
|
||||
private readonly assignments: AssignmentsService,
|
||||
private readonly uow: UnitOfWork,
|
||||
// Plan 4 Task 2 — canAccessTrip's own DatabaseService delegation is gone:
|
||||
// this injects TripsRepository directly and calls findAccessible.
|
||||
@InjectRepository(Trips) private readonly trips: TripsRepository,
|
||||
// this injects TripAccessService (trip-membership) and calls findAccessible.
|
||||
private readonly trips: TripAccessService,
|
||||
@InjectRepository(DayAccommodations) private readonly dayAccommodationsRepo: DayAccommodationsRepository,
|
||||
@InjectRepository(DayAssignments) private readonly dayAssignmentsRepo: DayAssignmentsRepository,
|
||||
@InjectRepository(Places) private readonly placesRepo: PlacesRepository,
|
||||
|
||||
@@ -13,6 +13,7 @@ import { TripMembers } from '../../db/entities/TripMembers.entity';
|
||||
import { RoadtripVias } from '../../db/entities/RoadtripVias.entity';
|
||||
import { Trips } from '../../db/entities/Trips.entity';
|
||||
import { Tours } from '../../db/entities/Tours.entity';
|
||||
import { TripMembershipModule } from '../trip-membership/trip-membership.module';
|
||||
|
||||
/**
|
||||
* The assignments SERVICE, split from the controller/MCP/RPC surfaces (the
|
||||
@@ -35,7 +36,7 @@ import { Tours } from '../../db/entities/Tours.entity';
|
||||
* one-Tour-per-day check create and move share.
|
||||
*/
|
||||
@Module({
|
||||
imports: [
|
||||
imports: [TripMembershipModule,
|
||||
PermissionsModule,
|
||||
QueryHelpersModule,
|
||||
JourneyDomainModule,
|
||||
|
||||
@@ -17,8 +17,6 @@ import { AssignmentParticipants } from '../../db/entities/AssignmentParticipants
|
||||
import type { AssignmentParticipantsRepository } from '../../db/repositories/AssignmentParticipants.repository';
|
||||
import { Days } from '../../db/entities/Days.entity';
|
||||
import type { DaysRepository } from '../../db/repositories/Days.repository';
|
||||
import { Trips } from '../../db/entities/Trips.entity';
|
||||
import type { TripsRepository } from '../../db/repositories/Trips.repository';
|
||||
import { Places } from '../../db/entities/Places.entity';
|
||||
import type { PlacesRepository } from '../../db/repositories/Places.repository';
|
||||
import { TripMembers } from '../../db/entities/TripMembers.entity';
|
||||
@@ -27,6 +25,7 @@ import { RoadtripVias } from '../../db/entities/RoadtripVias.entity';
|
||||
import type { RoadtripViasRepository } from '../../db/repositories/RoadtripVias.repository';
|
||||
import { Tours } from '../../db/entities/Tours.entity';
|
||||
import type { ToursRepository } from '../../db/repositories/Tours.repository';
|
||||
import { TripAccessService } from '../trip-membership/trip-access.service';
|
||||
|
||||
type Trip = TripAccess;
|
||||
|
||||
@@ -100,8 +99,8 @@ function sortMinutes(time: string | null): number | null {
|
||||
export class AssignmentsService {
|
||||
constructor(
|
||||
// Plan 4 Task 2 — canAccessTrip's own DatabaseService delegation is gone:
|
||||
// this injects TripsRepository directly and calls findAccessible.
|
||||
@InjectRepository(Trips) private readonly trips: TripsRepository,
|
||||
// this injects TripAccessService (trip-membership) and calls findAccessible.
|
||||
private readonly trips: TripAccessService,
|
||||
private readonly permissions: PermissionsService,
|
||||
private readonly realtime: RealtimeService,
|
||||
private readonly queryHelpers: QueryHelpersService,
|
||||
|
||||
@@ -4,13 +4,11 @@ import { PluginGuards } from '../../nest-rpc/plugin-guards.service';
|
||||
import { BadParams, ForbiddenResource } from '../../nest-rpc/rpc-errors';
|
||||
import { num, schemaMessage } from '../../nest-rpc/rpc-params';
|
||||
import type { PluginRpcContext } from '../../nest-rpc/rpc-kit/types';
|
||||
import { InjectRepository } from '@mikro-orm/nestjs';
|
||||
import { RealtimeService } from '../realtime/realtime.service';
|
||||
import { Trips } from '../../db/entities/Trips.entity';
|
||||
import type { TripsRepository } from '../../db/repositories/Trips.repository';
|
||||
import { TripMembershipService } from '../trip-membership/trip-membership.service';
|
||||
import { ADDON_IDS } from '../../addons';
|
||||
import { BudgetService } from './budget.service';
|
||||
import { TripAccessService } from '../trip-membership/trip-access.service';
|
||||
|
||||
/** Costs are budget items, and the app edits them under 'budget_edit'. */
|
||||
const BUDGET_EDIT_ACTION = 'budget_edit';
|
||||
@@ -33,8 +31,8 @@ export class CostsRpc {
|
||||
constructor(
|
||||
private readonly budget: BudgetService,
|
||||
// Plan 4 Task 2 — canAccessTrip's own DatabaseService delegation is gone:
|
||||
// this injects TripsRepository directly and calls findAccessible.
|
||||
@InjectRepository(Trips) private readonly trips: TripsRepository,
|
||||
// this injects TripAccessService (trip-membership) and calls findAccessible.
|
||||
private readonly trips: TripAccessService,
|
||||
private readonly realtime: RealtimeService,
|
||||
private readonly guards: PluginGuards,
|
||||
private readonly membership: TripMembershipService,
|
||||
|
||||
@@ -11,6 +11,7 @@ import { McpSharedModule } from '../mcp-shared/mcp-shared.module';
|
||||
import { DayNotes } from '../../db/entities/DayNotes.entity';
|
||||
import { Days } from '../../db/entities/Days.entity';
|
||||
import { Trips } from '../../db/entities/Trips.entity';
|
||||
import { TripMembershipModule } from '../trip-membership/trip-membership.module';
|
||||
|
||||
/**
|
||||
* Day notes. Its own domain rather than a second file set inside days/, which
|
||||
@@ -28,7 +29,7 @@ import { Trips } from '../../db/entities/Trips.entity';
|
||||
@Module({
|
||||
// DayNotes/Days: Plan 4 Task 1 — DayNotesService's own DayNotesRepository/
|
||||
// DaysRepository.existsInTrip, replacing its raw `this.dbs.all/get/run`.
|
||||
imports: [McpSharedModule, PermissionsModule, RealtimeModule, PluginGuardsModule, MikroOrmModule.forFeature([DayNotes, Days, Trips])],
|
||||
imports: [TripMembershipModule, McpSharedModule, PermissionsModule, RealtimeModule, PluginGuardsModule, MikroOrmModule.forFeature([DayNotes, Days, Trips])],
|
||||
controllers: [DayNotesController],
|
||||
providers: [DayNotesService, DayNotesMcp, DayNotesRpc],
|
||||
exports: [DayNotesService],
|
||||
|
||||
@@ -9,8 +9,7 @@ import { DayNotes } from '../../db/entities/DayNotes.entity';
|
||||
import type { DayNotesRepository } from '../../db/repositories/DayNotes.repository';
|
||||
import { Days } from '../../db/entities/Days.entity';
|
||||
import type { DaysRepository } from '../../db/repositories/Days.repository';
|
||||
import { Trips } from '../../db/entities/Trips.entity';
|
||||
import type { TripsRepository } from '../../db/repositories/Trips.repository';
|
||||
import { TripAccessService } from '../trip-membership/trip-access.service';
|
||||
|
||||
/**
|
||||
* Day-notes domain service — the legacy dayNoteService SQL folded in over
|
||||
@@ -42,9 +41,9 @@ export function normalizeNoteColor(color: string | null | undefined): string | n
|
||||
export class DayNotesService {
|
||||
constructor(
|
||||
// Plan 4 Task 2 — canAccessTrip's own DatabaseService delegation is gone:
|
||||
// this injects TripsRepository directly (same constructor slot) and
|
||||
// this injects TripAccessService (trip-membership) (same constructor slot) and
|
||||
// calls findAccessible.
|
||||
@InjectRepository(Trips) private readonly trips: TripsRepository,
|
||||
private readonly trips: TripAccessService,
|
||||
private readonly permissions: PermissionsService,
|
||||
private readonly realtime: RealtimeService,
|
||||
@InjectRepository(DayNotes) private readonly dayNotes: DayNotesRepository,
|
||||
|
||||
@@ -27,9 +27,10 @@ import { DayAssignments } from '../../db/entities/DayAssignments.entity';
|
||||
import { BudgetItems } from '../../db/entities/BudgetItems.entity';
|
||||
import { Users } from '../../db/entities/Users.entity';
|
||||
import { Trips } from '../../db/entities/Trips.entity';
|
||||
import { TripMembershipModule } from '../trip-membership/trip-membership.module';
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
imports: [TripMembershipModule,
|
||||
MulterModule.registerAsync({
|
||||
imports: [StorageModule, AllowedFileTypesModule],
|
||||
inject: [StorageService, AllowedFileTypesService],
|
||||
|
||||
@@ -15,8 +15,6 @@ import { Users } from '../../db/entities/Users.entity';
|
||||
import { UserSessions } from '../../db/entities/UserSessions.entity';
|
||||
import type { User } from '../../types';
|
||||
import type { TripAccess } from '../../db/repositories/Trips.repository';
|
||||
import { Trips } from '../../db/entities/Trips.entity';
|
||||
import type { TripsRepository } from '../../db/repositories/Trips.repository';
|
||||
import { UnitOfWork } from '../database/unit-of-work';
|
||||
import { toRowId } from '../common/row-id';
|
||||
import { StorageService } from '../storage/storage.service';
|
||||
@@ -33,6 +31,7 @@ import { DayAssignments } from '../../db/entities/DayAssignments.entity';
|
||||
import type { DayAssignmentsRepository } from '../../db/repositories/DayAssignments.repository';
|
||||
import { BudgetItems } from '../../db/entities/BudgetItems.entity';
|
||||
import type { BudgetItemsRepository } from '../../db/repositories/BudgetItems.repository';
|
||||
import { TripAccessService } from '../trip-membership/trip-access.service';
|
||||
|
||||
type Trip = TripAccess;
|
||||
type FilePermission = 'file_upload' | 'file_edit' | 'file_delete';
|
||||
@@ -119,9 +118,9 @@ function coerceLinkId(value: string | number | null | undefined): number | null
|
||||
export class FilesService {
|
||||
constructor(
|
||||
// Plan 4 Task 2 — canAccessTrip's own DatabaseService delegation is gone:
|
||||
// this injects TripsRepository directly (same constructor slot) and
|
||||
// this injects TripAccessService (trip-membership) (same constructor slot) and
|
||||
// calls findAccessible.
|
||||
@InjectRepository(Trips) private readonly trips: TripsRepository,
|
||||
private readonly trips: TripAccessService,
|
||||
private readonly permissions: PermissionsService,
|
||||
private readonly realtime: RealtimeService,
|
||||
private readonly tokens: EphemeralTokenService,
|
||||
|
||||
@@ -4,7 +4,6 @@ import {
|
||||
TOOL_ANNOTATIONS_DELETE, TOOL_ANNOTATIONS_NON_IDEMPOTENT,
|
||||
errorResult, ok,
|
||||
} from '../../nest-mcp';
|
||||
import { InjectRepository } from '@mikro-orm/nestjs';
|
||||
import { McpToolGuardsService } from '../mcp-shared/mcp-tool-guards.service';
|
||||
import {
|
||||
mapsSearchRequestSchema,
|
||||
@@ -28,8 +27,7 @@ import { MapsService } from '../maps/maps.service';
|
||||
import { PlacesService } from './places.service';
|
||||
import { isUpdateConflict } from '../common/conflictResult';
|
||||
import { isDirectionsUrl } from './maps-dir.helpers';
|
||||
import { Trips } from '../../db/entities/Trips.entity';
|
||||
import type { TripsRepository } from '../../db/repositories/Trips.repository';
|
||||
import { TripAccessService } from '../trip-membership/trip-access.service';
|
||||
|
||||
function parseId(value: string | string[]): number | null {
|
||||
const n = Number(Array.isArray(value) ? value[0] : value);
|
||||
@@ -61,7 +59,7 @@ export class PlacesMcp {
|
||||
constructor(
|
||||
private readonly places: PlacesService,
|
||||
private readonly maps: MapsService,
|
||||
@InjectRepository(Trips) private readonly tripsRepo: TripsRepository,
|
||||
private readonly tripsRepo: TripAccessService,
|
||||
private readonly journey: JourneyDomainService,
|
||||
private readonly assignments: AssignmentsService,
|
||||
private readonly guards: McpToolGuardsService,
|
||||
|
||||
@@ -30,6 +30,7 @@ import { StorageModule } from '../storage/storage.module';
|
||||
import { StorageService } from '../storage/storage.service';
|
||||
import { buildStorageUploadOptions } from '../storage/storage-upload.factory';
|
||||
import { MAX_PLACE_IMAGE_SIZE } from '../common/place-image-upload';
|
||||
import { TripMembershipModule } from '../trip-membership/trip-membership.module';
|
||||
|
||||
/**
|
||||
* Places domain (S8 — Phase 2 trip sub-domain). Depends on L4 Categories + L5
|
||||
@@ -40,7 +41,7 @@ import { MAX_PLACE_IMAGE_SIZE } from '../common/place-image-upload';
|
||||
* is no places.bridge.ts: nothing outside the container consumes this domain.
|
||||
*/
|
||||
@Module({
|
||||
imports: [
|
||||
imports: [TripMembershipModule,
|
||||
// Module-level options deliberately carry NO fileFilter: the GPX/KML import
|
||||
// route keeps its inline memoryStorage config and would inherit a
|
||||
// module-level filter via the interceptor's shallow merge — the image
|
||||
|
||||
@@ -1,14 +1,13 @@
|
||||
import { Controller, Get, Param, Req, UseGuards } from '@nestjs/common';
|
||||
import type { Request } from 'express';
|
||||
import { InjectRepository } from '@mikro-orm/nestjs';
|
||||
import { Trips } from '../../../db/entities/Trips.entity';
|
||||
import type { TripsRepository } from '../../../db/repositories/Trips.repository';
|
||||
import { Days } from '../../../db/entities/Days.entity';
|
||||
import type { DaysRepository } from '../../../db/repositories/Days.repository';
|
||||
import { JwtAuthGuard } from '../../auth-core/jwt-auth.guard';
|
||||
import { pluginsEnabled } from '../kill-switch';
|
||||
import { PluginHooks } from '../plugin-hooks.service';
|
||||
import { stripEmoji } from '../text-sanitize';
|
||||
import { TripAccessService } from '../../trip-membership/trip-access.service';
|
||||
|
||||
/**
|
||||
* GET /api/day-schedule/:tripId — bounded time contributions plugins attach to the
|
||||
@@ -84,7 +83,7 @@ function normalize(pluginId: string, tripDayIds: ReadonlySet<number>, raw: unkno
|
||||
export class DayScheduleController {
|
||||
constructor(
|
||||
private readonly hooks: PluginHooks,
|
||||
@InjectRepository(Trips) private readonly trips: TripsRepository,
|
||||
private readonly trips: TripAccessService,
|
||||
// CT1 (Plan 3j Task 5) — the trip's day-id set, converted onto Days.repository.ts.
|
||||
@InjectRepository(Days) private readonly days: DaysRepository,
|
||||
) {}
|
||||
|
||||
@@ -1,14 +1,13 @@
|
||||
import { Controller, Get, Param, Req, UseGuards } from '@nestjs/common';
|
||||
import type { Request } from 'express';
|
||||
import { InjectRepository } from '@mikro-orm/nestjs';
|
||||
import { Trips } from '../../../db/entities/Trips.entity';
|
||||
import type { TripsRepository } from '../../../db/repositories/Trips.repository';
|
||||
import { Days } from '../../../db/entities/Days.entity';
|
||||
import type { DaysRepository } from '../../../db/repositories/Days.repository';
|
||||
import { JwtAuthGuard } from '../../auth-core/jwt-auth.guard';
|
||||
import { pluginsEnabled } from '../kill-switch';
|
||||
import { PluginHooks } from '../plugin-hooks.service';
|
||||
import { stripEmoji } from '../text-sanitize';
|
||||
import { TripAccessService } from '../../trip-membership/trip-access.service';
|
||||
|
||||
/**
|
||||
* GET /api/day-tints/:tripId — the colours the planner paints into a day card in the
|
||||
@@ -161,7 +160,7 @@ function normalize(pluginId: string, tripDayIds: ReadonlySet<number>, raw: unkno
|
||||
export class DayTintsController {
|
||||
constructor(
|
||||
private readonly hooks: PluginHooks,
|
||||
@InjectRepository(Trips) private readonly trips: TripsRepository,
|
||||
private readonly trips: TripAccessService,
|
||||
// CT2 (Plan 3j Task 5) — the trip's day-id set, converted onto Days.repository.ts.
|
||||
@InjectRepository(Days) private readonly days: DaysRepository,
|
||||
) {}
|
||||
|
||||
@@ -1,12 +1,10 @@
|
||||
import { Controller, Get, Param, Req, UseGuards } from '@nestjs/common';
|
||||
import type { Request } from 'express';
|
||||
import { InjectRepository } from '@mikro-orm/nestjs';
|
||||
import { Trips } from '../../../db/entities/Trips.entity';
|
||||
import type { TripsRepository } from '../../../db/repositories/Trips.repository';
|
||||
import { JwtAuthGuard } from '../../auth-core/jwt-auth.guard';
|
||||
import { pluginsEnabled } from '../kill-switch';
|
||||
import { PluginHooks } from '../plugin-hooks.service';
|
||||
import { stripEmoji } from '../text-sanitize';
|
||||
import { TripAccessService } from '../../trip-membership/trip-access.service';
|
||||
|
||||
/**
|
||||
* GET /api/map-layers/:tripId — bounded vector overlays plugins draw on the trip map
|
||||
@@ -149,7 +147,7 @@ function normalize(pluginId: string, raw: unknown): MapLayer[] {
|
||||
export class MapLayersController {
|
||||
constructor(
|
||||
private readonly hooks: PluginHooks,
|
||||
@InjectRepository(Trips) private readonly trips: TripsRepository,
|
||||
private readonly trips: TripAccessService,
|
||||
) {}
|
||||
|
||||
@Get(':tripId')
|
||||
|
||||
@@ -1,12 +1,10 @@
|
||||
import { Controller, Get, Param, Req, UseGuards } from '@nestjs/common';
|
||||
import type { Request } from 'express';
|
||||
import { InjectRepository } from '@mikro-orm/nestjs';
|
||||
import { Trips } from '../../../db/entities/Trips.entity';
|
||||
import type { TripsRepository } from '../../../db/repositories/Trips.repository';
|
||||
import { JwtAuthGuard } from '../../auth-core/jwt-auth.guard';
|
||||
import { pluginsEnabled } from '../kill-switch';
|
||||
import { PluginHooks } from '../plugin-hooks.service';
|
||||
import { stripEmoji } from '../text-sanitize';
|
||||
import { TripAccessService } from '../../trip-membership/trip-access.service';
|
||||
|
||||
/**
|
||||
* GET /api/map-markers/:tripId — bounded markers plugins overlay on the trip map
|
||||
@@ -82,7 +80,7 @@ function normalize(pluginId: string, raw: unknown): MapMarker[] {
|
||||
export class MapMarkersController {
|
||||
constructor(
|
||||
private readonly hooks: PluginHooks,
|
||||
@InjectRepository(Trips) private readonly trips: TripsRepository,
|
||||
private readonly trips: TripAccessService,
|
||||
) {}
|
||||
|
||||
@Get(':tripId')
|
||||
|
||||
@@ -1,12 +1,10 @@
|
||||
import { Controller, Get, Param, Req, UseGuards } from '@nestjs/common';
|
||||
import type { Request } from 'express';
|
||||
import { InjectRepository } from '@mikro-orm/nestjs';
|
||||
import { Trips } from '../../../db/entities/Trips.entity';
|
||||
import type { TripsRepository } from '../../../db/repositories/Trips.repository';
|
||||
import { JwtAuthGuard } from '../../auth-core/jwt-auth.guard';
|
||||
import { pluginsEnabled } from '../kill-switch';
|
||||
import { PluginHooks } from '../plugin-hooks.service';
|
||||
import { stripEmoji } from '../text-sanitize';
|
||||
import { TripAccessService } from '../../trip-membership/trip-access.service';
|
||||
|
||||
/**
|
||||
* GET /api/pdf-sections/:tripId — text-only sections plugins append to a trip's
|
||||
@@ -78,7 +76,7 @@ function normalize(pluginId: string, raw: unknown): PdfSection[] {
|
||||
export class PdfSectionsController {
|
||||
constructor(
|
||||
private readonly hooks: PluginHooks,
|
||||
@InjectRepository(Trips) private readonly trips: TripsRepository,
|
||||
private readonly trips: TripAccessService,
|
||||
) {}
|
||||
|
||||
@Get(':tripId')
|
||||
|
||||
@@ -1,14 +1,13 @@
|
||||
import { Controller, Get, Param, Req, UseGuards } from '@nestjs/common';
|
||||
import type { Request } from 'express';
|
||||
import { InjectRepository } from '@mikro-orm/nestjs';
|
||||
import { Trips } from '../../../db/entities/Trips.entity';
|
||||
import type { TripsRepository } from '../../../db/repositories/Trips.repository';
|
||||
import { Places } from '../../../db/entities/Places.entity';
|
||||
import type { PlacesRepository } from '../../../db/repositories/Places.repository';
|
||||
import { JwtAuthGuard } from '../../auth-core/jwt-auth.guard';
|
||||
import { pluginsEnabled } from '../kill-switch';
|
||||
import { PluginHooks } from '../plugin-hooks.service';
|
||||
import { stripEmoji } from '../text-sanitize';
|
||||
import { TripAccessService } from '../../trip-membership/trip-access.service';
|
||||
|
||||
/**
|
||||
* GET /api/place-details/:placeId — extra info for a place, contributed by plugins
|
||||
@@ -67,7 +66,7 @@ function normalize(raw: unknown): DetailItem[] {
|
||||
export class PlaceDetailsController {
|
||||
constructor(
|
||||
private readonly hooks: PluginHooks,
|
||||
@InjectRepository(Trips) private readonly trips: TripsRepository,
|
||||
private readonly trips: TripAccessService,
|
||||
// CT7 (Plan 3j Task 5) — the place's owning trip id, converted onto Places.repository.ts.
|
||||
@InjectRepository(Places) private readonly places: PlacesRepository,
|
||||
) {}
|
||||
|
||||
@@ -30,6 +30,7 @@ import { PdfSectionsController } from './pdf-sections.controller';
|
||||
import { AtlasLayersController } from './atlas-layers.controller';
|
||||
import { JournalEntryRowsController } from './journal-entry-rows.controller';
|
||||
import { DemoModule } from '../../common/demo.module';
|
||||
import { TripMembershipModule } from '../../trip-membership/trip-membership.module';
|
||||
|
||||
/**
|
||||
* The read-only surface plugins contribute to the app: photos, calendar events,
|
||||
@@ -63,7 +64,7 @@ import { DemoModule } from '../../common/demo.module';
|
||||
* dependency unresolved.
|
||||
*/
|
||||
@Module({
|
||||
imports: [PluginsRuntimeModule, AddonsModule, JourneyDomainModule, DemoModule, MikroOrmModule.forFeature([JourneyEntries, Plugins, Days, Places, Trips])],
|
||||
imports: [TripMembershipModule, PluginsRuntimeModule, AddonsModule, JourneyDomainModule, DemoModule, MikroOrmModule.forFeature([JourneyEntries, Plugins, Days, Places, Trips])],
|
||||
controllers: [
|
||||
PlaceDetailsController,
|
||||
PluginSearchController,
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
import { Body, Controller, Param, Post, Req, UseGuards } from '@nestjs/common';
|
||||
import type { Request } from 'express';
|
||||
import { InjectRepository } from '@mikro-orm/nestjs';
|
||||
import { Trips } from '../../../db/entities/Trips.entity';
|
||||
import type { TripsRepository } from '../../../db/repositories/Trips.repository';
|
||||
import { Plugins } from '../../../db/entities/Plugins.entity';
|
||||
import type { PluginsRepository } from '../../../db/repositories/Plugins.repository';
|
||||
import { JwtAuthGuard } from '../../auth-core/jwt-auth.guard';
|
||||
@@ -10,6 +8,7 @@ import { pluginsEnabled } from '../kill-switch';
|
||||
import { PluginHooks } from '../plugin-hooks.service';
|
||||
import { PluginRouteDto } from '../plugins.dto';
|
||||
import { normalize, declaredProfiles, readWaypoints, PROFILE_RE, type PluginRouteOut } from './plugin-route-normalize';
|
||||
import { TripAccessService } from '../../trip-membership/trip-access.service';
|
||||
export type { PluginRouteOut } from './plugin-route-normalize';
|
||||
|
||||
/**
|
||||
@@ -36,7 +35,7 @@ export type { PluginRouteOut } from './plugin-route-normalize';
|
||||
export class PluginRoutesController {
|
||||
constructor(
|
||||
private readonly hooks: PluginHooks,
|
||||
@InjectRepository(Trips) private readonly trips: TripsRepository,
|
||||
private readonly trips: TripAccessService,
|
||||
@InjectRepository(Plugins) private readonly plugins: PluginsRepository,
|
||||
) {}
|
||||
|
||||
|
||||
@@ -1,12 +1,10 @@
|
||||
import { Controller, Get, Query, Req, UseGuards } from '@nestjs/common';
|
||||
import type { Request } from 'express';
|
||||
import { InjectRepository } from '@mikro-orm/nestjs';
|
||||
import { Trips } from '../../../db/entities/Trips.entity';
|
||||
import type { TripsRepository } from '../../../db/repositories/Trips.repository';
|
||||
import { JwtAuthGuard } from '../../auth-core/jwt-auth.guard';
|
||||
import { pluginsEnabled } from '../kill-switch';
|
||||
import { PluginHooks } from '../plugin-hooks.service';
|
||||
import { stripEmoji } from '../text-sanitize';
|
||||
import { TripAccessService } from '../../trip-membership/trip-access.service';
|
||||
|
||||
/**
|
||||
* GET /api/trip-card-contributions?tripIds=1,2,3 — host-rendered badges that plugins
|
||||
@@ -86,7 +84,7 @@ function normalize(pluginId: string, raw: unknown, allowed: Set<number>): TripCa
|
||||
export class TripCardContributionsController {
|
||||
constructor(
|
||||
private readonly hooks: PluginHooks,
|
||||
@InjectRepository(Trips) private readonly trips: TripsRepository,
|
||||
private readonly trips: TripAccessService,
|
||||
) {}
|
||||
|
||||
@Get()
|
||||
|
||||
@@ -1,12 +1,10 @@
|
||||
import { Controller, Get, Param, Req, UseGuards } from '@nestjs/common';
|
||||
import type { Request } from 'express';
|
||||
import { InjectRepository } from '@mikro-orm/nestjs';
|
||||
import { Trips } from '../../../db/entities/Trips.entity';
|
||||
import type { TripsRepository } from '../../../db/repositories/Trips.repository';
|
||||
import { JwtAuthGuard } from '../../auth-core/jwt-auth.guard';
|
||||
import { pluginsEnabled } from '../kill-switch';
|
||||
import { PluginHooks } from '../plugin-hooks.service';
|
||||
import { stripEmoji } from '../text-sanitize';
|
||||
import { TripAccessService } from '../../trip-membership/trip-access.service';
|
||||
|
||||
/**
|
||||
* GET /api/trip-warnings/:tripId — validation/warning contributions from plugins
|
||||
@@ -31,7 +29,7 @@ const MESSAGE_MAX = 300;
|
||||
export class TripWarningsController {
|
||||
constructor(
|
||||
private readonly hooks: PluginHooks,
|
||||
@InjectRepository(Trips) private readonly trips: TripsRepository,
|
||||
private readonly trips: TripAccessService,
|
||||
) {}
|
||||
|
||||
@Get(':tripId')
|
||||
|
||||
@@ -1,12 +1,10 @@
|
||||
import { z } from 'zod';
|
||||
import { McpController, Tool, TOOL_ANNOTATIONS_READONLY, ok, type McpContext, type McpTextResult } from '../../../nest-mcp';
|
||||
import { noAccess } from '../../../mcp/tools/_shared';
|
||||
import { InjectRepository } from '@mikro-orm/nestjs';
|
||||
import { Trips } from '../../../db/entities/Trips.entity';
|
||||
import type { TripsRepository } from '../../../db/repositories/Trips.repository';
|
||||
import { pluginsEnabled } from '../kill-switch';
|
||||
import { PluginHooks } from '../plugin-hooks.service';
|
||||
import { stripEmoji } from '../text-sanitize';
|
||||
import { TripAccessService } from '../../trip-membership/trip-access.service';
|
||||
|
||||
/**
|
||||
* The MCP half of GET /api/trip-warnings/:tripId (#1429).
|
||||
@@ -42,7 +40,7 @@ const MESSAGE_MAX = 300;
|
||||
export class TripWarningsMcp {
|
||||
constructor(
|
||||
private readonly hooks: PluginHooks,
|
||||
@InjectRepository(Trips) private readonly trips: TripsRepository,
|
||||
private readonly trips: TripAccessService,
|
||||
) {}
|
||||
|
||||
@Tool({
|
||||
|
||||
@@ -1,12 +1,10 @@
|
||||
import { Controller, Get, Param, Req, UseGuards } from '@nestjs/common';
|
||||
import type { Request } from 'express';
|
||||
import { InjectRepository } from '@mikro-orm/nestjs';
|
||||
import { Trips } from '../../../db/entities/Trips.entity';
|
||||
import type { TripsRepository } from '../../../db/repositories/Trips.repository';
|
||||
import { JwtAuthGuard } from '../../auth-core/jwt-auth.guard';
|
||||
import { pluginsEnabled } from '../kill-switch';
|
||||
import { PluginHooks } from '../plugin-hooks.service';
|
||||
import { stripEmoji } from '../text-sanitize';
|
||||
import { TripAccessService } from '../../trip-membership/trip-access.service';
|
||||
|
||||
/**
|
||||
* GET /api/view-contributions/:view/:tripId — host-rendered columns/actions that
|
||||
@@ -106,7 +104,7 @@ function normalize(pluginId: string, raw: unknown): Contribution[] {
|
||||
export class ViewContributionsController {
|
||||
constructor(
|
||||
private readonly hooks: PluginHooks,
|
||||
@InjectRepository(Trips) private readonly trips: TripsRepository,
|
||||
private readonly trips: TripAccessService,
|
||||
) {}
|
||||
|
||||
@Get(':view/:tripId')
|
||||
|
||||
@@ -6,6 +6,7 @@ import { AppSettings } from '../../db/entities/AppSettings.entity';
|
||||
import { Users } from '../../db/entities/Users.entity';
|
||||
import { Trips } from '../../db/entities/Trips.entity';
|
||||
import { processRooms, RoomRegistry, roomsSlot } from './ws-state';
|
||||
import { TripMembershipModule } from '../trip-membership/trip-membership.module';
|
||||
|
||||
/**
|
||||
* The transport, kept out of RealtimeModule on purpose.
|
||||
@@ -28,7 +29,7 @@ import { processRooms, RoomRegistry, roomsSlot } from './ws-state';
|
||||
// DatabaseService onto UsersRepository/AppSettingsRepository. Trips: Plan 4
|
||||
// Task 2 — handleJoin's own canAccessTrip delegate, now TripsRepository
|
||||
// directly.
|
||||
imports: [EphemeralTokenModule, MikroOrmModule.forFeature([Users, AppSettings, Trips])],
|
||||
imports: [TripMembershipModule, EphemeralTokenModule, MikroOrmModule.forFeature([Users, AppSettings, Trips])],
|
||||
// The room registry is the process-wide in-memory one; the constructor
|
||||
// below hands whichever one the container resolved to the broadcast
|
||||
// functions, so the gateway and the broadcasts never use two registries.
|
||||
|
||||
@@ -16,8 +16,6 @@ import { AppSettings } from '../../db/entities/AppSettings.entity';
|
||||
import type { AppSettingsRepository } from '../../db/repositories/AppSettings.repository';
|
||||
import { Users } from '../../db/entities/Users.entity';
|
||||
import type { UsersRepository } from '../../db/repositories/Users.repository';
|
||||
import { Trips } from '../../db/entities/Trips.entity';
|
||||
import type { TripsRepository } from '../../db/repositories/Trips.repository';
|
||||
import { User } from '../../types';
|
||||
import { logError } from '../audit/audit-log.logger';
|
||||
import {
|
||||
@@ -33,6 +31,7 @@ import {
|
||||
import { JOURNEY_ACCESS, type JourneyAccess } from './journey-access.types';
|
||||
import { readAppSetting } from '../common/app-settings.registry';
|
||||
import { runningVersion } from '../../app-config';
|
||||
import { TripAccessService } from '../trip-membership/trip-access.service';
|
||||
|
||||
const HEARTBEAT_INTERVAL = 30_000;
|
||||
|
||||
@@ -64,9 +63,9 @@ export class RealtimeGateway
|
||||
|
||||
constructor(
|
||||
// Plan 4 Task 2 — canAccessTrip's own DatabaseService delegation is gone:
|
||||
// this injects TripsRepository directly (same constructor slot) and
|
||||
// this injects TripAccessService (trip-membership) (same constructor slot) and
|
||||
// calls findAccessible from `handleJoin`.
|
||||
@InjectRepository(Trips) private readonly trips: TripsRepository,
|
||||
private readonly trips: TripAccessService,
|
||||
private readonly tokens: EphemeralTokenService,
|
||||
/*
|
||||
* For the book rooms, and injected rather than reimplemented: who may open
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { Controller, HttpException, Param, Post, UploadedFile, UseGuards, UseInterceptors } from '@nestjs/common';
|
||||
import { FileInterceptor } from '@nestjs/platform-express';
|
||||
import { InjectRepository } from '@mikro-orm/nestjs';
|
||||
import { memoryStorage } from 'multer';
|
||||
import type { ReceiptScanStartResponse } from '@trek/shared';
|
||||
import type { User } from '../../types';
|
||||
@@ -8,12 +7,11 @@ import { ADDON_IDS } from '../../addons';
|
||||
import { AddonsService } from '../addons/addons.service';
|
||||
import { JwtAuthGuard } from '../auth-core/jwt-auth.guard';
|
||||
import { CurrentUser } from '../auth-core/current-user.decorator';
|
||||
import { Trips } from '../../db/entities/Trips.entity';
|
||||
import type { TripsRepository } from '../../db/repositories/Trips.repository';
|
||||
import { PermissionsService } from '../permissions/permissions.service';
|
||||
import { ImportJobsService } from '../booking-import/import-jobs.service';
|
||||
import { LlmParseService } from '../llm-parse/llm-parse.service';
|
||||
import { imageMimeType } from '../llm-parse/image-input';
|
||||
import { TripAccessService } from '../trip-membership/trip-access.service';
|
||||
|
||||
const MAX_FILE_BYTES = 10 * 1024 * 1024;
|
||||
|
||||
@@ -40,7 +38,7 @@ export class ReceiptScanController {
|
||||
constructor(
|
||||
private readonly importJobs: ImportJobsService,
|
||||
private readonly llmParse: LlmParseService,
|
||||
@InjectRepository(Trips) private readonly trips: TripsRepository,
|
||||
private readonly trips: TripAccessService,
|
||||
private readonly permissions: PermissionsService,
|
||||
private readonly addons: AddonsService,
|
||||
) {}
|
||||
|
||||
@@ -6,10 +6,11 @@ import { BookingImportModule } from '../booking-import/booking-import.module';
|
||||
import { LlmParseModule } from '../llm-parse/llm-parse.module';
|
||||
import { AddonsModule } from '../addons/addons.module';
|
||||
import { PermissionsModule } from '../permissions/permissions.module';
|
||||
import { TripMembershipModule } from '../trip-membership/trip-membership.module';
|
||||
|
||||
/** The Costs tab's receipt scan: an HTTP surface over the import job queue. */
|
||||
@Module({
|
||||
imports: [MikroOrmModule.forFeature([Trips]), BookingImportModule, LlmParseModule, AddonsModule, PermissionsModule],
|
||||
imports: [TripMembershipModule, MikroOrmModule.forFeature([Trips]), BookingImportModule, LlmParseModule, AddonsModule, PermissionsModule],
|
||||
controllers: [ReceiptScanController],
|
||||
})
|
||||
export class ReceiptScanModule {}
|
||||
|
||||
@@ -8,12 +8,10 @@ import { airtrailImportSchema } from '@trek/shared';
|
||||
import { ADDON_IDS } from '../../addons';
|
||||
import { addonGate } from '../addons/addon-gate';
|
||||
import { AddonsService } from '../addons/addons.service';
|
||||
import { InjectRepository } from '@mikro-orm/nestjs';
|
||||
import { Trips } from '../../db/entities/Trips.entity';
|
||||
import type { TripsRepository } from '../../db/repositories/Trips.repository';
|
||||
import { McpToolGuardsService } from '../mcp-shared/mcp-tool-guards.service';
|
||||
import { noAccess, permissionDenied } from '../../mcp/tools/_shared';
|
||||
import { AirtrailImportService } from '../integrations/airtrail-import.service';
|
||||
import { TripAccessService } from '../trip-membership/trip-access.service';
|
||||
|
||||
/** The handler's own @RequireAddon(ADDON_IDS.AIRTRAIL), as an availability gate. */
|
||||
const airtrailAddonOn = addonGate(ADDON_IDS.AIRTRAIL);
|
||||
@@ -44,9 +42,9 @@ export class ReservationImportMcp {
|
||||
constructor(
|
||||
private readonly airtrailImport: AirtrailImportService,
|
||||
// Plan 4 Task 2 — canAccessTrip's own DatabaseService delegation is gone:
|
||||
// this injects TripsRepository directly (same constructor slot) and
|
||||
// this injects TripAccessService (trip-membership) (same constructor slot) and
|
||||
// calls findAccessible.
|
||||
@InjectRepository(Trips) private readonly trips: TripsRepository,
|
||||
private readonly trips: TripAccessService,
|
||||
private readonly guards: McpToolGuardsService,
|
||||
readonly addons: AddonsService,
|
||||
) {}
|
||||
|
||||
@@ -8,6 +8,7 @@ import { AddonsModule } from '../addons/addons.module';
|
||||
import { PermissionsModule } from '../permissions/permissions.module';
|
||||
import { McpSharedModule } from '../mcp-shared/mcp-shared.module';
|
||||
import { Trips } from '../../db/entities/Trips.entity';
|
||||
import { TripMembershipModule } from '../trip-membership/trip-membership.module';
|
||||
|
||||
/**
|
||||
* The one route prefix that turns something external into reservations.
|
||||
@@ -22,7 +23,7 @@ import { Trips } from '../../db/entities/Trips.entity';
|
||||
* @Global, so it is named here.
|
||||
*/
|
||||
@Module({
|
||||
imports: [BookingImportModule, AirtrailModule, AddonsModule, PermissionsModule, McpSharedModule, MikroOrmModule.forFeature([Trips])],
|
||||
imports: [TripMembershipModule, BookingImportModule, AirtrailModule, AddonsModule, PermissionsModule, McpSharedModule, MikroOrmModule.forFeature([Trips])],
|
||||
controllers: [ReservationImportController],
|
||||
providers: [ReservationImportMcp],
|
||||
})
|
||||
|
||||
@@ -5,15 +5,13 @@ import { noAccess } from '../../mcp/tools/_shared';
|
||||
import { McpController, Tool, TOOL_ANNOTATIONS_READONLY, ok, type McpContext } from '../../nest-mcp';
|
||||
import { addonGate } from '../addons/addon-gate';
|
||||
import { AddonsService } from '../addons/addons.service';
|
||||
import { InjectRepository } from '@mikro-orm/nestjs';
|
||||
import { Trips } from '../../db/entities/Trips.entity';
|
||||
import type { TripsRepository } from '../../db/repositories/Trips.repository';
|
||||
import { ChargingService } from './charging.service';
|
||||
import { answeringRefusals } from './roadtrip-mcp.helpers';
|
||||
import { TripAccessService } from '../trip-membership/trip-access.service';
|
||||
|
||||
@McpController()
|
||||
export class ChargingMcp {
|
||||
constructor(private readonly charging: ChargingService, @InjectRepository(Trips) private readonly tripsRepo: TripsRepository, readonly addons: AddonsService) {}
|
||||
constructor(private readonly charging: ChargingService, private readonly tripsRepo: TripAccessService, readonly addons: AddonsService) {}
|
||||
@Tool({ name: 'get_roadtrip_charging_info', description: 'Read open charging availability and published tariffs for a saved charging stop. No API key required. Coverage varies by region and operator. Unknown or stale status is not available capacity. Prices can have conditions and extra fees and are not personalized roaming quotes.',
|
||||
inputSchema: { tripId: z.number().int().positive(), placeId: z.number().int().positive() },
|
||||
annotations: TOOL_ANNOTATIONS_READONLY, access: { group: 'trips', mode: 'read' }, when: addonGate(ADDON_IDS.ROADTRIP) })
|
||||
|
||||
@@ -2,9 +2,6 @@ import { z } from 'zod';
|
||||
import { MAX_TRIP_DAYS, roadtripDayBoundarySchema, type RoadtripDayBoundary } from '@trek/shared';
|
||||
import { McpController, Tool, TOOL_ANNOTATIONS_READONLY, TOOL_ANNOTATIONS_WRITE, ok, type McpContext } from '../../nest-mcp';
|
||||
import { noAccess, permissionDenied } from '../../mcp/tools/_shared';
|
||||
import { InjectRepository } from '@mikro-orm/nestjs';
|
||||
import { Trips } from '../../db/entities/Trips.entity';
|
||||
import type { TripsRepository } from '../../db/repositories/Trips.repository';
|
||||
import { McpToolGuardsService } from '../mcp-shared/mcp-tool-guards.service';
|
||||
import { RealtimeService } from '../realtime/realtime.service';
|
||||
import { AddonsService } from '../addons/addons.service';
|
||||
@@ -12,6 +9,7 @@ import { addonGate } from '../addons/addon-gate';
|
||||
import { ADDON_IDS } from '../../addons';
|
||||
import { DayBoundariesService } from './day-boundaries.service';
|
||||
import { answeringRefusals } from './roadtrip-mcp.helpers';
|
||||
import { TripAccessService } from '../trip-membership/trip-access.service';
|
||||
|
||||
const when = addonGate(ADDON_IDS.ROADTRIP);
|
||||
|
||||
@@ -19,7 +17,7 @@ const when = addonGate(ADDON_IDS.ROADTRIP);
|
||||
export class DayBoundariesMcp {
|
||||
constructor(
|
||||
private readonly boundaries: DayBoundariesService,
|
||||
@InjectRepository(Trips) private readonly tripsRepo: TripsRepository,
|
||||
private readonly tripsRepo: TripAccessService,
|
||||
private readonly guards: McpToolGuardsService,
|
||||
private readonly realtime: RealtimeService,
|
||||
readonly addons: AddonsService,
|
||||
|
||||
@@ -8,10 +8,9 @@ import { UnitOfWork } from '../database/unit-of-work';
|
||||
import { PlacesService } from '../places/places.service';
|
||||
import { AssignmentsService } from '../assignments/assignments.service';
|
||||
import { PermissionsService } from '../permissions/permissions.service';
|
||||
import { Trips } from '../../db/entities/Trips.entity';
|
||||
import type { TripsRepository } from '../../db/repositories/Trips.repository';
|
||||
import { Users } from '../../db/entities/Users.entity';
|
||||
import type { UsersRepository } from '../../db/repositories/Users.repository';
|
||||
import { TripAccessService } from '../trip-membership/trip-access.service';
|
||||
|
||||
@Injectable()
|
||||
export class GoogleRouteService {
|
||||
@@ -19,7 +18,7 @@ export class GoogleRouteService {
|
||||
private readonly places: PlacesService, private readonly assignments: AssignmentsService,
|
||||
private readonly permissions: PermissionsService,
|
||||
private readonly uow: UnitOfWork,
|
||||
@InjectRepository(Trips) private readonly tripsRepo: TripsRepository,
|
||||
private readonly tripsRepo: TripAccessService,
|
||||
@InjectRepository(Users) private readonly usersRepo: UsersRepository) {}
|
||||
|
||||
async preview(raw: string): Promise<GoogleRoutePreview> {
|
||||
|
||||
@@ -4,14 +4,12 @@ import { noAccess } from '../../mcp/tools/_shared';
|
||||
import { McpController, Tool, TOOL_ANNOTATIONS_READONLY, ok, type McpContext } from '../../nest-mcp';
|
||||
import { addonGate } from '../addons/addon-gate';
|
||||
import { AddonsService } from '../addons/addons.service';
|
||||
import { InjectRepository } from '@mikro-orm/nestjs';
|
||||
import { Trips } from '../../db/entities/Trips.entity';
|
||||
import type { TripsRepository } from '../../db/repositories/Trips.repository';
|
||||
import { RoadtripHazardsService } from './roadtrip-hazards.service';
|
||||
import { TripAccessService } from '../trip-membership/trip-access.service';
|
||||
|
||||
@McpController()
|
||||
export class RoadtripHazardsMcp {
|
||||
constructor(private readonly hazards: RoadtripHazardsService, @InjectRepository(Trips) private readonly tripsRepo: TripsRepository, readonly addons: AddonsService) {}
|
||||
constructor(private readonly hazards: RoadtripHazardsService, private readonly tripsRepo: TripAccessService, readonly addons: AddonsService) {}
|
||||
@Tool({
|
||||
name: 'get_roadtrip_hazards',
|
||||
description: 'Read current DWD and GDACS hazard notices, geometry, source timestamps and feed availability. These are current notices, not forecasts for the trip dates or confirmed road closures. A point means no affected-area polygon is available. Coverage can be incomplete. This does not change routes. The shared roadtrip_show_hazards preference controls map display.',
|
||||
|
||||
@@ -6,8 +6,6 @@ import { RoadtripRouterService } from './roadtrip-router.service';
|
||||
import { RoadtripService } from './roadtrip.service';
|
||||
import { Injectable, HttpException } from '@nestjs/common';
|
||||
import { type RoadtripPreferences } from '@trek/shared';
|
||||
import { Trips } from '../../db/entities/Trips.entity';
|
||||
import type { TripsRepository } from '../../db/repositories/Trips.repository';
|
||||
import { Days } from '../../db/entities/Days.entity';
|
||||
import type { DaysRepository } from '../../db/repositories/Days.repository';
|
||||
import { DayAssignments } from '../../db/entities/DayAssignments.entity';
|
||||
@@ -48,6 +46,7 @@ import {
|
||||
type RouteAvoidClass,
|
||||
standsAsDay,
|
||||
} from '@trek/shared/roadtrip';
|
||||
import { TripAccessService } from '../trip-membership/trip-access.service';
|
||||
|
||||
const stopKey = (s: RoadtripStop) =>
|
||||
`${s.lat.toFixed(5)},${s.lng.toFixed(5)},${s.legMode ?? ''},${s.incomingLegMode ?? ''}`;
|
||||
@@ -60,7 +59,7 @@ export class RoadtripPlanService {
|
||||
private readonly router: RoadtripRouterService,
|
||||
private readonly roadtrip: RoadtripService,
|
||||
private readonly boundaries: DayBoundariesService,
|
||||
@InjectRepository(Trips) private readonly tripsRepo: TripsRepository,
|
||||
private readonly tripsRepo: TripAccessService,
|
||||
@InjectRepository(Days) private readonly daysRepo: DaysRepository,
|
||||
@InjectRepository(DayAssignments) private readonly dayAssignmentsRepo: DayAssignmentsRepository,
|
||||
@InjectRepository(DayAccommodations) private readonly dayAccommodationsRepo: DayAccommodationsRepository,
|
||||
|
||||
@@ -10,15 +10,13 @@ import {
|
||||
} from '../../nest-mcp';
|
||||
import { addonGate } from '../addons/addon-gate';
|
||||
import { AddonsService } from '../addons/addons.service';
|
||||
import { InjectRepository } from '@mikro-orm/nestjs';
|
||||
import { Trips } from '../../db/entities/Trips.entity';
|
||||
import type { TripsRepository } from '../../db/repositories/Trips.repository';
|
||||
import { McpToolGuardsService } from '../mcp-shared/mcp-tool-guards.service';
|
||||
import { RoadtripPreferencesService } from './roadtrip-preferences.service';
|
||||
import { answeringRefusals } from './roadtrip-mcp.helpers';
|
||||
import { roadtripPreferencesUpdateSchema, type RoadtripPreferences } from '@trek/shared';
|
||||
|
||||
import { z } from 'zod';
|
||||
import { TripAccessService } from '../trip-membership/trip-access.service';
|
||||
|
||||
const when = addonGate(ADDON_IDS.ROADTRIP);
|
||||
|
||||
@@ -27,7 +25,7 @@ export class RoadtripPreferencesMcp {
|
||||
constructor(
|
||||
private readonly preferences: RoadtripPreferencesService,
|
||||
readonly addons: AddonsService,
|
||||
@InjectRepository(Trips) private readonly tripsRepo: TripsRepository,
|
||||
private readonly tripsRepo: TripAccessService,
|
||||
private readonly guards: McpToolGuardsService,
|
||||
) {}
|
||||
|
||||
|
||||
@@ -1,15 +1,13 @@
|
||||
import { McpController, Tool, TOOL_ANNOTATIONS_READONLY, TOOL_ANNOTATIONS_NON_IDEMPOTENT, ok, type McpContext } from '../../nest-mcp';
|
||||
import { z } from 'zod';
|
||||
import { InjectRepository } from '@mikro-orm/nestjs';
|
||||
import { RoadtripService } from './roadtrip.service';
|
||||
import { Trips } from '../../db/entities/Trips.entity';
|
||||
import type { TripsRepository } from '../../db/repositories/Trips.repository';
|
||||
import { McpToolGuardsService } from '../mcp-shared/mcp-tool-guards.service';
|
||||
import { noAccess, permissionDenied } from '../../mcp/tools/_shared';
|
||||
import { ADDON_IDS } from '../../addons';
|
||||
import { addonGate } from '../addons/addon-gate';
|
||||
import { AddonsService } from '../addons/addons.service';
|
||||
import { roadtripViaUpdateRequestSchema, type RoadtripViaUpdateRequest } from '@trek/shared';
|
||||
import { TripAccessService } from '../trip-membership/trip-access.service';
|
||||
|
||||
/**
|
||||
* The whole surface rides the road trip addon, the same way the controller does
|
||||
@@ -40,7 +38,7 @@ const BOOKEND_VIA_NOTE =
|
||||
export class RoadtripMcp {
|
||||
constructor(
|
||||
private readonly roadtrip: RoadtripService,
|
||||
@InjectRepository(Trips) private readonly tripsRepo: TripsRepository,
|
||||
private readonly tripsRepo: TripAccessService,
|
||||
private readonly guards: McpToolGuardsService,
|
||||
readonly addons: AddonsService,
|
||||
) {}
|
||||
|
||||
@@ -47,6 +47,7 @@ import { DayAccommodations } from '../../db/entities/DayAccommodations.entity';
|
||||
import { Reservations } from '../../db/entities/Reservations.entity';
|
||||
import { ReservationEndpoints } from '../../db/entities/ReservationEndpoints.entity';
|
||||
import { ReservationDayPositions } from '../../db/entities/ReservationDayPositions.entity';
|
||||
import { TripMembershipModule } from '../trip-membership/trip-membership.module';
|
||||
|
||||
/** Road trip domain (#1797): the points a drive is routed through. Registered in AppModule. */
|
||||
@Module({
|
||||
@@ -71,7 +72,7 @@ import { ReservationDayPositions } from '../../db/entities/ReservationDayPositio
|
||||
// `ReservationDayPositions`: `RoadtripPlanService`'s RPL3-RPL5 reads (the
|
||||
// booked nights and the carrier bookings that seam the drive) plus the RPL6
|
||||
// terminal and RS19 day-position reads it joins onto them.
|
||||
imports: [
|
||||
imports: [TripMembershipModule,
|
||||
McpSharedModule,
|
||||
PermissionsModule,
|
||||
|
||||
|
||||
@@ -13,6 +13,7 @@ import { TodoItems } from '../../db/entities/TodoItems.entity';
|
||||
import { TodoCategoryAssignees } from '../../db/entities/TodoCategoryAssignees.entity';
|
||||
import { Trips } from '../../db/entities/Trips.entity';
|
||||
import { TripMembers } from '../../db/entities/TripMembers.entity';
|
||||
import { TripMembershipModule } from '../trip-membership/trip-membership.module';
|
||||
|
||||
/** To-do domain (S3 — Phase 2 trip sub-domain). Registered in AppModule.
|
||||
* Exports TodoService for in-container consumers (TripsService bundle).
|
||||
@@ -23,7 +24,7 @@ import { TripMembers } from '../../db/entities/TripMembers.entity';
|
||||
* (Plan 4 Task 2): `verifyTripAccess`'s own canAccessTrip delegate, now
|
||||
* TripsRepository directly. */
|
||||
@Module({
|
||||
imports: [MikroOrmModule.forFeature([TodoItems, TodoCategoryAssignees, Trips, TripMembers]), McpSharedModule, PermissionsModule, RealtimeModule, PluginGuardsModule, AddonsModule],
|
||||
imports: [TripMembershipModule, MikroOrmModule.forFeature([TodoItems, TodoCategoryAssignees, Trips, TripMembers]), McpSharedModule, PermissionsModule, RealtimeModule, PluginGuardsModule, AddonsModule],
|
||||
controllers: [TodoController],
|
||||
providers: [TodoService, TodoMcp, TodoRpc],
|
||||
exports: [TodoService],
|
||||
|
||||
@@ -9,10 +9,10 @@ import { TodoItems } from '../../db/entities/TodoItems.entity';
|
||||
import type { TodoItemsRepository } from '../../db/repositories/TodoItems.repository';
|
||||
import { TodoCategoryAssignees } from '../../db/entities/TodoCategoryAssignees.entity';
|
||||
import type { TodoCategoryAssigneesRepository } from '../../db/repositories/TodoCategoryAssignees.repository';
|
||||
import { Trips } from '../../db/entities/Trips.entity';
|
||||
import type { TripsRepository, TripAccess } from '../../db/repositories/Trips.repository';
|
||||
import type { TripAccess } from '../../db/repositories/Trips.repository';
|
||||
import { TripMembers } from '../../db/entities/TripMembers.entity';
|
||||
import type { TripMembersRepository } from '../../db/repositories/TripMembers.repository';
|
||||
import { TripAccessService } from '../trip-membership/trip-access.service';
|
||||
|
||||
type Trip = TripAccess;
|
||||
|
||||
@@ -38,8 +38,8 @@ export class TodoService {
|
||||
@InjectRepository(TodoItems) private readonly todoItemsRepo: TodoItemsRepository,
|
||||
@InjectRepository(TodoCategoryAssignees) private readonly todoCategoryAssigneesRepo: TodoCategoryAssigneesRepository,
|
||||
// Plan 4 Task 2 — canAccessTrip's own DatabaseService delegation is
|
||||
// gone: this injects TripsRepository directly.
|
||||
@InjectRepository(Trips) private readonly tripsRepo: TripsRepository,
|
||||
// gone: this injects TripAccessService (trip-membership).
|
||||
private readonly tripsRepo: TripAccessService,
|
||||
// Plan 4 Task 3 — DatabaseService.rosterUserIds inlined onto
|
||||
// TripMembersRepository.rosterUserIds directly.
|
||||
@InjectRepository(TripMembers) private readonly tripMembersRepo: TripMembersRepository,
|
||||
|
||||
@@ -15,13 +15,11 @@ import {
|
||||
} from './transit-itinerary.helpers';
|
||||
import { noAccess, permissionDenied } from '../../mcp/tools/_shared';
|
||||
import { RateLimitService } from '../common/rate-limit.service';
|
||||
import { InjectRepository } from '@mikro-orm/nestjs';
|
||||
import { Trips } from '../../db/entities/Trips.entity';
|
||||
import type { TripsRepository } from '../../db/repositories/Trips.repository';
|
||||
import { DaysService } from '../days/days.service';
|
||||
import { ReservationsService } from '../reservations/reservations.service';
|
||||
import { SCHEDULED_TRANSIT_MODES, type TransitItinerary } from './transit.helpers';
|
||||
import { TransitService } from './transit.service';
|
||||
import { TripAccessService } from '../trip-membership/trip-access.service';
|
||||
|
||||
const TRANSIT_RATE_WINDOW = 15 * 60 * 1000;
|
||||
|
||||
@@ -65,9 +63,9 @@ export class TransitMcp {
|
||||
private readonly days: DaysService,
|
||||
private readonly reservations: ReservationsService,
|
||||
// Plan 4 Task 2 — canAccessTrip's own DatabaseService delegation is gone:
|
||||
// this injects TripsRepository directly (same constructor slot) and
|
||||
// this injects TripAccessService (trip-membership) (same constructor slot) and
|
||||
// calls findAccessible.
|
||||
@InjectRepository(Trips) private readonly trips: TripsRepository,
|
||||
private readonly trips: TripAccessService,
|
||||
private readonly guards: McpToolGuardsService,
|
||||
// The limiter RateLimitModule provides, so its store is the one every
|
||||
// other caller counts in. The MCP buckets (mcp_transit_*) are keyed by
|
||||
|
||||
@@ -13,6 +13,7 @@ import { Users } from '../../db/entities/Users.entity';
|
||||
import { Trips } from '../../db/entities/Trips.entity';
|
||||
import { GoogleQuotaModule } from '../google-quota/google-quota.module';
|
||||
import { AppConfigModule } from '../app-config/app-config.module';
|
||||
import { TripMembershipModule } from '../trip-membership/trip-membership.module';
|
||||
|
||||
/**
|
||||
* Transit domain (#1065) — the Transitous/MOTIS proxy, with the optional
|
||||
@@ -32,7 +33,7 @@ import { AppConfigModule } from '../app-config/app-config.module';
|
||||
*/
|
||||
@Module({
|
||||
// DaysModule + ReservationsModule: TransitMcp's create_transit_journey injects both.
|
||||
imports: [AppConfigModule, McpSharedModule, RateLimitModule, DaysModule, ReservationsModule, MikroOrmModule.forFeature([AppSettings, Users, Trips]), GoogleQuotaModule],
|
||||
imports: [TripMembershipModule, AppConfigModule, McpSharedModule, RateLimitModule, DaysModule, ReservationsModule, MikroOrmModule.forFeature([AppSettings, Users, Trips]), GoogleQuotaModule],
|
||||
controllers: [TransitController],
|
||||
providers: [TransitService, TransitMcp, GoogleTransitProvider],
|
||||
exports: [TransitService],
|
||||
|
||||
@@ -3,13 +3,12 @@ import { InjectRepository } from '@mikro-orm/nestjs';
|
||||
import crypto from 'crypto';
|
||||
import { UnitOfWork } from '../database/unit-of-work';
|
||||
import type { TripAccess } from '../../db/repositories/Trips.repository';
|
||||
import { Trips } from '../../db/entities/Trips.entity';
|
||||
import type { TripsRepository } from '../../db/repositories/Trips.repository';
|
||||
import { PermissionsService } from '../permissions/permissions.service';
|
||||
import { TripMembershipService } from '../trip-membership/trip-membership.service';
|
||||
import { TripInviteTokens } from '../../db/entities/TripInviteTokens.entity';
|
||||
import type { TripInviteTokensRepository } from '../../db/repositories/TripInviteTokens.repository';
|
||||
import type { User } from '../../types';
|
||||
import { TripAccessService } from '../trip-membership/trip-access.service';
|
||||
|
||||
type Trip = TripAccess;
|
||||
|
||||
@@ -41,9 +40,9 @@ export interface TripInviteInfo {
|
||||
export class TripInviteService {
|
||||
constructor(
|
||||
// Plan 4 Task 2 — canAccessTrip's own DatabaseService delegation is gone:
|
||||
// this injects TripsRepository directly (same constructor slot) and
|
||||
// this injects TripAccessService (trip-membership) (same constructor slot) and
|
||||
// calls findAccessible.
|
||||
@InjectRepository(Trips) private readonly trips: TripsRepository,
|
||||
private readonly trips: TripAccessService,
|
||||
private readonly permissions: PermissionsService,
|
||||
private readonly membership: TripMembershipService,
|
||||
private readonly uow: UnitOfWork,
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { InjectRepository } from '@mikro-orm/nestjs';
|
||||
import { Trips } from '../../db/entities/Trips.entity';
|
||||
import type { TripAccess, TripsRepository } from '../../db/repositories/Trips.repository';
|
||||
|
||||
/**
|
||||
* "May this user see this trip?" for every domain that is not the trips domain.
|
||||
*
|
||||
* It answers with the trip's access row (id, owner, currency) when the user
|
||||
* owns the trip or is a member, and undefined otherwise. It deliberately makes
|
||||
* no HTTP decision: a REST route turns undefined into its 404, an MCP tool into
|
||||
* its "not found or access denied" result, a plugin RPC into its error, and
|
||||
* each caller keeps the one it had.
|
||||
*
|
||||
* Before this the domains injected TripsRepository themselves for this one
|
||||
* read, so the trips table had a reader in thirty files outside its domain.
|
||||
* The id is bound exactly as given (TripsRepository.findAccessible explains
|
||||
* why it is never coerced here); a caller that coerced before still does.
|
||||
*/
|
||||
@Injectable()
|
||||
export class TripAccessService {
|
||||
constructor(@InjectRepository(Trips) private readonly trips: TripsRepository) {}
|
||||
|
||||
findAccessible(tripId: number | string, userId: number): Promise<TripAccess | undefined> {
|
||||
return this.trips.findAccessible(tripId, userId);
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { MikroOrmModule } from '@mikro-orm/nestjs';
|
||||
import { TripMembershipService } from './trip-membership.service';
|
||||
import { TripAccessService } from './trip-access.service';
|
||||
import { Trips } from '../../db/entities/Trips.entity';
|
||||
import { TripMembers } from '../../db/entities/TripMembers.entity';
|
||||
|
||||
@@ -13,10 +14,13 @@ import { TripMembers } from '../../db/entities/TripMembers.entity';
|
||||
* (Plan 3c Task 1 ruling): five modules import THIS module, so a new service
|
||||
* import here would close a cycle. `EntityManager`/repository resolution
|
||||
* comes entirely from the ORM's own `@Global()` core module + this
|
||||
* `forFeature` registration, keeping this module a leaf. */
|
||||
* `forFeature` registration, keeping this module a leaf.
|
||||
*
|
||||
* TripAccessService is the trip-visibility read every other domain asks
|
||||
* instead of injecting TripsRepository for it. */
|
||||
@Module({
|
||||
imports: [MikroOrmModule.forFeature([Trips, TripMembers])],
|
||||
providers: [TripMembershipService],
|
||||
exports: [TripMembershipService],
|
||||
providers: [TripMembershipService, TripAccessService],
|
||||
exports: [TripMembershipService, TripAccessService],
|
||||
})
|
||||
export class TripMembershipModule {}
|
||||
|
||||
@@ -20,6 +20,7 @@ import {
|
||||
createTestJourneyPhotosRepo, createTestJourneyEntryPhotosRepo,
|
||||
} from './journey-repos';
|
||||
import { createTestToursRepo } from './tours-repos';
|
||||
import { TripAccessService } from '../../src/nest/trip-membership/trip-access.service';
|
||||
|
||||
/**
|
||||
* AccommodationsService over a test connection.
|
||||
@@ -51,7 +52,7 @@ export async function accommodationsOver(conn: Database): Promise<Accommodations
|
||||
const realtime = new RealtimeService();
|
||||
const t = await sharedTestOrm(conn);
|
||||
const assignments = new AssignmentsService(
|
||||
await createTestTripsRepo(conn), permissions, realtime,
|
||||
new TripAccessService(await createTestTripsRepo(conn)), permissions, realtime,
|
||||
new QueryHelpersService(await createTestTagsRepo(conn), await createTestPlaceRatingsRepo(conn), await createTestAssignmentParticipantsRepo(conn)),
|
||||
new JourneyDomainService(
|
||||
realtime, new TrekPhotoRegistrationService(t.repo(TrekPhotos), t.repo(TripPhotos), await createTestJourneyPhotosRepo(conn)), await createTestUnitOfWork(conn),
|
||||
@@ -75,7 +76,7 @@ export async function accommodationsOver(conn: Database): Promise<Accommodations
|
||||
// TripsRepository.findAccessible. Plan 4 Task 3 — stampLodging's
|
||||
// getPlaceWithTags is PlacesRepository.findWithTagsAndRatings directly
|
||||
// (`placesRepo` below, already injected).
|
||||
await createTestTripsRepo(conn),
|
||||
new TripAccessService(await createTestTripsRepo(conn)),
|
||||
await createTestDayAccommodationsRepo(conn),
|
||||
await createTestDayAssignmentsRepo(conn),
|
||||
await createTestPlacesRepo(conn),
|
||||
|
||||
@@ -183,6 +183,7 @@ import { noGoogleQuota } from './google-quota';
|
||||
import { createTestToursRepo } from './tours-repos';
|
||||
import { createTestBudgetSettlementsRepo } from './budget-repos';
|
||||
import { MaintenanceRepository } from '../../src/db/repositories/MaintenanceRepository';
|
||||
import { TripAccessService } from '../../src/nest/trip-membership/trip-access.service';
|
||||
|
||||
/**
|
||||
* Hand-wired counterpart of the boot-time discovery in McpRegistryService,
|
||||
@@ -226,7 +227,7 @@ export async function createMcpTestRegistry(): Promise<McpRegistry> {
|
||||
await createTestRoadtripViasRepo(db),
|
||||
await createTestRoadtripDayBoundariesRepo(db),
|
||||
);
|
||||
const todoService = new TodoService(permissionsService, realtimeService, await createTestUnitOfWork(db), await createTestTodoItemsRepo(db), await createTestTodoCategoryAssigneesRepo(db), await createTestTripsRepo(db), await createTestTripMembersRepo(db));
|
||||
const todoService = new TodoService(permissionsService, realtimeService, await createTestUnitOfWork(db), await createTestTodoItemsRepo(db), await createTestTodoCategoryAssigneesRepo(db), new TripAccessService(await createTestTripsRepo(db)), await createTestTripMembersRepo(db));
|
||||
const packingService = new PackingService(
|
||||
permissionsService, realtimeService, notificationsStub(), await createTestUnitOfWork(db),
|
||||
await createTestPackingItemsRepo(db), await createTestPackingItemContributorsRepo(db), await createTestPackingBagsRepo(db),
|
||||
@@ -266,7 +267,7 @@ export async function createMcpTestRegistry(): Promise<McpRegistry> {
|
||||
// One instance, four consumers: AssignmentsMcp, ReservationsMcp, PlacesMcp and
|
||||
// AccommodationsService, which writes the day stop a booked night implies.
|
||||
const assignmentsService = new AssignmentsService(
|
||||
await createTestTripsRepo(db), permissionsService, realtimeService, queryHelpersService, journeyDomain, await createTestUnitOfWork(db),
|
||||
new TripAccessService(await createTestTripsRepo(db)), permissionsService, realtimeService, queryHelpersService, journeyDomain, await createTestUnitOfWork(db),
|
||||
await createTestDayAssignmentsRepo(db),
|
||||
await createTestAssignmentParticipantsRepo(db),
|
||||
await createTestDaysRepo(db),
|
||||
@@ -277,7 +278,7 @@ export async function createMcpTestRegistry(): Promise<McpRegistry> {
|
||||
);
|
||||
const accommodationsService = new AccommodationsService(
|
||||
permissionsService, realtimeService, assignmentsService, await createTestUnitOfWork(db),
|
||||
await createTestTripsRepo(db),
|
||||
new TripAccessService(await createTestTripsRepo(db)),
|
||||
await createTestDayAccommodationsRepo(db),
|
||||
await createTestDayAssignmentsRepo(db),
|
||||
await createTestPlacesRepo(db),
|
||||
@@ -338,7 +339,7 @@ export async function createMcpTestRegistry(): Promise<McpRegistry> {
|
||||
// once and shared by both consumers below (readModelService, FilesMcp)
|
||||
// rather than reconstructed twice with the same seven-repository tail.
|
||||
const filesService = new FilesService(
|
||||
await createTestTripsRepo(db), permissionsService, realtimeService, new EphemeralTokenService(), generalStorage, mcpOrm.em,
|
||||
new TripAccessService(await createTestTripsRepo(db)), permissionsService, realtimeService, new EphemeralTokenService(), generalStorage, mcpOrm.em,
|
||||
await createTestUnitOfWork(db),
|
||||
await createTestTripFilesRepo(db),
|
||||
await createTestFileLinksRepo(db),
|
||||
@@ -388,7 +389,7 @@ export async function createMcpTestRegistry(): Promise<McpRegistry> {
|
||||
new PackingMcp(packingService, addonsService, guards),
|
||||
new BudgetMcp(budgetService, exchangeRatesService, new RuntimeEnvService(), new TripMembershipService(await createTestTripsRepo(db), await createTestTripMembersRepo(db)), addonsService, guards, await createTestUnitOfWork(db), await createTestPlacesRepo(db), await createTestTripsRepo(db), await createTestTripMembersRepo(db)),
|
||||
new ReservationsMcp(reservationsService, daysService, budgetService, assignmentsService, guards),
|
||||
new DayNotesMcp(new DayNotesService(await createTestTripsRepo(db), permissionsService, realtimeService, await createTestDayNotesRepo(db), await createTestDaysRepo(db)), guards),
|
||||
new DayNotesMcp(new DayNotesService(new TripAccessService(await createTestTripsRepo(db)), permissionsService, realtimeService, await createTestDayNotesRepo(db), await createTestDaysRepo(db)), guards),
|
||||
new DaysMcp(daysService, guards, dayRemovalService),
|
||||
new RoadtripMcp(
|
||||
new RoadtripService(
|
||||
@@ -399,13 +400,13 @@ export async function createMcpTestRegistry(): Promise<McpRegistry> {
|
||||
await createTestRoadtripViasRepo(db),
|
||||
await createTestRoadtripDayTracksRepo(db),
|
||||
),
|
||||
await createTestTripsRepo(db),
|
||||
new TripAccessService(await createTestTripsRepo(db)),
|
||||
guards,
|
||||
addonsService,
|
||||
),
|
||||
new RoadtripPreferencesMcp(
|
||||
new RoadtripPreferencesService(realtimeService, await createTestUnitOfWork(db), await createTestRoadtripPreferencesRepo(db)),
|
||||
addonsService, await createTestTripsRepo(db), guards,
|
||||
addonsService, new TripAccessService(await createTestTripsRepo(db)), guards,
|
||||
),
|
||||
new FilesMcp(filesService, guards, new AllowedFileTypesService(appSettings)),
|
||||
new AccommodationsMcp(accommodationsService, placesService, guards, await createTestUnitOfWork(db)),
|
||||
@@ -434,9 +435,9 @@ export async function createMcpTestRegistry(): Promise<McpRegistry> {
|
||||
new TripPromptsMcp(tripsService, readModelService, packingService, addonsService),
|
||||
new ShareMcp(new ShareService(new SettingsService(await createTestUnitOfWork(db), appSettings, await createTestSettingsRepo(db)), permissionsService, queryHelpersService, placePhotoCache, await createTestUnitOfWork(db), ...(await shareServiceRepoArgs(db))), guards),
|
||||
new FeedsMcp(new FeedsService(await createTestTripsRepo(db), usersRepo, calendarService), await createTestTripsRepo(db), new RuntimeEnvService(), guards),
|
||||
new TripInviteMcp(new TripInviteService(await createTestTripsRepo(db), permissionsService, new TripMembershipService(await createTestTripsRepo(db), await createTestTripMembersRepo(db)), await createTestUnitOfWork(db), await createTestTripInviteTokensRepo(db)), new RuntimeEnvService(), guards, new AuditService(auditLogRepo, usersRepo)),
|
||||
new TripInviteMcp(new TripInviteService(new TripAccessService(await createTestTripsRepo(db)), permissionsService, new TripMembershipService(await createTestTripsRepo(db), await createTestTripMembersRepo(db)), await createTestUnitOfWork(db), await createTestTripInviteTokensRepo(db)), new RuntimeEnvService(), guards, new AuditService(auditLogRepo, usersRepo)),
|
||||
new MapsMcp(mapsService),
|
||||
new PlacesMcp(placesService, mapsService, await createTestTripsRepo(db), journeyDomain, assignmentsService, guards, await createTestUnitOfWork(db)),
|
||||
new PlacesMcp(placesService, mapsService, new TripAccessService(await createTestTripsRepo(db)), journeyDomain, assignmentsService, guards, await createTestUnitOfWork(db)),
|
||||
new CollectionsMcp(
|
||||
new CollectionsService(
|
||||
permissionsService, realtimeService, notificationsStub(), generalStorage, await createTestUnitOfWork(db),
|
||||
@@ -460,7 +461,7 @@ export async function createMcpTestRegistry(): Promise<McpRegistry> {
|
||||
// (`findUsernameEmail`, UM11's precedent) added in its place.
|
||||
usersRepo, addonsService,
|
||||
),
|
||||
new TransitMcp(new TransitService(new GoogleTransitProvider(appSettings, usersRepo, noGoogleQuota), transitConfig()), daysService, reservationsService, await createTestTripsRepo(db), guards),
|
||||
new TransitMcp(new TransitService(new GoogleTransitProvider(appSettings, usersRepo, noGoogleQuota), transitConfig()), daysService, reservationsService, new TripAccessService(await createTestTripsRepo(db)), guards),
|
||||
new AtlasMcp(new AtlasService(
|
||||
await createTestBucketListRepo(db), await createTestHiddenCountriesRepo(db),
|
||||
await createTestHiddenRegionsRepo(db), await createTestVisitedCountriesRepo(db),
|
||||
@@ -488,10 +489,10 @@ export async function createMcpTestRegistry(): Promise<McpRegistry> {
|
||||
await createTestReservationsRepo(db), await createTestReservationEndpointsRepo(db), await createTestDaysRepo(db),
|
||||
realtimeService, reservationsService, new AirtrailClient(), new AirtrailService(await createTestUserAirtrailRepo(db), new AuditService(auditLogRepo, usersRepo), new AirtrailClient()),
|
||||
await createTestUnitOfWork(db),
|
||||
), await createTestTripsRepo(db), guards, addonsService),
|
||||
), new TripAccessService(await createTestTripsRepo(db)), guards, addonsService),
|
||||
new SettingsMcp(new SettingsService(await createTestUnitOfWork(db), appSettings, await createTestSettingsRepo(db))),
|
||||
new HelpMcp(), new AddonsMcp(addonsService),
|
||||
new TripWarningsMcp(new PluginHooks({ providersOf: () => [], invokeHook: async () => [] } as unknown as PluginRuntimeService), await createTestTripsRepo(db)),
|
||||
new TripWarningsMcp(new PluginHooks({ providersOf: () => [], invokeHook: async () => [] } as unknown as PluginRuntimeService), new TripAccessService(await createTestTripsRepo(db))),
|
||||
new PluginSearchMcp(new PluginHooks({ providersOf: () => [], invokeHook: async () => [] } as unknown as PluginRuntimeService)),
|
||||
new PluginPoisMcp(new PluginPoisService(new PluginHooks({ providersOf: () => [], invokeHook: async () => [] } as unknown as PluginRuntimeService), await createTestPluginsRepo(db))),
|
||||
],
|
||||
|
||||
@@ -143,6 +143,7 @@ import { noGoogleQuota } from './google-quota';
|
||||
import { createTestToursRepo } from './tours-repos';
|
||||
import { createTestBudgetSettlementsRepo } from './budget-repos';
|
||||
import { MaintenanceRepository } from '../../src/db/repositories/MaintenanceRepository';
|
||||
import { TripAccessService } from '../../src/nest/trip-membership/trip-access.service';
|
||||
|
||||
/**
|
||||
* Hand-wired counterpart of the PluginsModule DI graph for no-Nest tests
|
||||
@@ -178,7 +179,7 @@ export async function createPluginRpcHostParts(
|
||||
const budget = new BudgetService(permissions, exchangeRates, realtime, await createTestUnitOfWork(db), ...(await budgetRepoArgs(db)));
|
||||
const addons = await createTestAddonsService(db);
|
||||
const queryHelpers = new QueryHelpersService(await createTestTagsRepo(db), await createTestPlaceRatingsRepo(db), await createTestAssignmentParticipantsRepo(db));
|
||||
const todos = new TodoService(permissions, realtime, await createTestUnitOfWork(db), await createTestTodoItemsRepo(db), await createTestTodoCategoryAssigneesRepo(db), await createTestTripsRepo(db), await createTestTripMembersRepo(db));
|
||||
const todos = new TodoService(permissions, realtime, await createTestUnitOfWork(db), await createTestTodoItemsRepo(db), await createTestTodoCategoryAssigneesRepo(db), new TripAccessService(await createTestTripsRepo(db)), await createTestTripMembersRepo(db));
|
||||
const packing = new PackingService(
|
||||
permissions, realtime, notificationsStub(), await createTestUnitOfWork(db),
|
||||
await createTestPackingItemsRepo(db), await createTestPackingItemContributorsRepo(db), await createTestPackingBagsRepo(db),
|
||||
@@ -188,7 +189,7 @@ export async function createPluginRpcHostParts(
|
||||
// Plan 3e Task 1 (files): FilesService now also takes uow + the repositories
|
||||
// its R2 transactions and R12 cross-object trip-scoping guard need.
|
||||
const files = new FilesService(
|
||||
await createTestTripsRepo(db), permissions, realtime, new EphemeralTokenService(), generalStorage, (await sharedTestOrm(db)).em,
|
||||
new TripAccessService(await createTestTripsRepo(db)), permissions, realtime, new EphemeralTokenService(), generalStorage, (await sharedTestOrm(db)).em,
|
||||
await createTestUnitOfWork(db),
|
||||
await createTestTripFilesRepo(db),
|
||||
await createTestFileLinksRepo(db),
|
||||
@@ -267,9 +268,9 @@ export async function createPluginRpcHostParts(
|
||||
await createTestTripsRepo(db), await createTestPlacesRepo(db),
|
||||
await createTestReservationEndpointsRepo(db), await createTestUnitOfWork(db), (await sharedTestOrm(db)).orm,
|
||||
);
|
||||
const dayNotes = new DayNotesService(await createTestTripsRepo(db), permissions, realtime, await createTestDayNotesRepo(db), await createTestDaysRepo(db));
|
||||
const dayNotes = new DayNotesService(new TripAccessService(await createTestTripsRepo(db)), permissions, realtime, await createTestDayNotesRepo(db), await createTestDaysRepo(db));
|
||||
const assignments = new AssignmentsService(
|
||||
await createTestTripsRepo(db), permissions, realtime, queryHelpers, journey, await createTestUnitOfWork(db),
|
||||
new TripAccessService(await createTestTripsRepo(db)), permissions, realtime, queryHelpers, journey, await createTestUnitOfWork(db),
|
||||
await createTestDayAssignmentsRepo(db),
|
||||
await createTestAssignmentParticipantsRepo(db),
|
||||
await createTestDaysRepo(db),
|
||||
@@ -287,7 +288,7 @@ export async function createPluginRpcHostParts(
|
||||
const oauth = new PluginOAuthService(pluginOrm.repo(Plugins), pluginOrm.repo(PluginOauthTokens), pluginOrm.repo(PluginOauthState), pluginOrm.repo(PluginSettingsFields), await createTestUnitOfWork(db));
|
||||
const accommodations = new AccommodationsService(
|
||||
permissions, realtime, assignments, await createTestUnitOfWork(db),
|
||||
await createTestTripsRepo(db),
|
||||
new TripAccessService(await createTestTripsRepo(db)),
|
||||
await createTestDayAccommodationsRepo(db),
|
||||
await createTestDayAssignmentsRepo(db),
|
||||
await createTestPlacesRepo(db),
|
||||
@@ -339,7 +340,7 @@ export async function createPluginRpcHostParts(
|
||||
new AccommodationsRpc(accommodations, realtime, guards),
|
||||
new ItineraryRpc(assignments, realtime, guards),
|
||||
new TripsRpc(trips, reservations, days, membership, realtime, guards, accommodations, members, (await sharedTestOrm(db)).em),
|
||||
new CostsRpc(budget, await createTestTripsRepo(db), realtime, guards, membership),
|
||||
new CostsRpc(budget, new TripAccessService(await createTestTripsRepo(db)), realtime, guards, membership),
|
||||
new ReservationsRpc(reservations, realtime, guards),
|
||||
new CollabRpc(collab, realtime, guards),
|
||||
new AtlasRpc(atlas, guards),
|
||||
|
||||
@@ -21,6 +21,7 @@ import type { UsersRepository } from '../../../src/db/repositories/Users.reposit
|
||||
import type { TripMembershipService } from '../../../src/nest/trip-membership/trip-membership.service';
|
||||
import type { RpcRequest, RpcError } from '../../../src/nest/plugins/protocol/envelope';
|
||||
import { makeDeps } from '../../helpers/rpc-host-deps';
|
||||
import { TripAccessService } from '../../../src/nest/trip-membership/trip-access.service';
|
||||
|
||||
const req = (method: string, params: Record<string, unknown> = {}): RpcRequest => ({ k: 'req', id: 'x', method, params });
|
||||
|
||||
@@ -55,7 +56,7 @@ function build(opts: { addonOn?: boolean; canEdit?: boolean; missing?: boolean;
|
||||
);
|
||||
// The leaf membership read replaced the deleted trips.bridge for listMine.
|
||||
const membership = { listAccessibleTripIds: vi.fn(() => [1, 2]) } as unknown as TripMembershipService;
|
||||
const rpc = new CostsRpc(budget, db, realtime, guards, membership);
|
||||
const rpc = new CostsRpc(budget, new TripAccessService(db), realtime, guards, membership);
|
||||
const host = (...grants: string[]) =>
|
||||
new PluginRpcHost('p', new Set(grants.length ? grants : ['db:read:costs', 'db:write:costs']), makeDeps(), createTestPluginRegistry([rpc]));
|
||||
return { budget, realtime, host };
|
||||
|
||||
@@ -60,6 +60,7 @@ import { Days } from '../../../src/db/entities/Days.entity';
|
||||
import { Places } from '../../../src/db/entities/Places.entity';
|
||||
import { RoadtripVias } from '../../../src/db/entities/RoadtripVias.entity';
|
||||
import { Users } from '../../../src/db/entities/Users.entity';
|
||||
import { TripAccessService } from '../../../src/nest/trip-membership/trip-access.service';
|
||||
|
||||
let svc: AssignmentsService;
|
||||
beforeAll(async () => {
|
||||
@@ -67,7 +68,7 @@ beforeAll(async () => {
|
||||
svc = new AssignmentsService(
|
||||
// Plan 4 Task 2 — AssignmentsService's own canAccessTrip delegate is now
|
||||
// TripsRepository.findAccessible, in the same constructor slot.
|
||||
await createTestTripsRepo(testDb),
|
||||
new TripAccessService(await createTestTripsRepo(testDb)),
|
||||
new PermissionsService(await createTestAppSettingsRepo(testDb), await createTestUnitOfWork(testDb)),
|
||||
realtime,
|
||||
new QueryHelpersService(await createTestTagsRepo(testDb), await createTestPlaceRatingsRepo(testDb), await createTestAssignmentParticipantsRepo(testDb)),
|
||||
|
||||
@@ -39,13 +39,14 @@ import { createTestUnitOfWork, createTestAppSettingsRepo, createTestDayNotesRepo
|
||||
import { sharedTestOrm } from '../../helpers/test-uow';
|
||||
import { findRow } from '../../helpers/factories/rows';
|
||||
import { DayNotes } from '../../../src/db/entities/DayNotes.entity';
|
||||
import { TripAccessService } from '../../../src/nest/trip-membership/trip-access.service';
|
||||
|
||||
let svc: DayNotesService;
|
||||
beforeAll(async () => {
|
||||
// Plan 4 Task 2 — DayNotesService's own canAccessTrip delegate is now
|
||||
// TripsRepository.findAccessible, in the same constructor slot.
|
||||
svc = new DayNotesService(
|
||||
await createTestTripsRepo(testDb),
|
||||
new TripAccessService(await createTestTripsRepo(testDb)),
|
||||
new PermissionsService(await createTestAppSettingsRepo(testDb), await createTestUnitOfWork(testDb)),
|
||||
new RealtimeService(),
|
||||
await createTestDayNotesRepo(testDb),
|
||||
@@ -279,7 +280,7 @@ describe('DayNotesService.canEdit', () => {
|
||||
it('DAYNOTE-SVC-090 asks for day_edit and flags a non-owner as shared', async () => {
|
||||
const checkPermission = vi.fn(() => true);
|
||||
const permissions = { checkPermission } as unknown as PermissionsService;
|
||||
const withStub = new DayNotesService(await createTestTripsRepo(testDb), permissions, new RealtimeService(), await createTestDayNotesRepo(testDb), await createTestDaysRepo(testDb));
|
||||
const withStub = new DayNotesService(new TripAccessService(await createTestTripsRepo(testDb)), permissions, new RealtimeService(), await createTestDayNotesRepo(testDb), await createTestDaysRepo(testDb));
|
||||
const trip = { id: 1, user_id: 1 } as never;
|
||||
|
||||
expect(await withStub.canEdit(trip, { id: 1, role: 'user' } as never)).toBe(true);
|
||||
|
||||
@@ -62,6 +62,7 @@ import { ReservationEndpoints } from '../../../src/db/entities/ReservationEndpoi
|
||||
import { RoadtripDayBoundaries } from '../../../src/db/entities/RoadtripDayBoundaries.entity';
|
||||
import { Trips } from '../../../src/db/entities/Trips.entity';
|
||||
import { legacyBoundIntegerText } from '../../../src/nest/common/row-id';
|
||||
import { TripAccessService } from '../../../src/nest/trip-membership/trip-access.service';
|
||||
|
||||
let days: DaysService;
|
||||
let journey: JourneyDomainService;
|
||||
@@ -96,7 +97,7 @@ beforeAll(async () => {
|
||||
await createTestJourneyPhotosRepo(testDb), await createTestJourneyEntryPhotosRepo(testDb), await createTestPlacesRepo(testDb),
|
||||
);
|
||||
assignments = new AssignmentsService(
|
||||
await createTestTripsRepo(testDb), permissions, realtime, queryHelpers, journey,
|
||||
new TripAccessService(await createTestTripsRepo(testDb)), permissions, realtime, queryHelpers, journey,
|
||||
await createTestUnitOfWork(testDb),
|
||||
await createTestDayAssignmentsRepo(testDb),
|
||||
await createTestAssignmentParticipantsRepo(testDb),
|
||||
@@ -108,7 +109,7 @@ beforeAll(async () => {
|
||||
);
|
||||
accommodations = new AccommodationsService(
|
||||
permissions, realtime, assignments, await createTestUnitOfWork(testDb),
|
||||
await createTestTripsRepo(testDb),
|
||||
new TripAccessService(await createTestTripsRepo(testDb)),
|
||||
await createTestDayAccommodationsRepo(testDb),
|
||||
await createTestDayAssignmentsRepo(testDb),
|
||||
await createTestPlacesRepo(testDb),
|
||||
|
||||
@@ -79,6 +79,7 @@ import { EphemeralTokenService } from '../../../src/nest/auth-core/ephemeral-tok
|
||||
import type { EntityManager } from '@mikro-orm/core';
|
||||
import { Users } from '../../../src/db/entities/Users.entity';
|
||||
import { UserSessions } from '../../../src/db/entities/UserSessions.entity';
|
||||
import { TripAccessService } from '../../../src/nest/trip-membership/trip-access.service';
|
||||
|
||||
const storageDelete = vi.fn();
|
||||
const storageStub = { delete: storageDelete } as unknown as import('../../../src/nest/storage/storage.service').StorageService;
|
||||
@@ -114,7 +115,7 @@ beforeAll(async () => {
|
||||
tripFilesRepo = await createTestTripFilesRepo(testDb);
|
||||
fileLinksRepo = await createTestFileLinksRepo(testDb);
|
||||
svc = new FilesService(
|
||||
await createTestTripsRepo(testDb),
|
||||
new TripAccessService(await createTestTripsRepo(testDb)),
|
||||
permissionsStub,
|
||||
new RealtimeService(),
|
||||
new EphemeralTokenService(),
|
||||
|
||||
@@ -43,6 +43,7 @@ import type { JourneyDomainService } from '../../../src/nest/journey/journey-dom
|
||||
import type { UsersRepository } from '../../../src/db/repositories/Users.repository';
|
||||
import type { AppSettingsRepository } from '../../../src/db/repositories/AppSettings.repository';
|
||||
import type { User } from '../../../src/types';
|
||||
import { TripAccessService } from '../../../src/nest/trip-membership/trip-access.service';
|
||||
|
||||
interface FakeSocket extends TrekWebSocket {
|
||||
sent: string[];
|
||||
@@ -88,7 +89,7 @@ const canAccessJourney = vi.fn((journeyId: number) => (journeyId === 4 ? null :
|
||||
const journeys = { canAccessJourney } as unknown as JourneyDomainService;
|
||||
|
||||
async function connect(url: string) {
|
||||
const gw = new RealtimeGateway(db, tokens, journeys, users, appSettings);
|
||||
const gw = new RealtimeGateway(new TripAccessService(db), tokens, journeys, users, appSettings);
|
||||
const ws = socket();
|
||||
await gw.handleConnection(ws, { url } as never);
|
||||
return { gw, ws };
|
||||
@@ -158,7 +159,7 @@ describe('RealtimeGateway handshake', () => {
|
||||
});
|
||||
|
||||
it('WSGW-007b: survives an upgrade request with no url, rather than throwing at it', async () => {
|
||||
const gw = new RealtimeGateway(db, tokens, journeys, users, appSettings);
|
||||
const gw = new RealtimeGateway(new TripAccessService(db), tokens, journeys, users, appSettings);
|
||||
const ws = socket();
|
||||
await expect(gw.handleConnection(ws, {} as never)).resolves.not.toThrow();
|
||||
expect(ws.closedWith).toEqual([4001, 'Authentication required']);
|
||||
@@ -198,7 +199,7 @@ describe('RealtimeGateway handshake', () => {
|
||||
const throwingUsers = {
|
||||
findForWsHandshake: () => { throw new Error('db exploded'); },
|
||||
} as unknown as UsersRepository;
|
||||
const gw = new RealtimeGateway(db, tokens, journeys, throwingUsers, appSettings);
|
||||
const gw = new RealtimeGateway(new TripAccessService(db), tokens, journeys, throwingUsers, appSettings);
|
||||
const ws = socket();
|
||||
|
||||
await gw.handleConnection(ws, { url: '/ws?token=x' } as never);
|
||||
@@ -211,7 +212,7 @@ describe('RealtimeGateway handshake', () => {
|
||||
const throwingUsers = {
|
||||
findForWsHandshake: () => { throw 'db string'; }, // non-Error throw, pinning the ternary's String(err) fallback
|
||||
} as unknown as UsersRepository;
|
||||
const gw = new RealtimeGateway(db, tokens, journeys, throwingUsers, appSettings);
|
||||
const gw = new RealtimeGateway(new TripAccessService(db), tokens, journeys, throwingUsers, appSettings);
|
||||
const ws = socket();
|
||||
|
||||
await gw.handleConnection(ws, { url: '/ws?token=x' } as never);
|
||||
@@ -248,7 +249,7 @@ describe('RealtimeGateway heartbeat', () => {
|
||||
alive.isAlive = true;
|
||||
const stale = socket();
|
||||
stale.isAlive = false;
|
||||
const gw = new RealtimeGateway(db, tokens, journeys, users, appSettings);
|
||||
const gw = new RealtimeGateway(new TripAccessService(db), tokens, journeys, users, appSettings);
|
||||
gw.afterInit({ clients: new Set([alive, stale]) } as never);
|
||||
|
||||
vi.advanceTimersByTime(30_000);
|
||||
@@ -269,7 +270,7 @@ describe('RealtimeGateway heartbeat', () => {
|
||||
try {
|
||||
const ws = socket();
|
||||
ws.isAlive = true;
|
||||
const gw = new RealtimeGateway(db, tokens, journeys, users, appSettings);
|
||||
const gw = new RealtimeGateway(new TripAccessService(db), tokens, journeys, users, appSettings);
|
||||
gw.afterInit({ clients: new Set([ws]) } as never);
|
||||
gw.onModuleDestroy();
|
||||
|
||||
@@ -378,7 +379,7 @@ describe('book rooms', () => {
|
||||
let nextJourney = 100;
|
||||
|
||||
async function joined(journeyId: number) {
|
||||
const gw = new RealtimeGateway(db, tokens, journeys, users, appSettings);
|
||||
const gw = new RealtimeGateway(new TripAccessService(db), tokens, journeys, users, appSettings);
|
||||
const ws = socket();
|
||||
registerSocket(ws, { id: 3, username: 'm' } as User);
|
||||
const reply = await gw.handleBookJoin({ journeyId }, ws);
|
||||
@@ -394,7 +395,7 @@ describe('book rooms', () => {
|
||||
|
||||
/* Same shape as the trip room's refusal, and for the same reason. */
|
||||
it('WSGW-BOOK-002: refuses a journey the user cannot see, and adds nobody', async () => {
|
||||
const gw = new RealtimeGateway(db, tokens, journeys, users, appSettings);
|
||||
const gw = new RealtimeGateway(new TripAccessService(db), tokens, journeys, users, appSettings);
|
||||
const ws = socket();
|
||||
registerSocket(ws, { id: 3, username: 'm' } as User);
|
||||
|
||||
@@ -445,7 +446,7 @@ describe('book pointers', () => {
|
||||
|
||||
async function pair() {
|
||||
const journeyId = nextJourney++;
|
||||
const gw = new RealtimeGateway(db, tokens, journeys, users, appSettings);
|
||||
const gw = new RealtimeGateway(new TripAccessService(db), tokens, journeys, users, appSettings);
|
||||
const mine = socket();
|
||||
const theirs = socket();
|
||||
registerSocket(mine, { id: 3, username: 'm' } as User);
|
||||
@@ -510,7 +511,7 @@ describe('book messages that are refused', () => {
|
||||
let nextJourney = 300;
|
||||
|
||||
it('WSGW-BOOK-006: a join without a journey id is ignored', async () => {
|
||||
const gw = new RealtimeGateway(db, tokens, journeys, users, appSettings);
|
||||
const gw = new RealtimeGateway(new TripAccessService(db), tokens, journeys, users, appSettings);
|
||||
const ws = socket();
|
||||
registerSocket(ws, { id: 3, username: 'm' } as User);
|
||||
|
||||
@@ -520,7 +521,7 @@ describe('book messages that are refused', () => {
|
||||
|
||||
/* An unauthenticated socket never got as far as the registry. */
|
||||
it('WSGW-BOOK-007: a join from a socket with no user is ignored', async () => {
|
||||
const gw = new RealtimeGateway(db, tokens, journeys, users, appSettings);
|
||||
const gw = new RealtimeGateway(new TripAccessService(db), tokens, journeys, users, appSettings);
|
||||
const j = nextJourney++;
|
||||
|
||||
expect(await gw.handleBookJoin({ journeyId: j }, socket())).toBeUndefined();
|
||||
@@ -528,7 +529,7 @@ describe('book messages that are refused', () => {
|
||||
});
|
||||
|
||||
it('WSGW-BOOK-008: a journey id that is not a number is refused, not joined', async () => {
|
||||
const gw = new RealtimeGateway(db, tokens, journeys, users, appSettings);
|
||||
const gw = new RealtimeGateway(new TripAccessService(db), tokens, journeys, users, appSettings);
|
||||
const ws = socket();
|
||||
registerSocket(ws, { id: 3, username: 'm' } as User);
|
||||
|
||||
@@ -537,7 +538,7 @@ describe('book messages that are refused', () => {
|
||||
});
|
||||
|
||||
it('WSGW-BOOK-009: a leave without a journey id is ignored', () => {
|
||||
const gw = new RealtimeGateway(db, tokens, journeys, users, appSettings);
|
||||
const gw = new RealtimeGateway(new TripAccessService(db), tokens, journeys, users, appSettings);
|
||||
const ws = socket();
|
||||
registerSocket(ws, { id: 3, username: 'm' } as User);
|
||||
|
||||
@@ -546,7 +547,7 @@ describe('book messages that are refused', () => {
|
||||
|
||||
/* Leaving a room nobody is in must not create one on the way out. */
|
||||
it('WSGW-BOOK-010: leaving a book that was never joined does nothing', () => {
|
||||
const gw = new RealtimeGateway(db, tokens, journeys, users, appSettings);
|
||||
const gw = new RealtimeGateway(new TripAccessService(db), tokens, journeys, users, appSettings);
|
||||
const ws = socket();
|
||||
registerSocket(ws, { id: 3, username: 'm' } as User);
|
||||
const j = nextJourney++;
|
||||
@@ -556,7 +557,7 @@ describe('book messages that are refused', () => {
|
||||
});
|
||||
|
||||
it('WSGW-CUR-005: a pointer without a journey id reaches nobody', async () => {
|
||||
const gw = new RealtimeGateway(db, tokens, journeys, users, appSettings);
|
||||
const gw = new RealtimeGateway(new TripAccessService(db), tokens, journeys, users, appSettings);
|
||||
const mine = socket();
|
||||
const theirs = socket();
|
||||
registerSocket(mine, { id: 3, username: 'm' } as User);
|
||||
@@ -571,7 +572,7 @@ describe('book messages that are refused', () => {
|
||||
});
|
||||
|
||||
it('WSGW-CUR-006: a pointer from a socket with no user reaches nobody', async () => {
|
||||
const gw = new RealtimeGateway(db, tokens, journeys, users, appSettings);
|
||||
const gw = new RealtimeGateway(new TripAccessService(db), tokens, journeys, users, appSettings);
|
||||
const theirs = socket();
|
||||
registerSocket(theirs, { id: 4, username: 'o' } as User);
|
||||
const j = nextJourney++;
|
||||
|
||||
@@ -7,6 +7,7 @@ import type { TripsRepository } from '../../../src/db/repositories/Trips.reposit
|
||||
import type { PermissionsService } from '../../../src/nest/permissions/permissions.service';
|
||||
import type { AddonsService } from '../../../src/nest/addons/addons.service';
|
||||
import type { User } from '../../../src/types';
|
||||
import { TripAccessService } from '../../../src/nest/trip-membership/trip-access.service';
|
||||
|
||||
const user = { id: 3, role: 'user' } as User;
|
||||
const photo = (name = 'r.jpg') => ({ originalname: name, buffer: Buffer.from('x') } as Express.Multer.File);
|
||||
@@ -17,7 +18,7 @@ function make(over: { readsImages?: boolean; trip?: unknown; addon?: boolean; al
|
||||
const c = new ReceiptScanController(
|
||||
{ startReceipt } as unknown as ImportJobsService,
|
||||
{ readsImages: vi.fn(async () => over.readsImages ?? true) } as unknown as LlmParseService,
|
||||
{ findAccessible: vi.fn(async () => ('trip' in over ? over.trip : { user_id: 5 })) } as unknown as TripsRepository,
|
||||
new TripAccessService({ findAccessible: vi.fn(async () => ('trip' in over ? over.trip : { user_id: 5 })) } as unknown as TripsRepository),
|
||||
{ checkPermission } as unknown as PermissionsService,
|
||||
{ isAddonEnabled: vi.fn(async () => over.addon ?? true) } as unknown as AddonsService,
|
||||
);
|
||||
|
||||
@@ -31,6 +31,7 @@ import { bookendAssignmentId, type RoadtripStop } from '@trek/shared/roadtrip';
|
||||
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
||||
import { legacyBoundIntegerText } from '../../../src/nest/common/row-id';
|
||||
import { TripAccessService } from '../../../src/nest/trip-membership/trip-access.service';
|
||||
|
||||
/** Every leg an hour and 60 km, whatever it joins. */
|
||||
function hourlyRouter() {
|
||||
@@ -61,7 +62,7 @@ function hourlyRouter() {
|
||||
/** The repositories the service reads through, all on the worker's one test handle. */
|
||||
async function planRepos() {
|
||||
return [
|
||||
await createTestTripsRepo(db),
|
||||
new TripAccessService(await createTestTripsRepo(db)),
|
||||
await createTestDaysRepo(db),
|
||||
await createTestDayAssignmentsRepo(db),
|
||||
await createTestDayAccommodationsRepo(db),
|
||||
|
||||
@@ -37,6 +37,7 @@ import { TodoItems } from '../../../src/db/entities/TodoItems.entity';
|
||||
import { TodoCategoryAssignees } from '../../../src/db/entities/TodoCategoryAssignees.entity';
|
||||
import { Users } from '../../../src/db/entities/Users.entity';
|
||||
import { createTestTodoItemsRepo, createTestTodoCategoryAssigneesRepo } from '../../helpers/todo-repos';
|
||||
import { TripAccessService } from '../../../src/nest/trip-membership/trip-access.service';
|
||||
|
||||
let svc: TodoService;
|
||||
let todoItemsRepoDirect: Awaited<ReturnType<typeof createTestTodoItemsRepo>>;
|
||||
@@ -48,7 +49,7 @@ beforeAll(async () => {
|
||||
todoItemsRepoDirect, await createTestTodoCategoryAssigneesRepo(testDb),
|
||||
// Plan 4 Task 2 — TodoService's own canAccessTrip delegate is now
|
||||
// TripsRepository.findAccessible, a new trailing constructor param.
|
||||
await createTestTripsRepo(testDb),
|
||||
new TripAccessService(await createTestTripsRepo(testDb)),
|
||||
// Plan 4 Task 3 — DatabaseService.rosterUserIds inlined onto
|
||||
// TripMembersRepository.rosterUserIds directly.
|
||||
await createTestTripMembersRepo(testDb),
|
||||
@@ -404,7 +405,7 @@ describe('TodoService.canEdit', () => {
|
||||
const withStub = new TodoService(
|
||||
permissions, new RealtimeService(), await createTestUnitOfWork(testDb),
|
||||
await createTestTodoItemsRepo(testDb), await createTestTodoCategoryAssigneesRepo(testDb),
|
||||
await createTestTripsRepo(testDb), await createTestTripMembersRepo(testDb),
|
||||
new TripAccessService(await createTestTripsRepo(testDb)), await createTestTripMembersRepo(testDb),
|
||||
);
|
||||
const trip = { id: 1, user_id: 1 } as never;
|
||||
|
||||
|
||||
@@ -36,6 +36,7 @@ import { createTestUnitOfWork, createTestAppSettingsRepo, createTestTripsRepo, c
|
||||
import { sharedTestOrm } from '../../helpers/test-uow';
|
||||
import { countRows, updateRows } from '../../helpers/factories/rows';
|
||||
import { TripInviteTokens } from '../../../src/db/entities/TripInviteTokens.entity';
|
||||
import { TripAccessService } from '../../../src/nest/trip-membership/trip-access.service';
|
||||
|
||||
let svc: TripInviteService;
|
||||
beforeAll(async () => {
|
||||
@@ -43,7 +44,7 @@ beforeAll(async () => {
|
||||
svc = new TripInviteService(
|
||||
// Plan 4 Task 2 — TripInviteService's own canAccessTrip delegate is now
|
||||
// TripsRepository.findAccessible, in the same constructor slot.
|
||||
await createTestTripsRepo(testDb),
|
||||
new TripAccessService(await createTestTripsRepo(testDb)),
|
||||
new PermissionsService(await createTestAppSettingsRepo(testDb), uow),
|
||||
new TripMembershipService(await createTestTripsRepo(testDb), await createTestTripMembersRepo(testDb)),
|
||||
uow,
|
||||
|
||||
@@ -108,6 +108,7 @@ import { createTestCollectionPlacesRepo } from '../../helpers/test-uow';
|
||||
import { noGoogleQuota } from '../../helpers/google-quota';
|
||||
import { createTestBudgetSettlementsRepo } from '../../helpers/budget-repos';
|
||||
import { MaintenanceRepository } from '../../../src/db/repositories/MaintenanceRepository';
|
||||
import { TripAccessService } from '../../../src/nest/trip-membership/trip-access.service';
|
||||
|
||||
// Real sibling services over the same in-memory DB — the aggregation runs the
|
||||
// actual SQL of every domain it fans out to, so a shape change downstream shows
|
||||
@@ -200,7 +201,7 @@ const buildReadModel = async (tripsRepo: TripsRepository, roster: TripMembersSer
|
||||
await createTestTodoItemsRepo(testDb), await createTestTodoCategoryAssigneesRepo(testDb),
|
||||
// Plan 4 Task 2 — TodoService's own canAccessTrip delegate is now
|
||||
// TripsRepository.findAccessible, a new trailing constructor param.
|
||||
await createTestTripsRepo(testDb),
|
||||
new TripAccessService(await createTestTripsRepo(testDb)),
|
||||
// Plan 4 Task 3 — DatabaseService.rosterUserIds inlined onto
|
||||
// TripMembersRepository.rosterUserIds directly.
|
||||
await createTestTripMembersRepo(testDb),
|
||||
@@ -208,7 +209,7 @@ const buildReadModel = async (tripsRepo: TripsRepository, roster: TripMembersSer
|
||||
new FilesService(
|
||||
// Plan 4 Task 2 — FilesService's own canAccessTrip delegate is now
|
||||
// TripsRepository.findAccessible, in the same constructor slot.
|
||||
await createTestTripsRepo(testDb),
|
||||
new TripAccessService(await createTestTripsRepo(testDb)),
|
||||
new PermissionsService(await createTestAppSettingsRepo(testDb), await createTestUnitOfWork(testDb)),
|
||||
new RealtimeService(),
|
||||
new EphemeralTokenService(),
|
||||
|
||||
@@ -153,6 +153,7 @@ import { TripMembers } from '../../../src/db/entities/TripMembers.entity';
|
||||
import { Trips } from '../../../src/db/entities/Trips.entity';
|
||||
import { Users } from '../../../src/db/entities/Users.entity';
|
||||
import { legacyBoundIntegerText } from '../../../src/nest/common/row-id';
|
||||
import { TripAccessService } from '../../../src/nest/trip-membership/trip-access.service';
|
||||
|
||||
const orm = () => sharedTestOrm(testDb);
|
||||
|
||||
@@ -322,13 +323,13 @@ beforeAll(async () => {
|
||||
new TodoService(
|
||||
new PermissionsService(await createTestAppSettingsRepo(testDb), await createTestUnitOfWork(testDb)), new RealtimeService(), await createTestUnitOfWork(testDb),
|
||||
await createTestTodoItemsRepo(testDb), await createTestTodoCategoryAssigneesRepo(testDb),
|
||||
await createTestTripsRepo(testDb),
|
||||
new TripAccessService(await createTestTripsRepo(testDb)),
|
||||
await createTestTripMembersRepo(testDb),
|
||||
),
|
||||
new FilesService(
|
||||
// Plan 4 Task 2 — FilesService's own canAccessTrip delegate is now
|
||||
// TripsRepository.findAccessible, in the same constructor slot.
|
||||
await createTestTripsRepo(testDb),
|
||||
new TripAccessService(await createTestTripsRepo(testDb)),
|
||||
new PermissionsService(await createTestAppSettingsRepo(testDb), await createTestUnitOfWork(testDb)),
|
||||
new RealtimeService(),
|
||||
new EphemeralTokenService(),
|
||||
|
||||
@@ -26,6 +26,7 @@ import type { AddonsService } from '../../../src/nest/addons/addons.service';
|
||||
import type { UsersRepository } from '../../../src/db/repositories/Users.repository';
|
||||
import type { RpcRequest, RpcError } from '../../../src/nest/plugins/protocol/envelope';
|
||||
import { makeDeps } from '../../helpers/rpc-host-deps';
|
||||
import { TripAccessService } from '../../../src/nest/trip-membership/trip-access.service';
|
||||
|
||||
const req = (method: string, params: Record<string, unknown> = {}): RpcRequest => ({ k: 'req', id: 'x', method, params });
|
||||
|
||||
@@ -81,7 +82,7 @@ function build(addonOn: boolean) {
|
||||
// SV8 — Plan 3i: DemoService.isDemoUserId, the shared demo-gate primitive.
|
||||
{ isDemoUserId: async () => false } as never),
|
||||
new CollectionsRpc(spyService(calls, 'collections'), guards),
|
||||
new CostsRpc(spyService(calls, 'budget'), db, { broadcast: vi.fn() } as never, guards, spyService(calls, 'membership')),
|
||||
new CostsRpc(spyService(calls, 'budget'), new TripAccessService(db), { broadcast: vi.fn() } as never, guards, spyService(calls, 'membership')),
|
||||
]);
|
||||
const granted = new Set([
|
||||
'db:read:collab', 'db:write:collab', 'db:read:journal', 'db:write:journal',
|
||||
|
||||
@@ -22,6 +22,7 @@ vi.mock('../../../src/nest/plugins/kill-switch', () => ({ pluginsEnabled }));
|
||||
import { DayScheduleController } from '../../../src/nest/plugins/contributions/day-schedule.controller';
|
||||
import type { PluginHooks } from '../../../src/nest/plugins/plugin-hooks.service';
|
||||
import type { DaysRepository } from '../../../src/db/repositories/Days.repository';
|
||||
import { TripAccessService } from '../../../src/nest/trip-membership/trip-access.service';
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
const req = (id?: number) => ({ user: id === undefined ? undefined : { id } }) as any;
|
||||
@@ -32,7 +33,7 @@ function controller(invoke: (id: string) => unknown, providers = ['p1']) {
|
||||
} as unknown as PluginHooks;
|
||||
// CT1 (Plan 3j Task 5) — the day-id-set read is now DaysRepository.listIdsByTrip.
|
||||
const days = { listIdsByTrip: vi.fn(async () => tripDays.value.map((d) => d.id)) } as unknown as DaysRepository;
|
||||
return { c: new DayScheduleController(runtime, { findAccessible: canAccessTrip } as unknown as TripsRepository, days), runtime };
|
||||
return { c: new DayScheduleController(runtime, new TripAccessService({ findAccessible: canAccessTrip } as unknown as TripsRepository), days), runtime };
|
||||
}
|
||||
const item = (over: Record<string, unknown> = {}) => ({ id: 's1', dayId: 10, label: 'Charging', ...over });
|
||||
|
||||
|
||||
@@ -22,6 +22,7 @@ vi.mock('../../../src/nest/plugins/kill-switch', () => ({ pluginsEnabled }));
|
||||
import { DayTintsController } from '../../../src/nest/plugins/contributions/day-tints.controller';
|
||||
import type { PluginHooks } from '../../../src/nest/plugins/plugin-hooks.service';
|
||||
import type { DaysRepository } from '../../../src/db/repositories/Days.repository';
|
||||
import { TripAccessService } from '../../../src/nest/trip-membership/trip-access.service';
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
const req = (id?: number) => ({ user: id === undefined ? undefined : { id } }) as any;
|
||||
@@ -32,7 +33,7 @@ function controller(invoke: (id: string) => unknown, providers = ['p1']) {
|
||||
} as unknown as PluginHooks;
|
||||
// CT2 (Plan 3j Task 5) — the day-id-set read is now DaysRepository.listIdsByTrip.
|
||||
const days = { listIdsByTrip: vi.fn(async () => tripDays.value.map((d) => d.id)) } as unknown as DaysRepository;
|
||||
return { c: new DayTintsController(runtime, { findAccessible: canAccessTrip } as unknown as TripsRepository, days), runtime };
|
||||
return { c: new DayTintsController(runtime, new TripAccessService({ findAccessible: canAccessTrip } as unknown as TripsRepository), days), runtime };
|
||||
}
|
||||
const tint = (over: Record<string, unknown> = {}) => ({ dayId: 10, tone: 'success', ...over });
|
||||
|
||||
|
||||
@@ -17,6 +17,7 @@ vi.mock('../../../src/nest/plugins/kill-switch', () => ({ pluginsEnabled }));
|
||||
import { MapLayersController } from '../../../src/nest/plugins/contributions/map-layers.controller';
|
||||
import type { PluginHooks } from '../../../src/nest/plugins/plugin-hooks.service';
|
||||
import type { TripsRepository } from '../../../src/db/repositories/Trips.repository';
|
||||
import { TripAccessService } from '../../../src/nest/trip-membership/trip-access.service';
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
const req = (id?: number) => ({ user: id === undefined ? undefined : { id } }) as any;
|
||||
@@ -25,7 +26,7 @@ function controller(invoke: (id: string) => unknown, providers = ['p1']) {
|
||||
providersOf: vi.fn(() => providers),
|
||||
mapLayers: vi.fn(async (id: string) => invoke(id)),
|
||||
} as unknown as PluginHooks;
|
||||
return { c: new MapLayersController(runtime, { findAccessible: canAccessTrip } as unknown as TripsRepository), runtime };
|
||||
return { c: new MapLayersController(runtime, new TripAccessService({ findAccessible: canAccessTrip } as unknown as TripsRepository)), runtime };
|
||||
}
|
||||
const line = (n = 3, over: Record<string, unknown> = {}) => ({
|
||||
type: 'polyline',
|
||||
|
||||
@@ -16,6 +16,7 @@ vi.mock('../../../src/nest/plugins/kill-switch', () => ({ pluginsEnabled }));
|
||||
import { MapMarkersController } from '../../../src/nest/plugins/contributions/map-markers.controller';
|
||||
import type { PluginHooks } from '../../../src/nest/plugins/plugin-hooks.service';
|
||||
import type { TripsRepository } from '../../../src/db/repositories/Trips.repository';
|
||||
import { TripAccessService } from '../../../src/nest/trip-membership/trip-access.service';
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
const req = (id?: number) => ({ user: id === undefined ? undefined : { id } }) as any;
|
||||
@@ -24,7 +25,7 @@ function controller(invoke: (id: string) => unknown, providers = ['p1']) {
|
||||
providersOf: vi.fn(() => providers),
|
||||
mapMarkers: vi.fn(async (id: string) => invoke(id)),
|
||||
} as unknown as PluginHooks;
|
||||
return { c: new MapMarkersController(runtime, { findAccessible: canAccessTrip } as unknown as TripsRepository), runtime };
|
||||
return { c: new MapMarkersController(runtime, new TripAccessService({ findAccessible: canAccessTrip } as unknown as TripsRepository)), runtime };
|
||||
}
|
||||
const mk = (over: Record<string, unknown> = {}) => ({ id: 'm1', lat: 48.85, lng: 2.35, ...over });
|
||||
|
||||
|
||||
@@ -17,6 +17,7 @@ vi.mock('../../../src/nest/plugins/kill-switch', () => ({ pluginsEnabled }));
|
||||
import { PdfSectionsController } from '../../../src/nest/plugins/contributions/pdf-sections.controller';
|
||||
import type { PluginHooks } from '../../../src/nest/plugins/plugin-hooks.service';
|
||||
import type { TripsRepository } from '../../../src/db/repositories/Trips.repository';
|
||||
import { TripAccessService } from '../../../src/nest/trip-membership/trip-access.service';
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
const req = (id?: number) => ({ user: id === undefined ? undefined : { id } }) as any;
|
||||
@@ -25,7 +26,7 @@ function controller(invoke: (id: string) => unknown, providers = ['p1']) {
|
||||
providersOf: vi.fn(() => providers),
|
||||
pdfSections: vi.fn(async (id: string) => invoke(id)),
|
||||
} as unknown as PluginHooks;
|
||||
return { c: new PdfSectionsController(runtime, { findAccessible: canAccessTrip } as unknown as TripsRepository), runtime };
|
||||
return { c: new PdfSectionsController(runtime, new TripAccessService({ findAccessible: canAccessTrip } as unknown as TripsRepository)), runtime };
|
||||
}
|
||||
const sec = (over: Record<string, unknown> = {}) => ({ title: 'Weather', ...over });
|
||||
|
||||
|
||||
@@ -15,6 +15,7 @@ vi.mock('../../../src/nest/plugins/kill-switch', () => ({ pluginsEnabled }));
|
||||
import { PlaceDetailsController } from '../../../src/nest/plugins/contributions/place-details.controller';
|
||||
import type { PluginHooks } from '../../../src/nest/plugins/plugin-hooks.service';
|
||||
import type { PlacesRepository } from '../../../src/db/repositories/Places.repository';
|
||||
import { TripAccessService } from '../../../src/nest/trip-membership/trip-access.service';
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
const req = (id?: number) => ({ user: id === undefined ? undefined : { id } }) as any;
|
||||
@@ -26,7 +27,7 @@ function controller(over: Partial<PluginHooks> = {}) {
|
||||
} as unknown as PluginHooks;
|
||||
// CT7 (Plan 3j Task 5) — the place's owning trip id is now Places.repository.ts#findTripId.
|
||||
const places = { findTripId: vi.fn(async (placeId: number) => placeTrip(placeId)?.trip_id) } as unknown as PlacesRepository;
|
||||
return { c: new PlaceDetailsController(runtime, { findAccessible: canAccessTrip } as unknown as TripsRepository, places), runtime };
|
||||
return { c: new PlaceDetailsController(runtime, new TripAccessService({ findAccessible: canAccessTrip } as unknown as TripsRepository), places), runtime };
|
||||
}
|
||||
|
||||
describe('PlaceDetailsController', () => {
|
||||
|
||||
@@ -22,6 +22,7 @@ vi.mock('../../../src/nest/plugins/kill-switch', () => ({ pluginsEnabled }));
|
||||
import { PluginRoutesController } from '../../../src/nest/plugins/contributions/plugin-routes.controller';
|
||||
import type { PluginHooks } from '../../../src/nest/plugins/plugin-hooks.service';
|
||||
import type { PluginsRepository } from '../../../src/db/repositories/Plugins.repository';
|
||||
import { TripAccessService } from '../../../src/nest/trip-membership/trip-access.service';
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
const req = (id?: number) => ({ user: id === undefined ? undefined : { id } }) as any;
|
||||
@@ -36,7 +37,7 @@ function controller(invoke: () => unknown, providers = ['ev-plug']) {
|
||||
const plugins = {
|
||||
findCapabilities: vi.fn(async () => (capabilitiesRow.value === undefined ? null : capabilitiesRow.value)),
|
||||
} as unknown as PluginsRepository;
|
||||
return { c: new PluginRoutesController(runtime, { findAccessible: canAccessTrip } as unknown as TripsRepository, plugins), runtime };
|
||||
return { c: new PluginRoutesController(runtime, new TripAccessService({ findAccessible: canAccessTrip } as unknown as TripsRepository), plugins), runtime };
|
||||
}
|
||||
const wp = (n = 3) => Array.from({ length: n }, (_, i) => ({ lat: 48 + i * 0.1, lng: 2 + i * 0.1 }));
|
||||
const goodRoute = (n = 3, over: Record<string, unknown> = {}) => ({
|
||||
|
||||
@@ -18,6 +18,7 @@ vi.mock('../../../src/nest/plugins/kill-switch', () => ({ pluginsEnabled }));
|
||||
import { TripCardContributionsController } from '../../../src/nest/plugins/contributions/trip-card-contributions.controller';
|
||||
import type { PluginHooks } from '../../../src/nest/plugins/plugin-hooks.service';
|
||||
import type { TripsRepository } from '../../../src/db/repositories/Trips.repository';
|
||||
import { TripAccessService } from '../../../src/nest/trip-membership/trip-access.service';
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
const req = (id?: number) => ({ user: id === undefined ? undefined : { id } }) as any;
|
||||
@@ -26,7 +27,7 @@ function controller(invoke: (id: string, args: unknown[]) => unknown, providers
|
||||
providersOf: vi.fn(() => providers),
|
||||
tripCards: vi.fn(async (id: string, _h: string, _fn: string, args: unknown[]) => invoke(id, args)),
|
||||
} as unknown as PluginHooks;
|
||||
return { c: new TripCardContributionsController(runtime, { findAccessible: canAccessTrip } as unknown as TripsRepository), runtime };
|
||||
return { c: new TripCardContributionsController(runtime, new TripAccessService({ findAccessible: canAccessTrip } as unknown as TripsRepository)), runtime };
|
||||
}
|
||||
const badge = (over: Record<string, unknown> = {}) => ({ tripId: 1, id: 'b1', label: 'Visa', ...over });
|
||||
|
||||
|
||||
@@ -10,6 +10,7 @@ vi.mock('../../../src/nest/plugins/kill-switch', () => ({ pluginsEnabled }));
|
||||
import { TripWarningsController } from '../../../src/nest/plugins/contributions/trip-warnings.controller';
|
||||
import type { PluginHooks } from '../../../src/nest/plugins/plugin-hooks.service';
|
||||
import type { TripsRepository } from '../../../src/db/repositories/Trips.repository';
|
||||
import { TripAccessService } from '../../../src/nest/trip-membership/trip-access.service';
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
const req = (id?: number) => ({ user: id === undefined ? undefined : { id } }) as any;
|
||||
@@ -20,7 +21,7 @@ function controller(over: Partial<PluginHooks> = {}) {
|
||||
id === 'p2' ? [{ level: 'error', message: 'Day 3 is overpacked', dayId: 3 }] : [{ level: 'warning', message: 'Museum closed Mon', placeId: 7 }]),
|
||||
...over,
|
||||
} as unknown as PluginHooks;
|
||||
return { c: new TripWarningsController(runtime, { findAccessible: canAccessTrip } as unknown as TripsRepository), runtime };
|
||||
return { c: new TripWarningsController(runtime, new TripAccessService({ findAccessible: canAccessTrip } as unknown as TripsRepository)), runtime };
|
||||
}
|
||||
|
||||
describe('TripWarningsController', () => {
|
||||
|
||||
@@ -17,6 +17,7 @@ vi.mock('../../../src/nest/plugins/kill-switch', () => ({ pluginsEnabled }));
|
||||
import { ViewContributionsController } from '../../../src/nest/plugins/contributions/view-contributions.controller';
|
||||
import type { PluginHooks } from '../../../src/nest/plugins/plugin-hooks.service';
|
||||
import type { TripsRepository } from '../../../src/db/repositories/Trips.repository';
|
||||
import { TripAccessService } from '../../../src/nest/trip-membership/trip-access.service';
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
const req = (id?: number) => ({ user: id === undefined ? undefined : { id } }) as any;
|
||||
@@ -25,7 +26,7 @@ function controller(invoke: (id: string) => unknown, providers = ['p1']) {
|
||||
providersOf: vi.fn(() => providers),
|
||||
tableContributions: vi.fn(async (id: string) => invoke(id)),
|
||||
} as unknown as PluginHooks;
|
||||
return { c: new ViewContributionsController(runtime, { findAccessible: canAccessTrip } as unknown as TripsRepository), runtime };
|
||||
return { c: new ViewContributionsController(runtime, new TripAccessService({ findAccessible: canAccessTrip } as unknown as TripsRepository)), runtime };
|
||||
}
|
||||
const col = (over: Record<string, unknown> = {}) => ({ kind: 'column', entityId: 1, id: 'c1', label: 'X', ...over });
|
||||
const act = (over: Record<string, unknown> = {}) => ({ kind: 'action', entityId: 1, id: 'a1', label: 'Go', target: { kind: 'frame', sub: '/ui' }, ...over });
|
||||
|
||||
Reference in New Issue
Block a user