Skip to content

Commit

Permalink
User Management (#153)
Browse files Browse the repository at this point in the history
Co-authored-by: Joel Butcher <[email protected]>
  • Loading branch information
jbrooksuk and joelbutcher authored Jan 15, 2025
1 parent 24ca011 commit 9682337
Show file tree
Hide file tree
Showing 48 changed files with 1,626 additions and 41 deletions.
1 change: 1 addition & 0 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
"illuminate/events": "^11.23.0",
"illuminate/queue": "^11.23.0",
"illuminate/support": "^11.23.0",
"laravel/sanctum": "^4.0",
"nesbot/carbon": "^2.70",
"spatie/laravel-data": "^4.11",
"spatie/laravel-query-builder": "^5.5",
Expand Down
4 changes: 3 additions & 1 deletion config/cachet.php
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,9 @@
| This is the model that will be used to authenticate users. This model
| must be an instance of Illuminate\Foundation\Auth\User.
*/
'user_model' => \App\Models\User::class,
'user_model' => env('CACHET_USER_MODEL', \App\Models\User::class),

'user_migrations' => env('CACHET_USER_MIGRATIONS', true),

/*
|--------------------------------------------------------------------------
Expand Down
44 changes: 44 additions & 0 deletions database/factories/UserFactory.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
<?php

namespace Cachet\Database\Factories;

use Illuminate\Database\Eloquent\Factories\Factory;
use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Str;

/**
* @extends \Illuminate\Database\Eloquent\Factories\Factory<\Cachet\Models\User>
*/
class UserFactory extends Factory
{
/**
* The current password being used by the factory.
*/
protected static ?string $password;

/**
* Define the model's default state.
*
* @return array<string, mixed>
*/
public function definition(): array
{
return [
'name' => fake()->name(),
'email' => fake()->unique()->safeEmail(),
'email_verified_at' => now(),
'password' => static::$password ??= Hash::make('password'),
'remember_token' => Str::random(10),
];
}

/**
* Indicate that the model's email address should be unverified.
*/
public function unverified(): static
{
return $this->state(fn (array $attributes) => [
'email_verified_at' => null,
]);
}
}
33 changes: 33 additions & 0 deletions database/migrations/2025_01_11_090556_create_api_keys_table.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
<?php

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::create('api_keys', function (Blueprint $table) {
$table->id();
$table->string('name')->unique();
$table->string('token_', 64)->unique();
$table->foreignId('user_id')->constrained()->onDelete('cascade');
$table->timestamp('last_used_at')->nullable();
$table->timestamp('expires_at')->nullable();
$table->boolean('revoked_at')->nullable();
$table->timestamps();
});
}

/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::dropIfExists('api_keys');
}
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
<?php

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::table('users', function (Blueprint $table) {
$table->boolean('is_admin')->default(false);
});
}

/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::table('users', function (Blueprint $table) {
$table->dropColumn('is_admin');
});
}
};
1 change: 1 addition & 0 deletions database/seeders/DatabaseSeeder.php
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ public function run(): void
'email' => '[email protected]',
'password' => bcrypt('test123'),
'email_verified_at' => now(),
'is_admin' => true,
]);

Schedule::create([
Expand Down
7 changes: 7 additions & 0 deletions phpstan-baseline.neon
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
parameters:
ignoreErrors:
-
message: '#^Access to an undefined property Laravel\\Sanctum\\PersonalAccessToken\:\:\$expires_at\.$#'
identifier: property.notFound
count: 3
path: src/Filament/Resources/ApiKeyResource.php
1 change: 1 addition & 0 deletions phpstan.neon.dist
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
includes:
- vendor/larastan/larastan/extension.neon
- phpstan-baseline.neon

parameters:
level: 5
Expand Down
31 changes: 31 additions & 0 deletions resources/lang/en/api_key.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
<?php

return [
'resource_label' => 'API Key|API Keys',
'show_token' => [
'heading' => 'Your API Token has been generated',
'description' => 'Please copy your new API token. For your security, it won\'t be shown again.',
'copy_tooltip' => 'Token copied!',
],
'abilities_label' => ':ability :resource',
'form' => [
'name_label' => 'Token Name',
'expires_at_label' => 'Expires At',
'expires_at_helper' => 'Expires at midnight. Leave empty for no expiry',
'expires_at_validation' => 'The expiry date must be in the future',
'abilities_label' => 'Permissions',
'abilities_hint' => 'Leaving this empty will give the token full permissions',
],
'list' => [
'actions' => [
'revoke' => 'Revoke',
],
'headers' => [
'name' => 'Token Name',
'abilities' => 'Permissions',
'created_at' => 'Created At',
'expires_at' => 'Expires At',
'updated_at' => 'Updated At',
],
],
];
1 change: 1 addition & 0 deletions resources/lang/en/navigation.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
'manage_cachet' => 'Manage Cachet',
'manage_customization' => 'Manage Customization',
'manage_theme' => 'Manage Theme',
'manage_api_keys' => 'Manage API Keys',
'manage_webhooks' => 'Manage Webhooks',
],
],
Expand Down
19 changes: 19 additions & 0 deletions resources/lang/en/user.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,23 @@
'admin' => 'Admin',
'user' => 'User',
],
'resource_label' => 'User|Users',
'list' => [
'headers' => [
'name' => 'Name',
'email' => 'Email Address',
'email_verified_at' => 'Email Verified At',
'is_admin' => 'Is Admin?',
],
'actions' => [
'verify_email' => 'Verify Email',
],
],
'form' => [
'name_label' => 'Name',
'email_label' => 'Email Address',
'password_label' => 'Password',
'password_confirmation_label' => 'Confirm Password',
'is_admin_label' => 'Admin',
],
];
27 changes: 27 additions & 0 deletions resources/views/filament/pages/api-key/index.blade.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
<x-filament::page>
@if($token = session('api-token'))
<x-filament::section :heading="__('cachet::api_key.show_token.heading')">
<p class="text-sm leading-6 text-gray-500 dark:text-gray-400">
{{ __('cachet::api_key.show_token.description') }}
</p>

