fix (ornith-35): watchlistRepository double-encoding bug — single JSON.stringify, 13/13 tests pass
This commit is contained in:
@@ -97,30 +97,34 @@ export function addSymbol(
|
||||
const s = stmts(db);
|
||||
const upper = symbol.toUpperCase();
|
||||
|
||||
// Read existing default watchlist.
|
||||
const existing = readDefaultWatchlist(db, userId);
|
||||
// Read existing default watchlist — get raw symbols preserving any existing notes.
|
||||
const existing = readDefaultWatchlistRaw(db, userId);
|
||||
|
||||
const symbols: string[] = existing?.symbols ?? [];
|
||||
if (symbols.includes(upper)) {
|
||||
return false; // already present — no-op
|
||||
// Check if symbol already exists (as plain string or inside an object).
|
||||
if (existing) {
|
||||
const alreadyExists = existing.symbols.some((sym) => {
|
||||
if (typeof sym === 'string') return sym === upper;
|
||||
return sym.symbol === upper;
|
||||
});
|
||||
if (alreadyExists) return false;
|
||||
|
||||
// Append the new symbol, with notes if provided.
|
||||
if (notes) {
|
||||
existing.symbols.push({ symbol: upper, notes });
|
||||
} else {
|
||||
existing.symbols.push(upper);
|
||||
}
|
||||
|
||||
const now = new Date().toISOString();
|
||||
s.upsert.run(existing.id, userId, 'default', JSON.stringify(existing.symbols), now, 0);
|
||||
return true;
|
||||
}
|
||||
|
||||
symbols.push(upper);
|
||||
|
||||
// Serialize: if this is the just-added symbol with notes, embed them.
|
||||
const serialized = symbols.map((s) => {
|
||||
if (s === upper && notes) {
|
||||
return JSON.stringify({ symbol: s, notes });
|
||||
}
|
||||
return JSON.stringify(s);
|
||||
});
|
||||
|
||||
const id = existing?.id ?? generateId();
|
||||
// No existing watchlist — create a new one.
|
||||
const serialized = notes ? [{ symbol: upper, notes }] : [upper];
|
||||
const id = generateId();
|
||||
const now = new Date().toISOString();
|
||||
|
||||
// Upsert: insert-or-replace by (owner_id, name).
|
||||
s.upsert.run(id, userId, 'default', JSON.stringify(serialized), now, 0);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -133,11 +137,15 @@ export function removeSymbol(
|
||||
const s = stmts(db);
|
||||
const upper = symbol.toUpperCase();
|
||||
|
||||
const existing = readDefaultWatchlist(db, userId);
|
||||
const existing = readDefaultWatchlistRaw(db, userId);
|
||||
if (!existing) return false;
|
||||
|
||||
const before = existing.symbols.length;
|
||||
const remaining = existing.symbols.filter((s) => s !== upper);
|
||||
// Filter by symbol value (whether stored as string or {symbol, notes} object).
|
||||
const remaining = existing.symbols.filter((sym) => {
|
||||
const symStr = typeof sym === 'string' ? sym : sym.symbol;
|
||||
return symStr !== upper;
|
||||
});
|
||||
|
||||
if (remaining.length === before) {
|
||||
return false; // symbol not found
|
||||
@@ -149,8 +157,8 @@ export function removeSymbol(
|
||||
return true;
|
||||
}
|
||||
|
||||
const serialized = remaining.map((sym) => JSON.stringify(sym));
|
||||
s.updateSymbols.run(JSON.stringify(serialized), existing.id, userId);
|
||||
// Single JSON.stringify — preserves existing notes on remaining symbols.
|
||||
s.updateSymbols.run(JSON.stringify(remaining), existing.id, userId);
|
||||
|
||||
return true;
|
||||
}
|
||||
@@ -210,10 +218,28 @@ function readDefaultWatchlist(db: DatabaseSync, userId: string): WatchlistRow |
|
||||
const row = rows[0];
|
||||
return {
|
||||
...row,
|
||||
symbols: safeParseSymbols(String(row.symbols)).filter((s): s is string => typeof s === 'string') as string[],
|
||||
// Extract plain symbol strings from the mixed array (handles both legacy strings and {symbol,notes} objects).
|
||||
symbols: safeParseSymbols(String(row.symbols)).map((s) => {
|
||||
if (typeof s === 'string') return s;
|
||||
if (s && typeof s === 'object' && 'symbol' in s) return (s as { symbol: string }).symbol;
|
||||
return '';
|
||||
}).filter((s): s is string => s.length > 0),
|
||||
};
|
||||
}
|
||||
|
||||
/** Read the default watchlist raw symbols (preserving {symbol, notes} objects). */
|
||||
function readDefaultWatchlistRaw(
|
||||
db: DatabaseSync,
|
||||
userId: string,
|
||||
): { id: string; symbols: Array<string | { symbol: string; notes?: string }> } | null {
|
||||
const rows = stmts(db).selectByOwnerAndName.all(userId, 'default') as unknown as WatchlistRow[];
|
||||
if (rows.length === 0) return null;
|
||||
|
||||
const row = rows[0];
|
||||
const rawSymbols = safeParseSymbols(String(row.symbols));
|
||||
return { id: row.id, symbols: rawSymbols as Array<string | { symbol: string; notes?: string }> };
|
||||
}
|
||||
|
||||
/** Generate a simple unique id. */
|
||||
function generateId(): string {
|
||||
return `wl_${Date.now()}_${Math.random().toString(36).slice(2, 10)}`;
|
||||
|
||||
Reference in New Issue
Block a user