Compare commits
44 Commits
dev-media-
...
v0.16.3
| Author | SHA1 | Date | |
|---|---|---|---|
| 16023f1a26 | |||
| 725034dd4e | |||
| 234aeab120 | |||
| 044df00982 | |||
| 92015feaac | |||
| 5b704d9b36 | |||
| 4f72481829 | |||
| 4ee338e397 | |||
| 08d28d9a4f | |||
| b17313c8fb | |||
| f2b50b4f60 | |||
| 393e3ef41f | |||
| b1ccf3bf00 | |||
| 785794790c | |||
| c0f67a32ff | |||
| 325e4e14a7 | |||
| f9017297f3 | |||
| 0c4def42a2 | |||
| 3100912927 | |||
| 07553a172f | |||
| a88da4f637 | |||
| a88f128ccb | |||
| b1ec344f42 | |||
| 1ee6db4668 | |||
| 76bad88190 | |||
| a4029725f9 | |||
| aed3d92462 | |||
| 76531f397f | |||
| c763d67fcb | |||
| e85ae5ac95 | |||
| 794189d70a | |||
| 34d9029b7c | |||
| 168bf04550 | |||
| 81eb2d1f62 | |||
| f5102b859f | |||
| a9c6abe06b | |||
| 764fef37e5 | |||
| 565481aa5a | |||
| 64b7d4c963 | |||
| 9eb7079ea6 | |||
| bd92480958 | |||
| 079a7e2cb5 | |||
| 295f68cb92 | |||
| a2a1154a22 |
2
.env
2
.env
@@ -36,3 +36,5 @@ MERCURE_JWT_SECRET="!ChangeThisMercureHubJWTSecretKey!"
|
|||||||
# MESSENGER_TRANSPORT_DSN=redis://localhost:6379/messages
|
# MESSENGER_TRANSPORT_DSN=redis://localhost:6379/messages
|
||||||
MESSENGER_TRANSPORT_DSN=doctrine://default?auto_setup=0
|
MESSENGER_TRANSPORT_DSN=doctrine://default?auto_setup=0
|
||||||
###< symfony/messenger ###
|
###< symfony/messenger ###
|
||||||
|
|
||||||
|
REDIS_HOST=redis://redis
|
||||||
|
|||||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -1,4 +1,5 @@
|
|||||||
.idea
|
.idea
|
||||||
|
bolt.db
|
||||||
###> symfony/framework-bundle ###
|
###> symfony/framework-bundle ###
|
||||||
/.env.local
|
/.env.local
|
||||||
/.env.local.php
|
/.env.local.php
|
||||||
|
|||||||
24
Dockerfile
24
Dockerfile
@@ -1,10 +1,18 @@
|
|||||||
FROM registry.caldwell.digital/library/php:8.4-apache
|
FROM dunglas/frankenphp:php8.4-alpine
|
||||||
|
|
||||||
RUN apt-get update && \
|
ENV SERVER_NAME=":80"
|
||||||
apt-get install libldap2-dev -y && \
|
ENV CADDY_GLOBAL_OPTIONS="auto_https off"
|
||||||
rm -rf /var/lib/apt/lists/* && \
|
ENV APP_RUNTIME="Runtime\\FrankenPhpSymfony\\Runtime"
|
||||||
docker-php-ext-configure ldap --with-libdir=lib/x86_64-linux-gnu/ && \
|
|
||||||
docker-php-ext-install ldap
|
|
||||||
|
|
||||||
COPY ./bash/vhost.conf /etc/apache2/sites-enabled/vhost.conf
|
RUN install-php-extensions \
|
||||||
RUN rm /etc/apache2/sites-enabled/000-default.conf
|
pdo_mysql \
|
||||||
|
gd \
|
||||||
|
intl \
|
||||||
|
zip \
|
||||||
|
opcache
|
||||||
|
|
||||||
|
RUN apk add --no-cache wget
|
||||||
|
|
||||||
|
HEALTHCHECK --interval=3s --timeout=3s --retries=10 CMD [ "php", "/app/bin/console", "startup:status" ]
|
||||||
|
|
||||||
|
COPY docker/app/Caddyfile /etc/frankenphp/Caddyfile
|
||||||
|
|||||||
@@ -1,11 +0,0 @@
|
|||||||
FROM registry.caldwell.digital/library/php:8.4-apache
|
|
||||||
|
|
||||||
RUN apt-get update && \
|
|
||||||
apt-get install libldap2-dev -y && \
|
|
||||||
rm -rf /var/lib/apt/lists/* && \
|
|
||||||
docker-php-ext-configure ldap --with-libdir=lib/x86_64-linux-gnu/ && \
|
|
||||||
docker-php-ext-install ldap
|
|
||||||
|
|
||||||
COPY --chown=www-data:www-data . /var/www
|
|
||||||
COPY ./bash/vhost.conf /etc/apache2/sites-enabled/vhost.conf
|
|
||||||
RUN rm /etc/apache2/sites-enabled/000-default.conf
|
|
||||||
@@ -29,6 +29,12 @@ export default class extends Controller {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
deleteDownload(data) {
|
||||||
|
fetch(`/api/download/${data.params.id}`, {method: 'DELETE'})
|
||||||
|
.then(res => res.json())
|
||||||
|
.then(json => console.debug(json));
|
||||||
|
}
|
||||||
|
|
||||||
// Add custom controller actions here
|
// Add custom controller actions here
|
||||||
// fooBar() { this.fooTarget.classList.toggle(this.bazClass) }
|
// fooBar() { this.fooTarget.classList.toggle(this.bazClass) }
|
||||||
|
|
||||||
|
|||||||
@@ -1,24 +0,0 @@
|
|||||||
import { Controller } from '@hotwired/stimulus';
|
|
||||||
|
|
||||||
/*
|
|
||||||
* The following line makes this controller "lazy": it won't be downloaded until needed
|
|
||||||
* See https://github.com/symfony/stimulus-bridge#lazy-controllers
|
|
||||||
*/
|
|
||||||
/* stimulusFetch: 'lazy' */
|
|
||||||
export default class extends Controller {
|
|
||||||
static values = {
|
|
||||||
title: String,
|
|
||||||
tmdbId: String,
|
|
||||||
imdbId: String,
|
|
||||||
mediaType: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
addMovieMonitor() {
|
|
||||||
console.log(`/monitor/movies/${this.tmdbIdValue}/${this.imdbIdValue}/${encodeURI(this.titleValue)}`)
|
|
||||||
fetch(`/monitor/movies/${this.tmdbIdValue}/${this.imdbIdValue}/${encodeURI(this.titleValue)}`)
|
|
||||||
.then(res => res.json())
|
|
||||||
.then(json => {
|
|
||||||
console.log(json)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
14
assets/controllers/monitor_list_controller.js
Normal file
14
assets/controllers/monitor_list_controller.js
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
import { Controller } from '@hotwired/stimulus';
|
||||||
|
|
||||||
|
/*
|
||||||
|
* The following line makes this controller "lazy": it won't be downloaded until needed
|
||||||
|
* See https://github.com/symfony/stimulus-bridge#lazy-controllers
|
||||||
|
*/
|
||||||
|
/* stimulusFetch: 'lazy' */
|
||||||
|
export default class extends Controller {
|
||||||
|
deleteMonitor(data) {
|
||||||
|
fetch(`/api/monitor/${data.params.id}`, {method: 'DELETE'})
|
||||||
|
.then(res => res.json())
|
||||||
|
.then(json => console.debug(json));
|
||||||
|
}
|
||||||
|
}
|
||||||
1
assets/icons/ic/twotone-cancel.svg
Normal file
1
assets/icons/ic/twotone-cancel.svg
Normal file
@@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="https://www.w3.org/2000/svg" viewBox="0 0 24 24"><path fill="currentColor" d="M12 4c-4.41 0-8 3.59-8 8s3.59 8 8 8s8-3.59 8-8s-3.59-8-8-8m5 11.59L15.59 17L12 13.41L8.41 17L7 15.59L10.59 12L7 8.41L8.41 7L12 10.59L15.59 7L17 8.41L13.41 12z" opacity=".3"/><path fill="currentColor" d="M12 2C6.47 2 2 6.47 2 12s4.47 10 10 10s10-4.47 10-10S17.53 2 12 2m0 18c-4.41 0-8-3.59-8-8s3.59-8 8-8s8 3.59 8 8s-3.59 8-8 8m3.59-13L12 10.59L8.41 7L7 8.41L10.59 12L7 15.59L8.41 17L12 13.41L15.59 17L17 15.59L13.41 12L17 8.41z"/></svg>
|
||||||
|
After Width: | Height: | Size: 526 B |
@@ -1,2 +0,0 @@
|
|||||||
# $1 = movies/tvshows/etc, $2 = title of media, $3 = URL of download
|
|
||||||
cd /var/download/${1} && if [ ! -d "${2}" ]; then mkdir "${2}"; fi && cd "${2}" && wget "${3}"
|
|
||||||
@@ -1,18 +0,0 @@
|
|||||||
#!/bin/bash
|
|
||||||
|
|
||||||
# Sleep for a second to ensure DB is awake and ready
|
|
||||||
SLEEP_TIME=$(shuf -i 2-5 -n 1)
|
|
||||||
echo "> Sleeping for ${SLEEP_TIME} seconds to wait for the database"
|
|
||||||
echo "> If there are errors after the migration runs, it's possible another container (scheduler, worker, etc.) already ran the migrations"
|
|
||||||
sleep $SLEEP_TIME
|
|
||||||
|
|
||||||
# Provision database
|
|
||||||
php /var/www/bin/console doctrine:migrations:migrate --no-interaction
|
|
||||||
php /var/www/bin/console db:seed
|
|
||||||
|
|
||||||
# Start Apache in the foreground
|
|
||||||
echo "Starting Apache..."
|
|
||||||
exec apachectl -D FOREGROUND
|
|
||||||
|
|
||||||
exec "$@"
|
|
||||||
|
|
||||||
@@ -1,23 +0,0 @@
|
|||||||
<VirtualHost *:80>
|
|
||||||
ServerName localhost
|
|
||||||
|
|
||||||
DocumentRoot /var/www/public
|
|
||||||
DirectoryIndex /index.php
|
|
||||||
|
|
||||||
<LocationMatch "/hub/">
|
|
||||||
ProxyPass http://mercure:80/
|
|
||||||
ProxyPassReverse http://mercure:80/
|
|
||||||
</LocationMatch>
|
|
||||||
|
|
||||||
<Directory /var/www/public>
|
|
||||||
AllowOverride None
|
|
||||||
Order Allow,Deny
|
|
||||||
Allow from All
|
|
||||||
|
|
||||||
FallbackResource /index.php
|
|
||||||
</Directory>
|
|
||||||
|
|
||||||
<Directory /var/www/public/bundles>
|
|
||||||
FallbackResource disabled
|
|
||||||
</Directory>
|
|
||||||
</VirtualHost>
|
|
||||||
85
compose.yml
85
compose.yml
@@ -12,6 +12,41 @@ services:
|
|||||||
- $PWD/bash/caddy:/etc/caddy
|
- $PWD/bash/caddy:/etc/caddy
|
||||||
- $PWD/bash/certs:/etc/ssl
|
- $PWD/bash/certs:/etc/ssl
|
||||||
|
|
||||||
|
|
||||||
|
app:
|
||||||
|
build: .
|
||||||
|
restart: unless-stopped
|
||||||
|
volumes:
|
||||||
|
- $PWD:/app
|
||||||
|
- mercure_data:/data
|
||||||
|
- mercure_config:/config
|
||||||
|
tty: true
|
||||||
|
environment:
|
||||||
|
MERCURE_PUBLISHER_JWT_KEY: '!ChangeThisMercureHubJWTSecretKey!'
|
||||||
|
MERCURE_SUBSCRIBER_JWT_KEY: '!ChangeThisMercureHubJWTSecretKey!'
|
||||||
|
depends_on:
|
||||||
|
database:
|
||||||
|
condition: service_healthy
|
||||||
|
|
||||||
|
|
||||||
|
worker:
|
||||||
|
build: .
|
||||||
|
restart: unless-stopped
|
||||||
|
volumes:
|
||||||
|
- $PWD:/app
|
||||||
|
tty: true
|
||||||
|
command: php /app/bin/console messenger:consume async -vv --time-limit=3600 --limit=10
|
||||||
|
|
||||||
|
|
||||||
|
scheduler:
|
||||||
|
build: .
|
||||||
|
restart: unless-stopped
|
||||||
|
volumes:
|
||||||
|
- $PWD:/app
|
||||||
|
command: php /app/bin/console messenger:consume scheduler_monitor -vv
|
||||||
|
tty: true
|
||||||
|
|
||||||
|
|
||||||
redis:
|
redis:
|
||||||
image: redis:latest
|
image: redis:latest
|
||||||
volumes:
|
volumes:
|
||||||
@@ -19,51 +54,6 @@ services:
|
|||||||
command: redis-server --maxmemory 512MB
|
command: redis-server --maxmemory 512MB
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
|
|
||||||
app:
|
|
||||||
image: code.caldwell.digital/home/torsearch:0.14.5-app
|
|
||||||
volumes:
|
|
||||||
- ./:/var/www
|
|
||||||
depends_on:
|
|
||||||
database:
|
|
||||||
condition: service_healthy
|
|
||||||
|
|
||||||
worker:
|
|
||||||
image: code.caldwell.digital/home/torsearch:0.14.5-worker
|
|
||||||
volumes:
|
|
||||||
- ./:/var/www
|
|
||||||
- ./var/download:/var/download
|
|
||||||
command: php ./bin/console messenger:consume async -vvv --time-limit=3600
|
|
||||||
depends_on:
|
|
||||||
app:
|
|
||||||
condition: service_healthy
|
|
||||||
|
|
||||||
scheduler:
|
|
||||||
image: code.caldwell.digital/home/torsearch:0.14.5-worker
|
|
||||||
volumes:
|
|
||||||
- ./:/var/www
|
|
||||||
- ./var/download:/var/download
|
|
||||||
command: php ./bin/console messenger:consume scheduler_monitor -vv --time-limit=3600
|
|
||||||
depends_on:
|
|
||||||
app:
|
|
||||||
condition: service_healthy
|
|
||||||
|
|
||||||
mercure:
|
|
||||||
image: dunglas/mercure
|
|
||||||
restart: unless-stopped
|
|
||||||
ports:
|
|
||||||
- "3000:80"
|
|
||||||
environment:
|
|
||||||
SERVER_NAME: ':80'
|
|
||||||
MERCURE_PUBLISHER_JWT_KEY: '!ChangeThisMercureHubJWTSecretKey!'
|
|
||||||
MERCURE_SUBSCRIBER_JWT_KEY: '!ChangeThisMercureHubJWTSecretKey!'
|
|
||||||
MERCURE_EXTRA_DIRECTIVES: |
|
|
||||||
cors_origins *
|
|
||||||
anonymous
|
|
||||||
command: /usr/bin/caddy run --config /etc/caddy/dev.Caddyfile
|
|
||||||
volumes:
|
|
||||||
- mercure_data:/data
|
|
||||||
- mercure_config:/config
|
|
||||||
|
|
||||||
|
|
||||||
database:
|
database:
|
||||||
image: mariadb:10.11.2
|
image: mariadb:10.11.2
|
||||||
@@ -78,14 +68,17 @@ services:
|
|||||||
MYSQL_ROOT_PASSWORD: password
|
MYSQL_ROOT_PASSWORD: password
|
||||||
healthcheck:
|
healthcheck:
|
||||||
test: [ "CMD", "mysqladmin" ,"ping", "-h", "localhost" ]
|
test: [ "CMD", "mysqladmin" ,"ping", "-h", "localhost" ]
|
||||||
timeout: 10s
|
interval: 5s
|
||||||
|
timeout: 5s
|
||||||
retries: 10
|
retries: 10
|
||||||
|
|
||||||
|
|
||||||
adminer:
|
adminer:
|
||||||
image: adminer
|
image: adminer
|
||||||
ports:
|
ports:
|
||||||
- "8081:8080"
|
- "8081:8080"
|
||||||
|
|
||||||
|
|
||||||
volumes:
|
volumes:
|
||||||
mysql:
|
mysql:
|
||||||
mercure_data:
|
mercure_data:
|
||||||
|
|||||||
@@ -22,28 +22,29 @@
|
|||||||
"p3k/emoji-detector": "^1.2",
|
"p3k/emoji-detector": "^1.2",
|
||||||
"php-tmdb/api": "^4.1",
|
"php-tmdb/api": "^4.1",
|
||||||
"predis/predis": "^2.4",
|
"predis/predis": "^2.4",
|
||||||
"symfony/asset": "7.2.*",
|
"runtime/frankenphp-symfony": "^0.2.0",
|
||||||
"symfony/console": "7.2.*",
|
"symfony/asset": "7.3.*",
|
||||||
"symfony/doctrine-messenger": "7.2.*",
|
"symfony/console": "7.3.*",
|
||||||
"symfony/dotenv": "7.2.*",
|
"symfony/doctrine-messenger": "7.3.*",
|
||||||
"symfony/filesystem": "7.2.*",
|
"symfony/dotenv": "7.3.*",
|
||||||
"symfony/finder": "7.2.*",
|
"symfony/filesystem": "7.3.*",
|
||||||
|
"symfony/finder": "7.3.*",
|
||||||
"symfony/flex": "^2",
|
"symfony/flex": "^2",
|
||||||
"symfony/form": "7.2.*",
|
"symfony/form": "7.3.*",
|
||||||
"symfony/framework-bundle": "7.2.*",
|
"symfony/framework-bundle": "7.3.*",
|
||||||
"symfony/ldap": "7.2.*",
|
"symfony/ldap": "7.3.*",
|
||||||
"symfony/mercure-bundle": "^0.3.9",
|
"symfony/mercure-bundle": "^0.3.9",
|
||||||
"symfony/messenger": "7.2.*",
|
"symfony/messenger": "7.3.*",
|
||||||
"symfony/runtime": "7.2.*",
|
"symfony/runtime": "7.3.*",
|
||||||
"symfony/scheduler": "7.2.*",
|
"symfony/scheduler": "7.3.*",
|
||||||
"symfony/security-bundle": "7.2.*",
|
"symfony/security-bundle": "7.3.*",
|
||||||
"symfony/stimulus-bundle": "^2.24",
|
"symfony/stimulus-bundle": "^2.24",
|
||||||
"symfony/twig-bundle": "7.2.*",
|
"symfony/twig-bundle": "7.3.*",
|
||||||
"symfony/ux-icons": "^2.24",
|
"symfony/ux-icons": "^2.24",
|
||||||
"symfony/ux-live-component": "^2.24",
|
"symfony/ux-live-component": "^2.24",
|
||||||
"symfony/ux-turbo": "^2.24",
|
"symfony/ux-turbo": "^2.24",
|
||||||
"symfony/ux-twig-component": "^2.24",
|
"symfony/ux-twig-component": "^2.24",
|
||||||
"symfony/yaml": "7.2.*",
|
"symfony/yaml": "7.3.*",
|
||||||
"symfonycasts/tailwind-bundle": "^0.10.0",
|
"symfonycasts/tailwind-bundle": "^0.10.0",
|
||||||
"twig/extra-bundle": "^2.12|^3.0",
|
"twig/extra-bundle": "^2.12|^3.0",
|
||||||
"twig/twig": "^2.12|^3.0"
|
"twig/twig": "^2.12|^3.0"
|
||||||
@@ -97,13 +98,13 @@
|
|||||||
"extra": {
|
"extra": {
|
||||||
"symfony": {
|
"symfony": {
|
||||||
"allow-contrib": false,
|
"allow-contrib": false,
|
||||||
"require": "7.2.*"
|
"require": "7.3.*"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"require-dev": {
|
"require-dev": {
|
||||||
"phpstan/phpstan": "^2.1",
|
"phpstan/phpstan": "^2.1",
|
||||||
"symfony/maker-bundle": "^1.62",
|
"symfony/maker-bundle": "^1.62",
|
||||||
"symfony/stopwatch": "7.2.*",
|
"symfony/stopwatch": "7.3.*",
|
||||||
"symfony/web-profiler-bundle": "7.2.*"
|
"symfony/web-profiler-bundle": "7.3.*"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
825
composer.lock
generated
825
composer.lock
generated
File diff suppressed because it is too large
Load Diff
@@ -29,6 +29,7 @@ framework:
|
|||||||
'App\Monitor\Action\Command\MonitorTvSeasonCommand': async
|
'App\Monitor\Action\Command\MonitorTvSeasonCommand': async
|
||||||
'App\Monitor\Action\Command\MonitorTvShowCommand': async
|
'App\Monitor\Action\Command\MonitorTvShowCommand': async
|
||||||
'App\Monitor\Action\Command\MonitorMovieCommand': async
|
'App\Monitor\Action\Command\MonitorMovieCommand': async
|
||||||
|
'App\Torrentio\Action\Command\GetTvShowOptionsCommand': async
|
||||||
|
|
||||||
# when@test:
|
# when@test:
|
||||||
# framework:
|
# framework:
|
||||||
|
|||||||
3
config/packages/property_info.yaml
Normal file
3
config/packages/property_info.yaml
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
framework:
|
||||||
|
property_info:
|
||||||
|
with_constructor_extractor: true
|
||||||
@@ -7,12 +7,12 @@ parameters:
|
|||||||
# Media
|
# Media
|
||||||
media.default_movies_dir: movies
|
media.default_movies_dir: movies
|
||||||
media.default_tvshows_dir: tvshows
|
media.default_tvshows_dir: tvshows
|
||||||
media.movies_path: '/var/download/%env(default:media.default_movies_dir:MOVIES_PATH)%'
|
media.movies_path: '%env(default:media.default_movies_dir:MOVIES_PATH)%'
|
||||||
media.tvshows_path: '/var/download/%env(default:media.default_tvshows_dir:TVSHOWS_PATH)%'
|
media.tvshows_path: '/var/download/%env(default:media.default_tvshows_dir:TVSHOWS_PATH)%'
|
||||||
|
|
||||||
# Mercure
|
# Mercure
|
||||||
app.mercure.url: 'http://mercure/.well-known/mercure'
|
app.mercure.url: 'http://app/.well-known/mercure'
|
||||||
app.mercure.public_url: '%env(APP_URL)%/hub/.well-known/mercure'
|
app.mercure.public_url: '%env(APP_URL)%/.well-known/mercure'
|
||||||
|
|
||||||
# Cache
|
# Cache
|
||||||
app.cache.adapter: '%env(default:app.cache.adapter.default:CACHE_ADAPTER)%'
|
app.cache.adapter: '%env(default:app.cache.adapter.default:CACHE_ADAPTER)%'
|
||||||
|
|||||||
@@ -1,42 +1,57 @@
|
|||||||
services:
|
services:
|
||||||
php:
|
app:
|
||||||
image: registry.caldwell.digital/home/torsearch/app:${TAG}
|
image: registry.caldwell.digital/home/torsearch-app:${TAG}
|
||||||
ports:
|
ports:
|
||||||
- "8001:80"
|
- '8001:80'
|
||||||
deploy:
|
|
||||||
replicas: 2
|
|
||||||
|
|
||||||
worker:
|
|
||||||
image: registry.caldwell.digital/home/torsearch/app:${TAG}
|
|
||||||
volumes:
|
|
||||||
- /mnt/media/downloads:/var/download
|
|
||||||
command: php ./bin/console messenger:consume async -v --time-limit=3600 --limit=10
|
|
||||||
deploy:
|
|
||||||
replicas: 2
|
|
||||||
|
|
||||||
scheduler:
|
|
||||||
image: registry.caldwell.digital/home/torsearch/app:${TAG}
|
|
||||||
volumes:
|
|
||||||
- /mnt/media/downloads:/var/download
|
|
||||||
command: php ./bin/console messenger:consume scheduler_monitor -vv --time-limit=3600
|
|
||||||
|
|
||||||
mercure:
|
|
||||||
image: dunglas/mercure
|
|
||||||
restart: unless-stopped
|
|
||||||
ports:
|
|
||||||
- "3000:80"
|
|
||||||
environment:
|
environment:
|
||||||
SERVER_NAME: ':80'
|
|
||||||
MERCURE_PUBLISHER_JWT_KEY: '!ChangeThisMercureHubJWTSecretKey!'
|
MERCURE_PUBLISHER_JWT_KEY: '!ChangeThisMercureHubJWTSecretKey!'
|
||||||
MERCURE_SUBSCRIBER_JWT_KEY: '!ChangeThisMercureHubJWTSecretKey!'
|
MERCURE_SUBSCRIBER_JWT_KEY: '!ChangeThisMercureHubJWTSecretKey!'
|
||||||
MERCURE_EXTRA_DIRECTIVES: |
|
deploy:
|
||||||
cors_origins *
|
replicas: 2
|
||||||
anonymous
|
|
||||||
command: /usr/bin/caddy run --config /etc/caddy/dev.Caddyfile
|
|
||||||
volumes:
|
volumes:
|
||||||
|
- /mnt/media/downloads/movies:/var/download/movies
|
||||||
|
- /mnt/media/downloads/tvshows:/var/download/tvshows
|
||||||
- mercure_data:/data
|
- mercure_data:/data
|
||||||
- mercure_config:/config
|
- mercure_config:/config
|
||||||
|
depends_on:
|
||||||
|
- database
|
||||||
|
|
||||||
|
|
||||||
|
worker:
|
||||||
|
image: registry.caldwell.digital/home/torsearch-worker:${TAG}
|
||||||
|
volumes:
|
||||||
|
- /mnt/media/downloads/movies:/var/download/movies
|
||||||
|
- /mnt/media/downloads/tvshows:/var/download/tvshows
|
||||||
|
restart: always
|
||||||
|
command: -vv --time-limit=3600 --limit=10
|
||||||
|
deploy:
|
||||||
|
replicas: 2
|
||||||
|
depends_on:
|
||||||
|
- app
|
||||||
|
|
||||||
|
|
||||||
|
scheduler:
|
||||||
|
image: registry.caldwell.digital/home/torsearch-scheduler:${TAG}
|
||||||
|
volumes:
|
||||||
|
- /mnt/media/downloads/movies:/var/download/movies
|
||||||
|
- /mnt/media/downloads/tvshows:/var/download/tvshows
|
||||||
|
restart: always
|
||||||
|
command: -vv
|
||||||
|
depends_on:
|
||||||
|
- app
|
||||||
|
|
||||||
|
|
||||||
|
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,16 +1,22 @@
|
|||||||
FROM registry.caldwell.digital/library/php:8.4-apache
|
FROM dunglas/frankenphp
|
||||||
|
|
||||||
RUN apt-get update && \
|
ENV SERVER_NAME=":80"
|
||||||
apt-get install libldap2-dev -y && \
|
ENV CADDY_GLOBAL_OPTIONS="auto_https off"
|
||||||
rm -rf /var/lib/apt/lists/* && \
|
ENV APP_RUNTIME="Runtime\\FrankenPhpSymfony\\Runtime"
|
||||||
docker-php-ext-configure ldap --with-libdir=lib/x86_64-linux-gnu/ && \
|
|
||||||
docker-php-ext-install ldap
|
|
||||||
|
|
||||||
COPY --chown=www-data:www-data . /var/www
|
RUN install-php-extensions \
|
||||||
COPY --chmod=0775 ./bash/entrypoint.sh /usr/local/bin/
|
pdo_mysql \
|
||||||
COPY ./bash/vhost.conf /etc/apache2/sites-enabled/vhost.conf
|
gd \
|
||||||
RUN rm /etc/apache2/sites-enabled/000-default.conf
|
intl \
|
||||||
|
zip \
|
||||||
|
opcache
|
||||||
|
|
||||||
HEALTHCHECK --interval=5s --timeout=5s --retries=5 CMD [ "php", "/var/www/bin/console", "startup:status" ]
|
COPY . /app
|
||||||
|
COPY --chmod=775 docker/app/entrypoint.sh /usr/local/bin/docker-entrypoint
|
||||||
|
COPY docker/app/Caddyfile /etc/frankenphp/Caddyfile
|
||||||
|
|
||||||
ENTRYPOINT [ "/usr/local/bin/entrypoint.sh" ]
|
ENTRYPOINT [ "/usr/local/bin/docker-entrypoint" ]
|
||||||
|
|
||||||
|
CMD [ "frankenphp", "run", "--config", "/etc/frankenphp/Caddyfile" ]
|
||||||
|
|
||||||
|
HEALTHCHECK --interval=3s --timeout=3s --retries=10 CMD [ "php", "/app/bin/console", "startup:status" ]
|
||||||
|
|||||||
18
docker/Dockerfile.scheduler
Normal file
18
docker/Dockerfile.scheduler
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
FROM dunglas/frankenphp:php8.4-alpine
|
||||||
|
|
||||||
|
ENV SERVER_NAME=":80"
|
||||||
|
ENV CADDY_GLOBAL_OPTIONS="auto_https off"
|
||||||
|
ENV APP_RUNTIME="Runtime\\FrankenPhpSymfony\\Runtime"
|
||||||
|
|
||||||
|
RUN install-php-extensions \
|
||||||
|
pdo_mysql \
|
||||||
|
gd \
|
||||||
|
intl \
|
||||||
|
zip \
|
||||||
|
opcache
|
||||||
|
|
||||||
|
COPY . /app
|
||||||
|
|
||||||
|
ENTRYPOINT [ "php", "/app/bin/console", "messenger:consume", "scheduler_monitor" ]
|
||||||
|
|
||||||
|
HEALTHCHECK --interval=3s --timeout=3s --retries=10 CMD return 0
|
||||||
@@ -1,12 +1,20 @@
|
|||||||
FROM registry.caldwell.digital/library/php:8.4-apache
|
FROM dunglas/frankenphp:php8.4-alpine
|
||||||
|
|
||||||
RUN apt-get update && \
|
ENV SERVER_NAME=":80"
|
||||||
apt-get install libldap2-dev -y && \
|
ENV CADDY_GLOBAL_OPTIONS="auto_https off"
|
||||||
rm -rf /var/lib/apt/lists/* && \
|
ENV APP_RUNTIME="Runtime\\FrankenPhpSymfony\\Runtime"
|
||||||
docker-php-ext-configure ldap --with-libdir=lib/x86_64-linux-gnu/ && \
|
|
||||||
docker-php-ext-install ldap
|
|
||||||
|
|
||||||
COPY --chown=www-data:www-data . /var/www
|
RUN install-php-extensions \
|
||||||
|
pdo_mysql \
|
||||||
|
gd \
|
||||||
|
intl \
|
||||||
|
zip \
|
||||||
|
opcache
|
||||||
|
|
||||||
COPY ./bash/vhost.conf /etc/apache2/sites-enabled/vhost.conf
|
RUN apk add --no-cache wget
|
||||||
RUN rm /etc/apache2/sites-enabled/000-default.conf
|
|
||||||
|
COPY . /app
|
||||||
|
|
||||||
|
ENTRYPOINT [ "php", "/app/bin/console", "messenger:consume", "async" ]
|
||||||
|
|
||||||
|
HEALTHCHECK --interval=3s --timeout=3s --retries=10 CMD return 0
|
||||||
|
|||||||
61
docker/app/Caddyfile
Normal file
61
docker/app/Caddyfile
Normal file
@@ -0,0 +1,61 @@
|
|||||||
|
{
|
||||||
|
{$CADDY_GLOBAL_OPTIONS}
|
||||||
|
|
||||||
|
frankenphp {
|
||||||
|
{$FRANKENPHP_CONFIG}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
{$CADDY_EXTRA_CONFIG}
|
||||||
|
|
||||||
|
{$SERVER_NAME:localhost} {
|
||||||
|
log {
|
||||||
|
{$CADDY_SERVER_LOG_OPTIONS}
|
||||||
|
# Redact the authorization query parameter that can be set by Mercure
|
||||||
|
format filter {
|
||||||
|
request>uri query {
|
||||||
|
replace authorization REDACTED
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
root /app/public
|
||||||
|
encode zstd br gzip
|
||||||
|
|
||||||
|
mercure {
|
||||||
|
# Publisher JWT key
|
||||||
|
publisher_jwt {env.MERCURE_PUBLISHER_JWT_KEY} {env.MERCURE_PUBLISHER_JWT_ALG}
|
||||||
|
# Subscriber JWT key
|
||||||
|
subscriber_jwt {env.MERCURE_SUBSCRIBER_JWT_KEY} {env.MERCURE_SUBSCRIBER_JWT_ALG}
|
||||||
|
# Allow anonymous subscribers (double-check that it's what you want)
|
||||||
|
anonymous
|
||||||
|
# Enable the subscription API (double-check that it's what you want)
|
||||||
|
subscriptions
|
||||||
|
# Custmo cors
|
||||||
|
cors_origins *
|
||||||
|
# Extra directives
|
||||||
|
{$MERCURE_EXTRA_DIRECTIVES}
|
||||||
|
}
|
||||||
|
|
||||||
|
vulcain
|
||||||
|
|
||||||
|
{$CADDY_SERVER_EXTRA_DIRECTIVES}
|
||||||
|
|
||||||
|
# Disable Topics tracking if not enabled explicitly: https://github.com/jkarlin/topics
|
||||||
|
header ?Permissions-Policy "browsing-topics=()"
|
||||||
|
|
||||||
|
@phpRoute {
|
||||||
|
not path /.well-known/mercure*
|
||||||
|
not file {path}
|
||||||
|
}
|
||||||
|
rewrite @phpRoute index.php
|
||||||
|
|
||||||
|
@frontController path index.php
|
||||||
|
php @frontController
|
||||||
|
|
||||||
|
file_server {
|
||||||
|
hide *.php
|
||||||
|
}
|
||||||
|
}
|
||||||
12
docker/app/entrypoint.sh
Normal file
12
docker/app/entrypoint.sh
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
# Sleep for a second to ensure DB is awake and ready
|
||||||
|
SLEEP_TIME=$(shuf -i 2-4 -n 1)
|
||||||
|
echo "> Sleeping for ${SLEEP_TIME} seconds to wait for the database"
|
||||||
|
sleep $SLEEP_TIME
|
||||||
|
|
||||||
|
# Provision database
|
||||||
|
php /app/bin/console doctrine:migrations:migrate --no-interaction
|
||||||
|
php /app/bin/console db:seed
|
||||||
|
|
||||||
|
exec docker-php-entrypoint "$@"
|
||||||
@@ -5,6 +5,7 @@
|
|||||||
# used to generate the Mercure URL behind the scenes.
|
# used to generate the Mercure URL behind the scenes.
|
||||||
APP_URL="https://torsearch.idocode.io"
|
APP_URL="https://torsearch.idocode.io"
|
||||||
APP_SECRET="70169beadfbc8101c393cbfbba27a313"
|
APP_SECRET="70169beadfbc8101c393cbfbba27a313"
|
||||||
|
APP_ENV=prod
|
||||||
|
|
||||||
# Use the DATABASE_URL below to use the MariaDB container
|
# Use the DATABASE_URL below to use the MariaDB container
|
||||||
# provided in the example.compose.yml file, or remove this
|
# provided in the example.compose.yml file, or remove this
|
||||||
@@ -18,13 +19,17 @@ DATABASE_URL="mysql://root:password@database:3306/app?serverVersion=10.6.19.2-Ma
|
|||||||
# This key is never saved anywhere
|
# This key is never saved anywhere
|
||||||
# else and is passed to Torrentio
|
# else and is passed to Torrentio
|
||||||
# to retrieve download options
|
# to retrieve download options
|
||||||
REAL_DEBRID_KEY=""
|
#REAL_DEBRID_KEY=""
|
||||||
|
|
||||||
# Enter you TMDB API key
|
# Enter you TMDB API key
|
||||||
# This is used to provide rich search results
|
# This is used to provide rich search results
|
||||||
# when searching for media and rendering the
|
# when searching for media and rendering the
|
||||||
# Popular Movies and TV Shows section.
|
# Popular Movies and TV Shows section.
|
||||||
TMDB_API=
|
#TMDB_API=
|
||||||
|
|
||||||
|
REAL_DEBRID_KEY="QYYBR7OSQ4VEFKWASDEZ2B4VO67KHUJY6IWOT7HHA7ATXO7QCYDQ"
|
||||||
|
TMDB_API=eyJhbGciOiJIUzI1NiJ9.eyJhdWQiOiI0ZTJjYjJhOGUzOGJhNjdiNjVhOGU1NGM0ZWI1MzhmOCIsIm5iZiI6MTczNzkyNjA0NC41NjQsInN1YiI6IjY3OTZhNTljYzdiMDFiNzJjNzIzZWM5YiIsInNjb3BlcyI6WyJhcGlfcmVhZCJdLCJ2ZXJzaW9uIjoxfQ.e8DbNe9qrSBC1y-ANRv-VWBAtls-ZS2r7aNCiI68mpw
|
||||||
|
|
||||||
|
|
||||||
MERCURE_JWT_SECRET="!ChangeThisMercureHubJWTSecretKey!"
|
MERCURE_JWT_SECRET="!ChangeThisMercureHubJWTSecretKey!"
|
||||||
|
|
||||||
|
|||||||
@@ -1,32 +1,36 @@
|
|||||||
services:
|
services:
|
||||||
# This container runs the actual web app in a php:8.4-apache
|
# The "entrypoint" into the application. This reverse proxy
|
||||||
# base container. If not running behind a reverse proxy,
|
# proxies traffic back to their respective services. If not
|
||||||
# inject your SSL certificates into this container
|
# running behind a reverse proxy inject your SSL certificates
|
||||||
|
# into this container.
|
||||||
|
# This container runs the actual web app in a php:8.4-fpm
|
||||||
|
# base container.
|
||||||
app:
|
app:
|
||||||
image: code.caldwell.digital/home/torsearch:0.14.5-app
|
image: code.caldwell.digital/home/torsearch-app:latest
|
||||||
ports:
|
ports:
|
||||||
- "8006:80"
|
- '8006:80'
|
||||||
env_file:
|
env_file:
|
||||||
- .env
|
- .env
|
||||||
depends_on:
|
depends_on:
|
||||||
database:
|
database:
|
||||||
condition: service_healthy
|
condition: service_healthy
|
||||||
|
|
||||||
# Downloads happen asynchronously in this container. Replicate
|
# Downloads happen in this container. Replicate this
|
||||||
# this container to run multiple downloads simultaneously.
|
# container to run multiple downloads simultaneously.
|
||||||
# Map your "movies" folder to /var/download/movies
|
# Map your "movies" folder to /var/download/movies
|
||||||
# Map your TV shows folder to /var/download/tvshows
|
# Map your "TV shows" folder to /var/download/tvshows
|
||||||
# If your folders are on another machine, use an NFS volume.
|
# If your folders are on another machine, use an NFS volume.
|
||||||
# This container runs a Symfony worker process.
|
# This container runs a Symfony worker process.
|
||||||
# See: https://symfony.com/doc/current/messenger.html
|
# See: https://symfony.com/doc/current/messenger.html
|
||||||
worker:
|
worker:
|
||||||
image: code.caldwell.digital/home/torsearch:0.14.5-worker
|
image: code.caldwell.digital/home/torsearch-worker:latest
|
||||||
volumes:
|
volumes:
|
||||||
- ./downloads/movies:/var/download/movies
|
- /mnt/media/downloads/movies:/var/download/movies
|
||||||
- ./downloads/tvshows:/var/download/tvshows
|
- /mnt/media/downloads/tvshows:/var/download/tvshows
|
||||||
command: php ./bin/console messenger:consume async -v --time-limit=3600 --limit=10
|
command: -vvv
|
||||||
env_file:
|
env_file:
|
||||||
- .env
|
- .env
|
||||||
|
restart: always
|
||||||
depends_on:
|
depends_on:
|
||||||
app:
|
app:
|
||||||
condition: service_healthy
|
condition: service_healthy
|
||||||
@@ -37,12 +41,12 @@ services:
|
|||||||
# This container runs a Symfony worker process.
|
# This container runs a Symfony worker process.
|
||||||
# See: https://symfony.com/doc/current/messenger.html
|
# See: https://symfony.com/doc/current/messenger.html
|
||||||
scheduler:
|
scheduler:
|
||||||
image: code.caldwell.digital/home/torsearch:0.14.5-worker
|
image: code.caldwell.digital/home/torsearch-scheduler:latest
|
||||||
volumes:
|
volumes:
|
||||||
- ./downloads:/var/download
|
- ./downloads:/var/download
|
||||||
command: php ./bin/console messenger:consume scheduler_monitor -vv --time-limit=3600
|
|
||||||
env_file:
|
env_file:
|
||||||
- .env
|
- .env
|
||||||
|
restart: always
|
||||||
depends_on:
|
depends_on:
|
||||||
app:
|
app:
|
||||||
condition: service_healthy
|
condition: service_healthy
|
||||||
@@ -79,7 +83,8 @@ services:
|
|||||||
MYSQL_ROOT_PASSWORD: password
|
MYSQL_ROOT_PASSWORD: password
|
||||||
healthcheck:
|
healthcheck:
|
||||||
test: [ "CMD", "mysqladmin" ,"ping", "-h", "localhost" ]
|
test: [ "CMD", "mysqladmin" ,"ping", "-h", "localhost" ]
|
||||||
timeout: 10s
|
interval: 5s
|
||||||
|
timeout: 5s
|
||||||
retries: 10
|
retries: 10
|
||||||
|
|
||||||
redis:
|
redis:
|
||||||
|
|||||||
115
docs/examples/swarm.compose.yml
Normal file
115
docs/examples/swarm.compose.yml
Normal file
@@ -0,0 +1,115 @@
|
|||||||
|
services:
|
||||||
|
# The "entrypoint" into the application. This reverse proxy
|
||||||
|
# proxies traffic back to their respective services. If not
|
||||||
|
# running behind a reverse proxy inject your SSL certificates
|
||||||
|
# into this container.
|
||||||
|
# This container runs the actual web app in a php:8.4-fpm
|
||||||
|
# base container.
|
||||||
|
app:
|
||||||
|
image: code.caldwell.digital/home/torsearch-app:latest
|
||||||
|
ports:
|
||||||
|
- '8006:80'
|
||||||
|
configs:
|
||||||
|
- env_file
|
||||||
|
deploy:
|
||||||
|
replicas: 2
|
||||||
|
depends_on:
|
||||||
|
- database
|
||||||
|
|
||||||
|
# Downloads happen in this container. Replicate this
|
||||||
|
# container to run multiple downloads simultaneously.
|
||||||
|
# Map your "movies" folder to /var/download/movies
|
||||||
|
# Map your "TV shows" folder to /var/download/tvshows
|
||||||
|
# If your folders are on another machine, use an NFS volume.
|
||||||
|
# This container runs a Symfony worker process.
|
||||||
|
# See: https://symfony.com/doc/current/messenger.html
|
||||||
|
worker:
|
||||||
|
image: code.caldwell.digital/home/torsearch-worker:latest
|
||||||
|
configs:
|
||||||
|
- source: env_file
|
||||||
|
target: /app/bin/.env.local
|
||||||
|
volumes:
|
||||||
|
- /mnt/media/downloads/movies:/var/download/movies
|
||||||
|
- /mnt/media/downloads/tvshows:/var/download/tvshows
|
||||||
|
restart: always
|
||||||
|
command: -vv
|
||||||
|
deploy:
|
||||||
|
replicas: 4
|
||||||
|
depends_on:
|
||||||
|
- app
|
||||||
|
|
||||||
|
# This container handles the monitoring for new media. When new
|
||||||
|
# monitors are added, jobs are periodically dispatched to this
|
||||||
|
# container, and the desired media is searched for and downloaded.
|
||||||
|
# This container runs a Symfony worker process.
|
||||||
|
# See: https://symfony.com/doc/current/messenger.html
|
||||||
|
scheduler:
|
||||||
|
image: code.caldwell.digital/home/torsearch-scheduler:latest
|
||||||
|
configs:
|
||||||
|
- env_file
|
||||||
|
volumes:
|
||||||
|
- ./downloads:/var/download
|
||||||
|
restart: always
|
||||||
|
depends_on:
|
||||||
|
- app
|
||||||
|
|
||||||
|
# This container facilitates viewing the progress of downloads
|
||||||
|
# in realtime. It also handles sending alerts and notifications.
|
||||||
|
# The MERCURE_PUBLISHER_JWT key & MERCURE_SUBSCRIBER_JWT_KEY should
|
||||||
|
# match the MERCURE_JWT_SECRET environment variable.
|
||||||
|
mercure:
|
||||||
|
image: dunglas/mercure
|
||||||
|
restart: unless-stopped
|
||||||
|
ports:
|
||||||
|
- "3001:80"
|
||||||
|
environment:
|
||||||
|
SERVER_NAME: ':80'
|
||||||
|
MERCURE_PUBLISHER_JWT_KEY: '!ChangeThisMercureHubJWTSecretKey!'
|
||||||
|
MERCURE_SUBSCRIBER_JWT_KEY: '!ChangeThisMercureHubJWTSecretKey!'
|
||||||
|
MERCURE_EXTRA_DIRECTIVES: |
|
||||||
|
cors_origins *
|
||||||
|
anonymous
|
||||||
|
command: /usr/bin/caddy run --config /etc/caddy/dev.Caddyfile
|
||||||
|
volumes:
|
||||||
|
- mercure_data:/data
|
||||||
|
- mercure_config:/config
|
||||||
|
|
||||||
|
database:
|
||||||
|
image: mariadb:10.11.2
|
||||||
|
volumes:
|
||||||
|
- mysql:/var/lib/mysql
|
||||||
|
environment:
|
||||||
|
MYSQL_DATABASE: app
|
||||||
|
MYSQL_USERNAME: app
|
||||||
|
MYSQL_PASSWORD: password
|
||||||
|
MYSQL_ROOT_PASSWORD: password
|
||||||
|
healthcheck:
|
||||||
|
test: [ "CMD", "mysqladmin" ,"ping", "-h", "localhost" ]
|
||||||
|
interval: 5s
|
||||||
|
timeout: 5s
|
||||||
|
retries: 10
|
||||||
|
|
||||||
|
redis:
|
||||||
|
image: redis:latest
|
||||||
|
volumes:
|
||||||
|
- redis_data:/data
|
||||||
|
command: redis-server --maxmemory 512MB
|
||||||
|
restart: unless-stopped
|
||||||
|
|
||||||
|
# **Optional**
|
||||||
|
# Provides a simple method of viewing the database
|
||||||
|
adminer:
|
||||||
|
image: adminer
|
||||||
|
ports:
|
||||||
|
- "8081:8080"
|
||||||
|
|
||||||
|
volumes:
|
||||||
|
mysql:
|
||||||
|
mercure_config:
|
||||||
|
mercure_data:
|
||||||
|
redis_data:
|
||||||
|
|
||||||
|
configs:
|
||||||
|
env_file:
|
||||||
|
file: $PWD/.env
|
||||||
|
|
||||||
129
nomad.deploy.hcl
Normal file
129
nomad.deploy.hcl
Normal file
@@ -0,0 +1,129 @@
|
|||||||
|
variable "image_tag" {
|
||||||
|
type = string
|
||||||
|
description = "Docker image tag to deploy."
|
||||||
|
default = "latest"
|
||||||
|
}
|
||||||
|
|
||||||
|
job "torsearch" {
|
||||||
|
datacenters = [ "home" ]
|
||||||
|
type = "service"
|
||||||
|
|
||||||
|
group "app" {
|
||||||
|
count = 2
|
||||||
|
|
||||||
|
update {
|
||||||
|
max_parallel = 4
|
||||||
|
min_healthy_time = "30s"
|
||||||
|
healthy_deadline = "3m"
|
||||||
|
auto_revert = true
|
||||||
|
}
|
||||||
|
|
||||||
|
network {
|
||||||
|
port "app" {
|
||||||
|
to = 80
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
task "app" {
|
||||||
|
driver = "docker"
|
||||||
|
|
||||||
|
config {
|
||||||
|
image = "registry.caldwell.digital/home/torsearch-app:${var.image_tag}"
|
||||||
|
ports = ["app"]
|
||||||
|
}
|
||||||
|
|
||||||
|
env {
|
||||||
|
MERCURE_PUBLISHER_JWT_KEY = "!ChangeThisMercureHubJWTSecretKey!"
|
||||||
|
MERCURE_SUBSCRIBER_JWT_KEY = "!ChangeThisMercureHubJWTSecretKey!"
|
||||||
|
}
|
||||||
|
|
||||||
|
service {
|
||||||
|
name = "torsearch-app"
|
||||||
|
provider = "nomad"
|
||||||
|
port = "app"
|
||||||
|
|
||||||
|
meta {
|
||||||
|
nomad_ingress_enabled = true
|
||||||
|
nomad_ingress_hostname = "torsearch-nomad.caldwell.digital"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
group "worker" {
|
||||||
|
count = 2
|
||||||
|
update {
|
||||||
|
max_parallel = 4
|
||||||
|
min_healthy_time = "30s"
|
||||||
|
healthy_deadline = "3m"
|
||||||
|
auto_revert = true
|
||||||
|
}
|
||||||
|
|
||||||
|
volume "media" {
|
||||||
|
type = "host"
|
||||||
|
source = "media"
|
||||||
|
read_only = false
|
||||||
|
}
|
||||||
|
|
||||||
|
task "worker" {
|
||||||
|
driver = "docker"
|
||||||
|
|
||||||
|
volume_mount {
|
||||||
|
volume = "media"
|
||||||
|
destination = "/var/download"
|
||||||
|
read_only = false
|
||||||
|
}
|
||||||
|
|
||||||
|
config {
|
||||||
|
image = "registry.caldwell.digital/home/torsearch-worker:${var.image_tag}"
|
||||||
|
args = [
|
||||||
|
"-vv"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
service {
|
||||||
|
name = "torsearch-worker"
|
||||||
|
provider = "nomad"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
group "scheduler" {
|
||||||
|
count = 1
|
||||||
|
|
||||||
|
update {
|
||||||
|
max_parallel = 2
|
||||||
|
min_healthy_time = "30s"
|
||||||
|
healthy_deadline = "3m"
|
||||||
|
auto_revert = true
|
||||||
|
}
|
||||||
|
|
||||||
|
volume "media" {
|
||||||
|
type = "host"
|
||||||
|
source = "media"
|
||||||
|
read_only = false
|
||||||
|
}
|
||||||
|
|
||||||
|
task "scheduler" {
|
||||||
|
driver = "docker"
|
||||||
|
|
||||||
|
volume_mount {
|
||||||
|
volume = "media"
|
||||||
|
destination = "/var/download"
|
||||||
|
read_only = false
|
||||||
|
}
|
||||||
|
|
||||||
|
config {
|
||||||
|
image = "registry.caldwell.digital/home/torsearch-scheduler:${var.image_tag}"
|
||||||
|
args = [
|
||||||
|
"-vv"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
service {
|
||||||
|
name = "torsearch-scheduler"
|
||||||
|
provider = "nomad"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -2,34 +2,25 @@
|
|||||||
|
|
||||||
namespace App\Controller;
|
namespace App\Controller;
|
||||||
|
|
||||||
|
use App\Util\Broadcaster;
|
||||||
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
||||||
use Symfony\Component\DependencyInjection\Attribute\Autowire;
|
|
||||||
use Symfony\Component\HttpFoundation\Response;
|
use Symfony\Component\HttpFoundation\Response;
|
||||||
use Symfony\Component\Mercure\HubInterface;
|
|
||||||
use Symfony\Component\Mercure\Update;
|
|
||||||
use Symfony\Component\Routing\Attribute\Route;
|
use Symfony\Component\Routing\Attribute\Route;
|
||||||
use Twig\Environment;
|
|
||||||
|
|
||||||
final class AlertController extends AbstractController
|
final class AlertController extends AbstractController
|
||||||
{
|
{
|
||||||
public function __construct(
|
public function __construct(
|
||||||
#[Autowire(service: 'twig')] private readonly Environment $renderer,
|
private readonly Broadcaster $broadcaster,
|
||||||
private readonly HubInterface $hub,
|
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
#[Route('/alert', name: 'app_alert')]
|
#[Route('/alert', name: 'app_alert')]
|
||||||
public function index(): Response
|
public function index(): Response
|
||||||
{
|
{
|
||||||
$update = new Update(
|
$this->broadcaster->alert(
|
||||||
'alerts',
|
'Added to queue',
|
||||||
$this->renderer->render('Alert.stream.html.twig', [
|
'This is a testy test!'
|
||||||
'alert_id' => 1,
|
|
||||||
'title' => 'Added to queue',
|
|
||||||
'message' => 'This is a testy test!',
|
|
||||||
])
|
|
||||||
);
|
);
|
||||||
|
|
||||||
$this->hub->publish($update);
|
|
||||||
return $this->json([
|
return $this->json([
|
||||||
'Success' => 'Published'
|
'Success' => 'Published'
|
||||||
]);
|
]);
|
||||||
|
|||||||
@@ -6,18 +6,20 @@ use App\Search\Action\Handler\GetMediaInfoHandler;
|
|||||||
use App\Search\Action\Handler\SearchHandler;
|
use App\Search\Action\Handler\SearchHandler;
|
||||||
use App\Search\Action\Input\GetMediaInfoInput;
|
use App\Search\Action\Input\GetMediaInfoInput;
|
||||||
use App\Search\Action\Input\SearchInput;
|
use App\Search\Action\Input\SearchInput;
|
||||||
use Carbon\Carbon;
|
use App\Tmdb\TmdbResult;
|
||||||
|
use App\Torrentio\Action\Command\GetMovieOptionsCommand;
|
||||||
|
use App\Torrentio\Action\Command\GetTvShowOptionsCommand;
|
||||||
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\Routing\Attribute\Route;
|
use Symfony\Component\Routing\Attribute\Route;
|
||||||
use Symfony\Contracts\Cache\CacheInterface;
|
|
||||||
use Symfony\Contracts\Cache\ItemInterface;
|
|
||||||
|
|
||||||
final class SearchController extends AbstractController
|
final class SearchController extends AbstractController
|
||||||
{
|
{
|
||||||
public function __construct(
|
public function __construct(
|
||||||
private SearchHandler $searchHandler,
|
private SearchHandler $searchHandler,
|
||||||
private GetMediaInfoHandler $getMediaInfoHandler,
|
private GetMediaInfoHandler $getMediaInfoHandler,
|
||||||
|
private MessageBusInterface $bus,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
#[Route('/search', name: 'app_search', methods: ['GET'])]
|
#[Route('/search', name: 'app_search', methods: ['GET'])]
|
||||||
@@ -36,6 +38,8 @@ final class SearchController extends AbstractController
|
|||||||
GetMediaInfoInput $input,
|
GetMediaInfoInput $input,
|
||||||
): Response {
|
): Response {
|
||||||
$result = $this->getMediaInfoHandler->handle($input->toCommand());
|
$result = $this->getMediaInfoHandler->handle($input->toCommand());
|
||||||
|
$this->warmDownloadOptionCache($result->media);
|
||||||
|
|
||||||
return $this->render('search/result.html.twig', [
|
return $this->render('search/result.html.twig', [
|
||||||
'results' => $result,
|
'results' => $result,
|
||||||
'filter' => [
|
'filter' => [
|
||||||
@@ -48,4 +52,32 @@ final class SearchController extends AbstractController
|
|||||||
]
|
]
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private function warmDownloadOptionCache(TmdbResult $result)
|
||||||
|
{
|
||||||
|
if ($result->mediaType === 'tvshows') {
|
||||||
|
// dispatches a job to get the download options
|
||||||
|
// for each episode and load them in cache
|
||||||
|
foreach ($result->episodes as $season => $episodes) {
|
||||||
|
// Only do the first 2 seasons, so we reduce
|
||||||
|
// getting rate-limited by Torrentio
|
||||||
|
if ($season > 2) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
foreach ($episodes as $episode) {
|
||||||
|
$this->bus->dispatch(new GetTvShowOptionsCommand(
|
||||||
|
tmdbId: $result->tmdbId,
|
||||||
|
imdbId: $result->imdbId,
|
||||||
|
season: $season,
|
||||||
|
episode: $episode['episode_number'],
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} elseif ($result->mediaType === 'movies') {
|
||||||
|
$this->bus->dispatch(new GetMovieOptionsCommand(
|
||||||
|
$result->tmdbId,
|
||||||
|
$result->imdbId,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,12 +6,11 @@ use App\Torrentio\Action\Handler\GetMovieOptionsHandler;
|
|||||||
use App\Torrentio\Action\Handler\GetTvShowOptionsHandler;
|
use App\Torrentio\Action\Handler\GetTvShowOptionsHandler;
|
||||||
use App\Torrentio\Action\Input\GetMovieOptionsInput;
|
use App\Torrentio\Action\Input\GetMovieOptionsInput;
|
||||||
use App\Torrentio\Action\Input\GetTvShowOptionsInput;
|
use App\Torrentio\Action\Input\GetTvShowOptionsInput;
|
||||||
|
use App\Util\Broadcaster;
|
||||||
use Carbon\Carbon;
|
use Carbon\Carbon;
|
||||||
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
||||||
use Symfony\Component\HttpFoundation\Request;
|
use Symfony\Component\HttpFoundation\Request;
|
||||||
use Symfony\Component\HttpFoundation\Response;
|
use Symfony\Component\HttpFoundation\Response;
|
||||||
use Symfony\Component\Mercure\HubInterface;
|
|
||||||
use Symfony\Component\Mercure\Update;
|
|
||||||
use Symfony\Component\Routing\Attribute\Route;
|
use Symfony\Component\Routing\Attribute\Route;
|
||||||
use Symfony\Contracts\Cache\CacheInterface;
|
use Symfony\Contracts\Cache\CacheInterface;
|
||||||
use Symfony\Contracts\Cache\ItemInterface;
|
use Symfony\Contracts\Cache\ItemInterface;
|
||||||
@@ -21,8 +20,7 @@ final class TorrentioController extends AbstractController
|
|||||||
public function __construct(
|
public function __construct(
|
||||||
private readonly GetMovieOptionsHandler $getMovieOptionsHandler,
|
private readonly GetMovieOptionsHandler $getMovieOptionsHandler,
|
||||||
private readonly GetTvShowOptionsHandler $getTvShowOptionsHandler,
|
private readonly GetTvShowOptionsHandler $getTvShowOptionsHandler,
|
||||||
private readonly HubInterface $hub,
|
private readonly Broadcaster $broadcaster,
|
||||||
private readonly \Twig\Environment $renderer,
|
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
#[Route('/torrentio/movies/{tmdbId}/{imdbId}', name: 'app_torrentio_movies')]
|
#[Route('/torrentio/movies/{tmdbId}/{imdbId}', name: 'app_torrentio_movies')]
|
||||||
@@ -75,14 +73,10 @@ final class TorrentioController extends AbstractController
|
|||||||
);
|
);
|
||||||
$cache->delete($cacheId);
|
$cache->delete($cacheId);
|
||||||
|
|
||||||
$this->hub->publish(new Update(
|
$this->broadcaster->alert(
|
||||||
$request->getSession()->get('mercure_alert_topic'),
|
title: 'Success',
|
||||||
$this->renderer->render('Alert.stream.html.twig', [
|
message: 'Torrentio cache Cleared.'
|
||||||
'alert_id' => uniqid(),
|
);
|
||||||
'title' => 'Success',
|
|
||||||
'message' => 'Torrentio cache Cleared.',
|
|
||||||
])
|
|
||||||
));
|
|
||||||
|
|
||||||
return $cache->get($cacheId, function (ItemInterface $item) use ($input) {
|
return $cache->get($cacheId, function (ItemInterface $item) use ($input) {
|
||||||
$item->expiresAt(Carbon::now()->addHour()->setMinute(0)->setSecond(0));
|
$item->expiresAt(Carbon::now()->addHour()->setMinute(0)->setSecond(0));
|
||||||
|
|||||||
15
src/Download/Action/Command/DeleteDownloadCommand.php
Normal file
15
src/Download/Action/Command/DeleteDownloadCommand.php
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Download\Action\Command;
|
||||||
|
|
||||||
|
use OneToMany\RichBundle\Contract\CommandInterface;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @implements CommandInterface<DeleteDownloadCommand>
|
||||||
|
*/
|
||||||
|
class DeleteDownloadCommand implements CommandInterface
|
||||||
|
{
|
||||||
|
public function __construct(
|
||||||
|
public int $downloadId,
|
||||||
|
) {}
|
||||||
|
}
|
||||||
26
src/Download/Action/Handler/DeleteDownloadHandler.php
Normal file
26
src/Download/Action/Handler/DeleteDownloadHandler.php
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Download\Action\Handler;
|
||||||
|
|
||||||
|
use App\Download\Action\Command\DeleteDownloadCommand;
|
||||||
|
use App\Download\Action\Result\DeleteDownloadResult;
|
||||||
|
use App\Download\Framework\Repository\DownloadRepository;
|
||||||
|
use OneToMany\RichBundle\Contract\CommandInterface;
|
||||||
|
use OneToMany\RichBundle\Contract\HandlerInterface;
|
||||||
|
use OneToMany\RichBundle\Contract\ResultInterface;
|
||||||
|
|
||||||
|
/** @implements HandlerInterface<DeleteDownloadCommand, DeleteDownloadResult> */
|
||||||
|
readonly class DeleteDownloadHandler implements HandlerInterface
|
||||||
|
{
|
||||||
|
public function __construct(
|
||||||
|
private DownloadRepository $downloadRepository,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
public function handle(CommandInterface $command): ResultInterface
|
||||||
|
{
|
||||||
|
$download = $this->downloadRepository->find($command->downloadId);
|
||||||
|
$this->downloadRepository->delete($command->downloadId);
|
||||||
|
|
||||||
|
return new DeleteDownloadResult(200, 'Success', $download);
|
||||||
|
}
|
||||||
|
}
|
||||||
24
src/Download/Action/Input/DeleteDownloadInput.php
Normal file
24
src/Download/Action/Input/DeleteDownloadInput.php
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Download\Action\Input;
|
||||||
|
|
||||||
|
use App\Download\Action\Command\DeleteDownloadCommand;
|
||||||
|
use OneToMany\RichBundle\Attribute\SourceRoute;
|
||||||
|
use OneToMany\RichBundle\Contract\CommandInterface;
|
||||||
|
use OneToMany\RichBundle\Contract\InputInterface;
|
||||||
|
|
||||||
|
/** @implements InputInterface<DeleteDownloadInput> */
|
||||||
|
class DeleteDownloadInput implements InputInterface
|
||||||
|
{
|
||||||
|
public function __construct(
|
||||||
|
#[SourceRoute('downloadId')]
|
||||||
|
public int $downloadId,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
public function toCommand(): CommandInterface
|
||||||
|
{
|
||||||
|
return new DeleteDownloadCommand(
|
||||||
|
$this->downloadId,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
16
src/Download/Action/Result/DeleteDownloadResult.php
Normal file
16
src/Download/Action/Result/DeleteDownloadResult.php
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Download\Action\Result;
|
||||||
|
|
||||||
|
use App\Download\Framework\Entity\Download;
|
||||||
|
use OneToMany\RichBundle\Contract\ResultInterface;
|
||||||
|
|
||||||
|
/** @implements ResultInterface<DownloadMediaResult> */
|
||||||
|
class DeleteDownloadResult implements ResultInterface
|
||||||
|
{
|
||||||
|
public function __construct(
|
||||||
|
public int $status,
|
||||||
|
public string $message,
|
||||||
|
public Download $download,
|
||||||
|
) {}
|
||||||
|
}
|
||||||
@@ -2,13 +2,13 @@
|
|||||||
|
|
||||||
namespace App\Download\Framework\Controller;
|
namespace App\Download\Framework\Controller;
|
||||||
|
|
||||||
|
use App\Download\Action\Handler\DeleteDownloadHandler;
|
||||||
|
use App\Download\Action\Input\DeleteDownloadInput;
|
||||||
use App\Download\Action\Input\DownloadMediaInput;
|
use App\Download\Action\Input\DownloadMediaInput;
|
||||||
use App\Download\Framework\Repository\DownloadRepository;
|
use App\Download\Framework\Repository\DownloadRepository;
|
||||||
|
use App\Util\Broadcaster;
|
||||||
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
||||||
use Symfony\Component\HttpFoundation\Request;
|
|
||||||
use Symfony\Component\HttpFoundation\Response;
|
use Symfony\Component\HttpFoundation\Response;
|
||||||
use Symfony\Component\Mercure\HubInterface;
|
|
||||||
use Symfony\Component\Mercure\Update;
|
|
||||||
use Symfony\Component\Messenger\MessageBusInterface;
|
use Symfony\Component\Messenger\MessageBusInterface;
|
||||||
use Symfony\Component\Routing\Attribute\Route;
|
use Symfony\Component\Routing\Attribute\Route;
|
||||||
|
|
||||||
@@ -17,12 +17,11 @@ class ApiController extends AbstractController
|
|||||||
public function __construct(
|
public function __construct(
|
||||||
private DownloadRepository $downloadRepository,
|
private DownloadRepository $downloadRepository,
|
||||||
private MessageBusInterface $bus,
|
private MessageBusInterface $bus,
|
||||||
private readonly HubInterface $hub,
|
private readonly Broadcaster $broadcaster,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
#[Route('/api/download', name: 'api_download', methods: ['POST'])]
|
#[Route('/api/download', name: 'api_download', methods: ['POST'])]
|
||||||
public function download(
|
public function download(
|
||||||
Request $request,
|
|
||||||
DownloadMediaInput $input,
|
DownloadMediaInput $input,
|
||||||
): Response {
|
): Response {
|
||||||
$download = $this->downloadRepository->insert(
|
$download = $this->downloadRepository->insert(
|
||||||
@@ -45,15 +44,25 @@ class ApiController extends AbstractController
|
|||||||
return $this->json(['error' => $exception->getMessage()], Response::HTTP_INTERNAL_SERVER_ERROR);
|
return $this->json(['error' => $exception->getMessage()], Response::HTTP_INTERNAL_SERVER_ERROR);
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->hub->publish(new Update(
|
$this->broadcaster->alert(
|
||||||
$request->getSession()->get('mercure_alert_topic'),
|
title: 'Success',
|
||||||
$this->renderView('broadcast/Alert.stream.html.twig', [
|
message: "$input->title added to Queue."
|
||||||
'alert_id' => uniqid(),
|
);
|
||||||
'title' => 'Success',
|
|
||||||
'message' => '"' . $input->title . '" added to Queue',
|
|
||||||
])
|
|
||||||
));
|
|
||||||
|
|
||||||
return $this->json(['status' => 200, 'message' => 'Added to Queue']);
|
return $this->json(['status' => 200, 'message' => 'Added to Queue']);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
#[Route('/api/download/{downloadId}', name: 'api_download_delete', methods: ['DELETE'])]
|
||||||
|
public function deleteDownload(
|
||||||
|
DeleteDownloadInput $input,
|
||||||
|
DeleteDownloadHandler $handler,
|
||||||
|
): Response {
|
||||||
|
$result = $handler->handle($input->toCommand());
|
||||||
|
$this->broadcaster->alert(
|
||||||
|
title: 'Success',
|
||||||
|
message: "{$result->download->getTitle()} has been deleted.",
|
||||||
|
);
|
||||||
|
|
||||||
|
return $this->json(['status' => 200, 'message' => 'Download Deleted']);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ class DownloadRepository extends ServiceEntityRepository
|
|||||||
$this->paginator = $paginator;
|
$this->paginator = $paginator;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getCompletePaginated(UserInterface $user, int $pageNumber = 1, int $perPage = 10)
|
public function getCompletePaginated(UserInterface $user, int $pageNumber = 1, int $perPage = 10): Paginator
|
||||||
{
|
{
|
||||||
$query = $this->createQueryBuilder('d')
|
$query = $this->createQueryBuilder('d')
|
||||||
->andWhere('d.status IN (:statuses)')
|
->andWhere('d.status IN (:statuses)')
|
||||||
@@ -38,7 +38,7 @@ class DownloadRepository extends ServiceEntityRepository
|
|||||||
return $this->paginator->paginate($query, $pageNumber, $perPage);
|
return $this->paginator->paginate($query, $pageNumber, $perPage);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getActivePaginated(UserInterface $user, int $pageNumber = 1, int $perPage = 5)
|
public function getActivePaginated(UserInterface $user, int $pageNumber = 1, int $perPage = 5): Paginator
|
||||||
{
|
{
|
||||||
$query = $this->createQueryBuilder('d')
|
$query = $this->createQueryBuilder('d')
|
||||||
->andWhere('d.status IN (:statuses)')
|
->andWhere('d.status IN (:statuses)')
|
||||||
|
|||||||
13
src/Monitor/Action/Command/DeleteMonitorCommand.php
Normal file
13
src/Monitor/Action/Command/DeleteMonitorCommand.php
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Monitor\Action\Command;
|
||||||
|
|
||||||
|
use OneToMany\RichBundle\Contract\CommandInterface;
|
||||||
|
|
||||||
|
/** @implements CommandInterface<DeleteMonitorCommand> */
|
||||||
|
class DeleteMonitorCommand implements CommandInterface
|
||||||
|
{
|
||||||
|
public function __construct(
|
||||||
|
public string $monitorId,
|
||||||
|
) {}
|
||||||
|
}
|
||||||
36
src/Monitor/Action/Handler/DeleteMonitorHandler.php
Normal file
36
src/Monitor/Action/Handler/DeleteMonitorHandler.php
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Monitor\Action\Handler;
|
||||||
|
|
||||||
|
use App\Monitor\Action\Command\AddMonitorCommand;
|
||||||
|
use App\Monitor\Action\Command\DeleteMonitorCommand;
|
||||||
|
use App\Monitor\Action\Result\AddMonitorResult;
|
||||||
|
use App\Monitor\Action\Result\DeleteMonitorResult;
|
||||||
|
use App\Monitor\Framework\Entity\Monitor;
|
||||||
|
use App\Monitor\Framework\Repository\MonitorRepository;
|
||||||
|
use App\User\Framework\Repository\UserRepository;
|
||||||
|
use DateTimeImmutable;
|
||||||
|
use OneToMany\RichBundle\Contract\CommandInterface;
|
||||||
|
use OneToMany\RichBundle\Contract\HandlerInterface;
|
||||||
|
use OneToMany\RichBundle\Contract\ResultInterface;
|
||||||
|
|
||||||
|
/** @implements HandlerInterface<DeleteMonitorCommand, DeleteMonitorResult> */
|
||||||
|
readonly class DeleteMonitorHandler implements HandlerInterface
|
||||||
|
{
|
||||||
|
public function __construct(
|
||||||
|
private MonitorRepository $monitorRepository,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
public function handle(CommandInterface $command): ResultInterface
|
||||||
|
{
|
||||||
|
$monitor = $this->monitorRepository->find($command->monitorId);
|
||||||
|
$this->monitorRepository->getEntityManager()->remove($monitor);
|
||||||
|
$this->monitorRepository->getEntityManager()->flush();
|
||||||
|
|
||||||
|
return new DeleteMonitorResult(
|
||||||
|
status: 'OK',
|
||||||
|
result: [],
|
||||||
|
monitor: $monitor
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
24
src/Monitor/Action/Input/DeleteMonitorInput.php
Normal file
24
src/Monitor/Action/Input/DeleteMonitorInput.php
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Monitor\Action\Input;
|
||||||
|
|
||||||
|
use App\Monitor\Action\Command\DeleteMonitorCommand;
|
||||||
|
use OneToMany\RichBundle\Attribute\SourceRoute;
|
||||||
|
use OneToMany\RichBundle\Contract\CommandInterface;
|
||||||
|
use OneToMany\RichBundle\Contract\InputInterface;
|
||||||
|
|
||||||
|
/** @implements InputInterface<DeleteMonitorInput, DeleteMonitorCommand> */
|
||||||
|
class DeleteMonitorInput implements InputInterface
|
||||||
|
{
|
||||||
|
public function __construct(
|
||||||
|
#[SourceRoute('monitorId')]
|
||||||
|
public int $monitorId,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
public function toCommand(): CommandInterface
|
||||||
|
{
|
||||||
|
return new DeleteMonitorCommand(
|
||||||
|
$this->monitorId
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
15
src/Monitor/Action/Result/DeleteMonitorResult.php
Normal file
15
src/Monitor/Action/Result/DeleteMonitorResult.php
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Monitor\Action\Result;
|
||||||
|
|
||||||
|
use App\Monitor\Framework\Entity\Monitor;
|
||||||
|
use OneToMany\RichBundle\Contract\ResultInterface;
|
||||||
|
|
||||||
|
class DeleteMonitorResult implements ResultInterface
|
||||||
|
{
|
||||||
|
public function __construct(
|
||||||
|
public string $status,
|
||||||
|
public array $result,
|
||||||
|
public Monitor $monitor,
|
||||||
|
) {}
|
||||||
|
}
|
||||||
@@ -2,21 +2,23 @@
|
|||||||
|
|
||||||
namespace App\Monitor\Framework\Controller;
|
namespace App\Monitor\Framework\Controller;
|
||||||
|
|
||||||
|
use App\Download\Action\Input\DeleteDownloadInput;
|
||||||
use App\Monitor\Action\Handler\AddMonitorHandler;
|
use App\Monitor\Action\Handler\AddMonitorHandler;
|
||||||
|
use App\Monitor\Action\Handler\DeleteMonitorHandler;
|
||||||
use App\Monitor\Action\Input\AddMonitorInput;
|
use App\Monitor\Action\Input\AddMonitorInput;
|
||||||
|
use App\Monitor\Action\Input\DeleteMonitorInput;
|
||||||
|
use App\Util\Broadcaster;
|
||||||
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
||||||
use Symfony\Bundle\SecurityBundle\Security;
|
use Symfony\Bundle\SecurityBundle\Security;
|
||||||
use Symfony\Component\DependencyInjection\Attribute\Autowire;
|
use Symfony\Component\DependencyInjection\Attribute\Autowire;
|
||||||
use Symfony\Component\Mercure\HubInterface;
|
use Symfony\Component\Mercure\HubInterface;
|
||||||
use Symfony\Component\Mercure\Update;
|
use Symfony\Component\Mercure\Update;
|
||||||
use Symfony\Component\Routing\Attribute\Route;
|
use Symfony\Component\Routing\Attribute\Route;
|
||||||
use Twig\Environment;
|
|
||||||
|
|
||||||
class ApiController extends AbstractController
|
class ApiController extends AbstractController
|
||||||
{
|
{
|
||||||
public function __construct(
|
public function __construct(
|
||||||
#[Autowire(service: 'twig')]
|
private readonly Broadcaster $broadcaster,
|
||||||
private readonly Environment $renderer,
|
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
#[Route('/api/monitor', name: 'api_monitor', methods: ['POST'])]
|
#[Route('/api/monitor', name: 'api_monitor', methods: ['POST'])]
|
||||||
@@ -29,12 +31,31 @@ class ApiController extends AbstractController
|
|||||||
$command->userId = $this->getUser()->getId();
|
$command->userId = $this->getUser()->getId();
|
||||||
$response = $handler->handle($command);
|
$response = $handler->handle($command);
|
||||||
|
|
||||||
|
$this->broadcaster->alert(
|
||||||
|
title: 'Success',
|
||||||
|
message: "New monitor added for {$input->title}",
|
||||||
|
);
|
||||||
|
|
||||||
|
return $this->json([
|
||||||
|
'status' => 200,
|
||||||
|
'message' => $response
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[Route('/api/monitor/{monitorId}', name: 'api_monitor_delete', methods: ['DELETE'])]
|
||||||
|
public function deleteMonitor(
|
||||||
|
DeleteMonitorInput $input,
|
||||||
|
DeleteMonitorHandler $handler,
|
||||||
|
HubInterface $hub,
|
||||||
|
) {
|
||||||
|
$response = $handler->handle($input->toCommand());
|
||||||
|
|
||||||
$hub->publish(new Update(
|
$hub->publish(new Update(
|
||||||
'alerts',
|
'alerts',
|
||||||
$this->renderer->render('broadcast/Alert.stream.html.twig', [
|
$this->renderer->render('broadcast/Alert.stream.html.twig', [
|
||||||
'alert_id' => uniqid(),
|
'alert_id' => uniqid(),
|
||||||
'title' => 'Success',
|
'title' => 'Success',
|
||||||
'message' => "New monitor added for {$input->title}",
|
'message' => "New monitor added for {$response->monitor->getTitle()}",
|
||||||
])
|
])
|
||||||
));
|
));
|
||||||
|
|
||||||
|
|||||||
31
src/Monitor/Framework/Controller/WebController.php
Normal file
31
src/Monitor/Framework/Controller/WebController.php
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Monitor\Framework\Controller;
|
||||||
|
|
||||||
|
use App\Download\Action\Input\DeleteDownloadInput;
|
||||||
|
use App\Monitor\Action\Handler\AddMonitorHandler;
|
||||||
|
use App\Monitor\Action\Handler\DeleteMonitorHandler;
|
||||||
|
use App\Monitor\Action\Input\AddMonitorInput;
|
||||||
|
use App\Monitor\Action\Input\DeleteMonitorInput;
|
||||||
|
use App\Monitor\Framework\Repository\MonitorRepository;
|
||||||
|
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
||||||
|
use Symfony\Bundle\SecurityBundle\Security;
|
||||||
|
use Symfony\Component\DependencyInjection\Attribute\Autowire;
|
||||||
|
use Symfony\Component\Mercure\HubInterface;
|
||||||
|
use Symfony\Component\Mercure\Update;
|
||||||
|
use Symfony\Component\Routing\Attribute\Route;
|
||||||
|
use Twig\Environment;
|
||||||
|
|
||||||
|
class WebController extends AbstractController
|
||||||
|
{
|
||||||
|
public function __construct(
|
||||||
|
#[Autowire(service: 'twig')]
|
||||||
|
private readonly Environment $renderer,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
#[Route('/monitors', name: 'app_monitors', methods: ['GET'])]
|
||||||
|
public function addMonitor()
|
||||||
|
{
|
||||||
|
return $this->render('monitor/index.html.twig');
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -7,7 +7,9 @@ use App\User\Framework\Entity\User;
|
|||||||
use Doctrine\DBAL\Types\Types;
|
use Doctrine\DBAL\Types\Types;
|
||||||
use Doctrine\ORM\Mapping as ORM;
|
use Doctrine\ORM\Mapping as ORM;
|
||||||
use Symfony\Component\Serializer\Attribute\Ignore;
|
use Symfony\Component\Serializer\Attribute\Ignore;
|
||||||
|
use Symfony\UX\Turbo\Attribute\Broadcast;
|
||||||
|
|
||||||
|
#[Broadcast(template: 'broadcast/Monitor.stream.html.twig')]
|
||||||
#[ORM\Entity(repositoryClass: MonitorRepository::class)]
|
#[ORM\Entity(repositoryClass: MonitorRepository::class)]
|
||||||
class Monitor
|
class Monitor
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ class MonitorRepository extends ServiceEntityRepository
|
|||||||
$this->paginator = $paginator;
|
$this->paginator = $paginator;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getUserMonitorsPaginated(UserInterface $user, int $page, int $perPage)
|
public function getUserMonitorsPaginated(UserInterface $user, int $page, int $perPage): Paginator
|
||||||
{
|
{
|
||||||
$query = $this->createQueryBuilder('m')
|
$query = $this->createQueryBuilder('m')
|
||||||
->andWhere('m.status IN (:statuses)')
|
->andWhere('m.status IN (:statuses)')
|
||||||
|
|||||||
@@ -16,15 +16,11 @@ final class DownloadList extends AbstractController
|
|||||||
{
|
{
|
||||||
use DefaultActionTrait;
|
use DefaultActionTrait;
|
||||||
|
|
||||||
|
use PaginateTrait;
|
||||||
|
|
||||||
#[LiveProp(writable: true)]
|
#[LiveProp(writable: true)]
|
||||||
public string $type;
|
public string $type;
|
||||||
|
|
||||||
#[LiveProp(writable: true)]
|
|
||||||
public int $pageNumber = 1;
|
|
||||||
|
|
||||||
#[LiveProp(writable: true)]
|
|
||||||
public int $perPage = 5;
|
|
||||||
|
|
||||||
#[LiveProp(writable: true)]
|
#[LiveProp(writable: true)]
|
||||||
public bool $isWidget = true;
|
public bool $isWidget = true;
|
||||||
|
|
||||||
@@ -42,10 +38,4 @@ final class DownloadList extends AbstractController
|
|||||||
|
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
#[LiveAction]
|
|
||||||
public function paginate(#[LiveArg] int $page)
|
|
||||||
{
|
|
||||||
$this->pageNumber = $page;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
14
src/Twig/Components/DownloadListRow.php
Normal file
14
src/Twig/Components/DownloadListRow.php
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Twig\Components;
|
||||||
|
|
||||||
|
use App\Download\Framework\Entity\Download;
|
||||||
|
use Symfony\UX\TwigComponent\Attribute\AsTwigComponent;
|
||||||
|
|
||||||
|
#[AsTwigComponent]
|
||||||
|
final class DownloadListRow
|
||||||
|
{
|
||||||
|
public Download $download;
|
||||||
|
|
||||||
|
public bool $isWidget = true;
|
||||||
|
}
|
||||||
@@ -3,14 +3,24 @@
|
|||||||
namespace App\Twig\Components;
|
namespace App\Twig\Components;
|
||||||
|
|
||||||
use App\Monitor\Framework\Repository\MonitorRepository;
|
use App\Monitor\Framework\Repository\MonitorRepository;
|
||||||
|
use Doctrine\ORM\Query\Expr\OrderBy;
|
||||||
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
||||||
|
use Symfony\UX\LiveComponent\Attribute\AsLiveComponent;
|
||||||
use Symfony\UX\LiveComponent\Attribute\LiveAction;
|
use Symfony\UX\LiveComponent\Attribute\LiveAction;
|
||||||
use Symfony\UX\LiveComponent\Attribute\LiveArg;
|
use Symfony\UX\LiveComponent\Attribute\LiveProp;
|
||||||
use Symfony\UX\TwigComponent\Attribute\AsTwigComponent;
|
use Symfony\UX\LiveComponent\DefaultActionTrait;
|
||||||
|
|
||||||
#[AsTwigComponent]
|
#[AsLiveComponent]
|
||||||
final class MonitorList extends AbstractController
|
final class MonitorList extends AbstractController
|
||||||
{
|
{
|
||||||
|
use DefaultActionTrait;
|
||||||
|
|
||||||
|
use PaginateTrait;
|
||||||
|
|
||||||
|
#[LiveProp(writable: true)]
|
||||||
|
public string $type;
|
||||||
|
|
||||||
|
#[LiveProp(writable: true)]
|
||||||
public bool $isWidget = true;
|
public bool $isWidget = true;
|
||||||
|
|
||||||
public function __construct(
|
public function __construct(
|
||||||
@@ -18,8 +28,36 @@ final class MonitorList extends AbstractController
|
|||||||
) {}
|
) {}
|
||||||
|
|
||||||
#[LiveAction]
|
#[LiveAction]
|
||||||
public function getUserMonitors(#[LiveArg] int $page = 1, #[LiveArg] int $perPage = 5)
|
public function getMonitors()
|
||||||
{
|
{
|
||||||
return $this->monitorRepository->getUserMonitorsPaginated($this->getUser(), $page, $perPage);
|
if ($this->type === "active") {
|
||||||
|
return $this->getActiveUserMonitors();
|
||||||
|
} elseif ($this->type === "complete") {
|
||||||
|
return $this->getCompleteUserMonitors();
|
||||||
|
}
|
||||||
|
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
#[LiveAction]
|
||||||
|
public function getActiveUserMonitors()
|
||||||
|
{
|
||||||
|
return $this->asPaginator($this->monitorRepository->createQueryBuilder('m')
|
||||||
|
->andWhere('m.status IN (:statuses)')
|
||||||
|
->setParameter('statuses', ['New', 'In Progress'])
|
||||||
|
->orderBy('m.id', 'DESC')
|
||||||
|
->getQuery()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[LiveAction]
|
||||||
|
public function getCompleteUserMonitors()
|
||||||
|
{
|
||||||
|
return $this->asPaginator($this->monitorRepository->createQueryBuilder('m')
|
||||||
|
->andWhere('m.status = :status')
|
||||||
|
->setParameter('status', 'Complete')
|
||||||
|
->orderBy('m.id', 'DESC')
|
||||||
|
->getQuery()
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
12
src/Twig/Components/MonitorListRow.php
Normal file
12
src/Twig/Components/MonitorListRow.php
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Twig\Components;
|
||||||
|
|
||||||
|
use App\Monitor\Framework\Entity\Monitor;
|
||||||
|
use Symfony\UX\TwigComponent\Attribute\AsTwigComponent;
|
||||||
|
|
||||||
|
#[AsTwigComponent]
|
||||||
|
final class MonitorListRow
|
||||||
|
{
|
||||||
|
public Monitor $monitor;
|
||||||
|
}
|
||||||
30
src/Twig/Components/PaginateTrait.php
Normal file
30
src/Twig/Components/PaginateTrait.php
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Twig\Components;
|
||||||
|
|
||||||
|
use App\Util\Paginator;
|
||||||
|
use Doctrine\ORM\Query;
|
||||||
|
use Symfony\UX\LiveComponent\Attribute\LiveAction;
|
||||||
|
use Symfony\UX\LiveComponent\Attribute\LiveArg;
|
||||||
|
use Symfony\UX\LiveComponent\Attribute\LiveProp;
|
||||||
|
|
||||||
|
trait PaginateTrait
|
||||||
|
{
|
||||||
|
#[LiveProp(writable: true)]
|
||||||
|
public int $pageNumber = 1;
|
||||||
|
|
||||||
|
#[LiveProp(writable: true)]
|
||||||
|
public int $perPage = 5;
|
||||||
|
|
||||||
|
#[LiveAction]
|
||||||
|
public function paginate(#[LiveArg] int $page)
|
||||||
|
{
|
||||||
|
$this->pageNumber = $page;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function asPaginator(Query $query): Paginator
|
||||||
|
{
|
||||||
|
return (new Paginator())
|
||||||
|
->paginate($query, $this->pageNumber, $this->perPage);
|
||||||
|
}
|
||||||
|
}
|
||||||
36
src/Twig/Extensions/MonitorExtension.php
Normal file
36
src/Twig/Extensions/MonitorExtension.php
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Twig\Extensions;
|
||||||
|
|
||||||
|
use App\Monitor\Framework\Entity\Monitor;
|
||||||
|
use Twig\Attribute\AsTwigFilter;
|
||||||
|
|
||||||
|
class MonitorExtension
|
||||||
|
{
|
||||||
|
#[AsTwigFilter('monitor_type')]
|
||||||
|
public function type(string $type)
|
||||||
|
{
|
||||||
|
$types = [
|
||||||
|
'tvshows' => 'Show',
|
||||||
|
'tvseason' => 'Season',
|
||||||
|
'tvepisode' => 'Episode',
|
||||||
|
];
|
||||||
|
|
||||||
|
return $types[$type] ?? '-';
|
||||||
|
}
|
||||||
|
|
||||||
|
#[AsTwigFilter('monitor_media_id')]
|
||||||
|
public function mediaId(Monitor $monitor)
|
||||||
|
{
|
||||||
|
if ($monitor->getMonitorType() === "tvseason") {
|
||||||
|
return "S". str_pad($monitor->getSeason(), 2, "0", STR_PAD_LEFT);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($monitor->getMonitorType() === "tvepisode") {
|
||||||
|
return "S". str_pad($monitor->getSeason(), 2, "0", STR_PAD_LEFT) .
|
||||||
|
"E". str_pad($monitor->getEpisode(), 2, "0", STR_PAD_LEFT);
|
||||||
|
}
|
||||||
|
|
||||||
|
return "-";
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -2,10 +2,8 @@
|
|||||||
|
|
||||||
namespace App\User\Framework\Controller\Web;
|
namespace App\User\Framework\Controller\Web;
|
||||||
|
|
||||||
use App\User\Action\Handler\RegisterUserHandler;
|
|
||||||
use App\User\Framework\Repository\UserRepository;
|
use App\User\Framework\Repository\UserRepository;
|
||||||
use Doctrine\Common\Collections\ArrayCollection;
|
use Doctrine\Common\Collections\ArrayCollection;
|
||||||
use Psr\Log\LoggerInterface;
|
|
||||||
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\Routing\Attribute\Route;
|
use Symfony\Component\Routing\Attribute\Route;
|
||||||
|
|||||||
@@ -9,23 +9,20 @@ use App\User\Action\Handler\SaveUserMediaPreferencesHandler;
|
|||||||
use App\User\Action\Input\SaveUserDownloadPreferencesInput;
|
use App\User\Action\Input\SaveUserDownloadPreferencesInput;
|
||||||
use App\User\Action\Input\SaveUserMediaPreferencesInput;
|
use App\User\Action\Input\SaveUserMediaPreferencesInput;
|
||||||
use App\User\Framework\Repository\PreferencesRepository;
|
use App\User\Framework\Repository\PreferencesRepository;
|
||||||
|
use App\Util\Broadcaster;
|
||||||
use App\Util\CountryLanguages;
|
use App\Util\CountryLanguages;
|
||||||
use App\Util\ProviderList;
|
use App\Util\ProviderList;
|
||||||
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
||||||
use Symfony\Component\HttpFoundation\Request;
|
|
||||||
use Symfony\Component\HttpFoundation\Response;
|
use Symfony\Component\HttpFoundation\Response;
|
||||||
use Symfony\Component\Mercure\HubInterface;
|
|
||||||
use Symfony\Component\Mercure\Update;
|
|
||||||
use Symfony\Component\Routing\Attribute\Route;
|
use Symfony\Component\Routing\Attribute\Route;
|
||||||
|
|
||||||
class PreferencesController extends AbstractController
|
class PreferencesController extends AbstractController
|
||||||
{
|
{
|
||||||
public function __construct(
|
public function __construct(
|
||||||
private readonly PreferencesRepository $preferencesRepository,
|
private readonly PreferencesRepository $preferencesRepository,
|
||||||
private readonly SaveUserMediaPreferencesHandler $saveUserMediaPreferencesHandler,
|
private readonly Broadcaster $broadcaster,
|
||||||
private readonly HubInterface $hub,
|
|
||||||
private readonly SaveUserDownloadPreferencesHandler $saveUserDownloadPreferencesHandler,
|
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
#[Route('/user/preferences', 'app_user_preferences', methods: ['GET'])]
|
#[Route('/user/preferences', 'app_user_preferences', methods: ['GET'])]
|
||||||
public function mediaPreferences(): Response
|
public function mediaPreferences(): Response
|
||||||
{
|
{
|
||||||
@@ -48,25 +45,21 @@ class PreferencesController extends AbstractController
|
|||||||
|
|
||||||
#[Route('/user/preferences/media', 'app_save_media_preferences', methods: ['POST'])]
|
#[Route('/user/preferences/media', 'app_save_media_preferences', methods: ['POST'])]
|
||||||
public function saveMediaPreferences(
|
public function saveMediaPreferences(
|
||||||
Request $request,
|
|
||||||
SaveUserMediaPreferencesInput $input,
|
SaveUserMediaPreferencesInput $input,
|
||||||
|
SaveUserMediaPreferencesHandler $saveUserMediaPreferencesHandler,
|
||||||
): Response
|
): Response
|
||||||
{
|
{
|
||||||
$this->saveUserMediaPreferencesHandler->handle($input->toCommand());
|
$saveUserMediaPreferencesHandler->handle($input->toCommand());
|
||||||
$mediaPreferences = $this->getUser()->getMediaPreferences();
|
$mediaPreferences = $this->getUser()->getMediaPreferences();
|
||||||
$downloadPreferences = $this->getUser()->getDownloadPreferences();
|
$downloadPreferences = $this->getUser()->getDownloadPreferences();
|
||||||
|
|
||||||
$languages = CountryLanguages::$languages;
|
$languages = CountryLanguages::$languages;
|
||||||
sort($languages);
|
sort($languages);
|
||||||
|
|
||||||
$this->hub->publish(new Update(
|
$this->broadcaster->alert(
|
||||||
$request->getSession()->get('mercure_alert_topic'),
|
title: 'Success',
|
||||||
$this->renderView('broadcast/Alert.stream.html.twig', [
|
message: 'Your media preferences have been saved.'
|
||||||
'alert_id' => uniqid(),
|
);
|
||||||
'title' => 'Success',
|
|
||||||
'message' => 'Your media preferences have been saved.',
|
|
||||||
])
|
|
||||||
));
|
|
||||||
|
|
||||||
return $this->render(
|
return $this->render(
|
||||||
'user/preferences.html.twig',
|
'user/preferences.html.twig',
|
||||||
@@ -82,24 +75,20 @@ class PreferencesController extends AbstractController
|
|||||||
|
|
||||||
#[Route('/user/preferences/download', 'app_save_download_preferences', methods: ['POST'])]
|
#[Route('/user/preferences/download', 'app_save_download_preferences', methods: ['POST'])]
|
||||||
public function saveDownloadPreferences(
|
public function saveDownloadPreferences(
|
||||||
Request $request,
|
|
||||||
SaveUserDownloadPreferencesInput $input,
|
SaveUserDownloadPreferencesInput $input,
|
||||||
|
SaveUserDownloadPreferencesHandler $saveUserDownloadPreferencesHandler,
|
||||||
): Response
|
): Response
|
||||||
{
|
{
|
||||||
$downloadPreferences = $this->saveUserDownloadPreferencesHandler->handle($input->toCommand())->downloadPreferences;
|
$downloadPreferences = $saveUserDownloadPreferencesHandler->handle($input->toCommand())->downloadPreferences;
|
||||||
$mediaPreferences = $this->getUser()->getMediaPreferences();
|
$mediaPreferences = $this->getUser()->getMediaPreferences();
|
||||||
|
|
||||||
$languages = CountryLanguages::$languages;
|
$languages = CountryLanguages::$languages;
|
||||||
sort($languages);
|
sort($languages);
|
||||||
|
|
||||||
$this->hub->publish(new Update(
|
$this->broadcaster->alert(
|
||||||
$request->getSession()->get('mercure_alert_topic'),
|
title: 'Success',
|
||||||
$this->renderView('broadcast/Alert.stream.html.twig', [
|
message: 'Your download preferences have been saved.'
|
||||||
'alert_id' => uniqid(),
|
);
|
||||||
'title' => 'Success',
|
|
||||||
'message' => 'Your download preferences have been saved.',
|
|
||||||
])
|
|
||||||
));
|
|
||||||
|
|
||||||
return $this->render(
|
return $this->render(
|
||||||
'user/preferences.html.twig',
|
'user/preferences.html.twig',
|
||||||
|
|||||||
33
src/Util/Broadcaster.php
Normal file
33
src/Util/Broadcaster.php
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Util;
|
||||||
|
|
||||||
|
use Symfony\Component\DependencyInjection\Attribute\Autowire;
|
||||||
|
use Symfony\Component\HttpFoundation\RequestStack;
|
||||||
|
use Symfony\Component\Mercure\HubInterface;
|
||||||
|
use Symfony\Component\Mercure\Update;
|
||||||
|
use Twig\Environment;
|
||||||
|
|
||||||
|
readonly class Broadcaster
|
||||||
|
{
|
||||||
|
public function __construct(
|
||||||
|
#[Autowire(service: 'twig')]
|
||||||
|
private Environment $renderer,
|
||||||
|
private HubInterface $hub,
|
||||||
|
private RequestStack $requestStack,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
public function alert(string $title, string $message): void
|
||||||
|
{
|
||||||
|
$userAlertTopic = $this->requestStack->getCurrentRequest()->getSession()->get('mercure_alert_topic');
|
||||||
|
$update = new Update(
|
||||||
|
$userAlertTopic,
|
||||||
|
$this->renderer->render('broadcast/Alert.stream.html.twig', [
|
||||||
|
'alert_id' => uniqid(),
|
||||||
|
'title' => $title,
|
||||||
|
'message' => $message,
|
||||||
|
])
|
||||||
|
);
|
||||||
|
$this->hub->publish($update);
|
||||||
|
}
|
||||||
|
}
|
||||||
21
symfony.lock
21
symfony.lock
@@ -2,6 +2,15 @@
|
|||||||
"1tomany/rich-bundle": {
|
"1tomany/rich-bundle": {
|
||||||
"version": "v1.8.3"
|
"version": "v1.8.3"
|
||||||
},
|
},
|
||||||
|
"doctrine/deprecations": {
|
||||||
|
"version": "1.1",
|
||||||
|
"recipe": {
|
||||||
|
"repo": "github.com/symfony/recipes",
|
||||||
|
"branch": "main",
|
||||||
|
"version": "1.0",
|
||||||
|
"ref": "87424683adc81d7dc305eefec1fced883084aab9"
|
||||||
|
}
|
||||||
|
},
|
||||||
"doctrine/doctrine-bundle": {
|
"doctrine/doctrine-bundle": {
|
||||||
"version": "2.14",
|
"version": "2.14",
|
||||||
"recipe": {
|
"recipe": {
|
||||||
@@ -169,6 +178,18 @@
|
|||||||
"config/packages/messenger.yaml"
|
"config/packages/messenger.yaml"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
"symfony/property-info": {
|
||||||
|
"version": "7.3",
|
||||||
|
"recipe": {
|
||||||
|
"repo": "github.com/symfony/recipes",
|
||||||
|
"branch": "main",
|
||||||
|
"version": "7.3",
|
||||||
|
"ref": "dae70df71978ae9226ae915ffd5fad817f5ca1f7"
|
||||||
|
},
|
||||||
|
"files": [
|
||||||
|
"config/packages/property_info.yaml"
|
||||||
|
]
|
||||||
|
},
|
||||||
"symfony/routing": {
|
"symfony/routing": {
|
||||||
"version": "7.2",
|
"version": "7.2",
|
||||||
"recipe": {
|
"recipe": {
|
||||||
|
|||||||
@@ -4,6 +4,14 @@ module.exports = {
|
|||||||
"./assets/**/*.js",
|
"./assets/**/*.js",
|
||||||
"./templates/**/*.html.twig",
|
"./templates/**/*.html.twig",
|
||||||
],
|
],
|
||||||
|
safelist: [
|
||||||
|
"bg-blue-300",
|
||||||
|
"bg-orange-300",
|
||||||
|
"bg-fuchsia-300",
|
||||||
|
"bg-green-400",
|
||||||
|
"bg-purple-400",
|
||||||
|
"bg-orange-400",
|
||||||
|
],
|
||||||
theme: {
|
theme: {
|
||||||
extend: {},
|
extend: {},
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -5,36 +5,19 @@
|
|||||||
|
|
||||||
<turbo-stream action="append" target="active_downloads">
|
<turbo-stream action="append" target="active_downloads">
|
||||||
<template>
|
<template>
|
||||||
<tr data-download-list-target="download" id="ad_download_{{ entity.id }}">
|
<twig:DownloadListRow download="{{ entity }}" />
|
||||||
<td class="px-6 py-4 whitespace-nowrap text-sm font-medium text-gray-800 dark:text-stone-800 min-w-[45ch] max-w-[45ch] truncate">
|
|
||||||
{{ entity.title }}
|
|
||||||
</td>
|
|
||||||
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-800 dark:text-gray-50">
|
|
||||||
{% if entity.progress < 100 %}
|
|
||||||
<div class="w-[3.25ch] h-[3.25ch] bg-purple-600 rounded-full block text-center table-cell align-middle text-xs text-gray-50">
|
|
||||||
{{ entity.progress }}
|
|
||||||
</div>
|
|
||||||
{% else %}
|
|
||||||
<twig:StatusBadge color="green" status="Complete" />
|
|
||||||
{% endif %}
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
</template>
|
</template>
|
||||||
</turbo-stream>
|
</turbo-stream>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block update %}
|
{% block update %}
|
||||||
{% if entity.status != "Complete" %}
|
{% if entity.status != "Complete" %}
|
||||||
<turbo-stream action="update" target="ad_download_{{ id }}">
|
<turbo-stream action="update" target="download_progress_{{ id }}">
|
||||||
<template>
|
<template>
|
||||||
<td class="px-6 py-4 whitespace-nowrap text-sm font-medium text-gray-800 dark:text-stone-800 min-w-[45ch] max-w-[45ch] truncate">
|
<div class="text-green-700 rounded-sm text-bold text-gray-950 text-center bg-green-600 h-5"
|
||||||
{{ entity.title }}
|
style="width:{{ entity.progress }}%">
|
||||||
</td>
|
<span>{{ entity.progress }}%</span>
|
||||||
<td class="px-6 py-4 whitespace-nowrap text-sm text-end text-gray-800 dark:text-gray-50">
|
</div>
|
||||||
<div class="border-2 border-green-700 rounded-md w-full h-6 align-middle overflow-hidden">
|
|
||||||
<div class="text-green-700 rounded-sm text-bold text-gray-950 text-center bg-green-600 h-5" style="width:{{ entity.progress }}%">{{ entity.progress }}%</div>
|
|
||||||
</div>
|
|
||||||
</td>
|
|
||||||
</template>
|
</template>
|
||||||
</turbo-stream>
|
</turbo-stream>
|
||||||
{% else %}
|
{% else %}
|
||||||
@@ -52,14 +35,7 @@
|
|||||||
|
|
||||||
<turbo-stream action="prepend" target="complete_downloads">
|
<turbo-stream action="prepend" target="complete_downloads">
|
||||||
<template>
|
<template>
|
||||||
<tr id="ad_download_{{ entity.id }}">
|
<twig:DownloadListRow download="{{ entity }}" />
|
||||||
<td class="px-6 py-4 whitespace-nowrap text-sm font-medium text-gray-800 dark:text-stone-800 min-w-[45ch] max-w-[45ch] truncate">
|
|
||||||
{{ entity.title }}
|
|
||||||
</td>
|
|
||||||
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-800 dark:text-gray-50">
|
|
||||||
<twig:StatusBadge color="green" status="Complete" />
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
</template>
|
</template>
|
||||||
</turbo-stream>
|
</turbo-stream>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|||||||
29
templates/broadcast/Monitor.stream.html.twig
Normal file
29
templates/broadcast/Monitor.stream.html.twig
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
{# Learn how to use Turbo Streams: https://github.com/symfony/ux-turbo#broadcast-doctrine-entities-update #}
|
||||||
|
{% block create %}
|
||||||
|
<turbo-stream action="remove" target="active_monitors_no_monitors">
|
||||||
|
</turbo-stream>
|
||||||
|
|
||||||
|
<turbo-stream action="prepend" target="monitors">
|
||||||
|
<template>
|
||||||
|
<twig:MonitorListRow monitor="{{ entity }}" />
|
||||||
|
</template>
|
||||||
|
</turbo-stream>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block update %}
|
||||||
|
<turbo-stream action="replace" target="monitor_{{ id }}">
|
||||||
|
<template>
|
||||||
|
<twig:MonitorListRow monitor="{{ entity }}" />
|
||||||
|
</template>
|
||||||
|
</turbo-stream>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block remove %}
|
||||||
|
<turbo-stream action="remove" target="monitor_{{ id }}"></turbo-stream>
|
||||||
|
|
||||||
|
<turbo-stream action="prepend" target="alert_list">
|
||||||
|
<template>
|
||||||
|
<twig:Alert title="Success" message="Your Monitor for '{{ entity.title }}' has been removed." alert_id="monitor_alert_{{ entity.id }}" data-controller="alert" />
|
||||||
|
</template>
|
||||||
|
</turbo-stream>
|
||||||
|
{% endblock %}
|
||||||
@@ -23,42 +23,19 @@
|
|||||||
class="px-6 py-3 text-start text-xs font-medium text-gray-500 uppercase dark:text-stone-800">
|
class="px-6 py-3 text-start text-xs font-medium text-gray-500 uppercase dark:text-stone-800">
|
||||||
Progress
|
Progress
|
||||||
</th>
|
</th>
|
||||||
|
<th scope="col"
|
||||||
|
class="px-6 py-3 text-start text-xs font-medium text-gray-500 uppercase dark:text-stone-800">
|
||||||
|
</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody id="{{ table_body_id }}" class="divide-y divide-gray-200 dark:divide-gray-50">
|
<tbody id="{{ table_body_id }}" class="divide-y divide-gray-200 dark:divide-gray-50">
|
||||||
{% if this.downloads.items|length > 0 %}
|
{% if this.downloads.items|length > 0 %}
|
||||||
{% for download in this.downloads.items %}
|
{% for download in this.downloads.items %}
|
||||||
<tr id="ad_download_{{ download.id }}">
|
<twig:DownloadListRow download="{{ download }}" isWidget="{{ this.isWidget }}" />
|
||||||
<td class="px-6 py-4 whitespace-nowrap text-sm font-medium text-gray-800 dark:text-stone-800 {% if this.isWidget == true %}min-w-[45ch] max-w-[45ch]{% endif %} truncate">
|
|
||||||
{{ download.title }}
|
|
||||||
</td>
|
|
||||||
|
|
||||||
{% if this.isWidget == false %}
|
|
||||||
<td class="px-6 py-4 whitespace-nowrap text-sm font-medium text-gray-800 dark:text-stone-800 truncate">
|
|
||||||
{{ download.filename }}
|
|
||||||
</td>
|
|
||||||
<td class="px-6 py-4 whitespace-nowrap text-sm font-medium text-gray-800 dark:text-stone-800 truncate">
|
|
||||||
{{ download.mediaType }}
|
|
||||||
</td>
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
<td class="px-6 py-4 whitespace-nowrap text-sm align-middle text-gray-800 dark:text-gray-50">
|
|
||||||
{% if download.progress < 100 %}
|
|
||||||
<div class="border-2 border-green-700 rounded-md w-full h-6 align-middle overflow-hidden">
|
|
||||||
<div class="text-green-700 rounded-sm text-bold text-gray-950 text-center bg-green-600 h-5" style="width:{{ download.progress }}%">{{ download.progress }}%</div>
|
|
||||||
</div>
|
|
||||||
{# <div class="w-[3.25ch] h-[3.25ch] bg-purple-600 rounded-full block text-center table-cell align-middle text-xs text-gray-50">#}
|
|
||||||
{# {{ download.progress }}#}
|
|
||||||
{# </div>#}
|
|
||||||
{% else %}
|
|
||||||
<twig:StatusBadge color="green" status="Completed" />
|
|
||||||
{% endif %}
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
{% if this.isWidget == true and this.downloads.items|length > this.perPage %}
|
{% if this.isWidget == true and this.downloads.items|length > this.perPage %}
|
||||||
<tr id="download_view_all">
|
<tr id="download_view_all">
|
||||||
<td class="py-2 whitespace-nowrap bg-orange-500 uppercase text-sm font-medium text-center text-white truncate" colspan="100%">
|
<td class="py-2 whitespace-nowrap bg-orange-300 uppercase text-xs font-medium text-center text-black truncate" colspan="100%">
|
||||||
<a href="{{ path('app_downloads') }}">View All Downloads</a>
|
<a href="{{ path('app_downloads') }}">View All Downloads</a>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
|||||||
36
templates/components/DownloadListRow.html.twig
Normal file
36
templates/components/DownloadListRow.html.twig
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
<tr{{ attributes }} id="ad_download_{{ download.id }}">
|
||||||
|
<td class="px-6 py-4 whitespace-nowrap text-sm font-medium text-gray-800 dark:text-stone-800 truncate">
|
||||||
|
{{ download.title }}
|
||||||
|
</td>
|
||||||
|
|
||||||
|
{% if isWidget == false %}
|
||||||
|
<td class="px-6 py-4 whitespace-nowrap text-sm font-medium text-gray-800 dark:text-stone-800 max-w-[60ch] truncate">
|
||||||
|
{{ download.filename }}
|
||||||
|
</td>
|
||||||
|
<td class="px-6 py-4 whitespace-nowrap text-sm font-medium text-gray-800 dark:text-stone-800 truncate">
|
||||||
|
{{ download.mediaType }}
|
||||||
|
</td>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
<td class="px-6 py-4 whitespace-nowrap text-sm align-middle text-gray-800 dark:text-gray-50">
|
||||||
|
{% if download.progress < 100 %}
|
||||||
|
<div id="download_progress_{{ download.id }}" class="border-2 border-green-700 rounded-md w-full h-6 align-middle overflow-hidden">
|
||||||
|
<div class="text-green-700 rounded-sm text-bold text-gray-950 text-center bg-green-600 h-5"
|
||||||
|
style="width:56%">
|
||||||
|
<span>{{ download.progress }}%</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% else %}
|
||||||
|
<twig:StatusBadge color="green" status="Complete" />
|
||||||
|
{% endif %}
|
||||||
|
</td>
|
||||||
|
<td class="px-6 py-4 flex flex-row align-middle justify-center">
|
||||||
|
<button {{ stimulus_action('download_list', 'deleteDownload', 'click', {id: download.id}) }}>
|
||||||
|
<twig:ux:icon
|
||||||
|
name="ic:twotone-cancel" width="18px"
|
||||||
|
class="rounded-full align-middle text-red-600"
|
||||||
|
title="Remove {{ download.title }} from your Download list. This will not delete the file."
|
||||||
|
/>
|
||||||
|
</button>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
@@ -1,12 +1,15 @@
|
|||||||
<div{{ attributes }} class="min-w-48">
|
<div{{ attributes.defaults(stimulus_controller('monitor_list')) }}>
|
||||||
<p class="text-white mb-1">The items you're currently monitoring to automatically download.</p>
|
<table id="monitor_list" class="divide-y divide-gray-200 bg-gray-50 overflow-hidden rounded-lg table-auto w-full" {{ turbo_stream_listen('App\\Monitor\\Framework\\Entity\\Monitor') }}>
|
||||||
<table id="downloads" class="divide-y divide-gray-200 bg-gray-50 overflow-hidden rounded-lg table-fixed" {{ turbo_stream_listen('App\\Download\\Framework\\Entity\\Download') }}>
|
|
||||||
<thead>
|
<thead>
|
||||||
<tr class="bg-orange-500 bg-filter bg-blur-lg bg-opacity-80 text-gray-950">
|
<tr class="bg-orange-500 bg-filter bg-blur-lg bg-opacity-80 text-gray-950">
|
||||||
<th scope="col"
|
<th scope="col"
|
||||||
class="px-6 py-3 text-start text-xs font-medium uppercase min-w-[45ch] max-w-[45ch] truncate">
|
class="px-6 py-3 text-start text-xs font-medium uppercase truncate">
|
||||||
Title
|
Title
|
||||||
</th>
|
</th>
|
||||||
|
<th scope="col"
|
||||||
|
class="px-6 py-3 text-start text-xs font-medium uppercase">
|
||||||
|
ID
|
||||||
|
</th>
|
||||||
<th scope="col"
|
<th scope="col"
|
||||||
class="px-6 py-3 text-start text-xs font-medium uppercase">
|
class="px-6 py-3 text-start text-xs font-medium uppercase">
|
||||||
Search Count
|
Search Count
|
||||||
@@ -19,49 +22,32 @@
|
|||||||
class="px-6 py-3 text-start text-xs font-medium uppercase">
|
class="px-6 py-3 text-start text-xs font-medium uppercase">
|
||||||
Last Search Date
|
Last Search Date
|
||||||
</th>
|
</th>
|
||||||
|
<th scope="col"
|
||||||
|
class="px-6 py-3 text-start text-xs font-medium uppercase">
|
||||||
|
Type
|
||||||
|
</th>
|
||||||
<th scope="col"
|
<th scope="col"
|
||||||
class="px-6 py-3 text-start text-xs font-medium uppercase">
|
class="px-6 py-3 text-start text-xs font-medium uppercase">
|
||||||
Status
|
Status
|
||||||
</th>
|
</th>
|
||||||
|
<th></th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody class="divide-y divide-gray-50">
|
<tbody id="monitors" class="divide-y divide-gray-50">
|
||||||
{% if this.userMonitors.items|length > 0 %}
|
{% if this.monitors.items|length > 0 %}
|
||||||
{% for monitor in this.userMonitors.items %}
|
{% for monitor in this.monitors.items %}
|
||||||
<tr id="monitor_{{ monitor.id }}">
|
<twig:MonitorListRow :monitor="monitor" />
|
||||||
<td class="px-6 py-4 whitespace-nowrap text-sm font-medium text-stone-800 min-w-[50ch] max-w-[50ch] truncate">
|
|
||||||
{{ monitor.title }}
|
|
||||||
</td>
|
|
||||||
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-800">
|
|
||||||
{{ monitor.searchCount }}
|
|
||||||
</td>
|
|
||||||
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-800">
|
|
||||||
{{ monitor.createdAt|date('m/d/Y h:i a') }}
|
|
||||||
</td>
|
|
||||||
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-800">
|
|
||||||
{{ monitor.lastSearch|date('m/d/Y h:i a') }}
|
|
||||||
</td>
|
|
||||||
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-800">
|
|
||||||
{% if monitor.status == "New" %}
|
|
||||||
<twig:StatusBadge color="orange" status="{{ monitor.status }}" />
|
|
||||||
{% elseif monitor.status == "In Progress" or monitor.status == "Active" %}
|
|
||||||
<twig:StatusBadge color="purple" status="{{ monitor.status }}" />
|
|
||||||
{% else %}
|
|
||||||
<twig:StatusBadge color="green" status="{{ monitor.status }}" />
|
|
||||||
{% endif %}
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
{% if this.userMonitors.items|length > 5 %}
|
{% if this.isWidget and this.monitors.items|length > 5 %}
|
||||||
<tr id="monitor_view_all">
|
<tr id="monitor_view_all">
|
||||||
<td colspan="5" class="py-2 whitespace-nowrap bg-orange-500 uppercase text-sm font-medium text-center text-white min-w-[50ch] max-w-[50ch] truncate">
|
<td colspan="100%" class="py-2 whitespace-nowrap bg-orange-300 uppercase text-xs font-medium text-center text-black min-w-[50ch] max-w-[50ch] truncate">
|
||||||
<a href="#">View All Monitors</a>
|
<a href="{{ path('app_monitors') }}">View All Monitors</a>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% else %}
|
{% else %}
|
||||||
<tr>
|
<tr id="active_monitors_no_monitors">
|
||||||
<td class="px-6 py-4 whitespace-nowrap text-xs uppercase text-center col-span-2 font-medium text-stone-800" colspan="2">
|
<td class="px-6 py-4 whitespace-nowrap text-xs uppercase text-center col-span-2 font-medium text-stone-800" colspan="100%">
|
||||||
No monitors
|
No monitors
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
@@ -70,8 +56,8 @@
|
|||||||
</table>
|
</table>
|
||||||
|
|
||||||
{% if this.isWidget == false %}
|
{% if this.isWidget == false %}
|
||||||
{% if this.userMonitors.items|length > 0 %}
|
{% if this.monitors.items|length > 0 %}
|
||||||
{% set paginator = this.userMonitors %}
|
{% set paginator = this.monitors %}
|
||||||
{% include 'partial/paginator.html.twig' %}
|
{% include 'partial/paginator.html.twig' %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|||||||
44
templates/components/MonitorListRow.html.twig
Normal file
44
templates/components/MonitorListRow.html.twig
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
<tr{{ attributes }} id="monitor_{{ monitor.id }}">
|
||||||
|
<td class="px-6 py-4 whitespace-nowrap text-sm font-medium text-stone-800 truncate">
|
||||||
|
{{ monitor.title }}
|
||||||
|
</td>
|
||||||
|
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-800">
|
||||||
|
{{ monitor|monitor_media_id }}
|
||||||
|
</td>
|
||||||
|
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-800">
|
||||||
|
{{ monitor.searchCount }}
|
||||||
|
</td>
|
||||||
|
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-800">
|
||||||
|
{{ monitor.createdAt|date('m/d/Y h:i a') }}
|
||||||
|
</td>
|
||||||
|
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-800">
|
||||||
|
{{ monitor.lastSearch|date('m/d/Y h:i a') }}
|
||||||
|
</td>
|
||||||
|
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-800">
|
||||||
|
{% if monitor.monitorType == "tvshow" %}
|
||||||
|
<twig:StatusBadge color="blue" number="300" text="black" status="{{ monitor.monitorType|monitor_type }}" />
|
||||||
|
{% elseif monitor.monitorType == "tvseason" %}
|
||||||
|
<twig:StatusBadge color="orange" number="300" text="black" status="{{ monitor.monitorType|monitor_type }}" />
|
||||||
|
{% else %}
|
||||||
|
<twig:StatusBadge color="fuchsia" number="300" text="black" status="{{ monitor.monitorType|monitor_type }}" />
|
||||||
|
{% endif %}
|
||||||
|
</td>
|
||||||
|
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-800">
|
||||||
|
{% if monitor.status == "New" %}
|
||||||
|
<twig:StatusBadge color="orange" status="{{ monitor.status }}" />
|
||||||
|
{% elseif monitor.status == "In Progress" or monitor.status == "Active" %}
|
||||||
|
<twig:StatusBadge color="purple" status="{{ monitor.status }}" />
|
||||||
|
{% else %}
|
||||||
|
<twig:StatusBadge color="green" status="{{ monitor.status }}" />
|
||||||
|
{% endif %}
|
||||||
|
</td>
|
||||||
|
<td class="px-6 py-4 flex flex-row align-middle justify-center">
|
||||||
|
<button {{ stimulus_action('monitor_list', 'deleteMonitor', 'click', {id: monitor.id}) }}>
|
||||||
|
<twig:ux:icon
|
||||||
|
name="ic:twotone-cancel" width="18px"
|
||||||
|
class="rounded-full align-middle text-red-600"
|
||||||
|
title="Remove {{ monitor.title }} from your Monitor list."
|
||||||
|
/>
|
||||||
|
</button>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
@@ -20,6 +20,15 @@
|
|||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
|
|
||||||
|
<li>
|
||||||
|
<a href="{{ path('app_monitors') }}"
|
||||||
|
class="block rounded-lg
|
||||||
|
bg-orange-500 hover:bg-opacity-80 bg-clip-padding backdrop-filter backdrop-blur-md bg-opacity-60
|
||||||
|
px-4 py-2 text-sm font-medium text-gray-50">
|
||||||
|
Monitors
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
|
||||||
<li>
|
<li>
|
||||||
<a href="{{ path('app_user_preferences') }}"
|
<a href="{{ path('app_user_preferences') }}"
|
||||||
class="block rounded-lg px-4 py-2 text-sm font-medium text-gray-50 hover:bg-gray-100 hover:text-stone-700">
|
class="block rounded-lg px-4 py-2 text-sm font-medium text-gray-50 hover:bg-gray-100 hover:text-stone-700">
|
||||||
|
|||||||
@@ -1,3 +1,8 @@
|
|||||||
<span {{ attributes }} class="py-[3px] px-[7px] bg-{{ color|default('green') }}-600 rounded-lg inline-block text-center text-xs text-white">
|
<span {{ attributes }}
|
||||||
|
class="py-[3px] px-[7px]
|
||||||
|
bg-{{ color|default('green') }}-{{ number|default(400) }}
|
||||||
|
text-{{ text|default('black') }}
|
||||||
|
rounded-lg inline-block text-center text-xs"
|
||||||
|
>
|
||||||
{{ status }}
|
{{ status }}
|
||||||
</span>
|
</span>
|
||||||
@@ -16,7 +16,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="flex flex-row gap-4">
|
<div class="flex flex-row gap-4">
|
||||||
<twig:Card title="Monitors" class="w-full">
|
<twig:Card title="Monitors" class="w-full">
|
||||||
<twig:MonitorList />
|
<twig:MonitorList :type="'active'" :isWidget="true" />
|
||||||
</twig:Card>
|
</twig:Card>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex flex-col gap-4">
|
<div class="flex flex-col gap-4">
|
||||||
|
|||||||
18
templates/monitor/index.html.twig
Normal file
18
templates/monitor/index.html.twig
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
{% extends 'base.html.twig' %}
|
||||||
|
|
||||||
|
{% block title %}Monitors — Torsearch{% endblock %}
|
||||||
|
{% block h2 %}Monitors{% endblock %}
|
||||||
|
|
||||||
|
{% block body %}
|
||||||
|
<div class="p-4">
|
||||||
|
<twig:Card title="Active Monitors">
|
||||||
|
<twig:MonitorList :type="'active'" :isWidget="false" :perPage="10"></twig:MonitorList>
|
||||||
|
</twig:Card>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="p-4">
|
||||||
|
<twig:Card title="Complete Monitors">
|
||||||
|
<twig:MonitorList :type="'complete'" :isWidget="false" :perPage="10"></twig:MonitorList>
|
||||||
|
</twig:Card>
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
||||||
Reference in New Issue
Block a user