pull/2478/head
TidusJar 6 years ago
commit 0a4acb314c

@ -1,5 +1,215 @@
# Changelog
## v3.0.3587 (2018-08-19)
### **New Features**
- Added the ability to invite Plex Friends from the user management screen. [Jamie]
- Added rich notifications for mobile. [Jamie]
- Updater fixes. [Jamie]
- Added updater test mode. [Jamie Rees]
- Added a new API method to delete issue comments. [TidusJar]
- Updated @ngu/carousel to beta version to remove rxjs-compat dependency. [Matt Jeanes]
- Update to Angular 6/Webpack 4. [Matt Jeanes]
- Update CHANGELOG.md. [Jamie]
- Updated the way we create the wizard user, errors show now be fed back to the user. [Jamie]
- Added Brazillian Portuguese as a language and also Polish. [Jamie]
- Updated swagger. [Jamie]
- Updated to 2.1.1. [Jamie]
### **Fixes**
- #2408 Added the feature to delete comments on issues. [Jamie]
- New translations en.json (Swedish) [Jamie]
- New translations en.json (French) [Jamie]
- Fixed #2440. [TidusJar]
- Delete cake.config. [Chris Pritchard]
- Initial attempt at getting anime seriestype working. [Chris Pritchard]
- Add cake.config. [Chris Pritchard]
- Fixed the issue where we wouldn't correctly mark some shows as available when there was no provider id #2429. [Jamie]
- Fixed the 'loop' in the cacher #2429. [Jamie]
- Fixed #2427. [Jamie]
- Fixed #2424. [Jamie]
- Fixed #2409. [Jamie]
- More updater. [Jamie]
- Humanize the request type enum in notifications e.g. TvShow will now appear as "Tv Show" #2416. [TidusJar]
- Made the quality override and root folder override load when we load the show (It will now appear) [Jamie]
- Fixed #2415 where power users could not set the Sonarr Quality Override or Root Folder Override. [Jamie]
- #2371 Fixed the issue where certain actions would not setup the series correctly in Sonarr. [Jamie]
- Tightened up the security from an API perspecitve. [TidusJar]
- Stop the root folder and profile calls from erroring. [TidusJar]
- New translations en.json (Polish) [Jamie]
- New translations en.json (Polish) [Jamie]
- New translations en.json (Polish) [Jamie]
- New translations en.json (Portuguese, Brazilian) [Jamie]
- New translations en.json (Portuguese, Brazilian) [Jamie]
- New translations en.json (Portuguese, Brazilian) [Jamie]
- New translations en.json (Portuguese, Brazilian) [Jamie]
- New translations en.json (Portuguese, Brazilian) [Jamie]
- Fixed all linting. [TidusJar]
- Comment out envparam stuff. [Matt Jeanes]
- Fixed prod build issue. [Matt Jeanes]
- Missed a tiny bit. [Matt Jeanes]
- Fix test. [Matt Jeanes]
- Fix test build. [Matt Jeanes]
- Linting + remove debug. [Matt Jeanes]
- Switch to Yarn and disable auto publish in release mode. [Matt Jeanes]
- Fix for #2409. [TidusJar]
- New translations en.json (Swedish) [Jamie]
- New translations en.json (Spanish) [Jamie]
- New translations en.json (Portuguese, Brazilian) [Jamie]
- New translations en.json (Polish) [Jamie]
- New translations en.json (Norwegian) [Jamie]
- New translations en.json (Italian) [Jamie]
- New translations en.json (German) [Jamie]
- New translations en.json (French) [Jamie]
- New translations en.json (Dutch) [Jamie]
- New translations en.json (Danish) [Jamie]
- Possible fix for #2298. [D34DC3N73R]
- Fixed the text for #2370. [Jamie]
- Fixed where you couldn't bulk edit the limits to 0 #2318. [Jamie]
- Upgraded to .net 2.1.2 (Includes security fixes) [Jamie]
## v3.0.3477 (2018-07-18)
### **New Features**
- Updated the Emby availability checker to bring it more in line with what we do with Plex. [TidusJar]
- Added the ability to impersonate a user when using the API Key. This allows people to use the API and request as a certain user. #2363. [Jamie Rees]
- Added more background images and it will loop through the available ones. [Jamie Rees]
- Added chunk hashing to resolve #2330. [Jamie Rees]
- Added API at /api/v1/status/info to get branch and version information #2331. [Jamie Rees]
- Update to .net 2.1.1. [Jamie]
### **Fixes**
- Fix #2322 caused by continue statement inside try catch block. [Anojh]
- Fixed #2367. [TidusJar]
- Fixed the issue where you could not delete a user #2365. [TidusJar]
- Another attempt to fix #2366. [Jamie Rees]
- Fixed the Plex OAuth warning. [Jamie]
- Revert "Fixed Plex OAuth, should no longer show Insecure warning" [Jamie Rees]
- Fixed Plex OAuth, should no longer show Insecure warning. [Jamie Rees]
- Fixed the View On Emby URL since the Link changed #2368. [Jamie Rees]
- Fixed the issue where episodes were not being marked as available in the search #2367. [Jamie Rees]
- Fixed #2371. [Jamie Rees]
- Fixed collection issues in Emby #2366. [Jamie Rees]
- Do not delete the Emby Information every time we run, let's keep the content now. [Jamie Rees]
- Emby Improvements: Batch up the amount we get from the server. [Jamie Rees]
- Log errors when they are uncaught. [Jamie Rees]
- Fix unclosed table tags causing overflow #2322. [Anojh]
- This should now fix #2350. [Jamie]
- Improve the validation around the Application URL. [Jamie Rees]
- Fixed #2341. [Jamie Rees]
- Stop spamming errors when FanArt doesn't have the image. [Jamie Rees]
- Fixed #2338. [Jamie Rees]
- Removed some logging statements. [Jamie Rees]
- Fixed the api key being case sensative #2350. [Jamie Rees]
- Improved the Emby API #2230 Thanks Luke! [Jamie Rees]
- Revert. [Jamie Rees]
- Fixed a small error in the Mobile Notification Provider. [Jamie Rees]
- Minor style tweaks. [Randall Bruder]
- Downgrade to .net core 2.0. [Jamie Rees]
- Downgrade Microsoft.AspNetCore.All package back to 2.0.8. [Jamie Rees]
- Removed old code. [Jamie Rees]
- Swap out the old way of validating the API key with a real middlewear this time. [Jamie Rees]
## v3.0.3421 (2018-06-23)
### **New Features**

@ -15,19 +15,19 @@ test: off
after_build:
- cmd: >-
appveyor PushArtifact "%APPVEYOR_BUILD_FOLDER%\src\Ombi\bin\Release\netcoreapp2.0\windows.zip"
appveyor PushArtifact "%APPVEYOR_BUILD_FOLDER%\src\Ombi\bin\Release\netcoreapp2.1\windows.zip"
appveyor PushArtifact "%APPVEYOR_BUILD_FOLDER%\src\Ombi\bin\Release\netcoreapp2.0\osx.tar.gz"
appveyor PushArtifact "%APPVEYOR_BUILD_FOLDER%\src\Ombi\bin\Release\netcoreapp2.1\osx.tar.gz"
appveyor PushArtifact "%APPVEYOR_BUILD_FOLDER%\src\Ombi\bin\Release\netcoreapp2.0\linux.tar.gz"
appveyor PushArtifact "%APPVEYOR_BUILD_FOLDER%\src\Ombi\bin\Release\netcoreapp2.1\linux.tar.gz"
appveyor PushArtifact "%APPVEYOR_BUILD_FOLDER%\src\Ombi\bin\Release\netcoreapp2.0\linux-arm.tar.gz"
appveyor PushArtifact "%APPVEYOR_BUILD_FOLDER%\src\Ombi\bin\Release\netcoreapp2.1\linux-arm.tar.gz"
appveyor PushArtifact "%APPVEYOR_BUILD_FOLDER%\src\Ombi\bin\Release\netcoreapp2.0\windows-32bit.zip"
appveyor PushArtifact "%APPVEYOR_BUILD_FOLDER%\src\Ombi\bin\Release\netcoreapp2.1\windows-32bit.zip"
# appveyor PushArtifact "%APPVEYOR_BUILD_FOLDER%\src\Ombi\bin\Release\netcoreapp2.0\linux-arm64.tar.gz"

@ -1,10 +1,10 @@
#tool "nuget:?package=GitVersion.CommandLine"
#addin "Cake.Gulp"
#addin "nuget:?package=Cake.Npm&version=0.13.0"
#addin "SharpZipLib"
#addin nuget:?package=Cake.Compression&version=0.1.4
#addin "Cake.Incubator"
#addin "Cake.Yarn"
//////////////////////////////////////////////////////////////////////
// ARGUMENTS
@ -26,7 +26,7 @@ var csProj = "./src/Ombi/Ombi.csproj"; // Path to the project.csproj
var solutionFile = "Ombi.sln"; // Solution file if needed
GitVersion versionInfo = null;
var frameworkVer = "netcoreapp2.0";
var frameworkVer = "netcoreapp2.1";
var buildSettings = new DotNetCoreBuildSettings
{
@ -122,36 +122,19 @@ Task("SetVersionInfo")
Task("NPM")
.Does(() => {
var settings = new NpmInstallSettings {
LogLevel = NpmLogLevel.Silent,
WorkingDirectory = webProjDir,
Production = true
};
NpmInstall(settings);
Yarn.FromPath(webProjDir).Install();
});
Task("Gulp Publish")
.IsDependentOn("NPM")
.Does(() => {
var runScriptSettings = new NpmRunScriptSettings {
ScriptName="publish",
WorkingDirectory = webProjDir,
};
NpmRunScript(runScriptSettings);
.Does(() => {
Yarn.FromPath(webProjDir).RunScript("publish");
});
Task("TSLint")
.Does(() =>
{
var settings = new NpmRunScriptSettings {
WorkingDirectory = webProjDir,
ScriptName = "lint"
};
NpmRunScript(settings);
Yarn.FromPath(webProjDir).RunScript("lint");
});
Task("PrePublish")
@ -178,7 +161,7 @@ Task("Publish")
.IsDependentOn("Publish-OSX")
.IsDependentOn("Publish-Linux")
.IsDependentOn("Publish-Linux-ARM")
//.IsDependentOn("Publish-Linux-ARM-64Bit")
.IsDependentOn("Publish-Linux-ARM-64Bit")
.IsDependentOn("Package");
Task("Publish-Windows")
@ -189,6 +172,8 @@ Task("Publish-Windows")
DotNetCorePublish("./src/Ombi/Ombi.csproj", publishSettings);
CopyFile(buildDir + "/"+frameworkVer+"/win10-x64/Swagger.xml", buildDir + "/"+frameworkVer+"/win10-x64/published/Swagger.xml");
publishSettings.OutputDirectory = Directory(buildDir) + Directory(frameworkVer +"/win10-x64/published/updater");
DotNetCorePublish("./src/Ombi.Updater/Ombi.Updater.csproj", publishSettings);
});
@ -200,6 +185,9 @@ Task("Publish-Windows-32bit")
DotNetCorePublish("./src/Ombi/Ombi.csproj", publishSettings);
CopyFile(buildDir + "/"+frameworkVer+"/win10-x86/Swagger.xml", buildDir + "/"+frameworkVer+"/win10-x86/published/Swagger.xml");
publishSettings.OutputDirectory = Directory(buildDir) + Directory(frameworkVer +"/win10-x86/published/updater");
DotNetCorePublish("./src/Ombi.Updater/Ombi.Updater.csproj", publishSettings);
});
@ -211,6 +199,8 @@ Task("Publish-OSX")
DotNetCorePublish("./src/Ombi/Ombi.csproj", publishSettings);
CopyFile(buildDir + "/"+frameworkVer+"/osx-x64/Swagger.xml", buildDir + "/"+frameworkVer+"/osx-x64/published/Swagger.xml");
publishSettings.OutputDirectory = Directory(buildDir) + Directory(frameworkVer +"/osx-x64/published/updater");
DotNetCorePublish("./src/Ombi.Updater/Ombi.Updater.csproj", publishSettings);
});
@ -222,6 +212,8 @@ Task("Publish-Linux")
DotNetCorePublish("./src/Ombi/Ombi.csproj", publishSettings);
CopyFile(buildDir + "/"+frameworkVer+"/linux-x64/Swagger.xml", buildDir + "/"+frameworkVer+"/linux-x64/published/Swagger.xml");
publishSettings.OutputDirectory = Directory(buildDir) + Directory(frameworkVer +"/linux-x64/published/updater");
DotNetCorePublish("./src/Ombi.Updater/Ombi.Updater.csproj", publishSettings);
});
@ -235,6 +227,8 @@ Task("Publish-Linux-ARM")
CopyFile(
buildDir + "/"+frameworkVer+"/linux-arm/Swagger.xml",
buildDir + "/"+frameworkVer+"/linux-arm/published/Swagger.xml");
publishSettings.OutputDirectory = Directory(buildDir) + Directory(frameworkVer +"/linux-arm/published/updater");
DotNetCorePublish("./src/Ombi.Updater/Ombi.Updater.csproj", publishSettings);
});
@ -248,6 +242,8 @@ Task("Publish-Linux-ARM-64Bit")
CopyFile(
buildDir + "/"+frameworkVer+"/linux-arm64/Swagger.xml",
buildDir + "/"+frameworkVer+"/linux-arm64/published/Swagger.xml");
publishSettings.OutputDirectory = Directory(buildDir) + Directory(frameworkVer +"/linux-arm64/published/updater");
DotNetCorePublish("./src/Ombi.Updater/Ombi.Updater.csproj", publishSettings);
});

291
package-lock.json generated

@ -0,0 +1,291 @@
{
"requires": true,
"lockfileVersion": 1,
"dependencies": {
"ansi-regex": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz",
"integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8="
},
"ansi-styles": {
"version": "2.2.1",
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz",
"integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4="
},
"argparse": {
"version": "1.0.10",
"resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz",
"integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==",
"requires": {
"sprintf-js": "1.0.3"
}
},
"babel-code-frame": {
"version": "6.26.0",
"resolved": "https://registry.npmjs.org/babel-code-frame/-/babel-code-frame-6.26.0.tgz",
"integrity": "sha1-Y/1D99weO7fONZR9uP42mj9Yx0s=",
"requires": {
"chalk": "1.1.3",
"esutils": "2.0.2",
"js-tokens": "3.0.2"
},
"dependencies": {
"chalk": {
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz",
"integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=",
"requires": {
"ansi-styles": "2.2.1",
"escape-string-regexp": "1.0.5",
"has-ansi": "2.0.0",
"strip-ansi": "3.0.1",
"supports-color": "2.0.0"
}
}
}
},
"balanced-match": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz",
"integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c="
},
"brace-expansion": {
"version": "1.1.11",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
"integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
"requires": {
"balanced-match": "1.0.0",
"concat-map": "0.0.1"
}
},
"builtin-modules": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-1.1.1.tgz",
"integrity": "sha1-Jw8HbFpywC9bZaR9+Uxf46J4iS8="
},
"chalk": {
"version": "2.4.1",
"resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.1.tgz",
"integrity": "sha512-ObN6h1v2fTJSmUXoS3nMQ92LbDK9be4TV+6G+omQlGJFdcUX5heKi1LZ1YnRMIgwTLEj3E24bT6tYni50rlCfQ==",
"requires": {
"ansi-styles": "3.2.1",
"escape-string-regexp": "1.0.5",
"supports-color": "5.4.0"
},
"dependencies": {
"ansi-styles": {
"version": "3.2.1",
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz",
"integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==",
"requires": {
"color-convert": "1.9.1"
}
},
"supports-color": {
"version": "5.4.0",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.4.0.tgz",
"integrity": "sha512-zjaXglF5nnWpsq470jSv6P9DwPvgLkuapYmfDm3JWOm0vkNTVF2tI4UrN2r6jH1qM/uc/WtxYY1hYoA2dOKj5w==",
"requires": {
"has-flag": "3.0.0"
}
}
}
},
"color-convert": {
"version": "1.9.1",
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.1.tgz",
"integrity": "sha512-mjGanIiwQJskCC18rPR6OmrZ6fm2Lc7PeGFYwCmy5J34wC6F1PzdGL6xeMfmgicfYcNLGuVFA3WzXtIDCQSZxQ==",
"requires": {
"color-name": "1.1.3"
}
},
"color-name": {
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz",
"integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU="
},
"commander": {
"version": "2.15.1",
"resolved": "https://registry.npmjs.org/commander/-/commander-2.15.1.tgz",
"integrity": "sha512-VlfT9F3V0v+jr4yxPc5gg9s62/fIVWsd2Bk2iD435um1NlGMYdVCq+MjcXnhYq2icNOizHr1kK+5TI6H0Hy0ag=="
},
"concat-map": {
"version": "0.0.1",
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
"integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s="
},
"diff": {
"version": "3.5.0",
"resolved": "https://registry.npmjs.org/diff/-/diff-3.5.0.tgz",
"integrity": "sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA=="
},
"escape-string-regexp": {
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz",
"integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ="
},
"esprima": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.0.tgz",
"integrity": "sha512-oftTcaMu/EGrEIu904mWteKIv8vMuOgGYo7EhVJJN00R/EED9DCua/xxHRdYnKtcECzVg7xOWhflvJMnqcFZjw=="
},
"esutils": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.2.tgz",
"integrity": "sha1-Cr9PHKpbyx96nYrMbepPqqBLrJs="
},
"fs.realpath": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
"integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8="
},
"glob": {
"version": "7.1.2",
"resolved": "https://registry.npmjs.org/glob/-/glob-7.1.2.tgz",
"integrity": "sha512-MJTUg1kjuLeQCJ+ccE4Vpa6kKVXkPYJ2mOCQyUuKLcLQsdrMCpBPUi8qVE6+YuaJkozeA9NusTAw3hLr8Xe5EQ==",
"requires": {
"fs.realpath": "1.0.0",
"inflight": "1.0.6",
"inherits": "2.0.3",
"minimatch": "3.0.4",
"once": "1.4.0",
"path-is-absolute": "1.0.1"
}
},
"has-ansi": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-2.0.0.tgz",
"integrity": "sha1-NPUEnOHs3ysGSa8+8k5F7TVBbZE=",
"requires": {
"ansi-regex": "2.1.1"
}
},
"has-flag": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz",
"integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0="
},
"inflight": {
"version": "1.0.6",
"resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
"integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=",
"requires": {
"once": "1.4.0",
"wrappy": "1.0.2"
}
},
"inherits": {
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz",
"integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4="
},
"js-tokens": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-3.0.2.tgz",
"integrity": "sha1-mGbfOVECEw449/mWvOtlRDIJwls="
},
"js-yaml": {
"version": "3.12.0",
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.12.0.tgz",
"integrity": "sha512-PIt2cnwmPfL4hKNwqeiuz4bKfnzHTBv6HyVgjahA6mPLwPDzjDWrplJBMjHUFxku/N3FlmrbyPclad+I+4mJ3A==",
"requires": {
"argparse": "1.0.10",
"esprima": "4.0.0"
}
},
"minimatch": {
"version": "3.0.4",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz",
"integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==",
"requires": {
"brace-expansion": "1.1.11"
}
},
"once": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
"integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=",
"requires": {
"wrappy": "1.0.2"
}
},
"path-is-absolute": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
"integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18="
},
"path-parse": {
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.5.tgz",
"integrity": "sha1-PBrfhx6pzWyUMbbqK9dKD/BVxME="
},
"resolve": {
"version": "1.7.1",
"resolved": "https://registry.npmjs.org/resolve/-/resolve-1.7.1.tgz",
"integrity": "sha512-c7rwLofp8g1U+h1KNyHL/jicrKg1Ek4q+Lr33AL65uZTinUZHe30D5HlyN5V9NW0JX1D5dXQ4jqW5l7Sy/kGfw==",
"requires": {
"path-parse": "1.0.5"
}
},
"semver": {
"version": "5.5.0",
"resolved": "https://registry.npmjs.org/semver/-/semver-5.5.0.tgz",
"integrity": "sha512-4SJ3dm0WAwWy/NVeioZh5AntkdJoWKxHxcmyP622fOkgHa4z3R0TdBJICINyaSDE6uNwVc8gZr+ZinwZAH4xIA=="
},
"sprintf-js": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz",
"integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw="
},
"strip-ansi": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz",
"integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=",
"requires": {
"ansi-regex": "2.1.1"
}
},
"supports-color": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz",
"integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc="
},
"tslib": {
"version": "1.9.2",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-1.9.2.tgz",
"integrity": "sha512-AVP5Xol3WivEr7hnssHDsaM+lVrVXWUvd1cfXTRkTj80b//6g2wIFEH6hZG0muGZRnHGrfttpdzRk3YlBkWjKw=="
},
"tslint": {
"version": "5.10.0",
"resolved": "https://registry.npmjs.org/tslint/-/tslint-5.10.0.tgz",
"integrity": "sha1-EeJrzLiK+gLdDZlWyuPUVAtfVMM=",
"requires": {
"babel-code-frame": "6.26.0",
"builtin-modules": "1.1.1",
"chalk": "2.4.1",
"commander": "2.15.1",
"diff": "3.5.0",
"glob": "7.1.2",
"js-yaml": "3.12.0",
"minimatch": "3.0.4",
"resolve": "1.7.1",
"semver": "5.5.0",
"tslib": "1.9.2",
"tsutils": "2.27.1"
}
},
"tsutils": {
"version": "2.27.1",
"resolved": "https://registry.npmjs.org/tsutils/-/tsutils-2.27.1.tgz",
"integrity": "sha512-AE/7uzp32MmaHvNNFES85hhUDHFdFZp6OAiZcd6y4ZKKIg6orJTm8keYWBhIhrJQH3a4LzNKat7ZPXZt5aTf6w==",
"requires": {
"tslib": "1.9.2"
}
},
"wrappy": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
"integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8="
}
}
}

