Files
numolus/db/migrate/20260928120000_create_core_tables.rb
T
wkhandClaude Opus 5.5 e14bada114 Add Rails backend: accounts via magic link, server documents, sharing
The app becomes a Rails 8 application; the Vue frontend moves to
app/frontend and is served through vite_rails.

- Without an account documents stay in the browser (IndexedDB). Sign-in
  is by magic link or the 6-digit code from the same mail; afterwards
  local documents can be uploaded into the account.
- Server documents (/d/<id>) with versioned saves and a single-editor
  lock per browser tab; others see them read-only and follow by polling.
- Sharing via link (works without an account) and via e-mail invitation,
  roles viewer/editor; the owner manages links and people.
- Store gains a read-only mode that blocks all content changes.
- Solid Queue, Cache and Cable share the one PostgreSQL database.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-28 20:09:06 +02:00

72 lines
2.6 KiB
Ruby

class CreateCoreTables < ActiveRecord::Migration[8.1]
def change
enable_extension "citext"
enable_extension "pgcrypto"
create_table :users do |t|
t.citext :email, null: false, index: { unique: true }
t.string :display_name
t.string :color
t.datetime :last_seen_at
t.timestamps
end
create_table :sessions do |t|
t.references :user, null: false, foreign_key: { on_delete: :cascade }
t.string :user_agent
t.string :ip_address
t.datetime :last_active_at
t.timestamps
end
create_table :documents, id: :uuid do |t|
t.references :owner, null: false, foreign_key: { to_table: :users, on_delete: :cascade }
t.string :title, null: false
t.text :content, null: false # the DocumentState JSON, same as inside a .num file
t.integer :version, null: false, default: 1
t.references :updated_by, foreign_key: { to_table: :users, on_delete: :nullify }
t.datetime :content_updated_at, null: false
# simple edit lock until live collaboration exists (concept, phase 3)
t.string :lock_holder
t.string :lock_holder_name
t.datetime :lock_expires_at
t.timestamps
end
create_table :share_links do |t|
t.references :document, type: :uuid, null: false, foreign_key: { on_delete: :cascade }
t.integer :role, null: false
t.string :token, null: false, index: { unique: true }
t.references :created_by, null: false, foreign_key: { to_table: :users, on_delete: :cascade }
t.datetime :revoked_at
t.timestamps
end
create_table :document_memberships do |t|
t.references :document, type: :uuid, null: false, foreign_key: { on_delete: :cascade }
t.references :user, null: false, foreign_key: { on_delete: :cascade }
t.integer :role, null: false
t.integer :source, null: false
t.references :share_link, foreign_key: { on_delete: :cascade }
t.datetime :last_opened_at
t.timestamps
t.index [ :document_id, :user_id ], unique: true
end
create_table :magic_links do |t|
t.citext :email, null: false, index: true
t.string :token_digest, null: false, index: { unique: true }
t.string :code_digest, null: false
t.integer :purpose, null: false, default: 0
t.references :document, type: :uuid, foreign_key: { on_delete: :cascade }
t.integer :role
t.references :invited_by, foreign_key: { to_table: :users, on_delete: :cascade }
t.string :return_to
t.integer :failed_attempts, null: false, default: 0
t.datetime :expires_at, null: false
t.datetime :used_at
t.timestamps
end
end
end