Skip to content

Latest commit

 

History

History
224 lines (164 loc) · 7.68 KB

File metadata and controls

224 lines (164 loc) · 7.68 KB

CoPlan Host App Integration Guide

Overview

CoPlan is a Rails engine that manages collaborative planning documents. It owns its own CoPlan::User model and handles authentication internally via a callback you configure.

Setup

1. Add the gem

# Gemfile
gem "coplan-engine", require: "coplan"

Then run bundle install.

2. Mount the engine

# config/routes.rb
mount CoPlan::Engine, at: "/coplan"

You can also mount at the root if CoPlan is the primary purpose of your app. When doing so, add as: "coplan" so the engine's route helpers resolve correctly:

# config/routes.rb
mount CoPlan::Engine, at: "/", as: "coplan"

3. Install migrations

bin/rails coplan:install:migrations
bin/rails db:migrate

This creates the engine's tables (coplan_users, coplan_plans, etc.) in your database.

Note: The engine auto-appends its migration paths at boot, so you can skip coplan:install:migrations if you prefer — db:migrate will pick them up automatically. Use install:migrations if you want local copies you can inspect or modify.

4. Install required reference data

bin/rails coplan:seed

CoPlan needs a built-in General plan type to exist. Replaying every migration creates it, but databases initialized from a checked-in schema (db:schema:load, db:prepare, db:setup) skip data migrations — the tables exist and the migrations are marked applied, but the row is missing and API plan creation with plan_type returns 422.

coplan:seed is idempotent and never overwrites plan types you've customized, so run it after any of the setup paths above. Alternatively, call CoPlan::Engine.load_seed from your own db/seeds.rb so db:setup and db:seed cover it automatically.

5. Configure authentication

Provide an authenticate callback that receives a Rack request and returns user identity attributes (or nil if unauthenticated):

# config/initializers/coplan.rb
CoPlan.configure do |config|
  config.authenticate = ->(request) {
    # Example: session-based auth
    user_id = request.session[:user_id]
    return nil unless user_id

    user = User.find_by(id: user_id)
    return nil unless user

    {
      external_id: user.id.to_s,   # required — unique ID from your auth system
      name: user.name,             # required — display name
      admin: user.admin?,          # optional — can manage CoPlan settings (default: false)
      metadata: {}                 # optional — arbitrary data (default: {})
    }
  }

  # Optional: AI provider configuration
  config.ai_api_key = ENV["OPENAI_API_KEY"]
  config.ai_model = "gpt-4o"
end

The callback is called on every CoPlan request. The engine automatically finds or creates a CoPlan::User from the returned attributes, keeping the name and admin flag in sync.

Callback return values

Key Type Required Description
external_id String Yes Unique identifier from your auth system
name String Yes Display name
admin Boolean No Can manage reviewers, settings (default: false)
metadata Hash No Arbitrary data stored as JSON (default: {})

Return nil to indicate the user is not authenticated (the engine will respond with 401 Unauthorized).

Authentication examples

Devise

config.authenticate = ->(request) {
  env = request.env
  warden = env["warden"]
  user = warden&.user
  return nil unless user

  {
    external_id: user.id.to_s,
    name: user.name,
    admin: user.admin?
  }
}

CoPlan::User model

The engine manages a coplan_users table with these columns:

Column Type Description
id String UUIDv7 primary key (auto-assigned)
external_id String Unique ID from your auth system
name String Display name
admin Boolean Admin flag
metadata JSON Extensible data bag

CoPlan::User is a normal ActiveRecord model. Host apps can reference it directly:

class Notification < ApplicationRecord
  belongs_to :user, class_name: "CoPlan::User"
end

File attachments (ActiveStorage)

Plans support file attachments (images, PDFs, text/CSV/JSON, ZIP — max 25 MB per file), built on ActiveStorage.

Migrations

The engine ships the ActiveStorage table migrations (active_storage_blobs, active_storage_attachments, active_storage_variant_records) alongside its own migrations, so they're installed the same way:

bin/rails co_plan:install:migrations
bin/rails db:migrate

The engine's copy differs from the stock Rails install migration in one way: active_storage_attachments.record_id is a string(36) column so it can hold CoPlan's UUID primary keys. The migration is guarded with table_exists? checks, so it's a no-op if your app already has ActiveStorage installed.

Warning: If your app already has ActiveStorage with a bigint record_id, CoPlan attachments won't work — the polymorphic record_id column can't store CoPlan's string UUIDs. You'll need to widen the column to string(36) (existing integer IDs will store fine as strings, but verify any raw-SQL queries you have against that table).

Storage service

Configure a storage service per environment (this is standard Rails — see the ActiveStorage guide):

# config/storage.yml
local:
  service: Disk
  root: <%= Rails.root.join("storage") %>

amazon:
  service: S3
  access_key_id: <%= Rails.application.credentials.dig(:aws, :access_key_id) %>
  secret_access_key: <%= Rails.application.credentials.dig(:aws, :secret_access_key) %>
  region: us-east-1
  bucket: your-bucket-<%= Rails.env %>
# config/environments/production.rb
config.active_storage.service = :amazon   # or :local for single-node Disk storage

The local Disk service is fine for development and single-node deployments (make sure storage/ is on a persistent volume). Use S3 (add gem "aws-sdk-s3") or another cloud service for multi-node production deployments.

API tokens

CoPlan provides a REST API for programmatic access. Users create API tokens in the Settings UI. API requests authenticate via Authorization: Bearer <token> headers — no session or callback required.

Layout and sign-out

CoPlan inherits from your ::ApplicationController for layout and middleware. The engine's nav bar will render a "Sign out" link if your app defines a sign_out_path route helper.

Configuration reference

CoPlan.configure do |config|
  # Required
  config.authenticate = ->(request) { ... }

  # AI provider (optional)
  config.ai_base_url = "https://api.openai.com/v1"  # default
  config.ai_api_key = nil
  config.ai_model = "gpt-4o"                         # default

  # Error reporting (optional)
  config.error_reporter = ->(exception, context) {
    Rails.error.report(exception, context: context)   # default
  }

  # Notifications (optional)
  config.notification_handler = ->(event, payload) { ... }

  # Analytics (optional)
  #
  # Fires for every event the engine instruments (page_view, plan_created,
  # plan_published, comment_created, thread_resolved, ...). Called inline on
  # the request thread; if your handler is slow, enqueue a job from inside it.
  # Exceptions raised by the handler are swallowed and reported via
  # `error_reporter`, so a broken sink will never break a user request.
  #
  # Payload always contains: :event, :timestamp, :user_id, :properties (Hash).
  config.track_event = ->(event, payload) {
    AnalyticsEvent.create!(name: event, payload: payload)
  }
end