@ -91,27 +91,31 @@ namespace Ombi.Api.Emby
request.AddContentHeader("Content-Type", "application/json");
}
public async Task<EmbyItemContainer<MovieInformation>> GetCollection(string mediaId, string apiKey, string userId, string baseUrl)
public async Task<EmbyItemContainer<EmbyMovie>> GetCollection(string mediaId, string apiKey, string userId, string baseUrl)
{
var request = new Request($"emby/users/{userId}/items?parentId={mediaId}", baseUrl, HttpMethod.Get);
AddHeaders(request, apiKey);
return await Api.Request<EmbyItemContainer<MovieInformation>>(request);
request.AddQueryString("Fields", "ProviderIds,Overview");
request.AddQueryString("VirtualItem", "False");
return await Api.Request<EmbyItemContainer<EmbyMovie>>(request);
}
public async Task<EmbyItemContainer<EmbyMovie>> GetAllMovies(string apiKey, string userId, string baseUri)
public async Task<EmbyItemContainer<EmbyMovie>> GetAllMovies(string apiKey, int startIndex, int count, string userId, string baseUri)
{
return await GetAll<EmbyMovie>("Movie", apiKey, userId, baseUri, true);
return await GetAll<EmbyMovie>("Movie", apiKey, userId, baseUri, true, startIndex, count);
}
public async Task<EmbyItemContainer<EmbyEpisodes>> GetAllEpisodes(string apiKey, string userId, string baseUri)
public async Task<EmbyItemContainer<EmbyEpisodes>> GetAllEpisodes(string apiKey, int startIndex, int count, string userId, string baseUri)
{
return await GetAll<EmbyEpisodes>("Episode", apiKey, userId, baseUri);
return await GetAll<EmbyEpisodes>("Episode", apiKey, userId, baseUri, false, startIndex, count);
}
public async Task<EmbyItemContainer<EmbySeries>> GetAllShows(string apiKey, string userId, string baseUri)
public async Task<EmbyItemContainer<EmbySeries>> GetAllShows(string apiKey, int startIndex, int count, string userId, string baseUri)
{
return await GetAll<EmbySeries>("Series", apiKey, userId, baseUri);
return await GetAll<EmbySeries>("Series", apiKey, userId, baseUri, false, startIndex, count);
}
public async Task<SeriesInformation> GetSeriesInformation(string mediaId, string apiKey, string userId, string baseUrl)
@ -145,7 +149,25 @@ namespace Ombi.Api.Emby
request.AddQueryString("IncludeItemTypes", type);
request.AddQueryString("Fields", includeOverview ? "ProviderIds,Overview" : "ProviderIds");
request.AddQueryString("VirtualItem","False");
request.AddQueryString("VirtualItem", "False");
AddHeaders(request, apiKey);
var obj = await Api.Request<EmbyItemContainer<T>>(request);
return obj;
}
private async Task<EmbyItemContainer<T>> GetAll<T>(string type, string apiKey, string userId, string baseUri, bool includeOverview, int startIndex, int count)
{
var request = new Request($"emby/users/{userId}/items", baseUri, HttpMethod.Get);
request.AddQueryString("Recursive", true.ToString());
request.AddQueryString("IncludeItemTypes", type);
request.AddQueryString("Fields", includeOverview ? "ProviderIds,Overview" : "ProviderIds");
request.AddQueryString("startIndex", startIndex.ToString());
request.AddQueryString("limit", count.ToString());
request.AddQueryString("VirtualItem", "False");
AddHeaders(request, apiKey);

@ -14,12 +14,17 @@ namespace Ombi.Api.Emby
Task<EmbyUser> LogIn(string username, string password, string apiKey, string baseUri);
Task<EmbyConnectUser> LoginConnectUser(string username, string password);
Task<EmbyItemContainer<EmbyMovie>> GetAllMovies(string apiKey, string userId, string baseUri);
Task<EmbyItemContainer<EmbyEpisodes>> GetAllEpisodes(string apiKey, string userId, string baseUri);
Task<EmbyItemContainer<EmbySeries>> GetAllShows(string apiKey, string userId, string baseUri);
Task<EmbyItemContainer<EmbyMovie>> GetAllMovies(string apiKey, int startIndex, int count, string userId,
string baseUri);
Task<EmbyItemContainer<MovieInformation>> GetCollection(string mediaId, string apiKey, string userId,
string baseUrl);
Task<EmbyItemContainer<EmbyEpisodes>> GetAllEpisodes(string apiKey, int startIndex, int count, string userId,
string baseUri);
Task<EmbyItemContainer<EmbySeries>> GetAllShows(string apiKey, int startIndex, int count, string userId,
string baseUri);
Task<EmbyItemContainer<EmbyMovie>> GetCollection(string mediaId,
string apiKey, string userId, string baseUrl);
Task<SeriesInformation> GetSeriesInformation(string mediaId, string apiKey, string userId, string baseUrl);
Task<MovieInformation> GetMovieInformation(string mediaId, string apiKey, string userId, string baseUrl);

@ -6,6 +6,6 @@ namespace Ombi.Api.Notifications
{
public interface IOneSignalApi
{
Task<OneSignalNotificationResponse> PushNotification(List<string> playerIds, string message);
Task<OneSignalNotificationResponse> PushNotification(List<string> playerIds, string message, bool isAdminNotification, int requestId, int requestType);
}
}

@ -4,18 +4,22 @@
{
public string app_id { get; set; }
public string[] include_player_ids { get; set; }
public Data data { get; set; }
public object data { get; set; }
public Button[] buttons { get; set; }
public Contents contents { get; set; }
}
public class Data
{
public string foo { get; set; }
}
public class Contents
{
public string en { get; set; }
}
public class Button
{
public string id { get; set; }
public string text { get; set; }
//public string icon { get; set; }
}
}

@ -4,6 +4,10 @@
<TargetFramework>netstandard2.0</TargetFramework>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Humanizer.Core" Version="2.4.2" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Ombi.Api\Ombi.Api.csproj" />
</ItemGroup>

@ -20,7 +20,7 @@ namespace Ombi.Api.Notifications
private readonly IApplicationConfigRepository _appConfig;
private const string ApiUrl = "https://onesignal.com/api/v1/notifications";
public async Task<OneSignalNotificationResponse> PushNotification(List<string> playerIds, string message)
public async Task<OneSignalNotificationResponse> PushNotification(List<string> playerIds, string message, bool isAdminNotification, int requestId, int requestType)
{
if (!playerIds.Any())
{
@ -39,6 +39,17 @@ namespace Ombi.Api.Notifications
include_player_ids = playerIds.ToArray()
};
if (isAdminNotification)
{
// Add the action buttons
body.data = new { requestid = requestId, requestType = requestType};
body.buttons = new[]
{
new Button {id = "approve", text = "Approve Request"},
new Button {id = "deny", text = "Deny Request"},
};
}
request.AddJsonBody(body);
var result = await _api.Request<OneSignalNotificationResponse>(request);

@ -11,6 +11,7 @@ namespace Ombi.Api.Plex
public interface IPlexApi
{
Task<PlexStatus> GetStatus(string authToken, string uri);
Task<PlexLibrariesForMachineId> GetLibrariesForMachineId(string authToken, string machineId);
Task<PlexAuthentication> SignIn(UserRequest user);
Task<PlexServer> GetServer(string authToken);
Task<PlexContainer> GetLibrarySections(string authToken, string plexFullHost);
@ -22,8 +23,8 @@ namespace Ombi.Api.Plex
Task<PlexFriends> GetUsers(string authToken);
Task<PlexAccount> GetAccount(string authToken);
Task<PlexMetadata> GetRecentlyAdded(string authToken, string uri, string sectionId);
Task<OAuthPin> CreatePin();
Task<OAuthPin> GetPin(int pinId);
Uri GetOAuthUrl(int pinId, string code, string applicationUrl, bool wizard);
Task<Uri> GetOAuthUrl(int pinId, string code, string applicationUrl);
Task<PlexAddWrapper> AddUser(string emailAddress, string serverId, string authToken, int[] libs);
}
}

@ -0,0 +1,84 @@
using System.Collections.Generic;
using System.Xml.Serialization;
namespace Ombi.Api.Plex.Models
{
[XmlRoot(ElementName = "Section")]
public class Section
{
[XmlAttribute(AttributeName = "id")]
public string Id { get; set; }
[XmlAttribute(AttributeName = "key")]
public string Key { get; set; }
[XmlAttribute(AttributeName = "title")]
public string Title { get; set; }
[XmlAttribute(AttributeName = "type")]
public string Type { get; set; }
[XmlAttribute(AttributeName = "shared")]
public string Shared { get; set; }
}
[XmlRoot(ElementName = "SharedServer")]
public class SharedServer
{
[XmlElement(ElementName = "Section")]
public List<Section> Section { get; set; }
[XmlAttribute(AttributeName = "id")]
public string Id { get; set; }
[XmlAttribute(AttributeName = "username")]
public string Username { get; set; }
[XmlAttribute(AttributeName = "email")]
public string Email { get; set; }
[XmlAttribute(AttributeName = "userID")]
public string UserID { get; set; }
[XmlAttribute(AttributeName = "accessToken")]
public string AccessToken { get; set; }
[XmlAttribute(AttributeName = "name")]
public string Name { get; set; }
[XmlAttribute(AttributeName = "acceptedAt")]
public string AcceptedAt { get; set; }
[XmlAttribute(AttributeName = "invitedAt")]
public string InvitedAt { get; set; }
[XmlAttribute(AttributeName = "allowSync")]
public string AllowSync { get; set; }
[XmlAttribute(AttributeName = "allowCameraUpload")]
public string AllowCameraUpload { get; set; }
[XmlAttribute(AttributeName = "allowChannels")]
public string AllowChannels { get; set; }
[XmlAttribute(AttributeName = "allowTuners")]
public string AllowTuners { get; set; }
[XmlAttribute(AttributeName = "owned")]
public string Owned { get; set; }
}
[XmlRoot(ElementName = "MediaContainer")]
public class PlexAdd
{
[XmlElement(ElementName = "SharedServer")]
public SharedServer SharedServer { get; set; }
[XmlAttribute(AttributeName = "friendlyName")]
public string FriendlyName { get; set; }
[XmlAttribute(AttributeName = "identifier")]
public string Identifier { get; set; }
[XmlAttribute(AttributeName = "machineIdentifier")]
public string MachineIdentifier { get; set; }
[XmlAttribute(AttributeName = "size")]
public string Size { get; set; }
}
[XmlRoot(ElementName = "Response")]
public class AddUserError
{
[XmlAttribute(AttributeName = "code")]
public string Code { get; set; }
[XmlAttribute(AttributeName = "status")]
public string Status { get; set; }
}
public class PlexAddWrapper
{
public PlexAdd Add { get; set; }
public AddUserError Error { get; set; }
public bool HasError => Error != null;
}
}

@ -0,0 +1,66 @@
namespace Ombi.Api.Plex.Models
{
using System;
using System.Xml.Serialization;
using System.Collections.Generic;
[XmlRoot(ElementName = "Section")]
public class SectionLite
{
[XmlAttribute(AttributeName = "id")]
public string Id { get; set; }
[XmlAttribute(AttributeName = "key")]
public string Key { get; set; }
[XmlAttribute(AttributeName = "type")]
public string Type { get; set; }
[XmlAttribute(AttributeName = "title")]
public string Title { get; set; }
}
[XmlRoot(ElementName = "Server")]
public class ServerLib
{
[XmlElement(ElementName = "Section")]
public List<SectionLite> Section { get; set; }
[XmlAttribute(AttributeName = "name")]
public string Name { get; set; }
[XmlAttribute(AttributeName = "address")]
public string Address { get; set; }
[XmlAttribute(AttributeName = "port")]
public string Port { get; set; }
[XmlAttribute(AttributeName = "version")]
public string Version { get; set; }
[XmlAttribute(AttributeName = "scheme")]
public string Scheme { get; set; }
[XmlAttribute(AttributeName = "host")]
public string Host { get; set; }
[XmlAttribute(AttributeName = "localAddresses")]
public string LocalAddresses { get; set; }
[XmlAttribute(AttributeName = "machineIdentifier")]
public string MachineIdentifier { get; set; }
[XmlAttribute(AttributeName = "createdAt")]
public string CreatedAt { get; set; }
[XmlAttribute(AttributeName = "updatedAt")]
public string UpdatedAt { get; set; }
[XmlAttribute(AttributeName = "owned")]
public string Owned { get; set; }
[XmlAttribute(AttributeName = "synced")]
public string Synced { get; set; }
}
[XmlRoot(ElementName = "MediaContainer")]
public class PlexLibrariesForMachineId
{
[XmlElement(ElementName = "Server")]
public ServerLib Server { get; set; }
[XmlAttribute(AttributeName = "friendlyName")]
public string FriendlyName { get; set; }
[XmlAttribute(AttributeName = "identifier")]
public string Identifier { get; set; }
[XmlAttribute(AttributeName = "machineIdentifier")]
public string MachineIdentifier { get; set; }
[XmlAttribute(AttributeName = "size")]
public string Size { get; set; }
}
}

@ -16,14 +16,16 @@ namespace Ombi.Api.Plex
{
public class PlexApi : IPlexApi
{
public PlexApi(IApi api, ISettingsService<CustomizationSettings> settings)
public PlexApi(IApi api, ISettingsService<CustomizationSettings> settings, ISettingsService<PlexSettings> p)
{
Api = api;
_custom = settings;
_plexSettings = p;
}
private IApi Api { get; }
private readonly ISettingsService<CustomizationSettings> _custom;
private readonly ISettingsService<PlexSettings> _plexSettings;
private string _app;
private string ApplicationName
@ -39,7 +41,18 @@ namespace Ombi.Api.Plex
}
else
{
_app = settings.ApplicationName;
// Check for non-ascii characters (New .Net Core HTTPLib does not allow this)
var chars = settings.ApplicationName.ToCharArray();
var hasNonAscii = false;
foreach (var c in chars)
{
if (c > 128)
{
hasNonAscii = true;
}
}
_app = hasNonAscii ? "Ombi" : settings.ApplicationName;
}
return _app;
@ -69,7 +82,7 @@ namespace Ombi.Api.Plex
};
var request = new Request(SignInUri, string.Empty, HttpMethod.Post);
AddHeaders(request);
await AddHeaders(request);
request.AddJsonBody(userModel);
var obj = await Api.Request<PlexAuthentication>(request);
@ -80,14 +93,14 @@ namespace Ombi.Api.Plex
public async Task<PlexStatus> GetStatus(string authToken, string uri)
{
var request = new Request(uri, string.Empty, HttpMethod.Get);
AddHeaders(request, authToken);
await AddHeaders(request, authToken);
return await Api.Request<PlexStatus>(request);
}
public async Task<PlexAccount> GetAccount(string authToken)
{
var request = new Request(GetAccountUri, string.Empty, HttpMethod.Get);
AddHeaders(request, authToken);
await AddHeaders(request, authToken);
return await Api.Request<PlexAccount>(request);
}
@ -95,7 +108,7 @@ namespace Ombi.Api.Plex
{
var request = new Request(ServerUri, string.Empty, HttpMethod.Get, ContentType.Xml);
AddHeaders(request, authToken);
await AddHeaders(request, authToken);
return await Api.Request<PlexServer>(request);
}
@ -103,17 +116,24 @@ namespace Ombi.Api.Plex
public async Task<PlexContainer> GetLibrarySections(string authToken, string plexFullHost)
{
var request = new Request("library/sections", plexFullHost, HttpMethod.Get);
AddHeaders(request, authToken);
await AddHeaders(request, authToken);
return await Api.Request<PlexContainer>(request);
}
public async Task<PlexContainer> GetLibrary(string authToken, string plexFullHost, string libraryId)
{
var request = new Request($"library/sections/{libraryId}/all", plexFullHost, HttpMethod.Get);
AddHeaders(request, authToken);
await AddHeaders(request, authToken);
return await Api.Request<PlexContainer>(request);
}
public async Task<PlexLibrariesForMachineId> GetLibrariesForMachineId(string authToken, string machineId)
{
var request = new Request("", $"https://plex.tv/api/servers/{machineId}", HttpMethod.Get, ContentType.Xml);
await AddHeaders(request, authToken);
return await Api.Request<PlexLibrariesForMachineId>(request);
}
/// <summary>
// 192.168.1.69:32400/library/metadata/3662/allLeaves
// The metadata ratingkey should be in the Cache
@ -128,21 +148,21 @@ namespace Ombi.Api.Plex
public async Task<PlexMetadata> GetEpisodeMetaData(string authToken, string plexFullHost, int ratingKey)
{
var request = new Request($"/library/metadata/{ratingKey}", plexFullHost, HttpMethod.Get);
AddHeaders(request, authToken);
await AddHeaders(request, authToken);
return await Api.Request<PlexMetadata>(request);
}
public async Task<PlexMetadata> GetMetadata(string authToken, string plexFullHost, int itemId)
{
var request = new Request($"library/metadata/{itemId}", plexFullHost, HttpMethod.Get);
AddHeaders(request, authToken);
await AddHeaders(request, authToken);
return await Api.Request<PlexMetadata>(request);
}
public async Task<PlexMetadata> GetSeasons(string authToken, string plexFullHost, int ratingKey)
{
var request = new Request($"library/metadata/{ratingKey}/children", plexFullHost, HttpMethod.Get);
AddHeaders(request, authToken);
await AddHeaders(request, authToken);
return await Api.Request<PlexMetadata>(request);
}
@ -161,9 +181,9 @@ namespace Ombi.Api.Plex
request.AddQueryString("type", "4");
AddLimitHeaders(request, start, retCount);
AddHeaders(request, authToken);
await AddHeaders(request, authToken);
return await Api.Request<PlexContainer>(request);
return await Api.Request<PlexContainer>(request);
}
/// <summary>
@ -174,8 +194,8 @@ namespace Ombi.Api.Plex
/// <returns></returns>
public async Task<PlexFriends> GetUsers(string authToken)
{
var request = new Request(string.Empty,FriendsUri, HttpMethod.Get, ContentType.Xml);
AddHeaders(request, authToken);
var request = new Request(string.Empty, FriendsUri, HttpMethod.Get, ContentType.Xml);
await AddHeaders(request, authToken);
return await Api.Request<PlexFriends>(request);
}
@ -183,43 +203,36 @@ namespace Ombi.Api.Plex
public async Task<PlexMetadata> GetRecentlyAdded(string authToken, string uri, string sectionId)
{
var request = new Request($"library/sections/{sectionId}/recentlyAdded", uri, HttpMethod.Get);
AddHeaders(request, authToken);
await AddHeaders(request, authToken);
AddLimitHeaders(request, 0, 50);
return await Api.Request<PlexMetadata>(request);
}
public async Task<OAuthPin> CreatePin()
{
var request = new Request($"api/v2/pins", "https://plex.tv/", HttpMethod.Post);
request.AddQueryString("strong", "true");
AddHeaders(request);
return await Api.Request<OAuthPin>(request);
}
public async Task<OAuthPin> GetPin(int pinId)
{
var request = new Request($"api/v2/pins/{pinId}", "https://plex.tv/", HttpMethod.Get);
AddHeaders(request);
await AddHeaders(request);
return await Api.Request<OAuthPin>(request);
}
public Uri GetOAuthUrl(int pinId, string code, string applicationUrl, bool wizard)
public async Task<Uri> GetOAuthUrl(int pinId, string code, string applicationUrl)
{
var request = new Request("auth#", "https://app.plex.tv", HttpMethod.Get);
AddHeaders(request);
var forwardUrl = wizard
? new Request($"Wizard/OAuth/{pinId}", applicationUrl, HttpMethod.Get)
: new Request($"Login/OAuth/{pinId}", applicationUrl, HttpMethod.Get);
request.AddQueryString("forwardUrl", forwardUrl.FullUri.ToString());
await AddHeaders(request);
request.AddQueryString("pinID", pinId.ToString());
request.AddQueryString("code", code);
request.AddQueryString("context[device][product]", "Ombi");
request.AddQueryString("context[device][product]", ApplicationName);
request.AddQueryString("context[device][environment]", "bundled");
request.AddQueryString("clientID", $"OmbiV3");
request.AddQueryString("context[device][layout]", "desktop");
request.AddQueryString("context[device][platform]", "Web");
request.AddQueryString("context[device][device]", "Ombi (Web)");
var s = await GetSettings();
await CheckInstallId(s);
request.AddQueryString("clientID", s.InstallId.ToString("N"));
if (request.FullUri.Fragment.Equals("#"))
{
@ -233,26 +246,58 @@ namespace Ombi.Api.Plex
return request.FullUri;
}
public async Task<PlexAddWrapper> AddUser(string emailAddress, string serverId, string authToken, int[] libs)
{
var request = new Request(string.Empty, $"https://plex.tv/api/servers/{serverId}/shared_servers", HttpMethod.Post, ContentType.Xml);
await AddHeaders(request, authToken);
request.AddJsonBody(new
{
server_id = serverId,
shared_server = new
{
library_section_ids = libs.Length > 0 ? libs : new int[]{},
invited_email = emailAddress
},
sharing_settings = new { }
});
var result = await Api.RequestContent(request);
try
{
var add = Api.DeserializeXml<PlexAdd>(result);
return new PlexAddWrapper{Add = add};
}
catch (InvalidOperationException)
{
var error = Api.DeserializeXml<AddUserError>(result);
return new PlexAddWrapper{Error = error};
}
}
/// <summary>
/// Adds the required headers and also the authorization header
/// </summary>
/// <param name="request"></param>
/// <param name="authToken"></param>
private void AddHeaders(Request request, string authToken)
private async Task AddHeaders(Request request, string authToken)
{
request.AddHeader("X-Plex-Token", authToken);
AddHeaders(request);
await AddHeaders(request);
}
/// <summary>
/// Adds the main required headers to the Plex Request
/// </summary>
/// <param name="request"></param>
private void AddHeaders(Request request)
private async Task AddHeaders(Request request)
{
request.AddHeader("X-Plex-Client-Identifier", $"OmbiV3");
var s = await GetSettings();
await CheckInstallId(s);
request.AddHeader("X-Plex-Client-Identifier", s.InstallId.ToString("N"));
request.AddHeader("X-Plex-Product", ApplicationName);
request.AddHeader("X-Plex-Version", "3");
request.AddHeader("X-Plex-Device", "Ombi (Web)");
request.AddHeader("X-Plex-Platform", "Web");
request.AddContentHeader("Content-Type", request.ContentType == ContentType.Json ? "application/json" : "application/xml");
request.AddHeader("Accept", "application/json");
}
@ -262,5 +307,19 @@ namespace Ombi.Api.Plex
request.AddHeader("X-Plex-Container-Start", from.ToString());
request.AddHeader("X-Plex-Container-Size", to.ToString());
}
private async Task CheckInstallId(PlexSettings s)
{
if (s.InstallId == null || s.InstallId == Guid.Empty)
{
s.InstallId = Guid.NewGuid();
await _plexSettings.SaveSettingsAsync(s);
}
}
private PlexSettings _settings;
private async Task<PlexSettings> GetSettings()
{
return _settings ?? (_settings = await _plexSettings.GetSettingsAsync());
}
}
}

@ -9,7 +9,7 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="2.0.2" />
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="2.1.1" />
</ItemGroup>
<ItemGroup>

@ -79,7 +79,7 @@ namespace Ombi.Api.Radarr
tmdbId = tmdbId,
qualityProfileId = qualityId,
rootFolderPath = rootPath,
titleSlug = title,
titleSlug = title + year,
monitored = true,
year = year,
minimumAvailability = minimumAvailability

@ -11,7 +11,7 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Options" Version="2.0.2" />
<PackageReference Include="Microsoft.Extensions.Options" Version="2.1.1" />
</ItemGroup>
<ItemGroup>

@ -6,6 +6,30 @@ namespace Ombi.Api.Sonarr.Models
{
public class Episode
{
public Episode()
{
}
public Episode(Episode ep)
{
seriesId = ep.seriesId;
episodeFileId = ep.episodeFileId;
seasonNumber = ep.seasonNumber;
episodeNumber = ep.episodeNumber;
title = ep.title;
airDate = ep.airDate;
airDateUtc = ep.airDateUtc;
overview = ep.overview;
hasFile = ep.hasFile;
monitored = ep.monitored;
unverifiedSceneNumbering = ep.unverifiedSceneNumbering;
id = ep.id;
absoluteEpisodeNumber = ep.absoluteEpisodeNumber;
sceneAbsoluteEpisodeNumber = ep.sceneAbsoluteEpisodeNumber;
sceneEpisodeNumber = ep.sceneEpisodeNumber;
sceneSeasonNumber = ep.sceneSeasonNumber;
}
public int seriesId { get; set; }
public int episodeFileId { get; set; }
public int seasonNumber { get; set; }
@ -27,6 +51,24 @@ namespace Ombi.Api.Sonarr.Models
public class Episodefile
{
public Episodefile()
{
}
public Episodefile(Episodefile e)
{
seriesId = e.seriesId;
seasonNumber = e.seasonNumber;
relativePath = e.relativePath;
path = e.path;
size = e.size;
dateAdded = e.dateAdded;
sceneName = e.sceneName;
quality = new EpisodeQuality(e.quality);
qualityCutoffNotMet = e.qualityCutoffNotMet;
id = e.id;
}
public int seriesId { get; set; }
public int seasonNumber { get; set; }
public string relativePath { get; set; }
@ -41,12 +83,32 @@ namespace Ombi.Api.Sonarr.Models
public class EpisodeQuality
{
public EpisodeQuality()
{
}
public EpisodeQuality(EpisodeQuality e)
{
quality = new Quality(e.quality);
revision = new Revision(e.revision);
}
public Quality quality { get; set; }
public Revision revision { get; set; }
}
public class Revision
{
public Revision()
{
}
public Revision(Revision r)
{
version = r.version;
real = r.real;
}
public int version { get; set; }
public int real { get; set; }
}

@ -23,6 +23,7 @@ namespace Ombi.Api.Sonarr.Models
public string cleanTitle { get; set; }
public string imdbId { get; set; }
public string titleSlug { get; set; }
public string seriesType { get; set; }
public int id { get; set; }
public List<SonarrImage> images { get; set; }

@ -2,6 +2,16 @@ namespace Ombi.Api.Sonarr.Models
{
public class Quality
{
public Quality()
{
}
public Quality(Quality q)
{
id = q.id;
name = q.name;
}
public int id { get; set; }
public string name { get; set; }
}

@ -80,15 +80,20 @@ namespace Ombi.Api
else
{
// XML
XmlSerializer serializer = new XmlSerializer(typeof(T));
StringReader reader = new StringReader(receivedString);
var value = (T)serializer.Deserialize(reader);
return value;
return DeserializeXml<T>(receivedString);
}
}
}
public T DeserializeXml<T>(string receivedString)
{
XmlSerializer serializer = new XmlSerializer(typeof(T));
StringReader reader = new StringReader(receivedString);
var value = (T) serializer.Deserialize(reader);
return value;
}
public async Task<string> RequestContent(Request request)
{
using (var httpRequestMessage = new HttpRequestMessage(request.HttpMethod, request.FullUri))

@ -7,5 +7,6 @@ namespace Ombi.Api
Task Request(Request request);
Task<T> Request<T>(Request request);
Task<string> RequestContent(Request request);
T DeserializeXml<T>(string receivedString);
}
}

@ -9,9 +9,9 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="2.0.2" />
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="2.1.1" />
<PackageReference Include="Newtonsoft.Json" Version="11.0.2" />
<PackageReference Include="Polly" Version="5.8.0" />
<PackageReference Include="Polly" Version="6.1.0" />
<PackageReference Include="System.Xml.XmlSerializer" Version="4.3.0" />
</ItemGroup>

@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netcoreapp2.0</TargetFramework>
<TargetFramework>netcoreapp2.1</TargetFramework>
</PropertyGroup>
<ItemGroup>
@ -9,7 +9,7 @@
<PackageReference Include="Nunit" Version="3.8.1" />
<PackageReference Include="NUnit.ConsoleRunner" Version="3.7.0" />
<PackageReference Include="NUnit3TestAdapter" Version="3.8.0" />
<packagereference Include="Microsoft.NET.Test.Sdk" Version="15.7.2"></packagereference>
<packagereference Include="Microsoft.NET.Test.Sdk" Version="15.8.0"></packagereference>
</ItemGroup>
<ItemGroup>

@ -29,7 +29,10 @@ namespace Ombi.Core.Tests.Rule.Search
{
ProviderId = "123"
});
var search = new SearchMovieViewModel();
var search = new SearchMovieViewModel()
{
TheMovieDbId = "123",
};
var result = await Rule.Execute(search);
Assert.True(result.Success);

