Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
4a3d9ea
feat(config): add table_prefix and tables array (defaults)
cjmellor May 12, 2026
5f05191
style(config): apply Pint and fix comment spacing
cjmellor May 12, 2026
cc3dc91
feat(config): resolve table names at service-provider boot
cjmellor May 12, 2026
3f1e118
feat(concerns): add ResolvesConfiguredTable trait
cjmellor May 12, 2026
f93e5fe
refactor(models): Experience and Activity use ResolvesConfiguredTable…
cjmellor May 12, 2026
da4fa94
refactor(models): all package models use ResolvesConfiguredTable trait
cjmellor May 12, 2026
b349286
feat(migrations): resolve create-table names and FK targets from config
cjmellor May 12, 2026
cc7cb3a
feat(migrations): resolve alter/add migration table targets and FK re…
cjmellor May 12, 2026
48ac0e2
test(migrations): integration test for prefixed tables and FK resolution
cjmellor May 12, 2026
9f542a0
docs(tests): explain Testbench timing in TablePrefixIntegrationTest
cjmellor May 12, 2026
5483476
revert: keep Pint rule enabled; drop comment it strips
cjmellor May 13, 2026
1877a68
docs: configurable table names + deprecation note
cjmellor May 13, 2026
1f959f4
fix(config): treat empty-string overrides as unset in table resolver
cjmellor May 13, 2026
e288bad
fix(relations): resolve pivot table names through config
cjmellor May 13, 2026
8811370
refactor(concerns): extract achievementsRelation() helper in HasAchie…
cjmellor May 13, 2026
2e77ab6
fix(challenges): resolve table names in qualified column queries
cjmellor May 14, 2026
33630b0
fix(multipliers): use MorphPivot so configurable IDs apply to scopes
cjmellor May 17, 2026
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
8 changes: 8 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,14 @@

All notable changes to `level-up` will be documented in this file.

## [Unreleased]

### Added
- Configurable table names: new `table_prefix` config key applies a prefix to every package table; new `tables` array allows per-table overrides for all 13 package tables. See the README "Customizing Table Names" section for usage.

### Deprecated
- The top-level `'table'` config key is deprecated in favour of `'tables.experiences'`. The old key still works as a fallback for existing installs — no action required.

## v2.0.0 - 2026-04-03

