update nice visuel

This commit is contained in:
gpatruno
2026-07-25 20:04:34 +02:00
parent 3a8871cbae
commit 810e4ac922
78 changed files with 6742 additions and 3116 deletions
-12
View File
@@ -1,12 +0,0 @@
#!/usr/bin/env bash
# Deprecated alias — use docker-db-export.sh on prod, or local-db-export.sh on your laptop.
# This wrapper keeps the old name for existing docs / muscle memory.
set -euo pipefail
DIR="$(cd "$(dirname "$0")" && pwd)"
if [[ -f "$DIR/../docker-compose.yml" ]] && command -v docker >/dev/null 2>&1 \
&& docker compose -f "$DIR/../docker-compose.yml" ps -q casegambling >/dev/null 2>&1; then
echo "Note: forwarding to docker-db-export.sh (Docker detected)." >&2
exec "$DIR/docker-db-export.sh" "$@"
fi
echo "Note: forwarding to local-db-export.sh." >&2
exec "$DIR/local-db-export.sh" "$@"
-11
View File
@@ -1,11 +0,0 @@
#!/usr/bin/env bash
# Deprecated alias — use docker-db-import.sh (Docker) or local-db-import.sh (laptop).
set -euo pipefail
DIR="$(cd "$(dirname "$0")" && pwd)"
if [[ -f "$DIR/../docker-compose.yml" ]] && command -v docker >/dev/null 2>&1 \
&& docker compose -f "$DIR/../docker-compose.yml" ps -aq casegambling >/dev/null 2>&1; then
echo "Note: forwarding to docker-db-import.sh (Docker detected)." >&2
exec "$DIR/docker-db-import.sh" "$@"
fi
echo "Note: forwarding to local-db-import.sh." >&2
exec "$DIR/local-db-import.sh" "$@"
+61 -5
View File
@@ -5,30 +5,38 @@
# Usage:
# ./scripts/local-db-import.sh ./backups/casegambling-YYYYMMDD-HHMMSS
# ./scripts/local-db-import.sh ./backups/casegambling-YYYYMMDD-HHMMSS.tar.gz
# ./scripts/local-db-import.sh ./backups/….tar.gz <username> # player-only merge
#
# Typical flow (prod → laptop):
# # on prod:
# ./scripts/docker-db-export.sh
# # copy the .tar.gz here, then:
# ./scripts/local-db-import.sh ./backups/casegambling-….tar.gz
# # or only one player into the existing local DB:
# ./scripts/local-db-import.sh ./backups/casegambling-….tar.gz SomePlayer
#
# Stop `npm run dev` before importing so SQLite is not locked.
set -euo pipefail
ROOT="$(cd "$(dirname "$0")/.." && pwd)"
MERGE_PLAYER_SCRIPT="$ROOT/scripts/merge-player-db.sh"
if [[ -z "${1:-}" ]]; then
echo "Usage: $0 <backup-dir-or-tar.gz>" >&2
if [[ -z "${1:-}" || "${1:-}" == "-h" || "${1:-}" == "--help" ]]; then
echo "Usage: $0 <backup-dir-or-tar.gz> [username]" >&2
echo "Example: $0 ./backups/casegambling-20260116-203000.tar.gz" >&2
echo " $0 ./backups/casegambling-20260116-203000.tar.gz SomePlayer" >&2
echo "Create a backup from Docker: ./scripts/docker-db-export.sh" >&2
exit 1
fi
BACKUP_ARG="$1"
USERNAME="${2:-}"
# Resolve backup path from the caller's cwd
if [[ "$1" = /* ]]; then
SRC="$1"
if [[ "$BACKUP_ARG" = /* ]]; then
SRC="$BACKUP_ARG"
else
SRC="$(pwd)/$1"
SRC="$(pwd)/$BACKUP_ARG"
fi
LOCAL_DB="${LOCAL_DB:-$ROOT/server/prisma/dev.db}"
@@ -49,6 +57,25 @@ is_archive() {
esac
}
copy_player_avatar() {
local avatar_url="$1"
local uploads_src="$2"
local uploads_dst="$3"
[[ -z "$avatar_url" || ! -d "$uploads_src" ]] && return 0
local rel="${avatar_url#/}"
rel="${rel#uploads/}"
rel="${rel#./}"
case "$rel" in
''|http:*|https:*|*..*) return 0 ;;
esac
local src_file="$uploads_src/$rel"
if [[ -f "$src_file" ]]; then
mkdir -p "$uploads_dst/$(dirname "$rel")"
cp -f "$src_file" "$uploads_dst/$rel"
echo "==> Copied avatar upload: $rel"
fi
}
if [[ -f "$SRC" ]] && is_archive "$SRC"; then
echo "==> Extracting $(basename "$SRC")"
TMP="$(mktemp -d)"
@@ -84,6 +111,35 @@ if [[ -z "$DB" ]]; then
exit 1
fi
# ── Player-only merge into existing local DB ─────────────────────────────────
if [[ -n "$USERNAME" ]]; then
if [[ ! -f "$LOCAL_DB" ]]; then
echo "Error: local DB not found: $LOCAL_DB" >&2
echo "Tip: run a full import first (without username), or start the app once." >&2
exit 1
fi
if [[ ! -x "$MERGE_PLAYER_SCRIPT" ]]; then
echo "Error: merge helper missing or not executable: $MERGE_PLAYER_SCRIPT" >&2
exit 1
fi
# Drop WAL/SHM so we merge against the real file contents
rm -f "${LOCAL_DB}-wal" "${LOCAL_DB}-shm"
"$MERGE_PLAYER_SCRIPT" "$DB" "$LOCAL_DB" "$USERNAME"
AVATAR_URL="$(sqlite3 "$DB" "SELECT avatarUrl FROM User WHERE username = '${USERNAME//\'/\'\'}' LIMIT 1;" 2>/dev/null || true)"
if [[ -d "$SRC/uploads" ]]; then
copy_player_avatar "${AVATAR_URL:-}" "$SRC/uploads" "$LOCAL_UPLOADS"
fi
echo "==> Done (player-only)."
echo " db: $LOCAL_DB"
echo " Tip: stop npm run dev before import if the DB was locked; then restart:"
echo " npm run dev"
exit 0
fi
# ── Full import (replace local DB + uploads) ─────────────────────────────────
mkdir -p "$(dirname "$LOCAL_DB")"
echo "==> Importing database → $LOCAL_DB"
+251
View File
@@ -0,0 +1,251 @@
#!/usr/bin/env bash
# Merge one player's rows from a source SQLite DB into a target SQLite DB.
#
# Usage (sourced or executed):
# ./scripts/merge-player-db.sh <source.db> <target.db> <username>
#
# Replaces any existing target user with the same username.
# Copies (when present in both DBs): User, InventoryItem, Transaction,
# PaymentOrder, CaseKeyProgress, CatalogEntry, PrestigeLog, UserAchievement.
# Skips multiplayer state (Jackpot*/Duel*).
# Tolerates older backups missing newer tables/columns (e.g. PaymentOrder).
#
# Requires matching Item / Case / Achievement ids in both DBs (same catalog lineage).
# Requires sqlite3.
set -euo pipefail
SRC_DB="${1:-}"
DST_DB="${2:-}"
USERNAME="${3:-}"
if [[ -z "$SRC_DB" || -z "$DST_DB" || -z "$USERNAME" ]]; then
echo "Usage: $0 <source.db> <target.db> <username>" >&2
exit 1
fi
if [[ ! -f "$SRC_DB" ]]; then
echo "Error: source DB not found: $SRC_DB" >&2
exit 1
fi
if [[ ! -f "$DST_DB" ]]; then
echo "Error: target DB not found: $DST_DB" >&2
echo "Tip: run a full import once first, or start the app so the DB exists." >&2
exit 1
fi
if ! command -v sqlite3 >/dev/null 2>&1; then
echo "Error: sqlite3 is required for player-only import" >&2
exit 1
fi
sql_quote() {
local s="$1"
s="${s//\'/\'\'}"
printf "'%s'" "$s"
}
table_exists() {
local db="$1"
local table="$2"
local n
n="$(sqlite3 "$db" "SELECT COUNT(*) FROM sqlite_master WHERE type='table' AND name=$(sql_quote "$table");")"
[[ "$n" -gt 0 ]]
}
# Column names for a table (space-separated), excluding names passed after $2
table_cols() {
local db="$1"
local table="$2"
shift 2
local -A skip=()
local e col
for e in "$@"; do
skip["$e"]=1
done
while IFS='|' read -r _ col _; do
[[ -n "$col" && -z "${skip[$col]:-}" ]] && printf '%s\n' "$col"
done < <(sqlite3 -separator '|' "$db" "PRAGMA table_info($(sql_quote "$table"));")
}
# Intersection of columns present in BOTH DBs, preserving destination order.
common_cols() {
local table="$1"
shift
local -A src_set=()
local col
while IFS= read -r col; do
[[ -n "$col" ]] && src_set["$col"]=1
done < <(table_cols "$SRC_DB" "$table" "$@")
local -a out=()
while IFS= read -r col; do
[[ -n "$col" && -n "${src_set[$col]:-}" ]] && out+=("$col")
done < <(table_cols "$DST_DB" "$table" "$@")
local IFS=', '
echo "${out[*]}"
}
count_rows() {
local table="$1"
local qtable="$1"
[[ "$table" == "Transaction" ]] && qtable='"Transaction"'
if table_exists "$SRC_DB" "$table"; then
sqlite3 "$SRC_DB" "SELECT COUNT(*) FROM $qtable WHERE userId = $SRC_UID;"
else
echo "skip"
fi
}
SRC_UID="$(sqlite3 "$SRC_DB" "SELECT id FROM User WHERE username = $(sql_quote "$USERNAME") LIMIT 1;")"
if [[ -z "$SRC_UID" ]]; then
echo "Error: no user with username '$USERNAME' in source DB" >&2
exit 2
fi
echo "==> Merging player '$USERNAME' (source id=$SRC_UID)"
echo " source: $SRC_DB"
echo " target: $DST_DB"
N_INV="$(count_rows InventoryItem)"
N_TX="$(count_rows Transaction)"
N_PAY="$(count_rows PaymentOrder)"
N_KEY="$(count_rows CaseKeyProgress)"
N_CAT="$(count_rows CatalogEntry)"
N_PRE="$(count_rows PrestigeLog)"
N_ACH="$(count_rows UserAchievement)"
echo " rows: inventory=$N_INV tx=$N_TX payments=$N_PAY keys=$N_KEY catalog=$N_CAT prestige=$N_PRE achievements=$N_ACH"
USER_COLS="$(common_cols User id)"
if [[ -z "$USER_COLS" ]]; then
echo "Error: no common User columns between source and target" >&2
exit 1
fi
INV_COLS="$(common_cols InventoryItem id userId)"
TX_COLS="$(common_cols Transaction id userId)"
if [[ -z "$INV_COLS" || -z "$TX_COLS" ]]; then
echo "Error: InventoryItem / Transaction schema mismatch" >&2
exit 1
fi
PAY_COLS=""
if table_exists "$SRC_DB" PaymentOrder && table_exists "$DST_DB" PaymentOrder; then
PAY_COLS="$(common_cols PaymentOrder userId)"
fi
KEY_COLS=""
table_exists "$SRC_DB" CaseKeyProgress && table_exists "$DST_DB" CaseKeyProgress && KEY_COLS="$(common_cols CaseKeyProgress id userId)"
CAT_COLS=""
table_exists "$SRC_DB" CatalogEntry && table_exists "$DST_DB" CatalogEntry && CAT_COLS="$(common_cols CatalogEntry id userId)"
PRE_COLS=""
table_exists "$SRC_DB" PrestigeLog && table_exists "$DST_DB" PrestigeLog && PRE_COLS="$(common_cols PrestigeLog id userId)"
ACH_COLS=""
table_exists "$SRC_DB" UserAchievement && table_exists "$DST_DB" UserAchievement && ACH_COLS="$(common_cols UserAchievement id userId)"
SQL_EXTRA_DELETE=""
SQL_EXTRA_INSERT=""
if [[ -n "$PAY_COLS" ]]; then
SQL_EXTRA_DELETE+="
DELETE FROM PaymentOrder
WHERE id IN (SELECT id FROM src.PaymentOrder WHERE userId = $SRC_UID);
"
SQL_EXTRA_INSERT+="
INSERT INTO PaymentOrder ($PAY_COLS, userId)
SELECT $PAY_COLS, (SELECT newId FROM _merge_uid)
FROM src.PaymentOrder WHERE userId = $SRC_UID;
"
else
echo " note: PaymentOrder absent in backup — skipped (osu top-up orders)"
fi
if [[ -n "$KEY_COLS" ]]; then
SQL_EXTRA_INSERT+="
INSERT INTO CaseKeyProgress (userId, $KEY_COLS)
SELECT (SELECT newId FROM _merge_uid), $KEY_COLS
FROM src.CaseKeyProgress WHERE userId = $SRC_UID;
"
fi
if [[ -n "$CAT_COLS" ]]; then
SQL_EXTRA_INSERT+="
INSERT INTO CatalogEntry (userId, $CAT_COLS)
SELECT (SELECT newId FROM _merge_uid), $CAT_COLS
FROM src.CatalogEntry WHERE userId = $SRC_UID;
"
fi
if [[ -n "$PRE_COLS" ]]; then
SQL_EXTRA_INSERT+="
INSERT INTO PrestigeLog (userId, $PRE_COLS)
SELECT (SELECT newId FROM _merge_uid), $PRE_COLS
FROM src.PrestigeLog WHERE userId = $SRC_UID;
"
fi
if [[ -n "$ACH_COLS" ]]; then
SQL_EXTRA_INSERT+="
INSERT INTO UserAchievement (userId, $ACH_COLS)
SELECT (SELECT newId FROM _merge_uid), $ACH_COLS
FROM src.UserAchievement WHERE userId = $SRC_UID;
"
fi
SQL_CLEANUP=""
table_exists "$DST_DB" JackpotRound && SQL_CLEANUP+="
UPDATE JackpotRound SET winnerUserId = NULL
WHERE winnerUserId IN (SELECT id FROM User WHERE username = $(sql_quote "$USERNAME"));
"
table_exists "$DST_DB" DuelRoom && SQL_CLEANUP+="
UPDATE DuelRoom SET winnerUserId = NULL
WHERE winnerUserId IN (SELECT id FROM User WHERE username = $(sql_quote "$USERNAME"));
DELETE FROM DuelRoom
WHERE creatorId IN (SELECT id FROM User WHERE username = $(sql_quote "$USERNAME"));
"
table_exists "$DST_DB" JackpotDeposit && SQL_CLEANUP+="
DELETE FROM JackpotDeposit
WHERE userId IN (SELECT id FROM User WHERE username = $(sql_quote "$USERNAME"));
"
table_exists "$DST_DB" DuelDeposit && SQL_CLEANUP+="
DELETE FROM DuelDeposit
WHERE userId IN (SELECT id FROM User WHERE username = $(sql_quote "$USERNAME"));
"
sqlite3 "$DST_DB" <<SQL
PRAGMA foreign_keys = ON;
ATTACH DATABASE $(sql_quote "$SRC_DB") AS src;
BEGIN;
$SQL_CLEANUP
DELETE FROM User WHERE username = $(sql_quote "$USERNAME");
$SQL_EXTRA_DELETE
INSERT INTO User ($USER_COLS)
SELECT $USER_COLS
FROM src.User WHERE id = $SRC_UID;
CREATE TEMP TABLE _merge_uid AS
SELECT id AS newId FROM User WHERE username = $(sql_quote "$USERNAME") LIMIT 1;
INSERT INTO InventoryItem (userId, $INV_COLS)
SELECT (SELECT newId FROM _merge_uid), $INV_COLS
FROM src.InventoryItem WHERE userId = $SRC_UID;
INSERT INTO "Transaction" (userId, $TX_COLS)
SELECT (SELECT newId FROM _merge_uid), $TX_COLS
FROM src."Transaction" WHERE userId = $SRC_UID;
$SQL_EXTRA_INSERT
DROP TABLE _merge_uid;
COMMIT;
DETACH DATABASE src;
SQL
NEW_UID="$(sqlite3 "$DST_DB" "SELECT id FROM User WHERE username = $(sql_quote "$USERNAME") LIMIT 1;")"
echo "==> Player merged as target id=$NEW_UID"
echo " (jackpot/duel deposits & rooms not imported)"
+89 -7
View File
@@ -3,8 +3,9 @@
# Run this on the machine that runs docker compose.
#
# Usage:
# ./scripts/docker-db-import.sh ./backups/casegambling-YYYYMMDD-HHMMSS
# ./scripts/docker-db-import.sh ./backups/casegambling-YYYYMMDD-HHMMSS.tar.gz
# ./scripts/xdocker-db-import.sh ./backups/casegambling-YYYYMMDD-HHMMSS
# ./scripts/xdocker-db-import.sh ./backups/casegambling-YYYYMMDD-HHMMSS.tar.gz
# ./scripts/xdocker-db-import.sh ./backups/….tar.gz <username> # player-only merge
#
# Expects a backup from docker-db-export.sh or local-db-export.sh
# (prod.db or dev.db + uploads/).
@@ -24,18 +25,24 @@ else
exit 1
fi
if [[ -z "${1:-}" ]]; then
echo "Usage: $0 <backup-dir-or-tar.gz>" >&2
MERGE_PLAYER_SCRIPT="$ROOT/scripts/merge-player-db.sh"
if [[ -z "${1:-}" || "${1:-}" == "-h" || "${1:-}" == "--help" ]]; then
echo "Usage: $0 <backup-dir-or-tar.gz> [username]" >&2
echo "Example: $0 ./backups/casegambling-20260116-203000.tar.gz" >&2
echo " $0 ./backups/casegambling-20260116-203000.tar.gz SomePlayer" >&2
echo "Create a backup first: ./scripts/docker-db-export.sh (or local-db-export.sh)" >&2
exit 1
fi
BACKUP_ARG="$1"
USERNAME="${2:-}"
# Resolve backup path from the caller's cwd (before any cd)
if [[ "$1" = /* ]]; then
SRC="$1"
if [[ "$BACKUP_ARG" = /* ]]; then
SRC="$BACKUP_ARG"
else
SRC="$(pwd)/$1"
SRC="$(pwd)/$BACKUP_ARG"
fi
cd "$ROOT"
@@ -52,10 +59,14 @@ fi
SERVICE="${SERVICE:-casegambling}"
TMP=""
WORK=""
cleanup() {
if [[ -n "$TMP" && -d "$TMP" ]]; then
rm -rf "$TMP"
fi
if [[ -n "$WORK" && -d "$WORK" ]]; then
rm -rf "$WORK"
fi
}
trap cleanup EXIT
@@ -66,6 +77,25 @@ is_archive() {
esac
}
copy_player_avatar_docker() {
local avatar_url="$1"
local uploads_src="$2"
[[ -z "$avatar_url" || ! -d "$uploads_src" ]] && return 0
local rel="${avatar_url#/}"
rel="${rel#uploads/}"
rel="${rel#./}"
case "$rel" in
''|http:*|https:*|*..*) return 0 ;;
esac
local src_file="$uploads_src/$rel"
if [[ -f "$src_file" ]]; then
"${COMPOSE[@]}" run --rm --no-deps --entrypoint sh "$SERVICE" -c \
"mkdir -p /app/uploads/$(dirname "$rel")" >/dev/null
"${COMPOSE[@]}" cp "$src_file" "$SERVICE:/app/uploads/$rel"
echo "==> Copied avatar upload: $rel"
fi
}
if ! command -v docker >/dev/null 2>&1; then
echo "Error: docker not found" >&2
exit 1
@@ -109,6 +139,58 @@ fi
echo "==> Ensuring compose service exists"
"${COMPOSE[@]}" up -d --no-start "$SERVICE" >/dev/null
# ── Player-only merge into existing Docker DB ────────────────────────────────
if [[ -n "$USERNAME" ]]; then
if [[ ! -x "$MERGE_PLAYER_SCRIPT" ]]; then
echo "Error: merge helper missing or not executable: $MERGE_PLAYER_SCRIPT" >&2
exit 1
fi
if ! command -v sqlite3 >/dev/null 2>&1; then
echo "Error: sqlite3 is required on the host for player-only import" >&2
exit 1
fi
echo "==> Stopping $SERVICE (safe SQLite merge)"
"${COMPOSE[@]}" stop "$SERVICE" >/dev/null 2>&1 || true
WORK="$(mktemp -d)"
TARGET_DB="$WORK/prod.db"
echo "==> Fetching current /data/prod.db"
if ! "${COMPOSE[@]}" cp "$SERVICE:/data/prod.db" "$TARGET_DB" 2>/dev/null; then
echo "Error: could not copy /data/prod.db from $SERVICE" >&2
echo "Tip: run a full import once first so the volume has a database." >&2
exit 1
fi
"${COMPOSE[@]}" run --rm --no-deps --entrypoint sh "$SERVICE" -c \
'rm -f /data/prod.db-wal /data/prod.db-shm' >/dev/null
"$MERGE_PLAYER_SCRIPT" "$DB" "$TARGET_DB" "$USERNAME"
echo "==> Writing merged database → /data/prod.db"
"${COMPOSE[@]}" cp "$TARGET_DB" "$SERVICE:/data/prod.db"
AVATAR_URL="$(sqlite3 "$DB" "SELECT avatarUrl FROM User WHERE username = '${USERNAME//\'/\'\'}' LIMIT 1;" 2>/dev/null || true)"
if [[ -d "$SRC/uploads" ]]; then
copy_player_avatar_docker "${AVATAR_URL:-}" "$SRC/uploads"
fi
echo "==> Starting $SERVICE"
"${COMPOSE[@]}" start "$SERVICE" 2>/dev/null || "${COMPOSE[@]}" up -d "$SERVICE"
HOST_PORT_VAL="${HOST_PORT:-}"
if [[ -z "$HOST_PORT_VAL" && -f "$ENV_FILE" ]]; then
HOST_PORT_VAL="$(grep -E '^HOST_PORT=' "$ENV_FILE" 2>/dev/null | cut -d= -f2- | tr -d '"' || true)"
fi
HOST_PORT_VAL="${HOST_PORT_VAL:-3009}"
echo "==> Done (player-only)."
echo " App: http://localhost:${HOST_PORT_VAL}"
echo " Logs: ${COMPOSE[*]} logs -f --tail=50"
exit 0
fi
# ── Full import (replace Docker DB + uploads) ────────────────────────────────
echo "==> Stopping $SERVICE (safe SQLite replace)"
"${COMPOSE[@]}" stop "$SERVICE" >/dev/null 2>&1 || true