Skip to content

Conversation

@hussam789
Copy link

@hussam789 hussam789 commented Oct 30, 2025

User description

PR #10


PR Type

Enhancement


Description

  • Add calendar cache status tracking with updatedAt timestamp

  • Implement cache deletion functionality via new tRPC endpoint

  • Create CredentialActionsDropdown component for cache management UI

  • Integrate cache status into connectedCalendars response

  • Add translation strings for cache-related user messages


Diagram Walkthrough

flowchart LR
  A["CalendarCache Schema"] -->|updatedAt field| B["CalendarCacheRepository"]
  B -->|getCacheStatusByCredentialIds| C["connectedCalendars Handler"]
  C -->|cacheUpdatedAt enrichment| D["CredentialActionsDropdown"]
  D -->|deleteCache mutation| E["deleteCacheHandler"]
  E -->|delete operation| F["Prisma CalendarCache"]
Loading

File Walkthrough

Relevant files
Schema changes
1 files
schema.prisma
Add updatedAt timestamp field to CalendarCache                     
+2/-0     
Database migration
1 files
migration.sql
Database migration for CalendarCache updatedAt column       
+9/-0     
Interface enhancement
1 files
calendar-cache.repository.interface.ts
Add getCacheStatusByCredentialIds method signature             
+3/-0     
Repository implementation
1 files
calendar-cache.repository.ts
Implement getCacheStatusByCredentialIds with groupBy query
+17/-0   
Testing utilities
2 files
calendar-cache.repository.mock.ts
Add mock implementation for getCacheStatusByCredentialIds
+5/-0     
test-gcal-webhooks.sh
New script for testing Google Calendar webhooks with Tunnelmole
+79/-0   
Repository enhancement
1 files
selectedCalendar.ts
Add updateManyByCredentialId method and fix type issues   
+8/-2     
Query enhancement
1 files
user.ts
Include updatedAt and googleChannelId in selectedCalendars query
+2/-0     
Type definition enhancement
1 files
getConnectedDestinationCalendars.ts
Extend UserWithCalendars type with cache-related fields   
+8/-2     
Service enhancement
1 files
CalendarService.ts
Update SelectedCalendar timestamps after availability sync
+3/-0     
Router enhancement
1 files
_router.tsx
Add deleteCache tRPC endpoint to calendars router               
+9/-0     
Handler enhancement
1 files
connectedCalendars.handler.ts
Enrich connectedCalendars response with cache status data
+13/-1   
New handler
1 files
deleteCache.handler.ts
New handler for deleting calendar cache by credential       
+33/-0   
New component
1 files
CredentialActionsDropdown.tsx
New dropdown component for credential and cache actions   
+157/-0 
Component replacement
1 files
SelectedCalendarsSettingsWebWrapper.tsx
Replace DisconnectIntegration with CredentialActionsDropdown
+23/-25 
Localization
1 files
common.json
Add translation strings for cache status and deletion       
+7/-0     
Configuration changes
1 files
package.json
Update dev:cron script to use npx tsx instead of ts-node 
+1/-1     

* feat: add calendar cache status dropdown

- Add updatedAt field to CalendarCache schema with migration
- Create tRPC cacheStatus endpoint for fetching cache timestamps
- Add action dropdown to CalendarSwitch for Google Calendar entries
- Display formatted last updated timestamp in dropdown
- Add placeholder for cache deletion functionality
- Include translation strings for dropdown content

The dropdown only appears for Google Calendar integrations that have
active cache entries and provides cache management options for future
extensibility.

Co-Authored-By: [email protected] <[email protected]>

* fix: resolve Prisma type incompatibilities in repository files

- Remove problematic satisfies clause in selectedCalendar.ts
- Add missing cacheStatus parameter to ConnectedCalendarList component
- Fixes type errors that were preventing CI from passing

Co-Authored-By: [email protected] <[email protected]>

* refactor: integrate cache status into connectedCalendars handler

- Remove separate cacheStatus tRPC endpoint as requested
- Return cache status as separate field in connectedCalendars response
- Update UI components to use cache data from connectedCalendars
- Fix Prisma type incompatibilities in repository files

Co-Authored-By: [email protected] <[email protected]>

* fix: resolve Prisma type incompatibilities and fix data flow for cache status

- Fix Prisma.SortOrder usage in membership.ts orderBy clauses
- Remove problematic satisfies clause in selectedCalendar.ts
- Fix TeamSelect type reference in team.ts
- Update SelectedCalendarsSettingsWebWrapper to properly pass cacheStatus data flow

Co-Authored-By: [email protected] <[email protected]>

* Discard changes to packages/lib/server/repository/membership.ts

* Discard changes to packages/lib/server/repository/team.ts

* fix: improve calendar cache dropdown with proper formatting and subscription logic

- Fix timestamp HTML entity encoding with interpolation escapeValue: false
- Only show dropdown for subscribed Google calendars (googleChannelId exists)
- Hide delete option when no cache data exists
- Include updatedAt and googleChannelId fields upstream in user repository
- Update data flow to pass subscription status through components

