From 24fafa185965be7979e21d1958abc8862fd7b4ed Mon Sep 17 00:00:00 2001 From: Gyan Gupta Date: Mon, 5 Jan 2026 17:41:15 +0530 Subject: [PATCH 1/8] Remove all changes from PR From 9d8b7034b64a93eaac5ac49abc8ab6fc9e633836 Mon Sep 17 00:00:00 2001 From: Gyan Gupta Date: Tue, 6 Jan 2026 01:04:04 +0530 Subject: [PATCH 2/8] sc-17557 add legacy data dump api --- .../api/v3/legacy_data_dumps_controller.rb | 60 +++++++ app/models/legacy_mobile_data_dump.rb | 6 + config/routes.rb | 2 + ...5123000_create_legacy_mobile_data_dumps.rb | 14 ++ db/structure.sql | 49 +++++- .../requests/api/v3/legacy_data_dumps_spec.rb | 160 ++++++++++++++++++ 6 files changed, 289 insertions(+), 2 deletions(-) create mode 100644 app/controllers/api/v3/legacy_data_dumps_controller.rb create mode 100644 app/models/legacy_mobile_data_dump.rb create mode 100644 db/migrate/20260105123000_create_legacy_mobile_data_dumps.rb create mode 100644 spec/requests/api/v3/legacy_data_dumps_spec.rb diff --git a/app/controllers/api/v3/legacy_data_dumps_controller.rb b/app/controllers/api/v3/legacy_data_dumps_controller.rb new file mode 100644 index 0000000000..07250d0beb --- /dev/null +++ b/app/controllers/api/v3/legacy_data_dumps_controller.rb @@ -0,0 +1,60 @@ +class Api::V3::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, + user_id: user_id, + mobile_version: mobile_version + } + end + + def raw_payload + params.require(:legacy_data_dump).to_unsafe_h + end + + def user_id + params.dig(:legacy_data_dump, :user) || + request.headers["X-USER-ID"] + end + + def mobile_version + params.dig(:legacy_data_dump, :mobile_version) || + request.headers["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 diff --git a/app/models/legacy_mobile_data_dump.rb b/app/models/legacy_mobile_data_dump.rb new file mode 100644 index 0000000000..01c6587156 --- /dev/null +++ b/app/models/legacy_mobile_data_dump.rb @@ -0,0 +1,6 @@ +class LegacyMobileDataDump < ActiveRecord::Base + belongs_to :user + + validates :raw_payload, presence: true + validates :dump_date, presence: true +end diff --git a/config/routes.rb b/config/routes.rb index 86a5e0e938..cc03a56a04 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -123,6 +123,8 @@ namespace :analytics do resource :user_analytics, only: [:show] end + + resources :legacy_data_dumps, only: [:create] end namespace :v4, path: "v4" do diff --git a/db/migrate/20260105123000_create_legacy_mobile_data_dumps.rb b/db/migrate/20260105123000_create_legacy_mobile_data_dumps.rb new file mode 100644 index 0000000000..04cbed5b26 --- /dev/null +++ b/db/migrate/20260105123000_create_legacy_mobile_data_dumps.rb @@ -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 \ No newline at end of file diff --git a/db/structure.sql b/db/structure.sql index 0ff07a878e..2025d0451e 100644 --- a/db/structure.sql +++ b/db/structure.sql @@ -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); + + -- -- 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'); diff --git a/spec/requests/api/v3/legacy_data_dumps_spec.rb b/spec/requests/api/v3/legacy_data_dumps_spec.rb new file mode 100644 index 0000000000..4d89e3d3fb --- /dev/null +++ b/spec/requests/api/v3/legacy_data_dumps_spec.rb @@ -0,0 +1,160 @@ +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/v3/legacy_data_dumps" do + let(:valid_payload) do + { + legacy_data_dump: { + patients: [ + { + id: SecureRandom.uuid, + full_name: "Test Patient", + age: 45, + gender: "male", + status: "active" + } + ], + medical_histories: [ + { + id: SecureRandom.uuid, + patient_id: SecureRandom.uuid, + prior_heart_attack: "yes", + diabetes: "unknown" + } + ], + blood_pressures: [ + { + id: SecureRandom.uuid, + patient_id: SecureRandom.uuid, + systolic: 140, + diastolic: 90 + } + ], + blood_sugars: [ + { + id: SecureRandom.uuid, + patient_id: SecureRandom.uuid, + blood_sugar_type: "random", + blood_sugar_value: 180 + } + ], + prescription_drugs: [ + { + id: SecureRandom.uuid, + patient_id: SecureRandom.uuid, + name: "Amlodipine", + dosage: "5mg" + } + ], + appointments: [ + { + id: SecureRandom.uuid, + patient_id: SecureRandom.uuid, + scheduled_date: "2024-02-01", + status: "scheduled" + } + ], + encounters: [ + { + id: SecureRandom.uuid, + patient_id: SecureRandom.uuid, + notes: "Follow-up visit" + } + ] + } + } + end + + it "creates a legacy data dump successfully" do + expect { + post "/api/v3/legacy_data_dumps", + params: valid_payload.to_json, + headers: headers + }.to change(LegacyMobileDataDump, :count).by(1) + + expect(response).to have_http_status(:ok) + json_response = JSON.parse(response.body) + expect(json_response["status"]).to eq("ok") + expect(json_response["id"]).to be_present + end + + it "stores the raw payload with all legacy data" do + post "/api/v3/legacy_data_dumps", + params: valid_payload.to_json, + headers: headers + + dump = LegacyMobileDataDump.last + + expect(dump.raw_payload["patients"]).to be_present + expect(dump.raw_payload["medical_histories"]).to be_present + expect(dump.raw_payload["blood_pressures"]).to be_present + expect(dump.raw_payload["blood_sugars"]).to be_present + expect(dump.raw_payload["prescription_drugs"]).to be_present + expect(dump.raw_payload["appointments"]).to be_present + expect(dump.raw_payload["encounters"]).to be_present + end + + it "records the user who made the dump" do + post "/api/v3/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/v3/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/v3/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/v3/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/v3/legacy_data_dumps", + params: valid_payload.to_json, + headers: invalid_headers + + expect(response).to have_http_status(:bad_request) + end + end +end From 0848bef0f374e9f10c3eef83e667acf54fc4ca62 Mon Sep 17 00:00:00 2001 From: Gyan Gupta Date: Tue, 6 Jan 2026 01:14:09 +0530 Subject: [PATCH 3/8] lint changes --- .../api/v3/legacy_data_dumps_controller.rb | 4 +-- ...5123000_create_legacy_mobile_data_dumps.rb | 2 +- .../requests/api/v3/legacy_data_dumps_spec.rb | 28 +++++++++---------- 3 files changed, 17 insertions(+), 17 deletions(-) diff --git a/app/controllers/api/v3/legacy_data_dumps_controller.rb b/app/controllers/api/v3/legacy_data_dumps_controller.rb index 07250d0beb..5858788601 100644 --- a/app/controllers/api/v3/legacy_data_dumps_controller.rb +++ b/app/controllers/api/v3/legacy_data_dumps_controller.rb @@ -5,11 +5,11 @@ def create if legacy_dump.save log_success(legacy_dump) - render json: { id: legacy_dump.id, status: "ok" }, status: :ok + 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 + render json: {errors: legacy_dump.errors.full_messages}, status: :unprocessable_entity end end diff --git a/db/migrate/20260105123000_create_legacy_mobile_data_dumps.rb b/db/migrate/20260105123000_create_legacy_mobile_data_dumps.rb index 04cbed5b26..67b613f143 100644 --- a/db/migrate/20260105123000_create_legacy_mobile_data_dumps.rb +++ b/db/migrate/20260105123000_create_legacy_mobile_data_dumps.rb @@ -11,4 +11,4 @@ def change add_index :legacy_mobile_data_dumps, :dump_date end -end \ No newline at end of file +end diff --git a/spec/requests/api/v3/legacy_data_dumps_spec.rb b/spec/requests/api/v3/legacy_data_dumps_spec.rb index 4d89e3d3fb..3c871d17ee 100644 --- a/spec/requests/api/v3/legacy_data_dumps_spec.rb +++ b/spec/requests/api/v3/legacy_data_dumps_spec.rb @@ -82,8 +82,8 @@ it "creates a legacy data dump successfully" do expect { post "/api/v3/legacy_data_dumps", - params: valid_payload.to_json, - headers: headers + params: valid_payload.to_json, + headers: headers }.to change(LegacyMobileDataDump, :count).by(1) expect(response).to have_http_status(:ok) @@ -94,8 +94,8 @@ it "stores the raw payload with all legacy data" do post "/api/v3/legacy_data_dumps", - params: valid_payload.to_json, - headers: headers + params: valid_payload.to_json, + headers: headers dump = LegacyMobileDataDump.last @@ -110,8 +110,8 @@ it "records the user who made the dump" do post "/api/v3/legacy_data_dumps", - params: valid_payload.to_json, - headers: headers + params: valid_payload.to_json, + headers: headers dump = LegacyMobileDataDump.last expect(dump.user).to eq(request_user) @@ -119,8 +119,8 @@ it "records the mobile version from headers" do post "/api/v3/legacy_data_dumps", - params: valid_payload.to_json, - headers: headers + params: valid_payload.to_json, + headers: headers dump = LegacyMobileDataDump.last expect(dump.mobile_version).to eq("2024.1.0") @@ -129,8 +129,8 @@ it "records the dump date" do freeze_time do post "/api/v3/legacy_data_dumps", - params: valid_payload.to_json, - headers: headers + params: valid_payload.to_json, + headers: headers dump = LegacyMobileDataDump.last expect(dump.dump_date).to be_within(1.second).of(Time.current) @@ -141,8 +141,8 @@ invalid_headers = headers.merge("HTTP_AUTHORIZATION" => "Bearer invalid_token") post "/api/v3/legacy_data_dumps", - params: valid_payload.to_json, - headers: invalid_headers + params: valid_payload.to_json, + headers: invalid_headers expect(response).to have_http_status(:unauthorized) end @@ -151,8 +151,8 @@ invalid_headers = headers.except("HTTP_X_FACILITY_ID") post "/api/v3/legacy_data_dumps", - params: valid_payload.to_json, - headers: invalid_headers + params: valid_payload.to_json, + headers: invalid_headers expect(response).to have_http_status(:bad_request) end From 1cfeb9543d8698900c5016d1d8aa3ae3d30e035e Mon Sep 17 00:00:00 2001 From: Gyan Gupta Date: Tue, 6 Jan 2026 01:32:40 +0530 Subject: [PATCH 4/8] minor changes --- app/controllers/api/v3/legacy_data_dumps_controller.rb | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/app/controllers/api/v3/legacy_data_dumps_controller.rb b/app/controllers/api/v3/legacy_data_dumps_controller.rb index 5858788601..9d751f7c5e 100644 --- a/app/controllers/api/v3/legacy_data_dumps_controller.rb +++ b/app/controllers/api/v3/legacy_data_dumps_controller.rb @@ -19,7 +19,7 @@ def legacy_dump_params { raw_payload: raw_payload, dump_date: Time.current, - user_id: user_id, + user: current_user, mobile_version: mobile_version } end @@ -28,14 +28,8 @@ def raw_payload params.require(:legacy_data_dump).to_unsafe_h end - def user_id - params.dig(:legacy_data_dump, :user) || - request.headers["X-USER-ID"] - end - def mobile_version - params.dig(:legacy_data_dump, :mobile_version) || - request.headers["X-APP-VERSION"] + request.headers["HTTP_X_APP_VERSION"] end def log_success(legacy_dump) From 0afccfe6e371a8d2b7c08f9092f9c63ef0eacc69 Mon Sep 17 00:00:00 2001 From: Gyan Gupta Date: Tue, 6 Jan 2026 12:59:50 +0530 Subject: [PATCH 5/8] sc-17557 align legacy data dump payload with patient-nested structure (v4) --- .../legacy_data_dumps_controller.rb | 6 +- app/models/legacy_mobile_data_dump.rb | 1 + config/routes.rb | 4 +- ...5123000_create_legacy_mobile_data_dumps.rb | 1 + .../requests/api/v4/legacy_data_dumps_spec.rb | 161 ++++++++++++++++++ 5 files changed, 168 insertions(+), 5 deletions(-) rename app/controllers/api/{v3 => v4}/legacy_data_dumps_controller.rb (89%) create mode 100644 spec/requests/api/v4/legacy_data_dumps_spec.rb diff --git a/app/controllers/api/v3/legacy_data_dumps_controller.rb b/app/controllers/api/v4/legacy_data_dumps_controller.rb similarity index 89% rename from app/controllers/api/v3/legacy_data_dumps_controller.rb rename to app/controllers/api/v4/legacy_data_dumps_controller.rb index 9d751f7c5e..c54bb22709 100644 --- a/app/controllers/api/v3/legacy_data_dumps_controller.rb +++ b/app/controllers/api/v4/legacy_data_dumps_controller.rb @@ -1,4 +1,4 @@ -class Api::V3::LegacyDataDumpsController < APIController +class Api::V4::LegacyDataDumpsController < APIController def create legacy_dump = LegacyMobileDataDump.new(legacy_dump_params) @@ -18,14 +18,14 @@ def create def legacy_dump_params { raw_payload: raw_payload, - dump_date: Time.current, + dump_date: Time.current.utc, user: current_user, mobile_version: mobile_version } end def raw_payload - params.require(:legacy_data_dump).to_unsafe_h + params.to_unsafe_h end def mobile_version diff --git a/app/models/legacy_mobile_data_dump.rb b/app/models/legacy_mobile_data_dump.rb index 01c6587156..c6b247d97e 100644 --- a/app/models/legacy_mobile_data_dump.rb +++ b/app/models/legacy_mobile_data_dump.rb @@ -1,6 +1,7 @@ class LegacyMobileDataDump < ActiveRecord::Base belongs_to :user + validates :user, presence: true validates :raw_payload, presence: true validates :dump_date, presence: true end diff --git a/config/routes.rb b/config/routes.rb index cc03a56a04..d48555010f 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -123,13 +123,13 @@ namespace :analytics do resource :user_analytics, only: [:show] end - - resources :legacy_data_dumps, only: [:create] end 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" diff --git a/db/migrate/20260105123000_create_legacy_mobile_data_dumps.rb b/db/migrate/20260105123000_create_legacy_mobile_data_dumps.rb index 67b613f143..4c156acb4c 100644 --- a/db/migrate/20260105123000_create_legacy_mobile_data_dumps.rb +++ b/db/migrate/20260105123000_create_legacy_mobile_data_dumps.rb @@ -10,5 +10,6 @@ def change end add_index :legacy_mobile_data_dumps, :dump_date + add_index :legacy_mobile_data_dumps, :user_id end end diff --git a/spec/requests/api/v4/legacy_data_dumps_spec.rb b/spec/requests/api/v4/legacy_data_dumps_spec.rb new file mode 100644 index 0000000000..f10959e82f --- /dev/null +++ b/spec/requests/api/v4/legacy_data_dumps_spec.rb @@ -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 From 4df8c3a0f10d615f4c14c863e033f2de23f3a0db Mon Sep 17 00:00:00 2001 From: Gyan Gupta Date: Tue, 6 Jan 2026 13:06:55 +0530 Subject: [PATCH 6/8] minior changes --- db/migrate/20260105123000_create_legacy_mobile_data_dumps.rb | 1 - 1 file changed, 1 deletion(-) diff --git a/db/migrate/20260105123000_create_legacy_mobile_data_dumps.rb b/db/migrate/20260105123000_create_legacy_mobile_data_dumps.rb index 4c156acb4c..67b613f143 100644 --- a/db/migrate/20260105123000_create_legacy_mobile_data_dumps.rb +++ b/db/migrate/20260105123000_create_legacy_mobile_data_dumps.rb @@ -10,6 +10,5 @@ def change end add_index :legacy_mobile_data_dumps, :dump_date - add_index :legacy_mobile_data_dumps, :user_id end end From 70bc890449744264dbad4da221cc4583e9a00ab0 Mon Sep 17 00:00:00 2001 From: Gyan Gupta Date: Tue, 6 Jan 2026 13:22:08 +0530 Subject: [PATCH 7/8] minor changes --- app/controllers/api/v4/legacy_data_dumps_controller.rb | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/app/controllers/api/v4/legacy_data_dumps_controller.rb b/app/controllers/api/v4/legacy_data_dumps_controller.rb index c54bb22709..a2ef68c4e0 100644 --- a/app/controllers/api/v4/legacy_data_dumps_controller.rb +++ b/app/controllers/api/v4/legacy_data_dumps_controller.rb @@ -25,7 +25,9 @@ def legacy_dump_params end def raw_payload - params.to_unsafe_h + { + "patients" => params.require(:patients) + } end def mobile_version From 16939291e0e20d7d53c3f1ff3bf8711bca740d0e Mon Sep 17 00:00:00 2001 From: Gyan Gupta Date: Tue, 6 Jan 2026 13:25:39 +0530 Subject: [PATCH 8/8] lint changes --- .../requests/api/v3/legacy_data_dumps_spec.rb | 160 ------------------ 1 file changed, 160 deletions(-) delete mode 100644 spec/requests/api/v3/legacy_data_dumps_spec.rb diff --git a/spec/requests/api/v3/legacy_data_dumps_spec.rb b/spec/requests/api/v3/legacy_data_dumps_spec.rb deleted file mode 100644 index 3c871d17ee..0000000000 --- a/spec/requests/api/v3/legacy_data_dumps_spec.rb +++ /dev/null @@ -1,160 +0,0 @@ -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/v3/legacy_data_dumps" do - let(:valid_payload) do - { - legacy_data_dump: { - patients: [ - { - id: SecureRandom.uuid, - full_name: "Test Patient", - age: 45, - gender: "male", - status: "active" - } - ], - medical_histories: [ - { - id: SecureRandom.uuid, - patient_id: SecureRandom.uuid, - prior_heart_attack: "yes", - diabetes: "unknown" - } - ], - blood_pressures: [ - { - id: SecureRandom.uuid, - patient_id: SecureRandom.uuid, - systolic: 140, - diastolic: 90 - } - ], - blood_sugars: [ - { - id: SecureRandom.uuid, - patient_id: SecureRandom.uuid, - blood_sugar_type: "random", - blood_sugar_value: 180 - } - ], - prescription_drugs: [ - { - id: SecureRandom.uuid, - patient_id: SecureRandom.uuid, - name: "Amlodipine", - dosage: "5mg" - } - ], - appointments: [ - { - id: SecureRandom.uuid, - patient_id: SecureRandom.uuid, - scheduled_date: "2024-02-01", - status: "scheduled" - } - ], - encounters: [ - { - id: SecureRandom.uuid, - patient_id: SecureRandom.uuid, - notes: "Follow-up visit" - } - ] - } - } - end - - it "creates a legacy data dump successfully" do - expect { - post "/api/v3/legacy_data_dumps", - params: valid_payload.to_json, - headers: headers - }.to change(LegacyMobileDataDump, :count).by(1) - - expect(response).to have_http_status(:ok) - json_response = JSON.parse(response.body) - expect(json_response["status"]).to eq("ok") - expect(json_response["id"]).to be_present - end - - it "stores the raw payload with all legacy data" do - post "/api/v3/legacy_data_dumps", - params: valid_payload.to_json, - headers: headers - - dump = LegacyMobileDataDump.last - - expect(dump.raw_payload["patients"]).to be_present - expect(dump.raw_payload["medical_histories"]).to be_present - expect(dump.raw_payload["blood_pressures"]).to be_present - expect(dump.raw_payload["blood_sugars"]).to be_present - expect(dump.raw_payload["prescription_drugs"]).to be_present - expect(dump.raw_payload["appointments"]).to be_present - expect(dump.raw_payload["encounters"]).to be_present - end - - it "records the user who made the dump" do - post "/api/v3/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/v3/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/v3/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/v3/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/v3/legacy_data_dumps", - params: valid_payload.to_json, - headers: invalid_headers - - expect(response).to have_http_status(:bad_request) - end - end -end