Skip to content

Commit bc8da82

Browse files
authored
Merge pull request #196 from game-node-app/dev
Indexes and ordering changes
2 parents 688761a + 5c6d7b1 commit bc8da82

File tree

4 files changed

+118
-30
lines changed

4 files changed

+118
-30
lines changed

.infra/db_backup.sh

Lines changed: 100 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,40 +1,113 @@
11
#!/bin/bash
2-
# This is a sample backup script for your MySQL database.
3-
# There's probably a hundred better ways to do it, but this + a simple cronjob does the job most of the time.
2+
# Enhanced MySQL backup script (multi-instance, env-driven)
3+
# Works great with Dockerized MySQL in a shared network (e.g. game_node_app)
44

5-
# Current date in YYYY-MM-DD-HHMMSS format for unique backup filenames
5+
set -euo pipefail
6+
IFS=$'\n\t'
7+
8+
# === CONFIGURATION ===
9+
10+
# Current date for unique backup filenames
611
DATE=$(date +%F-%H%M%S)
712

8-
# Backup directory on the host
9-
BACKUP_DIR="~/backups/sql"
13+
# Backup directory on host
14+
BACKUP_DIR="${BACKUP_DIR:-/var/local/backups/sql}"
15+
mkdir -p "$BACKUP_DIR"
16+
17+
# Docker network shared by both DB containers
18+
NETWORK="${NETWORK:-game_node_app}"
19+
20+
# Docker MySQL image used for dumping
21+
MYSQL_IMAGE="${MYSQL_IMAGE:-mysql:8.3}"
22+
23+
# Rclone config (optional)
24+
RCLONE_CONFIG_NAME="${RCLONE_CONFIG_NAME:-cloudflare}"
25+
RCLONE_BUCKET_NAME="${RCLONE_BUCKET_NAME:-gamenode-sql-backup}"
26+
27+
# === DATABASES TO BACKUP ===
28+
# These must be passed via environment variables in crontab, e.g.:
29+
# DB1_NAME=gamenode DB1_USER=root DB1_PASS=pass DB1_HOST=db
30+
# DB2_NAME=supertokens DB2_USER=root DB2_PASS=pass DB2_HOST=supertokens_db
31+
# and so on.
32+
33+
declare -A DBS=(
34+
["$DB1_NAME"]="$DB1_USER:$DB1_PASS@$DB1_HOST"
35+
)
36+
37+
# Add second database if defined
38+
if [[ -n "${DB2_NAME:-}" && -n "${DB2_USER:-}" && -n "${DB2_PASS:-}" && -n "${DB2_HOST:-}" ]]; then
39+
DBS["$DB2_NAME"]="$DB2_USER:$DB2_PASS@$DB2_HOST"
40+
fi
41+
42+
# === BACKUP LOOP ===
43+
44+
for DB_NAME in "${!DBS[@]}"; do
45+
echo "🔹 Backing up database: $DB_NAME"
46+
47+
CREDENTIALS="${DBS[$DB_NAME]}"
48+
DB_USER="${CREDENTIALS%%:*}"
49+
REST="${CREDENTIALS#*:}"
50+
DB_PASS="${REST%%@*}"
51+
DB_HOST="${REST#*@}"
52+
53+
BACKUP_FILENAME="$BACKUP_DIR/${DB_NAME}-${DATE}.sql"
54+
COMPRESSED_BACKUP_FILENAME="${BACKUP_FILENAME}.zst"
55+
56+
# Maximum number of retries
57+
MAX_RETRIES=3
58+
RETRY_DELAY=5 # seconds between attempts
1059

11-
# Database credentials and details
12-
DB_HOST="localhost" #hostname of the mysql container
13-
DB_USER="root"
14-
DB_PASSWORD="root"
15-
DB_NAME="gamenode"
16-
NETWORK="your_network" #name of the network where mysql container is running. You can check the list of the docker neworks using doocker network ls
60+
# Dump DB using Dockerized MySQL client with retries
61+
SUCCESS=false
62+
for ((i=1; i<=MAX_RETRIES; i++)); do
63+
echo "🔹 Attempt $i: Backing up database $DB_NAME..."
64+
if docker run --rm --network "$NETWORK" "$MYSQL_IMAGE" \
65+
mysqldump --compact --single-transaction --quick --lock-tables=false \
66+
-h "$DB_HOST" -u "$DB_USER" -p"$DB_PASS" "$DB_NAME" > "$BACKUP_FILENAME"; then
67+
SUCCESS=true
68+
break
69+
else
70+
echo "⚠️ Backup attempt $i failed."
71+
if [[ $i -lt $MAX_RETRIES ]]; then
72+
echo "⏳ Retrying in $RETRY_DELAY seconds..."
73+
sleep $RETRY_DELAY
74+
fi
75+
fi
76+
done
1777

18-
# Docker image version of MySQL
19-
MYSQL_IMAGE="mysql:8.3"
78+
if [[ "$SUCCESS" != true ]]; then
79+
echo "❌ Failed to backup database $DB_NAME after $MAX_RETRIES attempts. Exiting."
80+
exit 1
81+
fi
2082

