diff --git a/Gemfile.lock b/Gemfile.lock index fd3a3d5..b0346e8 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,6 +1,6 @@ GIT remote: https://github.com/rails/rails.git - revision: 2ab0094ab6e94d30a5a424dc93c2aeb1066cc76b + revision: 95deab7b439abba23fdc4bd659116dab5dbe2606 branch: main specs: actioncable (8.1.0.alpha) @@ -127,7 +127,7 @@ GEM concurrent-ruby (1.3.4) connection_pool (2.4.1) crass (1.0.6) - date (3.4.0) + date (3.4.1) debug (1.9.2) irb (~> 1.10) reline (>= 0.3.8) @@ -144,11 +144,11 @@ GEM activesupport (>= 6.1) i18n (1.14.6) concurrent-ruby (~> 1.0) - io-console (0.7.2) + io-console (0.8.0) irb (1.14.1) rdoc (>= 4.0.0) reline (>= 0.4.2) - json (2.8.2) + json (2.9.0) kamal (2.3.0) activesupport (>= 7.0) base64 (~> 0.2) @@ -162,7 +162,7 @@ GEM zeitwerk (>= 2.6.18, < 3.0) keccak (1.3.1) language_server-protocol (3.17.0.3) - logger (1.6.1) + logger (1.6.2) loofah (2.23.1) crass (~> 1.0.2) nokogiri (>= 1.12.0) @@ -175,7 +175,7 @@ GEM matrix (0.4.2) mini_mime (1.1.5) mini_portile2 (2.8.8) - minitest (5.25.2) + minitest (5.25.4) msgpack (1.7.5) net-imap (0.5.1) date @@ -192,27 +192,28 @@ GEM net-protocol net-ssh (7.3.0) nio4r (2.7.4) - nokogiri (1.16.7-aarch64-linux) + nokogiri (1.16.8-aarch64-linux) racc (~> 1.4) - nokogiri (1.16.7-arm-linux) + nokogiri (1.16.8-arm-linux) racc (~> 1.4) - nokogiri (1.16.7-arm64-darwin) + nokogiri (1.16.8-arm64-darwin) racc (~> 1.4) - nokogiri (1.16.7-x86-linux) + nokogiri (1.16.8-x86-linux) racc (~> 1.4) - nokogiri (1.16.7-x86_64-darwin) + nokogiri (1.16.8-x86_64-darwin) racc (~> 1.4) - nokogiri (1.16.7-x86_64-linux) + nokogiri (1.16.8-x86_64-linux) racc (~> 1.4) ostruct (0.6.1) - pagy (9.3.1) + pagy (9.3.2) parallel (1.26.3) parser (3.3.6.0) ast (~> 2.4.1) racc pg (1.5.9) pkg-config (1.5.8) - psych (5.2.0) + psych (5.2.1) + date stringio public_suffix (6.0.1) puma (6.5.0) @@ -230,9 +231,9 @@ GEM activesupport (>= 5.0.0) minitest nokogiri (>= 1.6) - rails-html-sanitizer (1.6.0) + rails-html-sanitizer (1.6.1) loofah (~> 2.21) - nokogiri (~> 1.14) + nokogiri (>= 1.15.7, != 1.16.7, != 1.16.6, != 1.16.5, != 1.16.4, != 1.16.3, != 1.16.2, != 1.16.1, != 1.16.0.rc1, != 1.16.0) rainbow (3.1.1) rake (13.2.1) rbsecp256k1 (6.0.0) @@ -241,18 +242,18 @@ GEM rubyzip (~> 2.3) rdoc (6.8.1) psych (>= 4.0.0) - regexp_parser (2.9.2) - reline (0.5.11) + regexp_parser (2.9.3) + reline (0.5.12) io-console (~> 0.5) rexml (3.3.9) - rubocop (1.69.0) + rubocop (1.69.1) json (~> 2.3) language_server-protocol (>= 3.17.0) parallel (~> 1.10) parser (>= 3.3.0.2) rainbow (>= 2.2.2, < 4.0) - regexp_parser (>= 2.4, < 3.0) - rubocop-ast (>= 1.36.1, < 2.0) + regexp_parser (>= 2.9.3, < 3.0) + rubocop-ast (>= 1.36.2, < 2.0) ruby-progressbar (~> 1.7) unicode-display_width (>= 2.4.0, < 4.0) rubocop-ast (1.36.2) @@ -275,7 +276,7 @@ GEM rubocop-rails ruby-progressbar (1.13.0) rubyzip (2.3.2) - securerandom (0.3.2) + securerandom (0.4.0) selenium-webdriver (4.27.0) base64 (~> 0.2) logger (~> 1.4) @@ -318,7 +319,7 @@ GEM unicode-emoji (~> 4.0, >= 4.0.4) unicode-emoji (4.0.4) uri (1.0.2) - useragent (0.16.10) + useragent (0.16.11) web-console (4.2.1) actionview (>= 6.0.0) activemodel (>= 6.0.0) diff --git a/app/controllers/api/events_controller.rb b/app/controllers/api/events_controller.rb index 8870203..bccd84b 100644 --- a/app/controllers/api/events_controller.rb +++ b/app/controllers/api/events_controller.rb @@ -44,6 +44,14 @@ def create } }, status: :unprocessable_content end + rescue ActiveRecord::StaleObjectError + render json: { + status: "error", + error: { + message: "Event not saved", + data: "Too fast requests." + } + }, status: :unprocessable_content end def batch_create @@ -60,6 +68,8 @@ def batch_create errored = event_params break end + rescue ActiveRecord::StaleObjectError + errored = event_params end render json: { diff --git a/app/models/conversation.rb b/app/models/conversation.rb new file mode 100644 index 0000000..a2a129e --- /dev/null +++ b/app/models/conversation.rb @@ -0,0 +1,14 @@ +# frozen_string_literal: true + +class Conversation < ApplicationRecord + self.primary_key = %i[pubkey session] + self.lock_optimistically = true + + has_many :events, + foreign_key: %i[pubkey session], + dependent: :restrict_with_exception + + def latest_event + events.order(id: :desc).first + end +end diff --git a/app/models/event.rb b/app/models/event.rb index e080e39..43a7ee6 100644 --- a/app/models/event.rb +++ b/app/models/event.rb @@ -8,18 +8,17 @@ class Event < ApplicationRecord scope :of_topic, ->(topic) { where(topic:) } scope :of_recipient, ->(recipient) { where(recipient:) } + belongs_to :conversation, foreign_key: %i[pubkey session], required: true, autosave: true has_one :merkle_node, dependent: :restrict_with_exception after_create :add_to_merkle_tree # A publisher must not send events in the same time which makes harder to sort them. validates :created_at, - uniqueness: { - scope: :pubkey - }, + presence: true, comparison: { greater_than_or_equal_to: ->(current) { - current.latest&.created_at || 0 + current.conversation&.latest_event_created_at || 0 } } @@ -37,14 +36,20 @@ class Event < ApplicationRecord format: { with: /\A\h+\z/ }, allow_nil: true - before_validation do + before_validation on: :create do self.session = tags.find { |tag| tag[0] == "s" }&.[](1) self.topic = tags.find { |tag| tag[0] == "t" }&.[](1) self.recipient = tags.find { |tag| tag[0] == "p" }&.[](1) + + self.conversation ||= Conversation.find_or_create_by(pubkey: pubkey, session: session) do |c| + c.latest_event_created_at = created_at + c.events_count = 0 + end end - def readonly? - persisted? + after_validation on: :create do + self.conversation.latest_event_created_at = created_at + self.conversation.events_count += 1 end def merkle_tree_hash @@ -63,19 +68,8 @@ def inclusion_proof merkle_node&.inclusion_proof end - def latest - @latest ||= - Event - .of_topic(topic) - .of_pubkey(pubkey) - .of_session(session) - .order(id: :desc) - .first - end - - def reload(options = nil) - @latest = nil - super + def readonly? + persisted? end class << self diff --git a/config/application.rb b/config/application.rb index 93f6bb5..7c97837 100644 --- a/config/application.rb +++ b/config/application.rb @@ -37,5 +37,12 @@ class Application < Rails::Application # # config.time_zone = "Central Time (US & Canada)" # config.eager_load_paths << Rails.root.join("extras") + + config.i18n.load_path += Dir[Rails.root.join("config", "locales", "**", "*.{rb,yml}")] + + config.generators do |g| + g.helper false + g.assets false + end end end diff --git a/db/migrate/20240325200100_create_conversations.rb b/db/migrate/20240325200100_create_conversations.rb new file mode 100644 index 0000000..e20f05f --- /dev/null +++ b/db/migrate/20240325200100_create_conversations.rb @@ -0,0 +1,16 @@ +# frozen_string_literal: true + +class CreateConversations < ActiveRecord::Migration[8.1] + def change + create_table :conversations, primary_key: %i[pubkey session] do |t| + t.string :pubkey, null: false + t.string :session, null: false + + t.integer :lock_version + t.datetime :latest_event_created_at + t.integer :events_count + + t.timestamps + end + end +end diff --git a/db/migrate/20240325200101_create_events.rb b/db/migrate/20240325200101_create_events.rb index 058d02b..41af6d2 100644 --- a/db/migrate/20240325200101_create_events.rb +++ b/db/migrate/20240325200101_create_events.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -class CreateEvents < ActiveRecord::Migration[8.0] +class CreateEvents < ActiveRecord::Migration[8.1] def change create_table :events do |t| t.string :eid, null: false, index: { unique: true } @@ -10,9 +10,7 @@ def change t.string :content, null: false t.string :sig, null: false - t.datetime :created_at, null: false - - t.index %i[pubkey created_at], unique: true + t.datetime :created_at, null: false, index: true end end end diff --git a/db/migrate/20240325200102_add_extended_fields_to_events.rb b/db/migrate/20240325200102_add_extended_fields_to_events.rb index 2d3a080..fe4c5b6 100644 --- a/db/migrate/20240325200102_add_extended_fields_to_events.rb +++ b/db/migrate/20240325200102_add_extended_fields_to_events.rb @@ -1,8 +1,8 @@ # frozen_string_literal: true -class AddExtendedFieldsToEvents < ActiveRecord::Migration[8.0] +class AddExtendedFieldsToEvents < ActiveRecord::Migration[8.1] def change - change_table :events, id: :string do |t| + change_table :events do |t| t.string :session, null: false t.string :topic, null: true, index: true diff --git a/db/migrate/20240325200103_create_merkle_nodes.rb b/db/migrate/20240325200103_create_merkle_nodes.rb index 16729cb..0cbf426 100644 --- a/db/migrate/20240325200103_create_merkle_nodes.rb +++ b/db/migrate/20240325200103_create_merkle_nodes.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -class CreateMerkleNodes < ActiveRecord::Migration[8.0] +class CreateMerkleNodes < ActiveRecord::Migration[8.1] def change # 创建顺序:parent to children create_table :merkle_nodes do |t| diff --git a/db/schema.rb b/db/schema.rb index c75821a..9201790 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -14,6 +14,16 @@ # These are extensions that must be enabled in order to support this database enable_extension "pg_catalog.plpgsql" + create_table "conversations", primary_key: ["pubkey", "session"], force: :cascade do |t| + t.string "pubkey", null: false + t.string "session", null: false + t.integer "lock_version" + t.datetime "latest_event_created_at" + t.integer "events_count" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + end + create_table "events", force: :cascade do |t| t.string "eid", null: false t.string "pubkey", null: false @@ -25,8 +35,8 @@ t.string "session", null: false t.string "topic" t.string "recipient" + t.index ["created_at"], name: "index_events_on_created_at" t.index ["eid"], name: "index_events_on_eid", unique: true - t.index ["pubkey", "created_at"], name: "index_events_on_pubkey_and_created_at", unique: true t.index ["pubkey", "session"], name: "index_events_on_pubkey_and_session" t.index ["pubkey"], name: "index_events_on_pubkey" t.index ["recipient"], name: "index_events_on_recipient" diff --git a/rbs_collection.lock.yaml b/rbs_collection.lock.yaml index 0ad23ce..5aa22e0 100644 --- a/rbs_collection.lock.yaml +++ b/rbs_collection.lock.yaml @@ -6,7 +6,7 @@ gems: source: type: git name: ruby/gem_rbs_collection - revision: b438fcecafb7fe0faad88c2bf3b8eec17d83e4d9 + revision: 0a6ea105a0afc7eaee4494585a7775f47eea6145 remote: https://github.com/ruby/gem_rbs_collection.git repo_dir: gems - name: actionmailer @@ -14,7 +14,7 @@ gems: source: type: git name: ruby/gem_rbs_collection - revision: b438fcecafb7fe0faad88c2bf3b8eec17d83e4d9 + revision: 0a6ea105a0afc7eaee4494585a7775f47eea6145 remote: https://github.com/ruby/gem_rbs_collection.git repo_dir: gems - name: actionpack @@ -22,7 +22,7 @@ gems: source: type: git name: ruby/gem_rbs_collection - revision: b438fcecafb7fe0faad88c2bf3b8eec17d83e4d9 + revision: 0a6ea105a0afc7eaee4494585a7775f47eea6145 remote: https://github.com/ruby/gem_rbs_collection.git repo_dir: gems - name: actiontext @@ -30,7 +30,7 @@ gems: source: type: git name: ruby/gem_rbs_collection - revision: b438fcecafb7fe0faad88c2bf3b8eec17d83e4d9 + revision: 0a6ea105a0afc7eaee4494585a7775f47eea6145 remote: https://github.com/ruby/gem_rbs_collection.git repo_dir: gems - name: actionview @@ -38,7 +38,7 @@ gems: source: type: git name: ruby/gem_rbs_collection - revision: b438fcecafb7fe0faad88c2bf3b8eec17d83e4d9 + revision: 0a6ea105a0afc7eaee4494585a7775f47eea6145 remote: https://github.com/ruby/gem_rbs_collection.git repo_dir: gems - name: activejob @@ -46,7 +46,7 @@ gems: source: type: git name: ruby/gem_rbs_collection - revision: b438fcecafb7fe0faad88c2bf3b8eec17d83e4d9 + revision: 0a6ea105a0afc7eaee4494585a7775f47eea6145 remote: https://github.com/ruby/gem_rbs_collection.git repo_dir: gems - name: activemodel @@ -54,7 +54,7 @@ gems: source: type: git name: ruby/gem_rbs_collection - revision: b438fcecafb7fe0faad88c2bf3b8eec17d83e4d9 + revision: 0a6ea105a0afc7eaee4494585a7775f47eea6145 remote: https://github.com/ruby/gem_rbs_collection.git repo_dir: gems - name: activerecord @@ -62,7 +62,7 @@ gems: source: type: git name: ruby/gem_rbs_collection - revision: b438fcecafb7fe0faad88c2bf3b8eec17d83e4d9 + revision: 0a6ea105a0afc7eaee4494585a7775f47eea6145 remote: https://github.com/ruby/gem_rbs_collection.git repo_dir: gems - name: activestorage @@ -70,7 +70,7 @@ gems: source: type: git name: ruby/gem_rbs_collection - revision: b438fcecafb7fe0faad88c2bf3b8eec17d83e4d9 + revision: 0a6ea105a0afc7eaee4494585a7775f47eea6145 remote: https://github.com/ruby/gem_rbs_collection.git repo_dir: gems - name: activesupport @@ -78,7 +78,7 @@ gems: source: type: git name: ruby/gem_rbs_collection - revision: b438fcecafb7fe0faad88c2bf3b8eec17d83e4d9 + revision: 0a6ea105a0afc7eaee4494585a7775f47eea6145 remote: https://github.com/ruby/gem_rbs_collection.git repo_dir: gems - name: addressable @@ -86,7 +86,7 @@ gems: source: type: git name: ruby/gem_rbs_collection - revision: b438fcecafb7fe0faad88c2bf3b8eec17d83e4d9 + revision: 0a6ea105a0afc7eaee4494585a7775f47eea6145 remote: https://github.com/ruby/gem_rbs_collection.git repo_dir: gems - name: base64 @@ -110,7 +110,7 @@ gems: source: type: git name: ruby/gem_rbs_collection - revision: b438fcecafb7fe0faad88c2bf3b8eec17d83e4d9 + revision: 0a6ea105a0afc7eaee4494585a7775f47eea6145 remote: https://github.com/ruby/gem_rbs_collection.git repo_dir: gems - name: connection_pool @@ -118,7 +118,7 @@ gems: source: type: git name: ruby/gem_rbs_collection - revision: b438fcecafb7fe0faad88c2bf3b8eec17d83e4d9 + revision: 0a6ea105a0afc7eaee4494585a7775f47eea6145 remote: https://github.com/ruby/gem_rbs_collection.git repo_dir: gems - name: date @@ -146,7 +146,7 @@ gems: source: type: git name: ruby/gem_rbs_collection - revision: b438fcecafb7fe0faad88c2bf3b8eec17d83e4d9 + revision: 0a6ea105a0afc7eaee4494585a7775f47eea6145 remote: https://github.com/ruby/gem_rbs_collection.git repo_dir: gems - name: i18n @@ -154,7 +154,7 @@ gems: source: type: git name: ruby/gem_rbs_collection - revision: b438fcecafb7fe0faad88c2bf3b8eec17d83e4d9 + revision: 0a6ea105a0afc7eaee4494585a7775f47eea6145 remote: https://github.com/ruby/gem_rbs_collection.git repo_dir: gems - name: io-console @@ -170,7 +170,7 @@ gems: source: type: git name: ruby/gem_rbs_collection - revision: b438fcecafb7fe0faad88c2bf3b8eec17d83e4d9 + revision: 0a6ea105a0afc7eaee4494585a7775f47eea6145 remote: https://github.com/ruby/gem_rbs_collection.git repo_dir: gems - name: marcel @@ -178,7 +178,7 @@ gems: source: type: git name: ruby/gem_rbs_collection - revision: b438fcecafb7fe0faad88c2bf3b8eec17d83e4d9 + revision: 0a6ea105a0afc7eaee4494585a7775f47eea6145 remote: https://github.com/ruby/gem_rbs_collection.git repo_dir: gems - name: mini_mime @@ -186,7 +186,7 @@ gems: source: type: git name: ruby/gem_rbs_collection - revision: b438fcecafb7fe0faad88c2bf3b8eec17d83e4d9 + revision: 0a6ea105a0afc7eaee4494585a7775f47eea6145 remote: https://github.com/ruby/gem_rbs_collection.git repo_dir: gems - name: minitest @@ -214,7 +214,7 @@ gems: source: type: git name: ruby/gem_rbs_collection - revision: b438fcecafb7fe0faad88c2bf3b8eec17d83e4d9 + revision: 0a6ea105a0afc7eaee4494585a7775f47eea6145 remote: https://github.com/ruby/gem_rbs_collection.git repo_dir: gems - name: openssl @@ -238,7 +238,7 @@ gems: source: type: git name: ruby/gem_rbs_collection - revision: b438fcecafb7fe0faad88c2bf3b8eec17d83e4d9 + revision: 0a6ea105a0afc7eaee4494585a7775f47eea6145 remote: https://github.com/ruby/gem_rbs_collection.git repo_dir: gems - name: rails-dom-testing @@ -246,7 +246,7 @@ gems: source: type: git name: ruby/gem_rbs_collection - revision: b438fcecafb7fe0faad88c2bf3b8eec17d83e4d9 + revision: 0a6ea105a0afc7eaee4494585a7775f47eea6145 remote: https://github.com/ruby/gem_rbs_collection.git repo_dir: gems - name: rails-html-sanitizer @@ -254,7 +254,7 @@ gems: source: type: git name: ruby/gem_rbs_collection - revision: b438fcecafb7fe0faad88c2bf3b8eec17d83e4d9 + revision: 0a6ea105a0afc7eaee4494585a7775f47eea6145 remote: https://github.com/ruby/gem_rbs_collection.git repo_dir: gems - name: railties @@ -262,7 +262,7 @@ gems: source: type: git name: ruby/gem_rbs_collection - revision: b438fcecafb7fe0faad88c2bf3b8eec17d83e4d9 + revision: 0a6ea105a0afc7eaee4494585a7775f47eea6145 remote: https://github.com/ruby/gem_rbs_collection.git repo_dir: gems - name: rake @@ -270,7 +270,7 @@ gems: source: type: git name: ruby/gem_rbs_collection - revision: b438fcecafb7fe0faad88c2bf3b8eec17d83e4d9 + revision: 0a6ea105a0afc7eaee4494585a7775f47eea6145 remote: https://github.com/ruby/gem_rbs_collection.git repo_dir: gems - name: rdoc @@ -282,7 +282,7 @@ gems: source: type: git name: ruby/gem_rbs_collection - revision: b438fcecafb7fe0faad88c2bf3b8eec17d83e4d9 + revision: 0a6ea105a0afc7eaee4494585a7775f47eea6145 remote: https://github.com/ruby/gem_rbs_collection.git repo_dir: gems - name: rubyzip @@ -290,7 +290,7 @@ gems: source: type: git name: ruby/gem_rbs_collection - revision: b438fcecafb7fe0faad88c2bf3b8eec17d83e4d9 + revision: 0a6ea105a0afc7eaee4494585a7775f47eea6145 remote: https://github.com/ruby/gem_rbs_collection.git repo_dir: gems - name: securerandom @@ -314,7 +314,7 @@ gems: source: type: git name: ruby/gem_rbs_collection - revision: b438fcecafb7fe0faad88c2bf3b8eec17d83e4d9 + revision: 0a6ea105a0afc7eaee4494585a7775f47eea6145 remote: https://github.com/ruby/gem_rbs_collection.git repo_dir: gems - name: time @@ -334,7 +334,7 @@ gems: source: type: git name: ruby/gem_rbs_collection - revision: b438fcecafb7fe0faad88c2bf3b8eec17d83e4d9 + revision: 0a6ea105a0afc7eaee4494585a7775f47eea6145 remote: https://github.com/ruby/gem_rbs_collection.git repo_dir: gems - name: uri