dev to master (#5210)

Co-authored-by: tidusjar <tidusjar@gmail.com>
Co-authored-by: sephrat <34862846+sephrat@users.noreply.github.com>
Co-authored-by: Conventional Changelog Action <conventional.changelog.action@github.com>
Co-authored-by: Teifun2 <Teifun2@users.noreply.github.com>
Co-authored-by: contrib-readme-bot <contrib-readme-action@noreply.com>
Co-authored-by: dr3amer <91037083+dr3am37@users.noreply.github.com>
Co-authored-by: echel0n <echel0n@sickrage.ca>
Co-authored-by: Marley <55280588+marleypowell@users.noreply.github.com>
Co-authored-by: Igor Borges <igor@borges.dev>
Co-authored-by: Lucane <Lucane@users.noreply.github.com>
Co-authored-by: mkgeeky <github@mkgeeky.xyz>
Co-authored-by: Miguel A Vico Moya <mvicomoya@gmail.com>
Co-authored-by: Hadrien <26697460+ketsapiwiq@users.noreply.github.com>
Co-authored-by: Victor Usoltsev <bernarden@users.noreply.github.com>
Co-authored-by: Wesley King <kingwe92@gmail.com>
Co-authored-by: Lea <me@janderedev.xyz>
Co-authored-by: snyk-bot <snyk-bot@snyk.io>
Co-authored-by: snyk-bot <snyk-bot@snyk.io> [skip ci]
Co-authored-by: Jamie <tidusjar@gmail.com>
Co-authored-by: Drew <drewm727@hotmail.com> [skip ci]
Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com>
Co-authored-by: ryan-c44 <54028283+ryan-c44@users.noreply.github.com>
Co-authored-by: Alexander Russell <ajex94@gmail.com>
Co-authored-by: Grygon <647846+Grygon@users.noreply.github.com>
Co-authored-by: phildups7 <60622768+phildups7@users.noreply.github.com>
Co-authored-by: Avi <357984+Unimatrix0@users.noreply.github.com>
Co-authored-by: Antonin <82907030+Antonin-Bruzard@users.noreply.github.com>
Co-authored-by: Drew <mcminn.drew@gmail.com>
Co-authored-by: Joel Samson <joel.samson@hotmail.com>
Co-authored-by: David Benson <dben@users.noreply.github.com>
Co-authored-by: Alexandre Picavet <apicavet@hotmail.fr>
fix(#4847): Invalid Discord request fixed, also fixed an issue where App Only users would not show as logged in on the user management page (#4848)
fix(discover): 🐛 Fixed the default poster not taking into account the base url in some scenarios #4845
fix(wizard): 🐛 Stop access to the wizard when you have already setup ombi (#4866)
fix(plex-oauth): 🐛 Fixed an issue where using OAuth you could log in as a Ombi Local user #4835
fix: Some minor tweaks to the movie info panel (#4883)
fix(sonarr): 🐛 Stop the sonarr version endpoint from breaking when Sonarr is down #4895
fix: Support duplicates in Emby/JF collections (#4902)
fix(discover): Fix denied requests displayed as approved (#4901)
fix: Fix denied movie shown as 'processing request' in details view (#4900)
fix(#4906): 🐛 Fixed an issue with power users and permissions
fix: Cron Validation (#4842)
fix(lidarr): Change monitor to Existing to properly add artist #3597
fix(API): Allow RequestOnBehalf rights if requested from the API (#4919)
Fixes #4610
fix: added media type tag to media type text (#4638)
fix(sickrage): Fixed issue with incorrect handling of SiCKRAGE episode results returned during episode status changes, now expects array of objects from data path if present (#4648)
fix: Missing Poster broken link fix (#4637)
fix: Improve Swagger documentation (#4652)
fix(API): Fix pagination in some edge cases (#4649)
fix(discover): Carousel touch not working when scrolling page and recommendations and similar movie navigation (#4633)
fix: 🐛 Fixed the Request on Behalf of having blanks (#4667)
fix(images): Retry images with a backoff when we get a Too Many requests from TheMovieDb #4685
fix: Landing and Login page improvements (#4690)
fix(discover): 🐛 Created new Image component to handle 429's from TMDB (#4698) and fixed #4635 (#4699)
fix: Override Sonarr V3 Profiles endpoint (#4678)
fix(4K) :4K request fixes (#4702)
fix(translations): 🌐 New translations from Crowdin [skip ci] (#4713)
fix: fixed trakt image not loading when base url present (#4711)
fix: 🐛 Fixed missing externals (#4712)
fix: Log Microsoft warnings to log file (#4723)
fix: Localize recently requested on discover page (#4729)
Fix: Ombi.Api.Lidarr: Remove unused fields from ArtistAdd (#4727)
fix: Fix conflicting property name for Swagger (#4733)
fix: fixed stats controller (#4742)
fix(webhook): Remove added trailing slash from webhook URL #4710
fix(sonarr): 🐛 Cleaned up and removed Sonarr v3 option, sonarr v3 is now the default. This allows us to get ready for the upcoming Sonarr v4 (#4764)
fix: Fixes default image for recently requested items. (#4767)
Fixes build warnings. (#4769)
fix: Reworked the version check (#4719) (#4781)
fix: Unable to Delete Jellyfin Server (#4705) (#4780)
fix: Partially Available prevents further TV requests (#4768) (#4779)
fix: Consistently reset loading flag when requesting movies on discover page. (#4777)
fix(sonarr): 🐛 Fixed an issue where the language list didn't correctly load for power users in the advanced options #4782
fix: Only log error messages from Microsoft (#4787)
fix(notifications): Fixed the Partially TV notifications going to the admin #4797 (#4799)
fix(translations): 🌐 New translations from Crowdin [skip ci] (#4801)
fix(sonarr): 🐛 Sonarr V4 should work now (#4810)
fixed (#4833)
fix(plex-watchlist): Lookup the ID from different sources when Plex doesn't contain the metadata (#4843)
fix(emby): Fix Emby played sync running a full sync during recently added sync (#4932)
Fixes #4947
fix: Fix various styling issues (#4935)
fix(translations): 🌐 New translations from Crowdin [skip ci] (#4926)
fix: upgrade @microsoft/signalr from 6.0.11 to 6.0.16 (#4964) [skip ci]
fix: upgrade primeng from 15.0.0-rc.1 to 15.4.1 (#4962) [skip ci]
fix: src/Ombi.Notifications/Ombi.Notifications.csproj to reduce vulnerabilities (#4969) [skip ci]
fix: upgrade @fortawesome/fontawesome-free from 6.1.2 to 6.4.0 (#4965) [skip ci]
fix: upgrade multiple dependencies with Snyk (#4963) [skip ci]
fix flaky tests (#4970) [skip ci]
fix: More automation tests mainly around the Plex Settings page (#4821)
fix: upgrade cypress-real-events from 1.7.4 to 1.8.1 (#4968) [skip ci]
fix: Remove Angular TSLint (#4973)
fix: upgrade zone.js from 0.11.8 to 0.13.0 (#4975)
fix: upgrade jquery from 3.6.1 to 3.7.0 (#4974)
fix: upgrade multiple dependencies with Snyk (#4961)
fixes (#4978)
fix(user-importer): 🐛 Fixed an issue where the cleanup wouldn't delete users #4812
fix(user-importer): Do not delete the Plex Admin as part of the user Importer cleanup #4870 (#4981)
Fixes #4957
fix: Remove old trending source (#4987)
fix(plex-api): Switch over to the new API to avoid deprecation & save… (#4986)
fix: switch back to the old plex friends API #4989
fix(user-importer): Fixed not importing all correct users #4989
fix: upgrade cypress-real-events from 1.8.1 to 1.9.1 (#5000) [skip ci]
fix: upgrade @microsoft/signalr from 6.0.18 to 6.0.20 (#4999) [skip ci]
fix: upgrade @fortawesome/fontawesome-free from 6.4.0 to 6.4.2 (#5005) [skip ci]
fix: upgrade @types/jquery from 3.5.16 to 3.5.17 (#5011) [skip ci]
fix: src/Ombi/ClientApp/package.json & src/Ombi/ClientApp/yarn.lock to reduce vulnerabilities (#5010) [skip ci]
fix: upgrade @microsoft/signalr from 6.0.21 to 6.0.22 (#5020)
fix: upgrade @types/jquery from 3.5.18 to 3.5.19 (#5022)
fix: upgrade zone.js from 0.13.1 to 0.13.2 (#5019)
fix: upgrade multiple dependencies with Snyk (#5030)
fix: upgrade cypress-real-events from 1.10.0 to 1.10.1 (#5014)
fix: upgrade jquery from 3.7.0 to 3.7.1 (#5015)
Fix: Linkify logo in newsletter (#5036)
fix: upgrade @types/jquery from 3.5.22 to 3.5.23 (#5034) [skip ci]
fix: upgrade @microsoft/signalr from 6.0.22 to 6.0.23 (#5032) [skip ci]
Fix(mutliple servers): attempting to cache the Plex Content in server (#5018)
fix(radarr-4k): 🐛 Fixed an issue when using Radarr 4k with user set quality profiles. There are now user quality profiles for 4k profile #5025
fix: src/Ombi/ClientApp/package.json & src/Ombi/ClientApp/yarn.lock to reduce vulnerabilities (#5072) [skip ci]
fix: src/Ombi/Ombi.csproj to reduce vulnerabilities (#5066) [skip ci]
fix: upgrade @fortawesome/fontawesome-free from 6.4.2 to 6.5.0 (#5053)  [skip ci]
fix: upgrade @types/jquery from 3.5.27 to 3.5.28 (#5049)  [skip ci]
fix: src/Ombi/ClientApp/package.json & src/Ombi/ClientApp/yarn.lock to reduce vulnerabilities (#5040)  [skip ci]
fix: upgrade moment from 2.29.4 to 2.30.1 (#5075) [skip ci]
fix: upgrade multiple dependencies with Snyk (#5073) [skip ci]
fix: upgrade multiple dependencies with Snyk (#5104) [skip ci]
fix: src/Ombi.Store/Ombi.Store.csproj to reduce vulnerabilities (#5160)
fix: src/Ombi.Notifications/Ombi.Notifications.csproj to reduce vulnerabilities (#5167)
fix(radarr-settings): this.normalForm is undefined (#5207)
Fixes #4994
This commit is contained in:
Jamie Rees 2025-01-03 16:29:20 +00:00 committed by GitHub
commit 43631a495a
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
70 changed files with 8051 additions and 2807 deletions

View file

@ -27,7 +27,7 @@ jobs:
run: yarn --cwd ./src/Ombi/ClientApp run build run: yarn --cwd ./src/Ombi/ClientApp run build
- name: Publish UI Artifacts - name: Publish UI Artifacts
uses: actions/upload-artifact@v2 uses: actions/upload-artifact@v4
with: with:
name: angular_dist name: angular_dist
path: | path: |
@ -130,7 +130,7 @@ jobs:
working-directory: src/Ombi working-directory: src/Ombi
- name: Download Angular - name: Download Angular
uses: actions/download-artifact@v2 uses: actions/download-artifact@v4
with: with:
name: angular_dist name: angular_dist
path: ~/src/Ombi/dist path: ~/src/Ombi/dist
@ -156,7 +156,7 @@ jobs:
directory: 'src/Ombi/${{ matrix.os }}' directory: 'src/Ombi/${{ matrix.os }}'
- name: Publish Release - name: Publish Release
uses: actions/upload-artifact@v2 uses: actions/upload-artifact@v4
with: with:
name: ${{ matrix.os }} name: ${{ matrix.os }}
path: | path: |
@ -170,7 +170,7 @@ jobs:
- name: Download Artifacts - name: Download Artifacts
id: download id: download
uses: actions/download-artifact@v2 uses: actions/download-artifact@v4
with: with:
path: artifacts path: artifacts

File diff suppressed because it is too large Load diff

View file

@ -301,8 +301,8 @@ Here are some of the features Ombi has:
</a> </a>
</td> </td>
<td align="center"> <td align="center">
<a href="https://github.com/deepwather"> <a href="https://github.com/tuxmi">
<img src="https://avatars.githubusercontent.com/u/12274612?v=4" width="50;" alt="deepwather"/> <img src="https://avatars.githubusercontent.com/u/12274612?v=4" width="50;" alt="tuxmi"/>
<br /> <br />
<sub><b>Michael Reber</b></sub> <sub><b>Michael Reber</b></sub>
</a> </a>

View file

@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<project version="4"> <project version="4">
<component name="ContentModelUserStore"> <component name="UserContentModel">
<attachedFolders /> <attachedFolders />
<explicitIncludes /> <explicitIncludes />
<explicitExcludes /> <explicitExcludes />

View file

@ -1,25 +1,22 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<project version="4"> <project version="4">
<component name="AutoImportSettings">
<option name="autoReloadType" value="SELECTIVE" />
</component>
<component name="ChangeListManager"> <component name="ChangeListManager">
<list default="true" id="57001998-efde-494a-80b3-d7acfc91f770" name="Default Changelist" comment=""> <list default="true" id="57001998-efde-494a-80b3-d7acfc91f770" name="Default Changelist" comment="">
<change afterPath="$PROJECT_DIR$/Ombi.Core/Engine/Interfaces/IMusicSearchEngineV2.cs" afterDir="false" />
<change afterPath="$PROJECT_DIR$/Ombi.Core/Engine/V2/MusicSearchEngineV2.cs" afterDir="false" />
<change afterPath="$PROJECT_DIR$/Ombi.Core/Models/Search/V2/Music/ArtistInformation.cs" afterDir="false" />
<change beforePath="$PROJECT_DIR$/.idea/.idea.Ombi/.idea/contentModel.xml" beforeDir="false" afterPath="$PROJECT_DIR$/.idea/.idea.Ombi/.idea/contentModel.xml" afterDir="false" />
<change beforePath="$PROJECT_DIR$/.idea/.idea.Ombi/.idea/workspace.xml" beforeDir="false" afterPath="$PROJECT_DIR$/.idea/.idea.Ombi/.idea/workspace.xml" afterDir="false" /> <change beforePath="$PROJECT_DIR$/.idea/.idea.Ombi/.idea/workspace.xml" beforeDir="false" afterPath="$PROJECT_DIR$/.idea/.idea.Ombi/.idea/workspace.xml" afterDir="false" />
<change beforePath="$PROJECT_DIR$/.idea/config/applicationhost.config" beforeDir="false" afterPath="$PROJECT_DIR$/.idea/config/applicationhost.config" afterDir="false" /> <change beforePath="$PROJECT_DIR$/Ombi/ClientApp/src/app/wizard/welcome/welcome.component.html" beforeDir="false" afterPath="$PROJECT_DIR$/Ombi/ClientApp/src/app/wizard/welcome/welcome.component.html" afterDir="false" />
<change beforePath="$PROJECT_DIR$/Ombi.Api.MusicBrainz/Models/Artist/ArtistInformation.cs" beforeDir="false" afterPath="$PROJECT_DIR$/Ombi.Api.MusicBrainz/Models/Artist/ArtistInformation.cs" afterDir="false" /> <change beforePath="$PROJECT_DIR$/Ombi/Controllers/V2/WizardController.cs" beforeDir="false" afterPath="$PROJECT_DIR$/Ombi/Controllers/V2/WizardController.cs" afterDir="false" />
<change beforePath="$PROJECT_DIR$/Ombi.DependencyInjection/IocExtensions.cs" beforeDir="false" afterPath="$PROJECT_DIR$/Ombi.DependencyInjection/IocExtensions.cs" afterDir="false" />
<change beforePath="$PROJECT_DIR$/Ombi/ClientApp/src/app/media-details/components/artist/artist-details.component.ts" beforeDir="false" afterPath="$PROJECT_DIR$/Ombi/ClientApp/src/app/media-details/components/artist/artist-details.component.ts" afterDir="false" />
<change beforePath="$PROJECT_DIR$/Ombi/ClientApp/src/app/services/searchV2.service.ts" beforeDir="false" afterPath="$PROJECT_DIR$/Ombi/ClientApp/src/app/services/searchV2.service.ts" afterDir="false" />
<change beforePath="$PROJECT_DIR$/Ombi/Controllers/V2/SearchController.cs" beforeDir="false" afterPath="$PROJECT_DIR$/Ombi/Controllers/V2/SearchController.cs" afterDir="false" />
</list> </list>
<option name="EXCLUDED_CONVERTED_TO_IGNORED" value="true" />
<option name="SHOW_DIALOG" value="false" /> <option name="SHOW_DIALOG" value="false" />
<option name="HIGHLIGHT_CONFLICTS" value="true" /> <option name="HIGHLIGHT_CONFLICTS" value="true" />
<option name="HIGHLIGHT_NON_ACTIVE_CHANGELIST" value="false" /> <option name="HIGHLIGHT_NON_ACTIVE_CHANGELIST" value="false" />
<option name="LAST_RESOLUTION" value="IGNORE" /> <option name="LAST_RESOLUTION" value="IGNORE" />
</component> </component>
<component name="DpaMonitoringSettings">
<option name="firstShow" value="false" />
</component>
<component name="FileEditorManager"> <component name="FileEditorManager">
<leaf> <leaf>
<file pinned="false" current-in-tab="false"> <file pinned="false" current-in-tab="false">
@ -237,27 +234,63 @@
<component name="Git.Settings"> <component name="Git.Settings">
<option name="RECENT_GIT_ROOT_PATH" value="$PROJECT_DIR$/.." /> <option name="RECENT_GIT_ROOT_PATH" value="$PROJECT_DIR$/.." />
</component> </component>
<component name="GitToolBoxStore">
<option name="recentBranches">
<RecentBranches>
<option name="branchesForRepo">
<list>
<RecentBranchesForRepo>
<option name="branches">
<list>
<RecentBranch>
<option name="branchName" value="wizard-database" />
<option name="lastUsedInstant" value="1735917525" />
</RecentBranch>
<RecentBranch>
<option name="branchName" value="develop" />
<option name="lastUsedInstant" value="1735917524" />
</RecentBranch>
</list>
</option>
<option name="repositoryRootUrl" value="file://$PROJECT_DIR$/.." />
</RecentBranchesForRepo>
</list>
</option>
</RecentBranches>
</option>
</component>
<component name="GithubProjectSettings">
<option name="branchProtectionPatterns">
<list>
<option value="master" />
<option value="develop" />
</list>
</option>
</component>
<component name="HighlightingSettingsPerFile"> <component name="HighlightingSettingsPerFile">
<setting file="file://$PROJECT_DIR$/Ombi.Helpers.Tests/EmbyHelperTests.cs" root0="FORCE_HIGHLIGHTING" /> <setting file="file://$APPLICATION_CONFIG_DIR$/resharper-host/DecompilerCache/decompiler/990126b794024fe2bd16aebdd37eba1d7b600/93/25662f04/ServerVersion.cs" root0="FORCE_HIGHLIGHTING" />
<setting file="file://$PROJECT_DIR$/Ombi.Api.MusicBrainz/MusicBrainzApi.cs" root0="FORCE_HIGHLIGHTING" /> <setting file="file://$APPLICATION_CONFIG_DIR$/resharper-host/SourcesCache/3bd4df5aff92cabbc4d630be64227073db1b8539b3a1e47786b4b189d7cdb7/DbContext.cs" root0="FORCE_HIGHLIGHTING" />
<setting file="file://$PROJECT_DIR$/Ombi.Schedule.Tests/OmbiQuartzTests.cs" root0="FORCE_HIGHLIGHTING" /> <setting file="file://$APPLICATION_CONFIG_DIR$/resharper-host/SourcesCache/449b441523c469ed34ff5a5e14f0bafcd8f097aa463655303dc19048fa44ac3/EntityFrameworkServiceCollectionExtensions.cs" root0="FORCE_HIGHLIGHTING" />
<setting file="file://$PROJECT_DIR$/Ombi.Api.MusicBrainz/Models/Artist/ArtistInformation.cs" root0="FORCE_HIGHLIGHTING" /> <setting file="file://$APPLICATION_CONFIG_DIR$/resharper-host/SourcesCache/7d81b2d4f22bee75e5438c707251ae43cb0974c207db91ffc159118c84b4eb9/ServiceProvider.cs" root0="FORCE_HIGHLIGHTING" />
<setting file="file://$PROJECT_DIR$/Ombi/Controllers/V2/SearchController.cs" root0="FORCE_HIGHLIGHTING" /> <setting file="file://$APPLICATION_CONFIG_DIR$/resharper-host/SourcesCache/a424e6912048b4cd25715f158e789aae24db5c2911d9e622d39bc6ac3246c6/MySqlConnectionStringBuilder.cs" root0="SKIP_HIGHLIGHTING" />
<setting file="file://$PROJECT_DIR$/Ombi.DependencyInjection/IocExtensions.cs" root0="FORCE_HIGHLIGHTING" /> <setting file="file://$APPLICATION_CONFIG_DIR$/resharper-host/SourcesCache/bd1d5c50194fea68ff3559c160230b0ab50f5acf4ce3061bffd6d62958e2182/ExceptionDispatchInfo.cs" root0="FORCE_HIGHLIGHTING" />
<setting file="file://$PROJECT_DIR$/Ombi.Core/Engine/V2/MultiSearchEngine.cs" root0="FORCE_HIGHLIGHTING" /> <setting file="file://$APPLICATION_CONFIG_DIR$/resharper-host/SourcesCache/e9881a453a581134c1a18331ac1f8f1201a5382a685bf2a40777fa22619/DbContextOptions`.cs" root0="FORCE_HIGHLIGHTING" />
<setting file="file://$PROJECT_DIR$/Ombi.Core/Engine/V2/IMultiSearchEngine.cs" root0="FORCE_HIGHLIGHTING" />
<setting file="file://$PROJECT_DIR$/Ombi.Api.MusicBrainz/IMusicBrainzApi.cs" root0="FORCE_HIGHLIGHTING" /> <setting file="file://$PROJECT_DIR$/Ombi.Api.MusicBrainz/IMusicBrainzApi.cs" root0="FORCE_HIGHLIGHTING" />
<setting file="file://$PROJECT_DIR$/Ombi.Api.MusicBrainz/MusicBrainzApi.cs" root0="FORCE_HIGHLIGHTING" />
<setting file="file://$PROJECT_DIR$/Ombi.Core/Engine/Interfaces/IMusicSearchEngineV2.cs" root0="FORCE_HIGHLIGHTING" /> <setting file="file://$PROJECT_DIR$/Ombi.Core/Engine/Interfaces/IMusicSearchEngineV2.cs" root0="FORCE_HIGHLIGHTING" />
<setting file="file://$PROJECT_DIR$/Ombi.Core/Models/Search/V2/Music/ArtistInformation.cs" root0="FORCE_HIGHLIGHTING" />
<setting file="mock:///Dummy.cs" root0="FORCE_HIGHLIGHTING" />
<setting file="file://$PROJECT_DIR$/Ombi.Core/Engine/V2/MusicSearchEngineV2.cs" root0="FORCE_HIGHLIGHTING" />
<setting file="file://$PROJECT_DIR$/Ombi.Core/Engine/RecentlyAddedEngine.cs" root0="FORCE_HIGHLIGHTING" />
<setting file="mock:///Dummy.cs" root0="FORCE_HIGHLIGHTING" />
<setting file="file://$PROJECT_DIR$/Ombi/Controllers/V1/TokenController.cs" root0="FORCE_HIGHLIGHTING" />
<setting file="file://$PROJECT_DIR$/Ombi.Core/Engine/MusicSearchEngine.cs" root0="FORCE_HIGHLIGHTING" /> <setting file="file://$PROJECT_DIR$/Ombi.Core/Engine/MusicSearchEngine.cs" root0="FORCE_HIGHLIGHTING" />
<setting file="file://$PROJECT_DIR$/Ombi/Program.cs" root0="FORCE_HIGHLIGHTING" /> <setting file="file://$PROJECT_DIR$/Ombi.Core/Engine/RecentlyAddedEngine.cs" root0="FORCE_HIGHLIGHTING" />
<setting file="file://$PROJECT_DIR$/Ombi.Core/Engine/UserStatsEngine.cs" root0="FORCE_HIGHLIGHTING" /> <setting file="file://$PROJECT_DIR$/Ombi.Core/Engine/UserStatsEngine.cs" root0="FORCE_HIGHLIGHTING" />
<setting file="mock:///Dummy.cs" root0="FORCE_HIGHLIGHTING" /> <setting file="file://$PROJECT_DIR$/Ombi.Core/Engine/V2/IMultiSearchEngine.cs" root0="FORCE_HIGHLIGHTING" />
<setting file="file://$PROJECT_DIR$/Ombi.Core/Engine/V2/MultiSearchEngine.cs" root0="FORCE_HIGHLIGHTING" />
<setting file="file://$PROJECT_DIR$/Ombi.Core/Engine/V2/MusicSearchEngineV2.cs" root0="FORCE_HIGHLIGHTING" />
<setting file="file://$PROJECT_DIR$/Ombi.Core/Models/Search/V2/Music/ArtistInformation.cs" root0="FORCE_HIGHLIGHTING" />
<setting file="file://$PROJECT_DIR$/Ombi.DependencyInjection/IocExtensions.cs" root0="FORCE_HIGHLIGHTING" />
<setting file="file://$PROJECT_DIR$/Ombi.Helpers.Tests/EmbyHelperTests.cs" root0="FORCE_HIGHLIGHTING" />
<setting file="file://$PROJECT_DIR$/Ombi.Schedule.Tests/OmbiQuartzTests.cs" root0="FORCE_HIGHLIGHTING" />
<setting file="file://$PROJECT_DIR$/Ombi/Controllers/V1/TokenController.cs" root0="FORCE_HIGHLIGHTING" />
<setting file="file://$PROJECT_DIR$/Ombi/Controllers/V2/SearchController.cs" root0="FORCE_HIGHLIGHTING" />
<setting file="file://$PROJECT_DIR$/Ombi/Program.cs" root0="FORCE_HIGHLIGHTING" />
</component> </component>
<component name="IdeDocumentHistory"> <component name="IdeDocumentHistory">
<option name="CHANGED_PATHS"> <option name="CHANGED_PATHS">
@ -343,27 +376,14 @@
<pane id="FileSystemExplorer" /> <pane id="FileSystemExplorer" />
</panes> </panes>
</component> </component>
<component name="PropertiesComponent"> <component name="PropertiesComponent"><![CDATA[{
<property name="ASKED_SHARE_PROJECT_CONFIGURATION_FILES" value="true" /> "keyToString": {
<property name="Rider.DefaultBreakpoints.AreToggled" value="true" /> ".NET Launch Settings Profile.Ombi.executor": "Run",
<property name="Rider.ProjectViewActivator.IsNotFirstRun" value="true" /> "git-widget-placeholder": "#5208 on wizard-database",
<property name="SHARE_PROJECT_CONFIGURATION_FILES" value="true" /> "node.js.selected.package.tslint": "(autodetect)"
<property name="WebServerToolWindowFactoryState" value="false" /> }
<property name="nodejs_package_manager_path" value="npm" /> }]]></component>
</component> <component name="RunManager" selected=".NET Launch Settings Profile.Ombi">
<component name="RunDashboard">
<option name="ruleStates">
<list>
<RuleState>
<option name="name" value="ConfigurationTypeDashboardGroupingRule" />
</RuleState>
<RuleState>
<option name="name" value="StatusDashboardGroupingRule" />
</RuleState>
</list>
</option>
</component>
<component name="RunManager" selected=".NET Launch Settings Profile.Ombi: IIS Express">
<configuration name="Ombi" type="LaunchSettings" factoryName=".NET Launch Settings Profile"> <configuration name="Ombi" type="LaunchSettings" factoryName=".NET Launch Settings Profile">
<option name="LAUNCH_PROFILE_PROJECT_FILE_PATH" value="$PROJECT_DIR$/Ombi/Ombi.csproj" /> <option name="LAUNCH_PROFILE_PROJECT_FILE_PATH" value="$PROJECT_DIR$/Ombi/Ombi.csproj" />
<option name="LAUNCH_PROFILE_TFM" value=".NETCoreApp,Version=v2.2" /> <option name="LAUNCH_PROFILE_TFM" value=".NETCoreApp,Version=v2.2" />
@ -376,7 +396,7 @@
<option name="SEND_DEBUG_REQUEST" value="1" /> <option name="SEND_DEBUG_REQUEST" value="1" />
<option name="ADDITIONAL_IIS_EXPRESS_ARGUMENTS" value="" /> <option name="ADDITIONAL_IIS_EXPRESS_ARGUMENTS" value="" />
<method v="2"> <method v="2">
<option name="Build" enabled="true" /> <option name="Build" />
</method> </method>
</configuration> </configuration>
<configuration name="Ombi: IIS Express" type="LaunchSettings" factoryName=".NET Launch Settings Profile"> <configuration name="Ombi: IIS Express" type="LaunchSettings" factoryName=".NET Launch Settings Profile">
@ -391,7 +411,7 @@
<option name="SEND_DEBUG_REQUEST" value="1" /> <option name="SEND_DEBUG_REQUEST" value="1" />
<option name="ADDITIONAL_IIS_EXPRESS_ARGUMENTS" value="" /> <option name="ADDITIONAL_IIS_EXPRESS_ARGUMENTS" value="" />
<method v="2"> <method v="2">
<option name="Build" enabled="true" /> <option name="Build" />
</method> </method>
</configuration> </configuration>
<configuration name="Ombi.Schedule.Tests" type="LaunchSettings" factoryName=".NET Launch Settings Profile"> <configuration name="Ombi.Schedule.Tests" type="LaunchSettings" factoryName=".NET Launch Settings Profile">
@ -406,7 +426,7 @@
<option name="SEND_DEBUG_REQUEST" value="1" /> <option name="SEND_DEBUG_REQUEST" value="1" />
<option name="ADDITIONAL_IIS_EXPRESS_ARGUMENTS" value="" /> <option name="ADDITIONAL_IIS_EXPRESS_ARGUMENTS" value="" />
<method v="2"> <method v="2">
<option name="Build" enabled="true" /> <option name="Build" />
</method> </method>
</configuration> </configuration>
<configuration name="Ombi.Schedule.Tests: IIS Express" type="LaunchSettings" factoryName=".NET Launch Settings Profile"> <configuration name="Ombi.Schedule.Tests: IIS Express" type="LaunchSettings" factoryName=".NET Launch Settings Profile">
@ -421,7 +441,7 @@
<option name="SEND_DEBUG_REQUEST" value="1" /> <option name="SEND_DEBUG_REQUEST" value="1" />
<option name="ADDITIONAL_IIS_EXPRESS_ARGUMENTS" value="" /> <option name="ADDITIONAL_IIS_EXPRESS_ARGUMENTS" value="" />
<method v="2"> <method v="2">
<option name="Build" enabled="true" /> <option name="Build" />
</method> </method>
</configuration> </configuration>
<configuration name="Ombi.Updater" type="LaunchSettings" factoryName=".NET Launch Settings Profile"> <configuration name="Ombi.Updater" type="LaunchSettings" factoryName=".NET Launch Settings Profile">
@ -436,7 +456,7 @@
<option name="SEND_DEBUG_REQUEST" value="1" /> <option name="SEND_DEBUG_REQUEST" value="1" />
<option name="ADDITIONAL_IIS_EXPRESS_ARGUMENTS" value="" /> <option name="ADDITIONAL_IIS_EXPRESS_ARGUMENTS" value="" />
<method v="2"> <method v="2">
<option name="Build" enabled="true" /> <option name="Build" />
</method> </method>
</configuration> </configuration>
</component> </component>
@ -492,9 +512,7 @@
<window_info anchor="right" content_ui="combo" id="Hierarchy" order="4" weight="0.25" /> <window_info anchor="right" content_ui="combo" id="Hierarchy" order="4" weight="0.25" />
</layout> </layout>
</component> </component>
<component name="TypeScriptGeneratedFilesManager"> <component name="UnityProjectConfiguration" hasMinimizedUI="false" />
<option name="version" value="1" />
</component>
<component name="XDebuggerManager"> <component name="XDebuggerManager">
<breakpoint-manager> <breakpoint-manager>
<breakpoints> <breakpoints>
@ -505,7 +523,7 @@
<line-breakpoint enabled="true" type="DotNet Breakpoints"> <line-breakpoint enabled="true" type="DotNet Breakpoints">
<url>file://$PROJECT_DIR$/Ombi/Controllers/V1/TokenController.cs</url> <url>file://$PROJECT_DIR$/Ombi/Controllers/V1/TokenController.cs</url>
<line>48</line> <line>48</line>
<properties documentPath="$PROJECT_DIR$/Ombi/Controllers/V1/TokenController.cs" initialLine="48"> <properties documentPath="$PROJECT_DIR$/Ombi/Controllers/V1/TokenController.cs">
<startOffsets> <startOffsets>
<option value="1518" /> <option value="1518" />
</startOffsets> </startOffsets>
@ -518,7 +536,7 @@
<line-breakpoint enabled="true" type="DotNet Breakpoints"> <line-breakpoint enabled="true" type="DotNet Breakpoints">
<url>file://$PROJECT_DIR$/Ombi.Core/Engine/V2/MultiSearchEngine.cs</url> <url>file://$PROJECT_DIR$/Ombi.Core/Engine/V2/MultiSearchEngine.cs</url>
<line>59</line> <line>59</line>
<properties documentPath="$PROJECT_DIR$/Ombi.Core/Engine/V2/MultiSearchEngine.cs" initialLine="59"> <properties documentPath="$PROJECT_DIR$/Ombi.Core/Engine/V2/MultiSearchEngine.cs">
<startOffsets> <startOffsets>
<option value="2276" /> <option value="2276" />
</startOffsets> </startOffsets>
@ -531,7 +549,7 @@
<line-breakpoint enabled="true" type="DotNet Breakpoints"> <line-breakpoint enabled="true" type="DotNet Breakpoints">
<url>file://$PROJECT_DIR$/Ombi.Core/Engine/V2/MultiSearchEngine.cs</url> <url>file://$PROJECT_DIR$/Ombi.Core/Engine/V2/MultiSearchEngine.cs</url>
<line>49</line> <line>49</line>
<properties documentPath="$PROJECT_DIR$/Ombi.Core/Engine/V2/MultiSearchEngine.cs" initialLine="49"> <properties documentPath="$PROJECT_DIR$/Ombi.Core/Engine/V2/MultiSearchEngine.cs">
<startOffsets> <startOffsets>
<option value="2001" /> <option value="2001" />
</startOffsets> </startOffsets>
@ -544,7 +562,7 @@
<line-breakpoint enabled="true" type="DotNet Breakpoints"> <line-breakpoint enabled="true" type="DotNet Breakpoints">
<url>file://$PROJECT_DIR$/Ombi.Api.MusicBrainz/MusicBrainzApi.cs</url> <url>file://$PROJECT_DIR$/Ombi.Api.MusicBrainz/MusicBrainzApi.cs</url>
<line>30</line> <line>30</line>
<properties documentPath="$PROJECT_DIR$/Ombi.Api.MusicBrainz/MusicBrainzApi.cs" initialLine="30"> <properties documentPath="$PROJECT_DIR$/Ombi.Api.MusicBrainz/MusicBrainzApi.cs">
<startOffsets> <startOffsets>
<option value="917" /> <option value="917" />
</startOffsets> </startOffsets>

View file

@ -0,0 +1,67 @@
using System;
using System.Text;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Storage;
using Npgsql.EntityFrameworkCore.PostgreSQL.Storage.Internal;
using Ombi.Core.Models;
using Polly;
using Pomelo.EntityFrameworkCore.MySql.Storage.Internal;
namespace Ombi.Core.Helpers;
public static class DatabaseConfigurationSetup
{
public static void ConfigurePostgres(DbContextOptionsBuilder options, PerDatabaseConfiguration config)
{
options.UseNpgsql(config.ConnectionString, b =>
{
b.EnableRetryOnFailure();
}).ReplaceService<ISqlGenerationHelper, NpgsqlCaseInsensitiveSqlGenerationHelper>();
}
public static void ConfigureMySql(DbContextOptionsBuilder options, PerDatabaseConfiguration config)
{
if (string.IsNullOrEmpty(config.ConnectionString))
{
throw new ArgumentNullException("ConnectionString for the MySql/Mariadb database is empty");
}
options.UseMySql(config.ConnectionString, GetServerVersion(config.ConnectionString), b =>
{
//b.CharSetBehavior(Pomelo.EntityFrameworkCore.MySql.Infrastructure.CharSetBehavior.NeverAppend); // ##ISSUE, link to migrations?
b.EnableRetryOnFailure();
});
}
private static ServerVersion GetServerVersion(string connectionString)
{
// Workaround Windows bug, that can lead to the following exception:
//
// MySqlConnector.MySqlException (0x80004005): SSL Authentication Error
// ---> System.Security.Authentication.AuthenticationException: Authentication failed, see inner exception.
// ---> System.ComponentModel.Win32Exception (0x8009030F): The message or signature supplied for verification has been altered
//
// See https://github.com/dotnet/runtime/issues/17005#issuecomment-305848835
//
// Also workaround for the fact, that ServerVersion.AutoDetect() does not use any retrying strategy.
ServerVersion serverVersion = null;
#pragma warning disable EF1001
var retryPolicy = Policy.Handle<Exception>(exception => MySqlTransientExceptionDetector.ShouldRetryOn(exception))
#pragma warning restore EF1001
.WaitAndRetry(3, (count, context) => TimeSpan.FromMilliseconds(count * 250));
serverVersion = retryPolicy.Execute(() => serverVersion = ServerVersion.AutoDetect(connectionString));
return serverVersion;
}
public class NpgsqlCaseInsensitiveSqlGenerationHelper : NpgsqlSqlGenerationHelper
{
const string EFMigrationsHisory = "__EFMigrationsHistory";
public NpgsqlCaseInsensitiveSqlGenerationHelper(RelationalSqlGenerationHelperDependencies dependencies)
: base(dependencies) { }
public override string DelimitIdentifier(string identifier) =>
base.DelimitIdentifier(identifier == EFMigrationsHisory ? identifier : identifier.ToLower());
public override void DelimitIdentifier(StringBuilder builder, string identifier)
=> base.DelimitIdentifier(builder, identifier == EFMigrationsHisory ? identifier : identifier.ToLower());
}
}

View file

@ -0,0 +1,10 @@
namespace Ombi.Core.Helpers;
public class FileSystem : IFileSystem
{
public bool FileExists(string path)
{
return System.IO.File.Exists(path);
}
// Implement other file system operations as needed
}

View file

@ -0,0 +1,7 @@
namespace Ombi.Core.Helpers;
public interface IFileSystem
{
bool FileExists(string path);
// Add other file system operations as needed
}

View file

@ -0,0 +1,40 @@
using System.IO;
namespace Ombi.Core.Models;
public class DatabaseConfiguration
{
public const string SqliteDatabase = "Sqlite";
public DatabaseConfiguration()
{
}
public DatabaseConfiguration(string defaultSqlitePath)
{
OmbiDatabase = new PerDatabaseConfiguration(SqliteDatabase, $"Data Source={Path.Combine(defaultSqlitePath, "Ombi.db")}");
SettingsDatabase = new PerDatabaseConfiguration(SqliteDatabase, $"Data Source={Path.Combine(defaultSqlitePath, "OmbiSettings.db")}");
ExternalDatabase = new PerDatabaseConfiguration(SqliteDatabase, $"Data Source={Path.Combine(defaultSqlitePath, "OmbiExternal.db")}");
}
public PerDatabaseConfiguration OmbiDatabase { get; set; }
public PerDatabaseConfiguration SettingsDatabase { get; set; }
public PerDatabaseConfiguration ExternalDatabase { get; set; }
}
public class PerDatabaseConfiguration
{
public PerDatabaseConfiguration(string type, string connectionString)
{
Type = type;
ConnectionString = connectionString;
}
// Used in Deserialization
public PerDatabaseConfiguration()
{
}
public string Type { get; set; }
public string ConnectionString { get; set; }
}

View file

@ -0,0 +1,69 @@
using System;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Extensions.Logging;
using Newtonsoft.Json;
using Ombi.Core.Helpers;
using Ombi.Core.Models;
using Ombi.Helpers;
namespace Ombi.Core.Services;
public class DatabaseConfigurationService : IDatabaseConfigurationService
{
private readonly ILogger _logger;
private readonly IFileSystem _fileSystem;
public DatabaseConfigurationService(
ILogger<DatabaseConfigurationService> logger,
IFileSystem fileSystem)
{
_logger = logger;
_fileSystem = fileSystem;
}
public async Task<bool> ConfigureDatabase(string databaseType, string connectionString, CancellationToken token)
{
var i = StartupSingleton.Instance;
if (string.IsNullOrEmpty(i.StoragePath))
{
i.StoragePath = string.Empty;
}
var databaseFileLocation = Path.Combine(i.StoragePath, "database.json");
if (_fileSystem.FileExists(databaseFileLocation))
{
var error = $"The database file at '{databaseFileLocation}' already exists";
_logger.LogError(error);
return false;
}
var configuration = new DatabaseConfiguration
{
ExternalDatabase = new PerDatabaseConfiguration(databaseType, connectionString),
OmbiDatabase = new PerDatabaseConfiguration(databaseType, connectionString),
SettingsDatabase = new PerDatabaseConfiguration(databaseType, connectionString)
};
var json = JsonConvert.SerializeObject(configuration, Formatting.Indented);
_logger.LogInformation("Writing database configuration to file");
try
{
await File.WriteAllTextAsync(databaseFileLocation, json, token);
}
catch (Exception e)
{
_logger.LogError(e, "Failed to write database configuration to file");
return false;
}
_logger.LogInformation("Database configuration written to file");
return true;
}
}

View file

@ -0,0 +1,11 @@
using System.Threading;
using System.Threading.Tasks;
namespace Ombi.Core.Services;
public interface IDatabaseConfigurationService
{
const string MySqlDatabase = "MySQL";
const string PostgresDatabase = "Postgres";
Task<bool> ConfigureDatabase(string databaseType, string connectionString, CancellationToken token);
}

View file

@ -236,6 +236,8 @@ namespace Ombi.DependencyInjection
services.AddScoped<IFeatureService, FeatureService>(); services.AddScoped<IFeatureService, FeatureService>();
services.AddTransient<IRecentlyRequestedService, RecentlyRequestedService>(); services.AddTransient<IRecentlyRequestedService, RecentlyRequestedService>();
services.AddTransient<IPlexService, PlexService>(); services.AddTransient<IPlexService, PlexService>();
services.AddSingleton<IFileSystem, FileSystem>();
services.AddSingleton<IDatabaseConfigurationService, DatabaseConfigurationService>();
} }
public static void RegisterJobs(this IServiceCollection services) public static void RegisterJobs(this IServiceCollection services)

View file

@ -12,7 +12,7 @@
<ItemGroup> <ItemGroup>
<PackageReference Include="Ensure.That" Version="10.1.0" /> <PackageReference Include="Ensure.That" Version="10.1.0" />
<PackageReference Include="MailKit" Version="4.1.0" /> <PackageReference Include="MailKit" Version="4.6.0" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>

View file

@ -682,7 +682,6 @@ namespace Ombi.Schedule.Tests
[Test] [Test]
public async Task MovieRequestFromWatchList_AlreadyImported() public async Task MovieRequestFromWatchList_AlreadyImported()
{ {
_mocker.Setup<ISettingsService<PlexSettings>, Task<PlexSettings>>(x => x.GetSettingsAsync()).ReturnsAsync(new PlexSettings { Enable = true, EnableWatchlistImport = true }); _mocker.Setup<ISettingsService<PlexSettings>, Task<PlexSettings>>(x => x.GetSettingsAsync()).ReturnsAsync(new PlexSettings { Enable = true, EnableWatchlistImport = true });
_mocker.Setup<IPlexApi, Task<PlexWatchlistContainer>>(x => x.GetWatchlist(It.IsAny<string>(), It.IsAny<CancellationToken>())).ReturnsAsync(new PlexWatchlistContainer _mocker.Setup<IPlexApi, Task<PlexWatchlistContainer>>(x => x.GetWatchlist(It.IsAny<string>(), It.IsAny<CancellationToken>())).ReturnsAsync(new PlexWatchlistContainer
{ {
@ -719,7 +718,7 @@ namespace Ombi.Schedule.Tests
} }
}); });
_mocker.Setup<IExternalRepository<PlexWatchlistHistory>, IQueryable<PlexWatchlistHistory>>(x => x.GetAll()).Returns(new List<PlexWatchlistHistory> { new PlexWatchlistHistory { Id = 1, TmdbId = "123" } }.AsQueryable()); _mocker.Setup<IExternalRepository<PlexWatchlistHistory>, IQueryable<PlexWatchlistHistory>>(x => x.GetAll()).Returns(new List<PlexWatchlistHistory> { new PlexWatchlistHistory { Id = 1, TmdbId = "123", UserId = "abc" } }.AsQueryable());
await _subject.Execute(_context.Object); await _subject.Execute(_context.Object);
_mocker.Verify<IMovieRequestEngine>(x => x.RequestMovie(It.IsAny<MovieRequestViewModel>()), Times.Never); _mocker.Verify<IMovieRequestEngine>(x => x.RequestMovie(It.IsAny<MovieRequestViewModel>()), Times.Never);
_mocker.Verify<IPlexApi>(x => x.GetWatchlistMetadata("abc", It.IsAny<string>(), It.IsAny<CancellationToken>()), Times.Once); _mocker.Verify<IPlexApi>(x => x.GetWatchlistMetadata("abc", It.IsAny<string>(), It.IsAny<CancellationToken>()), Times.Once);

View file

@ -7,6 +7,7 @@ using Microsoft.Extensions.Logging;
using Ombi.Api.Emby; using Ombi.Api.Emby;
using Ombi.Api.Jellyfin; using Ombi.Api.Jellyfin;
using Ombi.Api.Plex; using Ombi.Api.Plex;
using Ombi.Api.Plex.Models;
using Ombi.Api.TheMovieDb; using Ombi.Api.TheMovieDb;
using Ombi.Api.TheMovieDb.Models; using Ombi.Api.TheMovieDb.Models;
using Ombi.Api.TvMaze; using Ombi.Api.TvMaze;
@ -286,7 +287,18 @@ namespace Ombi.Schedule.Jobs.Ombi
continue; continue;
} }
var servers = settings.Servers[0]; var servers = settings.Servers[0];
var metaData = await _plexApi.GetMetadata(servers.PlexAuthToken, settings.Servers[0].FullUri, movie.Key); PlexMetadata metaData = null;
try
{
metaData = await _plexApi.GetMetadata(servers.PlexAuthToken, settings.Servers[0].FullUri, movie.Key);
}
catch (Exception e)
{
_log.LogError($"Could not find the metadata for title: '{movie.Title}', skipping");
_log.LogDebug(e, $"Could not find the metadata for title: '{movie.Title}', skipping");
continue;
}
var guids = new List<string>(); var guids = new List<string>();
var meta = metaData.MediaContainer.Metadata.FirstOrDefault(); var meta = metaData.MediaContainer.Metadata.FirstOrDefault();

View file

@ -304,6 +304,13 @@ namespace Ombi.Schedule.Jobs.Plex
var existing = await Repo.GetFirstContentByCustom(x => x.Title == movie.title var existing = await Repo.GetFirstContentByCustom(x => x.Title == movie.title
&& x.ReleaseYear == movie.year.ToString() && x.ReleaseYear == movie.year.ToString()
&& x.Type == MediaType.Movie); && x.Type == MediaType.Movie);
if (existing == null)
{
// Let's just check the key
existing = await Repo.GetByKey(movie.ratingKey);
}
if (existing != null) if (existing != null)
{ {
// We need to see if this is a different quality, // We need to see if this is a different quality,
@ -342,12 +349,6 @@ namespace Ombi.Schedule.Jobs.Plex
continue; continue;
} }
//var hasSameKey = await Repo.GetByKey(movie.ratingKey);
//if (hasSameKey != null)
//{
// await Repo.Delete(hasSameKey);
//}
Logger.LogDebug("Adding movie {0}", movie.title); Logger.LogDebug("Adding movie {0}", movie.title);
var guids = new List<string>(); var guids = new List<string>();
if (!movie.Guid.Any()) if (!movie.Guid.Any())

View file

@ -128,7 +128,7 @@ namespace Ombi.Schedule.Jobs.Plex
} }
// Check to see if we have already imported this item // Check to see if we have already imported this item
var alreadyImported = _watchlistRepo.GetAll().Any(x => x.TmdbId == providerIds.TheMovieDb); var alreadyImported = _watchlistRepo.GetAll().Any(x => x.TmdbId == providerIds.TheMovieDb && x.UserId == user.Id);
if (alreadyImported) if (alreadyImported)
{ {
_logger.LogDebug($"{item.title} already imported via Plex WatchList, skipping"); _logger.LogDebug($"{item.title} already imported via Plex WatchList, skipping");
@ -202,14 +202,14 @@ namespace Ombi.Schedule.Jobs.Plex
if (response.ErrorCode == ErrorCode.AlreadyRequested) if (response.ErrorCode == ErrorCode.AlreadyRequested)
{ {
_logger.LogDebug($"Movie already requested for user '{user.UserName}'"); _logger.LogDebug($"Movie already requested for user '{user.UserName}'");
await AddToHistory(theMovieDbId); await AddToHistory(theMovieDbId, user.Id);
return; return;
} }
_logger.LogInformation($"Error adding title from PlexWatchlist for user '{user.UserName}'. Message: '{response.ErrorMessage}'"); _logger.LogInformation($"Error adding title from PlexWatchlist for user '{user.UserName}'. Message: '{response.ErrorMessage}'");
} }
else else
{ {
await AddToHistory(theMovieDbId); await AddToHistory(theMovieDbId, user.Id);
_logger.LogInformation($"Added title from PlexWatchlist for user '{user.UserName}'. {response.Message}"); _logger.LogInformation($"Added title from PlexWatchlist for user '{user.UserName}'. {response.Message}");
} }
@ -230,24 +230,26 @@ namespace Ombi.Schedule.Jobs.Plex
if (response.ErrorCode == ErrorCode.AlreadyRequested) if (response.ErrorCode == ErrorCode.AlreadyRequested)
{ {
_logger.LogDebug($"Show already requested for user '{user.UserName}'"); _logger.LogDebug($"Show already requested for user '{user.UserName}'");
await AddToHistory(theMovieDbId); await AddToHistory(theMovieDbId, user.Id);
return; return;
} }
_logger.LogInformation($"Error adding title from PlexWatchlist for user '{user.UserName}'. Message: '{response.ErrorMessage}'"); _logger.LogInformation($"Error adding title from PlexWatchlist for user '{user.UserName}'. Message: '{response.ErrorMessage}'");
} }
else else
{ {
await AddToHistory(theMovieDbId); await AddToHistory(theMovieDbId, user.Id);
_logger.LogInformation($"Added title from PlexWatchlist for user '{user.UserName}'. {response.Message}"); _logger.LogInformation($"Added title from PlexWatchlist for user '{user.UserName}'. {response.Message}");
} }
} }
private async Task AddToHistory(int theMovieDbId) private async Task AddToHistory(int theMovieDbId, string userId)
{ {
// Add to the watchlist history // Add to the watchlist history
var history = new PlexWatchlistHistory var history = new PlexWatchlistHistory
{ {
TmdbId = theMovieDbId.ToString() TmdbId = theMovieDbId.ToString(),
AddedAt = DateTime.UtcNow,
UserId = userId
}; };
await _watchlistRepo.Add(history); await _watchlistRepo.Add(history);
} }

View file

@ -1,4 +1,5 @@
using System.ComponentModel.DataAnnotations.Schema; using System;
using System.ComponentModel.DataAnnotations.Schema;
namespace Ombi.Store.Entities namespace Ombi.Store.Entities
{ {
@ -6,5 +7,7 @@ namespace Ombi.Store.Entities
public class PlexWatchlistHistory : Entity public class PlexWatchlistHistory : Entity
{ {
public string TmdbId { get; set; } public string TmdbId { get; set; }
public string UserId { get; set; }
public DateTime AddedAt { get; set; }
} }
} }

View file

@ -44,6 +44,21 @@ namespace Ombi.Store.Entities.Requests
public DateTime MarkedAsDenied4K { get; set; } public DateTime MarkedAsDenied4K { get; set; }
public string DeniedReason4K { get; set; } public string DeniedReason4K { get; set; }
[NotMapped]
public RequestCombination RequestCombination
{
get
{
if (Has4KRequest && RequestedDate != default)
{
return RequestCombination.Both;
}
if (Has4KRequest) { return RequestCombination.FourK; }
return RequestCombination.Normal;
}
}
/// <summary> /// <summary>
/// Only Use for setting the Language Code, Use the LanguageCode property for reading /// Only Use for setting the Language Code, Use the LanguageCode property for reading

View file

@ -0,0 +1,9 @@
namespace Ombi.Store.Entities.Requests
{
public enum RequestCombination
{
Normal,
FourK,
Both
}
}

View file

@ -27,3 +27,48 @@ If running migrations for any db provider other than Sqlite, then ensure the dat
cd src/Ombi.Store cd src/Ombi.Store
dotnet ef migrations add <name> --context <context> --startup-project ../Ombi/Ombi.csproj dotnet ef migrations add <name> --context <context> --startup-project ../Ombi/Ombi.csproj
``` ```
docker run -d \
--name some-postgres \
-e POSTGRES_PASSWORD=ombi \
-e POSTGRES_USER=ombi \
-e POSTGRES_DB=ombi \
postgres
### MySql example
```
{
"OmbiDatabase": {
"Type": "MySQL",
"ConnectionString": "Server=192.168.68.118;Port=3306;Database=ombiNew;User=ombi"
},
"SettingsDatabase": {
"Type": "MySQL",
"ConnectionString": "Server=192.168.68.118;Port=3306;Database=ombiNew;User=ombi"
},
"ExternalDatabase": {
"Type": "MySQL",
"ConnectionString": "Server=192.168.68.118;Port=3306;Database=ombiNew;User=ombi"
}
}
```
### Postgres Example
```
{
"OmbiDatabase": {
"Type": "Postgres",
"ConnectionString": "Host=localhost;Port=5432;Database=ombi;Username=ombi;Password=ombi"
},
"SettingsDatabase": {
"Type": "Postgres",
"ConnectionString": "Host=localhost;Port=5432;Database=ombi;Username=ombi;Password=ombi"
},
"ExternalDatabase": {
"Type": "Postgres",
"ConnectionString": "Host=localhost;Port=5432;Database=ombi;Username=ombi;Password=ombi"
}
}
```

View file

@ -0,0 +1,635 @@
// <auto-generated />
using System;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Metadata;
using Microsoft.EntityFrameworkCore.Migrations;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
using Ombi.Store.Context.MySql;
#nullable disable
namespace Ombi.Store.Migrations.ExternalMySql
{
[DbContext(typeof(ExternalMySqlContext))]
[Migration("20240909082427_WatchListUserId")]
partial class WatchListUserId
{
/// <inheritdoc />
protected override void BuildTargetModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder
.HasAnnotation("ProductVersion", "8.0.5")
.HasAnnotation("Relational:MaxIdentifierLength", 64);
MySqlModelBuilderExtensions.AutoIncrementColumns(modelBuilder);
modelBuilder.Entity("Ombi.Store.Entities.CouchPotatoCache", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int");
MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property<int>("Id"));
b.Property<int>("TheMovieDbId")
.HasColumnType("int");
b.HasKey("Id");
b.ToTable("CouchPotatoCache");
});
modelBuilder.Entity("Ombi.Store.Entities.EmbyContent", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int");
MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property<int>("Id"));
b.Property<DateTime>("AddedAt")
.HasColumnType("datetime(6)");
b.Property<string>("EmbyId")
.IsRequired()
.HasColumnType("varchar(255)");
b.Property<bool>("Has4K")
.HasColumnType("tinyint(1)");
b.Property<string>("ImdbId")
.HasColumnType("longtext");
b.Property<string>("ProviderId")
.HasColumnType("longtext");
b.Property<string>("Quality")
.HasColumnType("longtext");
b.Property<string>("TheMovieDbId")
.HasColumnType("longtext");
b.Property<string>("Title")
.HasColumnType("longtext");
b.Property<string>("TvDbId")
.HasColumnType("longtext");
b.Property<int>("Type")
.HasColumnType("int");
b.Property<string>("Url")
.HasColumnType("longtext");
b.HasKey("Id");
b.ToTable("EmbyContent");
});
modelBuilder.Entity("Ombi.Store.Entities.EmbyEpisode", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int");
MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property<int>("Id"));
b.Property<DateTime>("AddedAt")
.HasColumnType("datetime(6)");
b.Property<string>("EmbyId")
.HasColumnType("longtext");
b.Property<int>("EpisodeNumber")
.HasColumnType("int");
b.Property<string>("ImdbId")
.HasColumnType("longtext");
b.Property<string>("ParentId")
.HasColumnType("varchar(255)");
b.Property<string>("ProviderId")
.HasColumnType("longtext");
b.Property<int>("SeasonNumber")
.HasColumnType("int");
b.Property<string>("TheMovieDbId")
.HasColumnType("longtext");
b.Property<string>("Title")
.HasColumnType("longtext");
b.Property<string>("TvDbId")
.HasColumnType("longtext");
b.HasKey("Id");
b.HasIndex("ParentId");
b.ToTable("EmbyEpisode");
});
modelBuilder.Entity("Ombi.Store.Entities.JellyfinContent", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int");
MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property<int>("Id"));
b.Property<DateTime>("AddedAt")
.HasColumnType("datetime(6)");
b.Property<bool>("Has4K")
.HasColumnType("tinyint(1)");
b.Property<string>("ImdbId")
.HasColumnType("longtext");
b.Property<string>("JellyfinId")
.IsRequired()
.HasColumnType("varchar(255)");
b.Property<string>("ProviderId")
.HasColumnType("longtext");
b.Property<string>("Quality")
.HasColumnType("longtext");
b.Property<string>("TheMovieDbId")
.HasColumnType("longtext");
b.Property<string>("Title")
.HasColumnType("longtext");
b.Property<string>("TvDbId")
.HasColumnType("longtext");
b.Property<int>("Type")
.HasColumnType("int");
b.Property<string>("Url")
.HasColumnType("longtext");
b.HasKey("Id");
b.ToTable("JellyfinContent");
});
modelBuilder.Entity("Ombi.Store.Entities.JellyfinEpisode", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int");
MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property<int>("Id"));
b.Property<DateTime>("AddedAt")
.HasColumnType("datetime(6)");
b.Property<int>("EpisodeNumber")
.HasColumnType("int");
b.Property<string>("ImdbId")
.HasColumnType("longtext");
b.Property<string>("JellyfinId")
.HasColumnType("longtext");
b.Property<string>("ParentId")
.HasColumnType("varchar(255)");
b.Property<string>("ProviderId")
.HasColumnType("longtext");
b.Property<int>("SeasonNumber")
.HasColumnType("int");
b.Property<string>("TheMovieDbId")
.HasColumnType("longtext");
b.Property<string>("Title")
.HasColumnType("longtext");
b.Property<string>("TvDbId")
.HasColumnType("longtext");
b.HasKey("Id");
b.HasIndex("ParentId");
b.ToTable("JellyfinEpisode");
});
modelBuilder.Entity("Ombi.Store.Entities.LidarrAlbumCache", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int");
MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property<int>("Id"));
b.Property<DateTime>("AddedAt")
.HasColumnType("datetime(6)");
b.Property<int>("ArtistId")
.HasColumnType("int");
b.Property<string>("ForeignAlbumId")
.HasColumnType("longtext");
b.Property<bool>("Monitored")
.HasColumnType("tinyint(1)");
b.Property<decimal>("PercentOfTracks")
.HasColumnType("decimal(65,30)");
b.Property<DateTime>("ReleaseDate")
.HasColumnType("datetime(6)");
b.Property<string>("Title")
.HasColumnType("longtext");
b.Property<int>("TrackCount")
.HasColumnType("int");
b.HasKey("Id");
b.ToTable("LidarrAlbumCache");
});
modelBuilder.Entity("Ombi.Store.Entities.LidarrArtistCache", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int");
MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property<int>("Id"));
b.Property<int>("ArtistId")
.HasColumnType("int");
b.Property<string>("ArtistName")
.HasColumnType("longtext");
b.Property<string>("ForeignArtistId")
.HasColumnType("longtext");
b.Property<bool>("Monitored")
.HasColumnType("tinyint(1)");
b.HasKey("Id");
b.ToTable("LidarrArtistCache");
});
modelBuilder.Entity("Ombi.Store.Entities.PlexEpisode", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int");
MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property<int>("Id"));
b.Property<int>("EpisodeNumber")
.HasColumnType("int");
b.Property<string>("GrandparentKey")
.HasColumnType("varchar(255)");
b.Property<string>("Key")
.HasColumnType("longtext");
b.Property<string>("ParentKey")
.HasColumnType("longtext");
b.Property<int>("SeasonNumber")
.HasColumnType("int");
b.Property<string>("Title")
.HasColumnType("longtext");
b.HasKey("Id");
b.HasIndex("GrandparentKey");
b.ToTable("PlexEpisode");
});
modelBuilder.Entity("Ombi.Store.Entities.PlexSeasonsContent", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int");
MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property<int>("Id"));
b.Property<string>("ParentKey")
.HasColumnType("longtext");
b.Property<string>("PlexContentId")
.HasColumnType("longtext");
b.Property<int?>("PlexServerContentId")
.HasColumnType("int");
b.Property<string>("SeasonKey")
.HasColumnType("longtext");
b.Property<int>("SeasonNumber")
.HasColumnType("int");
b.HasKey("Id");
b.HasIndex("PlexServerContentId");
b.ToTable("PlexSeasonsContent");
});
modelBuilder.Entity("Ombi.Store.Entities.PlexServerContent", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int");
MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property<int>("Id"));
b.Property<DateTime>("AddedAt")
.HasColumnType("datetime(6)");
b.Property<bool>("Has4K")
.HasColumnType("tinyint(1)");
b.Property<string>("ImdbId")
.HasColumnType("longtext");
b.Property<string>("Key")
.IsRequired()
.HasColumnType("varchar(255)");
b.Property<string>("Quality")
.HasColumnType("longtext");
b.Property<string>("ReleaseYear")
.HasColumnType("longtext");
b.Property<int?>("RequestId")
.HasColumnType("int");
b.Property<string>("TheMovieDbId")
.HasColumnType("longtext");
b.Property<string>("Title")
.HasColumnType("longtext");
b.Property<string>("TvDbId")
.HasColumnType("longtext");
b.Property<int>("Type")
.HasColumnType("int");
b.Property<string>("Url")
.HasColumnType("longtext");
b.HasKey("Id");
b.ToTable("PlexServerContent");
});
modelBuilder.Entity("Ombi.Store.Entities.PlexWatchlistHistory", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int");
MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property<int>("Id"));
b.Property<DateTime>("AddedAt")
.HasColumnType("datetime(6)");
b.Property<string>("TmdbId")
.HasColumnType("longtext");
b.Property<string>("UserId")
.HasColumnType("longtext");
b.HasKey("Id");
b.ToTable("PlexWatchlistHistory");
});
modelBuilder.Entity("Ombi.Store.Entities.RadarrCache", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int");
MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property<int>("Id"));
b.Property<bool>("Has4K")
.HasColumnType("tinyint(1)");
b.Property<bool>("HasFile")
.HasColumnType("tinyint(1)");
b.Property<bool>("HasRegular")
.HasColumnType("tinyint(1)");
b.Property<int>("TheMovieDbId")
.HasColumnType("int");
b.HasKey("Id");
b.ToTable("RadarrCache");
});
modelBuilder.Entity("Ombi.Store.Entities.SickRageCache", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int");
MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property<int>("Id"));
b.Property<int>("TvDbId")
.HasColumnType("int");
b.HasKey("Id");
b.ToTable("SickRageCache");
});
modelBuilder.Entity("Ombi.Store.Entities.SickRageEpisodeCache", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int");
MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property<int>("Id"));
b.Property<int>("EpisodeNumber")
.HasColumnType("int");
b.Property<int>("SeasonNumber")
.HasColumnType("int");
b.Property<int>("TvDbId")
.HasColumnType("int");
b.HasKey("Id");
b.ToTable("SickRageEpisodeCache");
});
modelBuilder.Entity("Ombi.Store.Entities.SonarrCache", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int");
MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property<int>("Id"));
b.Property<int>("TheMovieDbId")
.HasColumnType("int");
b.Property<int>("TvDbId")
.HasColumnType("int");
b.HasKey("Id");
b.ToTable("SonarrCache");
});
modelBuilder.Entity("Ombi.Store.Entities.SonarrEpisodeCache", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int");
MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property<int>("Id"));
b.Property<int>("EpisodeNumber")
.HasColumnType("int");
b.Property<bool>("HasFile")
.HasColumnType("tinyint(1)");
b.Property<int>("MovieDbId")
.HasColumnType("int");
b.Property<int>("SeasonNumber")
.HasColumnType("int");
b.Property<int>("TvDbId")
.HasColumnType("int");
b.HasKey("Id");
b.ToTable("SonarrEpisodeCache");
});
modelBuilder.Entity("Ombi.Store.Entities.UserPlayedEpisode", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int");
MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property<int>("Id"));
b.Property<int>("EpisodeNumber")
.HasColumnType("int");
b.Property<int>("SeasonNumber")
.HasColumnType("int");
b.Property<int>("TheMovieDbId")
.HasColumnType("int");
b.Property<string>("UserId")
.HasColumnType("longtext");
b.HasKey("Id");
b.ToTable("UserPlayedEpisode");
});
modelBuilder.Entity("Ombi.Store.Entities.UserPlayedMovie", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int");
MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property<int>("Id"));
b.Property<int>("TheMovieDbId")
.HasColumnType("int");
b.Property<string>("UserId")
.HasColumnType("longtext");
b.HasKey("Id");
b.ToTable("UserPlayedMovie");
});
modelBuilder.Entity("Ombi.Store.Entities.EmbyEpisode", b =>
{
b.HasOne("Ombi.Store.Entities.EmbyContent", "Series")
.WithMany("Episodes")
.HasForeignKey("ParentId")
.HasPrincipalKey("EmbyId");
b.Navigation("Series");
});
modelBuilder.Entity("Ombi.Store.Entities.JellyfinEpisode", b =>
{
b.HasOne("Ombi.Store.Entities.JellyfinContent", "Series")
.WithMany("Episodes")
.HasForeignKey("ParentId")
.HasPrincipalKey("JellyfinId");
b.Navigation("Series");
});
modelBuilder.Entity("Ombi.Store.Entities.PlexEpisode", b =>
{
b.HasOne("Ombi.Store.Entities.PlexServerContent", "Series")
.WithMany("Episodes")
.HasForeignKey("GrandparentKey")
.HasPrincipalKey("Key");
b.Navigation("Series");
});
modelBuilder.Entity("Ombi.Store.Entities.PlexSeasonsContent", b =>
{
b.HasOne("Ombi.Store.Entities.PlexServerContent", null)
.WithMany("Seasons")
.HasForeignKey("PlexServerContentId");
});
modelBuilder.Entity("Ombi.Store.Entities.EmbyContent", b =>
{
b.Navigation("Episodes");
});
modelBuilder.Entity("Ombi.Store.Entities.JellyfinContent", b =>
{
b.Navigation("Episodes");
});
modelBuilder.Entity("Ombi.Store.Entities.PlexServerContent", b =>
{
b.Navigation("Episodes");
b.Navigation("Seasons");
});
#pragma warning restore 612, 618
}
}
}

View file

@ -0,0 +1,366 @@
using System;
using Microsoft.EntityFrameworkCore.Metadata;
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace Ombi.Store.Migrations.ExternalMySql
{
/// <inheritdoc />
public partial class WatchListUserId : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AlterColumn<int>(
name: "Id",
table: "UserPlayedMovie",
type: "int",
nullable: false,
oldClrType: typeof(int),
oldType: "int")
.Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn);
migrationBuilder.AlterColumn<int>(
name: "Id",
table: "UserPlayedEpisode",
type: "int",
nullable: false,
oldClrType: typeof(int),
oldType: "int")
.Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn);
migrationBuilder.AlterColumn<int>(
name: "Id",
table: "SonarrEpisodeCache",
type: "int",
nullable: false,
oldClrType: typeof(int),
oldType: "int")
.Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn);
migrationBuilder.AlterColumn<int>(
name: "Id",
table: "SonarrCache",
type: "int",
nullable: false,
oldClrType: typeof(int),
oldType: "int")
.Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn);
migrationBuilder.AlterColumn<int>(
name: "Id",
table: "SickRageEpisodeCache",
type: "int",
nullable: false,
oldClrType: typeof(int),
oldType: "int")
.Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn);
migrationBuilder.AlterColumn<int>(
name: "Id",
table: "SickRageCache",
type: "int",
nullable: false,
oldClrType: typeof(int),
oldType: "int")
.Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn);
migrationBuilder.AlterColumn<int>(
name: "Id",
table: "RadarrCache",
type: "int",
nullable: false,
oldClrType: typeof(int),
oldType: "int")
.Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn);
migrationBuilder.AlterColumn<int>(
name: "Id",
table: "PlexWatchlistHistory",
type: "int",
nullable: false,
oldClrType: typeof(int),
oldType: "int")
.Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn);
migrationBuilder.AddColumn<DateTime>(
name: "AddedAt",
table: "PlexWatchlistHistory",
type: "datetime(6)",
nullable: false,
defaultValue: new DateTime(1, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified));
migrationBuilder.AddColumn<string>(
name: "UserId",
table: "PlexWatchlistHistory",
type: "longtext",
nullable: true)
.Annotation("MySql:CharSet", "utf8mb4");
migrationBuilder.AlterColumn<int>(
name: "Id",
table: "PlexServerContent",
type: "int",
nullable: false,
oldClrType: typeof(int),
oldType: "int")
.Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn);
migrationBuilder.AlterColumn<int>(
name: "Id",
table: "PlexSeasonsContent",
type: "int",
nullable: false,
oldClrType: typeof(int),
oldType: "int")
.Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn);
migrationBuilder.AlterColumn<int>(
name: "Id",
table: "PlexEpisode",
type: "int",
nullable: false,
oldClrType: typeof(int),
oldType: "int")
.Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn);
migrationBuilder.AlterColumn<int>(
name: "Id",
table: "LidarrArtistCache",
type: "int",
nullable: false,
oldClrType: typeof(int),
oldType: "int")
.Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn);
migrationBuilder.AlterColumn<int>(
name: "Id",
table: "LidarrAlbumCache",
type: "int",
nullable: false,
oldClrType: typeof(int),
oldType: "int")
.Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn);
migrationBuilder.AlterColumn<int>(
name: "Id",
table: "JellyfinEpisode",
type: "int",
nullable: false,
oldClrType: typeof(int),
oldType: "int")
.Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn);
migrationBuilder.AlterColumn<int>(
name: "Id",
table: "JellyfinContent",
type: "int",
nullable: false,
oldClrType: typeof(int),
oldType: "int")
.Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn);
migrationBuilder.AlterColumn<int>(
name: "Id",
table: "EmbyEpisode",
type: "int",
nullable: false,
oldClrType: typeof(int),
oldType: "int")
.Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn);
migrationBuilder.AlterColumn<int>(
name: "Id",
table: "EmbyContent",
type: "int",
nullable: false,
oldClrType: typeof(int),
oldType: "int")
.Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn);
migrationBuilder.AlterColumn<int>(
name: "Id",
table: "CouchPotatoCache",
type: "int",
nullable: false,
oldClrType: typeof(int),
oldType: "int")
.Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn);
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropColumn(
name: "AddedAt",
table: "PlexWatchlistHistory");
migrationBuilder.DropColumn(
name: "UserId",
table: "PlexWatchlistHistory");
migrationBuilder.AlterColumn<int>(
name: "Id",
table: "UserPlayedMovie",
type: "int",
nullable: false,
oldClrType: typeof(int),
oldType: "int")
.OldAnnotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn);
migrationBuilder.AlterColumn<int>(
name: "Id",
table: "UserPlayedEpisode",
type: "int",
nullable: false,
oldClrType: typeof(int),
oldType: "int")
.OldAnnotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn);
migrationBuilder.AlterColumn<int>(
name: "Id",
table: "SonarrEpisodeCache",
type: "int",
nullable: false,
oldClrType: typeof(int),
oldType: "int")
.OldAnnotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn);
migrationBuilder.AlterColumn<int>(
name: "Id",
table: "SonarrCache",
type: "int",
nullable: false,
oldClrType: typeof(int),
oldType: "int")
.OldAnnotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn);
migrationBuilder.AlterColumn<int>(
name: "Id",
table: "SickRageEpisodeCache",
type: "int",
nullable: false,
oldClrType: typeof(int),
oldType: "int")
.OldAnnotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn);
migrationBuilder.AlterColumn<int>(
name: "Id",
table: "SickRageCache",
type: "int",
nullable: false,
oldClrType: typeof(int),
oldType: "int")
.OldAnnotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn);
migrationBuilder.AlterColumn<int>(
name: "Id",
table: "RadarrCache",
type: "int",
nullable: false,
oldClrType: typeof(int),
oldType: "int")
.OldAnnotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn);
migrationBuilder.AlterColumn<int>(
name: "Id",
table: "PlexWatchlistHistory",
type: "int",
nullable: false,
oldClrType: typeof(int),
oldType: "int")
.OldAnnotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn);
migrationBuilder.AlterColumn<int>(
name: "Id",
table: "PlexServerContent",
type: "int",
nullable: false,
oldClrType: typeof(int),
oldType: "int")
.OldAnnotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn);
migrationBuilder.AlterColumn<int>(
name: "Id",
table: "PlexSeasonsContent",
type: "int",
nullable: false,
oldClrType: typeof(int),
oldType: "int")
.OldAnnotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn);
migrationBuilder.AlterColumn<int>(
name: "Id",
table: "PlexEpisode",
type: "int",
nullable: false,
oldClrType: typeof(int),
oldType: "int")
.OldAnnotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn);
migrationBuilder.AlterColumn<int>(
name: "Id",
table: "LidarrArtistCache",
type: "int",
nullable: false,
oldClrType: typeof(int),
oldType: "int")
.OldAnnotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn);
migrationBuilder.AlterColumn<int>(
name: "Id",
table: "LidarrAlbumCache",
type: "int",
nullable: false,
oldClrType: typeof(int),
oldType: "int")
.OldAnnotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn);
migrationBuilder.AlterColumn<int>(
name: "Id",
table: "JellyfinEpisode",
type: "int",
nullable: false,
oldClrType: typeof(int),
oldType: "int")
.OldAnnotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn);
migrationBuilder.AlterColumn<int>(
name: "Id",
table: "JellyfinContent",
type: "int",
nullable: false,
oldClrType: typeof(int),
oldType: "int")
.OldAnnotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn);
migrationBuilder.AlterColumn<int>(
name: "Id",
table: "EmbyEpisode",
type: "int",
nullable: false,
oldClrType: typeof(int),
oldType: "int")
.OldAnnotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn);
migrationBuilder.AlterColumn<int>(
name: "Id",
table: "EmbyContent",
type: "int",
nullable: false,
oldClrType: typeof(int),
oldType: "int")
.OldAnnotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn);
migrationBuilder.AlterColumn<int>(
name: "Id",
table: "CouchPotatoCache",
type: "int",
nullable: false,
oldClrType: typeof(int),
oldType: "int")
.OldAnnotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn);
}
}
}

