Compare commits
40 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
445224d368 | ||
|
|
9a660279be | ||
|
|
c1adedf74d | ||
|
|
9a0e7fce26 | ||
|
|
d90b4d7863 | ||
|
|
2860d2e949 | ||
|
|
ad2bbfd48c | ||
|
|
5e306c6740 | ||
|
|
56129de3f9 | ||
|
|
22b2b46da5 | ||
|
|
7cc48ffc73 | ||
|
|
adb79db8f5 | ||
|
|
9ca87af938 | ||
|
|
b01840b111 | ||
|
|
1759b6dfdc | ||
|
|
2d3bc35e45 | ||
|
|
69f07b57ce | ||
|
|
b86028acee | ||
|
|
b67781fe23 | ||
|
|
6920b82684 | ||
|
|
0f291aa147 | ||
|
|
c4160081a1 | ||
|
|
5d414590cb | ||
|
|
d28b743684 | ||
|
|
c4e8e9b35e | ||
|
|
6fbd56c952 | ||
|
|
f5732fbcea | ||
|
|
0f095ab7f8 | ||
|
|
2e376337fa | ||
|
|
fc203f1bd3 | ||
|
|
2eda8e0808 | ||
| d3431b76e2 | |||
| a978469564 | |||
| 3097189c49 | |||
| 6f11de70e0 | |||
| 4e06fe6636 | |||
| fd46abf58f | |||
| 2237a45d6f | |||
| 846de2c257 | |||
| d01b725435 |
@@ -12,3 +12,4 @@ phpstan.dist.neon
|
|||||||
phpunit.dist.xml
|
phpunit.dist.xml
|
||||||
nomad.deploy.hcl
|
nomad.deploy.hcl
|
||||||
deployment.properties
|
deployment.properties
|
||||||
|
var/download/*
|
||||||
|
|||||||
4
.env
4
.env
@@ -58,3 +58,7 @@ OIDC_BYPASS_FORM_LOGIN=false
|
|||||||
|
|
||||||
NOTIFICATION_TRANSPORT=
|
NOTIFICATION_TRANSPORT=
|
||||||
NTFY_DSN=
|
NTFY_DSN=
|
||||||
|
|
||||||
|
###> sentry/sentry-symfony ###
|
||||||
|
SENTRY_DSN=
|
||||||
|
###< sentry/sentry-symfony ###
|
||||||
|
|||||||
@@ -13,34 +13,7 @@ export default class extends Controller {
|
|||||||
tmdbId: String,
|
tmdbId: String,
|
||||||
imdbId: String,
|
imdbId: String,
|
||||||
title: String,
|
title: String,
|
||||||
}
|
season: Number,
|
||||||
|
|
||||||
initialize() {
|
|
||||||
// Called once when the controller is first instantiated (per element)
|
|
||||||
|
|
||||||
// Here you can initialize variables, create scoped callables for event
|
|
||||||
// listeners, instantiate external libraries, etc.
|
|
||||||
// this._fooBar = this.fooBar.bind(this)
|
|
||||||
}
|
|
||||||
|
|
||||||
connect() {
|
|
||||||
// Called every time the controller is connected to the DOM
|
|
||||||
// (on page load, when it's added to the DOM, moved in the DOM, etc.)
|
|
||||||
|
|
||||||
// Here you can add event listeners on the element or target elements,
|
|
||||||
// add or remove classes, attributes, dispatch custom events, etc.
|
|
||||||
// this.fooTarget.addEventListener('click', this._fooBar)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add custom controller actions here
|
|
||||||
// fooBar() { this.fooTarget.classList.toggle(this.bazClass) }
|
|
||||||
|
|
||||||
disconnect() {
|
|
||||||
// Called anytime its element is disconnected from the DOM
|
|
||||||
// (on page change, when it's removed from or moved in the DOM, etc.)
|
|
||||||
|
|
||||||
// Here you should remove all event listeners added in "connect()"
|
|
||||||
// this.fooTarget.removeEventListener('click', this._fooBar)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
toggle() {
|
toggle() {
|
||||||
@@ -53,34 +26,13 @@ export default class extends Controller {
|
|||||||
imdbId: this.imdbIdValue,
|
imdbId: this.imdbIdValue,
|
||||||
title: this.titleValue,
|
title: this.titleValue,
|
||||||
monitorType: 'tvshows',
|
monitorType: 'tvshows',
|
||||||
|
season: this.seasonValue
|
||||||
});
|
});
|
||||||
if (this.hasDialogOutlet) {
|
if (this.hasDialogOutlet) {
|
||||||
this.dialogOutlet.close();
|
this.dialogOutlet.close();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async monitorSeason() {
|
|
||||||
await this.makeMonitor({
|
|
||||||
tmdbId: this.tmdbIdValue,
|
|
||||||
imdbId: this.imdbIdValue,
|
|
||||||
title: this.titleValue,
|
|
||||||
monitorType: 'tvseason',
|
|
||||||
season: this.resultFilterOutlet.activeFilter['season'],
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
async monitorEpisode() {
|
|
||||||
// ToDo: figure out how to set episode
|
|
||||||
await this.makeMonitor({
|
|
||||||
tmdbId: this.tmdbIdValue,
|
|
||||||
imdbId: this.imdbIdValue,
|
|
||||||
title: this.titleValue,
|
|
||||||
monitorType: 'tvepisode',
|
|
||||||
season: this.resultFilterOutlet.activeFilter['season'],
|
|
||||||
episode: '',
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
async makeMonitor(body) {
|
async makeMonitor(body) {
|
||||||
const response = await fetch('/api/monitor', {
|
const response = await fetch('/api/monitor', {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
@@ -90,7 +42,6 @@ export default class extends Controller {
|
|||||||
},
|
},
|
||||||
body: JSON.stringify(body)
|
body: JSON.stringify(body)
|
||||||
});
|
});
|
||||||
|
|
||||||
return await response.json();
|
return await response.json();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,14 +1,22 @@
|
|||||||
# torsearch-app is built from this base
|
# torsearch-app is built from this base
|
||||||
export APP_FRANKENPHP_TAG=php8.4
|
export APP_FRANKENPHP_TAG=php8.4
|
||||||
|
|
||||||
docker buildx build --platform=linux/amd64 -f docker/Dockerfile.base.app -t code.caldwell.digital/home/torsearch-base:${APP_FRANKENPHP_TAG} -t code.caldwell.digital/home/torsearch-base:latest --build-arg "FRANKENPHP_TAG=${APP_FRANKENPHP_TAG}" .
|
#docker buildx build --platform=linux/amd64,linux/arm64 -f docker/Dockerfile.base.app -t code.caldwell.digital/home/torsearch-base:${APP_FRANKENPHP_TAG} -t code.caldwell.digital/home/torsearch-base:latest --build-arg "FRANKENPHP_TAG=${APP_FRANKENPHP_TAG}" .
|
||||||
|
docker build -f docker/Dockerfile.base.app -t code.caldwell.digital/home/torsearch-base:${APP_FRANKENPHP_TAG} -t code.caldwell.digital/home/torsearch-base:latest --build-arg "FRANKENPHP_TAG=${APP_FRANKENPHP_TAG}" .
|
||||||
docker push code.caldwell.digital/home/torsearch-base:${APP_FRANKENPHP_TAG}
|
docker push code.caldwell.digital/home/torsearch-base:${APP_FRANKENPHP_TAG}
|
||||||
docker push code.caldwell.digital/home/torsearch-base:latest
|
docker push code.caldwell.digital/home/torsearch-base:latest
|
||||||
|
|
||||||
# torsearch-worker & torsearch-scheduler are built from this base
|
# torsearch-worker & torsearch-scheduler are built from this base
|
||||||
export WORKER_FRANKENPHP_TAG=php8.4-alpine
|
export WORKER_FRANKENPHP_TAG=php8.4-alpine
|
||||||
|
|
||||||
docker buildx build --platform=linux/amd64 -f docker/Dockerfile.base.worker -t code.caldwell.digital/home/torsearch-base-worker:${WORKER_FRANKENPHP_TAG} -t code.caldwell.digital/home/torsearch-base-worker:latest --build-arg "FRANKENPHP_TAG=${WORKER_FRANKENPHP_TAG}" .
|
#docker buildx build --platform=linux/amd64,linux/arm64 -f docker/Dockerfile.base.worker -t code.caldwell.digital/home/torsearch-base-worker:${WORKER_FRANKENPHP_TAG} -t code.caldwell.digital/home/torsearch-base-worker:latest --build-arg "FRANKENPHP_TAG=${WORKER_FRANKENPHP_TAG}" .
|
||||||
|
docker build -f docker/Dockerfile.base.worker -t code.caldwell.digital/home/torsearch-base-worker:${WORKER_FRANKENPHP_TAG} -t code.caldwell.digital/home/torsearch-base-worker:latest --build-arg "FRANKENPHP_TAG=${WORKER_FRANKENPHP_TAG}" .
|
||||||
docker push code.caldwell.digital/home/torsearch-base-worker:${WORKER_FRANKENPHP_TAG}
|
docker push code.caldwell.digital/home/torsearch-base-worker:${WORKER_FRANKENPHP_TAG}
|
||||||
docker push code.caldwell.digital/home/torsearch-base-worker:latest
|
docker push code.caldwell.digital/home/torsearch-base-worker:latest
|
||||||
|
|
||||||
|
# torsearch-worker-supervisord
|
||||||
|
export ALPINE_VERSION=3.22
|
||||||
|
|
||||||
|
#docker buildx build --platform=linux/amd64,linux/arm64 -f docker/Dockerfile.base.worker -t code.caldwell.digital/home/torsearch-base-worker-supervisord:latest --build-arg "ALPINE_VERSION=${ALPINE_VERSION}" .
|
||||||
|
docker build -f docker/Dockerfile.base.worker -t code.caldwell.digital/home/torsearch-base-worker-supervisord:latest --build-arg "ALPINE_VERSION=${ALPINE_VERSION}" .
|
||||||
|
docker push code.caldwell.digital/home/torsearch-base-worker-supervisord:latest
|
||||||
|
|||||||
17
compose.yml
17
compose.yml
@@ -34,10 +34,8 @@ services:
|
|||||||
|
|
||||||
worker:
|
worker:
|
||||||
build:
|
build:
|
||||||
dockerfile: docker/Dockerfile.base.worker
|
dockerfile: docker/Dockerfile.worker
|
||||||
context: .
|
context: .
|
||||||
args:
|
|
||||||
FRANKENPHP_TAG: php8.4-alpine
|
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
volumes:
|
volumes:
|
||||||
- $PWD:/app
|
- $PWD:/app
|
||||||
@@ -45,22 +43,19 @@ services:
|
|||||||
tty: true
|
tty: true
|
||||||
environment:
|
environment:
|
||||||
TZ: America/Chicago
|
TZ: America/Chicago
|
||||||
command: php /app/bin/console messenger:consume async --time-limit=3600 -vv
|
|
||||||
|
|
||||||
|
|
||||||
scheduler:
|
scheduler:
|
||||||
build:
|
build:
|
||||||
dockerfile: docker/Dockerfile.base.worker
|
dockerfile: docker/Dockerfile.scheduler
|
||||||
context: .
|
context: .
|
||||||
args:
|
|
||||||
FRANKENPHP_TAG: php8.4-alpine
|
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
volumes:
|
volumes:
|
||||||
- $PWD:/app
|
- $PWD:/app
|
||||||
|
- $PWD/var/download:/var/download
|
||||||
|
tty: true
|
||||||
environment:
|
environment:
|
||||||
TZ: America/Chicago
|
TZ: America/Chicago
|
||||||
command: php /app/bin/console messenger:consume scheduler_monitor -vv
|
WORKER_MONITOR: 2
|
||||||
tty: true
|
|
||||||
|
|
||||||
|
|
||||||
redis:
|
redis:
|
||||||
@@ -96,6 +91,8 @@ services:
|
|||||||
image: adminer
|
image: adminer
|
||||||
ports:
|
ports:
|
||||||
- "8081:8080"
|
- "8081:8080"
|
||||||
|
environment:
|
||||||
|
ADMINER_DEFAULT_SERVER: database
|
||||||
|
|
||||||
|
|
||||||
volumes:
|
volumes:
|
||||||
|
|||||||
@@ -29,6 +29,7 @@
|
|||||||
"phpstan/phpdoc-parser": "^2.1",
|
"phpstan/phpdoc-parser": "^2.1",
|
||||||
"predis/predis": "^2.4",
|
"predis/predis": "^2.4",
|
||||||
"runtime/frankenphp-symfony": "^0.2.0",
|
"runtime/frankenphp-symfony": "^0.2.0",
|
||||||
|
"sentry/sentry-symfony": "^5.6",
|
||||||
"spatie/icalendar-generator": "^3.0",
|
"spatie/icalendar-generator": "^3.0",
|
||||||
"spomky-labs/pwa-bundle": "^1.2",
|
"spomky-labs/pwa-bundle": "^1.2",
|
||||||
"stof/doctrine-extensions-bundle": "^1.14",
|
"stof/doctrine-extensions-bundle": "^1.14",
|
||||||
@@ -46,6 +47,7 @@
|
|||||||
"symfony/mailer": "7.3.*",
|
"symfony/mailer": "7.3.*",
|
||||||
"symfony/mercure-bundle": "^0.3.9",
|
"symfony/mercure-bundle": "^0.3.9",
|
||||||
"symfony/messenger": "7.3.*",
|
"symfony/messenger": "7.3.*",
|
||||||
|
"symfony/monolog-bundle": "^3.10",
|
||||||
"symfony/notifier": "7.3.*",
|
"symfony/notifier": "7.3.*",
|
||||||
"symfony/ntfy-notifier": "7.3.*",
|
"symfony/ntfy-notifier": "7.3.*",
|
||||||
"symfony/object-mapper": "7.3.*",
|
"symfony/object-mapper": "7.3.*",
|
||||||
|
|||||||
601
composer.lock
generated
601
composer.lock
generated
@@ -4,7 +4,7 @@
|
|||||||
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
||||||
"This file is @generated automatically"
|
"This file is @generated automatically"
|
||||||
],
|
],
|
||||||
"content-hash": "e055bbbbe5836c92bb147b6dbb1d1d46",
|
"content-hash": "952def2c32d975032ac0061e5aa37319",
|
||||||
"packages": [
|
"packages": [
|
||||||
{
|
{
|
||||||
"name": "1tomany/rich-bundle",
|
"name": "1tomany/rich-bundle",
|
||||||
@@ -2552,6 +2552,66 @@
|
|||||||
],
|
],
|
||||||
"time": "2025-03-27T12:30:47+00:00"
|
"time": "2025-03-27T12:30:47+00:00"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "jean85/pretty-package-versions",
|
||||||
|
"version": "2.1.1",
|
||||||
|
"source": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/Jean85/pretty-package-versions.git",
|
||||||
|
"reference": "4d7aa5dab42e2a76d99559706022885de0e18e1a"
|
||||||
|
},
|
||||||
|
"dist": {
|
||||||
|
"type": "zip",
|
||||||
|
"url": "https://api.github.com/repos/Jean85/pretty-package-versions/zipball/4d7aa5dab42e2a76d99559706022885de0e18e1a",
|
||||||
|
"reference": "4d7aa5dab42e2a76d99559706022885de0e18e1a",
|
||||||
|
"shasum": ""
|
||||||
|
},
|
||||||
|
"require": {
|
||||||
|
"composer-runtime-api": "^2.1.0",
|
||||||
|
"php": "^7.4|^8.0"
|
||||||
|
},
|
||||||
|
"require-dev": {
|
||||||
|
"friendsofphp/php-cs-fixer": "^3.2",
|
||||||
|
"jean85/composer-provided-replaced-stub-package": "^1.0",
|
||||||
|
"phpstan/phpstan": "^2.0",
|
||||||
|
"phpunit/phpunit": "^7.5|^8.5|^9.6",
|
||||||
|
"rector/rector": "^2.0",
|
||||||
|
"vimeo/psalm": "^4.3 || ^5.0"
|
||||||
|
},
|
||||||
|
"type": "library",
|
||||||
|
"extra": {
|
||||||
|
"branch-alias": {
|
||||||
|
"dev-master": "1.x-dev"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"autoload": {
|
||||||
|
"psr-4": {
|
||||||
|
"Jean85\\": "src/"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"notification-url": "https://packagist.org/downloads/",
|
||||||
|
"license": [
|
||||||
|
"MIT"
|
||||||
|
],
|
||||||
|
"authors": [
|
||||||
|
{
|
||||||
|
"name": "Alessandro Lai",
|
||||||
|
"email": "alessandro.lai85@gmail.com"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "A library to get pretty versions strings of installed dependencies",
|
||||||
|
"keywords": [
|
||||||
|
"composer",
|
||||||
|
"package",
|
||||||
|
"release",
|
||||||
|
"versions"
|
||||||
|
],
|
||||||
|
"support": {
|
||||||
|
"issues": "https://github.com/Jean85/pretty-package-versions/issues",
|
||||||
|
"source": "https://github.com/Jean85/pretty-package-versions/tree/2.1.1"
|
||||||
|
},
|
||||||
|
"time": "2025-03-19T14:43:43+00:00"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "lcobucci/jwt",
|
"name": "lcobucci/jwt",
|
||||||
"version": "5.5.0",
|
"version": "5.5.0",
|
||||||
@@ -2681,6 +2741,109 @@
|
|||||||
},
|
},
|
||||||
"time": "2025-02-06T08:48:15+00:00"
|
"time": "2025-02-06T08:48:15+00:00"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "monolog/monolog",
|
||||||
|
"version": "3.9.0",
|
||||||
|
"source": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/Seldaek/monolog.git",
|
||||||
|
"reference": "10d85740180ecba7896c87e06a166e0c95a0e3b6"
|
||||||
|
},
|
||||||
|
"dist": {
|
||||||
|
"type": "zip",
|
||||||
|
"url": "https://api.github.com/repos/Seldaek/monolog/zipball/10d85740180ecba7896c87e06a166e0c95a0e3b6",
|
||||||
|
"reference": "10d85740180ecba7896c87e06a166e0c95a0e3b6",
|
||||||
|
"shasum": ""
|
||||||
|
},
|
||||||
|
"require": {
|
||||||
|
"php": ">=8.1",
|
||||||
|
"psr/log": "^2.0 || ^3.0"
|
||||||
|
},
|
||||||
|
"provide": {
|
||||||
|
"psr/log-implementation": "3.0.0"
|
||||||
|
},
|
||||||
|
"require-dev": {
|
||||||
|
"aws/aws-sdk-php": "^3.0",
|
||||||
|
"doctrine/couchdb": "~1.0@dev",
|
||||||
|
"elasticsearch/elasticsearch": "^7 || ^8",
|
||||||
|
"ext-json": "*",
|
||||||
|
"graylog2/gelf-php": "^1.4.2 || ^2.0",
|
||||||
|
"guzzlehttp/guzzle": "^7.4.5",
|
||||||
|
"guzzlehttp/psr7": "^2.2",
|
||||||
|
"mongodb/mongodb": "^1.8",
|
||||||
|
"php-amqplib/php-amqplib": "~2.4 || ^3",
|
||||||
|
"php-console/php-console": "^3.1.8",
|
||||||
|
"phpstan/phpstan": "^2",
|
||||||
|
"phpstan/phpstan-deprecation-rules": "^2",
|
||||||
|
"phpstan/phpstan-strict-rules": "^2",
|
||||||
|
"phpunit/phpunit": "^10.5.17 || ^11.0.7",
|
||||||
|
"predis/predis": "^1.1 || ^2",
|
||||||
|
"rollbar/rollbar": "^4.0",
|
||||||
|
"ruflin/elastica": "^7 || ^8",
|
||||||
|
"symfony/mailer": "^5.4 || ^6",
|
||||||
|
"symfony/mime": "^5.4 || ^6"
|
||||||
|
},
|
||||||
|
"suggest": {
|
||||||
|
"aws/aws-sdk-php": "Allow sending log messages to AWS services like DynamoDB",
|
||||||
|
"doctrine/couchdb": "Allow sending log messages to a CouchDB server",
|
||||||
|
"elasticsearch/elasticsearch": "Allow sending log messages to an Elasticsearch server via official client",
|
||||||
|
"ext-amqp": "Allow sending log messages to an AMQP server (1.0+ required)",
|
||||||
|
"ext-curl": "Required to send log messages using the IFTTTHandler, the LogglyHandler, the SendGridHandler, the SlackWebhookHandler or the TelegramBotHandler",
|
||||||
|
"ext-mbstring": "Allow to work properly with unicode symbols",
|
||||||
|
"ext-mongodb": "Allow sending log messages to a MongoDB server (via driver)",
|
||||||
|
"ext-openssl": "Required to send log messages using SSL",
|
||||||
|
"ext-sockets": "Allow sending log messages to a Syslog server (via UDP driver)",
|
||||||
|
"graylog2/gelf-php": "Allow sending log messages to a GrayLog2 server",
|
||||||
|
"mongodb/mongodb": "Allow sending log messages to a MongoDB server (via library)",
|
||||||
|
"php-amqplib/php-amqplib": "Allow sending log messages to an AMQP server using php-amqplib",
|
||||||
|
"rollbar/rollbar": "Allow sending log messages to Rollbar",
|
||||||
|
"ruflin/elastica": "Allow sending log messages to an Elastic Search server"
|
||||||
|
},
|
||||||
|
"type": "library",
|
||||||
|
"extra": {
|
||||||
|
"branch-alias": {
|
||||||
|
"dev-main": "3.x-dev"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"autoload": {
|
||||||
|
"psr-4": {
|
||||||
|
"Monolog\\": "src/Monolog"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"notification-url": "https://packagist.org/downloads/",
|
||||||
|
"license": [
|
||||||
|
"MIT"
|
||||||
|
],
|
||||||
|
"authors": [
|
||||||
|
{
|
||||||
|
"name": "Jordi Boggiano",
|
||||||
|
"email": "j.boggiano@seld.be",
|
||||||
|
"homepage": "https://seld.be"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "Sends your logs to files, sockets, inboxes, databases and various web services",
|
||||||
|
"homepage": "https://github.com/Seldaek/monolog",
|
||||||
|
"keywords": [
|
||||||
|
"log",
|
||||||
|
"logging",
|
||||||
|
"psr-3"
|
||||||
|
],
|
||||||
|
"support": {
|
||||||
|
"issues": "https://github.com/Seldaek/monolog/issues",
|
||||||
|
"source": "https://github.com/Seldaek/monolog/tree/3.9.0"
|
||||||
|
},
|
||||||
|
"funding": [
|
||||||
|
{
|
||||||
|
"url": "https://github.com/Seldaek",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "https://tidelift.com/funding/github/packagist/monolog/monolog",
|
||||||
|
"type": "tidelift"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"time": "2025-03-24T10:02:05+00:00"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "nesbot/carbon",
|
"name": "nesbot/carbon",
|
||||||
"version": "3.10.0",
|
"version": "3.10.0",
|
||||||
@@ -4757,6 +4920,196 @@
|
|||||||
],
|
],
|
||||||
"time": "2023-12-12T12:06:11+00:00"
|
"time": "2023-12-12T12:06:11+00:00"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "sentry/sentry",
|
||||||
|
"version": "4.17.1",
|
||||||
|
"source": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/getsentry/sentry-php.git",
|
||||||
|
"reference": "5c696b8de57e841a2bf3b6f6eecfd99acfdda80c"
|
||||||
|
},
|
||||||
|
"dist": {
|
||||||
|
"type": "zip",
|
||||||
|
"url": "https://api.github.com/repos/getsentry/sentry-php/zipball/5c696b8de57e841a2bf3b6f6eecfd99acfdda80c",
|
||||||
|
"reference": "5c696b8de57e841a2bf3b6f6eecfd99acfdda80c",
|
||||||
|
"shasum": ""
|
||||||
|
},
|
||||||
|
"require": {
|
||||||
|
"ext-curl": "*",
|
||||||
|
"ext-json": "*",
|
||||||
|
"ext-mbstring": "*",
|
||||||
|
"guzzlehttp/psr7": "^1.8.4|^2.1.1",
|
||||||
|
"jean85/pretty-package-versions": "^1.5|^2.0.4",
|
||||||
|
"php": "^7.2|^8.0",
|
||||||
|
"psr/log": "^1.0|^2.0|^3.0",
|
||||||
|
"symfony/options-resolver": "^4.4.30|^5.0.11|^6.0|^7.0"
|
||||||
|
},
|
||||||
|
"conflict": {
|
||||||
|
"raven/raven": "*"
|
||||||
|
},
|
||||||
|
"require-dev": {
|
||||||
|
"friendsofphp/php-cs-fixer": "^3.4",
|
||||||
|
"guzzlehttp/promises": "^2.0.3",
|
||||||
|
"guzzlehttp/psr7": "^1.8.4|^2.1.1",
|
||||||
|
"monolog/monolog": "^1.6|^2.0|^3.0",
|
||||||
|
"phpbench/phpbench": "^1.0",
|
||||||
|
"phpstan/phpstan": "^1.3",
|
||||||
|
"phpunit/phpunit": "^8.5|^9.6",
|
||||||
|
"vimeo/psalm": "^4.17"
|
||||||
|
},
|
||||||
|
"suggest": {
|
||||||
|
"monolog/monolog": "Allow sending log messages to Sentry by using the included Monolog handler."
|
||||||
|
},
|
||||||
|
"type": "library",
|
||||||
|
"autoload": {
|
||||||
|
"files": [
|
||||||
|
"src/functions.php"
|
||||||
|
],
|
||||||
|
"psr-4": {
|
||||||
|
"Sentry\\": "src/"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"notification-url": "https://packagist.org/downloads/",
|
||||||
|
"license": [
|
||||||
|
"MIT"
|
||||||
|
],
|
||||||
|
"authors": [
|
||||||
|
{
|
||||||
|
"name": "Sentry",
|
||||||
|
"email": "accounts@sentry.io"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "PHP SDK for Sentry (http://sentry.io)",
|
||||||
|
"homepage": "http://sentry.io",
|
||||||
|
"keywords": [
|
||||||
|
"crash-reporting",
|
||||||
|
"crash-reports",
|
||||||
|
"error-handler",
|
||||||
|
"error-monitoring",
|
||||||
|
"log",
|
||||||
|
"logging",
|
||||||
|
"profiling",
|
||||||
|
"sentry",
|
||||||
|
"tracing"
|
||||||
|
],
|
||||||
|
"support": {
|
||||||
|
"issues": "https://github.com/getsentry/sentry-php/issues",
|
||||||
|
"source": "https://github.com/getsentry/sentry-php/tree/4.17.1"
|
||||||
|
},
|
||||||
|
"funding": [
|
||||||
|
{
|
||||||
|
"url": "https://sentry.io/",
|
||||||
|
"type": "custom"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "https://sentry.io/pricing/",
|
||||||
|
"type": "custom"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"time": "2025-10-23T15:19:24+00:00"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "sentry/sentry-symfony",
|
||||||
|
"version": "5.6.0",
|
||||||
|
"source": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/getsentry/sentry-symfony.git",
|
||||||
|
"reference": "9867751f5091b55d7e3a223f48d88e228132e073"
|
||||||
|
},
|
||||||
|
"dist": {
|
||||||
|
"type": "zip",
|
||||||
|
"url": "https://api.github.com/repos/getsentry/sentry-symfony/zipball/9867751f5091b55d7e3a223f48d88e228132e073",
|
||||||
|
"reference": "9867751f5091b55d7e3a223f48d88e228132e073",
|
||||||
|
"shasum": ""
|
||||||
|
},
|
||||||
|
"require": {
|
||||||
|
"guzzlehttp/psr7": "^2.1.1",
|
||||||
|
"jean85/pretty-package-versions": "^1.5||^2.0",
|
||||||
|
"php": "^7.2||^8.0",
|
||||||
|
"sentry/sentry": "^4.16.0",
|
||||||
|
"symfony/cache-contracts": "^1.1||^2.4||^3.0",
|
||||||
|
"symfony/config": "^4.4.20||^5.0.11||^6.0||^7.0",
|
||||||
|
"symfony/console": "^4.4.20||^5.0.11||^6.0||^7.0",
|
||||||
|
"symfony/dependency-injection": "^4.4.20||^5.0.11||^6.0||^7.0",
|
||||||
|
"symfony/event-dispatcher": "^4.4.20||^5.0.11||^6.0||^7.0",
|
||||||
|
"symfony/http-kernel": "^4.4.20||^5.0.11||^6.0||^7.0",
|
||||||
|
"symfony/polyfill-php80": "^1.22",
|
||||||
|
"symfony/psr-http-message-bridge": "^1.2||^2.0||^6.4||^7.0"
|
||||||
|
},
|
||||||
|
"require-dev": {
|
||||||
|
"doctrine/dbal": "^2.13||^3.3||^4.0",
|
||||||
|
"doctrine/doctrine-bundle": "^2.6",
|
||||||
|
"friendsofphp/php-cs-fixer": "^2.19||^3.40",
|
||||||
|
"masterminds/html5": "^2.8",
|
||||||
|
"phpstan/extension-installer": "^1.0",
|
||||||
|
"phpstan/phpstan": "1.12.5",
|
||||||
|
"phpstan/phpstan-phpunit": "1.4.0",
|
||||||
|
"phpstan/phpstan-symfony": "1.4.10",
|
||||||
|
"phpunit/phpunit": "^8.5.40||^9.6.21",
|
||||||
|
"symfony/browser-kit": "^4.4.20||^5.0.11||^6.0||^7.0",
|
||||||
|
"symfony/cache": "^4.4.20||^5.0.11||^6.0||^7.0",
|
||||||
|
"symfony/dom-crawler": "^4.4.20||^5.0.11||^6.0||^7.0",
|
||||||
|
"symfony/framework-bundle": "^4.4.20||^5.0.11||^6.0||^7.0",
|
||||||
|
"symfony/http-client": "^4.4.20||^5.0.11||^6.0||^7.0",
|
||||||
|
"symfony/messenger": "^4.4.20||^5.0.11||^6.0||^7.0",
|
||||||
|
"symfony/monolog-bundle": "^3.4",
|
||||||
|
"symfony/phpunit-bridge": "^5.2.6||^6.0||^7.0",
|
||||||
|
"symfony/process": "^4.4.20||^5.0.11||^6.0||^7.0",
|
||||||
|
"symfony/security-core": "^4.4.20||^5.0.11||^6.0||^7.0",
|
||||||
|
"symfony/security-http": "^4.4.20||^5.0.11||^6.0||^7.0",
|
||||||
|
"symfony/twig-bundle": "^4.4.20||^5.0.11||^6.0||^7.0",
|
||||||
|
"symfony/yaml": "^4.4.20||^5.0.11||^6.0||^7.0",
|
||||||
|
"vimeo/psalm": "^4.3||^5.16.0"
|
||||||
|
},
|
||||||
|
"suggest": {
|
||||||
|
"doctrine/doctrine-bundle": "Allow distributed tracing of database queries using Sentry.",
|
||||||
|
"monolog/monolog": "Allow sending log messages to Sentry by using the included Monolog handler.",
|
||||||
|
"symfony/cache": "Allow distributed tracing of cache pools using Sentry.",
|
||||||
|
"symfony/twig-bundle": "Allow distributed tracing of Twig template rendering using Sentry."
|
||||||
|
},
|
||||||
|
"type": "symfony-bundle",
|
||||||
|
"autoload": {
|
||||||
|
"files": [
|
||||||
|
"src/aliases.php"
|
||||||
|
],
|
||||||
|
"psr-4": {
|
||||||
|
"Sentry\\SentryBundle\\": "src/"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"notification-url": "https://packagist.org/downloads/",
|
||||||
|
"license": [
|
||||||
|
"MIT"
|
||||||
|
],
|
||||||
|
"authors": [
|
||||||
|
{
|
||||||
|
"name": "Sentry",
|
||||||
|
"email": "accounts@sentry.io"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "Symfony integration for Sentry (http://getsentry.com)",
|
||||||
|
"homepage": "http://getsentry.com",
|
||||||
|
"keywords": [
|
||||||
|
"errors",
|
||||||
|
"logging",
|
||||||
|
"sentry",
|
||||||
|
"symfony"
|
||||||
|
],
|
||||||
|
"support": {
|
||||||
|
"issues": "https://github.com/getsentry/sentry-symfony/issues",
|
||||||
|
"source": "https://github.com/getsentry/sentry-symfony/tree/5.6.0"
|
||||||
|
},
|
||||||
|
"funding": [
|
||||||
|
{
|
||||||
|
"url": "https://sentry.io/",
|
||||||
|
"type": "custom"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "https://sentry.io/pricing/",
|
||||||
|
"type": "custom"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"time": "2025-09-24T13:41:01+00:00"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "spatie/icalendar-generator",
|
"name": "spatie/icalendar-generator",
|
||||||
"version": "3.0.0",
|
"version": "3.0.0",
|
||||||
@@ -7637,6 +7990,169 @@
|
|||||||
],
|
],
|
||||||
"time": "2025-02-19T08:51:26+00:00"
|
"time": "2025-02-19T08:51:26+00:00"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "symfony/monolog-bridge",
|
||||||
|
"version": "v7.3.5",
|
||||||
|
"source": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/symfony/monolog-bridge.git",
|
||||||
|
"reference": "c66a65049c75f3ddf03d73c8c9ed61405779ce47"
|
||||||
|
},
|
||||||
|
"dist": {
|
||||||
|
"type": "zip",
|
||||||
|
"url": "https://api.github.com/repos/symfony/monolog-bridge/zipball/c66a65049c75f3ddf03d73c8c9ed61405779ce47",
|
||||||
|
"reference": "c66a65049c75f3ddf03d73c8c9ed61405779ce47",
|
||||||
|
"shasum": ""
|
||||||
|
},
|
||||||
|
"require": {
|
||||||
|
"monolog/monolog": "^3",
|
||||||
|
"php": ">=8.2",
|
||||||
|
"symfony/http-kernel": "^6.4|^7.0",
|
||||||
|
"symfony/service-contracts": "^2.5|^3"
|
||||||
|
},
|
||||||
|
"conflict": {
|
||||||
|
"symfony/console": "<6.4",
|
||||||
|
"symfony/http-foundation": "<6.4",
|
||||||
|
"symfony/security-core": "<6.4"
|
||||||
|
},
|
||||||
|
"require-dev": {
|
||||||
|
"symfony/console": "^6.4|^7.0",
|
||||||
|
"symfony/http-client": "^6.4|^7.0",
|
||||||
|
"symfony/mailer": "^6.4|^7.0",
|
||||||
|
"symfony/messenger": "^6.4|^7.0",
|
||||||
|
"symfony/mime": "^6.4|^7.0",
|
||||||
|
"symfony/security-core": "^6.4|^7.0",
|
||||||
|
"symfony/var-dumper": "^6.4|^7.0"
|
||||||
|
},
|
||||||
|
"type": "symfony-bridge",
|
||||||
|
"autoload": {
|
||||||
|
"psr-4": {
|
||||||
|
"Symfony\\Bridge\\Monolog\\": ""
|
||||||
|
},
|
||||||
|
"exclude-from-classmap": [
|
||||||
|
"/Tests/"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"notification-url": "https://packagist.org/downloads/",
|
||||||
|
"license": [
|
||||||
|
"MIT"
|
||||||
|
],
|
||||||
|
"authors": [
|
||||||
|
{
|
||||||
|
"name": "Fabien Potencier",
|
||||||
|
"email": "fabien@symfony.com"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Symfony Community",
|
||||||
|
"homepage": "https://symfony.com/contributors"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "Provides integration for Monolog with various Symfony components",
|
||||||
|
"homepage": "https://symfony.com",
|
||||||
|
"support": {
|
||||||
|
"source": "https://github.com/symfony/monolog-bridge/tree/v7.3.5"
|
||||||
|
},
|
||||||
|
"funding": [
|
||||||
|
{
|
||||||
|
"url": "https://symfony.com/sponsor",
|
||||||
|
"type": "custom"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "https://github.com/fabpot",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "https://github.com/nicolas-grekas",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
|
||||||
|
"type": "tidelift"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"time": "2025-10-14T19:16:15+00:00"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "symfony/monolog-bundle",
|
||||||
|
"version": "v3.10.0",
|
||||||
|
"source": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/symfony/monolog-bundle.git",
|
||||||
|
"reference": "414f951743f4aa1fd0f5bf6a0e9c16af3fe7f181"
|
||||||
|
},
|
||||||
|
"dist": {
|
||||||
|
"type": "zip",
|
||||||
|
"url": "https://api.github.com/repos/symfony/monolog-bundle/zipball/414f951743f4aa1fd0f5bf6a0e9c16af3fe7f181",
|
||||||
|
"reference": "414f951743f4aa1fd0f5bf6a0e9c16af3fe7f181",
|
||||||
|
"shasum": ""
|
||||||
|
},
|
||||||
|
"require": {
|
||||||
|
"monolog/monolog": "^1.25.1 || ^2.0 || ^3.0",
|
||||||
|
"php": ">=7.2.5",
|
||||||
|
"symfony/config": "^5.4 || ^6.0 || ^7.0",
|
||||||
|
"symfony/dependency-injection": "^5.4 || ^6.0 || ^7.0",
|
||||||
|
"symfony/http-kernel": "^5.4 || ^6.0 || ^7.0",
|
||||||
|
"symfony/monolog-bridge": "^5.4 || ^6.0 || ^7.0"
|
||||||
|
},
|
||||||
|
"require-dev": {
|
||||||
|
"symfony/console": "^5.4 || ^6.0 || ^7.0",
|
||||||
|
"symfony/phpunit-bridge": "^6.3 || ^7.0",
|
||||||
|
"symfony/yaml": "^5.4 || ^6.0 || ^7.0"
|
||||||
|
},
|
||||||
|
"type": "symfony-bundle",
|
||||||
|
"extra": {
|
||||||
|
"branch-alias": {
|
||||||
|
"dev-master": "3.x-dev"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"autoload": {
|
||||||
|
"psr-4": {
|
||||||
|
"Symfony\\Bundle\\MonologBundle\\": ""
|
||||||
|
},
|
||||||
|
"exclude-from-classmap": [
|
||||||
|
"/Tests/"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"notification-url": "https://packagist.org/downloads/",
|
||||||
|
"license": [
|
||||||
|
"MIT"
|
||||||
|
],
|
||||||
|
"authors": [
|
||||||
|
{
|
||||||
|
"name": "Fabien Potencier",
|
||||||
|
"email": "fabien@symfony.com"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Symfony Community",
|
||||||
|
"homepage": "https://symfony.com/contributors"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "Symfony MonologBundle",
|
||||||
|
"homepage": "https://symfony.com",
|
||||||
|
"keywords": [
|
||||||
|
"log",
|
||||||
|
"logging"
|
||||||
|
],
|
||||||
|
"support": {
|
||||||
|
"issues": "https://github.com/symfony/monolog-bundle/issues",
|
||||||
|
"source": "https://github.com/symfony/monolog-bundle/tree/v3.10.0"
|
||||||
|
},
|
||||||
|
"funding": [
|
||||||
|
{
|
||||||
|
"url": "https://symfony.com/sponsor",
|
||||||
|
"type": "custom"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "https://github.com/fabpot",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
|
||||||
|
"type": "tidelift"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"time": "2023-11-06T17:08:13+00:00"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "symfony/notifier",
|
"name": "symfony/notifier",
|
||||||
"version": "v7.3.0",
|
"version": "v7.3.0",
|
||||||
@@ -8777,6 +9293,89 @@
|
|||||||
],
|
],
|
||||||
"time": "2025-04-04T13:12:05+00:00"
|
"time": "2025-04-04T13:12:05+00:00"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "symfony/psr-http-message-bridge",
|
||||||
|
"version": "v7.3.0",
|
||||||
|
"source": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/symfony/psr-http-message-bridge.git",
|
||||||
|
"reference": "03f2f72319e7acaf2a9f6fcbe30ef17eec51594f"
|
||||||
|
},
|
||||||
|
"dist": {
|
||||||
|
"type": "zip",
|
||||||
|
"url": "https://api.github.com/repos/symfony/psr-http-message-bridge/zipball/03f2f72319e7acaf2a9f6fcbe30ef17eec51594f",
|
||||||
|
"reference": "03f2f72319e7acaf2a9f6fcbe30ef17eec51594f",
|
||||||
|
"shasum": ""
|
||||||
|
},
|
||||||
|
"require": {
|
||||||
|
"php": ">=8.2",
|
||||||
|
"psr/http-message": "^1.0|^2.0",
|
||||||
|
"symfony/http-foundation": "^6.4|^7.0"
|
||||||
|
},
|
||||||
|
"conflict": {
|
||||||
|
"php-http/discovery": "<1.15",
|
||||||
|
"symfony/http-kernel": "<6.4"
|
||||||
|
},
|
||||||
|
"require-dev": {
|
||||||
|
"nyholm/psr7": "^1.1",
|
||||||
|
"php-http/discovery": "^1.15",
|
||||||
|
"psr/log": "^1.1.4|^2|^3",
|
||||||
|
"symfony/browser-kit": "^6.4|^7.0",
|
||||||
|
"symfony/config": "^6.4|^7.0",
|
||||||
|
"symfony/event-dispatcher": "^6.4|^7.0",
|
||||||
|
"symfony/framework-bundle": "^6.4|^7.0",
|
||||||
|
"symfony/http-kernel": "^6.4|^7.0"
|
||||||
|
},
|
||||||
|
"type": "symfony-bridge",
|
||||||
|
"autoload": {
|
||||||
|
"psr-4": {
|
||||||
|
"Symfony\\Bridge\\PsrHttpMessage\\": ""
|
||||||
|
},
|
||||||
|
"exclude-from-classmap": [
|
||||||
|
"/Tests/"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"notification-url": "https://packagist.org/downloads/",
|
||||||
|
"license": [
|
||||||
|
"MIT"
|
||||||
|
],
|
||||||
|
"authors": [
|
||||||
|
{
|
||||||
|
"name": "Fabien Potencier",
|
||||||
|
"email": "fabien@symfony.com"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Symfony Community",
|
||||||
|
"homepage": "https://symfony.com/contributors"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "PSR HTTP message bridge",
|
||||||
|
"homepage": "https://symfony.com",
|
||||||
|
"keywords": [
|
||||||
|
"http",
|
||||||
|
"http-message",
|
||||||
|
"psr-17",
|
||||||
|
"psr-7"
|
||||||
|
],
|
||||||
|
"support": {
|
||||||
|
"source": "https://github.com/symfony/psr-http-message-bridge/tree/v7.3.0"
|
||||||
|
},
|
||||||
|
"funding": [
|
||||||
|
{
|
||||||
|
"url": "https://symfony.com/sponsor",
|
||||||
|
"type": "custom"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "https://github.com/fabpot",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
|
||||||
|
"type": "tidelift"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"time": "2024-09-26T08:57:56+00:00"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "symfony/routing",
|
"name": "symfony/routing",
|
||||||
"version": "v7.3.0",
|
"version": "v7.3.0",
|
||||||
|
|||||||
@@ -23,4 +23,6 @@ return [
|
|||||||
SymfonyCasts\Bundle\ResetPassword\SymfonyCastsResetPasswordBundle::class => ['all' => true],
|
SymfonyCasts\Bundle\ResetPassword\SymfonyCastsResetPasswordBundle::class => ['all' => true],
|
||||||
Drenso\OidcBundle\DrensoOidcBundle::class => ['all' => true],
|
Drenso\OidcBundle\DrensoOidcBundle::class => ['all' => true],
|
||||||
SpomkyLabs\PwaBundle\SpomkyLabsPwaBundle::class => ['all' => true],
|
SpomkyLabs\PwaBundle\SpomkyLabsPwaBundle::class => ['all' => true],
|
||||||
|
Sentry\SentryBundle\SentryBundle::class => ['prod' => true],
|
||||||
|
Symfony\Bundle\MonologBundle\MonologBundle::class => ['all' => true],
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -41,7 +41,13 @@ doctrine:
|
|||||||
is_bundle: false
|
is_bundle: false
|
||||||
dir: '%kernel.project_dir%/src/Monitor/Framework/Entity'
|
dir: '%kernel.project_dir%/src/Monitor/Framework/Entity'
|
||||||
prefix: 'App\Monitor\Framework\Entity'
|
prefix: 'App\Monitor\Framework\Entity'
|
||||||
alias: Download
|
alias: Monitor
|
||||||
|
EventLog:
|
||||||
|
type: attribute
|
||||||
|
is_bundle: false
|
||||||
|
dir: '%kernel.project_dir%/src/EventLog/Framework/Entity'
|
||||||
|
prefix: 'App\EventLog\Framework\Entity'
|
||||||
|
alias: EventLog
|
||||||
controller_resolver:
|
controller_resolver:
|
||||||
auto_mapping: false
|
auto_mapping: false
|
||||||
|
|
||||||
|
|||||||
@@ -15,6 +15,26 @@ framework:
|
|||||||
max_retries: 1
|
max_retries: 1
|
||||||
multiplier: 1
|
multiplier: 1
|
||||||
|
|
||||||
|
download:
|
||||||
|
dsn: '%env(MESSENGER_TRANSPORT_DSN)%'
|
||||||
|
options:
|
||||||
|
use_notify: true
|
||||||
|
check_delayed_interval: 60000
|
||||||
|
queue_name: download
|
||||||
|
retry_strategy:
|
||||||
|
max_retries: 3
|
||||||
|
multiplier: 1
|
||||||
|
|
||||||
|
monitor:
|
||||||
|
dsn: '%env(MESSENGER_TRANSPORT_DSN)%'
|
||||||
|
options:
|
||||||
|
use_notify: true
|
||||||
|
check_delayed_interval: 60000
|
||||||
|
queue_name: monitor
|
||||||
|
retry_strategy:
|
||||||
|
max_retries: 1
|
||||||
|
multiplier: 1
|
||||||
|
|
||||||
media_cache:
|
media_cache:
|
||||||
dsn: '%env(MESSENGER_TRANSPORT_DSN)%'
|
dsn: '%env(MESSENGER_TRANSPORT_DSN)%'
|
||||||
options:
|
options:
|
||||||
@@ -36,12 +56,12 @@ framework:
|
|||||||
routing:
|
routing:
|
||||||
# Route your messages to the transports
|
# Route your messages to the transports
|
||||||
# 'App\Message\YourMessage': async
|
# 'App\Message\YourMessage': async
|
||||||
'App\Download\Action\Command\DownloadMediaCommand': async
|
'App\Download\Action\Command\DownloadMediaCommand': download
|
||||||
'App\Download\Action\Command\DownloadSeasonCommand': async
|
'App\Download\Action\Command\DownloadSeasonCommand': async
|
||||||
'App\Monitor\Action\Command\MonitorTvEpisodeCommand': async
|
'App\Monitor\Action\Command\MonitorTvEpisodeCommand': monitor
|
||||||
'App\Monitor\Action\Command\MonitorTvSeasonCommand': async
|
'App\Monitor\Action\Command\MonitorTvSeasonCommand': monitor
|
||||||
'App\Monitor\Action\Command\MonitorTvShowCommand': async
|
'App\Monitor\Action\Command\MonitorTvShowCommand': monitor
|
||||||
'App\Monitor\Action\Command\MonitorMovieCommand': async
|
'App\Monitor\Action\Command\MonitorMovieCommand': monitor
|
||||||
'App\Torrentio\Action\Command\GetTvShowOptionsCommand': media_cache
|
'App\Torrentio\Action\Command\GetTvShowOptionsCommand': media_cache
|
||||||
|
|
||||||
# when@test:
|
# when@test:
|
||||||
|
|||||||
63
config/packages/monolog.yaml
Normal file
63
config/packages/monolog.yaml
Normal file
@@ -0,0 +1,63 @@
|
|||||||
|
monolog:
|
||||||
|
channels:
|
||||||
|
- deprecation # Deprecations are logged in the dedicated "deprecation" channel when it exists
|
||||||
|
|
||||||
|
when@dev:
|
||||||
|
monolog:
|
||||||
|
handlers:
|
||||||
|
main:
|
||||||
|
type: stream
|
||||||
|
path: "%kernel.logs_dir%/%kernel.environment%.log"
|
||||||
|
level: debug
|
||||||
|
channels: ["!event"]
|
||||||
|
# uncomment to get logging in your browser
|
||||||
|
# you may have to allow bigger header sizes in your Web server configuration
|
||||||
|
#firephp:
|
||||||
|
# type: firephp
|
||||||
|
# level: info
|
||||||
|
#chromephp:
|
||||||
|
# type: chromephp
|
||||||
|
# level: info
|
||||||
|
console:
|
||||||
|
type: console
|
||||||
|
process_psr_3_messages: false
|
||||||
|
channels: ["!event", "!doctrine", "!console"]
|
||||||
|
|
||||||
|
when@test:
|
||||||
|
monolog:
|
||||||
|
handlers:
|
||||||
|
main:
|
||||||
|
type: fingers_crossed
|
||||||
|
action_level: error
|
||||||
|
handler: nested
|
||||||
|
excluded_http_codes: [404, 405]
|
||||||
|
channels: ["!event"]
|
||||||
|
nested:
|
||||||
|
type: stream
|
||||||
|
path: "%kernel.logs_dir%/%kernel.environment%.log"
|
||||||
|
level: debug
|
||||||
|
|
||||||
|
when@prod:
|
||||||
|
monolog:
|
||||||
|
handlers:
|
||||||
|
main:
|
||||||
|
type: fingers_crossed
|
||||||
|
action_level: error
|
||||||
|
handler: nested
|
||||||
|
excluded_http_codes: [404, 405]
|
||||||
|
channels: ["!deprecation"]
|
||||||
|
buffer_size: 50 # How many messages should be saved? Prevent memory leaks
|
||||||
|
nested:
|
||||||
|
type: stream
|
||||||
|
path: php://stderr
|
||||||
|
level: debug
|
||||||
|
formatter: monolog.formatter.json
|
||||||
|
console:
|
||||||
|
type: console
|
||||||
|
process_psr_3_messages: false
|
||||||
|
channels: ["!event", "!doctrine"]
|
||||||
|
deprecation:
|
||||||
|
type: stream
|
||||||
|
channels: [deprecation]
|
||||||
|
path: php://stderr
|
||||||
|
formatter: monolog.formatter.json
|
||||||
34
config/packages/sentry.yaml
Normal file
34
config/packages/sentry.yaml
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
when@prod:
|
||||||
|
sentry:
|
||||||
|
register_error_listener: true # Disables the ErrorListener to avoid duplicated log in sentry
|
||||||
|
register_error_handler: true # Disables the ErrorListener, ExceptionListener and FatalErrorListener integrations of the base PHP SDK
|
||||||
|
|
||||||
|
tracing:
|
||||||
|
enabled: true
|
||||||
|
dbal: # DB queries
|
||||||
|
enabled: true
|
||||||
|
cache: # cache pools
|
||||||
|
enabled: true
|
||||||
|
twig: # templating engine
|
||||||
|
enabled: true
|
||||||
|
|
||||||
|
services:
|
||||||
|
# (Optionally) Configure the breadcrumb handler as a service (needed for the breadcrumb Monolog handler)
|
||||||
|
Sentry\Monolog\BreadcrumbHandler:
|
||||||
|
arguments:
|
||||||
|
- '@Sentry\State\HubInterface'
|
||||||
|
- !php/const Monolog\Logger::INFO # Configures the level of messages to capture as breadcrumbs
|
||||||
|
monolog:
|
||||||
|
handlers:
|
||||||
|
# (Optionally) Register the breadcrumb handler as a Monolog handler
|
||||||
|
sentry_breadcrumbs:
|
||||||
|
type: service
|
||||||
|
name: sentry_breadcrumbs
|
||||||
|
id: Sentry\Monolog\BreadcrumbHandler
|
||||||
|
# Register the handler as a Monolog handler to capture messages as events
|
||||||
|
sentry:
|
||||||
|
type: sentry
|
||||||
|
level: !php/const Monolog\Logger::ERROR # Configures the level of messages to capture as events
|
||||||
|
hub_id: Sentry\State\HubInterface
|
||||||
|
fill_extra_context: true # Enables sending monolog context to Sentry
|
||||||
|
process_psr_3_messages: false # Disables the resolution of PSR-3 placeholders in reported messages
|
||||||
@@ -6,6 +6,14 @@ controllersBase:
|
|||||||
defaults:
|
defaults:
|
||||||
schemes: [ 'https' ]
|
schemes: [ 'https' ]
|
||||||
|
|
||||||
|
controllersEventLog:
|
||||||
|
resource:
|
||||||
|
path: ../src/EventLog/Framework/Controller/
|
||||||
|
namespace: App\EventLog\Framework\Controller
|
||||||
|
type: attribute
|
||||||
|
defaults:
|
||||||
|
schemes: [ 'https' ]
|
||||||
|
|
||||||
controllersLibrary:
|
controllersLibrary:
|
||||||
resource:
|
resource:
|
||||||
path: ../src/Library/Framework/Controller/
|
path: ../src/Library/Framework/Controller/
|
||||||
|
|||||||
@@ -13,12 +13,6 @@ services:
|
|||||||
- /mnt/media/downloads/tvshows:/var/download/tvshows
|
- /mnt/media/downloads/tvshows:/var/download/tvshows
|
||||||
- mercure_data:/data
|
- mercure_data:/data
|
||||||
- mercure_config:/config
|
- mercure_config:/config
|
||||||
depends_on:
|
|
||||||
- database
|
|
||||||
logging:
|
|
||||||
driver: "gelf"
|
|
||||||
options:
|
|
||||||
gelf-address: "tcp://192.168.1.197:12202"
|
|
||||||
|
|
||||||
|
|
||||||
worker:
|
worker:
|
||||||
@@ -26,16 +20,10 @@ services:
|
|||||||
volumes:
|
volumes:
|
||||||
- /mnt/media/downloads/movies:/var/download/movies
|
- /mnt/media/downloads/movies:/var/download/movies
|
||||||
- /mnt/media/downloads/tvshows:/var/download/tvshows
|
- /mnt/media/downloads/tvshows:/var/download/tvshows
|
||||||
restart: always
|
|
||||||
command: -vv --time-limit=3600 --limit=10
|
|
||||||
deploy:
|
deploy:
|
||||||
replicas: 2
|
replicas: 2
|
||||||
depends_on:
|
depends_on:
|
||||||
- app
|
- app
|
||||||
logging:
|
|
||||||
driver: "gelf"
|
|
||||||
options:
|
|
||||||
gelf-address: "tcp://192.168.1.197:12203"
|
|
||||||
|
|
||||||
|
|
||||||
scheduler:
|
scheduler:
|
||||||
@@ -43,28 +31,10 @@ services:
|
|||||||
volumes:
|
volumes:
|
||||||
- /mnt/media/downloads/movies:/var/download/movies
|
- /mnt/media/downloads/movies:/var/download/movies
|
||||||
- /mnt/media/downloads/tvshows:/var/download/tvshows
|
- /mnt/media/downloads/tvshows:/var/download/tvshows
|
||||||
restart: always
|
|
||||||
command: -vv
|
|
||||||
depends_on:
|
depends_on:
|
||||||
- app
|
- app
|
||||||
logging:
|
|
||||||
driver: "gelf"
|
|
||||||
options:
|
|
||||||
gelf-address: "tcp://192.168.1.197:12204"
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
redis:
|
|
||||||
image: redis:latest
|
|
||||||
volumes:
|
|
||||||
- redis_data:/data
|
|
||||||
command: redis-server --maxmemory 512MB
|
|
||||||
restart: unless-stopped
|
|
||||||
|
|
||||||
|
|
||||||
volumes:
|
volumes:
|
||||||
mysql:
|
|
||||||
mercure_config:
|
mercure_config:
|
||||||
mercure_data:
|
mercure_data:
|
||||||
redis_data:
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,19 +1,40 @@
|
|||||||
ARG FRANKENPHP_TAG
|
#####
|
||||||
|
# This version of Torsearch runs the scheduler, downloader, and worker
|
||||||
|
# in a single container. Each process is managerd by supervisord
|
||||||
|
# and can be configured via environment variables and more
|
||||||
|
# than one of these containers cans till be run.
|
||||||
|
#####
|
||||||
|
ARG ALPINE_VERSION="3.22"
|
||||||
|
FROM alpine:${ALPINE_VERSION} AS build_stage
|
||||||
|
|
||||||
FROM dunglas/frankenphp:${FRANKENPHP_TAG}
|
RUN apk add --no-cache \
|
||||||
|
curl \
|
||||||
|
php84 \
|
||||||
|
php84-ctype \
|
||||||
|
php84-curl \
|
||||||
|
php84-dom \
|
||||||
|
php84-fileinfo \
|
||||||
|
php84-fpm \
|
||||||
|
php84-gd \
|
||||||
|
php84-mbstring \
|
||||||
|
php84-mysqli \
|
||||||
|
php84-opcache \
|
||||||
|
php84-openssl \
|
||||||
|
php84-pdo_mysql \
|
||||||
|
php84-tokenizer \
|
||||||
|
php84-xml \
|
||||||
|
supervisor
|
||||||
|
|
||||||
ENV SERVER_NAME=":80"
|
RUN ln -s /usr/bin/php84 /usr/bin/php
|
||||||
ENV CADDY_GLOBAL_OPTIONS="auto_https off"
|
|
||||||
ENV APP_RUNTIME="Runtime\\FrankenPhpSymfony\\Runtime"
|
|
||||||
|
|
||||||
ARG APP_VERSION="0.dev"
|
RUN mkdir -p /etc/supervisor/conf.d
|
||||||
ENV APP_VERSION="${APP_VERSION}"
|
|
||||||
|
|
||||||
RUN install-php-extensions \
|
# We start supervisord and supervisord starts
|
||||||
pdo_mysql \
|
# respective service in the configuration file.
|
||||||
gd \
|
ENTRYPOINT ["/app/bin/console", "init:worker"]
|
||||||
intl \
|
|
||||||
zip \
|
# Message transports can be enabled by passing them as command options.
|
||||||
opcache
|
# Inlcluding a number with the option will start that amount of processes
|
||||||
|
# for the transport. Not supplying a number will default to 1.
|
||||||
|
# --download --monitor OR --download 2 --monitor
|
||||||
|
|
||||||
RUN apk add --no-cache wget
|
|
||||||
|
|||||||
@@ -1,10 +1,24 @@
|
|||||||
FROM code.caldwell.digital/home/torsearch-base-worker:php8.4-alpine
|
###
|
||||||
|
# This version of Torsearch can run the scheduler, downloader, and
|
||||||
|
# worker in one container. Each process is managerd by supervisord
|
||||||
|
# and can be configured via environment variables, and more than
|
||||||
|
# one of these containers can still be run.
|
||||||
|
###
|
||||||
|
|
||||||
ARG APP_VERSION="0.0.0-dev"
|
# Default to latest, but should pass in a version
|
||||||
ENV APP_VERSION="${APP_VERSION}"
|
ARG APP_VERSION="latest"
|
||||||
|
|
||||||
COPY . /app
|
# Set aside the actual app image so we can copy the app from there
|
||||||
|
FROM code.caldwell.digital/home/torsearch-app:${APP_VERSION} AS app_image
|
||||||
|
|
||||||
ENTRYPOINT [ "php", "/app/bin/console", "messenger:consume", "scheduler_monitor" ]
|
# Start with our base worker image
|
||||||
|
FROM code.caldwell.digital/home/torsearch-base-worker-supervisord:latest
|
||||||
|
|
||||||
HEALTHCHECK --interval=3s --timeout=3s --retries=10 CMD return 0
|
# Set the APP_VERSION in the image
|
||||||
|
ENV APP_VERSION=${APP_VERSION}
|
||||||
|
|
||||||
|
# Copy the actual application code from the previously built app
|
||||||
|
COPY --chown=1000:1000 --from=app_image /app /app
|
||||||
|
|
||||||
|
# To retain backwards compatibility, default to async & download transports
|
||||||
|
CMD [ "--monitor" ]
|
||||||
|
|||||||
@@ -1,10 +1,24 @@
|
|||||||
FROM code.caldwell.digital/home/torsearch-base-worker:php8.4-alpine
|
###
|
||||||
|
# This version of Torsearch can run the scheduler, downloader, and
|
||||||
|
# worker in one container. Each process is managerd by supervisord
|
||||||
|
# and can be configured via environment variables, and more than
|
||||||
|
# one of these containers can still be run.
|
||||||
|
###
|
||||||
|
|
||||||
ARG APP_VERSION="0.0.0-dev"
|
# Default to latest, but should pass in a version
|
||||||
ENV APP_VERSION="${APP_VERSION}"
|
ARG APP_VERSION="latest"
|
||||||
|
|
||||||
COPY . /app
|
# Set aside the actual app image so we can copy the app from there
|
||||||
|
FROM code.caldwell.digital/home/torsearch-app:${APP_VERSION} AS app_image
|
||||||
|
|
||||||
ENTRYPOINT [ "php", "/app/bin/console", "messenger:consume", "async" ]
|
# Start with our base worker image
|
||||||
|
FROM code.caldwell.digital/home/torsearch-base-worker-supervisord:latest
|
||||||
|
|
||||||
HEALTHCHECK --interval=3s --timeout=3s --retries=10 CMD return 0
|
# Set the APP_VERSION in the image
|
||||||
|
ENV APP_VERSION=${APP_VERSION}
|
||||||
|
|
||||||
|
# Copy the actual application code from the previously built app
|
||||||
|
COPY --chown=1000:1000 --from=app_image /app /app
|
||||||
|
|
||||||
|
# To retain backwards compatibility, default to async & download transports
|
||||||
|
CMD [ "--async", "--download" ]
|
||||||
|
|||||||
10
docker/worker/async.conf
Normal file
10
docker/worker/async.conf
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
[program:torsearch-worker]
|
||||||
|
command=/app/bin/console messenger:consume async -vv --time-limit 3600
|
||||||
|
numprocs=1
|
||||||
|
stdout_logfile=/dev/stdout
|
||||||
|
stdout_logfile_maxbytes=0
|
||||||
|
stderr_logfile=/dev/stderr
|
||||||
|
stderr_logfile_maxbytes=0
|
||||||
|
autorestart=true
|
||||||
|
startretries=3
|
||||||
|
process_name=%(program_name)s_%(process_num)02d
|
||||||
10
docker/worker/download.conf
Normal file
10
docker/worker/download.conf
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
[program:torsearch-downloader]
|
||||||
|
command=/app/bin/console messenger:consume download -vv --time-limit 3600
|
||||||
|
numprocs=1
|
||||||
|
stdout_logfile=/dev/stdout
|
||||||
|
stdout_logfile_maxbytes=0
|
||||||
|
stderr_logfile=/dev/stderr
|
||||||
|
stderr_logfile_maxbytes=0
|
||||||
|
autorestart=true
|
||||||
|
startretries=3
|
||||||
|
process_name=%(program_name)s_%(process_num)02d
|
||||||
10
docker/worker/monitor.conf
Normal file
10
docker/worker/monitor.conf
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
[program:torsearch-scheduler]
|
||||||
|
command=/app/bin/console messenger:consume scheduler_monitor monitor -vv --time-limit 21600
|
||||||
|
numprocs=1
|
||||||
|
stdout_logfile=/dev/stdout
|
||||||
|
stdout_logfile_maxbytes=0
|
||||||
|
stderr_logfile=/dev/stderr
|
||||||
|
stderr_logfile_maxbytes=0
|
||||||
|
autorestart=true
|
||||||
|
startretries=3
|
||||||
|
process_name=%(program_name)s_%(process_num)02d
|
||||||
5
docker/worker/supervisord.conf
Normal file
5
docker/worker/supervisord.conf
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
[supervisord]
|
||||||
|
nodaemon=true
|
||||||
|
logfile=/dev/null
|
||||||
|
logfile_maxbytes=0
|
||||||
|
pidfile=/run/supervisord.pid
|
||||||
@@ -21,10 +21,7 @@ final class Version20250831013403 extends AbstractMigration
|
|||||||
{
|
{
|
||||||
// this up() migration is auto-generated, please modify it to your needs
|
// this up() migration is auto-generated, please modify it to your needs
|
||||||
$this->addSql(<<<'SQL'
|
$this->addSql(<<<'SQL'
|
||||||
DROP TABLE sessions
|
DROP TABLE IF EXISTS sessions
|
||||||
SQL);
|
|
||||||
$this->addSql(<<<'SQL'
|
|
||||||
ALTER TABLE download CHANGE created_at created_at DATETIME NOT NULL, CHANGE updated_at updated_at DATETIME NOT NULL
|
|
||||||
SQL);
|
SQL);
|
||||||
$this->addSql(<<<'SQL'
|
$this->addSql(<<<'SQL'
|
||||||
ALTER TABLE user_preference CHANGE preference_value preference_value VARCHAR(1024) DEFAULT NULL
|
ALTER TABLE user_preference CHANGE preference_value preference_value VARCHAR(1024) DEFAULT NULL
|
||||||
@@ -37,9 +34,6 @@ final class Version20250831013403 extends AbstractMigration
|
|||||||
$this->addSql(<<<'SQL'
|
$this->addSql(<<<'SQL'
|
||||||
CREATE TABLE sessions (sess_id VARBINARY(128) NOT NULL, sess_data LONGBLOB NOT NULL, sess_lifetime INT UNSIGNED NOT NULL, sess_time INT UNSIGNED NOT NULL, INDEX sess_lifetime_idx (sess_lifetime), PRIMARY KEY(sess_id)) DEFAULT CHARACTER SET utf8mb4 COLLATE `utf8mb4_bin` ENGINE = InnoDB COMMENT = ''
|
CREATE TABLE sessions (sess_id VARBINARY(128) NOT NULL, sess_data LONGBLOB NOT NULL, sess_lifetime INT UNSIGNED NOT NULL, sess_time INT UNSIGNED NOT NULL, INDEX sess_lifetime_idx (sess_lifetime), PRIMARY KEY(sess_id)) DEFAULT CHARACTER SET utf8mb4 COLLATE `utf8mb4_bin` ENGINE = InnoDB COMMENT = ''
|
||||||
SQL);
|
SQL);
|
||||||
$this->addSql(<<<'SQL'
|
|
||||||
ALTER TABLE download CHANGE created_at created_at DATETIME DEFAULT NULL, CHANGE updated_at updated_at DATETIME DEFAULT NULL
|
|
||||||
SQL);
|
|
||||||
$this->addSql(<<<'SQL'
|
$this->addSql(<<<'SQL'
|
||||||
ALTER TABLE user_preference CHANGE preference_value preference_value VARCHAR(255) DEFAULT NULL
|
ALTER TABLE user_preference CHANGE preference_value preference_value VARCHAR(255) DEFAULT NULL
|
||||||
SQL);
|
SQL);
|
||||||
|
|||||||
35
migrations/Version20251101194723.php
Normal file
35
migrations/Version20251101194723.php
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace DoctrineMigrations;
|
||||||
|
|
||||||
|
use Doctrine\DBAL\Schema\Schema;
|
||||||
|
use Doctrine\Migrations\AbstractMigration;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Auto-generated Migration: Please modify to your needs!
|
||||||
|
*/
|
||||||
|
final class Version20251101194723 extends AbstractMigration
|
||||||
|
{
|
||||||
|
public function getDescription(): string
|
||||||
|
{
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
public function up(Schema $schema): void
|
||||||
|
{
|
||||||
|
// this up() migration is auto-generated, please modify it to your needs
|
||||||
|
$this->addSql(<<<'SQL'
|
||||||
|
ALTER TABLE user_preference CHANGE preference_value preference_value VARCHAR(255) DEFAULT NULL
|
||||||
|
SQL);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function down(Schema $schema): void
|
||||||
|
{
|
||||||
|
// this down() migration is auto-generated, please modify it to your needs
|
||||||
|
$this->addSql(<<<'SQL'
|
||||||
|
ALTER TABLE user_preference CHANGE preference_value preference_value VARCHAR(1024) DEFAULT NULL
|
||||||
|
SQL);
|
||||||
|
}
|
||||||
|
}
|
||||||
35
migrations/Version20251101211617.php
Normal file
35
migrations/Version20251101211617.php
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace DoctrineMigrations;
|
||||||
|
|
||||||
|
use Doctrine\DBAL\Schema\Schema;
|
||||||
|
use Doctrine\Migrations\AbstractMigration;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Auto-generated Migration: Please modify to your needs!
|
||||||
|
*/
|
||||||
|
final class Version20251101211617 extends AbstractMigration
|
||||||
|
{
|
||||||
|
public function getDescription(): string
|
||||||
|
{
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
public function up(Schema $schema): void
|
||||||
|
{
|
||||||
|
// this up() migration is auto-generated, please modify it to your needs
|
||||||
|
$this->addSql(<<<'SQL'
|
||||||
|
CREATE TABLE event_log (id INT AUTO_INCREMENT NOT NULL, type VARCHAR(255) DEFAULT NULL, message LONGTEXT DEFAULT NULL, context JSON DEFAULT NULL COMMENT '(DC2Type:json)', PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8mb4 COLLATE `utf8mb4_unicode_ci` ENGINE = InnoDB
|
||||||
|
SQL);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function down(Schema $schema): void
|
||||||
|
{
|
||||||
|
// this down() migration is auto-generated, please modify it to your needs
|
||||||
|
$this->addSql(<<<'SQL'
|
||||||
|
DROP TABLE event_log
|
||||||
|
SQL);
|
||||||
|
}
|
||||||
|
}
|
||||||
47
migrations/Version20251102004627.php
Normal file
47
migrations/Version20251102004627.php
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace DoctrineMigrations;
|
||||||
|
|
||||||
|
use Doctrine\DBAL\Schema\Schema;
|
||||||
|
use Doctrine\Migrations\AbstractMigration;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Auto-generated Migration: Please modify to your needs!
|
||||||
|
*/
|
||||||
|
final class Version20251102004627 extends AbstractMigration
|
||||||
|
{
|
||||||
|
public function getDescription(): string
|
||||||
|
{
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
public function up(Schema $schema): void
|
||||||
|
{
|
||||||
|
// this up() migration is auto-generated, please modify it to your needs
|
||||||
|
$this->addSql(<<<'SQL'
|
||||||
|
ALTER TABLE event_log ADD user_id INT DEFAULT NULL
|
||||||
|
SQL);
|
||||||
|
$this->addSql(<<<'SQL'
|
||||||
|
ALTER TABLE event_log ADD CONSTRAINT FK_9EF0AD16A76ED395 FOREIGN KEY (user_id) REFERENCES user (id)
|
||||||
|
SQL);
|
||||||
|
$this->addSql(<<<'SQL'
|
||||||
|
CREATE INDEX IDX_9EF0AD16A76ED395 ON event_log (user_id)
|
||||||
|
SQL);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function down(Schema $schema): void
|
||||||
|
{
|
||||||
|
// this down() migration is auto-generated, please modify it to your needs
|
||||||
|
$this->addSql(<<<'SQL'
|
||||||
|
ALTER TABLE event_log DROP FOREIGN KEY FK_9EF0AD16A76ED395
|
||||||
|
SQL);
|
||||||
|
$this->addSql(<<<'SQL'
|
||||||
|
DROP INDEX IDX_9EF0AD16A76ED395 ON event_log
|
||||||
|
SQL);
|
||||||
|
$this->addSql(<<<'SQL'
|
||||||
|
ALTER TABLE event_log DROP user_id
|
||||||
|
SQL);
|
||||||
|
}
|
||||||
|
}
|
||||||
35
migrations/Version20251102221034.php
Normal file
35
migrations/Version20251102221034.php
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace DoctrineMigrations;
|
||||||
|
|
||||||
|
use Doctrine\DBAL\Schema\Schema;
|
||||||
|
use Doctrine\Migrations\AbstractMigration;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Auto-generated Migration: Please modify to your needs!
|
||||||
|
*/
|
||||||
|
final class Version20251102221034 extends AbstractMigration
|
||||||
|
{
|
||||||
|
public function getDescription(): string
|
||||||
|
{
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
public function up(Schema $schema): void
|
||||||
|
{
|
||||||
|
// this up() migration is auto-generated, please modify it to your needs
|
||||||
|
$this->addSql(<<<'SQL'
|
||||||
|
ALTER TABLE event_log ADD created_at DATETIME NULL, ADD updated_at DATETIME NULL
|
||||||
|
SQL);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function down(Schema $schema): void
|
||||||
|
{
|
||||||
|
// this down() migration is auto-generated, please modify it to your needs
|
||||||
|
$this->addSql(<<<'SQL'
|
||||||
|
ALTER TABLE event_log DROP created_at, DROP updated_at
|
||||||
|
SQL);
|
||||||
|
}
|
||||||
|
}
|
||||||
17
psalm.xml
Normal file
17
psalm.xml
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
<?xml version="1.0"?>
|
||||||
|
<psalm
|
||||||
|
errorLevel="7"
|
||||||
|
resolveFromConfigFile="true"
|
||||||
|
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||||
|
xmlns="https://getpsalm.org/schema/config"
|
||||||
|
xsi:schemaLocation="https://getpsalm.org/schema/config vendor/vimeo/psalm/config.xsd"
|
||||||
|
findUnusedBaselineEntry="true"
|
||||||
|
findUnusedCode="true"
|
||||||
|
>
|
||||||
|
<projectFiles>
|
||||||
|
<directory name="src" />
|
||||||
|
<ignoreFiles>
|
||||||
|
<directory name="vendor" />
|
||||||
|
</ignoreFiles>
|
||||||
|
</projectFiles>
|
||||||
|
</psalm>
|
||||||
90
src/Base/Framework/Command/InitWorker.php
Normal file
90
src/Base/Framework/Command/InitWorker.php
Normal file
@@ -0,0 +1,90 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Base\Framework\Command;
|
||||||
|
|
||||||
|
use Symfony\Component\Console\Command\Command;
|
||||||
|
use Symfony\Component\Console\Input\InputInterface;
|
||||||
|
use Symfony\Component\Console\Input\InputOption;
|
||||||
|
use Symfony\Component\Console\Output\OutputInterface;
|
||||||
|
use Twig\Environment;
|
||||||
|
|
||||||
|
class InitWorker extends Command
|
||||||
|
{
|
||||||
|
public function __construct(
|
||||||
|
private Environment $twig,
|
||||||
|
) {
|
||||||
|
parent::__construct();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function configure(): void
|
||||||
|
{
|
||||||
|
$this
|
||||||
|
->setName('init:worker')
|
||||||
|
->addOption('async', null, InputOption::VALUE_OPTIONAL, 'Run the async worker.',false)
|
||||||
|
->addOption('download', null, InputOption::VALUE_OPTIONAL, 'Run the download worker.', false)
|
||||||
|
->addOption('monitor', null, InputOption::VALUE_OPTIONAL, 'Run the monitor worker.', false)
|
||||||
|
;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function execute(InputInterface $input, OutputInterface $output): int
|
||||||
|
{
|
||||||
|
$configFile = $this->twig->render("config/supervisord.conf.twig", []);
|
||||||
|
|
||||||
|
if ($this->optionExists($input, $output, 'async')) {
|
||||||
|
$configFile .= $this->twig->render("config/async.conf.twig", [
|
||||||
|
'replicas' => $this->getOptionValue('async', $input),
|
||||||
|
]) . "\n\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($this->optionExists($input, $output, 'download')) {
|
||||||
|
$configFile .= $this->twig->render("config/download.conf.twig", [
|
||||||
|
'replicas' => $this->getOptionValue('download', $input),
|
||||||
|
]). "\n\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($this->optionExists($input, $output, 'monitor')) {
|
||||||
|
$configFile .= $this->twig->render("config/monitor.conf.twig", [
|
||||||
|
'replicas' => $this->getOptionValue('monitor', $input),
|
||||||
|
]). "\n\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
if ("" !== $configFile) {
|
||||||
|
$output->writeln("[init:worker] Writing /etc/supervisor/conf.d/supervisord.conf");
|
||||||
|
file_put_contents("/etc/supervisor/conf.d/supervisord.conf", $configFile);
|
||||||
|
|
||||||
|
$output->writeln("[init:worker] Starting supervisord");
|
||||||
|
shell_exec("/usr/bin/supervisord -c /etc/supervisor/conf.d/supervisord.conf");
|
||||||
|
return Command::SUCCESS;
|
||||||
|
}
|
||||||
|
|
||||||
|
$output->writeln("[init:worker] No workers selected. Exiting.");
|
||||||
|
return Command::FAILURE;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function optionExists(InputInterface $input, OutputInterface $output, string $option): bool
|
||||||
|
{
|
||||||
|
if ($input->getOption($option) !== false) {
|
||||||
|
$value = $input->getOption($option) ?? 1;
|
||||||
|
$output->writeln("[init:worker] transport: $option // $value // input var");;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
$optionKey = 'WORKER_' . strtoupper($option);
|
||||||
|
if (array_key_exists($optionKey, $_SERVER) && $_SERVER[$optionKey] !== null && $_SERVER[$optionKey] !== '') {
|
||||||
|
$output->writeln("[init:worker] transport: $option // $_SERVER[$optionKey] // env var");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function getOptionValue(string $option, InputInterface $input): int
|
||||||
|
{
|
||||||
|
if ($input->getOption($option) !== false) {
|
||||||
|
return $input->getOption($option) ?? 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
$optionKey = 'WORKER_' . strtoupper($option);
|
||||||
|
return $_SERVER[$optionKey];
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -4,17 +4,22 @@ namespace App\Download\Action\Handler;
|
|||||||
|
|
||||||
use App\Download\Action\Command\DeleteDownloadCommand;
|
use App\Download\Action\Command\DeleteDownloadCommand;
|
||||||
use App\Download\Action\Result\DeleteDownloadResult;
|
use App\Download\Action\Result\DeleteDownloadResult;
|
||||||
|
use App\Download\DownloadEvents;
|
||||||
use App\Download\Framework\Repository\DownloadRepository;
|
use App\Download\Framework\Repository\DownloadRepository;
|
||||||
|
use App\EventLog\Action\Command\AddEventLogCommand;
|
||||||
use App\Library\Action\Command\DeleteMediaFileCommand;
|
use App\Library\Action\Command\DeleteMediaFileCommand;
|
||||||
use App\Library\Action\Handler\DeleteMediaFileHandler;
|
use App\Library\Action\Handler\DeleteMediaFileHandler;
|
||||||
|
use App\Monitor\MonitorEvents;
|
||||||
use OneToMany\RichBundle\Contract\CommandInterface;
|
use OneToMany\RichBundle\Contract\CommandInterface;
|
||||||
use OneToMany\RichBundle\Contract\HandlerInterface;
|
use OneToMany\RichBundle\Contract\HandlerInterface;
|
||||||
use OneToMany\RichBundle\Contract\ResultInterface;
|
use OneToMany\RichBundle\Contract\ResultInterface;
|
||||||
|
use Symfony\Component\Messenger\MessageBusInterface;
|
||||||
|
|
||||||
/** @implements HandlerInterface<DeleteDownloadCommand, DeleteDownloadResult> */
|
/** @implements HandlerInterface<DeleteDownloadCommand, DeleteDownloadResult> */
|
||||||
readonly class DeleteDownloadHandler implements HandlerInterface
|
readonly class DeleteDownloadHandler implements HandlerInterface
|
||||||
{
|
{
|
||||||
public function __construct(
|
public function __construct(
|
||||||
|
private MessageBusInterface $bus,
|
||||||
private DownloadRepository $downloadRepository,
|
private DownloadRepository $downloadRepository,
|
||||||
private DeleteMediaFileHandler $deleteMediaFileHandler,
|
private DeleteMediaFileHandler $deleteMediaFileHandler,
|
||||||
) {}
|
) {}
|
||||||
@@ -31,6 +36,13 @@ readonly class DeleteDownloadHandler implements HandlerInterface
|
|||||||
}
|
}
|
||||||
$this->downloadRepository->delete($command->downloadId);
|
$this->downloadRepository->delete($command->downloadId);
|
||||||
|
|
||||||
|
$this->bus->dispatch(new AddEventLogCommand(
|
||||||
|
$download->getUser(),
|
||||||
|
DownloadEvents::DOWNLOAD_DELETED->type(),
|
||||||
|
DownloadEvents::DOWNLOAD_DELETED->message(),
|
||||||
|
(array) $download
|
||||||
|
));
|
||||||
|
|
||||||
return new DeleteDownloadResult(
|
return new DeleteDownloadResult(
|
||||||
status: 200,
|
status: 200,
|
||||||
message: 'Success',
|
message: 'Success',
|
||||||
|
|||||||
@@ -4,18 +4,22 @@ namespace App\Download\Action\Handler;
|
|||||||
|
|
||||||
use App\Download\Action\Command\DownloadMediaCommand;
|
use App\Download\Action\Command\DownloadMediaCommand;
|
||||||
use App\Download\Action\Result\DownloadMediaResult;
|
use App\Download\Action\Result\DownloadMediaResult;
|
||||||
|
use App\Download\DownloadEvents;
|
||||||
use App\Download\Framework\Repository\DownloadRepository;
|
use App\Download\Framework\Repository\DownloadRepository;
|
||||||
use App\Download\Downloader\DownloaderInterface;
|
use App\Download\Downloader\DownloaderInterface;
|
||||||
|
use App\EventLog\Action\Command\AddEventLogCommand;
|
||||||
use App\User\Framework\Repository\UserRepository;
|
use App\User\Framework\Repository\UserRepository;
|
||||||
use OneToMany\RichBundle\Contract\CommandInterface;
|
use OneToMany\RichBundle\Contract\CommandInterface;
|
||||||
use OneToMany\RichBundle\Contract\HandlerInterface;
|
use OneToMany\RichBundle\Contract\HandlerInterface;
|
||||||
use OneToMany\RichBundle\Contract\ResultInterface;
|
use OneToMany\RichBundle\Contract\ResultInterface;
|
||||||
use Symfony\Component\Messenger\Exception\UnrecoverableMessageHandlingException;
|
use Symfony\Component\Messenger\Exception\UnrecoverableMessageHandlingException;
|
||||||
|
use Symfony\Component\Messenger\MessageBusInterface;
|
||||||
|
|
||||||
/** @implements HandlerInterface<DownloadMediaCommand, DownloadMediaResult> */
|
/** @implements HandlerInterface<DownloadMediaCommand, DownloadMediaResult> */
|
||||||
readonly class DownloadMediaHandler implements HandlerInterface
|
readonly class DownloadMediaHandler implements HandlerInterface
|
||||||
{
|
{
|
||||||
public function __construct(
|
public function __construct(
|
||||||
|
private MessageBusInterface $bus,
|
||||||
private DownloaderInterface $downloader,
|
private DownloaderInterface $downloader,
|
||||||
private DownloadRepository $downloadRepository,
|
private DownloadRepository $downloadRepository,
|
||||||
private UserRepository $userRepository,
|
private UserRepository $userRepository,
|
||||||
@@ -23,9 +27,17 @@ readonly class DownloadMediaHandler implements HandlerInterface
|
|||||||
|
|
||||||
public function handle(CommandInterface $command): ResultInterface
|
public function handle(CommandInterface $command): ResultInterface
|
||||||
{
|
{
|
||||||
|
$user = $this->userRepository->find($command->userId);
|
||||||
|
$this->bus->dispatch(new AddEventLogCommand(
|
||||||
|
$user,
|
||||||
|
DownloadEvents::DOWNLOAD_STARTED->type(),
|
||||||
|
DownloadEvents::DOWNLOAD_STARTED->message(),
|
||||||
|
(array) $command
|
||||||
|
));
|
||||||
|
|
||||||
if (null === $command->downloadId) {
|
if (null === $command->downloadId) {
|
||||||
$download = $this->downloadRepository->insert(
|
$download = $this->downloadRepository->insert(
|
||||||
$this->userRepository->find($command->userId),
|
$user,
|
||||||
$command->url,
|
$command->url,
|
||||||
$command->title,
|
$command->title,
|
||||||
$command->filename,
|
$command->filename,
|
||||||
@@ -57,6 +69,12 @@ readonly class DownloadMediaHandler implements HandlerInterface
|
|||||||
throw new UnrecoverableMessageHandlingException($exception->getMessage(), 500);
|
throw new UnrecoverableMessageHandlingException($exception->getMessage(), 500);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$this->bus->dispatch(new AddEventLogCommand(
|
||||||
|
$user,
|
||||||
|
DownloadEvents::DOWNLOAD_FINISHED->type(),
|
||||||
|
DownloadEvents::DOWNLOAD_FINISHED->message(),
|
||||||
|
(array) $command
|
||||||
|
));
|
||||||
return new DownloadMediaResult(200, "Success.");
|
return new DownloadMediaResult(200, "Success.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
34
src/Download/DownloadEvents.php
Normal file
34
src/Download/DownloadEvents.php
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Download;
|
||||||
|
|
||||||
|
enum DownloadEvents
|
||||||
|
{
|
||||||
|
case DOWNLOAD_ADDED;
|
||||||
|
case DOWNLOAD_STARTED;
|
||||||
|
case DOWNLOAD_FINISHED;
|
||||||
|
case DOWNLOAD_DELETED;
|
||||||
|
case DOWNLOAD_ERROR;
|
||||||
|
|
||||||
|
public function type(): string
|
||||||
|
{
|
||||||
|
return match ($this) {
|
||||||
|
self::DOWNLOAD_ADDED => 'download_added',
|
||||||
|
self::DOWNLOAD_STARTED => 'download_started',
|
||||||
|
self::DOWNLOAD_FINISHED => 'download_finished',
|
||||||
|
self::DOWNLOAD_DELETED => 'download_deleted',
|
||||||
|
self::DOWNLOAD_ERROR => 'download_error',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public function message(): string
|
||||||
|
{
|
||||||
|
return match ($this) {
|
||||||
|
self::DOWNLOAD_ADDED => 'A new download has been added.',
|
||||||
|
self::DOWNLOAD_STARTED => 'A download has started.',
|
||||||
|
self::DOWNLOAD_FINISHED => 'A download has finished.',
|
||||||
|
self::DOWNLOAD_DELETED => 'A download has been deleted.',
|
||||||
|
self::DOWNLOAD_ERROR => 'A download has encountered an error.',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -4,9 +4,12 @@ namespace App\Download\Downloader;
|
|||||||
|
|
||||||
use App\Base\Service\Broadcaster;
|
use App\Base\Service\Broadcaster;
|
||||||
use App\Base\Service\MediaFiles;
|
use App\Base\Service\MediaFiles;
|
||||||
|
use App\Download\DownloadEvents;
|
||||||
use App\Download\Framework\Entity\Download;
|
use App\Download\Framework\Entity\Download;
|
||||||
|
use App\EventLog\Action\Command\AddEventLogCommand;
|
||||||
use Doctrine\ORM\EntityManagerInterface;
|
use Doctrine\ORM\EntityManagerInterface;
|
||||||
use Symfony\Component\Cache\Adapter\RedisAdapter;
|
use Symfony\Component\Cache\Adapter\RedisAdapter;
|
||||||
|
use Symfony\Component\Messenger\MessageBusInterface;
|
||||||
use Symfony\Component\Process\Exception\ProcessFailedException;
|
use Symfony\Component\Process\Exception\ProcessFailedException;
|
||||||
use Symfony\Component\Process\Process;
|
use Symfony\Component\Process\Process;
|
||||||
use Symfony\Contracts\Cache\CacheInterface;
|
use Symfony\Contracts\Cache\CacheInterface;
|
||||||
@@ -17,6 +20,7 @@ class ProcessDownloader implements DownloaderInterface
|
|||||||
* @var RedisAdapter $cache
|
* @var RedisAdapter $cache
|
||||||
*/
|
*/
|
||||||
public function __construct(
|
public function __construct(
|
||||||
|
private MessageBusInterface $bus,
|
||||||
private EntityManagerInterface $entityManager,
|
private EntityManagerInterface $entityManager,
|
||||||
private MediaFiles $mediaFiles,
|
private MediaFiles $mediaFiles,
|
||||||
private CacheInterface $cache,
|
private CacheInterface $cache,
|
||||||
@@ -88,6 +92,12 @@ class ProcessDownloader implements DownloaderInterface
|
|||||||
}
|
}
|
||||||
} catch (ProcessFailedException $exception) {
|
} catch (ProcessFailedException $exception) {
|
||||||
$downloadEntity->setStatus('Failed');
|
$downloadEntity->setStatus('Failed');
|
||||||
|
$this->bus->dispatch(new AddEventLogCommand(
|
||||||
|
$downloadEntity->getUser()->getId(),
|
||||||
|
DownloadEvents::DOWNLOAD_ERROR->type(),
|
||||||
|
DownloadEvents::DOWNLOAD_ERROR->message() . ': ' . $exception->getMessage(),
|
||||||
|
(array) $downloadEntity
|
||||||
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->entityManager->flush();
|
$this->entityManager->flush();
|
||||||
|
|||||||
@@ -11,7 +11,9 @@ use App\Download\Action\Input\DownloadMediaInput;
|
|||||||
use App\Download\Action\Input\DownloadSeasonInput;
|
use App\Download\Action\Input\DownloadSeasonInput;
|
||||||
use App\Download\Action\Input\PauseDownloadInput;
|
use App\Download\Action\Input\PauseDownloadInput;
|
||||||
use App\Download\Action\Input\ResumeDownloadInput;
|
use App\Download\Action\Input\ResumeDownloadInput;
|
||||||
|
use App\Download\DownloadEvents;
|
||||||
use App\Download\Framework\Repository\DownloadRepository;
|
use App\Download\Framework\Repository\DownloadRepository;
|
||||||
|
use App\EventLog\Action\Command\AddEventLogCommand;
|
||||||
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
||||||
use Symfony\Component\HttpFoundation\Response;
|
use Symfony\Component\HttpFoundation\Response;
|
||||||
use Symfony\Component\Messenger\MessageBusInterface;
|
use Symfony\Component\Messenger\MessageBusInterface;
|
||||||
@@ -41,6 +43,13 @@ class ApiController extends AbstractController
|
|||||||
$input->downloadId = $download->getId();
|
$input->downloadId = $download->getId();
|
||||||
$input->userId = $this->getUser()->getId();
|
$input->userId = $this->getUser()->getId();
|
||||||
|
|
||||||
|
$this->bus->dispatch(new AddEventLogCommand(
|
||||||
|
$this->getUser(),
|
||||||
|
DownloadEvents::DOWNLOAD_ADDED->type(),
|
||||||
|
DownloadEvents::DOWNLOAD_ADDED->message(),
|
||||||
|
(array) $download
|
||||||
|
));
|
||||||
|
|
||||||
try {
|
try {
|
||||||
$this->bus->dispatch($input->toCommand());
|
$this->bus->dispatch($input->toCommand());
|
||||||
} catch (\Throwable $exception) {
|
} catch (\Throwable $exception) {
|
||||||
|
|||||||
17
src/EventLog/Action/Command/AddEventLogCommand.php
Normal file
17
src/EventLog/Action/Command/AddEventLogCommand.php
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\EventLog\Action\Command;
|
||||||
|
|
||||||
|
use OneToMany\RichBundle\Contract\CommandInterface;
|
||||||
|
use Symfony\Component\Security\Core\User\UserInterface;
|
||||||
|
|
||||||
|
/** @implements CommandInterface<AddEventLogCommand> */
|
||||||
|
class AddEventLogCommand implements CommandInterface
|
||||||
|
{
|
||||||
|
public function __construct(
|
||||||
|
public UserInterface $user,
|
||||||
|
public string $type,
|
||||||
|
public string $message,
|
||||||
|
public array $context,
|
||||||
|
) {}
|
||||||
|
}
|
||||||
29
src/EventLog/Action/Handler/AddEventLogHandler.php
Normal file
29
src/EventLog/Action/Handler/AddEventLogHandler.php
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\EventLog\Action\Handler;
|
||||||
|
|
||||||
|
use App\EventLog\Framework\Repository\EventLogRepository;
|
||||||
|
use App\EventLog\Action\Command\AddEventLogCommand;
|
||||||
|
use App\EventLog\Action\Result\AddEventLogResult;
|
||||||
|
use OneToMany\RichBundle\Contract\CommandInterface;
|
||||||
|
use OneToMany\RichBundle\Contract\HandlerInterface;
|
||||||
|
use OneToMany\RichBundle\Contract\ResultInterface;
|
||||||
|
|
||||||
|
/*** @implements HandlerInterface<AddEventLogCommand> */
|
||||||
|
class AddEventLogHandler implements HandlerInterface
|
||||||
|
{
|
||||||
|
public function __construct(
|
||||||
|
private EventLogRepository $repository,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
public function handle(CommandInterface $command): ResultInterface
|
||||||
|
{
|
||||||
|
$eventLog = $this->repository->insert(
|
||||||
|
user: $command->user,
|
||||||
|
type: $command->type,
|
||||||
|
message: $command->message,
|
||||||
|
context: $command->context
|
||||||
|
);
|
||||||
|
return new AddEventLogResult($eventLog);
|
||||||
|
}
|
||||||
|
}
|
||||||
36
src/EventLog/Action/Input/AddEventLogInput.php
Normal file
36
src/EventLog/Action/Input/AddEventLogInput.php
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\EventLog\Action\Input;
|
||||||
|
|
||||||
|
use App\EventLog\Action\Command\AddEventLogCommand;
|
||||||
|
use OneToMany\RichBundle\Attribute\SourceQuery;
|
||||||
|
use OneToMany\RichBundle\Attribute\SourceRequest;
|
||||||
|
use OneToMany\RichBundle\Contract\CommandInterface;
|
||||||
|
use OneToMany\RichBundle\Contract\InputInterface;
|
||||||
|
|
||||||
|
/** @implements InputInterface<AddEventLogCommand> */
|
||||||
|
class AddEventLogInput implements InputInterface
|
||||||
|
{
|
||||||
|
public function __construct(
|
||||||
|
#[SourceQuery('type')]
|
||||||
|
#[SourceRequest('type')]
|
||||||
|
public string $type,
|
||||||
|
|
||||||
|
#[SourceQuery('message')]
|
||||||
|
#[SourceRequest('message')]
|
||||||
|
public string $message,
|
||||||
|
|
||||||
|
#[SourceQuery('context')]
|
||||||
|
#[SourceRequest('context')]
|
||||||
|
public array $context = []
|
||||||
|
){}
|
||||||
|
|
||||||
|
public function toCommand(): CommandInterface
|
||||||
|
{
|
||||||
|
return new AddEventLogCommand(
|
||||||
|
$this->type,
|
||||||
|
$this->message,
|
||||||
|
$this->context
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
14
src/EventLog/Action/Result/AddEventLogResult.php
Normal file
14
src/EventLog/Action/Result/AddEventLogResult.php
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\EventLog\Action\Result;
|
||||||
|
|
||||||
|
use App\EventLog\Framework\Entity\EventLog;
|
||||||
|
use OneToMany\RichBundle\Contract\ResultInterface;
|
||||||
|
|
||||||
|
/** @implements ResultInterface<AddEventLogResult> */
|
||||||
|
class AddEventLogResult implements ResultInterface
|
||||||
|
{
|
||||||
|
public function __construct(
|
||||||
|
public EventLog $eventLog,
|
||||||
|
) {}
|
||||||
|
}
|
||||||
28
src/EventLog/Framework/Controller/EventLogController.php
Normal file
28
src/EventLog/Framework/Controller/EventLogController.php
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\EventLog\Framework\Controller;
|
||||||
|
|
||||||
|
use App\Base\Service\Broadcaster;
|
||||||
|
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
||||||
|
use Symfony\Component\HttpFoundation\Response;
|
||||||
|
use Symfony\Component\Routing\Attribute\Route;
|
||||||
|
|
||||||
|
final class EventLogController extends AbstractController
|
||||||
|
{
|
||||||
|
public function __construct(
|
||||||
|
private readonly Broadcaster $broadcaster,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
#[Route('/alert', name: 'app_alert')]
|
||||||
|
public function index(): Response
|
||||||
|
{
|
||||||
|
$this->broadcaster->alert(
|
||||||
|
'Added to queue',
|
||||||
|
'This is a testy test!'
|
||||||
|
);
|
||||||
|
|
||||||
|
return $this->json([
|
||||||
|
'Success' => 'Published'
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
82
src/EventLog/Framework/Entity/EventLog.php
Normal file
82
src/EventLog/Framework/Entity/EventLog.php
Normal file
@@ -0,0 +1,82 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\EventLog\Framework\Entity;
|
||||||
|
|
||||||
|
use App\EventLog\Framework\Repository\EventLogRepository;
|
||||||
|
use App\User\Framework\Entity\User;
|
||||||
|
use Doctrine\DBAL\Types\Types;
|
||||||
|
use Doctrine\ORM\Mapping as ORM;
|
||||||
|
use Gedmo\Timestampable\Traits\TimestampableEntity;
|
||||||
|
|
||||||
|
#[ORM\Entity(repositoryClass: EventLogRepository::class)]
|
||||||
|
class EventLog
|
||||||
|
{
|
||||||
|
use TimestampableEntity;
|
||||||
|
|
||||||
|
#[ORM\Id]
|
||||||
|
#[ORM\GeneratedValue]
|
||||||
|
#[ORM\Column(nullable: true)]
|
||||||
|
private ?int $id = null;
|
||||||
|
|
||||||
|
#[ORM\Column(length: 255, nullable: true)]
|
||||||
|
private ?string $type = null;
|
||||||
|
|
||||||
|
#[ORM\Column(type: Types::TEXT, nullable: true)]
|
||||||
|
private ?string $message = null;
|
||||||
|
|
||||||
|
#[ORM\Column(type: Types::JSON, nullable: true)]
|
||||||
|
private ?array $context = null;
|
||||||
|
|
||||||
|
#[ORM\ManyToOne(inversedBy: 'eventLogs')]
|
||||||
|
private ?User $user = null;
|
||||||
|
|
||||||
|
public function getId(): ?int
|
||||||
|
{
|
||||||
|
return $this->id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getType(): ?string
|
||||||
|
{
|
||||||
|
return $this->type;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setType(?string $type): self
|
||||||
|
{
|
||||||
|
$this->type = $type;
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getMessage(): ?string
|
||||||
|
{
|
||||||
|
return $this->message;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setMessage(?string $message): self
|
||||||
|
{
|
||||||
|
$this->message = $message;
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getContext(): ?array
|
||||||
|
{
|
||||||
|
return $this->context;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setContext(?array $context): self
|
||||||
|
{
|
||||||
|
$this->context = $context;
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getUser(): ?User
|
||||||
|
{
|
||||||
|
return $this->user;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setUser(?User $user): static
|
||||||
|
{
|
||||||
|
$this->user = $user;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
}
|
||||||
35
src/EventLog/Framework/Repository/EventLogRepository.php
Normal file
35
src/EventLog/Framework/Repository/EventLogRepository.php
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\EventLog\Framework\Repository;
|
||||||
|
|
||||||
|
use App\EventLog\Framework\Entity\EventLog;
|
||||||
|
use App\User\Framework\Entity\User;
|
||||||
|
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
|
||||||
|
use Doctrine\Persistence\ManagerRegistry;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @extends ServiceEntityRepository<EventLog>
|
||||||
|
*/
|
||||||
|
class EventLogRepository extends ServiceEntityRepository
|
||||||
|
{
|
||||||
|
public function __construct(ManagerRegistry $registry)
|
||||||
|
{
|
||||||
|
parent::__construct($registry, EventLog::class);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function insert(
|
||||||
|
User $user,
|
||||||
|
string $type,
|
||||||
|
string $message,
|
||||||
|
array $context = []
|
||||||
|
): EventLog {
|
||||||
|
$eventLog = new EventLog()
|
||||||
|
->setUser($user)
|
||||||
|
->setType($type)
|
||||||
|
->setMessage($message)
|
||||||
|
->setContext($context);
|
||||||
|
$this->getEntityManager()->persist($eventLog);
|
||||||
|
$this->getEntityManager()->flush();
|
||||||
|
return $eventLog;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -2,20 +2,24 @@
|
|||||||
|
|
||||||
namespace App\Monitor\Action\Handler;
|
namespace App\Monitor\Action\Handler;
|
||||||
|
|
||||||
|
use App\EventLog\Action\Command\AddEventLogCommand;
|
||||||
use App\Monitor\Action\Command\AddMonitorCommand;
|
use App\Monitor\Action\Command\AddMonitorCommand;
|
||||||
use App\Monitor\Action\Result\AddMonitorResult;
|
use App\Monitor\Action\Result\AddMonitorResult;
|
||||||
use App\Monitor\Framework\Entity\Monitor;
|
use App\Monitor\Framework\Entity\Monitor;
|
||||||
use App\Monitor\Framework\Repository\MonitorRepository;
|
use App\Monitor\Framework\Repository\MonitorRepository;
|
||||||
|
use App\Monitor\MonitorEvents;
|
||||||
use App\User\Framework\Repository\UserRepository;
|
use App\User\Framework\Repository\UserRepository;
|
||||||
use DateTimeImmutable;
|
use DateTimeImmutable;
|
||||||
use OneToMany\RichBundle\Contract\CommandInterface;
|
use OneToMany\RichBundle\Contract\CommandInterface;
|
||||||
use OneToMany\RichBundle\Contract\HandlerInterface;
|
use OneToMany\RichBundle\Contract\HandlerInterface;
|
||||||
use OneToMany\RichBundle\Contract\ResultInterface;
|
use OneToMany\RichBundle\Contract\ResultInterface;
|
||||||
|
use Symfony\Component\Messenger\MessageBusInterface;
|
||||||
|
|
||||||
/** @implements HandlerInterface<AddMonitorCommand> */
|
/** @implements HandlerInterface<AddMonitorCommand> */
|
||||||
readonly class AddMonitorHandler implements HandlerInterface
|
readonly class AddMonitorHandler implements HandlerInterface
|
||||||
{
|
{
|
||||||
public function __construct(
|
public function __construct(
|
||||||
|
private MessageBusInterface $bus,
|
||||||
private MonitorRepository $movieMonitorRepository,
|
private MonitorRepository $movieMonitorRepository,
|
||||||
private UserRepository $userRepository,
|
private UserRepository $userRepository,
|
||||||
) {}
|
) {}
|
||||||
@@ -35,6 +39,13 @@ readonly class AddMonitorHandler implements HandlerInterface
|
|||||||
->setSearchCount(0)
|
->setSearchCount(0)
|
||||||
->setStatus('New');
|
->setStatus('New');
|
||||||
|
|
||||||
|
$this->bus->dispatch(new AddEventLogCommand(
|
||||||
|
$user,
|
||||||
|
MonitorEvents::MONITOR_ADDED->type(),
|
||||||
|
MonitorEvents::MONITOR_ADDED->message(),
|
||||||
|
(array) $monitor
|
||||||
|
));
|
||||||
|
|
||||||
$this->movieMonitorRepository->getEntityManager()->persist($monitor);
|
$this->movieMonitorRepository->getEntityManager()->persist($monitor);
|
||||||
$this->movieMonitorRepository->getEntityManager()->flush();
|
$this->movieMonitorRepository->getEntityManager()->flush();
|
||||||
|
|
||||||
|
|||||||
@@ -2,22 +2,22 @@
|
|||||||
|
|
||||||
namespace App\Monitor\Action\Handler;
|
namespace App\Monitor\Action\Handler;
|
||||||
|
|
||||||
use App\Monitor\Action\Command\AddMonitorCommand;
|
use App\Download\DownloadEvents;
|
||||||
|
use App\EventLog\Action\Command\AddEventLogCommand;
|
||||||
use App\Monitor\Action\Command\DeleteMonitorCommand;
|
use App\Monitor\Action\Command\DeleteMonitorCommand;
|
||||||
use App\Monitor\Action\Result\AddMonitorResult;
|
|
||||||
use App\Monitor\Action\Result\DeleteMonitorResult;
|
use App\Monitor\Action\Result\DeleteMonitorResult;
|
||||||
use App\Monitor\Framework\Entity\Monitor;
|
|
||||||
use App\Monitor\Framework\Repository\MonitorRepository;
|
use App\Monitor\Framework\Repository\MonitorRepository;
|
||||||
use App\User\Framework\Repository\UserRepository;
|
use App\Monitor\MonitorEvents;
|
||||||
use DateTimeImmutable;
|
|
||||||
use OneToMany\RichBundle\Contract\CommandInterface;
|
use OneToMany\RichBundle\Contract\CommandInterface;
|
||||||
use OneToMany\RichBundle\Contract\HandlerInterface;
|
use OneToMany\RichBundle\Contract\HandlerInterface;
|
||||||
use OneToMany\RichBundle\Contract\ResultInterface;
|
use OneToMany\RichBundle\Contract\ResultInterface;
|
||||||
|
use Symfony\Component\Messenger\MessageBusInterface;
|
||||||
|
|
||||||
/** @implements HandlerInterface<DeleteMonitorCommand, DeleteMonitorResult> */
|
/** @implements HandlerInterface<DeleteMonitorCommand, DeleteMonitorResult> */
|
||||||
readonly class DeleteMonitorHandler implements HandlerInterface
|
readonly class DeleteMonitorHandler implements HandlerInterface
|
||||||
{
|
{
|
||||||
public function __construct(
|
public function __construct(
|
||||||
|
private MessageBusInterface $bus,
|
||||||
private MonitorRepository $monitorRepository,
|
private MonitorRepository $monitorRepository,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
@@ -27,6 +27,13 @@ readonly class DeleteMonitorHandler implements HandlerInterface
|
|||||||
$this->monitorRepository->getEntityManager()->remove($monitor);
|
$this->monitorRepository->getEntityManager()->remove($monitor);
|
||||||
$this->monitorRepository->getEntityManager()->flush();
|
$this->monitorRepository->getEntityManager()->flush();
|
||||||
|
|
||||||
|
$this->bus->dispatch(new AddEventLogCommand(
|
||||||
|
$monitor->getUser(),
|
||||||
|
MonitorEvents::MONITOR_DELETED->type(),
|
||||||
|
MonitorEvents::MONITOR_DELETED->message(),
|
||||||
|
(array) $monitor
|
||||||
|
));
|
||||||
|
|
||||||
return new DeleteMonitorResult(
|
return new DeleteMonitorResult(
|
||||||
status: 'OK',
|
status: 'OK',
|
||||||
result: [],
|
result: [],
|
||||||
|
|||||||
@@ -6,9 +6,11 @@ use App\Base\Util\EpisodeId;
|
|||||||
use App\Download\Action\Command\DownloadMediaCommand;
|
use App\Download\Action\Command\DownloadMediaCommand;
|
||||||
use App\Download\DownloadOptionEvaluator;
|
use App\Download\DownloadOptionEvaluator;
|
||||||
use App\Download\Framework\Repository\DownloadRepository;
|
use App\Download\Framework\Repository\DownloadRepository;
|
||||||
|
use App\EventLog\Action\Command\AddEventLogCommand;
|
||||||
use App\Monitor\Action\Command\MonitorMovieCommand;
|
use App\Monitor\Action\Command\MonitorMovieCommand;
|
||||||
use App\Monitor\Action\Result\MonitorTvEpisodeResult;
|
use App\Monitor\Action\Result\MonitorTvEpisodeResult;
|
||||||
use App\Monitor\Framework\Repository\MonitorRepository;
|
use App\Monitor\Framework\Repository\MonitorRepository;
|
||||||
|
use App\Monitor\MonitorEvents;
|
||||||
use App\Tmdb\TmdbClient;
|
use App\Tmdb\TmdbClient;
|
||||||
use App\Torrentio\Action\Command\GetTvShowOptionsCommand;
|
use App\Torrentio\Action\Command\GetTvShowOptionsCommand;
|
||||||
use App\Torrentio\Action\Handler\GetTvShowOptionsHandler;
|
use App\Torrentio\Action\Handler\GetTvShowOptionsHandler;
|
||||||
@@ -42,9 +44,16 @@ readonly class MonitorTvEpisodeHandler implements HandlerInterface
|
|||||||
$monitor = $this->monitorRepository->find($command->movieMonitorId);
|
$monitor = $this->monitorRepository->find($command->movieMonitorId);
|
||||||
$this->logger->info('> [MonitorTvEpisodeHandler] Executing MonitorTvEpisodeHandler for ' . $monitor->getTitle() . ' season ' . $monitor->getSeason() . ' episode ' . $monitor->getEpisode());
|
$this->logger->info('> [MonitorTvEpisodeHandler] Executing MonitorTvEpisodeHandler for ' . $monitor->getTitle() . ' season ' . $monitor->getSeason() . ' episode ' . $monitor->getEpisode());
|
||||||
|
|
||||||
|
$this->bus->dispatch(new AddEventLogCommand(
|
||||||
|
$monitor->getUser(),
|
||||||
|
MonitorEvents::MONITOR_STARTED->type(),
|
||||||
|
MonitorEvents::MONITOR_STARTED->message(),
|
||||||
|
(array) $command
|
||||||
|
));
|
||||||
|
|
||||||
$episodeData = $this->tmdb->tvEpisodeDetails($monitor->getTmdbId(), $monitor->getImdbId(), $monitor->getSeason(), $monitor->getEpisode());
|
$episodeData = $this->tmdb->tvEpisodeDetails($monitor->getTmdbId(), $monitor->getImdbId(), $monitor->getSeason(), $monitor->getEpisode());
|
||||||
|
|
||||||
if (null === $episodeData->episodeAirDate || "" !== $episodeData->episodeAirDate) {
|
if (null === $episodeData->airDate || "" === $episodeData->airDate) {
|
||||||
$this->logger->info('> [MonitorTvEpisodeHandler] ...Episode does not have an air date, skipping for now');
|
$this->logger->info('> [MonitorTvEpisodeHandler] ...Episode does not have an air date, skipping for now');
|
||||||
return new MonitorTvEpisodeResult(
|
return new MonitorTvEpisodeResult(
|
||||||
status: 'OK',
|
status: 'OK',
|
||||||
@@ -56,10 +65,10 @@ readonly class MonitorTvEpisodeHandler implements HandlerInterface
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (null === $monitor->getAirDate()) {
|
if (null === $monitor->getAirDate()) {
|
||||||
$monitor->setAirDate(Carbon::parse($episodeData->episodeAirDate));
|
$monitor->setAirDate(Carbon::parse($episodeData->airDate));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (Carbon::createFromTimestamp($episodeData->episodeAirDate) > Carbon::today('UTC')) {
|
if (Carbon::createFromTimestamp($episodeData->airDate) > Carbon::today('UTC')) {
|
||||||
$this->logger->info('> [MonitorTvEpisodeHandler] ...Episode has not aired yet, skipping for now');
|
$this->logger->info('> [MonitorTvEpisodeHandler] ...Episode has not aired yet, skipping for now');
|
||||||
return new MonitorTvEpisodeResult(
|
return new MonitorTvEpisodeResult(
|
||||||
status: 'OK',
|
status: 'OK',
|
||||||
@@ -116,12 +125,25 @@ readonly class MonitorTvEpisodeHandler implements HandlerInterface
|
|||||||
$this->logger->error('> [MonitorTvEpisodeHandler] ...Exception thrown: ' . $exception->getMessage());
|
$this->logger->error('> [MonitorTvEpisodeHandler] ...Exception thrown: ' . $exception->getMessage());
|
||||||
$this->logger->error($exception->getMessage());
|
$this->logger->error($exception->getMessage());
|
||||||
$monitor->setStatus('Active');
|
$monitor->setStatus('Active');
|
||||||
|
$this->bus->dispatch(new AddEventLogCommand(
|
||||||
|
$monitor->getUser(),
|
||||||
|
MonitorEvents::MONITOR_ERROR->type(),
|
||||||
|
MonitorEvents::MONITOR_ERROR->message() . ': ' . $exception->getMessage(),
|
||||||
|
(array) $monitor
|
||||||
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
$monitor->setLastSearch(new DateTimeImmutable());
|
$monitor->setLastSearch(new DateTimeImmutable());
|
||||||
$monitor->setSearchCount($monitor->getSearchCount() + 1);
|
$monitor->setSearchCount($monitor->getSearchCount() + 1);
|
||||||
$this->monitorRepository->getEntityManager()->flush();
|
$this->monitorRepository->getEntityManager()->flush();
|
||||||
|
|
||||||
|
$this->bus->dispatch(new AddEventLogCommand(
|
||||||
|
$monitor->getUser(),
|
||||||
|
MonitorEvents::MONITOR_FINISHED->type(),
|
||||||
|
MonitorEvents::MONITOR_FINISHED->message(),
|
||||||
|
(array) $monitor
|
||||||
|
));
|
||||||
|
|
||||||
return new MonitorTvEpisodeResult(
|
return new MonitorTvEpisodeResult(
|
||||||
status: 'OK',
|
status: 'OK',
|
||||||
result: [
|
result: [
|
||||||
|
|||||||
@@ -30,7 +30,8 @@ readonly class MonitorTvShowHandler implements HandlerInterface
|
|||||||
private MediaFiles $mediaFiles,
|
private MediaFiles $mediaFiles,
|
||||||
private LoggerInterface $logger,
|
private LoggerInterface $logger,
|
||||||
private TmdbClient $tmdb,
|
private TmdbClient $tmdb,
|
||||||
) {}
|
) {
|
||||||
|
}
|
||||||
|
|
||||||
public function handle(CommandInterface $command): ResultInterface
|
public function handle(CommandInterface $command): ResultInterface
|
||||||
{
|
{
|
||||||
@@ -40,79 +41,77 @@ readonly class MonitorTvShowHandler implements HandlerInterface
|
|||||||
// Check current episodes
|
// Check current episodes
|
||||||
$downloadedEpisodes = $this->mediaFiles
|
$downloadedEpisodes = $this->mediaFiles
|
||||||
->getEpisodes($monitor->getTitle())
|
->getEpisodes($monitor->getTitle())
|
||||||
->map(fn($episode) => (object) (new PTN())->parse($episode))
|
->map(fn($episode) => (object)(new PTN())->parse($episode))
|
||||||
->filter(fn ($episode) =>
|
->filter(fn($episode) => property_exists($episode, 'episode')
|
||||||
property_exists($episode, 'episode')
|
|
||||||
&& property_exists($episode, 'season')
|
&& property_exists($episode, 'season')
|
||||||
&& null !== $episode->episode
|
&& null !== $episode->episode
|
||||||
&& null !== $episode->season
|
&& null !== $episode->season
|
||||||
)
|
);
|
||||||
;
|
|
||||||
|
|
||||||
$this->logger->info('> [MonitorTvShowHandler] Found ' . count($downloadedEpisodes) . ' downloaded episodes for title: ' . $monitor->getTitle());
|
$this->logger->info('> [MonitorTvShowHandler] Found ' . count($downloadedEpisodes) . ' downloaded episodes for title: ' . $monitor->getTitle());
|
||||||
|
|
||||||
// Compare against list from TMDB
|
// Compare against list from TMDB
|
||||||
$episodesInShow = Map::from(
|
$episodesInShow = Map::from(
|
||||||
$this->tmdb->tvshowDetails($monitor->getImdbId())->episodes
|
$this->tmdb->tvshowDetails($monitor->getImdbId())->episodes
|
||||||
)->flat(1);
|
)->flat(1)
|
||||||
|
->filter(fn(TmdbEpisodeDto $episode) => $episode->seasonNumber >= $monitor->getSeason())
|
||||||
|
->values();
|
||||||
|
|
||||||
$this->logger->info('> [MonitorTvShowHandler] Found ' . count($episodesInShow) . ' episodes for title: ' . $monitor->getTitle());
|
$this->logger->info('> [MonitorTvShowHandler] Found ' . count($episodesInShow) . ' episodes for title: ' . $monitor->getTitle());
|
||||||
|
|
||||||
$episodeMonitors = [];
|
$episodeMonitors = [];
|
||||||
if ($downloadedEpisodes->count() !== $episodesInShow->count()) {
|
// Dispatch Episode commands for each missing Episode
|
||||||
// Dispatch Episode commands for each missing Episode
|
foreach ($episodesInShow as $episode) {
|
||||||
foreach ($episodesInShow as $episode) {
|
/** @var TmdbEpisodeDto $episode */
|
||||||
/** @var TmdbEpisodeDto $episode */
|
// Only monitor future episodes
|
||||||
// Only monitor future episodes
|
$this->logger->info('> [MonitorTvShowHandler] Evaluating "' . $monitor->getTitle() . '", season "' . $episode->seasonNumber . '" episode "' . $episode->episodeNumber . '"');
|
||||||
$this->logger->info('> [MonitorTvShowHandler] Evaluating "' . $monitor->getTitle() . '", season "' . $episode->seasonNumber . '" episode "' . $episode->episodeNumber . '"');
|
$episodeInFuture = $this->episodeReleasedAfterMonitorCreated($monitor->getCreatedAt(), $episode);
|
||||||
$episodeInFuture = $this->episodeReleasedAfterMonitorCreated($monitor->getCreatedAt(), $episode);
|
$this->logger->info('> [MonitorTvShowHandler] ...Released after monitor started? ' . (true === $episodeInFuture ? 'YES' : 'NO'));
|
||||||
$this->logger->info('> [MonitorTvShowHandler] ...Released after monitor started? ' . (true === $episodeInFuture ? 'YES' : 'NO'));
|
if (false === $episodeInFuture) {
|
||||||
if (false === $episodeInFuture) {
|
$this->logger->info('> [MonitorTvShowHandler] ...Skipping');
|
||||||
$this->logger->info('> [MonitorTvShowHandler] ...Skipping');
|
continue;
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check if the episode is already downloaded
|
|
||||||
$episodeExists = $this->episodeExists($episode, $downloadedEpisodes);
|
|
||||||
$this->logger->info('> [MonitorTvShowHandler] ...Episode exists? ' . (true === $episodeExists ? 'YES' : 'NO'));
|
|
||||||
if (true === $episodeExists) {
|
|
||||||
$this->logger->info('> [MonitorTvShowHandler] ...Skipping');
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check for existing monitors
|
|
||||||
$monitorExists = $this->monitorExists($monitor, $episode);
|
|
||||||
$this->logger->info('> [MonitorTvShowHandler] ...Monitor exists? ' . (true === $monitorExists ? 'YES' : 'NO'));
|
|
||||||
if (true === $monitorExists) {
|
|
||||||
$this->logger->info('> [MonitorTvShowHandler] ...Skipping');
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create the monitor
|
|
||||||
$episodeMonitor = (new Monitor())
|
|
||||||
->setParent($monitor)
|
|
||||||
->setUser($monitor->getUser())
|
|
||||||
->setTmdbId($monitor->getTmdbId())
|
|
||||||
->setImdbId($monitor->getImdbId())
|
|
||||||
->setTitle($monitor->getTitle())
|
|
||||||
->setMonitorType('tvepisode')
|
|
||||||
->setSeason($episode->seasonNumber)
|
|
||||||
->setEpisode($episode->episodeNumber)
|
|
||||||
->setAirDate($episode->airDate !== null && $episode->airDate !== "" ? Carbon::parse($episode->airDate) : null)
|
|
||||||
->setCreatedAt(new DateTimeImmutable())
|
|
||||||
->setSearchCount(0)
|
|
||||||
->setStatus('New');
|
|
||||||
|
|
||||||
$this->monitorRepository->getEntityManager()->persist($episodeMonitor);
|
|
||||||
$this->monitorRepository->getEntityManager()->flush();
|
|
||||||
|
|
||||||
$episodeMonitors[] = $episodeMonitor;
|
|
||||||
|
|
||||||
// Immediately run the monitor
|
|
||||||
$command = new MonitorTvEpisodeCommand($episodeMonitor->getId());
|
|
||||||
$this->monitorTvEpisodeHandler->handle($command);
|
|
||||||
$this->logger->info('> [MonitorTvShowHandler] ...Dispatching MonitorTvEpisodeCommand');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Check if the episode is already downloaded
|
||||||
|
$episodeExists = $this->episodeExists($episode, $downloadedEpisodes);
|
||||||
|
$this->logger->info('> [MonitorTvShowHandler] ...Episode exists? ' . (true === $episodeExists ? 'YES' : 'NO'));
|
||||||
|
if (true === $episodeExists) {
|
||||||
|
$this->logger->info('> [MonitorTvShowHandler] ...Skipping');
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check for existing monitors
|
||||||
|
$monitorExists = $this->monitorExists($monitor, $episode);
|
||||||
|
$this->logger->info('> [MonitorTvShowHandler] ...Monitor exists? ' . (true === $monitorExists ? 'YES' : 'NO'));
|
||||||
|
if (true === $monitorExists) {
|
||||||
|
$this->logger->info('> [MonitorTvShowHandler] ...Skipping');
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create the monitor
|
||||||
|
$episodeMonitor = (new Monitor())
|
||||||
|
->setParent($monitor)
|
||||||
|
->setUser($monitor->getUser())
|
||||||
|
->setTmdbId($monitor->getTmdbId())
|
||||||
|
->setImdbId($monitor->getImdbId())
|
||||||
|
->setTitle($monitor->getTitle())
|
||||||
|
->setMonitorType('tvepisode')
|
||||||
|
->setSeason($episode->seasonNumber)
|
||||||
|
->setEpisode($episode->episodeNumber)
|
||||||
|
->setAirDate($episode->airDate !== null && $episode->airDate !== "" ? Carbon::parse($episode->airDate) : null)
|
||||||
|
->setCreatedAt(new DateTimeImmutable())
|
||||||
|
->setSearchCount(0)
|
||||||
|
->setStatus('New');
|
||||||
|
|
||||||
|
$this->monitorRepository->getEntityManager()->persist($episodeMonitor);
|
||||||
|
$this->monitorRepository->getEntityManager()->flush();
|
||||||
|
|
||||||
|
$episodeMonitors[] = $episodeMonitor;
|
||||||
|
|
||||||
|
// Immediately run the monitor
|
||||||
|
$command = new MonitorTvEpisodeCommand($episodeMonitor->getId());
|
||||||
|
$this->monitorTvEpisodeHandler->handle($command);
|
||||||
|
$this->logger->info('> [MonitorTvShowHandler] ...Dispatching MonitorTvEpisodeCommand');
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set the status to Active, so it will be re-executed.
|
// Set the status to Active, so it will be re-executed.
|
||||||
@@ -130,8 +129,10 @@ readonly class MonitorTvShowHandler implements HandlerInterface
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
private function episodeReleasedAfterMonitorCreated(string|DateTimeImmutable $monitorStartDate, TmdbEpisodeDto $episodeInShow): bool
|
private function episodeReleasedAfterMonitorCreated(
|
||||||
{
|
string|DateTimeImmutable $monitorStartDate,
|
||||||
|
TmdbEpisodeDto $episodeInShow
|
||||||
|
): bool {
|
||||||
$monitorStartDate = Carbon::parse($monitorStartDate)->setTime(0, 0);
|
$monitorStartDate = Carbon::parse($monitorStartDate)->setTime(0, 0);
|
||||||
$episodeAirDate = Carbon::parse($episodeInShow->airDate);
|
$episodeAirDate = Carbon::parse($episodeInShow->airDate);
|
||||||
return $episodeAirDate >= $monitorStartDate;
|
return $episodeAirDate >= $monitorStartDate;
|
||||||
@@ -140,8 +141,8 @@ readonly class MonitorTvShowHandler implements HandlerInterface
|
|||||||
private function episodeExists(TmdbEpisodeDto $episodeInShow, Map $downloadedEpisodes): bool
|
private function episodeExists(TmdbEpisodeDto $episodeInShow, Map $downloadedEpisodes): bool
|
||||||
{
|
{
|
||||||
return $downloadedEpisodes->filter(
|
return $downloadedEpisodes->filter(
|
||||||
fn (object $episode) => $episode->episode === $episodeInShow->episodeNumber
|
fn(object $episode) => $episode->episode === $episodeInShow->episodeNumber
|
||||||
&& $episode->season === $episodeInShow->seasonNumber
|
&& $episode->season === $episodeInShow->seasonNumber
|
||||||
)->count() > 0;
|
)->count() > 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
34
src/Monitor/MonitorEvents.php
Normal file
34
src/Monitor/MonitorEvents.php
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Monitor;
|
||||||
|
|
||||||
|
enum MonitorEvents
|
||||||
|
{
|
||||||
|
case MONITOR_ADDED;
|
||||||
|
case MONITOR_STARTED;
|
||||||
|
case MONITOR_FINISHED;
|
||||||
|
case MONITOR_DELETED;
|
||||||
|
case MONITOR_ERROR;
|
||||||
|
|
||||||
|
public function type(): string
|
||||||
|
{
|
||||||
|
return match ($this) {
|
||||||
|
self::MONITOR_ADDED => 'monitor_added',
|
||||||
|
self::MONITOR_STARTED => 'monitor_started',
|
||||||
|
self::MONITOR_FINISHED => 'monitor_finished',
|
||||||
|
self::MONITOR_DELETED => 'monitor_deleted',
|
||||||
|
self::MONITOR_ERROR => 'monitor_error',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public function message(): string
|
||||||
|
{
|
||||||
|
return match ($this) {
|
||||||
|
self::MONITOR_ADDED => 'A new monitor has been added.',
|
||||||
|
self::MONITOR_STARTED => 'A monitor has started.',
|
||||||
|
self::MONITOR_FINISHED => 'A monitor has finished.',
|
||||||
|
self::MONITOR_DELETED => 'A monitor has been deleted',
|
||||||
|
self::MONITOR_ERROR => 'A monitor has encountered an error.',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -38,6 +38,10 @@ class TmdbTvShowResultDenormalizer extends TmdbResultDenormalizer implements Den
|
|||||||
$result->year = (null !== $airDate) ? $airDate->format('Y') : null;
|
$result->year = (null !== $airDate) ? $airDate->format('Y') : null;
|
||||||
$result->mediaType = MediaType::TvShow->value;
|
$result->mediaType = MediaType::TvShow->value;
|
||||||
|
|
||||||
|
if (is_array($result->episodes)) {
|
||||||
|
$result->latestSeason = array_key_last($result->episodes);
|
||||||
|
}
|
||||||
|
|
||||||
return $result;
|
return $result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -150,6 +150,8 @@ class TmdbClient
|
|||||||
$data['episode_count'] > 0;
|
$data['episode_count'] > 0;
|
||||||
})->map(function ($data) use ($media) {
|
})->map(function ($data) use ($media) {
|
||||||
return $this->tvSeasonDetails($media['id'], $data['season_number'])['episodes'];
|
return $this->tvSeasonDetails($media['id'], $data['season_number'])['episodes'];
|
||||||
|
})->rekey(function ($data) use ($media) {
|
||||||
|
return $data[1]['season_number'];
|
||||||
})->toArray();
|
})->toArray();
|
||||||
|
|
||||||
return $this->parseResult(
|
return $this->parseResult(
|
||||||
|
|||||||
@@ -56,5 +56,6 @@ class TmdbResult
|
|||||||
public ?array $producers = null,
|
public ?array $producers = null,
|
||||||
public ?int $runtime = null,
|
public ?int $runtime = null,
|
||||||
public ?int $numberSeasons = null,
|
public ?int $numberSeasons = null,
|
||||||
|
public ?int $latestSeason = null,
|
||||||
) {}
|
) {}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,29 +21,32 @@ class ResultFactory
|
|||||||
string $bingeGroup = "-",
|
string $bingeGroup = "-",
|
||||||
string $imdbId = "-",
|
string $imdbId = "-",
|
||||||
) {
|
) {
|
||||||
$ptn = (object) (new PTN())->parse($title);
|
$title = trim(preg_replace('/\s+/', ' ', $title));
|
||||||
return new TorrentioResult(
|
$ptn = (object) new PTN()->parse(self::setFilename($url));
|
||||||
|
$result = new TorrentioResult(
|
||||||
self::trimTitle($title),
|
self::trimTitle($title),
|
||||||
urldecode($url),
|
self::setUrl($url),
|
||||||
self::setFilename($url),
|
self::setFilename($url),
|
||||||
self::setSize($title),
|
self::setSize($title),
|
||||||
self::setSeeders($title),
|
self::setSeeders($title),
|
||||||
self::setProvider($title),
|
self::setProvider($title),
|
||||||
self::setEpisode($title),
|
self::setEpisode($title),
|
||||||
$ptn->season ?? "-",
|
self::setSeason($ptn),
|
||||||
$bingeGroup,
|
$bingeGroup,
|
||||||
$ptn->resolution ?? "-",
|
self::setResolution($ptn),
|
||||||
self::setCodec($ptn->codec ?? "-"),
|
self::setCodec($ptn),
|
||||||
$ptn->quality ?? "-",
|
self::setQuality($ptn),
|
||||||
$ptn,
|
$ptn,
|
||||||
substr(base64_encode($url), strlen($url) - 10),
|
self::setKey($url),
|
||||||
$ptn->episode ?? "-",
|
self::setEpisodeNumber($ptn),
|
||||||
self::setLanguages($title),
|
self::setLanguages($title),
|
||||||
self::setLanguageFlags($title),
|
self::setLanguageFlags($title),
|
||||||
false,
|
false,
|
||||||
uniqid(),
|
uniqid(),
|
||||||
$imdbId,
|
$imdbId,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
return $result;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static function setFilename(string $url)
|
public static function setFilename(string $url)
|
||||||
@@ -52,6 +55,11 @@ class ResultFactory
|
|||||||
return end($file);
|
return end($file);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static function setUrl(string $url): string
|
||||||
|
{
|
||||||
|
return urldecode($url);
|
||||||
|
}
|
||||||
|
|
||||||
public static function setSize(string $title): string
|
public static function setSize(string $title): string
|
||||||
{
|
{
|
||||||
$sizeMatch = [];
|
$sizeMatch = [];
|
||||||
@@ -112,9 +120,15 @@ class ResultFactory
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static function setCodec(string $codec): string
|
public static function setCodec(object $ptn): string
|
||||||
{
|
{
|
||||||
return self::$codecMap[strtolower($codec)] ?? $codec;
|
if (isset($ptn->codec) && array_key_exists($ptn->codec, self::$codecMap)) {
|
||||||
|
return self::$codecMap[strtolower($ptn->codec)];
|
||||||
|
} elseif (isset($ptn->codec)) {
|
||||||
|
return $ptn->codec;
|
||||||
|
}
|
||||||
|
|
||||||
|
return "-";
|
||||||
}
|
}
|
||||||
|
|
||||||
private static function setEpisode(string $title)
|
private static function setEpisode(string $title)
|
||||||
@@ -124,6 +138,36 @@ class ResultFactory
|
|||||||
return array_key_exists(0, $value) ? strtoupper($value[0]) : "n/a";
|
return array_key_exists(0, $value) ? strtoupper($value[0]) : "n/a";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static function setSeason(object $ptn): string
|
||||||
|
{
|
||||||
|
return $ptn->season ?? "-";
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function setBingeGroup(string $bingeGroup): string
|
||||||
|
{
|
||||||
|
return $bingeGroup;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function setResolution(object $ptn): string
|
||||||
|
{
|
||||||
|
return $ptn->resolution ?? "-";
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function setQuality(object $ptn): string
|
||||||
|
{
|
||||||
|
return $ptn->quality ?? "-";
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function setKey(string $url): string
|
||||||
|
{
|
||||||
|
return substr(base64_encode($url), strlen($url) - 10);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function setEpisodeNumber(object $ptn): string
|
||||||
|
{
|
||||||
|
return $ptn->episode ?? "-";
|
||||||
|
}
|
||||||
|
|
||||||
private static function trimTitle(string $title)
|
private static function trimTitle(string $title)
|
||||||
{
|
{
|
||||||
$emoji = \Emoji\detect_emoji($title);
|
$emoji = \Emoji\detect_emoji($title);
|
||||||
|
|||||||
@@ -49,7 +49,6 @@ final class TvEpisodeList
|
|||||||
}
|
}
|
||||||
|
|
||||||
$this->reloadCount++;
|
$this->reloadCount++;
|
||||||
// dd(new TvEpisodePaginator()->paginate($results, $this->pageNumber, $this->perPage));
|
|
||||||
return new TvEpisodePaginator()->paginate($results, $this->pageNumber, $this->perPage);
|
return new TvEpisodePaginator()->paginate($results, $this->pageNumber, $this->perPage);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ namespace App\User\Framework\Entity;
|
|||||||
|
|
||||||
use Aimeos\Map;
|
use Aimeos\Map;
|
||||||
use App\Download\Framework\Entity\Download;
|
use App\Download\Framework\Entity\Download;
|
||||||
|
use App\EventLog\Framework\Entity\EventLog;
|
||||||
use App\Monitor\Framework\Entity\Monitor;
|
use App\Monitor\Framework\Entity\Monitor;
|
||||||
use App\User\Framework\Repository\UserRepository;
|
use App\User\Framework\Repository\UserRepository;
|
||||||
use Doctrine\Common\Collections\ArrayCollection;
|
use Doctrine\Common\Collections\ArrayCollection;
|
||||||
@@ -56,11 +57,18 @@ class User implements UserInterface, PasswordAuthenticatedUserInterface
|
|||||||
#[ORM\OneToMany(targetEntity: Download::class, mappedBy: 'user')]
|
#[ORM\OneToMany(targetEntity: Download::class, mappedBy: 'user')]
|
||||||
private Collection $downloads;
|
private Collection $downloads;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var Collection<int, EventLog>
|
||||||
|
*/
|
||||||
|
#[ORM\OneToMany(targetEntity: EventLog::class, mappedBy: 'user')]
|
||||||
|
private Collection $eventLogs;
|
||||||
|
|
||||||
public function __construct()
|
public function __construct()
|
||||||
{
|
{
|
||||||
$this->userPreferences = new ArrayCollection();
|
$this->userPreferences = new ArrayCollection();
|
||||||
$this->monitors = new ArrayCollection();
|
$this->monitors = new ArrayCollection();
|
||||||
$this->downloads = new ArrayCollection();
|
$this->downloads = new ArrayCollection();
|
||||||
|
$this->eventLogs = new ArrayCollection();
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getId(): ?int
|
public function getId(): ?int
|
||||||
@@ -342,4 +350,34 @@ class User implements UserInterface, PasswordAuthenticatedUserInterface
|
|||||||
return $this->hasUserPreference('enable_ical_up_ep') &&
|
return $this->hasUserPreference('enable_ical_up_ep') &&
|
||||||
(bool) $this->getUserPreference('enable_ical_up_ep')->getPreferenceValue() === true;
|
(bool) $this->getUserPreference('enable_ical_up_ep')->getPreferenceValue() === true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return Collection<int, EventLog>
|
||||||
|
*/
|
||||||
|
public function getEventLogs(): Collection
|
||||||
|
{
|
||||||
|
return $this->eventLogs;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function addEventLog(EventLog $eventLog): static
|
||||||
|
{
|
||||||
|
if (!$this->eventLogs->contains($eventLog)) {
|
||||||
|
$this->eventLogs->add($eventLog);
|
||||||
|
$eventLog->setUser($this);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function removeEventLog(EventLog $eventLog): static
|
||||||
|
{
|
||||||
|
if ($this->eventLogs->removeElement($eventLog)) {
|
||||||
|
// set the owning side to null (unless already changed)
|
||||||
|
if ($eventLog->getUser() === $this) {
|
||||||
|
$eventLog->setUser(null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
24
symfony.lock
24
symfony.lock
@@ -101,6 +101,18 @@
|
|||||||
"bin/phpunit"
|
"bin/phpunit"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
"sentry/sentry-symfony": {
|
||||||
|
"version": "5.6",
|
||||||
|
"recipe": {
|
||||||
|
"repo": "github.com/symfony/recipes-contrib",
|
||||||
|
"branch": "main",
|
||||||
|
"version": "5.0",
|
||||||
|
"ref": "b6cb4b34429dadecd7187852123be19d628fa37a"
|
||||||
|
},
|
||||||
|
"files": [
|
||||||
|
"config/packages/sentry.yaml"
|
||||||
|
]
|
||||||
|
},
|
||||||
"spomky-labs/pwa-bundle": {
|
"spomky-labs/pwa-bundle": {
|
||||||
"version": "1.2.5"
|
"version": "1.2.5"
|
||||||
},
|
},
|
||||||
@@ -232,6 +244,18 @@
|
|||||||
"config/packages/messenger.yaml"
|
"config/packages/messenger.yaml"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
"symfony/monolog-bundle": {
|
||||||
|
"version": "3.10",
|
||||||
|
"recipe": {
|
||||||
|
"repo": "github.com/symfony/recipes",
|
||||||
|
"branch": "main",
|
||||||
|
"version": "3.7",
|
||||||
|
"ref": "f5f5f3e4c23f5349796b7de587f19c51e7104299"
|
||||||
|
},
|
||||||
|
"files": [
|
||||||
|
"config/packages/monolog.yaml"
|
||||||
|
]
|
||||||
|
},
|
||||||
"symfony/notifier": {
|
"symfony/notifier": {
|
||||||
"version": "7.3",
|
"version": "7.3",
|
||||||
"recipe": {
|
"recipe": {
|
||||||
|
|||||||
16
templates/bundles/TwigBundle/Exception/error.html.twig
Normal file
16
templates/bundles/TwigBundle/Exception/error.html.twig
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
{% extends 'bare.html.twig' %}
|
||||||
|
|
||||||
|
{% block body %}
|
||||||
|
<h2 class="px-4 py-4 text-3xl font-extrabold text-orange-500">500</h2>
|
||||||
|
<div class="flex flex-col bg-orange-500/50 p-4 rounded-lg gap-4 w-full md:w-[420px] border-orange-500 border-2 text-gray-50 animate-fade">
|
||||||
|
<div class="flex flex-col m-0 text-center">
|
||||||
|
<h3 class="text-2xl text-bold text-center text-gray-50">Oh crap!</h3>
|
||||||
|
</div>
|
||||||
|
<p class="mb-2">There are many things I'm capable of, but this ain't one of 'em!</p>
|
||||||
|
<pre class="bg-gray-800 text-white p-4 rounded-md overflow-x-auto">
|
||||||
|
<code class="language-plaintext">
|
||||||
|
{{ exception.message }}
|
||||||
|
</code>
|
||||||
|
</pre>
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
||||||
13
templates/bundles/TwigBundle/Exception/error404.html.twig
Normal file
13
templates/bundles/TwigBundle/Exception/error404.html.twig
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
{% extends 'bare.html.twig' %}
|
||||||
|
|
||||||
|
{% block body %}
|
||||||
|
<h2 class="px-4 py-4 text-3xl font-extrabold text-orange-500">404</h2>
|
||||||
|
<div class="flex flex-col bg-orange-500/50 p-4 rounded-lg gap-4 w-full md:w-[420px] border-orange-500 border-2 text-gray-50 animate-fade">
|
||||||
|
<div class="flex flex-col m-0 text-center">
|
||||||
|
<h3 class="text-2xl text-bold text-center text-gray-50">It's not you, it's me!</h3>
|
||||||
|
<small>(or is it?)</small>
|
||||||
|
</div>
|
||||||
|
<p class="mb-2">I don't know, maybe I used to have that page-maybe I didn't, but one thing's for sure: I
|
||||||
|
don't have it now. Sorry!</p>
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
||||||
@@ -2,6 +2,7 @@
|
|||||||
class="episode-list flex flex-col gap-4"
|
class="episode-list flex flex-col gap-4"
|
||||||
>
|
>
|
||||||
<div data-live-id="{{ uniqid() }}" class="episode-container flex flex-col gap-4">
|
<div data-live-id="{{ uniqid() }}" class="episode-container flex flex-col gap-4">
|
||||||
|
{% if this.getEpisodes().items != null %}
|
||||||
{% for episode in this.getEpisodes().items %}
|
{% for episode in this.getEpisodes().items %}
|
||||||
<episode-container id="{{ episode_anchor(episode.seasonNumber, episode.episodeNumber) }}" class="results"
|
<episode-container id="{{ episode_anchor(episode.seasonNumber, episode.episodeNumber) }}" class="results"
|
||||||
show-title="{{ this.title }}"
|
show-title="{{ this.title }}"
|
||||||
@@ -17,7 +18,7 @@
|
|||||||
>
|
>
|
||||||
<div class="p-4 md:p-6 flex flex-col gap-6 bg-orange-500/60 bg-clip-padding backdrop-filter backdrop-blur-md rounded-md">
|
<div class="p-4 md:p-6 flex flex-col gap-6 bg-orange-500/60 bg-clip-padding backdrop-filter backdrop-blur-md rounded-md">
|
||||||
<div class="flex flex-col md:flex-row gap-4">
|
<div class="flex flex-col md:flex-row gap-4">
|
||||||
{% if episode.poster != null %}
|
{% if "jpg" in episode.poster %}
|
||||||
<img class="w-full md:w-64 rounded-lg" src="{{ episode.poster }}" />
|
<img class="w-full md:w-64 rounded-lg" src="{{ episode.poster }}" />
|
||||||
{% else %}
|
{% else %}
|
||||||
<div class="w-full md:w-64 min-w-64 sticky h-[144px] rounded-lg bg-gray-700 flex items-center justify-center">
|
<div class="w-full md:w-64 min-w-64 sticky h-[144px] rounded-lg bg-gray-700 flex items-center justify-center">
|
||||||
@@ -85,6 +86,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</episode-container>
|
</episode-container>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
{% set paginator = this.episodes %}
|
{% set paginator = this.episodes %}
|
||||||
{% include 'partial/tv-episode-list-paginator.html.twig' %}
|
{% include 'partial/tv-episode-list-paginator.html.twig' %}
|
||||||
|
|||||||
10
templates/config/async.conf.twig
Normal file
10
templates/config/async.conf.twig
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
[program:torsearch-worker]
|
||||||
|
command=/app/bin/console messenger:consume async -vv --time-limit 3600
|
||||||
|
numprocs={{ replicas|default(1) }}
|
||||||
|
stdout_logfile=/dev/stdout
|
||||||
|
stdout_logfile_maxbytes=0
|
||||||
|
stderr_logfile=/dev/stderr
|
||||||
|
stderr_logfile_maxbytes=0
|
||||||
|
autorestart=true
|
||||||
|
startretries=3
|
||||||
|
process_name=%(program_name)s_%(process_num)02d
|
||||||
10
templates/config/download.conf.twig
Normal file
10
templates/config/download.conf.twig
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
[program:torsearch-downloader]
|
||||||
|
command=/app/bin/console messenger:consume download -vv --time-limit 3600
|
||||||
|
numprocs={{ replicas|default(1) }}
|
||||||
|
stdout_logfile=/dev/stdout
|
||||||
|
stdout_logfile_maxbytes=0
|
||||||
|
stderr_logfile=/dev/stderr
|
||||||
|
stderr_logfile_maxbytes=0
|
||||||
|
autorestart=true
|
||||||
|
startretries=3
|
||||||
|
process_name=%(program_name)s_%(process_num)02d
|
||||||
10
templates/config/monitor.conf.twig
Normal file
10
templates/config/monitor.conf.twig
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
[program:torsearch-scheduler]
|
||||||
|
command=/app/bin/console messenger:consume scheduler_monitor monitor -vv --time-limit 21600
|
||||||
|
numprocs={{ replicas|default(1) }}
|
||||||
|
stdout_logfile=/dev/stdout
|
||||||
|
stdout_logfile_maxbytes=0
|
||||||
|
stderr_logfile=/dev/stderr
|
||||||
|
stderr_logfile_maxbytes=0
|
||||||
|
autorestart=true
|
||||||
|
startretries=3
|
||||||
|
process_name=%(program_name)s_%(process_num)02d
|
||||||
5
templates/config/supervisord.conf.twig
Normal file
5
templates/config/supervisord.conf.twig
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
[supervisord]
|
||||||
|
nodaemon=true
|
||||||
|
logfile=/dev/null
|
||||||
|
logfile_maxbytes=0
|
||||||
|
pidfile=/run/supervisord.pid
|
||||||
@@ -28,6 +28,7 @@
|
|||||||
tmdbId: results.media.tmdbId,
|
tmdbId: results.media.tmdbId,
|
||||||
imdbId: results.media.imdbId,
|
imdbId: results.media.imdbId,
|
||||||
title: results.media.title,
|
title: results.media.title,
|
||||||
|
season: results.media.latestSeason,
|
||||||
})}}
|
})}}
|
||||||
data-monitor-button-result-filter-outlet="#filter"
|
data-monitor-button-result-filter-outlet="#filter"
|
||||||
data-monitor-button-dialog-outlet=".monitor-modal"
|
data-monitor-button-dialog-outlet=".monitor-modal"
|
||||||
|
|||||||
Reference in New Issue
Block a user