v3 : socle Rust (moteur pur, accès trustinWallet, serveur) ; maquette Nuxt rangée dans maquette/
- crates/ld-engine : formule inertielle (parité avec la maquette, garde C), coquille / changement de fond, assistant d'édition à règles, composition d'un document sous vote permanent (adoptions, aperçus, rivales). - crates/trustin-access : qui voit, qui ouvre, qui gère les accès. - crates/ld-server : API axum + SQLite, connexion Ğ1 (client OAuth2 de connect.monnaie-libre.fr), invitations, entrée en vigueur automatique, signalements de contradiction, traductions éditoriales, publication git, taille de la toile lue sur la chaîne. - seeds/acte-engagement-certification.json : 33 articles, fr + en + es ; en vigueur : préambule, E1, E2 (seed a minima). - maquette/ : frontend, backend et docker v2 déplacés tels quels. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,112 @@
|
||||
"""Alembic async environment for libreDecision.
|
||||
|
||||
Uses asyncpg via SQLAlchemy's async engine.
|
||||
All models are imported so that Base.metadata is fully populated
|
||||
before autogenerate runs.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import asyncio
|
||||
from logging.config import fileConfig
|
||||
|
||||
from sqlalchemy import pool
|
||||
from sqlalchemy.engine import Connection
|
||||
from sqlalchemy.ext.asyncio import async_engine_from_config
|
||||
|
||||
from alembic import context
|
||||
|
||||
# Import settings to get the DATABASE_URL
|
||||
from app.config import settings
|
||||
|
||||
# Import Base for target_metadata
|
||||
from app.database import Base
|
||||
|
||||
# Import ALL models so their tables are registered on Base.metadata
|
||||
from app.models import ( # noqa: F401
|
||||
DuniterIdentity,
|
||||
Session,
|
||||
Organization,
|
||||
OrgMember,
|
||||
Document,
|
||||
DocumentItem,
|
||||
ItemVersion,
|
||||
Decision,
|
||||
DecisionStep,
|
||||
VoteSession,
|
||||
Vote,
|
||||
Mandate,
|
||||
MandateStep,
|
||||
VotingProtocol,
|
||||
FormulaConfig,
|
||||
QualificationProtocol,
|
||||
SanctuaryEntry,
|
||||
BlockchainCache,
|
||||
)
|
||||
|
||||
# Alembic Config object
|
||||
config = context.config
|
||||
|
||||
# Interpret the config file for Python logging
|
||||
if config.config_file_name is not None:
|
||||
fileConfig(config.config_file_name)
|
||||
|
||||
# Override sqlalchemy.url with the real DATABASE_URL from settings
|
||||
config.set_main_option("sqlalchemy.url", settings.DATABASE_URL)
|
||||
|
||||
# Target metadata for autogenerate support
|
||||
target_metadata = Base.metadata
|
||||
|
||||
|
||||
def run_migrations_offline() -> None:
|
||||
"""Run migrations in 'offline' mode.
|
||||
|
||||
Configures the context with just a URL and not an Engine.
|
||||
Calls to context.execute() emit the given string to the script output.
|
||||
"""
|
||||
url = config.get_main_option("sqlalchemy.url")
|
||||
context.configure(
|
||||
url=url,
|
||||
target_metadata=target_metadata,
|
||||
literal_binds=True,
|
||||
dialect_opts={"paramstyle": "named"},
|
||||
)
|
||||
|
||||
with context.begin_transaction():
|
||||
context.run_migrations()
|
||||
|
||||
|
||||
def do_run_migrations(connection: Connection) -> None:
|
||||
"""Run migrations with an active connection."""
|
||||
context.configure(
|
||||
connection=connection,
|
||||
target_metadata=target_metadata,
|
||||
)
|
||||
|
||||
with context.begin_transaction():
|
||||
context.run_migrations()
|
||||
|
||||
|
||||
async def run_async_migrations() -> None:
|
||||
"""Run migrations in 'online' mode with an async engine."""
|
||||
connectable = async_engine_from_config(
|
||||
config.get_section(config.config_ini_section, {}),
|
||||
prefix="sqlalchemy.",
|
||||
poolclass=pool.NullPool,
|
||||
)
|
||||
|
||||
async with connectable.connect() as connection:
|
||||
await connection.run_sync(do_run_migrations)
|
||||
|
||||
await connectable.dispose()
|
||||
|
||||
|
||||
def run_migrations_online() -> None:
|
||||
"""Run migrations in 'online' mode."""
|
||||
asyncio.run(run_async_migrations())
|
||||
|
||||
|
||||
if context.is_offline_mode():
|
||||
run_migrations_offline()
|
||||
else:
|
||||
run_migrations_online()
|
||||
@@ -0,0 +1,26 @@
|
||||
"""${message}
|
||||
|
||||
Revision ID: ${up_revision}
|
||||
Revises: ${down_revision | comma,n}
|
||||
Create Date: ${create_date}
|
||||
|
||||
"""
|
||||
from typing import Sequence, Union
|
||||
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
${imports if imports else ""}
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision: str = ${repr(up_revision)}
|
||||
down_revision: Union[str, None] = ${repr(down_revision)}
|
||||
branch_labels: Union[str, Sequence[str], None] = ${repr(branch_labels)}
|
||||
depends_on: Union[str, Sequence[str], None] = ${repr(depends_on)}
|
||||
|
||||
|
||||
def upgrade() -> None:
|
||||
${upgrades if upgrades else "pass"}
|
||||
|
||||
|
||||
def downgrade() -> None:
|
||||
${downgrades if downgrades else "pass"}
|
||||
@@ -0,0 +1,301 @@
|
||||
"""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("""
|
||||
DO $$
|
||||
BEGIN
|
||||
IF NOT EXISTS (
|
||||
SELECT 1 FROM information_schema.table_constraints
|
||||
WHERE constraint_name = 'item_versions_item_id_fkey'
|
||||
AND table_name = 'item_versions'
|
||||
) THEN
|
||||
ALTER TABLE item_versions
|
||||
ADD CONSTRAINT item_versions_item_id_fkey
|
||||
FOREIGN KEY (item_id) REFERENCES document_items(id);
|
||||
END IF;
|
||||
END $$
|
||||
""")
|
||||
|
||||
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
|
||||
@@ -0,0 +1,40 @@
|
||||
"""add organizations
|
||||
|
||||
Revision ID: 70914b334cfb
|
||||
Revises:
|
||||
Create Date: 2026-04-23 12:27:56.220214+00:00
|
||||
|
||||
"""
|
||||
from typing import Sequence, Union
|
||||
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
|
||||
|
||||
revision: str = '70914b334cfb'
|
||||
down_revision: Union[str, None] = '0b9c1d2e3f4a'
|
||||
branch_labels: Union[str, Sequence[str], None] = None
|
||||
depends_on: Union[str, Sequence[str], None] = None
|
||||
|
||||
|
||||
def upgrade() -> None:
|
||||
# ADD COLUMN IF NOT EXISTS — idempotent (safe on DBs bootstrapped via create_all)
|
||||
op.execute("ALTER TABLE decisions ADD COLUMN IF NOT EXISTS organization_id UUID REFERENCES organizations(id)")
|
||||
op.execute("CREATE INDEX IF NOT EXISTS ix_decisions_organization_id ON decisions (organization_id)")
|
||||
op.execute("ALTER TABLE documents ADD COLUMN IF NOT EXISTS organization_id UUID REFERENCES organizations(id)")
|
||||
op.execute("CREATE INDEX IF NOT EXISTS ix_documents_organization_id ON documents (organization_id)")
|
||||
op.execute("ALTER TABLE mandates ADD COLUMN IF NOT EXISTS organization_id UUID REFERENCES organizations(id)")
|
||||
op.execute("CREATE INDEX IF NOT EXISTS ix_mandates_organization_id ON mandates (organization_id)")
|
||||
op.execute("ALTER TABLE voting_protocols ADD COLUMN IF NOT EXISTS organization_id UUID REFERENCES organizations(id)")
|
||||
op.execute("CREATE INDEX IF NOT EXISTS ix_voting_protocols_organization_id ON voting_protocols (organization_id)")
|
||||
|
||||
|
||||
def downgrade() -> None:
|
||||
op.drop_index(op.f('ix_voting_protocols_organization_id'), table_name='voting_protocols')
|
||||
op.drop_column('voting_protocols', 'organization_id')
|
||||
op.drop_index(op.f('ix_mandates_organization_id'), table_name='mandates')
|
||||
op.drop_column('mandates', 'organization_id')
|
||||
op.drop_index(op.f('ix_documents_organization_id'), table_name='documents')
|
||||
op.drop_column('documents', 'organization_id')
|
||||
op.drop_index(op.f('ix_decisions_organization_id'), table_name='decisions')
|
||||
op.drop_column('decisions', 'organization_id')
|
||||
+34
@@ -0,0 +1,34 @@
|
||||
"""add_qualification_protocol
|
||||
|
||||
Revision ID: b78571ae9e00
|
||||
Revises: 70914b334cfb
|
||||
Create Date: 2026-04-23 17:08:07.161306+00:00
|
||||
"""
|
||||
from typing import Sequence, Union
|
||||
|
||||
import sqlalchemy as sa
|
||||
from alembic import op
|
||||
|
||||
revision: str = 'b78571ae9e00'
|
||||
down_revision: Union[str, None] = '70914b334cfb'
|
||||
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 qualification_protocols (
|
||||
id UUID PRIMARY KEY,
|
||||
name VARCHAR(128) NOT NULL,
|
||||
description TEXT,
|
||||
small_group_max INTEGER NOT NULL DEFAULT 5,
|
||||
collective_wot_min INTEGER NOT NULL DEFAULT 50,
|
||||
default_modalities_json TEXT NOT NULL DEFAULT '["vote_wot","vote_smith","consultation_avis","election"]',
|
||||
is_active BOOLEAN NOT NULL DEFAULT TRUE,
|
||||
created_at TIMESTAMPTZ DEFAULT now()
|
||||
)
|
||||
""")
|
||||
|
||||
|
||||
def downgrade() -> None:
|
||||
op.drop_table('qualification_protocols')
|
||||
@@ -0,0 +1,48 @@
|
||||
"""Add groups and group_members tables.
|
||||
|
||||
Revision ID: c4e812fb3a01
|
||||
Revises: b78571ae9e00
|
||||
Create Date: 2026-04-23 19:00:00.000000
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import sqlalchemy as sa
|
||||
from alembic import op
|
||||
|
||||
revision = "c4e812fb3a01"
|
||||
down_revision = "b78571ae9e00"
|
||||
branch_labels = None
|
||||
depends_on = None
|
||||
|
||||
|
||||
def upgrade() -> None:
|
||||
op.execute("""
|
||||
CREATE TABLE IF NOT EXISTS groups (
|
||||
id UUID PRIMARY KEY,
|
||||
name VARCHAR(128) NOT NULL,
|
||||
description TEXT,
|
||||
organization_id UUID REFERENCES organizations(id),
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
||||
updated_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
||||
)
|
||||
""")
|
||||
op.execute("CREATE INDEX IF NOT EXISTS ix_groups_organization_id ON groups (organization_id)")
|
||||
|
||||
op.execute("""
|
||||
CREATE TABLE IF NOT EXISTS group_members (
|
||||
id UUID PRIMARY KEY,
|
||||
group_id UUID NOT NULL REFERENCES groups(id),
|
||||
identity_id UUID REFERENCES duniter_identities(id),
|
||||
display_name VARCHAR(128) NOT NULL,
|
||||
added_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
||||
)
|
||||
""")
|
||||
op.execute("CREATE INDEX IF NOT EXISTS ix_group_members_group_id ON group_members (group_id)")
|
||||
|
||||
|
||||
def downgrade() -> None:
|
||||
op.drop_index("ix_group_members_group_id", table_name="group_members")
|
||||
op.drop_table("group_members")
|
||||
op.drop_index("ix_groups_organization_id", table_name="groups")
|
||||
op.drop_table("groups")
|
||||
@@ -0,0 +1,24 @@
|
||||
"""Add origin column to mandates table.
|
||||
|
||||
Revision ID: d91a3c7f8b02
|
||||
Revises: c4e812fb3a01
|
||||
Create Date: 2026-04-24 10:00:00.000000
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import sqlalchemy as sa
|
||||
from alembic import op
|
||||
|
||||
revision = "d91a3c7f8b02"
|
||||
down_revision = "c4e812fb3a01"
|
||||
branch_labels = None
|
||||
depends_on = None
|
||||
|
||||
|
||||
def upgrade() -> None:
|
||||
op.execute("ALTER TABLE mandates ADD COLUMN IF NOT EXISTS origin TEXT")
|
||||
|
||||
|
||||
def downgrade() -> None:
|
||||
op.drop_column("mandates", "origin")
|
||||
@@ -0,0 +1,27 @@
|
||||
"""Mandate origin: replace free-text with FK to duniter_identities.
|
||||
|
||||
Revision ID: e3f4a5b6c7d8
|
||||
Revises: d91a3c7f8b02
|
||||
Create Date: 2026-04-25 10:00:00.000000
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from alembic import op
|
||||
|
||||
revision = "e3f4a5b6c7d8"
|
||||
down_revision = "d91a3c7f8b02"
|
||||
branch_labels = None
|
||||
depends_on = None
|
||||
|
||||
|
||||
def upgrade() -> None:
|
||||
op.execute("ALTER TABLE mandates DROP COLUMN IF EXISTS origin")
|
||||
op.execute("ALTER TABLE mandates ADD COLUMN IF NOT EXISTS origin_id UUID REFERENCES duniter_identities(id)")
|
||||
op.execute("CREATE INDEX IF NOT EXISTS ix_mandates_origin_id ON mandates (origin_id)")
|
||||
|
||||
|
||||
def downgrade() -> None:
|
||||
op.execute("DROP INDEX IF EXISTS ix_mandates_origin_id")
|
||||
op.execute("ALTER TABLE mandates DROP COLUMN IF EXISTS origin_id")
|
||||
op.execute("ALTER TABLE mandates ADD COLUMN IF NOT EXISTS origin TEXT")
|
||||
Reference in New Issue
Block a user