@ -20,12 +20,6 @@ namespace Ombi.Core.Authentication
private readonly IPlexApi _api;
private readonly ISettingsService<CustomizationSettings> _customizationSettingsService;
public async Task<OAuthPin> RequestPin()
{
var pin = await _api.CreatePin();
return pin;
}
public async Task<string> GetAccessTokenFromPin(int pinId)
{
var pin = await _api.GetPin(pinId);
@ -34,19 +28,6 @@ namespace Ombi.Core.Authentication
return string.Empty;
}
if (pin.authToken.IsNullOrEmpty())
{
// Looks like we do not have a pin yet, we should retry a few times.
var retryCount = 0;
var retryMax = 5;
var retryWaitMs = 1000;
while (pin.authToken.IsNullOrEmpty() && retryCount < retryMax)
{
retryCount++;
await Task.Delay(retryWaitMs);
pin = await _api.GetPin(pinId);
}
}
return pin.authToken;
}
@ -58,14 +39,14 @@ namespace Ombi.Core.Authentication
public async Task<Uri> GetOAuthUrl(int pinId, string code, string websiteAddress = null)
{
var settings = await _customizationSettingsService.GetSettingsAsync();
var url = _api.GetOAuthUrl(pinId, code, settings.ApplicationUrl.IsNullOrEmpty() ? websiteAddress : settings.ApplicationUrl, false);
var url = await _api.GetOAuthUrl(pinId, code, settings.ApplicationUrl.IsNullOrEmpty() ? websiteAddress : settings.ApplicationUrl);
return url;
}
public Uri GetWizardOAuthUrl(int pinId, string code, string websiteAddress)
public async Task<Uri> GetWizardOAuthUrl(int pinId, string code, string websiteAddress)
{
var url = _api.GetOAuthUrl(pinId, code, websiteAddress, true);
var url = await _api.GetOAuthUrl(pinId, code, websiteAddress);
return url;
}
}

@ -0,0 +1,9 @@
using System.Threading.Tasks;
namespace Ombi.Core.Engine
{
public interface IUserStatsEngine
{
Task<UserStatsSummary> GetSummary(SummaryRequest request);
}
}

@ -15,13 +15,13 @@ namespace Ombi.Core.Engine.Interfaces
Task<RequestEngineResult> DenyChildRequest(int requestId);
Task<RequestsViewModel<TvRequests>> GetRequestsLite(int count, int position, OrderFilterModel type);
Task<IEnumerable<TvRequests>> SearchTvRequest(string search);
Task<IEnumerable<TreeNode<TvRequests, List<ChildRequests>>>> SearchTvRequestTree(string search);
Task<TvRequests> UpdateTvRequest(TvRequests request);
Task<IEnumerable<TreeNode<TvRequests, List<ChildRequests>>>> GetRequestsTreeNode(int count, int position);
Task<IEnumerable<ChildRequests>> GetAllChldren(int tvId);
Task<ChildRequests> UpdateChildRequest(ChildRequests request);
Task RemoveTvChild(int requestId);
Task<RequestEngineResult> ApproveChildRequest(int id);
Task<IEnumerable<TvRequests>> GetRequestsLite();
Task UpdateQualityProfile(int requestId, int profileId);
Task UpdateRootPath(int requestId, int rootPath);
}
}

@ -7,16 +7,10 @@ namespace Ombi.Core.Engine.Interfaces
public interface ITvSearchEngine
{
Task<IEnumerable<SearchTvShowViewModel>> Search(string searchTerm);
Task<IEnumerable<TreeNode<SearchTvShowViewModel>>> SearchTreeNode(string searchTerm);
Task<TreeNode<SearchTvShowViewModel>> GetShowInformationTreeNode(int tvdbid);
Task<SearchTvShowViewModel> GetShowInformation(int tvdbid);
Task<IEnumerable<TreeNode<SearchTvShowViewModel>>> PopularTree();
Task<IEnumerable<SearchTvShowViewModel>> Popular();
Task<IEnumerable<TreeNode<SearchTvShowViewModel>>> AnticipatedTree();
Task<IEnumerable<SearchTvShowViewModel>> Anticipated();
Task<IEnumerable<TreeNode<SearchTvShowViewModel>>> MostWatchesTree();
Task<IEnumerable<SearchTvShowViewModel>> MostWatches();
Task<IEnumerable<TreeNode<SearchTvShowViewModel>>> TrendingTree();
Task<IEnumerable<SearchTvShowViewModel>> Trending();
}
}

@ -452,6 +452,7 @@ namespace Ombi.Core.Engine
}
request.Available = true;
request.MarkedAsAvailable = DateTime.Now;
NotificationHelper.Notify(request, NotificationType.RequestAvailable);
await MovieRepository.Update(request);

@ -1,23 +0,0 @@
using System.Collections.Generic;
namespace Ombi.Core.Engine
{
public class TreeNode<T>
{
public string Label { get; set; }
public T Data { get; set; }
public List<TreeNode<T>> Children { get; set; }
public bool Leaf { get; set; }
public bool Expanded { get; set; }
}
public class TreeNode<T,U>
{
public string Label { get; set; }
public T Data { get; set; }
public List<TreeNode<U>> Children { get; set; }
public bool Leaf { get; set; }
public bool Expanded { get; set; }
}
}

@ -143,7 +143,7 @@ namespace Ombi.Core.Engine
.Include(x => x.ChildRequests)
.ThenInclude(x => x.SeasonRequests)
.ThenInclude(x => x.Episodes)
.OrderByDescending(x => x.ChildRequests.Max(y => y.RequestedDate))
.OrderByDescending(x => x.ChildRequests.Select(y => y.RequestedDate).FirstOrDefault())
.Skip(position).Take(count).ToListAsync();
// Filter out children
@ -156,8 +156,9 @@ namespace Ombi.Core.Engine
.Include(x => x.ChildRequests)
.ThenInclude(x => x.SeasonRequests)
.ThenInclude(x => x.Episodes)
.OrderByDescending(x => x.ChildRequests.Max(y => y.RequestedDate))
.OrderByDescending(x => x.ChildRequests.Select(y => y.RequestedDate).FirstOrDefault())
.Skip(position).Take(count).ToListAsync();
}
allRequests.ForEach(async r => { await CheckForSubscription(shouldHide, r); });
@ -171,24 +172,30 @@ namespace Ombi.Core.Engine
public async Task<RequestsViewModel<TvRequests>> GetRequestsLite(int count, int position, OrderFilterModel type)
{
var shouldHide = await HideFromOtherUsers();
List<TvRequests> allRequests;
List<TvRequests> allRequests = null;
if (shouldHide.Hide)
{
allRequests = await TvRepository.GetLite(shouldHide.UserId)
.OrderByDescending(x => x.ChildRequests.Max(y => y.RequestedDate))
.Skip(position).Take(count).ToListAsync();
var tv = TvRepository.GetLite(shouldHide.UserId);
if (tv.Any() && tv.Select(x => x.ChildRequests).Any())
{
allRequests = await tv.OrderByDescending(x => x.ChildRequests.Select(y => y.RequestedDate).FirstOrDefault()).Skip(position).Take(count).ToListAsync();
}
// Filter out children
FilterChildren(allRequests, shouldHide);
}
else
{
allRequests = await TvRepository.GetLite()
.OrderByDescending(x => x.ChildRequests.Max(y => y.RequestedDate))
.Skip(position).Take(count).ToListAsync();
var tv = TvRepository.GetLite();
if (tv.Any() && tv.Select(x => x.ChildRequests).Any())
{
allRequests = await tv.OrderByDescending(x => x.ChildRequests.Select(y => y.RequestedDate).FirstOrDefault()).Skip(position).Take(count).ToListAsync();
}
}
if (allRequests == null)
{
return new RequestsViewModel<TvRequests>();
}
allRequests.ForEach(async r => { await CheckForSubscription(shouldHide, r); });
return new RequestsViewModel<TvRequests>
@ -196,38 +203,6 @@ namespace Ombi.Core.Engine
Collection = allRequests
};
}
public async Task<IEnumerable<TreeNode<TvRequests, List<ChildRequests>>>> GetRequestsTreeNode(int count, int position)
{
var shouldHide = await HideFromOtherUsers();
List<TvRequests> allRequests;
if (shouldHide.Hide)
{
allRequests = await TvRepository.Get(shouldHide.UserId)
.Include(x => x.ChildRequests)
.ThenInclude(x => x.SeasonRequests)
.ThenInclude(x => x.Episodes)
.Where(x => x.ChildRequests.Any())
.OrderByDescending(x => x.ChildRequests.Max(y => y.RequestedDate))
.Skip(position).Take(count).ToListAsync();
FilterChildren(allRequests, shouldHide);
}
else
{
allRequests = await TvRepository.Get()
.Include(x => x.ChildRequests)
.ThenInclude(x => x.SeasonRequests)
.ThenInclude(x => x.Episodes)
.Where(x => x.ChildRequests.Any())
.OrderByDescending(x => x.ChildRequests.Max(y => y.RequestedDate))
.Skip(position).Take(count).ToListAsync();
}
allRequests.ForEach(async r => { await CheckForSubscription(shouldHide, r); });
return ParseIntoTreeNode(allRequests);
}
public async Task<IEnumerable<TvRequests>> GetRequests()
{
var shouldHide = await HideFromOtherUsers();
@ -288,6 +263,10 @@ namespace Ombi.Core.Engine
private static void FilterChildren(IEnumerable<TvRequests> allRequests, HideResult shouldHide)
{
if (allRequests == null)
{
return;
}
// Filter out children
foreach (var t in allRequests)
{
@ -350,21 +329,22 @@ namespace Ombi.Core.Engine
return results;
}
public async Task<IEnumerable<TreeNode<TvRequests, List<ChildRequests>>>> SearchTvRequestTree(string search)
public async Task UpdateRootPath(int requestId, int rootPath)
{
var shouldHide = await HideFromOtherUsers();
IQueryable<TvRequests> allRequests;
if (shouldHide.Hide)
{
allRequests = TvRepository.Get(shouldHide.UserId);
}
else
{
allRequests = TvRepository.Get();
}
var results = await allRequests.Where(x => x.Title.Contains(search, CompareOptions.IgnoreCase)).ToListAsync();
results.ForEach(async r => { await CheckForSubscription(shouldHide, r); });
return ParseIntoTreeNode(results);
var allRequests = TvRepository.Get();
var results = await allRequests.FirstOrDefaultAsync(x => x.Id == requestId);
results.RootFolder = rootPath;
await TvRepository.Update(results);
}
public async Task UpdateQualityProfile(int requestId, int profileId)
{
var allRequests = TvRepository.Get();
var results = await allRequests.FirstOrDefaultAsync(x => x.Id == requestId);
results.QualityOverride = profileId;
await TvRepository.Update(results);
}
public async Task<TvRequests> UpdateTvRequest(TvRequests request)
@ -516,6 +496,7 @@ namespace Ombi.Core.Engine
};
}
request.Available = true;
request.MarkedAsAvailable = DateTime.Now;
foreach (var season in request.SeasonRequests)
{
foreach (var e in season.Episodes)
@ -585,29 +566,7 @@ namespace Ombi.Core.Engine
return await AfterRequest(model.ChildRequests.FirstOrDefault());
}
private static List<TreeNode<TvRequests, List<ChildRequests>>> ParseIntoTreeNode(IEnumerable<TvRequests> result)
{
var node = new List<TreeNode<TvRequests, List<ChildRequests>>>();
foreach (var value in result)
{
node.Add(new TreeNode<TvRequests, List<ChildRequests>>
{
Data = value,
Children = new List<TreeNode<List<ChildRequests>>>
{
new TreeNode<List<ChildRequests>>
{
Data = SortEpisodes(value.ChildRequests),
Leaf = true
}
}
});
}
return node;
}
private static List<ChildRequests> SortEpisodes(List<ChildRequests> items)
private static List<ChildRequests> SortEpisodes(List<ChildRequests> items)
{
foreach (var value in items)
{

@ -59,11 +59,6 @@ namespace Ombi.Core.Engine
return null;
}
public async Task<IEnumerable<TreeNode<SearchTvShowViewModel>>> SearchTreeNode(string searchTerm)
{
var result = await Search(searchTerm);
return result.Select(ParseIntoTreeNode).ToList();
}
public async Task<SearchTvShowViewModel> GetShowInformation(int tvdbid)
{
var show = await TvMazeApi.ShowLookupByTheTvDbId(tvdbid);
@ -116,19 +111,6 @@ namespace Ombi.Core.Engine
return await ProcessResult(mapped);
}
public async Task<TreeNode<SearchTvShowViewModel>> GetShowInformationTreeNode(int tvdbid)
{
var result = await GetShowInformation(tvdbid);
return ParseIntoTreeNode(result);
}
public async Task<IEnumerable<TreeNode<SearchTvShowViewModel>>> PopularTree()
{
var result = await Cache.GetOrAdd(CacheKeys.PopularTv, async () => await TraktApi.GetPopularShows(), DateTime.Now.AddHours(12));
var processed = await ProcessResults(result);
return processed.Select(ParseIntoTreeNode).ToList();
}
public async Task<IEnumerable<SearchTvShowViewModel>> Popular()
{
var result = await Cache.GetOrAdd(CacheKeys.PopularTv, async () => await TraktApi.GetPopularShows(), DateTime.Now.AddHours(12));
@ -136,12 +118,6 @@ namespace Ombi.Core.Engine
return processed;
}
public async Task<IEnumerable<TreeNode<SearchTvShowViewModel>>> AnticipatedTree()
{
var result = await Cache.GetOrAdd(CacheKeys.AnticipatedTv, async () => await TraktApi.GetAnticipatedShows(), DateTime.Now.AddHours(12));
var processed = await ProcessResults(result);
return processed.Select(ParseIntoTreeNode).ToList();
}
public async Task<IEnumerable<SearchTvShowViewModel>> Anticipated()
{
@ -150,12 +126,6 @@ namespace Ombi.Core.Engine
return processed;
}
public async Task<IEnumerable<TreeNode<SearchTvShowViewModel>>> MostWatchesTree()
{
var result = await Cache.GetOrAdd(CacheKeys.MostWatchesTv, async () => await TraktApi.GetMostWatchesShows(), DateTime.Now.AddHours(12));
var processed = await ProcessResults(result);
return processed.Select(ParseIntoTreeNode).ToList();
}
public async Task<IEnumerable<SearchTvShowViewModel>> MostWatches()
{
var result = await Cache.GetOrAdd(CacheKeys.MostWatchesTv, async () => await TraktApi.GetMostWatchesShows(), DateTime.Now.AddHours(12));
@ -163,13 +133,6 @@ namespace Ombi.Core.Engine
return processed;
}
public async Task<IEnumerable<TreeNode<SearchTvShowViewModel>>> TrendingTree()
{
var result = await Cache.GetOrAdd(CacheKeys.TrendingTv, async () => await TraktApi.GetTrendingShows(), DateTime.Now.AddHours(12));
var processed = await ProcessResults(result);
return processed.Select(ParseIntoTreeNode).ToList();
}
public async Task<IEnumerable<SearchTvShowViewModel>> Trending()
{
var result = await Cache.GetOrAdd(CacheKeys.TrendingTv, async () => await TraktApi.GetTrendingShows(), DateTime.Now.AddHours(12));
@ -177,22 +140,6 @@ namespace Ombi.Core.Engine
return processed;
}
private static TreeNode<SearchTvShowViewModel> ParseIntoTreeNode(SearchTvShowViewModel result)
{
return new TreeNode<SearchTvShowViewModel>
{
Data = result,
Children = new List<TreeNode<SearchTvShowViewModel>>
{
new TreeNode<SearchTvShowViewModel>
{
Data = result, Leaf = true
}
},
Leaf = false
};
}
private async Task<IEnumerable<SearchTvShowViewModel>> ProcessResults<T>(IEnumerable<T> items)
{
var retVal = new List<SearchTvShowViewModel>();

@ -0,0 +1,77 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Microsoft.EntityFrameworkCore;
using Ombi.Core.Authentication;
using Ombi.Store.Entities;
using Ombi.Store.Repository.Requests;
namespace Ombi.Core.Engine
{
public class UserStatsEngine : IUserStatsEngine
{
public UserStatsEngine(OmbiUserManager um, IMovieRequestRepository movieRequest, ITvRequestRepository tvRequest)
{
_userManager = um;
_movieRequest = movieRequest;
_tvRequest = tvRequest;
}
private readonly OmbiUserManager _userManager;
private readonly IMovieRequestRepository _movieRequest;
private readonly ITvRequestRepository _tvRequest;
public async Task<UserStatsSummary> GetSummary(SummaryRequest request)
{
// get all movie requests
var movies = _movieRequest.GetWithUser();
var filteredMovies = movies.Where(x => x.RequestedDate >= request.From && x.RequestedDate <= request.To);
var tv = _tvRequest.GetLite();
var children = tv.SelectMany(x =>
x.ChildRequests.Where(c => c.RequestedDate >= request.From && c.RequestedDate <= request.To));
var moviesCount = filteredMovies.CountAsync();
var childrenCount = children.CountAsync();
var availableMovies =
movies.Select(x => x.MarkedAsAvailable >= request.From && x.MarkedAsAvailable <= request.To).CountAsync();
var availableChildren = tv.SelectMany(x =>
x.ChildRequests.Where(c => c.MarkedAsAvailable >= request.From && c.MarkedAsAvailable <= request.To)).CountAsync();
var userMovie = filteredMovies.GroupBy(x => x.RequestedUserId).OrderBy(x => x.Key).FirstOrDefaultAsync();
var userTv = children.GroupBy(x => x.RequestedUserId).OrderBy(x => x.Key).FirstOrDefaultAsync();
return new UserStatsSummary
{
TotalMovieRequests = await moviesCount,
TotalTvRequests = await childrenCount,
CompletedRequestsTv = await availableChildren,
CompletedRequestsMovies = await availableMovies,
MostRequestedUserMovie = (await userMovie).FirstOrDefault().RequestedUser,
MostRequestedUserTv = (await userTv).FirstOrDefault().RequestedUser,
};
}
}
public class SummaryRequest
{
public DateTime From { get; set; }
public DateTime To { get; set; }
}
public class UserStatsSummary
{
public int TotalRequests => TotalTvRequests + TotalMovieRequests;
public int TotalMovieRequests { get; set; }
public int TotalTvRequests { get; set; }
public int TotalIssues { get; set; }
public int CompletedRequestsMovies { get; set; }
public int CompletedRequestsTv { get; set; }
public int CompletedRequests => CompletedRequestsMovies + CompletedRequestsTv;
public OmbiUser MostRequestedUserMovie { get; set; }
public OmbiUser MostRequestedUserTv { get; set; }
}
}

@ -1,16 +1,14 @@
using System;
using System.Threading.Tasks;
using Ombi.Api.Plex.Models;
using Ombi.Api.Plex.Models.OAuth;
namespace Ombi.Core.Authentication
{
public interface IPlexOAuthManager
{
Task<string> GetAccessTokenFromPin(int pinId);
Task<OAuthPin> RequestPin();
Task<Uri> GetOAuthUrl(int pinId, string code, string websiteAddress = null);
Uri GetWizardOAuthUrl(int pinId, string code, string websiteAddress);
Task<Uri> GetWizardOAuthUrl(int pinId, string code, string websiteAddress);
Task<PlexAccount> GetAccount(string accessToken);
}
}

@ -12,9 +12,9 @@
<PackageReference Include="AutoMapper" Version="6.1.1" />
<PackageReference Include="AutoMapper.Extensions.Microsoft.DependencyInjection" Version="3.2.0" />
<PackageReference Include="Hangfire" Version="1.6.19" />
<PackageReference Include="Microsoft.AspNetCore.Cryptography.KeyDerivation" Version="2.0.3" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="2.0.3" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite.Design" Version="1.1.5" />
<PackageReference Include="Microsoft.AspNetCore.Cryptography.KeyDerivation" Version="2.1.1" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="2.1.1" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite.Design" Version="2.0.0-preview1-final" />
<PackageReference Include="MiniProfiler.AspNetCore" Version="4.0.0-alpha6-79" />
<PackageReference Include="Newtonsoft.Json" Version="11.0.2" />
<PackageReference Include="System.Diagnostics.Process" Version="4.3.0" />

@ -1,4 +1,6 @@
using System.Linq;
using System;
using System.Linq;
using System.Linq.Expressions;
using System.Threading.Tasks;
using Microsoft.EntityFrameworkCore;
using Ombi.Core.Models.Search;
@ -59,10 +61,19 @@ namespace Ombi.Core.Rule.Rules.Search
{
EmbyEpisode epExists = null;
epExists = await allEpisodes.FirstOrDefaultAsync(x =>
x.EpisodeNumber == episode.EpisodeNumber && x.SeasonNumber == season.SeasonNumber &&
x.Series.ProviderId == item.ProviderId.ToString());
if (item.HasImdb)
{
epExists = await allEpisodes.FirstOrDefaultAsync(e => e.EpisodeNumber == episode.EpisodeNumber && e.SeasonNumber == season.SeasonNumber
&& e.ImdbId == item.ImdbId);
} if (item.HasTvDb && epExists == null)
{
epExists = await allEpisodes.FirstOrDefaultAsync(e => e.EpisodeNumber == episode.EpisodeNumber && e.SeasonNumber == season.SeasonNumber
&& e.Series.TvDbId == item.TvDbId);
} if (item.HasTheMovieDb && epExists == null)
{
epExists = await allEpisodes.FirstOrDefaultAsync(e => e.EpisodeNumber == episode.EpisodeNumber && e.SeasonNumber == season.SeasonNumber
&& e.TheMovieDbId == item.TheMovieDbId);
}
if (epExists != null)
{

@ -120,6 +120,7 @@ namespace Ombi.Core.Senders
int qualityToUse;
string rootFolderPath;
string seriesType;
if (model.SeriesType == SeriesType.Anime)
{
@ -128,6 +129,8 @@ namespace Ombi.Core.Senders
// TODO make this overrideable via the UI
rootFolderPath = await GetSonarrRootPath(model.ParentRequest.RootFolder ?? int.Parse(s.RootPathAnime), s);
int.TryParse(s.QualityProfileAnime, out qualityToUse);
seriesType = "anime";
}
else
{
@ -136,6 +139,7 @@ namespace Ombi.Core.Senders
// For some reason, if we haven't got one use the first root folder in Sonarr
// TODO make this overrideable via the UI
rootFolderPath = await GetSonarrRootPath(model.ParentRequest.RootFolder ?? int.Parse(s.RootPath), s);
seriesType = "standard";
}
if (model.ParentRequest.QualityOverride.HasValue)
@ -163,27 +167,18 @@ namespace Ombi.Core.Senders
rootFolderPath = rootFolderPath,
qualityProfileId = qualityToUse,
titleSlug = model.ParentRequest.Title,
seriesType = seriesType,
addOptions = new AddOptions
{
ignoreEpisodesWithFiles = true, // There shouldn't be any episodes with files, this is a new season
ignoreEpisodesWithoutFiles = true, // We want all missing
ignoreEpisodesWithFiles = false, // There shouldn't be any episodes with files, this is a new season
ignoreEpisodesWithoutFiles = false, // We want all missing
searchForMissingEpisodes = false // we want dont want to search yet. We want to make sure everything is unmonitored/monitored correctly.
}
};
// Montitor the correct seasons,
// If we have that season in the model then it's monitored!
var seasonsToAdd = new List<Season>();
for (var i = 0; i < model.ParentRequest.TotalSeasons + 1; i++)
{
var index = i;
var season = new Season
{
seasonNumber = i,
monitored = model.SeasonRequests.Any(x => x.SeasonNumber == index && x.SeasonNumber != 0)
};
seasonsToAdd.Add(season);
}
var seasonsToAdd = GetSeasonsToCreate(model);
newSeries.seasons = seasonsToAdd;
var result = await SonarrApi.AddSeries(newSeries, s.ApiKey, s.FullUri);
existingSeries = await SonarrApi.GetSeriesById(result.id, s.ApiKey, s.FullUri);
@ -237,7 +232,7 @@ namespace Ombi.Core.Senders
{
var sonarrEp = sonarrEpList.FirstOrDefault(x =>
x.episodeNumber == ep.EpisodeNumber && x.seasonNumber == req.SeasonNumber);
if (sonarrEp != null)
if (sonarrEp != null && !sonarrEp.monitored)
{
sonarrEp.monitored = true;
episodesToUpdate.Add(sonarrEp);
@ -245,22 +240,64 @@ namespace Ombi.Core.Senders
}
}
var seriesChanges = false;
foreach (var season in model.SeasonRequests)
{
var sonarrSeason = sonarrEpList.Where(x => x.seasonNumber == season.SeasonNumber);
var sonarrEpCount = sonarrSeason.Count();
var ourRequestCount = season.Episodes.Count;
var existingSeason =
result.seasons.FirstOrDefault(x => x.seasonNumber == season.SeasonNumber);
if (existingSeason == null)
{
Logger.LogError("There was no season numer {0} in Sonarr for title {1}", season.SeasonNumber, model.ParentRequest.Title);
continue;
}
if (sonarrEpCount == ourRequestCount)
{
// We have the same amount of requests as all of the episodes in the season.
var existingSeason =
result.seasons.First(x => x.seasonNumber == season.SeasonNumber);
existingSeason.monitored = true;
seriesChanges = true;
if (!existingSeason.monitored)
{
existingSeason.monitored = true;
seriesChanges = true;
}
}
else
{
// Make sure this season is set to monitored
if (!existingSeason.monitored)
{
// We need to monitor it, problem being is all episodes will now be monitored
// So we need to monior the series but unmonitor every episode
// Except the episodes that are already monitored before we update the series (we do not want to unmonitor episodes that are monitored beforehand)
existingSeason.monitored = true;
var sea = result.seasons.FirstOrDefault(x => x.seasonNumber == existingSeason.seasonNumber);
sea.monitored = true;
//var previouslyMonitoredEpisodes = sonarrEpList.Where(x =>
// x.seasonNumber == existingSeason.seasonNumber && x.monitored).Select(x => x.episodeNumber).ToList(); // We probably don't actually care about this
result = await SonarrApi.UpdateSeries(result, s.ApiKey, s.FullUri);
var epToUnmonitor = new List<Episode>();
var newEpList = sonarrEpList.ConvertAll(ep => new Episode(ep)); // Clone it so we don't modify the orignal member
foreach (var ep in newEpList.Where(x => x.seasonNumber == existingSeason.seasonNumber).ToList())
{
//if (previouslyMonitoredEpisodes.Contains(ep.episodeNumber))
//{
// // This was previously monitored.
// continue;
//}
ep.monitored = false;
epToUnmonitor.Add(ep);
}
foreach (var epToUpdate in epToUnmonitor)
{
await SonarrApi.UpdateEpisode(epToUpdate, s.ApiKey, s.FullUri);
}
}
// Now update the episodes that need updating
foreach (var epToUpdate in episodesToUpdate.Where(x => x.seasonNumber == season.SeasonNumber))
{
@ -280,6 +317,24 @@ namespace Ombi.Core.Senders
}
}
private static List<Season> GetSeasonsToCreate(ChildRequests model)
{
// Let's get a list of seasons just incase we need to change it
var seasonsToUpdate = new List<Season>();
for (var i = 0; i < model.ParentRequest.TotalSeasons + 1; i++)
{
var index = i;
var sea = new Season
{
seasonNumber = i,
monitored = model.SeasonRequests.Any(x => x.SeasonNumber == index && x.SeasonNumber != 0)
};
seasonsToUpdate.Add(sea);
}
return seasonsToUpdate;
}
private async Task<bool> SendToSickRage(ChildRequests model, SickRageSettings settings, string qualityId = null)
{
var tvdbid = model.ParentRequest.TvDbId;

@ -79,6 +79,7 @@ namespace Ombi.DependencyInjection
services.AddTransient<ITvRequestEngine, TvRequestEngine>();
services.AddTransient<ITvSearchEngine, TvSearchEngine>();
services.AddTransient<IRuleEvaluator, RuleEvaluator>();
services.AddTransient<IUserStatsEngine, UserStatsEngine>();
services.AddTransient<IMovieSender, MovieSender>();
services.AddTransient<IRecentlyAddedEngine, RecentlyAddedEngine>();
services.AddTransient<ITvSender, TvSender>();

@ -9,9 +9,9 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="2.0.4" />
<PackageReference Include="Microsoft.AspNetCore.Authorization" Version="2.0.4" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="2.0.0" />
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="2.1.1" />
<PackageReference Include="Microsoft.AspNetCore.Authorization" Version="2.1.1" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="2.1.1" />
</ItemGroup>
<ItemGroup>

@ -10,7 +10,7 @@ namespace Ombi.Helpers
public static string GetEmbyMediaUrl(string mediaId)
{
var url =
$"http://app.emby.media/itemdetails.html?id={mediaId}";
$"http://app.emby.media/#!/itemdetails.html?id={mediaId}";
return url;
}
}

@ -10,8 +10,8 @@
<ItemGroup>
<PackageReference Include="EasyCrypto" Version="3.3.2" />
<PackageReference Include="Microsoft.Extensions.Caching.Abstractions" Version="2.0.2" />
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="2.0.2" />
<PackageReference Include="Microsoft.Extensions.Caching.Abstractions" Version="2.1.1" />
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="2.1.1" />
<PackageReference Include="Newtonsoft.Json" Version="11.0.2" />
<PackageReference Include="Nito.AsyncEx" Version="5.0.0-pre-05" />
<PackageReference Include="System.Security.Claims" Version="4.3.0" />

@ -182,14 +182,16 @@
</tr>
<tr>
<td>
{@RECENTLYADDED}
{@RECENTLYADDED}
</td>
</tr>
<!-- END MAIN CONTENT AREA -->
</table>
<!-- START FOOTER -->
<div class="footer" style="clear: both; padding-top: 10px; text-align: center; width: 100%;">
<table border="0" cellpadding="0" cellspacing="0" style="border-collapse: separate; mso-table-lspace: 0pt; mso-table-rspace: 0pt; width: 100%;" width="100%">
<table border="0" cellpadding="0" cellspacing="0" style="border-collapse: separate; mso-table-lspace: 0pt; mso-table-rspace: 0pt; width: 100%;">
<tr>
<td class="content-block powered-by" style="font-family: sans-serif; vertical-align: top; padding-top: 10px; padding-bottom: 10px; font-size: 12px; color: #999999; text-align: center;" valign="top" align="center">
Powered by <a href="https://github.com/tidusjar/Ombi" style="color: #999999; font-size: 12px; text-align: center; text-decoration: underline;">Ombi</a>

@ -1,14 +1,14 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netcoreapp2.0</TargetFramework>
<TargetFramework>netcoreapp2.1</TargetFramework>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Nunit" Version="3.8.1" />
<PackageReference Include="NUnit.ConsoleRunner" Version="3.7.0" />
<PackageReference Include="NUnit3TestAdapter" Version="3.8.0" />
<packagereference Include="Microsoft.NET.Test.Sdk" Version="15.7.2"></packagereference>
<packagereference Include="Microsoft.NET.Test.Sdk" Version="15.8.0"></packagereference>
<PackageReference Include="Moq" Version="4.7.99" />
</ItemGroup>

@ -57,7 +57,7 @@ namespace Ombi.Notifications.Agents
// Get admin devices
var playerIds = await GetAdmins(NotificationType.NewRequest);
await Send(playerIds, notification, settings);
await Send(playerIds, notification, settings, model, true);
}
protected override async Task NewIssue(NotificationOptions model, MobileNotificationSettings settings)
@ -75,7 +75,7 @@ namespace Ombi.Notifications.Agents
// Get admin devices
var playerIds = await GetAdmins(NotificationType.Issue);
await Send(playerIds, notification, settings);
await Send(playerIds, notification, settings, model);
}
protected override async Task IssueComment(NotificationOptions model, MobileNotificationSettings settings)
@ -97,13 +97,13 @@ namespace Ombi.Notifications.Agents
{
// Send to user
var playerIds = GetUsers(model, NotificationType.IssueComment);
await Send(playerIds, notification, settings);
await Send(playerIds, notification, settings, model);
}
else
{
// Send to admin
var playerIds = await GetAdmins(NotificationType.IssueComment);
await Send(playerIds, notification, settings);
await Send(playerIds, notification, settings, model);
}
}
}
@ -124,7 +124,7 @@ namespace Ombi.Notifications.Agents
// Send to user
var playerIds = GetUsers(model, NotificationType.IssueResolved);
await Send(playerIds, notification, settings);
await Send(playerIds, notification, settings, model);
}
@ -149,7 +149,7 @@ namespace Ombi.Notifications.Agents
};
// Get admin devices
var playerIds = await GetAdmins(NotificationType.Test);
await Send(playerIds, notification, settings);
await Send(playerIds, notification, settings, model);
}
protected override async Task RequestDeclined(NotificationOptions model, MobileNotificationSettings settings)
@ -168,7 +168,7 @@ namespace Ombi.Notifications.Agents
// Send to user
var playerIds = GetUsers(model, NotificationType.RequestDeclined);
await AddSubscribedUsers(playerIds);
await Send(playerIds, notification, settings);
await Send(playerIds, notification, settings, model);
}
protected override async Task RequestApproved(NotificationOptions model, MobileNotificationSettings settings)
@ -188,7 +188,7 @@ namespace Ombi.Notifications.Agents
var playerIds = GetUsers(model, NotificationType.RequestApproved);
await AddSubscribedUsers(playerIds);
await Send(playerIds, notification, settings);
await Send(playerIds, notification, settings, model);
}
protected override async Task AvailableRequest(NotificationOptions model, MobileNotificationSettings settings)
@ -207,20 +207,20 @@ namespace Ombi.Notifications.Agents
var playerIds = GetUsers(model, NotificationType.RequestAvailable);
await AddSubscribedUsers(playerIds);
await Send(playerIds, notification, settings);
await Send(playerIds, notification, settings, model);
}
protected override Task Send(NotificationMessage model, MobileNotificationSettings settings)
{
throw new NotImplementedException();
}
protected async Task Send(List<string> playerIds, NotificationMessage model, MobileNotificationSettings settings)
protected async Task Send(List<string> playerIds, NotificationMessage model, MobileNotificationSettings settings, NotificationOptions requestModel, bool isAdminNotification = false)
{
if (playerIds == null || !playerIds.Any())
{
return;
}
var response = await _api.PushNotification(playerIds, model.Message);
var response = await _api.PushNotification(playerIds, model.Message, isAdminNotification, requestModel.RequestId, (int)requestModel.RequestType);
_logger.LogDebug("Sent message to {0} recipients with message id {1}", response.recipients, response.id);
}
@ -239,7 +239,7 @@ namespace Ombi.Notifications.Agents
}
var playerIds = user.NotificationUserIds.Select(x => x.PlayerId).ToList();
await Send(playerIds, notification, settings);
await Send(playerIds, notification, settings, model);
}
private async Task<List<string>> GetAdmins(NotificationType type)

