feat(p8): /v1/calendar REST + kernel-client methods + @insignia/iios-meeting-web
Task 8.6: CalendarController exposes schedule/request(+fromCallback/fromClaim)/ list/get/consent/transcript/summarize/action-items/providers(+sync) (session-auth). kernel-client RestClient gains scheduleMeeting/requestMeeting/listMeetings/ getMeeting/setConsent/generateTranscript/summarizeMeeting/listActionItems/ connect+syncCalendarProvider + Meeting/MeetingParticipant/MeetingActionItem types. New @insignia/iios-meeting-web (MeetingProvider + useMeetings/useMeeting/useSchedule/ useConsent/useSummarize/useActionItems). Boundary: meeting-web -> contracts + kernel-client (fails on meeting-web->service). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,89 @@
|
||||
import { BadRequestException, Body, Controller, Get, Headers, Param, Post, Query } from '@nestjs/common';
|
||||
import { IiosConsentStatus, IiosMeetingType } from '@prisma/client';
|
||||
import { CalendarService } from './calendar.service';
|
||||
import { CalendarAdapter } from './calendar-adapter';
|
||||
import { SessionVerifier } from '../platform/session.verifier';
|
||||
import type { MessagePrincipal } from '../identity/actor.resolver';
|
||||
import { ConsentDto, RequestMeetingDto, ScheduleMeetingDto } from './calendar.dto';
|
||||
|
||||
@Controller('v1/calendar')
|
||||
export class CalendarController {
|
||||
constructor(
|
||||
private readonly calendar: CalendarService,
|
||||
private readonly adapter: CalendarAdapter,
|
||||
private readonly session: SessionVerifier,
|
||||
) {}
|
||||
|
||||
@Post('meetings')
|
||||
async schedule(@Body() body: ScheduleMeetingDto, @Headers('authorization') auth?: string) {
|
||||
return this.calendar.scheduleDirect(this.principal(auth), { ...body, meetingType: body.meetingType as IiosMeetingType });
|
||||
}
|
||||
|
||||
@Post('requests')
|
||||
async request(
|
||||
@Body() body: RequestMeetingDto,
|
||||
@Query('fromCallback') fromCallback?: string,
|
||||
@Query('fromClaim') fromClaim?: string,
|
||||
@Headers('authorization') auth?: string,
|
||||
) {
|
||||
const principal = this.principal(auth);
|
||||
if (fromCallback) return this.calendar.fromCallback(principal, fromCallback, body.requestedWindow);
|
||||
if (fromClaim) return this.calendar.fromEventClaim(principal, fromClaim, body.requestedWindow);
|
||||
return this.calendar.requestMeeting(principal, {
|
||||
requestedWindow: body.requestedWindow,
|
||||
meetingType: body.meetingType as IiosMeetingType | undefined,
|
||||
interactionId: body.interactionId,
|
||||
targetActorId: body.targetActorId,
|
||||
});
|
||||
}
|
||||
|
||||
@Get('meetings')
|
||||
async list(@Headers('authorization') auth?: string) {
|
||||
return this.calendar.listMeetings(this.principal(auth));
|
||||
}
|
||||
|
||||
@Get('meetings/:id')
|
||||
async get(@Param('id') id: string, @Headers('authorization') auth?: string) {
|
||||
this.principal(auth);
|
||||
return this.calendar.getMeeting(id);
|
||||
}
|
||||
|
||||
@Post('meetings/:id/consent')
|
||||
async consent(@Param('id') id: string, @Body() body: ConsentDto, @Headers('authorization') auth?: string) {
|
||||
this.principal(auth);
|
||||
return this.calendar.setConsent(id, body.actorRefId, body.status as IiosConsentStatus);
|
||||
}
|
||||
|
||||
@Post('meetings/:id/transcript')
|
||||
async transcript(@Param('id') id: string, @Headers('authorization') auth?: string) {
|
||||
return this.calendar.generateTranscript(id, this.principal(auth));
|
||||
}
|
||||
|
||||
@Post('meetings/:id/summarize')
|
||||
async summarize(@Param('id') id: string, @Headers('authorization') auth?: string) {
|
||||
return this.calendar.summarize(id, this.principal(auth));
|
||||
}
|
||||
|
||||
@Get('meetings/:id/action-items')
|
||||
async actionItems(@Param('id') id: string, @Headers('authorization') auth?: string) {
|
||||
this.principal(auth);
|
||||
return this.calendar.listActionItems(id);
|
||||
}
|
||||
|
||||
@Post('providers')
|
||||
async connect(@Headers('authorization') auth?: string) {
|
||||
return this.adapter.connectProvider(this.principal(auth));
|
||||
}
|
||||
|
||||
@Post('providers/:id/sync')
|
||||
async sync(@Param('id') id: string, @Headers('authorization') auth?: string) {
|
||||
this.principal(auth);
|
||||
return this.adapter.syncOnce(id);
|
||||
}
|
||||
|
||||
private principal(authorization?: string): MessagePrincipal {
|
||||
const token = (authorization ?? '').replace(/^Bearer\s+/i, '');
|
||||
if (!token) throw new BadRequestException('Authorization bearer token is required');
|
||||
return this.session.verify(token);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
import { IsArray, IsIn, IsObject, IsOptional, IsString } from 'class-validator';
|
||||
|
||||
const MEETING_TYPES = ['ZOOM', 'PHONE', 'IN_PERSON', 'CALLBACK', 'INTERNAL'] as const;
|
||||
const CONSENT = ['UNKNOWN', 'GRANTED', 'DENIED', 'REVOKED'] as const;
|
||||
|
||||
export class ScheduleMeetingDto {
|
||||
@IsIn(MEETING_TYPES) meetingType!: (typeof MEETING_TYPES)[number];
|
||||
@IsString() title!: string;
|
||||
@IsString() startAt!: string;
|
||||
@IsOptional() @IsString() endAt?: string;
|
||||
@IsOptional() @IsString() timezone?: string;
|
||||
@IsOptional() @IsArray() attendees?: { userId: string; displayName?: string; role?: 'REQUIRED' | 'OPTIONAL'; visibility?: 'FULL' | 'LIMITED' | 'NONE' }[];
|
||||
@IsOptional() @IsString() requestId?: string;
|
||||
}
|
||||
|
||||
export class RequestMeetingDto {
|
||||
@IsObject() requestedWindow!: Record<string, unknown>;
|
||||
@IsOptional() @IsIn(MEETING_TYPES) meetingType?: (typeof MEETING_TYPES)[number];
|
||||
@IsOptional() @IsString() interactionId?: string;
|
||||
@IsOptional() @IsString() targetActorId?: string;
|
||||
}
|
||||
|
||||
export class ConsentDto {
|
||||
@IsString() actorRefId!: string;
|
||||
@IsIn(CONSENT) status!: (typeof CONSENT)[number];
|
||||
}
|
||||
@@ -5,6 +5,7 @@ import { AiModule } from '../ai/ai.module';
|
||||
import { CalendarService } from './calendar.service';
|
||||
import { CalendarProjector } from './calendar.projector';
|
||||
import { CalendarAdapter } from './calendar-adapter';
|
||||
import { CalendarController } from './calendar.controller';
|
||||
|
||||
/**
|
||||
* Calendar & Meeting SDK (P8). Imports OutboxModule (projector/events),
|
||||
@@ -13,6 +14,7 @@ import { CalendarAdapter } from './calendar-adapter';
|
||||
*/
|
||||
@Module({
|
||||
imports: [OutboxModule, AdaptersModule, AiModule],
|
||||
controllers: [CalendarController],
|
||||
providers: [CalendarService, CalendarProjector, CalendarAdapter],
|
||||
exports: [CalendarService, CalendarAdapter],
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user