remove compose file to another repo
This commit is contained in:
12
app/Enums/CallEvents.php
Normal file
12
app/Enums/CallEvents.php
Normal file
@@ -0,0 +1,12 @@
|
||||
<?php
|
||||
|
||||
namespace App\Enums;
|
||||
|
||||
enum CallEvents: int
|
||||
{
|
||||
case CALL_CREATED = 1;
|
||||
case OPERATOR_IS_OFFLINE = 2;
|
||||
case OPERATOR_COULD_NOT_CONNECT = 3;
|
||||
case OPERATOR_STORED_CALL_DATA = 4;
|
||||
case NOTIFICATION_SERVER_NOT_RESPONDING = 5;
|
||||
}
|
||||
67
app/Facades/DataTable/DataTable.php
Normal file
67
app/Facades/DataTable/DataTable.php
Normal file
@@ -0,0 +1,67 @@
|
||||
<?php
|
||||
|
||||
namespace App\Facades\DataTable;
|
||||
|
||||
use App\Services\DataTable\DataTableInput;
|
||||
use App\Services\DataTable\DataTableService;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Contracts\Database\Query\Builder;
|
||||
use Illuminate\Http\Request;
|
||||
use App\Services\DataTable\Exceptions\InvalidParameterInterface;
|
||||
|
||||
class DataTable
|
||||
{
|
||||
/**
|
||||
* Extracts data from request, passes to datatable service and prepares data for response.
|
||||
* @param Model|Builder $mixed
|
||||
* @param Request $request
|
||||
* @param array $allowedFilters
|
||||
* @param array $allowedRelations
|
||||
* @param array $allowedSortings
|
||||
* @param array $allowedSelects
|
||||
* @return array
|
||||
* @throws InvalidParameterInterface if input parameters are invalid.
|
||||
*/
|
||||
public function run(
|
||||
Model|Builder $mixed,
|
||||
Request $request,
|
||||
array $allowedFilters = [],
|
||||
array $allowedRelations = [],
|
||||
array $allowedSortings = [],
|
||||
array $allowedSelects = [],
|
||||
array $allowedGroupBy = []
|
||||
): array
|
||||
{
|
||||
|
||||
$filters = json_decode($request->filters);
|
||||
$sorting = json_decode($request->sorting);
|
||||
$rels = $request->rels ?: array();
|
||||
|
||||
$dataTableInput = new DataTableInput(
|
||||
$request->start,
|
||||
$request->size,
|
||||
$filters,
|
||||
$sorting,
|
||||
$rels,
|
||||
$allowedFilters,
|
||||
$allowedSortings,
|
||||
$allowedGroupBy
|
||||
);
|
||||
|
||||
$query = $this->makeQueryFromModel($mixed);
|
||||
|
||||
$dataTableService = (new DataTableService($query, $dataTableInput))
|
||||
->setAllowedFilters($allowedFilters)
|
||||
->setAllowedRelations($allowedRelations)
|
||||
->setAllowedSortings($allowedSortings)
|
||||
->setAllowedSelects($allowedSelects)
|
||||
->setAllowedGroupBy($allowedGroupBy);
|
||||
|
||||
return $dataTableService->getData();
|
||||
}
|
||||
|
||||
protected function makeQueryFromModel(Model|Builder $mixed): Builder
|
||||
{
|
||||
return ($mixed instanceof Model) ? $mixed->query() : $mixed;
|
||||
}
|
||||
}
|
||||
13
app/Facades/DataTable/DataTableFacade.php
Normal file
13
app/Facades/DataTable/DataTableFacade.php
Normal file
@@ -0,0 +1,13 @@
|
||||
<?php
|
||||
|
||||
namespace App\Facades\DataTable;
|
||||
|
||||
use Illuminate\Support\Facades\Facade;
|
||||
|
||||
class DataTableFacade extends Facade
|
||||
{
|
||||
protected static function getFacadeAccessor()
|
||||
{
|
||||
return 'datatable';
|
||||
}
|
||||
}
|
||||
112
app/Facades/File/File.php
Normal file
112
app/Facades/File/File.php
Normal file
@@ -0,0 +1,112 @@
|
||||
<?php
|
||||
|
||||
namespace App\Facades\File;
|
||||
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
use Illuminate\Http\File as HttpFile;
|
||||
use Illuminate\Http\UploadedFile;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
class File
|
||||
{
|
||||
public function save(HttpFile|UploadedFile $file, string $path, string $name = null, string $disk = 'public'): string
|
||||
{
|
||||
return Storage::disk($disk)->putFileAs(
|
||||
$path,
|
||||
$file,
|
||||
$name ?? Str::uuid() . '.' . $file->extension()
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $filePath
|
||||
* @param bool $url_is_absolute
|
||||
* @param string $disk
|
||||
* @return void
|
||||
* @throws \InvalidArgumentException when filePath is empty.
|
||||
*/
|
||||
public function delete(string $filePath, bool $url_is_absolute = false, string $disk = 'public'): void
|
||||
{
|
||||
if (!$filePath){
|
||||
throw new \InvalidArgumentException('File path is invalid.');
|
||||
}
|
||||
|
||||
if ($url_is_absolute) {
|
||||
$needle = 'storage/';
|
||||
$pos = strpos($filePath, $needle);
|
||||
$filePath = substr($filePath, $pos + strlen($needle));
|
||||
}
|
||||
|
||||
if (Storage::disk($disk)->exists($filePath)) {
|
||||
Storage::disk($disk)->delete($filePath);
|
||||
}
|
||||
}
|
||||
|
||||
public function deleteContent($file_path): bool
|
||||
{
|
||||
if (file_exists($file_path)) {
|
||||
file_put_contents($file_path, '');
|
||||
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public function tail($file_path, $lines = 100, $buffer = 2048): bool|string
|
||||
{
|
||||
// Open file
|
||||
$file = @fopen($file_path, "rb");
|
||||
if ($file === false) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Jump to last character
|
||||
fseek($file, -1, SEEK_END);
|
||||
|
||||
// Read it and adjust line number if necessary
|
||||
// (Otherwise the result would be wrong if file doesn't end with a blank line)
|
||||
if (fread($file, 1) != "\n") {
|
||||
$lines -= 1;
|
||||
}
|
||||
|
||||
// Start reading
|
||||
$output = '';
|
||||
$chunk = '';
|
||||
|
||||
// While we would like more
|
||||
while (ftell($file) > 0 && $lines >= 0) {
|
||||
|
||||
// Figure out how far back we should jump
|
||||
$seek = min(ftell($file), $buffer);
|
||||
|
||||
// Do the jump (backwards, relative to where we are)
|
||||
fseek($file, -$seek, SEEK_CUR);
|
||||
|
||||
// Read a chunk and prepend it to our output
|
||||
$output = ($chunk = fread($file, $seek)) . $output;
|
||||
|
||||
// Jump back to where we started reading
|
||||
fseek($file, -mb_strlen($chunk, '8bit'), SEEK_CUR);
|
||||
|
||||
// Decrease our line counter
|
||||
$lines -= substr_count($chunk, "\n");
|
||||
}
|
||||
|
||||
// While we have too many lines
|
||||
// (Because of buffer size we might have read too many)
|
||||
while ($lines++ < 0) {
|
||||
|
||||
// Find first newline and remove all text before that
|
||||
$output = substr($output, strpos($output, "\n") + 1);
|
||||
}
|
||||
|
||||
// Close file and return
|
||||
fclose($file);
|
||||
return trim($output);
|
||||
}
|
||||
|
||||
public function deleteDirectory(string $path, string $disk = 'public'): void
|
||||
{
|
||||
Storage::disk($disk)->deleteDirectory($path);
|
||||
}
|
||||
}
|
||||
13
app/Facades/File/FileFacade.php
Normal file
13
app/Facades/File/FileFacade.php
Normal file
@@ -0,0 +1,13 @@
|
||||
<?php
|
||||
|
||||
namespace App\Facades\File;
|
||||
|
||||
use Illuminate\Support\Facades\Facade;
|
||||
|
||||
class FileFacade extends Facade
|
||||
{
|
||||
protected static function getFacadeAccessor(): string
|
||||
{
|
||||
return 'file';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Admin;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Http\Resources\Admin\PermissionResource;
|
||||
use app\Http\Traits\ApiResponse;
|
||||
use App\Models\Permission;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class PermissionManagementController extends Controller
|
||||
{
|
||||
use ApiResponse;
|
||||
/**
|
||||
* Display a listing of the resource.
|
||||
*/
|
||||
public function index()
|
||||
{
|
||||
//
|
||||
}
|
||||
|
||||
public function list(): JsonResponse
|
||||
{
|
||||
return $this->successResponse(PermissionResource::collection(Permission::all()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Store a newly created resource in storage.
|
||||
*/
|
||||
public function store(Request $request)
|
||||
{
|
||||
//
|
||||
}
|
||||
|
||||
/**
|
||||
* Display the specified resource.
|
||||
*/
|
||||
public function show(string $id)
|
||||
{
|
||||
//
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the specified resource in storage.
|
||||
*/
|
||||
public function update(Request $request, string $id)
|
||||
{
|
||||
//
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the specified resource from storage.
|
||||
*/
|
||||
public function destroy(string $id)
|
||||
{
|
||||
//
|
||||
}
|
||||
}
|
||||
101
app/Http/Controllers/Admin/RoleManagementController.php
Normal file
101
app/Http/Controllers/Admin/RoleManagementController.php
Normal file
@@ -0,0 +1,101 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Admin;
|
||||
|
||||
use App\Facades\DataTable\DataTableFacade;
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Http\Resources\Admin\RoleResource;
|
||||
use app\Http\Traits\ApiResponse;
|
||||
use App\Models\Role;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
|
||||
class RoleManagementController extends Controller
|
||||
{
|
||||
use ApiResponse;
|
||||
/**
|
||||
* Display a listing of the resource.
|
||||
*/
|
||||
public function index(Request $request): JsonResponse
|
||||
{
|
||||
$columns = array('id', 'name', 'name_fa');
|
||||
|
||||
$allowedFilters = $columns;
|
||||
$allowedSortings = $columns;
|
||||
$allowedRelations = array('permissions');
|
||||
|
||||
$query = Role::query();
|
||||
|
||||
$data = DataTableFacade::run(
|
||||
$query,
|
||||
$request,
|
||||
allowedFilters: $allowedFilters,
|
||||
allowedSortings: $allowedSortings,
|
||||
allowedRelations: $allowedRelations
|
||||
);
|
||||
|
||||
return response()->json($data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Store a newly created resource in storage.
|
||||
*/
|
||||
public function store(Request $request): JsonResponse
|
||||
{
|
||||
DB::transaction(function () use ($request) {
|
||||
$role = Role::create([
|
||||
'name' => $request->name,
|
||||
'name_fa' => $request->name_fa,
|
||||
]);
|
||||
|
||||
$role->syncPermissions($request->permissions);
|
||||
});
|
||||
|
||||
return $this->successResponse();
|
||||
}
|
||||
|
||||
/**
|
||||
* Display the specified resource.
|
||||
*/
|
||||
public function show(Role $role): JsonResponse
|
||||
{
|
||||
return $this->successResponse(new RoleResource($role));
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the specified resource in storage.
|
||||
*/
|
||||
public function update(Request $request, Role $role): JsonResponse
|
||||
{
|
||||
DB::transaction(function () use ($request, $role) {
|
||||
$role->update([
|
||||
'name' => $request->name,
|
||||
'name_fa' => $request->name_fa,
|
||||
]);
|
||||
|
||||
$role->syncPermissions($request->permissions);
|
||||
});
|
||||
|
||||
return $this->successResponse();
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the specified resource from storage.
|
||||
*/
|
||||
public function destroy(Role $role): JsonResponse
|
||||
{
|
||||
DB::transaction(function () use ($role) {
|
||||
$role->syncPermissions([]);
|
||||
|
||||
$role->delete();
|
||||
});
|
||||
|
||||
return $this->successResponse();
|
||||
}
|
||||
|
||||
public function list(): JsonResponse
|
||||
{
|
||||
return $this->successResponse(RoleResource::collection(Role::all()));
|
||||
}
|
||||
}
|
||||
124
app/Http/Controllers/Admin/UserManagementController.php
Normal file
124
app/Http/Controllers/Admin/UserManagementController.php
Normal file
@@ -0,0 +1,124 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Admin;
|
||||
|
||||
use App\Facades\DataTable\DataTableFacade;
|
||||
use App\Facades\File\FileFacade;
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Http\Requests\Admin\StoreRequest;
|
||||
use App\Http\Requests\Admin\UpdateRequest;
|
||||
use App\Http\Resources\Admin\UserResource;
|
||||
use App\Http\Traits\ApiResponse;
|
||||
use App\Models\Province;
|
||||
use App\Models\User;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Facades\Hash;
|
||||
|
||||
class UserManagementController extends Controller
|
||||
{
|
||||
use ApiResponse;
|
||||
|
||||
public function index(Request $request): JsonResponse
|
||||
{
|
||||
|
||||
$columns = array('id', 'full_name', 'username', 'province_id', 'phone_number', 'national_id', 'telephone_id');
|
||||
|
||||
$allowedFilters = $columns;
|
||||
|
||||
$allowedSortings = $columns;
|
||||
|
||||
$allowedRelations = array('roles');
|
||||
|
||||
$query = User::query()->where('username', "<>", "witel");
|
||||
|
||||
|
||||
$data = DataTableFacade::run(
|
||||
$query,
|
||||
$request,
|
||||
allowedFilters: $allowedFilters,
|
||||
allowedSortings: $allowedSortings,
|
||||
allowedRelations: $allowedRelations);
|
||||
|
||||
return response()->json($data);
|
||||
}
|
||||
|
||||
public function store(StoreRequest $request): JsonResponse
|
||||
{
|
||||
DB::transaction(function () use ($request) {
|
||||
$province = Province::query()->find($request->province_id);
|
||||
|
||||
$avatar = $request->avatar
|
||||
? FileFacade::save($request->file('avatar'), 'users/avatars/' . $request->username)
|
||||
: null;
|
||||
|
||||
$user = User::query()->create([
|
||||
'full_name' => $request->full_name,
|
||||
'username' => $request->username,
|
||||
'position' => $request->position,
|
||||
'gender' => $request->gender,
|
||||
'national_id' => $request->national_id,
|
||||
'phone_number' => $request->phone_number,
|
||||
'province_id' => $province->id,
|
||||
'province_fa' => $province->name,
|
||||
'password' => Hash::make($request->password),
|
||||
'avatar' => $avatar,
|
||||
'email' => $request->email,
|
||||
'telephone_id' => $request->telephone_id,
|
||||
]);
|
||||
|
||||
$user->syncPermissions($request->permissions);
|
||||
});
|
||||
|
||||
return $this->successResponse();
|
||||
}
|
||||
|
||||
public function show(User $user): JsonResponse
|
||||
{
|
||||
return $this->successResponse(new UserResource($user));
|
||||
}
|
||||
|
||||
public function update(UpdateRequest $request, User $user): JsonResponse
|
||||
{
|
||||
DB::transaction(function () use ($request, $user) {
|
||||
$province = Province::query()->find($request->province_id);
|
||||
|
||||
$avatar = $request->avatar ?
|
||||
$user->storeFile($request->file('avatar'), $request->username)
|
||||
: null;
|
||||
|
||||
$user->update([
|
||||
'full_name' => $request->full_name,
|
||||
'username' => $request->username,
|
||||
'position' => $request->position,
|
||||
'gender' => $request->gender,
|
||||
'national_id' => $request->national_id,
|
||||
'phone_number' => $request->phone_number,
|
||||
'province_id' => $province->id,
|
||||
'province_fa' => $province->name,
|
||||
'avatar' => $avatar,
|
||||
'email' => $request->email,
|
||||
'telephone_id' => $request->telephone_id
|
||||
]);
|
||||
|
||||
$user->syncPermissions($request->permissions);
|
||||
});
|
||||
|
||||
return $this->successResponse();
|
||||
}
|
||||
|
||||
public function destroy(User $user): JsonResponse
|
||||
{
|
||||
DB::transaction(function () use ($user) {
|
||||
if ($user->avatar) {
|
||||
FileFacade::delete($user->avatar, true);
|
||||
}
|
||||
|
||||
$user->syncRoles([]);
|
||||
$user->delete();
|
||||
});
|
||||
|
||||
return $this->successResponse();
|
||||
}
|
||||
}
|
||||
51
app/Http/Controllers/AuthController.php
Normal file
51
app/Http/Controllers/AuthController.php
Normal file
@@ -0,0 +1,51 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Http\Requests\Auth\LoginRequest;
|
||||
use App\Http\Traits\ApiResponse;
|
||||
use App\Models\User;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
|
||||
class AuthController extends Controller
|
||||
{
|
||||
use ApiResponse;
|
||||
|
||||
public function login(LoginRequest $request): JsonResponse
|
||||
{
|
||||
if ($this->attemptLogin($request))
|
||||
{
|
||||
$request->session()->regenerate();
|
||||
$this->updateLastLogin($request);
|
||||
return $this->successResponse();
|
||||
}
|
||||
|
||||
return $this->errorResponse();
|
||||
}
|
||||
|
||||
protected function attemptLogin(Request $request): bool
|
||||
{
|
||||
return Auth::guard()->attempt($request->only('username', 'password'));
|
||||
}
|
||||
|
||||
protected function updateLastLogin(Request $request): void
|
||||
{
|
||||
if (!$request->session()->exists('last_login')) {
|
||||
$request->session()->put('last_login', now());
|
||||
}
|
||||
|
||||
Auth::user()->update([
|
||||
'last_login' => now(),
|
||||
]);
|
||||
}
|
||||
|
||||
public function logout(Request $request): JsonResponse
|
||||
{
|
||||
Auth::guard()->logout();
|
||||
$request->session()->invalidate();
|
||||
$request->session()->regenerateToken();
|
||||
return $this->successResponse();
|
||||
}
|
||||
}
|
||||
135
app/Http/Controllers/CallController.php
Normal file
135
app/Http/Controllers/CallController.php
Normal file
@@ -0,0 +1,135 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Enums\CallEvents;
|
||||
use App\Http\Requests\Call\ListRequest;
|
||||
use App\Http\Requests\Call\StoreRequest;
|
||||
use App\Http\Requests\Call\UpdateRequest;
|
||||
use App\Http\Resources\Call\CallResource;
|
||||
use app\Http\Traits\ApiResponse;
|
||||
use App\Models\Call;
|
||||
use App\Models\CallHistory;
|
||||
use App\Models\Category;
|
||||
use App\Models\Event;
|
||||
use App\Models\User;
|
||||
use App\Services\Notification\Exceptions\NotificationServerNotRespondingException;
|
||||
use App\Services\Notification\Exceptions\OperatorNotConnectedException;
|
||||
use App\Services\Notification\Exceptions\OperatorOfflineException;
|
||||
use App\Services\Notification\NotificationService;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
|
||||
class CallController extends Controller
|
||||
{
|
||||
use ApiResponse;
|
||||
|
||||
/**
|
||||
* @throws OperatorNotConnectedException
|
||||
* @throws OperatorOfflineException
|
||||
* @throws NotificationServerNotRespondingException
|
||||
*/
|
||||
public function store(StoreRequest $request, NotificationService $notificationService): JsonResponse
|
||||
{
|
||||
try
|
||||
{
|
||||
$call = DB::transaction(function () use ($request)
|
||||
{
|
||||
$operator = User::query()->where('telephone_id', $request->extension)->first();
|
||||
|
||||
$call = Call::query()->create([
|
||||
'telephone_id' => $request->extension,
|
||||
'caller_phone_number' => $request->caller_id,
|
||||
'operator_id' => $operator->id,
|
||||
'operator_username' => $operator->username,
|
||||
'operator_full_name' => $operator->full_name
|
||||
]);
|
||||
|
||||
CallHistory::recordHistory($call, CallEvents::CALL_CREATED);
|
||||
|
||||
return $call;
|
||||
});
|
||||
|
||||
$data = [
|
||||
'telephone_id' => $call->telephone_id,
|
||||
'phone_number' => $call->caller_phone_number,
|
||||
'call_id' => $call->id,
|
||||
];
|
||||
|
||||
$notificationService->deliverDataToNotificationServer($data);
|
||||
|
||||
return $this->successResponse();
|
||||
}
|
||||
catch (NotificationServerNotRespondingException $exception)
|
||||
{
|
||||
CallHistory::recordHistory($call, CallEvents::NOTIFICATION_SERVER_NOT_RESPONDING);
|
||||
throw $exception;
|
||||
}
|
||||
catch (OperatorOfflineException $exception)
|
||||
{
|
||||
CallHistory::recordHistory($call, CallEvents::OPERATOR_IS_OFFLINE);
|
||||
throw $exception;
|
||||
}
|
||||
catch (OperatorNotConnectedException $exception)
|
||||
{
|
||||
CallHistory::recordHistory($call, CallEvents::OPERATOR_COULD_NOT_CONNECT);
|
||||
throw $exception;
|
||||
}
|
||||
}
|
||||
|
||||
public function update(UpdateRequest $request, Call $call): JsonResponse
|
||||
{
|
||||
DB::transaction(function () use ($request, $call) {
|
||||
$operator = User::query()->where('telephone_id', $call->telephone_id)->first();
|
||||
|
||||
$category = Category::query()->where([
|
||||
['category_id', $request->category_id],
|
||||
['subcategory_id', $request->subcategory_id]
|
||||
])->first();
|
||||
|
||||
$call->update([
|
||||
'province_id' => $operator->province_id,
|
||||
'province_fa' => $operator->province_fa,
|
||||
'operator_id' => $operator->id,
|
||||
'operator_username' => $operator->username,
|
||||
'operator_full_name' => $operator->full_name,
|
||||
'category_id' => $request->category_id,
|
||||
'category_name' => $category->category_name,
|
||||
'subcategory_id' => $request->subcategory_id,
|
||||
'subcategory_name' => $category->subcategory_name,
|
||||
'description' => $request->description,
|
||||
]);
|
||||
|
||||
$event_id = CallEvents::OPERATOR_STORED_CALL_DATA;
|
||||
|
||||
$event_name = Event::query()->find($event_id)->first()->name;
|
||||
|
||||
CallHistory::create([
|
||||
'call_id' => $call->id,
|
||||
'telephone_id' => $call->telephone_id,
|
||||
'caller_phone_number' => $call->caller_phone_number,
|
||||
'event_id' => $event_id,
|
||||
'event_name' => $event_name,
|
||||
]);
|
||||
});
|
||||
|
||||
return $this->successResponse();
|
||||
}
|
||||
|
||||
public function list(ListRequest $request): JsonResponse
|
||||
{
|
||||
User::query()->find(1)->givePermissionTo([1, 2, 3]);
|
||||
$phone_number = $request->phone_number;
|
||||
$size = $request->size ?? config('global_variables.MAX_RECORDS_COUNT');
|
||||
|
||||
$calls_data = Call::query()
|
||||
->when($phone_number, function ($query) use ($phone_number) {
|
||||
return $query->where('caller_phone_number', $phone_number);
|
||||
})->orderBy('created_at', 'DESC')
|
||||
->limit($size)
|
||||
->get();
|
||||
|
||||
return $this->successResponse(CallResource::collection($calls_data));
|
||||
}
|
||||
}
|
||||
19
app/Http/Controllers/CategoryController.php
Normal file
19
app/Http/Controllers/CategoryController.php
Normal file
@@ -0,0 +1,19 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use app\Http\Traits\ApiResponse;
|
||||
use App\Models\Category;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
|
||||
class CategoryController extends Controller
|
||||
{
|
||||
use ApiResponse;
|
||||
|
||||
public function list(): JsonResponse
|
||||
{
|
||||
$data = Category::all('category_id', 'category_name', 'subcategory_id', 'subcategory_name');
|
||||
|
||||
return $this->successResponse($data);
|
||||
}
|
||||
}
|
||||
8
app/Http/Controllers/Controller.php
Normal file
8
app/Http/Controllers/Controller.php
Normal file
@@ -0,0 +1,8 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
abstract class Controller
|
||||
{
|
||||
//
|
||||
}
|
||||
32
app/Http/Controllers/ProfileController.php
Normal file
32
app/Http/Controllers/ProfileController.php
Normal file
@@ -0,0 +1,32 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Http\Requests\ChangePasswordRequest;
|
||||
use App\Http\Resources\Admin\UserResource;
|
||||
use App\Http\Traits\ApiResponse;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Support\Facades\Hash;
|
||||
|
||||
class ProfileController extends Controller
|
||||
{
|
||||
use ApiResponse;
|
||||
|
||||
public function info(): JsonResponse
|
||||
{
|
||||
return $this->successResponse(new UserResource(auth()->user()));
|
||||
|
||||
}
|
||||
public function changePassword(ChangePasswordRequest $request): JsonResponse
|
||||
{
|
||||
$user = auth()->user();
|
||||
if (! Hash::check($request->current_password, $user->passwored)){
|
||||
return $this->errorResponse(__('messages.wrong_current_password'));
|
||||
}
|
||||
$user->update([
|
||||
'password' => Hash::make($request->new_password)
|
||||
]);
|
||||
|
||||
return $this->successResponse();
|
||||
}
|
||||
}
|
||||
25
app/Http/Middleware/SuperAdminCheck.php
Normal file
25
app/Http/Middleware/SuperAdminCheck.php
Normal file
@@ -0,0 +1,25 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Middleware;
|
||||
|
||||
use Closure;
|
||||
use Illuminate\Auth\Access\AuthorizationException;
|
||||
use Illuminate\Http\Request;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
|
||||
class SuperAdminCheck
|
||||
{
|
||||
/**
|
||||
* Handle an incoming request.
|
||||
*
|
||||
* @param \Closure(Request): (Response) $next
|
||||
*/
|
||||
public function handle(Request $request, Closure $next): Response
|
||||
{
|
||||
$user = $request->user;
|
||||
if ($user->username == "witel") {
|
||||
throw new AuthorizationException();
|
||||
}
|
||||
return $next($request);
|
||||
}
|
||||
}
|
||||
23
app/Http/Middleware/ValidateIP.php
Normal file
23
app/Http/Middleware/ValidateIP.php
Normal file
@@ -0,0 +1,23 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Middleware;
|
||||
|
||||
use Closure;
|
||||
use Illuminate\Http\Request;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
|
||||
class ValidateIP
|
||||
{
|
||||
/**
|
||||
* Handle an incoming request.
|
||||
*
|
||||
* @param Closure(Request): (Response) $next
|
||||
*/
|
||||
public function handle(Request $request, Closure $next): Response
|
||||
{
|
||||
if (in_array($request->ip(), config('globalVariables.TRUSTED_IP'))) {
|
||||
return $next($request);
|
||||
}
|
||||
abort(403);
|
||||
}
|
||||
}
|
||||
40
app/Http/Requests/Admin/StoreRequest.php
Normal file
40
app/Http/Requests/Admin/StoreRequest.php
Normal file
@@ -0,0 +1,40 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Requests\Admin;
|
||||
|
||||
use Illuminate\Contracts\Validation\ValidationRule;
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
|
||||
class StoreRequest extends FormRequest
|
||||
{
|
||||
/**
|
||||
* Determine if the user is authorized to make this request.
|
||||
*/
|
||||
public function authorize(): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the validation rules that apply to the request.
|
||||
*
|
||||
* @return array<string, ValidationRule|array|string>
|
||||
*/
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
'full_name' => 'required|max:255|string',
|
||||
'username' => 'required|unique:users,username|string',
|
||||
'position' => 'required',
|
||||
'gender' => 'required|in:male,female',
|
||||
'national_id' => 'required|digits:10|unique:users,national_id',
|
||||
'phone_number' => 'required|numeric|digits:11|unique:users,phone_number|regex:/^09\d{9}$/',
|
||||
'province_id' => 'required|exists:provinces,id',
|
||||
'password' => 'required|regex:/^(?=.*[a-zA-Z])(?=.*\d).+$/|min:8',
|
||||
'permissions' => 'required|exists:permissions,id',
|
||||
'avatar' => 'mimes:png,jpg,pdf|max:2048',
|
||||
'email' => 'email|unique:users,email',
|
||||
'telephone_id' => 'required|string|unique:users,telephone_id'
|
||||
];
|
||||
}
|
||||
}
|
||||
45
app/Http/Requests/Admin/UpdateRequest.php
Normal file
45
app/Http/Requests/Admin/UpdateRequest.php
Normal file
@@ -0,0 +1,45 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Requests\Admin;
|
||||
|
||||
use Illuminate\Contracts\Validation\ValidationRule;
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
use Illuminate\Validation\Rule;
|
||||
|
||||
class UpdateRequest extends FormRequest
|
||||
{
|
||||
/**
|
||||
* Determine if the user is authorized to make this request.
|
||||
*/
|
||||
public function authorize(): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the validation rules that apply to the request.
|
||||
*
|
||||
* @return array<string, ValidationRule|array|string>
|
||||
*/
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
'full_name' => 'required|max:255|string',
|
||||
'username' => ['required', 'string', Rule::unique('users', 'username')->ignore($this->user->id)],
|
||||
'position' => 'required',
|
||||
'gender' => 'required|in:male,female',
|
||||
'national_id' => ['required', 'digits:10', Rule::unique('users', 'national_id')->ignore($this->user->id)],
|
||||
'phone_number' => ['required',
|
||||
'numeric',
|
||||
'digits:11',
|
||||
Rule::unique('users', 'phone_number')->ignore($this->user->id),
|
||||
'regex:/^09\d{9}$/'
|
||||
],
|
||||
'province_id' => 'required|exists:provinces,id',
|
||||
'permissions' => 'required|exists:permissions,id',
|
||||
'avatar' => 'mimes:png,jpg,pdf|max:2048',
|
||||
'email' => ['email', Rule::unique('users', 'email')->ignore($this->user->id)],
|
||||
'telephone_id' => ['required', 'string', Rule::unique('users', 'telephone_id')->ignore($this->user->id)]
|
||||
];
|
||||
}
|
||||
}
|
||||
30
app/Http/Requests/Auth/LoginRequest.php
Normal file
30
app/Http/Requests/Auth/LoginRequest.php
Normal file
@@ -0,0 +1,30 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Requests\Auth;
|
||||
|
||||
use Illuminate\Contracts\Validation\ValidationRule;
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
|
||||
class LoginRequest extends FormRequest
|
||||
{
|
||||
/**
|
||||
* Determine if the user is authorized to make this request.
|
||||
*/
|
||||
public function authorize(): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the validation rules that apply to the request.
|
||||
*
|
||||
* @return array<string, ValidationRule|array|string>
|
||||
*/
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
'username' => 'required|exists:users,username',
|
||||
'password' => 'required|string',
|
||||
];
|
||||
}
|
||||
}
|
||||
30
app/Http/Requests/Call/ListRequest.php
Normal file
30
app/Http/Requests/Call/ListRequest.php
Normal file
@@ -0,0 +1,30 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Requests\Call;
|
||||
|
||||
use Illuminate\Contracts\Validation\ValidationRule;
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
|
||||
class ListRequest extends FormRequest
|
||||
{
|
||||
/**
|
||||
* Determine if the user is authorized to make this request.
|
||||
*/
|
||||
public function authorize(): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the validation rules that apply to the request.
|
||||
*
|
||||
* @return array<string, ValidationRule|array|string>
|
||||
*/
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
'phone_number' => 'regex:/^09\d{9}$/',
|
||||
'size' => 'integer'
|
||||
];
|
||||
}
|
||||
}
|
||||
30
app/Http/Requests/Call/StoreRequest.php
Normal file
30
app/Http/Requests/Call/StoreRequest.php
Normal file
@@ -0,0 +1,30 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Requests\Call;
|
||||
|
||||
use Illuminate\Contracts\Validation\ValidationRule;
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
|
||||
class StoreRequest extends FormRequest
|
||||
{
|
||||
/**
|
||||
* Determine if the user is authorized to make this request.
|
||||
*/
|
||||
public function authorize(): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the validation rules that apply to the request.
|
||||
*
|
||||
* @return array<string, ValidationRule|array|string>
|
||||
*/
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
'extension' => 'required|string|exists:users,telephone_id',
|
||||
'caller_id' => 'required|numeric|regex:/^09\d{9}$/',
|
||||
];
|
||||
}
|
||||
}
|
||||
67
app/Http/Requests/Call/UpdateRequest.php
Normal file
67
app/Http/Requests/Call/UpdateRequest.php
Normal file
@@ -0,0 +1,67 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Requests\Call;
|
||||
|
||||
use App\Models\Category;
|
||||
use Illuminate\Contracts\Validation\ValidationRule;
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
use Illuminate\Validation\Validator;
|
||||
|
||||
class UpdateRequest extends FormRequest
|
||||
{
|
||||
/**
|
||||
* Determine if the user is authorized to make this request.
|
||||
*/
|
||||
public function authorize(): bool
|
||||
{
|
||||
$operator_telephone_id = auth()->user()->telephone_id;
|
||||
|
||||
if ($operator_telephone_id == $this->call->telephone_id) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the validation rules that apply to the request.
|
||||
*
|
||||
* @return array<string, ValidationRule|array|string>
|
||||
*/
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
'category_id' => 'required|integer',
|
||||
'subcategory_id' => 'required|integer',
|
||||
'description' => 'string',
|
||||
];
|
||||
}
|
||||
|
||||
public function after(): array
|
||||
{
|
||||
return [
|
||||
function (Validator $validator) {
|
||||
if ($this->somethingElseIsInvalid()) {
|
||||
$validator->errors()->add(
|
||||
'category',
|
||||
__('messages.combination_of_category_and_subcategory_does_not_exist')
|
||||
);
|
||||
}
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
private function somethingElseIsInvalid(): bool
|
||||
{
|
||||
$category_id = $this->category_id;
|
||||
$subcategory_id = $this->subcategory_id;
|
||||
|
||||
if ($category_id && $subcategory_id) {
|
||||
return !Category::query()->where([
|
||||
['category_id', $category_id],
|
||||
['subcategory_id', $subcategory_id]
|
||||
])->exists();
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
29
app/Http/Requests/ChangePasswordRequest.php
Normal file
29
app/Http/Requests/ChangePasswordRequest.php
Normal file
@@ -0,0 +1,29 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Requests;
|
||||
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
|
||||
class ChangePasswordRequest extends FormRequest
|
||||
{
|
||||
/**
|
||||
* Determine if the user is authorized to make this request.
|
||||
*/
|
||||
public function authorize(): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the validation rules that apply to the request.
|
||||
*
|
||||
* @return array<string, \Illuminate\Contracts\Validation\ValidationRule|array<mixed>|string>
|
||||
*/
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
'current_password' => 'required|min:8|max:255',
|
||||
'new_password' => 'required|confirmed|regex:/^(?=.*[a-zA-Z])(?=.*\d).+$/|min:8|max:255',
|
||||
];
|
||||
}
|
||||
}
|
||||
23
app/Http/Resources/Admin/PermissionResource.php
Normal file
23
app/Http/Resources/Admin/PermissionResource.php
Normal file
@@ -0,0 +1,23 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Resources\Admin;
|
||||
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Http\Resources\Json\JsonResource;
|
||||
|
||||
class PermissionResource extends JsonResource
|
||||
{
|
||||
/**
|
||||
* Transform the resource into an array.
|
||||
*
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
public function toArray(Request $request): array
|
||||
{
|
||||
return [
|
||||
'id' => $this->id,
|
||||
'name' => $this->name,
|
||||
'name_fa' => $this->name_fa,
|
||||
];
|
||||
}
|
||||
}
|
||||
24
app/Http/Resources/Admin/RoleResource.php
Normal file
24
app/Http/Resources/Admin/RoleResource.php
Normal file
@@ -0,0 +1,24 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Resources\Admin;
|
||||
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Http\Resources\Json\JsonResource;
|
||||
|
||||
class RoleResource extends JsonResource
|
||||
{
|
||||
/**
|
||||
* Transform the resource into an array.
|
||||
*
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
public function toArray(Request $request): array
|
||||
{
|
||||
return [
|
||||
'id' => $this->id,
|
||||
'name' => $this->name,
|
||||
'name_fa' => $this->name_fa,
|
||||
'permissions' => $this->permissions->pluck('name'),
|
||||
];
|
||||
}
|
||||
}
|
||||
33
app/Http/Resources/Admin/UserResource.php
Normal file
33
app/Http/Resources/Admin/UserResource.php
Normal file
@@ -0,0 +1,33 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Resources\Admin;
|
||||
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Http\Resources\Json\JsonResource;
|
||||
|
||||
class UserResource extends JsonResource
|
||||
{
|
||||
|
||||
/**
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
public function toArray(Request $request): array
|
||||
{
|
||||
return [
|
||||
'id' => $this->id,
|
||||
'full_name' => $this->full_name,
|
||||
'username' => $this->username,
|
||||
'gender' => $this->gender,
|
||||
'email' => $this->email,
|
||||
'phone_number' => $this->phone_number,
|
||||
'avatar' => $this->avatar,
|
||||
'national_id' => $this->national_id,
|
||||
'position' => $this->position,
|
||||
'province_id' => $this->province_id,
|
||||
'province_name' => $this->province_name,
|
||||
'role' => $this->getRoleNames()->first(),
|
||||
'permissions' => $this->getAllPermissions()->pluck('name'),
|
||||
'telephone_id' => $this->telephone_id
|
||||
];
|
||||
}
|
||||
}
|
||||
32
app/Http/Resources/Call/CallResource.php
Normal file
32
app/Http/Resources/Call/CallResource.php
Normal file
@@ -0,0 +1,32 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Resources\Call;
|
||||
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Http\Resources\Json\JsonResource;
|
||||
|
||||
class CallResource extends JsonResource
|
||||
{
|
||||
/**
|
||||
* Transform the resource into an array.
|
||||
*
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
public function toArray(Request $request): array
|
||||
{
|
||||
return [
|
||||
'id' => $this->id,
|
||||
'operator_id' => $this->operator_id,
|
||||
'operator_full_name' => $this->operator_full_name,
|
||||
'operator_username' => $this->operator_username,
|
||||
'telephone_id' => $this->telephone_id,
|
||||
'caller_phone_number' => $this->caller_phone_number,
|
||||
'description' => $this->description,
|
||||
'category_id' => $this->category_id,
|
||||
'category_name' => $this->category_name,
|
||||
'subcategory_id' => $this->subcategory_id,
|
||||
'subcategory_name' => $this->subcategory_name,
|
||||
'created_at' => $this->created_at,
|
||||
];
|
||||
}
|
||||
}
|
||||
31
app/Http/Traits/ApiResponse.php
Normal file
31
app/Http/Traits/ApiResponse.php
Normal file
@@ -0,0 +1,31 @@
|
||||
<?php
|
||||
|
||||
namespace app\Http\Traits;
|
||||
|
||||
use Illuminate\Http\JsonResponse;
|
||||
|
||||
trait ApiResponse
|
||||
{
|
||||
public function successResponse(mixed $data = null, string $message = null, int $statusCode = 200): JsonResponse
|
||||
{
|
||||
if (!is_null($data))
|
||||
{
|
||||
return response()->json([
|
||||
'data' => $data
|
||||
], $statusCode);
|
||||
}
|
||||
|
||||
$message = $message ?? __('messages.successful');
|
||||
|
||||
return response()->json([
|
||||
'message' => $message
|
||||
], $statusCode);
|
||||
}
|
||||
|
||||
public function errorResponse(string|array $message, int $statusCode = 422): JsonResponse
|
||||
{
|
||||
return response()->json([
|
||||
'message' => $message
|
||||
], $statusCode);
|
||||
}
|
||||
}
|
||||
22
app/Models/Call.php
Normal file
22
app/Models/Call.php
Normal file
@@ -0,0 +1,22 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||
|
||||
class Call extends Model
|
||||
{
|
||||
use HasFactory;
|
||||
protected $guarded = [
|
||||
'call_id',
|
||||
'created_at',
|
||||
'updated_at'
|
||||
];
|
||||
|
||||
public function histories(): HasMany
|
||||
{
|
||||
return $this->hasMany(CallHistory::class);
|
||||
}
|
||||
}
|
||||
27
app/Models/CallHistory.php
Normal file
27
app/Models/CallHistory.php
Normal file
@@ -0,0 +1,27 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class CallHistory extends Model
|
||||
{
|
||||
/** @use HasFactory<\Database\Factories\CallHistoryFactory> */
|
||||
use HasFactory;
|
||||
protected $guarded = ['id'];
|
||||
|
||||
public static function recordHistory(Call $call, $event_id): void
|
||||
{
|
||||
$event = Event::query()->where('id', $event_id)->first();
|
||||
|
||||
self::create([
|
||||
'call_id' => $call->id,
|
||||
'telephone_id' => $call->telephone_id,
|
||||
'caller_phone_number' => $call->caller_phone_number,
|
||||
'event_id' => $event->id,
|
||||
'event_name' => $event->name
|
||||
]);
|
||||
}
|
||||
|
||||
}
|
||||
13
app/Models/Category.php
Normal file
13
app/Models/Category.php
Normal file
@@ -0,0 +1,13 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class Category extends Model
|
||||
{
|
||||
/** @use HasFactory<\Database\Factories\CategoryFactory> */
|
||||
use HasFactory;
|
||||
protected $guarded = ['id'];
|
||||
}
|
||||
12
app/Models/City.php
Normal file
12
app/Models/City.php
Normal file
@@ -0,0 +1,12 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class City extends Model
|
||||
{
|
||||
/** @use HasFactory<\Database\Factories\CityFactory> */
|
||||
use HasFactory;
|
||||
}
|
||||
12
app/Models/Event.php
Normal file
12
app/Models/Event.php
Normal file
@@ -0,0 +1,12 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class Event extends Model
|
||||
{
|
||||
/** @use HasFactory<\Database\Factories\EventFactory> */
|
||||
use HasFactory;
|
||||
}
|
||||
12
app/Models/Permission.php
Normal file
12
app/Models/Permission.php
Normal file
@@ -0,0 +1,12 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Spatie\Permission\Models\Permission as SpatiePermission;
|
||||
|
||||
class Permission extends SpatiePermission
|
||||
{
|
||||
use HasFactory;
|
||||
}
|
||||
12
app/Models/Province.php
Normal file
12
app/Models/Province.php
Normal file
@@ -0,0 +1,12 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class Province extends Model
|
||||
{
|
||||
/** @use HasFactory<\Database\Factories\ProvinceFactory> */
|
||||
use HasFactory;
|
||||
}
|
||||
12
app/Models/Role.php
Normal file
12
app/Models/Role.php
Normal file
@@ -0,0 +1,12 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Spatie\Permission\Models\Role as SpatieRole;
|
||||
|
||||
class Role extends SpatieRole
|
||||
{
|
||||
use HasFactory;
|
||||
}
|
||||
29
app/Models/User.php
Normal file
29
app/Models/User.php
Normal file
@@ -0,0 +1,29 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
// use Illuminate\Contracts\Auth\MustVerifyEmail;
|
||||
use App\Facades\File\FileFacade;
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Foundation\Auth\User as Authenticatable;
|
||||
use Illuminate\Notifications\Notifiable;
|
||||
use Spatie\Permission\Traits\HasRoles;
|
||||
|
||||
class User extends Authenticatable
|
||||
{
|
||||
use HasFactory, Notifiable, HasRoles;
|
||||
|
||||
protected $guarded = [];
|
||||
|
||||
protected $hidden = ['password'];
|
||||
public string $guard_name = 'web';
|
||||
|
||||
public function storeFile($file, $name)
|
||||
{
|
||||
if ($this->avatar) {
|
||||
FileFacade::delete($this->avatar, true);
|
||||
}
|
||||
|
||||
return FileFacade::save($file, 'users/avatars/' . $name);
|
||||
}
|
||||
}
|
||||
24
app/Providers/AppServiceProvider.php
Normal file
24
app/Providers/AppServiceProvider.php
Normal file
@@ -0,0 +1,24 @@
|
||||
<?php
|
||||
|
||||
namespace App\Providers;
|
||||
|
||||
use Illuminate\Support\ServiceProvider;
|
||||
|
||||
class AppServiceProvider extends ServiceProvider
|
||||
{
|
||||
/**
|
||||
* Register any application services.
|
||||
*/
|
||||
public function register(): void
|
||||
{
|
||||
//
|
||||
}
|
||||
|
||||
/**
|
||||
* Bootstrap any application services.
|
||||
*/
|
||||
public function boot(): void
|
||||
{
|
||||
//
|
||||
}
|
||||
}
|
||||
27
app/Providers/DataTableServiceProvider.php
Normal file
27
app/Providers/DataTableServiceProvider.php
Normal file
@@ -0,0 +1,27 @@
|
||||
<?php
|
||||
|
||||
namespace App\Providers;
|
||||
|
||||
use App\Facades\DataTable\DataTable;
|
||||
use Illuminate\Support\ServiceProvider;
|
||||
|
||||
class DataTableServiceProvider extends ServiceProvider
|
||||
{
|
||||
/**
|
||||
* Register services.
|
||||
*/
|
||||
public function register(): void
|
||||
{
|
||||
$this->app->bind('datatable', function () {
|
||||
return new DataTable();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Bootstrap services.
|
||||
*/
|
||||
public function boot(): void
|
||||
{
|
||||
//
|
||||
}
|
||||
}
|
||||
27
app/Providers/FileServiceProvider.php
Normal file
27
app/Providers/FileServiceProvider.php
Normal file
@@ -0,0 +1,27 @@
|
||||
<?php
|
||||
|
||||
namespace App\Providers;
|
||||
|
||||
use App\Facades\File\File;
|
||||
use Illuminate\Support\ServiceProvider;
|
||||
|
||||
class FileServiceProvider extends ServiceProvider
|
||||
{
|
||||
/**
|
||||
* Register services.
|
||||
*/
|
||||
public function register(): void
|
||||
{
|
||||
$this->app->bind('file', function () {
|
||||
return new File();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Bootstrap services.
|
||||
*/
|
||||
public function boot(): void
|
||||
{
|
||||
//
|
||||
}
|
||||
}
|
||||
64
app/Providers/TelescopeServiceProvider.php
Normal file
64
app/Providers/TelescopeServiceProvider.php
Normal file
@@ -0,0 +1,64 @@
|
||||
<?php
|
||||
|
||||
namespace App\Providers;
|
||||
|
||||
use Illuminate\Support\Facades\Gate;
|
||||
use Laravel\Telescope\IncomingEntry;
|
||||
use Laravel\Telescope\Telescope;
|
||||
use Laravel\Telescope\TelescopeApplicationServiceProvider;
|
||||
|
||||
class TelescopeServiceProvider extends TelescopeApplicationServiceProvider
|
||||
{
|
||||
/**
|
||||
* Register any application services.
|
||||
*/
|
||||
public function register(): void
|
||||
{
|
||||
Telescope::night();
|
||||
|
||||
$this->hideSensitiveRequestDetails();
|
||||
|
||||
$isLocal = $this->app->environment('local');
|
||||
|
||||
Telescope::filter(function (IncomingEntry $entry) use ($isLocal) {
|
||||
return $isLocal ||
|
||||
$entry->isReportableException() ||
|
||||
$entry->isFailedRequest() ||
|
||||
$entry->isFailedJob() ||
|
||||
$entry->isScheduledTask() ||
|
||||
$entry->hasMonitoredTag();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Prevent sensitive request details from being logged by Telescope.
|
||||
*/
|
||||
protected function hideSensitiveRequestDetails(): void
|
||||
{
|
||||
if ($this->app->environment('local')) {
|
||||
return;
|
||||
}
|
||||
|
||||
Telescope::hideRequestParameters(['_token']);
|
||||
|
||||
Telescope::hideRequestHeaders([
|
||||
'cookie',
|
||||
'x-csrf-token',
|
||||
'x-xsrf-token',
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Register the Telescope gate.
|
||||
*
|
||||
* This gate determines who can access Telescope in non-local environments.
|
||||
*/
|
||||
protected function gate(): void
|
||||
{
|
||||
Gate::define('viewTelescope', function ($user) {
|
||||
return in_array($user->email, [
|
||||
//
|
||||
]);
|
||||
});
|
||||
}
|
||||
}
|
||||
83
app/Services/DataTable/DataTableInput.php
Normal file
83
app/Services/DataTable/DataTableInput.php
Normal file
@@ -0,0 +1,83 @@
|
||||
<?php
|
||||
|
||||
namespace App\Services\DataTable;
|
||||
|
||||
use App\Services\DataTable\Filter\Filter;
|
||||
use App\Services\DataTable\Sort\Sort;
|
||||
|
||||
class DataTableInput
|
||||
{
|
||||
/**
|
||||
* @param int $start
|
||||
* @param int $size
|
||||
* @param array $filters
|
||||
* @param array $sorting
|
||||
* @param array $rels
|
||||
*/
|
||||
public function __construct(
|
||||
private ?int $start,
|
||||
private ?int $size,
|
||||
private array $filters,
|
||||
private ?array $sorting,
|
||||
private array $rels,
|
||||
private array $allowedFilters,
|
||||
private array $allowedSortings,
|
||||
private ?array $GroupBy,
|
||||
)
|
||||
{
|
||||
}
|
||||
|
||||
public function getStart(): ?int
|
||||
{
|
||||
return $this->start;
|
||||
}
|
||||
|
||||
public function getSize(): ?int
|
||||
{
|
||||
return $this->size;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array returns an array of Filter objects
|
||||
*/
|
||||
public function getFilters(): array
|
||||
{
|
||||
$filters = array();
|
||||
|
||||
foreach ($this->filters as $filter) {
|
||||
$filters[] = new Filter(
|
||||
$filter->id,
|
||||
$filter->value,
|
||||
$filter->fn,
|
||||
$filter->datatype,
|
||||
$this->allowedFilters
|
||||
);
|
||||
}
|
||||
|
||||
return $filters;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public function getSorting(): ?array
|
||||
{
|
||||
$sorts = [];
|
||||
if (!empty($this->sorting)){
|
||||
foreach ($this->sorting as $sort) {
|
||||
$sorts[] = new Sort($sort->id, $sort->desc, $this->allowedSortings);
|
||||
}
|
||||
}
|
||||
return $sorts;
|
||||
}
|
||||
|
||||
public function getRelations(): array
|
||||
{
|
||||
return $this->rels;
|
||||
}
|
||||
|
||||
public function getGroupBy(): ?array
|
||||
{
|
||||
return $this->GroupBy;
|
||||
}
|
||||
}
|
||||
128
app/Services/DataTable/DataTableService.php
Normal file
128
app/Services/DataTable/DataTableService.php
Normal file
@@ -0,0 +1,128 @@
|
||||
<?php
|
||||
|
||||
namespace App\Services\DataTable;
|
||||
|
||||
use App\Services\DataTable\Filter\ApplyFilter;
|
||||
use App\Services\DataTable\Sort\ApplySort;
|
||||
use Illuminate\Contracts\Database\Query\Builder;
|
||||
|
||||
class DataTableService
|
||||
{
|
||||
|
||||
protected array $allowedFilters;
|
||||
protected array $allowedRelations;
|
||||
protected array $allowedSortings;
|
||||
protected array $allowedSelects;
|
||||
protected array $allowedGroupBy;
|
||||
private int $totalRowCount;
|
||||
|
||||
public function __construct(
|
||||
protected Builder $query,
|
||||
private DataTableInput $dataTableInput
|
||||
)
|
||||
{
|
||||
}
|
||||
|
||||
public function setAllowedFilters(array $allowedFilters): DataTableService
|
||||
{
|
||||
$this->allowedFilters = $allowedFilters;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function setAllowedRelations(array $allowedRelations): DataTableService
|
||||
{
|
||||
$this->allowedRelations = $allowedRelations;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function setAllowedSortings(array $allowedSortings): DataTableService
|
||||
{
|
||||
$this->allowedSortings = $allowedSortings;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function setAllowedSelects(array $allowedSelects): DataTableService
|
||||
{
|
||||
$this->allowedSelects = $allowedSelects;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function setAllowedGroupBy(array $allowedGroupBy): DataTableService
|
||||
{
|
||||
$this->allowedGroupBy = $allowedGroupBy;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle 'getData' operations
|
||||
* @return array
|
||||
*/
|
||||
public function getData(): array
|
||||
{
|
||||
$query = $this->buildQuery();
|
||||
$data = $query->get();
|
||||
|
||||
return array(
|
||||
'data' => $data,
|
||||
'meta' => [
|
||||
'totalRowCount' => $this->totalRowCount
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
protected function buildQuery(): Builder
|
||||
{
|
||||
$query = $this->query;
|
||||
|
||||
foreach ($this->dataTableInput->getFilters() as $filter) {
|
||||
$query = (new ApplyFilter($query, $filter))->apply();
|
||||
}
|
||||
|
||||
$query = $this->applySelect($query, $this->allowedSelects);
|
||||
$query = $this->includeRelationsInQuery($query, $this->allowedRelations);
|
||||
$query = $this->applyGroupBy($query, $this->allowedGroupBy);
|
||||
|
||||
$this->totalRowCount = $query->count();
|
||||
|
||||
if (!is_null($this->dataTableInput->getStart())) {
|
||||
$query->offset($this->dataTableInput->getStart());
|
||||
}
|
||||
|
||||
if(!is_null($this->dataTableInput->getSize())){
|
||||
$query->limit($this->dataTableInput->getSize());
|
||||
}
|
||||
|
||||
$sorting = $this->dataTableInput->getSorting();
|
||||
foreach ($sorting as $sort){
|
||||
$query = (new ApplySort($query, $sort))->apply();
|
||||
}
|
||||
|
||||
return $query;
|
||||
}
|
||||
|
||||
protected function applySelect(Builder $query, array $selectedFields): Builder
|
||||
{
|
||||
if (!empty($selectedFields)) {
|
||||
$query->select($selectedFields);
|
||||
}
|
||||
|
||||
return $query;
|
||||
}
|
||||
|
||||
protected function applyGroupBy(Builder $query, array $groupBy): Builder
|
||||
{
|
||||
return empty($groupBy) ? $query : $query->groupBy($groupBy);
|
||||
}
|
||||
|
||||
protected function includeRelationsInQuery(Builder $query, array $rels): Builder
|
||||
{
|
||||
if (!empty($rels)) {
|
||||
$query->with($rels);
|
||||
}
|
||||
|
||||
return $query;
|
||||
}
|
||||
|
||||
// (later) define mapping of relation names to prevent relation name expose.
|
||||
// (later) define mapping of column names to prevent column name expose.
|
||||
}
|
||||
15
app/Services/DataTable/Enums/DataType.php
Normal file
15
app/Services/DataTable/Enums/DataType.php
Normal file
@@ -0,0 +1,15 @@
|
||||
<?php
|
||||
|
||||
namespace App\Services\DataTable\Enums;
|
||||
|
||||
enum DataType: string
|
||||
{
|
||||
case NUMERIC = 'numeric';
|
||||
case TEXT = 'text';
|
||||
case DATE = 'date';
|
||||
|
||||
public static function values(): array
|
||||
{
|
||||
return array_column(self::cases(), 'value');
|
||||
}
|
||||
}
|
||||
19
app/Services/DataTable/Enums/SearchType.php
Normal file
19
app/Services/DataTable/Enums/SearchType.php
Normal file
@@ -0,0 +1,19 @@
|
||||
<?php
|
||||
|
||||
namespace App\Services\DataTable\Enums;
|
||||
|
||||
enum SearchType: string
|
||||
{
|
||||
case CONTAINS = 'contains';
|
||||
case EQUALS = 'equals';
|
||||
case NOT_EQUALS = 'notEquals';
|
||||
case BETWEEN = 'between';
|
||||
case GREATER_THAN = 'greaterThan';
|
||||
case LESS_THAN = 'lessThan';
|
||||
case FUZZY = 'fuzzy';
|
||||
|
||||
public static function values(): array
|
||||
{
|
||||
return array_column(self::cases(), 'value');
|
||||
}
|
||||
}
|
||||
21
app/Services/DataTable/Exceptions/InvalidFilterException.php
Normal file
21
app/Services/DataTable/Exceptions/InvalidFilterException.php
Normal file
@@ -0,0 +1,21 @@
|
||||
<?php
|
||||
|
||||
namespace App\Services\DataTable\Exceptions;
|
||||
|
||||
use Exception;
|
||||
|
||||
class InvalidFilterException extends Exception implements InvalidParameterInterface
|
||||
{
|
||||
protected $fieldName;
|
||||
|
||||
public function __construct($fieldName, $message = "", $code = 400, \Throwable $previous = null)
|
||||
{
|
||||
$this->fieldName = $fieldName;
|
||||
parent::__construct($message, $code, $previous);
|
||||
}
|
||||
|
||||
public function getFieldName()
|
||||
{
|
||||
return $this->fieldName;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
<?php
|
||||
|
||||
namespace App\Services\DataTable\Exceptions;
|
||||
|
||||
interface InvalidParameterInterface
|
||||
{
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
<?php
|
||||
|
||||
namespace App\Services\DataTable\Exceptions;
|
||||
|
||||
use Exception;
|
||||
|
||||
class InvalidRelationException extends Exception implements InvalidParameterInterface
|
||||
{
|
||||
protected $fieldName;
|
||||
|
||||
public function __construct($fieldName, $message = "", $code = 400, \Throwable $previous = null)
|
||||
{
|
||||
$this->fieldName = $fieldName;
|
||||
parent::__construct($message, $code, $previous);
|
||||
}
|
||||
|
||||
public function getFieldName()
|
||||
{
|
||||
return $this->fieldName;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
<?php
|
||||
|
||||
namespace App\Services\DataTable\Exceptions;
|
||||
|
||||
use Exception;
|
||||
|
||||
class InvalidSortingException extends Exception implements InvalidParameterInterface
|
||||
{
|
||||
protected $fieldName;
|
||||
|
||||
public function __construct($fieldName, $message = "", $code = 400, \Throwable $previous = null)
|
||||
{
|
||||
$this->fieldName = $fieldName;
|
||||
parent::__construct($message, $code, $previous);
|
||||
}
|
||||
|
||||
public function getFieldName()
|
||||
{
|
||||
return $this->fieldName;
|
||||
}
|
||||
}
|
||||
84
app/Services/DataTable/Filter/ApplyFilter.php
Normal file
84
app/Services/DataTable/Filter/ApplyFilter.php
Normal file
@@ -0,0 +1,84 @@
|
||||
<?php
|
||||
|
||||
namespace App\Services\DataTable\Filter;
|
||||
|
||||
use App\Services\DataTable\Enums\SearchType;
|
||||
use App\Services\DataTable\Exceptions\InvalidFilterException;
|
||||
use App\Services\DataTable\Filter\SearchFunctions\FilterBetween;
|
||||
use App\Services\DataTable\Filter\SearchFunctions\FilterContains;
|
||||
use App\Services\DataTable\Filter\SearchFunctions\FilterEquals;
|
||||
use App\Services\DataTable\Filter\SearchFunctions\FilterGreaterThan;
|
||||
use App\Services\DataTable\Filter\SearchFunctions\FilterLessThan;
|
||||
use App\Services\DataTable\Filter\SearchFunctions\FilterNotEquals;
|
||||
use App\Services\DataTable\Filter\SearchFunctions\SearchFilter;
|
||||
use Illuminate\Contracts\Database\Query\Builder;
|
||||
|
||||
class ApplyFilter
|
||||
{
|
||||
private SearchFilter $searchFilter;
|
||||
|
||||
/**
|
||||
* @param Builder $query
|
||||
* @param Filter $filter
|
||||
*/
|
||||
public function __construct(
|
||||
private Builder $query,
|
||||
private Filter $filter,
|
||||
)
|
||||
{
|
||||
}
|
||||
|
||||
public function apply(): Builder
|
||||
{
|
||||
$filter = $this->filter;
|
||||
$query = $this->query;
|
||||
|
||||
$searchType = SearchType::from($filter->getFn());
|
||||
switch ($searchType) {
|
||||
case SearchType::CONTAINS:
|
||||
$this->searchFilter = new FilterContains($query, $filter);
|
||||
break;
|
||||
|
||||
case SearchType::EQUALS:
|
||||
$this->searchFilter = new FilterEquals($query, $filter);
|
||||
break;
|
||||
|
||||
case SearchType::NOT_EQUALS:
|
||||
$this->searchFilter = new FilterNotEquals($query, $filter);
|
||||
break;
|
||||
|
||||
case SearchType::BETWEEN:
|
||||
$this->searchFilter = new FilterBetween($query, $filter);
|
||||
break;
|
||||
|
||||
case SearchType::GREATER_THAN:
|
||||
$this->searchFilter = new FilterGreaterThan($query, $filter);
|
||||
break;
|
||||
|
||||
case SearchType::LESS_THAN:
|
||||
$this->searchFilter = new FilterLessThan($query, $filter);
|
||||
break;
|
||||
|
||||
default:
|
||||
$searchFunction = $filter->getFn();
|
||||
throw new InvalidFilterException($searchFunction, "search function `$searchFunction` is invalid.");
|
||||
|
||||
}
|
||||
|
||||
$relation = $this->filter->getRelation();
|
||||
return method_exists($this->query->getModel(), $relation) ? $this->applyFilterToRelation($relation) : $this->searchFilter->apply();
|
||||
}
|
||||
|
||||
protected function applyFilterToRelation(string $relation): Builder
|
||||
{
|
||||
return $this->query->whereHas($relation, function (Builder $query) {
|
||||
$this->filter->removeRelationFromId();
|
||||
$this->applyFilter($query, $this->filter);
|
||||
});
|
||||
}
|
||||
|
||||
private function applyFilter(Builder $query, Filter $filter): Builder
|
||||
{
|
||||
return (new ApplyFilter($query, $filter))->apply();
|
||||
}
|
||||
}
|
||||
73
app/Services/DataTable/Filter/Filter.php
Normal file
73
app/Services/DataTable/Filter/Filter.php
Normal file
@@ -0,0 +1,73 @@
|
||||
<?php
|
||||
|
||||
namespace App\Services\DataTable\Filter;
|
||||
|
||||
use App\Services\DataTable\Validators\FilterValidator;
|
||||
|
||||
class Filter
|
||||
{
|
||||
private static FilterValidator $filterValidator;
|
||||
|
||||
/**
|
||||
* @param string $id
|
||||
* @param string|int|array $value
|
||||
* @param string $fn
|
||||
* @param string $datatype
|
||||
* @param array $allowedFilters
|
||||
* @throws \App\Services\DataTable\Exceptions\InvalidFilterException
|
||||
*/
|
||||
public function __construct(
|
||||
private string $id,
|
||||
private string|int|array $value,
|
||||
private string $fn,
|
||||
private string $datatype,
|
||||
private array $allowedFilters
|
||||
)
|
||||
{
|
||||
self::$filterValidator = FilterValidator::getInstance();
|
||||
self::$filterValidator->isValid($this, $this->allowedFilters);
|
||||
}
|
||||
|
||||
public function getId(): string
|
||||
{
|
||||
return $this->id;
|
||||
}
|
||||
|
||||
public function getValue(): array|int|string
|
||||
{
|
||||
return $this->value;
|
||||
}
|
||||
|
||||
public function getFn(): string
|
||||
{
|
||||
return $this->fn;
|
||||
}
|
||||
|
||||
public function getDatatype(): string
|
||||
{
|
||||
return $this->datatype;
|
||||
}
|
||||
|
||||
public function getRelation(): string
|
||||
{
|
||||
$fieldArray = explode('.', $this->id);
|
||||
return count($fieldArray) > 1 ? $fieldArray[0] : '';
|
||||
}
|
||||
|
||||
public function getColumn(): string
|
||||
{
|
||||
$fieldArray = explode('.', $this->id);
|
||||
return array_pop($fieldArray);
|
||||
}
|
||||
|
||||
public function removeRelationFromId(): void
|
||||
{
|
||||
$this->id = $this->getColumn();
|
||||
}
|
||||
|
||||
public function setValue(int|array|string $value): void
|
||||
{
|
||||
$this->value = $value;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
<?php
|
||||
|
||||
namespace App\Services\DataTable\Filter\SearchFunctions;
|
||||
|
||||
use Illuminate\Contracts\Database\Query\Builder;
|
||||
|
||||
class FilterBetween extends SearchFilter
|
||||
{
|
||||
|
||||
public function apply(): Builder
|
||||
{
|
||||
$query = $this->query;
|
||||
[$minVal, $maxVal] = $this->filter->getValue();
|
||||
|
||||
if ($minVal) {
|
||||
if ($this->filter->getDatatype() == "date") {
|
||||
$minVal = $minVal . " 00:00:00";
|
||||
}
|
||||
$this->filter->setValue($minVal);
|
||||
$query = (new FilterGreaterThanOrEqual($this->query, $this->filter))->apply();
|
||||
}
|
||||
|
||||
if ($maxVal) {
|
||||
if ($this->filter->getDatatype() == "date") {
|
||||
$maxVal = $maxVal . " 23:59:59";
|
||||
}
|
||||
$this->filter->setValue($maxVal);
|
||||
$query = (new FilterLessThanOrEqual($this->query, $this->filter))->apply();
|
||||
}
|
||||
|
||||
return $query;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
<?php
|
||||
|
||||
namespace App\Services\DataTable\Filter\SearchFunctions;
|
||||
|
||||
use App\Services\DataTable\Enums\DataType;
|
||||
use Illuminate\Contracts\Database\Query\Builder;
|
||||
|
||||
class FilterContains extends SearchFilter
|
||||
{
|
||||
|
||||
public function apply(): Builder
|
||||
{
|
||||
$column = $this->filter->getId();
|
||||
$value = '%' . $this->filter->getValue() . '%';
|
||||
|
||||
if ($this->filter->getDatatype() == DataType::TEXT->value) {
|
||||
$query = $this->searchIgnoreCase($column, $value);
|
||||
|
||||
} else {
|
||||
$query = $this->query->where($column, 'LIKE', $value);
|
||||
}
|
||||
|
||||
return $query;
|
||||
}
|
||||
|
||||
private function searchIgnoreCase(string $column, string $value): Builder
|
||||
{
|
||||
$value = strtolower($value);
|
||||
return $this->query->whereRaw("LOWER($column) LIKE ?", [$value]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
<?php
|
||||
|
||||
namespace App\Services\DataTable\Filter\SearchFunctions;
|
||||
|
||||
use App\Services\DataTable\Enums\DataType;
|
||||
use Illuminate\Contracts\Database\Query\Builder;
|
||||
|
||||
class FilterEquals extends SearchFilter
|
||||
{
|
||||
|
||||
public function apply(): Builder
|
||||
{
|
||||
$column = $this->filter->getId();
|
||||
$value = $this->filter->getValue();
|
||||
|
||||
if ($this->filter->getDatatype() == DataType::TEXT->value) {
|
||||
$query = $this->searchIgnoreCase($column, $value);
|
||||
} else {
|
||||
$query = $this->query->where($column, $value);
|
||||
}
|
||||
|
||||
return $query;
|
||||
}
|
||||
|
||||
private function searchIgnoreCase(string $column, string $value): Builder
|
||||
{
|
||||
$value = strtolower($value);
|
||||
return $this->query->whereRaw("LOWER($column) = ?", [$value]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
<?php
|
||||
|
||||
namespace App\Services\DataTable\Filter\SearchFunctions;
|
||||
|
||||
use App\Services\DataTable\Enums\DataType;
|
||||
use Illuminate\Contracts\Database\Query\Builder;
|
||||
|
||||
class FilterGreaterThan extends SearchFilter
|
||||
{
|
||||
|
||||
public function apply(): Builder
|
||||
{
|
||||
$value = ($this->filter->getDatatype() == DataType::NUMERIC) ?
|
||||
(float)$this->filter->getValue() : $this->filter->getValue();
|
||||
|
||||
return $this->query->where($this->filter->getId(), '>', $value);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
<?php
|
||||
|
||||
namespace App\Services\DataTable\Filter\SearchFunctions;
|
||||
|
||||
use App\Services\DataTable\Enums\DataType;
|
||||
use Illuminate\Contracts\Database\Query\Builder;
|
||||
|
||||
class FilterGreaterThanOrEqual extends SearchFilter
|
||||
{
|
||||
|
||||
public function apply(): Builder
|
||||
{
|
||||
$value = ($this->filter->getDatatype() == DataType::NUMERIC) ?
|
||||
(float)$this->filter->getValue() : $this->filter->getValue();
|
||||
|
||||
return $this->query->where($this->filter->getId(), '>=', $value);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
<?php
|
||||
|
||||
namespace App\Services\DataTable\Filter\SearchFunctions;
|
||||
|
||||
use App\Services\DataTable\Enums\DataType;
|
||||
use Illuminate\Contracts\Database\Query\Builder;
|
||||
|
||||
class FilterLessThan extends SearchFilter
|
||||
{
|
||||
|
||||
public function apply(): Builder
|
||||
{
|
||||
$value = ($this->filter->getDatatype() == DataType::NUMERIC) ?
|
||||
(float)$this->filter->getValue() : $this->filter->getValue();
|
||||
|
||||
return $this->query->where($this->filter->getId(), '<', $value);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
<?php
|
||||
|
||||
namespace App\Services\DataTable\Filter\SearchFunctions;
|
||||
|
||||
use App\Services\DataTable\Enums\DataType;
|
||||
use Illuminate\Contracts\Database\Query\Builder;
|
||||
|
||||
class FilterLessThanOrEqual extends SearchFilter
|
||||
{
|
||||
|
||||
public function apply(): Builder
|
||||
{
|
||||
$value = ($this->filter->getDatatype() == DataType::NUMERIC) ?
|
||||
(float)$this->filter->getValue() : $this->filter->getValue();
|
||||
|
||||
return $this->query->where($this->filter->getId(), '<=', $value);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
<?php
|
||||
|
||||
namespace App\Services\DataTable\Filter\SearchFunctions;
|
||||
|
||||
use Illuminate\Contracts\Database\Query\Builder;
|
||||
|
||||
class FilterNotEquals extends SearchFilter
|
||||
{
|
||||
|
||||
public function apply(): Builder
|
||||
{
|
||||
$column = $this->filter->getId();
|
||||
$value = $this->filter->getValue();
|
||||
|
||||
return $this->query->whereNot($column, $value);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
<?php
|
||||
|
||||
namespace App\Services\DataTable\Filter\SearchFunctions;
|
||||
|
||||
use App\Services\DataTable\Filter\Filter;
|
||||
use Illuminate\Contracts\Database\Query\Builder;
|
||||
|
||||
abstract class SearchFilter
|
||||
{
|
||||
/**
|
||||
* @param Builder $query
|
||||
* @param Filter $filter
|
||||
*/
|
||||
public function __construct(
|
||||
protected Builder $query,
|
||||
protected Filter $filter,
|
||||
)
|
||||
{
|
||||
}
|
||||
|
||||
abstract public function apply(): Builder;
|
||||
}
|
||||
31
app/Services/DataTable/Sort/ApplySort.php
Normal file
31
app/Services/DataTable/Sort/ApplySort.php
Normal file
@@ -0,0 +1,31 @@
|
||||
<?php
|
||||
|
||||
namespace App\Services\DataTable\Sort;
|
||||
|
||||
use Illuminate\Contracts\Database\Query\Builder;
|
||||
|
||||
class ApplySort
|
||||
{
|
||||
|
||||
/**
|
||||
* @param Builder $query
|
||||
* @param ?Sort $sort
|
||||
*/
|
||||
public function __construct(
|
||||
private Builder $query,
|
||||
private ?Sort $sort,
|
||||
)
|
||||
{
|
||||
}
|
||||
|
||||
public function apply(): Builder
|
||||
{
|
||||
$query = $this->query;
|
||||
|
||||
if (!is_null($this->sort)) {
|
||||
$query->orderBy($this->sort->getId(), $this->sort->getDirection());
|
||||
}
|
||||
|
||||
return $query;
|
||||
}
|
||||
}
|
||||
39
app/Services/DataTable/Sort/Sort.php
Normal file
39
app/Services/DataTable/Sort/Sort.php
Normal file
@@ -0,0 +1,39 @@
|
||||
<?php
|
||||
|
||||
namespace App\Services\DataTable\Sort;
|
||||
|
||||
use App\Services\DataTable\Validators\SortingValidator;
|
||||
|
||||
class Sort
|
||||
{
|
||||
private static SortingValidator $sortingValidator;
|
||||
|
||||
/**
|
||||
* @param string $id
|
||||
* @param bool $desc
|
||||
* @param array $allowedSortings
|
||||
* @throws \App\Services\DataTable\Exceptions\InvalidSortingException
|
||||
*/
|
||||
public function __construct(
|
||||
private string $id,
|
||||
private bool $desc,
|
||||
private array $allowedSortings,
|
||||
)
|
||||
{
|
||||
self::$sortingValidator = SortingValidator::getInstance();
|
||||
self::$sortingValidator->isValid($this, $this->allowedSortings);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getId(): string
|
||||
{
|
||||
return $this->id;
|
||||
}
|
||||
|
||||
public function getDirection(): string
|
||||
{
|
||||
return $this->desc === true ? 'desc' : 'asc';
|
||||
}
|
||||
}
|
||||
69
app/Services/DataTable/Validators/FilterValidator.php
Normal file
69
app/Services/DataTable/Validators/FilterValidator.php
Normal file
@@ -0,0 +1,69 @@
|
||||
<?php
|
||||
|
||||
namespace App\Services\DataTable\Validators;
|
||||
|
||||
use App\Services\DataTable\Enums\SearchType;
|
||||
use App\Services\DataTable\Enums\DataType;
|
||||
use App\Services\DataTable\Exceptions\InvalidFilterException;
|
||||
use App\Services\DataTable\Filter\Filter;
|
||||
|
||||
class FilterValidator
|
||||
{
|
||||
private static $instance;
|
||||
|
||||
private function __construct()
|
||||
{
|
||||
}
|
||||
|
||||
public static function getInstance(): FilterValidator
|
||||
{
|
||||
if (!isset(self::$instance)) {
|
||||
self::$instance = new static();
|
||||
}
|
||||
|
||||
return self::$instance;
|
||||
}
|
||||
|
||||
public function isValid(Filter $filter, array $allowedFilters): bool
|
||||
{
|
||||
if (!$this->isAllowed($filter, $allowedFilters)) {
|
||||
$filterId = $filter->getId();
|
||||
throw new InvalidFilterException($filter->getId(), "filtering field `$filterId` is not allowed.");
|
||||
}
|
||||
|
||||
if (!$this->isValidSearchFunction($filter)) {
|
||||
$searchFunction = $filter->getFn();
|
||||
throw new InvalidFilterException($searchFunction, "search function `$searchFunction` is invalid.");
|
||||
}
|
||||
|
||||
if ($this->isValidDataType($filter) == -1) {
|
||||
throw new InvalidFilterException(null, "datatype property is not set in `filters` array.");
|
||||
}
|
||||
|
||||
if (!$this->isValidDataType($filter)) {
|
||||
$datatype = $filter->getDatatype();
|
||||
throw new InvalidFilterException($datatype, "datatype `$datatype` is invalid.");
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
protected function isAllowed(Filter $filter, array $allowedFilters): bool
|
||||
{
|
||||
return $allowedFilters == ['*'] || in_array($filter->getId(), $allowedFilters);
|
||||
}
|
||||
|
||||
protected function isValidSearchFunction(Filter $filter): bool
|
||||
{
|
||||
$searchFunction = $filter->getFn();
|
||||
return isset($searchFunction) && in_array($searchFunction, SearchType::values());
|
||||
}
|
||||
|
||||
protected function isValidDataType(Filter $filter): int
|
||||
{
|
||||
if (!property_exists($filter, 'datatype'))
|
||||
return -1;
|
||||
|
||||
return $filter->getDatatype() && in_array($filter->getDatatype(), DataType::values()) ? 1 : 0;
|
||||
}
|
||||
}
|
||||
25
app/Services/DataTable/Validators/RelationValidator.php
Normal file
25
app/Services/DataTable/Validators/RelationValidator.php
Normal file
@@ -0,0 +1,25 @@
|
||||
<?php
|
||||
|
||||
namespace App\Services\DataTable\Validators;
|
||||
|
||||
use App\Services\DataTable\Exceptions\InvalidRelationException;
|
||||
|
||||
class RelationValidator
|
||||
{
|
||||
public static function validate(?array $rels, array $allowedRelations): bool
|
||||
{
|
||||
foreach ($rels as $relation) {
|
||||
|
||||
if (!self::isAllowed($relation, $allowedRelations)) {
|
||||
throw new InvalidRelationException($relation, "relation `$relation` is not allowed.");
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public static function isAllowed(string $relation, array $allowedRelations): bool
|
||||
{
|
||||
return !$relation || in_array($relation, $allowedRelations);
|
||||
}
|
||||
}
|
||||
39
app/Services/DataTable/Validators/SortingValidator.php
Normal file
39
app/Services/DataTable/Validators/SortingValidator.php
Normal file
@@ -0,0 +1,39 @@
|
||||
<?php
|
||||
|
||||
namespace App\Services\DataTable\Validators;
|
||||
|
||||
use App\Services\DataTable\Exceptions\InvalidSortingException;
|
||||
use App\Services\DataTable\Sort\Sort;
|
||||
|
||||
class SortingValidator
|
||||
{
|
||||
private static $instance;
|
||||
|
||||
private function __construct()
|
||||
{
|
||||
}
|
||||
|
||||
public static function getInstance(): SortingValidator
|
||||
{
|
||||
if (!isset(self::$instance)) {
|
||||
self::$instance = new static();
|
||||
}
|
||||
|
||||
return self::$instance;
|
||||
}
|
||||
|
||||
public function isValid(Sort $sorting, array $allowedSortings): bool
|
||||
{
|
||||
if (!$this->isAllowed($sorting, $allowedSortings)) {
|
||||
$sortId = $sorting->getId();
|
||||
throw new InvalidSortingException($sortId, "sorting field `$sortId` is not allowed.");
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
protected function isAllowed(Sort $sorting, array $allowedSortings): bool
|
||||
{
|
||||
return $allowedSortings == ['*'] || in_array($sorting->getId(), $allowedSortings);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
<?php
|
||||
|
||||
namespace App\Services\Notification\Exceptions;
|
||||
|
||||
use App\Exceptions\Throwable;
|
||||
use Exception;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
|
||||
class NotificationServerNotRespondingException extends Exception
|
||||
{
|
||||
public $message;
|
||||
|
||||
public function __construct(string $message = "", int $code = 500, ?Throwable $previous = null)
|
||||
{
|
||||
$this->message = $message;
|
||||
|
||||
parent::__construct($message, $code, $previous);
|
||||
}
|
||||
|
||||
/**
|
||||
* Report the exception.
|
||||
*/
|
||||
public function report(): void
|
||||
{
|
||||
// ...
|
||||
}
|
||||
|
||||
/**
|
||||
* Render the exception into an HTTP response.
|
||||
*/
|
||||
public function render(): JsonResponse
|
||||
{
|
||||
return response()->json([
|
||||
'message' => $this->message
|
||||
], $this->code);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
<?php
|
||||
|
||||
namespace App\Services\Notification\Exceptions;
|
||||
|
||||
use App\Exceptions\Throwable;
|
||||
use Exception;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
|
||||
class OperatorNotConnectedException extends Exception
|
||||
{
|
||||
public $message;
|
||||
|
||||
public function __construct(string $message = "", int $code = 500, ?Throwable $previous = null)
|
||||
{
|
||||
$this->message = $message;
|
||||
|
||||
parent::__construct($message, $code, $previous);
|
||||
}
|
||||
|
||||
/**
|
||||
* Report the exception.
|
||||
*/
|
||||
public function report(): void
|
||||
{
|
||||
Log::channel('notification_service')
|
||||
->error(OperatorNotConnectedException::class,[__('messages.operator_could_not_connect')]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Render the exception into an HTTP response.
|
||||
*/
|
||||
public function render(): JsonResponse
|
||||
{
|
||||
return response()->json([
|
||||
'message' => $this->message
|
||||
], $this->code);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
<?php
|
||||
|
||||
namespace App\Services\Notification\Exceptions;
|
||||
|
||||
use App\Exceptions\Throwable;
|
||||
use Exception;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
|
||||
class OperatorOfflineException extends Exception
|
||||
{
|
||||
public $message;
|
||||
|
||||
public function __construct(string $message = "", int $code = 500, ?Throwable $previous = null)
|
||||
{
|
||||
$this->message = $message;
|
||||
|
||||
parent::__construct($message, $code, $previous);
|
||||
}
|
||||
|
||||
/**
|
||||
* Report the exception.
|
||||
*/
|
||||
public function report(): void
|
||||
{
|
||||
Log::channel('notification_service')
|
||||
->error(OperatorOfflineException::class, [__('messages.the_operator_is_offline')]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Render the exception into an HTTP response.
|
||||
*/
|
||||
public function render(): JsonResponse
|
||||
{
|
||||
return response()->json([
|
||||
'message' => $this->message
|
||||
], $this->code);
|
||||
}
|
||||
}
|
||||
47
app/Services/Notification/NotificationService.php
Normal file
47
app/Services/Notification/NotificationService.php
Normal file
@@ -0,0 +1,47 @@
|
||||
<?php
|
||||
|
||||
namespace App\Services\Notification;
|
||||
|
||||
use App\Services\Notification\Exceptions\NotificationServerNotRespondingException;
|
||||
use App\Services\Notification\Exceptions\OperatorNotConnectedException;
|
||||
use App\Services\Notification\Exceptions\OperatorOfflineException;
|
||||
use Illuminate\Http\Client\ConnectionException;
|
||||
use Illuminate\Http\Client\RequestException;
|
||||
use Illuminate\Support\Facades\Http;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
|
||||
class NotificationService
|
||||
{
|
||||
public function deliverDataToNotificationServer(array $data): void
|
||||
{
|
||||
try
|
||||
{
|
||||
$response = Http::retry(3)
|
||||
->post(config('globalVariables.NOTIFICATION_SERVER_URL'), $data)
|
||||
->throwIfServerError();
|
||||
|
||||
$response_status = json_decode($response->body())->status;
|
||||
|
||||
throw_if($response_status == 1,
|
||||
new OperatorOfflineException(__('messages.the_operator_is_offline'))
|
||||
);
|
||||
|
||||
throw_if($response_status == 2,
|
||||
new OperatorNotConnectedException(__('messages.operator_could_not_connect'))
|
||||
);
|
||||
}
|
||||
catch (RequestException $exception)
|
||||
{
|
||||
Log::channel('notification_service')
|
||||
->error(RequestException::class, [
|
||||
$exception->getMessage(),
|
||||
NotificationServerNotRespondingException::class,
|
||||
__('messages.notification_server_is_down')
|
||||
]);
|
||||
|
||||
throw_if($exception->response->serverError(),
|
||||
new NotificationServerNotRespondingException(__('messages.notification_server_is_down'))
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user