Compare commits
11 commits
feature_hc
...
master
Author | SHA1 | Date | |
---|---|---|---|
40118a0edc | |||
23d8401bb2 | |||
2ef4859466 | |||
8723e5bce4 | |||
bc232dc2a7 | |||
8e5e3cceb9 | |||
e88ca59e06 | |||
a08486daae | |||
181d449f37 | |||
5bf62f362e | |||
b959d2e300 |
50
.dockerignore
Normal file
50
.dockerignore
Normal file
|
@ -0,0 +1,50 @@
|
|||
### Symfony template
|
||||
# Cache and logs (Symfony2)
|
||||
/app/cache/*
|
||||
/app/logs/*
|
||||
!app/cache/.gitkeep
|
||||
!app/logs/.gitkeep
|
||||
|
||||
# Cache, session files and logs (Symfony3)
|
||||
/var/cache/*
|
||||
/var/sessions/*
|
||||
!var/cache/.gitkeep
|
||||
!var/sessions/.gitkeep
|
||||
|
||||
# Logs (Symfony4)
|
||||
/var/log/*
|
||||
!var/log/.gitkeep
|
||||
|
||||
# Managed by Composer
|
||||
/app/bootstrap.php.cache
|
||||
/var/bootstrap.php.cache
|
||||
/bin/*
|
||||
!bin/console
|
||||
!bin/symfony_requirements
|
||||
/vendor/
|
||||
|
||||
# PHPUnit
|
||||
/app/phpunit.xml
|
||||
/phpunit.xml
|
||||
|
||||
# Composer PHAR
|
||||
/composer.phar
|
||||
|
||||
# Backup entities generated with doctrine:generate:entities command
|
||||
**/Entity/*~
|
||||
|
||||
# Embedded web-server pid file
|
||||
/.web-server-pid
|
||||
|
||||
# DotEnv
|
||||
.env.local
|
||||
|
||||
# Git files
|
||||
/.git
|
||||
/.gitignore
|
||||
|
||||
# Drone config
|
||||
/.drone.yml
|
||||
|
||||
# Docker Ignore
|
||||
/.dockerignore
|
35
.drone.yml
Normal file
35
.drone.yml
Normal file
|
@ -0,0 +1,35 @@
|
|||
kind: pipeline
|
||||
type: docker
|
||||
name: build-app
|
||||
|
||||
steps:
|
||||
# TODO: prepare image with necessary extensions
|
||||
# - name: build-deps
|
||||
# image: 'composer:2.3'
|
||||
# environment:
|
||||
# APP_ENV: dev
|
||||
# commands:
|
||||
# - composer install --no-progress --no-interaction --optimize-autoloader
|
||||
# - bin/console about
|
||||
# - 'bin/console lint:container'
|
||||
# - 'bin/console lint:twig'
|
||||
# - 'bin/console lint:container'
|
||||
# - 'bin/console lint:yaml'
|
||||
- name: docker-build
|
||||
# https://github.com/drone/drone-plugin-index/blob/main/plugins/docker/original.md
|
||||
image: plugins/docker
|
||||
when:
|
||||
branch:
|
||||
- master
|
||||
event:
|
||||
- push
|
||||
settings:
|
||||
username:
|
||||
from_secret: docker_username
|
||||
password:
|
||||
from_secret: docker_token
|
||||
repo:
|
||||
from_secret: docker_repo
|
||||
tags:
|
||||
- latest
|
||||
- ${DRONE_COMMIT_SHA:0:10}
|
25
.env
25
.env
|
@ -3,7 +3,7 @@
|
|||
# https://symfony.com/doc/current/best_practices/configuration.html#infrastructure-related-configuration
|
||||
|
||||
###> symfony/framework-bundle ###
|
||||
APP_ENV=dev
|
||||
APP_ENV=prod
|
||||
APP_SECRET=xxx
|
||||
###< symfony/framework-bundle ###
|
||||
|
||||
|
@ -22,16 +22,23 @@ REDIS_DSN=redis://127.0.0.1:6379/0
|
|||
SENTRY_DSN=
|
||||
###< sentry/sentry-symfony ###
|
||||
|
||||
# docker-compose
|
||||
PHP_FPM_PORT=9000
|
||||
APP_LOCAL_PATH=/var/www/magnetico-web/current
|
||||
|
||||
###> symfony/mailer ###
|
||||
MAILER_DSN=smtp://localhost
|
||||
MAILER_FROM=no-reply@magnetico-web.tld
|
||||
###< symfony/mailer ###
|
||||
|
||||
###> meteo-concept/hcaptcha-bundle ###
|
||||
HCAPTCHA_SITE=123456789
|
||||
HCAPTCHA_SECRET=0x0000000000000000000000000000000000000000
|
||||
###< meteo-concept/hcaptcha-bundle ###
|
||||
###> google/recaptcha ###
|
||||
# To use Google Recaptcha, you must register a site on Recaptcha's admin panel:
|
||||
# https://www.google.com/recaptcha/admin
|
||||
#GOOGLE_RECAPTCHA_SITE_KEY=
|
||||
#GOOGLE_RECAPTCHA_SECRET=
|
||||
###< google/recaptcha ###
|
||||
|
||||
###> excelwebzone/recaptcha-bundle ###
|
||||
EWZ_RECAPTCHA_SITE_KEY=
|
||||
EWZ_RECAPTCHA_SECRET=
|
||||
###< excelwebzone/recaptcha-bundle ###
|
||||
|
||||
# Run this app only behind reverse-proxy if this is set!
|
||||
# https://symfony.com/doc/5.4/deployment/proxies.html#but-what-if-the-ip-of-my-reverse-proxy-changes-constantly
|
||||
TRUSTED_PROXIES=127.0.0.1
|
||||
|
|
9
.rr.yaml
9
.rr.yaml
|
@ -1,12 +1,11 @@
|
|||
# https://roadrunner.dev/docs/intro-config/2.x/en
|
||||
version: "2.7"
|
||||
|
||||
server:
|
||||
command: "php public/index.php"
|
||||
# If you are not using symfony 5.3+ and the new Runtime component:
|
||||
# remove the previous `command` line above and uncomment the line below to use the deprecated command.
|
||||
# command: "php bin/console baldinof:roadrunner:worker"
|
||||
env:
|
||||
- APP_RUNTIME: Baldinof\RoadRunnerBundle\Runtime\Runtime
|
||||
- APP_ENV: prod
|
||||
|
||||
http:
|
||||
address: 0.0.0.0:8080
|
||||
|
@ -16,6 +15,10 @@ http:
|
|||
static:
|
||||
dir: "public"
|
||||
forbid: [ ".php", ".htaccess" ]
|
||||
pool:
|
||||
max_jobs: 16
|
||||
supervisor:
|
||||
max_worker_memory: 256
|
||||
|
||||
logs:
|
||||
mode: production
|
||||
|
|
43
Dockerfile
Normal file
43
Dockerfile
Normal file
|
@ -0,0 +1,43 @@
|
|||
# https://github.com/roadrunner-server/roadrunner/pkgs/container/roadrunner
|
||||
FROM ghcr.io/roadrunner-server/roadrunner:latest AS roadrunner
|
||||
FROM php:8.1-alpine
|
||||
|
||||
ENV PHP_TIMEZONE Europe/Moscow
|
||||
ENV APP_ENV=prod
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
COPY --from=roadrunner /usr/bin/rr /app/bin/rr
|
||||
COPY . /app/
|
||||
|
||||
RUN apk update && \
|
||||
apk add autoconf build-base icu libpq postgresql-dev && \
|
||||
docker-php-ext-configure intl && \
|
||||
docker-php-ext-configure pdo_pgsql && \
|
||||
docker-php-ext-configure sockets && \
|
||||
docker-php-ext-install -j$(nproc) intl && \
|
||||
docker-php-ext-install -j$(nproc) pdo_pgsql && \
|
||||
docker-php-ext-install -j$(nproc) sockets && \
|
||||
pecl install igbinary-3.2.7 && \
|
||||
pecl install redis-5.3.7 && \
|
||||
docker-php-ext-enable igbinary && \
|
||||
docker-php-ext-enable intl && \
|
||||
docker-php-ext-enable pdo_pgsql && \
|
||||
docker-php-ext-enable redis && \
|
||||
apk del autoconf build-base postgresql-dev && \
|
||||
mv "$PHP_INI_DIR/php.ini-production" "$PHP_INI_DIR/php.ini" && \
|
||||
echo "date.timezone = $PHP_TIMEZONE" > $PHP_INI_DIR/conf.d/timezone.ini && \
|
||||
mkdir -p /usr/local/bin && \
|
||||
wget -O /usr/local/bin/composer https://getcomposer.org/download/latest-stable/composer.phar && \
|
||||
chmod +x /usr/local/bin/composer && \
|
||||
ls -la /app && ls -la /app/bin && \
|
||||
chmod +x /app/bin/console && \
|
||||
/usr/local/bin/composer install --no-dev --no-progress --no-interaction --optimize-autoloader
|
||||
|
||||
EXPOSE 8080/tcp
|
||||
|
||||
VOLUME /var/log
|
||||
|
||||
HEALTHCHECK --retries=3 --timeout=10s CMD curl http://localhost:8080 || exit 1
|
||||
|
||||
CMD ["/app/bin/rr", "serve"]
|
89
README.md
89
README.md
|
@ -1,5 +1,4 @@
|
|||
[![Codeship Status for skobkin/magnetico-web](https://app.codeship.com/projects/9da4d3e0-57cf-0136-9885-5644a850740d/status?branch=master)](https://app.codeship.com/projects/295041)
|
||||
[![Total Downloads](https://poser.pugx.org/skobkin/magnetico-web/downloads)](https://packagist.org/packages/skobkin/magnetico-web)
|
||||
[![Build Status](https://ci.skobk.in/api/badges/skobkin/magnetico-web/status.svg)](https://ci.skobk.in/skobkin/magnetico-web)
|
||||
[![License](https://poser.pugx.org/skobkin/magnetico-web/license)](https://packagist.org/packages/skobkin/magnetico-web)
|
||||
|
||||
# Magnetico Web PHP
|
||||
|
@ -91,3 +90,89 @@ php bin/console user:add <your_username> <your_email> [your_password] [--invites
|
|||
# see --help for more info
|
||||
php bin/console invite:add <username> <number-of-invites>
|
||||
```
|
||||
|
||||
## Enabling dev mode
|
||||
|
||||
```shell
|
||||
echo 'APP_ENV=dev > .env.local'
|
||||
```
|
||||
|
||||
## Running using [RoadRunner](https://roadrunner.dev) instead of [PHP-FPM](https://www.php.net/manual/en/install.fpm.php)
|
||||
|
||||
```shell
|
||||
# First time only:
|
||||
./vendor/bin/rr get --location bin/
|
||||
|
||||
# Running the server:
|
||||
./bin/rr serve
|
||||
|
||||
# Running the server in dev mode (watching enabled)
|
||||
bin/rr serve -c .rr.dev.yaml
|
||||
```
|
||||
|
||||
Read more [here](https://github.com/baldinof/roadrunner-bundle) and [here](https://github.com/roadrunner-server/roadrunner).
|
||||
|
||||
### Trusted proxies
|
||||
|
||||
If you're running the app in RoadRunner and experiencing problems with proper URL generation (HTTP instead of HTTPS),
|
||||
check beginning of the section about running in Docker below.
|
||||
|
||||
## Running in Docker
|
||||
|
||||
### Docker Compose example:
|
||||
|
||||
When running in Docker **DO NOT FORGET** to use Nginx or other reverse-proxy server and properly set `TRUSTED_PROXIES`
|
||||
environment variable. You can read more about it [here](https://symfony.com/doc/current/deployment/proxies.html#but-what-if-the-ip-of-my-reverse-proxy-changes-constantly).
|
||||
|
||||
```yaml
|
||||
version: '3.7'
|
||||
|
||||
services:
|
||||
magnetico-web:
|
||||
image: skobkin/magnetico-web
|
||||
container_name: magnetico-web
|
||||
hostname: magnetico-web
|
||||
extra_hosts:
|
||||
- 'host.docker.internal:host-gateway'
|
||||
ports:
|
||||
- "127.0.0.1:${EXT_HTTP_PORT:-8080}:8080/tcp"
|
||||
restart: unless-stopped
|
||||
user: "$UID"
|
||||
volumes:
|
||||
- "${LOG_PATH:-./var/log}:/app/var/log"
|
||||
env_file: .env
|
||||
logging:
|
||||
driver: "json-file"
|
||||
options:
|
||||
max-size: "${LOG_MAX_SIZE:-5m}"
|
||||
max-file: "${LOG_MAX_FILE:-5}"
|
||||
```
|
||||
|
||||
Use dotenv file to configure this stack:
|
||||
|
||||
```dotenv
|
||||
# Example with some useful parameters
|
||||
APP_SECRET=qwerty
|
||||
|
||||
APP_DATABASE_URL=postgres://magnetico-web:password@host.docker.internal:5432/magnetico-web?application_name=magnetico_web
|
||||
MAGNETICOD_DATABASE_URL=postgres://magneticod:password@host.docker.internal:5432/magneticod?application_name=magnetico_web
|
||||
|
||||
REDIS_DSN=redis://host.docker.internal:6379/0
|
||||
|
||||
# BE CAREFUL WITH 'REMOTE_ADDR'. Use ONLY with trusted reverse-proxy
|
||||
TRUSTED_PROXIES=127.0.0.1,REMOTE_ADDR
|
||||
|
||||
###> sentry/sentry-symfony ###
|
||||
SENTRY_DSN=https://abcabcdaefdaef@sentry.io/123456
|
||||
###< sentry/sentry-symfony ###
|
||||
|
||||
###> symfony/mailer ###
|
||||
MAILER_DSN=smtp://mail@domain.tld:password@smtp.domain.tld:587
|
||||
MAILER_FROM=no-reply@domain.tld
|
||||
###< symfony/mailer ###
|
||||
|
||||
###> excelwebzone/recaptcha-bundle ###
|
||||
EWZ_RECAPTCHA_SITE_KEY=key
|
||||
EWZ_RECAPTCHA_SECRET=secret
|
||||
###< excelwebzone/recaptcha-bundle ###
|
||||
```
|
||||
|
|
|
@ -24,8 +24,7 @@
|
|||
"doctrine/doctrine-bundle": "^2",
|
||||
"doctrine/doctrine-migrations-bundle": "^2",
|
||||
"doctrine/orm": "^2",
|
||||
"meteo-concept/hcaptcha-bundle": "^3.2",
|
||||
"nyholm/psr7": "^1.5",
|
||||
"excelwebzone/recaptcha-bundle": "^1.5",
|
||||
"pagerfanta/doctrine-orm-adapter": "^3.6",
|
||||
"pagerfanta/twig": "^3.6",
|
||||
"phpdocumentor/reflection-docblock": "^5.3",
|
||||
|
@ -40,7 +39,7 @@
|
|||
"symfony/flex": "^1.0",
|
||||
"symfony/form": "^5.4",
|
||||
"symfony/framework-bundle": "^5.4",
|
||||
"symfony/http-client": "5.4.*",
|
||||
"symfony/http-client": "^5.4",
|
||||
"symfony/mailer": "^5.4",
|
||||
"symfony/monolog-bundle": "^3.3",
|
||||
"symfony/property-access": "^5.4",
|
||||
|
|
254
composer.lock
generated
254
composer.lock
generated
|
@ -4,7 +4,7 @@
|
|||
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
||||
"This file is @generated automatically"
|
||||
],
|
||||
"content-hash": "a8bee2535da266f0feee500c5adb71a1",
|
||||
"content-hash": "bfa726a8284bf45a7ccbbb6600e02128",
|
||||
"packages": [
|
||||
{
|
||||
"name": "babdev/pagerfanta-bundle",
|
||||
|
@ -1816,6 +1816,66 @@
|
|||
],
|
||||
"time": "2022-06-18T20:57:19+00:00"
|
||||
},
|
||||
{
|
||||
"name": "excelwebzone/recaptcha-bundle",
|
||||
"version": "v1.5.35",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/excelwebzone/EWZRecaptchaBundle.git",
|
||||
"reference": "8dad97a2017dcb650c18ff17ff1b8c54c1847dac"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/excelwebzone/EWZRecaptchaBundle/zipball/8dad97a2017dcb650c18ff17ff1b8c54c1847dac",
|
||||
"reference": "8dad97a2017dcb650c18ff17ff1b8c54c1847dac",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"google/recaptcha": "^1.1",
|
||||
"php": "^7.1 || ^8.0",
|
||||
"symfony/form": "^2.8 || ^3.0 || ^4.0 || ^5.0 || ^6.0",
|
||||
"symfony/framework-bundle": "^2.8 || ^3.0 || ^4.0 || ^5.0 || ^6.0",
|
||||
"symfony/security-bundle": "^2.8 || ^3.0 || ^4.0 || ^5.0 || ^6.0",
|
||||
"symfony/validator": "^2.8 || ^3.0 || ^4.0 || ^5.0 || ^6.0",
|
||||
"symfony/yaml": "^2.8 || ^3.0 || ^4.0 || ^5.0 || ^6.0",
|
||||
"twig/twig": "^1.40 || ^2.9 || ^3.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"phpunit/phpunit": "^7 || ^8 || ^9.5"
|
||||
},
|
||||
"type": "symfony-bundle",
|
||||
"extra": {
|
||||
"symfony": {
|
||||
"allow-contrib": "true"
|
||||
}
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"EWZ\\Bundle\\RecaptchaBundle\\": "src"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Michael H. Arieli",
|
||||
"email": "excelwebzone@gmail.com",
|
||||
"homepage": "http://excelwebzone.com/"
|
||||
}
|
||||
],
|
||||
"description": "This bundle provides easy reCAPTCHA form field integration",
|
||||
"homepage": "https://github.com/excelwebzone/EWZRecaptchaBundle",
|
||||
"keywords": [
|
||||
"recaptcha"
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/excelwebzone/EWZRecaptchaBundle/issues",
|
||||
"source": "https://github.com/excelwebzone/EWZRecaptchaBundle/tree/v1.5.35"
|
||||
},
|
||||
"time": "2022-04-22T16:52:19+00:00"
|
||||
},
|
||||
{
|
||||
"name": "friendsofphp/proxy-manager-lts",
|
||||
"version": "v1.0.12",
|
||||
|
@ -1898,6 +1958,58 @@
|
|||
],
|
||||
"time": "2022-05-05T09:31:05+00:00"
|
||||
},
|
||||
{
|
||||
"name": "google/recaptcha",
|
||||
"version": "1.2.4",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/google/recaptcha.git",
|
||||
"reference": "614f25a9038be4f3f2da7cbfd778dc5b357d2419"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/google/recaptcha/zipball/614f25a9038be4f3f2da7cbfd778dc5b357d2419",
|
||||
"reference": "614f25a9038be4f3f2da7cbfd778dc5b357d2419",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": ">=5.5"
|
||||
},
|
||||
"require-dev": {
|
||||
"friendsofphp/php-cs-fixer": "^2.2.20|^2.15",
|
||||
"php-coveralls/php-coveralls": "^2.1",
|
||||
"phpunit/phpunit": "^4.8.36|^5.7.27|^6.59|^7.5.11"
|
||||
},
|
||||
"type": "library",
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-master": "1.2.x-dev"
|
||||
}
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"ReCaptcha\\": "src/ReCaptcha"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"BSD-3-Clause"
|
||||
],
|
||||
"description": "Client library for reCAPTCHA, a free service that protects websites from spam and abuse.",
|
||||
"homepage": "https://www.google.com/recaptcha/",
|
||||
"keywords": [
|
||||
"Abuse",
|
||||
"captcha",
|
||||
"recaptcha",
|
||||
"spam"
|
||||
],
|
||||
"support": {
|
||||
"forum": "https://groups.google.com/forum/#!forum/recaptcha",
|
||||
"issues": "https://github.com/google/recaptcha/issues",
|
||||
"source": "https://github.com/google/recaptcha"
|
||||
},
|
||||
"time": "2020-03-31T17:50:54+00:00"
|
||||
},
|
||||
{
|
||||
"name": "guzzlehttp/promises",
|
||||
"version": "1.5.1",
|
||||
|
@ -2280,69 +2392,6 @@
|
|||
],
|
||||
"time": "2022-06-06T11:26:02+00:00"
|
||||
},
|
||||
{
|
||||
"name": "meteo-concept/hcaptcha-bundle",
|
||||
"version": "v3.2.1",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/Meteo-Concept/hcaptcha-bundle.git",
|
||||
"reference": "7d552dde4453722f5aec092ab21a9c4ed2e92fae"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/Meteo-Concept/hcaptcha-bundle/zipball/7d552dde4453722f5aec092ab21a9c4ed2e92fae",
|
||||
"reference": "7d552dde4453722f5aec092ab21a9c4ed2e92fae",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": "^7.4|^8.0",
|
||||
"psr/http-client": "^1.0",
|
||||
"psr/http-client-implementation": "^1.0",
|
||||
"psr/http-factory-implementation": "^1.0",
|
||||
"psr/http-message": "^1.0",
|
||||
"symfony/config": "~5.4|~6.0",
|
||||
"symfony/form": "~5.4|~6.0",
|
||||
"symfony/twig-bridge": "~5.4|~6.0",
|
||||
"symfony/validator": "~5.4|~6.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"matthiasnoback/symfony-dependency-injection-test": "^4.3",
|
||||
"nyholm/psr7": "^1.3",
|
||||
"nyholm/symfony-bundle-test": "1.x-dev",
|
||||
"php-http/mock-client": "^1.5",
|
||||
"symfony/browser-kit": "^5.3|^6.0",
|
||||
"symfony/css-selector": "^5.3|^6.0",
|
||||
"symfony/framework-bundle": "^5.3|^6.0",
|
||||
"symfony/panther": "^1.1|^2.0",
|
||||
"symfony/phpunit-bridge": "^5.3|^6.0",
|
||||
"symfony/twig-bundle": "^5.3|^6.0"
|
||||
},
|
||||
"suggest": {
|
||||
"symfony/http-client": "A Symfony component that implements PSR-18 HTTP client interface"
|
||||
},
|
||||
"type": "symfony-bundle",
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"MeteoConcept\\HCaptchaBundle\\": "./"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Laurent Georget",
|
||||
"email": "laurent.georget@meteo-concept.fr"
|
||||
}
|
||||
],
|
||||
"description": "A Symfony bundle to use hCaptcha in forms",
|
||||
"support": {
|
||||
"issues": "https://github.com/Meteo-Concept/hcaptcha-bundle/issues",
|
||||
"source": "https://github.com/Meteo-Concept/hcaptcha-bundle/tree/v3.2.1"
|
||||
},
|
||||
"time": "2022-01-17T07:46:02+00:00"
|
||||
},
|
||||
{
|
||||
"name": "monolog/monolog",
|
||||
"version": "2.7.0",
|
||||
|
@ -2447,83 +2496,6 @@
|
|||
],
|
||||
"time": "2022-06-09T08:59:12+00:00"
|
||||
},
|
||||
{
|
||||
"name": "nyholm/psr7",
|
||||
"version": "1.5.1",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/Nyholm/psr7.git",
|
||||
"reference": "f734364e38a876a23be4d906a2a089e1315be18a"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/Nyholm/psr7/zipball/f734364e38a876a23be4d906a2a089e1315be18a",
|
||||
"reference": "f734364e38a876a23be4d906a2a089e1315be18a",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": ">=7.1",
|
||||
"php-http/message-factory": "^1.0",
|
||||
"psr/http-factory": "^1.0",
|
||||
"psr/http-message": "^1.0"
|
||||
},
|
||||
"provide": {
|
||||
"psr/http-factory-implementation": "1.0",
|
||||
"psr/http-message-implementation": "1.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"http-interop/http-factory-tests": "^0.9",
|
||||
"php-http/psr7-integration-tests": "^1.0",
|
||||
"phpunit/phpunit": "^7.5 || 8.5 || 9.4",
|
||||
"symfony/error-handler": "^4.4"
|
||||
},
|
||||
"type": "library",
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-master": "1.4-dev"
|
||||
}
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Nyholm\\Psr7\\": "src/"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Tobias Nyholm",
|
||||
"email": "tobias.nyholm@gmail.com"
|
||||
},
|
||||
{
|
||||
"name": "Martijn van der Ven",
|
||||
"email": "martijn@vanderven.se"
|
||||
}
|
||||
],
|
||||
"description": "A fast PHP7 implementation of PSR-7",
|
||||
"homepage": "https://tnyholm.se",
|
||||
"keywords": [
|
||||
"psr-17",
|
||||
"psr-7"
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/Nyholm/psr7/issues",
|
||||
"source": "https://github.com/Nyholm/psr7/tree/1.5.1"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
"url": "https://github.com/Zegnat",
|
||||
"type": "github"
|
||||
},
|
||||
{
|
||||
"url": "https://github.com/nyholm",
|
||||
"type": "github"
|
||||
}
|
||||
],
|
||||
"time": "2022-06-22T07:13:36+00:00"
|
||||
},
|
||||
{
|
||||
"name": "pagerfanta/core",
|
||||
"version": "v3.6.1",
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
return [
|
||||
Symfony\Bundle\FrameworkBundle\FrameworkBundle::class => ['all' => true],
|
||||
|
@ -11,6 +12,6 @@ return [
|
|||
Symfony\Bundle\MonologBundle\MonologBundle::class => ['all' => true],
|
||||
Symfony\Bundle\SecurityBundle\SecurityBundle::class => ['all' => true],
|
||||
Sentry\SentryBundle\SentryBundle::class => ['all' => true],
|
||||
EWZ\Bundle\RecaptchaBundle\EWZRecaptchaBundle::class => ['all' => true],
|
||||
Baldinof\RoadRunnerBundle\BaldinofRoadRunnerBundle::class => ['all' => true],
|
||||
MeteoConcept\HCaptchaBundle\MeteoConceptHCaptchaBundle::class => ['all' => true],
|
||||
];
|
||||
|
|
|
@ -3,7 +3,8 @@ baldinof_road_runner:
|
|||
# See https://github.com/baldinof/roadrunner-bundle#kernel-reboots
|
||||
kernel_reboot:
|
||||
# if you want to use a fresh container on each request, use the `always` strategy
|
||||
strategy: on_exception
|
||||
#strategy: on_exception
|
||||
strategy: always
|
||||
# Exceptions you KNOW that do not put your app in an errored state
|
||||
allowed_exceptions:
|
||||
- Symfony\Component\HttpKernel\Exception\HttpExceptionInterface
|
||||
|
|
2
config/packages/dev/ewz_recaptcha.yaml
Normal file
2
config/packages/dev/ewz_recaptcha.yaml
Normal file
|
@ -0,0 +1,2 @@
|
|||
ewz_recaptcha:
|
||||
enabled: false
|
|
@ -30,7 +30,7 @@ doctrine:
|
|||
mappings:
|
||||
App:
|
||||
is_bundle: false
|
||||
type: annotation
|
||||
type: attribute
|
||||
dir: '%kernel.project_dir%/src/Entity'
|
||||
prefix: 'App\Entity'
|
||||
alias: App
|
||||
|
@ -39,7 +39,7 @@ doctrine:
|
|||
mappings:
|
||||
Magnetico:
|
||||
is_bundle: false
|
||||
type: annotation
|
||||
type: attribute
|
||||
dir: '%kernel.project_dir%/src/Magnetico/Entity'
|
||||
prefix: 'App\Magnetico'
|
||||
alias: Magnetico
|
||||
|
|
6
config/packages/ewz_recaptcha.yaml
Normal file
6
config/packages/ewz_recaptcha.yaml
Normal file
|
@ -0,0 +1,6 @@
|
|||
# See https://github.com/excelwebzone/EWZRecaptchaBundle for full configuration
|
||||
ewz_recaptcha:
|
||||
version: 3
|
||||
hide_badge: false
|
||||
public_key: '%env(EWZ_RECAPTCHA_SITE_KEY)%'
|
||||
private_key: '%env(EWZ_RECAPTCHA_SECRET)%'
|
|
@ -4,6 +4,9 @@ framework:
|
|||
#csrf_protection: true
|
||||
http_method_override: false
|
||||
|
||||
# https://symfony.com/doc/5.4/deployment/proxies.html#but-what-if-the-ip-of-my-reverse-proxy-changes-constantly
|
||||
trusted_proxies: '%env(TRUSTED_PROXIES)%'
|
||||
|
||||
# Enables session support. Note that the session will ONLY be started if you read or write from it.
|
||||
# Remove or comment this section to explicitly disable session support.
|
||||
session:
|
||||
|
|
21
config/packages/google_recaptcha.yaml
Normal file
21
config/packages/google_recaptcha.yaml
Normal file
|
@ -0,0 +1,21 @@
|
|||
#services:
|
||||
#
|
||||
# # Inject this service in your controllers/services to verify a submitted captcha.
|
||||
# ReCaptcha\ReCaptcha:
|
||||
# arguments:
|
||||
# $secret: '%env(GOOGLE_RECAPTCHA_SECRET)%'
|
||||
# $requestMethod: '@ReCaptcha\RequestMethod'
|
||||
#
|
||||
# # Curl is set here as default transport to communicate with Google servers.
|
||||
# # If you do not have php-curl extension, you can change for a socket or a plain POST request.
|
||||
# # Check out the repository for all other request methods:
|
||||
# # https://github.com/google/recaptcha/tree/master/src/ReCaptcha/RequestMethod
|
||||
# ReCaptcha\RequestMethod: '@ReCaptcha\RequestMethod\CurlPost'
|
||||
# ReCaptcha\RequestMethod\CurlPost: null
|
||||
# ReCaptcha\RequestMethod\Curl: null
|
||||
#
|
||||
## Uncomment this line if you want to inject the site key to all your Twig templates.
|
||||
## You can also inject the "google_recaptcha_site_key" container parameter to your controllers.
|
||||
##twig:
|
||||
## globals:
|
||||
## google_recaptcha_site_key: '%google_recaptcha_site_key%'
|
|
@ -1,4 +0,0 @@
|
|||
meteo_concept_h_captcha:
|
||||
hcaptcha:
|
||||
site_key: '%env(resolve:HCAPTCHA_SITE)%'
|
||||
secret: '%env(resolve:HCAPTCHA_SECRET)%'
|
|
@ -1,21 +0,0 @@
|
|||
services:
|
||||
# Register nyholm/psr7 services for autowiring with PSR-17 (HTTP factories)
|
||||
Psr\Http\Message\RequestFactoryInterface: '@nyholm.psr7.psr17_factory'
|
||||
Psr\Http\Message\ResponseFactoryInterface: '@nyholm.psr7.psr17_factory'
|
||||
Psr\Http\Message\ServerRequestFactoryInterface: '@nyholm.psr7.psr17_factory'
|
||||
Psr\Http\Message\StreamFactoryInterface: '@nyholm.psr7.psr17_factory'
|
||||
Psr\Http\Message\UploadedFileFactoryInterface: '@nyholm.psr7.psr17_factory'
|
||||
Psr\Http\Message\UriFactoryInterface: '@nyholm.psr7.psr17_factory'
|
||||
|
||||
# Register nyholm/psr7 services for autowiring with HTTPlug factories
|
||||
Http\Message\MessageFactory: '@nyholm.psr7.httplug_factory'
|
||||
Http\Message\RequestFactory: '@nyholm.psr7.httplug_factory'
|
||||
Http\Message\ResponseFactory: '@nyholm.psr7.httplug_factory'
|
||||
Http\Message\StreamFactory: '@nyholm.psr7.httplug_factory'
|
||||
Http\Message\UriFactory: '@nyholm.psr7.httplug_factory'
|
||||
|
||||
nyholm.psr7.psr17_factory:
|
||||
class: Nyholm\Psr7\Factory\Psr17Factory
|
||||
|
||||
nyholm.psr7.httplug_factory:
|
||||
class: Nyholm\Psr7\Factory\HttplugFactory
|
|
@ -6,10 +6,9 @@ security:
|
|||
class: App\Entity\User
|
||||
property: username
|
||||
manager_name: default
|
||||
encoders:
|
||||
password_hashers:
|
||||
App\Entity\User:
|
||||
# https://symfony.com/blog/new-in-symfony-4-3-native-password-encoder
|
||||
algorithm: 'auto'
|
||||
algorithm: sodium
|
||||
firewalls:
|
||||
dev:
|
||||
pattern: ^/(_(profiler|wdt)|css|images|js)/
|
||||
|
|
|
@ -13,6 +13,8 @@ services:
|
|||
- 'Symfony\Component\HttpKernel\Exception\BadRequestHttpException'
|
||||
- 'Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException'
|
||||
|
||||
# TODO: update:
|
||||
# https://docs.sentry.io/platforms/php/guides/symfony/#monolog-integration
|
||||
Sentry\Monolog\Handler:
|
||||
arguments:
|
||||
$hub: '@Sentry\State\HubInterface'
|
||||
|
|
|
@ -2,6 +2,4 @@ twig:
|
|||
paths: ['%kernel.project_dir%/templates']
|
||||
debug: '%kernel.debug%'
|
||||
strict_variables: '%kernel.debug%'
|
||||
form_themes:
|
||||
- 'bootstrap_4_layout.html.twig'
|
||||
- '@MeteoConceptHCaptcha/hcaptcha_form.html.twig'
|
||||
form_themes: ['bootstrap_4_layout.html.twig']
|
||||
|
|
|
@ -53,6 +53,12 @@ user_account:
|
|||
requirements:
|
||||
method: GET
|
||||
|
||||
user_account_password_change:
|
||||
path: /account/password
|
||||
controller: App\Controller\AccountController::changePassword
|
||||
requirements:
|
||||
method: POST
|
||||
|
||||
user_account_token_create:
|
||||
path: /profile/api/token/create
|
||||
controller: App\Controller\AccountController::addApiToken
|
||||
|
|
|
@ -59,8 +59,3 @@ services:
|
|||
class: App\Pager\View\TwitterBootstrap4PagelessView
|
||||
public: false
|
||||
tags: [{ name: pagerfanta.view, alias: twitter_bootstrap4_pageless }]
|
||||
|
||||
# for hCaptcha
|
||||
# https://github.com/Meteo-Concept/hcaptcha-bundle#configuration
|
||||
Psr\Http\Client\ClientInterface:
|
||||
class: Symfony\Component\HttpClient\Psr18Client
|
||||
|
|
|
@ -1,19 +0,0 @@
|
|||
version: '3.7'
|
||||
|
||||
services:
|
||||
magnetico-web:
|
||||
image: skobkin/magnetico-web-fpm
|
||||
build:
|
||||
context: ./docker
|
||||
container_name: magnetico-web-fpm
|
||||
network_mode: host
|
||||
ports:
|
||||
- "127.0.0.1:${PHP_FPM_PORT:-9000}:9000/tcp"
|
||||
restart: unless-stopped
|
||||
volumes:
|
||||
- "${APP_LOCAL_PATH}:${APP_LOCAL_PATH}"
|
||||
logging:
|
||||
driver: "json-file"
|
||||
options:
|
||||
max-size: "${LOG_MAX_SIZE:-5m}"
|
||||
max-file: "${LOG_MAX_FILE:-5}"
|
|
@ -1,11 +0,0 @@
|
|||
FROM php:7.4-fpm-alpine
|
||||
|
||||
ENV PHP_TIMEZONE Europe/Moscow
|
||||
|
||||
RUN apk update && \
|
||||
apk add postgresql-dev libpq && \
|
||||
docker-php-ext-configure pdo_pgsql && \
|
||||
docker-php-ext-install -j$(nproc) pdo_pgsql && \
|
||||
apk del postgresql-dev && \
|
||||
mv "$PHP_INI_DIR/php.ini-production" "$PHP_INI_DIR/php.ini" && \
|
||||
echo "date.timezone = $PHP_TIMEZONE" > $PHP_INI_DIR/conf.d/timezone.ini
|
|
@ -1,4 +1,5 @@
|
|||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Api\V1\Controller;
|
||||
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Api\V1\Controller;
|
||||
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Api\V1\Controller;
|
||||
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Api\V1\Controller;
|
||||
|
||||
|
|
|
@ -1,9 +1,10 @@
|
|||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Api\V1\DTO;
|
||||
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
use Symfony\Component\Serializer\Annotation\{Groups, MaxDepth};
|
||||
use Symfony\Component\Serializer\Annotation\Groups;
|
||||
|
||||
class ApiResponse
|
||||
{
|
||||
|
@ -12,33 +13,20 @@ class ApiResponse
|
|||
public const STATUS_FAIL = 'fail';
|
||||
public const STATUS_UNKNOWN = 'unknown';
|
||||
|
||||
/**
|
||||
* @var int HTTP response status code
|
||||
*
|
||||
* @Groups({"api"})
|
||||
*/
|
||||
private $code;
|
||||
#[Groups(['api'])]
|
||||
private int $code;
|
||||
|
||||
/**
|
||||
* @var string Status text: 'success' (1xx-3xx), 'error' (4xx), 'fail' (5xx) or 'unknown'
|
||||
*
|
||||
* @Groups({"api"})
|
||||
*/
|
||||
private $status;
|
||||
/** Status text: 'success' (1xx-3xx), 'error' (4xx), 'fail' (5xx) or 'unknown' */
|
||||
#[Groups(['api'])]
|
||||
private string $status;
|
||||
|
||||
/**
|
||||
* @var string|null Used for 'fail' and 'error'
|
||||
*
|
||||
* @Groups({"api"})
|
||||
*/
|
||||
private $message;
|
||||
/** Used for 'fail' and 'error') */
|
||||
#[Groups(['api'])]
|
||||
private ?string $message;
|
||||
|
||||
/**
|
||||
* @var string|\object|array|null Response body. In case of 'error' or 'fail' contains cause or exception name.
|
||||
*
|
||||
* @Groups({"api"})
|
||||
*/
|
||||
private $data;
|
||||
/** @Response body. In case of 'error' or 'fail' contains cause or exception name. */
|
||||
#[Groups(['api'])]
|
||||
private string|object|array|null $data;
|
||||
|
||||
public function __construct($data = null, int $code = Response::HTTP_OK, string $message = null, string $status = '')
|
||||
{
|
||||
|
|
|
@ -1,46 +1,27 @@
|
|||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Api\V1\DTO;
|
||||
|
||||
use Pagerfanta\Pagerfanta;
|
||||
use Symfony\Component\Serializer\Annotation as Serializer;
|
||||
use Symfony\Component\Serializer\Annotation\Groups;
|
||||
|
||||
class ListPage
|
||||
{
|
||||
/**
|
||||
* @var int
|
||||
*
|
||||
* @Serializer\Groups({"api"})
|
||||
*/
|
||||
private $numberOfPages;
|
||||
#[Groups(['api'])]
|
||||
private int $numberOfPages;
|
||||
|
||||
/**
|
||||
* @var int
|
||||
*
|
||||
* @Serializer\Groups({"api"})
|
||||
*/
|
||||
private $currentPage;
|
||||
#[Groups(['api'])]
|
||||
private int $currentPage;
|
||||
|
||||
/**
|
||||
* @var int
|
||||
*
|
||||
* @Serializer\Groups({"api"})
|
||||
*/
|
||||
private $numberOfResults;
|
||||
#[Groups(['api'])]
|
||||
private int $numberOfResults;
|
||||
|
||||
/**
|
||||
* @var int
|
||||
*
|
||||
* @Serializer\Groups({"api"})
|
||||
*/
|
||||
private $maxPerPage;
|
||||
#[Groups(['api'])]
|
||||
private int $maxPerPage;
|
||||
|
||||
/**
|
||||
* @var \Traversable
|
||||
*
|
||||
* @Serializer\Groups({"api"})
|
||||
*/
|
||||
protected $items;
|
||||
#[Groups(['api'])]
|
||||
protected \Traversable $items;
|
||||
|
||||
public function __construct(\Traversable $items, int $numberOfResults, int $numberOfPages, int $currentPage, int $maxPerPage)
|
||||
{
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Command;
|
||||
|
||||
|
@ -11,22 +12,12 @@ use Symfony\Component\Console\Output\OutputInterface;
|
|||
|
||||
class AddInvitesCommand extends Command
|
||||
{
|
||||
/** @var EntityManagerInterface */
|
||||
private $em;
|
||||
|
||||
/** @var UserRepository */
|
||||
private $userRepo;
|
||||
|
||||
/** @var InviteManager */
|
||||
private $inviteManager;
|
||||
|
||||
public function __construct(EntityManagerInterface $em, UserRepository $userRepo, InviteManager $inviteManager)
|
||||
{
|
||||
public function __construct(
|
||||
private readonly EntityManagerInterface $em,
|
||||
private readonly UserRepository $userRepo,
|
||||
private readonly InviteManager $inviteManager
|
||||
) {
|
||||
parent::__construct();
|
||||
|
||||
$this->em = $em;
|
||||
$this->userRepo = $userRepo;
|
||||
$this->inviteManager = $inviteManager;
|
||||
}
|
||||
|
||||
protected function configure()
|
||||
|
@ -38,23 +29,23 @@ class AddInvitesCommand extends Command
|
|||
;
|
||||
}
|
||||
|
||||
protected function execute(InputInterface $input, OutputInterface $output)
|
||||
protected function execute(InputInterface $input, OutputInterface $output): int
|
||||
{
|
||||
$username = $input->getArgument('username');
|
||||
$number = $input->getArgument('number');
|
||||
$number = (int) $input->getArgument('number');
|
||||
|
||||
if (null === $user = $this->userRepo->findOneBy(['username' => $username])) {
|
||||
$output->writeln('<error>User not found.</error>');
|
||||
|
||||
return 1;
|
||||
return Command::FAILURE;
|
||||
}
|
||||
|
||||
$this->inviteManager->createInvitesForUser($user, $number);
|
||||
|
||||
$this->em->flush();
|
||||
|
||||
$output->writeln(sprintf('<info>%d invites added to \'%s\'.</info>', $number, $user->getUsername()));
|
||||
$output->writeln(sprintf('<info>%d invites added to \'%s\'.</info>', $number, $user->getUserIdentifier()));
|
||||
|
||||
return 0;
|
||||
return Command::SUCCESS;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Command;
|
||||
|
||||
|
@ -13,26 +14,13 @@ use Symfony\Component\Console\Question\Question;
|
|||
|
||||
class AddUserCommand extends Command
|
||||
{
|
||||
/** @var EntityManagerInterface */
|
||||
private $em;
|
||||
|
||||
/** @var UserManager */
|
||||
private $userManager;
|
||||
|
||||
/** @var UserRepository */
|
||||
private $userRepo;
|
||||
|
||||
/** @var InviteManager */
|
||||
private $inviteManager;
|
||||
|
||||
public function __construct(EntityManagerInterface $em, UserManager $userManager, UserRepository $userRepo, InviteManager $inviteManager)
|
||||
{
|
||||
public function __construct(
|
||||
private readonly EntityManagerInterface $em,
|
||||
private readonly UserManager $userManager,
|
||||
private readonly UserRepository $userRepo,
|
||||
private readonly InviteManager $inviteManager,
|
||||
) {
|
||||
parent::__construct();
|
||||
|
||||
$this->em = $em;
|
||||
$this->userManager = $userManager;
|
||||
$this->userRepo = $userRepo;
|
||||
$this->inviteManager = $inviteManager;
|
||||
}
|
||||
|
||||
protected function configure()
|
||||
|
@ -47,7 +35,7 @@ class AddUserCommand extends Command
|
|||
;
|
||||
}
|
||||
|
||||
protected function execute(InputInterface $input, OutputInterface $output)
|
||||
protected function execute(InputInterface $input, OutputInterface $output): int
|
||||
{
|
||||
$username = $input->getArgument('username');
|
||||
$email = $input->getArgument('email');
|
||||
|
@ -68,7 +56,7 @@ class AddUserCommand extends Command
|
|||
if (!$password) {
|
||||
$output->writeln('<error>User password cannot be empty.</error>');
|
||||
|
||||
return 1;
|
||||
return Command::FAILURE;
|
||||
}
|
||||
|
||||
if ($roles) {
|
||||
|
@ -86,9 +74,9 @@ class AddUserCommand extends Command
|
|||
|
||||
$this->em->flush();
|
||||
|
||||
$output->writeln(sprintf('<info>User \'%s\' registered, %d invites added.</info>', $user->getUsername(), $invites));
|
||||
$output->writeln(sprintf('<info>User \'%s\' registered, %d invites added.</info>', $user->getUserIdentifier(), $invites));
|
||||
|
||||
return 0;
|
||||
return Command::SUCCESS;
|
||||
}
|
||||
|
||||
}
|
|
@ -1,19 +1,25 @@
|
|||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Controller;
|
||||
|
||||
use App\Entity\ApiToken;
|
||||
use App\Entity\User;
|
||||
use App\Repository\ApiTokenRepository;
|
||||
use App\Repository\InviteRepository;
|
||||
use App\Entity\{ApiToken, User};
|
||||
use App\Repository\{ApiTokenRepository, InviteRepository};
|
||||
use App\Form\Data\PasswordChangeData;
|
||||
use App\Form\PasswordChangeType;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
use Symfony\Component\Form\Extension\Core\Type\SubmitType;
|
||||
use Symfony\Component\Form\FormInterface;
|
||||
use Symfony\Component\HttpFoundation\{Request, Response};
|
||||
use Symfony\Component\PasswordHasher\Hasher\PasswordHasherFactoryInterface;
|
||||
|
||||
class AccountController extends AbstractController
|
||||
{
|
||||
public function account(InviteRepository $inviteRepo, ApiTokenRepository $apiTokenRepo): Response
|
||||
{
|
||||
public function account(
|
||||
InviteRepository $inviteRepo,
|
||||
ApiTokenRepository $apiTokenRepo
|
||||
): Response {
|
||||
/** @var User $user */
|
||||
if (null === $user = $this->getUser()) {
|
||||
throw $this->createAccessDeniedException('User not found exception');
|
||||
|
@ -27,6 +33,32 @@ class AccountController extends AbstractController
|
|||
]);
|
||||
}
|
||||
|
||||
public function changePassword(
|
||||
Request $request,
|
||||
EntityManagerInterface $em,
|
||||
PasswordHasherFactoryInterface $hasherFactory,
|
||||
): Response {
|
||||
$data = new PasswordChangeData();
|
||||
$form = $this->createChangePasswordForm($data);
|
||||
$form->handleRequest($request);
|
||||
|
||||
if ($form->isSubmitted() && $form->isValid()) {
|
||||
/** @var User $user */
|
||||
$user = $this->getUser();
|
||||
$hasher = $hasherFactory->getPasswordHasher($user);
|
||||
|
||||
$user->changePassword($hasher, $data->newPassword);
|
||||
$em->flush();
|
||||
$this->addFlash('success', 'Password changed.');
|
||||
|
||||
return $this->redirectToRoute('user_account');
|
||||
}
|
||||
|
||||
return $this->renderForm('Account/password.html.twig', [
|
||||
'form' => $form,
|
||||
]);
|
||||
}
|
||||
|
||||
public function addApiToken(EntityManagerInterface $em): Response
|
||||
{
|
||||
/** @var User|null $user */
|
||||
|
@ -58,4 +90,13 @@ class AccountController extends AbstractController
|
|||
|
||||
return $this->redirectToRoute('user_account');
|
||||
}
|
||||
|
||||
private function createChangePasswordForm(PasswordChangeData $data): FormInterface
|
||||
{
|
||||
return $this
|
||||
->createForm(PasswordChangeType::class, $data, [
|
||||
'action' => $this->generateUrl('user_account_password_change'),
|
||||
])
|
||||
->add('submit', SubmitType::class);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Controller;
|
||||
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Controller;
|
||||
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Controller;
|
||||
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Controller;
|
||||
|
||||
|
|
|
@ -1,16 +1,19 @@
|
|||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Controller;
|
||||
|
||||
use App\Entity\{Invite, PasswordResetToken};
|
||||
use App\Repository\PasswordResetTokenRepository;
|
||||
use App\Form\{Data\PasswordResetRequestData, Data\PasswordResetData, PasswordResetRequestType, PasswordResetType, RegisterType, Data\RegisterData};
|
||||
use App\Form\Data\{PasswordResetRequestData, PasswordResetData, RegisterData};
|
||||
use App\Form\{PasswordResetRequestType, PasswordResetType, RegisterType};
|
||||
use App\Repository\InviteRepository;
|
||||
use App\User\{Exception\UserNotFoundException, InviteManager, PasswordResetManager, UserManager};
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
||||
use Symfony\Component\Form\{Extension\Core\Type\SubmitType, FormError, FormInterface};
|
||||
use Symfony\Component\HttpFoundation\{Request, Response};
|
||||
use Symfony\Component\PasswordHasher\Hasher\PasswordHasherFactoryInterface;
|
||||
|
||||
class UserController extends AbstractController
|
||||
{
|
||||
|
@ -22,8 +25,8 @@ class UserController extends AbstractController
|
|||
InviteManager $inviteManager,
|
||||
InviteRepository $inviteRepo
|
||||
): Response {
|
||||
$formData = new RegisterData($code);
|
||||
$form = $this->createRegisterForm($formData, $code);
|
||||
$data = new RegisterData($code);
|
||||
$form = $this->createRegisterForm($data, $code);
|
||||
|
||||
/** @var Invite $invite */
|
||||
$invite = $inviteRepo->findOneBy(['code' => $code, 'usedBy' => null]);
|
||||
|
@ -32,9 +35,9 @@ class UserController extends AbstractController
|
|||
|
||||
if ($form->isSubmitted() && $form->isValid()) {
|
||||
$user = $userManager->createUserByInvite(
|
||||
$formData->username,
|
||||
$formData->password,
|
||||
$formData->email,
|
||||
$data->username,
|
||||
$data->password,
|
||||
$data->email,
|
||||
$invite
|
||||
);
|
||||
|
||||
|
@ -51,10 +54,10 @@ class UserController extends AbstractController
|
|||
]);
|
||||
}
|
||||
|
||||
public function requestReset(Request $request, PasswordResetManager $manager): Response
|
||||
public function requestReset(Request $request, EntityManagerInterface $em, PasswordResetManager $manager): Response
|
||||
{
|
||||
$formData = new PasswordResetRequestData();
|
||||
$form = $this->createResetRequestForm($formData);
|
||||
$data = new PasswordResetRequestData();
|
||||
$form = $this->createResetRequestForm($data);
|
||||
|
||||
$form->handleRequest($request);
|
||||
|
||||
|
@ -62,7 +65,7 @@ class UserController extends AbstractController
|
|||
|
||||
if ($form->isSubmitted() && $form->isValid()) {
|
||||
try {
|
||||
$manager->sendResetLink($formData->email);
|
||||
$manager->sendResetLink($data->email);
|
||||
|
||||
$message = 'Password reset link was sent';
|
||||
} catch (UserNotFoundException $e) {
|
||||
|
@ -83,12 +86,12 @@ class UserController extends AbstractController
|
|||
string $code,
|
||||
Request $request,
|
||||
EntityManagerInterface $em,
|
||||
UserManager $manager,
|
||||
PasswordResetTokenRepository $tokenRepository
|
||||
PasswordHasherFactoryInterface $hasherFactory,
|
||||
PasswordResetTokenRepository $tokenRepository,
|
||||
): Response
|
||||
{
|
||||
$formData = new PasswordResetData();
|
||||
$form = $this->createResetForm($formData, $code);
|
||||
$data = new PasswordResetData();
|
||||
$form = $this->createPasswordResetForm($data, $code);
|
||||
|
||||
/** @var PasswordResetToken $token */
|
||||
$token = $tokenRepository->find($code);
|
||||
|
@ -97,15 +100,17 @@ class UserController extends AbstractController
|
|||
|
||||
if ($form->isSubmitted() && $form->isValid()) {
|
||||
if ($token && $token->isValid()) {
|
||||
$manager->changePassword($token->getUser(), $formData->password);
|
||||
$user = $token->getUser();
|
||||
$hasher = $hasherFactory->getPasswordHasher($user);
|
||||
$user->changePassword($hasher, $data->password);
|
||||
|
||||
$em->remove($token);
|
||||
$em->flush();
|
||||
|
||||
return $this->redirectToRoute('user_auth_login');
|
||||
} else {
|
||||
$form->addError(new FormError('Invalid token.'));
|
||||
}
|
||||
|
||||
$form->addError(new FormError('Invalid token.'));
|
||||
}
|
||||
|
||||
return $this->render('User/reset.html.twig', [
|
||||
|
@ -115,32 +120,28 @@ class UserController extends AbstractController
|
|||
|
||||
private function createResetRequestForm(PasswordResetRequestData $formData): FormInterface
|
||||
{
|
||||
$form = $this->createForm(PasswordResetRequestType::class, $formData, [
|
||||
'action' => $this->generateUrl('user_reset_request'),
|
||||
]);
|
||||
$form->add('submit', SubmitType::class);
|
||||
|
||||
return $form;
|
||||
return $this
|
||||
->createForm(PasswordResetRequestType::class, $formData, [
|
||||
'action' => $this->generateUrl('user_reset_request'),
|
||||
])
|
||||
->add('submit', SubmitType::class);
|
||||
}
|
||||
|
||||
private function createResetForm(PasswordResetData $formData, string $code): FormInterface
|
||||
private function createPasswordResetForm(PasswordResetData $data, string $code): FormInterface
|
||||
{
|
||||
$form = $this->createForm(PasswordResetType::class, $formData, [
|
||||
'action' => $this->generateUrl('user_reset', ['code' => $code]),
|
||||
]);
|
||||
$form->add('submit', SubmitType::class);
|
||||
|
||||
return $form;
|
||||
return $this
|
||||
->createForm(PasswordResetType::class, $data, [
|
||||
'action' => $this->generateUrl('user_reset', ['code' => $code]),
|
||||
])
|
||||
->add('submit', SubmitType::class);
|
||||
}
|
||||
|
||||
private function createRegisterForm(RegisterData $formData, string $code): FormInterface
|
||||
{
|
||||
$form = $this->createForm(RegisterType::class, $formData, [
|
||||
'action' => $this->generateUrl('user_register', ['code' => $code]),
|
||||
]);
|
||||
|
||||
$form->add('submit', SubmitType::class);
|
||||
|
||||
return $form;
|
||||
return $this
|
||||
->createForm(RegisterType::class, $formData, [
|
||||
'action' => $this->generateUrl('user_register', ['code' => $code]),
|
||||
])
|
||||
->add('submit', SubmitType::class);
|
||||
}
|
||||
}
|
|
@ -1,5 +1,4 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Doctrine\ORM\AST;
|
||||
|
@ -14,14 +13,13 @@ use Doctrine\ORM\Query\{AST\Functions\FunctionNode, AST\Node, Lexer, Parser, Sql
|
|||
*/
|
||||
abstract class BaseFunction extends FunctionNode
|
||||
{
|
||||
/** @var string */
|
||||
protected $functionPrototype;
|
||||
protected string $functionPrototype;
|
||||
|
||||
/** @var string[] */
|
||||
protected $nodesMapping = [];
|
||||
protected array $nodesMapping = [];
|
||||
|
||||
/** @var Node[] */
|
||||
protected $nodes = [];
|
||||
protected array $nodes = [];
|
||||
|
||||
abstract protected function customiseFunction(): void;
|
||||
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Doctrine\ORM\AST;
|
||||
|
|
|
@ -1,40 +1,27 @@
|
|||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Entity;
|
||||
|
||||
use App\Repository\ApiTokenRepository;
|
||||
use Doctrine\ORM\Mapping as ORM;
|
||||
use Symfony\Component\Serializer\Annotation as Serializer;
|
||||
|
||||
/**
|
||||
* @ORM\Table(name="api_tokens", schema="users")
|
||||
* @ORM\Entity(repositoryClass="App\Repository\ApiTokenRepository", readOnly=true)
|
||||
*/
|
||||
#[ORM\Table(name: 'api_tokens', schema: 'users')]
|
||||
#[ORM\Entity(repositoryClass: ApiTokenRepository::class, readOnly: true)]
|
||||
class ApiToken
|
||||
{
|
||||
/**
|
||||
* @var User
|
||||
*
|
||||
* @ORM\ManyToOne(targetEntity="App\Entity\User")
|
||||
* @ORM\JoinColumn(name="user_id", nullable=false)
|
||||
*/
|
||||
private $user;
|
||||
#[ORM\ManyToOne(targetEntity: User::class)]
|
||||
#[ORM\JoinColumn(name: 'user_id', nullable: false)]
|
||||
private User $user;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*
|
||||
* @Serializer\Groups({"api", "api_v1_login"})
|
||||
*
|
||||
* @ORM\Id()
|
||||
* @ORM\Column(name="key", type="string", length=32)
|
||||
*/
|
||||
private $key;
|
||||
#[Serializer\Groups(['api', 'api_v1_login'])]
|
||||
#[ORM\Id]
|
||||
#[ORM\Column(name: 'key', type: 'string', length: 32)]
|
||||
private string $key;
|
||||
|
||||
/**
|
||||
* @var \DateTime
|
||||
*
|
||||
* @ORM\Column(name="created_at", type="datetime")
|
||||
*/
|
||||
private $createdAt;
|
||||
#[ORM\Column(name: 'created_at', type: 'datetime')]
|
||||
private \DateTime $createdAt;
|
||||
|
||||
public function __construct(User $user)
|
||||
{
|
||||
|
|
|
@ -1,51 +1,35 @@
|
|||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Entity;
|
||||
|
||||
use App\Repository\InviteRepository;
|
||||
use Doctrine\ORM\Mapping as ORM;
|
||||
|
||||
/**
|
||||
* @ORM\Table(name="invites", schema="users")
|
||||
* @ORM\Entity(repositoryClass="App\Repository\InviteRepository")
|
||||
*/
|
||||
#[ORM\Table(name: 'invites', schema: 'users')]
|
||||
#[ORM\Entity(repositoryClass: InviteRepository::class)]
|
||||
class Invite
|
||||
{
|
||||
/**
|
||||
* @var int
|
||||
*
|
||||
* @ORM\Id()
|
||||
* @ORM\GeneratedValue(strategy="AUTO")
|
||||
* @ORM\Column(name="id", type="integer")
|
||||
*/
|
||||
private $id;
|
||||
#[ORM\Id]
|
||||
#[ORM\GeneratedValue(strategy: 'AUTO')]
|
||||
#[ORM\Column(name: 'id', type: 'integer')]
|
||||
private int $id;
|
||||
|
||||
/**
|
||||
* @var User
|
||||
*
|
||||
* @ORM\ManyToOne(targetEntity="App\Entity\User", inversedBy="invites")
|
||||
* @ORM\JoinColumn(name="user_id", nullable=false)
|
||||
*/
|
||||
private $user;
|
||||
#[ORM\ManyToOne(targetEntity: User::class, inversedBy: 'invites')]
|
||||
#[ORM\JoinColumn(name: 'user_id', nullable: false)]
|
||||
private User $user;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*
|
||||
* @ORM\Column(name="code", type="string", length=32, unique=true)
|
||||
*/
|
||||
private $code;
|
||||
#[ORM\Column(name: 'code', type: 'string', length: 32, unique: true)]
|
||||
private string $code;
|
||||
|
||||
/**
|
||||
* @var User|null
|
||||
*
|
||||
* @ORM\ManyToOne(targetEntity="App\Entity\User")
|
||||
* @ORM\JoinColumn(name="used_by_id", nullable=true)
|
||||
*/
|
||||
private $usedBy;
|
||||
#[ORM\ManyToOne(targetEntity: User::class)]
|
||||
#[ORM\JoinColumn(name: 'used_by_id', nullable: true)]
|
||||
private ?User $usedBy;
|
||||
|
||||
public function __construct(User $forUser)
|
||||
{
|
||||
$this->user = $forUser;
|
||||
$this->code = md5(random_bytes(100));
|
||||
$this->code = md5(\random_bytes(100));
|
||||
}
|
||||
|
||||
public function getId(): int
|
||||
|
|
|
@ -1,38 +1,24 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Entity;
|
||||
|
||||
use Doctrine\ORM\Mapping as ORM;
|
||||
|
||||
/**
|
||||
* @ORM\Table(schema="users", name="password_reset_tokens")
|
||||
* @ORM\Entity(readOnly=true)
|
||||
*/
|
||||
#[ORM\Table(schema: 'users', name: 'password_reset_tokens')]
|
||||
#[ORM\Entity(readOnly: true)]
|
||||
class PasswordResetToken
|
||||
{
|
||||
/**
|
||||
* @var User
|
||||
*
|
||||
* @ORM\ManyToOne(targetEntity="User", fetch="EAGER")
|
||||
* @ORM\JoinColumn(name="user_id", nullable=false, onDelete="CASCADE")
|
||||
*/
|
||||
private $user;
|
||||
#[ORM\ManyToOne(targetEntity: User::class, fetch: 'EAGER')]
|
||||
#[ORM\JoinColumn(name: 'user_id', nullable: false, onDelete: 'CASCADE')]
|
||||
private User $user;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*
|
||||
* @ORM\Id()
|
||||
* @ORM\Column(name="code", type="text", nullable=false)
|
||||
*/
|
||||
private $code;
|
||||
#[ORM\Id]
|
||||
#[ORM\Column(name: 'code', type: 'text', nullable: false)]
|
||||
private string $code;
|
||||
|
||||
/**
|
||||
* @var \DateTime
|
||||
*
|
||||
* @ORM\Column(name="valid_until", type="datetime", nullable=false)
|
||||
*/
|
||||
private $validUntil;
|
||||
#[ORM\Column(name: 'valid_until', type: 'datetime', nullable: false)]
|
||||
private \DateTime $validUntil;
|
||||
|
||||
public function __construct(User $user, \DateInterval $validFor = null)
|
||||
{
|
||||
|
|
|
@ -1,73 +1,46 @@
|
|||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Entity;
|
||||
|
||||
use App\Repository\UserRepository;
|
||||
use Doctrine\Common\Collections\ArrayCollection;
|
||||
use Doctrine\ORM\Mapping as ORM;
|
||||
use Symfony\Component\Security\Core\Encoder\PasswordEncoderInterface;
|
||||
use Symfony\Component\Security\Core\User\UserInterface;
|
||||
use Symfony\Component\PasswordHasher\PasswordHasherInterface;
|
||||
use Symfony\Component\Security\Core\User\{PasswordAuthenticatedUserInterface, UserInterface};
|
||||
|
||||
/**
|
||||
* @ORM\Table(name="users", schema="users")
|
||||
* @ORM\Entity(repositoryClass="App\Repository\UserRepository")
|
||||
*/
|
||||
class User implements UserInterface, \Serializable
|
||||
#[ORM\Table(name: 'users', schema: 'users')]
|
||||
#[ORM\Entity(repositoryClass: UserRepository::class)]
|
||||
class User implements UserInterface, PasswordAuthenticatedUserInterface
|
||||
{
|
||||
/**
|
||||
* @var int
|
||||
*
|
||||
* @ORM\Id
|
||||
* @ORM\Column(name="id", type="integer")
|
||||
* @ORM\GeneratedValue(strategy="AUTO")
|
||||
*/
|
||||
private $id;
|
||||
#[ORM\Id]
|
||||
#[ORM\Column(name: 'id', type: 'integer')]
|
||||
#[ORM\GeneratedValue(strategy: 'AUTO')]
|
||||
private int $id;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*
|
||||
* @ORM\Column(name="username", type="string", length=25, unique=true)
|
||||
*/
|
||||
private $username;
|
||||
#[ORM\Column(name: 'username', type: 'string', length: 25, unique: true)]
|
||||
private string $username;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*
|
||||
* @ORM\Column(name="password", type="text")
|
||||
*/
|
||||
private $password;
|
||||
#[ORM\Column(name: 'password', type: 'text')]
|
||||
private string $password;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*
|
||||
* @ORM\Column(name="email", type="string", length=254, unique=true)
|
||||
*/
|
||||
private $email;
|
||||
#[ORM\Column(name: 'email', type: 'string', length: 254, unique: true)]
|
||||
private string $email;
|
||||
|
||||
/**
|
||||
* @var string[]
|
||||
*
|
||||
* @ORM\Column(name="roles", type="json")
|
||||
*/
|
||||
private $roles = [];
|
||||
#[ORM\Column(name: 'roles', type: 'json')]
|
||||
private array $roles = [];
|
||||
|
||||
/**
|
||||
* @var \DateTime
|
||||
*
|
||||
* @ORM\Column(name="created_at", type="datetime")
|
||||
*/
|
||||
private $createdAt;
|
||||
#[ORM\Column(name: 'created_at', type: 'datetime')]
|
||||
private \DateTime $createdAt;
|
||||
|
||||
/**
|
||||
* @var Invite[]|ArrayCollection
|
||||
*
|
||||
* @ORM\OneToMany(targetEntity="App\Entity\Invite", mappedBy="user", fetch="EXTRA_LAZY")
|
||||
*/
|
||||
/** @var Invite[]|ArrayCollection */
|
||||
#[ORM\OneToMany(targetEntity: Invite::class, mappedBy: 'user', fetch: 'EXTRA_LAZY')]
|
||||
private $invites;
|
||||
|
||||
public function __construct(string $username, PasswordEncoderInterface $encoder, string $rawPassword, string $email, array $roles = [])
|
||||
public function __construct(string $username, PasswordHasherInterface $hasher, string $rawPassword, string $email, array $roles = [])
|
||||
{
|
||||
$this->username = $username;
|
||||
$this->password = $encoder->encodePassword($rawPassword, null);
|
||||
$this->password = $hasher->hash($rawPassword);
|
||||
$this->email = $email;
|
||||
$this->roles = $roles ?: ['ROLE_USER'];
|
||||
$this->createdAt = new \DateTime();
|
||||
|
@ -78,22 +51,29 @@ class User implements UserInterface, \Serializable
|
|||
return $this->id;
|
||||
}
|
||||
|
||||
public function getUsername()
|
||||
public function getUserIdentifier(): string
|
||||
{
|
||||
return $this->username;
|
||||
}
|
||||
|
||||
public function getPassword()
|
||||
/** @deprecated since Symfony 5.3, use getUserIdentifier() instead */
|
||||
public function getUsername(): string
|
||||
{
|
||||
return $this->username;
|
||||
}
|
||||
|
||||
public function getPassword(): string
|
||||
{
|
||||
return $this->password;
|
||||
}
|
||||
|
||||
public function changePassword(PasswordEncoderInterface $encoder, string $rawPassword): void
|
||||
public function changePassword(PasswordHasherInterface $hasher, string $rawPassword): void
|
||||
{
|
||||
$this->password = $encoder->encodePassword($rawPassword, null);
|
||||
$this->password = $hasher->hash($rawPassword);
|
||||
}
|
||||
|
||||
public function getSalt()
|
||||
/** @deprecated since Symfony 5.3 */
|
||||
public function getSalt(): ?string
|
||||
{
|
||||
// Salt is not needed when using Argon2i
|
||||
// @see https://symfony.com/doc/current/reference/configuration/security.html#using-the-argon2i-password-encoder
|
||||
|
@ -132,7 +112,7 @@ class User implements UserInterface, \Serializable
|
|||
}
|
||||
|
||||
/** @see \Serializable::serialize() */
|
||||
public function serialize()
|
||||
public function serialize(): string
|
||||
{
|
||||
return serialize([
|
||||
$this->id,
|
||||
|
@ -142,7 +122,7 @@ class User implements UserInterface, \Serializable
|
|||
}
|
||||
|
||||
/** @see \Serializable::unserialize() */
|
||||
public function unserialize($serialized)
|
||||
public function unserialize($serialized): void
|
||||
{
|
||||
[
|
||||
$this->id,
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
|
||||
namespace App\Feed;
|
||||
|
||||
use App\Magnet\MagnetGenerator;
|
||||
|
@ -21,15 +20,12 @@ class RssGenerator
|
|||
private const PER_PAGE = 1000;
|
||||
private const MIME_TYPE = 'application/x-bittorrent';
|
||||
|
||||
private TorrentRepository $repo;
|
||||
private RouterInterface $router;
|
||||
private MagnetGenerator $magnetGenerator;
|
||||
public function __construct(
|
||||
private readonly TorrentRepository $repo,
|
||||
private readonly RouterInterface $router,
|
||||
private readonly MagnetGenerator $magnetGenerator
|
||||
) {
|
||||
|
||||
public function __construct(TorrentRepository $repo, RouterInterface $router, MagnetGenerator $magnetGenerator)
|
||||
{
|
||||
$this->repo = $repo;
|
||||
$this->router = $router;
|
||||
$this->magnetGenerator = $magnetGenerator;
|
||||
}
|
||||
|
||||
public function generateLast(int $page): string
|
||||
|
@ -43,8 +39,6 @@ class RssGenerator
|
|||
|
||||
/**
|
||||
* @param Torrent[] $torrents
|
||||
*
|
||||
* @return Feed
|
||||
*/
|
||||
private function createFeedFromTorrents(array $torrents): Feed
|
||||
{
|
||||
|
|
19
src/Form/Data/PasswordChangeData.php
Normal file
19
src/Form/Data/PasswordChangeData.php
Normal file
|
@ -0,0 +1,19 @@
|
|||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Form\Data;
|
||||
|
||||
use Symfony\Component\Validator\Constraints as Assert;
|
||||
use Symfony\Component\Security\Core\Validator\Constraints as SecurityAssert;
|
||||
|
||||
class PasswordChangeData
|
||||
{
|
||||
#[Assert\NotBlank]
|
||||
#[SecurityAssert\UserPassword(message: 'Wrong password')]
|
||||
public string $currentPassword;
|
||||
|
||||
#[Assert\NotBlank]
|
||||
#[Assert\Length(min: 8, max: 4096)]
|
||||
#[Assert\NotCompromisedPassword(skipOnError: true)]
|
||||
public string $newPassword;
|
||||
}
|
|
@ -1,4 +1,5 @@
|
|||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Form\Data;
|
||||
|
||||
|
@ -6,12 +7,8 @@ use Symfony\Component\Validator\Constraints as Assert;
|
|||
|
||||
class PasswordResetData
|
||||
{
|
||||
/**
|
||||
* @var string
|
||||
*
|
||||
* @Assert\NotBlank
|
||||
* @Assert\Length(min="8", max="4096")
|
||||
* @Assert\NotCompromisedPassword(skipOnError=true)
|
||||
*/
|
||||
public $password;
|
||||
#[Assert\NotBlank]
|
||||
#[Assert\Length(min: 8, max: 4096)]
|
||||
#[Assert\NotCompromisedPassword(skipOnError: true)]
|
||||
public string $password;
|
||||
}
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Form\Data;
|
||||
|
||||
|
@ -7,11 +8,10 @@ use Symfony\Component\Validator\Constraints as Assert;
|
|||
|
||||
class PasswordResetRequestData
|
||||
{
|
||||
/**
|
||||
* @var string
|
||||
*
|
||||
* @Assert\Email()
|
||||
* @Assert\NotBlank()
|
||||
*/
|
||||
public $email;
|
||||
#[Assert\Email]
|
||||
#[Assert\NotBlank]
|
||||
public string $email;
|
||||
|
||||
#[ReCaptcha\IsTrueV3]
|
||||
public string $recaptcha;
|
||||
}
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Form\Data;
|
||||
|
||||
|
@ -10,37 +11,21 @@ use App\Validator\Constraints as AppAssert;
|
|||
*/
|
||||
class RegisterData
|
||||
{
|
||||
/**
|
||||
* @var string
|
||||
*
|
||||
* @Assert\NotBlank()
|
||||
* @Assert\Length(min="2", max="25")
|
||||
*/
|
||||
public $username;
|
||||
#[Assert\NotBlank]
|
||||
#[Assert\Length(min: 2, max: 25)]
|
||||
public string $username;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*
|
||||
* @Assert\NotBlank()
|
||||
* @Assert\Length(min="8", max="4096")
|
||||
*/
|
||||
public $password;
|
||||
#[Assert\NotBlank]
|
||||
#[Assert\Length(min: 8, max: 4096)]
|
||||
public string $password;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*
|
||||
* @Assert\Email()
|
||||
*/
|
||||
public $email;
|
||||
#[Assert\Email]
|
||||
public string $email;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*
|
||||
* @Assert\NotBlank()
|
||||
* @Assert\Length(min="32", max="32")
|
||||
* @AppAssert\ValidInvite()
|
||||
*/
|
||||
public $inviteCode;
|
||||
#[Assert\NotBlank]
|
||||
#[Assert\Length(min: 32, max: 32)]
|
||||
#[AppAssert\ValidInvite()]
|
||||
public string $inviteCode;
|
||||
|
||||
public function __construct(string $inviteCode = null)
|
||||
{
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Form;
|
||||
|
||||
|
@ -8,7 +9,7 @@ use Symfony\Component\Form\FormBuilderInterface;
|
|||
|
||||
class LoginType extends AbstractType
|
||||
{
|
||||
public function buildForm(FormBuilderInterface $builder, array $options)
|
||||
public function buildForm(FormBuilderInterface $builder, array $options): void
|
||||
{
|
||||
$builder
|
||||
->add('_username', TextType::class, ['mapped' => false, 'required' => true])
|
||||
|
@ -16,9 +17,9 @@ class LoginType extends AbstractType
|
|||
;
|
||||
}
|
||||
|
||||
public function getBlockPrefix()
|
||||
public function getBlockPrefix(): string
|
||||
{
|
||||
// Empty prefix for default UsernamePasswordFrormAuthenticationListener
|
||||
// Empty prefix for default UsernamePasswordFormAuthenticationListener
|
||||
return '';
|
||||
}
|
||||
}
|
||||
|
|
38
src/Form/PasswordChangeType.php
Normal file
38
src/Form/PasswordChangeType.php
Normal file
|
@ -0,0 +1,38 @@
|
|||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Form;
|
||||
|
||||
use App\Form\Data\PasswordChangeData;
|
||||
use Symfony\Component\Form\AbstractType;
|
||||
use Symfony\Component\Form\Extension\Core\Type\{PasswordType, RepeatedType};
|
||||
use Symfony\Component\Form\FormBuilderInterface;
|
||||
use Symfony\Component\OptionsResolver\OptionsResolver;
|
||||
|
||||
class PasswordChangeType extends AbstractType
|
||||
{
|
||||
public function buildForm(FormBuilderInterface $builder, array $options): void
|
||||
{
|
||||
$builder
|
||||
->add('currentPassword', PasswordType::class, [
|
||||
'label' => 'Current password',
|
||||
'invalid_message' => 'Wrong password.',
|
||||
'required' => true,
|
||||
])
|
||||
->add('newPassword', RepeatedType::class, [
|
||||
'type' => PasswordType::class,
|
||||
'invalid_message' => 'The password fields must match.',
|
||||
'required' => true,
|
||||
'first_options' => ['label' => 'New password'],
|
||||
'second_options' => ['label' => 'Repeat'],
|
||||
])
|
||||
;
|
||||
}
|
||||
|
||||
public function configureOptions(OptionsResolver $resolver): void
|
||||
{
|
||||
$resolver->setDefaults([
|
||||
'data_class' => PasswordChangeData::class,
|
||||
]);
|
||||
}
|
||||
}
|
|
@ -1,23 +1,24 @@
|
|||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Form;
|
||||
|
||||
use App\Form\Data\PasswordResetRequestData;
|
||||
use Symfony\Component\Form\{AbstractType, Extension\Core\Type\EmailType, FormBuilderInterface};
|
||||
use MeteoConcept\HCaptchaBundle\Form\HCaptchaType;
|
||||
use EWZ\Bundle\RecaptchaBundle\Form\Type\EWZRecaptchaV3Type;
|
||||
use Symfony\Component\OptionsResolver\OptionsResolver;
|
||||
|
||||
class PasswordResetRequestType extends AbstractType
|
||||
{
|
||||
public function buildForm(FormBuilderInterface $builder, array $options)
|
||||
public function buildForm(FormBuilderInterface $builder, array $options): void
|
||||
{
|
||||
$builder
|
||||
->add('email', EmailType::class, ['required' => true])
|
||||
->add('captcha', HCaptchaType::class)
|
||||
->add('recaptcha', EWZRecaptchaV3Type::class)
|
||||
;
|
||||
}
|
||||
|
||||
public function configureOptions(OptionsResolver $resolver)
|
||||
public function configureOptions(OptionsResolver $resolver): void
|
||||
{
|
||||
$resolver->setDefaults([
|
||||
'data_class' => PasswordResetRequestData::class,
|
||||
|
|
|
@ -1,16 +1,17 @@
|
|||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Form;
|
||||
|
||||
use App\Form\Data\PasswordResetData;
|
||||
use Symfony\Component\Form\AbstractType;
|
||||
use Symfony\Component\Form\Extension\Core\Type\{HiddenType, PasswordType, RepeatedType};
|
||||
use Symfony\Component\Form\Extension\Core\Type\{PasswordType, RepeatedType};
|
||||
use Symfony\Component\Form\FormBuilderInterface;
|
||||
use Symfony\Component\OptionsResolver\OptionsResolver;
|
||||
|
||||
class PasswordResetType extends AbstractType
|
||||
{
|
||||
public function buildForm(FormBuilderInterface $builder, array $options)
|
||||
public function buildForm(FormBuilderInterface $builder, array $options): void
|
||||
{
|
||||
$builder
|
||||
->add('password', RepeatedType::class, [
|
||||
|
@ -23,7 +24,7 @@ class PasswordResetType extends AbstractType
|
|||
;
|
||||
}
|
||||
|
||||
public function configureOptions(OptionsResolver $resolver)
|
||||
public function configureOptions(OptionsResolver $resolver): void
|
||||
{
|
||||
$resolver->setDefaults([
|
||||
'data_class' => PasswordResetData::class,
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Form;
|
||||
|
||||
|
@ -10,7 +11,7 @@ use Symfony\Component\OptionsResolver\OptionsResolver;
|
|||
|
||||
class RegisterType extends AbstractType
|
||||
{
|
||||
public function buildForm(FormBuilderInterface $builder, array $options)
|
||||
public function buildForm(FormBuilderInterface $builder, array $options): void
|
||||
{
|
||||
$builder
|
||||
->add('username', TextType::class, ['required' => true])
|
||||
|
@ -20,7 +21,7 @@ class RegisterType extends AbstractType
|
|||
;
|
||||
}
|
||||
|
||||
public function configureOptions(OptionsResolver $resolver)
|
||||
public function configureOptions(OptionsResolver $resolver): void
|
||||
{
|
||||
$resolver->setDefaults([
|
||||
'data_class' => RegisterData::class,
|
||||
|
|
|
@ -1,11 +1,12 @@
|
|||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Helper;
|
||||
|
||||
use App\Magnetico\Entity\Torrent;
|
||||
use App\View\Torrent\FileTreeNode;
|
||||
|
||||
class BstreeviewFileTreeBuilder
|
||||
class BsTreeviewFileTreeBuilder
|
||||
{
|
||||
private const DEFAULT_FILE_ICON = 'fas fa-file';
|
||||
private const DEFAULT_DIR_ICON = 'fas fa-folder';
|
|
@ -30,11 +30,7 @@ class FileSizeHumanizer
|
|||
|
||||
$maxSuffixIndex = count(self::SIZE_SUFFIXES) - 1;
|
||||
|
||||
if ($maxSuffixIndex >= $factor) {
|
||||
$suffixIndex = $factor;
|
||||
} else {
|
||||
$suffixIndex = $maxSuffixIndex;
|
||||
}
|
||||
$suffixIndex = min($maxSuffixIndex, $factor);
|
||||
|
||||
$suffix = self::SIZE_SUFFIXES[$suffixIndex];
|
||||
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App;
|
||||
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Magnet;
|
||||
|
||||
|
|
|
@ -1,51 +1,32 @@
|
|||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Magnetico\Entity;
|
||||
|
||||
use Doctrine\ORM\Mapping as ORM;
|
||||
use Symfony\Component\Serializer\Annotation as Serializer;
|
||||
|
||||
/**
|
||||
* @ORM\Table(schema="magneticod", name="files", indexes={
|
||||
* @ORM\Index(name="file_info_hash_index", columns={"torrent_id"})
|
||||
* })
|
||||
* @ORM\Entity(readOnly=true)
|
||||
*/
|
||||
#[ORM\Entity(readOnly: true)]
|
||||
#[ORM\Table(schema: 'magneticod', name: 'files')]
|
||||
#[ORM\Index(name: 'file_info_hash_index', columns: ['torrent_id'])]
|
||||
class File
|
||||
{
|
||||
/**
|
||||
* @var int
|
||||
*
|
||||
* @ORM\Column(name="id", type="integer")
|
||||
* @ORM\Id
|
||||
*/
|
||||
private $id;
|
||||
#[ORM\Id]
|
||||
#[ORM\Column(name: 'id', type: 'integer')]
|
||||
private int $id;
|
||||
|
||||
/**
|
||||
* @var Torrent
|
||||
*
|
||||
* @ORM\ManyToOne(targetEntity="App\Magnetico\Entity\Torrent", inversedBy="files")
|
||||
* @ORM\JoinColumn(name="torrent_id")
|
||||
*/
|
||||
private $torrent;
|
||||
#[ORM\ManyToOne(targetEntity: Torrent::class, inversedBy: 'files')]
|
||||
#[ORM\JoinColumn(name: 'torrent_id')]
|
||||
private Torrent $torrent;
|
||||
|
||||
/**
|
||||
* @var int File size in bytes
|
||||
*
|
||||
* @Serializer\Groups({"api_v1_show"})
|
||||
*
|
||||
* @ORM\Column(name="size", type="bigint", nullable=false)
|
||||
*/
|
||||
private $size;
|
||||
/** File size in bytes */
|
||||
#[Serializer\Groups(['api_v1_show'])]
|
||||
#[ORM\Column(name: 'size', type: 'bigint', nullable: false)]
|
||||
private int $size;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*
|
||||
* @Serializer\Groups({"api_v1_show"})
|
||||
*
|
||||
* @ORM\Column(name="path", type="text", nullable=false)
|
||||
*/
|
||||
private $path;
|
||||
#[Serializer\Groups(['api_v1_show'])]
|
||||
#[ORM\Column(name: 'path', type: 'text', nullable: false)]
|
||||
private string $path;
|
||||
|
||||
public function getId(): int
|
||||
{
|
||||
|
|
|
@ -1,78 +1,49 @@
|
|||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Magnetico\Entity;
|
||||
|
||||
use App\Magnetico\Repository\TorrentRepository;
|
||||
use Doctrine\Common\Collections\ArrayCollection;
|
||||
use Doctrine\ORM\Mapping as ORM;
|
||||
use Symfony\Component\Serializer\Annotation as Serializer;
|
||||
|
||||
/**
|
||||
* @ORM\Table(schema="magneticod", name="torrents", indexes={
|
||||
* @ORM\Index(name="discovered_on_index", columns={"discovered_on"}),
|
||||
* @ORM\Index(name="info_hash_index", columns={"info_hash"})
|
||||
* })
|
||||
* @ORM\Entity(readOnly=true, repositoryClass="App\Magnetico\Repository\TorrentRepository")
|
||||
*/
|
||||
#[ORM\Entity(repositoryClass: TorrentRepository::class, readOnly: true)]
|
||||
#[ORM\Table(schema: 'magneticod', name: 'torrents')]
|
||||
#[ORM\Index(name: 'discovered_on_index', columns: ['discovered_on'])]
|
||||
#[ORM\Index(name: 'info_hash_index', columns: ['info_hash'])]
|
||||
class Torrent
|
||||
{
|
||||
/**
|
||||
* @var int
|
||||
*
|
||||
* @Serializer\Groups({"api_v1_search", "api_v1_show"})
|
||||
*
|
||||
* @ORM\Column(name="id", type="integer")
|
||||
* @ORM\Id
|
||||
*/
|
||||
private $id;
|
||||
#[Serializer\Groups(['api_v1_search', 'api_v1_show'])]
|
||||
#[ORM\Id]
|
||||
#[ORM\Column(name: 'id', type: 'integer')]
|
||||
private int $id;
|
||||
|
||||
/**
|
||||
* @var resource Resource pointing to info-hash BLOB
|
||||
*
|
||||
* @Serializer\Groups({"api_v1_search", "api_v1_show"})
|
||||
*
|
||||
* @ORM\Column(name="info_hash", type="blob", nullable=false)
|
||||
*/
|
||||
/** @var resource Resource pointing to info-hash BLOB */
|
||||
#[Serializer\Groups(['api_v1_search', 'api_v1_show'])]
|
||||
#[ORM\Column(name: 'info_hash', type: 'blob', nullable: false)]
|
||||
private $infoHash;
|
||||
|
||||
/**
|
||||
* @var string Cached value of self::infoHash in HEX string
|
||||
*/
|
||||
private $infoHashHexCache;
|
||||
/** Cached value of self::infoHash in HEX string */
|
||||
private ?string $infoHashHexCache = null;
|
||||
|
||||
/**
|
||||
* @var string Torrent name
|
||||
*
|
||||
* @Serializer\Groups({"api_v1_search", "api_v1_show"})
|
||||
*
|
||||
* @ORM\Column(name="name", type="text", nullable=false)
|
||||
*/
|
||||
private $name;
|
||||
#[Serializer\Groups(['api_v1_search', 'api_v1_show'])]
|
||||
#[ORM\Column(name: 'name', type: 'text', nullable: false)]
|
||||
private string $name;
|
||||
|
||||
/**
|
||||
* @var int Torrent files total size in bytes
|
||||
*
|
||||
* @Serializer\Groups({"api_v1_search", "api_v1_show"})
|
||||
*
|
||||
* @ORM\Column(name="total_size", type="bigint", nullable=false)
|
||||
*/
|
||||
private $totalSize;
|
||||
/** Torrent files total size in bytes */
|
||||
#[Serializer\Groups(['api_v1_search', 'api_v1_show'])]
|
||||
#[ORM\Column(name: 'total_size', type: 'bigint', nullable: false)]
|
||||
private int $totalSize;
|
||||
|
||||
/**
|
||||
* @var int Torrent discovery timestamp
|
||||
*
|
||||
* @Serializer\Groups({"api_v1_search", "api_v1_show"})
|
||||
*
|
||||
* @ORM\Column(name="discovered_on", type="integer", nullable=false)
|
||||
*/
|
||||
/** Torrent discovery timestamp */
|
||||
#[Serializer\Groups(['api_v1_search', 'api_v1_show'])]
|
||||
#[ORM\Column(name: 'discovered_on', type: 'integer', nullable: false)]
|
||||
private $discoveredOn;
|
||||
|
||||
/**
|
||||
* @var File[]|ArrayCollection
|
||||
*
|
||||
* @Serializer\Groups({"api_v1_show"})
|
||||
*
|
||||
* @ORM\OneToMany(targetEntity="App\Magnetico\Entity\File", fetch="EXTRA_LAZY", mappedBy="torrent")
|
||||
*/
|
||||
/** @var File[]|ArrayCollection */
|
||||
#[Serializer\Groups(['api_v1_show'])]
|
||||
#[ORM\OneToMany(targetEntity: File::class, fetch: 'EXTRA_LAZY', mappedBy: 'torrent')]
|
||||
private $files;
|
||||
|
||||
public function getId(): int
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Magnetico\Repository;
|
||||
|
||||
|
|
|
@ -1,13 +1,11 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Pager\View;
|
||||
|
||||
use Pagerfanta\Pagerfanta;
|
||||
use Pagerfanta\PagerfantaInterface;
|
||||
use Pagerfanta\View\Template\TemplateInterface;
|
||||
use Pagerfanta\View\Template\TwitterBootstrap4Template;
|
||||
use Pagerfanta\View\Template\{TemplateInterface, TwitterBootstrap4Template};
|
||||
use Pagerfanta\View\ViewInterface;
|
||||
|
||||
/**
|
||||
|
@ -48,31 +46,31 @@ class TwitterBootstrap4PagelessView implements ViewInterface
|
|||
return new TwitterBootstrap4Template();
|
||||
}
|
||||
|
||||
private function initializePagerfanta(PagerfantaInterface $pagerfanta)
|
||||
private function initializePagerfanta(PagerfantaInterface $pagerfanta): void
|
||||
{
|
||||
$this->pagerfanta = $pagerfanta;
|
||||
$this->currentPage = $pagerfanta->getCurrentPage();
|
||||
}
|
||||
|
||||
private function configureTemplate($routeGenerator, $options)
|
||||
private function configureTemplate($routeGenerator, $options): void
|
||||
{
|
||||
$this->template->setRouteGenerator($routeGenerator);
|
||||
$this->template->setOptions($options);
|
||||
}
|
||||
|
||||
private function generate()
|
||||
private function generate(): array|string
|
||||
{
|
||||
$pages = $this->generatePages();
|
||||
|
||||
return $this->generateContainer($pages);
|
||||
}
|
||||
|
||||
private function generateContainer($pages)
|
||||
private function generateContainer($pages): array|string
|
||||
{
|
||||
return str_replace('%pages%', $pages, $this->template->container());
|
||||
}
|
||||
|
||||
private function generatePages()
|
||||
private function generatePages(): string
|
||||
{
|
||||
return $this->previous().$this->currentPage().$this->next();
|
||||
}
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Repository;
|
||||
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Repository;
|
||||
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Repository;
|
||||
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Repository;
|
||||
|
||||
|
|
|
@ -13,16 +13,11 @@ class TorrentSearcher
|
|||
|
||||
private const ORDER_DISABLED_FIELDS = ['infoHash'];
|
||||
|
||||
/** @var TorrentRepository */
|
||||
private $torrentRepo;
|
||||
public function __construct(
|
||||
private readonly TorrentRepository $torrentRepo,
|
||||
private readonly ClassMetadata $classMetadata
|
||||
) {
|
||||
|
||||
/** @var ClassMetadata */
|
||||
private $classMetadata;
|
||||
|
||||
public function __construct(TorrentRepository $torrentRepo, ClassMetadata $classMetadata)
|
||||
{
|
||||
$this->torrentRepo = $torrentRepo;
|
||||
$this->classMetadata = $classMetadata;
|
||||
}
|
||||
|
||||
public function createSearchQueryBuilder(string $query, string $orderBy = null, string $order = 'asc'): QueryBuilder
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Twig;
|
||||
|
||||
|
|
|
@ -1,21 +1,22 @@
|
|||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Twig;
|
||||
|
||||
use App\Helper\BstreeviewFileTreeBuilder;
|
||||
use App\Helper\BsTreeviewFileTreeBuilder;
|
||||
use Twig\Extension\AbstractExtension;
|
||||
use Twig\{TwigFilter};
|
||||
|
||||
class FileTreeExtension extends AbstractExtension
|
||||
{
|
||||
private BstreeviewFileTreeBuilder $builder;
|
||||
private BsTreeviewFileTreeBuilder $builder;
|
||||
|
||||
public function __construct(BstreeviewFileTreeBuilder $builder)
|
||||
public function __construct(BsTreeviewFileTreeBuilder $builder)
|
||||
{
|
||||
$this->builder = $builder;
|
||||
}
|
||||
|
||||
public function getFilters()
|
||||
public function getFilters(): array
|
||||
{
|
||||
return [
|
||||
new TwigFilter('file_tree', [$this->builder, 'buildFileTreeDataArray']),
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Twig;
|
||||
|
||||
|
@ -8,12 +9,10 @@ use Twig\{TwigFilter, TwigFunction};
|
|||
|
||||
class MagnetExtension extends AbstractExtension
|
||||
{
|
||||
/** @var MagnetGenerator */
|
||||
private $magnetGenerator;
|
||||
public function __construct(
|
||||
private readonly MagnetGenerator $magnetGenerator
|
||||
) {
|
||||
|
||||
public function __construct(MagnetGenerator $magnetGenerator)
|
||||
{
|
||||
$this->magnetGenerator = $magnetGenerator;
|
||||
}
|
||||
|
||||
public function getFunctions(): array
|
||||
|
@ -23,7 +22,7 @@ class MagnetExtension extends AbstractExtension
|
|||
];
|
||||
}
|
||||
|
||||
public function getFilters()
|
||||
public function getFilters(): array
|
||||
{
|
||||
return [
|
||||
new TwigFilter('magnet', [$this->magnetGenerator, 'generate']),
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\User\Exception;
|
||||
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\User\Exception;
|
||||
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\User;
|
||||
|
||||
|
@ -7,16 +8,11 @@ use App\Repository\InviteRepository;
|
|||
|
||||
class InviteManager
|
||||
{
|
||||
/** @var InviteRepository */
|
||||
private $inviteRepo;
|
||||
public function __construct(
|
||||
private readonly InviteRepository $inviteRepo,
|
||||
private readonly int $newUserInvites = 0
|
||||
) {
|
||||
|
||||
/** @var int Which amount of invites we need to give to the new user */
|
||||
private $newUserInvites;
|
||||
|
||||
public function __construct(InviteRepository $inviteRepo, int $newUserInvites = 0)
|
||||
{
|
||||
$this->inviteRepo = $inviteRepo;
|
||||
$this->newUserInvites = $newUserInvites;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -28,7 +24,7 @@ class InviteManager
|
|||
return [];
|
||||
}
|
||||
|
||||
$amount = (null !== $forceAmount) ? $forceAmount : $this->newUserInvites;
|
||||
$amount = $forceAmount ?? $this->newUserInvites;
|
||||
|
||||
$invites = [];
|
||||
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\User;
|
||||
|
||||
|
@ -14,38 +14,15 @@ use Symfony\Component\Routing\{Generator\UrlGeneratorInterface, RouterInterface}
|
|||
|
||||
class PasswordResetManager
|
||||
{
|
||||
/** @var UserRepository */
|
||||
private $userRepo;
|
||||
|
||||
/** @var PasswordResetTokenRepository */
|
||||
private $tokenRepo;
|
||||
|
||||
/** @var EntityManagerInterface */
|
||||
private $em;
|
||||
|
||||
/** @var MailerInterface */
|
||||
private $mailer;
|
||||
|
||||
/** @var RouterInterface */
|
||||
private $router;
|
||||
|
||||
/** @var string */
|
||||
private $fromAddress;
|
||||
|
||||
public function __construct(
|
||||
UserRepository $userRepo,
|
||||
PasswordResetTokenRepository $tokenRepo,
|
||||
EntityManagerInterface $em,
|
||||
MailerInterface $mailer,
|
||||
RouterInterface $router,
|
||||
string $fromAddress
|
||||
private readonly UserRepository $userRepo,
|
||||
private readonly PasswordResetTokenRepository $tokenRepo,
|
||||
private readonly EntityManagerInterface $em,
|
||||
private readonly MailerInterface $mailer,
|
||||
private readonly RouterInterface $router,
|
||||
private readonly string $fromAddress
|
||||
) {
|
||||
$this->userRepo = $userRepo;
|
||||
$this->tokenRepo = $tokenRepo;
|
||||
$this->em = $em;
|
||||
$this->mailer = $mailer;
|
||||
$this->router = $router;
|
||||
$this->fromAddress = $fromAddress;
|
||||
|
||||
}
|
||||
|
||||
public function sendResetLink(string $address): void
|
||||
|
|
|
@ -1,37 +1,29 @@
|
|||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\User;
|
||||
|
||||
use App\Entity\{Invite, User};
|
||||
use App\Repository\{InviteRepository, UserRepository};
|
||||
use App\Repository\UserRepository;
|
||||
use App\User\Exception\InvalidInviteException;
|
||||
use Symfony\Component\Security\Core\Encoder\EncoderFactoryInterface;
|
||||
use Symfony\Component\PasswordHasher\Hasher\PasswordHasherFactoryInterface;
|
||||
|
||||
class UserManager
|
||||
{
|
||||
private const DEFAULT_ROLES = ['ROLE_USER'];
|
||||
|
||||
/** @var UserRepository */
|
||||
private $userRepo;
|
||||
public function __construct(
|
||||
private readonly PasswordHasherFactoryInterface $hasherFactory,
|
||||
private readonly UserRepository $userRepo,
|
||||
) {
|
||||
|
||||
/** @var InviteRepository */
|
||||
private $inviteRepo;
|
||||
|
||||
/** @var EncoderFactoryInterface */
|
||||
private $encoderFactory;
|
||||
|
||||
public function __construct(EncoderFactoryInterface $encoderFactory, UserRepository $userRepo, InviteRepository $inviteRepo)
|
||||
{
|
||||
$this->userRepo = $userRepo;
|
||||
$this->inviteRepo = $inviteRepo;
|
||||
$this->encoderFactory = $encoderFactory;
|
||||
}
|
||||
|
||||
public function createUser(string $username, string $password, string $email, array $roles = self::DEFAULT_ROLES): User
|
||||
{
|
||||
$user = new User(
|
||||
$username,
|
||||
$this->encoderFactory->getEncoder(User::class),
|
||||
$this->hasherFactory->getPasswordHasher(User::class),
|
||||
$password,
|
||||
$email,
|
||||
$roles
|
||||
|
@ -42,14 +34,6 @@ class UserManager
|
|||
return $user;
|
||||
}
|
||||
|
||||
public function changePassword(User $user, string $rawPassword): void
|
||||
{
|
||||
$user->changePassword(
|
||||
$this->encoderFactory->getEncoder(User::class),
|
||||
$rawPassword
|
||||
);
|
||||
}
|
||||
|
||||
public function createUserByInvite(string $username, string $password, string $email, Invite $invite, array $roles = self::DEFAULT_ROLES): User
|
||||
{
|
||||
if (null !== $invite->getUsedBy()) {
|
||||
|
|
|
@ -1,12 +1,11 @@
|
|||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Validator\Constraints;
|
||||
|
||||
use Symfony\Component\Validator\Constraint;
|
||||
|
||||
/**
|
||||
* @Annotation
|
||||
*/
|
||||
#[\Attribute(\Attribute::TARGET_PROPERTY | \Attribute::TARGET_METHOD)]
|
||||
class ValidInvite extends Constraint
|
||||
{
|
||||
public $notFoundMessage = 'Invite {{ code }} not found.';
|
||||
|
@ -14,6 +13,6 @@ class ValidInvite extends Constraint
|
|||
|
||||
public function validatedBy(): string
|
||||
{
|
||||
return get_class($this).'Validator';
|
||||
return static::class.'Validator';
|
||||
}
|
||||
}
|
|
@ -1,4 +1,5 @@
|
|||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Validator\Constraints;
|
||||
|
||||
|
@ -8,19 +9,16 @@ use Symfony\Component\Validator\{Constraint, ConstraintValidator};
|
|||
|
||||
class ValidInviteValidator extends ConstraintValidator
|
||||
{
|
||||
/** @var InviteRepository */
|
||||
private $inviteRepo;
|
||||
public function __construct(
|
||||
private readonly InviteRepository $inviteRepo
|
||||
) {
|
||||
|
||||
public function __construct(InviteRepository $inviteRepo)
|
||||
{
|
||||
$this->inviteRepo = $inviteRepo;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mixed $value
|
||||
* @param ValidInvite $constraint
|
||||
*/
|
||||
public function validate($value, Constraint $constraint)
|
||||
public function validate(mixed $value, Constraint $constraint)
|
||||
{
|
||||
/** @var Invite $invite */
|
||||
if (null === $invite = $this->inviteRepo->findOneBy(['code' => $value])) {
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\View\Torrent;
|
||||
|
||||
use App\Magnetico\Entity\File;
|
||||
use App\Magnetico\Entity\Torrent;
|
||||
use App\Magnetico\Entity\{File, Torrent};
|
||||
|
||||
class FileTreeNode
|
||||
{
|
||||
|
@ -52,7 +52,7 @@ class FileTreeNode
|
|||
|
||||
// If we have file only file and not a tree.
|
||||
if (1 === count($pathParts)) {
|
||||
$this->addChild($path, FileTreeNode::createFromFile($path, $file, $this));
|
||||
$this->addChild($path, self::createFromFile($path, $file, $this));
|
||||
|
||||
return;
|
||||
}
|
||||
|
@ -89,12 +89,7 @@ class FileTreeNode
|
|||
return array_key_exists($name, $this->children);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $name
|
||||
*
|
||||
* @return FileTreeNode|File
|
||||
*/
|
||||
public function getChild(string $name)
|
||||
public function getChild(string $name): File|FileTreeNode
|
||||
{
|
||||
if (!array_key_exists($name, $this->children)) {
|
||||
throw new \InvalidArgumentException(sprintf(
|
||||
|
@ -119,7 +114,7 @@ class FileTreeNode
|
|||
$files = [];
|
||||
|
||||
foreach ($this->children as $name => $child) {
|
||||
if ($child instanceof FileTreeNode) {
|
||||
if ($child instanceof self) {
|
||||
$dirs[$name] = $child;
|
||||
} elseif ($child instanceof File) {
|
||||
$files[] = $child;
|
||||
|
|
49
symfony.lock
49
symfony.lock
|
@ -86,6 +86,31 @@
|
|||
"egulias/email-validator": {
|
||||
"version": "2.1.14"
|
||||
},
|
||||
"excelwebzone/recaptcha-bundle": {
|
||||
"version": "1.5",
|
||||
"recipe": {
|
||||
"repo": "github.com/symfony/recipes-contrib",
|
||||
"branch": "master",
|
||||
"version": "1.5",
|
||||
"ref": "fd4da7bc71749db65bc83abf5d164bfa9c839cf4"
|
||||
},
|
||||
"files": [
|
||||
"config/packages/dev/ewz_recaptcha.yaml",
|
||||
"config/packages/ewz_recaptcha.yaml"
|
||||
]
|
||||
},
|
||||
"google/recaptcha": {
|
||||
"version": "1.1",
|
||||
"recipe": {
|
||||
"repo": "github.com/symfony/recipes-contrib",
|
||||
"branch": "master",
|
||||
"version": "1.1",
|
||||
"ref": "d087df3e087f50da3955f2def05079380da5894b"
|
||||
},
|
||||
"files": [
|
||||
"config/packages/google_recaptcha.yaml"
|
||||
]
|
||||
},
|
||||
"guzzlehttp/promises": {
|
||||
"version": "v1.3.1"
|
||||
},
|
||||
|
@ -104,33 +129,9 @@
|
|||
"laminas/laminas-zendframework-bridge": {
|
||||
"version": "1.0.1"
|
||||
},
|
||||
"meteo-concept/hcaptcha-bundle": {
|
||||
"version": "3.2",
|
||||
"recipe": {
|
||||
"repo": "github.com/symfony/recipes-contrib",
|
||||
"branch": "main",
|
||||
"version": "1.0",
|
||||
"ref": "e94efaa6cd60c87dc9cc6de56b333a1754187d1e"
|
||||
},
|
||||
"files": [
|
||||
"config/packages/meteo_concept_h_captcha.yaml"
|
||||
]
|
||||
},
|
||||
"monolog/monolog": {
|
||||
"version": "1.23.0"
|
||||
},
|
||||
"nyholm/psr7": {
|
||||
"version": "1.5",
|
||||
"recipe": {
|
||||
"repo": "github.com/symfony/recipes",
|
||||
"branch": "main",
|
||||
"version": "1.0",
|
||||
"ref": "0cd4d2d0e7f646fda75f9944f747a56e6ed13d4c"
|
||||
},
|
||||
"files": [
|
||||
"config/packages/nyholm_psr7.yaml"
|
||||
]
|
||||
},
|
||||
"ocramius/package-versions": {
|
||||
"version": "1.3.0"
|
||||
},
|
||||
|
|
5
templates/Account/password.html.twig
Normal file
5
templates/Account/password.html.twig
Normal file
|
@ -0,0 +1,5 @@
|
|||
{% extends 'base.html.twig' %}
|
||||
|
||||
{% block content %}
|
||||
{{ form(form) }}
|
||||
{% endblock %}
|
|
@ -10,7 +10,7 @@
|
|||
<tbody>
|
||||
<tr>
|
||||
<th scope="row">Username</th>
|
||||
<td>{{ user.username }}</td>
|
||||
<td>{{ user.userIdentifier }}</td>
|
||||
<td></td>
|
||||
</tr>
|
||||
<tr>
|
||||
|
@ -20,5 +20,12 @@
|
|||
<button type="button" class="btn disabled" title="Not implemented">Change</button>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">Password</th>
|
||||
<td>********</td>
|
||||
<td>
|
||||
<a role="button" class="btn btn-primary" href="{{ path('user_account_password_change') }}">Change</a>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
Loading…
Reference in a new issue