Files
microdao-daarion/site/tasks/TASK_PHASE8_DAO_DASHBOARD/index.html

1507 lines
93 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!doctype html>
<html lang="en" class="no-js">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<link rel="canonical" href="https://IvanTytar.github.io/microdao-daarion/tasks/TASK_PHASE8_DAO_DASHBOARD/">
<link rel="icon" href="../../assets/images/favicon.png">
<meta name="generator" content="mkdocs-1.5.3, mkdocs-material-9.5.18">
<title>TASK_PHASE8_DAO_DASHBOARD.md - DAARION Documentation</title>
<link rel="stylesheet" href="../../assets/stylesheets/main.66ac8b77.min.css">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Roboto:300,300i,400,400i,700,700i%7CRoboto+Mono:400,400i,700,700i&display=fallback">
<style>:root{--md-text-font:"Roboto";--md-code-font:"Roboto Mono"}</style>
<script>__md_scope=new URL("../..",location),__md_hash=e=>[...e].reduce((e,_)=>(e<<5)-e+_.charCodeAt(0),0),__md_get=(e,_=localStorage,t=__md_scope)=>JSON.parse(_.getItem(t.pathname+"."+e)),__md_set=(e,_,t=localStorage,a=__md_scope)=>{try{t.setItem(a.pathname+"."+e,JSON.stringify(_))}catch(e){}}</script>
</head>
<body dir="ltr">
<input class="md-toggle" data-md-toggle="drawer" type="checkbox" id="__drawer" autocomplete="off">
<input class="md-toggle" data-md-toggle="search" type="checkbox" id="__search" autocomplete="off">
<label class="md-overlay" for="__drawer"></label>
<div data-md-component="skip">
<a href="#task_phase8_dao_dashboardmd" class="md-skip">
Skip to content
</a>
</div>
<div data-md-component="announce">
</div>
<header class="md-header md-header--shadow" data-md-component="header">
<nav class="md-header__inner md-grid" aria-label="Header">
<a href="../.." title="DAARION Documentation" class="md-header__button md-logo" aria-label="DAARION Documentation" data-md-component="logo">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M12 8a3 3 0 0 0 3-3 3 3 0 0 0-3-3 3 3 0 0 0-3 3 3 3 0 0 0 3 3m0 3.54C9.64 9.35 6.5 8 3 8v11c3.5 0 6.64 1.35 9 3.54 2.36-2.19 5.5-3.54 9-3.54V8c-3.5 0-6.64 1.35-9 3.54Z"/></svg>
</a>
<label class="md-header__button md-icon" for="__drawer">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M3 6h18v2H3V6m0 5h18v2H3v-2m0 5h18v2H3v-2Z"/></svg>
</label>
<div class="md-header__title" data-md-component="header-title">
<div class="md-header__ellipsis">
<div class="md-header__topic">
<span class="md-ellipsis">
DAARION Documentation
</span>
</div>
<div class="md-header__topic" data-md-component="header-topic">
<span class="md-ellipsis">
TASK_PHASE8_DAO_DASHBOARD.md
</span>
</div>
</div>
</div>
<script>var media,input,key,value,palette=__md_get("__palette");if(palette&&palette.color){"(prefers-color-scheme)"===palette.color.media&&(media=matchMedia("(prefers-color-scheme: light)"),input=document.querySelector(media.matches?"[data-md-color-media='(prefers-color-scheme: light)']":"[data-md-color-media='(prefers-color-scheme: dark)']"),palette.color.media=input.getAttribute("data-md-color-media"),palette.color.scheme=input.getAttribute("data-md-color-scheme"),palette.color.primary=input.getAttribute("data-md-color-primary"),palette.color.accent=input.getAttribute("data-md-color-accent"));for([key,value]of Object.entries(palette.color))document.body.setAttribute("data-md-color-"+key,value)}</script>
<label class="md-header__button md-icon" for="__search">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M9.5 3A6.5 6.5 0 0 1 16 9.5c0 1.61-.59 3.09-1.56 4.23l.27.27h.79l5 5-1.5 1.5-5-5v-.79l-.27-.27A6.516 6.516 0 0 1 9.5 16 6.5 6.5 0 0 1 3 9.5 6.5 6.5 0 0 1 9.5 3m0 2C7 5 5 7 5 9.5S7 14 9.5 14 14 12 14 9.5 12 5 9.5 5Z"/></svg>
</label>
<div class="md-search" data-md-component="search" role="dialog">
<label class="md-search__overlay" for="__search"></label>
<div class="md-search__inner" role="search">
<form class="md-search__form" name="search">
<input type="text" class="md-search__input" name="query" aria-label="Search" placeholder="Search" autocapitalize="off" autocorrect="off" autocomplete="off" spellcheck="false" data-md-component="search-query" required>
<label class="md-search__icon md-icon" for="__search">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M9.5 3A6.5 6.5 0 0 1 16 9.5c0 1.61-.59 3.09-1.56 4.23l.27.27h.79l5 5-1.5 1.5-5-5v-.79l-.27-.27A6.516 6.516 0 0 1 9.5 16 6.5 6.5 0 0 1 3 9.5 6.5 6.5 0 0 1 9.5 3m0 2C7 5 5 7 5 9.5S7 14 9.5 14 14 12 14 9.5 12 5 9.5 5Z"/></svg>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M20 11v2H8l5.5 5.5-1.42 1.42L4.16 12l7.92-7.92L13.5 5.5 8 11h12Z"/></svg>
</label>
<nav class="md-search__options" aria-label="Search">
<button type="reset" class="md-search__icon md-icon" title="Clear" aria-label="Clear" tabindex="-1">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M19 6.41 17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41Z"/></svg>
</button>
</nav>
</form>
<div class="md-search__output">
<div class="md-search__scrollwrap" data-md-scrollfix>
<div class="md-search-result" data-md-component="search-result">
<div class="md-search-result__meta">
Initializing search
</div>
<ol class="md-search-result__list" role="presentation"></ol>
</div>
</div>
</div>
</div>
</div>
</nav>
</header>
<div class="md-container" data-md-component="container">
<main class="md-main" data-md-component="main">
<div class="md-main__inner md-grid">
<div class="md-sidebar md-sidebar--primary" data-md-component="sidebar" data-md-type="navigation" >
<div class="md-sidebar__scrollwrap">
<div class="md-sidebar__inner">
<nav class="md-nav md-nav--primary" aria-label="Navigation" data-md-level="0">
<label class="md-nav__title" for="__drawer">
<a href="../.." title="DAARION Documentation" class="md-nav__button md-logo" aria-label="DAARION Documentation" data-md-component="logo">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M12 8a3 3 0 0 0 3-3 3 3 0 0 0-3-3 3 3 0 0 0-3 3 3 3 0 0 0 3 3m0 3.54C9.64 9.35 6.5 8 3 8v11c3.5 0 6.64 1.35 9 3.54 2.36-2.19 5.5-3.54 9-3.54V8c-3.5 0-6.64 1.35-9 3.54Z"/></svg>
</a>
DAARION Documentation
</label>
<ul class="md-nav__list" data-md-scrollfix>
<li class="md-nav__item">
<a href="../../public/" class="md-nav__link">
<span class="md-ellipsis">
Home
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../../public/getting-started/" class="md-nav__link">
<span class="md-ellipsis">
Getting Started
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../../public/architecture-overview/" class="md-nav__link">
<span class="md-ellipsis">
Architecture
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../../public/daiS_daos_overview/" class="md-nav__link">
<span class="md-ellipsis">
DAIS & DAOS
</span>
</a>
</li>
<li class="md-nav__item md-nav__item--section md-nav__item--nested">
<input class="md-nav__toggle md-toggle " type="checkbox" id="__nav_5" >
<label class="md-nav__link" for="__nav_5" id="__nav_5_label" tabindex="">
<span class="md-ellipsis">
Internal
</span>
<span class="md-nav__icon md-icon"></span>
</label>
<nav class="md-nav" data-md-level="1" aria-labelledby="__nav_5_label" aria-expanded="false">
<label class="md-nav__title" for="__nav_5">
<span class="md-nav__icon md-icon"></span>
Internal
</label>
<ul class="md-nav__list" data-md-scrollfix>
<li class="md-nav__item md-nav__item--nested">
<input class="md-nav__toggle md-toggle " type="checkbox" id="__nav_5_1" >
<label class="md-nav__link" for="__nav_5_1" id="__nav_5_1_label" tabindex="0">
<span class="md-ellipsis">
Infra
</span>
<span class="md-nav__icon md-icon"></span>
</label>
<nav class="md-nav" data-md-level="2" aria-labelledby="__nav_5_1_label" aria-expanded="false">
<label class="md-nav__title" for="__nav_5_1">
<span class="md-nav__icon md-icon"></span>
Infra
</label>
<ul class="md-nav__list" data-md-scrollfix>
<li class="md-nav__item">
<a href="../../internal/infra/INFRA_AUTOMATION_PACK_V1/" class="md-nav__link">
<span class="md-ellipsis">
Infra Automation Pack v1
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../../internal/infra/monitoring_overview/" class="md-nav__link">
<span class="md-ellipsis">
Monitoring Overview
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../../internal/infra/nodes_registry_v0/" class="md-nav__link">
<span class="md-ellipsis">
Nodes Registry v0
</span>
</a>
</li>
</ul>
</nav>
</li>
<li class="md-nav__item md-nav__item--nested">
<input class="md-nav__toggle md-toggle " type="checkbox" id="__nav_5_2" >
<label class="md-nav__link" for="__nav_5_2" id="__nav_5_2_label" tabindex="0">
<span class="md-ellipsis">
Specs
</span>
<span class="md-nav__icon md-icon"></span>
</label>
<nav class="md-nav" data-md-level="2" aria-labelledby="__nav_5_2_label" aria-expanded="false">
<label class="md-nav__title" for="__nav_5_2">
<span class="md-nav__icon md-icon"></span>
Specs
</label>
<ul class="md-nav__list" data-md-scrollfix>
<li class="md-nav__item">
<a href="../../internal/specs/matrix_presence_aggregator/" class="md-nav__link">
<span class="md-ellipsis">
Matrix Presence Aggregator
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../../internal/specs/city_map_spec/" class="md-nav__link">
<span class="md-ellipsis">
City Map Spec
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../../internal/specs/node_join_protocol_draft/" class="md-nav__link">
<span class="md-ellipsis">
Node Join Protocol (Draft)
</span>
</a>
</li>
</ul>
</nav>
</li>
</ul>
</nav>
</li>
</ul>
</nav>
</div>
</div>
</div>
<div class="md-sidebar md-sidebar--secondary" data-md-component="sidebar" data-md-type="toc" >
<div class="md-sidebar__scrollwrap">
<div class="md-sidebar__inner">
<nav class="md-nav md-nav--secondary" aria-label="Table of contents">
<label class="md-nav__title" for="__toc">
<span class="md-nav__icon md-icon"></span>
Table of contents
</label>
<ul class="md-nav__list" data-md-component="toc" data-md-scrollfix>
<li class="md-nav__item">
<a href="#phase-8-dao-dashboard-governance-treasury-voting" class="md-nav__link">
<span class="md-ellipsis">
PHASE 8 — DAO Dashboard (Governance + Treasury + Voting)
</span>
</a>
<nav class="md-nav" aria-label="PHASE 8 — DAO Dashboard (Governance + Treasury + Voting)">
<ul class="md-nav__list">
<li class="md-nav__item">
<a href="#goal" class="md-nav__link">
<span class="md-ellipsis">
Goal
</span>
</a>
</li>
</ul>
</nav>
</li>
<li class="md-nav__item">
<a href="#0" class="md-nav__link">
<span class="md-ellipsis">
0. Вихідні умови
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#1-database-dao-core-schema" class="md-nav__link">
<span class="md-ellipsis">
1. Database: DAO Core Schema
</span>
</a>
<nav class="md-nav" aria-label="1. Database: DAO Core Schema">
<ul class="md-nav__list">
<li class="md-nav__item">
<a href="#11" class="md-nav__link">
<span class="md-ellipsis">
1.1. Таблиці
</span>
</a>
</li>
</ul>
</nav>
</li>
<li class="md-nav__item">
<a href="#2-backend-dao-service-fastapi" class="md-nav__link">
<span class="md-ellipsis">
2. Backend: dao-service (FastAPI)
</span>
</a>
<nav class="md-nav" aria-label="2. Backend: dao-service (FastAPI)">
<ul class="md-nav__list">
<li class="md-nav__item">
<a href="#21-modelspy" class="md-nav__link">
<span class="md-ellipsis">
2.1. models.py
</span>
</a>
</li>
</ul>
</nav>
</li>
<li class="md-nav__item">
<a href="#3-repository-layer" class="md-nav__link">
<span class="md-ellipsis">
3. Repository layer
</span>
</a>
<nav class="md-nav" aria-label="3. Repository layer">
<ul class="md-nav__list">
<li class="md-nav__item">
<a href="#31-repository_daopy" class="md-nav__link">
<span class="md-ellipsis">
3.1. repository_dao.py
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#32-repository_proposalspy" class="md-nav__link">
<span class="md-ellipsis">
3.2. repository_proposals.py
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#33-repository_votespy" class="md-nav__link">
<span class="md-ellipsis">
3.3. repository_votes.py
</span>
</a>
</li>
</ul>
</nav>
</li>
<li class="md-nav__item">
<a href="#4-governance-engine" class="md-nav__link">
<span class="md-ellipsis">
4. Governance Engine
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#5-nats-events" class="md-nav__link">
<span class="md-ellipsis">
5. NATS Events
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#6-routes-fastapi" class="md-nav__link">
<span class="md-ellipsis">
6. Routes (FastAPI)
</span>
</a>
<nav class="md-nav" aria-label="6. Routes (FastAPI)">
<ul class="md-nav__list">
<li class="md-nav__item">
<a href="#61-authpdp" class="md-nav__link">
<span class="md-ellipsis">
6.1. Auth/PDP
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#62-dao-routes" class="md-nav__link">
<span class="md-ellipsis">
6.2. DAO Routes
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#63-members-routes" class="md-nav__link">
<span class="md-ellipsis">
6.3. Members Routes
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#64-treasury-routes" class="md-nav__link">
<span class="md-ellipsis">
6.4. Treasury Routes
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#65-proposals-votes" class="md-nav__link">
<span class="md-ellipsis">
6.5. Proposals &amp; Votes
</span>
</a>
</li>
</ul>
</nav>
</li>
<li class="md-nav__item">
<a href="#7-frontend-dao-dashboard" class="md-nav__link">
<span class="md-ellipsis">
7. Frontend: DAO Dashboard
</span>
</a>
<nav class="md-nav" aria-label="7. Frontend: DAO Dashboard">
<ul class="md-nav__list">
<li class="md-nav__item">
<a href="#71-api-client" class="md-nav__link">
<span class="md-ellipsis">
7.1. API Client
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#72" class="md-nav__link">
<span class="md-ellipsis">
7.2. Сторінки
</span>
</a>
<nav class="md-nav" aria-label="7.2. Сторінки">
<ul class="md-nav__list">
<li class="md-nav__item">
<a href="#dao" class="md-nav__link">
<span class="md-ellipsis">
/dao
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#daoslug" class="md-nav__link">
<span class="md-ellipsis">
/dao/:slug
</span>
</a>
</li>
</ul>
</nav>
</li>
</ul>
</nav>
</li>
<li class="md-nav__item">
<a href="#8-websocket-live-updates-mvp" class="md-nav__link">
<span class="md-ellipsis">
8. WebSocket / Live Updates (MVP)
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#9-microdao-console" class="md-nav__link">
<span class="md-ellipsis">
9. Інтеграція з microDAO Console
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#10-docker-scripts" class="md-nav__link">
<span class="md-ellipsis">
10. Docker / Scripts
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#11-acceptance-criteria" class="md-nav__link">
<span class="md-ellipsis">
11. Acceptance Criteria
</span>
</a>
</li>
</ul>
</nav>
</div>
</div>
</div>
<div class="md-content" data-md-component="content">
<article class="md-content__inner md-typeset">
<h1 id="task_phase8_dao_dashboardmd">TASK_PHASE8_DAO_DASHBOARD.md<a class="headerlink" href="#task_phase8_dao_dashboardmd" title="Permanent link">&para;</a></h1>
<h2 id="phase-8-dao-dashboard-governance-treasury-voting">PHASE 8 — DAO Dashboard (Governance + Treasury + Voting)<a class="headerlink" href="#phase-8-dao-dashboard-governance-treasury-voting" title="Permanent link">&para;</a></h2>
<h3 id="goal">Goal<a class="headerlink" href="#goal" title="Permanent link">&para;</a></h3>
<p>Завершити <strong>DAO-рівень governance</strong> поверх вже готового microDAO Console:</p>
<ul>
<li>створити <strong>dao-service</strong> (бекенд) з повним CRUD;</li>
<li>додати <strong>governance models</strong> (simple / quadratic / delegated);</li>
<li>реалізувати <strong>proposals + votes + treasury</strong>;</li>
<li>інтегрувати з <strong>PDP/Auth</strong> (Phase 4);</li>
<li>зробити <strong>DAO Dashboard UI</strong> (frontend);</li>
<li>підключити <strong>NATS-події</strong> для живого оновлення.</li>
</ul>
<p>Фінальний результат:<br />
DAO Dashboard, який показує стан DAO (учасники, казна, пропозиції, голосування) і дозволяє керувати governance.</p>
<hr />
<h2 id="0">0. Вихідні умови<a class="headerlink" href="#0" title="Permanent link">&para;</a></h2>
<p>Вважати, що в репозиторії вже є:</p>
<ul>
<li><strong>Phase 17 завершені</strong> (Messenger, Agents, LLM, Security, Passkey, Agent Hub, microDAO Console);</li>
<li>База даних (PostgreSQL) з таблицями <code>users</code>, <code>microdaos</code>, <code>microdao_members</code>, <code>microdao_treasury</code>, <code>microdao_settings</code>;</li>
<li><strong>auth-service</strong>, <strong>pdp-service</strong>, <strong>usage-engine</strong>, <strong>messaging-service</strong>, <strong>agents-service</strong>, <strong>microdao-service</strong>;</li>
<li>Frontend (React/TS), з:</li>
<li><code>MicrodaoListPage.tsx</code>, <code>MicrodaoConsolePage.tsx</code>;</li>
<li>auth/pdp інтеграцією;</li>
<li>Agent Hub UI.</li>
</ul>
<p>Цей таск додає <strong>новий шар DAO</strong> поверх існуючих microDAO.</p>
<hr />
<h2 id="1-database-dao-core-schema">1. Database: DAO Core Schema<a class="headerlink" href="#1-database-dao-core-schema" title="Permanent link">&para;</a></h2>
<p>Створити нову міграцію:</p>
<p><code>migrations/009_create_dao_core.sql</code></p>
<h3 id="11">1.1. Таблиці<a class="headerlink" href="#11" title="Permanent link">&para;</a></h3>
<div class="codehilite"><pre><span></span><code><span class="c1">-- 1) DAO (верхній рівень governance)</span>
<span class="k">create</span><span class="w"> </span><span class="k">table</span><span class="w"> </span><span class="k">if</span><span class="w"> </span><span class="k">not</span><span class="w"> </span><span class="k">exists</span><span class="w"> </span><span class="n">dao</span><span class="w"> </span><span class="p">(</span>
<span class="w"> </span><span class="n">id</span><span class="w"> </span><span class="n">uuid</span><span class="w"> </span><span class="k">primary</span><span class="w"> </span><span class="k">key</span><span class="w"> </span><span class="k">default</span><span class="w"> </span><span class="n">gen_random_uuid</span><span class="p">(),</span>
<span class="w"> </span><span class="n">slug</span><span class="w"> </span><span class="nb">text</span><span class="w"> </span><span class="k">not</span><span class="w"> </span><span class="k">null</span><span class="w"> </span><span class="k">unique</span><span class="p">,</span>
<span class="w"> </span><span class="n">name</span><span class="w"> </span><span class="nb">text</span><span class="w"> </span><span class="k">not</span><span class="w"> </span><span class="k">null</span><span class="p">,</span>
<span class="w"> </span><span class="n">description</span><span class="w"> </span><span class="nb">text</span><span class="p">,</span>
<span class="w"> </span><span class="n">microdao_id</span><span class="w"> </span><span class="n">uuid</span><span class="w"> </span><span class="k">not</span><span class="w"> </span><span class="k">null</span><span class="w"> </span><span class="k">references</span><span class="w"> </span><span class="n">microdaos</span><span class="p">(</span><span class="n">id</span><span class="p">),</span>
<span class="w"> </span><span class="n">owner_user_id</span><span class="w"> </span><span class="n">uuid</span><span class="w"> </span><span class="k">not</span><span class="w"> </span><span class="k">null</span><span class="w"> </span><span class="k">references</span><span class="w"> </span><span class="n">users</span><span class="p">(</span><span class="n">id</span><span class="p">),</span>
<span class="w"> </span><span class="n">governance_model</span><span class="w"> </span><span class="nb">text</span><span class="w"> </span><span class="k">not</span><span class="w"> </span><span class="k">null</span><span class="w"> </span><span class="k">default</span><span class="w"> </span><span class="s1">&#39;simple&#39;</span><span class="p">,</span><span class="w"> </span><span class="c1">-- &#39;simple&#39; | &#39;quadratic&#39; | &#39;delegated&#39;</span>
<span class="w"> </span><span class="n">voting_period_seconds</span><span class="w"> </span><span class="nb">integer</span><span class="w"> </span><span class="k">not</span><span class="w"> </span><span class="k">null</span><span class="w"> </span><span class="k">default</span><span class="w"> </span><span class="mi">604800</span><span class="p">,</span><span class="w"> </span><span class="c1">-- 7 днів</span>
<span class="w"> </span><span class="n">quorum_percent</span><span class="w"> </span><span class="nb">integer</span><span class="w"> </span><span class="k">not</span><span class="w"> </span><span class="k">null</span><span class="w"> </span><span class="k">default</span><span class="w"> </span><span class="mi">20</span><span class="p">,</span><span class="w"> </span><span class="c1">-- 20%</span>
<span class="w"> </span><span class="n">is_active</span><span class="w"> </span><span class="nb">boolean</span><span class="w"> </span><span class="k">not</span><span class="w"> </span><span class="k">null</span><span class="w"> </span><span class="k">default</span><span class="w"> </span><span class="k">true</span><span class="p">,</span>
<span class="w"> </span><span class="n">created_at</span><span class="w"> </span><span class="n">timestamptz</span><span class="w"> </span><span class="k">not</span><span class="w"> </span><span class="k">null</span><span class="w"> </span><span class="k">default</span><span class="w"> </span><span class="n">now</span><span class="p">(),</span>
<span class="w"> </span><span class="n">updated_at</span><span class="w"> </span><span class="n">timestamptz</span><span class="w"> </span><span class="k">not</span><span class="w"> </span><span class="k">null</span><span class="w"> </span><span class="k">default</span><span class="w"> </span><span class="n">now</span><span class="p">()</span>
<span class="p">);</span>
<span class="k">create</span><span class="w"> </span><span class="k">index</span><span class="w"> </span><span class="k">if</span><span class="w"> </span><span class="k">not</span><span class="w"> </span><span class="k">exists</span><span class="w"> </span><span class="n">idx_dao_microdao_id</span><span class="w"> </span><span class="k">on</span><span class="w"> </span><span class="n">dao</span><span class="p">(</span><span class="n">microdao_id</span><span class="p">);</span>
<span class="k">create</span><span class="w"> </span><span class="k">index</span><span class="w"> </span><span class="k">if</span><span class="w"> </span><span class="k">not</span><span class="w"> </span><span class="k">exists</span><span class="w"> </span><span class="n">idx_dao_owner_user_id</span><span class="w"> </span><span class="k">on</span><span class="w"> </span><span class="n">dao</span><span class="p">(</span><span class="n">owner_user_id</span><span class="p">);</span>
<span class="c1">-- 2) DAO Members (над microdao_members)</span>
<span class="k">create</span><span class="w"> </span><span class="k">table</span><span class="w"> </span><span class="k">if</span><span class="w"> </span><span class="k">not</span><span class="w"> </span><span class="k">exists</span><span class="w"> </span><span class="n">dao_members</span><span class="w"> </span><span class="p">(</span>
<span class="w"> </span><span class="n">id</span><span class="w"> </span><span class="n">uuid</span><span class="w"> </span><span class="k">primary</span><span class="w"> </span><span class="k">key</span><span class="w"> </span><span class="k">default</span><span class="w"> </span><span class="n">gen_random_uuid</span><span class="p">(),</span>
<span class="w"> </span><span class="n">dao_id</span><span class="w"> </span><span class="n">uuid</span><span class="w"> </span><span class="k">not</span><span class="w"> </span><span class="k">null</span><span class="w"> </span><span class="k">references</span><span class="w"> </span><span class="n">dao</span><span class="p">(</span><span class="n">id</span><span class="p">)</span><span class="w"> </span><span class="k">on</span><span class="w"> </span><span class="k">delete</span><span class="w"> </span><span class="k">cascade</span><span class="p">,</span>
<span class="w"> </span><span class="n">user_id</span><span class="w"> </span><span class="n">uuid</span><span class="w"> </span><span class="k">not</span><span class="w"> </span><span class="k">null</span><span class="w"> </span><span class="k">references</span><span class="w"> </span><span class="n">users</span><span class="p">(</span><span class="n">id</span><span class="p">),</span>
<span class="w"> </span><span class="k">role</span><span class="w"> </span><span class="nb">text</span><span class="w"> </span><span class="k">not</span><span class="w"> </span><span class="k">null</span><span class="p">,</span><span class="w"> </span><span class="c1">-- &#39;owner&#39; | &#39;admin&#39; | &#39;member&#39; | &#39;guest&#39;</span>
<span class="w"> </span><span class="n">joined_at</span><span class="w"> </span><span class="n">timestamptz</span><span class="w"> </span><span class="k">not</span><span class="w"> </span><span class="k">null</span><span class="w"> </span><span class="k">default</span><span class="w"> </span><span class="n">now</span><span class="p">()</span>
<span class="p">);</span>
<span class="k">create</span><span class="w"> </span><span class="k">index</span><span class="w"> </span><span class="k">if</span><span class="w"> </span><span class="k">not</span><span class="w"> </span><span class="k">exists</span><span class="w"> </span><span class="n">idx_dao_members_user_id</span><span class="w"> </span><span class="k">on</span><span class="w"> </span><span class="n">dao_members</span><span class="p">(</span><span class="n">user_id</span><span class="p">);</span>
<span class="k">create</span><span class="w"> </span><span class="k">index</span><span class="w"> </span><span class="k">if</span><span class="w"> </span><span class="k">not</span><span class="w"> </span><span class="k">exists</span><span class="w"> </span><span class="n">idx_dao_members_dao_id_role</span><span class="w"> </span><span class="k">on</span><span class="w"> </span><span class="n">dao_members</span><span class="p">(</span><span class="n">dao_id</span><span class="p">,</span><span class="w"> </span><span class="k">role</span><span class="p">);</span>
<span class="c1">-- 3) DAO Treasury (агрегований шар над microdao_treasury, але на рівні DAO)</span>
<span class="k">create</span><span class="w"> </span><span class="k">table</span><span class="w"> </span><span class="k">if</span><span class="w"> </span><span class="k">not</span><span class="w"> </span><span class="k">exists</span><span class="w"> </span><span class="n">dao_treasury</span><span class="w"> </span><span class="p">(</span>
<span class="w"> </span><span class="n">id</span><span class="w"> </span><span class="n">uuid</span><span class="w"> </span><span class="k">primary</span><span class="w"> </span><span class="k">key</span><span class="w"> </span><span class="k">default</span><span class="w"> </span><span class="n">gen_random_uuid</span><span class="p">(),</span>
<span class="w"> </span><span class="n">dao_id</span><span class="w"> </span><span class="n">uuid</span><span class="w"> </span><span class="k">not</span><span class="w"> </span><span class="k">null</span><span class="w"> </span><span class="k">references</span><span class="w"> </span><span class="n">dao</span><span class="p">(</span><span class="n">id</span><span class="p">)</span><span class="w"> </span><span class="k">on</span><span class="w"> </span><span class="k">delete</span><span class="w"> </span><span class="k">cascade</span><span class="p">,</span>
<span class="w"> </span><span class="n">token_symbol</span><span class="w"> </span><span class="nb">text</span><span class="w"> </span><span class="k">not</span><span class="w"> </span><span class="k">null</span><span class="p">,</span>
<span class="w"> </span><span class="n">contract_address</span><span class="w"> </span><span class="nb">text</span><span class="p">,</span>
<span class="w"> </span><span class="n">balance</span><span class="w"> </span><span class="nb">numeric</span><span class="p">(</span><span class="mi">30</span><span class="p">,</span><span class="w"> </span><span class="mi">8</span><span class="p">)</span><span class="w"> </span><span class="k">not</span><span class="w"> </span><span class="k">null</span><span class="w"> </span><span class="k">default</span><span class="w"> </span><span class="mi">0</span><span class="p">,</span>
<span class="w"> </span><span class="n">updated_at</span><span class="w"> </span><span class="n">timestamptz</span><span class="w"> </span><span class="k">not</span><span class="w"> </span><span class="k">null</span><span class="w"> </span><span class="k">default</span><span class="w"> </span><span class="n">now</span><span class="p">()</span>
<span class="p">);</span>
<span class="k">create</span><span class="w"> </span><span class="k">unique</span><span class="w"> </span><span class="k">index</span><span class="w"> </span><span class="k">if</span><span class="w"> </span><span class="k">not</span><span class="w"> </span><span class="k">exists</span><span class="w"> </span><span class="n">uq_dao_treasury_token</span>
<span class="w"> </span><span class="k">on</span><span class="w"> </span><span class="n">dao_treasury</span><span class="p">(</span><span class="n">dao_id</span><span class="p">,</span><span class="w"> </span><span class="n">token_symbol</span><span class="p">);</span>
<span class="c1">-- 4) DAO Proposals</span>
<span class="k">create</span><span class="w"> </span><span class="k">table</span><span class="w"> </span><span class="k">if</span><span class="w"> </span><span class="k">not</span><span class="w"> </span><span class="k">exists</span><span class="w"> </span><span class="n">dao_proposals</span><span class="w"> </span><span class="p">(</span>
<span class="w"> </span><span class="n">id</span><span class="w"> </span><span class="n">uuid</span><span class="w"> </span><span class="k">primary</span><span class="w"> </span><span class="k">key</span><span class="w"> </span><span class="k">default</span><span class="w"> </span><span class="n">gen_random_uuid</span><span class="p">(),</span>
<span class="w"> </span><span class="n">dao_id</span><span class="w"> </span><span class="n">uuid</span><span class="w"> </span><span class="k">not</span><span class="w"> </span><span class="k">null</span><span class="w"> </span><span class="k">references</span><span class="w"> </span><span class="n">dao</span><span class="p">(</span><span class="n">id</span><span class="p">)</span><span class="w"> </span><span class="k">on</span><span class="w"> </span><span class="k">delete</span><span class="w"> </span><span class="k">cascade</span><span class="p">,</span>
<span class="w"> </span><span class="n">slug</span><span class="w"> </span><span class="nb">text</span><span class="w"> </span><span class="k">not</span><span class="w"> </span><span class="k">null</span><span class="p">,</span>
<span class="w"> </span><span class="n">title</span><span class="w"> </span><span class="nb">text</span><span class="w"> </span><span class="k">not</span><span class="w"> </span><span class="k">null</span><span class="p">,</span>
<span class="w"> </span><span class="n">description</span><span class="w"> </span><span class="nb">text</span><span class="p">,</span>
<span class="w"> </span><span class="n">created_by_user_id</span><span class="w"> </span><span class="n">uuid</span><span class="w"> </span><span class="k">not</span><span class="w"> </span><span class="k">null</span><span class="w"> </span><span class="k">references</span><span class="w"> </span><span class="n">users</span><span class="p">(</span><span class="n">id</span><span class="p">),</span>
<span class="w"> </span><span class="n">created_at</span><span class="w"> </span><span class="n">timestamptz</span><span class="w"> </span><span class="k">not</span><span class="w"> </span><span class="k">null</span><span class="w"> </span><span class="k">default</span><span class="w"> </span><span class="n">now</span><span class="p">(),</span>
<span class="w"> </span><span class="n">start_at</span><span class="w"> </span><span class="n">timestamptz</span><span class="p">,</span>
<span class="w"> </span><span class="n">end_at</span><span class="w"> </span><span class="n">timestamptz</span><span class="p">,</span>
<span class="w"> </span><span class="n">status</span><span class="w"> </span><span class="nb">text</span><span class="w"> </span><span class="k">not</span><span class="w"> </span><span class="k">null</span><span class="w"> </span><span class="k">default</span><span class="w"> </span><span class="s1">&#39;draft&#39;</span><span class="p">,</span><span class="w"> </span><span class="c1">-- &#39;draft&#39; | &#39;active&#39; | &#39;passed&#39; | &#39;rejected&#39; | &#39;executed&#39;</span>
<span class="w"> </span><span class="n">governance_model_override</span><span class="w"> </span><span class="nb">text</span><span class="p">,</span><span class="w"> </span><span class="c1">-- optional override</span>
<span class="w"> </span><span class="n">quorum_percent_override</span><span class="w"> </span><span class="nb">integer</span>
<span class="p">);</span>
<span class="k">create</span><span class="w"> </span><span class="k">unique</span><span class="w"> </span><span class="k">index</span><span class="w"> </span><span class="k">if</span><span class="w"> </span><span class="k">not</span><span class="w"> </span><span class="k">exists</span><span class="w"> </span><span class="n">uq_dao_proposals_slug</span>
<span class="w"> </span><span class="k">on</span><span class="w"> </span><span class="n">dao_proposals</span><span class="p">(</span><span class="n">dao_id</span><span class="p">,</span><span class="w"> </span><span class="n">slug</span><span class="p">);</span>
<span class="c1">-- 5) DAO Votes</span>
<span class="k">create</span><span class="w"> </span><span class="k">table</span><span class="w"> </span><span class="k">if</span><span class="w"> </span><span class="k">not</span><span class="w"> </span><span class="k">exists</span><span class="w"> </span><span class="n">dao_votes</span><span class="w"> </span><span class="p">(</span>
<span class="w"> </span><span class="n">id</span><span class="w"> </span><span class="n">uuid</span><span class="w"> </span><span class="k">primary</span><span class="w"> </span><span class="k">key</span><span class="w"> </span><span class="k">default</span><span class="w"> </span><span class="n">gen_random_uuid</span><span class="p">(),</span>
<span class="w"> </span><span class="n">proposal_id</span><span class="w"> </span><span class="n">uuid</span><span class="w"> </span><span class="k">not</span><span class="w"> </span><span class="k">null</span><span class="w"> </span><span class="k">references</span><span class="w"> </span><span class="n">dao_proposals</span><span class="p">(</span><span class="n">id</span><span class="p">)</span><span class="w"> </span><span class="k">on</span><span class="w"> </span><span class="k">delete</span><span class="w"> </span><span class="k">cascade</span><span class="p">,</span>
<span class="w"> </span><span class="n">voter_user_id</span><span class="w"> </span><span class="n">uuid</span><span class="w"> </span><span class="k">not</span><span class="w"> </span><span class="k">null</span><span class="w"> </span><span class="k">references</span><span class="w"> </span><span class="n">users</span><span class="p">(</span><span class="n">id</span><span class="p">),</span>
<span class="w"> </span><span class="n">vote_value</span><span class="w"> </span><span class="nb">text</span><span class="w"> </span><span class="k">not</span><span class="w"> </span><span class="k">null</span><span class="p">,</span><span class="w"> </span><span class="c1">-- &#39;yes&#39; | &#39;no&#39; | &#39;abstain&#39;</span>
<span class="w"> </span><span class="n">weight</span><span class="w"> </span><span class="nb">numeric</span><span class="p">(</span><span class="mi">30</span><span class="p">,</span><span class="w"> </span><span class="mi">8</span><span class="p">)</span><span class="w"> </span><span class="k">not</span><span class="w"> </span><span class="k">null</span><span class="p">,</span><span class="w"> </span><span class="c1">-- actual weight after applying model</span>
<span class="w"> </span><span class="n">raw_power</span><span class="w"> </span><span class="nb">numeric</span><span class="p">(</span><span class="mi">30</span><span class="p">,</span><span class="w"> </span><span class="mi">8</span><span class="p">),</span><span class="w"> </span><span class="c1">-- до обробки</span>
<span class="w"> </span><span class="n">created_at</span><span class="w"> </span><span class="n">timestamptz</span><span class="w"> </span><span class="k">not</span><span class="w"> </span><span class="k">null</span><span class="w"> </span><span class="k">default</span><span class="w"> </span><span class="n">now</span><span class="p">()</span>
<span class="p">);</span>
<span class="k">create</span><span class="w"> </span><span class="k">index</span><span class="w"> </span><span class="k">if</span><span class="w"> </span><span class="k">not</span><span class="w"> </span><span class="k">exists</span><span class="w"> </span><span class="n">idx_dao_votes_proposal_id</span><span class="w"> </span><span class="k">on</span><span class="w"> </span><span class="n">dao_votes</span><span class="p">(</span><span class="n">proposal_id</span><span class="p">);</span>
<span class="k">create</span><span class="w"> </span><span class="k">unique</span><span class="w"> </span><span class="k">index</span><span class="w"> </span><span class="k">if</span><span class="w"> </span><span class="k">not</span><span class="w"> </span><span class="k">exists</span><span class="w"> </span><span class="n">uq_dao_votes_proposal_voter</span>
<span class="w"> </span><span class="k">on</span><span class="w"> </span><span class="n">dao_votes</span><span class="p">(</span><span class="n">proposal_id</span><span class="p">,</span><span class="w"> </span><span class="n">voter_user_id</span><span class="p">);</span>
<span class="c1">-- 6) DAO Roles (додатковий шар, якщо потрібні нестандартні ролі)</span>
<span class="k">create</span><span class="w"> </span><span class="k">table</span><span class="w"> </span><span class="k">if</span><span class="w"> </span><span class="k">not</span><span class="w"> </span><span class="k">exists</span><span class="w"> </span><span class="n">dao_roles</span><span class="w"> </span><span class="p">(</span>
<span class="w"> </span><span class="n">id</span><span class="w"> </span><span class="n">uuid</span><span class="w"> </span><span class="k">primary</span><span class="w"> </span><span class="k">key</span><span class="w"> </span><span class="k">default</span><span class="w"> </span><span class="n">gen_random_uuid</span><span class="p">(),</span>
<span class="w"> </span><span class="n">dao_id</span><span class="w"> </span><span class="n">uuid</span><span class="w"> </span><span class="k">not</span><span class="w"> </span><span class="k">null</span><span class="w"> </span><span class="k">references</span><span class="w"> </span><span class="n">dao</span><span class="p">(</span><span class="n">id</span><span class="p">)</span><span class="w"> </span><span class="k">on</span><span class="w"> </span><span class="k">delete</span><span class="w"> </span><span class="k">cascade</span><span class="p">,</span>
<span class="w"> </span><span class="n">code</span><span class="w"> </span><span class="nb">text</span><span class="w"> </span><span class="k">not</span><span class="w"> </span><span class="k">null</span><span class="p">,</span>
<span class="w"> </span><span class="n">name</span><span class="w"> </span><span class="nb">text</span><span class="w"> </span><span class="k">not</span><span class="w"> </span><span class="k">null</span><span class="p">,</span>
<span class="w"> </span><span class="n">description</span><span class="w"> </span><span class="nb">text</span>
<span class="p">);</span>
<span class="k">create</span><span class="w"> </span><span class="k">unique</span><span class="w"> </span><span class="k">index</span><span class="w"> </span><span class="k">if</span><span class="w"> </span><span class="k">not</span><span class="w"> </span><span class="k">exists</span><span class="w"> </span><span class="n">uq_dao_roles_code</span>
<span class="w"> </span><span class="k">on</span><span class="w"> </span><span class="n">dao_roles</span><span class="p">(</span><span class="n">dao_id</span><span class="p">,</span><span class="w"> </span><span class="n">code</span><span class="p">);</span>
<span class="c1">-- 7) DAO Role Assignments</span>
<span class="k">create</span><span class="w"> </span><span class="k">table</span><span class="w"> </span><span class="k">if</span><span class="w"> </span><span class="k">not</span><span class="w"> </span><span class="k">exists</span><span class="w"> </span><span class="n">dao_role_assignments</span><span class="w"> </span><span class="p">(</span>
<span class="w"> </span><span class="n">id</span><span class="w"> </span><span class="n">uuid</span><span class="w"> </span><span class="k">primary</span><span class="w"> </span><span class="k">key</span><span class="w"> </span><span class="k">default</span><span class="w"> </span><span class="n">gen_random_uuid</span><span class="p">(),</span>
<span class="w"> </span><span class="n">dao_id</span><span class="w"> </span><span class="n">uuid</span><span class="w"> </span><span class="k">not</span><span class="w"> </span><span class="k">null</span><span class="w"> </span><span class="k">references</span><span class="w"> </span><span class="n">dao</span><span class="p">(</span><span class="n">id</span><span class="p">)</span><span class="w"> </span><span class="k">on</span><span class="w"> </span><span class="k">delete</span><span class="w"> </span><span class="k">cascade</span><span class="p">,</span>
<span class="w"> </span><span class="n">user_id</span><span class="w"> </span><span class="n">uuid</span><span class="w"> </span><span class="k">not</span><span class="w"> </span><span class="k">null</span><span class="w"> </span><span class="k">references</span><span class="w"> </span><span class="n">users</span><span class="p">(</span><span class="n">id</span><span class="p">),</span>
<span class="w"> </span><span class="n">role_code</span><span class="w"> </span><span class="nb">text</span><span class="w"> </span><span class="k">not</span><span class="w"> </span><span class="k">null</span><span class="p">,</span>
<span class="w"> </span><span class="n">assigned_at</span><span class="w"> </span><span class="n">timestamptz</span><span class="w"> </span><span class="k">not</span><span class="w"> </span><span class="k">null</span><span class="w"> </span><span class="k">default</span><span class="w"> </span><span class="n">now</span><span class="p">()</span>
<span class="p">);</span>
<span class="k">create</span><span class="w"> </span><span class="k">index</span><span class="w"> </span><span class="k">if</span><span class="w"> </span><span class="k">not</span><span class="w"> </span><span class="k">exists</span><span class="w"> </span><span class="n">idx_dao_role_assignments_user</span>
<span class="w"> </span><span class="k">on</span><span class="w"> </span><span class="n">dao_role_assignments</span><span class="p">(</span><span class="n">user_id</span><span class="p">);</span>
<span class="c1">-- 8) DAO Audit Log</span>
<span class="k">create</span><span class="w"> </span><span class="k">table</span><span class="w"> </span><span class="k">if</span><span class="w"> </span><span class="k">not</span><span class="w"> </span><span class="k">exists</span><span class="w"> </span><span class="n">dao_audit_log</span><span class="w"> </span><span class="p">(</span>
<span class="w"> </span><span class="n">id</span><span class="w"> </span><span class="n">uuid</span><span class="w"> </span><span class="k">primary</span><span class="w"> </span><span class="k">key</span><span class="w"> </span><span class="k">default</span><span class="w"> </span><span class="n">gen_random_uuid</span><span class="p">(),</span>
<span class="w"> </span><span class="n">dao_id</span><span class="w"> </span><span class="n">uuid</span><span class="w"> </span><span class="k">not</span><span class="w"> </span><span class="k">null</span><span class="w"> </span><span class="k">references</span><span class="w"> </span><span class="n">dao</span><span class="p">(</span><span class="n">id</span><span class="p">)</span><span class="w"> </span><span class="k">on</span><span class="w"> </span><span class="k">delete</span><span class="w"> </span><span class="k">cascade</span><span class="p">,</span>
<span class="w"> </span><span class="n">actor_user_id</span><span class="w"> </span><span class="n">uuid</span><span class="w"> </span><span class="k">references</span><span class="w"> </span><span class="n">users</span><span class="p">(</span><span class="n">id</span><span class="p">),</span>
<span class="w"> </span><span class="n">event_type</span><span class="w"> </span><span class="nb">text</span><span class="w"> </span><span class="k">not</span><span class="w"> </span><span class="k">null</span><span class="p">,</span>
<span class="w"> </span><span class="n">event_payload</span><span class="w"> </span><span class="n">jsonb</span><span class="p">,</span>
<span class="w"> </span><span class="n">created_at</span><span class="w"> </span><span class="n">timestamptz</span><span class="w"> </span><span class="k">not</span><span class="w"> </span><span class="k">null</span><span class="w"> </span><span class="k">default</span><span class="w"> </span><span class="n">now</span><span class="p">()</span>
<span class="p">);</span>
<span class="k">create</span><span class="w"> </span><span class="k">index</span><span class="w"> </span><span class="k">if</span><span class="w"> </span><span class="k">not</span><span class="w"> </span><span class="k">exists</span><span class="w"> </span><span class="n">idx_dao_audit_log_dao_id</span>
<span class="w"> </span><span class="k">on</span><span class="w"> </span><span class="n">dao_audit_log</span><span class="p">(</span><span class="n">dao_id</span><span class="p">);</span>
</code></pre></div>
<p>Перевірити, що міграція застосовується без помилок.</p>
<hr />
<h2 id="2-backend-dao-service-fastapi">2. Backend: <code>dao-service</code> (FastAPI)<a class="headerlink" href="#2-backend-dao-service-fastapi" title="Permanent link">&para;</a></h2>
<p>Створити новий сервіс:</p>
<p><code>services/dao-service/</code>:</p>
<ul>
<li><code>main.py</code></li>
<li><code>models.py</code></li>
<li><code>repository_dao.py</code></li>
<li><code>repository_proposals.py</code></li>
<li><code>repository_votes.py</code></li>
<li><code>governance_engine.py</code></li>
<li><code>nats_events.py</code></li>
<li><code>auth_client.py</code> / <code>pdp_client.py</code> (як thin-обгортки над існуючими)</li>
<li><code>requirements.txt</code></li>
<li><code>Dockerfile</code></li>
<li><code>README.md</code></li>
</ul>
<h3 id="21-modelspy">2.1. models.py<a class="headerlink" href="#21-modelspy" title="Permanent link">&para;</a></h3>
<p>Оголосити Pydantic-схеми (адаптувати до стилю проєкту):</p>
<div class="codehilite"><pre><span></span><code><span class="kn">from</span><span class="w"> </span><span class="nn">datetime</span><span class="w"> </span><span class="kn">import</span> <span class="n">datetime</span>
<span class="kn">from</span><span class="w"> </span><span class="nn">decimal</span><span class="w"> </span><span class="kn">import</span> <span class="n">Decimal</span>
<span class="kn">from</span><span class="w"> </span><span class="nn">pydantic</span><span class="w"> </span><span class="kn">import</span> <span class="n">BaseModel</span>
<span class="k">class</span><span class="w"> </span><span class="nc">DaoBase</span><span class="p">(</span><span class="n">BaseModel</span><span class="p">):</span>
<span class="n">slug</span><span class="p">:</span> <span class="nb">str</span>
<span class="n">name</span><span class="p">:</span> <span class="nb">str</span>
<span class="n">description</span><span class="p">:</span> <span class="nb">str</span> <span class="o">|</span> <span class="kc">None</span> <span class="o">=</span> <span class="kc">None</span>
<span class="k">class</span><span class="w"> </span><span class="nc">DaoCreate</span><span class="p">(</span><span class="n">DaoBase</span><span class="p">):</span>
<span class="n">governance_model</span><span class="p">:</span> <span class="nb">str</span> <span class="o">|</span> <span class="kc">None</span> <span class="o">=</span> <span class="kc">None</span>
<span class="n">voting_period_seconds</span><span class="p">:</span> <span class="nb">int</span> <span class="o">|</span> <span class="kc">None</span> <span class="o">=</span> <span class="kc">None</span>
<span class="n">quorum_percent</span><span class="p">:</span> <span class="nb">int</span> <span class="o">|</span> <span class="kc">None</span> <span class="o">=</span> <span class="kc">None</span>
<span class="k">class</span><span class="w"> </span><span class="nc">DaoUpdate</span><span class="p">(</span><span class="n">BaseModel</span><span class="p">):</span>
<span class="n">name</span><span class="p">:</span> <span class="nb">str</span> <span class="o">|</span> <span class="kc">None</span> <span class="o">=</span> <span class="kc">None</span>
<span class="n">description</span><span class="p">:</span> <span class="nb">str</span> <span class="o">|</span> <span class="kc">None</span> <span class="o">=</span> <span class="kc">None</span>
<span class="n">governance_model</span><span class="p">:</span> <span class="nb">str</span> <span class="o">|</span> <span class="kc">None</span> <span class="o">=</span> <span class="kc">None</span>
<span class="n">voting_period_seconds</span><span class="p">:</span> <span class="nb">int</span> <span class="o">|</span> <span class="kc">None</span> <span class="o">=</span> <span class="kc">None</span>
<span class="n">quorum_percent</span><span class="p">:</span> <span class="nb">int</span> <span class="o">|</span> <span class="kc">None</span> <span class="o">=</span> <span class="kc">None</span>
<span class="n">is_active</span><span class="p">:</span> <span class="nb">bool</span> <span class="o">|</span> <span class="kc">None</span> <span class="o">=</span> <span class="kc">None</span>
<span class="k">class</span><span class="w"> </span><span class="nc">DaoRead</span><span class="p">(</span><span class="n">DaoBase</span><span class="p">):</span>
<span class="nb">id</span><span class="p">:</span> <span class="nb">str</span>
<span class="n">microdao_id</span><span class="p">:</span> <span class="nb">str</span>
<span class="n">owner_user_id</span><span class="p">:</span> <span class="nb">str</span>
<span class="n">governance_model</span><span class="p">:</span> <span class="nb">str</span>
<span class="n">voting_period_seconds</span><span class="p">:</span> <span class="nb">int</span>
<span class="n">quorum_percent</span><span class="p">:</span> <span class="nb">int</span>
<span class="n">is_active</span><span class="p">:</span> <span class="nb">bool</span>
<span class="n">created_at</span><span class="p">:</span> <span class="n">datetime</span>
<span class="n">updated_at</span><span class="p">:</span> <span class="n">datetime</span>
<span class="k">class</span><span class="w"> </span><span class="nc">DaoMember</span><span class="p">(</span><span class="n">BaseModel</span><span class="p">):</span>
<span class="nb">id</span><span class="p">:</span> <span class="nb">str</span>
<span class="n">user_id</span><span class="p">:</span> <span class="nb">str</span>
<span class="n">role</span><span class="p">:</span> <span class="nb">str</span>
<span class="n">joined_at</span><span class="p">:</span> <span class="n">datetime</span>
<span class="k">class</span><span class="w"> </span><span class="nc">DaoTreasuryItem</span><span class="p">(</span><span class="n">BaseModel</span><span class="p">):</span>
<span class="n">token_symbol</span><span class="p">:</span> <span class="nb">str</span>
<span class="n">contract_address</span><span class="p">:</span> <span class="nb">str</span> <span class="o">|</span> <span class="kc">None</span> <span class="o">=</span> <span class="kc">None</span>
<span class="n">balance</span><span class="p">:</span> <span class="n">Decimal</span>
<span class="k">class</span><span class="w"> </span><span class="nc">ProposalBase</span><span class="p">(</span><span class="n">BaseModel</span><span class="p">):</span>
<span class="n">slug</span><span class="p">:</span> <span class="nb">str</span>
<span class="n">title</span><span class="p">:</span> <span class="nb">str</span>
<span class="n">description</span><span class="p">:</span> <span class="nb">str</span> <span class="o">|</span> <span class="kc">None</span> <span class="o">=</span> <span class="kc">None</span>
<span class="k">class</span><span class="w"> </span><span class="nc">ProposalCreate</span><span class="p">(</span><span class="n">ProposalBase</span><span class="p">):</span>
<span class="n">start_at</span><span class="p">:</span> <span class="n">datetime</span> <span class="o">|</span> <span class="kc">None</span> <span class="o">=</span> <span class="kc">None</span>
<span class="n">end_at</span><span class="p">:</span> <span class="n">datetime</span> <span class="o">|</span> <span class="kc">None</span> <span class="o">=</span> <span class="kc">None</span>
<span class="k">class</span><span class="w"> </span><span class="nc">ProposalRead</span><span class="p">(</span><span class="n">ProposalBase</span><span class="p">):</span>
<span class="nb">id</span><span class="p">:</span> <span class="nb">str</span>
<span class="n">dao_id</span><span class="p">:</span> <span class="nb">str</span>
<span class="n">created_by_user_id</span><span class="p">:</span> <span class="nb">str</span>
<span class="n">created_at</span><span class="p">:</span> <span class="n">datetime</span>
<span class="n">start_at</span><span class="p">:</span> <span class="n">datetime</span> <span class="o">|</span> <span class="kc">None</span>
<span class="n">end_at</span><span class="p">:</span> <span class="n">datetime</span> <span class="o">|</span> <span class="kc">None</span>
<span class="n">status</span><span class="p">:</span> <span class="nb">str</span>
<span class="n">governance_model_override</span><span class="p">:</span> <span class="nb">str</span> <span class="o">|</span> <span class="kc">None</span>
<span class="n">quorum_percent_override</span><span class="p">:</span> <span class="nb">int</span> <span class="o">|</span> <span class="kc">None</span>
<span class="k">class</span><span class="w"> </span><span class="nc">VoteCreate</span><span class="p">(</span><span class="n">BaseModel</span><span class="p">):</span>
<span class="n">vote_value</span><span class="p">:</span> <span class="nb">str</span> <span class="c1"># &#39;yes&#39; | &#39;no&#39; | &#39;abstain&#39;</span>
<span class="k">class</span><span class="w"> </span><span class="nc">VoteRead</span><span class="p">(</span><span class="n">BaseModel</span><span class="p">):</span>
<span class="nb">id</span><span class="p">:</span> <span class="nb">str</span>
<span class="n">proposal_id</span><span class="p">:</span> <span class="nb">str</span>
<span class="n">voter_user_id</span><span class="p">:</span> <span class="nb">str</span>
<span class="n">vote_value</span><span class="p">:</span> <span class="nb">str</span>
<span class="n">weight</span><span class="p">:</span> <span class="n">Decimal</span>
<span class="n">raw_power</span><span class="p">:</span> <span class="n">Decimal</span> <span class="o">|</span> <span class="kc">None</span>
<span class="n">created_at</span><span class="p">:</span> <span class="n">datetime</span>
<span class="k">class</span><span class="w"> </span><span class="nc">DaoOverview</span><span class="p">(</span><span class="n">BaseModel</span><span class="p">):</span>
<span class="n">dao</span><span class="p">:</span> <span class="n">DaoRead</span>
<span class="n">members_count</span><span class="p">:</span> <span class="nb">int</span>
<span class="n">active_proposals_count</span><span class="p">:</span> <span class="nb">int</span>
<span class="n">treasury_items</span><span class="p">:</span> <span class="nb">list</span><span class="p">[</span><span class="n">DaoTreasuryItem</span><span class="p">]</span>
</code></pre></div>
<hr />
<h2 id="3-repository-layer">3. Repository layer<a class="headerlink" href="#3-repository-layer" title="Permanent link">&para;</a></h2>
<h3 id="31-repository_daopy">3.1. <code>repository_dao.py</code><a class="headerlink" href="#31-repository_daopy" title="Permanent link">&para;</a></h3>
<p>Реалізувати:</p>
<div class="codehilite"><pre><span></span><code><span class="k">async</span> <span class="k">def</span><span class="w"> </span><span class="nf">list_dao_for_user</span><span class="p">(</span><span class="n">db</span><span class="p">,</span> <span class="n">user_id</span><span class="p">:</span> <span class="n">uuid</span><span class="o">.</span><span class="n">UUID</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">list</span><span class="p">[</span><span class="n">DaoRead</span><span class="p">]:</span> <span class="o">...</span>
<span class="k">async</span> <span class="k">def</span><span class="w"> </span><span class="nf">get_dao_by_slug</span><span class="p">(</span><span class="n">db</span><span class="p">,</span> <span class="n">slug</span><span class="p">:</span> <span class="nb">str</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">DaoRead</span> <span class="o">|</span> <span class="kc">None</span><span class="p">:</span> <span class="o">...</span>
<span class="k">async</span> <span class="k">def</span><span class="w"> </span><span class="nf">create_dao</span><span class="p">(</span><span class="n">db</span><span class="p">,</span> <span class="o">*</span><span class="p">,</span> <span class="n">microdao_id</span><span class="p">,</span> <span class="n">owner_user_id</span><span class="p">,</span> <span class="n">data</span><span class="p">:</span> <span class="n">DaoCreate</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">DaoRead</span><span class="p">:</span> <span class="o">...</span>
<span class="k">async</span> <span class="k">def</span><span class="w"> </span><span class="nf">update_dao</span><span class="p">(</span><span class="n">db</span><span class="p">,</span> <span class="o">*</span><span class="p">,</span> <span class="n">dao_id</span><span class="p">,</span> <span class="n">data</span><span class="p">:</span> <span class="n">DaoUpdate</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">DaoRead</span> <span class="o">|</span> <span class="kc">None</span><span class="p">:</span> <span class="o">...</span>
<span class="k">async</span> <span class="k">def</span><span class="w"> </span><span class="nf">soft_delete_dao</span><span class="p">(</span><span class="n">db</span><span class="p">,</span> <span class="o">*</span><span class="p">,</span> <span class="n">dao_id</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="kc">None</span><span class="p">:</span> <span class="o">...</span>
<span class="k">async</span> <span class="k">def</span><span class="w"> </span><span class="nf">list_members</span><span class="p">(</span><span class="n">db</span><span class="p">,</span> <span class="n">dao_id</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">list</span><span class="p">[</span><span class="n">DaoMember</span><span class="p">]:</span> <span class="o">...</span>
<span class="k">async</span> <span class="k">def</span><span class="w"> </span><span class="nf">add_member</span><span class="p">(</span><span class="n">db</span><span class="p">,</span> <span class="n">dao_id</span><span class="p">,</span> <span class="n">user_id</span><span class="p">,</span> <span class="n">role</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">DaoMember</span><span class="p">:</span> <span class="o">...</span>
<span class="k">async</span> <span class="k">def</span><span class="w"> </span><span class="nf">remove_member</span><span class="p">(</span><span class="n">db</span><span class="p">,</span> <span class="n">member_id</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="kc">None</span><span class="p">:</span> <span class="o">...</span>
<span class="k">async</span> <span class="k">def</span><span class="w"> </span><span class="nf">get_treasury_items</span><span class="p">(</span><span class="n">db</span><span class="p">,</span> <span class="n">dao_id</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">list</span><span class="p">[</span><span class="n">DaoTreasuryItem</span><span class="p">]:</span> <span class="o">...</span>
</code></pre></div>
<h3 id="32-repository_proposalspy">3.2. <code>repository_proposals.py</code><a class="headerlink" href="#32-repository_proposalspy" title="Permanent link">&para;</a></h3>
<div class="codehilite"><pre><span></span><code><span class="k">async</span> <span class="k">def</span><span class="w"> </span><span class="nf">list_proposals</span><span class="p">(</span><span class="n">db</span><span class="p">,</span> <span class="n">dao_id</span><span class="p">:</span> <span class="n">uuid</span><span class="o">.</span><span class="n">UUID</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">list</span><span class="p">[</span><span class="n">ProposalRead</span><span class="p">]:</span> <span class="o">...</span>
<span class="k">async</span> <span class="k">def</span><span class="w"> </span><span class="nf">get_proposal</span><span class="p">(</span><span class="n">db</span><span class="p">,</span> <span class="n">proposal_id</span><span class="p">:</span> <span class="n">uuid</span><span class="o">.</span><span class="n">UUID</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">ProposalRead</span> <span class="o">|</span> <span class="kc">None</span><span class="p">:</span> <span class="o">...</span>
<span class="k">async</span> <span class="k">def</span><span class="w"> </span><span class="nf">get_proposal_by_slug</span><span class="p">(</span><span class="n">db</span><span class="p">,</span> <span class="n">dao_id</span><span class="p">,</span> <span class="n">slug</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">ProposalRead</span> <span class="o">|</span> <span class="kc">None</span><span class="p">:</span> <span class="o">...</span>
<span class="k">async</span> <span class="k">def</span><span class="w"> </span><span class="nf">create_proposal</span><span class="p">(</span><span class="n">db</span><span class="p">,</span> <span class="n">dao_id</span><span class="p">,</span> <span class="n">created_by_user_id</span><span class="p">,</span> <span class="n">data</span><span class="p">:</span> <span class="n">ProposalCreate</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">ProposalRead</span><span class="p">:</span> <span class="o">...</span>
<span class="k">async</span> <span class="k">def</span><span class="w"> </span><span class="nf">update_proposal_status</span><span class="p">(</span><span class="n">db</span><span class="p">,</span> <span class="n">proposal_id</span><span class="p">,</span> <span class="n">status</span><span class="p">:</span> <span class="nb">str</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">ProposalRead</span> <span class="o">|</span> <span class="kc">None</span><span class="p">:</span> <span class="o">...</span>
</code></pre></div>
<h3 id="33-repository_votespy">3.3. <code>repository_votes.py</code><a class="headerlink" href="#33-repository_votespy" title="Permanent link">&para;</a></h3>
<div class="codehilite"><pre><span></span><code><span class="k">async</span> <span class="k">def</span><span class="w"> </span><span class="nf">list_votes_for_proposal</span><span class="p">(</span><span class="n">db</span><span class="p">,</span> <span class="n">proposal_id</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">list</span><span class="p">[</span><span class="n">VoteRead</span><span class="p">]:</span> <span class="o">...</span>
<span class="k">async</span> <span class="k">def</span><span class="w"> </span><span class="nf">create_or_update_vote</span><span class="p">(</span><span class="n">db</span><span class="p">,</span> <span class="n">proposal_id</span><span class="p">,</span> <span class="n">voter_user_id</span><span class="p">,</span> <span class="n">vote_value</span><span class="p">,</span> <span class="n">weight</span><span class="p">,</span> <span class="n">raw_power</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">VoteRead</span><span class="p">:</span> <span class="o">...</span>
</code></pre></div>
<hr />
<h2 id="4-governance-engine">4. Governance Engine<a class="headerlink" href="#4-governance-engine" title="Permanent link">&para;</a></h2>
<p>Файл: <code>services/dao-service/governance_engine.py</code></p>
<p>Реалізувати три моделі:</p>
<div class="codehilite"><pre><span></span><code><span class="k">async</span> <span class="k">def</span><span class="w"> </span><span class="nf">calculate_voting_power_simple</span><span class="p">(</span><span class="n">actor</span><span class="p">,</span> <span class="n">dao</span><span class="p">:</span> <span class="n">DaoRead</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">Decimal</span><span class="p">:</span>
<span class="c1"># 1 user = 1 голос</span>
<span class="k">return</span> <span class="n">Decimal</span><span class="p">(</span><span class="mi">1</span><span class="p">)</span>
<span class="k">async</span> <span class="k">def</span><span class="w"> </span><span class="nf">calculate_voting_power_quadratic</span><span class="p">(</span><span class="n">actor</span><span class="p">,</span> <span class="n">dao</span><span class="p">:</span> <span class="n">DaoRead</span><span class="p">,</span> <span class="n">base_power</span><span class="p">:</span> <span class="n">Decimal</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">Decimal</span><span class="p">:</span>
<span class="c1"># weight = sqrt(base_power)</span>
<span class="c1"># base_power може бути, наприклад, кількістю токенів з treasury або окремої таблиці</span>
<span class="kn">from</span><span class="w"> </span><span class="nn">decimal</span><span class="w"> </span><span class="kn">import</span> <span class="n">Decimal</span><span class="p">,</span> <span class="n">getcontext</span>
<span class="n">getcontext</span><span class="p">()</span><span class="o">.</span><span class="n">prec</span> <span class="o">=</span> <span class="mi">28</span>
<span class="k">return</span> <span class="n">base_power</span><span class="o">.</span><span class="n">sqrt</span><span class="p">()</span>
<span class="k">async</span> <span class="k">def</span><span class="w"> </span><span class="nf">calculate_voting_power_delegated</span><span class="p">(</span><span class="n">actor</span><span class="p">,</span> <span class="n">dao</span><span class="p">:</span> <span class="n">DaoRead</span><span class="p">,</span> <span class="n">delegation_graph</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">Decimal</span><span class="p">:</span>
<span class="c1"># з урахуванням делегацій</span>
<span class="o">...</span>
</code></pre></div>
<p>Також функцію <strong>обчислення результату</strong>:</p>
<div class="codehilite"><pre><span></span><code><span class="k">async</span> <span class="k">def</span><span class="w"> </span><span class="nf">evaluate_proposal_outcome</span><span class="p">(</span>
<span class="n">dao</span><span class="p">:</span> <span class="n">DaoRead</span><span class="p">,</span>
<span class="n">proposal</span><span class="p">:</span> <span class="n">ProposalRead</span><span class="p">,</span>
<span class="n">votes</span><span class="p">:</span> <span class="nb">list</span><span class="p">[</span><span class="n">VoteRead</span><span class="p">]</span>
<span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">dict</span><span class="p">:</span>
<span class="c1"># рахуємо total weight, yes/no/abstain, quorum, чи passed</span>
<span class="o">...</span>
</code></pre></div>
<hr />
<h2 id="5-nats-events">5. NATS Events<a class="headerlink" href="#5-nats-events" title="Permanent link">&para;</a></h2>
<p>Файл: <code>services/dao-service/nats_events.py</code></p>
<p>Функція:</p>
<div class="codehilite"><pre><span></span><code><span class="k">async</span> <span class="k">def</span><span class="w"> </span><span class="nf">publish_event</span><span class="p">(</span><span class="n">subject</span><span class="p">:</span> <span class="nb">str</span><span class="p">,</span> <span class="n">payload</span><span class="p">:</span> <span class="nb">dict</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="kc">None</span><span class="p">:</span> <span class="o">...</span>
</code></pre></div>
<p>Викликати з backend:</p>
<ul>
<li>при створенні DAO:
<code>dao.event.created</code></li>
<li>при оновленні DAO:
<code>dao.event.updated</code></li>
<li>при оновленні treasury:
<code>dao.event.treasury_updated</code></li>
<li>при створенні пропозиції:
<code>dao.event.proposal_created</code></li>
<li>при активації/закритті пропозиції:
<code>dao.event.proposal_status_changed</code></li>
<li>при голосуванні:
<code>dao.event.vote_cast</code></li>
</ul>
<hr />
<h2 id="6-routes-fastapi">6. Routes (FastAPI)<a class="headerlink" href="#6-routes-fastapi" title="Permanent link">&para;</a></h2>
<p>У <code>services/dao-service/main.py</code> створити FastAPI app з router'ами:</p>
<h3 id="61-authpdp">6.1. Auth/PDP<a class="headerlink" href="#61-authpdp" title="Permanent link">&para;</a></h3>
<p>Створити <code>auth_client.py</code>, <code>pdp_client.py</code> (як у інших сервісах):</p>
<div class="codehilite"><pre><span></span><code><span class="k">async</span> <span class="k">def</span><span class="w"> </span><span class="nf">get_actor_identity</span><span class="p">(</span><span class="n">request</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">ActorIdentity</span><span class="p">:</span> <span class="o">...</span>
<span class="k">async</span> <span class="k">def</span><span class="w"> </span><span class="nf">check_permission</span><span class="p">(</span><span class="n">actor</span><span class="p">,</span> <span class="n">action</span><span class="p">:</span> <span class="nb">str</span><span class="p">,</span> <span class="n">resource</span><span class="p">:</span> <span class="nb">dict</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="kc">None</span><span class="p">:</span>
<span class="c1"># кинути HTTPException(403) якщо deny</span>
</code></pre></div>
<h3 id="62-dao-routes">6.2. DAO Routes<a class="headerlink" href="#62-dao-routes" title="Permanent link">&para;</a></h3>
<p>Файл: <code>routes_dao.py</code>:</p>
<div class="codehilite"><pre><span></span><code><span class="n">router</span> <span class="o">=</span> <span class="n">APIRouter</span><span class="p">(</span><span class="n">prefix</span><span class="o">=</span><span class="s2">&quot;/dao&quot;</span><span class="p">,</span> <span class="n">tags</span><span class="o">=</span><span class="p">[</span><span class="s2">&quot;dao&quot;</span><span class="p">])</span>
</code></pre></div>
<p>Endpoints:</p>
<ol>
<li>
<p><code>GET /dao</code></p>
</li>
<li>
<p>повертає DAO, де actor є членом:</p>
</li>
<li>
<p><code>list_dao_for_user(db, actor.user_id)</code></p>
</li>
<li>
<p><code>POST /dao</code></p>
</li>
<li>
<p>PDP: <code>DAO_CREATE</code></p>
</li>
<li>body: <code>DaoCreate</code></li>
<li>потрібно вказати <code>microdao_id</code> (як поле або через контекст)</li>
<li>
<p>створюємо DAO, додаємо owner у <code>dao_members</code> з роллю <code>owner</code>.</p>
</li>
<li>
<p><code>GET /dao/{slug}</code></p>
</li>
<li>
<p>PDP: <code>DAO_READ</code></p>
</li>
<li>
<p>повертає <code>DaoRead</code> + агреговану інформацію (можна через <code>DaoOverview</code>).</p>
</li>
<li>
<p><code>PUT /dao/{slug}</code></p>
</li>
<li>
<p>PDP: <code>DAO_MANAGE</code></p>
</li>
<li>
<p>оновити <code>name/description/governance_model/...</code>.</p>
</li>
<li>
<p><code>DELETE /dao/{slug}</code></p>
</li>
<li>
<p>PDP: <code>DAO_MANAGE</code></p>
</li>
<li><code>is_active=false</code>.</li>
</ol>
<h3 id="63-members-routes">6.3. Members Routes<a class="headerlink" href="#63-members-routes" title="Permanent link">&para;</a></h3>
<p><code>GET /dao/{slug}/members</code>
<code>POST /dao/{slug}/members</code>
<code>DELETE /dao/{slug}/members/{memberId}</code></p>
<h3 id="64-treasury-routes">6.4. Treasury Routes<a class="headerlink" href="#64-treasury-routes" title="Permanent link">&para;</a></h3>
<p><code>GET /dao/{slug}/treasury</code>
<code>POST /dao/{slug}/treasury</code> (delta-операція: <code>token_symbol</code>, <code>delta</code>)</p>
<h3 id="65-proposals-votes">6.5. Proposals &amp; Votes<a class="headerlink" href="#65-proposals-votes" title="Permanent link">&para;</a></h3>
<p><code>GET /dao/{slug}/proposals</code>
<code>POST /dao/{slug}/proposals</code>
<code>GET /dao/{slug}/proposals/{proposalSlug}</code>
<code>POST /dao/{slug}/proposals/{proposalSlug}/activate</code>
<code>POST /dao/{slug}/proposals/{proposalSlug}/close</code></p>
<p><code>GET /dao/{slug}/proposals/{proposalSlug}/votes</code>
<code>POST /dao/{slug}/proposals/{proposalSlug}/votes</code> (create/update vote)</p>
<hr />
<h2 id="7-frontend-dao-dashboard">7. Frontend: DAO Dashboard<a class="headerlink" href="#7-frontend-dao-dashboard" title="Permanent link">&para;</a></h2>
<p>У фронтенді створити структуру:</p>
<p><code>src/api/dao.ts</code>
<code>src/features/dao/DaoListPage.tsx</code>
<code>src/features/dao/DaoDashboardPage.tsx</code>
<code>src/features/dao/components/...</code></p>
<h3 id="71-api-client">7.1. API Client<a class="headerlink" href="#71-api-client" title="Permanent link">&para;</a></h3>
<p>У <code>src/api/dao.ts</code>:</p>
<div class="codehilite"><pre><span></span><code><span class="k">export</span><span class="w"> </span><span class="k">async</span><span class="w"> </span><span class="kd">function</span><span class="w"> </span><span class="nx">getMyDaos</span><span class="p">()</span><span class="o">:</span><span class="w"> </span><span class="nb">Promise</span><span class="o">&lt;</span><span class="nx">DaoSummary</span><span class="p">[]</span><span class="o">&gt;</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="p">...</span><span class="w"> </span><span class="p">}</span>
<span class="k">export</span><span class="w"> </span><span class="k">async</span><span class="w"> </span><span class="kd">function</span><span class="w"> </span><span class="nx">getDao</span><span class="p">(</span><span class="nx">slug</span><span class="o">:</span><span class="w"> </span><span class="kt">string</span><span class="p">)</span><span class="o">:</span><span class="w"> </span><span class="nb">Promise</span><span class="o">&lt;</span><span class="nx">DaoOverview</span><span class="o">&gt;</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="p">...</span><span class="w"> </span><span class="p">}</span>
<span class="k">export</span><span class="w"> </span><span class="k">async</span><span class="w"> </span><span class="kd">function</span><span class="w"> </span><span class="nx">createDao</span><span class="p">(</span><span class="nx">payload</span><span class="o">:</span><span class="w"> </span><span class="kt">DaoCreatePayload</span><span class="p">)</span><span class="o">:</span><span class="w"> </span><span class="nb">Promise</span><span class="o">&lt;</span><span class="nx">DaoRead</span><span class="o">&gt;</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="p">...</span><span class="w"> </span><span class="p">}</span>
<span class="k">export</span><span class="w"> </span><span class="k">async</span><span class="w"> </span><span class="kd">function</span><span class="w"> </span><span class="nx">getDaoMembers</span><span class="p">(</span><span class="nx">slug</span><span class="o">:</span><span class="w"> </span><span class="kt">string</span><span class="p">)</span><span class="o">:</span><span class="w"> </span><span class="nb">Promise</span><span class="o">&lt;</span><span class="nx">DaoMember</span><span class="p">[]</span><span class="o">&gt;</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="p">...</span><span class="w"> </span><span class="p">}</span>
<span class="k">export</span><span class="w"> </span><span class="k">async</span><span class="w"> </span><span class="kd">function</span><span class="w"> </span><span class="nx">getDaoTreasury</span><span class="p">(</span><span class="nx">slug</span><span class="o">:</span><span class="w"> </span><span class="kt">string</span><span class="p">)</span><span class="o">:</span><span class="w"> </span><span class="nb">Promise</span><span class="o">&lt;</span><span class="nx">DaoTreasuryItem</span><span class="p">[]</span><span class="o">&gt;</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="p">...</span><span class="w"> </span><span class="p">}</span>
<span class="k">export</span><span class="w"> </span><span class="k">async</span><span class="w"> </span><span class="kd">function</span><span class="w"> </span><span class="nx">getDaoProposals</span><span class="p">(</span><span class="nx">slug</span><span class="o">:</span><span class="w"> </span><span class="kt">string</span><span class="p">)</span><span class="o">:</span><span class="w"> </span><span class="nb">Promise</span><span class="o">&lt;</span><span class="nx">DaoProposal</span><span class="p">[]</span><span class="o">&gt;</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="p">...</span><span class="w"> </span><span class="p">}</span>
<span class="k">export</span><span class="w"> </span><span class="k">async</span><span class="w"> </span><span class="kd">function</span><span class="w"> </span><span class="nx">createDaoProposal</span><span class="p">(</span><span class="nx">slug</span><span class="o">:</span><span class="w"> </span><span class="kt">string</span><span class="p">,</span><span class="w"> </span><span class="nx">payload</span><span class="o">:</span><span class="w"> </span><span class="kt">ProposalCreatePayload</span><span class="p">)</span><span class="o">:</span><span class="w"> </span><span class="nb">Promise</span><span class="o">&lt;</span><span class="nx">DaoProposal</span><span class="o">&gt;</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="p">...</span><span class="w"> </span><span class="p">}</span>
<span class="k">export</span><span class="w"> </span><span class="k">async</span><span class="w"> </span><span class="kd">function</span><span class="w"> </span><span class="nx">getDaoProposal</span><span class="p">(</span><span class="nx">slug</span><span class="o">:</span><span class="w"> </span><span class="kt">string</span><span class="p">,</span><span class="w"> </span><span class="nx">proposalSlug</span><span class="o">:</span><span class="w"> </span><span class="kt">string</span><span class="p">)</span><span class="o">:</span><span class="w"> </span><span class="nb">Promise</span><span class="o">&lt;</span><span class="nx">DaoProposalDetail</span><span class="o">&gt;</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="p">...</span><span class="w"> </span><span class="p">}</span>
<span class="k">export</span><span class="w"> </span><span class="k">async</span><span class="w"> </span><span class="kd">function</span><span class="w"> </span><span class="nx">castVote</span><span class="p">(</span><span class="nx">slug</span><span class="o">:</span><span class="w"> </span><span class="kt">string</span><span class="p">,</span><span class="w"> </span><span class="nx">proposalSlug</span><span class="o">:</span><span class="w"> </span><span class="kt">string</span><span class="p">,</span><span class="w"> </span><span class="nx">payload</span><span class="o">:</span><span class="w"> </span><span class="kt">VotePayload</span><span class="p">)</span><span class="o">:</span><span class="w"> </span><span class="nb">Promise</span><span class="o">&lt;</span><span class="nx">VoteRead</span><span class="o">&gt;</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="p">...</span><span class="w"> </span><span class="p">}</span>
</code></pre></div>
<p>Типи — у цьому ж файлі або в <code>types/dao.ts</code>.</p>
<h3 id="72">7.2. Сторінки<a class="headerlink" href="#72" title="Permanent link">&para;</a></h3>
<h4 id="dao"><code>/dao</code><a class="headerlink" href="#dao" title="Permanent link">&para;</a></h4>
<p><code>DaoListPage.tsx</code>:</p>
<ul>
<li>
<p>список DAO картками:</p>
</li>
<li>
<p>назва</p>
</li>
<li>опис</li>
<li>governance модель</li>
<li>кількість учасників, активних пропозицій</li>
<li>кнопка "Створити DAO" (dialog → createDao)</li>
</ul>
<h4 id="daoslug"><code>/dao/:slug</code><a class="headerlink" href="#daoslug" title="Permanent link">&para;</a></h4>
<p><code>DaoDashboardPage.tsx</code>:</p>
<p>Tabs:</p>
<ul>
<li>
<p>Overview:</p>
</li>
<li>
<p>загальна інформація</p>
</li>
<li>короткі stats (members, proposals, treasury)</li>
<li>
<p>Proposals:</p>
</li>
<li>
<p>список пропозицій</p>
</li>
<li>кнопка "Створити пропозицію"</li>
<li>статуси, дедлайни</li>
<li>
<p>Proposal detail:</p>
</li>
<li>
<p>окрема панель (може бути drawer/side-panel або окрема сторінка)</p>
</li>
<li>кнопка Vote (Yes/No/Abstain)</li>
<li>показ quorum, результатів</li>
<li>
<p>Treasury:</p>
</li>
<li>
<p>список токенів, баланси</p>
</li>
<li>простий графік</li>
<li>
<p>Members:</p>
</li>
<li>
<p>таблиця учасників</p>
</li>
<li>роль</li>
<li>
<p>Activity:</p>
</li>
<li>
<p>стрічка подій DAO (з <code>dao_audit_log</code> або NATS)</p>
</li>
</ul>
<hr />
<h2 id="8-websocket-live-updates-mvp">8. WebSocket / Live Updates (MVP)<a class="headerlink" href="#8-websocket-live-updates-mvp" title="Permanent link">&para;</a></h2>
<p>Опційно для цієї фази (якщо є час):</p>
<ul>
<li>у <code>dao-service</code>:</li>
</ul>
<div class="codehilite"><pre><span></span><code><span class="nd">@router</span><span class="o">.</span><span class="n">websocket</span><span class="p">(</span><span class="s2">&quot;/ws/dao-events&quot;</span><span class="p">)</span>
<span class="k">async</span> <span class="k">def</span><span class="w"> </span><span class="nf">dao_events_ws</span><span class="p">(</span><span class="n">ws</span><span class="p">:</span> <span class="n">WebSocket</span><span class="p">):</span>
<span class="c1"># підписка на NATS dao.event.* і пуш у клієнт</span>
<span class="o">...</span>
</code></pre></div>
<ul>
<li>
<p>на фронті <code>useDaoEvents(slug)</code>:</p>
</li>
<li>
<p>фільтрувати тільки події конкретного DAO;</p>
</li>
<li>оновлювати списки proposals/treasury.</li>
</ul>
<p>Якщо часу мало — можна залишити це на наступну фазу, але місце під WS варто зарезервувати.</p>
<hr />
<h2 id="9-microdao-console">9. Інтеграція з microDAO Console<a class="headerlink" href="#9-microdao-console" title="Permanent link">&para;</a></h2>
<p>У <code>MicrodaoConsolePage.tsx</code> додати:</p>
<ul>
<li>
<p>секцію <code>Governance</code>:</p>
</li>
<li>
<p>якщо для даного microDAO існує DAO → кнопка:</p>
<ul>
<li>"Відкрити DAO Dashboard"</li>
<li><code>navigate('/dao/' + daoSlug)</code></li>
<li>
<p>якщо не існує DAO → кнопка:</p>
</li>
<li>
<p>"Створити DAO Governance"</p>
</li>
<li>виклик <code>createDao({ microdaoId, slug, name, ... })</code>
(можна автогенерувати slug з microDAO slug).</li>
</ul>
</li>
</ul>
<hr />
<h2 id="10-docker-scripts">10. Docker / Scripts<a class="headerlink" href="#10-docker-scripts" title="Permanent link">&para;</a></h2>
<p>Оновити:</p>
<ul>
<li>
<p><code>docker-compose.phase8.yml</code> (або доповнити існуючий compose):</p>
</li>
<li>
<p><code>dao-service</code> (порт, наприклад, 7016)</p>
</li>
<li>
<p><code>scripts/start-phase8.sh</code>:</p>
</li>
<li>
<p>застосування <code>009</code> міграції;</p>
</li>
<li>запуск <code>dao-service</code> разом з іншими сервісами.</li>
</ul>
<hr />
<h2 id="11-acceptance-criteria">11. Acceptance Criteria<a class="headerlink" href="#11-acceptance-criteria" title="Permanent link">&para;</a></h2>
<p>Вважати Phase 8 виконаною, якщо:</p>
<ul>
<li>[ ] <code>009_create_dao_core.sql</code> застосовується без помилок;</li>
<li>[ ] Запущено <code>dao-service</code> з <code>/health</code> endpoint;</li>
<li>[ ] <code>GET /dao</code> повертає DAO, де actor є членом;</li>
<li>[ ] <code>POST /dao</code> створює DAO, додає owner у members і публікує <code>dao.event.created</code>;</li>
<li>[ ] <code>GET /dao/{slug}</code> повертає overview DAO (включно з members_count, active_proposals_count, treasury_items);</li>
<li>[ ] <code>POST /dao/{slug}/proposals</code> створює пропозицію, <code>dao.event.proposal_created</code> публікується;</li>
<li>[ ] <code>POST /dao/{slug}/proposals/{proposalSlug}/votes</code> створює/оновлює голос;</li>
<li>[ ] у фронтенді <code>/dao</code> показує реальні DAO з БД;</li>
<li>[ ] у фронтенді <code>/dao/:slug</code> показує Overview/Proposals/Treasury/Members з реальних endpoint'ів (без mock);</li>
<li>[ ] PDP блокує доступ до DAO, де actor не є членом (403).</li>
</ul>
<p>END OF TASK</p>
</article>
</div>
<script>var target=document.getElementById(location.hash.slice(1));target&&target.name&&(target.checked=target.name.startsWith("__tabbed_"))</script>
</div>
</main>
<footer class="md-footer">
<div class="md-footer-meta md-typeset">
<div class="md-footer-meta__inner md-grid">
<div class="md-copyright">
Made with
<a href="https://squidfunk.github.io/mkdocs-material/" target="_blank" rel="noopener">
Material for MkDocs
</a>
</div>
</div>
</div>
</footer>
</div>
<div class="md-dialog" data-md-component="dialog">
<div class="md-dialog__inner md-typeset"></div>
</div>
<script id="__config" type="application/json">{"base": "../..", "features": ["navigation.sections", "navigation.instant", "content.code.copy"], "search": "../../assets/javascripts/workers/search.b8dbb3d2.min.js", "translations": {"clipboard.copied": "Copied to clipboard", "clipboard.copy": "Copy to clipboard", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.placeholder": "Type to start searching", "search.result.term.missing": "Missing", "select.version": "Select version"}}</script>
<script src="../../assets/javascripts/bundle.3220b9d7.min.js"></script>
</body>
</html>