@ -2,6 +2,7 @@
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Humanizer;
using Ombi.Helpers;
using Ombi.Notifications.Models;
using Ombi.Settings.Settings.Models;
@ -39,7 +40,7 @@ namespace Ombi.Notifications
RequestedDate = req?.RequestedDate.ToString("D");
if (Type.IsNullOrEmpty())
{
Type = req?.RequestType.ToString();
Type = req?.RequestType.Humanize();
}
Overview = req?.Overview;
Year = req?.ReleaseDate.Year.ToString();
@ -91,7 +92,7 @@ namespace Ombi.Notifications
RequestedDate = req?.RequestedDate.ToString("D");
if (Type.IsNullOrEmpty())
{
Type = req?.RequestType.ToString();
Type = req?.RequestType.Humanize();
}
Overview = req?.ParentRequest.Overview;
@ -161,7 +162,7 @@ namespace Ombi.Notifications
IssueSubject = opts.Substitutes.TryGetValue("IssueSubject", out val) ? val : string.Empty;
NewIssueComment = opts.Substitutes.TryGetValue("NewIssueComment", out val) ? val : string.Empty;
UserName = opts.Substitutes.TryGetValue("IssueUser", out val) ? val : string.Empty;
Type = opts.Substitutes.TryGetValue("RequestType", out val) ? val : string.Empty;
Type = opts.Substitutes.TryGetValue("RequestType", out val) ? val.Humanize() : string.Empty;
}
// User Defined

@ -10,7 +10,7 @@
<ItemGroup>
<PackageReference Include="Ensure.That" Version="7.0.0-pre32" />
<PackageReference Include="MailKit" Version="2.0.3" />
<PackageReference Include="MailKit" Version="2.0.5" />
</ItemGroup>
<ItemGroup>

@ -1,16 +1,16 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netcoreapp2.0</TargetFramework>
<TargetFramework>netcoreapp2.1</TargetFramework>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore" Version="2.0.3" />
<PackageReference Include="Microsoft.AspNetCore" Version="2.1.2" />
<PackageReference Include="Moq" Version="4.7.99" />
<PackageReference Include="Nunit" Version="3.10.1" />
<PackageReference Include="NUnit.ConsoleRunner" Version="3.8.0" />
<PackageReference Include="NUnit3TestAdapter" Version="3.10.0" />
<packagereference Include="Microsoft.NET.Test.Sdk" Version="15.7.0"></packagereference>
<packagereference Include="Microsoft.NET.Test.Sdk" Version="15.8.0"></packagereference>
</ItemGroup>
<ItemGroup>

