Skip to content

(WIP) Adding google calendar support#1520

Closed
githubdoramon wants to merge 4 commits intofastrepl:mainfrom
githubdoramon:feat/20250929_googleCal
Closed

(WIP) Adding google calendar support#1520
githubdoramon wants to merge 4 commits intofastrepl:mainfrom
githubdoramon:feat/20250929_googleCal

Conversation

@githubdoramon
Copy link

Adds the possibility to connect google calendar from multiple accounts, and choose subcalendars to sync

image

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Oct 1, 2025

📝 Walkthrough

Walkthrough

Adds a full Google Calendar integration: new Tauri plugin with OAuth, calendar/events/contacts APIs, commands, worker, and UI for account management and calendar selection. Renames event URL field from google_event_url to event_external_url across interface, DB schema, seed data, and usages. Expands calendar DB metadata and adjusts migrations and ops. Wires plugin into desktop app and sync flows.

Changes

Cohort / File(s) Summary
Workspace wiring
Cargo.toml, apps/desktop/src-tauri/Cargo.toml, apps/desktop/package.json, apps/desktop/src-tauri/capabilities/default.json, apps/desktop/src-tauri/src/lib.rs
Adds Google Calendar plugin as workspace dependency, desktop dependency, capability permission, and initializes plugin at app startup.
Event URL field rename
crates/calendar-interface/src/lib.rs, crates/calendar-apple/src/lib.rs, crates/calendar-google/src/lib.rs, crates/db-user/src/events_types.rs, crates/db-user/src/events_ops.rs, crates/db-user/src/events_migration_3.sql, plugins/db/seed/dev.json, plugins/db/seed/onboarding.json, plugins/db/seed/schema.json
Renames Event field from google_event_url to event_external_url across interface, DB types/ops, migration, and seed data; updates usage in Apple/Google calendar crates and schema.
DB calendar metadata
crates/db-user/src/calendars_migration_2.sql, crates/db-user/src/calendars_types.rs, crates/db-user/src/calendars_ops.rs
Adds calendars columns: connection_status, account_id, last_sync_error, last_sync_at; updates Calendar struct and ops (upsert, status update, reconnection query).
DB migrations runner
crates/db-core/src/lib.rs, crates/db-user/src/lib.rs
Executes migration files by splitting on semicolons per statement; appends two new migrations to MIGRATIONS.
Desktop UI: external link field
apps/desktop/src/components/human-profile/upcoming-events.tsx, apps/desktop/src/components/organization-profile/upcoming-events.tsx, apps/desktop/src/components/workspace-calendar/event-card.tsx
Switches event link usage from google_event_url to event_external_url in conditionals and hrefs.
Desktop: calendar sync trigger
apps/desktop/src/components/left-sidebar/events-list.tsx
Imports googleCalendarCommands and runs Google + Apple events sync in parallel via Promise.all with try/catch logging.
Settings: Google integration UI
apps/desktop/src/components/settings/components/calendar/google-calendar-integration-details.tsx, apps/desktop/src/components/settings/views/calendar.tsx, apps/desktop/src/components/settings/components/calendar/calendar-icon-with-text.tsx
Adds Google integration management UI (accounts, selections); enables Google integration in settings; replaces local Google icon with Remix icon.
STT view (logs/models)
apps/desktop/src/components/settings/components/ai/stt-view-local.tsx
Expands default/other model keys; adds debug logging.
Locales (no-op)
apps/desktop/src/locales/en/messages.ts, apps/desktop/src/locales/ko/messages.ts
Formatting-only/no functional changes.
Apple calendar plugin adjustments
plugins/apple-calendar/src/sync.rs
Propagates new calendar metadata on upsert; renames event URL field usage; removes some logs.
Google Calendar plugin: packaging
plugins/google-calendar/Cargo.toml, plugins/google-calendar/package.json, plugins/google-calendar/build.rs, plugins/google-calendar/tsconfig.json, plugins/google-calendar/js/index.ts
Adds new plugin crate/package, JS re-export, build script registering commands, and TS config.
Google Calendar plugin: APIs and commands
plugins/google-calendar/src/lib.rs, plugins/google-calendar/src/commands.rs, plugins/google-calendar/src/calendar_api.rs, plugins/google-calendar/src/contacts_api.rs, plugins/google-calendar/src/oauth.rs, plugins/google-calendar/src/error.rs, plugins/google-calendar/src/ext.rs, plugins/google-calendar/src/worker.rs
Introduces plugin modules: public types, Specta bindings, Tauri commands, Google OAuth client, Calendar and Contacts API clients, error types, extension trait implementing all flows (auth, sync, selections, accounts), and cron-based worker.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  actor User
  participant Desktop as Desktop App (UI)
  participant Tauri as Tauri App
  participant GPlugin as Google Calendar Plugin
  participant OAuth as Google OAuth
  participant Store as Token Store
  participant DB as User DB

  User->>Desktop: Click "Add Google Account"
  Desktop->>Tauri: invoke add_google_account()
  Tauri->>GPlugin: add_google_account()
  GPlugin->>OAuth: Build auth URL (combined scopes)
  GPlugin-->>Tauri: Return auth URL
  Tauri-->>Desktop: URL
  Desktop->>OAuth: Open auth URL (browser)
  OAuth-->>GPlugin: Redirect with code (local callback)
  GPlugin->>OAuth: Exchange code for tokens
  OAuth-->>GPlugin: Access + Refresh token
  GPlugin->>Store: Persist tokens + account info
  GPlugin->>DB: Sync calendars (initial)
  DB-->>GPlugin: Upsert calendars (connected)
  GPlugin-->>Tauri: Done
  Tauri-->>Desktop: Success
Loading
sequenceDiagram
  autonumber
  participant Worker as Plugin Worker (cron)
  participant GPlugin as Google Calendar Plugin
  participant Store as Token Store
  participant API as Google Calendar API
  participant DB as User DB

  rect rgba(200,230,255,0.3)
    note over Worker,GPlugin: Every 10 minutes
    Worker->>GPlugin: sync_calendars_with_db(user)
    GPlugin->>Store: Load accounts + tokens
    loop per account
      GPlugin->>API: GET /users/me/calendarList (paginate)
      API-->>GPlugin: Calendars
      GPlugin->>DB: Upsert calendars (status, account_id, last_sync_at)
    end
  end

  rect rgba(200,255,200,0.3)
    note over Worker,GPlugin: Every 5 minutes
    Worker->>GPlugin: sync_events_with_db(user)
    GPlugin->>DB: Get selected calendars
    loop per selected calendar
      GPlugin->>Store: Resolve account token
      GPlugin->>API: GET /calendars/{id}/events (paginate)
      API-->>GPlugin: Events
      GPlugin->>DB: Upsert events (event_external_url, times, attendees)
    end
  end
Loading
sequenceDiagram
  autonumber
  participant UI as Desktop UI
  participant Apple as Apple Calendar Plugin
  participant Google as Google Calendar Plugin

  UI->>Google: sync_events(null)
  UI->>Apple: sync_events()
  par Parallel sync
    Google-->>UI: Ok/Error (logged)
    Apple-->>UI: Ok/Error (logged)
  end
Loading

Estimated code review effort

🎯 5 (Critical) | ⏱️ ~120 minutes

Possibly related PRs

  • Finder view merge #1213 — Touches the same file apps/desktop/src/components/left-sidebar/events-list.tsx, modifying how calendar sync or navigation is triggered.
  • Event timing issue #1198 — Modifies plugins/apple-calendar/src/sync.rs affecting event sync flows, overlapping with this PR’s Apple sync adjustments.
  • More analytics #1442 — Updates stt-view-local.tsx, related to this PR’s changes expanding STT model keys and adding logs.

Pre-merge checks and finishing touches

❌ Failed checks (2 warnings)
Check name Status Explanation Resolution
Title Check ⚠️ Warning Although it summarizes the main feature of adding Google Calendar support, the title includes the “(WIP)” prefix which is unnecessary noise and uses lowercase “google”, so it does not fully adhere to the concise and clean phrasing guidelines. Remove the “(WIP)” prefix and capitalize “Google” to make the title concise and free of noise, for example: “Add Google Calendar support”.
Docstring Coverage ⚠️ Warning Docstring coverage is 45.10% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (1 passed)
Check name Status Explanation
Description Check ✅ Passed The description clearly explains the new capability to connect multiple Google Calendar accounts and select subcalendars to sync, which aligns directly with the changes in the pull request.
✨ Finishing touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment

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.

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: 20

