Multi-tenancy : espaces de travail + fix auth reload (rate limiter OPTIONS)
- Modèles Organization + OrgMember, migration Alembic (SQLite compatible) - organization_id nullable sur Document, Decision, Mandate, VotingProtocol - Service, schéma, router /organizations + dependency get_active_org_id - Seed : Duniter G1 + Axiom Team ; tout le contenu seed attaché à Duniter G1 - Backend : list/create filtrés par header X-Organization - Frontend : store organizations, WorkspaceSelector réel, useApi injecte l'org - Fix critique : rate_limiter exclut les requêtes OPTIONS (CORS preflight) → résout le bug "Failed to fetch /auth/me" au reload (429 sur preflight) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -32,6 +32,7 @@ from app.models.protocol import FormulaConfig, VotingProtocol
|
||||
from app.models.document import Document, DocumentItem
|
||||
from app.models.decision import Decision, DecisionStep
|
||||
from app.models.mandate import Mandate, MandateStep
|
||||
from app.models.organization import Organization
|
||||
from app.models.user import DuniterIdentity
|
||||
from app.models.vote import VoteSession, Vote
|
||||
|
||||
@@ -161,6 +162,7 @@ async def seed_formula_configs(session: AsyncSession) -> dict[str, FormulaConfig
|
||||
async def seed_voting_protocols(
|
||||
session: AsyncSession,
|
||||
formulas: dict[str, FormulaConfig],
|
||||
org_id: uuid.UUID | None = None,
|
||||
) -> dict[str, VotingProtocol]:
|
||||
protocols: dict[str, dict] = {
|
||||
"Vote WoT standard": {
|
||||
@@ -206,6 +208,7 @@ async def seed_voting_protocols(
|
||||
instance, created = await get_or_create(
|
||||
session, VotingProtocol, "name", name, **params,
|
||||
)
|
||||
instance.organization_id = org_id
|
||||
status = "created" if created else "exists"
|
||||
print(f" VotingProtocol '{name}': {status}")
|
||||
result[name] = instance
|
||||
@@ -829,6 +832,7 @@ ENGAGEMENT_CERTIFICATION_ITEMS: list[dict] = [
|
||||
async def seed_document_engagement_certification(
|
||||
session: AsyncSession,
|
||||
protocols: dict[str, VotingProtocol],
|
||||
org_id: uuid.UUID | None = None,
|
||||
) -> Document:
|
||||
genesis = json.dumps(GENESIS_CERTIFICATION, ensure_ascii=False, indent=2)
|
||||
|
||||
@@ -850,6 +854,7 @@ async def seed_document_engagement_certification(
|
||||
),
|
||||
genesis_json=genesis,
|
||||
)
|
||||
doc.organization_id = org_id
|
||||
print(f" Document 'Acte d'engagement Certification': {'created' if created else 'exists'}")
|
||||
|
||||
if created:
|
||||
@@ -1893,6 +1898,7 @@ ENGAGEMENT_FORGERON_ITEMS: list[dict] = [
|
||||
async def seed_document_engagement_forgeron(
|
||||
session: AsyncSession,
|
||||
protocols: dict[str, VotingProtocol],
|
||||
org_id: uuid.UUID | None = None,
|
||||
) -> Document:
|
||||
genesis = json.dumps(GENESIS_FORGERON, ensure_ascii=False, indent=2)
|
||||
|
||||
@@ -1916,6 +1922,7 @@ async def seed_document_engagement_forgeron(
|
||||
),
|
||||
genesis_json=genesis,
|
||||
)
|
||||
doc.organization_id = org_id
|
||||
print(f" Document 'Acte d'engagement forgeron': {'created' if created else 'exists'}")
|
||||
|
||||
if created:
|
||||
@@ -1988,7 +1995,7 @@ RUNTIME_UPGRADE_STEPS: list[dict] = [
|
||||
]
|
||||
|
||||
|
||||
async def seed_decision_runtime_upgrade(session: AsyncSession) -> Decision:
|
||||
async def seed_decision_runtime_upgrade(session: AsyncSession, org_id: uuid.UUID | None = None) -> Decision:
|
||||
decision, created = await get_or_create(
|
||||
session,
|
||||
Decision,
|
||||
@@ -2009,6 +2016,7 @@ async def seed_decision_runtime_upgrade(session: AsyncSession) -> Decision:
|
||||
decision_type="runtime_upgrade",
|
||||
status="draft",
|
||||
)
|
||||
decision.organization_id = org_id
|
||||
print(f" Decision 'Runtime Upgrade': {'created' if created else 'exists'}")
|
||||
|
||||
if created:
|
||||
@@ -2148,7 +2156,7 @@ async def seed_votes_on_items(
|
||||
# Seed: Additional decisions (demo content)
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
async def seed_decision_licence_evolution(session: AsyncSession) -> Decision:
|
||||
async def seed_decision_licence_evolution(session: AsyncSession, org_id: uuid.UUID | None = None) -> Decision:
|
||||
"""Seed a community decision: evolution of the G1 monetary license."""
|
||||
decision, created = await get_or_create(
|
||||
session,
|
||||
@@ -2170,6 +2178,7 @@ async def seed_decision_licence_evolution(session: AsyncSession) -> Decision:
|
||||
decision_type="community",
|
||||
status="draft",
|
||||
)
|
||||
decision.organization_id = org_id
|
||||
print(f" Decision 'Évolution Licence G1 v0.4.0': {'created' if created else 'exists'}")
|
||||
|
||||
if created:
|
||||
@@ -2225,7 +2234,7 @@ async def seed_decision_licence_evolution(session: AsyncSession) -> Decision:
|
||||
# Seed: Mandates (Comité Technique + Admin Forgerons)
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
async def seed_mandates(session: AsyncSession, voters: list[DuniterIdentity]) -> None:
|
||||
async def seed_mandates(session: AsyncSession, voters: list[DuniterIdentity], org_id: uuid.UUID | None = None) -> None:
|
||||
"""Seed example mandates: TechComm and Smith Admin."""
|
||||
now = datetime.now(timezone.utc)
|
||||
|
||||
@@ -2397,6 +2406,7 @@ async def seed_mandates(session: AsyncSession, voters: list[DuniterIdentity]) ->
|
||||
m_data["title"],
|
||||
**{k: v for k, v in m_data.items() if k != "title"},
|
||||
)
|
||||
mandate.organization_id = org_id
|
||||
status_str = "created" if created else "exists"
|
||||
print(f" Mandate '{mandate.title[:50]}': {status_str}")
|
||||
|
||||
@@ -2408,6 +2418,43 @@ async def seed_mandates(session: AsyncSession, voters: list[DuniterIdentity]) ->
|
||||
print(f" -> {len(steps_data)} steps created")
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Seed: Organizations
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
async def seed_organizations(session: AsyncSession) -> dict[str, Organization]:
|
||||
"""Create the two base transparent organizations (idempotent)."""
|
||||
orgs_data = [
|
||||
{
|
||||
"slug": "duniter-g1",
|
||||
"name": "Duniter G1",
|
||||
"org_type": "community",
|
||||
"is_transparent": True,
|
||||
"color": "#22c55e",
|
||||
"icon": "i-lucide-globe",
|
||||
"description": "Communauté Duniter — monnaie libre G1. Accessible à tous les membres authentifiés.",
|
||||
},
|
||||
{
|
||||
"slug": "axiom-team",
|
||||
"name": "Axiom Team",
|
||||
"org_type": "collective",
|
||||
"is_transparent": True,
|
||||
"color": "#3b82f6",
|
||||
"icon": "i-lucide-users",
|
||||
"description": "Équipe Axiom — développement et gouvernance des outils communs.",
|
||||
},
|
||||
]
|
||||
|
||||
orgs: dict[str, Organization] = {}
|
||||
for data in orgs_data:
|
||||
org, created = await get_or_create(session, Organization, "slug", data["slug"], **{k: v for k, v in data.items() if k != "slug"})
|
||||
status_str = "created" if created else "exists"
|
||||
print(f" Organisation '{org.name}': {status_str}")
|
||||
orgs[org.slug] = org
|
||||
|
||||
return orgs
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Main seed runner
|
||||
# ---------------------------------------------------------------------------
|
||||
@@ -2423,23 +2470,27 @@ async def run_seed():
|
||||
|
||||
async with async_session() as session:
|
||||
async with session.begin():
|
||||
print("\n[0/10] Organizations...")
|
||||
orgs = await seed_organizations(session)
|
||||
duniter_g1_id = orgs["duniter-g1"].id
|
||||
|
||||
print("\n[1/10] Formula Configs...")
|
||||
formulas = await seed_formula_configs(session)
|
||||
|
||||
print("\n[2/10] Voting Protocols...")
|
||||
protocols = await seed_voting_protocols(session, formulas)
|
||||
protocols = await seed_voting_protocols(session, formulas, org_id=duniter_g1_id)
|
||||
|
||||
print("\n[3/10] Document: Acte d'engagement Certification...")
|
||||
await seed_document_engagement_certification(session, protocols)
|
||||
await seed_document_engagement_certification(session, protocols, org_id=duniter_g1_id)
|
||||
|
||||
print("\n[4/10] Document: Acte d'engagement forgeron v2.0.0...")
|
||||
doc_forgeron = await seed_document_engagement_forgeron(session, protocols)
|
||||
doc_forgeron = await seed_document_engagement_forgeron(session, protocols, org_id=duniter_g1_id)
|
||||
|
||||
print("\n[5/10] Decision: Runtime Upgrade...")
|
||||
await seed_decision_runtime_upgrade(session)
|
||||
await seed_decision_runtime_upgrade(session, org_id=duniter_g1_id)
|
||||
|
||||
print("\n[6/10] Decision: Évolution Licence G1 v0.4.0...")
|
||||
await seed_decision_licence_evolution(session)
|
||||
await seed_decision_licence_evolution(session, org_id=duniter_g1_id)
|
||||
|
||||
print("\n[7/10] Simulated voters...")
|
||||
voters = await seed_voters(session)
|
||||
@@ -2453,7 +2504,7 @@ async def run_seed():
|
||||
)
|
||||
|
||||
print("\n[9/10] Mandates...")
|
||||
await seed_mandates(session, voters)
|
||||
await seed_mandates(session, voters, org_id=duniter_g1_id)
|
||||
|
||||
print("\n[10/10] Done.")
|
||||
|
||||
|
||||
Reference in New Issue
Block a user