adds logging and policies

This commit is contained in:
2026-02-03 11:47:08 +01:00
parent 9583b7030c
commit c693cde038
23 changed files with 2151 additions and 16 deletions

View File

@@ -6,8 +6,10 @@
use App\Http\Controllers\Controller;
use App\Models\User;
use App\Services\ActivityLogger;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Arr;
use Laravel\Socialite\Facades\Socialite;
final class SocialiteController extends Controller
@@ -37,6 +39,8 @@ public function callback(): RedirectResponse
auth()->login($user);
ActivityLogger::log('login', $user->id, metadata: ['email' => $user->email, 'firm_name' => Arr::get($azureUser, 'companyName')]);
return redirect('/');
}
@@ -45,6 +49,8 @@ public function callback(): RedirectResponse
*/
public function logout(Request $request): RedirectResponse
{
ActivityLogger::log('logout', auth()->id());
auth()->logout();
$request->session()->invalidate();

View File

@@ -7,8 +7,10 @@
use App\Http\Requests\Screening\UpdateScreeningRequest;
use App\Models\Category;
use App\Models\Screening;
use App\Services\ActivityLogger;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Arr;
use Inertia\Inertia;
use Inertia\Response;
@@ -23,6 +25,8 @@ public function store(Request $request): RedirectResponse
'user_id' => auth()->id(),
]);
ActivityLogger::log('screening_started', auth()->id());
return redirect()->route('screening.show', $screening);
}
@@ -44,8 +48,10 @@ public function update(UpdateScreeningRequest $request, Screening $screening): R
{
$validated = $request->validated();
$this->saveAnswers($screening, $validated['answers']);
$this->calculateAndUpdateScore($screening, $validated['answers']);
$this->saveAnswers($screening, Arr::get($validated, 'answers'));
$this->calculateAndUpdateScore($screening, Arr::get($validated, 'answers'));
ActivityLogger::log('screening_completed', auth()->id(), metadata: ['score' => $screening->score, 'passed' => $screening->passed]);
return redirect()->route('screening.result', $screening);
}

View File

@@ -6,9 +6,11 @@
use App\Http\Requests\Session\UpdateSessionRequest;
use App\Models\Session;
use App\Services\ActivityLogger;
use App\Services\ScoringService;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Arr;
use Inertia\Inertia;
use Inertia\Response;
@@ -26,6 +28,8 @@ public function store(Request $request): RedirectResponse
'status' => 'in_progress',
]);
ActivityLogger::log('session_started', auth()->id(), sessionId: $session->id, categoryId: (int) $request->input('category_id'), metadata: ['category_id' => $request->input('category_id')]);
return redirect()->route('sessions.show', $session);
}
@@ -42,6 +46,8 @@ public function show(Session $session): Response
->orderBy('sort_order')
->get();
ActivityLogger::log('step_viewed', auth()->id(), sessionId: $session->id, categoryId: $session->category_id, metadata: ['question_group_id' => $questionGroups->first()?->id]);
$answers = $session->answers()->get()->keyBy('question_id');
$scoringService = new ScoringService;
@@ -62,16 +68,16 @@ public function update(UpdateSessionRequest $request, Session $session): Redirec
{
$validated = $request->validated();
if (isset($validated['basic_info'])) {
$session->update(['basic_info' => $validated['basic_info']]);
if (Arr::has($validated, 'basic_info')) {
$session->update(['basic_info' => Arr::get($validated, 'basic_info')]);
}
if (isset($validated['answers'])) {
$this->saveAnswers($session, $validated['answers']);
if (Arr::has($validated, 'answers')) {
$this->saveAnswers($session, Arr::get($validated, 'answers'));
}
if (isset($validated['additional_comments'])) {
$session->update(['additional_comments' => $validated['additional_comments']]);
if (Arr::has($validated, 'additional_comments')) {
$session->update(['additional_comments' => Arr::get($validated, 'additional_comments')]);
}
if ($request->boolean('complete')) {
@@ -90,10 +96,15 @@ private function saveAnswers(Session $session, array $answers): void
$session->answers()->updateOrCreate(
['question_id' => (int) $questionId],
[
'value' => $answer['value'] ?? null,
'text_value' => $answer['text_value'] ?? null,
'value' => Arr::get($answer, 'value'),
'text_value' => Arr::get($answer, 'text_value'),
]
);
ActivityLogger::log('answer_saved', auth()->id(), sessionId: $session->id, categoryId: $session->category_id, metadata: [
'question_id' => (int) $questionId,
'value' => Arr::get($answer, 'value'),
]);
}
}
@@ -113,6 +124,8 @@ private function completeSession(Session $session): RedirectResponse
'completed_at' => now(),
]);
ActivityLogger::log('session_completed', auth()->id(), sessionId: $session->id, categoryId: $session->category_id, metadata: ['category_id' => $session->category_id, 'score' => $score, 'result' => $result]);
return redirect()->route('sessions.result', $session);
}

