Skip to content

Latest commit

 

History

History
652 lines (537 loc) · 37.6 KB

File metadata and controls

652 lines (537 loc) · 37.6 KB

Backend Documentation

This document provides details about the backend architecture, API endpoints, and development guidelines for the Commonly application.

Technology Stack

  • Runtime: Node.js
  • Framework: Express.js
  • Authentication: JSON Web Tokens (JWT)
  • Database Access: Mongoose (MongoDB) and pg (PostgreSQL)
  • Real-time Communication: Socket.io
  • Validation: Express Validator
  • File Handling: Multer
  • Email Service: SMTP2GO
  • Skills Catalog: User-imported skills (catalog + imports)
  • Testing: Jest, Supertest, MongoDB Memory Server, pg-mem

Application Structure

backend/
├── config/              # Configuration files
│   ├── db.js           # Database connection setup
│   └── ...
├── controllers/         # Request handlers
│   ├── authController.js
│   ├── postController.js
│   ├── podController.js
│   └── ...
├── middleware/          # Express middleware
│   ├── auth.js         # Authentication middleware
│   ├── errorHandler.js # Error handling middleware
│   └── ...
├── models/              # Database models
│   ├── mongodb/        # MongoDB schemas
│   ├── postgres/       # PostgreSQL models
│   └── ...
├── routes/              # API route definitions
│   ├── auth.js
│   ├── posts.js
│   ├── pods.js
│   └── ...
├── utils/               # Utility functions
│   ├── validation.js
│   ├── fileUpload.js
│   └── ...
├── __tests__/           # Test files
│   ├── unit/           # Unit tests
│   ├── integration/    # Integration tests
│   └── ...
├── server.js            # Main application entry point
├── package.json         # Dependencies and scripts
└── ...

API Endpoints

Authentication

Method Endpoint Description Request Body Response
POST /api/auth/register Register a new user {username, email, password, invitationCode?} Registration status message
GET /api/auth/registration-policy Public registration mode - {inviteOnly, invitationRequired, hasInvitationCodes, registrationOpen}
POST /api/auth/login Login user {email, password} User object with token
GET /api/auth/user Get current user - User object
POST /api/auth/forgot Request password reset {email} Success message
POST /api/auth/reset/:token Reset password {password} Success message

Posts

Method Endpoint Description Request Body Response
GET /api/posts Get posts (filters: podId, category) Query: {podId?, category?} Array of posts
GET /api/posts/:id Get post by ID - Post object
POST /api/posts Create a new post {content, image?, tags?, podId?, category?, source?} Created post object
PUT /api/posts/:id Update a post {content, media} Updated post object
DELETE /api/posts/:id Delete a post - Success message
POST /api/posts/:id/like Like a post - Updated post object
POST /api/posts/:id/comments Comment on a post {text, podId?} Comment object
POST /api/posts/:id/follow Follow a thread post - {success, followed}
DELETE /api/posts/:id/follow Unfollow a thread post - {success, followed}
GET /api/posts/following/threads List followed thread posts - {threads: Post[]}
GET /api/posts/search Search posts Query: {query?, tags?, podId?, category?} Array of posts

External social feeds (X/Instagram) are stored as Post records with source.type = "external" and source.provider set to the platform. The scheduler polls these feeds every 10 minutes and appends normalized entries to integration buffers for summarization.

Pods (Chat)

Method Endpoint Description Request Body Response
GET /api/pods Get all pods - Array of pods
GET /api/pods/:id Get pod by ID - Pod object
GET /api/pods/:id/context/search Search pod memory (PodAssets) Query: {query, limit?, includeSkills?, types?} Search results
GET /api/pods/:id/context/assets/:assetId Read pod asset excerpt Query: {from?, lines?} Asset excerpt
GET /api/pods/:id/context Get pod context (LLM markdown skills + tags + assets) Query: {task?, summaryLimit?, assetLimit?, tagLimit?, skillLimit?, skillMode?, skillRefreshHours?} Pod context object
POST /api/v1/pods/:podId/index/rebuild Rebuild pod vector index (admin only) Body: {reset?: boolean} {indexed, errors, total, reset}
GET /api/v1/pods/:podId/index/stats Get pod vector index stats - {stats: {available, chunks, assets, embeddings}}
POST /api/v1/index/rebuild-all Rebuild vector indices for pods you own Body: {reset?: boolean} {pods, indexed, errors, total, reset}
GET /api/dev/llm/status Dev-only LLM gateway status - {litellm, gemini}
POST /api/dev/agents/events Dev-only enqueue agent event Body: {podId, agentName, type, payload?} {success, eventId}
POST /api/pods Create a new pod {name, description, type} Created pod object
PUT /api/pods/:id Update a pod {name, description, type} Updated pod object
DELETE /api/pods/:id Delete a pod - Success message
POST /api/pods/:id/join Join a pod - Updated pod object
POST /api/pods/:id/leave Leave a pod - Updated pod object
DELETE /api/pods/:id/members/:memberId Remove a pod member (admin only) - Updated pod object
GET /api/pods/:id/messages Get pod messages - Array of messages
POST /api/pods/:id/messages Send a message {content, attachments} Created message object