@ -89,6 +89,7 @@ namespace Ombi.Schedule.Jobs.Emby
_log.LogInformation("We have found the request {0} on Emby, sending the notification", movie?.Title ?? string.Empty);
movie.Available = true;
movie.MarkedAsAvailable = DateTime.Now;
if (movie.Available)
{
var recipient = movie.RequestedUser.Email.HasValue() ? movie.RequestedUser.Email : string.Empty;
@ -121,24 +122,53 @@ namespace Ombi.Schedule.Jobs.Emby
foreach (var child in tv)
{
IQueryable<EmbyEpisode> seriesEpisodes;
if (child.ParentRequest.TvDbId > 0)
var useImdb = false;
var useTvDb = false;
if (child.ParentRequest.ImdbId.HasValue())
{
useImdb = true;
}
if (child.ParentRequest.TvDbId.ToString().HasValue())
{
seriesEpisodes = embyEpisodes.Where(x => x.Series.TvDbId == child.ParentRequest.TvDbId.ToString());
useTvDb = true;
}
else if(child.ParentRequest.ImdbId.HasValue())
var tvDbId = child.ParentRequest.TvDbId;
var imdbId = child.ParentRequest.ImdbId;
IQueryable<EmbyEpisode> seriesEpisodes = null;
if (useImdb)
{
seriesEpisodes = embyEpisodes.Where(x => x.Series.ImdbId == child.ParentRequest.ImdbId);
seriesEpisodes = embyEpisodes.Where(x => x.Series.ImdbId == imdbId.ToString());
}
else
if (useTvDb && (seriesEpisodes == null || !seriesEpisodes.Any()))
{
seriesEpisodes = embyEpisodes.Where(x => x.Series.TvDbId == tvDbId.ToString());
}
if (seriesEpisodes == null)
{
continue;
}
if (!seriesEpisodes.Any())
{
// Let's try and match the series by name
seriesEpisodes = embyEpisodes.Where(x =>
x.Series.Title.Equals(child.Title, StringComparison.CurrentCultureIgnoreCase));
}
foreach (var season in child.SeasonRequests)
{
foreach (var episode in season.Episodes)
{
if (episode.Available)
{
continue;
}
var foundEp = await seriesEpisodes.FirstOrDefaultAsync(
x => x.EpisodeNumber == episode.EpisodeNumber &&
x.SeasonNumber == episode.Season.SeasonNumber);
@ -156,13 +186,14 @@ namespace Ombi.Schedule.Jobs.Emby
{
// We have fulfulled this request!
child.Available = true;
child.MarkedAsAvailable = DateTime.Now;
BackgroundJob.Enqueue(() => _notificationService.Publish(new NotificationOptions
{
DateTime = DateTime.Now,
NotificationType = NotificationType.RequestAvailable,
RequestId = child.ParentRequestId,
RequestId = child.Id,
RequestType = RequestType.TvShow,
Recipient = child.RequestedUser.Email,
Recipient = child.RequestedUser.Email
}));
}
}

@ -68,40 +68,96 @@ namespace Ombi.Schedule.Jobs.Emby
if (!ValidateSettings(server))
return;
await _repo.ExecuteSql("DELETE FROM EmbyEpisode");
await _repo.ExecuteSql("DELETE FROM EmbyContent");
//await _repo.ExecuteSql("DELETE FROM EmbyEpisode");
//await _repo.ExecuteSql("DELETE FROM EmbyContent");
var movies = await _api.GetAllMovies(server.ApiKey, 0, 200, server.AdministratorId, server.FullUri);
var totalCount = movies.TotalRecordCount;
var processed = 1;
var movies = await _api.GetAllMovies(server.ApiKey, server.AdministratorId, server.FullUri);
var mediaToAdd = new HashSet<EmbyContent>();
foreach (var movie in movies.Items)
while (processed < totalCount)
{
// Regular movie
await ProcessMovies(movie, mediaToAdd);
foreach (var movie in movies.Items)
{
if (movie.Type.Equals("boxset", StringComparison.InvariantCultureIgnoreCase))
{
var movieInfo =
await _api.GetCollection(movie.Id, server.ApiKey, server.AdministratorId, server.FullUri);
foreach (var item in movieInfo.Items)
{
await ProcessMovies(item, mediaToAdd);
}
processed++;
}
else
{
processed++;
// Regular movie
await ProcessMovies(movie, mediaToAdd);
}
}
// Get the next batch
movies = await _api.GetAllMovies(server.ApiKey, processed, 200, server.AdministratorId, server.FullUri);
await _repo.AddRange(mediaToAdd);
mediaToAdd.Clear();
}
// TV Time
var tv = await _api.GetAllShows(server.ApiKey, server.AdministratorId, server.FullUri);
foreach (var tvShow in tv.Items)
{
if (string.IsNullOrEmpty(tvShow.ProviderIds?.Tvdb))
// TV Time
var tv = await _api.GetAllShows(server.ApiKey, 0, 200, server.AdministratorId, server.FullUri);
var totalTv = tv.TotalRecordCount;
processed = 1;
while (processed < totalTv)
{
foreach (var tvShow in tv.Items)
{
Log.Error("Provider Id on tv {0} is null", tvShow.Name);
continue;
}
try
{
var existingTv = await _repo.GetByEmbyId(tvShow.Id);
if (existingTv == null)
mediaToAdd.Add(new EmbyContent
processed++;
if (string.IsNullOrEmpty(tvShow.ProviderIds?.Tvdb))
{
_logger.LogInformation("Provider Id on tv {0} is null", tvShow.Name);
continue;
}
var existingTv = await _repo.GetByEmbyId(tvShow.Id);
if (existingTv == null)
{
_logger.LogDebug("Adding new TV Show {0}", tvShow.Name);
mediaToAdd.Add(new EmbyContent
{
TvDbId = tvShow.ProviderIds?.Tvdb,
ImdbId = tvShow.ProviderIds?.Imdb,
TheMovieDbId = tvShow.ProviderIds?.Tmdb,
Title = tvShow.Name,
Type = EmbyMediaType.Series,
EmbyId = tvShow.Id,
Url = EmbyHelper.GetEmbyMediaUrl(tvShow.Id),
AddedAt = DateTime.UtcNow
});
}
else
{
_logger.LogDebug("We already have TV Show {0}", tvShow.Name);
}
}
catch (Exception)
{
TvDbId = tvShow.ProviderIds?.Tvdb,
ImdbId = tvShow.ProviderIds?.Imdb,
TheMovieDbId = tvShow.ProviderIds?.Tmdb,
Title = tvShow.Name,
Type = EmbyMediaType.Series,
EmbyId = tvShow.Id,
Url = EmbyHelper.GetEmbyMediaUrl(tvShow.Id),
AddedAt = DateTime.UtcNow
});
throw;
}
}
// Get the next batch
tv = await _api.GetAllShows(server.ApiKey, processed, 200, server.AdministratorId, server.FullUri);
await _repo.AddRange(mediaToAdd);
mediaToAdd.Clear();
}
if (mediaToAdd.Any())
@ -112,8 +168,10 @@ namespace Ombi.Schedule.Jobs.Emby
{
// Check if it exists
var existingMovie = await _repo.GetByEmbyId(movieInfo.Id);
if (existingMovie == null)
var alreadyGoingToAdd = content.Any(x => x.EmbyId == movieInfo.Id);
if (existingMovie == null && !alreadyGoingToAdd)
{
_logger.LogDebug("Adding new movie {0}", movieInfo.Name);
content.Add(new EmbyContent
{
ImdbId = movieInfo.ProviderIds.Imdb,
@ -124,6 +182,12 @@ namespace Ombi.Schedule.Jobs.Emby
Url = EmbyHelper.GetEmbyMediaUrl(movieInfo.Id),
AddedAt = DateTime.UtcNow,
});
}
else
{
// we have this
_logger.LogDebug("We already have movie {0}", movieInfo.Name);
}
}
private bool ValidateSettings(EmbyServers server)

@ -73,37 +73,51 @@ namespace Ombi.Schedule.Jobs.Emby
private async Task CacheEpisodes(EmbyServers server)
{
var allEpisodes = await _api.GetAllEpisodes(server.ApiKey, server.AdministratorId, server.FullUri);
var epToAdd = new List<EmbyEpisode>();
foreach (var ep in allEpisodes.Items)
var allEpisodes = await _api.GetAllEpisodes(server.ApiKey, 0, 200, server.AdministratorId, server.FullUri);
var total = allEpisodes.TotalRecordCount;
var processed = 1;
var epToAdd = new HashSet<EmbyEpisode>();
while (processed < total)
{
// Let's make sure we have the parent request, stop those pesky forign key errors,
// Damn me having data integrity
var parent = await _repo.GetByEmbyId(ep.SeriesId);
if (parent == null)
foreach (var ep in allEpisodes.Items)
{
_logger.LogInformation("The episode {0} does not relate to a series, so we cannot save this", ep.Name);
continue;
}
processed++;
// Let's make sure we have the parent request, stop those pesky forign key errors,
// Damn me having data integrity
var parent = await _repo.GetByEmbyId(ep.SeriesId);
if (parent == null)
{
_logger.LogInformation("The episode {0} does not relate to a series, so we cannot save this",
ep.Name);
continue;
}
var existingEpisode = await _repo.GetEpisodeByEmbyId(ep.Id);
if (existingEpisode == null)
{
// add it
epToAdd.Add(new EmbyEpisode
var existingEpisode = await _repo.GetEpisodeByEmbyId(ep.Id);
// Make sure it's not in the hashset too
var existingInList = epToAdd.Any(x => x.EmbyId == ep.Id);
if (existingEpisode == null && !existingInList)
{
EmbyId = ep.Id,
EpisodeNumber = ep.IndexNumber,
SeasonNumber = ep.ParentIndexNumber,
ParentId = ep.SeriesId,
TvDbId = ep.ProviderIds.Tvdb,
TheMovieDbId = ep.ProviderIds.Tmdb,
ImdbId = ep.ProviderIds.Imdb,
Title = ep.Name,
AddedAt = DateTime.UtcNow
});
_logger.LogDebug("Adding new episode {0} to parent {1}", ep.Name, ep.SeriesName);
// add it
epToAdd.Add(new EmbyEpisode
{
EmbyId = ep.Id,
EpisodeNumber = ep.IndexNumber,
SeasonNumber = ep.ParentIndexNumber,
ParentId = ep.SeriesId,
TvDbId = ep.ProviderIds.Tvdb,
TheMovieDbId = ep.ProviderIds.Tmdb,
ImdbId = ep.ProviderIds.Imdb,
Title = ep.Name,
AddedAt = DateTime.UtcNow
});
}
}
await _repo.AddRange(epToAdd);
epToAdd.Clear();
allEpisodes = await _api.GetAllEpisodes(server.ApiKey, processed, 200, server.AdministratorId, server.FullUri);
}
if (epToAdd.Any())

@ -492,41 +492,42 @@ namespace Ombi.Schedule.Jobs.Ombi
var orderedTv = series.OrderByDescending(x => x.AddedAt);
foreach (var t in orderedTv)
{
try
if (!t.HasTvDb)
{
if (!t.HasTvDb)
// We may need to use themoviedb for the imdbid or their own id to get info
if (t.HasTheMovieDb)
{
// We may need to use themoviedb for the imdbid or their own id to get info
if (t.HasTheMovieDb)
int.TryParse(t.TheMovieDbId, out var movieId);
var externals = await _movieApi.GetTvExternals(movieId);
if (externals == null || externals.tvdb_id <= 0)
{
int.TryParse(t.TheMovieDbId, out var movieId);
var externals = await _movieApi.GetTvExternals(movieId);
if (externals == null || externals.tvdb_id <= 0)
{
continue;
}
t.TvDbId = externals.tvdb_id.ToString();
continue;
}
// WE could check the below but we need to get the moviedb and then perform the above, let the metadata job figure this out.
//else if(t.HasImdb)
//{
// // Check the imdbid
// var externals = await _movieApi.Find(t.ImdbId, ExternalSource.imdb_id);
// if (externals?.tv_results == null || externals.tv_results.Length <= 0)
// {
// continue;
// }
// t.TvDbId = externals.tv_results.FirstOrDefault()..ToString();
//}
t.TvDbId = externals.tvdb_id.ToString();
}
// WE could check the below but we need to get the moviedb and then perform the above, let the metadata job figure this out.
//else if(t.HasImdb)
//{
// // Check the imdbid
// var externals = await _movieApi.Find(t.ImdbId, ExternalSource.imdb_id);
// if (externals?.tv_results == null || externals.tv_results.Length <= 0)
// {
// continue;
// }
// t.TvDbId = externals.tv_results.FirstOrDefault()..ToString();
//}
int.TryParse(t.TvDbId, out var tvdbId);
var info = await _tvApi.ShowLookupByTheTvDbId(tvdbId);
if (info == null)
{
continue;
}
}
int.TryParse(t.TvDbId, out var tvdbId);
var info = await _tvApi.ShowLookupByTheTvDbId(tvdbId);
if (info == null)
{
continue;
}
try
{
var banner = info.image?.original;
if (!string.IsNullOrEmpty(banner))
{
@ -588,7 +589,7 @@ namespace Ombi.Schedule.Jobs.Ombi
{
AddGenres(sb, $"Genres: {string.Join(", ", info.genres.Select(x => x.ToString()).ToArray())}");
}
}
catch (Exception e)
{
@ -676,20 +677,20 @@ namespace Ombi.Schedule.Jobs.Ombi
var orderedTv = series.OrderByDescending(x => x.AddedAt);
foreach (var t in orderedTv)
{
try
if (!t.TvDbId.HasValue())
{
if (!t.TvDbId.HasValue())
{
continue;
}
continue;
}
int.TryParse(t.TvDbId, out var tvdbId);
var info = await _tvApi.ShowLookupByTheTvDbId(tvdbId);
if (info == null)
{
continue;
}
int.TryParse(t.TvDbId, out var tvdbId);
var info = await _tvApi.ShowLookupByTheTvDbId(tvdbId);
if (info == null)
{
continue;
}
try
{
var banner = info.image?.original;
if (!string.IsNullOrEmpty(banner))
{
@ -752,7 +753,7 @@ namespace Ombi.Schedule.Jobs.Ombi
{
AddGenres(sb, $"Genres: {string.Join(", ", info.genres.Select(x => x.ToString()).ToArray())}");
}
}
catch (Exception e)
{

@ -72,7 +72,7 @@ namespace Ombi.Schedule.Jobs.Ombi
Logger.LogDebug(LoggingEvents.Updater, "Starting Update job");
var settings = await Settings.GetSettingsAsync();
if (!settings.AutoUpdateEnabled)
if (!settings.AutoUpdateEnabled && !settings.TestMode)
{
Logger.LogDebug(LoggingEvents.Updater, "Auto update is not enabled");
return;
@ -83,7 +83,7 @@ namespace Ombi.Schedule.Jobs.Ombi
var productVersion = AssemblyHelper.GetRuntimeVersion();
Logger.LogDebug(LoggingEvents.Updater, "Product Version {0}", productVersion);
var serverVersion = string.Empty;
try
{
var productArray = GetVersion();
@ -96,13 +96,17 @@ namespace Ombi.Schedule.Jobs.Ombi
Logger.LogDebug(LoggingEvents.Updater, "Branch {0}", branch);
Logger.LogDebug(LoggingEvents.Updater, "Looking for updates now");
//TODO this fails because the branch = featureupdater when it should be feature/updater
var updates = await Processor.Process(branch);
Logger.LogDebug(LoggingEvents.Updater, "Updates: {0}", updates);
var serverVersion = updates.UpdateVersionString;
serverVersion = updates.UpdateVersionString;
Logger.LogDebug(LoggingEvents.Updater, "Service Version {0}", updates.UpdateVersionString);
if (!serverVersion.Equals(version, StringComparison.CurrentCultureIgnoreCase))
if (!serverVersion.Equals(version, StringComparison.CurrentCultureIgnoreCase) || settings.TestMode)
{
// Let's download the correct zip
var desc = RuntimeInformation.OSDescription;
@ -135,7 +139,8 @@ namespace Ombi.Schedule.Jobs.Ombi
if (process == Architecture.Arm)
{
download = updates.Downloads.FirstOrDefault(x => x.Name.Contains("arm.", CompareOptions.IgnoreCase));
} else if (process == Architecture.Arm64)
}
else if (process == Architecture.Arm64)
{
download = updates.Downloads.FirstOrDefault(x => x.Name.Contains("arm64.", CompareOptions.IgnoreCase));
}
@ -206,32 +211,34 @@ namespace Ombi.Schedule.Jobs.Ombi
updaterExtension = ".exe";
}
var updaterFile = Path.Combine(Path.GetDirectoryName(Assembly.GetEntryAssembly().Location),
"TempUpdate", $"Ombi.Updater{updaterExtension}");
"TempUpdate", "updater", $"Ombi.Updater{updaterExtension}");
// Make sure the file is an executable
ExecLinuxCommand($"chmod +x {updaterFile}");
//ExecLinuxCommand($"chmod +x {updaterFile}");
// There must be an update
var start = new ProcessStartInfo
{
UseShellExecute = true,
CreateNoWindow = false, // Ignored if UseShellExecute is set to true
UseShellExecute = false,
CreateNoWindow = true, // Ignored if UseShellExecute is set to true
FileName = updaterFile,
Arguments = GetArgs(settings),
WorkingDirectory = Path.Combine(Path.GetDirectoryName(Assembly.GetEntryAssembly().Location), "TempUpdate"),
};
if (settings.Username.HasValue())
//if (settings.Username.HasValue())
//{
// start.UserName = settings.Username;
//}
//if (settings.Password.HasValue())
//{
// start.Password = settings.Password.ToSecureString();
//}
using (var proc = new Process { StartInfo = start })
{
start.UserName = settings.Username;
proc.Start();
}
if (settings.Password.HasValue())
{
start.Password = settings.Password.ToSecureString();
}
var proc = new Process { StartInfo = start };
proc.Start();
Logger.LogDebug(LoggingEvents.Updater, "Bye bye");
}
@ -254,10 +261,10 @@ namespace Ombi.Schedule.Jobs.Ombi
var sb = new StringBuilder();
sb.Append($"--applicationPath \"{currentLocation}\" --processname \"{processName}\" ");
if (settings.WindowsService)
{
sb.Append($"--windowsServiceName \"{settings.WindowsServiceName}\" ");
}
//if (settings.WindowsService)
//{
// sb.Append($"--windowsServiceName \"{settings.WindowsServiceName}\" ");
//}
var sb2 = new StringBuilder();
if (url?.Value.HasValue() ?? false)
{

@ -8,7 +8,7 @@ namespace Ombi.Schedule.Jobs.Plex.Models
public IEnumerable<int> Content { get; set; }
public IEnumerable<int> Episodes { get; set; }
public bool HasProcessedContent => Content.Any();
public bool HasProcessedEpisodes => Episodes.Any();
public bool HasProcessedContent => Content?.Any() ?? false;
public bool HasProcessedEpisodes => Episodes?.Any() ?? false;
}
}

@ -79,11 +79,16 @@ namespace Ombi.Schedule.Jobs.Plex
{
seriesEpisodes = plexEpisodes.Where(x => x.Series.ImdbId == imdbId.ToString());
}
if (useTvDb)
if (useTvDb && (seriesEpisodes == null || !seriesEpisodes.Any()) )
{
seriesEpisodes = plexEpisodes.Where(x => x.Series.TvDbId == tvDbId.ToString());
}
if (seriesEpisodes == null)
{
continue;
}
if (!seriesEpisodes.Any())
{
// Let's try and match the series by name
@ -118,6 +123,7 @@ namespace Ombi.Schedule.Jobs.Plex
{
// We have fulfulled this request!
child.Available = true;
child.MarkedAsAvailable = DateTime.Now;
_backgroundJobClient.Enqueue(() => _notificationService.Publish(new NotificationOptions
{
DateTime = DateTime.Now,
@ -158,6 +164,7 @@ namespace Ombi.Schedule.Jobs.Plex
}
movie.Available = true;
movie.MarkedAsAvailable = DateTime.Now;
if (movie.Available)
{
_backgroundJobClient.Enqueue(() => _notificationService.Publish(new NotificationOptions

@ -150,9 +150,9 @@ namespace Ombi.Schedule.Jobs.Plex
var retVal = new ProcessedContent();
var contentProcessed = new Dictionary<int, int>();
var episodesProcessed = new List<int>();
Logger.LogInformation("Getting all content from server {0}", servers.Name);
Logger.LogDebug("Getting all content from server {0}", servers.Name);
var allContent = await GetAllContent(servers, recentlyAddedSearch);
Logger.LogInformation("We found {0} items", allContent.Count);
Logger.LogDebug("We found {0} items", allContent.Count);
// Let's now process this.
var contentToAdd = new HashSet<PlexServerContent>();
@ -163,7 +163,7 @@ namespace Ombi.Schedule.Jobs.Plex
{
if (content.viewGroup.Equals(PlexMediaType.Episode.ToString(), StringComparison.CurrentCultureIgnoreCase))
{
Logger.LogInformation("Found some episodes, this must be a recently added sync");
Logger.LogDebug("Found some episodes, this must be a recently added sync");
var count = 0;
foreach (var epInfo in content.Metadata ?? new Metadata[]{})
{
@ -208,7 +208,7 @@ namespace Ombi.Schedule.Jobs.Plex
if (content.viewGroup.Equals(PlexMediaType.Show.ToString(), StringComparison.CurrentCultureIgnoreCase))
{
// Process Shows
Logger.LogInformation("Processing TV Shows");
Logger.LogDebug("Processing TV Shows");
var count = 0;
foreach (var show in content.Metadata ?? new Metadata[] { })
{
@ -237,7 +237,7 @@ namespace Ombi.Schedule.Jobs.Plex
}
if (content.viewGroup.Equals(PlexMediaType.Movie.ToString(), StringComparison.CurrentCultureIgnoreCase))
{
Logger.LogInformation("Processing Movies");
Logger.LogDebug("Processing Movies");
foreach (var movie in content?.Metadata ?? new Metadata[] { })
{
// Let's check if we have this movie
@ -251,7 +251,7 @@ namespace Ombi.Schedule.Jobs.Plex
//var existing = await Repo.GetByKey(movie.ratingKey);
if (existing != null)
{
Logger.LogInformation("We already have movie {0}", movie.title);
Logger.LogDebug("We already have movie {0}", movie.title);
continue;
}
@ -261,7 +261,7 @@ namespace Ombi.Schedule.Jobs.Plex
await Repo.Delete(hasSameKey);
}
Logger.LogInformation("Adding movie {0}", movie.title);
Logger.LogDebug("Adding movie {0}", movie.title);
var metaData = await PlexApi.GetMetadata(servers.PlexAuthToken, servers.FullUri,
movie.ratingKey);
var providerIds = PlexHelper.GetProviderIdFromPlexGuid(metaData.MediaContainer.Metadata
@ -381,6 +381,19 @@ namespace Ombi.Schedule.Jobs.Plex
if (existingContent != null)
{
// Let's make sure that we have some sort of ID e.g. Imdbid for this,
// Looks like it's possible to not have an Id for a show
// I suspect we cached that show just as it was added to Plex.
if (!existingContent.HasImdb && !existingContent.HasTheMovieDb && !existingContent.HasTvDb)
{
var showMetadata = await PlexApi.GetMetadata(servers.PlexAuthToken, servers.FullUri,
existingContent.Key);
GetProviderIds(showMetadata, existingContent);
await Repo.Update(existingContent);
}
// Just check the key
if (existingKey != null)
{
@ -421,7 +434,7 @@ namespace Ombi.Schedule.Jobs.Plex
{
try
{
Logger.LogInformation("We already have show {0} checking for new seasons",
Logger.LogDebug("We already have show {0} checking for new seasons",
existingContent.Title);
// Ok so we have it, let's check if there are any new seasons
var itemAdded = false;
@ -472,16 +485,13 @@ namespace Ombi.Schedule.Jobs.Plex
{
try
{
Logger.LogInformation("New show {0}, so add it", show.title);
Logger.LogDebug("New show {0}, so add it", show.title);
// Get the show metadata... This sucks since the `metadata` var contains all information about the show
// But it does not contain the `guid` property that we need to pull out thetvdb id...
var showMetadata = await PlexApi.GetMetadata(servers.PlexAuthToken, servers.FullUri,
show.ratingKey);
var providerIds =
PlexHelper.GetProviderIdFromPlexGuid(showMetadata.MediaContainer.Metadata.FirstOrDefault()
.guid);
var item = new PlexServerContent
{
AddedAt = DateTime.Now,
@ -492,20 +502,7 @@ namespace Ombi.Schedule.Jobs.Plex
Url = PlexHelper.GetPlexMediaUrl(servers.MachineIdentifier, show.ratingKey),
Seasons = new List<PlexSeasonsContent>()
};
if (providerIds.Type == ProviderType.ImdbId)
{
item.ImdbId = providerIds.ImdbId;
}
if (providerIds.Type == ProviderType.TheMovieDbId)
{
item.TheMovieDbId = providerIds.TheMovieDb;
}
if (providerIds.Type == ProviderType.TvDbId)
{
item.TvDbId = providerIds.TheTvDb;
}
GetProviderIds(showMetadata, item);
// Let's just double check to make sure we do not have it now we have some id's
var existingImdb = false;
@ -547,6 +544,27 @@ namespace Ombi.Schedule.Jobs.Plex
}
}
private static void GetProviderIds(PlexMetadata showMetadata, PlexServerContent existingContent)
{
var providerIds =
PlexHelper.GetProviderIdFromPlexGuid(showMetadata.MediaContainer.Metadata.FirstOrDefault()
.guid);
if (providerIds.Type == ProviderType.ImdbId)
{
existingContent.ImdbId = providerIds.ImdbId;
}
if (providerIds.Type == ProviderType.TheMovieDbId)
{
existingContent.TheMovieDbId = providerIds.TheMovieDb;
}
if (providerIds.Type == ProviderType.TvDbId)
{
existingContent.TvDbId = providerIds.TheTvDb;
}
}
/// <summary>
/// Gets all the library sections.
/// If the user has specified only certain libraries then we will only look for those
@ -573,7 +591,7 @@ namespace Ombi.Schedule.Jobs.Plex
.Select(x => x.Key.ToString()).ToList();
if (!keys.Contains(dir.key))
{
Logger.LogInformation("Lib {0} is not monitored, so skipping", dir.key);
Logger.LogDebug("Lib {0} is not monitored, so skipping", dir.key);
// We are not monitoring this lib
continue;
}

@ -57,6 +57,10 @@ namespace Ombi.Schedule.Jobs.Sonarr
await _ctx.Database.ExecuteSqlCommandAsync("DELETE FROM SonarrEpisodeCache");
foreach (var s in sonarrSeries)
{
if (!s.monitored)
{
continue;
}
_log.LogDebug("Syncing series: {0}", s.title);
var episodes = await _api.GetEpisodes(s.id, settings.ApiKey, settings.FullUri);
var monitoredEpisodes = episodes.Where(x => x.monitored || x.hasFile);

@ -9,7 +9,7 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="2.0.0" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="2.1.1" />
<PackageReference Include="Newtonsoft.Json" Version="11.0.2" />
</ItemGroup>

@ -1,4 +1,5 @@
using System.Collections.Generic;
using System;
using System.Collections.Generic;
using Ombi.Settings.Settings.Models.External;
namespace Ombi.Core.Settings.Models.External
@ -6,6 +7,10 @@ namespace Ombi.Core.Settings.Models.External
public sealed class PlexSettings : Ombi.Settings.Settings.Models.Settings
{
public bool Enable { get; set; }
/// <summary>
/// This is the ClientId for OAuth
/// </summary>
public Guid InstallId { get; set; }
public List<PlexServers> Servers { get; set; }
}

@ -10,5 +10,6 @@
public string ScriptLocation { get; set; }
public string WindowsServiceName { get; set; }
public bool WindowsService { get; set; }
public bool TestMode { get; set; }
}
}

@ -183,7 +183,7 @@ namespace Ombi.Store.Context
notificationToAdd = new NotificationTemplates
{
NotificationType = notificationType,
Message = "Hello! You {Title} on {ApplicationName}! This is now available! :)",
Message = "Hello! Your request for {Title} on {ApplicationName}! This is now available! :)",
Subject = "{ApplicationName}: {Title} is now available!",
Agent = agent,
Enabled = true,

@ -6,7 +6,7 @@ namespace Ombi.Store.Entities
{
public enum RequestType
{
TvShow,
Movie
TvShow = 0,
Movie = 1
}
}

@ -8,10 +8,13 @@ namespace Ombi.Store.Entities.Requests
{
public string Title { get; set; }
public bool Approved { get; set; }
public DateTime MarkedAsApproved { get; set; }
public DateTime RequestedDate { get; set; }
public bool Available { get; set; }
public DateTime? MarkedAsAvailable { get; set; }
public string RequestedUserId { get; set; }
public bool? Denied { get; set; }
public DateTime MarkedAsDenied { get; set; }
public string DeniedReason { get; set; }
public RequestType RequestType { get; set; }

@ -0,0 +1,981 @@
// <auto-generated />
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Metadata;
using Microsoft.EntityFrameworkCore.Migrations;
using Microsoft.EntityFrameworkCore.Storage;
using Microsoft.EntityFrameworkCore.Storage.Internal;
using Ombi.Helpers;
using Ombi.Store.Context;
using Ombi.Store.Entities;
using Ombi.Store.Entities.Requests;
using System;
namespace Ombi.Store.Migrations
{
[DbContext(typeof(OmbiContext))]
[Migration("20180703200952_EmbyUrlFix")]
partial class EmbyUrlFix
{
protected override void BuildTargetModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder
.HasAnnotation("ProductVersion", "2.0.3-rtm-10026");
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b =>
{
b.Property<string>("Id")
.ValueGeneratedOnAdd();
b.Property<string>("ConcurrencyStamp")
.IsConcurrencyToken();
b.Property<string>("Name")
.HasMaxLength(256);
b.Property<string>("NormalizedName")
.HasMaxLength(256);
b.HasKey("Id");
b.HasIndex("NormalizedName")
.IsUnique()
.HasName("RoleNameIndex");
b.ToTable("AspNetRoles");
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim<string>", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd();
b.Property<string>("ClaimType");
b.Property<string>("ClaimValue");
b.Property<string>("RoleId")
.IsRequired();
b.HasKey("Id");
b.HasIndex("RoleId");
b.ToTable("AspNetRoleClaims");
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim<string>", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd();
b.Property<string>("ClaimType");
b.Property<string>("ClaimValue");
b.Property<string>("UserId")
.IsRequired();
b.HasKey("Id");
b.HasIndex("UserId");
b.ToTable("AspNetUserClaims");
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin<string>", b =>
{
b.Property<string>("LoginProvider");
b.Property<string>("ProviderKey");
b.Property<string>("ProviderDisplayName");
b.Property<string>("UserId")
.IsRequired();
b.HasKey("LoginProvider", "ProviderKey");
b.HasIndex("UserId");
b.ToTable("AspNetUserLogins");
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole<string>", b =>
{
b.Property<string>("UserId");
b.Property<string>("RoleId");
b.HasKey("UserId", "RoleId");
b.HasIndex("RoleId");
b.ToTable("AspNetUserRoles");
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken<string>", b =>
{
b.Property<string>("UserId");
b.Property<string>("LoginProvider");
b.Property<string>("Name");
b.Property<string>("Value");
b.HasKey("UserId", "LoginProvider", "Name");
b.ToTable("AspNetUserTokens");
});
modelBuilder.Entity("Ombi.Store.Entities.ApplicationConfiguration", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd();
b.Property<int>("Type");
b.Property<string>("Value");
b.HasKey("Id");
b.ToTable("ApplicationConfiguration");
});
modelBuilder.Entity("Ombi.Store.Entities.Audit", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd();
b.Property<int>("AuditArea");
b.Property<int>("AuditType");
b.Property<DateTime>("DateTime");
b.Property<string>("Description");
b.Property<string>("User");
b.HasKey("Id");
b.ToTable("Audit");
});
modelBuilder.Entity("Ombi.Store.Entities.CouchPotatoCache", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd();
b.Property<int>("TheMovieDbId");
b.HasKey("Id");
b.ToTable("CouchPotatoCache");
});
modelBuilder.Entity("Ombi.Store.Entities.EmbyContent", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd();
b.Property<DateTime>("AddedAt");
b.Property<string>("EmbyId")
.IsRequired();
b.Property<string>("ImdbId");
b.Property<string>("ProviderId");
b.Property<string>("TheMovieDbId");
b.Property<string>("Title");
b.Property<string>("TvDbId");
b.Property<int>("Type");
b.Property<string>("Url");
b.HasKey("Id");
b.ToTable("EmbyContent");
});
modelBuilder.Entity("Ombi.Store.Entities.EmbyEpisode", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd();
b.Property<DateTime>("AddedAt");
b.Property<string>("EmbyId");
b.Property<int>("EpisodeNumber");
b.Property<string>("ImdbId");
b.Property<string>("ParentId");
b.Property<string>("ProviderId");
b.Property<int>("SeasonNumber");
b.Property<string>("TheMovieDbId");
b.Property<string>("Title");
b.Property<string>("TvDbId");
b.HasKey("Id");
b.HasIndex("ParentId");
b.ToTable("EmbyEpisode");
});
modelBuilder.Entity("Ombi.Store.Entities.GlobalSettings", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd();
b.Property<string>("Content");
b.Property<string>("SettingsName");
b.HasKey("Id");
b.ToTable("GlobalSettings");
});
modelBuilder.Entity("Ombi.Store.Entities.NotificationTemplates", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd();
b.Property<int>("Agent");
b.Property<bool>("Enabled");
b.Property<string>("Message");
b.Property<int>("NotificationType");
b.Property<string>("Subject");
b.HasKey("Id");
b.ToTable("NotificationTemplates");
});
modelBuilder.Entity("Ombi.Store.Entities.NotificationUserId", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd();
b.Property<DateTime>("AddedAt");
b.Property<string>("PlayerId");
b.Property<string>("UserId");
b.HasKey("Id");
b.HasIndex("UserId");
b.ToTable("NotificationUserId");
});
modelBuilder.Entity("Ombi.Store.Entities.OmbiUser", b =>
{
b.Property<string>("Id")
.ValueGeneratedOnAdd();
b.Property<int>("AccessFailedCount");
b.Property<string>("Alias");
b.Property<string>("ConcurrencyStamp")
.IsConcurrencyToken();
b.Property<string>("Email")
.HasMaxLength(256);
b.Property<bool>("EmailConfirmed");
b.Property<string>("EmbyConnectUserId");
b.Property<int?>("EpisodeRequestLimit");
b.Property<DateTime?>("LastLoggedIn");
b.Property<bool>("LockoutEnabled");
b.Property<DateTimeOffset?>("LockoutEnd");
b.Property<int?>("MovieRequestLimit");
b.Property<string>("NormalizedEmail")
.HasMaxLength(256);
b.Property<string>("NormalizedUserName")
.HasMaxLength(256);
b.Property<string>("PasswordHash");
b.Property<string>("PhoneNumber");
b.Property<bool>("PhoneNumberConfirmed");
b.Property<string>("ProviderUserId");
b.Property<string>("SecurityStamp");
b.Property<bool>("TwoFactorEnabled");
b.Property<string>("UserAccessToken");
b.Property<string>("UserName")
.HasMaxLength(256);
b.Property<int>("UserType");
b.HasKey("Id");
b.HasIndex("NormalizedEmail")
.HasName("EmailIndex");
b.HasIndex("NormalizedUserName")
.IsUnique()
.HasName("UserNameIndex");
b.ToTable("AspNetUsers");
});
modelBuilder.Entity("Ombi.Store.Entities.PlexEpisode", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd();
b.Property<int>("EpisodeNumber");
b.Property<int>("GrandparentKey");
b.Property<int>("Key");
b.Property<int>("ParentKey");
b.Property<int>("SeasonNumber");
b.Property<string>("Title");
b.HasKey("Id");
b.HasIndex("GrandparentKey");
b.ToTable("PlexEpisode");
});
modelBuilder.Entity("Ombi.Store.Entities.PlexSeasonsContent", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd();
b.Property<int>("ParentKey");
b.Property<int>("PlexContentId");
b.Property<int?>("PlexServerContentId");
b.Property<int>("SeasonKey");
b.Property<int>("SeasonNumber");
b.HasKey("Id");
b.HasIndex("PlexServerContentId");
b.ToTable("PlexSeasonsContent");
});
modelBuilder.Entity("Ombi.Store.Entities.PlexServerContent", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd();
b.Property<DateTime>("AddedAt");
b.Property<string>("ImdbId");
b.Property<int>("Key");
b.Property<string>("Quality");
b.Property<string>("ReleaseYear");
b.Property<string>("TheMovieDbId");
b.Property<string>("Title");
b.Property<string>("TvDbId");
b.Property<int>("Type");
b.Property<string>("Url");
b.HasKey("Id");
b.ToTable("PlexServerContent");
});
modelBuilder.Entity("Ombi.Store.Entities.RadarrCache", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd();
b.Property<bool>("HasFile");
b.Property<int>("TheMovieDbId");
b.HasKey("Id");
b.ToTable("RadarrCache");
});
modelBuilder.Entity("Ombi.Store.Entities.RecentlyAddedLog", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd();
b.Property<DateTime>("AddedAt");
b.Property<int>("ContentId");
b.Property<int>("ContentType");
b.Property<int?>("EpisodeNumber");
b.Property<int?>("SeasonNumber");
b.Property<int>("Type");
b.HasKey("Id");
b.ToTable("RecentlyAddedLog");
});
modelBuilder.Entity("Ombi.Store.Entities.Requests.ChildRequests", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd();
b.Property<bool>("Approved");
b.Property<bool>("Available");
b.Property<bool?>("Denied");
b.Property<string>("DeniedReason");
b.Property<int?>("IssueId");
b.Property<int>("ParentRequestId");
b.Property<int>("RequestType");
b.Property<DateTime>("RequestedDate");
b.Property<string>("RequestedUserId");
b.Property<int>("SeriesType");
b.Property<string>("Title");
b.HasKey("Id");
b.HasIndex("ParentRequestId");
b.HasIndex("RequestedUserId");
b.ToTable("ChildRequests");
});
modelBuilder.Entity("Ombi.Store.Entities.Requests.IssueCategory", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd();
b.Property<string>("Value");
b.HasKey("Id");
b.ToTable("IssueCategory");
});
modelBuilder.Entity("Ombi.Store.Entities.Requests.IssueComments", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd();
b.Property<string>("Comment");
b.Property<DateTime>("Date");
b.Property<int?>("IssuesId");
b.Property<string>("UserId");
b.HasKey("Id");
b.HasIndex("IssuesId");
b.HasIndex("UserId");
b.ToTable("IssueComments");
});
modelBuilder.Entity("Ombi.Store.Entities.Requests.Issues", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd();
b.Property<string>("Description");
b.Property<int>("IssueCategoryId");
b.Property<int?>("IssueId");
b.Property<string>("ProviderId");
b.Property<int?>("RequestId");
b.Property<int>("RequestType");
b.Property<DateTime?>("ResovledDate");
b.Property<int>("Status");
b.Property<string>("Subject");
b.Property<string>("Title");
b.Property<string>("UserReportedId");
b.HasKey("Id");
b.HasIndex("IssueCategoryId");
b.HasIndex("IssueId");
b.HasIndex("UserReportedId");
b.ToTable("Issues");
});
modelBuilder.Entity("Ombi.Store.Entities.Requests.MovieRequests", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd();
b.Property<bool>("Approved");
b.Property<bool>("Available");
b.Property<string>("Background");
b.Property<bool?>("Denied");
b.Property<string>("DeniedReason");
b.Property<DateTime?>("DigitalReleaseDate");
b.Property<string>("ImdbId");
b.Property<int?>("IssueId");
b.Property<string>("Overview");
b.Property<string>("PosterPath");
b.Property<int>("QualityOverride");
b.Property<DateTime>("ReleaseDate");
b.Property<int>("RequestType");
b.Property<DateTime>("RequestedDate");
b.Property<string>("RequestedUserId");
b.Property<int>("RootPathOverride");
b.Property<string>("Status");
b.Property<int>("TheMovieDbId");
b.Property<string>("Title");
b.HasKey("Id");
b.HasIndex("RequestedUserId");
b.ToTable("MovieRequests");
});
modelBuilder.Entity("Ombi.Store.Entities.Requests.RequestLog", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd();
b.Property<int>("EpisodeCount");
b.Property<DateTime>("RequestDate");
b.Property<int>("RequestId");
b.Property<int>("RequestType");
b.Property<string>("UserId");
b.HasKey("Id");
b.HasIndex("UserId");
b.ToTable("RequestLog");
});
modelBuilder.Entity("Ombi.Store.Entities.Requests.TvRequests", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd();
b.Property<string>("Background");
b.Property<string>("ImdbId");
b.Property<string>("Overview");
b.Property<string>("PosterPath");
b.Property<int?>("QualityOverride");
b.Property<DateTime>("ReleaseDate");
b.Property<int?>("RootFolder");
b.Property<string>("Status");
b.Property<string>("Title");
b.Property<int>("TvDbId");
b.HasKey("Id");
b.ToTable("TvRequests");
});
modelBuilder.Entity("Ombi.Store.Entities.RequestSubscription", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd();
b.Property<int>("RequestId");
b.Property<int>("RequestType");
b.Property<string>("UserId");
b.HasKey("Id");
b.HasIndex("UserId");
b.ToTable("RequestSubscription");
});
modelBuilder.Entity("Ombi.Store.Entities.SickRageCache", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd();
b.Property<int>("TvDbId");
b.HasKey("Id");
b.ToTable("SickRageCache");
});
modelBuilder.Entity("Ombi.Store.Entities.SickRageEpisodeCache", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd();
b.Property<int>("EpisodeNumber");
b.Property<int>("SeasonNumber");
b.Property<int>("TvDbId");
b.HasKey("Id");
b.ToTable("SickRageEpisodeCache");
});
modelBuilder.Entity("Ombi.Store.Entities.SonarrCache", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd();
b.Property<int>("TvDbId");
b.HasKey("Id");
b.ToTable("SonarrCache");
});
modelBuilder.Entity("Ombi.Store.Entities.SonarrEpisodeCache", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd();
b.Property<int>("EpisodeNumber");
b.Property<bool>("HasFile");
b.Property<int>("SeasonNumber");
b.Property<int>("TvDbId");
b.HasKey("Id");
b.ToTable("SonarrEpisodeCache");
});
modelBuilder.Entity("Ombi.Store.Entities.Tokens", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd();
b.Property<string>("Token");
b.Property<string>("UserId");
b.HasKey("Id");
b.HasIndex("UserId");
b.ToTable("Tokens");
});
modelBuilder.Entity("Ombi.Store.Repository.Requests.EpisodeRequests", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd();
b.Property<DateTime>("AirDate");
b.Property<bool>("Approved");
b.Property<bool>("Available");
b.Property<int>("EpisodeNumber");
b.Property<bool>("Requested");
b.Property<int>("SeasonId");
b.Property<string>("Title");
b.Property<string>("Url");
b.HasKey("Id");
b.HasIndex("SeasonId");
b.ToTable("EpisodeRequests");
});
modelBuilder.Entity("Ombi.Store.Repository.Requests.SeasonRequests", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd();
b.Property<int>("ChildRequestId");
b.Property<int>("SeasonNumber");
b.HasKey("Id");
b.HasIndex("ChildRequestId");
b.ToTable("SeasonRequests");
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim<string>", b =>
{
b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole")
.WithMany()
.HasForeignKey("RoleId")
.OnDelete(DeleteBehavior.Cascade);
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim<string>", b =>
{
b.HasOne("Ombi.Store.Entities.OmbiUser")
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade);
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin<string>", b =>
{
b.HasOne("Ombi.Store.Entities.OmbiUser")
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade);
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole<string>", b =>
{
b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole")
.WithMany()
.HasForeignKey("RoleId")
.OnDelete(DeleteBehavior.Cascade);
b.HasOne("Ombi.Store.Entities.OmbiUser")
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade);
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken<string>", b =>
{
b.HasOne("Ombi.Store.Entities.OmbiUser")
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade);
});
modelBuilder.Entity("Ombi.Store.Entities.EmbyEpisode", b =>
{
b.HasOne("Ombi.Store.Entities.EmbyContent", "Series")
.WithMany("Episodes")
.HasForeignKey("ParentId")
.HasPrincipalKey("EmbyId");
});
modelBuilder.Entity("Ombi.Store.Entities.NotificationUserId", b =>
{
b.HasOne("Ombi.Store.Entities.OmbiUser", "User")
.WithMany("NotificationUserIds")
.HasForeignKey("UserId");
});
modelBuilder.Entity("Ombi.Store.Entities.PlexEpisode", b =>
{
b.HasOne("Ombi.Store.Entities.PlexServerContent", "Series")
.WithMany("Episodes")
.HasForeignKey("GrandparentKey")
.HasPrincipalKey("Key")
.OnDelete(DeleteBehavior.Cascade);
});
modelBuilder.Entity("Ombi.Store.Entities.PlexSeasonsContent", b =>
{
b.HasOne("Ombi.Store.Entities.PlexServerContent")
.WithMany("Seasons")
.HasForeignKey("PlexServerContentId");
});
modelBuilder.Entity("Ombi.Store.Entities.Requests.ChildRequests", b =>
{
b.HasOne("Ombi.Store.Entities.Requests.TvRequests", "ParentRequest")
.WithMany("ChildRequests")
.HasForeignKey("ParentRequestId")
.OnDelete(DeleteBehavior.Cascade);
b.HasOne("Ombi.Store.Entities.OmbiUser", "RequestedUser")
.WithMany()
.HasForeignKey("RequestedUserId");
});
modelBuilder.Entity("Ombi.Store.Entities.Requests.IssueComments", b =>
{
b.HasOne("Ombi.Store.Entities.Requests.Issues", "Issues")
.WithMany("Comments")
.HasForeignKey("IssuesId");
b.HasOne("Ombi.Store.Entities.OmbiUser", "User")
.WithMany()
.HasForeignKey("UserId");
});
modelBuilder.Entity("Ombi.Store.Entities.Requests.Issues", b =>
{
b.HasOne("Ombi.Store.Entities.Requests.IssueCategory", "IssueCategory")
.WithMany()
.HasForeignKey("IssueCategoryId")
.OnDelete(DeleteBehavior.Cascade);
b.HasOne("Ombi.Store.Entities.Requests.ChildRequests")
.WithMany("Issues")
.HasForeignKey("IssueId");
b.HasOne("Ombi.Store.Entities.Requests.MovieRequests")
.WithMany("Issues")
.HasForeignKey("IssueId");
b.HasOne("Ombi.Store.Entities.OmbiUser", "UserReported")
.WithMany()
.HasForeignKey("UserReportedId");
});
modelBuilder.Entity("Ombi.Store.Entities.Requests.MovieRequests", b =>
{
b.HasOne("Ombi.Store.Entities.OmbiUser", "RequestedUser")
.WithMany()
.HasForeignKey("RequestedUserId");
});
modelBuilder.Entity("Ombi.Store.Entities.Requests.RequestLog", b =>
{
b.HasOne("Ombi.Store.Entities.OmbiUser", "User")
.WithMany()
.HasForeignKey("UserId");
});
modelBuilder.Entity("Ombi.Store.Entities.RequestSubscription", b =>
{
b.HasOne("Ombi.Store.Entities.OmbiUser", "User")
.WithMany()
.HasForeignKey("UserId");
});
modelBuilder.Entity("Ombi.Store.Entities.Tokens", b =>
{
b.HasOne("Ombi.Store.Entities.OmbiUser", "User")
.WithMany()
.HasForeignKey("UserId");
});
modelBuilder.Entity("Ombi.Store.Repository.Requests.EpisodeRequests", b =>
{
b.HasOne("Ombi.Store.Repository.Requests.SeasonRequests", "Season")
.WithMany("Episodes")
.HasForeignKey("SeasonId")
.OnDelete(DeleteBehavior.Cascade);
});
modelBuilder.Entity("Ombi.Store.Repository.Requests.SeasonRequests", b =>
{
b.HasOne("Ombi.Store.Entities.Requests.ChildRequests", "ChildRequest")
.WithMany("SeasonRequests")
.HasForeignKey("ChildRequestId")
.OnDelete(DeleteBehavior.Cascade);
});
#pragma warning restore 612, 618
}
}
}