127
app/Nova/AnswerResource.php Normal file
View File

@@ -0,0 +1,127 @@
<?php
declare(strict_types=1);
namespace App\Nova;
use Laravel\Nova\Fields\BelongsTo;
use Laravel\Nova\Fields\DateTime;
use Laravel\Nova\Fields\ID;
use Laravel\Nova\Fields\Text;
use Laravel\Nova\Fields\Textarea;
use Laravel\Nova\Http\Requests\NovaRequest;
use Maatwebsite\LaravelNovaExcel\Actions\DownloadExcel;
final class AnswerResource extends Resource
{
/**
* The model the resource corresponds to.
*
* @var class-string<\App\Models\Answer>
*/
public static string $model = \App\Models\Answer::class;
/**
* The single value that should be used to represent the resource when being displayed.
*
* @var string
*/
public static $title = 'id';
/**
* The columns that should be searched.
*
* @var array
*/
public static $search = ['id', 'value'];
/**
* Indicates if the resource should be displayed in the sidebar.
*
* @var bool
*/
public static $displayInNavigation = false;
/**
* Get the fields displayed by the resource.
*
* @return array<int, \Laravel\Nova\Fields\Field|\Laravel\Nova\Panel|\Laravel\Nova\ResourceTool|\Illuminate\Http\Resources\MergeValue>
*/
public function fields(NovaRequest $request): array
{
return [
ID::make()->sortable(),
BelongsTo::make('Session', 'session', SessionResource::class)
->sortable()
->filterable()
->rules('required'),
BelongsTo::make('Question', 'question', QuestionResource::class)
->sortable()
->filterable()
->rules('required'),
Text::make('Value')
->sortable()
->filterable()
->copyable()
->rules('nullable', 'max:255'),
Textarea::make('Text Value')
->rules('nullable'),
DateTime::make('Created At')
->exceptOnForms()
->sortable()
->filterable(),
DateTime::make('Updated At')
->exceptOnForms()
->sortable()
->filterable(),
];
}
/**
* Get the cards available for the request.
*
* @return array<int, \Laravel\Nova\Card>
*/
public function cards(NovaRequest $request): array
{
return [];
}
/**
* Get the filters available for the resource.
*
* @return array<int, \Laravel\Nova\Filters\Filter>
*/
public function filters(NovaRequest $request): array
{
return [];
}
/**
* Get the lenses available for the resource.
*
* @return array<int, \Laravel\Nova\Lenses\Lens>
*/
public function lenses(NovaRequest $request): array
{
return [];
}
/**
* Get the actions available for the resource.
*
* @return array<int, \Laravel\Nova\Actions\Action>
*/
public function actions(NovaRequest $request): array
{
return [
new DownloadExcel,
];
}
}

View File

