POWR/lib/db/services/EventCache.ts
DocNR 29c4dd1675 feat(database): Add workout and template persistence
Implements database tables and services for workout and template storage:
- Updates schema to version 5 with new workout and template tables
- Adds WorkoutService for CRUD operations on workouts
- Enhances TemplateService for template management
- Creates NostrWorkoutService for bidirectional Nostr event handling
- Implements React hooks for database access
- Connects workout store to database layer for persistence
- Improves offline support with publication queue

This change ensures workouts and templates are properly saved to SQLite
and can be referenced across app sessions, while maintaining Nostr
integration for social sharing.
2025-03-08 15:48:07 -05:00

121 lines
3.1 KiB
TypeScript

// lib/db/services/EventCache.ts
import { SQLiteDatabase } from 'expo-sqlite';
import { NostrEvent } from '@/types/nostr';
import { DbService } from '../db-service';
export class EventCache {
private db: DbService;
constructor(database: SQLiteDatabase) {
this.db = new DbService(database);
}
/**
* Store a Nostr event in the cache
*/
async setEvent(event: NostrEvent, skipExisting: boolean = false): Promise<void> {
if (!event.id) return;
try {
// Check if event already exists
if (skipExisting) {
const exists = await this.db.getFirstAsync<{ id: string }>(
'SELECT id FROM nostr_events WHERE id = ?',
[event.id]
);
if (exists) return;
}
// Store the event
await this.db.withTransactionAsync(async () => {
await this.db.runAsync(
`INSERT OR REPLACE INTO nostr_events
(id, pubkey, kind, created_at, content, sig, raw_event, received_at)
VALUES (?, ?, ?, ?, ?, ?, ?, ?)`,
[
event.id,
event.pubkey || '',
event.kind,
event.created_at,
event.content,
event.sig || '',
JSON.stringify(event),
Date.now()
]
);
// Store event tags
if (event.tags && event.tags.length > 0) {
// Delete existing tags first
await this.db.runAsync(
'DELETE FROM event_tags WHERE event_id = ?',
[event.id]
);
// Insert new tags
for (let i = 0; i < event.tags.length; i++) {
const tag = event.tags[i];
if (tag.length >= 2) {
await this.db.runAsync(
'INSERT INTO event_tags (event_id, name, value, index_num) VALUES (?, ?, ?, ?)',
[event.id, tag[0], tag[1], i]
);
}
}
}
});
} catch (error) {
console.error('Error caching event:', error);
throw error;
}
}
/**
* Get an event from the cache by ID
*/
async getEvent(id: string): Promise<NostrEvent | null> {
try {
const event = await this.db.getFirstAsync<{
id: string;
pubkey: string;
kind: number;
created_at: number;
content: string;
sig: string;
raw_event: string;
}>(
'SELECT * FROM nostr_events WHERE id = ?',
[id]
);
if (!event) return null;
// Get tags
const tags = await this.db.getAllAsync<{
name: string;
value: string;
index_num: number;
}>(
'SELECT name, value, index_num FROM event_tags WHERE event_id = ? ORDER BY index_num',
[id]
);
// Build the event object
const nostrEvent: NostrEvent = {
id: event.id,
pubkey: event.pubkey,
kind: event.kind,
created_at: event.created_at,
content: event.content,
sig: event.sig,
tags: tags.map(tag => [tag.name, tag.value])
};
return nostrEvent;
} catch (error) {
console.error('Error retrieving event:', error);
return null;
}
}
}