@ -0,0 +1,20 @@
using Microsoft.EntityFrameworkCore.Migrations;
using System;
using System.Collections.Generic;
namespace Ombi.Store.Migrations
{
public partial class EmbyUrlFix : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.Sql(
@"UPDATE EmbyContent SET Url = replace( Url, 'http://app.emby.media/itemdetails.html', 'http://app.emby.media/#!/itemdetails.html' ) WHERE Url LIKE 'http://app.emby.media/itemdetails.html%';");
}
protected override void Down(MigrationBuilder migrationBuilder)
{
}
}
}

@ -0,0 +1,988 @@
// <auto-generated />
using System;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Migrations;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
using Ombi.Store.Context;
namespace Ombi.Store.Migrations
{
[DbContext(typeof(OmbiContext))]
[Migration("20180730085903_UserStats")]
partial class UserStats
{
protected override void BuildTargetModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder
.HasAnnotation("ProductVersion", "2.1.1-rtm-30846");
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b =>
{
b.Property<string>("Id")
.ValueGeneratedOnAdd();
b.Property<string>("ConcurrencyStamp")
.IsConcurrencyToken();
b.Property<string>("Name")
.HasMaxLength(256);
b.Property<string>("NormalizedName")
.HasMaxLength(256);
b.HasKey("Id");
b.HasIndex("NormalizedName")
.IsUnique()
.HasName("RoleNameIndex");
b.ToTable("AspNetRoles");
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim<string>", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd();
b.Property<string>("ClaimType");
b.Property<string>("ClaimValue");
b.Property<string>("RoleId")
.IsRequired();
b.HasKey("Id");
b.HasIndex("RoleId");
b.ToTable("AspNetRoleClaims");
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim<string>", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd();
b.Property<string>("ClaimType");
b.Property<string>("ClaimValue");
b.Property<string>("UserId")
.IsRequired();
b.HasKey("Id");
b.HasIndex("UserId");
b.ToTable("AspNetUserClaims");
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin<string>", b =>
{
b.Property<string>("LoginProvider");
b.Property<string>("ProviderKey");
b.Property<string>("ProviderDisplayName");
b.Property<string>("UserId")
.IsRequired();
b.HasKey("LoginProvider", "ProviderKey");
b.HasIndex("UserId");
b.ToTable("AspNetUserLogins");
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole<string>", b =>
{
b.Property<string>("UserId");
b.Property<string>("RoleId");
b.HasKey("UserId", "RoleId");
b.HasIndex("RoleId");
b.ToTable("AspNetUserRoles");
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken<string>", b =>
{
b.Property<string>("UserId");
b.Property<string>("LoginProvider");
b.Property<string>("Name");
b.Property<string>("Value");
b.HasKey("UserId", "LoginProvider", "Name");
b.ToTable("AspNetUserTokens");
});
modelBuilder.Entity("Ombi.Store.Entities.ApplicationConfiguration", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd();
b.Property<int>("Type");
b.Property<string>("Value");
b.HasKey("Id");
b.ToTable("ApplicationConfiguration");
});
modelBuilder.Entity("Ombi.Store.Entities.Audit", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd();
b.Property<int>("AuditArea");
b.Property<int>("AuditType");
b.Property<DateTime>("DateTime");
b.Property<string>("Description");
b.Property<string>("User");
b.HasKey("Id");
b.ToTable("Audit");
});
modelBuilder.Entity("Ombi.Store.Entities.CouchPotatoCache", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd();
b.Property<int>("TheMovieDbId");
b.HasKey("Id");
b.ToTable("CouchPotatoCache");
});
modelBuilder.Entity("Ombi.Store.Entities.EmbyContent", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd();
b.Property<DateTime>("AddedAt");
b.Property<string>("EmbyId")
.IsRequired();
b.Property<string>("ImdbId");
b.Property<string>("ProviderId");
b.Property<string>("TheMovieDbId");
b.Property<string>("Title");
b.Property<string>("TvDbId");
b.Property<int>("Type");
b.Property<string>("Url");
b.HasKey("Id");
b.ToTable("EmbyContent");
});
modelBuilder.Entity("Ombi.Store.Entities.EmbyEpisode", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd();
b.Property<DateTime>("AddedAt");
b.Property<string>("EmbyId");
b.Property<int>("EpisodeNumber");
b.Property<string>("ImdbId");
b.Property<string>("ParentId");
b.Property<string>("ProviderId");
b.Property<int>("SeasonNumber");
b.Property<string>("TheMovieDbId");
b.Property<string>("Title");
b.Property<string>("TvDbId");
b.HasKey("Id");
b.HasIndex("ParentId");
b.ToTable("EmbyEpisode");
});
modelBuilder.Entity("Ombi.Store.Entities.GlobalSettings", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd();
b.Property<string>("Content");
b.Property<string>("SettingsName");
b.HasKey("Id");
b.ToTable("GlobalSettings");
});
modelBuilder.Entity("Ombi.Store.Entities.NotificationTemplates", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd();
b.Property<int>("Agent");
b.Property<bool>("Enabled");
b.Property<string>("Message");
b.Property<int>("NotificationType");
b.Property<string>("Subject");
b.HasKey("Id");
b.ToTable("NotificationTemplates");
});
modelBuilder.Entity("Ombi.Store.Entities.NotificationUserId", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd();
b.Property<DateTime>("AddedAt");
b.Property<string>("PlayerId");
b.Property<string>("UserId");
b.HasKey("Id");
b.HasIndex("UserId");
b.ToTable("NotificationUserId");
});
modelBuilder.Entity("Ombi.Store.Entities.OmbiUser", b =>
{
b.Property<string>("Id")
.ValueGeneratedOnAdd();
b.Property<int>("AccessFailedCount");
b.Property<string>("Alias");
b.Property<string>("ConcurrencyStamp")
.IsConcurrencyToken();
b.Property<string>("Email")
.HasMaxLength(256);
b.Property<bool>("EmailConfirmed");
b.Property<string>("EmbyConnectUserId");
b.Property<int?>("EpisodeRequestLimit");
b.Property<DateTime?>("LastLoggedIn");
b.Property<bool>("LockoutEnabled");
b.Property<DateTimeOffset?>("LockoutEnd");
b.Property<int?>("MovieRequestLimit");
b.Property<string>("NormalizedEmail")
.HasMaxLength(256);
b.Property<string>("NormalizedUserName")
.HasMaxLength(256);
b.Property<string>("PasswordHash");
b.Property<string>("PhoneNumber");
b.Property<bool>("PhoneNumberConfirmed");
b.Property<string>("ProviderUserId");
b.Property<string>("SecurityStamp");
b.Property<bool>("TwoFactorEnabled");
b.Property<string>("UserAccessToken");
b.Property<string>("UserName")
.HasMaxLength(256);
b.Property<int>("UserType");
b.HasKey("Id");
b.HasIndex("NormalizedEmail")
.HasName("EmailIndex");
b.HasIndex("NormalizedUserName")
.IsUnique()
.HasName("UserNameIndex");
b.ToTable("AspNetUsers");
});
modelBuilder.Entity("Ombi.Store.Entities.PlexEpisode", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd();
b.Property<int>("EpisodeNumber");
b.Property<int>("GrandparentKey");
b.Property<int>("Key");
b.Property<int>("ParentKey");
b.Property<int>("SeasonNumber");
b.Property<string>("Title");
b.HasKey("Id");
b.HasIndex("GrandparentKey");
b.ToTable("PlexEpisode");
});
modelBuilder.Entity("Ombi.Store.Entities.PlexSeasonsContent", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd();
b.Property<int>("ParentKey");
b.Property<int>("PlexContentId");
b.Property<int?>("PlexServerContentId");
b.Property<int>("SeasonKey");
b.Property<int>("SeasonNumber");
b.HasKey("Id");
b.HasIndex("PlexServerContentId");
b.ToTable("PlexSeasonsContent");
});
modelBuilder.Entity("Ombi.Store.Entities.PlexServerContent", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd();
b.Property<DateTime>("AddedAt");
b.Property<string>("ImdbId");
b.Property<int>("Key");
b.Property<string>("Quality");
b.Property<string>("ReleaseYear");
b.Property<string>("TheMovieDbId");
b.Property<string>("Title");
b.Property<string>("TvDbId");
b.Property<int>("Type");
b.Property<string>("Url");
b.HasKey("Id");
b.ToTable("PlexServerContent");
});
modelBuilder.Entity("Ombi.Store.Entities.RadarrCache", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd();
b.Property<bool>("HasFile");
b.Property<int>("TheMovieDbId");
b.HasKey("Id");
b.ToTable("RadarrCache");
});
modelBuilder.Entity("Ombi.Store.Entities.RecentlyAddedLog", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd();
b.Property<DateTime>("AddedAt");
b.Property<int>("ContentId");
b.Property<int>("ContentType");
b.Property<int?>("EpisodeNumber");
b.Property<int?>("SeasonNumber");
b.Property<int>("Type");
b.HasKey("Id");
b.ToTable("RecentlyAddedLog");
});
modelBuilder.Entity("Ombi.Store.Entities.Requests.ChildRequests", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd();
b.Property<bool>("Approved");
b.Property<bool>("Available");
b.Property<bool?>("Denied");
b.Property<string>("DeniedReason");
b.Property<int?>("IssueId");
b.Property<DateTime>("MarkedAsApproved");
b.Property<DateTime?>("MarkedAsAvailable");
b.Property<DateTime>("MarkedAsDenied");
b.Property<int>("ParentRequestId");
b.Property<int>("RequestType");
b.Property<DateTime>("RequestedDate");
b.Property<string>("RequestedUserId");
b.Property<int>("SeriesType");
b.Property<string>("Title");
b.HasKey("Id");
b.HasIndex("ParentRequestId");
b.HasIndex("RequestedUserId");
b.ToTable("ChildRequests");
});
modelBuilder.Entity("Ombi.Store.Entities.Requests.IssueCategory", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd();
b.Property<string>("Value");
b.HasKey("Id");
b.ToTable("IssueCategory");
});
modelBuilder.Entity("Ombi.Store.Entities.Requests.IssueComments", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd();
b.Property<string>("Comment");
b.Property<DateTime>("Date");
b.Property<int?>("IssuesId");
b.Property<string>("UserId");
b.HasKey("Id");
b.HasIndex("IssuesId");
b.HasIndex("UserId");
b.ToTable("IssueComments");
});
modelBuilder.Entity("Ombi.Store.Entities.Requests.Issues", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd();
b.Property<string>("Description");
b.Property<int>("IssueCategoryId");
b.Property<int?>("IssueId");
b.Property<string>("ProviderId");
b.Property<int?>("RequestId");
b.Property<int>("RequestType");
b.Property<DateTime?>("ResovledDate");
b.Property<int>("Status");
b.Property<string>("Subject");
b.Property<string>("Title");
b.Property<string>("UserReportedId");
b.HasKey("Id");
b.HasIndex("IssueCategoryId");
b.HasIndex("IssueId");
b.HasIndex("UserReportedId");
b.ToTable("Issues");
});
modelBuilder.Entity("Ombi.Store.Entities.Requests.MovieRequests", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd();
b.Property<bool>("Approved");
b.Property<bool>("Available");
b.Property<string>("Background");
b.Property<bool?>("Denied");
b.Property<string>("DeniedReason");
b.Property<DateTime?>("DigitalReleaseDate");
b.Property<string>("ImdbId");
b.Property<int?>("IssueId");
b.Property<DateTime>("MarkedAsApproved");
b.Property<DateTime?>("MarkedAsAvailable");
b.Property<DateTime>("MarkedAsDenied");
b.Property<string>("Overview");
b.Property<string>("PosterPath");
b.Property<int>("QualityOverride");
b.Property<DateTime>("ReleaseDate");
b.Property<int>("RequestType");
b.Property<DateTime>("RequestedDate");
b.Property<string>("RequestedUserId");
b.Property<int>("RootPathOverride");
b.Property<string>("Status");
b.Property<int>("TheMovieDbId");
b.Property<string>("Title");
b.HasKey("Id");
b.HasIndex("RequestedUserId");
b.ToTable("MovieRequests");
});
modelBuilder.Entity("Ombi.Store.Entities.Requests.RequestLog", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd();
b.Property<int>("EpisodeCount");
b.Property<DateTime>("RequestDate");
b.Property<int>("RequestId");
b.Property<int>("RequestType");
b.Property<string>("UserId");
b.HasKey("Id");
b.HasIndex("UserId");
b.ToTable("RequestLog");
});
modelBuilder.Entity("Ombi.Store.Entities.Requests.TvRequests", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd();
b.Property<string>("Background");
b.Property<string>("ImdbId");
b.Property<string>("Overview");
b.Property<string>("PosterPath");
b.Property<int?>("QualityOverride");
b.Property<DateTime>("ReleaseDate");
b.Property<int?>("RootFolder");
b.Property<string>("Status");
b.Property<string>("Title");
b.Property<int>("TvDbId");
b.HasKey("Id");
b.ToTable("TvRequests");
});
modelBuilder.Entity("Ombi.Store.Entities.RequestSubscription", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd();
b.Property<int>("RequestId");
b.Property<int>("RequestType");
b.Property<string>("UserId");
b.HasKey("Id");
b.HasIndex("UserId");
b.ToTable("RequestSubscription");
});
modelBuilder.Entity("Ombi.Store.Entities.SickRageCache", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd();
b.Property<int>("TvDbId");
b.HasKey("Id");
b.ToTable("SickRageCache");
});
modelBuilder.Entity("Ombi.Store.Entities.SickRageEpisodeCache", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd();
b.Property<int>("EpisodeNumber");
b.Property<int>("SeasonNumber");
b.Property<int>("TvDbId");
b.HasKey("Id");
b.ToTable("SickRageEpisodeCache");
});
modelBuilder.Entity("Ombi.Store.Entities.SonarrCache", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd();
b.Property<int>("TvDbId");
b.HasKey("Id");
b.ToTable("SonarrCache");
});
modelBuilder.Entity("Ombi.Store.Entities.SonarrEpisodeCache", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd();
b.Property<int>("EpisodeNumber");
b.Property<bool>("HasFile");
b.Property<int>("SeasonNumber");
b.Property<int>("TvDbId");
b.HasKey("Id");
b.ToTable("SonarrEpisodeCache");
});
modelBuilder.Entity("Ombi.Store.Entities.Tokens", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd();
b.Property<string>("Token");
b.Property<string>("UserId");
b.HasKey("Id");
b.HasIndex("UserId");
b.ToTable("Tokens");
});
modelBuilder.Entity("Ombi.Store.Repository.Requests.EpisodeRequests", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd();
b.Property<DateTime>("AirDate");
b.Property<bool>("Approved");
b.Property<bool>("Available");
b.Property<int>("EpisodeNumber");
b.Property<bool>("Requested");
b.Property<int>("SeasonId");
b.Property<string>("Title");
b.Property<string>("Url");
b.HasKey("Id");
b.HasIndex("SeasonId");
b.ToTable("EpisodeRequests");
});
modelBuilder.Entity("Ombi.Store.Repository.Requests.SeasonRequests", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd();
b.Property<int>("ChildRequestId");
b.Property<int>("SeasonNumber");
b.HasKey("Id");
b.HasIndex("ChildRequestId");
b.ToTable("SeasonRequests");
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim<string>", b =>
{
b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole")
.WithMany()
.HasForeignKey("RoleId")
.OnDelete(DeleteBehavior.Cascade);
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim<string>", b =>
{
b.HasOne("Ombi.Store.Entities.OmbiUser")
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade);
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin<string>", b =>
{
b.HasOne("Ombi.Store.Entities.OmbiUser")
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade);
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole<string>", b =>
{
b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole")
.WithMany()
.HasForeignKey("RoleId")
.OnDelete(DeleteBehavior.Cascade);
b.HasOne("Ombi.Store.Entities.OmbiUser")
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade);
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken<string>", b =>
{
b.HasOne("Ombi.Store.Entities.OmbiUser")
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade);
});
modelBuilder.Entity("Ombi.Store.Entities.EmbyEpisode", b =>
{
b.HasOne("Ombi.Store.Entities.EmbyContent", "Series")
.WithMany("Episodes")
.HasForeignKey("ParentId")
.HasPrincipalKey("EmbyId");
});
modelBuilder.Entity("Ombi.Store.Entities.NotificationUserId", b =>
{
b.HasOne("Ombi.Store.Entities.OmbiUser", "User")
.WithMany("NotificationUserIds")
.HasForeignKey("UserId");
});
modelBuilder.Entity("Ombi.Store.Entities.PlexEpisode", b =>
{
b.HasOne("Ombi.Store.Entities.PlexServerContent", "Series")
.WithMany("Episodes")
.HasForeignKey("GrandparentKey")
.HasPrincipalKey("Key")
.OnDelete(DeleteBehavior.Cascade);
});
modelBuilder.Entity("Ombi.Store.Entities.PlexSeasonsContent", b =>
{
b.HasOne("Ombi.Store.Entities.PlexServerContent")
.WithMany("Seasons")
.HasForeignKey("PlexServerContentId");
});
modelBuilder.Entity("Ombi.Store.Entities.Requests.ChildRequests", b =>
{
b.HasOne("Ombi.Store.Entities.Requests.TvRequests", "ParentRequest")
.WithMany("ChildRequests")
.HasForeignKey("ParentRequestId")
.OnDelete(DeleteBehavior.Cascade);
b.HasOne("Ombi.Store.Entities.OmbiUser", "RequestedUser")
.WithMany()
.HasForeignKey("RequestedUserId");
});
modelBuilder.Entity("Ombi.Store.Entities.Requests.IssueComments", b =>
{
b.HasOne("Ombi.Store.Entities.Requests.Issues", "Issues")
.WithMany("Comments")
.HasForeignKey("IssuesId");
b.HasOne("Ombi.Store.Entities.OmbiUser", "User")
.WithMany()
.HasForeignKey("UserId");
});
modelBuilder.Entity("Ombi.Store.Entities.Requests.Issues", b =>
{
b.HasOne("Ombi.Store.Entities.Requests.IssueCategory", "IssueCategory")
.WithMany()
.HasForeignKey("IssueCategoryId")
.OnDelete(DeleteBehavior.Cascade);
b.HasOne("Ombi.Store.Entities.Requests.ChildRequests")
.WithMany("Issues")
.HasForeignKey("IssueId");
b.HasOne("Ombi.Store.Entities.Requests.MovieRequests")
.WithMany("Issues")
.HasForeignKey("IssueId");
b.HasOne("Ombi.Store.Entities.OmbiUser", "UserReported")
.WithMany()
.HasForeignKey("UserReportedId");
});
modelBuilder.Entity("Ombi.Store.Entities.Requests.MovieRequests", b =>
{
b.HasOne("Ombi.Store.Entities.OmbiUser", "RequestedUser")
.WithMany()
.HasForeignKey("RequestedUserId");
});
modelBuilder.Entity("Ombi.Store.Entities.Requests.RequestLog", b =>
{
b.HasOne("Ombi.Store.Entities.OmbiUser", "User")
.WithMany()
.HasForeignKey("UserId");
});
modelBuilder.Entity("Ombi.Store.Entities.RequestSubscription", b =>
{
b.HasOne("Ombi.Store.Entities.OmbiUser", "User")
.WithMany()
.HasForeignKey("UserId");
});
modelBuilder.Entity("Ombi.Store.Entities.Tokens", b =>
{
b.HasOne("Ombi.Store.Entities.OmbiUser", "User")
.WithMany()
.HasForeignKey("UserId");
});
modelBuilder.Entity("Ombi.Store.Repository.Requests.EpisodeRequests", b =>
{
b.HasOne("Ombi.Store.Repository.Requests.SeasonRequests", "Season")
.WithMany("Episodes")
.HasForeignKey("SeasonId")
.OnDelete(DeleteBehavior.Cascade);
});
modelBuilder.Entity("Ombi.Store.Repository.Requests.SeasonRequests", b =>
{
b.HasOne("Ombi.Store.Entities.Requests.ChildRequests", "ChildRequest")
.WithMany("SeasonRequests")
.HasForeignKey("ChildRequestId")
.OnDelete(DeleteBehavior.Cascade);
});
#pragma warning restore 612, 618
}
}
}

