Skip to content

Self-service tournament administration and yearly rollover - #79

Open
leonshimizu wants to merge 14 commits into
mainfrom
codex/admin-self-service
Open

Self-service tournament administration and yearly rollover#79
leonshimizu wants to merge 14 commits into
mainfrom
codex/admin-self-service

Conversation

@leonshimizu

@leonshimizu leonshimizu commented Jul 11, 2026

Copy link
Copy Markdown
Contributor

Summary

  • add seasons and a safe annual rollover workflow that copies reusable event structure while clearing stale dates, registration links, live state, result IDs, hotel booking details, and translations
  • add publishing readiness, explicit publish/unpublish actions, season-level main-event and slug safeguards, and source-event lineage
  • add schedulable sponsor placements with season/event scope, media uploads, and a public featured sponsor bar
  • make the documented viewer role genuinely read-only across admin APIs and expose viewer access in the admin shell
  • add audit history to the dashboard and core event/season/sponsor-placement operations
  • refresh repository documentation and add an admin operations runbook
  • replace the gallery bulk update path that triggered the prior high-confidence Brakeman warning

Verification

  • bundle exec rails test — 7 tests, 31 assertions, 0 failures
  • bundle exec rails zeitwerk:check
  • bundle exec brakeman -q --no-pager — 0 high-confidence warnings; 7 pre-existing medium-confidence findings remain
  • npm run lint
  • npx tsc -b
  • npx vite build
  • in-app smoke check of dashboard, seasons, and sponsor placement routes

Operational notes

  • Deploy must run the three new Rails migrations.
  • The app data and the linked official listing appeared to disagree by one day for the July 2026 Hong Kong event. This PR does not guess or overwrite that date; the runbook requires human verification before publishing.
  • Registration payments, bracket management, check-in, and tournament-day scoring remain intentionally outside this content/event administration scope.

Greptile Summary

This PR introduces season lifecycle management (draft → active → archived), a safe annual rollover workflow that pre-stages blobs outside the DB transaction, schedulable sponsor placements with a public featured-bar component, read-only viewer access across all admin APIs, and an audit log trail on core operations.

  • Rollover safety: blobs are copied to storage before the transaction begins; a rollover_committed flag ensures the rescue block only purges blobs if the DB commit never happened. The audit log is written after the commit with a non-raising rescue, avoiding the transaction-abort risk noted in a prior review.
  • Publishing guardrail: publishable? is checked in both the update and new publish actions before a status promotion to upcoming/live is allowed; the migration handles slug deduplication and main-event uniqueness validation before creating the corresponding indexes.
  • Security: cta_url is validated to http/https only at the model layer (cta_url_is_safe) and again at the frontend (normalizeExternalUrl), closing the stored-XSS vector flagged in the prior review. The gallery bulk_update action replaces update_all with per-record update! inside a transaction, resolving the prior Brakeman mass-assignment warning.

Confidence Score: 5/5

  • The rollover workflow, publishing guardrails, and viewer-access enforcement are all well-structured. The three new migrations are the main operational step needed before merging.
  • The blob pre-staging + transaction + post-commit audit pattern is correct. Migration deduplication guards are in place. The cta_url XSS concern from the prior review is closed at both model and frontend layers. The remaining comments are performance/observability suggestions that do not affect correctness.
  • No files require special attention before merging, though api/app/models/season.rb and api/app/controllers/api/v1/sponsor_placements_controller.rb have minor query inefficiencies worth addressing in a follow-up.

Important Files Changed

