diff --git a/backend/open_webui/migrations/versions/56359461a091_add_calendar_tables.py b/backend/open_webui/migrations/versions/56359461a091_add_calendar_tables.py index 8277daa738..a0812578c8 100644 --- a/backend/open_webui/migrations/versions/56359461a091_add_calendar_tables.py +++ b/backend/open_webui/migrations/versions/56359461a091_add_calendar_tables.py @@ -24,7 +24,7 @@ def upgrade() -> None: sa.Column('user_id', sa.Text(), nullable=False), sa.Column('name', sa.Text(), nullable=False), sa.Column('color', sa.Text(), nullable=True), - sa.Column('is_system', sa.Boolean(), nullable=False), + sa.Column('is_default', sa.Boolean(), nullable=False), sa.Column('data', sa.JSON(), nullable=True), sa.Column('meta', sa.JSON(), nullable=True), sa.Column('created_at', sa.BigInteger(), nullable=False), diff --git a/backend/open_webui/models/calendar.py b/backend/open_webui/models/calendar.py index e055c87f90..859632c494 100644 --- a/backend/open_webui/models/calendar.py +++ b/backend/open_webui/models/calendar.py @@ -17,6 +17,7 @@ from sqlalchemy import ( exists, func, delete, + update, ) from sqlalchemy.ext.asyncio import AsyncSession @@ -40,7 +41,7 @@ class Calendar(Base): user_id = Column(Text, nullable=False) name = Column(Text, nullable=False) color = Column(Text, nullable=True) - is_system = Column(Boolean, nullable=False, default=False) + is_default = Column(Boolean, nullable=False, default=False) data = Column(JSON, nullable=True) meta = Column(JSON, nullable=True) @@ -107,7 +108,8 @@ class CalendarModel(BaseModel): user_id: str name: str color: Optional[str] = None - is_system: bool = False + is_default: bool = False + data: Optional[dict] = None meta: Optional[dict] = None @@ -269,7 +271,7 @@ class CalendarTable: user_id=user_id, name='Personal', color='#3b82f6', - is_system=True, + is_default=True, created_at=now, updated_at=now, ), @@ -278,7 +280,6 @@ class CalendarTable: user_id=user_id, name='Scheduled Tasks', color='#8b5cf6', - is_system=True, created_at=now + 1, updated_at=now + 1, ), @@ -338,7 +339,6 @@ class CalendarTable: select(Calendar).filter( Calendar.user_id == user_id, Calendar.name == 'Scheduled Tasks', - Calendar.is_system == True, ) ) cal = result.scalars().first() @@ -349,7 +349,6 @@ class CalendarTable: select(Calendar).filter( Calendar.user_id == user_id, Calendar.name == 'Scheduled Tasks', - Calendar.is_system == True, ) ) cal = result.scalars().first() @@ -366,7 +365,7 @@ class CalendarTable: user_id=user_id, name=form_data.name, color=form_data.color, - is_system=False, + is_default=False, data=form_data.data, meta=form_data.meta, created_at=now, @@ -403,13 +402,36 @@ class CalendarTable: await db.commit() return await self._to_calendar_model(cal, db=db) + async def set_default_calendar( + self, user_id: str, calendar_id: str, db: Optional[AsyncSession] = None + ) -> Optional[CalendarModel]: + """Set a calendar as the user's default, clearing all others.""" + async with get_async_db_context(db) as db: + # Clear all defaults for this user + await db.execute( + update(Calendar) + .where(Calendar.user_id == user_id, Calendar.is_default == True) + .values(is_default=False) + ) + # Set the new default + result = await db.execute( + select(Calendar).filter(Calendar.id == calendar_id, Calendar.user_id == user_id) + ) + cal = result.scalars().first() + if not cal: + return None + cal.is_default = True + cal.updated_at = int(time.time_ns()) + await db.commit() + return await self._to_calendar_model(cal, db=db) + async def delete_calendar_by_id(self, id: str, db: Optional[AsyncSession] = None) -> bool: - """Delete a non-system calendar. Cascades to events, attendees, and grants.""" + """Delete a non-default calendar. Cascades to events, attendees, and grants.""" try: async with get_async_db_context(db) as db: result = await db.execute(select(Calendar).filter(Calendar.id == id)) cal = result.scalars().first() - if not cal or cal.is_system: + if not cal or cal.is_default: return False # Delete attendees for all events in this calendar diff --git a/backend/open_webui/routers/calendar.py b/backend/open_webui/routers/calendar.py index 4b052bd754..220edf853c 100644 --- a/backend/open_webui/routers/calendar.py +++ b/backend/open_webui/routers/calendar.py @@ -310,10 +310,16 @@ async def delete_calendar(calendar_id: str, user: UserModel = Depends(get_verifi if cal.user_id != user.id and user.role != 'admin': raise HTTPException(status_code=403, detail='Only owner can delete calendar') - if cal.is_system: - raise HTTPException(status_code=400, detail='Cannot delete system calendar') result = await Calendars.delete_calendar_by_id(calendar_id) if not result: raise HTTPException(status_code=500, detail='Failed to delete') return {'status': True} + + +@router.post('/{calendar_id}/default') +async def set_default_calendar(calendar_id: str, user: UserModel = Depends(get_verified_user)): + cal = await Calendars.set_default_calendar(user.id, calendar_id) + if not cal: + raise HTTPException(status_code=404, detail='Calendar not found') + return cal diff --git a/src/lib/apis/calendar/index.ts b/src/lib/apis/calendar/index.ts index 39540bb890..fa75f9a6a7 100644 --- a/src/lib/apis/calendar/index.ts +++ b/src/lib/apis/calendar/index.ts @@ -5,7 +5,7 @@ export type CalendarModel = { user_id: string; name: string; color: string | null; - is_system: boolean; + is_default: boolean; data: Record | null; meta: Record | null; access_grants: any[]; @@ -188,6 +188,37 @@ export const deleteCalendar = async (token: string, calendarId: string): Promise return res?.status ?? false; }; +export const setDefaultCalendar = async ( + token: string, + calendarId: string +): Promise => { + let error = null; + + const res = await fetch(`${WEBUI_API_BASE_URL}/calendars/${calendarId}/default`, { + method: 'POST', + headers: { + Accept: 'application/json', + 'Content-Type': 'application/json', + authorization: `Bearer ${token}` + } + }) + .then(async (res) => { + if (!res.ok) throw await res.json(); + return res.json(); + }) + .catch((err) => { + error = err.detail; + console.error(err); + return null; + }); + + if (error) { + throw error; + } + + return res; +}; + // ── Events ───────────────────────────────── export const getCalendarEvents = async ( diff --git a/src/routes/(app)/calendar/+page.svelte b/src/routes/(app)/calendar/+page.svelte index 89940ef23b..25671fc791 100644 --- a/src/routes/(app)/calendar/+page.svelte +++ b/src/routes/(app)/calendar/+page.svelte @@ -128,7 +128,7 @@ showEventModal = true; } - $: defaultCalendarId = calendars.find((c) => !c.is_system || c.name === 'Personal')?.id || calendars[0]?.id || ''; + $: defaultCalendarId = calendars.find((c) => c.is_default)?.id || calendars[0]?.id || ''; onMount(async () => { await loadCalendars();