@@ -0,0 +1,124 @@
<?php
declare(strict_types=1);
namespace App\Nova;
use Laravel\Nova\Fields\DateTime;
use Laravel\Nova\Fields\HasMany;
use Laravel\Nova\Fields\ID;
use Laravel\Nova\Fields\Number;
use Laravel\Nova\Fields\Text;
use Laravel\Nova\Http\Requests\NovaRequest;
use Maatwebsite\LaravelNovaExcel\Actions\DownloadExcel;
final class CategoryResource extends Resource
{
/**
* The model the resource corresponds to.
*
* @var class-string<\App\Models\Category>
*/
public static string $model = \App\Models\Category::class;
/**
* The single value that should be used to represent the resource when being displayed.
*
* @var string
*/
public static $title = 'name';
/**
* The columns that should be searched.
*
* @var array
*/
public static $search = ['id', 'name'];
/**
* Indicates if the resource should be displayed in the sidebar.
*
* @var bool
*/
public static $displayInNavigation = false;
/**
* Get the fields displayed by the resource.
*
* @return array<int, \Laravel\Nova\Fields\Field|\Laravel\Nova\Panel|\Laravel\Nova\ResourceTool|\Illuminate\Http\Resources\MergeValue>
*/
public function fields(NovaRequest $request): array
{
return [
ID::make()->sortable(),
Text::make('Name')
->sortable()
->filterable()
->copyable()
->rules('required', 'max:255'),
Number::make('Sort Order')
->sortable()
->filterable()
->copyable()
->rules('required', 'integer'),
DateTime::make('Created At')
->exceptOnForms()
->sortable()
->filterable(),
DateTime::make('Updated At')
->exceptOnForms()
->sortable()
->filterable(),
HasMany::make('Question Groups', 'questionGroups', QuestionGroupResource::class),
HasMany::make('Sessions', 'sessions', SessionResource::class),
];
}
/**
* Get the cards available for the request.
*
* @return array<int, \Laravel\Nova\Card>
*/
public function cards(NovaRequest $request): array
{
return [];
}
/**
* Get the filters available for the resource.
*
* @return array<int, \Laravel\Nova\Filters\Filter>
*/
public function filters(NovaRequest $request): array
{
return [];
}
/**
* Get the lenses available for the resource.
*
* @return array<int, \Laravel\Nova\Lenses\Lens>
*/
public function lenses(NovaRequest $request): array
{
return [];
}
/**
* Get the actions available for the resource.
*
* @return array<int, \Laravel\Nova\Actions\Action>
*/
public function actions(NovaRequest $request): array
{
return [
new DownloadExcel,
];
}
}

138
app/Nova/LogResource.php Normal file
View File

@@ -0,0 +1,138 @@
<?php
declare(strict_types=1);
namespace App\Nova;
use Laravel\Nova\Fields\BelongsTo;
use Laravel\Nova\Fields\Code;
use Laravel\Nova\Fields\DateTime;
use Laravel\Nova\Fields\ID;
use Laravel\Nova\Fields\Text;
use Laravel\Nova\Http\Requests\NovaRequest;
use Maatwebsite\LaravelNovaExcel\Actions\DownloadExcel;
final class LogResource extends Resource
{
/**
* The model the resource corresponds to.
*
* @var class-string<\App\Models\Log>
*/
public static string $model = \App\Models\Log::class;
/**
* The single value that should be used to represent the resource when being displayed.
*
* @var string
*/
public static $title = 'action';
/**
* The columns that should be searched.
*
* @var array
*/
public static $search = ['id', 'action'];
/**
* Indicates if the resource should be displayed in the sidebar.
*
* @var bool
*/
public static $displayInNavigation = true;
/**
* The group associated with the resource.
*
* @var string
*/
public static $group = 'Analytics';
/**
* Get the fields displayed by the resource.
*
* @return array<int, \Laravel\Nova\Fields\Field|\Laravel\Nova\Panel|\Laravel\Nova\ResourceTool|\Illuminate\Http\Resources\MergeValue>
*/
public function fields(NovaRequest $request): array
{
return [
ID::make()->sortable(),
BelongsTo::make('User', 'user', User::class)
->nullable()
->sortable()
->filterable()
->rules('nullable'),
BelongsTo::make('Session', 'session', SessionResource::class)
->nullable()
->sortable()
->filterable()
->rules('nullable'),
BelongsTo::make('Category', 'category', CategoryResource::class)
->nullable()
->sortable()
->filterable()
->rules('nullable'),
Text::make('Action')
->sortable()
->filterable()
->copyable()
->rules('required', 'max:255'),
Code::make('Metadata')
->json()
->rules('nullable'),
DateTime::make('Created At')
->exceptOnForms()
->sortable()
->filterable(),
];
}
/**
* Get the cards available for the request.
*
* @return array<int, \Laravel\Nova\Card>
*/
public function cards(NovaRequest $request): array
{
return [];
}
/**
* Get the filters available for the resource.
*
* @return array<int, \Laravel\Nova\Filters\Filter>
*/
public function filters(NovaRequest $request): array
{
return [];
}
/**
* Get the lenses available for the resource.
*
* @return array<int, \Laravel\Nova\Lenses\Lens>
*/
public function lenses(NovaRequest $request): array
{
return [];
}
/**
* Get the actions available for the resource.
*
* @return array<int, \Laravel\Nova\Actions\Action>
*/
public function actions(NovaRequest $request): array
{
return [
new DownloadExcel,
];
}
}

