diff --git a/app/application/app/Dto/Builder/ProjectFeedback.php b/app/application/app/Dto/Builder/ProjectFeedback.php new file mode 100644 index 0000000..0186427 --- /dev/null +++ b/app/application/app/Dto/Builder/ProjectFeedback.php @@ -0,0 +1,10 @@ +clientIp; + } + + public function getUserAgent(): ?string + { + return $this->userAgent; + } +} diff --git a/app/application/app/Dto/Service/Admin/Project/Feedback/Index.php b/app/application/app/Dto/Service/Admin/Project/Feedback/Index.php new file mode 100644 index 0000000..1352071 --- /dev/null +++ b/app/application/app/Dto/Service/Admin/Project/Feedback/Index.php @@ -0,0 +1,21 @@ +projectFeedbackBuilderDto; + } +} diff --git a/app/application/app/Dto/Service/Admin/Project/Translation/Translations.php b/app/application/app/Dto/Service/Admin/Project/Translation/Translations.php index a0ac031..ed702cb 100644 --- a/app/application/app/Dto/Service/Admin/Project/Translation/Translations.php +++ b/app/application/app/Dto/Service/Admin/Project/Translation/Translations.php @@ -30,6 +30,14 @@ public static function getTranslationCodes(): array 'site.Choose language', 'site.Page without translation', 'site.Project', + 'site.Feedback', + 'site.Feedback-send', + 'site.required field', + 'site.attributes.name', + 'site.attributes.email', + 'site.attributes.message', + 'site.Message sent successfully', + 'Server Error', ]; } } diff --git a/app/application/app/Dto/Service/Site/Feedback/Send.php b/app/application/app/Dto/Service/Site/Feedback/Send.php new file mode 100644 index 0000000..749b8f7 --- /dev/null +++ b/app/application/app/Dto/Service/Site/Feedback/Send.php @@ -0,0 +1,36 @@ +name; + } + + public function getEmail(): ?string + { + return $this->email; + } + + public function getMessage(): string + { + return $this->message; + } + + public function getHttpUserData(): HttpUserData + { + return $this->httpUserData; + } +} diff --git a/app/application/app/Enums/Permission.php b/app/application/app/Enums/Permission.php index 652a7d5..f939554 100644 --- a/app/application/app/Enums/Permission.php +++ b/app/application/app/Enums/Permission.php @@ -11,6 +11,7 @@ enum Permission: string case ProjectContent = 'project-content'; case ProjectLink = 'project-link'; case ProjectTranslation = 'project-translation'; + case ProjectFeedback = 'project-feedback'; public function getPermissions(): array { @@ -27,6 +28,9 @@ public function getPermissions(): array 'view' => __('permissions.Allowed to watch'), 'update' => __('permissions.Allowed to edit'), ], + self::ProjectFeedback => [ + 'view' => __('permissions.Allowed to watch'), + ], default => $this->getBasePermissions() }; diff --git a/app/application/app/Enums/Site/ProjectSection.php b/app/application/app/Enums/Site/ProjectSection.php index 5ab5228..740c7d8 100644 --- a/app/application/app/Enums/Site/ProjectSection.php +++ b/app/application/app/Enums/Site/ProjectSection.php @@ -8,6 +8,8 @@ enum ProjectSection { case Home; + case Feedback; + case FeedbackSend; public function url(Project $project, ?ProjectLanguage $language = null): string { @@ -26,6 +28,9 @@ public function url(Project $project, ?ProjectLanguage $language = null): string $route = match ($this) { self::Home => \route($prefixProject . 'home' . $prefixLanguage, $parameters, false), + + self::Feedback => \route($prefixProject . 'feedback' . $prefixLanguage, $parameters, false), + self::FeedbackSend => \route($prefixProject . 'feedback.send' . $prefixLanguage, $parameters, false), }; return $project->http_host . $route; diff --git a/app/application/app/Http/Controllers/Admin/Projects/FeedbacksController.php b/app/application/app/Http/Controllers/Admin/Projects/FeedbacksController.php new file mode 100644 index 0000000..370c3ac --- /dev/null +++ b/app/application/app/Http/Controllers/Admin/Projects/FeedbacksController.php @@ -0,0 +1,52 @@ +user(); + $data = $request->getDto(); + $querySettingsDto = new QuerySettingsDto( + limit: 20, + page: $data->getPage(), + queryWith: ['project'] + ); + + $result = $this->feedbackService->index($data->getProjectFeedbackBuilderDto(), $querySettingsDto, $user); + if ($result->isError()) { + $this->errors($result); + } + + return view('admin.projects.feedbacks.index', $result->getData()); + } + + public function project(int $projectId, IndexRequest $request): View + { + $user = $request->user(); + $data = $request->getDto(); + $querySettingsDto = new QuerySettingsDto( + limit: 20, + page: $data->getPage(), + queryWith: [] + ); + + $result = $this->feedbackService->project($projectId, $data->getProjectFeedbackBuilderDto(), $querySettingsDto, $user); + if ($result->isError()) { + $this->errors($result); + } + + return view('admin.projects.feedbacks.project', $result->getData()); + } +} diff --git a/app/application/app/Http/Controllers/Site/FeedbackController.php b/app/application/app/Http/Controllers/Site/FeedbackController.php new file mode 100644 index 0000000..5448daa --- /dev/null +++ b/app/application/app/Http/Controllers/Site/FeedbackController.php @@ -0,0 +1,41 @@ + $request->get('project'), + 'websiteTranslations' => $request->get('websiteTranslations'), + ]); + } + + public function send(SendRequest $request): RedirectResponse + { + $project = $request->get('project'); + $websiteTranslations = $request->get('websiteTranslations'); + $user = $request->user(); + $data = $request->getDto(); + + $result = $this->feedbackService->send($data, $project, $websiteTranslations, $user); + if ($result->isError()) { + return redirect()->back()->withInput()->withErrors($result->getErrorsOrMessage()); + } + + $url = \App\Enums\Site\ProjectSection::Feedback->url($project, $websiteTranslations->getLanguage()); + return redirect($url)->withSuccess($result->getMessage()); + } +} diff --git a/app/application/app/Http/Requests/Admin/Projects/Feedbacks/IndexRequest.php b/app/application/app/Http/Requests/Admin/Projects/Feedbacks/IndexRequest.php new file mode 100644 index 0000000..0ae2b2d --- /dev/null +++ b/app/application/app/Http/Requests/Admin/Projects/Feedbacks/IndexRequest.php @@ -0,0 +1,29 @@ + ['nullable', 'numeric', 'min:1'] + ]; + } + + public function getDto(): Index + { + return new Index( + projectFeedbackBuilderDto: new ProjectFeedback(), + page: (int) $this->input('page', 1), + ); + } +} diff --git a/app/application/app/Http/Requests/Site/Feedback/SendRequest.php b/app/application/app/Http/Requests/Site/Feedback/SendRequest.php new file mode 100644 index 0000000..befefb6 --- /dev/null +++ b/app/application/app/Http/Requests/Site/Feedback/SendRequest.php @@ -0,0 +1,57 @@ +request->get('websiteTranslations', null); + if ($websiteTranslations) { + /** @var WebsiteTranslations $websiteTranslations */ + $attributes = [ + 'name' => $websiteTranslations->translate('site.attributes.name'), + 'email' => $websiteTranslations->translate('site.attributes.email'), + 'message' => $websiteTranslations->translate('site.attributes.message'), + ]; + } + + return $attributes; + } + + /** + * Get the validation rules that apply to the request. + */ + public function rules(): array + { + return [ + 'name' => ['nullable', 'string', 'max:255'], + 'email' => ['nullable', 'string', 'max:255', 'email'], + 'message' => ['required', 'string', 'max:5000'], + 'captcha-verified' => ['captcha'], + ]; + } + + public function getDto(): Send + { + $httpUserData = new HttpUserData( + clientIp: $this->getClientIp(), + userAgent: $this->userAgent(), + ); + + return new Send( + message: $this->input('message'), + httpUserData: $httpUserData, + name: $this->input('name', null), + email: $this->input('email', null), + ); + } +} diff --git a/app/application/app/Models/Project.php b/app/application/app/Models/Project.php index 5e2862b..fc2e671 100644 --- a/app/application/app/Models/Project.php +++ b/app/application/app/Models/Project.php @@ -57,4 +57,9 @@ public function links(): HasMany { return $this->hasMany(ProjectLink::class); } + + public function feedbacks(): HasMany + { + return $this->hasMany(ProjectFeedback::class); + } } diff --git a/app/application/app/Models/ProjectFeedback.php b/app/application/app/Models/ProjectFeedback.php new file mode 100644 index 0000000..04e7e3c --- /dev/null +++ b/app/application/app/Models/ProjectFeedback.php @@ -0,0 +1,52 @@ + + */ + protected function casts(): array + { + return [ + 'name' => 'encrypted', + 'email' => 'encrypted', + 'message' => 'encrypted', + ]; + } + + public function project(): BelongsTo + { + return $this->belongsTo(Project::class); + } +} diff --git a/app/application/app/Models/Scopes/CreatedScope.php b/app/application/app/Models/Scopes/CreatedScope.php new file mode 100644 index 0000000..6206e89 --- /dev/null +++ b/app/application/app/Models/Scopes/CreatedScope.php @@ -0,0 +1,18 @@ +orderBy('created_at', 'desc'); + } +} diff --git a/app/application/app/Policies/ProjectFeedbackPolicy.php b/app/application/app/Policies/ProjectFeedbackPolicy.php new file mode 100644 index 0000000..45f7ffb --- /dev/null +++ b/app/application/app/Policies/ProjectFeedbackPolicy.php @@ -0,0 +1,19 @@ +hasPermission('project-feedback.view'); + } + + public function view(User $user, ProjectFeedback $projectFeedback): bool + { + return $user->hasPermission('project-feedback.view'); + } +} diff --git a/app/application/app/Repositories/ProjectFeedbackRepository.php b/app/application/app/Repositories/ProjectFeedbackRepository.php new file mode 100644 index 0000000..6bb730b --- /dev/null +++ b/app/application/app/Repositories/ProjectFeedbackRepository.php @@ -0,0 +1,38 @@ +builderCommand->execute( + query: ProjectFeedback::query()->with($with), + projectFeedbackBuilderDto: $projectFeedbackBuilderDto + ); + + return $this->createSearchInstanceCommand->execute($query); + } + + public function getFeedbacksByProject(Project $project, ProjectFeedbackBuilderDto $projectFeedbackBuilderDto, array $with = []): Search + { + $query = $this->builderCommand->execute( + query: $project->feedbacks()->with($with), + projectFeedbackBuilderDto: $projectFeedbackBuilderDto + ); + + return $this->createSearchInstanceCommand->execute($query); + } +} diff --git a/app/application/app/Services/Admin/Project/FeedbackService.php b/app/application/app/Services/Admin/Project/FeedbackService.php new file mode 100644 index 0000000..a374b7d --- /dev/null +++ b/app/application/app/Services/Admin/Project/FeedbackService.php @@ -0,0 +1,66 @@ +cannot('viewAny', ProjectFeedback::class)) { + return $this->errFobidden(__('Access is denied')); + } + + $feedbacks = $this->projectFeedbackRepository->getFeedbacks( + $projectFeedbackBuilderDto, + $querySettingsDto->getQueryWith() + )->pagination( + $querySettingsDto->getLimit(), + $querySettingsDto->getPage() + ); + + return $this->result([ + 'feedbacks' => $feedbacks, + ]); + } + + public function project(int $projectId, ProjectFeedbackBuilderDto $projectFeedbackBuilderDto, QuerySettingsDto $querySettingsDto, User $user): ServiceResultError | ServiceResultArray + { + $project = $this->projectRepository->getProjectById($projectId); + if (\is_null($project)) { + return $this->errNotFound(__('Not Found')); + } + + if ($user->cannot('viewAny', ProjectFeedback::class)) { + return $this->errFobidden(__('Access is denied')); + } + + $feedbacks = $this->projectFeedbackRepository->getFeedbacksByProject( + $project, + $projectFeedbackBuilderDto, + $querySettingsDto->getQueryWith() + )->pagination( + $querySettingsDto->getLimit(), + $querySettingsDto->getPage() + ); + + return $this->result([ + 'project' => $project, + 'feedbacks' => $feedbacks, + ]); + } +} diff --git a/app/application/app/Services/ProjectFeedback/BuilderCommand.php b/app/application/app/Services/ProjectFeedback/BuilderCommand.php new file mode 100644 index 0000000..b4ed779 --- /dev/null +++ b/app/application/app/Services/ProjectFeedback/BuilderCommand.php @@ -0,0 +1,15 @@ +feedbacks()->create($data); + return $feedback; + } +} diff --git a/app/application/app/Services/Site/FeedbackService.php b/app/application/app/Services/Site/FeedbackService.php new file mode 100644 index 0000000..7bbe72f --- /dev/null +++ b/app/application/app/Services/Site/FeedbackService.php @@ -0,0 +1,53 @@ +getDataFeedback($send); + $data['user_id'] = $user?->id; + $this->feedbackCommandHandler->handleStore($project, $data); + }); + } catch (\Throwable $e) { + report($e); + return $this->errService($websiteTranslations->translate('Server Error')); + } + + return $this->ok($websiteTranslations->translate('site.Message sent successfully')); + } + + private function getDataFeedback(Send $send): array + { + $name = $send->getName(); + if ($name !== null) { + $name = strip_tags($name); + } + $message = $send->getMessage(); + $message = strip_tags($message, ['', '', '

', '
', '