feat: Space Booking System - MVP complet
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>
This commit is contained in:
173
backend/app/services/google_calendar_service.py
Normal file
173
backend/app/services/google_calendar_service.py
Normal file
@@ -0,0 +1,173 @@
|
||||
"""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
|
||||
Reference in New Issue
Block a user