View File

@@ -0,0 +1,135 @@
<?php
declare(strict_types=1);
namespace App\Nova;
use Laravel\Nova\Fields\BelongsTo;
use Laravel\Nova\Fields\DateTime;
use Laravel\Nova\Fields\HasMany;
use Laravel\Nova\Fields\ID;
use Laravel\Nova\Fields\Number;
use Laravel\Nova\Fields\Text;
use Laravel\Nova\Fields\Textarea;
use Laravel\Nova\Http\Requests\NovaRequest;
use Maatwebsite\LaravelNovaExcel\Actions\DownloadExcel;
final class QuestionGroupResource extends Resource
{
/**
* The model the resource corresponds to.
*
* @var class-string<\App\Models\QuestionGroup>
*/
public static string $model = \App\Models\QuestionGroup::class;
/**
* The single value that should be used to represent the resource when being displayed.
*
* @var string
*/
public static $title = 'name';
/**
* The columns that should be searched.
*
* @var array
*/
public static $search = ['id', 'name'];
/**
* Indicates if the resource should be displayed in the sidebar.
*
* @var bool
*/
public static $displayInNavigation = false;
/**
* Get the fields displayed by the resource.
*
* @return array<int, \Laravel\Nova\Fields\Field|\Laravel\Nova\Panel|\Laravel\Nova\ResourceTool|\Illuminate\Http\Resources\MergeValue>
*/
public function fields(NovaRequest $request): array
{
return [
ID::make()->sortable(),
BelongsTo::make('Category', 'category', CategoryResource::class)
->sortable()
->filterable()
->rules('required'),
Text::make('Name')
->sortable()
->filterable()
->copyable()
->rules('required', 'max:255'),
Number::make('Sort Order')
->sortable()
->filterable()
->copyable()
->rules('required', 'integer'),
Textarea::make('Description')
->rules('nullable'),
Textarea::make('Scoring Instructions')
->rules('nullable'),
DateTime::make('Created At')
->exceptOnForms()
->sortable()
->filterable(),
DateTime::make('Updated At')
->exceptOnForms()
->sortable()
->filterable(),
HasMany::make('Questions', 'questions', QuestionResource::class),
];
}
/**
* Get the cards available for the request.
*
* @return array<int, \Laravel\Nova\Card>
*/
public function cards(NovaRequest $request): array
{
return [];
}
/**
* Get the filters available for the resource.
*
* @return array<int, \Laravel\Nova\Filters\Filter>
*/
public function filters(NovaRequest $request): array
{
return [];
}
/**
* Get the lenses available for the resource.
*
* @return array<int, \Laravel\Nova\Lenses\Lens>
*/
public function lenses(NovaRequest $request): array
{
return [];
}
/**
* Get the actions available for the resource.
*
* @return array<int, \Laravel\Nova\Actions\Action>
*/
public function actions(NovaRequest $request): array
{
return [
new DownloadExcel,
];
}
}

View File

