feat: build compact BO2 grind companion with cumulative ranks
Some checks failed
Test and deploy staging / staging (push) Failing after 2m50s

This commit is contained in:
2026-10-08 21:06:19 +00:00
parent c2ba8febb4
commit c2bc9050c2
39 changed files with 1499 additions and 447 deletions

View File

@@ -0,0 +1,33 @@
import postgres from 'postgres';
// Read-only fingerprints: lets a deployment verify saved progress without logging it.
if (!process.env.DB_URL) throw new Error('DB_URL is required');
const client = postgres(process.env.DB_URL, { max: 1 });
const tables = [
'user_entries',
'user_games',
'catch_em_all_settings',
'pokemon_journeys',
'pokemon_catches',
'pokemon_milestones',
'pokemon_seen',
'tracker_progress',
'tracker_ranks',
'tracker_weapon_ranks',
'tracker_challenge_progress',
'osu_entries'
];
try {
const result: Record<string, unknown> = {};
for (const table of tables) {
const [exists] = await client`select to_regclass(${'public.' + table}) is not null as present`;
if (!exists.present) continue;
const [row] = await client.unsafe(`select count(*)::int as rows,
md5(coalesce(string_agg(record, E'\\n' order by record), '')) as fingerprint
from (select row_to_json(t)::text as record from "${table}" t) s`);
result[table] = row;
}
console.log(JSON.stringify(result, null, 2));
} finally {
await client.end();
}

View File

@@ -114,6 +114,19 @@ try {
if (Number(catalog.cod_games) < 7) {
throw new Error('Call of Duty catalog has not been seeded in this database');
}
const [bo2] = await client`select rules,
(select count(*)::int from tracker_items i where i.game_id=g.id and i.kind='weapon') as weapons,
(select count(*)::int from tracker_items i where i.game_id=g.id and i.kind='weapon' and i.icon is not null) as pictures
from tracker_games g where slug='black-ops-2'`;
if (
!bo2 ||
bo2.rules.rankCaps?.weaponPrestige !== 2 ||
bo2.rules.rankCaps?.weaponLevelsByClass?.['Assault Rifles'] !== 17 ||
bo2.weapons < 40 ||
bo2.pictures < 39
) {
throw new Error('Seed the updated BO2 catalog and artwork before deploying');
}
} finally {
await client.end();
}

View File

@@ -17,6 +17,11 @@ esac
image="git.stanrunge.dev/stan/personal-website:$sha"
compose="$directory/compose.prod.yml"
if [ "$directory" = /opt/personal-website-staging ]; then
environment_name=staging
else
environment_name=production
fi
previous_tag=$(sed -n 's/^IMAGE_TAG=//p' "$directory/.env" | head -n 1)
switched=0
rollback() {
@@ -28,6 +33,47 @@ rollback() {
}
trap rollback ERR
db_version=$(sudo docker run --rm --network backend --env-file "$environment" "$image" \
bun scripts/print-db-server-version.ts)
case "$db_version" in
*[!0-9]*|'') echo 'Could not determine the PostgreSQL server version' >&2; exit 1 ;;
esac
if [ "$db_version" -ge 100000 ]; then
db_major=$((db_version / 10000))
else
db_major="$((db_version / 10000)).$(((db_version / 100) % 100))"
fi
backup_image="postgres:${db_major}-alpine"
backup_dir=/var/backups/personal-website
backup_stamp=$(date -u +%Y%m%dT%H%M%SZ)
backup_name="${environment_name}-${backup_stamp}-${sha}.dump"
sudo docker pull "$backup_image"
sudo install -d -m 0700 "$backup_dir"
sudo docker run --rm --network backend --env-file "$environment" \
--mount "type=bind,source=$backup_dir,target=/backups" \
--env "BACKUP_FILE=$backup_name" "$backup_image" \
sh -eu -c 'pg_dump --format=custom --no-owner --no-acl --file="/backups/$BACKUP_FILE" "$DB_URL"'
sudo docker run --rm --mount "type=bind,source=$backup_dir,target=/backups" \
"$backup_image" pg_restore --list "/backups/$backup_name" >/dev/null
sudo chmod 0600 "$backup_dir/$backup_name"
echo "Verified $environment_name database backup at $backup_dir/$backup_name"
run_database_script() {
echo "Running $1"
sudo docker run --rm --network backend --env-file "$environment" "$image" bun "$1"
}
run_database_script scripts/migrate-auth-catalog.ts
run_database_script scripts/migrate-foundation.ts
run_database_script scripts/migrate-money-tracker.ts
run_database_script scripts/migrate-game-tracker.ts
run_database_script scripts/migrate-tracker-journal.ts
run_database_script scripts/seed-catalog.ts
run_database_script scripts/seed-cod-catalog.ts
run_database_script scripts/seed-pokemon-research.ts
run_database_script scripts/seed-cod-challenges.ts
run_database_script scripts/seed-cod-art.ts
sudo docker run --rm --network backend --env-file "$environment" "$image" \
bun scripts/deploy-preflight.ts "$origin"
@@ -46,5 +92,7 @@ curl --retry 12 --retry-delay 2 --retry-all-errors --fail --silent --show-error
"$origin/login" >/dev/null
curl --retry 6 --retry-delay 2 --retry-all-errors --fail --silent --show-error \
"$origin/projects/game-tracker/pokemon" >/dev/null
curl --retry 6 --retry-delay 2 --retry-all-errors --fail --silent --show-error \
"$origin/projects/game-tracker/call-of-duty/black-ops-2" >/dev/null
trap - ERR
echo "Deployed $sha to $origin"

View File

@@ -0,0 +1,10 @@
import postgres from 'postgres';
if (!process.env.DB_URL) throw new Error('DB_URL is required');
const client = postgres(process.env.DB_URL, { max: 1 });
try {
const [row] = await client`select current_setting('server_version_num')::integer as version`;
console.log(row.version);
} finally {
await client.end();
}

View File

@@ -10,6 +10,7 @@ import {
trackerItems,
trackerProgress
} from '../src/lib/server/db/schema/game-tracker';
import { trackerWeaponRanks } from '../src/lib/server/db/schema/tracker-journal';
import type { MasteryRules } from '../src/lib/game-tracker/mastery';
type Catalog = {
@@ -136,7 +137,16 @@ try {
stale.map((item) => item.id)
)
);
const keep = new Set(kept.map((row) => row.id));
const ranked = await tx
.select({ id: trackerWeaponRanks.itemId })
.from(trackerWeaponRanks)
.where(
inArray(
trackerWeaponRanks.itemId,
stale.map((item) => item.id)
)
);
const keep = new Set([...kept, ...ranked].map((row) => row.id));
const remove = stale.filter((item) => !keep.has(item.id)).map((item) => item.id);
if (remove.length) await tx.delete(trackerItems).where(inArray(trackerItems.id, remove));
for (const item of stale.filter((item) => keep.has(item.id)))