diff --git a/app/Contracts/GenerateTokenCommand.php b/app/Contracts/GenerateTokenCommand.php
new file mode 100644
index 0000000..e7fabf2
--- /dev/null
+++ b/app/Contracts/GenerateTokenCommand.php
@@ -0,0 +1,11 @@
+captchaTokenDto;
+ }
+
+ public function getPage(): int
+ {
+ return $this->page;
+ }
+}
diff --git a/app/Dto/Request/Private/CaptchaToken/StoreUpdate.php b/app/Dto/Request/Private/CaptchaToken/StoreUpdate.php
new file mode 100644
index 0000000..086014f
--- /dev/null
+++ b/app/Dto/Request/Private/CaptchaToken/StoreUpdate.php
@@ -0,0 +1,17 @@
+title;
+ }
+}
diff --git a/app/Enums/Permission.php b/app/Enums/Permission.php
index 383f347..cc36e2b 100644
--- a/app/Enums/Permission.php
+++ b/app/Enums/Permission.php
@@ -6,10 +6,24 @@ enum Permission: string
{
case Role = 'role';
case User = 'user';
+ case CaptchaToken = 'captcha_token';
public function getPermissions(): array
{
- return $this->getBasePermissions();
+ $permissions = match ($this) {
+ self::CaptchaToken => [
+ 'view' => __('permissions.Allowed to watch all tokens'),
+ 'view_own' => __('permissions.Allowed to view own tokens'),
+ 'create' => __('permissions.Allowed to create tokens'),
+ 'update' => __('permissions.Allowed to edit all tokens'),
+ 'update_own' => __('permissions.Allowed to edit own tokens'),
+ 'delete' => __('permissions.Allowed to delete all tokens'),
+ 'delete_own' => __('permissions.Allowed to delete own tokens'),
+ ],
+ default => $this->getBasePermissions()
+ };
+
+ return $permissions;
}
public function getTitle(): string
diff --git a/app/Exceptions/Service/GenerateTokenCommandException.php b/app/Exceptions/Service/GenerateTokenCommandException.php
new file mode 100644
index 0000000..c19f6de
--- /dev/null
+++ b/app/Exceptions/Service/GenerateTokenCommandException.php
@@ -0,0 +1,8 @@
+user();
+ $data = $request->getDto();
+ $querySettingsDto = new QuerySettingsDto(
+ limit: 20,
+ page: $data->getPage(),
+ queryWith: []
+ );
+
+ $result = $this->captchaTokenService->index($data->getCaptchaTokenDto(), $querySettingsDto, $user);
+ if ($result->isError()) {
+ $this->errors($result);
+ }
+
+ return view('private/captcha_tokens/index', $result->getData());
+ }
+
+ public function create(Request $request): View
+ {
+ $user = $request->user();
+ $result = $this->captchaTokenService->create($user);
+ if ($result->isError()) {
+ $this->errors($result);
+ }
+
+ return view('private/captcha_tokens/create', $result->getData());
+ }
+
+ public function edit(int $id, Request $request): View
+ {
+ $user = $request->user();
+ $result = $this->captchaTokenService->edit($id, $user);
+ if ($result->isError()) {
+ $this->errors($result);
+ }
+
+ return view('private/captcha_tokens/edit', $result->getData());
+ }
+
+ public function store(StoreUpdateRequest $request): RedirectResponse
+ {
+ $data = $request->getDto();
+ $user = $request->user();
+ $result = $this->captchaTokenService->store($data, $user);
+ if ($result->isError()) {
+ return redirect()->back()->withInput()->withErrors($result->getErrorsOrMessage());
+ }
+
+ return redirect()->route('captcha-tokens.edit', $result->getModel())->withSuccess($result->getMessage());
+ }
+
+ public function update(int $id, StoreUpdateRequest $request): RedirectResponse
+ {
+ $data = $request->getDto();
+ $user = $request->user();
+ $result = $this->captchaTokenService->update($id, $data, $user);
+ if ($result->isError()) {
+ return redirect()->back()->withInput()->withErrors($result->getErrorsOrMessage());
+ }
+
+ return redirect()->route('captcha-tokens.edit', $result->getModel())->withSuccess($result->getMessage());
+ }
+
+ public function destroy(int $id, Request $request): RedirectResponse
+ {
+ $user = $request->user();
+ $result = $this->captchaTokenService->destroy($id, $user);
+ if ($result->isError()) {
+ return redirect()->back()->withInput()->withErrors($result->getErrorsOrMessage());
+ }
+
+ return redirect()->route('captcha-tokens.index')->withSuccess($result->getMessage());
+ }
+}
diff --git a/app/Http/Controllers/Private/DashboardController.php b/app/Http/Controllers/Private/DashboardController.php
new file mode 100644
index 0000000..8d1cade
--- /dev/null
+++ b/app/Http/Controllers/Private/DashboardController.php
@@ -0,0 +1,13 @@
+redirect = route('users.index');
+ return [
+ 'page' => ['nullable', 'numeric', 'min:1']
+ ];
+ }
+
+ public function getDto(): Index
+ {
+ return new Index(
+ captchaTokenDto: new CaptchaToken(),
+ page: (int) $this->input('page', 1)
+ );
+ }
+}
diff --git a/app/Http/Requests/Private/CaptchaTokens/StoreUpdateRequest.php b/app/Http/Requests/Private/CaptchaTokens/StoreUpdateRequest.php
new file mode 100644
index 0000000..01cf143
--- /dev/null
+++ b/app/Http/Requests/Private/CaptchaTokens/StoreUpdateRequest.php
@@ -0,0 +1,28 @@
+ ['required', 'max:255'],
+ ];
+ }
+
+
+ public function getDto(): StoreUpdate
+ {
+ return new StoreUpdate(
+ title: $this->input('title'),
+ );
+ }
+}
diff --git a/app/Models/CaptchaToken.php b/app/Models/CaptchaToken.php
new file mode 100644
index 0000000..71544be
--- /dev/null
+++ b/app/Models/CaptchaToken.php
@@ -0,0 +1,33 @@
+
+ */
+ protected $hidden = [
+ 'public_token',
+ 'private_token',
+ ];
+}
diff --git a/app/Policies/CaptchaTokenPolicy.php b/app/Policies/CaptchaTokenPolicy.php
new file mode 100644
index 0000000..c02fc5a
--- /dev/null
+++ b/app/Policies/CaptchaTokenPolicy.php
@@ -0,0 +1,47 @@
+hasPermission('captcha_token.view') || $user->hasPermission('captcha_token.view_own');
+ }
+
+ public function viewAnyAll(User $user): bool
+ {
+ return $user->hasPermission('captcha_token.view');
+ }
+
+ public function viewAnyOwn(User $user): bool
+ {
+ return $user->hasPermission('captcha_token.view_own');
+ }
+
+ public function view(User $user, CaptchaToken $captchaToken): bool
+ {
+ return $user->hasPermission('captcha_token.view') ||
+ $user->hasPermission('captcha_token.view_own') && $captchaToken->user_id === $user->id;
+ }
+
+ public function create(User $user): bool
+ {
+ return $user->hasPermission('captcha_token.create');
+ }
+
+ public function update(User $user, CaptchaToken $captchaToken): bool
+ {
+ return $user->hasPermission('captcha_token.update') ||
+ $user->hasPermission('captcha_token.update_own') && $captchaToken->user_id === $user->id;
+ }
+
+ public function delete(User $user, CaptchaToken $captchaToken): bool
+ {
+ return $user->hasPermission('captcha_token.delete') ||
+ $user->hasPermission('captcha_token.delete_own') && $captchaToken->user_id === $user->id;
+ }
+}
diff --git a/app/Providers/AppServiceProvider.php b/app/Providers/AppServiceProvider.php
index d7a5122..f9a56fd 100644
--- a/app/Providers/AppServiceProvider.php
+++ b/app/Providers/AppServiceProvider.php
@@ -11,6 +11,9 @@ use App\Captcha\Images\Head;
use App\Captcha\Images\ImageManager;
use App\Captcha\Images\Lines;
use App\Services\Api\V1\CaptchaService;
+use App\Services\CaptchaToken\CaptchaTokenHandler;
+use App\Services\GenerateTokenCommand\GenerateTokenUlidCommand;
+use App\Services\GenerateTokenCommand\GenerateTokenUuidCommand;
use App\Services\Search\CreateSearchInstanceCommand;
use App\Services\Search\Search;
use Illuminate\Contracts\Foundation\Application;
@@ -43,6 +46,13 @@ final class AppServiceProvider extends ServiceProvider
imageBody: $app->make(ImageBody::class)
);
});
+
+ $this->app->bind(CaptchaTokenHandler::class, function (Application $app) {
+ return new CaptchaTokenHandler(
+ generatePublicTokenCommand: $app->make(GenerateTokenUlidCommand::class),
+ generatePrivateTokenCommand: $app->make(GenerateTokenUuidCommand::class)
+ );
+ });
}
/**
diff --git a/app/Providers/AuthServiceProvider.php b/app/Providers/AuthServiceProvider.php
index f4c0ae9..63964b0 100644
--- a/app/Providers/AuthServiceProvider.php
+++ b/app/Providers/AuthServiceProvider.php
@@ -1,11 +1,11 @@
-
*/
protected $policies = [
- \App\Models\Role::class => \App\Policies\RolePolicy::class,
+ \App\Models\Role::class => \App\Policies\RolePolicy::class,
+ \App\Models\User::class => \App\Policies\UserPolicy::class,
+ \App\Models\CaptchaToken::class => \App\Policies\CaptchaTokenPolicy::class,
];
/**
diff --git a/app/Repositories/CaptchaTokenRepository.php b/app/Repositories/CaptchaTokenRepository.php
new file mode 100644
index 0000000..d09f7ce
--- /dev/null
+++ b/app/Repositories/CaptchaTokenRepository.php
@@ -0,0 +1,39 @@
+where('id', $id)->first();
+ }
+
+ public function getCaptchaTokens(CaptchaTokenDto $captchaTokenDto, array $with = [], ?int $userId = null): Search
+ {
+ $query = CaptchaToken::query()
+ ->when($userId, function (Builder $query, int $userId) {
+ $query->where('user_id', $userId);
+ })
+ ->with($with);
+
+ $query = $this->builderCommand->execute(
+ query: $query,
+ captchaTokenDto: $captchaTokenDto
+ );
+
+ return $this->createSearchInstanceCommand->execute($query);
+ }
+}
diff --git a/app/Services/CaptchaToken/BuilderCommand.php b/app/Services/CaptchaToken/BuilderCommand.php
new file mode 100644
index 0000000..ca70171
--- /dev/null
+++ b/app/Services/CaptchaToken/BuilderCommand.php
@@ -0,0 +1,15 @@
+public_token = $this->generatePublicTokenCommand->unique(CaptchaToken::query(), 'public_token');
+ $captchaToken->private_token = $this->generatePrivateTokenCommand->unique(CaptchaToken::query(), 'private_token');
+ $captchaToken->user_id = $user->id;
+ $captchaToken->fill($data)->save();
+
+ return $captchaToken;
+ }
+
+ public function handleUpdate(CaptchaToken $captchaToken, array $data): CaptchaToken
+ {
+ $captchaToken->update($data);
+ $captchaToken->touch();
+
+ return $captchaToken;
+ }
+
+ public function handleDestroy(CaptchaToken $captchaToken): void
+ {
+ $captchaToken->delete();
+ }
+}
diff --git a/app/Services/GenerateTokenCommand/GenerateTokenCommand.php b/app/Services/GenerateTokenCommand/GenerateTokenCommand.php
new file mode 100644
index 0000000..27ee1d4
--- /dev/null
+++ b/app/Services/GenerateTokenCommand/GenerateTokenCommand.php
@@ -0,0 +1,34 @@
+execute();
+ if ($builder->where($field, '=', $token)->doesntExist()) {
+ return $token;
+ }
+ }
+
+ throw new GenerateTokenCommandException(__('Failed to generate token.'));
+ }
+}
diff --git a/app/Services/GenerateTokenCommand/GenerateTokenUlidCommand.php b/app/Services/GenerateTokenCommand/GenerateTokenUlidCommand.php
new file mode 100644
index 0000000..cd2d35d
--- /dev/null
+++ b/app/Services/GenerateTokenCommand/GenerateTokenUlidCommand.php
@@ -0,0 +1,13 @@
+cannot('viewAny', CaptchaToken::class)) {
+ return $this->errFobidden(__('Access is denied'));
+ }
+
+ $userId = null;
+ if ($user->cannot('viewAnyAll', CaptchaToken::class)) {
+ $userId = $user->id;
+ }
+
+ $captchaTokens = $this->captchaTokenRepository->getCaptchaTokens(
+ $captchaTokenDto,
+ $querySettingsDto->getQueryWith(),
+ $userId
+ )->pagination(
+ $querySettingsDto->getLimit(),
+ $querySettingsDto->getPage()
+ );
+
+ return $this->result([
+ 'captchaTokens' => $captchaTokens,
+ ]);
+ }
+
+ public function create(User $user): ServiceResultError | ServiceResultArray
+ {
+ if ($user->cannot('create', CaptchaToken::class)) {
+ return $this->errFobidden(__('Access is denied'));
+ }
+
+ return $this->result([
+ 'captchaToken' => new CaptchaToken(),
+ ]);
+ }
+
+ public function edit(int $id, User $user): ServiceResultError | ServiceResultArray
+ {
+ $modelCaptchaToken = $this->captchaTokenRepository->getCaptchaTokenById($id);
+
+ if (is_null($modelCaptchaToken)) {
+ return $this->errNotFound(__('Not Found'));
+ }
+
+ if ($user->cannot('view', $modelCaptchaToken)) {
+ return $this->errFobidden(__('Access is denied'));
+ }
+
+ return $this->result([
+ 'captchaToken' => $modelCaptchaToken,
+ ]);
+ }
+
+ public function store(StoreUpdate $data, User $user): ServiceResultError | StoreUpdateResult
+ {
+ if ($user->cannot('create', CaptchaToken::class)) {
+ return $this->errFobidden(__('Access is denied'));
+ }
+
+ try {
+ $modelCaptchaToken = DB::transaction(function () use ($data, $user) {
+ $dataCaptchaToken = $this->getDataCaptchaToken($data);
+ return $this->captchaTokenHandler->handleStore($dataCaptchaToken, $user);
+ });
+ } catch (\Throwable $e) {
+ report($e);
+ return $this->errService(__('Server Error'));
+ }
+
+ return $this->resultStoreUpdateModel($modelCaptchaToken, __('Captcha token created successfully'));
+ }
+
+ public function update(int $id, StoreUpdate $data, User $user): ServiceResultError | StoreUpdateResult
+ {
+ $modelCaptchaToken = $this->captchaTokenRepository->getCaptchaTokenById($id);
+
+ if (is_null($modelCaptchaToken)) {
+ return $this->errNotFound(__('Not Found'));
+ }
+
+ if ($user->cannot('update', $modelCaptchaToken)) {
+ return $this->errFobidden(__('Access is denied'));
+ }
+
+ try {
+ $modelCaptchaToken = DB::transaction(function () use ($data, $modelCaptchaToken) {
+ $dataCaptchaToken = $this->getDataCaptchaToken($data);
+
+ return $this->captchaTokenHandler->handleUpdate($modelCaptchaToken, $dataCaptchaToken);
+ });
+ } catch (\Throwable $e) {
+ report($e);
+ return $this->errService(__('Server Error'));
+ }
+
+ return $this->resultStoreUpdateModel($modelCaptchaToken, __('Captcha token updated successfully'));
+ }
+
+ public function destroy(int $id, User $user): ServiceResultError | ServiceResultSuccess
+ {
+ $modelCaptchaToken = $this->captchaTokenRepository->getCaptchaTokenById($id);
+
+ if (is_null($modelCaptchaToken)) {
+ return $this->errNotFound(__('Not Found'));
+ }
+
+ if ($user->cannot('delete', $modelCaptchaToken)) {
+ return $this->errFobidden(__('Access is denied'));
+ }
+
+ try {
+ DB::transaction(function () use ($modelCaptchaToken) {
+ $this->captchaTokenHandler->handleDestroy($modelCaptchaToken);
+ });
+ } catch (\Throwable $e) {
+ report($e);
+ return $this->errService(__('Server Error'));
+ }
+
+ return $this->ok(__('The captcha token has been removed'));
+ }
+
+ private function getDataCaptchaToken(StoreUpdate $data): array
+ {
+ return [
+ 'title' => $data->getTitle(),
+ ];
+ }
+}
diff --git a/database/migrations/2023_08_03_145020_create_captcha_tokens_table.php b/database/migrations/2023_08_03_145020_create_captcha_tokens_table.php
new file mode 100644
index 0000000..b36701f
--- /dev/null
+++ b/database/migrations/2023_08_03_145020_create_captcha_tokens_table.php
@@ -0,0 +1,33 @@
+id();
+ $table->string('title');
+ $table->ulid('public_token')->unique();
+ $table->uuid('private_token')->unique();
+ $table->unsignedBigInteger('user_id')->index();
+ $table->foreign('user_id')->references('id')->on('users');
+ $table->timestamps();
+ $table->softDeletes();
+ });
+ }
+
+ /**
+ * Reverse the migrations.
+ */
+ public function down(): void
+ {
+ Schema::dropIfExists('captcha_tokens');
+ }
+};
diff --git a/lang/en.json b/lang/en.json
index bd47dd9..c9cf9e4 100644
--- a/lang/en.json
+++ b/lang/en.json
@@ -58,5 +58,9 @@
"no": "no",
"The user was successfully created": "The user was successfully created",
"The user was successfully updated": "The user was successfully updated",
- "The user has been deleted": "The user has been deleted"
+ "The user has been deleted": "The user has been deleted",
+ "Captcha token created successfully": "Captcha token created successfully",
+ "Captcha token updated successfully": "Captcha token updated successfully",
+ "The captcha token has been removed": "The captcha token has been removed",
+ "Failed to generate token.": "Failed to generate token."
}
diff --git a/lang/en/permissions.php b/lang/en/permissions.php
index d207a51..2029569 100644
--- a/lang/en/permissions.php
+++ b/lang/en/permissions.php
@@ -7,4 +7,11 @@ return [
'Allowed to create' => 'Allowed to create',
'Allowed to edit' => 'Allowed to edit',
'Allowed to delete' => 'Allowed to delete',
+ 'Allowed to watch all tokens' => 'Allowed to watch all tokens',
+ 'Allowed to view own tokens' => 'Allowed to view own tokens',
+ 'Allowed to create tokens' => 'Allowed to create tokens',
+ 'Allowed to edit all tokens' => 'Allowed to edit all tokens',
+ 'Allowed to edit own tokens' => 'Allowed to edit own tokens',
+ 'Allowed to delete own tokens' => 'Allowed to delete own tokens',
+ 'Allowed to delete all tokens' => 'Allowed to delete all tokens',
];
diff --git a/lang/en/sections.php b/lang/en/sections.php
index 14945a5..73fc2f6 100644
--- a/lang/en/sections.php
+++ b/lang/en/sections.php
@@ -4,4 +4,5 @@ return [
'Dashboard' => 'Dashboard',
'User group' => 'User group',
'Users' => 'Users',
+ 'Captcha tokens' => 'Captcha tokens',
];
diff --git a/lang/en/validation.php b/lang/en/validation.php
index f336241..0e251fb 100644
--- a/lang/en/validation.php
+++ b/lang/en/validation.php
@@ -220,5 +220,7 @@ return [
'permissions' => 'permissions',
'is_active' => 'is active',
'roles' => 'user group',
+ 'public_token' => 'public token',
+ 'private_token' => 'private token',
],
];
diff --git a/lang/ru.json b/lang/ru.json
index 8479ec6..81b8482 100644
--- a/lang/ru.json
+++ b/lang/ru.json
@@ -58,5 +58,9 @@
"no": "нет",
"The user was successfully created": "Пользователь был успешно создан",
"The user was successfully updated": "Пользователь был успешно обновлен",
- "The user has been deleted": "Пользователь был удален"
+ "The user has been deleted": "Пользователь был удален",
+ "Captcha token created successfully": "Токен капчи успешно создан",
+ "Captcha token updated successfully": "Токен капчи успешно обновлен",
+ "The captcha token has been removed": "Токен капчи был удален",
+ "Failed to generate token.": "Не удалось сгенерировать токен."
}
diff --git a/lang/ru/permissions.php b/lang/ru/permissions.php
index 275f953..a64c97f 100644
--- a/lang/ru/permissions.php
+++ b/lang/ru/permissions.php
@@ -7,4 +7,11 @@ return [
'Allowed to create' => 'Разрешено создать',
'Allowed to edit' => 'Разрешено редактировать',
'Allowed to delete' => 'Разрешено удалять',
+ 'Allowed to watch all tokens' => 'Разрешено смотреть все токены',
+ 'Allowed to view own tokens' => 'Разрешено просматривать собственные токены',
+ 'Allowed to create tokens' => 'Разрешено создавать токены',
+ 'Allowed to edit all tokens' => 'Разрешено редактировать все токены',
+ 'Allowed to edit own tokens' => 'Разрешено редактировать собственные токены',
+ 'Allowed to delete all tokens' => 'Разрешено удалять все токены',
+ 'Allowed to delete own tokens' => 'Разрешено удалять собственные токены',
];
diff --git a/lang/ru/sections.php b/lang/ru/sections.php
index 358c65b..216d1b6 100644
--- a/lang/ru/sections.php
+++ b/lang/ru/sections.php
@@ -4,5 +4,6 @@ return [
'Dashboard' => 'Dashboard',
'User group' => 'Группа пользователей',
'Users' => 'Пользователи',
+ 'Captcha tokens' => 'Токены от капчи',
];
diff --git a/lang/ru/validation.php b/lang/ru/validation.php
index 8732c5f..3a75b75 100644
--- a/lang/ru/validation.php
+++ b/lang/ru/validation.php
@@ -220,5 +220,7 @@ return [
'permissions' => 'разрешения',
'is_active' => 'активен',
'roles' => 'группа пользователей',
+ 'public_token' => 'публичный токен',
+ 'private_token' => 'приватный токен',
],
];
diff --git a/resources/views/private/captcha_tokens/_from.blade.php b/resources/views/private/captcha_tokens/_from.blade.php
new file mode 100644
index 0000000..db1ab7a
--- /dev/null
+++ b/resources/views/private/captcha_tokens/_from.blade.php
@@ -0,0 +1,5 @@
+@csrf
+
{{ __('validation.attributes.title') }} | ++ |
---|---|
+ + + {{ $captchaToken->title }} + + | ++ @can('delete', $captchaToken) + + @endcan + | +