fix: resolve event deletion 500 error with proper foreign key handling
Mirror to GitHub / mirror (push) Successful in 22s
Test and Lint / backend-test (push) Successful in 1m7s
continuous-integration/drone/push Build is passing
Test and Lint / frontend-test (push) Successful in 2m18s
Version and Release / version-bump (push) Failing after 31s
Version and Release / trigger-drone (push) Has been skipped
Mirror to GitHub / mirror (push) Successful in 22s
Test and Lint / backend-test (push) Successful in 1m7s
continuous-integration/drone/push Build is passing
Test and Lint / frontend-test (push) Successful in 2m18s
Version and Release / version-bump (push) Failing after 31s
Version and Release / trigger-drone (push) Has been skipped
- Rewrite delete endpoint to use database transaction - Delete related data in correct order: access_logs, email_queue, photos, photo_categories, then event - Add cleanup for storage folders and archive files - Improve error messages for better debugging 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -14,6 +14,7 @@ jobs:
|
|||||||
outputs:
|
outputs:
|
||||||
new_version: ${{ steps.version.outputs.new_version }}
|
new_version: ${{ steps.version.outputs.new_version }}
|
||||||
version_changed: ${{ steps.version.outputs.version_changed }}
|
version_changed: ${{ steps.version.outputs.version_changed }}
|
||||||
|
component_changed: ${{ steps.version.outputs.component_changed }}
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v3
|
||||||
with:
|
with:
|
||||||
@@ -30,15 +31,79 @@ jobs:
|
|||||||
git config --global user.name 'Gitea Actions Bot'
|
git config --global user.name 'Gitea Actions Bot'
|
||||||
git config --global user.email 'actions@gitea.local'
|
git config --global user.email 'actions@gitea.local'
|
||||||
|
|
||||||
- name: Bump version
|
- name: Detect changes and bump version
|
||||||
id: version
|
id: version
|
||||||
run: |
|
run: |
|
||||||
# Get current version from backend package.json
|
# Get the commit range for changed files
|
||||||
CURRENT_VERSION=$(node -p "require('./backend/package.json').version")
|
if [ "${{ github.event.before }}" != "0000000000000000000000000000000000000000" ]; then
|
||||||
echo "Current version: $CURRENT_VERSION"
|
COMMIT_RANGE="${{ github.event.before }}..${{ github.sha }}"
|
||||||
|
else
|
||||||
|
# First commit, check all files
|
||||||
|
COMMIT_RANGE="${{ github.sha }}"
|
||||||
|
fi
|
||||||
|
|
||||||
# Split version into parts
|
# Check what changed
|
||||||
IFS='.' read -r -a version_parts <<< "$CURRENT_VERSION"
|
BACKEND_CHANGED=$(git diff --name-only $COMMIT_RANGE | grep -E '^backend/' | wc -l)
|
||||||
|
FRONTEND_CHANGED=$(git diff --name-only $COMMIT_RANGE | grep -E '^frontend/' | wc -l)
|
||||||
|
ROOT_CHANGED=$(git diff --name-only $COMMIT_RANGE | grep -E '^(package\.json|docker-compose|Dockerfile|scripts/)' | wc -l)
|
||||||
|
|
||||||
|
echo "Backend files changed: $BACKEND_CHANGED"
|
||||||
|
echo "Frontend files changed: $FRONTEND_CHANGED"
|
||||||
|
echo "Root files changed: $ROOT_CHANGED"
|
||||||
|
|
||||||
|
# Get current versions
|
||||||
|
BACKEND_VERSION=$(node -p "require('./backend/package.json').version")
|
||||||
|
FRONTEND_VERSION=$(node -p "require('./frontend/package.json').version")
|
||||||
|
|
||||||
|
echo "Current backend version: $BACKEND_VERSION"
|
||||||
|
echo "Current frontend version: $FRONTEND_VERSION"
|
||||||
|
|
||||||
|
# Determine what to update based on changes
|
||||||
|
BACKEND_UPDATE=false
|
||||||
|
FRONTEND_UPDATE=false
|
||||||
|
COMPONENT_CHANGED=""
|
||||||
|
|
||||||
|
if [ $ROOT_CHANGED -gt 0 ]; then
|
||||||
|
# Root changes affect both components
|
||||||
|
BACKEND_UPDATE=true
|
||||||
|
FRONTEND_UPDATE=true
|
||||||
|
COMPONENT_CHANGED="both"
|
||||||
|
SOURCE_VERSION=$BACKEND_VERSION
|
||||||
|
elif [ $BACKEND_CHANGED -gt 0 ] && [ $FRONTEND_CHANGED -gt 0 ]; then
|
||||||
|
# Both components changed
|
||||||
|
BACKEND_UPDATE=true
|
||||||
|
FRONTEND_UPDATE=true
|
||||||
|
COMPONENT_CHANGED="both"
|
||||||
|
# Use the higher version as source
|
||||||
|
if [ "$(printf '%s\n' "$BACKEND_VERSION" "$FRONTEND_VERSION" | sort -V | tail -n1)" = "$BACKEND_VERSION" ]; then
|
||||||
|
SOURCE_VERSION=$BACKEND_VERSION
|
||||||
|
else
|
||||||
|
SOURCE_VERSION=$FRONTEND_VERSION
|
||||||
|
fi
|
||||||
|
elif [ $BACKEND_CHANGED -gt 0 ]; then
|
||||||
|
# Only backend changed
|
||||||
|
BACKEND_UPDATE=true
|
||||||
|
COMPONENT_CHANGED="backend"
|
||||||
|
SOURCE_VERSION=$BACKEND_VERSION
|
||||||
|
elif [ $FRONTEND_CHANGED -gt 0 ]; then
|
||||||
|
# Only frontend changed
|
||||||
|
FRONTEND_UPDATE=true
|
||||||
|
COMPONENT_CHANGED="frontend"
|
||||||
|
SOURCE_VERSION=$FRONTEND_VERSION
|
||||||
|
else
|
||||||
|
echo "No relevant changes detected"
|
||||||
|
echo "version_changed=false" >> $GITHUB_OUTPUT
|
||||||
|
echo "component_changed=none" >> $GITHUB_OUTPUT
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "Component changed: $COMPONENT_CHANGED"
|
||||||
|
echo "Source version: $SOURCE_VERSION"
|
||||||
|
echo "Backend update: $BACKEND_UPDATE"
|
||||||
|
echo "Frontend update: $FRONTEND_UPDATE"
|
||||||
|
|
||||||
|
# Calculate new version
|
||||||
|
IFS='.' read -r -a version_parts <<< "$SOURCE_VERSION"
|
||||||
MAJOR="${version_parts[0]}"
|
MAJOR="${version_parts[0]}"
|
||||||
MINOR="${version_parts[1]}"
|
MINOR="${version_parts[1]}"
|
||||||
PATCH="${version_parts[2]}"
|
PATCH="${version_parts[2]}"
|
||||||
@@ -49,13 +114,22 @@ jobs:
|
|||||||
|
|
||||||
echo "New version: $NEW_VERSION"
|
echo "New version: $NEW_VERSION"
|
||||||
echo "new_version=$NEW_VERSION" >> $GITHUB_OUTPUT
|
echo "new_version=$NEW_VERSION" >> $GITHUB_OUTPUT
|
||||||
|
echo "component_changed=$COMPONENT_CHANGED" >> $GITHUB_OUTPUT
|
||||||
|
|
||||||
# Update version in package.json files
|
# Update versions in package.json files
|
||||||
cd backend && npm version $NEW_VERSION --no-git-tag-version
|
if [ "$BACKEND_UPDATE" = true ]; then
|
||||||
cd ../frontend && npm version $NEW_VERSION --no-git-tag-version
|
echo "Updating backend version to $NEW_VERSION"
|
||||||
cd ..
|
cd backend && npm version $NEW_VERSION --no-git-tag-version
|
||||||
|
cd ..
|
||||||
|
fi
|
||||||
|
|
||||||
# Check if there are changes
|
if [ "$FRONTEND_UPDATE" = true ]; then
|
||||||
|
echo "Updating frontend version to $NEW_VERSION"
|
||||||
|
cd frontend && npm version $NEW_VERSION --no-git-tag-version
|
||||||
|
cd ..
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Check if there are changes to commit
|
||||||
if [[ -n $(git status -s) ]]; then
|
if [[ -n $(git status -s) ]]; then
|
||||||
echo "version_changed=true" >> $GITHUB_OUTPUT
|
echo "version_changed=true" >> $GITHUB_OUTPUT
|
||||||
else
|
else
|
||||||
@@ -65,15 +139,36 @@ jobs:
|
|||||||
- name: Commit version bump
|
- name: Commit version bump
|
||||||
if: steps.version.outputs.version_changed == 'true'
|
if: steps.version.outputs.version_changed == 'true'
|
||||||
run: |
|
run: |
|
||||||
git add backend/package.json backend/package-lock.json
|
COMPONENT="${{ steps.version.outputs.component_changed }}"
|
||||||
git add frontend/package.json frontend/package-lock.json
|
|
||||||
git commit -m "chore: bump version to ${{ steps.version.outputs.new_version }}"
|
if [ "$COMPONENT" = "both" ]; then
|
||||||
|
git add backend/package.json backend/package-lock.json
|
||||||
|
git add frontend/package.json frontend/package-lock.json
|
||||||
|
git commit -m "chore: bump version to ${{ steps.version.outputs.new_version }} (backend + frontend)"
|
||||||
|
elif [ "$COMPONENT" = "backend" ]; then
|
||||||
|
git add backend/package.json backend/package-lock.json
|
||||||
|
git commit -m "chore: bump backend version to ${{ steps.version.outputs.new_version }}"
|
||||||
|
elif [ "$COMPONENT" = "frontend" ]; then
|
||||||
|
git add frontend/package.json frontend/package-lock.json
|
||||||
|
git commit -m "chore: bump frontend version to ${{ steps.version.outputs.new_version }}"
|
||||||
|
fi
|
||||||
|
|
||||||
git push
|
git push
|
||||||
|
|
||||||
- name: Create Git tag
|
- name: Create Git tag
|
||||||
if: steps.version.outputs.version_changed == 'true'
|
if: steps.version.outputs.version_changed == 'true'
|
||||||
run: |
|
run: |
|
||||||
git tag -a "v${{ steps.version.outputs.new_version }}" -m "Release v${{ steps.version.outputs.new_version }}"
|
COMPONENT="${{ steps.version.outputs.component_changed }}"
|
||||||
|
|
||||||
|
if [ "$COMPONENT" = "both" ]; then
|
||||||
|
TAG_MESSAGE="Release v${{ steps.version.outputs.new_version }} (backend + frontend)"
|
||||||
|
elif [ "$COMPONENT" = "backend" ]; then
|
||||||
|
TAG_MESSAGE="Release v${{ steps.version.outputs.new_version }} (backend)"
|
||||||
|
elif [ "$COMPONENT" = "frontend" ]; then
|
||||||
|
TAG_MESSAGE="Release v${{ steps.version.outputs.new_version }} (frontend)"
|
||||||
|
fi
|
||||||
|
|
||||||
|
git tag -a "v${{ steps.version.outputs.new_version }}" -m "$TAG_MESSAGE"
|
||||||
git push origin "v${{ steps.version.outputs.new_version }}"
|
git push origin "v${{ steps.version.outputs.new_version }}"
|
||||||
|
|
||||||
trigger-drone:
|
trigger-drone:
|
||||||
@@ -84,5 +179,6 @@ jobs:
|
|||||||
- name: Trigger Drone Build
|
- name: Trigger Drone Build
|
||||||
run: |
|
run: |
|
||||||
echo "Version bumped to ${{ needs.version-bump.outputs.new_version }}"
|
echo "Version bumped to ${{ needs.version-bump.outputs.new_version }}"
|
||||||
|
echo "Component(s) changed: ${{ needs.version-bump.outputs.component_changed }}"
|
||||||
echo "Drone will automatically trigger on the new tag"
|
echo "Drone will automatically trigger on the new tag"
|
||||||
# Drone CI will automatically trigger on the tag push event
|
# Drone CI will automatically trigger on the tag push event
|
||||||
@@ -393,13 +393,53 @@ router.delete('/:id', adminAuth, async (req, res) => {
|
|||||||
return res.status(404).json({ error: 'Event not found' });
|
return res.status(404).json({ error: 'Event not found' });
|
||||||
}
|
}
|
||||||
|
|
||||||
// Delete associated photos
|
// Start a transaction to ensure all deletions succeed or fail together
|
||||||
await db('photos').where('event_id', id).del();
|
await db.transaction(async (trx) => {
|
||||||
|
// 1. Delete access logs
|
||||||
|
await trx('access_logs').where('event_id', id).del();
|
||||||
|
|
||||||
// Delete event
|
// 2. Delete email queue entries
|
||||||
await db('events').where('id', id).del();
|
await trx('email_queue').where('event_id', id).del();
|
||||||
|
|
||||||
// Log activity
|
// 3. Delete photos (this will also handle hero_photo_id foreign key)
|
||||||
|
await trx('photos').where('event_id', id).del();
|
||||||
|
|
||||||
|
// 4. Delete categories (photo_categories has CASCADE delete for event_id)
|
||||||
|
await trx('photo_categories').where('event_id', id).del();
|
||||||
|
|
||||||
|
// 5. Finally delete the event
|
||||||
|
await trx('events').where('id', id).del();
|
||||||
|
|
||||||
|
// Delete event folder from storage if it exists
|
||||||
|
if (event.folder_path) {
|
||||||
|
const storagePath = process.env.STORAGE_PATH || path.join(__dirname, '../../../storage');
|
||||||
|
const eventFolderPath = path.join(storagePath, 'events', 'active', event.folder_path);
|
||||||
|
|
||||||
|
try {
|
||||||
|
const fsPromises = require('fs').promises;
|
||||||
|
await fsPromises.rm(eventFolderPath, { recursive: true, force: true });
|
||||||
|
} catch (err) {
|
||||||
|
console.error('Failed to delete event folder:', err);
|
||||||
|
// Don't fail the transaction if folder deletion fails
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete archive if exists
|
||||||
|
if (event.archive_path) {
|
||||||
|
const storagePath = process.env.STORAGE_PATH || path.join(__dirname, '../../../storage');
|
||||||
|
const archivePath = path.join(storagePath, event.archive_path);
|
||||||
|
|
||||||
|
try {
|
||||||
|
const fsPromises = require('fs').promises;
|
||||||
|
await fsPromises.unlink(archivePath);
|
||||||
|
} catch (err) {
|
||||||
|
console.error('Failed to delete archive file:', err);
|
||||||
|
// Don't fail the transaction if file deletion fails
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Log activity (outside transaction)
|
||||||
await logActivity('event_deleted',
|
await logActivity('event_deleted',
|
||||||
{ event_name: event.event_name },
|
{ event_name: event.event_name },
|
||||||
null,
|
null,
|
||||||
@@ -409,7 +449,19 @@ router.delete('/:id', adminAuth, async (req, res) => {
|
|||||||
res.json({ message: 'Event deleted successfully' });
|
res.json({ message: 'Event deleted successfully' });
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error deleting event:', error);
|
console.error('Error deleting event:', error);
|
||||||
res.status(500).json({ error: 'Failed to delete event' });
|
|
||||||
|
// Provide more specific error messages
|
||||||
|
if (error.message && error.message.includes('foreign key constraint')) {
|
||||||
|
res.status(500).json({
|
||||||
|
error: 'Cannot delete event due to existing references. Please contact support.',
|
||||||
|
details: error.message
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
res.status(500).json({
|
||||||
|
error: 'Failed to delete event',
|
||||||
|
details: process.env.NODE_ENV === 'development' ? error.message : undefined
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user