Skip to content

feat: hourly storage part 1#1151

Closed
WcaleNieWolny wants to merge 16 commits into
mainfrom
storage_hourly
Closed

feat: hourly storage part 1#1151
WcaleNieWolny wants to merge 16 commits into
mainfrom
storage_hourly

Conversation

@WcaleNieWolny
Copy link
Copy Markdown
Contributor

@WcaleNieWolny WcaleNieWolny commented Jun 10, 2025

Summary by CodeRabbit

  • New Features

    • Hourly storage tracking and automated hourly computation for apps; dashboard shows a storage warning badge and updated tooltip.
  • Improvements

    • Daily metrics now derived from hourly snapshots for improved accuracy.
    • Storage unit labels updated to GiB/MiB.
    • Data cleanup to ensure version/create-delete consistency.
  • Database Changes

    • New hourly storage and cache tables with indexes and RLS.
  • Bug Fixes

    • Input validation for retention values.
  • Localization

    • Added storage-warning translations across locales.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Jun 10, 2025

Walkthrough

Adds hourly storage tracking: new DB tables and migrations, a cron HTTP trigger to compute hourly storage, type/schema updates, UI unit/tooltip and warning changes, a retention validation, and a minor version bump. Daily metrics now source hourly snapshots.

Changes

Cohort / File(s) Change Summary
DB migrations & schema
supabase/migrations/20250609033531_migrate_to_hourly_storage.sql, supabase/migrations/20250608130257_fix_version_meta.sql, supabase/migrations/20250530233128_base.sql
Create storage_hourly and storage_hourly_cache tables (indexes, RLS, FKs), add version_meta cleanup/repair DO blocks, and change public.get_app_metrics to lateral-join hourly snapshots from storage_hourly.
Backend trigger & routing
supabase/functions/_backend/triggers/cron_hourly_storage.ts, supabase/functions/triggers/index.ts
New Hono POST endpoint /cron_hourly_storage (exported app) computing/persisting per-app hourly storage with cache, validation, aggregation, alerts; route registered on global triggers router.
Backend types & DB typings
supabase/functions/_backend/utils/supabase.types.ts, src/types/supabase.types.ts
Add storage_hourly and storage_hourly_cache table types; large GraphQL/public type surface and function/enum/table signature adjustments in src/types/supabase.types.ts.
Backend logic tweaks
supabase/functions/_backend/public/statistics/index.ts, supabase/functions/_backend/public/app/put.ts, supabase/functions/_backend/utils/version.ts
Conditionalized storage prefix-sum via new storageV2 flag in stats calc; added retention bounds validation on app PUT (2-year max); bumped backend version string to 12.26.7.
Frontend components
src/components/dashboard/Usage.vue, src/components/dashboard/UsageCard.vue
Switch units to GiB/MiB, add local storageV2 and pass :storage prop; add storage prop to UsageCard and render conditional storage warning badge + centered tooltip.
I18n translation additions
messages/*.json (de.json,en.json,es.json,fr.json,id.json,it.json,ja.json,ko.json,pl.json,pt-br.json,ru.json,tr.json,vi.json,zh-cn.json)
Add storage-warning translations across locales and update some retention message text / minor i18n keys.
Release/meta
CHANGELOG.md, package.json
Inserted two new release headings and bumped version from 12.26.5 to 12.26.7.

Sequence Diagram(s)

sequenceDiagram
    participant Cron as Cron / Scheduler
    participant Hono as /cron_hourly_storage (Hono)
    participant Admin as Supabase Admin Client
    participant DB as Postgres
    participant Discord as Alerting

    Cron->>Hono: POST / (app_id, optional current_hour)
    Hono->>Admin: resolve org & billing cycle (get_cycle_info_org)
    Admin->>DB: query billing cycle & app info
    Hono->>Admin: read cache (storage_hourly_cache) if present
    Admin->>DB: SELECT version_meta (filter by cacheModified if present)
    DB-->>Admin: version_meta rows
    Hono->>Hono: validate, aggregate per-version changes into hourly window
    alt storage_added null detected
      Hono->>Discord: reportStorageAddedNull (alert)
    end
    Hono->>Admin: DELETE existing storage_hourly for app
    Admin->>DB: DELETE FROM storage_hourly WHERE app_id=...
    Hono->>Admin: INSERT computed hourly rows into storage_hourly
    Admin->>DB: INSERT ... VALUES ...
    DB-->>Admin: confirm inserts
    Hono-->>Cron: return success / error
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Suggested reviewers

  • riderx

Poem

🐰
I hop the hours, stitch each byte,
New tables keep the count just right.
Triggers drum and types align,
Badges blink and warnings shine.
Hourly hops — storage feels light!

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Description Check ⚠️ Warning The pull request has no author-provided description while the repository requires a structured template; the required sections (Summary, Test plan, Screenshots, and Checklist) are all missing, so the PR description is incomplete and does not meet repository standards. Please populate the PR using the provided template: add a concise Summary of intent and scope, a Test plan with concrete steps (including running the new migrations, validating the cron endpoint, and verifying frontend/UI changes), include screenshots for UI changes if applicable, and complete the Checklist (lint/tests/docs updates).
✅ Passed checks (2 passed)
Check name Status Explanation
Title Check ✅ Passed The title "feat: hourly storage part 1" is concise and accurately reflects the primary change—adding hourly storage support (DB migrations, hourly tables/cache, cron trigger, and related frontend/i18n changes)—and the "part 1" suffix appropriately signals this is the first incremental change in a larger effort.
Docstring Coverage ✅ Passed No functions found in the changes. Docstring coverage check skipped.
✨ Finishing touches
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch storage_hourly

Tip

👮 Agentic pre-merge checks are now available in preview!

Pro plan users can now enable pre-merge checks in their settings to enforce checklists before merging PRs.

  • Built-in checks – Quickly apply ready-made checks to enforce title conventions, require pull request descriptions that follow templates, validate linked issues for compliance, and more.
  • Custom agentic checks – Define your own rules using CodeRabbit’s advanced agentic capabilities to enforce organization-specific policies and workflows. For example, you can instruct CodeRabbit’s agent to verify that API documentation is updated whenever API schema files are modified in a PR. Note: Upto 5 custom checks are currently allowed during the preview period. Pricing for this feature will be announced in a few weeks.

Please see the documentation for more information.

Example:

reviews:
  pre_merge_checks:
    custom_checks:
      - name: "Undocumented Breaking Changes"
        mode: "warning"
        instructions: |
          Pass/fail criteria: All breaking changes to public APIs, CLI flags, environment variables, configuration keys, database schemas, or HTTP/GraphQL endpoints must be documented in the "Breaking Change" section of the PR description and in CHANGELOG.md. Exclude purely internal or private changes (e.g., code not exported from package entry points or explicitly marked as internal).

Please share your feedback with us on this Discord post.


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
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 4

🧹 Nitpick comments (2)
supabase/migrations/20250530233128_base.sql (2)

880-898: Ensure correct date-time matching in LATERAL join.
Casting sh.date::date implicitly strips time and may introduce subtle timezone discrepancies. Consider using date_trunc('day', sh.date) for clarity, and refine the midnight next-day check to capture any snapshot within the first hour rather than requiring an exact timestamp match:

LEFT JOIN LATERAL (
  SELECT sh.size AS storage
  FROM public.storage_hourly sh
  WHERE sh.app_id = aa.app_id
    AND (
      -- Prefer any record in the first hour of the next day
      (sh.date >= ds.date + INTERVAL '1 day'
       AND sh.date < ds.date + INTERVAL '1 day' + INTERVAL '1 hour')
      OR
      -- Otherwise get latest snapshot from the current date
      date_trunc('day', sh.date) = ds.date
    )
  ORDER BY
    CASE
      WHEN sh.date >= ds.date + INTERVAL '1 day' AND sh.date < ds.date + INTERVAL '1 day' + INTERVAL '1 hour' THEN 1
      ELSE 2
    END,
    sh.date DESC
  LIMIT 1
) dst ON true

880-898: Add indexing on storage_hourly for performance.
This lateral subquery runs per-row and may scan the entire storage_hourly table. Ensure there’s a composite index on (app_id, date) (or on (app_id, date::date)) and, if you adopt the hourly window approach, on (app_id, date) and possibly (app_id, EXTRACT(HOUR FROM date)) to avoid full scans.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between df94b61 and 9cfc318.

📒 Files selected for processing (7)
  • supabase/functions/_backend/public/statistics/index.ts (2 hunks)
  • supabase/functions/_backend/triggers/cron_hourly_storage.ts (1 hunks)
  • supabase/functions/_backend/utils/supabase.types.ts (1 hunks)
  • supabase/functions/triggers/index.ts (2 hunks)
  • supabase/migrations/20250530233128_base.sql (1 hunks)
  • supabase/migrations/20250608130257_fix_version_meta.sql (1 hunks)
  • supabase/migrations/20250609033531_migrate_to_hourly_storage.sql (1 hunks)
🧰 Additional context used
🧬 Code Graph Analysis (1)
supabase/functions/_backend/utils/supabase.types.ts (1)
src/types/supabase.types.ts (1)
  • Json (1-7)
⏰ Context from checks skipped due to timeout of 90000ms (1)
  • GitHub Check: Run tests
🔇 Additional comments (7)
supabase/functions/triggers/index.ts (1)

11-11: LGTM! Clean integration of the new hourly storage cron endpoint.

The import and route registration follow the established patterns in the codebase and integrate seamlessly with the existing trigger infrastructure.

Also applies to: 71-71

supabase/functions/_backend/public/statistics/index.ts (2)

253-253: Good practice: Changing to const for immutable array.

Since the storage array is no longer being reassigned (due to the removed cumulative operation), using const is the correct approach.


322-322:

Details

❓ Verification inconclusive

Verify the storage data format change and clean up commented code.

The commented cumulative sum operation suggests that storage data is now processed differently with the new hourly storage system. However, this represents a significant change in data processing logic.

Please verify that:

  1. The new storage data format from the database function provides the expected cumulative values
  2. All consumers of this API endpoint still receive data in the expected format
  3. The change has been properly tested across different date ranges and app configurations

If this change is finalized, consider removing the commented code entirely rather than leaving it as a comment for better code clarity.


🏁 Script executed:

#!/bin/bash
# Search for any tests or documentation that might reference the old storage processing behavior
rg -A 5 -B 5 "storage.*reduce|cumulative.*storage" --type js --type ts

Length of output: 4759


🏁 Script executed:

#!/bin/bash
# Show context around storage processing in the public statistics endpoint
rg -n "storage" -A 8 -B 8 supabase/functions/_backend/public/statistics/index.ts

Length of output: 4468


Verify and clean up legacy storage cumulative logic

The commented-out cumulative reduce for storage at
supabase/functions/_backend/public/statistics/index.ts:322
indicates leftover code from the previous data-processing strategy. Since storage is now built up per day with an initial offset applied via the RPC call, please:

• Confirm that the new hourly-aggregated storage values still produce the correct running totals in this endpoint (across various date ranges and app configurations).
• Ensure all API consumers relying on cumulative daily storage continue to work as expected.
• Add or update tests to cover the revised storage logic, then remove the commented legacy reduce entirely for clarity.

supabase/functions/_backend/utils/supabase.types.ts (1)

1085-1145: LGTM! Type definitions are well-structured.

The new table type definitions for storage_hourly and storage_hourly_cache are properly implemented with correct Row, Insert, Update types and foreign key relationships to the apps table.

supabase/migrations/20250608130257_fix_version_meta.sql (1)

167-258: Well-implemented cleanup logic for version metadata consistency.

The additional cleanup steps properly handle edge cases:

  • Removes orphaned delete records that have no corresponding create record and don't exist in app_versions
  • Adds missing create records for deletes that do have corresponding app_versions entries

The use of transactions via DO blocks ensures atomicity, and the logging provides good visibility into the cleanup process.

supabase/migrations/20250609033531_migrate_to_hourly_storage.sql (1)

1-36: Well-designed schema for hourly storage tracking.

The table structures are properly designed with:

  • Appropriate indexes for query performance
  • Unique constraint on (app_id, date) to prevent duplicate hourly entries
  • Foreign key constraints with CASCADE DELETE for data integrity
  • RLS enabled for security
supabase/functions/_backend/triggers/cron_hourly_storage.ts (1)

59-60: Unexplained hour adjustment needs clarification.

Adding 1 hour to both start and end dates without explanation could lead to off-by-one errors in storage calculations.

Please verify if this adjustment is intentional and document the reasoning. If it's meant to handle timezone issues, consider using more explicit timezone handling.

Comment on lines +52 to +56
const { data, error } = await supabase.from('version_meta').select('*').eq('app_id', app_id).limit(100_000)
if (error) {
console.error(error)
return c.json({ status: 'Cannot get storage per hour', error: JSON.stringify(error) }, 500)
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Potential data loss due to hard limit.

The query limits results to 100,000 records. For apps with extensive version history, this could miss important data and lead to incorrect storage calculations.

Consider implementing pagination or streaming to handle large datasets:

-    const { data, error } = await supabase.from('version_meta').select('*').eq('app_id', app_id).limit(100_000)
+    // Use pagination to handle large datasets
+    const allData = []
+    let offset = 0
+    const batchSize = 10000
+    
+    while (true) {
+      const { data, error } = await supabase
+        .from('version_meta')
+        .select('*')
+        .eq('app_id', app_id)
+        .range(offset, offset + batchSize - 1)
+      
+      if (error) throw error
+      if (!data || data.length === 0) break
+      
+      allData.push(...data)
+      offset += batchSize
+    }
+    
+    const data = allData

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In supabase/functions/_backend/triggers/cron_hourly_storage.ts around lines 52
to 56, the query limits results to 100,000 records which risks missing data for
apps with large version histories. To fix this, implement pagination by fetching
data in batches using offset or cursor-based pagination, or use streaming if
supported, to ensure all relevant records are processed without data loss.
Adjust the logic to accumulate results across pages before proceeding with
storage calculations.

Comment on lines +163 to +171
for (let i = startIndex; i <= endIndex; i++) {
hourlyTimestamps[i].storage += item.size * (i - startIndex + 1)
biggestValueAdded = Math.max(biggestValueAdded, item.size * (i - startIndex + 1))
}

for (let i = endIndex + 1; i < hourlyTimestamps.length; i++) {
hourlyTimestamps[i].storage += biggestValueAdded
}
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Incorrect storage calculation logic.

The current logic multiplies storage size by (i - startIndex + 1), which doesn't make sense for storage tracking. Storage should be constant while a version exists, not increasing over time.

The storage calculation should add the version size to each hour it exists:

-      let biggestValueAdded = 0
-
-      for (let i = startIndex; i <= endIndex; i++) {
-        hourlyTimestamps[i].storage += item.size * (i - startIndex + 1)
-        biggestValueAdded = Math.max(biggestValueAdded, item.size * (i - startIndex + 1))
-      }
-
-      for (let i = endIndex + 1; i < hourlyTimestamps.length; i++) {
-        hourlyTimestamps[i].storage += biggestValueAdded
-      }
+      // Add the version size to each hour it exists
+      for (let i = startIndex; i < hourlyTimestamps.length; i++) {
+        if (i <= endIndex) {
+          // Version exists during this hour
+          hourlyTimestamps[i].storage += item.size
+        }
+      }
📝 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
for (let i = startIndex; i <= endIndex; i++) {
hourlyTimestamps[i].storage += item.size * (i - startIndex + 1)
biggestValueAdded = Math.max(biggestValueAdded, item.size * (i - startIndex + 1))
}
for (let i = endIndex + 1; i < hourlyTimestamps.length; i++) {
hourlyTimestamps[i].storage += biggestValueAdded
}
}
// Add the version size to each hour it exists
for (let i = startIndex; i < hourlyTimestamps.length; i++) {
if (i <= endIndex) {
// Version exists during this hour
hourlyTimestamps[i].storage += item.size
}
}
🤖 Prompt for AI Agents
In supabase/functions/_backend/triggers/cron_hourly_storage.ts around lines 163
to 171, the storage calculation incorrectly multiplies the item size by the
elapsed hours, causing storage to increase over time. To fix this, update the
logic so that for each hour index from startIndex to endIndex, you add the
item's size directly without multiplying by the hour offset, reflecting constant
storage usage while the version exists. Remove the second loop adding
biggestValueAdded to subsequent hours, as it is not needed.

Comment on lines +145 to +157
const startIndex = (startHour.toDate().getTime() - cycleStartHour.toDate().getTime()) / 3600000
if (startIndex % 1 !== 0) {
throw new Error(`Start index must be a whole number, is ${startIndex}`)
}
const endIndex = (cycleEndHour.toDate().getTime() - (endHour.toDate().getTime() + 1)) / 3600000
if (endIndex % 1 !== 0) {
throw new Error(`
End index must be a whole number, is ${endIndex}.
Before devision: ${cycleEndHour.toDate().getTime() - endHour.toDate().getTime()},
cycleEndHour: ${cycleEndHour.toDate().getTime()},
endHour: ${endHour.toDate().getTime() + 1},
`)
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

⚠️ Potential issue

Complex and error-prone index calculations.

The calculation of endIndex appears to be backwards - subtracting from cycleEndHour instead of calculating forward from the start. This makes the logic hard to follow and error-prone.

Simplify the index calculations:

-      console.log(`startHour: ${startHour.toDate().getTime()}, cycleStartHour: ${cycleStartHour.toDate().getTime()}`)
-      const startIndex = (startHour.toDate().getTime() - cycleStartHour.toDate().getTime()) / 3600000
-      if (startIndex % 1 !== 0) {
-        throw new Error(`Start index must be a whole number, is ${startIndex}`)
-      }
-      const endIndex = (cycleEndHour.toDate().getTime() - (endHour.toDate().getTime() + 1)) / 3600000
-      if (endIndex % 1 !== 0) {
-        throw new Error(`
-                End index must be a whole number, is ${endIndex}. 
-                Before devision: ${cycleEndHour.toDate().getTime() - endHour.toDate().getTime()}, 
-                cycleEndHour: ${cycleEndHour.toDate().getTime()}, 
-                endHour: ${endHour.toDate().getTime() + 1}, 
-            `)
-      }
+      // Calculate indices based on hour differences
+      const startIndex = Math.floor(startHour.diff(cycleStartHour, 'hours'))
+      const endIndex = Math.floor(endHour.diff(cycleStartHour, 'hours'))
+      
+      if (startIndex < 0 || endIndex >= hourlyTimestamps.length) {
+        throw new Error(`Index out of bounds: startIndex=${startIndex}, endIndex=${endIndex}, arrayLength=${hourlyTimestamps.length}`)
+      }
📝 Committable suggestion

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

Suggested change
const startIndex = (startHour.toDate().getTime() - cycleStartHour.toDate().getTime()) / 3600000
if (startIndex % 1 !== 0) {
throw new Error(`Start index must be a whole number, is ${startIndex}`)
}
const endIndex = (cycleEndHour.toDate().getTime() - (endHour.toDate().getTime() + 1)) / 3600000
if (endIndex % 1 !== 0) {
throw new Error(`
End index must be a whole number, is ${endIndex}.
Before devision: ${cycleEndHour.toDate().getTime() - endHour.toDate().getTime()},
cycleEndHour: ${cycleEndHour.toDate().getTime()},
endHour: ${endHour.toDate().getTime() + 1},
`)
}
// Calculate indices based on hour differences
const startIndex = Math.floor(startHour.diff(cycleStartHour, 'hours'))
const endIndex = Math.floor(endHour.diff(cycleStartHour, 'hours'))
if (startIndex < 0 || endIndex >= hourlyTimestamps.length) {
throw new Error(
`Index out of bounds: startIndex=${startIndex}, endIndex=${endIndex}, arrayLength=${hourlyTimestamps.length}`
)
}
🤖 Prompt for AI Agents
In supabase/functions/_backend/triggers/cron_hourly_storage.ts around lines 145
to 157, the calculation of endIndex is confusing and error-prone because it
subtracts endHour from cycleEndHour, which is backwards. To fix this, rewrite
the endIndex calculation to measure the elapsed hours forward from
cycleStartHour to endHour, similar to how startIndex is calculated, ensuring
both indices are computed consistently and clearly. Remove the unnecessary +1
adjustment and verify both indices are whole numbers after the calculation.

Comment on lines +137 to +142
startHour = startHour.startOf('hour')

let endHour = item.storage_removed ?? now.clone()
if (endHour.isAfter(cycleEndHour))
endHour = cycleEndHour
endHour = endHour.endOf('hour')
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Inconsistent hour boundary handling.

Using startOf('hour') for start time but endOf('hour') for end time creates an asymmetry that could lead to off-by-one errors.

Use consistent hour boundaries:

-      startHour = startHour.startOf('hour')
+      startHour = startHour.startOf('hour')
 
       let endHour = item.storage_removed ?? now.clone()
       if (endHour.isAfter(cycleEndHour))
         endHour = cycleEndHour
-      endHour = endHour.endOf('hour')
+      endHour = endHour.startOf('hour')
📝 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
startHour = startHour.startOf('hour')
let endHour = item.storage_removed ?? now.clone()
if (endHour.isAfter(cycleEndHour))
endHour = cycleEndHour
endHour = endHour.endOf('hour')
startHour = startHour.startOf('hour')
let endHour = item.storage_removed ?? now.clone()
if (endHour.isAfter(cycleEndHour))
endHour = cycleEndHour
endHour = endHour.startOf('hour')
🤖 Prompt for AI Agents
In supabase/functions/_backend/triggers/cron_hourly_storage.ts around lines 137
to 142, the code uses startOf('hour') for the startHour but endOf('hour') for
the endHour, causing inconsistent hour boundary handling. To fix this, use
startOf('hour') for both startHour and endHour to maintain symmetry and avoid
off-by-one errors in time calculations.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 5

🔭 Outside diff range comments (1)
src/components/dashboard/Usage.vue (1)

95-97: 🛠️ Refactor suggestion

Binary/decimal mismatch in storage graph data

bytesToGb(item.storage ?? 0, 2) keeps using the GB path while the card advertises GiB. Same inconsistency as above – users will see ≈ 7 % lower numbers than expected.

♻️ Duplicate comments (2)
supabase/functions/_backend/triggers/cron_hourly_storage.ts (2)

126-133: Hard-coded .limit(100_000) risks silent data loss
Same issue flagged in previous review – large apps may exceed this cap. Implement pagination or streaming.


324-333: Storage calculation multiplies size by elapsed hours (bug reported earlier)

Adding item.size * (i - startIndex + 1) inflates storage usage exponentially. Each hour should record constant size while the version exists.

🧹 Nitpick comments (2)
src/components/dashboard/UsageCard.vue (2)

5-7: Import ordering breaks ESLint rule

ExclamationCircle must be imported before InformationInfo to satisfy perfectionist/sort-imports.

-import InformationInfo from '~icons/heroicons/information-circle'
-import ExclamationCircle from '~icons/heroicons/exclamation-circle'
+import ExclamationCircle   from '~icons/heroicons/exclamation-circle'
+import InformationInfo    from '~icons/heroicons/information-circle'
🧰 Tools
🪛 ESLint

[error] 6-6: Expected "~icons/heroicons/exclamation-circle" to come before "~icons/heroicons/information-circle".

(perfectionist/sort-imports)


113-122: Tooltip markup duplicates a tooltip inside tooltip

The <div class="tooltip tooltip-bottom"> is nested in the outer badge but wraps only the icon – this renders a tooltip inside another tooltip for the same badge which is redundant and may break positioning.

Simpler & more robust:

-<div v-if="props.storage" class="font-medium badge badge-error self-end ml-auto mb-2">
-  <div class="tooltip tooltip-bottom">
-    <ExclamationCircle class="w-4 h-4" />
-    <div class="tooltip-content ...">
-      ...
-    </div>
-  </div>
-</div>
+<div v-if="props.storage" class="font-medium badge badge-error self-end ml-auto mb-2 tooltip tooltip-bottom">
+  <ExclamationCircle class="w-4 h-4" />
+  <div class="tooltip-content ...">
+    ...
+  </div>
+</div>
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 9cfc318 and 000d696.

📒 Files selected for processing (4)
  • src/components/dashboard/Usage.vue (3 hunks)
  • src/components/dashboard/UsageCard.vue (4 hunks)
  • supabase/functions/_backend/triggers/cron_hourly_storage.ts (1 hunks)
  • supabase/migrations/20250609033531_migrate_to_hourly_storage.sql (1 hunks)
🧰 Additional context used
🪛 ESLint
src/components/dashboard/UsageCard.vue

[error] 6-6: Expected "~icons/heroicons/exclamation-circle" to come before "~icons/heroicons/information-circle".

(perfectionist/sort-imports)

🪛 Biome (1.9.4)
supabase/functions/_backend/triggers/cron_hourly_storage.ts

[error] 171-171: await is only allowed within async functions and at the top levels of modules.

(parse)


[error] 185-185: await is only allowed within async functions and at the top levels of modules.

(parse)


[error] 194-194: await is only allowed within async functions and at the top levels of modules.

(parse)


[error] 197-197: await is only allowed within async functions and at the top levels of modules.

(parse)


[error] 337-337: Expected a catch clause but instead found 'const'.

Expected a catch clause here.

(parse)


[error] 356-356: expected , but instead found catch

Remove catch

(parse)


[error] 359-359: Illegal return statement outside of a function

(parse)


[error] 360-363: Expected a statement but instead found '})

async'.

Expected a statement here.

(parse)


[error] 365-365: await is only allowed within async functions and at the top levels of modules.

(parse)


[error] 159-159: This variable is used before its declaration.

The variable is declared here:

(lint/correctness/noInvalidUseBeforeDeclaration)


[error] 159-159: This variable is used before its declaration.

The variable is declared here:

(lint/correctness/noInvalidUseBeforeDeclaration)


[error] 160-160: This variable is used before its declaration.

The variable is declared here:

(lint/correctness/noInvalidUseBeforeDeclaration)


[error] 160-160: This variable is used before its declaration.

The variable is declared here:

(lint/correctness/noInvalidUseBeforeDeclaration)


[error] 162-162: This variable is used before its declaration.

The variable is declared here:

(lint/correctness/noInvalidUseBeforeDeclaration)


[error] 234-234: Shouldn't redeclare 'semiSortedData'. Consider to delete it or rename it.

'semiSortedData' is defined here:

(lint/suspicious/noRedeclare)


[error] 235-235: Shouldn't redeclare 'positiveData'. Consider to delete it or rename it.

'positiveData' is defined here:

(lint/suspicious/noRedeclare)


[error] 236-236: Shouldn't redeclare 'negativeData'. Consider to delete it or rename it.

'negativeData' is defined here:

(lint/suspicious/noRedeclare)


[error] 258-258: Shouldn't redeclare 'storageChanggesPerVersion'. Consider to delete it or rename it.

'storageChanggesPerVersion' is defined here:

(lint/suspicious/noRedeclare)


[error] 356-360: This block statement doesn't serve any purpose and can be safely removed.

Standalone block statements without any block-level declarations are redundant in JavaScript and can be removed to simplify the code.

(lint/complexity/noUselessLoneBlockStatements)

🪛 GitHub Actions: autofix.ci
supabase/functions/_backend/triggers/cron_hourly_storage.ts

[error] 337-337: ESLint Parsing error: 'catch' or 'finally' expected

⏰ Context from checks skipped due to timeout of 90000ms (2)
  • GitHub Check: Run tests
  • GitHub Check: Analyze (java-kotlin)
🔇 Additional comments (3)
src/components/dashboard/Usage.vue (1)

148-151: Boolean prop should not be bound with quotes

:storage="true" is correct, but "true" (string literal) would silently coerce to truthy. If someone copies this pattern without the leading colon Vue will treat it as string – worth keeping an eye on in reviews.

supabase/migrations/20250609033531_migrate_to_hourly_storage.sql (2)

38-38: Potential integer overflow when multiplying plans.storage

storage is multiplied by 31 * 24 ( = 744 ).
If the column type is INTEGER this may overflow for larger original values (e.g. ≥ 2.8 GB ➜ > 2 TB). Verify the column is BIGINT or adjust calculation.


34-37: RLS enabled but no policies defined

ALTER TABLE ... ENABLE ROW LEVEL SECURITY; without at least a default policy will block all access. Make sure policies are created in the same or a following migration.

Comment on lines +23 to 24
const storageUnit = computed(() => storageDisplayGb.value ? 'GiB' : 'MiB')
// const noData = computed(() => datas.value.mau.length == 0)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

storageUnit naming vs. actual value conversion is misleading

storageUnit returns "GiB"/"MiB", but the values fed into datas.storage are produced via bytesToGb, which (judging from the name) converts using decimal GB.
Either rename the helper (bytesToGiB / bytesToMiB) or switch to a binary–aware converter to avoid displaying binary units for decimal values.

🤖 Prompt for AI Agents
In src/components/dashboard/Usage.vue around lines 23 to 24, the computed
property storageUnit returns "GiB" or "MiB" while the underlying data is
converted using a decimal-based bytesToGb function, causing a mismatch between
unit label and actual value. To fix this, either rename the conversion helper to
reflect decimal units (e.g., bytesToGB) or replace the conversion function with
one that uses binary units (e.g., bytesToGiB) to align the displayed unit with
the value conversion method.

Comment on lines +147 to +160
const semiSortedData: typeof data = []
const positiveData: typeof data = []
const negativeData: typeof data = []
for (const item of data) {
if (item.size > 0) {
positiveData.push(item)
}
else {
negativeData.push(item)
}
}

semiSortedData.push(...positiveData)
semiSortedData.push(...negativeData)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Duplicate declarations & merge artefacts – file will not compile

semiSortedData, positiveData, negativeData are declared twice (lines 147-160 and 234-244). This, plus the repeated “step” comments, indicates an unresolved merge. Remove the second block or merge them properly.

🧰 Tools
🪛 Biome (1.9.4)

[error] 159-159: This variable is used before its declaration.

The variable is declared here:

(lint/correctness/noInvalidUseBeforeDeclaration)


[error] 159-159: This variable is used before its declaration.

The variable is declared here:

(lint/correctness/noInvalidUseBeforeDeclaration)


[error] 160-160: This variable is used before its declaration.

The variable is declared here:

(lint/correctness/noInvalidUseBeforeDeclaration)


[error] 160-160: This variable is used before its declaration.

The variable is declared here:

(lint/correctness/noInvalidUseBeforeDeclaration)

🤖 Prompt for AI Agents
In supabase/functions/_backend/triggers/cron_hourly_storage.ts around lines 147
to 160 and 234 to 244, the arrays semiSortedData, positiveData, and negativeData
are declared twice, causing duplicate declarations and preventing compilation.
Identify and remove the redundant declaration block and any repeated step
comments to resolve the merge conflict, ensuring only one properly merged and
correctly placed declaration and usage of these arrays remains in the file.

Comment on lines +298 to +306
if (startHour.isBefore(cycleStartHour))
startHour = cycleStartHour
startHour = startHour.startOf('hour')

let endHour = item.storage_removed ?? now.clone()
if (endHour.isAfter(cycleEndHour))
endHour = cycleEndHour
endHour = endHour.endOf('hour')

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Undefined identifiers: startHour, now

startHour and now are referenced without prior declaration in this scope; code won’t compile. Define them or remove this block.

🤖 Prompt for AI Agents
In supabase/functions/_backend/triggers/cron_hourly_storage.ts around lines 298
to 306, the variables startHour and now are used without being declared, causing
a compilation error. To fix this, declare and initialize startHour and now
appropriately before their usage, ensuring they have valid values based on the
context, or if they are unnecessary, remove this code block entirely.

Comment on lines +354 to +360
return c.json(BRES)
}
catch (error) {
console.error(error)
cloudlogErr(error)
return c.json({ error: 'Internal server error' }, 500)
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

BRES is not defined – runtime error

return c.json(BRES) references an undeclared identifier. Replace with actual response payload or remove.

🧰 Tools
🪛 Biome (1.9.4)

[error] 356-356: expected , but instead found catch

Remove catch

(parse)


[error] 359-359: Illegal return statement outside of a function

(parse)


[error] 356-360: This block statement doesn't serve any purpose and can be safely removed.

Standalone block statements without any block-level declarations are redundant in JavaScript and can be removed to simplify the code.

(lint/complexity/noUselessLoneBlockStatements)

🤖 Prompt for AI Agents
In supabase/functions/_backend/triggers/cron_hourly_storage.ts around lines 354
to 360, the variable BRES is used in return c.json(BRES) but it is not defined
anywhere, causing a runtime error. Replace BRES with the actual response data
object that should be returned or remove this line if no response data is
needed. Ensure the returned value is a properly defined variable or literal
representing the intended JSON response.

Comment on lines +162 to +183
const storageChanggesPerVersion = semiSortedData.reduce((acc, item) => {
if (item.size > 0) {
if (acc.has(item.version_id)) {
// What the fuck? how can two versions be added TWICE? Bad, throw
throw new Error('Two versions are added at the same time')
}
const ownerOrgId = appData.owner_org

// Step two: get the billing data
const cycleInfoData = await supabase.rpc('get_cycle_info_org', { orgid: ownerOrgId }).single()
const cycleInfo = cycleInfoData.data
if (!cycleInfo || !cycleInfo.subscription_anchor_start || !cycleInfo.subscription_anchor_end)
return c.json({ status: 'Cannot get cycle info' }, 400)

// Step three: validate the billing date is not at more than 34 days apart
const startDate = new Date(cycleInfo.subscription_anchor_start)
const endDate = new Date(cycleInfo.subscription_anchor_end)
const diffTime = Math.abs(endDate.getTime() - startDate.getTime())
const diffDays = Math.ceil(diffTime / (1000 * 60 * 60 * 24))
if (diffDays > 34)
return c.json({ status: 'Billing date is more than 34 days apart' }, 400)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

await used inside non-async reducer – syntax error

The reducer starting at 162 is synchronous ((acc, item) => {}) yet it contains multiple await calls. Either mark the callback async and await the whole reduce, or pre-process data outside the reducer.

🧰 Tools
🪛 Biome (1.9.4)

[error] 171-171: await is only allowed within async functions and at the top levels of modules.

(parse)


[error] 162-162: This variable is used before its declaration.

The variable is declared here:

(lint/correctness/noInvalidUseBeforeDeclaration)

🤖 Prompt for AI Agents
In supabase/functions/_backend/triggers/cron_hourly_storage.ts around lines 162
to 183, the reducer function is synchronous but contains await calls, causing a
syntax error. To fix this, refactor the code by moving the asynchronous
operations outside the reduce callback. For example, pre-process the data with
async calls before the reduce or use a for-loop with async/await instead of
reduce to handle asynchronous logic properly.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 5

Caution

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

⚠️ Outside diff range comments (1)
src/components/dashboard/UsageCard.vue (1)

2-12: Missing import: useOrganizationStore

useOrganizationStore() is used but not imported, causing runtime error.

 import { useI18n } from 'vue-i18n'
 import ExclamationCircle from '~icons/heroicons/exclamation-circle'
+import { useOrganizationStore } from '~/stores/organization'

Also applies to: 33-38

🧹 Nitpick comments (18)
messages/vi.json (1)

660-660: Fix typo and improve phrasing for clarity and consistency.

Use “biểu đồ” (not “grap”) and “theo giờ” phrasing; explicitly contrast with prior daily data.

Please confirm the previous granularity was daily; if not, adjust “theo ngày”.

-  "storage-warning": "Hệ thống lưu trữ gần đây đã thay đổi và bây giờ grap này đang hiển thị lưu trữ mỗi giờ thay vì dữ liệu trước đó",
+  "storage-warning": "Hệ thống lưu trữ đã được cập nhật; biểu đồ này hiện hiển thị dữ liệu lưu trữ theo giờ thay vì theo ngày như trước.",
messages/en.json (1)

660-660: Fix typos and clarify the storage warning

"grap" → "graph"; make the sentence concise and user-facing.

Apply:

-  "storage-warning": "The storage system has recently changed and now this grap is showing storage per hour instead of the previous data",
+  "storage-warning": "The storage system has changed. This graph now shows hourly storage instead of daily totals.",
CHANGELOG.md (1)

5-8: Fix heading level to satisfy markdownlint (MD001).

Use H2 after H1 to avoid “heading-increment” warnings.

-### 12.26.7 (2025-09-16)
-
-### 12.26.6 (2025-09-16)
+## 12.26.7 (2025-09-16)
+
+## 12.26.6 (2025-09-16)
messages/de.json (1)

660-661: Polish translation: fix “Grap” and clarify wording.

Replace with more natural German.

-  "storage-warning": "Das Speichersystem hat sich kürzlich geändert und jetzt zeigt dieser Grap die Speicherung pro Stunde anstelle der vorherigen Daten an",
+  "storage-warning": "Das Speichersystem wurde kürzlich geändert. Dieser Graph zeigt jetzt stündliche Speicherwerte statt der bisherigen Tagesdaten.",
messages/ru.json (1)

660-661: Polish translation: replace “стержень” with “график” and clarify.

-  "storage-warning": "Система хранения недавно изменилась, и теперь этот стержень показывает хранение в час вместо предыдущих данных",
+  "storage-warning": "Система хранения недавно изменилась, и теперь этот график показывает почасовые значения вместо прежних суточных данных.",
messages/id.json (1)

660-661: Polish translation: fix “grap” → “grafik” and clarify.

-  "storage-warning": "Sistem penyimpanan baru -baru ini berubah dan sekarang grap ini menunjukkan penyimpanan per jam, bukan data sebelumnya",
+  "storage-warning": "Sistem penyimpanan baru-baru ini berubah. Grafik ini sekarang menampilkan penyimpanan per jam, bukan data harian sebelumnya.",
messages/es.json (1)

660-661: Polish translation: “uva” → “gráfico” and clarify.

-  "storage-warning": "El sistema de almacenamiento ha cambiado recientemente y ahora esta uva muestra el almacenamiento por hora en lugar de los datos anteriores",
+  "storage-warning": "El sistema de almacenamiento ha cambiado recientemente. Este gráfico ahora muestra el almacenamiento por hora en lugar de los datos diarios anteriores.",
src/types/supabase.types.ts (3)

1091-1119: New table storage_hourly — schema LGTM

Shape/relations align with existing daily tables. Make sure the migration index strategy supports typical queries: (app_id, date) with DESC on date.

Provide migration indexes if missing:

+CREATE INDEX IF NOT EXISTS storage_hourly_app_id_date_idx
+  ON public.storage_hourly (app_id, date DESC);

1120-1151: storage_hourly_cache — check TTL/size policy

cache: Json may grow. Consider a retention/TTL and index on (app_id).

+CREATE INDEX IF NOT EXISTS storage_hourly_cache_app_id_idx
+  ON public.storage_hourly_cache (app_id);
+-- Optional: add a job/trigger to prune stale rows by updated_at

1473-1483: Minor: misnamed arg in convert_mb_to_bytes

Args: { gb: number } for convert_mb_to_bytes is surprising. If DB function actually expects mb, consider renaming in SQL and regenerating types. If not, ignore.

messages/ja.json (1)

660-661: Fix JP translation typo (“グラップ” → “グラフ”)

Also prefer “1時間ごと” for clarity.

-  "storage-warning": "ストレージシステムが最近変更され、このグラップは以前のデータではなく1時間あたりのストレージを表示しています",
+  "storage-warning": "ストレージシステムが最近変更され、このグラフは以前のデータではなく1時間ごとのストレージを表示しています",
messages/pt-br.json (1)

660-661: Fix PT-BR translation (“uva” → “gráfico”)

-  "storage-warning": "O sistema de armazenamento mudou recentemente e agora esta uva está mostrando armazenamento por hora em vez dos dados anteriores",
+  "storage-warning": "O sistema de armazenamento mudou recentemente e agora este gráfico mostra armazenamento por hora em vez dos dados anteriores",
src/components/dashboard/UsageCard.vue (6)

5-7: Fix import order to satisfy lint (perfectionist/sort-imports)

Place ExclamationCircle before InformationInfo.

-import InformationInfo from '~icons/heroicons/information-circle'
-import ExclamationCircle from '~icons/heroicons/exclamation-circle'
+import ExclamationCircle from '~icons/heroicons/exclamation-circle'
+import InformationInfo from '~icons/heroicons/information-circle'

90-90: Self-close empty div to satisfy vue/html-self-closing

-            <div class="flex-1"></div>
+            <div class="flex-1" />

85-101: Header structure: simplify and improve a11y

  • H2 shouldn’t wrap block divs; keep text inside H2 and move controls outside.
  • Add aria-label to the warning icon for keyboard users.
-        <h2 class="mb-2 mr-2 text-2xl font-semibold text-slate-800 dark:text-white w-full">
-          <div class="flex items-center justify-center mb-2 mr-2 cursor-pointer">
-            {{ props.title }}
-            <InformationInfo class="text-gray-400 hover:text-blue-500 transition-colors duration-200 w-5 h-5 ml-3" />
-            <div class="flex-1"></div>
-            <div v-if="props.storage" class="font-medium badge badge-error w-5 h-5 mr-2">
+        <div class="flex items-center mb-2 mr-2">
+          <h2 class="text-2xl font-semibold text-slate-800 dark:text-white flex-1">
+            {{ props.title }}
+          </h2>
+          <InformationInfo class="text-gray-400 hover:text-blue-500 transition-colors duration-200 w-5 h-5 ml-3" />
+          <div v-if="props.storage" class="ml-2">
               <div class="group relative flex items-center">
-                <ExclamationCircle class="w-5 h-5 cursor-help text-gray-400" />
+                <ExclamationCircle aria-label="storage warning" class="w-5 h-5 cursor-help text-gray-400" />
                 <div
                   class="pointer-events-none absolute left-1/2 top-full z-10 mt-2 hidden w-max max-w-xs -translate-x-1/2 rounded-md bg-slate-800 px-3 py-2 text-sm text-white shadow-lg group-hover:block dark:bg-slate-200 dark:text-black"
                 >
                   {{ t('storage-warning') }}
                 </div>
               </div>
             </div>
-          </div>
-        </h2>
+        </div>

71-78: Minor calc edge case: use filtered length

Use arrWithoutUndefined.length instead of arr.length to decide denominator.

-  const denominator = arr.length > 2 ? oldTotal : diff
+  const denominator = arrWithoutUndefined.length > 2 ? oldTotal : diff

36-37: Guard undefined subscription dates

If org or dates are missing, show “—”.

-const subscription_anchor_start = dayjs(organizationStore.currentOrganization?.subscription_start).format('YYYY/MM/D')
-const subscription_anchor_end = dayjs(organizationStore.currentOrganization?.subscription_end).format('YYYY/MM/D')
+const subscription_anchor_start = organizationStore.currentOrganization?.subscription_start
+  ? dayjs(organizationStore.currentOrganization.subscription_start).format('YYYY/MM/D') : '—'
+const subscription_anchor_end = organizationStore.currentOrganization?.subscription_end
+  ? dayjs(organizationStore.currentOrganization.subscription_end).format('YYYY/MM/D') : '—'

Also applies to: 131-144


154-159: Consistent prop usage in template

Mixing props.unit and unit is inconsistent. Prefer props.* or destructure with toRefs.

-          {{ total?.toLocaleString() }} {{ unit }}
+          {{ total?.toLocaleString() }} {{ props.unit }}

Also applies to: 166-166

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 000d696 and 8e73e48.

📒 Files selected for processing (24)
  • CHANGELOG.md (1 hunks)
  • messages/de.json (1 hunks)
  • messages/en.json (3 hunks)
  • messages/es.json (1 hunks)
  • messages/fr.json (1 hunks)
  • messages/id.json (1 hunks)
  • messages/it.json (1 hunks)
  • messages/ja.json (1 hunks)
  • messages/ko.json (1 hunks)
  • messages/pl.json (1 hunks)
  • messages/pt-br.json (1 hunks)
  • messages/ru.json (1 hunks)
  • messages/tr.json (1 hunks)
  • messages/vi.json (1 hunks)
  • messages/zh-cn.json (1 hunks)
  • package.json (1 hunks)
  • src/components/dashboard/Usage.vue (2 hunks)
  • src/components/dashboard/UsageCard.vue (3 hunks)
  • src/types/supabase.types.ts (32 hunks)
  • supabase/functions/_backend/public/app/put.ts (1 hunks)
  • supabase/functions/_backend/public/statistics/index.ts (2 hunks)
  • supabase/functions/_backend/triggers/cron_hourly_storage.ts (1 hunks)
  • supabase/functions/_backend/utils/supabase.types.ts (1 hunks)
  • supabase/functions/_backend/utils/version.ts (1 hunks)
🔥 Files not summarized due to errors (7)
  • messages/vi.json: Error: Server error: no LLM provider could handle the message
  • supabase/functions/_backend/utils/supabase.types.ts: Error: Server error: no LLM provider could handle the message
  • messages/pl.json: Error: Server error: no LLM provider could handle the message
  • messages/it.json: Error: Server error: no LLM provider could handle the message
  • messages/ko.json: Error: Server error: no LLM provider could handle the message
  • messages/en.json: Error: Server error: no LLM provider could handle the message
  • messages/fr.json: Error: Server error: no LLM provider could handle the message
✅ Files skipped from review due to trivial changes (3)
  • package.json
  • messages/zh-cn.json
  • messages/tr.json
🚧 Files skipped from review as they are similar to previous changes (3)
  • src/components/dashboard/Usage.vue
  • supabase/functions/_backend/triggers/cron_hourly_storage.ts
  • supabase/functions/_backend/public/statistics/index.ts
🧰 Additional context used
📓 Path-based instructions (5)
{src,supabase/functions/_backend}/**/*.{ts,tsx,js,jsx,vue}