<div class="flex items-center gap-3 mt-3">
<div
style="--c-50:var(--success-50);--c-400:var(--success-400);--c-600:var(--success-600);"
class="fi-badge cursor-pointer inline-block rounded-md px-3 py-2 text-sm ring-1 ring-inset min-w-[theme(spacing.6)] fi-color-custom bg-custom-50 text-custom-600 ring-custom-600/10 dark:bg-custom-400/10 dark:text-custom-400 dark:ring-custom-400/30 fi-color-success"
x-on:click="
window.navigator.clipboard.writeText(@js($token))
$tooltip(@js(__('cachet::api_key.show_token.copy_tooltip')), {
theme: $store.theme,
timeout: 2000,
})
"
>
{{ $token }}
</div>
</div>
</x-filament::section>
@endsession

{{ $this->table }}
</x-filament::page>
42 changes: 38 additions & 4 deletions routes/api.php
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
<?php

use Cachet\Enums\ApiAbility;
use Cachet\Http\Controllers\Api\ComponentController;
use Cachet\Http\Controllers\Api\ComponentGroupController;
use Cachet\Http\Controllers\Api\GeneralController;
Expand All @@ -20,18 +21,51 @@
'incident-templates' => IncidentTemplateController::class,
'metrics' => MetricController::class,
'schedules' => ScheduleController::class,
]);
], ['except' => ['store', 'update', 'destroy']]);

Route::apiResource('incidents.updates', IncidentUpdateController::class)
Route::apiResource('incidents.updates', IncidentUpdateController::class, [
'except' => ['store', 'update', 'destroy'],
])
->scoped(['updateable_id']);

Route::apiResource('schedules.updates', ScheduleUpdateController::class)
Route::apiResource('schedules.updates', ScheduleUpdateController::class, [
'except' => ['store', 'update', 'destroy'],
])
->scoped(['updateable_id']);

Route::apiResource('metrics.points', MetricPointController::class)
Route::apiResource('metrics.points', MetricPointController::class, [
'except' => ['store', 'update', 'destroy'],
])
->parameter('points', 'metricPoint')
->scoped();

Route::middleware(['auth:sanctum'])->group(function () {
Route::apiResources([
'components' => ComponentController::class,
'component-groups' => ComponentGroupController::class,
'incidents' => IncidentController::class,
'incident-templates' => IncidentTemplateController::class,
'metrics' => MetricController::class,
'schedules' => ScheduleController::class,
], ['except' => ['index', 'show']]);

Route::apiResource('incidents.updates', IncidentUpdateController::class, [
'except' => ['index', 'show'],
])
->scoped(['updateable_id']);

Route::apiResource('schedules.updates', ScheduleUpdateController::class, [
'except' => ['index', 'show'],
])
->scoped(['updateable_id']);

Route::apiResource('metrics.points', MetricPointController::class, [
'except' => ['index', 'show'],
])
->parameter('points', 'metricPoint')
->scoped();
});

Route::get('/ping', [GeneralController::class, 'ping'])->name('ping');
Route::get('/version', [GeneralController::class, 'version'])->name('version');
Route::get('/status', StatusController::class)->name('status');
16 changes: 16 additions & 0 deletions src/Cachet.php
Original file line number Diff line number Diff line change
Expand Up @@ -67,4 +67,20 @@ public static function version(): string
{
return trim(file_get_contents(__DIR__.'/../VERSION'));
}

/** @return array<string, list<string>> */
public static function getResourceApiAbilities(): array
{
return [
'components' => ['manage', 'delete'],
'component-groups' => ['manage', 'delete'],
'incidents' => ['manage', 'delete'],
'incident-updates' => ['manage', 'delete'],
'incident-templates' => ['manage', 'delete'],
'metrics' => ['manage', 'delete'],
'metric-points' => ['manage', 'delete'],
'schedules' => ['manage', 'delete'],
'schedule-updates' => ['manage', 'delete'],
];
}
}
Loading

0 comments on commit 9682337

Please sign in to comment.