format codebase with prettier
This commit is contained in:
@@ -1,17 +1,17 @@
|
|||||||
type Theme = {
|
type Theme = {
|
||||||
chatBot: string,
|
chatBot: string;
|
||||||
primeFg: string,
|
primeFg: string;
|
||||||
primeBg: string,
|
primeBg: string;
|
||||||
messageBorderBg: string,
|
messageBorderBg: string;
|
||||||
placeholderBg: string,
|
placeholderBg: string;
|
||||||
calenderBg: string,
|
calenderBg: string;
|
||||||
confirmButton: string,
|
confirmButton: string;
|
||||||
rejectButton: string,
|
rejectButton: string;
|
||||||
disabledButton: string,
|
disabledButton: string;
|
||||||
buttonText: string,
|
buttonText: string;
|
||||||
textSecondary: string,
|
textSecondary: string;
|
||||||
textMuted: string,
|
textMuted: string;
|
||||||
}
|
};
|
||||||
|
|
||||||
const defaultLight: Theme = {
|
const defaultLight: Theme = {
|
||||||
chatBot: "#DE6C20",
|
chatBot: "#DE6C20",
|
||||||
@@ -26,7 +26,7 @@ const defaultLight: Theme = {
|
|||||||
buttonText: "#fff",
|
buttonText: "#fff",
|
||||||
textSecondary: "#666",
|
textSecondary: "#666",
|
||||||
textMuted: "#888",
|
textMuted: "#888",
|
||||||
}
|
};
|
||||||
|
|
||||||
let currentTheme: Theme = defaultLight;
|
let currentTheme: Theme = defaultLight;
|
||||||
export default currentTheme;
|
export default currentTheme;
|
||||||
|
|||||||
@@ -4,24 +4,30 @@ import theme from "../../Themes";
|
|||||||
|
|
||||||
export default function TabLayout() {
|
export default function TabLayout() {
|
||||||
return (
|
return (
|
||||||
<Tabs screenOptions={{
|
<Tabs
|
||||||
headerShown: false,
|
screenOptions={{
|
||||||
tabBarActiveTintColor: theme.chatBot,
|
headerShown: false,
|
||||||
tabBarInactiveTintColor: theme.primeFg,
|
tabBarActiveTintColor: theme.chatBot,
|
||||||
tabBarStyle: { backgroundColor: theme.primeBg },
|
tabBarInactiveTintColor: theme.primeFg,
|
||||||
}}>
|
tabBarStyle: { backgroundColor: theme.primeBg },
|
||||||
|
}}
|
||||||
|
>
|
||||||
<Tabs.Screen
|
<Tabs.Screen
|
||||||
name="chat"
|
name="chat"
|
||||||
options={{
|
options={{
|
||||||
title: 'Chat',
|
title: "Chat",
|
||||||
tabBarIcon: ({ color }) => <Ionicons size={28} name="chatbubble" color={color} />,
|
tabBarIcon: ({ color }) => (
|
||||||
|
<Ionicons size={28} name="chatbubble" color={color} />
|
||||||
|
),
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<Tabs.Screen
|
<Tabs.Screen
|
||||||
name="calendar"
|
name="calendar"
|
||||||
options={{
|
options={{
|
||||||
title: 'Calendar',
|
title: "Calendar",
|
||||||
tabBarIcon: ({ color }) => <Ionicons size={28} name="calendar" color={color} />,
|
tabBarIcon: ({ color }) => (
|
||||||
|
<Ionicons size={28} name="calendar" color={color} />
|
||||||
|
),
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</Tabs>
|
</Tabs>
|
||||||
|
|||||||
@@ -40,13 +40,13 @@ const Chat = () => {
|
|||||||
action: "confirm" | "reject",
|
action: "confirm" | "reject",
|
||||||
messageId: string,
|
messageId: string,
|
||||||
conversationId: string,
|
conversationId: string,
|
||||||
proposedChange?: ProposedEventChange
|
proposedChange?: ProposedEventChange,
|
||||||
) => {
|
) => {
|
||||||
// Mark message as responded (optimistic update)
|
// Mark message as responded (optimistic update)
|
||||||
setMessages((prev) =>
|
setMessages((prev) =>
|
||||||
prev.map((msg) =>
|
prev.map((msg) =>
|
||||||
msg.id === messageId ? { ...msg, respondedAction: action } : msg
|
msg.id === messageId ? { ...msg, respondedAction: action } : msg,
|
||||||
)
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@@ -58,7 +58,7 @@ const Chat = () => {
|
|||||||
proposedChange.action,
|
proposedChange.action,
|
||||||
proposedChange.event,
|
proposedChange.event,
|
||||||
proposedChange.eventId,
|
proposedChange.eventId,
|
||||||
proposedChange.updates
|
proposedChange.updates,
|
||||||
)
|
)
|
||||||
: await ChatService.rejectEvent(conversationId, messageId);
|
: await ChatService.rejectEvent(conversationId, messageId);
|
||||||
|
|
||||||
@@ -74,8 +74,8 @@ const Chat = () => {
|
|||||||
// Revert on error
|
// Revert on error
|
||||||
setMessages((prev) =>
|
setMessages((prev) =>
|
||||||
prev.map((msg) =>
|
prev.map((msg) =>
|
||||||
msg.id === messageId ? { ...msg, respondedAction: undefined } : msg
|
msg.id === messageId ? { ...msg, respondedAction: undefined } : msg,
|
||||||
)
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -119,7 +119,12 @@ const Chat = () => {
|
|||||||
proposedChange={item.proposedChange}
|
proposedChange={item.proposedChange}
|
||||||
respondedAction={item.respondedAction}
|
respondedAction={item.respondedAction}
|
||||||
onConfirm={() =>
|
onConfirm={() =>
|
||||||
handleEventResponse("confirm", item.id, item.conversationId!, item.proposedChange)
|
handleEventResponse(
|
||||||
|
"confirm",
|
||||||
|
item.id,
|
||||||
|
item.conversationId!,
|
||||||
|
item.proposedChange,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
onReject={() =>
|
onReject={() =>
|
||||||
handleEventResponse("reject", item.id, item.conversationId!)
|
handleEventResponse("reject", item.id, item.conversationId!)
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { View, Text, TextInput, Pressable } from 'react-native';
|
import { View, Text, TextInput, Pressable } from "react-native";
|
||||||
import { useLocalSearchParams } from 'expo-router';
|
import { useLocalSearchParams } from "expo-router";
|
||||||
import BaseBackground from '../../components/BaseBackground';
|
import BaseBackground from "../../components/BaseBackground";
|
||||||
|
|
||||||
const EventDetailScreen = () => {
|
const EventDetailScreen = () => {
|
||||||
const { id } = useLocalSearchParams<{ id: string }>();
|
const { id } = useLocalSearchParams<{ id: string }>();
|
||||||
@@ -12,7 +12,7 @@ const EventDetailScreen = () => {
|
|||||||
// TODO: Delete button -> EventService.delete()
|
// TODO: Delete button -> EventService.delete()
|
||||||
// TODO: Link to NoteScreen for this event
|
// TODO: Link to NoteScreen for this event
|
||||||
// TODO: Loading and error states
|
// TODO: Loading and error states
|
||||||
throw new Error('Not implemented');
|
throw new Error("Not implemented");
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<BaseBackground>
|
<BaseBackground>
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { View, Text, TextInput, Pressable } from 'react-native';
|
import { View, Text, TextInput, Pressable } from "react-native";
|
||||||
import BaseBackground from '../components/BaseBackground';
|
import BaseBackground from "../components/BaseBackground";
|
||||||
|
|
||||||
const LoginScreen = () => {
|
const LoginScreen = () => {
|
||||||
// TODO: Email input field
|
// TODO: Email input field
|
||||||
@@ -8,7 +8,7 @@ const LoginScreen = () => {
|
|||||||
// TODO: Link to RegisterScreen
|
// TODO: Link to RegisterScreen
|
||||||
// TODO: Error handling and display
|
// TODO: Error handling and display
|
||||||
// TODO: Navigate to Calendar on success
|
// TODO: Navigate to Calendar on success
|
||||||
throw new Error('Not implemented');
|
throw new Error("Not implemented");
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<BaseBackground>
|
<BaseBackground>
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { View, Text, TextInput, Pressable } from 'react-native';
|
import { View, Text, TextInput, Pressable } from "react-native";
|
||||||
import { useLocalSearchParams } from 'expo-router';
|
import { useLocalSearchParams } from "expo-router";
|
||||||
import BaseBackground from '../../components/BaseBackground';
|
import BaseBackground from "../../components/BaseBackground";
|
||||||
|
|
||||||
const NoteScreen = () => {
|
const NoteScreen = () => {
|
||||||
const { id } = useLocalSearchParams<{ id: string }>();
|
const { id } = useLocalSearchParams<{ id: string }>();
|
||||||
@@ -10,7 +10,7 @@ const NoteScreen = () => {
|
|||||||
// TODO: Auto-save or manual save button
|
// TODO: Auto-save or manual save button
|
||||||
// TODO: Save changes -> EventService.update({ note: ... })
|
// TODO: Save changes -> EventService.update({ note: ... })
|
||||||
// TODO: Loading and error states
|
// TODO: Loading and error states
|
||||||
throw new Error('Not implemented');
|
throw new Error("Not implemented");
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<BaseBackground>
|
<BaseBackground>
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { View, Text, TextInput, Pressable } from 'react-native';
|
import { View, Text, TextInput, Pressable } from "react-native";
|
||||||
import BaseBackground from '../components/BaseBackground';
|
import BaseBackground from "../components/BaseBackground";
|
||||||
|
|
||||||
const RegisterScreen = () => {
|
const RegisterScreen = () => {
|
||||||
// TODO: Email input field
|
// TODO: Email input field
|
||||||
@@ -10,7 +10,7 @@ const RegisterScreen = () => {
|
|||||||
// TODO: Link to LoginScreen
|
// TODO: Link to LoginScreen
|
||||||
// TODO: Error handling and display
|
// TODO: Error handling and display
|
||||||
// TODO: Navigate to Calendar on success
|
// TODO: Navigate to Calendar on success
|
||||||
throw new Error('Not implemented');
|
throw new Error("Not implemented");
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<BaseBackground>
|
<BaseBackground>
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
import { View } from "react-native"
|
import { View } from "react-native";
|
||||||
import currentTheme from "../Themes"
|
import currentTheme from "../Themes";
|
||||||
import { ReactNode } from "react";
|
import { ReactNode } from "react";
|
||||||
|
|
||||||
type BaseBackgroundProps = {
|
type BaseBackgroundProps = {
|
||||||
children?: ReactNode;
|
children?: ReactNode;
|
||||||
className?: string;
|
className?: string;
|
||||||
}
|
};
|
||||||
|
|
||||||
const BaseBackground = (props: BaseBackgroundProps) => {
|
const BaseBackground = (props: BaseBackgroundProps) => {
|
||||||
return (
|
return (
|
||||||
@@ -17,7 +17,7 @@ const BaseBackground = (props: BaseBackgroundProps) => {
|
|||||||
>
|
>
|
||||||
{props.children}
|
{props.children}
|
||||||
</View>
|
</View>
|
||||||
)
|
);
|
||||||
}
|
};
|
||||||
|
|
||||||
export default BaseBackground;
|
export default BaseBackground;
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { View, Text, Pressable } from 'react-native';
|
import { View, Text, Pressable } from "react-native";
|
||||||
import { CalendarEvent } from '@caldav/shared';
|
import { CalendarEvent } from "@caldav/shared";
|
||||||
|
|
||||||
type EventCardProps = {
|
type EventCardProps = {
|
||||||
event: CalendarEvent;
|
event: CalendarEvent;
|
||||||
@@ -10,7 +10,7 @@ const EventCard = ({ event: _event, onPress: _onPress }: EventCardProps) => {
|
|||||||
// TODO: Display event title, time, and description preview
|
// TODO: Display event title, time, and description preview
|
||||||
// TODO: Handle onPress to navigate to EventDetailScreen
|
// TODO: Handle onPress to navigate to EventDetailScreen
|
||||||
// TODO: Style based on event type or time of day
|
// TODO: Style based on event type or time of day
|
||||||
throw new Error('Not implemented');
|
throw new Error("Not implemented");
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Pressable>
|
<Pressable>
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { View, Text, Modal, Pressable } from 'react-native';
|
import { View, Text, Modal, Pressable } from "react-native";
|
||||||
import { CreateEventDTO } from '@caldav/shared';
|
import { CreateEventDTO } from "@caldav/shared";
|
||||||
|
|
||||||
type EventConfirmDialogProps = {
|
type EventConfirmDialogProps = {
|
||||||
visible: boolean;
|
visible: boolean;
|
||||||
@@ -20,7 +20,7 @@ const EventConfirmDialog = ({
|
|||||||
// TODO: Confirm button calls onConfirm and closes dialog
|
// TODO: Confirm button calls onConfirm and closes dialog
|
||||||
// TODO: Reject button calls onReject and closes dialog
|
// TODO: Reject button calls onReject and closes dialog
|
||||||
// TODO: Close button or backdrop tap calls onClose
|
// TODO: Close button or backdrop tap calls onClose
|
||||||
throw new Error('Not implemented');
|
throw new Error("Not implemented");
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Modal visible={false} transparent animationType="fade">
|
<Modal visible={false} transparent animationType="fade">
|
||||||
|
|||||||
@@ -41,7 +41,10 @@ export const ProposedEventCard = ({
|
|||||||
{formatDateTime(event?.startTime)}
|
{formatDateTime(event?.startTime)}
|
||||||
</Text>
|
</Text>
|
||||||
{event?.description && (
|
{event?.description && (
|
||||||
<Text style={{ color: currentTheme.textSecondary }} className="text-sm mt-1">
|
<Text
|
||||||
|
style={{ color: currentTheme.textSecondary }}
|
||||||
|
className="text-sm mt-1"
|
||||||
|
>
|
||||||
{event.description}
|
{event.description}
|
||||||
</Text>
|
</Text>
|
||||||
)}
|
)}
|
||||||
@@ -65,7 +68,10 @@ export const ProposedEventCard = ({
|
|||||||
borderColor: currentTheme.confirmButton,
|
borderColor: currentTheme.confirmButton,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Text style={{ color: currentTheme.buttonText }} className="font-medium">
|
<Text
|
||||||
|
style={{ color: currentTheme.buttonText }}
|
||||||
|
className="font-medium"
|
||||||
|
>
|
||||||
Annehmen
|
Annehmen
|
||||||
</Text>
|
</Text>
|
||||||
</Pressable>
|
</Pressable>
|
||||||
@@ -81,7 +87,10 @@ export const ProposedEventCard = ({
|
|||||||
borderColor: currentTheme.rejectButton,
|
borderColor: currentTheme.rejectButton,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Text style={{ color: currentTheme.buttonText }} className="font-medium">
|
<Text
|
||||||
|
style={{ color: currentTheme.buttonText }}
|
||||||
|
className="font-medium"
|
||||||
|
>
|
||||||
Ablehnen
|
Ablehnen
|
||||||
</Text>
|
</Text>
|
||||||
</Pressable>
|
</Pressable>
|
||||||
|
|||||||
@@ -1,19 +1,19 @@
|
|||||||
import { LoginDTO, CreateUserDTO, AuthResponse } from '@caldav/shared';
|
import { LoginDTO, CreateUserDTO, AuthResponse } from "@caldav/shared";
|
||||||
|
|
||||||
export const AuthService = {
|
export const AuthService = {
|
||||||
login: async (_credentials: LoginDTO): Promise<AuthResponse> => {
|
login: async (_credentials: LoginDTO): Promise<AuthResponse> => {
|
||||||
throw new Error('Not implemented');
|
throw new Error("Not implemented");
|
||||||
},
|
},
|
||||||
|
|
||||||
register: async (_data: CreateUserDTO): Promise<AuthResponse> => {
|
register: async (_data: CreateUserDTO): Promise<AuthResponse> => {
|
||||||
throw new Error('Not implemented');
|
throw new Error("Not implemented");
|
||||||
},
|
},
|
||||||
|
|
||||||
logout: async (): Promise<void> => {
|
logout: async (): Promise<void> => {
|
||||||
throw new Error('Not implemented');
|
throw new Error("Not implemented");
|
||||||
},
|
},
|
||||||
|
|
||||||
refresh: async (): Promise<AuthResponse> => {
|
refresh: async (): Promise<AuthResponse> => {
|
||||||
throw new Error('Not implemented');
|
throw new Error("Not implemented");
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -28,27 +28,32 @@ export const ChatService = {
|
|||||||
action: EventAction,
|
action: EventAction,
|
||||||
event?: CreateEventDTO,
|
event?: CreateEventDTO,
|
||||||
eventId?: string,
|
eventId?: string,
|
||||||
updates?: UpdateEventDTO
|
updates?: UpdateEventDTO,
|
||||||
): Promise<ChatResponse> => {
|
): Promise<ChatResponse> => {
|
||||||
const body: ConfirmEventRequest = { action, event, eventId, updates };
|
const body: ConfirmEventRequest = { action, event, eventId, updates };
|
||||||
return ApiClient.post<ChatResponse>(`/chat/confirm/${conversationId}/${messageId}`, body);
|
return ApiClient.post<ChatResponse>(
|
||||||
|
`/chat/confirm/${conversationId}/${messageId}`,
|
||||||
|
body,
|
||||||
|
);
|
||||||
},
|
},
|
||||||
|
|
||||||
rejectEvent: async (
|
rejectEvent: async (
|
||||||
conversationId: string,
|
conversationId: string,
|
||||||
messageId: string
|
messageId: string,
|
||||||
): Promise<ChatResponse> => {
|
): Promise<ChatResponse> => {
|
||||||
return ApiClient.post<ChatResponse>(`/chat/reject/${conversationId}/${messageId}`);
|
return ApiClient.post<ChatResponse>(
|
||||||
|
`/chat/reject/${conversationId}/${messageId}`,
|
||||||
|
);
|
||||||
},
|
},
|
||||||
|
|
||||||
getConversations: async (): Promise<ConversationSummary[]> => {
|
getConversations: async (): Promise<ConversationSummary[]> => {
|
||||||
throw new Error('Not implemented');
|
throw new Error("Not implemented");
|
||||||
},
|
},
|
||||||
|
|
||||||
getConversation: async (
|
getConversation: async (
|
||||||
_id: string,
|
_id: string,
|
||||||
_options?: GetMessagesOptions
|
_options?: GetMessagesOptions,
|
||||||
): Promise<ChatMessage[]> => {
|
): Promise<ChatMessage[]> => {
|
||||||
throw new Error('Not implemented');
|
throw new Error("Not implemented");
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,27 +1,33 @@
|
|||||||
import { CalendarEvent, CreateEventDTO, UpdateEventDTO } from '@caldav/shared';
|
import { CalendarEvent, CreateEventDTO, UpdateEventDTO } from "@caldav/shared";
|
||||||
|
|
||||||
export const EventService = {
|
export const EventService = {
|
||||||
getAll: async (): Promise<CalendarEvent[]> => {
|
getAll: async (): Promise<CalendarEvent[]> => {
|
||||||
throw new Error('Not implemented');
|
throw new Error("Not implemented");
|
||||||
},
|
},
|
||||||
|
|
||||||
getById: async (_id: string): Promise<CalendarEvent> => {
|
getById: async (_id: string): Promise<CalendarEvent> => {
|
||||||
throw new Error('Not implemented');
|
throw new Error("Not implemented");
|
||||||
},
|
},
|
||||||
|
|
||||||
getByDateRange: async (_start: Date, _end: Date): Promise<CalendarEvent[]> => {
|
getByDateRange: async (
|
||||||
throw new Error('Not implemented');
|
_start: Date,
|
||||||
|
_end: Date,
|
||||||
|
): Promise<CalendarEvent[]> => {
|
||||||
|
throw new Error("Not implemented");
|
||||||
},
|
},
|
||||||
|
|
||||||
create: async (_data: CreateEventDTO): Promise<CalendarEvent> => {
|
create: async (_data: CreateEventDTO): Promise<CalendarEvent> => {
|
||||||
throw new Error('Not implemented');
|
throw new Error("Not implemented");
|
||||||
},
|
},
|
||||||
|
|
||||||
update: async (_id: string, _data: UpdateEventDTO): Promise<CalendarEvent> => {
|
update: async (
|
||||||
throw new Error('Not implemented');
|
_id: string,
|
||||||
|
_data: UpdateEventDTO,
|
||||||
|
): Promise<CalendarEvent> => {
|
||||||
|
throw new Error("Not implemented");
|
||||||
},
|
},
|
||||||
|
|
||||||
delete: async (_id: string): Promise<void> => {
|
delete: async (_id: string): Promise<void> => {
|
||||||
throw new Error('Not implemented');
|
throw new Error("Not implemented");
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
export { ApiClient, API_BASE_URL } from './ApiClient';
|
export { ApiClient, API_BASE_URL } from "./ApiClient";
|
||||||
export { AuthService } from './AuthService';
|
export { AuthService } from "./AuthService";
|
||||||
export { EventService } from './EventService';
|
export { EventService } from "./EventService";
|
||||||
export { ChatService } from './ChatService';
|
export { ChatService } from "./ChatService";
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { create } from 'zustand';
|
import { create } from "zustand";
|
||||||
import { User } from '@caldav/shared';
|
import { User } from "@caldav/shared";
|
||||||
|
|
||||||
interface AuthState {
|
interface AuthState {
|
||||||
user: User | null;
|
user: User | null;
|
||||||
@@ -15,12 +15,12 @@ export const useAuthStore = create<AuthState>((set) => ({
|
|||||||
token: null,
|
token: null,
|
||||||
isAuthenticated: false,
|
isAuthenticated: false,
|
||||||
login: (_user: User, _token: string) => {
|
login: (_user: User, _token: string) => {
|
||||||
throw new Error('Not implemented');
|
throw new Error("Not implemented");
|
||||||
},
|
},
|
||||||
logout: () => {
|
logout: () => {
|
||||||
throw new Error('Not implemented');
|
throw new Error("Not implemented");
|
||||||
},
|
},
|
||||||
setToken: (_token: string) => {
|
setToken: (_token: string) => {
|
||||||
throw new Error('Not implemented');
|
throw new Error("Not implemented");
|
||||||
},
|
},
|
||||||
}));
|
}));
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { create } from 'zustand';
|
import { create } from "zustand";
|
||||||
import { CalendarEvent } from '@caldav/shared';
|
import { CalendarEvent } from "@caldav/shared";
|
||||||
|
|
||||||
interface EventsState {
|
interface EventsState {
|
||||||
events: CalendarEvent[];
|
events: CalendarEvent[];
|
||||||
@@ -12,15 +12,15 @@ interface EventsState {
|
|||||||
export const useEventsStore = create<EventsState>((set) => ({
|
export const useEventsStore = create<EventsState>((set) => ({
|
||||||
events: [],
|
events: [],
|
||||||
setEvents: (_events: CalendarEvent[]) => {
|
setEvents: (_events: CalendarEvent[]) => {
|
||||||
throw new Error('Not implemented');
|
throw new Error("Not implemented");
|
||||||
},
|
},
|
||||||
addEvent: (_event: CalendarEvent) => {
|
addEvent: (_event: CalendarEvent) => {
|
||||||
throw new Error('Not implemented');
|
throw new Error("Not implemented");
|
||||||
},
|
},
|
||||||
updateEvent: (_id: string, _event: Partial<CalendarEvent>) => {
|
updateEvent: (_id: string, _event: Partial<CalendarEvent>) => {
|
||||||
throw new Error('Not implemented');
|
throw new Error("Not implemented");
|
||||||
},
|
},
|
||||||
deleteEvent: (_id: string) => {
|
deleteEvent: (_id: string) => {
|
||||||
throw new Error('Not implemented');
|
throw new Error("Not implemented");
|
||||||
},
|
},
|
||||||
}));
|
}));
|
||||||
|
|||||||
@@ -1,2 +1,2 @@
|
|||||||
export { useAuthStore } from './AuthStore';
|
export { useAuthStore } from "./AuthStore";
|
||||||
export { useEventsStore } from './EventsStore';
|
export { useEventsStore } from "./EventsStore";
|
||||||
|
|||||||
@@ -1,18 +1,21 @@
|
|||||||
import Anthropic from '@anthropic-ai/sdk';
|
import Anthropic from "@anthropic-ai/sdk";
|
||||||
import { AIProvider, AIContext, AIResponse } from '../services/interfaces';
|
import { AIProvider, AIContext, AIResponse } from "../services/interfaces";
|
||||||
|
|
||||||
export class ClaudeAdapter implements AIProvider {
|
export class ClaudeAdapter implements AIProvider {
|
||||||
private client: Anthropic;
|
private client: Anthropic;
|
||||||
private model: string;
|
private model: string;
|
||||||
|
|
||||||
constructor(apiKey?: string, model: string = 'claude-3-haiku-20240307') {
|
constructor(apiKey?: string, model: string = "claude-3-haiku-20240307") {
|
||||||
this.client = new Anthropic({
|
this.client = new Anthropic({
|
||||||
apiKey: apiKey || process.env.ANTHROPIC_API_KEY,
|
apiKey: apiKey || process.env.ANTHROPIC_API_KEY,
|
||||||
});
|
});
|
||||||
this.model = model;
|
this.model = model;
|
||||||
}
|
}
|
||||||
|
|
||||||
async processMessage(message: string, context: AIContext): Promise<AIResponse> {
|
async processMessage(
|
||||||
throw new Error('Not implemented');
|
message: string,
|
||||||
|
context: AIContext,
|
||||||
|
): Promise<AIResponse> {
|
||||||
|
throw new Error("Not implemented");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
export * from './ClaudeAdapter';
|
export * from "./ClaudeAdapter";
|
||||||
|
|||||||
@@ -1,28 +1,35 @@
|
|||||||
import express from 'express';
|
import express from "express";
|
||||||
import mongoose from 'mongoose';
|
import mongoose from "mongoose";
|
||||||
import 'dotenv/config'
|
import "dotenv/config";
|
||||||
|
|
||||||
import { createRoutes } from './routes';
|
import { createRoutes } from "./routes";
|
||||||
import { AuthController, ChatController, EventController } from './controllers';
|
import { AuthController, ChatController, EventController } from "./controllers";
|
||||||
import { AuthService, ChatService, EventService } from './services';
|
import { AuthService, ChatService, EventService } from "./services";
|
||||||
import { MongoUserRepository, MongoEventRepository, MongoChatRepository } from './repositories';
|
import {
|
||||||
import { ClaudeAdapter } from './ai';
|
MongoUserRepository,
|
||||||
|
MongoEventRepository,
|
||||||
|
MongoChatRepository,
|
||||||
|
} from "./repositories";
|
||||||
|
import { ClaudeAdapter } from "./ai";
|
||||||
|
|
||||||
const app = express();
|
const app = express();
|
||||||
const port = process.env.PORT || 3000;
|
const port = process.env.PORT || 3000;
|
||||||
const mongoUri = process.env.MONGODB_URI || 'mongodb://localhost:27017/caldav';
|
const mongoUri = process.env.MONGODB_URI || "mongodb://localhost:27017/caldav";
|
||||||
|
|
||||||
// Middleware
|
// Middleware
|
||||||
app.use(express.json());
|
app.use(express.json());
|
||||||
|
|
||||||
// CORS - only needed for web browser development
|
// CORS - only needed for web browser development
|
||||||
// Native mobile apps don't send Origin headers and aren't affected by CORS
|
// Native mobile apps don't send Origin headers and aren't affected by CORS
|
||||||
if (process.env.NODE_ENV !== 'production') {
|
if (process.env.NODE_ENV !== "production") {
|
||||||
app.use((req, res, next) => {
|
app.use((req, res, next) => {
|
||||||
res.header('Access-Control-Allow-Origin', '*');
|
res.header("Access-Control-Allow-Origin", "*");
|
||||||
res.header('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS');
|
res.header(
|
||||||
res.header('Access-Control-Allow-Headers', 'Content-Type, Authorization');
|
"Access-Control-Allow-Methods",
|
||||||
if (req.method === 'OPTIONS') {
|
"GET, POST, PUT, DELETE, OPTIONS",
|
||||||
|
);
|
||||||
|
res.header("Access-Control-Allow-Headers", "Content-Type, Authorization");
|
||||||
|
if (req.method === "OPTIONS") {
|
||||||
res.sendStatus(200);
|
res.sendStatus(200);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -49,34 +56,37 @@ const chatController = new ChatController(chatService);
|
|||||||
const eventController = new EventController(eventService);
|
const eventController = new EventController(eventService);
|
||||||
|
|
||||||
// Setup routes
|
// Setup routes
|
||||||
app.use('/api', createRoutes({
|
app.use(
|
||||||
authController,
|
"/api",
|
||||||
chatController,
|
createRoutes({
|
||||||
eventController,
|
authController,
|
||||||
}));
|
chatController,
|
||||||
|
eventController,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
// Health check
|
// Health check
|
||||||
app.get('/health', (_, res) => {
|
app.get("/health", (_, res) => {
|
||||||
res.json({ status: 'ok' });
|
res.json({ status: "ok" });
|
||||||
});
|
});
|
||||||
|
|
||||||
// AI Test endpoint (for development only)
|
// AI Test endpoint (for development only)
|
||||||
app.post('/api/ai/test', async (req, res) => {
|
app.post("/api/ai/test", async (req, res) => {
|
||||||
try {
|
try {
|
||||||
const { message } = req.body;
|
const { message } = req.body;
|
||||||
if (!message) {
|
if (!message) {
|
||||||
res.status(400).json({ error: 'message is required' });
|
res.status(400).json({ error: "message is required" });
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const result = await aiProvider.processMessage(message, {
|
const result = await aiProvider.processMessage(message, {
|
||||||
userId: 'test-user',
|
userId: "test-user",
|
||||||
conversationHistory: [],
|
conversationHistory: [],
|
||||||
existingEvents: [],
|
existingEvents: [],
|
||||||
currentDate: new Date(),
|
currentDate: new Date(),
|
||||||
});
|
});
|
||||||
res.json(result);
|
res.json(result);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('AI test error:', error);
|
console.error("AI test error:", error);
|
||||||
res.status(500).json({ error: String(error) });
|
res.status(500).json({ error: String(error) });
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -85,13 +95,13 @@ app.post('/api/ai/test', async (req, res) => {
|
|||||||
async function start() {
|
async function start() {
|
||||||
try {
|
try {
|
||||||
await mongoose.connect(mongoUri);
|
await mongoose.connect(mongoUri);
|
||||||
console.log('Connected to MongoDB');
|
console.log("Connected to MongoDB");
|
||||||
|
|
||||||
app.listen(port, () => {
|
app.listen(port, () => {
|
||||||
console.log(`Server running on port ${port}`);
|
console.log(`Server running on port ${port}`);
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Failed to start server:', error);
|
console.error("Failed to start server:", error);
|
||||||
process.exit(1);
|
process.exit(1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { Request, Response } from 'express';
|
import { Request, Response } from "express";
|
||||||
import { AuthService } from '../services';
|
import { AuthService } from "../services";
|
||||||
|
|
||||||
export class AuthController {
|
export class AuthController {
|
||||||
constructor(private authService: AuthService) {}
|
constructor(private authService: AuthService) {}
|
||||||
@@ -23,10 +23,10 @@ export class AuthController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async refresh(req: Request, res: Response): Promise<void> {
|
async refresh(req: Request, res: Response): Promise<void> {
|
||||||
throw new Error('Not implemented');
|
throw new Error("Not implemented");
|
||||||
}
|
}
|
||||||
|
|
||||||
async logout(req: Request, res: Response): Promise<void> {
|
async logout(req: Request, res: Response): Promise<void> {
|
||||||
throw new Error('Not implemented');
|
throw new Error("Not implemented");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,12 @@
|
|||||||
import { Response } from 'express';
|
import { Response } from "express";
|
||||||
import { SendMessageDTO, CreateEventDTO, UpdateEventDTO, EventAction } from '@caldav/shared';
|
import {
|
||||||
import { ChatService } from '../services';
|
SendMessageDTO,
|
||||||
import { AuthenticatedRequest } from '../middleware';
|
CreateEventDTO,
|
||||||
|
UpdateEventDTO,
|
||||||
|
EventAction,
|
||||||
|
} from "@caldav/shared";
|
||||||
|
import { ChatService } from "../services";
|
||||||
|
import { AuthenticatedRequest } from "../middleware";
|
||||||
|
|
||||||
export class ChatController {
|
export class ChatController {
|
||||||
constructor(private chatService: ChatService) {}
|
constructor(private chatService: ChatService) {}
|
||||||
@@ -13,7 +18,7 @@ export class ChatController {
|
|||||||
const response = await this.chatService.processMessage(userId, data);
|
const response = await this.chatService.processMessage(userId, data);
|
||||||
res.json(response);
|
res.json(response);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
res.status(500).json({ error: 'Failed to process message' });
|
res.status(500).json({ error: "Failed to process message" });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -34,11 +39,11 @@ export class ChatController {
|
|||||||
action,
|
action,
|
||||||
event,
|
event,
|
||||||
eventId,
|
eventId,
|
||||||
updates
|
updates,
|
||||||
);
|
);
|
||||||
res.json(response);
|
res.json(response);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
res.status(500).json({ error: 'Failed to confirm event' });
|
res.status(500).json({ error: "Failed to confirm event" });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -46,18 +51,28 @@ export class ChatController {
|
|||||||
try {
|
try {
|
||||||
const userId = req.user!.userId;
|
const userId = req.user!.userId;
|
||||||
const { conversationId, messageId } = req.params;
|
const { conversationId, messageId } = req.params;
|
||||||
const response = await this.chatService.rejectEvent(userId, conversationId, messageId);
|
const response = await this.chatService.rejectEvent(
|
||||||
|
userId,
|
||||||
|
conversationId,
|
||||||
|
messageId,
|
||||||
|
);
|
||||||
res.json(response);
|
res.json(response);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
res.status(500).json({ error: 'Failed to reject event' });
|
res.status(500).json({ error: "Failed to reject event" });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async getConversations(req: AuthenticatedRequest, res: Response): Promise<void> {
|
async getConversations(
|
||||||
throw new Error('Not implemented');
|
req: AuthenticatedRequest,
|
||||||
|
res: Response,
|
||||||
|
): Promise<void> {
|
||||||
|
throw new Error("Not implemented");
|
||||||
}
|
}
|
||||||
|
|
||||||
async getConversation(req: AuthenticatedRequest, res: Response): Promise<void> {
|
async getConversation(
|
||||||
throw new Error('Not implemented');
|
req: AuthenticatedRequest,
|
||||||
|
res: Response,
|
||||||
|
): Promise<void> {
|
||||||
|
throw new Error("Not implemented");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,31 +1,34 @@
|
|||||||
import { Response } from 'express';
|
import { Response } from "express";
|
||||||
import { EventService } from '../services';
|
import { EventService } from "../services";
|
||||||
import { AuthenticatedRequest } from '../middleware';
|
import { AuthenticatedRequest } from "../middleware";
|
||||||
|
|
||||||
export class EventController {
|
export class EventController {
|
||||||
constructor(private eventService: EventService) {}
|
constructor(private eventService: EventService) {}
|
||||||
|
|
||||||
async create(req: AuthenticatedRequest, res: Response): Promise<void> {
|
async create(req: AuthenticatedRequest, res: Response): Promise<void> {
|
||||||
throw new Error('Not implemented');
|
throw new Error("Not implemented");
|
||||||
}
|
}
|
||||||
|
|
||||||
async getById(req: AuthenticatedRequest, res: Response): Promise<void> {
|
async getById(req: AuthenticatedRequest, res: Response): Promise<void> {
|
||||||
throw new Error('Not implemented');
|
throw new Error("Not implemented");
|
||||||
}
|
}
|
||||||
|
|
||||||
async getAll(req: AuthenticatedRequest, res: Response): Promise<void> {
|
async getAll(req: AuthenticatedRequest, res: Response): Promise<void> {
|
||||||
throw new Error('Not implemented');
|
throw new Error("Not implemented");
|
||||||
}
|
}
|
||||||
|
|
||||||
async getByDateRange(req: AuthenticatedRequest, res: Response): Promise<void> {
|
async getByDateRange(
|
||||||
throw new Error('Not implemented');
|
req: AuthenticatedRequest,
|
||||||
|
res: Response,
|
||||||
|
): Promise<void> {
|
||||||
|
throw new Error("Not implemented");
|
||||||
}
|
}
|
||||||
|
|
||||||
async update(req: AuthenticatedRequest, res: Response): Promise<void> {
|
async update(req: AuthenticatedRequest, res: Response): Promise<void> {
|
||||||
throw new Error('Not implemented');
|
throw new Error("Not implemented");
|
||||||
}
|
}
|
||||||
|
|
||||||
async delete(req: AuthenticatedRequest, res: Response): Promise<void> {
|
async delete(req: AuthenticatedRequest, res: Response): Promise<void> {
|
||||||
throw new Error('Not implemented');
|
throw new Error("Not implemented");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,3 @@
|
|||||||
export * from './AuthController';
|
export * from "./AuthController";
|
||||||
export * from './ChatController';
|
export * from "./ChatController";
|
||||||
export * from './EventController';
|
export * from "./EventController";
|
||||||
|
|||||||
@@ -1,16 +1,20 @@
|
|||||||
import { Request, Response, NextFunction } from 'express';
|
import { Request, Response, NextFunction } from "express";
|
||||||
import { verifyToken, TokenPayload } from '../utils/jwt';
|
import { verifyToken, TokenPayload } from "../utils/jwt";
|
||||||
|
|
||||||
export interface AuthenticatedRequest extends Request {
|
export interface AuthenticatedRequest extends Request {
|
||||||
user?: TokenPayload;
|
user?: TokenPayload;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function authenticate(req: AuthenticatedRequest, res: Response, next: NextFunction): void {
|
export function authenticate(
|
||||||
|
req: AuthenticatedRequest,
|
||||||
|
res: Response,
|
||||||
|
next: NextFunction,
|
||||||
|
): void {
|
||||||
// TODO: Implement real JWT verification
|
// TODO: Implement real JWT verification
|
||||||
// Fake user for testing purposes
|
// Fake user for testing purposes
|
||||||
req.user = {
|
req.user = {
|
||||||
userId: 'fake-user-id',
|
userId: "fake-user-id",
|
||||||
email: 'test@example.com',
|
email: "test@example.com",
|
||||||
};
|
};
|
||||||
next();
|
next();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
export * from './AuthMiddleware';
|
export * from "./AuthMiddleware";
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
export * from './mongo';
|
export * from "./mongo";
|
||||||
|
|||||||
@@ -1,23 +1,34 @@
|
|||||||
import { ChatMessage, Conversation, CreateMessageDTO, GetMessagesOptions } from '@caldav/shared';
|
import {
|
||||||
import { ChatRepository } from '../../services/interfaces';
|
ChatMessage,
|
||||||
import { ChatMessageModel, ConversationModel } from './models';
|
Conversation,
|
||||||
|
CreateMessageDTO,
|
||||||
|
GetMessagesOptions,
|
||||||
|
} from "@caldav/shared";
|
||||||
|
import { ChatRepository } from "../../services/interfaces";
|
||||||
|
import { ChatMessageModel, ConversationModel } from "./models";
|
||||||
|
|
||||||
export class MongoChatRepository implements ChatRepository {
|
export class MongoChatRepository implements ChatRepository {
|
||||||
// Conversations
|
// Conversations
|
||||||
async getConversationsByUser(userId: string): Promise<Conversation[]> {
|
async getConversationsByUser(userId: string): Promise<Conversation[]> {
|
||||||
throw new Error('Not implemented');
|
throw new Error("Not implemented");
|
||||||
}
|
}
|
||||||
|
|
||||||
async createConversation(userId: string): Promise<Conversation> {
|
async createConversation(userId: string): Promise<Conversation> {
|
||||||
throw new Error('Not implemented');
|
throw new Error("Not implemented");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Messages (cursor-based pagination)
|
// Messages (cursor-based pagination)
|
||||||
async getMessages(conversationId: string, options?: GetMessagesOptions): Promise<ChatMessage[]> {
|
async getMessages(
|
||||||
throw new Error('Not implemented');
|
conversationId: string,
|
||||||
|
options?: GetMessagesOptions,
|
||||||
|
): Promise<ChatMessage[]> {
|
||||||
|
throw new Error("Not implemented");
|
||||||
}
|
}
|
||||||
|
|
||||||
async createMessage(conversationId: string, message: CreateMessageDTO): Promise<ChatMessage> {
|
async createMessage(
|
||||||
throw new Error('Not implemented');
|
conversationId: string,
|
||||||
|
message: CreateMessageDTO,
|
||||||
|
): Promise<ChatMessage> {
|
||||||
|
throw new Error("Not implemented");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { CalendarEvent, CreateEventDTO, UpdateEventDTO } from '@caldav/shared';
|
import { CalendarEvent, CreateEventDTO, UpdateEventDTO } from "@caldav/shared";
|
||||||
import { EventRepository } from '../../services/interfaces';
|
import { EventRepository } from "../../services/interfaces";
|
||||||
import { EventModel } from './models';
|
import { EventModel } from "./models";
|
||||||
|
|
||||||
export class MongoEventRepository implements EventRepository {
|
export class MongoEventRepository implements EventRepository {
|
||||||
async findById(id: string): Promise<CalendarEvent | null> {
|
async findById(id: string): Promise<CalendarEvent | null> {
|
||||||
@@ -11,15 +11,19 @@ export class MongoEventRepository implements EventRepository {
|
|||||||
|
|
||||||
async findByUserId(userId: string): Promise<CalendarEvent[]> {
|
async findByUserId(userId: string): Promise<CalendarEvent[]> {
|
||||||
const events = await EventModel.find({ userId }).sort({ startTime: 1 });
|
const events = await EventModel.find({ userId }).sort({ startTime: 1 });
|
||||||
return events.map(e => e.toJSON() as unknown as CalendarEvent);
|
return events.map((e) => e.toJSON() as unknown as CalendarEvent);
|
||||||
}
|
}
|
||||||
|
|
||||||
async findByDateRange(userId: string, startDate: Date, endDate: Date): Promise<CalendarEvent[]> {
|
async findByDateRange(
|
||||||
|
userId: string,
|
||||||
|
startDate: Date,
|
||||||
|
endDate: Date,
|
||||||
|
): Promise<CalendarEvent[]> {
|
||||||
const events = await EventModel.find({
|
const events = await EventModel.find({
|
||||||
userId,
|
userId,
|
||||||
startTime: { $gte: startDate, $lte: endDate }
|
startTime: { $gte: startDate, $lte: endDate },
|
||||||
}).sort({ startTime: 1 });
|
}).sort({ startTime: 1 });
|
||||||
return events.map(e => e.toJSON() as unknown as CalendarEvent);
|
return events.map((e) => e.toJSON() as unknown as CalendarEvent);
|
||||||
}
|
}
|
||||||
|
|
||||||
async create(userId: string, data: CreateEventDTO): Promise<CalendarEvent> {
|
async create(userId: string, data: CreateEventDTO): Promise<CalendarEvent> {
|
||||||
@@ -28,7 +32,10 @@ export class MongoEventRepository implements EventRepository {
|
|||||||
return event.toJSON() as unknown as CalendarEvent;
|
return event.toJSON() as unknown as CalendarEvent;
|
||||||
}
|
}
|
||||||
|
|
||||||
async update(id: string, data: UpdateEventDTO): Promise<CalendarEvent | null> {
|
async update(
|
||||||
|
id: string,
|
||||||
|
data: UpdateEventDTO,
|
||||||
|
): Promise<CalendarEvent | null> {
|
||||||
const event = await EventModel.findByIdAndUpdate(id, data, { new: true });
|
const event = await EventModel.findByIdAndUpdate(id, data, { new: true });
|
||||||
if (!event) return null;
|
if (!event) return null;
|
||||||
return event.toJSON() as unknown as CalendarEvent;
|
return event.toJSON() as unknown as CalendarEvent;
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
import { User } from '@caldav/shared';
|
import { User } from "@caldav/shared";
|
||||||
import { UserRepository, CreateUserData } from '../../services/interfaces';
|
import { UserRepository, CreateUserData } from "../../services/interfaces";
|
||||||
import { UserModel } from './models';
|
import { UserModel } from "./models";
|
||||||
|
|
||||||
export class MongoUserRepository implements UserRepository {
|
export class MongoUserRepository implements UserRepository {
|
||||||
async findById(id: string): Promise<User | null> {
|
async findById(id: string): Promise<User | null> {
|
||||||
throw new Error('Not implemented');
|
throw new Error("Not implemented");
|
||||||
}
|
}
|
||||||
|
|
||||||
async findByEmail(email: string): Promise<User | null> {
|
async findByEmail(email: string): Promise<User | null> {
|
||||||
|
|||||||
@@ -1,3 +1,3 @@
|
|||||||
export * from './MongoUserRepository';
|
export * from "./MongoUserRepository";
|
||||||
export * from './MongoEventRepository';
|
export * from "./MongoEventRepository";
|
||||||
export * from './MongoChatRepository';
|
export * from "./MongoChatRepository";
|
||||||
|
|||||||
@@ -1,11 +1,18 @@
|
|||||||
import mongoose, { Schema, Document, Model } from 'mongoose';
|
import mongoose, { Schema, Document, Model } from "mongoose";
|
||||||
import { ChatMessage, Conversation, CreateEventDTO, UpdateEventDTO, ProposedEventChange } from '@caldav/shared';
|
import {
|
||||||
import { IdVirtual } from './types';
|
ChatMessage,
|
||||||
|
Conversation,
|
||||||
|
CreateEventDTO,
|
||||||
|
UpdateEventDTO,
|
||||||
|
ProposedEventChange,
|
||||||
|
} from "@caldav/shared";
|
||||||
|
import { IdVirtual } from "./types";
|
||||||
|
|
||||||
export interface ChatMessageDocument extends Omit<ChatMessage, 'id'>, Document {
|
export interface ChatMessageDocument extends Omit<ChatMessage, "id">, Document {
|
||||||
toJSON(): ChatMessage;
|
toJSON(): ChatMessage;
|
||||||
}
|
}
|
||||||
export interface ConversationDocument extends Omit<Conversation, 'id'>, Document {
|
export interface ConversationDocument
|
||||||
|
extends Omit<Conversation, "id">, Document {
|
||||||
toJSON(): Conversation;
|
toJSON(): Conversation;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -19,7 +26,7 @@ const EventSchema = new Schema<CreateEventDTO>(
|
|||||||
isRecurring: { type: Boolean },
|
isRecurring: { type: Boolean },
|
||||||
recurrenceRule: { type: String },
|
recurrenceRule: { type: String },
|
||||||
},
|
},
|
||||||
{ _id: false }
|
{ _id: false },
|
||||||
);
|
);
|
||||||
|
|
||||||
const UpdatesSchema = new Schema<UpdateEventDTO>(
|
const UpdatesSchema = new Schema<UpdateEventDTO>(
|
||||||
@@ -32,20 +39,30 @@ const UpdatesSchema = new Schema<UpdateEventDTO>(
|
|||||||
isRecurring: { type: Boolean },
|
isRecurring: { type: Boolean },
|
||||||
recurrenceRule: { type: String },
|
recurrenceRule: { type: String },
|
||||||
},
|
},
|
||||||
{ _id: false }
|
{ _id: false },
|
||||||
);
|
);
|
||||||
|
|
||||||
const ProposedChangeSchema = new Schema<ProposedEventChange>(
|
const ProposedChangeSchema = new Schema<ProposedEventChange>(
|
||||||
{
|
{
|
||||||
action: { type: String, enum: ['create', 'update', 'delete'], required: true },
|
action: {
|
||||||
|
type: String,
|
||||||
|
enum: ["create", "update", "delete"],
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
eventId: { type: String },
|
eventId: { type: String },
|
||||||
event: { type: EventSchema },
|
event: { type: EventSchema },
|
||||||
updates: { type: UpdatesSchema },
|
updates: { type: UpdatesSchema },
|
||||||
},
|
},
|
||||||
{ _id: false }
|
{ _id: false },
|
||||||
);
|
);
|
||||||
|
|
||||||
const ChatMessageSchema = new Schema<ChatMessageDocument, Model<ChatMessageDocument, {}, {}, IdVirtual>, {}, {}, IdVirtual>(
|
const ChatMessageSchema = new Schema<
|
||||||
|
ChatMessageDocument,
|
||||||
|
Model<ChatMessageDocument, {}, {}, IdVirtual>,
|
||||||
|
{},
|
||||||
|
{},
|
||||||
|
IdVirtual
|
||||||
|
>(
|
||||||
{
|
{
|
||||||
conversationId: {
|
conversationId: {
|
||||||
type: String,
|
type: String,
|
||||||
@@ -53,7 +70,7 @@ const ChatMessageSchema = new Schema<ChatMessageDocument, Model<ChatMessageDocum
|
|||||||
},
|
},
|
||||||
sender: {
|
sender: {
|
||||||
type: String,
|
type: String,
|
||||||
enum: ['user', 'assistant'],
|
enum: ["user", "assistant"],
|
||||||
required: true,
|
required: true,
|
||||||
},
|
},
|
||||||
content: {
|
content: {
|
||||||
@@ -81,10 +98,16 @@ const ChatMessageSchema = new Schema<ChatMessageDocument, Model<ChatMessageDocum
|
|||||||
return ret;
|
return ret;
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
const ConversationSchema = new Schema<ConversationDocument, Model<ConversationDocument, {}, {}, IdVirtual>, {}, {}, IdVirtual>(
|
const ConversationSchema = new Schema<
|
||||||
|
ConversationDocument,
|
||||||
|
Model<ConversationDocument, {}, {}, IdVirtual>,
|
||||||
|
{},
|
||||||
|
{},
|
||||||
|
IdVirtual
|
||||||
|
>(
|
||||||
{
|
{
|
||||||
userId: {
|
userId: {
|
||||||
type: String,
|
type: String,
|
||||||
@@ -109,8 +132,14 @@ const ConversationSchema = new Schema<ConversationDocument, Model<ConversationDo
|
|||||||
return ret;
|
return ret;
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
export const ChatMessageModel = mongoose.model<ChatMessageDocument>('ChatMessage', ChatMessageSchema);
|
export const ChatMessageModel = mongoose.model<ChatMessageDocument>(
|
||||||
export const ConversationModel = mongoose.model<ConversationDocument>('Conversation', ConversationSchema);
|
"ChatMessage",
|
||||||
|
ChatMessageSchema,
|
||||||
|
);
|
||||||
|
export const ConversationModel = mongoose.model<ConversationDocument>(
|
||||||
|
"Conversation",
|
||||||
|
ConversationSchema,
|
||||||
|
);
|
||||||
|
|||||||
@@ -1,12 +1,18 @@
|
|||||||
import mongoose, { Schema, Document, Model } from 'mongoose';
|
import mongoose, { Schema, Document, Model } from "mongoose";
|
||||||
import { CalendarEvent } from '@caldav/shared';
|
import { CalendarEvent } from "@caldav/shared";
|
||||||
import { IdVirtual } from './types';
|
import { IdVirtual } from "./types";
|
||||||
|
|
||||||
export interface EventDocument extends Omit<CalendarEvent, 'id'>, Document {
|
export interface EventDocument extends Omit<CalendarEvent, "id">, Document {
|
||||||
toJSON(): CalendarEvent;
|
toJSON(): CalendarEvent;
|
||||||
}
|
}
|
||||||
|
|
||||||
const EventSchema = new Schema<EventDocument, Model<EventDocument, {}, {}, IdVirtual>, {}, {}, IdVirtual>(
|
const EventSchema = new Schema<
|
||||||
|
EventDocument,
|
||||||
|
Model<EventDocument, {}, {}, IdVirtual>,
|
||||||
|
{},
|
||||||
|
{},
|
||||||
|
IdVirtual
|
||||||
|
>(
|
||||||
{
|
{
|
||||||
userId: {
|
userId: {
|
||||||
type: String,
|
type: String,
|
||||||
@@ -58,9 +64,9 @@ const EventSchema = new Schema<EventDocument, Model<EventDocument, {}, {}, IdVir
|
|||||||
return ret;
|
return ret;
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
EventSchema.index({ userId: 1, startTime: 1, endTime: 1 });
|
EventSchema.index({ userId: 1, startTime: 1, endTime: 1 });
|
||||||
|
|
||||||
export const EventModel = mongoose.model<EventDocument>('Event', EventSchema);
|
export const EventModel = mongoose.model<EventDocument>("Event", EventSchema);
|
||||||
|
|||||||
@@ -1,12 +1,18 @@
|
|||||||
import mongoose, { Schema, Document, Model } from 'mongoose';
|
import mongoose, { Schema, Document, Model } from "mongoose";
|
||||||
import { User } from '@caldav/shared';
|
import { User } from "@caldav/shared";
|
||||||
import { IdVirtual } from './types';
|
import { IdVirtual } from "./types";
|
||||||
|
|
||||||
export interface UserDocument extends Omit<User, 'id'>, Document {
|
export interface UserDocument extends Omit<User, "id">, Document {
|
||||||
toJSON(): User;
|
toJSON(): User;
|
||||||
}
|
}
|
||||||
|
|
||||||
const UserSchema = new Schema<UserDocument, Model<UserDocument, {}, {}, IdVirtual>, {}, {}, IdVirtual>(
|
const UserSchema = new Schema<
|
||||||
|
UserDocument,
|
||||||
|
Model<UserDocument, {}, {}, IdVirtual>,
|
||||||
|
{},
|
||||||
|
{},
|
||||||
|
IdVirtual
|
||||||
|
>(
|
||||||
{
|
{
|
||||||
email: {
|
email: {
|
||||||
type: String,
|
type: String,
|
||||||
@@ -43,7 +49,7 @@ const UserSchema = new Schema<UserDocument, Model<UserDocument, {}, {}, IdVirtua
|
|||||||
return ret;
|
return ret;
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
export const UserModel = mongoose.model<UserDocument>('User', UserSchema);
|
export const UserModel = mongoose.model<UserDocument>("User", UserSchema);
|
||||||
|
|||||||
@@ -1,3 +1,3 @@
|
|||||||
export * from './UserModel';
|
export * from "./UserModel";
|
||||||
export * from './EventModel';
|
export * from "./EventModel";
|
||||||
export * from './ChatModel';
|
export * from "./ChatModel";
|
||||||
|
|||||||
@@ -1,13 +1,13 @@
|
|||||||
import { Router } from 'express';
|
import { Router } from "express";
|
||||||
import { AuthController } from '../controllers';
|
import { AuthController } from "../controllers";
|
||||||
|
|
||||||
export function createAuthRoutes(authController: AuthController): Router {
|
export function createAuthRoutes(authController: AuthController): Router {
|
||||||
const router = Router();
|
const router = Router();
|
||||||
|
|
||||||
router.post('/login', (req, res) => authController.login(req, res));
|
router.post("/login", (req, res) => authController.login(req, res));
|
||||||
router.post('/register', (req, res) => authController.register(req, res));
|
router.post("/register", (req, res) => authController.register(req, res));
|
||||||
router.post('/refresh', (req, res) => authController.refresh(req, res));
|
router.post("/refresh", (req, res) => authController.refresh(req, res));
|
||||||
router.post('/logout', (req, res) => authController.logout(req, res));
|
router.post("/logout", (req, res) => authController.logout(req, res));
|
||||||
|
|
||||||
return router;
|
return router;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,17 +1,25 @@
|
|||||||
import { Router } from 'express';
|
import { Router } from "express";
|
||||||
import { ChatController } from '../controllers';
|
import { ChatController } from "../controllers";
|
||||||
import { authenticate } from '../middleware';
|
import { authenticate } from "../middleware";
|
||||||
|
|
||||||
export function createChatRoutes(chatController: ChatController): Router {
|
export function createChatRoutes(chatController: ChatController): Router {
|
||||||
const router = Router();
|
const router = Router();
|
||||||
|
|
||||||
router.use(authenticate);
|
router.use(authenticate);
|
||||||
|
|
||||||
router.post('/message', (req, res) => chatController.sendMessage(req, res));
|
router.post("/message", (req, res) => chatController.sendMessage(req, res));
|
||||||
router.post('/confirm/:conversationId/:messageId', (req, res) => chatController.confirmEvent(req, res));
|
router.post("/confirm/:conversationId/:messageId", (req, res) =>
|
||||||
router.post('/reject/:conversationId/:messageId', (req, res) => chatController.rejectEvent(req, res));
|
chatController.confirmEvent(req, res),
|
||||||
router.get('/conversations', (req, res) => chatController.getConversations(req, res));
|
);
|
||||||
router.get('/conversations/:id', (req, res) => chatController.getConversation(req, res));
|
router.post("/reject/:conversationId/:messageId", (req, res) =>
|
||||||
|
chatController.rejectEvent(req, res),
|
||||||
|
);
|
||||||
|
router.get("/conversations", (req, res) =>
|
||||||
|
chatController.getConversations(req, res),
|
||||||
|
);
|
||||||
|
router.get("/conversations/:id", (req, res) =>
|
||||||
|
chatController.getConversation(req, res),
|
||||||
|
);
|
||||||
|
|
||||||
return router;
|
return router;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,18 +1,18 @@
|
|||||||
import { Router } from 'express';
|
import { Router } from "express";
|
||||||
import { EventController } from '../controllers';
|
import { EventController } from "../controllers";
|
||||||
import { authenticate } from '../middleware';
|
import { authenticate } from "../middleware";
|
||||||
|
|
||||||
export function createEventRoutes(eventController: EventController): Router {
|
export function createEventRoutes(eventController: EventController): Router {
|
||||||
const router = Router();
|
const router = Router();
|
||||||
|
|
||||||
router.use(authenticate);
|
router.use(authenticate);
|
||||||
|
|
||||||
router.post('/', (req, res) => eventController.create(req, res));
|
router.post("/", (req, res) => eventController.create(req, res));
|
||||||
router.get('/', (req, res) => eventController.getAll(req, res));
|
router.get("/", (req, res) => eventController.getAll(req, res));
|
||||||
router.get('/range', (req, res) => eventController.getByDateRange(req, res));
|
router.get("/range", (req, res) => eventController.getByDateRange(req, res));
|
||||||
router.get('/:id', (req, res) => eventController.getById(req, res));
|
router.get("/:id", (req, res) => eventController.getById(req, res));
|
||||||
router.put('/:id', (req, res) => eventController.update(req, res));
|
router.put("/:id", (req, res) => eventController.update(req, res));
|
||||||
router.delete('/:id', (req, res) => eventController.delete(req, res));
|
router.delete("/:id", (req, res) => eventController.delete(req, res));
|
||||||
|
|
||||||
return router;
|
return router;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,8 +1,12 @@
|
|||||||
import { Router } from 'express';
|
import { Router } from "express";
|
||||||
import { createAuthRoutes } from './auth.routes';
|
import { createAuthRoutes } from "./auth.routes";
|
||||||
import { createChatRoutes } from './chat.routes';
|
import { createChatRoutes } from "./chat.routes";
|
||||||
import { createEventRoutes } from './event.routes';
|
import { createEventRoutes } from "./event.routes";
|
||||||
import { AuthController, ChatController, EventController } from '../controllers';
|
import {
|
||||||
|
AuthController,
|
||||||
|
ChatController,
|
||||||
|
EventController,
|
||||||
|
} from "../controllers";
|
||||||
|
|
||||||
export interface Controllers {
|
export interface Controllers {
|
||||||
authController: AuthController;
|
authController: AuthController;
|
||||||
@@ -13,13 +17,13 @@ export interface Controllers {
|
|||||||
export function createRoutes(controllers: Controllers): Router {
|
export function createRoutes(controllers: Controllers): Router {
|
||||||
const router = Router();
|
const router = Router();
|
||||||
|
|
||||||
router.use('/auth', createAuthRoutes(controllers.authController));
|
router.use("/auth", createAuthRoutes(controllers.authController));
|
||||||
router.use('/chat', createChatRoutes(controllers.chatController));
|
router.use("/chat", createChatRoutes(controllers.chatController));
|
||||||
router.use('/events', createEventRoutes(controllers.eventController));
|
router.use("/events", createEventRoutes(controllers.eventController));
|
||||||
|
|
||||||
return router;
|
return router;
|
||||||
}
|
}
|
||||||
|
|
||||||
export * from './auth.routes';
|
export * from "./auth.routes";
|
||||||
export * from './chat.routes';
|
export * from "./chat.routes";
|
||||||
export * from './event.routes';
|
export * from "./event.routes";
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { User, CreateUserDTO, LoginDTO, AuthResponse } from '@caldav/shared';
|
import { User, CreateUserDTO, LoginDTO, AuthResponse } from "@caldav/shared";
|
||||||
import { UserRepository } from './interfaces';
|
import { UserRepository } from "./interfaces";
|
||||||
import * as jwt from '../utils/jwt';
|
import * as jwt from "../utils/jwt";
|
||||||
import * as password from '../utils/password';
|
import * as password from "../utils/password";
|
||||||
|
|
||||||
export class AuthService {
|
export class AuthService {
|
||||||
constructor(private userRepo: UserRepository) {}
|
constructor(private userRepo: UserRepository) {}
|
||||||
@@ -9,21 +9,21 @@ export class AuthService {
|
|||||||
async login(data: LoginDTO): Promise<AuthResponse> {
|
async login(data: LoginDTO): Promise<AuthResponse> {
|
||||||
const user = await this.userRepo.findByEmail(data.email);
|
const user = await this.userRepo.findByEmail(data.email);
|
||||||
if (!user || !user.passwordHash) {
|
if (!user || !user.passwordHash) {
|
||||||
throw new Error('Invalid credentials');
|
throw new Error("Invalid credentials");
|
||||||
}
|
}
|
||||||
|
|
||||||
const isValid = await password.compare(data.password, user.passwordHash);
|
const isValid = await password.compare(data.password, user.passwordHash);
|
||||||
if (!isValid) {
|
if (!isValid) {
|
||||||
throw new Error('Invalid credentials');
|
throw new Error("Invalid credentials");
|
||||||
}
|
}
|
||||||
|
|
||||||
return { user, accessToken: '' };
|
return { user, accessToken: "" };
|
||||||
}
|
}
|
||||||
|
|
||||||
async register(data: CreateUserDTO): Promise<AuthResponse> {
|
async register(data: CreateUserDTO): Promise<AuthResponse> {
|
||||||
const existingUser = await this.userRepo.findByEmail(data.email);
|
const existingUser = await this.userRepo.findByEmail(data.email);
|
||||||
if (existingUser) {
|
if (existingUser) {
|
||||||
throw new Error('Email already exists');
|
throw new Error("Email already exists");
|
||||||
}
|
}
|
||||||
|
|
||||||
const passwordHash = await password.hash(data.password);
|
const passwordHash = await password.hash(data.password);
|
||||||
@@ -33,14 +33,14 @@ export class AuthService {
|
|||||||
passwordHash,
|
passwordHash,
|
||||||
});
|
});
|
||||||
|
|
||||||
return { user, accessToken: '' };
|
return { user, accessToken: "" };
|
||||||
}
|
}
|
||||||
|
|
||||||
async refreshToken(refreshToken: string): Promise<AuthResponse> {
|
async refreshToken(refreshToken: string): Promise<AuthResponse> {
|
||||||
throw new Error('Not implemented');
|
throw new Error("Not implemented");
|
||||||
}
|
}
|
||||||
|
|
||||||
async logout(userId: string): Promise<void> {
|
async logout(userId: string): Promise<void> {
|
||||||
throw new Error('Not implemented');
|
throw new Error("Not implemented");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,17 @@
|
|||||||
import { ChatMessage, ChatResponse, SendMessageDTO, ConversationSummary, GetMessagesOptions, ProposedEventChange, getDay, CreateEventDTO, UpdateEventDTO, EventAction } from '@caldav/shared';
|
import {
|
||||||
import { ChatRepository, EventRepository, AIProvider } from './interfaces';
|
ChatMessage,
|
||||||
import { getWeeksOverview, getMonthOverview } from '../utils/eventFormatters';
|
ChatResponse,
|
||||||
|
SendMessageDTO,
|
||||||
|
ConversationSummary,
|
||||||
|
GetMessagesOptions,
|
||||||
|
ProposedEventChange,
|
||||||
|
getDay,
|
||||||
|
CreateEventDTO,
|
||||||
|
UpdateEventDTO,
|
||||||
|
EventAction,
|
||||||
|
} from "@caldav/shared";
|
||||||
|
import { ChatRepository, EventRepository, AIProvider } from "./interfaces";
|
||||||
|
import { getWeeksOverview, getMonthOverview } from "../utils/eventFormatters";
|
||||||
|
|
||||||
type TestResponse = { content: string; proposedChange?: ProposedEventChange };
|
type TestResponse = { content: string; proposedChange?: ProposedEventChange };
|
||||||
|
|
||||||
@@ -9,139 +20,147 @@ let responseIndex = 8;
|
|||||||
|
|
||||||
// Static test responses (event proposals)
|
// Static test responses (event proposals)
|
||||||
const staticResponses: TestResponse[] = [
|
const staticResponses: TestResponse[] = [
|
||||||
// {{{
|
// {{{
|
||||||
// Response 0: Meeting mit Jens - next Friday 14:00
|
// Response 0: Meeting mit Jens - next Friday 14:00
|
||||||
{
|
{
|
||||||
content: "Alles klar! Ich erstelle dir einen Termin für das Meeting mit Jens am nächsten Freitag um 14:00 Uhr:",
|
content:
|
||||||
proposedChange: {
|
"Alles klar! Ich erstelle dir einen Termin für das Meeting mit Jens am nächsten Freitag um 14:00 Uhr:",
|
||||||
action: 'create',
|
proposedChange: {
|
||||||
event: {
|
action: "create",
|
||||||
title: "Meeting mit Jens",
|
event: {
|
||||||
startTime: getDay('Friday', 1, 14, 0),
|
title: "Meeting mit Jens",
|
||||||
endTime: getDay('Friday', 1, 15, 0),
|
startTime: getDay("Friday", 1, 14, 0),
|
||||||
description: "Arbeitstreffen",
|
endTime: getDay("Friday", 1, 15, 0),
|
||||||
}
|
description: "Arbeitstreffen",
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
// Response 1: Recurring event - every Saturday 10:00
|
},
|
||||||
{
|
// Response 1: Recurring event - every Saturday 10:00
|
||||||
content: "Verstanden! Ich erstelle einen wiederkehrenden Termin: Jeden Samstag um 10:00 Uhr Badezimmer putzen:",
|
{
|
||||||
proposedChange: {
|
content:
|
||||||
action: 'create',
|
"Verstanden! Ich erstelle einen wiederkehrenden Termin: Jeden Samstag um 10:00 Uhr Badezimmer putzen:",
|
||||||
event: {
|
proposedChange: {
|
||||||
title: "Badezimmer putzen",
|
action: "create",
|
||||||
startTime: getDay('Saturday', 1, 10, 0),
|
event: {
|
||||||
endTime: getDay('Saturday', 1, 11, 0),
|
title: "Badezimmer putzen",
|
||||||
isRecurring: true,
|
startTime: getDay("Saturday", 1, 10, 0),
|
||||||
recurrenceRule: "FREQ=WEEKLY;BYDAY=SA",
|
endTime: getDay("Saturday", 1, 11, 0),
|
||||||
}
|
isRecurring: true,
|
||||||
}
|
recurrenceRule: "FREQ=WEEKLY;BYDAY=SA",
|
||||||
|
},
|
||||||
},
|
},
|
||||||
// Response 2: 2-week overview (DYNAMIC - placeholder)
|
},
|
||||||
{ content: '' },
|
// Response 2: 2-week overview (DYNAMIC - placeholder)
|
||||||
// Response 3: Delete "Meeting mit Jens" (DYNAMIC - placeholder)
|
{ content: "" },
|
||||||
{ content: '' },
|
// Response 3: Delete "Meeting mit Jens" (DYNAMIC - placeholder)
|
||||||
// Response 4: Doctor appointment with description
|
{ content: "" },
|
||||||
{
|
// Response 4: Doctor appointment with description
|
||||||
content: "Ich habe dir einen Arzttermin eingetragen. Denk daran, deine Versichertenkarte mitzunehmen!",
|
{
|
||||||
proposedChange: {
|
content:
|
||||||
action: 'create',
|
"Ich habe dir einen Arzttermin eingetragen. Denk daran, deine Versichertenkarte mitzunehmen!",
|
||||||
event: {
|
proposedChange: {
|
||||||
title: "Arzttermin Dr. Müller",
|
action: "create",
|
||||||
startTime: getDay('Wednesday', 1, 9, 30),
|
event: {
|
||||||
endTime: getDay('Wednesday', 1, 10, 30),
|
title: "Arzttermin Dr. Müller",
|
||||||
description: "Routineuntersuchung - Versichertenkarte nicht vergessen",
|
startTime: getDay("Wednesday", 1, 9, 30),
|
||||||
}
|
endTime: getDay("Wednesday", 1, 10, 30),
|
||||||
}
|
description: "Routineuntersuchung - Versichertenkarte nicht vergessen",
|
||||||
|
},
|
||||||
},
|
},
|
||||||
// Response 5: Birthday - yearly recurring
|
},
|
||||||
{
|
// Response 5: Birthday - yearly recurring
|
||||||
content: "Geburtstage vergisst man leicht - aber nicht mit mir! Ich habe Mamas Geburtstag eingetragen:",
|
{
|
||||||
proposedChange: {
|
content:
|
||||||
action: 'create',
|
"Geburtstage vergisst man leicht - aber nicht mit mir! Ich habe Mamas Geburtstag eingetragen:",
|
||||||
event: {
|
proposedChange: {
|
||||||
title: "Mamas Geburtstag",
|
action: "create",
|
||||||
startTime: getDay('Thursday', 2, 0, 0),
|
event: {
|
||||||
endTime: getDay('Thursday', 2, 23, 59),
|
title: "Mamas Geburtstag",
|
||||||
isRecurring: true,
|
startTime: getDay("Thursday", 2, 0, 0),
|
||||||
recurrenceRule: "FREQ=YEARLY",
|
endTime: getDay("Thursday", 2, 23, 59),
|
||||||
}
|
isRecurring: true,
|
||||||
}
|
recurrenceRule: "FREQ=YEARLY",
|
||||||
|
},
|
||||||
},
|
},
|
||||||
// Response 6: Gym - recurring for 2 months (8 weeks)
|
},
|
||||||
{
|
// Response 6: Gym - recurring for 2 months (8 weeks)
|
||||||
content: "Perfekt! Ich habe dein Probetraining eingetragen - jeden Dienstag für die nächsten 2 Monate:",
|
{
|
||||||
proposedChange: {
|
content:
|
||||||
action: 'create',
|
"Perfekt! Ich habe dein Probetraining eingetragen - jeden Dienstag für die nächsten 2 Monate:",
|
||||||
event: {
|
proposedChange: {
|
||||||
title: "Fitnessstudio Probetraining",
|
action: "create",
|
||||||
startTime: getDay('Tuesday', 1, 18, 0),
|
event: {
|
||||||
endTime: getDay('Tuesday', 1, 19, 30),
|
title: "Fitnessstudio Probetraining",
|
||||||
isRecurring: true,
|
startTime: getDay("Tuesday", 1, 18, 0),
|
||||||
recurrenceRule: "FREQ=WEEKLY;BYDAY=TU;COUNT=8",
|
endTime: getDay("Tuesday", 1, 19, 30),
|
||||||
}
|
isRecurring: true,
|
||||||
}
|
recurrenceRule: "FREQ=WEEKLY;BYDAY=TU;COUNT=8",
|
||||||
|
},
|
||||||
},
|
},
|
||||||
// Response 7: 1-week overview (DYNAMIC - placeholder)
|
},
|
||||||
{ content: '' },
|
// Response 7: 1-week overview (DYNAMIC - placeholder)
|
||||||
// Response 8: Help response (text only)
|
{ content: "" },
|
||||||
{
|
// Response 8: Help response (text only)
|
||||||
content: "Ich bin dein Kalender-Assistent! Du kannst mir einfach sagen, welche Termine du erstellen, ändern oder löschen möchtest. Zum Beispiel:\n\n" +
|
{
|
||||||
"• \"Erstelle einen Termin für morgen um 15 Uhr\"\n" +
|
content:
|
||||||
"• \"Was habe ich nächste Woche vor?\"\n" +
|
"Ich bin dein Kalender-Assistent! Du kannst mir einfach sagen, welche Termine du erstellen, ändern oder löschen möchtest. Zum Beispiel:\n\n" +
|
||||||
"• \"Verschiebe das Meeting auf Donnerstag\"\n\n" +
|
'• "Erstelle einen Termin für morgen um 15 Uhr"\n' +
|
||||||
"Wie kann ich dir helfen?",
|
'• "Was habe ich nächste Woche vor?"\n' +
|
||||||
|
'• "Verschiebe das Meeting auf Donnerstag"\n\n' +
|
||||||
|
"Wie kann ich dir helfen?",
|
||||||
|
},
|
||||||
|
// Response 9: Phone call - short appointment
|
||||||
|
{
|
||||||
|
content:
|
||||||
|
"Alles klar! Ich habe das Telefonat mit deiner Mutter eingetragen:",
|
||||||
|
proposedChange: {
|
||||||
|
action: "create",
|
||||||
|
event: {
|
||||||
|
title: "Telefonat mit Mama",
|
||||||
|
startTime: getDay("Sunday", 0, 11, 0),
|
||||||
|
endTime: getDay("Sunday", 0, 11, 30),
|
||||||
|
},
|
||||||
},
|
},
|
||||||
// Response 9: Phone call - short appointment
|
},
|
||||||
{
|
// Response 10: Update "Telefonat mit Mama" +2 days (DYNAMIC - placeholder)
|
||||||
content: "Alles klar! Ich habe das Telefonat mit deiner Mutter eingetragen:",
|
{ content: "" },
|
||||||
proposedChange: {
|
// Response 11: Birthday party - evening event
|
||||||
action: 'create',
|
{
|
||||||
event: {
|
content: "Super! Die Geburtstagsfeier ist eingetragen. Viel Spaß!",
|
||||||
title: "Telefonat mit Mama",
|
proposedChange: {
|
||||||
startTime: getDay('Sunday', 0, 11, 0),
|
action: "create",
|
||||||
endTime: getDay('Sunday', 0, 11, 30),
|
event: {
|
||||||
}
|
title: "Geburtstagsfeier Lisa",
|
||||||
}
|
startTime: getDay("Saturday", 2, 19, 0),
|
||||||
|
endTime: getDay("Saturday", 2, 23, 0),
|
||||||
|
description: "Geschenk: Buch über Fotografie",
|
||||||
|
},
|
||||||
},
|
},
|
||||||
// Response 10: Update "Telefonat mit Mama" +2 days (DYNAMIC - placeholder)
|
},
|
||||||
{ content: '' },
|
// Response 12: Language course - limited to 8 weeks
|
||||||
// Response 11: Birthday party - evening event
|
{
|
||||||
{
|
content:
|
||||||
content: "Super! Die Geburtstagsfeier ist eingetragen. Viel Spaß!",
|
"Dein Spanischkurs ist eingetragen! Er läuft jeden Donnerstag für die nächsten 8 Wochen:",
|
||||||
proposedChange: {
|
proposedChange: {
|
||||||
action: 'create',
|
action: "create",
|
||||||
event: {
|
event: {
|
||||||
title: "Geburtstagsfeier Lisa",
|
title: "Spanischkurs VHS",
|
||||||
startTime: getDay('Saturday', 2, 19, 0),
|
startTime: getDay("Thursday", 1, 19, 0),
|
||||||
endTime: getDay('Saturday', 2, 23, 0),
|
endTime: getDay("Thursday", 1, 20, 30),
|
||||||
description: "Geschenk: Buch über Fotografie",
|
isRecurring: true,
|
||||||
}
|
recurrenceRule: "FREQ=WEEKLY;BYDAY=TH;COUNT=8",
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
// Response 12: Language course - limited to 8 weeks
|
},
|
||||||
{
|
// Response 13: Monthly overview (DYNAMIC - placeholder)
|
||||||
content: "Dein Spanischkurs ist eingetragen! Er läuft jeden Donnerstag für die nächsten 8 Wochen:",
|
{ content: "" },
|
||||||
proposedChange: {
|
// }}}
|
||||||
action: 'create',
|
|
||||||
event: {
|
|
||||||
title: "Spanischkurs VHS",
|
|
||||||
startTime: getDay('Thursday', 1, 19, 0),
|
|
||||||
endTime: getDay('Thursday', 1, 20, 30),
|
|
||||||
isRecurring: true,
|
|
||||||
recurrenceRule: "FREQ=WEEKLY;BYDAY=TH;COUNT=8",
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
// Response 13: Monthly overview (DYNAMIC - placeholder)
|
|
||||||
{ content: '' },
|
|
||||||
// }}}
|
|
||||||
];
|
];
|
||||||
|
|
||||||
async function getTestResponse(
|
async function getTestResponse(
|
||||||
index: number,
|
index: number,
|
||||||
eventRepo: EventRepository,
|
eventRepo: EventRepository,
|
||||||
userId: string
|
userId: string,
|
||||||
): Promise<TestResponse> {
|
): Promise<TestResponse> {
|
||||||
const responseIdx = index % staticResponses.length;
|
const responseIdx = index % staticResponses.length;
|
||||||
|
|
||||||
@@ -153,14 +172,15 @@ async function getTestResponse(
|
|||||||
if (responseIdx === 3) {
|
if (responseIdx === 3) {
|
||||||
// Delete "Meeting mit Jens"
|
// Delete "Meeting mit Jens"
|
||||||
const events = await eventRepo.findByUserId(userId);
|
const events = await eventRepo.findByUserId(userId);
|
||||||
const jensEvent = events.find(e => e.title === 'Meeting mit Jens');
|
const jensEvent = events.find((e) => e.title === "Meeting mit Jens");
|
||||||
if (jensEvent) {
|
if (jensEvent) {
|
||||||
return {
|
return {
|
||||||
content: "Alles klar, ich lösche den Termin 'Meeting mit Jens' für dich:",
|
content:
|
||||||
|
"Alles klar, ich lösche den Termin 'Meeting mit Jens' für dich:",
|
||||||
proposedChange: {
|
proposedChange: {
|
||||||
action: 'delete',
|
action: "delete",
|
||||||
eventId: jensEvent.id,
|
eventId: jensEvent.id,
|
||||||
}
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
return { content: "Ich konnte keinen Termin 'Meeting mit Jens' finden." };
|
return { content: "Ich konnte keinen Termin 'Meeting mit Jens' finden." };
|
||||||
@@ -173,16 +193,17 @@ async function getTestResponse(
|
|||||||
if (responseIdx === 10) {
|
if (responseIdx === 10) {
|
||||||
// Update "Telefonat mit Mama" +2 days
|
// Update "Telefonat mit Mama" +2 days
|
||||||
const events = await eventRepo.findByUserId(userId);
|
const events = await eventRepo.findByUserId(userId);
|
||||||
const mamaEvent = events.find(e => e.title === 'Telefonat mit Mama');
|
const mamaEvent = events.find((e) => e.title === "Telefonat mit Mama");
|
||||||
if (mamaEvent) {
|
if (mamaEvent) {
|
||||||
const newStart = new Date(mamaEvent.startTime);
|
const newStart = new Date(mamaEvent.startTime);
|
||||||
newStart.setDate(newStart.getDate() + 2);
|
newStart.setDate(newStart.getDate() + 2);
|
||||||
const newEnd = new Date(mamaEvent.endTime);
|
const newEnd = new Date(mamaEvent.endTime);
|
||||||
newEnd.setDate(newEnd.getDate() + 2);
|
newEnd.setDate(newEnd.getDate() + 2);
|
||||||
return {
|
return {
|
||||||
content: "Alles klar, ich verschiebe das Telefonat mit Mama um 2 Tage nach hinten:",
|
content:
|
||||||
|
"Alles klar, ich verschiebe das Telefonat mit Mama um 2 Tage nach hinten:",
|
||||||
proposedChange: {
|
proposedChange: {
|
||||||
action: 'update',
|
action: "update",
|
||||||
eventId: mamaEvent.id,
|
eventId: mamaEvent.id,
|
||||||
updates: { startTime: newStart, endTime: newEnd },
|
updates: { startTime: newStart, endTime: newEnd },
|
||||||
// Include event with new times for display
|
// Include event with new times for display
|
||||||
@@ -191,8 +212,8 @@ async function getTestResponse(
|
|||||||
startTime: newStart,
|
startTime: newStart,
|
||||||
endTime: newEnd,
|
endTime: newEnd,
|
||||||
description: mamaEvent.description,
|
description: mamaEvent.description,
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
return { content: "Ich konnte keinen Termin 'Telefonat mit Mama' finden." };
|
return { content: "Ich konnte keinen Termin 'Telefonat mit Mama' finden." };
|
||||||
@@ -200,7 +221,14 @@ async function getTestResponse(
|
|||||||
|
|
||||||
if (responseIdx === 13) {
|
if (responseIdx === 13) {
|
||||||
const now = new Date();
|
const now = new Date();
|
||||||
return { content: await getMonthOverview(eventRepo, userId, now.getFullYear(), now.getMonth()) };
|
return {
|
||||||
|
content: await getMonthOverview(
|
||||||
|
eventRepo,
|
||||||
|
userId,
|
||||||
|
now.getFullYear(),
|
||||||
|
now.getMonth(),
|
||||||
|
),
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
return staticResponses[responseIdx];
|
return staticResponses[responseIdx];
|
||||||
@@ -210,17 +238,24 @@ export class ChatService {
|
|||||||
constructor(
|
constructor(
|
||||||
private chatRepo: ChatRepository,
|
private chatRepo: ChatRepository,
|
||||||
private eventRepo: EventRepository,
|
private eventRepo: EventRepository,
|
||||||
private aiProvider: AIProvider
|
private aiProvider: AIProvider,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
async processMessage(userId: string, data: SendMessageDTO): Promise<ChatResponse> {
|
async processMessage(
|
||||||
const response = await getTestResponse(responseIndex, this.eventRepo, userId);
|
userId: string,
|
||||||
|
data: SendMessageDTO,
|
||||||
|
): Promise<ChatResponse> {
|
||||||
|
const response = await getTestResponse(
|
||||||
|
responseIndex,
|
||||||
|
this.eventRepo,
|
||||||
|
userId,
|
||||||
|
);
|
||||||
responseIndex++;
|
responseIndex++;
|
||||||
|
|
||||||
const message: ChatMessage = {
|
const message: ChatMessage = {
|
||||||
id: Date.now().toString(),
|
id: Date.now().toString(),
|
||||||
conversationId: data.conversationId || 'temp-conv-id',
|
conversationId: data.conversationId || "temp-conv-id",
|
||||||
sender: 'assistant',
|
sender: "assistant",
|
||||||
content: response.content,
|
content: response.content,
|
||||||
proposedChange: response.proposedChange,
|
proposedChange: response.proposedChange,
|
||||||
};
|
};
|
||||||
@@ -235,49 +270,57 @@ export class ChatService {
|
|||||||
action: EventAction,
|
action: EventAction,
|
||||||
event?: CreateEventDTO,
|
event?: CreateEventDTO,
|
||||||
eventId?: string,
|
eventId?: string,
|
||||||
updates?: UpdateEventDTO
|
updates?: UpdateEventDTO,
|
||||||
): Promise<ChatResponse> {
|
): Promise<ChatResponse> {
|
||||||
let content: string;
|
let content: string;
|
||||||
|
|
||||||
if (action === 'create' && event) {
|
if (action === "create" && event) {
|
||||||
const createdEvent = await this.eventRepo.create(userId, event);
|
const createdEvent = await this.eventRepo.create(userId, event);
|
||||||
content = `Der Termin "${createdEvent.title}" wurde erstellt.`;
|
content = `Der Termin "${createdEvent.title}" wurde erstellt.`;
|
||||||
} else if (action === 'update' && eventId && updates) {
|
} else if (action === "update" && eventId && updates) {
|
||||||
const updatedEvent = await this.eventRepo.update(eventId, updates);
|
const updatedEvent = await this.eventRepo.update(eventId, updates);
|
||||||
content = updatedEvent
|
content = updatedEvent
|
||||||
? `Der Termin "${updatedEvent.title}" wurde aktualisiert.`
|
? `Der Termin "${updatedEvent.title}" wurde aktualisiert.`
|
||||||
: 'Termin nicht gefunden.';
|
: "Termin nicht gefunden.";
|
||||||
} else if (action === 'delete' && eventId) {
|
} else if (action === "delete" && eventId) {
|
||||||
await this.eventRepo.delete(eventId);
|
await this.eventRepo.delete(eventId);
|
||||||
content = 'Der Termin wurde gelöscht.';
|
content = "Der Termin wurde gelöscht.";
|
||||||
} else {
|
} else {
|
||||||
content = 'Ungültige Aktion.';
|
content = "Ungültige Aktion.";
|
||||||
}
|
}
|
||||||
|
|
||||||
const message: ChatMessage = {
|
const message: ChatMessage = {
|
||||||
id: Date.now().toString(),
|
id: Date.now().toString(),
|
||||||
conversationId,
|
conversationId,
|
||||||
sender: 'assistant',
|
sender: "assistant",
|
||||||
content,
|
content,
|
||||||
};
|
};
|
||||||
return { message, conversationId };
|
return { message, conversationId };
|
||||||
}
|
}
|
||||||
|
|
||||||
async rejectEvent(userId: string, conversationId: string, messageId: string): Promise<ChatResponse> {
|
async rejectEvent(
|
||||||
|
userId: string,
|
||||||
|
conversationId: string,
|
||||||
|
messageId: string,
|
||||||
|
): Promise<ChatResponse> {
|
||||||
const message: ChatMessage = {
|
const message: ChatMessage = {
|
||||||
id: Date.now().toString(),
|
id: Date.now().toString(),
|
||||||
conversationId,
|
conversationId,
|
||||||
sender: 'assistant',
|
sender: "assistant",
|
||||||
content: 'Der Vorschlag wurde abgelehnt.',
|
content: "Der Vorschlag wurde abgelehnt.",
|
||||||
};
|
};
|
||||||
return { message, conversationId };
|
return { message, conversationId };
|
||||||
}
|
}
|
||||||
|
|
||||||
async getConversations(userId: string): Promise<ConversationSummary[]> {
|
async getConversations(userId: string): Promise<ConversationSummary[]> {
|
||||||
throw new Error('Not implemented');
|
throw new Error("Not implemented");
|
||||||
}
|
}
|
||||||
|
|
||||||
async getConversation(userId: string, conversationId: string, options?: GetMessagesOptions): Promise<ChatMessage[]> {
|
async getConversation(
|
||||||
throw new Error('Not implemented');
|
userId: string,
|
||||||
|
conversationId: string,
|
||||||
|
options?: GetMessagesOptions,
|
||||||
|
): Promise<ChatMessage[]> {
|
||||||
|
throw new Error("Not implemented");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,30 +1,38 @@
|
|||||||
import { CalendarEvent, CreateEventDTO, UpdateEventDTO } from '@caldav/shared';
|
import { CalendarEvent, CreateEventDTO, UpdateEventDTO } from "@caldav/shared";
|
||||||
import { EventRepository } from './interfaces';
|
import { EventRepository } from "./interfaces";
|
||||||
|
|
||||||
export class EventService {
|
export class EventService {
|
||||||
constructor(private eventRepo: EventRepository) {}
|
constructor(private eventRepo: EventRepository) {}
|
||||||
|
|
||||||
async create(userId: string, data: CreateEventDTO): Promise<CalendarEvent> {
|
async create(userId: string, data: CreateEventDTO): Promise<CalendarEvent> {
|
||||||
throw new Error('Not implemented');
|
throw new Error("Not implemented");
|
||||||
}
|
}
|
||||||
|
|
||||||
async getById(id: string, userId: string): Promise<CalendarEvent | null> {
|
async getById(id: string, userId: string): Promise<CalendarEvent | null> {
|
||||||
throw new Error('Not implemented');
|
throw new Error("Not implemented");
|
||||||
}
|
}
|
||||||
|
|
||||||
async getAll(userId: string): Promise<CalendarEvent[]> {
|
async getAll(userId: string): Promise<CalendarEvent[]> {
|
||||||
throw new Error('Not implemented');
|
throw new Error("Not implemented");
|
||||||
}
|
}
|
||||||
|
|
||||||
async getByDateRange(userId: string, startDate: Date, endDate: Date): Promise<CalendarEvent[]> {
|
async getByDateRange(
|
||||||
throw new Error('Not implemented');
|
userId: string,
|
||||||
|
startDate: Date,
|
||||||
|
endDate: Date,
|
||||||
|
): Promise<CalendarEvent[]> {
|
||||||
|
throw new Error("Not implemented");
|
||||||
}
|
}
|
||||||
|
|
||||||
async update(id: string, userId: string, data: UpdateEventDTO): Promise<CalendarEvent | null> {
|
async update(
|
||||||
throw new Error('Not implemented');
|
id: string,
|
||||||
|
userId: string,
|
||||||
|
data: UpdateEventDTO,
|
||||||
|
): Promise<CalendarEvent | null> {
|
||||||
|
throw new Error("Not implemented");
|
||||||
}
|
}
|
||||||
|
|
||||||
async delete(id: string, userId: string): Promise<boolean> {
|
async delete(id: string, userId: string): Promise<boolean> {
|
||||||
throw new Error('Not implemented');
|
throw new Error("Not implemented");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
export * from './AuthService';
|
export * from "./AuthService";
|
||||||
export * from './ChatService';
|
export * from "./ChatService";
|
||||||
export * from './EventService';
|
export * from "./EventService";
|
||||||
export * from './interfaces';
|
export * from "./interfaces";
|
||||||
|
|||||||
@@ -1,4 +1,8 @@
|
|||||||
import { CalendarEvent, ChatMessage, ProposedEventChange } from '@caldav/shared';
|
import {
|
||||||
|
CalendarEvent,
|
||||||
|
ChatMessage,
|
||||||
|
ProposedEventChange,
|
||||||
|
} from "@caldav/shared";
|
||||||
|
|
||||||
export interface AIContext {
|
export interface AIContext {
|
||||||
userId: string;
|
userId: string;
|
||||||
|
|||||||
@@ -1,4 +1,9 @@
|
|||||||
import { ChatMessage, Conversation, CreateMessageDTO, GetMessagesOptions } from '@caldav/shared';
|
import {
|
||||||
|
ChatMessage,
|
||||||
|
Conversation,
|
||||||
|
CreateMessageDTO,
|
||||||
|
GetMessagesOptions,
|
||||||
|
} from "@caldav/shared";
|
||||||
|
|
||||||
export interface ChatRepository {
|
export interface ChatRepository {
|
||||||
// Conversations
|
// Conversations
|
||||||
@@ -6,6 +11,12 @@ export interface ChatRepository {
|
|||||||
createConversation(userId: string): Promise<Conversation>;
|
createConversation(userId: string): Promise<Conversation>;
|
||||||
|
|
||||||
// Messages (cursor-based pagination)
|
// Messages (cursor-based pagination)
|
||||||
getMessages(conversationId: string, options?: GetMessagesOptions): Promise<ChatMessage[]>;
|
getMessages(
|
||||||
createMessage(conversationId: string, message: CreateMessageDTO): Promise<ChatMessage>;
|
conversationId: string,
|
||||||
|
options?: GetMessagesOptions,
|
||||||
|
): Promise<ChatMessage[]>;
|
||||||
|
createMessage(
|
||||||
|
conversationId: string,
|
||||||
|
message: CreateMessageDTO,
|
||||||
|
): Promise<ChatMessage>;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,9 +1,13 @@
|
|||||||
import { CalendarEvent, CreateEventDTO, UpdateEventDTO } from '@caldav/shared';
|
import { CalendarEvent, CreateEventDTO, UpdateEventDTO } from "@caldav/shared";
|
||||||
|
|
||||||
export interface EventRepository {
|
export interface EventRepository {
|
||||||
findById(id: string): Promise<CalendarEvent | null>;
|
findById(id: string): Promise<CalendarEvent | null>;
|
||||||
findByUserId(userId: string): Promise<CalendarEvent[]>;
|
findByUserId(userId: string): Promise<CalendarEvent[]>;
|
||||||
findByDateRange(userId: string, startDate: Date, endDate: Date): Promise<CalendarEvent[]>;
|
findByDateRange(
|
||||||
|
userId: string,
|
||||||
|
startDate: Date,
|
||||||
|
endDate: Date,
|
||||||
|
): Promise<CalendarEvent[]>;
|
||||||
create(userId: string, data: CreateEventDTO): Promise<CalendarEvent>;
|
create(userId: string, data: CreateEventDTO): Promise<CalendarEvent>;
|
||||||
update(id: string, data: UpdateEventDTO): Promise<CalendarEvent | null>;
|
update(id: string, data: UpdateEventDTO): Promise<CalendarEvent | null>;
|
||||||
delete(id: string): Promise<boolean>;
|
delete(id: string): Promise<boolean>;
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { User } from '@caldav/shared';
|
import { User } from "@caldav/shared";
|
||||||
|
|
||||||
export interface CreateUserData {
|
export interface CreateUserData {
|
||||||
email: string;
|
email: string;
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
export * from './AIProvider';
|
export * from "./AIProvider";
|
||||||
export * from './UserRepository';
|
export * from "./UserRepository";
|
||||||
export * from './EventRepository';
|
export * from "./EventRepository";
|
||||||
export * from './ChatRepository';
|
export * from "./ChatRepository";
|
||||||
|
|||||||
@@ -4,34 +4,37 @@ import {
|
|||||||
DAY_TO_GERMAN,
|
DAY_TO_GERMAN,
|
||||||
DAY_TO_GERMAN_SHORT,
|
DAY_TO_GERMAN_SHORT,
|
||||||
MONTH_TO_GERMAN,
|
MONTH_TO_GERMAN,
|
||||||
} from '@caldav/shared';
|
} from "@caldav/shared";
|
||||||
import { EventRepository } from '../services/interfaces';
|
import { EventRepository } from "../services/interfaces";
|
||||||
import { expandRecurringEvents, ExpandedEvent } from './recurrenceExpander';
|
import { expandRecurringEvents, ExpandedEvent } from "./recurrenceExpander";
|
||||||
|
|
||||||
// Private formatting helpers
|
// Private formatting helpers
|
||||||
|
|
||||||
function formatTime(date: Date): string {
|
function formatTime(date: Date): string {
|
||||||
const hours = date.getHours().toString().padStart(2, '0');
|
const hours = date.getHours().toString().padStart(2, "0");
|
||||||
const minutes = date.getMinutes().toString().padStart(2, '0');
|
const minutes = date.getMinutes().toString().padStart(2, "0");
|
||||||
return `${hours}:${minutes}`;
|
return `${hours}:${minutes}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
function formatDateShort(date: Date): string {
|
function formatDateShort(date: Date): string {
|
||||||
const day = date.getDate().toString().padStart(2, '0');
|
const day = date.getDate().toString().padStart(2, "0");
|
||||||
const month = (date.getMonth() + 1).toString().padStart(2, '0');
|
const month = (date.getMonth() + 1).toString().padStart(2, "0");
|
||||||
return `${day}.${month}.`;
|
return `${day}.${month}.`;
|
||||||
}
|
}
|
||||||
|
|
||||||
function getWeekNumber(date: Date): number {
|
function getWeekNumber(date: Date): number {
|
||||||
const d = new Date(Date.UTC(date.getFullYear(), date.getMonth(), date.getDate()));
|
const d = new Date(
|
||||||
|
Date.UTC(date.getFullYear(), date.getMonth(), date.getDate()),
|
||||||
|
);
|
||||||
const dayNum = d.getUTCDay() || 7;
|
const dayNum = d.getUTCDay() || 7;
|
||||||
d.setUTCDate(d.getUTCDate() + 4 - dayNum);
|
d.setUTCDate(d.getUTCDate() + 4 - dayNum);
|
||||||
const yearStart = new Date(Date.UTC(d.getUTCFullYear(), 0, 1));
|
const yearStart = new Date(Date.UTC(d.getUTCFullYear(), 0, 1));
|
||||||
return Math.ceil((((d.getTime() - yearStart.getTime()) / 86400000) + 1) / 7);
|
return Math.ceil(((d.getTime() - yearStart.getTime()) / 86400000 + 1) / 7);
|
||||||
}
|
}
|
||||||
|
|
||||||
function formatWeeksText(events: ExpandedEvent[], weeks: number): string {
|
function formatWeeksText(events: ExpandedEvent[], weeks: number): string {
|
||||||
const weeksText = weeks === 1 ? 'die nächste Woche' : `die nächsten ${weeks} Wochen`;
|
const weeksText =
|
||||||
|
weeks === 1 ? "die nächste Woche" : `die nächsten ${weeks} Wochen`;
|
||||||
|
|
||||||
if (events.length === 0) {
|
if (events.length === 0) {
|
||||||
return `Du hast für ${weeksText} keine Termine.`;
|
return `Du hast für ${weeksText} keine Termine.`;
|
||||||
@@ -47,8 +50,10 @@ function formatWeeksText(events: ExpandedEvent[], weeks: number): string {
|
|||||||
lines.push(`${weekday}, ${dateStr} - ${timeStr} Uhr: ${event.title}`);
|
lines.push(`${weekday}, ${dateStr} - ${timeStr} Uhr: ${event.title}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
lines.push(`\nInsgesamt ${events.length} Termin${events.length === 1 ? '' : 'e'}.`);
|
lines.push(
|
||||||
return lines.join('\n');
|
`\nInsgesamt ${events.length} Termin${events.length === 1 ? "" : "e"}.`,
|
||||||
|
);
|
||||||
|
return lines.join("\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
function formatMonthText(events: ExpandedEvent[], monthName: string): string {
|
function formatMonthText(events: ExpandedEvent[], monthName: string): string {
|
||||||
@@ -66,13 +71,17 @@ function formatMonthText(events: ExpandedEvent[], monthName: string): string {
|
|||||||
weekGroups.get(weekNum)!.push(event);
|
weekGroups.get(weekNum)!.push(event);
|
||||||
}
|
}
|
||||||
|
|
||||||
const lines: string[] = [`Hier ist deine Monatsübersicht für ${monthName}:\n`];
|
const lines: string[] = [
|
||||||
|
`Hier ist deine Monatsübersicht für ${monthName}:\n`,
|
||||||
|
];
|
||||||
|
|
||||||
// Sort weeks and format
|
// Sort weeks and format
|
||||||
const sortedWeeks = Array.from(weekGroups.keys()).sort((a, b) => a - b);
|
const sortedWeeks = Array.from(weekGroups.keys()).sort((a, b) => a - b);
|
||||||
for (const weekNum of sortedWeeks) {
|
for (const weekNum of sortedWeeks) {
|
||||||
const weekEvents = weekGroups.get(weekNum)!;
|
const weekEvents = weekGroups.get(weekNum)!;
|
||||||
lines.push(`KW ${weekNum}: ${weekEvents.length} Termin${weekEvents.length === 1 ? '' : 'e'}`);
|
lines.push(
|
||||||
|
`KW ${weekNum}: ${weekEvents.length} Termin${weekEvents.length === 1 ? "" : "e"}`,
|
||||||
|
);
|
||||||
|
|
||||||
for (const event of weekEvents) {
|
for (const event of weekEvents) {
|
||||||
const day = DAY_INDEX_TO_DAY[event.occurrenceStart.getDay()];
|
const day = DAY_INDEX_TO_DAY[event.occurrenceStart.getDay()];
|
||||||
@@ -81,11 +90,13 @@ function formatMonthText(events: ExpandedEvent[], monthName: string): string {
|
|||||||
const timeStr = formatTime(event.occurrenceStart);
|
const timeStr = formatTime(event.occurrenceStart);
|
||||||
lines.push(` • ${weekdayShort} ${dateStr}, ${timeStr}: ${event.title}`);
|
lines.push(` • ${weekdayShort} ${dateStr}, ${timeStr}: ${event.title}`);
|
||||||
}
|
}
|
||||||
lines.push('');
|
lines.push("");
|
||||||
}
|
}
|
||||||
|
|
||||||
lines.push(`Insgesamt ${events.length} Termin${events.length === 1 ? '' : 'e'} im ${monthName}.`);
|
lines.push(
|
||||||
return lines.join('\n');
|
`Insgesamt ${events.length} Termin${events.length === 1 ? "" : "e"} im ${monthName}.`,
|
||||||
|
);
|
||||||
|
return lines.join("\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Public API
|
// Public API
|
||||||
@@ -97,7 +108,7 @@ function formatMonthText(events: ExpandedEvent[], monthName: string): string {
|
|||||||
export async function getWeeksOverview(
|
export async function getWeeksOverview(
|
||||||
eventRepo: EventRepository,
|
eventRepo: EventRepository,
|
||||||
userId: string,
|
userId: string,
|
||||||
weeks: number
|
weeks: number,
|
||||||
): Promise<string> {
|
): Promise<string> {
|
||||||
const now = new Date();
|
const now = new Date();
|
||||||
const endDate = new Date(now.getTime() + weeks * 7 * 24 * 60 * 60 * 1000);
|
const endDate = new Date(now.getTime() + weeks * 7 * 24 * 60 * 60 * 1000);
|
||||||
@@ -114,7 +125,7 @@ export async function getMonthOverview(
|
|||||||
eventRepo: EventRepository,
|
eventRepo: EventRepository,
|
||||||
userId: string,
|
userId: string,
|
||||||
year: number,
|
year: number,
|
||||||
month: number
|
month: number,
|
||||||
): Promise<string> {
|
): Promise<string> {
|
||||||
const startOfMonth = new Date(year, month, 1);
|
const startOfMonth = new Date(year, month, 1);
|
||||||
const endOfMonth = new Date(year, month + 1, 0, 23, 59, 59);
|
const endOfMonth = new Date(year, month + 1, 0, 23, 59, 59);
|
||||||
|
|||||||
@@ -1,2 +1,2 @@
|
|||||||
export * from './jwt';
|
export * from "./jwt";
|
||||||
export * from './password';
|
export * from "./password";
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import jwt from 'jsonwebtoken';
|
import jwt from "jsonwebtoken";
|
||||||
|
|
||||||
export interface TokenPayload {
|
export interface TokenPayload {
|
||||||
userId: string;
|
userId: string;
|
||||||
@@ -10,17 +10,17 @@ export interface JWTConfig {
|
|||||||
expiresIn: string;
|
expiresIn: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
const JWT_SECRET = process.env.JWT_SECRET || 'your-secret-key';
|
const JWT_SECRET = process.env.JWT_SECRET || "your-secret-key";
|
||||||
const JWT_EXPIRES_IN = process.env.JWT_EXPIRES_IN || '1h';
|
const JWT_EXPIRES_IN = process.env.JWT_EXPIRES_IN || "1h";
|
||||||
|
|
||||||
export function signToken(payload: TokenPayload): string {
|
export function signToken(payload: TokenPayload): string {
|
||||||
throw new Error('Not implemented');
|
throw new Error("Not implemented");
|
||||||
}
|
}
|
||||||
|
|
||||||
export function verifyToken(token: string): TokenPayload {
|
export function verifyToken(token: string): TokenPayload {
|
||||||
throw new Error('Not implemented');
|
throw new Error("Not implemented");
|
||||||
}
|
}
|
||||||
|
|
||||||
export function decodeToken(token: string): TokenPayload | null {
|
export function decodeToken(token: string): TokenPayload | null {
|
||||||
throw new Error('Not implemented');
|
throw new Error("Not implemented");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import bcrypt from 'bcrypt';
|
import bcrypt from "bcrypt";
|
||||||
|
|
||||||
const SALT_ROUNDS = 10;
|
const SALT_ROUNDS = 10;
|
||||||
|
|
||||||
@@ -6,6 +6,9 @@ export async function hash(password: string): Promise<string> {
|
|||||||
return bcrypt.hash(password, SALT_ROUNDS);
|
return bcrypt.hash(password, SALT_ROUNDS);
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function compare(password: string, hash: string): Promise<boolean> {
|
export async function compare(
|
||||||
|
password: string,
|
||||||
|
hash: string,
|
||||||
|
): Promise<boolean> {
|
||||||
return bcrypt.compare(password, hash);
|
return bcrypt.compare(password, hash);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { RRule, rrulestr } from 'rrule';
|
import { RRule, rrulestr } from "rrule";
|
||||||
import { CalendarEvent } from '@caldav/shared';
|
import { CalendarEvent } from "@caldav/shared";
|
||||||
|
|
||||||
export interface ExpandedEvent extends CalendarEvent {
|
export interface ExpandedEvent extends CalendarEvent {
|
||||||
occurrenceStart: Date;
|
occurrenceStart: Date;
|
||||||
@@ -9,14 +9,16 @@ export interface ExpandedEvent extends CalendarEvent {
|
|||||||
// Convert local time to "fake UTC" for rrule
|
// Convert local time to "fake UTC" for rrule
|
||||||
// rrule interprets all dates as UTC internally, so we need to trick it
|
// rrule interprets all dates as UTC internally, so we need to trick it
|
||||||
function toRRuleDate(date: Date): Date {
|
function toRRuleDate(date: Date): Date {
|
||||||
return new Date(Date.UTC(
|
return new Date(
|
||||||
date.getFullYear(),
|
Date.UTC(
|
||||||
date.getMonth(),
|
date.getFullYear(),
|
||||||
date.getDate(),
|
date.getMonth(),
|
||||||
date.getHours(),
|
date.getDate(),
|
||||||
date.getMinutes(),
|
date.getHours(),
|
||||||
date.getSeconds()
|
date.getMinutes(),
|
||||||
));
|
date.getSeconds(),
|
||||||
|
),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Convert rrule result back to local time
|
// Convert rrule result back to local time
|
||||||
@@ -27,7 +29,7 @@ function fromRRuleDate(date: Date): Date {
|
|||||||
date.getUTCDate(),
|
date.getUTCDate(),
|
||||||
date.getUTCHours(),
|
date.getUTCHours(),
|
||||||
date.getUTCMinutes(),
|
date.getUTCMinutes(),
|
||||||
date.getUTCSeconds()
|
date.getUTCSeconds(),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -38,7 +40,7 @@ function fromRRuleDate(date: Date): Date {
|
|||||||
export function expandRecurringEvents(
|
export function expandRecurringEvents(
|
||||||
events: CalendarEvent[],
|
events: CalendarEvent[],
|
||||||
rangeStart: Date,
|
rangeStart: Date,
|
||||||
rangeEnd: Date
|
rangeEnd: Date,
|
||||||
): ExpandedEvent[] {
|
): ExpandedEvent[] {
|
||||||
const expanded: ExpandedEvent[] = [];
|
const expanded: ExpandedEvent[] = [];
|
||||||
|
|
||||||
@@ -61,13 +63,15 @@ export function expandRecurringEvents(
|
|||||||
|
|
||||||
// Recurring event: parse RRULE and expand
|
// Recurring event: parse RRULE and expand
|
||||||
try {
|
try {
|
||||||
const rule = rrulestr(`DTSTART:${formatRRuleDateString(startTime)}\nRRULE:${event.recurrenceRule}`);
|
const rule = rrulestr(
|
||||||
|
`DTSTART:${formatRRuleDateString(startTime)}\nRRULE:${event.recurrenceRule}`,
|
||||||
|
);
|
||||||
|
|
||||||
// Get occurrences within the range (using fake UTC dates)
|
// Get occurrences within the range (using fake UTC dates)
|
||||||
const occurrences = rule.between(
|
const occurrences = rule.between(
|
||||||
toRRuleDate(rangeStart),
|
toRRuleDate(rangeStart),
|
||||||
toRRuleDate(rangeEnd),
|
toRRuleDate(rangeEnd),
|
||||||
true // inclusive
|
true, // inclusive
|
||||||
);
|
);
|
||||||
|
|
||||||
for (const occurrence of occurrences) {
|
for (const occurrence of occurrences) {
|
||||||
@@ -82,7 +86,10 @@ export function expandRecurringEvents(
|
|||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
// If RRULE parsing fails, include the event as a single occurrence
|
// If RRULE parsing fails, include the event as a single occurrence
|
||||||
console.error(`Failed to parse recurrence rule for event ${event.id}:`, error);
|
console.error(
|
||||||
|
`Failed to parse recurrence rule for event ${event.id}:`,
|
||||||
|
error,
|
||||||
|
);
|
||||||
expanded.push({
|
expanded.push({
|
||||||
...event,
|
...event,
|
||||||
occurrenceStart: startTime,
|
occurrenceStart: startTime,
|
||||||
@@ -92,7 +99,9 @@ export function expandRecurringEvents(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Sort by occurrence start time
|
// Sort by occurrence start time
|
||||||
expanded.sort((a, b) => a.occurrenceStart.getTime() - b.occurrenceStart.getTime());
|
expanded.sort(
|
||||||
|
(a, b) => a.occurrenceStart.getTime() - b.occurrenceStart.getTime(),
|
||||||
|
);
|
||||||
|
|
||||||
return expanded;
|
return expanded;
|
||||||
}
|
}
|
||||||
@@ -100,10 +109,10 @@ export function expandRecurringEvents(
|
|||||||
// Format date as RRULE DTSTART string (YYYYMMDDTHHMMSS)
|
// Format date as RRULE DTSTART string (YYYYMMDDTHHMMSS)
|
||||||
function formatRRuleDateString(date: Date): string {
|
function formatRRuleDateString(date: Date): string {
|
||||||
const year = date.getFullYear();
|
const year = date.getFullYear();
|
||||||
const month = String(date.getMonth() + 1).padStart(2, '0');
|
const month = String(date.getMonth() + 1).padStart(2, "0");
|
||||||
const day = String(date.getDate()).padStart(2, '0');
|
const day = String(date.getDate()).padStart(2, "0");
|
||||||
const hours = String(date.getHours()).padStart(2, '0');
|
const hours = String(date.getHours()).padStart(2, "0");
|
||||||
const minutes = String(date.getMinutes()).padStart(2, '0');
|
const minutes = String(date.getMinutes()).padStart(2, "0");
|
||||||
const seconds = String(date.getSeconds()).padStart(2, '0');
|
const seconds = String(date.getSeconds()).padStart(2, "0");
|
||||||
return `${year}${month}${day}T${hours}${minutes}${seconds}`;
|
return `${year}${month}${day}T${hours}${minutes}${seconds}`;
|
||||||
}
|
}
|
||||||
|
|||||||
2
package-lock.json
generated
2
package-lock.json
generated
@@ -13,6 +13,7 @@
|
|||||||
],
|
],
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"eslint": "^9.25.0",
|
"eslint": "^9.25.0",
|
||||||
|
"prettier": "^3.7.4",
|
||||||
"typescript": "~5.9.2"
|
"typescript": "~5.9.2"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -11757,7 +11758,6 @@
|
|||||||
"integrity": "sha512-v6UNi1+3hSlVvv8fSaoUbggEM5VErKmmpGA7Pl3HF8V6uKY7rvClBOJlH6yNwQtfTueNkGVpOv/mtWL9L4bgRA==",
|
"integrity": "sha512-v6UNi1+3hSlVvv8fSaoUbggEM5VErKmmpGA7Pl3HF8V6uKY7rvClBOJlH6yNwQtfTueNkGVpOv/mtWL9L4bgRA==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"bin": {
|
"bin": {
|
||||||
"prettier": "bin/prettier.cjs"
|
"prettier": "bin/prettier.cjs"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -8,6 +8,7 @@
|
|||||||
],
|
],
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"eslint": "^9.25.0",
|
"eslint": "^9.25.0",
|
||||||
|
"prettier": "^3.7.4",
|
||||||
"typescript": "~5.9.2"
|
"typescript": "~5.9.2"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,2 +1,2 @@
|
|||||||
export * from './models';
|
export * from "./models";
|
||||||
export * from './utils';
|
export * from "./utils";
|
||||||
|
|||||||
@@ -1,14 +1,14 @@
|
|||||||
import { CreateEventDTO, UpdateEventDTO } from './CalendarEvent';
|
import { CreateEventDTO, UpdateEventDTO } from "./CalendarEvent";
|
||||||
|
|
||||||
export type MessageSender = 'user' | 'assistant';
|
export type MessageSender = "user" | "assistant";
|
||||||
|
|
||||||
export type EventAction = 'create' | 'update' | 'delete';
|
export type EventAction = "create" | "update" | "delete";
|
||||||
|
|
||||||
export interface ProposedEventChange {
|
export interface ProposedEventChange {
|
||||||
action: EventAction;
|
action: EventAction;
|
||||||
eventId?: string; // Required for update/delete
|
eventId?: string; // Required for update/delete
|
||||||
event?: CreateEventDTO; // Required for create
|
event?: CreateEventDTO; // Required for create
|
||||||
updates?: UpdateEventDTO; // Required for update
|
updates?: UpdateEventDTO; // Required for update
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ChatMessage {
|
export interface ChatMessage {
|
||||||
@@ -39,8 +39,8 @@ export interface CreateMessageDTO {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface GetMessagesOptions {
|
export interface GetMessagesOptions {
|
||||||
before?: string; // Message ID - load messages before this one
|
before?: string; // Message ID - load messages before this one
|
||||||
limit?: number; // Default: 20
|
limit?: number; // Default: 20
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ChatResponse {
|
export interface ChatResponse {
|
||||||
|
|||||||
@@ -39,40 +39,48 @@ export const DAY_INDEX: Record<Day, number> = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// Mapping from Date.getDay() index (0=Sunday) to Day type
|
// Mapping from Date.getDay() index (0=Sunday) to Day type
|
||||||
export const DAY_INDEX_TO_DAY: Day[] = ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'];
|
export const DAY_INDEX_TO_DAY: Day[] = [
|
||||||
|
"Sunday",
|
||||||
|
"Monday",
|
||||||
|
"Tuesday",
|
||||||
|
"Wednesday",
|
||||||
|
"Thursday",
|
||||||
|
"Friday",
|
||||||
|
"Saturday",
|
||||||
|
];
|
||||||
|
|
||||||
// German translations
|
// German translations
|
||||||
export const DAY_TO_GERMAN: Record<Day, string> = {
|
export const DAY_TO_GERMAN: Record<Day, string> = {
|
||||||
Monday: 'Montag',
|
Monday: "Montag",
|
||||||
Tuesday: 'Dienstag',
|
Tuesday: "Dienstag",
|
||||||
Wednesday: 'Mittwoch',
|
Wednesday: "Mittwoch",
|
||||||
Thursday: 'Donnerstag',
|
Thursday: "Donnerstag",
|
||||||
Friday: 'Freitag',
|
Friday: "Freitag",
|
||||||
Saturday: 'Samstag',
|
Saturday: "Samstag",
|
||||||
Sunday: 'Sonntag',
|
Sunday: "Sonntag",
|
||||||
};
|
};
|
||||||
|
|
||||||
export const DAY_TO_GERMAN_SHORT: Record<Day, string> = {
|
export const DAY_TO_GERMAN_SHORT: Record<Day, string> = {
|
||||||
Monday: 'Mo',
|
Monday: "Mo",
|
||||||
Tuesday: 'Di',
|
Tuesday: "Di",
|
||||||
Wednesday: 'Mi',
|
Wednesday: "Mi",
|
||||||
Thursday: 'Do',
|
Thursday: "Do",
|
||||||
Friday: 'Fr',
|
Friday: "Fr",
|
||||||
Saturday: 'Sa',
|
Saturday: "Sa",
|
||||||
Sunday: 'So',
|
Sunday: "So",
|
||||||
};
|
};
|
||||||
|
|
||||||
export const MONTH_TO_GERMAN: Record<Month, string> = {
|
export const MONTH_TO_GERMAN: Record<Month, string> = {
|
||||||
January: 'Januar',
|
January: "Januar",
|
||||||
February: 'Februar',
|
February: "Februar",
|
||||||
March: 'März',
|
March: "März",
|
||||||
April: 'April',
|
April: "April",
|
||||||
May: 'Mai',
|
May: "Mai",
|
||||||
June: 'Juni',
|
June: "Juni",
|
||||||
July: 'Juli',
|
July: "Juli",
|
||||||
August: 'August',
|
August: "August",
|
||||||
September: 'September',
|
September: "September",
|
||||||
October: 'Oktober',
|
October: "Oktober",
|
||||||
November: 'November',
|
November: "November",
|
||||||
December: 'Dezember',
|
December: "Dezember",
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ export interface LoginDTO {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface AuthResponse {
|
export interface AuthResponse {
|
||||||
user: Omit<User, 'passwordHash'>;
|
user: Omit<User, "passwordHash">;
|
||||||
accessToken: string;
|
accessToken: string;
|
||||||
refreshToken?: string;
|
refreshToken?: string;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
export * from './User';
|
export * from "./User";
|
||||||
export * from './CalendarEvent';
|
export * from "./CalendarEvent";
|
||||||
export * from './ChatMessage';
|
export * from "./ChatMessage";
|
||||||
export * from './Constants';
|
export * from "./Constants";
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { Day, DAY_INDEX } from '../models/Constants';
|
import { Day, DAY_INDEX } from "../models/Constants";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get a date for a specific weekday relative to today.
|
* Get a date for a specific weekday relative to today.
|
||||||
@@ -7,7 +7,12 @@ import { Day, DAY_INDEX } from '../models/Constants';
|
|||||||
* @param hour - Hour of day (0-23)
|
* @param hour - Hour of day (0-23)
|
||||||
* @param minute - Minute (0-59)
|
* @param minute - Minute (0-59)
|
||||||
*/
|
*/
|
||||||
export function getDay(day: Day, offset: number, hour: number, minute: number): Date {
|
export function getDay(
|
||||||
|
day: Day,
|
||||||
|
offset: number,
|
||||||
|
hour: number,
|
||||||
|
minute: number,
|
||||||
|
): Date {
|
||||||
const today = new Date();
|
const today = new Date();
|
||||||
const currentDay = today.getDay();
|
const currentDay = today.getDay();
|
||||||
const targetDay = DAY_INDEX[day];
|
const targetDay = DAY_INDEX[day];
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
export * from './dateHelpers';
|
export * from "./dateHelpers";
|
||||||
|
|||||||
Reference in New Issue
Block a user