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
2 changes: 1 addition & 1 deletion CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ CI (`.github/workflows/{backend,frontend,native}.yml`) uses path filters — bac
The backend uses **both PostgreSQL and MongoDB simultaneously**, split by data type:

- **PostgreSQL (ActiveRecord)** — relational/reference data: `User` (Devise auth), `Condition`, `Symptom`, `Treatment`, `Food`, `Tag`, `Profile`, `Weather`, and the `user_*` join tables. These models subclass `ActiveRecord::Base` and carry a `# == Schema Information` header. Schema lives in `db/schema.rb` + `db/structure.sql`; migrations in `db/migrate/`.
- **MongoDB (Mongoid 8)** — high-volume, user-generated, schemaless data: `Checkin` (the core daily symptom/treatment/tag log), `Comment`, `Reaction`, `Pattern`, `Notification`, `HarveyBradshawIndex`, `Feedback`, `PromotionRate`, `OracleRequest`. These `include Mongoid::Document`. Config in `config/mongoid.yml`.
- **MongoDB (Mongoid 9)** — high-volume, user-generated, schemaless data: `Checkin` (the core daily symptom/treatment/tag log), `Comment`, `Reaction`, `Pattern`, `Notification`, `HarveyBradshawIndex`, `Feedback`, `PromotionRate`, `OracleRequest`. These `include Mongoid::Document`. Config in `config/mongoid.yml`.

The two stores are linked by an **encrypted foreign key**: Mongo documents store `encrypted_user_id` (symmetric-encryption gem, see `config/symmetric-encryption.yml`) rather than a plain `user_id`, and dereference it back to the Postgres `User`. When querying check-in data by user, filter on `encrypted_user_id`, not `user_id`. `Checkin` embeds condition/symptom/treatment sub-documents inline.

Expand Down
2 changes: 1 addition & 1 deletion backend/Gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ gem "sprockets-rails"
gem "active_model_serializers", "0.9.8"

# Use postgresql and mongo as the database for Active Record
gem "mongoid", "8.1.3" # https://www.mongodb.com/docs/mongoid/current/reference/compatibility/#rails-compatibility
gem "mongoid", "9.0.11" # https://www.mongodb.com/docs/mongoid/current/reference/compatibility/#rails-compatibility
gem "pg"

# Use Puma as the app server
Expand Down
16 changes: 9 additions & 7 deletions backend/Gemfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -106,11 +106,11 @@ GEM
bigdecimal (4.1.2)
brakeman (6.1.2)
racc
bson (4.15.0)
bson (5.2.0)
bugsnag (6.27.1)
concurrent-ruby (~> 1.0)
builder (3.3.0)
bullet (7.2.0)
bullet (8.2.0)
activesupport (>= 3.0.0)
uniform_notifier (~> 1.11)
byebug (11.1.3)
Expand Down Expand Up @@ -265,13 +265,14 @@ GEM
minitest (6.0.6)
drb (~> 2.0)
prism (~> 1.5)
mongo (2.20.1)
mongo (2.25.0)
base64
bson (>= 4.14.1, < 6.0.0)
mongoid (8.1.3)
activemodel (>= 5.1, < 7.2, != 7.0.0)
mongoid (9.0.11)
activemodel (>= 5.1, < 8.2, != 7.0.0)
concurrent-ruby (>= 1.0.5, < 2.0)
mongo (>= 2.18.0, < 3.0.0)
ruby2_keywords (~> 0.0.5)
ostruct
mongoid-compatibility (0.6.0)
activesupport
mongoid (>= 2.0)
Expand Down Expand Up @@ -314,6 +315,7 @@ GEM
oauth2 (~> 1.1)
omniauth (~> 1.2)
orm_adapter (0.5.0)
ostruct (0.6.3)
parallel (1.24.0)
parser (3.3.0.5)
ast (~> 2.4.1)
Expand Down Expand Up @@ -542,7 +544,7 @@ DEPENDENCIES
kaminari-actionview
kaminari-mongoid
letter_opener
mongoid (= 8.1.3)
mongoid (= 9.0.11)
mongoid-rspec
nearest_time_zone
omniauth (~> 1.8)
Expand Down
20 changes: 20 additions & 0 deletions backend/spec/models/checkin_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -15,4 +15,24 @@
it { is_expected.to validate_presence_of(:date) }
it { is_expected.to validate_presence_of(:encrypted_user_id) }
end

describe "encrypted_user_id" do
let(:user) { create(:user) }
subject(:checkin) { create(:checkin, user_id: user.id) }

it "persists the user id encrypted rather than in the clear" do
expect(checkin.encrypted_user_id).to be_present
expect(checkin.encrypted_user_id).not_to eq user.id.to_s
end

it "round-trips back to the Postgres user" do
expect(checkin.user_id).to eq user.id
expect(checkin.user).to eq user
end

it "is queryable by the encrypted value" do
checkin
expect(Checkin.where(encrypted_user_id: user.encrypted_id).to_a).to eq [checkin]
end
end
end
Loading