Co-Authored-By: [email protected] <[email protected]>

* feat: update SelectedCalendar.updatedAt when Google webhooks trigger cache refresh

- Add updateManyByCredentialId method to SelectedCalendarRepository
- Update fetchAvailabilityAndSetCache to refresh SelectedCalendar timestamps
- Ensure webhook flow updates both CalendarCache and SelectedCalendar records
- Maintain proper timestamp tracking for calendar cache operations

Co-Authored-By: [email protected] <[email protected]>

* Add script to automate Tunnelmole webhook setup

Introduces test-gcal-webhooks.sh to start Tunnelmole, extract the public URL, and update GOOGLE_WEBHOOK_URL in the .env file. Handles process management, rate limits, and ensures environment configuration for Google Calendar webhooks.

* Update dev:cron script to use npx tsx

Replaces 'ts-node' with 'npx tsx' in the dev:cron script for running cron-tester.ts, likely to improve compatibility or leverage tsx features.

* Update cache status string and improve CalendarSwitch UI

Renamed 'last_updated' to 'cache_last_updated' in locale file for clarity and updated CalendarSwitch to use the new string. Also added dark mode text color support for cache status display.

* refactor: move cache management to credential-level dropdown with Remove App

- Create CredentialActionsDropdown component consolidating cache and app removal actions
- Add deleteCache tRPC mutation for credential-level cache deletion
- Update connectedCalendars handler to include cacheUpdatedAt at credential level
- Move dropdown from individual CalendarSwitch to credential level in SelectedCalendarsSettingsWebWrapper
- Remove cache-related props from CalendarSwitch component
- Add translation strings for cache management actions
- Consolidate all credential-level actions (cache management + Remove App) in one dropdown

Co-Authored-By: [email protected] <[email protected]>

* fix: remove duplicate translation keys in common.json

- Remove duplicate cache-related keys at lines 51-56
- Keep properly positioned keys later in file
- Addresses GitHub comment from zomars about duplicate keys

Co-Authored-By: [email protected] <[email protected]>

* fix: rename translation key to cache_last_updated

- Address GitHub comment from zomars
- Rename 'last_updated' to 'cache_last_updated' for specificity
- Update usage in CredentialActionsDropdown component

Co-Authored-By: [email protected] <[email protected]>

* fix: remove duplicate last_updated translation key

Co-Authored-By: [email protected] <[email protected]>

* fix: add confirmation dialog for cache deletion and use repository pattern

- Add confirmation dialog for destructive cache deletion action
- Replace direct Prisma calls with CalendarCacheRepository pattern
- Add getCacheStatusByCredentialIds method to repository interface
- Fix import paths for UI components
- Address GitHub review comments from zomars

Co-Authored-By: [email protected] <[email protected]>

* Update CredentialActionsDropdown.tsx

* Update common.json

* Update common.json

* fix: remove nested div wrapper to resolve HTML structure error

- Remove wrapping div around DisconnectIntegration component
- Fixes nested <p> tag validation error preventing Remove App functionality
- Maintains existing confirmation dialog patterns

Co-Authored-By: [email protected] <[email protected]>

* Fix API handler response termination logic

Removed unnecessary return values after setting status in the integrations API handler. This clarifies response handling and prevents returning the response object when not needed. Resolves "API handler should not return a value, received object".

