Installing Laravel.
A simple application skeleton.
This commit is contained in:
parent
bb2b68507b
commit
e8e9e50162
9
.env.example
Normal file
9
.env.example
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
DOCKER_APP_PORT=8080
|
||||||
|
DOCKER_CAPTCHA_PORT=8081
|
||||||
|
DOCKER_DB_PORT=3306
|
||||||
|
MYSQL_ROOT_PASSWORD=root_pass
|
||||||
|
DB_DATABASE=my-projetcs
|
||||||
|
DB_USERNAME=my-projetcs
|
||||||
|
DB_PASSWORD=my-projetcs_pass
|
||||||
|
UID=1000
|
||||||
|
GID=1000
|
6
app/docker/.dockerignore
Normal file
6
app/docker/.dockerignore
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
**/*.env
|
||||||
|
**/*.env.example
|
||||||
|
app/bootstrap/cache/*
|
||||||
|
app/storage/**
|
||||||
|
app/vendor/**
|
||||||
|
app/node_modules/**
|
117
app/docker/Dockerfile
Normal file
117
app/docker/Dockerfile
Normal file
@ -0,0 +1,117 @@
|
|||||||
|
FROM docker.io/php:8.3-zts-alpine3.18 AS UNIT_BUILDER
|
||||||
|
|
||||||
|
ARG UNIT_VERSION=1.31.1
|
||||||
|
|
||||||
|
RUN apk --no-cache add pcre2-dev gcc git musl-dev make && \
|
||||||
|
mkdir -p /usr/lib/unit/modules && \
|
||||||
|
git clone https://github.com/nginx/unit.git && \
|
||||||
|
cd unit && \
|
||||||
|
git checkout $UNIT_VERSION && \
|
||||||
|
./configure --prefix=/var --statedir=/var/lib/unit --runstatedir=/var/run --control=unix:/run/unit/control.unit.sock --log=/var/log/unit.log --user=www-data --group=www-data --tmpdir=/tmp --modulesdir=/var/lib/unit/modules && \
|
||||||
|
./configure php && \
|
||||||
|
make && \
|
||||||
|
make install
|
||||||
|
|
||||||
|
FROM docker.io/php:8.3-zts-alpine3.18 as BUILD
|
||||||
|
|
||||||
|
COPY --from=UNIT_BUILDER /var/sbin/unitd /usr/sbin/unitd
|
||||||
|
COPY --from=UNIT_BUILDER /var/lib/unit/ /var/lib/unit/
|
||||||
|
|
||||||
|
COPY docker/unit-config.json /docker-entrypoint.d/config.json
|
||||||
|
|
||||||
|
RUN apk --no-cache add pcre2 libbz2 libpng libwebp libjpeg-turbo icu-libs freetype oniguruma libzip \
|
||||||
|
&& apk add --no-cache --virtual .phpize-deps icu-dev libpng-dev bzip2-dev libwebp-dev libjpeg-turbo-dev freetype-dev oniguruma-dev libzip-dev pcre2-dev ${PHPIZE_DEPS} \
|
||||||
|
&& docker-php-ext-configure intl --enable-intl && \
|
||||||
|
docker-php-ext-configure bcmath --enable-bcmath && \
|
||||||
|
docker-php-ext-configure gd --with-freetype --with-jpeg --with-webp && \
|
||||||
|
docker-php-ext-install -j$(nproc) gd && \
|
||||||
|
docker-php-ext-install bcmath &&\
|
||||||
|
docker-php-ext-install pdo \
|
||||||
|
mysqli pdo_mysql \
|
||||||
|
intl mbstring \
|
||||||
|
zip pcntl \
|
||||||
|
exif opcache bz2 \
|
||||||
|
calendar \
|
||||||
|
&& pear update-channels && pecl update-channels \
|
||||||
|
&& pecl install redis && docker-php-ext-enable redis \
|
||||||
|
&& rm -rf /tmp/pear \
|
||||||
|
&& docker-php-source delete \
|
||||||
|
&& apk del .phpize-deps \
|
||||||
|
&& rm -rf /var/cache/apk/* && rm -rf /etc/apk/cache \
|
||||||
|
&& rm -rf /usr/share/php && rm -rf /tmp/* \
|
||||||
|
&& rm "$PHP_INI_DIR/php.ini-development" \
|
||||||
|
&& mv "$PHP_INI_DIR/php.ini-production" "$PHP_INI_DIR/php.ini" \
|
||||||
|
&& mkdir -p /tmp/php/upload \
|
||||||
|
&& mkdir -p /tmp/php/sys \
|
||||||
|
&& mkdir -p /tmp/php/session \
|
||||||
|
&& chown -R www-data:www-data /tmp/php \
|
||||||
|
&& ln -sf /dev/stdout /var/log/unit.log \
|
||||||
|
&& addgroup -S unit && adduser -S unit -G unit
|
||||||
|
|
||||||
|
FROM BUILD as APP_BUILD_FOR_PRODUCTION
|
||||||
|
WORKDIR /home/app
|
||||||
|
|
||||||
|
COPY src /home/app
|
||||||
|
|
||||||
|
RUN apk --no-cache add git nodejs npm \
|
||||||
|
&& curl -sS https://getcomposer.org/installer | php -- --install-dir=/usr/local/bin --filename=composer \
|
||||||
|
&& composer install --optimize-autoloader --no-dev \
|
||||||
|
&& npm install && npm run build \
|
||||||
|
&& rm -rf /home/app/node_modules /home/app/.env
|
||||||
|
|
||||||
|
|
||||||
|
FROM BUILD AS PRODUCTION
|
||||||
|
|
||||||
|
COPY --from=APP_BUILD_FOR_PRODUCTION /home/app /var/www/html
|
||||||
|
COPY docker/docker-entrypoint_prod.sh /home/unit/docker-entrypoint.sh
|
||||||
|
|
||||||
|
WORKDIR /var/www/html
|
||||||
|
|
||||||
|
RUN chmod 755 /home/unit/docker-entrypoint.sh
|
||||||
|
|
||||||
|
STOPSIGNAL SIGTERM
|
||||||
|
|
||||||
|
ENTRYPOINT ["/home/unit/docker-entrypoint.sh"]
|
||||||
|
EXPOSE 9000
|
||||||
|
CMD ["unitd", "--no-daemon", "--control", "unix:/var/run/control.unit.sock", "--user", "unit", "--group", "unit"]
|
||||||
|
|
||||||
|
|
||||||
|
FROM BUILD AS DEVELOP
|
||||||
|
|
||||||
|
WORKDIR /var/www/html
|
||||||
|
|
||||||
|
COPY docker/docker-entrypoint_dev.sh /home/unit/docker-entrypoint.sh
|
||||||
|
|
||||||
|
STOPSIGNAL SIGTERM
|
||||||
|
|
||||||
|
RUN chmod 755 /home/unit/docker-entrypoint.sh \
|
||||||
|
&& apk --no-cache add git nodejs npm \
|
||||||
|
&& curl -sS https://getcomposer.org/installer | php -- --install-dir=/usr/local/bin --filename=composer
|
||||||
|
|
||||||
|
ENTRYPOINT ["/home/unit/docker-entrypoint.sh"]
|
||||||
|
|
||||||
|
EXPOSE 9000
|
||||||
|
CMD ["unitd", "--no-daemon", "--control", "unix:/var/run/control.unit.sock", "--user", "unit", "--group", "unit"]
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
FROM BUILD AS ARTISAN
|
||||||
|
WORKDIR /var/www/html
|
||||||
|
STOPSIGNAL SIGTERM
|
||||||
|
ENTRYPOINT ["php", "/var/www/html/artisan"]
|
||||||
|
|
||||||
|
|
||||||
|
FROM BUILD AS COMPOSER
|
||||||
|
WORKDIR /var/www/html
|
||||||
|
STOPSIGNAL SIGTERM
|
||||||
|
RUN apk --no-cache add git \
|
||||||
|
&& curl -sS https://getcomposer.org/installer | php -- --install-dir=/usr/local/bin --filename=composer
|
||||||
|
ENTRYPOINT ["composer"]
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
FROM BUILD AS NPM
|
||||||
|
WORKDIR /var/www/html
|
||||||
|
STOPSIGNAL SIGTERM
|
||||||
|
RUN apk --no-cache add nodejs npm
|
||||||
|
ENTRYPOINT ["npm"]
|
103
app/docker/docker-entrypoint_dev.sh
Normal file
103
app/docker/docker-entrypoint_dev.sh
Normal file
@ -0,0 +1,103 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
WAITLOOPS=5
|
||||||
|
SLEEPSEC=1
|
||||||
|
unitd="unitd"
|
||||||
|
|
||||||
|
curl_put()
|
||||||
|
{
|
||||||
|
RET=$(/usr/bin/curl -s -w '%{http_code}' -X PUT --data-binary @$1 --unix-socket /var/run/control.unit.sock http://localhost/$2)
|
||||||
|
RET_BODY=$(echo $RET | /bin/sed '$ s/...$//')
|
||||||
|
RET_STATUS=$(echo $RET | /usr/bin/tail -c 4)
|
||||||
|
if [ "$RET_STATUS" -ne "200" ]; then
|
||||||
|
echo "$0: Error: HTTP response status code is '$RET_STATUS'"
|
||||||
|
echo "$RET_BODY"
|
||||||
|
return 1
|
||||||
|
else
|
||||||
|
echo "$0: OK: HTTP response status code is '$RET_STATUS'"
|
||||||
|
echo "$RET_BODY"
|
||||||
|
fi
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
if [ "$unitd" = "unitd" ] || [ "$unitd" = "unitd-debug" ]; then
|
||||||
|
echo "$0: Launching Unit daemon to perform initial configuration..."
|
||||||
|
/usr/sbin/$unitd --control unix:/var/run/control.unit.sock
|
||||||
|
for i in $(/usr/bin/seq $WAITLOOPS); do
|
||||||
|
if [ ! -S /var/run/control.unit.sock ]; then
|
||||||
|
echo "$0: Waiting for control socket to be created..."
|
||||||
|
/bin/sleep $SLEEPSEC
|
||||||
|
else
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
# even when the control socket exists, it does not mean unit has finished initialisation
|
||||||
|
# this curl call will get a reply once unit is fully launched
|
||||||
|
/usr/bin/curl -s -X GET --unix-socket /var/run/control.unit.sock http://localhost/
|
||||||
|
|
||||||
|
if /usr/bin/find "/docker-entrypoint.d/" -mindepth 1 -print -quit 2>/dev/null | /bin/grep -q .; then
|
||||||
|
echo "$0: /docker-entrypoint.d/ is not empty, applying initial configuration..."
|
||||||
|
|
||||||
|
echo "$0: Looking for certificate bundles in /docker-entrypoint.d/..."
|
||||||
|
for f in $(/usr/bin/find /docker-entrypoint.d/ -type f -name "*.pem"); do
|
||||||
|
echo "$0: Uploading certificates bundle: $f"
|
||||||
|
curl_put $f "certificates/$(basename $f .pem)"
|
||||||
|
done
|
||||||
|
|
||||||
|
echo "$0: Looking for JavaScript modules in /docker-entrypoint.d/..."
|
||||||
|
for f in $(/usr/bin/find /docker-entrypoint.d/ -type f -name "*.js"); do
|
||||||
|
echo "$0: Uploading JavaScript module: $f"
|
||||||
|
curl_put $f "js_modules/$(basename $f .js)"
|
||||||
|
done
|
||||||
|
|
||||||
|
echo "$0: Looking for configuration snippets in /docker-entrypoint.d/..."
|
||||||
|
for f in $(/usr/bin/find /docker-entrypoint.d/ -type f -name "*.json"); do
|
||||||
|
echo "$0: Applying configuration $f";
|
||||||
|
curl_put $f "config"
|
||||||
|
done
|
||||||
|
|
||||||
|
if [ ! -z ${UNIT_SOURCE+x} ]
|
||||||
|
then
|
||||||
|
echo "[${UNIT_SOURCE}]" > /docker-entrypoint.d/unit_source.json
|
||||||
|
curl_put "/docker-entrypoint.d/unit_source.json" "config/listeners/*:9000/forwarded/source"
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "$0: Looking for shell scripts in /docker-entrypoint.d/..."
|
||||||
|
for f in $(/usr/bin/find /docker-entrypoint.d/ -type f -name "*.sh"); do
|
||||||
|
echo "$0: Launching $f";
|
||||||
|
"$f"
|
||||||
|
done
|
||||||
|
|
||||||
|
# warn on filetypes we don't know what to do with
|
||||||
|
for f in $(/usr/bin/find /docker-entrypoint.d/ -type f -not -name "*.sh" -not -name "*.json" -not -name "*.pem" -not -name "*.js"); do
|
||||||
|
echo "$0: Ignoring $f";
|
||||||
|
done
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "$0: Stopping Unit daemon after initial configuration..."
|
||||||
|
kill -TERM $(/bin/cat /var/run/unit.pid)
|
||||||
|
|
||||||
|
for i in $(/usr/bin/seq $WAITLOOPS); do
|
||||||
|
if [ -S /var/run/control.unit.sock ]; then
|
||||||
|
echo "$0: Waiting for control socket to be removed..."
|
||||||
|
/bin/sleep $SLEEPSEC
|
||||||
|
else
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
if [ -S /var/run/control.unit.sock ]; then
|
||||||
|
kill -KILL $(/bin/cat /var/run/unit.pid)
|
||||||
|
rm -f /var/run/control.unit.sock
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo
|
||||||
|
echo "$0: Unit initial configuration complete; ready for start up..."
|
||||||
|
echo
|
||||||
|
fi
|
||||||
|
|
||||||
|
chmod -R 777 /var/www/html/storage /var/www/html/bootstrap/cache
|
||||||
|
|
||||||
|
exec "$@"
|
111
app/docker/docker-entrypoint_prod.sh
Normal file
111
app/docker/docker-entrypoint_prod.sh
Normal file
@ -0,0 +1,111 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
WAITLOOPS=5
|
||||||
|
SLEEPSEC=1
|
||||||
|
unitd="unitd"
|
||||||
|
|
||||||
|
curl_put()
|
||||||
|
{
|
||||||
|
RET=$(/usr/bin/curl -s -w '%{http_code}' -X PUT --data-binary @$1 --unix-socket /var/run/control.unit.sock http://localhost/$2)
|
||||||
|
RET_BODY=$(echo $RET | /bin/sed '$ s/...$//')
|
||||||
|
RET_STATUS=$(echo $RET | /usr/bin/tail -c 4)
|
||||||
|
if [ "$RET_STATUS" -ne "200" ]; then
|
||||||
|
echo "$0: Error: HTTP response status code is '$RET_STATUS'"
|
||||||
|
echo "$RET_BODY"
|
||||||
|
return 1
|
||||||
|
else
|
||||||
|
echo "$0: OK: HTTP response status code is '$RET_STATUS'"
|
||||||
|
echo "$RET_BODY"
|
||||||
|
fi
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
if [ "$unitd" = "unitd" ] || [ "$unitd" = "unitd-debug" ]; then
|
||||||
|
echo "$0: Launching Unit daemon to perform initial configuration..."
|
||||||
|
/usr/sbin/$unitd --control unix:/var/run/control.unit.sock
|
||||||
|
for i in $(/usr/bin/seq $WAITLOOPS); do
|
||||||
|
if [ ! -S /var/run/control.unit.sock ]; then
|
||||||
|
echo "$0: Waiting for control socket to be created..."
|
||||||
|
/bin/sleep $SLEEPSEC
|
||||||
|
else
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
# even when the control socket exists, it does not mean unit has finished initialisation
|
||||||
|
# this curl call will get a reply once unit is fully launched
|
||||||
|
/usr/bin/curl -s -X GET --unix-socket /var/run/control.unit.sock http://localhost/
|
||||||
|
|
||||||
|
if /usr/bin/find "/docker-entrypoint.d/" -mindepth 1 -print -quit 2>/dev/null | /bin/grep -q .; then
|
||||||
|
echo "$0: /docker-entrypoint.d/ is not empty, applying initial configuration..."
|
||||||
|
|
||||||
|
echo "$0: Looking for certificate bundles in /docker-entrypoint.d/..."
|
||||||
|
for f in $(/usr/bin/find /docker-entrypoint.d/ -type f -name "*.pem"); do
|
||||||
|
echo "$0: Uploading certificates bundle: $f"
|
||||||
|
curl_put $f "certificates/$(basename $f .pem)"
|
||||||
|
done
|
||||||
|
|
||||||
|
echo "$0: Looking for JavaScript modules in /docker-entrypoint.d/..."
|
||||||
|
for f in $(/usr/bin/find /docker-entrypoint.d/ -type f -name "*.js"); do
|
||||||
|
echo "$0: Uploading JavaScript module: $f"
|
||||||
|
curl_put $f "js_modules/$(basename $f .js)"
|
||||||
|
done
|
||||||
|
|
||||||
|
echo "$0: Looking for configuration snippets in /docker-entrypoint.d/..."
|
||||||
|
for f in $(/usr/bin/find /docker-entrypoint.d/ -type f -name "*.json"); do
|
||||||
|
echo "$0: Applying configuration $f";
|
||||||
|
curl_put $f "config"
|
||||||
|
done
|
||||||
|
|
||||||
|
if [ ! -z ${UNIT_SOURCE+x} ]
|
||||||
|
then
|
||||||
|
echo "[${UNIT_SOURCE}]" > /docker-entrypoint.d/unit_source.json
|
||||||
|
curl_put "/docker-entrypoint.d/unit_source.json" "config/listeners/*:9000/forwarded/source"
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "$0: Looking for shell scripts in /docker-entrypoint.d/..."
|
||||||
|
for f in $(/usr/bin/find /docker-entrypoint.d/ -type f -name "*.sh"); do
|
||||||
|
echo "$0: Launching $f";
|
||||||
|
"$f"
|
||||||
|
done
|
||||||
|
|
||||||
|
# warn on filetypes we don't know what to do with
|
||||||
|
for f in $(/usr/bin/find /docker-entrypoint.d/ -type f -not -name "*.sh" -not -name "*.json" -not -name "*.pem" -not -name "*.js"); do
|
||||||
|
echo "$0: Ignoring $f";
|
||||||
|
done
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "$0: Stopping Unit daemon after initial configuration..."
|
||||||
|
kill -TERM $(/bin/cat /var/run/unit.pid)
|
||||||
|
|
||||||
|
for i in $(/usr/bin/seq $WAITLOOPS); do
|
||||||
|
if [ -S /var/run/control.unit.sock ]; then
|
||||||
|
echo "$0: Waiting for control socket to be removed..."
|
||||||
|
/bin/sleep $SLEEPSEC
|
||||||
|
else
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
if [ -S /var/run/control.unit.sock ]; then
|
||||||
|
kill -KILL $(/bin/cat /var/run/unit.pid)
|
||||||
|
rm -f /var/run/control.unit.sock
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo
|
||||||
|
echo "$0: Unit initial configuration complete; ready for start up..."
|
||||||
|
echo
|
||||||
|
fi
|
||||||
|
|
||||||
|
php artisan config:cache
|
||||||
|
php artisan event:cache
|
||||||
|
php artisan route:cache
|
||||||
|
php artisan view:cache
|
||||||
|
php artisan migrate --force
|
||||||
|
|
||||||
|
chown -R unit:unit /var/www/html
|
||||||
|
chown -R www-data:www-data /var/www/html/storage /var/www/html/bootstrap/cache
|
||||||
|
chmod -R 777 /var/www/html/storage /var/www/html/bootstrap/cache
|
||||||
|
|
||||||
|
exec "$@"
|
69
app/docker/unit-config.json
Normal file
69
app/docker/unit-config.json
Normal file
@ -0,0 +1,69 @@
|
|||||||
|
{
|
||||||
|
"listeners": {
|
||||||
|
"*:9000": {
|
||||||
|
"pass": "routes",
|
||||||
|
"forwarded": {
|
||||||
|
"client_ip": "X-Forwarded-For",
|
||||||
|
"recursive": false,
|
||||||
|
"source": [
|
||||||
|
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
"routes": [
|
||||||
|
{
|
||||||
|
"match": {
|
||||||
|
"uri": [
|
||||||
|
"/index.php/",
|
||||||
|
"~^/index\\.php/.*",
|
||||||
|
"~\\.php$"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"action": {
|
||||||
|
"return": 404
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"action": {
|
||||||
|
"share": "/var/www/html/public$uri",
|
||||||
|
"fallback": {
|
||||||
|
"pass": "applications/laravel"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
|
||||||
|
"applications": {
|
||||||
|
"laravel": {
|
||||||
|
"type": "php",
|
||||||
|
"root": "/var/www/html/public",
|
||||||
|
"working_directory": "/var/www/html",
|
||||||
|
"user": "www-data",
|
||||||
|
"group": "www-data",
|
||||||
|
"script": "index.php",
|
||||||
|
"processes": {
|
||||||
|
"max": 10,
|
||||||
|
"spare": 5,
|
||||||
|
"idle_timeout": 20
|
||||||
|
},
|
||||||
|
"options": {
|
||||||
|
"file": "/usr/local/etc/php/php.ini",
|
||||||
|
"admin": {
|
||||||
|
"upload_tmp_dir": "/tmp/php/upload",
|
||||||
|
"sys_temp_dir": "/tmp/php/sys",
|
||||||
|
"session.save_path": "/tmp/php/session",
|
||||||
|
"open_basedir": "/var/www/html:/tmp/php:.",
|
||||||
|
"memory_limit": "256M",
|
||||||
|
"upload_max_filesize": "20M",
|
||||||
|
"post_max_size": "20M",
|
||||||
|
"expose_php": "0"
|
||||||
|
},
|
||||||
|
"user": {
|
||||||
|
"display_errors": "0"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
18
app/src/.editorconfig
Normal file
18
app/src/.editorconfig
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
root = true
|
||||||
|
|
||||||
|
[*]
|
||||||
|
charset = utf-8
|
||||||
|
end_of_line = lf
|
||||||
|
indent_size = 4
|
||||||
|
indent_style = space
|
||||||
|
insert_final_newline = true
|
||||||
|
trim_trailing_whitespace = true
|
||||||
|
|
||||||
|
[*.md]
|
||||||
|
trim_trailing_whitespace = false
|
||||||
|
|
||||||
|
[*.{yml,yaml}]
|
||||||
|
indent_size = 2
|
||||||
|
|
||||||
|
[docker-compose.yml]
|
||||||
|
indent_size = 4
|
69
app/src/.env.example
Normal file
69
app/src/.env.example
Normal file
@ -0,0 +1,69 @@
|
|||||||
|
APP_NAME=Laravel
|
||||||
|
APP_ENV=local
|
||||||
|
APP_KEY=
|
||||||
|
APP_DEBUG=true
|
||||||
|
APP_TIMEZONE=UTC
|
||||||
|
APP_URL=http://localhost
|
||||||
|
|
||||||
|
APP_FORCE_HTTPS=false
|
||||||
|
|
||||||
|
APP_DEFAULT_LOCALE=ru
|
||||||
|
APP_FAKER_LOCALE=ru_RU
|
||||||
|
APP_DEFAULT_USER_TIMEZONE=UTC
|
||||||
|
|
||||||
|
APP_MAINTENANCE_DRIVER=file
|
||||||
|
APP_MAINTENANCE_STORE=database
|
||||||
|
|
||||||
|
#LOGIN_MAX_REQUEST=50
|
||||||
|
#LOGIN_MAX_EMAIL_REQUEST=10
|
||||||
|
|
||||||
|
BCRYPT_ROUNDS=12
|
||||||
|
|
||||||
|
LOG_CHANNEL=stack
|
||||||
|
LOG_STACK=single
|
||||||
|
LOG_DEPRECATIONS_CHANNEL=null
|
||||||
|
LOG_LEVEL=debug
|
||||||
|
|
||||||
|
DB_CONNECTION=mysql
|
||||||
|
DB_HOST=127.0.0.1
|
||||||
|
DB_PORT=3306
|
||||||
|
DB_DATABASE=laravel
|
||||||
|
DB_USERNAME=root
|
||||||
|
DB_PASSWORD=
|
||||||
|
|
||||||
|
SESSION_DRIVER=redis
|
||||||
|
SESSION_LIFETIME=120
|
||||||
|
SESSION_ENCRYPT=true
|
||||||
|
SESSION_PATH=/
|
||||||
|
SESSION_DOMAIN=null
|
||||||
|
|
||||||
|
BROADCAST_CONNECTION=log
|
||||||
|
FILESYSTEM_DISK=local
|
||||||
|
QUEUE_CONNECTION=redis
|
||||||
|
|
||||||
|
CACHE_STORE=redis
|
||||||
|
CACHE_PREFIX=
|
||||||
|
|
||||||
|
MEMCACHED_HOST=127.0.0.1
|
||||||
|
|
||||||
|
REDIS_CLIENT=phpredis
|
||||||
|
REDIS_HOST=app-redis
|
||||||
|
REDIS_PASSWORD=null
|
||||||
|
REDIS_PORT=6379
|
||||||
|
|
||||||
|
MAIL_MAILER=log
|
||||||
|
MAIL_HOST=127.0.0.1
|
||||||
|
MAIL_PORT=2525
|
||||||
|
MAIL_USERNAME=null
|
||||||
|
MAIL_PASSWORD=null
|
||||||
|
MAIL_ENCRYPTION=null
|
||||||
|
MAIL_FROM_ADDRESS="hello@example.com"
|
||||||
|
MAIL_FROM_NAME="${APP_NAME}"
|
||||||
|
|
||||||
|
AWS_ACCESS_KEY_ID=
|
||||||
|
AWS_SECRET_ACCESS_KEY=
|
||||||
|
AWS_DEFAULT_REGION=us-east-1
|
||||||
|
AWS_BUCKET=
|
||||||
|
AWS_USE_PATH_STYLE_ENDPOINT=false
|
||||||
|
|
||||||
|
VITE_APP_NAME="${APP_NAME}"
|
11
app/src/.gitattributes
vendored
Normal file
11
app/src/.gitattributes
vendored
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
* text=auto eol=lf
|
||||||
|
|
||||||
|
*.blade.php diff=html
|
||||||
|
*.css diff=css
|
||||||
|
*.html diff=html
|
||||||
|
*.md diff=markdown
|
||||||
|
*.php diff=php
|
||||||
|
|
||||||
|
/.github export-ignore
|
||||||
|
CHANGELOG.md export-ignore
|
||||||
|
.styleci.yml export-ignore
|
19
app/src/.gitignore
vendored
Normal file
19
app/src/.gitignore
vendored
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
/.phpunit.cache
|
||||||
|
/node_modules
|
||||||
|
/public/build
|
||||||
|
/public/hot
|
||||||
|
/public/storage
|
||||||
|
/storage/*.key
|
||||||
|
/vendor
|
||||||
|
.env
|
||||||
|
.env.backup
|
||||||
|
.env.production
|
||||||
|
.phpunit.result.cache
|
||||||
|
Homestead.json
|
||||||
|
Homestead.yaml
|
||||||
|
auth.json
|
||||||
|
npm-debug.log
|
||||||
|
yarn-error.log
|
||||||
|
/.fleet
|
||||||
|
/.idea
|
||||||
|
/.vscode
|
66
app/src/README.md
Normal file
66
app/src/README.md
Normal file
@ -0,0 +1,66 @@
|
|||||||
|
<p align="center"><a href="https://laravel.com" target="_blank"><img src="https://raw.githubusercontent.com/laravel/art/master/logo-lockup/5%20SVG/2%20CMYK/1%20Full%20Color/laravel-logolockup-cmyk-red.svg" width="400" alt="Laravel Logo"></a></p>
|
||||||
|
|
||||||
|
<p align="center">
|
||||||
|
<a href="https://github.com/laravel/framework/actions"><img src="https://github.com/laravel/framework/workflows/tests/badge.svg" alt="Build Status"></a>
|
||||||
|
<a href="https://packagist.org/packages/laravel/framework"><img src="https://img.shields.io/packagist/dt/laravel/framework" alt="Total Downloads"></a>
|
||||||
|
<a href="https://packagist.org/packages/laravel/framework"><img src="https://img.shields.io/packagist/v/laravel/framework" alt="Latest Stable Version"></a>
|
||||||
|
<a href="https://packagist.org/packages/laravel/framework"><img src="https://img.shields.io/packagist/l/laravel/framework" alt="License"></a>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
## About Laravel
|
||||||
|
|
||||||
|
Laravel is a web application framework with expressive, elegant syntax. We believe development must be an enjoyable and creative experience to be truly fulfilling. Laravel takes the pain out of development by easing common tasks used in many web projects, such as:
|
||||||
|
|
||||||
|
- [Simple, fast routing engine](https://laravel.com/docs/routing).
|
||||||
|
- [Powerful dependency injection container](https://laravel.com/docs/container).
|
||||||
|
- Multiple back-ends for [session](https://laravel.com/docs/session) and [cache](https://laravel.com/docs/cache) storage.
|
||||||
|
- Expressive, intuitive [database ORM](https://laravel.com/docs/eloquent).
|
||||||
|
- Database agnostic [schema migrations](https://laravel.com/docs/migrations).
|
||||||
|
- [Robust background job processing](https://laravel.com/docs/queues).
|
||||||
|
- [Real-time event broadcasting](https://laravel.com/docs/broadcasting).
|
||||||
|
|
||||||
|
Laravel is accessible, powerful, and provides tools required for large, robust applications.
|
||||||
|
|
||||||
|
## Learning Laravel
|
||||||
|
|
||||||
|
Laravel has the most extensive and thorough [documentation](https://laravel.com/docs) and video tutorial library of all modern web application frameworks, making it a breeze to get started with the framework.
|
||||||
|
|
||||||
|
You may also try the [Laravel Bootcamp](https://bootcamp.laravel.com), where you will be guided through building a modern Laravel application from scratch.
|
||||||
|
|
||||||
|
If you don't feel like reading, [Laracasts](https://laracasts.com) can help. Laracasts contains thousands of video tutorials on a range of topics including Laravel, modern PHP, unit testing, and JavaScript. Boost your skills by digging into our comprehensive video library.
|
||||||
|
|
||||||
|
## Laravel Sponsors
|
||||||
|
|
||||||
|
We would like to extend our thanks to the following sponsors for funding Laravel development. If you are interested in becoming a sponsor, please visit the [Laravel Partners program](https://partners.laravel.com).
|
||||||
|
|
||||||
|
### Premium Partners
|
||||||
|
|
||||||
|
- **[Vehikl](https://vehikl.com/)**
|
||||||
|
- **[Tighten Co.](https://tighten.co)**
|
||||||
|
- **[WebReinvent](https://webreinvent.com/)**
|
||||||
|
- **[Kirschbaum Development Group](https://kirschbaumdevelopment.com)**
|
||||||
|
- **[64 Robots](https://64robots.com)**
|
||||||
|
- **[Curotec](https://www.curotec.com/services/technologies/laravel/)**
|
||||||
|
- **[Cyber-Duck](https://cyber-duck.co.uk)**
|
||||||
|
- **[DevSquad](https://devsquad.com/hire-laravel-developers)**
|
||||||
|
- **[Jump24](https://jump24.co.uk)**
|
||||||
|
- **[Redberry](https://redberry.international/laravel/)**
|
||||||
|
- **[Active Logic](https://activelogic.com)**
|
||||||
|
- **[byte5](https://byte5.de)**
|
||||||
|
- **[OP.GG](https://op.gg)**
|
||||||
|
|
||||||
|
## Contributing
|
||||||
|
|
||||||
|
Thank you for considering contributing to the Laravel framework! The contribution guide can be found in the [Laravel documentation](https://laravel.com/docs/contributions).
|
||||||
|
|
||||||
|
## Code of Conduct
|
||||||
|
|
||||||
|
In order to ensure that the Laravel community is welcoming to all, please review and abide by the [Code of Conduct](https://laravel.com/docs/contributions#code-of-conduct).
|
||||||
|
|
||||||
|
## Security Vulnerabilities
|
||||||
|
|
||||||
|
If you discover a security vulnerability within Laravel, please send an e-mail to Taylor Otwell via [taylor@laravel.com](mailto:taylor@laravel.com). All security vulnerabilities will be promptly addressed.
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
The Laravel framework is open-sourced software licensed under the [MIT license](https://opensource.org/licenses/MIT).
|
94
app/src/app/Console/Commands/CreateUserAdmin.php
Normal file
94
app/src/app/Console/Commands/CreateUserAdmin.php
Normal file
@ -0,0 +1,94 @@
|
|||||||
|
<?php declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace App\Console\Commands;
|
||||||
|
|
||||||
|
use App\Dto\User\ManyRoleDto;
|
||||||
|
use App\Enums\SystemRole;
|
||||||
|
use App\Repositories\RoleRepository;
|
||||||
|
use App\Services\User\UserCommandHandler;
|
||||||
|
use Illuminate\Console\Command;
|
||||||
|
use Illuminate\Support\Facades\DB;
|
||||||
|
use Illuminate\Support\Facades\Validator as ValidatorFacade;
|
||||||
|
use Illuminate\Validation\Rules\Password;
|
||||||
|
use Illuminate\Validation\Validator;
|
||||||
|
|
||||||
|
final class CreateUserAdmin extends Command
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* The name and signature of the console command.
|
||||||
|
*
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
protected $signature = 'app:create-user-admin {email} {password}';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The console command description.
|
||||||
|
*
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
protected $description = 'Create admin user.';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Execute the console command.
|
||||||
|
*/
|
||||||
|
public function handle(UserCommandHandler $userCommandHandler, RoleRepository $roleRepository): void
|
||||||
|
{
|
||||||
|
$validator = $this->getData();
|
||||||
|
|
||||||
|
if ($validator->fails()) {
|
||||||
|
$this->errorMessageAndStop($validator->errors()->all());
|
||||||
|
}
|
||||||
|
$data = $validator->valid();
|
||||||
|
|
||||||
|
try {
|
||||||
|
$role = $roleRepository->getRoleByCode(SystemRole::Admin->value);
|
||||||
|
if (is_null($role)) {
|
||||||
|
$this->errorMessageAndStop('Administrator role not found.');
|
||||||
|
}
|
||||||
|
$user = DB::transaction(function () use($data, $userCommandHandler, $role) {
|
||||||
|
$data['name'] = 'Administrator';
|
||||||
|
$user = $userCommandHandler->handleStore($data, $data['password']);
|
||||||
|
$userCommandHandler->handleConfirmationByEmail($user);
|
||||||
|
$roles = new ManyRoleDto([$role->id]);
|
||||||
|
$userCommandHandler->handleSyncRoles($user, $roles);
|
||||||
|
|
||||||
|
return $user;
|
||||||
|
});
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
$this->errorMessageAndStop($e->getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->info('The command was successful!');
|
||||||
|
}
|
||||||
|
|
||||||
|
private function getData(): Validator
|
||||||
|
{
|
||||||
|
return ValidatorFacade::make([
|
||||||
|
'email' => $this->argument('email'),
|
||||||
|
'password' => $this->argument('password'),
|
||||||
|
], [
|
||||||
|
'email' => ['required', 'email', 'unique:users,email'],
|
||||||
|
'password' => ['required', Password::default()],
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function stop(): never
|
||||||
|
{
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function errorMessageAndStop(string | array $error): never
|
||||||
|
{
|
||||||
|
$this->info('User not created. See error messages below:');
|
||||||
|
|
||||||
|
if (is_array($error)) {
|
||||||
|
foreach ($error as $err) {
|
||||||
|
$this->error($err);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
$this->error($error);
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->stop();
|
||||||
|
}
|
||||||
|
}
|
10
app/src/app/Contracts/FormRequestDto.php
Normal file
10
app/src/app/Contracts/FormRequestDto.php
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
<?php declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace App\Contracts;
|
||||||
|
|
||||||
|
use App\Dto\Service\Dto;
|
||||||
|
|
||||||
|
interface FormRequestDto
|
||||||
|
{
|
||||||
|
public function getDto(): Dto;
|
||||||
|
}
|
18
app/src/app/Contracts/Search.php
Normal file
18
app/src/app/Contracts/Search.php
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
<?php declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace App\Contracts;
|
||||||
|
|
||||||
|
use Illuminate\Database\Eloquent\Builder;
|
||||||
|
use Illuminate\Database\Eloquent\Relations\Relation;
|
||||||
|
use Illuminate\Pagination\CursorPaginator;
|
||||||
|
use Illuminate\Pagination\LengthAwarePaginator;
|
||||||
|
use Illuminate\Support\Collection;
|
||||||
|
|
||||||
|
interface Search
|
||||||
|
{
|
||||||
|
public function __construct(Relation | Builder $query);
|
||||||
|
public function all(): Collection;
|
||||||
|
public function get(int $limit): Collection;
|
||||||
|
public function pagination(int $limit, int $page = 1): LengthAwarePaginator;
|
||||||
|
public function cursorPaginate(int $limit): CursorPaginator;
|
||||||
|
}
|
10
app/src/app/Contracts/ServiceResult.php
Normal file
10
app/src/app/Contracts/ServiceResult.php
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
<?php
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace App\Contracts;
|
||||||
|
|
||||||
|
interface ServiceResult
|
||||||
|
{
|
||||||
|
public function isSuccess(): bool;
|
||||||
|
public function isError(): bool;
|
||||||
|
}
|
12
app/src/app/Contracts/ServiceResultError.php
Normal file
12
app/src/app/Contracts/ServiceResultError.php
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
<?php declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace App\Contracts;
|
||||||
|
|
||||||
|
interface ServiceResultError
|
||||||
|
{
|
||||||
|
public function getCode(): ?int;
|
||||||
|
public function getMessage(): string;
|
||||||
|
public function getErrors(): array;
|
||||||
|
public function getErrorsOrMessage(): array|string;
|
||||||
|
public function getData(): array;
|
||||||
|
}
|
10
app/src/app/Dto/Builder/Role.php
Normal file
10
app/src/app/Dto/Builder/Role.php
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
<?php declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace App\Dto\Builder;
|
||||||
|
|
||||||
|
final readonly class Role
|
||||||
|
{
|
||||||
|
public function __construct(
|
||||||
|
|
||||||
|
) { }
|
||||||
|
}
|
10
app/src/app/Dto/Builder/User.php
Normal file
10
app/src/app/Dto/Builder/User.php
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
<?php declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace App\Dto\Builder;
|
||||||
|
|
||||||
|
final readonly class User
|
||||||
|
{
|
||||||
|
public function __construct(
|
||||||
|
|
||||||
|
) { }
|
||||||
|
}
|
27
app/src/app/Dto/QuerySettingsDto.php
Normal file
27
app/src/app/Dto/QuerySettingsDto.php
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
<?php declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace App\Dto;
|
||||||
|
|
||||||
|
final readonly class QuerySettingsDto
|
||||||
|
{
|
||||||
|
public function __construct(
|
||||||
|
private int $limit,
|
||||||
|
private int $page = 1,
|
||||||
|
private array $queryWith = []
|
||||||
|
) { }
|
||||||
|
|
||||||
|
public function getLimit(): int
|
||||||
|
{
|
||||||
|
return $this->limit;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getPage(): int
|
||||||
|
{
|
||||||
|
return $this->page;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getQueryWith(): array
|
||||||
|
{
|
||||||
|
return $this->queryWith;
|
||||||
|
}
|
||||||
|
}
|
24
app/src/app/Dto/Service/Admin/Role/Index.php
Normal file
24
app/src/app/Dto/Service/Admin/Role/Index.php
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
<?php declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace App\Dto\Service\Admin\Role;
|
||||||
|
|
||||||
|
use App\Dto\Builder\Role;
|
||||||
|
use App\Dto\Service\Dto;
|
||||||
|
|
||||||
|
final readonly class Index extends Dto
|
||||||
|
{
|
||||||
|
public function __construct(
|
||||||
|
private Role $roleBuilderDto,
|
||||||
|
private int $page
|
||||||
|
) { }
|
||||||
|
|
||||||
|
public function getRoleBuilderDto(): Role
|
||||||
|
{
|
||||||
|
return $this->roleBuilderDto;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getPage(): int
|
||||||
|
{
|
||||||
|
return $this->page;
|
||||||
|
}
|
||||||
|
}
|
29
app/src/app/Dto/Service/Admin/Role/StoreUpdate.php
Normal file
29
app/src/app/Dto/Service/Admin/Role/StoreUpdate.php
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
<?php declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace App\Dto\Service\Admin\Role;
|
||||||
|
|
||||||
|
use App\Dto\Service\Dto;
|
||||||
|
|
||||||
|
final readonly class StoreUpdate extends Dto
|
||||||
|
{
|
||||||
|
public function __construct(
|
||||||
|
private string $name,
|
||||||
|
private ?string $code,
|
||||||
|
private array $permissions,
|
||||||
|
) { }
|
||||||
|
|
||||||
|
public function getName(): string
|
||||||
|
{
|
||||||
|
return $this->name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getCode(): ?string
|
||||||
|
{
|
||||||
|
return $this->code;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getPermissions(): array
|
||||||
|
{
|
||||||
|
return $this->permissions;
|
||||||
|
}
|
||||||
|
}
|
24
app/src/app/Dto/Service/Admin/User/Index.php
Normal file
24
app/src/app/Dto/Service/Admin/User/Index.php
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
<?php declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace App\Dto\Service\Admin\User;
|
||||||
|
|
||||||
|
use App\Dto\Builder\User;
|
||||||
|
use App\Dto\Service\Dto;
|
||||||
|
|
||||||
|
final readonly class Index extends Dto
|
||||||
|
{
|
||||||
|
public function __construct(
|
||||||
|
private User $userBuilderDto,
|
||||||
|
private int $page
|
||||||
|
) { }
|
||||||
|
|
||||||
|
public function getUserBuilderDto(): User
|
||||||
|
{
|
||||||
|
return $this->userBuilderDto;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getPage(): int
|
||||||
|
{
|
||||||
|
return $this->page;
|
||||||
|
}
|
||||||
|
}
|
36
app/src/app/Dto/Service/Admin/User/StoreUpdate.php
Normal file
36
app/src/app/Dto/Service/Admin/User/StoreUpdate.php
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
<?php declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace App\Dto\Service\Admin\User;
|
||||||
|
|
||||||
|
use App\Dto\Service\Dto;
|
||||||
|
use App\Dto\User\ManyRoleDto;
|
||||||
|
|
||||||
|
final readonly class StoreUpdate extends Dto
|
||||||
|
{
|
||||||
|
public function __construct(
|
||||||
|
private string $name,
|
||||||
|
private string $email,
|
||||||
|
private ManyRoleDto $roles,
|
||||||
|
private ?string $password = null
|
||||||
|
) { }
|
||||||
|
|
||||||
|
public function getName(): string
|
||||||
|
{
|
||||||
|
return $this->name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getEmail(): string
|
||||||
|
{
|
||||||
|
return $this->email;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getPassword(): ?string
|
||||||
|
{
|
||||||
|
return $this->password;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getRoles(): ManyRoleDto
|
||||||
|
{
|
||||||
|
return $this->roles;
|
||||||
|
}
|
||||||
|
}
|
27
app/src/app/Dto/Service/Authorization.php
Normal file
27
app/src/app/Dto/Service/Authorization.php
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
<?php declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace App\Dto\Service;
|
||||||
|
|
||||||
|
final readonly class Authorization extends Dto
|
||||||
|
{
|
||||||
|
public function __construct(
|
||||||
|
private string $email,
|
||||||
|
private string $password,
|
||||||
|
private bool $remember = false
|
||||||
|
) { }
|
||||||
|
|
||||||
|
public function getEmail(): string
|
||||||
|
{
|
||||||
|
return $this->email;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getPassword(): string
|
||||||
|
{
|
||||||
|
return $this->password;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getRemember(): bool
|
||||||
|
{
|
||||||
|
return $this->remember;
|
||||||
|
}
|
||||||
|
}
|
8
app/src/app/Dto/Service/Dto.php
Normal file
8
app/src/app/Dto/Service/Dto.php
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
<?php declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace App\Dto\Service;
|
||||||
|
|
||||||
|
abstract readonly class Dto
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
17
app/src/app/Dto/Service/Private/Profile/Update.php
Normal file
17
app/src/app/Dto/Service/Private/Profile/Update.php
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
<?php declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace App\Dto\Service\Private\Profile;
|
||||||
|
|
||||||
|
use App\Dto\Service\Dto;
|
||||||
|
|
||||||
|
final readonly class Update extends Dto
|
||||||
|
{
|
||||||
|
public function __construct(
|
||||||
|
private string $name
|
||||||
|
) { }
|
||||||
|
|
||||||
|
public function getName(): string
|
||||||
|
{
|
||||||
|
return $this->name;
|
||||||
|
}
|
||||||
|
}
|
24
app/src/app/Dto/Service/Private/Profile/UpdateSettings.php
Normal file
24
app/src/app/Dto/Service/Private/Profile/UpdateSettings.php
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
<?php declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace App\Dto\Service\Private\Profile;
|
||||||
|
|
||||||
|
use App\Dto\Service\Dto;
|
||||||
|
use App\Enums\Lang;
|
||||||
|
|
||||||
|
final readonly class UpdateSettings extends Dto
|
||||||
|
{
|
||||||
|
public function __construct(
|
||||||
|
private ?Lang $lang,
|
||||||
|
private ?string $timezone
|
||||||
|
) { }
|
||||||
|
|
||||||
|
public function getLang(): ?Lang
|
||||||
|
{
|
||||||
|
return $this->lang;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getTimezone(): ?string
|
||||||
|
{
|
||||||
|
return $this->timezone;
|
||||||
|
}
|
||||||
|
}
|
17
app/src/app/Dto/Service/User/UpdatePassword.php
Normal file
17
app/src/app/Dto/Service/User/UpdatePassword.php
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
<?php declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace App\Dto\Service\User;
|
||||||
|
|
||||||
|
use App\Dto\Service\Dto;
|
||||||
|
|
||||||
|
final readonly class UpdatePassword extends Dto
|
||||||
|
{
|
||||||
|
public function __construct(
|
||||||
|
private string $password
|
||||||
|
) { }
|
||||||
|
|
||||||
|
public function getPassword(): string
|
||||||
|
{
|
||||||
|
return $this->password;
|
||||||
|
}
|
||||||
|
}
|
32
app/src/app/Dto/User/ManyRoleDto.php
Normal file
32
app/src/app/Dto/User/ManyRoleDto.php
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
<?php declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace App\Dto\User;
|
||||||
|
|
||||||
|
use App\Exceptions\Dto\User\ManyRoleDtoException;
|
||||||
|
|
||||||
|
final class ManyRoleDto
|
||||||
|
{
|
||||||
|
private array $roles = [];
|
||||||
|
|
||||||
|
public function __construct(array $roles = []) {
|
||||||
|
foreach ($roles as $role) {
|
||||||
|
if (!is_numeric($role) || is_float($role)) {
|
||||||
|
throw new ManyRoleDtoException('Not an integer: ' . $role . '.');
|
||||||
|
}
|
||||||
|
$this->add((int) $role);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function add(int $id): void
|
||||||
|
{
|
||||||
|
if ($id < 1) {
|
||||||
|
throw new ManyRoleDtoException('Only Integer > 0.');
|
||||||
|
}
|
||||||
|
$this->roles[] = $id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function toArray(): array
|
||||||
|
{
|
||||||
|
return $this->roles;
|
||||||
|
}
|
||||||
|
}
|
46
app/src/app/Enums/Lang.php
Normal file
46
app/src/app/Enums/Lang.php
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
<?php declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace App\Enums;
|
||||||
|
|
||||||
|
use Illuminate\Support\Collection;
|
||||||
|
|
||||||
|
enum Lang: int
|
||||||
|
{
|
||||||
|
case Ru = 1;
|
||||||
|
case En = 2;
|
||||||
|
|
||||||
|
public function getTitle(): string
|
||||||
|
{
|
||||||
|
return match ($this) {
|
||||||
|
self::Ru => 'Русский',
|
||||||
|
self::En => 'English'
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getLocale(): string
|
||||||
|
{
|
||||||
|
return match ($this) {
|
||||||
|
self::Ru => 'ru',
|
||||||
|
self::En => 'en'
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function toArray(): array
|
||||||
|
{
|
||||||
|
$choices = [];
|
||||||
|
foreach (self::cases() as $lang) {
|
||||||
|
$choices[] = [
|
||||||
|
'name' => $lang->name,
|
||||||
|
'value' => $lang->value,
|
||||||
|
'title' => $lang->getTitle(),
|
||||||
|
'locale' => $lang->getLocale()
|
||||||
|
];
|
||||||
|
}
|
||||||
|
return $choices;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function toCollection(): Collection
|
||||||
|
{
|
||||||
|
return collect(self::toArray());
|
||||||
|
}
|
||||||
|
}
|
61
app/src/app/Enums/Permission.php
Normal file
61
app/src/app/Enums/Permission.php
Normal file
@ -0,0 +1,61 @@
|
|||||||
|
<?php declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace App\Enums;
|
||||||
|
|
||||||
|
enum Permission: string
|
||||||
|
{
|
||||||
|
case AdminPanel = 'allow-admin-panel';
|
||||||
|
case Role = 'role';
|
||||||
|
case User = 'user';
|
||||||
|
|
||||||
|
public function getPermissions(): array
|
||||||
|
{
|
||||||
|
$permissions = match ($this) {
|
||||||
|
self::AdminPanel => [
|
||||||
|
'view' => __('permissions.Administrative panel allowed'),
|
||||||
|
],
|
||||||
|
default => $this->getBasePermissions()
|
||||||
|
};
|
||||||
|
|
||||||
|
return $permissions;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getTitle(): string
|
||||||
|
{
|
||||||
|
return __('permissions.' . $this->name);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function formatValue(string $permission): string
|
||||||
|
{
|
||||||
|
return $this->value . '.' . $permission;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function toArrayList(): array
|
||||||
|
{
|
||||||
|
$permissions = [];
|
||||||
|
foreach (self::cases() as $permissionEnum) {
|
||||||
|
foreach ($permissionEnum->getPermissions() as $permissionName => $permissionTitle) {
|
||||||
|
$name = $permissionEnum->formatValue($permissionName);
|
||||||
|
$title = $permissionEnum->getTitle() . ' - ' . $permissionTitle;
|
||||||
|
$permissions[$name] = $title;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $permissions;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function toArrayListCodes(): array
|
||||||
|
{
|
||||||
|
return \array_keys(self::toArrayList());
|
||||||
|
}
|
||||||
|
|
||||||
|
private function getBasePermissions(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'view' => __('permissions.Allowed to watch'),
|
||||||
|
'create' => __('permissions.Allowed to create'),
|
||||||
|
'update' => __('permissions.Allowed to edit'),
|
||||||
|
'delete' => __('permissions.Allowed to delete'),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
8
app/src/app/Enums/SystemRole.php
Normal file
8
app/src/app/Enums/SystemRole.php
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
<?php declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace App\Enums;
|
||||||
|
|
||||||
|
enum SystemRole: string
|
||||||
|
{
|
||||||
|
case Admin = 'admin';
|
||||||
|
}
|
8
app/src/app/Exceptions/Dto/User/ManyRoleDtoException.php
Normal file
8
app/src/app/Exceptions/Dto/User/ManyRoleDtoException.php
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
<?php declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace App\Exceptions\Dto\User;
|
||||||
|
|
||||||
|
final class ManyRoleDtoException extends \Exception
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
25
app/src/app/Helpers/Helpers.php
Normal file
25
app/src/app/Helpers/Helpers.php
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
<?php declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace App\Helpers;
|
||||||
|
|
||||||
|
use App\Models\User;
|
||||||
|
use Illuminate\Support\Facades\Cache;
|
||||||
|
use Illuminate\Support\Collection;
|
||||||
|
|
||||||
|
final readonly class Helpers
|
||||||
|
{
|
||||||
|
public static function getTimeZoneList(): Collection
|
||||||
|
{
|
||||||
|
return Cache::rememberForever('timezones_list_collection', function () {
|
||||||
|
$timezone = [];
|
||||||
|
foreach (timezone_identifiers_list(\DateTimeZone::ALL) as $key => $value) {
|
||||||
|
$timezone[$value] = $value . ' (UTC ' . now($value)->format('P') . ')';
|
||||||
|
}
|
||||||
|
return collect($timezone)->sortKeys();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function getUserTimeZone() {
|
||||||
|
return auth()->user()?->timezone ?? config('app.user_timezone');
|
||||||
|
}
|
||||||
|
}
|
10
app/src/app/Http/Controllers/Admin/Controller.php
Normal file
10
app/src/app/Http/Controllers/Admin/Controller.php
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
<?php declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace App\Http\Controllers\Admin;
|
||||||
|
|
||||||
|
use App\Http\Controllers\Controller as BaseController;
|
||||||
|
|
||||||
|
abstract class Controller extends BaseController
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
13
app/src/app/Http/Controllers/Admin/DashboardController.php
Normal file
13
app/src/app/Http/Controllers/Admin/DashboardController.php
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
<?php declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace App\Http\Controllers\Admin;
|
||||||
|
|
||||||
|
use Illuminate\View\View;
|
||||||
|
|
||||||
|
final class DashboardController extends Controller
|
||||||
|
{
|
||||||
|
public function index(): View
|
||||||
|
{
|
||||||
|
return view('admin/dashboard/index');
|
||||||
|
}
|
||||||
|
}
|
92
app/src/app/Http/Controllers/Admin/RolesController.php
Normal file
92
app/src/app/Http/Controllers/Admin/RolesController.php
Normal file
@ -0,0 +1,92 @@
|
|||||||
|
<?php declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace App\Http\Controllers\Admin;
|
||||||
|
|
||||||
|
use App\Dto\QuerySettingsDto;
|
||||||
|
use App\Http\Requests\Admin\Roles\IndexRequest;
|
||||||
|
use App\Http\Requests\Admin\Roles\StoreUpdateRequest;
|
||||||
|
use App\Services\Admin\RoleService;
|
||||||
|
use Illuminate\Http\RedirectResponse;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
use Illuminate\View\View;
|
||||||
|
|
||||||
|
final class RolesController extends Controller
|
||||||
|
{
|
||||||
|
public function __construct(
|
||||||
|
private readonly RoleService $roleService
|
||||||
|
) { }
|
||||||
|
public function index(IndexRequest $request): View
|
||||||
|
{
|
||||||
|
$user = $request->user();
|
||||||
|
$data = $request->getDto();
|
||||||
|
$querySettingsDto = new QuerySettingsDto(
|
||||||
|
limit: 20,
|
||||||
|
page: $data->getPage(),
|
||||||
|
queryWith: []
|
||||||
|
);
|
||||||
|
|
||||||
|
$result = $this->roleService->index($data->getRoleBuilderDto(), $querySettingsDto, $user);
|
||||||
|
if ($result->isError()) {
|
||||||
|
$this->errors($result);
|
||||||
|
}
|
||||||
|
|
||||||
|
return view('admin/roles/index', $result->getData());
|
||||||
|
}
|
||||||
|
|
||||||
|
public function create(Request $request): View
|
||||||
|
{
|
||||||
|
$user = $request->user();
|
||||||
|
$result = $this->roleService->create($user);
|
||||||
|
if ($result->isError()) {
|
||||||
|
$this->errors($result);
|
||||||
|
}
|
||||||
|
|
||||||
|
return view('admin/roles/create', $result->getData());
|
||||||
|
}
|
||||||
|
|
||||||
|
public function edit(int $id, Request $request): View
|
||||||
|
{
|
||||||
|
$user = $request->user();
|
||||||
|
$result = $this->roleService->edit($id, $user);
|
||||||
|
if ($result->isError()) {
|
||||||
|
$this->errors($result);
|
||||||
|
}
|
||||||
|
|
||||||
|
return view('admin/roles/edit', $result->getData());
|
||||||
|
}
|
||||||
|
|
||||||
|
public function store(StoreUpdateRequest $request): RedirectResponse
|
||||||
|
{
|
||||||
|
$data = $request->getDto();
|
||||||
|
$user = $request->user();
|
||||||
|
$result = $this->roleService->store($data, $user);
|
||||||
|
if ($result->isError()) {
|
||||||
|
return redirect()->back()->withInput()->withErrors($result->getErrorsOrMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
return redirect()->route('admin.roles.edit', $result->getModel())->withSuccess($result->getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
public function update(int $id, StoreUpdateRequest $request): RedirectResponse
|
||||||
|
{
|
||||||
|
$data = $request->getDto();
|
||||||
|
$user = $request->user();
|
||||||
|
$result = $this->roleService->update($id, $data, $user);
|
||||||
|
if ($result->isError()) {
|
||||||
|
return redirect()->back()->withInput()->withErrors($result->getErrorsOrMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
return redirect()->route('admin.roles.edit', $result->getModel())->withSuccess($result->getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
public function destroy(int $id, Request $request): RedirectResponse
|
||||||
|
{
|
||||||
|
$user = $request->user();
|
||||||
|
$result = $this->roleService->destroy($id, $user);
|
||||||
|
if ($result->isError()) {
|
||||||
|
return redirect()->back()->withInput()->withErrors($result->getErrorsOrMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
return redirect()->route('admin.roles.index')->withSuccess($result->getMessage());
|
||||||
|
}
|
||||||
|
}
|
106
app/src/app/Http/Controllers/Admin/UsersController.php
Normal file
106
app/src/app/Http/Controllers/Admin/UsersController.php
Normal file
@ -0,0 +1,106 @@
|
|||||||
|
<?php declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace App\Http\Controllers\Admin;
|
||||||
|
|
||||||
|
use App\Dto\QuerySettingsDto;
|
||||||
|
use App\Http\Requests\Admin\Users\IndexRequest;
|
||||||
|
use App\Http\Requests\Admin\Users\StoreUpdateRequest;
|
||||||
|
use App\Http\Requests\Admin\Users\UpdatePasswordRequest;
|
||||||
|
use App\Services\Admin\UserService;
|
||||||
|
use Illuminate\Http\RedirectResponse;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
use Illuminate\View\View;
|
||||||
|
|
||||||
|
class UsersController extends Controller
|
||||||
|
{
|
||||||
|
public function __construct(
|
||||||
|
private readonly UserService $userService
|
||||||
|
) { }
|
||||||
|
|
||||||
|
public function index(IndexRequest $request): View
|
||||||
|
{
|
||||||
|
$user = $request->user();
|
||||||
|
$data = $request->getDto();
|
||||||
|
$querySettingsDto = new QuerySettingsDto(
|
||||||
|
limit: 20,
|
||||||
|
page: $data->getPage(),
|
||||||
|
queryWith: []
|
||||||
|
);
|
||||||
|
|
||||||
|
$result = $this->userService->index($data->getUserBuilderDto(), $querySettingsDto, $user);
|
||||||
|
if ($result->isError()) {
|
||||||
|
$this->errors($result);
|
||||||
|
}
|
||||||
|
|
||||||
|
return view('admin/users/index', $result->getData());
|
||||||
|
}
|
||||||
|
|
||||||
|
public function create(Request $request): View
|
||||||
|
{
|
||||||
|
$user = $request->user();
|
||||||
|
$result = $this->userService->create($user);
|
||||||
|
if ($result->isError()) {
|
||||||
|
$this->errors($result);
|
||||||
|
}
|
||||||
|
|
||||||
|
return view('admin/users/create', $result->getData());
|
||||||
|
}
|
||||||
|
|
||||||
|
public function edit(int $id, Request $request): View
|
||||||
|
{
|
||||||
|
$user = $request->user();
|
||||||
|
$result = $this->userService->edit($id, $user);
|
||||||
|
if ($result->isError()) {
|
||||||
|
$this->errors($result);
|
||||||
|
}
|
||||||
|
|
||||||
|
return view('admin/users/edit', $result->getData());
|
||||||
|
}
|
||||||
|
|
||||||
|
public function store(StoreUpdateRequest $request): RedirectResponse
|
||||||
|
{
|
||||||
|
$data = $request->getDto();
|
||||||
|
$user = $request->user();
|
||||||
|
$result = $this->userService->store($data, $user);
|
||||||
|
if ($result->isError()) {
|
||||||
|
return redirect()->back()->withInput()->withErrors($result->getErrorsOrMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
return redirect()->route('admin.users.edit', $result->getModel())->withSuccess($result->getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
public function update(int $id, StoreUpdateRequest $request): RedirectResponse
|
||||||
|
{
|
||||||
|
$data = $request->getDto();
|
||||||
|
$user = $request->user();
|
||||||
|
$result = $this->userService->update($id, $data, $user);
|
||||||
|
if ($result->isError()) {
|
||||||
|
return redirect()->back()->withInput()->withErrors($result->getErrorsOrMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
return redirect()->route('admin.users.edit', $result->getModel())->withSuccess($result->getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
public function updatePassword(int $id, UpdatePasswordRequest $request): RedirectResponse
|
||||||
|
{
|
||||||
|
$data = $request->getDto();
|
||||||
|
$user = $request->user();
|
||||||
|
$result = $this->userService->updatePassword($id, $data, $user);
|
||||||
|
if ($result->isError()) {
|
||||||
|
return redirect()->back()->withInput()->withErrors($result->getErrorsOrMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
return redirect()->route('admin.users.edit', $result->getModel())->withSuccess($result->getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
public function destroy(int $id, Request $request): RedirectResponse
|
||||||
|
{
|
||||||
|
$user = $request->user();
|
||||||
|
$result = $this->userService->destroy($id, $user);
|
||||||
|
if ($result->isError()) {
|
||||||
|
return redirect()->back()->withInput()->withErrors($result->getErrorsOrMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
return redirect()->route('admin.users.index')->withSuccess($result->getMessage());
|
||||||
|
}
|
||||||
|
}
|
47
app/src/app/Http/Controllers/AuthController.php
Normal file
47
app/src/app/Http/Controllers/AuthController.php
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
<?php declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace App\Http\Controllers;
|
||||||
|
|
||||||
|
use App\Http\Requests\AuthorizationRequest;
|
||||||
|
use App\Services\AuthService;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
use Illuminate\Http\Response;
|
||||||
|
use Illuminate\Support\Facades\Log;
|
||||||
|
use Illuminate\View\View;
|
||||||
|
use Illuminate\Http\RedirectResponse;
|
||||||
|
use Illuminate\Support\Facades\Auth;
|
||||||
|
|
||||||
|
final class AuthController extends Controller
|
||||||
|
{
|
||||||
|
public function __construct(
|
||||||
|
private readonly AuthService $authService
|
||||||
|
) { }
|
||||||
|
|
||||||
|
public function login(): View
|
||||||
|
{
|
||||||
|
return view('login');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function authorization(AuthorizationRequest $request): RedirectResponse
|
||||||
|
{
|
||||||
|
$authorization = $request->getDto();
|
||||||
|
$result = $this->authService->authorization($authorization);
|
||||||
|
if ($result->isError()) {
|
||||||
|
if ($result->getCode() === Response::HTTP_UNAUTHORIZED) {
|
||||||
|
Log::warning('Unauthorized ' . $authorization->getEmail() . ' [' . $request->getClientIp() . ']');
|
||||||
|
}
|
||||||
|
return redirect()->route('login')->withInput()->withErrors($result->getMessage());
|
||||||
|
}
|
||||||
|
$request->session()->regenerate();
|
||||||
|
Log::notice('Logged in ' . $authorization->getEmail() . ' [' . $request->getClientIp() . ']');
|
||||||
|
return redirect()->route('admin.home');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function logout(Request $request): RedirectResponse
|
||||||
|
{
|
||||||
|
Auth::logout();
|
||||||
|
$request->session()->invalidate();
|
||||||
|
$request->session()->regenerateToken();
|
||||||
|
return redirect(route('login'));
|
||||||
|
}
|
||||||
|
}
|
18
app/src/app/Http/Controllers/Controller.php
Normal file
18
app/src/app/Http/Controllers/Controller.php
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
<?php declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace App\Http\Controllers;
|
||||||
|
|
||||||
|
use App\Contracts\ServiceResultError as ServiceResultErrorContract;
|
||||||
|
use Illuminate\Http\Response;
|
||||||
|
|
||||||
|
abstract class Controller
|
||||||
|
{
|
||||||
|
final protected function errors(ServiceResultErrorContract $result): never
|
||||||
|
{
|
||||||
|
if ($result->getCode() === Response::HTTP_UNPROCESSABLE_ENTITY) {
|
||||||
|
redirect()->back()->withInput()->withErrors($result->getErrors());
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
abort($result->getCode(), $result->getMessage());
|
||||||
|
}
|
||||||
|
}
|
10
app/src/app/Http/Controllers/Private/Controller.php
Normal file
10
app/src/app/Http/Controllers/Private/Controller.php
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
<?php declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace App\Http\Controllers\Private;
|
||||||
|
|
||||||
|
use App\Http\Controllers\Controller as BaseController;
|
||||||
|
|
||||||
|
abstract class Controller extends BaseController
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
72
app/src/app/Http/Controllers/Private/ProfileController.php
Normal file
72
app/src/app/Http/Controllers/Private/ProfileController.php
Normal file
@ -0,0 +1,72 @@
|
|||||||
|
<?php declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace App\Http\Controllers\Private;
|
||||||
|
|
||||||
|
use App\Enums\Lang;
|
||||||
|
use App\Helpers\Helpers;
|
||||||
|
use App\Http\Requests\Private\Profile\UpdatePasswordRequest;
|
||||||
|
use App\Http\Requests\Private\Profile\UpdateRequest;
|
||||||
|
use App\Http\Requests\Private\Profile\UpdateSettingsRequest;
|
||||||
|
use App\Services\Private\ProfileService;
|
||||||
|
use Illuminate\Http\RedirectResponse;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
use Illuminate\View\View;
|
||||||
|
|
||||||
|
final class ProfileController extends Controller
|
||||||
|
{
|
||||||
|
public function __construct(
|
||||||
|
private readonly ProfileService $profileService
|
||||||
|
) { }
|
||||||
|
|
||||||
|
public function profile(Request $request): View
|
||||||
|
{
|
||||||
|
return view('private/profile/profile', [
|
||||||
|
'user' => $request->user()
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function settings(Request $request): View
|
||||||
|
{
|
||||||
|
return view('private/profile/settings', [
|
||||||
|
'user' => $request->user(),
|
||||||
|
'languages' => Lang::toCollection()->pluck(value: 'title', key: 'value')->toArray(),
|
||||||
|
'timezone' => Helpers::getTimeZoneList()->toArray(),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function update(UpdateRequest $request): RedirectResponse
|
||||||
|
{
|
||||||
|
$data = $request->getDto();
|
||||||
|
$user = $request->user();
|
||||||
|
|
||||||
|
$result = $this->profileService->update($data, $user);
|
||||||
|
if ($result->isError()) {
|
||||||
|
return redirect()->back()->withInput()->withErrors($result->getMessage());
|
||||||
|
}
|
||||||
|
return redirect()->route('profile.edit')->withSuccess($result->getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
public function updatePassword(UpdatePasswordRequest $request): RedirectResponse
|
||||||
|
{
|
||||||
|
$data = $request->getDto();
|
||||||
|
$user = $request->user();
|
||||||
|
|
||||||
|
$result = $this->profileService->updatePassword($data, $user);
|
||||||
|
if ($result->isError()) {
|
||||||
|
return redirect()->back()->withInput()->withErrors($result->getMessage());
|
||||||
|
}
|
||||||
|
return redirect()->route('profile.edit')->withSuccess($result->getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
public function updateSettings(UpdateSettingsRequest $request): RedirectResponse
|
||||||
|
{
|
||||||
|
$data = $request->getDto();
|
||||||
|
$user = $request->user();
|
||||||
|
|
||||||
|
$result = $this->profileService->updateSettings($data, $user);
|
||||||
|
if ($result->isError()) {
|
||||||
|
return redirect()->back()->withInput()->withErrors($result->getMessage());
|
||||||
|
}
|
||||||
|
return redirect()->route('profile.settings')->withSuccess($result->getMessage());
|
||||||
|
}
|
||||||
|
}
|
26
app/src/app/Http/Middleware/AdminPanel.php
Normal file
26
app/src/app/Http/Middleware/AdminPanel.php
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
<?php declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace App\Http\Middleware;
|
||||||
|
|
||||||
|
use Closure;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
use Symfony\Component\HttpFoundation\Response;
|
||||||
|
|
||||||
|
class AdminPanel
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Handle an incoming request.
|
||||||
|
*
|
||||||
|
* @param \Closure(\Illuminate\Http\Request): (\Symfony\Component\HttpFoundation\Response) $next
|
||||||
|
*/
|
||||||
|
public function handle(Request $request, Closure $next): Response
|
||||||
|
{
|
||||||
|
if (
|
||||||
|
$request->user() === null || $request->user()->cannot('AdminPanel')
|
||||||
|
) {
|
||||||
|
abort(403);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $next($request);
|
||||||
|
}
|
||||||
|
}
|
19
app/src/app/Http/Middleware/UserLocale.php
Normal file
19
app/src/app/Http/Middleware/UserLocale.php
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
<?php declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace App\Http\Middleware;
|
||||||
|
|
||||||
|
use Closure;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
use Illuminate\Support\Facades\App;
|
||||||
|
|
||||||
|
final class UserLocale
|
||||||
|
{
|
||||||
|
public function handle(Request $request, Closure $next)
|
||||||
|
{
|
||||||
|
if ($request->user() && $request->user()->lang) {
|
||||||
|
App::setLocale($request->user()->lang->getLocale());
|
||||||
|
}
|
||||||
|
|
||||||
|
return $next($request);
|
||||||
|
}
|
||||||
|
}
|
30
app/src/app/Http/Requests/Admin/Roles/IndexRequest.php
Normal file
30
app/src/app/Http/Requests/Admin/Roles/IndexRequest.php
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
<?php declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace App\Http\Requests\Admin\Roles;
|
||||||
|
|
||||||
|
use App\Contracts\FormRequestDto;
|
||||||
|
use App\Dto\Builder\Role;
|
||||||
|
use App\Dto\Service\Admin\Role\Index;
|
||||||
|
use Illuminate\Foundation\Http\FormRequest;
|
||||||
|
|
||||||
|
final class IndexRequest extends FormRequest implements FormRequestDto
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Get the validation rules that apply to the request.
|
||||||
|
*/
|
||||||
|
public function rules(): array
|
||||||
|
{
|
||||||
|
$this->redirect = route('admin.roles.index');
|
||||||
|
return [
|
||||||
|
'page' => ['nullable', 'numeric', 'min:1']
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getDto(): Index
|
||||||
|
{
|
||||||
|
return new Index(
|
||||||
|
roleBuilderDto: new Role(),
|
||||||
|
page: (int) $this->input('page', 1)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
37
app/src/app/Http/Requests/Admin/Roles/StoreUpdateRequest.php
Normal file
37
app/src/app/Http/Requests/Admin/Roles/StoreUpdateRequest.php
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
<?php declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace App\Http\Requests\Admin\Roles;
|
||||||
|
|
||||||
|
use App\Contracts\FormRequestDto;
|
||||||
|
use App\Dto\Service\Admin\Role\StoreUpdate;
|
||||||
|
use App\Rules\Permission;
|
||||||
|
use Illuminate\Foundation\Http\FormRequest;
|
||||||
|
|
||||||
|
final class StoreUpdateRequest extends FormRequest implements FormRequestDto
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Get the validation rules that apply to the request.
|
||||||
|
*/
|
||||||
|
public function rules(): array
|
||||||
|
{
|
||||||
|
$rules = [
|
||||||
|
'name' => ['required', 'max:255'],
|
||||||
|
'permissions' => ['array', new Permission()]
|
||||||
|
];
|
||||||
|
if ($this->getMethod() === 'POST') {
|
||||||
|
$rules['code'] = ['required', 'min:3', 'max:255', 'regex:/^[a-z0-9_-]+$/i'];
|
||||||
|
}
|
||||||
|
|
||||||
|
return $rules;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public function getDto(): StoreUpdate
|
||||||
|
{
|
||||||
|
return new StoreUpdate(
|
||||||
|
name: $this->input('name'),
|
||||||
|
code: $this->input('code', null),
|
||||||
|
permissions: $this->input('permissions', [])
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
30
app/src/app/Http/Requests/Admin/Users/IndexRequest.php
Normal file
30
app/src/app/Http/Requests/Admin/Users/IndexRequest.php
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
<?php declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace App\Http\Requests\Admin\Users;
|
||||||
|
|
||||||
|
use App\Contracts\FormRequestDto;
|
||||||
|
use App\Dto\Builder\User;
|
||||||
|
use App\Dto\Service\Admin\User\Index;
|
||||||
|
use Illuminate\Foundation\Http\FormRequest;
|
||||||
|
|
||||||
|
final class IndexRequest extends FormRequest implements FormRequestDto
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Get the validation rules that apply to the request.
|
||||||
|
*/
|
||||||
|
public function rules(): array
|
||||||
|
{
|
||||||
|
$this->redirect = route('admin.users.index');
|
||||||
|
return [
|
||||||
|
'page' => ['nullable', 'numeric', 'min:1']
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getDto(): Index
|
||||||
|
{
|
||||||
|
return new Index(
|
||||||
|
userBuilderDto: new User(),
|
||||||
|
page: (int) $this->input('page', 1)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
42
app/src/app/Http/Requests/Admin/Users/StoreUpdateRequest.php
Normal file
42
app/src/app/Http/Requests/Admin/Users/StoreUpdateRequest.php
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
<?php declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace App\Http\Requests\Admin\Users;
|
||||||
|
|
||||||
|
use App\Contracts\FormRequestDto;
|
||||||
|
use App\Dto\Service\Admin\User\StoreUpdate;
|
||||||
|
use App\Dto\User\ManyRoleDto;
|
||||||
|
use Illuminate\Foundation\Http\FormRequest;
|
||||||
|
use Illuminate\Validation\Rule;
|
||||||
|
use Illuminate\Validation\Rules\Password;
|
||||||
|
|
||||||
|
final class StoreUpdateRequest extends FormRequest implements FormRequestDto
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Get the validation rules that apply to the request.
|
||||||
|
*/
|
||||||
|
public function rules(): array
|
||||||
|
{
|
||||||
|
$rules = [
|
||||||
|
'name' => ['required', 'max:255'],
|
||||||
|
'email' => ['required', 'email', 'max:255'],
|
||||||
|
'roles' => ['array', Rule::exists('roles', 'id')],
|
||||||
|
];
|
||||||
|
|
||||||
|
if ($this->getMethod() === 'POST') {
|
||||||
|
$rules['password'] = ['required', Password::default()];
|
||||||
|
}
|
||||||
|
|
||||||
|
return $rules;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public function getDto(): StoreUpdate
|
||||||
|
{
|
||||||
|
return new StoreUpdate(
|
||||||
|
name: $this->input('name'),
|
||||||
|
email: $this->input('email'),
|
||||||
|
roles: new ManyRoleDto($this->input('roles', [])),
|
||||||
|
password: $this->input('password', null),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,26 @@
|
|||||||
|
<?php declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace App\Http\Requests\Admin\Users;
|
||||||
|
|
||||||
|
use App\Contracts\FormRequestDto;
|
||||||
|
use App\Dto\Service\User\UpdatePassword;
|
||||||
|
use Illuminate\Foundation\Http\FormRequest;
|
||||||
|
use Illuminate\Validation\Rules\Password;
|
||||||
|
|
||||||
|
final class UpdatePasswordRequest extends FormRequest implements FormRequestDto
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Get the validation rules that apply to the request.
|
||||||
|
*/
|
||||||
|
public function rules(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'password' => ['required', Password::default()],
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getDto(): UpdatePassword
|
||||||
|
{
|
||||||
|
return new UpdatePassword(password: $this->input('password'));
|
||||||
|
}
|
||||||
|
}
|
31
app/src/app/Http/Requests/AuthorizationRequest.php
Normal file
31
app/src/app/Http/Requests/AuthorizationRequest.php
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
<?php declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace App\Http\Requests;
|
||||||
|
|
||||||
|
use App\Contracts\FormRequestDto;
|
||||||
|
use App\Dto\Service\Authorization;
|
||||||
|
use Illuminate\Foundation\Http\FormRequest;
|
||||||
|
|
||||||
|
final class AuthorizationRequest extends FormRequest implements FormRequestDto
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Get the validation rules that apply to the request.
|
||||||
|
*/
|
||||||
|
public function rules(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'email' => ['required', 'email', 'max:255'],
|
||||||
|
'password' => ['required', 'min:3'],
|
||||||
|
'remember' => ['nullable', 'boolean'],
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getDto(): Authorization
|
||||||
|
{
|
||||||
|
return new Authorization(
|
||||||
|
email: $this->input('email'),
|
||||||
|
password: $this->input('password'),
|
||||||
|
remember: (bool) $this->input('remember', false)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,26 @@
|
|||||||
|
<?php declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace App\Http\Requests\Private\Profile;
|
||||||
|
|
||||||
|
use App\Contracts\FormRequestDto;
|
||||||
|
use App\Dto\Service\User\UpdatePassword;
|
||||||
|
use Illuminate\Foundation\Http\FormRequest;
|
||||||
|
use Illuminate\Validation\Rules\Password;
|
||||||
|
|
||||||
|
final class UpdatePasswordRequest extends FormRequest implements FormRequestDto
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Get the validation rules that apply to the request.
|
||||||
|
*/
|
||||||
|
public function rules(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'password' => ['required', 'confirmed', Password::default()],
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getDto(): UpdatePassword
|
||||||
|
{
|
||||||
|
return new UpdatePassword(password: $this->input('password'));
|
||||||
|
}
|
||||||
|
}
|
25
app/src/app/Http/Requests/Private/Profile/UpdateRequest.php
Normal file
25
app/src/app/Http/Requests/Private/Profile/UpdateRequest.php
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
<?php declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace App\Http\Requests\Private\Profile;
|
||||||
|
|
||||||
|
use App\Contracts\FormRequestDto;
|
||||||
|
use App\Dto\Service\Private\Profile\Update;
|
||||||
|
use Illuminate\Foundation\Http\FormRequest;
|
||||||
|
|
||||||
|
final class UpdateRequest extends FormRequest implements FormRequestDto
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Get the validation rules that apply to the request.
|
||||||
|
*/
|
||||||
|
public function rules(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'name' => ['required', 'max:255'],
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getDto(): Update
|
||||||
|
{
|
||||||
|
return new Update(name: $this->input('name'));
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,38 @@
|
|||||||
|
<?php declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace App\Http\Requests\Private\Profile;
|
||||||
|
|
||||||
|
use App\Contracts\FormRequestDto;
|
||||||
|
use App\Dto\Service\Private\Profile\UpdateSettings;
|
||||||
|
use App\Enums\Lang;
|
||||||
|
use App\Helpers\Helpers;
|
||||||
|
use Illuminate\Foundation\Http\FormRequest;
|
||||||
|
use Illuminate\Validation\Rule;
|
||||||
|
use Illuminate\Validation\Rules\Enum;
|
||||||
|
|
||||||
|
final class UpdateSettingsRequest extends FormRequest implements FormRequestDto
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Get the validation rules that apply to the request.
|
||||||
|
*/
|
||||||
|
public function rules(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'lang' => ['nullable', new Enum(Lang::class)],
|
||||||
|
'timezone' => ['nullable', Rule::in(Helpers::getTimeZoneList()->keys()->toArray())]
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getDto(): UpdateSettings
|
||||||
|
{
|
||||||
|
$lang = $this->input('lang', null);
|
||||||
|
if (!is_null($lang)) {
|
||||||
|
$lang = Lang::from((int) $lang);
|
||||||
|
}
|
||||||
|
|
||||||
|
return new UpdateSettings(
|
||||||
|
lang: $lang,
|
||||||
|
timezone: $this->input('timezone', null),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
55
app/src/app/Models/Role.php
Normal file
55
app/src/app/Models/Role.php
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
<?php declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace App\Models;
|
||||||
|
|
||||||
|
use App\Enums\SystemRole as SystemRoleEnum;
|
||||||
|
use Illuminate\Database\Eloquent\Casts\Attribute;
|
||||||
|
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||||
|
use Illuminate\Database\Eloquent\Model;
|
||||||
|
use Illuminate\Database\Eloquent\Builder;
|
||||||
|
use Illuminate\Database\Eloquent\Relations\hasMany;
|
||||||
|
use Illuminate\Database\Eloquent\SoftDeletes;
|
||||||
|
|
||||||
|
final class Role extends Model
|
||||||
|
{
|
||||||
|
use HasFactory, SoftDeletes;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The attributes that are mass assignable.
|
||||||
|
*
|
||||||
|
* @var array
|
||||||
|
*/
|
||||||
|
protected $fillable = [
|
||||||
|
'name',
|
||||||
|
'code'
|
||||||
|
];
|
||||||
|
|
||||||
|
public function scopeLatest(Builder $query): Builder
|
||||||
|
{
|
||||||
|
return $query->orderBy('id', 'desc');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function scopeAlphavit(Builder $query): Builder
|
||||||
|
{
|
||||||
|
return $query->orderBy('name', 'asc');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function permissions(): hasMany
|
||||||
|
{
|
||||||
|
return $this->hasMany(RolePermission::class, 'role_id', 'id');
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function isRemove(): Attribute
|
||||||
|
{
|
||||||
|
return Attribute::make(
|
||||||
|
get: fn ($dontRemove) => ( SystemRoleEnum::tryFrom($this->code) === null ),
|
||||||
|
)->shouldCache();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function isAdmin(): Attribute
|
||||||
|
{
|
||||||
|
return Attribute::make(
|
||||||
|
get: fn () => ( $this->code === SystemRoleEnum::Admin->value ),
|
||||||
|
)->shouldCache();
|
||||||
|
}
|
||||||
|
}
|
21
app/src/app/Models/RolePermission.php
Normal file
21
app/src/app/Models/RolePermission.php
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
<?php declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace App\Models;
|
||||||
|
|
||||||
|
use App\Enums\Permission;
|
||||||
|
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||||
|
use Illuminate\Database\Eloquent\Model;
|
||||||
|
|
||||||
|
final class RolePermission extends Model
|
||||||
|
{
|
||||||
|
use HasFactory;
|
||||||
|
|
||||||
|
protected $table = 'role_permission';
|
||||||
|
|
||||||
|
public function getPermissionTitleAttribute(): string
|
||||||
|
{
|
||||||
|
$pemissions = Permission::toArrayList();
|
||||||
|
|
||||||
|
return $pemissions[$this->permission] ?? '';
|
||||||
|
}
|
||||||
|
}
|
88
app/src/app/Models/User.php
Normal file
88
app/src/app/Models/User.php
Normal file
@ -0,0 +1,88 @@
|
|||||||
|
<?php declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace App\Models;
|
||||||
|
|
||||||
|
// use Illuminate\Contracts\Auth\MustVerifyEmail;
|
||||||
|
use App\Enums\Lang;
|
||||||
|
use App\Enums\SystemRole;
|
||||||
|
use Illuminate\Database\Eloquent\Casts\Attribute;
|
||||||
|
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||||
|
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
|
||||||
|
use Illuminate\Foundation\Auth\User as Authenticatable;
|
||||||
|
use Illuminate\Notifications\Notifiable;
|
||||||
|
use Illuminate\Database\Eloquent\SoftDeletes;
|
||||||
|
|
||||||
|
class User extends Authenticatable
|
||||||
|
{
|
||||||
|
use HasFactory, Notifiable, SoftDeletes;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The attributes that are mass assignable.
|
||||||
|
*
|
||||||
|
* @var array<int, string>
|
||||||
|
*/
|
||||||
|
protected $fillable = [
|
||||||
|
'name',
|
||||||
|
'email',
|
||||||
|
'password',
|
||||||
|
'nickname',
|
||||||
|
'timezone',
|
||||||
|
'lang',
|
||||||
|
];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The attributes that should be hidden for serialization.
|
||||||
|
*
|
||||||
|
* @var array<int, string>
|
||||||
|
*/
|
||||||
|
protected $hidden = [
|
||||||
|
'password',
|
||||||
|
'remember_token',
|
||||||
|
];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the attributes that should be cast.
|
||||||
|
*
|
||||||
|
* @return array<string, string>
|
||||||
|
*/
|
||||||
|
protected function casts(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'email_verified_at' => 'datetime',
|
||||||
|
'password' => 'hashed',
|
||||||
|
'lang' => Lang::class,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function roles(): belongsToMany
|
||||||
|
{
|
||||||
|
return $this->belongsToMany(Role::class);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function hasRole(string $role): bool
|
||||||
|
{
|
||||||
|
return $this->roles->where('code', $role)->isNotEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function hasPermission(string $permission): bool
|
||||||
|
{
|
||||||
|
return $this->permissions->search($permission) !== false;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function isAdmin(): Attribute
|
||||||
|
{
|
||||||
|
return Attribute::make(
|
||||||
|
get: fn () => $this->hasRole(SystemRole::Admin->value),
|
||||||
|
)->shouldCache();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function permissions(): Attribute
|
||||||
|
{
|
||||||
|
return Attribute::make(
|
||||||
|
get: function () {
|
||||||
|
$roles = $this->roles->modelKeys();
|
||||||
|
return RolePermission::query()->whereIn('role_id', $roles)->select('permission')->pluck('permission');
|
||||||
|
},
|
||||||
|
)->shouldCache();
|
||||||
|
}
|
||||||
|
}
|
13
app/src/app/Policies/AdminPanel.php
Normal file
13
app/src/app/Policies/AdminPanel.php
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
<?php declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace App\Policies;
|
||||||
|
|
||||||
|
use App\Models\User;
|
||||||
|
|
||||||
|
readonly class AdminPanel extends Policy
|
||||||
|
{
|
||||||
|
public function view(User $user): bool
|
||||||
|
{
|
||||||
|
return $user->hasPermission('allow-admin-panel.view');
|
||||||
|
}
|
||||||
|
}
|
20
app/src/app/Policies/Policy.php
Normal file
20
app/src/app/Policies/Policy.php
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
<?php declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace App\Policies;
|
||||||
|
|
||||||
|
use App\Models\User;
|
||||||
|
use Illuminate\Auth\Access\HandlesAuthorization;
|
||||||
|
|
||||||
|
abstract readonly class Policy
|
||||||
|
{
|
||||||
|
use HandlesAuthorization;
|
||||||
|
|
||||||
|
final public function before(User $user): ?bool
|
||||||
|
{
|
||||||
|
if ($user->is_admin) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
34
app/src/app/Policies/RolePolicy.php
Normal file
34
app/src/app/Policies/RolePolicy.php
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
<?php declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace App\Policies;
|
||||||
|
|
||||||
|
use App\Models\Role;
|
||||||
|
use App\Models\User;
|
||||||
|
|
||||||
|
final readonly class RolePolicy extends Policy
|
||||||
|
{
|
||||||
|
public function viewAny(User $user): bool
|
||||||
|
{
|
||||||
|
return $user->hasPermission('role.view');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function view(User $user, Role $role): bool
|
||||||
|
{
|
||||||
|
return $user->hasPermission('role.view');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function create(User $user): bool
|
||||||
|
{
|
||||||
|
return $user->hasPermission('role.create');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function update(User $user, Role $role): bool
|
||||||
|
{
|
||||||
|
return $user->hasPermission('role.update');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function delete(User $user, Role $role): bool
|
||||||
|
{
|
||||||
|
return $user->hasPermission('role.delete');
|
||||||
|
}
|
||||||
|
}
|
33
app/src/app/Policies/UserPolicy.php
Normal file
33
app/src/app/Policies/UserPolicy.php
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
<?php declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace App\Policies;
|
||||||
|
|
||||||
|
use App\Models\User;
|
||||||
|
|
||||||
|
final readonly class UserPolicy extends Policy
|
||||||
|
{
|
||||||
|
public function viewAny(User $user): bool
|
||||||
|
{
|
||||||
|
return $user->hasPermission('user.view');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function view(User $user, User $userView): bool
|
||||||
|
{
|
||||||
|
return $user->hasPermission('user.view');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function create(User $user): bool
|
||||||
|
{
|
||||||
|
return $user->hasPermission('user.create');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function update(User $user, User $userUpdate): bool
|
||||||
|
{
|
||||||
|
return $user->hasPermission('user.update');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function delete(User $user, User $userDelete): bool
|
||||||
|
{
|
||||||
|
return $user->hasPermission('user.delete');
|
||||||
|
}
|
||||||
|
}
|
66
app/src/app/Providers/AppServiceProvider.php
Normal file
66
app/src/app/Providers/AppServiceProvider.php
Normal file
@ -0,0 +1,66 @@
|
|||||||
|
<?php declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace App\Providers;
|
||||||
|
|
||||||
|
use App\Services\Search\CreateSearchInstanceCommand;
|
||||||
|
use App\Services\Search\Search;
|
||||||
|
use Illuminate\Cache\RateLimiting\Limit;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
use Illuminate\Support\Facades\Gate;
|
||||||
|
use Illuminate\Support\Facades\RateLimiter;
|
||||||
|
use Illuminate\Support\ServiceProvider;
|
||||||
|
use Illuminate\Support\Facades\URL;
|
||||||
|
use Illuminate\Validation\Rules\Password;
|
||||||
|
|
||||||
|
class AppServiceProvider extends ServiceProvider
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Register any application services.
|
||||||
|
*/
|
||||||
|
public function register(): void
|
||||||
|
{
|
||||||
|
$this->app->bind(CreateSearchInstanceCommand::class, function () {
|
||||||
|
return new CreateSearchInstanceCommand(Search::class);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Bootstrap any application services.
|
||||||
|
*/
|
||||||
|
public function boot(): void
|
||||||
|
{
|
||||||
|
if (config('app.force_https') === true) {
|
||||||
|
URL::forceScheme('https');
|
||||||
|
}
|
||||||
|
|
||||||
|
Password::defaults(function () {
|
||||||
|
$rule = Password::min(8);
|
||||||
|
|
||||||
|
if ($this->app->isProduction()) {
|
||||||
|
$rule->letters()
|
||||||
|
->mixedCase()
|
||||||
|
->numbers()
|
||||||
|
->symbols()
|
||||||
|
->uncompromised();
|
||||||
|
}
|
||||||
|
|
||||||
|
return $rule;
|
||||||
|
});
|
||||||
|
|
||||||
|
$this->configureRateLimiting();
|
||||||
|
Gate::define('AdminPanel', [\App\Policies\AdminPanel::class, 'view']);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Configure the rate limiters for the application.
|
||||||
|
*/
|
||||||
|
private function configureRateLimiting(): void
|
||||||
|
{
|
||||||
|
RateLimiter::for('login', function (Request $request) {
|
||||||
|
return [
|
||||||
|
Limit::perHour(config('rate_limiting.login_max_request', 50))->by($request->getClientIp()),
|
||||||
|
Limit::perHour(config('rate_limiting.login_max_email_request', 10))->by($request->getClientIp() . '-' . $request->input('email')),
|
||||||
|
];
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
59
app/src/app/Repositories/RoleRepository.php
Normal file
59
app/src/app/Repositories/RoleRepository.php
Normal file
@ -0,0 +1,59 @@
|
|||||||
|
<?php declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace App\Repositories;
|
||||||
|
|
||||||
|
use App\Contracts\Search;
|
||||||
|
use App\Models\Role;
|
||||||
|
use App\Dto\Builder\Role as RoleBuilderDto;
|
||||||
|
use App\Services\Role\BuilderCommand;
|
||||||
|
use App\Services\Search\CreateSearchInstanceCommand;
|
||||||
|
use Illuminate\Database\Eloquent\Builder;
|
||||||
|
|
||||||
|
final readonly class RoleRepository
|
||||||
|
{
|
||||||
|
public function __construct(
|
||||||
|
private CreateSearchInstanceCommand $createSearchInstanceCommand,
|
||||||
|
private BuilderCommand $builderCommand
|
||||||
|
) { }
|
||||||
|
|
||||||
|
public function getRoleById(int $id): ?Role
|
||||||
|
{
|
||||||
|
return Role::query()->where('id', $id)->first();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getRoleByCode(string $code): ?Role
|
||||||
|
{
|
||||||
|
return Role::query()->where('code', $code)->first();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getRoles(RoleBuilderDto $roleBuilderDto, array $with = []): Search
|
||||||
|
{
|
||||||
|
$query = $this->builderCommand->execute(
|
||||||
|
query: Role::query()->with($with),
|
||||||
|
roleBuilderDto: $roleBuilderDto
|
||||||
|
);
|
||||||
|
|
||||||
|
return $this->createSearchInstanceCommand->execute($query);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getRolesForSelect(array $withExcepts = []): array
|
||||||
|
{
|
||||||
|
return Role::query()
|
||||||
|
->when($withExcepts, function (Builder $query, array $withExcepts) {
|
||||||
|
$query->withTrashed()->whereNull('deleted_at')->orWhereIn('id', $withExcepts);
|
||||||
|
})
|
||||||
|
->pluck('name', 'id')
|
||||||
|
->toArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function isExistsCode(string $code, ?int $exceptId = null): bool
|
||||||
|
{
|
||||||
|
return Role::query()
|
||||||
|
->where('code', $code)
|
||||||
|
->when($exceptId, function (Builder $query, int $exceptId) {
|
||||||
|
$query->where('id', '!=', $exceptId);
|
||||||
|
})
|
||||||
|
->withTrashed()
|
||||||
|
->exists();
|
||||||
|
}
|
||||||
|
}
|
50
app/src/app/Repositories/UserRepository.php
Normal file
50
app/src/app/Repositories/UserRepository.php
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
<?php declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace App\Repositories;
|
||||||
|
|
||||||
|
use App\Contracts\Search;
|
||||||
|
use App\Models\User;
|
||||||
|
use App\Dto\Builder\User as UserBuilderDto;
|
||||||
|
use App\Services\User\BuilderCommand;
|
||||||
|
use App\Services\Search\CreateSearchInstanceCommand;
|
||||||
|
use Illuminate\Support\Str;
|
||||||
|
use Illuminate\Database\Eloquent\Builder;
|
||||||
|
|
||||||
|
final readonly class UserRepository
|
||||||
|
{
|
||||||
|
public function __construct(
|
||||||
|
private CreateSearchInstanceCommand $createSearchInstanceCommand,
|
||||||
|
private BuilderCommand $builderCommand
|
||||||
|
) { }
|
||||||
|
|
||||||
|
public function getUserById(int $id): ?User
|
||||||
|
{
|
||||||
|
return User::query()->where('id', $id)->first();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getUserByEmail(string $email): ?User
|
||||||
|
{
|
||||||
|
return User::query()->where('email', Str::lower($email))->first();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getUsers(UserBuilderDto $userBuilderDto, array $with = []): Search
|
||||||
|
{
|
||||||
|
$query = $this->builderCommand->execute(
|
||||||
|
query: User::query()->with($with),
|
||||||
|
userBuilderDto: $userBuilderDto
|
||||||
|
);
|
||||||
|
|
||||||
|
return $this->createSearchInstanceCommand->execute($query);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function isExistsEmail(string $email, ?int $exceptId = null): bool
|
||||||
|
{
|
||||||
|
return User::query()
|
||||||
|
->where('email', Str::lower($email))
|
||||||
|
->when($exceptId, function (Builder $query, int $exceptId) {
|
||||||
|
$query->where('id', '!=', $exceptId);
|
||||||
|
})
|
||||||
|
->withTrashed()
|
||||||
|
->exists();
|
||||||
|
}
|
||||||
|
}
|
55
app/src/app/Rules/Permission.php
Normal file
55
app/src/app/Rules/Permission.php
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
<?php declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace App\Rules;
|
||||||
|
|
||||||
|
use Closure;
|
||||||
|
use Illuminate\Contracts\Validation\ValidationRule;
|
||||||
|
|
||||||
|
final readonly class Permission implements ValidationRule
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Run the validation rule.
|
||||||
|
*
|
||||||
|
* @param \Closure(string): \Illuminate\Translation\PotentiallyTranslatedString $fail
|
||||||
|
*/
|
||||||
|
public function validate(string $attribute, mixed $value, Closure $fail): void
|
||||||
|
{
|
||||||
|
if (!is_string($value) && !is_array($value)) {
|
||||||
|
$fail('validation.no_type')->translate(['type' => 'string, array']);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (is_string($value)) {
|
||||||
|
$this->validatePermission($value, $fail);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (is_array($value)) {
|
||||||
|
foreach ($value as $item) {
|
||||||
|
$this->validatePermission($item, $fail);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private function validatePermission(string $value, Closure $fail): void
|
||||||
|
{
|
||||||
|
$value = explode('.', $value, 2);
|
||||||
|
|
||||||
|
if (count($value) !== 2) {
|
||||||
|
$fail('validation.enum')->translate();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
list($permissionEnum, $permission) = $value;
|
||||||
|
$permissionEnum = \App\Enums\Permission::tryFrom($permissionEnum);
|
||||||
|
if (is_null($permissionEnum)) {
|
||||||
|
$fail('validation.enum')->translate();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$permissions = $permissionEnum->getPermissions();
|
||||||
|
if (!isset($permissions[$permission])) {
|
||||||
|
$fail('validation.enum')->translate();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
20
app/src/app/ServiceResults/ServiceResult.php
Normal file
20
app/src/app/ServiceResults/ServiceResult.php
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
<?php
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace App\ServiceResults;
|
||||||
|
|
||||||
|
use App\Contracts\ServiceResult as ServiceResultContract;
|
||||||
|
use App\Contracts\ServiceResultError as ServiceResultErrorContract;
|
||||||
|
|
||||||
|
abstract class ServiceResult implements ServiceResultContract
|
||||||
|
{
|
||||||
|
public function isSuccess(): bool
|
||||||
|
{
|
||||||
|
return $this->isError() === false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function isError(): bool
|
||||||
|
{
|
||||||
|
return $this instanceof ServiceResultErrorContract === true;
|
||||||
|
}
|
||||||
|
}
|
16
app/src/app/ServiceResults/ServiceResultArray.php
Normal file
16
app/src/app/ServiceResults/ServiceResultArray.php
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
<?php
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace App\ServiceResults;
|
||||||
|
|
||||||
|
final class ServiceResultArray extends ServiceResult
|
||||||
|
{
|
||||||
|
public function __construct(
|
||||||
|
private readonly array $data,
|
||||||
|
) { }
|
||||||
|
|
||||||
|
public function getData(): array
|
||||||
|
{
|
||||||
|
return $this->data;
|
||||||
|
}
|
||||||
|
}
|
49
app/src/app/ServiceResults/ServiceResultError.php
Normal file
49
app/src/app/ServiceResults/ServiceResultError.php
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
<?php
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace App\ServiceResults;
|
||||||
|
|
||||||
|
use App\Contracts\ServiceResultError as ServiceResultErrorContract;
|
||||||
|
|
||||||
|
final class ServiceResultError extends ServiceResult implements ServiceResultErrorContract
|
||||||
|
{
|
||||||
|
public function __construct(
|
||||||
|
private readonly string $message,
|
||||||
|
private readonly array $errors = [],
|
||||||
|
private readonly ?int $code = null,
|
||||||
|
) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getMessage(): string
|
||||||
|
{
|
||||||
|
return $this->message;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getCode(): ?int
|
||||||
|
{
|
||||||
|
return $this->code;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getErrors(): array
|
||||||
|
{
|
||||||
|
return $this->errors;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getErrorsOrMessage(): array|string
|
||||||
|
{
|
||||||
|
if (!empty($this->getErrors())) {
|
||||||
|
return $this->getErrors();
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->getMessage();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getData(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'message' => $this->getMessage(),
|
||||||
|
'errors' => $this->errors
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
15
app/src/app/ServiceResults/ServiceResultSuccess.php
Normal file
15
app/src/app/ServiceResults/ServiceResultSuccess.php
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
<?php declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace App\ServiceResults;
|
||||||
|
|
||||||
|
final class ServiceResultSuccess extends ServiceResult
|
||||||
|
{
|
||||||
|
public function __construct(
|
||||||
|
private readonly string $message
|
||||||
|
) { }
|
||||||
|
|
||||||
|
public function getMessage(): string
|
||||||
|
{
|
||||||
|
return $this->message;
|
||||||
|
}
|
||||||
|
}
|
23
app/src/app/ServiceResults/StoreUpdateResult.php
Normal file
23
app/src/app/ServiceResults/StoreUpdateResult.php
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
<?php declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace App\ServiceResults;
|
||||||
|
|
||||||
|
use Illuminate\Database\Eloquent\Model;
|
||||||
|
|
||||||
|
final class StoreUpdateResult extends ServiceResult
|
||||||
|
{
|
||||||
|
public function __construct(
|
||||||
|
private readonly Model $model,
|
||||||
|
private readonly string $message
|
||||||
|
) { }
|
||||||
|
|
||||||
|
public function getModel(): Model
|
||||||
|
{
|
||||||
|
return $this->model;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getMessage(): string
|
||||||
|
{
|
||||||
|
return $this->message;
|
||||||
|
}
|
||||||
|
}
|
165
app/src/app/Services/Admin/RoleService.php
Normal file
165
app/src/app/Services/Admin/RoleService.php
Normal file
@ -0,0 +1,165 @@
|
|||||||
|
<?php declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace App\Services\Admin;
|
||||||
|
|
||||||
|
use App\Dto\Builder\Role as RoleBuilderDto;
|
||||||
|
use App\Dto\QuerySettingsDto;
|
||||||
|
use App\Dto\Service\Admin\Role\StoreUpdate;
|
||||||
|
use App\Models\Role;
|
||||||
|
use App\Models\User;
|
||||||
|
use App\Repositories\RoleRepository;
|
||||||
|
use App\ServiceResults\ServiceResultArray;
|
||||||
|
use App\ServiceResults\ServiceResultError;
|
||||||
|
use App\ServiceResults\ServiceResultSuccess;
|
||||||
|
use App\ServiceResults\StoreUpdateResult;
|
||||||
|
use App\Services\Role\RoleCommandHandler;
|
||||||
|
use App\Services\Role\RoleSyncPermissionsCommandHandler;
|
||||||
|
use App\Services\Service;
|
||||||
|
use Illuminate\Support\Facades\DB;
|
||||||
|
|
||||||
|
final class RoleService extends Service
|
||||||
|
{
|
||||||
|
public function __construct(
|
||||||
|
private readonly RoleRepository $roleRepository,
|
||||||
|
private readonly RoleCommandHandler $roleCommandHandler,
|
||||||
|
private readonly RoleSyncPermissionsCommandHandler $roleSyncPermissionsCommandHandler
|
||||||
|
) { }
|
||||||
|
|
||||||
|
public function index(RoleBuilderDto $roleBuilderDto, QuerySettingsDto $querySettingsDto, User $user): ServiceResultError | ServiceResultArray
|
||||||
|
{
|
||||||
|
if ($user->cannot('viewAny', Role::class)) {
|
||||||
|
return $this->errFobidden(__('Access is denied'));
|
||||||
|
}
|
||||||
|
|
||||||
|
$roles = $this->roleRepository->getRoles(
|
||||||
|
$roleBuilderDto,
|
||||||
|
$querySettingsDto->getQueryWith()
|
||||||
|
)->pagination(
|
||||||
|
$querySettingsDto->getLimit(),
|
||||||
|
$querySettingsDto->getPage()
|
||||||
|
);
|
||||||
|
|
||||||
|
return $this->result([
|
||||||
|
'roles' => $roles
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function create(User $user): ServiceResultError | ServiceResultArray
|
||||||
|
{
|
||||||
|
if ($user->cannot('create', Role::class)) {
|
||||||
|
return $this->errFobidden(__('Access is denied'));
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->result([
|
||||||
|
'role' => new Role(),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function edit(int $id, User $user): ServiceResultError | ServiceResultArray
|
||||||
|
{
|
||||||
|
$role = $this->roleRepository->getRoleById($id);
|
||||||
|
|
||||||
|
if (is_null($role)) {
|
||||||
|
return $this->errNotFound(__('Not Found'));
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($user->cannot('view', $role)) {
|
||||||
|
return $this->errFobidden(__('Access is denied'));
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->result([
|
||||||
|
'role' => $role,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function store(StoreUpdate $data, User $user): ServiceResultError | StoreUpdateResult
|
||||||
|
{
|
||||||
|
if ($user->cannot('create', Role::class)) {
|
||||||
|
return $this->errFobidden(__('Access is denied'));
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($this->roleRepository->isExistsCode($data->getCode())) {
|
||||||
|
return $this->errValidate(
|
||||||
|
__('validation.unique', ['attribute' => __('validation.attributes.code')]),
|
||||||
|
['code' => __('validation.unique', ['attribute' => __('validation.attributes.code')])]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
$role = DB::transaction(function () use ($data) {
|
||||||
|
$dataRole = $this->getDataRole($data);
|
||||||
|
$dataRole['code'] = $data->getCode();
|
||||||
|
|
||||||
|
$role = $this->roleCommandHandler->handleStore($dataRole);
|
||||||
|
$role = $this->roleSyncPermissionsCommandHandler->handle($role, $data->getPermissions());
|
||||||
|
|
||||||
|
return $role;
|
||||||
|
});
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
report($e);
|
||||||
|
return $this->errService(__('Server Error'));
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->resultStoreUpdateModel($role, __('The group was successfully created'));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function update(int $id, StoreUpdate $data, User $user): ServiceResultError | StoreUpdateResult
|
||||||
|
{
|
||||||
|
$role = $this->roleRepository->getRoleById($id);
|
||||||
|
|
||||||
|
if (is_null($role)) {
|
||||||
|
return $this->errNotFound(__('Not Found'));
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($user->cannot('update', $role)) {
|
||||||
|
return $this->errFobidden(__('Access is denied'));
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
$role = DB::transaction(function () use ($data, $role) {
|
||||||
|
$dataRole = $this->getDataRole($data);
|
||||||
|
|
||||||
|
$role = $this->roleCommandHandler->handleUpdate($role, $dataRole);
|
||||||
|
$role = $this->roleSyncPermissionsCommandHandler->handle($role, $data->getPermissions());
|
||||||
|
|
||||||
|
return $role;
|
||||||
|
});
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
report($e);
|
||||||
|
return $this->errService(__('Server Error'));
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->resultStoreUpdateModel($role, __('The group was successfully updated'));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function destroy(int $id, User $user): ServiceResultError|ServiceResultSuccess
|
||||||
|
{
|
||||||
|
$role = $this->roleRepository->getRoleById($id);
|
||||||
|
|
||||||
|
if (is_null($role)) {
|
||||||
|
return $this->errNotFound(__('Not Found'));
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($user->cannot('delete', $role)) {
|
||||||
|
return $this->errFobidden(__('Access is denied'));
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
DB::transaction(function () use ($role) {
|
||||||
|
$this->roleCommandHandler->handleDestroy($role);
|
||||||
|
});
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
report($e);
|
||||||
|
return $this->errService(__('Server Error'));
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->ok(__('The group has been deleted'));
|
||||||
|
}
|
||||||
|
|
||||||
|
private function getDataRole(StoreUpdate $data): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'name' => $data->getName(),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
200
app/src/app/Services/Admin/UserService.php
Normal file
200
app/src/app/Services/Admin/UserService.php
Normal file
@ -0,0 +1,200 @@
|
|||||||
|
<?php declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace App\Services\Admin;
|
||||||
|
|
||||||
|
use App\Dto\Builder\User as UserBuilderDto;
|
||||||
|
use App\Dto\Service\Admin\User\StoreUpdate;
|
||||||
|
use App\Dto\Service\User\UpdatePassword;
|
||||||
|
use App\Helpers\Helpers;
|
||||||
|
use App\Models\User;
|
||||||
|
use App\Dto\QuerySettingsDto;
|
||||||
|
use App\Repositories\RoleRepository;
|
||||||
|
use App\Repositories\UserRepository;
|
||||||
|
use App\ServiceResults\ServiceResultArray;
|
||||||
|
use App\ServiceResults\ServiceResultError;
|
||||||
|
use App\ServiceResults\ServiceResultSuccess;
|
||||||
|
use App\ServiceResults\StoreUpdateResult;
|
||||||
|
use App\Services\Service;
|
||||||
|
use App\Services\User\UserCommandHandler;
|
||||||
|
use Illuminate\Support\Facades\DB;
|
||||||
|
|
||||||
|
final class UserService extends Service
|
||||||
|
{
|
||||||
|
public function __construct(
|
||||||
|
private readonly UserRepository $userRepository,
|
||||||
|
private readonly UserCommandHandler $userCommandHandler,
|
||||||
|
private readonly RoleRepository $roleRepository
|
||||||
|
) { }
|
||||||
|
|
||||||
|
public function index(UserBuilderDto $userBuilderDto, QuerySettingsDto $querySettingsDto, User $user): ServiceResultError | ServiceResultArray
|
||||||
|
{
|
||||||
|
if ($user->cannot('viewAny', User::class)) {
|
||||||
|
return $this->errFobidden(__('Access is denied'));
|
||||||
|
}
|
||||||
|
|
||||||
|
$users = $this->userRepository->getUsers(
|
||||||
|
$userBuilderDto,
|
||||||
|
$querySettingsDto->getQueryWith()
|
||||||
|
)->pagination(
|
||||||
|
$querySettingsDto->getLimit(),
|
||||||
|
$querySettingsDto->getPage()
|
||||||
|
);
|
||||||
|
|
||||||
|
return $this->result([
|
||||||
|
'users' => $users,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function create(User $user): ServiceResultError | ServiceResultArray
|
||||||
|
{
|
||||||
|
if ($user->cannot('create', User::class)) {
|
||||||
|
return $this->errFobidden(__('Access is denied'));
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->result([
|
||||||
|
'user' => new User(),
|
||||||
|
'roles' => $this->roleRepository->getRolesForSelect(),
|
||||||
|
'userRoles' => [],
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function edit(int $id, User $user): ServiceResultError | ServiceResultArray
|
||||||
|
{
|
||||||
|
$modelUser = $this->userRepository->getUserById($id);
|
||||||
|
|
||||||
|
if (is_null($modelUser)) {
|
||||||
|
return $this->errNotFound(__('Not Found'));
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($user->cannot('view', $modelUser)) {
|
||||||
|
return $this->errFobidden(__('Access is denied'));
|
||||||
|
}
|
||||||
|
|
||||||
|
$userRoles = $modelUser->roles()->withTrashed()->pluck('id')->toArray();
|
||||||
|
return $this->result([
|
||||||
|
'user' => $modelUser,
|
||||||
|
'roles' => $this->roleRepository->getRolesForSelect($userRoles),
|
||||||
|
'userRoles' => $userRoles,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function store(StoreUpdate $data, User $user): ServiceResultError | StoreUpdateResult
|
||||||
|
{
|
||||||
|
if ($user->cannot('create', User::class)) {
|
||||||
|
return $this->errFobidden(__('Access is denied'));
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($this->userRepository->isExistsEmail($data->getEmail())) {
|
||||||
|
return $this->errValidate(
|
||||||
|
__('validation.unique', ['attribute' => __('validation.attributes.email')]),
|
||||||
|
['code' => __('validation.unique', ['attribute' => __('validation.attributes.email')])]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
$modelUser = DB::transaction(function () use ($data) {
|
||||||
|
$dataUser = $this->getDataUser($data);
|
||||||
|
|
||||||
|
$modelUser = $this->userCommandHandler->handleStore($dataUser, $data->getPassword());
|
||||||
|
$this->userCommandHandler->handleSyncRoles($modelUser, $data->getRoles());
|
||||||
|
|
||||||
|
return $modelUser;
|
||||||
|
});
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
report($e);
|
||||||
|
return $this->errService(__('Server Error'));
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->resultStoreUpdateModel($modelUser, __('The user was successfully created'));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function update(int $id, StoreUpdate $data, User $user): ServiceResultError | StoreUpdateResult
|
||||||
|
{
|
||||||
|
$modelUser = $this->userRepository->getUserById($id);
|
||||||
|
|
||||||
|
if (is_null($modelUser)) {
|
||||||
|
return $this->errNotFound(__('Not Found'));
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($user->cannot('update', $modelUser)) {
|
||||||
|
return $this->errFobidden(__('Access is denied'));
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($this->userRepository->isExistsEmail($data->getEmail(), $modelUser->id)) {
|
||||||
|
return $this->errValidate(
|
||||||
|
__('validation.unique', ['attribute' => __('validation.attributes.email')]),
|
||||||
|
['code' => __('validation.unique', ['attribute' => __('validation.attributes.email')])]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
$modelUser = DB::transaction(function () use ($data, $modelUser) {
|
||||||
|
$dataUser = $this->getDataUser($data);
|
||||||
|
|
||||||
|
$modelUser = $this->userCommandHandler->handleUpdate($modelUser, $dataUser);
|
||||||
|
$this->userCommandHandler->handleSyncRoles($modelUser, $data->getRoles());
|
||||||
|
|
||||||
|
return $modelUser;
|
||||||
|
});
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
report($e);
|
||||||
|
return $this->errService(__('Server Error'));
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->resultStoreUpdateModel($modelUser, __('The user was successfully updated'));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function updatePassword(int $id, UpdatePassword $data, User $user): ServiceResultError | StoreUpdateResult
|
||||||
|
{
|
||||||
|
$modelUser = $this->userRepository->getUserById($id);
|
||||||
|
|
||||||
|
if (is_null($modelUser)) {
|
||||||
|
return $this->errNotFound(__('Not Found'));
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($user->cannot('update', $modelUser)) {
|
||||||
|
return $this->errFobidden(__('Access is denied'));
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
$this->userCommandHandler->handleUpdatePassword($modelUser, $data->getPassword());
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
report($e->getMessage());
|
||||||
|
return $this->errService($e->getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->resultStoreUpdateModel($modelUser, __('The password has been changed'));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function destroy(int $id, User $user): ServiceResultError | ServiceResultSuccess
|
||||||
|
{
|
||||||
|
$modelUser = $this->userRepository->getUserById($id);
|
||||||
|
|
||||||
|
if (is_null($modelUser)) {
|
||||||
|
return $this->errNotFound(__('Not Found'));
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($user->cannot('delete', $modelUser)) {
|
||||||
|
return $this->errFobidden(__('Access is denied'));
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
DB::transaction(function () use ($modelUser) {
|
||||||
|
$this->userCommandHandler->handleDestroy($modelUser);
|
||||||
|
});
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
report($e);
|
||||||
|
return $this->errService(__('Server Error'));
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->ok(__('The user has been deleted'));
|
||||||
|
}
|
||||||
|
|
||||||
|
private function getDataUser(StoreUpdate $data): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'name' => $data->getName(),
|
||||||
|
'email' => $data->getEmail(),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
37
app/src/app/Services/AuthService.php
Normal file
37
app/src/app/Services/AuthService.php
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
<?php declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace App\Services;
|
||||||
|
|
||||||
|
use App\Dto\Service\Authorization;
|
||||||
|
use App\Repositories\UserRepository;
|
||||||
|
use App\ServiceResults\ServiceResultError;
|
||||||
|
use App\ServiceResults\ServiceResultSuccess;
|
||||||
|
use Illuminate\Support\Facades\Auth;
|
||||||
|
use Illuminate\Support\Facades\Hash;
|
||||||
|
|
||||||
|
final class AuthService extends Service
|
||||||
|
{
|
||||||
|
public function __construct(
|
||||||
|
private readonly UserRepository $userRepository
|
||||||
|
) { }
|
||||||
|
|
||||||
|
public function authorization(Authorization $authorization): ServiceResultError | ServiceResultSuccess
|
||||||
|
{
|
||||||
|
$user = $this->userRepository->getUserByEmail($authorization->getEmail());
|
||||||
|
if (is_null($user)) {
|
||||||
|
return $this->errUnauthorized(__('auth.failed'));
|
||||||
|
}
|
||||||
|
if (Hash::check($authorization->getPassword(), $user->password) !== true) {
|
||||||
|
return $this->errUnauthorized(__('auth.password'));
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
Auth::login($user, $authorization->getRemember());
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
report($e);
|
||||||
|
return $this->errService(__('Server Error'));
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->ok(__('auth.success'));
|
||||||
|
}
|
||||||
|
}
|
59
app/src/app/Services/Private/ProfileService.php
Normal file
59
app/src/app/Services/Private/ProfileService.php
Normal file
@ -0,0 +1,59 @@
|
|||||||
|
<?php declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace App\Services\Private;
|
||||||
|
|
||||||
|
use App\Dto\Service\Private\Profile\Update;
|
||||||
|
use App\Dto\Service\Private\Profile\UpdateSettings;
|
||||||
|
use App\Dto\Service\User\UpdatePassword;
|
||||||
|
use App\Models\User;
|
||||||
|
use App\ServiceResults\ServiceResultError;
|
||||||
|
use App\ServiceResults\ServiceResultSuccess;
|
||||||
|
use App\Services\Service;
|
||||||
|
use App\Services\User\UserCommandHandler;
|
||||||
|
|
||||||
|
final class ProfileService extends Service
|
||||||
|
{
|
||||||
|
public function __construct(
|
||||||
|
private readonly UserCommandHandler $userCommandHandler
|
||||||
|
) { }
|
||||||
|
|
||||||
|
public function update(Update $update, User $user): ServiceResultError | ServiceResultSuccess
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
$data = [
|
||||||
|
'name' => $update->getName()
|
||||||
|
];
|
||||||
|
$this->userCommandHandler->handleUpdate($user, $data);
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
report($e->getMessage());
|
||||||
|
return $this->errService($e->getMessage());
|
||||||
|
}
|
||||||
|
return $this->ok(__('Profile saved successfully'));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function updatePassword(UpdatePassword $update, User $user): ServiceResultError | ServiceResultSuccess
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
$this->userCommandHandler->handleUpdatePassword($user, $update->getPassword());
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
report($e->getMessage());
|
||||||
|
return $this->errService($e->getMessage());
|
||||||
|
}
|
||||||
|
return $this->ok(__('The password has been changed'));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function updateSettings(UpdateSettings $update, User $user): ServiceResultError | ServiceResultSuccess
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
$data = [
|
||||||
|
'lang' => $update->getLang(),
|
||||||
|
'timezone' => $update->getTimezone(),
|
||||||
|
];
|
||||||
|
$this->userCommandHandler->handleUpdate($user, $data);
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
report($e->getMessage());
|
||||||
|
return $this->errService($e->getMessage());
|
||||||
|
}
|
||||||
|
return $this->ok(__('The settings have been saved'));
|
||||||
|
}
|
||||||
|
}
|
15
app/src/app/Services/Role/BuilderCommand.php
Normal file
15
app/src/app/Services/Role/BuilderCommand.php
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
<?php declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace App\Services\Role;
|
||||||
|
|
||||||
|
use App\Dto\Builder\Role as RoleBuilderDto;
|
||||||
|
use Illuminate\Database\Eloquent\Builder;
|
||||||
|
use Illuminate\Database\Eloquent\Relations\Relation;
|
||||||
|
|
||||||
|
final readonly class BuilderCommand
|
||||||
|
{
|
||||||
|
public function execute(Relation | Builder $query, RoleBuilderDto $roleBuilderDto): Relation | Builder
|
||||||
|
{
|
||||||
|
return $query;
|
||||||
|
}
|
||||||
|
}
|
26
app/src/app/Services/Role/RoleCommandHandler.php
Normal file
26
app/src/app/Services/Role/RoleCommandHandler.php
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
<?php declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace App\Services\Role;
|
||||||
|
|
||||||
|
use App\Models\Role;
|
||||||
|
|
||||||
|
final readonly class RoleCommandHandler
|
||||||
|
{
|
||||||
|
public function handleStore(array $data): Role
|
||||||
|
{
|
||||||
|
return Role::create($data);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function handleUpdate(Role $role, array $data): Role
|
||||||
|
{
|
||||||
|
$role->update($data);
|
||||||
|
$role->touch();
|
||||||
|
|
||||||
|
return $role;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function handleDestroy(Role $role): void
|
||||||
|
{
|
||||||
|
$role->delete();
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,57 @@
|
|||||||
|
<?php declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace App\Services\Role;
|
||||||
|
|
||||||
|
use App\Enums\Permission;
|
||||||
|
use App\Exceptions\Rule\RoleSyncPermissionsCommandHandlerException;
|
||||||
|
use App\Models\Role;
|
||||||
|
use App\Models\RolePermission;
|
||||||
|
|
||||||
|
final readonly class RoleSyncPermissionsCommandHandler
|
||||||
|
{
|
||||||
|
public function handle(Role $role, array $dataPermissions): Role
|
||||||
|
{
|
||||||
|
$rolePermissions = $role->permissions->pluck('id', 'permission')->toArray();
|
||||||
|
|
||||||
|
$data = $this->getInsertDeleteData($role->id, $rolePermissions, $dataPermissions);
|
||||||
|
|
||||||
|
if (!empty($data['insert'])) {
|
||||||
|
RolePermission::query()->insert($data['insert']);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!empty($data['delete'])) {
|
||||||
|
RolePermission::query()->whereIn('id', $data['delete'])->delete();
|
||||||
|
}
|
||||||
|
|
||||||
|
return $role;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function getInsertDeleteData(int $roleId, array $rolePermissions, array $permissionsData): array
|
||||||
|
{
|
||||||
|
$data = [
|
||||||
|
'insert' => [],
|
||||||
|
'delete' => []
|
||||||
|
];
|
||||||
|
|
||||||
|
$permissions = Permission::toArrayListCodes();
|
||||||
|
foreach ($permissionsData as $permission) {
|
||||||
|
if (array_search($permission, $permissions) === false) {
|
||||||
|
throw new RoleSyncPermissionsCommandHandlerException('Таких разрешений в системе нет: ' . $permission);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isset($rolePermissions[$permission])) {
|
||||||
|
unset($rolePermissions[$permission]);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$data['insert'][] = [
|
||||||
|
'role_id' => $roleId,
|
||||||
|
'permission' => $permission,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
$data['delete'] = array_values($rolePermissions);
|
||||||
|
|
||||||
|
return $data;
|
||||||
|
}
|
||||||
|
}
|
19
app/src/app/Services/Search/CreateSearchInstanceCommand.php
Normal file
19
app/src/app/Services/Search/CreateSearchInstanceCommand.php
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
<?php declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace App\Services\Search;
|
||||||
|
|
||||||
|
use App\Contracts\Search;
|
||||||
|
use Illuminate\Database\Eloquent\Builder;
|
||||||
|
use Illuminate\Database\Eloquent\Relations\Relation;
|
||||||
|
|
||||||
|
final readonly class CreateSearchInstanceCommand
|
||||||
|
{
|
||||||
|
public function __construct(
|
||||||
|
private string $abstract
|
||||||
|
) { }
|
||||||
|
|
||||||
|
public function execute(Relation | Builder $query): Search
|
||||||
|
{
|
||||||
|
return new $this->abstract($query);
|
||||||
|
}
|
||||||
|
}
|
80
app/src/app/Services/Search/Search.php
Normal file
80
app/src/app/Services/Search/Search.php
Normal file
@ -0,0 +1,80 @@
|
|||||||
|
<?php declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace App\Services\Search;
|
||||||
|
|
||||||
|
use App\Models\Role;
|
||||||
|
use Illuminate\Container\Container;
|
||||||
|
use Illuminate\Database\Eloquent\Builder;
|
||||||
|
use Illuminate\Database\Eloquent\Relations\Relation;
|
||||||
|
use Illuminate\Pagination\CursorPaginator;
|
||||||
|
use Illuminate\Pagination\LengthAwarePaginator;
|
||||||
|
use Illuminate\Pagination\Paginator;
|
||||||
|
use Illuminate\Support\Collection;
|
||||||
|
use App\Contracts\Search as SearchContract;
|
||||||
|
|
||||||
|
final readonly class Search implements SearchContract
|
||||||
|
{
|
||||||
|
public function __construct(
|
||||||
|
private Relation|Builder $query
|
||||||
|
) { }
|
||||||
|
|
||||||
|
public function all(): Collection
|
||||||
|
{
|
||||||
|
return $this->query->get();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function get(int $limit): Collection
|
||||||
|
{
|
||||||
|
return $this->query->limit($limit)->get();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function pagination(int $limit, int $page = 1): LengthAwarePaginator
|
||||||
|
{
|
||||||
|
if ($page > 100) {
|
||||||
|
return $this->paginationPerfomance($limit, $page);
|
||||||
|
}
|
||||||
|
return $this->query->paginate($limit, page: $page)->withQueryString();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function cursorPaginate(int $limit): CursorPaginator
|
||||||
|
{
|
||||||
|
return $this->query->cursorPaginate($limit);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function paginationPerfomance(int $limit, int $page = 1): LengthAwarePaginator
|
||||||
|
{
|
||||||
|
$total = $this->query->clone()->count();
|
||||||
|
$options = [
|
||||||
|
'path' => Paginator::resolveCurrentPath(),
|
||||||
|
'pageName' => 'page',
|
||||||
|
];
|
||||||
|
|
||||||
|
$result = collect();
|
||||||
|
if ($total > 0) {
|
||||||
|
$result = $this->subQuery($limit, $page);
|
||||||
|
}
|
||||||
|
|
||||||
|
$pagination = Container::getInstance()->makeWith(LengthAwarePaginator::class, [
|
||||||
|
'items' => $result,
|
||||||
|
'total' => $total,
|
||||||
|
'perPage' => $limit,
|
||||||
|
'currentPage' => $page,
|
||||||
|
'options' => $options
|
||||||
|
]);
|
||||||
|
|
||||||
|
return $pagination->withQueryString();
|
||||||
|
}
|
||||||
|
|
||||||
|
private function subQuery(int $limit, int $page): Collection
|
||||||
|
{
|
||||||
|
$table = $this->query->getModel()->getTable();
|
||||||
|
return $this->query->getModel()::query()
|
||||||
|
->select($table.'.*')
|
||||||
|
->with($this->query->getEagerLoads())
|
||||||
|
->from(
|
||||||
|
clone $this->query->select('id')->forPage($page, $limit),
|
||||||
|
'q'
|
||||||
|
)->join($table.' as '.$table, $table.'.id', '=', 'q.id')
|
||||||
|
->get();
|
||||||
|
}
|
||||||
|
}
|
67
app/src/app/Services/Service.php
Normal file
67
app/src/app/Services/Service.php
Normal file
@ -0,0 +1,67 @@
|
|||||||
|
<?php declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace App\Services;
|
||||||
|
|
||||||
|
use App\ServiceResults\ServiceResultArray;
|
||||||
|
use App\ServiceResults\ServiceResultError;
|
||||||
|
use App\ServiceResults\ServiceResultSuccess;
|
||||||
|
use App\ServiceResults\StoreUpdateResult;
|
||||||
|
use Illuminate\Database\Eloquent\Model;
|
||||||
|
use Illuminate\Http\Response;
|
||||||
|
|
||||||
|
abstract class Service
|
||||||
|
{
|
||||||
|
final protected function errValidate(string $message, array $errors = []): ServiceResultError
|
||||||
|
{
|
||||||
|
return $this->error(Response::HTTP_UNPROCESSABLE_ENTITY, $message, $errors);
|
||||||
|
}
|
||||||
|
|
||||||
|
final protected function errFobidden(string $message): ServiceResultError
|
||||||
|
{
|
||||||
|
return $this->error(Response::HTTP_FORBIDDEN, $message);
|
||||||
|
}
|
||||||
|
|
||||||
|
final protected function errNotFound(string $message): ServiceResultError
|
||||||
|
{
|
||||||
|
return $this->error(Response::HTTP_NOT_FOUND, $message);
|
||||||
|
}
|
||||||
|
|
||||||
|
final protected function errService(string $message): ServiceResultError
|
||||||
|
{
|
||||||
|
return $this->error(Response::HTTP_INTERNAL_SERVER_ERROR, $message);
|
||||||
|
}
|
||||||
|
|
||||||
|
final protected function notAcceptable(string $message): ServiceResultError
|
||||||
|
{
|
||||||
|
return $this->error(Response::HTTP_NOT_ACCEPTABLE, $message);
|
||||||
|
}
|
||||||
|
|
||||||
|
final protected function errUnauthorized(string $message): ServiceResultError
|
||||||
|
{
|
||||||
|
return $this->error(Response::HTTP_UNAUTHORIZED, $message);
|
||||||
|
}
|
||||||
|
|
||||||
|
final protected function ok(string $message = 'OK'): ServiceResultSuccess
|
||||||
|
{
|
||||||
|
return new ServiceResultSuccess($message);
|
||||||
|
}
|
||||||
|
|
||||||
|
final protected function resultStoreUpdateModel(Model $model, string $message = 'OK'): StoreUpdateResult
|
||||||
|
{
|
||||||
|
return new StoreUpdateResult($model, $message);
|
||||||
|
}
|
||||||
|
|
||||||
|
final protected function result(array $data = []): ServiceResultArray
|
||||||
|
{
|
||||||
|
return new ServiceResultArray(data: $data);
|
||||||
|
}
|
||||||
|
|
||||||
|
final protected function error(int $code, string $message, array $errors = []): ServiceResultError
|
||||||
|
{
|
||||||
|
return new ServiceResultError(
|
||||||
|
message: $message,
|
||||||
|
errors: $errors,
|
||||||
|
code: $code
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
15
app/src/app/Services/User/BuilderCommand.php
Normal file
15
app/src/app/Services/User/BuilderCommand.php
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
<?php declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace App\Services\User;
|
||||||
|
|
||||||
|
use App\Dto\Builder\User as UserBuilderDto;
|
||||||
|
use Illuminate\Database\Eloquent\Builder;
|
||||||
|
use Illuminate\Database\Eloquent\Relations\Relation;
|
||||||
|
|
||||||
|
final readonly class BuilderCommand
|
||||||
|
{
|
||||||
|
public function execute(Relation | Builder $query, UserBuilderDto $userBuilderDto): Relation | Builder
|
||||||
|
{
|
||||||
|
return $query;
|
||||||
|
}
|
||||||
|
}
|
62
app/src/app/Services/User/UserCommandHandler.php
Normal file
62
app/src/app/Services/User/UserCommandHandler.php
Normal file
@ -0,0 +1,62 @@
|
|||||||
|
<?php declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace App\Services\User;
|
||||||
|
|
||||||
|
use App\Dto\User\ManyRoleDto;
|
||||||
|
use App\Models\User;
|
||||||
|
use Illuminate\Support\Carbon;
|
||||||
|
use Illuminate\Support\Facades\Hash;
|
||||||
|
use Illuminate\Support\Str;
|
||||||
|
|
||||||
|
final readonly class UserCommandHandler
|
||||||
|
{
|
||||||
|
public function handleStore(array $data, int|string $password): User
|
||||||
|
{
|
||||||
|
$data['email'] = Str::lower($data['email']);
|
||||||
|
$data['password'] = $this->hashPassword($password);
|
||||||
|
$user = User::create($data);
|
||||||
|
|
||||||
|
return $user;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function handleUpdate(User $user, array $data): User
|
||||||
|
{
|
||||||
|
if (isset($data['email'])) {
|
||||||
|
$data['email'] = Str::lower($data['email']);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isset($data['password'])) {
|
||||||
|
unset($data['password']);
|
||||||
|
}
|
||||||
|
|
||||||
|
$user->update($data);
|
||||||
|
$user->touch();
|
||||||
|
|
||||||
|
return $user;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function handleConfirmationByEmail(User $user): void
|
||||||
|
{
|
||||||
|
$user->update(['email_verified_at' => new Carbon('NOW')]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function handleUpdatePassword(User $user, int|string $password): void
|
||||||
|
{
|
||||||
|
$user->update(['password' => $this->hashPassword($password)]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function handleSyncRoles(User $user, ManyRoleDto $roles): void
|
||||||
|
{
|
||||||
|
$user->roles()->sync($roles->toArray());
|
||||||
|
}
|
||||||
|
|
||||||
|
private function hashPassword(int|string $password): string
|
||||||
|
{
|
||||||
|
return Hash::make($password);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function handleDestroy(User $user): void
|
||||||
|
{
|
||||||
|
$user->delete();
|
||||||
|
}
|
||||||
|
}
|
13
app/src/app/View/Components/Admin/Layout.php
Normal file
13
app/src/app/View/Components/Admin/Layout.php
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
<?php declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace App\View\Components\Admin;
|
||||||
|
|
||||||
|
use App\View\Components\PrivateLayout;
|
||||||
|
|
||||||
|
final class Layout extends PrivateLayout
|
||||||
|
{
|
||||||
|
protected function getNavigation(): string
|
||||||
|
{
|
||||||
|
return 'admin.layout._navigation';
|
||||||
|
}
|
||||||
|
}
|
14
app/src/app/View/Components/AuthLayout.php
Normal file
14
app/src/app/View/Components/AuthLayout.php
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
<?php declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace App\View\Components;
|
||||||
|
|
||||||
|
use Illuminate\View\Component;
|
||||||
|
use Illuminate\View\View;
|
||||||
|
|
||||||
|
final class AuthLayout extends Component
|
||||||
|
{
|
||||||
|
public function render(): View
|
||||||
|
{
|
||||||
|
return view('layout.auth');
|
||||||
|
}
|
||||||
|
}
|
13
app/src/app/View/Components/Private/Layout.php
Normal file
13
app/src/app/View/Components/Private/Layout.php
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
<?php declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace App\View\Components\Private;
|
||||||
|
|
||||||
|
use App\View\Components\PrivateLayout;
|
||||||
|
|
||||||
|
final class Layout extends PrivateLayout
|
||||||
|
{
|
||||||
|
protected function getNavigation(): string
|
||||||
|
{
|
||||||
|
return 'private.layout._navigation';
|
||||||
|
}
|
||||||
|
}
|
18
app/src/app/View/Components/PrivateLayout.php
Normal file
18
app/src/app/View/Components/PrivateLayout.php
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
<?php declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace App\View\Components;
|
||||||
|
|
||||||
|
use Illuminate\View\Component;
|
||||||
|
use Illuminate\View\View;
|
||||||
|
|
||||||
|
abstract class PrivateLayout extends Component
|
||||||
|
{
|
||||||
|
abstract protected function getNavigation(): string;
|
||||||
|
|
||||||
|
public function render(): View
|
||||||
|
{
|
||||||
|
return view('layout.private', [
|
||||||
|
'navigation' => $this->getNavigation(),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
57
app/src/app/View/Components/Volt/Forms/Checkbox.php
Normal file
57
app/src/app/View/Components/Volt/Forms/Checkbox.php
Normal file
@ -0,0 +1,57 @@
|
|||||||
|
<?php declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace App\View\Components\Volt\Forms;
|
||||||
|
|
||||||
|
use Illuminate\Support\Str;
|
||||||
|
use Illuminate\View\View;
|
||||||
|
|
||||||
|
final class Checkbox extends Form
|
||||||
|
{
|
||||||
|
public function __construct(
|
||||||
|
private readonly string $title,
|
||||||
|
private readonly string $name,
|
||||||
|
private readonly string $checkboxValue,
|
||||||
|
private readonly ?string $userValue = '',
|
||||||
|
private readonly ?string $notCheckedValue = null
|
||||||
|
) { }
|
||||||
|
|
||||||
|
protected function getName(): string
|
||||||
|
{
|
||||||
|
return $this->name;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function getTitle(): string
|
||||||
|
{
|
||||||
|
return Str::ucfirst($this->title);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function getCheckboxValue(): string
|
||||||
|
{
|
||||||
|
return (string) old($this->getRequestName(), $this->checkboxValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getUserValue(): string
|
||||||
|
{
|
||||||
|
return (string) $this->userValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getNotCheckedValue(): ?string
|
||||||
|
{
|
||||||
|
return $this->notCheckedValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @inheritDoc
|
||||||
|
*/
|
||||||
|
public function render(): View
|
||||||
|
{
|
||||||
|
return view('components.volt.forms.checkbox', [
|
||||||
|
'title' => $this->getTitle(),
|
||||||
|
'name' => $this->getName(),
|
||||||
|
'requestName' => $this->getRequestName(),
|
||||||
|
'checkboxValue' => $this->getCheckboxValue(),
|
||||||
|
'userValue' => $this->getUserValue(),
|
||||||
|
'notCheckedValue' => $this->getNotCheckedValue(),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
32
app/src/app/View/Components/Volt/Forms/Form.php
Normal file
32
app/src/app/View/Components/Volt/Forms/Form.php
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
<?php declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace App\View\Components\Volt\Forms;
|
||||||
|
|
||||||
|
use Illuminate\Support\Str;
|
||||||
|
use Illuminate\View\Component;
|
||||||
|
use Illuminate\View\View;
|
||||||
|
|
||||||
|
abstract class Form extends Component
|
||||||
|
{
|
||||||
|
private ?string $requestName = null;
|
||||||
|
|
||||||
|
abstract protected function getName(): string;
|
||||||
|
abstract public function render(): View;
|
||||||
|
|
||||||
|
protected function getRequestName(): string
|
||||||
|
{
|
||||||
|
if (!is_null($this->requestName)) {
|
||||||
|
return $this->requestName;
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->requestName = Str::of($this->getName())
|
||||||
|
->replace(
|
||||||
|
['.', '[', ']'],
|
||||||
|
['_', '.', ''],
|
||||||
|
)
|
||||||
|
->rtrim('.')
|
||||||
|
->value();
|
||||||
|
|
||||||
|
return $this->requestName;
|
||||||
|
}
|
||||||
|
}
|
50
app/src/app/View/Components/Volt/Forms/Input.php
Normal file
50
app/src/app/View/Components/Volt/Forms/Input.php
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
<?php declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace App\View\Components\Volt\Forms;
|
||||||
|
|
||||||
|
use Illuminate\Support\Str;
|
||||||
|
use Illuminate\View\View;
|
||||||
|
|
||||||
|
final class Input extends Form
|
||||||
|
{
|
||||||
|
public function __construct(
|
||||||
|
private readonly string $title,
|
||||||
|
private readonly string $name,
|
||||||
|
private readonly string $type = 'text',
|
||||||
|
private readonly ?string $value = ''
|
||||||
|
) { }
|
||||||
|
|
||||||
|
protected function getName(): string
|
||||||
|
{
|
||||||
|
return $this->name;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function getTitle(): string
|
||||||
|
{
|
||||||
|
return Str::ucfirst($this->title);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function getType(): string
|
||||||
|
{
|
||||||
|
return $this->type;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function getValue(): string
|
||||||
|
{
|
||||||
|
return (string) old($this->getRequestName(), $this->value);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @inheritDoc
|
||||||
|
*/
|
||||||
|
public function render(): View
|
||||||
|
{
|
||||||
|
return view('components.volt.forms.input', [
|
||||||
|
'title' => $this->getTitle(),
|
||||||
|
'name' => $this->getName(),
|
||||||
|
'requestName' => $this->getRequestName(),
|
||||||
|
'type' => $this->getType(),
|
||||||
|
'value' => $this->getValue()
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
53
app/src/app/View/Components/Volt/Forms/MultiCheckbox.php
Normal file
53
app/src/app/View/Components/Volt/Forms/MultiCheckbox.php
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
<?php declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace App\View\Components\Volt\Forms;
|
||||||
|
|
||||||
|
use Illuminate\Support\Str;
|
||||||
|
use Illuminate\View\View;
|
||||||
|
|
||||||
|
final class MultiCheckbox extends Form
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @param string $title
|
||||||
|
* @param string $name
|
||||||
|
* @param array $list = [ [key => value], ... ]
|
||||||
|
* @param array $value
|
||||||
|
*/
|
||||||
|
public function __construct(
|
||||||
|
private readonly string $title,
|
||||||
|
private readonly string $name,
|
||||||
|
private readonly array $list,
|
||||||
|
private readonly array $value = []
|
||||||
|
) { }
|
||||||
|
|
||||||
|
protected function getName(): string
|
||||||
|
{
|
||||||
|
return $this->name;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function getTitle(): string
|
||||||
|
{
|
||||||
|
return Str::ucfirst($this->title);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function getValue(): array
|
||||||
|
{
|
||||||
|
return old($this->getRequestName(), $this->value);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function getList(): array
|
||||||
|
{
|
||||||
|
return $this->list;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function render(): View
|
||||||
|
{
|
||||||
|
return view('components.volt.forms.multi_checkbox', [
|
||||||
|
'title' => $this->getTitle(),
|
||||||
|
'name' => $this->getName(),
|
||||||
|
'requestName' => $this->getRequestName(),
|
||||||
|
'list' => $this->getList(),
|
||||||
|
'value' => $this->getValue()
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,62 @@
|
|||||||
|
<?php declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace App\View\Components\Volt\Forms;
|
||||||
|
|
||||||
|
use App\Enums\Permission;
|
||||||
|
use App\Models\Role;
|
||||||
|
use Illuminate\Support\Collection;
|
||||||
|
use Illuminate\Support\Str;
|
||||||
|
use Illuminate\View\View;
|
||||||
|
|
||||||
|
final class PermissionsForRole extends Form
|
||||||
|
{
|
||||||
|
public function __construct(
|
||||||
|
private readonly string $title,
|
||||||
|
private readonly string $name,
|
||||||
|
private readonly Role $role,
|
||||||
|
private readonly array $value
|
||||||
|
) { }
|
||||||
|
|
||||||
|
protected function getName(): string
|
||||||
|
{
|
||||||
|
return $this->name;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
private function getTitle(): string
|
||||||
|
{
|
||||||
|
return Str::ucfirst($this->title);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
private function getValue(): Collection
|
||||||
|
{
|
||||||
|
$value = old($this->getRequestName(), $this->value);
|
||||||
|
return collect($value);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return Role
|
||||||
|
*/
|
||||||
|
private function getRole(): Role
|
||||||
|
{
|
||||||
|
return $this->role;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function render(): View
|
||||||
|
{
|
||||||
|
return view('components.volt.forms.permissions_for_role', [
|
||||||
|
'title' => $this->getTitle(),
|
||||||
|
'name' => $this->getName(),
|
||||||
|
'requestName' => $this->getRequestName(),
|
||||||
|
'permissions' => Permission::cases(),
|
||||||
|
'role' => $this->getRole(),
|
||||||
|
'value' => $this->getValue(),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
54
app/src/app/View/Components/Volt/Forms/Select.php
Normal file
54
app/src/app/View/Components/Volt/Forms/Select.php
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
<?php declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace App\View\Components\Volt\Forms;
|
||||||
|
|
||||||
|
use Illuminate\Support\Str;
|
||||||
|
use Illuminate\View\View;
|
||||||
|
|
||||||
|
final class Select extends Form
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @param string $title
|
||||||
|
* @param string $name
|
||||||
|
* @param array $list = [ [key => value], ... ]
|
||||||
|
* @param null|string $value
|
||||||
|
*/
|
||||||
|
public function __construct(
|
||||||
|
private readonly string $title,
|
||||||
|
private readonly string $name,
|
||||||
|
private readonly array $list,
|
||||||
|
private readonly ?string $value = ''
|
||||||
|
) { }
|
||||||
|
|
||||||
|
protected function getName(): string
|
||||||
|
{
|
||||||
|
return $this->name;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function getTitle(): string
|
||||||
|
{
|
||||||
|
return Str::ucfirst($this->title);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function getValue(): string
|
||||||
|
{
|
||||||
|
return (string) old($this->getRequestName(), $this->value);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function getList(): array
|
||||||
|
{
|
||||||
|
return $this->list;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function render(): View
|
||||||
|
{
|
||||||
|
return view('components.volt.forms.select', [
|
||||||
|
'title' => $this->getTitle(),
|
||||||
|
'name' => $this->getName(),
|
||||||
|
'requestName' => $this->getRequestName(),
|
||||||
|
'list' => $this->getList(),
|
||||||
|
'value' => $this->getValue()
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
15
app/src/artisan
Executable file
15
app/src/artisan
Executable file
@ -0,0 +1,15 @@
|
|||||||
|
#!/usr/bin/env php
|
||||||
|
<?php
|
||||||
|
|
||||||
|
use Symfony\Component\Console\Input\ArgvInput;
|
||||||
|
|
||||||
|
define('LARAVEL_START', microtime(true));
|
||||||
|
|
||||||
|
// Register the Composer autoloader...
|
||||||
|
require __DIR__.'/vendor/autoload.php';
|
||||||
|
|
||||||
|
// Bootstrap Laravel and handle the command...
|
||||||
|
$status = (require_once __DIR__.'/bootstrap/app.php')
|
||||||
|
->handleCommand(new ArgvInput);
|
||||||
|
|
||||||
|
exit($status);
|
18
app/src/bootstrap/app.php
Normal file
18
app/src/bootstrap/app.php
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
use Illuminate\Foundation\Application;
|
||||||
|
use Illuminate\Foundation\Configuration\Exceptions;
|
||||||
|
use Illuminate\Foundation\Configuration\Middleware;
|
||||||
|
|
||||||
|
return Application::configure(basePath: dirname(__DIR__))
|
||||||
|
->withRouting(
|
||||||
|
web: __DIR__.'/../routes/web.php',
|
||||||
|
commands: __DIR__.'/../routes/console.php',
|
||||||
|
health: '/up',
|
||||||
|
)
|
||||||
|
->withMiddleware(function (Middleware $middleware) {
|
||||||
|
//
|
||||||
|
})
|
||||||
|
->withExceptions(function (Exceptions $exceptions) {
|
||||||
|
//
|
||||||
|
})->create();
|
2
app/src/bootstrap/cache/.gitignore
vendored
Executable file
2
app/src/bootstrap/cache/.gitignore
vendored
Executable file
@ -0,0 +1,2 @@
|
|||||||
|
*
|
||||||
|
!.gitignore
|
5
app/src/bootstrap/providers.php
Normal file
5
app/src/bootstrap/providers.php
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
return [
|
||||||
|
App\Providers\AppServiceProvider::class,
|
||||||
|
];
|
67
app/src/composer.json
Normal file
67
app/src/composer.json
Normal file
@ -0,0 +1,67 @@
|
|||||||
|
{
|
||||||
|
"name": "laravel/laravel",
|
||||||
|
"type": "project",
|
||||||
|
"description": "The skeleton application for the Laravel framework.",
|
||||||
|
"keywords": ["laravel", "framework"],
|
||||||
|
"license": "MIT",
|
||||||
|
"require": {
|
||||||
|
"php": "^8.3",
|
||||||
|
"laravel/framework": "^11.0",
|
||||||
|
"laravel/tinker": "^2.9"
|
||||||
|
},
|
||||||
|
"require-dev": {
|
||||||
|
"fakerphp/faker": "^1.23",
|
||||||
|
"laravel-lang/common": "^6.2",
|
||||||
|
"laravel/pint": "^1.13",
|
||||||
|
"laravel/sail": "^1.26",
|
||||||
|
"mockery/mockery": "^1.6",
|
||||||
|
"nunomaduro/collision": "^8.0",
|
||||||
|
"phpunit/phpunit": "^10.5",
|
||||||
|
"spatie/laravel-ignition": "^2.4"
|
||||||
|
},
|
||||||
|
"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",
|
||||||
|
"@php -r \"file_exists('database/database.sqlite') || touch('database/database.sqlite');\"",
|
||||||
|
"@php artisan migrate --graceful --ansi"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"extra": {
|
||||||
|
"laravel": {
|
||||||
|
"dont-discover": []
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"optimize-autoloader": true,
|
||||||
|
"preferred-install": "dist",
|
||||||
|
"sort-packages": true,
|
||||||
|
"allow-plugins": {
|
||||||
|
"pestphp/pest-plugin": true,
|
||||||
|
"php-http/discovery": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"minimum-stability": "stable",
|
||||||
|
"prefer-stable": true
|
||||||
|
}
|
9412
app/src/composer.lock
generated
Normal file
9412
app/src/composer.lock
generated
Normal file
File diff suppressed because it is too large
Load Diff
129
app/src/config/app.php
Normal file
129
app/src/config/app.php
Normal file
@ -0,0 +1,129 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
return [
|
||||||
|
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| Application Name
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| This value is the name of your application, which will be used when the
|
||||||
|
| framework needs to place the application's name in a notification or
|
||||||
|
| other UI elements where an application name needs to be displayed.
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
'name' => env('APP_NAME', 'Laravel'),
|
||||||
|
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| Application Environment
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| This value determines the "environment" your application is currently
|
||||||
|
| running in. This may determine how you prefer to configure various
|
||||||
|
| services the application utilizes. Set this in your ".env" file.
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
'env' => env('APP_ENV', 'production'),
|
||||||
|
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| Application Debug Mode
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| When your application is in debug mode, detailed error messages with
|
||||||
|
| stack traces will be shown on every error that occurs within your
|
||||||
|
| application. If disabled, a simple generic error page is shown.
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
'debug' => (bool) env('APP_DEBUG', false),
|
||||||
|
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| Application URL
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| This URL is used by the console to properly generate URLs when using
|
||||||
|
| the Artisan command line tool. You should set this to the root of
|
||||||
|
| the application so that it's available within Artisan commands.
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
'url' => env('APP_URL', 'http://localhost'),
|
||||||
|
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| Application Timezone
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| Here you may specify the default timezone for your application, which
|
||||||
|
| will be used by the PHP date and date-time functions. The timezone
|
||||||
|
| is set to "UTC" by default as it is suitable for most use cases.
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
'timezone' => 'UTC',
|
||||||
|
'user_timezone' => env('APP_DEFAULT_USER_TIMEZONE', 'UTC'),
|
||||||
|
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| Application Locale Configuration
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| The application locale determines the default locale that will be used
|
||||||
|
| by Laravel's translation / localization methods. This option can be
|
||||||
|
| set to any locale for which you plan to have translation strings.
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
'locale' => env('APP_DEFAULT_LOCALE', 'ru'),
|
||||||
|
|
||||||
|
'fallback_locale' => 'ru',
|
||||||
|
|
||||||
|
'faker_locale' => env('APP_FAKER_LOCALE', 'ru_RU'),
|
||||||
|
|
||||||
|
'force_https' => (bool) env('APP_FORCE_HTTPS', false),
|
||||||
|
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| Encryption Key
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| This key is utilized by Laravel's encryption services and should be set
|
||||||
|
| to a random, 32 character string to ensure that all encrypted values
|
||||||
|
| are secure. You should do this prior to deploying the application.
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
'cipher' => 'AES-256-CBC',
|
||||||
|
|
||||||
|
'key' => env('APP_KEY'),
|
||||||
|
|
||||||
|
'previous_keys' => [
|
||||||
|
...array_filter(
|
||||||
|
explode(',', env('APP_PREVIOUS_KEYS', ''))
|
||||||
|
),
|
||||||
|
],
|
||||||
|
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| Maintenance Mode Driver
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| These configuration options determine the driver used to determine and
|
||||||
|
| manage Laravel's "maintenance mode" status. The "cache" driver will
|
||||||
|
| allow maintenance mode to be controlled across multiple machines.
|
||||||
|
|
|
||||||
|
| Supported drivers: "file", "cache"
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
'maintenance' => [
|
||||||
|
'driver' => env('APP_MAINTENANCE_DRIVER', 'file'),
|
||||||
|
'store' => env('APP_MAINTENANCE_STORE', 'database'),
|
||||||
|
],
|
||||||
|
|
||||||
|
];
|
115
app/src/config/auth.php
Normal file
115
app/src/config/auth.php
Normal file
@ -0,0 +1,115 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
return [
|
||||||
|
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| Authentication Defaults
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| This option defines the default authentication "guard" and password
|
||||||
|
| reset "broker" for your application. You may change these values
|
||||||
|
| as required, but they're a perfect start for most applications.
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
'defaults' => [
|
||||||
|
'guard' => env('AUTH_GUARD', 'web'),
|
||||||
|
'passwords' => env('AUTH_PASSWORD_BROKER', 'users'),
|
||||||
|
],
|
||||||
|
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| Authentication Guards
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| Next, you may define every authentication guard for your application.
|
||||||
|
| Of course, a great default configuration has been defined for you
|
||||||
|
| which utilizes session storage plus the Eloquent user provider.
|
||||||
|
|
|
||||||
|
| All authentication guards have a user provider, which defines how the
|
||||||
|
| users are actually retrieved out of your database or other storage
|
||||||
|
| system used by the application. Typically, Eloquent is utilized.
|
||||||
|
|
|
||||||
|
| Supported: "session"
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
'guards' => [
|
||||||
|
'web' => [
|
||||||
|
'driver' => 'session',
|
||||||
|
'provider' => 'users',
|
||||||
|
],
|
||||||
|
],
|
||||||
|
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| User Providers
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| All authentication guards have a user provider, which defines how the
|
||||||
|
| users are actually retrieved out of your database or other storage
|
||||||
|
| system used by the application. Typically, Eloquent is utilized.
|
||||||
|
|
|
||||||
|
| If you have multiple user tables or models you may configure multiple
|
||||||
|
| providers to represent the model / table. These providers may then
|
||||||
|
| be assigned to any extra authentication guards you have defined.
|
||||||
|
|
|
||||||
|
| Supported: "database", "eloquent"
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
'providers' => [
|
||||||
|
'users' => [
|
||||||
|
'driver' => 'eloquent',
|
||||||
|
'model' => env('AUTH_MODEL', App\Models\User::class),
|
||||||
|
],
|
||||||
|
|
||||||
|
// 'users' => [
|
||||||
|
// 'driver' => 'database',
|
||||||
|
// 'table' => 'users',
|
||||||
|
// ],
|
||||||
|
],
|
||||||
|
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| Resetting Passwords
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| These configuration options specify the behavior of Laravel's password
|
||||||
|
| reset functionality, including the table utilized for token storage
|
||||||
|
| and the user provider that is invoked to actually retrieve users.
|
||||||
|
|
|
||||||
|
| The expiry time is the number of minutes that each reset token will be
|
||||||
|
| considered valid. This security feature keeps tokens short-lived so
|
||||||
|
| they have less time to be guessed. You may change this as needed.
|
||||||
|
|
|
||||||
|
| The throttle setting is the number of seconds a user must wait before
|
||||||
|
| generating more password reset tokens. This prevents the user from
|
||||||
|
| quickly generating a very large amount of password reset tokens.
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
'passwords' => [
|
||||||
|
'users' => [
|
||||||
|
'provider' => 'users',
|
||||||
|
'table' => env('AUTH_PASSWORD_RESET_TOKEN_TABLE', 'password_reset_tokens'),
|
||||||
|
'expire' => 60,
|
||||||
|
'throttle' => 60,
|
||||||
|
],
|
||||||
|
],
|
||||||
|
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| Password Confirmation Timeout
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| Here you may define the amount of seconds before a password confirmation
|
||||||
|
| window expires and users are asked to re-enter their password via the
|
||||||
|
| confirmation screen. By default, the timeout lasts for three hours.
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
'password_timeout' => env('AUTH_PASSWORD_TIMEOUT', 10800),
|
||||||
|
|
||||||
|
];
|
109
app/src/config/cache.php
Normal file
109
app/src/config/cache.php
Normal file
@ -0,0 +1,109 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
use Illuminate\Support\Str;
|
||||||
|
|
||||||
|
return [
|
||||||
|
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| Default Cache Store
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| This option controls the default cache store that will be used by the
|
||||||
|
| framework. This connection is utilized if another isn't explicitly
|
||||||
|
| specified when running a cache operation inside the application.
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
'default' => env('CACHE_STORE', 'database'),
|
||||||
|
|
||||||
|
'limiter' => 'redis',
|
||||||
|
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| Cache Stores
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| Here you may define all of the cache "stores" for your application as
|
||||||
|
| well as their drivers. You may even define multiple stores for the
|
||||||
|
| same cache driver to group types of items stored in your caches.
|
||||||
|
|
|
||||||
|
| Supported drivers: "apc", "array", "database", "file", "memcached",
|
||||||
|
| "redis", "dynamodb", "octane", "null"
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
'stores' => [
|
||||||
|
|
||||||
|
'array' => [
|
||||||
|
'driver' => 'array',
|
||||||
|
'serialize' => false,
|
||||||
|
],
|
||||||
|
|
||||||
|
'database' => [
|
||||||
|
'driver' => 'database',
|
||||||
|
'table' => env('DB_CACHE_TABLE', 'cache'),
|
||||||
|
'connection' => env('DB_CACHE_CONNECTION'),
|
||||||
|
'lock_connection' => env('DB_CACHE_LOCK_CONNECTION'),
|
||||||
|
],
|
||||||
|
|
||||||
|
'file' => [
|
||||||
|
'driver' => 'file',
|
||||||
|
'path' => storage_path('framework/cache/data'),
|
||||||
|
'lock_path' => storage_path('framework/cache/data'),
|
||||||
|
],
|
||||||
|
|
||||||
|
'memcached' => [
|
||||||
|
'driver' => 'memcached',
|
||||||
|
'persistent_id' => env('MEMCACHED_PERSISTENT_ID'),
|
||||||
|
'sasl' => [
|
||||||
|
env('MEMCACHED_USERNAME'),
|
||||||
|
env('MEMCACHED_PASSWORD'),
|
||||||
|
],
|
||||||
|
'options' => [
|
||||||
|
// Memcached::OPT_CONNECT_TIMEOUT => 2000,
|
||||||
|
],
|
||||||
|
'servers' => [
|
||||||
|
[
|
||||||
|
'host' => env('MEMCACHED_HOST', '127.0.0.1'),
|
||||||
|
'port' => env('MEMCACHED_PORT', 11211),
|
||||||
|
'weight' => 100,
|
||||||
|
],
|
||||||
|
],
|
||||||
|
],
|
||||||
|
|
||||||
|
'redis' => [
|
||||||
|
'driver' => 'redis',
|
||||||
|
'connection' => env('REDIS_CACHE_CONNECTION', 'cache'),
|
||||||
|
'lock_connection' => env('REDIS_CACHE_LOCK_CONNECTION', 'default'),
|
||||||
|
],
|
||||||
|
|
||||||
|
'dynamodb' => [
|
||||||
|
'driver' => 'dynamodb',
|
||||||
|
'key' => env('AWS_ACCESS_KEY_ID'),
|
||||||
|
'secret' => env('AWS_SECRET_ACCESS_KEY'),
|
||||||
|
'region' => env('AWS_DEFAULT_REGION', 'us-east-1'),
|
||||||
|
'table' => env('DYNAMODB_CACHE_TABLE', 'cache'),
|
||||||
|
'endpoint' => env('DYNAMODB_ENDPOINT'),
|
||||||
|
],
|
||||||
|
|
||||||
|
'octane' => [
|
||||||
|
'driver' => 'octane',
|
||||||
|
],
|
||||||
|
|
||||||
|
],
|
||||||
|
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| Cache Key Prefix
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| When utilizing the APC, database, memcached, Redis, and DynamoDB cache
|
||||||
|
| stores, there might be other applications using the same cache. For
|
||||||
|
| that reason, you may prefix every cache key to avoid collisions.
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
'prefix' => env('CACHE_PREFIX', Str::slug(env('APP_NAME', 'laravel'), '_').'_cache_'),
|
||||||
|
|
||||||
|
];
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user