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
This commit is contained in:
53
backend/http/agents.routes.ts
Normal file
53
backend/http/agents.routes.ts
Normal file
@@ -0,0 +1,53 @@
|
||||
/**
|
||||
* Agents Routes (MVP Stub)
|
||||
* Based on: api-mvp.md
|
||||
*
|
||||
* Endpoints:
|
||||
* - POST /api/v1/dao/{dao_id}/agents/{agent_id}/invoke - Invoke agent
|
||||
*/
|
||||
|
||||
import { Router } from 'express';
|
||||
import { pdpService } from '../../services/pdp/pdp.service';
|
||||
|
||||
export const agentsRoutes = Router();
|
||||
|
||||
// POST /api/v1/dao/{dao_id}/agents/{agent_id}/invoke - Invoke agent
|
||||
agentsRoutes.post('/:daoId/agents/:agentId/invoke', async (req, res) => {
|
||||
try {
|
||||
const userId = (req as any).userId;
|
||||
const { daoId, agentId } = req.params;
|
||||
const { input, metadata } = req.body;
|
||||
|
||||
// Check PDP policy
|
||||
const pdpResult = await pdpService.check(
|
||||
'policy.agent.run',
|
||||
{ agentId },
|
||||
{ userId, daoId }
|
||||
);
|
||||
|
||||
if (pdpResult.decision !== 'allow') {
|
||||
res.status(403).json({
|
||||
error: 'ACCESS_DENIED',
|
||||
message: pdpResult.reason || 'PDP denied',
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// MVP: Return stub response
|
||||
// TODO: Implement actual agent invocation
|
||||
const runId = `run_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
|
||||
|
||||
res.json({
|
||||
run_id: runId,
|
||||
status: 'queued',
|
||||
output: null,
|
||||
});
|
||||
} catch (error: any) {
|
||||
res.status(500).json({
|
||||
error: 'INTERNAL_ERROR',
|
||||
message: error.message,
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
78
backend/http/dao.routes.ts
Normal file
78
backend/http/dao.routes.ts
Normal file
@@ -0,0 +1,78 @@
|
||||
/**
|
||||
* DAO Routes
|
||||
* Based on: api-mvp.md
|
||||
*
|
||||
* Endpoints:
|
||||
* - POST /api/v1/dao - Create DAO
|
||||
* - GET /api/v1/dao/{dao_id} - Get DAO by ID
|
||||
* - GET /api/v1/dao - List DAOs
|
||||
*/
|
||||
|
||||
import { Router } from 'express';
|
||||
import { daoFactoryService } from '../../services/dao-factory/dao-factory.service';
|
||||
import { registryService } from '../../services/registry/registry.service';
|
||||
import type { CreateDaoInput } from '../../domain/dao/types';
|
||||
|
||||
export const daoRoutes = Router();
|
||||
|
||||
// POST /api/v1/dao - Create DAO
|
||||
daoRoutes.post('/', async (req, res) => {
|
||||
try {
|
||||
const userId = (req as any).userId;
|
||||
const input: CreateDaoInput = req.body;
|
||||
|
||||
const result = await daoFactoryService.createDao(userId, input);
|
||||
|
||||
res.status(201).json(result);
|
||||
} catch (error: any) {
|
||||
res.status(400).json({
|
||||
error: error.message || 'BAD_REQUEST',
|
||||
message: error.message,
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// GET /api/v1/dao/{dao_id} - Get DAO by ID
|
||||
daoRoutes.get('/:daoId', async (req, res) => {
|
||||
try {
|
||||
const { daoId } = req.params;
|
||||
const dao = await registryService.getDaoById(daoId);
|
||||
|
||||
if (!dao) {
|
||||
res.status(404).json({
|
||||
error: 'NOT_FOUND',
|
||||
message: `DAO ${daoId} not found`,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
res.json(dao);
|
||||
} catch (error: any) {
|
||||
res.status(500).json({
|
||||
error: 'INTERNAL_ERROR',
|
||||
message: error.message,
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// GET /api/v1/dao - List DAOs
|
||||
daoRoutes.get('/', async (req, res) => {
|
||||
try {
|
||||
const { level, type } = req.query;
|
||||
const filter = {
|
||||
level: level as string | undefined,
|
||||
type: type as string | undefined,
|
||||
};
|
||||
|
||||
const daos = await registryService.listDaos(filter);
|
||||
|
||||
res.json({ items: daos });
|
||||
} catch (error: any) {
|
||||
res.status(500).json({
|
||||
error: 'INTERNAL_ERROR',
|
||||
message: error.message,
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
38
backend/http/pdp.routes.ts
Normal file
38
backend/http/pdp.routes.ts
Normal file
@@ -0,0 +1,38 @@
|
||||
/**
|
||||
* PDP Routes
|
||||
* Based on: api-mvp.md
|
||||
*
|
||||
* Endpoints:
|
||||
* - POST /api/v1/pdp/check - Check policy
|
||||
*/
|
||||
|
||||
import { Router } from 'express';
|
||||
import { pdpService } from '../../services/pdp/pdp.service';
|
||||
import type { PdpRequest } from '../../domain/pdp/policy.model';
|
||||
|
||||
export const pdpRoutes = Router();
|
||||
|
||||
// POST /api/v1/pdp/check - Check policy
|
||||
pdpRoutes.post('/check', async (req, res) => {
|
||||
try {
|
||||
const userId = (req as any).userId;
|
||||
const { policy, resource, context }: PdpRequest = req.body;
|
||||
|
||||
const result = await pdpService.check(policy, resource, {
|
||||
...context,
|
||||
userId: context.userId || userId,
|
||||
});
|
||||
|
||||
res.json({
|
||||
decision: result.decision,
|
||||
reason: result.reason || null,
|
||||
});
|
||||
} catch (error: any) {
|
||||
res.status(500).json({
|
||||
error: 'INTERNAL_ERROR',
|
||||
message: error.message,
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
48
backend/http/platforms.routes.ts
Normal file
48
backend/http/platforms.routes.ts
Normal file
@@ -0,0 +1,48 @@
|
||||
/**
|
||||
* Platforms Routes
|
||||
* Based on: api-mvp.md
|
||||
*
|
||||
* Endpoints:
|
||||
* - POST /api/v1/platforms - Create platform
|
||||
* - GET /api/v1/platforms - List platforms
|
||||
*/
|
||||
|
||||
import { Router } from 'express';
|
||||
import { daoFactoryService } from '../../services/dao-factory/dao-factory.service';
|
||||
import { registryService } from '../../services/registry/registry.service';
|
||||
import type { CreatePlatformInput } from '../../domain/dao/types';
|
||||
|
||||
export const platformsRoutes = Router();
|
||||
|
||||
// POST /api/v1/platforms - Create platform
|
||||
platformsRoutes.post('/', async (req, res) => {
|
||||
try {
|
||||
const userId = (req as any).userId;
|
||||
const input: CreatePlatformInput = req.body;
|
||||
|
||||
const result = await daoFactoryService.createPlatform(userId, input);
|
||||
|
||||
res.status(201).json(result);
|
||||
} catch (error: any) {
|
||||
res.status(400).json({
|
||||
error: error.message || 'BAD_REQUEST',
|
||||
message: error.message,
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// GET /api/v1/platforms - List platforms
|
||||
platformsRoutes.get('/', async (req, res) => {
|
||||
try {
|
||||
const platforms = await registryService.listPlatforms();
|
||||
|
||||
res.json({ items: platforms });
|
||||
} catch (error: any) {
|
||||
res.status(500).json({
|
||||
error: 'INTERNAL_ERROR',
|
||||
message: error.message,
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
156
backend/http/teams.routes.ts
Normal file
156
backend/http/teams.routes.ts
Normal file
@@ -0,0 +1,156 @@
|
||||
/**
|
||||
* Teams Routes (MicroDAO)
|
||||
* Based on: api-mvp.md, updated for MicroDAO creation with balance checks
|
||||
*
|
||||
* Endpoints:
|
||||
* - POST /api/v1/teams - Create MicroDAO (requires 1 DAARION on balance)
|
||||
* - GET /api/v1/teams - List teams/MicroDAO
|
||||
* - GET /api/v1/teams/:teamId - Get team/MicroDAO by ID
|
||||
* - POST /api/v1/teams/:teamId/members - Invite member (requires balance check)
|
||||
*/
|
||||
|
||||
import { Router } from 'express';
|
||||
import { walletService } from '../../services/wallet/wallet.service';
|
||||
import { daoFactoryService } from '../../services/dao-factory/dao-factory.service';
|
||||
import type { CreateTeamRequest } from '../../../types/api';
|
||||
|
||||
export const teamsRoutes = Router();
|
||||
|
||||
// POST /api/v1/teams - Create MicroDAO
|
||||
teamsRoutes.post('/', async (req, res) => {
|
||||
try {
|
||||
const userId = (req as any).userId;
|
||||
const input: CreateTeamRequest = req.body;
|
||||
|
||||
// 1. Check wallet balance - need 1 DAARION on balance
|
||||
const hasEnough = await walletService.hasEnoughForMicroDaoCreate(userId);
|
||||
if (!hasEnough) {
|
||||
res.status(403).json({
|
||||
error: 'INSUFFICIENT_BALANCE',
|
||||
message: 'Need 1 DAARION on balance to create MicroDAO',
|
||||
required: { daarion: 1.0 },
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// 2. Create MicroDAO through DAOFactory
|
||||
const daoResult = await daoFactoryService.createDao(userId, {
|
||||
name: input.name,
|
||||
description: input.description,
|
||||
type: input.mode === 'confidential' ? 'private' : 'public',
|
||||
level: 'A4', // User-created MicroDAO are A4 level
|
||||
});
|
||||
|
||||
// 3. TODO: Create team record in database
|
||||
// For now, return DAO result
|
||||
res.status(201).json({
|
||||
id: daoResult.daoId,
|
||||
name: input.name,
|
||||
description: input.description,
|
||||
mode: input.mode || 'public',
|
||||
type: input.type || 'community',
|
||||
created_at: new Date().toISOString(),
|
||||
});
|
||||
} catch (error: any) {
|
||||
res.status(400).json({
|
||||
error: error.message?.includes('INSUFFICIENT_BALANCE') ? 'INSUFFICIENT_BALANCE' : 'BAD_REQUEST',
|
||||
message: error.message,
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// GET /api/v1/teams - List teams/MicroDAO
|
||||
teamsRoutes.get('/', async (req, res) => {
|
||||
try {
|
||||
const userId = (req as any).userId;
|
||||
|
||||
// TODO: Get teams from database
|
||||
// For now, return empty list
|
||||
res.json({ teams: [] });
|
||||
} catch (error: any) {
|
||||
res.status(500).json({
|
||||
error: 'INTERNAL_ERROR',
|
||||
message: error.message,
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// GET /api/v1/teams/:teamId - Get team/MicroDAO by ID
|
||||
teamsRoutes.get('/:teamId', async (req, res) => {
|
||||
try {
|
||||
const { teamId } = req.params;
|
||||
|
||||
// TODO: Get team from database
|
||||
res.status(404).json({
|
||||
error: 'NOT_FOUND',
|
||||
message: `Team ${teamId} not found`,
|
||||
});
|
||||
} catch (error: any) {
|
||||
res.status(500).json({
|
||||
error: 'INTERNAL_ERROR',
|
||||
message: error.message,
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// POST /api/v1/teams/:teamId/members - Invite member
|
||||
teamsRoutes.post('/:teamId/members', async (req, res) => {
|
||||
try {
|
||||
const userId = (req as any).userId; // Current user (admin)
|
||||
const { teamId } = req.params;
|
||||
const { email, role = 'member' } = req.body;
|
||||
|
||||
// 1. Check if current user is admin (has 1 DAARION)
|
||||
const isAdmin = await walletService.hasEnoughForAdminRole(userId);
|
||||
if (!isAdmin) {
|
||||
res.status(403).json({
|
||||
error: 'ACCESS_DENIED',
|
||||
message: 'Need 1 DAARION on balance to invite members',
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// 2. Check invited user balance based on role
|
||||
// TODO: Get invited user ID from email
|
||||
const invitedUserId = `user_${email}`; // Placeholder
|
||||
|
||||
if (role === 'admin') {
|
||||
// Admin role requires 1 DAARION
|
||||
const hasEnough = await walletService.hasEnoughForAdminRole(invitedUserId);
|
||||
if (!hasEnough) {
|
||||
res.status(403).json({
|
||||
error: 'INSUFFICIENT_BALANCE',
|
||||
message: 'Invited user needs 1 DAARION on balance to be Admin',
|
||||
required: { daarion: 1.0 },
|
||||
});
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
// Member role requires 0.01 DAARION
|
||||
const hasEnough = await walletService.hasEnoughForMicroDaoUsage(invitedUserId);
|
||||
if (!hasEnough) {
|
||||
res.status(403).json({
|
||||
error: 'INSUFFICIENT_BALANCE',
|
||||
message: 'Invited user needs 0.01 DAARION on balance to use MicroDAO',
|
||||
required: { daarion: 0.01 },
|
||||
});
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// 3. TODO: Create team member record in database
|
||||
res.status(201).json({
|
||||
team_id: teamId,
|
||||
user_id: invitedUserId,
|
||||
email,
|
||||
role,
|
||||
status: 'invited',
|
||||
});
|
||||
} catch (error: any) {
|
||||
res.status(400).json({
|
||||
error: 'BAD_REQUEST',
|
||||
message: error.message,
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
52
backend/http/vendor.routes.ts
Normal file
52
backend/http/vendor.routes.ts
Normal file
@@ -0,0 +1,52 @@
|
||||
/**
|
||||
* Vendor Routes
|
||||
* Based on: api-mvp.md
|
||||
*
|
||||
* Endpoints:
|
||||
* - POST /api/v1/platforms/{platform_id}/vendors - Register vendor
|
||||
*/
|
||||
|
||||
import { Router } from 'express';
|
||||
import { pdpService } from '../../services/pdp/pdp.service';
|
||||
|
||||
export const vendorRoutes = Router();
|
||||
|
||||
// POST /api/v1/platforms/{platform_id}/vendors - Register vendor
|
||||
vendorRoutes.post('/:platformId/vendors', async (req, res) => {
|
||||
try {
|
||||
const userId = (req as any).userId;
|
||||
const { platformId } = req.params;
|
||||
const { display_name, contact } = req.body;
|
||||
|
||||
// Check PDP policy
|
||||
const pdpResult = await pdpService.check(
|
||||
'policy.vendor.register',
|
||||
{ platformId },
|
||||
{ userId, daoId: platformId, daoLevel: 'A2' }
|
||||
);
|
||||
|
||||
if (pdpResult.decision !== 'allow') {
|
||||
res.status(403).json({
|
||||
error: 'ACCESS_DENIED',
|
||||
message: pdpResult.reason || 'PDP denied',
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// TODO: Save vendor to database
|
||||
const vendorId = `vendor_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
|
||||
|
||||
res.status(201).json({
|
||||
vendor_id: vendorId,
|
||||
platform_id: platformId,
|
||||
status: 'approved',
|
||||
});
|
||||
} catch (error: any) {
|
||||
res.status(500).json({
|
||||
error: 'INTERNAL_ERROR',
|
||||
message: error.message,
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
72
backend/http/wallet.routes.ts
Normal file
72
backend/http/wallet.routes.ts
Normal file
@@ -0,0 +1,72 @@
|
||||
/**
|
||||
* Wallet Routes
|
||||
* Based on: api-mvp.md
|
||||
*
|
||||
* Endpoints:
|
||||
* - GET /api/v1/wallet/me - Get user balances
|
||||
* - POST /api/v1/wallet/check-access - Check access for action
|
||||
*/
|
||||
|
||||
import { Router } from 'express';
|
||||
import { walletService } from '../../services/wallet/wallet.service';
|
||||
import type { AccessCheck } from '../../domain/wallet/types';
|
||||
|
||||
export const walletRoutes = Router();
|
||||
|
||||
// GET /api/v1/wallet/me - Get user balances
|
||||
walletRoutes.get('/me', async (req, res) => {
|
||||
try {
|
||||
const userId = (req as any).userId;
|
||||
const balances = await walletService.getBalances(userId);
|
||||
|
||||
res.json({
|
||||
user_id: userId,
|
||||
balances,
|
||||
});
|
||||
} catch (error: any) {
|
||||
res.status(500).json({
|
||||
error: 'INTERNAL_ERROR',
|
||||
message: error.message,
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// POST /api/v1/wallet/check-access - Check access
|
||||
walletRoutes.post('/check-access', async (req, res) => {
|
||||
try {
|
||||
const userId = (req as any).userId;
|
||||
const { check }: AccessCheck = req.body;
|
||||
|
||||
let allowed = false;
|
||||
let reason: string | undefined;
|
||||
|
||||
switch (check) {
|
||||
case 'dao.create':
|
||||
allowed = await walletService.hasEnoughForDaoCreate(userId);
|
||||
if (!allowed) reason = 'INSUFFICIENT_BALANCE';
|
||||
break;
|
||||
case 'vendor.register':
|
||||
allowed = await walletService.hasEnoughForVendorRegister(userId);
|
||||
if (!allowed) reason = 'INSUFFICIENT_STAKED_DAARION';
|
||||
break;
|
||||
case 'platform.create':
|
||||
allowed = await walletService.hasEnoughForPlatformCreate(userId);
|
||||
if (!allowed) reason = 'INSUFFICIENT_STAKED_DAARION';
|
||||
break;
|
||||
default:
|
||||
reason = 'UNKNOWN_CHECK';
|
||||
}
|
||||
|
||||
res.json({
|
||||
allowed,
|
||||
reason: allowed ? null : reason,
|
||||
});
|
||||
} catch (error: any) {
|
||||
res.status(500).json({
|
||||
error: 'INTERNAL_ERROR',
|
||||
message: error.message,
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user