From c562d35345ab825783a0b373ab8c7fce6598da2c Mon Sep 17 00:00:00 2001 From: "Adrian A. Baumann" Date: Sat, 28 Feb 2026 19:55:54 +0100 Subject: [PATCH] feat: add SQLAlchemy models and database setup --- backend/app/__init__.py | 0 backend/app/database.py | 31 +++++++++++ backend/app/models.py | 114 ++++++++++++++++++++++++++++++++++++++++ 3 files changed, 145 insertions(+) create mode 100644 backend/app/__init__.py create mode 100644 backend/app/database.py create mode 100644 backend/app/models.py diff --git a/backend/app/__init__.py b/backend/app/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/backend/app/database.py b/backend/app/database.py new file mode 100644 index 0000000..d709403 --- /dev/null +++ b/backend/app/database.py @@ -0,0 +1,31 @@ +from sqlalchemy import create_engine +from sqlalchemy.orm import DeclarativeBase, sessionmaker +from pydantic_settings import BaseSettings + + +class Settings(BaseSettings): + database_url: str + jwt_secret_key: str + jwt_algorithm: str = "HS256" + jwt_expire_minutes: int = 60 + + class Config: + env_file = ".env" + + +settings = Settings() + +engine = create_engine(settings.database_url) +SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine) + + +class Base(DeclarativeBase): + pass + + +def get_db(): + db = SessionLocal() + try: + yield db + finally: + db.close() diff --git a/backend/app/models.py b/backend/app/models.py new file mode 100644 index 0000000..58d513a --- /dev/null +++ b/backend/app/models.py @@ -0,0 +1,114 @@ +from datetime import datetime +from sqlalchemy import ( + Boolean, DateTime, ForeignKey, Integer, String, Text, UniqueConstraint, func +) +from sqlalchemy.orm import Mapped, mapped_column, relationship +from app.database import Base + + +class User(Base): + __tablename__ = "users" + + id: Mapped[int] = mapped_column(Integer, primary_key=True) + username: Mapped[str] = mapped_column(String(64), unique=True, nullable=False) + email: Mapped[str] = mapped_column(String(255), unique=True, nullable=False) + hashed_password: Mapped[str] = mapped_column(String(255), nullable=False) + is_active: Mapped[bool] = mapped_column(Boolean, default=True, nullable=False) + configs: Mapped[list["Config"]] = relationship("Config", back_populates="owner") + + +class Config(Base): + __tablename__ = "configs" + + id: Mapped[int] = mapped_column(Integer, primary_key=True) + name: Mapped[str] = mapped_column(String(128), nullable=False) + description: Mapped[str] = mapped_column(Text, default="") + is_active: Mapped[bool] = mapped_column(Boolean, default=True) + created_at: Mapped[datetime] = mapped_column(DateTime, server_default=func.now()) + updated_at: Mapped[datetime] = mapped_column(DateTime, server_default=func.now(), onupdate=func.now()) + owner_id: Mapped[int] = mapped_column(Integer, ForeignKey("users.id"), nullable=False) + + owner: Mapped["User"] = relationship("User", back_populates="configs") + zones: Mapped[list["Zone"]] = relationship("Zone", back_populates="config", cascade="all, delete-orphan") + interfaces: Mapped[list["Interface"]] = relationship("Interface", back_populates="config", cascade="all, delete-orphan") + policies: Mapped[list["Policy"]] = relationship("Policy", back_populates="config", cascade="all, delete-orphan", order_by="Policy.position") + rules: Mapped[list["Rule"]] = relationship("Rule", back_populates="config", cascade="all, delete-orphan", order_by="Rule.position") + masq_entries: Mapped[list["Masq"]] = relationship("Masq", back_populates="config", cascade="all, delete-orphan") + + +class Zone(Base): + __tablename__ = "zones" + __table_args__ = (UniqueConstraint("config_id", "name", name="uq_zone_name_per_config"),) + + id: Mapped[int] = mapped_column(Integer, primary_key=True) + config_id: Mapped[int] = mapped_column(Integer, ForeignKey("configs.id"), nullable=False) + name: Mapped[str] = mapped_column(String(32), nullable=False) + type: Mapped[str] = mapped_column(String(16), nullable=False) + options: Mapped[str] = mapped_column(Text, default="") + + config: Mapped["Config"] = relationship("Config", back_populates="zones") + interfaces: Mapped[list["Interface"]] = relationship("Interface", back_populates="zone") + + +class Interface(Base): + __tablename__ = "interfaces" + + id: Mapped[int] = mapped_column(Integer, primary_key=True) + config_id: Mapped[int] = mapped_column(Integer, ForeignKey("configs.id"), nullable=False) + name: Mapped[str] = mapped_column(String(32), nullable=False) + zone_id: Mapped[int] = mapped_column(Integer, ForeignKey("zones.id"), nullable=False) + options: Mapped[str] = mapped_column(Text, default="") + + config: Mapped["Config"] = relationship("Config", back_populates="interfaces") + zone: Mapped["Zone"] = relationship("Zone", back_populates="interfaces") + + +class Policy(Base): + __tablename__ = "policies" + + id: Mapped[int] = mapped_column(Integer, primary_key=True) + config_id: Mapped[int] = mapped_column(Integer, ForeignKey("configs.id"), nullable=False) + src_zone_id: Mapped[int] = mapped_column(Integer, ForeignKey("zones.id"), nullable=False) + dst_zone_id: Mapped[int] = mapped_column(Integer, ForeignKey("zones.id"), nullable=False) + policy: Mapped[str] = mapped_column(String(16), nullable=False) + log_level: Mapped[str] = mapped_column(String(16), default="") + comment: Mapped[str] = mapped_column(Text, default="") + position: Mapped[int] = mapped_column(Integer, default=0) + + config: Mapped["Config"] = relationship("Config", back_populates="policies") + src_zone: Mapped["Zone"] = relationship("Zone", foreign_keys=[src_zone_id]) + dst_zone: Mapped["Zone"] = relationship("Zone", foreign_keys=[dst_zone_id]) + + +class Rule(Base): + __tablename__ = "rules" + + id: Mapped[int] = mapped_column(Integer, primary_key=True) + config_id: Mapped[int] = mapped_column(Integer, ForeignKey("configs.id"), nullable=False) + action: Mapped[str] = mapped_column(String(32), nullable=False) + src_zone_id: Mapped[int | None] = mapped_column(Integer, ForeignKey("zones.id"), nullable=True) + dst_zone_id: Mapped[int | None] = mapped_column(Integer, ForeignKey("zones.id"), nullable=True) + src_ip: Mapped[str] = mapped_column(String(64), default="") + dst_ip: Mapped[str] = mapped_column(String(64), default="") + proto: Mapped[str] = mapped_column(String(16), default="") + dport: Mapped[str] = mapped_column(String(64), default="") + sport: Mapped[str] = mapped_column(String(64), default="") + comment: Mapped[str] = mapped_column(Text, default="") + position: Mapped[int] = mapped_column(Integer, default=0) + + config: Mapped["Config"] = relationship("Config", back_populates="rules") + src_zone: Mapped["Zone | None"] = relationship("Zone", foreign_keys=[src_zone_id]) + dst_zone: Mapped["Zone | None"] = relationship("Zone", foreign_keys=[dst_zone_id]) + + +class Masq(Base): + __tablename__ = "masq" + + id: Mapped[int] = mapped_column(Integer, primary_key=True) + config_id: Mapped[int] = mapped_column(Integer, ForeignKey("configs.id"), nullable=False) + source_network: Mapped[str] = mapped_column(String(64), nullable=False) + out_interface: Mapped[str] = mapped_column(String(32), nullable=False) + to_address: Mapped[str] = mapped_column(String(64), default="") + comment: Mapped[str] = mapped_column(Text, default="") + + config: Mapped["Config"] = relationship("Config", back_populates="masq_entries")