Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
43 changes: 43 additions & 0 deletions app/controllers/api/v1/beacons/sync_statuses_controller.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
module Api
module V1
module Beacons
class SyncStatusesController < Beacons::BaseController
def create
result = recorder.call(sync_status_params)

if result.success
render json: { status: "accepted" }, status: :ok
else
render json: { errors: result.errors }, status: :unprocessable_entity
end
end

private

def recorder
::Beacons::SyncStatusRecorder.new(Current.beacon)
end

def sync_status_params
{
status: params[:status],
manifest_version: params[:manifest_version],
manifest_checksum: params[:manifest_checksum],
synced_at: params[:synced_at],
files_count: params[:files_count],
total_size_bytes: params[:total_size_bytes],
error_message: params[:error_message],
device_info: extract_device_info,
}
end

def extract_device_info
value = params[:device_info]
return nil if value.blank?

value.respond_to?(:to_unsafe_h) ? value.to_unsafe_h : value
end
end
end
end
end
41 changes: 28 additions & 13 deletions app/models/beacon.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,32 +3,45 @@
# Table name: beacons
# Database name: primary
#
# id :bigint not null, primary key
# api_key_digest :string not null
# api_key_prefix :string not null
# manifest_checksum :string
# manifest_data :jsonb
# manifest_version :integer default(0), not null
# name :string not null
# previous_manifest_data :jsonb
# revoked_at :datetime
# created_at :datetime not null
# updated_at :datetime not null
# language_id :bigint not null
# region_id :bigint not null
# id :bigint not null, primary key
# api_key_digest :string not null
# api_key_prefix :string not null
# device_info :jsonb
# last_seen_at :datetime
# last_sync_at :datetime
# last_sync_error :text
# manifest_checksum :string
# manifest_data :jsonb
# manifest_version :integer default(0), not null
# name :string not null
# previous_manifest_data :jsonb
# reported_files_count :integer
# reported_manifest_checksum :string
# reported_manifest_version :string
# reported_total_size_bytes :bigint
# revoked_at :datetime
# sync_status :string
# created_at :datetime not null
# updated_at :datetime not null
# language_id :bigint not null
# region_id :bigint not null
#
# Indexes
#
# index_beacons_on_api_key_digest (api_key_digest) UNIQUE
# index_beacons_on_language_id (language_id)
# index_beacons_on_last_seen_at (last_seen_at)
# index_beacons_on_region_id (region_id)
# index_beacons_on_sync_status (sync_status)
#
# Foreign Keys
#
# fk_rails_... (language_id => languages.id)
# fk_rails_... (region_id => regions.id)
#
class Beacon < ApplicationRecord
SYNC_STATUSES = %w[synced syncing outdated error].freeze

belongs_to :language
belongs_to :region

Expand All @@ -41,6 +54,8 @@ class Beacon < ApplicationRecord
delegate :name, to: :region, prefix: true
delegate :name, to: :language, prefix: true

enum :sync_status, SYNC_STATUSES.index_with(&:itself)

validates :name, presence: true
validates :api_key_digest, presence: true, uniqueness: true
validates :api_key_prefix, presence: true
Expand Down
57 changes: 57 additions & 0 deletions app/services/beacons/sync_status_recorder.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
module Beacons
class SyncStatusRecorder
Result = Data.define(:success, :errors)

def initialize(beacon, clock: Time)
@beacon = beacon
@clock = clock
end

def call(payload)
attrs = normalize(payload)
return Result.new(success: false, errors: [ "status is invalid" ]) unless valid_status?(attrs[:sync_status])

beacon.update!(attrs)
Result.new(success: true, errors: [])
end

private

attr_reader :beacon, :clock

def normalize(payload)
now = clock.current
status = payload[:status].to_s

{
sync_status: status,
last_seen_at: now,
last_sync_at: last_sync_at_for(status, payload[:synced_at]),
reported_manifest_version: payload[:manifest_version],
reported_manifest_checksum: payload[:manifest_checksum],
reported_files_count: payload[:files_count],
reported_total_size_bytes: payload[:total_size_bytes],
last_sync_error: status == "error" ? payload[:error_message] : nil,
device_info: payload[:device_info],
}
end

def last_sync_at_for(status, synced_at)
return beacon.last_sync_at unless status == "synced"

parse_time(synced_at) || clock.current
end

