Sistem web pentru rezervarea de birouri și săli de ședință cu flux de aprobare administrativă. Stack: FastAPI + Vue.js 3 + SQLite + TypeScript Features implementate: - Autentificare JWT + Self-registration cu email verification - CRUD Spații, Utilizatori, Settings (Admin) - Calendar interactiv (FullCalendar) cu drag-and-drop - Creare rezervări cu validare (durată, program, overlap, max/zi) - Rezervări recurente (săptămânal) - Admin: aprobare/respingere/anulare cereri - Admin: creare directă rezervări (bypass approval) - Admin: editare orice rezervare - User: editare/anulare rezervări proprii - Notificări in-app (bell icon + dropdown) - Notificări email (async SMTP cu BackgroundTasks) - Jurnal acțiuni administrative (audit log) - Rapoarte avansate (utilizare, top users, approval rate) - Șabloane rezervări (booking templates) - Atașamente fișiere (upload/download) - Conflict warnings (verificare disponibilitate real-time) - Integrare Google Calendar (OAuth2) - Suport timezone (UTC storage + user preference) - 225+ teste backend Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
174 lines
4.8 KiB
Python
174 lines
4.8 KiB
Python
"""Google Calendar integration service."""
|
|
from datetime import datetime
|
|
from typing import Optional
|
|
|
|
from google.auth.transport.requests import Request
|
|
from google.oauth2.credentials import Credentials
|
|
from googleapiclient.discovery import build
|
|
from sqlalchemy.orm import Session
|
|
|
|
from app.core.config import settings
|
|
from app.models.booking import Booking
|
|
from app.models.google_calendar_token import GoogleCalendarToken
|
|
|
|
|
|
def get_google_calendar_service(db: Session, user_id: int):
|
|
"""
|
|
Get authenticated Google Calendar service for user.
|
|
|
|
Args:
|
|
db: Database session
|
|
user_id: User ID
|
|
|
|
Returns:
|
|
Google Calendar service object or None if not connected
|
|
"""
|
|
token_record = (
|
|
db.query(GoogleCalendarToken)
|
|
.filter(GoogleCalendarToken.user_id == user_id)
|
|
.first()
|
|
)
|
|
|
|
if not token_record:
|
|
return None
|
|
|
|
# Create credentials
|
|
creds = Credentials(
|
|
token=token_record.access_token,
|
|
refresh_token=token_record.refresh_token,
|
|
token_uri="https://oauth2.googleapis.com/token",
|
|
client_id=settings.google_client_id,
|
|
client_secret=settings.google_client_secret,
|
|
)
|
|
|
|
# Refresh if expired
|
|
if creds.expired and creds.refresh_token:
|
|
try:
|
|
creds.refresh(Request())
|
|
|
|
# Update tokens in DB
|
|
token_record.access_token = creds.token # type: ignore[assignment]
|
|
if creds.expiry:
|
|
token_record.token_expiry = creds.expiry # type: ignore[assignment]
|
|
db.commit()
|
|
except Exception as e:
|
|
print(f"Failed to refresh Google token: {e}")
|
|
return None
|
|
|
|
# Build service
|
|
try:
|
|
service = build("calendar", "v3", credentials=creds)
|
|
return service
|
|
except Exception as e:
|
|
print(f"Failed to build Google Calendar service: {e}")
|
|
return None
|
|
|
|
|
|
def create_calendar_event(
|
|
db: Session, booking: Booking, user_id: int
|
|
) -> Optional[str]:
|
|
"""
|
|
Create Google Calendar event for booking.
|
|
|
|
Args:
|
|
db: Database session
|
|
booking: Booking object
|
|
user_id: User ID
|
|
|
|
Returns:
|
|
Google Calendar event ID or None if failed
|
|
"""
|
|
try:
|
|
service = get_google_calendar_service(db, user_id)
|
|
if not service:
|
|
return None
|
|
|
|
# Create event
|
|
event = {
|
|
"summary": f"{booking.space.name}: {booking.title}",
|
|
"description": booking.description or "",
|
|
"start": {
|
|
"dateTime": booking.start_datetime.isoformat(), # type: ignore[union-attr]
|
|
"timeZone": "UTC",
|
|
},
|
|
"end": {
|
|
"dateTime": booking.end_datetime.isoformat(), # type: ignore[union-attr]
|
|
"timeZone": "UTC",
|
|
},
|
|
}
|
|
|
|
created_event = service.events().insert(calendarId="primary", body=event).execute()
|
|
|
|
return created_event.get("id")
|
|
except Exception as e:
|
|
print(f"Failed to create Google Calendar event: {e}")
|
|
return None
|
|
|
|
|
|
def update_calendar_event(
|
|
db: Session, booking: Booking, user_id: int, event_id: str
|
|
) -> bool:
|
|
"""
|
|
Update Google Calendar event for booking.
|
|
|
|
Args:
|
|
db: Database session
|
|
booking: Booking object
|
|
user_id: User ID
|
|
event_id: Google Calendar event ID
|
|
|
|
Returns:
|
|
True if successful, False otherwise
|
|
"""
|
|
try:
|
|
service = get_google_calendar_service(db, user_id)
|
|
if not service:
|
|
return False
|
|
|
|
# Update event
|
|
event = {
|
|
"summary": f"{booking.space.name}: {booking.title}",
|
|
"description": booking.description or "",
|
|
"start": {
|
|
"dateTime": booking.start_datetime.isoformat(), # type: ignore[union-attr]
|
|
"timeZone": "UTC",
|
|
},
|
|
"end": {
|
|
"dateTime": booking.end_datetime.isoformat(), # type: ignore[union-attr]
|
|
"timeZone": "UTC",
|
|
},
|
|
}
|
|
|
|
service.events().update(
|
|
calendarId="primary", eventId=event_id, body=event
|
|
).execute()
|
|
|
|
return True
|
|
except Exception as e:
|
|
print(f"Failed to update Google Calendar event: {e}")
|
|
return False
|
|
|
|
|
|
def delete_calendar_event(db: Session, event_id: str, user_id: int) -> bool:
|
|
"""
|
|
Delete Google Calendar event.
|
|
|
|
Args:
|
|
db: Database session
|
|
event_id: Google Calendar event ID
|
|
user_id: User ID
|
|
|
|
Returns:
|
|
True if successful, False otherwise
|
|
"""
|
|
try:
|
|
service = get_google_calendar_service(db, user_id)
|
|
if not service:
|
|
return False
|
|
|
|
service.events().delete(calendarId="primary", eventId=event_id).execute()
|
|
return True
|
|
except Exception as e:
|
|
print(f"Failed to delete Google Calendar event: {e}")
|
|
return False
|