feat: hourly storage part 1#1151
Conversation
WalkthroughAdds 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
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
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes Possibly related PRs
Suggested reviewers
Poem
Pre-merge checks and finishing touches❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✨ Finishing touches🧪 Generate unit tests
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.
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. Comment |
There was a problem hiding this comment.
Actionable comments posted: 4
🧹 Nitpick comments (2)
supabase/migrations/20250530233128_base.sql (2)
880-898: Ensure correct date-time matching in LATERAL join.
Castingsh.date::dateimplicitly strips time and may introduce subtle timezone discrepancies. Consider usingdate_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 entirestorage_hourlytable. 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
📒 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 toconstfor immutable array.Since the storage array is no longer being reassigned (due to the removed cumulative operation), using
constis 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:
- The new storage data format from the database function provides the expected cumulative values
- All consumers of this API endpoint still receive data in the expected format
- 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 tsLength 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.tsLength of output: 4468
Verify and clean up legacy storage cumulative logic
The commented-out cumulative
reduceforstorageat
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 legacyreduceentirely 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_hourlyandstorage_hourly_cacheare properly implemented with correct Row, Insert, Update types and foreign key relationships to theappstable.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.
| 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) | ||
| } |
There was a problem hiding this comment.
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 = allDataCommittable 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.
| 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 | ||
| } | ||
| } |
There was a problem hiding this comment.
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.
| 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.
| 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}, | ||
| `) | ||
| } |
There was a problem hiding this comment.
🛠️ Refactor suggestion
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.
| 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.
| startHour = startHour.startOf('hour') | ||
|
|
||
| let endHour = item.storage_removed ?? now.clone() | ||
| if (endHour.isAfter(cycleEndHour)) | ||
| endHour = cycleEndHour | ||
| endHour = endHour.endOf('hour') |
There was a problem hiding this comment.
🛠️ 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.
| 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.
There was a problem hiding this comment.
Actionable comments posted: 5
🔭 Outside diff range comments (1)
src/components/dashboard/Usage.vue (1)
95-97: 🛠️ Refactor suggestionBinary/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
ExclamationCirclemust be imported beforeInformationInfoto satisfyperfectionist/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 tooltipThe
<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
📒 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 multiplyingplans.storage
storageis multiplied by31 * 24( = 744 ).
If the column type isINTEGERthis may overflow for larger original values (e.g. ≥ 2.8 GB ➜ > 2 TB). Verify the column isBIGINTor adjust calculation.
34-37: RLS enabled but no policies defined
ALTER TABLE ... ENABLE ROW LEVEL SECURITY;without at least a defaultpolicywill block all access. Make sure policies are created in the same or a following migration.
| const storageUnit = computed(() => storageDisplayGb.value ? 'GiB' : 'MiB') | ||
| // const noData = computed(() => datas.value.mau.length == 0) |
There was a problem hiding this comment.
🛠️ 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.
| 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) |
There was a problem hiding this comment.
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.
| 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') | ||
|
|
There was a problem hiding this comment.
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.
| return c.json(BRES) | ||
| } | ||
| catch (error) { | ||
| console.error(error) | ||
| cloudlogErr(error) | ||
| return c.json({ error: 'Internal server error' }, 500) | ||
| } |
There was a problem hiding this comment.
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.
| 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) | ||
|
|
There was a problem hiding this comment.
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.
Limit retention api
There was a problem hiding this comment.
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 LGTMShape/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: Jsonmay 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 }forconvert_mb_to_bytesis surprising. If DB function actually expectsmb, 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-labelto 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 lengthUse
arrWithoutUndefined.lengthinstead ofarr.lengthto decide denominator.- const denominator = arr.length > 2 ? oldTotal : diff + const denominator = arrWithoutUndefined.length > 2 ? oldTotal : diff
36-37: Guard undefined subscription datesIf 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 templateMixing
props.unitandunitis inconsistent. Preferprops.*or destructure withtoRefs.- {{ total?.toLocaleString() }} {{ unit }} + {{ total?.toLocaleString() }} {{ props.unit }}Also applies to: 166-166
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 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.tssupabase/functions/_backend/utils/version.tssupabase/functions/_backend/public/app/put.tssrc/components/dashboard/UsageCard.vuesrc/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.tssupabase/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.tssupabase/functions/_backend/utils/version.tssupabase/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 phrasingFile: 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 deduplicateAutomated 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.versionadded 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 callersArg 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 usageRepo 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_idRepo 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 requiredsrc/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.
| "storage-limit-exceeded": "저장 공간 한도 초과", | ||
| "storage-updates-stopped-upgrade-required": "저장 공간 한도에 도달했습니다. 업로드가 차단되었습니다. 서비스를 복구하려면 업그레이드해 주세요.", | ||
| "storage-usage": "저장 공간 사용량:", | ||
| "storage-warning": "저장 시스템이 최근에 변경되었으며 이제이 포도는 이전 데이터 대신 시간당 스토리지를 표시합니다.", |
There was a problem hiding this comment.
🧩 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"
doneLength 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"
fiLength 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.
| "storage-warning": "저장 시스템이 최근에 변경되었으며 이제이 포도는 이전 데이터 대신 시간당 스토리지를 표시합니다.", | |
| "storage-warning": "저장 시스템이 최근에 변경되었습니다. 이제 이 그래프는 기존 데이터 대신 시간별 저장소 사용량을 표시합니다.", |
| "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", |
There was a problem hiding this comment.
🧩 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".
| 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 | ||
| } |
There was a problem hiding this comment.
🧩 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 -SLength 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' -nLength 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: |
There was a problem hiding this comment.
🧩 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=vueLength 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}' -nLength 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.
| 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 }) | ||
| } |
There was a problem hiding this comment.
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.
| 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'.
|
There was a problem hiding this comment.
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
📒 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.
| "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)", |
There was a problem hiding this comment.
🧩 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.
|
Conflict not taking care of, PR not atomic, closed |




Summary by CodeRabbit
New Features
Improvements
Database Changes
Bug Fixes
Localization