Skip to content

Commit b159fd7

Browse files
author
n3kosempai
committed
[feature] cache for categories
1 parent eb167c5 commit b159fd7

4 files changed

Lines changed: 122 additions & 18 deletions

File tree

architecture.md

Lines changed: 46 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -149,31 +149,39 @@ Automatically detects image type from HTTP `Content-Type` header:
149149

150150
### 2. Database Caching
151151

152-
Intelligent daily caching for app data to minimize API calls and improve performance.
152+
Intelligent caching system for app data with configurable expiration times to minimize API calls and improve performance.
153153

154154
#### Architecture
155155
- **Database**: SQLite (`~/.local/share/com.gatorand.klia-store/kliastore.db`)
156156
- **Tables**:
157-
- `destacados`: Stores app of the day data
158-
- `apps_of_the_week`: Stores weekly featured apps
157+
- `destacados`: Stores app of the day data (daily cache)
158+
- `apps_of_the_week`: Stores weekly featured apps (daily cache)
159+
- `categories`: Stores Flathub categories (weekly cache - 7 days)
159160
- `cache_metadata`: Tracks last update dates per section
160161

161162
#### Cache Strategy
162-
The system uses date-based cache invalidation:
163-
1. **On App Launch**: Check if cached data is from current day
164-
2. **Same Day**: Load from SQLite database (no API calls)
165-
3. **New Day**: Fetch from API and update database
163+
The system uses date-based cache invalidation with configurable durations:
164+
1. **On App Launch**: Check if cached data is older than specified duration
165+
2. **Valid Cache**: Load from SQLite database (no API calls)
166+
3. **Expired Cache**: Fetch from API and update database
167+
168+
**Cache Durations**:
169+
- App of the Day: Daily (0 days = current day only)
170+
- Apps of the Week: Daily (0 days = current day only)
171+
- Categories: Weekly (7 days)
166172

167173
#### Components
168174

169175
**Database Cache Manager** (`src/utils/dbCache.ts`):
170176
- `DBCacheManager`: Singleton managing all DB cache operations
171177
- Key methods:
172-
- `shouldUpdateSection(sectionName)`: Returns true if data is from a previous day
178+
- `shouldUpdateSection(sectionName, maxDaysOld)`: Returns true if data is older than maxDaysOld days
173179
- `getCachedAppOfTheDay()`: Retrieves cached app of the day
174180
- `cacheAppOfTheDay(app)`: Stores app of the day with current date
175181
- `getCachedAppsOfTheWeek()`: Retrieves cached weekly apps
176182
- `cacheAppsOfTheWeek(apps)`: Stores weekly apps with current date
183+
- `getCachedCategories()`: Retrieves cached categories
184+
- `cacheCategories(categories)`: Stores categories with current date
177185
- `updateSectionDate(sectionName)`: Updates last_update_date in cache_metadata
178186

179187
**Database Schema**:
@@ -205,11 +213,19 @@ CREATE TABLE cache_metadata (
205213
last_update_date TEXT NOT NULL, -- YYYY-MM-DD format
206214
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP
207215
);
216+
217+
-- Categories (weekly cache)
218+
CREATE TABLE categories (
219+
category_name TEXT PRIMARY KEY,
220+
cached_at DATETIME DEFAULT CURRENT_TIMESTAMP
221+
);
208222
```
209223

210-
#### Cache Flow Example (App of the Day)
224+
#### Cache Flow Examples
225+
226+
**App of the Day (Daily Cache)**:
211227
1. **User opens app**
212-
2. `useAppOfTheDay` hook calls `dbCacheManager.shouldUpdateSection("appOfTheDay")`
228+
2. `useAppOfTheDay` hook calls `dbCacheManager.shouldUpdateSection("appOfTheDay", 0)`
213229
3. Check `cache_metadata` for last_update_date
214230
4. **If same day**:
215231
- Load from `destacados` table
@@ -220,11 +236,25 @@ CREATE TABLE cache_metadata (
220236
- Update `cache_metadata` with current date
221237
- Return fresh data
222238

239+
**Categories (Weekly Cache)**:
240+
1. **User opens app**
241+
2. `useCategories` hook calls `dbCacheManager.shouldUpdateSection("categories", 7)`
242+
3. Check `cache_metadata` for last_update_date
243+
4. **If less than 7 days old**:
244+
- Load from `categories` table
245+
- Return cached data (no API call)
246+
5. **If 7+ days old or no cache**:
247+
- Call Flathub API
248+
- Store in `categories` table
249+
- Update `cache_metadata` with current date
250+
- Return fresh data
251+
223252
#### Benefits
224-
- **Reduced API Calls**: Only 1 API call per day per section (instead of every app launch)
253+
- **Reduced API Calls**: Only 1 API call per configured period (daily/weekly) instead of every app launch
225254
- **Faster Load Times**: SQLite queries are instant vs network requests
226255
- **Offline Support**: Can show cached data even without internet
227256
- **Bandwidth Savings**: Minimal data transfer for daily usage
257+
- **Flexible Expiration**: Different cache durations for different data types
228258

229259
## Key Implementation Details
230260

@@ -281,6 +311,11 @@ CREATE TABLE cache_metadata (
281311

282312
## Development Guidelines
283313

314+
### Code Style
315+
- **IMPORTANT**: All code comments must be written in English
316+
- Use clear, descriptive variable and function names
317+
- Follow TypeScript best practices and type safety
318+
284319
### Adding New API Endpoints
285320
1. Add TypeScript types in `src/types/index.ts`
286321
2. Create service method in `src/services/api.ts`

src/hooks/useCategories.ts

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,26 @@
11
import { useQuery } from "@tanstack/react-query";
22
import { apiService } from "../services/api";
3+
import { dbCacheManager } from "../utils/dbCache";
34

45
export const useCategories = () => {
56
return useQuery({
67
queryKey: ["categories"],
7-
queryFn: apiService.getCategories,
8+
queryFn: async () => {
9+
// Check if cache should be updated (weekly cache = 7 days)
10+
const shouldUpdate = await dbCacheManager.shouldUpdateSection("categories", 7);
11+
12+
if (!shouldUpdate) {
13+
// Load from cache (less than 7 days old)
14+
const cachedCategories = await dbCacheManager.getCachedCategories();
15+
if (cachedCategories.length > 0) {
16+
return cachedCategories;
17+
}
18+
}
19+
20+
// Fetch from API and update cache
21+
const categories = await apiService.getCategories();
22+
await dbCacheManager.cacheCategories(categories);
23+
return categories;
24+
},
825
});
926
};

src/hooks/useCompleteSetup.ts

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ export async function completeSetup() {
3838
)
3939
`);
4040