View file

@ -2,6 +2,7 @@
using System; using System;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure; using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Metadata;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion; using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
using Ombi.Store.Context.MySql; using Ombi.Store.Context.MySql;
@ -16,15 +17,19 @@ namespace Ombi.Store.Migrations.ExternalMySql
{ {
#pragma warning disable 612, 618 #pragma warning disable 612, 618
modelBuilder modelBuilder
.HasAnnotation("ProductVersion", "6.0.9") .HasAnnotation("ProductVersion", "8.0.5")
.HasAnnotation("Relational:MaxIdentifierLength", 64); .HasAnnotation("Relational:MaxIdentifierLength", 64);
MySqlModelBuilderExtensions.AutoIncrementColumns(modelBuilder);
modelBuilder.Entity("Ombi.Store.Entities.CouchPotatoCache", b => modelBuilder.Entity("Ombi.Store.Entities.CouchPotatoCache", b =>
{ {
b.Property<int>("Id") b.Property<int>("Id")
.ValueGeneratedOnAdd() .ValueGeneratedOnAdd()
.HasColumnType("int"); .HasColumnType("int");
MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property<int>("Id"));
b.Property<int>("TheMovieDbId") b.Property<int>("TheMovieDbId")
.HasColumnType("int"); .HasColumnType("int");
@ -39,6 +44,8 @@ namespace Ombi.Store.Migrations.ExternalMySql
.ValueGeneratedOnAdd() .ValueGeneratedOnAdd()
.HasColumnType("int"); .HasColumnType("int");
MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property<int>("Id"));
b.Property<DateTime>("AddedAt") b.Property<DateTime>("AddedAt")
.HasColumnType("datetime(6)"); .HasColumnType("datetime(6)");
@ -84,6 +91,8 @@ namespace Ombi.Store.Migrations.ExternalMySql
.ValueGeneratedOnAdd() .ValueGeneratedOnAdd()
.HasColumnType("int"); .HasColumnType("int");
MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property<int>("Id"));
b.Property<DateTime>("AddedAt") b.Property<DateTime>("AddedAt")
.HasColumnType("datetime(6)"); .HasColumnType("datetime(6)");
@ -127,6 +136,8 @@ namespace Ombi.Store.Migrations.ExternalMySql
.ValueGeneratedOnAdd() .ValueGeneratedOnAdd()
.HasColumnType("int"); .HasColumnType("int");
MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property<int>("Id"));
b.Property<DateTime>("AddedAt") b.Property<DateTime>("AddedAt")
.HasColumnType("datetime(6)"); .HasColumnType("datetime(6)");
@ -172,6 +183,8 @@ namespace Ombi.Store.Migrations.ExternalMySql
.ValueGeneratedOnAdd() .ValueGeneratedOnAdd()
.HasColumnType("int"); .HasColumnType("int");
MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property<int>("Id"));
b.Property<DateTime>("AddedAt") b.Property<DateTime>("AddedAt")
.HasColumnType("datetime(6)"); .HasColumnType("datetime(6)");
@ -215,6 +228,8 @@ namespace Ombi.Store.Migrations.ExternalMySql
.ValueGeneratedOnAdd() .ValueGeneratedOnAdd()
.HasColumnType("int"); .HasColumnType("int");
MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property<int>("Id"));
b.Property<DateTime>("AddedAt") b.Property<DateTime>("AddedAt")
.HasColumnType("datetime(6)"); .HasColumnType("datetime(6)");
@ -250,6 +265,8 @@ namespace Ombi.Store.Migrations.ExternalMySql
.ValueGeneratedOnAdd() .ValueGeneratedOnAdd()
.HasColumnType("int"); .HasColumnType("int");
MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property<int>("Id"));
b.Property<int>("ArtistId") b.Property<int>("ArtistId")
.HasColumnType("int"); .HasColumnType("int");
@ -273,6 +290,8 @@ namespace Ombi.Store.Migrations.ExternalMySql
.ValueGeneratedOnAdd() .ValueGeneratedOnAdd()
.HasColumnType("int"); .HasColumnType("int");
MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property<int>("Id"));
b.Property<int>("EpisodeNumber") b.Property<int>("EpisodeNumber")
.HasColumnType("int"); .HasColumnType("int");
@ -304,6 +323,8 @@ namespace Ombi.Store.Migrations.ExternalMySql
.ValueGeneratedOnAdd() .ValueGeneratedOnAdd()
.HasColumnType("int"); .HasColumnType("int");
MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property<int>("Id"));
b.Property<string>("ParentKey") b.Property<string>("ParentKey")
.HasColumnType("longtext"); .HasColumnType("longtext");
@ -332,6 +353,8 @@ namespace Ombi.Store.Migrations.ExternalMySql
.ValueGeneratedOnAdd() .ValueGeneratedOnAdd()
.HasColumnType("int"); .HasColumnType("int");
MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property<int>("Id"));
b.Property<DateTime>("AddedAt") b.Property<DateTime>("AddedAt")
.HasColumnType("datetime(6)"); .HasColumnType("datetime(6)");
@ -380,9 +403,17 @@ namespace Ombi.Store.Migrations.ExternalMySql
.ValueGeneratedOnAdd() .ValueGeneratedOnAdd()
.HasColumnType("int"); .HasColumnType("int");
MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property<int>("Id"));
b.Property<DateTime>("AddedAt")
.HasColumnType("datetime(6)");
b.Property<string>("TmdbId") b.Property<string>("TmdbId")
.HasColumnType("longtext"); .HasColumnType("longtext");
b.Property<string>("UserId")
.HasColumnType("longtext");
b.HasKey("Id"); b.HasKey("Id");
b.ToTable("PlexWatchlistHistory"); b.ToTable("PlexWatchlistHistory");
@ -394,6 +425,8 @@ namespace Ombi.Store.Migrations.ExternalMySql
.ValueGeneratedOnAdd() .ValueGeneratedOnAdd()
.HasColumnType("int"); .HasColumnType("int");
MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property<int>("Id"));
b.Property<bool>("Has4K") b.Property<bool>("Has4K")
.HasColumnType("tinyint(1)"); .HasColumnType("tinyint(1)");
@ -417,6 +450,8 @@ namespace Ombi.Store.Migrations.ExternalMySql
.ValueGeneratedOnAdd() .ValueGeneratedOnAdd()
.HasColumnType("int"); .HasColumnType("int");
MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property<int>("Id"));
b.Property<int>("TvDbId") b.Property<int>("TvDbId")
.HasColumnType("int"); .HasColumnType("int");
@ -431,6 +466,8 @@ namespace Ombi.Store.Migrations.ExternalMySql
.ValueGeneratedOnAdd() .ValueGeneratedOnAdd()
.HasColumnType("int"); .HasColumnType("int");
MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property<int>("Id"));
b.Property<int>("EpisodeNumber") b.Property<int>("EpisodeNumber")
.HasColumnType("int"); .HasColumnType("int");
@ -451,6 +488,8 @@ namespace Ombi.Store.Migrations.ExternalMySql
.ValueGeneratedOnAdd() .ValueGeneratedOnAdd()
.HasColumnType("int"); .HasColumnType("int");
MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property<int>("Id"));
b.Property<int>("TheMovieDbId") b.Property<int>("TheMovieDbId")
.HasColumnType("int"); .HasColumnType("int");
@ -468,6 +507,8 @@ namespace Ombi.Store.Migrations.ExternalMySql
.ValueGeneratedOnAdd() .ValueGeneratedOnAdd()
.HasColumnType("int"); .HasColumnType("int");
MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property<int>("Id"));
b.Property<int>("EpisodeNumber") b.Property<int>("EpisodeNumber")
.HasColumnType("int"); .HasColumnType("int");
@ -494,6 +535,8 @@ namespace Ombi.Store.Migrations.ExternalMySql
.ValueGeneratedOnAdd() .ValueGeneratedOnAdd()
.HasColumnType("int"); .HasColumnType("int");
MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property<int>("Id"));
b.Property<int>("EpisodeNumber") b.Property<int>("EpisodeNumber")
.HasColumnType("int"); .HasColumnType("int");
@ -517,6 +560,8 @@ namespace Ombi.Store.Migrations.ExternalMySql
.ValueGeneratedOnAdd() .ValueGeneratedOnAdd()
.HasColumnType("int"); .HasColumnType("int");
MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property<int>("Id"));
b.Property<int>("TheMovieDbId") b.Property<int>("TheMovieDbId")
.HasColumnType("int"); .HasColumnType("int");

