[BE] Eloquent Models — ArcadeGame & ArcadeSession

Closed Beginner friendly
#460 0 comments 0 reactions 0 assignees View on GitHub

@pedroafmonteiro is already working on this.

Since Oct 28, 2026.

  • #480 by @pedroafmonteiro — open

Assessment

Difficulty
2/5
Estimated time
1-3 hours
Newbie friendliness
82/100
Issue type
Feature
Clarity
Clearly specified
Activity status
Active
Tech stack
laravel, php
Domain
backend

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

backend gamification P0

🎮 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)
  1. Points Storage: Points are stored as an unsigned integer in enrollments.points (one row per participant per edition).
  2. PostgreSQL Trigger Caution: The database has existing triggers that modify points when rows are inserted/deleted from enrollment_slot and enrollment_product.
    • NEVER insert into or delete from those pivot tables for arcade games or raffles!
    • Directly mutating enrollments.points via PHP ($enrollment->decrement('points', $wager)) does not fire those triggers and is the only safe way to deduct/credit points.
  3. No Negative Balance Constraint in DB: There is no CHECK (points >= 0) in PostgreSQL. All validation against negative balances must occur in PHP.
  4. 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(...).
  5. 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), ArcadeWagerService security 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_config and server_state as 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_config returns 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->arcadeSessions relationship 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

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

More from semanadeinformatica/website

All issues in semanadeinformatica/website

Similar issues

More Backend & API Design issues

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.