diff --git a/apps/mana/apps/web/src/lib/data/events/catalog.ts b/apps/mana/apps/web/src/lib/data/events/catalog.ts index f2f9be0ad..4c778dca2 100644 --- a/apps/mana/apps/web/src/lib/data/events/catalog.ts +++ b/apps/mana/apps/web/src/lib/data/events/catalog.ts @@ -295,6 +295,82 @@ export interface ContactDeletedPayload { export type ContactsEventType = 'ContactCreated' | 'ContactDeleted'; +// ── Finance ───────────────────────────────────────── + +export interface TransactionCreatedPayload { + transactionId: string; + amount: number; + type: string; + category?: string; + description?: string; +} + +export interface TransactionDeletedPayload { + transactionId: string; +} + +export type FinanceEventType = 'TransactionCreated' | 'TransactionDeleted'; + +// ── Dreams ────────────────────────────────────────── + +export interface DreamCreatedPayload { + dreamId: string; + title?: string; + isLucid: boolean; + mood?: string; +} + +export interface DreamDeletedPayload { + dreamId: string; +} + +export type DreamsEventType = 'DreamCreated' | 'DreamDeleted'; + +// ── Cards ─────────────────────────────────────────── + +export interface CardStudiedPayload { + cardId: string; + deckId: string; + quality: number; +} + +export interface CardCreatedPayload { + cardId: string; + deckId: string; +} + +export type CardsEventType = 'CardStudied' | 'CardCreated'; + +// ── Times ─────────────────────────────────────────── + +export interface TimerStartedPayload { + entryId: string; + description?: string; + projectId?: string; +} + +export interface TimerStoppedPayload { + entryId: string; + durationMinutes: number; + description?: string; +} + +export type TimesEventType = 'TimerStarted' | 'TimerStopped'; + +// ── Social Events ─────────────────────────────────── + +export interface SocialEventCreatedPayload { + eventId: string; + title: string; + date?: string; +} + +export interface SocialEventDeletedPayload { + eventId: string; +} + +export type SocialEventsEventType = 'SocialEventCreated' | 'SocialEventDeleted'; + // ── Body ──────────────────────────────────────────── export interface WorkoutStartedPayload { @@ -369,6 +445,11 @@ export type ManaEventType = | JournalEventType | NotesEventType | ContactsEventType + | FinanceEventType + | DreamsEventType + | CardsEventType + | TimesEventType + | SocialEventsEventType | BodyEventType | SystemEventType; @@ -422,6 +503,21 @@ export type ManaEvent = // Contacts | DomainEvent<'ContactCreated', ContactCreatedPayload> | DomainEvent<'ContactDeleted', ContactDeletedPayload> + // Finance + | DomainEvent<'TransactionCreated', TransactionCreatedPayload> + | DomainEvent<'TransactionDeleted', TransactionDeletedPayload> + // Dreams + | DomainEvent<'DreamCreated', DreamCreatedPayload> + | DomainEvent<'DreamDeleted', DreamDeletedPayload> + // Cards + | DomainEvent<'CardStudied', CardStudiedPayload> + | DomainEvent<'CardCreated', CardCreatedPayload> + // Times + | DomainEvent<'TimerStarted', TimerStartedPayload> + | DomainEvent<'TimerStopped', TimerStoppedPayload> + // Social Events + | DomainEvent<'SocialEventCreated', SocialEventCreatedPayload> + | DomainEvent<'SocialEventDeleted', SocialEventDeletedPayload> // Body | DomainEvent<'WorkoutStarted', WorkoutStartedPayload> | DomainEvent<'WorkoutFinished', WorkoutFinishedPayload> diff --git a/apps/mana/apps/web/src/lib/data/tools/init.ts b/apps/mana/apps/web/src/lib/data/tools/init.ts index 39cabaffa..90d97ca2e 100644 --- a/apps/mana/apps/web/src/lib/data/tools/init.ts +++ b/apps/mana/apps/web/src/lib/data/tools/init.ts @@ -14,6 +14,11 @@ import { journalTools } from '$lib/modules/journal/tools'; import { notesTools } from '$lib/modules/notes/tools'; import { contactsTools } from '$lib/modules/contacts/tools'; import { bodyTools } from '$lib/modules/body/tools'; +import { financeTools } from '$lib/modules/finance/tools'; +import { dreamsTools } from '$lib/modules/dreams/tools'; +import { cardsTools } from '$lib/modules/cards/tools'; +import { timesTools } from '$lib/modules/times/tools'; +import { socialEventsTools } from '$lib/modules/events/tools'; let initialized = false; @@ -29,5 +34,10 @@ export function initTools(): void { registerTools(notesTools); registerTools(contactsTools); registerTools(bodyTools); + registerTools(financeTools); + registerTools(dreamsTools); + registerTools(cardsTools); + registerTools(timesTools); + registerTools(socialEventsTools); initialized = true; } diff --git a/apps/mana/apps/web/src/lib/modules/cards/stores/cards.svelte.ts b/apps/mana/apps/web/src/lib/modules/cards/stores/cards.svelte.ts index fde0b03c8..586c255e5 100644 --- a/apps/mana/apps/web/src/lib/modules/cards/stores/cards.svelte.ts +++ b/apps/mana/apps/web/src/lib/modules/cards/stores/cards.svelte.ts @@ -9,6 +9,7 @@ import { CardsEvents } from '@mana/shared-utils/analytics'; import { cardTable, cardDeckTable } from '../collections'; import { toCard } from '../queries'; import { encryptRecord } from '$lib/data/crypto'; +import { emitDomainEvent } from '$lib/data/events'; import type { LocalCard, Card, CreateCardInput, UpdateCardInput } from '../types'; let error = $state(null); @@ -44,6 +45,10 @@ export const cardStore = { }); } + emitDomainEvent('CardCreated', 'cards', 'cards', newLocal.id, { + cardId: newLocal.id, + deckId: input.deckId, + }); CardsEvents.cardCreated(); return plaintextSnapshot; } catch (err: any) { diff --git a/apps/mana/apps/web/src/lib/modules/cards/tools.ts b/apps/mana/apps/web/src/lib/modules/cards/tools.ts new file mode 100644 index 000000000..8133eb6da --- /dev/null +++ b/apps/mana/apps/web/src/lib/modules/cards/tools.ts @@ -0,0 +1,25 @@ +import type { ModuleTool } from '$lib/data/tools/types'; +import { cardStore } from './stores/cards.svelte'; + +export const cardsTools: ModuleTool[] = [ + { + name: 'create_card', + module: 'cards', + description: 'Erstellt eine neue Lernkarte (Flashcard)', + parameters: [ + { name: 'deckId', type: 'string', description: 'ID des Decks', required: true }, + { name: 'front', type: 'string', description: 'Vorderseite (Frage)', required: true }, + { name: 'back', type: 'string', description: 'Rueckseite (Antwort)', required: true }, + ], + async execute(params) { + const card = await cardStore.createCard({ + deckId: params.deckId as string, + front: params.front as string, + back: params.back as string, + }); + return card + ? { success: true, data: card, message: 'Lernkarte erstellt' } + : { success: false, message: 'Fehler beim Erstellen der Karte' }; + }, + }, +]; diff --git a/apps/mana/apps/web/src/lib/modules/dreams/stores/dreams.svelte.ts b/apps/mana/apps/web/src/lib/modules/dreams/stores/dreams.svelte.ts index e67c7605a..943f9ad8a 100644 --- a/apps/mana/apps/web/src/lib/modules/dreams/stores/dreams.svelte.ts +++ b/apps/mana/apps/web/src/lib/modules/dreams/stores/dreams.svelte.ts @@ -11,6 +11,7 @@ import { dreamSymbolTable, dreamTable } from '../collections'; import { toDream } from '../queries'; import { encryptRecord } from '$lib/data/crypto'; +import { emitDomainEvent } from '$lib/data/events'; import { createBlock, deleteBlock } from '$lib/data/time-blocks/service'; import { transcribeAudio } from '$lib/voice/transcribe'; import type { @@ -118,6 +119,12 @@ export const dreamsStore = { await encryptRecord('dreams', newLocal); await dreamTable.add(newLocal); await this.touchSymbols(plaintextSnapshot.symbols, +1); + emitDomainEvent('DreamCreated', 'dreams', 'dreams', dreamId, { + dreamId, + title: data.title ?? undefined, + isLucid: data.isLucid ?? false, + mood: data.mood ?? undefined, + }); return plaintextSnapshot; }, @@ -304,6 +311,7 @@ export const dreamsStore = { deletedAt: new Date().toISOString(), updatedAt: new Date().toISOString(), }); + emitDomainEvent('DreamDeleted', 'dreams', 'dreams', id, { dreamId: id }); }, async togglePin(id: string) { diff --git a/apps/mana/apps/web/src/lib/modules/dreams/tools.ts b/apps/mana/apps/web/src/lib/modules/dreams/tools.ts new file mode 100644 index 000000000..e6a4b0c28 --- /dev/null +++ b/apps/mana/apps/web/src/lib/modules/dreams/tools.ts @@ -0,0 +1,27 @@ +import type { ModuleTool } from '$lib/data/tools/types'; +import { dreamsStore } from './stores/dreams.svelte'; + +export const dreamsTools: ModuleTool[] = [ + { + name: 'create_dream', + module: 'dreams', + description: 'Erstellt einen Traum-Eintrag im Traumtagebuch', + parameters: [ + { name: 'title', type: 'string', description: 'Titel des Traums', required: false }, + { name: 'content', type: 'string', description: 'Traumbeschreibung', required: true }, + { name: 'isLucid', type: 'boolean', description: 'Luzider Traum?', required: false }, + ], + async execute(params) { + const dream = await dreamsStore.createDream({ + title: params.title as string | undefined, + content: params.content as string, + isLucid: (params.isLucid as boolean) ?? false, + }); + return { + success: true, + data: dream, + message: `Traum "${dream.title || 'Unbenannt'}" erstellt`, + }; + }, + }, +]; diff --git a/apps/mana/apps/web/src/lib/modules/events/stores/events.svelte.ts b/apps/mana/apps/web/src/lib/modules/events/stores/events.svelte.ts index 92d494a87..40797bbe3 100644 --- a/apps/mana/apps/web/src/lib/modules/events/stores/events.svelte.ts +++ b/apps/mana/apps/web/src/lib/modules/events/stores/events.svelte.ts @@ -9,6 +9,7 @@ import { db } from '$lib/data/database'; import { createBlock, updateBlock, deleteBlock } from '$lib/data/time-blocks/service'; import { timeBlockTable } from '$lib/data/time-blocks/collections'; import { encryptRecord, decryptRecord } from '$lib/data/crypto'; +import { emitDomainEvent } from '$lib/data/events'; import type { LocalSocialEvent, LocalEventItem, EventStatus } from '../types'; import { eventsApi } from '../api'; import { recordTombstone } from '../tombstones'; @@ -73,6 +74,11 @@ export const eventsStore = { // linked TimeBlock was already encrypted by createBlock above. await encryptRecord('socialEvents', newLocal); await db.table('socialEvents').add(newLocal); + emitDomainEvent('SocialEventCreated', 'events', 'socialEvents', eventId, { + eventId, + title: input.title, + date: input.startTime.split('T')[0], + }); return { success: true as const, id: eventId }; } catch (e) { error = e instanceof Error ? e.message : 'Failed to create event'; @@ -162,6 +168,7 @@ export const eventsStore = { deletedAt: new Date().toISOString(), updatedAt: new Date().toISOString(), }); + emitDomainEvent('SocialEventDeleted', 'events', 'socialEvents', id, { eventId: id }); return { success: true as const }; } catch (e) { error = e instanceof Error ? e.message : 'Failed to delete event'; diff --git a/apps/mana/apps/web/src/lib/modules/events/tools.ts b/apps/mana/apps/web/src/lib/modules/events/tools.ts new file mode 100644 index 000000000..ae7be50b1 --- /dev/null +++ b/apps/mana/apps/web/src/lib/modules/events/tools.ts @@ -0,0 +1,29 @@ +import type { ModuleTool } from '$lib/data/tools/types'; +import { eventsStore } from './stores/events.svelte'; + +export const socialEventsTools: ModuleTool[] = [ + { + name: 'create_social_event', + module: 'events', + description: 'Erstellt ein soziales Event (Party, Treffen, Feier)', + parameters: [ + { name: 'title', type: 'string', description: 'Name des Events', required: true }, + { name: 'startTime', type: 'string', description: 'Startzeit (ISO 8601)', required: true }, + { name: 'endTime', type: 'string', description: 'Endzeit (ISO 8601)', required: true }, + { name: 'location', type: 'string', description: 'Ort', required: false }, + { name: 'description', type: 'string', description: 'Beschreibung', required: false }, + ], + async execute(params) { + const result = await eventsStore.createEvent({ + title: params.title as string, + startTime: params.startTime as string, + endTime: params.endTime as string, + location: params.location as string | undefined, + description: params.description as string | undefined, + }); + return result.success + ? { success: true, data: { id: result.id }, message: `Event "${params.title}" erstellt` } + : { success: false, message: result.error ?? 'Fehler' }; + }, + }, +]; diff --git a/apps/mana/apps/web/src/lib/modules/finance/stores/finance.svelte.ts b/apps/mana/apps/web/src/lib/modules/finance/stores/finance.svelte.ts index ce7651eb8..6412a454d 100644 --- a/apps/mana/apps/web/src/lib/modules/finance/stores/finance.svelte.ts +++ b/apps/mana/apps/web/src/lib/modules/finance/stores/finance.svelte.ts @@ -10,6 +10,7 @@ import { transactionTable, categoryTable } from '../collections'; import { toTransaction, toCategory } from '../queries'; import { encryptRecord } from '$lib/data/crypto'; +import { emitDomainEvent } from '$lib/data/events'; import type { LocalTransaction, LocalFinanceCategory, TransactionType } from '../types'; export const financeStore = { @@ -34,6 +35,12 @@ export const financeStore = { const plaintextSnapshot = toTransaction(newLocal); await encryptRecord('transactions', newLocal); await transactionTable.add(newLocal); + emitDomainEvent('TransactionCreated', 'finance', 'transactions', newLocal.id, { + transactionId: newLocal.id, + amount: data.amount, + type: data.type, + description: data.description, + }); return plaintextSnapshot; }, @@ -56,6 +63,7 @@ export const financeStore = { deletedAt: new Date().toISOString(), updatedAt: new Date().toISOString(), }); + emitDomainEvent('TransactionDeleted', 'finance', 'transactions', id, { transactionId: id }); }, async addCategory(data: { name: string; emoji: string; color: string; type: TransactionType }) { diff --git a/apps/mana/apps/web/src/lib/modules/finance/tools.ts b/apps/mana/apps/web/src/lib/modules/finance/tools.ts new file mode 100644 index 000000000..c8d0344b1 --- /dev/null +++ b/apps/mana/apps/web/src/lib/modules/finance/tools.ts @@ -0,0 +1,33 @@ +import type { ModuleTool } from '$lib/data/tools/types'; +import { financeStore } from './stores/finance.svelte'; + +export const financeTools: ModuleTool[] = [ + { + name: 'add_transaction', + module: 'finance', + description: 'Erfasst eine Einnahme oder Ausgabe', + parameters: [ + { + name: 'type', + type: 'string', + description: 'Art', + required: true, + enum: ['income', 'expense'], + }, + { name: 'amount', type: 'number', description: 'Betrag in Euro', required: true }, + { name: 'description', type: 'string', description: 'Beschreibung', required: true }, + ], + async execute(params) { + const tx = await financeStore.addTransaction({ + type: params.type as 'income' | 'expense', + amount: params.amount as number, + description: params.description as string, + }); + return { + success: true, + data: tx, + message: `${params.type === 'income' ? 'Einnahme' : 'Ausgabe'}: ${params.amount}€ (${params.description})`, + }; + }, + }, +]; diff --git a/apps/mana/apps/web/src/lib/modules/times/stores/timer.svelte.ts b/apps/mana/apps/web/src/lib/modules/times/stores/timer.svelte.ts index 73531274d..2afbed541 100644 --- a/apps/mana/apps/web/src/lib/modules/times/stores/timer.svelte.ts +++ b/apps/mana/apps/web/src/lib/modules/times/stores/timer.svelte.ts @@ -8,6 +8,7 @@ import { browser } from '$app/environment'; import { db } from '$lib/data/database'; +import { emitDomainEvent } from '$lib/data/events'; import { timeEntryTable, settingsTable } from '$lib/modules/times/collections'; import { roundDuration } from '$lib/modules/times/utils/rounding'; import { createBlock, updateBlock, deleteBlock } from '$lib/data/time-blocks/service'; @@ -135,6 +136,11 @@ export const timerStore = { elapsedSeconds = 0; startTicking(); startAutoSave(); + emitDomainEvent('TimerStarted', 'times', 'timeEntries', entryId, { + entryId, + description: options?.description, + projectId: options?.projectId, + }); }, /** Stop the running timer */ @@ -168,6 +174,11 @@ export const timerStore = { ...runningEntry, duration: roundedDuration, }; + emitDomainEvent('TimerStopped', 'times', 'timeEntries', runningEntry.id, { + entryId: runningEntry.id, + durationMinutes: Math.round(roundedDuration / 60), + description: runningEntry.description, + }); stopTicking(); runningEntry = null; runningBlock = null; diff --git a/apps/mana/apps/web/src/lib/modules/times/tools.ts b/apps/mana/apps/web/src/lib/modules/times/tools.ts new file mode 100644 index 000000000..789bbe031 --- /dev/null +++ b/apps/mana/apps/web/src/lib/modules/times/tools.ts @@ -0,0 +1,41 @@ +import type { ModuleTool } from '$lib/data/tools/types'; + +export const timesTools: ModuleTool[] = [ + { + name: 'start_timer', + module: 'times', + description: 'Startet einen Zeitmess-Timer', + parameters: [ + { + name: 'description', + type: 'string', + description: 'Beschreibung der Taetigkeit', + required: false, + }, + ], + async execute(params) { + const { timerStore } = await import('./stores/timer.svelte'); + await timerStore.start({ description: params.description as string | undefined }); + return { + success: true, + message: `Timer gestartet${params.description ? `: "${params.description}"` : ''}`, + }; + }, + }, + { + name: 'stop_timer', + module: 'times', + description: 'Stoppt den laufenden Timer', + parameters: [], + async execute() { + const { timerStore } = await import('./stores/timer.svelte'); + const entry = await timerStore.stop(); + if (!entry) return { success: false, message: 'Kein Timer aktiv' }; + return { + success: true, + data: entry, + message: `Timer gestoppt (${Math.round(entry.duration / 60)} min)`, + }; + }, + }, +];