fix(types): resolve TypeScript errors across multiple packages

- bot-services: Add registerAsync to AI, Calendar, Clock, Todo modules
- bot-services: Add convenience methods to ClockService for bot handlers
- bot-services: Make CreateEventInput.endTime optional with sensible defaults
- bot-services: Fix empty interface ESLint errors (use type aliases)
- questions-backend: Add missing schema columns (isDefault, sortOrder, deletedAt)
- questions-backend: Fix or() return type handling in question service
- questions-web: Add guard for undefined question ID in route params
- skilltree-web: Fix DBSchema type by not extending idb interface directly
- calendar-web: Fix Check icon prop (use weight instead of strokeWidth)
- matrix-mana-bot: Update clock handler to use new service methods

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Till-JS 2026-01-29 13:33:01 +01:00
parent 91143a497b
commit 1733580d05
14 changed files with 314 additions and 37 deletions

View file

@ -1,4 +1,4 @@
import { Module, DynamicModule } from '@nestjs/common';
import { Module, DynamicModule, Provider, Type, ModuleMetadata } from '@nestjs/common';
import { CalendarService, CALENDAR_STORAGE_PROVIDER } from './calendar.service';
import { StorageProvider } from '../shared/types';
import { FileStorageProvider } from '../shared/storage';
@ -9,6 +9,11 @@ export interface CalendarModuleOptions {
storageProvider?: StorageProvider<CalendarData>;
}
export interface CalendarModuleAsyncOptions extends Pick<ModuleMetadata, 'imports'> {
useFactory: (...args: unknown[]) => Promise<CalendarModuleOptions> | CalendarModuleOptions;
inject?: (Type<unknown> | string | symbol)[];
}
@Module({})
export class CalendarModule {
/**
@ -24,7 +29,8 @@ export class CalendarModule {
{
provide: CALENDAR_STORAGE_PROVIDER,
useValue:
options?.storageProvider ?? new FileStorageProvider<CalendarData>(storagePath, defaultData),
options?.storageProvider ??
new FileStorageProvider<CalendarData>(storagePath, defaultData),
},
CalendarService,
],
@ -48,4 +54,30 @@ export class CalendarModule {
exports: [CalendarService],
};
}
/**
* Register asynchronously with factory function
*/
static registerAsync(options: CalendarModuleAsyncOptions): DynamicModule {
const storageProvider: Provider = {
provide: CALENDAR_STORAGE_PROVIDER,
useFactory: async (...args: unknown[]) => {
const moduleOptions = await options.useFactory(...args);
const storagePath = moduleOptions?.storagePath ?? './data/calendar-data.json';
const defaultData: CalendarData = { events: [], calendars: [] };
return (
moduleOptions?.storageProvider ??
new FileStorageProvider<CalendarData>(storagePath, defaultData)
);
},
inject: options.inject || [],
};
return {
module: CalendarModule,
imports: options.imports || [],
providers: [storageProvider, CalendarService],
exports: [CalendarService],
};
}
}

View file

@ -36,7 +36,10 @@ export class CalendarService implements OnModuleInit {
) {
this.storage =
storage ||
new FileStorageProvider<CalendarData>('./data/calendar-data.json', { events: [], calendars: [] });
new FileStorageProvider<CalendarData>('./data/calendar-data.json', {
events: [],
calendars: [],
});
}
async onModuleInit() {
@ -46,7 +49,9 @@ export class CalendarService implements OnModuleInit {
private async loadData(): Promise<void> {
try {
this.data = await this.storage.load();
this.logger.log(`Loaded ${this.data.events.length} events, ${this.data.calendars.length} calendars`);
this.logger.log(
`Loaded ${this.data.events.length} events, ${this.data.calendars.length} calendars`
);
} catch (error) {
this.logger.error('Failed to load calendar data:', error);
this.data = { events: [], calendars: [] };
@ -81,6 +86,19 @@ export class CalendarService implements OnModuleInit {
async createEvent(userId: string, input: CreateEventInput): Promise<CalendarEvent> {
const calendar = this.ensureDefaultCalendar(userId);
// Calculate endTime if not provided
let endTime: Date;
if (input.endTime) {
endTime = input.endTime;
} else if (input.isAllDay) {
// For all-day events, end at end of the same day
endTime = new Date(input.startTime);
endTime.setHours(23, 59, 59, 999);
} else {
// Default to 1 hour after start
endTime = new Date(input.startTime.getTime() + 60 * 60 * 1000);
}
const event: CalendarEvent = {
id: generateId(),
userId,
@ -88,7 +106,7 @@ export class CalendarService implements OnModuleInit {
description: input.description ?? null,
location: input.location ?? null,
startTime: input.startTime.toISOString(),
endTime: input.endTime.toISOString(),
endTime: endTime.toISOString(),
isAllDay: input.isAllDay ?? false,
calendarId: input.calendarId ?? calendar.id,
calendarName: calendar.name,
@ -101,7 +119,11 @@ export class CalendarService implements OnModuleInit {
return event;
}
async updateEvent(userId: string, eventId: string, input: UpdateEventInput): Promise<CalendarEvent | null> {
async updateEvent(
userId: string,
eventId: string,
input: UpdateEventInput
): Promise<CalendarEvent | null> {
const event = this.data.events.find((e) => e.id === eventId && e.userId === userId);
if (!event) return null;
@ -197,7 +219,7 @@ export class CalendarService implements OnModuleInit {
return this.getEventsInRange(userId, today, weekEnd);
}
async getUpcomingEvents(userId: string, days: number = 7): Promise<CalendarEvent[]> {
async getUpcomingEvents(userId: string, days = 7): Promise<CalendarEvent[]> {
const now = new Date();
const endDate = addDays(now, days);
return this.getEventsInRange(userId, now, endDate);

View file

@ -1,4 +1,4 @@
import { UserEntity } from '../shared/types';
import { type UserEntity } from '../shared/types';
/**
* Calendar event entity
@ -38,7 +38,7 @@ export interface CalendarData {
export interface CreateEventInput {
title: string;
startTime: Date;
endTime: Date;
endTime?: Date; // Optional - defaults to startTime + 1 hour, or end of day for all-day events
description?: string | null;
location?: string | null;
isAllDay?: boolean;