-
Notifications
You must be signed in to change notification settings - Fork 39
sc-17557 add historical sync APIs to accept invalid app data #5745
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
24fafa1
9d8b703
0848bef
1cfeb95
0afccfe
4df8c3a
70bc890
1693929
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| 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 |
| 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 | ||
| end | ||
| 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 |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -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: - | ||
| -- | ||
|
|
@@ -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: - | ||
| -- | ||
|
|
@@ -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); | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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: - | ||
| -- | ||
|
|
@@ -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: - | ||
| -- | ||
|
|
@@ -8597,5 +8642,5 @@ INSERT INTO "schema_migrations" (version) VALUES | |
| ('20250924102156'), | ||
| ('20250925094123'), | ||
| ('20251125090819'), | ||
| ('20251211073126'); | ||
|
|
||
| ('20251211073126'), | ||
| ('20260105123000'); | ||
| 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 |
There was a problem hiding this comment.
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 ?