* fix: 400 is correct error code for computing slot for past booking (#22574)

* fix

* add test

* chore: release v5.5.1

* Refactor credential disconnect to use confirmation dialog

Replaces the DisconnectIntegration component with an inline confirmation dialog for removing app credentials. Adds disconnect mutation logic and updates UI to improve user experience and consistency.

* Set default value for CalendarCache.updatedAt

Added a default value of NOW() for the updatedAt column in the CalendarCache table to ensure existing and future rows have a valid timestamp. Updated the Prisma schema to reflect this change and provide compatibility for legacy data and raw inserts.

---------

Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Co-authored-by: Benny Joo <[email protected]>
Co-authored-by: emrysal <[email protected]>
@qodo-code-review
Copy link

PR Compliance Guide 🔍

Below is a summary of compliance checks for this PR:

Security Compliance
Insufficient auditing

Description: The deleteCache handler throws a generic Error string on auth failure and returns generic
success, lacking granular error types and rate limiting; while authorization is checked,
absence of audit/logging for destructive action could hinder incident response.
deleteCache.handler.ts [13-32]

Referred Code
export const deleteCacheHandler = async ({ ctx, input }: DeleteCacheOptions) => {
  const { user } = ctx;
  const { credentialId } = input;

  const credential = await prisma.credential.findFirst({
    where: {
      id: credentialId,
      userId: user.id,
    },
  });

  if (!credential) {
    throw new Error("Credential not found or access denied");
  }

  await prisma.calendarCache.deleteMany({
    where: { credentialId },
  });

  return { success: true };
Unsafe file write

Description: The script writes/updates GOOGLE_WEBHOOK_URL in a relative ../.env file and parses tmole
output without strict validation, which could overwrite unintended env files if run from
unexpected cwd.
test-gcal-webhooks.sh [61-71]

Referred Code
# Update env file
if [ ! -f "$ENV_FILE" ]; then
  echo "⚠️  $ENV_FILE not found. Creating new file."
  touch "$ENV_FILE"
fi

if grep -q '^GOOGLE_WEBHOOK_URL=' "$ENV_FILE"; then
  sed -i '' -E "s|^GOOGLE_WEBHOOK_URL=.*|GOOGLE_WEBHOOK_URL=$TUNNEL_URL|" "$ENV_FILE"
else
  echo "GOOGLE_WEBHOOK_URL=$TUNNEL_URL" >> "$ENV_FILE"
fi
Ticket Compliance
🎫 No ticket provided
  • Create ticket/issue
Codebase Duplication Compliance
Codebase context is not defined

Follow the guide to enable codebase context checks.

Custom Compliance
🟢
Generic: Meaningful Naming and Self-Documenting Code

Objective: Ensure all identifiers clearly express their purpose and intent, making code
self-documenting

Status: Passed

Generic: Secure Logging Practices

Objective: To ensure logs are useful for debugging and auditing without exposing sensitive
information like PII, PHI, or cardholder data.

Status: Passed

Generic: Security-First Input Validation and Data Handling

Objective: Ensure all data inputs are validated, sanitized, and handled securely to prevent
vulnerabilities

Status: Passed

Generic: Comprehensive Audit Trails

Objective: To create a detailed and reliable record of critical system actions for security analysis
and compliance.

Status:
Missing audit log: Deleting cache is a critical action but the new handler performs the deletion without
emitting any audit log including user, credential, or outcome context.

Referred Code
export const deleteCacheHandler = async ({ ctx, input }: DeleteCacheOptions) => {
  const { user } = ctx;
  const { credentialId } = input;

  const credential = await prisma.credential.findFirst({
    where: {
      id: credentialId,
      userId: user.id,
    },
  });

  if (!credential) {
    throw new Error("Credential not found or access denied");
  }

  await prisma.calendarCache.deleteMany({
    where: { credentialId },
  });

  return { success: true };
Generic: Robust Error Handling and Edge Case Management

Objective: Ensure comprehensive error handling that provides meaningful context and graceful
degradation

Status:
Generic error thrown: The handler throws a generic Error string for missing/unauthorized credential and does not
handle possible failures from deleteMany or validate input edge cases.

Referred Code
if (!credential) {
  throw new Error("Credential not found or access denied");
}

await prisma.calendarCache.deleteMany({
  where: { credentialId },
});
Generic: Secure Error Handling

Objective: To prevent the leakage of sensitive system information through error messages while
providing sufficient detail for internal debugging.

Status:
Info leakage risk: The error message "Credential not found or access denied" may reveal the
existence of a credential to an attacker depending on routing; consider a uniform generic
message for end users.

Referred Code
if (!credential) {
  throw new Error("Credential not found or access denied");
}
Compliance status legend 🟢 - Fully Compliant
🟡 - Partial Compliant
🔴 - Not Compliant
⚪ - Requires Further Human Verification
🏷️ - Compliance label

@qodo-code-review
Copy link

PR Code Suggestions ✨

Explore these optional code suggestions:

CategorySuggestion                                                                                                                                    Impact
Possible issue
Fix no-op database update call

Explicitly set the updatedAt field in the updateManyByCredentialId call to
ensure the timestamp is updated, as passing an empty object results in a no-op.

packages/app-store/googlecalendar/lib/CalendarService.ts [1023-1024]

 // Update SelectedCalendar.updatedAt for all calendars under this credential
-await SelectedCalendarRepository.updateManyByCredentialId(this.credential.id, {});
+await SelectedCalendarRepository.updateManyByCredentialId(this.credential.id, { updatedAt: new Date() });
  • Apply / Chat
Suggestion importance[1-10]: 8

__

Why: The suggestion correctly identifies a bug where an update operation is a no-op, preventing the updatedAt field from being updated as intended, and provides the correct fix.

Medium
General
Use dynamic locale for date formatting

Replace the hardcoded "en-US" locale in Intl.DateTimeFormat with the dynamic
language variable from the useLocale hook to ensure dates are formatted
according to the user's selected language.

packages/features/apps/components/CredentialActionsDropdown.tsx [88-94]

 {t("cache_last_updated", {
-  timestamp: new Intl.DateTimeFormat("en-US", {
+  timestamp: new Intl.DateTimeFormat(language, {
     dateStyle: "short",
     timeStyle: "short",
   }).format(new Date(cacheUpdatedAt)),
   interpolation: { escapeValue: false },
 })}
  • Apply / Chat
Suggestion importance[1-10]: 7

__

Why: The suggestion correctly points out a hardcoded "en-US" locale for date formatting, which harms internationalization. Using the dynamic locale from the useLocale hook is a good improvement for user experience.

Medium
  • More

@bar-qodo
Copy link

@greptile review

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.

3 participants