Skip to content

feat: add database migration system for schema changes#423

Merged
roshankumar0036singh merged 29 commits into
roshankumar0036singh:mainfrom
rahul616sama:main
May 31, 2026
Merged

feat: add database migration system for schema changes#423
roshankumar0036singh merged 29 commits into
roshankumar0036singh:mainfrom
rahul616sama:main

Conversation

@rahul616sama
Copy link
Copy Markdown
Contributor

@rahul616sama rahul616sama commented May 30, 2026

Closes #339

What changed

  • Created cloud-functions/migrations/ directory
  • Created cloud-functions/migrations/migrate.ts — a migration runner that finds and runs all pending migrations in order
  • Created cloud-functions/migrations/001_add_feedbackRequestSent_to_events.ts — sample migration that adds feedbackRequestSent: false to events missing the field
  • Added npm run migrate script to cloud-functions/package.json

How it works

  1. Run npm run migrate from the cloud-functions/ folder
  2. It reads all .ts files in the migrations/ folder sorted by name
  3. It checks Firestore (migrations/applied) to see which ones already ran
  4. It runs only the new ones and marks them as done
  5. Same migration never runs twice

Summary by CodeRabbit

  • Bug Fixes

    • Package configuration contains an unresolved merge conflict that requires manual resolution.
  • New Features

    • Migrations add a missing feedback flag to existing event records with safe batched updates.
    • Migration runner now claims work atomically and supports rolling back the most recent migration.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 30, 2026

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 4cb3b65a-0c1b-4846-a6b2-4204a677f02b

📥 Commits

Reviewing files that changed from the base of the PR and between 1b77988 and 562ced2.

📒 Files selected for processing (1)
  • cloud-functions/migrations/migrate.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • cloud-functions/migrations/migrate.ts

📝 Walkthrough

Walkthrough

Adds a transactional Firestore migration runner with CLI forward/rollback paths and dynamic imports, implements a migration to backfill/remove events.feedbackRequestSent, and leaves a blocking unresolved Git merge conflict in package.json.

Changes

Unresolved merge conflict (BLOCKING)

Layer / File(s) Summary
Merge conflict markers in package.json
package.json
Git conflict markers (<<<<<<< / ======= / >>>>>>>) wrap incompatible scripts and devDependencies blocks; the file is not valid JSON and must be resolved before merge.

Firestore migration infrastructure and first migration

Layer / File(s) Summary
Migration runner with transaction-based state tracking
cloud-functions/migrations/migrate.ts
Initializes Firebase Admin and Firestore, reads tracker document, provides claimMigration() / unclaimMigration() transactions, discovers/sorts *.ts migration files, dynamically imports migration modules, runs migration.up(db) for new migrations, supports down rollback by importing last applied migration and running migration.down(db).
First migration: backfill feedbackRequestSent field
cloud-functions/migrations/001_add_feedbackRequestSent_to_events.ts
Adds exported up(db) to set feedbackRequestSent=false on events missing the field (batched commits, 500 docs per batch) and exported down(db) to remove feedbackRequestSent from events using FieldValue.delete() with identical batching; both log totals.

Sequence Diagram

sequenceDiagram
  participant CLI as migrate.ts
  participant Firestore
  participant MigrationModule
  CLI->>Firestore: getAppliedMigrations()
  Firestore-->>CLI: applied[]
  CLI->>CLI: list and sort migration files
  CLI->>Firestore: claimMigration(name) [transaction]
  Firestore-->>CLI: claimed=true
  CLI->>MigrationModule: import migration module
  CLI->>MigrationModule: migration.up(db) / migration.down(db)
  MigrationModule->>Firestore: read/write batches
  MigrationModule-->>CLI: complete
  CLI->>Firestore: unclaimMigration(name) [transaction] (on rollback or failure)
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Suggested labels

level:intermediate, type:refactor, quality:clean

Suggested reviewers

  • roshankumar0036singh

Poem

🐰 A rabbit hops through Firestore's glen,
claiming migrations, then back again,
batches of five hundred leap with care,
fields set, then removed — all checked and fair,
a lone conflict waits for a human's den.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Out of Scope Changes check ⚠️ Warning The unresolved merge conflict markers in package.json appear to be an incomplete fix noted in PR comments. The conflict should have been fully resolved before merging. Resolve the package.json merge conflict by choosing one version or manually merging the scripts and devDependencies sections, then remove all Git conflict markers.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The PR title 'feat: add database migration system for schema changes' directly and clearly describes the main change—adding a complete migration system. It matches the core objective.
Linked Issues check ✅ Passed All coding requirements from issue #339 are met: migrations directory created, timestamped migration files added, runner implemented with npm script, Firestore-based tracking via transactions, and rollback support included.
Docstring Coverage ✅ Passed Docstring coverage is 85.71% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@rahul616sama
Copy link
Copy Markdown
Contributor Author

