refactor(#56): Improve how the dex updating is handled and the error messaging around it

This commit is contained in:
Josh Creek
2026-01-10 12:07:05 +00:00
parent 845cc1c739
commit b1b6e63b6f
11 changed files with 700 additions and 107 deletions
+39 -2
View File
@@ -43,6 +43,35 @@ class CatchRecordRepository {
return dbData;
}
/**
* Bulk upsert catch records in a single request when possible.
*
* Requires a unique constraint matching the `onConflict` columns.
* If the database is not configured with that constraint, the caller should
* catch the error and fall back to per-record upserts.
*/
async bulkUpsert(records: Partial<CatchRecord>[]): Promise<CatchRecord[]> {
const dbRows: Partial<CatchRecordDB>[] = records.map((r) => ({
userId: this.userId,
pokedexId: this.pokedexId,
...this.transformToDatabase(r)
}));
const { data: result, error } = await this.supabase
.from('catch_records')
.upsert(dbRows, {
onConflict: 'userId,pokedexId,pokedexEntryId'
})
.select();
if (error) {
console.error('Supabase error bulk upserting catch records:', error);
throw new Error(`Failed to bulk upsert catch records: ${error.message}`);
}
return (result ?? []).map((row) => this.transformCatchRecord(row));
}
async findById(id: string): Promise<CatchRecord | null> {
const { data, error } = await this.supabase
.from('catch_records')
@@ -138,7 +167,11 @@ class CatchRecordRepository {
return this.update(data._id, data);
} else if (data.pokedexEntryId) {
// Try to find existing record for this user, pokemon, and pokedex
const existing = await this.findByUserAndPokemon(this.userId, data.pokedexEntryId, this.pokedexId);
const existing = await this.findByUserAndPokemon(
this.userId,
data.pokedexEntryId,
this.pokedexId
);
if (existing) {
return this.update(existing._id, data);
} else {
@@ -148,7 +181,11 @@ class CatchRecordRepository {
return this.create(data);
}
async findByUserAndPokemon(userId: string, pokedexEntryId: string, pokedexId: string): Promise<CatchRecord | null> {
async findByUserAndPokemon(
userId: string,
pokedexEntryId: string,
pokedexId: string
): Promise<CatchRecord | null> {
const numericId = Number(pokedexEntryId);
if (isNaN(numericId)) {
return null;
+16 -3
View File
@@ -70,7 +70,10 @@ class PokedexRepository {
if (error) {
console.error('Error creating pokedex:', error);
throw new Error(`Failed to create pokedex: ${error.message}`);
// Preserve the Supabase error code so the API layer can map to a user-friendly response.
throw Object.assign(new Error(`Failed to create pokedex: ${error.message}`), {
code: error.code
});
}
if (!result) {
@@ -91,12 +94,22 @@ class PokedexRepository {
.select()
.single();
if (error || !result) return null;
if (error) {
console.error('Error updating pokedex:', error);
throw Object.assign(new Error(`Failed to update pokedex: ${error.message}`), {
code: error.code
});
}
if (!result) return null;
return this.transform(result);
}
async delete(id: string): Promise<void> {
const { error } = await this.supabase.from('pokedexes').delete().eq('id', id).eq('userId', this.userId);
const { error } = await this.supabase
.from('pokedexes')
.delete()
.eq('id', id)
.eq('userId', this.userId);
if (error) {
console.error('Error deleting pokedex:', error);