- matrix-gateway: POST /internal/matrix/presence/online endpoint - usePresenceHeartbeat hook with activity tracking - Auto away after 5 min inactivity - Offline on page close/visibility change - Integrated in MatrixChatRoom component
12 KiB
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: верифікація та дрібний тюнінг
- Відкрити
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
);
- Додати індекси, якщо їх ще немає:
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);
- Переконатися, що міграція застосована до 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: strrole: 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
- subject:
- при
update_microdao:microdao.event.updated
- при додаванні/видаленні члена:
microdao.event.member_addedmicrodao.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
- додати
/healthendpoint (якщо ще не зроблено).
8. Frontend: використати реальний бекенд
Оновити src/api/microdao.ts:
getMyMicrodaos() → GET /microdaogetMicrodao(slug) → GET /microdao/{slug}createMicrodao(payload) → POST /microdaogetMicrodaoMembers(slug) → GET /microdao/{slug}/membersgetMicrodaoTreasury(slug) → GET /microdao/{slug}/treasurygetMicrodaoSettings(slug) → GET /microdao/{slug}/settings
Потім оновити:
MicrodaoListPage.tsx:- щоб брав дані з
getMyMicrodaos();
- щоб брав дані з
MicrodaoConsolePage.tsx:- Overview →
getMicrodao(slug); - Members tab →
getMicrodaoMembers(slug); - Treasury tab →
getMicrodaoTreasury(slug).
- Overview →
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