From f45d0f130ef9c3d3958f54320b987f2e2eacc421 Mon Sep 17 00:00:00 2001 From: Timothy Jaeryang Baek Date: Sun, 19 Apr 2026 22:22:15 +0900 Subject: [PATCH] refac --- backend/open_webui/config.py | 6 +++ backend/open_webui/main.py | 3 ++ backend/open_webui/routers/auths.py | 4 ++ backend/open_webui/routers/calendar.py | 49 ++++++++++++++----- backend/open_webui/utils/tools.py | 5 +- .../components/admin/Settings/General.svelte | 8 +++ .../components/layout/Sidebar/UserMenu.svelte | 2 +- 7 files changed, 63 insertions(+), 14 deletions(-) diff --git a/backend/open_webui/config.py b/backend/open_webui/config.py index a68720a7c..1ee107a6a 100644 --- a/backend/open_webui/config.py +++ b/backend/open_webui/config.py @@ -1624,6 +1624,12 @@ ENABLE_CHANNELS = PersistentConfig( os.environ.get('ENABLE_CHANNELS', 'False').lower() == 'true', ) +ENABLE_CALENDAR = PersistentConfig( + 'ENABLE_CALENDAR', + 'calendar.enable', + os.environ.get('ENABLE_CALENDAR', 'True').lower() == 'true', +) + AUTOMATION_MAX_COUNT = PersistentConfig( 'AUTOMATION_MAX_COUNT', 'automations.max_count', diff --git a/backend/open_webui/main.py b/backend/open_webui/main.py index c13250c58..23379a775 100644 --- a/backend/open_webui/main.py +++ b/backend/open_webui/main.py @@ -396,6 +396,7 @@ from open_webui.config import ( AUTOMATION_MAX_COUNT, AUTOMATION_MIN_INTERVAL, ENABLE_CHANNELS, + ENABLE_CALENDAR, ENABLE_NOTES, ENABLE_USER_STATUS, ENABLE_COMMUNITY_SHARING, @@ -902,6 +903,7 @@ app.state.config.FOLDER_MAX_FILE_COUNT = FOLDER_MAX_FILE_COUNT app.state.config.AUTOMATION_MAX_COUNT = AUTOMATION_MAX_COUNT app.state.config.AUTOMATION_MIN_INTERVAL = AUTOMATION_MIN_INTERVAL app.state.config.ENABLE_CHANNELS = ENABLE_CHANNELS +app.state.config.ENABLE_CALENDAR = ENABLE_CALENDAR app.state.config.ENABLE_NOTES = ENABLE_NOTES app.state.config.ENABLE_COMMUNITY_SHARING = ENABLE_COMMUNITY_SHARING app.state.config.ENABLE_MESSAGE_RATING = ENABLE_MESSAGE_RATING @@ -2218,6 +2220,7 @@ async def get_app_config(request: Request): 'enable_folders': app.state.config.ENABLE_FOLDERS, 'folder_max_file_count': app.state.config.FOLDER_MAX_FILE_COUNT, 'enable_channels': app.state.config.ENABLE_CHANNELS, + 'enable_calendar': app.state.config.ENABLE_CALENDAR, 'enable_notes': app.state.config.ENABLE_NOTES, 'enable_web_search': app.state.config.ENABLE_WEB_SEARCH, 'enable_code_execution': app.state.config.ENABLE_CODE_EXECUTION, diff --git a/backend/open_webui/routers/auths.py b/backend/open_webui/routers/auths.py index 651e123b6..c8daa4957 100644 --- a/backend/open_webui/routers/auths.py +++ b/backend/open_webui/routers/auths.py @@ -972,6 +972,7 @@ async def get_admin_config(request: Request, user=Depends(get_admin_user)): 'AUTOMATION_MAX_COUNT': request.app.state.config.AUTOMATION_MAX_COUNT, 'AUTOMATION_MIN_INTERVAL': request.app.state.config.AUTOMATION_MIN_INTERVAL, 'ENABLE_CHANNELS': request.app.state.config.ENABLE_CHANNELS, + 'ENABLE_CALENDAR': request.app.state.config.ENABLE_CALENDAR, 'ENABLE_MEMORIES': request.app.state.config.ENABLE_MEMORIES, 'ENABLE_NOTES': request.app.state.config.ENABLE_NOTES, 'ENABLE_USER_WEBHOOKS': request.app.state.config.ENABLE_USER_WEBHOOKS, @@ -1000,6 +1001,7 @@ class AdminConfig(BaseModel): AUTOMATION_MAX_COUNT: Optional[int | str] = None AUTOMATION_MIN_INTERVAL: Optional[int | str] = None ENABLE_CHANNELS: bool + ENABLE_CALENDAR: bool ENABLE_MEMORIES: bool ENABLE_NOTES: bool ENABLE_USER_WEBHOOKS: bool @@ -1031,6 +1033,7 @@ async def update_admin_config(request: Request, form_data: AdminConfig, user=Dep int(form_data.AUTOMATION_MIN_INTERVAL) if form_data.AUTOMATION_MIN_INTERVAL else '' ) request.app.state.config.ENABLE_CHANNELS = form_data.ENABLE_CHANNELS + request.app.state.config.ENABLE_CALENDAR = form_data.ENABLE_CALENDAR request.app.state.config.ENABLE_MEMORIES = form_data.ENABLE_MEMORIES request.app.state.config.ENABLE_NOTES = form_data.ENABLE_NOTES @@ -1074,6 +1077,7 @@ async def update_admin_config(request: Request, form_data: AdminConfig, user=Dep 'AUTOMATION_MAX_COUNT': request.app.state.config.AUTOMATION_MAX_COUNT, 'AUTOMATION_MIN_INTERVAL': request.app.state.config.AUTOMATION_MIN_INTERVAL, 'ENABLE_CHANNELS': request.app.state.config.ENABLE_CHANNELS, + 'ENABLE_CALENDAR': request.app.state.config.ENABLE_CALENDAR, 'ENABLE_MEMORIES': request.app.state.config.ENABLE_MEMORIES, 'ENABLE_NOTES': request.app.state.config.ENABLE_NOTES, 'ENABLE_USER_WEBHOOKS': request.app.state.config.ENABLE_USER_WEBHOOKS, diff --git a/backend/open_webui/routers/calendar.py b/backend/open_webui/routers/calendar.py index 220edf853..f92f5b894 100644 --- a/backend/open_webui/routers/calendar.py +++ b/backend/open_webui/routers/calendar.py @@ -2,7 +2,7 @@ import logging import time from typing import Optional -from fastapi import APIRouter, Depends, HTTPException +from fastapi import APIRouter, Depends, HTTPException, Request from pydantic import BaseModel from open_webui.models.calendar import ( @@ -24,12 +24,22 @@ from open_webui.models.groups import Groups from open_webui.models.users import UserModel from open_webui.utils.auth import get_verified_user from open_webui.utils.calendar import expand_recurring_event +from open_webui.constants import ERROR_MESSAGES log = logging.getLogger(__name__) router = APIRouter() +async def check_calendar_enabled(request: Request): + """Dependency to ensure calendar feature is globally enabled.""" + if not request.app.state.config.ENABLE_CALENDAR: + raise HTTPException( + status_code=403, + detail=ERROR_MESSAGES.FEATURE_DISABLED('Calendar'), + ) + + async def _check_calendar_access( calendar_id: str, user: UserModel, permission: str = 'write' ) -> CalendarModel: @@ -58,14 +68,16 @@ async def _check_calendar_access( @router.get('/', response_model=list[CalendarModel]) -async def get_calendars(user: UserModel = Depends(get_verified_user)): +async def get_calendars(request: Request, user: UserModel = Depends(get_verified_user)): """List user's calendars (owned + shared). Auto-creates defaults on first call.""" + await check_calendar_enabled(request) return await Calendars.get_calendars_by_user(user.id) @router.post('/create', response_model=CalendarModel) -async def create_calendar(form_data: CalendarForm, user: UserModel = Depends(get_verified_user)): +async def create_calendar(request: Request, form_data: CalendarForm, user: UserModel = Depends(get_verified_user)): """Create a new user calendar.""" + await check_calendar_enabled(request) return await Calendars.insert_new_calendar(user.id, form_data) @@ -76,6 +88,7 @@ async def create_calendar(form_data: CalendarForm, user: UserModel = Depends(get @router.get('/events') async def get_events( + request: Request, start: str, end: str, calendar_ids: Optional[str] = None, @@ -92,6 +105,7 @@ async def get_events( - Stored events from the database - Virtual events computed from active automation RRULEs (Scheduled Tasks calendar) """ + await check_calendar_enabled(request) from datetime import datetime try: @@ -203,25 +217,29 @@ async def get_events( @router.post('/events/create', response_model=CalendarEventModel) -async def create_event(form_data: CalendarEventForm, user: UserModel = Depends(get_verified_user)): +async def create_event(request: Request, form_data: CalendarEventForm, user: UserModel = Depends(get_verified_user)): + await check_calendar_enabled(request) await _check_calendar_access(form_data.calendar_id, user, 'write') return await CalendarEvents.insert_new_event(user.id, form_data) @router.get('/events/search', response_model=CalendarEventListResponse) async def search_events( + request: Request, query: Optional[str] = None, skip: int = 0, limit: int = 30, user: UserModel = Depends(get_verified_user), ): + await check_calendar_enabled(request) return await CalendarEvents.search_events( user_id=user.id, query=query, skip=skip, limit=limit ) @router.get('/events/{event_id}', response_model=CalendarEventModel) -async def get_event(event_id: str, user: UserModel = Depends(get_verified_user)): +async def get_event(request: Request, event_id: str, user: UserModel = Depends(get_verified_user)): + await check_calendar_enabled(request) event = await CalendarEvents.get_event_by_id(event_id) if not event: raise HTTPException(status_code=404, detail='Event not found') @@ -233,8 +251,9 @@ async def get_event(event_id: str, user: UserModel = Depends(get_verified_user)) @router.post('/events/{event_id}/update', response_model=CalendarEventModel) async def update_event( - event_id: str, form_data: CalendarEventUpdateForm, user: UserModel = Depends(get_verified_user) + request: Request, event_id: str, form_data: CalendarEventUpdateForm, user: UserModel = Depends(get_verified_user) ): + await check_calendar_enabled(request) event = await CalendarEvents.get_event_by_id(event_id) if not event: raise HTTPException(status_code=404, detail='Event not found') @@ -248,7 +267,8 @@ async def update_event( @router.delete('/events/{event_id}/delete') -async def delete_event(event_id: str, user: UserModel = Depends(get_verified_user)): +async def delete_event(request: Request, event_id: str, user: UserModel = Depends(get_verified_user)): + await check_calendar_enabled(request) event = await CalendarEvents.get_event_by_id(event_id) if not event: raise HTTPException(status_code=404, detail='Event not found') @@ -263,9 +283,10 @@ async def delete_event(event_id: str, user: UserModel = Depends(get_verified_use @router.post('/events/{event_id}/rsvp', response_model=dict) async def rsvp_event( - event_id: str, form_data: RSVPForm, user: UserModel = Depends(get_verified_user) + request: Request, event_id: str, form_data: RSVPForm, user: UserModel = Depends(get_verified_user) ): """Update own RSVP status for an event.""" + await check_calendar_enabled(request) if form_data.status not in ('accepted', 'declined', 'tentative', 'pending'): raise HTTPException(status_code=400, detail='Invalid status') @@ -281,15 +302,17 @@ async def rsvp_event( @router.get('/{calendar_id}', response_model=CalendarModel) -async def get_calendar_by_id(calendar_id: str, user: UserModel = Depends(get_verified_user)): +async def get_calendar_by_id(request: Request, calendar_id: str, user: UserModel = Depends(get_verified_user)): + await check_calendar_enabled(request) cal = await _check_calendar_access(calendar_id, user, 'read') return cal @router.post('/{calendar_id}/update', response_model=CalendarModel) async def update_calendar( - calendar_id: str, form_data: CalendarUpdateForm, user: UserModel = Depends(get_verified_user) + request: Request, calendar_id: str, form_data: CalendarUpdateForm, user: UserModel = Depends(get_verified_user) ): + await check_calendar_enabled(request) cal = await _check_calendar_access(calendar_id, user, 'write') # Only owner/admin can change access grants @@ -303,7 +326,8 @@ async def update_calendar( @router.delete('/{calendar_id}/delete') -async def delete_calendar(calendar_id: str, user: UserModel = Depends(get_verified_user)): +async def delete_calendar(request: Request, calendar_id: str, user: UserModel = Depends(get_verified_user)): + await check_calendar_enabled(request) cal = await _check_calendar_access(calendar_id, user, 'write') # Only owner/admin can delete @@ -318,7 +342,8 @@ async def delete_calendar(calendar_id: str, user: UserModel = Depends(get_verifi @router.post('/{calendar_id}/default') -async def set_default_calendar(calendar_id: str, user: UserModel = Depends(get_verified_user)): +async def set_default_calendar(request: Request, calendar_id: str, user: UserModel = Depends(get_verified_user)): + await check_calendar_enabled(request) cal = await Calendars.set_default_calendar(user.id, calendar_id) if not cal: raise HTTPException(status_code=404, detail='Calendar not found') diff --git a/backend/open_webui/utils/tools.py b/backend/open_webui/utils/tools.py index 52a886839..e0791a35f 100644 --- a/backend/open_webui/utils/tools.py +++ b/backend/open_webui/utils/tools.py @@ -557,7 +557,10 @@ async def get_builtin_tools( ) # Calendar tools - search/create/update/delete events - if is_builtin_tool_enabled('calendar'): + if ( + is_builtin_tool_enabled('calendar') + and getattr(request.app.state.config, 'ENABLE_CALENDAR', False) + ): builtin_functions.extend( [search_calendar_events, create_calendar_event, update_calendar_event, delete_calendar_event] ) diff --git a/src/lib/components/admin/Settings/General.svelte b/src/lib/components/admin/Settings/General.svelte index f2ba4a3ee..f535bd68e 100644 --- a/src/lib/components/admin/Settings/General.svelte +++ b/src/lib/components/admin/Settings/General.svelte @@ -756,6 +756,14 @@ +
+
+ {$i18n.t('Calendar')} ({$i18n.t('Beta')}) +
+ + +
+
{$i18n.t('Memories')} ({$i18n.t('Beta')}) diff --git a/src/lib/components/layout/Sidebar/UserMenu.svelte b/src/lib/components/layout/Sidebar/UserMenu.svelte index 30c29962b..b159a0dd6 100644 --- a/src/lib/components/layout/Sidebar/UserMenu.svelte +++ b/src/lib/components/layout/Sidebar/UserMenu.svelte @@ -250,7 +250,7 @@ {/if} - {#if $user?.role === 'admin' || $user?.permissions?.features?.calendar} + {#if $config?.features?.enable_calendar && ($user?.role === 'admin' || $user?.permissions?.features?.calendar)}