Compare commits
3 Commits
v0.1.5.1
...
863539a5e9
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
863539a5e9 | ||
|
|
2fa8aa0042 | ||
|
|
8ecf430bf5 |
@@ -469,10 +469,12 @@ export async function PUT(request: Request) {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Execute all database write operations in a transaction to ensure consistency
|
||||||
|
const updatedSong = await prisma.$transaction(async (tx) => {
|
||||||
// Handle SpecialSong relations separately
|
// Handle SpecialSong relations separately
|
||||||
if (effectiveSpecialIds !== undefined) {
|
if (effectiveSpecialIds !== undefined) {
|
||||||
// First, get current special assignments
|
// First, get current special assignments (within transaction)
|
||||||
const currentSpecials = await prisma.specialSong.findMany({
|
const currentSpecials = await tx.specialSong.findMany({
|
||||||
where: { songId: Number(id) }
|
where: { songId: Number(id) }
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -482,7 +484,7 @@ export async function PUT(request: Request) {
|
|||||||
// Delete removed specials
|
// Delete removed specials
|
||||||
const toDelete = currentSpecialIds.filter(sid => !newSpecialIds.includes(sid));
|
const toDelete = currentSpecialIds.filter(sid => !newSpecialIds.includes(sid));
|
||||||
if (toDelete.length > 0) {
|
if (toDelete.length > 0) {
|
||||||
await prisma.specialSong.deleteMany({
|
await tx.specialSong.deleteMany({
|
||||||
where: {
|
where: {
|
||||||
songId: Number(id),
|
songId: Number(id),
|
||||||
specialId: { in: toDelete }
|
specialId: { in: toDelete }
|
||||||
@@ -493,7 +495,7 @@ export async function PUT(request: Request) {
|
|||||||
// Add new specials
|
// Add new specials
|
||||||
const toAdd = newSpecialIds.filter(sid => !currentSpecialIds.includes(sid));
|
const toAdd = newSpecialIds.filter(sid => !currentSpecialIds.includes(sid));
|
||||||
if (toAdd.length > 0) {
|
if (toAdd.length > 0) {
|
||||||
await prisma.specialSong.createMany({
|
await tx.specialSong.createMany({
|
||||||
data: toAdd.map(specialId => ({
|
data: toAdd.map(specialId => ({
|
||||||
songId: Number(id),
|
songId: Number(id),
|
||||||
specialId,
|
specialId,
|
||||||
@@ -503,7 +505,8 @@ export async function PUT(request: Request) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const updatedSong = await prisma.song.update({
|
// Update song (this also handles genre relations via Prisma's set operation)
|
||||||
|
return await tx.song.update({
|
||||||
where: { id: Number(id) },
|
where: { id: Number(id) },
|
||||||
data,
|
data,
|
||||||
include: {
|
include: {
|
||||||
@@ -515,6 +518,7 @@ export async function PUT(request: Request) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
});
|
||||||
|
|
||||||
return NextResponse.json(updatedSong);
|
return NextResponse.json(updatedSong);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@@ -559,7 +563,7 @@ export async function DELETE(request: Request) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Delete file
|
// Delete files first (outside transaction, as file system operations can't be rolled back)
|
||||||
const filePath = path.join(process.cwd(), 'public/uploads', song.filename);
|
const filePath = path.join(process.cwd(), 'public/uploads', song.filename);
|
||||||
try {
|
try {
|
||||||
await unlink(filePath);
|
await unlink(filePath);
|
||||||
@@ -578,10 +582,12 @@ export async function DELETE(request: Request) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Delete from database (will cascade delete related puzzles)
|
// Delete from database in transaction (will cascade delete related puzzles, SpecialSong, etc.)
|
||||||
await prisma.song.delete({
|
await prisma.$transaction(async (tx) => {
|
||||||
|
await tx.song.delete({
|
||||||
where: { id: Number(id) },
|
where: { id: Number(id) },
|
||||||
});
|
});
|
||||||
|
});
|
||||||
|
|
||||||
return NextResponse.json({ success: true });
|
return NextResponse.json({ success: true });
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "hoerdle",
|
"name": "hoerdle",
|
||||||
"version": "0.1.5.1",
|
"version": "0.1.5.2",
|
||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "next dev",
|
"dev": "next dev",
|
||||||
|
|||||||
77
scripts/backup-restic.sh
Executable file
77
scripts/backup-restic.sh
Executable file
@@ -0,0 +1,77 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
# Restic backup script for Hördle deployment
|
||||||
|
# Creates a backup snapshot with tags and handles errors gracefully
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
|
echo "💾 Creating Restic backup..."
|
||||||
|
|
||||||
|
if ! command -v restic >/dev/null 2>&1; then
|
||||||
|
echo "⚠️ restic not found in PATH, skipping Restic backup"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Check required environment variables
|
||||||
|
if [ -z "$RESTIC_PASSWORD" ]; then
|
||||||
|
echo "⚠️ RESTIC_PASSWORD not set, skipping Restic backup"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ -z "$RESTIC_AUTH_USER" ] || [ -z "$RESTIC_AUTH_PASSWORD" ]; then
|
||||||
|
echo "⚠️ RESTIC_AUTH_USER or RESTIC_AUTH_PASSWORD not set, skipping Restic backup"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Build repository URL
|
||||||
|
RESTIC_REPO="rest:https://${RESTIC_AUTH_USER}:${RESTIC_AUTH_PASSWORD}@restic.elpatron.me/"
|
||||||
|
|
||||||
|
# Get current commit hash for tagging
|
||||||
|
CURRENT_COMMIT_SHORT="$(git rev-parse --short HEAD 2>/dev/null || echo 'unknown')"
|
||||||
|
CURRENT_DATE="$(date +%Y-%m-%d)"
|
||||||
|
|
||||||
|
# Export password for restic
|
||||||
|
export RESTIC_PASSWORD
|
||||||
|
|
||||||
|
# Check if repository exists, initialize if not
|
||||||
|
if ! restic -r "$RESTIC_REPO" snapshots >/dev/null 2>&1; then
|
||||||
|
echo " Initializing Restic repository..."
|
||||||
|
if ! restic -r "$RESTIC_REPO" init >/dev/null 2>&1; then
|
||||||
|
echo "⚠️ Failed to initialize Restic repository, skipping backup"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Create backup with tags
|
||||||
|
# Backup important directories: backups, config files, but exclude node_modules, .git, etc.
|
||||||
|
echo " Creating Restic snapshot..."
|
||||||
|
RESTIC_EXIT_CODE=0
|
||||||
|
restic -r "$RESTIC_REPO" backup \
|
||||||
|
--tag deployment \
|
||||||
|
--tag hoerdle \
|
||||||
|
--tag "date:${CURRENT_DATE}" \
|
||||||
|
--tag "commit:${CURRENT_COMMIT_SHORT}" \
|
||||||
|
--exclude='.git' \
|
||||||
|
--exclude='node_modules' \
|
||||||
|
--exclude='.next' \
|
||||||
|
--exclude='*.log' \
|
||||||
|
./backups \
|
||||||
|
./data \
|
||||||
|
./public/uploads \
|
||||||
|
docker-compose.yml \
|
||||||
|
.env \
|
||||||
|
package.json \
|
||||||
|
prisma/schema.prisma \
|
||||||
|
prisma/migrations \
|
||||||
|
scripts/ || RESTIC_EXIT_CODE=$?
|
||||||
|
|
||||||
|
if [ $RESTIC_EXIT_CODE -eq 0 ]; then
|
||||||
|
echo "✅ Restic backup completed successfully"
|
||||||
|
exit 0
|
||||||
|
elif [ $RESTIC_EXIT_CODE -eq 3 ]; then
|
||||||
|
echo "⚠️ Restic backup completed with warnings (some files could not be read), continuing..."
|
||||||
|
exit 0
|
||||||
|
else
|
||||||
|
echo "⚠️ Restic backup failed (exit code: $RESTIC_EXIT_CODE), continuing deployment..."
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
@@ -59,6 +59,9 @@ else
|
|||||||
echo "⚠️ Could not determine database path from config files"
|
echo "⚠️ Could not determine database path from config files"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
# Restic backup to remote repository
|
||||||
|
./scripts/backup-restic.sh
|
||||||
|
|
||||||
# Nur neueste Version holen (shallow fetch), vollständiges Repo ist im Deployment nicht nötig
|
# Nur neueste Version holen (shallow fetch), vollständiges Repo ist im Deployment nicht nötig
|
||||||
echo "📥 Fetching latest commit (shallow clone) from git..."
|
echo "📥 Fetching latest commit (shallow clone) from git..."
|
||||||
git fetch --prune --tags --depth=1 origin master
|
git fetch --prune --tags --depth=1 origin master
|
||||||
|
|||||||
Reference in New Issue
Block a user