Compare commits
78 Commits
dev-search
...
dev-ldap
| Author | SHA1 | Date | |
|---|---|---|---|
| 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 |
17
.env
17
.env
@@ -18,3 +18,20 @@
|
||||
APP_ENV=dev
|
||||
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 ###
|
||||
|
||||
###> 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 ###
|
||||
|
||||
1
.env.dev
1
.env.dev
@@ -5,3 +5,4 @@ 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!"
|
||||
MONITOR_FREQUENCY="* * * * *"
|
||||
|
||||
30
.env.dist
30
.env.dist
@@ -1 +1,31 @@
|
||||
APP_ENV=%%app_env%%
|
||||
APP_SECRET="%%app_secret%%"
|
||||
DATABASE_URL="%%db_url%%"
|
||||
DOWNLOAD_DIR=%%download_dir%%
|
||||
REAL_DEBRID_KEY="%%rd_key%%"
|
||||
TMDB_API=%%tmdb_api%%
|
||||
MERCURE_URL=%%mercure_url%%
|
||||
MERCURE_PUBLIC_URL=%%mercure_public_url%%
|
||||
MERCURE_JWT_SECRET="%%mercure_jwt_secret%%"
|
||||
JELLYFIN_URL=%%jellyfin_url%%
|
||||
JELLYFIN_TOKEN=%%jellyfin_token%%
|
||||
REDIS_HOST="%%redis_host%%"
|
||||
|
||||
|
||||
|
||||
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="cn=admins,cn=groups,cn=accounts,dc=caldwell,dc=local"
|
||||
LDAP_EMAIL_ATTRIBUTE=mail
|
||||
LDAP_USERNAME_ATTRIBUTE=uid
|
||||
LDAP_NAME_ATTRIBUTE=displayname
|
||||
|
||||
4
.gitignore
vendored
4
.gitignore
vendored
@@ -13,3 +13,7 @@
|
||||
/public/assets/
|
||||
/assets/vendor/
|
||||
###< symfony/asset-mapper ###
|
||||
|
||||
###> phpstan/phpstan ###
|
||||
phpstan.neon
|
||||
###< phpstan/phpstan ###
|
||||
|
||||
@@ -1 +1,10 @@
|
||||
FROM registry.caldwell.digital/library/php:8.4-apache
|
||||
|
||||
RUN apt-get update && \
|
||||
apt-get install libldap2-dev -y && \
|
||||
rm -rf /var/lib/apt/lists/* && \
|
||||
docker-php-ext-configure ldap --with-libdir=lib/x86_64-linux-gnu/ && \
|
||||
docker-php-ext-install ldap
|
||||
|
||||
COPY ./bash/vhost.conf /etc/apache2/sites-enabled/vhost.conf
|
||||
RUN rm /etc/apache2/sites-enabled/000-default.conf
|
||||
|
||||
@@ -1,6 +1,11 @@
|
||||
FROM registry.caldwell.digital/library/php:8.4-apache
|
||||
|
||||
RUN apt-get update && \
|
||||
apt-get install libldap2-dev -y && \
|
||||
rm -rf /var/lib/apt/lists/* && \
|
||||
docker-php-ext-configure ldap --with-libdir=lib/x86_64-linux-gnu/ && \
|
||||
docker-php-ext-install ldap
|
||||
|
||||
COPY --chown=www-data:www-data . /var/www
|
||||
COPY ./bash/vhost.conf /etc/apache2/sites-enabled/vhost.conf
|
||||
|
||||
RUN rm /etc/apache2/sites-enabled/000-default.conf
|
||||
|
||||
@@ -8,3 +8,13 @@ import './bootstrap.js';
|
||||
import './styles/app.css';
|
||||
|
||||
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});
|
||||
|
||||
|
||||
@@ -1,4 +1,24 @@
|
||||
{
|
||||
"controllers": [],
|
||||
"controllers": {
|
||||
"@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": []
|
||||
}
|
||||
|
||||
18
assets/controllers/alert_controller.js
Normal file
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));
|
||||
}
|
||||
}
|
||||
37
assets/controllers/download_button_controller.js
Normal file
37
assets/controllers/download_button_controller.js
Normal file
@@ -0,0 +1,37 @@
|
||||
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,
|
||||
}
|
||||
|
||||
download() {
|
||||
fetch('/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,
|
||||
})
|
||||
})
|
||||
.then(res => res.json())
|
||||
.then(json => {
|
||||
console.log(json)
|
||||
})
|
||||
}
|
||||
}
|
||||
93
assets/controllers/monitor_button_controller.js
Normal file
93
assets/controllers/monitor_button_controller.js
Normal file
@@ -0,0 +1,93 @@
|
||||
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']
|
||||
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',
|
||||
});
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
}
|
||||
24
assets/controllers/monitor_controller.js
Normal file
24
assets/controllers/monitor_controller.js
Normal file
@@ -0,0 +1,24 @@
|
||||
import { Controller } from '@hotwired/stimulus';
|
||||
|
||||
/*
|
||||
* The following line makes this controller "lazy": it won't be downloaded until needed
|
||||
* See https://github.com/symfony/stimulus-bridge#lazy-controllers
|
||||
*/
|
||||
/* stimulusFetch: 'lazy' */
|
||||
export default class extends Controller {
|
||||
static values = {
|
||||
title: String,
|
||||
tmdbId: String,
|
||||
imdbId: String,
|
||||
mediaType: String,
|
||||
}
|
||||
|
||||
addMovieMonitor() {
|
||||
console.log(`/monitor/movies/${this.tmdbIdValue}/${this.imdbIdValue}/${encodeURI(this.titleValue)}`)
|
||||
fetch(`/monitor/movies/${this.tmdbIdValue}/${this.imdbIdValue}/${encodeURI(this.titleValue)}`)
|
||||
.then(res => res.json())
|
||||
.then(json => {
|
||||
console.log(json)
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -6,25 +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']
|
||||
|
||||
options = []
|
||||
optionsLoaded = false
|
||||
|
||||
async connect() {
|
||||
await this.setOptions();
|
||||
}
|
||||
|
||||
async setOptions() {
|
||||
if (this.options.length === 0) {
|
||||
await fetch(`/torrentio/movies/${this.imdbIdValue}`)
|
||||
if (false === this.optionsLoaded) {
|
||||
this.optionsLoaded = true;
|
||||
await fetch(`/torrentio/movies/${this.tmdbIdValue}/${this.imdbIdValue}`)
|
||||
.then(res => res.text())
|
||||
.then(response => {
|
||||
this.element.innerHTML = response;
|
||||
this.options = this.element.querySelectorAll('tbody tr');
|
||||
this.options.forEach((option) => option.querySelector('.download-btn').dataset['title'] = this.titleValue);
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -34,7 +42,54 @@ 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 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');
|
||||
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.add('hidden');
|
||||
} else if (true === firstIncluded) {
|
||||
count = 1;
|
||||
selectedCount = selectedCount + 1;
|
||||
option.querySelector('input[type="checkbox"]').checked = true;
|
||||
firstIncluded = false;
|
||||
} else {
|
||||
count = count + 1;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
25
assets/controllers/navbar_controller.js
Normal file
25
assets/controllers/navbar_controller.js
Normal file
@@ -0,0 +1,25 @@
|
||||
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() {
|
||||
console.log(window.location.pathname);
|
||||
this.element.querySelectorAll('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;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
setActive() {
|
||||
|
||||
}
|
||||
}
|
||||
@@ -21,15 +21,17 @@ export default class extends Controller {
|
||||
}
|
||||
|
||||
static outlets = ['movie-results', 'tv-results']
|
||||
static targets = ['resolution', 'codec', 'language', 'provider', 'season']
|
||||
static targets = ['resolution', 'codec', 'language', 'provider', 'season', 'selectAll', 'downloadSelected']
|
||||
static values = {
|
||||
'media-type': String,
|
||||
'episodes': Array,
|
||||
}
|
||||
|
||||
connect() {
|
||||
async connect() {
|
||||
if (this.mediaTypeValue === "tvshows") {
|
||||
this.activeFilter['season'] = 1;}
|
||||
this.activeFilter['season'] = 1;
|
||||
}
|
||||
await this.filter();
|
||||
}
|
||||
|
||||
async movieResultsOutletConnected(outlet) {
|
||||
@@ -48,6 +50,7 @@ export default class extends Controller {
|
||||
this.addLanguages(option, option.dataset);
|
||||
this.addProviders(option, option.dataset);
|
||||
})
|
||||
await this.filter();
|
||||
}
|
||||
|
||||
addLanguages(option, props) {
|
||||
@@ -58,9 +61,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,9 +85,22 @@ 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();
|
||||
|
||||
}
|
||||
@@ -89,77 +118,33 @@ export default class extends Controller {
|
||||
|
||||
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;
|
||||
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();
|
||||
}
|
||||
uncheckSelectAllBtn() {
|
||||
this.selectAllTarget.checked = false;
|
||||
}
|
||||
|
||||
selectAllEpisodes() {
|
||||
this.tvResultsOutlets.forEach((episode) => {
|
||||
if (episode.isActive()) {
|
||||
episode.selectEpisodeForDownload()
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (false === resultList.isActive()) {
|
||||
return;
|
||||
downloadSelectedEpisodes() {
|
||||
this.tvResultsOutlets.forEach(episode => {
|
||||
if (episode.isActive() && episode.isSelected()) {
|
||||
episode.download();
|
||||
}
|
||||
|
||||
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));
|
||||
});
|
||||
this.selectAllTarget.checked = false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,7 +18,7 @@ export default class extends Controller {
|
||||
active: Boolean,
|
||||
};
|
||||
|
||||
static targets = ['list', 'count']
|
||||
static targets = ['list', 'count', 'episodeSelector']
|
||||
static outlets = ['loading-icon']
|
||||
|
||||
options = []
|
||||
@@ -25,18 +29,31 @@ export default class extends Controller {
|
||||
}
|
||||
|
||||
async setOptions() {
|
||||
if (true === this.activeValue) {
|
||||
if (true === this.activeValue && this.optionsLoaded === false) {
|
||||
this.optionsLoaded = true;
|
||||
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;
|
||||
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;
|
||||
} else {
|
||||
this.episodeSelectorTarget.disabled = true;
|
||||
}
|
||||
this.loadingIconOutlet.increaseCount();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// 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');
|
||||
@@ -45,8 +62,11 @@ export default class extends Controller {
|
||||
}
|
||||
}
|
||||
|
||||
setInActive() {
|
||||
async setInActive() {
|
||||
this.activeValue = false;
|
||||
// if (true === this.hasEpisodeSelectorTarget()) {
|
||||
this.episodeSelectorTarget.checked = false;
|
||||
// }
|
||||
this.element.classList.add('hidden');
|
||||
}
|
||||
|
||||
@@ -54,7 +74,95 @@ export default class extends Controller {
|
||||
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('hidden');
|
||||
}
|
||||
|
||||
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 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');
|
||||
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.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
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/material-symbols/logout.svg
Normal file
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/ri/user-line.svg
Normal file
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 |
2
bash/app/wget_download.sh
Executable file
2
bash/app/wget_download.sh
Executable file
@@ -0,0 +1,2 @@
|
||||
# $1 = movies/tvshows/etc, $2 = title of media, $3 = URL of download
|
||||
cd /var/download/${1} && if [ ! -d "${2}" ]; then mkdir "${2}"; fi && cd "${2}" && wget "${3}"
|
||||
@@ -4,6 +4,11 @@
|
||||
DocumentRoot /var/www/public
|
||||
DirectoryIndex /index.php
|
||||
|
||||
<LocationMatch "/hub/">
|
||||
ProxyPass http://mercure:80/
|
||||
ProxyPassReverse http://mercure:80/
|
||||
</LocationMatch>
|
||||
|
||||
<Directory /var/www/public>
|
||||
AllowOverride None
|
||||
Order Allow,Deny
|
||||
|
||||
33
build.xml
33
build.xml
@@ -1,7 +1,7 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project name="Caldwell Digital Symfony Template" 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">
|
||||
@@ -9,20 +9,35 @@
|
||||
</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>
|
||||
|
||||
42
compose.yml
42
compose.yml
@@ -12,11 +12,50 @@ services:
|
||||
- $PWD/bash/caddy:/etc/caddy
|
||||
- $PWD/bash/certs:/etc/ssl
|
||||
|
||||
redis:
|
||||
image: redis:latest
|
||||
volumes:
|
||||
- redis_data:/data
|
||||
command: redis-server --maxmemory 512MB
|
||||
restart: unless-stopped
|
||||
|
||||
php:
|
||||
build: .
|
||||
volumes:
|
||||
- ./:/var/www
|
||||
|
||||
worker:
|
||||
build: .
|
||||
volumes:
|
||||
- ./:/var/www
|
||||
- ./var/download:/var/download
|
||||
command: php ./bin/console messenger:consume async -vv --time-limit=3600
|
||||
|
||||
scheduler:
|
||||
build: .
|
||||
volumes:
|
||||
- ./:/var/www
|
||||
- ./var/download:/var/download
|
||||
command: php ./bin/console messenger:consume scheduler_monitor -vv --time-limit=3600
|
||||
|
||||
mercure:
|
||||
image: dunglas/mercure
|
||||
restart: unless-stopped
|
||||
ports:
|
||||
- "3000:80"
|
||||
environment:
|
||||
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
|
||||
ports:
|
||||
@@ -36,3 +75,6 @@ services:
|
||||
|
||||
volumes:
|
||||
mysql:
|
||||
mercure_data:
|
||||
mercure_config:
|
||||
redis_data:
|
||||
|
||||
@@ -8,20 +8,38 @@
|
||||
"ext-ctype": "*",
|
||||
"ext-iconv": "*",
|
||||
"1tomany/rich-bundle": "^1.8",
|
||||
"aimeos/map": "^3.12",
|
||||
"doctrine/dbal": "^3",
|
||||
"doctrine/doctrine-bundle": "^2.14",
|
||||
"doctrine/doctrine-migrations-bundle": "^3.4",
|
||||
"doctrine/orm": "^3.3",
|
||||
"dragonmantank/cron-expression": "^3.4",
|
||||
"nesbot/carbon": "^3.9",
|
||||
"nihilarr/parse-torrent-name": "^0.0.1",
|
||||
"nyholm/psr7": "*",
|
||||
"p3k/emoji-detector": "^1.2",
|
||||
"php-tmdb/api": "^4.1",
|
||||
"predis/predis": "^2.4",
|
||||
"symfony/asset": "7.2.*",
|
||||
"symfony/console": "7.2.*",
|
||||
"symfony/doctrine-messenger": "7.2.*",
|
||||
"symfony/dotenv": "7.2.*",
|
||||
"symfony/filesystem": "7.2.*",
|
||||
"symfony/finder": "7.2.*",
|
||||
"symfony/flex": "^2",
|
||||
"symfony/form": "7.2.*",
|
||||
"symfony/framework-bundle": "7.2.*",
|
||||
"symfony/ldap": "7.2.*",
|
||||
"symfony/mercure-bundle": "^0.3.9",
|
||||
"symfony/messenger": "7.2.*",
|
||||
"symfony/runtime": "7.2.*",
|
||||
"symfony/scheduler": "7.2.*",
|
||||
"symfony/security-bundle": "7.2.*",
|
||||
"symfony/stimulus-bundle": "^2.24",
|
||||
"symfony/twig-bundle": "7.2.*",
|
||||
"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.*",
|
||||
"symfonycasts/tailwind-bundle": "^0.10.0",
|
||||
@@ -81,6 +99,9 @@
|
||||
}
|
||||
},
|
||||
"require-dev": {
|
||||
"symfony/maker-bundle": "^1.62"
|
||||
"phpstan/phpstan": "^2.1",
|
||||
"symfony/maker-bundle": "^1.62",
|
||||
"symfony/stopwatch": "7.2.*",
|
||||
"symfony/web-profiler-bundle": "7.2.*"
|
||||
}
|
||||
}
|
||||
|
||||
3487
composer.lock
generated
3487
composer.lock
generated
File diff suppressed because it is too large
Load Diff
@@ -11,4 +11,10 @@ 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],
|
||||
];
|
||||
|
||||
56
config/dist/ldap.security.yaml
vendored
Normal file
56
config/dist/ldap.security.yaml
vendored
Normal file
@@ -0,0 +1,56 @@
|
||||
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: ^/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
|
||||
54
config/dist/local.security.yaml
vendored
Normal file
54
config/dist/local.security.yaml
vendored
Normal file
@@ -0,0 +1,54 @@
|
||||
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: ^/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: '%env(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
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
|
||||
66
config/packages/doctrine.yaml
Normal file
66
config/packages/doctrine.yaml
Normal file
@@ -0,0 +1,66 @@
|
||||
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:
|
||||
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
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
|
||||
@@ -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: Symfony\Component\HttpFoundation\Session\Storage\Handler\PdoSessionHandler
|
||||
|
||||
#esi: true
|
||||
#fragments: true
|
||||
|
||||
8
config/packages/mercure.yaml
Normal file
8
config/packages/mercure.yaml
Normal file
@@ -0,0 +1,8 @@
|
||||
mercure:
|
||||
hubs:
|
||||
default:
|
||||
url: '%env(MERCURE_URL)%'
|
||||
public_url: '%env(MERCURE_PUBLIC_URL)%'
|
||||
jwt:
|
||||
secret: '%env(MERCURE_JWT_SECRET)%'
|
||||
publish: '*'
|
||||
39
config/packages/messenger.yaml
Normal file
39
config/packages/messenger.yaml
Normal file
@@ -0,0 +1,39 @@
|
||||
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
|
||||
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\Monitor\Action\Command\MonitorTvEpisodeCommand': async
|
||||
'App\Monitor\Action\Command\MonitorTvSeasonCommand': async
|
||||
'App\Monitor\Action\Command\MonitorTvShowCommand': async
|
||||
'App\Monitor\Action\Command\MonitorMovieCommand': async
|
||||
|
||||
# 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://'
|
||||
@@ -5,13 +5,29 @@ 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_ldap:
|
||||
id: App\User\Framework\Security\LdapUserProvider
|
||||
|
||||
firewalls:
|
||||
dev:
|
||||
pattern: ^/(_(profiler|wdt)|css|images|js)/
|
||||
security: false
|
||||
main:
|
||||
lazy: true
|
||||
provider: users_in_memory
|
||||
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
|
||||
@@ -22,8 +38,9 @@ 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: ^/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/translation.yaml
Normal file
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:
|
||||
11
config/packages/web_profiler.yaml
Normal file
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 }
|
||||
@@ -4,5 +4,20 @@ controllersIndex:
|
||||
namespace: App\Controller
|
||||
type: attribute
|
||||
defaults:
|
||||
schemes: [ https ]
|
||||
schemes: [ 'https' ]
|
||||
|
||||
controllersUser:
|
||||
resource:
|
||||
path: ../src/User/Framework/Controller
|
||||
namespace: App\User\Framework\Controller
|
||||
type: attribute
|
||||
defaults:
|
||||
schemes: ['https']
|
||||
|
||||
controllersMonitor:
|
||||
resource:
|
||||
path: ../src/Monitor/Framework/Controller
|
||||
namespace: App\Monitor\Framework\Controller
|
||||
type: attribute
|
||||
defaults:
|
||||
schemes: ['https']
|
||||
|
||||
5
config/routes/ux_live_component.yaml
Normal file
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
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
|
||||
61
config/security.yaml
Normal file
61
config/security.yaml
Normal file
@@ -0,0 +1,61 @@
|
||||
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
|
||||
entry_point: form_login_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)%'
|
||||
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: ^/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
|
||||
@@ -4,6 +4,10 @@
|
||||
# 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:
|
||||
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)%'
|
||||
|
||||
services:
|
||||
# default configuration for services in *this* file
|
||||
@@ -22,3 +26,39 @@ 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:
|
||||
$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,42 @@
|
||||
services:
|
||||
php:
|
||||
image: code.caldwell.digital/home/torsearch/app:${TAG}
|
||||
image: registry.caldwell.digital/home/torsearch/app:${TAG}
|
||||
ports:
|
||||
- "8001:80"
|
||||
deploy:
|
||||
replicas: 2
|
||||
|
||||
worker:
|
||||
image: registry.caldwell.digital/home/torsearch/app:${TAG}
|
||||
volumes:
|
||||
- /mnt/media/downloads:/var/download
|
||||
command: php ./bin/console messenger:consume async -v --time-limit=3600 --limit=10
|
||||
deploy:
|
||||
replicas: 2
|
||||
|
||||
scheduler:
|
||||
image: registry.caldwell.digital/home/torsearch/app:${TAG}
|
||||
volumes:
|
||||
- /mnt/media/downloads:/var/download
|
||||
command: php ./bin/console messenger:consume scheduler_monitor -vv --time-limit=3600
|
||||
|
||||
mercure:
|
||||
image: dunglas/mercure
|
||||
restart: unless-stopped
|
||||
ports:
|
||||
- "3000:80"
|
||||
environment:
|
||||
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
|
||||
|
||||
volumes:
|
||||
mercure_config:
|
||||
mercure_data:
|
||||
|
||||
@@ -22,4 +22,10 @@ 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',
|
||||
],
|
||||
];
|
||||
|
||||
0
migrations/.gitignore
vendored
Executable file
0
migrations/.gitignore
vendored
Executable file
31
migrations/Version20241211055503.php
Normal 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
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
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
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
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
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
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
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
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
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
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
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
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);
|
||||
}
|
||||
}
|
||||
53
migrations/Version20250505211458.php
Normal file
53
migrations/Version20250505211458.php
Normal file
@@ -0,0 +1,53 @@
|
||||
<?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 Version20250505211458 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 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)', monitor_type VARCHAR(255) NOT NULL, season INT DEFAULT NULL, episode INT DEFAULT NULL, INDEX IDX_E1159985A76ED395 (user_id), PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8mb4 COLLATE `utf8mb4_unicode_ci` ENGINE = InnoDB
|
||||
SQL);
|
||||
$this->addSql(<<<'SQL'
|
||||
ALTER TABLE monitor ADD CONSTRAINT FK_E1159985A76ED395 FOREIGN KEY (user_id) REFERENCES user (id)
|
||||
SQL);
|
||||
$this->addSql(<<<'SQL'
|
||||
ALTER TABLE movie_monitor DROP FOREIGN KEY FK_C183DBABA76ED395
|
||||
SQL);
|
||||
$this->addSql(<<<'SQL'
|
||||
DROP TABLE movie_monitor
|
||||
SQL);
|
||||
}
|
||||
|
||||
public function down(Schema $schema): void
|
||||
{
|
||||
// this down() 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) CHARACTER SET utf8mb4 DEFAULT NULL COLLATE `utf8mb4_unicode_ci`, imdb_id VARCHAR(255) CHARACTER SET utf8mb4 NOT NULL COLLATE `utf8mb4_unicode_ci`, tmdb_id VARCHAR(255) CHARACTER SET utf8mb4 NOT NULL COLLATE `utf8mb4_unicode_ci`, status VARCHAR(255) CHARACTER SET utf8mb4 NOT NULL COLLATE `utf8mb4_unicode_ci`, 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 COMMENT = ''
|
||||
SQL);
|
||||
$this->addSql(<<<'SQL'
|
||||
ALTER TABLE movie_monitor ADD CONSTRAINT FK_C183DBABA76ED395 FOREIGN KEY (user_id) REFERENCES user (id)
|
||||
SQL);
|
||||
$this->addSql(<<<'SQL'
|
||||
ALTER TABLE monitor DROP FOREIGN KEY FK_E1159985A76ED395
|
||||
SQL);
|
||||
$this->addSql(<<<'SQL'
|
||||
DROP TABLE monitor
|
||||
SQL);
|
||||
}
|
||||
}
|
||||
35
migrations/Version20250510185814.php
Normal file
35
migrations/Version20250510185814.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 Version20250510185814 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 username 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 username
|
||||
SQL);
|
||||
}
|
||||
}
|
||||
47
migrations/Version20250511050008.php
Normal file
47
migrations/Version20250511050008.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 Version20250511050008 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 download ADD user_id INT DEFAULT NULL
|
||||
SQL);
|
||||
$this->addSql(<<<'SQL'
|
||||
ALTER TABLE download ADD CONSTRAINT FK_781A8270A76ED395 FOREIGN KEY (user_id) REFERENCES user (id)
|
||||
SQL);
|
||||
$this->addSql(<<<'SQL'
|
||||
CREATE INDEX IDX_781A8270A76ED395 ON download (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 download DROP FOREIGN KEY FK_781A8270A76ED395
|
||||
SQL);
|
||||
$this->addSql(<<<'SQL'
|
||||
DROP INDEX IDX_781A8270A76ED395 ON download
|
||||
SQL);
|
||||
$this->addSql(<<<'SQL'
|
||||
ALTER TABLE download DROP user_id
|
||||
SQL);
|
||||
}
|
||||
}
|
||||
8
phpstan.dist.neon
Normal file
8
phpstan.dist.neon
Normal file
@@ -0,0 +1,8 @@
|
||||
parameters:
|
||||
level: 6
|
||||
paths:
|
||||
- bin/
|
||||
- config/
|
||||
- public/
|
||||
- src/
|
||||
- tests/
|
||||
58
src/Command/ConfigSetCommand.php
Normal file
58
src/Command/ConfigSetCommand.php
Normal file
@@ -0,0 +1,58 @@
|
||||
<?php
|
||||
|
||||
namespace App\Command;
|
||||
|
||||
use Symfony\Component\Console\Attribute\AsCommand;
|
||||
use Symfony\Component\Console\Command\Command;
|
||||
use Symfony\Component\Console\Input\InputArgument;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Input\InputOption;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
use Symfony\Component\Console\Style\SymfonyStyle;
|
||||
|
||||
#[AsCommand(
|
||||
name: 'config:set',
|
||||
description: 'Add a short description for your command',
|
||||
)]
|
||||
class ConfigSetCommand extends Command
|
||||
{
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
protected function configure(): void
|
||||
{
|
||||
$this
|
||||
->addArgument('key', InputArgument::REQUIRED, 'Config key')
|
||||
->addArgument('value', InputArgument::REQUIRED, 'Config value')
|
||||
;
|
||||
}
|
||||
|
||||
protected function execute(InputInterface $input, OutputInterface $output): int
|
||||
{
|
||||
$io = new SymfonyStyle($input, $output);
|
||||
$key = $input->getArgument('key');
|
||||
$handlers = [
|
||||
'auth.method' => 'setAuthMethod',
|
||||
];
|
||||
|
||||
$handler = $handlers[$key];
|
||||
$this->$handler($input, $io);
|
||||
|
||||
$io->success('Success: "' . $input->getArgument('key') . '" set to "' . $input->getArgument('value') . '"');
|
||||
|
||||
return Command::SUCCESS;
|
||||
}
|
||||
|
||||
private function setAuthMethod(InputInterface $input, SymfonyStyle $io)
|
||||
{
|
||||
$config = [
|
||||
'local' => 'config/dist/local.security.yaml',
|
||||
'ldap' => 'config/dist/ldap.security.yaml',
|
||||
];
|
||||
$authMethod = $input->getArgument('value');
|
||||
$io->text('> Setting auth method to: ' . $authMethod);
|
||||
copy($config[$authMethod], 'config/packages/security.yaml');
|
||||
}
|
||||
}
|
||||
37
src/Controller/AlertController.php
Normal file
37
src/Controller/AlertController.php
Normal file
@@ -0,0 +1,37 @@
|
||||
<?php
|
||||
|
||||
namespace App\Controller;
|
||||
|
||||
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
||||
use Symfony\Component\DependencyInjection\Attribute\Autowire;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
use Symfony\Component\Mercure\HubInterface;
|
||||
use Symfony\Component\Mercure\Update;
|
||||
use Symfony\Component\Routing\Attribute\Route;
|
||||
use Twig\Environment;
|
||||
|
||||
final class AlertController extends AbstractController
|
||||
{
|
||||
public function __construct(
|
||||
#[Autowire(service: 'twig')] private readonly Environment $renderer,
|
||||
private readonly HubInterface $hub,
|
||||
) {}
|
||||
|
||||
#[Route('/alert', name: 'app_alert')]
|
||||
public function index(): Response
|
||||
{
|
||||
$update = new Update(
|
||||
'alerts',
|
||||
$this->renderer->render('broadcast/Alert.html.twig', [
|
||||
'alert_id' => 1,
|
||||
'title' => 'Added to queue',
|
||||
'message' => 'This is a testy test!',
|
||||
])
|
||||
);
|
||||
|
||||
$this->hub->publish($update);
|
||||
return $this->json([
|
||||
'Success' => 'Published'
|
||||
]);
|
||||
}
|
||||
}
|
||||
42
src/Controller/DownloadController.php
Normal file
42
src/Controller/DownloadController.php
Normal file
@@ -0,0 +1,42 @@
|
||||
<?php
|
||||
|
||||
namespace App\Controller;
|
||||
|
||||
use App\Download\Action\Input\DownloadMediaInput;
|
||||
use App\Download\Framework\Repository\DownloadRepository;
|
||||
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
use Symfony\Component\Mercure\HubInterface;
|
||||
use Symfony\Component\Messenger\MessageBusInterface;
|
||||
use Symfony\Component\Routing\Attribute\Route;
|
||||
|
||||
class DownloadController extends AbstractController
|
||||
{
|
||||
public function __construct(
|
||||
private DownloadRepository $downloadRepository,
|
||||
private MessageBusInterface $bus,
|
||||
) {}
|
||||
|
||||
#[Route('/download', name: 'app_download', methods: ['POST'])]
|
||||
public function download(
|
||||
DownloadMediaInput $input,
|
||||
): Response {
|
||||
$download = $this->downloadRepository->insert(
|
||||
$this->getUser(),
|
||||
$input->url,
|
||||
$input->title,
|
||||
$input->filename,
|
||||
$input->imdbId,
|
||||
$input->mediaType,
|
||||
"",
|
||||
);
|
||||
$input->downloadId = $download->getId();
|
||||
try {
|
||||
$this->bus->dispatch($input->toCommand());
|
||||
} catch (\Throwable $exception) {
|
||||
return $this->json(['error' => $exception->getMessage()], Response::HTTP_INTERNAL_SERVER_ERROR);
|
||||
}
|
||||
|
||||
return $this->json(['status' => 200, 'message' => 'Added to Queue']);
|
||||
}
|
||||
}
|
||||
@@ -2,17 +2,28 @@
|
||||
|
||||
namespace App\Controller;
|
||||
|
||||
use App\Download\Framework\Repository\DownloadRepository;
|
||||
use App\Tmdb\Tmdb;
|
||||
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
use Symfony\Component\Routing\Attribute\Route;
|
||||
|
||||
final class IndexController extends AbstractController
|
||||
{
|
||||
public function __construct(
|
||||
private readonly DownloadRepository $downloadRepository,
|
||||
private readonly Tmdb $tmdb,
|
||||
) {}
|
||||
|
||||
#[Route('/', name: 'app_index')]
|
||||
public function index(): Response
|
||||
{
|
||||
// dd($this->getUser()->getActiveDownloads());
|
||||
return $this->render('index/index.html.twig', [
|
||||
'controller_name' => 'IndexController',
|
||||
'active_downloads' => $this->getUser()->getActiveDownloads(),
|
||||
'recent_downloads' => $this->getUser()->getDownloads(),
|
||||
'popular_movies' => $this->tmdb->popularMovies(1, 6),
|
||||
'popular_tvshows' => $this->tmdb->popularTvShows(1, 6),
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,9 +6,12 @@ use App\Search\Action\Handler\GetMediaInfoHandler;
|
||||
use App\Search\Action\Handler\SearchHandler;
|
||||
use App\Search\Action\Input\GetMediaInfoInput;
|
||||
use App\Search\Action\Input\SearchInput;
|
||||
use Carbon\Carbon;
|
||||
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
use Symfony\Component\Routing\Attribute\Route;
|
||||
use Symfony\Contracts\Cache\CacheInterface;
|
||||
use Symfony\Contracts\Cache\ItemInterface;
|
||||
|
||||
final class SearchController extends AbstractController
|
||||
{
|
||||
@@ -30,10 +33,9 @@ final class SearchController extends AbstractController
|
||||
|
||||
#[Route('/result/{mediaType}/{tmdbId}', name: 'app_search_result')]
|
||||
public function result(
|
||||
GetMediaInfoInput $getDownloadOptionsInput,
|
||||
GetMediaInfoInput $input,
|
||||
): Response {
|
||||
$result = $this->getMediaInfoHandler->handle($getDownloadOptionsInput->toCommand());
|
||||
|
||||
$result = $this->getMediaInfoHandler->handle($input->toCommand());
|
||||
return $this->render('search/result.html.twig', [
|
||||
'results' => $result,
|
||||
'filter' => [
|
||||
|
||||
@@ -6,34 +6,89 @@ use App\Torrentio\Action\Handler\GetMovieOptionsHandler;
|
||||
use App\Torrentio\Action\Handler\GetTvShowOptionsHandler;
|
||||
use App\Torrentio\Action\Input\GetMovieOptionsInput;
|
||||
use App\Torrentio\Action\Input\GetTvShowOptionsInput;
|
||||
use Carbon\Carbon;
|
||||
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
use Symfony\Component\Mercure\HubInterface;
|
||||
use Symfony\Component\Mercure\Update;
|
||||
use Symfony\Component\Routing\Attribute\Route;
|
||||
use Symfony\Contracts\Cache\CacheInterface;
|
||||
use Symfony\Contracts\Cache\ItemInterface;
|
||||
|
||||
final class TorrentioController extends AbstractController
|
||||
{
|
||||
public function __construct(
|
||||
private readonly GetMovieOptionsHandler $getMovieOptionsHandler,
|
||||
private readonly GetTvShowOptionsHandler $getTvShowOptionsHandler,
|
||||
private readonly HubInterface $hub,
|
||||
private readonly \Twig\Environment $renderer,
|
||||
) {}
|
||||
|
||||
#[Route('/torrentio/movies/{imdbId}', name: 'app_torrentio_movies')]
|
||||
public function movieOptions(GetMovieOptionsInput $input): Response
|
||||
#[Route('/torrentio/movies/{tmdbId}/{imdbId}', name: 'app_torrentio_movies')]
|
||||
public function movieOptions(GetMovieOptionsInput $input, CacheInterface $cache): Response
|
||||
{
|
||||
$results = $this->getMovieOptionsHandler->handle($input->toCommand());
|
||||
$cacheId = sprintf(
|
||||
"page.torrentio.movies.%s.%s",
|
||||
$input->tmdbId,
|
||||
$input->imdbId
|
||||
);
|
||||
|
||||
return $this->render('torrentio/movies.html.twig', [
|
||||
'results' => $results,
|
||||
]);
|
||||
return $cache->get($cacheId, function (ItemInterface $item) use ($input) {
|
||||
$item->expiresAt(Carbon::now()->addHour()->setMinute(0)->setSecond(0));
|
||||
$results = $this->getMovieOptionsHandler->handle($input->toCommand());
|
||||
return $this->render('torrentio/movies.html.twig', [
|
||||
'results' => $results,
|
||||
]);
|
||||
});
|
||||
}
|
||||
|
||||
#[Route('/torrentio/tvshows/{tmdbId}/{imdbId}/{season?}/{episode?}', name: 'app_torrentio_tvshows')]
|
||||
public function tvShowOptions(GetTvShowOptionsInput $input): Response
|
||||
public function tvShowOptions(GetTvShowOptionsInput $input, CacheInterface $cache): Response
|
||||
{
|
||||
$results = $this->getTvShowOptionsHandler->handle($input->toCommand());
|
||||
$cacheId = sprintf(
|
||||
"page.torrentio.tvshows.%s.%s.%s.%s",
|
||||
$input->tmdbId,
|
||||
$input->imdbId,
|
||||
$input->season,
|
||||
$input->episode,
|
||||
);
|
||||
|
||||
return $this->render('torrentio/tvshows.html.twig', [
|
||||
'results' => $results,
|
||||
]);
|
||||
return $cache->get($cacheId, function (ItemInterface $item) use ($input) {
|
||||
$item->expiresAt(Carbon::now()->addHour()->setMinute(0)->setSecond(0));
|
||||
$results = $this->getTvShowOptionsHandler->handle($input->toCommand());
|
||||
return $this->render('torrentio/tvshows.html.twig', [
|
||||
'results' => $results,
|
||||
]);
|
||||
});
|
||||
}
|
||||
|
||||
#[Route('/torrentio/tvshows/clear/{tmdbId}/{imdbId}/{season?}/{episode?}', name: 'app_clear_torrentio_tvshows')]
|
||||
public function clearTvShowOptions(GetTvShowOptionsInput $input, CacheInterface $cache): Response
|
||||
{
|
||||
$cacheId = sprintf(
|
||||
"page.torrentio.tvshows.%s.%s.%s.%s",
|
||||
$input->tmdbId,
|
||||
$input->imdbId,
|
||||
$input->season,
|
||||
$input->episode,
|
||||
);
|
||||
$cache->delete($cacheId);
|
||||
|
||||
$this->hub->publish(new Update(
|
||||
'alerts',
|
||||
$this->renderer->render('broadcast/Alert.html.twig', [
|
||||
'alert_id' => uniqid(),
|
||||
'title' => 'Success',
|
||||
'message' => 'Torrentio cache Cleared.',
|
||||
])
|
||||
));
|
||||
|
||||
return $cache->get($cacheId, function (ItemInterface $item) use ($input) {
|
||||
$item->expiresAt(Carbon::now()->addHour()->setMinute(0)->setSecond(0));
|
||||
$results = $this->getTvShowOptionsHandler->handle($input->toCommand());
|
||||
return $this->render('torrentio/tvshows.html.twig', [
|
||||
'results' => $results,
|
||||
]);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
21
src/Download/Action/Command/DownloadMediaCommand.php
Normal file
21
src/Download/Action/Command/DownloadMediaCommand.php
Normal file
@@ -0,0 +1,21 @@
|
||||
<?php
|
||||
|
||||
namespace App\Download\Action\Command;
|
||||
|
||||
use OneToMany\RichBundle\Contract\CommandInterface;
|
||||
|
||||
/**
|
||||
* @implements CommandInterface<DownloadMediaCommand>
|
||||
*/
|
||||
class DownloadMediaCommand implements CommandInterface
|
||||
{
|
||||
public function __construct(
|
||||
public string $url,
|
||||
public string $title,
|
||||
public string $filename,
|
||||
public string $mediaType,
|
||||
public string $imdbId,
|
||||
public int $userId,
|
||||
public ?int $downloadId = null,
|
||||
) {}
|
||||
}
|
||||
@@ -1,14 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Download\Action\Command;
|
||||
|
||||
use OneToMany\RichBundle\Contract\CommandInterface;
|
||||
|
||||
class GetDownloadOptionsCommand implements CommandInterface
|
||||
{
|
||||
/** @implements CommandInterface<GetDownloadOptionsCommand> */
|
||||
public function __construct(
|
||||
public string $tmdbId,
|
||||
public string $mediaType,
|
||||
) {}
|
||||
}
|
||||
58
src/Download/Action/Handler/DownloadMediaHandler.php
Normal file
58
src/Download/Action/Handler/DownloadMediaHandler.php
Normal file
@@ -0,0 +1,58 @@
|
||||
<?php
|
||||
|
||||
namespace App\Download\Action\Handler;
|
||||
|
||||
use App\Download\Action\Command\DownloadMediaCommand;
|
||||
use App\Download\Action\Result\DownloadMediaResult;
|
||||
use App\Download\Framework\Repository\DownloadRepository;
|
||||
use App\Download\Downloader\DownloaderInterface;
|
||||
use App\User\Framework\Repository\UserRepository;
|
||||
use OneToMany\RichBundle\Contract\CommandInterface;
|
||||
use OneToMany\RichBundle\Contract\HandlerInterface;
|
||||
use OneToMany\RichBundle\Contract\ResultInterface;
|
||||
use Symfony\Component\Messenger\Exception\UnrecoverableMessageHandlingException;
|
||||
|
||||
/** @implements HandlerInterface<DownloadMediaCommand, DownloadMediaResult> */
|
||||
readonly class DownloadMediaHandler implements HandlerInterface
|
||||
{
|
||||
public function __construct(
|
||||
private DownloaderInterface $downloader,
|
||||
private DownloadRepository $downloadRepository,
|
||||
private UserRepository $userRepository,
|
||||
) {}
|
||||
|
||||
public function handle(CommandInterface $command): ResultInterface
|
||||
{
|
||||
if (null === $command->downloadId) {
|
||||
$download = $this->downloadRepository->insert(
|
||||
$this->userRepository->find($command->userId),
|
||||
$command->url,
|
||||
$command->title,
|
||||
$command->filename,
|
||||
$command->imdbId,
|
||||
$command->mediaType,
|
||||
""
|
||||
);
|
||||
} else {
|
||||
$download = $this->downloadRepository->find($command->downloadId);
|
||||
}
|
||||
|
||||
try {
|
||||
$this->downloadRepository->updateStatus($download->getId(), 'In Progress');
|
||||
|
||||
$this->downloader->download(
|
||||
$command->mediaType,
|
||||
$command->title,
|
||||
$command->url,
|
||||
$download->getId()
|
||||
);
|
||||
|
||||
$this->downloadRepository->updateStatus($download->getId(), 'Complete');
|
||||
|
||||
} catch (\Throwable $exception) {
|
||||
throw new UnrecoverableMessageHandlingException($exception->getMessage(), 500);
|
||||
}
|
||||
|
||||
return new DownloadMediaResult(200, "Success.");
|
||||
}
|
||||
}
|
||||
@@ -1,22 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Download\Action\Handler;
|
||||
|
||||
use App\Tmdb\Tmdb;
|
||||
use App\Torrentio\Client\Torrentio;
|
||||
use OneToMany\RichBundle\Contract\CommandInterface;
|
||||
use OneToMany\RichBundle\Contract\HandlerInterface;
|
||||
use OneToMany\RichBundle\Contract\ResultInterface;
|
||||
|
||||
class GetDownloadOptionsHandler implements HandlerInterface
|
||||
{
|
||||
public function __construct(
|
||||
private readonly Tmdb $tmdb,
|
||||
private readonly Torrentio $torrentio,
|
||||
) {}
|
||||
|
||||
public function handle(CommandInterface $command): ResultInterface
|
||||
{
|
||||
$media = $this->tmdb->mediaDetails($command->tmdbId, $command->mediaType);
|
||||
}
|
||||
}
|
||||
46
src/Download/Action/Input/DownloadMediaInput.php
Normal file
46
src/Download/Action/Input/DownloadMediaInput.php
Normal file
@@ -0,0 +1,46 @@
|
||||
<?php
|
||||
|
||||
namespace App\Download\Action\Input;
|
||||
|
||||
use App\Download\Action\Command\DownloadMediaCommand;
|
||||
use OneToMany\RichBundle\Attribute\SourceRequest;
|
||||
use OneToMany\RichBundle\Contract\CommandInterface;
|
||||
use OneToMany\RichBundle\Contract\InputInterface;
|
||||
|
||||
/** @implements InputInterface<DownloadMediaInput> */
|
||||
class DownloadMediaInput implements InputInterface
|
||||
{
|
||||
public function __construct(
|
||||
#[SourceRequest('url')]
|
||||
public string $url,
|
||||
|
||||
#[SourceRequest('title')]
|
||||
public string $title,
|
||||
|
||||
#[SourceRequest('filename')]
|
||||
public string $filename,
|
||||
|
||||
#[SourceRequest('mediaType')]
|
||||
public string $mediaType,
|
||||
|
||||
#[SourceRequest('imdbId')]
|
||||
public string $imdbId,
|
||||
|
||||
public ?int $userId = null,
|
||||
|
||||
public ?int $downloadId = null,
|
||||
) {}
|
||||
|
||||
public function toCommand(): CommandInterface
|
||||
{
|
||||
return new DownloadMediaCommand(
|
||||
$this->url,
|
||||
$this->title,
|
||||
$this->filename,
|
||||
$this->mediaType,
|
||||
$this->imdbId,
|
||||
$this->downloadId,
|
||||
$this->userId
|
||||
);
|
||||
}
|
||||
}
|
||||
14
src/Download/Action/Result/DownloadMediaResult.php
Normal file
14
src/Download/Action/Result/DownloadMediaResult.php
Normal file
@@ -0,0 +1,14 @@
|
||||
<?php
|
||||
|
||||
namespace App\Download\Action\Result;
|
||||
|
||||
use OneToMany\RichBundle\Contract\ResultInterface;
|
||||
|
||||
/** @implements ResultInterface<DownloadMediaResult> */
|
||||
class DownloadMediaResult implements ResultInterface
|
||||
{
|
||||
public function __construct(
|
||||
public int $status,
|
||||
public string $message,
|
||||
) {}
|
||||
}
|
||||
@@ -1,12 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Download\Action\Result;
|
||||
|
||||
use App\Tmdb\TmdbResult;
|
||||
|
||||
class GetDownloadOptionsResult
|
||||
{
|
||||
public function __construct(
|
||||
public TmdbResult $media,
|
||||
) {}
|
||||
}
|
||||
20
src/Download/Downloader/DownloaderInterface.php
Normal file
20
src/Download/Downloader/DownloaderInterface.php
Normal file
@@ -0,0 +1,20 @@
|
||||
<?php
|
||||
|
||||
namespace App\Download\Downloader;
|
||||
|
||||
|
||||
use App\Message\DownloadMessage;
|
||||
use App\Message\DownloadMovieMessage;
|
||||
use App\Message\DownloadTvShowMessage;
|
||||
|
||||
interface DownloaderInterface
|
||||
{
|
||||
/**
|
||||
* @param string $baseDir
|
||||
* @param string $title
|
||||
* @param string $url
|
||||
* @return void
|
||||
* Downloads the requested file.
|
||||
*/
|
||||
public function download(string $baseDir, string $title, string $url, ?int $downloadId): void;
|
||||
}
|
||||
64
src/Download/Downloader/ProcessDownloader.php
Normal file
64
src/Download/Downloader/ProcessDownloader.php
Normal file
@@ -0,0 +1,64 @@
|
||||
<?php
|
||||
|
||||
namespace App\Download\Downloader;
|
||||
|
||||
use App\Download\Framework\Entity\Download;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use Symfony\Component\Process\Exception\ProcessFailedException;
|
||||
use Symfony\Component\Process\Process;
|
||||
|
||||
class ProcessDownloader implements DownloaderInterface
|
||||
{
|
||||
public function __construct(
|
||||
private EntityManagerInterface $entityManager,
|
||||
) {}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function download(string $baseDir, string $title, string $url, ?int $downloadId): void
|
||||
{
|
||||
/** @var Download $downloadEntity */
|
||||
$downloadEntity = $this->entityManager->getRepository(Download::class)->find($downloadId);
|
||||
$downloadEntity->setProgress(0);
|
||||
$this->entityManager->flush();
|
||||
|
||||
$process = new Process([
|
||||
'/bin/sh',
|
||||
'/var/www/bash/app/wget_download.sh',
|
||||
$baseDir,
|
||||
$title,
|
||||
$url
|
||||
]);
|
||||
|
||||
$process->setTimeout(1800); // 30 min
|
||||
$process->setIdleTimeout(600); // 10 min
|
||||
|
||||
$process->start();
|
||||
|
||||
try {
|
||||
$progress = 0;
|
||||
$this->entityManager->flush();
|
||||
$process->wait(function ($type, $buffer) use ($progress, $downloadEntity): void {
|
||||
if (Process::ERR === $type) {
|
||||
$pregMatchOutput = [];
|
||||
preg_match('/[\d]+%/', $buffer, $pregMatchOutput);
|
||||
|
||||
if (!empty($pregMatchOutput)) {
|
||||
if ($pregMatchOutput[0] !== $progress) {
|
||||
$progress = (int) $pregMatchOutput[0];
|
||||
$downloadEntity->setProgress($progress);
|
||||
$this->entityManager->flush();
|
||||
}
|
||||
}
|
||||
}
|
||||
fwrite(STDOUT, $buffer);
|
||||
});
|
||||
$downloadEntity->setProgress(100);
|
||||
} catch (ProcessFailedException $exception) {
|
||||
$downloadEntity->setStatus('Failed');
|
||||
}
|
||||
|
||||
$this->entityManager->flush();
|
||||
}
|
||||
}
|
||||
27
src/Download/Downloader/WgetDownloader.php
Normal file
27
src/Download/Downloader/WgetDownloader.php
Normal file
@@ -0,0 +1,27 @@
|
||||
<?php
|
||||
|
||||
namespace App\Download\Downloader;
|
||||
|
||||
use App\Message\DownloadMessage;
|
||||
use App\Message\DownloadMovieMessage;
|
||||
use App\Message\DownloadTvShowMessage;
|
||||
|
||||
class WgetDownloader implements DownloaderInterface
|
||||
{
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
* SSHs into the NAS and performs the download.
|
||||
* This way retains the fast DL speed on the NAS.
|
||||
*/
|
||||
public function download(string $baseDir, string $title, string $url, ?int $downloadId): void
|
||||
{
|
||||
// SSHs into the NAS, cds into movies dir, makes new dir based on filename, cds into that dir, downloads movie
|
||||
system(sprintf(
|
||||
'sh /var/www/bash/app/wget_download.sh "%s" "%s" "%s"',
|
||||
$baseDir,
|
||||
$title,
|
||||
$url
|
||||
));
|
||||
}
|
||||
}
|
||||
0
src/Download/Framework/Entity/.gitignore
vendored
Normal file
0
src/Download/Framework/Entity/.gitignore
vendored
Normal file
165
src/Download/Framework/Entity/Download.php
Normal file
165
src/Download/Framework/Entity/Download.php
Normal file
@@ -0,0 +1,165 @@
|
||||
<?php
|
||||
|
||||
namespace App\Download\Framework\Entity;
|
||||
|
||||
use App\Download\Framework\Repository\DownloadRepository;
|
||||
use App\User\Framework\Entity\User;
|
||||
use Doctrine\ORM\Mapping as ORM;
|
||||
use Symfony\UX\Turbo\Attribute\Broadcast;
|
||||
|
||||
#[ORM\Entity(repositoryClass: DownloadRepository::class)]
|
||||
#[Broadcast(template: 'broadcast/Download.stream.html.twig')]
|
||||
class Download
|
||||
{
|
||||
#[ORM\Id]
|
||||
#[ORM\GeneratedValue]
|
||||
#[ORM\Column]
|
||||
private ?int $id = null;
|
||||
|
||||
#[ORM\Column(length: 20, nullable: true)]
|
||||
private ?string $imdbId = null;
|
||||
|
||||
#[ORM\Column(length: 255, nullable: true)]
|
||||
private ?string $mediaType = null;
|
||||
|
||||
#[ORM\Column(length: 255, nullable: true)]
|
||||
private ?string $title = null;
|
||||
|
||||
#[ORM\Column(length: 1024)]
|
||||
private ?string $url = null;
|
||||
|
||||
#[ORM\Column(length: 1024, nullable: true)]
|
||||
private ?string $filename = null;
|
||||
|
||||
#[ORM\Column(length: 255, nullable: true)]
|
||||
private ?string $status = null;
|
||||
|
||||
#[ORM\Column(nullable: true)]
|
||||
private ?int $progress = null;
|
||||
|
||||
#[ORM\Column(length: 255, nullable: true)]
|
||||
private ?string $batchId = null;
|
||||
|
||||
#[ORM\ManyToOne(inversedBy: 'downloads')]
|
||||
private ?User $user = null;
|
||||
|
||||
public function getId(): ?int
|
||||
{
|
||||
return $this->id;
|
||||
}
|
||||
|
||||
public function setId(int $id): static
|
||||
{
|
||||
$this->id = $id;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getImdbId(): ?string
|
||||
{
|
||||
return $this->imdbId;
|
||||
}
|
||||
|
||||
public function setImdbId(?string $imdbId): static
|
||||
{
|
||||
$this->imdbId = $imdbId;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getMediaType(): ?string
|
||||
{
|
||||
return $this->mediaType;
|
||||
}
|
||||
|
||||
public function setMediaType(?string $mediaType): static
|
||||
{
|
||||
$this->mediaType = $mediaType;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getTitle(): ?string
|
||||
{
|
||||
return $this->title;
|
||||
}
|
||||
|
||||
public function setTitle(?string $title): static
|
||||
{
|
||||
$this->title = $title;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getUrl(): ?string
|
||||
{
|
||||
return $this->url;
|
||||
}
|
||||
|
||||
public function setUrl(string $url): static
|
||||
{
|
||||
$this->url = $url;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getFilename(): ?string
|
||||
{
|
||||
return $this->filename;
|
||||
}
|
||||
|
||||
public function setFilename(?string $filename): static
|
||||
{
|
||||
$this->filename = $filename;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getStatus(): ?string
|
||||
{
|
||||
return $this->status;
|
||||
}
|
||||
|
||||
public function setStatus(?string $status): static
|
||||
{
|
||||
$this->status = $status;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getProgress(): ?int
|
||||
{
|
||||
return $this->progress;
|
||||
}
|
||||
|
||||
public function setProgress(?int $progress): static
|
||||
{
|
||||
$this->progress = $progress;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getBatchId(): ?string
|
||||
{
|
||||
return $this->batchId;
|
||||
}
|
||||
|
||||
public function setBatchId(?string $batchId): static
|
||||
{
|
||||
$this->batchId = $batchId;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getUser(): ?User
|
||||
{
|
||||
return $this->user;
|
||||
}
|
||||
|
||||
public function setUser(?User $user): static
|
||||
{
|
||||
$this->user = $user;
|
||||
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
0
src/Download/Framework/Repository/.gitignore
vendored
Normal file
0
src/Download/Framework/Repository/.gitignore
vendored
Normal file
107
src/Download/Framework/Repository/DownloadRepository.php
Normal file
107
src/Download/Framework/Repository/DownloadRepository.php
Normal file
@@ -0,0 +1,107 @@
|
||||
<?php
|
||||
|
||||
namespace App\Download\Framework\Repository;
|
||||
|
||||
use App\Download\Framework\Entity\Download;
|
||||
use App\User\Framework\Entity\User;
|
||||
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
|
||||
use Doctrine\Persistence\ManagerRegistry;
|
||||
use Knp\Component\Pager\Paginator;
|
||||
use Knp\Component\Pager\PaginatorInterface;
|
||||
use Symfony\Component\Security\Core\User\UserInterface;
|
||||
|
||||
/**
|
||||
* @extends ServiceEntityRepository<Download>
|
||||
*/
|
||||
class DownloadRepository extends ServiceEntityRepository
|
||||
{
|
||||
private ManagerRegistry $managerRegistry;
|
||||
|
||||
public function __construct(ManagerRegistry $registry, ManagerRegistry $managerRegistry)
|
||||
{
|
||||
parent::__construct($registry, Download::class);
|
||||
$this->managerRegistry = $managerRegistry;
|
||||
}
|
||||
|
||||
public function getCompletePaginated(int $pageNumber = 1, int $perPage = 10)
|
||||
{
|
||||
$firstResult = ($pageNumber - 1) * $perPage;
|
||||
$query = $this->createQueryBuilder('d')
|
||||
->andWhere('d.status IN (:statuses)')
|
||||
->orderBy('d.id', 'DESC')
|
||||
->setParameter('statuses', ['Complete'])
|
||||
->setFirstResult($firstResult)
|
||||
->setMaxResults($perPage)
|
||||
->getQuery();
|
||||
|
||||
return new \Doctrine\ORM\Tools\Pagination\Paginator($query);
|
||||
}
|
||||
|
||||
public function getActivePaginated(int $pageNumber = 1, int $perPage = 5)
|
||||
{
|
||||
$firstResult = ($pageNumber - 1) * $perPage;
|
||||
$query = $this->createQueryBuilder('d')
|
||||
->andWhere('d.status IN (:statuses)')
|
||||
->orderBy('d.id', 'ASC')
|
||||
->setParameter('statuses', ['New', 'In Progress'])
|
||||
->setFirstResult($firstResult)
|
||||
->setMaxResults($perPage)
|
||||
->getQuery();
|
||||
|
||||
return new \Doctrine\ORM\Tools\Pagination\Paginator($query);
|
||||
}
|
||||
|
||||
public function insert(
|
||||
UserInterface $user,
|
||||
string $url,
|
||||
string $title,
|
||||
string $filename,
|
||||
string $imdbId,
|
||||
string $mediaType,
|
||||
string $batchId,
|
||||
string $status = 'New'
|
||||
): Download {
|
||||
/** @var User $user */
|
||||
$download = (new Download())
|
||||
->setUser($user)
|
||||
->setUrl($url)
|
||||
->setTitle($title)
|
||||
->setFilename($filename)
|
||||
->setImdbId($imdbId)
|
||||
->setMediaType($mediaType)
|
||||
->setBatchId($batchId)
|
||||
->setProgress(0)
|
||||
->setStatus($status);
|
||||
|
||||
$this->getEntityManager()->persist($download);
|
||||
$this->getEntityManager()->flush();
|
||||
|
||||
return $download;
|
||||
}
|
||||
|
||||
public function updateStatus(int $id, string $status): Download
|
||||
{
|
||||
$download = $this->find($id);
|
||||
$download->setStatus($status);
|
||||
$this->getEntityManager()->flush();
|
||||
return $download;
|
||||
}
|
||||
|
||||
public function delete(int $id)
|
||||
{
|
||||
$entity = $this->find($id);
|
||||
$this->getEntityManager()->remove($entity);
|
||||
$this->getEntityManager()->flush();
|
||||
}
|
||||
|
||||
public function latest(int $limit = 1)
|
||||
{
|
||||
return $this->createQueryBuilder('d')
|
||||
->andWhere('d.status IN (:statuses)')
|
||||
->setParameter('statuses', ['Complete'])
|
||||
->setMaxResults($limit)
|
||||
->orderBy('d.id', 'DESC')
|
||||
->getQuery()
|
||||
->getResult();
|
||||
}
|
||||
}
|
||||
18
src/Monitor/Action/Command/AddMonitorCommand.php
Normal file
18
src/Monitor/Action/Command/AddMonitorCommand.php
Normal file
@@ -0,0 +1,18 @@
|
||||
<?php
|
||||
|
||||
namespace App\Monitor\Action\Command;
|
||||
|
||||
use OneToMany\RichBundle\Contract\CommandInterface;
|
||||
|
||||
class AddMonitorCommand implements CommandInterface
|
||||
{
|
||||
public function __construct(
|
||||
public string $userId,
|
||||
public string $title,
|
||||
public string $imdbId,
|
||||
public string $tmdbId,
|
||||
public string $monitorType,
|
||||
public ?int $season,
|
||||
public ?int $episode,
|
||||
) {}
|
||||
}
|
||||
12
src/Monitor/Action/Command/MonitorMovieCommand.php
Normal file
12
src/Monitor/Action/Command/MonitorMovieCommand.php
Normal file
@@ -0,0 +1,12 @@
|
||||
<?php
|
||||
|
||||
namespace App\Monitor\Action\Command;
|
||||
|
||||
use OneToMany\RichBundle\Contract\CommandInterface;
|
||||
|
||||
class MonitorMovieCommand implements CommandInterface
|
||||
{
|
||||
public function __construct(
|
||||
public int $movieMonitorId,
|
||||
) {}
|
||||
}
|
||||
12
src/Monitor/Action/Command/MonitorTvEpisodeCommand.php
Normal file
12
src/Monitor/Action/Command/MonitorTvEpisodeCommand.php
Normal file
@@ -0,0 +1,12 @@
|
||||
<?php
|
||||
|
||||
namespace App\Monitor\Action\Command;
|
||||
|
||||
use OneToMany\RichBundle\Contract\CommandInterface;
|
||||
|
||||
class MonitorTvEpisodeCommand implements CommandInterface
|
||||
{
|
||||
public function __construct(
|
||||
public int $movieMonitorId,
|
||||
) {}
|
||||
}
|
||||
12
src/Monitor/Action/Command/MonitorTvSeasonCommand.php
Normal file
12
src/Monitor/Action/Command/MonitorTvSeasonCommand.php
Normal file
@@ -0,0 +1,12 @@
|
||||
<?php
|
||||
|
||||
namespace App\Monitor\Action\Command;
|
||||
|
||||
use OneToMany\RichBundle\Contract\CommandInterface;
|
||||
|
||||
class MonitorTvSeasonCommand implements CommandInterface
|
||||
{
|
||||
public function __construct(
|
||||
public int $monitorId,
|
||||
) {}
|
||||
}
|
||||
12
src/Monitor/Action/Command/MonitorTvShowCommand.php
Normal file
12
src/Monitor/Action/Command/MonitorTvShowCommand.php
Normal file
@@ -0,0 +1,12 @@
|
||||
<?php
|
||||
|
||||
namespace App\Monitor\Action\Command;
|
||||
|
||||
use OneToMany\RichBundle\Contract\CommandInterface;
|
||||
|
||||
class MonitorTvShowCommand implements CommandInterface
|
||||
{
|
||||
public function __construct(
|
||||
public int $monitorId,
|
||||
) {}
|
||||
}
|
||||
48
src/Monitor/Action/Handler/AddMonitorHandler.php
Normal file
48
src/Monitor/Action/Handler/AddMonitorHandler.php
Normal file
@@ -0,0 +1,48 @@
|
||||
<?php
|
||||
|
||||
namespace App\Monitor\Action\Handler;
|
||||
|
||||
use App\Monitor\Action\Command\AddMonitorCommand;
|
||||
use App\Monitor\Action\Result\AddMonitorResult;
|
||||
use App\Monitor\Framework\Entity\Monitor;
|
||||
use App\Monitor\Framework\Repository\MonitorRepository;
|
||||
use App\User\Framework\Repository\UserRepository;
|
||||
use DateTimeImmutable;
|
||||
use OneToMany\RichBundle\Contract\CommandInterface;
|
||||
use OneToMany\RichBundle\Contract\HandlerInterface;
|
||||
use OneToMany\RichBundle\Contract\ResultInterface;
|
||||
|
||||
/** @implements HandlerInterface<AddMonitorCommand> */
|
||||
readonly class AddMonitorHandler implements HandlerInterface
|
||||
{
|
||||
public function __construct(
|
||||
private MonitorRepository $movieMonitorRepository,
|
||||
private UserRepository $userRepository,
|
||||
) {}
|
||||
|
||||
public function handle(CommandInterface $command): ResultInterface
|
||||
{
|
||||
$user = $this->userRepository->find($command->userId);
|
||||
$monitor = (new Monitor())
|
||||
->setUser($user)
|
||||
->setTmdbId($command->tmdbId)
|
||||
->setImdbId($command->imdbId)
|
||||
->setTitle($command->title)
|
||||
->setMonitorType($command->monitorType)
|
||||
->setSeason($command->season)
|
||||
->setEpisode($command->episode)
|
||||
->setCreatedAt(new DateTimeImmutable())
|
||||
->setSearchCount(0)
|
||||
->setStatus('New');
|
||||
|
||||
$this->movieMonitorRepository->getEntityManager()->persist($monitor);
|
||||
$this->movieMonitorRepository->getEntityManager()->flush();
|
||||
|
||||
return new AddMonitorResult(
|
||||
status: 'OK',
|
||||
result: [
|
||||
'monitor' => $monitor,
|
||||
]
|
||||
);
|
||||
}
|
||||
}
|
||||
77
src/Monitor/Action/Handler/MonitorMovieHandler.php
Normal file
77
src/Monitor/Action/Handler/MonitorMovieHandler.php
Normal file
@@ -0,0 +1,77 @@
|
||||
<?php
|
||||
|
||||
namespace App\Monitor\Action\Handler;
|
||||
|
||||
use App\Download\Action\Command\DownloadMediaCommand;
|
||||
use App\Monitor\Action\Command\MonitorMovieCommand;
|
||||
use App\Monitor\Action\Result\MonitorMovieResult;
|
||||
use App\Monitor\Framework\Entity\Monitor;
|
||||
use App\Monitor\Framework\Repository\MonitorRepository;
|
||||
use App\Monitor\Service\MonitorOptionEvaluator;
|
||||
use App\Torrentio\Action\Command\GetMovieOptionsCommand;
|
||||
use App\Torrentio\Action\Handler\GetMovieOptionsHandler;
|
||||
use DateTimeImmutable;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use OneToMany\RichBundle\Contract\CommandInterface;
|
||||
use OneToMany\RichBundle\Contract\HandlerInterface;
|
||||
use OneToMany\RichBundle\Contract\ResultInterface;
|
||||
use Psr\Log\LoggerInterface;
|
||||
use Symfony\Bundle\SecurityBundle\Security;
|
||||
use Symfony\Component\Messenger\MessageBusInterface;
|
||||
|
||||
/** @implements HandlerInterface<MonitorMovieCommand> */
|
||||
readonly class MonitorMovieHandler implements HandlerInterface
|
||||
{
|
||||
public function __construct(
|
||||
private MonitorRepository $movieMonitorRepository,
|
||||
private GetMovieOptionsHandler $getMovieOptionsHandler,
|
||||
private MonitorOptionEvaluator $monitorOptionEvaluator,
|
||||
private EntityManagerInterface $entityManager,
|
||||
private MessageBusInterface $bus,
|
||||
private LoggerInterface $logger,
|
||||
private Security $security,
|
||||
) {}
|
||||
|
||||
public function handle(CommandInterface $command): ResultInterface
|
||||
{
|
||||
$this->logger->info('> [MonitorMovieHandler] Executing MonitorMovieHandler');
|
||||
/** @var Monitor $monitor */
|
||||
$monitor = $this->movieMonitorRepository->find($command->movieMonitorId);
|
||||
$monitor->setStatus('In Progress');
|
||||
|
||||
$this->logger->info('> [MonitorMovieHandler] Searching for "' . $monitor->getTitle() . '" download options');
|
||||
$results = $this->getMovieOptionsHandler->handle(
|
||||
new GetMovieOptionsCommand($monitor->getTmdbId(), $monitor->getImdbId())
|
||||
);
|
||||
$this->logger->info('> [MonitorMovieHandler] Found ' . count($results->results) . ' download options');
|
||||
|
||||
$result = $this->monitorOptionEvaluator->evaluateOptions($monitor, $results->results);
|
||||
|
||||
if (null !== $result) {
|
||||
$this->logger->info('> [MonitorMovieHandler] 1 result found: dispatching DownloadMediaCommand for "' . $result->title . '"');
|
||||
$this->bus->dispatch(new DownloadMediaCommand(
|
||||
$result->url,
|
||||
$monitor->getTitle(),
|
||||
$result->filename,
|
||||
'movies',
|
||||
$monitor->getImdbId(),
|
||||
$monitor->getUser()->getId(),
|
||||
));
|
||||
$monitor->setStatus('Complete');
|
||||
$monitor->setDownloadedAt(new DateTimeIMmutable());
|
||||
} else {
|
||||
$monitor->setStatus('Active');
|
||||
}
|
||||
|
||||
$monitor->setLastSearch(new DateTimeImmutable());
|
||||
$monitor->incrementSearchCount();
|
||||
$this->entityManager->flush();
|
||||
|
||||
return new MonitorMovieResult(
|
||||
status: 'OK',
|
||||
result: [
|
||||
'monitor' => $monitor,
|
||||
]
|
||||
);
|
||||
}
|
||||
}
|
||||
81
src/Monitor/Action/Handler/MonitorTvEpisodeHandler.php
Normal file
81
src/Monitor/Action/Handler/MonitorTvEpisodeHandler.php
Normal file
@@ -0,0 +1,81 @@
|
||||
<?php
|
||||
|
||||
namespace App\Monitor\Action\Handler;
|
||||
|
||||
use App\Download\Action\Command\DownloadMediaCommand;
|
||||
use App\Monitor\Action\Command\MonitorMovieCommand;
|
||||
use App\Monitor\Action\Result\MonitorMovieResult;
|
||||
use App\Monitor\Framework\Repository\MonitorRepository;
|
||||
use App\Monitor\Service\MonitorOptionEvaluator;
|
||||
use App\Torrentio\Action\Command\GetTvShowOptionsCommand;
|
||||
use App\Torrentio\Action\Handler\GetTvShowOptionsHandler;
|
||||
use DateTimeImmutable;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use OneToMany\RichBundle\Contract\CommandInterface;
|
||||
use OneToMany\RichBundle\Contract\HandlerInterface;
|
||||
use OneToMany\RichBundle\Contract\ResultInterface;
|
||||
use Psr\Log\LoggerInterface;
|
||||
use Symfony\Component\Messenger\MessageBusInterface;
|
||||
|
||||
/** @implements HandlerInterface<MonitorMovieCommand> */
|
||||
readonly class MonitorTvEpisodeHandler implements HandlerInterface
|
||||
{
|
||||
public function __construct(
|
||||
private GetTvShowOptionsHandler $getTvShowOptionsHandler,
|
||||
private MonitorOptionEvaluator $monitorOptionEvaluator,
|
||||
private EntityManagerInterface $entityManager,
|
||||
private MessageBusInterface $bus,
|
||||
private LoggerInterface $logger,
|
||||
private MonitorRepository $monitorRepository,
|
||||
) {}
|
||||
|
||||
public function handle(CommandInterface $command): ResultInterface
|
||||
{
|
||||
$this->logger->info('> [MonitorTvEpisodeHandler] Executing MonitorTvEpisodeHandler');
|
||||
$monitor = $this->monitorRepository->find($command->movieMonitorId);
|
||||
$monitor->setStatus('In Progress');
|
||||
$this->monitorRepository->getEntityManager()->flush();
|
||||
|
||||
$this->logger->info('> [MonitorTvEpisodeHandler] Searching for "' . $monitor->getTitle() . '" season ' . $monitor->getSeason() . ' episode ' . $monitor->getEpisode() . ' download options');
|
||||
$results = $this->getTvShowOptionsHandler->handle(
|
||||
new GetTvShowOptionsCommand(
|
||||
$monitor->getTmdbId(),
|
||||
$monitor->getImdbId(),
|
||||
$monitor->getSeason(),
|
||||
$monitor->getEpisode()
|
||||
)
|
||||
);
|
||||
|
||||
$this->logger->info('> [MonitorTvEpisodeHandler] Found ' . count($results->results) . ' download options');
|
||||
|
||||
$result = $this->monitorOptionEvaluator->evaluateOptions($monitor, $results->results);
|
||||
|
||||
if (null !== $result) {
|
||||
$this->logger->info('> [MonitorTvEpisodeHandler] 1 matching result found: dispatching DownloadMediaCommand for "' . $result->title . '"');
|
||||
$this->bus->dispatch(new DownloadMediaCommand(
|
||||
$result->url,
|
||||
$monitor->getTitle(),
|
||||
$result->filename,
|
||||
'tvshows',
|
||||
$monitor->getImdbId(),
|
||||
$monitor->getUser()->getId(),
|
||||
));
|
||||
$monitor->setStatus('Complete');
|
||||
$monitor->setDownloadedAt(new DateTimeImmutable());
|
||||
} else {
|
||||
$this->logger->info('> [MonitorTvEpisodeHandler] 0 matching results found, monitor will run at next interval');
|
||||
$monitor->setStatus('Active');
|
||||
}
|
||||
|
||||
$monitor->setLastSearch(new DateTimeImmutable());
|
||||
$monitor->setSearchCount($monitor->getSearchCount() + 1);
|
||||
$this->entityManager->flush();
|
||||
|
||||
return new MonitorMovieResult(
|
||||
status: 'OK',
|
||||
result: [
|
||||
'monitor' => $monitor,
|
||||
]
|
||||
);
|
||||
}
|
||||
}
|
||||
103
src/Monitor/Action/Handler/MonitorTvSeasonHandler.php
Normal file
103
src/Monitor/Action/Handler/MonitorTvSeasonHandler.php
Normal file
@@ -0,0 +1,103 @@
|
||||
<?php
|
||||
|
||||
namespace App\Monitor\Action\Handler;
|
||||
|
||||
use Aimeos\Map;
|
||||
use App\Monitor\Action\Command\MonitorMovieCommand;
|
||||
use App\Monitor\Action\Command\MonitorTvEpisodeCommand;
|
||||
use App\Monitor\Action\Result\MonitorMovieResult;
|
||||
use App\Monitor\Framework\Entity\Monitor;
|
||||
use App\Monitor\Framework\Repository\MonitorRepository;
|
||||
use App\Monitor\Service\MediaFiles;
|
||||
use App\Tmdb\Tmdb;
|
||||
use DateTimeImmutable;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use Nihilarr\PTN;
|
||||
use OneToMany\RichBundle\Contract\CommandInterface;
|
||||
use OneToMany\RichBundle\Contract\HandlerInterface;
|
||||
use OneToMany\RichBundle\Contract\ResultInterface;
|
||||
use Psr\Log\LoggerInterface;
|
||||
use Symfony\Component\Messenger\MessageBusInterface;
|
||||
|
||||
/** @implements HandlerInterface<MonitorMovieCommand> */
|
||||
readonly class MonitorTvSeasonHandler implements HandlerInterface
|
||||
{
|
||||
public function __construct(
|
||||
private MonitorRepository $monitorRepository,
|
||||
private EntityManagerInterface $entityManager,
|
||||
private MediaFiles $mediaFiles,
|
||||
private MessageBusInterface $bus,
|
||||
private LoggerInterface $logger,
|
||||
private Tmdb $tmdb,
|
||||
private MonitorTvEpisodeHandler $monitorTvEpisodeHandler,
|
||||
) {}
|
||||
|
||||
public function handle(CommandInterface $command): ResultInterface
|
||||
{
|
||||
$this->logger->info('> [MonitorTvSeasonHandler] Executing MonitorTvSeasonHandler');
|
||||
$monitor = $this->monitorRepository->find($command->monitorId);
|
||||
|
||||
// Check current episodes
|
||||
$downloadedEpisodes = $this->mediaFiles
|
||||
->getEpisodes($monitor->getTitle())
|
||||
->map(fn($episode) => (object) (new PTN())->parse($episode))
|
||||
->rekey(fn($episode) => $episode->episode);
|
||||
$this->logger->info('> [MonitorTvSeasonHandler] Found ' . count($downloadedEpisodes) . ' downloaded episodes for title: ' . $monitor->getTitle());
|
||||
|
||||
// Compare against list from TMDB
|
||||
$episodesInSeason = Map::from(
|
||||
$this->tmdb->tvDetails($monitor->getTmdbId())
|
||||
->episodes[$monitor->getSeason()]
|
||||
)->rekey(fn($episode) => $episode['episode_number']);
|
||||
$this->logger->info('> [MonitorTvSeasonHandler] Found ' . count($episodesInSeason) . ' episodes in season ' . $monitor->getSeason() . ' for title: ' . $monitor->getTitle());
|
||||
|
||||
// Dispatch Episode commands for each missing Episode
|
||||
foreach ($episodesInSeason as $episode) {
|
||||
$monitorCheck = $this->monitorRepository->findOneBy([
|
||||
'imdbId' => $monitor->getImdbId(),
|
||||
'title' => $monitor->getTitle(),
|
||||
'monitorType' => 'tvepisode',
|
||||
'season' => $monitor->getSeason(),
|
||||
'episode' => $episode['episode_number'],
|
||||
'status' => ['New', 'Active', 'In Progress']
|
||||
]);
|
||||
|
||||
$this->logger->info('> [MonitorTvSeasonHandler] Monitor exists for season ' . $monitor->getSeason() . ' episode ' . $episode['episode_number'] . ' for title: ' . $monitor->getTitle() . ' ? ' . (null !== $monitorCheck ? 'YES' : 'NO'));
|
||||
|
||||
if (!array_key_exists($episode['episode_number'], $downloadedEpisodes->toArray())
|
||||
&& null === $monitorCheck
|
||||
) {
|
||||
$episodeMonitor = (new Monitor())
|
||||
->setUser($monitor->getUser())
|
||||
->setTmdbId($monitor->getTmdbId())
|
||||
->setImdbId($monitor->getImdbId())
|
||||
->setTitle($monitor->getTitle())
|
||||
->setMonitorType('tvepisode')
|
||||
->setSeason($monitor->getSeason())
|
||||
->setEpisode($episode['episode_number'])
|
||||
->setCreatedAt(new DateTimeImmutable())
|
||||
->setSearchCount(0)
|
||||
->setStatus('New');
|
||||
|
||||
$this->monitorRepository->getEntityManager()->persist($episodeMonitor);
|
||||
$this->monitorRepository->getEntityManager()->flush();
|
||||
|
||||
$command = new MonitorTvEpisodeCommand($episodeMonitor->getId());
|
||||
$this->monitorTvEpisodeHandler->handle($command);
|
||||
$this->logger->info('> [MonitorTvSeasonHandler] Dispatching MonitorTvEpisodeCommand for season ' . $episodeMonitor->getSeason() . ' episode ' . $episodeMonitor->getEpisode() . ' for title: ' . $monitor->getTitle());
|
||||
}
|
||||
}
|
||||
|
||||
$monitor->setLastSearch(new DateTimeImmutable());
|
||||
$monitor->setSearchCount($monitor->getSearchCount() + 1);
|
||||
$monitor->setStatus('Complete');
|
||||
$this->entityManager->flush();
|
||||
|
||||
return new MonitorMovieResult(
|
||||
status: 'OK',
|
||||
result: [
|
||||
'monitor' => $monitor,
|
||||
]
|
||||
);
|
||||
}
|
||||
}
|
||||
93
src/Monitor/Action/Handler/MonitorTvShowHandler.php
Normal file
93
src/Monitor/Action/Handler/MonitorTvShowHandler.php
Normal file
@@ -0,0 +1,93 @@
|
||||
<?php
|
||||
|
||||
namespace App\Monitor\Action\Handler;
|
||||
|
||||
use Aimeos\Map;
|
||||
use App\Monitor\Action\Command\MonitorMovieCommand;
|
||||
use App\Monitor\Action\Command\MonitorTvEpisodeCommand;
|
||||
use App\Monitor\Action\Result\MonitorTvEpisodeResult;
|
||||
use App\Monitor\Framework\Entity\Monitor;
|
||||
use App\Monitor\Framework\Repository\MonitorRepository;
|
||||
use App\Monitor\Service\MediaFiles;
|
||||
use App\Tmdb\Tmdb;
|
||||
use DateTimeImmutable;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use Nihilarr\PTN;
|
||||
use OneToMany\RichBundle\Contract\CommandInterface;
|
||||
use OneToMany\RichBundle\Contract\HandlerInterface;
|
||||
use OneToMany\RichBundle\Contract\ResultInterface;
|
||||
use Psr\Log\LoggerInterface;
|
||||
use Symfony\Component\Messenger\MessageBusInterface;
|
||||
|
||||
/** @implements HandlerInterface<MonitorMovieCommand> */
|
||||
readonly class MonitorTvShowHandler implements HandlerInterface
|
||||
{
|
||||
public function __construct(
|
||||
private MonitorRepository $monitorRepository,
|
||||
private EntityManagerInterface $entityManager,
|
||||
private MediaFiles $mediaFiles,
|
||||
private MessageBusInterface $bus,
|
||||
private LoggerInterface $logger,
|
||||
private Tmdb $tmdb,
|
||||
) {}
|
||||
|
||||
public function handle(CommandInterface $command): ResultInterface
|
||||
{
|
||||
$this->logger->info('> [MonitorTvShowHandler] Executing MonitorTvShowHandler');
|
||||
$monitor = $this->monitorRepository->find($command->monitorId);
|
||||
|
||||
// Check current episodes
|
||||
$downloadedEpisodes = $this->mediaFiles
|
||||
->getEpisodes($monitor->getTitle())
|
||||
->map(fn($episode) => (object) (new PTN())->parse($episode));
|
||||
$this->logger->info('> [MonitorTvShowHandler] Found ' . count($downloadedEpisodes) . ' downloaded episodes for title: ' . $monitor->getTitle());
|
||||
|
||||
// Compare against list from TMDB
|
||||
$episodesInShow = Map::from(
|
||||
$this->tmdb->tvDetails($monitor->getTmdbId())
|
||||
->episodes
|
||||
)->flat(1);
|
||||
$this->logger->info('> [MonitorTvShowHandler] Found ' . count($episodesInShow) . ' episodes in season ' . $monitor->getSeason() . ' for title: ' . $monitor->getTitle());
|
||||
|
||||
// Dispatch Episode commands for each missing Episode
|
||||
foreach ($episodesInShow as $episode) {
|
||||
$episodeAlreadyDownloaded = $downloadedEpisodes->find(
|
||||
fn($ep) => $ep->episode === $episode['episode_number'] && $ep->season === $episode['season_number']
|
||||
);
|
||||
$episodeAlreadyDownloaded = !is_null($episodeAlreadyDownloaded);
|
||||
|
||||
if (false === $episodeAlreadyDownloaded) {
|
||||
$monitor = (new Monitor())
|
||||
->setUser($monitor->getUser())
|
||||
->setTmdbId($monitor->getTmdbId())
|
||||
->setImdbId($monitor->getImdbId())
|
||||
->setTitle($monitor->getTitle())
|
||||
->setMonitorType('tvshow')
|
||||
->setSeason($episode['season_number'])
|
||||
->setEpisode($episode['episode_number'])
|
||||
->setCreatedAt(new DateTimeImmutable())
|
||||
->setSearchCount(0)
|
||||
->setStatus('New');
|
||||
|
||||
$this->monitorRepository->getEntityManager()->persist($monitor);
|
||||
$this->monitorRepository->getEntityManager()->flush();
|
||||
|
||||
$command = new MonitorTvEpisodeCommand($monitor->getId());
|
||||
$this->bus->dispatch($command);
|
||||
$this->logger->info('> [MonitorTvShowHandler] Dispatching MonitorTvEpisodeCommand for season ' . $monitor->getSeason() . ' episode ' . $monitor->getEpisode() . ' for title: ' . $monitor->getTitle());
|
||||
}
|
||||
}
|
||||
|
||||
$monitor->setLastSearch(new DateTimeImmutable());
|
||||
$monitor->setSearchCount($monitor->getSearchCount() + 1);
|
||||
$monitor->setStatus('Complete');
|
||||
$this->entityManager->flush();
|
||||
|
||||
return new MonitorTvEpisodeResult(
|
||||
status: 'OK',
|
||||
result: [
|
||||
'monitor' => $monitor,
|
||||
]
|
||||
);
|
||||
}
|
||||
}
|
||||
48
src/Monitor/Action/Input/AddMonitorInput.php
Normal file
48
src/Monitor/Action/Input/AddMonitorInput.php
Normal file
@@ -0,0 +1,48 @@
|
||||
<?php
|
||||
|
||||
namespace App\Monitor\Action\Input;
|
||||
|
||||
use App\Monitor\Action\Command\AddMonitorCommand;
|
||||
use OneToMany\RichBundle\Attribute\SourceRequest;
|
||||
use OneToMany\RichBundle\Attribute\SourceSecurity;
|
||||
use OneToMany\RichBundle\Contract\CommandInterface;
|
||||
use OneToMany\RichBundle\Contract\InputInterface;
|
||||
|
||||
class AddMonitorInput implements InputInterface
|
||||
{
|
||||
public function __construct(
|
||||
#[SourceSecurity]
|
||||
public int|string $userId,
|
||||
|
||||
#[SourceRequest('tmdbId')]
|
||||
public string $tmdbId,
|
||||
|
||||
#[SourceRequest('imdbId')]
|
||||
public string $imdbId,
|
||||
|
||||
#[SourceRequest('title')]
|
||||
public string $title,
|
||||
|
||||
#[SourceRequest('monitorType')]
|
||||
public string $monitorType,
|
||||
|
||||
#[SourceRequest('season', nullify: true)]
|
||||
public ?int $season,
|
||||
|
||||
#[SourceRequest('episode', nullify: true)]
|
||||
public ?int $episode,
|
||||
) {}
|
||||
|
||||
public function toCommand(): CommandInterface
|
||||
{
|
||||
return new AddMonitorCommand(
|
||||
$this->userId,
|
||||
$this->title,
|
||||
$this->imdbId,
|
||||
$this->tmdbId,
|
||||
$this->monitorType,
|
||||
$this->season,
|
||||
$this->episode,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,24 +1,24 @@
|
||||
<?php
|
||||
|
||||
namespace App\Download\Action\Input;
|
||||
namespace App\Monitor\Action\Input;
|
||||
|
||||
use App\Download\Action\Command\GetDownloadOptionsCommand;
|
||||
use App\Monitor\Action\Command\MonitorMovieCommand;
|
||||
use OneToMany\RichBundle\Attribute\SourceRoute;
|
||||
use OneToMany\RichBundle\Contract\CommandInterface;
|
||||
use OneToMany\RichBundle\Contract\InputInterface;
|
||||
|
||||
class GetDownloadOptionsInput implements InputInterface
|
||||
class MonitorMovieInput implements InputInterface
|
||||
{
|
||||
public function __construct(
|
||||
#[SourceRoute('tmdbId')]
|
||||
public string $tmdbId,
|
||||
|
||||
#[SourceRoute('mediaType')]
|
||||
public string $mediaType,
|
||||
#[SourceRoute('imdbId')]
|
||||
public string $imdbId,
|
||||
) {}
|
||||
|
||||
public function toCommand(): CommandInterface
|
||||
{
|
||||
return new GetDownloadOptionsCommand($this->tmdbId, $this->mediaType);
|
||||
return new MonitorMovieCommand($this->tmdbId, $this->imdbId);
|
||||
}
|
||||
}
|
||||
}
|
||||
13
src/Monitor/Action/Result/AddMonitorResult.php
Normal file
13
src/Monitor/Action/Result/AddMonitorResult.php
Normal file
@@ -0,0 +1,13 @@
|
||||
<?php
|
||||
|
||||
namespace App\Monitor\Action\Result;
|
||||
|
||||
use OneToMany\RichBundle\Contract\ResultInterface;
|
||||
|
||||
class AddMonitorResult implements ResultInterface
|
||||
{
|
||||
public function __construct(
|
||||
public string $status,
|
||||
public array $result,
|
||||
) {}
|
||||
}
|
||||
13
src/Monitor/Action/Result/MonitorMovieResult.php
Normal file
13
src/Monitor/Action/Result/MonitorMovieResult.php
Normal file
@@ -0,0 +1,13 @@
|
||||
<?php
|
||||
|
||||
namespace App\Monitor\Action\Result;
|
||||
|
||||
use OneToMany\RichBundle\Contract\ResultInterface;
|
||||
|
||||
class MonitorMovieResult implements ResultInterface
|
||||
{
|
||||
public function __construct(
|
||||
public string $status,
|
||||
public array $result,
|
||||
) {}
|
||||
}
|
||||
13
src/Monitor/Action/Result/MonitorTvEpisodeResult.php
Normal file
13
src/Monitor/Action/Result/MonitorTvEpisodeResult.php
Normal file
@@ -0,0 +1,13 @@
|
||||
<?php
|
||||
|
||||
namespace App\Monitor\Action\Result;
|
||||
|
||||
use OneToMany\RichBundle\Contract\ResultInterface;
|
||||
|
||||
class MonitorTvEpisodeResult implements ResultInterface
|
||||
{
|
||||
public function __construct(
|
||||
public string $status,
|
||||
public array $result,
|
||||
) {}
|
||||
}
|
||||
48
src/Monitor/Framework/Controller/ApiController.php
Normal file
48
src/Monitor/Framework/Controller/ApiController.php
Normal file
@@ -0,0 +1,48 @@
|
||||
<?php
|
||||
|
||||
namespace App\Monitor\Framework\Controller;
|
||||
|
||||
use App\Monitor\Action\Handler\AddMonitorHandler;
|
||||
use App\Monitor\Action\Input\AddMonitorInput;
|
||||
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
||||
use Symfony\Bundle\SecurityBundle\Security;
|
||||
use Symfony\Component\DependencyInjection\Attribute\Autowire;
|
||||
use Symfony\Component\Mercure\HubInterface;
|
||||
use Symfony\Component\Mercure\Update;
|
||||
use Symfony\Component\Routing\Attribute\Route;
|
||||
use Twig\Environment;
|
||||
|
||||
class ApiController extends AbstractController
|
||||
{
|
||||
public function __construct(
|
||||
#[Autowire(service: 'twig')]
|
||||
private readonly Environment $renderer,
|
||||
private readonly HubInterface $hub,
|
||||
private readonly Security $security,
|
||||
) {}
|
||||
|
||||
#[Route('/api/monitor', name: 'api_monitor', methods: ['POST'])]
|
||||
public function addMonitor(
|
||||
AddMonitorInput $input,
|
||||
AddMonitorHandler $handler,
|
||||
HubInterface $hub,
|
||||
) {
|
||||
$command = $input->toCommand();
|
||||
$command->userId = $this->security->getUser()->getId();
|
||||
$response = $handler->handle($command);
|
||||
|
||||
$hub->publish(new Update(
|
||||
'alerts',
|
||||
$this->renderer->render('broadcast/Alert.html.twig', [
|
||||
'alert_id' => uniqid(),
|
||||
'title' => 'Success',
|
||||
'message' => "New monitor added for {$input->title}",
|
||||
])
|
||||
));
|
||||
|
||||
return $this->json([
|
||||
'status' => 200,
|
||||
'message' => $response
|
||||
]);
|
||||
}
|
||||
}
|
||||
0
src/Monitor/Framework/Entity/.gitignore
vendored
Normal file
0
src/Monitor/Framework/Entity/.gitignore
vendored
Normal file
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user