### Added
Expand Down
50 changes: 50 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,56 @@ return [
];
```

# Customizing Table Names

If you're installing into an app that already has tables called `experiences`, `levels`, `tiers`, `multipliers`, `challenges`, or any of the package's other defaults, you can rename them via config — no need to patch published migrations.

**Option 1 — Apply a single prefix to every package table:**

Set an env var (no config publish required):

```dotenv
LEVEL_UP_TABLE_PREFIX=levelup_
```

…or publish the config and edit the prefix line:

```bash
php artisan vendor:publish --tag=level-up-config
```

```php
// config/level-up.php
'table_prefix' => 'levelup_',
```

All package tables now use the prefix: `levelup_experiences`, `levelup_levels`, `levelup_tiers`, `levelup_multipliers`, `levelup_challenges`, and so on.

**Option 2 — Rename specific tables:**

Edit the `tables` array in the published config:

```php
'tables' => [
'experiences' => 'xp_log', // renamed
'levels' => 'user_tiers', // renamed
'tiers' => 'rank_brackets', // renamed
// leave the rest at their defaults
],
```

**Combining both:** any value left equal to the default receives the `table_prefix`; any value you change is taken verbatim and the prefix is NOT applied. This lets you prefix everything but override one or two outliers:

```php
'table_prefix' => 'lvl_',
'tables' => [
'levels' => 'xp_levels', // → 'xp_levels' (NO prefix)
// others stay default → all become 'lvl_<default>'
],
```

> **Upgrading from v1.x or earlier v2:** the previous top-level `'table'` config key (used to override only the experiences table) still works as a fallback. New installations should prefer `'tables.experiences'` instead.

# Usage

## 💯 Experience Points (XP)
Expand Down
6 changes: 6 additions & 0 deletions UPGRADE.md
Original file line number Diff line number Diff line change
Expand Up @@ -184,6 +184,12 @@ class User extends Model

The package's own primary keys can now be configured as `uuid` or `ulid` via `level-up.entities.id_type` (default remains `bigint`). Existing installs are unaffected on `composer update` — the setting only changes how *new* migrations build their tables. See the [Customizing Identifiers section in the README](README.md#customizing-identifiers) for the conversion path if you want to migrate an existing install.

### New: Configurable Table Names

**Likelihood of Impact: Low**

A new `table_prefix` and `tables` config block lets you rename any of the package's 13 tables. The previous top-level `'table'` key is now deprecated — if you customised it before, it still works as a fallback for `tables.experiences`. No action required; consider migrating to `tables.experiences` on your next config publish. See the [Customizing Table Names section in the README](README.md#customizing-table-names) for examples.

### Bug Fixes Included

- `levelUp()` now correctly fires `UserLevelledUp` events for all intermediate levels (previously only fired for the final level)
Expand Down
43 changes: 41 additions & 2 deletions config/level-up.php
Original file line number Diff line number Diff line change
Expand Up @@ -62,10 +62,49 @@

/*
|--------------------------------------------------------------------------
| Experience Table
| Table Prefix
|--------------------------------------------------------------------------
|
| This value is the name of the table that will be used to store experience data.
| Prepended to every default package table name. Leave empty for no prefix.
| Per-table overrides in 'tables' below are taken verbatim and are NOT
| prefixed.
|
*/
'table_prefix' => env('LEVEL_UP_TABLE_PREFIX', ''),

/*
|--------------------------------------------------------------------------
| Tables
|--------------------------------------------------------------------------
|
| The table name used for each of the package's models. Leave a value
| equal to the default to apply table_prefix above; set it to any other
| string to override that table's name exactly (no prefix applied).
|
*/
'tables' => [
'experiences' => 'experiences',
'experience_audits' => 'experience_audits',
'levels' => 'levels',
'achievements' => 'achievements',
'achievement_user' => 'achievement_user',
'streaks' => 'streaks',
'streak_histories' => 'streak_histories',
'streak_activities' => 'streak_activities',
'tiers' => 'tiers',
'multipliers' => 'multipliers',
'multiplier_scopes' => 'multiplier_scopes',
'challenges' => 'challenges',
'challenge_user' => 'challenge_user',
],

/*
|--------------------------------------------------------------------------
| Experience Table (deprecated — use tables.experiences)
|--------------------------------------------------------------------------
|
| Kept for backwards compatibility. If you customised this key in v1.x,
| it still works. New installs should prefer 'tables.experiences' above.
|
*/
'table' => 'experiences',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ return new class extends Migration {
$table->entityForeignId('level_id')
->after('remember_token')
->nullable()
->constrained();
->constrained(table: config('level-up.tables.levels'));
});
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,14 @@ use Illuminate\Support\Facades\Schema;
return new class extends Migration {
public function up(): void
{
Schema::table('experience_audits', function (Blueprint $table) {
Schema::table(config('level-up.tables.experience_audits'), function (Blueprint $table) {
$table->json('multipliers')->nullable()->after('reason');
});
}

public function down(): void
{
Schema::table('experience_audits', function (Blueprint $table) {
Schema::table(config('level-up.tables.experience_audits'), function (Blueprint $table) {
$table->dropColumn('multipliers');
});
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ use Illuminate\Support\Facades\Schema;
return new class extends Migration {
public function up(): void
{
Schema::table('streaks', function (Blueprint $table) {
Schema::table(config('level-up.tables.streaks'), function (Blueprint $table) {
$table->after('activity_at', function (Blueprint $table) {
$table->timestamp('frozen_until')->nullable();
});
Expand All @@ -16,7 +16,7 @@ return new class extends Migration {

public function down(): void
{
Schema::table('streaks', function (Blueprint $table) {
Schema::table(config('level-up.tables.streaks'), function (Blueprint $table) {
$table->dropColumn('frozen_until');
});
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,14 @@ use Illuminate\Support\Facades\Schema;
return new class extends Migration {
public function up(): void
{
Schema::table('achievements', function (Blueprint $table) {
$table->entityForeignId('tier_id')->nullable()->constrained('tiers')->nullOnDelete();
Schema::table(config('level-up.tables.achievements'), function (Blueprint $table) {
$table->entityForeignId('tier_id')->nullable()->constrained(table: config('level-up.tables.tiers'))->nullOnDelete();
});
}

public function down(): void
{
Schema::table('achievements', function (Blueprint $table) {
Schema::table(config('level-up.tables.achievements'), function (Blueprint $table) {
$table->dropConstrainedForeignId('tier_id');
});
}
Expand Down
6 changes: 3 additions & 3 deletions database/migrations/add_tier_id_to_experiences_table.php.stub
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,14 @@ use Illuminate\Support\Facades\Schema;
return new class extends Migration {
public function up(): void
{
Schema::table(config('level-up.table'), function (Blueprint $table) {
$table->entityForeignId('tier_id')->nullable()->constrained('tiers')->nullOnDelete();
Schema::table(config('level-up.tables.experiences'), function (Blueprint $table) {
$table->entityForeignId('tier_id')->nullable()->constrained(table: config('level-up.tables.tiers'))->nullOnDelete();
});
}

public function down(): void
{
Schema::table(config('level-up.table'), function (Blueprint $table) {
Schema::table(config('level-up.tables.experiences'), function (Blueprint $table) {
$table->dropConstrainedForeignId('tier_id');
});
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,14 @@ use Illuminate\Support\Facades\Schema;
return new class extends Migration {
public function up(): void
{
Schema::table('experience_audits', function (Blueprint $table) {
Schema::table(config('level-up.tables.experience_audits'), function (Blueprint $table) {
$table->string('type')->change();
});
}

public function down(): void
{
Schema::table('experience_audits', function (Blueprint $table) {
Schema::table(config('level-up.tables.experience_audits'), function (Blueprint $table) {
$table->enum('type', ['add', 'remove', 'reset', 'level_up', 'tier_up', 'tier_down'])->change();
});
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,17 +8,17 @@ use LevelUp\Experience\Support\UserForeignKey;
return new class extends Migration {
public function up(): void
{
Schema::create('achievement_user', function (Blueprint $table) {
Schema::create(config('level-up.tables.achievement_user'), function (Blueprint $table) {
$table->entityId();
UserForeignKey::on($table)->constrained(config('level-up.user.users_table'));
$table->entityForeignId(column: 'achievement_id')->constrained();
$table->entityForeignId(column: 'achievement_id')->constrained(table: config('level-up.tables.achievements'));
$table->integer(column: 'progress')->nullable()->index();
$table->timestamps();
});
}

public function down(): void
{
Schema::dropIfExists('achievement_user');
Schema::dropIfExists(config('level-up.tables.achievement_user'));
}
};
4 changes: 2 additions & 2 deletions database/migrations/create_achievements_table.php.stub
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ use Illuminate\Support\Facades\Schema;
return new class extends Migration {
public function up(): void
{
Schema::create('achievements', function (Blueprint $table) {
Schema::create(config('level-up.tables.achievements'), function (Blueprint $table) {
$table->entityId();
$table->string('name');
$table->boolean('is_secret')->default(false);
Expand All @@ -19,6 +19,6 @@ return new class extends Migration {

public function down(): void
{
Schema::dropIfExists('achievements');
Schema::dropIfExists(config('level-up.tables.achievements'));
}
};
6 changes: 3 additions & 3 deletions database/migrations/create_challenge_user_table.php.stub
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,10 @@ use LevelUp\Experience\Support\UserForeignKey;
return new class extends Migration {
public function up(): void
{
Schema::create('challenge_user', function (Blueprint $table) {
Schema::create(config('level-up.tables.challenge_user'), function (Blueprint $table) {
$table->entityId();
UserForeignKey::on($table)->constrained(config('level-up.user.users_table'));
$table->entityForeignId(column: 'challenge_id')->constrained();
$table->entityForeignId(column: 'challenge_id')->constrained(table: config('level-up.tables.challenges'));
$table->json(column: 'progress')->nullable();
$table->timestamp(column: 'completed_at')->nullable();
$table->timestamps();
Expand All @@ -23,6 +23,6 @@ return new class extends Migration {

public function down(): void
{
Schema::dropIfExists('challenge_user');
Schema::dropIfExists(config('level-up.tables.challenge_user'));
}
};
4 changes: 2 additions & 2 deletions database/migrations/create_challenges_table.php.stub
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ use Illuminate\Support\Facades\Schema;
return new class extends Migration {
public function up(): void
{
Schema::create('challenges', function (Blueprint $table) {
Schema::create(config('level-up.tables.challenges'), function (Blueprint $table) {
$table->entityId();
$table->string(column: 'name');
$table->text(column: 'description')->nullable();
Expand All @@ -25,6 +25,6 @@ return new class extends Migration {

public function down(): void
{
Schema::dropIfExists('challenges');
Schema::dropIfExists(config('level-up.tables.challenges'));
}
};
4 changes: 2 additions & 2 deletions database/migrations/create_experience_audits_table.php.stub
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ use LevelUp\Experience\Support\UserForeignKey;
return new class extends Migration {
public function up(): void
{
Schema::create('experience_audits', function (Blueprint $table) {
Schema::create(config('level-up.tables.experience_audits'), function (Blueprint $table) {
$table->entityId();
UserForeignKey::on($table)->constrained(config('level-up.user.users_table'));
$table->integer('points')->index();
Expand All @@ -22,6 +22,6 @@ return new class extends Migration {

public function down(): void
{
Schema::dropIfExists('experience_audits');
Schema::dropIfExists(config('level-up.tables.experience_audits'));
}
};
6 changes: 3 additions & 3 deletions database/migrations/create_experiences_table.php.stub
Original file line number Diff line number Diff line change
Expand Up @@ -9,17 +9,17 @@ return new class extends Migration
{
public function up()
{
Schema::create(config('level-up.table'), function (Blueprint $table) {
Schema::create(config('level-up.tables.experiences'), function (Blueprint $table) {
$table->entityId();
UserForeignKey::on($table)->constrained(config('level-up.user.users_table'));
$table->entityForeignId('level_id')->constrained();
$table->entityForeignId('level_id')->constrained(table: config('level-up.tables.levels'));
$table->integer('experience_points')->default(0)->index();
$table->timestamps();
});
}

public function down()
{
Schema::dropIfExists(config('level-up.table'));
Schema::dropIfExists(config('level-up.tables.experiences'));
}
};
4 changes: 2 additions & 2 deletions database/migrations/create_levels_table.php.stub
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ return new class extends Migration
{
public function up()
{
Schema::create('levels', function (Blueprint $table) {
Schema::create(config('level-up.tables.levels'), function (Blueprint $table) {
$table->entityId();
$table->integer('level')->unique();
$table->integer('next_level_experience')->nullable()->index();
Expand All @@ -18,6 +18,6 @@ return new class extends Migration

public function down()
{
Schema::dropIfExists('levels');
Schema::dropIfExists(config('level-up.tables.levels'));
}
};
6 changes: 3 additions & 3 deletions database/migrations/create_multiplier_scopes_table.php.stub
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,9 @@ use Illuminate\Support\Facades\Schema;
return new class extends Migration {
public function up(): void
{
Schema::create('multiplier_scopes', function (Blueprint $table) {
Schema::create(config('level-up.tables.multiplier_scopes'), function (Blueprint $table) {
$table->entityId();
$table->entityForeignId('multiplier_id')->constrained()->cascadeOnDelete();
$table->entityForeignId('multiplier_id')->constrained(table: config('level-up.tables.multipliers'))->cascadeOnDelete();
$table->string('scopeable_type');
$table->string('scopeable_id');
$table->timestamps();
Expand All @@ -21,6 +21,6 @@ return new class extends Migration {

public function down(): void
{
Schema::dropIfExists('multiplier_scopes');
Schema::dropIfExists(config('level-up.tables.multiplier_scopes'));
}
};
Loading
Loading