Skip to content

Commit e66cf59

Browse files
authored
Add beacon sync status reporting endpoint (#661)
2 parents f08c2d6 + f818db9 commit e66cf59

10 files changed

Lines changed: 472 additions & 40 deletions

File tree

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
module Api
2+
module V1
3+
module Beacons
4+
class SyncStatusesController < Beacons::BaseController
5+
def create
6+
result = recorder.call(sync_status_params)
7+
8+
if result.success
9+
render json: { status: "accepted" }, status: :ok
10+
else
11+
render json: { errors: result.errors }, status: :unprocessable_entity
12+
end
13+
end
14+
15+
private
16+
17+
def recorder
18+
::Beacons::SyncStatusRecorder.new(Current.beacon)
19+
end
20+
21+
def sync_status_params
22+
{
23+
status: params[:status],
24+
manifest_version: params[:manifest_version],
25+
manifest_checksum: params[:manifest_checksum],
26+
synced_at: params[:synced_at],
27+
files_count: params[:files_count],
28+
total_size_bytes: params[:total_size_bytes],
29+
error_message: params[:error_message],
30+
device_info: extract_device_info,
31+
}
32+
end
33+
34+
def extract_device_info
35+
value = params[:device_info]
36+
return nil if value.blank?
37+
38+
value.respond_to?(:to_unsafe_h) ? value.to_unsafe_h : value
39+
end
40+
end
41+
end
42+
end
43+
end

app/models/beacon.rb

Lines changed: 28 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -3,32 +3,45 @@
33
# Table name: beacons
44
# Database name: primary
55
#
6-
# id :bigint not null, primary key
7-
# api_key_digest :string not null
8-
# api_key_prefix :string not null
9-
# manifest_checksum :string
10-
# manifest_data :jsonb
11-
# manifest_version :integer default(0), not null
12-
# name :string not null
13-
# previous_manifest_data :jsonb
14-
# revoked_at :datetime
15-
# created_at :datetime not null
16-
# updated_at :datetime not null
17-
# language_id :bigint not null
18-
# region_id :bigint not null
6+
# id :bigint not null, primary key
7+
# api_key_digest :string not null
8+
# api_key_prefix :string not null
9+
# device_info :jsonb
10+
# last_seen_at :datetime
11+
# last_sync_at :datetime
12+
# last_sync_error :text
13+
# manifest_checksum :string
14+
# manifest_data :jsonb
15+
# manifest_version :integer default(0), not null
16+
# name :string not null
17+
# previous_manifest_data :jsonb
18+
# reported_files_count :integer
19+
# reported_manifest_checksum :string
20+
# reported_manifest_version :string
21+
# reported_total_size_bytes :bigint
22+
# revoked_at :datetime
23+
# sync_status :string
24+
# created_at :datetime not null
25+
# updated_at :datetime not null
26+
# language_id :bigint not null
27+
# region_id :bigint not null
1928
#
2029
# Indexes
2130
#
2231
# index_beacons_on_api_key_digest (api_key_digest) UNIQUE
2332
# index_beacons_on_language_id (language_id)
33+
# index_beacons_on_last_seen_at (last_seen_at)
2434
# index_beacons_on_region_id (region_id)
35+
# index_beacons_on_sync_status (sync_status)
2536
#
2637
# Foreign Keys
2738
#
2839
# fk_rails_... (language_id => languages.id)
2940
# fk_rails_... (region_id => regions.id)
3041
#
3142
class Beacon < ApplicationRecord
43+
SYNC_STATUSES = %w[synced syncing outdated error].freeze
44+
3245
belongs_to :language
3346
belongs_to :region
3447

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

57+
enum :sync_status, SYNC_STATUSES.index_with(&:itself)
58+
4459
validates :name, presence: true
4560
validates :api_key_digest, presence: true, uniqueness: true
4661
validates :api_key_prefix, presence: true
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
module Beacons
2+
class SyncStatusRecorder
3+
Result = Data.define(:success, :errors)
4+
5+
def initialize(beacon, clock: Time)
6+
@beacon = beacon
7+
@clock = clock
8+
end
9+
10+
def call(payload)
11+
attrs = normalize(payload)
12+
return Result.new(success: false, errors: [ "status is invalid" ]) unless valid_status?(attrs[:sync_status])
13+
14+
beacon.update!(attrs)
15+
Result.new(success: true, errors: [])
16+
end
17+
18+
private
19+
20+
attr_reader :beacon, :clock
21+
22+
def normalize(payload)
23+
now = clock.current
24+
status = payload[:status].to_s
25+
26+
{
27+
sync_status: status,
28+
last_seen_at: now,
29+
last_sync_at: last_sync_at_for(status, payload[:synced_at]),
30+
reported_manifest_version: payload[:manifest_version],
31+
reported_manifest_checksum: payload[:manifest_checksum],
32+
reported_files_count: payload[:files_count],
33+
reported_total_size_bytes: payload[:total_size_bytes],
34+
last_sync_error: status == "error" ? payload[:error_message] : nil,
35+
device_info: payload[:device_info],
36+
}
37+
end
38+
39+
def last_sync_at_for(status, synced_at)
40+
return beacon.last_sync_at unless status == "synced"
41+
42+
parse_time(synced_at) || clock.current
43+
end
44+
45+
def parse_time(value)
46+
return nil if value.blank?
47+
48+
Time.iso8601(value.to_s)
49+
rescue ArgumentError
50+
nil
51+
end
52+
53+
def valid_status?(status)
54+
Beacon::SYNC_STATUSES.include?(status)
55+
end
56+
end
57+
end

config/routes.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@
5353
namespace :beacons do
5454
resources :files, only: :show
5555
resource :status, only: :show
56+
resource :sync_status, only: :create
5657
resource :manifest, only: :show
5758
end
5859
end
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
class AddSyncStatusToBeacons < ActiveRecord::Migration[8.0]
2+
def change
3+
add_column :beacons, :sync_status, :string
4+
add_column :beacons, :last_seen_at, :datetime
5+
add_column :beacons, :last_sync_at, :datetime
6+
add_column :beacons, :reported_manifest_version, :string
7+
add_column :beacons, :reported_manifest_checksum, :string
8+
add_column :beacons, :reported_files_count, :integer
9+
add_column :beacons, :reported_total_size_bytes, :bigint
10+
add_column :beacons, :last_sync_error, :text
11+
add_column :beacons, :device_info, :jsonb
12+
13+
add_index :beacons, :sync_status
14+
add_index :beacons, :last_seen_at
15+
end
16+
end

db/schema.rb

Lines changed: 12 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

spec/factories/beacons.rb

Lines changed: 24 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -3,25 +3,36 @@
33
# Table name: beacons
44
# Database name: primary
55
#
6-
# id :bigint not null, primary key
7-
# api_key_digest :string not null
8-
# api_key_prefix :string not null
9-
# manifest_checksum :string
10-
# manifest_data :jsonb
11-
# manifest_version :integer default(0), not null
12-
# name :string not null
13-
# previous_manifest_data :jsonb
14-
# revoked_at :datetime
15-
# created_at :datetime not null
16-
# updated_at :datetime not null
17-
# language_id :bigint not null
18-
# region_id :bigint not null
6+
# id :bigint not null, primary key
7+
# api_key_digest :string not null
8+
# api_key_prefix :string not null
9+
# device_info :jsonb
10+
# last_seen_at :datetime
11+
# last_sync_at :datetime
12+
# last_sync_error :text
13+
# manifest_checksum :string
14+
# manifest_data :jsonb
15+
# manifest_version :integer default(0), not null
16+
# name :string not null
17+
# previous_manifest_data :jsonb
18+
# reported_files_count :integer
19+
# reported_manifest_checksum :string
20+
# reported_manifest_version :string
21+
# reported_total_size_bytes :bigint
22+
# revoked_at :datetime
23+
# sync_status :string
24+
# created_at :datetime not null
25+
# updated_at :datetime not null
26+
# language_id :bigint not null
27+
# region_id :bigint not null
1928
#
2029
# Indexes
2130
#
2231
# index_beacons_on_api_key_digest (api_key_digest) UNIQUE
2332
# index_beacons_on_language_id (language_id)
33+
# index_beacons_on_last_seen_at (last_seen_at)
2434
# index_beacons_on_region_id (region_id)
35+
# index_beacons_on_sync_status (sync_status)
2536
#
2637
# Foreign Keys
2738
#

spec/models/beacon_spec.rb

Lines changed: 24 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -3,25 +3,36 @@
33
# Table name: beacons
44
# Database name: primary
55
#
6-
# id :bigint not null, primary key
7-
# api_key_digest :string not null
8-
# api_key_prefix :string not null
9-
# manifest_checksum :string
10-
# manifest_data :jsonb
11-
# manifest_version :integer default(0), not null
12-
# name :string not null
13-
# previous_manifest_data :jsonb
14-
# revoked_at :datetime
15-
# created_at :datetime not null
16-
# updated_at :datetime not null
17-
# language_id :bigint not null
18-
# region_id :bigint not null
6+
# id :bigint not null, primary key
7+
# api_key_digest :string not null
8+
# api_key_prefix :string not null
9+
# device_info :jsonb
10+
# last_seen_at :datetime
11+
# last_sync_at :datetime
12+
# last_sync_error :text
13+
# manifest_checksum :string
14+
# manifest_data :jsonb
15+
# manifest_version :integer default(0), not null
16+
# name :string not null
17+
# previous_manifest_data :jsonb
18+
# reported_files_count :integer
19+
# reported_manifest_checksum :string
20+
# reported_manifest_version :string
21+
# reported_total_size_bytes :bigint
22+
# revoked_at :datetime
23+
# sync_status :string
24+
# created_at :datetime not null
25+
# updated_at :datetime not null
26+
# language_id :bigint not null
27+
# region_id :bigint not null
1928
#
2029
# Indexes
2130
#
2231
# index_beacons_on_api_key_digest (api_key_digest) UNIQUE
2332
# index_beacons_on_language_id (language_id)
33+
# index_beacons_on_last_seen_at (last_seen_at)
2434
# index_beacons_on_region_id (region_id)
35+
# index_beacons_on_sync_status (sync_status)
2536
#
2637
# Foreign Keys
2738
#

0 commit comments

Comments
 (0)