41-
// Tabla para app destacada (app of the day)
41+
// Table for featured app (app of the day)
4242
await db.execute(`
4343
CREATE TABLE IF NOT EXISTS destacados (
4444
app_id TEXT PRIMARY KEY,
@@ -51,7 +51,7 @@ export async function completeSetup() {
5151
)
5252
`);
5353

54-
// Tabla para apps de la semana
54+
// Table for apps of the week
5555
await db.execute(`
5656
CREATE TABLE IF NOT EXISTS apps_of_the_week (
5757
app_id TEXT PRIMARY KEY,
@@ -63,7 +63,7 @@ export async function completeSetup() {
6363
)
6464
`);
6565

66-
// Tabla para metadata de caché (fechas de actualización)
66+
// Table for cache metadata (update dates)
6767
await db.execute(`
6868
CREATE TABLE IF NOT EXISTS cache_metadata (
6969
section_name TEXT PRIMARY KEY,
@@ -72,6 +72,14 @@ export async function completeSetup() {
7272
)
7373
`);
7474

75+
// Table for categories (weekly cache)
76+
await db.execute(`
77+
CREATE TABLE IF NOT EXISTS categories (
78+
category_name TEXT PRIMARY KEY,
79+
cached_at DATETIME DEFAULT CURRENT_TIMESTAMP
80+
)
81+
`);
82+
7583
console.log("Setup completed successfully");
7684
} catch (err) {
7785
console.error("Failed to complete setup:", err);

src/utils/dbCache.ts

Lines changed: 47 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,13 @@ export class DBCacheManager {
3434
return now.toISOString().split("T")[0]; // YYYY-MM-DD
3535
}
3636

37-
async shouldUpdateSection(sectionName: string): Promise<boolean> {
37+
private getDateDaysAgo(daysAgo: number): string {
38+
const date = new Date();
39+
date.setDate(date.getDate() - daysAgo);
40+
return date.toISOString().split("T")[0]; // YYYY-MM-DD
41+
}
42+
43+
async shouldUpdateSection(sectionName: string, maxDaysOld = 0): Promise<boolean> {
3844
await this.initialize();
3945
if (!this.db) throw new Error("Database not initialized");
4046

@@ -46,10 +52,17 @@ export class DBCacheManager {
4652
);
4753

4854
if (result.length === 0) {
49-
return true; // No hay caché, necesita actualizar
55+
return true; // No cache exists, needs update
56+
}
57+
58+
// If maxDaysOld is 0, compare with current date (daily behavior)
59+
if (maxDaysOld === 0) {
60+
return result[0].last_update_date !== currentDate;
5061
}
5162

52-
return result[0].last_update_date !== currentDate;
63+
// If maxDaysOld > 0, check if last update is older than maxDaysOld days
64+
const oldestAllowedDate = this.getDateDaysAgo(maxDaysOld);
65+
return result[0].last_update_date < oldestAllowedDate;
5366
}
5467

5568
async updateSectionDate(sectionName: string): Promise<void> {
@@ -168,6 +181,37 @@ export class DBCacheManager {
168181

169182
await this.updateSectionDate("appsOfTheWeek");
170183
}
184+
185+
// Categories (weekly cache)
186+
async getCachedCategories(): Promise<string[]> {
187+
await this.initialize();
188+
if (!this.db) throw new Error("Database not initialized");
189+
190+
const result = await this.db.select<
191+
Array<{
192+
category_name: string;
193+
}>
194+
>("SELECT category_name FROM categories ORDER BY category_name");
195+
196+
return result.map((row) => row.category_name);
197+
}
198+
199+
async cacheCategories(categories: string[]): Promise<void> {
200+
await this.initialize();
201+
if (!this.db) throw new Error("Database not initialized");
202+
203+
// Clear table first
204+
await this.db.execute("DELETE FROM categories");
205+
206+
for (const category of categories) {
207+
await this.db.execute(
208+
"INSERT INTO categories (category_name) VALUES ($1)",
209+
[category],
210+
);
211+
}
212+
213+
await this.updateSectionDate("categories");
214+
}
171215
}
172216

173217
export const dbCacheManager = DBCacheManager.getInstance();

0 commit comments

Comments
 (0)