View file

@ -0,0 +1,635 @@
// <auto-generated />
using System;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Migrations;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
using Ombi.Store.Context.Postgres;
#nullable disable
namespace Ombi.Store.Migrations.ExternalPostgres
{
[DbContext(typeof(ExternalPostgresContext))]
[Migration("20240909071802_WatchListUserId")]
partial class WatchListUserId
{
/// <inheritdoc />
protected override void BuildTargetModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder
.HasAnnotation("ProductVersion", "8.0.5")
.HasAnnotation("Relational:MaxIdentifierLength", 63);
NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder);
modelBuilder.Entity("Ombi.Store.Entities.CouchPotatoCache", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("integer");
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
b.Property<int>("TheMovieDbId")
.HasColumnType("integer");
b.HasKey("Id");
b.ToTable("CouchPotatoCache");
});
modelBuilder.Entity("Ombi.Store.Entities.EmbyContent", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("integer");
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
b.Property<DateTime>("AddedAt")
.HasColumnType("timestamp without time zone");
b.Property<string>("EmbyId")
.IsRequired()
.HasColumnType("text");
b.Property<bool>("Has4K")
.HasColumnType("boolean");
b.Property<string>("ImdbId")
.HasColumnType("text");
b.Property<string>("ProviderId")
.HasColumnType("text");
b.Property<string>("Quality")
.HasColumnType("text");
b.Property<string>("TheMovieDbId")
.HasColumnType("text");
b.Property<string>("Title")
.HasColumnType("text");
b.Property<string>("TvDbId")
.HasColumnType("text");
b.Property<int>("Type")
.HasColumnType("integer");
b.Property<string>("Url")
.HasColumnType("text");
b.HasKey("Id");
b.ToTable("EmbyContent");
});
modelBuilder.Entity("Ombi.Store.Entities.EmbyEpisode", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("integer");
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
b.Property<DateTime>("AddedAt")
.HasColumnType("timestamp without time zone");
b.Property<string>("EmbyId")
.HasColumnType("text");
b.Property<int>("EpisodeNumber")
.HasColumnType("integer");
b.Property<string>("ImdbId")
.HasColumnType("text");
b.Property<string>("ParentId")
.HasColumnType("text");
b.Property<string>("ProviderId")
.HasColumnType("text");
b.Property<int>("SeasonNumber")
.HasColumnType("integer");
b.Property<string>("TheMovieDbId")
.HasColumnType("text");
b.Property<string>("Title")
.HasColumnType("text");
b.Property<string>("TvDbId")
.HasColumnType("text");
b.HasKey("Id");
b.HasIndex("ParentId");
b.ToTable("EmbyEpisode");
});
modelBuilder.Entity("Ombi.Store.Entities.JellyfinContent", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("integer");
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
b.Property<DateTime>("AddedAt")
.HasColumnType("timestamp without time zone");
b.Property<bool>("Has4K")
.HasColumnType("boolean");
b.Property<string>("ImdbId")
.HasColumnType("text");
b.Property<string>("JellyfinId")
.IsRequired()
.HasColumnType("text");
b.Property<string>("ProviderId")
.HasColumnType("text");
b.Property<string>("Quality")
.HasColumnType("text");
b.Property<string>("TheMovieDbId")
.HasColumnType("text");
b.Property<string>("Title")
.HasColumnType("text");
b.Property<string>("TvDbId")
.HasColumnType("text");
b.Property<int>("Type")
.HasColumnType("integer");
b.Property<string>("Url")
.HasColumnType("text");
b.HasKey("Id");
b.ToTable("JellyfinContent");
});
modelBuilder.Entity("Ombi.Store.Entities.JellyfinEpisode", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("integer");
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
b.Property<DateTime>("AddedAt")
.HasColumnType("timestamp without time zone");
b.Property<int>("EpisodeNumber")
.HasColumnType("integer");
b.Property<string>("ImdbId")
.HasColumnType("text");
b.Property<string>("JellyfinId")
.HasColumnType("text");
b.Property<string>("ParentId")
.HasColumnType("text");
b.Property<string>("ProviderId")
.HasColumnType("text");
b.Property<int>("SeasonNumber")
.HasColumnType("integer");
b.Property<string>("TheMovieDbId")
.HasColumnType("text");
b.Property<string>("Title")
.HasColumnType("text");
b.Property<string>("TvDbId")
.HasColumnType("text");
b.HasKey("Id");
b.HasIndex("ParentId");
b.ToTable("JellyfinEpisode");
});
modelBuilder.Entity("Ombi.Store.Entities.LidarrAlbumCache", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("integer");
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
b.Property<DateTime>("AddedAt")
.HasColumnType("timestamp without time zone");
b.Property<int>("ArtistId")
.HasColumnType("integer");
b.Property<string>("ForeignAlbumId")
.HasColumnType("text");
b.Property<bool>("Monitored")
.HasColumnType("boolean");
b.Property<decimal>("PercentOfTracks")
.HasColumnType("numeric");
b.Property<DateTime>("ReleaseDate")
.HasColumnType("timestamp without time zone");
b.Property<string>("Title")
.HasColumnType("text");
b.Property<int>("TrackCount")
.HasColumnType("integer");
b.HasKey("Id");
b.ToTable("LidarrAlbumCache");
});
modelBuilder.Entity("Ombi.Store.Entities.LidarrArtistCache", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("integer");
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
b.Property<int>("ArtistId")
.HasColumnType("integer");
b.Property<string>("ArtistName")
.HasColumnType("text");
b.Property<string>("ForeignArtistId")
.HasColumnType("text");
b.Property<bool>("Monitored")
.HasColumnType("boolean");
b.HasKey("Id");
b.ToTable("LidarrArtistCache");
});
modelBuilder.Entity("Ombi.Store.Entities.PlexEpisode", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("integer");
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
b.Property<int>("EpisodeNumber")
.HasColumnType("integer");
b.Property<string>("GrandparentKey")
.HasColumnType("text");
b.Property<string>("Key")
.HasColumnType("text");
b.Property<string>("ParentKey")
.HasColumnType("text");
b.Property<int>("SeasonNumber")
.HasColumnType("integer");
b.Property<string>("Title")
.HasColumnType("text");
b.HasKey("Id");
b.HasIndex("GrandparentKey");
b.ToTable("PlexEpisode");
});
modelBuilder.Entity("Ombi.Store.Entities.PlexSeasonsContent", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("integer");
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
b.Property<string>("ParentKey")
.HasColumnType("text");
b.Property<string>("PlexContentId")
.HasColumnType("text");
b.Property<int?>("PlexServerContentId")
.HasColumnType("integer");
b.Property<string>("SeasonKey")
.HasColumnType("text");
b.Property<int>("SeasonNumber")
.HasColumnType("integer");
b.HasKey("Id");
b.HasIndex("PlexServerContentId");
b.ToTable("PlexSeasonsContent");
});
modelBuilder.Entity("Ombi.Store.Entities.PlexServerContent", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("integer");
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
b.Property<DateTime>("AddedAt")
.HasColumnType("timestamp without time zone");
b.Property<bool>("Has4K")
.HasColumnType("boolean");
b.Property<string>("ImdbId")
.HasColumnType("text");
b.Property<string>("Key")
.IsRequired()
.HasColumnType("text");
b.Property<string>("Quality")
.HasColumnType("text");
b.Property<string>("ReleaseYear")
.HasColumnType("text");
b.Property<int?>("RequestId")
.HasColumnType("integer");
b.Property<string>("TheMovieDbId")
.HasColumnType("text");
b.Property<string>("Title")
.HasColumnType("text");
b.Property<string>("TvDbId")
.HasColumnType("text");
b.Property<int>("Type")
.HasColumnType("integer");
b.Property<string>("Url")
.HasColumnType("text");
b.HasKey("Id");
b.ToTable("PlexServerContent");
});
modelBuilder.Entity("Ombi.Store.Entities.PlexWatchlistHistory", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("integer");
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
b.Property<DateTime>("AddedAt")
.HasColumnType("timestamp without time zone");
b.Property<string>("TmdbId")
.HasColumnType("text");
b.Property<string>("UserId")
.HasColumnType("text");
b.HasKey("Id");
b.ToTable("PlexWatchlistHistory");
});
modelBuilder.Entity("Ombi.Store.Entities.RadarrCache", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("integer");
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
b.Property<bool>("Has4K")
.HasColumnType("boolean");
b.Property<bool>("HasFile")
.HasColumnType("boolean");
b.Property<bool>("HasRegular")
.HasColumnType("boolean");
b.Property<int>("TheMovieDbId")
.HasColumnType("integer");
b.HasKey("Id");
b.ToTable("RadarrCache");
});
modelBuilder.Entity("Ombi.Store.Entities.SickRageCache", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("integer");
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
b.Property<int>("TvDbId")
.HasColumnType("integer");
b.HasKey("Id");
b.ToTable("SickRageCache");
});
modelBuilder.Entity("Ombi.Store.Entities.SickRageEpisodeCache", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("integer");
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
b.Property<int>("EpisodeNumber")
.HasColumnType("integer");
b.Property<int>("SeasonNumber")
.HasColumnType("integer");
b.Property<int>("TvDbId")
.HasColumnType("integer");
b.HasKey("Id");
b.ToTable("SickRageEpisodeCache");
});
modelBuilder.Entity("Ombi.Store.Entities.SonarrCache", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("integer");
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
b.Property<int>("TheMovieDbId")
.HasColumnType("integer");
b.Property<int>("TvDbId")
.HasColumnType("integer");
b.HasKey("Id");
b.ToTable("SonarrCache");
});
modelBuilder.Entity("Ombi.Store.Entities.SonarrEpisodeCache", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("integer");
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
b.Property<int>("EpisodeNumber")
.HasColumnType("integer");
b.Property<bool>("HasFile")
.HasColumnType("boolean");
b.Property<int>("MovieDbId")
.HasColumnType("integer");
b.Property<int>("SeasonNumber")
.HasColumnType("integer");
b.Property<int>("TvDbId")
.HasColumnType("integer");
b.HasKey("Id");
b.ToTable("SonarrEpisodeCache");
});
modelBuilder.Entity("Ombi.Store.Entities.UserPlayedEpisode", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("integer");
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
b.Property<int>("EpisodeNumber")
.HasColumnType("integer");
b.Property<int>("SeasonNumber")
.HasColumnType("integer");
b.Property<int>("TheMovieDbId")
.HasColumnType("integer");
b.Property<string>("UserId")
.HasColumnType("text");
b.HasKey("Id");
b.ToTable("UserPlayedEpisode");
});
modelBuilder.Entity("Ombi.Store.Entities.UserPlayedMovie", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("integer");
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
b.Property<int>("TheMovieDbId")
.HasColumnType("integer");
b.Property<string>("UserId")
.HasColumnType("text");
b.HasKey("Id");
b.ToTable("UserPlayedMovie");
});
modelBuilder.Entity("Ombi.Store.Entities.EmbyEpisode", b =>
{
b.HasOne("Ombi.Store.Entities.EmbyContent", "Series")
.WithMany("Episodes")
.HasForeignKey("ParentId")
.HasPrincipalKey("EmbyId");
b.Navigation("Series");
});
modelBuilder.Entity("Ombi.Store.Entities.JellyfinEpisode", b =>
{
b.HasOne("Ombi.Store.Entities.JellyfinContent", "Series")
.WithMany("Episodes")
.HasForeignKey("ParentId")
.HasPrincipalKey("JellyfinId");
b.Navigation("Series");
});
modelBuilder.Entity("Ombi.Store.Entities.PlexEpisode", b =>
{
b.HasOne("Ombi.Store.Entities.PlexServerContent", "Series")
.WithMany("Episodes")
.HasForeignKey("GrandparentKey")
.HasPrincipalKey("Key");
b.Navigation("Series");
});
modelBuilder.Entity("Ombi.Store.Entities.PlexSeasonsContent", b =>
{
b.HasOne("Ombi.Store.Entities.PlexServerContent", null)
.WithMany("Seasons")
.HasForeignKey("PlexServerContentId");
});
modelBuilder.Entity("Ombi.Store.Entities.EmbyContent", b =>
{
b.Navigation("Episodes");
});
modelBuilder.Entity("Ombi.Store.Entities.JellyfinContent", b =>
{
b.Navigation("Episodes");
});
modelBuilder.Entity("Ombi.Store.Entities.PlexServerContent", b =>
{
b.Navigation("Episodes");
b.Navigation("Seasons");
});
#pragma warning restore 612, 618
}
}
}

