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>
This commit is contained in:
@@ -0,0 +1,71 @@
|
||||
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
|
||||
Reference in New Issue
Block a user