Files
bincio-activity/mobile/db/queries.ts
T
2026-04-24 15:45:51 +02:00

146 lines
4.5 KiB
TypeScript

import { useSQLiteContext } from 'expo-sqlite';
// ── Types ──────────────────────────────────────────────────────────────────
export type ActivityRow = {
id: string;
source_hash: string;
detail_json: string;
timeseries_json: string | null;
geojson: string | null;
original_path: string | null;
synced_at: number | null;
origin: 'local' | 'remote';
created_at: number;
};
export type ActivitySummary = {
id: string;
title: string;
sport: string;
started_at: string;
distance_m: number | null;
duration_s: number | null;
elevation_gain_m: number | null;
origin: 'local' | 'remote';
synced_at: number | null;
};
// ── Activities ─────────────────────────────────────────────────────────────
export function useActivities(): ActivitySummary[] {
const db = useSQLiteContext();
// Summaries are derived from the stored detail_json at query time.
// JSON extraction via SQLite's json_extract keeps the table schema simple.
const rows = db.getAllSync<{
id: string;
origin: 'local' | 'remote';
synced_at: number | null;
title: string;
sport: string;
started_at: string;
distance_m: number | null;
duration_s: number | null;
elevation_gain_m: number | null;
}>(`
SELECT
id, origin, synced_at,
json_extract(detail_json, '$.title') AS title,
json_extract(detail_json, '$.sport') AS sport,
json_extract(detail_json, '$.started_at') AS started_at,
json_extract(detail_json, '$.distance_m') AS distance_m,
json_extract(detail_json, '$.duration_s') AS duration_s,
json_extract(detail_json, '$.elevation_gain_m') AS elevation_gain_m
FROM activities
ORDER BY json_extract(detail_json, '$.started_at') DESC
`);
return rows;
}
export function useActivity(id: string): ActivityRow | null {
const db = useSQLiteContext();
return db.getFirstSync<ActivityRow>(
'SELECT * FROM activities WHERE id = ?',
[id],
) ?? null;
}
export async function insertActivity(
db: ReturnType<typeof useSQLiteContext>,
row: Pick<ActivityRow, 'id' | 'source_hash' | 'detail_json' | 'timeseries_json' | 'geojson' | 'original_path' | 'origin'>,
): Promise<void> {
await db.runAsync(
`INSERT OR IGNORE INTO activities
(id, source_hash, detail_json, timeseries_json, geojson, original_path, origin)
VALUES (?, ?, ?, ?, ?, ?, ?)`,
[
row.id,
row.source_hash,
row.detail_json,
row.timeseries_json ?? null,
row.geojson ?? null,
row.original_path ?? null,
row.origin,
],
);
}
export async function upsertRemoteActivity(
db: ReturnType<typeof useSQLiteContext>,
id: string,
detailJson: string,
): Promise<boolean> {
const now = Math.floor(Date.now() / 1000);
const result = await db.runAsync(
`INSERT INTO activities (id, source_hash, detail_json, origin, synced_at)
VALUES (?, ?, ?, 'remote', ?)
ON CONFLICT(id) DO UPDATE SET
detail_json = excluded.detail_json,
synced_at = excluded.synced_at
WHERE origin = 'remote'`,
[id, id, detailJson, now],
);
return result.changes > 0;
}
export async function deleteRemoteActivities(
db: ReturnType<typeof useSQLiteContext>,
): Promise<number> {
const result = await db.runAsync(`DELETE FROM activities WHERE origin = 'remote'`);
return result.changes;
}
// ── Settings ───────────────────────────────────────────────────────────────
export async function getSetting(
db: ReturnType<typeof useSQLiteContext>,
key: string,
): Promise<string | null> {
const row = db.getFirstSync<{ value: string }>(
'SELECT value FROM settings WHERE key = ?',
[key],
);
return row?.value ?? null;
}
export async function setSetting(
db: ReturnType<typeof useSQLiteContext>,
key: string,
value: string,
): Promise<void> {
await db.runAsync(
`INSERT INTO settings (key, value) VALUES (?, ?)
ON CONFLICT(key) DO UPDATE SET value = excluded.value`,
[key, value],
);
}
export function useSetting(key: string): string | null {
const db = useSQLiteContext();
const row = db.getFirstSync<{ value: string }>(
'SELECT value FROM settings WHERE key = ?',
[key],
);
return row?.value ?? null;
}