Skip to content

# TASK_PHASE7_BACKEND_COMPLETION.md

## PHASE 7 — microDAO Console Backend Completion

### Goal

Доробити бекенд для microdao-service до production-ready стану:

- повний CRUD для microDAO;
- учасники (members) з ролями;
- проста казна (treasury) з балансами;
- налаштування (settings);
- PDP + Auth перевірки;
- базові NATS-події;
- інтеграція з існуючим фронтендом microDAO Console (MVP вже є).

## 0. Вихідні умови (вважати, що вже є)

З попереднього Phase 7 (MVP) вже створено:

- `migrations/008_create_microdao_core.sql` (схема БД);
- `services/microdao-service/main.py` (FastAPI-скелет, health endpoint);
- `services/microdao-service/models.py` (базові Pydantic-схеми);
- `services/microdao-service/requirements.txt`, `Dockerfile`;
- фронтенд:
- `src/api/microdao.ts` (чернетка);
- `src/features/microdao/MicrodaoListPage.tsx`;
- `src/features/microdao/MicrodaoConsolePage.tsx` (MVP з tabs);
- інфраструктура:
- `docker-compose.phase7.yml`;
- `scripts/start-phase7.sh`, `scripts/stop-phase7.sh`.

Цей таск ДОПОВНЮЄ вже створене, НЕ переписує з нуля.

## 1. Database: верифікація та дрібний тюнінг

1. Відкрити `migrations/008_create_microdao_core.sql` і переконатися, що там є таблиці:

    microdaos (
      id uuid primary key,
      external_id text unique not null,
      slug text unique not null,
      name text not null,
      description text,
      owner_user_id uuid not null references users(id),
      is_active boolean not null default true,
      created_at timestamptz not null default now(),
      updated_at timestamptz not null default now()
    );

    microdao_members (
      id uuid primary key,
      microdao_id uuid not null references microdaos(id),
      user_id uuid not null references users(id),
      role text not null,        -- 'owner' | 'admin' | 'member' | 'guest'
      joined_at timestamptz not null default now()
    );

    microdao_treasury (
      id uuid primary key,
      microdao_id uuid not null references microdaos(id),
      token_symbol text not null,
      balance numeric(30, 8) not null default 0,
      updated_at timestamptz not null default now()
    );

    microdao_settings (
      id uuid primary key,
      microdao_id uuid not null references microdaos(id),
      key text not null,
      value jsonb
    );

1. Додати індекси, якщо їх ще немає:

    create index if not exists idx_microdao_members_user_id
      on microdao_members(user_id);

    create index if not exists idx_microdao_members_microdao_id_role
      on microdao_members(microdao_id, role);

    create index if not exists idx_microdao_treasury_microdao_id
      on microdao_treasury(microdao_id);

1. Переконатися, що міграція застосована до dev-БД.

## 2. Repository layer для microDAO

Створити/оновити `services/microdao-service/repository_microdao.py`:

### 2.1. Вважати, що вже є спільний модуль для БД

Подивитися, як це зроблено в `agents-service` / `messaging-service` (наприклад, `database.py` або `db.py` з `async_session` або `Pool`):

- використовувати той самий підхід (SQLAlchemy / asyncpg), НЕ вводити новий.

### 2.2. Оголосити інтерфейс

У `repository_microdao.py` реалізувати функції (асинхронні, якщо так прийнято):

    # Псевдо-інтерфейс, реалізувати згідно з існуючим стилем проєкту

    async def create_microdao(db, *, owner_user_id: uuid.UUID, slug: str, name: str, description: str | None) -> MicrodaoRead: ...
    async def update_microdao(db, *, microdao_id: uuid.UUID, data: MicrodaoUpdate) -> MicrodaoRead | None: ...
    async def delete_microdao(db, *, microdao_id: uuid.UUID) -> None: ...
    async def get_microdao_by_slug(db, slug: str) -> MicrodaoRead | None: ...
    async def get_microdao_by_id(db, microdao_id: uuid.UUID) -> MicrodaoRead | None: ...
    async def list_microdaos_for_user(db, user_id: uuid.UUID) -> list[MicrodaoRead]: ...