📄 CodeRabbit inference engine (CLAUDE.md)

Enforce code style per @antfu/eslint-config: single quotes and no semicolons

Files:

  • supabase/functions/_backend/utils/supabase.types.ts
  • supabase/functions/_backend/utils/version.ts
  • supabase/functions/_backend/public/app/put.ts
  • src/components/dashboard/UsageCard.vue
  • src/types/supabase.types.ts
supabase/functions/_backend/utils/**/*.{ts,tsx,js}

📄 CodeRabbit inference engine (CLAUDE.md)

Put shared utilities and database schemas under supabase/functions/_backend/utils/

Files:

  • supabase/functions/_backend/utils/supabase.types.ts
  • supabase/functions/_backend/utils/version.ts
supabase/functions/_backend/**/*.{ts,tsx,js}

📄 CodeRabbit inference engine (CLAUDE.md)

Write backend code as Deno-compatible Supabase Edge Functions

Files:

  • supabase/functions/_backend/utils/supabase.types.ts
  • supabase/functions/_backend/utils/version.ts
  • supabase/functions/_backend/public/app/put.ts
supabase/functions/_backend/public/**/*.{ts,tsx,js}

📄 CodeRabbit inference engine (CLAUDE.md)

Place public API endpoints (app, bundle, device management) under supabase/functions/_backend/public/

