Files
gartenmanager/backend/alembic/versions/001_initial.py

156 lines
7.9 KiB
Python
Raw Normal View History

"""initial schema
Revision ID: 001
Revises:
Create Date: 2026-04-06
"""
from typing import Sequence, Union
import sqlalchemy as sa
from alembic import op
import sqlalchemy.dialects.postgresql as pg
revision: str = "001"
down_revision: Union[str, None] = None
branch_labels: Union[str, Sequence[str], None] = None
depends_on: Union[str, Sequence[str], None] = None
def upgrade() -> None:
# Enums
op.execute("CREATE TYPE tenant_role AS ENUM ('READ_ONLY', 'READ_WRITE', 'TENANT_ADMIN')")
op.execute("CREATE TYPE nutrient_demand AS ENUM ('schwach', 'mittel', 'stark')")
op.execute("CREATE TYPE water_demand AS ENUM ('wenig', 'mittel', 'viel')")
op.execute("CREATE TYPE compatibility_rating AS ENUM ('gut', 'neutral', 'schlecht')")
op.execute("CREATE TYPE location_type AS ENUM ('sonnig', 'halbschatten', 'schatten')")
op.execute("CREATE TYPE soil_type AS ENUM ('normal', 'sandig', 'lehmig', 'humusreich')")
# users
op.create_table(
"users",
sa.Column("id", pg.UUID(as_uuid=True), primary_key=True),
sa.Column("email", sa.String(255), nullable=False, unique=True),
sa.Column("hashed_password", sa.String(255), nullable=False),
sa.Column("full_name", sa.String(255), nullable=False),
sa.Column("is_active", sa.Boolean, nullable=False, server_default="true"),
sa.Column("is_superadmin", sa.Boolean, nullable=False, server_default="false"),
sa.Column("created_at", sa.DateTime(timezone=True), server_default=sa.func.now(), nullable=False),
sa.Column("updated_at", sa.DateTime(timezone=True), server_default=sa.func.now(), nullable=False),
)
op.create_index("ix_users_id", "users", ["id"])
op.create_index("ix_users_email", "users", ["email"])
# tenants
op.create_table(
"tenants",
sa.Column("id", pg.UUID(as_uuid=True), primary_key=True),
sa.Column("name", sa.String(255), nullable=False),
sa.Column("slug", sa.String(100), nullable=False, unique=True),
sa.Column("created_at", sa.DateTime(timezone=True), server_default=sa.func.now(), nullable=False),
sa.Column("updated_at", sa.DateTime(timezone=True), server_default=sa.func.now(), nullable=False),
)
op.create_index("ix_tenants_id", "tenants", ["id"])
op.create_index("ix_tenants_slug", "tenants", ["slug"])
# user_tenants
op.create_table(
"user_tenants",
sa.Column("user_id", pg.UUID(as_uuid=True), sa.ForeignKey("users.id", ondelete="CASCADE"), primary_key=True),
sa.Column("tenant_id", pg.UUID(as_uuid=True), sa.ForeignKey("tenants.id", ondelete="CASCADE"), primary_key=True),
sa.Column("role", sa.Enum("READ_ONLY", "READ_WRITE", "TENANT_ADMIN", name="tenant_role", create_type=False), nullable=False),
sa.UniqueConstraint("user_id", "tenant_id", name="uq_user_tenant"),
)
# plant_families
op.create_table(
"plant_families",
sa.Column("id", pg.UUID(as_uuid=True), primary_key=True),
sa.Column("name", sa.String(255), nullable=False, unique=True),
sa.Column("latin_name", sa.String(255), nullable=False, unique=True),
)
op.create_index("ix_plant_families_id", "plant_families", ["id"])
# plants
op.create_table(
"plants",
sa.Column("id", pg.UUID(as_uuid=True), primary_key=True),
sa.Column("tenant_id", pg.UUID(as_uuid=True), sa.ForeignKey("tenants.id", ondelete="CASCADE"), nullable=True),
sa.Column("family_id", pg.UUID(as_uuid=True), sa.ForeignKey("plant_families.id", ondelete="RESTRICT"), nullable=False),
sa.Column("name", sa.String(255), nullable=False),
sa.Column("latin_name", sa.String(255), nullable=True),
sa.Column("nutrient_demand", sa.Enum("schwach", "mittel", "stark", name="nutrient_demand", create_type=False), nullable=False),
sa.Column("water_demand", sa.Enum("wenig", "mittel", "viel", name="water_demand", create_type=False), nullable=False),
sa.Column("spacing_cm", sa.Integer, nullable=False),
sa.Column("sowing_start_month", sa.Integer, nullable=False),
sa.Column("sowing_end_month", sa.Integer, nullable=False),
sa.Column("rest_years", sa.Integer, nullable=False, server_default="0"),
sa.Column("notes", sa.Text, nullable=True),
sa.Column("is_active", sa.Boolean, nullable=False, server_default="true"),
)
op.create_index("ix_plants_id", "plants", ["id"])
op.create_index("ix_plants_tenant_id", "plants", ["tenant_id"])
op.create_index("ix_plants_family_id", "plants", ["family_id"])
# plant_compatibilities
op.create_table(
"plant_compatibilities",
sa.Column("id", pg.UUID(as_uuid=True), primary_key=True),
sa.Column("plant_id_a", pg.UUID(as_uuid=True), sa.ForeignKey("plants.id", ondelete="CASCADE"), nullable=False),
sa.Column("plant_id_b", pg.UUID(as_uuid=True), sa.ForeignKey("plants.id", ondelete="CASCADE"), nullable=False),
sa.Column("rating", sa.Enum("gut", "neutral", "schlecht", name="compatibility_rating", create_type=False), nullable=False),
sa.Column("reason", sa.Text, nullable=True),
sa.UniqueConstraint("plant_id_a", "plant_id_b", name="uq_plant_compatibility"),
)
# beds
op.create_table(
"beds",
sa.Column("id", pg.UUID(as_uuid=True), primary_key=True),
sa.Column("tenant_id", pg.UUID(as_uuid=True), sa.ForeignKey("tenants.id", ondelete="CASCADE"), nullable=False),
sa.Column("name", sa.String(255), nullable=False),
sa.Column("width_m", sa.Numeric(5, 2), nullable=False),
sa.Column("length_m", sa.Numeric(5, 2), nullable=False),
sa.Column("location", sa.Enum("sonnig", "halbschatten", "schatten", name="location_type", create_type=False), nullable=False),
sa.Column("soil_type", sa.Enum("normal", "sandig", "lehmig", "humusreich", name="soil_type", create_type=False), nullable=False, server_default="normal"),
sa.Column("notes", sa.Text, nullable=True),
sa.Column("is_active", sa.Boolean, nullable=False, server_default="true"),
sa.Column("created_at", sa.DateTime(timezone=True), server_default=sa.func.now(), nullable=False),
sa.Column("updated_at", sa.DateTime(timezone=True), server_default=sa.func.now(), nullable=False),
)
op.create_index("ix_beds_id", "beds", ["id"])
op.create_index("ix_beds_tenant_id", "beds", ["tenant_id"])
# bed_plantings
op.create_table(
"bed_plantings",
sa.Column("id", pg.UUID(as_uuid=True), primary_key=True),
sa.Column("bed_id", pg.UUID(as_uuid=True), sa.ForeignKey("beds.id", ondelete="CASCADE"), nullable=False),
sa.Column("plant_id", pg.UUID(as_uuid=True), sa.ForeignKey("plants.id", ondelete="RESTRICT"), nullable=False),
sa.Column("area_m2", sa.Numeric(5, 2), nullable=True),
sa.Column("count", sa.Integer, nullable=True),
sa.Column("planted_date", sa.Date, nullable=True),
sa.Column("removed_date", sa.Date, nullable=True),
sa.Column("notes", sa.Text, nullable=True),
sa.Column("created_at", sa.DateTime(timezone=True), server_default=sa.func.now(), nullable=False),
)
op.create_index("ix_bed_plantings_id", "bed_plantings", ["id"])
op.create_index("ix_bed_plantings_bed_id", "bed_plantings", ["bed_id"])
op.create_index("ix_bed_plantings_plant_id", "bed_plantings", ["plant_id"])
def downgrade() -> None:
op.drop_table("bed_plantings")
op.drop_table("beds")
op.drop_table("plant_compatibilities")
op.drop_table("plants")
op.drop_table("plant_families")
op.drop_table("user_tenants")
op.drop_table("tenants")
op.drop_table("users")
op.execute("DROP TYPE IF EXISTS soil_type")
op.execute("DROP TYPE IF EXISTS location_type")
op.execute("DROP TYPE IF EXISTS compatibility_rating")
op.execute("DROP TYPE IF EXISTS water_demand")
op.execute("DROP TYPE IF EXISTS nutrient_demand")
op.execute("DROP TYPE IF EXISTS tenant_role")