Skip to content
Merged
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
56 changes: 56 additions & 0 deletions app/controllers/api/v4/legacy_data_dumps_controller.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
class Api::V4::LegacyDataDumpsController < APIController
def create
legacy_dump = LegacyMobileDataDump.new(legacy_dump_params)

if legacy_dump.save
log_success(legacy_dump)

render json: {id: legacy_dump.id, status: "ok"}, status: :ok
else
log_failure(legacy_dump)

render json: {errors: legacy_dump.errors.full_messages}, status: :unprocessable_entity
end
end

private

def legacy_dump_params
{
raw_payload: raw_payload,
dump_date: Time.current.utc,
user: current_user,
mobile_version: mobile_version
}
end

def raw_payload
{
"patients" => params.require(:patients)
}
end

def mobile_version
request.headers["HTTP_X_APP_VERSION"]
end

def log_success(legacy_dump)
Rails.logger.info(
msg: "legacy_data_dump_created",
legacy_dump_id: legacy_dump.id,
user_id: legacy_dump.user_id,
facility_id: current_facility.id,
mobile_version: legacy_dump.mobile_version,
payload_keys: legacy_dump.raw_payload.keys
)
end

def log_failure(legacy_dump)
Rails.logger.warn(
msg: "legacy_data_dump_failed",
user_id: legacy_dump.user_id,
facility_id: current_facility&.id,
errors: legacy_dump.errors.full_messages
)
end
end
7 changes: 7 additions & 0 deletions app/models/legacy_mobile_data_dump.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
class LegacyMobileDataDump < ActiveRecord::Base
belongs_to :user

validates :user, presence: true
validates :raw_payload, presence: true
validates :dump_date, presence: true
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we add a validation for user presence also ?

end
2 changes: 2 additions & 0 deletions config/routes.rb
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,8 @@
namespace :v4, path: "v4" do
put "import", to: "imports#import"

resources :legacy_data_dumps, only: [:create]

scope :blood_sugars do
get "sync", to: "blood_sugars#sync_to_user"
post "sync", to: "blood_sugars#sync_from_user"
Expand Down
14 changes: 14 additions & 0 deletions db/migrate/20260105123000_create_legacy_mobile_data_dumps.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
class CreateLegacyMobileDataDumps < ActiveRecord::Migration[6.1]
def change
create_table :legacy_mobile_data_dumps, id: :uuid do |t|
t.jsonb :raw_payload, null: false
t.datetime :dump_date, null: false
t.references :user, type: :uuid, foreign_key: true, null: false
t.string :mobile_version

t.timestamps
end

add_index :legacy_mobile_data_dumps, :dump_date
end
end
49 changes: 47 additions & 2 deletions db/structure.sql
Original file line number Diff line number Diff line change
Expand Up @@ -613,6 +613,21 @@ CREATE TABLE public.facilities (
);


--
-- Name: legacy_mobile_data_dumps; Type: TABLE; Schema: public; Owner: -
--

CREATE TABLE public.legacy_mobile_data_dumps (
id uuid DEFAULT public.gen_random_uuid() NOT NULL,
raw_payload jsonb NOT NULL,
dump_date timestamp without time zone NOT NULL,
user_id uuid NOT NULL,
mobile_version character varying,
created_at timestamp without time zone NOT NULL,
updated_at timestamp without time zone NOT NULL
);


--
-- Name: medical_histories; Type: TABLE; Schema: public; Owner: -
--
Expand Down Expand Up @@ -6297,6 +6312,14 @@ ALTER TABLE ONLY public.facilities
ADD CONSTRAINT facilities_pkey PRIMARY KEY (id);


--
-- Name: legacy_mobile_data_dumps legacy_mobile_data_dumps_pkey; Type: CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.legacy_mobile_data_dumps
ADD CONSTRAINT legacy_mobile_data_dumps_pkey PRIMARY KEY (id);


