import {
CanActivate,
ExecutionContext,
ForbiddenException,
Injectable,
} from '@nestjs/common';
import type { TenantScopedRequest } from './organization-context.guard';
import { DatabaseService } from '../../infrastructure/database/database.service';
export interface LocationScopedRequest extends TenantScopedRequest {
locationId?: string;
placeId?: string;
operationalUnitId?: string;
}
@Injectable()
export class LocationContextGuard implements CanActivate {
constructor(private readonly database: DatabaseService) {}
async canActivate(context: ExecutionContext): Promise<boolean> {
const request = context.switchToHttp().getRequest<LocationScopedRequest>();
if (!request.organizationId) {
throw new ForbiddenException({
code: 'tenant.organization_context_required',
});
}
const locationId = request.header('x-location-id')?.trim();
if (!locationId) {
throw new ForbiddenException({
code: 'tenant.location_context_required',
});
}
const rows = await this.database.query<{ id: string }>(
`select id
from locations
where id = $1
and organization_id = $2
and status = 'active'
limit 1`,
[locationId, request.organizationId],
);
if (!rows[0]) {
throw new ForbiddenException({
code: 'tenant.location_forbidden',
});
}
request.locationId = locationId;
const placeId = request.header('x-place-id')?.trim();
const operationalUnitId = request.header('x-operational-unit-id')?.trim();
if (operationalUnitId) {
const units = await this.database.query<{ id: string }>(
`select id from operational_units where id=$1 and organization_id=$2 and location_id=$3 and status='active' limit 1`,
[operationalUnitId, request.organizationId, locationId],
);
if (!units[0]) throw new ForbiddenException({ code: 'tenant.operational_unit_forbidden' });
request.operationalUnitId = operationalUnitId;
}
if (placeId) {
const places = await this.database.query<{ id: string; operational_unit_id: string | null }>(
`select id,operational_unit_id from places where id=$1 and organization_id=$2 and location_id=$3 and status in ('planned','active','closing') limit 1`,
[placeId, request.organizationId, locationId],
);
if (!places[0]) throw new ForbiddenException({ code: 'tenant.place_forbidden' });
if (operationalUnitId && places[0].operational_unit_id !== operationalUnitId) {
throw new ForbiddenException({ code: 'tenant.place_operational_unit_forbidden' });
}
request.placeId = placeId;
}
return true;
}
}
