Skip to content
Merged

Dev #119

Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
47 changes: 47 additions & 0 deletions docs/changelog.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
# Changelog

All notable changes to Kal will be documented in this file.

---

## [1.0.2] - 2026-04-07

### Added

- Per-minute usage progress bar in the Rate Limits documentation tab β€” see your real-time minute-level usage alongside daily limits
- Numbered page navigation on the Food Database β€” quickly jump to any page instead of clicking through one at a time
- Live reset countdown timers on dashboard usage cards β€” see exactly when your daily, monthly, and per-minute limits reset

### Fixed

- Corrected example Food IDs in the Setup page API quick reference to match actual records

---

## [1.0.1] - 2026-04-06

### Fixed

- Rate limit usage stats now only count successful requests β€” previously, rate-limited (429) responses were still counted against your usage, which made your dashboard stats appear higher than actual usage
- Improved error tracking in request logs with clearer error messages for failed requests

---

## [1.0.0] - 2026-03-17

### Added

- API v1 with versioned endpoints (`/api/v1/*`)
- Food database with comprehensive nutritional data
- API key management dashboard
- Request logging and analytics
- Setup wizard with code examples
- Feedback and bug reporting system
- Collapsible sidebar with mobile-responsive drawer

### Changed

- Migrated all API endpoints to versioned paths
- Improved dashboard performance and loading states

---
52 changes: 52 additions & 0 deletions packages/kal-backend/src/routers/api-keys.ts
Original file line number Diff line number Diff line change
Expand Up @@ -200,11 +200,63 @@ export const apiKeysRouter = router({
isRevoked: false,
});

// Minute usage β€” only valid if minuteWindow is within the current minute
const now = new Date();
const currentMinuteStart = new Date(now);
currentMinuteStart.setSeconds(0, 0);

const minuteWindowIsStale =
!usage?.minuteWindow ||
new Date(usage.minuteWindow).getTime() < currentMinuteStart.getTime();

const minuteUsed = minuteWindowIsStale ? 0 : usage?.minuteCount || 0;

return {
tier: user?.tier || "free",
dailyUsed: usage?.dailyCount || 0,
monthlyUsed,
activeKeyCount,
minuteUsed,
};
}),

/**
* Get key-specific stats: active, revoked, and expired counts
*/
getKeyStats: protectedProcedure.query(async ({ ctx }) => {
const now = new Date();
const keysCollection = ctx.db.collection<ApiKey>("api_keys");

const [activeCount, revokedCount, expiredCount, totalCount] =
await Promise.all([
// Active: not revoked and not expired
keysCollection.countDocuments({
userId: ctx.userId,
isRevoked: false,
$or: [{ expiresAt: null }, { expiresAt: { $gt: now } }],
}),
// Revoked
keysCollection.countDocuments({
userId: ctx.userId,
isRevoked: true,
}),
// Expired: not revoked but past expiration
keysCollection.countDocuments({
userId: ctx.userId,
isRevoked: false,
expiresAt: { $ne: null, $lte: now },
}),
// Total ever created
keysCollection.countDocuments({
userId: ctx.userId,
}),
]);

return {
active: activeCount,
revoked: revokedCount,
expired: expiredCount,
total: totalCount,
};
}),

Expand Down
Loading
Loading