Skip to content
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
7 changes: 7 additions & 0 deletions config/level-up.php
Original file line number Diff line number Diff line change
Expand Up @@ -26,10 +26,17 @@
|--------------------------------------------------------------------------
|
| This value is the foreign key that will be used to relate the Experience model to the User model.
|
| 'foreign_key_type' controls the DB column type used for the user FK on
| every package table. Set to 'uuid' or 'ulid' if your host User model
| uses HasUuids / HasUlids. Leave as 'bigint' for standard auto-increment
| user IDs. This only affects fresh migrations; existing installs keep
| whichever column type they originally migrated with.
|
*/
'user' => [
'foreign_key' => 'user_id',
'foreign_key_type' => 'bigint',
'model' => App\Models\User::class,
'users_table' => 'users',
],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,14 @@
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
use LevelUp\Experience\Support\UserForeignKey;

return new class extends Migration {
public function up(): void
{
Schema::create('achievement_user', function (Blueprint $table) {
$table->entityId();
$table->foreignId(column: config('level-up.user.foreign_key'))->constrained(config('level-up.user.users_table'));
UserForeignKey::on($table)->constrained(config('level-up.user.users_table'));
$table->entityForeignId(column: 'achievement_id')->constrained();
$table->integer(column: 'progress')->nullable()->index();
$table->timestamps();
Expand Down
3 changes: 2 additions & 1 deletion database/migrations/create_challenge_user_table.php.stub
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,14 @@
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
use LevelUp\Experience\Support\UserForeignKey;

return new class extends Migration {
public function up(): void
{
Schema::create('challenge_user', function (Blueprint $table) {
$table->entityId();
$table->foreignId(column: config('level-up.user.foreign_key'))->constrained(config('level-up.user.users_table'));
UserForeignKey::on($table)->constrained(config('level-up.user.users_table'));
$table->entityForeignId(column: 'challenge_id')->constrained();
$table->json(column: 'progress')->nullable();
$table->timestamp(column: 'completed_at')->nullable();
Expand Down
3 changes: 2 additions & 1 deletion database/migrations/create_experience_audits_table.php.stub
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,14 @@
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
use LevelUp\Experience\Support\UserForeignKey;

return new class extends Migration {
public function up(): void
{
Schema::create('experience_audits', function (Blueprint $table) {
$table->entityId();
$table->foreignId(config('level-up.user.foreign_key'))->constrained(config('level-up.user.users_table'));
UserForeignKey::on($table)->constrained(config('level-up.user.users_table'));
$table->integer('points')->index();
$table->boolean('levelled_up')->default(false);
$table->integer('level_to')->nullable();
Expand Down
3 changes: 2 additions & 1 deletion database/migrations/create_experiences_table.php.stub
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,15 @@
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
use LevelUp\Experience\Support\UserForeignKey;

return new class extends Migration
{
public function up()
{
Schema::create(config('level-up.table'), function (Blueprint $table) {
$table->entityId();
$table->foreignId(config('level-up.user.foreign_key'))->constrained(config('level-up.user.users_table'));
UserForeignKey::on($table)->constrained(config('level-up.user.users_table'));
$table->entityForeignId('level_id')->constrained();
$table->integer('experience_points')->default(0)->index();
$table->timestamps();
Expand Down
3 changes: 2 additions & 1 deletion database/migrations/create_streak_histories_table.php.stub
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,15 @@
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
use LevelUp\Experience\Support\UserForeignKey;

return new class extends Migration
{
public function up(): void
{
Schema::create(table: 'streak_histories', callback: function (Blueprint $table) {
$table->entityId();
$table->foreignId(column: config(key: 'level-up.user.foreign_key'))->constrained(table: config(key: 'level-up.user.users_table'))->cascadeOnDelete();
UserForeignKey::on(table: $table)->constrained(table: config(key: 'level-up.user.users_table'))->cascadeOnDelete();
$table->entityForeignId(column: 'activity_id')->constrained(table: 'streak_activities');
$table->integer(column: 'count')->default(value: 1);
$table->timestamp(column: 'started_at');
Expand Down
3 changes: 2 additions & 1 deletion database/migrations/create_streaks_table.php.stub
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,14 @@
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
use LevelUp\Experience\Support\UserForeignKey;

return new class extends Migration {
public function up(): void
{
Schema::create('streaks', function (Blueprint $table) {
$table->entityId();
$table->foreignId(column: config('level-up.user.foreign_key'))->constrained()->onDelete('cascade');
UserForeignKey::on($table)->constrained()->onDelete('cascade');
$table->entityForeignId(column: 'activity_id')->constrained('streak_activities')->onDelete('cascade');
$table->integer(column: 'count')->default(1);
$table->timestamp(column: 'activity_at');
Expand Down
2 changes: 2 additions & 0 deletions rector.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
use Rector\Config\RectorConfig;
use Rector\Php83\Rector\ClassMethod\AddOverrideAttributeToOverriddenMethodsRector;
use RectorLaravel\Rector\Class_\AddHasFactoryToModelsRector;
use RectorLaravel\Rector\Class_\TablePropertyToTableAttributeRector;
use RectorLaravel\Rector\ClassMethod\MakeModelAttributesAndScopesProtectedRector;
use RectorLaravel\Rector\FuncCall\RemoveDumpDataDeadCodeRector;
use RectorLaravel\Set\LaravelSetList;
Expand All @@ -21,6 +22,7 @@
AddOverrideAttributeToOverriddenMethodsRector::class,
AddHasFactoryToModelsRector::class,
MakeModelAttributesAndScopesProtectedRector::class,
TablePropertyToTableAttributeRector::class,
])
->withPreparedSets(
deadCode: true,
Expand Down
27 changes: 27 additions & 0 deletions src/Support/UserForeignKey.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
<?php

declare(strict_types=1);

namespace LevelUp\Experience\Support;

use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Schema\ForeignIdColumnDefinition;
use InvalidArgumentException;

class UserForeignKey
{
public static function on(Blueprint $table, ?string $column = null): ForeignIdColumnDefinition
{
$column ??= config('level-up.user.foreign_key', 'user_id');
$type = config('level-up.user.foreign_key_type', 'bigint');

return match ($type) {
'bigint' => $table->foreignId($column),
'uuid' => $table->foreignUuid($column),
'ulid' => $table->foreignUlid($column),
default => throw new InvalidArgumentException(
"Unknown level-up.user.foreign_key_type [{$type}]. Expected 'bigint', 'uuid', or 'ulid'."
),
};
}
}
32 changes: 32 additions & 0 deletions tests/Fixtures/UlidUser.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
<?php

declare(strict_types=1);

namespace LevelUp\Experience\Tests\Fixtures;

use Illuminate\Database\Eloquent\Concerns\HasUlids;
use Illuminate\Database\Eloquent\Model;
use LevelUp\Experience\Concerns\GiveExperience;
use LevelUp\Experience\Concerns\HasAchievements;
use LevelUp\Experience\Concerns\HasChallenges;
use LevelUp\Experience\Concerns\HasStreaks;
use LevelUp\Experience\Concerns\HasTiers;

class UlidUser extends Model
{
use GiveExperience;
use HasAchievements;
use HasChallenges;
use HasStreaks;
use HasTiers;
use HasUlids;

protected $table = 'users';

protected $guarded = [];

public function getForeignKey(): string
{
return 'user_id';
}
}
32 changes: 32 additions & 0 deletions tests/Fixtures/UuidUser.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
<?php

declare(strict_types=1);

namespace LevelUp\Experience\Tests\Fixtures;

use Illuminate\Database\Eloquent\Concerns\HasUuids;
use Illuminate\Database\Eloquent\Model;
use LevelUp\Experience\Concerns\GiveExperience;
use LevelUp\Experience\Concerns\HasAchievements;
use LevelUp\Experience\Concerns\HasChallenges;
use LevelUp\Experience\Concerns\HasStreaks;
use LevelUp\Experience\Concerns\HasTiers;

class UuidUser extends Model
{
use GiveExperience;
use HasAchievements;
use HasChallenges;
use HasStreaks;
use HasTiers;
use HasUuids;

protected $table = 'users';

protected $guarded = [];

public function getForeignKey(): string
{
return 'user_id';
}
}
65 changes: 55 additions & 10 deletions tests/Pest.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,29 @@
declare(strict_types=1);

use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Support\Collection;
use LevelUp\Experience\Models\Level;
use LevelUp\Experience\Tests\Fixtures\UlidUser;
use LevelUp\Experience\Tests\Fixtures\User;
use LevelUp\Experience\Tests\Fixtures\UuidUser;
use LevelUp\Experience\Tests\TestCase;
use LevelUp\Experience\Tests\UlidUserTestCase;
use LevelUp\Experience\Tests\UuidUserTestCase;

$seedLevels = function (): Collection {
$levels = Level::add(
['level' => 1, 'next_level_experience' => null],
['level' => 2, 'next_level_experience' => 100],
['level' => 3, 'next_level_experience' => 250],
['level' => 4, 'next_level_experience' => 400],
['level' => 5, 'next_level_experience' => 600],
);

return collect($levels)->keyBy('level');
};

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

$this->user->fill(attributes: [
Expand All @@ -18,17 +35,45 @@
'email_verified_at' => now(),
])->save();

$levels = Level::add(
['level' => 1, 'next_level_experience' => null],
['level' => 2, 'next_level_experience' => 100],
['level' => 3, 'next_level_experience' => 250],
['level' => 4, 'next_level_experience' => 400],
['level' => 5, 'next_level_experience' => 600],
);
$this->levels = $seedLevels();
})
->in(__DIR__.'/Concerns', __DIR__.'/Listeners', __DIR__.'/Models', __DIR__.'/Services');

uses(UuidUserTestCase::class, RefreshDatabase::class)
->beforeEach(function () use ($seedLevels): void {
config()->set(key: 'level-up.multiplier.enabled', value: false);
config()->set(key: 'level-up.tiers.enabled', value: false);

$this->user = new UuidUser;

$this->user->fill(attributes: [
'name' => 'UUID User',
'email' => 'uuid@example.test',
'password' => bcrypt(value: 'password'),
'email_verified_at' => now(),
])->save();

$this->levels = $seedLevels();
})
->in(__DIR__.'/Uuid');

uses(UlidUserTestCase::class, RefreshDatabase::class)
->beforeEach(function () use ($seedLevels): void {
config()->set(key: 'level-up.multiplier.enabled', value: false);
config()->set(key: 'level-up.tiers.enabled', value: false);

$this->user = new UlidUser;

$this->user->fill(attributes: [
'name' => 'ULID User',
'email' => 'ulid@example.test',
'password' => bcrypt(value: 'password'),
'email_verified_at' => now(),
])->save();

$this->levels = collect($levels)->keyBy('level');
$this->levels = $seedLevels();
})
->in(__DIR__);
->in(__DIR__.'/Ulid');

expect()->extend(name: 'toBeCarbon', extend: function (string $expected, ?string $format = null): object {
if ($format === null) {
Expand Down
23 changes: 20 additions & 3 deletions tests/TestCase.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
namespace LevelUp\Experience\Tests;

use Illuminate\Database\Eloquent\Factories\Factory;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
use LevelUp\Experience\LevelUpServiceProvider;
use Orchestra\Testbench\TestCase as Orchestra;
Expand Down Expand Up @@ -33,11 +34,12 @@ protected function getPackageProviders($app): array
protected function getEnvironmentSetUp($app): void
{
config()->set('database.default', 'testing');
config()->set('level-up.user.model', \LevelUp\Experience\Tests\Fixtures\User::class);
config()->set('level-up.entities.id_type', env('LEVELUP_TEST_KEY_TYPE', 'bigint'));

Schema::create('users', function ($table): void {
$table->id();
$this->defineUserConfig();

Schema::create('users', function (Blueprint $table): void {
$this->createUserIdColumn($table);
$table->string('name');
$table->string('email')->unique();
$table->timestamp('email_verified_at')->nullable();
Expand Down Expand Up @@ -78,4 +80,19 @@ protected function defineDatabaseMigrations(): void
$migration->up();
}
}

protected function defineUserConfig(): void
{
config()->set('level-up.user.model', \LevelUp\Experience\Tests\Fixtures\User::class);
}

protected function createUserIdColumn(Blueprint $table): void
{
match (config('level-up.user.foreign_key_type', 'bigint')) {
'bigint' => $table->id(),
'uuid' => $table->uuid('id')->primary(),
'ulid' => $table->ulid('id')->primary(),
default => $table->id(),
};
}
}
Loading
Loading