View file

@ -0,0 +1,152 @@
using System;
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace Ombi.Store.Migrations.ExternalPostgres
{
/// <inheritdoc />
public partial class WatchListUserId : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AddColumn<DateTime>(
name: "AddedAt",
table: "PlexWatchlistHistory",
type: "timestamp without time zone",
nullable: false,
defaultValue: new DateTime(1, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified));
migrationBuilder.AddColumn<string>(
name: "UserId",
table: "PlexWatchlistHistory",
type: "text",
nullable: true);
migrationBuilder.AlterColumn<DateTime>(
name: "AddedAt",
table: "PlexServerContent",
type: "timestamp without time zone",
nullable: false,
oldClrType: typeof(DateTime),
oldType: "timestamp with time zone");
migrationBuilder.AlterColumn<DateTime>(
name: "ReleaseDate",
table: "LidarrAlbumCache",
type: "timestamp without time zone",
nullable: false,
oldClrType: typeof(DateTime),
oldType: "timestamp with time zone");
migrationBuilder.AlterColumn<DateTime>(
name: "AddedAt",
table: "LidarrAlbumCache",
type: "timestamp without time zone",
nullable: false,
oldClrType: typeof(DateTime),
oldType: "timestamp with time zone");
migrationBuilder.AlterColumn<DateTime>(
name: "AddedAt",
table: "JellyfinEpisode",
type: "timestamp without time zone",
nullable: false,
oldClrType: typeof(DateTime),
oldType: "timestamp with time zone");
migrationBuilder.AlterColumn<DateTime>(
name: "AddedAt",
table: "JellyfinContent",
type: "timestamp without time zone",
nullable: false,
oldClrType: typeof(DateTime),
oldType: "timestamp with time zone");
migrationBuilder.AlterColumn<DateTime>(
name: "AddedAt",
table: "EmbyEpisode",
type: "timestamp without time zone",
nullable: false,
oldClrType: typeof(DateTime),
oldType: "timestamp with time zone");
migrationBuilder.AlterColumn<DateTime>(
name: "AddedAt",
table: "EmbyContent",
type: "timestamp without time zone",
nullable: false,
oldClrType: typeof(DateTime),
oldType: "timestamp with time zone");
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropColumn(
name: "AddedAt",
table: "PlexWatchlistHistory");
migrationBuilder.DropColumn(
name: "UserId",
table: "PlexWatchlistHistory");
migrationBuilder.AlterColumn<DateTime>(
name: "AddedAt",
table: "PlexServerContent",
type: "timestamp with time zone",
nullable: false,
oldClrType: typeof(DateTime),
oldType: "timestamp without time zone");
migrationBuilder.AlterColumn<DateTime>(
name: "ReleaseDate",
table: "LidarrAlbumCache",
type: "timestamp with time zone",
nullable: false,
oldClrType: typeof(DateTime),
oldType: "timestamp without time zone");
migrationBuilder.AlterColumn<DateTime>(
name: "AddedAt",
table: "LidarrAlbumCache",
type: "timestamp with time zone",
nullable: false,
oldClrType: typeof(DateTime),
oldType: "timestamp without time zone");
migrationBuilder.AlterColumn<DateTime>(
name: "AddedAt",
table: "JellyfinEpisode",
type: "timestamp with time zone",
nullable: false,
oldClrType: typeof(DateTime),
oldType: "timestamp without time zone");
migrationBuilder.AlterColumn<DateTime>(
name: "AddedAt",
table: "JellyfinContent",
type: "timestamp with time zone",
nullable: false,
oldClrType: typeof(DateTime),
oldType: "timestamp without time zone");
migrationBuilder.AlterColumn<DateTime>(
name: "AddedAt",
table: "EmbyEpisode",
type: "timestamp with time zone",
nullable: false,
oldClrType: typeof(DateTime),
oldType: "timestamp without time zone");
migrationBuilder.AlterColumn<DateTime>(
name: "AddedAt",
table: "EmbyContent",
type: "timestamp with time zone",
nullable: false,
oldClrType: typeof(DateTime),
oldType: "timestamp without time zone");
}
}
}

