refactor(vacay): the invite and share tools read the caller's name from UsersRepository

VacayMcp injected AuthService only to call getCurrentUser for the caller's
username and email before sending an invite or sharing the calendar, which
kept VacayModule importing AuthModule. UsersRepository.findUsernameEmail
answers the same two fields (and undefined for an unknown id, which the
tools already refuse as 'User not found.').
This commit is contained in:
Maurice
2026-10-10 12:46:49 +02:00
parent 806f3c8e62
commit 734cbc2a94
4 changed files with 12 additions and 45 deletions
@@ -16,16 +16,13 @@
"places -> accommodations",
"places -> assignments",
"plugins -> trips",
"plugins -> vacay",
"reservations -> integrations",
"trip-members -> auth",
"trip-members -> trips",
"trip-read-model -> trip-members",
"trip-read-model -> trips",
"trips -> trip-members",
"trips -> trip-read-model",
"trips -> vacay",
"vacay -> auth"
"trips -> trip-read-model"
],
"sharedImportsDomain": [
"nest/common/idempotency-cleanup.job.ts -> nest/audit/audit-log.logger.ts",
+7 -4
View File
@@ -12,7 +12,9 @@ import {
vacayUpdatePlanRequestSchema,
} from '@trek/shared';
import type { VacayUpdatePlanRequest } from '@trek/shared';
import { AuthService } from '../auth/auth.service';
import { InjectRepository } from '@mikro-orm/nestjs';
import { Users } from '../../db/entities/Users.entity';
import type { UsersRepository } from '../../db/repositories/Users.repository';
import { ADDON_IDS } from '../../addons';
import { VacayService } from './vacay.service';
import { addonGate } from '../addons/addon-gate';
@@ -79,7 +81,8 @@ function calendarRegionCodes(country: string, data: unknown): { region: string;
export class VacayMcp {
constructor(
private readonly vacay: VacayService,
private readonly auth: AuthService,
// The invite and share tools need the caller's username and email.
@InjectRepository(Users) private readonly users: UsersRepository,
readonly addons: AddonsService,
) {}
@@ -180,7 +183,7 @@ export class VacayMcp {
})
async sendVacayInvite({ targetUserId }: { targetUserId: number }, ctx: McpContext) {
const planId = await this.vacay.getActivePlanId(ctx.userId);
const me = await this.auth.getCurrentUser(ctx.userId);
const me = await this.users.findUsernameEmail(ctx.userId);
if (!me) return errorResult('User not found.');
const result = await this.vacay.sendInvite(planId, ctx.userId, me.username, me.email, targetUserId);
if (result.error) return errorResult(result.error);
@@ -559,7 +562,7 @@ export class VacayMcp {
access: { group: 'vacay', mode: 'write' },
})
async shareVacayCalendar({ targetUserId }: { targetUserId: number }, ctx: McpContext) {
const me = await this.auth.getCurrentUser(ctx.userId);
const me = await this.users.findUsernameEmail(ctx.userId);
if (!me) return errorResult('User not found.');
const result = await this.vacay.shareCalendar(ctx.userId, me.email, targetUserId);
if (result.error) return errorResult(result.error);
+3 -2
View File
@@ -5,7 +5,7 @@ import { VacayService } from './vacay.service';
import { VacayRpc } from './vacay.rpc';
import { PluginGuardsModule } from '../../nest-rpc/plugin-guards.module';
import { VacayMcp } from './vacay.mcp';
import { AuthModule } from '../auth/auth.module';
import { Users } from '../../db/entities/Users.entity';
import { AddonsModule } from '../addons/addons.module';
import { NotificationsModule } from '../notifications/notifications.module';
import { VacayPlans } from '../../db/entities/VacayPlans.entity';
@@ -37,13 +37,14 @@ import { SchoolHolidayRegions } from '../../db/entities/SchoolHolidayRegions.ent
@Module({
imports: [
NotificationsModule,
AuthModule,
PluginGuardsModule,
AddonsModule,
MikroOrmModule.forFeature([
VacayPlans, VacayPlanMembers, VacayYears, VacayUserYears, VacayUserColors,
VacayEntries, VacayCompanyHolidays, VacayHolidayCalendars, VacayShares, VacayUserSettings,
SchoolHolidayRegions,
// VacayMcp's invite and share tools read the caller's username/email.
Users,
]),
],
controllers: [VacayController],
+1 -35
View File
@@ -7,7 +7,6 @@ import { AssignmentsMcp } from '../../src/nest/assignments/assignments.mcp';
import { AssignmentsService } from '../../src/nest/assignments/assignments.service';
import { AtlasMcp } from '../../src/nest/atlas/atlas.mcp';
import { AtlasService } from '../../src/nest/atlas/atlas.service';
import { AuthService } from '../../src/nest/auth/auth.service';
import { BudgetMcp } from '../../src/nest/budget/budget.mcp';
import { BudgetService } from '../../src/nest/budget/budget.service';
import { ExchangeRatesService } from '../../src/nest/budget/exchange-rates.service';
@@ -73,9 +72,7 @@ import { TrekPhotoCacheMeta } from '../../src/db/entities/TrekPhotoCacheMeta.ent
import { PhotoProviders } from '../../src/db/entities/PhotoProviders.entity';
import { UnsplashService } from '../../src/nest/unsplash/unsplash.service';
import { UserCleanupService } from '../../src/nest/auth/user-cleanup.service';
import { WebauthnConfigService } from '../../src/nest/auth/webauthn-config.service';
import { TripMembershipService } from '../../src/nest/trip-membership/trip-membership.service';
import { MailerService } from '../../src/nest/notifications/mailer/mailer.service';
import { CalendarService } from '../../src/nest/calendar/calendar.service';
import { FeedsMcp } from '../../src/nest/feeds/feeds.mcp';
import { FeedsService } from '../../src/nest/feeds/feeds.service';
@@ -182,18 +179,10 @@ import { createTestJourneyShareTokensRepo } from './journey-share-repos';
import { AppSettings } from '../../src/db/entities/AppSettings.entity';
import { AuditLog } from '../../src/db/entities/AuditLog.entity';
import { Users } from '../../src/db/entities/Users.entity';
import { Settings } from '../../src/db/entities/Settings.entity';
import { InviteTokens } from '../../src/db/entities/InviteTokens.entity';
import { McpTokens } from '../../src/db/entities/McpTokens.entity';
import { OauthTokens } from '../../src/db/entities/OauthTokens.entity';
import { WebauthnCredentials } from '../../src/db/entities/WebauthnCredentials.entity';
import { PasswordResetTokens } from '../../src/db/entities/PasswordResetTokens.entity';
import { noGoogleQuota } from './google-quota';
import { createTestPushSubscriptionsRepo } from './notifications-repos';
import { createTestToursRepo } from './tours-repos';
import { createTestBudgetSettlementsRepo } from './budget-repos';
import { MaintenanceRepository } from '../../src/db/repositories/MaintenanceRepository';
import { createTestSessionsService } from './sessions';
/**
* Hand-wired counterpart of the boot-time discovery in McpRegistryService,
@@ -214,36 +203,13 @@ export async function createMcpTestRegistry(): Promise<McpRegistry> {
const appSettings = (await createTestOrm(db)).repo(AppSettings);
const auditLogRepo = mcpOrm.repo(AuditLog);
const usersRepo = mcpOrm.repo(Users);
const settingsRepo = mcpOrm.repo(Settings);
const inviteTokensRepo = mcpOrm.repo(InviteTokens);
const mcpTokensRepoForAuth = mcpOrm.repo(McpTokens);
const oauthTokensRepo = mcpOrm.repo(OauthTokens);
const webauthnCredentialsRepoForAuth = mcpOrm.repo(WebauthnCredentials);
const passwordResetTokensRepo = mcpOrm.repo(PasswordResetTokens);
const permissionsService = new PermissionsService(await createTestAppSettingsRepo(db), await createTestUnitOfWork(db));
// Same argument list as auth.bridge.ts. AtlasService used to sit in third
// place; when getTravelStats moved onto AtlasService itself the edge was
// dropped and four collaborators took its place, but this call site kept the
// old shape, so `membership` and `webauthn` held the wrong objects and
// userCleanup/mailer/tokens were undefined.
const realtimeService = new RealtimeService();
// Plan 4 Task 1 constructor-ripple: the trip `user_id`/user `role` reads
// moved off `DatabaseService` onto `TripsRepository`/`UsersRepository`.
const guards = new McpToolGuardsService(mcpOrm.repo(Trips), usersRepo, permissionsService, realtimeService);
const exchangeRatesService = new ExchangeRatesService();
const budgetService = new BudgetService(permissionsService, exchangeRatesService, realtimeService, await createTestUnitOfWork(db), ...(await budgetRepoArgs(db)));
const authService = new AuthService(
permissionsService,
new TripMembershipService(await createTestTripsRepo(db), await createTestTripMembersRepo(db)),
new WebauthnConfigService(appSettings),
new UserCleanupService(new MaintenanceRepository(mcpOrm.em), budgetService, await createTestUnitOfWork(db), usersRepo, await createTestTripMembersRepo(db), await createTestBudgetItemsRepo(db), await createTestBudgetSettlementsRepo(db), await createTestJourneyShareTokensRepo(db), await createTestJourneysRepo(db), await createTestJourneyEntriesRepo(db), await createTestJourneyContributorsRepo(db), await createTestShareTokensRepo(db), await createTestPluginsRepo(db), await createTestPluginUserErasureQueueRepo(db)),
new MailerService(usersRepo, settingsRepo, appSettings),
new EphemeralTokenService(),
new AllowedFileTypesService(appSettings), await createTestUnitOfWork(db),
appSettings, usersRepo, inviteTokensRepo, mcpTokensRepoForAuth, oauthTokensRepo, webauthnCredentialsRepoForAuth, passwordResetTokensRepo,
await createTestPushSubscriptionsRepo(db),
await createTestSessionsService(db),
);
const queryHelpersService = new QueryHelpersService(await createTestTagsRepo(db), await createTestPlaceRatingsRepo(db), await createTestAssignmentParticipantsRepo(db));
const daysService = new DaysService(
permissionsService,
@@ -453,7 +419,7 @@ export async function createMcpTestRegistry(): Promise<McpRegistry> {
await createTestVacaySharesRepo(db), await createTestVacayUserSettingsRepo(db),
await createTestSchoolHolidayRegionsRepo(db),
realtimeService, notificationsStub(), await createTestUnitOfWork(db),
), authService, addonsService),
), usersRepo, addonsService),
new SchoolHolidaysMcp(
new SchoolHolidaysService(
await createTestSchoolHolidayCountriesRepo(db),