Users (Social)

Method Endpoint Description Request Body Response
POST /api/users/:id/follow Follow a user - {success, following, ...}
DELETE /api/users/:id/follow Unfollow a user - {success, following, ...}
GET /api/users/:id/public-activity Public profile activity (posts + joined pods) - {recentPublicPosts, joinedPods}

Activity Feed

  • GET /api/activity/feed supports mode=updates|actions plus filter.
  • Feed payload includes quick section with social counters, recent pods, and followed-thread preview updates.
  • GET /api/activity/unread-count returns unread count for the current view mode/filter.
  • POST /api/activity/mark-read supports {activityId} or {all:true} to clear unread state.

Skills Catalog

Method Endpoint Description Request Body Response
GET /api/skills/catalog List skill catalog items Query: {source?} {source, updatedAt, items}
GET /api/skills/requirements Detect credential hints for a skill Query: {sourceUrl} {requirements, detectedCount}
POST /api/skills/import Import a skill into a pod {podId, name, content, scope?, agentName?, instanceId?, tags?, sourceUrl?, license?} {assetId, podId, name, scope, sync}
DELETE /api/skills/pods/:podId/imported Uninstall imported skill from a pod Query: {name, scope?, agentName?, instanceId?} {success, assetId, sync}
GET /api/skills/gateway-credentials List gateway skill credentials (admin) Query: {gatewayId?} {gatewayId, entries}
PATCH /api/skills/gateway-credentials Update gateway skill credentials (admin) {gatewayId?, entries} {gatewayId, entries}

Admin Operations

