Refactors repository layer and IndexedDB architecture

Eliminates redundant repository abstraction layer by directly using EntityService methods

Implements key improvements:
- Removes unnecessary repository wrappers
- Introduces polymorphic DataSeeder for mock data loading
- Renames IndexedDBService to IndexedDBContext
- Fixes database injection timing with lazy access pattern
- Simplifies EventManager to use services directly

Reduces code complexity and improves separation of concerns
This commit is contained in:
Janus C. H. Knudsen 2025-11-20 21:45:09 +01:00
parent 5648c7c304
commit dcd76836bd
10 changed files with 1260 additions and 574 deletions

View file

@ -2,38 +2,39 @@ import { IEventBus, ICalendarEvent } from '../types/CalendarTypes';
import { CoreEvents } from '../constants/CoreEvents';
import { Configuration } from '../configurations/CalendarConfig';
import { DateService } from '../utils/DateService';
import { IEventRepository } from '../repositories/IEventRepository';
import { EventService } from '../storage/events/EventService';
import { IEntityService } from '../storage/IEntityService';
/**
* EventManager - Event lifecycle and CRUD operations
* Delegates all data operations to IEventRepository
* No longer maintains in-memory cache - repository is single source of truth
* Delegates all data operations to EventService
* EventService provides CRUD operations via BaseEntityService (save, delete, getAll)
*/
export class EventManager {
private dateService: DateService;
private config: Configuration;
private repository: IEventRepository;
private eventService: EventService;
constructor(
private eventBus: IEventBus,
dateService: DateService,
config: Configuration,
repository: IEventRepository
eventService: IEntityService<ICalendarEvent>
) {
this.dateService = dateService;
this.config = config;
this.repository = repository;
this.eventService = eventService as EventService;
}
/**
* Load event data from repository
* No longer caches - delegates to repository
* Load event data from service
* Ensures data is loaded (called during initialization)
*/
public async loadData(): Promise<void> {
try {
// Just ensure repository is ready - no caching
await this.repository.loadEvents();
// Just ensure service is ready - getAll() will return data
await this.eventService.getAll();
} catch (error) {
console.error('Failed to load event data:', error);
throw error;
@ -41,19 +42,19 @@ export class EventManager {
}
/**
* Get all events from repository
* Get all events from service
*/
public async getEvents(copy: boolean = false): Promise<ICalendarEvent[]> {
const events = await this.repository.loadEvents();
const events = await this.eventService.getAll();
return copy ? [...events] : events;
}
/**
* Get event by ID from repository
* Get event by ID from service
*/
public async getEventById(id: string): Promise<ICalendarEvent | undefined> {
const events = await this.repository.loadEvents();
return events.find(event => event.id === id);
const event = await this.eventService.get(id);
return event || undefined;
}
/**
@ -116,7 +117,7 @@ export class EventManager {
* Get events that overlap with a given time period
*/
public async getEventsForPeriod(startDate: Date, endDate: Date): Promise<ICalendarEvent[]> {
const events = await this.repository.loadEvents();
const events = await this.eventService.getAll();
// Event overlaps period if it starts before period ends AND ends after period starts
return events.filter(event => {
return event.start <= endDate && event.end >= startDate;
@ -125,10 +126,19 @@ export class EventManager {
/**
* Create a new event and add it to the calendar
* Delegates to repository with source='local'
* Generates ID and saves via EventService
*/
public async addEvent(event: Omit<ICalendarEvent, 'id'>): Promise<ICalendarEvent> {
const newEvent = await this.repository.createEvent(event, 'local');
// Generate unique ID
const id = `event-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
const newEvent: ICalendarEvent = {
...event,
id,
syncStatus: 'synced' // No queue yet, mark as synced
};
await this.eventService.save(newEvent);
this.eventBus.emit(CoreEvents.EVENT_CREATED, {
event: newEvent
@ -139,11 +149,23 @@ export class EventManager {
/**
* Update an existing event
* Delegates to repository with source='local'
* Merges updates with existing event and saves
*/
public async updateEvent(id: string, updates: Partial<ICalendarEvent>): Promise<ICalendarEvent | null> {
try {
const updatedEvent = await this.repository.updateEvent(id, updates, 'local');
const existingEvent = await this.eventService.get(id);
if (!existingEvent) {
throw new Error(`Event with ID ${id} not found`);
}
const updatedEvent: ICalendarEvent = {
...existingEvent,
...updates,
id, // Ensure ID doesn't change
syncStatus: 'synced' // No queue yet, mark as synced
};
await this.eventService.save(updatedEvent);
this.eventBus.emit(CoreEvents.EVENT_UPDATED, {
event: updatedEvent
@ -158,11 +180,11 @@ export class EventManager {
/**
* Delete an event
* Delegates to repository with source='local'
* Calls EventService.delete()
*/
public async deleteEvent(id: string): Promise<boolean> {
try {
await this.repository.deleteEvent(id, 'local');
await this.eventService.delete(id);
this.eventBus.emit(CoreEvents.EVENT_DELETED, {
eventId: id
@ -177,18 +199,24 @@ export class EventManager {
/**
* Handle remote update from SignalR
* Delegates to repository with source='remote'
* Saves remote event directly (no queue logic yet)
*/
public async handleRemoteUpdate(event: ICalendarEvent): Promise<void> {
try {
await this.repository.updateEvent(event.id, event, 'remote');
// Mark as synced since it comes from remote
const remoteEvent: ICalendarEvent = {
...event,
syncStatus: 'synced'
};
await this.eventService.save(remoteEvent);
this.eventBus.emit(CoreEvents.REMOTE_UPDATE_RECEIVED, {
event
event: remoteEvent
});
this.eventBus.emit(CoreEvents.EVENT_UPDATED, {
event
event: remoteEvent
});
} catch (error) {
console.error(`Failed to handle remote update for event ${event.id}:`, error);