21-
# Backup filename
22-
BACKUP_FILENAME="$BACKUP_DIR/$DB_NAME-$DATE.sql"
23-
COMPRESSED_BACKUP_FILENAME="$BACKUP_FILENAME.zst"
83+
# Compress with 2 threads
84+
zstd -9 -T2 "$BACKUP_FILENAME" -o "$COMPRESSED_BACKUP_FILENAME"
2485

25-
# S3 bucket name
26-
BUCKET_NAME="my-sql-backup"
27-
RCLONE_CONFIG_NAME="sql-backup"
86+
# Remove uncompressed dump
87+
rm -f "$BACKUP_FILENAME"
2888

29-
# Run mysqldump within a new Docker container
30-
docker run --rm --network $NETWORK $MYSQL_IMAGE mysqldump --compact -h $DB_HOST -u $DB_USER -p$DB_PASSWORD $DB_NAME > $BACKUP_FILENAME
89+
echo "✅ Backup complete: $COMPRESSED_BACKUP_FILENAME"
3190

32-
# Compress the backup file
33-
zstd -19 "$BACKUP_FILENAME" -o "$COMPRESSED_BACKUP_FILENAME"
91+
# Upload to R2 via rclone if enabled
92+
if [[ "${UPLOAD_TO_RCLONE:-false}" == "true" ]]; then
93+
echo "☁️ Uploading $COMPRESSED_BACKUP_FILENAME to R2..."
94+
rclone copy "$COMPRESSED_BACKUP_FILENAME" "${RCLONE_CONFIG_NAME}:${RCLONE_BUCKET_NAME}" \
95+
--quiet --s3-no-check-bucket
96+
fi
3497

35-
# Removes SQL backup file after compression
36-
rm $BACKUP_FILENAME
98+
# Optionally remove local copy after upload
99+
if [[ "${CLEANUP_LOCAL:-false}" == "true" ]]; then
100+
echo "🧹 Removing local backup $COMPRESSED_BACKUP_FILENAME"
101+
rm -f "$COMPRESSED_BACKUP_FILENAME"
102+
fi
103+
done
37104

38-
rclone copy $COMPRESSED_BACKUP_FILENAME $RCLONE_CONFIG_NAME:$BUCKET_NAME
105+
# === Cleanup old backups (older than 14 days) ===
106+
# From local files
107+
echo "🗑️ Removing local backups older than 14 days in $BACKUP_DIR..."
108+
find "$BACKUP_DIR" -type f -name "*.zst" -mtime +14 -exec rm -f {} \;
109+
# From RCLONE
110+
echo "🗑️ Removing bucket backups older than 14 days in ${RCLONE_CONFIG_NAME}:${RCLONE_BUCKET_NAME}..."
111+
rclone delete --min-age 14d "${RCLONE_CONFIG_NAME}:${RCLONE_BUCKET_NAME}"
39112

40-
rm $COMPRESSED_BACKUP_FILENAME
113+
echo "🎉 All backups completed successfully."

server_swagger.json

Lines changed: 1 addition & 1 deletion
Large diffs are not rendered by default.

src/collections/collections-entries/collections-entries.service.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -207,12 +207,12 @@ export class CollectionsEntriesService {
207207
game: buildGameFilterFindOptions(dto?.gameFilters),
208208
},
209209
order: {
210-
createdAt: dto?.orderBy?.addedDate,
211210
game: {
212211
firstReleaseDate: dto?.orderBy?.releaseDate,
213212
},
214213
collectionsMap: {
215214
order: dto?.orderBy?.userCustom,
215+
createdAt: dto?.orderBy?.addedDate,
216216
},
217217
},
218218
relations: this.relations,

src/collections/collections-entries/entities/collection-entry-to-collection.entity.ts

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,19 @@
1-
import { Column, Entity, Index, ManyToOne, PrimaryColumn } from "typeorm";
1+
import {
2+
Column,
3+
CreateDateColumn,
4+
Entity,
5+
Index,
6+
ManyToOne,
7+
PrimaryColumn,
8+
} from "typeorm";
29
import { CollectionEntry } from "./collection-entry.entity";
310
import { Collection } from "../../entities/collection.entity";
411

512
@Entity({
613
name: "collection_entry_collections_collection",
714
})
15+
@Index(["collectionId", "order"])
16+
@Index(["collectionId", "createdAt"])
817
export class CollectionEntryToCollection {
918
@PrimaryColumn({
1019
type: "varchar",
@@ -37,4 +46,10 @@ export class CollectionEntryToCollection {
3746
onUpdate: "CASCADE",
3847
})
3948
collection: Collection;
49+
50+
@CreateDateColumn({
51+
type: "datetime",
52+
default: () => "CURRENT_TIMESTAMP(6)",
53+
})
54+
createdAt: Date;
4055
}

0 commit comments

Comments
 (0)