109 lines
4.3 KiB
Python
109 lines
4.3 KiB
Python
from sqlalchemy import (
|
|
Column, String, Integer, Boolean, DateTime, Text, ForeignKey, create_engine
|
|
)
|
|
from sqlalchemy.orm import declarative_base, relationship, sessionmaker
|
|
from datetime import datetime, timezone
|
|
import os
|
|
|
|
DATABASE_URL = os.getenv("DATABASE_URL", "postgresql://cardsync:changeme@localhost:5432/cardsync")
|
|
|
|
engine = create_engine(DATABASE_URL)
|
|
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
|
|
Base = declarative_base()
|
|
|
|
|
|
def get_db():
|
|
db = SessionLocal()
|
|
try:
|
|
yield db
|
|
finally:
|
|
db.close()
|
|
|
|
|
|
class SynologyConfig(Base):
|
|
"""Zentrale Synology-Serverkonfiguration (Admin-seitig)"""
|
|
__tablename__ = "synology_config"
|
|
|
|
id = Column(Integer, primary_key=True, index=True)
|
|
server_url = Column(String(500), nullable=False)
|
|
username = Column(String(255), nullable=False)
|
|
password = Column(Text, nullable=False)
|
|
updated_at = Column(DateTime, default=lambda: datetime.now(timezone.utc))
|
|
|
|
|
|
class AzureGroupConfig(Base):
|
|
"""Konfiguration für Azure AD Gruppen-basiertes User-Management"""
|
|
__tablename__ = "azure_group_config"
|
|
|
|
id = Column(Integer, primary_key=True, index=True)
|
|
group_id = Column(String(255), nullable=False) # Azure AD Group Object ID
|
|
group_name = Column(String(255)) # Anzeigename (cached)
|
|
auto_sync_members = Column(Boolean, default=True) # Mitglieder regelmäßig abgleichen
|
|
member_sync_interval_hours = Column(Integer, default=6) # Wie oft Gruppenmitgliedschaft prüfen
|
|
default_carddav_folder = Column(String(500)) # Default für neue User
|
|
default_sync_interval_minutes = Column(Integer, default=60)
|
|
default_sync_enabled = Column(Boolean, default=True) # Neue User direkt aktiviert?
|
|
last_member_sync_at = Column(DateTime)
|
|
last_member_sync_status = Column(String(50))
|
|
last_member_sync_message = Column(Text)
|
|
updated_at = Column(DateTime, default=lambda: datetime.now(timezone.utc))
|
|
|
|
|
|
class User(Base):
|
|
"""Benutzer (per OAuth oder per Azure-AD-Gruppen-Import angelegt)"""
|
|
__tablename__ = "users"
|
|
|
|
id = Column(Integer, primary_key=True, index=True)
|
|
ms_user_id = Column(String(255), unique=True, nullable=False, index=True)
|
|
email = Column(String(255), unique=True, nullable=False, index=True)
|
|
display_name = Column(String(255))
|
|
|
|
# Delegated-Mode (OAuth) Tokens — optional, nur wenn User sich selbst angemeldet hat
|
|
ms_access_token = Column(Text)
|
|
ms_refresh_token = Column(Text)
|
|
token_expires_at = Column(DateTime)
|
|
|
|
# Wie wurde der User angelegt? "oauth" oder "group_import"
|
|
source = Column(String(50), default="oauth")
|
|
|
|
sync_enabled = Column(Boolean, default=False)
|
|
carddav_folder = Column(String(500))
|
|
sync_interval_minutes = Column(Integer, default=60)
|
|
last_sync_at = Column(DateTime)
|
|
last_sync_status = Column(String(50))
|
|
last_sync_message = Column(Text)
|
|
created_at = Column(DateTime, default=lambda: datetime.now(timezone.utc))
|
|
updated_at = Column(DateTime, default=lambda: datetime.now(timezone.utc))
|
|
|
|
sync_logs = relationship("SyncLog", back_populates="user", cascade="all, delete-orphan")
|
|
|
|
|
|
class SyncLog(Base):
|
|
"""Protokoll aller Sync-Vorgänge"""
|
|
__tablename__ = "sync_logs"
|
|
|
|
id = Column(Integer, primary_key=True, index=True)
|
|
user_id = Column(Integer, ForeignKey("users.id"), nullable=False)
|
|
started_at = Column(DateTime, default=lambda: datetime.now(timezone.utc))
|
|
finished_at = Column(DateTime)
|
|
status = Column(String(50))
|
|
contacts_synced = Column(Integer, default=0)
|
|
contacts_created = Column(Integer, default=0)
|
|
contacts_updated = Column(Integer, default=0)
|
|
contacts_deleted = Column(Integer, default=0)
|
|
error_message = Column(Text)
|
|
|
|
user = relationship("User", back_populates="sync_logs")
|
|
|
|
|
|
def create_tables():
|
|
Base.metadata.create_all(bind=engine)
|
|
|
|
# Migration für bestehende DBs: neue Spalte "source" ergänzen falls sie fehlt
|
|
from sqlalchemy import inspect, text
|
|
inspector = inspect(engine)
|
|
if "users" in inspector.get_table_names():
|
|
cols = [c["name"] for c in inspector.get_columns("users")]
|
|
if "source" not in cols:
|
|
with engine.begin() as conn:
|
|
conn.execute(text("ALTER TABLE users ADD COLUMN source VARCHAR(50) DEFAULT 'oauth'"))
|