Skip to content

Commit

Permalink
Merge pull request #89 from Simoneu01/main
Browse files Browse the repository at this point in the history
Allow model customisation
  • Loading branch information
cjmellor authored Oct 24, 2024
2 parents 1db8a0a + e500202 commit 2d388a0
Show file tree
Hide file tree
Showing 12 changed files with 95 additions and 26 deletions.
12 changes: 12 additions & 0 deletions config/level-up.php
Original file line number Diff line number Diff line change
@@ -1,6 +1,18 @@
<?php

return [

'models' => [
'achievement' => LevelUp\Experience\Models\Achievement::class,
'activity' => LevelUp\Experience\Models\Activity::class,
'experience' => LevelUp\Experience\Models\Experience::class,
'experience_audit' => LevelUp\Experience\Models\ExperienceAudit::class,
'level' => LevelUp\Experience\Models\Level::class,
'streak' => LevelUp\Experience\Models\Streak::class,
'streak_history' => LevelUp\Experience\Models\StreakHistory::class,
'achievement_user' => LevelUp\Experience\Models\Pivots\AchievementUser::class,
],

/*
|--------------------------------------------------------------------------
| User Foreign Key
Expand Down
17 changes: 10 additions & 7 deletions src/Concerns/GiveExperience.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@
use LevelUp\Experience\Events\PointsIncreased;
use LevelUp\Experience\Events\UserLevelledUp;
use LevelUp\Experience\Models\Experience;
use LevelUp\Experience\Models\ExperienceAudit;
use LevelUp\Experience\Models\Level;
use LevelUp\Experience\Services\MultiplierService;

Expand All @@ -34,7 +33,9 @@ public function addPoints(
$type = AuditType::Add->value;
}

$lastLevel = Level::orderByDesc(column: 'level')->first();
$levelClass = config(key: 'level-up.models.level');

$lastLevel = $levelClass::orderByDesc(column: 'level')->first();
throw_if(
condition: isset($lastLevel->next_level_experience) && $amount > $lastLevel->next_level_experience,
message: 'Points exceed the last level\'s experience points.',
Expand Down Expand Up @@ -63,7 +64,7 @@ public function addPoints(
* If the User does not have an Experience record, create one.
*/
if ($this->experience()->doesntExist()) {
$level = Level::query()
$level = $levelClass::query()
->where(column: 'next_level_experience', operator: '<=', value: $amount)
->orderByDesc(column: 'next_level_experience')
->first();
Expand Down Expand Up @@ -118,7 +119,7 @@ protected function getMultipliers(int $amount): int

public function experience(): HasOne
{
return $this->hasOne(related: Experience::class);
return $this->hasOne(related: config('level-up.models.experience'));
}

protected function dispatchEvent(int $amount, string $type, ?string $reason): void
Expand Down Expand Up @@ -146,7 +147,7 @@ public function getLevel(): int

public function experienceHistory(): HasMany
{
return $this->hasMany(related: ExperienceAudit::class);
return $this->hasMany(related: config('level-up.models.experience_audit'));
}

public function deductPoints(int $amount, ?string $reason = null): Experience
Expand Down Expand Up @@ -196,7 +197,9 @@ public function withMultiplierData(array|callable $data): static

public function nextLevelAt(?int $checkAgainst = null, bool $showAsPercentage = false): int
{
$nextLevel = Level::firstWhere(column: 'level', operator: '=', value: is_null($checkAgainst) ? $this->getLevel() + 1 : $checkAgainst);
$levelClass = config(key: 'level-up.models.level');

$nextLevel = $levelClass::firstWhere(column: 'level', operator: '=', value: is_null($checkAgainst) ? $this->getLevel() + 1 : $checkAgainst);

if ($this->levelCapExceedsUserLevel()) {
return 0;
Expand All @@ -206,7 +209,7 @@ public function nextLevelAt(?int $checkAgainst = null, bool $showAsPercentage =
return 0;
}

$currentLevelExperience = Level::firstWhere(column: 'level', operator: '=', value: $this->getLevel())->next_level_experience;
$currentLevelExperience = $levelClass::firstWhere(column: 'level', operator: '=', value: $this->getLevel())->next_level_experience;

if ($showAsPercentage) {
return (int) ((($this->getPoints() - $currentLevelExperience) / ($nextLevel->next_level_experience - $currentLevelExperience)) * 100);
Expand Down
11 changes: 5 additions & 6 deletions src/Concerns/HasAchievements.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@
use LevelUp\Experience\Events\AchievementAwarded;
use LevelUp\Experience\Events\AchievementProgressionIncreased;
use LevelUp\Experience\Models\Achievement;
use LevelUp\Experience\Models\Pivots\AchievementUser;

trait HasAchievements
{
Expand All @@ -33,11 +32,11 @@ public function grantAchievement(Achievement $achievement, $progress = null): vo

public function achievements(): BelongsToMany
{
return $this->belongsToMany(related: Achievement::class)
return $this->belongsToMany(related: config(key: 'level-up.models.achievement'))
->withTimestamps()
->withPivot(columns: 'progress')
->where('is_secret', false)
->using(AchievementUser::class);
->using(config(key: 'level-up.models.achievement_user'));
}

public function incrementAchievementProgress(Achievement $achievement, int $amount = 1)
Expand All @@ -53,21 +52,21 @@ public function incrementAchievementProgress(Achievement $achievement, int $amou

public function allAchievements(): BelongsToMany
{
return $this->belongsToMany(related: Achievement::class)
return $this->belongsToMany(related: config(key: 'level-up.models.achievement'))
->withPivot(columns: 'progress');
}

public function achievementsWithProgress(): BelongsToMany
{
return $this->belongsToMany(related: Achievement::class)
return $this->belongsToMany(related: config(key: 'level-up.models.achievement'))
->withPivot(columns: 'progress')
->where('is_secret', false)
->wherePivotNotNull(column: 'progress');
}

public function secretAchievements(): BelongsToMany
{
return $this->belongsToMany(related: Achievement::class)
return $this->belongsToMany(related: config(key: 'level-up.models.achievement'))
->withPivot(columns: 'progress')
->where('is_secret', true);
}
Expand Down
9 changes: 5 additions & 4 deletions src/Concerns/HasStreaks.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@
use LevelUp\Experience\Events\StreakUnfroze;
use LevelUp\Experience\Models\Activity;
use LevelUp\Experience\Models\Streak;
use LevelUp\Experience\Models\StreakHistory;

trait HasStreaks
{
Expand Down Expand Up @@ -68,7 +67,7 @@ protected function hasStreakForActivity(Activity $activity): bool

public function streaks(): HasMany
{
return $this->hasMany(related: Streak::class);
return $this->hasMany(related: config('level-up.models.streak'));
}

protected function startNewStreak(Activity $activity): Model|Streak
Expand Down Expand Up @@ -112,7 +111,9 @@ protected function archiveStreak(Activity $activity): void
{
$latestStreak = $this->getStreakLastActivity($activity);

StreakHistory::create([
$streakHistoryClass = config(key: 'level-up.models.streak_history');

$streakHistoryClass::create([
config(key: 'level-up.streaks.foreign_key', default: 'user_id') => $this->id,
'activity_id' => $activity->id,
'count' => $latestStreak->count,
Expand Down Expand Up @@ -150,7 +151,7 @@ public function freezeStreak(Activity $activity, ?int $days = null): bool

public function unFreezeStreak(Activity $activity): bool
{
Event::dispatch(new StreakUnfroze());
Event::dispatch(new StreakUnfroze);

return $this->getStreakLastActivity($activity)
->update(['frozen_until' => null]);
Expand Down
2 changes: 1 addition & 1 deletion src/LevelUpServiceProvider.php
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ public function register(): void
parent::register();

$this->app->register(provider: EventServiceProvider::class);
$this->app->singleton(abstract: 'leaderboard', concrete: fn () => new LeaderboardService());
$this->app->singleton(abstract: 'leaderboard', concrete: fn () => new LeaderboardService);
$this->app->register(provider: MultiplierServiceProvider::class);
}
}
10 changes: 6 additions & 4 deletions src/Listeners/PointsIncreasedListener.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ class PointsIncreasedListener
{
public function __invoke(PointsIncreased $event): void
{
$levelModel = config(key: 'level-up.models.level');

if (config(key: 'level-up.audit.enabled')) {
$event->user->experienceHistory()->create([
'points' => $event->pointsAdded,
Expand All @@ -17,15 +19,15 @@ public function __invoke(PointsIncreased $event): void
]);
}

if (Level::count() === 0) {
Level::add([
if ($levelModel::count() === 0) {
$levelModel::add([
'level' => config(key: 'level-up.starting_level'),
'next_level_experience' => null,
]);
}

// Get the next level experience needed for the user's current level
$nextLevel = Level::firstWhere(column: 'level', operator: '=', value: $event->user->getLevel() + 1);
$nextLevel = $levelModel::firstWhere(column: 'level', operator: '=', value: $event->user->getLevel() + 1);

if (! $nextLevel) {
// If there is no next level, return
Expand All @@ -35,7 +37,7 @@ public function __invoke(PointsIncreased $event): void
// Check if user's points are equal or greater than the next level's required experience
if ($event->user->getPoints() >= $nextLevel->next_level_experience) {
// Find the highest level the user can achieve with current points
$highestAchievableLevel = Level::query()
$highestAchievableLevel = $levelModel::query()
->where(column: 'next_level_experience', operator: '<=', value: $event->user->getPoints())
->orderByDesc(column: 'level')
->first();
Expand Down
2 changes: 1 addition & 1 deletion src/Models/Activity.php
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,6 @@ class Activity extends Model

public function streaks(): HasMany
{
return $this->hasMany(related: Streak::class);
return $this->hasMany(related: config(key: 'level-up.models.streak'));
}
}
2 changes: 1 addition & 1 deletion src/Models/Experience.php
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,6 @@ public function user(): BelongsTo

public function status(): BelongsTo
{
return $this->belongsTo(related: Level::class, foreignKey: 'level_id');
return $this->belongsTo(related: config('level-up.models.level'), foreignKey: 'level_id');
}
}
2 changes: 1 addition & 1 deletion src/Models/Streak.php
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,6 @@ public function user(): BelongsTo

public function activity(): BelongsTo
{
return $this->belongsTo(related: Activity::class);
return $this->belongsTo(related: config(key: 'level-up.models.activity'));
}
}
11 changes: 11 additions & 0 deletions tests/Fixtures/Level.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<?php

namespace LevelUp\Experience\Tests\Fixtures;

class Level extends \LevelUp\Experience\Models\Level
{
public function extra_function(): string
{
return 'extra_function';
}
}
41 changes: 41 additions & 0 deletions tests/Models/LevelTestWithCustomModel.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
<?php

use LevelUp\Experience\Exceptions\LevelExistsException;

uses()->group('levels');

beforeEach(function (): void {
config()->set('level-up.models.level', \LevelUp\Experience\Tests\Fixtures\Level::class);
});

it(description: 'is a custom model', closure: function (): void {
$levelClass = config('level-up.models.level');

$level = $levelClass::add([
'level' => 6,
'next_level_experience' => 750,
]);

expect(value: $level[0])->toBeInstanceOf($levelClass);
expect(value: $level[0]->extra_function())->toBe(expected: 'extra_function');
});

it(description: 'can create a level with custom model', closure: function (): void {

$level = config('level-up.models.level')::add([
'level' => 6,
'next_level_experience' => 750,
]);

expect(value: $level[0]->level)->toBe(expected: 6);

$this->assertDatabaseHas(table: 'levels', data: [
'level' => 6,
'next_level_experience' => 750,
]);
});

it(description: 'throws an error if a level exists with custom model', closure: function (): void {
config('level-up.models.level')::add(level: 1, pointsToNextLevel: 100);
config('level-up.models.level')::add(level: 1, pointsToNextLevel: 100);
})->throws(exception: LevelExistsException::class, exceptionMessage: 'The level with number "1" already exists');
2 changes: 1 addition & 1 deletion tests/Pest.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@

uses(TestCase::class, FastRefreshDatabase::class)
->beforeEach(hook: function (): void {
$this->user = new User();
$this->user = new User;

$this->user->fill(attributes: [
'name' => 'Chris Mellor',
Expand Down

0 comments on commit 2d388a0

Please sign in to comment.