def parse_time(value)
return nil if value.blank?

Time.iso8601(value.to_s)
rescue ArgumentError
nil
end

def valid_status?(status)
Beacon::SYNC_STATUSES.include?(status)
end
end
end
1 change: 1 addition & 0 deletions config/routes.rb
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@
namespace :beacons do
resources :files, only: :show
resource :status, only: :show
resource :sync_status, only: :create
resource :manifest, only: :show
end
end
Expand Down
16 changes: 16 additions & 0 deletions db/migrate/20260203100008_add_sync_status_to_beacons.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
class AddSyncStatusToBeacons < ActiveRecord::Migration[8.0]
def change
add_column :beacons, :sync_status, :string
add_column :beacons, :last_seen_at, :datetime
add_column :beacons, :last_sync_at, :datetime
add_column :beacons, :reported_manifest_version, :string
add_column :beacons, :reported_manifest_checksum, :string
add_column :beacons, :reported_files_count, :integer
add_column :beacons, :reported_total_size_bytes, :bigint
add_column :beacons, :last_sync_error, :text
add_column :beacons, :device_info, :jsonb

add_index :beacons, :sync_status
add_index :beacons, :last_seen_at
end
end
13 changes: 12 additions & 1 deletion db/schema.rb

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

37 changes: 24 additions & 13 deletions spec/factories/beacons.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,25 +3,36 @@
# Table name: beacons
# Database name: primary
#
# id :bigint not null, primary key
# api_key_digest :string not null
# api_key_prefix :string not null
# manifest_checksum :string
# manifest_data :jsonb
# manifest_version :integer default(0), not null
# name :string not null
# previous_manifest_data :jsonb
# revoked_at :datetime
# created_at :datetime not null
# updated_at :datetime not null
# language_id :bigint not null
# region_id :bigint not null
# id :bigint not null, primary key
# api_key_digest :string not null
# api_key_prefix :string not null
# device_info :jsonb
# last_seen_at :datetime
# last_sync_at :datetime
# last_sync_error :text
# manifest_checksum :string
# manifest_data :jsonb
# manifest_version :integer default(0), not null
# name :string not null
# previous_manifest_data :jsonb
# reported_files_count :integer
# reported_manifest_checksum :string
# reported_manifest_version :string
# reported_total_size_bytes :bigint
# revoked_at :datetime
# sync_status :string
# created_at :datetime not null
# updated_at :datetime not null
# language_id :bigint not null
# region_id :bigint not null
#
# Indexes
#
# index_beacons_on_api_key_digest (api_key_digest) UNIQUE
# index_beacons_on_language_id (language_id)
# index_beacons_on_last_seen_at (last_seen_at)
# index_beacons_on_region_id (region_id)
# index_beacons_on_sync_status (sync_status)
#
# Foreign Keys
#
Expand Down
37 changes: 24 additions & 13 deletions spec/models/beacon_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,25 +3,36 @@
# Table name: beacons
# Database name: primary
#
# id :bigint not null, primary key
# api_key_digest :string not null
# api_key_prefix :string not null
# manifest_checksum :string
# manifest_data :jsonb
# manifest_version :integer default(0), not null
# name :string not null
# previous_manifest_data :jsonb
# revoked_at :datetime
# created_at :datetime not null
# updated_at :datetime not null
# language_id :bigint not null
# region_id :bigint not null
# id :bigint not null, primary key
# api_key_digest :string not null
# api_key_prefix :string not null
# device_info :jsonb
# last_seen_at :datetime
# last_sync_at :datetime
# last_sync_error :text
# manifest_checksum :string
# manifest_data :jsonb
# manifest_version :integer default(0), not null
# name :string not null
# previous_manifest_data :jsonb
# reported_files_count :integer
# reported_manifest_checksum :string
# reported_manifest_version :string
# reported_total_size_bytes :bigint
# revoked_at :datetime
# sync_status :string
# created_at :datetime not null
# updated_at :datetime not null
# language_id :bigint not null
# region_id :bigint not null
#
# Indexes
#
# index_beacons_on_api_key_digest (api_key_digest) UNIQUE
# index_beacons_on_language_id (language_id)
# index_beacons_on_last_seen_at (last_seen_at)
# index_beacons_on_region_id (region_id)
# index_beacons_on_sync_status (sync_status)
#
# Foreign Keys
#
Expand Down
Loading