forked from yvv/decision
Alembic : migration initiale + chaîne idempotente IF NOT EXISTS
ci/woodpecker/push/woodpecker Pipeline failed
ci/woodpecker/push/woodpecker Pipeline failed
- 0b9c1d2e3f4a : migration initiale (CREATE TABLE IF NOT EXISTS) — safe sur une DB déjà bootstrappée via create_all() - 70914b334cfb : ADD COLUMN IF NOT EXISTS (organization_id) — was down_revision=None - b78571ae9e00 : CREATE TABLE IF NOT EXISTS qualification_protocols - c4e812fb3a01 : CREATE TABLE IF NOT EXISTS groups + group_members - d91a3c7f8b02 : ADD COLUMN IF NOT EXISTS origin (mandates) - Dockerfile prod : restaure alembic upgrade head au démarrage Sur le serveur prod, exécuter une fois : docker exec <projet>-backend alembic upgrade head Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,292 @@
|
|||||||
|
"""initial schema
|
||||||
|
|
||||||
|
Revision ID: 0b9c1d2e3f4a
|
||||||
|
Revises:
|
||||||
|
Create Date: 2026-04-23 00:00:00.000000+00:00
|
||||||
|
|
||||||
|
Idempotent: uses CREATE TABLE IF NOT EXISTS so it is safe to run
|
||||||
|
against a DB that was already bootstrapped via SQLAlchemy create_all().
|
||||||
|
"""
|
||||||
|
|
||||||
|
from typing import Sequence, Union
|
||||||
|
|
||||||
|
from alembic import op
|
||||||
|
|
||||||
|
revision: str = '0b9c1d2e3f4a'
|
||||||
|
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:
|
||||||
|
op.execute("""
|
||||||
|
CREATE TABLE IF NOT EXISTS duniter_identities (
|
||||||
|
id UUID PRIMARY KEY,
|
||||||
|
address VARCHAR(64) NOT NULL,
|
||||||
|
display_name VARCHAR(128),
|
||||||
|
wot_status VARCHAR(32) NOT NULL DEFAULT 'unknown',
|
||||||
|
is_smith BOOLEAN NOT NULL DEFAULT FALSE,
|
||||||
|
is_techcomm BOOLEAN NOT NULL DEFAULT FALSE,
|
||||||
|
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
||||||
|
updated_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
||||||
|
)
|
||||||
|
""")
|
||||||
|
op.execute("CREATE UNIQUE INDEX IF NOT EXISTS ix_duniter_identities_address ON duniter_identities (address)")
|
||||||
|
|
||||||
|
op.execute("""
|
||||||
|
CREATE TABLE IF NOT EXISTS sessions (
|
||||||
|
id UUID PRIMARY KEY,
|
||||||
|
token_hash VARCHAR(128) NOT NULL,
|
||||||
|
identity_id UUID NOT NULL REFERENCES duniter_identities(id),
|
||||||
|
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
||||||
|
expires_at TIMESTAMPTZ NOT NULL
|
||||||
|
)
|
||||||
|
""")
|
||||||
|
op.execute("CREATE UNIQUE INDEX IF NOT EXISTS ix_sessions_token_hash ON sessions (token_hash)")
|
||||||
|
|
||||||
|
op.execute("""
|
||||||
|
CREATE TABLE IF NOT EXISTS organizations (
|
||||||
|
id UUID PRIMARY KEY,
|
||||||
|
name VARCHAR(128) NOT NULL,
|
||||||
|
slug VARCHAR(128) NOT NULL,
|
||||||
|
org_type VARCHAR(64) NOT NULL DEFAULT 'community',
|
||||||
|
is_transparent BOOLEAN NOT NULL DEFAULT FALSE,
|
||||||
|
color VARCHAR(32),
|
||||||
|
icon VARCHAR(64),
|
||||||
|
description TEXT,
|
||||||
|
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
||||||
|
)
|
||||||
|
""")
|
||||||
|
op.execute("CREATE UNIQUE INDEX IF NOT EXISTS ix_organizations_slug ON organizations (slug)")
|
||||||
|
|
||||||
|
op.execute("""
|
||||||
|
CREATE TABLE IF NOT EXISTS org_members (
|
||||||
|
id UUID PRIMARY KEY,
|
||||||
|
org_id UUID NOT NULL REFERENCES organizations(id),
|
||||||
|
identity_id UUID NOT NULL REFERENCES duniter_identities(id),
|
||||||
|
role VARCHAR(32) NOT NULL DEFAULT 'member',
|
||||||
|
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
||||||
|
)
|
||||||
|
""")
|
||||||
|
|
||||||
|
op.execute("""
|
||||||
|
CREATE TABLE IF NOT EXISTS formula_configs (
|
||||||
|
id UUID PRIMARY KEY,
|
||||||
|
name VARCHAR(128) NOT NULL,
|
||||||
|
description TEXT,
|
||||||
|
duration_days INTEGER NOT NULL DEFAULT 30,
|
||||||
|
majority_pct INTEGER NOT NULL DEFAULT 50,
|
||||||
|
base_exponent FLOAT NOT NULL DEFAULT 0.1,
|
||||||
|
gradient_exponent FLOAT NOT NULL DEFAULT 0.2,
|
||||||
|
constant_base FLOAT NOT NULL DEFAULT 0.0,
|
||||||
|
smith_exponent FLOAT,
|
||||||
|
techcomm_exponent FLOAT,
|
||||||
|
nuanced_min_participants INTEGER,
|
||||||
|
nuanced_threshold_pct INTEGER,
|
||||||
|
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
||||||
|
)
|
||||||
|
""")
|
||||||
|
|
||||||
|
op.execute("""
|
||||||
|
CREATE TABLE IF NOT EXISTS voting_protocols (
|
||||||
|
id UUID PRIMARY KEY,
|
||||||
|
name VARCHAR(128) NOT NULL,
|
||||||
|
description TEXT,
|
||||||
|
vote_type VARCHAR(32) NOT NULL,
|
||||||
|
formula_config_id UUID NOT NULL REFERENCES formula_configs(id),
|
||||||
|
mode_params VARCHAR(64),
|
||||||
|
is_meta_governed BOOLEAN NOT NULL DEFAULT FALSE,
|
||||||
|
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
||||||
|
)
|
||||||
|
""")
|
||||||
|
|
||||||
|
op.execute("""
|
||||||
|
CREATE TABLE IF NOT EXISTS sanctuary_entries (
|
||||||
|
id UUID PRIMARY KEY,
|
||||||
|
entry_type VARCHAR(64) NOT NULL,
|
||||||
|
reference_id UUID NOT NULL,
|
||||||
|
title VARCHAR(256),
|
||||||
|
content_hash VARCHAR(128) NOT NULL,
|
||||||
|
ipfs_cid VARCHAR(128),
|
||||||
|
chain_tx_hash VARCHAR(128),
|
||||||
|
chain_block INTEGER,
|
||||||
|
metadata_json TEXT,
|
||||||
|
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
||||||
|
)
|
||||||
|
""")
|
||||||
|
|
||||||
|
op.execute("""
|
||||||
|
CREATE TABLE IF NOT EXISTS blockchain_cache (
|
||||||
|
id UUID PRIMARY KEY,
|
||||||
|
cache_key VARCHAR(256) NOT NULL,
|
||||||
|
cache_value JSON NOT NULL,
|
||||||
|
fetched_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
||||||
|
expires_at TIMESTAMPTZ NOT NULL
|
||||||
|
)
|
||||||
|
""")
|
||||||
|
op.execute("CREATE UNIQUE INDEX IF NOT EXISTS ix_blockchain_cache_cache_key ON blockchain_cache (cache_key)")
|
||||||
|
|
||||||
|
op.execute("""
|
||||||
|
CREATE TABLE IF NOT EXISTS documents (
|
||||||
|
id UUID PRIMARY KEY,
|
||||||
|
slug VARCHAR(128) NOT NULL,
|
||||||
|
title VARCHAR(256) NOT NULL,
|
||||||
|
doc_type VARCHAR(64) NOT NULL,
|
||||||
|
version VARCHAR(32) NOT NULL DEFAULT '0.1.0',
|
||||||
|
status VARCHAR(32) NOT NULL DEFAULT 'draft',
|
||||||
|
description TEXT,
|
||||||
|
ipfs_cid VARCHAR(128),
|
||||||
|
chain_anchor VARCHAR(128),
|
||||||
|
genesis_json TEXT,
|
||||||
|
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
||||||
|
updated_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
||||||
|
)
|
||||||
|
""")
|
||||||
|
op.execute("CREATE UNIQUE INDEX IF NOT EXISTS ix_documents_slug ON documents (slug)")
|
||||||
|
|
||||||
|
op.execute("""
|
||||||
|
CREATE TABLE IF NOT EXISTS decisions (
|
||||||
|
id UUID PRIMARY KEY,
|
||||||
|
title VARCHAR(256) NOT NULL,
|
||||||
|
description TEXT,
|
||||||
|
context TEXT,
|
||||||
|
decision_type VARCHAR(64) NOT NULL,
|
||||||
|
status VARCHAR(32) NOT NULL DEFAULT 'draft',
|
||||||
|
voting_protocol_id UUID REFERENCES voting_protocols(id),
|
||||||
|
created_by_id UUID REFERENCES duniter_identities(id),
|
||||||
|
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
||||||
|
updated_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
||||||
|
)
|
||||||
|
""")
|
||||||
|
|
||||||
|
op.execute("""
|
||||||
|
CREATE TABLE IF NOT EXISTS item_versions (
|
||||||
|
id UUID PRIMARY KEY,
|
||||||
|
item_id UUID NOT NULL,
|
||||||
|
proposed_text TEXT NOT NULL,
|
||||||
|
diff_text TEXT,
|
||||||
|
rationale TEXT,
|
||||||
|
status VARCHAR(32) NOT NULL DEFAULT 'proposed',
|
||||||
|
decision_id UUID REFERENCES decisions(id),
|
||||||
|
proposed_by_id UUID REFERENCES duniter_identities(id),
|
||||||
|
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
||||||
|
)
|
||||||
|
""")
|
||||||
|
|
||||||
|
op.execute("""
|
||||||
|
CREATE TABLE IF NOT EXISTS vote_sessions (
|
||||||
|
id UUID PRIMARY KEY,
|
||||||
|
decision_id UUID REFERENCES decisions(id),
|
||||||
|
item_version_id UUID REFERENCES item_versions(id),
|
||||||
|
voting_protocol_id UUID NOT NULL REFERENCES voting_protocols(id),
|
||||||
|
wot_size INTEGER NOT NULL DEFAULT 0,
|
||||||
|
smith_size INTEGER NOT NULL DEFAULT 0,
|
||||||
|
techcomm_size INTEGER NOT NULL DEFAULT 0,
|
||||||
|
starts_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
||||||
|
ends_at TIMESTAMPTZ NOT NULL,
|
||||||
|
status VARCHAR(32) NOT NULL DEFAULT 'open',
|
||||||
|
votes_for INTEGER NOT NULL DEFAULT 0,
|
||||||
|
votes_against INTEGER NOT NULL DEFAULT 0,
|
||||||
|
votes_total INTEGER NOT NULL DEFAULT 0,
|
||||||
|
smith_votes_for INTEGER NOT NULL DEFAULT 0,
|
||||||
|
techcomm_votes_for INTEGER NOT NULL DEFAULT 0,
|
||||||
|
threshold_required FLOAT NOT NULL DEFAULT 0.0,
|
||||||
|
result VARCHAR(32),
|
||||||
|
chain_recorded BOOLEAN NOT NULL DEFAULT FALSE,
|
||||||
|
chain_tx_hash VARCHAR(128),
|
||||||
|
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
||||||
|
)
|
||||||
|
""")
|
||||||
|
|
||||||
|
op.execute("""
|
||||||
|
CREATE TABLE IF NOT EXISTS decision_steps (
|
||||||
|
id UUID PRIMARY KEY,
|
||||||
|
decision_id UUID NOT NULL REFERENCES decisions(id),
|
||||||
|
step_order INTEGER NOT NULL,
|
||||||
|
step_type VARCHAR(32) NOT NULL,
|
||||||
|
title VARCHAR(256),
|
||||||
|
description TEXT,
|
||||||
|
status VARCHAR(32) NOT NULL DEFAULT 'pending',
|
||||||
|
vote_session_id UUID REFERENCES vote_sessions(id),
|
||||||
|
outcome TEXT,
|
||||||
|
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
||||||
|
)
|
||||||
|
""")
|
||||||
|
|
||||||
|
op.execute("""
|
||||||
|
CREATE TABLE IF NOT EXISTS document_items (
|
||||||
|
id UUID PRIMARY KEY,
|
||||||
|
document_id UUID NOT NULL REFERENCES documents(id),
|
||||||
|
position VARCHAR(16) NOT NULL,
|
||||||
|
item_type VARCHAR(32) NOT NULL DEFAULT 'clause',
|
||||||
|
title VARCHAR(256),
|
||||||
|
current_text TEXT NOT NULL,
|
||||||
|
voting_protocol_id UUID REFERENCES voting_protocols(id),
|
||||||
|
sort_order INTEGER NOT NULL DEFAULT 0,
|
||||||
|
section_tag VARCHAR(64),
|
||||||
|
inertia_preset VARCHAR(16) NOT NULL DEFAULT 'standard',
|
||||||
|
is_permanent_vote BOOLEAN NOT NULL DEFAULT TRUE,
|
||||||
|
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
||||||
|
updated_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
||||||
|
)
|
||||||
|
""")
|
||||||
|
|
||||||
|
op.execute("""
|
||||||
|
ALTER TABLE item_versions
|
||||||
|
ADD CONSTRAINT IF NOT EXISTS item_versions_item_id_fkey
|
||||||
|
FOREIGN KEY (item_id) REFERENCES document_items(id)
|
||||||
|
""")
|
||||||
|
|
||||||
|
op.execute("""
|
||||||
|
CREATE TABLE IF NOT EXISTS votes (
|
||||||
|
id UUID PRIMARY KEY,
|
||||||
|
session_id UUID NOT NULL REFERENCES vote_sessions(id),
|
||||||
|
voter_id UUID NOT NULL REFERENCES duniter_identities(id),
|
||||||
|
vote_value VARCHAR(32) NOT NULL,
|
||||||
|
nuanced_level INTEGER,
|
||||||
|
comment TEXT,
|
||||||
|
signature TEXT NOT NULL,
|
||||||
|
signed_payload TEXT NOT NULL,
|
||||||
|
voter_wot_status VARCHAR(32) NOT NULL DEFAULT 'member',
|
||||||
|
voter_is_smith BOOLEAN NOT NULL DEFAULT FALSE,
|
||||||
|
voter_is_techcomm BOOLEAN NOT NULL DEFAULT FALSE,
|
||||||
|
is_active BOOLEAN NOT NULL DEFAULT TRUE,
|
||||||
|
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
||||||
|
)
|
||||||
|
""")
|
||||||
|
|
||||||
|
op.execute("""
|
||||||
|
CREATE TABLE IF NOT EXISTS mandates (
|
||||||
|
id UUID PRIMARY KEY,
|
||||||
|
title VARCHAR(256) NOT NULL,
|
||||||
|
description TEXT,
|
||||||
|
mandate_type VARCHAR(64) NOT NULL,
|
||||||
|
status VARCHAR(32) NOT NULL DEFAULT 'draft',
|
||||||
|
mandatee_id UUID REFERENCES duniter_identities(id),
|
||||||
|
decision_id UUID REFERENCES decisions(id),
|
||||||
|
starts_at TIMESTAMPTZ,
|
||||||
|
ends_at TIMESTAMPTZ,
|
||||||
|
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
||||||
|
updated_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
||||||
|
)
|
||||||
|
""")
|
||||||
|
|
||||||
|
op.execute("""
|
||||||
|
CREATE TABLE IF NOT EXISTS mandate_steps (
|
||||||
|
id UUID PRIMARY KEY,
|
||||||
|
mandate_id UUID NOT NULL REFERENCES mandates(id),
|
||||||
|
step_order INTEGER NOT NULL,
|
||||||
|
step_type VARCHAR(32) NOT NULL,
|
||||||
|
title VARCHAR(256),
|
||||||
|
description TEXT,
|
||||||
|
status VARCHAR(32) NOT NULL DEFAULT 'pending',
|
||||||
|
vote_session_id UUID REFERENCES vote_sessions(id),
|
||||||
|
outcome TEXT,
|
||||||
|
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
||||||
|
)
|
||||||
|
""")
|
||||||
|
|
||||||
|
|
||||||
|
def downgrade() -> None:
|
||||||
|
# Intentionally left empty — dropping the initial schema would destroy all data.
|
||||||
|
pass
|
||||||
@@ -12,22 +12,21 @@ import sqlalchemy as sa
|
|||||||
|
|
||||||
|
|
||||||
revision: str = '70914b334cfb'
|
revision: str = '70914b334cfb'
|
||||||
down_revision: Union[str, None] = None
|
down_revision: Union[str, None] = '0b9c1d2e3f4a'
|
||||||
branch_labels: Union[str, Sequence[str], None] = None
|
branch_labels: Union[str, Sequence[str], None] = None
|
||||||
depends_on: Union[str, Sequence[str], None] = None
|
depends_on: Union[str, Sequence[str], None] = None
|
||||||
|
|
||||||
|
|
||||||
def upgrade() -> None:
|
def upgrade() -> None:
|
||||||
# SQLite does not support ADD CONSTRAINT via ALTER TABLE — FK constraints
|
# ADD COLUMN IF NOT EXISTS — idempotent (safe on DBs bootstrapped via create_all)
|
||||||
# are declared in models only; integrity is enforced at app layer.
|
op.execute("ALTER TABLE decisions ADD COLUMN IF NOT EXISTS organization_id UUID REFERENCES organizations(id)")
|
||||||
op.add_column('decisions', sa.Column('organization_id', sa.Uuid(), nullable=True))
|
op.execute("CREATE INDEX IF NOT EXISTS ix_decisions_organization_id ON decisions (organization_id)")
|
||||||
op.create_index(op.f('ix_decisions_organization_id'), 'decisions', ['organization_id'], unique=False)
|
op.execute("ALTER TABLE documents ADD COLUMN IF NOT EXISTS organization_id UUID REFERENCES organizations(id)")
|
||||||
op.add_column('documents', sa.Column('organization_id', sa.Uuid(), nullable=True))
|
op.execute("CREATE INDEX IF NOT EXISTS ix_documents_organization_id ON documents (organization_id)")
|
||||||
op.create_index(op.f('ix_documents_organization_id'), 'documents', ['organization_id'], unique=False)
|
op.execute("ALTER TABLE mandates ADD COLUMN IF NOT EXISTS organization_id UUID REFERENCES organizations(id)")
|
||||||
op.add_column('mandates', sa.Column('organization_id', sa.Uuid(), nullable=True))
|
op.execute("CREATE INDEX IF NOT EXISTS ix_mandates_organization_id ON mandates (organization_id)")
|
||||||
op.create_index(op.f('ix_mandates_organization_id'), 'mandates', ['organization_id'], unique=False)
|
op.execute("ALTER TABLE voting_protocols ADD COLUMN IF NOT EXISTS organization_id UUID REFERENCES organizations(id)")
|
||||||
op.add_column('voting_protocols', sa.Column('organization_id', sa.Uuid(), nullable=True))
|
op.execute("CREATE INDEX IF NOT EXISTS ix_voting_protocols_organization_id ON voting_protocols (organization_id)")
|
||||||
op.create_index(op.f('ix_voting_protocols_organization_id'), 'voting_protocols', ['organization_id'], unique=False)
|
|
||||||
|
|
||||||
|
|
||||||
def downgrade() -> None:
|
def downgrade() -> None:
|
||||||
|
|||||||
+11
-16
@@ -16,23 +16,18 @@ depends_on: Union[str, Sequence[str], None] = None
|
|||||||
|
|
||||||
|
|
||||||
def upgrade() -> None:
|
def upgrade() -> None:
|
||||||
op.create_table(
|
op.execute("""
|
||||||
'qualification_protocols',
|
CREATE TABLE IF NOT EXISTS qualification_protocols (
|
||||||
sa.Column('id', sa.Uuid(), nullable=False),
|
id UUID PRIMARY KEY,
|
||||||
sa.Column('name', sa.String(128), nullable=False),
|
name VARCHAR(128) NOT NULL,
|
||||||
sa.Column('description', sa.Text(), nullable=True),
|
description TEXT,
|
||||||
sa.Column('small_group_max', sa.Integer(), nullable=False, server_default='5'),
|
small_group_max INTEGER NOT NULL DEFAULT 5,
|
||||||
sa.Column('collective_wot_min', sa.Integer(), nullable=False, server_default='50'),
|
collective_wot_min INTEGER NOT NULL DEFAULT 50,
|
||||||
sa.Column(
|
default_modalities_json TEXT NOT NULL DEFAULT '["vote_wot","vote_smith","consultation_avis","election"]',
|
||||||
'default_modalities_json',
|
is_active BOOLEAN NOT NULL DEFAULT TRUE,
|
||||||
sa.Text(),
|
created_at TIMESTAMPTZ DEFAULT now()
|
||||||
nullable=False,
|
|
||||||
server_default='["vote_wot","vote_smith","consultation_avis","election"]',
|
|
||||||
),
|
|
||||||
sa.Column('is_active', sa.Boolean(), nullable=False, server_default='1'),
|
|
||||||
sa.Column('created_at', sa.DateTime(timezone=True), server_default=sa.func.now()),
|
|
||||||
sa.PrimaryKeyConstraint('id'),
|
|
||||||
)
|
)
|
||||||
|
""")
|
||||||
|
|
||||||
|
|
||||||
def downgrade() -> None:
|
def downgrade() -> None:
|
||||||
|
|||||||
@@ -17,31 +17,28 @@ depends_on = None
|
|||||||
|
|
||||||
|
|
||||||
def upgrade() -> None:
|
def upgrade() -> None:
|
||||||
op.create_table(
|
op.execute("""
|
||||||
"groups",
|
CREATE TABLE IF NOT EXISTS groups (
|
||||||
sa.Column("id", sa.Uuid(), nullable=False),
|
id UUID PRIMARY KEY,
|
||||||
sa.Column("name", sa.String(128), nullable=False),
|
name VARCHAR(128) NOT NULL,
|
||||||
sa.Column("description", sa.Text(), nullable=True),
|
description TEXT,
|
||||||
sa.Column("organization_id", sa.Uuid(), nullable=True),
|
organization_id UUID REFERENCES organizations(id),
|
||||||
sa.Column("created_at", sa.DateTime(timezone=True), server_default=sa.func.now(), nullable=False),
|
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
||||||
sa.Column("updated_at", sa.DateTime(timezone=True), server_default=sa.func.now(), nullable=False),
|
updated_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
||||||
sa.ForeignKeyConstraint(["organization_id"], ["organizations.id"]),
|
|
||||||
sa.PrimaryKeyConstraint("id"),
|
|
||||||
)
|
)
|
||||||
op.create_index("ix_groups_organization_id", "groups", ["organization_id"])
|
""")
|
||||||
|
op.execute("CREATE INDEX IF NOT EXISTS ix_groups_organization_id ON groups (organization_id)")
|
||||||
|
|
||||||
op.create_table(
|
op.execute("""
|
||||||
"group_members",
|
CREATE TABLE IF NOT EXISTS group_members (
|
||||||
sa.Column("id", sa.Uuid(), nullable=False),
|
id UUID PRIMARY KEY,
|
||||||
sa.Column("group_id", sa.Uuid(), nullable=False),
|
group_id UUID NOT NULL REFERENCES groups(id),
|
||||||
sa.Column("identity_id", sa.Uuid(), nullable=True),
|
identity_id UUID REFERENCES duniter_identities(id),
|
||||||
sa.Column("display_name", sa.String(128), nullable=False),
|
display_name VARCHAR(128) NOT NULL,
|
||||||
sa.Column("added_at", sa.DateTime(timezone=True), server_default=sa.func.now(), nullable=False),
|
added_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
||||||
sa.ForeignKeyConstraint(["group_id"], ["groups.id"]),
|
|
||||||
sa.ForeignKeyConstraint(["identity_id"], ["duniter_identities.id"]),
|
|
||||||
sa.PrimaryKeyConstraint("id"),
|
|
||||||
)
|
)
|
||||||
op.create_index("ix_group_members_group_id", "group_members", ["group_id"])
|
""")
|
||||||
|
op.execute("CREATE INDEX IF NOT EXISTS ix_group_members_group_id ON group_members (group_id)")
|
||||||
|
|
||||||
|
|
||||||
def downgrade() -> None:
|
def downgrade() -> None:
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ depends_on = None
|
|||||||
|
|
||||||
|
|
||||||
def upgrade() -> None:
|
def upgrade() -> None:
|
||||||
op.add_column("mandates", sa.Column("origin", sa.Text(), nullable=True))
|
op.execute("ALTER TABLE mandates ADD COLUMN IF NOT EXISTS origin TEXT")
|
||||||
|
|
||||||
|
|
||||||
def downgrade() -> None:
|
def downgrade() -> None:
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ EXPOSE 8002
|
|||||||
HEALTHCHECK --interval=30s --timeout=5s --start-period=10s --retries=3 \
|
HEALTHCHECK --interval=30s --timeout=5s --start-period=10s --retries=3 \
|
||||||
CMD curl -f http://localhost:8002/api/health || exit 1
|
CMD curl -f http://localhost:8002/api/health || exit 1
|
||||||
|
|
||||||
CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8002"]
|
CMD ["sh", "-c", "alembic upgrade head && uvicorn app.main:app --host 0.0.0.0 --port 8002"]
|
||||||
|
|
||||||
# ── Development ───────────────────────────────────────────────────────────────
|
# ── Development ───────────────────────────────────────────────────────────────
|
||||||
FROM base AS development
|
FROM base AS development
|
||||||
|
|||||||
Reference in New Issue
Block a user