add game tracker with owned pokemon data and call of duty mastery
All checks were successful
Test and deploy staging / staging (push) Successful in 18s

Fold catchemall into /projects/game-tracker (old URL redirects) and add
Call of Duty tracking for Black Ops through WWII.

- Pokémon catalog is now owned: locations, encounters and per-game
  evolution methods are built from the pinned PokéAPI snapshot and seeded.
- Pokémon you can evolve or breed in a game count as obtainable; mythicals
  without another route are marked event-only.
- Detail dialog with evolution chain and where to find it, location pages
  with admin notes and pictures, and admin corrections that survive reseeds.
- Call of Duty weapons, milestones and Steam achievements per game, with
  derived Diamond / Dark Matter / Solar / Black Sky / Chrome tiers.
- New migration (db:migrate:game-tracker), seeds (db:seed, db:seed:cod) and
  deploy preflight checks for them.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-09-24 17:36:18 +00:00
parent 438623d857
commit a10585a4d2
49 changed files with 8236 additions and 170 deletions

View File

@@ -68,12 +68,12 @@ test('register, persist progress, isolate users, enforce admin, revoke session',
page.on('pageerror', (error) => errors.push(error.message));
const email = `auth-test-${crypto.randomUUID()}@example.com`;
createdEmails.push(email);
await page.goto('/register?returnTo=/projects/catch-em-all');
await page.goto('/register?returnTo=/projects/game-tracker/pokemon');
await page.getByLabel('Name', { exact: true }).fill('Browser Trainer');
await page.getByRole('textbox', { name: 'Email', exact: true }).fill(email);
await page.getByLabel('Password', { exact: true }).fill(password);
await page.getByRole('button', { name: 'Create account', exact: true }).click();
await expect(page).toHaveURL(`${origin}/projects/catch-em-all`);
await expect(page).toHaveURL(`${origin}/projects/game-tracker/pokemon`);
const session = await (await page.request.get('/api/auth/get-session')).json();
createdUsers.push(session.user.id);
expect(session.user.role).toBe('user');
@@ -96,14 +96,16 @@ test('register, persist progress, isolate users, enforce admin, revoke session',
)[0]
).toEqual(caught);
const [game] = await sql`select id from games order by id limit 1`;
const selection = await action(page.request, '/projects/catch-em-all?/setGames', {
const selection = await action(page.request, '/projects/game-tracker/pokemon?/setGames', {
gameIds: String(game.id)
});
expect(selection.status()).toBe(200);
await page.reload();
await expect(page.locator('thead th')).toHaveCount(2);
// Explicitly selecting none must not revert to all games after a reload.
expect((await action(page.request, '/projects/catch-em-all?/setGames')).status()).toBe(200);
expect((await action(page.request, '/projects/game-tracker/pokemon?/setGames')).status()).toBe(
200
);
await page.reload();
await expect(page.locator('thead th')).toHaveCount(1);
@@ -112,7 +114,7 @@ test('register, persist progress, isolate users, enforce admin, revoke session',
const other = await register(bob, 'Other Trainer');
expect(
(
await action(bob, '/projects/catch-em-all?/togglePokemonCaught', {
await action(bob, '/projects/game-tracker/pokemon?/togglePokemonCaught', {
entryId: String(caught.pokemon_game_entry_id)
})
).status()
@@ -135,11 +137,15 @@ test('register, persist progress, isolate users, enforce admin, revoke session',
).status()
).toBe(403);
expect(
(await action(bob, '/projects/catch-em-all?/setGames', { gameIds: 'not-a-game' })).status()
(
await action(bob, '/projects/game-tracker/pokemon?/setGames', { gameIds: 'not-a-game' })
).status()
).toBe(400);
expect(
(
await action(bob, '/projects/catch-em-all?/togglePokemonCaught', { entryId: '999999999' })
await action(bob, '/projects/game-tracker/pokemon?/togglePokemonCaught', {
entryId: '999999999'
})
).status()
).toBe(404);
} finally {
@@ -204,12 +210,21 @@ test('anonymous/forged cookies, reserved identities, unsafe redirects, OAuth sta
expect((await action(page.request, '/lists?/newList')).status()).toBe(401);
expect(
(
await action(page.request, '/projects/catch-em-all?/togglePokemonCaught', {
await action(page.request, '/projects/game-tracker/pokemon?/togglePokemonCaught', {
entryId: '1',
caught: 'on'
})
).status()
).toBe(401);
expect(
(
await action(page.request, '/projects/game-tracker/call-of-duty/black-ops-2?/toggle', {
itemId: '1',
milestone: 'gold',
done: 'on'
})
).status()
).toBe(401);
expect(
(await page.request.post('/admin/login', { data: { password: 'anything' } })).status()
).toBe(410);