@@ -0,0 +1,158 @@
<?php
declare(strict_types=1);
namespace App\Nova;
use Laravel\Nova\Fields\BelongsTo;
use Laravel\Nova\Fields\Boolean;
use Laravel\Nova\Fields\DateTime;
use Laravel\Nova\Fields\ID;
use Laravel\Nova\Fields\Number;
use Laravel\Nova\Fields\Text;
use Laravel\Nova\Fields\Textarea;
use Laravel\Nova\Http\Requests\NovaRequest;
use Maatwebsite\LaravelNovaExcel\Actions\DownloadExcel;
final class QuestionResource extends Resource
{
/**
* The model the resource corresponds to.
*
* @var class-string<\App\Models\Question>
*/
public static string $model = \App\Models\Question::class;
/**
* The single value that should be used to represent the resource when being displayed.
*
* @var string
*/
public static $title = 'text';
/**
* The columns that should be searched.
*
* @var array
*/
public static $search = ['id', 'text'];
/**
* Indicates if the resource should be displayed in the sidebar.
*
* @var bool
*/
public static $displayInNavigation = true;
/**
* The group associated with the resource.
*
* @var string
*/
public static $group = 'Questionnaire';
/**
* Get the fields displayed by the resource.
*
* @return array<int, \Laravel\Nova\Fields\Field|\Laravel\Nova\Panel|\Laravel\Nova\ResourceTool|\Illuminate\Http\Resources\MergeValue>
*/
public function fields(NovaRequest $request): array
{
return [
ID::make()->sortable(),
BelongsTo::make('Question Group', 'questionGroup', QuestionGroupResource::class)
->sortable()
->filterable()
->readonly(),
Textarea::make('Text')
->rules('required')
->updateRules('required'),
Boolean::make('Has Yes')
->sortable()
->filterable()
->readonly(),
Boolean::make('Has No')
->sortable()
->filterable()
->readonly(),
Boolean::make('Has NA', 'has_na')
->sortable()
->filterable()
->readonly(),
Text::make('Details')
->sortable()
->filterable()
->copyable()
->readonly(),
Number::make('Sort Order')
->sortable()
->filterable()
->copyable()
->readonly(),
Boolean::make('Is Scored')
->sortable()
->filterable()
->readonly(),
DateTime::make('Created At')
->exceptOnForms()
->sortable()
->filterable(),
DateTime::make('Updated At')
->exceptOnForms()
->sortable()
->filterable(),
];
}
/**
* Get the cards available for the request.
*
* @return array<int, \Laravel\Nova\Card>
*/
public function cards(NovaRequest $request): array
{
return [];
}
/**
* Get the filters available for the resource.
*
* @return array<int, \Laravel\Nova\Filters\Filter>
*/
public function filters(NovaRequest $request): array
{
return [];
}
/**
* Get the lenses available for the resource.
*
* @return array<int, \Laravel\Nova\Lenses\Lens>
*/
public function lenses(NovaRequest $request): array
{
return [];
}
/**
* Get the actions available for the resource.
*
* @return array<int, \Laravel\Nova\Actions\Action>
*/
public function actions(NovaRequest $request): array
{
return [
new DownloadExcel,
];
}
}

View File

@@ -0,0 +1,134 @@
<?php
declare(strict_types=1);
namespace App\Nova;
use Laravel\Nova\Fields\BelongsTo;
use Laravel\Nova\Fields\Boolean;
use Laravel\Nova\Fields\DateTime;
use Laravel\Nova\Fields\HasMany;
use Laravel\Nova\Fields\ID;
use Laravel\Nova\Fields\Number;
use Laravel\Nova\Http\Requests\NovaRequest;
use Maatwebsite\LaravelNovaExcel\Actions\DownloadExcel;
final class ScreeningResource extends Resource
{
/**
* The model the resource corresponds to.
*
* @var class-string<\App\Models\Screening>
*/
public static string $model = \App\Models\Screening::class;
/**
* The single value that should be used to represent the resource when being displayed.
*
* @var string
*/
public static $title = 'id';
/**
* The columns that should be searched.
*
* @var array
*/
public static $search = ['id'];
/**
* Indicates if the resource should be displayed in the sidebar.
*
* @var bool
*/
public static $displayInNavigation = true;
/**
* The group associated with the resource.
*
* @var string
*/
public static $group = 'Questionnaire';
/**
* Get the fields displayed by the resource.
*
* @return array<int, \Laravel\Nova\Fields\Field|\Laravel\Nova\Panel|\Laravel\Nova\ResourceTool|\Illuminate\Http\Resources\MergeValue>
*/
public function fields(NovaRequest $request): array
{
return [
ID::make()->sortable(),
BelongsTo::make('User', 'user', User::class)
->sortable()
->filterable()
->rules('required'),
Number::make('Score')
->sortable()
->filterable()
->copyable()
->rules('required', 'integer'),
Boolean::make('Passed')
->sortable()
->filterable()
->rules('required', 'boolean'),
DateTime::make('Created At')
->exceptOnForms()
->sortable()
->filterable(),
DateTime::make('Updated At')
->exceptOnForms()
->sortable()
->filterable(),
HasMany::make('Sessions', 'sessions', SessionResource::class),
];
}
/**
* Get the cards available for the request.
*
* @return array<int, \Laravel\Nova\Card>
*/
public function cards(NovaRequest $request): array
{
return [];
}
/**
* Get the filters available for the resource.
*
* @return array<int, \Laravel\Nova\Filters\Filter>
*/
public function filters(NovaRequest $request): array
{
return [];
}
/**
* Get the lenses available for the resource.
*
* @return array<int, \Laravel\Nova\Lenses\Lens>
*/
public function lenses(NovaRequest $request): array
{
return [];
}
/**
* Get the actions available for the resource.
*
* @return array<int, \Laravel\Nova\Actions\Action>
*/
public function actions(NovaRequest $request): array
{
return [
new DownloadExcel,
];
}
}