Files:

  • supabase/functions/_backend/public/app/put.ts
src/**/*.vue

📄 CodeRabbit inference engine (CLAUDE.md)

Use Vue 3 with Composition API and <script setup> syntax in Vue components

Files:

  • src/components/dashboard/UsageCard.vue
🧠 Learnings (2)
📚 Learning: 2025-09-08T17:29:59.398Z
Learnt from: CR
PR: Cap-go/capgo#0
File: CLAUDE.md:0-0
Timestamp: 2025-09-08T17:29:59.398Z
Learning: Applies to supabase/functions/_backend/public/**/*.{ts,tsx,js} : Place public API endpoints (app, bundle, device management) under supabase/functions/_backend/public/

Applied to files:

  • src/types/supabase.types.ts
📚 Learning: 2025-09-08T17:29:59.398Z
Learnt from: CR
PR: Cap-go/capgo#0
File: CLAUDE.md:0-0
Timestamp: 2025-09-08T17:29:59.398Z
Learning: Applies to supabase/functions/_backend/utils/**/*.{ts,tsx,js} : Put shared utilities and database schemas under supabase/functions/_backend/utils/

Applied to files:

  • src/types/supabase.types.ts
🧬 Code graph analysis (3)
supabase/functions/_backend/utils/supabase.types.ts (1)
src/types/supabase.types.ts (1)
  • Json (1-7)
