Compare commits
323 Commits
dev-search
...
v0.25.2
| Author | SHA1 | Date | |
|---|---|---|---|
| 5e722dcbc7 | |||
| a126871af8 | |||
| 70f551cea9 | |||
| 4824c2d344 | |||
| c09c7ad030 | |||
| f610297294 | |||
| f2971eee9c | |||
| 47108af1f8 | |||
| f7163b5e00 | |||
| 31e364d691 | |||
| b42981b2a1 | |||
| accfa9c9bf | |||
| 8b50b50466 | |||
| e38498f69b | |||
| 490f341875 | |||
| b1b28864ea | |||
| 891ce81902 | |||
| b7d7025114 | |||
| 41114446d0 | |||
| 592e02484e | |||
| bd9fde94d1 | |||
| d0b2852de5 | |||
| 2fae99e24b | |||
| b74b563c56 | |||
| 04993ebb27 | |||
| db521ad9a9 | |||
| 6a7474173e | |||
| 9f38429c2a | |||
| 9fd6745125 | |||
| 60376ca0a2 | |||
| 6f1f1032f6 | |||
| c6e98eff4c | |||
| cff0d5234e | |||
| d2e7650b6c | |||
| bb6dcdef30 | |||
| b5526dc2dd | |||
| 3959696b66 | |||
| 7353806915 | |||
| 42e232bef3 | |||
| 45b484d44c | |||
| dd52a903f6 | |||
| 5729949774 | |||
| e055ed0c15 | |||
| 46d90e800c | |||
| 9fb513bfbd | |||
| 2384bb2414 | |||
| 7c8fa0c439 | |||
| 97aa8d8982 | |||
| a88720fe7e | |||
| 8a12303470 | |||
| 13b9047841 | |||
| 8c0ec98c20 | |||
| 2c9138290a | |||
| c1a6cddb8f | |||
| 64d3fbbddb | |||
| 32389cb27a | |||
| 5e48fdb978 | |||
| 5f54e48b3f | |||
| 073a37c080 | |||
| 3fe28c74a1 | |||
| 5c5fa8fde2 | |||
| 8fa06d4462 | |||
| 1fc5a8e500 | |||
| a0050e425b | |||
| 791af9c9e7 | |||
| e54bcd44d8 | |||
| 402d513147 | |||
| d2de374f57 | |||
| 9a1847a2c3 | |||
| 17f6316353 | |||
| cc366eb09f | |||
| b0425f7085 | |||
| 023b1b7844 | |||
| eafcf3fcb1 | |||
| 25f803d1dd | |||
| 98041fd20b | |||
| d29b84ec78 | |||
| ccce0303c3 | |||
| 9eaa120257 | |||
| d6cbb53da6 | |||
| bd47107399 | |||
| ac97fdd08f | |||
| 727c11e1c6 | |||
| be65e2d4e2 | |||
| 497a3a74cd | |||
| 101460cd47 | |||
| 591c9cdd2a | |||
| 2dc53c5270 | |||
| 5938d33c89 | |||
| d95ab85415 | |||
| cc39e46bfd | |||
| ba092ab3c2 | |||
| 774d6f4999 | |||
| 0b56ee937d | |||
| 7b3d57b94a | |||
| be7b610111 | |||
| 3e93a7c9c1 | |||
| bc78b83f8d | |||
| c5bcaeb1d4 | |||
| e39182ba91 | |||
| 965b747594 | |||
| 937e3c6270 | |||
| 2bb2845ead | |||
| fca189648b | |||
| 2121466322 | |||
| 1e130c3490 | |||
| 4b97faeadb | |||
| 3701e31ee0 | |||
| 210c674f25 | |||
| 175f4330f1 | |||
| 12aaf8e737 | |||
| 2e468dd9b0 | |||
| e070b95a36 | |||
| 20d397589a | |||
| 6c7a35005e | |||
| 0f16423f66 | |||
| 937313fe59 | |||
| 9b3506ab17 | |||
| 6e0eed8b4e | |||
| 38a5baa17e | |||
| 1d573c09e7 | |||
| 7989e2abd2 | |||
| df6c68aa46 | |||
| 6cd9a9b18e | |||
| b95e8f3794 | |||
| 8cc81fea19 | |||
| 15648e711b | |||
| f855aabd69 | |||
| 55a866170e | |||
| 9ab4f6cf57 | |||
| d40a4764eb | |||
| 0119830ea7 | |||
| 7ffa927d55 | |||
| 0b80779975 | |||
| 3c2092095f | |||
| a7bedae3db | |||
| 51c2a1c577 | |||
| f5ed464719 | |||
| aa31701ac8 | |||
| 781e4dcd23 | |||
| b5cd240fbd | |||
| ce5bc525dd | |||
| 63850e48fd | |||
| f9a284cb67 | |||
| 228d320edc | |||
| 6858d2d722 | |||
| 8cd004db4a | |||
| 6cc8985c4d | |||
| 9bfd92a011 | |||
| ac6276f444 | |||
| 6d4bbf2e72 | |||
| 8004de1dc8 | |||
| 2d9a2a1f14 | |||
| b6bc1645b4 | |||
| 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 | |||
| 3074b2d5f1 | |||
| 4638f3765a | |||
| 70189b95e1 | |||
| c47b1fc23f | |||
| f39e307bc4 | |||
| 3c965aa1ec | |||
| bd4ce76177 | |||
| fd0853d6f0 | |||
| 53ad80c90b | |||
| fb47cf9d6b | |||
| eac586d946 | |||
| ae0e416cdd | |||
| a64ac6b2cb | |||
| 1881247bf2 | |||
| 0337d7530a | |||
| 9b9240c35b | |||
| a568cb7c86 | |||
| daafeb79b7 | |||
| bc8c214c99 | |||
| d5f0098280 | |||
| 4cb9fd0810 | |||
| 7bc8720377 | |||
| cc4942a537 | |||
| c4f416af37 | |||
| 2c31485964 | |||
| 5d5d66bd79 | |||
| ce6fda257b | |||
| 7afc845343 | |||
| 546039aa43 | |||
| e6196ce078 | |||
| eecd5444a6 | |||
| 74506f6928 | |||
| 845a67bdd8 | |||
| 651697640c | |||
| dd48cc542f | |||
| 6c0e42d291 | |||
| e230913c89 | |||
| 8967d407cb | |||
| 217a667df2 | |||
| 4653feb123 | |||
| 6ad10a585d | |||
| eed2e70d21 | |||
| eded5a2fc8 | |||
| 8428fc6cf6 | |||
| 888a030680 | |||
| a628d85ef2 | |||
| afb62645f6 | |||
| 8aba35fee1 | |||
| 6817bd8c80 | |||
| 854177a121 | |||
| ddb71b3bb0 | |||
| 35a3e48ac9 | |||
| 6e55195e6f | |||
| e325687af5 | |||
| 4506306377 | |||
| 4287b52bd4 | |||
| 3724bcbb16 | |||
| 6c2cd7510f | |||
| 98bf8d2880 | |||
| 20ade478b1 | |||
| 4eed5fef78 | |||
| 5ff9842eaa | |||
| b93da3df1d | |||
| fe0ab2ef5a | |||
| 25ff3e726d | |||
| 527adb73c1 | |||
| 9166b4bbc8 | |||
| 5688b3a0df | |||
| babcb00440 | |||
| 993b34d668 | |||
| 73b3e5179a | |||
| d3176baff2 | |||
| cc77cccf0b | |||
| b0c10a028a | |||
| 12bf90a2b4 | |||
| 687b5ed873 | |||
| e5f0f358b7 | |||
| fd84648100 | |||
| f3285ba60c | |||
| 4f6f8f43f1 | |||
| b23d8a2ba3 | |||
| bfd5f53d67 | |||
| f10168a1a7 | |||
| 8970ca0f8f | |||
| 994bd775ea | |||
| d0e7941809 | |||
| a28f0d9369 | |||
| 964cdca151 | |||
| b59069551a | |||
| 3971cf3260 | |||
| 8a1a89f17d | |||
| c3eaf109e3 | |||
| 0225bead60 | |||
| 1b1feaebec | |||
| 7045116b56 | |||
| 883442225f | |||
| c664e9fbca | |||
| 5d257e4404 | |||
| a6dc4f0b03 | |||
| 3c6e41af94 | |||
| 3e081df01c | |||
| 3384720c09 | |||
| c32ff2e464 | |||
| 6138c94d7a | |||
| a1a38cb74c | |||
| e9ccb5ad2b | |||
| 9d350a572d | |||
| cd271b568b | |||
| 6b88483635 | |||
| 0120ddcedd | |||
| 7270fa2936 | |||
| 6a2567bf98 | |||
| c12a33de86 | |||
| 7d84e13a40 | |||
| 5c5937d01f | |||
| 35718958ee | |||
| d8e8c7f0f0 | |||
| 27164a8680 | |||
| a5c02464df | |||
| 35db1ad6fd | |||
| f9fad08a30 | |||
| f23048e813 | |||
| 6dc6fbd449 | |||
| 5402680abf | |||
| a5c827b48f |
35
.env
@@ -15,6 +15,39 @@
|
||||
# https://symfony.com/doc/current/best_practices.html#use-environment-variables-for-infrastructure-configuration
|
||||
|
||||
###> symfony/framework-bundle ###
|
||||
APP_ENV=dev
|
||||
APP_ENV=prod
|
||||
APP_SECRET=
|
||||
###< symfony/framework-bundle ###
|
||||
|
||||
###> doctrine/doctrine-bundle ###
|
||||
# Format described at https://www.doctrine-project.org/projects/doctrine-dbal/en/latest/reference/configuration.html#connecting-using-a-url
|
||||
# IMPORTANT: You MUST configure your server version, either here or in config/packages/doctrine.yaml
|
||||
#
|
||||
# DATABASE_URL="sqlite:///%kernel.project_dir%/var/data.db"
|
||||
# DATABASE_URL="mysql://app:!ChangeMe!@127.0.0.1:3306/app?serverVersion=8.0.32&charset=utf8mb4"
|
||||
# DATABASE_URL="mysql://app:!ChangeMe!@127.0.0.1:3306/app?serverVersion=10.11.2-MariaDB&charset=utf8mb4"
|
||||
# DATABASE_URL="postgresql://app:!ChangeMe!@127.0.0.1:5432/app?serverVersion=16&charset=utf8"
|
||||
|
||||
###< doctrine/doctrine-bundle ###
|
||||
MERCURE_JWT_SECRET="!ChangeThisMercureHubJWTSecretKey!"
|
||||
###> symfony/messenger ###
|
||||
# Choose one of the transports below
|
||||
# MESSENGER_TRANSPORT_DSN=amqp://guest:guest@localhost:5672/%2f/messages
|
||||
# MESSENGER_TRANSPORT_DSN=redis://localhost:6379/messages
|
||||
MESSENGER_TRANSPORT_DSN=doctrine://default?auto_setup=0
|
||||
###< symfony/messenger ###
|
||||
|
||||
REDIS_HOST=redis://redis
|
||||
|
||||
###> symfony/mailer ###
|
||||
MAILER_DSN=null://null
|
||||
###< symfony/mailer ###
|
||||
|
||||
AUTH_METHOD=form_login
|
||||
|
||||
###> drenso/symfony-oidc-bundle ###
|
||||
OIDC_WELL_KNOWN_URL="https://oidc/.well-known"
|
||||
OIDC_CLIENT_ID="Enter your OIDC client id"
|
||||
OIDC_CLIENT_SECRET="Enter your OIDC client secret"
|
||||
OIDC_BYPASS_FORM_LOGIN=false
|
||||
###< drenso/symfony-oidc-bundle ###
|
||||
|
||||
7
.env.dev
@@ -1,7 +0,0 @@
|
||||
DATABASE_URL="mysql://root:password@database:3306/app?serverVersion=10.6.19.2-MariaDB&charset=utf8mb4"
|
||||
APP_SECRET=70169beadfbc8101c393cbfbba27a313
|
||||
|
||||
DOWNLOAD_DIR=./movies
|
||||
MERCURE_URL=http://mercure/.well-known/mercure
|
||||
MERCURE_PUBLIC_URL=https://dev.caldwell.digital/hub/.well-known/mercure
|
||||
MERCURE_JWT_SECRET="!ChangeThisMercureHubJWTSecretKey!"
|
||||
6
.gitignore
vendored
@@ -1,4 +1,5 @@
|
||||
.idea
|
||||
bolt.db
|
||||
###> symfony/framework-bundle ###
|
||||
/.env.local
|
||||
/.env.local.php
|
||||
@@ -13,3 +14,8 @@
|
||||
/public/assets/
|
||||
/assets/vendor/
|
||||
###< symfony/asset-mapper ###
|
||||
|
||||
###> phpstan/phpstan ###
|
||||
phpstan.neon
|
||||
###< phpstan/phpstan ###
|
||||
.php-cs-fixer.cache
|
||||
|
||||
20
Dockerfile
@@ -1 +1,19 @@
|
||||
FROM registry.caldwell.digital/library/php:8.4-apache
|
||||
FROM dunglas/frankenphp:php8.4
|
||||
|
||||
ENV SERVER_NAME=":80"
|
||||
ENV CADDY_GLOBAL_OPTIONS="auto_https off"
|
||||
ENV APP_RUNTIME="Runtime\\FrankenPhpSymfony\\Runtime"
|
||||
ENV APP_VERSION="0.0.1"
|
||||
|
||||
RUN install-php-extensions \
|
||||
pdo_mysql \
|
||||
gd \
|
||||
intl \
|
||||
zip \
|
||||
opcache
|
||||
|
||||
RUN apt update && apt install -y wget
|
||||
|
||||
HEALTHCHECK --interval=3s --timeout=3s --retries=10 CMD [ "php", "/app/bin/console", "startup:status" ]
|
||||
|
||||
COPY --chmod=0755 docker/app/Caddyfile /etc/frankenphp/Caddyfile
|
||||
|
||||
@@ -1,6 +0,0 @@
|
||||
FROM registry.caldwell.digital/library/php:8.4-apache
|
||||
|
||||
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
|
||||
57
README.md
@@ -1,13 +1,50 @@
|
||||
# Caldwell Digital - Symfony Template
|
||||
Get up and running quickly with this Symfony framework template!
|
||||
# Torsearch
|
||||
Torsearch is a "media acquisition tool" that works strictly with Real Debrid. Torsearch makes it easy to search for
|
||||
and download your favorite movies and tv shows. You can think of it like Stremio, but without the streaming. Why the
|
||||
comparison to Stremio? That's because Torsearch uses the same source for media files that Stremio uses: Torrentio
|
||||
(hence the name: Torsearch).
|
||||
|
||||
After two failed attempts at running a media server, I decided to hang up my hat and give up my dream of a self-hosted
|
||||
media server. I figured the days of torrenting were mostly over and everybody ranting & raving about their media collections
|
||||
must be going to Walmart and buying up the bucket of old movies they have. That's until I stumbled across Stremio.
|
||||
|
||||
At first, it seemed too good to be true, but I was yearning for something just sketchy enough to try out. What could
|
||||
go wrong with handing over my card information to an unknown organization across the pond? At the end of the day,
|
||||
the cost benefit analysis landed in my favor, and about 30 minutes after purchasing my Real Debrid subscription and
|
||||
setting up Stremio, I was in business.
|
||||
|
||||
My mind was blown. I might not have the most "cultured" taste in media, but it had everything I searched for and more! After
|
||||
watching a few movies, I noticed the "Copy Download Link" button. "What's this lil guy do?" I asked myself. Duh, it
|
||||
downloads the f*****g movie. And there's the 💡flashing over my head. There's gotta be a way to automate this, I told myself.
|
||||
|
||||
After a month of studying Stremio's code and lots of tinkering, I finally figured it out. Torrentio is the magic behind
|
||||
the scenes. You feed it a Real Debrid API key and an IMDB ID, and it gives you a list of results to download that media.
|
||||
Easy peasy.
|
||||
|
||||
In about an hour I had a proof of concept working. It wasn't pretty, but it wasn't supposed to be. That proof-of-concept
|
||||
has blossomed into the beautiful Torsearch that I've been using nearly every day since then. The code in this repo
|
||||
is a complete re-write of the proof-of-concept that started out ugly and ended up even uglier. Knowing the core functionality
|
||||
required to make it work, I was able to re-write the app with some design patterns in place.
|
||||
|
||||
## Pics or didn't happen
|
||||

|
||||

|
||||

|
||||

|
||||

