Compare commits
147 commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3ee706c80b | ||
|
|
0c8bcc9937 | ||
|
|
93673a9034 | ||
|
|
f8c4c03941 | ||
|
|
de078eb142 | ||
|
|
14b28365bc | ||
|
|
ac738c02e0 | ||
|
|
9d081727bc | ||
|
|
7369d3c820 | ||
|
|
479cb39cc0 | ||
|
|
5262941eab | ||
|
|
c7577c9d1b | ||
|
|
bacb71185c | ||
|
|
cb192d46dc | ||
|
|
a10d9f5979 | ||
|
|
eecbb56d6c | ||
|
|
d367b1d086 | ||
|
|
9c09746f79 | ||
|
|
8488b8a51c | ||
|
|
91c42d7dc6 | ||
|
|
57692c4eae | ||
|
|
fd82054d0a | ||
|
|
f077ae0c28 | ||
|
|
2c8507829b | ||
|
|
a87a61386d | ||
|
|
078894c577 | ||
|
|
f0fa683a37 | ||
|
|
fc49072916 | ||
|
|
31e585b5a1 | ||
|
|
04049ea4c3 | ||
|
|
43d9d846cf | ||
|
|
6f40f7f3a7 | ||
|
|
2c86d0094e | ||
|
|
fee8a3ec10 | ||
|
|
f830e07f3e | ||
|
|
951fa0311e | ||
|
|
dad597ba87 | ||
|
|
73e6dd5194 | ||
|
|
f21567e028 | ||
|
|
57554f4156 | ||
|
|
046695112a | ||
|
|
576bfc8b32 | ||
|
|
13532c05ae | ||
|
|
49cac11412 | ||
|
|
3f81a0b3f6 | ||
|
|
76cd500aa8 | ||
|
|
5bbf4e531d | ||
|
|
a0ca50ad4e | ||
|
|
058979eb04 | ||
|
|
483c415d4d | ||
|
|
737d69e94f | ||
|
|
7adb1d5005 | ||
|
|
564de0d2bd | ||
|
|
d43c223101 | ||
|
|
c0f82c577d | ||
|
|
e7f989e392 | ||
|
|
5af7cddaf9 | ||
|
|
69a51c2f07 | ||
|
|
e06d3d7d25 | ||
|
|
e9d09e0693 | ||
|
|
ff0eb7aedb | ||
|
|
221e44fe2f | ||
|
|
4e2038f82a | ||
|
|
4ea4648d75 | ||
|
|
2ddb0de4b7 | ||
|
|
298465b32b | ||
|
|
95379ae856 | ||
|
|
6caeb050c1 | ||
|
|
be3a251894 | ||
|
|
06ca89f74e | ||
|
|
518c18331c | ||
|
|
cc74d8d662 | ||
|
|
6bbbcd50e5 | ||
|
|
b34151d602 | ||
|
|
bed2226f2b | ||
|
|
95376bf592 | ||
|
|
2b9fb68ebb | ||
|
|
a5fd81c630 | ||
|
|
96e6403930 | ||
|
|
aec99587c8 | ||
|
|
c6e45ad028 | ||
|
|
b6d88d87c1 | ||
|
|
cf093d44ad | ||
|
|
6aba7629ac | ||
|
|
064715a06d | ||
|
|
8c64ddca5e | ||
|
|
ebfb106781 | ||
|
|
c22b78c68b | ||
|
|
0cbf94e9c4 | ||
|
|
1d3efb4187 | ||
|
|
d556761e72 | ||
|
|
4c3c69fea0 | ||
|
|
2fb5b4c421 | ||
|
|
656dd92a70 | ||
|
|
f140ef582b | ||
|
|
c8f4d918eb | ||
|
|
d28c5bd2a5 | ||
|
|
0ba52c6f61 | ||
|
|
a20c4a2b1a | ||
|
|
30d230905a | ||
|
|
f02e040a64 | ||
|
|
e312e299e6 | ||
|
|
ee5810e129 | ||
|
|
a2a2903db1 | ||
|
|
0abde46789 | ||
|
|
de2e0cf84a | ||
|
|
35b3eee576 | ||
|
|
03ad609648 | ||
|
|
b08fe1d32d | ||
|
|
80cf95e7c1 | ||
|
|
10d399a798 | ||
|
|
8e789ca6e6 | ||
|
|
bda991087e | ||
|
|
923ac45252 | ||
|
|
d2b77f6143 | ||
|
|
a17b86466b | ||
|
|
0097f3699b | ||
|
|
ef14493201 | ||
|
|
4961bb0593 | ||
|
|
e897e969ce | ||
|
|
a351189ab3 | ||
|
|
ce0fdf9efc | ||
|
|
37f9cf0793 | ||
|
|
093413abf8 | ||
|
|
b761da48f7 | ||
|
|
231ba7eaf7 | ||
|
|
7f4ad281c0 | ||
|
|
7a9d265940 | ||
|
|
15e1c98f0d | ||
|
|
25f975d4aa | ||
|
|
6e598e8043 | ||
|
|
bafe9503cd | ||
|
|
727d16373c | ||
|
|
3d46d570f2 | ||
|
|
a771c1817b | ||
|
|
8ce5d76faa | ||
|
|
e3c3757acd | ||
|
|
fb56dbba66 | ||
|
|
58517aa01d | ||
|
|
071677f665 | ||
|
|
d097ef0ef5 | ||
|
|
1c34b2aba0 | ||
|
|
ea89408d91 | ||
|
|
9727d59a18 | ||
|
|
a94370a4d9 | ||
|
|
e5f8d4865f | ||
|
|
f9114e9a01 |
436 changed files with 49474 additions and 0 deletions
21
.dockerignore
Normal file
21
.dockerignore
Normal file
|
|
@ -0,0 +1,21 @@
|
||||||
|
.git
|
||||||
|
.git/
|
||||||
|
.gitlab/
|
||||||
|
.gitlab-ci.yml
|
||||||
|
*.md
|
||||||
|
node_modules/
|
||||||
|
npm-debug.log
|
||||||
|
.npmrc
|
||||||
|
tests/
|
||||||
|
Dockerfile*
|
||||||
|
.dockerignore
|
||||||
|
.git
|
||||||
|
.gitignore
|
||||||
|
README.md
|
||||||
|
LICENSE
|
||||||
|
.vscode
|
||||||
|
public/build/
|
||||||
|
auth.json
|
||||||
|
manifest.yaml
|
||||||
|
production-resources.yaml
|
||||||
|
staging-resources.yaml
|
||||||
21
.editorconfig
Normal file
21
.editorconfig
Normal file
|
|
@ -0,0 +1,21 @@
|
||||||
|
root = true
|
||||||
|
|
||||||
|
[*]
|
||||||
|
charset = utf-8
|
||||||
|
end_of_line = lf
|
||||||
|
insert_final_newline = true
|
||||||
|
indent_style = space
|
||||||
|
indent_size = 4
|
||||||
|
trim_trailing_whitespace = true
|
||||||
|
|
||||||
|
[*.md]
|
||||||
|
trim_trailing_whitespace = false
|
||||||
|
|
||||||
|
[*.{yml,yaml}]
|
||||||
|
indent_size = 2
|
||||||
|
|
||||||
|
[*.vue]
|
||||||
|
indent_size = 2
|
||||||
|
|
||||||
|
[docker-compose.yml]
|
||||||
|
indent_size = 4
|
||||||
37
.env.example
Normal file
37
.env.example
Normal file
|
|
@ -0,0 +1,37 @@
|
||||||
|
APP_NAME=Laravel
|
||||||
|
APP_ENV=local
|
||||||
|
APP_KEY=
|
||||||
|
APP_DEBUG=true
|
||||||
|
APP_URL=http://localhost
|
||||||
|
|
||||||
|
LOG_CHANNEL=stack
|
||||||
|
LOG_LEVEL=debug
|
||||||
|
|
||||||
|
DB_HOST=127.0.0.1
|
||||||
|
DB_DATABASE=laravel
|
||||||
|
DB_USERNAME=root
|
||||||
|
DB_PASSWORD=
|
||||||
|
|
||||||
|
MAIL_HOST=mailhog
|
||||||
|
MAIL_PORT=1025
|
||||||
|
MAIL_USERNAME=null
|
||||||
|
MAIL_PASSWORD=null
|
||||||
|
MAIL_ENCRYPTION=null
|
||||||
|
MAIL_FROM_ADDRESS=null
|
||||||
|
MAIL_FROM_NAME="${APP_NAME}"
|
||||||
|
|
||||||
|
# SENTRY
|
||||||
|
SENTRY_LARAVEL_DSN=
|
||||||
|
|
||||||
|
# UWIL ADMIN SECURE
|
||||||
|
# - LIST SEPARATED WITH ',' (Required)
|
||||||
|
UWIL_ADMIN_SECURE_CIDR_LIST=""
|
||||||
|
# - LIST SEPARATED WITH ',' (Optional)
|
||||||
|
UWIL_ADMIN_SECURE_CIDR_PROJECT_ALLOWED_LIST=""
|
||||||
|
|
||||||
|
# SAML
|
||||||
|
SAML2_DEFAULT_TENANT_UUID=
|
||||||
|
SAML2_SP_CERT_x509=
|
||||||
|
SAML2_SP_CERT_PRIVATEKEY=
|
||||||
|
SAML2_CONTACT_TECHNICAL_EMAIL=
|
||||||
|
SAML2_CONTACT_SUPPORT_EMAIL=
|
||||||
62
.eslintrc.json
Normal file
62
.eslintrc.json
Normal file
|
|
@ -0,0 +1,62 @@
|
||||||
|
{
|
||||||
|
"env": {
|
||||||
|
"browser": true,
|
||||||
|
"es2021": true
|
||||||
|
},
|
||||||
|
"extends": [
|
||||||
|
"eslint:recommended",
|
||||||
|
"plugin:vue/vue3-strongly-recommended",
|
||||||
|
"@vue/eslint-config-typescript"
|
||||||
|
],
|
||||||
|
"overrides": [
|
||||||
|
{
|
||||||
|
"files": ["*.ts", "*.vue"],
|
||||||
|
"rules": {
|
||||||
|
"no-undef": "off"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"files": ["*.vue"],
|
||||||
|
"rules": {
|
||||||
|
"indent": "off"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"parser": "vue-eslint-parser",
|
||||||
|
"parserOptions": {
|
||||||
|
"parser": "@typescript-eslint/parser",
|
||||||
|
"ecmaVersion": "latest",
|
||||||
|
"sourceType": "module"
|
||||||
|
},
|
||||||
|
"plugins": ["vue", "@typescript-eslint"],
|
||||||
|
"ignorePatterns": [
|
||||||
|
"webpack.mix.js",
|
||||||
|
"**/*.php",
|
||||||
|
"**/*.svg",
|
||||||
|
"**/vendor/*.js",
|
||||||
|
"**/vendor/**/*.js",
|
||||||
|
"**/vendor/**/*.scss"
|
||||||
|
],
|
||||||
|
"rules": {
|
||||||
|
"indent": ["error", 4, { "SwitchCase": 1 }],
|
||||||
|
"linebreak-style": ["error", "unix"],
|
||||||
|
"quotes": ["error", "double"],
|
||||||
|
"semi": ["error", "always"],
|
||||||
|
"vue/script-indent": ["error", 2, { "baseIndent": 0, "switchCase": 1 }],
|
||||||
|
"vue/one-component-per-file": "off"
|
||||||
|
},
|
||||||
|
"settings": {
|
||||||
|
"import/resolver": {
|
||||||
|
"node": {
|
||||||
|
"extensions": [".js", ".vue", ".ts", ".d.ts"]
|
||||||
|
},
|
||||||
|
"alias": {
|
||||||
|
"extensions": [".vue", ".js", ".ts", ".scss", ".d.ts"],
|
||||||
|
"map": [
|
||||||
|
["~", "./resources/assets"],
|
||||||
|
["@", "./node_modules"]
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
10
.gitattributes
vendored
Normal file
10
.gitattributes
vendored
Normal file
|
|
@ -0,0 +1,10 @@
|
||||||
|
* text=auto
|
||||||
|
|
||||||
|
*.blade.php diff=html
|
||||||
|
*.css diff=css
|
||||||
|
*.html diff=html
|
||||||
|
*.md diff=markdown
|
||||||
|
*.php diff=php
|
||||||
|
|
||||||
|
/.github export-ignore
|
||||||
|
CHANGELOG.md export-ignore
|
||||||
21
.gitignore
vendored
Normal file
21
.gitignore
vendored
Normal file
|
|
@ -0,0 +1,21 @@
|
||||||
|
/node_modules
|
||||||
|
/public/hot
|
||||||
|
/public/storage
|
||||||
|
/public/build
|
||||||
|
/public/medias
|
||||||
|
/public/uploads
|
||||||
|
/public/cache
|
||||||
|
/public/sitemap.xml
|
||||||
|
/storage/*.key
|
||||||
|
/vendor
|
||||||
|
.env
|
||||||
|
.phpunit.result.cache
|
||||||
|
docker-compose.override.yml
|
||||||
|
Homestead.json
|
||||||
|
Homestead.yaml
|
||||||
|
npm-debug.log
|
||||||
|
yarn-error.log
|
||||||
|
/.idea
|
||||||
|
/.vscode
|
||||||
|
auth.json
|
||||||
|
.npmrc
|
||||||
24
.gitlab-ci.yml
Normal file
24
.gitlab-ci.yml
Normal file
|
|
@ -0,0 +1,24 @@
|
||||||
|
include:
|
||||||
|
- project: "ldc-web/shared-ci"
|
||||||
|
ref: "main"
|
||||||
|
file: "lint/lint.yml"
|
||||||
|
- project: "ldc-web/shared-ci"
|
||||||
|
ref: "main"
|
||||||
|
file: "security/security.yml"
|
||||||
|
- project: "ldc-web/shared-ci"
|
||||||
|
ref: "main"
|
||||||
|
file: "build/docker-multistage.yml"
|
||||||
|
- project: "ldc-web/shared-ci"
|
||||||
|
ref: "main"
|
||||||
|
file: "test/test.yml"
|
||||||
|
- project: "ldc-web/shared-ci"
|
||||||
|
ref: "main"
|
||||||
|
file: "deploy/deploy.yml"
|
||||||
|
|
||||||
|
|
||||||
|
stages:
|
||||||
|
- lint
|
||||||
|
- security
|
||||||
|
- build
|
||||||
|
- test
|
||||||
|
- deploy
|
||||||
3
.husky/commit-msg
Normal file
3
.husky/commit-msg
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
npx --no -- commitlint --config node_modules/@uwil/husky-config/commitlint.config.cjs --edit $1
|
||||||
14
.styleci.yml
Normal file
14
.styleci.yml
Normal file
|
|
@ -0,0 +1,14 @@
|
||||||
|
php:
|
||||||
|
preset: laravel
|
||||||
|
version: 8
|
||||||
|
disabled:
|
||||||
|
- no_unused_imports
|
||||||
|
finder:
|
||||||
|
not-name:
|
||||||
|
- index.php
|
||||||
|
- server.php
|
||||||
|
js:
|
||||||
|
finder:
|
||||||
|
not-name:
|
||||||
|
- webpack.mix.js
|
||||||
|
css: true
|
||||||
45
.stylelintrc.json
Normal file
45
.stylelintrc.json
Normal file
|
|
@ -0,0 +1,45 @@
|
||||||
|
{
|
||||||
|
"extends": [
|
||||||
|
"stylelint-config-standard-scss",
|
||||||
|
"stylelint-config-sass-guidelines",
|
||||||
|
"stylelint-config-idiomatic-order"
|
||||||
|
],
|
||||||
|
"plugins": [
|
||||||
|
"stylelint-scss"
|
||||||
|
],
|
||||||
|
"files": [
|
||||||
|
"**/*.css",
|
||||||
|
"**/*.scss"
|
||||||
|
],
|
||||||
|
"ignoreFiles": [
|
||||||
|
"**/vendor/*.css",
|
||||||
|
"**/vendor/**/*.css",
|
||||||
|
"**/vendor/*.scss",
|
||||||
|
"**/vendor/**/*.scss",
|
||||||
|
"**/vendor/*.sass",
|
||||||
|
"**/vendor/**/*.sass",
|
||||||
|
"**/vendor/*.less",
|
||||||
|
"**/vendor/**/*.less"
|
||||||
|
],
|
||||||
|
"customSyntax": "postcss-scss",
|
||||||
|
"rules": {
|
||||||
|
"declaration-property-value-no-unknown": true,
|
||||||
|
"selector-max-id": null,
|
||||||
|
"color-no-invalid-hex": true,
|
||||||
|
"order/properties-alphabetical-order": null,
|
||||||
|
"max-nesting-depth": 5,
|
||||||
|
"selector-max-compound-selectors": 5,
|
||||||
|
"declaration-colon-newline-after": null,
|
||||||
|
"value-list-comma-newline-after": null,
|
||||||
|
"scss/double-slash-comment-empty-line-before": [
|
||||||
|
"always",
|
||||||
|
{
|
||||||
|
"ignore": [
|
||||||
|
"between-comments",
|
||||||
|
"stylelint-commands",
|
||||||
|
"inside-block"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
47
Dockerfile
Normal file
47
Dockerfile
Normal file
|
|
@ -0,0 +1,47 @@
|
||||||
|
FROM composer:2.9.2 AS vendor
|
||||||
|
|
||||||
|
ARG GITLAB_USER
|
||||||
|
ARG GITLAB_PASSWORD
|
||||||
|
|
||||||
|
COPY database/ database/
|
||||||
|
|
||||||
|
COPY composer.json composer.json
|
||||||
|
COPY composer.lock composer.lock
|
||||||
|
|
||||||
|
RUN composer config gitlab-token.lab.s-web.fr ${GITLAB_USER} ${GITLAB_PASSWORD}
|
||||||
|
RUN composer install \
|
||||||
|
--ignore-platform-reqs \
|
||||||
|
--no-interaction \
|
||||||
|
--no-plugins \
|
||||||
|
--no-scripts \
|
||||||
|
--no-dev \
|
||||||
|
--prefer-dist
|
||||||
|
|
||||||
|
FROM node:20.18 AS frontend
|
||||||
|
|
||||||
|
ARG GITLAB_PASSWORD
|
||||||
|
|
||||||
|
RUN mkdir -p /app/public/build
|
||||||
|
|
||||||
|
COPY package*.json tsconfig.json vite.config.ts .eslintrc.json .stylelintrc.json /app/
|
||||||
|
COPY resources/ /app/resources/
|
||||||
|
WORKDIR /app
|
||||||
|
RUN npm config set @ldc-web:registry https://lab.s-web.fr/api/v4/packages/npm/
|
||||||
|
RUN npm config set -- '//lab.s-web.fr/api/v4/packages/npm/:_authToken' "${GITLAB_PASSWORD}"
|
||||||
|
RUN npm config set @uwil:registry=https://lab.s-web.fr/api/v4/groups/ldc-web/-/packages/npm/
|
||||||
|
RUN npm config set -- '//lab.s-web.fr/api/v4/groups/ldc-web/-/packages/npm/:_authToken' "${GITLAB_PASSWORD}"
|
||||||
|
RUN npm ci --no-audit --no-fund --loglevel=error && npm run prod
|
||||||
|
|
||||||
|
|
||||||
|
FROM registry.s-web.fr/ldc-web/projets/ldc-web-images-docker/uwil-production-8.5:${PHP_8_5_TAG}
|
||||||
|
|
||||||
|
ARG USER=www-data
|
||||||
|
|
||||||
|
COPY --chown=${USER}:${USER} . /app/srcs
|
||||||
|
COPY --chown=${USER}:${USER} ./resources/favicon /app/srcs/public
|
||||||
|
COPY --from=vendor --chown=${USER}:${USER} /app/vendor/ /app/srcs/vendor/
|
||||||
|
COPY --from=frontend --chown=${USER}:${USER} /app/public/build/ /app/srcs/public/build/
|
||||||
|
|
||||||
|
USER root
|
||||||
|
RUN chmod -R 755 /app/srcs/storage
|
||||||
|
USER ${USER}
|
||||||
13
README.md
13
README.md
|
|
@ -0,0 +1,13 @@
|
||||||
|
# Nexurl
|
||||||
|
## Installation
|
||||||
|
```
|
||||||
|
cp .env.example .env
|
||||||
|
php artisan key:generate
|
||||||
|
php artisan migrate
|
||||||
|
php artisan storage:link
|
||||||
|
npm i && npm run prod
|
||||||
|
```
|
||||||
|
Création d'un administrateur :
|
||||||
|
```
|
||||||
|
php artisan user:create
|
||||||
|
```
|
||||||
202
app/Console/Commands/UserCreate.php
Normal file
202
app/Console/Commands/UserCreate.php
Normal file
|
|
@ -0,0 +1,202 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Console\Commands;
|
||||||
|
|
||||||
|
use App\Enums\Users\RoleEnum;
|
||||||
|
use App\Models\User;
|
||||||
|
use Illuminate\Console\Command;
|
||||||
|
use Illuminate\Support\Facades\Validator;
|
||||||
|
use Illuminate\Validation\ValidationException;
|
||||||
|
use Illuminate\Support\Str;
|
||||||
|
|
||||||
|
class UserCreate extends Command
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* The name and signature of the console command.
|
||||||
|
*
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
protected $signature = 'uwil:user:create';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The console command description.
|
||||||
|
*
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
protected $description = 'Create a backend admin user';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The first name of the new user.
|
||||||
|
*
|
||||||
|
* @var string|null
|
||||||
|
*/
|
||||||
|
protected $first_name;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The last name of the new user.
|
||||||
|
*
|
||||||
|
* @var string|null
|
||||||
|
*/
|
||||||
|
protected $last_name;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The email of the new user.
|
||||||
|
*
|
||||||
|
* @var string|null
|
||||||
|
*/
|
||||||
|
protected $email;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The role of the new user.
|
||||||
|
*
|
||||||
|
* @var string|null
|
||||||
|
*/
|
||||||
|
protected $role;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Execute the console command.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function handle(): void
|
||||||
|
{
|
||||||
|
$this->first_name = null;
|
||||||
|
$this->addFirstName();
|
||||||
|
|
||||||
|
$this->last_name = null;
|
||||||
|
$this->addLastName();
|
||||||
|
|
||||||
|
$this->email = null;
|
||||||
|
$this->addEmail();
|
||||||
|
|
||||||
|
$this->role = null;
|
||||||
|
$this->addRole();
|
||||||
|
|
||||||
|
$user = new User([
|
||||||
|
'firstname' => $this->first_name,
|
||||||
|
'lastname' => $this->last_name,
|
||||||
|
'email' => $this->email,
|
||||||
|
'role' => $this->role,
|
||||||
|
]);
|
||||||
|
$user->saveOrFail();
|
||||||
|
|
||||||
|
$this->info('User created 👌');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add a first name to the user.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
private function addFirstName(): void
|
||||||
|
{
|
||||||
|
while (is_null($this->first_name)) {
|
||||||
|
try {
|
||||||
|
$tmp = $this->ask('Type the wanted user first name', 'Admin');
|
||||||
|
$validator = Validator::make(['first_name' => $tmp], [
|
||||||
|
'first_name' => 'required|min:3|max:255'
|
||||||
|
]);
|
||||||
|
$validator->validate();
|
||||||
|
$this->first_name = $tmp;
|
||||||
|
} catch (ValidationException $e) {
|
||||||
|
$this->error(sprintf(
|
||||||
|
'Please provide a valid first name %s',
|
||||||
|
\implode(',', collect($e->errors())->flatten()->all())
|
||||||
|
));
|
||||||
|
continue;
|
||||||
|
} //end try
|
||||||
|
}; //end while
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add a last name to the user.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
private function addLastName(): void
|
||||||
|
{
|
||||||
|
while (is_null($this->last_name)) {
|
||||||
|
try {
|
||||||
|
$tmp = $this->ask('Type the wanted user last name', 'Saïtama');
|
||||||
|
$validator = Validator::make(['last_name' => $tmp], [
|
||||||
|
'last_name' => 'required|min:3|max:255'
|
||||||
|
]);
|
||||||
|
$validator->validate();
|
||||||
|
$this->last_name = $tmp;
|
||||||
|
} catch (ValidationException $e) {
|
||||||
|
$this->error(sprintf(
|
||||||
|
'Please provide a valid last_name %s',
|
||||||
|
\implode(',', collect($e->errors())->flatten()->all())
|
||||||
|
));
|
||||||
|
continue;
|
||||||
|
} //end try
|
||||||
|
}; //end while
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add an email to the user.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
private function addEmail(): void
|
||||||
|
{
|
||||||
|
while (is_null($this->email)) {
|
||||||
|
try {
|
||||||
|
$tmp = $this->ask('Type the wanted user email', 'equipe.web@ldc.fr');
|
||||||
|
$validator = Validator::make(['email' => $tmp], [
|
||||||
|
'email' => 'required|unique:users,email|email:rfc,dns'
|
||||||
|
]);
|
||||||
|
$validator->validate();
|
||||||
|
$this->email = $tmp;
|
||||||
|
} catch (ValidationException $e) {
|
||||||
|
$this->error(sprintf(
|
||||||
|
'Please provide a valid email %s',
|
||||||
|
\implode(',', collect($e->errors())->flatten()->all())
|
||||||
|
));
|
||||||
|
continue;
|
||||||
|
} //end try
|
||||||
|
}; //end while
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add a role to the user.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
private function addRole(): void
|
||||||
|
{
|
||||||
|
while (is_null($this->role)) {
|
||||||
|
try {
|
||||||
|
$tmp = $this->choice(
|
||||||
|
'Select his role',
|
||||||
|
[
|
||||||
|
Str::ucFirst(RoleEnum::conceptor->label()),
|
||||||
|
Str::ucFirst(RoleEnum::admin->label()),
|
||||||
|
Str::ucFirst(RoleEnum::visitor->label())
|
||||||
|
],
|
||||||
|
Str::ucFirst(RoleEnum::visitor->label()),
|
||||||
|
);
|
||||||
|
if ($tmp === Str::ucFirst(RoleEnum::conceptor->label())) {
|
||||||
|
$tmp = RoleEnum::conceptor;
|
||||||
|
}
|
||||||
|
if ($tmp === Str::ucFirst(RoleEnum::admin->label())) {
|
||||||
|
$tmp = RoleEnum::admin;
|
||||||
|
}
|
||||||
|
if ($tmp === Str::ucFirst(RoleEnum::visitor->label())) {
|
||||||
|
$tmp = RoleEnum::visitor;
|
||||||
|
}
|
||||||
|
$validator = Validator::make(['role' => $tmp], [
|
||||||
|
'role' => 'required'
|
||||||
|
]);
|
||||||
|
$validator->validate();
|
||||||
|
$this->role = $tmp;
|
||||||
|
} catch (ValidationException $e) {
|
||||||
|
$this->error(sprintf(
|
||||||
|
'Please choose from the selection %s',
|
||||||
|
\implode(',', collect($e->errors())->flatten()->all())
|
||||||
|
));
|
||||||
|
continue;
|
||||||
|
} //end try
|
||||||
|
}; //end while
|
||||||
|
}
|
||||||
|
}
|
||||||
31
app/Console/Kernel.php
Normal file
31
app/Console/Kernel.php
Normal file
|
|
@ -0,0 +1,31 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Console;
|
||||||
|
|
||||||
|
use Illuminate\Console\Scheduling\Schedule;
|
||||||
|
use Illuminate\Foundation\Console\Kernel as ConsoleKernel;
|
||||||
|
|
||||||
|
class Kernel extends ConsoleKernel
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Define the application's command schedule.
|
||||||
|
*
|
||||||
|
* @param \Illuminate\Console\Scheduling\Schedule $schedule
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
protected function schedule(Schedule $schedule)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Register the commands for the application.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
protected function commands()
|
||||||
|
{
|
||||||
|
$this->load(__DIR__ . '/Commands');
|
||||||
|
|
||||||
|
require base_path('routes/console.php');
|
||||||
|
}
|
||||||
|
}
|
||||||
47
app/Enums/ActivityLogs/ActivityLogsEventEnum.php
Normal file
47
app/Enums/ActivityLogs/ActivityLogsEventEnum.php
Normal file
|
|
@ -0,0 +1,47 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Enums\ActivityLogs;
|
||||||
|
|
||||||
|
use Uwil\LaravelCommon\Traits\Enums\BaseEnum;
|
||||||
|
|
||||||
|
enum ActivityLogsEventEnum: int
|
||||||
|
{
|
||||||
|
use BaseEnum;
|
||||||
|
|
||||||
|
case created = 1;
|
||||||
|
case updated = 2;
|
||||||
|
case duplicated = 3;
|
||||||
|
case deleted = 4;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Optionnal labels definition
|
||||||
|
*
|
||||||
|
* @phpstan-ignore-next-line
|
||||||
|
*/
|
||||||
|
private const LABELS = [
|
||||||
|
self::created->name => 'Création',
|
||||||
|
self::updated->name => 'Mise à jour',
|
||||||
|
self::duplicated->name => 'Duplication',
|
||||||
|
self::deleted->name => 'Suppression',
|
||||||
|
];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Custom added classes definition
|
||||||
|
*/
|
||||||
|
private const BOOTSTRAPCLASS = [
|
||||||
|
self::created->name => 'success',
|
||||||
|
self::updated->name => 'warning',
|
||||||
|
self::duplicated->name => 'secondary',
|
||||||
|
self::deleted->name => 'danger',
|
||||||
|
];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get Class
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function bootstrapClass(): string
|
||||||
|
{
|
||||||
|
return self::BOOTSTRAPCLASS[$this->name];
|
||||||
|
}
|
||||||
|
}
|
||||||
25
app/Enums/Api/ApiPeriodEnum.php
Normal file
25
app/Enums/Api/ApiPeriodEnum.php
Normal file
|
|
@ -0,0 +1,25 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Enums\Api;
|
||||||
|
|
||||||
|
use Uwil\LaravelCommon\Traits\Enums\BaseEnum;
|
||||||
|
|
||||||
|
enum ApiPeriodEnum: int
|
||||||
|
{
|
||||||
|
use BaseEnum;
|
||||||
|
|
||||||
|
case day = 1;
|
||||||
|
case week = 2;
|
||||||
|
case month = 3;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Optionnal labels definition.
|
||||||
|
*
|
||||||
|
* @phpstan-ignore-next-line
|
||||||
|
*/
|
||||||
|
private const LABELS = [
|
||||||
|
self::day->name => 'jour',
|
||||||
|
self::week->name => 'semaine',
|
||||||
|
self::month->name => 'mois',
|
||||||
|
];
|
||||||
|
}
|
||||||
16
app/Enums/Pagination/ItemsPerPaginationEnum.php
Normal file
16
app/Enums/Pagination/ItemsPerPaginationEnum.php
Normal file
|
|
@ -0,0 +1,16 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Enums\Pagination;
|
||||||
|
|
||||||
|
use Uwil\LaravelCommon\Traits\Enums\BaseEnum;
|
||||||
|
|
||||||
|
enum ItemsPerPaginationEnum: int
|
||||||
|
{
|
||||||
|
use BaseEnum;
|
||||||
|
|
||||||
|
case five = 5;
|
||||||
|
case twelve = 12;
|
||||||
|
case twentyfive = 25;
|
||||||
|
case fifty = 50;
|
||||||
|
case onehundred = 100;
|
||||||
|
}
|
||||||
25
app/Enums/PeriodEnum.php
Normal file
25
app/Enums/PeriodEnum.php
Normal file
|
|
@ -0,0 +1,25 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Enums;
|
||||||
|
|
||||||
|
use Uwil\LaravelCommon\Traits\Enums\BaseEnum;
|
||||||
|
|
||||||
|
enum PeriodEnum: int
|
||||||
|
{
|
||||||
|
use BaseEnum;
|
||||||
|
|
||||||
|
case day = 1;
|
||||||
|
case week = 2;
|
||||||
|
case month = 3;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Optionnal labels definition.
|
||||||
|
*
|
||||||
|
* @phpstan-ignore-next-line
|
||||||
|
*/
|
||||||
|
private const LABELS = [
|
||||||
|
self::day->name => 'jour',
|
||||||
|
self::week->name => 'semaine',
|
||||||
|
self::month->name => 'mois',
|
||||||
|
];
|
||||||
|
}
|
||||||
27
app/Enums/Users/RoleEnum.php
Normal file
27
app/Enums/Users/RoleEnum.php
Normal file
|
|
@ -0,0 +1,27 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Enums\Users;
|
||||||
|
|
||||||
|
use Uwil\LaravelCommon\Traits\Enums\BaseEnum;
|
||||||
|
|
||||||
|
enum RoleEnum: int
|
||||||
|
{
|
||||||
|
use BaseEnum;
|
||||||
|
|
||||||
|
case conceptor = 1;
|
||||||
|
case admin = 2;
|
||||||
|
case marketing = 3;
|
||||||
|
case visitor = 99;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Optionnal labels definition.
|
||||||
|
*
|
||||||
|
* @phpstan-ignore-next-line
|
||||||
|
*/
|
||||||
|
private const LABELS = [
|
||||||
|
self::conceptor->name => 'enums.user-role.conceptor',
|
||||||
|
self::admin->name => 'enums.user-role.admin',
|
||||||
|
self::marketing->name => 'enums.user-role.marketing',
|
||||||
|
self::visitor->name => 'enums.user-role.visitor',
|
||||||
|
];
|
||||||
|
}
|
||||||
48
app/Exceptions/Handler.php
Normal file
48
app/Exceptions/Handler.php
Normal file
|
|
@ -0,0 +1,48 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Exceptions;
|
||||||
|
|
||||||
|
use Illuminate\Foundation\Exceptions\Handler as ExceptionHandler;
|
||||||
|
use Throwable;
|
||||||
|
|
||||||
|
class Handler extends ExceptionHandler
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* A list of exception types with their corresponding custom log levels.
|
||||||
|
*
|
||||||
|
* @var array<class-string<\Throwable>, \Psr\Log\LogLevel::*>
|
||||||
|
*/
|
||||||
|
protected $levels = [];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A list of the exception types that are not reported.
|
||||||
|
*
|
||||||
|
* @var array<int, class-string<\Throwable>>
|
||||||
|
*/
|
||||||
|
protected $dontReport = [];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A list of the inputs that are never flashed to the session on validation exceptions.
|
||||||
|
*
|
||||||
|
* @var array<int, string>
|
||||||
|
*/
|
||||||
|
protected $dontFlash = [
|
||||||
|
'current_password',
|
||||||
|
'password',
|
||||||
|
'password_confirmation',
|
||||||
|
];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Register the exception handling callbacks for the application.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function register()
|
||||||
|
{
|
||||||
|
$this->reportable(function (Throwable $e) {
|
||||||
|
if (app()->bound('sentry')) {
|
||||||
|
app('sentry')->captureException($e);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
60
app/Http/Controllers/Api/BrandController.php
Normal file
60
app/Http/Controllers/Api/BrandController.php
Normal file
|
|
@ -0,0 +1,60 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Controllers\Api;
|
||||||
|
|
||||||
|
use App\Http\Controllers\Controller;
|
||||||
|
use App\Http\Resources\BrandCollection;
|
||||||
|
use App\Http\Resources\BrandResource;
|
||||||
|
use App\Http\Resources\UrlCollection;
|
||||||
|
use App\Models\Brand;
|
||||||
|
use App\Models\Service;
|
||||||
|
|
||||||
|
class BrandController extends Controller
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Returns a listing of the brands.
|
||||||
|
*
|
||||||
|
* @return \App\Http\Resources\BrandCollection
|
||||||
|
*/
|
||||||
|
public function index()
|
||||||
|
{
|
||||||
|
return new BrandCollection(Brand::paginate(25));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the specified brand.
|
||||||
|
*
|
||||||
|
* @param \Illuminate\Http\Request $request
|
||||||
|
* @param Brand $brand
|
||||||
|
* @return \App\Http\Resources\BrandResource
|
||||||
|
*
|
||||||
|
* @throws \Exception If the brand isn't the brand associated to the service.
|
||||||
|
*/
|
||||||
|
public function show(\Illuminate\Http\Request $request, Brand $brand)
|
||||||
|
{
|
||||||
|
$service = Service::query()->where('bearer_token', $request->bearer_token)->firstOrFail();
|
||||||
|
if ($brand != $service->brand) {
|
||||||
|
throw new \Exception('Invalid brand.');
|
||||||
|
};
|
||||||
|
|
||||||
|
return new BrandResource($brand);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the urls for a given brand.
|
||||||
|
*
|
||||||
|
* @param \Illuminate\Http\Request $request
|
||||||
|
* @param Brand $brand
|
||||||
|
* @return \App\Http\Resources\UrlCollection
|
||||||
|
*
|
||||||
|
* @throws \Exception If the brand isn't the brand associated to the service.
|
||||||
|
*/
|
||||||
|
public function getUrls(\Illuminate\Http\Request $request, Brand $brand)
|
||||||
|
{
|
||||||
|
$service = Service::query()->where('bearer_token', $request->bearer_token)->firstOrFail();
|
||||||
|
if ($brand != $service->brand) {
|
||||||
|
throw new \Exception('Invalid brand.');
|
||||||
|
};
|
||||||
|
return new UrlCollection($brand->urls);
|
||||||
|
}
|
||||||
|
}
|
||||||
151
app/Http/Controllers/Api/UrlController.php
Normal file
151
app/Http/Controllers/Api/UrlController.php
Normal file
|
|
@ -0,0 +1,151 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Controllers\Api;
|
||||||
|
|
||||||
|
use App\Enums\Api\ApiPeriodEnum;
|
||||||
|
use App\Http\Controllers\Controller;
|
||||||
|
use App\Http\Requests\Api\GetStatsRequest;
|
||||||
|
use App\Http\Resources\UrlCollection;
|
||||||
|
use App\Http\Resources\UrlHistoryCollection;
|
||||||
|
use App\Http\Resources\UrlResource;
|
||||||
|
use App\Models\Url;
|
||||||
|
use Carbon\Carbon;
|
||||||
|
use Carbon\CarbonPeriod;
|
||||||
|
use DateTime;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
|
||||||
|
class UrlController extends Controller
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Returns a listing of the urls.
|
||||||
|
*
|
||||||
|
* @return \App\Http\Resources\UrlCollection
|
||||||
|
*/
|
||||||
|
public function index()
|
||||||
|
{
|
||||||
|
return new UrlCollection(Url::paginate(25));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the specified url.
|
||||||
|
*
|
||||||
|
* @param Url $url
|
||||||
|
* @return \App\Http\Resources\UrlResource
|
||||||
|
*/
|
||||||
|
public function show(Url $url)
|
||||||
|
{
|
||||||
|
return new UrlResource($url);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the history for a given url.
|
||||||
|
*
|
||||||
|
* @param Url $url
|
||||||
|
* @param Request $request
|
||||||
|
* @return \Illuminate\Http\Response|\App\Http\Resources\UrlHistoryCollection
|
||||||
|
*/
|
||||||
|
public function getHistory(Url $url, Request $request)
|
||||||
|
{
|
||||||
|
// Set start date with request or now - 1 year.
|
||||||
|
if ($request->start) {
|
||||||
|
$start = $request->start;
|
||||||
|
$start = DateTime::createFromFormat('dmY', $start);
|
||||||
|
} else {
|
||||||
|
$start = now()->modify('-1 year');
|
||||||
|
}
|
||||||
|
// Set end date with request or now.
|
||||||
|
if ($request->end) {
|
||||||
|
$end = $request->end;
|
||||||
|
$end = DateTime::createFromFormat('dmY', $end)->modify('+1 day');
|
||||||
|
} else {
|
||||||
|
$end = now();
|
||||||
|
}
|
||||||
|
// Check if the difference bewteen end and start is < than 1 year.
|
||||||
|
if ($start->diff($end)->days > 366) {
|
||||||
|
return response('Date interval too long', 403);
|
||||||
|
};
|
||||||
|
$history = $url->history()
|
||||||
|
->where('start_date', '>', $start)
|
||||||
|
->where('end_date', '<', $end)
|
||||||
|
->get();
|
||||||
|
return new UrlHistoryCollection($history);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the stats for the given url.
|
||||||
|
*
|
||||||
|
* @param \App\Http\Requests\Api\GetStatsRequest $request
|
||||||
|
* @param Url $url
|
||||||
|
* @return \Illuminate\Http\Response|\Illuminate\Http\JsonResponse
|
||||||
|
*/
|
||||||
|
public function getStats(GetStatsRequest $request, Url $url)
|
||||||
|
{
|
||||||
|
$period = ApiPeriodEnum::make($request->period, 'name');
|
||||||
|
$start = Carbon::make($request->start);
|
||||||
|
$end = Carbon::make($request->end);
|
||||||
|
// Check if the difference bewteen end and start is < than 1 year.
|
||||||
|
if ($start->diff($end)->days > 366) {
|
||||||
|
return response('Date interval too long', 403);
|
||||||
|
};
|
||||||
|
|
||||||
|
$stats = [];
|
||||||
|
switch ($period->label()) {
|
||||||
|
case \App\Enums\Api\ApiPeriodEnum::day->label():
|
||||||
|
$carbonPeriod = CarbonPeriod::create(
|
||||||
|
$start->format('Y-m-d'),
|
||||||
|
$end->format('Y-m-d')
|
||||||
|
)->toArray();
|
||||||
|
$dbStats = $url->stats()
|
||||||
|
->whereDate('date', '>', $start)
|
||||||
|
->whereDate('date', '<=', $end)
|
||||||
|
->get(['date', 'calls']);
|
||||||
|
|
||||||
|
foreach ($carbonPeriod as $day) {
|
||||||
|
$stats[] = $dbStats->where('date', $day)->isNotEmpty() ?
|
||||||
|
$dbStats->where('date', $day)->first()->calls :
|
||||||
|
0;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case \App\Enums\Api\ApiPeriodEnum::week->label():
|
||||||
|
// Create a Carbon Period for weeks to make only one query per week.
|
||||||
|
$carbonPeriod = CarbonPeriod::create(
|
||||||
|
$start->startOfWeek()->format('Y-m-d'),
|
||||||
|
$end->endOfWeek()->format('Y-m-d')
|
||||||
|
)->weeks()->toArray();
|
||||||
|
|
||||||
|
foreach ($carbonPeriod as $week) {
|
||||||
|
$stats[] = $url->stats()
|
||||||
|
->whereIn(
|
||||||
|
'date',
|
||||||
|
CarbonPeriod::create(
|
||||||
|
$week->startOfWeek(Carbon::MONDAY)->format('Y-m-d'),
|
||||||
|
$week->endOfWeek(Carbon::SUNDAY)->format('Y-m-d')
|
||||||
|
)->toArray()
|
||||||
|
)
|
||||||
|
->sum('calls');
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case \App\Enums\Api\ApiPeriodEnum::month->label():
|
||||||
|
// Create a Carbon Period for months to make only one query per month.
|
||||||
|
$carbonPeriod = CarbonPeriod::create(
|
||||||
|
$start->startOfMonth()->format('Y-m-d'),
|
||||||
|
$end->endOfMonth()->format('Y-m-d')
|
||||||
|
)->months()->toArray();
|
||||||
|
|
||||||
|
foreach ($carbonPeriod as $month) {
|
||||||
|
$stats[] = $url->stats()
|
||||||
|
->whereIn(
|
||||||
|
'date',
|
||||||
|
CarbonPeriod::create(
|
||||||
|
$month->startOfMonth()->format('Y-m-d'),
|
||||||
|
$month->endOfMonth()->format('Y-m-d')
|
||||||
|
)->toArray()
|
||||||
|
)
|
||||||
|
->sum('calls');
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
} //end switch
|
||||||
|
|
||||||
|
return \response()->json($stats);
|
||||||
|
}
|
||||||
|
}
|
||||||
79
app/Http/Controllers/Bo/ActivityLogController.php
Normal file
79
app/Http/Controllers/Bo/ActivityLogController.php
Normal file
|
|
@ -0,0 +1,79 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Controllers\Bo;
|
||||||
|
|
||||||
|
use App\Http\Controllers\Controller;
|
||||||
|
use App\Models\ActivityLog;
|
||||||
|
use App\Traits\Controllers\ChangesModelOrder;
|
||||||
|
use App\Traits\Controllers\UpdateModelPublished;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
|
||||||
|
class ActivityLogController extends Controller
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Display a listing of the resource.
|
||||||
|
*
|
||||||
|
* @param \Illuminate\Http\Request $request
|
||||||
|
* @return \Illuminate\Contracts\View\View
|
||||||
|
*/
|
||||||
|
public function index(Request $request): \Illuminate\Contracts\View\View
|
||||||
|
{
|
||||||
|
$this->authorize('isConceptor');
|
||||||
|
/** @var \Illuminate\Database\Eloquent\Builder $query */
|
||||||
|
$query = ActivityLog::query()->with('user');
|
||||||
|
|
||||||
|
/** @var \App\Models\User|null $userModel */
|
||||||
|
$userModel = $request->route()->parameter('user');
|
||||||
|
if ($userModel) {
|
||||||
|
$query->where('user_id', $userModel->getKey());
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @var string $search Search field */
|
||||||
|
$search = $request->search;
|
||||||
|
if ($search) {
|
||||||
|
$this->searchQuery(
|
||||||
|
$query,
|
||||||
|
$search,
|
||||||
|
null,
|
||||||
|
'model_class',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
$searchFields = \implode(', ', [
|
||||||
|
trans('validation.custom.target'),
|
||||||
|
]);
|
||||||
|
|
||||||
|
/** Sort columns with a query */
|
||||||
|
$this->sortQuery($query);
|
||||||
|
|
||||||
|
/** Custom pagination */
|
||||||
|
$activitylogModels = $this->paginate($query);
|
||||||
|
|
||||||
|
return view('bo.pages.activity_logs.index', compact(
|
||||||
|
'activitylogModels',
|
||||||
|
'search',
|
||||||
|
'searchFields',
|
||||||
|
'userModel'
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Show the form for editing the specified resource.
|
||||||
|
*
|
||||||
|
* @param \App\Models\ActivityLog $activity_log
|
||||||
|
* @return \Illuminate\Contracts\View\View
|
||||||
|
*/
|
||||||
|
public function show(ActivityLog $activity_log): \Illuminate\Contracts\View\View
|
||||||
|
{
|
||||||
|
$this->authorize('isConceptor');
|
||||||
|
$targetModelClass = $activity_log->model_class;
|
||||||
|
$targetModel = $activity_log->model_class::where(
|
||||||
|
(new $targetModelClass())->getRouteKeyName(),
|
||||||
|
$activity_log->model_id
|
||||||
|
)->first();
|
||||||
|
|
||||||
|
return view('bo.pages.activity_logs.show', [
|
||||||
|
'activitylogModel' => $activity_log,
|
||||||
|
'targetModel' => $targetModel,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
51
app/Http/Controllers/Bo/Auth/LoginController.php
Normal file
51
app/Http/Controllers/Bo/Auth/LoginController.php
Normal file
|
|
@ -0,0 +1,51 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Controllers\Bo\Auth;
|
||||||
|
|
||||||
|
use App\Http\Controllers\Controller;
|
||||||
|
use Illuminate\Foundation\Auth\AuthenticatesUsers;
|
||||||
|
use Illuminate\Support\Facades\Auth;
|
||||||
|
|
||||||
|
class LoginController extends Controller
|
||||||
|
{
|
||||||
|
use AuthenticatesUsers;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new controller instance.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function __construct()
|
||||||
|
{
|
||||||
|
$this->middleware('guest:backend')->except('logout');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the guard to be used during authentication.
|
||||||
|
*
|
||||||
|
* @return \Illuminate\Contracts\Auth\StatefulGuard
|
||||||
|
*/
|
||||||
|
protected function guard()
|
||||||
|
{
|
||||||
|
return Auth::guard('backend');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Show the application's login form.
|
||||||
|
*
|
||||||
|
* @return \Illuminate\View\View
|
||||||
|
*/
|
||||||
|
public function showLoginForm()
|
||||||
|
{
|
||||||
|
return view('bo.auth.login');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Where to redirect users after login.
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
protected function redirectTo(): string
|
||||||
|
{
|
||||||
|
return response()->redirectToIntended()->getTargetUrl();
|
||||||
|
}
|
||||||
|
}
|
||||||
31
app/Http/Controllers/Bo/Auth/ResetPasswordController.php
Normal file
31
app/Http/Controllers/Bo/Auth/ResetPasswordController.php
Normal file
|
|
@ -0,0 +1,31 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Controllers\Bo\Auth;
|
||||||
|
|
||||||
|
use App\Http\Controllers\Controller;
|
||||||
|
use Illuminate\Foundation\Auth\ResetsPasswords;
|
||||||
|
use Illuminate\Support\Facades\Auth;
|
||||||
|
|
||||||
|
class ResetPasswordController extends Controller
|
||||||
|
{
|
||||||
|
use ResetsPasswords;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the guard to be used during authentication.
|
||||||
|
*
|
||||||
|
* @return \Illuminate\Contracts\Auth\StatefulGuard
|
||||||
|
*/
|
||||||
|
protected function guard()
|
||||||
|
{
|
||||||
|
return Auth::guard('backend');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Where to redirect users after login.
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
protected function redirectTo(): string
|
||||||
|
{
|
||||||
|
return \route('bo.login');
|
||||||
|
}
|
||||||
|
}
|
||||||
178
app/Http/Controllers/Bo/BrandController.php
Normal file
178
app/Http/Controllers/Bo/BrandController.php
Normal file
|
|
@ -0,0 +1,178 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Controllers\Bo;
|
||||||
|
|
||||||
|
use App\Http\Controllers\Controller;
|
||||||
|
use App\Http\Requests\Bo\Brands\GetBrandUrlsRequest;
|
||||||
|
use App\Http\Requests\Bo\Brands\StoreBrandRequest;
|
||||||
|
use App\Http\Requests\Bo\Brands\UpdateBrandRequest;
|
||||||
|
use App\Models\Brand;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
use Illuminate\Support\Facades\DB;
|
||||||
|
use Illuminate\Support\Str;
|
||||||
|
|
||||||
|
class BrandController extends Controller
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Create the controller instance.
|
||||||
|
*/
|
||||||
|
public function __construct()
|
||||||
|
{
|
||||||
|
$this->authorizeResource(Brand::class);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Display a listing of the resource.
|
||||||
|
*
|
||||||
|
* @param \Illuminate\Http\Request $request
|
||||||
|
* @return \Illuminate\Contracts\View\Factory|\Illuminate\Contracts\View\View
|
||||||
|
*/
|
||||||
|
public function index(Request $request)
|
||||||
|
{
|
||||||
|
/** @var \Illuminate\Database\Eloquent\Builder $brands */
|
||||||
|
$brands = Brand::query();
|
||||||
|
|
||||||
|
if ($request->search) {
|
||||||
|
$this->searchQuery(
|
||||||
|
$brands,
|
||||||
|
$request->search,
|
||||||
|
null,
|
||||||
|
'name',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->sortQuery($brands);
|
||||||
|
$searchFields = trans('Nom');
|
||||||
|
|
||||||
|
$brands = $this->paginate($brands);
|
||||||
|
|
||||||
|
return view('bo.pages.brands.index', compact('brands', 'searchFields'));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Show the form for creating a new resource.
|
||||||
|
*
|
||||||
|
* @return \Illuminate\Contracts\View\Factory|\Illuminate\Contracts\View\View
|
||||||
|
*/
|
||||||
|
public function create()
|
||||||
|
{
|
||||||
|
return view('bo.pages.brands.create');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Store a newly created resource in storage.
|
||||||
|
*
|
||||||
|
* @param \App\Http\Requests\Bo\Brands\StoreBrandRequest $request
|
||||||
|
* @return \Illuminate\Http\RedirectResponse
|
||||||
|
*/
|
||||||
|
public function store(StoreBrandRequest $request)
|
||||||
|
{
|
||||||
|
return DB::transaction(function () use ($request) {
|
||||||
|
$brand = new Brand();
|
||||||
|
$brand->fill($request->validated());
|
||||||
|
|
||||||
|
if ($brand->saveOrFail()) {
|
||||||
|
return redirect()
|
||||||
|
->route('bo.brands.edit', $brand)
|
||||||
|
->with('success', trans(
|
||||||
|
':model a été créé',
|
||||||
|
['model' => Str::singular(trans('models.classes.brands'))]
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
return redirect()
|
||||||
|
->back()
|
||||||
|
->with('error', trans(
|
||||||
|
':model n\'a pas été créé',
|
||||||
|
['model' => Str::singular(trans('models.classes.brands'))]
|
||||||
|
));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Show the form for editing the specified resource.
|
||||||
|
*
|
||||||
|
* @param \App\Models\Brand $brand
|
||||||
|
* @return \Illuminate\Contracts\View\Factory|\Illuminate\Contracts\View\View
|
||||||
|
*/
|
||||||
|
public function edit(Brand $brand)
|
||||||
|
{
|
||||||
|
return view('bo.pages.brands.edit', [
|
||||||
|
'brandModel' => $brand,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update the specified resource in storage.
|
||||||
|
*
|
||||||
|
* @param \App\Http\Requests\Bo\Brands\UpdateBrandRequest $request
|
||||||
|
* @param \App\Models\Brand $brand
|
||||||
|
* @return \Illuminate\Http\RedirectResponse
|
||||||
|
*/
|
||||||
|
public function update(UpdateBrandRequest $request, Brand $brand)
|
||||||
|
{
|
||||||
|
return DB::transaction(function () use ($request, $brand) {
|
||||||
|
$brand->fill($request->validated());
|
||||||
|
if ($brand->saveOrFail()) {
|
||||||
|
return redirect()
|
||||||
|
->route('bo.brands.edit', $brand)
|
||||||
|
->with('success', trans(
|
||||||
|
':model a été mis à jour',
|
||||||
|
['model' => Str::singular(trans('models.classes.brands'))]
|
||||||
|
));
|
||||||
|
}
|
||||||
|
return redirect()
|
||||||
|
->back()
|
||||||
|
->with('error', trans(
|
||||||
|
':model n\'a pas été mis à jour',
|
||||||
|
['model' => Str::singular(trans('models.classes.brands'))]
|
||||||
|
));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove the specified resource from storage.
|
||||||
|
*
|
||||||
|
* @param \App\Models\Brand $brand
|
||||||
|
* @return \Illuminate\Http\RedirectResponse
|
||||||
|
*/
|
||||||
|
public function destroy(Brand $brand)
|
||||||
|
{
|
||||||
|
if ($brand->deleteOrFail()) {
|
||||||
|
return redirect()
|
||||||
|
->route('bo.brands.index')
|
||||||
|
->with('warning', trans(
|
||||||
|
':model a bien été supprimée',
|
||||||
|
['model' => Str::singular(trans('models.classes.brands'))]
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
return redirect()
|
||||||
|
->back()
|
||||||
|
->with('error', trans(
|
||||||
|
':model n\'a pas été supprimée',
|
||||||
|
['model' => Str::singular(trans('models.classes.brands'))]
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get urls from a given brand.
|
||||||
|
*
|
||||||
|
* @param \App\Http\Requests\Bo\Brands\GetBrandUrlsRequest $request
|
||||||
|
* @param \App\Models\Brand $brand
|
||||||
|
* @return \Illuminate\Http\JsonResponse
|
||||||
|
*/
|
||||||
|
public function getUrls(GetBrandUrlsRequest $request, Brand $brand)
|
||||||
|
{
|
||||||
|
if (!$request->wantsJson()) {
|
||||||
|
return abort(404);
|
||||||
|
}
|
||||||
|
|
||||||
|
return response()
|
||||||
|
->json($brand->urls);
|
||||||
|
}
|
||||||
|
}
|
||||||
18
app/Http/Controllers/Bo/DashboardController.php
Normal file
18
app/Http/Controllers/Bo/DashboardController.php
Normal file
|
|
@ -0,0 +1,18 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Controllers\Bo;
|
||||||
|
|
||||||
|
use App\Http\Controllers\Controller;
|
||||||
|
|
||||||
|
class DashboardController extends Controller
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Dashboard Page
|
||||||
|
*
|
||||||
|
* @return \Illuminate\Contracts\View\View
|
||||||
|
*/
|
||||||
|
public function index()
|
||||||
|
{
|
||||||
|
return view('bo.pages.dashboard');
|
||||||
|
}
|
||||||
|
}
|
||||||
200
app/Http/Controllers/Bo/ServiceController.php
Normal file
200
app/Http/Controllers/Bo/ServiceController.php
Normal file
|
|
@ -0,0 +1,200 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Controllers\Bo;
|
||||||
|
|
||||||
|
use App\Http\Controllers\Controller;
|
||||||
|
use App\Http\Requests\Bo\Services\StoreServiceRequest;
|
||||||
|
use App\Http\Requests\Bo\Services\UpdateServiceRequest;
|
||||||
|
use App\Models\Brand;
|
||||||
|
use App\Models\Service;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
use Illuminate\Support\Facades\DB;
|
||||||
|
use Illuminate\Support\Str;
|
||||||
|
|
||||||
|
class ServiceController extends Controller
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Create the controller instance.
|
||||||
|
*/
|
||||||
|
public function __construct()
|
||||||
|
{
|
||||||
|
$this->authorizeResource(Service::class);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Display a listing of the resource.
|
||||||
|
*
|
||||||
|
* @param \Illuminate\Http\Request $request
|
||||||
|
* @return \Illuminate\Contracts\View\Factory|\Illuminate\Contracts\View\View
|
||||||
|
*/
|
||||||
|
public function index(Request $request)
|
||||||
|
{
|
||||||
|
/** @var \Illuminate\Database\Eloquent\Builder $services */
|
||||||
|
$services = Service::query();
|
||||||
|
|
||||||
|
if ($request->search) {
|
||||||
|
$this->searchQuery(
|
||||||
|
$services,
|
||||||
|
$request->search,
|
||||||
|
null,
|
||||||
|
'name',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->sortQuery($services);
|
||||||
|
$searchFields = trans('Nom');
|
||||||
|
|
||||||
|
$services = $this->paginate($services);
|
||||||
|
|
||||||
|
return view('bo.pages.services.index', compact('services', 'searchFields'));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Show the form for creating a new resource.
|
||||||
|
*
|
||||||
|
* @return \Illuminate\Contracts\View\Factory|\Illuminate\Contracts\View\View
|
||||||
|
*/
|
||||||
|
public function create()
|
||||||
|
{
|
||||||
|
$brandModels = Brand::orderBy('name', 'ASC')->get();
|
||||||
|
return view('bo.pages.services.create', compact('brandModels'));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Store a newly created resource in storage.
|
||||||
|
*
|
||||||
|
* @param \App\Http\Requests\Bo\Services\StoreServiceRequest $request
|
||||||
|
* @return \Illuminate\Http\RedirectResponse
|
||||||
|
*/
|
||||||
|
public function store(StoreServiceRequest $request)
|
||||||
|
{
|
||||||
|
return DB::transaction(function () use ($request) {
|
||||||
|
$service = new Service();
|
||||||
|
$service->fill($request->validated());
|
||||||
|
|
||||||
|
if ($service->saveOrFail()) {
|
||||||
|
$this->generateServicePassword($service);
|
||||||
|
return redirect()
|
||||||
|
->route('bo.services.edit', $service)
|
||||||
|
->with('success', trans(
|
||||||
|
':model a été créé',
|
||||||
|
['model' => Str::singular(trans('models.classes.services'))]
|
||||||
|
))
|
||||||
|
->with(
|
||||||
|
'warning',
|
||||||
|
"ATTENTION : METTEZ À JOUR LES SITES UTILISANT L'API ! \n
|
||||||
|
Nouvel id de marque pour le service $service->name : $request->brand_id"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return redirect()
|
||||||
|
->back()
|
||||||
|
->with('error', trans(
|
||||||
|
':model n\'a pas été créé',
|
||||||
|
['model' => Str::singular(trans('models.classes.services'))]
|
||||||
|
));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Show the form for editing the specified resource.
|
||||||
|
*
|
||||||
|
* @param \App\Models\Service $service
|
||||||
|
* @return \Illuminate\Contracts\View\Factory|\Illuminate\Contracts\View\View
|
||||||
|
*/
|
||||||
|
public function edit(Service $service)
|
||||||
|
{
|
||||||
|
$brandModels = Brand::orderBy('name', 'ASC')->get();
|
||||||
|
return view('bo.pages.services.edit', [
|
||||||
|
'serviceModel' => $service,
|
||||||
|
'brandModels' => $brandModels
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update the specified resource in storage.
|
||||||
|
*
|
||||||
|
* @param \App\Http\Requests\Bo\Services\UpdateServiceRequest $request
|
||||||
|
* @param \App\Models\Service $service
|
||||||
|
* @return \Illuminate\Http\RedirectResponse
|
||||||
|
*/
|
||||||
|
public function update(UpdateServiceRequest $request, Service $service)
|
||||||
|
{
|
||||||
|
return DB::transaction(function () use ($request, $service) {
|
||||||
|
$oldBrandId = $service->brand->id;
|
||||||
|
$service->fill($request->validated());
|
||||||
|
if ($service->saveOrFail()) {
|
||||||
|
if ($request->bearer_pwd === 'on') {
|
||||||
|
$this->generateServicePassword($service);
|
||||||
|
}
|
||||||
|
if ($oldBrandId != $request->brand_id) {
|
||||||
|
$changeBrandMessage = "ATTENTION : METTEZ À JOUR LES SITES UTILISANT L'API ! \n
|
||||||
|
Nouvel id de marque pour le service $service->name : $request->brand_id";
|
||||||
|
};
|
||||||
|
return redirect()
|
||||||
|
->route('bo.services.edit', $service)
|
||||||
|
->with('success', trans(
|
||||||
|
':model a été mis à jour',
|
||||||
|
['model' => Str::singular(trans('models.classes.services'))]
|
||||||
|
))
|
||||||
|
->with(
|
||||||
|
'warning',
|
||||||
|
$changeBrandMessage ?? ''
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return redirect()
|
||||||
|
->back()
|
||||||
|
->with('error', trans(
|
||||||
|
':model n\'a pas été mis à jour',
|
||||||
|
['model' => Str::singular(trans('models.classes.services'))]
|
||||||
|
));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Remove the specified resource from storage.
|
||||||
|
*
|
||||||
|
* @param \App\Models\Service $service
|
||||||
|
* @return \Illuminate\Http\RedirectResponse
|
||||||
|
*/
|
||||||
|
public function destroy(Service $service)
|
||||||
|
{
|
||||||
|
if ($service->deleteOrFail()) {
|
||||||
|
return redirect()
|
||||||
|
->route('bo.services.index')
|
||||||
|
->with('warning', trans(
|
||||||
|
':model a bien été supprimée',
|
||||||
|
['model' => Str::singular(trans('models.classes.services'))]
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
return redirect()
|
||||||
|
->back()
|
||||||
|
->with('error', trans(
|
||||||
|
':model n\'a pas été supprimée',
|
||||||
|
['model' => Str::singular(trans('models.classes.services'))]
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Regen service credentials
|
||||||
|
*
|
||||||
|
* @param Service $service
|
||||||
|
* @return \Illuminate\Http\RedirectResponse
|
||||||
|
*/
|
||||||
|
private function generateServicePassword(Service $service)
|
||||||
|
{
|
||||||
|
$bearerPwd = $service->generateBearerPassword();
|
||||||
|
$service->bearer_token_date = now();
|
||||||
|
$service->saveOrFail();
|
||||||
|
return redirect()->route('bo.services.edit', $service)
|
||||||
|
->with(
|
||||||
|
'error',
|
||||||
|
"ATTENTION : METTEZ À JOUR LES SITES UTILISANT L'API ! \n
|
||||||
|
Nouveau token d'API de $service->name : '$service->bearer_token' et mot de passe '$bearerPwd'"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
279
app/Http/Controllers/Bo/StatsController.php
Normal file
279
app/Http/Controllers/Bo/StatsController.php
Normal file
|
|
@ -0,0 +1,279 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Controllers\Bo;
|
||||||
|
|
||||||
|
use App\Enums\PeriodEnum;
|
||||||
|
use App\Http\Controllers\Controller;
|
||||||
|
use App\Http\Requests\Bo\StatsRequest;
|
||||||
|
use App\Lib\Helpers\ChartJsHelper;
|
||||||
|
use App\Models\Brand;
|
||||||
|
use App\Models\Url;
|
||||||
|
use App\Models\UrlStats;
|
||||||
|
use Carbon\Carbon;
|
||||||
|
use Carbon\CarbonPeriod;
|
||||||
|
use Illuminate\Support\Arr;
|
||||||
|
use Illuminate\Support\Str;
|
||||||
|
use Colors\RandomColor;
|
||||||
|
use Illuminate\Database\Eloquent\Model;
|
||||||
|
|
||||||
|
class StatsController extends Controller
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Create the controller instance.
|
||||||
|
*/
|
||||||
|
public function __construct()
|
||||||
|
{
|
||||||
|
$this->authorizeResource(UrlStats::class);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @var array Colors used in charts */
|
||||||
|
private $colors = [];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Display a resource.
|
||||||
|
*
|
||||||
|
* @param \App\Http\Requests\Bo\StatsRequest $request
|
||||||
|
* @return \Illuminate\Contracts\View\View
|
||||||
|
*/
|
||||||
|
public function index(StatsRequest $request): \Illuminate\Contracts\View\View
|
||||||
|
{
|
||||||
|
/** @var \App\Models\User $user */
|
||||||
|
$user = auth('backend')->user();
|
||||||
|
$brandModels = $user->getRelatedBrands();
|
||||||
|
|
||||||
|
// Check if it's a post request or not to get data.
|
||||||
|
if ($request->post()) {
|
||||||
|
$requestValidated = $request->validated();
|
||||||
|
$start = $requestValidated['start'] ? Carbon::parse($requestValidated['start']) : null;
|
||||||
|
$end = $requestValidated['end'] ? Carbon::parse($requestValidated['end']) : null;
|
||||||
|
$chart = $this->getChart($requestValidated);
|
||||||
|
}
|
||||||
|
|
||||||
|
return view('bo.pages.stats', [
|
||||||
|
'brandModels' => $brandModels,
|
||||||
|
'chart' => $chart ?? null,
|
||||||
|
'start' => $start ?? null,
|
||||||
|
'end' => $end ?? null,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a chart from ChartJs.
|
||||||
|
*
|
||||||
|
* @param array $requestValidated
|
||||||
|
* @return \IcehouseVentures\LaravelChartjs\Builder
|
||||||
|
*/
|
||||||
|
private function getChart(array $requestValidated): \IcehouseVentures\LaravelChartjs\Builder
|
||||||
|
{
|
||||||
|
// Get PeriodEnum from Request. Default = PeriodEnum::day.
|
||||||
|
$periodEnum = PeriodEnum::tryFrom($requestValidated['period']) ?? PeriodEnum::day;
|
||||||
|
$periodInterval = $this->getDateInterval(
|
||||||
|
$requestValidated['start'],
|
||||||
|
$requestValidated['end'],
|
||||||
|
$periodEnum,
|
||||||
|
);
|
||||||
|
|
||||||
|
// Get all labels contained in the interval period.
|
||||||
|
$labels = [];
|
||||||
|
foreach ($periodInterval as $date) {
|
||||||
|
$labels[] = $this->getChartLabels($date, $periodEnum);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get data from an entity :
|
||||||
|
* - total of all urls from a brand
|
||||||
|
* - total of specific url from a brand
|
||||||
|
* - total of all urls for each brand
|
||||||
|
*/
|
||||||
|
$datasets = match (true) {
|
||||||
|
!$requestValidated['brand'] => $this->buildDatasets(
|
||||||
|
Brand::all(),
|
||||||
|
$periodInterval,
|
||||||
|
$periodEnum,
|
||||||
|
fn($brand, $date, $period) => $this->getCallsSum($brand, $date, $period),
|
||||||
|
fn($brand) => $brand->name
|
||||||
|
),
|
||||||
|
!$requestValidated['url'] => $this->buildDatasets(
|
||||||
|
Brand::find($requestValidated['brand'])->urls,
|
||||||
|
$periodInterval,
|
||||||
|
$periodEnum,
|
||||||
|
fn($url, $date, $period) => $this->getCallsSum($url, $date, $period),
|
||||||
|
fn($url) => $url->value
|
||||||
|
),
|
||||||
|
default => $this->buildDatasets(
|
||||||
|
[Url::find($requestValidated['url'])],
|
||||||
|
$periodInterval,
|
||||||
|
$periodEnum,
|
||||||
|
fn($url, $date, $period) => $this->getCallsSum($url, $date, $period),
|
||||||
|
fn($url) => $url->value
|
||||||
|
),
|
||||||
|
}; //end match
|
||||||
|
|
||||||
|
// Get the type of graph when there are several data or only one.
|
||||||
|
$chartType = count(Arr::first($datasets)['data'] ?? []) > 1 ? 'line' : 'bar';
|
||||||
|
|
||||||
|
return (new ChartJsHelper())
|
||||||
|
->name('stats')
|
||||||
|
->type($chartType)
|
||||||
|
->labels($labels)
|
||||||
|
->datasets($datasets);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get data from an entity.
|
||||||
|
*
|
||||||
|
* @param iterable $entities
|
||||||
|
* @param \Carbon\CarbonPeriod $periodInterval
|
||||||
|
* @param \App\Enums\PeriodEnum $period
|
||||||
|
* @param callable $getStats
|
||||||
|
* @param callable $getLabel
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
private function buildDatasets(
|
||||||
|
iterable $entities,
|
||||||
|
CarbonPeriod $periodInterval,
|
||||||
|
PeriodEnum $period,
|
||||||
|
callable $getStats,
|
||||||
|
callable $getLabel
|
||||||
|
): array {
|
||||||
|
$datasets = [];
|
||||||
|
foreach ($entities as $entity) {
|
||||||
|
$data = [];
|
||||||
|
foreach ($periodInterval as $date) {
|
||||||
|
$data[] = $getStats($entity, $date, $period);
|
||||||
|
}
|
||||||
|
$color = $this->getUniqueColor();
|
||||||
|
$datasets[] = [
|
||||||
|
'label' => $getLabel($entity),
|
||||||
|
'backgroundColor' => $color,
|
||||||
|
'borderColor' => $color,
|
||||||
|
'pointBorderColor' => $color,
|
||||||
|
'pointBackgroundColor' => $color,
|
||||||
|
'data' => $data,
|
||||||
|
'lineTension' => 0.4,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
return $datasets;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the sum of all calls.
|
||||||
|
*
|
||||||
|
* @param \Illuminate\Database\Eloquent\Model $entity
|
||||||
|
* @param \Carbon\Carbon $date
|
||||||
|
* @param \App\Enums\PeriodEnum $period
|
||||||
|
* @return integer
|
||||||
|
*/
|
||||||
|
private function getCallsSum(Model $entity, Carbon $date, PeriodEnum $period): int
|
||||||
|
{
|
||||||
|
$urlKeys = method_exists($entity, 'urls') && $entity->urls()->exists()
|
||||||
|
? $entity->urls()->pluck('key')
|
||||||
|
: collect([$entity->key]);
|
||||||
|
|
||||||
|
return UrlStats::query()
|
||||||
|
->when(
|
||||||
|
$period === PeriodEnum::day,
|
||||||
|
fn($query) => $query->whereDate('date', $date)
|
||||||
|
)
|
||||||
|
->when(
|
||||||
|
$period === PeriodEnum::week,
|
||||||
|
fn($query) => $query->whereBetween('date', [
|
||||||
|
$date->copy()->startOfWeek(),
|
||||||
|
$date->copy()->endOfWeek()
|
||||||
|
])
|
||||||
|
)
|
||||||
|
->when(
|
||||||
|
$period === PeriodEnum::month,
|
||||||
|
fn($query) => $query->whereBetween('date', [
|
||||||
|
$date->copy()->startOfMonth(),
|
||||||
|
$date->copy()->endOfMonth()
|
||||||
|
])
|
||||||
|
)
|
||||||
|
->whereIn('url_key', $urlKeys)
|
||||||
|
->sum('calls');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the CarbonPeriod for the given period.
|
||||||
|
*
|
||||||
|
* @param string $start
|
||||||
|
* @param string $end
|
||||||
|
* @param \App\Enums\PeriodEnum $period
|
||||||
|
* @return \Carbon\CarbonPeriod
|
||||||
|
*/
|
||||||
|
private function getDateInterval(string $start, string $end, PeriodEnum $period): \Carbon\CarbonPeriod
|
||||||
|
{
|
||||||
|
$startDate = Carbon::parse($start);
|
||||||
|
$endDate = Carbon::parse($end);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set config for each enum names.
|
||||||
|
* methods: specific methods needed.
|
||||||
|
* interval: specific interval defined.
|
||||||
|
*/
|
||||||
|
$unitMap = [
|
||||||
|
PeriodEnum::day->value => [
|
||||||
|
'methods' => null,
|
||||||
|
'interval' => null
|
||||||
|
],
|
||||||
|
PeriodEnum::week->value => [
|
||||||
|
'methods' => ['startOfWeek', 'endOfWeek'],
|
||||||
|
'interval' => '1 week'
|
||||||
|
],
|
||||||
|
PeriodEnum::month->value => [
|
||||||
|
'methods' => ['startOfMonth', 'endOfMonth'],
|
||||||
|
'interval' => '1 month'
|
||||||
|
],
|
||||||
|
];
|
||||||
|
|
||||||
|
// Check if config exist for the given period, get the day config by default.
|
||||||
|
$config = $unitMap[$period->value] ?? $unitMap[PeriodEnum::day->value];
|
||||||
|
|
||||||
|
// Execute configuration methods where available.
|
||||||
|
if ($config['methods']) {
|
||||||
|
[$startMethod, $endMethod] = $config['methods'];
|
||||||
|
$startDate = $startDate->$startMethod();
|
||||||
|
$endDate = $endDate->$endMethod();
|
||||||
|
}
|
||||||
|
|
||||||
|
return CarbonPeriod::create($startDate, $config['interval'], $endDate);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get labels for charts.
|
||||||
|
*
|
||||||
|
* @param \Carbon\Carbon $date
|
||||||
|
* @param \App\Enums\PeriodEnum $period
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
private function getChartLabels(Carbon $date, PeriodEnum $period): array
|
||||||
|
{
|
||||||
|
// Match given period with enum names.
|
||||||
|
return match ($period) {
|
||||||
|
PeriodEnum::week => array_unique([sprintf(
|
||||||
|
'Du %s au %s',
|
||||||
|
$date->startOfWeek()->format('Y-m-d'),
|
||||||
|
$date->endOfWeek()->format('Y-m-d')
|
||||||
|
)]),
|
||||||
|
PeriodEnum::month => array_unique([
|
||||||
|
Str::ucfirst($date->monthName)
|
||||||
|
]),
|
||||||
|
default => array_unique([$date->format('d-m-Y')]),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get unique color.
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
private function getUniqueColor(): string
|
||||||
|
{
|
||||||
|
do {
|
||||||
|
$color = RandomColor::one();
|
||||||
|
// Check if color is already used.
|
||||||
|
} while (in_array($color, $this->colors));
|
||||||
|
$this->colors[] = $color;
|
||||||
|
return $color;
|
||||||
|
}
|
||||||
|
}
|
||||||
244
app/Http/Controllers/Bo/UrlController.php
Normal file
244
app/Http/Controllers/Bo/UrlController.php
Normal file
|
|
@ -0,0 +1,244 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Controllers\Bo;
|
||||||
|
|
||||||
|
use App\Enums\Users\RoleEnum;
|
||||||
|
use App\Http\Controllers\Controller;
|
||||||
|
use App\Http\Requests\Bo\Urls\StoreUrlRequest;
|
||||||
|
use App\Http\Requests\Bo\Urls\UpdateUrlRequest;
|
||||||
|
use App\Models\Url;
|
||||||
|
use App\Models\UrlHistory;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
use Illuminate\Http\UploadedFile;
|
||||||
|
use Illuminate\Support\Facades\DB;
|
||||||
|
use Illuminate\Support\Str;
|
||||||
|
use Uwil\LaravelCommon\Helpers\FileHelper;
|
||||||
|
|
||||||
|
class UrlController extends Controller
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Create the controller instance.
|
||||||
|
*/
|
||||||
|
public function __construct()
|
||||||
|
{
|
||||||
|
$this->authorizeResource(Url::class);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Display a listing of the resource.
|
||||||
|
*
|
||||||
|
* @param \Illuminate\Http\Request $request
|
||||||
|
* @return \Illuminate\Contracts\View\Factory|\Illuminate\Contracts\View\View
|
||||||
|
*/
|
||||||
|
public function index(Request $request)
|
||||||
|
{
|
||||||
|
/** @var \App\Models\User $user */
|
||||||
|
$user = auth('backend')->user();
|
||||||
|
|
||||||
|
/** @var \Illuminate\Database\Eloquent\Builder $urls */
|
||||||
|
$urls = $user->role->equals(RoleEnum::conceptor) ?
|
||||||
|
Url::query() :
|
||||||
|
Url::query()->whereIn('brand_id', $user->brands->pluck('id'));
|
||||||
|
|
||||||
|
if ($request->search) {
|
||||||
|
$this->searchQuery(
|
||||||
|
$urls,
|
||||||
|
$request->search,
|
||||||
|
null,
|
||||||
|
'key',
|
||||||
|
'value',
|
||||||
|
'title',
|
||||||
|
'brand_id'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->sortQuery($urls);
|
||||||
|
$searchFields = trans('Clé, Redirection, Titre, Marque');
|
||||||
|
|
||||||
|
$urls = $this->paginate($urls);
|
||||||
|
|
||||||
|
return view('bo.pages.urls.index', compact('urls', 'searchFields'));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Show the form for creating a new resource.
|
||||||
|
*
|
||||||
|
* @param \App\Models\Url $url
|
||||||
|
* @return \Illuminate\Contracts\View\Factory|\Illuminate\Contracts\View\View
|
||||||
|
*/
|
||||||
|
public function create(Url $url)
|
||||||
|
{
|
||||||
|
/** @var \App\Models\User $user */
|
||||||
|
$user = auth('backend')->user();
|
||||||
|
$brandModels = $user->getRelatedBrands();
|
||||||
|
|
||||||
|
return view('bo.pages.urls.create', [
|
||||||
|
'brands' => $brandModels,
|
||||||
|
'urlModel' => $url
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Store a newly created resource in storage.
|
||||||
|
*
|
||||||
|
* @param StoreUrlRequest $request
|
||||||
|
* @return \Illuminate\Http\RedirectResponse
|
||||||
|
*/
|
||||||
|
public function store(StoreUrlRequest $request)
|
||||||
|
{
|
||||||
|
return DB::transaction(function () use ($request) {
|
||||||
|
$url = new Url();
|
||||||
|
$url->fill($request->validated());
|
||||||
|
|
||||||
|
// When duplicated, $url->logo is the path of the logo from the original model.
|
||||||
|
$url->logo = is_string($url->logo) ?
|
||||||
|
new UploadedFile(
|
||||||
|
FileHelper::getPublicAbsolutePath($url->logo),
|
||||||
|
sprintf(
|
||||||
|
'%s.%s',
|
||||||
|
Str::uuid(),
|
||||||
|
new \SplFileInfo(asset($url->logo))->getExtension()
|
||||||
|
)
|
||||||
|
) :
|
||||||
|
$url->logo;
|
||||||
|
|
||||||
|
$url->saveOrFail();
|
||||||
|
|
||||||
|
// Reassign the logo in image settings to save only the path.
|
||||||
|
// Do it after the first saveOrFail to get the real logo path, not tmp.
|
||||||
|
$settings = json_decode($request->validated('image_settings'));
|
||||||
|
$settings->image = $url->logo;
|
||||||
|
$url->image_settings = $settings;
|
||||||
|
|
||||||
|
if ($url->saveOrFail()) {
|
||||||
|
return redirect()
|
||||||
|
->route('bo.urls.edit', $url)
|
||||||
|
->with('success', trans(
|
||||||
|
':model a été créé',
|
||||||
|
['model' => Str::singular(trans('models.classes.urls'))]
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
return redirect()
|
||||||
|
->back()
|
||||||
|
->with('error', trans(
|
||||||
|
':model n\'a pas été créé',
|
||||||
|
['model' => Str::singular(trans('models.classes.urls'))]
|
||||||
|
));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Show the form for editing the specified resource.
|
||||||
|
*
|
||||||
|
* @param \App\Models\Url $url
|
||||||
|
* @return \Illuminate\Contracts\View\Factory|\Illuminate\Contracts\View\View
|
||||||
|
*/
|
||||||
|
public function edit(Url $url)
|
||||||
|
{
|
||||||
|
/** @var \App\Models\User $user */
|
||||||
|
$user = auth('backend')->user();
|
||||||
|
$brandModels = $user->getRelatedBrands();
|
||||||
|
|
||||||
|
return view('bo.pages.urls.edit', [
|
||||||
|
'urlModel' => $url,
|
||||||
|
'brands' => $brandModels
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update the specified resource in storage.
|
||||||
|
*
|
||||||
|
* @param UpdateUrlRequest $request
|
||||||
|
* @param Url $url
|
||||||
|
* @return \Illuminate\Http\RedirectResponse
|
||||||
|
*/
|
||||||
|
public function update(UpdateUrlRequest $request, Url $url)
|
||||||
|
{
|
||||||
|
return DB::transaction(function () use ($request, $url) {
|
||||||
|
$oldUrl = $url->value;
|
||||||
|
$data = $request->validated();
|
||||||
|
|
||||||
|
// Reassign the logo in image settings to save only the path.
|
||||||
|
// Do it after the first saveOrFail to get the real logo path, not tmp.
|
||||||
|
$settings = json_decode($request->validated('image_settings'));
|
||||||
|
|
||||||
|
if (empty($settings->image)) {
|
||||||
|
$url->logo = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
$url->fill($data);
|
||||||
|
$url->saveOrFail();
|
||||||
|
|
||||||
|
if ($oldUrl !== $url->value) {
|
||||||
|
$this->saveUrlHistory($url, $oldUrl);
|
||||||
|
}
|
||||||
|
|
||||||
|
$settings->image = $url->logo;
|
||||||
|
$url->image_settings = $settings;
|
||||||
|
|
||||||
|
if ($url->saveOrFail()) {
|
||||||
|
return redirect()
|
||||||
|
->route('bo.urls.edit', $url)
|
||||||
|
->with('success', trans(
|
||||||
|
':model a été mis à jour',
|
||||||
|
['model' => Str::singular(trans('models.classes.urls'))]
|
||||||
|
));
|
||||||
|
}
|
||||||
|
return redirect()
|
||||||
|
->back()
|
||||||
|
->with('error', trans(
|
||||||
|
':model n\'a pas été mis à jour',
|
||||||
|
['model' => Str::singular(trans('models.classes.urls'))]
|
||||||
|
));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove the specified resource from storage.
|
||||||
|
*
|
||||||
|
* @param Url $url
|
||||||
|
* @return \Illuminate\Http\RedirectResponse
|
||||||
|
*/
|
||||||
|
public function destroy(Url $url)
|
||||||
|
{
|
||||||
|
foreach ($url->history as $history) {
|
||||||
|
$history->deleteOrFail();
|
||||||
|
}
|
||||||
|
|
||||||
|
$url->deleteOrFail();
|
||||||
|
return redirect()->route('bo.urls.index')
|
||||||
|
->with(['warning' => 'L\'url pour la clé ' . $url->key . ' a bien été supprimée.']);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Duplicate the specified resource.
|
||||||
|
*
|
||||||
|
* @param \App\Models\Url $url
|
||||||
|
* @return \Illuminate\Contracts\View\View
|
||||||
|
*/
|
||||||
|
public function duplicate(Url $url): \Illuminate\Contracts\View\View
|
||||||
|
{
|
||||||
|
$duplicatedUrl = $url->replicate(['key']);
|
||||||
|
|
||||||
|
return $this->create($duplicatedUrl);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Save url history when updating url.
|
||||||
|
* @param Url $url
|
||||||
|
* @param string $oldValue
|
||||||
|
* @return \App\Models\UrlHistory
|
||||||
|
*/
|
||||||
|
private function saveUrlHistory(Url $url, string $oldValue)
|
||||||
|
{
|
||||||
|
$history = new UrlHistory();
|
||||||
|
$history->value = $oldValue;
|
||||||
|
$history->start_date = $url->updated_at;
|
||||||
|
$history->end_date = now();
|
||||||
|
$history->url_id = $url->id;
|
||||||
|
$history->saveOrFail();
|
||||||
|
return $history;
|
||||||
|
}
|
||||||
|
}
|
||||||
175
app/Http/Controllers/Bo/UserController.php
Normal file
175
app/Http/Controllers/Bo/UserController.php
Normal file
|
|
@ -0,0 +1,175 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Controllers\Bo;
|
||||||
|
|
||||||
|
use App\Http\Controllers\Controller;
|
||||||
|
use App\Http\Requests\Bo\Users\StoreUserRequest;
|
||||||
|
use App\Http\Requests\Bo\Users\UpdateUserRequest;
|
||||||
|
use App\Models\Brand;
|
||||||
|
use App\Models\User;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
use Illuminate\Support\Facades\DB;
|
||||||
|
|
||||||
|
class UserController extends Controller
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Create the controller instance.
|
||||||
|
*/
|
||||||
|
public function __construct()
|
||||||
|
{
|
||||||
|
$this->authorizeResource(User::class);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Display a listing of the resource.
|
||||||
|
*
|
||||||
|
* @param \Illuminate\Http\Request $request
|
||||||
|
* @return \Illuminate\Contracts\View\Factory|\Illuminate\Contracts\View\View
|
||||||
|
*/
|
||||||
|
public function index(Request $request)
|
||||||
|
{
|
||||||
|
/** @var \Illuminate\Database\Eloquent\Builder $users */
|
||||||
|
$users = User::query();
|
||||||
|
|
||||||
|
if ($request->search) {
|
||||||
|
$this->searchQuery(
|
||||||
|
$users,
|
||||||
|
$request->search,
|
||||||
|
null,
|
||||||
|
'firstname',
|
||||||
|
'lastname',
|
||||||
|
'email'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->sortQuery($users);
|
||||||
|
$searchFields = \implode(', ', [
|
||||||
|
trans('validation.custom.firstname'),
|
||||||
|
trans('validation.custom.lastname'),
|
||||||
|
trans('validation.custom.email'),
|
||||||
|
]);
|
||||||
|
|
||||||
|
|
||||||
|
$users = $this->paginate($users);
|
||||||
|
|
||||||
|
return view('bo.pages.users.index', compact('users', 'searchFields'));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Show the form for creating a new resource.
|
||||||
|
*
|
||||||
|
* @return \Illuminate\Contracts\View\Factory|\Illuminate\Contracts\View\View
|
||||||
|
*/
|
||||||
|
public function create()
|
||||||
|
{
|
||||||
|
$brandModels = Brand::query()->get();
|
||||||
|
return view('bo.pages.users.create', compact('brandModels'));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Store a newly created resource in storage.
|
||||||
|
*
|
||||||
|
* @param \App\Http\Requests\Bo\Users\StoreUserRequest $request
|
||||||
|
* @return \Illuminate\Http\RedirectResponse
|
||||||
|
*/
|
||||||
|
public function store(StoreUserRequest $request)
|
||||||
|
{
|
||||||
|
return DB::transaction(function () use ($request) {
|
||||||
|
$user = new User();
|
||||||
|
$user->fill($request->validated());
|
||||||
|
|
||||||
|
if ($user->saveOrFail()) {
|
||||||
|
$user->brands()->sync(collect($request->brands)->pluck('id'));
|
||||||
|
return redirect()
|
||||||
|
->route('bo.users.edit', $user)
|
||||||
|
->with('success', trans(
|
||||||
|
':model a été créé',
|
||||||
|
['model' => trans('models.classes.user')]
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
return redirect()
|
||||||
|
->back()
|
||||||
|
->with('error', trans(
|
||||||
|
':model n\'a pas été créé',
|
||||||
|
['model' => trans('models.classes.user')]
|
||||||
|
));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Show the form for editing the specified resource.
|
||||||
|
*
|
||||||
|
* @param \App\Models\User $user
|
||||||
|
* @return \Illuminate\Contracts\View\Factory|\Illuminate\Contracts\View\View
|
||||||
|
*/
|
||||||
|
public function edit(User $user)
|
||||||
|
{
|
||||||
|
$brandModels = Brand::query()->get();
|
||||||
|
return view('bo.pages.users.edit', [
|
||||||
|
'userModel' => $user,
|
||||||
|
'brandModels' => $brandModels
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update the specified resource in storage.
|
||||||
|
*
|
||||||
|
* @param \App\Http\Requests\Bo\Users\UpdateUserRequest $request
|
||||||
|
* @param \App\Models\User $user
|
||||||
|
* @return \Illuminate\Http\RedirectResponse
|
||||||
|
*/
|
||||||
|
public function update(UpdateUserRequest $request, User $user)
|
||||||
|
{
|
||||||
|
return DB::transaction(function () use ($request, $user) {
|
||||||
|
$user->fill($request->validated());
|
||||||
|
$user->brands()->sync(collect($request->brands)->pluck('id'));
|
||||||
|
if ($user->saveOrFail()) {
|
||||||
|
return redirect()
|
||||||
|
->route('bo.users.edit', $user)
|
||||||
|
->with('success', trans(
|
||||||
|
':model a été mis à jour',
|
||||||
|
['model' => trans('models.classes.user')]
|
||||||
|
));
|
||||||
|
}
|
||||||
|
return redirect()
|
||||||
|
->back()
|
||||||
|
->with('error', trans(
|
||||||
|
':model n\'a pas été mis à jour',
|
||||||
|
['model' => trans('models.classes.user')]
|
||||||
|
));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove the specified resource from storage.
|
||||||
|
*
|
||||||
|
* @param \App\Models\User $user
|
||||||
|
* @return \Illuminate\Http\RedirectResponse
|
||||||
|
*/
|
||||||
|
public function destroy(User $user)
|
||||||
|
{
|
||||||
|
return DB::transaction(function () use ($user) {
|
||||||
|
$user->brands()->detach();
|
||||||
|
if ($user->deleteOrFail()) {
|
||||||
|
return redirect()
|
||||||
|
->route('bo.users.index')
|
||||||
|
->with('warning', trans(
|
||||||
|
':model a bien été supprimé',
|
||||||
|
['model' => trans('models.classes.user')]
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
return redirect()
|
||||||
|
->back()
|
||||||
|
->with('error', trans(
|
||||||
|
':model n\'a pas été supprimé',
|
||||||
|
['model' => trans('models.classes.user')]
|
||||||
|
));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
15
app/Http/Controllers/Controller.php
Normal file
15
app/Http/Controllers/Controller.php
Normal file
|
|
@ -0,0 +1,15 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Controllers;
|
||||||
|
|
||||||
|
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
|
||||||
|
use Illuminate\Foundation\Bus\DispatchesJobs;
|
||||||
|
use Illuminate\Foundation\Validation\ValidatesRequests;
|
||||||
|
use Uwil\LaravelCommon\Controllers\Controller as BaseController;
|
||||||
|
|
||||||
|
class Controller extends BaseController
|
||||||
|
{
|
||||||
|
use AuthorizesRequests;
|
||||||
|
use DispatchesJobs;
|
||||||
|
use ValidatesRequests;
|
||||||
|
}
|
||||||
18
app/Http/Controllers/Fo/HomeController.php
Normal file
18
app/Http/Controllers/Fo/HomeController.php
Normal file
|
|
@ -0,0 +1,18 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Controllers\Fo;
|
||||||
|
|
||||||
|
use App\Http\Controllers\Controller;
|
||||||
|
|
||||||
|
class HomeController extends Controller
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Display a listing of the resource.
|
||||||
|
*
|
||||||
|
* @return \Illuminate\Contracts\View\View
|
||||||
|
*/
|
||||||
|
public function index()
|
||||||
|
{
|
||||||
|
return view('fo.pages.home');
|
||||||
|
}
|
||||||
|
}
|
||||||
25
app/Http/Controllers/Fo/UrlController.php
Normal file
25
app/Http/Controllers/Fo/UrlController.php
Normal file
|
|
@ -0,0 +1,25 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Controllers\Fo;
|
||||||
|
|
||||||
|
use App\Http\Controllers\Controller;
|
||||||
|
use App\Jobs\IncrementUrlStat;
|
||||||
|
use App\Models\Url;
|
||||||
|
|
||||||
|
class UrlController extends Controller
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Redirect an url to its value.
|
||||||
|
*
|
||||||
|
* @param \App\Models\Url $url
|
||||||
|
*
|
||||||
|
* @return \Illuminate\Http\RedirectResponse
|
||||||
|
*/
|
||||||
|
public function redirect(Url $url)
|
||||||
|
{
|
||||||
|
IncrementUrlStat::dispatch($url)->onConnection('database')->onQueue('database');
|
||||||
|
|
||||||
|
return \redirect($url->value, 301)
|
||||||
|
->header('Cache-Control', 'no-store, no-cache, must-revalidate');
|
||||||
|
}
|
||||||
|
}
|
||||||
76
app/Http/Kernel.php
Normal file
76
app/Http/Kernel.php
Normal file
|
|
@ -0,0 +1,76 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http;
|
||||||
|
|
||||||
|
use Illuminate\Foundation\Http\Kernel as HttpKernel;
|
||||||
|
|
||||||
|
class Kernel extends HttpKernel
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* The application's global HTTP middleware stack.
|
||||||
|
*
|
||||||
|
* These middleware are run during every request to your application.
|
||||||
|
*
|
||||||
|
* @var array<int, class-string|string>
|
||||||
|
*/
|
||||||
|
protected $middleware = [
|
||||||
|
\App\Http\Middleware\TrustHosts::class,
|
||||||
|
\App\Http\Middleware\TrustProxies::class,
|
||||||
|
\Illuminate\Http\Middleware\HandleCors::class,
|
||||||
|
\App\Http\Middleware\PreventRequestsDuringMaintenance::class,
|
||||||
|
\Illuminate\Foundation\Http\Middleware\ValidatePostSize::class,
|
||||||
|
\App\Http\Middleware\TrimStrings::class,
|
||||||
|
\Illuminate\Foundation\Http\Middleware\ConvertEmptyStringsToNull::class,
|
||||||
|
];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The application's route middleware groups.
|
||||||
|
*
|
||||||
|
* @var array<string, array<int, class-string|string>>
|
||||||
|
*/
|
||||||
|
protected $middlewareGroups = [
|
||||||
|
'web' => [
|
||||||
|
'htmlsanitize',
|
||||||
|
\App\Http\Middleware\EncryptCookies::class,
|
||||||
|
\Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class,
|
||||||
|
\Illuminate\Session\Middleware\StartSession::class,
|
||||||
|
\Illuminate\View\Middleware\ShareErrorsFromSession::class,
|
||||||
|
\App\Http\Middleware\VerifyCsrfToken::class,
|
||||||
|
\Illuminate\Routing\Middleware\SubstituteBindings::class,
|
||||||
|
\App\Http\Middleware\SecureHeaders::class
|
||||||
|
],
|
||||||
|
|
||||||
|
'saml' => [
|
||||||
|
\App\Http\Middleware\EncryptCookies::class,
|
||||||
|
\Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class,
|
||||||
|
\Illuminate\Session\Middleware\StartSession::class,
|
||||||
|
],
|
||||||
|
|
||||||
|
'api' => [
|
||||||
|
'throttle:api',
|
||||||
|
\Illuminate\Routing\Middleware\SubstituteBindings::class,
|
||||||
|
\App\Http\Middleware\JsonOnly::class,
|
||||||
|
\App\Http\Middleware\ApiCheckTimeStamps::class,
|
||||||
|
],
|
||||||
|
];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The application's route middleware.
|
||||||
|
*
|
||||||
|
* These middleware may be assigned to groups or used individually.
|
||||||
|
*
|
||||||
|
* @var array<string, class-string|string>
|
||||||
|
*/
|
||||||
|
protected $routeMiddleware = [
|
||||||
|
'auth' => \App\Http\Middleware\Authenticate::class,
|
||||||
|
'auth.basic' => \Illuminate\Auth\Middleware\AuthenticateWithBasicAuth::class,
|
||||||
|
'cache.headers' => \Illuminate\Http\Middleware\SetCacheHeaders::class,
|
||||||
|
'can' => \Illuminate\Auth\Middleware\Authorize::class,
|
||||||
|
'guest' => \App\Http\Middleware\RedirectIfAuthenticated::class,
|
||||||
|
'password.confirm' => \Illuminate\Auth\Middleware\RequirePassword::class,
|
||||||
|
'signed' => \Illuminate\Routing\Middleware\ValidateSignature::class,
|
||||||
|
'throttle' => \Illuminate\Routing\Middleware\ThrottleRequests::class,
|
||||||
|
'verified' => \Illuminate\Auth\Middleware\EnsureEmailIsVerified::class,
|
||||||
|
'htmlsanitize' => \App\Http\Middleware\HtmlSanitizer::class,
|
||||||
|
];
|
||||||
|
}
|
||||||
40
app/Http/Middleware/ApiCheckTimeStamps.php
Normal file
40
app/Http/Middleware/ApiCheckTimeStamps.php
Normal file
|
|
@ -0,0 +1,40 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Middleware;
|
||||||
|
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
use Illuminate\Support\Carbon;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check Http Header Timestamp with request timestamp
|
||||||
|
* to prevent replay attacks
|
||||||
|
* https://restfulapi.net/security-essentials/
|
||||||
|
*/
|
||||||
|
class ApiCheckTimeStamps
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Handle an incoming request.
|
||||||
|
*
|
||||||
|
* @param \Illuminate\Http\Request $request
|
||||||
|
* @param \Closure $next
|
||||||
|
* @return mixed
|
||||||
|
* @throws \Symfony\Component\HttpKernel\Exception\HttpException If timestamp has an incorrect
|
||||||
|
* format or is timed out.
|
||||||
|
*/
|
||||||
|
public function handle(Request $request, \Closure $next)
|
||||||
|
{
|
||||||
|
if (
|
||||||
|
!\is_string($request->input('timestamp')) or
|
||||||
|
!Carbon::hasFormat($request->input('timestamp'), 'Y-m-d H:i:s')
|
||||||
|
) {
|
||||||
|
abort(401, 'Timestamp isn\'t correctly formatted');
|
||||||
|
}
|
||||||
|
if (
|
||||||
|
Carbon::parse($request->input('timestamp'), 'utc')
|
||||||
|
->isBefore(Carbon::now('UTC')->subSeconds(config('api.timestampTimeout', 10)))
|
||||||
|
) {
|
||||||
|
abort(401, 'Timestamp is too old');
|
||||||
|
}
|
||||||
|
return $next($request);
|
||||||
|
}
|
||||||
|
}
|
||||||
39
app/Http/Middleware/Authenticate.php
Normal file
39
app/Http/Middleware/Authenticate.php
Normal file
|
|
@ -0,0 +1,39 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Middleware;
|
||||||
|
|
||||||
|
use Illuminate\Auth\Middleware\Authenticate as Middleware;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
use Illuminate\Support\Facades\Session;
|
||||||
|
|
||||||
|
class Authenticate extends Middleware
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Get the path the user should be redirected to when they are not authenticated.
|
||||||
|
*
|
||||||
|
* @param \Illuminate\Http\Request $request
|
||||||
|
* @return string|null
|
||||||
|
*/
|
||||||
|
protected function redirectTo(Request $request)
|
||||||
|
{
|
||||||
|
$logins = [
|
||||||
|
// Dans tous les cas bo.login.
|
||||||
|
'frontend' => route('bo.login'),
|
||||||
|
'backend' => route('bo.login')
|
||||||
|
];
|
||||||
|
if (!$request->expectsJson()) {
|
||||||
|
// * Notify must login
|
||||||
|
Session::flash('warning', trans('Veuillez vous connecter pour continuer'));
|
||||||
|
foreach ($request->route()->gatherMiddleware() as $middleware) {
|
||||||
|
if (\is_string($middleware) and strpos($middleware, 'auth:') !== false) {
|
||||||
|
$route = $logins[str_replace('auth:', '', $middleware)] ?? null;
|
||||||
|
if ($route) {
|
||||||
|
return $route;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return route('bo.login');
|
||||||
|
}
|
||||||
|
abort(401);
|
||||||
|
}
|
||||||
|
}
|
||||||
16
app/Http/Middleware/EncryptCookies.php
Normal file
16
app/Http/Middleware/EncryptCookies.php
Normal file
|
|
@ -0,0 +1,16 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Middleware;
|
||||||
|
|
||||||
|
use Illuminate\Cookie\Middleware\EncryptCookies as Middleware;
|
||||||
|
|
||||||
|
class EncryptCookies extends Middleware
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* The names of the cookies that should not be encrypted.
|
||||||
|
*
|
||||||
|
* @var array<int, string>
|
||||||
|
*/
|
||||||
|
protected $except = [
|
||||||
|
];
|
||||||
|
}
|
||||||
53
app/Http/Middleware/HtmlSanitizer.php
Normal file
53
app/Http/Middleware/HtmlSanitizer.php
Normal file
|
|
@ -0,0 +1,53 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Middleware;
|
||||||
|
|
||||||
|
use App\Lib\Helpers\ToolboxHelper;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
|
||||||
|
class HtmlSanitizer
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Handle an incoming request.
|
||||||
|
*
|
||||||
|
* @param \Illuminate\Http\Request $request
|
||||||
|
* @param \Closure $next
|
||||||
|
* @param string|array ...$params
|
||||||
|
* @return mixed
|
||||||
|
*/
|
||||||
|
public function handle(Request $request, \Closure $next, string|array ...$params)
|
||||||
|
{
|
||||||
|
$inputs = $request->all();
|
||||||
|
/** @var callable|null */
|
||||||
|
$cleanInput = null;
|
||||||
|
$cleanInput = function (&$input, $name, $rootName = null) use ($params, &$cleanInput) {
|
||||||
|
// * Nested element array
|
||||||
|
if (is_array($input)) {
|
||||||
|
array_walk_recursive($input, $cleanInput, $name);
|
||||||
|
}
|
||||||
|
if (!is_string($input)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (in_array($name, $params) or in_array($rootName ?? $name, $params)) {
|
||||||
|
// ? Is a json input ?
|
||||||
|
if ($isJson = \json_decode($input, true) and is_array($isJson)) {
|
||||||
|
// ! Decode each json field recursive.
|
||||||
|
\array_walk_recursive($isJson, function (&$item) {
|
||||||
|
if (\is_string($item)) {
|
||||||
|
$item = clean($item);
|
||||||
|
$item = ToolboxHelper::stripCkEditorSchemesOnMediaInputs($item);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
$input = clean($input);
|
||||||
|
$input = ToolboxHelper::stripCkEditorSchemesOnMediaInputs($input);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
$input = strip_tags($input);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
\array_walk($inputs, $cleanInput);
|
||||||
|
$request->merge($inputs);
|
||||||
|
return $next($request);
|
||||||
|
}
|
||||||
|
}
|
||||||
26
app/Http/Middleware/JsonOnly.php
Normal file
26
app/Http/Middleware/JsonOnly.php
Normal file
|
|
@ -0,0 +1,26 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Middleware;
|
||||||
|
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
|
||||||
|
class JsonOnly
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Handle an incoming request.
|
||||||
|
*
|
||||||
|
* @param \Illuminate\Http\Request $request
|
||||||
|
* @param \Closure $next
|
||||||
|
* @return mixed
|
||||||
|
*/
|
||||||
|
public function handle(Request $request, \Closure $next)
|
||||||
|
{
|
||||||
|
if (
|
||||||
|
(!($request->isJson() or $request->expectsJson()))
|
||||||
|
) {
|
||||||
|
abort(400, 'Invalid JSON');
|
||||||
|
}
|
||||||
|
|
||||||
|
return $next($request);
|
||||||
|
}
|
||||||
|
}
|
||||||
15
app/Http/Middleware/PreventRequestsDuringMaintenance.php
Normal file
15
app/Http/Middleware/PreventRequestsDuringMaintenance.php
Normal file
|
|
@ -0,0 +1,15 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Middleware;
|
||||||
|
|
||||||
|
use Illuminate\Foundation\Http\Middleware\PreventRequestsDuringMaintenance as Middleware;
|
||||||
|
|
||||||
|
class PreventRequestsDuringMaintenance extends Middleware
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* The URIs that should be reachable while maintenance mode is enabled.
|
||||||
|
*
|
||||||
|
* @var array<int, string>
|
||||||
|
*/
|
||||||
|
protected $except = [];
|
||||||
|
}
|
||||||
41
app/Http/Middleware/RedirectIfAuthenticated.php
Normal file
41
app/Http/Middleware/RedirectIfAuthenticated.php
Normal file
|
|
@ -0,0 +1,41 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Middleware;
|
||||||
|
|
||||||
|
use Closure;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
use Illuminate\Support\Facades\Auth;
|
||||||
|
|
||||||
|
class RedirectIfAuthenticated
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Handle an incoming request.
|
||||||
|
*
|
||||||
|
* @phpcs:disable Generic.Files.LineLength.TooLong
|
||||||
|
* @param \Illuminate\Http\Request $request
|
||||||
|
* @param \Closure(\Illuminate\Http\Request): (\Illuminate\Http\Response|\Illuminate\Http\RedirectResponse) $next
|
||||||
|
* @param string|null ...$guards
|
||||||
|
* @return \Illuminate\Http\Response|\Illuminate\Http\RedirectResponse
|
||||||
|
* @throws \RuntimeException If guard is unhandled.
|
||||||
|
* @phpcs:enable
|
||||||
|
*/
|
||||||
|
public function handle(Request $request, Closure $next, ...$guards)
|
||||||
|
{
|
||||||
|
$guards = empty($guards) ? [null] : $guards;
|
||||||
|
|
||||||
|
foreach ($guards as $guard) {
|
||||||
|
if (Auth::guard($guard)->check()) {
|
||||||
|
switch ($guard) {
|
||||||
|
case 'frontend':
|
||||||
|
return \redirect()->route('home');
|
||||||
|
case 'backend':
|
||||||
|
return \redirect()->route('bo.dashboard');
|
||||||
|
default:
|
||||||
|
throw new \RuntimeException("Unhandled guard `{$guard}` .");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $next($request);
|
||||||
|
}
|
||||||
|
}
|
||||||
107
app/Http/Middleware/SecureHeaders.php
Normal file
107
app/Http/Middleware/SecureHeaders.php
Normal file
|
|
@ -0,0 +1,107 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Middleware;
|
||||||
|
|
||||||
|
use Closure;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
use Illuminate\Support\Facades\Route;
|
||||||
|
use Illuminate\Support\Facades\View;
|
||||||
|
use Illuminate\Support\Facades\Vite;
|
||||||
|
|
||||||
|
class SecureHeaders
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Enumerate headers which you do not want in your application's responses.
|
||||||
|
* Great starting point would be to go check out @Scott_Helme's:
|
||||||
|
* https://securityheaders.com/
|
||||||
|
*
|
||||||
|
* @var array
|
||||||
|
*/
|
||||||
|
private $unwantedHeaderList = [
|
||||||
|
'X-Powered-By',
|
||||||
|
'Server',
|
||||||
|
];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle an incoming request.
|
||||||
|
*
|
||||||
|
* @param \Illuminate\Http\Request $request
|
||||||
|
* @param \Closure $next
|
||||||
|
* @return \Illuminate\Http\Response|\Illuminate\Http\RedirectResponse
|
||||||
|
*/
|
||||||
|
public function handle(Request $request, Closure $next)
|
||||||
|
{
|
||||||
|
// * For Vite DEV
|
||||||
|
$host = \parse_url(\config('app.url'), \PHP_URL_HOST);
|
||||||
|
View::share('nonce', $nonce = Vite::useCspNonce());
|
||||||
|
// * For VueJS dev tool to execute
|
||||||
|
$jsDev = config('app.debug') ? "'unsafe-eval' 'unsafe-inline'" : "'nonce-{$nonce}' 'strict-dynamic'";
|
||||||
|
$scheme = config('app.env') === 'local' ? 'http' : 'https';
|
||||||
|
$reportUri = Route::has('cspReportUri') ?
|
||||||
|
\sprintf(' report-uri %s ;', \parse_url(route('cspReportUri'), \PHP_URL_PATH)) : '';
|
||||||
|
$this->removeUnwantedHeaders($this->unwantedHeaderList);
|
||||||
|
$response = $next($request);
|
||||||
|
$response->headers->set('Referrer-Policy', 'no-referrer-when-downgrade');
|
||||||
|
$response->headers->set('X-Content-Type-Options', 'nosniff');
|
||||||
|
$response->headers->set('X-XSS-Protection', '1; mode=block');
|
||||||
|
$response->headers->set('X-Frame-Options', 'SAMEORIGIN');
|
||||||
|
$response->headers->set('Strict-Transport-Security', 'max-age=31536000; includeSubDomains;');
|
||||||
|
$cspPolicies = [
|
||||||
|
"default-src 'none';",
|
||||||
|
"manifest-src 'self';",
|
||||||
|
// * CKEDITOR Specific https://ckeditor.com/docs/ckeditor5/latest/installation/advanced/csp.html
|
||||||
|
"connect-src 'self' blob: data: " . implode(' ', [
|
||||||
|
// * VITE DEV
|
||||||
|
config('app.debug') ? "wss://{$host}:4443" : '',
|
||||||
|
config('app.debug') ? "https://{$host}:4443" : '',
|
||||||
|
]) . ';',
|
||||||
|
"img-src 'self' data: blob: " . implode(' ', [
|
||||||
|
// * VITE DEV
|
||||||
|
config('app.debug') ? "https://{$host}:4443" : '',
|
||||||
|
]) . ';',
|
||||||
|
"object-src 'self' " . implode(' ', []) . ';',
|
||||||
|
'frame-src ' . implode(' ', []) . ';',
|
||||||
|
"font-src 'self' data: " . \implode(' ', [
|
||||||
|
// * VITE DEV
|
||||||
|
config('app.debug') ? "https://{$host}:4443" : '',
|
||||||
|
]) . ';',
|
||||||
|
// ! Using CKEditor will must have unsafe-inline style
|
||||||
|
// phpcs:ignore Generic.Files.LineLength.TooLong
|
||||||
|
"style-src 'self' 'unsafe-inline' " . \implode(' ', [
|
||||||
|
// * File Manager
|
||||||
|
'https://cdn.jsdelivr.net',
|
||||||
|
'https://use.fontawesome.com',
|
||||||
|
'https://fonts.googleapis.com'
|
||||||
|
]) . ';',
|
||||||
|
// ! If you dont use ckeditor use this
|
||||||
|
// "style-src 'self' 'nonce-$nonce';",
|
||||||
|
// phpcs:ignore Generic.Files.LineLength.TooLong
|
||||||
|
"script-src 'self' {$jsDev} " . \implode(' ', [
|
||||||
|
'https: blob:',
|
||||||
|
'https://cdnjs.cloudflare.com',
|
||||||
|
// * VITE DEV
|
||||||
|
config('app.debug') ? "https://{$host}:4443" : '',
|
||||||
|
]) . ';'
|
||||||
|
];
|
||||||
|
|
||||||
|
$response->headers->set(
|
||||||
|
'Content-Security-Policy',
|
||||||
|
implode(' ', $cspPolicies) . $reportUri
|
||||||
|
);
|
||||||
|
$response->headers->set('Feature-Policy', 'fullscreen \'self\';');
|
||||||
|
return $response;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove unwanted headers
|
||||||
|
*
|
||||||
|
* @param array $headerList
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
private function removeUnwantedHeaders(array $headerList)
|
||||||
|
{
|
||||||
|
foreach ($headerList as $header) {
|
||||||
|
header_remove($header);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
19
app/Http/Middleware/TrimStrings.php
Normal file
19
app/Http/Middleware/TrimStrings.php
Normal file
|
|
@ -0,0 +1,19 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Middleware;
|
||||||
|
|
||||||
|
use Illuminate\Foundation\Http\Middleware\TrimStrings as Middleware;
|
||||||
|
|
||||||
|
class TrimStrings extends Middleware
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* The names of the attributes that should not be trimmed.
|
||||||
|
*
|
||||||
|
* @var array<int, string>
|
||||||
|
*/
|
||||||
|
protected $except = [
|
||||||
|
'current_password',
|
||||||
|
'password',
|
||||||
|
'password_confirmation',
|
||||||
|
];
|
||||||
|
}
|
||||||
20
app/Http/Middleware/TrustHosts.php
Normal file
20
app/Http/Middleware/TrustHosts.php
Normal file
|
|
@ -0,0 +1,20 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Middleware;
|
||||||
|
|
||||||
|
use Illuminate\Http\Middleware\TrustHosts as Middleware;
|
||||||
|
|
||||||
|
class TrustHosts extends Middleware
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Get the host patterns that should be trusted.
|
||||||
|
*
|
||||||
|
* @return array<int, string|null>
|
||||||
|
*/
|
||||||
|
public function hosts()
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
$this->allSubdomainsOfApplicationUrl(),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
28
app/Http/Middleware/TrustProxies.php
Normal file
28
app/Http/Middleware/TrustProxies.php
Normal file
|
|
@ -0,0 +1,28 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Middleware;
|
||||||
|
|
||||||
|
use Illuminate\Http\Middleware\TrustProxies as Middleware;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
|
||||||
|
class TrustProxies extends Middleware
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* The trusted proxies for this application.
|
||||||
|
*
|
||||||
|
* @var array<int, string>|string|null
|
||||||
|
*/
|
||||||
|
protected $proxies;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The headers that should be used to detect proxies.
|
||||||
|
*
|
||||||
|
* @var integer
|
||||||
|
*/
|
||||||
|
protected $headers =
|
||||||
|
Request::HEADER_X_FORWARDED_FOR |
|
||||||
|
Request::HEADER_X_FORWARDED_HOST |
|
||||||
|
Request::HEADER_X_FORWARDED_PORT |
|
||||||
|
Request::HEADER_X_FORWARDED_PROTO |
|
||||||
|
Request::HEADER_X_FORWARDED_AWS_ELB;
|
||||||
|
}
|
||||||
15
app/Http/Middleware/VerifyCsrfToken.php
Normal file
15
app/Http/Middleware/VerifyCsrfToken.php
Normal file
|
|
@ -0,0 +1,15 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Middleware;
|
||||||
|
|
||||||
|
use Illuminate\Foundation\Http\Middleware\VerifyCsrfToken as Middleware;
|
||||||
|
|
||||||
|
class VerifyCsrfToken extends Middleware
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* The URIs that should be excluded from CSRF verification.
|
||||||
|
*
|
||||||
|
* @var array<int, string>
|
||||||
|
*/
|
||||||
|
protected $except = ['/csp-report'];
|
||||||
|
}
|
||||||
33
app/Http/Requests/Api/GetStatsRequest.php
Normal file
33
app/Http/Requests/Api/GetStatsRequest.php
Normal file
|
|
@ -0,0 +1,33 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Requests\Api;
|
||||||
|
|
||||||
|
use Carbon\Carbon;
|
||||||
|
use Illuminate\Foundation\Http\FormRequest;
|
||||||
|
|
||||||
|
class GetStatsRequest extends FormRequest
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Prepare the data for validation.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
protected function prepareForValidation(): void
|
||||||
|
{
|
||||||
|
$this->merge([
|
||||||
|
'period' => $this->input('period') ? $this->input('period') : 'day',
|
||||||
|
'start' => $this->input('start') ? Carbon::create($this->input('start')) : now()->modify('-1 year'),
|
||||||
|
'end' => $this->input('end') ? Carbon::create($this->input('end')) : now(),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the validation rules that apply to the request.
|
||||||
|
*
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
public function rules(): array
|
||||||
|
{
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
}
|
||||||
28
app/Http/Requests/Bo/Brands/GetBrandUrlsRequest.php
Normal file
28
app/Http/Requests/Bo/Brands/GetBrandUrlsRequest.php
Normal file
|
|
@ -0,0 +1,28 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Requests\Bo\Brands;
|
||||||
|
|
||||||
|
use Illuminate\Foundation\Http\FormRequest;
|
||||||
|
|
||||||
|
class GetBrandUrlsRequest extends FormRequest
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Determine if the user is authorized to make this request.
|
||||||
|
*
|
||||||
|
* @return boolean
|
||||||
|
*/
|
||||||
|
public function authorize()
|
||||||
|
{
|
||||||
|
return auth('backend')->check();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the validation rules that apply to the request.
|
||||||
|
*
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
public function rules()
|
||||||
|
{
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
}
|
||||||
30
app/Http/Requests/Bo/Brands/StoreBrandRequest.php
Normal file
30
app/Http/Requests/Bo/Brands/StoreBrandRequest.php
Normal file
|
|
@ -0,0 +1,30 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Requests\Bo\Brands;
|
||||||
|
|
||||||
|
use Illuminate\Foundation\Http\FormRequest;
|
||||||
|
|
||||||
|
class StoreBrandRequest extends FormRequest
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Determine if the user is authorized to make this request.
|
||||||
|
*
|
||||||
|
* @return boolean
|
||||||
|
*/
|
||||||
|
public function authorize()
|
||||||
|
{
|
||||||
|
return auth('backend')->check();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the validation rules that apply to the request.
|
||||||
|
*
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
public function rules()
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'name' => 'string|max:255|required|unique:brands,name'
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
25
app/Http/Requests/Bo/Brands/UpdateBrandRequest.php
Normal file
25
app/Http/Requests/Bo/Brands/UpdateBrandRequest.php
Normal file
|
|
@ -0,0 +1,25 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Requests\Bo\Brands;
|
||||||
|
|
||||||
|
use Illuminate\Validation\Rule;
|
||||||
|
|
||||||
|
class UpdateBrandRequest extends StoreBrandRequest
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Get the validation rules that apply to the request.
|
||||||
|
*
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
public function rules()
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'name' => [
|
||||||
|
'string',
|
||||||
|
'max:255',
|
||||||
|
'required',
|
||||||
|
Rule::unique('brands', 'name')->ignore($this->brand->id)
|
||||||
|
]
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
31
app/Http/Requests/Bo/Services/StoreServiceRequest.php
Normal file
31
app/Http/Requests/Bo/Services/StoreServiceRequest.php
Normal file
|
|
@ -0,0 +1,31 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Requests\Bo\Services;
|
||||||
|
|
||||||
|
use Illuminate\Foundation\Http\FormRequest;
|
||||||
|
|
||||||
|
class StoreServiceRequest extends FormRequest
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Determine if the user is authorized to make this request.
|
||||||
|
*
|
||||||
|
* @return boolean
|
||||||
|
*/
|
||||||
|
public function authorize()
|
||||||
|
{
|
||||||
|
return auth('backend')->check();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the validation rules that apply to the request.
|
||||||
|
*
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
public function rules()
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'name' => 'string|max:255|required',
|
||||||
|
'brand_id' => 'required|exists:brands,id'
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
19
app/Http/Requests/Bo/Services/UpdateServiceRequest.php
Normal file
19
app/Http/Requests/Bo/Services/UpdateServiceRequest.php
Normal file
|
|
@ -0,0 +1,19 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Requests\Bo\Services;
|
||||||
|
|
||||||
|
class UpdateServiceRequest extends StoreServiceRequest
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Get the validation rules that apply to the request.
|
||||||
|
*
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
public function rules()
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'name' => 'string|max:255|required',
|
||||||
|
'brand_id' => 'required|exists:brands,id'
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
75
app/Http/Requests/Bo/StatsRequest.php
Normal file
75
app/Http/Requests/Bo/StatsRequest.php
Normal file
|
|
@ -0,0 +1,75 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Requests\Bo;
|
||||||
|
|
||||||
|
use App\Enums\PeriodEnum;
|
||||||
|
use App\Models\Brand;
|
||||||
|
use App\Models\Url;
|
||||||
|
use Carbon\Carbon;
|
||||||
|
use Illuminate\Foundation\Http\FormRequest;
|
||||||
|
use Illuminate\Validation\Rule;
|
||||||
|
|
||||||
|
class StatsRequest extends FormRequest
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Determine if the user is authorized to make this request.
|
||||||
|
*
|
||||||
|
* @return boolean
|
||||||
|
*/
|
||||||
|
public function authorize(): bool
|
||||||
|
{
|
||||||
|
return auth('backend')->check();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Prepare the data for validation.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
protected function prepareForValidation(): void
|
||||||
|
{
|
||||||
|
$now = Carbon::now();
|
||||||
|
$urlId = $this->input('url');
|
||||||
|
$brandId = $this->input('brand');
|
||||||
|
$startDate = Carbon::parse($this->input('start'));
|
||||||
|
$endDate = Carbon::parse($this->input('end'));
|
||||||
|
$adjustedEndDate = $endDate->greaterThan($now) ? $now : $endDate;
|
||||||
|
|
||||||
|
$period = PeriodEnum::tryFrom($this->input('period'));
|
||||||
|
|
||||||
|
// Get start & end dates by period.
|
||||||
|
[$adjustedStart, $adjustedEnd] = match ($period) {
|
||||||
|
PeriodEnum::week => [$startDate->copy()->startOfWeek(), $endDate->copy()->endOfWeek()],
|
||||||
|
PeriodEnum::month => [$startDate->copy()->startOfMonth(), $endDate->copy()->endOfMonth()],
|
||||||
|
default => [$startDate, $adjustedEndDate],
|
||||||
|
};
|
||||||
|
|
||||||
|
$this->merge([
|
||||||
|
'url' => Url::find($urlId) ? $urlId : null,
|
||||||
|
'brand' => Brand::find($brandId) ? $brandId : null,
|
||||||
|
'start' => $adjustedStart->toDateString(),
|
||||||
|
'end' => $adjustedEnd->toDateString(),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the validation rules that apply to the request.
|
||||||
|
*
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
public function rules(): array
|
||||||
|
{
|
||||||
|
return (request()->post()) ? [
|
||||||
|
'period' => [
|
||||||
|
'required',
|
||||||
|
'string',
|
||||||
|
Rule::enum(PeriodEnum::class)
|
||||||
|
],
|
||||||
|
'start' => 'required|date|date_format:Y-m-d|before_or_equal:today|after_or_equal:'
|
||||||
|
. Carbon::parse($this->input('end'))->modify('-1 year'),
|
||||||
|
'end' => 'required|date|date_format:Y-m-d|after_or_equal:start',
|
||||||
|
'brand' => 'nullable|integer|exists:App\Models\Brand,id',
|
||||||
|
'url' => 'nullable|integer|exists:App\Models\Url,id'
|
||||||
|
] : [];
|
||||||
|
}
|
||||||
|
}
|
||||||
66
app/Http/Requests/Bo/Urls/StoreUrlRequest.php
Normal file
66
app/Http/Requests/Bo/Urls/StoreUrlRequest.php
Normal file
|
|
@ -0,0 +1,66 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Requests\Bo\Urls;
|
||||||
|
|
||||||
|
use App\Enums\Users\RoleEnum;
|
||||||
|
use App\Traits\Requests\HasPictureWithSvgSupport;
|
||||||
|
use Illuminate\Foundation\Http\FormRequest;
|
||||||
|
use Illuminate\Support\Str;
|
||||||
|
use Illuminate\Validation\Rule;
|
||||||
|
use Uwil\LaravelCommon\Traits\Requests\HasPicture;
|
||||||
|
|
||||||
|
class StoreUrlRequest extends FormRequest
|
||||||
|
{
|
||||||
|
use HasPicture;
|
||||||
|
use HasPictureWithSvgSupport;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determine if the user is authorized to make this request.
|
||||||
|
*
|
||||||
|
* @return boolean
|
||||||
|
*/
|
||||||
|
public function authorize()
|
||||||
|
{
|
||||||
|
return auth('backend')->check();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Prepare the data for validation.
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
protected function prepareForValidation(): void
|
||||||
|
{
|
||||||
|
$this->mergePicture('logo');
|
||||||
|
$this->merge([
|
||||||
|
'key' => Str::slug($this->key),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the validation rules that apply to the request.
|
||||||
|
*
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
public function rules()
|
||||||
|
{
|
||||||
|
$user = auth('backend')->user();
|
||||||
|
|
||||||
|
$rules = [
|
||||||
|
'key' => 'required|string|max:45|unique:App\Models\Url,key',
|
||||||
|
'value' => 'required|url:http,https|max:255',
|
||||||
|
'title' => 'required|string|min:3|max:255',
|
||||||
|
'brand_id' => [
|
||||||
|
'required',
|
||||||
|
'integer',
|
||||||
|
'exists:App\Models\Brand,id',
|
||||||
|
!$user->role->equals(RoleEnum::conceptor) ? Rule::in($user->brands->pluck('id')) : ''
|
||||||
|
],
|
||||||
|
'image_settings' => 'required|json',
|
||||||
|
];
|
||||||
|
|
||||||
|
return \array_merge(
|
||||||
|
$rules,
|
||||||
|
$this->pictureRulesWithSvgSupport('logo', 100, 100, 500, 500, true),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
41
app/Http/Requests/Bo/Urls/UpdateUrlRequest.php
Normal file
41
app/Http/Requests/Bo/Urls/UpdateUrlRequest.php
Normal file
|
|
@ -0,0 +1,41 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Requests\Bo\Urls;
|
||||||
|
|
||||||
|
use Illuminate\Validation\Rule;
|
||||||
|
|
||||||
|
class UpdateUrlRequest extends StoreUrlRequest
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Prepare the data for validation.
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
protected function prepareForValidation(): void
|
||||||
|
{
|
||||||
|
$this->merge([
|
||||||
|
'key' => $this->url->key,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the validation rules that apply to the request.
|
||||||
|
*
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
public function rules()
|
||||||
|
{
|
||||||
|
$rules = [
|
||||||
|
'key' => [
|
||||||
|
'required',
|
||||||
|
'string',
|
||||||
|
'max:45',
|
||||||
|
Rule::unique('urls', 'key')->ignore($this->url->id)
|
||||||
|
],
|
||||||
|
];
|
||||||
|
|
||||||
|
return \array_merge(
|
||||||
|
parent::rules(),
|
||||||
|
$rules,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
55
app/Http/Requests/Bo/Users/StoreUserRequest.php
Normal file
55
app/Http/Requests/Bo/Users/StoreUserRequest.php
Normal file
|
|
@ -0,0 +1,55 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Requests\Bo\Users;
|
||||||
|
|
||||||
|
use App\Enums\Users\RoleEnum;
|
||||||
|
use App\Models\User;
|
||||||
|
use Illuminate\Foundation\Http\FormRequest;
|
||||||
|
use Illuminate\Support\Facades\Gate;
|
||||||
|
use Illuminate\Validation\Rules\Enum;
|
||||||
|
|
||||||
|
class StoreUserRequest extends FormRequest
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Determine if the user is authorized to make this request.
|
||||||
|
*
|
||||||
|
* @return boolean
|
||||||
|
*/
|
||||||
|
public function authorize()
|
||||||
|
{
|
||||||
|
return Gate::check('create', User::class);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the validation rules that apply to the request.
|
||||||
|
*
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
public function rules()
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'firstname' => 'required|string|min:3|max:255',
|
||||||
|
'lastname' => 'required|string|min:3|max:255',
|
||||||
|
'email' => 'required|email:rfc,strict,dns,spoof,filter|unique:users,email',
|
||||||
|
'role' => ['required', new Enum(RoleEnum::class)],
|
||||||
|
'brands' => 'nullable|array|min:1',
|
||||||
|
'brands.*' => 'nullable|array',
|
||||||
|
'brands.*.id' => 'nullable|exists:\App\Models\Brand,id',
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get custom attributes for validator errors.
|
||||||
|
*
|
||||||
|
* @return array<string, string>
|
||||||
|
*/
|
||||||
|
public function attributes(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'first_name' => trans('validation.attributes.first_name'),
|
||||||
|
'last_name' => trans('validation.attributes.last_name'),
|
||||||
|
'email' => trans('validation.attributes.email'),
|
||||||
|
'role' => trans('validation.attributes.role'),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
52
app/Http/Requests/Bo/Users/UpdateUserRequest.php
Normal file
52
app/Http/Requests/Bo/Users/UpdateUserRequest.php
Normal file
|
|
@ -0,0 +1,52 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Requests\Bo\Users;
|
||||||
|
|
||||||
|
use App\Enums\Users\RoleEnum;
|
||||||
|
use Illuminate\Support\Facades\Gate;
|
||||||
|
use Illuminate\Validation\Rule;
|
||||||
|
|
||||||
|
class UpdateUserRequest extends StoreUserRequest
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Determine if the user is authorized to make this request.
|
||||||
|
*
|
||||||
|
* @return boolean
|
||||||
|
*/
|
||||||
|
public function authorize(): bool
|
||||||
|
{
|
||||||
|
return Gate::check('update', $this->route('user'));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Prepare the data for validation.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
protected function prepareForValidation(): void
|
||||||
|
{
|
||||||
|
/** @var \App\Models\User $user */
|
||||||
|
$user = $this->route('user');
|
||||||
|
if (!$user->role->equals(RoleEnum::conceptor)) {
|
||||||
|
$this->merge(['email' => $user->email]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the validation rules that apply to the request.
|
||||||
|
*
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
public function rules()
|
||||||
|
{
|
||||||
|
/** @var \App\Models\User $user */
|
||||||
|
$user = $this->route('user');
|
||||||
|
return array_merge(parent::rules(), [
|
||||||
|
'email' => [
|
||||||
|
'required',
|
||||||
|
'email:rfc,strict,dns,spoof,filter',
|
||||||
|
Rule::unique('users', 'email')->ignore($user->getRouteKey()),
|
||||||
|
]
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
0
app/Http/Requests/Fo/.gitkeep
Normal file
0
app/Http/Requests/Fo/.gitkeep
Normal file
23
app/Http/Resources/BrandCollection.php
Normal file
23
app/Http/Resources/BrandCollection.php
Normal file
|
|
@ -0,0 +1,23 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Resources;
|
||||||
|
|
||||||
|
use Illuminate\Http\Resources\Json\ResourceCollection;
|
||||||
|
|
||||||
|
class BrandCollection extends ResourceCollection
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Transform the resource collection into an array.
|
||||||
|
*
|
||||||
|
* @param \Illuminate\Http\Request $request
|
||||||
|
* @return array|\Illuminate\Contracts\Support\Arrayable|\JsonSerializable
|
||||||
|
*
|
||||||
|
* @phpcs:disable Generic.CodeAnalysis.UnusedFunctionParameter.FoundInExtendedClass, Squiz.Commenting.FunctionComment.TypeHintMissing
|
||||||
|
*/
|
||||||
|
public function toArray($request)
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'data' => $this->collection
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
27
app/Http/Resources/BrandResource.php
Normal file
27
app/Http/Resources/BrandResource.php
Normal file
|
|
@ -0,0 +1,27 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Resources;
|
||||||
|
|
||||||
|
use Illuminate\Http\Resources\Json\JsonResource;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @mixin \App\Models\Brand
|
||||||
|
*/
|
||||||
|
class BrandResource extends JsonResource
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Transform the resource into an array.
|
||||||
|
*
|
||||||
|
* @param \Illuminate\Http\Request $request
|
||||||
|
* @return array|\Illuminate\Contracts\Support\Arrayable|\JsonSerializable
|
||||||
|
*
|
||||||
|
* @phpcs:disable Generic.CodeAnalysis.UnusedFunctionParameter.FoundInExtendedClass, Squiz.Commenting.FunctionComment.TypeHintMissing
|
||||||
|
*/
|
||||||
|
public function toArray($request)
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'id' => $this->id,
|
||||||
|
'name' => $this->name,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
23
app/Http/Resources/UrlCollection.php
Normal file
23
app/Http/Resources/UrlCollection.php
Normal file
|
|
@ -0,0 +1,23 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Resources;
|
||||||
|
|
||||||
|
use Illuminate\Http\Resources\Json\ResourceCollection;
|
||||||
|
|
||||||
|
class UrlCollection extends ResourceCollection
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Transform the resource collection into an array.
|
||||||
|
*
|
||||||
|
* @param \Illuminate\Http\Request $request
|
||||||
|
* @return array|\Illuminate\Contracts\Support\Arrayable|\JsonSerializable
|
||||||
|
*
|
||||||
|
* @phpcs:disable Generic.CodeAnalysis.UnusedFunctionParameter.FoundInExtendedClass, Squiz.Commenting.FunctionComment.TypeHintMissing
|
||||||
|
*/
|
||||||
|
public function toArray($request)
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'data' => $this->collection
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
23
app/Http/Resources/UrlHistoryCollection.php
Normal file
23
app/Http/Resources/UrlHistoryCollection.php
Normal file
|
|
@ -0,0 +1,23 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Resources;
|
||||||
|
|
||||||
|
use Illuminate\Http\Resources\Json\ResourceCollection;
|
||||||
|
|
||||||
|
class UrlHistoryCollection extends ResourceCollection
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Transform the resource collection into an array.
|
||||||
|
*
|
||||||
|
* @param \Illuminate\Http\Request $request
|
||||||
|
* @return array|\Illuminate\Contracts\Support\Arrayable|\JsonSerializable
|
||||||
|
*
|
||||||
|
* @phpcs:disable Generic.CodeAnalysis.UnusedFunctionParameter.FoundInExtendedClass, Squiz.Commenting.FunctionComment.TypeHintMissing
|
||||||
|
*/
|
||||||
|
public function toArray($request)
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'data' => $this->collection
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
29
app/Http/Resources/UrlHistoryResource.php
Normal file
29
app/Http/Resources/UrlHistoryResource.php
Normal file
|
|
@ -0,0 +1,29 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Resources;
|
||||||
|
|
||||||
|
use Illuminate\Http\Resources\Json\JsonResource;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @mixin \App\Models\UrlHistory
|
||||||
|
*/
|
||||||
|
class UrlHistoryResource extends JsonResource
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Transform the resource into an array.
|
||||||
|
*
|
||||||
|
* @param \Illuminate\Http\Request $request
|
||||||
|
* @return array|\Illuminate\Contracts\Support\Arrayable|\JsonSerializable
|
||||||
|
*
|
||||||
|
* @phpcs:disable Generic.CodeAnalysis.UnusedFunctionParameter.FoundInExtendedClass, Squiz.Commenting.FunctionComment.TypeHintMissing
|
||||||
|
*/
|
||||||
|
public function toArray($request)
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'id' => $this->id,
|
||||||
|
'value' => $this->value,
|
||||||
|
'start' => $this->start_date,
|
||||||
|
'end' => $this->end_date,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
29
app/Http/Resources/UrlResource.php
Normal file
29
app/Http/Resources/UrlResource.php
Normal file
|
|
@ -0,0 +1,29 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Resources;
|
||||||
|
|
||||||
|
use Illuminate\Http\Resources\Json\JsonResource;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @mixin \App\Models\Url
|
||||||
|
*/
|
||||||
|
class UrlResource extends JsonResource
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Transform the resource into an array.
|
||||||
|
*
|
||||||
|
* @param \Illuminate\Http\Request $request
|
||||||
|
* @return array|\Illuminate\Contracts\Support\Arrayable|\JsonSerializable
|
||||||
|
*
|
||||||
|
* @phpcs:disable Generic.CodeAnalysis.UnusedFunctionParameter.FoundInExtendedClass, Squiz.Commenting.FunctionComment.TypeHintMissing
|
||||||
|
*/
|
||||||
|
public function toArray($request)
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'id' => $this->id,
|
||||||
|
'key' => $this->key,
|
||||||
|
'value' => $this->value,
|
||||||
|
'brand' => new BrandResource($this->brand)
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
24
app/Http/Resources/UrlStatsCollection.php
Normal file
24
app/Http/Resources/UrlStatsCollection.php
Normal file
|
|
@ -0,0 +1,24 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Resources;
|
||||||
|
|
||||||
|
use Illuminate\Http\Resources\Json\ResourceCollection;
|
||||||
|
|
||||||
|
class UrlStatsCollection extends ResourceCollection
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Transform the resource collection into an array.
|
||||||
|
*
|
||||||
|
* @param \Illuminate\Http\Request $request
|
||||||
|
* @return array|\Illuminate\Contracts\Support\Arrayable|\JsonSerializable
|
||||||
|
*
|
||||||
|
* @phpcs:disable Generic.CodeAnalysis.UnusedFunctionParameter.FoundInExtendedClass, Squiz.Commenting.FunctionComment.TypeHintMissing
|
||||||
|
*/
|
||||||
|
public function toArray($request)
|
||||||
|
{
|
||||||
|
// phpcs:enable
|
||||||
|
return [
|
||||||
|
'data' => $this->collection
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
28
app/Http/Resources/UrlStatsResource.php
Normal file
28
app/Http/Resources/UrlStatsResource.php
Normal file
|
|
@ -0,0 +1,28 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Resources;
|
||||||
|
|
||||||
|
use Illuminate\Http\Resources\Json\JsonResource;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @mixin \App\Models\UrlStats
|
||||||
|
*/
|
||||||
|
class UrlStatsResource extends JsonResource
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Transform the resource into an array.
|
||||||
|
*
|
||||||
|
* @param \Illuminate\Http\Request $request
|
||||||
|
* @return array|\Illuminate\Contracts\Support\Arrayable|\JsonSerializable
|
||||||
|
*
|
||||||
|
* @phpcs:disable Generic.CodeAnalysis.UnusedFunctionParameter.FoundInExtendedClass, Squiz.Commenting.FunctionComment.TypeHintMissing
|
||||||
|
*/
|
||||||
|
public function toArray($request)
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'id' => $this->id,
|
||||||
|
'calls' => $this->calls,
|
||||||
|
'date' => $this->date,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
59
app/Jobs/IncrementUrlStat.php
Normal file
59
app/Jobs/IncrementUrlStat.php
Normal file
|
|
@ -0,0 +1,59 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Jobs;
|
||||||
|
|
||||||
|
use App\Models\Url;
|
||||||
|
use App\Models\UrlStats;
|
||||||
|
use Illuminate\Bus\Queueable;
|
||||||
|
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||||
|
use Illuminate\Foundation\Bus\Dispatchable;
|
||||||
|
use Illuminate\Queue\InteractsWithQueue;
|
||||||
|
use Illuminate\Support\Facades\Log;
|
||||||
|
|
||||||
|
class IncrementUrlStat implements ShouldQueue
|
||||||
|
{
|
||||||
|
use Dispatchable;
|
||||||
|
use InteractsWithQueue;
|
||||||
|
use Queueable;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* To be serialized by jobs system
|
||||||
|
*
|
||||||
|
* @var Url
|
||||||
|
* */
|
||||||
|
private $url;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new job instance.
|
||||||
|
*
|
||||||
|
* @param \App\Models\Url $url
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function __construct(Url $url)
|
||||||
|
{
|
||||||
|
$this->url = $url;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Execute the job.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function handle()
|
||||||
|
{
|
||||||
|
$urlStat = UrlStats::query()
|
||||||
|
->whereDate('date', now())
|
||||||
|
->firstOrNew(
|
||||||
|
[
|
||||||
|
'url_key' => $this->url->key,
|
||||||
|
'url' => $this->url->value,
|
||||||
|
],
|
||||||
|
);
|
||||||
|
|
||||||
|
$urlStat->calls += 1;
|
||||||
|
|
||||||
|
$urlStat->saveOrFail();
|
||||||
|
|
||||||
|
Log::debug(sprintf('Stats incremented for URL : %s', $this->url->key));
|
||||||
|
}
|
||||||
|
}
|
||||||
182
app/Lib/Helpers/ChartJsHelper.php
Normal file
182
app/Lib/Helpers/ChartJsHelper.php
Normal file
|
|
@ -0,0 +1,182 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Lib\Helpers;
|
||||||
|
|
||||||
|
use IcehouseVentures\LaravelChartjs\Builder;
|
||||||
|
use Illuminate\Support\Arr;
|
||||||
|
|
||||||
|
class ChartJsHelper extends Builder
|
||||||
|
{
|
||||||
|
// phpcs:disable
|
||||||
|
/**
|
||||||
|
* @var array
|
||||||
|
*/
|
||||||
|
public $charts = [];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
public $name;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var array
|
||||||
|
*/
|
||||||
|
private $defaults = [
|
||||||
|
'datasets' => [],
|
||||||
|
'labels' => [],
|
||||||
|
'type' => 'line',
|
||||||
|
'options' => [],
|
||||||
|
'size' => ['width' => null, 'height' => null]
|
||||||
|
];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var array
|
||||||
|
*/
|
||||||
|
private $types = [
|
||||||
|
'bar',
|
||||||
|
'horizontalBar',
|
||||||
|
'bubble',
|
||||||
|
'scatter',
|
||||||
|
'doughnut',
|
||||||
|
'line',
|
||||||
|
'pie',
|
||||||
|
'polarArea',
|
||||||
|
'radar'
|
||||||
|
];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param $name
|
||||||
|
*
|
||||||
|
* @return $this|Builder
|
||||||
|
*/
|
||||||
|
public function name($name)
|
||||||
|
{
|
||||||
|
$this->name = $name;
|
||||||
|
$this->charts[$name] = $this->defaults;
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param $element
|
||||||
|
*
|
||||||
|
* @return Builder
|
||||||
|
*/
|
||||||
|
public function element($element)
|
||||||
|
{
|
||||||
|
return $this->set('element', $element);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param array $labels
|
||||||
|
*
|
||||||
|
* @return Builder
|
||||||
|
*/
|
||||||
|
public function labels(array $labels)
|
||||||
|
{
|
||||||
|
return $this->set('labels', $labels);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param array $datasets
|
||||||
|
*
|
||||||
|
* @return Builder
|
||||||
|
*/
|
||||||
|
public function datasets(array $datasets)
|
||||||
|
{
|
||||||
|
return $this->set('datasets', $datasets);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param $type
|
||||||
|
*
|
||||||
|
* @return Builder
|
||||||
|
*/
|
||||||
|
public function type($type)
|
||||||
|
{
|
||||||
|
if (!in_array($type, $this->types)) {
|
||||||
|
throw new \InvalidArgumentException('Invalid Chart type.');
|
||||||
|
}
|
||||||
|
return $this->set('type', $type);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param array $size
|
||||||
|
*
|
||||||
|
* @return Builder
|
||||||
|
*/
|
||||||
|
public function size($size)
|
||||||
|
{
|
||||||
|
return $this->set('size', $size);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param array $options
|
||||||
|
*
|
||||||
|
* @return $this|Builder
|
||||||
|
*/
|
||||||
|
public function options(array $options)
|
||||||
|
{
|
||||||
|
foreach ($options as $key => $value) {
|
||||||
|
$this->set('options.' . $key, $value);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param string|array $optionsRaw
|
||||||
|
* @return $this
|
||||||
|
*/
|
||||||
|
public function optionsRaw($optionsRaw)
|
||||||
|
{
|
||||||
|
if (is_array($optionsRaw)) {
|
||||||
|
$this->set('optionsRaw', json_encode($optionsRaw));
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->set('optionsRaw', $optionsRaw);
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return mixed
|
||||||
|
*/
|
||||||
|
public function render()
|
||||||
|
{
|
||||||
|
$chart = $this->charts[$this->name];
|
||||||
|
|
||||||
|
return view('vendor.chart-template')
|
||||||
|
->with('datasets', $chart['datasets'])
|
||||||
|
->with('element', $this->name)
|
||||||
|
->with('labels', $chart['labels'])
|
||||||
|
->with('options', isset($chart['options']) ? $chart['options'] : '')
|
||||||
|
->with('optionsRaw', isset($chart['optionsRaw']) ? $chart['optionsRaw'] : '')
|
||||||
|
->with('type', $chart['type'])
|
||||||
|
->with('size', $chart['size']);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param $key
|
||||||
|
*
|
||||||
|
* @return mixed
|
||||||
|
*/
|
||||||
|
public function get($key)
|
||||||
|
{
|
||||||
|
return Arr::get($this->charts[$this->name], $key);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param $key
|
||||||
|
* @param $value
|
||||||
|
*
|
||||||
|
* @return $this|Builder
|
||||||
|
*/
|
||||||
|
public function set($key, $value)
|
||||||
|
{
|
||||||
|
Arr::set($this->charts[$this->name], $key, $value);
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
// phpcs:enable
|
||||||
|
}
|
||||||
85
app/Lib/Helpers/ToolboxHelper.php
Normal file
85
app/Lib/Helpers/ToolboxHelper.php
Normal file
|
|
@ -0,0 +1,85 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Lib\Helpers;
|
||||||
|
|
||||||
|
use Illuminate\Contracts\Encryption\DecryptException;
|
||||||
|
use Illuminate\Support\Facades\Crypt;
|
||||||
|
use Uwil\LaravelCommon\Helpers\ToolboxHelper as BaseToolboxHelper;
|
||||||
|
|
||||||
|
class ToolboxHelper extends BaseToolboxHelper
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Calculate the request signature
|
||||||
|
*
|
||||||
|
* @param string $secret
|
||||||
|
* @param string $uri
|
||||||
|
* @param string $method
|
||||||
|
* @param array $parameters
|
||||||
|
* @return string request signature
|
||||||
|
* @throws \RuntimeException If an error occurs.
|
||||||
|
*/
|
||||||
|
public static function signRequest(string $secret, string $uri, string $method, array $parameters): string
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
$uri = collect(\parse_url($uri));
|
||||||
|
$method = \strtolower($method);
|
||||||
|
|
||||||
|
// * Remove signature from parameters to calculate the hashmac.
|
||||||
|
$signature = collect([
|
||||||
|
'uri' => sprintf(
|
||||||
|
'%s://%s%s',
|
||||||
|
$uri->get('scheme'),
|
||||||
|
$uri->get('host'),
|
||||||
|
$uri->get('path')
|
||||||
|
),
|
||||||
|
'method' => $method
|
||||||
|
]);
|
||||||
|
|
||||||
|
$flattenRequestArray = function (array $array) use (&$flattenRequestArray) {
|
||||||
|
$out = [];
|
||||||
|
foreach ($array as $key => $value) {
|
||||||
|
if (is_array($value)) {
|
||||||
|
$subArray = $flattenRequestArray($value);
|
||||||
|
foreach ($subArray as $subKey => $subValue) {
|
||||||
|
$out["$key.$subKey"] = $subValue;
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
$out[$key] = $value;
|
||||||
|
}
|
||||||
|
return $out;
|
||||||
|
};
|
||||||
|
|
||||||
|
$parameters = $flattenRequestArray($parameters);
|
||||||
|
$parameters = collect($parameters);
|
||||||
|
|
||||||
|
// * Remove signature from parameters.
|
||||||
|
$parameters->pull('signature');
|
||||||
|
|
||||||
|
// * Natural sort all parameters.
|
||||||
|
$parameters = $parameters->sortKeys(\SORT_NATURAL)
|
||||||
|
->map(function ($value, $key) {
|
||||||
|
return sprintf('%s=%s', \rawurlencode($key), \rawurlencode($value));
|
||||||
|
});
|
||||||
|
|
||||||
|
$signature->push($parameters->implode('&'));
|
||||||
|
|
||||||
|
$calculatedHash = base64_encode(hash_hmac(
|
||||||
|
'sha256',
|
||||||
|
$signature->implode('\n'),
|
||||||
|
Crypt::decryptString($secret),
|
||||||
|
true
|
||||||
|
));
|
||||||
|
|
||||||
|
if (!$calculatedHash) {
|
||||||
|
throw new \RuntimeException('Failed to calculate requests hash');
|
||||||
|
}
|
||||||
|
|
||||||
|
return $calculatedHash;
|
||||||
|
} catch (DecryptException $e) {
|
||||||
|
throw new \RuntimeException('Failed to decrypt service credentials', 0, $e);
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
throw new \RuntimeException('An error occured when authenticating service', 0, $e);
|
||||||
|
} //end try
|
||||||
|
}
|
||||||
|
}
|
||||||
176
app/Models/ActivityLog.php
Normal file
176
app/Models/ActivityLog.php
Normal file
|
|
@ -0,0 +1,176 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Models;
|
||||||
|
|
||||||
|
use App\Enums\ActivityLogs\ActivityLogsEventEnum;
|
||||||
|
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||||
|
use Illuminate\Database\Eloquent\Model;
|
||||||
|
use Illuminate\Support\Str;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @property string $model Target model.
|
||||||
|
* @property string $model_id Id of the target model.
|
||||||
|
* @property \App\Enums\ActivityLogs\ActivityLogsEventEnum $event Event of this activity (ActivityLogsEventEnum).
|
||||||
|
* @property array $data Changes.
|
||||||
|
* @property-read \Illuminate\Support\Carbon $created_at Created date.
|
||||||
|
*
|
||||||
|
* @property-read \App\Models\User $user User BelongsTo relation.
|
||||||
|
*/
|
||||||
|
class ActivityLog extends Model
|
||||||
|
{
|
||||||
|
use HasFactory;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Indicates if the model should be timestamped.
|
||||||
|
*
|
||||||
|
* @var boolean
|
||||||
|
*/
|
||||||
|
public $timestamps = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The attributes that are mass assignable.
|
||||||
|
*
|
||||||
|
* @var array<int, string>
|
||||||
|
*/
|
||||||
|
protected $fillable = [
|
||||||
|
'is_anonymous',
|
||||||
|
'model_class',
|
||||||
|
'model_id',
|
||||||
|
'event',
|
||||||
|
'data',
|
||||||
|
'created_at',
|
||||||
|
];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The attributes that should be cast.
|
||||||
|
*
|
||||||
|
* @var array<string, string>
|
||||||
|
*/
|
||||||
|
protected $casts = [
|
||||||
|
'is_anonymous' => 'boolean',
|
||||||
|
'event' => ActivityLogsEventEnum::class,
|
||||||
|
'data' => 'json',
|
||||||
|
'created_at' => 'datetime',
|
||||||
|
];
|
||||||
|
|
||||||
|
// * METHODS
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add new activity to the activity logs list.
|
||||||
|
*
|
||||||
|
* @param \Illuminate\Database\Eloquent\Model $model
|
||||||
|
* @param \App\Enums\ActivityLogs\ActivityLogsEventEnum $eventEnum
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public static function addActivity(Model $model, ActivityLogsEventEnum $eventEnum): void
|
||||||
|
{
|
||||||
|
$isAnonymous = true;
|
||||||
|
/** @var \Illuminate\Database\Eloquent\Model|null */
|
||||||
|
$userModel = auth('backend')->user();
|
||||||
|
if (is_object($userModel)) {
|
||||||
|
$userModel = $userModel->getKeyForSelectQuery();
|
||||||
|
$isAnonymous = false;
|
||||||
|
}
|
||||||
|
$activity = new self();
|
||||||
|
$activity->user_id = $userModel;
|
||||||
|
$activity->is_console = app()->runningInConsole();
|
||||||
|
$activity->is_anonymous = $isAnonymous;
|
||||||
|
$activity->model_class = \get_class($model);
|
||||||
|
$activity->model_id = $model->getKey();
|
||||||
|
$activity->event = $eventEnum;
|
||||||
|
$activity->data = static::getChangedColumns($activity, $model, $eventEnum);
|
||||||
|
// @phpstan-ignore-next-line
|
||||||
|
$activity->created_at = now();
|
||||||
|
$activity->saveOrFail();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get old, new columns that has changed.
|
||||||
|
*
|
||||||
|
* @param self $activity
|
||||||
|
* @param \Illuminate\Database\Eloquent\Model $model
|
||||||
|
* @param \App\Enums\ActivityLogs\ActivityLogsEventEnum $eventEnum
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
public static function getChangedColumns(self $activity, Model $model, ActivityLogsEventEnum $eventEnum): array
|
||||||
|
{
|
||||||
|
$modelClassName = $activity->model_class;
|
||||||
|
/** Get type of each fields of the target model */
|
||||||
|
/** @var \Illuminate\Database\Eloquent\Model|null $targetModel */
|
||||||
|
$targetModel = $activity->model_class::where(
|
||||||
|
(new $modelClassName())->getRouteKeyName(),
|
||||||
|
$activity->model_id
|
||||||
|
)->first();
|
||||||
|
|
||||||
|
if (\is_null($targetModel)) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
$targetModelTypes = collect($targetModel->toArray())
|
||||||
|
->map(fn ($value) => self::getValueType($value))
|
||||||
|
->toArray();
|
||||||
|
/** Return old, new and type of values changed */
|
||||||
|
return match ($eventEnum) {
|
||||||
|
ActivityLogsEventEnum::created => array_intersect_key($targetModelTypes, $model->toArray()),
|
||||||
|
ActivityLogsEventEnum::duplicated => array_intersect_key($targetModelTypes, $model->toArray()),
|
||||||
|
ActivityLogsEventEnum::updated => array_intersect_key($targetModelTypes, $model->getChanges()),
|
||||||
|
default => []
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a value type as a string
|
||||||
|
* ! Use this for display purpose only
|
||||||
|
*
|
||||||
|
* @param mixed $value
|
||||||
|
* @return string
|
||||||
|
* @throws \RuntimeException If type is unhlanded.
|
||||||
|
* @phpcs:disable Generic.Metrics.CyclomaticComplexity.MaxExceeded
|
||||||
|
*/
|
||||||
|
private static function getValueType(mixed $value): string
|
||||||
|
{
|
||||||
|
// phpcs:enable
|
||||||
|
$type = \gettype($value);
|
||||||
|
switch ($type) {
|
||||||
|
// phpcs:ignore PSR2.ControlStructures.SwitchDeclaration.TerminatingComment
|
||||||
|
case 'string':
|
||||||
|
return match (true) {
|
||||||
|
Str::length(\strip_tags($value)) !== Str::length($value) => 'html',
|
||||||
|
Str::startsWith($value, 'storage/modelfiles') => 'file',
|
||||||
|
Str::isUuid($value) => 'uuid',
|
||||||
|
Str::isUlid($value) => 'ulid',
|
||||||
|
Str::isUrl($value) => 'url',
|
||||||
|
Str::isMatch('/^[0-9A-Za-z_-]{10}[048AEIMQUYcgkosw]$/', $value) => 'youtube_video_id',
|
||||||
|
\is_numeric($value) => 'numeric',
|
||||||
|
Str::isJson($value) and
|
||||||
|
(Str::startsWith($value, '[') or Str::startsWith($value, '{')) => 'json',
|
||||||
|
default => 'string'
|
||||||
|
};
|
||||||
|
case 'NULL':
|
||||||
|
case 'object':
|
||||||
|
case 'array':
|
||||||
|
case 'integer':
|
||||||
|
case 'boolean':
|
||||||
|
return $type;
|
||||||
|
case 'double':
|
||||||
|
return is_float($value) ? 'float' : 'double';
|
||||||
|
case 'resource (closed)':
|
||||||
|
case 'resource':
|
||||||
|
throw new \RuntimeException('Laravel model properties should not contain resources !');
|
||||||
|
case 'unknown type':
|
||||||
|
default:
|
||||||
|
throw new \RuntimeException('Unhandled Type ' . \gettype($value));
|
||||||
|
} //end switch
|
||||||
|
}
|
||||||
|
|
||||||
|
// * RELATIONS
|
||||||
|
|
||||||
|
/**
|
||||||
|
* User BelongsTo relation.
|
||||||
|
*
|
||||||
|
* @return \Illuminate\Database\Eloquent\Relations\BelongsTo
|
||||||
|
*/
|
||||||
|
public function user(): \Illuminate\Database\Eloquent\Relations\BelongsTo
|
||||||
|
{
|
||||||
|
return $this->belongsTo(User::class);
|
||||||
|
}
|
||||||
|
}
|
||||||
71
app/Models/Brand.php
Normal file
71
app/Models/Brand.php
Normal file
|
|
@ -0,0 +1,71 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Models;
|
||||||
|
|
||||||
|
use App\Traits\Models\ActivityLog;
|
||||||
|
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||||
|
use Illuminate\Database\Eloquent\Model;
|
||||||
|
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
|
||||||
|
|
||||||
|
class Brand extends Model
|
||||||
|
{
|
||||||
|
use HasFactory;
|
||||||
|
use ActivityLog;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The attributes that are mass assignable.
|
||||||
|
*
|
||||||
|
* @var list<string>
|
||||||
|
*/
|
||||||
|
protected $fillable = [
|
||||||
|
'name'
|
||||||
|
];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The "booted" method of the model.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
protected static function boot(): void
|
||||||
|
{
|
||||||
|
parent::boot();
|
||||||
|
|
||||||
|
static::deleting(function (self $model) {
|
||||||
|
$model->users()->detach();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// * RELATIONSHIPS
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the urls for the brand.
|
||||||
|
*
|
||||||
|
* @return \Illuminate\Database\Eloquent\Relations\HasMany<\App\Models\Url, $this>
|
||||||
|
*/
|
||||||
|
public function urls()
|
||||||
|
{
|
||||||
|
return $this->hasMany(Url::class);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the urls for the brand.
|
||||||
|
*
|
||||||
|
* @return \Illuminate\Database\Eloquent\Relations\HasMany<\App\Models\Service, $this>
|
||||||
|
*/
|
||||||
|
public function services()
|
||||||
|
{
|
||||||
|
return $this->hasMany(Service::class);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The users that belong to the brand.
|
||||||
|
*
|
||||||
|
* @return \Illuminate\Database\Eloquent\Relations\BelongsToMany<\App\Models\User,
|
||||||
|
* $this,
|
||||||
|
* \Illuminate\Database\Eloquent\Relations\Pivot>
|
||||||
|
*/
|
||||||
|
public function users(): BelongsToMany
|
||||||
|
{
|
||||||
|
return $this->belongsToMany(User::class);
|
||||||
|
}
|
||||||
|
}
|
||||||
156
app/Models/Service.php
Normal file
156
app/Models/Service.php
Normal file
|
|
@ -0,0 +1,156 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Models;
|
||||||
|
|
||||||
|
use App\Lib\Helpers\ToolboxHelper;
|
||||||
|
use App\Traits\Models\ActivityLog;
|
||||||
|
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||||
|
use Illuminate\Database\Eloquent\Model;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
use Illuminate\Support\Facades\Crypt;
|
||||||
|
use Illuminate\Support\Str;
|
||||||
|
|
||||||
|
class Service extends Model
|
||||||
|
{
|
||||||
|
use HasFactory;
|
||||||
|
use ActivityLog;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The attributes that are mass assignable.
|
||||||
|
*
|
||||||
|
* @var list<string>
|
||||||
|
*/
|
||||||
|
protected $fillable = [
|
||||||
|
'name',
|
||||||
|
'bearer_token',
|
||||||
|
'bearer_pwd',
|
||||||
|
'brand_id'
|
||||||
|
];
|
||||||
|
|
||||||
|
// * EVENTS
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Perform any actions required after the model boots.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
protected static function booted()
|
||||||
|
{
|
||||||
|
static::creating(function (self $model) {
|
||||||
|
self::assertFieldIsUnique('name', $model->name);
|
||||||
|
$model->generateBearerToken();
|
||||||
|
$model->generateBearerPassword();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// * METHODS
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Asserts using validation that the field is unique.
|
||||||
|
*
|
||||||
|
* @param string $nameField
|
||||||
|
* @param string $valueField
|
||||||
|
* @param integer|null $id
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
private static function assertFieldIsUnique(string $nameField, string $valueField, ?int $id = null)
|
||||||
|
{
|
||||||
|
$table = (new self())->getTable();
|
||||||
|
ToolboxHelper::assertFieldIsUnique($table, $nameField, $valueField, $id);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generates new bearer token
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function generateBearerToken(): string
|
||||||
|
{
|
||||||
|
$this->bearer_token = hash('ripemd160', Str::uuid());
|
||||||
|
$this->bearer_token_date = now();
|
||||||
|
return $this->bearer_token;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generates new bearer password
|
||||||
|
* It store the crypted password in model
|
||||||
|
* and returns the plain password
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function generateBearerPassword(): string
|
||||||
|
{
|
||||||
|
$pwd = hash('sha256', Str::uuid());
|
||||||
|
$this->bearer_pwd = Crypt::encryptString($pwd);
|
||||||
|
return $pwd;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Authenticate the request for this service
|
||||||
|
*
|
||||||
|
* This will check hash-mac and that the request is wellformed
|
||||||
|
*
|
||||||
|
* The request should have the minimum following parameters
|
||||||
|
* - timestamp using format Y-m-d H:i:s, and must be UTC (Greenwich Mean Time)
|
||||||
|
* - bearerToken the public token generated by the crm
|
||||||
|
* - signature the hashmac signature of the request
|
||||||
|
*
|
||||||
|
* ! Signature must be calculated with every parameter urlencode once only
|
||||||
|
* ! All parameters to calculate the signature must be sorted (natural sort asc)
|
||||||
|
*
|
||||||
|
* ! Signature should look like
|
||||||
|
* * httpVerb\n
|
||||||
|
* * uri\n
|
||||||
|
* * queryString
|
||||||
|
*
|
||||||
|
* ! httpVerb as well as uri must be lower case
|
||||||
|
* ! uri should include scheme, host and path and nothing else, if path is empty a trailing slash should be added
|
||||||
|
* ! eg: https://ldc-crm.fr/customer/1 or https://ldc-crm.fr/
|
||||||
|
* ! The query string shall be separated with '&', eg param1=value1¶m2=value2
|
||||||
|
* ! Key names and values of the query string must be url encoded using RFC 3986
|
||||||
|
* ! http://www.faqs.org/rfcs/rfc3986.html
|
||||||
|
* ! Signature must be hmac sha256 using the private key generatedby the crm
|
||||||
|
*
|
||||||
|
* * ! They should then be concat in a string separated by \n
|
||||||
|
*
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* @param \Illuminate\Http\Request $request
|
||||||
|
* @return boolean
|
||||||
|
* @throws \RuntimeException If an error occurs.
|
||||||
|
*/
|
||||||
|
public function authenticateApi(Request $request): bool
|
||||||
|
{
|
||||||
|
// ! Hack to prevent Laravel to cast empty strings to null or the opposite.
|
||||||
|
// ! If cannot decode json user legacy all() method.
|
||||||
|
$data = \json_decode($request->getContent(), true);
|
||||||
|
$data = (\is_array($data) and \json_last_error() === \JSON_ERROR_NONE) ?
|
||||||
|
array_replace_recursive($request->all(), $data) : $request->all();
|
||||||
|
|
||||||
|
$uri = $request->getUri();
|
||||||
|
if (app()->environment(['staging', 'production']) and Str::of($uri)->startsWith('http:')) {
|
||||||
|
$uri = (string)Str::of($uri)->replaceFirst('http:', 'https:');
|
||||||
|
}
|
||||||
|
$calculatedHash = ToolboxHelper::signRequest(
|
||||||
|
$this->bearer_pwd,
|
||||||
|
$uri,
|
||||||
|
$request->getMethod(),
|
||||||
|
$data
|
||||||
|
);
|
||||||
|
|
||||||
|
// ! Très important hash_equals https://fr.wikipedia.org/wiki/Attaque_temporelle
|
||||||
|
return \hash_equals($calculatedHash, $request->signature);
|
||||||
|
}
|
||||||
|
|
||||||
|
// * RELATIONSHIPS
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the brand that owns the service.
|
||||||
|
*
|
||||||
|
* @return \Illuminate\Database\Eloquent\Relations\BelongsTo<\App\Models\Brand, $this>
|
||||||
|
*/
|
||||||
|
public function brand()
|
||||||
|
{
|
||||||
|
return $this->belongsTo(Brand::class);
|
||||||
|
}
|
||||||
|
}
|
||||||
79
app/Models/Url.php
Normal file
79
app/Models/Url.php
Normal file
|
|
@ -0,0 +1,79 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Models;
|
||||||
|
|
||||||
|
use App\Traits\Models\ActivityLog;
|
||||||
|
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||||
|
use Illuminate\Database\Eloquent\Model;
|
||||||
|
use Uwil\LaravelCommon\Helpers\FileHelper;
|
||||||
|
|
||||||
|
class Url extends Model
|
||||||
|
{
|
||||||
|
use HasFactory;
|
||||||
|
use ActivityLog;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The attributes that are mass assignable.
|
||||||
|
*
|
||||||
|
* @var list<string>
|
||||||
|
*/
|
||||||
|
protected $fillable = [
|
||||||
|
'key',
|
||||||
|
'value',
|
||||||
|
'title',
|
||||||
|
'image_settings',
|
||||||
|
'logo',
|
||||||
|
'brand_id'
|
||||||
|
];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The attributes that should be cast.
|
||||||
|
*
|
||||||
|
* @var array<string, string>
|
||||||
|
*/
|
||||||
|
protected $casts = [
|
||||||
|
'image_settings' => 'array',
|
||||||
|
];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The "booted" method of the model.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
protected static function boot(): void
|
||||||
|
{
|
||||||
|
static::creating(function (self $model) {
|
||||||
|
$model->logo = FileHelper::storeFile($model, $model->logo, true);
|
||||||
|
});
|
||||||
|
static::updating(function (self $model) {
|
||||||
|
$model->logo = FileHelper::storeFile($model, $model->logo, true);
|
||||||
|
});
|
||||||
|
static::updated(function (self $model) {
|
||||||
|
FileHelper::removeOldFile($model, 'logo');
|
||||||
|
});
|
||||||
|
static::deleted(function (self $model) {
|
||||||
|
FileHelper::removeFile($model, 'logo');
|
||||||
|
});
|
||||||
|
parent::boot();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the brand that owns the url.
|
||||||
|
*
|
||||||
|
* @return \Illuminate\Database\Eloquent\Relations\BelongsTo<\App\Models\Brand, $this>
|
||||||
|
*/
|
||||||
|
public function brand()
|
||||||
|
{
|
||||||
|
return $this->belongsTo(Brand::class);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the history for the url.
|
||||||
|
*
|
||||||
|
* @return \Illuminate\Database\Eloquent\Relations\HasMany<\App\Models\UrlHistory, $this>
|
||||||
|
*/
|
||||||
|
public function history()
|
||||||
|
{
|
||||||
|
return $this->hasMany(UrlHistory::class);
|
||||||
|
}
|
||||||
|
}
|
||||||
32
app/Models/UrlHistory.php
Normal file
32
app/Models/UrlHistory.php
Normal file
|
|
@ -0,0 +1,32 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Models;
|
||||||
|
|
||||||
|
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||||
|
use Illuminate\Database\Eloquent\Model;
|
||||||
|
|
||||||
|
class UrlHistory extends Model
|
||||||
|
{
|
||||||
|
use HasFactory;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The attributes that are mass assignable.
|
||||||
|
*
|
||||||
|
* @var list<string>
|
||||||
|
*/
|
||||||
|
protected $fillable = [
|
||||||
|
'value',
|
||||||
|
'start_date',
|
||||||
|
'end_date'
|
||||||
|
];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the url that owns the stats.
|
||||||
|
*
|
||||||
|
* @return \Illuminate\Database\Eloquent\Relations\BelongsTo<\App\Models\Url, $this>
|
||||||
|
*/
|
||||||
|
public function url()
|
||||||
|
{
|
||||||
|
return $this->belongsTo(Url::class);
|
||||||
|
}
|
||||||
|
}
|
||||||
45
app/Models/UrlStats.php
Normal file
45
app/Models/UrlStats.php
Normal file
|
|
@ -0,0 +1,45 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Models;
|
||||||
|
|
||||||
|
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||||
|
use Illuminate\Database\Eloquent\Model;
|
||||||
|
|
||||||
|
class UrlStats extends Model
|
||||||
|
{
|
||||||
|
use HasFactory;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The attributes that are mass assignable.
|
||||||
|
*
|
||||||
|
* @var list<string>
|
||||||
|
*/
|
||||||
|
protected $fillable = [
|
||||||
|
'calls',
|
||||||
|
'date',
|
||||||
|
'url_key',
|
||||||
|
'url',
|
||||||
|
];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The attributes that should be cast.
|
||||||
|
*
|
||||||
|
* @var array<string, string>
|
||||||
|
*/
|
||||||
|
protected $casts = [
|
||||||
|
'date' => 'date',
|
||||||
|
];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Perform any actions required after the model boots.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
protected static function booted(): void
|
||||||
|
{
|
||||||
|
static::creating(function (self $model) {
|
||||||
|
$model->calls = 1;
|
||||||
|
$model->date = now();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
107
app/Models/User.php
Normal file
107
app/Models/User.php
Normal file
|
|
@ -0,0 +1,107 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Models;
|
||||||
|
|
||||||
|
use App\Enums\Users\RoleEnum;
|
||||||
|
use App\Traits\Models\ActivityLog;
|
||||||
|
use Illuminate\Auth\Access\AuthorizationException;
|
||||||
|
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||||
|
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
|
||||||
|
use Illuminate\Foundation\Auth\User as Authenticatable;
|
||||||
|
use Illuminate\Notifications\Notifiable;
|
||||||
|
|
||||||
|
class User extends Authenticatable
|
||||||
|
{
|
||||||
|
use HasFactory;
|
||||||
|
use Notifiable;
|
||||||
|
use ActivityLog;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The attributes that are mass assignable.
|
||||||
|
*
|
||||||
|
* @var list<string>
|
||||||
|
*/
|
||||||
|
protected $fillable = [
|
||||||
|
'firstname',
|
||||||
|
'lastname',
|
||||||
|
'email',
|
||||||
|
'role',
|
||||||
|
];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The attributes that should be hidden for serialization.
|
||||||
|
*
|
||||||
|
* @var list<string>
|
||||||
|
*/
|
||||||
|
protected $hidden = [
|
||||||
|
'remember_token',
|
||||||
|
];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The attributes that should be cast.
|
||||||
|
*
|
||||||
|
* @var array<string, string>
|
||||||
|
*/
|
||||||
|
protected $casts = [
|
||||||
|
'role' => RoleEnum::class,
|
||||||
|
'last_connection' => 'date',
|
||||||
|
];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Perform any actions required after the model boots.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
protected static function booted()
|
||||||
|
{
|
||||||
|
static::creating(function (self $model) {
|
||||||
|
self::checkElevationPrivileges($model);
|
||||||
|
});
|
||||||
|
static::updating(function (self $model) {
|
||||||
|
self::checkElevationPrivileges($model);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Prevent user elevation privileges.
|
||||||
|
*
|
||||||
|
* @param self $user
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
private static function checkElevationPrivileges(self $user): void
|
||||||
|
{
|
||||||
|
throw_if(
|
||||||
|
auth('backend')->user() and auth('backend')->user()->role->value() > $user->role->value(),
|
||||||
|
AuthorizationException::class
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get related brands or all the brands if the user is consceptor.
|
||||||
|
*
|
||||||
|
* @return \Illuminate\Database\Eloquent\Collection<int, \App\Models\Brand>|
|
||||||
|
* \Illuminate\Database\Eloquent\Relations\BelongsToMany<\App\Models\Brand,
|
||||||
|
* $this,
|
||||||
|
* \Illuminate\Database\Eloquent\Relations\Pivot>
|
||||||
|
*/
|
||||||
|
public function getRelatedBrands()
|
||||||
|
{
|
||||||
|
return $this->role->equals(RoleEnum::conceptor) ?
|
||||||
|
Brand::all() :
|
||||||
|
$this->brands;
|
||||||
|
}
|
||||||
|
|
||||||
|
// * RELATIONSHIPS
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The brands that belong to the user.
|
||||||
|
*
|
||||||
|
* @return \Illuminate\Database\Eloquent\Relations\BelongsToMany<\App\Models\Brand,
|
||||||
|
* $this,
|
||||||
|
* \Illuminate\Database\Eloquent\Relations\Pivot>
|
||||||
|
*/
|
||||||
|
public function brands(): BelongsToMany
|
||||||
|
{
|
||||||
|
return $this->belongsToMany(Brand::class);
|
||||||
|
}
|
||||||
|
}
|
||||||
54
app/Observers/ModelObserver.php
Normal file
54
app/Observers/ModelObserver.php
Normal file
|
|
@ -0,0 +1,54 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Observers;
|
||||||
|
|
||||||
|
use App\Enums\ActivityLogs\ActivityLogsEventEnum;
|
||||||
|
use App\Models\ActivityLog;
|
||||||
|
use Illuminate\Database\Eloquent\Model;
|
||||||
|
|
||||||
|
class ModelObserver
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Handle the model "created" event.
|
||||||
|
*
|
||||||
|
* @param \Illuminate\Database\Eloquent\Model $model
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public static function created(Model $model): void
|
||||||
|
{
|
||||||
|
ActivityLog::addActivity($model, ActivityLogsEventEnum::created);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle the model "updated" event.
|
||||||
|
*
|
||||||
|
* @param \Illuminate\Database\Eloquent\Model $model
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public static function updated(Model $model): void
|
||||||
|
{
|
||||||
|
ActivityLog::addActivity($model, ActivityLogsEventEnum::updated);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle the model "duplicated" event.
|
||||||
|
*
|
||||||
|
* @param \Illuminate\Database\Eloquent\Model $model
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public static function duplicated(Model $model): void
|
||||||
|
{
|
||||||
|
ActivityLog::addActivity($model, ActivityLogsEventEnum::duplicated);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle the model "deleted" event.
|
||||||
|
*
|
||||||
|
* @param \Illuminate\Database\Eloquent\Model $model
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public static function deleted(Model $model): void
|
||||||
|
{
|
||||||
|
ActivityLog::addActivity($model, ActivityLogsEventEnum::deleted);
|
||||||
|
}
|
||||||
|
}
|
||||||
65
app/Policies/BrandPolicy.php
Normal file
65
app/Policies/BrandPolicy.php
Normal file
|
|
@ -0,0 +1,65 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Policies;
|
||||||
|
|
||||||
|
use App\Enums\Users\RoleEnum;
|
||||||
|
use App\Models\User;
|
||||||
|
use App\Policies\Rules\UserStaticRules;
|
||||||
|
|
||||||
|
class BrandPolicy
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Determine whether the user can view any models.
|
||||||
|
*
|
||||||
|
* @param \App\Models\User $authUser
|
||||||
|
* @return boolean
|
||||||
|
*/
|
||||||
|
public function viewAny(User $authUser): bool
|
||||||
|
{
|
||||||
|
return UserStaticRules::atLeastRole($authUser, RoleEnum::conceptor);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determine whether the user can view the model.
|
||||||
|
*
|
||||||
|
* @param \App\Models\User $authUser
|
||||||
|
* @return boolean
|
||||||
|
*/
|
||||||
|
public function view(User $authUser): bool
|
||||||
|
{
|
||||||
|
return UserStaticRules::atLeastRole($authUser, RoleEnum::conceptor);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determine whether the user can create a new model.
|
||||||
|
*
|
||||||
|
* @param \App\Models\User $authUser
|
||||||
|
* @return boolean
|
||||||
|
*/
|
||||||
|
public function create(User $authUser): bool
|
||||||
|
{
|
||||||
|
return UserStaticRules::atLeastRole($authUser, RoleEnum::conceptor);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determine whether the user can update the model.
|
||||||
|
*
|
||||||
|
* @param \App\Models\User $authUser
|
||||||
|
* @return boolean
|
||||||
|
*/
|
||||||
|
public function update(User $authUser): bool
|
||||||
|
{
|
||||||
|
return UserStaticRules::atLeastRole($authUser, RoleEnum::conceptor);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determine whether the user can delete the model.
|
||||||
|
*
|
||||||
|
* @param \App\Models\User $authUser
|
||||||
|
* @return boolean
|
||||||
|
*/
|
||||||
|
public function delete(User $authUser): bool
|
||||||
|
{
|
||||||
|
return UserStaticRules::atLeastRole($authUser, RoleEnum::conceptor);
|
||||||
|
}
|
||||||
|
}
|
||||||
47
app/Policies/Rules/UserStaticRules.php
Normal file
47
app/Policies/Rules/UserStaticRules.php
Normal file
|
|
@ -0,0 +1,47 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Policies\Rules;
|
||||||
|
|
||||||
|
use App\Enums\Users\RoleEnum;
|
||||||
|
use Illuminate\Database\Eloquent\Model;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Userlevel Policiy Static Rules
|
||||||
|
*
|
||||||
|
* @codeCoverageIgnore
|
||||||
|
*/
|
||||||
|
abstract class UserStaticRules
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Allow everyone.
|
||||||
|
*
|
||||||
|
* @return boolean
|
||||||
|
*/
|
||||||
|
public static function everyOne(): bool
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Allow no one.
|
||||||
|
*
|
||||||
|
* @return boolean
|
||||||
|
*/
|
||||||
|
public static function noOne(): bool
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* At least a minimum role
|
||||||
|
*
|
||||||
|
* @param \Illuminate\Database\Eloquent\Model $authModel
|
||||||
|
* @param \App\Enums\Users\RoleEnum $minRole
|
||||||
|
* @return boolean
|
||||||
|
*/
|
||||||
|
public static function atLeastRole(Model $authModel, RoleEnum $minRole): bool
|
||||||
|
{
|
||||||
|
// phpcs:enable
|
||||||
|
return isset($authModel->role) && $authModel->role->value() <= $minRole->value();
|
||||||
|
}
|
||||||
|
}
|
||||||
65
app/Policies/ServicePolicy.php
Normal file
65
app/Policies/ServicePolicy.php
Normal file
|
|
@ -0,0 +1,65 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Policies;
|
||||||
|
|
||||||
|
use App\Enums\Users\RoleEnum;
|
||||||
|
use App\Models\User;
|
||||||
|
use App\Policies\Rules\UserStaticRules;
|
||||||
|
|
||||||
|
class ServicePolicy
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Determine whether the user can view any models.
|
||||||
|
*
|
||||||
|
* @param \App\Models\User $authUser
|
||||||
|
* @return boolean
|
||||||
|
*/
|
||||||
|
public function viewAny(User $authUser): bool
|
||||||
|
{
|
||||||
|
return UserStaticRules::atLeastRole($authUser, RoleEnum::conceptor);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determine whether the user can view the model.
|
||||||
|
*
|
||||||
|
* @param \App\Models\User $authUser
|
||||||
|
* @return boolean
|
||||||
|
*/
|
||||||
|
public function view(User $authUser): bool
|
||||||
|
{
|
||||||
|
return UserStaticRules::atLeastRole($authUser, RoleEnum::conceptor);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determine whether the user can create a new model.
|
||||||
|
*
|
||||||
|
* @param \App\Models\User $authUser
|
||||||
|
* @return boolean
|
||||||
|
*/
|
||||||
|
public function create(User $authUser): bool
|
||||||
|
{
|
||||||
|
return UserStaticRules::atLeastRole($authUser, RoleEnum::conceptor);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determine whether the user can update the model.
|
||||||
|
*
|
||||||
|
* @param \App\Models\User $authUser
|
||||||
|
* @return boolean
|
||||||
|
*/
|
||||||
|
public function update(User $authUser): bool
|
||||||
|
{
|
||||||
|
return UserStaticRules::atLeastRole($authUser, RoleEnum::conceptor);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determine whether the user can delete the model.
|
||||||
|
*
|
||||||
|
* @param \App\Models\User $authUser
|
||||||
|
* @return boolean
|
||||||
|
*/
|
||||||
|
public function delete(User $authUser): bool
|
||||||
|
{
|
||||||
|
return UserStaticRules::atLeastRole($authUser, RoleEnum::conceptor);
|
||||||
|
}
|
||||||
|
}
|
||||||
86
app/Policies/UrlPolicy.php
Normal file
86
app/Policies/UrlPolicy.php
Normal file
|
|
@ -0,0 +1,86 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Policies;
|
||||||
|
|
||||||
|
use App\Enums\Users\RoleEnum;
|
||||||
|
use App\Models\Url;
|
||||||
|
use App\Models\User;
|
||||||
|
use App\Policies\Rules\UserStaticRules;
|
||||||
|
|
||||||
|
class UrlPolicy
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Determine whether the user can view any models.
|
||||||
|
*
|
||||||
|
* @param \App\Models\User $authUser
|
||||||
|
* @return boolean
|
||||||
|
*/
|
||||||
|
public function viewAny(User $authUser): bool
|
||||||
|
{
|
||||||
|
return UserStaticRules::atLeastRole($authUser, RoleEnum::marketing);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determine whether the user can view the model.
|
||||||
|
*
|
||||||
|
* @param \App\Models\User $authUser
|
||||||
|
* @param \App\Models\Url $url
|
||||||
|
* @return boolean
|
||||||
|
*/
|
||||||
|
public function view(User $authUser, Url $url): bool
|
||||||
|
{
|
||||||
|
return (UserStaticRules::atLeastRole($authUser, RoleEnum::marketing) &&
|
||||||
|
\collect($authUser->brands->pluck('id'))->contains($url->brand->id)) ||
|
||||||
|
UserStaticRules::atLeastRole($authUser, RoleEnum::conceptor);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determine whether the user can create a new model.
|
||||||
|
*
|
||||||
|
* @param \App\Models\User $authUser
|
||||||
|
* @return boolean
|
||||||
|
*/
|
||||||
|
public function create(User $authUser): bool
|
||||||
|
{
|
||||||
|
return UserStaticRules::atLeastRole($authUser, RoleEnum::marketing);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determine whether the user can update the model.
|
||||||
|
*
|
||||||
|
* @param \App\Models\User $authUser
|
||||||
|
* @param \App\Models\Url $url
|
||||||
|
* @return boolean
|
||||||
|
*/
|
||||||
|
public function update(User $authUser, Url $url): bool
|
||||||
|
{
|
||||||
|
return (UserStaticRules::atLeastRole($authUser, RoleEnum::marketing) &&
|
||||||
|
\collect($authUser->brands->pluck('id'))->contains($url->brand->id)) ||
|
||||||
|
UserStaticRules::atLeastRole($authUser, RoleEnum::conceptor);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determine whether the user can duplicate the model.
|
||||||
|
*
|
||||||
|
* @param \App\Models\User $authUser
|
||||||
|
* @param \App\Models\Url $url
|
||||||
|
* @return boolean
|
||||||
|
*/
|
||||||
|
public function duplicate(User $authUser, Url $url): bool
|
||||||
|
{
|
||||||
|
return (UserStaticRules::atLeastRole($authUser, RoleEnum::marketing) &&
|
||||||
|
\collect($authUser->brands->pluck('id'))->contains($url->brand->id)) ||
|
||||||
|
UserStaticRules::atLeastRole($authUser, RoleEnum::conceptor);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determine whether the user can delete the model.
|
||||||
|
*
|
||||||
|
* @param \App\Models\User $authUser
|
||||||
|
* @return boolean
|
||||||
|
*/
|
||||||
|
public function delete(User $authUser): bool
|
||||||
|
{
|
||||||
|
return UserStaticRules::atLeastRole($authUser, RoleEnum::conceptor);
|
||||||
|
}
|
||||||
|
}
|
||||||
65
app/Policies/UrlStatsPolicy.php
Normal file
65
app/Policies/UrlStatsPolicy.php
Normal file
|
|
@ -0,0 +1,65 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Policies;
|
||||||
|
|
||||||
|
use App\Enums\Users\RoleEnum;
|
||||||
|
use App\Models\User;
|
||||||
|
use App\Policies\Rules\UserStaticRules;
|
||||||
|
|
||||||
|
class UrlStatsPolicy
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Determine whether the user can view any models.
|
||||||
|
*
|
||||||
|
* @param \App\Models\User $authUser
|
||||||
|
* @return boolean
|
||||||
|
*/
|
||||||
|
public function viewAny(User $authUser): bool
|
||||||
|
{
|
||||||
|
return UserStaticRules::atLeastRole($authUser, RoleEnum::conceptor);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determine whether the user can view the model.
|
||||||
|
*
|
||||||
|
* @param \App\Models\User $authUser
|
||||||
|
* @return boolean
|
||||||
|
*/
|
||||||
|
public function view(User $authUser): bool
|
||||||
|
{
|
||||||
|
return UserStaticRules::atLeastRole($authUser, RoleEnum::conceptor);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determine whether the user can create a new model.
|
||||||
|
*
|
||||||
|
* @param \App\Models\User $authUser
|
||||||
|
* @return boolean
|
||||||
|
*/
|
||||||
|
public function create(User $authUser): bool
|
||||||
|
{
|
||||||
|
return UserStaticRules::atLeastRole($authUser, RoleEnum::conceptor);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determine whether the user can update the model.
|
||||||
|
*
|
||||||
|
* @param \App\Models\User $authUser
|
||||||
|
* @return boolean
|
||||||
|
*/
|
||||||
|
public function update(User $authUser): bool
|
||||||
|
{
|
||||||
|
return UserStaticRules::atLeastRole($authUser, RoleEnum::conceptor);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determine whether the user can delete the model.
|
||||||
|
*
|
||||||
|
* @param \App\Models\User $authUser
|
||||||
|
* @return boolean
|
||||||
|
*/
|
||||||
|
public function delete(User $authUser): bool
|
||||||
|
{
|
||||||
|
return UserStaticRules::atLeastRole($authUser, RoleEnum::conceptor);
|
||||||
|
}
|
||||||
|
}
|
||||||
65
app/Policies/UserPolicy.php
Normal file
65
app/Policies/UserPolicy.php
Normal file
|
|
@ -0,0 +1,65 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Policies;
|
||||||
|
|
||||||
|
use App\Enums\Users\RoleEnum;
|
||||||
|
use App\Models\User;
|
||||||
|
use App\Policies\Rules\UserStaticRules;
|
||||||
|
|
||||||
|
class UserPolicy
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Determine whether the user can view any models.
|
||||||
|
*
|
||||||
|
* @param \App\Models\User $authUser
|
||||||
|
* @return boolean
|
||||||
|
*/
|
||||||
|
public function viewAny(User $authUser): bool
|
||||||
|
{
|
||||||
|
return UserStaticRules::atLeastRole($authUser, RoleEnum::conceptor);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determine whether the user can view the model.
|
||||||
|
*
|
||||||
|
* @param \App\Models\User $authUser
|
||||||
|
* @return boolean
|
||||||
|
*/
|
||||||
|
public function view(User $authUser): bool
|
||||||
|
{
|
||||||
|
return UserStaticRules::atLeastRole($authUser, RoleEnum::conceptor);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determine whether the user can create a new model.
|
||||||
|
*
|
||||||
|
* @param \App\Models\User $authUser
|
||||||
|
* @return boolean
|
||||||
|
*/
|
||||||
|
public function create(User $authUser): bool
|
||||||
|
{
|
||||||
|
return UserStaticRules::atLeastRole($authUser, RoleEnum::conceptor);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determine whether the user can update the model.
|
||||||
|
*
|
||||||
|
* @param \App\Models\User $authUser
|
||||||
|
* @return boolean
|
||||||
|
*/
|
||||||
|
public function update(User $authUser): bool
|
||||||
|
{
|
||||||
|
return UserStaticRules::atLeastRole($authUser, RoleEnum::conceptor);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determine whether the user can delete the model.
|
||||||
|
*
|
||||||
|
* @param \App\Models\User $authUser
|
||||||
|
* @return boolean
|
||||||
|
*/
|
||||||
|
public function delete(User $authUser): bool
|
||||||
|
{
|
||||||
|
return UserStaticRules::atLeastRole($authUser, RoleEnum::conceptor);
|
||||||
|
}
|
||||||
|
}
|
||||||
40
app/Providers/AppServiceProvider.php
Normal file
40
app/Providers/AppServiceProvider.php
Normal file
|
|
@ -0,0 +1,40 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Providers;
|
||||||
|
|
||||||
|
use Illuminate\Support\Carbon;
|
||||||
|
use Illuminate\Support\Facades\App;
|
||||||
|
use Illuminate\Support\Facades\URL;
|
||||||
|
use Illuminate\Support\ServiceProvider;
|
||||||
|
|
||||||
|
class AppServiceProvider extends ServiceProvider
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Bootstrap any application services.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function boot()
|
||||||
|
{
|
||||||
|
// * HTTPS FIX
|
||||||
|
URL::forceScheme('https');
|
||||||
|
|
||||||
|
// * HA PROXY FIX
|
||||||
|
App::singleton('clientIp', function () {
|
||||||
|
if (config('app.env') === 'local') {
|
||||||
|
return request()->getClientIp();
|
||||||
|
} else {
|
||||||
|
return request()->server('HTTP_X_FORWARDED_FOR') ??
|
||||||
|
request()->getClientIp();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// * LOCAL FORCE eg: for Carbon
|
||||||
|
setlocale(LC_ALL, \sprintf(
|
||||||
|
'%s_%s.UTF-8',
|
||||||
|
config('app.locale'),
|
||||||
|
\strtoupper(config('app.locale'))
|
||||||
|
));
|
||||||
|
Carbon::setLocale(config('app.locale'));
|
||||||
|
}
|
||||||
|
}
|
||||||
47
app/Providers/AuthServiceProvider.php
Normal file
47
app/Providers/AuthServiceProvider.php
Normal file
|
|
@ -0,0 +1,47 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Providers;
|
||||||
|
|
||||||
|
use App\Enums\Users\RoleEnum;
|
||||||
|
use App\Models\Service;
|
||||||
|
use Illuminate\Database\Eloquent\Model;
|
||||||
|
use Illuminate\Database\Eloquent\ModelNotFoundException;
|
||||||
|
use Illuminate\Foundation\Support\Providers\AuthServiceProvider as ServiceProvider;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
use Illuminate\Support\Facades\Auth;
|
||||||
|
use Illuminate\Support\Facades\Gate;
|
||||||
|
|
||||||
|
class AuthServiceProvider extends ServiceProvider
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Register any authentication / authorization services.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function boot()
|
||||||
|
{
|
||||||
|
$this->registerPolicies();
|
||||||
|
|
||||||
|
Gate::define('isConceptor', function (Model $authModel) {
|
||||||
|
return isset($authModel->role) && $authModel->role === RoleEnum::conceptor;
|
||||||
|
});
|
||||||
|
|
||||||
|
Auth::viaRequest('api-hmac', function (Request $request) {
|
||||||
|
$service = null;
|
||||||
|
|
||||||
|
try {
|
||||||
|
// * On récupère le service actuel correspondant au token fourni.
|
||||||
|
$service = Service::query()->where('bearer_token', $request->input('bearer_token'))->firstOrFail();
|
||||||
|
} catch (ModelNotFoundException $e) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// * On authenticate la request en vérifiant sa signature.
|
||||||
|
if ($service->authenticateApi($request)) {
|
||||||
|
return $service;
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
78
app/Providers/EventServiceProvider.php
Normal file
78
app/Providers/EventServiceProvider.php
Normal file
|
|
@ -0,0 +1,78 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Providers;
|
||||||
|
|
||||||
|
use App\Models\User;
|
||||||
|
use Illuminate\Auth\Events\Registered;
|
||||||
|
use Illuminate\Auth\Listeners\SendEmailVerificationNotification;
|
||||||
|
use Illuminate\Foundation\Support\Providers\EventServiceProvider as ServiceProvider;
|
||||||
|
use Illuminate\Support\Facades\Auth;
|
||||||
|
use Illuminate\Support\Facades\Cache;
|
||||||
|
use Illuminate\Support\Facades\Event;
|
||||||
|
use Illuminate\Support\Facades\Session;
|
||||||
|
use Slides\Saml2\Events\SignedIn;
|
||||||
|
|
||||||
|
class EventServiceProvider extends ServiceProvider
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* The event listener mappings for the application.
|
||||||
|
*
|
||||||
|
* @var array<class-string, array<int, class-string>>
|
||||||
|
*/
|
||||||
|
protected $listen = [
|
||||||
|
Registered::class => [
|
||||||
|
SendEmailVerificationNotification::class,
|
||||||
|
],
|
||||||
|
];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determine if events and listeners should be automatically discovered.
|
||||||
|
*
|
||||||
|
* @return boolean
|
||||||
|
*/
|
||||||
|
public function shouldDiscoverEvents(): bool
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Register any events for your application.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function boot()
|
||||||
|
{
|
||||||
|
Event::listen('Slides\Saml2\Events\SignedIn', function (SignedIn $event) {
|
||||||
|
$messageId = $event->getAuth()->getLastMessageId();
|
||||||
|
|
||||||
|
if (Cache::get(config('cache.prefix') . "_saml_message_id_" . $messageId)) {
|
||||||
|
logger()->error(
|
||||||
|
'SAML MESSAGE ID HANDLER',
|
||||||
|
['error' => 'Message ID : ' . $messageId . " replayed !"]
|
||||||
|
);
|
||||||
|
abort(403);
|
||||||
|
} else {
|
||||||
|
// Cache for 8h.
|
||||||
|
Cache::put(config('cache.prefix') . "_saml_message_id_" . $messageId, $messageId, 28800);
|
||||||
|
}
|
||||||
|
|
||||||
|
$user = $event->getSaml2User();
|
||||||
|
$email = $user->getNameId();
|
||||||
|
$lUser = User::query()->where('email', $email)->first();
|
||||||
|
|
||||||
|
if (!$lUser) {
|
||||||
|
abort(403);
|
||||||
|
}
|
||||||
|
|
||||||
|
Auth::guard('backend')->login($lUser);
|
||||||
|
session(['sessionIndex' => $user->getSessionIndex()]);
|
||||||
|
$lUser->last_connection = now();
|
||||||
|
$lUser->saveQuietly();
|
||||||
|
});
|
||||||
|
|
||||||
|
Event::listen('Slides\Saml2\Events\SignedOut', function () {
|
||||||
|
Auth::guard('backend')->logout();
|
||||||
|
Session::save();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
104
app/Providers/RouteServiceProvider.php
Normal file
104
app/Providers/RouteServiceProvider.php
Normal file
|
|
@ -0,0 +1,104 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Providers;
|
||||||
|
|
||||||
|
use App\Models\Service;
|
||||||
|
use Illuminate\Cache\RateLimiting\Limit;
|
||||||
|
use Illuminate\Foundation\Support\Providers\RouteServiceProvider as ServiceProvider;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
use Illuminate\Support\Facades\RateLimiter;
|
||||||
|
use Illuminate\Support\Facades\Route;
|
||||||
|
|
||||||
|
class RouteServiceProvider extends ServiceProvider
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* The path to the "home" route for your application.
|
||||||
|
*
|
||||||
|
* This is used by Laravel authentication to redirect users after login.
|
||||||
|
*
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
public const HOME = '/';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Define your route model bindings, pattern filters, etc.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function boot()
|
||||||
|
{
|
||||||
|
$this->configureRateLimiting();
|
||||||
|
|
||||||
|
$this->cspReportRoute();
|
||||||
|
|
||||||
|
$this->routes(function () {
|
||||||
|
Route::middleware('web')
|
||||||
|
->namespace($this->namespace)
|
||||||
|
->group(base_path('routes/web-bo.php'));
|
||||||
|
|
||||||
|
Route::middleware('web')
|
||||||
|
->namespace($this->namespace)
|
||||||
|
->group(base_path('routes/web-fo.php'));
|
||||||
|
|
||||||
|
Route::middleware('api')
|
||||||
|
->namespace($this->namespace)
|
||||||
|
->group(base_path('routes/api.php'));
|
||||||
|
});
|
||||||
|
|
||||||
|
Route::model('service', Service::class);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Configure the rate limiters for the application.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
protected function configureRateLimiting()
|
||||||
|
{
|
||||||
|
RateLimiter::for('api', function (Request $request) {
|
||||||
|
return Limit::perMinute(150)->by(optional($request->user())->id ?: app('clientIp'));
|
||||||
|
});
|
||||||
|
RateLimiter::for('web', function (Request $request) {
|
||||||
|
return Limit::perMinute(150)->by(optional($request->user())->id ?: app('clientIp'));
|
||||||
|
});
|
||||||
|
RateLimiter::for('credentials', function (Request $request) {
|
||||||
|
return Limit::perMinute(30)->by(optional($request->user())->id ?: $request->ip());
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* * Register CSP report URI
|
||||||
|
*
|
||||||
|
* Put this function inside RouteServiceProvider,
|
||||||
|
* and call it in RouteServiceProvider::boot
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
private function cspReportRoute()
|
||||||
|
{
|
||||||
|
// * CSP REPORT URI, Please add /csp-report to VerifyCsrfToken $except middleware
|
||||||
|
\Illuminate\Support\Facades\Route::post('/csp-report', function () {
|
||||||
|
$isCspRequest = request()->header('Content-Type') === 'application/csp-report';
|
||||||
|
if (!$isCspRequest or !request()->json('csp-report') or !is_array(request()->json('csp-report'))) {
|
||||||
|
abort(401);
|
||||||
|
}
|
||||||
|
// * Flatten if needed
|
||||||
|
$cspReport = collect(request()->json('csp-report'))
|
||||||
|
->flatMap(function ($v, $k) {
|
||||||
|
return [$k => is_array($v) ? collect($v)->flatten()->implode(', ') : $v];
|
||||||
|
})->all();
|
||||||
|
// * Filter array keys
|
||||||
|
$cspReport = collect(array_intersect_key($cspReport, [
|
||||||
|
'blocked-uri' => '', 'document-uri' => '', 'original-policy' => '',
|
||||||
|
'referrer' => '', 'violated-directive' => ''
|
||||||
|
]));
|
||||||
|
\Illuminate\Support\Facades\Log::warning(sprintf(
|
||||||
|
"CSP REPORT for %s : \n %s",
|
||||||
|
$cspReport->get('document-uri'),
|
||||||
|
$cspReport->implode("\n")
|
||||||
|
));
|
||||||
|
})->middleware('throttle:5,2')
|
||||||
|
->withoutMiddleware(\App\Http\Middleware\VerifyCsrfToken::class)
|
||||||
|
->name('cspReportUri');
|
||||||
|
}
|
||||||
|
}
|
||||||
78
app/Providers/TranslationServiceProvider.php
Normal file
78
app/Providers/TranslationServiceProvider.php
Normal file
|
|
@ -0,0 +1,78 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Providers;
|
||||||
|
|
||||||
|
use Illuminate\Support\Collection;
|
||||||
|
use Illuminate\Support\Facades\File;
|
||||||
|
use Illuminate\Support\Facades\Cache;
|
||||||
|
use Illuminate\Support\ServiceProvider;
|
||||||
|
use Symfony\Component\Finder\SplFileInfo;
|
||||||
|
|
||||||
|
class TranslationServiceProvider extends ServiceProvider
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Bootstrap the application services.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function boot()
|
||||||
|
{
|
||||||
|
foreach ($this->collectLocalesStrings() as $locale) {
|
||||||
|
// * Suported locales
|
||||||
|
Cache::rememberForever(sprintf('translations.%s', $locale), function () use ($locale) {
|
||||||
|
$translations = [
|
||||||
|
'php' => $this->phpTranslations($locale),
|
||||||
|
'json' => $this->jsonTranslations($locale),
|
||||||
|
];
|
||||||
|
return $translations;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gather provided langs checking from files
|
||||||
|
*
|
||||||
|
* @return Collection
|
||||||
|
*/
|
||||||
|
private function collectLocalesStrings(): Collection
|
||||||
|
{
|
||||||
|
return collect(File::allFiles(resource_path('lang/')))->flatMap(function (SplFileInfo $file) {
|
||||||
|
if ($file->getRelativePath() and \strpos($file->getRelativePath(), 'vendor') !== 0) {
|
||||||
|
return [$file->getRelativePath() => ''];
|
||||||
|
}
|
||||||
|
})->keys();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gather all php translations
|
||||||
|
*
|
||||||
|
* @param string $locale
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
private function phpTranslations(string $locale): array
|
||||||
|
{
|
||||||
|
$path = resource_path("lang/$locale");
|
||||||
|
|
||||||
|
return collect(File::allFiles($path))->flatMap(function ($file) use ($locale) {
|
||||||
|
$translation = $file->getBasename('.php');
|
||||||
|
return [$translation => trans($translation, [], $locale)];
|
||||||
|
})->all();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gather json translations
|
||||||
|
*
|
||||||
|
* @param string $locale
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
private function jsonTranslations(string $locale): array
|
||||||
|
{
|
||||||
|
$path = resource_path("lang/$locale.json");
|
||||||
|
|
||||||
|
if (!File::exists($path) or !File::isReadable($path)) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
return json_decode(File::get($path), true) ?? [];
|
||||||
|
}
|
||||||
|
}
|
||||||
30
app/Providers/ViewServiceProvider.php
Normal file
30
app/Providers/ViewServiceProvider.php
Normal file
|
|
@ -0,0 +1,30 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Providers;
|
||||||
|
|
||||||
|
use Illuminate\Pagination\Paginator;
|
||||||
|
use Illuminate\Support\ServiceProvider;
|
||||||
|
|
||||||
|
class ViewServiceProvider extends ServiceProvider
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Bootstrap any application services.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function boot()
|
||||||
|
{
|
||||||
|
if (!app()->runningInConsole()) {
|
||||||
|
// * FORCE BOOTSTRAP PAGINATOR (or custom if in front)
|
||||||
|
// Early boot, request wont be filled as expected.
|
||||||
|
switch (collect(explode('/', \request()->getPathInfo()))->get(1)) {
|
||||||
|
case 'bo':
|
||||||
|
Paginator::defaultView('bo.modules.pagination');
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
Paginator::useBootstrapFive();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
} //end if
|
||||||
|
}
|
||||||
|
}
|
||||||
0
app/Traits/Controllers/.gitkeep
Normal file
0
app/Traits/Controllers/.gitkeep
Normal file
0
app/Traits/Models/.gitkeep
Normal file
0
app/Traits/Models/.gitkeep
Normal file
36
app/Traits/Models/ActivityLog.php
Normal file
36
app/Traits/Models/ActivityLog.php
Normal file
|
|
@ -0,0 +1,36 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Traits\Models;
|
||||||
|
|
||||||
|
use App\Observers\ModelObserver;
|
||||||
|
use App\Models\ActivityLog as ActivityLogModel;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If there is a static function in a trait (named boot[TraitName]),
|
||||||
|
* it will be executed as the boot() function would on an Eloquent model.
|
||||||
|
* Futhermore, event in the Model observer doesn't erase actual events in the model,
|
||||||
|
* but adds them.
|
||||||
|
*/
|
||||||
|
trait ActivityLog
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public static function bootActivityLog(): void
|
||||||
|
{
|
||||||
|
static::observe(ModelObserver::class);
|
||||||
|
}
|
||||||
|
|
||||||
|
// * RELATIONS
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Activities logs HasMany relationship.
|
||||||
|
*
|
||||||
|
* @return \Illuminate\Database\Eloquent\Relations\HasMany
|
||||||
|
*/
|
||||||
|
public function activityLogs(): \Illuminate\Database\Eloquent\Relations\HasMany
|
||||||
|
{
|
||||||
|
return $this->hasMany(ActivityLogModel::class, 'model_id')->with('user');
|
||||||
|
}
|
||||||
|
}
|
||||||
0
app/Traits/Requests/.gitkeep
Normal file
0
app/Traits/Requests/.gitkeep
Normal file
175
app/Traits/Requests/HasPictureWithSvgSupport.php
Normal file
175
app/Traits/Requests/HasPictureWithSvgSupport.php
Normal file
|
|
@ -0,0 +1,175 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Traits\Requests;
|
||||||
|
|
||||||
|
use Illuminate\Support\Facades\Storage;
|
||||||
|
use Illuminate\Support\Facades\File;
|
||||||
|
|
||||||
|
|
||||||
|
trait HasPictureWithSvgSupport
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Minimum width
|
||||||
|
*
|
||||||
|
* @var integer
|
||||||
|
*/
|
||||||
|
private $minWidth = 400;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Maximum width
|
||||||
|
*
|
||||||
|
* @var integer
|
||||||
|
*/
|
||||||
|
private $maxWidth = 1800;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Minimum height
|
||||||
|
*
|
||||||
|
* @var integer
|
||||||
|
*/
|
||||||
|
private $minHeight = 300;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Maximum height
|
||||||
|
*
|
||||||
|
* @var integer
|
||||||
|
*/
|
||||||
|
private $maxHeight = 1800;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Image rules
|
||||||
|
*
|
||||||
|
* @param string $name
|
||||||
|
* @param integer|null $minWidth
|
||||||
|
* @param integer|null $minHeight
|
||||||
|
* @param integer|null $maxWidth
|
||||||
|
* @param integer|null $maxHeight
|
||||||
|
* @param boolean $nullable
|
||||||
|
* @param integer|null $size
|
||||||
|
* @param string|null $requiredWith
|
||||||
|
* @param string|null $requiredWithout
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
public function pictureRulesWithSvgSupport(
|
||||||
|
string $name = 'picture',
|
||||||
|
?int $minWidth = null,
|
||||||
|
?int $minHeight = null,
|
||||||
|
?int $maxWidth = null,
|
||||||
|
?int $maxHeight = null,
|
||||||
|
bool $nullable = false,
|
||||||
|
?int $size = null,
|
||||||
|
?string $requiredWith = null,
|
||||||
|
?string $requiredWithout = null,
|
||||||
|
): array {
|
||||||
|
$dimensions = (object)[
|
||||||
|
'minWidth' => $minWidth ?? $this->minWidth,
|
||||||
|
'minHeight' => $minHeight ?? $this->minHeight,
|
||||||
|
'maxWidth' => $maxWidth ?? $this->maxWidth,
|
||||||
|
'maxHeight' => $maxHeight ?? $this->maxHeight
|
||||||
|
];
|
||||||
|
return [
|
||||||
|
$name => [
|
||||||
|
!$nullable ? 'required' : 'nullable',
|
||||||
|
(strlen($requiredWith)) ? 'required_with:' . $requiredWith : '',
|
||||||
|
(strlen($requiredWithout)) ? 'required_without:' . $requiredWithout : '',
|
||||||
|
($size) ? 'max:' . $size : '',
|
||||||
|
function (string $attribute, $value, callable $fail) use ($dimensions) {
|
||||||
|
self::validatePicture($attribute, $value, $fail, $dimensions);
|
||||||
|
}
|
||||||
|
]
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validate picture
|
||||||
|
*
|
||||||
|
* @param string $attribute
|
||||||
|
* @param string|null $value
|
||||||
|
* @param callable $fail
|
||||||
|
* @param object $sizes
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public static function validatePicture(string $attribute, string|null $value, callable $fail, object $sizes): void
|
||||||
|
{
|
||||||
|
if (!($useStorage = Storage::exists($value)) && !File::exists($value)) {
|
||||||
|
$fail(trans(':attribute n\'existe pas dans le système de fichier', [
|
||||||
|
'attribute' => $attribute
|
||||||
|
]));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
$fileFullPath = self::getUploadedFileFullPath($value, $useStorage);
|
||||||
|
if (
|
||||||
|
!\in_array(self::getUploadedFileMimeType($value, $useStorage), [
|
||||||
|
'image/jpeg',
|
||||||
|
'image/jpg',
|
||||||
|
'image/png',
|
||||||
|
'image/gif',
|
||||||
|
'image/svg+xml'
|
||||||
|
])
|
||||||
|
) {
|
||||||
|
$fail(trans(':attribute doit être une image de type \'image/jpeg,image/jpg,image/png,image/gif\',image/svg\'', [
|
||||||
|
'attribute' => $attribute
|
||||||
|
]));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (self::getUploadedFileMimeType($value, $useStorage) !== "image/svg+xml") {
|
||||||
|
if (!($dimensions = \getimagesize($fileFullPath))) {
|
||||||
|
$fail(trans(':attribute impossible de valider les dimensions', [
|
||||||
|
'attribute' => $attribute
|
||||||
|
]));
|
||||||
|
} else {
|
||||||
|
$width = intval($dimensions[0]);
|
||||||
|
$height = intval($dimensions[1]);
|
||||||
|
|
||||||
|
$xMessage = trans(
|
||||||
|
":attribute la largeur doit être comprise entre {$sizes->minWidth}px et {$sizes->maxWidth}px"
|
||||||
|
);
|
||||||
|
if ($sizes->minWidth === $sizes->maxWidth) {
|
||||||
|
$xMessage = trans(":attribute la largeur doit être de {$sizes->minWidth}px");
|
||||||
|
}
|
||||||
|
$yMessage = trans(
|
||||||
|
":attribute la hauteur doit être comprise entre {$sizes->minHeight}px et {$sizes->maxHeight}px"
|
||||||
|
);
|
||||||
|
if ($sizes->minHeight === $sizes->maxHeight) {
|
||||||
|
$yMessage = trans(":attribute la hauteur doit être de {$sizes->minHeight}px");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (($width < $sizes->minWidth) || ($width > $sizes->maxWidth)) {
|
||||||
|
$fail($xMessage);
|
||||||
|
}
|
||||||
|
if (($height < $sizes->minHeight) || ($height > $sizes->maxHeight)) {
|
||||||
|
$fail($yMessage);
|
||||||
|
}
|
||||||
|
} //end if
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the file full path
|
||||||
|
*
|
||||||
|
* @param string $value
|
||||||
|
* @param boolean $useStorage
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
private static function getUploadedFileFullPath(string $value, bool $useStorage): string
|
||||||
|
{
|
||||||
|
/** @var \Illuminate\Filesystem\FilesystemAdapter */
|
||||||
|
$disk = Storage::disk();
|
||||||
|
return !$useStorage ? (realpath($value) ?: $value) : $disk->path($value);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the file mimetype
|
||||||
|
*
|
||||||
|
* @param string $value
|
||||||
|
* @param boolean $useStorage
|
||||||
|
* @return string|false
|
||||||
|
*/
|
||||||
|
private static function getUploadedFileMimeType(string $value, bool $useStorage)
|
||||||
|
{
|
||||||
|
return !$useStorage ? File::mimeType($value) : Storage::mimeType($value);
|
||||||
|
}
|
||||||
|
}
|
||||||
53
artisan
Executable file
53
artisan
Executable file
|
|
@ -0,0 +1,53 @@
|
||||||
|
#!/usr/bin/env php
|
||||||
|
<?php
|
||||||
|
|
||||||
|
define('LARAVEL_START', microtime(true));
|
||||||
|
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| Register The Auto Loader
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| Composer provides a convenient, automatically generated class loader
|
||||||
|
| for our application. We just need to utilize it! We'll require it
|
||||||
|
| into the script here so that we do not have to worry about the
|
||||||
|
| loading of any of our classes manually. It's great to relax.
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
require __DIR__.'/vendor/autoload.php';
|
||||||
|
|
||||||
|
$app = require_once __DIR__.'/bootstrap/app.php';
|
||||||
|
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| Run The Artisan Application
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| When we run the console application, the current CLI command will be
|
||||||
|
| executed in this console and the response sent back to a terminal
|
||||||
|
| or another output device for the developers. Here goes nothing!
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
$kernel = $app->make(Illuminate\Contracts\Console\Kernel::class);
|
||||||
|
|
||||||
|
$status = $kernel->handle(
|
||||||
|
$input = new Symfony\Component\Console\Input\ArgvInput,
|
||||||
|
new Symfony\Component\Console\Output\ConsoleOutput
|
||||||
|
);
|
||||||
|
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| Shutdown The Application
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| Once Artisan has finished running, we will fire off the shutdown events
|
||||||
|
| so that any final work may be done by the application before we shut
|
||||||
|
| down the process. This is the last thing to happen to the request.
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
$kernel->terminate($input, $status);
|
||||||
|
|
||||||
|
exit($status);
|
||||||
55
bootstrap/app.php
Normal file
55
bootstrap/app.php
Normal file
|
|
@ -0,0 +1,55 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| Create The Application
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| The first thing we will do is create a new Laravel application instance
|
||||||
|
| which serves as the "glue" for all the components of Laravel, and is
|
||||||
|
| the IoC container for the system binding all of the various parts.
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
$app = new Illuminate\Foundation\Application(
|
||||||
|
$_ENV['APP_BASE_PATH'] ?? dirname(__DIR__)
|
||||||
|
);
|
||||||
|
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| Bind Important Interfaces
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| Next, we need to bind some important interfaces into the container so
|
||||||
|
| we will be able to resolve them when needed. The kernels serve the
|
||||||
|
| incoming requests to this application from both the web and CLI.
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
$app->singleton(
|
||||||
|
Illuminate\Contracts\Http\Kernel::class,
|
||||||
|
App\Http\Kernel::class
|
||||||
|
);
|
||||||
|
|
||||||
|
$app->singleton(
|
||||||
|
Illuminate\Contracts\Console\Kernel::class,
|
||||||
|
App\Console\Kernel::class
|
||||||
|
);
|
||||||
|
|
||||||
|
$app->singleton(
|
||||||
|
Illuminate\Contracts\Debug\ExceptionHandler::class,
|
||||||
|
App\Exceptions\Handler::class
|
||||||
|
);
|
||||||
|
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| Return The Application
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| This script returns the application instance. The instance is given to
|
||||||
|
| the calling script so we can separate the building of the instances
|
||||||
|
| from the actual running of the application and sending responses.
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
return $app;
|
||||||
2
bootstrap/cache/.gitignore
vendored
Normal file
2
bootstrap/cache/.gitignore
vendored
Normal file
|
|
@ -0,0 +1,2 @@
|
||||||
|
*
|
||||||
|
!.gitignore
|
||||||
1
commitlint.config.cjs
Normal file
1
commitlint.config.cjs
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
module.exports = require('@uwil/husky-config/commitlint.config.cjs');
|
||||||
85
composer.json
Normal file
85
composer.json
Normal file
|
|
@ -0,0 +1,85 @@
|
||||||
|
{
|
||||||
|
"name": "uwil/qrcollector-back",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"type": "project",
|
||||||
|
"description": "Administration interface for QRCollector",
|
||||||
|
"license": "MIT",
|
||||||
|
"require": {
|
||||||
|
"php": "^8.5",
|
||||||
|
"24slides/laravel-saml2": "^2.4",
|
||||||
|
"diglactic/laravel-breadcrumbs": "^10.0",
|
||||||
|
"guzzlehttp/guzzle": "^7.5.0",
|
||||||
|
"icehouse-ventures/laravel-chartjs": "^4.2",
|
||||||
|
"kwaadpepper/image-resizer": "^3.0",
|
||||||
|
"laravel/framework": "^12.0",
|
||||||
|
"laravel/tinker": "^2.5",
|
||||||
|
"laravel/ui": "^4.0",
|
||||||
|
"mews/purifier": "^3.4",
|
||||||
|
"mistic100/randomcolor": "^1.0",
|
||||||
|
"sentry/sentry-laravel": "^4.20",
|
||||||
|
"uwil/admin-secure-laravel": "^5.0",
|
||||||
|
"uwil/k8s-laravel": "^2.0",
|
||||||
|
"uwil/laravel-common": "^4.1"
|
||||||
|
},
|
||||||
|
"require-dev": {
|
||||||
|
"fakerphp/faker": "^1.9.1",
|
||||||
|
"larastan/larastan": "^3.9",
|
||||||
|
"laravel/sail": "^1.18",
|
||||||
|
"mockery/mockery": "^1.4.4",
|
||||||
|
"nunomaduro/collision": "^8.1",
|
||||||
|
"phpunit/phpunit": "^11.0",
|
||||||
|
"slevomat/coding-standard": "^8.10"
|
||||||
|
},
|
||||||
|
"autoload": {
|
||||||
|
"psr-4": {
|
||||||
|
"App\\": "app/",
|
||||||
|
"Database\\Factories\\": "database/factories/",
|
||||||
|
"Database\\Seeders\\": "database/seeders/"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"autoload-dev": {
|
||||||
|
"psr-4": {
|
||||||
|
"Tests\\": "tests/"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"scripts": {
|
||||||
|
"post-autoload-dump": [
|
||||||
|
"Illuminate\\Foundation\\ComposerScripts::postAutoloadDump",
|
||||||
|
"@php artisan package:discover --ansi"
|
||||||
|
],
|
||||||
|
"post-update-cmd": [
|
||||||
|
"@php artisan vendor:publish --tag=laravel-assets --ansi --force"
|
||||||
|
],
|
||||||
|
"post-root-package-install": [
|
||||||
|
"@php -r \"file_exists('.env') || copy('.env.example', '.env');\""
|
||||||
|
],
|
||||||
|
"post-create-project-cmd": [
|
||||||
|
"@php artisan key:generate --ansi"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"extra": {
|
||||||
|
"laravel": {
|
||||||
|
"dont-discover": []
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"optimize-autoloader": true,
|
||||||
|
"preferred-install": "dist",
|
||||||
|
"sort-packages": true,
|
||||||
|
"allow-plugins": {
|
||||||
|
"dealerdirect/phpcodesniffer-composer-installer": true,
|
||||||
|
"php-http/discovery": true
|
||||||
|
},
|
||||||
|
"gitlab-domains": [
|
||||||
|
"lab.s-web.fr"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"minimum-stability": "dev",
|
||||||
|
"prefer-stable": true,
|
||||||
|
"repositories": {
|
||||||
|
"lab.s-web.fr": {
|
||||||
|
"type": "composer",
|
||||||
|
"url": "https://lab.s-web.fr/api/v4/group/23/-/packages/composer/packages.json"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Reference in a new issue