Caution

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

⚠️ Outside diff range comments (2)
apps/desktop/src/components/settings/views/calendar.tsx (1)

28-29: Hardcoded accordion values may cause UX issues.

The accordion defaultValue and AccordionItem value are both hardcoded to "apple". This means:

  1. The accordion will always default to the Apple Calendar item being open, even if Google Calendar is the first integration shown.
  2. All integration types will share the same accordion item value, which could cause unexpected behavior.

Consider deriving these values dynamically:

-<Accordion type="single" collapsible defaultValue={"apple"}>
-  <AccordionItem value="apple">
+<Accordion type="single" collapsible defaultValue={type}>
+  <AccordionItem value={type}>
crates/db-user/src/calendars_ops.rs (1)

225-233: Update the Calendar literal in test_calendars to include the new optional fields
In crates/db-user/src/calendars_ops.rs (around line 225), the Calendar struct literal omits connection_status, account_id, last_sync_error, and last_sync_at. Either supply values for those fields or append ..Default::default() to the struct literal so the test compiles.

🧹 Nitpick comments (25)
plugins/google-calendar/src/error.rs (2)

1-32: Consider adding #[non_exhaustive] for future compatibility.

The Error enum lacks #[non_exhaustive], meaning adding new variants will be a breaking change. For a plugin that's likely to evolve, this could limit future flexibility.

Apply this diff:

