Skip to content

Conversation

@coodos
Copy link
Contributor

@coodos coodos commented Jan 6, 2026

Description of change

Issue Number

Type of change

  • Chore (refactoring, build scripts or anything else that isn't user-facing)

How the change has been tested

Change checklist

  • I have ensured that the CI Checks pass locally
  • I have removed any unnecessary logic
  • My code is well documented
  • I have signed my commits
  • My code follows the pattern of the application
  • I have self reviewed my code

Summary by CodeRabbit

  • New Features
    • Added a new synchronization utility that enables bulk syncing of user and group data with comprehensive error handling, progress tracking, and rate-limiting for safe and efficient data operations.

✏️ Tip: You can customize this high-level summary in your review settings.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Jan 6, 2026

📝 Walkthrough

Walkthrough

A new synchronization script is introduced that retrieves user and group documents from Firestore collections, processes them sequentially through an adapter with error handling and progress tracking, and outputs final statistics. A corresponding npm script entry is added to enable execution.

Changes

Cohort / File(s) Change Summary
Synchronization Script Setup
platforms/blabsy-w3ds-auth-api/package.json, platforms/blabsy-w3ds-auth-api/src/scripts/syncUsersAndGroups.ts
Adds npm script entry and implements new async sync function that initializes Firebase Admin SDK (with multiple fallback strategies), retrieves all documents from "users" and "groups" Firestore collections, processes each document through adapter.handleChange with per-item error handling, and tracks progress with statistics output. Includes configurable delays between operations and tolerant error handling for group collection retrieval.

Sequence Diagram

sequenceDiagram
    participant Script as syncUsersAndGroups()
    participant Firebase as Firebase Admin
    participant Firestore as Firestore
    participant Adapter as Adapter
    participant Logger as Logging/Output

    Script->>Firebase: Initialize Admin SDK<br/>(credentials fallback)
    activate Firebase
    Firebase-->>Script: Ready
    deactivate Firebase

    Script->>Firestore: Get users collection
    activate Firestore
    Firestore-->>Script: User documents
    deactivate Firestore
    
    rect rgb(200, 220, 240)
        Note over Script,Adapter: Process Users
        loop Each user
            Script->>Script: Enrich with ID
            Script->>Adapter: handleChange(user, tableName="user")
            alt Success
                Adapter-->>Script: ✓
                Script->>Logger: Log progress
            else Error
                Script->>Logger: Log error, track failure
            end
            Script->>Script: Delay throttle
        end
    end

    Script->>Firestore: Get groups collection<br/>(tolerant to failure)
    activate Firestore
    Firestore-->>Script: Group documents<br/>or empty on error
    deactivate Firestore

    rect rgb(220, 240, 200)
        Note over Script,Adapter: Process Groups
        loop Each group
            Script->>Script: Enrich with ID
            Script->>Adapter: handleChange(group, tableName="group")
            alt Success
                Adapter-->>Script: ✓
                Script->>Logger: Log progress
            else Error
                Script->>Logger: Log error, track failure
            end
            Script->>Script: Delay throttle
        end
    end

    Script->>Logger: Output final statistics<br/>(totals, successes, failures)
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Poem

🐰 A script hops through the Firestore night,
Syncing users and groups with all its might,
Firebase sparks, adapters dance and play,
Progress tracked from night to day! ✨

Pre-merge checks and finishing touches

❌ Failed checks (1 inconclusive)
Check name Status Explanation Resolution
Description check ❓ Inconclusive The description follows the template structure and correctly identifies this as a Chore, but omits critical details like Issue Number, testing approach, and leaves all checklist items unchecked. Add the Issue Number, explain how the script was tested, and provide implementation details about the sync functionality or at least confirm the checklist items.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title 'chore: add qol script for sync' accurately describes the main change—adding a quality-of-life script for syncing users and groups.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
✨ Finishing touches
  • 📝 Generate docstrings

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.

@coodos coodos force-pushed the feat/qol-sync-script branch from a5bd8e6 to b2b8c29 Compare January 6, 2026 11:29
@coodos coodos force-pushed the feat/qol-sync-script branch from b2b8c29 to ef8cf01 Compare January 6, 2026 11:30
Copy link
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: 4

🤖 Fix all issues with AI Agents
In @platforms/blabsy-w3ds-auth-api/src/scripts/syncUsersAndGroups.ts:
- Around line 86-92: Remove the redundant per-call .catch() handlers that log
and re-throw around adapter.handleChange calls (e.g., the call to
adapter.handleChange({ data: enrichedData, tableName: "user" }) and the
analogous call in processGroup); the outer try-catch in the processing loops
already logs errors, so delete the .catch(...) wrappers and let exceptions
propagate to the existing try-catch so each error is logged only once.
- Line 218: The success-rate log can produce NaN when totalItems is 0; modify
the code around the console.log that uses processedCount and totalItems so it
guards against division-by-zero (e.g., if totalItems === 0, print a sensible
fallback like "0.0%" or "N/A" instead of doing (processedCount/totalItems));
update the expression that builds the string for the console.log to compute
percentage only when totalItems > 0 and otherwise output the fallback.
- Line 9: The dotenv.config call in syncUsersAndGroups.ts uses a brittle
hardcoded relative path ("../../../../../.env"); change it to load .env more
robustly by allowing an override and a sensible fallback: check for a
DOTENV_PATH env var and, if present, call dotenv.config({ path:
path.resolve(process.cwd(), process.env.DOTENV_PATH) }), otherwise call
dotenv.config({ path: path.resolve(process.cwd(), '.env') }) and if that fails
fall back to dotenv.config() with no args so dotenv can auto-discover; update
the dotenv.config invocation (and related imports) in syncUsersAndGroups.ts
accordingly so the script works regardless of its location.
- Line 154: Remove the unused local variable `remaining` (the line `const
remaining = users.length - userNumber;`) from the syncUsersAndGroups.ts code;
locate the declaration (variable name `remaining`) in the function that iterates
`users` and delete it, or if its value was intended to be used, replace the
deletion by actually using `remaining` in the relevant log/message instead.
🧹 Nitpick comments (3)
platforms/blabsy-w3ds-auth-api/src/scripts/syncUsersAndGroups.ts (3)

12-68: Consider simplifying the nested credential fallback logic.

The multiple nested try-catch blocks make the initialization flow difficult to follow. Consider refactoring into a cleaner chain of credential strategies.

💡 Suggested refactor for improved readability
function initializeFirebase(): void {
    if (getApps().length > 0) {
        console.log("✅ Firebase Admin SDK already initialized");
        return;
    }

    const credentialsPath = process.env.GOOGLE_APPLICATION_CREDENTIALS || process.env.FIREBASE_CREDENTIALS_PATH;
    
    // Strategy 1: Try loading from file
    if (credentialsPath && fs.existsSync(credentialsPath)) {
        try {
            const serviceAccount = JSON.parse(fs.readFileSync(credentialsPath, "utf8"));
            initializeApp({ credential: cert(serviceAccount) });
            console.log("✅ Firebase Admin SDK initialized with service account file");
            return;
        } catch (error: unknown) {
            console.error("❌ Failed to load service account file:", error);
        }
    }
    
    // Strategy 2: Try application default
    if (credentialsPath || process.env.GOOGLE_APPLICATION_CREDENTIALS) {
        try {
            initializeApp({ credential: applicationDefault() });
            console.log("✅ Firebase Admin SDK initialized with applicationDefault");
            return;
        } catch (error: unknown) {
            console.error("❌ Failed to initialize with applicationDefault:", error);
        }
    }
    
    // Strategy 3: Fail with clear message
    throw new Error("Firebase credentials not configured. Set GOOGLE_APPLICATION_CREDENTIALS or FIREBASE_CREDENTIALS_PATH");
}

82-109: Consolidate duplicate user and group processing logic.

processUser and processGroup are nearly identical and differ only in the tableName parameter. This violates DRY principles.

🔎 Proposed refactor to eliminate duplication
-async function processUser(doc: FirebaseFirestore.QueryDocumentSnapshot<DocumentData>): Promise<void> {
-    const data = doc.data();
-    const enrichedData: DocumentData & { id: string } = { ...data, id: doc.id };
-    
-    await adapter.handleChange({
-        data: enrichedData,
-        tableName: "user",
-    }).catch((e) => {
-        console.error(`Error processing user ${doc.id}:`, e);
-        throw e;
-    });
-}
-
-async function processGroup(doc: FirebaseFirestore.QueryDocumentSnapshot<DocumentData>): Promise<void> {
-    const data = doc.data();
-    const enrichedData: DocumentData & { id: string } = { ...data, id: doc.id };
-    
-    await adapter.handleChange({
-        data: enrichedData,
-        tableName: "group",
-    }).catch((e) => {
-        console.error(`Error processing group ${doc.id}:`, e);
-        throw e;
-    });
-}
+async function processDocument(
+    doc: FirebaseFirestore.QueryDocumentSnapshot<DocumentData>,
+    tableName: "user" | "group"
+): Promise<void> {
+    const data = doc.data();
+    const enrichedData: DocumentData & { id: string } = { ...data, id: doc.id };
+    
+    await adapter.handleChange({
+        data: enrichedData,
+        tableName,
+    });
+}

Then update the calls:

await processDocument(user, "user");
await processDocument(group, "group");

114-220: Consider performance and operational improvements for production use.

The current implementation has several operational limitations:

  1. Sequential processing with fixed delays takes considerable time for large datasets (e.g., 1,000 items × 3.33s = ~55 minutes)
  2. Memory usage: Loading all documents at once could cause issues with large collections
  3. No resume capability: If the script fails midway, it must restart from the beginning
  4. Hardcoded delay: The 3.33s delay isn't configurable

Consider these enhancements for production resilience:

  • Implement batch processing with controlled parallelism (e.g., process 5-10 items concurrently)
  • Use Firestore query cursors for pagination instead of loading everything into memory
  • Add checkpointing to track progress and enable resuming from the last successful item
  • Make the delay configurable via environment variable
  • Add a dry-run mode to validate without making changes
📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between d43f76c and ef8cf01.

📒 Files selected for processing (2)
  • platforms/blabsy-w3ds-auth-api/package.json
  • platforms/blabsy-w3ds-auth-api/src/scripts/syncUsersAndGroups.ts
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: build
🔇 Additional comments (2)
platforms/blabsy-w3ds-auth-api/package.json (1)

15-15: LGTM! Script entry properly configured.

The new npm script follows existing patterns and correctly references the TypeScript file.

platforms/blabsy-w3ds-auth-api/src/scripts/syncUsersAndGroups.ts (1)

223-235: LGTM! Script execution and export properly implemented.

The script guard correctly detects direct execution, handles both success and error cases with appropriate exit codes, and the function is properly exported for programmatic use.

import dotenv from "dotenv";
import { adapter } from "../controllers/WebhookController";

dotenv.config({ path: path.resolve(__dirname, "../../../../../.env") });
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Fragile path traversal for environment configuration.

The hardcoded relative path "../../../../../.env" is brittle and will break if the script moves or the directory structure changes. If the .env file isn't found, Firebase initialization will fail.

🔎 Recommended approach

Consider these alternatives:

  1. Use path.resolve(process.cwd(), '.env') to load from the repository root
  2. Allow the path to be overridden via an environment variable
  3. Let dotenv search for .env files automatically by calling dotenv.config() without arguments
-dotenv.config({ path: path.resolve(__dirname, "../../../../../.env") });
+// Load from repository root or allow DOTENV_CONFIG_PATH override
+dotenv.config({ path: process.env.DOTENV_CONFIG_PATH || path.resolve(process.cwd(), '.env') });
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
dotenv.config({ path: path.resolve(__dirname, "../../../../../.env") });
// Load from repository root or allow DOTENV_CONFIG_PATH override
dotenv.config({ path: process.env.DOTENV_CONFIG_PATH || path.resolve(process.cwd(), '.env') });
🤖 Prompt for AI Agents
In @platforms/blabsy-w3ds-auth-api/src/scripts/syncUsersAndGroups.ts at line 9,
The dotenv.config call in syncUsersAndGroups.ts uses a brittle hardcoded
relative path ("../../../../../.env"); change it to load .env more robustly by
allowing an override and a sensible fallback: check for a DOTENV_PATH env var
and, if present, call dotenv.config({ path: path.resolve(process.cwd(),
process.env.DOTENV_PATH) }), otherwise call dotenv.config({ path:
path.resolve(process.cwd(), '.env') }) and if that fails fall back to
dotenv.config() with no args so dotenv can auto-discover; update the
dotenv.config invocation (and related imports) in syncUsersAndGroups.ts
accordingly so the script works regardless of its location.

Comment on lines +86 to +92
await adapter.handleChange({
data: enrichedData,
tableName: "user",
}).catch((e) => {
console.error(`Error processing user ${doc.id}:`, e);
throw e;
});
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Remove redundant error logging in catch blocks.

The .catch() blocks log errors and re-throw them, but the outer try-catch in the processing loops (lines 161-164, 195-198) also logs errors. This creates duplicate error messages in the console output.

🔎 Proposed fix

Remove the .catch() blocks since the outer try-catch already handles logging:

 async function processUser(doc: FirebaseFirestore.QueryDocumentSnapshot<DocumentData>): Promise<void> {
     const data = doc.data();
     const enrichedData: DocumentData & { id: string } = { ...data, id: doc.id };
     
-    await adapter.handleChange({
+    return adapter.handleChange({
         data: enrichedData,
         tableName: "user",
-    }).catch((e) => {
-        console.error(`Error processing user ${doc.id}:`, e);
-        throw e;
     });
 }

Apply the same change to processGroup.

Also applies to: 102-108

🤖 Prompt for AI Agents
In @platforms/blabsy-w3ds-auth-api/src/scripts/syncUsersAndGroups.ts around
lines 86 - 92, Remove the redundant per-call .catch() handlers that log and
re-throw around adapter.handleChange calls (e.g., the call to
adapter.handleChange({ data: enrichedData, tableName: "user" }) and the
analogous call in processGroup); the outer try-catch in the processing loops
already logs errors, so delete the .catch(...) wrappers and let exceptions
propagate to the existing try-catch so each error is logged only once.

for (let i = 0; i < users.length; i++) {
const user = users[i];
const userNumber = i + 1;
const remaining = users.length - userNumber;
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Remove unused variable.

The remaining variable is declared but never used in the code.

🔎 Proposed fix
     for (let i = 0; i < users.length; i++) {
         const user = users[i];
         const userNumber = i + 1;
-        const remaining = users.length - userNumber;
         
         try {
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const remaining = users.length - userNumber;
for (let i = 0; i < users.length; i++) {
const user = users[i];
const userNumber = i + 1;
try {
🤖 Prompt for AI Agents
In @platforms/blabsy-w3ds-auth-api/src/scripts/syncUsersAndGroups.ts at line
154, Remove the unused local variable `remaining` (the line `const remaining =
users.length - userNumber;`) from the syncUsersAndGroups.ts code; locate the
declaration (variable name `remaining`) in the function that iterates `users`
and delete it, or if its value was intended to be used, replace the deletion by
actually using `remaining` in the relevant log/message instead.

console.log(` Total items: ${totalItems}`);
console.log(` ✅ Successfully processed: ${processedCount}`);
console.log(` ❌ Failed: ${failedCount}`);
console.log(` 📈 Success rate: ${((processedCount / totalItems) * 100).toFixed(1)}%`);
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Handle division by zero for empty collections.

If both users and groups collections are empty, totalItems will be 0, causing the success rate calculation to produce NaN.

🔎 Proposed fix
-    console.log(`   📈 Success rate: ${((processedCount / totalItems) * 100).toFixed(1)}%`);
+    console.log(`   📈 Success rate: ${totalItems > 0 ? ((processedCount / totalItems) * 100).toFixed(1) : '0.0'}%`);
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
console.log(` 📈 Success rate: ${((processedCount / totalItems) * 100).toFixed(1)}%`);
console.log(` 📈 Success rate: ${totalItems > 0 ? ((processedCount / totalItems) * 100).toFixed(1) : '0.0'}%`);
🤖 Prompt for AI Agents
In @platforms/blabsy-w3ds-auth-api/src/scripts/syncUsersAndGroups.ts at line
218, The success-rate log can produce NaN when totalItems is 0; modify the code
around the console.log that uses processedCount and totalItems so it guards
against division-by-zero (e.g., if totalItems === 0, print a sensible fallback
like "0.0%" or "N/A" instead of doing (processedCount/totalItems)); update the
expression that builds the string for the console.log to compute percentage only
when totalItems > 0 and otherwise output the fallback.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants