Files
microdao-daarion/migrations/009_create_dao_core.sql
Apple 3de3c8cb36 feat: Add presence heartbeat for Matrix online status
- 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
2025-11-27 00:19:40 -08:00

290 lines
12 KiB
PL/PgSQL
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.
-- ============================================================================
-- Migration 009: DAO Core Tables
-- Phase 8: DAO Dashboard (Governance + Treasury + Voting)
-- ============================================================================
-- ============================================================================
-- Table: dao
-- Purpose: DAO entities with governance configuration
-- ============================================================================
CREATE TABLE IF NOT EXISTS dao (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
slug TEXT NOT NULL UNIQUE,
name TEXT NOT NULL,
description TEXT,
microdao_id UUID NOT NULL REFERENCES microdaos(id) ON DELETE CASCADE,
owner_user_id UUID NOT NULL REFERENCES users(id) ON DELETE RESTRICT,
governance_model TEXT NOT NULL DEFAULT 'simple', -- 'simple' | 'quadratic' | 'delegated'
voting_period_seconds INTEGER NOT NULL DEFAULT 604800, -- 7 days
quorum_percent INTEGER NOT NULL DEFAULT 20, -- 20%
is_active BOOLEAN NOT NULL DEFAULT TRUE,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
CREATE INDEX idx_dao_microdao_id ON dao(microdao_id);
CREATE INDEX idx_dao_owner_user_id ON dao(owner_user_id);
CREATE INDEX idx_dao_is_active ON dao(is_active);
COMMENT ON TABLE dao IS 'DAO entities with governance configuration';
COMMENT ON COLUMN dao.governance_model IS 'simple, quadratic, or delegated voting';
COMMENT ON COLUMN dao.voting_period_seconds IS 'Default voting period for proposals';
COMMENT ON COLUMN dao.quorum_percent IS 'Minimum participation percentage for valid vote';
-- ============================================================================
-- Table: dao_members
-- Purpose: DAO membership with roles
-- ============================================================================
CREATE TABLE IF NOT EXISTS dao_members (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
dao_id UUID NOT NULL REFERENCES dao(id) ON DELETE CASCADE,
user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE,
role TEXT NOT NULL, -- 'owner' | 'admin' | 'member' | 'guest'
joined_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
UNIQUE(dao_id, user_id)
);
CREATE INDEX idx_dao_members_user_id ON dao_members(user_id);
CREATE INDEX idx_dao_members_dao_id ON dao_members(dao_id);
CREATE INDEX idx_dao_members_dao_id_role ON dao_members(dao_id, role);
COMMENT ON TABLE dao_members IS 'DAO membership with roles';
COMMENT ON COLUMN dao_members.role IS 'owner, admin, member, guest';
-- ============================================================================
-- Table: dao_treasury
-- Purpose: Token balances for DAO treasury
-- ============================================================================
CREATE TABLE IF NOT EXISTS dao_treasury (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
dao_id UUID NOT NULL REFERENCES dao(id) ON DELETE CASCADE,
token_symbol TEXT NOT NULL,
contract_address TEXT,
balance NUMERIC(30, 8) NOT NULL DEFAULT 0,
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
UNIQUE(dao_id, token_symbol)
);
CREATE INDEX idx_dao_treasury_dao_id ON dao_treasury(dao_id);
COMMENT ON TABLE dao_treasury IS 'Token balances for DAO treasury';
COMMENT ON COLUMN dao_treasury.balance IS 'Token balance with 8 decimal precision';
COMMENT ON COLUMN dao_treasury.contract_address IS 'Optional smart contract address';
-- ============================================================================
-- Table: dao_proposals
-- Purpose: Governance proposals for voting
-- ============================================================================
CREATE TABLE IF NOT EXISTS dao_proposals (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
dao_id UUID NOT NULL REFERENCES dao(id) ON DELETE CASCADE,
slug TEXT NOT NULL,
title TEXT NOT NULL,
description TEXT,
created_by_user_id UUID NOT NULL REFERENCES users(id) ON DELETE RESTRICT,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
start_at TIMESTAMPTZ,
end_at TIMESTAMPTZ,
status TEXT NOT NULL DEFAULT 'draft', -- 'draft' | 'active' | 'passed' | 'rejected' | 'executed'
governance_model_override TEXT,
quorum_percent_override INTEGER,
UNIQUE(dao_id, slug)
);
CREATE INDEX idx_dao_proposals_dao_id ON dao_proposals(dao_id);
CREATE INDEX idx_dao_proposals_status ON dao_proposals(status);
CREATE INDEX idx_dao_proposals_created_by ON dao_proposals(created_by_user_id);
COMMENT ON TABLE dao_proposals IS 'Governance proposals for voting';
COMMENT ON COLUMN dao_proposals.status IS 'draft, active, passed, rejected, executed';
COMMENT ON COLUMN dao_proposals.governance_model_override IS 'Override DAO default governance model';
-- ============================================================================
-- Table: dao_votes
-- Purpose: Individual votes on proposals
-- ============================================================================
CREATE TABLE IF NOT EXISTS dao_votes (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
proposal_id UUID NOT NULL REFERENCES dao_proposals(id) ON DELETE CASCADE,
voter_user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE,
vote_value TEXT NOT NULL, -- 'yes' | 'no' | 'abstain'
weight NUMERIC(30, 8) NOT NULL, -- actual weight after applying governance model
raw_power NUMERIC(30, 8), -- raw voting power before governance model
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
UNIQUE(proposal_id, voter_user_id)
);
CREATE INDEX idx_dao_votes_proposal_id ON dao_votes(proposal_id);
CREATE INDEX idx_dao_votes_voter_user_id ON dao_votes(voter_user_id);
COMMENT ON TABLE dao_votes IS 'Individual votes on proposals';
COMMENT ON COLUMN dao_votes.vote_value IS 'yes, no, abstain';
COMMENT ON COLUMN dao_votes.weight IS 'Calculated weight after governance model';
COMMENT ON COLUMN dao_votes.raw_power IS 'Raw voting power before calculation';
-- ============================================================================
-- Table: dao_roles
-- Purpose: Custom roles for DAO
-- ============================================================================
CREATE TABLE IF NOT EXISTS dao_roles (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
dao_id UUID NOT NULL REFERENCES dao(id) ON DELETE CASCADE,
code TEXT NOT NULL,
name TEXT NOT NULL,
description TEXT,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
UNIQUE(dao_id, code)
);
CREATE INDEX idx_dao_roles_dao_id ON dao_roles(dao_id);
COMMENT ON TABLE dao_roles IS 'Custom roles for DAO';
COMMENT ON COLUMN dao_roles.code IS 'Unique role code (e.g., treasury_manager)';
-- ============================================================================
-- Table: dao_role_assignments
-- Purpose: Custom role assignments to users
-- ============================================================================
CREATE TABLE IF NOT EXISTS dao_role_assignments (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
dao_id UUID NOT NULL REFERENCES dao(id) ON DELETE CASCADE,
user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE,
role_code TEXT NOT NULL,
assigned_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
UNIQUE(dao_id, user_id, role_code)
);
CREATE INDEX idx_dao_role_assignments_user_id ON dao_role_assignments(user_id);
CREATE INDEX idx_dao_role_assignments_dao_id ON dao_role_assignments(dao_id);
COMMENT ON TABLE dao_role_assignments IS 'Custom role assignments to users';
-- ============================================================================
-- Table: dao_audit_log
-- Purpose: Audit log for all DAO actions
-- ============================================================================
CREATE TABLE IF NOT EXISTS dao_audit_log (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
dao_id UUID NOT NULL REFERENCES dao(id) ON DELETE CASCADE,
actor_user_id UUID REFERENCES users(id) ON DELETE SET NULL,
event_type TEXT NOT NULL,
event_payload JSONB,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
CREATE INDEX idx_dao_audit_log_dao_id ON dao_audit_log(dao_id);
CREATE INDEX idx_dao_audit_log_created_at ON dao_audit_log(created_at DESC);
CREATE INDEX idx_dao_audit_log_event_type ON dao_audit_log(event_type);
COMMENT ON TABLE dao_audit_log IS 'Audit log for all DAO actions';
COMMENT ON COLUMN dao_audit_log.event_type IS 'Type of event (created, updated, voted, etc.)';
-- ============================================================================
-- Update Trigger: dao.updated_at
-- ============================================================================
CREATE OR REPLACE FUNCTION update_dao_updated_at()
RETURNS TRIGGER AS $$
BEGIN
NEW.updated_at = NOW();
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
CREATE TRIGGER trigger_update_dao_updated_at
BEFORE UPDATE ON dao
FOR EACH ROW
EXECUTE FUNCTION update_dao_updated_at();
-- ============================================================================
-- Seed Data: Sample DAO for DAARION microDAO
-- ============================================================================
-- Create DAO for DAARION microDAO
INSERT INTO dao (slug, name, description, microdao_id, owner_user_id, governance_model, quorum_percent)
SELECT
'daarion-governance',
'DAARION Governance',
'Децентралізоване управління екосистемою DAARION',
m.id,
m.owner_user_id,
'simple',
20
FROM microdaos m
WHERE m.external_id = 'microdao:daarion'
LIMIT 1
ON CONFLICT (slug) DO NOTHING;
-- Add owner as DAO member
INSERT INTO dao_members (dao_id, user_id, role)
SELECT
d.id,
d.owner_user_id,
'owner'
FROM dao d
WHERE d.slug = 'daarion-governance'
ON CONFLICT (dao_id, user_id) DO NOTHING;
-- Initialize DAO treasury with DAARION token
INSERT INTO dao_treasury (dao_id, token_symbol, balance)
SELECT
d.id,
'DAARION',
1000000.0
FROM dao d
WHERE d.slug = 'daarion-governance'
ON CONFLICT (dao_id, token_symbol) DO NOTHING;
-- Create sample proposal
INSERT INTO dao_proposals (dao_id, slug, title, description, created_by_user_id, status, start_at, end_at)
SELECT
d.id,
'proposal-1-funding',
'Фінансування розвитку Agent Hub',
'Пропозиція виділити 10,000 DAARION токенів на розвиток Agent Hub у Q1 2025',
d.owner_user_id,
'active',
NOW(),
NOW() + INTERVAL '7 days'
FROM dao d
WHERE d.slug = 'daarion-governance'
ON CONFLICT (dao_id, slug) DO NOTHING;
-- ============================================================================
-- Grants
-- ============================================================================
GRANT SELECT, INSERT, UPDATE, DELETE ON dao TO postgres;
GRANT SELECT, INSERT, UPDATE, DELETE ON dao_members TO postgres;
GRANT SELECT, INSERT, UPDATE, DELETE ON dao_treasury TO postgres;
GRANT SELECT, INSERT, UPDATE, DELETE ON dao_proposals TO postgres;
GRANT SELECT, INSERT, UPDATE, DELETE ON dao_votes TO postgres;
GRANT SELECT, INSERT, UPDATE, DELETE ON dao_roles TO postgres;
GRANT SELECT, INSERT, UPDATE, DELETE ON dao_role_assignments TO postgres;
GRANT SELECT, INSERT, UPDATE, DELETE ON dao_audit_log TO postgres;
-- ============================================================================
-- Migration Complete
-- ============================================================================
DO $$
BEGIN
IF EXISTS (
SELECT 1 FROM information_schema.tables
WHERE table_name IN ('dao', 'dao_members', 'dao_treasury', 'dao_proposals', 'dao_votes')
) THEN
RAISE NOTICE 'Migration 009: DAO Core Tables created successfully';
ELSE
RAISE EXCEPTION 'Migration 009: Failed to create tables';
END IF;
END $$;