+#[non_exhaustive]
 #[derive(Debug, thiserror::Error)]
 pub enum Error {

3-31: Overlapping error semantics between Auth, OAuth, and token variants.

The variants Auth, OAuth, InvalidToken, and TokenExpired have overlapping semantics. Consider whether OAuth and token-related errors could be subsumed under Auth for simpler error handling, or whether the distinction provides meaningful value for error recovery.

plugins/google-calendar/src/worker.rs (1)

46-64: Optional: pull cron expressions into named consts for clarity.

Keeps schedule intent DRY and easier to tweak.

Apply:

 pub async fn monitor(state: WorkerState) -> Result<(), std::io::Error> {
     use std::str::FromStr;
 
+    const EVERY_10_MIN_AT_SEC0: &str = "0 */10 * * * *";
+    const EVERY_5_MIN_AT_SEC0: &str = "0 */5 * * * *";
+
     apalis::prelude::Monitor::new()
         .register({
             WorkerBuilder::new(CALENDARS_SYNC_WORKER_NAME)
                 .data(state.clone())
                 .backend(apalis_cron::CronStream::new(
-                    apalis_cron::Schedule::from_str("0 */10 * * * *").unwrap(),
+                    apalis_cron::Schedule::from_str(EVERY_10_MIN_AT_SEC0).unwrap(),
                 ))
                 .build_fn(perform_calendars_sync)
         })
         .register({
             WorkerBuilder::new(EVENTS_SYNC_WORKER_NAME)
                 .data(state)
                 .backend(apalis_cron::CronStream::new(
-                    apalis_cron::Schedule::from_str("0 */5 * * * *").unwrap(),
+                    apalis_cron::Schedule::from_str(EVERY_5_MIN_AT_SEC0).unwrap(),
                 ))
                 .build_fn(perform_events_sync)
         })

No behavior change. As per coding guidelines.

crates/db-user/src/calendars_migration_2.sql (1)

1-11: LGTM with optional refactor suggestion.

The four new columns support calendar sync status tracking. The nullable fields are appropriate for existing calendar rows. Using TEXT for timestamps is consistent with the existing events table pattern.

Consider combining the four ALTER TABLE statements into a single statement for slightly better performance during migration:

-ALTER TABLE calendars 
-ADD COLUMN connection_status TEXT DEFAULT 'connected';
-
-ALTER TABLE calendars
-ADD COLUMN account_id TEXT;
-
-ALTER TABLE calendars
-ADD COLUMN last_sync_error TEXT;
-
-ALTER TABLE calendars
-ADD COLUMN last_sync_at TEXT;
+ALTER TABLE calendars 
+ADD COLUMN connection_status TEXT DEFAULT 'connected',
+ADD COLUMN account_id TEXT,
+ADD COLUMN last_sync_error TEXT,
+ADD COLUMN last_sync_at TEXT;
plugins/google-calendar/build.rs (1)

17-17: Remove trailing whitespace.

Line 17 contains trailing whitespace after "start_worker".

Apply this diff:

-    "start_worker", 
+    "start_worker",
crates/db-user/src/calendars_types.rs (1)

12-15: Consider using DateTime<Utc> for last_sync_at.

The last_sync_at field is typed as Option<String>, which is inconsistent with the existing pattern in this codebase. The Event struct (in events_types.rs) uses DateTime<Utc> for temporal fields like start_date and end_date. Using DateTime<Utc> would provide type safety, enable datetime operations, and avoid error-prone string parsing.

If changing the type, update the field definition:

-        pub last_sync_at: Option<String>,
+        pub last_sync_at: Option<DateTime<Utc>>,

Note: This would require corresponding changes in the database schema, migrations, and all usage sites.

plugins/google-calendar/Cargo.toml (3)

24-24: Don’t ship Tauri’s test feature to dependents; keep it only for dev/tests.

Enable tauri normally for the library and move features = ["test"] under dev-dependencies for test-only usage (your tests already need it).

-[dependencies]
-tauri = { workspace = true, features = ["test"] }
+[dependencies]
+tauri = { workspace = true }
 
 [dev-dependencies]
 specta-typescript = { workspace = true }
+tauri = { workspace = true, features = ["test"] }

Also applies to: 13-15


42-42: Use rustls only (avoid pulling native-tls) by disabling reqwest default features.

Right now both TLS stacks may compile. Trim size and avoid ambiguity.

-reqwest = { version = "0.11", features = ["json", "rustls-tls"] }
+reqwest = { version = "0.11", default-features = false, features = ["json", "rustls-tls"] }

4-4: Polish package metadata (authors/description).

Replace placeholders to aid crates.io/docs and internal tooling.

-authors = ["You"]
-description = ""
+authors = ["HyprNote Team"]
+description = "Tauri plugin providing Google Calendar multi-account sync and bindings."

Also applies to: 8-8

plugins/google-calendar/src/lib.rs (2)

78-89: Update tests to the non-generic builder.

Keep tests compiling with the Wry-specific builder.

-        make_specta_builder::<tauri::Wry>()
+        make_specta_builder()
             .export(
                 specta_typescript::Typescript::default()

20-25: Consider an async-aware mutex for plugin state.

Using std::sync::Mutex in async contexts can block threads; prefer tauri::async_runtime::Mutex or tokio::sync::Mutex for state accessed from async commands. Requires a small change in ext.rs call sites.

plugins/apple-calendar/src/sync.rs (3)

2-2: Clean up unused imports.

serde_json and CalendarSource aren’t used.

-use serde_json;
-
-use hypr_calendar_interface::{CalendarSource, EventFilter};
+use hypr_calendar_interface::EventFilter;

Also applies to: 4-4


131-132: Remove the unused “handled_system_event_ids” tracking.

The HashSet is written but never read; you already avoid duplicates via to_update/already_exists.

-    let mut handled_system_event_ids = std::collections::HashSet::<String>::new();
+    // no-op: removed unused handled set
-                    // Mark this system event as handled
-                    handled_system_event_ids.insert(composite_id);
+                    // already accounted via to_update

Also applies to: 194-195


47-52: Avoid taking a reference to a temporary Vec just to clone.

Use cloned().unwrap_or_default() for clarity.

-        let events = system_events_per_tracking_id
-            .get(&db_calendar.tracking_id)
-            .unwrap_or(&vec![])
-            .clone();
+        let events = system_events_per_tracking_id
+            .get(&db_calendar.tracking_id)
+            .cloned()
+            .unwrap_or_default();
plugins/google-calendar/src/contacts_api.rs (1)

15-17: Confirm population of account_email.

Contact.account_email isn’t set in get_contacts results. If callers rely on this metadata, ensure it’s filled upstream before exposing to UI or storage. Otherwise, consider removing it here to avoid confusion.

Also applies to: 91-118

apps/desktop/src/components/settings/components/calendar/google-calendar-integration-details.tsx (2)

84-89: Polling every 2s: consider gating or reducing frequency.

A constant 2s refetchInterval can be noisy. Consider gating to when the settings view is visible/active, or relaxing the interval. This keeps UX snappy without background churn.


93-110: New-account detection by count is brittle.

Using only count assumes the newest account is last. If ordering changes, the toast may reference the wrong account. Track a Set of emails and diff to find additions.

plugins/google-calendar/src/oauth.rs (2)

67-81: Enable incremental auth.

Add include_granted_scopes=true to the authorization URL to support incremental consent.

Apply this diff:

         url.query_pairs_mut()
             .append_pair("client_id", &self.config.client_id)
             .append_pair("redirect_uri", &self.config.redirect_uri)
             .append_pair("response_type", "code")
             .append_pair("scope", &scope_str)
             .append_pair("access_type", "offline")
             .append_pair("prompt", "consent")
+            .append_pair("include_granted_scopes", "true")
             .append_pair("state", state);

46-59: Scope minimization.

If write access isn’t required, drop "https://www.googleapis.com/auth/calendar" and keep only calendar.readonly to adhere to least‑privilege. If writes are needed, ignore.

plugins/google-calendar/src/calendar_api.rs (3)

99-104: Reuse a single configured Client (add timeout) to harden external calls.

Constructing a default reqwest::Client without a timeout risks hung requests; we also duplicate this pattern across contacts_api.rs. Prefer one configured client (e.g., timeout, user-agent) reused across APIs or passed in; this reduces pools and avoids hangs.

Tip: Extract a small ApiClient wrapper shared by calendar_api.rs and contacts_api.rs to consolidate make_request and client config. Based on learnings


162-187: Prefer URL/query building over manual string concatenation.

Manual query assembly is error‑prone and can miss proper encoding. Use reqwest::Url with .query_pairs_mut() or the request builder’s .query(&[("k", v)]) to build timeMin, timeMax, pageToken, singleEvents, orderBy.


198-222: Fallback-to-primary triggers on any error; consider narrowing the condition.

The fallback executes for any Err, potentially masking real issues (e.g., 401/429). If you keep this behavior, at least gate it to specific statuses (e.g., 404 invalid calendarId).

plugins/google-calendar/src/ext.rs (3)

446-456: Selection should use the stored selected flag, not connection_status.

You derive “selected” from connection_status == "syncing". This can drift from the actual selected boolean you persist. Use the selected field for truth.

-    for calendar in calendars {
-        let selected = calendar.connection_status.as_deref() == Some("syncing");
-        
-        calendar_selections.push(CalendarSelection {
+    for calendar in calendars {
+        calendar_selections.push(CalendarSelection {
             calendar_id: calendar.tracking_id.clone(),
             calendar_name: calendar.name.clone(),
-            selected,
+            selected: calendar.selected,
             color: None,
         });
     }

Also applies to: 419-459


766-786: Compile-time client secret embeds in binary; prefer runtime/configured source.

option_env! bakes GOOGLE_CLIENT_SECRET into the app. For desktop OAuth, client secret isn’t truly secret, but embedding hinders rotation and distribution variants. Consider loading from runtime config/Store or OS keystore.


624-638: Upsert prevents duplicate UUIDs on sync
The upsert_event SQL uses ON CONFLICT(tracking_id) DO UPDATE, so the newly generated UUID is only applied on first insert and won’t create duplicate rows on subsequent syncs. If your intent is to allow the same Google event ID across multiple calendars, consider extending the conflict target to (tracking_id, calendar_id).

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 70d037d and e5208c1.

⛔ Files ignored due to path filters (30)
  • Cargo.lock is excluded by !**/*.lock
  • plugins/db/js/bindings.gen.ts is excluded by !**/*.gen.ts
  • plugins/google-calendar/js/bindings.gen.ts is excluded by !**/*.gen.ts
  • plugins/google-calendar/permissions/autogenerated/commands/add_google_account.toml is excluded by !plugins/**/permissions/**
  • plugins/google-calendar/permissions/autogenerated/commands/calendar_access_status.toml is excluded by !plugins/**/permissions/**
  • plugins/google-calendar/permissions/autogenerated/commands/contacts_access_status.toml is excluded by !plugins/**/permissions/**
  • plugins/google-calendar/permissions/autogenerated/commands/get_auth_status.toml is excluded by !plugins/**/permissions/**
  • plugins/google-calendar/permissions/autogenerated/commands/get_calendar_selections.toml is excluded by !plugins/**/permissions/**
  • plugins/google-calendar/permissions/autogenerated/commands/get_calendars.toml is excluded by !plugins/**/permissions/**
  • plugins/google-calendar/permissions/autogenerated/commands/get_calendars_for_account.toml is excluded by !plugins/**/permissions/**
  • plugins/google-calendar/permissions/autogenerated/commands/get_connected_accounts.toml is excluded by !plugins/**/permissions/**
  • plugins/google-calendar/permissions/autogenerated/commands/get_contacts.toml is excluded by !plugins/**/permissions/**
  • plugins/google-calendar/permissions/autogenerated/commands/get_contacts_for_account.toml is excluded by !plugins/**/permissions/**
  • plugins/google-calendar/permissions/autogenerated/commands/handle_oauth_callback.toml is excluded by !plugins/**/permissions/**
  • plugins/google-calendar/permissions/autogenerated/commands/refresh_tokens.toml is excluded by !plugins/**/permissions/**
  • plugins/google-calendar/permissions/autogenerated/commands/remove_google_account.toml is excluded by !plugins/**/permissions/**
  • plugins/google-calendar/permissions/autogenerated/commands/request_calendar_access.toml is excluded by !plugins/**/permissions/**
  • plugins/google-calendar/permissions/autogenerated/commands/request_combined_access.toml is excluded by !plugins/**/permissions/**
  • plugins/google-calendar/permissions/autogenerated/commands/request_contacts_access.toml is excluded by !plugins/**/permissions/**
  • plugins/google-calendar/permissions/autogenerated/commands/revoke_access.toml is excluded by !plugins/**/permissions/**
  • plugins/google-calendar/permissions/autogenerated/commands/search_contacts.toml is excluded by !plugins/**/permissions/**
  • plugins/google-calendar/permissions/autogenerated/commands/set_calendar_selected.toml is excluded by !plugins/**/permissions/**
  • plugins/google-calendar/permissions/autogenerated/commands/start_worker.toml is excluded by !plugins/**/permissions/**
  • plugins/google-calendar/permissions/autogenerated/commands/stop_worker.toml is excluded by !plugins/**/permissions/**
  • plugins/google-calendar/permissions/autogenerated/commands/sync_calendars.toml is excluded by !plugins/**/permissions/**
  • plugins/google-calendar/permissions/autogenerated/commands/sync_contacts.toml is excluded by !plugins/**/permissions/**
  • plugins/google-calendar/permissions/autogenerated/commands/sync_events.toml is excluded by !plugins/**/permissions/**
  • plugins/google-calendar/permissions/autogenerated/reference.md is excluded by !plugins/**/permissions/**
  • plugins/google-calendar/permissions/default.toml is excluded by !plugins/**/permissions/**
  • plugins/google-calendar/permissions/schemas/schema.json is excluded by !plugins/**/permissions/**
📒 Files selected for processing (43)
  • Cargo.toml (1 hunks)
  • apps/desktop/package.json (1 hunks)
  • apps/desktop/src-tauri/Cargo.toml (1 hunks)
  • apps/desktop/src-tauri/capabilities/default.json (1 hunks)
  • apps/desktop/src-tauri/src/lib.rs (1 hunks)
  • apps/desktop/src/components/human-profile/upcoming-events.tsx (1 hunks)
  • apps/desktop/src/components/left-sidebar/events-list.tsx (2 hunks)
  • apps/desktop/src/components/organization-profile/upcoming-events.tsx (1 hunks)
  • apps/desktop/src/components/settings/components/ai/stt-view-local.tsx (3 hunks)
  • apps/desktop/src/components/settings/components/calendar/calendar-icon-with-text.tsx (2 hunks)
  • apps/desktop/src/components/settings/components/calendar/google-calendar-integration-details.tsx (1 hunks)
  • apps/desktop/src/components/settings/views/calendar.tsx (2 hunks)
  • apps/desktop/src/components/workspace-calendar/event-card.tsx (1 hunks)
  • apps/desktop/src/locales/en/messages.ts (1 hunks)
  • apps/desktop/src/locales/ko/messages.ts (1 hunks)
  • crates/calendar-apple/src/lib.rs (1 hunks)
  • crates/calendar-google/src/lib.rs (1 hunks)
  • crates/calendar-interface/src/lib.rs (2 hunks)
  • crates/db-core/src/lib.rs (1 hunks)
  • crates/db-user/src/calendars_migration_2.sql (1 hunks)
  • crates/db-user/src/calendars_ops.rs (3 hunks)
  • crates/db-user/src/calendars_types.rs (1 hunks)
  • crates/db-user/src/events_migration_3.sql (1 hunks)
  • crates/db-user/src/events_ops.rs (6 hunks)
  • crates/db-user/src/events_types.rs (1 hunks)
  • crates/db-user/src/lib.rs (2 hunks)
  • plugins/apple-calendar/src/sync.rs (5 hunks)
  • plugins/db/seed/dev.json (19 hunks)
  • plugins/db/seed/onboarding.json (1 hunks)
  • plugins/db/seed/schema.json (2 hunks)
  • plugins/google-calendar/Cargo.toml (1 hunks)
  • plugins/google-calendar/build.rs (1 hunks)
  • plugins/google-calendar/js/index.ts (1 hunks)
  • plugins/google-calendar/package.json (1 hunks)
  • plugins/google-calendar/src/calendar_api.rs (1 hunks)
  • plugins/google-calendar/src/commands.rs (1 hunks)
  • plugins/google-calendar/src/contacts_api.rs (1 hunks)
  • plugins/google-calendar/src/error.rs (1 hunks)
  • plugins/google-calendar/src/ext.rs (1 hunks)
  • plugins/google-calendar/src/lib.rs (1 hunks)
  • plugins/google-calendar/src/oauth.rs (1 hunks)
  • plugins/google-calendar/src/worker.rs (1 hunks)
  • plugins/google-calendar/tsconfig.json (1 hunks)
🧰 Additional context used
📓 Path-based instructions (1)
**/*.{js,ts,tsx,rs}

⚙️ CodeRabbit configuration file

**/*.{js,ts,tsx,rs}: 1. Do not add any error handling. Keep the existing one.
2. No unused imports, variables, or functions.
3. For comments, keep it minimal. It should be about "Why", not "What".

Files:

  • crates/db-user/src/events_types.rs
  • crates/calendar-interface/src/lib.rs
  • crates/db-user/src/events_ops.rs
  • crates/db-core/src/lib.rs
  • apps/desktop/src/locales/ko/messages.ts
  • apps/desktop/src/components/organization-profile/upcoming-events.tsx
  • plugins/google-calendar/src/error.rs
  • apps/desktop/src-tauri/src/lib.rs
  • plugins/google-calendar/js/index.ts
  • plugins/google-calendar/src/oauth.rs
  • plugins/google-calendar/build.rs
  • apps/desktop/src/components/settings/components/calendar/calendar-icon-with-text.tsx
  • apps/desktop/src/components/human-profile/upcoming-events.tsx
  • apps/desktop/src/components/workspace-calendar/event-card.tsx
  • plugins/apple-calendar/src/sync.rs
  • crates/db-user/src/calendars_ops.rs
  • apps/desktop/src/components/left-sidebar/events-list.tsx
  • crates/db-user/src/calendars_types.rs
  • crates/calendar-apple/src/lib.rs
  • crates/db-user/src/lib.rs
  • apps/desktop/src/locales/en/messages.ts
  • apps/desktop/src/components/settings/components/ai/stt-view-local.tsx
  • crates/calendar-google/src/lib.rs
  • plugins/google-calendar/src/commands.rs
  • apps/desktop/src/components/settings/views/calendar.tsx
  • plugins/google-calendar/src/ext.rs
  • plugins/google-calendar/src/calendar_api.rs
  • plugins/google-calendar/src/lib.rs
  • apps/desktop/src/components/settings/components/calendar/google-calendar-integration-details.tsx
  • plugins/google-calendar/src/contacts_api.rs
  • plugins/google-calendar/src/worker.rs
🧬 Code graph analysis (12)
apps/desktop/src/locales/ko/messages.ts (1)
apps/desktop/src/locales/en/messages.ts (1)
  • messages (1-1)
apps/desktop/src-tauri/src/lib.rs (2)
plugins/tracing/src/lib.rs (1)
  • init (28-62)
plugins/connector/src/lib.rs (1)
  • init (53-59)
plugins/google-calendar/build.rs (1)
apps/desktop/src-tauri/src/lib.rs (1)
  • main (13-249)
crates/db-user/src/calendars_ops.rs (2)
crates/db-core/src/lib.rs (1)
  • conn (17-22)
plugins/google-calendar/src/ext.rs (2)
  • get_calendars_needing_reconnection (60-60)
  • get_calendars_needing_reconnection (494-526)
apps/desktop/src/locales/en/messages.ts (1)
apps/desktop/src/locales/ko/messages.ts (1)
  • messages (1-1)
plugins/google-calendar/src/commands.rs (3)
plugins/google-calendar/src/ext.rs (38)
  • sync_calendars (28-28)
  • sync_calendars (114-125)
  • get_calendars (29-29)
  • get_calendars (375-407)
  • sync_events (31-31)
  • sync_events (160-171)
  • sync_contacts (37-37)
  • sync_contacts (247-250)
  • get_contacts (38-38)
  • get_contacts (252-255)
  • search_contacts (39-39)
  • search_contacts (257-260)
  • revoke_access (41-41)
  • revoke_access (262-265)
  • refresh_tokens (42-42)
  • refresh_tokens (267-270)
  • get_connected_accounts (49-49)
  • get_connected_accounts (272-286)
  • add_google_account (50-50)
  • add_google_account (65-112)
  • remove_google_account (51-51)
  • remove_google_account (288-373)
  • get_calendars_for_account (52-52)
  • get_calendars_for_account (409-412)
  • get_contacts_for_account (53-53)
  • get_contacts_for_account (414-417)
  • get_calendar_selections (56-56)
  • get_calendar_selections (419-459)
  • set_calendar_selected (57-57)
  • set_calendar_selected (461-492)
  • start_worker (45-45)
  • start_worker (548-574)
  • stop_worker (46-46)
  • stop_worker (576-583)
  • get_calendars_needing_reconnection (60-60)
  • get_calendars_needing_reconnection (494-526)
  • attempt_reconnect_account (61-61)
  • attempt_reconnect_account (528-546)
plugins/google-calendar/src/contacts_api.rs (2)
  • get_contacts (91-118)
  • search_contacts (120-143)
crates/db-user/src/calendars_ops.rs (1)
  • get_calendars_needing_reconnection (181-200)
apps/desktop/src/components/settings/views/calendar.tsx (2)
apps/desktop/src/types/index.ts (1)
  • CalendarIntegration (21-23)
apps/desktop/src/components/settings/components/calendar/google-calendar-integration-details.tsx (1)
  • GoogleCalendarIntegrationDetails (81-305)
plugins/google-calendar/src/ext.rs (6)
plugins/apple-calendar/src/sync.rs (4)
  • sync_calendars (9-24)
  • sync_events (26-65)
  • db (435-440)
  • db (462-476)
plugins/google-calendar/src/commands.rs (19)
  • sync_calendars (36-38)
  • get_calendars (42-46)
  • sync_events (50-57)
  • sync_contacts (61-63)
  • get_contacts (67-71)
  • search_contacts (75-80)
  • revoke_access (84-86)
  • refresh_tokens (90-92)
  • start_worker (138-140)
  • stop_worker (144-147)
  • get_connected_accounts (96-98)
  • add_google_account (102-104)
  • remove_google_account (108-110)
  • get_calendars_for_account (114-116)
  • get_contacts_for_account (120-122)
  • get_calendar_selections (126-128)
  • set_calendar_selected (132-134)
  • get_calendars_needing_reconnection (151-155)
  • attempt_reconnect_account (159-164)
plugins/google-calendar/src/contacts_api.rs (3)
  • get_contacts (91-118)
  • search_contacts (120-143)
  • new (62-67)
plugins/google-calendar/src/oauth.rs (2)
  • new (60-65)
  • refresh_token (123-153)
apps/desktop/src-tauri/src/ext.rs (2)
  • state (95-95)
  • app (176-176)
plugins/google-calendar/src/worker.rs (1)
  • monitor (43-67)
plugins/google-calendar/src/calendar_api.rs (1)
plugins/google-calendar/src/contacts_api.rs (2)
  • new (62-67)
  • make_request (69-89)
plugins/google-calendar/src/lib.rs (3)
apps/desktop/src-tauri/src/lib.rs (3)
  • make_specta_builder (251-263)
  • make_specta_builder (271-271)
  • export_types (270-280)
plugins/google-calendar/src/ext.rs (38)
  • sync_calendars (28-28)
  • sync_calendars (114-125)
  • get_calendars (29-29)
  • get_calendars (375-407)
  • sync_events (31-31)
  • sync_events (160-171)
  • sync_contacts (37-37)
  • sync_contacts (247-250)
  • get_contacts (38-38)
  • get_contacts (252-255)
  • search_contacts (39-39)
  • search_contacts (257-260)
  • revoke_access (41-41)
  • revoke_access (262-265)
  • refresh_tokens (42-42)
  • refresh_tokens (267-270)
  • get_connected_accounts (49-49)
  • get_connected_accounts (272-286)
  • add_google_account (50-50)
  • add_google_account (65-112)
  • remove_google_account (51-51)
  • remove_google_account (288-373)
  • get_calendars_for_account (52-52)
  • get_calendars_for_account (409-412)
  • get_contacts_for_account (53-53)
  • get_contacts_for_account (414-417)
  • get_calendar_selections (56-56)
  • get_calendar_selections (419-459)
  • set_calendar_selected (57-57)
  • set_calendar_selected (461-492)
  • start_worker (45-45)
  • start_worker (548-574)
  • stop_worker (46-46)
  • stop_worker (576-583)
  • get_calendars_needing_reconnection (60-60)
  • get_calendars_needing_reconnection (494-526)
  • attempt_reconnect_account (61-61)
  • attempt_reconnect_account (528-546)
plugins/google-calendar/src/commands.rs (19)
  • sync_calendars (36-38)
  • get_calendars (42-46)
  • sync_events (50-57)
  • sync_contacts (61-63)
  • get_contacts (67-71)
  • search_contacts (75-80)
  • revoke_access (84-86)
  • refresh_tokens (90-92)
  • get_connected_accounts (96-98)
  • add_google_account (102-104)
  • remove_google_account (108-110)
  • get_calendars_for_account (114-116)
  • get_contacts_for_account (120-122)
  • get_calendar_selections (126-128)
  • set_calendar_selected (132-134)
  • start_worker (138-140)
  • stop_worker (144-147)
  • get_calendars_needing_reconnection (151-155)
  • attempt_reconnect_account (159-164)
apps/desktop/src/components/settings/components/calendar/google-calendar-integration-details.tsx (2)
packages/ui/src/components/ui/toast.tsx (1)
  • toast (67-86)
packages/ui/src/components/ui/button.tsx (1)
  • Button (37-89)
plugins/google-calendar/src/contacts_api.rs (3)
plugins/google-calendar/src/calendar_api.rs (4)
  • new (99-104)
  • make_request (106-128)
  • make_request (190-190)
  • make_request (206-206)
plugins/google-calendar/src/ext.rs (4)
  • get_contacts (38-38)
  • get_contacts (252-255)
  • search_contacts (39-39)
  • search_contacts (257-260)
plugins/google-calendar/src/commands.rs (2)
  • get_contacts (67-71)
  • search_contacts (75-80)
🔇 Additional comments (37)
apps/desktop/src/components/settings/components/ai/stt-view-local.tsx (1)

15-15: Verify that this change belongs in this PR.

This modification expands the default models to include "QuantizedBase". However, the PR is titled "Adding google calendar support", and this STT model configuration appears unrelated to that objective.

Please confirm whether this change was intentionally included or if it should be moved to a separate PR focused on STT model improvements.

apps/desktop/src/components/organization-profile/upcoming-events.tsx (1)

68-70: Field rename is consistent.

The change from google_event_url to event_external_url is correctly applied in both the conditional check and href attribute, matching the pattern used in other components.

apps/desktop/src/components/workspace-calendar/event-card.tsx (1)

88-90: Field rename is correct with proper null check.

The change from google_event_url to event_external_url is properly guarded with a conditional check and preserves the existing error handling.

apps/desktop/src/components/human-profile/upcoming-events.tsx (1)

61-63: Unable to verify Event type in this repo
Event is imported from @hypr/plugin-db, so please confirm in that package that the Event type defines event_external_url and that all google_event_url references have been removed.

apps/desktop/src-tauri/capabilities/default.json (1)

40-40: LGTM!

The permission addition follows the established pattern and is correctly positioned after auth-related permissions.

apps/desktop/src/components/settings/components/calendar/calendar-icon-with-text.tsx (2)

1-1: LGTM!

Replacing the custom GoogleIcon with the Remix Icon library improves consistency with AppleIcon and reduces maintenance overhead.


11-11: LGTM!

The explicit size prop ensures consistent icon sizing across calendar types.

plugins/google-calendar/tsconfig.json (1)

1-10: Approve TypeScript configuration Extended base config at plugins/tsconfig.base.json exists and path mappings are correct.

apps/desktop/src-tauri/src/lib.rs (1)

78-78: LGTM: plugin wired into startup chain.

Order looks fine next to auth/db/specta-driven plugins; no additional handling added.

plugins/db/seed/onboarding.json (1)

60-60: Verified no leftover google_event_url
All seeds, types, and code now use event_external_url; the only remaining google_event_url references are in the initial migration (crates/db-user/src/events_migration.sql) and its rename migration, which are expected.

crates/db-user/src/lib.rs (2)

145-145: LGTM! Migration array size updated correctly.

The array size increase from 27 to 29 matches the two new migrations appended below.


173-174: LGTM! Migrations appended correctly.

The new migrations are properly appended to the end of the array, following the "append only" policy. The ordering (calendars_migration_2 before events_migration_3) is appropriate.

apps/desktop/src-tauri/Cargo.toml (1)

35-35: LGTM! Dependency added correctly.

The new Google Calendar plugin dependency follows the project's workspace dependency pattern and maintains alphabetical ordering.

crates/db-user/src/events_ops.rs (3)

42-42: LGTM! UPDATE query field renamed consistently.

Both the SQL column name and parameter binding correctly use the new event_external_url field name.

Also applies to: 55-55


88-88: LGTM! INSERT/UPSERT queries field renamed consistently.

All occurrences of the field in the upsert operation (column list, VALUES clause, UPDATE SET clause, and parameter binding) correctly use the new event_external_url name.

Also applies to: 100-100, 108-108, 121-121


248-248: LGTM! Test updated correctly.

The test construction and assertion now use event_external_url instead of the old field name.

Also applies to: 255-255

crates/db-user/src/events_migration_3.sql (1)

1-1: LGTM! Column rename migration is correct.

The migration properly renames the column from google_event_url to the more generic event_external_url, supporting multi-provider calendar integration. The SQLite RENAME COLUMN syntax is correct and preserves existing data.

Cargo.toml (1)

111-111: LGTM!

The workspace dependency for the Google Calendar plugin is correctly added with the standard path pattern and proper alphabetical ordering.

plugins/google-calendar/package.json (1)

1-11: LGTM!

The package structure follows the standard Tauri plugin pattern with:

  • Appropriate workspace privacy setting
  • Correct TypeScript entry point
  • Standard codegen script for type generation
  • Compatible Tauri API dependency
apps/desktop/package.json (1)

24-24: LGTM!

The Google Calendar plugin dependency is correctly added with the workspace protocol and proper alphabetical ordering among other plugin dependencies.

crates/calendar-google/src/lib.rs (1)

106-106: Approve field rename

No references to google_event_url remain; event_external_url is applied consistently across the interface definitions, database schema, and all provider implementations.

crates/db-user/src/events_types.rs (1)

15-15: LGTM! Field rename generalizes the naming.

The rename from google_event_url to event_external_url is a sensible generalization that reflects support for multiple calendar platforms (Apple, Google, Outlook) rather than being Google-specific.

plugins/google-calendar/build.rs (1)

21-23: LGTM! Standard Tauri plugin build pattern.

The build script correctly registers the plugin commands using the standard Tauri plugin builder pattern.

crates/calendar-interface/src/lib.rs (2)

47-47: LGTM! Field rename aligns with broader refactoring.

The rename from google_event_url to event_external_url is consistent with the changes in other files and generalizes the field name for multi-platform support.


88-88: LGTM! Usage correctly updated to new field name.

The reference to the renamed field is properly updated in the Google platform branch.

plugins/db/seed/dev.json (1)

64-311: LGTM! Seed data correctly updated with field rename.

All event objects in the seed data have been consistently updated to use event_external_url instead of google_event_url, with values properly preserved.

apps/desktop/src/components/settings/views/calendar.tsx (2)

1-10: LGTM! Google Calendar integration added correctly.

The import and supportedIntegrations array are properly updated to include Google Calendar support.


34-35: LGTM! Conditional rendering is correct.

The conditional rendering logic properly displays the appropriate integration details component based on the integration type.

plugins/db/seed/schema.json (2)

163-186: LGTM! Calendar sync status fields added correctly.

The new optional fields (connection_status, account_id, last_sync_error, last_sync_at) provide the necessary metadata to track synchronization state and account associations for multi-account calendar integrations.


239-239: LGTM! Field rename improves generality.

Renaming google_event_url to event_external_url makes the field platform-agnostic and better reflects its purpose for any external calendar provider.

crates/calendar-apple/src/lib.rs (1)

255-255: LGTM! Field rename applied correctly.

The Event construction now uses event_external_url (previously google_event_url), aligning with the platform-agnostic rename across the codebase. Setting it to None for Apple Calendar events is appropriate.

crates/db-user/src/calendars_ops.rs (2)

78-135: LGTM! Calendar upsert extended correctly.

The upsert operation now properly handles the new fields (connection_status, account_id, last_sync_error, last_sync_at) in both the INSERT and UPDATE paths, with corresponding parameter bindings.


181-200: LGTM! Reconnection query is well-structured.

The get_calendars_needing_reconnection method correctly queries calendars by user and connection status, providing a clean interface for identifying calendars requiring reconnection.

apps/desktop/src/components/left-sidebar/events-list.tsx (1)

40-56: Error swallowing may mask sync failures.

The try/catch block logs errors but doesn't rethrow them, which means:

  1. The mutation always resolves successfully (never rejects), even when both syncs fail.
  2. The onSuccess callback (lines 58-64) fires and invalidates queries regardless of sync failure.
  3. Users won't see any indication that sync failed, and the UI may show stale data.

Consider one of these approaches:

Option 1: Let errors propagate and handle them in the mutation callbacks:

-      try {
-      const result = await Promise.all([
-        googleCalendarCommands.syncEvents(null),
-        appleCalendarCommands.syncEvents(),
-      ]);
-      const elapsedTime = Date.now() - startTime;
-
-      if (elapsedTime < 500) {
-        await new Promise(resolve => setTimeout(resolve, 500 - elapsedTime));
-      }
-
-        return result;
-      } catch (error) {
-        console.error("error", error);
-      }
+      const result = await Promise.all([
+        googleCalendarCommands.syncEvents(null),
+        appleCalendarCommands.syncEvents(),
+      ]);
+      const elapsedTime = Date.now() - startTime;
+
+      if (elapsedTime < 500) {
+        await new Promise(resolve => setTimeout(resolve, 500 - elapsedTime));
+      }
+
+      return result;

Then add an onError callback to the mutation to handle failures appropriately.

Option 2: Use Promise.allSettled if partial sync is acceptable:

-      try {
-      const result = await Promise.all([
+      const results = await Promise.allSettled([
         googleCalendarCommands.syncEvents(null),
         appleCalendarCommands.syncEvents(),
       ]);
+      
+      // Check if any syncs failed
+      const failures = results.filter(r => r.status === 'rejected');
+      if (failures.length > 0) {
+        console.error("Some syncs failed:", failures);
+      }
+      
       const elapsedTime = Date.now() - startTime;
 
       if (elapsedTime < 500) {
         await new Promise(resolve => setTimeout(resolve, 500 - elapsedTime));
       }
 
-        return result;
-      } catch (error) {
-        console.error("error", error);
-      }
+      return results;

Likely an incorrect or invalid review comment.

plugins/apple-calendar/src/sync.rs (2)

102-106: Calendar metadata defaults look good; confirm allowed values.

Ensure connection_status = "connected" and account_id = "local" match the new DB contract/enums for calendars.


175-176: Field rename to event_external_url is consistent.

Updates/new events preserve or clear the external link as expected.

If any downstream code still references google_event_url, I can scan the repo and list stragglers.

Also applies to: 219-221, 301-306, 325-329

plugins/google-calendar/src/commands.rs (1)

136-141: LGTM: start/stop worker commands are thin pass-throughs.

Clear delegation to app extension; no extra error handling introduced.

setProviderToLocal,
}: STTViewProps) {

console.log("sttModels", sttModels);
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 debugging console.log statements.

These console.log statements should be removed before merging. Debug logs in production code clutter the console and may inadvertently expose information.

Apply this diff to remove the debug statements:

-
-  console.log("sttModels", sttModels);
   const { userId } = useHypr();
   ), [sttModels]);
-
-  console.log("modelsToShow", modelsToShow);
-

Also applies to: 132-132

🤖 Prompt for AI Agents
In apps/desktop/src/components/settings/components/ai/stt-view-local.tsx around
lines 60 and 132, remove the debugging console.log statements (e.g.,
console.log("sttModels", sttModels); and the one at line 132) so no debug output
remains in production code; update the file by deleting those console.log lines
and run a quick lint/check to ensure no unused variables or trailing commas are
introduced.

Comment on lines +51 to +59
<Checkbox
id={`calendar-${calSelection.calendarId}`}
checked={calSelection.selected}
onCheckedChange={(checked) =>
toggleCalendarSelectedMutation.mutate({
calendarId: calSelection.calendarId,
selected: checked as boolean
})
}
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

Do not cast CheckedState; coerce to strict boolean.

onCheckedChange may receive "indeterminate" (Radix CheckedState). Casting with as boolean is unsafe and can pass a string at runtime. Use a strict check.

Apply this diff:

-              onCheckedChange={(checked) => 
-                toggleCalendarSelectedMutation.mutate({ 
-                  calendarId: calSelection.calendarId, 
-                  selected: checked as boolean 
-                })
-              }
+              onCheckedChange={(checked) =>
+                toggleCalendarSelectedMutation.mutate({
+                  calendarId: calSelection.calendarId,
+                  selected: checked === true,
+                })
+              }
📝 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
<Checkbox
id={`calendar-${calSelection.calendarId}`}
checked={calSelection.selected}
onCheckedChange={(checked) =>
toggleCalendarSelectedMutation.mutate({
calendarId: calSelection.calendarId,
selected: checked as boolean
})
}
<Checkbox
id={`calendar-${calSelection.calendarId}`}
checked={calSelection.selected}
onCheckedChange={(checked) =>
toggleCalendarSelectedMutation.mutate({
calendarId: calSelection.calendarId,
selected: checked === true,
})
}
🤖 Prompt for AI Agents
In
apps/desktop/src/components/settings/components/calendar/google-calendar-integration-details.tsx
around lines 51 to 59, the code unsafely casts Radix CheckedState to boolean
using "as boolean"; replace the cast with a strict boolean coercion (e.g.,
compute selected = checked === true or Boolean(checked) so only true becomes
true and "indeterminate"/false become false) and pass that strict boolean into
toggleCalendarSelectedMutation.mutate({ calendarId: calSelection.calendarId,
selected }); ensure no TypeScript "as boolean" cast remains.

Comment on lines +185 to +197
// Split multiple SQL statements and execute each one separately
let migration_content = migration.as_ref().trim();
if migration_content.is_empty() {
continue;
}

// Split by semicolon and execute each statement
for statement in migration_content.split(';') {
let statement = statement.trim();
if !statement.is_empty() {
tx.execute(statement, ()).await?;
}
}
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 Major

🧩 Analysis chain

Naive semicolon splitting may break complex SQL statements.

Splitting by semicolon without parsing can fail when:

  • String literals contain semicolons: INSERT INTO t VALUES ('text;data')
  • Triggers or procedures contain semicolons in their body
  • Comments contain semicolons: -- comment with ; inside

This could cause migration failures or partial execution.

Consider these alternatives:

Option 1: Use libsql's batch execution (if available)

         for migration in migrations.iter().skip(current_version as usize) {
-            // Split multiple SQL statements and execute each one separately
-            let migration_content = migration.as_ref().trim();
-            if migration_content.is_empty() {
-                continue;
-            }
-            
-            // Split by semicolon and execute each statement
-            for statement in migration_content.split(';') {
-                let statement = statement.trim();
-                if !statement.is_empty() {
-                    tx.execute(statement, ()).await?;
-                }
-            }
+            let migration_content = migration.as_ref().trim();
+            if !migration_content.is_empty() {
+                tx.execute_batch(migration_content).await?;
+            }
         }

Option 2: Keep one statement per migration file

Ensure each migration file contains a single statement, avoiding the need for splitting entirely.

Check if libsql supports batch execution:


🏁 Script executed:

#!/bin/bash
# Search for batch execution methods in libsql usage
rg -n 'execute_batch|executescript|batch' --type rust -g '**/Cargo.toml' -g '**/src/**'

# Check libsql documentation
ast-grep --pattern 'impl $_ {
  $$$
  execute_batch($$$) {
    $$$
  }
  $$$
}'

Length of output: 3278


Avoid naive semicolon-based splitting in migrations
Splitting the SQL by ; will break on semicolons in literals, comments, and procedural blocks—use a proper SQL parser (e.g. the sqlparser crate) to split statements reliably or enforce one statement per migration file.

Comment on lines +158 to +179
pub async fn update_calendar_sync_status(
&self,
tracking_id: impl AsRef<str>,
connection_status: impl AsRef<str>,
sync_error: Option<String>,
sync_time: impl AsRef<str>,
) -> Result<(), crate::Error> {
let conn = self.conn()?;

conn.execute(
"UPDATE calendars SET connection_status = ?, last_sync_error = ?, last_sync_at = ? WHERE tracking_id = ?",
vec![
connection_status.as_ref(),
sync_error.as_deref().unwrap_or_default(),
sync_time.as_ref(),
tracking_id.as_ref(),
],
)
.await?;

Ok(())
}
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 | 🔴 Critical

🧩 Analysis chain

Verify last_sync_error column nullability.

Line 171 uses sync_error.as_deref().unwrap_or_default() to convert None to an empty string. Ensure this behavior aligns with the database schema:

  • If last_sync_error is nullable, passing an empty string instead of NULL may not be semantically correct.
  • If last_sync_error is NOT NULL, the current approach is appropriate.

Run the following script to check the schema definition:


🏁 Script executed:

#!/bin/bash
# Check if last_sync_error column is nullable in the calendars table schema
rg -nP -C3 'last_sync_error' --type=sql

Length of output: 467


Pass SQL NULL for None sync errors instead of ""
The last_sync_error column is defined as nullable (ADD COLUMN last_sync_error TEXT; in calendars_migration_2.sql), so mapping None to an empty string changes its meaning. Update the parameter binding in update_calendar_sync_status to use Option<&str> (e.g. sync_error.as_deref()) so that None is stored as SQL NULL.

🤖 Prompt for AI Agents
In crates/db-user/src/calendars_ops.rs around lines 158 to 179, the code
currently binds last_sync_error as sync_error.as_deref().unwrap_or_default()
which stores an empty string instead of SQL NULL for None; change the parameter
binding to pass an Option<&str> by using sync_error.as_deref() directly (i.e.,
do not unwrap_or_default) so the DB layer can bind NULL for None, and ensure the
execute parameter vector accepts the Option<&str> type (adjust imports/traits or
convert to a DB-friendly nullable binder if needed).

authors = ["You"]
edition = "2021"
exclude = ["/js", "/node_modules"]
links = "tauri-plugin-google-calendar"
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 | 🔴 Critical

Remove Cargo links — it’s for native “-sys” crates and will cause Cargo link uniqueness conflicts.

This crate doesn’t build a native library; keeping links can break builds in workspaces and is incorrect for a pure Rust plugin.

Apply this diff:

-links = "tauri-plugin-google-calendar"
📝 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
links = "tauri-plugin-google-calendar"
🤖 Prompt for AI Agents
In plugins/google-calendar/Cargo.toml around line 7, remove the Cargo.toml key
`links = "tauri-plugin-google-calendar"` because `links` is only for native
“-sys” crates and will cause link uniqueness conflicts in workspaces; delete
that line from the manifest, save the file, and run a local Cargo build/metadata
check to ensure the workspace builds correctly without the `links` entry.

Comment on lines +62 to +72
pub fn init<R: tauri::Runtime>() -> tauri::plugin::TauriPlugin<R> {
let specta_builder = make_specta_builder();

tauri::plugin::Builder::new(PLUGIN_NAME)
.invoke_handler(specta_builder.invoke_handler())
.setup(|app, _api| {
app.manage(ManagedState::default());
Ok(())
})
.build()
}
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 Major

Constrain the plugin to Wry and remove generic R here.

This aligns the plugin with the commands and enables removing the unsafe transmute in ext.rs (follow-up).

-pub fn init<R: tauri::Runtime>() -> tauri::plugin::TauriPlugin<R> {
-    let specta_builder = make_specta_builder();
+pub fn init() -> tauri::plugin::TauriPlugin<tauri::Wry> {
+    let specta_builder = make_specta_builder();
 
-    tauri::plugin::Builder::new(PLUGIN_NAME)
+    tauri::plugin::Builder::<tauri::Wry>::new(PLUGIN_NAME)
         .invoke_handler(specta_builder.invoke_handler())
         .setup(|app, _api| {
             app.manage(ManagedState::default());
             Ok(())
         })
         .build()
 }
📝 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
pub fn init<R: tauri::Runtime>() -> tauri::plugin::TauriPlugin<R> {
let specta_builder = make_specta_builder();
tauri::plugin::Builder::new(PLUGIN_NAME)
.invoke_handler(specta_builder.invoke_handler())
.setup(|app, _api| {
app.manage(ManagedState::default());
Ok(())
})
.build()
}
pub fn init() -> tauri::plugin::TauriPlugin<tauri::Wry> {
let specta_builder = make_specta_builder();
tauri::plugin::Builder::<tauri::Wry>::new(PLUGIN_NAME)
.invoke_handler(specta_builder.invoke_handler())
.setup(|app, _api| {
app.manage(ManagedState::default());
Ok(())
})
.build()
}
🤖 Prompt for AI Agents
In plugins/google-calendar/src/lib.rs around lines 62 to 72, change the function
to be concrete for the Wry runtime instead of generic R: replace the signature
pub fn init<R: tauri::Runtime>() -> tauri::plugin::TauriPlugin<R> with a
concrete pub fn init() -> tauri::plugin::TauriPlugin<tauri::Wry> (and remove the
generic parameter everywhere inside), update any local variables or calls that
referenced R to use tauri::Wry, and adjust imports if necessary; this constrains
the plugin to Wry and lets you remove the unsafe transmute in ext.rs in a
follow-up.

@@ -0,0 +1,67 @@
use apalis::prelude::{Data, Error, WorkerBuilder, WorkerFactoryFn};
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

Remove unused import to satisfy “no unused imports”.

WorkerFactoryFn isn’t used.

Apply:

-use apalis::prelude::{Data, Error, WorkerBuilder, WorkerFactoryFn};
+use apalis::prelude::{Data, Error, WorkerBuilder};
📝 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
use apalis::prelude::{Data, Error, WorkerBuilder, WorkerFactoryFn};
use apalis::prelude::{Data, Error, WorkerBuilder};
🤖 Prompt for AI Agents
In plugins/google-calendar/src/worker.rs around lines 1 to 1, the use statement
imports WorkerFactoryFn which is unused and triggers a “no unused imports” lint;
remove WorkerFactoryFn from the apalis::prelude use list (leaving Data, Error,
WorkerBuilder) so the import only contains symbols actually used in the file.

Comment on lines +7 to +22
#[allow(unused)]
#[derive(Default, Debug, Clone)]
pub struct Job(DateTime<Utc>);

#[derive(Clone)]
pub struct WorkerState {
pub db: hypr_db_user::UserDatabase,
pub user_id: String,
pub app_handle: tauri::AppHandle<tauri::Wry>,
}

impl From<DateTime<Utc>> for Job {
fn from(t: DateTime<Utc>) -> Self {
Job(t)
}
}
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 | 🔴 Critical

Drop the Job wrapper; it causes type mismatch with CronStream and adds a likely non-compiling Default derive.

  • CronStream yields DateTime<Utc>; worker fns expecting Job won’t match unless you map the stream.
  • #[derive(Default)] on Job(DateTime<Utc>) likely fails because DateTime<Utc> doesn’t implement Default.

Apply:

-#[allow(unused)]
-#[derive(Default, Debug, Clone)]
-pub struct Job(DateTime<Utc>);
-
-impl From<DateTime<Utc>> for Job {
-    fn from(t: DateTime<Utc>) -> Self {
-        Job(t)
-    }
-}

Then update worker function signatures to take DateTime<Utc> directly (see next comment).
As per coding guidelines.

📝 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
#[allow(unused)]
#[derive(Default, Debug, Clone)]
pub struct Job(DateTime<Utc>);
#[derive(Clone)]
pub struct WorkerState {
pub db: hypr_db_user::UserDatabase,
pub user_id: String,
pub app_handle: tauri::AppHandle<tauri::Wry>,
}
impl From<DateTime<Utc>> for Job {
fn from(t: DateTime<Utc>) -> Self {
Job(t)
}
}
#[derive(Clone)]
pub struct WorkerState {
pub db: hypr_db_user::UserDatabase,
pub user_id: String,
pub app_handle: tauri::AppHandle<tauri::Wry>,
}
🤖 Prompt for AI Agents
In plugins/google-calendar/src/worker.rs around lines 7 to 22, the newtype
wrapper Job(DateTime<Utc>) should be removed because CronStream yields
DateTime<Utc>, the #[derive(Default)] is invalid for DateTime<Utc>, and the From
impl is unnecessary; delete the Job struct and its impl, remove the Default and
unused attributes, then update any worker function signatures and call sites to
accept DateTime<Utc> directly (and adjust any pattern matches/uses accordingly)
so types line up with CronStream without extra mapping.

Comment on lines +27 to +33
#[tracing::instrument(skip(ctx), name = CALENDARS_SYNC_WORKER_NAME)]
pub async fn perform_calendars_sync(_job: Job, ctx: Data<WorkerState>) -> Result<(), Error> {
ctx.app_handle.sync_calendars_with_db(ctx.db.clone(), ctx.user_id.clone())
.await
.map_err(|e| Error::Failed(Arc::new(Box::new(e) as Box<dyn std::error::Error + Send + Sync>)))?;
Ok(())
}
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 | 🔴 Critical

Use string literals in #[instrument(name = ...)] and align param type with CronStream item.

instrument’s name expects a string literal; passing a const identifier is not accepted. Also, switch _job: Job_tick: DateTime<Utc> to match the backend.

Apply:

-#[tracing::instrument(skip(ctx), name = CALENDARS_SYNC_WORKER_NAME)]
-pub async fn perform_calendars_sync(_job: Job, ctx: Data<WorkerState>) -> Result<(), Error> {
+#[tracing::instrument(skip(ctx), name = "google_calendar_calendars_sync")]
+pub async fn perform_calendars_sync(_tick: DateTime<Utc>, ctx: Data<WorkerState>) -> Result<(), Error> {
     ctx.app_handle.sync_calendars_with_db(ctx.db.clone(), ctx.user_id.clone())
         .await
         .map_err(|e| Error::Failed(Arc::new(Box::new(e) as Box<dyn std::error::Error + Send + Sync>)))?;
     Ok(())
 }

Note: No new error handling added; only signature and attribute fixes. As per coding guidelines.

📝 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
#[tracing::instrument(skip(ctx), name = CALENDARS_SYNC_WORKER_NAME)]
pub async fn perform_calendars_sync(_job: Job, ctx: Data<WorkerState>) -> Result<(), Error> {
ctx.app_handle.sync_calendars_with_db(ctx.db.clone(), ctx.user_id.clone())
.await
.map_err(|e| Error::Failed(Arc::new(Box::new(e) as Box<dyn std::error::Error + Send + Sync>)))?;
Ok(())
}
#[tracing::instrument(skip(ctx), name = "google_calendar_calendars_sync")]
pub async fn perform_calendars_sync(_tick: DateTime<Utc>, ctx: Data<WorkerState>) -> Result<(), Error> {
ctx.app_handle.sync_calendars_with_db(ctx.db.clone(), ctx.user_id.clone())
.await
.map_err(|e| Error::Failed(Arc::new(Box::new(e) as Box<dyn std::error::Error + Send + Sync>)))?;
Ok(())
}
🤖 Prompt for AI Agents
In plugins/google-calendar/src/worker.rs around lines 27 to 33, the tracing
instrument attribute currently passes a const identifier for name and the
function signature uses Job; change the attribute to use a string literal e.g.
#[tracing::instrument(skip(ctx), name = "calendars_sync_worker")] and update the
function signature to accept the CronStream item type: pub async fn
perform_calendars_sync(_tick: chrono::DateTime<chrono::Utc>, ctx:
Data<WorkerState>) -> Result<(), Error> (rename the parameter to _tick to match
the backend); ensure chrono::DateTime and chrono::Utc are in scope or import
them at top of the file.

Comment on lines +35 to +41
#[tracing::instrument(skip(ctx), name = EVENTS_SYNC_WORKER_NAME)]
pub async fn perform_events_sync(_job: Job, ctx: Data<WorkerState>) -> Result<(), Error> {
ctx.app_handle.sync_events_with_db(ctx.db.clone(), ctx.user_id.clone(), None)
.await
.map_err(|e| Error::Failed(Arc::new(Box::new(e) as Box<dyn std::error::Error + Send + Sync>)))?;
Ok(())
}
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 | 🔴 Critical

Mirror the same fixes for events worker.

Use a literal name and DateTime<Utc> in the signature.

Apply:

-#[tracing::instrument(skip(ctx), name = EVENTS_SYNC_WORKER_NAME)]
-pub async fn perform_events_sync(_job: Job, ctx: Data<WorkerState>) -> Result<(), Error> {
+#[tracing::instrument(skip(ctx), name = "google_calendar_events_sync")]
+pub async fn perform_events_sync(_tick: DateTime<Utc>, ctx: Data<WorkerState>) -> Result<(), Error> {
     ctx.app_handle.sync_events_with_db(ctx.db.clone(), ctx.user_id.clone(), None)
         .await
         .map_err(|e| Error::Failed(Arc::new(Box::new(e) as Box<dyn std::error::Error + Send + Sync>)))?;
     Ok(())
 }
📝 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
#[tracing::instrument(skip(ctx), name = EVENTS_SYNC_WORKER_NAME)]
pub async fn perform_events_sync(_job: Job, ctx: Data<WorkerState>) -> Result<(), Error> {
ctx.app_handle.sync_events_with_db(ctx.db.clone(), ctx.user_id.clone(), None)
.await
.map_err(|e| Error::Failed(Arc::new(Box::new(e) as Box<dyn std::error::Error + Send + Sync>)))?;
Ok(())
}
#[tracing::instrument(skip(ctx), name = "google_calendar_events_sync")]
pub async fn perform_events_sync(_tick: DateTime<Utc>, ctx: Data<WorkerState>) -> Result<(), Error> {
ctx.app_handle.sync_events_with_db(ctx.db.clone(), ctx.user_id.clone(), None)
.await
.map_err(|e| Error::Failed(Arc::new(Box::new(e) as Box<dyn std::error::Error + Send + Sync>)))?;
Ok(())
}
🤖 Prompt for AI Agents
In plugins/google-calendar/src/worker.rs around lines 35 to 41, change the
worker to use a literal instrument name and accept a DateTime<Utc> instead of
the unused Job: replace the attribute name = EVENTS_SYNC_WORKER_NAME with a
literal string (e.g. name = "events_sync_worker"), change the fn signature from
perform_events_sync(_job: Job, ctx: Data<WorkerState>) to
perform_events_sync(_scheduled_at: DateTime<Utc>, ctx: Data<WorkerState>) and
pass Some(_scheduled_at) into ctx.app_handle.sync_events_with_db(...), and add
the necessary chrono imports (chrono::DateTime, chrono::Utc) while removing the
unused Job import.

@yujonglee
Copy link
Contributor

@githubdoramon thanks for the effort. unfortunately, we are doing partial rewrite of Hyprnote, (targeting early next week), so it is bit tricky to merge this right now. we will take this as inspiration. thank you!

@yujonglee yujonglee closed this Oct 6, 2025
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.

2 participants