supabase/functions/_backend/utils/version.ts (1)
scripts/update-version.js (1)
  • version (7-7)
supabase/functions/_backend/public/app/put.ts (1)
supabase/functions/_backend/utils/hono.ts (1)
  • quickError (196-208)
🪛 GitHub Actions: Deploy Preview Environment
src/components/dashboard/UsageCard.vue

[error] 6-6: bun lint: perfectionist/sort-imports: Expected '~icons/heroicons/exclamation-circle' to come before '~icons/heroicons/information-circle'.


[warning] 89-89: bun lint: vue/html-self-closing: Require self-closing on HTML elements (

).

🪛 markdownlint-cli2 (0.17.2)
CHANGELOG.md

5-5: Heading levels should only increment by one level at a time
Expected: h2; Actual: h3

(MD001, heading-increment)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Run tests
🔇 Additional comments (12)
messages/fr.json (1)

660-660: Fix FR translation: replace "raisin" with "graphique" and improve phrasing

File: messages/fr.json (around line 660)

-  "storage-warning": "Le système de stockage a récemment changé et maintenant ce raisin affiche un stockage par heure au lieu des données précédentes",
+  "storage-warning": "Le système de stockage a récemment été modifié et ce graphique affiche désormais des données de stockage horaires au lieu des données précédentes",