|
||||
|
||||
## Features
|
||||
- Search for Movies & TV Shows by their name
|
||||
- Download directly to your NAS
|
||||
- Monitor TV Shows for new episodes and automatically download them
|
||||
- Browse popular media and view its download options
|
||||
- LDAP or local auth (OIDC coming soon)
|
||||
|
||||
## Features on the roadmap
|
||||
- Requests - allow users to request new media
|
||||
- OIDC auth
|
||||
- Prometheus logging
|
||||
|
||||
## Getting Started
|
||||
1. Run `source bash/get_certs.sh` to grab the wildcard certs
|
||||
2. Set the docker image tag in the `bash/build.sh` file
|
||||
3. Set the docker image tag in the `deploy.compose.yml` file
|
||||
4. run `composer install`
|
||||
1. Clone the repo
|
||||
|
||||
## Optional steps
|
||||
1. Set phing vars
|
||||
2. Update the project name in `build.xml`
|
||||
3. Set a custom development hostname in `bash/vhost.conf`
|
||||
|
||||
@@ -6,5 +6,23 @@ import './bootstrap.js';
|
||||
* which should already be in your base.html.twig.
|
||||
*/
|
||||
import './styles/app.css';
|
||||
import PullToRefresh from 'pulltorefreshjs';
|
||||
|
||||
console.log('This log comes from assets/app.js - welcome to AssetMapper! 🎉');
|
||||
|
||||
let alert = document.querySelector('.alert');
|
||||
var observer = new MutationObserver(function(mutations) {
|
||||
if (document.contains(alert)) {
|
||||
observer.disconnect();
|
||||
}
|
||||
});
|
||||
|
||||
observer.observe(document, {attributes: false, childList: true, characterData: false, subtree:true});
|
||||
|
||||
const ptr = PullToRefresh.init({
|
||||
mainElement: 'body',
|
||||
onRefresh() {
|
||||
window.location.reload();
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
7
assets/bootstrap.js
vendored
@@ -1,5 +1,10 @@
|
||||
import { startStimulusApp } from '@symfony/stimulus-bundle';
|
||||
import Popover from '@stimulus-components/popover'
|
||||
import Dialog from '@stimulus-components/dialog'
|
||||
import Dropdown from '@stimulus-components/dropdown'
|
||||
|
||||
const app = startStimulusApp();
|
||||
// register any custom, 3rd party controllers here
|
||||
// app.register('some_controller_name', SomeImportedController);
|
||||
app.register('popover', Popover);
|
||||
app.register('dialog', Dialog);
|
||||
app.register('dropdown', Dropdown);
|
||||
|
||||
@@ -1,4 +1,53 @@
|
||||
{
|
||||
"controllers": [],
|
||||
"controllers": {
|
||||
"@spomky-labs/pwa-bundle": {
|
||||
"connection-status": {
|
||||
"enabled": true,
|
||||
"fetch": "eager"
|
||||
},
|
||||
"backgroundsync-form": {
|
||||
"enabled": true,
|
||||
"fetch": "eager"
|
||||
},
|
||||
"sync-broadcast": {
|
||||
"enabled": true,
|
||||
"fetch": "eager"
|
||||
},
|
||||
"prefetch-on-demand": {
|
||||
"enabled": true,
|
||||
"fetch": "eager"
|
||||
}
|
||||
},
|
||||
"@symfony/ux-autocomplete": {
|
||||
"autocomplete": {
|
||||
"enabled": true,
|
||||
"fetch": "eager",
|
||||
"autoimport": {
|
||||
"tom-select/dist/css/tom-select.default.css": true,
|
||||
"tom-select/dist/css/tom-select.bootstrap4.css": false,
|
||||
"tom-select/dist/css/tom-select.bootstrap5.css": false
|
||||
}
|
||||
}
|
||||
},
|
||||
"@symfony/ux-live-component": {
|
||||
"live": {
|
||||
"enabled": true,
|
||||
"fetch": "eager",
|
||||
"autoimport": {
|
||||
"@symfony/ux-live-component/dist/live.min.css": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"@symfony/ux-turbo": {
|
||||
"turbo-core": {
|
||||
"enabled": true,
|
||||
"fetch": "eager"
|
||||
},
|
||||
"mercure-turbo-stream": {
|
||||
"enabled": true,
|
||||
"fetch": "eager"
|
||||
}
|
||||
}
|
||||
},
|
||||
"entrypoints": []
|
||||
}
|
||||
|
||||
46
assets/controllers/action_button_controller.js
Normal file
@@ -0,0 +1,46 @@
|
||||
import { Controller } from '@hotwired/stimulus';
|
||||
|
||||
/*
|
||||
* The following line makes this controller "lazy": it won't be downloaded until needed
|
||||
* See https://symfony.com/bundles/StimulusBundle/current/index.html#lazy-stimulus-controllers
|
||||
*/
|
||||
|
||||
/* stimulusFetch: 'lazy' */
|
||||
export default class extends Controller {
|
||||
|
||||
initialize() {
|
||||
// Called once when the controller is first instantiated (per element)
|
||||
|
||||
// Here you can initialize variables, create scoped callables for event
|
||||
// listeners, instantiate external libraries, etc.
|
||||
// this._fooBar = this.fooBar.bind(this)
|
||||
}
|
||||
|
||||
connect() {
|
||||
// Called every time the controller is connected to the DOM
|
||||
// (on page load, when it's added to the DOM, moved in the DOM, etc.)
|
||||
|
||||
// Here you can add event listeners on the element or target elements,
|
||||
// add or remove classes, attributes, dispatch custom events, etc.
|
||||
// this.fooTarget.addEventListener('click', this._fooBar)
|
||||
}
|
||||
|
||||
// Add custom controller actions here
|
||||
// fooBar() { this.fooTarget.classList.toggle(this.bazClass) }
|
||||
|
||||
disconnect() {
|
||||
// Called anytime its element is disconnected from the DOM
|
||||
// (on page change, when it's removed from or moved in the DOM, etc.)
|
||||
|
||||
// Here you should remove all event listeners added in "connect()"
|
||||
// this.fooTarget.removeEventListener('click', this._fooBar)
|
||||
}
|
||||
|
||||
default() {
|
||||
console.log('Looks like you need to add an action to your action button...')
|
||||
}
|
||||
|
||||
monitorDispatch() {
|
||||
fetch('/api/monitor/dispatch')
|
||||
}
|
||||
}
|
||||
18
assets/controllers/alert_controller.js
Normal file
@@ -0,0 +1,18 @@
|
||||
import { Controller } from '@hotwired/stimulus';
|
||||
|
||||
export default class extends Controller {
|
||||
connect() {
|
||||
let timer = setTimeout(() => {
|
||||
this.element.remove();
|
||||
},
|
||||
"3000"
|
||||
);
|
||||
|
||||
this.element.addEventListener('mouseout', () => timer = setTimeout(() => {
|
||||
this.element.remove();
|
||||
},
|
||||
"3000"
|
||||
));
|
||||
this.element.addEventListener('mouseover', () => clearTimeout(timer));
|
||||
}
|
||||
}
|
||||
39
assets/controllers/download_button_controller.js
Normal file
@@ -0,0 +1,39 @@
|
||||
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 = {
|
||||
url: String,
|
||||
title: String,
|
||||
filename: String,
|
||||
mediaType: String,
|
||||
imdbId: String,
|
||||
episodeId: String
|
||||
}
|
||||
|
||||
download() {
|
||||
fetch('/api/download', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Accept': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({
|
||||
url: this.urlValue,
|
||||
title: this.element.dataset['title'],
|
||||
filename: this.filenameValue,
|
||||
mediaType: this.mediaTypeValue,
|
||||
imdbId: this.imdbIdValue,
|
||||
episodeId: this.episodeIdValue
|
||||
})
|
||||
})
|
||||
.then(res => res.json())
|
||||
.then(json => {
|
||||
console.log(json)
|
||||
})
|
||||
}
|
||||
}
|
||||
61
assets/controllers/download_list_controller.js
Normal file
@@ -0,0 +1,61 @@
|
||||
import { Controller } from '@hotwired/stimulus';
|
||||
import { getComponent } from '@symfony/ux-live-component';
|
||||
/*
|
||||
* The following line makes this controller "lazy": it won't be downloaded until needed
|
||||
* See https://symfony.com/bundles/StimulusBundle/current/index.html#lazy-stimulus-controllers
|
||||
*/
|
||||
|
||||
/* stimulusFetch: 'lazy' */
|
||||
export default class extends Controller {
|
||||
static targets = ['download', 'deleteFileInput']
|
||||
|
||||
async initialize() {
|
||||
this.component = await getComponent(this.element);
|
||||
}
|
||||
|
||||
connect() {
|
||||
// Called every time the controller is connected to the DOM
|
||||
// (on page load, when it's added to the DOM, moved in the DOM, etc.)
|
||||
|
||||
// Here you can add event listeners on the element or target elements,
|
||||
// add or remove classes, attributes, dispatch custom events, etc.
|
||||
// this.fooTarget.addEventListener('click', this._fooBar)
|
||||
}
|
||||
|
||||
downloadTargetConnected(target) {
|
||||
let downloads = this.element.querySelectorAll('tbody tr');
|
||||
if (downloads.length > 5) {
|
||||
target.classList.add('hidden');
|
||||
}
|
||||
}
|
||||
|
||||
pauseDownload(data) {
|
||||
fetch(`/api/download/${data.params.id}/pause`, {method: 'PATCH'})
|
||||
.then(res => res.json())
|
||||
.then(json => console.debug(json));
|
||||
}
|
||||
|
||||
resumeDownload(data) {
|
||||
fetch(`/api/download/${data.params.id}/resume`, {method: 'PATCH'})
|
||||
.then(res => res.json())
|
||||
.then(json => console.debug(json));
|
||||
}
|
||||
|
||||
deleteDownload(data) {
|
||||
const deleteFileInput = document.querySelector(`#delete_file_${data.params.id}`)
|
||||
fetch(`/api/download/${data.params.id}?deleteFile=${deleteFileInput.checked}`, {method: 'DELETE'})
|
||||
.then(res => res.json())
|
||||
.then(json => console.debug(json));
|
||||
}
|
||||
|
||||
// Add custom controller actions here
|
||||
// fooBar() { this.fooTarget.classList.toggle(this.bazClass) }
|
||||
|
||||
disconnect() {
|
||||
// Called anytime its element is disconnected from the DOM
|
||||
// (on page change, when it's removed from or moved in the DOM, etc.)
|
||||
|
||||
// Here you should remove all event listeners added in "connect()"
|
||||
// this.fooTarget.removeEventListener('click', this._fooBar)
|
||||
}
|
||||
}
|
||||
43
assets/controllers/hamburger_controller.js
Normal file
@@ -0,0 +1,43 @@
|
||||
import { Controller } from '@hotwired/stimulus';
|
||||
|
||||
/*
|
||||
* The following line makes this controller "lazy": it won't be downloaded until needed
|
||||
* See https://symfony.com/bundles/StimulusBundle/current/index.html#lazy-stimulus-controllers
|
||||
*/
|
||||
|
||||
/* stimulusFetch: 'lazy' */
|
||||
export default class extends Controller {
|
||||
static outlets = ['navbar']
|
||||
|
||||
initialize() {
|
||||
// Called once when the controller is first instantiated (per element)
|
||||
|
||||
// Here you can initialize variables, create scoped callables for event
|
||||
// listeners, instantiate external libraries, etc.
|
||||
// this._fooBar = this.fooBar.bind(this)
|
||||
}
|
||||
|
||||
connect() {
|
||||
// Called every time the controller is connected to the DOM
|
||||
// (on page load, when it's added to the DOM, moved in the DOM, etc.)
|
||||
|
||||
// Here you can add event listeners on the element or target elements,
|
||||
// add or remove classes, attributes, dispatch custom events, etc.
|
||||
// this.fooTarget.addEventListener('click', this._fooBar)
|
||||
}
|
||||
|
||||
toggleMenu() {
|
||||
this.navbarOutlet.toggle();
|
||||
}
|
||||
|
||||
// Add custom controller actions here
|
||||
// fooBar() { this.fooTarget.classList.toggle(this.bazClass) }
|
||||
|
||||
disconnect() {
|
||||
// Called anytime its element is disconnected from the DOM
|
||||
// (on page change, when it's removed from or moved in the DOM, etc.)
|
||||
|
||||
// Here you should remove all event listeners added in "connect()"
|
||||
// this.fooTarget.removeEventListener('click', this._fooBar)
|
||||
}
|
||||
}
|
||||
96
assets/controllers/monitor_button_controller.js
Normal file
@@ -0,0 +1,96 @@
|
||||
import { Controller } from '@hotwired/stimulus';
|
||||
|
||||
/*
|
||||
* The following line makes this controller "lazy": it won't be downloaded until needed
|
||||
* See https://symfony.com/bundles/StimulusBundle/current/index.html#lazy-stimulus-controllers
|
||||
*/
|
||||
|
||||
/* stimulusFetch: 'lazy' */
|
||||
export default class extends Controller {
|
||||
static targets = ['button', 'options']
|
||||
static outlets = ['result-filter', 'dialog']
|
||||
static values = {
|
||||
tmdbId: String,
|
||||
imdbId: String,
|
||||
title: String,
|
||||
}
|
||||
|
||||
initialize() {
|
||||
// Called once when the controller is first instantiated (per element)
|
||||
|
||||
// Here you can initialize variables, create scoped callables for event
|
||||
// listeners, instantiate external libraries, etc.
|
||||
// this._fooBar = this.fooBar.bind(this)
|
||||
}
|
||||
|
||||
connect() {
|
||||
// Called every time the controller is connected to the DOM
|
||||
// (on page load, when it's added to the DOM, moved in the DOM, etc.)
|
||||
|
||||
// Here you can add event listeners on the element or target elements,
|
||||
// add or remove classes, attributes, dispatch custom events, etc.
|
||||
// this.fooTarget.addEventListener('click', this._fooBar)
|
||||
}
|
||||
|
||||
// Add custom controller actions here
|
||||
// fooBar() { this.fooTarget.classList.toggle(this.bazClass) }
|
||||
|
||||
disconnect() {
|
||||
// Called anytime its element is disconnected from the DOM
|
||||
// (on page change, when it's removed from or moved in the DOM, etc.)
|
||||
|
||||
// Here you should remove all event listeners added in "connect()"
|
||||
// this.fooTarget.removeEventListener('click', this._fooBar)
|
||||
}
|
||||
|
||||
toggle() {
|
||||
this.optionsTarget.classList.toggle('hidden');
|
||||
}
|
||||
|
||||
async monitorSeries() {
|
||||
await this.makeMonitor({
|
||||
tmdbId: this.tmdbIdValue,
|
||||
imdbId: this.imdbIdValue,
|
||||
title: this.titleValue,
|
||||
monitorType: 'tvshows',
|
||||
});
|
||||
if (this.hasDialogOutlet) {
|
||||
this.dialogOutlet.close();
|
||||
}
|
||||
}
|
||||
|
||||
async monitorSeason() {
|
||||
await this.makeMonitor({
|
||||
tmdbId: this.tmdbIdValue,
|
||||
imdbId: this.imdbIdValue,
|
||||
title: this.titleValue,
|
||||
monitorType: 'tvseason',
|
||||
season: this.resultFilterOutlet.activeFilter['season'],
|
||||
});
|
||||
}
|
||||
|
||||
async monitorEpisode() {
|
||||
// ToDo: figure out how to set episode
|
||||
await this.makeMonitor({
|
||||
tmdbId: this.tmdbIdValue,
|
||||
imdbId: this.imdbIdValue,
|
||||
title: this.titleValue,
|
||||
monitorType: 'tvepisode',
|
||||
season: this.resultFilterOutlet.activeFilter['season'],
|
||||
episode: '',
|
||||
});
|
||||
}
|
||||
|
||||
async makeMonitor(body) {
|
||||
const response = await fetch('/api/monitor', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Accept': 'application/json',
|
||||
},
|
||||
body: JSON.stringify(body)
|
||||
});
|
||||
|
||||
return await response.json();
|
||||
}
|
||||
}
|
||||
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));
|
||||
}
|
||||
}
|
||||
@@ -6,27 +6,33 @@ import { Controller } from '@hotwired/stimulus';
|
||||
*/
|
||||
/* stimulusFetch: 'lazy' */
|
||||
export default class extends Controller {
|
||||
H264_CODECS = ['h264', 'h.264', 'x264']
|
||||
H265_CODECS = ['h265', 'h.265', 'x265', 'hevc']
|
||||
|
||||
static values = {
|
||||
title: String,
|
||||
tmdbId: String,
|
||||
imdbId: String
|
||||
};
|
||||
|
||||
static targets = ['list']
|
||||
static outlets = ['loading-icon']
|
||||
|
||||
options = []
|
||||
optionsLoaded = false
|
||||
resultCountEl = null
|
||||
|
||||
async connect() {
|
||||
await this.setOptions();
|
||||
this.resultCountEl = document.querySelector('#movie_results_count');
|
||||
}
|
||||
|
||||
async setOptions() {
|
||||
if (this.options.length === 0) {
|
||||
await fetch(`/torrentio/movies/${this.imdbIdValue}`)
|
||||
.then(res => res.text())
|
||||
.then(response => {
|
||||
this.element.innerHTML = response;
|
||||
this.options = this.element.querySelectorAll('tbody tr');
|
||||
});
|
||||
}
|
||||
async listTargetConnected() {
|
||||
this.optionsLoaded = true;
|
||||
this.options = this.element.querySelectorAll('tbody tr');
|
||||
this.options.forEach((option) => option.querySelector('.download-btn').dataset['title'] = this.titleValue);
|
||||
this.dispatch('optionsLoaded', {detail: {options: this.options}})
|
||||
this.loadingIconOutlet.toggleIcon();
|
||||
this.resultCountEl.innerText = this.options.length;
|
||||
}
|
||||
|
||||
// Keeps compatible with Filter & TV Shows
|
||||
@@ -34,7 +40,63 @@ export default class extends Controller {
|
||||
return true;
|
||||
}
|
||||
|
||||
listTargetConnected(target) {
|
||||
// console.log(target);
|
||||
async filter(activeFilter) {
|
||||
let firstIncluded = true;
|
||||
let count = 0;
|
||||
let selectedCount = 0;
|
||||
|
||||
this.options.forEach((option) => {
|
||||
const optionHeader = document.querySelector(`[data-option-id="${option.dataset['localId']}"]`)
|
||||
const props = {
|
||||
"resolution": option.querySelector('#resolution').textContent.trim(),
|
||||
"codec": option.querySelector('#codec').textContent.trim(),
|
||||
"provider": option.querySelector('#provider').textContent.trim(),
|
||||
"quality": option.dataset['quality'],
|
||||
"languages": JSON.parse(option.dataset['languages']),
|
||||
}
|
||||
|
||||
let include = true;
|
||||
option.classList.add('r-tablerow');
|
||||
option.classList.remove('hidden');
|
||||
optionHeader.classList.add('r-tablerow');
|
||||
optionHeader.classList.remove('hidden');
|
||||
option.querySelector('input[type="checkbox"]').checked = false;
|
||||
|
||||
for (let [key, value] of Object.entries(activeFilter)) {
|
||||
if (value === "" || key === "season") {
|
||||
continue;
|
||||
}
|
||||
if (key === "codec" && value === "h264") {
|
||||
if (!this.H264_CODECS.includes(props[key].toLowerCase())) {
|
||||
include = false;
|
||||
}
|
||||
} else if (key === "codec" && value === "h265") {
|
||||
if (!this.H265_CODECS.includes(props[key].toLowerCase())) {
|
||||
include = false;
|
||||
}
|
||||
} else if (key === "language") {
|
||||
if (!props["languages"].includes(value)) {
|
||||
include = false;
|
||||
}
|
||||
} else if (props[key] !== value) {
|
||||
include = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (false === include) {
|
||||
option.classList.remove('r-tablerow');
|
||||
option.classList.add('hidden');
|
||||
optionHeader.classList.remove('r-tablerow');
|
||||
optionHeader.classList.add('hidden');
|
||||
} else if (true === firstIncluded) {
|
||||
count = 1;
|
||||
selectedCount = selectedCount + 1;
|
||||
option.querySelector('input[type="checkbox"]').checked = true;
|
||||
firstIncluded = false;
|
||||
} else {
|
||||
count = count + 1;
|
||||
}
|
||||
});
|
||||
this.resultCountEl.innerText = count;
|
||||
}
|
||||
}
|
||||
|
||||
30
assets/controllers/navbar_controller.js
Normal file
@@ -0,0 +1,30 @@
|
||||
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 {
|
||||
inactiveStyles = "block rounded-lg px-4 py-2 text-sm font-medium text-gray-50 hover:bg-orange-500 hover:bg-opacity-80";
|
||||
activeStyles = "block rounded-lg bg-orange-500 hover:bg-opacity-70 bg-clip-padding backdrop-filter backdrop-blur-md bg-opacity-80 px-4 py-2 text-sm font-medium text-gray-50";
|
||||
|
||||
connect() {
|
||||
this.element.querySelectorAll('.nav-list a:not(.nav-foot)').forEach(link => {
|
||||
link.className = this.inactiveStyles;
|
||||
if (window.location.pathname === (new URL(link.href)).pathname || window.location.pathname.startsWith( (new URL(link.href)).pathname + '/' ) ) {
|
||||
link.className = this.activeStyles;
|
||||
}
|
||||
});
|
||||
|
||||
window.addEventListener("resize", (event) => {
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
toggle() {
|
||||
this.element.parentElement.classList.toggle('hidden');
|
||||
this.element.classList.toggle('fixed');
|
||||
this.element.classList.toggle('z-20');
|
||||
}
|
||||
}
|
||||
@@ -11,6 +11,7 @@ export default class extends Controller {
|
||||
|
||||
languages = []
|
||||
providers = []
|
||||
qualities = []
|
||||
seasons = []
|
||||
|
||||
activeFilter = {
|
||||
@@ -18,36 +19,33 @@ export default class extends Controller {
|
||||
"codec": "",
|
||||
"language": "",
|
||||
"provider": "",
|
||||
"quality": "",
|
||||
}
|
||||
|
||||
static outlets = ['movie-results', 'tv-results']
|
||||
static targets = ['resolution', 'codec', 'language', 'provider', 'season']
|
||||
static outlets = ['movie-results', 'tv-results', 'tv-episode-list']
|
||||
static targets = ['resolution', 'codec', 'language', 'provider', 'season', 'quality', 'selectAll', 'downloadSelected']
|
||||
static values = {
|
||||
'imdbId': String,
|
||||
'media-type': String,
|
||||
'episodes': Array,
|
||||
'reverseMappedQualities': Object,
|
||||
}
|
||||
|
||||
connect() {
|
||||
async connect() {
|
||||
if (this.mediaTypeValue === "tvshows") {
|
||||
this.activeFilter['season'] = 1;}
|
||||
}
|
||||
|
||||
async movieResultsOutletConnected(outlet) {
|
||||
await this.parseDownloadOptionForFilter(outlet)
|
||||
}
|
||||
|
||||
async tvResultsOutletConnected(outlet) {
|
||||
await this.parseDownloadOptionForFilter(outlet)
|
||||
}
|
||||
|
||||
async parseDownloadOptionForFilter(outlet) {
|
||||
if (outlet.options.length === 0) {
|
||||
await outlet.setOptions();
|
||||
this.activeFilter['season'] = 1;
|
||||
}
|
||||
outlet.options.forEach((option) => {
|
||||
await this.filter();
|
||||
}
|
||||
|
||||
// Event is fired from movies/tvshows controllers to populate this data
|
||||
async loadOptions({detail: { options }}) {
|
||||
await options.forEach((option) => {
|
||||
this.addLanguages(option, option.dataset);
|
||||
this.addProviders(option, option.dataset);
|
||||
this.addQualities(option, option.dataset);
|
||||
})
|
||||
await this.filter();
|
||||
}
|
||||
|
||||
addLanguages(option, props) {
|
||||
@@ -58,9 +56,22 @@ export default class extends Controller {
|
||||
}
|
||||
});
|
||||
|
||||
this.languageTarget.innerHTML = '<option value="">n/a</option>';
|
||||
const preferred = this.languageTarget.dataset.preferred;
|
||||
if (preferred) {
|
||||
this.languageTarget.innerHTML = '<option value="'+preferred+'" selected>'+preferred+'</option>';
|
||||
this.languageTarget.innerHTML += '<option value="">n/a</option>';
|
||||
} else {
|
||||
this.languageTarget.innerHTML = '<option value="">n/a</option>';
|
||||
}
|
||||
|
||||
this.languageTarget.innerHTML += this.languages.sort()
|
||||
.map((language) => '<option value="'+language+'">'+language+'</option>')
|
||||
.map((language) => {
|
||||
const preferred = this.languageTarget.dataset.preferred;
|
||||
if (preferred === language) {
|
||||
return;
|
||||
}
|
||||
return '<option value="'+language+'">'+language+'</option>';
|
||||
})
|
||||
.join();
|
||||
}
|
||||
|
||||
@@ -69,14 +80,54 @@ export default class extends Controller {
|
||||
this.providers.push(props['provider']);
|
||||
}
|
||||
|
||||
this.providerTarget.innerHTML = '<option value="">n/a</option>';
|
||||
const preferred = this.providerTarget.dataset.preferred;
|
||||
if (preferred) {
|
||||
this.providerTarget.innerHTML = '<option value="'+preferred+'" selected>'+preferred+'</option>';
|
||||
this.providerTarget.innerHTML += '<option value="">n/a</option>';
|
||||
} else {
|
||||
this.providerTarget.innerHTML = '<option value="">n/a</option>';
|
||||
}
|
||||
|
||||
this.providerTarget.innerHTML += this.providers.sort()
|
||||
.map((provider) => '<option value="'+provider+'">'+provider+'</option>')
|
||||
.map((provider) => {
|
||||
const preferred = this.languageTarget.dataset.preferred;
|
||||
if (preferred === provider) {
|
||||
return;
|
||||
}
|
||||
return '<option value="' + provider + '">' + provider + '</option>'
|
||||
})
|
||||
.join();
|
||||
|
||||
}
|
||||
|
||||
addQualities(option, props) {
|
||||
if (!this.qualities.includes(props['quality'])) {
|
||||
if (props['quality'].toLowerCase() in this.reverseMappedQualitiesValue) {
|
||||
this.qualities.push(props['quality']);
|
||||
}
|
||||
}
|
||||
|
||||
const preferred = this.qualityTarget.dataset.preferred;
|
||||
if (preferred) {
|
||||
this.qualityTarget.innerHTML = '<option value="'+preferred+'" selected>'+preferred+'</option>';
|
||||
this.qualityTarget.innerHTML += '<option value="">n/a</option>';
|
||||
} else {
|
||||
this.qualityTarget.innerHTML = '<option value="">n/a</option>';
|
||||
}
|
||||
|
||||
this.qualityTarget.innerHTML += this.qualities.sort()
|
||||
.map((quality) => {
|
||||
const preferred = this.qualityTarget.dataset.preferred;
|
||||
if (preferred === quality) {
|
||||
return;
|
||||
}
|
||||
return '<option value="' + quality + '">' + quality + '</option>'
|
||||
})
|
||||
.join();
|
||||
}
|
||||
|
||||
async filter() {
|
||||
const downloadSeasonSpan = document.querySelector("#downloadSeasonModal");
|
||||
const currentSeason = this.activeFilter['season'];
|
||||
|
||||
let results = [];
|
||||
@@ -85,81 +136,51 @@ export default class extends Controller {
|
||||
"codec": this.codecTarget.value,
|
||||
"language": this.languageTarget.value,
|
||||
"provider": this.providerTarget.value,
|
||||
"quality": this.qualityTarget.value,
|
||||
}
|
||||
|
||||
if ("movies" === this.mediaTypeValue) {
|
||||
results = this.movieResultsOutlets;
|
||||
await results.forEach((list) => list.filter(this.activeFilter));
|
||||
|
||||
} else if ("tvshows" === this.mediaTypeValue) {
|
||||
results = this.tvResultsOutlets;
|
||||
this.activeFilter.season = this.seasonTarget.value;
|
||||
downloadSeasonSpan.innerText = this.activeFilter.season;
|
||||
await results.forEach((list) => list.filter(this.activeFilter, currentSeason, this.seasonTarget.value));
|
||||
}
|
||||
}
|
||||
|
||||
const filterOperation = async (resultList, currentSeason) => {
|
||||
if ("tvshows" === this.mediaTypeValue && currentSeason !== this.activeFilter['season']) {
|
||||
if (resultList.seasonValue === this.seasonTarget.value) {
|
||||
await resultList.setActive();
|
||||
} else {
|
||||
resultList.setInActive();
|
||||
}
|
||||
setSeason(event) {
|
||||
this.tvEpisodeListOutlet.setSeason(event.target.value);
|
||||
}
|
||||
|
||||
uncheckSelectAllBtn() {
|
||||
this.selectAllTarget.checked = false;
|
||||
}
|
||||
|
||||
downloadSeason() {
|
||||
fetch(`/api/download/season/${this.imdbIdValue}/${this.activeFilter['season']}`, {
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
if (false === resultList.isActive()) {
|
||||
return;
|
||||
selectAllEpisodes() {
|
||||
this.tvResultsOutlets.forEach((episode) => {
|
||||
if (episode.isActive()) {
|
||||
episode.selectEpisodeForDownload()
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
let firstIncluded = true;
|
||||
let count = 0;
|
||||
let selectedCount = 0;
|
||||
|
||||
resultList.options.forEach((option) => {
|
||||
const props = {
|
||||
"resolution": option.querySelector('#resolution').textContent.trim(),
|
||||
"codec": option.querySelector('#codec').textContent.trim(),
|
||||
"provider": option.querySelector('#provider').textContent.trim(),
|
||||
"languages": JSON.parse(option.dataset['languages']),
|
||||
}
|
||||
|
||||
let include = true;
|
||||
option.classList.remove('hidden');
|
||||
|
||||
for (let [key, value] of Object.entries(this.activeFilter)) {
|
||||
if (value === "" || key === "season") {
|
||||
continue;
|
||||
}
|
||||
if (key === "codec" && value === "h264") {
|
||||
if (!this.H264_CODECS.includes(props[key].toLowerCase())) {
|
||||
include = false;
|
||||
}
|
||||
} else if (key === "codec" && value === "h265") {
|
||||
if (!this.H265_CODECS.includes(props[key].toLowerCase())) {
|
||||
include = false;
|
||||
}
|
||||
} else if (key === "language") {
|
||||
if (!props["languages"].includes(value)) {
|
||||
include = false;
|
||||
}
|
||||
} else if (props[key] !== value) {
|
||||
include = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (false === include) {
|
||||
option.classList.add('hidden');
|
||||
} else if (true === firstIncluded) {
|
||||
count = 1;
|
||||
selectedCount = selectedCount + 1;
|
||||
// option.selectInput.checked = true;
|
||||
firstIncluded = false;
|
||||
} else {
|
||||
count = count + 1;
|
||||
}
|
||||
|
||||
if ("tvshows" === this.mediaTypeValue) {
|
||||
resultList.countTarget.innerText = count;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
await results.forEach((list) => filterOperation(list, currentSeason));
|
||||
downloadSelectedEpisodes() {
|
||||
this.tvResultsOutlets.forEach(episode => {
|
||||
if (episode.isActive() && episode.isSelected()) {
|
||||
episode.download();
|
||||
}
|
||||
});
|
||||
this.selectAllTarget.checked = false;
|
||||
}
|
||||
}
|
||||
|
||||
59
assets/controllers/search_bar_controller.js
Normal file
@@ -0,0 +1,59 @@
|
||||
import { Controller } from '@hotwired/stimulus';
|
||||
|
||||
export default class extends Controller {
|
||||
initialize() {
|
||||
this._onPreConnect = this._onPreConnect.bind(this);
|
||||
this._onConnect = this._onConnect.bind(this);
|
||||
}
|
||||
|
||||
connect() {
|
||||
document.querySelector("#search").onsubmit = (event) => {
|
||||
event.preventDefault();
|
||||
const autocompleteController = this.application.getControllerForElementAndIdentifier(this.element, 'symfony--ux-autocomplete--autocomplete')
|
||||
window.location.href = `/search?term=${autocompleteController.tomSelect.lastValue}`
|
||||
}
|
||||
this.element.addEventListener('autocomplete:pre-connect', this._onPreConnect);
|
||||
this.element.addEventListener('autocomplete:connect', this._onConnect);
|
||||
}
|
||||
|
||||
disconnect() {
|
||||
// You should always remove listeners when the controller is disconnected to avoid side-effects
|
||||
this.element.removeEventListener('autocomplete:connect', this._onConnect);
|
||||
this.element.removeEventListener('autocomplete:pre-connect', this._onPreConnect);
|
||||
}
|
||||
|
||||
_onPreConnect(event) {
|
||||
// TomSelect has not been initialized - options can be changed
|
||||
// console.log(event.detail); // Options that will be used to initialize TomSelect
|
||||
event.detail.options.onItemAdd = (value, $item) => {
|
||||
const params = value.split('|')
|
||||
window.location.href = `/result/${params[0]}/${params[1]}`
|
||||
};
|
||||
event.detail.options.render.loading = (data, escape) => {
|
||||
return `
|
||||
<span data-controller="loading-icon" data-loading-icon-total-value="52" data-loading-icon-count-value="20" class="loading-icon">
|
||||
<svg viewBox="0 0 24 24" fill="currentColor" height="20" width="20" data-loading-icon-target="icon" class="text-end" aria-hidden="true"><path fill="none" stroke="currentColor" stroke-linecap="round" stroke-width="2" d="M12 6.99998C9.1747 6.99987 6.99997 9.24998 7 12C7.00003 14.55 9.02119 17 12 17C14.7712 17 17 14.75 17 12"><animateTransform attributeName="transform" attributeType="XML" dur="560ms" from="0,12,12" repeatCount="indefinite" to="360,12,12" type="rotate"></animateTransform></path></svg>
|
||||
</span>
|
||||
`;
|
||||
}
|
||||
event.detail.options.render.option = (data, escape) => {
|
||||
if (data.data.description.length > 60) {
|
||||
data.data.description = data.data.description.substring(0, 107) + "...";
|
||||
}
|
||||
return `<div class="flex flex-row">
|
||||
<img src="${data.data.poster}" class="w-16 rounded-md">
|
||||
<div class="p-2 flex flex-col">
|
||||
<h2>${data.data.title}</h2>
|
||||
<p class="max-w-[60ch] text-wrap">${data.data.description}</p>
|
||||
</div>
|
||||
</div>
|
||||
`
|
||||
}
|
||||
}
|
||||
|
||||
_onConnect(event) {
|
||||
// TomSelect has just been initialized and you can access details from the event
|
||||
// console.log(event.detail.tomSelect); // TomSelect instance
|
||||
// console.log(event.detail.options); // Options used to initialize TomSelect
|
||||
}
|
||||
}
|
||||
51
assets/controllers/tv_episode_list_controller.js
Normal file
@@ -0,0 +1,51 @@
|
||||
import { Controller } from '@hotwired/stimulus';
|
||||
import { getComponent } from '@symfony/ux-live-component';
|
||||
|
||||
/*
|
||||
* The following line makes this controller "lazy": it won't be downloaded until needed
|
||||
* See https://symfony.com/bundles/StimulusBundle/current/index.html#lazy-stimulus-controllers
|
||||
*/
|
||||
|
||||
/* stimulusFetch: 'lazy' */
|
||||
export default class extends Controller {
|
||||
|
||||
async initialize() {
|
||||
this.component = await getComponent(this.element);
|
||||
this.component.on('render:finished', (component) => {
|
||||
console.log(component);
|
||||
});
|
||||
}
|
||||
|
||||
setSeason(season) {
|
||||
this.element.querySelectorAll(".episode-container").forEach(element => element.remove());
|
||||
this.component.set('pageNumber', 1);
|
||||
this.component.set('season', parseInt(season));
|
||||
this.component.render();
|
||||
}
|
||||
|
||||
paginate(event) {
|
||||
this.element.querySelectorAll(".episode-container").forEach(element => element.remove());
|
||||
this.component.action('paginate', {page: event.params.page});
|
||||
this.component.render();
|
||||
}
|
||||
|
||||
connect() {
|
||||
// Called every time the controller is connected to the DOM
|
||||
// (on page load, when it's added to the DOM, moved in the DOM, etc.)
|
||||
|
||||
// Here you can add event listeners on the element or target elements,
|
||||
// add or remove classes, attributes, dispatch custom events, etc.
|
||||
// this.fooTarget.addEventListener('click', this._fooBar)
|
||||
}
|
||||
|
||||
// Add custom controller actions here
|
||||
// fooBar() { this.fooTarget.classList.toggle(this.bazClass) }
|
||||
|
||||
disconnect() {
|
||||
// Called anytime its element is disconnected from the DOM
|
||||
// (on page change, when it's removed from or moved in the DOM, etc.)
|
||||
|
||||
// Here you should remove all event listeners added in "connect()"
|
||||
// this.fooTarget.removeEventListener('click', this._fooBar)
|
||||
}
|
||||
}
|
||||
@@ -6,7 +6,11 @@ import { Controller } from '@hotwired/stimulus';
|
||||
*/
|
||||
/* stimulusFetch: 'lazy' */
|
||||
export default class extends Controller {
|
||||
H264_CODECS = ['h264', 'h.264', 'x264']
|
||||
H265_CODECS = ['h265', 'h.265', 'x265', 'hevc']
|
||||
|
||||
static values = {
|
||||
title: String,
|
||||
tmdbId: String,
|
||||
imdbId: String,
|
||||
season: String,
|
||||
@@ -14,47 +18,149 @@ export default class extends Controller {
|
||||
active: Boolean,
|
||||
};
|
||||
|
||||
static targets = ['list', 'count']
|
||||
static targets = ['list', 'count', 'episodeSelector', 'toggleButton', 'listContainer']
|
||||
static outlets = ['loading-icon']
|
||||
|
||||
options = []
|
||||
optionsLoaded = false
|
||||
isOpen = false
|
||||
|
||||
async connect() {
|
||||
await this.setOptions();
|
||||
}
|
||||
|
||||
async setOptions() {
|
||||
if (true === this.activeValue) {
|
||||
await fetch(`/torrentio/tvshows/${this.tmdbIdValue}/${this.imdbIdValue}/${this.seasonValue}/${this.episodeValue}`)
|
||||
.then(res => res.text())
|
||||
.then(response => {
|
||||
this.element.innerHTML = response;
|
||||
this.options = this.element.querySelectorAll('tbody tr');
|
||||
this.optionsLoaded = true;
|
||||
this.loadingIconOutlet.increaseCount();
|
||||
});
|
||||
async listTargetConnected() {
|
||||
this.options = this.element.querySelectorAll('tbody tr');
|
||||
if (this.options.length > 0) {
|
||||
this.options.forEach((option) =>
|
||||
option.querySelector('.download-btn').dataset['title'] = this.titleValue
|
||||
);
|
||||
this.options[0].querySelector('input[type="checkbox"]').checked = true;
|
||||
this.dispatch('optionsLoaded', {detail: {options: this.options}})
|
||||
this.loadingIconOutlet.increaseCount();
|
||||
} else {
|
||||
this.countTarget.innerText = 0;
|
||||
this.episodeSelectorTarget.disabled = true;
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// async clearCache() {
|
||||
// await fetch(`/torrentio/tvshows/clear/${this.tmdbIdValue}/${this.imdbIdValue}/${this.seasonValue}/${this.episodeValue}`)
|
||||
// .then(res => res.text())
|
||||
// .then(response => {});
|
||||
// }
|
||||
|
||||
async setActive() {
|
||||
this.activeValue = true;
|
||||
this.element.classList.remove('hidden');
|
||||
if (false === this.optionsLoaded) {
|
||||
await this.setOptions();
|
||||
}
|
||||
}
|
||||
|
||||
setInActive() {
|
||||
this.activeValue = false;
|
||||
this.element.classList.add('hidden');
|
||||
async setInActive() {
|
||||
this.episodeSelectorTarget.checked = false;
|
||||
}
|
||||
|
||||
isActive() {
|
||||
return this.activeValue;
|
||||
}
|
||||
|
||||
isSelected() {
|
||||
return this.episodeSelectorTarget.checked;
|
||||
}
|
||||
|
||||
selectEpisodeForDownload() {
|
||||
if (true === this.isActive() && this.episodeSelectorTarget.disabled === false) {
|
||||
this.episodeSelectorTarget.checked = !this.episodeSelectorTarget.checked;
|
||||
}
|
||||
}
|
||||
|
||||
toggleList() {
|
||||
this.listTarget.classList.toggle('options-table');
|
||||
this.listTarget.classList.toggle('hidden');
|
||||
this.toggleButtonTarget.classList.toggle('rotate-90');
|
||||
this.toggleButtonTarget.classList.toggle('-rotate-90');
|
||||
}
|
||||
|
||||
download() {
|
||||
this.options.forEach(option => {
|
||||
const optionSelector = option.querySelector('input[type="checkbox"]');
|
||||
if (true === optionSelector.checked) {
|
||||
const downloadBtn = option.querySelector('button.download-btn');
|
||||
const downloadBtnController = this.application.getControllerForElementAndIdentifier(downloadBtn, 'download-button');
|
||||
downloadBtnController.download();
|
||||
optionSelector.checked = false;
|
||||
this.episodeSelectorTarget.checked = false;
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
async filter(activeFilter, currentSeason, newSeason) {
|
||||
if (currentSeason !== activeFilter['season']) {
|
||||
if (this.seasonValue === newSeason) {
|
||||
await this.setActive();
|
||||
} else {
|
||||
await this.setInActive();
|
||||
}
|
||||
}
|
||||
|
||||
if (false === this.isActive()) {
|
||||
return;
|
||||
}
|
||||
|
||||
let firstIncluded = true;
|
||||
let count = 0;
|
||||
let selectedCount = 0;
|
||||
|
||||
this.options.forEach((option) => {
|
||||
const optionHeader = document.querySelector(`[data-option-id="${option.dataset['localId']}"]`)
|
||||
const props = {
|
||||
"resolution": option.querySelector('#resolution').textContent.trim(),
|
||||
"codec": option.querySelector('#codec').textContent.trim(),
|
||||
"provider": option.querySelector('#provider').textContent.trim(),
|
||||
"languages": JSON.parse(option.dataset['languages']),
|
||||
"quality": option.dataset['quality'],
|
||||
}
|
||||
|
||||
let include = true;
|
||||
option.classList.add('r-tablerow');
|
||||
option.classList.remove('hidden');
|
||||
optionHeader.classList.add('r-tablerow');
|
||||
optionHeader.classList.remove('hidden');
|
||||
option.querySelector('input[type="checkbox"]').checked = false;
|
||||
|
||||
for (let [key, value] of Object.entries(activeFilter)) {
|
||||
if (value === "" || key === "season") {
|
||||
continue;
|
||||
}
|
||||
if (key === "codec" && value === "h264") {
|
||||
if (!this.H264_CODECS.includes(props[key].toLowerCase())) {
|
||||
include = false;
|
||||
}
|
||||
} else if (key === "codec" && value === "h265") {
|
||||
if (!this.H265_CODECS.includes(props[key].toLowerCase())) {
|
||||
include = false;
|
||||
}
|
||||
} else if (key === "language") {
|
||||
if (!props["languages"].includes(value)) {
|
||||
include = false;
|
||||
}
|
||||
} else if (props[key] !== value) {
|
||||
include = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (false === include) {
|
||||
option.classList.remove('r-tablerow');
|
||||
option.classList.add('hidden');
|
||||
optionHeader.classList.remove('r-tablerow');
|
||||
optionHeader.classList.add('hidden');
|
||||
} else if (true === firstIncluded) {
|
||||
count = 1;
|
||||
selectedCount = selectedCount + 1;
|
||||
option.querySelector('input[type="checkbox"]').checked = true;
|
||||
firstIncluded = false;
|
||||
} else {
|
||||
count = count + 1;
|
||||
}
|
||||
|
||||
this.countTarget.innerText = count;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
1
assets/icons/hugeicons/loading-01.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="https://www.w3.org/2000/svg" viewBox="0 0 24 24"><g fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" color="currentColor"><path d="M17.201 2H6.8c-1.458 0-2.737.985-2.795 2.404c-.074 1.785 1.182 2.97 2.5 4.083c1.825 1.54 2.737 2.31 2.832 3.284q.023.229 0 .458c-.095.975-1.007 1.744-2.832 3.284c-1.355 1.143-2.578 2.207-2.5 4.083C4.062 21.016 5.34 22 6.799 22H17.2c1.458 0 2.737-.985 2.796-2.404c.046-1.13-.373-2.254-1.262-3.036c-.405-.357-.826-.698-1.24-1.047c-1.824-1.54-2.736-2.31-2.831-3.284a2.3 2.3 0 0 1 0-.458c.095-.975 1.008-1.744 2.832-3.284c1.34-1.131 2.577-2.229 2.5-4.083C19.939 2.984 18.66 2 17.202 2"/><path d="M9 21.638c0-.442 0-.663.088-.856a1 1 0 0 1 .046-.09c.107-.183.288-.312.65-.571c1.006-.719 1.51-1.078 2.081-1.116q.135-.009.27 0c.572.038 1.075.397 2.08 1.116c.363.259.544.388.651.571q.026.045.046.09c.088.193.088.414.088.856V22H9z"/></g></svg>
|
||||
|
After Width: | Height: | Size: 928 B |
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
assets/icons/icon-park-twotone/pause-one.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="https://www.w3.org/2000/svg" viewBox="0 0 48 48"><defs><mask id="ipTPauseOne0"><g fill="none" stroke="#fff" stroke-linejoin="round" stroke-width="4"><path fill="#555" d="M24 44c11.046 0 20-8.954 20-20S35.046 4 24 4S4 12.954 4 24s8.954 20 20 20Z"/><path stroke-linecap="round" d="M19 18v12m10-12v12"/></g></mask></defs><path fill="currentColor" d="M0 0h48v48H0z" mask="url(#ipTPauseOne0)"/></svg>
|
||||
|
After Width: | Height: | Size: 407 B |
1
assets/icons/icon-park-twotone/play.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="https://www.w3.org/2000/svg" viewBox="0 0 48 48"><defs><mask id="ipTPlay0"><g fill="#555" stroke="#fff" stroke-linejoin="round" stroke-width="4"><path d="M24 44c11.046 0 20-8.954 20-20S35.046 4 24 4S4 12.954 4 24s8.954 20 20 20Z"/><path d="M20 24v-6.928l6 3.464L32 24l-6 3.464l-6 3.464z"/></g></mask></defs><path fill="currentColor" d="M0 0h48v48H0z" mask="url(#ipTPlay0)"/></svg>
|
||||
|
After Width: | Height: | Size: 392 B |
1
assets/icons/line-md/circle-twotone.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="https://www.w3.org/2000/svg" viewBox="0 0 24 24"><path fill="currentColor" fill-opacity="0" stroke="currentColor" stroke-dasharray="64" stroke-dashoffset="64" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 12c0 -4.97 4.03 -9 9 -9c4.97 0 9 4.03 9 9c0 4.97 -4.03 9 -9 9c-4.97 0 -9 -4.03 -9 -9Z"><animate fill="freeze" attributeName="fill-opacity" begin="0.6s" dur="0.15s" values="0;0.3"/><animate fill="freeze" attributeName="stroke-dashoffset" dur="0.6s" values="64;0"/></path></svg>
|
||||
|
After Width: | Height: | Size: 517 B |
1
assets/icons/material-symbols/logout.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="https://www.w3.org/2000/svg" viewBox="0 0 24 24"><path fill="currentColor" d="M5 21q-.825 0-1.412-.587T3 19V5q0-.825.588-1.412T5 3h7v2H5v14h7v2zm11-4l-1.375-1.45l2.55-2.55H9v-2h8.175l-2.55-2.55L16 7l5 5z"/></svg>
|
||||
|
After Width: | Height: | Size: 224 B |
1
assets/icons/pajamas/hamburger.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="https://www.w3.org/2000/svg" viewBox="0 0 0 0"><path fill="currentColor" fill-rule="evenodd" d="M0 3.75A.75.75 0 0 1 .75 3h14.5a.75.75 0 0 1 0 1.5H.75A.75.75 0 0 1 0 3.75M0 8a.75.75 0 0 1 .75-.75h14.5a.75.75 0 0 1 0 1.5H.75A.75.75 0 0 1 0 8m.75 3.5a.75.75 0 0 0 0 1.5h14.5a.75.75 0 0 0 0-1.5z" clip-rule="evenodd"/></svg>
|
||||
|
After Width: | Height: | Size: 333 B |
1
assets/icons/ri/user-line.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="https://www.w3.org/2000/svg" viewBox="0 0 24 24"><path fill="currentColor" d="M4 22a8 8 0 1 1 16 0h-2a6 6 0 0 0-12 0zm8-9c-3.315 0-6-2.685-6-6s2.685-6 6-6s6 2.685 6 6s-2.685 6-6 6m0-2c2.21 0 4-1.79 4-4s-1.79-4-4-4s-4 1.79-4 4s1.79 4 4 4"/></svg>
|
||||
|
After Width: | Height: | Size: 257 B |
@@ -10,4 +10,123 @@
|
||||
h2 {
|
||||
font-size: var(--text-xl);
|
||||
}
|
||||
.rounded-ms {
|
||||
border-radius: 0.275rem;
|
||||
}
|
||||
}
|
||||
|
||||
@layer components {
|
||||
.alert {
|
||||
@apply text-white text-sm min-w-[250px] border px-4 py-3 rounded-md
|
||||
}
|
||||
.alert-success {
|
||||
@apply bg-green-950 hover:bg-green-900 border-green-500
|
||||
}
|
||||
.alert-warning {
|
||||
@apply bg-yellow-500 hover:bg-yellow-600 border-yellow-400 text-black
|
||||
}
|
||||
}
|
||||
|
||||
/* Prevent scrolling while dialog is open */
|
||||
body:has(dialog[data-dialog-target="dialog"][open]) {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
/* Customize the dialog backdrop */
|
||||
dialog {
|
||||
box-shadow: 0 0 0 100vw rgb(0 0 0 / 0.5);
|
||||
}
|
||||
|
||||
@keyframes fade-in {
|
||||
0% {
|
||||
opacity: 0;
|
||||
}
|
||||
100% {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes fade-out {
|
||||
0% {
|
||||
opacity: 1;
|
||||
}
|
||||
100% {
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
|
||||
/* Add animations */
|
||||
dialog[data-dialog-target="dialog"][open] {
|
||||
animation: fade-in 200ms forwards;
|
||||
}
|
||||
|
||||
dialog[data-dialog-target="dialog"][closing] {
|
||||
animation: fade-out 200ms forwards;
|
||||
}
|
||||
|
||||
.text-input {
|
||||
@apply bg-gray-50 text-gray-50 p-1 bg-transparent border-b-2 border-orange-400
|
||||
}
|
||||
|
||||
.submit-button {
|
||||
@apply bg-green-600/40 px-1.5 py-1 w-full rounded-md text-gray-50 backdrop-filter backdrop-blur-sm border-2 border-green-500 hover:bg-green-700/40
|
||||
}
|
||||
|
||||
.r-tablecell {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.r-tablerow {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
@media screen and (min-width: 768px) {
|
||||
.r-tablecell {
|
||||
display: table-cell;
|
||||
}
|
||||
|
||||
.r-tablerow {
|
||||
display: table-row;
|
||||
}
|
||||
}
|
||||
|
||||
.options-table {
|
||||
display: flex;
|
||||
|
||||
:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (min-width: 768px) {
|
||||
.options-table {
|
||||
display: inline-table;
|
||||
}
|
||||
}
|
||||
|
||||
#search .ts-wrapper.single .ts-control::after {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
#search .ts-control {
|
||||
background: transparent !important;
|
||||
border: none !important;
|
||||
box-shadow: none !important;
|
||||
color: #fff !important;
|
||||
padding-left: 0;
|
||||
|
||||
input {
|
||||
color: #fff !important;
|
||||
padding: 0;
|
||||
}
|
||||
}
|
||||
|
||||
#search .ts-dropdown {
|
||||
background: unset;
|
||||
@apply bg-orange-500/80 backdrop-filter backdrop-blur-md text-white border border-orange-500 rounded-md
|
||||
}
|
||||
|
||||
#search .ts-dropdown .ts-dropdown-content .option.active {
|
||||
background: unset;
|
||||
@apply bg-orange-500/80 text-black font-bold rounded-md
|
||||
}
|
||||
|
||||
@@ -1,2 +1,3 @@
|
||||
echo "> Building ${IMG} for linux/amd64"
|
||||
docker buildx build --platform linux/amd64 -f Dockerfile.prod -t ${IMG} .
|
||||
docker buildx build --platform linux/amd64 -f Dockerfile.app -t "${IMG}-app" .
|
||||
docker buildx build --platform linux/amd64 -f Dockerfile.worker -t "${IMG}-worker" .
|
||||
|
||||
@@ -1,2 +1,3 @@
|
||||
echo "> Building ${IMG} for linux/arm/v8"
|
||||
docker buildx build --platform linux/arm/v8 -f Dockerfile.prod -t ${IMG} .
|
||||
docker buildx build --platform linux/arm/v8 -f Dockerfile.app -t "${IMG}-app" .
|
||||
docker buildx build --platform linux/arm/v8 -f Dockerfile.app -t "${IMG}-worker" .
|
||||
@@ -1,2 +1,3 @@
|
||||
echo "> Pushing ${IMG}"
|
||||
docker push ${IMG}
|
||||
docker push "${IMG}-app"
|
||||
docker push "${IMG}-worker"
|
||||
@@ -1,6 +1,11 @@
|
||||
dev.caldwell.digital:443
|
||||
{
|
||||
log {
|
||||
level DEBUG
|
||||
}
|
||||
}
|
||||
|
||||
tls /etc/ssl/wildcard.crt /etc/ssl/wildcard.pem
|
||||
|
||||
reverse_proxy php:80
|
||||
dev.caldwell.digital:443 {
|
||||
tls /etc/ssl/wildcard.crt /etc/ssl/wildcard.pem
|
||||
|
||||
reverse_proxy app:80
|
||||
}
|
||||
|
||||
@@ -1,18 +0,0 @@
|
||||
<VirtualHost *:80>
|
||||
ServerName localhost
|
||||
|
||||
DocumentRoot /var/www/public
|
||||
DirectoryIndex /index.php
|
||||
|
||||
<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>
|
||||
36
build.xml
@@ -1,28 +1,44 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project name="Caldwell Digital Symfony Template" default="build">
|
||||
<project name="Torsearch" default="build">
|
||||
<!-- build dev for dev envs -->
|
||||
<target name="build" depends="setEnv,composer,compileAssets" />
|
||||
<target name="build" depends="setEnv,composer,compileAssets,migrateDb,clearCache" />
|
||||
|
||||
<target name="composer" description="Run composer">
|
||||
<exec executable="composer">
|
||||
<arg value="install" />
|
||||
<arg value="--ignore-platform-reqs" />
|
||||
</exec>
|
||||
</target>
|
||||
|
||||
<target name="setEnv" description="Set the database configuration">
|
||||
<copy file="${project.basedir}/.env.properties" tofile="${project.basedir}/.env.local" overwrite="true">
|
||||
</copy>
|
||||
</target>
|
||||
|
||||
<target name="compileAssets" description="Run composer">
|
||||
<exec executable="php">
|
||||
<arg value="bin/console" />
|
||||
<arg value="tailwind:build" />
|
||||
</exec>
|
||||
<exec executable="php">
|
||||
<arg value="bin/console" />
|
||||
<arg value="asset-map:compile" />
|
||||
</exec>
|
||||
</target>
|
||||
|
||||
<target name="setEnv" description="Set the database configuration">
|
||||
<copy file="${project.basedir}/.env.dist" tofile="${project.basedir}/.env.local" overwrite="true">
|
||||
<filterchain>
|
||||
<replacetokens begintoken="%%" endtoken="%%">
|
||||
<token key="db_url" value="${DATABASE_URL}" />
|
||||
</replacetokens>
|
||||
</filterchain>
|
||||
</copy>
|
||||
<target name="migrateDb" description="Migrate the database">
|
||||
<exec executable="php">
|
||||
<arg value="bin/console" />
|
||||
<arg value="--no-interaction" />
|
||||
<arg value="doctrine:migrations:migrate" />
|
||||
</exec>
|
||||
</target>
|
||||
|
||||
<target name="clearCache" description="Clear the application cache">
|
||||
<exec executable="php">
|
||||
<arg value="bin/console" />
|
||||
<arg value="cache:pool:clear" />
|
||||
<arg value="cache.app" />
|
||||
</exec>
|
||||
</target>
|
||||
</project>
|
||||
|
||||
63
compose.yml
@@ -2,6 +2,7 @@ services:
|
||||
caddy:
|
||||
image: caddy:2.9.1
|
||||
restart: unless-stopped
|
||||
tty: true
|
||||
cap_add:
|
||||
- NET_ADMIN
|
||||
ports:
|
||||
@@ -12,10 +13,57 @@ services:
|
||||
- $PWD/bash/caddy:/etc/caddy
|
||||
- $PWD/bash/certs:/etc/ssl
|
||||
|
||||
php:
|
||||
|
||||
app:
|
||||
build: .
|
||||
restart: unless-stopped
|
||||
volumes:
|
||||
- ./:/var/www
|
||||
- $PWD:/app
|
||||
- $PWD/var/download:/var/download
|
||||
- mercure_data:/data
|
||||
- mercure_config:/config
|
||||
tty: true
|
||||
environment:
|
||||
TZ: America/Chicago
|
||||
MERCURE_PUBLISHER_JWT_KEY: '!ChangeThisMercureHubJWTSecretKey!'
|
||||
MERCURE_SUBSCRIBER_JWT_KEY: '!ChangeThisMercureHubJWTSecretKey!'
|
||||
depends_on:
|
||||
database:
|
||||
condition: service_healthy
|
||||
|
||||
|
||||
worker:
|
||||
build: .
|
||||
restart: unless-stopped
|
||||
volumes:
|
||||
- $PWD:/app
|
||||
- $PWD/var/download:/var/download
|
||||
tty: true
|
||||
environment:
|
||||
TZ: America/Chicago
|
||||
command: php /app/bin/console messenger:consume async -vv --time-limit=3600
|
||||
|
||||
|
||||
scheduler:
|
||||
build: .
|
||||
restart: unless-stopped
|
||||
volumes:
|
||||
- $PWD:/app
|
||||
environment:
|
||||
TZ: America/Chicago
|
||||
command: php /app/bin/console messenger:consume scheduler_monitor -vv
|
||||
tty: true
|
||||
|
||||
|
||||
redis:
|
||||
image: redis:latest
|
||||
volumes:
|
||||
- redis_data:/data
|
||||
command: redis-server --maxmemory 512MB
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
TZ: America/Chicago
|
||||
|
||||
|
||||
database:
|
||||
image: mariadb:10.11.2
|
||||
@@ -24,15 +72,26 @@ services:
|
||||
volumes:
|
||||
- mysql:/var/lib/mysql
|
||||
environment:
|
||||
TZ: America/Chicago
|
||||
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
|
||||
|
||||
|
||||
adminer:
|
||||
image: adminer
|
||||
ports:
|
||||
- "8081:8080"
|
||||
|
||||
|
||||
volumes:
|
||||
mysql:
|
||||
mercure_data:
|
||||
mercure_config:
|
||||
redis_data:
|
||||
|
||||
@@ -4,29 +4,61 @@
|
||||
"minimum-stability": "stable",
|
||||
"prefer-stable": true,
|
||||
"require": {
|
||||
"php": ">=8.2",
|
||||
"php": ">=8.4",
|
||||
"ext-ctype": "*",
|
||||
"ext-iconv": "*",
|
||||
"1tomany/rich-bundle": "^1.8",
|
||||
"aimeos/map": "^3.12",
|
||||
"chrisullyott/php-filesize": "^4.2",
|
||||
"doctrine/dbal": "^3",
|
||||
"doctrine/doctrine-bundle": "^2.14",
|
||||
"doctrine/doctrine-fixtures-bundle": "^4.1",
|
||||
"doctrine/doctrine-migrations-bundle": "^3.4",
|
||||
"doctrine/orm": "^3.3",
|
||||
"dragonmantank/cron-expression": "^3.4",
|
||||
"drenso/symfony-oidc-bundle": "^4.2",
|
||||
"guzzlehttp/guzzle": "^7.9",
|
||||
"league/pipeline": "^1.1",
|
||||
"nesbot/carbon": "^3.9",
|
||||
"nihilarr/parse-torrent-name": "^0.0.1",
|
||||
"nyholm/psr7": "*",
|
||||
"p3k/emoji-detector": "^1.2",
|
||||
"php-http/cache-plugin": "^2.0",
|
||||
"php-tmdb/api": "^4.1",
|
||||
"symfony/asset": "7.2.*",
|
||||
"symfony/console": "7.2.*",
|
||||
"symfony/dotenv": "7.2.*",
|
||||
"predis/predis": "^2.4",
|
||||
"runtime/frankenphp-symfony": "^0.2.0",
|
||||
"spomky-labs/pwa-bundle": "^1.2",
|
||||
"stof/doctrine-extensions-bundle": "^1.14",
|
||||
"symfony/asset": "7.3.*",
|
||||
"symfony/console": "7.3.*",
|
||||
"symfony/doctrine-messenger": "7.3.*",
|
||||
"symfony/dotenv": "7.3.*",
|
||||
"symfony/filesystem": "7.3.*",
|
||||
"symfony/finder": "7.3.*",
|
||||
"symfony/flex": "^2",
|
||||
"symfony/framework-bundle": "7.2.*",
|
||||
"symfony/runtime": "7.2.*",
|
||||
"symfony/security-bundle": "7.2.*",
|
||||
"symfony/form": "7.3.*",
|
||||
"symfony/framework-bundle": "7.3.*",
|
||||
"symfony/http-client": "7.3.*",
|
||||
"symfony/ldap": "7.3.*",
|
||||
"symfony/mailer": "7.3.*",
|
||||
"symfony/mercure-bundle": "^0.3.9",
|
||||
"symfony/messenger": "7.3.*",
|
||||
"symfony/runtime": "7.3.*",
|
||||
"symfony/scheduler": "7.3.*",
|
||||
"symfony/security-bundle": "7.3.*",
|
||||
"symfony/stimulus-bundle": "^2.24",
|
||||
"symfony/twig-bundle": "7.2.*",
|
||||
"symfony/twig-bundle": "7.3.*",
|
||||
"symfony/ux-autocomplete": "^2.27",
|
||||
"symfony/ux-icons": "^2.24",
|
||||
"symfony/ux-live-component": "^2.24",
|
||||
"symfony/ux-turbo": "^2.24",
|
||||
"symfony/ux-twig-component": "^2.24",
|
||||
"symfony/yaml": "7.2.*",
|
||||
"symfony/yaml": "7.3.*",
|
||||
"symfonycasts/reset-password-bundle": "^1.23",
|
||||
"symfonycasts/tailwind-bundle": "^0.10.0",
|
||||
"twig/extra-bundle": "^2.12|^3.0",
|
||||
"twig/twig": "^2.12|^3.0"
|
||||
"twig/twig": "^2.12|^3.0",
|
||||
"web-token/jwt-library": "^4.0"
|
||||
},
|
||||
"config": {
|
||||
"allow-plugins": {
|
||||
@@ -34,6 +66,9 @@
|
||||
"symfony/flex": true,
|
||||
"symfony/runtime": true
|
||||
},
|
||||
"platform": {
|
||||
"php": "8.4"
|
||||
},
|
||||
"bump-after-update": true,
|
||||
"sort-packages": true
|
||||
},
|
||||
@@ -69,7 +104,7 @@
|
||||
"post-update-cmd": [
|
||||
"@auto-scripts"
|
||||
],
|
||||
"sym": "docker compose exec php ./bin/console"
|
||||
"sym": "docker compose exec app ./bin/console"
|
||||
},
|
||||
"conflict": {
|
||||
"symfony/symfony": "*"
|
||||
@@ -77,10 +112,13 @@
|
||||
"extra": {
|
||||
"symfony": {
|
||||
"allow-contrib": false,
|
||||
"require": "7.2.*"
|
||||
"require": "7.3.*"
|
||||
}
|
||||
},
|
||||
"require-dev": {
|
||||
"symfony/maker-bundle": "^1.62"
|
||||
"phpstan/phpstan": "^2.1",
|
||||
"symfony/maker-bundle": "^1.62",
|
||||
"symfony/stopwatch": "7.3.*",
|
||||
"symfony/web-profiler-bundle": "7.3.*"
|
||||
}
|
||||
}
|
||||
|
||||
6452
composer.lock
generated
@@ -11,4 +11,16 @@ return [
|
||||
OneToMany\RichBundle\RichBundle::class => ['all' => true],
|
||||
Symfony\Bundle\SecurityBundle\SecurityBundle::class => ['all' => true],
|
||||
Symfony\UX\StimulusBundle\StimulusBundle::class => ['all' => true],
|
||||
Doctrine\Bundle\DoctrineBundle\DoctrineBundle::class => ['all' => true],
|
||||
Doctrine\Bundle\MigrationsBundle\DoctrineMigrationsBundle::class => ['all' => true],
|
||||
Symfony\UX\LiveComponent\LiveComponentBundle::class => ['all' => true],
|
||||
Symfony\Bundle\MercureBundle\MercureBundle::class => ['all' => true],
|
||||
Symfony\UX\Turbo\TurboBundle::class => ['all' => true],
|
||||
Symfony\Bundle\WebProfilerBundle\WebProfilerBundle::class => ['dev' => true, 'test' => true],
|
||||
Doctrine\Bundle\FixturesBundle\DoctrineFixturesBundle::class => ['dev' => true, 'test' => true],
|
||||
Stof\DoctrineExtensionsBundle\StofDoctrineExtensionsBundle::class => ['all' => true],
|
||||
Symfony\UX\Autocomplete\AutocompleteBundle::class => ['all' => true],
|
||||
SymfonyCasts\Bundle\ResetPassword\SymfonyCastsResetPasswordBundle::class => ['all' => true],
|
||||
Drenso\OidcBundle\DrensoOidcBundle::class => ['all' => true],
|
||||
SpomkyLabs\PwaBundle\SpomkyLabsPwaBundle::class => ['all' => true],
|
||||
];
|
||||
|
||||
57
config/dist/ldap.security.yaml
vendored
Normal file
@@ -0,0 +1,57 @@
|
||||
security:
|
||||
# https://symfony.com/doc/current/security.html#registering-the-user-hashing-passwords
|
||||
password_hashers:
|
||||
Symfony\Component\Security\Core\User\PasswordAuthenticatedUserInterface: 'auto'
|
||||
# https://symfony.com/doc/current/security.html#loading-the-user-the-user-provider
|
||||
providers:
|
||||
users_in_memory: { memory: null }
|
||||
app_local:
|
||||
entity:
|
||||
class: App\User\Framework\Entity\User
|
||||
property: email
|
||||
|
||||
app_ldap:
|
||||
id: App\User\Framework\Security\LdapUserProvider
|
||||
|
||||
firewalls:
|
||||
dev:
|
||||
pattern: ^/(_(profiler|wdt)|css|images|js)/
|
||||
security: false
|
||||
main:
|
||||
lazy: true
|
||||
provider: app_ldap
|
||||
form_login_ldap:
|
||||
login_path: app_login
|
||||
check_path: app_login
|
||||
enable_csrf: true
|
||||
service: Symfony\Component\Ldap\Ldap
|
||||
dn_string: '%env(LDAP_DN_STRING)%'
|
||||
logout:
|
||||
path: app_logout
|
||||
|
||||
# activate different ways to authenticate
|
||||
# https://symfony.com/doc/current/security.html#the-firewall
|
||||
|
||||
# https://symfony.com/doc/current/security/impersonating_user.html
|
||||
# switch_user: true
|
||||
|
||||
# Easy way to control access for large sections of your site
|
||||
# Note: Only the *first* access control that matches will be used
|
||||
access_control:
|
||||
- { path: ^/getting-started, roles: PUBLIC_ACCESS }
|
||||
- { path: ^/register, roles: PUBLIC_ACCESS }
|
||||
- { path: ^/login, roles: PUBLIC_ACCESS }
|
||||
- { path: ^/, roles: ROLE_USER } # Or ROLE_ADMIN, ROLE_SUPER_ADMIN,
|
||||
|
||||
when@test:
|
||||
security:
|
||||
password_hashers:
|
||||
# By default, password hashers are resource intensive and take time. This is
|
||||
# important to generate secure password hashes. In tests however, secure hashes
|
||||
# are not important, waste resources and increase test times. The following
|
||||
# reduces the work factor to the lowest possible values.
|
||||
Symfony\Component\Security\Core\User\PasswordAuthenticatedUserInterface:
|
||||
algorithm: auto
|
||||
cost: 4 # Lowest possible value for bcrypt
|
||||
time_cost: 3 # Lowest possible value for argon
|
||||
memory_cost: 10 # Lowest possible value for argon
|
||||
55
config/dist/local.security.yaml
vendored
Normal file
@@ -0,0 +1,55 @@
|
||||
security:
|
||||
# https://symfony.com/doc/current/security.html#registering-the-user-hashing-passwords
|
||||
password_hashers:
|
||||
Symfony\Component\Security\Core\User\PasswordAuthenticatedUserInterface: 'auto'
|
||||
# https://symfony.com/doc/current/security.html#loading-the-user-the-user-provider
|
||||
providers:
|
||||
users_in_memory: { memory: null }
|
||||
app_local:
|
||||
entity:
|
||||
class: App\User\Framework\Entity\User
|
||||
property: email
|
||||
|
||||
app_ldap:
|
||||
id: App\User\Framework\Security\LdapUserProvider
|
||||
|
||||
firewalls:
|
||||
dev:
|
||||
pattern: ^/(_(profiler|wdt)|css|images|js)/
|
||||
security: false
|
||||
main:
|
||||
lazy: true
|
||||
provider: app_local
|
||||
form_login:
|
||||
login_path: app_login
|
||||
check_path: app_login
|
||||
enable_csrf: true
|
||||
logout:
|
||||
path: app_logout
|
||||
|
||||
# activate different ways to authenticate
|
||||
# https://symfony.com/doc/current/security.html#the-firewall
|
||||
|
||||
# https://symfony.com/doc/current/security/impersonating_user.html
|
||||
# switch_user: true
|
||||
|
||||
# Easy way to control access for large sections of your site
|
||||
# Note: Only the *first* access control that matches will be used
|
||||
access_control:
|
||||
- { path: ^/getting-started, roles: PUBLIC_ACCESS }
|
||||
- { path: ^/register, roles: PUBLIC_ACCESS }
|
||||
- { path: ^/login, roles: PUBLIC_ACCESS }
|
||||
- { path: ^/, roles: ROLE_USER } # Or ROLE_ADMIN, ROLE_SUPER_ADMIN,
|
||||
|
||||
when@test:
|
||||
security:
|
||||
password_hashers:
|
||||
# By default, password hashers are resource intensive and take time. This is
|
||||
# important to generate secure password hashes. In tests however, secure hashes
|
||||
# are not important, waste resources and increase test times. The following
|
||||
# reduces the work factor to the lowest possible values.
|
||||
Symfony\Component\Security\Core\User\PasswordAuthenticatedUserInterface:
|
||||
algorithm: auto
|
||||
cost: 4 # Lowest possible value for bcrypt
|
||||
time_cost: 3 # Lowest possible value for argon
|
||||
memory_cost: 10 # Lowest possible value for argon
|
||||
@@ -8,8 +8,8 @@ framework:
|
||||
# Other options include:
|
||||
|
||||
# Redis
|
||||
#app: cache.adapter.redis
|
||||
#default_redis_provider: redis://localhost
|
||||
app: cache.adapter.redis
|
||||
default_redis_provider: '%app.cache.redis.host%'
|
||||
|
||||
# APCu (not recommended with heavy random-write workloads as memory fragmentation can cause perf issues)
|
||||
#app: cache.adapter.apcu
|
||||
|
||||
11
config/packages/csrf.yaml
Normal file
@@ -0,0 +1,11 @@
|
||||
# Enable stateless CSRF protection for forms and logins/logouts
|
||||
framework:
|
||||
form:
|
||||
csrf_protection:
|
||||
token_id: submit
|
||||
|
||||
csrf_protection:
|
||||
stateless_token_ids:
|
||||
- submit
|
||||
- authenticate
|
||||
- logout
|
||||
72
config/packages/doctrine.yaml
Normal file
@@ -0,0 +1,72 @@
|
||||
doctrine:
|
||||
dbal:
|
||||
url: '%env(resolve:DATABASE_URL)%'
|
||||
|
||||
# IMPORTANT: You MUST configure your server version,
|
||||
# either here or in the DATABASE_URL env var (see .env file)
|
||||
#server_version: '16'
|
||||
|
||||
profiling_collect_backtrace: '%kernel.debug%'
|
||||
use_savepoints: true
|
||||
orm:
|
||||
auto_generate_proxy_classes: true
|
||||
enable_lazy_ghost_objects: true
|
||||
report_fields_where_declared: true
|
||||
validate_xml_mapping: true
|
||||
naming_strategy: doctrine.orm.naming_strategy.underscore_number_aware
|
||||
identity_generation_preferences:
|
||||
Doctrine\DBAL\Platforms\PostgreSQLPlatform: identity
|
||||
auto_mapping: true
|
||||
mappings:
|
||||
# App:
|
||||
# type: attribute
|
||||
# is_bundle: false
|
||||
# dir: '%kernel.project_dir%/src/Entity'
|
||||
# prefix: 'App\Entity'
|
||||
# alias: App
|
||||
Download:
|
||||
type: attribute
|
||||
is_bundle: false
|
||||
dir: '%kernel.project_dir%/src/Download/Framework/Entity'
|
||||
prefix: 'App\Download\Framework\Entity'
|
||||
alias: Download
|
||||
User:
|
||||
type: attribute
|
||||
is_bundle: false
|
||||
dir: '%kernel.project_dir%/src/User/Framework/Entity'
|
||||
prefix: 'App\User\Framework\Entity'
|
||||
alias: User
|
||||
Monitor:
|
||||
type: attribute
|
||||
is_bundle: false
|
||||
dir: '%kernel.project_dir%/src/Monitor/Framework/Entity'
|
||||
prefix: 'App\Monitor\Framework\Entity'
|
||||
alias: Download
|
||||
controller_resolver:
|
||||
auto_mapping: false
|
||||
|
||||
when@test:
|
||||
doctrine:
|
||||
dbal:
|
||||
# "TEST_TOKEN" is typically set by ParaTest
|
||||
dbname_suffix: '_test%env(default::TEST_TOKEN)%'
|
||||
|
||||
when@prod:
|
||||
doctrine:
|
||||
orm:
|
||||
auto_generate_proxy_classes: false
|
||||
proxy_dir: '%kernel.build_dir%/doctrine/orm/Proxies'
|
||||
query_cache_driver:
|
||||
type: pool
|
||||
pool: doctrine.system_cache_pool
|
||||
result_cache_driver:
|
||||
type: pool
|
||||
pool: doctrine.result_cache_pool
|
||||
|
||||
framework:
|
||||
cache:
|
||||
pools:
|
||||
doctrine.result_cache_pool:
|
||||
adapter: cache.app
|
||||
doctrine.system_cache_pool:
|
||||
adapter: cache.system
|
||||
6
config/packages/doctrine_migrations.yaml
Normal file
@@ -0,0 +1,6 @@
|
||||
doctrine_migrations:
|
||||
migrations_paths:
|
||||
# namespace is arbitrary but should be different from App\Migrations
|
||||
# as migrations classes should NOT be autoloaded
|
||||
'DoctrineMigrations': '%kernel.project_dir%/migrations'
|
||||
enable_profiler: false
|
||||
19
config/packages/drenso_oidc.yaml
Normal file
@@ -0,0 +1,19 @@
|
||||
drenso_oidc:
|
||||
#default_client: default # The default client, will be aliased to OidcClientInterface
|
||||
clients:
|
||||
default: # The client name, each client will be aliased to its name (for example, $defaultOidcClient)
|
||||
# Required OIDC client configuration
|
||||
well_known_url: '%env(OIDC_WELL_KNOWN_URL)%'
|
||||
client_id: '%env(OIDC_CLIENT_ID)%'
|
||||
client_secret: '%env(OIDC_CLIENT_SECRET)%'
|
||||
redirect_route: '/login/oidc/auth'
|
||||
|
||||
# Extra configuration options
|
||||
#redirect_route: '/login_check'
|
||||
#custom_client_headers: []
|
||||
|
||||
# Add any extra client
|
||||
#link: # Will be accessible using $linkOidcClient
|
||||
#well_known_url: '%env(LINK_WELL_KNOWN_URL)%'
|
||||
#client_id: '%env(LINK_CLIENT_ID)%'
|
||||
#client_secret: '%env(LINK_CLIENT_SECRET)%'
|
||||
@@ -2,8 +2,16 @@
|
||||
framework:
|
||||
secret: '%env(APP_SECRET)%'
|
||||
|
||||
# Note that the session will be started ONLY if you read or write from it.
|
||||
session: true
|
||||
serializer:
|
||||
default_context:
|
||||
enable_max_depth: true
|
||||
|
||||
trusted_proxies: 'private_ranges'
|
||||
# trust *all* "X-Forwarded-*" headers
|
||||
trusted_headers: [ 'x-forwarded-for', 'x-forwarded-host', 'x-forwarded-proto', 'x-forwarded-port', 'x-forwarded-prefix' ]
|
||||
|
||||
session:
|
||||
handler_id: '%env(REDIS_HOST)%'
|
||||
|
||||
#esi: true
|
||||
#fragments: true
|
||||
|
||||
7
config/packages/mailer.yaml
Normal file
@@ -0,0 +1,7 @@
|
||||
framework:
|
||||
mailer:
|
||||
dsn: 'smtp://%env(SMTP_USER)%:%env(SMTP_PASS)%@%env(SMTP_HOST)%:%env(SMTP_PORT)%'
|
||||
envelope:
|
||||
sender: '%env(SMTP_FROM)%'
|
||||
headers:
|
||||
From: '%env(SMTP_FROM_NAME)% <%env(SMTP_FROM)%>'
|
||||
8
config/packages/mercure.yaml
Normal file
@@ -0,0 +1,8 @@
|
||||
mercure:
|
||||
hubs:
|
||||
default:
|
||||
url: '%app.mercure.url%'
|
||||
public_url: '%app.mercure.public_url%'
|
||||
jwt:
|
||||
secret: '%env(MERCURE_JWT_SECRET)%'
|
||||
publish: '*'
|
||||
53
config/packages/messenger.yaml
Normal file
@@ -0,0 +1,53 @@
|
||||
framework:
|
||||
messenger:
|
||||
# Uncomment this (and the failed transport below) to send failed messages to this transport for later handling.
|
||||
failure_transport: failed
|
||||
|
||||
transports:
|
||||
# https://symfony.com/doc/current/messenger.html#transport-configuration
|
||||
async:
|
||||
dsn: '%env(MESSENGER_TRANSPORT_DSN)%'
|
||||
options:
|
||||
use_notify: true
|
||||
check_delayed_interval: 60000
|
||||
queue_name: default
|
||||
retry_strategy:
|
||||
max_retries: 1
|
||||
multiplier: 1
|
||||
|
||||
media_cache:
|
||||
dsn: '%env(MESSENGER_TRANSPORT_DSN)%'
|
||||
options:
|
||||
use_notify: true
|
||||
check_delayed_interval: 60000
|
||||
queue_name: media_cache
|
||||
retry_strategy:
|
||||
max_retries: 1
|
||||
multiplier: 1
|
||||
|
||||
|
||||
failed: 'doctrine://default?queue_name=failed'
|
||||
|
||||
default_bus: messenger.bus.default
|
||||
|
||||
buses:
|
||||
messenger.bus.default: []
|
||||
|
||||
routing:
|
||||
# Route your messages to the transports
|
||||
# 'App\Message\YourMessage': async
|
||||
'App\Download\Action\Command\DownloadMediaCommand': async
|
||||
'App\Download\Action\Command\DownloadSeasonCommand': async
|
||||
'App\Monitor\Action\Command\MonitorTvEpisodeCommand': async
|
||||
'App\Monitor\Action\Command\MonitorTvSeasonCommand': async
|
||||
'App\Monitor\Action\Command\MonitorTvShowCommand': async
|
||||
'App\Monitor\Action\Command\MonitorMovieCommand': async
|
||||
'App\Torrentio\Action\Command\GetTvShowOptionsCommand': media_cache
|
||||
|
||||
# when@test:
|
||||
# framework:
|
||||
# messenger:
|
||||
# transports:
|
||||
# # replace with your transport name here (e.g., my_transport: 'in-memory://')
|
||||
# # For more Messenger testing tools, see https://github.com/zenstruck/messenger-test
|
||||
# async: 'in-memory://'
|
||||
3
config/packages/property_info.yaml
Normal file
@@ -0,0 +1,3 @@
|
||||
framework:
|
||||
property_info:
|
||||
with_constructor_extractor: true
|
||||
21
config/packages/pwa.yaml
Normal file
@@ -0,0 +1,21 @@
|
||||
pwa:
|
||||
manifest:
|
||||
enabled: true
|
||||
name: "Torsearch"
|
||||
short_name: "torsearch"
|
||||
start_url: "/"
|
||||
display: "standalone"
|
||||
id: "/"
|
||||
background_color: "#f98e44"
|
||||
theme_color: "#083344"
|
||||
description: Torsearch provides a simple and intuitive way to manage your personal media library.
|
||||
icons:
|
||||
- src: "icon.png"
|
||||
sizes: [ 192 ]
|
||||
- src: "icon.png"
|
||||
sizes: [ 192 ]
|
||||
purpose: maskable
|
||||
categories:
|
||||
- entertainment
|
||||
- multimedia
|
||||
- utilities
|
||||
2
config/packages/reset_password.yaml
Normal file
@@ -0,0 +1,2 @@
|
||||
symfonycasts_reset_password:
|
||||
request_password_repository: App\User\Framework\Repository\ResetPasswordRequestRepository
|
||||
@@ -5,13 +5,36 @@ security:
|
||||
# https://symfony.com/doc/current/security.html#loading-the-user-the-user-provider
|
||||
providers:
|
||||
users_in_memory: { memory: null }
|
||||
app_local:
|
||||
entity:
|
||||
class: App\User\Framework\Entity\User
|
||||
property: email
|
||||
|
||||
app_oidc:
|
||||
id: App\User\Framework\Security\OidcUserProvider
|
||||
|
||||
app_ldap:
|
||||
id: App\User\Framework\Security\LdapUserProvider
|
||||
|
||||
firewalls:
|
||||
dev:
|
||||
pattern: ^/(_(profiler|wdt)|css|images|js)/
|
||||
security: false
|
||||
main:
|
||||
lazy: true
|
||||
provider: users_in_memory
|
||||
logout:
|
||||
path: /logout
|
||||
provider: app_oidc
|
||||
form_login:
|
||||
login_path: app_login
|
||||
check_path: app_login
|
||||
enable_csrf: true
|
||||
oidc:
|
||||
login_path: '/login/oidc'
|
||||
check_path: '/login/oidc/auth'
|
||||
enable_end_session_listener: true
|
||||
http_basic:
|
||||
realm: Secured Area
|
||||
entry_point: form_login
|
||||
|
||||
# activate different ways to authenticate
|
||||
# https://symfony.com/doc/current/security.html#the-firewall
|
||||
@@ -22,8 +45,11 @@ security:
|
||||
# Easy way to control access for large sections of your site
|
||||
# Note: Only the *first* access control that matches will be used
|
||||
access_control:
|
||||
# - { path: ^/admin, roles: ROLE_ADMIN }
|
||||
# - { path: ^/profile, roles: ROLE_USER }
|
||||
- { path: ^/reset-password, roles: PUBLIC_ACCESS }
|
||||
- { path: ^/getting-started, roles: PUBLIC_ACCESS }
|
||||
- { path: ^/register, roles: PUBLIC_ACCESS }
|
||||
- { path: ^/login, roles: PUBLIC_ACCESS }
|
||||
- { path: ^/, roles: ROLE_USER } # Or ROLE_ADMIN, ROLE_SUPER_ADMIN,
|
||||
|
||||
when@test:
|
||||
security:
|
||||
|
||||
7
config/packages/stof_doctrine_extensions.yaml
Normal file
@@ -0,0 +1,7 @@
|
||||
# Read the documentation: https://symfony.com/doc/current/bundles/StofDoctrineExtensionsBundle/index.html
|
||||
# See the official DoctrineExtensions documentation for more details: https://github.com/doctrine-extensions/DoctrineExtensions/tree/main/doc
|
||||
stof_doctrine_extensions:
|
||||
default_locale: en_US
|
||||
orm:
|
||||
default:
|
||||
timestampable: true
|
||||
7
config/packages/translation.yaml
Normal file
@@ -0,0 +1,7 @@
|
||||
framework:
|
||||
default_locale: en
|
||||
translator:
|
||||
default_path: '%kernel.project_dir%/translations'
|
||||
fallbacks:
|
||||
- en
|
||||
providers:
|
||||
@@ -1,5 +1,10 @@
|
||||
twig:
|
||||
globals:
|
||||
version: '%app.version%'
|
||||
file_name_pattern: '*.twig'
|
||||
date:
|
||||
format: 'm/d/Y'
|
||||
timezone: '%env(default:app.default.timezone:TZ)%'
|
||||
|
||||
when@test:
|
||||
twig:
|
||||
|
||||
11
config/packages/web_profiler.yaml
Normal file
@@ -0,0 +1,11 @@
|
||||
when@dev:
|
||||
web_profiler:
|
||||
toolbar: true
|
||||
|
||||
framework:
|
||||
profiler:
|
||||
collect_serializer_data: true
|
||||
|
||||
when@test:
|
||||
framework:
|
||||
profiler: { collect: false }
|
||||
@@ -1,8 +1,63 @@
|
||||
controllersIndex:
|
||||
controllersBase:
|
||||
resource:
|
||||
path: ../src/Controller/
|
||||
namespace: App\Controller
|
||||
path: ../src/Base/Framework/Controller/
|
||||
namespace: App\Base\Framework\Controller
|
||||
type: attribute
|
||||
defaults:
|
||||
schemes: [ https ]
|
||||
schemes: [ 'https' ]
|
||||
|
||||
controllersLibrary:
|
||||
resource:
|
||||
path: ../src/Library/Framework/Controller/
|
||||
namespace: App\Library\Framework\Controller
|
||||
type: attribute
|
||||
defaults:
|
||||
schemes: [ 'https' ]
|
||||
|
||||
controllersSearch:
|
||||
resource:
|
||||
path: ../src/Search/Framework/Controller/
|
||||
namespace: App\Search\Framework\Controller
|
||||
type: attribute
|
||||
defaults:
|
||||
schemes: [ 'https' ]
|
||||
|
||||
controllersUser:
|
||||
resource:
|
||||
path: ../src/User/Framework/Controller
|
||||
namespace: App\User\Framework\Controller
|
||||
type: attribute
|
||||
defaults:
|
||||
schemes: ['https']
|
||||
|
||||
controllersDownload:
|
||||
resource:
|
||||
path: ../src/Download/Framework/Controller
|
||||
namespace: App\Download\Framework\Controller
|
||||
type: attribute
|
||||
defaults:
|
||||
schemes: [ 'https' ]
|
||||
|
||||
controllersMonitor:
|
||||
resource:
|
||||
path: ../src/Monitor/Framework/Controller
|
||||
namespace: App\Monitor\Framework\Controller
|
||||
type: attribute
|
||||
defaults:
|
||||
schemes: ['https']
|
||||
|
||||
controllersTorrentio:
|
||||
resource:
|
||||
path: ../src/Torrentio/Framework/Controller
|
||||
namespace: App\Torrentio\Framework\Controller
|
||||
type: attribute
|
||||
defaults:
|
||||
schemes: ['https']
|
||||
|
||||
controllersTmdb:
|
||||
resource:
|
||||
path: ../src/Tmdb/Framework/Controller
|
||||
namespace: App\Tmdb\Framework\Controller
|
||||
type: attribute
|
||||
defaults:
|
||||
schemes: ['https']
|
||||
|
||||
3
config/routes/ux_autocomplete.yaml
Normal file
@@ -0,0 +1,3 @@
|
||||
ux_autocomplete:
|
||||
resource: '@AutocompleteBundle/config/routes.php'
|
||||
prefix: '/autocomplete'
|
||||
5
config/routes/ux_live_component.yaml
Normal file
@@ -0,0 +1,5 @@
|
||||
live_component:
|
||||
resource: '@LiveComponentBundle/config/routes.php'
|
||||
prefix: '/_components'
|
||||
# adjust prefix to add localization to your components
|
||||
#prefix: '/{_locale}/_components'
|
||||
8
config/routes/web_profiler.yaml
Normal file
@@ -0,0 +1,8 @@
|
||||
when@dev:
|
||||
web_profiler_wdt:
|
||||
resource: '@WebProfilerBundle/Resources/config/routing/wdt.xml'
|
||||
prefix: /_wdt
|
||||
|
||||
web_profiler_profiler:
|
||||
resource: '@WebProfilerBundle/Resources/config/routing/profiler.xml'
|
||||
prefix: /_profiler
|
||||
@@ -4,6 +4,45 @@
|
||||
# Put parameters here that don't need to change on each machine where the app is deployed
|
||||
# https://symfony.com/doc/current/best_practices.html#use-parameters-for-application-configuration
|
||||
parameters:
|
||||
# App
|
||||
app.url: '%env(APP_URL)%'
|
||||
app.version: '%env(default:app.default.version:APP_VERSION)%'
|
||||
|
||||
# Debrid Services
|
||||
app.debrid.real_debrid.key: '%env(REAL_DEBRID_KEY)%'
|
||||
|
||||
# TMDB Key
|
||||
app.meta_provider.tmdb.key: '%env(TMDB_API)%'
|
||||
|
||||
# Media
|
||||
media.base_path: '/var/download'
|
||||
media.default_movies_dir: movies
|
||||
media.default_tvshows_dir: tvshows
|
||||
media.movies_path: '/var/download/%env(default:media.default_movies_dir:MOVIES_PATH)%'
|
||||
media.tvshows_path: '/var/download/%env(default:media.default_tvshows_dir:TVSHOWS_PATH)%'
|
||||
|
||||
# Mercure
|
||||
app.mercure.url: 'http://app/.well-known/mercure'
|
||||
app.mercure.public_url: '%env(APP_URL)%/.well-known/mercure'
|
||||
|
||||
# Cache
|
||||
app.cache.adapter: '%env(default:app.cache.adapter.default:CACHE_ADAPTER)%'
|
||||
app.cache.redis.host: '%env(default:app.cache.redis.host.default:REDIS_HOST)%'
|
||||
app.cache.adapter.default: 'filesystem'
|
||||
app.cache.redis.host.default: 'redis://redis'
|
||||
|
||||
# Various configs
|
||||
app.default.version: '0.dev'
|
||||
app.default.timezone: 'America/Chicago'
|
||||
|
||||
# Auth
|
||||
auth.default.method: 'form_login'
|
||||
auth.method: '%env(default:auth.default.method:AUTH_METHOD)%'
|
||||
|
||||
auth.oidc.well_known_url: '%env(OIDC_WELL_KNOWN_URL)%'
|
||||
auth.oidc.client_id: '%env(OIDC_CLIENT_ID)%'
|
||||
auth.oidc.client_secret: '%env(OIDC_CLIENT_SECRET)%'
|
||||
auth.oidc.bypass_form_login: '%env(bool:OIDC_BYPASS_FORM_LOGIN)%'
|
||||
|
||||
services:
|
||||
# default configuration for services in *this* file
|
||||
@@ -22,3 +61,40 @@ services:
|
||||
|
||||
# add more service definitions when explicit configuration is needed
|
||||
# please note that last definitions always *replace* previous ones
|
||||
App\Download\Downloader\DownloaderInterface: "@App\\Download\\Downloader\\ProcessDownloader"
|
||||
|
||||
# Session
|
||||
Symfony\Component\HttpFoundation\Session\Storage\Handler\PdoSessionHandler:
|
||||
arguments:
|
||||
- '%env(DATABASE_URL)%'
|
||||
|
||||
# LDAP
|
||||
App\User\Framework\Security\LdapUserProvider:
|
||||
arguments:
|
||||
$registerLdapUserHandler: '@App\User\Action\Handler\RegisterLdapUserHandler'
|
||||
$userRepository: '@App\User\Framework\Repository\UserRepository'
|
||||
$ldap: '@Symfony\Component\Ldap\LdapInterface'
|
||||
$baseDn: '%env(LDAP_BASE_DN)%'
|
||||
$searchDn: '%env(LDAP_BIND_USER)%'
|
||||
$searchPassword: '%env(LDAP_BIND_PASS)%'
|
||||
$defaultRoles: ['ROLE_USER']
|
||||
$uidKey: '%env(LDAP_UID_KEY)%'
|
||||
# $passwordAttribute: '%env(LDAP_PASSWORD_ATTRIBUTE)%'
|
||||
|
||||
|
||||
Symfony\Component\Ldap\LdapInterface: '@Symfony\Component\Ldap\Ldap'
|
||||
|
||||
Symfony\Component\Ldap\Ldap:
|
||||
arguments: [ '@Symfony\Component\Ldap\Adapter\ExtLdap\Adapter' ]
|
||||
tags:
|
||||
- ldap
|
||||
|
||||
Symfony\Component\Ldap\Adapter\ExtLdap\Adapter:
|
||||
arguments:
|
||||
- host: '%env(LDAP_HOST)%'
|
||||
port: '%env(LDAP_PORT)%'
|
||||
encryption: '%env(LDAP_ENCRYPTION)%'
|
||||
options:
|
||||
protocol_version: 3
|
||||
referrals: false
|
||||
|
||||
|
||||
@@ -1,5 +1,58 @@
|
||||
services:
|
||||
php:
|
||||
image: code.caldwell.digital/home/torsearch/app:${TAG}
|
||||
app:
|
||||
image: registry.caldwell.digital/home/torsearch-app:${TAG}
|
||||
ports:
|
||||
- "8001:80"
|
||||
- '8001:80'
|
||||
environment:
|
||||
MERCURE_PUBLISHER_JWT_KEY: '!ChangeThisMercureHubJWTSecretKey!'
|
||||
MERCURE_SUBSCRIBER_JWT_KEY: '!ChangeThisMercureHubJWTSecretKey!'
|
||||
tty: true
|
||||
deploy:
|
||||
replicas: 2
|
||||
volumes:
|
||||
- /mnt/media/downloads/movies:/var/download/movies
|
||||
- /mnt/media/downloads/tvshows:/var/download/tvshows
|
||||
- mercure_data:/data
|
||||
- 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:
|
||||
mysql:
|
||||
mercure_config:
|
||||
mercure_data:
|
||||
redis_data:
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@ assets
|
||||
bash
|
||||
bin
|
||||
config
|
||||
docker
|
||||
migrations
|
||||
public
|
||||
src
|
||||
|
||||
25
docker/Dockerfile.app
Normal file
@@ -0,0 +1,25 @@
|
||||
FROM dunglas/frankenphp
|
||||
|
||||
ENV SERVER_NAME=":80"
|
||||
ENV CADDY_GLOBAL_OPTIONS="auto_https off"
|
||||
ENV APP_RUNTIME="Runtime\\FrankenPhpSymfony\\Runtime"
|
||||
|
||||
ARG APP_VERSION="0.dev"
|
||||
ENV APP_VERSION="${APP_VERSION}"
|
||||
|
||||
RUN install-php-extensions \
|
||||
pdo_mysql \
|
||||
gd \
|
||||
intl \
|
||||
zip \
|
||||
opcache
|
||||
|
||||
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/docker-entrypoint" ]
|
||||
|
||||
CMD [ "frankenphp", "run", "--config", "/etc/frankenphp/Caddyfile" ]
|
||||
|
||||
HEALTHCHECK --interval=3s --timeout=3s --retries=10 CMD [ "php", "/app/bin/console", "startup:status" ]
|
||||
21
docker/Dockerfile.scheduler
Normal file
@@ -0,0 +1,21 @@
|
||||
FROM dunglas/frankenphp:php8.4-alpine
|
||||
|
||||
ENV SERVER_NAME=":80"
|
||||
ENV CADDY_GLOBAL_OPTIONS="auto_https off"
|
||||
ENV APP_RUNTIME="Runtime\\FrankenPhpSymfony\\Runtime"
|
||||
|
||||
ARG APP_VERSION="0.dev"
|
||||
ENV APP_VERSION="${APP_VERSION}"
|
||||
|
||||
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
|
||||
23
docker/Dockerfile.worker
Normal file
@@ -0,0 +1,23 @@
|
||||
FROM dunglas/frankenphp:php8.4-alpine
|
||||
|
||||
ENV SERVER_NAME=":80"
|
||||
ENV CADDY_GLOBAL_OPTIONS="auto_https off"
|
||||
ENV APP_RUNTIME="Runtime\\FrankenPhpSymfony\\Runtime"
|
||||
|
||||
ARG APP_VERSION="0.dev"
|
||||
ENV APP_VERSION="${APP_VERSION}"
|
||||
|
||||
RUN install-php-extensions \
|
||||
pdo_mysql \
|
||||
gd \
|
||||
intl \
|
||||
zip \
|
||||
opcache
|
||||
|
||||
RUN apk add --no-cache wget
|
||||
|
||||
COPY . /app
|
||||
|
||||
ENTRYPOINT [ "php", "/app/bin/console", "messenger:consume", "async" ]
|
||||
|
||||
HEALTHCHECK --interval=3s --timeout=3s --retries=10 CMD return 0
|
||||
59
docker/app/Caddyfile
Normal file
@@ -0,0 +1,59 @@
|
||||
{
|
||||
{$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
@@ -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 "$@"
|
||||
12
docker/app/messenger-worker@.service
Normal file
@@ -0,0 +1,12 @@
|
||||
[Unit]
|
||||
Description=Torsearch media cache warming services
|
||||
|
||||
[Service]
|
||||
ExecStart=php /app/bin/console messenger:consume media_cache --time-limit=3600
|
||||
# for Redis, set a custom consumer name for each instance
|
||||
Environment="MESSENGER_CONSUMER_NAME=symfony-%n-%i"
|
||||
Restart=always
|
||||
RestartSec=30
|
||||
|
||||
[Install]
|
||||
WantedBy=default.target
|
||||
71
docs/examples/.env
Normal file
@@ -0,0 +1,71 @@
|
||||
# App must be served over HTTPS (requirement of Mercure)
|
||||
# Either serve behind an SSL terminating reverse proxy
|
||||
# or pass your certificates into the 'app' container.
|
||||
# Please omit any trailing slashes. The APP_URL is
|
||||
# used to generate the Mercure URL behind the scenes.
|
||||
APP_URL="https://dev.caldwell.digital"
|
||||
APP_SECRET="70169beadfbc8101c393cbfbba27a313"
|
||||
APP_ENV=prod
|
||||
|
||||
# Mercure is a Caddy module built into the webserver
|
||||
# that facilitates the usage of websockets to transmit
|
||||
# real time data (download progress, etc.)
|
||||
MERCURE_JWT_SECRET="!ChangeThisMercureHubJWTSecretKey!"
|
||||
|
||||
# Use the DATABASE_URL below to use the MariaDB container
|
||||
# provided in the example.compose.yml file, or remove this
|
||||
# line and fill in the details of your own MySQL/MariaDB server
|
||||
DATABASE_URL="mysql://root:password@database:3306/app?serverVersion=10.6.19.2-MariaDB&charset=utf8mb4"
|
||||
|
||||
# Fill in your MySQL/MariaDB connection details
|
||||
#DATABASE_URL="mysql://<mysql user>:<mysql pass>@<mysql host>:3306/<mysql db name>?serverVersion=10.6.19.2-MariaDB&charset=utf8mb4"
|
||||
|
||||
# Enter your Real Debrid API key
|
||||
# This key is never saved anywhere
|
||||
# else and is passed to Torrentio
|
||||
# to retrieve download options
|
||||
REAL_DEBRID_KEY=""
|
||||
|
||||
# Enter your TMDB API key
|
||||
# This is used to provide rich search results
|
||||
# when searching for media and rendering the
|
||||
# Popular Movies and TV Shows section.
|
||||
TMDB_API=""
|
||||
|
||||
# Use your own Redis instance or use the
|
||||
# below value to use the container included
|
||||
# in the example compose.yml file.
|
||||
REDIS_HOST="redis://redis"
|
||||
|
||||
### Auth ###
|
||||
# Change to "oidc" to and provide the required
|
||||
# environment variables below to use OIDC auth.
|
||||
AUTH_METHOD=form_login
|
||||
|
||||
# OIDC
|
||||
OIDC_WELL_KNOWN_URL=
|
||||
OIDC_CLIENT_ID=
|
||||
OIDC_CLIENT_SECRET=
|
||||
# Allows you to skip the login page and directly
|
||||
# rely on your IdP for auth.
|
||||
OIDC_BYPASS_FORM_LOGIN=
|
||||
|
||||
|
||||
# LDAP Config: To use LDAP, enter the below fields
|
||||
# and run 'php bin/console config:set auth.method ldap'
|
||||
# (LDAP is still in progress and not ready for use)
|
||||
#LDAP_HOST=
|
||||
#LDAP_PORT=
|
||||
#LDAP_ENCRYPTION=
|
||||
#LDAP_BASE_DN=
|
||||
#LDAP_BIND_USER=
|
||||
#LDAP_BIND_PASS=
|
||||
#LDAP_DN_STRING=
|
||||
#LDAP_UID_KEY="uid"
|
||||
# LDAP group that identifies an Admin
|
||||
# Users with this LDAP group will automatically
|
||||
# get the admin role in this system.
|
||||
#LDAP_ADMIN_ROLE_DN=""
|
||||
#LDAP_EMAIL_ATTRIBUTE=mail
|
||||
#LDAP_USERNAME_ATTRIBUTE=uid
|
||||
#LDAP_NAME_ATTRIBUTE=displayname
|
||||
94
docs/examples/compose.yml
Normal file
@@ -0,0 +1,94 @@
|
||||
services:
|
||||
app:
|
||||
image: code.caldwell.digital/home/torsearch-app:latest
|
||||
ports:
|
||||
- '8006:80'
|
||||
env_file:
|
||||
- .env
|
||||
volumes:
|
||||
- ./downloads/movies:/var/download/movies
|
||||
- ./downloads/tvshows:/var/download/tvshows
|
||||
environment:
|
||||
TZ: America/Chicago
|
||||
MERCURE_PUBLISHER_JWT_KEY: '!ChangeThisMercureHubJWTSecretKey!'
|
||||
MERCURE_SUBSCRIBER_JWT_KEY: '!ChangeThisMercureHubJWTSecretKey!'
|
||||
depends_on:
|
||||
database:
|
||||
condition: service_healthy
|
||||
|
||||
# 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
|
||||
volumes:
|
||||
- ./downloads/movies:/var/download/movies
|
||||
- ./downloads/tvshows:/var/download/tvshows
|
||||
environment:
|
||||
TZ: America/Chicago
|
||||
command: -vv --time-limit=3600 --limit=10
|
||||
env_file:
|
||||
- .env
|
||||
restart: always
|
||||
depends_on:
|
||||
app:
|
||||
condition: service_healthy
|
||||
|
||||
# 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
|
||||
volumes:
|
||||
- ./downloads/movies:/var/download/movies
|
||||
- ./downloads/tvshows:/var/download/tvshows
|
||||
env_file:
|
||||
- .env
|
||||
command: -vv
|
||||
environment:
|
||||
TZ: America/Chicago
|
||||
restart: always
|
||||
depends_on:
|
||||
app:
|
||||
condition: service_healthy
|
||||
|
||||
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:
|
||||
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
|
||||
|
||||
BIN
docs/img/torsearch_filter_download.png
Normal file
|
After Width: | Height: | Size: 200 KiB |
BIN
docs/img/torsearch_homepage.png
Normal file
|
After Width: | Height: | Size: 421 KiB |
BIN
docs/img/torsearch_inception_results.png
Normal file
|
After Width: | Height: | Size: 654 KiB |
BIN
docs/img/torsearch_monitor.png
Normal file
|
After Width: | Height: | Size: 24 KiB |
BIN
docs/img/torsearch_severance.png
Normal file
|
After Width: | Height: | Size: 568 KiB |
BIN
docs/img/torsearch_severance_episodes.png
Normal file
|
After Width: | Height: | Size: 889 KiB |
BIN
docs/img/torsearch_severance_media.png
Normal file
|
After Width: | Height: | Size: 643 KiB |
BIN
docs/img/torsearch_severance_results.png
Normal file
|
After Width: | Height: | Size: 438 KiB |
@@ -22,4 +22,49 @@ return [
|
||||
'@symfony/stimulus-bundle' => [
|
||||
'path' => './vendor/symfony/stimulus-bundle/assets/dist/loader.js',
|
||||
],
|
||||
'@symfony/ux-live-component' => [
|
||||
'path' => './vendor/symfony/ux-live-component/assets/dist/live_controller.js',
|
||||
],
|
||||
'@hotwired/turbo' => [
|
||||
'version' => '7.3.0',
|
||||
],
|
||||
'@stimulus-components/popover' => [
|
||||
'version' => '7.0.0',
|
||||
],
|
||||
'@stimulus-components/dialog' => [
|
||||
'version' => '1.0.1',
|
||||
],
|
||||
'@stimulus-components/dropdown' => [
|
||||
'version' => '3.0.0',
|
||||
],
|
||||
'stimulus-use' => [
|
||||
'version' => '0.52.2',
|
||||
],
|
||||
'animate.css' => [
|
||||
'version' => '4.1.1',
|
||||
],
|
||||
'animate.css/animate.min.css' => [
|
||||
'version' => '4.1.1',
|
||||
'type' => 'css',
|
||||
],
|
||||
'tom-select' => [
|
||||
'version' => '2.4.3',
|
||||
],
|
||||
'@orchidjs/sifter' => [
|
||||
'version' => '1.1.0',
|
||||
],
|
||||
'@orchidjs/unicode-variants' => [
|
||||
'version' => '1.1.2',
|
||||
],
|
||||
'tom-select/dist/css/tom-select.default.min.css' => [
|
||||
'version' => '2.4.3',
|
||||
'type' => 'css',
|
||||
],
|
||||
'tom-select/dist/css/tom-select.default.css' => [
|
||||
'version' => '2.4.3',
|
||||
'type' => 'css',
|
||||
],
|
||||
'pulltorefreshjs' => [
|
||||
'version' => '0.1.22',
|
||||
],
|
||||
];
|
||||
|
||||
0
src/Controller/.gitignore → migrations/.gitignore
vendored
Normal file → Executable file
31
migrations/Version20241211055503.php
Normal file
@@ -0,0 +1,31 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace DoctrineMigrations;
|
||||
|
||||
use Doctrine\DBAL\Schema\Schema;
|
||||
use Doctrine\Migrations\AbstractMigration;
|
||||
|
||||
/**
|
||||
* Auto-generated Migration: Please modify to your needs!
|
||||
*/
|
||||
final class Version20241211055503 extends AbstractMigration
|
||||
{
|
||||
public function getDescription(): string
|
||||
{
|
||||
return '';
|
||||
}
|
||||
|
||||
public function up(Schema $schema): void
|
||||
{
|
||||
// this up() migration is auto-generated, please modify it to your needs
|
||||
$this->addSql('CREATE TABLE messenger_messages (id BIGINT AUTO_INCREMENT NOT NULL, body LONGTEXT NOT NULL, headers LONGTEXT NOT NULL, queue_name VARCHAR(190) NOT NULL, created_at DATETIME NOT NULL COMMENT \'(DC2Type:datetime_immutable)\', available_at DATETIME NOT NULL COMMENT \'(DC2Type:datetime_immutable)\', delivered_at DATETIME DEFAULT NULL COMMENT \'(DC2Type:datetime_immutable)\', INDEX IDX_75EA56E0FB7336F0 (queue_name), INDEX IDX_75EA56E0E3BD61CE (available_at), INDEX IDX_75EA56E016BA31DB (delivered_at), PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8mb4 COLLATE `utf8mb4_unicode_ci` ENGINE = InnoDB');
|
||||
}
|
||||
|
||||
public function down(Schema $schema): void
|
||||
{
|
||||
// this down() migration is auto-generated, please modify it to your needs
|
||||
$this->addSql('DROP TABLE messenger_messages');
|
||||
}
|
||||
}
|
||||
43
migrations/Version20241218024301.php
Normal file
@@ -0,0 +1,43 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace DoctrineMigrations;
|
||||
|
||||
use Doctrine\DBAL\Schema\Schema;
|
||||
use Doctrine\Migrations\AbstractMigration;
|
||||
|
||||
/**
|
||||
* Auto-generated Migration: Please modify to your needs!
|
||||
*/
|
||||
final class Version20241218024301 extends AbstractMigration
|
||||
{
|
||||
public function getDescription(): string
|
||||
{
|
||||
return '';
|
||||
}
|
||||
|
||||
public function up(Schema $schema): void
|
||||
{
|
||||
// this up() migration is auto-generated, please modify it to your needs
|
||||
$this->addSql('CREATE TABLE episode (id INT AUTO_INCREMENT NOT NULL, series_id_id INT DEFAULT NULL, imdb_id VARCHAR(255) DEFAULT NULL, tvdb_id VARCHAR(255) DEFAULT NULL, title VARCHAR(255) DEFAULT NULL, year VARCHAR(5) DEFAULT NULL, poster VARCHAR(500) DEFAULT NULL, season VARCHAR(255) DEFAULT NULL, episode VARCHAR(255) DEFAULT NULL, episode_code VARCHAR(255) DEFAULT NULL, download_directory VARCHAR(500) DEFAULT NULL, INDEX IDX_DDAA1CDAACB7A4A (series_id_id), PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8mb4 COLLATE `utf8mb4_unicode_ci` ENGINE = InnoDB');
|
||||
$this->addSql('CREATE TABLE series (id INT AUTO_INCREMENT NOT NULL, imdb_id VARCHAR(255) NOT NULL, tvdb_id VARCHAR(255) DEFAULT NULL, title VARCHAR(255) DEFAULT NULL, year VARCHAR(5) DEFAULT NULL, poster VARCHAR(500) DEFAULT NULL, directory VARCHAR(255) DEFAULT NULL, number_seasons INT DEFAULT NULL, number_episodes INT DEFAULT NULL, PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8mb4 COLLATE `utf8mb4_unicode_ci` ENGINE = InnoDB');
|
||||
$this->addSql('ALTER TABLE episode ADD CONSTRAINT FK_DDAA1CDAACB7A4A FOREIGN KEY (series_id_id) REFERENCES series (id)');
|
||||
$this->addSql('DROP INDEX IDX_75EA56E016BA31DB ON messenger_messages');
|
||||
$this->addSql('DROP INDEX IDX_75EA56E0FB7336F0 ON messenger_messages');
|
||||
$this->addSql('DROP INDEX IDX_75EA56E0E3BD61CE ON messenger_messages');
|
||||
$this->addSql('ALTER TABLE messenger_messages CHANGE id id INT AUTO_INCREMENT NOT NULL');
|
||||
}
|
||||
|
||||
public function down(Schema $schema): void
|
||||
{
|
||||
// this down() migration is auto-generated, please modify it to your needs
|
||||
$this->addSql('ALTER TABLE episode DROP FOREIGN KEY FK_DDAA1CDAACB7A4A');
|
||||
$this->addSql('DROP TABLE episode');
|
||||
$this->addSql('DROP TABLE series');
|
||||
$this->addSql('ALTER TABLE messenger_messages CHANGE id id BIGINT AUTO_INCREMENT NOT NULL');
|
||||
$this->addSql('CREATE INDEX IDX_75EA56E016BA31DB ON messenger_messages (delivered_at)');
|
||||
$this->addSql('CREATE INDEX IDX_75EA56E0FB7336F0 ON messenger_messages (queue_name)');
|
||||
$this->addSql('CREATE INDEX IDX_75EA56E0E3BD61CE ON messenger_messages (available_at)');
|
||||
}
|
||||
}
|
||||
31
migrations/Version20241226201901.php
Normal file
@@ -0,0 +1,31 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace DoctrineMigrations;
|
||||
|
||||
use Doctrine\DBAL\Schema\Schema;
|
||||
use Doctrine\Migrations\AbstractMigration;
|
||||
|
||||
/**
|
||||
* Auto-generated Migration: Please modify to your needs!
|
||||
*/
|
||||
final class Version20241226201901 extends AbstractMigration
|
||||
{
|
||||
public function getDescription(): string
|
||||
{
|
||||
return '';
|
||||
}
|
||||
|
||||
public function up(Schema $schema): void
|
||||
{
|
||||
// this up() migration is auto-generated, please modify it to your needs
|
||||
|
||||
}
|
||||
|
||||
public function down(Schema $schema): void
|
||||
{
|
||||
// this down() migration is auto-generated, please modify it to your needs
|
||||
|
||||
}
|
||||
}
|
||||
31
migrations/Version20241226205937.php
Normal file
@@ -0,0 +1,31 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace DoctrineMigrations;
|
||||
|
||||
use Doctrine\DBAL\Schema\Schema;
|
||||
use Doctrine\Migrations\AbstractMigration;
|
||||
|
||||
/**
|
||||
* Auto-generated Migration: Please modify to your needs!
|
||||
*/
|
||||
final class Version20241226205937 extends AbstractMigration
|
||||
{
|
||||
public function getDescription(): string
|
||||
{
|
||||
return '';
|
||||
}
|
||||
|
||||
public function up(Schema $schema): void
|
||||
{
|
||||
// this up() migration is auto-generated, please modify it to your needs
|
||||
$this->addSql('CREATE TABLE download (id INT AUTO_INCREMENT NOT NULL, imdb_id VARCHAR(20) DEFAULT NULL, title VARCHAR(255) DEFAULT NULL, url VARCHAR(1024) NOT NULL, filename VARCHAR(1024) DEFAULT NULL, status VARCHAR(255) DEFAULT NULL, progress INT DEFAULT NULL, PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8mb4 COLLATE `utf8mb4_unicode_ci` ENGINE = InnoDB');
|
||||
}
|
||||
|
||||
public function down(Schema $schema): void
|
||||
{
|
||||
// this down() migration is auto-generated, please modify it to your needs
|
||||
$this->addSql('DROP TABLE download');
|
||||
}
|
||||
}
|
||||
31
migrations/Version20241226214700.php
Normal file
@@ -0,0 +1,31 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace DoctrineMigrations;
|
||||
|
||||
use Doctrine\DBAL\Schema\Schema;
|
||||
use Doctrine\Migrations\AbstractMigration;
|
||||
|
||||
/**
|
||||
* Auto-generated Migration: Please modify to your needs!
|
||||
*/
|
||||
final class Version20241226214700 extends AbstractMigration
|
||||
{
|
||||
public function getDescription(): string
|
||||
{
|
||||
return '';
|
||||
}
|
||||
|
||||
public function up(Schema $schema): void
|
||||
{
|
||||
// this up() migration is auto-generated, please modify it to your needs
|
||||
$this->addSql('ALTER TABLE download ADD media_type VARCHAR(255) DEFAULT NULL');
|
||||
}
|
||||
|
||||
public function down(Schema $schema): void
|
||||
{
|
||||
// this down() migration is auto-generated, please modify it to your needs
|
||||
$this->addSql('ALTER TABLE download DROP media_type');
|
||||
}
|
||||
}
|
||||
31
migrations/Version20250202035615.php
Normal file
@@ -0,0 +1,31 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace DoctrineMigrations;
|
||||
|
||||
use Doctrine\DBAL\Schema\Schema;
|
||||
use Doctrine\Migrations\AbstractMigration;
|
||||
|
||||
/**
|
||||
* Auto-generated Migration: Please modify to your needs!
|
||||
*/
|
||||
final class Version20250202035615 extends AbstractMigration
|
||||
{
|
||||
public function getDescription(): string
|
||||
{
|
||||
return '';
|
||||
}
|
||||
|
||||
public function up(Schema $schema): void
|
||||
{
|
||||
// this up() migration is auto-generated, please modify it to your needs
|
||||
$this->addSql('ALTER TABLE download ADD batch_id VARCHAR(255) DEFAULT NULL');
|
||||
}
|
||||
|
||||
public function down(Schema $schema): void
|
||||
{
|
||||
// this down() migration is auto-generated, please modify it to your needs
|
||||
$this->addSql('ALTER TABLE download DROP batch_id');
|
||||
}
|
||||
}
|
||||
32
migrations/Version20250217221120.php
Normal file
@@ -0,0 +1,32 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace DoctrineMigrations;
|
||||
|
||||
use Doctrine\DBAL\Schema\Schema;
|
||||
use Doctrine\Migrations\AbstractMigration;
|
||||
|
||||
/**
|
||||
* Auto-generated Migration: Please modify to your needs!
|
||||
*/
|
||||
final class Version20250217221120 extends AbstractMigration
|
||||
{
|
||||
public function getDescription(): string
|
||||
{
|
||||
return '';
|
||||
}
|
||||
|
||||
public function up(Schema $schema): void
|
||||
{
|
||||
// this up() migration is auto-generated, please modify it to your needs
|
||||
$this->addSql('CREATE TABLE processed_messages (id INT AUTO_INCREMENT NOT NULL, run_id INT NOT NULL, attempt SMALLINT NOT NULL, message_type VARCHAR(255) NOT NULL, description VARCHAR(255) DEFAULT NULL, dispatched_at DATETIME NOT NULL COMMENT \'(DC2Type:datetime_immutable)\', received_at DATETIME NOT NULL COMMENT \'(DC2Type:datetime_immutable)\', finished_at DATETIME NOT NULL COMMENT \'(DC2Type:datetime_immutable)\', wait_time BIGINT NOT NULL, handle_time BIGINT NOT NULL, memory_usage INT NOT NULL, transport VARCHAR(255) NOT NULL, tags VARCHAR(255) DEFAULT NULL, failure_type VARCHAR(255) DEFAULT NULL, failure_message LONGTEXT DEFAULT NULL, results JSON DEFAULT NULL COMMENT \'(DC2Type:json)\', PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8mb4 COLLATE `utf8mb4_unicode_ci` ENGINE = InnoDB');
|
||||
}
|
||||
|
||||
public function down(Schema $schema): void
|
||||
{
|
||||
// this down() migration is auto-generated, please modify it to your needs
|
||||
$this->addSql('CREATE TABLE preferences (id INT AUTO_INCREMENT NOT NULL, value VARCHAR(255) CHARACTER SET utf8mb4 NOT NULL COLLATE `utf8mb4_unicode_ci`, PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8mb4 COLLATE `utf8mb4_unicode_ci` ENGINE = InnoDB COMMENT = \'\' ');
|
||||
$this->addSql('DROP TABLE processed_messages');
|
||||
}
|
||||
}
|
||||
46
migrations/Version20250428133608.php
Normal file
@@ -0,0 +1,46 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace DoctrineMigrations;
|
||||
|
||||
use Doctrine\DBAL\Schema\Schema;
|
||||
use Doctrine\Migrations\AbstractMigration;
|
||||
|
||||
/**
|
||||
* Auto-generated Migration: Please modify to your needs!
|
||||
*/
|
||||
final class Version20250428133608 extends AbstractMigration
|
||||
{
|
||||
public function getDescription(): string
|
||||
{
|
||||
return '';
|
||||
}
|
||||
|
||||
public function up(Schema $schema): void
|
||||
{
|
||||
$this->addSql(<<<'SQL'
|
||||
CREATE TABLE preference (id VARCHAR(255) NOT NULL, name VARCHAR(255) DEFAULT NULL, description VARCHAR(255) DEFAULT NULL, enabled TINYINT(1) NOT NULL, PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8mb4 COLLATE `utf8mb4_unicode_ci` ENGINE = InnoDB
|
||||
SQL);
|
||||
$this->addSql(<<<'SQL'
|
||||
CREATE TABLE preference_option (id INT AUTO_INCREMENT NOT NULL, preference_id VARCHAR(255) DEFAULT NULL, name VARCHAR(255) DEFAULT NULL, value VARCHAR(255) DEFAULT NULL, enabled TINYINT(1) NOT NULL, INDEX IDX_607C52FD81022C0 (preference_id), PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8mb4 COLLATE `utf8mb4_unicode_ci` ENGINE = InnoDB
|
||||
SQL);
|
||||
$this->addSql(<<<'SQL'
|
||||
ALTER TABLE preference_option ADD CONSTRAINT FK_607C52FD81022C0 FOREIGN KEY (preference_id) REFERENCES preference (id)
|
||||
SQL);
|
||||
}
|
||||
|
||||
public function down(Schema $schema): void
|
||||
{
|
||||
// this down() migration is auto-generated, please modify it to your needs
|
||||
$this->addSql(<<<'SQL'
|
||||
ALTER TABLE preference_option DROP FOREIGN KEY FK_607C52FD81022C0
|
||||
SQL);
|
||||
$this->addSql(<<<'SQL'
|
||||
DROP TABLE preference
|
||||
SQL);
|
||||
$this->addSql(<<<'SQL'
|
||||
DROP TABLE preference_option
|
||||
SQL);
|
||||
}
|
||||
}
|
||||
83
migrations/Version20250428140450.php
Normal file
@@ -0,0 +1,83 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace DoctrineMigrations;
|
||||
|
||||
use Doctrine\DBAL\Schema\Schema;
|
||||
use Doctrine\Migrations\AbstractMigration;
|
||||
|
||||
/**
|
||||
* Auto-generated Migration: Please modify to your needs!
|
||||
*/
|
||||
final class Version20250428140450 extends AbstractMigration
|
||||
{
|
||||
public function getDescription(): string
|
||||
{
|
||||
return '';
|
||||
}
|
||||
|
||||
public function up(Schema $schema): void
|
||||
{
|
||||
// this up() migration is auto-generated, please modify it to your needs
|
||||
$this->addSql(<<<'SQL'
|
||||
CREATE TABLE user (id INT AUTO_INCREMENT NOT NULL, email VARCHAR(180) NOT NULL, roles JSON NOT NULL COMMENT '(DC2Type:json)', password VARCHAR(255) NOT NULL, UNIQUE INDEX UNIQ_8D93D649E7927C74 (email), PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8mb4 COLLATE `utf8mb4_unicode_ci` ENGINE = InnoDB
|
||||
SQL);
|
||||
$this->addSql(<<<'SQL'
|
||||
ALTER TABLE episode DROP FOREIGN KEY FK_DDAA1CDAACB7A4A
|
||||
SQL);
|
||||
$this->addSql(<<<'SQL'
|
||||
DROP TABLE processed_messages
|
||||
SQL);
|
||||
$this->addSql(<<<'SQL'
|
||||
DROP TABLE episode
|
||||
SQL);
|
||||
$this->addSql(<<<'SQL'
|
||||
DROP TABLE series
|
||||
SQL);
|
||||
$this->addSql(<<<'SQL'
|
||||
ALTER TABLE messenger_messages CHANGE id id BIGINT AUTO_INCREMENT NOT NULL
|
||||
SQL);
|
||||
$this->addSql(<<<'SQL'
|
||||
CREATE INDEX IDX_75EA56E0FB7336F0 ON messenger_messages (queue_name)
|
||||
SQL);
|
||||
$this->addSql(<<<'SQL'
|
||||
CREATE INDEX IDX_75EA56E0E3BD61CE ON messenger_messages (available_at)
|
||||
SQL);
|
||||
$this->addSql(<<<'SQL'
|
||||
CREATE INDEX IDX_75EA56E016BA31DB ON messenger_messages (delivered_at)
|
||||
SQL);
|
||||
}
|
||||
|
||||
public function down(Schema $schema): void
|
||||
{
|
||||
// this down() migration is auto-generated, please modify it to your needs
|
||||
$this->addSql(<<<'SQL'
|
||||
CREATE TABLE processed_messages (id INT AUTO_INCREMENT NOT NULL, run_id INT NOT NULL, attempt SMALLINT NOT NULL, message_type VARCHAR(255) CHARACTER SET utf8mb4 NOT NULL COLLATE `utf8mb4_unicode_ci`, description VARCHAR(255) CHARACTER SET utf8mb4 DEFAULT NULL COLLATE `utf8mb4_unicode_ci`, dispatched_at DATETIME NOT NULL COMMENT '(DC2Type:datetime_immutable)', received_at DATETIME NOT NULL COMMENT '(DC2Type:datetime_immutable)', finished_at DATETIME NOT NULL COMMENT '(DC2Type:datetime_immutable)', wait_time BIGINT NOT NULL, handle_time BIGINT NOT NULL, memory_usage INT NOT NULL, transport VARCHAR(255) CHARACTER SET utf8mb4 NOT NULL COLLATE `utf8mb4_unicode_ci`, tags VARCHAR(255) CHARACTER SET utf8mb4 DEFAULT NULL COLLATE `utf8mb4_unicode_ci`, failure_type VARCHAR(255) CHARACTER SET utf8mb4 DEFAULT NULL COLLATE `utf8mb4_unicode_ci`, failure_message LONGTEXT CHARACTER SET utf8mb4 DEFAULT NULL COLLATE `utf8mb4_unicode_ci`, results JSON DEFAULT NULL COMMENT '(DC2Type:json)', PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8mb4 COLLATE `utf8mb4_unicode_ci` ENGINE = InnoDB COMMENT = ''
|
||||
SQL);
|
||||
$this->addSql(<<<'SQL'
|
||||
CREATE TABLE episode (id INT AUTO_INCREMENT NOT NULL, series_id_id INT DEFAULT NULL, imdb_id VARCHAR(255) CHARACTER SET utf8mb4 DEFAULT NULL COLLATE `utf8mb4_unicode_ci`, tvdb_id VARCHAR(255) CHARACTER SET utf8mb4 DEFAULT NULL COLLATE `utf8mb4_unicode_ci`, title VARCHAR(255) CHARACTER SET utf8mb4 DEFAULT NULL COLLATE `utf8mb4_unicode_ci`, year VARCHAR(5) CHARACTER SET utf8mb4 DEFAULT NULL COLLATE `utf8mb4_unicode_ci`, poster VARCHAR(500) CHARACTER SET utf8mb4 DEFAULT NULL COLLATE `utf8mb4_unicode_ci`, season VARCHAR(255) CHARACTER SET utf8mb4 DEFAULT NULL COLLATE `utf8mb4_unicode_ci`, episode VARCHAR(255) CHARACTER SET utf8mb4 DEFAULT NULL COLLATE `utf8mb4_unicode_ci`, episode_code VARCHAR(255) CHARACTER SET utf8mb4 DEFAULT NULL COLLATE `utf8mb4_unicode_ci`, download_directory VARCHAR(500) CHARACTER SET utf8mb4 DEFAULT NULL COLLATE `utf8mb4_unicode_ci`, INDEX IDX_DDAA1CDAACB7A4A (series_id_id), PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8mb4 COLLATE `utf8mb4_unicode_ci` ENGINE = InnoDB COMMENT = ''
|
||||
SQL);
|
||||
$this->addSql(<<<'SQL'
|
||||
CREATE TABLE series (id INT AUTO_INCREMENT NOT NULL, imdb_id VARCHAR(255) CHARACTER SET utf8mb4 NOT NULL COLLATE `utf8mb4_unicode_ci`, tvdb_id VARCHAR(255) CHARACTER SET utf8mb4 DEFAULT NULL COLLATE `utf8mb4_unicode_ci`, title VARCHAR(255) CHARACTER SET utf8mb4 DEFAULT NULL COLLATE `utf8mb4_unicode_ci`, year VARCHAR(5) CHARACTER SET utf8mb4 DEFAULT NULL COLLATE `utf8mb4_unicode_ci`, poster VARCHAR(500) CHARACTER SET utf8mb4 DEFAULT NULL COLLATE `utf8mb4_unicode_ci`, directory VARCHAR(255) CHARACTER SET utf8mb4 DEFAULT NULL COLLATE `utf8mb4_unicode_ci`, number_seasons INT DEFAULT NULL, number_episodes INT DEFAULT NULL, PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8mb4 COLLATE `utf8mb4_unicode_ci` ENGINE = InnoDB COMMENT = ''
|
||||
SQL);
|
||||
$this->addSql(<<<'SQL'
|
||||
ALTER TABLE episode ADD CONSTRAINT FK_DDAA1CDAACB7A4A FOREIGN KEY (series_id_id) REFERENCES series (id)
|
||||
SQL);
|
||||
$this->addSql(<<<'SQL'
|
||||
DROP TABLE user
|
||||
SQL);
|
||||
$this->addSql(<<<'SQL'
|
||||
DROP INDEX IDX_75EA56E0FB7336F0 ON messenger_messages
|
||||
SQL);
|
||||
$this->addSql(<<<'SQL'
|
||||
DROP INDEX IDX_75EA56E0E3BD61CE ON messenger_messages
|
||||
SQL);
|
||||
$this->addSql(<<<'SQL'
|
||||
DROP INDEX IDX_75EA56E016BA31DB ON messenger_messages
|
||||
SQL);
|
||||
$this->addSql(<<<'SQL'
|
||||
ALTER TABLE messenger_messages CHANGE id id INT AUTO_INCREMENT NOT NULL
|
||||
SQL);
|
||||
}
|
||||
}
|
||||
35
migrations/Version20250429020903.php
Normal file
@@ -0,0 +1,35 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace DoctrineMigrations;
|
||||
|
||||
use Doctrine\DBAL\Schema\Schema;
|
||||
use Doctrine\Migrations\AbstractMigration;
|
||||
|
||||
/**
|
||||
* Auto-generated Migration: Please modify to your needs!
|
||||
*/
|
||||
final class Version20250429020903 extends AbstractMigration
|
||||
{
|
||||
public function getDescription(): string
|
||||
{
|
||||
return '';
|
||||
}
|
||||
|
||||
public function up(Schema $schema): void
|
||||
{
|
||||
// this up() migration is auto-generated, please modify it to your needs
|
||||
$this->addSql(<<<'SQL'
|
||||
ALTER TABLE user ADD name VARCHAR(255) DEFAULT NULL
|
||||
SQL);
|
||||
}
|
||||
|
||||
public function down(Schema $schema): void
|
||||
{
|
||||
// this down() migration is auto-generated, please modify it to your needs
|
||||
$this->addSql(<<<'SQL'
|
||||
ALTER TABLE user DROP name
|
||||
SQL);
|
||||
}
|
||||
}
|
||||
47
migrations/Version20250429032311.php
Normal file
@@ -0,0 +1,47 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace DoctrineMigrations;
|
||||
|
||||
use Doctrine\DBAL\Schema\Schema;
|
||||
use Doctrine\Migrations\AbstractMigration;
|
||||
|
||||
/**
|
||||
* Auto-generated Migration: Please modify to your needs!
|
||||
*/
|
||||
final class Version20250429032311 extends AbstractMigration
|
||||
{
|
||||
public function getDescription(): string
|
||||
{
|
||||
return '';
|
||||
}
|
||||
|
||||
public function up(Schema $schema): void
|
||||
{
|
||||
// this up() migration is auto-generated, please modify it to your needs
|
||||
$this->addSql(<<<'SQL'
|
||||
CREATE TABLE user_preference (id INT AUTO_INCREMENT NOT NULL, user_id INT NOT NULL, preference_id VARCHAR(255) NOT NULL, preference_value VARCHAR(255) DEFAULT NULL, INDEX IDX_FA0E76BFA76ED395 (user_id), INDEX IDX_FA0E76BFD81022C0 (preference_id), PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8mb4 COLLATE `utf8mb4_unicode_ci` ENGINE = InnoDB
|
||||
SQL);
|
||||
$this->addSql(<<<'SQL'
|
||||
ALTER TABLE user_preference ADD CONSTRAINT FK_FA0E76BFA76ED395 FOREIGN KEY (user_id) REFERENCES user (id)
|
||||
SQL);
|
||||
$this->addSql(<<<'SQL'
|
||||
ALTER TABLE user_preference ADD CONSTRAINT FK_FA0E76BFD81022C0 FOREIGN KEY (preference_id) REFERENCES preference (id)
|
||||
SQL);
|
||||
}
|
||||
|
||||
public function down(Schema $schema): void
|
||||
{
|
||||
// this down() migration is auto-generated, please modify it to your needs
|
||||
$this->addSql(<<<'SQL'
|
||||
ALTER TABLE user_preference DROP FOREIGN KEY FK_FA0E76BFA76ED395
|
||||
SQL);
|
||||
$this->addSql(<<<'SQL'
|
||||
ALTER TABLE user_preference DROP FOREIGN KEY FK_FA0E76BFD81022C0
|
||||
SQL);
|
||||
$this->addSql(<<<'SQL'
|
||||
DROP TABLE user_preference
|
||||
SQL);
|
||||
}
|
||||
}
|
||||
35
migrations/Version20250430231033.php
Normal file
@@ -0,0 +1,35 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace DoctrineMigrations;
|
||||
|
||||
use Doctrine\DBAL\Schema\Schema;
|
||||
use Doctrine\Migrations\AbstractMigration;
|
||||
|
||||
/**
|
||||
* Auto-generated Migration: Please modify to your needs!
|
||||
*/
|
||||
final class Version20250430231033 extends AbstractMigration
|
||||
{
|
||||
public function getDescription(): string
|
||||
{
|
||||
return '';
|
||||
}
|
||||
|
||||
public function up(Schema $schema): void
|
||||
{
|
||||
// this up() migration is auto-generated, please modify it to your needs
|
||||
$this->addSql(<<<'SQL'
|
||||
CREATE TABLE sessions (sess_id VARBINARY(128) NOT NULL, sess_data LONGBLOB NOT NULL, sess_lifetime INT UNSIGNED NOT NULL, sess_time INT UNSIGNED NOT NULL, INDEX sess_lifetime_idx (sess_lifetime), PRIMARY KEY(sess_id)) DEFAULT CHARACTER SET utf8mb4 COLLATE `utf8mb4_bin` ENGINE = InnoDB
|
||||
SQL);
|
||||
}
|
||||
|
||||
public function down(Schema $schema): void
|
||||
{
|
||||
// this down() migration is auto-generated, please modify it to your needs
|
||||
$this->addSql(<<<'SQL'
|
||||
DROP TABLE sessions
|
||||
SQL);
|
||||
}
|
||||
}
|
||||
41
migrations/Version20250503034641.php
Normal file
@@ -0,0 +1,41 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace DoctrineMigrations;
|
||||
|
||||
use Doctrine\DBAL\Schema\Schema;
|
||||
use Doctrine\Migrations\AbstractMigration;
|
||||
|
||||
/**
|
||||
* Auto-generated Migration: Please modify to your needs!
|
||||
*/
|
||||
final class Version20250503034641 extends AbstractMigration
|
||||
{
|
||||
public function getDescription(): string
|
||||
{
|
||||
return '';
|
||||
}
|
||||
|
||||
public function up(Schema $schema): void
|
||||
{
|
||||
// this up() migration is auto-generated, please modify it to your needs
|
||||
$this->addSql(<<<'SQL'
|
||||
CREATE TABLE movie_monitor (id INT AUTO_INCREMENT NOT NULL, user_id INT NOT NULL, title VARCHAR(255) DEFAULT NULL, imdb_id VARCHAR(255) NOT NULL, tmdb_id VARCHAR(255) NOT NULL, status VARCHAR(255) NOT NULL, search_count INT DEFAULT NULL, last_search DATETIME DEFAULT NULL, created_at DATETIME NOT NULL COMMENT '(DC2Type:datetime_immutable)', downloaded_at DATETIME DEFAULT NULL COMMENT '(DC2Type:datetime_immutable)', INDEX IDX_C183DBABA76ED395 (user_id), PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8mb4 COLLATE `utf8mb4_unicode_ci` ENGINE = InnoDB
|
||||
SQL);
|
||||
$this->addSql(<<<'SQL'
|
||||
ALTER TABLE movie_monitor ADD CONSTRAINT FK_C183DBABA76ED395 FOREIGN KEY (user_id) REFERENCES user (id)
|
||||
SQL);
|
||||
}
|
||||
|
||||
public function down(Schema $schema): void
|
||||
{
|
||||
// this down() migration is auto-generated, please modify it to your needs
|
||||
$this->addSql(<<<'SQL'
|
||||
ALTER TABLE movie_monitor DROP FOREIGN KEY FK_C183DBABA76ED395
|
||||
SQL);
|
||||
$this->addSql(<<<'SQL'
|
||||
DROP TABLE movie_monitor
|
||||
SQL);
|
||||
}
|
||||
}
|
||||