### 2.3. Members

    async def list_members(db, microdao_id: uuid.UUID) -> list[MicrodaoMember]: ...
    async def add_member(db, microdao_id: uuid.UUID, user_id: uuid.UUID, role: str) -> MicrodaoMember: ...
    async def remove_member(db, member_id: uuid.UUID) -> None: ...

Правила:

- при створенні microDAO — власник автоматично додається в `microdao_members` з `role='owner'`;
- при видаленні microDAO (`delete_microdao`) — або `is_active=false`, або м'яке видалення (краще `is_active=false`).

### 2.4. Treasury

    async def get_treasury_items(db, microdao_id: uuid.UUID) -> list[TreasuryItem]: ...
    async def apply_treasury_delta(db, microdao_id: uuid.UUID, token_symbol: str, delta: Decimal) -> TreasuryItem: ...

- `delta` може бути додатним/від'ємним;
- гарантувати, що `balance` не йде в мінус без крайньої потреби (можна кидати помилку при `balance+delta < 0`).

### 2.5. Settings

    async def get_settings(db, microdao_id: uuid.UUID) -> dict[str, Any]: ...
    async def upsert_setting(db, microdao_id: uuid.UUID, key: str, value: Any) -> None: ...

- повернути `dict[key] = value` для фронтенду.

## 3. Pydantic models — models.py

Оновити `services/microdao-service/models.py`:

    from datetime import datetime
    from decimal import Decimal
    from pydantic import BaseModel

    class MicrodaoBase(BaseModel):
        slug: str
        name: str
        description: str | None = None

    class MicrodaoCreate(MicrodaoBase):
        pass

    class MicrodaoUpdate(BaseModel):
        name: str | None = None
        description: str | None = None
        is_active: bool | None = None

    class MicrodaoRead(MicrodaoBase):
        id: str
        external_id: str
        owner_user_id: str
        is_active: bool
        created_at: datetime
        updated_at: datetime

    class MicrodaoMember(BaseModel):
        id: str
        user_id: str
        role: str
        joined_at: datetime

    class TreasuryItem(BaseModel):
        token_symbol: str
        balance: Decimal

    class MicrodaoSettings(BaseModel):
        values: dict[str, object]

За потреби вирівняти з уже існуючими типами в проєкті.

## 4. Routes: REST API для microDAO

Створити/оновити `services/microdao-service/routes_microdao.py`:

### 4.1. Auth + PDP клієнти

Створити `auth_client.py`, `pdp_client.py` (або використати спільні з інших сервісів, якщо вони вже є).

Мінімум:

    async def get_actor_identity(request) -> ActorIdentity: ...
    async def check_permission(actor, action: str, resource: dict) -> None:
        # кинути HTTPException(403) якщо deny

### 4.2. Endpoints

    from fastapi import APIRouter, Depends, HTTPException
    from .models import MicrodaoCreate, MicrodaoUpdate, MicrodaoRead, MicrodaoMember, TreasuryItem
    from . import repository_microdao as repo

    router = APIRouter(prefix="/microdao", tags=["microdao"])

#### GET `/microdao`

Повертає всі microDAO, де actor є member:

- `actor = get_actor_identity()`
- `repo.list_microdaos_for_user(db, actor.user_id)`

#### POST `/microdao`

Створює новий microDAO:

- PDP: `action="MICRODAO_CREATE"`
- `owner_user_id = actor.user_id`
- виклик `repo.create_microdao(...)`
- автоматично створити запис в `microdao_members` з `role='owner'`.

#### GET `/microdao/{slug}`

- знайти microDAO по `slug`;
- PDP: `action="MICRODAO_READ"`, `resource={"microdao_id": id}`;
- повернути `MicrodaoRead`.

#### PUT `/microdao/{slug}`

- PDP: `action="MICRODAO_MANAGE"`;
- дозволити тільки owner/admin;
- оновити `name/description/is_active`.

#### DELETE `/microdao/{slug}`

