diff --git a/BaGet.sln b/BaGet.sln index f904327e..b4879934 100644 --- a/BaGet.sln +++ b/BaGet.sln @@ -1,81 +1,81 @@ - -Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio 15 -VisualStudioVersion = 15.0.27130.2027 -MinimumVisualStudioVersion = 10.0.40219.1 -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BaGet", "src\BaGet\BaGet.csproj", "{284366CB-C68F-473E-908A-50A382616AE0}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BaGet.Core", "src\BaGet.Core\BaGet.Core.csproj", "{FFFACD28-C300-4046-BCFE-4A7899E88EA3}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BaGet.Azure", "src\BaGet.Azure\BaGet.Azure.csproj", "{716C970D-9614-4265-AC92-57E8B227B98E}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BaGet.Tools.AzureSearchImporter", "src\BaGet.Tools.AzureSearchImporter\BaGet.Tools.AzureSearchImporter.csproj", "{B232DAFE-5CE8-441F-ACC7-2BB54BCD094F}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BaGet.Core.Tests", "tests\BaGet.Core.Tests\BaGet.Core.Tests.csproj", "{89AB1AE2-6CAA-4809-8B74-D78CBE00B049}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BaGet.Tests", "tests\BaGet.Tests\BaGet.Tests.csproj", "{892A7A82-4283-4315-B7E5-6D5B70543000}" -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{26A0B557-53FB-4B9A-94C4-BCCF1BDCB0CC}" -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{C237857D-AD8E-4C52-974F-6A8155BB0C18}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BaGet.Protocol", "src\BaGet.Protocol\BaGet.Protocol.csproj", "{A2D23427-9278-4D52-B31F-759212252832}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BaGet.Protocol.Tests", "tests\BaGet.Protocol.Tests\BaGet.Protocol.Tests.csproj", "{AC764A9A-9EAF-422B-9223-D3290C3CFD79}" -EndProject -Global - GlobalSection(SolutionConfigurationPlatforms) = preSolution - Debug|Any CPU = Debug|Any CPU - Release|Any CPU = Release|Any CPU - EndGlobalSection - GlobalSection(ProjectConfigurationPlatforms) = postSolution - {284366CB-C68F-473E-908A-50A382616AE0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {284366CB-C68F-473E-908A-50A382616AE0}.Debug|Any CPU.Build.0 = Debug|Any CPU - {284366CB-C68F-473E-908A-50A382616AE0}.Release|Any CPU.ActiveCfg = Release|Any CPU - {284366CB-C68F-473E-908A-50A382616AE0}.Release|Any CPU.Build.0 = Release|Any CPU - {FFFACD28-C300-4046-BCFE-4A7899E88EA3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {FFFACD28-C300-4046-BCFE-4A7899E88EA3}.Debug|Any CPU.Build.0 = Debug|Any CPU - {FFFACD28-C300-4046-BCFE-4A7899E88EA3}.Release|Any CPU.ActiveCfg = Release|Any CPU - {FFFACD28-C300-4046-BCFE-4A7899E88EA3}.Release|Any CPU.Build.0 = Release|Any CPU - {716C970D-9614-4265-AC92-57E8B227B98E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {716C970D-9614-4265-AC92-57E8B227B98E}.Debug|Any CPU.Build.0 = Debug|Any CPU - {716C970D-9614-4265-AC92-57E8B227B98E}.Release|Any CPU.ActiveCfg = Release|Any CPU - {716C970D-9614-4265-AC92-57E8B227B98E}.Release|Any CPU.Build.0 = Release|Any CPU - {B232DAFE-5CE8-441F-ACC7-2BB54BCD094F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {B232DAFE-5CE8-441F-ACC7-2BB54BCD094F}.Debug|Any CPU.Build.0 = Debug|Any CPU - {B232DAFE-5CE8-441F-ACC7-2BB54BCD094F}.Release|Any CPU.ActiveCfg = Release|Any CPU - {B232DAFE-5CE8-441F-ACC7-2BB54BCD094F}.Release|Any CPU.Build.0 = Release|Any CPU - {89AB1AE2-6CAA-4809-8B74-D78CBE00B049}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {89AB1AE2-6CAA-4809-8B74-D78CBE00B049}.Debug|Any CPU.Build.0 = Debug|Any CPU - {89AB1AE2-6CAA-4809-8B74-D78CBE00B049}.Release|Any CPU.ActiveCfg = Release|Any CPU - {89AB1AE2-6CAA-4809-8B74-D78CBE00B049}.Release|Any CPU.Build.0 = Release|Any CPU - {892A7A82-4283-4315-B7E5-6D5B70543000}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {892A7A82-4283-4315-B7E5-6D5B70543000}.Debug|Any CPU.Build.0 = Debug|Any CPU - {892A7A82-4283-4315-B7E5-6D5B70543000}.Release|Any CPU.ActiveCfg = Release|Any CPU - {892A7A82-4283-4315-B7E5-6D5B70543000}.Release|Any CPU.Build.0 = Release|Any CPU - {A2D23427-9278-4D52-B31F-759212252832}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {A2D23427-9278-4D52-B31F-759212252832}.Debug|Any CPU.Build.0 = Debug|Any CPU - {A2D23427-9278-4D52-B31F-759212252832}.Release|Any CPU.ActiveCfg = Release|Any CPU - {A2D23427-9278-4D52-B31F-759212252832}.Release|Any CPU.Build.0 = Release|Any CPU - {AC764A9A-9EAF-422B-9223-D3290C3CFD79}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {AC764A9A-9EAF-422B-9223-D3290C3CFD79}.Debug|Any CPU.Build.0 = Debug|Any CPU - {AC764A9A-9EAF-422B-9223-D3290C3CFD79}.Release|Any CPU.ActiveCfg = Release|Any CPU - {AC764A9A-9EAF-422B-9223-D3290C3CFD79}.Release|Any CPU.Build.0 = Release|Any CPU - EndGlobalSection - GlobalSection(SolutionProperties) = preSolution - HideSolutionNode = FALSE - EndGlobalSection - GlobalSection(NestedProjects) = preSolution - {284366CB-C68F-473E-908A-50A382616AE0} = {26A0B557-53FB-4B9A-94C4-BCCF1BDCB0CC} - {FFFACD28-C300-4046-BCFE-4A7899E88EA3} = {26A0B557-53FB-4B9A-94C4-BCCF1BDCB0CC} - {716C970D-9614-4265-AC92-57E8B227B98E} = {26A0B557-53FB-4B9A-94C4-BCCF1BDCB0CC} - {B232DAFE-5CE8-441F-ACC7-2BB54BCD094F} = {26A0B557-53FB-4B9A-94C4-BCCF1BDCB0CC} - {89AB1AE2-6CAA-4809-8B74-D78CBE00B049} = {C237857D-AD8E-4C52-974F-6A8155BB0C18} - {892A7A82-4283-4315-B7E5-6D5B70543000} = {C237857D-AD8E-4C52-974F-6A8155BB0C18} - {A2D23427-9278-4D52-B31F-759212252832} = {26A0B557-53FB-4B9A-94C4-BCCF1BDCB0CC} - {AC764A9A-9EAF-422B-9223-D3290C3CFD79} = {C237857D-AD8E-4C52-974F-6A8155BB0C18} - EndGlobalSection - GlobalSection(ExtensibilityGlobals) = postSolution - SolutionGuid = {1423C027-2C90-417F-8629-2A4CF107C055} - EndGlobalSection -EndGlobal + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 15 +VisualStudioVersion = 15.0.27130.2027 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BaGet", "src\BaGet\BaGet.csproj", "{284366CB-C68F-473E-908A-50A382616AE0}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BaGet.Core", "src\BaGet.Core\BaGet.Core.csproj", "{FFFACD28-C300-4046-BCFE-4A7899E88EA3}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BaGet.Azure", "src\BaGet.Azure\BaGet.Azure.csproj", "{716C970D-9614-4265-AC92-57E8B227B98E}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BaGet.Tools.AzureSearchImporter", "src\BaGet.Tools.AzureSearchImporter\BaGet.Tools.AzureSearchImporter.csproj", "{B232DAFE-5CE8-441F-ACC7-2BB54BCD094F}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BaGet.Core.Tests", "tests\BaGet.Core.Tests\BaGet.Core.Tests.csproj", "{89AB1AE2-6CAA-4809-8B74-D78CBE00B049}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BaGet.Tests", "tests\BaGet.Tests\BaGet.Tests.csproj", "{892A7A82-4283-4315-B7E5-6D5B70543000}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{26A0B557-53FB-4B9A-94C4-BCCF1BDCB0CC}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{C237857D-AD8E-4C52-974F-6A8155BB0C18}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BaGet.Protocol", "src\BaGet.Protocol\BaGet.Protocol.csproj", "{A2D23427-9278-4D52-B31F-759212252832}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BaGet.Protocol.Tests", "tests\BaGet.Protocol.Tests\BaGet.Protocol.Tests.csproj", "{AC764A9A-9EAF-422B-9223-D3290C3CFD79}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {284366CB-C68F-473E-908A-50A382616AE0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {284366CB-C68F-473E-908A-50A382616AE0}.Debug|Any CPU.Build.0 = Debug|Any CPU + {284366CB-C68F-473E-908A-50A382616AE0}.Release|Any CPU.ActiveCfg = Release|Any CPU + {284366CB-C68F-473E-908A-50A382616AE0}.Release|Any CPU.Build.0 = Release|Any CPU + {FFFACD28-C300-4046-BCFE-4A7899E88EA3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {FFFACD28-C300-4046-BCFE-4A7899E88EA3}.Debug|Any CPU.Build.0 = Debug|Any CPU + {FFFACD28-C300-4046-BCFE-4A7899E88EA3}.Release|Any CPU.ActiveCfg = Release|Any CPU + {FFFACD28-C300-4046-BCFE-4A7899E88EA3}.Release|Any CPU.Build.0 = Release|Any CPU + {716C970D-9614-4265-AC92-57E8B227B98E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {716C970D-9614-4265-AC92-57E8B227B98E}.Debug|Any CPU.Build.0 = Debug|Any CPU + {716C970D-9614-4265-AC92-57E8B227B98E}.Release|Any CPU.ActiveCfg = Release|Any CPU + {716C970D-9614-4265-AC92-57E8B227B98E}.Release|Any CPU.Build.0 = Release|Any CPU + {B232DAFE-5CE8-441F-ACC7-2BB54BCD094F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {B232DAFE-5CE8-441F-ACC7-2BB54BCD094F}.Debug|Any CPU.Build.0 = Debug|Any CPU + {B232DAFE-5CE8-441F-ACC7-2BB54BCD094F}.Release|Any CPU.ActiveCfg = Release|Any CPU + {B232DAFE-5CE8-441F-ACC7-2BB54BCD094F}.Release|Any CPU.Build.0 = Release|Any CPU + {89AB1AE2-6CAA-4809-8B74-D78CBE00B049}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {89AB1AE2-6CAA-4809-8B74-D78CBE00B049}.Debug|Any CPU.Build.0 = Debug|Any CPU + {89AB1AE2-6CAA-4809-8B74-D78CBE00B049}.Release|Any CPU.ActiveCfg = Release|Any CPU + {89AB1AE2-6CAA-4809-8B74-D78CBE00B049}.Release|Any CPU.Build.0 = Release|Any CPU + {892A7A82-4283-4315-B7E5-6D5B70543000}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {892A7A82-4283-4315-B7E5-6D5B70543000}.Debug|Any CPU.Build.0 = Debug|Any CPU + {892A7A82-4283-4315-B7E5-6D5B70543000}.Release|Any CPU.ActiveCfg = Release|Any CPU + {892A7A82-4283-4315-B7E5-6D5B70543000}.Release|Any CPU.Build.0 = Release|Any CPU + {A2D23427-9278-4D52-B31F-759212252832}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {A2D23427-9278-4D52-B31F-759212252832}.Debug|Any CPU.Build.0 = Debug|Any CPU + {A2D23427-9278-4D52-B31F-759212252832}.Release|Any CPU.ActiveCfg = Release|Any CPU + {A2D23427-9278-4D52-B31F-759212252832}.Release|Any CPU.Build.0 = Release|Any CPU + {AC764A9A-9EAF-422B-9223-D3290C3CFD79}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {AC764A9A-9EAF-422B-9223-D3290C3CFD79}.Debug|Any CPU.Build.0 = Debug|Any CPU + {AC764A9A-9EAF-422B-9223-D3290C3CFD79}.Release|Any CPU.ActiveCfg = Release|Any CPU + {AC764A9A-9EAF-422B-9223-D3290C3CFD79}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(NestedProjects) = preSolution + {284366CB-C68F-473E-908A-50A382616AE0} = {26A0B557-53FB-4B9A-94C4-BCCF1BDCB0CC} + {FFFACD28-C300-4046-BCFE-4A7899E88EA3} = {26A0B557-53FB-4B9A-94C4-BCCF1BDCB0CC} + {716C970D-9614-4265-AC92-57E8B227B98E} = {26A0B557-53FB-4B9A-94C4-BCCF1BDCB0CC} + {B232DAFE-5CE8-441F-ACC7-2BB54BCD094F} = {26A0B557-53FB-4B9A-94C4-BCCF1BDCB0CC} + {89AB1AE2-6CAA-4809-8B74-D78CBE00B049} = {C237857D-AD8E-4C52-974F-6A8155BB0C18} + {892A7A82-4283-4315-B7E5-6D5B70543000} = {C237857D-AD8E-4C52-974F-6A8155BB0C18} + {A2D23427-9278-4D52-B31F-759212252832} = {26A0B557-53FB-4B9A-94C4-BCCF1BDCB0CC} + {AC764A9A-9EAF-422B-9223-D3290C3CFD79} = {C237857D-AD8E-4C52-974F-6A8155BB0C18} + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {1423C027-2C90-417F-8629-2A4CF107C055} + EndGlobalSection +EndGlobal diff --git a/LICENSE b/LICENSE index cc2ed50b..0f682f8d 100644 --- a/LICENSE +++ b/LICENSE @@ -1,19 +1,19 @@ -Copyright 2018 Loic Sharma - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL -THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +Copyright 2018 Loic Sharma + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. \ No newline at end of file diff --git a/docs/readme.md b/docs/readme.md index ade0ce39..c962291d 100644 --- a/docs/readme.md +++ b/docs/readme.md @@ -1,2 +1,2 @@ -Please use the [documentation website](https://loic-sharma.github.io/BaGet/) +Please use the [documentation website](https://loic-sharma.github.io/BaGet/) for the best browsing experience. \ No newline at end of file diff --git a/src/BaGet.Azure/BaGet.Azure.csproj b/src/BaGet.Azure/BaGet.Azure.csproj index 4ce007b0..803e06b4 100644 --- a/src/BaGet.Azure/BaGet.Azure.csproj +++ b/src/BaGet.Azure/BaGet.Azure.csproj @@ -1,19 +1,19 @@ - - - - netstandard2.0;net461 - - The libraries to host BaGet on Azure. - - - - - - - - - - - - - + + + + netstandard2.0;net461 + + The libraries to host BaGet on Azure. + + + + + + + + + + + + + diff --git a/src/BaGet.Azure/Configuration/AzureSearchOptions.cs b/src/BaGet.Azure/Configuration/AzureSearchOptions.cs index a8e74bc1..1ba788de 100644 --- a/src/BaGet.Azure/Configuration/AzureSearchOptions.cs +++ b/src/BaGet.Azure/Configuration/AzureSearchOptions.cs @@ -1,13 +1,13 @@ -using System.ComponentModel.DataAnnotations; - -namespace BaGet.Azure.Configuration -{ - public class AzureSearchOptions : Core.Configuration.SearchOptions - { - [Required] - public string AccountName { get; set; } - - [Required] - public string ApiKey { get; set; } - } -} +using System.ComponentModel.DataAnnotations; + +namespace BaGet.Azure.Configuration +{ + public class AzureSearchOptions : Core.Configuration.SearchOptions + { + [Required] + public string AccountName { get; set; } + + [Required] + public string ApiKey { get; set; } + } +} diff --git a/src/BaGet.Azure/Configuration/BlobStorageOptions.cs b/src/BaGet.Azure/Configuration/BlobStorageOptions.cs index 5649f32b..40799b03 100644 --- a/src/BaGet.Azure/Configuration/BlobStorageOptions.cs +++ b/src/BaGet.Azure/Configuration/BlobStorageOptions.cs @@ -1,16 +1,16 @@ -using System.ComponentModel.DataAnnotations; - -namespace BaGet.Azure.Configuration -{ - public class BlobStorageOptions - { - [Required] - public string AccountName { get; set; } - - [Required] - public string AccessKey { get; set; } - - [Required] - public string Container { get; set; } - } -} +using System.ComponentModel.DataAnnotations; + +namespace BaGet.Azure.Configuration +{ + public class BlobStorageOptions + { + [Required] + public string AccountName { get; set; } + + [Required] + public string AccessKey { get; set; } + + [Required] + public string Container { get; set; } + } +} diff --git a/src/BaGet.Azure/Extensions/ServiceCollectionExtensions.cs b/src/BaGet.Azure/Extensions/ServiceCollectionExtensions.cs index 783e0680..fd62e396 100644 --- a/src/BaGet.Azure/Extensions/ServiceCollectionExtensions.cs +++ b/src/BaGet.Azure/Extensions/ServiceCollectionExtensions.cs @@ -1,67 +1,67 @@ -using BaGet.Azure.Configuration; -using BaGet.Azure.Search; -using BaGet.Core.Services; -using Microsoft.Azure.Search; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Options; -using Microsoft.WindowsAzure.Storage; -using Microsoft.WindowsAzure.Storage.Auth; -using Microsoft.WindowsAzure.Storage.Blob; - -namespace BaGet.Azure.Extensions -{ - public static class ServiceCollectionExtensions - { - public static IServiceCollection AddBlobStorageService(this IServiceCollection services) - { - services.AddSingleton(provider => - { - var options = provider.GetRequiredService>().Value; - - return new CloudStorageAccount( - new StorageCredentials( - options.AccountName, - options.AccessKey), - useHttps: true); - }); - - services.AddTransient(provider => - { - var options = provider.GetRequiredService>().Value; - var account = provider.GetRequiredService(); - - var client = account.CreateCloudBlobClient(); - - return client.GetContainerReference(options.Container); - }); - - services.AddTransient(); - - return services; - } - - public static IServiceCollection AddAzureSearch(this IServiceCollection services) - { - services.AddTransient(); - services.AddTransient(); - - services.AddSingleton(provider => - { - var options = provider.GetRequiredService>().Value; - var credentials = new SearchCredentials(options.ApiKey); - - return new SearchServiceClient(options.AccountName, credentials); - }); - - services.AddSingleton(provider => - { - var options = provider.GetRequiredService>().Value; - var credentials = new SearchCredentials(options.ApiKey); - - return new SearchIndexClient(options.AccountName, PackageDocument.IndexName, credentials); - }); - - return services; - } - } -} +using BaGet.Azure.Configuration; +using BaGet.Azure.Search; +using BaGet.Core.Services; +using Microsoft.Azure.Search; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Options; +using Microsoft.WindowsAzure.Storage; +using Microsoft.WindowsAzure.Storage.Auth; +using Microsoft.WindowsAzure.Storage.Blob; + +namespace BaGet.Azure.Extensions +{ + public static class ServiceCollectionExtensions + { + public static IServiceCollection AddBlobStorageService(this IServiceCollection services) + { + services.AddSingleton(provider => + { + var options = provider.GetRequiredService>().Value; + + return new CloudStorageAccount( + new StorageCredentials( + options.AccountName, + options.AccessKey), + useHttps: true); + }); + + services.AddTransient(provider => + { + var options = provider.GetRequiredService>().Value; + var account = provider.GetRequiredService(); + + var client = account.CreateCloudBlobClient(); + + return client.GetContainerReference(options.Container); + }); + + services.AddTransient(); + + return services; + } + + public static IServiceCollection AddAzureSearch(this IServiceCollection services) + { + services.AddTransient(); + services.AddTransient(); + + services.AddSingleton(provider => + { + var options = provider.GetRequiredService>().Value; + var credentials = new SearchCredentials(options.ApiKey); + + return new SearchServiceClient(options.AccountName, credentials); + }); + + services.AddSingleton(provider => + { + var options = provider.GetRequiredService>().Value; + var credentials = new SearchCredentials(options.ApiKey); + + return new SearchIndexClient(options.AccountName, PackageDocument.IndexName, credentials); + }); + + return services; + } + } +} diff --git a/src/BaGet.Azure/Search/AzureSearchService.cs b/src/BaGet.Azure/Search/AzureSearchService.cs index 40810140..6c2e4831 100644 --- a/src/BaGet.Azure/Search/AzureSearchService.cs +++ b/src/BaGet.Azure/Search/AzureSearchService.cs @@ -1,85 +1,85 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; -using BaGet.Core.Services; -using Microsoft.Azure.Search; -using NuGet.Versioning; - -namespace BaGet.Azure.Search -{ - using BaGet.Core.Entities; - using SearchParameters = Microsoft.Azure.Search.Models.SearchParameters; - - public class AzureSearchService : ISearchService - { - private readonly BatchIndexer _indexer; - private readonly SearchIndexClient _searchClient; - - public AzureSearchService(BatchIndexer indexer, SearchIndexClient searchClient) - { - _indexer = indexer ?? throw new ArgumentNullException(nameof(indexer)); - _searchClient = searchClient ?? throw new ArgumentNullException(nameof(searchClient)); - } - - public Task IndexAsync(Package package) => _indexer.IndexAsync(package.Id); - - public async Task> SearchAsync(string query, int skip = 0, int take = 20) - { - query = query.TrimEnd().TrimEnd('*') + '*'; - - var search = await _searchClient.Documents.SearchAsync(query, new SearchParameters - { - Skip = skip, - Top = take - }); - - var results = new List(); - - foreach (var result in search.Results) - { - var document = result.Document; - var versions = new List(); - - if (document.Versions.Length != document.VersionDownloads.Length) - { - throw new InvalidOperationException($"Invalid document {document.Key} with mismatched versions"); - } - - for (var i = 0; i < document.Versions.Length; i++) - { - versions.Add(new SearchResultVersion( - NuGetVersion.Parse(document.Versions[i]), - long.Parse(document.VersionDownloads[i]))); - } - - results.Add(new SearchResult - { - Id = document.Id, - Version = NuGetVersion.Parse(document.Version), - Description = document.Description, - Authors = document.Authors, - IconUrl = document.IconUrl, - LicenseUrl = document.LicenseUrl, - Summary = document.Summary, - Tags = document.Tags, - Title = document.Title, - TotalDownloads = document.TotalDownloads, - Versions = versions.AsReadOnly() - }); - } - - return results.AsReadOnly(); - } - - public async Task> AutocompleteAsync(string query, int skip = 0, int take = 20) - { - var search = await _searchClient.Documents.SearchAsync(query); - - return search.Results - .Select(r => r.Document.Id) - .ToList() - .AsReadOnly(); - } - } -} +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using BaGet.Core.Services; +using Microsoft.Azure.Search; +using NuGet.Versioning; + +namespace BaGet.Azure.Search +{ + using BaGet.Core.Entities; + using SearchParameters = Microsoft.Azure.Search.Models.SearchParameters; + + public class AzureSearchService : ISearchService + { + private readonly BatchIndexer _indexer; + private readonly SearchIndexClient _searchClient; + + public AzureSearchService(BatchIndexer indexer, SearchIndexClient searchClient) + { + _indexer = indexer ?? throw new ArgumentNullException(nameof(indexer)); + _searchClient = searchClient ?? throw new ArgumentNullException(nameof(searchClient)); + } + + public Task IndexAsync(Package package) => _indexer.IndexAsync(package.Id); + + public async Task> SearchAsync(string query, int skip = 0, int take = 20) + { + query = query.TrimEnd().TrimEnd('*') + '*'; + + var search = await _searchClient.Documents.SearchAsync(query, new SearchParameters + { + Skip = skip, + Top = take + }); + + var results = new List(); + + foreach (var result in search.Results) + { + var document = result.Document; + var versions = new List(); + + if (document.Versions.Length != document.VersionDownloads.Length) + { + throw new InvalidOperationException($"Invalid document {document.Key} with mismatched versions"); + } + + for (var i = 0; i < document.Versions.Length; i++) + { + versions.Add(new SearchResultVersion( + NuGetVersion.Parse(document.Versions[i]), + long.Parse(document.VersionDownloads[i]))); + } + + results.Add(new SearchResult + { + Id = document.Id, + Version = NuGetVersion.Parse(document.Version), + Description = document.Description, + Authors = document.Authors, + IconUrl = document.IconUrl, + LicenseUrl = document.LicenseUrl, + Summary = document.Summary, + Tags = document.Tags, + Title = document.Title, + TotalDownloads = document.TotalDownloads, + Versions = versions.AsReadOnly() + }); + } + + return results.AsReadOnly(); + } + + public async Task> AutocompleteAsync(string query, int skip = 0, int take = 20) + { + var search = await _searchClient.Documents.SearchAsync(query); + + return search.Results + .Select(r => r.Document.Id) + .ToList() + .AsReadOnly(); + } + } +} diff --git a/src/BaGet.Azure/Search/BatchIndexer.cs b/src/BaGet.Azure/Search/BatchIndexer.cs index 2feebd5d..f16480b4 100644 --- a/src/BaGet.Azure/Search/BatchIndexer.cs +++ b/src/BaGet.Azure/Search/BatchIndexer.cs @@ -1,111 +1,111 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using BaGet.Core.Services; -using Microsoft.Azure.Search; -using Microsoft.Azure.Search.Models; -using Microsoft.Extensions.Logging; - -namespace BaGet.Azure.Search -{ - public class BatchIndexer - { - public const int MaxBatchSize = 1000; - - private readonly IPackageService _packageService; - private readonly ISearchIndexClient _indexClient; - private readonly ILogger _logger; - - public BatchIndexer( - IPackageService packageService, - SearchServiceClient searchClient, - ILogger logger) - { - if (searchClient == null) throw new ArgumentNullException(nameof(searchClient)); - - _indexClient = searchClient.Indexes.GetClient(PackageDocument.IndexName); - - _packageService = packageService ?? throw new ArgumentNullException(nameof(packageService)); - _logger = logger ?? throw new ArgumentNullException(nameof(logger)); - } - - public async Task IndexAsync(params string[] packageIds) - { - if (packageIds == null) throw new ArgumentNullException(nameof(packageIds)); - - var actions = new List>(); - var packageIdSet = new HashSet(packageIds, StringComparer.OrdinalIgnoreCase); - - if (packageIdSet.Count > MaxBatchSize) - { - throw new ArgumentException($"Cannot index more than {MaxBatchSize} packages at once"); - } - - _logger.LogInformation("Indexing {PackageCount} packages...", packageIdSet.Count); - - foreach (var packageId in packageIdSet) - { - var document = await BuildDocumentAsync(packageId); - - actions.Add(IndexAction.Upload(document)); - } - - var batch = IndexBatch.New(actions); - - // TODO: Add retry on IndexBatchException - // See: https://docs.microsoft.com/en-us/azure/search/search-import-data-dotnet#import-data-to-the-index - await _indexClient.Documents.IndexAsync(batch); - - _logger.LogInformation("Indexed {PackageCount} packages", packageIdSet.Count); - } - - private async Task BuildDocumentAsync(string packageId) - { - if (packageId == null) throw new ArgumentNullException(nameof(packageId)); - - var packages = await _packageService.FindAsync(packageId); - - if (packages.Count == 0) - { - _logger.LogError("Could not find package with id {PackageId}", packageId); - - throw new ArgumentException($"Invalid package id {packageId}", nameof(packageId)); - } - - var result = new PackageDocument(); - - var latest = packages.OrderByDescending(p => p.Version).First(); - var versions = packages.OrderBy(p => p.Version).ToList(); - - result.Key = EncodeKey(packageId.ToLowerInvariant()); - result.Id = latest.Id; - result.Version = latest.VersionString; - result.Description = latest.Description; - result.Authors = latest.Authors; - result.IconUrl = latest.IconUrlString; - result.LicenseUrl = latest.LicenseUrlString; - result.ProjectUrl = latest.ProjectUrlString; - result.Published = latest.Published; - result.Summary = latest.Summary; - result.Tags = latest.Tags; - result.Title = latest.Title; - result.TotalDownloads = versions.Sum(p => p.Downloads); - result.DownloadsMagnitude = result.TotalDownloads.ToString().Length; - result.Versions = versions.Select(p => p.VersionString).ToArray(); - result.VersionDownloads = versions.Select(p => p.Downloads.ToString()).ToArray(); - - return result; - } - - private string EncodeKey(string key) - { - // Keys can only contain letters, digits, underscore(_), dash(-), or equal sign(=). - var bytes = Encoding.UTF8.GetBytes(key); - var base64 = Convert.ToBase64String(bytes); - - return base64.Replace('+', '-').Replace('/', '_'); - } - } -} +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using BaGet.Core.Services; +using Microsoft.Azure.Search; +using Microsoft.Azure.Search.Models; +using Microsoft.Extensions.Logging; + +namespace BaGet.Azure.Search +{ + public class BatchIndexer + { + public const int MaxBatchSize = 1000; + + private readonly IPackageService _packageService; + private readonly ISearchIndexClient _indexClient; + private readonly ILogger _logger; + + public BatchIndexer( + IPackageService packageService, + SearchServiceClient searchClient, + ILogger logger) + { + if (searchClient == null) throw new ArgumentNullException(nameof(searchClient)); + + _indexClient = searchClient.Indexes.GetClient(PackageDocument.IndexName); + + _packageService = packageService ?? throw new ArgumentNullException(nameof(packageService)); + _logger = logger ?? throw new ArgumentNullException(nameof(logger)); + } + + public async Task IndexAsync(params string[] packageIds) + { + if (packageIds == null) throw new ArgumentNullException(nameof(packageIds)); + + var actions = new List>(); + var packageIdSet = new HashSet(packageIds, StringComparer.OrdinalIgnoreCase); + + if (packageIdSet.Count > MaxBatchSize) + { + throw new ArgumentException($"Cannot index more than {MaxBatchSize} packages at once"); + } + + _logger.LogInformation("Indexing {PackageCount} packages...", packageIdSet.Count); + + foreach (var packageId in packageIdSet) + { + var document = await BuildDocumentAsync(packageId); + + actions.Add(IndexAction.Upload(document)); + } + + var batch = IndexBatch.New(actions); + + // TODO: Add retry on IndexBatchException + // See: https://docs.microsoft.com/en-us/azure/search/search-import-data-dotnet#import-data-to-the-index + await _indexClient.Documents.IndexAsync(batch); + + _logger.LogInformation("Indexed {PackageCount} packages", packageIdSet.Count); + } + + private async Task BuildDocumentAsync(string packageId) + { + if (packageId == null) throw new ArgumentNullException(nameof(packageId)); + + var packages = await _packageService.FindAsync(packageId); + + if (packages.Count == 0) + { + _logger.LogError("Could not find package with id {PackageId}", packageId); + + throw new ArgumentException($"Invalid package id {packageId}", nameof(packageId)); + } + + var result = new PackageDocument(); + + var latest = packages.OrderByDescending(p => p.Version).First(); + var versions = packages.OrderBy(p => p.Version).ToList(); + + result.Key = EncodeKey(packageId.ToLowerInvariant()); + result.Id = latest.Id; + result.Version = latest.VersionString; + result.Description = latest.Description; + result.Authors = latest.Authors; + result.IconUrl = latest.IconUrlString; + result.LicenseUrl = latest.LicenseUrlString; + result.ProjectUrl = latest.ProjectUrlString; + result.Published = latest.Published; + result.Summary = latest.Summary; + result.Tags = latest.Tags; + result.Title = latest.Title; + result.TotalDownloads = versions.Sum(p => p.Downloads); + result.DownloadsMagnitude = result.TotalDownloads.ToString().Length; + result.Versions = versions.Select(p => p.VersionString).ToArray(); + result.VersionDownloads = versions.Select(p => p.Downloads.ToString()).ToArray(); + + return result; + } + + private string EncodeKey(string key) + { + // Keys can only contain letters, digits, underscore(_), dash(-), or equal sign(=). + var bytes = Encoding.UTF8.GetBytes(key); + var base64 = Convert.ToBase64String(bytes); + + return base64.Replace('+', '-').Replace('/', '_'); + } + } +} diff --git a/src/BaGet.Azure/Search/PackageDocument.cs b/src/BaGet.Azure/Search/PackageDocument.cs index 5de05be5..f902aa88 100644 --- a/src/BaGet.Azure/Search/PackageDocument.cs +++ b/src/BaGet.Azure/Search/PackageDocument.cs @@ -1,49 +1,49 @@ -using System; -using System.ComponentModel.DataAnnotations; -using Microsoft.Azure.Search; -using Microsoft.Azure.Search.Models; - -namespace BaGet.Azure.Search -{ - // See: https://docs.microsoft.com/en-us/nuget/api/search-query-service-resource#search-for-packages - [SerializePropertyNamesAsCamelCase] - public class PackageDocument - { - public const string IndexName = "packages"; - - [Key] - public string Key { get; set; } - - [IsSearchable, IsFilterable, IsSortable] - public string Id { get; set; } - - [IsSearchable, IsFilterable, IsSortable] - public string Version { get; set; } - - [IsSearchable] - public string Description { get; set; } - public string[] Authors { get; set; } - public string IconUrl { get; set; } - public string LicenseUrl { get; set; } - public string ProjectUrl { get; set; } - public DateTimeOffset Published { get; set; } - - [IsSearchable] - public string Summary { get; set; } - - [IsSearchable, IsFilterable, IsFacetable] - public string[] Tags { get; set; } - - [IsSearchable] - public string Title { get; set; } - - [IsFilterable, IsSortable] - public long TotalDownloads { get; set; } - - [IsFilterable, IsSortable] - public int DownloadsMagnitude { get; set; } - - public string[] Versions { get; set; } - public string[] VersionDownloads { get; set; } - } -} +using System; +using System.ComponentModel.DataAnnotations; +using Microsoft.Azure.Search; +using Microsoft.Azure.Search.Models; + +namespace BaGet.Azure.Search +{ + // See: https://docs.microsoft.com/en-us/nuget/api/search-query-service-resource#search-for-packages + [SerializePropertyNamesAsCamelCase] + public class PackageDocument + { + public const string IndexName = "packages"; + + [Key] + public string Key { get; set; } + + [IsSearchable, IsFilterable, IsSortable] + public string Id { get; set; } + + [IsSearchable, IsFilterable, IsSortable] + public string Version { get; set; } + + [IsSearchable] + public string Description { get; set; } + public string[] Authors { get; set; } + public string IconUrl { get; set; } + public string LicenseUrl { get; set; } + public string ProjectUrl { get; set; } + public DateTimeOffset Published { get; set; } + + [IsSearchable] + public string Summary { get; set; } + + [IsSearchable, IsFilterable, IsFacetable] + public string[] Tags { get; set; } + + [IsSearchable] + public string Title { get; set; } + + [IsFilterable, IsSortable] + public long TotalDownloads { get; set; } + + [IsFilterable, IsSortable] + public int DownloadsMagnitude { get; set; } + + public string[] Versions { get; set; } + public string[] VersionDownloads { get; set; } + } +} diff --git a/src/BaGet.Core/BaGet.Core.csproj b/src/BaGet.Core/BaGet.Core.csproj index 3d8c1d8a..482912f8 100644 --- a/src/BaGet.Core/BaGet.Core.csproj +++ b/src/BaGet.Core/BaGet.Core.csproj @@ -1,20 +1,20 @@ - - - - netstandard2.0;net461 - - The core libraries that power BaGet. - - - - - - - - - - - - - - + + + + netstandard2.0;net461 + + The core libraries that power BaGet. + + + + + + + + + + + + + + diff --git a/src/BaGet.Core/Configuration/BaGetOptions.cs b/src/BaGet.Core/Configuration/BaGetOptions.cs index f51d1291..92a6a004 100644 --- a/src/BaGet.Core/Configuration/BaGetOptions.cs +++ b/src/BaGet.Core/Configuration/BaGetOptions.cs @@ -1,41 +1,41 @@ -using System.ComponentModel.DataAnnotations; - -namespace BaGet.Core.Configuration -{ - public class BaGetOptions - { - /// - /// The SHA-256 hash of the API Key required to authenticate package - /// operations. If empty, package operations do not require authentication. - /// - public string ApiKeyHash { get; set; } - - /// - /// The application root URL for usage in reverse proxy scenarios. - /// - public string PathBase { get; set; } - - /// - /// If enabled, the database will be updated at app startup by running - /// Entity Framework migrations. This is not recommended in production. - /// - public bool RunMigrationsAtStartup { get; set; } = true; - - /// - /// How BaGet should interpret package deletion requests. - /// - public PackageDeletionBehavior PackageDeletionBehavior { get; set; } = PackageDeletionBehavior.Unlist; - - [Required] - public DatabaseOptions Database { get; set; } - - [Required] - public StorageOptions Storage { get; set; } - - [Required] - public SearchOptions Search { get; set; } - - [Required] - public MirrorOptions Mirror { get; set; } - } -} +using System.ComponentModel.DataAnnotations; + +namespace BaGet.Core.Configuration +{ + public class BaGetOptions + { + /// + /// The SHA-256 hash of the API Key required to authenticate package + /// operations. If empty, package operations do not require authentication. + /// + public string ApiKeyHash { get; set; } + + /// + /// The application root URL for usage in reverse proxy scenarios. + /// + public string PathBase { get; set; } + + /// + /// If enabled, the database will be updated at app startup by running + /// Entity Framework migrations. This is not recommended in production. + /// + public bool RunMigrationsAtStartup { get; set; } = true; + + /// + /// How BaGet should interpret package deletion requests. + /// + public PackageDeletionBehavior PackageDeletionBehavior { get; set; } = PackageDeletionBehavior.Unlist; + + [Required] + public DatabaseOptions Database { get; set; } + + [Required] + public StorageOptions Storage { get; set; } + + [Required] + public SearchOptions Search { get; set; } + + [Required] + public MirrorOptions Mirror { get; set; } + } +} diff --git a/src/BaGet.Core/Configuration/DatabaseOptions.cs b/src/BaGet.Core/Configuration/DatabaseOptions.cs index 69b74d39..411e6f4d 100644 --- a/src/BaGet.Core/Configuration/DatabaseOptions.cs +++ b/src/BaGet.Core/Configuration/DatabaseOptions.cs @@ -1,18 +1,18 @@ -using System.ComponentModel.DataAnnotations; - -namespace BaGet.Core.Configuration -{ - public class DatabaseOptions - { - public DatabaseType Type { get; set; } - - [Required] - public string ConnectionString { get; set; } - } - - public enum DatabaseType - { - Sqlite, - SqlServer, - } -} +using System.ComponentModel.DataAnnotations; + +namespace BaGet.Core.Configuration +{ + public class DatabaseOptions + { + public DatabaseType Type { get; set; } + + [Required] + public string ConnectionString { get; set; } + } + + public enum DatabaseType + { + Sqlite, + SqlServer, + } +} diff --git a/src/BaGet.Core/Configuration/FileSystemStorageOptions.cs b/src/BaGet.Core/Configuration/FileSystemStorageOptions.cs index 4df207e2..8bfbcd15 100644 --- a/src/BaGet.Core/Configuration/FileSystemStorageOptions.cs +++ b/src/BaGet.Core/Configuration/FileSystemStorageOptions.cs @@ -1,14 +1,14 @@ -using System.IO; - -namespace BaGet.Core.Configuration -{ - public class FileSystemStorageOptions : StorageOptions - { - /// - /// The path at which content will be stored. Defaults to the same path - /// as the main BaGet executable. This path will be created if it does not - /// exist at startup. Packages will be stored in a subfolder named "packages". - /// - public string Path { get; set; } = Directory.GetCurrentDirectory(); - } -} +using System.IO; + +namespace BaGet.Core.Configuration +{ + public class FileSystemStorageOptions : StorageOptions + { + /// + /// The path at which content will be stored. Defaults to the same path + /// as the main BaGet executable. This path will be created if it does not + /// exist at startup. Packages will be stored in a subfolder named "packages". + /// + public string Path { get; set; } = Directory.GetCurrentDirectory(); + } +} diff --git a/src/BaGet.Core/Configuration/MirrorOptions.cs b/src/BaGet.Core/Configuration/MirrorOptions.cs index 5a5038f1..096aef02 100644 --- a/src/BaGet.Core/Configuration/MirrorOptions.cs +++ b/src/BaGet.Core/Configuration/MirrorOptions.cs @@ -1,36 +1,36 @@ -using System; -using System.Collections.Generic; -using System.ComponentModel.DataAnnotations; - -namespace BaGet.Core.Configuration -{ - public class MirrorOptions : IValidatableObject - { - /// - /// If true, packages that aren't found locally will be indexed - /// using the upstream source. - /// - public bool Enabled { get; set; } - - /// - /// The v3 index that will be mirrored. - /// - public Uri PackageSource { get; set; } - - /// - /// The time before a download from the package source times out. - /// - [Range(0, int.MaxValue)] - public int PackageDownloadTimeoutSeconds { get; set; } = 600; - - public IEnumerable Validate(ValidationContext validationContext) - { - if (Enabled && PackageSource == null) - { - yield return new ValidationResult( - $"The {nameof(PackageSource)} configuration is required if mirroring is enabled", - new[] { nameof(PackageSource) }); - } - } - } -} +using System; +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; + +namespace BaGet.Core.Configuration +{ + public class MirrorOptions : IValidatableObject + { + /// + /// If true, packages that aren't found locally will be indexed + /// using the upstream source. + /// + public bool Enabled { get; set; } + + /// + /// The v3 index that will be mirrored. + /// + public Uri PackageSource { get; set; } + + /// + /// The time before a download from the package source times out. + /// + [Range(0, int.MaxValue)] + public int PackageDownloadTimeoutSeconds { get; set; } = 600; + + public IEnumerable Validate(ValidationContext validationContext) + { + if (Enabled && PackageSource == null) + { + yield return new ValidationResult( + $"The {nameof(PackageSource)} configuration is required if mirroring is enabled", + new[] { nameof(PackageSource) }); + } + } + } +} diff --git a/src/BaGet.Core/Configuration/PackageDeletionBehavior.cs b/src/BaGet.Core/Configuration/PackageDeletionBehavior.cs index 99681513..f6c6d227 100644 --- a/src/BaGet.Core/Configuration/PackageDeletionBehavior.cs +++ b/src/BaGet.Core/Configuration/PackageDeletionBehavior.cs @@ -1,22 +1,22 @@ -namespace BaGet.Core.Configuration -{ - /// - /// How BaGet should interpret package deletion requests. - /// See: https://docs.microsoft.com/en-us/nuget/api/package-publish-resource#delete-a-package - /// - public enum PackageDeletionBehavior - { - /// - /// Package "deletions" make the package undiscoverable. The package is still restorable - /// by consumers that know its id and version. This is the recommended behavior as it prevents - /// the "left pad" problem. - /// - Unlist, - - /// - /// Removes the package from the database and storage. Existing consumers will no longer - /// be able to restore the package. - /// - HardDelete, - } -} +namespace BaGet.Core.Configuration +{ + /// + /// How BaGet should interpret package deletion requests. + /// See: https://docs.microsoft.com/en-us/nuget/api/package-publish-resource#delete-a-package + /// + public enum PackageDeletionBehavior + { + /// + /// Package "deletions" make the package undiscoverable. The package is still restorable + /// by consumers that know its id and version. This is the recommended behavior as it prevents + /// the "left pad" problem. + /// + Unlist, + + /// + /// Removes the package from the database and storage. Existing consumers will no longer + /// be able to restore the package. + /// + HardDelete, + } +} diff --git a/src/BaGet.Core/Configuration/SearchOptions.cs b/src/BaGet.Core/Configuration/SearchOptions.cs index a1a6f652..af662923 100644 --- a/src/BaGet.Core/Configuration/SearchOptions.cs +++ b/src/BaGet.Core/Configuration/SearchOptions.cs @@ -1,13 +1,13 @@ -namespace BaGet.Core.Configuration -{ - public class SearchOptions - { - public SearchType Type { get; set; } - } - - public enum SearchType - { - Database = 0, - Azure = 1, - } -} +namespace BaGet.Core.Configuration +{ + public class SearchOptions + { + public SearchType Type { get; set; } + } + + public enum SearchType + { + Database = 0, + Azure = 1, + } +} diff --git a/src/BaGet.Core/Configuration/StorageOptions.cs b/src/BaGet.Core/Configuration/StorageOptions.cs index 88073932..561fc955 100644 --- a/src/BaGet.Core/Configuration/StorageOptions.cs +++ b/src/BaGet.Core/Configuration/StorageOptions.cs @@ -1,13 +1,13 @@ -namespace BaGet.Core.Configuration -{ - public class StorageOptions - { - public StorageType Type { get; set; } - } - - public enum StorageType - { - FileSystem = 0, - AzureBlobStorage = 1, - } -} +namespace BaGet.Core.Configuration +{ + public class StorageOptions + { + public StorageType Type { get; set; } + } + + public enum StorageType + { + FileSystem = 0, + AzureBlobStorage = 1, + } +} diff --git a/src/BaGet.Core/Entities/IContext.cs b/src/BaGet.Core/Entities/IContext.cs index 490cca68..3d358226 100644 --- a/src/BaGet.Core/Entities/IContext.cs +++ b/src/BaGet.Core/Entities/IContext.cs @@ -1,22 +1,22 @@ -using System.Threading.Tasks; -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Infrastructure; - -namespace BaGet.Core.Entities -{ - public interface IContext - { - DatabaseFacade Database { get; } - - DbSet Packages { get; set; } - - /// - /// Check whether a is due to a SQL unique constraint violation. - /// - /// The exception to inspect. - /// Whether the exception was caused to SQL unique constraint violation. - bool IsUniqueConstraintViolationException(DbUpdateException exception); - - Task SaveChangesAsync(); - } -} +using System.Threading.Tasks; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; + +namespace BaGet.Core.Entities +{ + public interface IContext + { + DatabaseFacade Database { get; } + + DbSet Packages { get; set; } + + /// + /// Check whether a is due to a SQL unique constraint violation. + /// + /// The exception to inspect. + /// Whether the exception was caused to SQL unique constraint violation. + bool IsUniqueConstraintViolationException(DbUpdateException exception); + + Task SaveChangesAsync(); + } +} diff --git a/src/BaGet.Core/Entities/Package.cs b/src/BaGet.Core/Entities/Package.cs index 54275aad..d23351bf 100644 --- a/src/BaGet.Core/Entities/Package.cs +++ b/src/BaGet.Core/Entities/Package.cs @@ -1,59 +1,59 @@ -using System; -using System.Collections.Generic; -using NuGet.Versioning; - -namespace BaGet.Core.Entities -{ - // See NuGetGallery's: https://github.com/NuGet/NuGetGallery/blob/master/src/NuGetGallery.Core/Entities/Package.cs - public class Package - { - public int Key { get; set; } - - public string Id { get; set; } - public NuGetVersion Version { get; set; } - - public string[] Authors { get; set; } - public string Description { get; set; } - public long Downloads { get; set; } - public bool HasReadme { get; set; } - public string Language { get; set; } - public bool Listed { get; set; } - public string MinClientVersion { get; set; } - public DateTime Published { get; set; } - public bool RequireLicenseAcceptance { get; set; } - public string Summary { get; set; } - public string Title { get; set; } - - public Uri IconUrl { get; set; } - public Uri LicenseUrl { get; set; } - public Uri ProjectUrl { get; set; } - - public Uri RepositoryUrl { get; set; } - public string RepositoryType { get; set; } - - public string[] Tags { get; set; } - - /// - /// Used for optimistic concurrency. - /// - public byte[] RowVersion { get; set; } - - public List Dependencies { get; set; } - - public string VersionString - { - get => Version?.ToNormalizedString().ToLowerInvariant() ?? string.Empty; - set - { - NuGetVersion.TryParse(value, out var version); - - Version = version; - } - } - - public string IconUrlString => IconUrl?.AbsoluteUri ?? string.Empty; - public string LicenseUrlString => LicenseUrl?.AbsoluteUri ?? string.Empty; - public string ProjectUrlString => ProjectUrl?.AbsoluteUri ?? string.Empty; - public string RepositoryUrlString => RepositoryUrl?.AbsoluteUri ?? string.Empty; - } -} +using System; +using System.Collections.Generic; +using NuGet.Versioning; + +namespace BaGet.Core.Entities +{ + // See NuGetGallery's: https://github.com/NuGet/NuGetGallery/blob/master/src/NuGetGallery.Core/Entities/Package.cs + public class Package + { + public int Key { get; set; } + + public string Id { get; set; } + public NuGetVersion Version { get; set; } + + public string[] Authors { get; set; } + public string Description { get; set; } + public long Downloads { get; set; } + public bool HasReadme { get; set; } + public string Language { get; set; } + public bool Listed { get; set; } + public string MinClientVersion { get; set; } + public DateTime Published { get; set; } + public bool RequireLicenseAcceptance { get; set; } + public string Summary { get; set; } + public string Title { get; set; } + + public Uri IconUrl { get; set; } + public Uri LicenseUrl { get; set; } + public Uri ProjectUrl { get; set; } + + public Uri RepositoryUrl { get; set; } + public string RepositoryType { get; set; } + + public string[] Tags { get; set; } + + /// + /// Used for optimistic concurrency. + /// + public byte[] RowVersion { get; set; } + + public List Dependencies { get; set; } + + public string VersionString + { + get => Version?.ToNormalizedString().ToLowerInvariant() ?? string.Empty; + set + { + NuGetVersion.TryParse(value, out var version); + + Version = version; + } + } + + public string IconUrlString => IconUrl?.AbsoluteUri ?? string.Empty; + public string LicenseUrlString => LicenseUrl?.AbsoluteUri ?? string.Empty; + public string ProjectUrlString => ProjectUrl?.AbsoluteUri ?? string.Empty; + public string RepositoryUrlString => RepositoryUrl?.AbsoluteUri ?? string.Empty; + } +} diff --git a/src/BaGet.Core/Entities/PackageDependency.cs b/src/BaGet.Core/Entities/PackageDependency.cs index 56fffe81..04a5a20b 100644 --- a/src/BaGet.Core/Entities/PackageDependency.cs +++ b/src/BaGet.Core/Entities/PackageDependency.cs @@ -1,14 +1,14 @@ -namespace BaGet.Core.Entities -{ - // See NuGetGallery.Core's: https://github.com/NuGet/NuGetGallery/blob/master/src/NuGetGallery.Core/Entities/PackageDependency.cs - public class PackageDependency - { - public int Key { get; set; } - - public string Id { get; set; } - public string VersionRange { get; set; } - public string TargetFramework { get; set; } - - public Package Package { get; set; } - } -} +namespace BaGet.Core.Entities +{ + // See NuGetGallery.Core's: https://github.com/NuGet/NuGetGallery/blob/master/src/NuGetGallery.Core/Entities/PackageDependency.cs + public class PackageDependency + { + public int Key { get; set; } + + public string Id { get; set; } + public string VersionRange { get; set; } + public string TargetFramework { get; set; } + + public Package Package { get; set; } + } +} diff --git a/src/BaGet.Core/Extensions/PackageArchiveReaderExtensions.cs b/src/BaGet.Core/Extensions/PackageArchiveReaderExtensions.cs index 5f4cbf60..98349150 100644 --- a/src/BaGet.Core/Extensions/PackageArchiveReaderExtensions.cs +++ b/src/BaGet.Core/Extensions/PackageArchiveReaderExtensions.cs @@ -1,45 +1,45 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; -using NuGet.Packaging; - -namespace BaGet.Core.Extensions -{ - public static class PackageArchiveReaderExtensions - { - private static readonly string[] OrderedReadmeFileNames = new[] - { - "readme.md", - "readme.txt", - }; - - private static readonly HashSet ReadmeFileNames = new HashSet( - OrderedReadmeFileNames, - StringComparer.OrdinalIgnoreCase); - - public static bool HasReadme(this PackageArchiveReader package) - => package.GetFiles().Any(ReadmeFileNames.Contains); - - public async static Task GetReadmeAsync( - this PackageArchiveReader package, - CancellationToken cancellationToken) - { - var packageFiles = package.GetFiles(); - - foreach (var readmeFileName in OrderedReadmeFileNames) - { - var readmePath = packageFiles.FirstOrDefault(f => f.Equals(readmeFileName, StringComparison.OrdinalIgnoreCase)); - - if (readmePath != null) - { - return await package.GetStreamAsync(readmePath, cancellationToken); - } - } - - throw new InvalidOperationException("Package does not have a readme!"); - } - } -} +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using NuGet.Packaging; + +namespace BaGet.Core.Extensions +{ + public static class PackageArchiveReaderExtensions + { + private static readonly string[] OrderedReadmeFileNames = new[] + { + "readme.md", + "readme.txt", + }; + + private static readonly HashSet ReadmeFileNames = new HashSet( + OrderedReadmeFileNames, + StringComparer.OrdinalIgnoreCase); + + public static bool HasReadme(this PackageArchiveReader package) + => package.GetFiles().Any(ReadmeFileNames.Contains); + + public async static Task GetReadmeAsync( + this PackageArchiveReader package, + CancellationToken cancellationToken) + { + var packageFiles = package.GetFiles(); + + foreach (var readmeFileName in OrderedReadmeFileNames) + { + var readmePath = packageFiles.FirstOrDefault(f => f.Equals(readmeFileName, StringComparison.OrdinalIgnoreCase)); + + if (readmePath != null) + { + return await package.GetStreamAsync(readmePath, cancellationToken); + } + } + + throw new InvalidOperationException("Package does not have a readme!"); + } + } +} diff --git a/src/BaGet.Core/Extensions/StreamExtensions.cs b/src/BaGet.Core/Extensions/StreamExtensions.cs index c4a30f50..82cf6e17 100644 --- a/src/BaGet.Core/Extensions/StreamExtensions.cs +++ b/src/BaGet.Core/Extensions/StreamExtensions.cs @@ -1,59 +1,59 @@ -using System; -using System.IO; -using System.Linq; -using System.Security.Cryptography; -using System.Threading; -using System.Threading.Tasks; - -namespace BaGet.Core.Extensions -{ - public static class StreamExtensions - { - // See: https://github.com/dotnet/corefx/blob/master/src/Common/src/CoreLib/System/IO/Stream.cs#L35 - private const int DefaultCopyBufferSize = 81920; - - /// - /// Copies a stream to a file, and returns that file as a stream. The underlying file will be - /// deleted when the resulting stream is disposed. - /// - /// The stream to be copied, at its current position. - /// - /// The copied stream, with its position reset to the beginning. - public static async Task AsTemporaryFileStreamAsync( - this Stream original, - CancellationToken cancellationToken = default) - { - var result = new FileStream( - Path.GetTempFileName(), - FileMode.Create, - FileAccess.ReadWrite, - FileShare.None, - DefaultCopyBufferSize, - FileOptions.DeleteOnClose); - - try - { - await original.CopyToAsync(result, DefaultCopyBufferSize, cancellationToken); - result.Position = 0; - } - catch (Exception) - { - result.Dispose(); - throw; - } - - return result; - } - - public static bool Matches(this Stream content, Stream target) - { - using (var sha256 = SHA256.Create()) - { - var contentHash = sha256.ComputeHash(content); - var targetHash = sha256.ComputeHash(target); - - return contentHash.SequenceEqual(targetHash); - } - } - } -} +using System; +using System.IO; +using System.Linq; +using System.Security.Cryptography; +using System.Threading; +using System.Threading.Tasks; + +namespace BaGet.Core.Extensions +{ + public static class StreamExtensions + { + // See: https://github.com/dotnet/corefx/blob/master/src/Common/src/CoreLib/System/IO/Stream.cs#L35 + private const int DefaultCopyBufferSize = 81920; + + /// + /// Copies a stream to a file, and returns that file as a stream. The underlying file will be + /// deleted when the resulting stream is disposed. + /// + /// The stream to be copied, at its current position. + /// + /// The copied stream, with its position reset to the beginning. + public static async Task AsTemporaryFileStreamAsync( + this Stream original, + CancellationToken cancellationToken = default) + { + var result = new FileStream( + Path.GetTempFileName(), + FileMode.Create, + FileAccess.ReadWrite, + FileShare.None, + DefaultCopyBufferSize, + FileOptions.DeleteOnClose); + + try + { + await original.CopyToAsync(result, DefaultCopyBufferSize, cancellationToken); + result.Position = 0; + } + catch (Exception) + { + result.Dispose(); + throw; + } + + return result; + } + + public static bool Matches(this Stream content, Stream target) + { + using (var sha256 = SHA256.Create()) + { + var contentHash = sha256.ComputeHash(content); + var targetHash = sha256.ComputeHash(target); + + return contentHash.SequenceEqual(targetHash); + } + } + } +} diff --git a/src/BaGet.Core/Mirror/DownloadsImporter.cs b/src/BaGet.Core/Mirror/DownloadsImporter.cs index 7e76250e..1a0f9205 100644 --- a/src/BaGet.Core/Mirror/DownloadsImporter.cs +++ b/src/BaGet.Core/Mirror/DownloadsImporter.cs @@ -1,66 +1,66 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; -using BaGet.Core.Entities; -using Microsoft.EntityFrameworkCore; -using Microsoft.Extensions.Logging; - -namespace BaGet.Core.Mirror -{ - public class DownloadsImporter - { - private const int BatchSize = 200; - - private readonly IContext _context; - private readonly IPackageDownloadsSource _downloadsSource; - private readonly ILogger _logger; - - public DownloadsImporter( - IContext context, - IPackageDownloadsSource downloadsSource, - ILogger logger) - { - _context = context ?? throw new ArgumentNullException(nameof(context)); - _downloadsSource = downloadsSource ?? throw new ArgumentNullException(nameof(downloadsSource)); - _logger = logger ?? throw new ArgumentNullException(nameof(logger)); - } - - public async Task ImportAsync() - { - var packageDownloads = await _downloadsSource.GetPackageDownloadsAsync(); - var packages = await _context.Packages.CountAsync(); - var batches = (packages / BatchSize) + 1; - - for (var batch = 0; batch < batches; batch++) - { - _logger.LogInformation("Importing batch {Batch}...", batch); - - foreach (var package in await GetBatch(batch)) - { - var packageId = package.Id.ToLowerInvariant(); - var packageVersion = package.VersionString.ToLowerInvariant(); - - if (!packageDownloads.ContainsKey(packageId) || - !packageDownloads[packageId].ContainsKey(packageVersion)) - { - continue; - } - - package.Downloads = packageDownloads[packageId][packageVersion]; - } - - await _context.SaveChangesAsync(); - - _logger.LogInformation("Imported batch {Batch}", batch); - } - } - - private Task> GetBatch(int batch) - => _context.Packages - .OrderBy(p => p.Key) - .Skip(batch * BatchSize) - .Take(BatchSize) - .ToListAsync(); - } -} +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using BaGet.Core.Entities; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Logging; + +namespace BaGet.Core.Mirror +{ + public class DownloadsImporter + { + private const int BatchSize = 200; + + private readonly IContext _context; + private readonly IPackageDownloadsSource _downloadsSource; + private readonly ILogger _logger; + + public DownloadsImporter( + IContext context, + IPackageDownloadsSource downloadsSource, + ILogger logger) + { + _context = context ?? throw new ArgumentNullException(nameof(context)); + _downloadsSource = downloadsSource ?? throw new ArgumentNullException(nameof(downloadsSource)); + _logger = logger ?? throw new ArgumentNullException(nameof(logger)); + } + + public async Task ImportAsync() + { + var packageDownloads = await _downloadsSource.GetPackageDownloadsAsync(); + var packages = await _context.Packages.CountAsync(); + var batches = (packages / BatchSize) + 1; + + for (var batch = 0; batch < batches; batch++) + { + _logger.LogInformation("Importing batch {Batch}...", batch); + + foreach (var package in await GetBatch(batch)) + { + var packageId = package.Id.ToLowerInvariant(); + var packageVersion = package.VersionString.ToLowerInvariant(); + + if (!packageDownloads.ContainsKey(packageId) || + !packageDownloads[packageId].ContainsKey(packageVersion)) + { + continue; + } + + package.Downloads = packageDownloads[packageId][packageVersion]; + } + + await _context.SaveChangesAsync(); + + _logger.LogInformation("Imported batch {Batch}", batch); + } + } + + private Task> GetBatch(int batch) + => _context.Packages + .OrderBy(p => p.Key) + .Skip(batch * BatchSize) + .Take(BatchSize) + .ToListAsync(); + } +} diff --git a/src/BaGet.Core/Mirror/FakeMirrorService.cs b/src/BaGet.Core/Mirror/FakeMirrorService.cs index 7770529b..3ef157f4 100644 --- a/src/BaGet.Core/Mirror/FakeMirrorService.cs +++ b/src/BaGet.Core/Mirror/FakeMirrorService.cs @@ -1,29 +1,29 @@ -using System.Collections.Generic; -using System.Threading; -using System.Threading.Tasks; -using BaGet.Core.Entities; -using NuGet.Versioning; - -namespace BaGet.Core.Mirror -{ - /// - /// The mirror service used when mirroring has been disabled. - /// - public class FakeMirrorService : IMirrorService - { - public Task> FindPackageVersionsOrNullAsync(string id, CancellationToken cancellationToken) - { - return Task.FromResult>(null); - } - - public Task> FindPackagesOrNullAsync(string id, CancellationToken cancellationToken) - { - return Task.FromResult>(null); - } - - public Task MirrorAsync(string id, NuGetVersion version, CancellationToken cancellationToken) - { - return Task.CompletedTask; - } - } -} +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; +using BaGet.Core.Entities; +using NuGet.Versioning; + +namespace BaGet.Core.Mirror +{ + /// + /// The mirror service used when mirroring has been disabled. + /// + public class FakeMirrorService : IMirrorService + { + public Task> FindPackageVersionsOrNullAsync(string id, CancellationToken cancellationToken) + { + return Task.FromResult>(null); + } + + public Task> FindPackagesOrNullAsync(string id, CancellationToken cancellationToken) + { + return Task.FromResult>(null); + } + + public Task MirrorAsync(string id, NuGetVersion version, CancellationToken cancellationToken) + { + return Task.CompletedTask; + } + } +} diff --git a/src/BaGet.Core/Mirror/IMirrorService.cs b/src/BaGet.Core/Mirror/IMirrorService.cs index 3ba67abd..48b4ff7b 100644 --- a/src/BaGet.Core/Mirror/IMirrorService.cs +++ b/src/BaGet.Core/Mirror/IMirrorService.cs @@ -1,47 +1,47 @@ -using System.Collections.Generic; -using System.Threading; -using System.Threading.Tasks; -using BaGet.Core.Entities; -using NuGet.Versioning; - -namespace BaGet.Core.Mirror -{ - /// - /// Indexes packages from an external source. - /// - public interface IMirrorService - { - /// - /// Attempt to find a package's versions using mirroring. This will merge - /// results from the configured upstream source with the locally indexed packages. - /// - /// The package's id to lookup - /// The token to cancel the lookup - /// - /// The package's versions, or null if the package cannot be found on the - /// configured upstream source. This includes unlisted versions. - /// - Task> FindPackageVersionsOrNullAsync(string id, CancellationToken cancellationToken); - - /// - /// Attempt to find a package's metadata using mirroring. This will merge - /// results from the configured upstream source with the locally indexed packages. - /// - /// The package's id to lookup - /// The token to cancel the lookup - /// - /// The package's metadata, or null if the package cannot be found on the configured - /// upstream source. - /// - Task> FindPackagesOrNullAsync(string id, CancellationToken cancellationToken); - - /// - /// If the package is unknown, attempt to index it from an upstream source. - /// - /// The package's id - /// The package's version - /// The token to cancel the mirroring - /// A task that completes when the package has been mirrored. - Task MirrorAsync(string id, NuGetVersion version, CancellationToken cancellationToken); - } -} +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; +using BaGet.Core.Entities; +using NuGet.Versioning; + +namespace BaGet.Core.Mirror +{ + /// + /// Indexes packages from an external source. + /// + public interface IMirrorService + { + /// + /// Attempt to find a package's versions using mirroring. This will merge + /// results from the configured upstream source with the locally indexed packages. + /// + /// The package's id to lookup + /// The token to cancel the lookup + /// + /// The package's versions, or null if the package cannot be found on the + /// configured upstream source. This includes unlisted versions. + /// + Task> FindPackageVersionsOrNullAsync(string id, CancellationToken cancellationToken); + + /// + /// Attempt to find a package's metadata using mirroring. This will merge + /// results from the configured upstream source with the locally indexed packages. + /// + /// The package's id to lookup + /// The token to cancel the lookup + /// + /// The package's metadata, or null if the package cannot be found on the configured + /// upstream source. + /// + Task> FindPackagesOrNullAsync(string id, CancellationToken cancellationToken); + + /// + /// If the package is unknown, attempt to index it from an upstream source. + /// + /// The package's id + /// The package's version + /// The token to cancel the mirroring + /// A task that completes when the package has been mirrored. + Task MirrorAsync(string id, NuGetVersion version, CancellationToken cancellationToken); + } +} diff --git a/src/BaGet.Core/Mirror/IPackageDownloader.cs b/src/BaGet.Core/Mirror/IPackageDownloader.cs index 292abc10..49ed659f 100644 --- a/src/BaGet.Core/Mirror/IPackageDownloader.cs +++ b/src/BaGet.Core/Mirror/IPackageDownloader.cs @@ -1,18 +1,18 @@ -using System; -using System.IO; -using System.Threading; -using System.Threading.Tasks; - -namespace BaGet.Core.Mirror -{ - public interface IPackageDownloader - { - /// - /// Attempt to download a package. - /// - /// The package to download. - /// The token to cancel the download. - /// The package, or null if it couldn't be downloaded. - Task DownloadOrNullAsync(Uri packageUri, CancellationToken cancellationToken); - } -} +using System; +using System.IO; +using System.Threading; +using System.Threading.Tasks; + +namespace BaGet.Core.Mirror +{ + public interface IPackageDownloader + { + /// + /// Attempt to download a package. + /// + /// The package to download. + /// The token to cancel the download. + /// The package, or null if it couldn't be downloaded. + Task DownloadOrNullAsync(Uri packageUri, CancellationToken cancellationToken); + } +} diff --git a/src/BaGet.Core/Mirror/IPackageDownloadsSource.cs b/src/BaGet.Core/Mirror/IPackageDownloadsSource.cs index fe37ac9e..fc051b92 100644 --- a/src/BaGet.Core/Mirror/IPackageDownloadsSource.cs +++ b/src/BaGet.Core/Mirror/IPackageDownloadsSource.cs @@ -1,10 +1,10 @@ -using System.Collections.Generic; -using System.Threading.Tasks; - -namespace BaGet.Core.Mirror -{ - public interface IPackageDownloadsSource - { - Task>> GetPackageDownloadsAsync(); - } -} +using System.Collections.Generic; +using System.Threading.Tasks; + +namespace BaGet.Core.Mirror +{ + public interface IPackageDownloadsSource + { + Task>> GetPackageDownloadsAsync(); + } +} diff --git a/src/BaGet.Core/Mirror/MirrorService.cs b/src/BaGet.Core/Mirror/MirrorService.cs index 7beacf72..51ca342c 100644 --- a/src/BaGet.Core/Mirror/MirrorService.cs +++ b/src/BaGet.Core/Mirror/MirrorService.cs @@ -1,240 +1,240 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; -using BaGet.Core.Entities; -using BaGet.Core.Services; -using BaGet.Protocol; -using Microsoft.Extensions.Logging; -using NuGet.Versioning; - -namespace BaGet.Core.Mirror -{ - using PackageIdentity = NuGet.Packaging.Core.PackageIdentity; - - public class MirrorService : IMirrorService - { - private readonly IPackageService _localPackages; - private readonly IPackageMetadataService _upstreamFeed; - private readonly IPackageDownloader _downloader; - private readonly IPackageIndexingService _indexer; - private readonly ILogger _logger; - - public MirrorService( - IPackageService localPackages, - IPackageMetadataService upstreamFeed, - IPackageDownloader downloader, - IPackageIndexingService indexer, - ILogger logger) - { - _localPackages = localPackages ?? throw new ArgumentNullException(nameof(localPackages)); - _upstreamFeed = upstreamFeed ?? throw new ArgumentNullException(nameof(upstreamFeed)); - _downloader = downloader ?? throw new ArgumentNullException(nameof(downloader)); - _indexer = indexer ?? throw new ArgumentNullException(nameof(indexer)); - _logger = logger ?? throw new ArgumentNullException(nameof(logger)); - } - - public async Task> FindPackageVersionsOrNullAsync( - string id, - CancellationToken cancellationToken) - { - var includeUnlisted = true; - var versions = await _upstreamFeed.GetAllVersionsOrNullAsync(id, includeUnlisted, cancellationToken); - if (versions == null) - { - return null; - } - - // Merge the local package versions into the upstream package versions. - var localPackages = await _localPackages.FindAsync(id, includeUnlisted); - var localVersions = localPackages.Select(p => p.Version); - - return versions.Concat(localVersions).Distinct().ToList(); - } - - public async Task> FindPackagesOrNullAsync(string id, CancellationToken cancellationToken) - { - var upstreamPackageMetadata = await _upstreamFeed.GetAllMetadataOrNullAsync(id, cancellationToken); - if (upstreamPackageMetadata == null) - { - return null; - } - - var upstreamPackages = upstreamPackageMetadata.Select(ToPackage); - - // Return the upstream packages if there are no local packages matching the package id. - var localPackages = await _localPackages.FindAsync(id, includeUnlisted: true); - if (!localPackages.Any()) - { - return upstreamPackages.ToList(); - } - - // Otherwise, merge the local packages into the upstream packages. - var result = upstreamPackages.ToDictionary(p => new PackageIdentity(p.Id, p.Version)); - var local = localPackages.ToDictionary(p => new PackageIdentity(p.Id, p.Version)); - - foreach (var localPackage in local) - { - result[localPackage.Key] = localPackage.Value; - } - - return result.Values.ToList(); - } - - public async Task MirrorAsync(string id, NuGetVersion version, CancellationToken cancellationToken) - { - if (await _localPackages.ExistsAsync(id, version)) - { - return; - } - - _logger.LogInformation( - "Package {PackageId} {PackageVersion} does not exist locally. Indexing from upstream feed...", - id, - version); - - await IndexFromSourceAsync(id, version, cancellationToken); - - _logger.LogInformation( - "Finished indexing {PackageId} {PackageVersion} from the upstream feed", - id, - version); - } - - private Package ToPackage(PackageMetadata metadata) - { - return new Package - { - Id = metadata.PackageId, - Version = metadata.Version, - Authors = ParseAuthors(metadata.Authors), - Description = metadata.Description, - Downloads = metadata.Downloads, - HasReadme = metadata.HasReadme, - Language = metadata.Language, - Listed = metadata.Listed, - MinClientVersion = metadata.MinClientVersion, - Published = metadata.Published, - RequireLicenseAcceptance = metadata.RequireLicenseAcceptance, - Summary = metadata.Summary, - Title = metadata.Title, - IconUrl = ParseUri(metadata.IconUrl), - LicenseUrl = ParseUri(metadata.LicenseUrl), - ProjectUrl = ParseUri(metadata.ProjectUrl), - RepositoryUrl = ParseUri(metadata.RepositoryUrl), - RepositoryType = metadata.RepositoryType, - Tags = metadata.Tags.ToArray(), - - Dependencies = FindDependencies(metadata) - }; - } - - private Uri ParseUri(string uriString) - { - if (uriString == null) return null; - - if (!Uri.TryCreate(uriString, UriKind.Absolute, out var uri)) - { - return null; - } - - return uri; - } - - private string[] ParseAuthors(string authors) - { - if (string.IsNullOrEmpty(authors)) return new string[0]; - - return authors - .Split(new[] { ',', ';', '\t', '\n', '\r' }, StringSplitOptions.RemoveEmptyEntries) - .Select(a => a.Trim()) - .ToArray(); - } - - private List FindDependencies(PackageMetadata package) - { - if ((package.DependencyGroups?.Count ?? 0) == 0) - { - return new List(); - } - - return package.DependencyGroups - .SelectMany(FindDependenciesFromDependencyGroup) - .ToList(); - } - - private IEnumerable FindDependenciesFromDependencyGroup(DependencyGroupItem group) - { - // BaGet stores a dependency group with no dependencies as a package dependency - // with no package id nor package version. - if ((group.Dependencies?.Count ?? 0) == 0) - { - return new[] - { - new PackageDependency - { - Id = null, - VersionRange = null, - TargetFramework = group.TargetFramework - } - }; - } - - return group.Dependencies.Select(d => new PackageDependency - { - Id = d.Id, - VersionRange = d.Range, - TargetFramework = group.TargetFramework - }); - } - - private async Task IndexFromSourceAsync(string id, NuGetVersion version, CancellationToken cancellationToken) - { - cancellationToken.ThrowIfCancellationRequested(); - - _logger.LogInformation( - "Attempting to mirror package {PackageId} {PackageVersion}...", - id, - version); - - try - { - var packageUri = await _upstreamFeed.GetPackageContentUriAsync(id, version); - - using (var stream = await _downloader.DownloadOrNullAsync(packageUri, cancellationToken)) - { - if (stream == null) - { - _logger.LogWarning( - "Failed to download package {PackageId} {PackageVersion}", - id, - version); - - return; - } - - _logger.LogInformation( - "Downloaded package {PackageId} {PackageVersion}, indexing...", - id, - version); - - var result = await _indexer.IndexAsync(stream, cancellationToken); - - _logger.LogInformation( - "Finished indexing package {PackageId} {PackageVersion} with result {Result}", - packageUri, - result); - } - } - catch (Exception e) - { - _logger.LogError( - e, - "Failed to mirror package {PackageId} {PackageVersion}", - id, - version); - } - } - } -} +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using BaGet.Core.Entities; +using BaGet.Core.Services; +using BaGet.Protocol; +using Microsoft.Extensions.Logging; +using NuGet.Versioning; + +namespace BaGet.Core.Mirror +{ + using PackageIdentity = NuGet.Packaging.Core.PackageIdentity; + + public class MirrorService : IMirrorService + { + private readonly IPackageService _localPackages; + private readonly IPackageMetadataService _upstreamFeed; + private readonly IPackageDownloader _downloader; + private readonly IPackageIndexingService _indexer; + private readonly ILogger _logger; + + public MirrorService( + IPackageService localPackages, + IPackageMetadataService upstreamFeed, + IPackageDownloader downloader, + IPackageIndexingService indexer, + ILogger logger) + { + _localPackages = localPackages ?? throw new ArgumentNullException(nameof(localPackages)); + _upstreamFeed = upstreamFeed ?? throw new ArgumentNullException(nameof(upstreamFeed)); + _downloader = downloader ?? throw new ArgumentNullException(nameof(downloader)); + _indexer = indexer ?? throw new ArgumentNullException(nameof(indexer)); + _logger = logger ?? throw new ArgumentNullException(nameof(logger)); + } + + public async Task> FindPackageVersionsOrNullAsync( + string id, + CancellationToken cancellationToken) + { + var includeUnlisted = true; + var versions = await _upstreamFeed.GetAllVersionsOrNullAsync(id, includeUnlisted, cancellationToken); + if (versions == null) + { + return null; + } + + // Merge the local package versions into the upstream package versions. + var localPackages = await _localPackages.FindAsync(id, includeUnlisted); + var localVersions = localPackages.Select(p => p.Version); + + return versions.Concat(localVersions).Distinct().ToList(); + } + + public async Task> FindPackagesOrNullAsync(string id, CancellationToken cancellationToken) + { + var upstreamPackageMetadata = await _upstreamFeed.GetAllMetadataOrNullAsync(id, cancellationToken); + if (upstreamPackageMetadata == null) + { + return null; + } + + var upstreamPackages = upstreamPackageMetadata.Select(ToPackage); + + // Return the upstream packages if there are no local packages matching the package id. + var localPackages = await _localPackages.FindAsync(id, includeUnlisted: true); + if (!localPackages.Any()) + { + return upstreamPackages.ToList(); + } + + // Otherwise, merge the local packages into the upstream packages. + var result = upstreamPackages.ToDictionary(p => new PackageIdentity(p.Id, p.Version)); + var local = localPackages.ToDictionary(p => new PackageIdentity(p.Id, p.Version)); + + foreach (var localPackage in local) + { + result[localPackage.Key] = localPackage.Value; + } + + return result.Values.ToList(); + } + + public async Task MirrorAsync(string id, NuGetVersion version, CancellationToken cancellationToken) + { + if (await _localPackages.ExistsAsync(id, version)) + { + return; + } + + _logger.LogInformation( + "Package {PackageId} {PackageVersion} does not exist locally. Indexing from upstream feed...", + id, + version); + + await IndexFromSourceAsync(id, version, cancellationToken); + + _logger.LogInformation( + "Finished indexing {PackageId} {PackageVersion} from the upstream feed", + id, + version); + } + + private Package ToPackage(PackageMetadata metadata) + { + return new Package + { + Id = metadata.PackageId, + Version = metadata.Version, + Authors = ParseAuthors(metadata.Authors), + Description = metadata.Description, + Downloads = metadata.Downloads, + HasReadme = metadata.HasReadme, + Language = metadata.Language, + Listed = metadata.Listed, + MinClientVersion = metadata.MinClientVersion, + Published = metadata.Published, + RequireLicenseAcceptance = metadata.RequireLicenseAcceptance, + Summary = metadata.Summary, + Title = metadata.Title, + IconUrl = ParseUri(metadata.IconUrl), + LicenseUrl = ParseUri(metadata.LicenseUrl), + ProjectUrl = ParseUri(metadata.ProjectUrl), + RepositoryUrl = ParseUri(metadata.RepositoryUrl), + RepositoryType = metadata.RepositoryType, + Tags = metadata.Tags.ToArray(), + + Dependencies = FindDependencies(metadata) + }; + } + + private Uri ParseUri(string uriString) + { + if (uriString == null) return null; + + if (!Uri.TryCreate(uriString, UriKind.Absolute, out var uri)) + { + return null; + } + + return uri; + } + + private string[] ParseAuthors(string authors) + { + if (string.IsNullOrEmpty(authors)) return new string[0]; + + return authors + .Split(new[] { ',', ';', '\t', '\n', '\r' }, StringSplitOptions.RemoveEmptyEntries) + .Select(a => a.Trim()) + .ToArray(); + } + + private List FindDependencies(PackageMetadata package) + { + if ((package.DependencyGroups?.Count ?? 0) == 0) + { + return new List(); + } + + return package.DependencyGroups + .SelectMany(FindDependenciesFromDependencyGroup) + .ToList(); + } + + private IEnumerable FindDependenciesFromDependencyGroup(DependencyGroupItem group) + { + // BaGet stores a dependency group with no dependencies as a package dependency + // with no package id nor package version. + if ((group.Dependencies?.Count ?? 0) == 0) + { + return new[] + { + new PackageDependency + { + Id = null, + VersionRange = null, + TargetFramework = group.TargetFramework + } + }; + } + + return group.Dependencies.Select(d => new PackageDependency + { + Id = d.Id, + VersionRange = d.Range, + TargetFramework = group.TargetFramework + }); + } + + private async Task IndexFromSourceAsync(string id, NuGetVersion version, CancellationToken cancellationToken) + { + cancellationToken.ThrowIfCancellationRequested(); + + _logger.LogInformation( + "Attempting to mirror package {PackageId} {PackageVersion}...", + id, + version); + + try + { + var packageUri = await _upstreamFeed.GetPackageContentUriAsync(id, version); + + using (var stream = await _downloader.DownloadOrNullAsync(packageUri, cancellationToken)) + { + if (stream == null) + { + _logger.LogWarning( + "Failed to download package {PackageId} {PackageVersion}", + id, + version); + + return; + } + + _logger.LogInformation( + "Downloaded package {PackageId} {PackageVersion}, indexing...", + id, + version); + + var result = await _indexer.IndexAsync(stream, cancellationToken); + + _logger.LogInformation( + "Finished indexing package {PackageId} {PackageVersion} with result {Result}", + packageUri, + result); + } + } + catch (Exception e) + { + _logger.LogError( + e, + "Failed to mirror package {PackageId} {PackageVersion}", + id, + version); + } + } + } +} diff --git a/src/BaGet.Core/Mirror/PackageDownloader.cs b/src/BaGet.Core/Mirror/PackageDownloader.cs index 743338cc..7507ed6b 100644 --- a/src/BaGet.Core/Mirror/PackageDownloader.cs +++ b/src/BaGet.Core/Mirror/PackageDownloader.cs @@ -1,82 +1,82 @@ -using System; -using System.Diagnostics; -using System.IO; -using System.Net; -using System.Net.Http; -using System.Threading; -using System.Threading.Tasks; -using BaGet.Core.Extensions; -using Microsoft.Extensions.Logging; - -namespace BaGet.Core.Mirror -{ - // See: https://github.com/NuGet/NuGet.Jobs/blob/master/src/Validation.Common.Job/PackageDownloader.cs - public class PackageDownloader : IPackageDownloader - { - private readonly HttpClient _httpClient; - private readonly ILogger _logger; - - public PackageDownloader(HttpClient httpClient, ILogger logger) - { - _httpClient = httpClient ?? throw new ArgumentNullException(nameof(httpClient)); - _logger = logger ?? throw new ArgumentNullException(nameof(logger)); - } - - public async Task DownloadOrNullAsync(Uri packageUri, CancellationToken cancellationToken) - { - cancellationToken.ThrowIfCancellationRequested(); - - _logger.LogInformation("Attempting to download package from {PackageUri}...", packageUri); - - Stream packageStream = null; - var stopwatch = Stopwatch.StartNew(); - - try - { - // Download the package from the network to a temporary file. - using (var response = await _httpClient.GetAsync(packageUri, HttpCompletionOption.ResponseHeadersRead, cancellationToken)) - { - _logger.LogInformation( - "Received response {StatusCode}: {ReasonPhrase} of type {ContentType} for request {PackageUri}", - response.StatusCode, - response.ReasonPhrase, - response.Content.Headers.ContentType, - packageUri); - - if (response.StatusCode != HttpStatusCode.OK) - { - _logger.LogWarning( - $"Expected status code {HttpStatusCode.OK} for package download, actual: {{ResponseStatusCode}}", - response.StatusCode); - - return null; - } - - using (var networkStream = await response.Content.ReadAsStreamAsync()) - { - packageStream = await networkStream.AsTemporaryFileStreamAsync(cancellationToken); - } - } - - _logger.LogInformation( - "Downloaded {PackageSizeInBytes} bytes in {DownloadElapsedTime} seconds for request {PackageUri}", - packageStream.Length, - stopwatch.Elapsed.TotalSeconds, - packageUri); - - return packageStream; - } - catch (Exception e) - { - _logger.LogError( - e, - "Exception thrown when trying to download package from {PackageUri}", - packageUri); - - packageStream?.Dispose(); - - return null; - } - } - } -} +using System; +using System.Diagnostics; +using System.IO; +using System.Net; +using System.Net.Http; +using System.Threading; +using System.Threading.Tasks; +using BaGet.Core.Extensions; +using Microsoft.Extensions.Logging; + +namespace BaGet.Core.Mirror +{ + // See: https://github.com/NuGet/NuGet.Jobs/blob/master/src/Validation.Common.Job/PackageDownloader.cs + public class PackageDownloader : IPackageDownloader + { + private readonly HttpClient _httpClient; + private readonly ILogger _logger; + + public PackageDownloader(HttpClient httpClient, ILogger logger) + { + _httpClient = httpClient ?? throw new ArgumentNullException(nameof(httpClient)); + _logger = logger ?? throw new ArgumentNullException(nameof(logger)); + } + + public async Task DownloadOrNullAsync(Uri packageUri, CancellationToken cancellationToken) + { + cancellationToken.ThrowIfCancellationRequested(); + + _logger.LogInformation("Attempting to download package from {PackageUri}...", packageUri); + + Stream packageStream = null; + var stopwatch = Stopwatch.StartNew(); + + try + { + // Download the package from the network to a temporary file. + using (var response = await _httpClient.GetAsync(packageUri, HttpCompletionOption.ResponseHeadersRead, cancellationToken)) + { + _logger.LogInformation( + "Received response {StatusCode}: {ReasonPhrase} of type {ContentType} for request {PackageUri}", + response.StatusCode, + response.ReasonPhrase, + response.Content.Headers.ContentType, + packageUri); + + if (response.StatusCode != HttpStatusCode.OK) + { + _logger.LogWarning( + $"Expected status code {HttpStatusCode.OK} for package download, actual: {{ResponseStatusCode}}", + response.StatusCode); + + return null; + } + + using (var networkStream = await response.Content.ReadAsStreamAsync()) + { + packageStream = await networkStream.AsTemporaryFileStreamAsync(cancellationToken); + } + } + + _logger.LogInformation( + "Downloaded {PackageSizeInBytes} bytes in {DownloadElapsedTime} seconds for request {PackageUri}", + packageStream.Length, + stopwatch.Elapsed.TotalSeconds, + packageUri); + + return packageStream; + } + catch (Exception e) + { + _logger.LogError( + e, + "Exception thrown when trying to download package from {PackageUri}", + packageUri); + + packageStream?.Dispose(); + + return null; + } + } + } +} diff --git a/src/BaGet.Core/Mirror/PackageDownloadsJsonSource.cs b/src/BaGet.Core/Mirror/PackageDownloadsJsonSource.cs index 8860470b..60a5d040 100644 --- a/src/BaGet.Core/Mirror/PackageDownloadsJsonSource.cs +++ b/src/BaGet.Core/Mirror/PackageDownloadsJsonSource.cs @@ -1,110 +1,110 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Net.Http; -using System.Threading.Tasks; -using Microsoft.Extensions.Logging; -using Newtonsoft.Json; -using Newtonsoft.Json.Linq; -using NuGet.Versioning; - -namespace BaGet.Core.Mirror -{ - // See https://github.com/NuGet/NuGet.Services.Metadata/blob/master/src/NuGet.Indexing/Downloads.cs - public class PackageDownloadsJsonSource : IPackageDownloadsSource - { - public const string PackageDownloadsV1Url = "https://nugetprod0.blob.core.windows.net/ng-search-data/downloads.v1.json"; - - private readonly HttpClient _httpClient; - private readonly ILogger _logger; - - public PackageDownloadsJsonSource(HttpClient httpClient, ILogger logger) - { - _httpClient = httpClient ?? throw new ArgumentNullException(nameof(httpClient)); - _logger = logger ?? throw new ArgumentNullException(nameof(logger)); - } - - public async Task>> GetPackageDownloadsAsync() - { - _logger.LogInformation("Fetching package downloads..."); - - var serializer = new JsonSerializer(); - var results = new Dictionary>(); - - using (var downloadsStream = await GetDownloadsStreamAsync()) - using (var downloadStreamReader = new StreamReader(downloadsStream)) - using (var jsonReader = new JsonTextReader(downloadStreamReader)) - { - _logger.LogInformation("Parsing package downloads..."); - - jsonReader.Read(); - - while (jsonReader.Read()) - { - try - { - if (jsonReader.TokenType == JsonToken.StartArray) - { - // TODO: This line reads the entire document into memory... - var record = JToken.ReadFrom(jsonReader); - var id = string.Intern(record[0].ToString().ToLowerInvariant()); - - // The second entry in each record should be an array of versions, if not move on to next entry. - // This is a check to safe guard against invalid entries. - if (record.Count() == 2 && record[1].Type != JTokenType.Array) - { - continue; - } - - if (!results.ContainsKey(id)) - { - results.Add(id, new Dictionary()); - } - - foreach (var token in record) - { - if (token != null && token.Count() == 2) - { - var version = string.Intern(NuGetVersion.Parse(token[0].ToString()).ToNormalizedString().ToLowerInvariant()); - var downloads = token[1].ToObject(); - - results[id][version] = downloads; - } - } - } - } - catch (JsonReaderException e) - { - _logger.LogError(e, "Invalid entry in downloads.v1.json"); - } - } - - _logger.LogInformation("Parsed package downloads"); - } - - return results; - } - - private async Task GetDownloadsStreamAsync() - { - _logger.LogInformation("Downloading downloads.v1.json..."); - - var fileStream = File.Open(Path.GetTempFileName(), FileMode.Create); - var response = await _httpClient.GetAsync(PackageDownloadsV1Url, HttpCompletionOption.ResponseHeadersRead); - - response.EnsureSuccessStatusCode(); - - using (var networkStream = await response.Content.ReadAsStreamAsync()) - { - await networkStream.CopyToAsync(fileStream); - } - - fileStream.Seek(0, SeekOrigin.Begin); - - _logger.LogInformation("Downloaded downloads.v1.json"); - - return fileStream; - } - } -} +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Net.Http; +using System.Threading.Tasks; +using Microsoft.Extensions.Logging; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using NuGet.Versioning; + +namespace BaGet.Core.Mirror +{ + // See https://github.com/NuGet/NuGet.Services.Metadata/blob/master/src/NuGet.Indexing/Downloads.cs + public class PackageDownloadsJsonSource : IPackageDownloadsSource + { + public const string PackageDownloadsV1Url = "https://nugetprod0.blob.core.windows.net/ng-search-data/downloads.v1.json"; + + private readonly HttpClient _httpClient; + private readonly ILogger _logger; + + public PackageDownloadsJsonSource(HttpClient httpClient, ILogger logger) + { + _httpClient = httpClient ?? throw new ArgumentNullException(nameof(httpClient)); + _logger = logger ?? throw new ArgumentNullException(nameof(logger)); + } + + public async Task>> GetPackageDownloadsAsync() + { + _logger.LogInformation("Fetching package downloads..."); + + var serializer = new JsonSerializer(); + var results = new Dictionary>(); + + using (var downloadsStream = await GetDownloadsStreamAsync()) + using (var downloadStreamReader = new StreamReader(downloadsStream)) + using (var jsonReader = new JsonTextReader(downloadStreamReader)) + { + _logger.LogInformation("Parsing package downloads..."); + + jsonReader.Read(); + + while (jsonReader.Read()) + { + try + { + if (jsonReader.TokenType == JsonToken.StartArray) + { + // TODO: This line reads the entire document into memory... + var record = JToken.ReadFrom(jsonReader); + var id = string.Intern(record[0].ToString().ToLowerInvariant()); + + // The second entry in each record should be an array of versions, if not move on to next entry. + // This is a check to safe guard against invalid entries. + if (record.Count() == 2 && record[1].Type != JTokenType.Array) + { + continue; + } + + if (!results.ContainsKey(id)) + { + results.Add(id, new Dictionary()); + } + + foreach (var token in record) + { + if (token != null && token.Count() == 2) + { + var version = string.Intern(NuGetVersion.Parse(token[0].ToString()).ToNormalizedString().ToLowerInvariant()); + var downloads = token[1].ToObject(); + + results[id][version] = downloads; + } + } + } + } + catch (JsonReaderException e) + { + _logger.LogError(e, "Invalid entry in downloads.v1.json"); + } + } + + _logger.LogInformation("Parsed package downloads"); + } + + return results; + } + + private async Task GetDownloadsStreamAsync() + { + _logger.LogInformation("Downloading downloads.v1.json..."); + + var fileStream = File.Open(Path.GetTempFileName(), FileMode.Create); + var response = await _httpClient.GetAsync(PackageDownloadsV1Url, HttpCompletionOption.ResponseHeadersRead); + + response.EnsureSuccessStatusCode(); + + using (var networkStream = await response.Content.ReadAsStreamAsync()) + { + await networkStream.CopyToAsync(fileStream); + } + + fileStream.Seek(0, SeekOrigin.Begin); + + _logger.LogInformation("Downloaded downloads.v1.json"); + + return fileStream; + } + } +} diff --git a/src/BaGet.Core/Services/ApiKeyAuthenticationService.cs b/src/BaGet.Core/Services/ApiKeyAuthenticationService.cs index 5ae3835a..53c2089f 100644 --- a/src/BaGet.Core/Services/ApiKeyAuthenticationService.cs +++ b/src/BaGet.Core/Services/ApiKeyAuthenticationService.cs @@ -1,56 +1,56 @@ -using System; -using System.Security.Cryptography; -using System.Text; -using System.Threading.Tasks; -using BaGet.Core.Configuration; -using Microsoft.Extensions.Options; - -namespace BaGet.Core.Services -{ - public class ApiKeyAuthenticationService : IAuthenticationService, IDisposable - { - private readonly string _apiKeyHash; - private readonly SHA256 _sha256; - - public ApiKeyAuthenticationService(IOptionsSnapshot options) - { - if (options == null) throw new ArgumentNullException(nameof(options)); - - if (string.IsNullOrEmpty(options.Value.ApiKeyHash)) - { - _apiKeyHash = null; - } - else - { - _apiKeyHash = options.Value.ApiKeyHash.ToLowerInvariant(); - } - - _sha256 = SHA256.Create(); - } - - public Task AuthenticateAsync(string apiKey) => Task.FromResult(Authenticate(apiKey)); - - private bool Authenticate(string apiKey) - { - // No authentication is necessary if there is no required API key. - if (_apiKeyHash == null) return true; - - // Otherwise, an API key is required. - if (string.IsNullOrEmpty(apiKey)) return false; - - return _apiKeyHash == ComputeSHA256Hash(apiKey); - } - - private string ComputeSHA256Hash(string input) - { - var bytes = _sha256.ComputeHash(Encoding.ASCII.GetBytes(input)); - - return BitConverter - .ToString(bytes) - .Replace("-", string.Empty) - .ToLowerInvariant(); - } - - public void Dispose() => _sha256.Dispose(); - } -} +using System; +using System.Security.Cryptography; +using System.Text; +using System.Threading.Tasks; +using BaGet.Core.Configuration; +using Microsoft.Extensions.Options; + +namespace BaGet.Core.Services +{ + public class ApiKeyAuthenticationService : IAuthenticationService, IDisposable + { + private readonly string _apiKeyHash; + private readonly SHA256 _sha256; + + public ApiKeyAuthenticationService(IOptionsSnapshot options) + { + if (options == null) throw new ArgumentNullException(nameof(options)); + + if (string.IsNullOrEmpty(options.Value.ApiKeyHash)) + { + _apiKeyHash = null; + } + else + { + _apiKeyHash = options.Value.ApiKeyHash.ToLowerInvariant(); + } + + _sha256 = SHA256.Create(); + } + + public Task AuthenticateAsync(string apiKey) => Task.FromResult(Authenticate(apiKey)); + + private bool Authenticate(string apiKey) + { + // No authentication is necessary if there is no required API key. + if (_apiKeyHash == null) return true; + + // Otherwise, an API key is required. + if (string.IsNullOrEmpty(apiKey)) return false; + + return _apiKeyHash == ComputeSHA256Hash(apiKey); + } + + private string ComputeSHA256Hash(string input) + { + var bytes = _sha256.ComputeHash(Encoding.ASCII.GetBytes(input)); + + return BitConverter + .ToString(bytes) + .Replace("-", string.Empty) + .ToLowerInvariant(); + } + + public void Dispose() => _sha256.Dispose(); + } +} diff --git a/src/BaGet.Core/Services/DatabaseSearchService.cs b/src/BaGet.Core/Services/DatabaseSearchService.cs index 7e375759..a02e2df8 100644 --- a/src/BaGet.Core/Services/DatabaseSearchService.cs +++ b/src/BaGet.Core/Services/DatabaseSearchService.cs @@ -1,92 +1,92 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; -using BaGet.Core.Entities; -using Microsoft.EntityFrameworkCore; - -namespace BaGet.Core.Services -{ - public class DatabaseSearchService : ISearchService - { - private readonly IContext _context; - - public DatabaseSearchService(IContext context) - { - _context = context ?? throw new ArgumentNullException(nameof(context)); - } - - public Task IndexAsync(Package package) => Task.CompletedTask; - - public async Task> SearchAsync(string query, int skip = 0, int take = 20) - { - IQueryable search = _context.Packages; - - if (!string.IsNullOrEmpty(query)) - { - query = query.ToLower(); - search = search.Where(p => p.Id.ToLower().Contains(query)); - } - - var packages = await _context.Packages - .Where(p => - search.Select(p2 => p2.Id) - .OrderBy(id => id) - .Distinct() - .Skip(skip) - .Take(take) - .Contains(p.Id)) - .GroupBy(p => p.Id) - .ToListAsync(); - - var result = new List(); - - foreach (var package in packages) - { - var versions = package.OrderByDescending(p => p.Version).ToList(); - var latest = versions.First(); - - var versionResults = versions.Select(p => new SearchResultVersion(p.Version, p.Downloads)); - - result.Add(new SearchResult - { - Id = latest.Id, - Version = latest.Version, - Description = latest.Description, - Authors = latest.Authors, - IconUrl = latest.IconUrlString, - LicenseUrl = latest.LicenseUrlString, - ProjectUrl = latest.ProjectUrlString, - Summary = latest.Summary, - Tags = latest.Tags, - Title = latest.Title, - TotalDownloads = versions.Sum(p => p.Downloads), - Versions = versionResults.ToList().AsReadOnly(), - }); - } - - return result.AsReadOnly(); - } - - public async Task> AutocompleteAsync(string query, int skip = 0, int take = 20) - { - IQueryable search = _context.Packages; - - if (!string.IsNullOrEmpty(query)) - { - query = query.ToLower(); - search = search.Where(p => p.Id.ToLower().Contains(query)); - } - - var results = await search.Where(p => p.Listed) - .OrderByDescending(p => p.Downloads) - .Skip(skip) - .Take(take) - .Select(p => p.Id) - .Distinct() - .ToListAsync(); - - return results.AsReadOnly(); - } - } -} +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using BaGet.Core.Entities; +using Microsoft.EntityFrameworkCore; + +namespace BaGet.Core.Services +{ + public class DatabaseSearchService : ISearchService + { + private readonly IContext _context; + + public DatabaseSearchService(IContext context) + { + _context = context ?? throw new ArgumentNullException(nameof(context)); + } + + public Task IndexAsync(Package package) => Task.CompletedTask; + + public async Task> SearchAsync(string query, int skip = 0, int take = 20) + { + IQueryable search = _context.Packages; + + if (!string.IsNullOrEmpty(query)) + { + query = query.ToLower(); + search = search.Where(p => p.Id.ToLower().Contains(query)); + } + + var packages = await _context.Packages + .Where(p => + search.Select(p2 => p2.Id) + .OrderBy(id => id) + .Distinct() + .Skip(skip) + .Take(take) + .Contains(p.Id)) + .GroupBy(p => p.Id) + .ToListAsync(); + + var result = new List(); + + foreach (var package in packages) + { + var versions = package.OrderByDescending(p => p.Version).ToList(); + var latest = versions.First(); + + var versionResults = versions.Select(p => new SearchResultVersion(p.Version, p.Downloads)); + + result.Add(new SearchResult + { + Id = latest.Id, + Version = latest.Version, + Description = latest.Description, + Authors = latest.Authors, + IconUrl = latest.IconUrlString, + LicenseUrl = latest.LicenseUrlString, + ProjectUrl = latest.ProjectUrlString, + Summary = latest.Summary, + Tags = latest.Tags, + Title = latest.Title, + TotalDownloads = versions.Sum(p => p.Downloads), + Versions = versionResults.ToList().AsReadOnly(), + }); + } + + return result.AsReadOnly(); + } + + public async Task> AutocompleteAsync(string query, int skip = 0, int take = 20) + { + IQueryable search = _context.Packages; + + if (!string.IsNullOrEmpty(query)) + { + query = query.ToLower(); + search = search.Where(p => p.Id.ToLower().Contains(query)); + } + + var results = await search.Where(p => p.Listed) + .OrderByDescending(p => p.Downloads) + .Skip(skip) + .Take(take) + .Select(p => p.Id) + .Distinct() + .ToListAsync(); + + return results.AsReadOnly(); + } + } +} diff --git a/src/BaGet.Core/Services/IAuthenticationService.cs b/src/BaGet.Core/Services/IAuthenticationService.cs index 56a6759e..987e84d0 100644 --- a/src/BaGet.Core/Services/IAuthenticationService.cs +++ b/src/BaGet.Core/Services/IAuthenticationService.cs @@ -1,9 +1,9 @@ -using System.Threading.Tasks; - -namespace BaGet.Core.Services -{ - public interface IAuthenticationService - { - Task AuthenticateAsync(string apiKey); - } -} +using System.Threading.Tasks; + +namespace BaGet.Core.Services +{ + public interface IAuthenticationService + { + Task AuthenticateAsync(string apiKey); + } +} diff --git a/src/BaGet.Core/Services/IPackageDeletionService.cs b/src/BaGet.Core/Services/IPackageDeletionService.cs index 79eb1132..6c8fd2d5 100644 --- a/src/BaGet.Core/Services/IPackageDeletionService.cs +++ b/src/BaGet.Core/Services/IPackageDeletionService.cs @@ -1,18 +1,18 @@ -using System.Threading; -using System.Threading.Tasks; -using NuGet.Versioning; - -namespace BaGet.Core.Services -{ - public interface IPackageDeletionService - { - /// - /// Attempt to delete a package. - /// - /// The id of the package to delete. - /// The version of the package to delete. - /// - /// False if the package does not exist. - Task TryDeletePackageAsync(string id, NuGetVersion version, CancellationToken cancellationToken); - } -} +using System.Threading; +using System.Threading.Tasks; +using NuGet.Versioning; + +namespace BaGet.Core.Services +{ + public interface IPackageDeletionService + { + /// + /// Attempt to delete a package. + /// + /// The id of the package to delete. + /// The version of the package to delete. + /// + /// False if the package does not exist. + Task TryDeletePackageAsync(string id, NuGetVersion version, CancellationToken cancellationToken); + } +} diff --git a/src/BaGet.Core/Services/IPackageService.cs b/src/BaGet.Core/Services/IPackageService.cs index 58736a7b..09b18bb3 100644 --- a/src/BaGet.Core/Services/IPackageService.cs +++ b/src/BaGet.Core/Services/IPackageService.cs @@ -1,95 +1,95 @@ -using System.Collections.Generic; -using System.Threading.Tasks; -using BaGet.Core.Entities; -using NuGet.Versioning; - -namespace BaGet.Core.Services -{ - /// - /// The result of attempting to add the package to the database. - /// See - /// - public enum PackageAddResult - { - /// - /// Failed to add the package as it already exists. - /// - PackageAlreadyExists, - - /// - /// The package was added successfully. - /// - Success - } - - /// - /// The "source of truth" for packages' state. Packages' content - /// are stored by the . - /// - public interface IPackageService - { - /// - /// Attempt to add a new package to the database. - /// - /// The package to add to the database. - /// The result of attempting to add the package to the database. - Task AddAsync(Package package); - - /// - /// Attempt to find a package with the given id and version. - /// - /// The package's id. - /// The package's version. - /// Whether unlisted results should be included. - /// The package found, or null. - Task FindOrNullAsync(string id, NuGetVersion version, bool includeUnlisted = false); - - /// - /// Attempt to find all packages with a given id. - /// - /// The packages' id. - /// Whether unlisted results should be included. - /// The packages found. Always non-null. - Task> FindAsync(string id, bool includeUnlisted = false); - - /// - /// Determine whether a package exists in the database (even if the package is unlisted). - /// - /// The package id to search. - /// The package version to search. - /// Whether the package exists in the database. - Task ExistsAsync(string id, NuGetVersion version = null); - - /// - /// Unlist a package, making it undiscoverable. - /// - /// The id of the package to unlist. - /// The version of the package to unlist. - /// False if the package does not exist. - Task UnlistPackageAsync(string id, NuGetVersion version); - - /// - /// Relist a package, making it discoverable. - /// - /// The id of the package to relist. - /// The version of the package to relist. - /// False if the package does not exist. - Task RelistPackageAsync(string id, NuGetVersion version); - - /// - /// Increment a package's download count. - /// - /// The id of the package to update. - /// The id of the package to update. - /// False if the package does not exist. - Task AddDownloadAsync(string id, NuGetVersion version); - - /// - /// Completely remove the package from the database. - /// - /// The id of the package to remove. - /// The version of the pacakge to remove. - /// False if the package doesn't exist. - Task HardDeletePackageAsync(string id, NuGetVersion version); - } -} +using System.Collections.Generic; +using System.Threading.Tasks; +using BaGet.Core.Entities; +using NuGet.Versioning; + +namespace BaGet.Core.Services +{ + /// + /// The result of attempting to add the package to the database. + /// See + /// + public enum PackageAddResult + { + /// + /// Failed to add the package as it already exists. + /// + PackageAlreadyExists, + + /// + /// The package was added successfully. + /// + Success + } + + /// + /// The "source of truth" for packages' state. Packages' content + /// are stored by the . + /// + public interface IPackageService + { + /// + /// Attempt to add a new package to the database. + /// + /// The package to add to the database. + /// The result of attempting to add the package to the database. + Task AddAsync(Package package); + + /// + /// Attempt to find a package with the given id and version. + /// + /// The package's id. + /// The package's version. + /// Whether unlisted results should be included. + /// The package found, or null. + Task FindOrNullAsync(string id, NuGetVersion version, bool includeUnlisted = false); + + /// + /// Attempt to find all packages with a given id. + /// + /// The packages' id. + /// Whether unlisted results should be included. + /// The packages found. Always non-null. + Task> FindAsync(string id, bool includeUnlisted = false); + + /// + /// Determine whether a package exists in the database (even if the package is unlisted). + /// + /// The package id to search. + /// The package version to search. + /// Whether the package exists in the database. + Task ExistsAsync(string id, NuGetVersion version = null); + + /// + /// Unlist a package, making it undiscoverable. + /// + /// The id of the package to unlist. + /// The version of the package to unlist. + /// False if the package does not exist. + Task UnlistPackageAsync(string id, NuGetVersion version); + + /// + /// Relist a package, making it discoverable. + /// + /// The id of the package to relist. + /// The version of the package to relist. + /// False if the package does not exist. + Task RelistPackageAsync(string id, NuGetVersion version); + + /// + /// Increment a package's download count. + /// + /// The id of the package to update. + /// The id of the package to update. + /// False if the package does not exist. + Task AddDownloadAsync(string id, NuGetVersion version); + + /// + /// Completely remove the package from the database. + /// + /// The id of the package to remove. + /// The version of the pacakge to remove. + /// False if the package doesn't exist. + Task HardDeletePackageAsync(string id, NuGetVersion version); + } +} diff --git a/src/BaGet.Core/Services/IPackageStorageService.cs b/src/BaGet.Core/Services/IPackageStorageService.cs index b060cffc..da3ecd10 100644 --- a/src/BaGet.Core/Services/IPackageStorageService.cs +++ b/src/BaGet.Core/Services/IPackageStorageService.cs @@ -1,69 +1,69 @@ -using System.IO; -using System.Threading; -using System.Threading.Tasks; -using BaGet.Core.Entities; -using NuGet.Versioning; - -namespace BaGet.Core.Services -{ - /// - /// Stores packages' content. Packages' state are stored by the - /// . - /// - public interface IPackageStorageService - { - /// - /// Persist a package's content to storage. This operation MUST fail if a package - /// with the same id/version but different content has already been stored. - /// - /// The package's metadata. - /// The package's nupkg stream. - /// The package's nuspec stream. - /// The package's readme stream, or null if none. - /// - /// - Task SavePackageContentAsync( - Package package, - Stream packageStream, - Stream nuspecStream, - Stream readmeStream, - CancellationToken cancellationToken); - - /// - /// Retrieve a package's nupkg stream. - /// - /// The package's id. - /// The package's version. - /// - /// The package's nupkg stream. - Task GetPackageStreamAsync(string id, NuGetVersion version, CancellationToken cancellationToken); - - /// - /// Retrieve a package's nuspec stream. - /// - /// The package's id. - /// The package's version. - /// - /// The package's nuspec stream. - Task GetNuspecStreamAsync(string id, NuGetVersion version, CancellationToken cancellationToken); - - /// - /// Retrieve a package's readme stream. - /// - /// The package's id. - /// The package's version. - /// - /// The package's readme stream. - Task GetReadmeStreamAsync(string id, NuGetVersion version, CancellationToken cancellationToken); - - /// - /// Remove a package's content from storage. This operation SHOULD succeed - /// even if the package does not exist. - /// - /// The package's id. - /// The package's version. - /// - /// - Task DeleteAsync(string id, NuGetVersion version, CancellationToken cancellationToken); - } -} +using System.IO; +using System.Threading; +using System.Threading.Tasks; +using BaGet.Core.Entities; +using NuGet.Versioning; + +namespace BaGet.Core.Services +{ + /// + /// Stores packages' content. Packages' state are stored by the + /// . + /// + public interface IPackageStorageService + { + /// + /// Persist a package's content to storage. This operation MUST fail if a package + /// with the same id/version but different content has already been stored. + /// + /// The package's metadata. + /// The package's nupkg stream. + /// The package's nuspec stream. + /// The package's readme stream, or null if none. + /// + /// + Task SavePackageContentAsync( + Package package, + Stream packageStream, + Stream nuspecStream, + Stream readmeStream, + CancellationToken cancellationToken); + + /// + /// Retrieve a package's nupkg stream. + /// + /// The package's id. + /// The package's version. + /// + /// The package's nupkg stream. + Task GetPackageStreamAsync(string id, NuGetVersion version, CancellationToken cancellationToken); + + /// + /// Retrieve a package's nuspec stream. + /// + /// The package's id. + /// The package's version. + /// + /// The package's nuspec stream. + Task GetNuspecStreamAsync(string id, NuGetVersion version, CancellationToken cancellationToken); + + /// + /// Retrieve a package's readme stream. + /// + /// The package's id. + /// The package's version. + /// + /// The package's readme stream. + Task GetReadmeStreamAsync(string id, NuGetVersion version, CancellationToken cancellationToken); + + /// + /// Remove a package's content from storage. This operation SHOULD succeed + /// even if the package does not exist. + /// + /// The package's id. + /// The package's version. + /// + /// + Task DeleteAsync(string id, NuGetVersion version, CancellationToken cancellationToken); + } +} diff --git a/src/BaGet.Core/Services/ISearchService.cs b/src/BaGet.Core/Services/ISearchService.cs index f5745595..316e08d6 100644 --- a/src/BaGet.Core/Services/ISearchService.cs +++ b/src/BaGet.Core/Services/ISearchService.cs @@ -1,49 +1,49 @@ -using System; -using System.Collections.Generic; -using System.Threading.Tasks; -using BaGet.Core.Entities; -using NuGet.Versioning; - -namespace BaGet.Core.Services -{ - public interface ISearchService - { - Task IndexAsync(Package package); - - Task> SearchAsync(string query, int skip = 0, int take = 20); - - Task> AutocompleteAsync(string query, int skip = 0, int take = 20); - } - - public class SearchResult - { - public string Id { get; set; } - - public NuGetVersion Version { get; set; } - - public string Description { get; set; } - public IReadOnlyList Authors { get; set; } - public string IconUrl { get; set; } - public string LicenseUrl { get; set; } - public string ProjectUrl { get; set; } - public string Summary { get; set; } - public string[] Tags { get; set; } - public string Title { get; set; } - public long TotalDownloads { get; set; } - - public IReadOnlyList Versions { get; set; } - } - - public class SearchResultVersion - { - public SearchResultVersion(NuGetVersion version, long downloads) - { - Version = version ?? throw new ArgumentNullException(nameof(version)); - Downloads = downloads; - } - - public NuGetVersion Version { get; } - - public long Downloads { get; } - } -} +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using BaGet.Core.Entities; +using NuGet.Versioning; + +namespace BaGet.Core.Services +{ + public interface ISearchService + { + Task IndexAsync(Package package); + + Task> SearchAsync(string query, int skip = 0, int take = 20); + + Task> AutocompleteAsync(string query, int skip = 0, int take = 20); + } + + public class SearchResult + { + public string Id { get; set; } + + public NuGetVersion Version { get; set; } + + public string Description { get; set; } + public IReadOnlyList Authors { get; set; } + public string IconUrl { get; set; } + public string LicenseUrl { get; set; } + public string ProjectUrl { get; set; } + public string Summary { get; set; } + public string[] Tags { get; set; } + public string Title { get; set; } + public long TotalDownloads { get; set; } + + public IReadOnlyList Versions { get; set; } + } + + public class SearchResultVersion + { + public SearchResultVersion(NuGetVersion version, long downloads) + { + Version = version ?? throw new ArgumentNullException(nameof(version)); + Downloads = downloads; + } + + public NuGetVersion Version { get; } + + public long Downloads { get; } + } +} diff --git a/src/BaGet.Core/Services/PackageDeletionService.cs b/src/BaGet.Core/Services/PackageDeletionService.cs index f763aee5..11cf6f9b 100644 --- a/src/BaGet.Core/Services/PackageDeletionService.cs +++ b/src/BaGet.Core/Services/PackageDeletionService.cs @@ -1,93 +1,93 @@ -using System; -using System.Threading; -using System.Threading.Tasks; -using BaGet.Core.Configuration; -using Microsoft.Extensions.Logging; -using Microsoft.Extensions.Options; -using NuGet.Versioning; - -namespace BaGet.Core.Services -{ - public class PackageDeletionService : IPackageDeletionService - { - private readonly IPackageService _packages; - private readonly IPackageStorageService _storage; - private readonly BaGetOptions _options; - private readonly ILogger _logger; - - public PackageDeletionService( - IPackageService packages, - IPackageStorageService storage, - IOptionsSnapshot options, - ILogger logger) - { - _packages = packages ?? throw new ArgumentNullException(nameof(packages)); - _storage = storage ?? throw new ArgumentNullException(nameof(storage)); - _options = options?.Value ?? throw new ArgumentNullException(nameof(options)); - _logger = logger ?? throw new ArgumentNullException(nameof(logger)); - } - - public async Task TryDeletePackageAsync(string id, NuGetVersion version, CancellationToken cancellationToken) - { - switch (_options.PackageDeletionBehavior) - { - case PackageDeletionBehavior.Unlist: - return await TryUnlistPackageAsync(id, version); - - case PackageDeletionBehavior.HardDelete: - return await TryHardDeletePackageAsync(id, version, cancellationToken); - - default: - throw new InvalidOperationException($"Unknown deletion behavior '{_options.PackageDeletionBehavior}'"); - } - } - - private async Task TryUnlistPackageAsync(string id, NuGetVersion version) - { - _logger.LogInformation("Unlisting package {PackageId} {PackageVersion}...", id, version); - - if (!await _packages.UnlistPackageAsync(id, version)) - { - _logger.LogWarning("Could not find package {PackageId} {PackageVersion}", id, version); - - return false; - } - - _logger.LogInformation("Unlisted package {PackageId} {PackageVersion}", id, version); - - return true; - } - - private async Task TryHardDeletePackageAsync(string id, NuGetVersion version, CancellationToken cancellationToken) - { - _logger.LogInformation( - "Hard deleting package {PackageId} {PackageVersion} from the database...", - id, - version); - - var found = await _packages.HardDeletePackageAsync(id, version); - if (!found) - { - _logger.LogWarning( - "Could not find package {PackageId} {PackageVersion} in the database", - id, - version); - } - - // Delete the package from storage. This is necessary even if the package isn't - // in the database to ensure that the storage is consistent with the database. - _logger.LogInformation("Hard deleting package {PackageId} {PackageVersion} from storage...", - id, - version); - - await _storage.DeleteAsync(id, version, cancellationToken); - - _logger.LogInformation( - "Hard deleted package {PackageId} {PackageVersion} from storage", - id, - version); - - return found; - } - } -} +using System; +using System.Threading; +using System.Threading.Tasks; +using BaGet.Core.Configuration; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; +using NuGet.Versioning; + +namespace BaGet.Core.Services +{ + public class PackageDeletionService : IPackageDeletionService + { + private readonly IPackageService _packages; + private readonly IPackageStorageService _storage; + private readonly BaGetOptions _options; + private readonly ILogger _logger; + + public PackageDeletionService( + IPackageService packages, + IPackageStorageService storage, + IOptionsSnapshot options, + ILogger logger) + { + _packages = packages ?? throw new ArgumentNullException(nameof(packages)); + _storage = storage ?? throw new ArgumentNullException(nameof(storage)); + _options = options?.Value ?? throw new ArgumentNullException(nameof(options)); + _logger = logger ?? throw new ArgumentNullException(nameof(logger)); + } + + public async Task TryDeletePackageAsync(string id, NuGetVersion version, CancellationToken cancellationToken) + { + switch (_options.PackageDeletionBehavior) + { + case PackageDeletionBehavior.Unlist: + return await TryUnlistPackageAsync(id, version); + + case PackageDeletionBehavior.HardDelete: + return await TryHardDeletePackageAsync(id, version, cancellationToken); + + default: + throw new InvalidOperationException($"Unknown deletion behavior '{_options.PackageDeletionBehavior}'"); + } + } + + private async Task TryUnlistPackageAsync(string id, NuGetVersion version) + { + _logger.LogInformation("Unlisting package {PackageId} {PackageVersion}...", id, version); + + if (!await _packages.UnlistPackageAsync(id, version)) + { + _logger.LogWarning("Could not find package {PackageId} {PackageVersion}", id, version); + + return false; + } + + _logger.LogInformation("Unlisted package {PackageId} {PackageVersion}", id, version); + + return true; + } + + private async Task TryHardDeletePackageAsync(string id, NuGetVersion version, CancellationToken cancellationToken) + { + _logger.LogInformation( + "Hard deleting package {PackageId} {PackageVersion} from the database...", + id, + version); + + var found = await _packages.HardDeletePackageAsync(id, version); + if (!found) + { + _logger.LogWarning( + "Could not find package {PackageId} {PackageVersion} in the database", + id, + version); + } + + // Delete the package from storage. This is necessary even if the package isn't + // in the database to ensure that the storage is consistent with the database. + _logger.LogInformation("Hard deleting package {PackageId} {PackageVersion} from storage...", + id, + version); + + await _storage.DeleteAsync(id, version, cancellationToken); + + _logger.LogInformation( + "Hard deleted package {PackageId} {PackageVersion} from storage", + id, + version); + + return found; + } + } +} diff --git a/src/BaGet.Core/Services/PackageService.cs b/src/BaGet.Core/Services/PackageService.cs index 39b7a703..f805315e 100644 --- a/src/BaGet.Core/Services/PackageService.cs +++ b/src/BaGet.Core/Services/PackageService.cs @@ -1,128 +1,128 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; -using BaGet.Core.Entities; -using Microsoft.EntityFrameworkCore; -using NuGet.Versioning; - -namespace BaGet.Core.Services -{ - public class PackageService : IPackageService - { - private readonly IContext _context; - - public PackageService(IContext context) - { - _context = context ?? throw new ArgumentNullException(nameof(context)); - } - - public async Task AddAsync(Package package) - { - try - { - _context.Packages.Add(package); - - await _context.SaveChangesAsync(); - - return PackageAddResult.Success; - } - catch (DbUpdateException e) - when (_context.IsUniqueConstraintViolationException(e)) - { - return PackageAddResult.PackageAlreadyExists; - } - } - - public Task ExistsAsync(string id, NuGetVersion version = null) - { - var query = _context.Packages.Where(p => p.Id == id); - - if (version != null) - { - query = query.Where(p => p.VersionString == version.ToNormalizedString()); - } - - return query.AnyAsync(); - } - - public async Task> FindAsync(string id, bool includeUnlisted = false) - { - var query = _context.Packages.Include(a => a.Dependencies).Where(p => p.Id == id); - - if (!includeUnlisted) - { - query = query.Where(p => p.Listed); - } - - return (await query.ToListAsync()).AsReadOnly(); - } - - public Task FindOrNullAsync(string id, NuGetVersion version, bool includeUnlisted = false) - { - var query = _context.Packages - .Include(a => a.Dependencies) - .Where(p => p.Id == id) - .Where(p => p.VersionString == version.ToNormalizedString()); - - if (!includeUnlisted) - { - query = query.Where(p => p.Listed); - } - - return query.FirstOrDefaultAsync(); - } - - public Task UnlistPackageAsync(string id, NuGetVersion version) - { - return TryUpdatePackageAsync(id, version, p => p.Listed = false); - } - - public Task RelistPackageAsync(string id, NuGetVersion version) - { - return TryUpdatePackageAsync(id, version, p => p.Listed = true); - } - - public Task AddDownloadAsync(string id, NuGetVersion version) - { - return TryUpdatePackageAsync(id, version, p => p.Downloads += 1); - } - - public async Task HardDeletePackageAsync(string id, NuGetVersion version) - { - var package = await _context.Packages - .Where(p => p.Id == id) - .Where(p => p.VersionString == version.ToNormalizedString()) - .Include(p => p.Dependencies) - .FirstOrDefaultAsync(); - - if (package == null) - { - return false; - } - - _context.Packages.Remove(package); - await _context.SaveChangesAsync(); - - return true; - } - - private async Task TryUpdatePackageAsync(string id, NuGetVersion version, Action action) - { - var package = await _context.Packages - .Where(p => p.Id == id) - .Where(p => p.VersionString == version.ToNormalizedString()) - .FirstOrDefaultAsync(); - - if (package != null) - { - action(package); - await _context.SaveChangesAsync(); - - return true; - } - - return false; - } - } -} +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using BaGet.Core.Entities; +using Microsoft.EntityFrameworkCore; +using NuGet.Versioning; + +namespace BaGet.Core.Services +{ + public class PackageService : IPackageService + { + private readonly IContext _context; + + public PackageService(IContext context) + { + _context = context ?? throw new ArgumentNullException(nameof(context)); + } + + public async Task AddAsync(Package package) + { + try + { + _context.Packages.Add(package); + + await _context.SaveChangesAsync(); + + return PackageAddResult.Success; + } + catch (DbUpdateException e) + when (_context.IsUniqueConstraintViolationException(e)) + { + return PackageAddResult.PackageAlreadyExists; + } + } + + public Task ExistsAsync(string id, NuGetVersion version = null) + { + var query = _context.Packages.Where(p => p.Id == id); + + if (version != null) + { + query = query.Where(p => p.VersionString == version.ToNormalizedString()); + } + + return query.AnyAsync(); + } + + public async Task> FindAsync(string id, bool includeUnlisted = false) + { + var query = _context.Packages.Include(a => a.Dependencies).Where(p => p.Id == id); + + if (!includeUnlisted) + { + query = query.Where(p => p.Listed); + } + + return (await query.ToListAsync()).AsReadOnly(); + } + + public Task FindOrNullAsync(string id, NuGetVersion version, bool includeUnlisted = false) + { + var query = _context.Packages + .Include(a => a.Dependencies) + .Where(p => p.Id == id) + .Where(p => p.VersionString == version.ToNormalizedString()); + + if (!includeUnlisted) + { + query = query.Where(p => p.Listed); + } + + return query.FirstOrDefaultAsync(); + } + + public Task UnlistPackageAsync(string id, NuGetVersion version) + { + return TryUpdatePackageAsync(id, version, p => p.Listed = false); + } + + public Task RelistPackageAsync(string id, NuGetVersion version) + { + return TryUpdatePackageAsync(id, version, p => p.Listed = true); + } + + public Task AddDownloadAsync(string id, NuGetVersion version) + { + return TryUpdatePackageAsync(id, version, p => p.Downloads += 1); + } + + public async Task HardDeletePackageAsync(string id, NuGetVersion version) + { + var package = await _context.Packages + .Where(p => p.Id == id) + .Where(p => p.VersionString == version.ToNormalizedString()) + .Include(p => p.Dependencies) + .FirstOrDefaultAsync(); + + if (package == null) + { + return false; + } + + _context.Packages.Remove(package); + await _context.SaveChangesAsync(); + + return true; + } + + private async Task TryUpdatePackageAsync(string id, NuGetVersion version, Action action) + { + var package = await _context.Packages + .Where(p => p.Id == id) + .Where(p => p.VersionString == version.ToNormalizedString()) + .FirstOrDefaultAsync(); + + if (package != null) + { + action(package); + await _context.SaveChangesAsync(); + + return true; + } + + return false; + } + } +} diff --git a/src/BaGet.Core/readme.md b/src/BaGet.Core/readme.md index 2387533c..fa74efcf 100644 --- a/src/BaGet.Core/readme.md +++ b/src/BaGet.Core/readme.md @@ -1,3 +1,3 @@ -# BaGet.Core - +# BaGet.Core + Contains BaGet's core logic. \ No newline at end of file diff --git a/src/BaGet.Tools.AzureSearchImporter/BaGet.Tools.AzureSearchImporter.csproj b/src/BaGet.Tools.AzureSearchImporter/BaGet.Tools.AzureSearchImporter.csproj index c5152816..cb6b83cf 100644 --- a/src/BaGet.Tools.AzureSearchImporter/BaGet.Tools.AzureSearchImporter.csproj +++ b/src/BaGet.Tools.AzureSearchImporter/BaGet.Tools.AzureSearchImporter.csproj @@ -1,30 +1,30 @@ - - - - Exe - netcoreapp2.1 - - false - - - - - - - - - - - - - - - - - - - - - - - + + + + Exe + netcoreapp2.1 + + false + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/BaGet.Tools.AzureSearchImporter/Entities/IndexerContext.cs b/src/BaGet.Tools.AzureSearchImporter/Entities/IndexerContext.cs index dcb2820e..732c9f17 100644 --- a/src/BaGet.Tools.AzureSearchImporter/Entities/IndexerContext.cs +++ b/src/BaGet.Tools.AzureSearchImporter/Entities/IndexerContext.cs @@ -1,30 +1,30 @@ -using Microsoft.EntityFrameworkCore; - -namespace BaGet.Tools.AzureSearchImporter.Entities -{ - public class IndexerContext : DbContext - { - public IndexerContext(DbContextOptions options) - : base(options) - {} - - public DbSet PackageIds { get; set; } - - protected override void OnModelCreating(ModelBuilder builder) - { - builder.Entity() - .HasKey(p => p.Key); - - builder.Entity() - .Property(p => p.Value) - .HasColumnType("TEXT COLLATE NOCASE"); - - builder.Entity() - .HasIndex(p => p.Value) - .IsUnique(); - - builder.Entity() - .HasIndex(p => p.Done); - } - } -} +using Microsoft.EntityFrameworkCore; + +namespace BaGet.Tools.AzureSearchImporter.Entities +{ + public class IndexerContext : DbContext + { + public IndexerContext(DbContextOptions options) + : base(options) + {} + + public DbSet PackageIds { get; set; } + + protected override void OnModelCreating(ModelBuilder builder) + { + builder.Entity() + .HasKey(p => p.Key); + + builder.Entity() + .Property(p => p.Value) + .HasColumnType("TEXT COLLATE NOCASE"); + + builder.Entity() + .HasIndex(p => p.Value) + .IsUnique(); + + builder.Entity() + .HasIndex(p => p.Done); + } + } +} diff --git a/src/BaGet.Tools.AzureSearchImporter/Entities/IndexerContextFactory.cs b/src/BaGet.Tools.AzureSearchImporter/Entities/IndexerContextFactory.cs index c6d35f56..da35befd 100644 --- a/src/BaGet.Tools.AzureSearchImporter/Entities/IndexerContextFactory.cs +++ b/src/BaGet.Tools.AzureSearchImporter/Entities/IndexerContextFactory.cs @@ -1,19 +1,19 @@ -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Design; - -namespace BaGet.Tools.AzureSearchImporter.Entities -{ - class IndexerContextFactory : IDesignTimeDbContextFactory - { - public const string ConnectionString = "Data Source=indexer.db"; - - public IndexerContext CreateDbContext(string[] args) - { - var optionsBuilder = new DbContextOptionsBuilder(); - - optionsBuilder.UseSqlite(ConnectionString); - - return new IndexerContext(optionsBuilder.Options); - } - } -} +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Design; + +namespace BaGet.Tools.AzureSearchImporter.Entities +{ + class IndexerContextFactory : IDesignTimeDbContextFactory + { + public const string ConnectionString = "Data Source=indexer.db"; + + public IndexerContext CreateDbContext(string[] args) + { + var optionsBuilder = new DbContextOptionsBuilder(); + + optionsBuilder.UseSqlite(ConnectionString); + + return new IndexerContext(optionsBuilder.Options); + } + } +} diff --git a/src/BaGet.Tools.AzureSearchImporter/Entities/PackageId.cs b/src/BaGet.Tools.AzureSearchImporter/Entities/PackageId.cs index 48bcffc8..bb0f53ac 100644 --- a/src/BaGet.Tools.AzureSearchImporter/Entities/PackageId.cs +++ b/src/BaGet.Tools.AzureSearchImporter/Entities/PackageId.cs @@ -1,11 +1,11 @@ -namespace BaGet.Tools.AzureSearchImporter.Entities -{ - public class PackageId - { - public int Key { get; set; } - - public string Value { get; set; } - - public bool Done { get; set; } - } +namespace BaGet.Tools.AzureSearchImporter.Entities +{ + public class PackageId + { + public int Key { get; set; } + + public string Value { get; set; } + + public bool Done { get; set; } + } } \ No newline at end of file diff --git a/src/BaGet.Tools.AzureSearchImporter/Importer.cs b/src/BaGet.Tools.AzureSearchImporter/Importer.cs index fc18cd15..fff42e03 100644 --- a/src/BaGet.Tools.AzureSearchImporter/Importer.cs +++ b/src/BaGet.Tools.AzureSearchImporter/Importer.cs @@ -1,70 +1,70 @@ -using System; -using System.Linq; -using System.Threading.Tasks; -using BaGet.Azure.Search; -using BaGet.Tools.AzureSearchImporter.Entities; -using Microsoft.EntityFrameworkCore; -using Microsoft.Extensions.Logging; -using MoreLinq; - -namespace BaGet.Tools.AzureSearchImporter -{ - public class Importer - { - private const int ImportBatchSize = 200; - - private readonly IndexerContext _context; - private readonly BatchIndexer _indexer; - private readonly ILogger _logger; - - public Importer(IndexerContext context, BatchIndexer indexer, ILogger logger) - { - _context = context ?? throw new ArgumentException(nameof(context)); - _indexer = indexer ?? throw new ArgumentNullException(nameof(indexer)); - _logger = logger ?? throw new ArgumentNullException(nameof(logger)); - } - - public async Task ImportAsync(int skip = 0) - { - _logger.LogInformation("Starting import with skip {Skip}...", skip); - - var batchCount = 1; - var left = await _context.PackageIds - .Where(p => !p.Done) - .CountAsync(); - - _logger.LogInformation("{PackageIdsLeft} package ids left to import", left); - - while (true) - { - _logger.LogInformation("Importing batch {BatchCount}...", batchCount); - - var batch = await _context.PackageIds - .Where(p => !p.Done) - .OrderBy(p => p.Key) - .Skip(skip) - .Take(ImportBatchSize) - .ToListAsync(); - - if (batch.Count == 0) - { - break; - } - - await _indexer.IndexAsync(batch.Select(p => p.Value).ToArray()); - - foreach (var package in batch) - { - package.Done = true; - } - - await _context.SaveChangesAsync(); - - _logger.LogInformation("Imported batch {BatchCount}", batchCount); - batchCount++; - } - - _logger.LogInformation("Finished importing"); - } - } -} +using System; +using System.Linq; +using System.Threading.Tasks; +using BaGet.Azure.Search; +using BaGet.Tools.AzureSearchImporter.Entities; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Logging; +using MoreLinq; + +namespace BaGet.Tools.AzureSearchImporter +{ + public class Importer + { + private const int ImportBatchSize = 200; + + private readonly IndexerContext _context; + private readonly BatchIndexer _indexer; + private readonly ILogger _logger; + + public Importer(IndexerContext context, BatchIndexer indexer, ILogger logger) + { + _context = context ?? throw new ArgumentException(nameof(context)); + _indexer = indexer ?? throw new ArgumentNullException(nameof(indexer)); + _logger = logger ?? throw new ArgumentNullException(nameof(logger)); + } + + public async Task ImportAsync(int skip = 0) + { + _logger.LogInformation("Starting import with skip {Skip}...", skip); + + var batchCount = 1; + var left = await _context.PackageIds + .Where(p => !p.Done) + .CountAsync(); + + _logger.LogInformation("{PackageIdsLeft} package ids left to import", left); + + while (true) + { + _logger.LogInformation("Importing batch {BatchCount}...", batchCount); + + var batch = await _context.PackageIds + .Where(p => !p.Done) + .OrderBy(p => p.Key) + .Skip(skip) + .Take(ImportBatchSize) + .ToListAsync(); + + if (batch.Count == 0) + { + break; + } + + await _indexer.IndexAsync(batch.Select(p => p.Value).ToArray()); + + foreach (var package in batch) + { + package.Done = true; + } + + await _context.SaveChangesAsync(); + + _logger.LogInformation("Imported batch {BatchCount}", batchCount); + batchCount++; + } + + _logger.LogInformation("Finished importing"); + } + } +} diff --git a/src/BaGet.Tools.AzureSearchImporter/Initializer.cs b/src/BaGet.Tools.AzureSearchImporter/Initializer.cs index 6999b2a2..733be192 100644 --- a/src/BaGet.Tools.AzureSearchImporter/Initializer.cs +++ b/src/BaGet.Tools.AzureSearchImporter/Initializer.cs @@ -1,99 +1,99 @@ -using System; -using System.Linq; -using System.Threading.Tasks; -using BaGet.Azure.Search; -using BaGet.Core.Entities; -using BaGet.Tools.AzureSearchImporter.Entities; -using Microsoft.Azure.Search; -using Microsoft.Azure.Search.Models; -using Microsoft.EntityFrameworkCore; -using Microsoft.Extensions.Logging; -using MoreLinq; - -namespace BaGet.Tools.AzureSearchImporter -{ - public class Initializer - { - public const int InitializationBatchSize = 100; - - private readonly IContext _bagetContext; - private readonly IndexerContext _indexerContext; - private readonly SearchServiceClient _searchClient; - private readonly ILogger _logger; - - public Initializer( - IContext bagetContext, - IndexerContext indexerContext, - SearchServiceClient searchClient, - ILogger logger) - { - _bagetContext = bagetContext ?? throw new ArgumentNullException(nameof(bagetContext)); - _indexerContext = indexerContext ?? throw new ArgumentNullException(nameof(indexerContext)); - _searchClient = searchClient ?? throw new ArgumentNullException(nameof(searchClient)); - _logger = logger ?? throw new ArgumentNullException(nameof(logger)); - } - - public Task InitializeAsync() - => Task.WhenAll( - InitializeIndex(), - InitializeStateAsync()); - - private async Task InitializeIndex() - { - if (await _searchClient.Indexes.ExistsAsync(PackageDocument.IndexName)) - { - _logger.LogInformation("Search index already exists"); - return; - } - - _logger.LogInformation("Search index does not exist, creating..."); - - await _searchClient.Indexes.CreateAsync(new Index - { - Name = PackageDocument.IndexName, - Fields = FieldBuilder.BuildForType() - }); - - _logger.LogInformation("Created search index"); - } - - private async Task InitializeStateAsync() - { - if (await _indexerContext.PackageIds.AnyAsync()) - { - _logger.LogInformation("Indexer state is already initialized"); - return; - } - - _logger.LogInformation("Unitialized state. Finding packages to track in indexer state..."); - - var packageIds = await _bagetContext.Packages - .Select(p => p.Id) - .Distinct() - .ToListAsync(); - - _logger.LogInformation("Found {PackageIdCount} package ids to track in indexer state", packageIds.Count); - - var batchCount = 1; - - foreach (var batch in packageIds.Batch(InitializationBatchSize)) - { - foreach (var packageId in batch) - { - _indexerContext.PackageIds.Add(new PackageId - { - Value = packageId, - Done = false, - }); - } - - _logger.LogInformation("Saving package id batch {BatchCount} to indexer state...", batchCount); - - await _indexerContext.SaveChangesAsync(); - batchCount++; - } - - _logger.LogInformation("Finished adding {PackageIdCount} package ids to indexer state"); - } - } -} +using System; +using System.Linq; +using System.Threading.Tasks; +using BaGet.Azure.Search; +using BaGet.Core.Entities; +using BaGet.Tools.AzureSearchImporter.Entities; +using Microsoft.Azure.Search; +using Microsoft.Azure.Search.Models; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Logging; +using MoreLinq; + +namespace BaGet.Tools.AzureSearchImporter +{ + public class Initializer + { + public const int InitializationBatchSize = 100; + + private readonly IContext _bagetContext; + private readonly IndexerContext _indexerContext; + private readonly SearchServiceClient _searchClient; + private readonly ILogger _logger; + + public Initializer( + IContext bagetContext, + IndexerContext indexerContext, + SearchServiceClient searchClient, + ILogger logger) + { + _bagetContext = bagetContext ?? throw new ArgumentNullException(nameof(bagetContext)); + _indexerContext = indexerContext ?? throw new ArgumentNullException(nameof(indexerContext)); + _searchClient = searchClient ?? throw new ArgumentNullException(nameof(searchClient)); + _logger = logger ?? throw new ArgumentNullException(nameof(logger)); + } + + public Task InitializeAsync() + => Task.WhenAll( + InitializeIndex(), + InitializeStateAsync()); + + private async Task InitializeIndex() + { + if (await _searchClient.Indexes.ExistsAsync(PackageDocument.IndexName)) + { + _logger.LogInformation("Search index already exists"); + return; + } + + _logger.LogInformation("Search index does not exist, creating..."); + + await _searchClient.Indexes.CreateAsync(new Index + { + Name = PackageDocument.IndexName, + Fields = FieldBuilder.BuildForType() + }); + + _logger.LogInformation("Created search index"); + } + + private async Task InitializeStateAsync() + { + if (await _indexerContext.PackageIds.AnyAsync()) + { + _logger.LogInformation("Indexer state is already initialized"); + return; + } + + _logger.LogInformation("Unitialized state. Finding packages to track in indexer state..."); + + var packageIds = await _bagetContext.Packages + .Select(p => p.Id) + .Distinct() + .ToListAsync(); + + _logger.LogInformation("Found {PackageIdCount} package ids to track in indexer state", packageIds.Count); + + var batchCount = 1; + + foreach (var batch in packageIds.Batch(InitializationBatchSize)) + { + foreach (var packageId in batch) + { + _indexerContext.PackageIds.Add(new PackageId + { + Value = packageId, + Done = false, + }); + } + + _logger.LogInformation("Saving package id batch {BatchCount} to indexer state...", batchCount); + + await _indexerContext.SaveChangesAsync(); + batchCount++; + } + + _logger.LogInformation("Finished adding {PackageIdCount} package ids to indexer state"); + } + } +} diff --git a/src/BaGet.Tools.AzureSearchImporter/Migrations/20180415185938_Initial.Designer.cs b/src/BaGet.Tools.AzureSearchImporter/Migrations/20180415185938_Initial.Designer.cs index 4d31be92..03e58faf 100644 --- a/src/BaGet.Tools.AzureSearchImporter/Migrations/20180415185938_Initial.Designer.cs +++ b/src/BaGet.Tools.AzureSearchImporter/Migrations/20180415185938_Initial.Designer.cs @@ -1,44 +1,44 @@ -// -using BaGet.Tools.AzureSearchImporter.Entities; -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Metadata; -using Microsoft.EntityFrameworkCore.Migrations; -using Microsoft.EntityFrameworkCore.Storage; -using System; - -namespace BaGet.Tools.AzureSearchImporter.Migrations -{ - [DbContext(typeof(IndexerContext))] - [Migration("20180415185938_Initial")] - partial class Initial - { - protected override void BuildTargetModel(ModelBuilder modelBuilder) - { -#pragma warning disable 612, 618 - modelBuilder - .HasAnnotation("ProductVersion", "2.0.1-rtm-125"); - - modelBuilder.Entity("BaGet.Tools.AzureSearchImporter.Entities.PackageId", b => - { - b.Property("Key") - .ValueGeneratedOnAdd(); - - b.Property("Done"); - - b.Property("Value") - .HasColumnType("TEXT COLLATE NOCASE"); - - b.HasKey("Key"); - - b.HasIndex("Done"); - - b.HasIndex("Value") - .IsUnique(); - - b.ToTable("PackageIds"); - }); -#pragma warning restore 612, 618 - } - } -} +// +using BaGet.Tools.AzureSearchImporter.Entities; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage; +using System; + +namespace BaGet.Tools.AzureSearchImporter.Migrations +{ + [DbContext(typeof(IndexerContext))] + [Migration("20180415185938_Initial")] + partial class Initial + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "2.0.1-rtm-125"); + + modelBuilder.Entity("BaGet.Tools.AzureSearchImporter.Entities.PackageId", b => + { + b.Property("Key") + .ValueGeneratedOnAdd(); + + b.Property("Done"); + + b.Property("Value") + .HasColumnType("TEXT COLLATE NOCASE"); + + b.HasKey("Key"); + + b.HasIndex("Done"); + + b.HasIndex("Value") + .IsUnique(); + + b.ToTable("PackageIds"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/BaGet.Tools.AzureSearchImporter/Migrations/20180415185938_Initial.cs b/src/BaGet.Tools.AzureSearchImporter/Migrations/20180415185938_Initial.cs index 647952f9..ea6e5d3f 100644 --- a/src/BaGet.Tools.AzureSearchImporter/Migrations/20180415185938_Initial.cs +++ b/src/BaGet.Tools.AzureSearchImporter/Migrations/20180415185938_Initial.cs @@ -1,43 +1,43 @@ -using Microsoft.EntityFrameworkCore.Migrations; -using System; -using System.Collections.Generic; - -namespace BaGet.Tools.AzureSearchImporter.Migrations -{ - public partial class Initial : Migration - { - protected override void Up(MigrationBuilder migrationBuilder) - { - migrationBuilder.CreateTable( - name: "PackageIds", - columns: table => new - { - Key = table.Column(nullable: false) - .Annotation("Sqlite:Autoincrement", true), - Done = table.Column(nullable: false), - Value = table.Column(type: "TEXT COLLATE NOCASE", nullable: true) - }, - constraints: table => - { - table.PrimaryKey("PK_PackageIds", x => x.Key); - }); - - migrationBuilder.CreateIndex( - name: "IX_PackageIds_Done", - table: "PackageIds", - column: "Done"); - - migrationBuilder.CreateIndex( - name: "IX_PackageIds_Value", - table: "PackageIds", - column: "Value", - unique: true); - } - - protected override void Down(MigrationBuilder migrationBuilder) - { - migrationBuilder.DropTable( - name: "PackageIds"); - } - } -} +using Microsoft.EntityFrameworkCore.Migrations; +using System; +using System.Collections.Generic; + +namespace BaGet.Tools.AzureSearchImporter.Migrations +{ + public partial class Initial : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "PackageIds", + columns: table => new + { + Key = table.Column(nullable: false) + .Annotation("Sqlite:Autoincrement", true), + Done = table.Column(nullable: false), + Value = table.Column(type: "TEXT COLLATE NOCASE", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_PackageIds", x => x.Key); + }); + + migrationBuilder.CreateIndex( + name: "IX_PackageIds_Done", + table: "PackageIds", + column: "Done"); + + migrationBuilder.CreateIndex( + name: "IX_PackageIds_Value", + table: "PackageIds", + column: "Value", + unique: true); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "PackageIds"); + } + } +} diff --git a/src/BaGet.Tools.AzureSearchImporter/Migrations/IndexerContextModelSnapshot.cs b/src/BaGet.Tools.AzureSearchImporter/Migrations/IndexerContextModelSnapshot.cs index 9bf7838b..278d0e5a 100644 --- a/src/BaGet.Tools.AzureSearchImporter/Migrations/IndexerContextModelSnapshot.cs +++ b/src/BaGet.Tools.AzureSearchImporter/Migrations/IndexerContextModelSnapshot.cs @@ -1,43 +1,43 @@ -// -using BaGet.Tools.AzureSearchImporter.Entities; -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Metadata; -using Microsoft.EntityFrameworkCore.Migrations; -using Microsoft.EntityFrameworkCore.Storage; -using System; - -namespace BaGet.Tools.AzureSearchImporter.Migrations -{ - [DbContext(typeof(IndexerContext))] - partial class IndexerContextModelSnapshot : ModelSnapshot - { - protected override void BuildModel(ModelBuilder modelBuilder) - { -#pragma warning disable 612, 618 - modelBuilder - .HasAnnotation("ProductVersion", "2.0.1-rtm-125"); - - modelBuilder.Entity("BaGet.Tools.AzureSearchImporter.Entities.PackageId", b => - { - b.Property("Key") - .ValueGeneratedOnAdd(); - - b.Property("Done"); - - b.Property("Value") - .HasColumnType("TEXT COLLATE NOCASE"); - - b.HasKey("Key"); - - b.HasIndex("Done"); - - b.HasIndex("Value") - .IsUnique(); - - b.ToTable("PackageIds"); - }); -#pragma warning restore 612, 618 - } - } -} +// +using BaGet.Tools.AzureSearchImporter.Entities; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage; +using System; + +namespace BaGet.Tools.AzureSearchImporter.Migrations +{ + [DbContext(typeof(IndexerContext))] + partial class IndexerContextModelSnapshot : ModelSnapshot + { + protected override void BuildModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "2.0.1-rtm-125"); + + modelBuilder.Entity("BaGet.Tools.AzureSearchImporter.Entities.PackageId", b => + { + b.Property("Key") + .ValueGeneratedOnAdd(); + + b.Property("Done"); + + b.Property("Value") + .HasColumnType("TEXT COLLATE NOCASE"); + + b.HasKey("Key"); + + b.HasIndex("Done"); + + b.HasIndex("Value") + .IsUnique(); + + b.ToTable("PackageIds"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/BaGet.Tools.AzureSearchImporter/Program.cs b/src/BaGet.Tools.AzureSearchImporter/Program.cs index d864a378..7f93280f 100644 --- a/src/BaGet.Tools.AzureSearchImporter/Program.cs +++ b/src/BaGet.Tools.AzureSearchImporter/Program.cs @@ -1,85 +1,85 @@ -using System; -using System.Threading.Tasks; -using BaGet.Azure.Extensions; -using BaGet.Core.Configuration; -using BaGet.Core.Services; -using BaGet.Extensions; -using BaGet.Tools.AzureSearchImporter.Entities; -using Microsoft.EntityFrameworkCore; -using Microsoft.Extensions.Configuration; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Logging; - -namespace BaGet.Tools.AzureSearchImporter -{ - class Program - { - public static void Main(string[] args) - => MainAsync(args) - .GetAwaiter() - .GetResult(); - - private async static Task MainAsync(string[] args) - { - // Parse the skip from arguments. - int skip = 0; - - if (args.Length > 0) - { - int.TryParse(args[args.Length - 1], out skip); - } - - // Prepare the job. - var provider = GetServiceProvider(GetConfiguration()); - var scopeFactory = provider.GetRequiredService(); - var initializer = provider.GetRequiredService(); - var importer = provider.GetRequiredService(); - - using (var scope = scopeFactory.CreateScope()) - { - scope.ServiceProvider - .GetRequiredService() - .Database - .Migrate(); - } - - // Initialize the state and start importing packages to the search index. - await initializer.InitializeAsync(); - await importer.ImportAsync(skip); - } - - private static IConfiguration GetConfiguration() - => new ConfigurationBuilder() - .SetBasePath(Environment.CurrentDirectory) - .AddJsonFile("appsettings.json") - .Build(); - - private static IServiceProvider GetServiceProvider(IConfiguration configuration) - { - var services = new ServiceCollection(); - - services.Configure(configuration); - services.ConfigureAzure(configuration); - - services.AddLogging(logging => - { - logging.AddFilter(DbLoggerCategory.Database.Command.Name, LogLevel.Warning); - logging.AddConsole(); - }); - - services.AddBaGetContext(); - services.AddDbContext((provider, options) => - { - options.UseSqlite(IndexerContextFactory.ConnectionString); - }); - - services.AddTransient(); - services.AddAzureSearch(); - - services.AddTransient(); - services.AddTransient(); - - return services.BuildServiceProvider(); - } - } -} +using System; +using System.Threading.Tasks; +using BaGet.Azure.Extensions; +using BaGet.Core.Configuration; +using BaGet.Core.Services; +using BaGet.Extensions; +using BaGet.Tools.AzureSearchImporter.Entities; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; + +namespace BaGet.Tools.AzureSearchImporter +{ + class Program + { + public static void Main(string[] args) + => MainAsync(args) + .GetAwaiter() + .GetResult(); + + private async static Task MainAsync(string[] args) + { + // Parse the skip from arguments. + int skip = 0; + + if (args.Length > 0) + { + int.TryParse(args[args.Length - 1], out skip); + } + + // Prepare the job. + var provider = GetServiceProvider(GetConfiguration()); + var scopeFactory = provider.GetRequiredService(); + var initializer = provider.GetRequiredService(); + var importer = provider.GetRequiredService(); + + using (var scope = scopeFactory.CreateScope()) + { + scope.ServiceProvider + .GetRequiredService() + .Database + .Migrate(); + } + + // Initialize the state and start importing packages to the search index. + await initializer.InitializeAsync(); + await importer.ImportAsync(skip); + } + + private static IConfiguration GetConfiguration() + => new ConfigurationBuilder() + .SetBasePath(Environment.CurrentDirectory) + .AddJsonFile("appsettings.json") + .Build(); + + private static IServiceProvider GetServiceProvider(IConfiguration configuration) + { + var services = new ServiceCollection(); + + services.Configure(configuration); + services.ConfigureAzure(configuration); + + services.AddLogging(logging => + { + logging.AddFilter(DbLoggerCategory.Database.Command.Name, LogLevel.Warning); + logging.AddConsole(); + }); + + services.AddBaGetContext(); + services.AddDbContext((provider, options) => + { + options.UseSqlite(IndexerContextFactory.ConnectionString); + }); + + services.AddTransient(); + services.AddAzureSearch(); + + services.AddTransient(); + services.AddTransient(); + + return services.BuildServiceProvider(); + } + } +} diff --git a/src/BaGet/BaGet.csproj b/src/BaGet/BaGet.csproj index 74b76288..be959660 100644 --- a/src/BaGet/BaGet.csproj +++ b/src/BaGet/BaGet.csproj @@ -1,54 +1,54 @@ - - - - netcoreapp2.1 - - ..\BaGet.UI\ - $(DefaultItemExcludes);$(SpaRoot)node_modules\** - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Workaround\%(DistFiles.Identity) - PreserveNewest - - - - - + + + + netcoreapp2.1 + + ..\BaGet.UI\ + $(DefaultItemExcludes);$(SpaRoot)node_modules\** + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Workaround\%(DistFiles.Identity) + PreserveNewest + + + + + diff --git a/src/BaGet/Configurations/ConfigureCorsOptions.cs b/src/BaGet/Configurations/ConfigureCorsOptions.cs index a3af624f..dfda4e14 100644 --- a/src/BaGet/Configurations/ConfigureCorsOptions.cs +++ b/src/BaGet/Configurations/ConfigureCorsOptions.cs @@ -1,19 +1,19 @@ -using Microsoft.AspNetCore.Cors.Infrastructure; -using Microsoft.Extensions.Options; - -namespace BaGet.Configurations -{ - public class ConfigureCorsOptions : IConfigureOptions - { - public const string CorsPolicy = "AllowAll"; - - public void Configure(CorsOptions options) - { - options.AddPolicy( - CorsPolicy, - builder => builder.AllowAnyOrigin() - .AllowAnyMethod() - .AllowAnyHeader()); - } - } -} +using Microsoft.AspNetCore.Cors.Infrastructure; +using Microsoft.Extensions.Options; + +namespace BaGet.Configurations +{ + public class ConfigureCorsOptions : IConfigureOptions + { + public const string CorsPolicy = "AllowAll"; + + public void Configure(CorsOptions options) + { + options.AddPolicy( + CorsPolicy, + builder => builder.AllowAnyOrigin() + .AllowAnyMethod() + .AllowAnyHeader()); + } + } +} diff --git a/src/BaGet/Controllers/IndexController.cs b/src/BaGet/Controllers/IndexController.cs index f613aa51..560633b1 100644 --- a/src/BaGet/Controllers/IndexController.cs +++ b/src/BaGet/Controllers/IndexController.cs @@ -1,40 +1,40 @@ -using System.Collections.Generic; -using BaGet.Extensions; -using BaGet.Protocol; -using Microsoft.AspNetCore.Mvc; -using NuGet.Versioning; - -namespace BaGet.Controllers -{ - /// - /// The NuGet Service Index. This aids NuGet client to discover this server's services. - /// - public class IndexController : Controller - { - private IEnumerable BuildResource(string name, string url, params string[] versions) - { - foreach (var version in versions) - { - var type = string.IsNullOrEmpty(version) ? name : $"{name}/{version}"; - - yield return new ServiceIndexResource(type, url); - } - } - - // GET v3/index - [HttpGet] - public ServiceIndex Get() - { - var resources = new List(); - - resources.AddRange(BuildResource("PackagePublish", Url.PackagePublish(), "2.0.0")); - resources.AddRange(BuildResource("SymbolPackagePublish", Url.SymbolPublish(), "4.9.0")); - resources.AddRange(BuildResource("SearchQueryService", Url.PackageSearch(), "", "3.0.0-beta", "3.0.0-rc")); - resources.AddRange(BuildResource("RegistrationsBaseUrl", Url.RegistrationsBase(), "", "3.0.0-rc", "3.0.0-beta")); - resources.AddRange(BuildResource("PackageBaseAddress", Url.PackageBase(), "3.0.0")); - resources.AddRange(BuildResource("SearchAutocompleteService", Url.PackageAutocomplete(), "", "3.0.0-rc", "3.0.0-beta")); - - return new ServiceIndex(new NuGetVersion("3.0.0"), resources); - } - } +using System.Collections.Generic; +using BaGet.Extensions; +using BaGet.Protocol; +using Microsoft.AspNetCore.Mvc; +using NuGet.Versioning; + +namespace BaGet.Controllers +{ + /// + /// The NuGet Service Index. This aids NuGet client to discover this server's services. + /// + public class IndexController : Controller + { + private IEnumerable BuildResource(string name, string url, params string[] versions) + { + foreach (var version in versions) + { + var type = string.IsNullOrEmpty(version) ? name : $"{name}/{version}"; + + yield return new ServiceIndexResource(type, url); + } + } + + // GET v3/index + [HttpGet] + public ServiceIndex Get() + { + var resources = new List(); + + resources.AddRange(BuildResource("PackagePublish", Url.PackagePublish(), "2.0.0")); + resources.AddRange(BuildResource("SymbolPackagePublish", Url.SymbolPublish(), "4.9.0")); + resources.AddRange(BuildResource("SearchQueryService", Url.PackageSearch(), "", "3.0.0-beta", "3.0.0-rc")); + resources.AddRange(BuildResource("RegistrationsBaseUrl", Url.RegistrationsBase(), "", "3.0.0-rc", "3.0.0-beta")); + resources.AddRange(BuildResource("PackageBaseAddress", Url.PackageBase(), "3.0.0")); + resources.AddRange(BuildResource("SearchAutocompleteService", Url.PackageAutocomplete(), "", "3.0.0-rc", "3.0.0-beta")); + + return new ServiceIndex(new NuGetVersion("3.0.0"), resources); + } + } } \ No newline at end of file diff --git a/src/BaGet/Controllers/PackageController.cs b/src/BaGet/Controllers/PackageController.cs index 96c83db2..00071e42 100644 --- a/src/BaGet/Controllers/PackageController.cs +++ b/src/BaGet/Controllers/PackageController.cs @@ -1,111 +1,111 @@ -using System; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; -using BaGet.Core.Mirror; -using BaGet.Core.Services; -using BaGet.Protocol; -using Microsoft.AspNetCore.Mvc; -using NuGet.Packaging.Core; -using NuGet.Versioning; - -namespace BaGet.Controllers -{ - public class PackageController : Controller - { - private readonly IMirrorService _mirror; - private readonly IPackageService _packages; - private readonly IPackageStorageService _storage; - - public PackageController(IMirrorService mirror, IPackageService packages, IPackageStorageService storage) - { - _mirror = mirror ?? throw new ArgumentNullException(nameof(mirror)); - _packages = packages ?? throw new ArgumentNullException(nameof(packages)); - _storage = storage ?? throw new ArgumentNullException(nameof(storage)); - } - - public async Task Versions(string id, CancellationToken cancellationToken) - { - // First, attempt to find all package versions using the upstream source. - var versions = await _mirror.FindPackageVersionsOrNullAsync(id, cancellationToken); - - if (versions == null) - { - // Fallback to the local packages if the package couldn't be found - // on the upstream source. - var packages = await _packages.FindAsync(id); - - if (!packages.Any()) - { - return NotFound(); - } - - versions = packages.Select(p => p.Version).ToList(); - } - - return Json(new PackageVersions(versions)); - } - - public async Task DownloadPackage(string id, string version, CancellationToken cancellationToken) - { - if (!NuGetVersion.TryParse(version, out var nugetVersion)) - { - return NotFound(); - } - - // Allow read-through caching if it is configured. - await _mirror.MirrorAsync(id, nugetVersion, cancellationToken); - - if (!await _packages.AddDownloadAsync(id, nugetVersion)) - { - return NotFound(); - } - - var packageStream = await _storage.GetPackageStreamAsync(id, nugetVersion, cancellationToken); - - return File(packageStream, "application/octet-stream"); - } - - public async Task DownloadNuspec(string id, string version, CancellationToken cancellationToken) - { - if (!NuGetVersion.TryParse(version, out var nugetVersion)) - { - return NotFound(); - } - - // Allow read-through caching if it is configured. - await _mirror.MirrorAsync(id, nugetVersion, cancellationToken); - - if (!await _packages.ExistsAsync(id, nugetVersion)) - { - return NotFound(); - } - - var nuspecStream = await _storage.GetNuspecStreamAsync(id, nugetVersion, cancellationToken); - - return File(nuspecStream, "text/xml"); - } - - public async Task DownloadReadme(string id, string version, CancellationToken cancellationToken) - { - if (!NuGetVersion.TryParse(version, out var nugetVersion)) - { - return NotFound(); - } - - // Allow read-through caching if it is configured. - await _mirror.MirrorAsync(id, nugetVersion, cancellationToken); - - var package = await _packages.FindOrNullAsync(id, nugetVersion); - - if (package == null || !package.HasReadme) - { - return NotFound(); - } - - var readmeStream = await _storage.GetReadmeStreamAsync(id, nugetVersion, cancellationToken); - - return File(readmeStream, "text/markdown"); - } - } +using System; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using BaGet.Core.Mirror; +using BaGet.Core.Services; +using BaGet.Protocol; +using Microsoft.AspNetCore.Mvc; +using NuGet.Packaging.Core; +using NuGet.Versioning; + +namespace BaGet.Controllers +{ + public class PackageController : Controller + { + private readonly IMirrorService _mirror; + private readonly IPackageService _packages; + private readonly IPackageStorageService _storage; + + public PackageController(IMirrorService mirror, IPackageService packages, IPackageStorageService storage) + { + _mirror = mirror ?? throw new ArgumentNullException(nameof(mirror)); + _packages = packages ?? throw new ArgumentNullException(nameof(packages)); + _storage = storage ?? throw new ArgumentNullException(nameof(storage)); + } + + public async Task Versions(string id, CancellationToken cancellationToken) + { + // First, attempt to find all package versions using the upstream source. + var versions = await _mirror.FindPackageVersionsOrNullAsync(id, cancellationToken); + + if (versions == null) + { + // Fallback to the local packages if the package couldn't be found + // on the upstream source. + var packages = await _packages.FindAsync(id); + + if (!packages.Any()) + { + return NotFound(); + } + + versions = packages.Select(p => p.Version).ToList(); + } + + return Json(new PackageVersions(versions)); + } + + public async Task DownloadPackage(string id, string version, CancellationToken cancellationToken) + { + if (!NuGetVersion.TryParse(version, out var nugetVersion)) + { + return NotFound(); + } + + // Allow read-through caching if it is configured. + await _mirror.MirrorAsync(id, nugetVersion, cancellationToken); + + if (!await _packages.AddDownloadAsync(id, nugetVersion)) + { + return NotFound(); + } + + var packageStream = await _storage.GetPackageStreamAsync(id, nugetVersion, cancellationToken); + + return File(packageStream, "application/octet-stream"); + } + + public async Task DownloadNuspec(string id, string version, CancellationToken cancellationToken) + { + if (!NuGetVersion.TryParse(version, out var nugetVersion)) + { + return NotFound(); + } + + // Allow read-through caching if it is configured. + await _mirror.MirrorAsync(id, nugetVersion, cancellationToken); + + if (!await _packages.ExistsAsync(id, nugetVersion)) + { + return NotFound(); + } + + var nuspecStream = await _storage.GetNuspecStreamAsync(id, nugetVersion, cancellationToken); + + return File(nuspecStream, "text/xml"); + } + + public async Task DownloadReadme(string id, string version, CancellationToken cancellationToken) + { + if (!NuGetVersion.TryParse(version, out var nugetVersion)) + { + return NotFound(); + } + + // Allow read-through caching if it is configured. + await _mirror.MirrorAsync(id, nugetVersion, cancellationToken); + + var package = await _packages.FindOrNullAsync(id, nugetVersion); + + if (package == null || !package.HasReadme) + { + return NotFound(); + } + + var readmeStream = await _storage.GetReadmeStreamAsync(id, nugetVersion, cancellationToken); + + return File(readmeStream, "text/markdown"); + } + } } \ No newline at end of file diff --git a/src/BaGet/Controllers/PackagePublishController.cs b/src/BaGet/Controllers/PackagePublishController.cs index 7a2a97c6..530af188 100644 --- a/src/BaGet/Controllers/PackagePublishController.cs +++ b/src/BaGet/Controllers/PackagePublishController.cs @@ -1,124 +1,124 @@ -using System; -using System.Threading; -using System.Threading.Tasks; -using BaGet.Core.Services; -using BaGet.Extensions; -using Microsoft.AspNetCore.Http; -using Microsoft.AspNetCore.Mvc; -using Microsoft.Extensions.Logging; -using NuGet.Versioning; - -namespace BaGet.Controllers -{ - public class PackagePublishController : Controller - { - private readonly IAuthenticationService _authentication; - private readonly IPackageIndexingService _indexer; - private readonly IPackageService _packages; - private readonly IPackageDeletionService _deleteService; - private readonly ILogger _logger; - - public PackagePublishController( - IAuthenticationService authentication, - IPackageIndexingService indexer, - IPackageService packages, - IPackageDeletionService deletionService, - ILogger logger) - { - _authentication = authentication ?? throw new ArgumentNullException(nameof(authentication)); - _indexer = indexer ?? throw new ArgumentNullException(nameof(indexer)); - _packages = packages ?? throw new ArgumentNullException(nameof(packages)); - _deleteService = deletionService ?? throw new ArgumentNullException(nameof(deletionService)); - _logger = logger ?? throw new ArgumentNullException(nameof(logger)); - } - - // See: https://docs.microsoft.com/en-us/nuget/api/package-publish-resource#push-a-package - public async Task Upload(CancellationToken cancellationToken) - { - if (!await _authentication.AuthenticateAsync(Request.GetApiKey())) - { - HttpContext.Response.StatusCode = 401; - return; - } - - try - { - using (var uploadStream = await Request.GetUploadStreamOrNullAsync(cancellationToken)) - { - if (uploadStream == null) - { - HttpContext.Response.StatusCode = 400; - return; - } - - var result = await _indexer.IndexAsync(uploadStream, cancellationToken); - - switch (result) - { - case PackageIndexingResult.InvalidPackage: - HttpContext.Response.StatusCode = 400; - break; - - case PackageIndexingResult.PackageAlreadyExists: - HttpContext.Response.StatusCode = 409; - break; - - case PackageIndexingResult.Success: - HttpContext.Response.StatusCode = 201; - break; - } - } - } - catch (Exception e) - { - _logger.LogError(e, "Exception thrown during package upload"); - - HttpContext.Response.StatusCode = 500; - } - } - - public async Task Delete(string id, string version, CancellationToken cancellationToken) - { - if (!NuGetVersion.TryParse(version, out var nugetVersion)) - { - return NotFound(); - } - - if (!await _authentication.AuthenticateAsync(Request.GetApiKey())) - { - return Unauthorized(); - } - - if (await _deleteService.TryDeletePackageAsync(id, nugetVersion, cancellationToken)) - { - return NoContent(); - } - else - { - return NotFound(); - } - } - - public async Task Relist(string id, string version) - { - if (!NuGetVersion.TryParse(version, out var nugetVersion)) - { - return NotFound(); - } - - if (!await _authentication.AuthenticateAsync(Request.GetApiKey())) - { - return Unauthorized(); - } - - if (await _packages.RelistPackageAsync(id, nugetVersion)) - { - return Ok(); - } - else - { - return NotFound(); - } - } - } -} +using System; +using System.Threading; +using System.Threading.Tasks; +using BaGet.Core.Services; +using BaGet.Extensions; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.Logging; +using NuGet.Versioning; + +namespace BaGet.Controllers +{ + public class PackagePublishController : Controller + { + private readonly IAuthenticationService _authentication; + private readonly IPackageIndexingService _indexer; + private readonly IPackageService _packages; + private readonly IPackageDeletionService _deleteService; + private readonly ILogger _logger; + + public PackagePublishController( + IAuthenticationService authentication, + IPackageIndexingService indexer, + IPackageService packages, + IPackageDeletionService deletionService, + ILogger logger) + { + _authentication = authentication ?? throw new ArgumentNullException(nameof(authentication)); + _indexer = indexer ?? throw new ArgumentNullException(nameof(indexer)); + _packages = packages ?? throw new ArgumentNullException(nameof(packages)); + _deleteService = deletionService ?? throw new ArgumentNullException(nameof(deletionService)); + _logger = logger ?? throw new ArgumentNullException(nameof(logger)); + } + + // See: https://docs.microsoft.com/en-us/nuget/api/package-publish-resource#push-a-package + public async Task Upload(CancellationToken cancellationToken) + { + if (!await _authentication.AuthenticateAsync(Request.GetApiKey())) + { + HttpContext.Response.StatusCode = 401; + return; + } + + try + { + using (var uploadStream = await Request.GetUploadStreamOrNullAsync(cancellationToken)) + { + if (uploadStream == null) + { + HttpContext.Response.StatusCode = 400; + return; + } + + var result = await _indexer.IndexAsync(uploadStream, cancellationToken); + + switch (result) + { + case PackageIndexingResult.InvalidPackage: + HttpContext.Response.StatusCode = 400; + break; + + case PackageIndexingResult.PackageAlreadyExists: + HttpContext.Response.StatusCode = 409; + break; + + case PackageIndexingResult.Success: + HttpContext.Response.StatusCode = 201; + break; + } + } + } + catch (Exception e) + { + _logger.LogError(e, "Exception thrown during package upload"); + + HttpContext.Response.StatusCode = 500; + } + } + + public async Task Delete(string id, string version, CancellationToken cancellationToken) + { + if (!NuGetVersion.TryParse(version, out var nugetVersion)) + { + return NotFound(); + } + + if (!await _authentication.AuthenticateAsync(Request.GetApiKey())) + { + return Unauthorized(); + } + + if (await _deleteService.TryDeletePackageAsync(id, nugetVersion, cancellationToken)) + { + return NoContent(); + } + else + { + return NotFound(); + } + } + + public async Task Relist(string id, string version) + { + if (!NuGetVersion.TryParse(version, out var nugetVersion)) + { + return NotFound(); + } + + if (!await _authentication.AuthenticateAsync(Request.GetApiKey())) + { + return Unauthorized(); + } + + if (await _packages.RelistPackageAsync(id, nugetVersion)) + { + return Ok(); + } + else + { + return NotFound(); + } + } + } +} diff --git a/src/BaGet/Controllers/Registration/RegistrationIndexController.cs b/src/BaGet/Controllers/Registration/RegistrationIndexController.cs index 79c6bc00..2f79952d 100644 --- a/src/BaGet/Controllers/Registration/RegistrationIndexController.cs +++ b/src/BaGet/Controllers/Registration/RegistrationIndexController.cs @@ -1,118 +1,118 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; -using BaGet.Core.Entities; -using BaGet.Core.Mirror; -using BaGet.Core.Services; -using BaGet.Extensions; -using BaGet.Protocol; -using Microsoft.AspNetCore.Mvc; - -namespace BaGet.Controllers.Registration -{ - /// - /// The API to retrieve the metadata of a specific package. - /// - public class RegistrationIndexController : Controller - { - private readonly IMirrorService _mirror; - private readonly IPackageService _packages; - - public RegistrationIndexController(IMirrorService mirror, IPackageService packages) - { - _mirror = mirror ?? throw new ArgumentNullException(nameof(mirror)); - _packages = packages ?? throw new ArgumentNullException(nameof(packages)); - } - - // GET v3/registration/{id}.json - [HttpGet] - public async Task Get(string id, CancellationToken cancellationToken) - { - // Find the packages that match the given package id from the upstream, if - // one is configured. If these packages cannot be found on the upstream, - // we'll return the local packages instead. - var packages = await _mirror.FindPackagesOrNullAsync(id, cancellationToken); - - if (packages == null) - { - packages = await _packages.FindAsync(id, includeUnlisted: true); - } - - if (!packages.Any()) - { - return NotFound(); - } - - var versions = packages.Select(p => p.Version).ToList(); - - // TODO: Paging of registration items. - // "Un-paged" example: https://api.nuget.org/v3/registration3/newtonsoft.json/index.json - // Paged example: https://api.nuget.org/v3/registration3/fake/index.json - return Json(new RegistrationIndex( - count: packages.Count, - totalDownloads: packages.Sum(p => p.Downloads), - pages: new[] - { - new RegistrationIndexPage( - Url.PackageRegistration(packages.First().Id), - count: packages.Count(), - itemsOrNull: packages.Select(ToRegistrationIndexPageItem).ToList(), - lower: versions.Min(), - upper: versions.Max()) - })); - } - - private RegistrationIndexPageItem ToRegistrationIndexPageItem(Package package) => - new RegistrationIndexPageItem( - leafUrl: Url.PackageRegistration(package.Id, package.Version), - packageMetadata: new PackageMetadata( - catalogUri: $"https://api.nuget.org/v3/catalog0/data/2015.02.01.06.24.15/{package.Id}.{package.Version}.json", - packageId: package.Id, - version: package.Version, - authors: string.Join(", ", package.Authors), - description: package.Description, - downloads: package.Downloads, - hasReadme: package.HasReadme, - iconUrl: package.IconUrlString, - language: package.Language, - licenseUrl: package.LicenseUrlString, - listed: package.Listed, - minClientVersion: package.MinClientVersion, - packageContent: Url.PackageDownload(package.Id, package.Version), - projectUrl: package.ProjectUrlString, - repositoryUrl: package.RepositoryUrlString, - repositoryType: package.RepositoryType, - published: package.Published, - requireLicenseAcceptance: package.RequireLicenseAcceptance, - summary: package.Summary, - tags: package.Tags, - title: package.Title, - dependencyGroups: ToDependencyGroups(package)), - packageContent: Url.PackageDownload(package.Id, package.Version)); - - private IReadOnlyList ToDependencyGroups(Package package) - { - var groups = new List(); - - var targetFrameworks = package.Dependencies.Select(d => d.TargetFramework).Distinct(); - - foreach (var target in targetFrameworks) - { - // A package may have no dependencies for a target framework. This is represented - // by a single dependency item with a null "Id" and "VersionRange". - var groupId = $"https://api.nuget.org/v3/catalog0/data/2015.02.01.06.24.15/{package.Id}.{package.Version}.json#dependencygroup/{target}"; - var dependencyItems = package.Dependencies - .Where(d => d.TargetFramework == target) - .Where(d => d.Id != null && d.VersionRange != null) - .Select(d => new DependencyItem($"{groupId}/{d.Id}", d.Id, d.VersionRange)) - .ToList(); - - groups.Add(new DependencyGroupItem(groupId, target, dependencyItems)); - } - - return groups; - } - } +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using BaGet.Core.Entities; +using BaGet.Core.Mirror; +using BaGet.Core.Services; +using BaGet.Extensions; +using BaGet.Protocol; +using Microsoft.AspNetCore.Mvc; + +namespace BaGet.Controllers.Registration +{ + /// + /// The API to retrieve the metadata of a specific package. + /// + public class RegistrationIndexController : Controller + { + private readonly IMirrorService _mirror; + private readonly IPackageService _packages; + + public RegistrationIndexController(IMirrorService mirror, IPackageService packages) + { + _mirror = mirror ?? throw new ArgumentNullException(nameof(mirror)); + _packages = packages ?? throw new ArgumentNullException(nameof(packages)); + } + + // GET v3/registration/{id}.json + [HttpGet] + public async Task Get(string id, CancellationToken cancellationToken) + { + // Find the packages that match the given package id from the upstream, if + // one is configured. If these packages cannot be found on the upstream, + // we'll return the local packages instead. + var packages = await _mirror.FindPackagesOrNullAsync(id, cancellationToken); + + if (packages == null) + { + packages = await _packages.FindAsync(id, includeUnlisted: true); + } + + if (!packages.Any()) + { + return NotFound(); + } + + var versions = packages.Select(p => p.Version).ToList(); + + // TODO: Paging of registration items. + // "Un-paged" example: https://api.nuget.org/v3/registration3/newtonsoft.json/index.json + // Paged example: https://api.nuget.org/v3/registration3/fake/index.json + return Json(new RegistrationIndex( + count: packages.Count, + totalDownloads: packages.Sum(p => p.Downloads), + pages: new[] + { + new RegistrationIndexPage( + Url.PackageRegistration(packages.First().Id), + count: packages.Count(), + itemsOrNull: packages.Select(ToRegistrationIndexPageItem).ToList(), + lower: versions.Min(), + upper: versions.Max()) + })); + } + + private RegistrationIndexPageItem ToRegistrationIndexPageItem(Package package) => + new RegistrationIndexPageItem( + leafUrl: Url.PackageRegistration(package.Id, package.Version), + packageMetadata: new PackageMetadata( + catalogUri: $"https://api.nuget.org/v3/catalog0/data/2015.02.01.06.24.15/{package.Id}.{package.Version}.json", + packageId: package.Id, + version: package.Version, + authors: string.Join(", ", package.Authors), + description: package.Description, + downloads: package.Downloads, + hasReadme: package.HasReadme, + iconUrl: package.IconUrlString, + language: package.Language, + licenseUrl: package.LicenseUrlString, + listed: package.Listed, + minClientVersion: package.MinClientVersion, + packageContent: Url.PackageDownload(package.Id, package.Version), + projectUrl: package.ProjectUrlString, + repositoryUrl: package.RepositoryUrlString, + repositoryType: package.RepositoryType, + published: package.Published, + requireLicenseAcceptance: package.RequireLicenseAcceptance, + summary: package.Summary, + tags: package.Tags, + title: package.Title, + dependencyGroups: ToDependencyGroups(package)), + packageContent: Url.PackageDownload(package.Id, package.Version)); + + private IReadOnlyList ToDependencyGroups(Package package) + { + var groups = new List(); + + var targetFrameworks = package.Dependencies.Select(d => d.TargetFramework).Distinct(); + + foreach (var target in targetFrameworks) + { + // A package may have no dependencies for a target framework. This is represented + // by a single dependency item with a null "Id" and "VersionRange". + var groupId = $"https://api.nuget.org/v3/catalog0/data/2015.02.01.06.24.15/{package.Id}.{package.Version}.json#dependencygroup/{target}"; + var dependencyItems = package.Dependencies + .Where(d => d.TargetFramework == target) + .Where(d => d.Id != null && d.VersionRange != null) + .Select(d => new DependencyItem($"{groupId}/{d.Id}", d.Id, d.VersionRange)) + .ToList(); + + groups.Add(new DependencyGroupItem(groupId, target, dependencyItems)); + } + + return groups; + } + } } \ No newline at end of file diff --git a/src/BaGet/Controllers/Registration/RegistrationLeafController.cs b/src/BaGet/Controllers/Registration/RegistrationLeafController.cs index d0b3c99d..f1f42a6b 100644 --- a/src/BaGet/Controllers/Registration/RegistrationLeafController.cs +++ b/src/BaGet/Controllers/Registration/RegistrationLeafController.cs @@ -1,57 +1,57 @@ -using System; -using System.Threading; -using System.Threading.Tasks; -using BaGet.Core.Mirror; -using BaGet.Core.Services; -using BaGet.Extensions; -using BaGet.Protocol; -using Microsoft.AspNetCore.Mvc; -using NuGet.Versioning; - -namespace BaGet.Controllers.Registration -{ - /// - /// The API to retrieve the metadata of a specific version of a specific package. - /// - public class RegistrationLeafController : Controller - { - private readonly IMirrorService _mirror; - private readonly IPackageService _packages; - - public RegistrationLeafController(IMirrorService mirror, IPackageService packages) - { - _mirror = mirror ?? throw new ArgumentNullException(nameof(mirror)); - _packages = packages ?? throw new ArgumentNullException(nameof(packages)); - } - - // GET v3/registration/{id}/{version}.json - [HttpGet] - public async Task Get(string id, string version, CancellationToken cancellationToken) - { - if (!NuGetVersion.TryParse(version, out var nugetVersion)) - { - return NotFound(); - } - - // Allow read-through caching to happen if it is configured. - await _mirror.MirrorAsync(id, nugetVersion, cancellationToken); - - var package = await _packages.FindOrNullAsync(id, nugetVersion, includeUnlisted: true); - - if (package == null) - { - return NotFound(); - } - - var result = new RegistrationLeaf( - registrationUri: Url.PackageRegistration(id, nugetVersion), - listed: package.Listed, - downloads: package.Downloads, - packageContentUrl: Url.PackageDownload(id, nugetVersion), - published: package.Published, - registrationIndexUrl: Url.PackageRegistration(id)); - - return Json(result); - } - } +using System; +using System.Threading; +using System.Threading.Tasks; +using BaGet.Core.Mirror; +using BaGet.Core.Services; +using BaGet.Extensions; +using BaGet.Protocol; +using Microsoft.AspNetCore.Mvc; +using NuGet.Versioning; + +namespace BaGet.Controllers.Registration +{ + /// + /// The API to retrieve the metadata of a specific version of a specific package. + /// + public class RegistrationLeafController : Controller + { + private readonly IMirrorService _mirror; + private readonly IPackageService _packages; + + public RegistrationLeafController(IMirrorService mirror, IPackageService packages) + { + _mirror = mirror ?? throw new ArgumentNullException(nameof(mirror)); + _packages = packages ?? throw new ArgumentNullException(nameof(packages)); + } + + // GET v3/registration/{id}/{version}.json + [HttpGet] + public async Task Get(string id, string version, CancellationToken cancellationToken) + { + if (!NuGetVersion.TryParse(version, out var nugetVersion)) + { + return NotFound(); + } + + // Allow read-through caching to happen if it is configured. + await _mirror.MirrorAsync(id, nugetVersion, cancellationToken); + + var package = await _packages.FindOrNullAsync(id, nugetVersion, includeUnlisted: true); + + if (package == null) + { + return NotFound(); + } + + var result = new RegistrationLeaf( + registrationUri: Url.PackageRegistration(id, nugetVersion), + listed: package.Listed, + downloads: package.Downloads, + packageContentUrl: Url.PackageDownload(id, nugetVersion), + published: package.Published, + registrationIndexUrl: Url.PackageRegistration(id)); + + return Json(result); + } + } } \ No newline at end of file diff --git a/src/BaGet/Controllers/SearchController.cs b/src/BaGet/Controllers/SearchController.cs index 9608ec86..a45351b8 100644 --- a/src/BaGet/Controllers/SearchController.cs +++ b/src/BaGet/Controllers/SearchController.cs @@ -1,67 +1,67 @@ -using System; -using System.Linq; -using System.Threading.Tasks; -using BaGet.Core.Services; -using BaGet.Extensions; -using BaGet.Protocol; -using Microsoft.AspNetCore.Mvc; - -namespace BaGet.Controllers -{ - using ProtocolSearchResult = Protocol.SearchResult; - using QuerySearchResult = Core.Services.SearchResult; - - public class SearchController : Controller - { - private readonly ISearchService _searchService; - - public SearchController(ISearchService searchService) - { - _searchService = searchService ?? throw new ArgumentNullException(nameof(searchService)); - } - - public async Task Get([FromQuery(Name = "q")] string query = null) - { - query = query ?? string.Empty; - - var results = await _searchService.SearchAsync(query); - var response = new SearchResponse( - totalHits: results.Count, - data: results.Select(ToSearchResult).ToList()); - - return Json(response); - } - - public async Task Autocomplete([FromQuery(Name = "q")] string query = null) - { - var results = await _searchService.AutocompleteAsync(query); - var response = new AutocompleteResult(results.Count, results); - - return Json(response); - } - - private ProtocolSearchResult ToSearchResult(QuerySearchResult result) - { - var versions = result.Versions.Select( - v => new Protocol.SearchResultVersion( - registrationLeafUrl: Url.PackageRegistration(result.Id, v.Version), - version: v.Version, - downloads: v.Downloads)); - - return new ProtocolSearchResult( - id: result.Id, - version: result.Version, - description: result.Description, - authors: result.Authors, - iconUrl: result.IconUrl, - licenseUrl: result.LicenseUrl, - projectUrl: result.ProjectUrl, - registrationUrl: Url.PackageRegistration(result.Id), - summary: result.Summary, - tags: result.Tags, - title: result.Title, - totalDownloads: result.TotalDownloads, - versions: versions.ToList()); - } - } +using System; +using System.Linq; +using System.Threading.Tasks; +using BaGet.Core.Services; +using BaGet.Extensions; +using BaGet.Protocol; +using Microsoft.AspNetCore.Mvc; + +namespace BaGet.Controllers +{ + using ProtocolSearchResult = Protocol.SearchResult; + using QuerySearchResult = Core.Services.SearchResult; + + public class SearchController : Controller + { + private readonly ISearchService _searchService; + + public SearchController(ISearchService searchService) + { + _searchService = searchService ?? throw new ArgumentNullException(nameof(searchService)); + } + + public async Task Get([FromQuery(Name = "q")] string query = null) + { + query = query ?? string.Empty; + + var results = await _searchService.SearchAsync(query); + var response = new SearchResponse( + totalHits: results.Count, + data: results.Select(ToSearchResult).ToList()); + + return Json(response); + } + + public async Task Autocomplete([FromQuery(Name = "q")] string query = null) + { + var results = await _searchService.AutocompleteAsync(query); + var response = new AutocompleteResult(results.Count, results); + + return Json(response); + } + + private ProtocolSearchResult ToSearchResult(QuerySearchResult result) + { + var versions = result.Versions.Select( + v => new Protocol.SearchResultVersion( + registrationLeafUrl: Url.PackageRegistration(result.Id, v.Version), + version: v.Version, + downloads: v.Downloads)); + + return new ProtocolSearchResult( + id: result.Id, + version: result.Version, + description: result.Description, + authors: result.Authors, + iconUrl: result.IconUrl, + licenseUrl: result.LicenseUrl, + projectUrl: result.ProjectUrl, + registrationUrl: Url.PackageRegistration(result.Id), + summary: result.Summary, + tags: result.Tags, + title: result.Title, + totalDownloads: result.TotalDownloads, + versions: versions.ToList()); + } + } } \ No newline at end of file diff --git a/src/BaGet/Entities/AbstractContext.cs b/src/BaGet/Entities/AbstractContext.cs index 00f47a35..df4af181 100644 --- a/src/BaGet/Entities/AbstractContext.cs +++ b/src/BaGet/Entities/AbstractContext.cs @@ -1,105 +1,105 @@ -using System.Threading; -using System.Threading.Tasks; -using BaGet.Core.Entities; -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Metadata.Builders; - -namespace BaGet.Entities -{ - public abstract class AbstractContext : DbContext, IContext where TContext : DbContext - { - public const int DefaultMaxStringLength = 4000; - - public const int MaxPackageIdLength = 128; - public const int MaxPackageVersionLength = 64; - public const int MaxPackageMinClientVersionLength = 44; - public const int MaxPackageLanguageLength = 20; - public const int MaxPackageTitleLength = 256; - public const int MaxRepositoryTypeLength = 100; - - public const int MaxPackageDependencyVersionRangeLength = 256; - public const int MaxPackageDependencyTargetFrameworkLength = 256; - - public AbstractContext(DbContextOptions options) - : base(options) - { } - - public DbSet Packages { get; set; } - public DbSet PackageDependencies { get; set; } - - public Task SaveChangesAsync() => SaveChangesAsync(default(CancellationToken)); - - public abstract bool IsUniqueConstraintViolationException(DbUpdateException exception); - - protected override void OnModelCreating(ModelBuilder builder) - { - builder.Entity(BuildPackageEntity); - builder.Entity(BuildPackageDependencyEntity); - } - - private void BuildPackageEntity(EntityTypeBuilder package) - { - package.HasKey(p => p.Key); - package.HasIndex(p => p.Id); - package.HasIndex(p => new { p.Id, p.VersionString }) - .IsUnique(); - - package.Property(p => p.Id) - .HasMaxLength(MaxPackageIdLength) - .IsRequired(); - - package.Property(p => p.VersionString) - .HasColumnName("Version") - .HasMaxLength(MaxPackageVersionLength) - .IsRequired(); - - package.Property(p => p.Authors) - .HasConversion(StringArrayToJsonConverter.Instance) - .HasMaxLength(DefaultMaxStringLength); - - package.Property(p => p.IconUrl) - .HasConversion(UriToStringConverter.Instance) - .HasMaxLength(DefaultMaxStringLength); - - package.Property(p => p.LicenseUrl) - .HasConversion(UriToStringConverter.Instance) - .HasMaxLength(DefaultMaxStringLength); - - package.Property(p => p.ProjectUrl) - .HasConversion(UriToStringConverter.Instance) - .HasMaxLength(DefaultMaxStringLength); - - package.Property(p => p.RepositoryUrl) - .HasConversion(UriToStringConverter.Instance) - .HasMaxLength(DefaultMaxStringLength); - - package.Property(p => p.Tags) - .HasConversion(StringArrayToJsonConverter.Instance) - .HasMaxLength(DefaultMaxStringLength); - - package.Property(p => p.Description).HasMaxLength(DefaultMaxStringLength); - package.Property(p => p.Language).HasMaxLength(MaxPackageLanguageLength); - package.Property(p => p.MinClientVersion).HasMaxLength(MaxPackageMinClientVersionLength); - package.Property(p => p.Summary).HasMaxLength(DefaultMaxStringLength); - package.Property(p => p.Title).HasMaxLength(MaxPackageTitleLength); - package.Property(p => p.RepositoryType).HasMaxLength(MaxRepositoryTypeLength); - - package.Ignore(p => p.Version); - package.Ignore(p => p.IconUrlString); - package.Ignore(p => p.LicenseUrlString); - package.Ignore(p => p.ProjectUrlString); - package.Ignore(p => p.RepositoryUrlString); - - package.Property(p => p.RowVersion).IsRowVersion(); - } - - private void BuildPackageDependencyEntity(EntityTypeBuilder dependency) - { - dependency.HasKey(d => d.Key); - - dependency.Property(d => d.Id).HasMaxLength(MaxPackageIdLength); - dependency.Property(d => d.VersionRange).HasMaxLength(MaxPackageDependencyVersionRangeLength); - dependency.Property(d => d.TargetFramework).HasMaxLength(MaxPackageDependencyTargetFrameworkLength); - } - } -} +using System.Threading; +using System.Threading.Tasks; +using BaGet.Core.Entities; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; + +namespace BaGet.Entities +{ + public abstract class AbstractContext : DbContext, IContext where TContext : DbContext + { + public const int DefaultMaxStringLength = 4000; + + public const int MaxPackageIdLength = 128; + public const int MaxPackageVersionLength = 64; + public const int MaxPackageMinClientVersionLength = 44; + public const int MaxPackageLanguageLength = 20; + public const int MaxPackageTitleLength = 256; + public const int MaxRepositoryTypeLength = 100; + + public const int MaxPackageDependencyVersionRangeLength = 256; + public const int MaxPackageDependencyTargetFrameworkLength = 256; + + public AbstractContext(DbContextOptions options) + : base(options) + { } + + public DbSet Packages { get; set; } + public DbSet PackageDependencies { get; set; } + + public Task SaveChangesAsync() => SaveChangesAsync(default(CancellationToken)); + + public abstract bool IsUniqueConstraintViolationException(DbUpdateException exception); + + protected override void OnModelCreating(ModelBuilder builder) + { + builder.Entity(BuildPackageEntity); + builder.Entity(BuildPackageDependencyEntity); + } + + private void BuildPackageEntity(EntityTypeBuilder package) + { + package.HasKey(p => p.Key); + package.HasIndex(p => p.Id); + package.HasIndex(p => new { p.Id, p.VersionString }) + .IsUnique(); + + package.Property(p => p.Id) + .HasMaxLength(MaxPackageIdLength) + .IsRequired(); + + package.Property(p => p.VersionString) + .HasColumnName("Version") + .HasMaxLength(MaxPackageVersionLength) + .IsRequired(); + + package.Property(p => p.Authors) + .HasConversion(StringArrayToJsonConverter.Instance) + .HasMaxLength(DefaultMaxStringLength); + + package.Property(p => p.IconUrl) + .HasConversion(UriToStringConverter.Instance) + .HasMaxLength(DefaultMaxStringLength); + + package.Property(p => p.LicenseUrl) + .HasConversion(UriToStringConverter.Instance) + .HasMaxLength(DefaultMaxStringLength); + + package.Property(p => p.ProjectUrl) + .HasConversion(UriToStringConverter.Instance) + .HasMaxLength(DefaultMaxStringLength); + + package.Property(p => p.RepositoryUrl) + .HasConversion(UriToStringConverter.Instance) + .HasMaxLength(DefaultMaxStringLength); + + package.Property(p => p.Tags) + .HasConversion(StringArrayToJsonConverter.Instance) + .HasMaxLength(DefaultMaxStringLength); + + package.Property(p => p.Description).HasMaxLength(DefaultMaxStringLength); + package.Property(p => p.Language).HasMaxLength(MaxPackageLanguageLength); + package.Property(p => p.MinClientVersion).HasMaxLength(MaxPackageMinClientVersionLength); + package.Property(p => p.Summary).HasMaxLength(DefaultMaxStringLength); + package.Property(p => p.Title).HasMaxLength(MaxPackageTitleLength); + package.Property(p => p.RepositoryType).HasMaxLength(MaxRepositoryTypeLength); + + package.Ignore(p => p.Version); + package.Ignore(p => p.IconUrlString); + package.Ignore(p => p.LicenseUrlString); + package.Ignore(p => p.ProjectUrlString); + package.Ignore(p => p.RepositoryUrlString); + + package.Property(p => p.RowVersion).IsRowVersion(); + } + + private void BuildPackageDependencyEntity(EntityTypeBuilder dependency) + { + dependency.HasKey(d => d.Key); + + dependency.Property(d => d.Id).HasMaxLength(MaxPackageIdLength); + dependency.Property(d => d.VersionRange).HasMaxLength(MaxPackageDependencyVersionRangeLength); + dependency.Property(d => d.TargetFramework).HasMaxLength(MaxPackageDependencyTargetFrameworkLength); + } + } +} diff --git a/src/BaGet/Entities/Converters/StringArrayToJsonConverter.cs b/src/BaGet/Entities/Converters/StringArrayToJsonConverter.cs index 73447ff5..e0df1038 100644 --- a/src/BaGet/Entities/Converters/StringArrayToJsonConverter.cs +++ b/src/BaGet/Entities/Converters/StringArrayToJsonConverter.cs @@ -1,17 +1,17 @@ -using Microsoft.EntityFrameworkCore.Storage.ValueConversion; -using Newtonsoft.Json; - -namespace BaGet.Entities -{ - public class StringArrayToJsonConverter : ValueConverter - { - public static readonly StringArrayToJsonConverter Instance = new StringArrayToJsonConverter(); - - public StringArrayToJsonConverter() - : base( - v => JsonConvert.SerializeObject(v), - v => (!string.IsNullOrEmpty(v)) ? JsonConvert.DeserializeObject(v) : new string[0]) - { - } - } -} +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Newtonsoft.Json; + +namespace BaGet.Entities +{ + public class StringArrayToJsonConverter : ValueConverter + { + public static readonly StringArrayToJsonConverter Instance = new StringArrayToJsonConverter(); + + public StringArrayToJsonConverter() + : base( + v => JsonConvert.SerializeObject(v), + v => (!string.IsNullOrEmpty(v)) ? JsonConvert.DeserializeObject(v) : new string[0]) + { + } + } +} diff --git a/src/BaGet/Entities/Converters/UriToStringConverter.cs b/src/BaGet/Entities/Converters/UriToStringConverter.cs index 9a15c526..d4d5c7f9 100644 --- a/src/BaGet/Entities/Converters/UriToStringConverter.cs +++ b/src/BaGet/Entities/Converters/UriToStringConverter.cs @@ -1,17 +1,17 @@ -using System; -using Microsoft.EntityFrameworkCore.Storage.ValueConversion; - -namespace BaGet.Entities -{ - public class UriToStringConverter : ValueConverter - { - public static readonly UriToStringConverter Instance = new UriToStringConverter(); - - public UriToStringConverter() - : base( - v => v.AbsoluteUri, - v => new Uri(v)) - { - } - } -} +using System; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; + +namespace BaGet.Entities +{ + public class UriToStringConverter : ValueConverter + { + public static readonly UriToStringConverter Instance = new UriToStringConverter(); + + public UriToStringConverter() + : base( + v => v.AbsoluteUri, + v => new Uri(v)) + { + } + } +} diff --git a/src/BaGet/Entities/SqlServerContext.cs b/src/BaGet/Entities/SqlServerContext.cs index 2e0cbf09..37998fcf 100644 --- a/src/BaGet/Entities/SqlServerContext.cs +++ b/src/BaGet/Entities/SqlServerContext.cs @@ -1,35 +1,35 @@ -using System.Data.SqlClient; -using System.Linq; -using Microsoft.EntityFrameworkCore; - -namespace BaGet.Entities -{ - public class SqlServerContext : AbstractContext - { - /// - /// The SQL Server error code for when a unique contraint is violated. - /// - private const int UniqueConstraintViolationErrorCode = 2627; - - public SqlServerContext(DbContextOptions options) - : base(options) - { } - - /// - /// Check whether a is due to a SQL unique constraint violation. - /// - /// The exception to inspect. - /// Whether the exception was caused to SQL unique constraint violation. - public override bool IsUniqueConstraintViolationException(DbUpdateException exception) - { - if (exception.GetBaseException() is SqlException sqlException) - { - return sqlException.Errors - .OfType() - .Any(error => error.Number == UniqueConstraintViolationErrorCode); - } - - return false; - } - } -} +using System.Data.SqlClient; +using System.Linq; +using Microsoft.EntityFrameworkCore; + +namespace BaGet.Entities +{ + public class SqlServerContext : AbstractContext + { + /// + /// The SQL Server error code for when a unique contraint is violated. + /// + private const int UniqueConstraintViolationErrorCode = 2627; + + public SqlServerContext(DbContextOptions options) + : base(options) + { } + + /// + /// Check whether a is due to a SQL unique constraint violation. + /// + /// The exception to inspect. + /// Whether the exception was caused to SQL unique constraint violation. + public override bool IsUniqueConstraintViolationException(DbUpdateException exception) + { + if (exception.GetBaseException() is SqlException sqlException) + { + return sqlException.Errors + .OfType() + .Any(error => error.Number == UniqueConstraintViolationErrorCode); + } + + return false; + } + } +} diff --git a/src/BaGet/Entities/SqliteContext.cs b/src/BaGet/Entities/SqliteContext.cs index ab61b4ce..e63f64c2 100644 --- a/src/BaGet/Entities/SqliteContext.cs +++ b/src/BaGet/Entities/SqliteContext.cs @@ -1,37 +1,37 @@ -using BaGet.Core.Entities; -using Microsoft.Data.Sqlite; -using Microsoft.EntityFrameworkCore; - -namespace BaGet.Entities -{ - public class SqliteContext : AbstractContext - { - /// - /// The Sqlite error code for when a unique constraint is violated. - /// - private const int SqliteUniqueConstraintViolationErrorCode = 19; - - public SqliteContext(DbContextOptions options) - : base(options) - { } - - public override bool IsUniqueConstraintViolationException(DbUpdateException exception) - { - return exception.InnerException is SqliteException sqliteException && - sqliteException.SqliteErrorCode == SqliteUniqueConstraintViolationErrorCode; - } - - protected override void OnModelCreating(ModelBuilder builder) - { - base.OnModelCreating(builder); - - builder.Entity() - .Property(p => p.Id) - .HasColumnType("TEXT COLLATE NOCASE"); - - builder.Entity() - .Property(p => p.Id) - .HasColumnType("TEXT COLLATE NOCASE"); - } - } -} +using BaGet.Core.Entities; +using Microsoft.Data.Sqlite; +using Microsoft.EntityFrameworkCore; + +namespace BaGet.Entities +{ + public class SqliteContext : AbstractContext + { + /// + /// The Sqlite error code for when a unique constraint is violated. + /// + private const int SqliteUniqueConstraintViolationErrorCode = 19; + + public SqliteContext(DbContextOptions options) + : base(options) + { } + + public override bool IsUniqueConstraintViolationException(DbUpdateException exception) + { + return exception.InnerException is SqliteException sqliteException && + sqliteException.SqliteErrorCode == SqliteUniqueConstraintViolationErrorCode; + } + + protected override void OnModelCreating(ModelBuilder builder) + { + base.OnModelCreating(builder); + + builder.Entity() + .Property(p => p.Id) + .HasColumnType("TEXT COLLATE NOCASE"); + + builder.Entity() + .Property(p => p.Id) + .HasColumnType("TEXT COLLATE NOCASE"); + } + } +} diff --git a/src/BaGet/Extensions/IHostBuilderExtensions.cs b/src/BaGet/Extensions/IHostBuilderExtensions.cs index 078362f4..59375db1 100644 --- a/src/BaGet/Extensions/IHostBuilderExtensions.cs +++ b/src/BaGet/Extensions/IHostBuilderExtensions.cs @@ -1,46 +1,46 @@ -using System; -using Microsoft.Extensions.Configuration; -using Microsoft.Extensions.Hosting; -using Microsoft.Extensions.Logging; - -namespace BaGet.Extensions -{ - // See https://github.com/aspnet/MetaPackages/blob/master/src/Microsoft.AspNetCore/WebHost.cs - public static class IHostBuilderExtensions - { - public static IHostBuilder ConfigureBaGetConfiguration(this IHostBuilder builder, string[] args) - { - return builder.ConfigureAppConfiguration((context, config) => - { - config.AddEnvironmentVariables(); - - config - .SetBasePath(Environment.CurrentDirectory) - .AddJsonFile("appsettings.json", optional: true, reloadOnChange: true); - - if (args != null) - { - config.AddCommandLine(args); - } - }); - - } - - public static IHostBuilder ConfigureBaGetLogging(this IHostBuilder builder) - { - return builder - .ConfigureLogging((context, logging) => - { - logging.AddConfiguration(context.Configuration.GetSection("Logging")); - logging.AddConsole(); - logging.AddDebug(); - }); - } - - public static IHostBuilder ConfigureBaGetServices(this IHostBuilder builder) - { - return builder - .ConfigureServices((context, services) => services.ConfigureBaGet(context.Configuration)); - } - } -} +using System; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.Logging; + +namespace BaGet.Extensions +{ + // See https://github.com/aspnet/MetaPackages/blob/master/src/Microsoft.AspNetCore/WebHost.cs + public static class IHostBuilderExtensions + { + public static IHostBuilder ConfigureBaGetConfiguration(this IHostBuilder builder, string[] args) + { + return builder.ConfigureAppConfiguration((context, config) => + { + config.AddEnvironmentVariables(); + + config + .SetBasePath(Environment.CurrentDirectory) + .AddJsonFile("appsettings.json", optional: true, reloadOnChange: true); + + if (args != null) + { + config.AddCommandLine(args); + } + }); + + } + + public static IHostBuilder ConfigureBaGetLogging(this IHostBuilder builder) + { + return builder + .ConfigureLogging((context, logging) => + { + logging.AddConfiguration(context.Configuration.GetSection("Logging")); + logging.AddConsole(); + logging.AddDebug(); + }); + } + + public static IHostBuilder ConfigureBaGetServices(this IHostBuilder builder) + { + return builder + .ConfigureServices((context, services) => services.ConfigureBaGet(context.Configuration)); + } + } +} diff --git a/src/BaGet/Extensions/IRouteBuilderExtensions.cs b/src/BaGet/Extensions/IRouteBuilderExtensions.cs index 95781392..66339ec5 100644 --- a/src/BaGet/Extensions/IRouteBuilderExtensions.cs +++ b/src/BaGet/Extensions/IRouteBuilderExtensions.cs @@ -1,111 +1,111 @@ -using Microsoft.AspNetCore.Builder; -using Microsoft.AspNetCore.Routing; -using Microsoft.AspNetCore.Routing.Constraints; - -namespace BaGet.Extensions -{ - public static class IRouteBuilderExtensions - { - public static IRouteBuilder MapServiceIndexRoutes(this IRouteBuilder routes) - { - return routes.MapRoute( - name: Routes.IndexRouteName, - template: "v3/index.json", - defaults: new { controller = "Index", action = "Get" }); - } - - public static IRouteBuilder MapPackagePublishRoutes(this IRouteBuilder routes) - { - routes.MapRoute( - name: Routes.UploadPackageRouteName, - template: "api/v2/package", - defaults: new { controller = "PackagePublish", action = "Upload" }, - constraints: new { httpMethod = new HttpMethodRouteConstraint("PUT") }); - - routes.MapRoute( - name: Routes.DeleteRouteName, - template: "api/v2/package/{id}/{version}", - defaults: new { controller = "PackagePublish", action = "Delete" }, - constraints: new { httpMethod = new HttpMethodRouteConstraint("DELETE") }); - - routes.MapRoute( - name: Routes.RelistRouteName, - template: "api/v2/package/{id}/{version}", - defaults: new { controller = "PackagePublish", action = "Relist" }, - constraints: new { httpMethod = new HttpMethodRouteConstraint("POST") }); - - return routes; - } - - public static IRouteBuilder MapSymbolRoutes(this IRouteBuilder routes) - { - routes.MapRoute( - name: Routes.UploadSymbolRouteName, - template: "api/v2/symbol", - defaults: new { controller = "Symbol", action = "Upload" }, - constraints: new { httpMethod = new HttpMethodRouteConstraint("PUT") }); - - routes.MapRoute( - name: Routes.SymbolDownloadRouteName, - template: "api/download/symbols/{file}/{key}/{file2}", - defaults: new { controller = "Symbol", action = "Get" }); - - return routes; - } - - public static IRouteBuilder MapSearchRoutes(this IRouteBuilder routes) - { - routes.MapRoute( - name: Routes.SearchRouteName, - template: "v3/search", - defaults: new { controller = "Search", action = "Get" }); - - routes.MapRoute( - name: Routes.AutocompleteRouteName, - template: "v3/autocomplete", - defaults: new { controller = "Search", action = "Autocomplete" }); - - return routes; - } - - public static IRouteBuilder MapRegistrationRoutes(this IRouteBuilder routes) - { - routes.MapRoute( - name: Routes.RegistrationIndexRouteName, - template: "v3/registration/{id}/index.json", - defaults: new { controller = "RegistrationIndex", action = "Get" }); - - routes.MapRoute( - name: Routes.RegistrationLeafRouteName, - template: "v3/registration/{id}/{version}.json", - defaults: new { controller = "RegistrationLeaf", action = "Get" }); - - return routes; - } - - public static IRouteBuilder MapPackageContentRoutes(this IRouteBuilder routes) - { - routes.MapRoute( - name: Routes.PackageVersionsRouteName, - template: "v3/package/{id}/index.json", - defaults: new { controller = "Package", action = "Versions" }); - - routes.MapRoute( - name: Routes.PackageDownloadRouteName, - template: "v3/package/{id}/{version}/{idVersion}.nupkg", - defaults: new { controller = "Package", action = "DownloadPackage" }); - - routes.MapRoute( - name: Routes.PackageDownloadManifestRouteName, - template: "v3/package/{id}/{version}/{id2}.nuspec", - defaults: new { controller = "Package", action = "DownloadNuspec" }); - - routes.MapRoute( - name: Routes.PackageDownloadReadmeRouteName, - template: "v3/package/{id}/{version}/readme", - defaults: new { controller = "Package", action = "DownloadReadme" }); - - return routes; - } - } -} +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Routing; +using Microsoft.AspNetCore.Routing.Constraints; + +namespace BaGet.Extensions +{ + public static class IRouteBuilderExtensions + { + public static IRouteBuilder MapServiceIndexRoutes(this IRouteBuilder routes) + { + return routes.MapRoute( + name: Routes.IndexRouteName, + template: "v3/index.json", + defaults: new { controller = "Index", action = "Get" }); + } + + public static IRouteBuilder MapPackagePublishRoutes(this IRouteBuilder routes) + { + routes.MapRoute( + name: Routes.UploadPackageRouteName, + template: "api/v2/package", + defaults: new { controller = "PackagePublish", action = "Upload" }, + constraints: new { httpMethod = new HttpMethodRouteConstraint("PUT") }); + + routes.MapRoute( + name: Routes.DeleteRouteName, + template: "api/v2/package/{id}/{version}", + defaults: new { controller = "PackagePublish", action = "Delete" }, + constraints: new { httpMethod = new HttpMethodRouteConstraint("DELETE") }); + + routes.MapRoute( + name: Routes.RelistRouteName, + template: "api/v2/package/{id}/{version}", + defaults: new { controller = "PackagePublish", action = "Relist" }, + constraints: new { httpMethod = new HttpMethodRouteConstraint("POST") }); + + return routes; + } + + public static IRouteBuilder MapSymbolRoutes(this IRouteBuilder routes) + { + routes.MapRoute( + name: Routes.UploadSymbolRouteName, + template: "api/v2/symbol", + defaults: new { controller = "Symbol", action = "Upload" }, + constraints: new { httpMethod = new HttpMethodRouteConstraint("PUT") }); + + routes.MapRoute( + name: Routes.SymbolDownloadRouteName, + template: "api/download/symbols/{file}/{key}/{file2}", + defaults: new { controller = "Symbol", action = "Get" }); + + return routes; + } + + public static IRouteBuilder MapSearchRoutes(this IRouteBuilder routes) + { + routes.MapRoute( + name: Routes.SearchRouteName, + template: "v3/search", + defaults: new { controller = "Search", action = "Get" }); + + routes.MapRoute( + name: Routes.AutocompleteRouteName, + template: "v3/autocomplete", + defaults: new { controller = "Search", action = "Autocomplete" }); + + return routes; + } + + public static IRouteBuilder MapRegistrationRoutes(this IRouteBuilder routes) + { + routes.MapRoute( + name: Routes.RegistrationIndexRouteName, + template: "v3/registration/{id}/index.json", + defaults: new { controller = "RegistrationIndex", action = "Get" }); + + routes.MapRoute( + name: Routes.RegistrationLeafRouteName, + template: "v3/registration/{id}/{version}.json", + defaults: new { controller = "RegistrationLeaf", action = "Get" }); + + return routes; + } + + public static IRouteBuilder MapPackageContentRoutes(this IRouteBuilder routes) + { + routes.MapRoute( + name: Routes.PackageVersionsRouteName, + template: "v3/package/{id}/index.json", + defaults: new { controller = "Package", action = "Versions" }); + + routes.MapRoute( + name: Routes.PackageDownloadRouteName, + template: "v3/package/{id}/{version}/{idVersion}.nupkg", + defaults: new { controller = "Package", action = "DownloadPackage" }); + + routes.MapRoute( + name: Routes.PackageDownloadManifestRouteName, + template: "v3/package/{id}/{version}/{id2}.nuspec", + defaults: new { controller = "Package", action = "DownloadNuspec" }); + + routes.MapRoute( + name: Routes.PackageDownloadReadmeRouteName, + template: "v3/package/{id}/{version}/readme", + defaults: new { controller = "Package", action = "DownloadReadme" }); + + return routes; + } + } +} diff --git a/src/BaGet/Extensions/ServiceCollectionExtensions.cs b/src/BaGet/Extensions/ServiceCollectionExtensions.cs index 1902e092..c76bc8e1 100644 --- a/src/BaGet/Extensions/ServiceCollectionExtensions.cs +++ b/src/BaGet/Extensions/ServiceCollectionExtensions.cs @@ -1,301 +1,301 @@ -using System; -using System.Collections.Generic; -using System.ComponentModel.DataAnnotations; -using System.IO; -using System.Net; -using System.Net.Http; -using System.Reflection; -using BaGet.Azure.Configuration; -using BaGet.Azure.Extensions; -using BaGet.Azure.Search; -using BaGet.Configurations; -using BaGet.Core.Configuration; -using BaGet.Core.Entities; -using BaGet.Core.Mirror; -using BaGet.Core.Services; -using BaGet.Entities; -using BaGet.Protocol; -using Microsoft.AspNetCore.Builder; -using Microsoft.AspNetCore.Cors.Infrastructure; -using Microsoft.AspNetCore.HttpOverrides; -using Microsoft.EntityFrameworkCore; -using Microsoft.Extensions.Configuration; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Options; - -namespace BaGet.Extensions -{ - public static class ServiceCollectionExtensions - { - public static IServiceCollection ConfigureBaGet( - this IServiceCollection services, - IConfiguration configuration, - bool httpServices = false) - { - services.ConfigureAndValidate(configuration); - services.ConfigureAndValidateSection(configuration, nameof(BaGetOptions.Search)); - services.ConfigureAndValidateSection(configuration, nameof(BaGetOptions.Mirror)); - services.ConfigureAndValidateSection(configuration, nameof(BaGetOptions.Storage)); - services.ConfigureAndValidateSection(configuration, nameof(BaGetOptions.Database)); - services.ConfigureAndValidateSection(configuration, nameof(BaGetOptions.Storage)); - services.ConfigureAndValidateSection(configuration, nameof(BaGetOptions.Storage)); - services.ConfigureAndValidateSection(configuration, nameof(BaGetOptions.Search)); - - services.AddBaGetContext(); - services.ConfigureAzure(configuration); - - if (httpServices) - { - services.ConfigureHttpServices(); - } - - services.AddTransient(); - services.AddTransient(); - services.AddTransient(); - services.AddTransient(); - services.AddMirrorServices(); - - services.ConfigureStorageProviders(); - services.ConfigureSearchProviders(); - services.ConfigureAuthenticationProviders(); - - return services; - } - - public static IServiceCollection AddBaGetContext(this IServiceCollection services) - { - if (services == null) throw new ArgumentNullException(nameof(services)); - - services.AddScoped(provider => - { - var databaseOptions = provider.GetRequiredService>(); - - switch (databaseOptions.Value.Type) - { - case DatabaseType.Sqlite: - return provider.GetRequiredService(); - - case DatabaseType.SqlServer: - return provider.GetRequiredService(); - - default: - throw new InvalidOperationException( - $"Unsupported database provider: {databaseOptions.Value.Type}"); - } - }); - - services.AddDbContext((provider, options) => - { - var databaseOptions = provider.GetRequiredService>(); - - options.UseSqlite(databaseOptions.Value.ConnectionString); - }); - - services.AddDbContext((provider, options) => - { - var databaseOptions = provider.GetRequiredService>(); - - options.UseSqlServer(databaseOptions.Value.ConnectionString); - }); - - return services; - } - - public static IServiceCollection ConfigureAzure( - this IServiceCollection services, - IConfiguration configuration) - { - services.ConfigureAndValidate(configuration.GetSection(nameof(BaGetOptions.Storage))); - services.ConfigureAndValidate(configuration.GetSection(nameof(BaGetOptions.Search))); - - return services; - } - - public static IServiceCollection ConfigureHttpServices(this IServiceCollection services) - { - services.AddMvc(); - services.AddCors(); - services.AddSingleton, ConfigureCorsOptions>(); - - services.Configure(options => - { - options.ForwardedHeaders = ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedProto; - - // Do not restrict to local network/proxy - options.KnownNetworks.Clear(); - options.KnownProxies.Clear(); - }); - - return services; - } - - public static IServiceCollection ConfigureStorageProviders(this IServiceCollection services) - { - services.AddTransient(); - services.AddTransient(); - services.AddTransient(); - - services.AddBlobStorageService(); - - services.AddTransient(provider => - { - var options = provider.GetRequiredService>(); - - switch (options.Value.Storage.Type) - { - case StorageType.FileSystem: - return provider.GetRequiredService(); - - case StorageType.AzureBlobStorage: - return provider.GetRequiredService(); - - default: - throw new InvalidOperationException( - $"Unsupported storage service: {options.Value.Storage.Type}"); - } - }); - - return services; - } - - public static IServiceCollection AddFileStorageService(this IServiceCollection services) - { - services.AddTransient(); - - return services; - } - - public static IServiceCollection ConfigureSearchProviders(this IServiceCollection services) - { - services.AddTransient(provider => - { - var options = provider.GetRequiredService>(); - - switch (options.Value.Type) - { - case SearchType.Database: - return provider.GetRequiredService(); - - case SearchType.Azure: - return provider.GetRequiredService(); - - default: - throw new InvalidOperationException( - $"Unsupported search service: {options.Value.Type}"); - } - }); - - services.AddTransient(); - services.AddAzureSearch(); - - return services; - } - - /// - /// Add the services that mirror an upstream package source. - /// - /// The defined services. - public static IServiceCollection AddMirrorServices(this IServiceCollection services) - { - services.AddTransient(); - services.AddTransient(); - - services.AddTransient(provider => - { - var options = provider.GetRequiredService>(); - - if (!options.Value.Enabled) - { - return provider.GetRequiredService(); - } - else - { - return provider.GetRequiredService(); - } - }); - - services.AddTransient(); - services.AddTransient(); - services.AddTransient(); - services.AddTransient(); - - services.AddSingleton(provider => - { - var options = provider.GetRequiredService>(); - - return new ServiceIndexService( - options.Value.PackageSource.ToString(), - provider.GetRequiredService()); - }); - - services.AddTransient(); - - services.AddSingleton(provider => - { - var options = provider.GetRequiredService>().Value; - - var assembly = Assembly.GetEntryAssembly(); - var assemblyName = assembly.GetName().Name; - var assemblyVersion = assembly.GetCustomAttribute()?.InformationalVersion ?? "0.0.0"; - - var client = new HttpClient(new HttpClientHandler - { - AutomaticDecompression = (DecompressionMethods.GZip | DecompressionMethods.Deflate), - }); - - client.DefaultRequestHeaders.Add("User-Agent", $"{assemblyName}/{assemblyVersion}"); - client.Timeout = TimeSpan.FromSeconds(options.Mirror.PackageDownloadTimeoutSeconds); - - return client; - }); - - services.AddSingleton(); - services.AddSingleton(); - - return services; - } - - public static IServiceCollection ConfigureAuthenticationProviders(this IServiceCollection services) - { - services.AddSingleton(); - - return services; - } - - public static IServiceCollection ConfigureAndValidateSection( - this IServiceCollection services, - IConfiguration config, - string sectionName) - where TOptions : class - { - services.ConfigureAndValidate(config.GetSection(sectionName), sectionName); - - return services; - } - - public static IServiceCollection ConfigureAndValidate( - this IServiceCollection services, - IConfiguration config, - string name = null) - where TOptions : class - { - services.Configure(config); - services.PostConfigure(options => - { - var context = new ValidationContext(options); - var validationResults = new List(); - if (!Validator.TryValidateObject(options, context, validationResults, validateAllProperties: true)) - { - var message = (name == null) - ? $"Invalid options" - : $"Invalid '{name}' options"; - - throw new InvalidOperationException( - $"{message}: {string.Join('\n', validationResults)}"); - } - }); - - return services; - } - } -} +using System; +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; +using System.IO; +using System.Net; +using System.Net.Http; +using System.Reflection; +using BaGet.Azure.Configuration; +using BaGet.Azure.Extensions; +using BaGet.Azure.Search; +using BaGet.Configurations; +using BaGet.Core.Configuration; +using BaGet.Core.Entities; +using BaGet.Core.Mirror; +using BaGet.Core.Services; +using BaGet.Entities; +using BaGet.Protocol; +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Cors.Infrastructure; +using Microsoft.AspNetCore.HttpOverrides; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Options; + +namespace BaGet.Extensions +{ + public static class ServiceCollectionExtensions + { + public static IServiceCollection ConfigureBaGet( + this IServiceCollection services, + IConfiguration configuration, + bool httpServices = false) + { + services.ConfigureAndValidate(configuration); + services.ConfigureAndValidateSection(configuration, nameof(BaGetOptions.Search)); + services.ConfigureAndValidateSection(configuration, nameof(BaGetOptions.Mirror)); + services.ConfigureAndValidateSection(configuration, nameof(BaGetOptions.Storage)); + services.ConfigureAndValidateSection(configuration, nameof(BaGetOptions.Database)); + services.ConfigureAndValidateSection(configuration, nameof(BaGetOptions.Storage)); + services.ConfigureAndValidateSection(configuration, nameof(BaGetOptions.Storage)); + services.ConfigureAndValidateSection(configuration, nameof(BaGetOptions.Search)); + + services.AddBaGetContext(); + services.ConfigureAzure(configuration); + + if (httpServices) + { + services.ConfigureHttpServices(); + } + + services.AddTransient(); + services.AddTransient(); + services.AddTransient(); + services.AddTransient(); + services.AddMirrorServices(); + + services.ConfigureStorageProviders(); + services.ConfigureSearchProviders(); + services.ConfigureAuthenticationProviders(); + + return services; + } + + public static IServiceCollection AddBaGetContext(this IServiceCollection services) + { + if (services == null) throw new ArgumentNullException(nameof(services)); + + services.AddScoped(provider => + { + var databaseOptions = provider.GetRequiredService>(); + + switch (databaseOptions.Value.Type) + { + case DatabaseType.Sqlite: + return provider.GetRequiredService(); + + case DatabaseType.SqlServer: + return provider.GetRequiredService(); + + default: + throw new InvalidOperationException( + $"Unsupported database provider: {databaseOptions.Value.Type}"); + } + }); + + services.AddDbContext((provider, options) => + { + var databaseOptions = provider.GetRequiredService>(); + + options.UseSqlite(databaseOptions.Value.ConnectionString); + }); + + services.AddDbContext((provider, options) => + { + var databaseOptions = provider.GetRequiredService>(); + + options.UseSqlServer(databaseOptions.Value.ConnectionString); + }); + + return services; + } + + public static IServiceCollection ConfigureAzure( + this IServiceCollection services, + IConfiguration configuration) + { + services.ConfigureAndValidate(configuration.GetSection(nameof(BaGetOptions.Storage))); + services.ConfigureAndValidate(configuration.GetSection(nameof(BaGetOptions.Search))); + + return services; + } + + public static IServiceCollection ConfigureHttpServices(this IServiceCollection services) + { + services.AddMvc(); + services.AddCors(); + services.AddSingleton, ConfigureCorsOptions>(); + + services.Configure(options => + { + options.ForwardedHeaders = ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedProto; + + // Do not restrict to local network/proxy + options.KnownNetworks.Clear(); + options.KnownProxies.Clear(); + }); + + return services; + } + + public static IServiceCollection ConfigureStorageProviders(this IServiceCollection services) + { + services.AddTransient(); + services.AddTransient(); + services.AddTransient(); + + services.AddBlobStorageService(); + + services.AddTransient(provider => + { + var options = provider.GetRequiredService>(); + + switch (options.Value.Storage.Type) + { + case StorageType.FileSystem: + return provider.GetRequiredService(); + + case StorageType.AzureBlobStorage: + return provider.GetRequiredService(); + + default: + throw new InvalidOperationException( + $"Unsupported storage service: {options.Value.Storage.Type}"); + } + }); + + return services; + } + + public static IServiceCollection AddFileStorageService(this IServiceCollection services) + { + services.AddTransient(); + + return services; + } + + public static IServiceCollection ConfigureSearchProviders(this IServiceCollection services) + { + services.AddTransient(provider => + { + var options = provider.GetRequiredService>(); + + switch (options.Value.Type) + { + case SearchType.Database: + return provider.GetRequiredService(); + + case SearchType.Azure: + return provider.GetRequiredService(); + + default: + throw new InvalidOperationException( + $"Unsupported search service: {options.Value.Type}"); + } + }); + + services.AddTransient(); + services.AddAzureSearch(); + + return services; + } + + /// + /// Add the services that mirror an upstream package source. + /// + /// The defined services. + public static IServiceCollection AddMirrorServices(this IServiceCollection services) + { + services.AddTransient(); + services.AddTransient(); + + services.AddTransient(provider => + { + var options = provider.GetRequiredService>(); + + if (!options.Value.Enabled) + { + return provider.GetRequiredService(); + } + else + { + return provider.GetRequiredService(); + } + }); + + services.AddTransient(); + services.AddTransient(); + services.AddTransient(); + services.AddTransient(); + + services.AddSingleton(provider => + { + var options = provider.GetRequiredService>(); + + return new ServiceIndexService( + options.Value.PackageSource.ToString(), + provider.GetRequiredService()); + }); + + services.AddTransient(); + + services.AddSingleton(provider => + { + var options = provider.GetRequiredService>().Value; + + var assembly = Assembly.GetEntryAssembly(); + var assemblyName = assembly.GetName().Name; + var assemblyVersion = assembly.GetCustomAttribute()?.InformationalVersion ?? "0.0.0"; + + var client = new HttpClient(new HttpClientHandler + { + AutomaticDecompression = (DecompressionMethods.GZip | DecompressionMethods.Deflate), + }); + + client.DefaultRequestHeaders.Add("User-Agent", $"{assemblyName}/{assemblyVersion}"); + client.Timeout = TimeSpan.FromSeconds(options.Mirror.PackageDownloadTimeoutSeconds); + + return client; + }); + + services.AddSingleton(); + services.AddSingleton(); + + return services; + } + + public static IServiceCollection ConfigureAuthenticationProviders(this IServiceCollection services) + { + services.AddSingleton(); + + return services; + } + + public static IServiceCollection ConfigureAndValidateSection( + this IServiceCollection services, + IConfiguration config, + string sectionName) + where TOptions : class + { + services.ConfigureAndValidate(config.GetSection(sectionName), sectionName); + + return services; + } + + public static IServiceCollection ConfigureAndValidate( + this IServiceCollection services, + IConfiguration config, + string name = null) + where TOptions : class + { + services.Configure(config); + services.PostConfigure(options => + { + var context = new ValidationContext(options); + var validationResults = new List(); + if (!Validator.TryValidateObject(options, context, validationResults, validateAllProperties: true)) + { + var message = (name == null) + ? $"Invalid options" + : $"Invalid '{name}' options"; + + throw new InvalidOperationException( + $"{message}: {string.Join('\n', validationResults)}"); + } + }); + + return services; + } + } +} diff --git a/src/BaGet/Extensions/UrlExtensions.cs b/src/BaGet/Extensions/UrlExtensions.cs index 667d981f..796f7400 100644 --- a/src/BaGet/Extensions/UrlExtensions.cs +++ b/src/BaGet/Extensions/UrlExtensions.cs @@ -1,58 +1,58 @@ -using System; -using Microsoft.AspNetCore.Mvc; -using NuGet.Versioning; - -namespace BaGet.Extensions -{ - public static class UrlExtensions - { - public static string PackageBase(this IUrlHelper url) => url.AbsoluteUrl("v3/package/"); - public static string RegistrationsBase(this IUrlHelper url) => url.AbsoluteUrl("v3/registration/"); - public static string PackagePublish(this IUrlHelper url) => url.AbsoluteRouteUrl(Routes.UploadPackageRouteName); - public static string SymbolPublish(this IUrlHelper url) => url.AbsoluteRouteUrl(Routes.UploadSymbolRouteName); - public static string PackageSearch(this IUrlHelper url) => url.AbsoluteRouteUrl(Routes.SearchRouteName); - public static string PackageAutocomplete(this IUrlHelper url) => url.AbsoluteRouteUrl(Routes.AutocompleteRouteName); - - public static string PackageRegistration(this IUrlHelper url, string id) - => url.AbsoluteRouteUrl( - Routes.RegistrationIndexRouteName, - new { Id = id.ToLowerInvariant() }); - - public static string PackageRegistration(this IUrlHelper url, string id, NuGetVersion version) - => url.AbsoluteRouteUrl( - Routes.RegistrationLeafRouteName, - new { - Id = id.ToLowerInvariant(), - Version = version.ToNormalizedString().ToLowerInvariant() - }); - - public static string PackageDownload(this IUrlHelper url, string id, NuGetVersion version) - { - id = id.ToLowerInvariant(); - var versionString = version.ToNormalizedString().ToLowerInvariant(); - - return url.AbsoluteRouteUrl( - Routes.PackageDownloadRouteName, - new - { - id, - Version = versionString, - IdVersion = $"{id}.{versionString}" - }); - } - - public static string AbsoluteUrl(this IUrlHelper url, string relativePath) - { - var request = url.ActionContext.HttpContext.Request; - - return string.Concat( - request.Scheme, "://", - request.Host.ToUriComponent(), - request.PathBase.ToUriComponent(), - "/", relativePath); - } - - public static string AbsoluteRouteUrl(this IUrlHelper url, string routeName, object routeValues = null) - => url.RouteUrl(routeName, routeValues, url.ActionContext.HttpContext.Request.Scheme); - } -} +using System; +using Microsoft.AspNetCore.Mvc; +using NuGet.Versioning; + +namespace BaGet.Extensions +{ + public static class UrlExtensions + { + public static string PackageBase(this IUrlHelper url) => url.AbsoluteUrl("v3/package/"); + public static string RegistrationsBase(this IUrlHelper url) => url.AbsoluteUrl("v3/registration/"); + public static string PackagePublish(this IUrlHelper url) => url.AbsoluteRouteUrl(Routes.UploadPackageRouteName); + public static string SymbolPublish(this IUrlHelper url) => url.AbsoluteRouteUrl(Routes.UploadSymbolRouteName); + public static string PackageSearch(this IUrlHelper url) => url.AbsoluteRouteUrl(Routes.SearchRouteName); + public static string PackageAutocomplete(this IUrlHelper url) => url.AbsoluteRouteUrl(Routes.AutocompleteRouteName); + + public static string PackageRegistration(this IUrlHelper url, string id) + => url.AbsoluteRouteUrl( + Routes.RegistrationIndexRouteName, + new { Id = id.ToLowerInvariant() }); + + public static string PackageRegistration(this IUrlHelper url, string id, NuGetVersion version) + => url.AbsoluteRouteUrl( + Routes.RegistrationLeafRouteName, + new { + Id = id.ToLowerInvariant(), + Version = version.ToNormalizedString().ToLowerInvariant() + }); + + public static string PackageDownload(this IUrlHelper url, string id, NuGetVersion version) + { + id = id.ToLowerInvariant(); + var versionString = version.ToNormalizedString().ToLowerInvariant(); + + return url.AbsoluteRouteUrl( + Routes.PackageDownloadRouteName, + new + { + id, + Version = versionString, + IdVersion = $"{id}.{versionString}" + }); + } + + public static string AbsoluteUrl(this IUrlHelper url, string relativePath) + { + var request = url.ActionContext.HttpContext.Request; + + return string.Concat( + request.Scheme, "://", + request.Host.ToUriComponent(), + request.PathBase.ToUriComponent(), + "/", relativePath); + } + + public static string AbsoluteRouteUrl(this IUrlHelper url, string routeName, object routeValues = null) + => url.RouteUrl(routeName, routeValues, url.ActionContext.HttpContext.Request.Scheme); + } +} diff --git a/src/BaGet/Migrations/SqlServer/20180804082816_Initial.Designer.cs b/src/BaGet/Migrations/SqlServer/20180804082816_Initial.Designer.cs index f5971a45..4acf6cba 100644 --- a/src/BaGet/Migrations/SqlServer/20180804082816_Initial.Designer.cs +++ b/src/BaGet/Migrations/SqlServer/20180804082816_Initial.Designer.cs @@ -1,132 +1,132 @@ -// -using System; -using BaGet.Entities; -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Metadata; -using Microsoft.EntityFrameworkCore.Migrations; -using Microsoft.EntityFrameworkCore.Storage.ValueConversion; - -namespace BaGet.Migrations.SqlServer -{ - [DbContext(typeof(SqlServerContext))] - [Migration("20180804082816_Initial")] - partial class Initial - { - protected override void BuildTargetModel(ModelBuilder modelBuilder) - { -#pragma warning disable 612, 618 - modelBuilder - .HasAnnotation("ProductVersion", "2.1.1-rtm-30846") - .HasAnnotation("Relational:MaxIdentifierLength", 128) - .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); - - modelBuilder.Entity("BaGet.Core.Entities.Package", b => - { - b.Property("Key") - .ValueGeneratedOnAdd() - .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); - - b.Property("Authors") - .HasMaxLength(4000); - - b.Property("Description") - .HasMaxLength(4000); - - b.Property("Downloads"); - - b.Property("HasReadme"); - - b.Property("IconUrl") - .HasMaxLength(4000); - - b.Property("Id") - .IsRequired() - .HasMaxLength(128); - - b.Property("Language") - .HasMaxLength(20); - - b.Property("LicenseUrl") - .HasMaxLength(4000); - - b.Property("Listed"); - - b.Property("MinClientVersion") - .HasMaxLength(44); - - b.Property("ProjectUrl") - .HasMaxLength(4000); - - b.Property("Published"); - - b.Property("RepositoryType") - .HasMaxLength(100); - - b.Property("RepositoryUrl") - .HasMaxLength(4000); - - b.Property("RequireLicenseAcceptance"); - - b.Property("RowVersion") - .IsConcurrencyToken() - .ValueGeneratedOnAddOrUpdate(); - - b.Property("Summary") - .HasMaxLength(4000); - - b.Property("Tags") - .HasMaxLength(4000); - - b.Property("Title") - .HasMaxLength(256); - - b.Property("VersionString") - .IsRequired() - .HasColumnName("Version") - .HasMaxLength(64); - - b.HasKey("Key"); - - b.HasIndex("Id"); - - b.HasIndex("Id", "VersionString") - .IsUnique(); - - b.ToTable("Packages"); - }); - - modelBuilder.Entity("BaGet.Core.Entities.PackageDependency", b => - { - b.Property("Key") - .ValueGeneratedOnAdd() - .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); - - b.Property("Id") - .HasMaxLength(128); - - b.Property("PackageKey"); - - b.Property("TargetFramework") - .HasMaxLength(256); - - b.Property("VersionRange") - .HasMaxLength(256); - - b.HasKey("Key"); - - b.HasIndex("PackageKey"); - - b.ToTable("PackageDependencies"); - }); - - modelBuilder.Entity("BaGet.Core.Entities.PackageDependency", b => - { - b.HasOne("BaGet.Core.Entities.Package", "Package") - .WithMany("Dependencies") - .HasForeignKey("PackageKey"); - }); -#pragma warning restore 612, 618 - } - } -} +// +using System; +using BaGet.Entities; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; + +namespace BaGet.Migrations.SqlServer +{ + [DbContext(typeof(SqlServerContext))] + [Migration("20180804082816_Initial")] + partial class Initial + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "2.1.1-rtm-30846") + .HasAnnotation("Relational:MaxIdentifierLength", 128) + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + modelBuilder.Entity("BaGet.Core.Entities.Package", b => + { + b.Property("Key") + .ValueGeneratedOnAdd() + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b.Property("Authors") + .HasMaxLength(4000); + + b.Property("Description") + .HasMaxLength(4000); + + b.Property("Downloads"); + + b.Property("HasReadme"); + + b.Property("IconUrl") + .HasMaxLength(4000); + + b.Property("Id") + .IsRequired() + .HasMaxLength(128); + + b.Property("Language") + .HasMaxLength(20); + + b.Property("LicenseUrl") + .HasMaxLength(4000); + + b.Property("Listed"); + + b.Property("MinClientVersion") + .HasMaxLength(44); + + b.Property("ProjectUrl") + .HasMaxLength(4000); + + b.Property("Published"); + + b.Property("RepositoryType") + .HasMaxLength(100); + + b.Property("RepositoryUrl") + .HasMaxLength(4000); + + b.Property("RequireLicenseAcceptance"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .ValueGeneratedOnAddOrUpdate(); + + b.Property("Summary") + .HasMaxLength(4000); + + b.Property("Tags") + .HasMaxLength(4000); + + b.Property("Title") + .HasMaxLength(256); + + b.Property("VersionString") + .IsRequired() + .HasColumnName("Version") + .HasMaxLength(64); + + b.HasKey("Key"); + + b.HasIndex("Id"); + + b.HasIndex("Id", "VersionString") + .IsUnique(); + + b.ToTable("Packages"); + }); + + modelBuilder.Entity("BaGet.Core.Entities.PackageDependency", b => + { + b.Property("Key") + .ValueGeneratedOnAdd() + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b.Property("Id") + .HasMaxLength(128); + + b.Property("PackageKey"); + + b.Property("TargetFramework") + .HasMaxLength(256); + + b.Property("VersionRange") + .HasMaxLength(256); + + b.HasKey("Key"); + + b.HasIndex("PackageKey"); + + b.ToTable("PackageDependencies"); + }); + + modelBuilder.Entity("BaGet.Core.Entities.PackageDependency", b => + { + b.HasOne("BaGet.Core.Entities.Package", "Package") + .WithMany("Dependencies") + .HasForeignKey("PackageKey"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/BaGet/Migrations/SqlServer/20180804082816_Initial.cs b/src/BaGet/Migrations/SqlServer/20180804082816_Initial.cs index 7b47bd56..0f4143d5 100644 --- a/src/BaGet/Migrations/SqlServer/20180804082816_Initial.cs +++ b/src/BaGet/Migrations/SqlServer/20180804082816_Initial.cs @@ -1,91 +1,91 @@ -using System; -using Microsoft.EntityFrameworkCore.Metadata; -using Microsoft.EntityFrameworkCore.Migrations; - -namespace BaGet.Migrations.SqlServer -{ - public partial class Initial : Migration - { - protected override void Up(MigrationBuilder migrationBuilder) - { - migrationBuilder.CreateTable( - name: "Packages", - columns: table => new - { - Key = table.Column(nullable: false) - .Annotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn), - Id = table.Column(maxLength: 128, nullable: false), - Authors = table.Column(maxLength: 4000, nullable: true), - Description = table.Column(maxLength: 4000, nullable: true), - Downloads = table.Column(nullable: false), - HasReadme = table.Column(nullable: false), - Language = table.Column(maxLength: 20, nullable: true), - Listed = table.Column(nullable: false), - MinClientVersion = table.Column(maxLength: 44, nullable: true), - Published = table.Column(nullable: false), - RequireLicenseAcceptance = table.Column(nullable: false), - Summary = table.Column(maxLength: 4000, nullable: true), - Title = table.Column(maxLength: 256, nullable: true), - IconUrl = table.Column(maxLength: 4000, nullable: true), - LicenseUrl = table.Column(maxLength: 4000, nullable: true), - ProjectUrl = table.Column(maxLength: 4000, nullable: true), - RepositoryUrl = table.Column(maxLength: 4000, nullable: true), - RepositoryType = table.Column(maxLength: 100, nullable: true), - Tags = table.Column(maxLength: 4000, nullable: true), - RowVersion = table.Column(rowVersion: true, nullable: true), - Version = table.Column(maxLength: 64, nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_Packages", x => x.Key); - }); - - migrationBuilder.CreateTable( - name: "PackageDependencies", - columns: table => new - { - Key = table.Column(nullable: false) - .Annotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn), - Id = table.Column(maxLength: 128, nullable: true), - VersionRange = table.Column(maxLength: 256, nullable: true), - TargetFramework = table.Column(maxLength: 256, nullable: true), - PackageKey = table.Column(nullable: true) - }, - constraints: table => - { - table.PrimaryKey("PK_PackageDependencies", x => x.Key); - table.ForeignKey( - name: "FK_PackageDependencies_Packages_PackageKey", - column: x => x.PackageKey, - principalTable: "Packages", - principalColumn: "Key", - onDelete: ReferentialAction.Restrict); - }); - - migrationBuilder.CreateIndex( - name: "IX_PackageDependencies_PackageKey", - table: "PackageDependencies", - column: "PackageKey"); - - migrationBuilder.CreateIndex( - name: "IX_Packages_Id", - table: "Packages", - column: "Id"); - - migrationBuilder.CreateIndex( - name: "IX_Packages_Id_Version", - table: "Packages", - columns: new[] { "Id", "Version" }, - unique: true); - } - - protected override void Down(MigrationBuilder migrationBuilder) - { - migrationBuilder.DropTable( - name: "PackageDependencies"); - - migrationBuilder.DropTable( - name: "Packages"); - } - } -} +using System; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; + +namespace BaGet.Migrations.SqlServer +{ + public partial class Initial : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "Packages", + columns: table => new + { + Key = table.Column(nullable: false) + .Annotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn), + Id = table.Column(maxLength: 128, nullable: false), + Authors = table.Column(maxLength: 4000, nullable: true), + Description = table.Column(maxLength: 4000, nullable: true), + Downloads = table.Column(nullable: false), + HasReadme = table.Column(nullable: false), + Language = table.Column(maxLength: 20, nullable: true), + Listed = table.Column(nullable: false), + MinClientVersion = table.Column(maxLength: 44, nullable: true), + Published = table.Column(nullable: false), + RequireLicenseAcceptance = table.Column(nullable: false), + Summary = table.Column(maxLength: 4000, nullable: true), + Title = table.Column(maxLength: 256, nullable: true), + IconUrl = table.Column(maxLength: 4000, nullable: true), + LicenseUrl = table.Column(maxLength: 4000, nullable: true), + ProjectUrl = table.Column(maxLength: 4000, nullable: true), + RepositoryUrl = table.Column(maxLength: 4000, nullable: true), + RepositoryType = table.Column(maxLength: 100, nullable: true), + Tags = table.Column(maxLength: 4000, nullable: true), + RowVersion = table.Column(rowVersion: true, nullable: true), + Version = table.Column(maxLength: 64, nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_Packages", x => x.Key); + }); + + migrationBuilder.CreateTable( + name: "PackageDependencies", + columns: table => new + { + Key = table.Column(nullable: false) + .Annotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn), + Id = table.Column(maxLength: 128, nullable: true), + VersionRange = table.Column(maxLength: 256, nullable: true), + TargetFramework = table.Column(maxLength: 256, nullable: true), + PackageKey = table.Column(nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_PackageDependencies", x => x.Key); + table.ForeignKey( + name: "FK_PackageDependencies_Packages_PackageKey", + column: x => x.PackageKey, + principalTable: "Packages", + principalColumn: "Key", + onDelete: ReferentialAction.Restrict); + }); + + migrationBuilder.CreateIndex( + name: "IX_PackageDependencies_PackageKey", + table: "PackageDependencies", + column: "PackageKey"); + + migrationBuilder.CreateIndex( + name: "IX_Packages_Id", + table: "Packages", + column: "Id"); + + migrationBuilder.CreateIndex( + name: "IX_Packages_Id_Version", + table: "Packages", + columns: new[] { "Id", "Version" }, + unique: true); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "PackageDependencies"); + + migrationBuilder.DropTable( + name: "Packages"); + } + } +} diff --git a/src/BaGet/Migrations/SqlServer/SqlServerContextModelSnapshot.cs b/src/BaGet/Migrations/SqlServer/SqlServerContextModelSnapshot.cs index 42f473f1..9ba67b60 100644 --- a/src/BaGet/Migrations/SqlServer/SqlServerContextModelSnapshot.cs +++ b/src/BaGet/Migrations/SqlServer/SqlServerContextModelSnapshot.cs @@ -1,130 +1,130 @@ -// -using System; -using BaGet.Entities; -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Metadata; -using Microsoft.EntityFrameworkCore.Storage.ValueConversion; - -namespace BaGet.Migrations.SqlServer -{ - [DbContext(typeof(SqlServerContext))] - partial class SqlServerContextModelSnapshot : ModelSnapshot - { - protected override void BuildModel(ModelBuilder modelBuilder) - { -#pragma warning disable 612, 618 - modelBuilder - .HasAnnotation("ProductVersion", "2.1.1-rtm-30846") - .HasAnnotation("Relational:MaxIdentifierLength", 128) - .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); - - modelBuilder.Entity("BaGet.Core.Entities.Package", b => - { - b.Property("Key") - .ValueGeneratedOnAdd() - .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); - - b.Property("Authors") - .HasMaxLength(4000); - - b.Property("Description") - .HasMaxLength(4000); - - b.Property("Downloads"); - - b.Property("HasReadme"); - - b.Property("IconUrl") - .HasMaxLength(4000); - - b.Property("Id") - .IsRequired() - .HasMaxLength(128); - - b.Property("Language") - .HasMaxLength(20); - - b.Property("LicenseUrl") - .HasMaxLength(4000); - - b.Property("Listed"); - - b.Property("MinClientVersion") - .HasMaxLength(44); - - b.Property("ProjectUrl") - .HasMaxLength(4000); - - b.Property("Published"); - - b.Property("RepositoryType") - .HasMaxLength(100); - - b.Property("RepositoryUrl") - .HasMaxLength(4000); - - b.Property("RequireLicenseAcceptance"); - - b.Property("RowVersion") - .IsConcurrencyToken() - .ValueGeneratedOnAddOrUpdate(); - - b.Property("Summary") - .HasMaxLength(4000); - - b.Property("Tags") - .HasMaxLength(4000); - - b.Property("Title") - .HasMaxLength(256); - - b.Property("VersionString") - .IsRequired() - .HasColumnName("Version") - .HasMaxLength(64); - - b.HasKey("Key"); - - b.HasIndex("Id"); - - b.HasIndex("Id", "VersionString") - .IsUnique(); - - b.ToTable("Packages"); - }); - - modelBuilder.Entity("BaGet.Core.Entities.PackageDependency", b => - { - b.Property("Key") - .ValueGeneratedOnAdd() - .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); - - b.Property("Id") - .HasMaxLength(128); - - b.Property("PackageKey"); - - b.Property("TargetFramework") - .HasMaxLength(256); - - b.Property("VersionRange") - .HasMaxLength(256); - - b.HasKey("Key"); - - b.HasIndex("PackageKey"); - - b.ToTable("PackageDependencies"); - }); - - modelBuilder.Entity("BaGet.Core.Entities.PackageDependency", b => - { - b.HasOne("BaGet.Core.Entities.Package", "Package") - .WithMany("Dependencies") - .HasForeignKey("PackageKey"); - }); -#pragma warning restore 612, 618 - } - } -} +// +using System; +using BaGet.Entities; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; + +namespace BaGet.Migrations.SqlServer +{ + [DbContext(typeof(SqlServerContext))] + partial class SqlServerContextModelSnapshot : ModelSnapshot + { + protected override void BuildModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "2.1.1-rtm-30846") + .HasAnnotation("Relational:MaxIdentifierLength", 128) + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + modelBuilder.Entity("BaGet.Core.Entities.Package", b => + { + b.Property("Key") + .ValueGeneratedOnAdd() + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b.Property("Authors") + .HasMaxLength(4000); + + b.Property("Description") + .HasMaxLength(4000); + + b.Property("Downloads"); + + b.Property("HasReadme"); + + b.Property("IconUrl") + .HasMaxLength(4000); + + b.Property("Id") + .IsRequired() + .HasMaxLength(128); + + b.Property("Language") + .HasMaxLength(20); + + b.Property("LicenseUrl") + .HasMaxLength(4000); + + b.Property("Listed"); + + b.Property("MinClientVersion") + .HasMaxLength(44); + + b.Property("ProjectUrl") + .HasMaxLength(4000); + + b.Property("Published"); + + b.Property("RepositoryType") + .HasMaxLength(100); + + b.Property("RepositoryUrl") + .HasMaxLength(4000); + + b.Property("RequireLicenseAcceptance"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .ValueGeneratedOnAddOrUpdate(); + + b.Property("Summary") + .HasMaxLength(4000); + + b.Property("Tags") + .HasMaxLength(4000); + + b.Property("Title") + .HasMaxLength(256); + + b.Property("VersionString") + .IsRequired() + .HasColumnName("Version") + .HasMaxLength(64); + + b.HasKey("Key"); + + b.HasIndex("Id"); + + b.HasIndex("Id", "VersionString") + .IsUnique(); + + b.ToTable("Packages"); + }); + + modelBuilder.Entity("BaGet.Core.Entities.PackageDependency", b => + { + b.Property("Key") + .ValueGeneratedOnAdd() + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b.Property("Id") + .HasMaxLength(128); + + b.Property("PackageKey"); + + b.Property("TargetFramework") + .HasMaxLength(256); + + b.Property("VersionRange") + .HasMaxLength(256); + + b.HasKey("Key"); + + b.HasIndex("PackageKey"); + + b.ToTable("PackageDependencies"); + }); + + modelBuilder.Entity("BaGet.Core.Entities.PackageDependency", b => + { + b.HasOne("BaGet.Core.Entities.Package", "Package") + .WithMany("Dependencies") + .HasForeignKey("PackageKey"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/BaGet/Migrations/Sqlite/20180804082808_Initial.Designer.cs b/src/BaGet/Migrations/Sqlite/20180804082808_Initial.Designer.cs index 8df1615e..6f192292 100644 --- a/src/BaGet/Migrations/Sqlite/20180804082808_Initial.Designer.cs +++ b/src/BaGet/Migrations/Sqlite/20180804082808_Initial.Designer.cs @@ -1,129 +1,129 @@ -// -using System; -using BaGet.Entities; -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Migrations; -using Microsoft.EntityFrameworkCore.Storage.ValueConversion; - -namespace BaGet.Migrations.Sqlite -{ - [DbContext(typeof(SqliteContext))] - [Migration("20180804082808_Initial")] - partial class Initial - { - protected override void BuildTargetModel(ModelBuilder modelBuilder) - { -#pragma warning disable 612, 618 - modelBuilder - .HasAnnotation("ProductVersion", "2.1.1-rtm-30846"); - - modelBuilder.Entity("BaGet.Core.Entities.Package", b => - { - b.Property("Key") - .ValueGeneratedOnAdd(); - - b.Property("Authors") - .HasMaxLength(4000); - - b.Property("Description") - .HasMaxLength(4000); - - b.Property("Downloads"); - - b.Property("HasReadme"); - - b.Property("IconUrl") - .HasMaxLength(4000); - - b.Property("Id") - .IsRequired() - .HasColumnType("TEXT COLLATE NOCASE") - .HasMaxLength(128); - - b.Property("Language") - .HasMaxLength(20); - - b.Property("LicenseUrl") - .HasMaxLength(4000); - - b.Property("Listed"); - - b.Property("MinClientVersion") - .HasMaxLength(44); - - b.Property("ProjectUrl") - .HasMaxLength(4000); - - b.Property("Published"); - - b.Property("RepositoryType") - .HasMaxLength(100); - - b.Property("RepositoryUrl") - .HasMaxLength(4000); - - b.Property("RequireLicenseAcceptance"); - - b.Property("RowVersion") - .IsConcurrencyToken() - .ValueGeneratedOnAddOrUpdate(); - - b.Property("Summary") - .HasMaxLength(4000); - - b.Property("Tags") - .HasMaxLength(4000); - - b.Property("Title") - .HasMaxLength(256); - - b.Property("VersionString") - .IsRequired() - .HasColumnName("Version") - .HasMaxLength(64); - - b.HasKey("Key"); - - b.HasIndex("Id"); - - b.HasIndex("Id", "VersionString") - .IsUnique(); - - b.ToTable("Packages"); - }); - - modelBuilder.Entity("BaGet.Core.Entities.PackageDependency", b => - { - b.Property("Key") - .ValueGeneratedOnAdd(); - - b.Property("Id") - .HasColumnType("TEXT COLLATE NOCASE") - .HasMaxLength(128); - - b.Property("PackageKey"); - - b.Property("TargetFramework") - .HasMaxLength(256); - - b.Property("VersionRange") - .HasMaxLength(256); - - b.HasKey("Key"); - - b.HasIndex("PackageKey"); - - b.ToTable("PackageDependencies"); - }); - - modelBuilder.Entity("BaGet.Core.Entities.PackageDependency", b => - { - b.HasOne("BaGet.Core.Entities.Package", "Package") - .WithMany("Dependencies") - .HasForeignKey("PackageKey"); - }); -#pragma warning restore 612, 618 - } - } -} +// +using System; +using BaGet.Entities; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; + +namespace BaGet.Migrations.Sqlite +{ + [DbContext(typeof(SqliteContext))] + [Migration("20180804082808_Initial")] + partial class Initial + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "2.1.1-rtm-30846"); + + modelBuilder.Entity("BaGet.Core.Entities.Package", b => + { + b.Property("Key") + .ValueGeneratedOnAdd(); + + b.Property("Authors") + .HasMaxLength(4000); + + b.Property("Description") + .HasMaxLength(4000); + + b.Property("Downloads"); + + b.Property("HasReadme"); + + b.Property("IconUrl") + .HasMaxLength(4000); + + b.Property("Id") + .IsRequired() + .HasColumnType("TEXT COLLATE NOCASE") + .HasMaxLength(128); + + b.Property("Language") + .HasMaxLength(20); + + b.Property("LicenseUrl") + .HasMaxLength(4000); + + b.Property("Listed"); + + b.Property("MinClientVersion") + .HasMaxLength(44); + + b.Property("ProjectUrl") + .HasMaxLength(4000); + + b.Property("Published"); + + b.Property("RepositoryType") + .HasMaxLength(100); + + b.Property("RepositoryUrl") + .HasMaxLength(4000); + + b.Property("RequireLicenseAcceptance"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .ValueGeneratedOnAddOrUpdate(); + + b.Property("Summary") + .HasMaxLength(4000); + + b.Property("Tags") + .HasMaxLength(4000); + + b.Property("Title") + .HasMaxLength(256); + + b.Property("VersionString") + .IsRequired() + .HasColumnName("Version") + .HasMaxLength(64); + + b.HasKey("Key"); + + b.HasIndex("Id"); + + b.HasIndex("Id", "VersionString") + .IsUnique(); + + b.ToTable("Packages"); + }); + + modelBuilder.Entity("BaGet.Core.Entities.PackageDependency", b => + { + b.Property("Key") + .ValueGeneratedOnAdd(); + + b.Property("Id") + .HasColumnType("TEXT COLLATE NOCASE") + .HasMaxLength(128); + + b.Property("PackageKey"); + + b.Property("TargetFramework") + .HasMaxLength(256); + + b.Property("VersionRange") + .HasMaxLength(256); + + b.HasKey("Key"); + + b.HasIndex("PackageKey"); + + b.ToTable("PackageDependencies"); + }); + + modelBuilder.Entity("BaGet.Core.Entities.PackageDependency", b => + { + b.HasOne("BaGet.Core.Entities.Package", "Package") + .WithMany("Dependencies") + .HasForeignKey("PackageKey"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/BaGet/Migrations/Sqlite/20180804082808_Initial.cs b/src/BaGet/Migrations/Sqlite/20180804082808_Initial.cs index 740fdcbe..3050c557 100644 --- a/src/BaGet/Migrations/Sqlite/20180804082808_Initial.cs +++ b/src/BaGet/Migrations/Sqlite/20180804082808_Initial.cs @@ -1,90 +1,90 @@ -using System; -using Microsoft.EntityFrameworkCore.Migrations; - -namespace BaGet.Migrations.Sqlite -{ - public partial class Initial : Migration - { - protected override void Up(MigrationBuilder migrationBuilder) - { - migrationBuilder.CreateTable( - name: "Packages", - columns: table => new - { - Key = table.Column(nullable: false) - .Annotation("Sqlite:Autoincrement", true), - Id = table.Column(type: "TEXT COLLATE NOCASE", maxLength: 128, nullable: false), - Authors = table.Column(maxLength: 4000, nullable: true), - Description = table.Column(maxLength: 4000, nullable: true), - Downloads = table.Column(nullable: false), - HasReadme = table.Column(nullable: false), - Language = table.Column(maxLength: 20, nullable: true), - Listed = table.Column(nullable: false), - MinClientVersion = table.Column(maxLength: 44, nullable: true), - Published = table.Column(nullable: false), - RequireLicenseAcceptance = table.Column(nullable: false), - Summary = table.Column(maxLength: 4000, nullable: true), - Title = table.Column(maxLength: 256, nullable: true), - IconUrl = table.Column(maxLength: 4000, nullable: true), - LicenseUrl = table.Column(maxLength: 4000, nullable: true), - ProjectUrl = table.Column(maxLength: 4000, nullable: true), - RepositoryUrl = table.Column(maxLength: 4000, nullable: true), - RepositoryType = table.Column(maxLength: 100, nullable: true), - Tags = table.Column(maxLength: 4000, nullable: true), - RowVersion = table.Column(rowVersion: true, nullable: true), - Version = table.Column(maxLength: 64, nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_Packages", x => x.Key); - }); - - migrationBuilder.CreateTable( - name: "PackageDependencies", - columns: table => new - { - Key = table.Column(nullable: false) - .Annotation("Sqlite:Autoincrement", true), - Id = table.Column(type: "TEXT COLLATE NOCASE", maxLength: 128, nullable: true), - VersionRange = table.Column(maxLength: 256, nullable: true), - TargetFramework = table.Column(maxLength: 256, nullable: true), - PackageKey = table.Column(nullable: true) - }, - constraints: table => - { - table.PrimaryKey("PK_PackageDependencies", x => x.Key); - table.ForeignKey( - name: "FK_PackageDependencies_Packages_PackageKey", - column: x => x.PackageKey, - principalTable: "Packages", - principalColumn: "Key", - onDelete: ReferentialAction.Restrict); - }); - - migrationBuilder.CreateIndex( - name: "IX_PackageDependencies_PackageKey", - table: "PackageDependencies", - column: "PackageKey"); - - migrationBuilder.CreateIndex( - name: "IX_Packages_Id", - table: "Packages", - column: "Id"); - - migrationBuilder.CreateIndex( - name: "IX_Packages_Id_Version", - table: "Packages", - columns: new[] { "Id", "Version" }, - unique: true); - } - - protected override void Down(MigrationBuilder migrationBuilder) - { - migrationBuilder.DropTable( - name: "PackageDependencies"); - - migrationBuilder.DropTable( - name: "Packages"); - } - } -} +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +namespace BaGet.Migrations.Sqlite +{ + public partial class Initial : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "Packages", + columns: table => new + { + Key = table.Column(nullable: false) + .Annotation("Sqlite:Autoincrement", true), + Id = table.Column(type: "TEXT COLLATE NOCASE", maxLength: 128, nullable: false), + Authors = table.Column(maxLength: 4000, nullable: true), + Description = table.Column(maxLength: 4000, nullable: true), + Downloads = table.Column(nullable: false), + HasReadme = table.Column(nullable: false), + Language = table.Column(maxLength: 20, nullable: true), + Listed = table.Column(nullable: false), + MinClientVersion = table.Column(maxLength: 44, nullable: true), + Published = table.Column(nullable: false), + RequireLicenseAcceptance = table.Column(nullable: false), + Summary = table.Column(maxLength: 4000, nullable: true), + Title = table.Column(maxLength: 256, nullable: true), + IconUrl = table.Column(maxLength: 4000, nullable: true), + LicenseUrl = table.Column(maxLength: 4000, nullable: true), + ProjectUrl = table.Column(maxLength: 4000, nullable: true), + RepositoryUrl = table.Column(maxLength: 4000, nullable: true), + RepositoryType = table.Column(maxLength: 100, nullable: true), + Tags = table.Column(maxLength: 4000, nullable: true), + RowVersion = table.Column(rowVersion: true, nullable: true), + Version = table.Column(maxLength: 64, nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_Packages", x => x.Key); + }); + + migrationBuilder.CreateTable( + name: "PackageDependencies", + columns: table => new + { + Key = table.Column(nullable: false) + .Annotation("Sqlite:Autoincrement", true), + Id = table.Column(type: "TEXT COLLATE NOCASE", maxLength: 128, nullable: true), + VersionRange = table.Column(maxLength: 256, nullable: true), + TargetFramework = table.Column(maxLength: 256, nullable: true), + PackageKey = table.Column(nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_PackageDependencies", x => x.Key); + table.ForeignKey( + name: "FK_PackageDependencies_Packages_PackageKey", + column: x => x.PackageKey, + principalTable: "Packages", + principalColumn: "Key", + onDelete: ReferentialAction.Restrict); + }); + + migrationBuilder.CreateIndex( + name: "IX_PackageDependencies_PackageKey", + table: "PackageDependencies", + column: "PackageKey"); + + migrationBuilder.CreateIndex( + name: "IX_Packages_Id", + table: "Packages", + column: "Id"); + + migrationBuilder.CreateIndex( + name: "IX_Packages_Id_Version", + table: "Packages", + columns: new[] { "Id", "Version" }, + unique: true); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "PackageDependencies"); + + migrationBuilder.DropTable( + name: "Packages"); + } + } +} diff --git a/src/BaGet/Migrations/Sqlite/SqliteContextModelSnapshot.cs b/src/BaGet/Migrations/Sqlite/SqliteContextModelSnapshot.cs index f42cf246..848ab9c2 100644 --- a/src/BaGet/Migrations/Sqlite/SqliteContextModelSnapshot.cs +++ b/src/BaGet/Migrations/Sqlite/SqliteContextModelSnapshot.cs @@ -1,127 +1,127 @@ -// -using System; -using BaGet.Entities; -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Storage.ValueConversion; - -namespace BaGet.Migrations.Sqlite -{ - [DbContext(typeof(SqliteContext))] - partial class SqliteContextModelSnapshot : ModelSnapshot - { - protected override void BuildModel(ModelBuilder modelBuilder) - { -#pragma warning disable 612, 618 - modelBuilder - .HasAnnotation("ProductVersion", "2.1.1-rtm-30846"); - - modelBuilder.Entity("BaGet.Core.Entities.Package", b => - { - b.Property("Key") - .ValueGeneratedOnAdd(); - - b.Property("Authors") - .HasMaxLength(4000); - - b.Property("Description") - .HasMaxLength(4000); - - b.Property("Downloads"); - - b.Property("HasReadme"); - - b.Property("IconUrl") - .HasMaxLength(4000); - - b.Property("Id") - .IsRequired() - .HasColumnType("TEXT COLLATE NOCASE") - .HasMaxLength(128); - - b.Property("Language") - .HasMaxLength(20); - - b.Property("LicenseUrl") - .HasMaxLength(4000); - - b.Property("Listed"); - - b.Property("MinClientVersion") - .HasMaxLength(44); - - b.Property("ProjectUrl") - .HasMaxLength(4000); - - b.Property("Published"); - - b.Property("RepositoryType") - .HasMaxLength(100); - - b.Property("RepositoryUrl") - .HasMaxLength(4000); - - b.Property("RequireLicenseAcceptance"); - - b.Property("RowVersion") - .IsConcurrencyToken() - .ValueGeneratedOnAddOrUpdate(); - - b.Property("Summary") - .HasMaxLength(4000); - - b.Property("Tags") - .HasMaxLength(4000); - - b.Property("Title") - .HasMaxLength(256); - - b.Property("VersionString") - .IsRequired() - .HasColumnName("Version") - .HasMaxLength(64); - - b.HasKey("Key"); - - b.HasIndex("Id"); - - b.HasIndex("Id", "VersionString") - .IsUnique(); - - b.ToTable("Packages"); - }); - - modelBuilder.Entity("BaGet.Core.Entities.PackageDependency", b => - { - b.Property("Key") - .ValueGeneratedOnAdd(); - - b.Property("Id") - .HasColumnType("TEXT COLLATE NOCASE") - .HasMaxLength(128); - - b.Property("PackageKey"); - - b.Property("TargetFramework") - .HasMaxLength(256); - - b.Property("VersionRange") - .HasMaxLength(256); - - b.HasKey("Key"); - - b.HasIndex("PackageKey"); - - b.ToTable("PackageDependencies"); - }); - - modelBuilder.Entity("BaGet.Core.Entities.PackageDependency", b => - { - b.HasOne("BaGet.Core.Entities.Package", "Package") - .WithMany("Dependencies") - .HasForeignKey("PackageKey"); - }); -#pragma warning restore 612, 618 - } - } -} +// +using System; +using BaGet.Entities; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; + +namespace BaGet.Migrations.Sqlite +{ + [DbContext(typeof(SqliteContext))] + partial class SqliteContextModelSnapshot : ModelSnapshot + { + protected override void BuildModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "2.1.1-rtm-30846"); + + modelBuilder.Entity("BaGet.Core.Entities.Package", b => + { + b.Property("Key") + .ValueGeneratedOnAdd(); + + b.Property("Authors") + .HasMaxLength(4000); + + b.Property("Description") + .HasMaxLength(4000); + + b.Property("Downloads"); + + b.Property("HasReadme"); + + b.Property("IconUrl") + .HasMaxLength(4000); + + b.Property("Id") + .IsRequired() + .HasColumnType("TEXT COLLATE NOCASE") + .HasMaxLength(128); + + b.Property("Language") + .HasMaxLength(20); + + b.Property("LicenseUrl") + .HasMaxLength(4000); + + b.Property("Listed"); + + b.Property("MinClientVersion") + .HasMaxLength(44); + + b.Property("ProjectUrl") + .HasMaxLength(4000); + + b.Property("Published"); + + b.Property("RepositoryType") + .HasMaxLength(100); + + b.Property("RepositoryUrl") + .HasMaxLength(4000); + + b.Property("RequireLicenseAcceptance"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .ValueGeneratedOnAddOrUpdate(); + + b.Property("Summary") + .HasMaxLength(4000); + + b.Property("Tags") + .HasMaxLength(4000); + + b.Property("Title") + .HasMaxLength(256); + + b.Property("VersionString") + .IsRequired() + .HasColumnName("Version") + .HasMaxLength(64); + + b.HasKey("Key"); + + b.HasIndex("Id"); + + b.HasIndex("Id", "VersionString") + .IsUnique(); + + b.ToTable("Packages"); + }); + + modelBuilder.Entity("BaGet.Core.Entities.PackageDependency", b => + { + b.Property("Key") + .ValueGeneratedOnAdd(); + + b.Property("Id") + .HasColumnType("TEXT COLLATE NOCASE") + .HasMaxLength(128); + + b.Property("PackageKey"); + + b.Property("TargetFramework") + .HasMaxLength(256); + + b.Property("VersionRange") + .HasMaxLength(256); + + b.HasKey("Key"); + + b.HasIndex("PackageKey"); + + b.ToTable("PackageDependencies"); + }); + + modelBuilder.Entity("BaGet.Core.Entities.PackageDependency", b => + { + b.HasOne("BaGet.Core.Entities.Package", "Package") + .WithMany("Dependencies") + .HasForeignKey("PackageKey"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/BaGet/Program.cs b/src/BaGet/Program.cs index 9df03c79..21e9684f 100644 --- a/src/BaGet/Program.cs +++ b/src/BaGet/Program.cs @@ -1,64 +1,64 @@ -using BaGet.Core.Mirror; -using BaGet.Extensions; -using McMaster.Extensions.CommandLineUtils; -using Microsoft.AspNetCore; -using Microsoft.AspNetCore.Hosting; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Hosting; - -namespace BaGet -{ - public class Program - { - public static void Main(string[] args) - { - var app = new CommandLineApplication - { - Name = "baget", - Description = "A light-weight NuGet service", - }; - - app.HelpOption(inherited: true); - - app.Command("import", import => - { - import.Command("downloads", downloads => - { - downloads.OnExecute(async () => - { - var provider = CreateHostBuilder(args).Build().Services; - - await provider - .GetRequiredService() - .ImportAsync(); - }); - }); - }); - - app.OnExecute(() => - { - CreateWebHostBuilder(args).Build().Run(); - }); - - app.Execute(args); - } - - public static IWebHostBuilder CreateWebHostBuilder(string[] args) => - WebHost.CreateDefaultBuilder(args) - .UseStartup() - .UseKestrel(options => - { - // Remove the upload limit from Kestrel. If needed, an upload limit can - // be enforced by a reverse proxy server, like IIS. - options.Limits.MaxRequestBodySize = null; - }); - - public static IHostBuilder CreateHostBuilder(string[] args) - { - return new HostBuilder() - .ConfigureBaGetConfiguration(args) - .ConfigureBaGetServices() - .ConfigureBaGetLogging(); - } - } -} +using BaGet.Core.Mirror; +using BaGet.Extensions; +using McMaster.Extensions.CommandLineUtils; +using Microsoft.AspNetCore; +using Microsoft.AspNetCore.Hosting; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; + +namespace BaGet +{ + public class Program + { + public static void Main(string[] args) + { + var app = new CommandLineApplication + { + Name = "baget", + Description = "A light-weight NuGet service", + }; + + app.HelpOption(inherited: true); + + app.Command("import", import => + { + import.Command("downloads", downloads => + { + downloads.OnExecute(async () => + { + var provider = CreateHostBuilder(args).Build().Services; + + await provider + .GetRequiredService() + .ImportAsync(); + }); + }); + }); + + app.OnExecute(() => + { + CreateWebHostBuilder(args).Build().Run(); + }); + + app.Execute(args); + } + + public static IWebHostBuilder CreateWebHostBuilder(string[] args) => + WebHost.CreateDefaultBuilder(args) + .UseStartup() + .UseKestrel(options => + { + // Remove the upload limit from Kestrel. If needed, an upload limit can + // be enforced by a reverse proxy server, like IIS. + options.Limits.MaxRequestBodySize = null; + }); + + public static IHostBuilder CreateHostBuilder(string[] args) + { + return new HostBuilder() + .ConfigureBaGetConfiguration(args) + .ConfigureBaGetServices() + .ConfigureBaGetLogging(); + } + } +} diff --git a/src/BaGet/Properties/launchSettings.json b/src/BaGet/Properties/launchSettings.json index 749a6a58..0fca24e1 100644 --- a/src/BaGet/Properties/launchSettings.json +++ b/src/BaGet/Properties/launchSettings.json @@ -1,27 +1,27 @@ -{ - "iisSettings": { - "windowsAuthentication": false, - "anonymousAuthentication": true, - "iisExpress": { - "applicationUrl": "http://localhost:50557/", - "sslPort": 0 - } - }, - "profiles": { - "IIS Express": { - "commandName": "IISExpress", - "launchBrowser": true, - "environmentVariables": { - "ASPNETCORE_ENVIRONMENT": "Development" - } - }, - "BaGet": { - "commandName": "Project", - "launchBrowser": true, - "environmentVariables": { - "ASPNETCORE_ENVIRONMENT": "Development" - }, - "applicationUrl": "http://localhost:50561/" - } - } +{ + "iisSettings": { + "windowsAuthentication": false, + "anonymousAuthentication": true, + "iisExpress": { + "applicationUrl": "http://localhost:50557/", + "sslPort": 0 + } + }, + "profiles": { + "IIS Express": { + "commandName": "IISExpress", + "launchBrowser": true, + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + }, + "BaGet": { + "commandName": "Project", + "launchBrowser": true, + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + }, + "applicationUrl": "http://localhost:50561/" + } + } } \ No newline at end of file diff --git a/src/BaGet/Routes.cs b/src/BaGet/Routes.cs index f8ed84b9..bcb0332a 100644 --- a/src/BaGet/Routes.cs +++ b/src/BaGet/Routes.cs @@ -1,21 +1,21 @@ -namespace BaGet -{ - public class Routes - { - public const string IndexRouteName = "index"; - public const string UploadPackageRouteName = "upload-package"; - public const string UploadSymbolRouteName = "upload-symbol"; - public const string DeleteRouteName = "delete"; - public const string RelistRouteName = "relist"; - public const string SearchRouteName = "search"; - public const string AutocompleteRouteName = "autocomplete"; - public const string RegistrationIndexRouteName = "registration-index"; - public const string RegistrationLeafRouteName = "registration-leaf"; - public const string PackageVersionsRouteName = "package-versions"; - public const string PackageDownloadRouteName = "package-download"; - public const string PackageDownloadManifestRouteName = "package-download-manifest"; - public const string PackageDownloadReadmeRouteName = "package-download-readme"; - public const string SymbolDownloadRouteName = "symbol-download"; - - } -} +namespace BaGet +{ + public class Routes + { + public const string IndexRouteName = "index"; + public const string UploadPackageRouteName = "upload-package"; + public const string UploadSymbolRouteName = "upload-symbol"; + public const string DeleteRouteName = "delete"; + public const string RelistRouteName = "relist"; + public const string SearchRouteName = "search"; + public const string AutocompleteRouteName = "autocomplete"; + public const string RegistrationIndexRouteName = "registration-index"; + public const string RegistrationLeafRouteName = "registration-leaf"; + public const string PackageVersionsRouteName = "package-versions"; + public const string PackageDownloadRouteName = "package-download"; + public const string PackageDownloadManifestRouteName = "package-download-manifest"; + public const string PackageDownloadReadmeRouteName = "package-download-readme"; + public const string SymbolDownloadRouteName = "symbol-download"; + + } +} diff --git a/src/BaGet/Startup.cs b/src/BaGet/Startup.cs index 71d51fd3..e22fbc0b 100644 --- a/src/BaGet/Startup.cs +++ b/src/BaGet/Startup.cs @@ -1,85 +1,85 @@ -using System; -using BaGet.Configurations; -using BaGet.Core.Configuration; -using BaGet.Core.Entities; -using BaGet.Extensions; -using Microsoft.AspNetCore.Builder; -using Microsoft.AspNetCore.Hosting; -using Microsoft.AspNetCore.SpaServices.ReactDevelopmentServer; -using Microsoft.EntityFrameworkCore; -using Microsoft.Extensions.Configuration; -using Microsoft.Extensions.DependencyInjection; - -namespace BaGet -{ - public class Startup - { - public Startup(IConfiguration configuration) - { - Configuration = configuration ?? throw new ArgumentNullException(nameof(configuration)); - } - - public IConfiguration Configuration { get; } - - public void ConfigureServices(IServiceCollection services) - { - services.ConfigureBaGet(Configuration, httpServices: true); - - // In production, the UI files will be served from this directory - services.AddSpaStaticFiles(configuration => - { - configuration.RootPath = "BaGet.UI/build"; - }); - } - - // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. - public void Configure(IApplicationBuilder app, IHostingEnvironment env) - { - if (env.IsDevelopment()) - { - app.UseDeveloperExceptionPage(); - app.UseStatusCodePages(); - } - - // Run migrations if necessary. - var options = Configuration.Get(); - if (options.RunMigrationsAtStartup) - { - using (var scope = app.ApplicationServices.CreateScope()) - { - scope.ServiceProvider - .GetRequiredService() - .Database - .Migrate(); - } - } - - app.UsePathBase(options.PathBase); - app.UseForwardedHeaders(); - app.UseSpaStaticFiles(); - - app.UseCors(ConfigureCorsOptions.CorsPolicy); - - app.UseMvc(routes => - { - routes - .MapServiceIndexRoutes() - .MapPackagePublishRoutes() - .MapSymbolRoutes() - .MapSearchRoutes() - .MapRegistrationRoutes() - .MapPackageContentRoutes(); - }); - - app.UseSpa(spa => - { - spa.Options.SourcePath = "../BaGet.UI"; - - if (env.IsDevelopment()) - { - spa.UseReactDevelopmentServer(npmScript: "start"); - } - }); - } - } -} +using System; +using BaGet.Configurations; +using BaGet.Core.Configuration; +using BaGet.Core.Entities; +using BaGet.Extensions; +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Hosting; +using Microsoft.AspNetCore.SpaServices.ReactDevelopmentServer; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; + +namespace BaGet +{ + public class Startup + { + public Startup(IConfiguration configuration) + { + Configuration = configuration ?? throw new ArgumentNullException(nameof(configuration)); + } + + public IConfiguration Configuration { get; } + + public void ConfigureServices(IServiceCollection services) + { + services.ConfigureBaGet(Configuration, httpServices: true); + + // In production, the UI files will be served from this directory + services.AddSpaStaticFiles(configuration => + { + configuration.RootPath = "BaGet.UI/build"; + }); + } + + // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. + public void Configure(IApplicationBuilder app, IHostingEnvironment env) + { + if (env.IsDevelopment()) + { + app.UseDeveloperExceptionPage(); + app.UseStatusCodePages(); + } + + // Run migrations if necessary. + var options = Configuration.Get(); + if (options.RunMigrationsAtStartup) + { + using (var scope = app.ApplicationServices.CreateScope()) + { + scope.ServiceProvider + .GetRequiredService() + .Database + .Migrate(); + } + } + + app.UsePathBase(options.PathBase); + app.UseForwardedHeaders(); + app.UseSpaStaticFiles(); + + app.UseCors(ConfigureCorsOptions.CorsPolicy); + + app.UseMvc(routes => + { + routes + .MapServiceIndexRoutes() + .MapPackagePublishRoutes() + .MapSymbolRoutes() + .MapSearchRoutes() + .MapRegistrationRoutes() + .MapPackageContentRoutes(); + }); + + app.UseSpa(spa => + { + spa.Options.SourcePath = "../BaGet.UI"; + + if (env.IsDevelopment()) + { + spa.UseReactDevelopmentServer(npmScript: "start"); + } + }); + } + } +} diff --git a/src/BaGet/readme.md b/src/BaGet/readme.md index a575cb3a..773439b1 100644 --- a/src/BaGet/readme.md +++ b/src/BaGet/readme.md @@ -1,16 +1,16 @@ -# BaGet - -This is the project that implements [NuGet service APIs](https://docs.microsoft.com/en-us/nuget/api/overview). Most of the core logic is contained within the `BaGet.Core` project. - -## Migrations - -Regenerate migrations with: - -``` -rm baget.db -dotnet ef migrations remove -dotnet ef migrations add Initial --context SqliteContext --output-dir Migrations/Sqlite -dotnet ef migrations add Initial --context SqlServerContext --output-dir Migrations/SqlServer - -dotnet ef database update +# BaGet + +This is the project that implements [NuGet service APIs](https://docs.microsoft.com/en-us/nuget/api/overview). Most of the core logic is contained within the `BaGet.Core` project. + +## Migrations + +Regenerate migrations with: + +``` +rm baget.db +dotnet ef migrations remove +dotnet ef migrations add Initial --context SqliteContext --output-dir Migrations/Sqlite +dotnet ef migrations add Initial --context SqlServerContext --output-dir Migrations/SqlServer + +dotnet ef database update ``` \ No newline at end of file diff --git a/src/readme.md b/src/readme.md index 705966a4..c3b5232d 100644 --- a/src/readme.md +++ b/src/readme.md @@ -1,9 +1,9 @@ -# BaGet Source Code - -These folders contain the core components of BaGet: - -* `BaGet` - the app's entry point, API controllers, and CLI commands -* `BaGet.Core` - the core logic and services -* `BaGet.Protocol` - Libraries to interact with NuGet server APIs -* `BaGet.UI` - BaGet's frontend -* `BaGet.Azure` - the Azure implementation of BaGet +# BaGet Source Code + +These folders contain the core components of BaGet: + +* `BaGet` - the app's entry point, API controllers, and CLI commands +* `BaGet.Core` - the core logic and services +* `BaGet.Protocol` - Libraries to interact with NuGet server APIs +* `BaGet.UI` - BaGet's frontend +* `BaGet.Azure` - the Azure implementation of BaGet diff --git a/tests/BaGet.Core.Tests/Services/PackageDeletionServiceTests.cs b/tests/BaGet.Core.Tests/Services/PackageDeletionServiceTests.cs index b28e8831..51ef11c9 100644 --- a/tests/BaGet.Core.Tests/Services/PackageDeletionServiceTests.cs +++ b/tests/BaGet.Core.Tests/Services/PackageDeletionServiceTests.cs @@ -1,116 +1,116 @@ -using System.Threading; -using System.Threading.Tasks; -using BaGet.Core.Configuration; -using BaGet.Core.Services; -using Microsoft.Extensions.Logging; -using Microsoft.Extensions.Options; -using Moq; -using NuGet.Versioning; -using Xunit; - -namespace BaGet.Core.Tests.Services -{ - public class PackageDeletionServiceTests - { - private static readonly string PackageId = "Package"; - private static readonly NuGetVersion PackageVersion = new NuGetVersion("1.0.0"); - - private readonly Mock _packages; - private readonly Mock _storage; - - private readonly BaGetOptions _options; - private readonly PackageDeletionService _target; - - public PackageDeletionServiceTests() - { - _packages = new Mock(); - _storage = new Mock(); - _options = new BaGetOptions(); - - var optionsSnapshot = new Mock>(); - optionsSnapshot.Setup(o => o.Value).Returns(_options); - - _target = new PackageDeletionService( - _packages.Object, - _storage.Object, - optionsSnapshot.Object, - Mock.Of>()); - } - - [Theory] - [InlineData(false)] - [InlineData(true)] - public async Task WhenUnlist_ReturnsTrueOnlyIfPackageExists(bool packageExists) - { - // Arrange - var cancellationToken = CancellationToken.None; - _options.PackageDeletionBehavior = PackageDeletionBehavior.Unlist; - - _packages - .Setup(p => p.UnlistPackageAsync(PackageId, PackageVersion)) - .ReturnsAsync(packageExists); - - // Act - var result = await _target.TryDeletePackageAsync(PackageId, PackageVersion, cancellationToken); - - // Assert - Assert.Equal(packageExists, result); - - _packages.Verify( - p => p.UnlistPackageAsync(PackageId, PackageVersion), - Times.Once); - - _packages.Verify( - p => p.HardDeletePackageAsync(It.IsAny(), It.IsAny()), - Times.Never); - _storage.Verify( - s => s.DeleteAsync(It.IsAny(), It.IsAny(), cancellationToken), - Times.Never); - } - - [Theory] - [InlineData(false)] - [InlineData(true)] - public async Task WhenHardDelete_ReturnsTrueOnlyIfPackageExists(bool packageExists) - { - // Arrange - _options.PackageDeletionBehavior = PackageDeletionBehavior.HardDelete; - - var step = 0; - var databaseStep = -1; - var storageStep = -1; - var cancellationToken = CancellationToken.None; - - _packages - .Setup(p => p.HardDeletePackageAsync(PackageId, PackageVersion)) - .Callback(() => databaseStep = step++) - .ReturnsAsync(packageExists); - - _storage - .Setup(s => s.DeleteAsync(PackageId, PackageVersion, cancellationToken)) - .Callback(() => storageStep = step++) - .Returns(Task.CompletedTask); - - // Act - var result = await _target.TryDeletePackageAsync(PackageId, PackageVersion, cancellationToken); - - // Assert - The database step MUST happen before the storage step. - Assert.Equal(packageExists, result); - Assert.Equal(0, databaseStep); - Assert.Equal(1, storageStep); - - // The storage deletion should happen even if the package couldn't - // be found in the database. This ensures consistency. - _packages.Verify( - p => p.HardDeletePackageAsync(PackageId, PackageVersion), - Times.Once); - _storage.Verify( - s => s.DeleteAsync(PackageId, PackageVersion, cancellationToken), - Times.Once); - - _packages.Verify( - p => p.UnlistPackageAsync(It.IsAny(), It.IsAny()), - Times.Never); - } - } -} +using System.Threading; +using System.Threading.Tasks; +using BaGet.Core.Configuration; +using BaGet.Core.Services; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; +using Moq; +using NuGet.Versioning; +using Xunit; + +namespace BaGet.Core.Tests.Services +{ + public class PackageDeletionServiceTests + { + private static readonly string PackageId = "Package"; + private static readonly NuGetVersion PackageVersion = new NuGetVersion("1.0.0"); + + private readonly Mock _packages; + private readonly Mock _storage; + + private readonly BaGetOptions _options; + private readonly PackageDeletionService _target; + + public PackageDeletionServiceTests() + { + _packages = new Mock(); + _storage = new Mock(); + _options = new BaGetOptions(); + + var optionsSnapshot = new Mock>(); + optionsSnapshot.Setup(o => o.Value).Returns(_options); + + _target = new PackageDeletionService( + _packages.Object, + _storage.Object, + optionsSnapshot.Object, + Mock.Of>()); + } + + [Theory] + [InlineData(false)] + [InlineData(true)] + public async Task WhenUnlist_ReturnsTrueOnlyIfPackageExists(bool packageExists) + { + // Arrange + var cancellationToken = CancellationToken.None; + _options.PackageDeletionBehavior = PackageDeletionBehavior.Unlist; + + _packages + .Setup(p => p.UnlistPackageAsync(PackageId, PackageVersion)) + .ReturnsAsync(packageExists); + + // Act + var result = await _target.TryDeletePackageAsync(PackageId, PackageVersion, cancellationToken); + + // Assert + Assert.Equal(packageExists, result); + + _packages.Verify( + p => p.UnlistPackageAsync(PackageId, PackageVersion), + Times.Once); + + _packages.Verify( + p => p.HardDeletePackageAsync(It.IsAny(), It.IsAny()), + Times.Never); + _storage.Verify( + s => s.DeleteAsync(It.IsAny(), It.IsAny(), cancellationToken), + Times.Never); + } + + [Theory] + [InlineData(false)] + [InlineData(true)] + public async Task WhenHardDelete_ReturnsTrueOnlyIfPackageExists(bool packageExists) + { + // Arrange + _options.PackageDeletionBehavior = PackageDeletionBehavior.HardDelete; + + var step = 0; + var databaseStep = -1; + var storageStep = -1; + var cancellationToken = CancellationToken.None; + + _packages + .Setup(p => p.HardDeletePackageAsync(PackageId, PackageVersion)) + .Callback(() => databaseStep = step++) + .ReturnsAsync(packageExists); + + _storage + .Setup(s => s.DeleteAsync(PackageId, PackageVersion, cancellationToken)) + .Callback(() => storageStep = step++) + .Returns(Task.CompletedTask); + + // Act + var result = await _target.TryDeletePackageAsync(PackageId, PackageVersion, cancellationToken); + + // Assert - The database step MUST happen before the storage step. + Assert.Equal(packageExists, result); + Assert.Equal(0, databaseStep); + Assert.Equal(1, storageStep); + + // The storage deletion should happen even if the package couldn't + // be found in the database. This ensures consistency. + _packages.Verify( + p => p.HardDeletePackageAsync(PackageId, PackageVersion), + Times.Once); + _storage.Verify( + s => s.DeleteAsync(PackageId, PackageVersion, cancellationToken), + Times.Once); + + _packages.Verify( + p => p.UnlistPackageAsync(It.IsAny(), It.IsAny()), + Times.Never); + } + } +} diff --git a/tests/BaGet.Core.Tests/Services/PackageServiceTests.cs b/tests/BaGet.Core.Tests/Services/PackageServiceTests.cs index d363b3a9..08e09952 100644 --- a/tests/BaGet.Core.Tests/Services/PackageServiceTests.cs +++ b/tests/BaGet.Core.Tests/Services/PackageServiceTests.cs @@ -1,206 +1,206 @@ -using System.Collections.Generic; -using System.Threading.Tasks; -using BaGet.Core.Entities; -using BaGet.Core.Services; -using Moq; -using Xunit; - -namespace BaGet.Core.Tests.Services -{ - public class PackageServiceTests - { - public class AddAsync : FactsBase - { - [Fact] - public async Task ReturnsPackageAlreadyExistsOnUniqueConstraintViolation() - { - await Task.Yield(); - } - - [Fact] - public async Task AddsPackage() - { - // TODO: Returns Success - // TODO: Adds package - await Task.Yield(); - } - } - - public class ExistsAsync : FactsBase - { - [Theory] - [InlineData("Package", "1.0.0", true)] - [InlineData("Package", "1.0.0.0", true)] - [InlineData("Unlisted.Package", "1.0.0", true)] - [InlineData("Fake.Package", "1.0.0", false)] - public async Task ReturnsTrueIfPackageExists(string packageId, string packageVersion, bool exists) - { - System.Console.WriteLine($"TODO: {packageId} {packageVersion} {exists}"); - await Task.Yield(); - } - } - - public class FindAsync : FactsBase - { - [Fact] - public async Task ReturnsEmptyListIfPackageDoesNotExist() - { - // Ensure the context has packages with a different id/version - await Task.Yield(); - } - - [Theory] - [MemberData(nameof(ReturnsPackagesData))] - public async Task ReturnsPackages(string packageId, string packageVersion, bool includeUnlisted, bool exists) - { - // TODO: Ensure resulting versions are normalized. - System.Console.WriteLine($"TODO: {packageId} {packageVersion} {includeUnlisted} {exists}"); - await Task.Yield(); - } - - public static IEnumerable ReturnsPackagesData() - { - object[] ReturnsPackagesHelper(string packageId, string packageVersion, bool includeUnlisted, bool exists) - { - return new object[] { packageId, packageVersion, includeUnlisted, exists }; - } - - // A package that doesn't exist should never be returned - yield return ReturnsPackagesHelper("Fake.Package", "1.0.0", includeUnlisted: true, exists: false); - - // A listed package should be returned regardless of the "includeUnlisted" parameter - yield return ReturnsPackagesHelper("Package", "1.0.0", includeUnlisted: false, exists: true); - yield return ReturnsPackagesHelper("Package", "1.0.0", includeUnlisted: true, exists: true); - - // The inputted package version should be normalized - yield return ReturnsPackagesHelper("Package", "1.0.0.0", includeUnlisted: false, exists: true); - - // Unlisted packages should only be returned if "includeUnlisted" is true - yield return ReturnsPackagesHelper("Unlisted.Package", "1.0.0", includeUnlisted: false, exists: false); - yield return ReturnsPackagesHelper("Unlisted.Package", "1.0.0", includeUnlisted: true, exists: true); - } - } - - public class FindOrNullAsync : FactsBase - { - [Fact] - public async Task ReturnsNullIfPackageDoesNotExist() - { - await Task.Yield(); - } - - [Theory] - [MemberData(nameof(ReturnsPackageData))] - public async Task ReturnsPackage(string packageId, string packageVersion, bool includeUnlisted, bool exists) - { - // TODO: Ensure resulting versions are normalized. - System.Console.WriteLine($"TODO: {packageId} {packageVersion} {includeUnlisted} {exists}"); - await Task.Yield(); - } - - public static IEnumerable ReturnsPackageData() - { - object[] ReturnsPackageHelper(string packageId, string packageVersion, bool includeUnlisted, bool exists) - { - return new object[] { packageId, packageVersion, includeUnlisted, exists }; - } - - // A package that doesn't exist should never be returned - yield return ReturnsPackageHelper("Fake.Package", "1.0.0", includeUnlisted: true, exists: false); - - // A listed package should be returned regardless of the "includeUnlisted" parameter - yield return ReturnsPackageHelper("Package", "1.0.0", includeUnlisted: false, exists: true); - yield return ReturnsPackageHelper("Package", "1.0.0", includeUnlisted: true, exists: true); - - // The inputted package version should be normalized - yield return ReturnsPackageHelper("Package", "1.0.0.0", includeUnlisted: false, exists: true); - - // Unlisted packages should only be returned if "includeUnlisted" is true - yield return ReturnsPackageHelper("Unlisted.Package", "1.0.0", includeUnlisted: false, exists: false); - yield return ReturnsPackageHelper("Unlisted.Package", "1.0.0", includeUnlisted: true, exists: true); - } - } - - public class UnlistPackageAsync : FactsBase - { - [Fact] - public async Task ReturnsFalseIfPackageDoesNotExist() - { - await Task.Yield(); - } - - [Theory] - [InlineData(false)] - [InlineData(true)] - public async Task UnlistsPackage(bool listed) - { - // TODO: This should succeed if the package is unlisted. - // TODO: Returns true - System.Console.WriteLine($"TODO: {listed}"); - await Task.Yield(); - } - } - - public class RelistPackageAsync : FactsBase - { - [Fact] - public async Task ReturnsFalseIfPackageDoesNotExist() - { - await Task.Yield(); - } - - [Theory] - [InlineData(false)] - [InlineData(true)] - public async Task RelistsPackage(bool listed) - { - // TODO: This should succeed if the package is listed. - // TODO: Return true - System.Console.WriteLine($"TODO: {listed}"); - await Task.Yield(); - } - } - - public class AddDownloadAsync : FactsBase - { - [Fact] - public async Task ReturnsFalseIfPackageDoesNotExist() - { - await Task.Yield(); - } - - [Fact] - public async Task IncrementsPackageDownloads() - { - await Task.Yield(); - } - } - - public class HardDeletePackageAsync : FactsBase - { - [Fact] - public async Task ReturnsFalseIfPackageDoesNotExist() - { - await Task.Yield(); - } - - [Fact] - public async Task DeletesPackage() - { - await Task.Yield(); - } - } - - public class FactsBase - { - protected readonly Mock _context; - protected readonly PackageService _target; - - public FactsBase() - { - _context = new Mock(); - _target = new PackageService(_context.Object); - } - } - } -} +using System.Collections.Generic; +using System.Threading.Tasks; +using BaGet.Core.Entities; +using BaGet.Core.Services; +using Moq; +using Xunit; + +namespace BaGet.Core.Tests.Services +{ + public class PackageServiceTests + { + public class AddAsync : FactsBase + { + [Fact] + public async Task ReturnsPackageAlreadyExistsOnUniqueConstraintViolation() + { + await Task.Yield(); + } + + [Fact] + public async Task AddsPackage() + { + // TODO: Returns Success + // TODO: Adds package + await Task.Yield(); + } + } + + public class ExistsAsync : FactsBase + { + [Theory] + [InlineData("Package", "1.0.0", true)] + [InlineData("Package", "1.0.0.0", true)] + [InlineData("Unlisted.Package", "1.0.0", true)] + [InlineData("Fake.Package", "1.0.0", false)] + public async Task ReturnsTrueIfPackageExists(string packageId, string packageVersion, bool exists) + { + System.Console.WriteLine($"TODO: {packageId} {packageVersion} {exists}"); + await Task.Yield(); + } + } + + public class FindAsync : FactsBase + { + [Fact] + public async Task ReturnsEmptyListIfPackageDoesNotExist() + { + // Ensure the context has packages with a different id/version + await Task.Yield(); + } + + [Theory] + [MemberData(nameof(ReturnsPackagesData))] + public async Task ReturnsPackages(string packageId, string packageVersion, bool includeUnlisted, bool exists) + { + // TODO: Ensure resulting versions are normalized. + System.Console.WriteLine($"TODO: {packageId} {packageVersion} {includeUnlisted} {exists}"); + await Task.Yield(); + } + + public static IEnumerable ReturnsPackagesData() + { + object[] ReturnsPackagesHelper(string packageId, string packageVersion, bool includeUnlisted, bool exists) + { + return new object[] { packageId, packageVersion, includeUnlisted, exists }; + } + + // A package that doesn't exist should never be returned + yield return ReturnsPackagesHelper("Fake.Package", "1.0.0", includeUnlisted: true, exists: false); + + // A listed package should be returned regardless of the "includeUnlisted" parameter + yield return ReturnsPackagesHelper("Package", "1.0.0", includeUnlisted: false, exists: true); + yield return ReturnsPackagesHelper("Package", "1.0.0", includeUnlisted: true, exists: true); + + // The inputted package version should be normalized + yield return ReturnsPackagesHelper("Package", "1.0.0.0", includeUnlisted: false, exists: true); + + // Unlisted packages should only be returned if "includeUnlisted" is true + yield return ReturnsPackagesHelper("Unlisted.Package", "1.0.0", includeUnlisted: false, exists: false); + yield return ReturnsPackagesHelper("Unlisted.Package", "1.0.0", includeUnlisted: true, exists: true); + } + } + + public class FindOrNullAsync : FactsBase + { + [Fact] + public async Task ReturnsNullIfPackageDoesNotExist() + { + await Task.Yield(); + } + + [Theory] + [MemberData(nameof(ReturnsPackageData))] + public async Task ReturnsPackage(string packageId, string packageVersion, bool includeUnlisted, bool exists) + { + // TODO: Ensure resulting versions are normalized. + System.Console.WriteLine($"TODO: {packageId} {packageVersion} {includeUnlisted} {exists}"); + await Task.Yield(); + } + + public static IEnumerable ReturnsPackageData() + { + object[] ReturnsPackageHelper(string packageId, string packageVersion, bool includeUnlisted, bool exists) + { + return new object[] { packageId, packageVersion, includeUnlisted, exists }; + } + + // A package that doesn't exist should never be returned + yield return ReturnsPackageHelper("Fake.Package", "1.0.0", includeUnlisted: true, exists: false); + + // A listed package should be returned regardless of the "includeUnlisted" parameter + yield return ReturnsPackageHelper("Package", "1.0.0", includeUnlisted: false, exists: true); + yield return ReturnsPackageHelper("Package", "1.0.0", includeUnlisted: true, exists: true); + + // The inputted package version should be normalized + yield return ReturnsPackageHelper("Package", "1.0.0.0", includeUnlisted: false, exists: true); + + // Unlisted packages should only be returned if "includeUnlisted" is true + yield return ReturnsPackageHelper("Unlisted.Package", "1.0.0", includeUnlisted: false, exists: false); + yield return ReturnsPackageHelper("Unlisted.Package", "1.0.0", includeUnlisted: true, exists: true); + } + } + + public class UnlistPackageAsync : FactsBase + { + [Fact] + public async Task ReturnsFalseIfPackageDoesNotExist() + { + await Task.Yield(); + } + + [Theory] + [InlineData(false)] + [InlineData(true)] + public async Task UnlistsPackage(bool listed) + { + // TODO: This should succeed if the package is unlisted. + // TODO: Returns true + System.Console.WriteLine($"TODO: {listed}"); + await Task.Yield(); + } + } + + public class RelistPackageAsync : FactsBase + { + [Fact] + public async Task ReturnsFalseIfPackageDoesNotExist() + { + await Task.Yield(); + } + + [Theory] + [InlineData(false)] + [InlineData(true)] + public async Task RelistsPackage(bool listed) + { + // TODO: This should succeed if the package is listed. + // TODO: Return true + System.Console.WriteLine($"TODO: {listed}"); + await Task.Yield(); + } + } + + public class AddDownloadAsync : FactsBase + { + [Fact] + public async Task ReturnsFalseIfPackageDoesNotExist() + { + await Task.Yield(); + } + + [Fact] + public async Task IncrementsPackageDownloads() + { + await Task.Yield(); + } + } + + public class HardDeletePackageAsync : FactsBase + { + [Fact] + public async Task ReturnsFalseIfPackageDoesNotExist() + { + await Task.Yield(); + } + + [Fact] + public async Task DeletesPackage() + { + await Task.Yield(); + } + } + + public class FactsBase + { + protected readonly Mock _context; + protected readonly PackageService _target; + + public FactsBase() + { + _context = new Mock(); + _target = new PackageService(_context.Object); + } + } + } +}