Filename Overview
api/app/models/season.rb New Season model with status/year validation, current_season helper, and as_json that runs an explicit preloader per serialization call — the :season back-association is re-fetched from DB even though every event in the result already belongs to the season being serialized.
api/app/services/season_rollover_service.rb Season rollover service: blob pre-staging outside the transaction, clean-up on failure in rescue block, audit log after commit with non-raising rescue — logic is sound. Previous audit-log-inside-transaction concern is resolved.
api/app/services/event_rollover_service.rb Event rollover: pre-stages blobs before the DB transaction, rolls back + purges on failure, tracks committed? to avoid double-purge. Logic is well-structured and correctly handles ownership of prepared attachments.
api/app/controllers/api/v1/admin/events_controller.rb Events admin controller: adds publish/unpublish actions, viewer read-only support, audit logging. includes list now covers :season and hero_image_attachment. update guards status promotion behind publishable?.
api/app/controllers/api/v1/admin/seasons_controller.rb New seasons CRUD + activate/rollover actions with audit logging. Uses @season.destroy (without bang) so restrict_with_error sets errors on the model and returns false — errors rendered via else branch. Rescue for InvalidForeignKey is a secondary guard.
api/app/models/sponsor_placement.rb New SponsorPlacement model: cta_url_is_safe validates scheme allowlist, media_is_safe validates MIME type and size. Previously flagged XSS vector is addressed at both model and frontend layers.
api/app/controllers/api/v1/sponsor_placements_controller.rb Public sponsor placements endpoint: calls Season.current_season on every request, which issues a DB query each time. With no caching this becomes a per-request query on a public, unauthenticated endpoint.
api/app/controllers/api/v1/admin/audit_logs_controller.rb New audit log endpoint: supports limit (capped to 100) but has no offset or cursor, making records older than the last 100 inaccessible through the API.
api/db/migrate/20260711090000_add_seasons_and_event_rollover_fields.rb Migration: includes deduplicate_and_backfill_event_slugs! before adding the unique index, and ensure_one_main_event_per_season! before adding the partial unique index — both pre-existing concerns from prior reviews are addressed.

Sequence Diagram

sequenceDiagram
    participant Admin
    participant SeasonsController
    participant SeasonRolloverService
    participant EventRolloverService
    participant BlobCopyService
    participant DB

    Admin->>SeasonsController: POST /admin/seasons/:id/rollover
    SeasonsController->>SeasonRolloverService: call(source_season, target_year)

    loop For each source event
        SeasonRolloverService->>EventRolloverService: prepare_attachments(event)
        EventRolloverService->>BlobCopyService: copy hero + accommodation blobs
        BlobCopyService-->>EventRolloverService: staged blobs (pre-transaction)
    end

    loop For each sponsor placement
        SeasonRolloverService->>BlobCopyService: copy media blob
    end

    SeasonRolloverService->>DB: BEGIN TRANSACTION
    SeasonRolloverService->>DB: INSERT season (draft)
    loop For each event plan
        SeasonRolloverService->>EventRolloverService: call(event, target_season, prepared_attachments)
        EventRolloverService->>DB: INSERT event + schedule + prizes + accommodations
        EventRolloverService->>DB: ATTACH hero blob
    end
    SeasonRolloverService->>DB: INSERT sponsor_placements + ATTACH media
    SeasonRolloverService->>DB: COMMIT

    Note over SeasonRolloverService: rollover_committed = true

    SeasonRolloverService->>DB: INSERT audit_log (rescued on failure)
    SeasonRolloverService-->>SeasonsController: "{season, events}"
    SeasonsController-->>Admin: 201 Created
Loading

Reviews (13): Last reviewed commit: "Harden admin rollover and sponsor placem..." | Re-trigger Greptile

@netlify

netlify Bot commented Jul 11, 2026

Copy link
Copy Markdown

Deploy Preview for marianas-open ready!

Name Link
🔨 Latest commit 96df0be
🔍 Latest deploy log https://app.netlify.com/projects/marianas-open/deploys/6a77e35404fa000008792c77
😎 Deploy Preview https://deploy-preview-79--marianas-open.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.
🤖 Make changes Run an agent on this branch

To edit notification comments on pull requests, go to your Netlify project configuration.

@coderabbitai

coderabbitai Bot commented Jul 11, 2026

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 1e866b3c-4c01-4591-8468-650bc276e020

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch codex/admin-self-service

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

Comment thread api/app/models/season.rb
Comment thread api/app/models/season.rb
@leonshimizu
leonshimizu marked this pull request as ready for review July 11, 2026 04:21
Comment thread web/src/components/SponsorPlacementBar.tsx Outdated
Comment thread api/app/services/season_rollover_service.rb
Comment thread api/app/controllers/api/v1/admin/seasons_controller.rb Outdated
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant