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:
Apple
2025-11-27 00:19:40 -08:00
parent 5bed515852
commit 3de3c8cb36
6371 changed files with 1317450 additions and 932 deletions

View File

@@ -0,0 +1,21 @@
/**
* Environment Configuration
* Load and validate environment variables
*/
export const config = {
port: parseInt(process.env.PORT || '3000', 10),
nodeEnv: process.env.NODE_ENV || 'development',
apiBaseUrl: process.env.API_BASE_URL || 'http://localhost:3000',
// Database
dbUrl: process.env.DATABASE_URL || 'postgresql://localhost:5432/microdao',
// Auth
jwtSecret: process.env.JWT_SECRET || 'change-me-in-production',
// Wallet/Chain (future)
chainRpcUrl: process.env.CHAIN_RPC_URL || '',
};

View File

@@ -0,0 +1,20 @@
/**
* Database Client
* MVP: Placeholder for future DB connection
* TODO: Replace with actual DB client (PostgreSQL, etc.)
*/
// MVP: No-op
export const dbClient = {
connect: async () => {
// TODO: Implement actual DB connection
console.log('[DB] Connected (stub)');
},
disconnect: async () => {
// TODO: Implement actual DB disconnection
console.log('[DB] Disconnected (stub)');
},
};

View File

@@ -0,0 +1,44 @@
/**
* DAO Repository
* Database access layer for DAO records
* MVP: In-memory storage, replace with actual DB later
*/
import type { DaoRecord } from '../../domain/dao/types';
// MVP: In-memory storage
const daoStore: Map<string, DaoRecord> = new Map();
export const daoRepository = {
async save(record: DaoRecord): Promise<void> {
daoStore.set(record.daoId, record);
},
async findById(daoId: string): Promise<DaoRecord | null> {
return daoStore.get(daoId) || null;
},
async findAll(filter?: { level?: string; type?: string }): Promise<DaoRecord[]> {
const all = Array.from(daoStore.values());
if (!filter) {
return all;
}
return all.filter(dao => {
if (filter.level && dao.level !== filter.level) {
return false;
}
if (filter.type && dao.type !== filter.type) {
return false;
}
return true;
});
},
async delete(daoId: string): Promise<void> {
daoStore.delete(daoId);
},
};

View File

@@ -0,0 +1,27 @@
/**
* Logger
* MVP: Simple console logger
* Future: Replace with proper logging library (Winston, Pino, etc.)
*/
type LogLevel = 'info' | 'warn' | 'error' | 'debug';
export const logger = {
info: (message: string, ...args: unknown[]) => {
console.log(`[INFO] ${message}`, ...args);
},
warn: (message: string, ...args: unknown[]) => {
console.warn(`[WARN] ${message}`, ...args);
},
error: (message: string, ...args: unknown[]) => {
console.error(`[ERROR] ${message}`, ...args);
},
debug: (message: string, ...args: unknown[]) => {
console.debug(`[DEBUG] ${message}`, ...args);
},
};