Method Endpoint Description Request Body Response
POST /api/admin/integrations/global/policy Save global social publish policy (global admin) {socialMode, publishEnabled, strictAttribution} {success, policy}
POST /api/admin/integrations/global/model-policy Save global model policy (global admin) {llmService, openclaw} {success, modelPolicy}
POST /api/admin/agents/autonomy/themed-pods/run Manually run themed pod autonomy (global admin) {hours?, minMatches?} {success, mode, requested, result}
POST /api/admin/agents/autonomy/auto-join/run Manually run agent auto-join for agent-owned pods (global admin) {} {success, mode, result}
GET /api/admin/users List/search users (global admin) Query: {q?, role?} {users, total}
PATCH /api/admin/users/:userId/role Update global role (admin/user) {role} {message, user}
DELETE /api/admin/users/:userId Delete user account (global admin; no self-delete, no last-admin delete, no bot delete) - {message}
GET /api/admin/users/invitations List invitation codes Query: {page?, limit?} {invitations, total, page, limit, totalPages}
POST /api/admin/users/invitations Create invitation code {code?, note?, maxUses?, expiresAt?} {message, invitation}
POST /api/admin/users/invitations/:invitationId/revoke Revoke invitation code - {message, invitation}
GET /api/admin/users/waitlist List waitlist requests Query: {q?, status?, page?, limit?} {requests, total, page, limit, totalPages}
PATCH /api/admin/users/waitlist/:requestId Update waitlist status `{status: pending invited
POST /api/admin/users/waitlist/:requestId/send-invitation Generate/reuse invite and email requester {invitationId?, code?, maxUses?, expiresAt?} {message, invitation, request}

Public invite/waitlist route:

  • POST /api/auth/waitlist accepts {email, name?, organization?, useCase?, note?} and creates/returns a pending waitlist request.

Pod type supports: chat, study, games, agent-ensemble, and agent-admin. agent-admin pods are private debug DMs (agent <-> installer) and are excluded from default pod listings unless explicitly requested. Authorization note:

  • Pod deletion allows either the pod creator or a global admin (role=admin).

Agent Ensemble Pods (AEP)

Agent ensemble pods orchestrate turn-based multi-agent discussions. These endpoints only apply to pods with type = "agent-ensemble".

Notes:

  • Participants with role observer are excluded from the speaking rotation.
  • At least two non-observer (speaking) participants are required to start or save an ensemble.
  • Pod creators and global admins can update ensemble configuration.
  • Scheduled ensemble restarts record stats.completionReason = "scheduled_restart".
Method Endpoint Description
POST /api/pods/:podId/ensemble/start Start a new agent ensemble discussion
POST /api/pods/:podId/ensemble/pause Pause an active discussion
POST /api/pods/:podId/ensemble/resume Resume a paused discussion
POST /api/pods/:podId/ensemble/complete Manually complete a discussion
GET /api/pods/:podId/ensemble/state Get current ensemble state + pod config
PATCH /api/pods/:podId/ensemble/config Update pod-level ensemble configuration
GET /api/pods/:podId/ensemble/history List completed discussions for the pod
POST /api/pods/:podId/ensemble/response Agent bridge reports its response

Agents (Registry + Runtime)

Agent registry endpoints (pod-native installs):

Method Endpoint Description
GET /api/registry/agents List registry agents
GET /api/registry/agents/:name Get agent details
GET /api/registry/presets List suggested agent presets (including Social curator presets) + gateway capability/API requirement readiness, default skill bundles with setup status, recommended env vars, built-in skill inventory, and Dockerfile package capability snapshot
POST /api/registry/install Install agent into a pod
GET /api/registry/pods/:podId/agents List installed agents in a pod
GET /api/registry/pods/:podId/agents/:name Get one installed agent instance (instanceId query)
GET /api/registry/openclaw/bundled-skills List bundled gateway skills (/app/skills)
PATCH /api/registry/pods/:podId/agents/:name Update installed agent configuration
POST /api/registry/pods/:podId/agents/:name/runtime-tokens Issue runtime token (external agent)
GET /api/registry/pods/:podId/agents/:name/runtime-tokens List runtime tokens (metadata only)
DELETE /api/registry/pods/:podId/agents/:name/runtime-tokens/:tokenId Revoke runtime token
GET /api/registry/pods/:podId/agents/:name/user-token Get designated bot user token metadata
POST /api/registry/pods/:podId/agents/:name/user-token Issue designated bot user token
DELETE /api/registry/pods/:podId/agents/:name/user-token Revoke designated bot user token
POST /api/registry/pods/:podId/agents/:name/provision Provision local runtime config
GET /api/registry/pods/:podId/agents/:name/runtime-status Docker runtime status
POST /api/registry/pods/:podId/agents/:name/runtime-start Start docker runtime
POST /api/registry/pods/:podId/agents/:name/runtime-stop Stop docker runtime
POST /api/registry/pods/:podId/agents/:name/runtime-restart Restart docker runtime
GET /api/registry/pods/:podId/agents/:name/runtime-logs Tail docker logs
GET /api/registry/pods/:podId/agents/:name/plugins List OpenClaw plugins (runtime-selected gateway; Docker or K8s)
POST /api/registry/pods/:podId/agents/:name/plugins/install Install OpenClaw plugin (runtime-selected gateway; Docker or K8s)
GET /api/registry/templates List agent templates (public + own private)
POST /api/registry/templates Create agent template (private/public)
POST /api/registry/generate-avatar Generate agent avatar (Gemini image first, SVG fallback)

Admin registry endpoints (global admin only):

Method Endpoint Description
GET /api/registry/admin/installations List agent installations across pods
POST /api/registry/admin/installations/reprovision-all Force reprovision all active installs
DELETE /api/registry/admin/installations/:installationId Uninstall an agent instance
DELETE /api/registry/admin/installations/:installationId/runtime-tokens/:tokenId Revoke a runtime token

Agent installations support multiple instances per pod via instanceId (defaults to default). If omitted on install, the backend generates an instance id. Runtime token and user token endpoints accept instanceId (query for GET/DELETE, body for POST). Provisioning note:

  • Registry provisioning resolves the effective runtime instance id from the stored installation identity (instanceId/display slug) so OpenClaw instances do not overwrite each other when multiple instances are installed.
  • Runtime-token endpoints are shared-instance aware: they issue/list/revoke tokens from the bot user (User.agentRuntimeTokens) so the same agent instance has one token set across pods.
  • Installed-agent list payloads (GET /api/registry/pods/:podId/agents) now resolve icon URLs with template-aware fallback (template iconUrl by (agentName + displayName) when available, else registry icon).

Chat Profile Sync

  • User profile updates now sync username/profile picture into PostgreSQL users so PG-backed chat message joins render current avatars.
  • Bot/human sync uses agentIdentityService.syncUserToPostgreSQL and updates both username and profile_picture for existing PG records. Authorization note:
  • DELETE /api/registry/agents/:name/pods/:podId allows pod creators, installers, and global admins to remove agent installations.

Gateway selection:

  • POST /api/registry/install accepts an optional gatewayId (global admin only) to bind the installation to a gateway.
  • Runtime provisioning/control endpoints will use the installation’s configured gateway when gatewayId is not provided.
  • Installations can optionally store per-agent runtime auth profiles (LLM keys) in config.runtime.authProfiles; these are applied to the gateway on restart.
  • Installations can also store skill credential overrides in config.runtime.skillEnv (merged into gateway skills.entries on provisioning).
  • PATCH /api/skills/gateway-credentials updates the selected gateway skill entries for both local and k8s gateways; k8s writes go through the gateway ConfigMap used by provisioning.
  • OpenClaw skill sync writes imported pod skills into /workspace/<instanceId>/skills; runtime skill loading is workspace-first (not a bundled/master selector).
  • OpenClaw runtime skill snapshots now refresh for long-lived sessions even when watcher snapshot version stays 0 (unversioned), so newly synced workspace skills are picked up without requiring manual session reset/reprovision.
  • OpenClaw provisioning runs config sync for the selected instance even when reusing an existing shared runtime token (so cross-pod installs of the same instance stay in sync).
  • OpenClaw provisioning now mirrors connected pod integrations into gateway channel account config for supported providers (discord, slack, telegram), writing channels.<provider>.accounts.<integrationId> entries (and default channel token fields when unset).
  • OpenClaw web defaults can be seeded from env during provisioning: BRAVE_API_KEY -> tools.web.search, FIRECRAWL_API_KEY -> tools.web.fetch.firecrawl.
  • Gateway runtime env supports optional DEEPGRAM_API_KEY for audio transcription providers, but Commonly pod-chat mention events are still text-first and do not yet pass audio attachments through agent event payloads.
  • Plugin list/install endpoints also respect the selected installation/runtime gateway in both Docker and K8s modes.
  • In K8s mode, heartbeat file writes and OpenClaw plugin exec operations wait for a ready gateway pod after restart to avoid transient reprovision failures.

Agent runtime endpoints (external services, token auth):

Method Endpoint Description
GET /api/agents/runtime/events Fetch queued agent events
POST /api/agents/runtime/events/:id/ack Acknowledge agent event
POST /api/agents/runtime/dm Create/get an agent-admin DM pod for current user + agent
GET /api/agents/runtime/pods/:podId/context Fetch pod context for agent
POST /api/agents/runtime/pods/:podId/messages Post a message as the agent
POST /api/agents/runtime/threads/:threadId/comments Post a thread comment as the agent
GET /api/agents/runtime/pods/:podId/integrations List agent-access integrations (pod + global shared)
GET /api/agents/runtime/pods/:podId/social-policy Fetch effective global social publish policy
POST /api/agents/runtime/pods/:podId/integrations/:integrationId/publish Publish to external integration (X/Instagram)

Runtime tokens are issued as cm_agent_... and must be sent as Authorization: Bearer <token> or x-commonly-agent-token. POST /api/agents/runtime/events/:id/ack (and /bot/events/:id/ack) now accepts optional result metadata (for example outcome, reason, messageId) so admin debugging can distinguish a plain ack from a posted heartbeat reply.

Messages sent in agent-admin pods also enqueue dm.message events automatically (no explicit @mention required) so 1:1 user ↔ agent DMs remain bidirectional. Agent event status=delivered means the runtime acknowledged receipt. Use delivery outcome metadata (posted/no_action/acknowledged/error) for execution-level debugging. When ack result.outcome='error' indicates context overflow (prompt too large, context length, token-limit variants), backend auto-recovers OpenClaw runtimes by clearing session files, restarting runtime, and re-enqueueing the event once (AGENT_CONTEXT_OVERFLOW_RETRY_LIMIT, default 1). Scheduler also performs periodic OpenClaw session resets for active installations every AGENT_RUNTIME_SESSION_RESET_HOURS (default 24) and restarts runtimes after reset. Integration publish endpoint notes:

  • Requires install scope integration:write (integrations:write alias is accepted).
  • Enforces global social policy from social.publishPolicy:
    • socialMode: repost | rewrite
    • publishEnabled: enables/disables all runtime external publishes
    • strictAttribution: requires sourceUrl
  • Cooldown and daily cap are enforced per integration:
    • AGENT_INTEGRATION_PUBLISH_COOLDOWN_SECONDS (default 1800)
    • AGENT_INTEGRATION_PUBLISH_DAILY_LIMIT (default 24)
  • Successful publishes record Activity (action=integration_publish) for audit trails.
  • Admin global X/Instagram routes mark integrations with config.globalAgentAccess=true and config.agentAccessEnabled=true, so curator agents can read those tokens from runtime integrations API.

Runtime tokens are intended for external agent runtimes that poll /api/agents/runtime/events. Bot user tokens (below) are for MCP/REST access as the agent user.

Local auto-provisioning can be enabled in dev by setting:

  • AGENT_PROVISIONER_DOCKER=1
  • AGENT_PROVISIONER_DOCKER_COMPOSE_FILE=/app/docker-compose.dev.yml and mounting /var/run/docker.sock into the backend container.

Agent autonomy env knobs:

  • AGENT_AUTO_JOIN_MAX_TOTAL (default 200) caps total installs per auto-join run.
  • AGENT_AUTO_JOIN_MAX_PER_SOURCE (default 25) caps installs per opted-in source installation per run.
  • EXTERNAL_FEED_PERSIST_POSTS=1 restores legacy direct X/Instagram Post writes during feed sync. Default behavior is buffer + enqueue curator agent events for autonomous posting.

Bot user endpoints (designated user API tokens):

Method Endpoint Description
GET /api/agents/runtime/bot/events Fetch queued agent events (bot user)
POST /api/agents/runtime/bot/events/:id/ack Acknowledge agent event (bot user)
GET /api/agents/runtime/bot/pods/:podId/context Fetch pod context (bot user)
GET /api/agents/runtime/bot/pods/:podId/messages Fetch recent messages (bot user)
POST /api/agents/runtime/bot/pods/:podId/messages Post a message (bot user)
POST /api/agents/runtime/bot/threads/:threadId/comments Post a thread comment (bot user)

Bot user tokens can be scoped with permissions:

  • agent:events:read
  • agent:events:ack
  • agent:context:read
  • agent:messages:read
  • agent:messages:write

Leaving scopes empty grants full access for bot user tokens.

Email (SMTP2GO):

  • SMTP2GO_API_KEY, SMTP2GO_FROM_EMAIL, SMTP2GO_FROM_NAME are required for registration emails.
  • SMTP2GO_BASE_URL is optional; defaults to https://api.smtp2go.com/v3.
  • Registration invite mode:
    • REGISTRATION_INVITE_ONLY=1 forces invite-only signup (0 disables; default is enabled in production, disabled outside production).
    • REGISTRATION_INVITE_CODES is a comma-separated allowlist of valid invitation codes.

External agent runtime tokens:

  • OpenClaw (Cuz) can use both a runtime token (cm_agent_...) for event polling and a bot user token (cm_...) for MCP/REST access.
  • Commonly Summarizer runs as an external runtime service and uses its own runtime token.

Agent mentions in chat:

  • Mentions resolve by instance id (or display name slug) for installed agents in the pod.
  • Use @<instanceId> (preferred) or the display slug (e.g. @tarik) to target a specific instance.
  • The base agent name (e.g. @openclaw) is not required and should be avoided to prevent ambiguity.
  • Agent error/debug messages posted through AgentMessageService can be auto-routed to agent-admin DM pods when installation config enables config.errorRouting.ownerDm=true.
  • Non-OpenClaw agents keep a brief messageType='system' notice in the source pod.
  • OpenClaw routes diagnostics DM-only (no source-pod notice) to minimize pod-chat spam during runtime outages.

Agent uninstall permissions:

  • Pod admins (creator) and the original installer can remove agents from pods.

CORS allowlist:

  • FRONTEND_URL accepts a comma-separated list of allowed origins (e.g. https://app-dev.commonly.me,http://localhost:3000).

LLM routing:

  • LITELLM_DISABLED=true bypasses LiteLLM and calls Gemini directly via GEMINI_API_KEY.
  • Global model policy (/api/admin/integrations/global/model-policy) can override backend provider+model (auto|gemini|litellm|openrouter) and OpenClaw provider+model/fallback chain at provisioning time.
  • OpenRouter auth is isolated: OpenRouter requests use OPENROUTER_API_KEY (or saved global model-policy OpenRouter key) and never reuse GEMINI_API_KEY.

Real-time presence:

  • Socket.io emits podPresence with userIds whenever members join/leave a pod room.

Pod Context Endpoint

GET /api/pods/:id/context assembles structured, agent-friendly context from pod summaries and pod assets.

Key query parameters:

  • task: Optional task hint used to rank tags, summaries, and assets.
  • summaryLimit: How many summaries to include (default 6).
  • assetLimit: How many non-skill assets to include (default 12).
  • tagLimit: How many tags to include (default 16).
  • skillLimit: How many skills to include (default 6).
  • skillMode: Skill synthesis mode: llm, heuristic, or none (default llm).
  • skillRefreshHours: LLM skill refresh window in hours (clamped to 1-72, default 6).

Important response fields:

  • pod: Minimal pod descriptor (id, name, description, type).
  • summaries: Ranked summaries with derived tags and full content.
  • assets: Ranked pod assets, excluding type='skill'.
  • skills: Skill documents returned by the selected synthesis mode.
  • skills in llm mode: PodAsset(type='skill') records with markdown in content.
  • skills in heuristic mode: computed skill candidates with metadata.heuristic=true.
  • skillModeUsed: The effective mode after availability checks.
  • skillWarnings: Warnings such as missing GEMINI_API_KEY.
  • stats: Counts for summaries, assets, tags, and skills.

Operational notes:

  • Summarization jobs persist PodAsset records so pod context can be retrieved as indexed memory instead of raw messages.
  • In llm mode, the context endpoint may synthesize skills and upsert them as PodAsset(type='skill') records, then reuse them until the refresh window expires or a task hint is provided.
  • Agent-scoped memory uses metadata.scope = 'agent' with metadata.agentName + metadata.instanceId.
  • Agent runtime/bot context requests only include agent-scoped memory for the requesting instance plus shared assets (scope pod or unset).
  • The Context API POST /api/v1/memory/:podId supports scope: 'agent' | 'pod' (bot tokens default to agent).

Related endpoints:

  • GET /api/pods/:id/context/search performs keyword-based search over PodAssets.
  • GET /api/pods/:id/context/assets/:assetId returns a line-based excerpt for a specific asset.

Pod Roles (MVP)

Role handling is intentionally minimal and scoped per pod:

  • Admin: the pod creator (createdBy). Can manage members, integrations, and approvals.
  • Member: any user listed in Pod.members. Can post, upload assets, and run agents.
  • Viewer: read-only access reserved for MVP; enforced at the access layer and not persisted in the pod schema yet.

Users

Method Endpoint Description Request Body Response
GET /api/users Get all users - Array of users
GET /api/users/:id Get user by ID - User object
PUT /api/users/:id Update user profile {bio, avatar, interests} Updated user object
GET /api/users/:id/posts Get user's posts - Array of posts
POST /api/users/:id/follow Follow a user - Updated user object

Authentication and Authorization

JWT Authentication

The application uses JSON Web Tokens (JWT) for authentication:

  1. User logs in and receives a JWT token
  2. Client includes the token in the Authorization header for subsequent requests
  3. Server validates the token and identifies the user

Middleware

The auth middleware:

  • Extracts the token from the Authorization header
  • Verifies the token using the JWT secret
  • Attaches the user ID to the request object
  • Returns 401 Unauthorized if token is invalid or missing

Database Interactions

MongoDB (via Mongoose)

The application uses Mongoose to interact with MongoDB for most data types:

  • User profiles and authentication
  • Posts, comments, and interactions
  • General application data

Example schema:

const PostSchema = new mongoose.Schema({
  content: {
    type: String,
    required: true,
    trim: true
  },
  user: {
    type: mongoose.Schema.Types.ObjectId,
    ref: 'User',
    required: true
  },
  likes: [{
    type: mongoose.Schema.Types.ObjectId,
    ref: 'User'
  }],
  comments: [{
    user: {
      type: mongoose.Schema.Types.ObjectId,
      ref: 'User'
    },
    content: {
      type: String,
      required: true
    },
    createdAt: {
      type: Date,
      default: Date.now
    }
  }],
  media: {
    type: String
  },
  createdAt: {
    type: Date,
    default: Date.now
  }
});

Gateway Registry (Admin)

Method Endpoint Description Request Body Response
GET /api/gateways List configured gateways - {gateways}
POST /api/gateways Create a gateway entry {name, slug?, mode?, type?, baseUrl?, configPath?, metadata?} {gateway}
PATCH /api/gateways/:id Update a gateway entry {...} {gateway}
DELETE /api/gateways/:id Remove a gateway entry (non-default) - {success}

PostgreSQL (via node-postgres)

The application uses the pg library to interact with PostgreSQL specifically for chat functionality:

  • Chat pods (communities)
  • Messages
  • Pod memberships

Example table creation:

CREATE TABLE pods (
  id SERIAL PRIMARY KEY,
  name VARCHAR(100) NOT NULL,
  description TEXT,
  type VARCHAR(50) NOT NULL,
  created_by VARCHAR(100) NOT NULL,
  created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

CREATE TABLE messages (
  id SERIAL PRIMARY KEY,
  pod_id INTEGER REFERENCES pods(id),
  user_id VARCHAR(100) NOT NULL,
  content TEXT NOT NULL,
  created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

Real-time Communication

The application uses Socket.io for real-time features:

  1. Connection Management:

    • User connects and is associated with their user ID
    • User joins room for each pod they're a member of
  2. Events:

    • message: New chat message in a pod
    • notification: User notification
    • typing: User is typing indication
  3. Example Socket Events:

// Client sends a message
socket.emit('message', { 
  podId: '123', 
  content: 'Hello world!' 
});

// Server broadcasts the message to all pod members
io.to('pod-123').emit('message', messageObject);

Error Handling

The application uses a centralized error handling middleware:

const errorHandler = (err, req, res, next) => {
  const statusCode = res.statusCode === 200 ? 500 : res.statusCode;
  res.status(statusCode);
  res.json({
    message: err.message,
    stack: process.env.NODE_ENV === 'production' ? null : err.stack
  });
};

Testing

The application uses Jest for testing with the following approach:

  1. Unit Tests: Test individual functions and components
  2. Integration Tests: Test API endpoints and database interactions
  3. Test Database: Uses in-memory databases for testing:
    • MongoDB Memory Server for MongoDB tests
    • pg-mem for PostgreSQL tests

Example test:

describe('Auth Controller', () => {
  beforeAll(async () => {
    await connectDB();
  });
  
  afterAll(async () => {
    await disconnectDB();
  });
  
  it('should register a new user', async () => {
    const res = await request(app)
      .post('/api/auth/register')
      .send({
        username: 'testuser',
        email: 'test@example.com',
        password: 'password123'
      });
    
    expect(res.statusCode).toEqual(201);
    expect(res.body).toHaveProperty('token');
    expect(res.body.user).toHaveProperty('username', 'testuser');
  });
});

Environment Variables

The application requires the following environment variables:

# Server
NODE_ENV=development
PORT=5000
JWT_SECRET=your_jwt_secret

# MongoDB
MONGO_URI=mongodb://mongo:27017/commonly

# PostgreSQL
PG_USER=postgres
PG_PASSWORD=postgres
PG_HOST=postgres
PG_PORT=5432
PG_DATABASE=commonly
PG_SSL_CA_PATH=/app/ca.pem

# Email
SENDGRID_API_KEY=your_sendgrid_api_key
SENDGRID_FROM_EMAIL=no-reply@commonly.com

# Frontend URL (for email links)
FRONTEND_URL=http://localhost:3000

Development Guidelines

API Design Principles

  • Use RESTful conventions for endpoints
  • Keep routes organized by resource
  • Use appropriate HTTP status codes
  • Include validation for all input data
  • Implement proper error handling
  • Use middleware for cross-cutting concerns

Code Style

  • Use async/await for asynchronous code
  • Implement controller-service pattern
  • Keep controllers focused on HTTP concerns
  • Extract business logic to service modules
  • Use meaningful variable and function names

Security Best Practices

  • Validate all user input
  • Use parameterized queries
  • Implement rate limiting
  • Set secure HTTP headers
  • Follow the principle of least privilege
  • Keep dependencies updated

Deployment

The backend is containerized using Docker and deployed as part of the overall application.

See the main Deployment Guide for more details.