View File

@@ -0,0 +1,168 @@
<?php
declare(strict_types=1);
namespace App\Nova;
use Laravel\Nova\Fields\BelongsTo;
use Laravel\Nova\Fields\Code;
use Laravel\Nova\Fields\DateTime;
use Laravel\Nova\Fields\HasMany;
use Laravel\Nova\Fields\ID;
use Laravel\Nova\Fields\Number;
use Laravel\Nova\Fields\Text;
use Laravel\Nova\Fields\Textarea;
use Laravel\Nova\Http\Requests\NovaRequest;
use Maatwebsite\LaravelNovaExcel\Actions\DownloadExcel;
final class SessionResource extends Resource
{
/**
* The model the resource corresponds to.
*
* @var class-string<\App\Models\Session>
*/
public static string $model = \App\Models\Session::class;
/**
* The single value that should be used to represent the resource when being displayed.
*
* @var string
*/
public static $title = 'id';
/**
* The columns that should be searched.
*
* @var array
*/
public static $search = ['id', 'status', 'result'];
/**
* Indicates if the resource should be displayed in the sidebar.
*
* @var bool
*/
public static $displayInNavigation = true;
/**
* The group associated with the resource.
*
* @var string
*/
public static $group = 'Questionnaire';
/**
* Get the fields displayed by the resource.
*
* @return array<int, \Laravel\Nova\Fields\Field|\Laravel\Nova\Panel|\Laravel\Nova\ResourceTool|\Illuminate\Http\Resources\MergeValue>
*/
public function fields(NovaRequest $request): array
{
return [
ID::make()->sortable(),
BelongsTo::make('User', 'user', User::class)
->sortable()
->filterable()
->rules('required'),
BelongsTo::make('Category', 'category', CategoryResource::class)
->sortable()
->filterable()
->rules('required'),
BelongsTo::make('Screening', 'screening', ScreeningResource::class)
->nullable()
->sortable()
->filterable()
->rules('nullable'),
Text::make('Status')
->sortable()
->filterable()
->copyable()
->rules('required', 'max:255'),
Number::make('Score')
->sortable()
->filterable()
->copyable()
->rules('nullable', 'integer'),
Text::make('Result')
->sortable()
->filterable()
->copyable()
->rules('nullable', 'max:255'),
Code::make('Basic Info', 'basic_info')
->json()
->rules('nullable'),
Textarea::make('Additional Comments')
->rules('nullable'),
DateTime::make('Completed At')
->sortable()
->filterable()
->rules('nullable'),
DateTime::make('Created At')
->exceptOnForms()
->sortable()
->filterable(),
DateTime::make('Updated At')
->exceptOnForms()
->sortable()
->filterable(),
HasMany::make('Answers', 'answers', AnswerResource::class),
HasMany::make('Logs', 'logs', LogResource::class),
];
}
/**
* Get the cards available for the request.
*
* @return array<int, \Laravel\Nova\Card>
*/
public function cards(NovaRequest $request): array
{
return [];
}
/**
* Get the filters available for the resource.
*
* @return array<int, \Laravel\Nova\Filters\Filter>
*/
public function filters(NovaRequest $request): array
{
return [];
}
/**
* Get the lenses available for the resource.
*
* @return array<int, \Laravel\Nova\Lenses\Lens>
*/
public function lenses(NovaRequest $request): array
{
return [];
}
/**
* Get the actions available for the resource.
*
* @return array<int, \Laravel\Nova\Actions\Action>
*/
public function actions(NovaRequest $request): array
{
return [
new DownloadExcel,
];
}
}