- PDP: `action="MICRODAO_MANAGE"`;
- `is_active=false` (soft delete).

## 5. Routes: Members / Treasury / Settings

### 5.1. Members

У `routes_members.py` (або в тому ж `routes_microdao.py`, якщо ти тримаєш все разом):

    GET    /microdao/{slug}/members
    POST   /microdao/{slug}/members
    DELETE /microdao/{slug}/members/{member_id}

Правила:

- тільки owner/admin можуть:
- додавати членів;
- видаляти членів;
- змінювати роль (якщо імплементуєш PATCH).
- простий body для POST:
- `user_id: str`
- `role: str`

### 5.2. Treasury

    GET  /microdao/{slug}/treasury
    POST /microdao/{slug}/treasury   # delta operation

- PDP: `READ_TREASURY` для GET, `MANAGE_TREASURY` для POST.

### 5.3. Settings

    GET  /microdao/{slug}/settings
    POST /microdao/{slug}/settings   # { key, value }

## 6. NATS Events

У `main.py` microdao-service або в окремому модулі:

- Підключитися до NATS (використати той самий клієнт, що в інших сервісах).
- Функція helper:

    async def publish_event(subject: str, payload: dict) -> None: ...

Викликати:

- при `create_microdao`:
- subject: `microdao.event.created`
- при `update_microdao`:
- `microdao.event.updated`
- при додаванні/видаленні члена:
- `microdao.event.member_added`
- `microdao.event.member_removed`
- при оновленні treasury:
- `microdao.event.treasury_updated`

Payload мінімальний:

    {
      "microdao_id": "...",
      "slug": "daarion-city",
      "actor_id": "user:...",
      "ts": "2025-11-24T12:00:00Z",
      "data": { ... }
    }

## 7. Інтеграція в main.py

Оновити `services/microdao-service/main.py`:

- створити `app = FastAPI(...)`
- підключити `router`:

    from .routes_microdao import router as microdao_router
    app.include_router(microdao_router)
    # за потреби: members_router, treasury_router

- додати `/health` endpoint (якщо ще не зроблено).

## 8. Frontend: використати реальний бекенд

Оновити `src/api/microdao.ts`:

- `getMyMicrodaos() → GET /microdao`
- `getMicrodao(slug) → GET /microdao/{slug}`
- `createMicrodao(payload) → POST /microdao`
- `getMicrodaoMembers(slug) → GET /microdao/{slug}/members`
- `getMicrodaoTreasury(slug) → GET /microdao/{slug}/treasury`
- `getMicrodaoSettings(slug) → GET /microdao/{slug}/settings`

Потім оновити:

- `MicrodaoListPage.tsx`:
- щоб брав дані з `getMyMicrodaos()`;
- `MicrodaoConsolePage.tsx`:
- Overview → `getMicrodao(slug)`;
- Members tab → `getMicrodaoMembers(slug)`;
- Treasury tab → `getMicrodaoTreasury(slug)`.

## 9. Docker / Scripts

Оновити (якщо потрібно):

- `docker-compose.phase7.yml`:
- переконатися, що `microdao-service` піднятий і залежить від Postgres та auth/pdp;
- `scripts/start-phase7.sh`:
- додати команду застосування міграції `008` (як це робиться для інших);
- `scripts/stop-phase7.sh`:
- зупинити microdao-service і пов'язані сервіси.

## 10. Acceptance Criteria

Вважати завдання виконаним, якщо:

- [ ] `/microdao` повертає список microDAO, де actor є member;
- [ ] `/microdao` (POST) створює новий microDAO і додає owner в members;
- [ ] `/microdao/{slug}` повертає деталі microDAO;
- [ ] `/microdao/{slug}/members` повертає список учасників;
- [ ] `/microdao/{slug}/treasury` повертає список токенів;
- [ ] PDP блокує доступ до чужих microDAO (403);
- [ ] MicrodaoListPage показує реальні microDAO із БД;
- [ ] MicrodaoConsolePage показує реальні Overview/Members/Treasury без mock-даних;
- [ ] всі тести/линт проходять успішно.

END OF TASK