-
Notifications
You must be signed in to change notification settings - Fork 0
feat: add calendar cache status and actions (#22532) #1
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: calendar-cache-foundation
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -1019,6 +1019,9 @@ export default class GoogleCalendarService implements Calendar { | |||||||||
| const data = await this.fetchAvailability(parsedArgs); | ||||||||||
| await this.setAvailabilityInCache(parsedArgs, data); | ||||||||||
| } | ||||||||||
|
|
||||||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🔴 CRITICAL — Business Logic Correctness (confidence: 98%) Calling Evidence:
Agent: logic There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🔴 CRITICAL — Business Logic Correctness (confidence: 100%) Calling Evidence:
Agent: logic There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🟡 MINOR — Data Integrity / Silent Failure (confidence: 100%) Calling Evidence:
Agent: security There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🔴 CRITICAL — Business logic correctness (confidence: 100%) Calling Evidence:
Agent: logic There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🟡 MINOR — Logic Bug / Silent Failure (confidence: 98%) Calling Evidence:
Agent: security There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ✅
Suggested change
🤖 Grapple PR auto-fix • critical • confidence: 100% There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ✅
Suggested change
🤖 Grapple PR auto-fix • minor • confidence: 98% |
||||||||||
| // Update SelectedCalendar.updatedAt for all calendars under this credential | ||||||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🟠 MAJOR — Scalability concerns (confidence: 97%) Passing an empty object Evidence:
Agent: architecture There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🟠 MAJOR — Behavioral Correctness (confidence: 100%) Calling Evidence:
Agent: architecture |
||||||||||
| await SelectedCalendarRepository.updateManyByCredentialId(this.credential.id, {}); | ||||||||||
| } | ||||||||||
|
|
||||||||||
| async createSelectedCalendar( | ||||||||||
|
|
||||||||||
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
| @@ -0,0 +1,157 @@ | ||||||
| "use client"; | ||||||
|
|
||||||
| import { useState } from "react"; | ||||||
|
|
||||||
| import { useLocale } from "@calcom/lib/hooks/useLocale"; | ||||||
| import { GOOGLE_CALENDAR_TYPE } from "@calcom/platform-constants"; | ||||||
| import { trpc } from "@calcom/trpc/react"; | ||||||
| import { Button } from "@calcom/ui/components/button"; | ||||||
| import { ConfirmationDialogContent } from "@calcom/ui/components/dialog"; | ||||||
| import { Dialog } from "@calcom/ui/components/dialog"; | ||||||
| import { | ||||||
| Dropdown, | ||||||
| DropdownItem, | ||||||
| DropdownMenuContent, | ||||||
| DropdownMenuItem, | ||||||
| DropdownMenuTrigger, | ||||||
| } from "@calcom/ui/components/dropdown"; | ||||||
| import { showToast } from "@calcom/ui/components/toast"; | ||||||
|
|
||||||
| interface CredentialActionsDropdownProps { | ||||||
| credentialId: number; | ||||||
| integrationType: string; | ||||||
| cacheUpdatedAt?: Date | null; | ||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🟡 MINOR — Data flow (confidence: 88%) The Evidence:
Agent: logic There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ✅
Suggested change
🤖 Grapple PR auto-fix • minor • confidence: 88% |
||||||
| onSuccess?: () => void; | ||||||
| delegationCredentialId?: string | null; | ||||||
| disableConnectionModification?: boolean; | ||||||
| } | ||||||
|
|
||||||
| export default function CredentialActionsDropdown({ | ||||||
| credentialId, | ||||||
| integrationType, | ||||||
| cacheUpdatedAt, | ||||||
| onSuccess, | ||||||
| delegationCredentialId, | ||||||
| disableConnectionModification, | ||||||
| }: CredentialActionsDropdownProps) { | ||||||
| const { t } = useLocale(); | ||||||
| const [dropdownOpen, setDropdownOpen] = useState(false); | ||||||
| const [deleteModalOpen, setDeleteModalOpen] = useState(false); | ||||||
| const [disconnectModalOpen, setDisconnectModalOpen] = useState(false); | ||||||
|
|
||||||
| const deleteCacheMutation = trpc.viewer.calendars.deleteCache.useMutation({ | ||||||
| onSuccess: () => { | ||||||
| showToast(t("cache_deleted_successfully"), "success"); | ||||||
| onSuccess?.(); | ||||||
| }, | ||||||
| onError: () => { | ||||||
| showToast(t("error_deleting_cache"), "error"); | ||||||
| }, | ||||||
| }); | ||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🟡 MINOR — Cross-service impact (confidence: 100%) After Evidence:
Agent: architecture |
||||||
|
|
||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🟠 MAJOR — Data Freshness / State Management (confidence: 100%) After successfully deleting the cache via Evidence:
Agent: logic |
||||||
| const utils = trpc.useUtils(); | ||||||
| const disconnectMutation = trpc.viewer.credentials.delete.useMutation({ | ||||||
| onSuccess: () => { | ||||||
| showToast(t("app_removed_successfully"), "success"); | ||||||
| onSuccess?.(); | ||||||
| }, | ||||||
| onError: () => { | ||||||
| showToast(t("error_removing_app"), "error"); | ||||||
| }, | ||||||
| async onSettled() { | ||||||
| await utils.viewer.calendars.connectedCalendars.invalidate(); | ||||||
| await utils.viewer.apps.integrations.invalidate(); | ||||||
| }, | ||||||
| }); | ||||||
|
|
||||||
| const isGoogleCalendar = integrationType === GOOGLE_CALENDAR_TYPE; | ||||||
| const canDisconnect = !delegationCredentialId && !disableConnectionModification; | ||||||
| const hasCache = isGoogleCalendar && cacheUpdatedAt; | ||||||
|
|
||||||
| if (!canDisconnect && !hasCache) { | ||||||
| return null; | ||||||
| } | ||||||
|
|
||||||
| return ( | ||||||
| <> | ||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🔵 INFO — Code patterns (confidence: 79%) The cacheStatusMap.get() uses || null which will coerce falsy values (including timestamp 0 for epoch) to null. While unlikely for timestamps, this could mask legitimate cache data. Evidence:
Agent: style |
||||||
| <Dropdown open={dropdownOpen} onOpenChange={setDropdownOpen}> | ||||||
| <DropdownMenuTrigger asChild> | ||||||
| <Button type="button" variant="icon" color="secondary" StartIcon="ellipsis" /> | ||||||
| </DropdownMenuTrigger> | ||||||
| <DropdownMenuContent> | ||||||
| {hasCache && ( | ||||||
| <> | ||||||
| <DropdownMenuItem className="focus:ring-muted"> | ||||||
| <div className="px-2 py-1"> | ||||||
| <div className="text-sm font-medium text-gray-900 dark:text-white">{t("cache_status")}</div> | ||||||
| <div className="text-xs text-gray-500 dark:text-white"> | ||||||
| {t("cache_last_updated", { | ||||||
| timestamp: new Intl.DateTimeFormat("en-US", { | ||||||
| dateStyle: "short", | ||||||
| timeStyle: "short", | ||||||
| }).format(new Date(cacheUpdatedAt)), | ||||||
| interpolation: { escapeValue: false }, | ||||||
| })} | ||||||
| </div> | ||||||
| </div> | ||||||
| </DropdownMenuItem> | ||||||
| <DropdownMenuItem className="outline-none"> | ||||||
| <DropdownItem | ||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🟡 MINOR — Timestamp Formatting / i18n (confidence: 95%) The timestamp is formatted using a hardcoded Evidence:
Agent: logic There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🟡 MINOR — Pattern violation (confidence: 93%) The timestamp formatting is hardcoded to Evidence:
Agent: architecture There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🟡 MINOR — Data Flow (confidence: 94%) The timestamp formatting is hardcoded to Evidence:
Agent: logic |
||||||
| type="button" | ||||||
| color="destructive" | ||||||
| StartIcon="trash" | ||||||
| onClick={() => { | ||||||
| setDeleteModalOpen(true); | ||||||
| setDropdownOpen(false); | ||||||
| }}> | ||||||
| {t("delete_cached_data")} | ||||||
| </DropdownItem> | ||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🟡 MINOR — Data Exposure (confidence: 93%) The timestamp is formatted with a hardcoded Evidence:
Agent: security |
||||||
| </DropdownMenuItem> | ||||||
| </> | ||||||
| )} | ||||||
| {canDisconnect && hasCache && <hr className="my-1" />} | ||||||
| {canDisconnect && ( | ||||||
| <DropdownMenuItem className="outline-none"> | ||||||
| <DropdownItem | ||||||
| type="button" | ||||||
| color="destructive" | ||||||
| StartIcon="trash" | ||||||
| onClick={() => { | ||||||
| setDisconnectModalOpen(true); | ||||||
| setDropdownOpen(false); | ||||||
| }}> | ||||||
| {t("remove_app")} | ||||||
| </DropdownItem> | ||||||
| </DropdownMenuItem> | ||||||
| )} | ||||||
| </DropdownMenuContent> | ||||||
| </Dropdown> | ||||||
|
|
||||||
| <Dialog open={deleteModalOpen} onOpenChange={setDeleteModalOpen}> | ||||||
| <ConfirmationDialogContent | ||||||
| variety="danger" | ||||||
| title={t("delete_cached_data")} | ||||||
| confirmBtnText={t("yes_delete_cache")} | ||||||
| onConfirm={() => { | ||||||
| deleteCacheMutation.mutate({ credentialId }); | ||||||
| setDeleteModalOpen(false); | ||||||
| }}> | ||||||
| {t("confirm_delete_cache")} | ||||||
| </ConfirmationDialogContent> | ||||||
| </Dialog> | ||||||
|
|
||||||
| <Dialog open={disconnectModalOpen} onOpenChange={setDisconnectModalOpen}> | ||||||
| <ConfirmationDialogContent | ||||||
| variety="danger" | ||||||
| title={t("remove_app")} | ||||||
| confirmBtnText={t("yes_remove_app")} | ||||||
| onConfirm={() => { | ||||||
| disconnectMutation.mutate({ id: credentialId }); | ||||||
| setDisconnectModalOpen(false); | ||||||
| }}> | ||||||
| {t("are_you_sure_you_want_to_remove_this_app")} | ||||||
| </ConfirmationDialogContent> | ||||||
| </Dialog> | ||||||
| </> | ||||||
| ); | ||||||
| } | ||||||
| Original file line number | Diff line number | Diff line change | ||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -169,4 +169,21 @@ export class CalendarCacheRepository implements ICalendarCacheRepository { | |||||||||||||||
| }, | ||||||||||||||||
| }); | ||||||||||||||||
| } | ||||||||||||||||
|
|
||||||||||||||||
| async getCacheStatusByCredentialIds(credentialIds: number[]) { | ||||||||||||||||
| const cacheStatuses = await prisma.calendarCache.groupBy({ | ||||||||||||||||
| by: ["credentialId"], | ||||||||||||||||
| where: { | ||||||||||||||||
| credentialId: { in: credentialIds }, | ||||||||||||||||
| }, | ||||||||||||||||
| _max: { | ||||||||||||||||
| updatedAt: true, | ||||||||||||||||
| }, | ||||||||||||||||
| }); | ||||||||||||||||
|
|
||||||||||||||||
| return cacheStatuses.map((cache) => ({ | ||||||||||||||||
| credentialId: cache.credentialId, | ||||||||||||||||
| updatedAt: cache._max.updatedAt, | ||||||||||||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🟡 MINOR — Edge Case Handling (confidence: 84%) When Evidence:
Agent: logic There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🟠 MAJOR — Edge cases (confidence: 94%) When Evidence:
Agent: logic There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ✅
Suggested change
🤖 Grapple PR auto-fix • major • confidence: 94% |
||||||||||||||||
| })); | ||||||||||||||||
| } | ||||||||||||||||
| } | ||||||||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -18,8 +18,14 @@ type ReturnTypeGetConnectedCalendars = Awaited<ReturnType<typeof getConnectedCal | |
| type ConnectedCalendarsFromGetConnectedCalendars = ReturnTypeGetConnectedCalendars["connectedCalendars"]; | ||
|
|
||
| export type UserWithCalendars = Pick<User, "id" | "email"> & { | ||
| allSelectedCalendars: Pick<SelectedCalendar, "externalId" | "integration" | "eventTypeId">[]; | ||
| userLevelSelectedCalendars: Pick<SelectedCalendar, "externalId" | "integration" | "eventTypeId">[]; | ||
| allSelectedCalendars: Pick< | ||
| SelectedCalendar, | ||
| "externalId" | "integration" | "eventTypeId" | "updatedAt" | "googleChannelId" | ||
| >[]; | ||
| userLevelSelectedCalendars: Pick< | ||
| SelectedCalendar, | ||
| "externalId" | "integration" | "eventTypeId" | "updatedAt" | "googleChannelId" | ||
| >[]; | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🟠 MAJOR — Module boundaries (confidence: 100%) The Evidence:
Agent: architecture |
||
| destinationCalendar: DestinationCalendar | null; | ||
| }; | ||
|
|
||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -257,8 +257,7 @@ export class SelectedCalendarRepository { | |
| } | ||
|
|
||
| static async findMany({ where, select, orderBy }: FindManyArgs) { | ||
| const args = { where, select, orderBy } satisfies Prisma.SelectedCalendarFindManyArgs; | ||
| return await prisma.selectedCalendar.findMany(args); | ||
| return await prisma.selectedCalendar.findMany({ where, select, orderBy }); | ||
| } | ||
|
|
||
| static async findUniqueOrThrow({ where }: { where: Prisma.SelectedCalendarWhereInput }) { | ||
|
|
@@ -398,6 +397,13 @@ export class SelectedCalendarRepository { | |
| }); | ||
| } | ||
|
|
||
| static async updateManyByCredentialId(credentialId: number, data: Prisma.SelectedCalendarUpdateInput) { | ||
| return await prisma.selectedCalendar.updateMany({ | ||
| where: { credentialId }, | ||
| data, | ||
| }); | ||
| } | ||
|
|
||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🟠 MAJOR — Input Validation (confidence: 99%)
Evidence:
Agent: security |
||
| static async setErrorInWatching({ id, error }: { id: string; error: string }) { | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🟡 MINOR — Code patterns (confidence: 80%) updateManyByCredentialId passes empty data object {} which may not trigger Prisma's @updatedat auto-update on SelectedCalendar. The intent appears to be updating the updatedAt timestamp, but empty data may result in a no-op. Evidence:
Agent: style |
||
| await SelectedCalendarRepository.updateById(id, { | ||
| error, | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -896,6 +896,8 @@ export class UserRepository { | |
| eventTypeId: true, | ||
| externalId: true, | ||
| integration: true, | ||
| updatedAt: true, | ||
| googleChannelId: true, | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🟡 MINOR — Module Boundaries / Type Expansion (confidence: 80%) Adding Evidence:
Agent: architecture |
||
| }, | ||
| }, | ||
| completedOnboarding: true, | ||
|
|
||
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
|
|
@@ -2,7 +2,7 @@ import Link from "next/link"; | |||||
| import React from "react"; | ||||||
|
|
||||||
| import AppListCard from "@calcom/features/apps/components/AppListCard"; | ||||||
| import DisconnectIntegration from "@calcom/features/apps/components/DisconnectIntegration"; | ||||||
| import CredentialActionsDropdown from "@calcom/features/apps/components/CredentialActionsDropdown"; | ||||||
| import AdditionalCalendarSelector from "@calcom/features/calendars/AdditionalCalendarSelector"; | ||||||
| import { CalendarSwitch } from "@calcom/features/calendars/CalendarSwitch"; | ||||||
| import { useLocale } from "@calcom/lib/hooks/useLocale"; | ||||||
|
|
@@ -67,18 +67,16 @@ const ConnectedCalendarList = ({ | |||||
| description={connectedCalendar.primary?.email ?? connectedCalendar.integration.description} | ||||||
| className="border-subtle mt-4 rounded-lg border" | ||||||
| actions={ | ||||||
| // Delegation credential can't be disconnected | ||||||
| !connectedCalendar.delegationCredentialId && | ||||||
| !disableConnectionModification && ( | ||||||
| <div className="flex w-32 justify-end"> | ||||||
| <DisconnectIntegration | ||||||
| credentialId={connectedCalendar.credentialId} | ||||||
| trashIcon | ||||||
| onSuccess={onChanged} | ||||||
| buttonProps={{ className: "border border-default" }} | ||||||
| /> | ||||||
| </div> | ||||||
| ) | ||||||
| <div className="flex w-32 justify-end"> | ||||||
| <CredentialActionsDropdown | ||||||
| credentialId={connectedCalendar.credentialId} | ||||||
| integrationType={connectedCalendar.integration.type} | ||||||
| cacheUpdatedAt={connectedCalendar.cacheUpdatedAt} | ||||||
| onSuccess={onChanged} | ||||||
| delegationCredentialId={connectedCalendar.delegationCredentialId} | ||||||
| disableConnectionModification={disableConnectionModification} | ||||||
| /> | ||||||
| </div> | ||||||
| }> | ||||||
| <div className="border-subtle border-t"> | ||||||
| {!fromOnboarding && ( | ||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🔵 INFO — Empty Wrapper DOM Node (confidence: 89%) The `` wrapper is now unconditionally rendered even when Evidence:
Agent: security |
||||||
|
|
@@ -97,7 +95,7 @@ const ConnectedCalendarList = ({ | |||||
| destination={cal.externalId === destinationCalendarId} | ||||||
| credentialId={cal.credentialId} | ||||||
| eventTypeId={shouldUseEventTypeScope ? eventTypeId : null} | ||||||
| delegationCredentialId={connectedCalendar.delegationCredentialId} | ||||||
| delegationCredentialId={connectedCalendar.delegationCredentialId || null} | ||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🔵 INFO — Naming Conventions (confidence: 74%) Inconsistent handling of delegationCredentialId type coercion using Evidence:
Agent: style |
||||||
| /> | ||||||
| ))} | ||||||
| </ul> | ||||||
|
|
@@ -122,17 +120,16 @@ const ConnectedCalendarList = ({ | |||||
| } | ||||||
| iconClassName="h-10 w-10 ml-2 mr-1 mt-0.5" | ||||||
| actions={ | ||||||
| // Delegation credential can't be disconnected | ||||||
| !connectedCalendar.delegationCredentialId && ( | ||||||
| <div className="flex w-32 justify-end"> | ||||||
| <DisconnectIntegration | ||||||
| credentialId={connectedCalendar.credentialId} | ||||||
| trashIcon | ||||||
| onSuccess={onChanged} | ||||||
| buttonProps={{ className: "border border-default" }} | ||||||
| /> | ||||||
| </div> | ||||||
| ) | ||||||
| <div className="flex w-32 justify-end"> | ||||||
| <CredentialActionsDropdown | ||||||
| credentialId={connectedCalendar.credentialId} | ||||||
| integrationType={connectedCalendar.integration.type} | ||||||
| cacheUpdatedAt={connectedCalendar.cacheUpdatedAt} | ||||||
| onSuccess={onChanged} | ||||||
| delegationCredentialId={connectedCalendar.delegationCredentialId} | ||||||
| disableConnectionModification={disableConnectionModification} | ||||||
| /> | ||||||
| </div> | ||||||
| } | ||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🟠 MAJOR — State management (confidence: 100%) In the error/fallback branch (when Evidence:
Agent: logic There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ✅
Suggested change
🤖 Grapple PR auto-fix • major • confidence: 100% |
||||||
| /> | ||||||
| ); | ||||||
|
|
@@ -162,6 +159,7 @@ export const SelectedCalendarsSettingsWebWrapper = (props: SelectedCalendarsSett | |||||
| refetchOnWindowFocus: false, | ||||||
| } | ||||||
| ); | ||||||
|
|
||||||
| const { isPending } = props; | ||||||
| const showScopeSelector = !!props.eventTypeId; | ||||||
| const isDisabled = disabledScope ? disabledScope === scope : false; | ||||||
|
|
||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,9 @@ | ||
| /* | ||
| Warnings: | ||
|
|
||
| - Added the required column `updatedAt` to the `CalendarCache` table without a default value. This is not possible if the table is not empty. | ||
|
|
||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🔵 INFO — Cryptographic issues (confidence: 89%) The migration file contains a misleading warning comment: 'Added the required column without a default value. This is not possible if the table is not empty.' The actual migration correctly adds Evidence:
Agent: security There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🔵 INFO — Migration Safety (confidence: 93%) The auto-generated Prisma migration warning comment states 'Added the required column Evidence:
Agent: architecture |
||
| */ | ||
| -- AlterTable | ||
| -- Add the column with a default value to safely handle existing rows | ||
| ALTER TABLE "CalendarCache" ADD COLUMN "updatedAt" TIMESTAMP(3) NOT NULL DEFAULT NOW(); | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🟡 MINOR — Migration Safety (confidence: 100%) Using Evidence:
Agent: logic There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🟡 MINOR — Misleading Migration Warning / Documentation Integrity (confidence: 100%) The migration file contains a Prisma-generated warning comment stating 'Added the required column Evidence:
Agent: security |
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -1715,6 +1715,8 @@ model CalendarCache { | |
| key String | ||
| value Json | ||
| expiresAt DateTime | ||
| // Provide an initial value for legacy rows and future raw inserts | ||
| updatedAt DateTime @default(now()) @updatedAt | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🔵 INFO — Documentation (confidence: 77%) Comment mentions 'legacy rows' but the migration adds DEFAULT NOW() which timestamps existing rows with current time, potentially misrepresenting when they were actually cached. This could be misleading to future maintainers. Evidence:
Agent: style |
||
| credentialId Int | ||
| userId Int? | ||
| credential Credential? @relation(fields: [credentialId], references: [id], onDelete: Cascade) | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🔴 CRITICAL — Intent Alignment (confidence: 100%)
The intent states 'When Google Calendar fetches availability and updates the cache, it also calls SelectedCalendarRepository.updateManyByCredentialId to update updatedAt for all selected calendars under that credential.' However, the
cacheUpdatedAtdisplayed in the UI comes fromCalendarCache.updatedAt(queried viaCalendarCacheRepository.getCacheStatusByCredentialIds), not fromSelectedCalendar.updatedAt. UpdatingSelectedCalendar.updatedAtdoes not affect the cache status displayed in the dropdown. TheCalendarCache.updatedAtfield has@updatedAtin the Prisma schema, so it should auto-update when cache rows are modified viasetAvailabilityInCache. ThisSelectedCalendarRepository.updateManyByCredentialIdcall appears to update the wrong table for its stated purpose.Evidence:
CalendarCacheRepository.getCacheStatusByCredentialIdswhich readsCalendarCache.updatedAtSelectedCalendarRepository.updateManyByCredentialIdwhich updatesSelectedCalendartableupdatedAt DateTime @default(now()) @updatedAtwhich auto-updates on Prisma writessetAvailabilityInCachecall on line 1020 already writes to CalendarCache, which should trigger the@updatedAtauto-update on CalendarCacheAgent: logic