mirror of
https://github.com/jcreek/LivingDexTracker.git
synced 2026-09-15 01:52:39 +00:00
3a2c18bbeb
`npm run check` reported 15 errors and `npm run lint` 20, all pre-existing, so
neither gate could pass. Fixing them turned up three real bugs:
- SignOut destructured `{ error }` off `.then(() => {})`, which resolves to
undefined, so every sign-out threw a TypeError - after the signed-out event had
already been emitted. Sign-out also left the user on the protected page they
were on, still showing its content; it now returns them to the home page and
re-runs the server loads.
- SignUp passed `redirectTo`, which is not a signUp option and was silently
ignored, so the confirmation link has always used Supabase's configured site
URL. Documented rather than changed, since pointing it elsewhere needs an
absolute allow-listed URL.
- The Pokédex page tracked totalRecordsCreated but never passed it to the box
view, so the "Processed N entries so far" progress message never rendered.
The rest is typing and dead code: cookie callback parameters in hooks.server.ts
and +layout.ts, the untyped supabase props, a query-builder type that made
PostgREST rows untyped downstream, an unused session destructure, and
`while (true)` paging loops rewritten as `for (;;)`.
165 lines
4.9 KiB
TypeScript
165 lines
4.9 KiB
TypeScript
import type { SupabaseClient } from '@supabase/supabase-js';
|
|
import type {
|
|
PokedexExportIntegration,
|
|
PokedexExportIntegrationDB
|
|
} from '$lib/models/PokedexExportIntegration';
|
|
|
|
// `from()` returns a table builder; only `select()` yields the filter builder that `eq`/`is`
|
|
// live on. Typing the scope helper with the table builder made `data` untyped downstream.
|
|
type IntegrationQuery = ReturnType<ReturnType<SupabaseClient['from']>['select']>;
|
|
|
|
class PokedexExportIntegrationRepository {
|
|
constructor(
|
|
private supabase: SupabaseClient,
|
|
private userId: string,
|
|
private pokedexId: string | null
|
|
) {}
|
|
|
|
private transform(db: PokedexExportIntegrationDB): PokedexExportIntegration {
|
|
return {
|
|
_id: db.id,
|
|
userId: db.userId,
|
|
pokedexId: db.pokedexId,
|
|
provider: db.provider,
|
|
enabled: db.enabled,
|
|
fileName: db.fileName,
|
|
folderId: db.folderId,
|
|
path: db.path,
|
|
accessToken: db.accessToken,
|
|
refreshToken: db.refreshToken,
|
|
accessTokenExpiresAt: db.accessTokenExpiresAt,
|
|
metadata: db.metadata,
|
|
lastExportedAt: db.lastExportedAt,
|
|
lastError: db.lastError
|
|
};
|
|
}
|
|
|
|
private baseQuery() {
|
|
return this.supabase.from('pokedex_export_integrations').select('*').eq('userId', this.userId);
|
|
}
|
|
|
|
private addPokedexScope(query: IntegrationQuery): IntegrationQuery {
|
|
if (this.pokedexId) {
|
|
return query.eq('pokedexId', this.pokedexId);
|
|
}
|
|
return query.is('pokedexId', null);
|
|
}
|
|
|
|
async listEnabled(): Promise<PokedexExportIntegration[]> {
|
|
const { data, error } = await this.addPokedexScope(this.baseQuery()).eq('enabled', true);
|
|
|
|
if (error) {
|
|
console.error('Failed to load export integrations:', error);
|
|
throw new Error(`Failed to load export integrations: ${error.message}`);
|
|
}
|
|
if (!data) return [];
|
|
// PostgREST rows are untyped without generated database types.
|
|
return (data as PokedexExportIntegrationDB[]).map((row) => this.transform(row));
|
|
}
|
|
|
|
async listAll(): Promise<PokedexExportIntegration[]> {
|
|
const { data, error } = await this.addPokedexScope(this.baseQuery());
|
|
|
|
if (error) {
|
|
console.error('Failed to load export integrations:', error);
|
|
throw new Error(`Failed to load export integrations: ${error.message}`);
|
|
}
|
|
if (!data) return [];
|
|
// PostgREST rows are untyped without generated database types.
|
|
return (data as PokedexExportIntegrationDB[]).map((row) => this.transform(row));
|
|
}
|
|
|
|
async upsert(
|
|
data: Partial<PokedexExportIntegrationDB> & Pick<PokedexExportIntegrationDB, 'provider'>
|
|
): Promise<PokedexExportIntegration> {
|
|
const payload: Partial<PokedexExportIntegrationDB> = {
|
|
userId: this.userId,
|
|
pokedexId: null,
|
|
...data
|
|
};
|
|
const { data: result, error } = await this.supabase
|
|
.from('pokedex_export_integrations')
|
|
.upsert(payload, {
|
|
onConflict: 'userId,provider'
|
|
})
|
|
.select()
|
|
.single();
|
|
|
|
if (error) {
|
|
console.error('Failed to upsert export integration:', error);
|
|
throw new Error(`Failed to save export integration: ${error.message}`);
|
|
}
|
|
if (!result) {
|
|
throw new Error('Failed to save export integration: No result returned');
|
|
}
|
|
return this.transform(result);
|
|
}
|
|
|
|
async listEnabledForPokedexOrUser(): Promise<PokedexExportIntegration[]> {
|
|
const base = this.supabase
|
|
.from('pokedex_export_integrations')
|
|
.select('*')
|
|
.eq('userId', this.userId)
|
|
.eq('enabled', true);
|
|
const query = this.pokedexId
|
|
? base.or(`pokedexId.eq.${this.pokedexId},pokedexId.is.null`)
|
|
: base.is('pokedexId', null);
|
|
const { data, error } = await query;
|
|
if (error) {
|
|
console.error('Failed to load export integrations:', error);
|
|
throw new Error(`Failed to load export integrations: ${error.message}`);
|
|
}
|
|
if (!data) return [];
|
|
// PostgREST rows are untyped without generated database types.
|
|
return (data as PokedexExportIntegrationDB[]).map((row) => this.transform(row));
|
|
}
|
|
|
|
async updateTokens(
|
|
id: string,
|
|
patch: {
|
|
accessToken?: string;
|
|
refreshToken?: string | null;
|
|
accessTokenExpiresAt?: string | null;
|
|
}
|
|
): Promise<void> {
|
|
const query = this.supabase
|
|
.from('pokedex_export_integrations')
|
|
.update(patch)
|
|
.eq('id', id)
|
|
.eq('userId', this.userId);
|
|
const { error } = this.pokedexId
|
|
? await query.eq('pokedexId', this.pokedexId)
|
|
: await query.is('pokedexId', null);
|
|
|
|
if (error) {
|
|
console.error('Failed to update export integration tokens:', error);
|
|
}
|
|
}
|
|
|
|
async updateExportStatus(
|
|
id: string,
|
|
patch: {
|
|
lastExportedAt?: string | null;
|
|
lastError?: string | null;
|
|
metadata?: Record<string, unknown> | null;
|
|
folderId?: string | null;
|
|
path?: string | null;
|
|
}
|
|
): Promise<void> {
|
|
const query = this.supabase
|
|
.from('pokedex_export_integrations')
|
|
.update(patch)
|
|
.eq('id', id)
|
|
.eq('userId', this.userId);
|
|
const { error } = this.pokedexId
|
|
? await query.eq('pokedexId', this.pokedexId)
|
|
: await query.is('pokedexId', null);
|
|
|
|
if (error) {
|
|
console.error('Failed to update export integration status:', error);
|
|
}
|
|
}
|
|
}
|
|
|
|
export default PokedexExportIntegrationRepository;
|