Verify other locales contain this key and consistent punctuation by running: fd -a 'messages' | rg -n 'storage-warning' -C1

messages/it.json (1)

660-660: Fix typo (“GRAP” → “grafico”) and improve Italian phrasing for clarity.

Proposed copy reads more naturally and clarifies the switch to hourly granularity.

-  "storage-warning": "Il sistema di archiviazione è recentemente cambiato e ora questo GRAP mostra l'archiviazione all'ora anziché i dati precedenti",
+  "storage-warning": "Il sistema di archiviazione è cambiato di recente: questo grafico ora mostra lo spazio di archiviazione su base oraria invece dell'aggregazione precedente",

Verification: search returned "messages/**/*.json: No such file or directory (os error 2)". Confirm file path or re-run the search for the key before merging.

messages/en.json (2)

602-602: Remove hardcoded 63113903 from UI text and align with backend constant

  • Findings: messages/en.json and supabase/functions/_backend/public/app/put.ts contain 63113903; tests reference 63113904 and 63072000. See:
    • messages/en.json: line 601
    • supabase/functions/_backend/public/app/put.ts: line 18
    • supabase/tests/16_test_retention.sql: lines 116, 120, 123–127
  • Action: replace the user-facing number with generic copy or interpolate a single backend constant; update server code and tests to use that canonical value. Proposed copy-only fix:
