mirror of
https://github.com/mauriceboe/TREK.git
synced 2026-06-19 13:21:46 +00:00
1f5deeba6c
* fix: clean up dangling FK references before deleting a user Resolves FOREIGN KEY constraint failed (500) on DELETE /api/admin/users/:id and DELETE /api/auth/me when the target user had rows in trip_members.invited_by, share_tokens.created_by, budget_items.paid_by_user_id, journeys.user_id, journey_entries.author_id, journey_contributors.user_id, or journey_share_tokens.created_by — none of which had ON DELETE clauses. Introduces deleteUserCompletely() in userCleanupService.ts which wraps all cleanup and the final DELETE FROM users in a single transaction. Both adminService.deleteUser and authService.deleteAccount now call it instead of the bare DELETE. Tests ADMIN-005b and AUTH-040 cover all reference types including notification sender/recipient and notice dismissals. * test: extend FK deletion tests to cover journeys, files, and photos ADMIN-005b and AUTH-040 now also seed and assert: - owned journey with entries (cascade-deleted via journeys.user_id cleanup) - trip_files.uploaded_by (SET NULL — file survives, attribution cleared) - trek_photos.owner_id (SET NULL — photo record survives, owner cleared) - trip_photos.user_id (CASCADE — photo association removed) * test: extend user deletion tests to cover all FK relationships ADMIN-005b and AUTH-040 now seed and assert every user FK relationship: CASCADE (row deleted): trips, trip_members, tags, mcp_tokens, oauth_tokens, oauth_consents, vacay_plans, vacay_plan_members, bucket_list, visited_countries, visited_regions, packing_templates, invite_tokens, collab_notes, settings, password_reset_tokens, notification_channel_preferences SET NULL (row survives, column nulled): categories, todo_items.assigned_user_id, packing_bags, audit_log Caught and fixed: notification_preferences was dropped in migration 72; correct table is notification_channel_preferences. * fix: preserve URL hash and OIDC redirect target through login flow - Include location.hash in redirect param at all three producer sites (ProtectedRoute, axios 401 interceptor, OAuthAuthorizePage) so hash fragments survive the login bounce - Stash redirectTarget in sessionStorage before any OIDC provider redirect and restore it after the code exchange, since the IdP strips the original ?redirect= param during the roundtrip - Clear sessionStorage on OIDC error to avoid stale state - Add tests covering sessionStorage stash on mount, navigate to saved redirect after OIDC exchange, fallback to /dashboard, and cleanup on error * fix: use day position instead of ID for accommodation date range clamping Math.min/Math.max over raw day IDs breaks the start/end picker when a trip's day IDs are non-monotonic relative to day_number (normal after repeated generateDays extend/shrink cycles). Replaced with findIndex lookups so clamping is always based on positional order. Closes #889 * fix: normalize env var comparisons to be case-insensitive All NODE_ENV, DEMO_MODE, OIDC_ONLY, FORCE_HTTPS, COOKIE_SECURE, and ALLOW_INTERNAL_NETWORK checks now use .toLowerCase() so values like 'Production' or 'True' behave identically to their lowercase forms. Also adds APP_VERSION to the startup banner. * fix: delete surplus days when shortening a trip When shrinking a trip's date range, surplus days are now deleted along with their assignments, notes, and accommodations (cascade). Places remain in the trip pool; reservations keep their day reference nulled by the existing ON DELETE SET NULL constraint (issue #909). Updates TRIP-SVC-011 to reflect the new behaviour; adds TRIP-SVC-016 as a regression test for the empty-day case. * fix: auto-backup retention deletes itself and manual backups on Docker Two bugs in cleanupOldBackups: 1. Filter was .endsWith('.zip') — swept manual backup-*.zip files too. Now restricted to auto-backup-* prefix. 2. Age was derived from stat.birthtimeMs, which is 0 on overlayfs (Docker default), making every backup appear epoch-old and get deleted immediately. Age is now parsed from the filename timestamp and falls back to mtimeMs (reliable on overlayfs). Also converts inline require('./services/auditLog') calls to a static import throughout scheduler.ts, and adds 8 unit tests covering the fixed retention logic including the overlayfs regression case. * test: update TRIP-024 to match delete behavior on trip shrink * feat: add bypass-branch-check label to skip branch enforcement
111 lines
3.8 KiB
YAML
111 lines
3.8 KiB
YAML
name: Enforce PR Target Branch
|
|
|
|
on:
|
|
pull_request_target:
|
|
types: [opened, reopened, edited, synchronize]
|
|
|
|
jobs:
|
|
check-target:
|
|
runs-on: ubuntu-latest
|
|
permissions:
|
|
pull-requests: write
|
|
issues: write
|
|
contents: read
|
|
|
|
steps:
|
|
- name: Flag or clear wrong base branch
|
|
uses: actions/github-script@v7
|
|
with:
|
|
script: |
|
|
const base = context.payload.pull_request.base.ref;
|
|
const labels = context.payload.pull_request.labels.map(l => l.name);
|
|
const prNumber = context.payload.pull_request.number;
|
|
|
|
// bypass-branch-check label skips all enforcement
|
|
if (labels.includes('bypass-branch-check')) {
|
|
console.log('bypass-branch-check label present, skipping enforcement.');
|
|
return;
|
|
}
|
|
|
|
// If the base was fixed, remove the label and let it through
|
|
if (base !== 'main') {
|
|
if (labels.includes('wrong-base-branch')) {
|
|
await github.rest.issues.removeLabel({
|
|
owner: context.repo.owner,
|
|
repo: context.repo.repo,
|
|
issue_number: prNumber,
|
|
name: 'wrong-base-branch',
|
|
});
|
|
}
|
|
return;
|
|
}
|
|
|
|
// Base is main — check if this user is a maintainer
|
|
let permission = 'none';
|
|
try {
|
|
const { data } = await github.rest.repos.getCollaboratorPermissionLevel({
|
|
owner: context.repo.owner,
|
|
repo: context.repo.repo,
|
|
username: context.payload.pull_request.user.login,
|
|
});
|
|
permission = data.permission;
|
|
} catch (_) {
|
|
// User is not a collaborator — treat as 'none'
|
|
}
|
|
|
|
if (['admin', 'write'].includes(permission)) {
|
|
console.log(`User has '${permission}' permission, skipping.`);
|
|
return;
|
|
}
|
|
|
|
// Already labeled — avoid spamming on every push
|
|
if (labels.includes('wrong-base-branch')) {
|
|
core.setFailed("PR must target `dev`, not `main`.");
|
|
return;
|
|
}
|
|
|
|
// Ensure the label exists
|
|
try {
|
|
await github.rest.issues.getLabel({
|
|
owner: context.repo.owner,
|
|
repo: context.repo.repo,
|
|
name: 'wrong-base-branch',
|
|
});
|
|
} catch (err) {
|
|
if (err.status === 404) {
|
|
await github.rest.issues.createLabel({
|
|
owner: context.repo.owner,
|
|
repo: context.repo.repo,
|
|
name: 'wrong-base-branch',
|
|
color: 'd73a4a',
|
|
description: 'PR is targeting the wrong base branch',
|
|
});
|
|
}
|
|
}
|
|
|
|
await github.rest.issues.addLabels({
|
|
owner: context.repo.owner,
|
|
repo: context.repo.repo,
|
|
issue_number: prNumber,
|
|
labels: ['wrong-base-branch'],
|
|
});
|
|
|
|
await github.rest.issues.createComment({
|
|
owner: context.repo.owner,
|
|
repo: context.repo.repo,
|
|
issue_number: prNumber,
|
|
body: [
|
|
'## Wrong target branch',
|
|
'',
|
|
'This PR targets `main`, but contributions must go through `dev` first.',
|
|
'',
|
|
'To fix this, click **Edit** next to the PR title and change the base branch to `dev`.',
|
|
'',
|
|
'**This PR will be automatically closed in 24 hours if the base branch has not been updated.**',
|
|
'',
|
|
'> _If you need to merge directly to `main`, contact a maintainer._',
|
|
].join('\n'),
|
|
});
|
|
|
|
core.setFailed("PR must target `dev`, not `main`.");
|