[BE] DB Migration — arcade_games & arcade_sessions tables
A pull request for this has already been merged.
- #480 by @pedroafmonteiro — merged
Assessment
- Difficulty
- 3/5
- Estimated time
- Half a day
- Newbie friendliness
- 62/100
Research direction
Create the new migration at database/migrations/XXXX_create_arcade_tables.php, using the Schema::create blocks given for arcade_games and arcade_sessions. Read the existing migrations for the editions and enrollments tables first, since both foreign keys point at them, and check whether the Edition and Enrollment models exist yet. Done means php artisan migrate and migrate:rollback both run cleanly, the UNIQUE(edition_id, slug) constraint rejects a duplicate insert, and no triggers are created.
Written by the indexing model from the issue text.
Description
🎮 Project Context: SINF Gamification & Arcade System
What is SINF and why are we building this?
Semana de Informática (SINF) is an annual tech conference organized by NIAEFEUP for university students. During the conference, attendees earn event tokens ("SINFrões" / points) by attending talks, workshops, and completing sponsor quests (scanning QR codes at company booths). Historically, these tokens were only spent at the physical conference store (/shop).
To massively increase student engagement throughout the days of the event, we are building a Gamification / Arcade & Prize Draw platform:
- Arcade (
/arcade): Participants can wager their earned tokens on 5 mini-games (PvP Coin Flip, High/Low Dice, Bug Sweeper, Mastermind, and Binary Blitz). - Prize Draws (
/raffle): Participants can sink tokens into daily and final raffle pools to win hardware and sponsor prizes. - Admin Management: Event organizers can configure multiplier payouts (
payout_config), toggle games on/off, create raffle pools, and trigger weighted-random winner drawings directly inside the admin dashboard.
🛡️ Core Economy & Security Rules (MUST READ FOR ALL DEVS)
- Points Storage: Points are stored as an unsigned integer in
enrollments.points(one row per participant per edition). - PostgreSQL Trigger Caution: The database has existing triggers that modify points when rows are inserted/deleted from
enrollment_slotandenrollment_product.- NEVER insert into or delete from those pivot tables for arcade games or raffles!
- Directly mutating
enrollments.pointsvia PHP ($enrollment->decrement('points', $wager)) does not fire those triggers and is the only safe way to deduct/credit points.
- No Negative Balance Constraint in DB: There is no
CHECK (points >= 0)in PostgreSQL. All validation against negative balances must occur in PHP. - Mandatory Concurrency Protection: Our attendees are Computer Science students who understand network inspection and race conditions. All token adjustments must run inside
DB::transaction()with$enrollment = Enrollment::lockForUpdate()->find(...). - Server Authority: Game outcomes, RNG (
random_int()), secret state (bombs, codes), and time limits must be verified strictly server-side.
🗓️ 3-Week Timeline & Where Your Task Fits
- Week 1 (Foundation - Current Phase): Database tables (#458, #459), Eloquent models (#460, #461),
ArcadeWagerServicesecurity core (#462), Coin Flip matchmaking backend (#463), Arcade lobby page (#464), and shared UI components (#465). - Week 2 (Game Building): 5 developer pairs concurrently build the 5 games (#466–#470) on top of this foundation.
- Week 3 (Raffles, Admin & QA): Raffle purchase flow (#471–#473), Admin CRUD (#474–#477), Global Navbar state (#478), and Security Test Suite (#479).
🎯 Why Issue #458 is Critical
You are creating the database schema for the entire Arcade feature (arcade_games and arcade_sessions).
arcade_gamesstores each game's parameters: wager limits, daily win caps, and admin-tunable multipliers in apayout_config JSONBcolumn.arcade_sessionsstores every wager, play attempt, audit record, and secret server-side state (such as hidden bomb locations or active player IDs for PvP games).
All subsequent models, controllers, and games depend directly on this migration running cleanly.
Context & Objective
This is the first foundation issue for the Gamification / Arcade feature. All other arcade issues (models, controllers, frontend) depend on this migration being merged and running cleanly.
Two new tables are needed:
arcade_games— one row per game type per edition (Coin Flip, High/Low, Bug Sweeper, Mastermind, Binary Blitz). Admins configure wager limits, toggle games on/off, and set payout multipliers through apayout_config JSONBcolumn.arcade_sessions— one row per play attempt. Acts as both the audit log and the server-side state store for multi-step games (Bug Sweeper grid, Mastermind secret code, Binary Blitz questions). For the PvP Coin Flip game it also holds the second player's enrollment ID.
No PostgreSQL triggers should be added. Points mutations happen in PHP via DB::transaction() in the game controllers.
Files to Create / Modify
- Create:
database/migrations/XXXX_create_arcade_tables.php
Technical Requirements
Use Schema::create() / Blueprint, not raw SQL DDL.
arcade_games
Schema::create('arcade_games', function (Blueprint $table) {
$table->id();
$table->foreignIdFor(Edition::class)->constrained()->cascadeOnDelete();
$table->string('slug', 64); // 'coin-flip' | 'high-low' | 'bug-sweeper' | 'mastermind' | 'binary-blitz'
$table->string('name');
$table->boolean('is_active')->default(true);
$table->boolean('is_pvp')->default(false); // true only for coin-flip
$table->unsignedInteger('min_wager')->default(10);
$table->unsignedInteger('max_wager')->default(500);
$table->unsignedInteger('daily_win_cap')->nullable(); // null = unlimited
$table->json('payout_config')->default('{}'); // admin-configurable, see docs
$table->timestamps();
$table->unique(['edition_id', 'slug']);
});
arcade_sessions
Schema::create('arcade_sessions', function (Blueprint $table) {
$table->id();
$table->foreignIdFor(Enrollment::class)->constrained()->cascadeOnDelete();
$table->foreignIdFor(ArcadeGame::class)->constrained()->cascadeOnDelete();
// PvP second player (nullable — only set for coin-flip after someone joins)
$table->foreignId('challenger_enrollment_id')
->nullable()
->constrained('enrollments')
->nullOnDelete();
$table->unsignedInteger('wager');
// pending → matched → win | loss | cashout | expired
$table->string('outcome', 16)->default('pending');
$table->unsignedInteger('payout')->default(0);
$table->json('server_state')->nullable(); // hashed/encrypted secrets never sent to FE
$table->timestamp('started_at')->useCurrent();
$table->timestamp('resolved_at')->nullable();
$table->timestamps();
});
payout_config expected shapes per slug (for documentation in a migration comment)
| slug | config keys |
|---|---|
coin-flip |
house_edge_pct (int, e.g. 10) |
high-low |
under_threshold, over_threshold, under_multiplier, over_multiplier, exact_multiplier |
bug-sweeper |
click_multipliers (array of 5 floats), bomb_count (int, default 3) |
mastermind |
max_attempts (int, default 5), attempt_multipliers (object "1"→6.0 … "5"→1.5) |
binary-blitz |
question_count, time_limit_seconds, win_multiplier, partial_multiplier, daily_win_cap |
Acceptance Criteria
-
php artisan migrateruns cleanly with no errors -
php artisan migrate:rollbackdrops both tables cleanly -
arcade_gamestable contains all columns as specified -
arcade_sessionstable contains all columns as specified -
UNIQUE(edition_id, slug)constraint onarcade_gamesis enforced (test with a duplicate insert) -
challenger_enrollment_idforeign key usesnullOnDelete()(row stays if challenger is deleted) - No PostgreSQL triggers are created
Dependencies
None — this is the first issue in the gamification feature.
- Dominant language
- Vue
- Stars
- 12
- Forks
- 1
- Avg merge
- 3d 42m
- Merged PRs (30d)
- 4
Getting set up
- Ships a Dockerfile or Docker Compose file
- No pull request template
- No contributing guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
More from semanadeinformatica/website
-
frontend P0
Difficulty 1/5 Under an hour Newbie friendliness 65/100
semanadeinformatica/website#483 ·
-
gamification P1
Difficulty 4/5 3-5 days Newbie friendliness 25/100
semanadeinformatica/website#479 ·
-
frontend gamification P1
Difficulty 3/5 1-2 days Newbie friendliness 62/100
semanadeinformatica/website#478 ·
-
frontend gamification P2
Difficulty 3/5 1-2 days Newbie friendliness 52/100
semanadeinformatica/website#477 ·
-
backend gamification P1
Difficulty 3/5 1-2 days Newbie friendliness 50/100
semanadeinformatica/website#476 ·
All issues in semanadeinformatica/website
Similar issues
-
5700模组网络优先级问题Open
Difficulty 2/5 1-3 hours Newbie friendliness 78/100
-
Chip source filter and denial markers miss Storage rows when a Storage activity source is pickedOpenbug
Difficulty 2/5 1-3 hours Newbie friendliness 78/100
Maintainers usually reply within 1 day
-
Bug
Difficulty 2/5 1-3 hours Newbie friendliness 78/100
Maintainers usually reply within 1 day
-
Difficulty 2/5 1-3 hours Newbie friendliness 78/100
filecoin-project/curio#1632 ·
Maintainers usually reply within 1 day
-
Difficulty 2/5 1-3 hours Newbie friendliness 68/100
Maintainers usually reply within 1 day