-  "retention-to-big": "Retention cannot be bigger than 63113903 (2 years)",
+  "retention-to-big": "Retention cannot exceed 2 years",
  • Also change supabase/functions/_backend/public/app/put.ts to throw the same message by referencing the server-side constant and reconcile tests to the agreed limit.

9-9: Duplicate i18n key with inconsistent casing — verify and deduplicate

Automated verification errored; confirm whether messages/en.json contains both "CustomId" (reported at line 9) and "custom-id" (reported at line 270). If they are duplicates, remove the "CustomId" entry and reuse "custom-id" to avoid translation drift.

-  "CustomId": "Custom ID",
supabase/functions/_backend/utils/supabase.types.ts (1)

1071-1131: Migration & usage verified — tables, FKs, indexes, and code paths present.
supabase/migrations/20250609033531_migrate_to_hourly_storage.sql defines storage_hourly (id BIGSERIAL, app_id TEXT REFERENCES apps(app_id) ON DELETE CASCADE, date TIMESTAMP WITH TIME ZONE, size BIGINT) and storage_hourly_cache (id BIGSERIAL, app_id TEXT REFERENCES apps(app_id), cache JSONB, created_at/updated_at TIMESTAMP), creates the indexes and enables RLS. supabase/functions/_backend/triggers/cron_hourly_storage.ts reads/writes storage_hourly and storage_hourly_cache; public.get_app_metrics_v2 queries storage_hourly; tests/cron_hourly_storage.test.ts exercise the flow.

src/types/supabase.types.ts (5)

1014-1015: Plan version added; seed/defaults and UI must handle it

plans.version added to Row/Insert/Update. Confirm migrations backfill a sensible default and any UI/API that renders plans doesn’t assume absence of this field.

Also applies to: 1037-1038, 1060-1061


1670-1671: Cron helper args rename/order changes — validate callers

Arg shapes changed (get_next_cron_value, parse_cron_field). Confirm all RPC callers pass the new keys in correct order (object arg names).

Also applies to: 1968-1969


10-33: GraphQL schema exposure looks fine — confirm consumers & client schema usage

Repo registers graphql_public in supabase/config.toml, but I found no references to Database['graphql_public'] or any { schema: 'graphql_public' } usages; code and tests consistently reference Database['public'] (tests/test-utils.ts creates the client with db.schema = 'public').

  • If graphql_public functions are intended to be consumed, update callers/helpers to target that schema (use generics or pass { schema: 'graphql_public' } to clients) and update tests/test-utils.ts accordingly.
  • Files to check: src/types/supabase.types.ts (graphql_public addition), supabase/config.toml, tests/test-utils.ts.

701-741: Resolved — devices.id intentionally removed; code uses device_id

Repo search found no references to devices.id; migrations define PRIMARY KEY (app_id, device_id) and the codebase (supabase migrations/seed, functions, tests, and UI) consistently uses device_id. No action required.


581-583: Breaking change: deleted_account.Insert.email is now required

src/types/supabase.types.ts (lines 581–583) marks Insert.email as required — this is breaking. Ensure every insert into deleted_account provides email or revert the type. Repo search returned no .insert(...) call sites for deleted_account; manually verify client/generated/external call sites and update them.

supabase/functions/_backend/utils/version.ts (1)

1-1: Approve — version matches package.json
package.json and supabase/functions/_backend/utils/version.ts both show 12.26.7.

src/components/dashboard/UsageCard.vue (1)

1-100: Fix import ordering and the empty div self-closing rule

  • Reorder the imports in the <script setup> (top of src/components/dashboard/UsageCard.vue) to satisfy perfectionist/sort-imports; run the project lint script (package.json "lint") to verify.
  • Replace the empty element at src/components/dashboard/UsageCard.vue:89 —
    — with a self-closing tag (
    ) or remove/use CSS spacing to satisfy vue/html-self-closing.

Comment thread messages/ko.json Outdated
"storage-limit-exceeded": "저장 공간 한도 초과",
"storage-updates-stopped-upgrade-required": "저장 공간 한도에 도달했습니다. 업로드가 차단되었습니다. 서비스를 복구하려면 업그레이드해 주세요.",
"storage-usage": "저장 공간 사용량:",
"storage-warning": "저장 시스템이 최근에 변경되었으며 이제이 포도는 이전 데이터 대신 시간당 스토리지를 표시합니다.",
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

🧩 Analysis chain

Fix mistranslation and improve naturalness in storage warning.

"포도" → "그래프", spacing and style tweaks; "시간당 스토리지" → "시간별 저장소 사용량" reads more natural and precise.

Apply this diff:

-  "storage-warning": "저장 시스템이 최근에 변경되었으며 이제이 포도는 이전 데이터 대신 시간당 스토리지를 표시합니다.",
+  "storage-warning": "저장 시스템이 최근에 변경되었습니다. 이제 이 그래프는 기존 데이터 대신 시간별 저장소 사용량을 표시합니다.",

Run to ensure all locales define the same key and spot similar MT artifacts:


🏁 Script executed:

#!/bin/bash
# Verify presence and inspect translations of storage-warning across locales
fd -t f 'messages/.*\.json$' | while read -r f; do
  echo "==> $f"
  rg -n '"storage-warning"\s*:' "$f" -C1 || echo "missing in $f"
done

Length of output: 0


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "PWD: $(pwd)"
echo "--- top-level ls ---"
ls -la | sed -n '1,120p' || true

echo "--- locating messages directories ---"
find . -type d -name 'messages' -print || true

echo "--- listing JSON files under any messages/ path ---"
find . -path '*/messages/*' -type f -name '*.json' -print || true

echo "--- searching for key \"storage-warning\" across repo ---"
grep -nR '"storage-warning"' || echo "no matches for storage-warning"

echo "--- show messages/ko.json around line 660 if present ---"
if [ -f messages/ko.json ]; then
  echo "==> messages/ko.json (lines 640-680)"
  nl -ba messages/ko.json | sed -n '640,680p'
else
  echo "messages/ko.json not found at repo root"
fi

Length of output: 7027


Fix mistranslated "graph" in ko.json and across all locales; correct English source first.

  • Apply KO fix (messages/ko.json, line 660):
-  "storage-warning": "저장 시스템이 최근에 변경되었으며 이제이 포도는 이전 데이터 대신 시간당 스토리지를 표시합니다.",
+  "storage-warning": "저장 시스템이 최근에 변경되었습니다. 이제 이 그래프는 기존 데이터 대신 시간별 저장소 사용량을 표시합니다.",
  • Fix en.json (line 660): change "grap" → "graph" (source typo caused the MT artifacts).
  • Inspect and correct the same key in messages/{de,en,es,fr,id,it,ja,ko,pl,pt-br,ru,tr,vi,zh-cn}.json (line 660) — many locales currently translate "grap" as fruit/incorrect terms and must be updated to the proper word/translation for "graph".
📝 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
"storage-warning": "저장 시스템이 최근에 변경되었으며 이제이 포도는 이전 데이터 대신 시간당 스토리지를 표시합니다.",
"storage-warning": "저장 시스템이 최근에 변경되었습니다. 이제 이 그래프는 기존 데이터 대신 시간별 저장소 사용량을 표시합니다.",

Comment thread messages/pl.json Outdated
"storage-limit-exceeded": "przekroczono limit przechowywania",
"storage-updates-stopped-upgrade-required": "Osiągnąłeś limit przechowywania. Przesyłanie zostało zablokowane. Proszę uaktualnić, aby przywrócić usługę.",
"storage-usage": "Użycie pamięci:",
"storage-warning": "System pamięci niedawno się zmienił, a teraz to winogrona pokazuje pamięć na godzinę zamiast poprzednich danych",
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

🧩 Analysis chain

Verify translation accuracy for storage warning message

The Polish translation on Line 660 seems to contain some unclear or potentially incorrect terms. The phrase "winogrona pokazuje pamięć" (grapes shows memory) appears unusual and may be an incorrect translation. Please verify that this translation accurately conveys the intended meaning about the storage system change to hourly tracking.


🏁 Script executed:

#!/bin/bash
# Description: Check for similar storage warning messages in other language files to compare translations

# Search for the storage-warning key in other language files
rg -n "storage-warning" --type=json messages/

Length of output: 2116


Replace Polish "winogrona" with "wykres" and fix source typo 'grap' → 'graph'

The English source contains "grap" (typo) and many locales translated it as "grape" (pl: "winogrona"); update messages/en.json:660 to "graph" and change messages/pl.json:660 to use "wykres" (graph). Suggested Polish string:
"System przechowywania danych został niedawno zmieniony i teraz ten wykres pokazuje przechowywanie na godzinę zamiast wcześniejszych danych"
Locations: messages/en.json:660, messages/pl.json:660.