View file

@ -17,7 +17,7 @@ namespace Ombi.Store.Migrations.ExternalPostgres
{ {
#pragma warning disable 612, 618 #pragma warning disable 612, 618
modelBuilder modelBuilder
.HasAnnotation("ProductVersion", "6.0.22") .HasAnnotation("ProductVersion", "8.0.5")
.HasAnnotation("Relational:MaxIdentifierLength", 63); .HasAnnotation("Relational:MaxIdentifierLength", 63);
NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder);
@ -47,7 +47,7 @@ namespace Ombi.Store.Migrations.ExternalPostgres
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id")); NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
b.Property<DateTime>("AddedAt") b.Property<DateTime>("AddedAt")
.HasColumnType("timestamp with time zone"); .HasColumnType("timestamp without time zone");
b.Property<string>("EmbyId") b.Property<string>("EmbyId")
.IsRequired() .IsRequired()
@ -94,7 +94,7 @@ namespace Ombi.Store.Migrations.ExternalPostgres
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id")); NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
b.Property<DateTime>("AddedAt") b.Property<DateTime>("AddedAt")
.HasColumnType("timestamp with time zone"); .HasColumnType("timestamp without time zone");
b.Property<string>("EmbyId") b.Property<string>("EmbyId")
.HasColumnType("text"); .HasColumnType("text");
@ -139,7 +139,7 @@ namespace Ombi.Store.Migrations.ExternalPostgres
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id")); NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
b.Property<DateTime>("AddedAt") b.Property<DateTime>("AddedAt")
.HasColumnType("timestamp with time zone"); .HasColumnType("timestamp without time zone");
b.Property<bool>("Has4K") b.Property<bool>("Has4K")
.HasColumnType("boolean"); .HasColumnType("boolean");
@ -186,7 +186,7 @@ namespace Ombi.Store.Migrations.ExternalPostgres
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id")); NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
b.Property<DateTime>("AddedAt") b.Property<DateTime>("AddedAt")
.HasColumnType("timestamp with time zone"); .HasColumnType("timestamp without time zone");
b.Property<int>("EpisodeNumber") b.Property<int>("EpisodeNumber")
.HasColumnType("integer"); .HasColumnType("integer");
@ -231,7 +231,7 @@ namespace Ombi.Store.Migrations.ExternalPostgres
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id")); NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
b.Property<DateTime>("AddedAt") b.Property<DateTime>("AddedAt")
.HasColumnType("timestamp with time zone"); .HasColumnType("timestamp without time zone");
b.Property<int>("ArtistId") b.Property<int>("ArtistId")
.HasColumnType("integer"); .HasColumnType("integer");
@ -246,7 +246,7 @@ namespace Ombi.Store.Migrations.ExternalPostgres
.HasColumnType("numeric"); .HasColumnType("numeric");
b.Property<DateTime>("ReleaseDate") b.Property<DateTime>("ReleaseDate")
.HasColumnType("timestamp with time zone"); .HasColumnType("timestamp without time zone");
b.Property<string>("Title") b.Property<string>("Title")
.HasColumnType("text"); .HasColumnType("text");
@ -356,7 +356,7 @@ namespace Ombi.Store.Migrations.ExternalPostgres
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id")); NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
b.Property<DateTime>("AddedAt") b.Property<DateTime>("AddedAt")
.HasColumnType("timestamp with time zone"); .HasColumnType("timestamp without time zone");
b.Property<bool>("Has4K") b.Property<bool>("Has4K")
.HasColumnType("boolean"); .HasColumnType("boolean");
@ -405,9 +405,15 @@ namespace Ombi.Store.Migrations.ExternalPostgres
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id")); NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
b.Property<DateTime>("AddedAt")
.HasColumnType("timestamp without time zone");
b.Property<string>("TmdbId") b.Property<string>("TmdbId")
.HasColumnType("text"); .HasColumnType("text");
b.Property<string>("UserId")
.HasColumnType("text");
b.HasKey("Id"); b.HasKey("Id");
b.ToTable("PlexWatchlistHistory"); b.ToTable("PlexWatchlistHistory");

View file

@ -0,0 +1,594 @@
// <auto-generated />
using System;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Migrations;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
using Ombi.Store.Context.Sqlite;
#nullable disable
namespace Ombi.Store.Migrations.ExternalSqlite
{
[DbContext(typeof(ExternalSqliteContext))]
[Migration("20240909070705_WatchListUserId")]
partial class WatchListUserId
{
/// <inheritdoc />
protected override void BuildTargetModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder.HasAnnotation("ProductVersion", "8.0.5");
modelBuilder.Entity("Ombi.Store.Entities.CouchPotatoCache", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<int>("TheMovieDbId")
.HasColumnType("INTEGER");
b.HasKey("Id");
b.ToTable("CouchPotatoCache");
});
modelBuilder.Entity("Ombi.Store.Entities.EmbyContent", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<DateTime>("AddedAt")
.HasColumnType("TEXT");
b.Property<string>("EmbyId")
.IsRequired()
.HasColumnType("TEXT");
b.Property<bool>("Has4K")
.HasColumnType("INTEGER");
b.Property<string>("ImdbId")
.HasColumnType("TEXT");
b.Property<string>("ProviderId")
.HasColumnType("TEXT");
b.Property<string>("Quality")
.HasColumnType("TEXT");
b.Property<string>("TheMovieDbId")
.HasColumnType("TEXT");
b.Property<string>("Title")
.HasColumnType("TEXT");
b.Property<string>("TvDbId")
.HasColumnType("TEXT");
b.Property<int>("Type")
.HasColumnType("INTEGER");
b.Property<string>("Url")
.HasColumnType("TEXT");
b.HasKey("Id");
b.ToTable("EmbyContent");
});
modelBuilder.Entity("Ombi.Store.Entities.EmbyEpisode", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<DateTime>("AddedAt")
.HasColumnType("TEXT");
b.Property<string>("EmbyId")
.HasColumnType("TEXT");
b.Property<int>("EpisodeNumber")
.HasColumnType("INTEGER");
b.Property<string>("ImdbId")
.HasColumnType("TEXT");
b.Property<string>("ParentId")
.HasColumnType("TEXT");
b.Property<string>("ProviderId")
.HasColumnType("TEXT");
b.Property<int>("SeasonNumber")
.HasColumnType("INTEGER");
b.Property<string>("TheMovieDbId")
.HasColumnType("TEXT");
b.Property<string>("Title")
.HasColumnType("TEXT");
b.Property<string>("TvDbId")
.HasColumnType("TEXT");
b.HasKey("Id");
b.HasIndex("ParentId");
b.ToTable("EmbyEpisode");
});
modelBuilder.Entity("Ombi.Store.Entities.JellyfinContent", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<DateTime>("AddedAt")
.HasColumnType("TEXT");
b.Property<bool>("Has4K")
.HasColumnType("INTEGER");
b.Property<string>("ImdbId")
.HasColumnType("TEXT");
b.Property<string>("JellyfinId")
.IsRequired()
.HasColumnType("TEXT");
b.Property<string>("ProviderId")
.HasColumnType("TEXT");
b.Property<string>("Quality")
.HasColumnType("TEXT");
b.Property<string>("TheMovieDbId")
.HasColumnType("TEXT");
b.Property<string>("Title")
.HasColumnType("TEXT");
b.Property<string>("TvDbId")
.HasColumnType("TEXT");
b.Property<int>("Type")
.HasColumnType("INTEGER");
b.Property<string>("Url")
.HasColumnType("TEXT");
b.HasKey("Id");
b.ToTable("JellyfinContent");
});
modelBuilder.Entity("Ombi.Store.Entities.JellyfinEpisode", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<DateTime>("AddedAt")
.HasColumnType("TEXT");
b.Property<int>("EpisodeNumber")
.HasColumnType("INTEGER");
b.Property<string>("ImdbId")
.HasColumnType("TEXT");
b.Property<string>("JellyfinId")
.HasColumnType("TEXT");
b.Property<string>("ParentId")
.HasColumnType("TEXT");
b.Property<string>("ProviderId")
.HasColumnType("TEXT");
b.Property<int>("SeasonNumber")
.HasColumnType("INTEGER");
b.Property<string>("TheMovieDbId")
.HasColumnType("TEXT");
b.Property<string>("Title")
.HasColumnType("TEXT");
b.Property<string>("TvDbId")
.HasColumnType("TEXT");
b.HasKey("Id");
b.HasIndex("ParentId");
b.ToTable("JellyfinEpisode");
});
modelBuilder.Entity("Ombi.Store.Entities.LidarrAlbumCache", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<DateTime>("AddedAt")
.HasColumnType("TEXT");
b.Property<int>("ArtistId")
.HasColumnType("INTEGER");
b.Property<string>("ForeignAlbumId")
.HasColumnType("TEXT");
b.Property<bool>("Monitored")
.HasColumnType("INTEGER");
b.Property<decimal>("PercentOfTracks")
.HasColumnType("TEXT");
b.Property<DateTime>("ReleaseDate")
.HasColumnType("TEXT");
b.Property<string>("Title")
.HasColumnType("TEXT");
b.Property<int>("TrackCount")
.HasColumnType("INTEGER");
b.HasKey("Id");
b.ToTable("LidarrAlbumCache");
});
modelBuilder.Entity("Ombi.Store.Entities.LidarrArtistCache", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<int>("ArtistId")
.HasColumnType("INTEGER");
b.Property<string>("ArtistName")
.HasColumnType("TEXT");
b.Property<string>("ForeignArtistId")
.HasColumnType("TEXT");
b.Property<bool>("Monitored")
.HasColumnType("INTEGER");
b.HasKey("Id");
b.ToTable("LidarrArtistCache");
});
modelBuilder.Entity("Ombi.Store.Entities.PlexEpisode", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<int>("EpisodeNumber")
.HasColumnType("INTEGER");
b.Property<string>("GrandparentKey")
.HasColumnType("TEXT");
b.Property<string>("Key")
.HasColumnType("TEXT");
b.Property<string>("ParentKey")
.HasColumnType("TEXT");
b.Property<int>("SeasonNumber")
.HasColumnType("INTEGER");
b.Property<string>("Title")
.HasColumnType("TEXT");
b.HasKey("Id");
b.HasIndex("GrandparentKey");
b.ToTable("PlexEpisode");
});
modelBuilder.Entity("Ombi.Store.Entities.PlexSeasonsContent", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<string>("ParentKey")
.HasColumnType("TEXT");
b.Property<string>("PlexContentId")
.HasColumnType("TEXT");
b.Property<int?>("PlexServerContentId")
.HasColumnType("INTEGER");
b.Property<string>("SeasonKey")
.HasColumnType("TEXT");
b.Property<int>("SeasonNumber")
.HasColumnType("INTEGER");
b.HasKey("Id");
b.HasIndex("PlexServerContentId");
b.ToTable("PlexSeasonsContent");
});
modelBuilder.Entity("Ombi.Store.Entities.PlexServerContent", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<DateTime>("AddedAt")
.HasColumnType("TEXT");
b.Property<bool>("Has4K")
.HasColumnType("INTEGER");
b.Property<string>("ImdbId")
.HasColumnType("TEXT");
b.Property<string>("Key")
.IsRequired()
.HasColumnType("TEXT");
b.Property<string>("Quality")
.HasColumnType("TEXT");
b.Property<string>("ReleaseYear")
.HasColumnType("TEXT");
b.Property<int?>("RequestId")
.HasColumnType("INTEGER");
b.Property<string>("TheMovieDbId")
.HasColumnType("TEXT");
b.Property<string>("Title")
.HasColumnType("TEXT");
b.Property<string>("TvDbId")
.HasColumnType("TEXT");
b.Property<int>("Type")
.HasColumnType("INTEGER");
b.Property<string>("Url")
.HasColumnType("TEXT");
b.HasKey("Id");
b.ToTable("PlexServerContent");
});
modelBuilder.Entity("Ombi.Store.Entities.PlexWatchlistHistory", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<DateTime>("AddedAt")
.HasColumnType("TEXT");
b.Property<string>("TmdbId")
.HasColumnType("TEXT");
b.Property<string>("UserId")
.HasColumnType("TEXT");
b.HasKey("Id");
b.ToTable("PlexWatchlistHistory");
});
modelBuilder.Entity("Ombi.Store.Entities.RadarrCache", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<bool>("Has4K")
.HasColumnType("INTEGER");
b.Property<bool>("HasFile")
.HasColumnType("INTEGER");
b.Property<bool>("HasRegular")
.HasColumnType("INTEGER");
b.Property<int>("TheMovieDbId")
.HasColumnType("INTEGER");
b.HasKey("Id");
b.ToTable("RadarrCache");
});
modelBuilder.Entity("Ombi.Store.Entities.SickRageCache", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<int>("TvDbId")
.HasColumnType("INTEGER");
b.HasKey("Id");
b.ToTable("SickRageCache");
});
modelBuilder.Entity("Ombi.Store.Entities.SickRageEpisodeCache", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<int>("EpisodeNumber")
.HasColumnType("INTEGER");
b.Property<int>("SeasonNumber")
.HasColumnType("INTEGER");
b.Property<int>("TvDbId")
.HasColumnType("INTEGER");
b.HasKey("Id");
b.ToTable("SickRageEpisodeCache");
});
modelBuilder.Entity("Ombi.Store.Entities.SonarrCache", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<int>("TheMovieDbId")
.HasColumnType("INTEGER");
b.Property<int>("TvDbId")
.HasColumnType("INTEGER");
b.HasKey("Id");
b.ToTable("SonarrCache");
});
modelBuilder.Entity("Ombi.Store.Entities.SonarrEpisodeCache", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<int>("EpisodeNumber")
.HasColumnType("INTEGER");
b.Property<bool>("HasFile")
.HasColumnType("INTEGER");
b.Property<int>("MovieDbId")
.HasColumnType("INTEGER");
b.Property<int>("SeasonNumber")
.HasColumnType("INTEGER");
b.Property<int>("TvDbId")
.HasColumnType("INTEGER");
b.HasKey("Id");
b.ToTable("SonarrEpisodeCache");
});
modelBuilder.Entity("Ombi.Store.Entities.UserPlayedEpisode", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<int>("EpisodeNumber")
.HasColumnType("INTEGER");
b.Property<int>("SeasonNumber")
.HasColumnType("INTEGER");
b.Property<int>("TheMovieDbId")
.HasColumnType("INTEGER");
b.Property<string>("UserId")
.HasColumnType("TEXT");
b.HasKey("Id");
b.ToTable("UserPlayedEpisode");
});
modelBuilder.Entity("Ombi.Store.Entities.UserPlayedMovie", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<int>("TheMovieDbId")
.HasColumnType("INTEGER");
b.Property<string>("UserId")
.HasColumnType("TEXT");
b.HasKey("Id");
b.ToTable("UserPlayedMovie");
});
modelBuilder.Entity("Ombi.Store.Entities.EmbyEpisode", b =>
{
b.HasOne("Ombi.Store.Entities.EmbyContent", "Series")
.WithMany("Episodes")
.HasForeignKey("ParentId")
.HasPrincipalKey("EmbyId");
b.Navigation("Series");
});
modelBuilder.Entity("Ombi.Store.Entities.JellyfinEpisode", b =>
{
b.HasOne("Ombi.Store.Entities.JellyfinContent", "Series")
.WithMany("Episodes")
.HasForeignKey("ParentId")
.HasPrincipalKey("JellyfinId");
b.Navigation("Series");
});
modelBuilder.Entity("Ombi.Store.Entities.PlexEpisode", b =>
{
b.HasOne("Ombi.Store.Entities.PlexServerContent", "Series")
.WithMany("Episodes")
.HasForeignKey("GrandparentKey")
.HasPrincipalKey("Key");
b.Navigation("Series");
});
modelBuilder.Entity("Ombi.Store.Entities.PlexSeasonsContent", b =>
{
b.HasOne("Ombi.Store.Entities.PlexServerContent", null)
.WithMany("Seasons")
.HasForeignKey("PlexServerContentId");
});
modelBuilder.Entity("Ombi.Store.Entities.EmbyContent", b =>
{
b.Navigation("Episodes");
});
modelBuilder.Entity("Ombi.Store.Entities.JellyfinContent", b =>
{
b.Navigation("Episodes");
});
modelBuilder.Entity("Ombi.Store.Entities.PlexServerContent", b =>
{
b.Navigation("Episodes");
b.Navigation("Seasons");
});
#pragma warning restore 612, 618
}
}
}