--
-- Name: facility_business_identifiers facility_business_identifiers_pkey; Type: CONSTRAINT; Schema: public; Owner: -
--
Expand Down Expand Up @@ -7143,6 +7166,20 @@ CREATE UNIQUE INDEX index_facilities_on_slug ON public.facilities USING btree (s
CREATE INDEX index_facilities_on_updated_at ON public.facilities USING btree (updated_at);


--
-- Name: index_legacy_mobile_data_dumps_on_dump_date; Type: INDEX; Schema: public; Owner: -
--

CREATE INDEX index_legacy_mobile_data_dumps_on_dump_date ON public.legacy_mobile_data_dumps USING btree (dump_date);


--
-- Name: index_legacy_mobile_data_dumps_on_user_id; Type: INDEX; Schema: public; Owner: -
--

CREATE INDEX index_legacy_mobile_data_dumps_on_user_id ON public.legacy_mobile_data_dumps USING btree (user_id);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This index is not created in the migration file. Please add it there

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

t.references :user already adds the index, so an explicit add_index is not required.



--
-- Name: index_facilities_teleconsult_mos_on_facility_id_and_user_id; Type: INDEX; Schema: public; Owner: -
--
Expand Down Expand Up @@ -8344,6 +8381,14 @@ ALTER TABLE ONLY public.facilities
ADD CONSTRAINT fk_rails_c44117c78f FOREIGN KEY (facility_group_id) REFERENCES public.facility_groups(id);


--
-- Name: legacy_mobile_data_dumps fk_rails_a1b2c3d4e5; Type: FK CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.legacy_mobile_data_dumps
ADD CONSTRAINT fk_rails_a1b2c3d4e5 FOREIGN KEY (user_id) REFERENCES public.users(id);


--
-- Name: dr_rai_action_plans fk_rails_c6db95d644; Type: FK CONSTRAINT; Schema: public; Owner: -
--
Expand Down Expand Up @@ -8597,5 +8642,5 @@ INSERT INTO "schema_migrations" (version) VALUES
('20250924102156'),
('20250925094123'),
('20251125090819'),
('20251211073126');

('20251211073126'),
('20260105123000');
161 changes: 161 additions & 0 deletions spec/requests/api/v4/legacy_data_dumps_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,161 @@
require "rails_helper"

RSpec.describe "Legacy Data Dumps", type: :request do
let(:request_user) { FactoryBot.create(:user) }
let(:request_facility) { request_user.facility }

let(:headers) do
{
"HTTP_X_USER_ID" => request_user.id,
"HTTP_X_FACILITY_ID" => request_facility.id,
"HTTP_AUTHORIZATION" => "Bearer #{request_user.access_token}",
"HTTP_X_APP_VERSION" => "2024.1.0",
"CONTENT_TYPE" => "application/json",
"ACCEPT" => "application/json"
}
end

describe "POST /api/v4/legacy_data_dumps" do
let(:valid_payload) do
{
patients: [
{
id: SecureRandom.uuid,
full_name: "Test Patient",
age: 45,
gender: "male",
status: "active",

medical_histories: [
{
id: SecureRandom.uuid,
diabetes: "unknown",
prior_heart_attack: "yes"
}
],

blood_pressures: [
{
id: SecureRandom.uuid,
systolic: 140,
diastolic: 90
}
],

blood_sugars: [
{
id: SecureRandom.uuid,
blood_sugar_type: "random",
blood_sugar_value: 180
}
],

prescription_drugs: [
{
id: SecureRandom.uuid,
name: "Amlodipine",
dosage: "5mg"
}
],

appointments: [
{
id: SecureRandom.uuid,
scheduled_date: "2024-02-01",
status: "scheduled"
}
],

encounters: [
{
id: SecureRandom.uuid,
notes: "Follow-up visit"
}
]
}
]
}
end

it "creates a legacy data dump successfully" do
expect {
post "/api/v4/legacy_data_dumps",
params: valid_payload.to_json,
headers: headers
}.to change(LegacyMobileDataDump, :count).by(1)

expect(response).to have_http_status(:ok)

json = JSON.parse(response.body)
expect(json["status"]).to eq("ok")
expect(json["id"]).to be_present
end

it "stores the raw payload with nested legacy data per patient" do
post "/api/v4/legacy_data_dumps",
params: valid_payload.to_json,
headers: headers

dump = LegacyMobileDataDump.last
expect(dump.raw_payload["patients"]).to be_present

patient = dump.raw_payload["patients"].first

expect(patient["medical_histories"]).to be_present
expect(patient["blood_pressures"]).to be_present
expect(patient["blood_sugars"]).to be_present
expect(patient["prescription_drugs"]).to be_present
expect(patient["appointments"]).to be_present
expect(patient["encounters"]).to be_present
end

it "records the user who made the dump" do
post "/api/v4/legacy_data_dumps",
params: valid_payload.to_json,
headers: headers

dump = LegacyMobileDataDump.last
expect(dump.user).to eq(request_user)
end

it "records the mobile version from headers" do
post "/api/v4/legacy_data_dumps",
params: valid_payload.to_json,
headers: headers

dump = LegacyMobileDataDump.last
expect(dump.mobile_version).to eq("2024.1.0")
end

it "records the dump date" do
freeze_time do
post "/api/v4/legacy_data_dumps",
params: valid_payload.to_json,
headers: headers

dump = LegacyMobileDataDump.last
expect(dump.dump_date).to be_within(1.second).of(Time.current)
end
end

it "returns unauthorized with invalid token" do
invalid_headers = headers.merge("HTTP_AUTHORIZATION" => "Bearer invalid_token")

post "/api/v4/legacy_data_dumps",
params: valid_payload.to_json,
headers: invalid_headers

expect(response).to have_http_status(:unauthorized)
end

it "returns bad request without facility id" do
invalid_headers = headers.except("HTTP_X_FACILITY_ID")

post "/api/v4/legacy_data_dumps",
params: valid_payload.to_json,
headers: invalid_headers

expect(response).to have_http_status(:bad_request)
end
end
end