@ -0,0 +1,72 @@
using System;
using Microsoft.EntityFrameworkCore.Migrations;
namespace Ombi.Store.Migrations
{
public partial class UserStats : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AddColumn<DateTime>(
name: "MarkedAsApproved",
table: "MovieRequests",
nullable: false,
defaultValue: new DateTime(1, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified));
migrationBuilder.AddColumn<DateTime>(
name: "MarkedAsAvailable",
table: "MovieRequests",
nullable: true);
migrationBuilder.AddColumn<DateTime>(
name: "MarkedAsDenied",
table: "MovieRequests",
nullable: false,
defaultValue: new DateTime(1, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified));
migrationBuilder.AddColumn<DateTime>(
name: "MarkedAsApproved",
table: "ChildRequests",
nullable: false,
defaultValue: new DateTime(1, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified));
migrationBuilder.AddColumn<DateTime>(
name: "MarkedAsAvailable",
table: "ChildRequests",
nullable: true);
migrationBuilder.AddColumn<DateTime>(
name: "MarkedAsDenied",
table: "ChildRequests",
nullable: false,
defaultValue: new DateTime(1, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified));
}
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropColumn(
name: "MarkedAsApproved",
table: "MovieRequests");
migrationBuilder.DropColumn(
name: "MarkedAsAvailable",
table: "MovieRequests");
migrationBuilder.DropColumn(
name: "MarkedAsDenied",
table: "MovieRequests");
migrationBuilder.DropColumn(
name: "MarkedAsApproved",
table: "ChildRequests");
migrationBuilder.DropColumn(
name: "MarkedAsAvailable",
table: "ChildRequests");
migrationBuilder.DropColumn(
name: "MarkedAsDenied",
table: "ChildRequests");
}
}
}

