mirror of
https://github.com/jcreek/LivingDexTracker.git
synced 2026-07-12 18:43:45 +00:00
refactor(*):Address PR comments
This commit is contained in:
+1
-2
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"name": "LivingDexTracker",
|
||||
"name": "livingdextracker",
|
||||
"version": "0.0.1",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
@@ -29,7 +29,6 @@
|
||||
"supabase:reset": "supabase db reset",
|
||||
"supabase:studio": "supabase studio",
|
||||
"migrate:convert-tsv": "node scripts/convert-tsv-to-sql.js",
|
||||
"migrate:from-mongo": "echo 'MongoDB to Supabase migration complete! Use new supabase endpoints.'",
|
||||
"dev:local": "./scripts/dev-local.sh && npm run dev",
|
||||
"dev:supabase": "supabase start && npm run dev"
|
||||
},
|
||||
|
||||
@@ -13,15 +13,25 @@
|
||||
export let supabase: any;
|
||||
|
||||
async function signInWithEmail() {
|
||||
// TODO use the data and error from the response
|
||||
const { data, error } = await supabase.auth
|
||||
.signInWithPassword({
|
||||
try {
|
||||
const { data, error } = await supabase.auth.signInWithPassword({
|
||||
email: email,
|
||||
password: password
|
||||
})
|
||||
.then(() => {
|
||||
emitSignedInEvent();
|
||||
});
|
||||
|
||||
if (error) {
|
||||
console.error('Sign in error:', error);
|
||||
alert(`Sign in failed: ${error.message}`);
|
||||
return;
|
||||
}
|
||||
|
||||
if (data) {
|
||||
emitSignedInEvent();
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('Sign in error:', err);
|
||||
alert('Sign in failed');
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
|
||||
@@ -12,8 +12,8 @@
|
||||
|
||||
// Create a working copy for editing
|
||||
$: workingCatchRecord = catchRecord || {
|
||||
_id: '',
|
||||
userId: '',
|
||||
_id: `temp-${Date.now()}-${pokedexEntry._id}`, // Generate temporary ID
|
||||
userId: '', // TODO: Get from session context
|
||||
pokedexEntryId: pokedexEntry._id,
|
||||
haveToEvolve: false,
|
||||
caught: false,
|
||||
@@ -136,8 +136,9 @@
|
||||
</div>
|
||||
{/if}
|
||||
<p>
|
||||
<label class="block font-bold mb-1" for={`personalNotesInput-${catchRecord?._id || pokedexEntry._id}`}
|
||||
>Notes:</label
|
||||
<label
|
||||
class="block font-bold mb-1"
|
||||
for={`personalNotesInput-${catchRecord?._id || pokedexEntry._id}`}>Notes:</label
|
||||
>
|
||||
<textarea
|
||||
bind:value={workingCatchRecord.personalNotes}
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import mongoose, { Schema, Document, Types } from 'mongoose';
|
||||
|
||||
export interface CatchRecord extends Document {
|
||||
// Supabase-based CatchRecord interface
|
||||
export interface CatchRecord {
|
||||
_id: string; // Maps to Supabase 'id' field for frontend compatibility
|
||||
userId: string;
|
||||
pokedexEntryId: Types.ObjectId;
|
||||
pokedexEntryId: string; // String representation of the foreign key
|
||||
haveToEvolve: boolean;
|
||||
caught: boolean;
|
||||
inHome: boolean;
|
||||
@@ -10,15 +10,16 @@ export interface CatchRecord extends Document {
|
||||
personalNotes: string;
|
||||
}
|
||||
|
||||
const catchRecordSchema = new Schema<CatchRecord>({
|
||||
userId: String,
|
||||
pokedexEntryId: { type: Schema.Types.ObjectId, ref: 'PokedexEntry', required: true },
|
||||
haveToEvolve: { type: Boolean, default: false },
|
||||
caught: { type: Boolean, default: false },
|
||||
inHome: { type: Boolean, default: false },
|
||||
hasGigantamaxed: { type: Boolean, default: false },
|
||||
personalNotes: String
|
||||
});
|
||||
|
||||
const CatchRecordModel = mongoose.model<CatchRecord>('CatchRecord', catchRecordSchema);
|
||||
export default CatchRecordModel;
|
||||
// Database record type for Supabase
|
||||
export interface CatchRecordDB {
|
||||
id: string;
|
||||
userId: string;
|
||||
pokedexEntryId: number; // Numeric foreign key in database
|
||||
haveToEvolve: boolean;
|
||||
caught: boolean;
|
||||
inHome: boolean;
|
||||
hasGigantamaxed: boolean;
|
||||
personalNotes: string;
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import mongoose, { Schema, Document } from 'mongoose';
|
||||
|
||||
export interface PokedexEntry extends Document {
|
||||
// Supabase-based PokedexEntry interface
|
||||
export interface PokedexEntry {
|
||||
_id: string; // Maps to Supabase 'id' field for frontend compatibility
|
||||
pokedexNumber: number;
|
||||
boxPlacement: { box: number; row: number; column: number };
|
||||
boxPlacementForms: { box: number; row: number; column: number };
|
||||
@@ -11,41 +11,27 @@ export interface PokedexEntry extends Document {
|
||||
gamesToCatchIn: string[];
|
||||
regionToEvolveIn: string;
|
||||
evolutionInformation: string;
|
||||
catchInformation: [
|
||||
{
|
||||
game: string;
|
||||
location: string;
|
||||
notes: string;
|
||||
}
|
||||
];
|
||||
catchInformation: string[];
|
||||
}
|
||||
|
||||
const pokedexEntrySchema = new Schema<PokedexEntry>({
|
||||
pokedexNumber: Number,
|
||||
boxPlacement: {
|
||||
box: Number,
|
||||
row: Number,
|
||||
column: Number
|
||||
},
|
||||
boxPlacementForms: {
|
||||
box: Number,
|
||||
row: Number,
|
||||
column: Number
|
||||
},
|
||||
pokemon: String,
|
||||
form: String,
|
||||
canGigantamax: Boolean,
|
||||
regionToCatchIn: String,
|
||||
gamesToCatchIn: Array<string>,
|
||||
regionToEvolveIn: String,
|
||||
evolutionInformation: String,
|
||||
catchInformation: [
|
||||
{
|
||||
game: String,
|
||||
location: String,
|
||||
notes: String
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
export default mongoose.model<PokedexEntry>('PokedexEntry', pokedexEntrySchema);
|
||||
// Database record type for Supabase
|
||||
export interface PokedexEntryDB {
|
||||
id: number;
|
||||
pokedexNumber: number;
|
||||
pokemon: string;
|
||||
form: string | null;
|
||||
canGigantamax: boolean;
|
||||
regionToCatchIn: string | null;
|
||||
gamesToCatchIn: string[] | null;
|
||||
regionToEvolveIn: string | null;
|
||||
evolutionInformation: string | null;
|
||||
catchInformation: string[] | null;
|
||||
boxPlacementFormsBox: number | null;
|
||||
boxPlacementFormsRow: number | null;
|
||||
boxPlacementFormsColumn: number | null;
|
||||
boxPlacementBox: number | null;
|
||||
boxPlacementRow: number | null;
|
||||
boxPlacementColumn: number | null;
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
}
|
||||
|
||||
@@ -1,35 +1,42 @@
|
||||
import type { CatchRecord } from '$lib/models/CatchRecord';
|
||||
import type { CatchRecord, CatchRecordDB } from '$lib/models/CatchRecord';
|
||||
import type { SupabaseClient } from '@supabase/supabase-js';
|
||||
|
||||
class CatchRecordRepository {
|
||||
constructor(private supabase: SupabaseClient, private userId: string) {}
|
||||
constructor(
|
||||
private supabase: SupabaseClient,
|
||||
private userId: string
|
||||
) {}
|
||||
|
||||
// Transform Supabase snake_case to frontend camelCase
|
||||
private transformCatchRecord(record: any): CatchRecord {
|
||||
// Transform Supabase data to frontend format (minimal transformation)
|
||||
private transformCatchRecord(record: CatchRecordDB): CatchRecord {
|
||||
return {
|
||||
_id: record.id,
|
||||
userId: record.user_id,
|
||||
pokedexEntryId: record.pokedex_entry_id.toString(),
|
||||
haveToEvolve: record.have_to_evolve,
|
||||
userId: record.userId,
|
||||
pokedexEntryId: record.pokedexEntryId.toString(),
|
||||
haveToEvolve: record.haveToEvolve,
|
||||
caught: record.caught,
|
||||
inHome: record.in_home,
|
||||
hasGigantamaxed: record.has_gigantamaxed,
|
||||
personalNotes: record.personal_notes
|
||||
inHome: record.inHome,
|
||||
hasGigantamaxed: record.hasGigantamaxed,
|
||||
personalNotes: record.personalNotes
|
||||
};
|
||||
}
|
||||
|
||||
// Transform frontend camelCase to Supabase snake_case
|
||||
private transformToDatabase(data: Partial<CatchRecord>): any {
|
||||
const dbData: any = {};
|
||||
// Transform frontend data to database format (minimal transformation)
|
||||
private transformToDatabase(data: Partial<CatchRecord>): Partial<CatchRecordDB> {
|
||||
const dbData: Partial<CatchRecordDB> = {};
|
||||
if (data.pokedexEntryId !== undefined && data.pokedexEntryId !== null) {
|
||||
dbData.pokedex_entry_id = Number(data.pokedexEntryId);
|
||||
const numericId = Number(data.pokedexEntryId);
|
||||
if (isNaN(numericId)) {
|
||||
throw new Error(`Invalid pokedexEntryId: ${data.pokedexEntryId}`);
|
||||
}
|
||||
dbData.pokedexEntryId = numericId;
|
||||
}
|
||||
if (data.haveToEvolve !== undefined) dbData.have_to_evolve = data.haveToEvolve;
|
||||
if (data.haveToEvolve !== undefined) dbData.haveToEvolve = data.haveToEvolve;
|
||||
if (data.caught !== undefined) dbData.caught = data.caught;
|
||||
if (data.inHome !== undefined) dbData.in_home = data.inHome;
|
||||
if (data.hasGigantamaxed !== undefined) dbData.has_gigantamaxed = data.hasGigantamaxed;
|
||||
if (data.personalNotes !== undefined) dbData.personal_notes = data.personalNotes;
|
||||
|
||||
if (data.inHome !== undefined) dbData.inHome = data.inHome;
|
||||
if (data.hasGigantamaxed !== undefined) dbData.hasGigantamaxed = data.hasGigantamaxed;
|
||||
if (data.personalNotes !== undefined) dbData.personalNotes = data.personalNotes;
|
||||
|
||||
return dbData;
|
||||
}
|
||||
|
||||
@@ -38,7 +45,7 @@ class CatchRecordRepository {
|
||||
.from('catch_records')
|
||||
.select('*')
|
||||
.eq('id', id)
|
||||
.eq('user_id', this.userId)
|
||||
.eq('userId', this.userId)
|
||||
.single();
|
||||
|
||||
if (error || !data) return null;
|
||||
@@ -49,19 +56,25 @@ class CatchRecordRepository {
|
||||
const { data, error } = await this.supabase
|
||||
.from('catch_records')
|
||||
.select('*')
|
||||
.eq('user_id', this.userId);
|
||||
.eq('userId', this.userId);
|
||||
|
||||
if (error || !data) return [];
|
||||
return data.map(record => this.transformCatchRecord(record));
|
||||
return data.map((record) => this.transformCatchRecord(record));
|
||||
}
|
||||
|
||||
async findByUserId(userId: string): Promise<CatchRecord[]> {
|
||||
return this.findAll(); // Already filtered by user in constructor
|
||||
const { data, error } = await this.supabase
|
||||
.from('catch_records')
|
||||
.select('*')
|
||||
.eq('userId', userId);
|
||||
|
||||
if (error || !data) return [];
|
||||
return data.map((record) => this.transformCatchRecord(record));
|
||||
}
|
||||
|
||||
async create(data: Partial<CatchRecord>): Promise<CatchRecord> {
|
||||
const dbData = {
|
||||
user_id: this.userId,
|
||||
userId: this.userId,
|
||||
...this.transformToDatabase(data)
|
||||
};
|
||||
|
||||
@@ -75,11 +88,11 @@ class CatchRecordRepository {
|
||||
console.error('Supabase error creating catch record:', error);
|
||||
throw new Error(`Failed to create catch record: ${error.message}`);
|
||||
}
|
||||
|
||||
|
||||
if (!result) {
|
||||
throw new Error('Failed to create catch record: No result returned');
|
||||
}
|
||||
|
||||
|
||||
return this.transformCatchRecord(result);
|
||||
}
|
||||
|
||||
@@ -90,7 +103,7 @@ class CatchRecordRepository {
|
||||
.from('catch_records')
|
||||
.update(dbData)
|
||||
.eq('id', id)
|
||||
.eq('user_id', this.userId)
|
||||
.eq('userId', this.userId)
|
||||
.select()
|
||||
.single();
|
||||
|
||||
@@ -99,11 +112,16 @@ class CatchRecordRepository {
|
||||
}
|
||||
|
||||
async delete(id: string): Promise<void> {
|
||||
await this.supabase
|
||||
const { error } = await this.supabase
|
||||
.from('catch_records')
|
||||
.delete()
|
||||
.eq('id', id)
|
||||
.eq('user_id', this.userId);
|
||||
.eq('userId', this.userId);
|
||||
|
||||
if (error) {
|
||||
console.error('Error deleting catch record:', error);
|
||||
throw new Error(`Failed to delete catch record: ${error.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
async upsert(data: Partial<CatchRecord>): Promise<CatchRecord | null> {
|
||||
@@ -125,8 +143,8 @@ class CatchRecordRepository {
|
||||
const { data, error } = await this.supabase
|
||||
.from('catch_records')
|
||||
.select('*')
|
||||
.eq('user_id', userId)
|
||||
.eq('pokedex_entry_id', pokedexEntryId)
|
||||
.eq('userId', userId)
|
||||
.eq('pokedexEntryId', pokedexEntryId)
|
||||
.single();
|
||||
|
||||
if (error || !data) return null;
|
||||
|
||||
@@ -1,47 +1,50 @@
|
||||
import { type PokedexEntry } from '$lib/models/PokedexEntry';
|
||||
import { type CatchRecord } from '$lib/models/CatchRecord';
|
||||
import { type PokedexEntry, type PokedexEntryDB } from '$lib/models/PokedexEntry';
|
||||
import { type CatchRecord, type CatchRecordDB } from '$lib/models/CatchRecord';
|
||||
import { type CombinedData } from '$lib/models/CombinedData';
|
||||
import type { SupabaseClient } from '@supabase/supabase-js';
|
||||
|
||||
class CombinedDataRepository {
|
||||
constructor(private supabase: SupabaseClient, private userId: string) {}
|
||||
constructor(
|
||||
private supabase: SupabaseClient,
|
||||
private userId: string
|
||||
) {}
|
||||
|
||||
// Transform Supabase data to match frontend expectations
|
||||
private transformPokedexEntry(entry: any): PokedexEntry {
|
||||
// Transform Supabase data to match frontend expectations (minimal transformation)
|
||||
private transformPokedexEntry(entry: PokedexEntryDB): PokedexEntry {
|
||||
return {
|
||||
_id: entry.id.toString(),
|
||||
pokedexNumber: entry.pokedex_number,
|
||||
pokedexNumber: entry.pokedexNumber,
|
||||
pokemon: entry.pokemon,
|
||||
form: entry.form,
|
||||
canGigantamax: entry.can_gigantamax,
|
||||
regionToCatchIn: entry.region_to_catch_in,
|
||||
gamesToCatchIn: entry.games_to_catch_in || [],
|
||||
regionToEvolveIn: entry.region_to_evolve_in,
|
||||
evolutionInformation: entry.evolution_information,
|
||||
catchInformation: entry.catch_information || [],
|
||||
form: entry.form || '',
|
||||
canGigantamax: entry.canGigantamax,
|
||||
regionToCatchIn: entry.regionToCatchIn || '',
|
||||
gamesToCatchIn: entry.gamesToCatchIn || [],
|
||||
regionToEvolveIn: entry.regionToEvolveIn || '',
|
||||
evolutionInformation: entry.evolutionInformation || '',
|
||||
catchInformation: entry.catchInformation || [],
|
||||
boxPlacementForms: {
|
||||
box: entry.box_placement_forms_box,
|
||||
row: entry.box_placement_forms_row,
|
||||
column: entry.box_placement_forms_column
|
||||
box: entry.boxPlacementFormsBox || 0,
|
||||
row: entry.boxPlacementFormsRow || 0,
|
||||
column: entry.boxPlacementFormsColumn || 0
|
||||
},
|
||||
boxPlacement: {
|
||||
box: entry.box_placement_box,
|
||||
row: entry.box_placement_row,
|
||||
column: entry.box_placement_column
|
||||
box: entry.boxPlacementBox || 0,
|
||||
row: entry.boxPlacementRow || 0,
|
||||
column: entry.boxPlacementColumn || 0
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private transformCatchRecord(record: any): CatchRecord {
|
||||
private transformCatchRecord(record: CatchRecordDB): CatchRecord {
|
||||
return {
|
||||
_id: record.id,
|
||||
userId: record.user_id,
|
||||
pokedexEntryId: record.pokedex_entry_id.toString(),
|
||||
haveToEvolve: record.have_to_evolve,
|
||||
userId: record.userId,
|
||||
pokedexEntryId: record.pokedexEntryId.toString(),
|
||||
haveToEvolve: record.haveToEvolve,
|
||||
caught: record.caught,
|
||||
inHome: record.in_home,
|
||||
hasGigantamaxed: record.has_gigantamaxed,
|
||||
personalNotes: record.personal_notes
|
||||
inHome: record.inHome,
|
||||
hasGigantamaxed: record.hasGigantamaxed,
|
||||
personalNotes: record.personalNotes
|
||||
};
|
||||
}
|
||||
|
||||
@@ -51,35 +54,35 @@ class CombinedDataRepository {
|
||||
region: string = '',
|
||||
game: string = ''
|
||||
): Promise<CombinedData[]> {
|
||||
let query = this.supabase
|
||||
.from('pokedex_entries')
|
||||
.select(`
|
||||
let query = this.supabase.from('pokedex_entries').select(`
|
||||
*,
|
||||
catch_records(*)
|
||||
`);
|
||||
|
||||
// Apply filters
|
||||
if (!enableForms) {
|
||||
query = query.not('box_placement_box', 'is', null);
|
||||
query = query.not('boxPlacementBox', 'is', null);
|
||||
}
|
||||
|
||||
if (region) {
|
||||
query = query.eq('region_to_catch_in', region);
|
||||
query = query.eq('regionToCatchIn', region);
|
||||
}
|
||||
|
||||
if (game) {
|
||||
query = query.contains('games_to_catch_in', [game]);
|
||||
query = query.contains('gamesToCatchIn', [game]);
|
||||
}
|
||||
|
||||
// Order by box placement
|
||||
if (enableForms) {
|
||||
query = query.order('box_placement_forms_box', { ascending: true })
|
||||
.order('box_placement_forms_row', { ascending: true })
|
||||
.order('box_placement_forms_column', { ascending: true });
|
||||
query = query
|
||||
.order('boxPlacementFormsBox', { ascending: true })
|
||||
.order('boxPlacementFormsRow', { ascending: true })
|
||||
.order('boxPlacementFormsColumn', { ascending: true });
|
||||
} else {
|
||||
query = query.order('box_placement_box', { ascending: true })
|
||||
.order('box_placement_row', { ascending: true })
|
||||
.order('box_placement_column', { ascending: true });
|
||||
query = query
|
||||
.order('boxPlacementBox', { ascending: true })
|
||||
.order('boxPlacementRow', { ascending: true })
|
||||
.order('boxPlacementColumn', { ascending: true });
|
||||
}
|
||||
|
||||
const { data, error } = await query;
|
||||
@@ -90,13 +93,13 @@ class CombinedDataRepository {
|
||||
}
|
||||
|
||||
// Transform the data and filter catch records by user
|
||||
return (data || []).map(entry => {
|
||||
const userCatchRecord = Array.isArray(entry.catch_records)
|
||||
? entry.catch_records.find((record: any) => record.user_id === userId)
|
||||
return (data || []).map((entry) => {
|
||||
const userCatchRecord = Array.isArray(entry.catch_records)
|
||||
? entry.catch_records.find((record: CatchRecordDB) => record.userId === userId)
|
||||
: null;
|
||||
|
||||
const transformedEntry = this.transformPokedexEntry(entry);
|
||||
const transformedCatchRecord = userCatchRecord
|
||||
const transformedCatchRecord = userCatchRecord
|
||||
? this.transformCatchRecord(userCatchRecord)
|
||||
: null;
|
||||
|
||||
@@ -120,34 +123,38 @@ class CombinedDataRepository {
|
||||
|
||||
let query = this.supabase
|
||||
.from('pokedex_entries')
|
||||
.select(`
|
||||
.select(
|
||||
`
|
||||
*,
|
||||
catch_records(*)
|
||||
`)
|
||||
`
|
||||
)
|
||||
.range(from, to);
|
||||
|
||||
// Apply filters
|
||||
if (!enableForms) {
|
||||
query = query.not('box_placement_box', 'is', null);
|
||||
query = query.not('boxPlacementBox', 'is', null);
|
||||
}
|
||||
|
||||
if (region) {
|
||||
query = query.eq('region_to_catch_in', region);
|
||||
query = query.eq('regionToCatchIn', region);
|
||||
}
|
||||
|
||||
if (game) {
|
||||
query = query.contains('games_to_catch_in', [game]);
|
||||
query = query.contains('gamesToCatchIn', [game]);
|
||||
}
|
||||
|
||||
// Order by box placement
|
||||
if (enableForms) {
|
||||
query = query.order('box_placement_forms_box', { ascending: true })
|
||||
.order('box_placement_forms_row', { ascending: true })
|
||||
.order('box_placement_forms_column', { ascending: true });
|
||||
query = query
|
||||
.order('boxPlacementFormsBox', { ascending: true })
|
||||
.order('boxPlacementFormsRow', { ascending: true })
|
||||
.order('boxPlacementFormsColumn', { ascending: true });
|
||||
} else {
|
||||
query = query.order('box_placement_box', { ascending: true })
|
||||
.order('box_placement_row', { ascending: true })
|
||||
.order('box_placement_column', { ascending: true });
|
||||
query = query
|
||||
.order('boxPlacementBox', { ascending: true })
|
||||
.order('boxPlacementRow', { ascending: true })
|
||||
.order('boxPlacementColumn', { ascending: true });
|
||||
}
|
||||
|
||||
const { data, error } = await query;
|
||||
@@ -158,13 +165,13 @@ class CombinedDataRepository {
|
||||
}
|
||||
|
||||
// Transform the data and filter catch records by user
|
||||
return (data || []).map(entry => {
|
||||
const userCatchRecord = Array.isArray(entry.catch_records)
|
||||
? entry.catch_records.find((record: any) => record.user_id === userId)
|
||||
return (data || []).map((entry) => {
|
||||
const userCatchRecord = Array.isArray(entry.catch_records)
|
||||
? entry.catch_records.find((record: CatchRecordDB) => record.userId === userId)
|
||||
: null;
|
||||
|
||||
const transformedEntry = this.transformPokedexEntry(entry);
|
||||
const transformedCatchRecord = userCatchRecord
|
||||
const transformedCatchRecord = userCatchRecord
|
||||
? this.transformCatchRecord(userCatchRecord)
|
||||
: null;
|
||||
|
||||
@@ -181,21 +188,19 @@ class CombinedDataRepository {
|
||||
region: string,
|
||||
game: string
|
||||
): Promise<number> {
|
||||
let query = this.supabase
|
||||
.from('pokedex_entries')
|
||||
.select('id', { count: 'exact', head: true });
|
||||
let query = this.supabase.from('pokedex_entries').select('id', { count: 'exact', head: true });
|
||||
|
||||
// Apply same filters as in findCombinedData
|
||||
if (!enableForms) {
|
||||
query = query.not('box_placement_box', 'is', null);
|
||||
query = query.not('boxPlacementBox', 'is', null);
|
||||
}
|
||||
|
||||
if (region) {
|
||||
query = query.eq('region_to_catch_in', region);
|
||||
query = query.eq('regionToCatchIn', region);
|
||||
}
|
||||
|
||||
if (game) {
|
||||
query = query.contains('games_to_catch_in', [game]);
|
||||
query = query.contains('gamesToCatchIn', [game]);
|
||||
}
|
||||
|
||||
const { count, error } = await query;
|
||||
@@ -209,4 +214,4 @@ class CombinedDataRepository {
|
||||
}
|
||||
}
|
||||
|
||||
export default CombinedDataRepository;
|
||||
export default CombinedDataRepository;
|
||||
|
||||
@@ -1,31 +1,31 @@
|
||||
import { type PokedexEntry } from '$lib/models/PokedexEntry';
|
||||
import { type PokedexEntry, type PokedexEntryDB } from '$lib/models/PokedexEntry';
|
||||
import type { SupabaseClient } from '@supabase/supabase-js';
|
||||
|
||||
class PokedexEntryRepository {
|
||||
constructor(private supabase: SupabaseClient) {}
|
||||
|
||||
// Transform Supabase data to match frontend expectations
|
||||
private transformPokedexEntry(entry: any): PokedexEntry {
|
||||
// Transform Supabase data to match frontend expectations (minimal transformation)
|
||||
private transformPokedexEntry(entry: PokedexEntryDB): PokedexEntry {
|
||||
return {
|
||||
_id: entry.id.toString(),
|
||||
pokedexNumber: entry.pokedex_number,
|
||||
pokedexNumber: entry.pokedexNumber,
|
||||
pokemon: entry.pokemon,
|
||||
form: entry.form,
|
||||
canGigantamax: entry.can_gigantamax,
|
||||
regionToCatchIn: entry.region_to_catch_in,
|
||||
gamesToCatchIn: entry.games_to_catch_in || [],
|
||||
regionToEvolveIn: entry.region_to_evolve_in,
|
||||
evolutionInformation: entry.evolution_information,
|
||||
catchInformation: entry.catch_information || [],
|
||||
form: entry.form || '',
|
||||
canGigantamax: entry.canGigantamax,
|
||||
regionToCatchIn: entry.regionToCatchIn || '',
|
||||
gamesToCatchIn: entry.gamesToCatchIn || [],
|
||||
regionToEvolveIn: entry.regionToEvolveIn || '',
|
||||
evolutionInformation: entry.evolutionInformation || '',
|
||||
catchInformation: entry.catchInformation || [],
|
||||
boxPlacementForms: {
|
||||
box: entry.box_placement_forms_box,
|
||||
row: entry.box_placement_forms_row,
|
||||
column: entry.box_placement_forms_column
|
||||
box: entry.boxPlacementFormsBox || 0,
|
||||
row: entry.boxPlacementFormsRow || 0,
|
||||
column: entry.boxPlacementFormsColumn || 0
|
||||
},
|
||||
boxPlacement: {
|
||||
box: entry.box_placement_box,
|
||||
row: entry.box_placement_row,
|
||||
column: entry.box_placement_column
|
||||
box: entry.boxPlacementBox || 0,
|
||||
row: entry.boxPlacementRow || 0,
|
||||
column: entry.boxPlacementColumn || 0
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -45,30 +45,35 @@ class PokedexEntryRepository {
|
||||
const { data, error } = await this.supabase
|
||||
.from('pokedex_entries')
|
||||
.select('*')
|
||||
.order('pokedex_number', { ascending: true });
|
||||
.order('pokedexNumber', { ascending: true });
|
||||
|
||||
if (error || !data) return [];
|
||||
return data.map(entry => this.transformPokedexEntry(entry));
|
||||
return data.map((entry) => this.transformPokedexEntry(entry));
|
||||
}
|
||||
|
||||
async create(data: Partial<PokedexEntry>): Promise<PokedexEntry> {
|
||||
// Transform camelCase to snake_case for database
|
||||
const dbData: any = {};
|
||||
if (data.pokedexNumber !== undefined) dbData.pokedex_number = data.pokedexNumber;
|
||||
// Transform to database format (minimal transformation needed)
|
||||
const dbData: Partial<PokedexEntryDB> = {};
|
||||
if (data.pokedexNumber !== undefined) dbData.pokedexNumber = data.pokedexNumber;
|
||||
if (data.pokemon !== undefined) dbData.pokemon = data.pokemon;
|
||||
if (data.form !== undefined) dbData.form = data.form;
|
||||
if (data.canGigantamax !== undefined) dbData.can_gigantamax = data.canGigantamax;
|
||||
if (data.regionToCatchIn !== undefined) dbData.region_to_catch_in = data.regionToCatchIn;
|
||||
if (data.gamesToCatchIn !== undefined) dbData.games_to_catch_in = data.gamesToCatchIn;
|
||||
if (data.regionToEvolveIn !== undefined) dbData.region_to_evolve_in = data.regionToEvolveIn;
|
||||
if (data.evolutionInformation !== undefined) dbData.evolution_information = data.evolutionInformation;
|
||||
if (data.catchInformation !== undefined) dbData.catch_information = data.catchInformation;
|
||||
if (data.boxPlacementForms?.box !== undefined) dbData.box_placement_forms_box = data.boxPlacementForms.box;
|
||||
if (data.boxPlacementForms?.row !== undefined) dbData.box_placement_forms_row = data.boxPlacementForms.row;
|
||||
if (data.boxPlacementForms?.column !== undefined) dbData.box_placement_forms_column = data.boxPlacementForms.column;
|
||||
if (data.boxPlacement?.box !== undefined) dbData.box_placement_box = data.boxPlacement.box;
|
||||
if (data.boxPlacement?.row !== undefined) dbData.box_placement_row = data.boxPlacement.row;
|
||||
if (data.boxPlacement?.column !== undefined) dbData.box_placement_column = data.boxPlacement.column;
|
||||
if (data.canGigantamax !== undefined) dbData.canGigantamax = data.canGigantamax;
|
||||
if (data.regionToCatchIn !== undefined) dbData.regionToCatchIn = data.regionToCatchIn;
|
||||
if (data.gamesToCatchIn !== undefined) dbData.gamesToCatchIn = data.gamesToCatchIn;
|
||||
if (data.regionToEvolveIn !== undefined) dbData.regionToEvolveIn = data.regionToEvolveIn;
|
||||
if (data.evolutionInformation !== undefined)
|
||||
dbData.evolutionInformation = data.evolutionInformation;
|
||||
if (data.catchInformation !== undefined) dbData.catchInformation = data.catchInformation;
|
||||
if (data.boxPlacementForms?.box !== undefined)
|
||||
dbData.boxPlacementFormsBox = data.boxPlacementForms.box;
|
||||
if (data.boxPlacementForms?.row !== undefined)
|
||||
dbData.boxPlacementFormsRow = data.boxPlacementForms.row;
|
||||
if (data.boxPlacementForms?.column !== undefined)
|
||||
dbData.boxPlacementFormsColumn = data.boxPlacementForms.column;
|
||||
if (data.boxPlacement?.box !== undefined) dbData.boxPlacementBox = data.boxPlacement.box;
|
||||
if (data.boxPlacement?.row !== undefined) dbData.boxPlacementRow = data.boxPlacement.row;
|
||||
if (data.boxPlacement?.column !== undefined)
|
||||
dbData.boxPlacementColumn = data.boxPlacement.column;
|
||||
|
||||
const { data: result, error } = await this.supabase
|
||||
.from('pokedex_entries')
|
||||
@@ -81,9 +86,9 @@ class PokedexEntryRepository {
|
||||
}
|
||||
|
||||
async update(id: string, data: Partial<PokedexEntry>): Promise<PokedexEntry | null> {
|
||||
// Transform camelCase to snake_case for database
|
||||
const dbData: any = {};
|
||||
if (data.pokedexNumber !== undefined) dbData.pokedex_number = data.pokedexNumber;
|
||||
// Transform to database format (minimal transformation needed)
|
||||
const dbData: Partial<PokedexEntryDB> = {};
|
||||
if (data.pokedexNumber !== undefined) dbData.pokedexNumber = data.pokedexNumber;
|
||||
if (data.pokemon !== undefined) dbData.pokemon = data.pokemon;
|
||||
// ... add other fields as needed
|
||||
|
||||
@@ -99,10 +104,7 @@ class PokedexEntryRepository {
|
||||
}
|
||||
|
||||
async delete(id: string): Promise<void> {
|
||||
await this.supabase
|
||||
.from('pokedex_entries')
|
||||
.delete()
|
||||
.eq('id', id);
|
||||
await this.supabase.from('pokedex_entries').delete().eq('id', id);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -7,15 +7,24 @@ import type { RequestEvent } from '@sveltejs/kit';
|
||||
export const GET = async (event: RequestEvent) => {
|
||||
try {
|
||||
const userId = await requireAuth(event);
|
||||
|
||||
// Get the user's session and set it on the Supabase client
|
||||
const { session } = await event.locals.safeGetSession();
|
||||
if (session) {
|
||||
await event.locals.supabase.auth.setSession(session);
|
||||
}
|
||||
|
||||
const repo = new CatchRecordRepository(event.locals.supabase, userId);
|
||||
const catchData = await repo.findByUserId(userId);
|
||||
// order by pokedexEntryId property, ascending
|
||||
const sortedData = catchData.sort((a, b) => Number(a.pokedexEntryId) - Number(b.pokedexEntryId));
|
||||
const sortedData = catchData.sort(
|
||||
(a, b) => Number(a.pokedexEntryId) - Number(b.pokedexEntryId)
|
||||
);
|
||||
return json(sortedData);
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
if (error.status) {
|
||||
throw error;
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
if (err && typeof err === 'object' && 'status' in err) {
|
||||
throw err;
|
||||
}
|
||||
return json({ error: 'Internal Server Error' }, { status: 500 });
|
||||
}
|
||||
@@ -23,18 +32,32 @@ export const GET = async (event: RequestEvent) => {
|
||||
|
||||
export const PUT = async (event: RequestEvent) => {
|
||||
try {
|
||||
console.log('PUT request received for catch-records');
|
||||
|
||||
// Check if we can get a session first
|
||||
const { session, user } = await event.locals.safeGetSession();
|
||||
console.log('Session check:', { hasSession: !!session, hasUser: !!user, userId: user?.id });
|
||||
|
||||
const userId = await requireAuth(event);
|
||||
console.log('Auth successful, userId:', userId);
|
||||
|
||||
const data: Partial<CatchRecord> = await event.request.json();
|
||||
|
||||
|
||||
// Get the user's session and set it on the Supabase client
|
||||
if (session) {
|
||||
await event.locals.supabase.auth.setSession(session);
|
||||
console.log('Session set on Supabase client');
|
||||
}
|
||||
|
||||
// Ensure the userId is set to the authenticated user
|
||||
data.userId = userId;
|
||||
|
||||
|
||||
const repo = new CatchRecordRepository(event.locals.supabase, userId);
|
||||
const upsertedRecord = await repo.upsert(data);
|
||||
return json(upsertedRecord);
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
if (err.status) {
|
||||
if (err && typeof err === 'object' && 'status' in err) {
|
||||
throw err;
|
||||
}
|
||||
return json({ error: 'Internal Server Error' }, { status: 500 });
|
||||
@@ -45,6 +68,13 @@ export const POST = async (event: RequestEvent) => {
|
||||
try {
|
||||
const userId = await requireAuth(event);
|
||||
const records: Partial<CatchRecord>[] = await event.request.json();
|
||||
|
||||
// Get the user's session and set it on the Supabase client
|
||||
const { session } = await event.locals.safeGetSession();
|
||||
if (session) {
|
||||
await event.locals.supabase.auth.setSession(session);
|
||||
}
|
||||
|
||||
const repo = new CatchRecordRepository(event.locals.supabase, userId);
|
||||
|
||||
const insertedRecords = [];
|
||||
@@ -58,7 +88,7 @@ export const POST = async (event: RequestEvent) => {
|
||||
return json(insertedRecords);
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
if (err.status) {
|
||||
if (err && typeof err === 'object' && 'status' in err) {
|
||||
throw err;
|
||||
}
|
||||
return json({ error: 'Internal Server Error' }, { status: 500 });
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { json } from '@sveltejs/kit';
|
||||
import CombinedDataRepository from '$lib/repositories/CombinedDataRepository';
|
||||
import { requireAuth } from '$lib/utils/auth';
|
||||
import { getOptionalUserId } from '$lib/utils/auth';
|
||||
import type { RequestEvent } from '@sveltejs/kit';
|
||||
|
||||
export const GET = async (event: RequestEvent) => {
|
||||
@@ -12,23 +12,40 @@ export const GET = async (event: RequestEvent) => {
|
||||
const game = url.searchParams.get('game') || '';
|
||||
|
||||
try {
|
||||
const userId = await requireAuth(event);
|
||||
const repo = new CombinedDataRepository(event.locals.supabase, userId);
|
||||
const combinedData = await repo.findCombinedData(userId, page, limit, enableForms, region, game);
|
||||
// Get userId if authenticated, null if not
|
||||
const userId = await getOptionalUserId(event);
|
||||
|
||||
// If user is authenticated, set their session on the Supabase client
|
||||
if (userId) {
|
||||
const { session } = await event.locals.safeGetSession();
|
||||
if (session) {
|
||||
await event.locals.supabase.auth.setSession(session);
|
||||
}
|
||||
}
|
||||
|
||||
const repo = new CombinedDataRepository(event.locals.supabase, userId || '');
|
||||
const combinedData = await repo.findCombinedData(
|
||||
userId || '',
|
||||
page,
|
||||
limit,
|
||||
enableForms,
|
||||
region,
|
||||
game
|
||||
);
|
||||
|
||||
// Return empty array instead of 404 for better UX
|
||||
if (combinedData.length === 0) {
|
||||
return json({ combinedData: [], totalPages: 0 });
|
||||
}
|
||||
|
||||
const totalCount = await repo.countCombinedData(userId, enableForms, region, game);
|
||||
|
||||
const totalCount = await repo.countCombinedData(userId || '', enableForms, region, game);
|
||||
const totalPages = Math.ceil(totalCount / limit);
|
||||
|
||||
return json({ combinedData, totalPages });
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
if (error.status) {
|
||||
throw error;
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
if (err && typeof err === 'object' && 'status' in err) {
|
||||
throw err;
|
||||
}
|
||||
return json({ error: 'Internal Server Error' }, { status: 500 });
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { json } from '@sveltejs/kit';
|
||||
import CombinedDataRepository from '$lib/repositories/CombinedDataRepository';
|
||||
import { requireAuth } from '$lib/utils/auth';
|
||||
import { getOptionalUserId } from '$lib/utils/auth';
|
||||
import type { RequestEvent } from '@sveltejs/kit';
|
||||
|
||||
export const GET = async (event: RequestEvent) => {
|
||||
@@ -10,16 +10,26 @@ export const GET = async (event: RequestEvent) => {
|
||||
const game = url.searchParams.get('game') || '';
|
||||
|
||||
try {
|
||||
const userId = await requireAuth(event);
|
||||
const repo = new CombinedDataRepository(event.locals.supabase, userId);
|
||||
const combinedData = await repo.findAllCombinedData(userId, enableForms, region, game);
|
||||
// Get userId if authenticated, null if not
|
||||
const userId = await getOptionalUserId(event);
|
||||
|
||||
// If user is authenticated, set their session on the Supabase client
|
||||
if (userId) {
|
||||
const { session } = await event.locals.safeGetSession();
|
||||
if (session) {
|
||||
await event.locals.supabase.auth.setSession(session);
|
||||
}
|
||||
}
|
||||
|
||||
const repo = new CombinedDataRepository(event.locals.supabase, userId || '');
|
||||
const combinedData = await repo.findAllCombinedData(userId || '', enableForms, region, game);
|
||||
|
||||
// Return empty array instead of 404 for better UX
|
||||
return json(combinedData);
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
if (error.status) {
|
||||
throw error;
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
if (err && typeof err === 'object' && 'status' in err) {
|
||||
throw err;
|
||||
}
|
||||
return json({ error: 'Internal Server Error' }, { status: 500 });
|
||||
}
|
||||
|
||||
@@ -8,6 +8,11 @@
|
||||
import PokedexSidebar from '$lib/components/pokedex/PokedexSidebar.svelte';
|
||||
import PokedexViewList from '$lib/components/pokedex/PokedexViewList.svelte';
|
||||
import PokedexViewBoxes from '$lib/components/pokedex/PokedexViewBoxes.svelte';
|
||||
import type { PageData } from './$types';
|
||||
|
||||
export let data: PageData;
|
||||
|
||||
$: ({ supabase, session } = data);
|
||||
|
||||
let combinedData = null as CombinedData[] | null;
|
||||
let currentPage = 1 as number;
|
||||
@@ -86,13 +91,19 @@
|
||||
|
||||
const requestOptions = {
|
||||
method: 'PUT',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(catchRecord)
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify(catchRecord),
|
||||
credentials: 'include' as RequestCredentials
|
||||
};
|
||||
|
||||
try {
|
||||
// Use enhanced fetch that includes authentication context
|
||||
const response = await fetch('/api/catch-records', requestOptions);
|
||||
if (!response.ok) {
|
||||
const errorText = await response.text();
|
||||
console.error('Server response:', response.status, errorText);
|
||||
alert('Failed to update catch record');
|
||||
throw new Error('Failed to update catch record');
|
||||
}
|
||||
@@ -121,7 +132,7 @@
|
||||
hasGigantamaxed: false,
|
||||
personalNotes: ''
|
||||
};
|
||||
|
||||
|
||||
let updatedRecord = { ...baseRecord };
|
||||
if (inHome !== null) {
|
||||
updatedRecord = {
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
export const load = async ({ parent }) => {
|
||||
const { supabase, session } = await parent();
|
||||
return {
|
||||
supabase,
|
||||
session
|
||||
};
|
||||
};
|
||||
@@ -168,7 +168,7 @@ enable_signup = true
|
||||
# addresses. If disabled, only the new email is required to confirm.
|
||||
double_confirm_changes = true
|
||||
# If enabled, users need to confirm their email address before signing in.
|
||||
enable_confirmations = false
|
||||
enable_confirmations = true
|
||||
# If enabled, users will need to reauthenticate or have logged in recently to change their password.
|
||||
secure_password_change = false
|
||||
# Controls the minimum amount of time that must pass before sending another signup confirmation or password reset email.
|
||||
|
||||
@@ -1,34 +0,0 @@
|
||||
-- Initial setup migration for LivingDexTracker
|
||||
-- This sets up any additional schema needed
|
||||
|
||||
-- Note: RLS is already enabled on auth.users by default in Supabase
|
||||
-- and proper policies are already in place
|
||||
|
||||
-- Create a profiles table if we need additional user data
|
||||
-- This is optional for the current setup since we only use Supabase for auth
|
||||
-- and store everything else in MongoDB
|
||||
|
||||
-- CREATE TABLE profiles (
|
||||
-- id UUID PRIMARY KEY REFERENCES auth.users(id) ON DELETE CASCADE,
|
||||
-- username TEXT,
|
||||
-- avatar_url TEXT,
|
||||
-- created_at TIMESTAMPTZ DEFAULT NOW(),
|
||||
-- updated_at TIMESTAMPTZ DEFAULT NOW()
|
||||
-- );
|
||||
|
||||
-- Enable RLS on profiles table
|
||||
-- ALTER TABLE profiles ENABLE ROW LEVEL SECURITY;
|
||||
|
||||
-- Create RLS policies for profiles
|
||||
-- CREATE POLICY "Users can view own profile"
|
||||
-- ON profiles
|
||||
-- FOR SELECT
|
||||
-- USING (auth.uid() = id);
|
||||
|
||||
-- CREATE POLICY "Users can update own profile"
|
||||
-- ON profiles
|
||||
-- FOR UPDATE
|
||||
-- USING (auth.uid() = id);
|
||||
|
||||
-- Note: MongoDB collections (pokedexentries, catchrecords) are handled by the app
|
||||
-- Supabase is only used for authentication in this hybrid setup
|
||||
@@ -6,102 +6,128 @@
|
||||
-- Pokemon entries table (replaces pokedexentries MongoDB collection)
|
||||
CREATE TABLE pokedex_entries (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
pokedex_number INTEGER NOT NULL,
|
||||
"pokedexNumber" INTEGER NOT NULL,
|
||||
pokemon TEXT NOT NULL,
|
||||
form TEXT,
|
||||
can_gigantamax BOOLEAN DEFAULT FALSE,
|
||||
region_to_catch_in TEXT,
|
||||
games_to_catch_in TEXT[], -- Array of games
|
||||
region_to_evolve_in TEXT,
|
||||
evolution_information TEXT,
|
||||
catch_information TEXT[], -- Array of catch info
|
||||
"canGigantamax" BOOLEAN DEFAULT FALSE,
|
||||
"regionToCatchIn" TEXT,
|
||||
"gamesToCatchIn" TEXT[], -- Array of games
|
||||
"regionToEvolveIn" TEXT,
|
||||
"evolutionInformation" TEXT,
|
||||
"catchInformation" TEXT[], -- Array of catch info
|
||||
|
||||
-- Box placement for forms view
|
||||
box_placement_forms_box INTEGER,
|
||||
box_placement_forms_row INTEGER,
|
||||
box_placement_forms_column INTEGER,
|
||||
"boxPlacementFormsBox" INTEGER,
|
||||
"boxPlacementFormsRow" INTEGER,
|
||||
"boxPlacementFormsColumn" INTEGER,
|
||||
|
||||
-- Box placement for no-forms view
|
||||
box_placement_box INTEGER,
|
||||
box_placement_row INTEGER,
|
||||
box_placement_column INTEGER,
|
||||
"boxPlacementBox" INTEGER,
|
||||
"boxPlacementRow" INTEGER,
|
||||
"boxPlacementColumn" INTEGER,
|
||||
|
||||
created_at TIMESTAMPTZ DEFAULT NOW(),
|
||||
updated_at TIMESTAMPTZ DEFAULT NOW()
|
||||
"createdAt" TIMESTAMPTZ DEFAULT NOW(),
|
||||
"updatedAt" TIMESTAMPTZ DEFAULT NOW()
|
||||
);
|
||||
|
||||
-- Add unique constraint to prevent duplicate pokemon forms
|
||||
ALTER TABLE pokedex_entries ADD CONSTRAINT unique_pokemon_form
|
||||
UNIQUE("pokedexNumber", form);
|
||||
|
||||
-- Catch records table (replaces catchrecords MongoDB collection)
|
||||
CREATE TABLE catch_records (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
user_id UUID NOT NULL REFERENCES auth.users(id) ON DELETE CASCADE,
|
||||
pokedex_entry_id BIGINT NOT NULL REFERENCES pokedex_entries(id) ON DELETE CASCADE,
|
||||
"userId" UUID NOT NULL REFERENCES auth.users(id) ON DELETE CASCADE,
|
||||
"pokedexEntryId" BIGINT NOT NULL REFERENCES pokedex_entries(id) ON DELETE CASCADE,
|
||||
|
||||
-- Catch status fields
|
||||
have_to_evolve BOOLEAN DEFAULT FALSE,
|
||||
"haveToEvolve" BOOLEAN DEFAULT FALSE,
|
||||
caught BOOLEAN DEFAULT FALSE,
|
||||
in_home BOOLEAN DEFAULT FALSE,
|
||||
has_gigantamaxed BOOLEAN DEFAULT FALSE,
|
||||
personal_notes TEXT,
|
||||
"inHome" BOOLEAN DEFAULT FALSE,
|
||||
"hasGigantamaxed" BOOLEAN DEFAULT FALSE,
|
||||
"personalNotes" TEXT,
|
||||
|
||||
created_at TIMESTAMPTZ DEFAULT NOW(),
|
||||
updated_at TIMESTAMPTZ DEFAULT NOW(),
|
||||
"createdAt" TIMESTAMPTZ DEFAULT NOW(),
|
||||
"updatedAt" TIMESTAMPTZ DEFAULT NOW(),
|
||||
|
||||
-- Ensure one record per user per pokemon entry
|
||||
UNIQUE(user_id, pokedex_entry_id)
|
||||
UNIQUE("userId", "pokedexEntryId")
|
||||
);
|
||||
|
||||
-- Region/Game mappings table (replaces regiongamemappings MongoDB collection)
|
||||
-- Region-game mappings table for filtering
|
||||
CREATE TABLE region_game_mappings (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
region TEXT NOT NULL,
|
||||
games TEXT[] NOT NULL,
|
||||
created_at TIMESTAMPTZ DEFAULT NOW()
|
||||
game TEXT NOT NULL,
|
||||
UNIQUE(region, game)
|
||||
);
|
||||
|
||||
-- Pokedex metadata table (replaces pokedexmetadata MongoDB collection)
|
||||
CREATE TABLE pokedex_metadata (
|
||||
-- Metadata table for migration tracking
|
||||
CREATE TABLE metadata (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
key TEXT UNIQUE NOT NULL,
|
||||
value JSONB NOT NULL,
|
||||
created_at TIMESTAMPTZ DEFAULT NOW(),
|
||||
updated_at TIMESTAMPTZ DEFAULT NOW()
|
||||
value TEXT,
|
||||
"createdAt" TIMESTAMPTZ DEFAULT NOW(),
|
||||
"updatedAt" TIMESTAMPTZ DEFAULT NOW()
|
||||
);
|
||||
|
||||
-- Enable Row Level Security (RLS) for user data isolation
|
||||
ALTER TABLE catch_records ENABLE ROW LEVEL SECURITY;
|
||||
|
||||
-- RLS Policy: Users can only access their own catch records
|
||||
CREATE POLICY "Users can only access their own catch records"
|
||||
ON catch_records FOR ALL
|
||||
USING (auth.uid() = user_id)
|
||||
WITH CHECK (auth.uid() = user_id);
|
||||
|
||||
-- Create indexes for better performance
|
||||
CREATE INDEX idx_pokedex_entries_pokedex_number ON pokedex_entries(pokedex_number);
|
||||
CREATE INDEX idx_pokedex_entries_pokedex_number ON pokedex_entries("pokedexNumber");
|
||||
CREATE INDEX idx_pokedex_entries_pokemon ON pokedex_entries(pokemon);
|
||||
CREATE INDEX idx_pokedex_entries_box_placement_forms ON pokedex_entries(box_placement_forms_box, box_placement_forms_row, box_placement_forms_column);
|
||||
CREATE INDEX idx_pokedex_entries_box_placement ON pokedex_entries(box_placement_box, box_placement_row, box_placement_column);
|
||||
CREATE INDEX idx_pokedex_entries_box_placement_forms ON pokedex_entries("boxPlacementFormsBox", "boxPlacementFormsRow", "boxPlacementFormsColumn");
|
||||
CREATE INDEX idx_pokedex_entries_box_placement ON pokedex_entries("boxPlacementBox", "boxPlacementRow", "boxPlacementColumn");
|
||||
|
||||
CREATE INDEX idx_catch_records_user_id ON catch_records(user_id);
|
||||
CREATE INDEX idx_catch_records_pokedex_entry_id ON catch_records(pokedex_entry_id);
|
||||
CREATE INDEX idx_catch_records_user_id ON catch_records("userId");
|
||||
CREATE INDEX idx_catch_records_pokedex_entry_id ON catch_records("pokedexEntryId");
|
||||
CREATE INDEX idx_catch_records_caught ON catch_records(caught);
|
||||
CREATE INDEX idx_catch_records_in_home ON catch_records(in_home);
|
||||
CREATE INDEX idx_catch_records_in_home ON catch_records("inHome");
|
||||
CREATE INDEX idx_catch_records_user_caught ON catch_records("userId", caught);
|
||||
|
||||
-- Create updated_at trigger function
|
||||
-- Function to automatically update updated_at timestamp
|
||||
CREATE OR REPLACE FUNCTION update_updated_at_column()
|
||||
RETURNS TRIGGER AS $$
|
||||
BEGIN
|
||||
NEW.updated_at = NOW();
|
||||
NEW."updatedAt" = NOW();
|
||||
RETURN NEW;
|
||||
END;
|
||||
$$ language 'plpgsql';
|
||||
$$ LANGUAGE plpgsql;
|
||||
|
||||
-- Add updated_at triggers
|
||||
CREATE TRIGGER update_pokedex_entries_updated_at BEFORE UPDATE ON pokedex_entries
|
||||
FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();
|
||||
-- Create triggers for updated_at
|
||||
CREATE TRIGGER update_pokedex_entries_updated_at
|
||||
BEFORE UPDATE ON pokedex_entries
|
||||
FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();
|
||||
|
||||
CREATE TRIGGER update_catch_records_updated_at BEFORE UPDATE ON catch_records
|
||||
FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();
|
||||
CREATE TRIGGER update_catch_records_updated_at
|
||||
BEFORE UPDATE ON catch_records
|
||||
FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();
|
||||
|
||||
CREATE TRIGGER update_pokedex_metadata_updated_at BEFORE UPDATE ON pokedex_metadata
|
||||
FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();
|
||||
-- Enable RLS on all tables
|
||||
ALTER TABLE pokedex_entries ENABLE ROW LEVEL SECURITY;
|
||||
ALTER TABLE catch_records ENABLE ROW LEVEL SECURITY;
|
||||
ALTER TABLE region_game_mappings ENABLE ROW LEVEL SECURITY;
|
||||
ALTER TABLE metadata ENABLE ROW LEVEL SECURITY;
|
||||
|
||||
-- RLS policies for pokedex_entries (public read)
|
||||
CREATE POLICY "Anyone can view pokemon entries" ON pokedex_entries
|
||||
FOR SELECT USING (true);
|
||||
|
||||
-- RLS policies for catch_records (user-specific)
|
||||
CREATE POLICY "Users can view own catch records" ON catch_records
|
||||
FOR SELECT USING (auth.uid() = "userId");
|
||||
|
||||
CREATE POLICY "Users can insert own catch records" ON catch_records
|
||||
FOR INSERT WITH CHECK (auth.uid() = "userId");
|
||||
|
||||
CREATE POLICY "Users can update own catch records" ON catch_records
|
||||
FOR UPDATE USING (auth.uid() = "userId");
|
||||
|
||||
CREATE POLICY "Users can delete own catch records" ON catch_records
|
||||
FOR DELETE USING (auth.uid() = "userId");
|
||||
|
||||
-- RLS policies for region_game_mappings (public read)
|
||||
CREATE POLICY "Anyone can view region game mappings" ON region_game_mappings
|
||||
FOR SELECT USING (true);
|
||||
|
||||
-- RLS policies for metadata (public read)
|
||||
CREATE POLICY "Anyone can view metadata" ON metadata
|
||||
FOR SELECT USING (true);
|
||||
|
||||
@@ -0,0 +1,130 @@
|
||||
-- Migration: Create PostgreSQL schema for Pokemon data
|
||||
-- This replaces the M-- Create indexes for better p$$ LANGUAGE plpgsql;
|
||||
|
||||
-- Create triggers for updated_at
|
||||
CREATE TRIGGER update_pokedex_entries_updated_at
|
||||
BEFORE UPDATE ON pokedex_entries
|
||||
FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();
|
||||
|
||||
CREATE TRIGGER update_catch_records_updated_at
|
||||
BEFORE UPDATE ON catch_records
|
||||
FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();ance
|
||||
CREATE INDEX idx_pokedex_entries_pokedex_number ON pokedex_entries(pokedex_number);
|
||||
CREATE INDEX idx_pokedex_entries_pokemon ON pokedex_entries(pokemon);
|
||||
CREATE INDEX idx_pokedex_entries_box_placement_forms ON pokedex_entries(box_placement_forms_box, box_placement_forms_row, box_placement_forms_column);
|
||||
CREATE INDEX idx_pokedex_entries_box_placement ON pokedex_entries(box_placement_box, box_placement_row, box_placement_column);
|
||||
|
||||
CREATE INDEX idx_catch_records_user_id ON catch_records("userId");
|
||||
CREATE INDEX idx_catch_records_pokedex_entry_id ON catch_records("pokedexEntryId");
|
||||
CREATE INDEX idx_catch_records_caught ON catch_records(caught);
|
||||
CREATE INDEX idx_catch_records_in_home ON catch_records("inHome");
|
||||
CREATE INDEX idx_catch_records_user_caught ON catch_records("userId", caught);lections with proper PostgreSQL tables
|
||||
|
||||
-- Note: Using individual columns instead of composite types for simplicity
|
||||
|
||||
-- Pokemon entries table (replaces pokedexentries MongoDB collection)
|
||||
CREATE TABLE pokedex_entries (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
pokedexNumber INTEGER NOT NULL,
|
||||
pokemon TEXT NOT NULL,
|
||||
form TEXT,
|
||||
canGigantamax BOOLEAN DEFAULT FALSE,
|
||||
regionToCatchIn TEXT,
|
||||
gamesToCatchIn TEXT[], -- Array of games
|
||||
regionToEvolveIn TEXT,
|
||||
evolutionInformation TEXT,
|
||||
catchInformation TEXT[], -- Array of catch info
|
||||
|
||||
-- Box placement for forms view
|
||||
boxPlacementFormsBox INTEGER,
|
||||
boxPlacementFormsRow INTEGER,
|
||||
boxPlacementFormsColumn INTEGER,
|
||||
|
||||
-- Box placement for no-forms view
|
||||
boxPlacementBox INTEGER,
|
||||
boxPlacementRow INTEGER,
|
||||
boxPlacementColumn INTEGER,
|
||||
|
||||
createdAt TIMESTAMPTZ DEFAULT NOW(),
|
||||
updatedAt TIMESTAMPTZ DEFAULT NOW()
|
||||
);
|
||||
|
||||
-- Catch records table (replaces catchrecords MongoDB collection)
|
||||
CREATE TABLE catch_records (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
"userId" UUID NOT NULL REFERENCES auth.users(id) ON DELETE CASCADE,
|
||||
"pokedexEntryId" BIGINT NOT NULL REFERENCES pokedex_entries(id) ON DELETE CASCADE,
|
||||
|
||||
-- Catch status fields
|
||||
"haveToEvolve" BOOLEAN DEFAULT FALSE,
|
||||
caught BOOLEAN DEFAULT FALSE,
|
||||
"inHome" BOOLEAN DEFAULT FALSE,
|
||||
"hasGigantamaxed" BOOLEAN DEFAULT FALSE,
|
||||
"personalNotes" TEXT,
|
||||
|
||||
"createdAt" TIMESTAMPTZ DEFAULT NOW(),
|
||||
"updatedAt" TIMESTAMPTZ DEFAULT NOW(),
|
||||
|
||||
-- Ensure one record per user per pokemon entry
|
||||
UNIQUE("userId", "pokedexEntryId")
|
||||
);
|
||||
|
||||
-- Region/Game mappings table (replaces regiongamemappings MongoDB collection)
|
||||
CREATE TABLE region_game_mappings (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
region TEXT NOT NULL,
|
||||
games TEXT[] NOT NULL,
|
||||
created_at TIMESTAMPTZ DEFAULT NOW()
|
||||
);
|
||||
|
||||
-- Pokedex metadata table (replaces pokedexmetadata MongoDB collection)
|
||||
CREATE TABLE pokedex_metadata (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
key TEXT UNIQUE NOT NULL,
|
||||
value JSONB NOT NULL,
|
||||
created_at TIMESTAMPTZ DEFAULT NOW(),
|
||||
updated_at TIMESTAMPTZ DEFAULT NOW()
|
||||
);
|
||||
|
||||
-- Add unique constraint to prevent duplicate pokemon forms
|
||||
ALTER TABLE pokedex_entries ADD CONSTRAINT unique_pokemon_form
|
||||
UNIQUE(pokedex_number, form);
|
||||
|
||||
-- Enable Row Level Security (RLS) for user data isolation
|
||||
ALTER TABLE catch_records ENABLE ROW LEVEL SECURITY;
|
||||
|
||||
-- RLS Policy: Users can only access their own catch records
|
||||
CREATE POLICY "Users can only access their own catch records"
|
||||
ON catch_records FOR ALL
|
||||
USING (auth.uid() = userId)
|
||||
WITH CHECK (auth.uid() = userId);
|
||||
|
||||
-- Create indexes for better performance
|
||||
CREATE INDEX idx_pokedex_entries_pokedex_number ON pokedex_entries(pokedexNumber);
|
||||
CREATE INDEX idx_pokedex_entries_pokemon ON pokedex_entries(pokemon);
|
||||
CREATE INDEX idx_pokedex_entries_box_placement_forms ON pokedex_entries(boxPlacementFormsBox, boxPlacementFormsRow, boxPlacementFormsColumn);
|
||||
CREATE INDEX idx_pokedex_entries_box_placement ON pokedex_entries(boxPlacementBox, boxPlacementRow, boxPlacementColumn);
|
||||
|
||||
CREATE INDEX idx_catch_records_user_id ON catch_records(userId);
|
||||
CREATE INDEX idx_catch_records_pokedex_entry_id ON catch_records(pokedexEntryId);
|
||||
CREATE INDEX idx_catch_records_caught ON catch_records(caught);
|
||||
CREATE INDEX idx_catch_records_in_home ON catch_records(inHome);
|
||||
|
||||
-- Create updated_at trigger function
|
||||
CREATE OR REPLACE FUNCTION update_updated_at_column()
|
||||
RETURNS TRIGGER AS $$
|
||||
BEGIN
|
||||
NEW.updated_at = NOW();
|
||||
RETURN NEW;
|
||||
END;
|
||||
$$ language 'plpgsql';
|
||||
|
||||
-- Add updated_at triggers
|
||||
CREATE TRIGGER update_pokedex_entries_updated_at BEFORE UPDATE ON pokedex_entries
|
||||
FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();
|
||||
|
||||
CREATE TRIGGER update_catch_records_updated_at BEFORE UPDATE ON catch_records
|
||||
FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();
|
||||
|
||||
CREATE TRIGGER update_pokedex_metadata_updated_at BEFORE UPDATE ON pokedex_metadata
|
||||
FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();
|
||||
@@ -0,0 +1,133 @@
|
||||
-- Migration: Create PostgreSQL schema for Pokemon data
|
||||
-- This replaces the MongoDB collections with proper PostgreSQL tables
|
||||
|
||||
-- Note: Using individual columns instead of composite types for simplicity
|
||||
|
||||
-- Pokemon entries table (replaces pokedexentries MongoDB collection)
|
||||
CREATE TABLE pokedex_entries (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
"pokedexNumber" INTEGER NOT NULL,
|
||||
pokemon TEXT NOT NULL,
|
||||
form TEXT,
|
||||
"canGigantamax" BOOLEAN DEFAULT FALSE,
|
||||
"regionToCatchIn" TEXT,
|
||||
"gamesToCatchIn" TEXT[], -- Array of games
|
||||
"regionToEvolveIn" TEXT,
|
||||
"evolutionInformation" TEXT,
|
||||
"catchInformation" TEXT[], -- Array of catch info
|
||||
|
||||
-- Box placement for forms view
|
||||
"boxPlacementFormsBox" INTEGER,
|
||||
"boxPlacementFormsRow" INTEGER,
|
||||
"boxPlacementFormsColumn" INTEGER,
|
||||
|
||||
-- Box placement for no-forms view
|
||||
"boxPlacementBox" INTEGER,
|
||||
"boxPlacementRow" INTEGER,
|
||||
"boxPlacementColumn" INTEGER,
|
||||
|
||||
"createdAt" TIMESTAMPTZ DEFAULT NOW(),
|
||||
"updatedAt" TIMESTAMPTZ DEFAULT NOW()
|
||||
);
|
||||
|
||||
-- Add unique constraint to prevent duplicate pokemon forms
|
||||
ALTER TABLE pokedex_entries ADD CONSTRAINT unique_pokemon_form
|
||||
UNIQUE("pokedexNumber", form);
|
||||
|
||||
-- Catch records table (replaces catchrecords MongoDB collection)
|
||||
CREATE TABLE catch_records (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
"userId" UUID NOT NULL REFERENCES auth.users(id) ON DELETE CASCADE,
|
||||
"pokedexEntryId" BIGINT NOT NULL REFERENCES pokedex_entries(id) ON DELETE CASCADE,
|
||||
|
||||
-- Catch status fields
|
||||
"haveToEvolve" BOOLEAN DEFAULT FALSE,
|
||||
caught BOOLEAN DEFAULT FALSE,
|
||||
"inHome" BOOLEAN DEFAULT FALSE,
|
||||
"hasGigantamaxed" BOOLEAN DEFAULT FALSE,
|
||||
"personalNotes" TEXT,
|
||||
|
||||
"createdAt" TIMESTAMPTZ DEFAULT NOW(),
|
||||
"updatedAt" TIMESTAMPTZ DEFAULT NOW(),
|
||||
|
||||
-- Ensure one record per user per pokemon entry
|
||||
UNIQUE("userId", "pokedexEntryId")
|
||||
);
|
||||
|
||||
-- Region-game mappings table for filtering
|
||||
CREATE TABLE region_game_mappings (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
region TEXT NOT NULL,
|
||||
game TEXT NOT NULL,
|
||||
UNIQUE(region, game)
|
||||
);
|
||||
|
||||
-- Metadata table for migration tracking
|
||||
CREATE TABLE metadata (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
key TEXT UNIQUE NOT NULL,
|
||||
value TEXT,
|
||||
"createdAt" TIMESTAMPTZ DEFAULT NOW(),
|
||||
"updatedAt" TIMESTAMPTZ DEFAULT NOW()
|
||||
);
|
||||
|
||||
-- Create indexes for better performance
|
||||
CREATE INDEX idx_pokedex_entries_pokedex_number ON pokedex_entries("pokedexNumber");
|
||||
CREATE INDEX idx_pokedex_entries_pokemon ON pokedex_entries(pokemon);
|
||||
CREATE INDEX idx_pokedex_entries_box_placement_forms ON pokedex_entries("boxPlacementFormsBox", "boxPlacementFormsRow", "boxPlacementFormsColumn");
|
||||
CREATE INDEX idx_pokedex_entries_box_placement ON pokedex_entries("boxPlacementBox", "boxPlacementRow", "boxPlacementColumn");
|
||||
|
||||
CREATE INDEX idx_catch_records_user_id ON catch_records("userId");
|
||||
CREATE INDEX idx_catch_records_pokedex_entry_id ON catch_records("pokedexEntryId");
|
||||
CREATE INDEX idx_catch_records_caught ON catch_records(caught);
|
||||
CREATE INDEX idx_catch_records_in_home ON catch_records("inHome");
|
||||
CREATE INDEX idx_catch_records_user_caught ON catch_records("userId", caught);
|
||||
|
||||
-- Function to automatically update updated_at timestamp
|
||||
CREATE OR REPLACE FUNCTION update_updated_at_column()
|
||||
RETURNS TRIGGER AS $$
|
||||
BEGIN
|
||||
NEW."updatedAt" = NOW();
|
||||
RETURN NEW;
|
||||
END;
|
||||
$$ LANGUAGE plpgsql;
|
||||
|
||||
-- Create triggers for updated_at
|
||||
CREATE TRIGGER update_pokedex_entries_updated_at
|
||||
BEFORE UPDATE ON pokedex_entries
|
||||
FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();
|
||||
|
||||
CREATE TRIGGER update_catch_records_updated_at
|
||||
BEFORE UPDATE ON catch_records
|
||||
FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();
|
||||
|
||||
-- Enable RLS on all tables
|
||||
ALTER TABLE pokedex_entries ENABLE ROW LEVEL SECURITY;
|
||||
ALTER TABLE catch_records ENABLE ROW LEVEL SECURITY;
|
||||
ALTER TABLE region_game_mappings ENABLE ROW LEVEL SECURITY;
|
||||
ALTER TABLE metadata ENABLE ROW LEVEL SECURITY;
|
||||
|
||||
-- RLS policies for pokedex_entries (public read)
|
||||
CREATE POLICY "Anyone can view pokemon entries" ON pokedex_entries
|
||||
FOR SELECT USING (true);
|
||||
|
||||
-- RLS policies for catch_records (user-specific)
|
||||
CREATE POLICY "Users can view own catch records" ON catch_records
|
||||
FOR SELECT USING (auth.uid() = "userId");
|
||||
|
||||
CREATE POLICY "Users can insert own catch records" ON catch_records
|
||||
FOR INSERT WITH CHECK (auth.uid() = "userId");
|
||||
|
||||
CREATE POLICY "Users can update own catch records" ON catch_records
|
||||
FOR UPDATE USING (auth.uid() = "userId");
|
||||
|
||||
CREATE POLICY "Users can delete own catch records" ON catch_records
|
||||
FOR DELETE USING (auth.uid() = "userId");
|
||||
|
||||
-- RLS policies for region_game_mappings (public read)
|
||||
CREATE POLICY "Anyone can view region game mappings" ON region_game_mappings
|
||||
FOR SELECT USING (true);
|
||||
|
||||
-- RLS policies for metadata (public read)
|
||||
CREATE POLICY "Anyone can view metadata" ON metadata
|
||||
FOR SELECT USING (true);
|
||||
@@ -1,54 +0,0 @@
|
||||
-- Migration: Seed Pokemon data from TSV
|
||||
-- This replaces the separate tsv-parser project and MongoDB seeding
|
||||
|
||||
-- Insert Pokemon data based on the TSV structure
|
||||
-- Note: This is a sample of the data structure - you'll need to convert the full TSV
|
||||
|
||||
INSERT INTO pokedex_entries (
|
||||
pokedex_number,
|
||||
pokemon,
|
||||
form,
|
||||
can_gigantamax,
|
||||
region_to_catch_in,
|
||||
games_to_catch_in,
|
||||
region_to_evolve_in,
|
||||
evolution_information,
|
||||
box_placement_forms_box,
|
||||
box_placement_forms_row,
|
||||
box_placement_forms_column,
|
||||
box_placement_box,
|
||||
box_placement_row,
|
||||
box_placement_column
|
||||
) VALUES
|
||||
-- Sample data from the TSV (first few entries)
|
||||
(1, 'Bulbasaur', NULL, FALSE, 'Kanto', ARRAY['Red','Blue','Yellow','LG: Pikachu','LG: Eevee'], NULL, NULL, 1, 1, 1, 1, 1, 1),
|
||||
(2, 'Ivysaur', NULL, FALSE, 'Kanto', ARRAY['Red','Blue','Yellow','LG: Pikachu','LG: Eevee'], NULL, NULL, 1, 1, 2, 1, 1, 2),
|
||||
(3, 'Venusaur', NULL, TRUE, 'Kanto', ARRAY['Red','Blue','Yellow','LG: Pikachu','LG: Eevee'], NULL, NULL, 1, 1, 3, 1, 1, 3),
|
||||
(3, 'Venusaur', 'Female', FALSE, 'Kanto', ARRAY['Red','Blue','Yellow','LG: Pikachu','LG: Eevee'], NULL, NULL, 1, 1, 4, NULL, NULL, NULL),
|
||||
(4, 'Charmander', NULL, FALSE, 'Kanto', ARRAY['Red','Blue','Yellow','LG: Pikachu','LG: Eevee'], NULL, NULL, 1, 1, 5, 1, 1, 4),
|
||||
(5, 'Charmeleon', NULL, FALSE, 'Kanto', ARRAY['Red','Blue','Yellow','LG: Pikachu','LG: Eevee'], NULL, NULL, 1, 1, 6, 1, 1, 5),
|
||||
(6, 'Charizard', NULL, TRUE, 'Kanto', ARRAY['Red','Blue','Yellow','LG: Pikachu','LG: Eevee'], NULL, NULL, 1, 2, 1, 1, 1, 6),
|
||||
(7, 'Squirtle', NULL, FALSE, 'Kanto', ARRAY['Red','Blue','Yellow','LG: Pikachu','LG: Eevee'], NULL, NULL, 1, 2, 2, 1, 2, 1),
|
||||
(8, 'Wartortle', NULL, FALSE, 'Kanto', ARRAY['Red','Blue','Yellow','LG: Pikachu','LG: Eevee'], NULL, NULL, 1, 2, 3, 1, 2, 2);
|
||||
|
||||
-- Insert region game mappings
|
||||
INSERT INTO region_game_mappings (region, games) VALUES
|
||||
('Kanto', ARRAY['Red', 'Blue', 'Yellow', 'LG: Pikachu', 'LG: Eevee']),
|
||||
('Johto', ARRAY['Gold', 'Silver', 'Crystal', 'HeartGold', 'SoulSilver']),
|
||||
('Hoenn', ARRAY['Ruby', 'Sapphire', 'Emerald', 'Omega Ruby', 'Alpha Sapphire']),
|
||||
('Sinnoh', ARRAY['Diamond', 'Pearl', 'Platinum', 'Brilliant Diamond', 'Shining Pearl']),
|
||||
('Unova', ARRAY['Black', 'White', 'Black 2', 'White 2']),
|
||||
('Kalos', ARRAY['X', 'Y']),
|
||||
('Alola', ARRAY['Sun', 'Moon', 'Ultra Sun', 'Ultra Moon']),
|
||||
('Galar', ARRAY['Sword', 'Shield']),
|
||||
('Paldea', ARRAY['Scarlet', 'Violet']);
|
||||
|
||||
-- Insert some metadata
|
||||
INSERT INTO pokedex_metadata (key, value) VALUES
|
||||
('total_pokemon', '{"count": 1025}'),
|
||||
('last_updated', '{"timestamp": "2025-01-26T00:00:00Z"}'),
|
||||
('supported_generations', '{"generations": [1,2,3,4,5,6,7,8,9]}');
|
||||
|
||||
-- Note: This is a sample seed. For the full migration, we'll need to:
|
||||
-- 1. Convert the entire TSV file to SQL inserts
|
||||
-- 2. Or create a function to import from the TSV data directly
|
||||
@@ -7,20 +7,20 @@ DELETE FROM pokedex_entries;
|
||||
|
||||
-- Insert full Pokemon dataset
|
||||
INSERT INTO pokedex_entries (
|
||||
pokedex_number,
|
||||
"pokedexNumber",
|
||||
pokemon,
|
||||
form,
|
||||
can_gigantamax,
|
||||
region_to_catch_in,
|
||||
games_to_catch_in,
|
||||
region_to_evolve_in,
|
||||
evolution_information,
|
||||
box_placement_forms_box,
|
||||
box_placement_forms_row,
|
||||
box_placement_forms_column,
|
||||
box_placement_box,
|
||||
box_placement_row,
|
||||
box_placement_column
|
||||
"canGigantamax",
|
||||
"regionToCatchIn",
|
||||
"gamesToCatchIn",
|
||||
"regionToEvolveIn",
|
||||
"evolutionInformation",
|
||||
"boxPlacementFormsBox",
|
||||
"boxPlacementFormsRow",
|
||||
"boxPlacementFormsColumn",
|
||||
"boxPlacementBox",
|
||||
"boxPlacementRow",
|
||||
"boxPlacementColumn"
|
||||
) VALUES
|
||||
(1, 'Bulbasaur', NULL, false, 'Kanto', ARRAY['Red', 'Blue', 'Yellow', 'LG: Pikachu', 'LG: Eevee'], NULL, NULL, 1, 1, 1, 1, 1, 1),
|
||||
(2, 'Ivysaur', NULL, false, 'Kanto', ARRAY['Red', 'Blue', 'Yellow', 'LG: Pikachu', 'LG: Eevee'], NULL, NULL, 1, 1, 2, 1, 1, 2),
|
||||
@@ -344,7 +344,8 @@ INSERT INTO pokedex_entries (
|
||||
(209, 'Snubbull', NULL, false, 'Johto', ARRAY['Gold', 'Silver', 'Crystal', 'HG', 'SS'], NULL, NULL, 12, 2, 6, 7, 5, 5),
|
||||
(210, 'Granbull', NULL, false, 'Johto', ARRAY['Gold', 'Silver', 'Crystal', 'HG', 'SS'], NULL, NULL, 12, 3, 1, 7, 5, 6),
|
||||
(211, 'Qwilfish', NULL, false, 'Johto', ARRAY['Gold', 'Silver', 'Crystal', 'HG', 'SS'], NULL, 'Route 32, Super Rod', 12, 3, 2, 8, 1, 1),
|
||||
(211, 'Qwilfish', 'Hisuian', false, 'Hisui', ARRAY['PLA'], NULL, 'Obsidian Fieldlands: near Ramanas Island (Hisuian Form)
|
||||
(211, 'Qwilfish', 'Hisuian', false, 'Hisui', ARRAY['PLA'], NULL, 'Obsidian Fieldlands: near Ramanas Island (Hisuian Form)
|
||||
Cobalt Coastlands: near Bathers'' Lagoon, near Hideaway Bay, near Tombolo Walk, near Sand''s Reach, Tranquility Cove, Islespy Shore and nearby (additional Alpha icon.png), Lunker''s Lair, near Seagrass Haven, near Firespit Island, massive mass outbreaks (Hisuian Form)', 12, 3, 3, null, null, null),
|
||||
(212, 'Scizor', NULL, false, 'Kanto', ARRAY['Red', 'Yellow', 'LG: Pikachu'], 'Johto', 'It evolves from Scyther when traded while holding a Metal Coat.', 12, 3, 4, 8, 1, 2),
|
||||
(212, 'Scizor', 'Female', false, 'Kanto', ARRAY['Red', 'Yellow', 'LG: Pikachu'], 'Johto', 'It evolves from Scyther when traded while holding a Metal Coat.', 12, 3, 5, null, null, null),
|
||||
(213, 'Shuckle', NULL, false, 'Johto', ARRAY['Gold', 'Silver', 'Crystal', 'HG', 'SS'], NULL, NULL, 12, 3, 6, 8, 1, 3),
|
||||
@@ -352,8 +353,10 @@ INSERT INTO pokedex_entries (
|
||||
(214, 'Heracross', 'Female', false, 'Johto', ARRAY['Gold', 'Silver', 'Crystal', 'HG', 'SS'], NULL, NULL, 12, 4, 2, null, null, null),
|
||||
(215, 'Sneasel', NULL, false, 'Johto', ARRAY['Gold', 'Silver', 'Crystal', 'HG', 'SS'], NULL, 'Ice Path, b3f, 6pm-4am', 12, 4, 3, 8, 1, 5),
|
||||
(215, 'Sneasel', 'Female', false, 'Johto', ARRAY['Gold', 'Silver', 'Crystal', 'HG', 'SS'], NULL, 'Ice Path, b3f, 6pm-4am', 12, 4, 4, null, null, null),
|
||||
(215, 'Sneasel', 'Female', false, 'Johto', ARRAY['Gold', 'Silver', 'Crystal', 'HG', 'SS'], NULL, 'Ice Path, b3f, 6pm-4am', 12, 4, 4, null, null, null),
|
||||
(215, 'Sneasel', 'Hisuian', false, 'Hisui', ARRAY['PLA'], NULL, 'Coronet Highlands: near Celestica Trail, near Primeval Grotto, massive mass outbreaks (Hisuian Form)
|
||||
(215, 'Sneasel', 'Hisuian', false, 'Hisui', ARRAY['PLA'], NULL, 'Coronet Highlands: near Celestica Trail, near Primeval Grotto, massive mass outbreaks (Hisuian Form)
|
||||
Alabaster Icelands: near Avalugg''s Legacy (additional Alpha icon.png), Glacier Terrace, near Pearl Settlement (Hisuian Form)', 12, 4, 5, null, null, null),
|
||||
(215, 'Sneasel', 'Female-Hisuian', false, 'Hisui', ARRAY['PLA'], NULL, 'Coronet Highlands: near Celestica Trail, near Primeval Grotto, massive mass outbreaks (Hisuian Form)
|
||||
Alabaster Icelands: near Avalugg''s Legacy (additional Alpha icon.png), Glacier Terrace, near Pearl Settlement (Hisuian Form)', 12, 4, 6, null, null, null),
|
||||
(216, 'Teddiursa', NULL, false, 'Johto', ARRAY['Gold', 'Crystal', 'SS'], NULL, 'Dark Cave, Blackthorn City Entrance, 4am-10am', 12, 5, 1, 8, 1, 6),
|
||||
(217, 'Ursaring', NULL, false, 'Johto', ARRAY['Gold', 'Crystal', 'SS'], NULL, NULL, 12, 5, 2, 8, 2, 1),
|
||||
(217, 'Ursaring', 'Female', false, 'Johto', ARRAY['Gold', 'Crystal', 'SS'], NULL, NULL, 12, 5, 3, null, null, null),
|
||||
@@ -765,7 +768,9 @@ INSERT INTO pokedex_entries (
|
||||
(549, 'Lilligant', 'Hisuian', false, 'Unova', ARRAY['White', 'White2'], 'Hisui', 'In Hisui, Petilil evolves into Hisuian Lilligant when exposed to a Sun Stone.', 27, 5, 5, null, null, null),
|
||||
(550, 'Basculin', 'Red-striped', false, 'Unova', ARRAY['Black', 'White', 'Black2', 'White2'], NULL, 'Routes 1, 3, 6, 11, 14, Striaton City, Wellspring Cave, Pinwheel Forest, Dragonspiral Tower, Challenger''s Cave, Victory Road, Village Bridge, Giant Chasm, Abundant Shrine, Lostlorn Forest (Surfing or fishing) (Red-Striped Form) (Rippling water) (Blue-Striped Form)', 27, 5, 6, 19, 2, 4),
|
||||
(550, 'Basculin', 'Blue-striped', false, 'Unova', ARRAY['Black', 'White', 'Black2', 'White2'], NULL, 'Routes 1, 3, 6, 11, 14, Striaton City, Wellspring Cave, Pinwheel Forest, Dragonspiral Tower, Challenger''s Cave, Victory Road, Village Bridge, Giant Chasm, Abundant Shrine, Lostlorn Forest (Surfing or fishing) (Red-Striped Form) (Rippling water) (Blue-Striped Form)', 28, 1, 1, null, null, null),
|
||||
(549, 'Lilligant', 'Hisuian', false, 'Unova', ARRAY['White', 'White2'], 'Hisui', 'In Hisui, Petilil evolves into Hisuian Lilligant when exposed to a Sun Stone.', 27, 5, 5, null, null, null),
|
||||
(550, 'Basculin', 'White-striped', false, 'Hisui', ARRAY['PLA'], NULL, 'Cobalt Coastlands: Tranquility Cove, near Islespy Shore, near Firespit Island, massive mass outbreaks (White-Striped Form)
|
||||
Coronet Highlands: Fabled Spring (White-Striped Form)
|
||||
Alabaster Icelands: near Avalugg''s Legacy, Heart''s Crag, Lake Acuity, near Pearl Settlement (White-Striped Form)', 28, 1, 2, null, null, null),
|
||||
(551, 'Sandile', NULL, false, 'Unova', ARRAY['Black', 'White', 'Black2', 'White2'], NULL, 'Route 4, Desert Resort, Relic Castle', 28, 1, 3, 19, 2, 5),
|
||||
(552, 'Krokorok', NULL, false, 'Unova', ARRAY['Black', 'White', 'Black2', 'White2'], NULL, 'Evolves from Sandile at level 29', 28, 1, 4, 19, 2, 6),
|
||||
(553, 'Krookodile', NULL, false, 'Unova', ARRAY['Black', 'White', 'Black2', 'White2'], NULL, 'Evolves from Krokorok at level 40', 28, 1, 5, 19, 3, 1),
|
||||
@@ -1414,9 +1419,51 @@ INSERT INTO pokedex_entries (
|
||||
(1025, 'Pecharunt', NULL, false, 'Paldea', ARRAY['Scarlet', 'Violet'], NULL, NULL, 51, 2, 6, 35, 1, 4);
|
||||
|
||||
-- Update metadata
|
||||
(1023, 'Iron Crown', NULL, false, 'Paldea', ARRAY['Violet'], NULL, NULL, 51, 2, 4, 35, 1, 2),
|
||||
(1024, 'Terapagos', NULL, false, 'Paldea', ARRAY['Scarlet', 'Violet'], NULL, NULL, 51, 2, 5, 35, 1, 3),
|
||||
UPDATE metadata
|
||||
SET value = '{"count": 1390}', "updatedAt" = NOW()
|
||||
WHERE key = 'total_pokemon';
|
||||
|
||||
-- Insert region-game mappings if not already present
|
||||
INSERT INTO region_game_mappings (region, game) VALUES
|
||||
('Kanto', 'Red'),
|
||||
('Kanto', 'Blue'),
|
||||
('Kanto', 'Yellow'),
|
||||
('Kanto', 'LG: Pikachu'),
|
||||
('Kanto', 'LG: Eevee'),
|
||||
('Johto', 'Gold'),
|
||||
('Johto', 'Silver'),
|
||||
('Johto', 'Crystal'),
|
||||
('Hoenn', 'Ruby'),
|
||||
('Hoenn', 'Sapphire'),
|
||||
('Hoenn', 'Emerald'),
|
||||
('Sinnoh', 'Diamond'),
|
||||
('Sinnoh', 'Pearl'),
|
||||
('Sinnoh', 'Platinum'),
|
||||
('Unova', 'Black'),
|
||||
('Unova', 'White'),
|
||||
('Unova', 'Black 2'),
|
||||
('Unova', 'White 2'),
|
||||
('Kalos', 'X'),
|
||||
('Kalos', 'Y'),
|
||||
('Alola', 'Sun'),
|
||||
('Alola', 'Moon'),
|
||||
('Alola', 'Ultra Sun'),
|
||||
('Alola', 'Ultra Moon'),
|
||||
('Galar', 'Sword'),
|
||||
('Galar', 'Shield'),
|
||||
('Paldea', 'Scarlet'),
|
||||
('Paldea', 'Violet')
|
||||
ON CONFLICT (region, game) DO NOTHING;
|
||||
|
||||
-- Insert metadata
|
||||
INSERT INTO metadata (key, value) VALUES
|
||||
('migration_version', '1.0'),
|
||||
('data_source', 'tsv-parser'),
|
||||
('last_updated', NOW()::TEXT),
|
||||
('total_pokemon', '{"count": 1390}')
|
||||
ON CONFLICT (key) DO UPDATE SET
|
||||
value = EXCLUDED.value,
|
||||
"updatedAt" = NOW();
|
||||
|
||||
-- Add success message
|
||||
SELECT 'Successfully loaded 1390 Pokemon entries' as message;
|
||||
|
||||
@@ -1,14 +0,0 @@
|
||||
-- Initial seed data for local development
|
||||
-- This file is referenced in config.toml and will be loaded during db reset
|
||||
|
||||
-- Note: RLS is already enabled on auth.users by default in Supabase
|
||||
-- No need to modify system tables here
|
||||
|
||||
-- Add any application-specific seed data here
|
||||
-- For now, this file is minimal since we use MongoDB for app data
|
||||
|
||||
-- Example: If we had a profiles table, we could seed it here
|
||||
-- INSERT INTO profiles (id, username) VALUES
|
||||
-- ('test-user-id', 'testuser');
|
||||
|
||||
SELECT 'Local Supabase setup complete' as message;
|
||||
+7
-1
@@ -17,7 +17,13 @@
|
||||
"virtual:pwa-assets"
|
||||
]
|
||||
},
|
||||
"include": ["node_modules/vite-plugin-pwa/client.d.ts", "node_modules/**/*.d.ts"]
|
||||
"include": [
|
||||
"src/**/*.ts",
|
||||
"src/**/*.svelte",
|
||||
"node_modules/vite-plugin-pwa/client.d.ts",
|
||||
"node_modules/**/*.d.ts"
|
||||
],
|
||||
"exclude": [".svelte-kit/**/*", "node_modules/**/*"]
|
||||
// Path aliases are handled by https://kit.svelte.dev/docs/configuration#alias
|
||||
//
|
||||
// If you want to overwrite includes/excludes, make sure to copy over the relevant includes/excludes
|
||||
|
||||
Reference in New Issue
Block a user