View File

@@ -1,5 +1,7 @@
<?php
declare(strict_types=1);
namespace App\Nova;
use Illuminate\Http\Request;
@@ -9,7 +11,7 @@
use Laravel\Nova\Fields\Text;
use Laravel\Nova\Http\Requests\NovaRequest;
class User extends Resource
final class User extends Resource
{
use PasswordValidationRules;

View File

@@ -0,0 +1,67 @@
<?php
declare(strict_types=1);
namespace App\Policies;
use App\Models\Answer;
use App\Models\User;
final class AnswerPolicy
{
/**
* Determine whether the user can view any answers.
*/
public function viewAny(User $user): bool
{
return true;
}
/**
* Determine whether the user can view the answer.
*/
public function view(User $user, Answer $answer): bool
{
return true;
}
/**
* Determine whether the user can create answers.
*/
public function create(User $user): bool
{
return false;
}
/**
* Determine whether the user can update the answer.
*/
public function update(User $user, Answer $answer): bool
{
return false;
}
/**
* Determine whether the user can delete the answer.
*/
public function delete(User $user, Answer $answer): bool
{
return false;
}
/**
* Determine whether the user can restore the answer.
*/
public function restore(User $user, Answer $answer): bool
{
return false;
}
/**
* Determine whether the user can permanently delete the answer.
*/
public function forceDelete(User $user, Answer $answer): bool
{
return false;
}
}

View File

@@ -0,0 +1,67 @@
<?php
declare(strict_types=1);
namespace App\Policies;
use App\Models\Category;
use App\Models\User;
final class CategoryPolicy
{
/**
* Determine whether the user can view any categories.
*/
public function viewAny(User $user): bool
{
return true;
}
/**
* Determine whether the user can view the category.
*/
public function view(User $user, Category $category): bool
{
return true;
}
/**
* Determine whether the user can create categories.
*/
public function create(User $user): bool
{
return false;
}
/**
* Determine whether the user can update the category.
*/
public function update(User $user, Category $category): bool
{
return false;
}
/**
* Determine whether the user can delete the category.
*/
public function delete(User $user, Category $category): bool
{
return false;
}
/**
* Determine whether the user can restore the category.
*/
public function restore(User $user, Category $category): bool
{
return false;
}
/**
* Determine whether the user can permanently delete the category.
*/
public function forceDelete(User $user, Category $category): bool
{
return false;
}
}

View File

@@ -0,0 +1,67 @@
<?php
declare(strict_types=1);
namespace App\Policies;
use App\Models\Log;
use App\Models\User;
final class LogPolicy
{
/**
* Determine whether the user can view any logs.
*/
public function viewAny(User $user): bool
{
return true;
}
/**
* Determine whether the user can view the log.
*/
public function view(User $user, Log $log): bool
{
return true;
}
/**
* Determine whether the user can create logs.
*/
public function create(User $user): bool
{
return false;
}
/**
* Determine whether the user can update the log.
*/
public function update(User $user, Log $log): bool
{
return false;
}
/**
* Determine whether the user can delete the log.
*/
public function delete(User $user, Log $log): bool
{
return false;
}
/**
* Determine whether the user can restore the log.
*/
public function restore(User $user, Log $log): bool
{
return false;
}
/**
* Determine whether the user can permanently delete the log.
*/
public function forceDelete(User $user, Log $log): bool
{
return false;
}
}

View File

@@ -0,0 +1,67 @@
<?php
declare(strict_types=1);
namespace App\Policies;
use App\Models\QuestionGroup;
use App\Models\User;
final class QuestionGroupPolicy
{
/**
* Determine whether the user can view any question groups.
*/
public function viewAny(User $user): bool
{
return true;
}
/**
* Determine whether the user can view the question group.
*/
public function view(User $user, QuestionGroup $questionGroup): bool
{
return true;
}
/**
* Determine whether the user can create question groups.
*/
public function create(User $user): bool
{
return false;
}
/**
* Determine whether the user can update the question group.
*/
public function update(User $user, QuestionGroup $questionGroup): bool
{
return false;
}
/**
* Determine whether the user can delete the question group.
*/
public function delete(User $user, QuestionGroup $questionGroup): bool
{
return false;
}
/**
* Determine whether the user can restore the question group.
*/
public function restore(User $user, QuestionGroup $questionGroup): bool
{
return false;
}
/**
* Determine whether the user can permanently delete the question group.
*/
public function forceDelete(User $user, QuestionGroup $questionGroup): bool
{
return false;
}
}

View File

@@ -0,0 +1,67 @@
<?php
declare(strict_types=1);
namespace App\Policies;
use App\Models\Question;
use App\Models\User;
final class QuestionPolicy
{
/**
* Determine whether the user can view any questions.
*/
public function viewAny(User $user): bool
{
return true;
}
/**
* Determine whether the user can view the question.
*/
public function view(User $user, Question $question): bool
{
return true;
}
/**
* Determine whether the user can create questions.
*/
public function create(User $user): bool
{
return false;
}
/**
* Determine whether the user can update the question.
*/
public function update(User $user, Question $question): bool
{
return true;
}
/**
* Determine whether the user can delete the question.
*/
public function delete(User $user, Question $question): bool
{
return false;
}
/**
* Determine whether the user can restore the question.
*/
public function restore(User $user, Question $question): bool
{
return false;
}
/**
* Determine whether the user can permanently delete the question.
*/
public function forceDelete(User $user, Question $question): bool
{
return false;
}
}

View File

@@ -0,0 +1,67 @@
<?php
declare(strict_types=1);
namespace App\Policies;
use App\Models\Screening;
use App\Models\User;
final class ScreeningPolicy
{
/**
* Determine whether the user can view any screenings.
*/
public function viewAny(User $user): bool
{
return true;
}
/**
* Determine whether the user can view the screening.
*/
public function view(User $user, Screening $screening): bool
{
return true;
}
/**
* Determine whether the user can create screenings.
*/
public function create(User $user): bool
{
return false;
}
/**
* Determine whether the user can update the screening.
*/
public function update(User $user, Screening $screening): bool
{
return false;
}
/**
* Determine whether the user can delete the screening.
*/
public function delete(User $user, Screening $screening): bool
{
return false;
}
/**
* Determine whether the user can restore the screening.
*/
public function restore(User $user, Screening $screening): bool
{
return false;
}
/**
* Determine whether the user can permanently delete the screening.
*/
public function forceDelete(User $user, Screening $screening): bool
{
return false;
}
}

View File

@@ -0,0 +1,67 @@
<?php
declare(strict_types=1);
namespace App\Policies;
use App\Models\Session;
use App\Models\User;
final class SessionPolicy
{
/**
* Determine whether the user can view any sessions.
*/
public function viewAny(User $user): bool
{
return true;
}
/**
* Determine whether the user can view the session.
*/
public function view(User $user, Session $session): bool
{
return true;
}
/**
* Determine whether the user can create sessions.
*/
public function create(User $user): bool
{
return false;
}
/**
* Determine whether the user can update the session.
*/
public function update(User $user, Session $session): bool
{
return false;
}
/**
* Determine whether the user can delete the session.
*/
public function delete(User $user, Session $session): bool
{
return false;
}
/**
* Determine whether the user can restore the session.
*/
public function restore(User $user, Session $session): bool
{
return false;
}
/**
* Determine whether the user can permanently delete the session.
*/
public function forceDelete(User $user, Session $session): bool
{
return false;
}
}

View File

@@ -0,0 +1,29 @@
<?php
declare(strict_types=1);
namespace App\Services;
use App\Models\Log;
final class ActivityLogger
{
/**
* Log an activity to the database.
*/
public static function log(
string $action,
?int $userId = null,
?int $sessionId = null,
?int $categoryId = null,
?array $metadata = null,
): void {
Log::create([
'user_id' => $userId,
'session_id' => $sessionId,
'category_id' => $categoryId,
'action' => $action,
'metadata' => $metadata,
]);
}
}