@ -1,15 +1,9 @@
// <auto-generated />
using System;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Metadata;
using Microsoft.EntityFrameworkCore.Migrations;
using Microsoft.EntityFrameworkCore.Storage;
using Microsoft.EntityFrameworkCore.Storage.Internal;
using Ombi.Helpers;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
using Ombi.Store.Context;
using Ombi.Store.Entities;
using Ombi.Store.Entities.Requests;
using System;
namespace Ombi.Store.Migrations
{
@ -20,7 +14,7 @@ namespace Ombi.Store.Migrations
{
#pragma warning disable 612, 618
modelBuilder
.HasAnnotation("ProductVersion", "2.0.3-rtm-10026");
.HasAnnotation("ProductVersion", "2.1.1-rtm-30846");
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b =>
{
@ -481,6 +475,12 @@ namespace Ombi.Store.Migrations
b.Property<int?>("IssueId");
b.Property<DateTime>("MarkedAsApproved");
b.Property<DateTime?>("MarkedAsAvailable");
b.Property<DateTime>("MarkedAsDenied");
b.Property<int>("ParentRequestId");
b.Property<int>("RequestType");
@ -595,6 +595,12 @@ namespace Ombi.Store.Migrations
b.Property<int?>("IssueId");
b.Property<DateTime>("MarkedAsApproved");
b.Property<DateTime?>("MarkedAsAvailable");
b.Property<DateTime>("MarkedAsDenied");
b.Property<string>("Overview");
b.Property<string>("PosterPath");

@ -10,10 +10,10 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="2.0.4" />
<PackageReference Include="Microsoft.AspNetCore.Identity.EntityFrameworkCore" Version="2.0.3" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="2.0.3" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="2.0.3" />
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="2.1.1" />
<PackageReference Include="Microsoft.AspNetCore.Identity.EntityFrameworkCore" Version="2.1.2" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="2.1.1" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="2.1.1" />
<PackageReference Include="Newtonsoft.Json" Version="11.0.2" />
<PackageReference Include="SQLitePCLRaw.bundle_e_sqlite3" Version="1.1.9" />
</ItemGroup>

@ -7,12 +7,12 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.TestHost" Version="2.0.3" />
<PackageReference Include="Microsoft.AspNetCore.TestHost" Version="2.1.1" />
<PackageReference Include="Moq" Version="4.7.99" />
<PackageReference Include="Nunit" Version="3.8.1" />
<PackageReference Include="NUnit.ConsoleRunner" Version="3.7.0" />
<PackageReference Include="NUnit3TestAdapter" Version="3.8.0" />
<packagereference Include="Microsoft.NET.Test.Sdk" Version="15.7.0"></packagereference>
<packagereference Include="Microsoft.NET.Test.Sdk" Version="15.8.0"></packagereference>
</ItemGroup>
<ItemGroup>

@ -11,7 +11,7 @@ namespace Ombi.Updater
ProcessInfo GetCurrentProcess();
int GetCurrentProcessId();
ProcessInfo GetProcessById(int id);
void Kill(StartupOptions opts);
bool Kill(StartupOptions opts);
void KillAll(string processName);
void SetPriority(int processId, ProcessPriorityClass priority);
void WaitForExit(Process process);

@ -22,29 +22,23 @@ namespace Ombi.Updater
{
// Kill Ombi Process
var p = new ProcessProvider();
bool killed = false;
try
{
p.Kill(opt);
killed = p.Kill(opt);
}
catch (Exception e)
{
Console.WriteLine(e);
}
// Make sure the process has been killed
while (p.FindProcessByName(opt.ProcessName).Any())
if (!killed)
{
Thread.Sleep(500);
_log.LogDebug("Found another process called {0}, KILLING!", opt.ProcessName);
var proc = p.FindProcessByName(opt.ProcessName).FirstOrDefault();
if (proc != null)
{
_log.LogDebug($"[{proc.Id}] - {proc.Name} - Path: {proc.StartPath}");
opt.OmbiProcessId = proc.Id;
p.Kill(opt);
}
_log.LogDebug("Couldn't kill the ombi process");
return;
}
_log.LogDebug("Starting to move the files");
@ -111,21 +105,23 @@ namespace Ombi.Updater
var location = System.Reflection.Assembly.GetEntryAssembly().Location;
location = Path.GetDirectoryName(location);
_log.LogDebug("We are currently in dir {0}", location);
var updatedLocation = Directory.GetParent(location).FullName;
_log.LogDebug("The files are in {0}", updatedLocation); // Since the updater is a folder deeper
_log.LogDebug("Ombi is installed at {0}", options.ApplicationPath);
//Now Create all of the directories
foreach (string dirPath in Directory.GetDirectories(location, "*",
foreach (string dirPath in Directory.GetDirectories(updatedLocation, "*",
SearchOption.AllDirectories))
{
var newDir = dirPath.Replace(location, options.ApplicationPath);
var newDir = dirPath.Replace(updatedLocation, options.ApplicationPath);
Directory.CreateDirectory(newDir);
_log.LogDebug("Created dir {0}", newDir);
}
//Copy all the files & Replaces any files with the same name
foreach (string currentPath in Directory.GetFiles(location, "*.*",
foreach (string currentPath in Directory.GetFiles(updatedLocation, "*.*",
SearchOption.AllDirectories))
{
var newFile = currentPath.Replace(location, options.ApplicationPath);
var newFile = currentPath.Replace(updatedLocation, options.ApplicationPath);
File.Copy(currentPath, newFile, true);
_log.LogDebug("Replaced file {0}", newFile);
}

@ -3,7 +3,7 @@
<PropertyGroup>
<OutputType>Exe</OutputType>
<RuntimeIdentifiers>win10-x64;win10-x86;osx-x64;ubuntu-x64;debian.8-x64;centos.7-x64;linux-x64;linux-arm;linux-arm64;</RuntimeIdentifiers>
<TargetFramework>netcoreapp2.0</TargetFramework>
<TargetFramework>netcoreapp2.1</TargetFramework>
<AssemblyVersion>3.0.0.0</AssemblyVersion>
<FileVersion>3.0.0.0</FileVersion>
<Version></Version>
@ -12,14 +12,14 @@
<ItemGroup>
<PackageReference Include="CommandLineParser" Version="2.1.1-beta" />
<PackageReference Include="Microsoft.Extensions.Configuration" Version="2.0.2" />
<PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="2.0.2" />
<PackageReference Include="Microsoft.Extensions.Configuration.CommandLine" Version="2.0.2" />
<PackageReference Include="Microsoft.Extensions.Configuration.FileExtensions" Version="2.0.2" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="2.0.0" />
<PackageReference Include="Microsoft.Extensions.Logging" Version="2.0.2" />
<PackageReference Include="Microsoft.Extensions.Logging.Console" Version="2.0.2" />
<PackageReference Include="Microsoft.Extensions.Logging.Debug" Version="2.0.2" />
<PackageReference Include="Microsoft.Extensions.Configuration" Version="2.1.1" />
<PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="2.1.1" />
<PackageReference Include="Microsoft.Extensions.Configuration.CommandLine" Version="2.1.1" />
<PackageReference Include="Microsoft.Extensions.Configuration.FileExtensions" Version="2.1.1" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="2.1.1" />
<PackageReference Include="Microsoft.Extensions.Logging" Version="2.1.1" />
<PackageReference Include="Microsoft.Extensions.Logging.Console" Version="2.1.1" />
<PackageReference Include="Microsoft.Extensions.Logging.Debug" Version="2.1.1" />
<PackageReference Include="Serilog" Version="2.6.0-dev-00892" />
<PackageReference Include="Serilog.Extensions.Logging" Version="2.0.2" />
<PackageReference Include="Serilog.Sinks.File" Version="3.2.0" />

@ -73,33 +73,32 @@ namespace Ombi.Updater
process.PriorityClass = priority;
}
public void Kill(StartupOptions opts)
public bool Kill(StartupOptions opts)
{
if (opts.IsWindowsService)
{
Console.WriteLine("Stopping Service {0}", opts.WindowsServiceName);
var process = new Process();
var startInfo =
new ProcessStartInfo
{
WindowStyle = ProcessWindowStyle.Hidden,
FileName = "cmd.exe",
Arguments = $"/C net stop \"{opts.WindowsServiceName}\""
};
process.StartInfo = startInfo;
process.Start();
}
else
{
//if (opts.IsWindowsService)
//{
// Console.WriteLine("Stopping Service {0}", opts.WindowsServiceName);
// var process = new Process();
// var startInfo =
// new ProcessStartInfo
// {
// WindowStyle = ProcessWindowStyle.Hidden,
// FileName = "cmd.exe",
// Arguments = $"/C net stop \"{opts.WindowsServiceName}\""
// };
// process.StartInfo = startInfo;
// process.Start();
//}
//else
//{
var process = Process.GetProcesses().FirstOrDefault(p => p.ProcessName == opts.ProcessName);
if (process == null)
{
Console.WriteLine("Cannot find process with name: {0}", opts.ProcessName);
return;
return false;
}
process.Refresh();
if (process.Id > 0)
{
@ -108,8 +107,12 @@ namespace Ombi.Updater
Console.WriteLine("[{0}]: Waiting for exit", process.Id);
process.WaitForExit();
Console.WriteLine("[{0}]: Process terminated successfully", process.Id);
return true;
}
}
return false;
//}
}
public void KillAll(string processName)

@ -2,7 +2,7 @@
"profiles": {
"Ombi.Updater": {
"commandName": "Project",
"commandLineArgs": "--applicationPath \\\"C:\\\\Users\\\\Jamie\\\\Source\\\\Repos\\\\Ombi\\\\src\\\\Ombi\\\\bin\\\\Debug\\\\netcoreapp2.0\\\" --processname \\\"Ombi\\\" --startupArgs http://*:5000"
"commandLineArgs": "--applicationPath \"C:\\_git\\ombi\\src\\Ombi.Updater\\bin\\Debug\\netcoreapp2.0\" --processname \"Ombi\""
}
}
}

@ -1,23 +1,10 @@
/wwwroot/css/**
/wwwroot/fonts/**
/wwwroot/lib/**
/wwwroot/maps/**
/wwwroot/dist/**
/wwwroot/*.js.map
/wwwroot/*.js
# dependencies
/node_modules
/bower_components
# misc
node_modules
bin
obj
wwwroot/dist
*.log
/.sass-cache
/connect.lock
/coverage/*
/libpeerconnection.log
npm-debug.log
testem.log
#/typings
/systemjs.config.js*
/Logs/**
**.db

@ -4,7 +4,7 @@
"version": "2.0.0",
"tasks": [
{
"taskName": "restore",
"label": "restore",
"command": "npm",
"type": "shell",
"args": [
@ -14,7 +14,16 @@
"problemMatcher": []
},
{
"taskName": "build",
"label": "clean",
"command": "dotnet",
"type": "shell",
"args": [
"clean"
],
"problemMatcher": "$msCompile"
},
{
"label": "build",
"command": "dotnet",
"type": "shell",
"args": [
@ -27,7 +36,7 @@
"problemMatcher": "$msCompile"
},
{
"taskName": "lint",
"label": "lint",
"type": "shell",
"command": "npm",
"args": [

@ -94,9 +94,31 @@ namespace Ombi
}
else
{
var identity = new GenericIdentity("API");
var principal = new GenericPrincipal(identity, new[] { "Admin", "ApiUser" });
context.User = principal;
// Check if we have a UserName header if so we can impersonate that user
if (context.Request.Headers.Keys.Contains("UserName", StringComparer.InvariantCultureIgnoreCase))
{
var username = context.Request.Headers["UserName"].FirstOrDefault();
var um = context.RequestServices.GetService<OmbiUserManager>();
var user = await um.Users.FirstOrDefaultAsync(x =>
x.UserName.Equals(username, StringComparison.InvariantCultureIgnoreCase));
if (user == null)
{
context.Response.StatusCode = (int)HttpStatusCode.Unauthorized;
await context.Response.WriteAsync("Invalid User");
await next.Invoke(context);
}
var roles = await um.GetRolesAsync(user);
var identity = new GenericIdentity(user.UserName);
var principal = new GenericPrincipal(identity, roles.ToArray());
context.User = principal;
}
else
{
var identity = new GenericIdentity("API");
var principal = new GenericPrincipal(identity, new[] { "Admin", "ApiUser" });
context.User = principal;
}
await next.Invoke(context);
}
}

@ -1,7 +1,7 @@
import { animate, style, transition, trigger } from "@angular/animations";
import { AnimationEntryMetadata } from "@angular/core";
import { AnimationTriggerMetadata } from "@angular/animations";
export const fadeInOutAnimation: AnimationEntryMetadata = trigger("fadeInOut", [
export const fadeInOutAnimation: AnimationTriggerMetadata = trigger("fadeInOut", [
transition(":enter", [ // :enter is alias to 'void => *'
style({ opacity: 0 }),
animate(1000, style({ opacity: 1 })),

@ -134,6 +134,12 @@
<li [ngClass]="{'active': 'no' === translate.currentLang}">
<a (click)="translate.use('no')" [translate]="'NavigationBar.Language.Norwegian'"></a>
</li>
<li [ngClass]="{'active': 'pt' === translate.currentLang}">
<a (click)="translate.use('pt')" [translate]="'NavigationBar.Language.BrazillianPortuguese'"></a>
</li>
<li [ngClass]="{'active': 'pl' === translate.currentLang}">
<a (click)="translate.use('pl')" [translate]="'NavigationBar.Language.Polish'"></a>
</li>
</ul>
</li>
</ul>

@ -33,20 +33,20 @@ export class AppComponent implements OnInit {
private readonly jobService: JobService,
public readonly translate: TranslateService,
private readonly identityService: IdentityService,
private readonly platformLocation: PlatformLocation) {
private readonly platformLocation: PlatformLocation) {
const base = this.platformLocation.getBaseHrefFromDOM();
const base = this.platformLocation.getBaseHrefFromDOM();
if (base.length > 1) {
__webpack_public_path__ = base + "/dist/";
}
this.translate.addLangs(["en", "de", "fr","da","es","it","nl","sv","no"]);
this.translate.addLangs(["en", "de", "fr", "da", "es", "it", "nl", "sv", "no", "pl", "pt"]);
// this language will be used as a fallback when a translation isn't found in the current language
this.translate.setDefaultLang("en");
// See if we can match the supported langs with the current browser lang
const browserLang: string = translate.getBrowserLang();
this.translate.use(browserLang.match(/en|fr|da|de|es|it|nl|sv|no/) ? browserLang : "en");
this.translate.use(browserLang.match(/en|fr|da|de|es|it|nl|sv|no|pl|pt/) ? browserLang : "en");
}
public ngOnInit() {
@ -88,8 +88,8 @@ export class AppComponent implements OnInit {
public openMobileApp(event: any) {
event.preventDefault();
if(!this.customizationSettings.applicationUrl) {
this.notificationService.warning("Mobile","Please ask your admin to setup the Application URL!");
if (!this.customizationSettings.applicationUrl) {
this.notificationService.warning("Mobile", "Please ask your admin to setup the Application URL!");
return;
}

@ -1,12 +1,12 @@
import {CommonModule, PlatformLocation} from "@angular/common";
import {HttpClient, HttpClientModule} from "@angular/common/http";
import {NgModule} from "@angular/core";
import {FormsModule, ReactiveFormsModule} from "@angular/forms";
import {HttpModule} from "@angular/http";
import {MatButtonModule, MatCardModule, MatInputModule, MatTabsModule} from "@angular/material";
import {BrowserModule} from "@angular/platform-browser";
import {BrowserAnimationsModule} from "@angular/platform-browser/animations";
import {RouterModule, Routes} from "@angular/router";
import { CommonModule, PlatformLocation } from "@angular/common";
import { HttpClient, HttpClientModule } from "@angular/common/http";
import { NgModule } from "@angular/core";
import { FormsModule, ReactiveFormsModule } from "@angular/forms";
import { HttpModule } from "@angular/http";
import { MatButtonModule, MatCardModule, MatInputModule, MatTabsModule } from "@angular/material";
import { BrowserModule } from "@angular/platform-browser";
import { BrowserAnimationsModule } from "@angular/platform-browser/animations";
import { RouterModule, Routes } from "@angular/router";
import { JwtModule } from "@auth0/angular-jwt";
@ -15,7 +15,7 @@ import { TranslateLoader, TranslateModule } from "@ngx-translate/core";
import { TranslateHttpLoader } from "@ngx-translate/http-loader";
import { CookieService } from "ng2-cookies";
import { GrowlModule } from "primeng/components/growl/growl";
import { ButtonModule, CaptchaModule, ConfirmationService, ConfirmDialogModule, DataTableModule,DialogModule, SharedModule, SidebarModule, TooltipModule } from "primeng/primeng";
import { ButtonModule, CaptchaModule, ConfirmationService, ConfirmDialogModule, DataTableModule, DialogModule, SharedModule, SidebarModule, TooltipModule } from "primeng/primeng";
// Components
import { AppComponent } from "./app.component";
@ -36,7 +36,7 @@ import { ImageService } from "./services";
import { LandingPageService } from "./services";
import { NotificationService } from "./services";
import { SettingsService } from "./services";
import { IssuesService, JobService, StatusService } from "./services";
import { IssuesService, JobService, PlexTvService, StatusService } from "./services";
const routes: Routes = [
{ path: "*", component: PageNotFoundComponent },
@ -67,6 +67,14 @@ export function HttpLoaderFactory(http: HttpClient, platformLocation: PlatformLo
return new TranslateHttpLoader(http, "/translations/", `.json?v=${version}`);
}
export function JwtTokenGetter() {
const token = localStorage.getItem("id_token");
if (!token) {
return "";
}
return token;
}
@NgModule({
imports: [
RouterModule.forRoot(routes),
@ -89,18 +97,12 @@ export function HttpLoaderFactory(http: HttpClient, platformLocation: PlatformLo
CaptchaModule,
TooltipModule,
ConfirmDialogModule,
CommonModule,
CommonModule,
JwtModule.forRoot({
config: {
tokenGetter: () => {
const token = localStorage.getItem("id_token");
if (!token) {
return "";
}
return token;
},
tokenGetter: JwtTokenGetter,
},
}),
}),
TranslateModule.forRoot({
loader: {
provide: TranslateLoader,
@ -119,7 +121,7 @@ export function HttpLoaderFactory(http: HttpClient, platformLocation: PlatformLo
TokenResetPasswordComponent,
CookieComponent,
LoginOAuthComponent,
],
],
providers: [
NotificationService,
AuthService,
@ -133,6 +135,7 @@ export function HttpLoaderFactory(http: HttpClient, platformLocation: PlatformLo
CookieService,
JobService,
IssuesService,
PlexTvService,
],
bootstrap: [AppComponent],
})

@ -1,8 +1,11 @@
export interface IUserLogin {
import { IPlexPin } from "../interfaces";
export interface IUserLogin {
username: string;
password: string;
rememberMe: boolean;
usePlexOAuth: boolean;
plexTvPin: IPlexPin;
}
export interface ILocalUser {

@ -1,8 +1,8 @@
import { PlatformLocation } from "@angular/common";
import { PlatformLocation } from "@angular/common";
import { HttpClient } from "@angular/common/http";
import { Injectable } from "@angular/core";
import { JwtHelperService } from "@auth0/angular-jwt";
import { Observable } from "rxjs/Rx";
import { Observable } from "rxjs";
import { ServiceHelpers } from "../services";
import { ILocalUser, IUserLogin } from "./IUserLogin";
@ -26,13 +26,13 @@ export class AuthService extends ServiceHelpers {
return this.http.post<boolean>(`${this.url}/requirePassword`, JSON.stringify(login), {headers: this.headers});
}
public loggedIn() {
public loggedIn() {
const token: string = this.jwtHelperService.tokenGetter();
if (!token) {
return false;
}
const tokenExpired: boolean = this.jwtHelperService.isTokenExpired(token);
return !tokenExpired;
}
@ -53,9 +53,9 @@ export class AuthService extends ServiceHelpers {
} else {
u.roles.push(roles);
}
return <ILocalUser>u;
return <ILocalUser> u;
}
return <ILocalUser>{};
return <ILocalUser> { };
}
public hasRole(role: string): boolean {

@ -11,7 +11,7 @@ export class CookieComponent implements OnInit {
public ngOnInit() {
const cookie = this.cookieService.getAll();
if(cookie.Auth) {
if (cookie.Auth) {
const jwtVal = cookie.Auth;
localStorage.setItem("id_token", jwtVal);
this.router.navigate(["search"]);

@ -46,6 +46,7 @@ export interface IIssueComments {
}
export interface IIssuesChat {
id: number;
comment: string;
date: Date;
username: string;

@ -2,6 +2,16 @@
user: IPlexUser;
}
export interface IPlexPin {
id: number;
code: string;
}
export interface IPlexOAuthViewModel {
wizard: boolean;
pin: IPlexPin;
}
export interface IPlexOAuthAccessToken {
accessToken: string;
}
@ -24,6 +34,19 @@ export interface IPlexLibResponse {
data: IPlexLibraries;
}
export interface IPlexLibSimpleResponse {
successful: boolean;
message: string;
data: IPlexSection[];
}
export interface IPlexSection {
id: string;
key: string;
type: string;
title: string;
}
export interface IMediaContainer {
directory: IDirectory[];
}
@ -39,6 +62,28 @@ export interface IPlexServerViewModel {
servers: IPlexServerResult;
}
export interface IPlexServerAddViewModel {
success: boolean;
servers: IPlexServersAdd[];
}
export interface IPlexServersAdd {
serverId: number;
machineId: string;
serverName: string;
}
export interface IPlexUserViewModel {
username: string;
machineIdentifier: string;
libsSelected: number[];
}
export interface IPlexUserAddResponse {
success: boolean;
error: string;
}
export interface IPlexServerResult {
friendlyName: string;
machineIdentifier: string;

@ -20,7 +20,7 @@ export interface IRecentlyAddedTvShows extends IRecentlyAddedMovies {
export interface IRecentlyAddedRangeModel {
from: Date;
to: Date;
to: Date;
}
export enum RecentlyAddedType {

@ -71,6 +71,10 @@ export interface ITvRequests {
status: string;
childRequests: IChildRequests[];
qualityOverride: number;
background: any;
totalSeasons: number;
tvDbId: number;
open: boolean; // THIS IS FOR THE UI
// For UI display
qualityOverrideTitle: string;

@ -28,8 +28,16 @@ export interface ISearchTvResult {
available: boolean;
plexUrl: string;
embyUrl: string;
quality: string;
firstSeason: boolean;
latestSeason: boolean;
theTvDbId: string;
subscribed: boolean;
showSubscribe: boolean;
fullyAvailable: boolean;
partlyAvailable: boolean;
background: any;
open: boolean; // THIS IS FOR THE UI
}
export interface ITvRequestViewModel {

@ -27,6 +27,7 @@ export interface IUpdateSettings extends ISettings {
windowsService: boolean;
windowsServiceName: string;
isWindows: boolean;
testMode: boolean;
}
export interface IEmbySettings extends ISettings {

@ -23,6 +23,11 @@ export interface ICreateWizardUser {
usePlexAdminAccount: boolean;
}
export interface IWizardUserResult {
result: boolean;
errors: string[];
}
export enum UserType {
LocalUser = 1,
PlexUser = 2,

@ -51,15 +51,17 @@
<div *ngIf="comments" class="panel-body msg_container_base">
<div *ngIf="comments.length <= 0" class="row msg_container base_receive">
<div class="col-md-10 col-xs-10">
<div class="messages msg_sent">
<p [translate]="'Issues.NoComments'"></p>
<div class="messages msg_sent">
<p [translate]="'Issues.NoComments'"></p>
</div>
</div>
</div>
<div *ngFor="let comment of comments" class="row msg_container" [ngClass]="{'base_sent': comment.adminComment, 'base_receive': !comment.adminComment}">
<div class="col-md-10 col-xs-10">
<div class="messages msg_sent">
<div class="messages msg_sent"> <i *ngIf="isAdmin" style="float:right;" class="fa fa-times" aria-hidden="true" (click)="deleteComment(comment.id)"></i>
<p>{{comment.comment}}</p>
<time>{{comment.username}} • {{comment.date | date:'short'}}</time>
</div>

@ -37,10 +37,10 @@ export class IssueDetailsComponent implements OnInit {
private notificationService: NotificationService,
private imageService: ImageService,
private sanitizer: DomSanitizer,
private readonly platformLocation: PlatformLocation) {
private readonly platformLocation: PlatformLocation) {
this.route.params
.subscribe((params: any) => {
this.issueId = parseInt(params.id);
this.issueId = parseInt(params.id);
});
this.isAdmin = this.authService.hasRole("Admin") || this.authService.hasRole("PowerUser");
@ -53,8 +53,8 @@ export class IssueDetailsComponent implements OnInit {
this.defaultPoster = "../../../images/";
}
}
public ngOnInit() {
public ngOnInit() {
this.issueService.getIssue(this.issueId).subscribe(x => {
this.issue = {
comments: x.comments,
@ -63,8 +63,8 @@ export class IssueDetailsComponent implements OnInit {
issueCategoryId: x.issueCategoryId,
subject: x.subject,
description: x.description,
status:x.status,
resolvedDate:x.resolvedDate,
status: x.status,
resolvedDate: x.resolvedDate,
title: x.title,
requestType: x.requestType,
requestId: x.requestId,
@ -97,6 +97,13 @@ export class IssueDetailsComponent implements OnInit {
});
}
public deleteComment(id: number) {
this.issueService.deleteComment(id).subscribe(x => {
this.loadComments();
this.notificationService.success("Comment Deleted");
});
}
private loadComments() {
this.issueService.getComments(this.issueId).subscribe(x => this.comments = x);
}
@ -117,7 +124,7 @@ export class IssueDetailsComponent implements OnInit {
} else {
this.imageService.getTvBackground(Number(issue.providerId)).subscribe(x => {
if(x) {
if (x) {
this.backgroundPath = this.sanitizer.bypassSecurityTrustStyle
("url(" + x + ")");
}

@ -22,8 +22,8 @@ export class IssuesComponent implements OnInit {
constructor(private issueService: IssuesService) { }
public ngOnInit() {
this.getPending();
public ngOnInit() {
this.getPending();
this.getInProg();
this.getResolved();
this.issueService.getIssuesCount().subscribe(x => this.count = x);
@ -61,5 +61,4 @@ export class IssuesComponent implements OnInit {
this.resolvedIssues = x;
});
}
}

@ -11,7 +11,7 @@ export class IssuesTableComponent {
@Input() public issues: IIssues[];
@Input() public totalRecords: number;
@Output() public changePage = new EventEmitter<IPagenator>();
@Output() public changePage = new EventEmitter<IPagenator>();
public IssueStatus = IssueStatus;
@ -47,7 +47,7 @@ export class IssuesTableComponent {
//event.rows = Number of rows to display in new page
//event.page = Index of the new page
//event.pageCount = Total number of pages
this.changePage.emit(event);
}

@ -6,7 +6,7 @@ import { TranslateService } from "@ngx-translate/core";
import { PlatformLocation } from "@angular/common";
import { AuthService } from "../auth/auth.service";
import { IAuthenticationSettings, ICustomizationSettings } from "../interfaces";
import { NotificationService } from "../services";
import { NotificationService, PlexTvService } from "../services";
import { SettingsService } from "../services";
import { StatusService } from "../services";
@ -30,9 +30,10 @@ export class LoginComponent implements OnDestroy, OnInit {
public landingFlag: boolean;
public baseUrl: string;
public loginWithOmbi: boolean;
public pinTimer: any;
public get appName(): string {
if(this.customizationSettings.applicationName) {
if (this.customizationSettings.applicationName) {
return this.customizationSettings.applicationName;
} else {
return "Ombi";
@ -40,13 +41,14 @@ export class LoginComponent implements OnDestroy, OnInit {
}
private timer: any;
private clientId: string;
private errorBody: string;
private errorValidation: string;
constructor(private authService: AuthService, private router: Router, private notify: NotificationService, private status: StatusService,
private fb: FormBuilder, private settingsService: SettingsService, private images: ImageService, private sanitizer: DomSanitizer,
private route: ActivatedRoute, private location: PlatformLocation, private readonly translate: TranslateService) {
private route: ActivatedRoute, private location: PlatformLocation, private translate: TranslateService, private plexTv: PlexTvService) {
this.route.params
.subscribe((params: any) => {
this.landingFlag = params.landing;
@ -71,20 +73,21 @@ export class LoginComponent implements OnDestroy, OnInit {
}
});
if(authService.loggedIn()) {
if (authService.loggedIn()) {
this.router.navigate(["search"]);
}
}
public ngOnInit() {
this.settingsService.getAuthentication().subscribe(x => this.authenticationSettings = x);
this.settingsService.getClientId().subscribe(x => this.clientId = x);
this.settingsService.getCustomization().subscribe(x => this.customizationSettings = x);
this.images.getRandomBackground().subscribe(x => {
this.background = this.sanitizer.bypassSecurityTrustStyle("linear-gradient(-10deg, transparent 20%, rgba(0,0,0,0.7) 20.0%, rgba(0,0,0,0.7) 80.0%, transparent 80%),url(" + x.url + ")");
});
this.timer = setInterval(() => {
this.cycleBackground();
}, 10000);
}, 7000);
const base = this.location.getBaseHrefFromDOM();
if (base.length > 1) {
@ -101,9 +104,9 @@ export class LoginComponent implements OnDestroy, OnInit {
return;
}
const value = form.value;
const user = { password: value.password, username: value.username, rememberMe: value.rememberMe, usePlexOAuth: false };
const user = { password: value.password, username: value.username, rememberMe: value.rememberMe, usePlexOAuth: false, plexTvPin: { id: 0, code: "" } };
this.authService.requiresPassword(user).subscribe(x => {
if(x && this.authenticationSettings.allowNoPassword) {
if (x && this.authenticationSettings.allowNoPassword) {
// Looks like this user requires a password
this.authenticationSettings.allowNoPassword = false;
return;
@ -113,6 +116,7 @@ export class LoginComponent implements OnDestroy, OnInit {
localStorage.setItem("id_token", x.access_token);
if (this.authService.loggedIn()) {
this.ngOnDestroy();
this.router.navigate(["search"]);
} else {
this.notify.error(this.errorBody);
@ -123,28 +127,58 @@ export class LoginComponent implements OnDestroy, OnInit {
}
public oauth() {
this.authService.login({usePlexOAuth: true, password:"",rememberMe:true,username:""}).subscribe(x => {
if (window.frameElement) {
// in frame
window.open(x.url, "_blank");
} else {
// not in frame
window.location.href = x.url;
}
this.plexTv.GetPin(this.clientId, this.appName).subscribe((pin: any) => {
this.authService.login({ usePlexOAuth: true, password: "", rememberMe: true, username: "", plexTvPin: pin }).subscribe(x => {
window.open(x.url, "_blank", `toolbar=0,
location=0,
status=0,
menubar=0,
scrollbars=1,
resizable=1,
width=500,
height=500`);
this.pinTimer = setInterval(() => {
this.notify.info("Authenticating", "Loading... Please Wait");
this.getPinResult(x.pinId);
}, 10000);
});
});
}
public getPinResult(pinId: number) {
this.authService.oAuth(pinId).subscribe(x => {
if(x.access_token) {
localStorage.setItem("id_token", x.access_token);
if (this.authService.loggedIn()) {
this.ngOnDestroy();
this.router.navigate(["search"]);
return;
}
}
}, err => {
this.notify.error(err.statusText);
this.router.navigate(["login"]);
});
}
public ngOnDestroy() {
clearInterval(this.timer);
clearInterval(this.pinTimer);
}
private cycleBackground() {
this.images.getRandomBackground().subscribe(x => {
this.background = "";
});
this.images.getRandomBackground().subscribe(x => {
this.background = this.sanitizer
.bypassSecurityTrustStyle("linear-gradient(-10deg, transparent 20%, rgba(0,0,0,0.7) 20.0%, rgba(0,0,0,0.7) 80.0%, transparent 80%), url(" + x.url + ")");
});
this.images.getRandomBackground().subscribe(x => {
this.background = "";
});
this.images.getRandomBackground().subscribe(x => {
this.background = this.sanitizer
.bypassSecurityTrustStyle("linear-gradient(-10deg, transparent 20%, rgba(0,0,0,0.7) 20.0%, rgba(0,0,0,0.7) 80.0%, transparent 80%), url(" + x.url + ")");
});
}
}

@ -16,7 +16,6 @@ export class LoginOAuthComponent implements OnInit {
this.route.params
.subscribe((params: any) => {
this.pin = params.pin;
});
}
@ -26,21 +25,20 @@ export class LoginOAuthComponent implements OnInit {
public auth() {
this.authService.oAuth(this.pin).subscribe(x => {
if(x.access_token) {
if (x.access_token) {
localStorage.setItem("id_token", x.access_token);
if (this.authService.loggedIn()) {
this.router.navigate(["search"]);
return;
}
}
}
if(x.errorMessage) {
if (x.errorMessage) {
this.error = x.errorMessage;
}
}, err => {
this.notify.error(err.statusText);
this.router.navigate(["login"]);
});
}

@ -4,7 +4,7 @@ import { FormBuilder, FormGroup, Validators } from "@angular/forms";
import { DomSanitizer } from "@angular/platform-browser";
import { ICustomizationSettings } from "../interfaces";
import { IdentityService, ImageService,NotificationService, SettingsService } from "../services";
import { IdentityService, ImageService, NotificationService, SettingsService } from "../services";
@Component({
templateUrl: "./resetpassword.component.html",

@ -1,5 +1,5 @@
import { Component, OnInit } from "@angular/core";
import { NguCarousel } from "@ngu/carousel";
import { NguCarouselConfig } from "@ngu/carousel";
import { ImageService, RecentlyAddedService } from "../services";
import { IRecentlyAddedMovies, IRecentlyAddedTvShows } from "./../interfaces";
@ -41,13 +41,13 @@ export class RecentlyAddedComponent implements OnInit {
public range: Date[];
public groupTv: boolean = false;
// https://github.com/sheikalthaf/ngu-carousel
public carouselTile: NguCarousel;
public carouselTile: NguCarouselConfig;
constructor(private recentlyAddedService: RecentlyAddedService,
private imageService: ImageService) {}
public ngOnInit() {
this.getMovies();
this.getShows();
@ -67,10 +67,10 @@ export class RecentlyAddedComponent implements OnInit {
}
public close() {
if(this.range.length < 2) {
if (this.range.length < 2) {
return;
}
if(!this.range[1]) {
if (!this.range[1]) {
// If we do not have a second date then just set it to now
this.range[1] = new Date();
}
@ -82,13 +82,13 @@ export class RecentlyAddedComponent implements OnInit {
}
private getShows() {
if(this.groupTv) {
if (this.groupTv) {
this.recentlyAddedService.getRecentlyAddedTvGrouped().subscribe(x => {
this.tv = x;
this.tv.forEach((t) => {
this.imageService.getTvPoster(t.tvDbId).subscribe(p => {
if(p) {
if (p) {
t.posterPath = p;
}
});
@ -97,10 +97,10 @@ export class RecentlyAddedComponent implements OnInit {
} else {
this.recentlyAddedService.getRecentlyAddedTv().subscribe(x => {
this.tv = x;
this.tv.forEach((t) => {
this.imageService.getTvPoster(t.tvDbId).subscribe(p => {
if(p) {
if (p) {
t.posterPath = p;
}
});
@ -114,11 +114,11 @@ export class RecentlyAddedComponent implements OnInit {
this.movies = x;
this.movies.forEach((movie) => {
if(movie.theMovieDbId) {
if (movie.theMovieDbId) {
this.imageService.getMoviePoster(movie.theMovieDbId).subscribe(p => {
movie.posterPath = p;
});
} else if(movie.imdbId) {
} else if (movie.imdbId) {
this.imageService.getMoviePoster(movie.imdbId).subscribe(p => {
movie.posterPath = p;
});

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save