View file

@ -0,0 +1,40 @@
using System;
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace Ombi.Store.Migrations.ExternalSqlite
{
/// <inheritdoc />
public partial class WatchListUserId : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AddColumn<DateTime>(
name: "AddedAt",
table: "PlexWatchlistHistory",
type: "TEXT",
nullable: false,
defaultValue: new DateTime(1, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified));
migrationBuilder.AddColumn<string>(
name: "UserId",
table: "PlexWatchlistHistory",
type: "TEXT",
nullable: true);
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropColumn(
name: "AddedAt",
table: "PlexWatchlistHistory");
migrationBuilder.DropColumn(
name: "UserId",
table: "PlexWatchlistHistory");
}
}
}

View file

@ -15,7 +15,7 @@ namespace Ombi.Store.Migrations.ExternalSqlite
protected override void BuildModel(ModelBuilder modelBuilder) protected override void BuildModel(ModelBuilder modelBuilder)
{ {
#pragma warning disable 612, 618 #pragma warning disable 612, 618
modelBuilder.HasAnnotation("ProductVersion", "6.0.9"); modelBuilder.HasAnnotation("ProductVersion", "8.0.5");
modelBuilder.Entity("Ombi.Store.Entities.CouchPotatoCache", b => modelBuilder.Entity("Ombi.Store.Entities.CouchPotatoCache", b =>
{ {
@ -378,9 +378,15 @@ namespace Ombi.Store.Migrations.ExternalSqlite
.ValueGeneratedOnAdd() .ValueGeneratedOnAdd()
.HasColumnType("INTEGER"); .HasColumnType("INTEGER");
b.Property<DateTime>("AddedAt")
.HasColumnType("TEXT");
b.Property<string>("TmdbId") b.Property<string>("TmdbId")
.HasColumnType("TEXT"); .HasColumnType("TEXT");
b.Property<string>("UserId")
.HasColumnType("TEXT");
b.HasKey("Id"); b.HasKey("Id");
b.ToTable("PlexWatchlistHistory"); b.ToTable("PlexWatchlistHistory");

View file

@ -19,7 +19,7 @@
</PackageReference> </PackageReference>
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="8.0.5" /> <PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="8.0.5" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.1" /> <PackageReference Include="Newtonsoft.Json" Version="13.0.1" />
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="6.0.22" /> <PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="8.0.0" />
<PackageReference Include="Pomelo.EntityFrameworkCore.MySql" Version="8.0.2" /> <PackageReference Include="Pomelo.EntityFrameworkCore.MySql" Version="8.0.2" />
<PackageReference Include="Nito.AsyncEx" Version="5.1.2" /> <PackageReference Include="Nito.AsyncEx" Version="5.1.2" />
<PackageReference Include="Polly" Version="7.2.3" /> <PackageReference Include="Polly" Version="7.2.3" />

View file

@ -94,7 +94,7 @@ namespace Ombi.Store.Repository
public async Task<PlexServerContent> GetByKey(string key) public async Task<PlexServerContent> GetByKey(string key)
{ {
return await Db.PlexServerContent.Include(x => x.Seasons).FirstOrDefaultAsync(x => x.Key == key); return await Db.PlexServerContent.Include(x => x.Seasons).Include(x => x.Episodes).FirstOrDefaultAsync(x => x.Key == key);
} }
public IEnumerable<PlexServerContent> GetWhereContentByCustom(Expression<Func<PlexServerContent, bool>> predicate) public IEnumerable<PlexServerContent> GetWhereContentByCustom(Expression<Func<PlexServerContent, bool>> predicate)

View file

@ -19,7 +19,7 @@
<PackageReference Include="Microsoft.Extensions.Configuration.FileExtensions" Version="6.0.0" /> <PackageReference Include="Microsoft.Extensions.Configuration.FileExtensions" Version="6.0.0" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="6.0.0" /> <PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="6.0.0" />
<PackageReference Include="Microsoft.Extensions.Logging" Version="6.0.0" /> <PackageReference Include="Microsoft.Extensions.Logging" Version="6.0.0" />
<PackageReference Include="Microsoft.Extensions.Logging.Console" Version="6.0.0" /> <PackageReference Include="Microsoft.Extensions.Logging.Console" Version="8.0.1" />
<PackageReference Include="Microsoft.Extensions.Logging.Debug" Version="6.0.0" /> <PackageReference Include="Microsoft.Extensions.Logging.Debug" Version="6.0.0" />
<PackageReference Include="Serilog" Version="2.12.0" /> <PackageReference Include="Serilog" Version="2.12.0" />
<PackageReference Include="Serilog.Extensions.Logging" Version="3.1.0" /> <PackageReference Include="Serilog.Extensions.Logging" Version="3.1.0" />

View file

@ -105,7 +105,6 @@
"options": { "options": {
"tsConfig": [ "tsConfig": [
"src/tsconfig.json" "src/tsconfig.json"
"src/tsconfig.json"
], ],
"exclude": [ "exclude": [
"**/node_modules/**" "**/node_modules/**"

View file

@ -26,13 +26,13 @@
"@angular/router": "^17.3.11", "@angular/router": "^17.3.11",
"@angularclass/hmr": "^3.0.0", "@angularclass/hmr": "^3.0.0",
"@auth0/angular-jwt": "^5.0.2", "@auth0/angular-jwt": "^5.0.2",
"@fortawesome/fontawesome-free": "^6.5.2", "@fortawesome/fontawesome-free": "^6.6.0",
"@microsoft/signalr": "^6.0.23", "@microsoft/signalr": "^6.0.23",
"@ngx-translate/core": "^14.0.0", "@ngx-translate/core": "^15.0.0",
"@ngx-translate/http-loader": "^7.0.0", "@ngx-translate/http-loader": "^8.0.0",
"@ngxs/devtools-plugin": "3.8.2", "@ngxs/devtools-plugin": "3.8.2",
"@ngxs/store": "3.8.2", "@ngxs/store": "3.8.2",
"@types/jquery": "^3.5.23", "@types/jquery": "^3.5.30",
"@yellowspot/ng-truncate": "^2.0.0", "@yellowspot/ng-truncate": "^2.0.0",
"angularx-qrcode": "^16.0.0", "angularx-qrcode": "^16.0.0",
"bootstrap": "^4.2.1", "bootstrap": "^4.2.1",
@ -47,7 +47,7 @@
"ngx-infinite-scroll": "^17.0.1", "ngx-infinite-scroll": "^17.0.1",
"popper.js": "^1.14.3", "popper.js": "^1.14.3",
"primeicons": "^6.0.1", "primeicons": "^6.0.1",
"primeng": "^17.6.0", "primeng": "^17.11.0",
"rxjs": "^7.5.4", "rxjs": "^7.5.4",
"ts-md5": "^1.2.7", "ts-md5": "^1.2.7",
"zone.js": "0.14.7" "zone.js": "0.14.7"

View file

@ -5,9 +5,13 @@
<mat-button-toggle id="{{id}}Tv" [ngClass]="{'button-active': discoverOptions === DiscoverOption.Tv}" value="{{DiscoverOption.Tv}}" class="discover-filter-button">{{'Discovery.Tv' | translate}}</mat-button-toggle> <mat-button-toggle id="{{id}}Tv" [ngClass]="{'button-active': discoverOptions === DiscoverOption.Tv}" value="{{DiscoverOption.Tv}}" class="discover-filter-button">{{'Discovery.Tv' | translate}}</mat-button-toggle>
</mat-button-toggle-group> </mat-button-toggle-group>
</div> </div>
@defer (when discoverResults.length > 0) {
<p-carousel #carousel [numVisible]="10" [numScroll]="10" [page]="0" [value]="discoverResults" [responsiveOptions]="responsiveOptions" (onPage)="newPage()"> <p-carousel #carousel [numVisible]="10" [numScroll]="10" [page]="0" [value]="discoverResults" [responsiveOptions]="responsiveOptions" (onPage)="newPage()">
<ng-template let-result pTemplate="item"> <ng-template let-result pTemplate="item">
<discover-card [discoverType]="discoverType" [isAdmin]="isAdmin" [result]="result" [is4kEnabled]="is4kEnabled"></discover-card> <discover-card [discoverType]="discoverType" [isAdmin]="isAdmin" [result]="result" [is4kEnabled]="is4kEnabled"></discover-card>
</ng-template> </ng-template>
</p-carousel> </p-carousel>
}
@placeholder(minimum 500) {
<p-skeleton width="100%" height="18rem"></p-skeleton>
}

View file

@ -293,7 +293,6 @@ export class CarouselListComponent implements OnInit {
} }
this.discoverResults.push(...tempResults); this.discoverResults.push(...tempResults);
this.carousel.ngAfterContentInit();
this.finishLoading(); this.finishLoading();
} }

View file

@ -1,12 +1,7 @@
<div class="small-middle-container"> <div class="small-middle-container">
<div class="section"> <div class="section">
@defer (on viewport; prefetch on idle) {
<h2 id="genreHeading" data-toggle="collapse" href="#genreCollapse" role="button">{{ 'Discovery.Genres' | translate }}</h2> <h2 id="genreHeading" data-toggle="collapse" href="#genreCollapse" role="button">{{ 'Discovery.Genres' | translate }}</h2>
<genre-button-select class="collapse show" id="genreCollapse"></genre-button-select> <genre-button-select class="collapse show" id="genreCollapse"></genre-button-select>
}
@placeholder {
<p-skeleton width="100%" height="2rem"></p-skeleton>
}
</div> </div>
<div class="section"> <div class="section">
<h2>{{ 'Discovery.RecentlyRequestedTab' | translate }}</h2> <h2>{{ 'Discovery.RecentlyRequestedTab' | translate }}</h2>

View file

@ -1,13 +1,26 @@
<div class="genre-container" *ngIf="movieGenreList$ | async as movies">
<mat-button-toggle-group name="discoverMode" (change)="toggleChanged($event, genre.type)" *ngFor="let genre of movies" @defer (when movieGenreList()) {
class="discover-filter-buttons-group"> <div class="genre-container">
<mat-button-toggle value="{{genre.id}}" class="discover-filter-button">{{genre.name}}</mat-button-toggle> <mat-button-toggle-group name="discoverMode" (change)="toggleChanged($event, genre.type)" *ngFor="let genre of movieGenreList()"
</mat-button-toggle-group> class="discover-filter-buttons-group">
</div> <mat-button-toggle value="{{genre.id}}" class="discover-filter-button">{{genre.name}}</mat-button-toggle>
<div class="genre-container" *ngIf="tvGenreList$ | async as tv"> </mat-button-toggle-group>
<mat-button-toggle-group name="discoverMode" (change)="toggleChanged($event, genre.type)" *ngFor="let genre of tv" </div>
class="discover-filter-buttons-group"> }
<mat-button-toggle value="{{genre.id}}" class="discover-filter-button">{{genre.name}}</mat-button-toggle> @placeholder(minimum 500) {
</mat-button-toggle-group> <p-skeleton width="100%" height="3.5rem"></p-skeleton>
<mat-spinner *ngIf="isLoading" [diameter]="30"></mat-spinner> }
</div>
@defer (when tvGenreList()) {
<div class="genre-container">
<mat-button-toggle-group name="discoverMode" (change)="toggleChanged($event, genre.type)" *ngFor="let genre of tvGenreList()"
class="discover-filter-buttons-group">
<mat-button-toggle value="{{genre.id}}" class="discover-filter-button">{{genre.name}}</mat-button-toggle>
</mat-button-toggle-group>
</div>
}
@placeholder(minimum 500) {
<p-skeleton width="100%" height="3.5rem"></p-skeleton>
}
<mat-spinner *ngIf="isLoading" [diameter]="30"></mat-spinner>

View file

@ -1,10 +1,11 @@
import { Component, OnInit } from "@angular/core"; import { Component, OnInit, computed, signal } from "@angular/core";
import { SearchV2Service } from "../../../services"; import { SearchV2Service } from "../../../services";
import { MatButtonToggleChange } from "@angular/material/button-toggle"; import { MatButtonToggleChange } from "@angular/material/button-toggle";
import { RequestType } from "../../../interfaces"; import { RequestType } from "../../../interfaces";
import { AdvancedSearchDialogDataService } from "app/shared/advanced-search-dialog/advanced-search-dialog-data.service"; import { AdvancedSearchDialogDataService } from "app/shared/advanced-search-dialog/advanced-search-dialog-data.service";
import { Router } from "@angular/router"; import { Router } from "@angular/router";
import { map, Observable } from "rxjs"; import { map, Observable } from "rxjs";
import { toSignal } from '@angular/core/rxjs-interop';
interface IGenreSelect { interface IGenreSelect {
name: string; name: string;
@ -17,8 +18,10 @@ interface IGenreSelect {
styleUrls: ["./genre-button-select.component.scss"], styleUrls: ["./genre-button-select.component.scss"],
}) })
export class GenreButtonSelectComponent implements OnInit { export class GenreButtonSelectComponent implements OnInit {
public movieGenreList$: Observable<IGenreSelect[]> = null;
public tvGenreList$: Observable<IGenreSelect[]> = null; public movieGenreList = signal<IGenreSelect[]>(null);
public tvGenreList = signal<IGenreSelect[]>(null);
isLoading: boolean = false; isLoading: boolean = false;
@ -27,8 +30,14 @@ export class GenreButtonSelectComponent implements OnInit {
private router: Router) { } private router: Router) { }
public ngOnInit(): void { public ngOnInit(): void {
this.movieGenreList$ = this.searchService.getGenres("movie").pipe(map(x => x.slice(0, 10).map(y => ({ name: y.name, id: y.id, type: "movie" })))); this.searchService.getGenres("movie").pipe(map(x => x.slice(0, 10).map(y => ({ name: y.name, id: y.id, type: "movie" } as IGenreSelect))))
this.tvGenreList$ = this.searchService.getGenres("tv").pipe(map(x => x.slice(0, 10).map(y => ({ name: y.name, id: y.id, type: "tv" })))); .subscribe(x => {
this.movieGenreList.set(x);
});
this.searchService.getGenres("tv").pipe(map(x => x.slice(0, 10).map(y => ({ name: y.name, id: y.id, type: "tv" } as IGenreSelect))))
.subscribe(x => {
this.tvGenreList.set(x);
});
} }
public async toggleChanged(event: MatButtonToggleChange, type: "movie"|"tv") { public async toggleChanged(event: MatButtonToggleChange, type: "movie"|"tv") {

View file

@ -1,9 +1,8 @@
<div *ngIf="requests$ | async as requests"> @defer (when requests()) {
<div *ngIf="requests.length > 0"> <div *ngIf="requests().length > 0">
<p-carousel #carousel [value]="requests" [numVisible]="3" [numScroll]="1" <p-carousel #carousel [value]="requests()" [numVisible]="3" [numScroll]="1"
[responsiveOptions]="responsiveOptions" [page]="0"> [responsiveOptions]="responsiveOptions" [page]="0">
<ng-template let-result pTemplate="item"> <ng-template let-result pTemplate="item">
@defer (on viewport; prefetch on idle) {
<ombi-detailed-card <ombi-detailed-card
[request]="result" [request]="result"
[isAdmin]="isAdmin" [isAdmin]="isAdmin"
@ -11,11 +10,25 @@
(onApprove)="approve(result)" (onApprove)="approve(result)"
(onDeny)="deny(result)"> (onDeny)="deny(result)">
</ombi-detailed-card> </ombi-detailed-card>
}
@placeholder {
<p-skeleton width="100%" height="315px"></p-skeleton>
}
</ng-template> </ng-template>
</p-carousel> </p-carousel>
</div> </div>
</div> }@placeholder(minimum 500) {
<div class="row loading-container">
<div class="col-2">
<p-skeleton width="100%" height="270px"></p-skeleton>
</div>
<div class="col-2">
<p-skeleton width="100%" height="270px"></p-skeleton>
</div>
<div class="col-2">
<p-skeleton width="100%" height="270px"></p-skeleton>
</div>
<div class="col-2">
<p-skeleton width="100%" height="270px"></p-skeleton>
</div>
<div class="col-2">
<p-skeleton width="100%" height="270px"></p-skeleton>
</div>
</div>
}

View file

@ -110,3 +110,7 @@
flex: 1 0 200px !important; flex: 1 0 200px !important;
} }
} }
.loading-container {
margin-left: 10rem;
}

View file

@ -1,4 +1,4 @@
import { Component, OnInit, Input, ViewChild, OnDestroy } from "@angular/core"; import { Component, OnInit, Input, ViewChild, OnDestroy, signal } from "@angular/core";
import { IRecentlyRequested, IRequestEngineResult, RequestType } from "../../../interfaces"; import { IRecentlyRequested, IRequestEngineResult, RequestType } from "../../../interfaces";
import { Carousel } from 'primeng/carousel'; import { Carousel } from 'primeng/carousel';
import { ResponsiveOptions } from "../carousel.options"; import { ResponsiveOptions } from "../carousel.options";
@ -31,6 +31,7 @@ export class RecentlyRequestedListComponent implements OnInit, OnDestroy {
@ViewChild('carousel', {static: false}) carousel: Carousel; @ViewChild('carousel', {static: false}) carousel: Carousel;
public requests$: Observable<IRecentlyRequested[]>; public requests$: Observable<IRecentlyRequested[]>;
public requests = signal<IRecentlyRequested[]>(null);
public responsiveOptions: any; public responsiveOptions: any;
public RequestType = RequestType; public RequestType = RequestType;
@ -119,11 +120,13 @@ export class RecentlyRequestedListComponent implements OnInit, OnDestroy {
} }
private loadData() { private loadData() {
this.requests$ = this.requestServiceV2.getRecentlyRequested().pipe( this.requestServiceV2.getRecentlyRequested().pipe(
tap(() => this.loading()), tap(() => this.loading()),
takeUntil(this.$loadSub), takeUntil(this.$loadSub),
finalize(() => this.finishLoading()) finalize(() => this.finishLoading())
); ).subscribe(x => {
this.requests.set(x);
});
} }
private loading() { private loading() {

View file

@ -25,12 +25,19 @@ export interface IMovieRequests extends IFullBaseRequest {
requestedDate: Date; requestedDate: Date;
watchedByRequestedUser: boolean; watchedByRequestedUser: boolean;
playedByUsersCount: number; playedByUsersCount: number;
requestCombination: RequestCombination;
// For the UI // For the UI
rootPathOverrideTitle: string; rootPathOverrideTitle: string;
qualityOverrideTitle: string; qualityOverrideTitle: string;
} }
export enum RequestCombination {
Normal,
FourK,
Both
}
export interface IMovieAdvancedOptions { export interface IMovieAdvancedOptions {
requestId: number; requestId: number;
qualityOverride: number; qualityOverride: number;

View file

@ -1,6 +1,6 @@
import { Component, Inject, OnInit } from "@angular/core"; import { Component, Inject, OnInit } from "@angular/core";
import { MatDialogRef, MAT_DIALOG_DATA } from "@angular/material/dialog"; import { MatDialogRef, MAT_DIALOG_DATA } from "@angular/material/dialog";
import { IAdvancedData, IRadarrProfile, IRadarrRootFolder } from "../../../../../interfaces"; import { IAdvancedData, IRadarrProfile, IRadarrRootFolder, RequestCombination } from "../../../../../interfaces";
import { RadarrService } from "../../../../../services"; import { RadarrService } from "../../../../../services";
@Component({ @Component({
@ -11,6 +11,8 @@ export class MovieAdvancedOptionsComponent implements OnInit {
public radarrProfiles: IRadarrProfile[]; public radarrProfiles: IRadarrProfile[];
public radarrRootFolders: IRadarrRootFolder[]; public radarrRootFolders: IRadarrRootFolder[];
public show4k: boolean = false;
public showNormal: boolean = false;
constructor(public dialogRef: MatDialogRef<MovieAdvancedOptionsComponent>, @Inject(MAT_DIALOG_DATA) public data: IAdvancedData, constructor(public dialogRef: MatDialogRef<MovieAdvancedOptionsComponent>, @Inject(MAT_DIALOG_DATA) public data: IAdvancedData,
private radarrService: RadarrService private radarrService: RadarrService
@ -19,16 +21,31 @@ export class MovieAdvancedOptionsComponent implements OnInit {
public async ngOnInit() { public async ngOnInit() {
this.radarrService.getQualityProfilesFromSettings().subscribe(c => { this.show4k = this.data.movieRequest.requestCombination === RequestCombination.FourK || this.data.movieRequest.requestCombination === RequestCombination.Both;
this.radarrProfiles = c; this.showNormal = this.data.movieRequest.requestCombination === RequestCombination.Normal || this.data.movieRequest.requestCombination === RequestCombination.Both;
this.data.profiles = c; if (this.show4k) {
this.setQualityOverrides(); this.radarrService.getQualityProfiles4kFromSettings().subscribe(c => {
}); this.radarrProfiles = c;
this.radarrService.getRootFoldersFromSettings().subscribe(c => { this.data.profiles = c;
this.radarrRootFolders = c; this.setQualityOverrides();
this.data.rootFolders = c; });
this.setRootFolderOverrides(); this.radarrService.getRootFolders4kFromSettings().subscribe(c => {
}); this.radarrRootFolders = c;
this.data.rootFolders = c;
this.setRootFolderOverrides();
});
} else { // Currently show either 4k or normal, if it's a dual request there needs to be more work done to save the overrides for 4k separately
this.radarrService.getQualityProfilesFromSettings().subscribe(c => {
this.radarrProfiles = c;
this.data.profiles = c;
this.setQualityOverrides();
});
this.radarrService.getRootFoldersFromSettings().subscribe(c => {
this.radarrRootFolders = c;
this.data.rootFolders = c;
this.setRootFolderOverrides();
});
}
} }
private setQualityOverrides(): void { private setQualityOverrides(): void {

View file

@ -28,10 +28,12 @@
<mat-form-field appearance="outline" > <mat-form-field appearance="outline" >
<mat-label>Hostname or IP</mat-label> <mat-label>Hostname or IP</mat-label>
<input matInput formControlName="ip"> <input matInput formControlName="ip">
<mat-error>Please enter a valid hostname or ip address</mat-error>
</mat-form-field> </mat-form-field>
<mat-form-field appearance="outline" > <mat-form-field appearance="outline" >
<mat-label>Port</mat-label> <mat-label>Port</mat-label>
<input matInput formControlName="port"> <input matInput formControlName="port">
<mat-error>Please enter a valid port number</mat-error>
</mat-form-field> </mat-form-field>
<mat-slide-toggle formControlName="ssl"> <mat-slide-toggle formControlName="ssl">
SSL SSL
@ -42,6 +44,7 @@
<mat-form-field appearance="outline" > <mat-form-field appearance="outline" >
<mat-label>API key</mat-label> <mat-label>API key</mat-label>
<input matInput formControlName="apiKey"> <input matInput formControlName="apiKey">
<mat-error>Please enter an API Key</mat-error>
</mat-form-field> </mat-form-field>
</div> </div>
<div class="md-form-field"> <div class="md-form-field">
@ -65,6 +68,7 @@
{{quality.name}} {{quality.name}}
</mat-option> </mat-option>
</mat-select> </mat-select>
<mat-error>Please select a value</mat-error>
</mat-form-field> </mat-form-field>
</div> </div>
@ -80,6 +84,7 @@
{{folder.path}} {{folder.path}}
</mat-option> </mat-option>
</mat-select> </mat-select>
<mat-error>Please select a value</mat-error>
</mat-form-field> </mat-form-field>
</div> </div>
@ -95,6 +100,7 @@
{{tag.label}} {{tag.label}}
</mat-option> </mat-option>
</mat-select> </mat-select>
<mat-error>Please select a value</mat-error>
</mat-form-field> </mat-form-field>
</div> </div>
@ -107,6 +113,7 @@
{{min.name}} {{min.name}}
</mat-option> </mat-option>
</mat-select> </mat-select>
<mat-error>Please select a value</mat-error>
</mat-form-field> </mat-form-field>
</div> </div>
</div> </div>

View file

@ -63,14 +63,14 @@ export class RadarrFormComponent implements OnInit {
} }
public toggleValidators() { public toggleValidators() {
debugger;
const enabled = this.form.controls.enabled.value as boolean; const enabled = this.form.controls.enabled.value as boolean;
this.form.controls.apiKey.setValidators(enabled ? [Validators.required] : null); this.form.controls.apiKey.setValidators(enabled ? [Validators.required] : null);
this.form.controls.defaultQualityProfile.setValidators(enabled ? [Validators.required] : null); this.form.controls.defaultQualityProfile.setValidators(enabled ? [Validators.required, Validators.min(1)] : null);
this.form.controls.defaultRootPath.setValidators(enabled ? [Validators.required] : null); this.form.controls.defaultRootPath.setValidators(enabled ? [Validators.required] : null);
this.form.controls.ip.setValidators(enabled ? [Validators.required] : null); this.form.controls.ip.setValidators(enabled ? [Validators.required] : null);
this.form.controls.port.setValidators(enabled ? [Validators.required] : null); this.form.controls.port.setValidators(enabled ? [Validators.required, Validators.min(1)] : null);
this.form.controls.minimumAvailability.setValidators(enabled ? [Validators.required] : null); this.form.controls.minimumAvailability.setValidators(enabled ? [Validators.required] : null);
enabled ? this.form.markAllAsTouched() : this.form.markAsUntouched();
} }
public getProfiles(form: UntypedFormGroup) { public getProfiles(form: UntypedFormGroup) {
@ -120,6 +120,7 @@ export class RadarrFormComponent implements OnInit {
this.notificationService.success("Successfully connected to Radarr!"); this.notificationService.success("Successfully connected to Radarr!");
} else if (result.expectedSubDir) { } else if (result.expectedSubDir) {
this.notificationService.error("Your Radarr Base URL must be set to " + result.expectedSubDir); this.notificationService.error("Your Radarr Base URL must be set to " + result.expectedSubDir);
form.controls.subDir.setValue(result.expectedSubDir);
} else { } else {
this.notificationService.error("We could not connect to Radarr!"); this.notificationService.error("We could not connect to Radarr!");
} }

View file

@ -1,5 +1,5 @@
<settings-menu></settings-menu> <settings-menu></settings-menu>
<div *ngIf="form" class="small-middle-container"> <div *ngIf="form$ | async as form" class="small-middle-container">
<fieldset> <fieldset>
<legend>Radarr Settings</legend> <legend>Radarr Settings</legend>
<div class="md-form-field" style="margin-top:1em;"></div> <div class="md-form-field" style="margin-top:1em;"></div>

View file

@ -1,4 +1,4 @@
import { Component, OnInit, QueryList, ViewChildren } from "@angular/core"; import { Component, OnDestroy, OnInit, QueryList, ViewChildren } from "@angular/core";
import { UntypedFormBuilder, UntypedFormGroup } from "@angular/forms"; import { UntypedFormBuilder, UntypedFormGroup } from "@angular/forms";
import { RadarrFacade } from "app/state/radarr"; import { RadarrFacade } from "app/state/radarr";
@ -6,36 +6,41 @@ import { IMinimumAvailability, IRadarrCombined, IRadarrProfile, IRadarrRootFolde
import { NotificationService } from "../../services"; import { NotificationService } from "../../services";
import { FeaturesFacade } from "../../state/features/features.facade"; import { FeaturesFacade } from "../../state/features/features.facade";
import { RadarrFormComponent } from "./components/radarr-form.component"; import { RadarrFormComponent } from "./components/radarr-form.component";
import { Observable, ReplaySubject, Subject, combineLatest, map, switchMap, takeUntil, tap } from "rxjs";
@Component({ @Component({
templateUrl: "./radarr.component.html", templateUrl: "./radarr.component.html",
styleUrls: ["./radarr.component.scss"] styleUrls: ["./radarr.component.scss"]
}) })
export class RadarrComponent implements OnInit { export class RadarrComponent implements OnInit, OnDestroy {
public qualities: IRadarrProfile[]; public qualities: IRadarrProfile[];
public rootFolders: IRadarrRootFolder[]; public rootFolders: IRadarrRootFolder[];
public minimumAvailabilityOptions: IMinimumAvailability[]; public minimumAvailabilityOptions: IMinimumAvailability[];
public profilesRunning: boolean; public profilesRunning: boolean;
public rootFoldersRunning: boolean; public rootFoldersRunning: boolean;
public form: UntypedFormGroup;
public is4kEnabled: boolean = false; public is4kEnabled: boolean = false;
@ViewChildren('4kForm') public form4k: QueryList<RadarrFormComponent>; public readonly form$: Observable<UntypedFormGroup>;
@ViewChildren('normalForm') public normalForm: QueryList<RadarrFormComponent>;
constructor(private radarrFacade: RadarrFacade, private readonly form4k$: ReplaySubject<QueryList<RadarrFormComponent>>;
private notificationService: NotificationService, private readonly normalForm$: ReplaySubject<QueryList<RadarrFormComponent>>;
private featureFacade: FeaturesFacade, private readonly destroyed$: Subject<void>;
private fb: UntypedFormBuilder) { }
constructor(
private readonly radarrFacade: RadarrFacade,
private readonly notificationService: NotificationService,
private readonly featureFacade: FeaturesFacade,
readonly fb: UntypedFormBuilder
) {
this.form4k$ = new ReplaySubject();
this.normalForm$ = new ReplaySubject();
this.destroyed$ = new Subject();
public ngOnInit() { this.form$ = radarrFacade.state$()
this.is4kEnabled = this.featureFacade.is4kEnabled(); .pipe(
this.radarrFacade.state$() map(x => fb.group({
.subscribe(x => { radarr: fb.group({
this.form = this.fb.group({
radarr: this.fb.group({
enabled: [x.settings.radarr.enabled], enabled: [x.settings.radarr.enabled],
apiKey: [x.settings.radarr.apiKey], apiKey: [x.settings.radarr.apiKey],
defaultQualityProfile: [+x.settings.radarr.defaultQualityProfile], defaultQualityProfile: [+x.settings.radarr.defaultQualityProfile],
@ -50,7 +55,7 @@ export class RadarrComponent implements OnInit {
minimumAvailability: [x.settings.radarr.minimumAvailability], minimumAvailability: [x.settings.radarr.minimumAvailability],
scanForAvailability: [x.settings.radarr.scanForAvailability] scanForAvailability: [x.settings.radarr.scanForAvailability]
}), }),
radarr4K: this.fb.group({ radarr4K: fb.group({
enabled: [x.settings.radarr4K.enabled], enabled: [x.settings.radarr4K.enabled],
apiKey: [x.settings.radarr4K.apiKey], apiKey: [x.settings.radarr4K.apiKey],
defaultQualityProfile: [+x.settings.radarr4K.defaultQualityProfile], defaultQualityProfile: [+x.settings.radarr4K.defaultQualityProfile],
@ -65,19 +70,44 @@ export class RadarrComponent implements OnInit {
minimumAvailability: [x.settings.radarr4K.minimumAvailability], minimumAvailability: [x.settings.radarr4K.minimumAvailability],
scanForAvailability: [x.settings.radarr4K.scanForAvailability] scanForAvailability: [x.settings.radarr4K.scanForAvailability]
}), }),
});
this.normalForm.changes.forEach((comp => {
comp.first.toggleValidators();
})) }))
if (this.is4kEnabled) { )
this.form4k.changes.forEach((comp => {
comp.first.toggleValidators();
}))
}
});
} }
@ViewChildren('4kForm')
protected set form4k(component: QueryList<RadarrFormComponent>) {
this.form4k$.next(component);
}
@ViewChildren('normalForm')
protected set normalForm(component: QueryList<RadarrFormComponent>) {
this.normalForm$.next(component);
}
public ngOnInit() {
this.is4kEnabled = this.featureFacade.is4kEnabled();
combineLatest([this.form$, this.normalForm$])
.pipe(
switchMap(([, normalForm]) => normalForm.changes),
tap(comp => comp.first.toggleValidators()),
takeUntil(this.destroyed$)
).subscribe();
if (this.is4kEnabled) {
combineLatest([this.form$, this.form4k$])
.pipe(
switchMap(([, form4k]) => form4k.changes),
tap(comp => comp.first.toggleValidators()),
takeUntil(this.destroyed$)
).subscribe();
}
}
public ngOnDestroy(): void {
this.destroyed$.next();
this.destroyed$.complete();
}
public onSubmit(form: UntypedFormGroup) { public onSubmit(form: UntypedFormGroup) {
if (form.invalid) { if (form.invalid) {

View file

@ -18,7 +18,7 @@ export class RadarrSettingsState {
@Action(LoadSettings) @Action(LoadSettings)
public load({ setState }: StateContext<RadarrState>): Observable<RadarrState> { public load({ setState }: StateContext<RadarrState>): Observable<RadarrState> {
const isAdmin = this.authService.hasRole("Admin"); const isAdmin = this.authService.isAdmin();
const calls = isAdmin ? [this.settingsService.getRadarr()] : [of({})]; const calls = isAdmin ? [this.settingsService.getRadarr()] : [of({})];
return combineLatest(calls).pipe( return combineLatest(calls).pipe(

View file

@ -18,7 +18,7 @@ export class SonarrSettingsState {
@Action(LoadSettings) @Action(LoadSettings)
public load({ setState }: StateContext<SonarrState>): Observable<SonarrState> { public load({ setState }: StateContext<SonarrState>): Observable<SonarrState> {
const isAdmin = this.authService.hasRole("Admin"); const isAdmin = this.authService.isAdmin();
const calls = isAdmin ? [this.sonarrService.getVersion(), this.settingsService.getSonarr()] : [of(""), of({})]; const calls = isAdmin ? [this.sonarrService.getVersion(), this.settingsService.getSonarr()] : [of(""), of({})];
return combineLatest(calls).pipe( return combineLatest(calls).pipe(

View file

@ -0,0 +1,104 @@
<div class="mediaserver-container">
<div class="left-container mediaserver">
<i class="fa fa-database text-logo"></i>
</div>
<div class="right-container mediaserver">
<div class="right-container-content mediaserver">
<h1>Choose a Database</h1>
<h4>
SQLite is the default option and the easiest to set up, as it requires no additional configuration.
<br>However, it has significant limitations, including potential performance issues and database locking.
<br>While many users start with SQLite and later migrate to MySQL or MariaDB, we <b>recommend</b> beginning with MySQL or MariaDB from the start for a more robust and scalable experience.
<br/>
<br/>
For more information on using alternate databases, <a target="_blank" href="https://docs.ombi.app/info/alternate-databases/">see the documentation.</a>
</h4>
<form [formGroup]="form">
<mat-tab-group (selectedTabChange)="tabChange($event)">
<mat-tab label="SQLite">
<p class="space-or">
Just press next to continue with SQLite
</p>
</mat-tab>
<mat-tab label="MySQL/MariaDB">
<p class="space-or">
Please enter your MySQL/MariaDB connection details below
</p>
<div>
<mat-form-field>
<input matInput type="text" formControlName="host" id="host" placeholder="Host">
<mat-error>This field is required</mat-error>
</mat-form-field>
</div>
<div>
<mat-form-field>
<input matInput type="number" formControlName="port" id="port" placeholder="Port">
<mat-error>This field is required</mat-error>
</mat-form-field>
</div>
<div>
<mat-form-field>
<input matInput type="text" formControlName="name" id="database" placeholder="Database Name">
<mat-error>This field is required</mat-error>
</mat-form-field>
</div>
<div>
<mat-form-field>
<input matInput type="text" formControlName="user" id="user" placeholder="User">
</mat-form-field>
</div>
<div>
<mat-form-field>
<input matInput type="password" formControlName="password" id="password" placeholder="Password">
</mat-form-field>
</div>
<p>{{connectionString | async}}</p>
<div style="text-align: center; margin-top: 20px">
<button (click)="save()" id="databaseSave" mat-raised-button color="accent" type="button" class="viewon-btn database" [disabled]="form.invalid">Save</button>
<div id="spinner"></div>
</div>
</mat-tab>
<mat-tab label="Postgres">
<p class="space-or">
Please enter your Postgres connection details below
</p>
<div>
<mat-form-field>
<input matInput type="text" formControlName="host" id="host" placeholder="Host">
<mat-error>This field is required</mat-error>
</mat-form-field>
</div>
<div>
<mat-form-field>
<input matInput type="number" formControlName="port" id="port" placeholder="Port">
<mat-error>This field is required</mat-error>
</mat-form-field>
</div>
<div>
<mat-form-field>
<input matInput type="text" formControlName="name" id="database" placeholder="Database Name">
<mat-error>This field is required</mat-error>
</mat-form-field>
</div>
<div>
<mat-form-field>
<input matInput type="text" formControlName="user" id="user" placeholder="User">
</mat-form-field>
</div>
<div>
<mat-form-field>
<input matInput type="password" formControlName="password" id="password" placeholder="Password">
</mat-form-field>
</div>
<p>{{connectionString | async}}</p>
<div style="text-align: center; margin-top: 20px">
<button (click)="save()" id="databaseSave" mat-raised-button color="accent" type="button" class="viewon-btn database" [disabled]="form.invalid">Save</button>
<div id="spinner"></div>
</div>
</mat-tab>
</mat-tab-group>
</form>
</div>
</div>
</div>

View file

@ -0,0 +1,92 @@
import { Component, EventEmitter, OnInit, Output } from "@angular/core";
import { FormBuilder, FormGroup, Validators } from "@angular/forms";
import { BehaviorSubject } from "rxjs";
import { WizardService } from "../services/wizard.service";
import { NotificationService } from "app/services";
import { MatTabChangeEvent } from "@angular/material/tabs";
@Component({
templateUrl: "./database.component.html",
styleUrls: ["../welcome/welcome.component.scss"],
selector: "wizard-database-selector",
})
export class DatabaseComponent implements OnInit {
public constructor(private fb: FormBuilder, private service: WizardService, private notification: NotificationService) { }
@Output() public configuredDatabase = new EventEmitter<void>();
public form: FormGroup;
public connectionString = new BehaviorSubject<string>("Server=;Port=3306;Database=ombi");
public ngOnInit(): void {
this.form = this.fb.group({
type: [""],
host: ["", [Validators.required]],
port: [3306, [Validators.required]],
name: ["ombi", [Validators.required]],
user: [""],
password: [""],
});
this.form.valueChanges.subscribe(x => {
console.log(x);
let connection = `Server=${x.host};Port=${x.port};Database=${x.name}`;
if (x.user) {
connection += `;User=${x.user}`;
if (x.password) {
connection += `;Password=*******`;
}
}
if (x.type !== "MySQL") {
connection = connection.replace("Server", "Host").replace("User", "Username");
}
this.connectionString.next(connection);
});
}
public tabChange(event: MatTabChangeEvent) {
if (event.index === 0) {
this.form.reset();
}
if (event.index === 1) {
this.form.reset({
type: "MySQL",
host: "",
name: "ombi",
port: 3306,
});
this.form.controls.type.setValue("MySQL");
}
if (event.index === 2) {
this.form.reset({
type:"Postgres",
host: "",
name: "ombi",
port: 5432,
});
}
this.form.markAllAsTouched();
}
public save() {
this.service.addDatabaseConfig(this.form.value).subscribe({
next: () => {
this.notification.success(`Database configuration updated! Please now restart Ombi!`);
this.configuredDatabase.emit();
},
error: error => {
if (error.error.message) {
this.notification.error(error.error.message);
} else {
this.notification.error("Something went wrong, please check the logs");
}
},
});
}
}

View file

@ -0,0 +1,13 @@
export interface DatabaseSettings {
type: string;
host: string;
port: number;
name: string;
user: string;
password: string;
}
export interface DatabaseConfigurationResult {
success: boolean;
message: string;
}

View file

@ -5,6 +5,7 @@ import { Observable } from "rxjs";
import { ICustomizationSettings } from "../../interfaces"; import { ICustomizationSettings } from "../../interfaces";
import { ServiceHelpers } from "../../services"; import { ServiceHelpers } from "../../services";
import { IOmbiConfigModel } from "../models/OmbiConfigModel"; import { IOmbiConfigModel } from "../models/OmbiConfigModel";
import { DatabaseConfigurationResult, DatabaseSettings } from "../models/DatabaseSettings";
@Injectable() @Injectable()
@ -16,4 +17,8 @@ export class WizardService extends ServiceHelpers {
public addOmbiConfig(config: IOmbiConfigModel): Observable<ICustomizationSettings> { public addOmbiConfig(config: IOmbiConfigModel): Observable<ICustomizationSettings> {
return this.http.post<ICustomizationSettings>(`${this.url}config`, config, {headers: this.headers}); return this.http.post<ICustomizationSettings>(`${this.url}config`, config, {headers: this.headers});
} }
public addDatabaseConfig(config: DatabaseSettings): Observable<DatabaseConfigurationResult> {
return this.http.post<DatabaseConfigurationResult>(`${this.url}database`, config, {headers: this.headers});
}
} }

View file

@ -1,6 +1,7 @@
<div class="wizard-background"> <div class="wizard-background">
<div class="container wizard-inner"> <div class="container wizard-inner">
<mat-stepper linear #stepper> @if (!needsRestart) {
<mat-stepper linear #stepper>
<mat-step > <mat-step >
<form > <form >
<ng-template matStepLabel>Welcome</ng-template> <ng-template matStepLabel>Welcome</ng-template>
@ -29,6 +30,12 @@
</div> </div>
</form> </form>
</mat-step> </mat-step>
<mat-step>
<ng-template matStepLabel>Database</ng-template>
<wizard-database-selector (configuredDatabase)="databaseConfigured()"></wizard-database-selector>
<button mat-button matStepperPrevious class="mat-raised-button mat-error left">Back</button>
<button mat-button matStepperNext class="mat-raised-button mat-accent right" data-test="nextDatabase">Next</button>
</mat-step>
<mat-step [optional]="true"> <mat-step [optional]="true">
<form > <form >
@ -82,5 +89,22 @@
</div> </div>
</mat-step> </mat-step>
</mat-stepper> </mat-stepper>
} @else {
<mat-stepper linear>
<mat-step >
<ng-template matStepLabel>Restart</ng-template>
<div class="welcome-container">
<div class="left-container mediaserver">
<i class="fa fa-database text-logo"></i>
</div>
<div class="right-container">
<div class="right-container-content">
<h1>Please Restart Ombi for the database changes to take effect!</h1>
</div>
</div>
</div>
</mat-step>
</mat-stepper>
}
</div> </div>
</div> </div>

View file

@ -151,6 +151,12 @@ p.space-or{
color: #A45FC4; color: #A45FC4;
} }
.viewon-btn.database {
border: 1px solid #A45FC4;
color: #A45FC4;
}
.text-logo{ .text-logo{
font-size:12em; font-size:12em;
} }

View file

@ -17,6 +17,7 @@ export class WelcomeComponent implements OnInit {
@ViewChild('stepper', {static: false}) public stepper: MatStepper; @ViewChild('stepper', {static: false}) public stepper: MatStepper;
public localUser: ICreateWizardUser; public localUser: ICreateWizardUser;
public needsRestart: boolean = false;
public config: IOmbiConfigModel; public config: IOmbiConfigModel;
constructor(private router: Router, private identityService: IdentityService, constructor(private router: Router, private identityService: IdentityService,
@ -48,7 +49,7 @@ export class WelcomeComponent implements OnInit {
this.settingsService.verifyUrl(this.config.applicationUrl).subscribe(x => { this.settingsService.verifyUrl(this.config.applicationUrl).subscribe(x => {
if (!x) { if (!x) {
this.notificationService.error(`The URL "${this.config.applicationUrl}" is not valid. Please format it correctly e.g. http://www.google.com/`); this.notificationService.error(`The URL "${this.config.applicationUrl}" is not valid. Please format it correctly e.g. http://www.google.com/`);
this.stepper.selectedIndex = 3; this.stepper.selectedIndex = 4;
return; return;
} }
this.saveConfig(); this.saveConfig();
@ -58,6 +59,10 @@ export class WelcomeComponent implements OnInit {
} }
} }
public databaseConfigured() {
this.needsRestart = true;
}
private saveConfig() { private saveConfig() {
this.WizardService.addOmbiConfig(this.config).subscribe({ this.WizardService.addOmbiConfig(this.config).subscribe({
next: (config) => { next: (config) => {

View file

@ -12,6 +12,7 @@ import { MediaServerComponent } from "./mediaserver/mediaserver.component";
import { PlexComponent } from "./plex/plex.component"; import { PlexComponent } from "./plex/plex.component";
import { WelcomeComponent } from "./welcome/welcome.component"; import { WelcomeComponent } from "./welcome/welcome.component";
import { OmbiConfigComponent } from "./ombiconfig/ombiconfig.component"; import { OmbiConfigComponent } from "./ombiconfig/ombiconfig.component";
import { DatabaseComponent } from "./database/database.component";
import { EmbyService } from "../services"; import { EmbyService } from "../services";
import { JellyfinService } from "../services"; import { JellyfinService } from "../services";
@ -48,6 +49,7 @@ const routes: Routes = [
EmbyComponent, EmbyComponent,
JellyfinComponent, JellyfinComponent,
OmbiConfigComponent, OmbiConfigComponent,
DatabaseComponent,
], ],
exports: [ exports: [
RouterModule, RouterModule,

File diff suppressed because it is too large Load diff

View file

@ -3,7 +3,6 @@ using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Net; using System.Net;
using System.Threading.Tasks; using System.Threading.Tasks;
using AutoMapper;
using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Identity; using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
@ -14,7 +13,6 @@ using Ombi.Api.TheMovieDb.Models;
using Ombi.Attributes; using Ombi.Attributes;
using Ombi.Core.Authentication; using Ombi.Core.Authentication;
using Ombi.Core.Engine; using Ombi.Core.Engine;
using Ombi.Core.Engine.Interfaces;
using Ombi.Core.Helpers; using Ombi.Core.Helpers;
using Ombi.Core.Models.UI; using Ombi.Core.Models.UI;
using Ombi.Core.Services; using Ombi.Core.Services;
@ -29,9 +27,7 @@ using Ombi.Schedule.Jobs.Ombi;
using Ombi.Settings.Settings.Models; using Ombi.Settings.Settings.Models;
using Ombi.Settings.Settings.Models.Notifications; using Ombi.Settings.Settings.Models.Notifications;
using Ombi.Store.Entities; using Ombi.Store.Entities;
using Ombi.Store.Entities.Requests;
using Ombi.Store.Repository; using Ombi.Store.Repository;
using Ombi.Store.Repository.Requests;
using IdentityResult = Microsoft.AspNetCore.Identity.IdentityResult; using IdentityResult = Microsoft.AspNetCore.Identity.IdentityResult;
using OmbiIdentityResult = Ombi.Models.Identity.IdentityResult; using OmbiIdentityResult = Ombi.Models.Identity.IdentityResult;

View file

@ -1,4 +1,6 @@
using Microsoft.AspNetCore.Authorization; using System;
using System.Threading;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using Ombi.Attributes; using Ombi.Attributes;
using Ombi.Core.Settings; using Ombi.Core.Settings;
@ -6,6 +8,10 @@ using Ombi.Helpers;
using Ombi.Models.V2; using Ombi.Models.V2;
using Ombi.Settings.Settings.Models; using Ombi.Settings.Settings.Models;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.Extensions.Logging;
using MySqlConnector;
using Npgsql;
using Ombi.Core.Services;
namespace Ombi.Controllers.V2 namespace Ombi.Controllers.V2
{ {
@ -13,15 +19,25 @@ namespace Ombi.Controllers.V2
[AllowAnonymous] [AllowAnonymous]
public class WizardController : V2Controller public class WizardController : V2Controller
{ {
private readonly ISettingsService<OmbiSettings> _ombiSettings;
private readonly IDatabaseConfigurationService _databaseConfigurationService;
private readonly ILogger _logger;
private ISettingsService<CustomizationSettings> _customizationSettings { get; } private ISettingsService<CustomizationSettings> _customizationSettings { get; }
public WizardController(ISettingsService<CustomizationSettings> customizationSettings) public WizardController(
ISettingsService<CustomizationSettings> customizationSettings,
ISettingsService<OmbiSettings> ombiSettings,
IDatabaseConfigurationService databaseConfigurationService,
ILogger<WizardController> logger)
{ {
_ombiSettings = ombiSettings;
_databaseConfigurationService = databaseConfigurationService;
_logger = logger;
_customizationSettings = customizationSettings; _customizationSettings = customizationSettings;
} }
[HttpPost("config")] [HttpPost("config")]
[ApiExplorerSettings(IgnoreApi =true)] [ApiExplorerSettings(IgnoreApi = true)]
public async Task<IActionResult> OmbiConfig([FromBody] OmbiConfigModel config) public async Task<IActionResult> OmbiConfig([FromBody] OmbiConfigModel config)
{ {
if (config == null) if (config == null)
@ -29,6 +45,13 @@ namespace Ombi.Controllers.V2
return BadRequest(); return BadRequest();
} }
var ombiSettings = await _ombiSettings.GetSettingsAsync();
if (ombiSettings.Wizard)
{
_logger.LogError("Wizard has already been completed");
return BadRequest();
}
var settings = await _customizationSettings.GetSettingsAsync(); var settings = await _customizationSettings.GetSettingsAsync();
if (config.ApplicationName.HasValue()) if (config.ApplicationName.HasValue())
@ -50,5 +73,67 @@ namespace Ombi.Controllers.V2
return new OkObjectResult(settings); return new OkObjectResult(settings);
} }
[HttpPost("database")]
[ApiExplorerSettings(IgnoreApi = true)]
public async Task<IActionResult> DatabaseConfig([FromBody] WizardDatabaseConfiguration config, CancellationToken token)
{
if (config == null)
{
return BadRequest();
}
var ombiSettings = await _ombiSettings.GetSettingsAsync();
if (ombiSettings.Wizard)
{
_logger.LogError("Wizard has already been completed");
return BadRequest();
}
var sanitizedType = config.Type.Replace(Environment.NewLine, "").Replace("\n", "").Replace("\r", "");
_logger.LogInformation("Setting up database type: {0}", sanitizedType);
var connectionString = string.Empty;
if (config.Type == IDatabaseConfigurationService.MySqlDatabase)
{
_logger.LogInformation("Building MySQL connectionstring");
var builder = new MySqlConnectionStringBuilder
{
Database = config.Name,
Port = Convert.ToUInt32(config.Port),
Server = config.Host,
UserID = config.User,
Password = config.Password
};
connectionString = builder.ToString();
}
if (config.Type == IDatabaseConfigurationService.PostgresDatabase)
{
_logger.LogInformation("Building Postgres connectionstring");
var builder = new NpgsqlConnectionStringBuilder
{
Host = config.Host,
Port = config.Port,
Database = config.Name,
Username = config.User,
Password = config.Password
};
connectionString = builder.ToString();
}
var result = await _databaseConfigurationService.ConfigureDatabase(config.Type, connectionString, token);
if (!result)
{
return BadRequest(new DatabaseConfigurationResult(false, "Could not configure the database, please check the logs"));
}
return Ok(new DatabaseConfigurationResult(true, "Database configured successfully"));
}
public record DatabaseConfigurationResult(bool Success, string Message);
} }
} }

View file

@ -8,6 +8,8 @@ using Microsoft.Extensions.Diagnostics.HealthChecks;
using MySqlConnector; using MySqlConnector;
using Newtonsoft.Json; using Newtonsoft.Json;
using Npgsql.EntityFrameworkCore.PostgreSQL.Storage.Internal; using Npgsql.EntityFrameworkCore.PostgreSQL.Storage.Internal;
using Ombi.Core.Helpers;
using Ombi.Core.Models;
using Ombi.Helpers; using Ombi.Helpers;
using Ombi.Store.Context; using Ombi.Store.Context;
using Ombi.Store.Context.MySql; using Ombi.Store.Context.MySql;
@ -38,11 +40,11 @@ namespace Ombi.Extensions
AddSqliteHealthCheck(hcBuilder, "Ombi Database", configuration.OmbiDatabase); AddSqliteHealthCheck(hcBuilder, "Ombi Database", configuration.OmbiDatabase);
break; break;
case var type when type.Equals(MySqlDatabase, StringComparison.InvariantCultureIgnoreCase): case var type when type.Equals(MySqlDatabase, StringComparison.InvariantCultureIgnoreCase):
services.AddDbContext<OmbiContext, OmbiMySqlContext>(x => ConfigureMySql(x, configuration.OmbiDatabase)); services.AddDbContext<OmbiContext, OmbiMySqlContext>(x => DatabaseConfigurationSetup.ConfigureMySql(x, configuration.OmbiDatabase));
AddMySqlHealthCheck(hcBuilder, "Ombi Database", configuration.OmbiDatabase); AddMySqlHealthCheck(hcBuilder, "Ombi Database", configuration.OmbiDatabase);
break; break;
case var type when type.Equals(PostgresDatabase, StringComparison.InvariantCultureIgnoreCase): case var type when type.Equals(PostgresDatabase, StringComparison.InvariantCultureIgnoreCase):
services.AddDbContext<OmbiContext, OmbiPostgresContext>(x => ConfigurePostgres(x, configuration.OmbiDatabase)); services.AddDbContext<OmbiContext, OmbiPostgresContext>(x => DatabaseConfigurationSetup.ConfigurePostgres(x, configuration.OmbiDatabase));
AddPostgresHealthCheck(hcBuilder, "Ombi Database", configuration.OmbiDatabase); AddPostgresHealthCheck(hcBuilder, "Ombi Database", configuration.OmbiDatabase);
break; break;
} }
@ -54,11 +56,11 @@ namespace Ombi.Extensions
AddSqliteHealthCheck(hcBuilder, "External Database", configuration.ExternalDatabase); AddSqliteHealthCheck(hcBuilder, "External Database", configuration.ExternalDatabase);
break; break;
case var type when type.Equals(MySqlDatabase, StringComparison.InvariantCultureIgnoreCase): case var type when type.Equals(MySqlDatabase, StringComparison.InvariantCultureIgnoreCase):
services.AddDbContext<ExternalContext, ExternalMySqlContext>(x => ConfigureMySql(x, configuration.ExternalDatabase)); services.AddDbContext<ExternalContext, ExternalMySqlContext>(x => DatabaseConfigurationSetup.ConfigureMySql(x, configuration.ExternalDatabase));
AddMySqlHealthCheck(hcBuilder, "External Database", configuration.ExternalDatabase); AddMySqlHealthCheck(hcBuilder, "External Database", configuration.ExternalDatabase);
break; break;
case var type when type.Equals(PostgresDatabase, StringComparison.InvariantCultureIgnoreCase): case var type when type.Equals(PostgresDatabase, StringComparison.InvariantCultureIgnoreCase):
services.AddDbContext<ExternalContext, ExternalPostgresContext>(x => ConfigurePostgres(x, configuration.ExternalDatabase)); services.AddDbContext<ExternalContext, ExternalPostgresContext>(x => DatabaseConfigurationSetup.ConfigurePostgres(x, configuration.ExternalDatabase));
AddPostgresHealthCheck(hcBuilder, "External Database", configuration.ExternalDatabase); AddPostgresHealthCheck(hcBuilder, "External Database", configuration.ExternalDatabase);
break; break;
} }
@ -70,11 +72,11 @@ namespace Ombi.Extensions
AddSqliteHealthCheck(hcBuilder, "Settings Database", configuration.SettingsDatabase); AddSqliteHealthCheck(hcBuilder, "Settings Database", configuration.SettingsDatabase);
break; break;
case var type when type.Equals(MySqlDatabase, StringComparison.InvariantCultureIgnoreCase): case var type when type.Equals(MySqlDatabase, StringComparison.InvariantCultureIgnoreCase):
services.AddDbContext<SettingsContext, SettingsMySqlContext>(x => ConfigureMySql(x, configuration.SettingsDatabase)); services.AddDbContext<SettingsContext, SettingsMySqlContext>(x => DatabaseConfigurationSetup.ConfigureMySql(x, configuration.SettingsDatabase));
AddMySqlHealthCheck(hcBuilder, "Settings Database", configuration.SettingsDatabase); AddMySqlHealthCheck(hcBuilder, "Settings Database", configuration.SettingsDatabase);
break; break;
case var type when type.Equals(PostgresDatabase, StringComparison.InvariantCultureIgnoreCase): case var type when type.Equals(PostgresDatabase, StringComparison.InvariantCultureIgnoreCase):
services.AddDbContext<SettingsContext, SettingsPostgresContext>(x => ConfigurePostgres(x, configuration.SettingsDatabase)); services.AddDbContext<SettingsContext, SettingsPostgresContext>(x => DatabaseConfigurationSetup.ConfigurePostgres(x, configuration.SettingsDatabase));
AddPostgresHealthCheck(hcBuilder, "Settings Database", configuration.SettingsDatabase); AddPostgresHealthCheck(hcBuilder, "Settings Database", configuration.SettingsDatabase);
break; break;
} }
@ -150,95 +152,5 @@ namespace Ombi.Extensions
SQLitePCL.raw.sqlite3_config(raw.SQLITE_CONFIG_MULTITHREAD); SQLitePCL.raw.sqlite3_config(raw.SQLITE_CONFIG_MULTITHREAD);
options.UseSqlite(config.ConnectionString); options.UseSqlite(config.ConnectionString);
} }
public static void ConfigureMySql(DbContextOptionsBuilder options, PerDatabaseConfiguration config)
{
if (string.IsNullOrEmpty(config.ConnectionString))
{
throw new ArgumentNullException("ConnectionString for the MySql/Mariadb database is empty");
}
options.UseMySql(config.ConnectionString, GetServerVersion(config.ConnectionString), b =>
{
//b.CharSetBehavior(Pomelo.EntityFrameworkCore.MySql.Infrastructure.CharSetBehavior.NeverAppend); // ##ISSUE, link to migrations?
b.EnableRetryOnFailure();
});
}
public static void ConfigurePostgres(DbContextOptionsBuilder options, PerDatabaseConfiguration config)
{
options.UseNpgsql(config.ConnectionString, b =>
{
b.EnableRetryOnFailure();
}).ReplaceService<ISqlGenerationHelper, NpgsqlCaseInsensitiveSqlGenerationHelper>();
}
private static ServerVersion GetServerVersion(string connectionString)
{
// Workaround Windows bug, that can lead to the following exception:
//
// MySqlConnector.MySqlException (0x80004005): SSL Authentication Error
// ---> System.Security.Authentication.AuthenticationException: Authentication failed, see inner exception.
// ---> System.ComponentModel.Win32Exception (0x8009030F): The message or signature supplied for verification has been altered
//
// See https://github.com/dotnet/runtime/issues/17005#issuecomment-305848835
//
// Also workaround for the fact, that ServerVersion.AutoDetect() does not use any retrying strategy.
ServerVersion serverVersion = null;
#pragma warning disable EF1001
var retryPolicy = Policy.Handle<Exception>(exception => MySqlTransientExceptionDetector.ShouldRetryOn(exception))
#pragma warning restore EF1001
.WaitAndRetry(3, (count, context) => TimeSpan.FromMilliseconds(count * 250));
serverVersion = retryPolicy.Execute(() => serverVersion = ServerVersion.AutoDetect(connectionString));
return serverVersion;
}
public class DatabaseConfiguration
{
public DatabaseConfiguration()
{
}
public DatabaseConfiguration(string defaultSqlitePath)
{
OmbiDatabase = new PerDatabaseConfiguration(SqliteDatabase, $"Data Source={Path.Combine(defaultSqlitePath, "Ombi.db")}");
SettingsDatabase = new PerDatabaseConfiguration(SqliteDatabase, $"Data Source={Path.Combine(defaultSqlitePath, "OmbiSettings.db")}");
ExternalDatabase = new PerDatabaseConfiguration(SqliteDatabase, $"Data Source={Path.Combine(defaultSqlitePath, "OmbiExternal.db")}");
}
public PerDatabaseConfiguration OmbiDatabase { get; set; }
public PerDatabaseConfiguration SettingsDatabase { get; set; }
public PerDatabaseConfiguration ExternalDatabase { get; set; }
}
public class PerDatabaseConfiguration
{
public PerDatabaseConfiguration(string type, string connectionString)
{
Type = type;
ConnectionString = connectionString;
}
// Used in Deserialization
public PerDatabaseConfiguration()
{
}
public string Type { get; set; }
public string ConnectionString { get; set; }
}
public class NpgsqlCaseInsensitiveSqlGenerationHelper : NpgsqlSqlGenerationHelper
{
const string EFMigrationsHisory = "__EFMigrationsHistory";
public NpgsqlCaseInsensitiveSqlGenerationHelper(RelationalSqlGenerationHelperDependencies dependencies)
: base(dependencies) { }
public override string DelimitIdentifier(string identifier) =>
base.DelimitIdentifier(identifier == EFMigrationsHisory ? identifier : identifier.ToLower());
public override void DelimitIdentifier(StringBuilder builder, string identifier)
=> base.DelimitIdentifier(builder, identifier == EFMigrationsHisory ? identifier : identifier.ToLower());
}
} }
} }

View file

@ -0,0 +1,3 @@
namespace Ombi.Models.V2;
public record WizardDatabaseConfiguration(string Type, string Host, int Port, string Name, string User, string Password);

View file

@ -54,10 +54,6 @@
<Content Remove="database.json" /> <Content Remove="database.json" />
</ItemGroup> </ItemGroup>
<ItemGroup>
<None Include="database.json" />
</ItemGroup>
<ItemGroup> <ItemGroup>
<None Include="..\.editorconfig" Link=".editorconfig" /> <None Include="..\.editorconfig" Link=".editorconfig" />
</ItemGroup> </ItemGroup>

View file

@ -87,7 +87,10 @@ namespace Ombi
services.AddJwtAuthentication(); services.AddJwtAuthentication();
services.AddMvc() services.AddMvc()
.AddNewtonsoftJson(x => x.SerializerSettings.ReferenceLoopHandling = ReferenceLoopHandling.Ignore); .AddNewtonsoftJson(x => {
x.SerializerSettings.ReferenceLoopHandling = ReferenceLoopHandling.Ignore;
x.SerializerSettings.DateTimeZoneHandling = DateTimeZoneHandling.Utc;
});
services.AddOmbiMappingProfile(); services.AddOmbiMappingProfile();
services.AddAutoMapper(expression => expression.AddCollectionMappers()); services.AddAutoMapper(expression => expression.AddCollectionMappers());

View file

@ -11,6 +11,7 @@ When("I visit Ombi", () => {
When("I click through all of the pages", () => { When("I click through all of the pages", () => {
Page.welcomeTab.next.click(); Page.welcomeTab.next.click();
Page.databaseTab.next.click();
Page.mediaServerTab.next.click(); Page.mediaServerTab.next.click();
Page.localUserTab.next.click(); Page.localUserTab.next.click();
Page.ombiConfigTab.next.click(); Page.ombiConfigTab.next.click();
@ -22,6 +23,7 @@ When("I click through all of the pages", () => {
When("I click through to the user page", () => { When("I click through to the user page", () => {
Page.welcomeTab.next.click(); Page.welcomeTab.next.click();
Page.databaseTab.next.click();
Page.mediaServerTab.next.click(); Page.mediaServerTab.next.click();
}); });
@ -48,6 +50,6 @@ Then("I should get a notification {string}", (string) => {
Then("I should be on the User tab", () => { Then("I should be on the User tab", () => {
Page.matStepsHeader.then((_) => { Page.matStepsHeader.then((_) => {
cy.get('#cdk-step-label-0-2').should('have.attr', 'aria-selected', 'true'); cy.get('#cdk-step-label-0-3').should('have.attr', 'aria-selected', 'true');
}); });
}); });

View file

@ -25,7 +25,7 @@
{ {
"episodeNumber": 1, "episodeNumber": 1,
"title": "Our Cup Runneth Over", "title": "Our Cup Runneth Over",
"airDate": "2015-01-13T00:00:00", "airDate": "2015-01-13T00:00:00Z",
"url": "https://www.tvmaze.com/episodes/153107/schitts-creek-1x01-our-cup-runneth-over", "url": "https://www.tvmaze.com/episodes/153107/schitts-creek-1x01-our-cup-runneth-over",
"available": false, "available": false,
"approved": false, "approved": false,

View file

@ -20,6 +20,12 @@ class WelcomeTab {
} }
} }
class DatabaseTab {
get next(): Cypress.Chainable<any> {
return cy.getByData('nextDatabase');
}
}
class MediaServerTab { class MediaServerTab {
get next(): Cypress.Chainable<any> { get next(): Cypress.Chainable<any> {
return cy.getByData('nextMediaServer'); return cy.getByData('nextMediaServer');
@ -35,6 +41,7 @@ class OmbiConfigTab {
class WizardPage extends BasePage { class WizardPage extends BasePage {
databaseTab: DatabaseTab;
localUserTab: LocalUserTab; localUserTab: LocalUserTab;
welcomeTab: WelcomeTab; welcomeTab: WelcomeTab;
mediaServerTab: MediaServerTab; mediaServerTab: MediaServerTab;
@ -54,6 +61,7 @@ class WizardPage extends BasePage {
this.welcomeTab = new WelcomeTab(); this.welcomeTab = new WelcomeTab();
this.mediaServerTab = new MediaServerTab(); this.mediaServerTab = new MediaServerTab();
this.ombiConfigTab = new OmbiConfigTab(); this.ombiConfigTab = new OmbiConfigTab();
this.databaseTab = new DatabaseTab();
} }
visit(options: Cypress.VisitOptions): Cypress.Chainable<Cypress.AUTWindow>; visit(options: Cypress.VisitOptions): Cypress.Chainable<Cypress.AUTWindow>;

View file

@ -1,3 +1,3 @@
{ {
"version": "4.44.1" "version": "4.47.0"
} }