Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow model customisation #89

Merged
merged 6 commits into from
Oct 24, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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