This commit is contained in:
Timothy Jaeryang Baek
2026-04-19 21:48:27 +09:00
parent 8d739e2aba
commit 4a5401b417
5 changed files with 73 additions and 14 deletions
@@ -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),
+31 -9
View File
@@ -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
+8 -2
View File
@@ -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
+32 -1
View File
@@ -5,7 +5,7 @@ export type CalendarModel = {
user_id: string;
name: string;
color: string | null;
is_system: boolean;
is_default: boolean;
data: Record<string, any> | null;
meta: Record<string, any> | 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<CalendarModel> => {
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 (
+1 -1
View File
@@ -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();