🤖 Prompt for AI Agents
In messages/pl.json around line 660 and messages/en.json around line 660, update
the Polish and English messages: replace the mistaken "winogrona" in Polish with
"wykres" and fix the English source typo "grap" → "graph"; set the Polish string
to "System przechowywania danych został niedawno zmieniony i teraz ten wykres
pokazuje przechowywanie na godzinę zamiast wcześniejszych danych" and update the
English message at the same line to use "graph" instead of "grap".

Comment on lines +2059 to +2082
reset_and_seed_app_data: {
Args: { p_app_id: string }
Returns: undefined
}
reset_and_seed_app_stats_data: {
Args: { p_app_id: string }
Returns: undefined
}
reset_and_seed_data: {
Args: Record<PropertyKey, never>
Returns: undefined
}
reset_and_seed_stats_data: {
Args: Record<PropertyKey, never>
Returns: undefined
}
reset_app_data: {
Args: { p_app_id: string }
Returns: undefined
}
reset_app_stats_data: {
Args: { p_app_id: string }
Returns: undefined
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

🧩 Analysis chain

Reset/seed functions in public schema — restrict execution

These DB functions (reset_*, reset_and_seed_*) must not be callable by anon/authenticated roles. Ensure privileges are limited to service role only.


🏁 Script executed:

#!/bin/bash
# Find any client-side invocation risks
rg -nP "rpc\(['\"](reset|seed|reset_and_seed)[^'\"]*['\"]" --type=ts --type=vue -S

Length of output: 27


🏁 Script executed:

#!/bin/bash
# Find any client-side invocation risks in TS, JS, and Vue files
rg -nP "rpc\(['\"](?:reset_and_seed|reset|seed)[^'\"]*['\"]" -g '*.ts' -g '*.tsx' -g '*.js' -g '*.jsx' -g '*.vue' -n

Length of output: 593


Restrict reset/seed RPCs to service role

These DB functions in the public schema must not be callable by anon/authenticated roles.

Found invocations (update tests or ensure they use service-role key):

  • tests/test-utils.ts:122 — supabase.rpc('reset_and_seed_app_data', …)
  • tests/test-utils.ts:156 — supabase.rpc('reset_app_data', …)
  • tests/test-utils.ts:185 — supabase.rpc('reset_and_seed_app_stats_data', …)
  • tests/test-utils.ts:212 — supabase.rpc('reset_app_stats_data', …)
  • tests/test-utils.ts:264 — supabase.rpc('seed_test_data', …)

Remediation: revoke EXECUTE for anon/authenticated roles (grant only to service role) and ensure tests invoke these RPCs using a service-role key or via backend-only endpoints.

🤖 Prompt for AI Agents
In src/types/supabase.types.ts around lines 2059 to 2082 the public-schema RPC
type declarations for reset_and_seed_app_data, reset_and_seed_app_stats_data,
reset_and_seed_data, reset_and_seed_stats_data, reset_app_data and
reset_app_stats_data indicate these functions are exposed; update the DB
permissions to revoke EXECUTE from anon/authenticated roles and grant EXECUTE
only to the service role (or schema owner), and update test invocations to call
these RPCs using the service-role key (or via backend-only endpoints) instead of
the anon client—apply the permission changes in your migration/SQL (REVOKE
EXECUTE ON FUNCTION ... FROM anon, auth; GRANT EXECUTE ON FUNCTION ... TO
service_role) and modify tests/test-utils.ts at lines ~122,156,185,212,264 to
use a Supabase client initialized with the service-role key or call a secured
backend endpoint.

| "canceled"
usage_mode: "5min" | "day" | "month" | "cycle" | "last_saved"
usage_mode: "last_saved" | "5min" | "day" | "cycle"
user_min_right:
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

🧩 Analysis chain

Enum change: usage_mode no longer includes 'month'

Audit UI/backend for references to "month" and adjust any selector/validation.

Also applies to: 2416-2417


🏁 Script executed:

#!/bin/bash
rg -nP "'month'|\"month\"" --type=ts --type=vue

Length of output: 27


🏁 Script executed:

#!/bin/bash
# Search for 'month' literal in TypeScript, TSX, and Vue files
rg -nP "'month'|\"month\"" -g '*.{ts,tsx,vue}' -n

Length of output: 860


Remove/replace 'month' usages — usage_mode no longer includes "month"

  • supabase/functions/_backend/utils/supabase.types.ts:2143 (usage_mode type still contains "month"); 2380 (default array includes "month") — remove or migrate this variant.
  • supabase/functions/_backend/private/stripe_checkout.ts:12 (type) and 51 (default argument) — recurrence uses 'month'; update type/default to supported cadence.
  • src/services/stripe.ts:91 — sets recurrence = 'month' when not yearly; update mapping.
  • tests/cron_hourly_storage.test.ts:18 — uses dayjs.add(1, 'month') (time-unit in test); change only if billing semantics change.

Audit UI selectors/validation and any DB/data migration logic that assume the "month" variant; update or map values consistently across backend, frontend, and tests.

🤖 Prompt for AI Agents
In src/types/supabase.types.ts around line 2182, the usage_mode value "month" is
no longer supported; remove "month" from the usage_mode union and any default
arrays that include it, and then propagate the change: update
supabase/functions/_backend/utils/supabase.types.ts (remove "month" at ~2143 and
from default array at ~2380), update
supabase/functions/_backend/private/stripe_checkout.ts to use the supported
cadence in the type and default argument (replace occurrences at ~12 and ~51),
change src/services/stripe.ts (around line ~91) to map non-yearly recurrence to
the correct supported value instead of 'month', and review tests (e.g.,
tests/cron_hourly_storage.test.ts) and UI selectors/validation and any DB
migration scripts to remove or map old "month" values to the new cadence so
backend, frontend, and stored data remain consistent.

Comment on lines +17 to +22
if (body.retention && body.retention >= 63113904) {
throw quickError(400, 'retention_to_big', 'Retention cannot be bigger than 63113903 (2 years)', { retention: body.retention })
}
else if (body.retention && body.retention < 0) {
throw quickError(400, 'retention_to_small', 'Retention cannot be smaller than 0', { retention: body.retention })
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Harden retention validation: avoid truthy checks and guard NaN/Infinity.

Current checks skip validation for 0 and any non-finite numbers may pass to DB. Validate only when provided, ensure finite, and keep exact bounds.

-  if (body.retention && body.retention >= 63113904) {
-    throw quickError(400, 'retention_to_big', 'Retention cannot be bigger than 63113903 (2 years)', { retention: body.retention })
-  }
-  else if (body.retention && body.retention < 0) {
-    throw quickError(400, 'retention_to_small', 'Retention cannot be smaller than 0', { retention: body.retention })
-  }
+  const MAX_RETENTION = 63113903 // 2 years in seconds (approx. 365.2425 * 2 * 86400)
+  if (body.retention != null) {
+    if (!Number.isFinite(body.retention)) {
+      throw quickError(400, 'retention_invalid', 'Retention must be a finite number', { retention: body.retention })
+    }
+    if (body.retention > MAX_RETENTION) {
+      throw quickError(400, 'retention_to_big', 'Retention cannot be bigger than 63113903 (2 years)', { retention: body.retention })
+    }
+    if (body.retention < 0) {
+      throw quickError(400, 'retention_to_small', 'Retention cannot be smaller than 0', { retention: body.retention })
+    }
+  }

If adding a new error code (retention_invalid) affects clients, reuse an existing code and adjust the message accordingly.

📝 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
if (body.retention && body.retention >= 63113904) {
throw quickError(400, 'retention_to_big', 'Retention cannot be bigger than 63113903 (2 years)', { retention: body.retention })
}
else if (body.retention && body.retention < 0) {
throw quickError(400, 'retention_to_small', 'Retention cannot be smaller than 0', { retention: body.retention })
}
const MAX_RETENTION = 63113903 // 2 years in seconds (approx. 365.2425 * 2 * 86400)
if (body.retention != null) {
if (!Number.isFinite(body.retention)) {
throw quickError(400, 'retention_invalid', 'Retention must be a finite number', { retention: body.retention })
}
if (body.retention > MAX_RETENTION) {
throw quickError(400, 'retention_to_big', 'Retention cannot be bigger than 63113903 (2 years)', { retention: body.retention })
}
if (body.retention < 0) {
throw quickError(400, 'retention_to_small', 'Retention cannot be smaller than 0', { retention: body.retention })
}
}
🤖 Prompt for AI Agents
In supabase/functions/_backend/public/app/put.ts around lines 17–22, the current
truthy checks allow 0 and non-finite values through; change to only validate
when retention is explicitly provided (e.g., body.retention !== undefined),
ensure it's a number and finite (use typeof === 'number' and isFinite), then
enforce 0 <= retention <= 63113903 (2 years) and throw quickError(400,
'retention_to_big', 'Retention cannot be bigger than 63113903 (2 years)', {
retention }) for too-large values or quickError(400, 'retention_to_small',
'Retention cannot be smaller than 0', { retention }) for negatives; for
NaN/Infinity/non-number reuse an existing error code (do not introduce new code)
and provide a clear message like 'Retention must be a finite number'.

Comment thread messages/en.json Outdated
Comment thread messages/fr.json Outdated
Comment thread src/components/dashboard/UsageCard.vue
Comment thread src/components/dashboard/UsageCard.vue Outdated
Comment thread supabase/functions/_backend/triggers/cron_hourly_storage.ts
Comment thread supabase/functions/_backend/triggers/cron_hourly_storage.ts
Comment thread supabase/functions/_backend/triggers/cron_hourly_storage.ts
Comment thread supabase/functions/_backend/triggers/cron_hourly_storage.ts
Comment thread supabase/functions/_backend/triggers/cron_hourly_storage.ts
Comment thread supabase/functions/_backend/triggers/cron_hourly_storage.ts
@sonarqubecloud
Copy link
Copy Markdown

Quality Gate Failed Quality Gate failed

Failed conditions
E Reliability Rating on New Code (required ≥ A)

See analysis details on SonarQube Cloud

Catch issues before they fail your Quality Gate with our IDE extension SonarQube for IDE

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

Caution

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

⚠️ Outside diff range comments (2)
src/components/dashboard/UsageCard.vue (2)

38-40: Fix potential crash when summing an empty slice.

sum([]) throws without an initial value; this happens when datas has exactly 2 defined points (slice(0, -2) = []). Initialize the reducer.

-function sum(arr: number[]) {
-  return arr.reduce((a, b) => a + b)
-}
+function sum(arr: number[]) {
+  return arr.reduce((a, b) => a + b, 0)
+}

56-78: Use filtered length for denominator decision.

Using arr.length (includes undefined) can skew the condition; use arrWithoutUndefined.length.

-  const denominator = arr.length > 2 ? oldTotal : diff
+  const denominator = arrWithoutUndefined.length > 2 ? oldTotal : diff
🧹 Nitpick comments (6)
messages/it.json (1)

660-660: Polish the phrasing for clarity/naturalness (IT).

Suggested: use “su base oraria” to read more idiomatic.

-  "storage-warning": "Il sistema di archiviazione è recentemente cambiato e ora questo grafico mostra l'archiviazione all'ora anziché i dati precedenti",
+  "storage-warning": "Il sistema di archiviazione è cambiato di recente; questo grafico ora mostra lo storage su base oraria invece dei dati precedenti",
messages/vi.json (1)

660-660: Improve Vietnamese wording.

Make it concise and natural (“lượng lưu trữ theo giờ”).

-  "storage-warning": "Hệ thống lưu trữ gần đây đã thay đổi và bây giờ biểu đồ này đang hiển thị lưu trữ mỗi giờ thay vì dữ liệu trước đó",
+  "storage-warning": "Hệ thống lưu trữ vừa được thay đổi; biểu đồ này giờ hiển thị lượng lưu trữ theo giờ thay vì dữ liệu trước đó",
messages/fr.json (1)

660-660: French style tweak for clarity.

Use “désormais” + “utilisation du stockage par heure”.

-  "storage-warning": "Le système de stockage a récemment changé et maintenant ce graphique affiche un stockage par heure au lieu des données précédentes",
+  "storage-warning": "Le système de stockage a récemment changé ; ce graphique affiche désormais l’utilisation du stockage par heure au lieu des données précédentes",
src/components/dashboard/UsageCard.vue (2)

85-150: Header layout/tooltip: simplify structure to avoid spacer hacks.

The h2 wraps a centered flex with an extra flex-1 spacer; prefer justify-between on the row and keep title left, icons right for consistent alignment.

Would you like a minimal patch to swap the wrapper to flex justify-between and drop the empty spacer?


138-147: Improve accessibility and keyboard support of the warning tooltip.

  • Add tabindex to the trigger and show tooltip on focus as well as hover.
  • Expose an aria-label using the translated text.
-            <div v-if="props.storage" class="font-medium badge badge-error w-5 h-5 mr-2">
-              <div class="group relative flex items-center">
+            <div v-if="props.storage" class="mr-2">
+              <div class="group relative flex items-center" tabindex="0" :aria-label="t('storage-warning')">
                 <ExclamationCircle class="w-5 h-5 cursor-help text-gray-400" />
                 <div
-                  class="pointer-events-none absolute left-1/2 top-full z-10 mt-2 hidden w-max max-w-xs -translate-x-1/2 rounded-md bg-slate-800 px-3 py-2 text-sm text-white shadow-lg group-hover:block dark:bg-slate-200 dark:text-black"
+                  class="pointer-events-none absolute left-1/2 top-full z-10 mt-2 hidden w-max max-w-xs -translate-x-1/2 rounded-md bg-slate-800 px-3 py-2 text-sm text-white shadow-lg group-hover:block group-focus:block dark:bg-slate-200 dark:text-black"
                 >
                   {{ t('storage-warning') }}
                 </div>
               </div>
             </div>
messages/en.json (1)

660-660: Tighten English phrasing.

Small readability tweak.

-  "storage-warning": "The storage system has recently changed and now this graph is showing storage per hour instead of the previous data",
+  "storage-warning": "The storage system recently changed; this chart now shows hourly storage instead of the previous data",
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 8e73e48 and 54fa821.

📒 Files selected for processing (15)
  • messages/de.json (1 hunks)
  • messages/en.json (3 hunks)
  • messages/es.json (1 hunks)
  • messages/fr.json (1 hunks)
  • messages/id.json (1 hunks)
  • messages/it.json (1 hunks)
  • messages/ja.json (1 hunks)
  • messages/ko.json (1 hunks)
  • messages/pl.json (1 hunks)
  • messages/pt-br.json (1 hunks)
  • messages/ru.json (1 hunks)
  • messages/tr.json (1 hunks)
  • messages/vi.json (1 hunks)
  • messages/zh-cn.json (1 hunks)
  • src/components/dashboard/UsageCard.vue (3 hunks)
🚧 Files skipped from review as they are similar to previous changes (8)
  • messages/zh-cn.json
  • messages/id.json
  • messages/de.json
  • messages/es.json
  • messages/tr.json
  • messages/ja.json
  • messages/pt-br.json
  • messages/ru.json
🧰 Additional context used
📓 Path-based instructions (2)
src/**/*.vue

📄 CodeRabbit inference engine (CLAUDE.md)

Use Vue 3 with Composition API and <script setup> syntax in Vue components

Files:

  • src/components/dashboard/UsageCard.vue
{src,supabase/functions/_backend}/**/*.{ts,tsx,js,jsx,vue}

📄 CodeRabbit inference engine (CLAUDE.md)

Enforce code style per @antfu/eslint-config: single quotes and no semicolons

Files:

  • src/components/dashboard/UsageCard.vue
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Analyze (java-kotlin)
🔇 Additional comments (5)
messages/pl.json (1)

660-660: Refine Polish wording; avoid “pamięć” (RAM) and use “przechowywanie/zużycie magazynu”.

-  "storage-warning": "System pamięci niedawno się zmienił, a teraz ten wykres pokazuje pamięć na godzinę zamiast poprzednich danych",
+  "storage-warning": "System przechowywania danych niedawno się zmienił; ten wykres pokazuje teraz godzinowe zużycie magazynu zamiast wcześniejszych danych",
messages/ko.json (1)

660-660: Fix spacing and phrasing (KO).

“이제이” → “이제 이”; use “시간별 저장소 사용량”.

-  "storage-warning": "스토리지 시스템이 최근에 변경되었으며 이제이 그래프는 이전 데이터 대신 시간당 스토리지를 표시합니다.",
+  "storage-warning": "저장 시스템이 최근에 변경되었습니다. 이제 이 그래프는 기존 데이터 대신 시간별 저장소 사용량을 표시합니다.",
src/components/dashboard/UsageCard.vue (2)

6-6: Icon import OK.

Importing ExclamationCircle is correct and used below.


36-37: Guard date formatting; useOrganizationStore is already present.

useOrganizationStore is initialized in this file (line 35). Replace the two dayjs-format calls so undefined/invalid dates don't render "Invalid Date":

@@
-const subscription_anchor_start = dayjs(organizationStore.currentOrganization?.subscription_start).format('YYYY/MM/D')
-const subscription_anchor_end = dayjs(organizationStore.currentOrganization?.subscription_end).format('YYYY/MM/D')
+const start = dayjs(organizationStore.currentOrganization?.subscription_start)
+const end = dayjs(organizationStore.currentOrganization?.subscription_end)
+const subscription_anchor_start = start.isValid() ? start.format('YYYY/MM/D') : t('unknown')
+const subscription_anchor_end = end.isValid() ? end.format('YYYY/MM/D') : t('unknown')

Likely an incorrect or invalid review comment.

messages/en.json (1)

9-9: CustomId entry OK.

Adding “Custom ID” here is fine.

Comment thread messages/en.json
"retention": "Auto delete bundles not used (after x seconds)",
"retention-cannot-be-negative": "Retention cannot be a negative number",
"retention-to-big": "Retention cannot be bigger than 2147483647",
"retention-to-big": "Retention cannot be bigger than 63113903 (2 years)",
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

🧩 Analysis chain

Verify the “2 years” seconds constant.

63113903 doesn’t match common 2‑year second counts (63,072,000 for 730 days; 63,115,200 for 2×365.25 days). Ensure this string matches backend validation to avoid user confusion and i18n drift.


🏁 Script executed:

#!/bin/bash
# Find server/client validations/constants for retention limits
rg -nP "(retention|TTL|expiry|expire).*?(63113903|63115200|63072000|2147483647)" -C2
rg -nP "(retention-to-big|retention).*2\s*year" -C2 messages/

Length of output: 7671


Align retention constant across backend, tests, and i18n

  • Backend enforces >= 63113904 and error text shows 63113903 (2 years): supabase/functions/_backend/public/app/put.ts (lines ~16–19) / messages/en.json (line 601).
  • Tests use 63072000 as the 2‑year boundary and set allowed max to 63071999: supabase/tests/16_test_retention.sql (lines 123–126).
  • Many other locale files use 2147483647 for this message (messages/*.json).

Action: pick a canonical seconds value (e.g., 63072000 for 730 days) and update the backend check, messages/en.json/translations, and tests to match; extract a single shared constant (MAX_RETENTION_SECONDS) to avoid future drift.

🤖 Prompt for AI Agents
In messages/en.json around line 601, the retention error message uses 63113903
while backend and tests use multiple differing values; unify to a canonical
seconds value (suggest 63072000 for 730 days) and propagate it: update
messages/en.json and all other locale files to use the chosen value in the
"retention-to-big" string, update the backend check in
supabase/functions/_backend/public/app/put.ts to compare against the same
constant, update supabase/tests/16_test_retention.sql to use the same boundary
and expected allowed max, and extract a single shared constant named
MAX_RETENTION_SECONDS (exported/consumed by backend and tests) so all checks and
messages reference that one constant to prevent future drift.

@riderx
Copy link
Copy Markdown
Member

riderx commented Oct 19, 2025

Conflict not taking care of, PR not atomic, closed

@riderx riderx closed this Oct 19, 2025
@riderx riderx deleted the storage_hourly branch December 31, 2025 20:27
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants