[BE] Eloquent Models — ArcadeGame & ArcadeSession
Assessment
- Difficulty
- 2/5
- Estimated time
- 1-3 hours
- Newbie friendliness
- 82/100
Research direction
Start from the dependency #458 migrations and existing app/Models/Enrollment.php to match conventions. Create app/Models/ArcadeGame.php and app/Models/ArcadeSession.php with the listed fillable, casts, relationships and scopes, then add arcadeSessions() to Enrollment. Done when ArcadeGame::with('sessions')->find($id) works, payout_config is an array, and ArcadeSession::wonToday($eId,$gId)->sum('payout') returns the daily total.
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 #460 is Critical
You are building the Eloquent ORM models (ArcadeGame and ArcadeSession) that bridge Laravel to the arcade tables.
- Casting
payout_configandserver_stateas JSON arrays ensures controllers can manipulate settings fluently without manual JSON encode/decode calls. - Helper scopes like
ArcadeSession::wonToday($enrollmentId, $gameId)provide the exact query logic needed by the security service to enforce daily win caps across skill games.
Every backend game controller developed in Week 2 will interact with these models.
Context & Objective
Create the Eloquent models for the two arcade tables introduced in #458. These models are the foundation used by all game controllers and the admin CRUD.
Files to Create / Modify
- Create:
app/Models/ArcadeGame.php - Create:
app/Models/ArcadeSession.php
Technical Requirements
ArcadeGame
class ArcadeGame extends Model
{
use HasFactory, Searchable;
protected $fillable = [
'edition_id', 'slug', 'name', 'is_active', 'is_pvp',
'min_wager', 'max_wager', 'daily_win_cap', 'payout_config',
];
protected $casts = [
'is_active' => 'boolean',
'is_pvp' => 'boolean',
'payout_config' => 'array', // auto JSON encode/decode
];
public function edition(): BelongsTo { ... }
public function sessions(): HasMany { return $this->hasMany(ArcadeSession::class); }
public function toSearchableArray(): array {
return ['name' => $this->name, 'slug' => $this->slug];
}
}
ArcadeSession
class ArcadeSession extends Model
{
use HasFactory;
protected $fillable = [
'enrollment_id', 'arcade_game_id', 'challenger_enrollment_id',
'wager', 'outcome', 'payout', 'server_state', 'started_at', 'resolved_at',
];
protected $casts = [
'server_state' => 'array',
'started_at' => 'datetime',
'resolved_at' => 'datetime',
];
public function enrollment(): BelongsTo { ... }
public function challenger(): BelongsTo {
return $this->belongsTo(Enrollment::class, 'challenger_enrollment_id');
}
public function game(): BelongsTo { return $this->belongsTo(ArcadeGame::class, 'arcade_game_id'); }
// Scopes
public function scopePending(Builder $q): Builder { return $q->where('outcome', 'pending'); }
public function scopeForEnrollment(Builder $q, int $enrollmentId): Builder {
return $q->where('enrollment_id', $enrollmentId);
}
public function scopeWonToday(Builder $q, int $enrollmentId, int $gameId): Builder {
return $q->where('enrollment_id', $enrollmentId)
->where('arcade_game_id', $gameId)
->where('outcome', 'win')
->whereDate('created_at', today());
}
}
Also add to Enrollment model:
public function arcadeSessions(): HasMany {
return $this->hasMany(ArcadeSession::class);
}
Acceptance Criteria
-
ArcadeGame::with('sessions')->find($id)works -
ArcadeGame->payout_configreturns a PHP array (not a JSON string) -
ArcadeSession::pending()->get()returns only pending sessions -
ArcadeSession::wonToday($eId, $gId)->sum('payout')works for daily cap check -
Enrollment->arcadeSessionsrelationship exists
Dependencies
- #458 (migrations must exist before models can be tested)
- 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 · 1 assignee ·
All issues in semanadeinformatica/website
Similar issues
-
Difficulty 2/5 1-3 hours Newbie friendliness 82/100
Maintainers usually reply within 2 days
-
desktop v3 windows
Difficulty 2/5 1-3 hours Newbie friendliness 75/100
esengine/DeepSeek-Reasonix#15016 · 1 comment ·
Maintainers usually reply within 1 day
-
kind/bug priority/medium qa_automation
Difficulty 2/5 1-3 hours Newbie friendliness 78/100
yugabyte/yugabyte-db#37324 ·
Maintainers usually reply within 1 day
-
Difficulty 2/5 1-3 hours Newbie friendliness 76/100
balovess/aria2_rust#88 ·
-
good first issue
Difficulty 1/5 Under an hour Newbie friendliness 87/100
btclib-org/btclib-node#2260 ·
Maintainers usually reply within 1 day