@roshankumar0036singh All 5 checks passing... Ready to merge

@roshankumar0036singh
Copy link
Copy Markdown
Owner

@rahul616sama package.json has an unresolved merge conflict here

@roshankumar0036singh
Copy link
Copy Markdown
Owner

flip the sequence here await
markAsDone(file, applied);
applied.push(file);

to
applied.push(file);
markAsDone(file, applied);

@roshankumar0036singh
Copy link
Copy Markdown
Owner

On migrate.ts — missing down()

@roshankumar0036singh
Copy link
Copy Markdown
Owner

@rahul616sama On 001_add_feedbackRequestSent_to_events.ts — intorudce batching as it is slow for writing oen at ta time

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
package.json (1)

2-45: ⚠️ Potential issue | 🔴 Critical | ⚡ Quick win

Resolve merge markers; package.json is currently invalid JSON and blocks install/CI.

<<<<<<<, =======, >>>>>>> markers (Line 2, Line 15, Line 45) make this file unparsable. This will break root install/lockfile update and any tooling depending on root dependencies.

Proposed reconciliation (remove conflict markers, keep merged content)
 {
-<<<<<<< HEAD
-    "scripts": {
-        "build": "tsc -p cloud-functions/tsconfig.json",
-        "test:rules": "firebase emulators:exec --only firestore \"jest tests/firestore.rules.test.ts --runInBand\""
-    },
-    "devDependencies": {
-        "`@firebase/rules-unit-testing`": "^5.0.1",
-        "`@types/jest`": "^30.0.0",
-        "`@types/node`": "^25.8.0",
-        "firebase-tools": "^15.18.0",
-        "jest": "^30.4.2",
-        "ts-jest": "^29.4.11"
-    }
-=======
   "scripts": {
     "build": "tsc -p cloud-functions/tsconfig.json",
     "test:rules": "firebase emulators:exec --only firestore \"jest tests/firestore.rules.test.ts --runInBand\"",
     "prepare": "husky"
@@
   "lint-staged": {
@@
   }
->>>>>>> upstream/main
 }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@package.json` around lines 2 - 45, The package.json contains unresolved merge
markers (<<<<<<<, =======, >>>>>>>) making it invalid; remove those markers and
reconcile the contents by keeping the merged block that includes the complete
"scripts" (build, test:rules, prepare), full "devDependencies" (including
eslint, husky, lint-staged, prettier), the "dependencies" section
(expo-clipboard), and the "lint-staged" configuration—ensuring keys "scripts",
"devDependencies", "dependencies", and "lint-staged" are present exactly once
and the file is valid JSON.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@cloud-functions/migrations/001_add_feedbackRequestSent_to_events.ts`:
- Around line 13-25: The migration currently updates documents one-by-one using
eventDoc.ref.update inside the for-loop (iterating eventsSnap.docs) which is
slow and risky; change it to use Firestore BulkWriter (or WriteBatch in
500-write chunks) to perform batched writes: create a BulkWriter instance (or
accumulate WriteBatch objects up to 500 writes), call update on eventDoc.ref for
documents missing feedbackRequestSent, and ensure you await writer.close() (or
commit all batches) at the end; keep the same predicate
(data.feedbackRequestSent === undefined) and preserve the console log behavior
for updated docs (use writer callbacks or log after successful commits).

In `@cloud-functions/migrations/migrate.ts`:
- Around line 56-57: The migration name is being added twice because
applied.push(file) runs locally and markAsDone(file, applied) also appends
migrationName before saving; remove the local duplicate by deleting the
applied.push(file) call (or alternatively change markAsDone to accept the
already-updated applied array and not append internally). Locate the call to
applied.push(file) and either remove it so markAsDone(file, applied) is the sole
appender, or update markAsDone(file, applied) to set the applied array instead
of pushing, ensuring only one append occurs.
- Around line 42-57: The migration runner currently does a non-atomic
check/update (applied includes -> migration.up -> applied.push -> markAsDone) so
concurrent processes can both run the same migration or re-apply side effects if
markAsDone fails; replace that flow for each file by implementing a
per-migration Firestore claim using a transaction: attempt to atomically
create/claim a migration doc (e.g., set a "claimedBy"/"claimedAt" field) inside
a transaction and abort if the doc already indicates claimed/completed, then run
migration.up(db) only after the claim succeeds, and finally update the migration
doc in a second transaction (or within the same transaction if possible) to mark
it completed (set "done"/"appliedAt" and remove claim), ensuring you do not use
DocumentReference.create() and instead use
transaction.get/transaction.set/transaction.update to guarantee atomicity and
avoid the known firebase-admin create() retry/hang issue; ensure failures during
mark-as-done do not leave side effects untracked by using the claim/complete
state to retry or compensate.

---

Outside diff comments:
In `@package.json`:
- Around line 2-45: The package.json contains unresolved merge markers (<<<<<<<,
=======, >>>>>>>) making it invalid; remove those markers and reconcile the
contents by keeping the merged block that includes the complete "scripts"
(build, test:rules, prepare), full "devDependencies" (including eslint, husky,
lint-staged, prettier), the "dependencies" section (expo-clipboard), and the
"lint-staged" configuration—ensuring keys "scripts", "devDependencies",
"dependencies", and "lint-staged" are present exactly once and the file is valid
JSON.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 3e0e0ccd-9edc-4057-b463-c3f347f42b85

📥 Commits

Reviewing files that changed from the base of the PR and between 0b5273a and 98ddc5a.

📒 Files selected for processing (4)
  • cloud-functions/migrations/001_add_feedbackRequestSent_to_events.ts
  • cloud-functions/migrations/migrate.ts
  • cloud-functions/package.json
  • package.json

Comment thread cloud-functions/migrations/001_add_feedbackRequestSent_to_events.ts
Comment thread cloud-functions/migrations/migrate.ts Outdated
Comment thread cloud-functions/migrations/migrate.ts Outdated
@rahul616sama
Copy link
Copy Markdown
Contributor Author

@roshankumar0036singh

Fixed package.json merge conflict
Flipped sequence — applied.push(file) now happens before markAsDone
Added down() rollback function to migrate.ts and 001_add_feedbackRequestSent_to_events.ts
Added batching to migration 001 (500 writes per batch)
Fixed double-append bug using atomic Firestore transaction
Added concurrency protection via transaction-based claim

Ready for review

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
package.json (1)

2-46: ⚠️ Potential issue | 🔴 Critical | ⚡ Quick win

Resolve the Git merge conflict before merge.

The file contains unresolved Git merge conflict markers (<<<<<<< HEAD, =======, >>>>>>> upstream/main) that make it invalid JSON. This blocks the PR from merging and will break any npm operations, including the update-lockfile.yml workflow that runs npm install --package-lock-only when package.json changes.

Resolution guidance:

  1. Manually edit this file to remove the conflict markers
  2. Choose which version of scripts and devDependencies to keep (likely the upstream/main side, which includes the fuller tooling setup with husky, lint-staged, prettier, and eslint)
  3. Ensure the final JSON is valid and includes the @types/node version update to ^25.9.1
✅ Recommended resolution

Accept the upstream/main side (remove HEAD section and conflict markers):

 {
-<<<<<<< HEAD
-    "scripts": {
-        "build": "tsc -p cloud-functions/tsconfig.json",
-        "test:rules": "firebase emulators:exec --only firestore \"jest tests/firestore.rules.test.ts --runInBand\""
-    },
-    "devDependencies": {
-        "`@firebase/rules-unit-testing`": "^5.0.1",
-        "`@types/jest`": "^30.0.0",
-        "`@types/node`": "^25.8.0",
-        "firebase-tools": "^15.18.0",
-        "jest": "^30.4.2",
-        "ts-jest": "^29.4.11"
-    }
-=======
   "scripts": {
     "build": "tsc -p cloud-functions/tsconfig.json",
     "test:rules": "firebase emulators:exec --only firestore \"jest tests/firestore.rules.test.ts --runInBand\"",
     "prepare": "husky"
   },
   "devDependencies": {
     "`@firebase/rules-unit-testing`": "^5.0.1",
     "`@types/jest`": "^30.0.0",
     "`@types/node`": "^25.9.1",
     "eslint": "^10.4.0",
     "firebase-tools": "^15.18.0",
     "husky": "^9.1.7",
     "jest": "^30.4.2",
     "lint-staged": "^17.0.5",
     "prettier": "^3.8.3",
     "ts-jest": "^29.4.11"
   },
   "dependencies": {
     "expo-clipboard": "^56.0.3"
   },
   "lint-staged": {
     "*.{js,jsx,ts,tsx}": [
       "eslint --fix",
       "prettier --write"
     ],
     "*.{json,md,yml,yaml}": [
       "prettier --write"
     ]
   }
->>>>>>> upstream/main
 }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@package.json` around lines 2 - 46, This file has unresolved Git conflict
markers; remove the conflict block and produce a single valid JSON object
merging the intended fields: keep the upstream/main side for "scripts"
(including "prepare": "husky"), the fuller "devDependencies" (including eslint,
husky, lint-staged, prettier and `@types/node` ^25.9.1), add the "dependencies"
and "lint-staged" sections, and ensure "scripts" still contains "build" and
"test:rules"; then validate the resulting package.json parses as JSON (no
<<<<<<<, =======, >>>>>>> markers) and run a quick npm install to verify the
lockfile workflow will succeed.
🧹 Nitpick comments (1)
cloud-functions/migrations/001_add_feedbackRequestSent_to_events.ts (1)

38-56: 💤 Low value

Consider: down() removes the field from all events, not just those modified by up().

The up() function only adds feedbackRequestSent to documents where it's undefined, but down() unconditionally removes the field from all events. If any event had this field set before the migration (e.g., manually), the rollback would delete that data.

For this specific new field this is likely fine. For future migrations with potentially existing data, consider tracking modified document IDs or using a more surgical rollback approach.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@cloud-functions/migrations/001_add_feedbackRequestSent_to_events.ts` around
lines 38 - 56, The down() migration unconditionally deletes feedbackRequestSent
from all events; change it to only remove the field from the documents that were
modified by up() by tracking which docs were changed (e.g., record modified
document IDs when up() sets feedbackRequestSent or write a marker
collection/document listing those IDs) and then have down() read that list and
delete feedbackRequestSent only for those specific event docs (reference
functions: up(), down(), field name feedbackRequestSent, and the events
collection).
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@cloud-functions/migrations/migrate.ts`:
- Around line 64-74: The migration runner currently claims a migration via
claimMigration(file) then runs migration.up(db) but never releases the claim on
failure; wrap the import + await migration.up(db) call in a try/catch and if any
error is thrown call unclaimMigration(file) (or the appropriate function that
removes the file from the applied/claimed list), log or rethrow the error so the
process exits/fails as before, and ensure the claim is only kept when
migration.up completes successfully (references: claimMigration, migration.up,
and unclaimMigration).

---

Outside diff comments:
In `@package.json`:
- Around line 2-46: This file has unresolved Git conflict markers; remove the
conflict block and produce a single valid JSON object merging the intended
fields: keep the upstream/main side for "scripts" (including "prepare":
"husky"), the fuller "devDependencies" (including eslint, husky, lint-staged,
prettier and `@types/node` ^25.9.1), add the "dependencies" and "lint-staged"
sections, and ensure "scripts" still contains "build" and "test:rules"; then
validate the resulting package.json parses as JSON (no <<<<<<<, =======, >>>>>>>
markers) and run a quick npm install to verify the lockfile workflow will
succeed.

---

Nitpick comments:
In `@cloud-functions/migrations/001_add_feedbackRequestSent_to_events.ts`:
- Around line 38-56: The down() migration unconditionally deletes
feedbackRequestSent from all events; change it to only remove the field from the
documents that were modified by up() by tracking which docs were changed (e.g.,
record modified document IDs when up() sets feedbackRequestSent or write a
marker collection/document listing those IDs) and then have down() read that
list and delete feedbackRequestSent only for those specific event docs
(reference functions: up(), down(), field name feedbackRequestSent, and the
events collection).
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 3ad9715d-e7ed-40ba-81f9-981e7e9ff4da

📥 Commits

Reviewing files that changed from the base of the PR and between 98ddc5a and 1b77988.

📒 Files selected for processing (3)
  • cloud-functions/migrations/001_add_feedbackRequestSent_to_events.ts
  • cloud-functions/migrations/migrate.ts
  • package.json

Comment thread cloud-functions/migrations/migrate.ts Outdated
@sonarqubecloud
Copy link
Copy Markdown

@rahul616sama
Copy link
Copy Markdown
Contributor Author

@roshankumar0036singh ready for review

@roshankumar0036singh roshankumar0036singh merged commit 4577e6f into roshankumar0036singh:main May 31, 2026
6 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add database migration system for schema changes

2 participants