;"`
+* la connection au serveur de messagerie,
+* l'URL du ou des sites à propulser,
+* Les autres détails.
+
+Pour faire ceci, vous pourrez éditer une copie du fichier `appsettings.json` vers `appsettings.Production.json`,
+pour renseigner toutes les valeurs spécifiées.
+
+* Démarrer le serveur :
+
+````bash
+sudo systemctl start isnd
+````
+
+* Activation du serveur :
+
+````bash
+sudo systemctl enable isnd
+````
+
+### Installation du client
+
+````bash
+sudo mkdir /usr/local/lib/isn
+sudo cp -a src/isn/bin/Release/net6.0/* /usr/local/lib/isn
+sudo chown -R root.root /usr/local/lib/isn
+sudo ln -s /usr/local/lib/isn/isn /usr/local/bin/isn
+````
+
+### Mises à jour
+````bash
+# compiler tout
+dotnet build -c Release
+dotnet publish -c Release -f net7.0 src/isnd
+# MAJ du serveur
+sudo systemctl stop isnd
+sudo cp -a src/isnd/bin/Release/net7.0/publish/* /srv/www/isnd
+sudo systemctl start isnd
+# MAJ du client
+sudo cp -a src/isn/bin/Release/net7.0/* /usr/local/lib/isn
+sudo chown -R root.root /usr/local/lib/isn
````
diff --git a/Startup.cs b/Startup.cs
deleted file mode 100644
index 00a2cfb..0000000
--- a/Startup.cs
+++ /dev/null
@@ -1,54 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Threading.Tasks;
-using Microsoft.AspNetCore.Builder;
-using Microsoft.AspNetCore.Hosting;
-using Microsoft.Extensions.Configuration;
-using Microsoft.Extensions.DependencyInjection;
-
-namespace nuget_host
-{
- public class Startup
- {
- public Startup(IHostingEnvironment env, IConfiguration config)
- {
- Configuration = config;
- }
-
- public IConfiguration Configuration { get; }
- public static string ExternalUrl { get; private set; }
- public static string SourceDir { get; private set; }
-
- // This method gets called by the runtime. Use this method to add services to the container.
- public void ConfigureServices(IServiceCollection services)
- {
- services.AddMvc();
- }
-
- // 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();
- }
- else
- {
- app.UseExceptionHandler("/Home/Error");
- }
-
- ExternalUrl = Configuration["NuGet:ExternalUrl"];
- SourceDir = Configuration["NuGet:SourceDir"];
-
- app.UseStaticFiles();
-
- app.UseMvc(routes =>
- {
- routes.MapRoute(
- name: "default",
- template: "{controller=Home}/{action=Index}/{id?}");
- });
- }
- }
-}
diff --git a/Views/Home/Index.cshtml b/Views/Home/Index.cshtml
deleted file mode 100644
index 470f4c3..0000000
--- a/Views/Home/Index.cshtml
+++ /dev/null
@@ -1,106 +0,0 @@
-@{
- ViewData["Title"] = "Home Page";
-}
-
-
-
-
-
-
-
-
-
-
-
-
-
- Learn how to build ASP.NET apps that can run anywhere.
-
- Learn More
-
-
-
-
-
-
-
-
- There are powerful new features in Visual Studio for building modern web apps.
-
- Learn More
-
-
-
-
-
-
-
-
- Bring in libraries from NuGet and npm, and automate tasks using Grunt or Gulp.
-
- Learn More
-
-
-
-
-
-
-
-
- Learn how Microsoft's Azure cloud platform allows you to build, deploy, and scale web apps.
-
- Learn More
-
-
-
-
-
-
-
- Previous
-
-
-
- Next
-
-
-
-
-
-
Application uses
-
- Sample pages using ASP.NET Core MVC
- Theming using Bootstrap
-
-
-
-
-
-
diff --git a/Views/Shared/Error.cshtml b/Views/Shared/Error.cshtml
deleted file mode 100644
index 75e2d51..0000000
--- a/Views/Shared/Error.cshtml
+++ /dev/null
@@ -1,22 +0,0 @@
-@model ErrorViewModel
-@{
- ViewData["Title"] = "Error";
-}
-
-Error.
-An error occurred while processing your request.
-
-@if (Model.ShowRequestId)
-{
-
- Request ID: @Model.RequestId
-
-}
-
-Development Mode
-
- Swapping to Development environment will display more detailed information about the error that occurred.
-
-
- Development environment should not be enabled in deployed applications , as it can result in sensitive information from exceptions being displayed to end users. For local debugging, development environment can be enabled by setting the ASPNETCORE_ENVIRONMENT environment variable to Development , and restarting the application.
-
diff --git a/Views/Shared/_Layout.cshtml b/Views/Shared/_Layout.cshtml
deleted file mode 100644
index f2eedc7..0000000
--- a/Views/Shared/_Layout.cshtml
+++ /dev/null
@@ -1,71 +0,0 @@
-
-
-
-
-
- @ViewData["Title"] - nuget_host
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- @RenderSection("Scripts", required: false)
-
-
diff --git a/Views/_ViewImports.cshtml b/Views/_ViewImports.cshtml
deleted file mode 100644
index 910d2a7..0000000
--- a/Views/_ViewImports.cshtml
+++ /dev/null
@@ -1,3 +0,0 @@
-@using nuget_host
-@using nuget_host.Models
-@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
diff --git a/appsettings.Development.json b/appsettings.Development.json
deleted file mode 100644
index 2ddd280..0000000
--- a/appsettings.Development.json
+++ /dev/null
@@ -1,6 +0,0 @@
-{
- "NuGet": {
- "ExternalUrl" : ""
- },
- "Logging": {
- "IncludeScopes": false,
- "LogLevel": {
- "Default": "Warning"
- }
- }
-}
diff --git a/contrib/isnd.service b/contrib/isnd.service
new file mode 100644
index 0000000..e5e3479
--- /dev/null
+++ b/contrib/isnd.service
@@ -0,0 +1,28 @@
+[Unit]
+Description=isnd - a Nuget package repository daemon
+After=syslog.target
+After=network.target
+
+Wants=postgresql.service
+After=postgresql.service
+
+[Service]
+RestartSec=5s
+Type=simple
+User=isn
+Group=isn
+WorkingDirectory=/srv/www/isnd/
+# If using Unix socket: tells systemd to create the /run/gitea folder, which will contain the gitea.sock file
+# (manually creating /run/gitea doesn't work, because it would not persist across reboots)
+#RuntimeDirectory=gitea
+ExecStart=/srv/www/isnd/isnd
+Restart=always
+Environment=HOME=/srv/www/isnd
+
+#CapabilityBoundingSet=CAP_NET_BIND_SERVICE
+AmbientCapabilities=CAP_NET_BIND_SERVICE
+
+[Install]
+WantedBy=multi-user.target
+
+
diff --git a/contrib/testinstnuget/NuGet.Config b/contrib/testinstnuget/NuGet.Config
new file mode 100644
index 0000000..0af3cfd
--- /dev/null
+++ b/contrib/testinstnuget/NuGet.Config
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
+
diff --git a/contrib/testinstnuget/call.lines b/contrib/testinstnuget/call.lines
new file mode 100644
index 0000000..c6abc1a
--- /dev/null
+++ b/contrib/testinstnuget/call.lines
@@ -0,0 +1,2 @@
+nuget install -Verbosity detailed -Source http://localhost:5000/index.json -Prerelease Yavsc.Abstract
+nuget locals all -clear
diff --git a/contrib/testinstnuget/urls.adoc b/contrib/testinstnuget/urls.adoc
new file mode 100644
index 0000000..5c81113
--- /dev/null
+++ b/contrib/testinstnuget/urls.adoc
@@ -0,0 +1,4 @@
+= URL's
+
+
+
diff --git a/dotnet-tools.json b/dotnet-tools.json
new file mode 100644
index 0000000..4c6f94a
--- /dev/null
+++ b/dotnet-tools.json
@@ -0,0 +1,30 @@
+{
+ "version": 1,
+ "isRoot": true,
+ "tools": {
+ "codecov.tool": {
+ "version": "1.13.0",
+ "commands": [
+ "codecov"
+ ]
+ },
+ "gitversion.tool": {
+ "version": "5.10.1",
+ "commands": [
+ "dotnet-gitversion"
+ ]
+ },
+ "gitreleasemanager.tool": {
+ "version": "0.13.0",
+ "commands": [
+ "dotnet-gitreleasemanager"
+ ]
+ },
+ "Wyam2.Tool": {
+ "version": "3.0.0-rc3",
+ "commands": [
+ "wyam2"
+ ]
+ }
+ }
+}
diff --git a/isn.sln b/isn.sln
new file mode 100644
index 0000000..1e20c9f
--- /dev/null
+++ b/isn.sln
@@ -0,0 +1,101 @@
+
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio Version 16
+VisualStudioVersion = 16.6.30114.105
+MinimumVisualStudioVersion = 10.0.40219.1
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "test", "test", "{3C312E42-9A47-4BED-8265-A405FCA6AFFF}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "isnd.tests", "test\isnd.tests\isnd.tests.csproj", "{9D758F00-17FF-433D-B088-F9C2D97C9BD1}"
+EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{E8A2DF68-847A-4D88-B002-64FB666F696C}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "isnd", "src\isnd\isnd.csproj", "{468DB0E4-6221-4E01-BEFF-F452865E59C1}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "isn", "src\isn\isn.csproj", "{910E800A-59AE-46C4-B7C7-879986179246}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "isn.tests", "test\isn.tests\isn.tests.csproj", "{305F640E-11BA-44F9-95E0-C6882E9CD151}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "isn.abst", "src\isn.abst\isn.abst.csproj", "{4EFA7D3E-8B31-4BF7-96D1-B9F2867735C7}"
+EndProject
+Global
+ GlobalSection(SolutionConfigurationPlatforms) = preSolution
+ Debug|Any CPU = Debug|Any CPU
+ Debug|x64 = Debug|x64
+ Debug|x86 = Debug|x86
+ Release|Any CPU = Release|Any CPU
+ Release|x64 = Release|x64
+ Release|x86 = Release|x86
+ EndGlobalSection
+ GlobalSection(SolutionProperties) = preSolution
+ HideSolutionNode = FALSE
+ EndGlobalSection
+ GlobalSection(ProjectConfigurationPlatforms) = postSolution
+ {9D758F00-17FF-433D-B088-F9C2D97C9BD1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {9D758F00-17FF-433D-B088-F9C2D97C9BD1}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {9D758F00-17FF-433D-B088-F9C2D97C9BD1}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {9D758F00-17FF-433D-B088-F9C2D97C9BD1}.Debug|x64.Build.0 = Debug|Any CPU
+ {9D758F00-17FF-433D-B088-F9C2D97C9BD1}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {9D758F00-17FF-433D-B088-F9C2D97C9BD1}.Debug|x86.Build.0 = Debug|Any CPU
+ {9D758F00-17FF-433D-B088-F9C2D97C9BD1}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {9D758F00-17FF-433D-B088-F9C2D97C9BD1}.Release|Any CPU.Build.0 = Release|Any CPU
+ {9D758F00-17FF-433D-B088-F9C2D97C9BD1}.Release|x64.ActiveCfg = Release|Any CPU
+ {9D758F00-17FF-433D-B088-F9C2D97C9BD1}.Release|x64.Build.0 = Release|Any CPU
+ {9D758F00-17FF-433D-B088-F9C2D97C9BD1}.Release|x86.ActiveCfg = Release|Any CPU
+ {9D758F00-17FF-433D-B088-F9C2D97C9BD1}.Release|x86.Build.0 = Release|Any CPU
+ {468DB0E4-6221-4E01-BEFF-F452865E59C1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {468DB0E4-6221-4E01-BEFF-F452865E59C1}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {468DB0E4-6221-4E01-BEFF-F452865E59C1}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {468DB0E4-6221-4E01-BEFF-F452865E59C1}.Debug|x64.Build.0 = Debug|Any CPU
+ {468DB0E4-6221-4E01-BEFF-F452865E59C1}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {468DB0E4-6221-4E01-BEFF-F452865E59C1}.Debug|x86.Build.0 = Debug|Any CPU
+ {468DB0E4-6221-4E01-BEFF-F452865E59C1}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {468DB0E4-6221-4E01-BEFF-F452865E59C1}.Release|Any CPU.Build.0 = Release|Any CPU
+ {468DB0E4-6221-4E01-BEFF-F452865E59C1}.Release|x64.ActiveCfg = Release|Any CPU
+ {468DB0E4-6221-4E01-BEFF-F452865E59C1}.Release|x64.Build.0 = Release|Any CPU
+ {468DB0E4-6221-4E01-BEFF-F452865E59C1}.Release|x86.ActiveCfg = Release|Any CPU
+ {468DB0E4-6221-4E01-BEFF-F452865E59C1}.Release|x86.Build.0 = Release|Any CPU
+ {910E800A-59AE-46C4-B7C7-879986179246}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {910E800A-59AE-46C4-B7C7-879986179246}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {910E800A-59AE-46C4-B7C7-879986179246}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {910E800A-59AE-46C4-B7C7-879986179246}.Debug|x64.Build.0 = Debug|Any CPU
+ {910E800A-59AE-46C4-B7C7-879986179246}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {910E800A-59AE-46C4-B7C7-879986179246}.Debug|x86.Build.0 = Debug|Any CPU
+ {910E800A-59AE-46C4-B7C7-879986179246}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {910E800A-59AE-46C4-B7C7-879986179246}.Release|Any CPU.Build.0 = Release|Any CPU
+ {910E800A-59AE-46C4-B7C7-879986179246}.Release|x64.ActiveCfg = Release|Any CPU
+ {910E800A-59AE-46C4-B7C7-879986179246}.Release|x64.Build.0 = Release|Any CPU
+ {910E800A-59AE-46C4-B7C7-879986179246}.Release|x86.ActiveCfg = Release|Any CPU
+ {910E800A-59AE-46C4-B7C7-879986179246}.Release|x86.Build.0 = Release|Any CPU
+ {305F640E-11BA-44F9-95E0-C6882E9CD151}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {305F640E-11BA-44F9-95E0-C6882E9CD151}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {305F640E-11BA-44F9-95E0-C6882E9CD151}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {305F640E-11BA-44F9-95E0-C6882E9CD151}.Debug|x64.Build.0 = Debug|Any CPU
+ {305F640E-11BA-44F9-95E0-C6882E9CD151}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {305F640E-11BA-44F9-95E0-C6882E9CD151}.Debug|x86.Build.0 = Debug|Any CPU
+ {305F640E-11BA-44F9-95E0-C6882E9CD151}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {305F640E-11BA-44F9-95E0-C6882E9CD151}.Release|Any CPU.Build.0 = Release|Any CPU
+ {305F640E-11BA-44F9-95E0-C6882E9CD151}.Release|x64.ActiveCfg = Release|Any CPU
+ {305F640E-11BA-44F9-95E0-C6882E9CD151}.Release|x64.Build.0 = Release|Any CPU
+ {305F640E-11BA-44F9-95E0-C6882E9CD151}.Release|x86.ActiveCfg = Release|Any CPU
+ {305F640E-11BA-44F9-95E0-C6882E9CD151}.Release|x86.Build.0 = Release|Any CPU
+ {4EFA7D3E-8B31-4BF7-96D1-B9F2867735C7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {4EFA7D3E-8B31-4BF7-96D1-B9F2867735C7}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {4EFA7D3E-8B31-4BF7-96D1-B9F2867735C7}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {4EFA7D3E-8B31-4BF7-96D1-B9F2867735C7}.Debug|x64.Build.0 = Debug|Any CPU
+ {4EFA7D3E-8B31-4BF7-96D1-B9F2867735C7}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {4EFA7D3E-8B31-4BF7-96D1-B9F2867735C7}.Debug|x86.Build.0 = Debug|Any CPU
+ {4EFA7D3E-8B31-4BF7-96D1-B9F2867735C7}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {4EFA7D3E-8B31-4BF7-96D1-B9F2867735C7}.Release|Any CPU.Build.0 = Release|Any CPU
+ {4EFA7D3E-8B31-4BF7-96D1-B9F2867735C7}.Release|x64.ActiveCfg = Release|Any CPU
+ {4EFA7D3E-8B31-4BF7-96D1-B9F2867735C7}.Release|x64.Build.0 = Release|Any CPU
+ {4EFA7D3E-8B31-4BF7-96D1-B9F2867735C7}.Release|x86.ActiveCfg = Release|Any CPU
+ {4EFA7D3E-8B31-4BF7-96D1-B9F2867735C7}.Release|x86.Build.0 = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(NestedProjects) = preSolution
+ {9D758F00-17FF-433D-B088-F9C2D97C9BD1} = {3C312E42-9A47-4BED-8265-A405FCA6AFFF}
+ {468DB0E4-6221-4E01-BEFF-F452865E59C1} = {E8A2DF68-847A-4D88-B002-64FB666F696C}
+ {910E800A-59AE-46C4-B7C7-879986179246} = {E8A2DF68-847A-4D88-B002-64FB666F696C}
+ {305F640E-11BA-44F9-95E0-C6882E9CD151} = {3C312E42-9A47-4BED-8265-A405FCA6AFFF}
+ {4EFA7D3E-8B31-4BF7-96D1-B9F2867735C7} = {E8A2DF68-847A-4D88-B002-64FB666F696C}
+ EndGlobalSection
+EndGlobal
diff --git a/nuget-host.csproj b/nuget-host.csproj
deleted file mode 100644
index 347cd3f..0000000
--- a/nuget-host.csproj
+++ /dev/null
@@ -1,20 +0,0 @@
-
-
-
- netcoreapp2.0
- 85fd766d-5d23-4476-aed1-463b2942e86a
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/omnisharp.json b/omnisharp.json
new file mode 100644
index 0000000..5df81ef
--- /dev/null
+++ b/omnisharp.json
@@ -0,0 +1,22 @@
+{
+ "dotnet": {
+ "enabled": false
+ },
+ "msbuild": {
+ "enabled": true
+ },
+ "Dnx": {
+ "enabled": false
+ },
+ "Script": {
+ "enabled": false
+ },
+ "fileOptions": {
+ "systemExcludeSearchPatterns": [
+ "**/bin/**/*",
+ "**/obj/**/*",
+ "**/node_modules/**/*"
+ ],
+ "userExcludeSearchPatterns": []
+ }
+}
diff --git a/src/isn.abst/ApiConfig.cs b/src/isn.abst/ApiConfig.cs
new file mode 100644
index 0000000..46f0efa
--- /dev/null
+++ b/src/isn.abst/ApiConfig.cs
@@ -0,0 +1,16 @@
+using isn.abst;
+
+namespace isnd.Entities
+{
+ public static class ApiConfig
+ {
+ public const string Catalog = "/catalog";
+ public const string Package = "/package";
+ public const string Search = "/search";
+ public const string AutoComplete = "/autocomplete";
+ public const string Registration = "/registration";
+
+ public const string Nuspec = "/nuspec";
+ public const string Nuget = "/nuget";
+ }
+}
\ No newline at end of file
diff --git a/src/isn.abst/ApiIndexViewModel.cs b/src/isn.abst/ApiIndexViewModel.cs
new file mode 100644
index 0000000..a8e9709
--- /dev/null
+++ b/src/isn.abst/ApiIndexViewModel.cs
@@ -0,0 +1,21 @@
+using isnd.Data.Catalog;
+using Newtonsoft.Json;
+
+namespace isn.Abstract
+{
+ public class ApiIndexViewModel : HappyIdOwner
+ {
+ public ApiIndexViewModel(string id) : base(id)
+ {
+ }
+
+ [JsonProperty("@id")]
+ public string Id { get => GetId(); }
+
+ [JsonProperty("version")]
+ public string Version { get; set; }
+
+ [JsonProperty("resources")]
+ public Resource[] Resources { get; set; }
+ }
+}
\ No newline at end of file
diff --git a/src/isn.abst/Constants.cs b/src/isn.abst/Constants.cs
new file mode 100644
index 0000000..2df9fa6
--- /dev/null
+++ b/src/isn.abst/Constants.cs
@@ -0,0 +1,9 @@
+namespace isn.abst
+{
+ public static class Constants
+ {
+ public const string PaquetFileEstension = "nupkg";
+ public const string SpecFileEstension = "nuspec";
+ public const string ApiVersion = "/v3";
+ }
+}
\ No newline at end of file
diff --git a/src/isn.abst/HappyIdOwner.cs b/src/isn.abst/HappyIdOwner.cs
new file mode 100644
index 0000000..253faac
--- /dev/null
+++ b/src/isn.abst/HappyIdOwner.cs
@@ -0,0 +1,35 @@
+using Newtonsoft.Json;
+
+namespace isnd.Data.Catalog
+{
+ public class HappyIdOwner
+ {
+ public HappyIdOwner(string id)
+ {
+ this.id = id;
+ }
+
+
+ private string id;
+
+ public string GetId() { return id; }
+
+ public override bool Equals(object obj)
+ {
+ if (obj!=null)
+ {
+ if (GetType().IsAssignableFrom(obj.GetType()))
+ {
+ var rpobj = (HappyIdOwner) obj;
+ return this.id == rpobj.id;
+ }
+ }
+ return base.Equals(obj);
+ }
+
+ public override int GetHashCode()
+ {
+ return id.GetHashCode();
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/isn.abst/Resource.cs b/src/isn.abst/Resource.cs
new file mode 100644
index 0000000..ab069a5
--- /dev/null
+++ b/src/isn.abst/Resource.cs
@@ -0,0 +1,24 @@
+using isnd.Data.Catalog;
+using Newtonsoft.Json;
+
+namespace isn.Abstract
+{
+ public class Resource : HappyIdOwner
+ {
+ public Resource(string id, string typename) : base(id)
+ {
+ Type = typename;
+ Id = id;
+ }
+
+ [JsonProperty("@id")]
+ public string Id {get; set; }
+
+ [JsonProperty("@type")]
+ public string Type {get; set; }
+
+ [JsonProperty("comment")]
+ public string Comment {get; set; }
+
+ }
+}
\ No newline at end of file
diff --git a/src/isn.abst/SafeNameAttribute.cs b/src/isn.abst/SafeNameAttribute.cs
new file mode 100644
index 0000000..6f4596e
--- /dev/null
+++ b/src/isn.abst/SafeNameAttribute.cs
@@ -0,0 +1,20 @@
+using System;
+using System.ComponentModel.DataAnnotations;
+using System.Linq;
+
+namespace isnd.Attributes
+{
+ public class SafeNameAttribute : ValidationAttribute
+ {
+ public override bool IsValid(object value)
+ {
+ if (!(value is string))
+ return false;
+ string str = value as string;
+ if (str.Length>126) return false;
+ if (str.Any(c => !char.IsLetterOrDigit(c)
+ && !"-_.".Contains(c))) return false;
+ return true;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/isn.abst/isn.abst.csproj b/src/isn.abst/isn.abst.csproj
new file mode 100644
index 0000000..84b9983
--- /dev/null
+++ b/src/isn.abst/isn.abst.csproj
@@ -0,0 +1,14 @@
+
+
+ 1.0.1
+ 1.0.7
+ net7.0
+ NETSDK1138
+ 1.0.7.0
+ 1.0.7.0
+ 1.0.7+Branch.main.Sha.3695c1742965d93eba0ad851656cfaa3e44ba327
+
+
+
+
+
\ No newline at end of file
diff --git a/src/isn/Constants.cs b/src/isn/Constants.cs
new file mode 100644
index 0000000..e68c94c
--- /dev/null
+++ b/src/isn/Constants.cs
@@ -0,0 +1,7 @@
+namespace isn
+{
+ internal static class Constants
+ {
+ internal const string ClientVersion = "isn v1.0";
+ }
+}
diff --git a/src/isn/IDataProtector.cs b/src/isn/IDataProtector.cs
new file mode 100644
index 0000000..d91ea23
--- /dev/null
+++ b/src/isn/IDataProtector.cs
@@ -0,0 +1,47 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+
+namespace isn
+{
+ public interface IDataProtector
+ {
+ string Protect(string data);
+ string UnProtect(string data);
+ }
+
+ public class DefaultDataProtector : IDataProtector
+ {
+ private byte delta = 145;
+
+ public DefaultDataProtector()
+ {
+
+ }
+
+ public string Protect(string data)
+ {
+ List protd = new List();
+ StringBuilder sb = new StringBuilder();
+ foreach (byte c in Encoding.UTF8.GetBytes(data))
+ {
+ protd.Add((byte) (c ^ delta));
+ }
+ return System.Convert.ToBase64String(protd.ToArray());
+ }
+
+ public string UnProtect(string data)
+ {
+ if (data==null) return null;
+ StringBuilder sb = new StringBuilder();
+ List unps = new List();
+
+ foreach (byte c in System.Convert.FromBase64CharArray(data.ToCharArray(),0,data.Length))
+ {
+ unps.Add((byte) (c ^ delta));
+ }
+ return Encoding.UTF8.GetString(unps.ToArray());
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/src/isn/IsnSourceSettings.cs b/src/isn/IsnSourceSettings.cs
new file mode 100644
index 0000000..bc2491c
--- /dev/null
+++ b/src/isn/IsnSourceSettings.cs
@@ -0,0 +1,9 @@
+namespace isn
+{
+ public class IsnSourceSettings
+ {
+ internal string Source { get; set; }
+
+ internal string[] Keys { get; set; }
+ }
+}
\ No newline at end of file
diff --git a/src/isn/IsndErrorMessage.cs b/src/isn/IsndErrorMessage.cs
new file mode 100644
index 0000000..fa6b062
--- /dev/null
+++ b/src/isn/IsndErrorMessage.cs
@@ -0,0 +1,8 @@
+namespace isn
+{
+ public class IsndErrorMessage
+ {
+ public int ecode { get; set; }
+ public string msg { get; set; }
+ }
+}
\ No newline at end of file
diff --git a/src/isn/Program.cs b/src/isn/Program.cs
new file mode 100644
index 0000000..8a543a6
--- /dev/null
+++ b/src/isn/Program.cs
@@ -0,0 +1,224 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using Mono.Options;
+using Newtonsoft.Json;
+
+namespace isn
+{
+ public partial class Program
+ {
+
+ public static void LoadConfig()
+ {
+ FileInfo cfgSettingIf = new FileInfo(_configFileName);
+ if (cfgSettingIf.Exists)
+ {
+ var json = File.ReadAllText(cfgSettingIf.FullName);
+ settings = JsonConvert.DeserializeObject(json);
+ if (settings.DefaultSourceKey == null)
+ {
+ Settings.DefaultSourceKey = settings.Sources.Keys.FirstOrDefault();
+ }
+ CurrentSource = settings.DefaultSourceKey;
+ }
+ }
+ static readonly OptionSet storeoptions = new OptionSet {
+ { "s|source=", "use source", val => CurrentSource = CurrentSource ?? val },
+ { "h|help", "show this message and exit", h => shouldShowPushHelp = h != null },
+ };
+
+ private static readonly string _configFileName =
+ Path.Combine(
+ Path.Combine(Environment.GetFolderPath(
+ Environment.SpecialFolder.UserProfile), ".isn"),
+ "config.json")
+ ;
+
+ public const string push = "push";
+ static readonly OptionSet options = new OptionSet {
+ { "h|help", "show this message and exit", h => shouldShowHelp = h != null },
+ { "v|version", "show soft version info and exit", h => shouldShowVersion = h != null }
+ };
+
+ static string apiKey;
+
+ static readonly OptionSet pushoptions = new OptionSet {
+ { "k|api-key=", "use api key", val => apiKey = apiKey ?? val },
+ { "p|store-api-key", "store used api key", val => storApiKey = val != null },
+ { "s|source=", "use source", val => CurrentSource = CurrentSource ?? val },
+ { "h|help", "show this message and exit", h => shouldShowPushHelp = h != null },
+ };
+ static readonly OptionSet sourceoptions = new OptionSet {
+ { "h|help", "show this message and exit", h => shouldShowSourceHelp = h != null },
+ };
+
+ static readonly OptionSet showOptions = new OptionSet {
+ { "h|help", "show this message and exit", h => shouldShowSourceHelp = h != null },
+ };
+
+ private static bool shouldShowHelp;
+ private static bool shouldShowVersion;
+ private static bool shouldShowSourceHelp;
+ private static bool shouldShowPushHelp;
+ private static string currentSource = null;
+ private static bool storApiKey = false;
+
+ static Settings settings = null;
+
+ public static Settings Settings
+ {
+ get
+ {
+ if (settings == null)
+ LoadConfig();
+ if (settings == null)
+ {
+ settings = new Settings
+ {
+ DataProtectionTitle = "isn",
+ Sources = new Dictionary()
+ };
+ }
+ return settings;
+ }
+ }
+
+ public static string CurrentSource { get => currentSource; set => currentSource = value; }
+
+ static int Main(string[] args)
+ {
+ var commandSet = new CommandSet("isn");
+ var srclst = new Command("list")
+ {
+ Run = sargs => SourceList(sargs)
+ };
+
+ var showconfig = new Command("config")
+ {
+ Run = sargs => ShowConfig()
+ };
+
+ var srcadd = new Command("add")
+ {
+ Run = sargs => SourceAdd(sargs)
+ };
+
+ var srcsetdef = new Command("set-default")
+ {
+ Run = sargs => SetDefaultSource(sargs?.FirstOrDefault())
+ };
+
+ var showCommand = new Command("show")
+ {
+ Run = sargs =>
+ {
+ var showCommandSet = new CommandSet("show")
+ {
+ showconfig
+ };
+ var pargs = showOptions.Parse(sargs);
+ if (shouldShowSourceHelp)
+ {
+ // output the options
+ Console.WriteLine("Sources Options:");
+ showOptions.WriteOptionDescriptions(Console.Out);
+ return;
+ }
+ showCommandSet.Run(pargs);
+ }
+ };
+
+ var srcCmd = new Command("sources")
+ {
+ Run = sargs =>
+ {
+
+ var sourcesCmdSet = new CommandSet("sources")
+ {
+ srclst,
+ srcadd,
+ srcsetdef
+ };
+
+
+ var pargs = sourceoptions.Parse(sargs);
+ if (shouldShowSourceHelp)
+ {
+ // output the options
+ Console.WriteLine("Sources Options:");
+ sourceoptions.WriteOptionDescriptions(Console.Out);
+ return;
+ }
+ sourcesCmdSet.Run(pargs);
+ }
+ };
+
+ var add = new Command("add")
+ {
+ Run = sargs => Add(sargs)
+ };
+
+ commandSet.Add(add);
+
+ var pushCmd = new Command(push)
+ {
+ Run = sargs =>
+ {
+ var pargs = pushoptions.Parse(sargs);
+ if (args.Count()==0) shouldShowPushHelp=true;
+ if (shouldShowPushHelp)
+ {
+ // output the options
+ Console.Error.WriteLine("Push Options:");
+ pushoptions.WriteOptionDescriptions(Console.Out);
+ return;
+ }
+ List reports = PushPkg(pargs);
+ }
+ };
+
+ var setapikey = new Command("set-api-key")
+ {
+ Run = sargs => StoreApiKey(sargs)
+ };
+
+ commandSet.Add(pushCmd);
+ commandSet.Add(setapikey);
+ commandSet.Add(srcCmd);
+ commandSet.Add(showCommand);
+
+ List extra;
+ try
+ {
+ // parse the command line
+ extra = options.Parse(args);
+ }
+ catch (OptionException e)
+ {
+ Console.Error.Write("isn: ");
+ Console.Error.WriteLine(e.Message);
+ Console.Error.WriteLine("Try `isn --help' for more information.");
+ return 2;
+ }
+
+
+ if (shouldShowHelp)
+ {
+ // output the options
+ Console.WriteLine("Options:");
+ options.WriteOptionDescriptions(Console.Out);
+ return 1;
+ }
+
+ if (shouldShowVersion)
+ {
+ Console.WriteLine("isn version " + GitVersionInformation.AssemblySemFileVer);
+ }
+
+ return commandSet.Run(args);
+
+ }
+ }
+}
diff --git a/src/isn/Settings.cs b/src/isn/Settings.cs
new file mode 100644
index 0000000..2bd0def
--- /dev/null
+++ b/src/isn/Settings.cs
@@ -0,0 +1,55 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+
+namespace isn
+{
+ public class SourceSettings
+ {
+ public string ApiKey { get; set; }
+ public string Alias { get; set; }
+
+ public string GetClearApiKey()
+ {
+ return Protector.UnProtect(ApiKey);
+ }
+ public void SetApiKey(string key)
+ {
+ ApiKey = Protector.Protect(key);
+ }
+
+ public static IDataProtector Protector { get; set; } = new DefaultDataProtector();
+ }
+
+
+
+ public class Settings
+ {
+ public string DataProtectionTitle { get; set; }
+ public Dictionary Sources { get; set; }
+
+ public bool AutoUpdateApiKey { get; set; } = false;
+
+ private string defSourceKey;
+
+ ///
+ /// Default source by its alias
+ ///
+ ///
+ public string DefaultSourceKey
+ {
+ get => defSourceKey;
+ set
+ {
+ if (!Sources.ContainsKey(value))
+ {
+ Sources[value]=new SourceSettings
+ {
+ Alias = defSourceKey
+ };
+ }
+ defSourceKey = value;
+ }
+ }
+ }
+}
diff --git a/src/isn/SourceHelpers.cs b/src/isn/SourceHelpers.cs
new file mode 100644
index 0000000..ba8762b
--- /dev/null
+++ b/src/isn/SourceHelpers.cs
@@ -0,0 +1,27 @@
+using System;
+using System.IO;
+using System.Net;
+using System.Net.Http;
+using System.Threading.Tasks;
+using Newtonsoft.Json;
+using isn.Abstract;
+
+namespace isn
+{
+ public static class SourceHelpers
+ {
+ public static ApiIndexViewModel GetServerResources(string url)
+ {
+ HttpClient client = new HttpClient();
+ ApiIndexViewModel result = null;
+ // var json = await client.GetStringAsync(new System.Uri(url));
+
+ Task.Run(async ()=> {
+ var response = await client.GetStringAsync(url);
+ result = JsonConvert.DeserializeObject(response);
+ }).Wait();
+
+ return result;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/isn/UploadFilesToServerUsingHttpClient.cs b/src/isn/UploadFilesToServerUsingHttpClient.cs
new file mode 100644
index 0000000..ce51297
--- /dev/null
+++ b/src/isn/UploadFilesToServerUsingHttpClient.cs
@@ -0,0 +1,59 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Net;
+using System.Net.Http;
+using System.Threading.Tasks;
+
+namespace isn
+{
+ public static class UploadFilesToServerUsingHttpClient
+ {
+ public static PushReport UploadFilesToServer(this HttpClient client, Uri uri, FileInfo fi,
+ string apikey)
+ {
+ return UploadFilesToServerAsync(client, uri, fi, apikey).Result;
+ }
+
+ public static async Task UploadFilesToServerAsync(this HttpClient client, Uri uri, FileInfo fi,
+ string apikey)
+ {
+
+ client.DefaultRequestHeaders.Add("X-NuGet-Client-Version", Constants.ClientVersion);
+ client.DefaultRequestHeaders.Add("X-NuGet-ApiKey", apikey);
+
+ using (var multipartFormDataContent = new MultipartFormDataContent())
+ {
+ /* var values = new[]
+ {
+ new KeyValuePair("Id", Guid.NewGuid().ToString()),
+ new KeyValuePair("Key", "awesome"),
+ new KeyValuePair("From", "khalid@home.com")
+ //other values
+ };foreach (var keyValuePair in values)
+ {
+ multipartFormDataContent.Add(new StringContent(keyValuePair.Value),
+ String.Format("\"{0}\"", keyValuePair.Key));
+ } */
+ multipartFormDataContent.Add(new ByteArrayContent(File.ReadAllBytes(fi.FullName)),
+ '"' + "File" + '"',
+ '"' + fi.Name + '"');
+
+ var result = await client.PutAsync(uri, multipartFormDataContent);
+
+ if (result.IsSuccessStatusCode)
+ {
+ string report = await result.Content.ReadAsStringAsync();
+ Console.WriteLine(report);
+
+ }
+ else
+ {
+ string ereport = await result.Content.ReadAsStringAsync();
+ Console.WriteLine(ereport);
+ }
+ return new PushReport();
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/isn/commands/PushCommand.cs b/src/isn/commands/PushCommand.cs
new file mode 100644
index 0000000..bf4e08a
--- /dev/null
+++ b/src/isn/commands/PushCommand.cs
@@ -0,0 +1,68 @@
+using System;
+using System.IO;
+using System.Linq;
+using System.Net;
+using System.Net.Http;
+using System.Threading.Tasks;
+using Newtonsoft.Json;
+
+namespace isn
+{
+ public static class PushCommand
+ {
+ static public PushReport Run(string pkg, string source, string apikey)
+ {
+ if (source == null) source = Program.Settings.DefaultSourceKey;
+ if (source == null) throw new InvalidOperationException("source is null");
+ if (Program.Settings.Sources.Values.Any(s=>s.Alias==source))
+ source = Program.Settings.Sources.First(s=>s.Value.Alias==source).Key;
+ if (source==null) throw new InvalidOperationException("source is invalid");
+
+ var resources = SourceHelpers.GetServerResources(source);
+ if (resources.Resources == null)
+ throw new InvalidOperationException("source gave no resource");
+ if (!resources.Resources.Any(res => res.Type == "PackagePublish/2.0.0"))
+ throw new InvalidOperationException("Source won't serve the expected push command");
+ var pubRes = resources.Resources.First(res => res.Type == "PackagePublish/2.0.0");
+ FileInfo fi = new FileInfo(pkg);
+ if (!fi.Exists)
+ {
+ var report = new PushReport
+ {
+ PkgName = fi.Name,
+ Message = "Le fichier n'existe pas"
+ };
+ return report;
+ }
+ using (var client = new HttpClient())
+ try
+ {
+ Console.WriteLine("Connecting to "+ pubRes.Id);
+ return client.UploadFilesToServer(new Uri(pubRes.Id), fi, apikey);
+ }
+ catch (HttpRequestException hrex)
+ {
+ var report = new PushReport
+ {
+ PkgName = fi.Name,
+ Message = "HttpRequest: " + hrex.Message,
+ StackTrace = hrex.StackTrace,
+ StatusCode = hrex.HResult.ToString()
+ };
+ Console.Error.WriteLine(hrex.Message);
+ return report;
+ }
+ catch (Exception ex)
+ {
+ var report = new PushReport
+ {
+ PkgName = fi.Name,
+ Message = ex.Message,
+ StackTrace = ex.StackTrace
+ };
+ Console.Error.WriteLine(ex.Message);
+ return report;
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/isn/commands/push.cs b/src/isn/commands/push.cs
new file mode 100644
index 0000000..58daf08
--- /dev/null
+++ b/src/isn/commands/push.cs
@@ -0,0 +1,35 @@
+using System;
+using System.Collections.Generic;
+using System.Threading.Tasks;
+using Newtonsoft.Json;
+
+
+namespace isn
+{
+
+ partial class Program
+ {
+ public static List PushPkg(IEnumerable pkgs)
+ {
+ List pushReports = new List();
+
+ foreach (string pkg in pkgs)
+ {
+ var report = PushCommand.Run(pkg, CurrentSource, apiKey);
+ Console.WriteLine(report.ToDoc());
+ pushReports.Add(report);
+ }
+ if (storApiKey)
+ {
+ EnsureKeyStored();
+ }
+ return pushReports;
+ }
+
+ private static object Add(IEnumerable str)
+ {
+ throw new NotImplementedException();
+ }
+
+ }
+}
\ No newline at end of file
diff --git a/src/isn/commands/set-api-key.cs b/src/isn/commands/set-api-key.cs
new file mode 100644
index 0000000..7b2f018
--- /dev/null
+++ b/src/isn/commands/set-api-key.cs
@@ -0,0 +1,75 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using Newtonsoft.Json;
+
+
+namespace isn
+{
+
+ partial class Program
+ {
+
+ private static void StoreApiKey(IEnumerable storeArgs)
+ {
+ var args = storeoptions.Parse(storeArgs);
+ if (shouldShowPushHelp)
+ {
+ // output the options
+ Console.Error.WriteLine("Push Options:");
+ storeoptions.WriteOptionDescriptions(Console.Out);
+ }
+ else
+ {
+ apiKey = args[0];
+ EnsureKeyStored();
+ }
+ }
+
+ public static void EnsureKeyStored()
+ {
+ if (CurrentSource == null)
+ {
+ if (Settings.DefaultSourceKey == null)
+ return;
+ CurrentSource = Settings.DefaultSourceKey;
+ }
+
+ if (Settings.Sources.ContainsKey(CurrentSource))
+ {
+ if (apiKey == null)
+ {
+ // Une suppression
+ Settings.Sources.Remove(CurrentSource);
+ if (Settings.DefaultSourceKey == CurrentSource) Settings.DefaultSourceKey = null;
+ }
+ else
+ {
+ // Une mise À jour
+ Settings.Sources[CurrentSource].SetApiKey(apiKey);
+ if (Settings.DefaultSourceKey == null) Settings.DefaultSourceKey = CurrentSource;
+ }
+ }
+ else if (apiKey != null)
+ {
+ // une addition
+ var setting = new SourceSettings ();
+ setting.SetApiKey(apiKey);
+ Settings.Sources.Add(CurrentSource, setting);
+ }
+ SaveConfig();
+ }
+ public static void SaveConfig()
+ {
+ FileInfo cfgSettingIf = new FileInfo(_configFileName);
+ if (!cfgSettingIf.Directory.Exists) cfgSettingIf.Directory.Create();
+ File.WriteAllText(
+ cfgSettingIf.FullName,
+ JsonConvert.SerializeObject(
+ Settings,
+ Formatting.Indented
+ ));
+
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/isn/commands/show.config.cs b/src/isn/commands/show.config.cs
new file mode 100644
index 0000000..ff00e36
--- /dev/null
+++ b/src/isn/commands/show.config.cs
@@ -0,0 +1,19 @@
+using System;
+using System.Collections.Generic;
+using Newtonsoft.Json;
+
+
+namespace isn
+{
+
+ partial class Program
+ {
+
+ private static void ShowConfig()
+ {
+ Console.WriteLine(JsonConvert.SerializeObject(Settings, Formatting.Indented));
+
+ }
+
+ }
+}
\ No newline at end of file
diff --git a/src/isn/commands/sources.add.cs b/src/isn/commands/sources.add.cs
new file mode 100644
index 0000000..90d2aed
--- /dev/null
+++ b/src/isn/commands/sources.add.cs
@@ -0,0 +1,25 @@
+
+using System;
+using System.Collections.Generic;
+using Newtonsoft.Json;
+
+namespace isn
+{
+
+ partial class Program
+ {
+ private static void SourceAdd(IEnumerable str)
+ {
+ foreach (string arg in str)
+ {
+ if (Settings.Sources.ContainsKey(arg))
+ {
+ SourceSettings setting = Settings.Sources[arg];
+ throw new InvalidOperationException
+ (setting.Alias);
+ }
+ throw new NotImplementedException();
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/isn/commands/sources.list.cs b/src/isn/commands/sources.list.cs
new file mode 100644
index 0000000..6a1239a
--- /dev/null
+++ b/src/isn/commands/sources.list.cs
@@ -0,0 +1,20 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using Newtonsoft.Json;
+
+namespace isn
+{
+ partial class Program
+ {
+ private static void SourceList(IEnumerable sargs)
+ {
+ IEnumerable spec = sargs.Count()>0 ? sargs : Settings.Sources.Keys;
+ foreach (string arg in spec)
+ {
+ SourceSettings setting = Settings.Sources[arg];
+ Console.WriteLine(JsonConvert.SerializeObject(setting));
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/isn/commands/sources.set-default.cs b/src/isn/commands/sources.set-default.cs
new file mode 100644
index 0000000..8a691b3
--- /dev/null
+++ b/src/isn/commands/sources.set-default.cs
@@ -0,0 +1,21 @@
+using System;
+using System.Linq;
+
+namespace isn
+{
+
+ partial class Program
+ {
+ private static void SetDefaultSource(string arg)
+ {
+ SourceSettings settings =
+ Settings.Sources.ContainsKey(arg) ?
+ Settings.Sources[arg] :
+ Settings.Sources.Values.FirstOrDefault((s)=> s.Alias == arg) ;
+ if (settings==null) throw new InvalidOperationException(arg);
+ Settings.DefaultSourceKey = arg;
+ SaveConfig();
+ }
+
+ }
+}
\ No newline at end of file
diff --git a/src/isn/isn.csproj b/src/isn/isn.csproj
new file mode 100644
index 0000000..2708c47
--- /dev/null
+++ b/src/isn/isn.csproj
@@ -0,0 +1,24 @@
+
+
+ Exe
+ net7.0
+ nuget_cli
+ 45b74c62-05bc-4603-95b4-3e80ae2fdf50
+ 1.0.7
+ 1.0.1
+ true
+ WTFPL
+ true
+ NETSDK1138
+ 1.0.7.0
+ 1.0.7.0
+ 1.0.7+Branch.main.Sha.3695c1742965d93eba0ad851656cfaa3e44ba327
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/isn/model/PushReport.cs b/src/isn/model/PushReport.cs
new file mode 100644
index 0000000..d8e082e
--- /dev/null
+++ b/src/isn/model/PushReport.cs
@@ -0,0 +1,30 @@
+using System.Net;
+using System.Text;
+
+namespace isn
+{
+ public class PushReport
+ {
+ public string PkgName { get; set; }
+ public bool Executed { get; internal set; }
+ public bool OK { get; internal set; }
+ public bool AlreadyPresent { get; internal set; }
+ public string Message { get; internal set; }
+ public string StatusCode { get; internal set; }
+ public string StackTrace { get; internal set; }
+
+ public string ToDoc()
+ {
+ StringBuilder sb = new StringBuilder($"= Pushing {PkgName}\n\n");
+ if (Executed) sb.AppendLine("* Executed");
+ if (OK) sb.AppendLine("* OK");
+ if (!string.IsNullOrWhiteSpace(Message))
+ sb.AppendLine(Message);
+ if (!string.IsNullOrWhiteSpace(StatusCode))
+ sb.AppendLine($"* Status Code : ");
+ if (!string.IsNullOrWhiteSpace(StackTrace))
+ sb.AppendLine($"* StackTrace : ");
+ return sb.ToString();
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/isnd/Authorisation/ValidApiKeyRequirement.cs b/src/isnd/Authorisation/ValidApiKeyRequirement.cs
new file mode 100644
index 0000000..acbac75
--- /dev/null
+++ b/src/isnd/Authorisation/ValidApiKeyRequirement.cs
@@ -0,0 +1,8 @@
+using Microsoft.AspNetCore.Authorization;
+
+namespace isnd.Authorization
+{
+ internal class ValidApiKeyRequirement : IAuthorizationRequirement
+ {
+ }
+}
\ No newline at end of file
diff --git a/src/isnd/Authorisation/ValidApiKeyRequirementHandler.cs b/src/isnd/Authorisation/ValidApiKeyRequirementHandler.cs
new file mode 100644
index 0000000..846088c
--- /dev/null
+++ b/src/isnd/Authorisation/ValidApiKeyRequirementHandler.cs
@@ -0,0 +1,13 @@
+using System.Threading.Tasks;
+using Microsoft.AspNetCore.Authorization;
+
+namespace isnd.Authorization
+{
+ internal class ValidApiKeyRequirementHandler : AuthorizationHandler
+ {
+ protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, ValidApiKeyRequirement requirement)
+ {
+ throw new System.NotImplementedException();
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/isnd/Constants.cs b/src/isnd/Constants.cs
new file mode 100644
index 0000000..f748334
--- /dev/null
+++ b/src/isnd/Constants.cs
@@ -0,0 +1,9 @@
+namespace isnd
+{
+ public static class IsndConstants
+ {
+ public const string AdministratorRoleName = "Admin";
+ public const string RequireAdminPolicyName = "RequireAdministratorRole";
+ public const string RequireValidApiKey = "RequireValideApiKey";
+ }
+}
\ No newline at end of file
diff --git a/src/isnd/Controllers/AccountController.cs b/src/isnd/Controllers/AccountController.cs
new file mode 100644
index 0000000..e367166
--- /dev/null
+++ b/src/isnd/Controllers/AccountController.cs
@@ -0,0 +1,258 @@
+// Copyright (c) Brock Allen & Dominick Baier. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information.
+
+using Microsoft.AspNetCore.Authentication;
+using Microsoft.AspNetCore.Authorization;
+using Microsoft.AspNetCore.Http;
+using Microsoft.AspNetCore.Identity;
+using Microsoft.AspNetCore.Mvc;
+using Microsoft.Extensions.Options;
+using isnd.Data;
+using isnd.Data.Roles;
+using System;
+using System.Linq;
+using System.Security.Claims;
+using System.Threading.Tasks;
+
+namespace isnd.Controllers
+{
+ [AllowAnonymous]
+ public class AccountController : Controller
+ {
+ private readonly IAuthenticationSchemeProvider _schemeProvider;
+
+ private readonly SignInManager _signInManager;
+ private readonly UserManager _userManager;
+ private readonly AdminStartupList _startupAdminList;
+
+ public AccountController(
+ IAuthenticationSchemeProvider schemeProvider,
+ SignInManager signInManager,
+ UserManager userManager,
+ IOptions startupAdminListConfig )
+ {
+ _schemeProvider = schemeProvider;
+ _signInManager = signInManager;
+ _userManager = userManager;
+ _startupAdminList = startupAdminListConfig.Value;
+ }
+
+ ///
+ /// Entry point into the login workflow
+ ///
+ [HttpGet]
+ public async Task Login(string returnUrl)
+ {
+ // build a model so we know what to show on the login page
+ var vm = await BuildLoginViewModelAsync(returnUrl);
+
+ if (vm.IsExternalLoginOnly)
+ {
+ // we only have one option for logging in and it's an external provider
+ return RedirectToAction("Challenge", "External", new { scheme = vm.ExternalLoginScheme, returnUrl });
+ }
+
+ return View(vm);
+ }
+
+ ///
+ /// Handle postback from username/password login
+ ///
+ [HttpPost]
+ [ValidateAntiForgeryToken]
+ public async Task Login(LoginInputModel model, string button)
+ {
+
+ // the user clicked the "cancel" button
+ if (button != "login")
+ {
+
+ // since we don't have a valid context, then we just go back to the home page
+ return Redirect("~/");
+ }
+
+ if (ModelState.IsValid)
+ {
+ // validate username/password
+ var user = await _userManager.FindByNameAsync(model.Username);
+ var signResult = await _signInManager.CheckPasswordSignInAsync(user, model.Password, true);
+
+ if (signResult.Succeeded)
+ {
+
+ // only set explicit expiration here if user chooses "remember me".
+ // otherwise we rely upon expiration configured in cookie middleware.
+ AuthenticationProperties props = null;
+ if (AccountOptions.AllowRememberLogin && model.RememberLogin)
+ {
+ props = new AuthenticationProperties
+ {
+ IsPersistent = true,
+ ExpiresUtc = DateTimeOffset.UtcNow.Add(AccountOptions.RememberMeLoginDuration)
+ };
+ };
+
+ await _signInManager.SignInAsync(user, model.RememberLogin && AccountOptions.AllowRememberLogin);
+ if (Url.IsLocalUrl(model.ReturnUrl))
+ {
+ return Redirect(model.ReturnUrl);
+ }
+ else if (string.IsNullOrEmpty(model.ReturnUrl))
+ {
+ return Redirect("~/");
+ }
+ else
+ {
+ // user might have clicked on a malicious link - should be logged
+ throw new Exception("invalid return URL");
+ }
+ }
+
+ ModelState.AddModelError(string.Empty, AccountOptions.InvalidCredentialsErrorMessage);
+ }
+
+ // something went wrong, show form with error
+ var vm = await BuildLoginViewModelAsync(model);
+ return View(vm);
+ }
+
+
+ ///
+ /// Show logout page
+ ///
+ [HttpGet]
+ public async Task Logout(string logoutId)
+ {
+ // build a model so the logout page knows what to display
+ var vm = BuildLogoutViewModel(logoutId);
+
+ if (vm.ShowLogoutPrompt == false)
+ {
+ // if the request for logout was properly authenticated from IdentityServer, then
+ // we don't need to show the prompt and can just log the user out directly.
+ return await Logout(vm);
+ }
+
+ return View(vm);
+ }
+
+ ///
+ /// Handle logout page postback
+ ///
+ [HttpPost]
+ [ValidateAntiForgeryToken]
+ public async Task Logout(LogoutInputModel model)
+ {
+ // build a model so the logged out page knows what to display
+ var vm = BuildLoggedOutViewModel(model.LogoutId);
+
+ if (User?.Identity.IsAuthenticated == true)
+ {
+ // delete local authentication cookie
+ await HttpContext.SignOutAsync();
+ }
+
+ // check if we need to trigger sign-out at an upstream identity provider
+ if (vm.TriggerExternalSignout)
+ {
+ // build a return URL so the upstream provider will redirect back
+ // to us after the user has logged out. this allows us to then
+ // complete our single sign-out processing.
+ string url = Url.Action("Logout", new { logoutId = vm.LogoutId });
+
+ // this triggers a redirect to the external provider for sign-out
+ return SignOut(new AuthenticationProperties { RedirectUri = url }, vm.ExternalAuthenticationScheme);
+ }
+
+ return View("LoggedOut", vm);
+ }
+
+ [HttpGet]
+ public IActionResult AccessDenied()
+ {
+ return new BadRequestObjectResult(403);
+ }
+
+
+ /*****************************************/
+ /* helper APIs for the AccountController */
+ /*****************************************/
+ private async Task BuildLoginViewModelAsync(string returnUrl)
+ {
+
+
+ var schemes = await _schemeProvider.GetAllSchemesAsync();
+
+ var providers = schemes
+ .Where(x => x.DisplayName != null)
+ .Select(x => new ExternalProvider
+ {
+ DisplayName = x.DisplayName ?? x.Name,
+ AuthenticationScheme = x.Name
+ }).ToList();
+
+ var allowLocal = true;
+
+
+ return new LoginViewModel
+ {
+ AllowRememberLogin = AccountOptions.AllowRememberLogin,
+ EnableLocalLogin = allowLocal && AccountOptions.AllowLocalLogin,
+ ReturnUrl = returnUrl,
+ ExternalProviders = providers.ToArray()
+ };
+ }
+
+ private async Task BuildLoginViewModelAsync(LoginInputModel model)
+ {
+ var vm = await BuildLoginViewModelAsync(model.ReturnUrl);
+ vm.Username = model.Username;
+ vm.RememberLogin = model.RememberLogin;
+ return vm;
+ }
+
+ private LogoutViewModel BuildLogoutViewModel(string logoutId)
+ {
+ var vm = new LogoutViewModel { LogoutId = logoutId, ShowLogoutPrompt = AccountOptions.ShowLogoutPrompt };
+
+ if (User?.Identity.IsAuthenticated != true)
+ {
+ // if the user is not authenticated, then just show logged out page
+ vm.ShowLogoutPrompt = false;
+ return vm;
+ }
+
+ // show the logout prompt. this prevents attacks where the user
+ // is automatically signed out by another malicious web page.
+ return vm;
+ }
+
+ private LoggedOutViewModel BuildLoggedOutViewModel(string logoutId)
+ {
+ var vm = new LoggedOutViewModel
+ {
+ AutomaticRedirectAfterSignOut = AccountOptions.AutomaticRedirectAfterSignOut,
+ LogoutId = logoutId
+ };
+ return vm;
+ }
+
+ [Authorize]
+ public async Task GetAdminrole()
+ {
+ string username = User.FindFirstValue(ClaimTypes.NameIdentifier);
+ if (_startupAdminList.Users.Contains(username))
+ {
+ var user = await _userManager.FindByNameAsync(username);
+ var roles = await _userManager.GetRolesAsync(user);
+ if (!roles.Contains(IsndConstants.AdministratorRoleName))
+ {
+ await _userManager.AddToRoleAsync(user, IsndConstants.AdministratorRoleName);
+
+ }
+ return Ok();
+ }
+ return BadRequest();
+ }
+ }
+}
diff --git a/src/isnd/Controllers/ApiKeysController.cs b/src/isnd/Controllers/ApiKeysController.cs
new file mode 100644
index 0000000..bbc2d21
--- /dev/null
+++ b/src/isnd/Controllers/ApiKeysController.cs
@@ -0,0 +1,145 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Security.Claims;
+using System.Threading.Tasks;
+using Microsoft.AspNetCore.Authorization;
+using Microsoft.AspNetCore.DataProtection;
+using Microsoft.AspNetCore.Identity;
+using Microsoft.AspNetCore.Mvc;
+using Microsoft.AspNetCore.Mvc.Rendering;
+using Microsoft.EntityFrameworkCore;
+using Microsoft.Extensions.Options;
+using isnd.Data;
+using isnd.Entities;
+using isnd.Data.ApiKeys;
+
+
+namespace isnd.Controllers
+{
+ [Authorize]
+ public class ApiKeysController : Controller
+ {
+ private readonly ApplicationDbContext dbContext;
+ private readonly IsndSettings isndSettings;
+ private readonly UserManager _userManager;
+
+ private readonly IDataProtector protector;
+ public ApiKeysController(ApplicationDbContext dbContext,
+ IOptions isndSettingsOptions,
+ IDataProtectionProvider provider,
+ UserManager userManager)
+ {
+ this.dbContext = dbContext;
+ this.isndSettings = isndSettingsOptions.Value;
+ protector = provider.CreateProtector(isndSettings.ProtectionTitle);
+ _userManager = userManager;
+ }
+
+ [HttpGet]
+ public async Task Index()
+ {
+ List index = await GetUserKeys().ToListAsync();
+ IndexModel model = new IndexModel { ApiKey = index };
+ ViewData["Title"] = "Index";
+ return View("Index", model);
+ }
+
+ [HttpGet]
+ public async Task Create()
+ {
+ var userId = User.FindFirstValue(ClaimTypes.NameIdentifier);
+ var user = await _userManager.FindByIdAsync(userId);
+ ViewBag.UserId = new SelectList(new List { user });
+ return View(new CreateModel{ });
+ }
+
+ [HttpPost]
+ public async Task Create(CreateModel model)
+ {
+ string userid = User.FindFirstValue(ClaimTypes.NameIdentifier);
+ IQueryable userKeys = GetUserKeys();
+ if (userKeys.Count() >= isndSettings.MaxUserKeyCount)
+ {
+ ModelState.AddModelError(null, "Maximum key count reached");
+ return View();
+ }
+ ApiKey newKey = new ApiKey { UserId = userid, Name = model.Name,
+ CreationDate = DateTime.Now };
+ _ = dbContext.ApiKeys.Add(newKey);
+ _ = await dbContext.SaveChangesAsync();
+ return View("Details", new DetailModel { Name = newKey.Name,
+ ProtectedValue = protector.Protect(newKey.Id),
+ ApiKey = newKey });
+ }
+
+ [HttpGet]
+ public async Task Delete(string id)
+ {
+ string userid = User.FindFirstValue(ClaimTypes.NameIdentifier);
+ ApiKey key = await dbContext.ApiKeys.FirstOrDefaultAsync(k => k.Id == id && k.UserId == userid);
+ return View(new DeleteModel { ApiKey = key });
+
+ }
+
+ [HttpPost]
+ public async Task Delete(DeleteModel model)
+ {
+ string userid = User.FindFirstValue(ClaimTypes.NameIdentifier);
+ ApiKey key = dbContext.ApiKeys.FirstOrDefault(k => k.Id == model.ApiKey.Id && k.UserId == userid);
+ if (key == null)
+ {
+ ModelState.AddModelError(null, "Key not found");
+ return View();
+ }
+ _ = dbContext.ApiKeys.Remove(key);
+ _ = await dbContext.SaveChangesAsync();
+ return View("Index", new IndexModel { ApiKey = GetUserKeys().ToList() } );
+ }
+
+ public async Task Details(string id)
+ {
+ string userid = User.FindFirstValue(ClaimTypes.NameIdentifier);
+ ApiKey key = await dbContext.ApiKeys.FirstOrDefaultAsync(k => k.Id == id && k.UserId == userid);
+ if (key == null)
+ {
+ ModelState.AddModelError(null, "Key not found");
+ return View();
+ }
+ return View("Details", new DetailModel { ApiKey = key, Name = key.Name, ProtectedValue = protector.Protect(key.Id)});
+
+ }
+
+ public async Task Edit(string id)
+ {
+
+ EditModel edit = new EditModel();
+ string userId = User.FindFirstValue(ClaimTypes.NameIdentifier);
+
+ var user = await _userManager.FindByIdAsync(userId);
+
+ edit.ApiKey = await GetUserKeys().SingleOrDefaultAsync(k =>
+ k.UserId == userId && k.Id == id);
+ ViewBag.UserId = new SelectList(new List { user });
+
+ return View(edit);
+ }
+
+ [HttpPost]
+ public async Task Edit(EditModel model)
+ {
+ string userId = User.FindFirstValue(ClaimTypes.NameIdentifier);
+
+ var apiKey = await dbContext.ApiKeys.SingleOrDefaultAsync(k => k.UserId == userId && k.Id == model.ApiKey.Id);
+ apiKey.Name = model.ApiKey.Name;
+ apiKey.ValidityPeriodInDays = model.ApiKey.ValidityPeriodInDays;
+ await dbContext.SaveChangesAsync();
+ return View("Details", new DetailModel { ApiKey = apiKey });
+ }
+
+ public IQueryable GetUserKeys()
+ {
+ return dbContext.ApiKeys.Include(k => k.User).Where(k => k.User.UserName == User.Identity.Name);
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/isnd/Controllers/HomeController.cs b/src/isnd/Controllers/HomeController.cs
new file mode 100644
index 0000000..45512c1
--- /dev/null
+++ b/src/isnd/Controllers/HomeController.cs
@@ -0,0 +1,78 @@
+using Microsoft.AspNetCore.Hosting;
+using Microsoft.AspNetCore.Mvc;
+using Microsoft.Extensions.Logging;
+using isnd.Data;
+using System.Linq;
+using isnd.ViewModels;
+using Unleash;
+using System.Reflection;
+using Microsoft.EntityFrameworkCore;
+
+namespace isnd.Controllers
+{
+ ///
+ /// Home Controller
+ ///
+ public class HomeController : Controller
+ {
+ private readonly ApplicationDbContext _dbContext;
+ private readonly IUnleash _unleashĈlient;
+ ///
+ /// Home controller ctor
+ ///
+ ///
+ ///
+
+ public HomeController(
+ ApplicationDbContext dbContext,
+ IUnleash unleashĈlient)
+ {
+ _dbContext = dbContext;
+ _unleashĈlient = unleashĈlient;
+ }
+
+ public IActionResult Index()
+ {
+ return View(new HomeIndexViewModel{
+ PkgCount = _dbContext.Packages
+ .Where(p => p.Versions.Count > 0)
+ .Count(),
+ UnleashClient = _unleashĈlient
+ });
+ }
+
+ public IActionResult About()
+ {
+ ViewData["Message"] = "Your application description page.";
+
+ return View();
+ }
+
+ public IActionResult Contact()
+ {
+ ViewData["Message"] = "Your contact page.";
+ var ass = typeof(isn.Abstract.Resource).GetType().Assembly;
+ var attrs = ass.GetCustomAttributes(true);
+ // ass.GetCustomAttributes(true);
+ System.Reflection.AssemblyFileVersionAttribute v = (AssemblyFileVersionAttribute)
+ GetType().Assembly.GetCustomAttribute(typeof(AssemblyFileVersionAttribute));
+
+ return View();
+ }
+
+ public IActionResult Privacy()
+ {
+ ViewData["Message"] = "Your Privacy page.";
+
+ return View(ViewData);
+ }
+
+ public IActionResult Error()
+ {
+ return View();
+ }
+ }
+}
+
+
+
diff --git a/src/isnd/Controllers/NewUpdateController.cs b/src/isnd/Controllers/NewUpdateController.cs
new file mode 100644
index 0000000..045b1b0
--- /dev/null
+++ b/src/isnd/Controllers/NewUpdateController.cs
@@ -0,0 +1,18 @@
+using System;
+using Microsoft.AspNetCore.Authorization;
+using Microsoft.AspNetCore.Mvc;
+using isnd.Data;
+
+namespace isnd.Controllers
+{
+
+ // TODO Web hook CI
+ public class NewUpdateController : Controller
+ {
+ [Authorize(Policy = IsndConstants.RequireAdminPolicyName)]
+ public IActionResult NewRelease(NewReleaseInfo release)
+ {
+ throw new NotImplementedException("web hook");
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/isnd/Controllers/PackageVersionController.cs b/src/isnd/Controllers/PackageVersionController.cs
new file mode 100644
index 0000000..94c2fb5
--- /dev/null
+++ b/src/isnd/Controllers/PackageVersionController.cs
@@ -0,0 +1,54 @@
+using System.Linq;
+using System.Security.Claims;
+using System.Threading.Tasks;
+using Microsoft.AspNetCore.Authorization;
+using Microsoft.AspNetCore.Mvc;
+using Microsoft.EntityFrameworkCore;
+using isnd.Data;
+using isnd.ViewModels;
+using isnd.Helpers;
+using isnd.Interfaces;
+
+namespace isnd
+{
+ [AllowAnonymous]
+ public class PackageVersionController : Controller
+ {
+ private readonly ApplicationDbContext _context;
+ private readonly IPackageManager _pm;
+
+ public PackageVersionController(ApplicationDbContext context,
+ IPackageManager pm)
+ {
+ _context = context;
+ _pm = pm;
+ }
+
+ // GET: PackageVersion
+ public async Task Index(PackageVersionIndexViewModel model)
+ {
+ var applicationDbContext = _context.PackageVersions.Include(p => p.Package)
+ .Include(p => p.Package.Owner)
+ .Include(p => p.Package.Versions)
+ .Where(
+ p => (model.Prerelease || !p.IsPrerelease)
+ && ((model.PackageId == null) || p.PackageId.StartsWith(model.PackageId)));
+ model.Versions = await applicationDbContext.ToArrayAsync();
+ return View(model);
+ }
+
+ [Authorize]
+ public async Task Mines(PackageVersionIndexViewModel model)
+ {
+ var userId = User.FindFirstValue(ClaimTypes.NameIdentifier);
+ var applicationDbContext = _context.PackageVersions
+ .Include(p => p.Package).Where(
+ p => (string.IsNullOrEmpty(model.PackageId) || p.PackageId.StartsWith(model.PackageId))
+ && p.Package.OwnerId == userId);
+
+ model.Versions = await applicationDbContext.ToArrayAsync();
+
+ return View("Index", model);
+ }
+ }
+}
diff --git a/src/isnd/Controllers/Packages/ApiIndex.cs b/src/isnd/Controllers/Packages/ApiIndex.cs
new file mode 100644
index 0000000..003479a
--- /dev/null
+++ b/src/isnd/Controllers/Packages/ApiIndex.cs
@@ -0,0 +1,43 @@
+using Microsoft.AspNetCore.Mvc;
+using isnd.Entities;
+using isnd.Services;
+using isn.Abstract;
+using isn.abst;
+using isnd.Interfaces;
+using Unleash;
+using System.Linq;
+
+
+namespace isnd.Controllers
+{
+ ///
+ /// Api Controller
+ ///
+ public class ApiController : Controller
+ {
+ private readonly IPackageManager packageManager;
+ private readonly Resource[] resources;
+
+ ///
+ /// Api Controller Constructor
+ ///
+ ///
+ ///
+ public ApiController(IPackageManager pm, IUnleash unleashĈlient)
+ {
+ packageManager = pm;
+ resources = packageManager.GetResources(unleashĈlient).ToArray();
+ }
+
+ ///
+ /// API index
+ ///
+ ///
+ [HttpGet("~" + Constants.ApiVersion + "/index")]
+ public IActionResult ApiIndex()
+ {
+ return Ok(new ApiIndexViewModel(packageManager.CatalogBaseUrl){ Version = PackageManager.BASE_API_LEVEL, Resources = resources });
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/src/isnd/Controllers/Packages/PackagesController.AutoComplete.cs b/src/isnd/Controllers/Packages/PackagesController.AutoComplete.cs
new file mode 100644
index 0000000..7276542
--- /dev/null
+++ b/src/isnd/Controllers/Packages/PackagesController.AutoComplete.cs
@@ -0,0 +1,34 @@
+using isnd.Services;
+using isnd.Entities;
+using Microsoft.AspNetCore.Mvc;
+using isn.abst;
+
+namespace isnd.Controllers
+{
+ public partial class PackagesController
+ {
+
+ // GET /autocomplete?id=isn.protocol&prerelease=true
+ [HttpGet("~" + Constants.ApiVersion + ApiConfig.AutoComplete)]
+ public IActionResult AutoComplete(
+ string id,
+ string semVerLevel,
+ bool prerelease = false,
+ string packageType = null,
+ int skip = 0,
+ int take = 25)
+ {
+ if (take > maxTake)
+ {
+ ModelState.AddModelError("take", "Maximum exceeded");
+ }
+ if (semVerLevel != PackageManager.BASE_API_LEVEL)
+ {
+ ModelState.AddModelError("semVerLevel", PackageManager.BASE_API_LEVEL + " expected");
+ }
+ if (ModelState.ErrorCount > 0) return BadRequest(ModelState);
+
+ return Ok(packageManager.AutoComplete(id,skip,take,prerelease,packageType));
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/isnd/Controllers/Packages/PackagesController.Catalog.cs b/src/isnd/Controllers/Packages/PackagesController.Catalog.cs
new file mode 100644
index 0000000..33c5eec
--- /dev/null
+++ b/src/isnd/Controllers/Packages/PackagesController.Catalog.cs
@@ -0,0 +1,51 @@
+using System.Data.Common;
+using System.Linq;
+using System.Threading.Tasks;
+using isnd.Services;
+using isnd.Entities;
+using Microsoft.AspNetCore.Mvc;
+using System.Collections.Generic;
+using NuGet.Versioning;
+using isnd.Data.Packages.Catalog;
+using isnd.Data.Catalog;
+using isn.abst;
+
+namespace isnd.Controllers
+{
+ public partial class PackagesController
+ {
+
+ // https://docs.microsoft.com/en-us/nuget/api/catalog-resource#versioning
+ [HttpGet("~" + Constants.ApiVersion + ApiConfig.Catalog)]
+ public async Task CatalogIndex()
+ {
+ return Ok(await packageManager.GetCatalogIndexAsync());
+ }
+
+
+ [HttpGet("~" + Constants.ApiVersion + ApiConfig.Registration + "/{id}/{version?}")]
+ public async Task CatalogRegistration(string id, string version)
+ {
+ if (string.IsNullOrWhiteSpace(version))
+ {
+ var query = new Data.Catalog.RegistrationPageIndexQuery
+ {
+ Query = id,
+ Prerelease = true
+ };
+ var index = await packageManager.GetPackageRegistrationIndexAsync(query);
+ if (index == null) return NotFound();
+ // query.TotalHits = result.Items.Select(i=>i.Items.Length).Aggregate((a,b)=>a+b);
+ return Ok(index);
+ }
+ // return a Package
+ var leaf = await packageManager.GetCatalogEntryAsync(id, version, null);
+
+ if (null == leaf) return NotFound(new { id, version });
+ return Ok(leaf);
+ }
+
+
+
+ }
+}
\ No newline at end of file
diff --git a/src/isnd/Controllers/Packages/PackagesController.Delete.cs b/src/isnd/Controllers/Packages/PackagesController.Delete.cs
new file mode 100644
index 0000000..0b5ea62
--- /dev/null
+++ b/src/isnd/Controllers/Packages/PackagesController.Delete.cs
@@ -0,0 +1,25 @@
+using System.Threading.Tasks;
+using Microsoft.AspNetCore.Mvc;
+using isnd.Helpers;
+using isnd.Entities;
+using System.ComponentModel.DataAnnotations;
+using isnd.Attributes;
+using System.Security.Claims;
+using isn.abst;
+
+namespace isnd.Controllers
+{
+ public partial class PackagesController
+ {
+ [HttpDelete("~" + Constants.ApiVersion + ApiConfig.Package + "/{id}/{lower?}/{type?}")]
+ public async Task ApiDelete(
+ [FromRoute][SafeName][Required] string id,
+ [FromRoute][SafeName][Required] string lower,
+ [FromRoute] string type)
+ {
+ var uid = User.FindFirstValue(ClaimTypes.NameIdentifier);
+ var report = await packageManager.UserAskForPackageDeletionAsync(uid, id, lower, type);
+ return Ok(report);
+ }
+ }
+}
diff --git a/src/isnd/Controllers/Packages/PackagesController.GetPackage.cs b/src/isnd/Controllers/Packages/PackagesController.GetPackage.cs
new file mode 100644
index 0000000..a37ad28
--- /dev/null
+++ b/src/isnd/Controllers/Packages/PackagesController.GetPackage.cs
@@ -0,0 +1,55 @@
+using System.ComponentModel.DataAnnotations;
+using System.IO;
+using Microsoft.AspNetCore.Mvc;
+using isnd.Attributes;
+using isnd.Entities;
+using isn.abst;
+
+namespace isnd.Controllers
+{
+
+ public partial class PackagesController
+ {
+ // Web get the paquet
+ [HttpGet("~" + Constants.ApiVersion + ApiConfig.Package + "/{id}/{lower}/{idf}-{lowerf}."
+ + Constants.PaquetFileEstension)]
+ public IActionResult GetPackage(
+ [FromRoute][SafeName][Required] string id,
+ [FromRoute][SafeName][Required] string lower,
+ [FromRoute] string idf, [FromRoute] string lowerf)
+ {
+ var pkgpath = Path.Combine(isndSettings.PackagesRootDir,
+ id, lower, $"{id}-{lower}." + Constants.PaquetFileEstension
+ );
+
+ FileInfo pkgfi = new FileInfo(pkgpath);
+
+ if (!pkgfi.Exists)
+ {
+ return BadRequest("!pkgfi.Exists");
+ }
+ return File(pkgfi.OpenRead(), "application/zip; charset=binary");
+ }
+
+ // Web get spec
+ [HttpGet("~" + Constants.ApiVersion + Constants.SpecFileEstension + "/{id}/{lower}/{idf}-{lowerf}."
+ + Constants.SpecFileEstension)]
+ public IActionResult GetNuspec(
+ [FromRoute][SafeName][Required] string id,
+ [FromRoute][SafeName][Required] string lower,
+ [FromRoute][SafeName][Required] string idf,
+ [FromRoute][SafeName][Required] string lowerf)
+ {
+ var pkgpath = Path.Combine(isndSettings.PackagesRootDir,
+ id, lower, $"{id}." + Constants.SpecFileEstension);
+
+ FileInfo pkgfi = new FileInfo(pkgpath);
+ if (!pkgfi.Exists)
+ {
+ return BadRequest("!pkgfi.Exists");
+ }
+
+ return File(pkgfi.OpenRead(), "text/xml; charset=utf-8");
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/isnd/Controllers/Packages/PackagesController.GetVersions.cs b/src/isnd/Controllers/Packages/PackagesController.GetVersions.cs
new file mode 100644
index 0000000..c6be899
--- /dev/null
+++ b/src/isnd/Controllers/Packages/PackagesController.GetVersions.cs
@@ -0,0 +1,39 @@
+using Microsoft.AspNetCore.Mvc;
+using NuGet.Versioning;
+using isnd.Entities;
+using isn.abst;
+
+namespace isnd.Controllers
+{
+ public partial class PackagesController
+ {
+
+ public IActionResult GetVersions(
+ string id,
+ string lower,
+ bool prerelease = false,
+ string packageType = null,
+ int skip = 0,
+ int take = 25)
+ {
+ if (take > maxTake)
+ {
+ ModelState.AddModelError("take", "Maximum exceeded");
+ }
+ // NugetVersion
+ if (!NuGetVersion.TryParse(lower, out NuGetVersion parsedVersion))
+ {
+ ModelState.AddModelError("lower", "invalid version string");
+ }
+ if (!ModelState.IsValid)
+ {
+ return BadRequest(ModelState);
+ }
+ return Ok(new
+ {
+ versions = packageManager.GetVersions(
+ id, parsedVersion, prerelease, packageType, skip, take)
+ });
+ }
+ }
+}
diff --git a/src/isnd/Controllers/Packages/PackagesController.Put.cs b/src/isnd/Controllers/Packages/PackagesController.Put.cs
new file mode 100644
index 0000000..267926b
--- /dev/null
+++ b/src/isnd/Controllers/Packages/PackagesController.Put.cs
@@ -0,0 +1,221 @@
+
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.IO.Compression;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using Microsoft.AspNetCore.DataProtection;
+using Microsoft.AspNetCore.Mvc;
+using Microsoft.Extensions.Logging;
+using NuGet.Packaging.Core;
+using NuGet.Versioning;
+using isnd.Data;
+using isnd.Helpers;
+using isnd.Entities;
+using Microsoft.AspNetCore.Http;
+using isn.abst;
+using isnd.Data.Packages;
+using Microsoft.AspNetCore.Authorization;
+
+namespace isnd.Controllers
+{
+
+ public partial class PackagesController
+ {
+ // TODO [Authorize(Policy = IsndConstants.RequireValidApiKey)]
+ [HttpPut("~" + Constants.ApiVersion + ApiConfig.Package)]
+ public async Task Put()
+ {
+ try
+ {
+ var clientVersionId = Request.Headers["X-NuGet-Client-Version"];
+ string apiKey = Request.Headers["X-NuGet-ApiKey"][0];
+ ViewData["versionId"] = typeof(PackagesController).Assembly.FullName;
+ var files = new List();
+ ViewData["files"] = files;
+
+ var clearkey = protector.Unprotect(apiKey);
+ var apikey = dbContext.ApiKeys.SingleOrDefault(k => k.Id == clearkey);
+ if (apikey == null)
+ {
+ logger.LogError("403 : no api-key");
+ return Unauthorized();
+ }
+ Commit commit = new Commit
+ {
+ Action = PackageAction.PublishPackage,
+ TimeStamp = DateTime.Now
+ };
+ dbContext.Commits.Add(commit);
+
+ foreach (IFormFile file in Request.Form.Files)
+ {
+ string initpath = Path.Combine(Environment.GetEnvironmentVariable("TEMP") ??
+ Environment.GetEnvironmentVariable("TMP") ?? "/tmp",
+ $"isn-{Guid.NewGuid()}."+Constants.PaquetFileEstension);
+
+ using (FileStream fw = new FileStream(initpath, FileMode.Create))
+ {
+ file.CopyTo(fw);
+ }
+
+ using (FileStream fw = new FileStream(initpath, FileMode.Open))
+ {
+ var archive = new ZipArchive(fw);
+
+ var spec = archive.Entries.FirstOrDefault(e => e.FullName.EndsWith("." + Constants.SpecFileEstension));
+ if (spec == null) return BadRequest(new { error = "no " + Constants.SpecFileEstension + " from archive" });
+ string pkgpath;
+ NuGetVersion version;
+ string pkgid;
+ string fullpath;
+
+ using (var specstr = spec.Open())
+ {
+ NuspecCoreReader reader = new NuspecCoreReader(specstr);
+
+ string pkgdesc = reader.GetDescription();
+ var types = reader.GetPackageTypes();
+ pkgid = reader.GetId();
+ version = reader.GetVersion();
+
+ string pkgidpath = Path.Combine(isndSettings.PackagesRootDir,
+ pkgid);
+ pkgpath = Path.Combine(pkgidpath, version.ToFullString());
+ string name = $"{pkgid}-{version}."+Constants.PaquetFileEstension;
+ fullpath = Path.Combine(pkgpath, name);
+
+ var destpkgiddir = new DirectoryInfo(pkgidpath);
+ Package pkg = dbContext.Packages.SingleOrDefault(p => p.Id == pkgid);
+ if (pkg != null)
+ {
+ if (pkg.OwnerId != apikey.UserId)
+ {
+ return new ForbidResult();
+ }
+ pkg.Description = pkgdesc;
+ }
+ else
+ {
+ pkg = new Package
+ {
+ Id = pkgid,
+ Description = pkgdesc,
+ OwnerId = apikey.UserId,
+ LatestVersion = commit,
+ };
+ dbContext.Packages.Add(pkg);
+ }
+ if (!destpkgiddir.Exists) destpkgiddir.Create();
+
+ var source = new FileInfo(initpath);
+ var dest = new FileInfo(fullpath);
+ var destdir = new DirectoryInfo(dest.DirectoryName);
+ if (dest.Exists)
+ {
+ // La version existe sur le disque,
+ // mais si elle ne l'est pas en base de donnéés,
+ // on remplace la version sur disque.
+ var pkgv = dbContext.PackageVersions.Where(
+ v => v.PackageId == pkg.Id
+ );
+
+ if (pkgv !=null && pkgv.Count()==0)
+ {
+ dest.Delete();
+ }
+ else {
+ logger.LogWarning("400 : pkgversion:existant");
+ ModelState.AddModelError("pkgversion", "existant" );
+ return BadRequest(ModelState);
+ }
+ }
+ {
+ if (!destdir.Exists) destdir.Create();
+ source.MoveTo(fullpath);
+ files.Add(name);
+ string fullstringversion = version.ToFullString();
+ var pkgvers = dbContext.PackageVersions.Where
+ (v => v.PackageId == pkg.Id && v.FullString == fullstringversion);
+ if (pkgvers.Count() > 0)
+ {
+ foreach (var v in pkgvers.ToArray())
+ dbContext.PackageVersions.Remove(v);
+ }
+
+ // FIXME default type or null
+ if (types==null || types.Count==0)
+ dbContext.PackageVersions.Add
+ (new PackageVersion{
+
+ Package = pkg,
+ Major = version.Major,
+ Minor = version.Minor,
+ Patch = version.Patch,
+ Revision = version.Revision,
+ IsPrerelease = version.IsPrerelease,
+ FullString = version.ToFullString(),
+ Type = null,
+ LatestCommit = commit
+ });
+ else
+ foreach (var type in types)
+ {
+ var pkgver = new PackageVersion
+ {
+ Package = pkg,
+ Major = version.Major,
+ Minor = version.Minor,
+ Patch = version.Patch,
+ IsPrerelease = version.IsPrerelease,
+ FullString = version.ToFullString(),
+ Type = type.Name,
+ LatestCommit = commit
+ };
+ dbContext.PackageVersions.Add(pkgver);
+ }
+
+ await dbContext.SaveChangesAsync();
+ packageManager.ÛpdateCatalogForAsync(commit);
+
+ logger.LogInformation($"new paquet : {spec.Name}");
+ }
+ }
+ using (var shacrypto = System.Security.Cryptography.SHA512.Create())
+ {
+ using (var stream = System.IO.File.OpenRead(fullpath))
+ {
+ var hash = shacrypto.ComputeHash(stream);
+ var shafullname = fullpath + ".sha512";
+ var hashtext = Convert.ToBase64String(hash);
+ var hashtextbytes = Encoding.ASCII.GetBytes(hashtext);
+
+ using (var shafile = System.IO.File.OpenWrite(shafullname))
+ {
+ shafile.Write(hashtextbytes, 0, hashtextbytes.Length);
+ }
+ }
+ }
+ string nuspecfullpath = Path.Combine(pkgpath, pkgid + "." + Constants.SpecFileEstension);
+ FileInfo nfpi = new FileInfo(nuspecfullpath);
+
+ if (nfpi.Exists)
+ nfpi.Delete();
+ spec.ExtractToFile(nuspecfullpath);
+ }
+
+ }
+ return Ok(ViewData);
+ }
+ catch (Exception ex)
+ {
+ logger.LogError(ex.Message);
+ logger.LogError("Stack Trace: " + ex.StackTrace);
+ return new ObjectResult(new { ViewData, ex.Message })
+ { StatusCode = 500 };
+ }
+ }
+ }
+}
diff --git a/src/isnd/Controllers/Packages/PackagesController.Search.cs b/src/isnd/Controllers/Packages/PackagesController.Search.cs
new file mode 100644
index 0000000..bffc995
--- /dev/null
+++ b/src/isnd/Controllers/Packages/PackagesController.Search.cs
@@ -0,0 +1,28 @@
+
+using System;
+using System.Threading.Tasks;
+using Microsoft.AspNetCore.Mvc;
+using isnd.Entities;
+using isn.abst;
+
+namespace isnd.Controllers
+{
+
+ public partial class PackagesController
+ {
+ // GET {@id}?q={QUERY}&skip={SKIP}&take={TAKE}&prerelease={PRERELEASE}&semVerLevel={SEMVERLEVEL}&packageType={PACKAGETYPE}
+ [HttpGet("~" + Constants.ApiVersion + ApiConfig.Search)]
+ public IActionResult Search(
+ string q,
+ int skip = 0,
+ int take = 25,
+ bool prerelease = false,
+ string semVerLevel = null,
+ string packageType = null
+ )
+ {
+
+ throw new NotImplementedException();
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/isnd/Controllers/Packages/PackagesController.WebViews.cs b/src/isnd/Controllers/Packages/PackagesController.WebViews.cs
new file mode 100644
index 0000000..eb6c477
--- /dev/null
+++ b/src/isnd/Controllers/Packages/PackagesController.WebViews.cs
@@ -0,0 +1,92 @@
+using System;
+using System.Linq;
+using System.Threading.Tasks;
+using isnd.Data;
+using isnd.Data.Catalog;
+using isnd.Helpers;
+using isnd.ViewModels;
+using Microsoft.AspNetCore.Authorization;
+using Microsoft.AspNetCore.Mvc;
+using Microsoft.EntityFrameworkCore;
+
+namespace isnd.Controllers
+{
+
+ public partial class PackagesController
+ {
+ // Web search
+ public async Task Index(RegistrationPageIndexQuery model)
+ {
+ return View(new RegistrationPageIndexQueryAndResult{Query = model,
+ Result = await packageManager.SearchPackageAsync(model)});
+ }
+
+ public async Task Details(string pkgid)
+ {
+ if (pkgid == null)
+ {
+ return NotFound();
+ }
+
+ var packageVersion = dbContext.PackageVersions
+ .Include(p => p.Package)
+ .Where(m => m.PackageId == pkgid)
+ .OrderByDescending(p => p)
+ ;
+
+ if (packageVersion.Count() == 0)
+ {
+ return NotFound();
+ }
+ bool results = await packageVersion.AnyAsync();
+ var latest = await packageVersion.FirstAsync();
+
+ return View("Details", new PackageDetailViewModel
+ {
+ ExternalUrl = isndSettings.ExternalUrl,
+ latest = latest,
+ pkgid = pkgid,
+ totalHits = packageVersion.Count(),
+ data = packageVersion.Take(MAX_PKG_VERSION_LIST).ToArray()
+ });
+
+ }
+ const int MAX_PKG_VERSION_LIST = 50;
+
+ [Authorize]
+ public async Task Delete(string pkgid, string version, string pkgtype)
+ {
+ if (pkgid == null || version == null)
+ {
+ return NotFound();
+ }
+ // var report = await packageManager.DeletePackageAsync(id, lower, type);
+
+ var packageVersion = await dbContext.PackageVersions.Include(p => p.Package)
+ .FirstOrDefaultAsync(m => m.PackageId == pkgid
+ && m.FullString == version && m.Type == pkgtype);
+ if (packageVersion == null) return NotFound();
+ if (!User.IsOwner(packageVersion)) return Unauthorized();
+ var pkg = await packageManager.GetPackageAsync(pkgid, version, pkgtype);
+ return View(pkg);
+ }
+
+ // POST: PackageVersion/Delete/5
+ [HttpPost, ActionName("Delete")]
+ [ValidateAntiForgeryToken]
+ public async Task DeleteConfirmed(string PackageId, string FullString,
+ string Type)
+ {
+ PackageVersion packageVersion = await dbContext.PackageVersions
+ .Include(pv => pv.Package)
+ .FirstOrDefaultAsync(m => m.PackageId == PackageId
+ && m.FullString == FullString && m.Type == Type);
+ if (packageVersion == null) return NotFound();
+ if (!User.IsOwner(packageVersion)) return Unauthorized();
+
+ await packageManager.DeletePackageAsync(PackageId, FullString, Type);
+ return RedirectToAction(nameof(Index));
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/src/isnd/Controllers/Packages/PackagesController.cs b/src/isnd/Controllers/Packages/PackagesController.cs
new file mode 100644
index 0000000..ddf9e69
--- /dev/null
+++ b/src/isnd/Controllers/Packages/PackagesController.cs
@@ -0,0 +1,52 @@
+using System.ComponentModel.DataAnnotations;
+using System.IO;
+using System.Linq;
+using Microsoft.AspNetCore.Authorization;
+using Microsoft.AspNetCore.DataProtection;
+using Microsoft.AspNetCore.Mvc;
+using Microsoft.EntityFrameworkCore;
+using Microsoft.Extensions.Logging;
+using Microsoft.Extensions.Options;
+using NuGet.Versioning;
+using isnd.Data;
+using isnd.Entities;
+using Unleash;
+using isnd.Services;
+using isnd.ViewModels;
+using System.Threading.Tasks;
+using isnd.Interfaces;
+using isn.Abstract;
+
+namespace isnd.Controllers
+{
+ [AllowAnonymous]
+ public partial class PackagesController : Controller
+ {
+ const int maxTake = 100;
+ private readonly Resource[] resources;
+ private readonly ILogger logger;
+ private readonly IDataProtector protector;
+
+ private readonly IsndSettings isndSettings;
+ readonly ApplicationDbContext dbContext;
+ private readonly IPackageManager packageManager;
+ private readonly IUnleash unleashĈlient;
+
+ public PackagesController(
+ ILoggerFactory loggerFactory,
+ IDataProtectionProvider provider,
+ IOptions isndOptions,
+ IUnleash unleashĈlient,
+ ApplicationDbContext dbContext,
+ IPackageManager pm)
+ {
+ logger = loggerFactory.CreateLogger();
+ isndSettings = isndOptions.Value;
+ protector = provider.CreateProtector(isndSettings.ProtectionTitle);
+ this.dbContext = dbContext;
+ packageManager = pm;
+ this.unleashĈlient = unleashĈlient;
+ resources = packageManager.GetResources(unleashĈlient).ToArray();
+ }
+ }
+}
diff --git a/src/isnd/Data/Account/AccountOptions.cs b/src/isnd/Data/Account/AccountOptions.cs
new file mode 100644
index 0000000..d2290cc
--- /dev/null
+++ b/src/isnd/Data/Account/AccountOptions.cs
@@ -0,0 +1,20 @@
+// Copyright (c) Brock Allen & Dominick Baier. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information.
+
+
+using System;
+
+namespace isnd.Data
+{
+ public class AccountOptions
+ {
+ public static bool AllowLocalLogin = true;
+ public static bool AllowRememberLogin = true;
+ public static TimeSpan RememberMeLoginDuration = TimeSpan.FromDays(30);
+
+ public static bool ShowLogoutPrompt = true;
+ public static bool AutomaticRedirectAfterSignOut = false;
+
+ public static string InvalidCredentialsErrorMessage = "Invalid username or password";
+ }
+}
diff --git a/src/isnd/Data/Account/ExternalProvider.cs b/src/isnd/Data/Account/ExternalProvider.cs
new file mode 100644
index 0000000..70781c3
--- /dev/null
+++ b/src/isnd/Data/Account/ExternalProvider.cs
@@ -0,0 +1,12 @@
+// Copyright (c) Brock Allen & Dominick Baier. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information.
+
+
+namespace isnd.Data
+{
+ public class ExternalProvider
+ {
+ public string DisplayName { get; set; }
+ public string AuthenticationScheme { get; set; }
+ }
+}
\ No newline at end of file
diff --git a/src/isnd/Data/Account/LoggedOutViewModel.cs b/src/isnd/Data/Account/LoggedOutViewModel.cs
new file mode 100644
index 0000000..61f68ae
--- /dev/null
+++ b/src/isnd/Data/Account/LoggedOutViewModel.cs
@@ -0,0 +1,19 @@
+// Copyright (c) Brock Allen & Dominick Baier. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information.
+
+
+namespace isnd.Data
+{
+ public class LoggedOutViewModel
+ {
+ public string PostLogoutRedirectUri { get; set; }
+ public string ClientName { get; set; }
+ public string SignOutIframeUrl { get; set; }
+
+ public bool AutomaticRedirectAfterSignOut { get; set; }
+
+ public string LogoutId { get; set; }
+ public bool TriggerExternalSignout => ExternalAuthenticationScheme != null;
+ public string ExternalAuthenticationScheme { get; set; }
+ }
+}
\ No newline at end of file
diff --git a/src/isnd/Data/Account/LoginInputModel.cs b/src/isnd/Data/Account/LoginInputModel.cs
new file mode 100644
index 0000000..e87909c
--- /dev/null
+++ b/src/isnd/Data/Account/LoginInputModel.cs
@@ -0,0 +1,18 @@
+// Copyright (c) Brock Allen & Dominick Baier. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information.
+
+
+using System.ComponentModel.DataAnnotations;
+
+namespace isnd.Data
+{
+ public class LoginInputModel
+ {
+ [Required]
+ public string Username { get; set; }
+ [Required]
+ public string Password { get; set; }
+ public bool RememberLogin { get; set; }
+ public string ReturnUrl { get; set; }
+ }
+}
\ No newline at end of file
diff --git a/src/isnd/Data/Account/LoginViewModel.cs b/src/isnd/Data/Account/LoginViewModel.cs
new file mode 100644
index 0000000..d641fe8
--- /dev/null
+++ b/src/isnd/Data/Account/LoginViewModel.cs
@@ -0,0 +1,22 @@
+// Copyright (c) Brock Allen & Dominick Baier. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information.
+
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+
+namespace isnd.Data
+{
+ public class LoginViewModel : LoginInputModel
+ {
+ public bool AllowRememberLogin { get; set; } = true;
+ public bool EnableLocalLogin { get; set; } = true;
+
+ public IEnumerable ExternalProviders { get; set; } = Enumerable.Empty();
+ public IEnumerable VisibleExternalProviders => ExternalProviders.Where(x => !String.IsNullOrWhiteSpace(x.DisplayName));
+
+ public bool IsExternalLoginOnly => EnableLocalLogin == false && ExternalProviders?.Count() == 1;
+ public string ExternalLoginScheme => IsExternalLoginOnly ? ExternalProviders?.SingleOrDefault()?.AuthenticationScheme : null;
+ }
+}
\ No newline at end of file
diff --git a/src/isnd/Data/Account/LogoutInputModel.cs b/src/isnd/Data/Account/LogoutInputModel.cs
new file mode 100644
index 0000000..3200b8e
--- /dev/null
+++ b/src/isnd/Data/Account/LogoutInputModel.cs
@@ -0,0 +1,11 @@
+// Copyright (c) Brock Allen & Dominick Baier. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information.
+
+
+namespace isnd.Data
+{
+ public class LogoutInputModel
+ {
+ public string LogoutId { get; set; }
+ }
+}
diff --git a/src/isnd/Data/Account/LogoutViewModel.cs b/src/isnd/Data/Account/LogoutViewModel.cs
new file mode 100644
index 0000000..4f24c46
--- /dev/null
+++ b/src/isnd/Data/Account/LogoutViewModel.cs
@@ -0,0 +1,11 @@
+// Copyright (c) Brock Allen & Dominick Baier. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information.
+
+
+namespace isnd.Data
+{
+ public class LogoutViewModel : LogoutInputModel
+ {
+ public bool ShowLogoutPrompt { get; set; } = true;
+ }
+}
diff --git a/src/isnd/Data/Account/RedirectViewModel.cs b/src/isnd/Data/Account/RedirectViewModel.cs
new file mode 100644
index 0000000..52a3961
--- /dev/null
+++ b/src/isnd/Data/Account/RedirectViewModel.cs
@@ -0,0 +1,12 @@
+// Copyright (c) Brock Allen & Dominick Baier. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information.
+
+
+
+namespace isnd.Data
+{
+ public class RedirectViewModel
+ {
+ public string RedirectUrl { get; set; }
+ }
+}
\ No newline at end of file
diff --git a/src/isnd/Data/Account/RegisterViewModel.cs b/src/isnd/Data/Account/RegisterViewModel.cs
new file mode 100644
index 0000000..23a8c7c
--- /dev/null
+++ b/src/isnd/Data/Account/RegisterViewModel.cs
@@ -0,0 +1,32 @@
+using System;
+using System.Collections.Generic;
+using System.ComponentModel.DataAnnotations;
+using System.Linq;
+using System.Threading.Tasks;
+
+namespace isnd.Data
+{
+ public class RegisterViewModel
+ {
+ [Required]
+ [StringLength(100, ErrorMessage = "The {0} must be at least {2} characters long.", MinimumLength = 2)]
+ [Display(Name = "FullName")]
+ public string FullName { get; set; }
+
+ [Required]
+ [EmailAddress]
+ [Display(Name = "Email")]
+ public string Email { get; set; }
+
+ [Required]
+ [StringLength(100, ErrorMessage = "The {0} must be at least {2} characters long.", MinimumLength = 6)]
+ [DataType(DataType.Password)]
+ [Display(Name = "Password")]
+ public string Password { get; set; }
+
+ [DataType(DataType.Password)]
+ [Display(Name = "Confirm password")]
+ [Compare("Password", ErrorMessage = "The password and confirmation password do not match.")]
+ public string ConfirmPassword { get; set; }
+ }
+}
diff --git a/src/isnd/Data/ApiKeys/ApiKey.cs b/src/isnd/Data/ApiKeys/ApiKey.cs
new file mode 100644
index 0000000..517e93a
--- /dev/null
+++ b/src/isnd/Data/ApiKeys/ApiKey.cs
@@ -0,0 +1,25 @@
+using System;
+using System.ComponentModel.DataAnnotations;
+using System.ComponentModel.DataAnnotations.Schema;
+
+namespace isnd.Data.ApiKeys
+{
+ public class ApiKey
+ {
+ [Required][Key][DatabaseGenerated(DatabaseGeneratedOption.Identity)]
+ public string Id { get; set; }
+
+ [Required][ForeignKey("User")]
+ public string UserId { get; set; }
+
+ public virtual ApplicationUser User { get; set; }
+
+ public string Name { get; set; }
+
+ public int ValidityPeriodInDays{ get; set; }
+
+ public DateTime CreationDate { get; set; }
+
+ public bool IsValid => CreationDate.AddDays(ValidityPeriodInDays) > DateTime.Now;
+ }
+}
\ No newline at end of file
diff --git a/src/isnd/Data/ApiKeys/ApiKeyViewModel.cs b/src/isnd/Data/ApiKeys/ApiKeyViewModel.cs
new file mode 100644
index 0000000..2a4f5d8
--- /dev/null
+++ b/src/isnd/Data/ApiKeys/ApiKeyViewModel.cs
@@ -0,0 +1,14 @@
+using System.ComponentModel.DataAnnotations;
+
+namespace isnd.Data.ApiKeys
+{
+ public class ApiKeyViewModel
+ {
+ [Display(Name = "Key Name")]
+ public string Name { get; set; }
+
+
+ [Display(Name = "Key Value")]
+ public string ProtectedValue { get; set; }
+ }
+}
\ No newline at end of file
diff --git a/src/isnd/Data/ApiKeys/CreateModel.cs b/src/isnd/Data/ApiKeys/CreateModel.cs
new file mode 100644
index 0000000..af6b909
--- /dev/null
+++ b/src/isnd/Data/ApiKeys/CreateModel.cs
@@ -0,0 +1,14 @@
+using System.ComponentModel.DataAnnotations;
+
+namespace isnd.Data.ApiKeys
+{
+ public class CreateModel
+ {
+
+ [Required][StringLength(255)]
+ [Display(Name = "Key Name")]
+ public string Name { get; set; }
+ public string UserId { get; set; }
+
+ }
+}
\ No newline at end of file
diff --git a/src/isnd/Data/ApiKeys/DeleteModel.cs b/src/isnd/Data/ApiKeys/DeleteModel.cs
new file mode 100644
index 0000000..87aaf0e
--- /dev/null
+++ b/src/isnd/Data/ApiKeys/DeleteModel.cs
@@ -0,0 +1,7 @@
+namespace isnd.Data.ApiKeys
+{
+ public class DeleteModel
+ {
+ public ApiKey ApiKey { get; set; }
+ }
+}
\ No newline at end of file
diff --git a/src/isnd/Data/ApiKeys/DetailModel.cs b/src/isnd/Data/ApiKeys/DetailModel.cs
new file mode 100644
index 0000000..2682dc9
--- /dev/null
+++ b/src/isnd/Data/ApiKeys/DetailModel.cs
@@ -0,0 +1,8 @@
+namespace isnd.Data.ApiKeys
+{
+ public class DetailModel : ApiKeyViewModel
+ {
+ public ApiKey ApiKey { get; set; }
+
+ }
+}
\ No newline at end of file
diff --git a/src/isnd/Data/ApiKeys/EditModel.cs b/src/isnd/Data/ApiKeys/EditModel.cs
new file mode 100644
index 0000000..fc6e200
--- /dev/null
+++ b/src/isnd/Data/ApiKeys/EditModel.cs
@@ -0,0 +1,12 @@
+namespace isnd.Data.ApiKeys
+{
+ public class EditModel
+ {
+ public EditModel()
+ {
+ if (ApiKey==null) ApiKey = new ApiKey();
+ }
+
+ public ApiKey ApiKey { get; set; }
+ }
+}
\ No newline at end of file
diff --git a/src/isnd/Data/ApiKeys/IndexModel.cs b/src/isnd/Data/ApiKeys/IndexModel.cs
new file mode 100644
index 0000000..58e7a85
--- /dev/null
+++ b/src/isnd/Data/ApiKeys/IndexModel.cs
@@ -0,0 +1,9 @@
+using System.Collections.Generic;
+
+namespace isnd.Data.ApiKeys
+{
+ public class IndexModel
+ {
+ public List ApiKey { get; set; }
+ }
+}
\ No newline at end of file
diff --git a/src/isnd/Data/ApplicationDbContext.cs b/src/isnd/Data/ApplicationDbContext.cs
new file mode 100644
index 0000000..d01c0b6
--- /dev/null
+++ b/src/isnd/Data/ApplicationDbContext.cs
@@ -0,0 +1,49 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+using Microsoft.AspNetCore.Identity;
+using Microsoft.AspNetCore.Identity.EntityFrameworkCore;
+using Microsoft.EntityFrameworkCore;
+using isnd.Data;
+using isnd.Data.ApiKeys;
+using isnd.Data.Packages;
+
+namespace isnd.Data
+{
+ ///
+ /// Application Db Context
+ ///
+ public class ApplicationDbContext : IdentityDbContext
+ {
+ ///
+ /// db context ctor
+ ///
+ ///
+ public ApplicationDbContext(DbContextOptions options)
+ : base(options) { }
+
+ ///
+ /// User API keys
+ ///
+ ///
+ public DbSet ApiKeys { get; set; }
+
+ ///
+ /// Packages
+ ///
+ ///
+ public DbSet Packages { get; set; }
+
+ ///
+ /// Package Versions
+ ///
+ ///
+ public DbSet PackageVersions { get; set; }
+
+ ///
+ /// Commits
+ ///
+ ///
+ public DbSet Commits { get; set; }
+ }
+}
diff --git a/src/isnd/Data/ApplicationUser.cs b/src/isnd/Data/ApplicationUser.cs
new file mode 100644
index 0000000..4814bba
--- /dev/null
+++ b/src/isnd/Data/ApplicationUser.cs
@@ -0,0 +1,10 @@
+using Microsoft.AspNetCore.Identity;
+
+namespace isnd.Data
+{
+ // Add profile data for application users by adding properties to the ApplicationUser class
+ public class ApplicationUser : IdentityUser
+ {
+ public string FullName { get; set; }
+ }
+}
diff --git a/src/isnd/Data/Catalog/AlternatePackage.cs b/src/isnd/Data/Catalog/AlternatePackage.cs
new file mode 100644
index 0000000..8e0dfc5
--- /dev/null
+++ b/src/isnd/Data/Catalog/AlternatePackage.cs
@@ -0,0 +1,11 @@
+using NuGet.Versioning;
+
+namespace isnd.Data.Catalog
+{
+ public class AlternatePackage
+ {
+ public string id { get ; set; }
+ public VersionRange range { get ; set; }
+ }
+
+}
\ No newline at end of file
diff --git a/src/isnd/Data/Catalog/CatalogEntry.cs b/src/isnd/Data/Catalog/CatalogEntry.cs
new file mode 100644
index 0000000..be8c858
--- /dev/null
+++ b/src/isnd/Data/Catalog/CatalogEntry.cs
@@ -0,0 +1,119 @@
+using System.Net.Sockets;
+using System.ComponentModel.DataAnnotations;
+using Newtonsoft.Json;
+using isnd.Data.Packages;
+using NuGet.Versioning;
+using System.Collections.Generic;
+using System;
+using isnd.Interfaces;
+
+namespace isnd.Data.Catalog
+{
+ public class CatalogEntry : HappyIdOwner , IObject// , IPackageDetails
+ {
+ ///
+ /// Creates a catalog entry
+ ///
+ /// package details url
+ ///
+ public CatalogEntry(string id): base(id)
+ {
+
+ }
+ ///
+ /// The ID of the package
+ ///
+ ///
+
+ [JsonProperty("id")]
+ public string Id { get; set; }
+
+ ///
+ /// The Package details url
+ ///
+ ///
+
+ [JsonProperty("@id")]
+ public string refid { get => GetId(); }
+
+
+ [JsonProperty("@type")]
+ public string[] RefType { get; protected set; } = new string[] { "PackageDetail" };
+
+ [JsonProperty("commitId")]
+ public string CommitId { get; set; }
+
+ [JsonProperty("commitTimeStamp")]
+ public DateTime CommitTimeStamp { get; set; }
+
+ ///
+ /// Authors
+ ///
+ /// string or array of strings
+ public string authors { get; set; }
+
+ ///
+ /// The dependencies of the package, grouped by target framework
+ ///
+ /// array of objects
+ public DependencyGroup[] dependencyGroups { get; set; }
+
+ ///
+ /// The deprecation associated with the package
+ ///
+ ///
+ public Deprecation deprecation { get; set; }
+
+ [JsonProperty("description")]
+ public string Description { get; set; }
+ public string iconUrl { get; set; }
+ public string language { get; set; }
+
+
+ public string licenseUrl { get; set; }
+ public string licenseExpression { get; set; }
+ ///
+ /// Should be considered as listed if absent
+ ///
+ ///
+ public bool listed { get; set; }
+ public string minClientVersion { get; set; }
+ public string projectUrl { get; set; }
+
+ public bool requireLicenseAcceptance { get; set; }
+ public string summary { get; set; }
+
+ ///
+ /// The tags
+ ///
+ ///
+ public string tags { get; set; }
+ public string title { get; set; }
+
+
+ ///
+ /// The security vulnerabilities of the package
+ ///
+ ///
+ public Vulnerabilitie[] vulnerabilities { get; set; }
+
+ public string packageContent { get; set; }
+
+
+ ///
+ /// A string containing a ISO 8601 timestamp of when the package was published
+ ///
+ ///
+ [JsonProperty("published")]
+ public DateTime Published { get; set; }
+
+ ///
+ /// The full version string after normalization
+ ///
+ ///
+ [Required,JsonRequired]
+ [JsonProperty("version")]
+ public string Version { get; set; }
+
+ }
+}
\ No newline at end of file
diff --git a/src/isnd/Data/Catalog/DependencyGroup.cs b/src/isnd/Data/Catalog/DependencyGroup.cs
new file mode 100644
index 0000000..240d6a3
--- /dev/null
+++ b/src/isnd/Data/Catalog/DependencyGroup.cs
@@ -0,0 +1,6 @@
+namespace isnd.Data.Catalog
+{
+ public class DependencyGroup
+ {
+ }
+}
\ No newline at end of file
diff --git a/src/isnd/Data/Catalog/Deprecation.cs b/src/isnd/Data/Catalog/Deprecation.cs
new file mode 100644
index 0000000..1c14a9f
--- /dev/null
+++ b/src/isnd/Data/Catalog/Deprecation.cs
@@ -0,0 +1,14 @@
+namespace isnd.Data.Catalog
+{
+ public class Deprecation
+ {
+ /*
+ Legacy The package is no longer maintained
+CriticalBugs The package has bugs which make it unsuitable for usage
+Other The package is deprecated due to a reason not on this list
+*/
+ public string[] reasons { get; set; } // array of strings yes The reasons why the package was deprecated
+ public string message { get; set; } // The additional details about this deprecation
+ public AlternatePackage alternatePackage { get; set; } // object no The alternate package that should be used instead
+ }
+}
\ No newline at end of file
diff --git a/src/isnd/Data/Catalog/RegistratioinLeave.cs b/src/isnd/Data/Catalog/RegistratioinLeave.cs
new file mode 100644
index 0000000..1248175
--- /dev/null
+++ b/src/isnd/Data/Catalog/RegistratioinLeave.cs
@@ -0,0 +1,40 @@
+using System.ComponentModel.DataAnnotations;
+using Newtonsoft.Json;
+
+namespace isnd.Data.Catalog
+{
+ ///
+ /// Hosts a catalog entry,
+ /// the atomic content reference
+ ///
+ public class RegistrationLeaf
+ {
+ /*
+ @id string yes
+ catalogEntry object yes
+ packageContent string yes
+ */
+ ///
+ /// The URL to the registration leaf
+ ///
+ ///
+ [JsonProperty("@id")]
+ [Key][Required]
+ [StringLength(1024)]
+ public string Id { get; set; }
+
+ ///
+ /// The catalog entry containing the package metadata
+ ///
+ ///
+ [JsonProperty("catalogEntry")]
+ public CatalogEntry Entry { get; set; }
+
+ ///
+ /// The URL to the package content (.nupkg)
+ ///
+ ///
+ [JsonProperty("packageContent")]
+ public string PackageContent { get; set; }
+ }
+}
\ No newline at end of file
diff --git a/src/isnd/Data/Catalog/RegistrationPage.cs b/src/isnd/Data/Catalog/RegistrationPage.cs
new file mode 100644
index 0000000..a341454
--- /dev/null
+++ b/src/isnd/Data/Catalog/RegistrationPage.cs
@@ -0,0 +1,100 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using isnd.Data.Packages;
+using Newtonsoft.Json;
+using NuGet.Versioning;
+
+namespace isnd.Data.Catalog
+{
+ public class RegistrationPage : HappyIdOwner
+ {
+ [JsonProperty("@id")]
+ public string Id { get => GetId(); }
+ private readonly string pkgid;
+ private readonly List items;
+
+ protected string Bid { get ; private set; }
+ protected string ExternalUrl { get; }
+
+ public RegistrationPage (string bid, string pkgid, string extUrl) : base(bid + "/" + pkgid + "/index.json")
+ {
+ Bid = bid;
+ Parent = Bid + $"/{pkgid}/index.json";
+ ExternalUrl = extUrl;
+ this.items = new List();
+ this.pkgid = pkgid;
+ }
+
+ public RegistrationPage(string bid, string pkgid, string extUrl, List versions) : this(bid, pkgid, extUrl)
+ {
+ AddVersionRange(versions);
+ }
+
+ public string GetPackageId()
+ {
+ return pkgid;
+ }
+
+ ///
+ /// The array of registration leaves and their associate metadata
+ ///
+ ///
+ [JsonProperty("items")]
+
+ public CatalogEntry[] Items { get => items.Select((p) => p.ToLeave(Bid, ExternalUrl)).ToArray(); }
+
+ public void AddVersionRange(IEnumerable vitems)
+ {
+ if (vitems.Count() == 0) return;
+ NuGetVersion upper = null;
+ NuGetVersion lower = null;
+ if (Lower!=null) lower = new NuGetVersion(Lower);
+ if (Upper!=null) upper = new NuGetVersion(Upper);
+
+ // Assert.True(items.All(p=>p.Id == id));
+ long commitMax = 0;
+ foreach (var p in vitems)
+ {
+ if (items.Contains(p)) continue;
+
+ if (upper == null) upper = p.NugetVersion;
+ else if ( upper < p.NugetVersion) upper = p.NugetVersion;
+
+ if (lower == null) lower = p.NugetVersion;
+ else if (lower > p.NugetVersion) lower = p.NugetVersion;
+
+ if (p.CommitNId > commitMax) commitMax = p.CommitNId;
+ items.Add(p);
+ }
+ Upper = upper.ToFullString();
+ Lower = lower.ToFullString();
+ CommitId = commitMax.ToString();
+ }
+
+ ///
+ /// The highest SemVer 2.0.0 version in the page (inclusive)
+ ///
+ ///
+ [JsonProperty("upper"), JsonRequired]
+ public string Upper { get; private set; }
+ ///
+ /// The lowest SemVer 2.0.0 version in the page (inclusive)
+ ///
+ ///
+ [JsonProperty("lower"), JsonRequired]
+ public string Lower { get; private set; }
+
+ ///
+ /// The URL to the registration index
+ ///
+ ///
+ [JsonProperty("parent")]
+ public string Parent { get; set; }
+
+ [JsonProperty("count")]
+ public int Count { get => items.Count; }
+ public string CommitId { get; internal set; }
+ public DateTime CommitTimeStamp { get; internal set; }
+ }
+}
\ No newline at end of file
diff --git a/src/isnd/Data/Catalog/RegistrationPageIndex.cs b/src/isnd/Data/Catalog/RegistrationPageIndex.cs
new file mode 100644
index 0000000..cd7a5f3
--- /dev/null
+++ b/src/isnd/Data/Catalog/RegistrationPageIndex.cs
@@ -0,0 +1,53 @@
+using isnd.Data.Packages;
+using Newtonsoft.Json;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+
+namespace isnd.Data.Catalog
+{
+ public class RegistrationPageIndex : HappyIdOwner
+ {
+
+ public RegistrationPageIndex(string url) : base(url)
+ {
+ Items = new List();
+ }
+
+ public RegistrationPageIndex(string bid, string id, string extUrl, IEnumerable pkgs) : base(bid + $"/{id}/index.json")
+ {
+ Items = new List();
+ long cnid = 0;
+ var pkgsGroups = pkgs.GroupBy(l => l.Id);
+ // Pour tous les groupes par Id
+ foreach (var gsp in pkgsGroups)
+ {
+ var pkgsbi = gsp.ToArray();
+ List versions = new List();
+
+ foreach(var l in pkgsbi.Select(p => p.Versions))
+ {
+ versions.AddRange(l);
+ foreach (var pv in l)
+ {
+ if (pv.CommitNId> cnid)
+ {
+ cnid = pv.CommitNId;
+ }
+ }
+ }
+ Items.Add(new RegistrationPage(bid, gsp.Key, extUrl, versions));
+ }
+ CommitId = cnid.ToString();
+ }
+
+ [JsonProperty("count")]
+ public int Count { get => Items.Count; }
+
+ [JsonProperty("items")]
+ public List Items { get; set; }
+
+ public string CommitId { get; set; }
+ public DateTime CommitTimeStamp { get; internal set; }
+ }
+}
\ No newline at end of file
diff --git a/src/isnd/Data/Catalog/RegistrationPageIndexQuery.cs b/src/isnd/Data/Catalog/RegistrationPageIndexQuery.cs
new file mode 100644
index 0000000..33d5696
--- /dev/null
+++ b/src/isnd/Data/Catalog/RegistrationPageIndexQuery.cs
@@ -0,0 +1,25 @@
+using System.Collections.Generic;
+using isnd.Data.Packages;
+using Newtonsoft.Json;
+
+namespace isnd.Data.Catalog
+{
+ public class RegistrationPageIndexQuery
+ {
+ public RegistrationPageIndexQuery()
+ {
+ }
+
+ [JsonProperty("query")]
+ public string Query { get; set; }
+
+ [JsonProperty("prerelease")]
+ public bool Prerelease { get; set; } = true;
+ [JsonProperty("skip")]
+
+ public int Skip { get; set; } = 0;
+ [JsonProperty("take")]
+
+ public int Take { get; set; } = 25;
+ }
+}
\ No newline at end of file
diff --git a/src/isnd/Data/Catalog/Vulnerabilitie.cs b/src/isnd/Data/Catalog/Vulnerabilitie.cs
new file mode 100644
index 0000000..fc85dcf
--- /dev/null
+++ b/src/isnd/Data/Catalog/Vulnerabilitie.cs
@@ -0,0 +1,9 @@
+namespace isnd.Data.Catalog
+{
+ public class Vulnerabilitie
+ {
+ public string advisoryUrl { get; set; } // string yes Location of security advisory for the package
+ public string severity { get; set; } // string yes Severity of advisory: "0" = Low, "1" = Moderate, "2" = High, "3" = Critical
+
+ }
+}
\ No newline at end of file
diff --git a/src/isnd/Data/Historic/PackageVersionCommit.cs b/src/isnd/Data/Historic/PackageVersionCommit.cs
new file mode 100644
index 0000000..5857a6e
--- /dev/null
+++ b/src/isnd/Data/Historic/PackageVersionCommit.cs
@@ -0,0 +1,31 @@
+using System.ComponentModel.DataAnnotations;
+using System.ComponentModel.DataAnnotations.Schema;
+using isnd.Data.Packages;
+using isnd.Data.Packages.Catalog;
+
+namespace isnd.Data.Historic
+{
+ public class PackageVersionCommit
+ {
+ public long CommitId { get; set; }
+
+ [StringLength(1024)]
+ public string PackageId { get; set; }
+
+ [StringLength(256)]
+ public string FullString { get; set; }
+
+ [StringLength(256)]
+
+ public string PackageType { get; set; }
+
+ [ForeignKey("CommitId")]
+ public virtual Commit Commit { get; set; }
+
+ [ForeignKey("PackageId")]
+ public virtual Package Package { get; set; }
+
+ [ForeignKey("PackageId,FullString,PackageType")]
+ public virtual PackageVersion PackageVersion { get; set; }
+ }
+}
\ No newline at end of file
diff --git a/src/isnd/Data/Packages/Catalog/IObject.cs b/src/isnd/Data/Packages/Catalog/IObject.cs
new file mode 100644
index 0000000..d003d98
--- /dev/null
+++ b/src/isnd/Data/Packages/Catalog/IObject.cs
@@ -0,0 +1,18 @@
+using System;
+using Newtonsoft.Json;
+
+namespace isnd.Interfaces
+{
+ public interface IObject
+
+ {
+ [JsonProperty("@type")]
+ public string Type { get => GetType().Name; }
+
+ [JsonProperty("commitId")]
+ string CommitId { get; }
+
+ [JsonProperty("commitTimeStamp")]
+ DateTime CommitTimeStamp { get; }
+ }
+}
\ No newline at end of file
diff --git a/src/isnd/Data/Packages/Catalog/PackageRef.cs b/src/isnd/Data/Packages/Catalog/PackageRef.cs
new file mode 100644
index 0000000..4fb93ed
--- /dev/null
+++ b/src/isnd/Data/Packages/Catalog/PackageRef.cs
@@ -0,0 +1,52 @@
+using System;
+using System.ComponentModel.DataAnnotations.Schema;
+using isnd.Interfaces;
+using Newtonsoft.Json;
+
+namespace isnd.Data.Packages.Catalog
+{
+ ///
+ /// An presence of package in a catalog,
+ /// for availability, or deletion,
+ ///
+ ///
+ public class PackageRef : IObject
+ {
+
+
+ [JsonProperty("@id")]
+ public string RefId { get; set; }
+
+ ///
+ /// Reference type :
+ /// nuget:PackageDetails vs nuget:PackageDelete
+ ///
+ ///
+ [JsonProperty("@type")]
+ public string RefType { get; set; }
+
+ ///
+ /// The NuGet Id
+ ///
+ ///
+ [JsonProperty("nuget:id")]
+ public string Id { get; set; }
+
+ ///
+ /// The NuGet version
+ ///
+ ///
+
+ [JsonProperty("nuget:version")]
+ public string Version { get; set; }
+
+ [JsonProperty("commitId")]
+ public string CommitId { get; set; }
+
+ [ForeignKey("CommitId"), JsonIgnore]
+ public virtual Commit LastCommit { get; set; }
+
+ [JsonProperty("commitTimeStamp")]
+ public DateTime CommitTimeStamp { get; set; }
+ }
+}
\ No newline at end of file
diff --git a/src/isnd/Data/Packages/Catalog/Page.cs b/src/isnd/Data/Packages/Catalog/Page.cs
new file mode 100644
index 0000000..f5e00e7
--- /dev/null
+++ b/src/isnd/Data/Packages/Catalog/Page.cs
@@ -0,0 +1,21 @@
+using System;
+using System.Collections.Generic;
+using isnd.Interfaces;
+using Newtonsoft.Json;
+
+namespace isnd.Data.Packages.Catalog
+{
+ public class Page : IObject
+ {
+ [JsonProperty("@id")]
+ public string Id { get; set; }
+
+ [JsonProperty("parent")]
+ public string Parent { get; set; }
+
+ [JsonProperty("items")]
+ public virtual List Items { get; set; }
+ public string CommitId { get; set; }
+ public DateTime CommitTimeStamp { get; set; }
+ }
+}
\ No newline at end of file
diff --git a/src/isnd/Data/Packages/Commit.cs b/src/isnd/Data/Packages/Commit.cs
new file mode 100644
index 0000000..e024d11
--- /dev/null
+++ b/src/isnd/Data/Packages/Commit.cs
@@ -0,0 +1,39 @@
+using System;
+using System.Collections.Generic;
+using System.ComponentModel.DataAnnotations;
+using System.ComponentModel.DataAnnotations.Schema;
+using Newtonsoft.Json;
+using isnd.Interfaces;
+
+namespace isnd.Data.Packages
+{
+ public enum PackageAction
+ {
+ DeletePackage,
+ PublishPackage
+ }
+
+ public class Commit : IObject
+ {
+ [Key,
+ DatabaseGenerated(DatabaseGeneratedOption.Identity)]
+ [JsonIgnore]
+ public long Id { get; set; }
+
+ [Required][JsonIgnore]
+ public DateTime TimeStamp{ get; set; }
+
+ [Required]
+ public PackageAction Action { get; set; }
+
+ [NotMapped]
+ public string CommitId { get => Id.ToString(); }
+
+ [NotMapped]
+ public DateTime CommitTimeStamp { get => TimeStamp; }
+
+ [ForeignKey("CommitNId")]
+
+ public virtual List Versions { get; set; }
+ }
+}
\ No newline at end of file
diff --git a/src/isnd/Data/Packages/Package.cs b/src/isnd/Data/Packages/Package.cs
new file mode 100644
index 0000000..24f4bf1
--- /dev/null
+++ b/src/isnd/Data/Packages/Package.cs
@@ -0,0 +1,66 @@
+using System;
+using System.Collections.Generic;
+using System.ComponentModel.DataAnnotations;
+using System.ComponentModel.DataAnnotations.Schema;
+using System.Linq;
+using isnd.Data.Catalog;
+using isnd.Interfaces;
+using Newtonsoft.Json;
+
+namespace isnd.Data.Packages
+{
+ public interface IPackage
+ {
+ string Id { get; set; }
+ string OwnerId { get; set; }
+ string Description { get; set; }
+ bool Public { get; set; }
+ ApplicationUser Owner { get; set; }
+ List Versions { get; set; }
+ long CommitNId { get; set; }
+ string CommitId { get; }
+ Commit LatestVersion { get; set; }
+ DateTime CommitTimeStamp { get; set; }
+ }
+
+ public class Package : IObject, IPackage
+ {
+ [Key]
+ [Required]
+ [StringLength(1024)]
+ public string Id { get; set; }
+
+ [Required]
+ [ForeignKey("Owner")]
+ public string OwnerId { get; set; }
+
+ [StringLength(1024)]
+ public string Description { get; set; }
+
+ public bool Public { get; set; }
+
+ [JsonIgnore]
+ virtual public ApplicationUser Owner { get; set; }
+
+ [JsonIgnore]
+
+ public virtual List Versions { get; set; }
+
+ ///
+ /// Latest version at put, posted,
+ /// or even deletion when no more active version.
+ ///
+ ///
+ [Required]
+ [JsonIgnore]
+ public long CommitNId { get; set; }
+
+ [NotMapped]
+ public string CommitId { get => CommitNId.ToString(); }
+
+ [ForeignKey("CommitNId")]
+
+ public virtual Commit LatestVersion { get; set; }
+ public DateTime CommitTimeStamp { get; set; }
+ }
+}
\ No newline at end of file
diff --git a/src/isnd/Data/Packages/PackageVersion.cs b/src/isnd/Data/Packages/PackageVersion.cs
new file mode 100644
index 0000000..9120d93
--- /dev/null
+++ b/src/isnd/Data/Packages/PackageVersion.cs
@@ -0,0 +1,80 @@
+
+using System.ComponentModel.DataAnnotations;
+using System.ComponentModel.DataAnnotations.Schema;
+using isn.abst;
+using isnd.Data.Catalog;
+using isnd.Data.Packages;
+using isnd.Data.Packages.Catalog;
+using Newtonsoft.Json;
+using NuGet.Versioning;
+
+namespace isnd.Data
+{
+
+
+ public class PackageVersion
+ {
+ [Required]
+ [ForeignKey("Package")]
+ [StringLength(1024)]
+ [JsonProperty("id")]
+ public string PackageId { get; set; }
+
+ [Required]
+ public int Major { get; set; }
+
+ [Required]
+ public int Minor { get; set; }
+
+ [Required]
+ public int Patch { get; set; }
+
+ public int Revision { get; set; }
+
+
+ ///
+ /// Full version string
+ ///
+ ///
+ [StringLength(256)]
+ [Required]
+ [Key]
+ public string FullString { get; set; }
+ public bool IsPrerelease { get; set; }
+
+ [StringLength(256)]
+ public string Type { get; set; }
+
+ [JsonIgnore]
+ public virtual Package Package { get; set; }
+
+ [Required]
+ [JsonIgnore]
+ [ForeignKey("LatestCommit")]
+ public long CommitNId { get; set; }
+
+ [NotMapped]
+
+ public string CommitId { get => CommitNId.ToString(); }
+
+ public virtual Commit LatestCommit { get; set; }
+ public string NugetLink => $"{Constants.PaquetFileEstension}/{PackageId}/{FullString}/{PackageId}-{FullString}."
+ + Constants.PaquetFileEstension;
+ public string NuspecLink => $"{Constants.SpecFileEstension}/{PackageId}/{FullString}/{PackageId}-{FullString}."
+ + Constants.SpecFileEstension;
+
+ public string SementicVersionString { get => $"{Major}.{Minor}.{Patch}"; }
+ public NuGetVersion NugetVersion { get => new NuGetVersion(FullString); }
+
+ public CatalogEntry ToLeave(string bid, string extUrl)
+ {
+ return new CatalogEntry(bid + "/" + this.PackageId + "/" + FullString + ".json")
+ {
+ Id = PackageId,
+ Version = FullString,
+ authors = $"{this.Package.Owner.FullName} <${Package.Owner.Email}>",
+ packageContent = extUrl + this.NugetLink
+ };
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/isnd/Data/Roles/Administrator.cs b/src/isnd/Data/Roles/Administrator.cs
new file mode 100644
index 0000000..7c5d9e5
--- /dev/null
+++ b/src/isnd/Data/Roles/Administrator.cs
@@ -0,0 +1,7 @@
+namespace isnd.Data.Roles
+{
+ public class AdminStartupList
+ {
+ public string [] Users { get; set;}
+ }
+}
\ No newline at end of file
diff --git a/src/isnd/Entities/IsndSettings.cs b/src/isnd/Entities/IsndSettings.cs
new file mode 100644
index 0000000..83b8073
--- /dev/null
+++ b/src/isnd/Entities/IsndSettings.cs
@@ -0,0 +1,16 @@
+using System;
+
+namespace isnd.Entities
+{
+ public class IsndSettings
+ {
+ public string ExternalUrl { get; set; }
+
+ public string ProtectionTitle {get; set;}
+ public string PackagesRootDir {get; set;}
+ public string CatalogDir {get; set;}
+ public int MaxUserKeyCount {get; set;}
+ public int CatalogPageLen {get; set;}
+ public TimeSpan DisplayDeletionLen {get; set;}
+ }
+}
\ No newline at end of file
diff --git a/src/isnd/Entities/NewReleaseInfo.cs b/src/isnd/Entities/NewReleaseInfo.cs
new file mode 100644
index 0000000..ef69c09
--- /dev/null
+++ b/src/isnd/Entities/NewReleaseInfo.cs
@@ -0,0 +1,11 @@
+using System;
+
+namespace isnd.Data
+{
+ public class NewReleaseInfo
+ {
+ public string Version { get; set; }
+ public string ChangeLog { get; set; }
+ public DateTime BuildDate { get; set; }
+ }
+}
\ No newline at end of file
diff --git a/src/isnd/Entities/SmtpSettings.cs b/src/isnd/Entities/SmtpSettings.cs
new file mode 100644
index 0000000..5a4a861
--- /dev/null
+++ b/src/isnd/Entities/SmtpSettings.cs
@@ -0,0 +1,12 @@
+namespace isnd.Entities
+{
+ public class SmtpSettings
+ {
+ public string Server {get; set;}
+ public int Port {get; set;}
+ public string SenderName {get; set;}
+ public string SenderEMail {get; set;}
+ public string UserName {get; set;}
+ public string Password {get; set;}
+ }
+}
\ No newline at end of file
diff --git a/src/isnd/Entities/UnleashClientSettings.cs b/src/isnd/Entities/UnleashClientSettings.cs
new file mode 100644
index 0000000..1313724
--- /dev/null
+++ b/src/isnd/Entities/UnleashClientSettings.cs
@@ -0,0 +1,8 @@
+namespace isnd.Entities
+{
+ public class UnleashClientSettings
+ {
+ public string ClientApiKey { get; set; }
+ public string ApiUrl { get; set; }
+ }
+}
\ No newline at end of file
diff --git a/src/isnd/Extensions/Extensions.cs b/src/isnd/Extensions/Extensions.cs
new file mode 100644
index 0000000..7753d01
--- /dev/null
+++ b/src/isnd/Extensions/Extensions.cs
@@ -0,0 +1,17 @@
+using System;
+using Microsoft.AspNetCore.Mvc;
+
+namespace isnd.Data
+{
+ public static class Extensions
+ {
+
+ public static IActionResult LoadingPage(this Controller controller, string viewName, string redirectUri)
+ {
+ controller.HttpContext.Response.StatusCode = 200;
+ controller.HttpContext.Response.Headers["Location"] = "";
+
+ return controller.View(viewName, new RedirectViewModel { RedirectUrl = redirectUri });
+ }
+ }
+}
diff --git a/src/isnd/Helpers/NuspecCoreReaderHelpers.cs b/src/isnd/Helpers/NuspecCoreReaderHelpers.cs
new file mode 100644
index 0000000..573de1f
--- /dev/null
+++ b/src/isnd/Helpers/NuspecCoreReaderHelpers.cs
@@ -0,0 +1,16 @@
+using System.Linq;
+using System.Xml.Linq;
+using NuGet.Packaging.Core;
+
+namespace isnd.Helpers
+{
+ public static class NuspecCoreReaderHelpers
+ {
+ public static string GetDescription(this NuspecCoreReader reader)
+ {
+ var meta = reader.GetMetadata();
+ var kv = meta.SingleOrDefault(i => i.Key == "description");
+ return kv.Value;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/isnd/Helpers/PackageIdHelpers.cs b/src/isnd/Helpers/PackageIdHelpers.cs
new file mode 100644
index 0000000..88bb43e
--- /dev/null
+++ b/src/isnd/Helpers/PackageIdHelpers.cs
@@ -0,0 +1,17 @@
+namespace isnd.Helpers
+{
+ ///
+ /// Package Id Helpers
+ ///
+ public static class PackageIdHelpers
+ {
+ internal static bool SeparatedByMinusMatch(string id, string q)
+ {
+ foreach (var part in id.Split('-'))
+ {
+ if (part.Equals(q, System.StringComparison.InvariantCultureIgnoreCase)) return true;
+ }
+ return false;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/isnd/Helpers/PackageVersionHelpers.cs b/src/isnd/Helpers/PackageVersionHelpers.cs
new file mode 100644
index 0000000..49f41d6
--- /dev/null
+++ b/src/isnd/Helpers/PackageVersionHelpers.cs
@@ -0,0 +1,20 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Security.Claims;
+using isn.abst;
+using isnd.Data;
+using isnd.Data.Catalog;
+using isnd.Entities;
+
+namespace isnd.Helpers
+{
+ public static class PackageVersionHelpers
+ {
+ public static bool IsOwner(this ClaimsPrincipal user, PackageVersion v)
+ {
+ var userId = user.FindFirstValue(ClaimTypes.NameIdentifier);
+ return v.Package.OwnerId == userId;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/isnd/Helpers/ParamHelpers.cs b/src/isnd/Helpers/ParamHelpers.cs
new file mode 100644
index 0000000..fa4f186
--- /dev/null
+++ b/src/isnd/Helpers/ParamHelpers.cs
@@ -0,0 +1,16 @@
+namespace isnd.Helpers
+{
+ public static class ParamHelpers
+ {
+ public static string Optional(ref string prm)
+ {
+ int sopt = prm.IndexOf('/');
+ if (sopt>0)
+ {
+ prm = prm.Substring(0,sopt);
+ return prm.Substring(sopt + 1);
+ }
+ return null;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/isnd/Helpers/SiteHelpers.cs b/src/isnd/Helpers/SiteHelpers.cs
new file mode 100644
index 0000000..cb5f5c5
--- /dev/null
+++ b/src/isnd/Helpers/SiteHelpers.cs
@@ -0,0 +1,13 @@
+namespace isnd.Helpers
+{
+ public static class SiteHelpers
+ {
+ ///
+ /// The Git Sementic version (from GitVersion.MsBuild)[published]
+ ///
+ ///
+ public static string SemVer {
+ get => GitVersionInformation.SemVer;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/isnd/Helpers/UnleashHelpers.cs b/src/isnd/Helpers/UnleashHelpers.cs
new file mode 100644
index 0000000..d107bff
--- /dev/null
+++ b/src/isnd/Helpers/UnleashHelpers.cs
@@ -0,0 +1,32 @@
+using System;
+using System.Collections.Generic;
+using isnd.Entities;
+using Microsoft.AspNetCore.Hosting;
+using Microsoft.AspNetCore.Mvc;
+using Unleash;
+using Unleash.ClientFactory;
+
+namespace isnd.Helpers
+{
+ public static class UnleashHelpers
+ {
+
+ public static IUnleash CreateUnleahClient(this IHostingEnvironment env,
+ UnleashClientSettings unleashClientSettings)
+ {
+ var unleashSettings = new UnleashSettings
+ {
+ UnleashApi = new Uri(unleashClientSettings.ApiUrl),
+ AppName = "isnd",
+ Environment = env.EnvironmentName,
+ CustomHttpHeaders = new Dictionary
+ {
+ { "Authorization", unleashClientSettings.ClientApiKey }
+ }
+ };
+
+ UnleashClientFactory unleashClientFactory = new UnleashClientFactory();
+ return unleashClientFactory.CreateClient(unleashSettings);
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/isnd/Interfaces/IMailer.cs b/src/isnd/Interfaces/IMailer.cs
new file mode 100644
index 0000000..9ade79b
--- /dev/null
+++ b/src/isnd/Interfaces/IMailer.cs
@@ -0,0 +1,10 @@
+
+using System.Threading.Tasks;
+
+namespace isnd.Interfaces
+{
+ public interface IMailer
+ {
+ Task SendMailAsync(string name, string email, string subjet, string body);
+ }
+}
\ No newline at end of file
diff --git a/src/isnd/Interfaces/IPackageManager.cs b/src/isnd/Interfaces/IPackageManager.cs
new file mode 100644
index 0000000..8e5c2b2
--- /dev/null
+++ b/src/isnd/Interfaces/IPackageManager.cs
@@ -0,0 +1,36 @@
+using System.Collections.Generic;
+using System.Threading.Tasks;
+using isn.Abstract;
+using isnd.Controllers;
+using isnd.Data;
+using isnd.Data.Catalog;
+using isnd.Data.Packages;
+using isnd.Data.Packages.Catalog;
+using isnd.Services;
+using isnd.ViewModels;
+using NuGet.Versioning;
+using Unleash;
+
+namespace isnd.Interfaces
+{
+ public interface IPackageManager
+ {
+ string CatalogBaseUrl { get; }
+ AutoCompleteResult AutoComplete(string pkgid, int skip, int take, bool prerelease = false, string packageType = null);
+
+ string[] GetVersions(string pkgid, NuGetVersion parsedVersion, bool prerelease = false, string packageType = null, int skip = 0, int take = 25);
+ IEnumerable GetResources(IUnleash unleashĈlient);
+ Task ÛpdateCatalogForAsync(Commit commit);
+ Task DeletePackageAsync(string pkgid, string version, string type);
+ Task UserAskForPackageDeletionAsync(string userid, string pkgId, string lower, string type);
+ Task GetPackageAsync(string pkgid, string version, string type);
+ Task GetCatalogEntryAsync(string pkgId, string version, string pkgType);
+ IEnumerable SearchCatalogEntriesById(string pkgId, string semver, string pkgType);
+
+ Task GetCatalogIndexAsync();
+ Task GetPackageRegistrationIndexAsync(RegistrationPageIndexQuery query);
+
+ Task SearchPackageAsync(RegistrationPageIndexQuery query);
+ }
+
+}
\ No newline at end of file
diff --git a/src/isnd/Migrations/20210424155323_init.Designer.cs b/src/isnd/Migrations/20210424155323_init.Designer.cs
new file mode 100644
index 0000000..79c891f
--- /dev/null
+++ b/src/isnd/Migrations/20210424155323_init.Designer.cs
@@ -0,0 +1,230 @@
+//
+using System;
+using Microsoft.EntityFrameworkCore;
+using Microsoft.EntityFrameworkCore.Infrastructure;
+using Microsoft.EntityFrameworkCore.Migrations;
+using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
+using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
+using isnd.Data;
+
+namespace isndhost.Migrations
+{
+ [DbContext(typeof(ApplicationDbContext))]
+ [Migration("20210424155323_init")]
+ partial class init
+ {
+ protected override void BuildTargetModel(ModelBuilder modelBuilder)
+ {
+#pragma warning disable 612, 618
+ modelBuilder
+ .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.SerialColumn)
+ .HasAnnotation("ProductVersion", "2.2.6-servicing-10079")
+ .HasAnnotation("Relational:MaxIdentifierLength", 63);
+
+ modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd();
+
+ b.Property("ConcurrencyStamp")
+ .IsConcurrencyToken();
+
+ b.Property("Name")
+ .HasMaxLength(256);
+
+ b.Property("NormalizedName")
+ .HasMaxLength(256);
+
+ b.HasKey("Id");
+
+ b.HasIndex("NormalizedName")
+ .IsUnique()
+ .HasName("RoleNameIndex");
+
+ b.ToTable("AspNetRoles");
+ });
+
+ modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd();
+
+ b.Property("ClaimType");
+
+ b.Property("ClaimValue");
+
+ b.Property("RoleId")
+ .IsRequired();
+
+ b.HasKey("Id");
+
+ b.HasIndex("RoleId");
+
+ b.ToTable("AspNetRoleClaims");
+ });
+
+ modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd();
+
+ b.Property("ClaimType");
+
+ b.Property("ClaimValue");
+
+ b.Property("UserId")
+ .IsRequired();
+
+ b.HasKey("Id");
+
+ b.HasIndex("UserId");
+
+ b.ToTable("AspNetUserClaims");
+ });
+
+ modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b =>
+ {
+ b.Property("LoginProvider");
+
+ b.Property("ProviderKey");
+
+ b.Property("ProviderDisplayName");
+
+ b.Property("UserId")
+ .IsRequired();
+
+ b.HasKey("LoginProvider", "ProviderKey");
+
+ b.HasIndex("UserId");
+
+ b.ToTable("AspNetUserLogins");
+ });
+
+ modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b =>
+ {
+ b.Property("UserId");
+
+ b.Property("RoleId");
+
+ b.HasKey("UserId", "RoleId");
+
+ b.HasIndex("RoleId");
+
+ b.ToTable("AspNetUserRoles");
+ });
+
+ modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b =>
+ {
+ b.Property("UserId");
+
+ b.Property("LoginProvider");
+
+ b.Property("Name");
+
+ b.Property("Value");
+
+ b.HasKey("UserId", "LoginProvider", "Name");
+
+ b.ToTable("AspNetUserTokens");
+ });
+
+ modelBuilder.Entity("isn.Data.ApplicationUser", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd();
+
+ b.Property("AccessFailedCount");
+
+ b.Property("ConcurrencyStamp")
+ .IsConcurrencyToken();
+
+ b.Property("Email")
+ .HasMaxLength(256);
+
+ b.Property("EmailConfirmed");
+
+ b.Property("FullName");
+
+ b.Property("LockoutEnabled");
+
+ b.Property("LockoutEnd");
+
+ b.Property("NormalizedEmail")
+ .HasMaxLength(256);
+
+ b.Property("NormalizedUserName")
+ .HasMaxLength(256);
+
+ b.Property("PasswordHash");
+
+ b.Property("PhoneNumber");
+
+ b.Property("PhoneNumberConfirmed");
+
+ b.Property("SecurityStamp");
+
+ b.Property("TwoFactorEnabled");
+
+ b.Property("UserName")
+ .HasMaxLength(256);
+
+ b.HasKey("Id");
+
+ b.HasIndex("NormalizedEmail")
+ .HasName("EmailIndex");
+
+ b.HasIndex("NormalizedUserName")
+ .IsUnique()
+ .HasName("UserNameIndex");
+
+ b.ToTable("AspNetUsers");
+ });
+
+ modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b =>
+ {
+ b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole")
+ .WithMany()
+ .HasForeignKey("RoleId")
+ .OnDelete(DeleteBehavior.Cascade);
+ });
+
+ modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b =>
+ {
+ b.HasOne("isn.Data.ApplicationUser")
+ .WithMany()
+ .HasForeignKey("UserId")
+ .OnDelete(DeleteBehavior.Cascade);
+ });
+
+ modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b =>
+ {
+ b.HasOne("isn.Data.ApplicationUser")
+ .WithMany()
+ .HasForeignKey("UserId")
+ .OnDelete(DeleteBehavior.Cascade);
+ });
+
+ modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b =>
+ {
+ b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole")
+ .WithMany()
+ .HasForeignKey("RoleId")
+ .OnDelete(DeleteBehavior.Cascade);
+
+ b.HasOne("isn.Data.ApplicationUser")
+ .WithMany()
+ .HasForeignKey("UserId")
+ .OnDelete(DeleteBehavior.Cascade);
+ });
+
+ modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b =>
+ {
+ b.HasOne("isn.Data.ApplicationUser")
+ .WithMany()
+ .HasForeignKey("UserId")
+ .OnDelete(DeleteBehavior.Cascade);
+ });
+#pragma warning restore 612, 618
+ }
+ }
+}
diff --git a/src/isnd/Migrations/20210424155323_init.cs b/src/isnd/Migrations/20210424155323_init.cs
new file mode 100644
index 0000000..98fc269
--- /dev/null
+++ b/src/isnd/Migrations/20210424155323_init.cs
@@ -0,0 +1,219 @@
+using System;
+using Microsoft.EntityFrameworkCore.Migrations;
+using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
+
+namespace isndhost.Migrations
+{
+ public partial class init : Migration
+ {
+ protected override void Up(MigrationBuilder migrationBuilder)
+ {
+ migrationBuilder.CreateTable(
+ name: "AspNetRoles",
+ columns: table => new
+ {
+ Id = table.Column(nullable: false),
+ Name = table.Column(maxLength: 256, nullable: true),
+ NormalizedName = table.Column(maxLength: 256, nullable: true),
+ ConcurrencyStamp = table.Column(nullable: true)
+ },
+ constraints: table =>
+ {
+ table.PrimaryKey("PK_AspNetRoles", x => x.Id);
+ });
+
+ migrationBuilder.CreateTable(
+ name: "AspNetUsers",
+ columns: table => new
+ {
+ Id = table.Column(nullable: false),
+ UserName = table.Column(maxLength: 256, nullable: true),
+ NormalizedUserName = table.Column(maxLength: 256, nullable: true),
+ Email = table.Column(maxLength: 256, nullable: true),
+ NormalizedEmail = table.Column(maxLength: 256, nullable: true),
+ EmailConfirmed = table.Column(nullable: false),
+ PasswordHash = table.Column(nullable: true),
+ SecurityStamp = table.Column(nullable: true),
+ ConcurrencyStamp = table.Column(nullable: true),
+ PhoneNumber = table.Column(nullable: true),
+ PhoneNumberConfirmed = table.Column(nullable: false),
+ TwoFactorEnabled = table.Column(nullable: false),
+ LockoutEnd = table.Column(nullable: true),
+ LockoutEnabled = table.Column(nullable: false),
+ AccessFailedCount = table.Column(nullable: false),
+ FullName = table.Column(nullable: true)
+ },
+ constraints: table =>
+ {
+ table.PrimaryKey("PK_AspNetUsers", x => x.Id);
+ });
+
+ migrationBuilder.CreateTable(
+ name: "AspNetRoleClaims",
+ columns: table => new
+ {
+ Id = table.Column(nullable: false)
+ .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.SerialColumn),
+ RoleId = table.Column(nullable: false),
+ ClaimType = table.Column(nullable: true),
+ ClaimValue = table.Column(nullable: true)
+ },
+ constraints: table =>
+ {
+ table.PrimaryKey("PK_AspNetRoleClaims", x => x.Id);
+ table.ForeignKey(
+ name: "FK_AspNetRoleClaims_AspNetRoles_RoleId",
+ column: x => x.RoleId,
+ principalTable: "AspNetRoles",
+ principalColumn: "Id",
+ onDelete: ReferentialAction.Cascade);
+ });
+
+ migrationBuilder.CreateTable(
+ name: "AspNetUserClaims",
+ columns: table => new
+ {
+ Id = table.Column(nullable: false)
+ .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.SerialColumn),
+ UserId = table.Column(nullable: false),
+ ClaimType = table.Column(nullable: true),
+ ClaimValue = table.Column(nullable: true)
+ },
+ constraints: table =>
+ {
+ table.PrimaryKey("PK_AspNetUserClaims", x => x.Id);
+ table.ForeignKey(
+ name: "FK_AspNetUserClaims_AspNetUsers_UserId",
+ column: x => x.UserId,
+ principalTable: "AspNetUsers",
+ principalColumn: "Id",
+ onDelete: ReferentialAction.Cascade);
+ });
+
+ migrationBuilder.CreateTable(
+ name: "AspNetUserLogins",
+ columns: table => new
+ {
+ LoginProvider = table.Column(nullable: false),
+ ProviderKey = table.Column(nullable: false),
+ ProviderDisplayName = table.Column(nullable: true),
+ UserId = table.Column(nullable: false)
+ },
+ constraints: table =>
+ {
+ table.PrimaryKey("PK_AspNetUserLogins", x => new { x.LoginProvider, x.ProviderKey });
+ table.ForeignKey(
+ name: "FK_AspNetUserLogins_AspNetUsers_UserId",
+ column: x => x.UserId,
+ principalTable: "AspNetUsers",
+ principalColumn: "Id",
+ onDelete: ReferentialAction.Cascade);
+ });
+
+ migrationBuilder.CreateTable(
+ name: "AspNetUserRoles",
+ columns: table => new
+ {
+ UserId = table.Column(nullable: false),
+ RoleId = table.Column(nullable: false)
+ },
+ constraints: table =>
+ {
+ table.PrimaryKey("PK_AspNetUserRoles", x => new { x.UserId, x.RoleId });
+ table.ForeignKey(
+ name: "FK_AspNetUserRoles_AspNetRoles_RoleId",
+ column: x => x.RoleId,
+ principalTable: "AspNetRoles",
+ principalColumn: "Id",
+ onDelete: ReferentialAction.Cascade);
+ table.ForeignKey(
+ name: "FK_AspNetUserRoles_AspNetUsers_UserId",
+ column: x => x.UserId,
+ principalTable: "AspNetUsers",
+ principalColumn: "Id",
+ onDelete: ReferentialAction.Cascade);
+ });
+
+ migrationBuilder.CreateTable(
+ name: "AspNetUserTokens",
+ columns: table => new
+ {
+ UserId = table.Column(nullable: false),
+ LoginProvider = table.Column(nullable: false),
+ Name = table.Column(nullable: false),
+ Value = table.Column(nullable: true)
+ },
+ constraints: table =>
+ {
+ table.PrimaryKey("PK_AspNetUserTokens", x => new { x.UserId, x.LoginProvider, x.Name });
+ table.ForeignKey(
+ name: "FK_AspNetUserTokens_AspNetUsers_UserId",
+ column: x => x.UserId,
+ principalTable: "AspNetUsers",
+ principalColumn: "Id",
+ onDelete: ReferentialAction.Cascade);
+ });
+
+ migrationBuilder.CreateIndex(
+ name: "IX_AspNetRoleClaims_RoleId",
+ table: "AspNetRoleClaims",
+ column: "RoleId");
+
+ migrationBuilder.CreateIndex(
+ name: "RoleNameIndex",
+ table: "AspNetRoles",
+ column: "NormalizedName",
+ unique: true);
+
+ migrationBuilder.CreateIndex(
+ name: "IX_AspNetUserClaims_UserId",
+ table: "AspNetUserClaims",
+ column: "UserId");
+
+ migrationBuilder.CreateIndex(
+ name: "IX_AspNetUserLogins_UserId",
+ table: "AspNetUserLogins",
+ column: "UserId");
+
+ migrationBuilder.CreateIndex(
+ name: "IX_AspNetUserRoles_RoleId",
+ table: "AspNetUserRoles",
+ column: "RoleId");
+
+ migrationBuilder.CreateIndex(
+ name: "EmailIndex",
+ table: "AspNetUsers",
+ column: "NormalizedEmail");
+
+ migrationBuilder.CreateIndex(
+ name: "UserNameIndex",
+ table: "AspNetUsers",
+ column: "NormalizedUserName",
+ unique: true);
+ }
+
+ protected override void Down(MigrationBuilder migrationBuilder)
+ {
+ migrationBuilder.DropTable(
+ name: "AspNetRoleClaims");
+
+ migrationBuilder.DropTable(
+ name: "AspNetUserClaims");
+
+ migrationBuilder.DropTable(
+ name: "AspNetUserLogins");
+
+ migrationBuilder.DropTable(
+ name: "AspNetUserRoles");
+
+ migrationBuilder.DropTable(
+ name: "AspNetUserTokens");
+
+ migrationBuilder.DropTable(
+ name: "AspNetRoles");
+
+ migrationBuilder.DropTable(
+ name: "AspNetUsers");
+ }
+ }
+}
diff --git a/src/isnd/Migrations/20210502153508_api-keys.Designer.cs b/src/isnd/Migrations/20210502153508_api-keys.Designer.cs
new file mode 100644
index 0000000..05c0c44
--- /dev/null
+++ b/src/isnd/Migrations/20210502153508_api-keys.Designer.cs
@@ -0,0 +1,253 @@
+//
+using System;
+using Microsoft.EntityFrameworkCore;
+using Microsoft.EntityFrameworkCore.Infrastructure;
+using Microsoft.EntityFrameworkCore.Migrations;
+using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
+using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
+using isnd.Data;
+
+namespace isndhost.Migrations
+{
+ [DbContext(typeof(ApplicationDbContext))]
+ [Migration("20210502153508_api-keys")]
+ partial class apikeys
+ {
+ protected override void BuildTargetModel(ModelBuilder modelBuilder)
+ {
+#pragma warning disable 612, 618
+ modelBuilder
+ .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.SerialColumn)
+ .HasAnnotation("ProductVersion", "2.2.6-servicing-10079")
+ .HasAnnotation("Relational:MaxIdentifierLength", 63);
+
+ modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd();
+
+ b.Property("ConcurrencyStamp")
+ .IsConcurrencyToken();
+
+ b.Property("Name")
+ .HasMaxLength(256);
+
+ b.Property("NormalizedName")
+ .HasMaxLength(256);
+
+ b.HasKey("Id");
+
+ b.HasIndex("NormalizedName")
+ .IsUnique()
+ .HasName("RoleNameIndex");
+
+ b.ToTable("AspNetRoles");
+ });
+
+ modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd();
+
+ b.Property("ClaimType");
+
+ b.Property("ClaimValue");
+
+ b.Property("RoleId")
+ .IsRequired();
+
+ b.HasKey("Id");
+
+ b.HasIndex("RoleId");
+
+ b.ToTable("AspNetRoleClaims");
+ });
+
+ modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd();
+
+ b.Property("ClaimType");
+
+ b.Property("ClaimValue");
+
+ b.Property("UserId")
+ .IsRequired();
+
+ b.HasKey("Id");
+
+ b.HasIndex("UserId");
+
+ b.ToTable("AspNetUserClaims");
+ });
+
+ modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b =>
+ {
+ b.Property("LoginProvider");
+
+ b.Property("ProviderKey");
+
+ b.Property("ProviderDisplayName");
+
+ b.Property("UserId")
+ .IsRequired();
+
+ b.HasKey("LoginProvider", "ProviderKey");
+
+ b.HasIndex("UserId");
+
+ b.ToTable("AspNetUserLogins");
+ });
+
+ modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b =>
+ {
+ b.Property("UserId");
+
+ b.Property("RoleId");
+
+ b.HasKey("UserId", "RoleId");
+
+ b.HasIndex("RoleId");
+
+ b.ToTable("AspNetUserRoles");
+ });
+
+ modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b =>
+ {
+ b.Property("UserId");
+
+ b.Property("LoginProvider");
+
+ b.Property("Name");
+
+ b.Property("Value");
+
+ b.HasKey("UserId", "LoginProvider", "Name");
+
+ b.ToTable("AspNetUserTokens");
+ });
+
+ modelBuilder.Entity("isn.Data.ApiKey", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd();
+
+ b.Property("UserId")
+ .IsRequired();
+
+ b.HasKey("Id");
+
+ b.HasIndex("UserId");
+
+ b.ToTable("ApiKeys");
+ });
+
+ modelBuilder.Entity("isn.Data.ApplicationUser", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd();
+
+ b.Property("AccessFailedCount");
+
+ b.Property("ConcurrencyStamp")
+ .IsConcurrencyToken();
+
+ b.Property("Email")
+ .HasMaxLength(256);
+
+ b.Property("EmailConfirmed");
+
+ b.Property("FullName");
+
+ b.Property("LockoutEnabled");
+
+ b.Property("LockoutEnd");
+
+ b.Property("NormalizedEmail")
+ .HasMaxLength(256);
+
+ b.Property("NormalizedUserName")
+ .HasMaxLength(256);
+
+ b.Property("PasswordHash");
+
+ b.Property("PhoneNumber");
+
+ b.Property("PhoneNumberConfirmed");
+
+ b.Property("SecurityStamp");
+
+ b.Property("TwoFactorEnabled");
+
+ b.Property("UserName")
+ .HasMaxLength(256);
+
+ b.HasKey("Id");
+
+ b.HasIndex("NormalizedEmail")
+ .HasName("EmailIndex");
+
+ b.HasIndex("NormalizedUserName")
+ .IsUnique()
+ .HasName("UserNameIndex");
+
+ b.ToTable("AspNetUsers");
+ });
+
+ modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b =>
+ {
+ b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole")
+ .WithMany()
+ .HasForeignKey("RoleId")
+ .OnDelete(DeleteBehavior.Cascade);
+ });
+
+ modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b =>
+ {
+ b.HasOne("isn.Data.ApplicationUser")
+ .WithMany()
+ .HasForeignKey("UserId")
+ .OnDelete(DeleteBehavior.Cascade);
+ });
+
+ modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b =>
+ {
+ b.HasOne("isn.Data.ApplicationUser")
+ .WithMany()
+ .HasForeignKey("UserId")
+ .OnDelete(DeleteBehavior.Cascade);
+ });
+
+ modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b =>
+ {
+ b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole")
+ .WithMany()
+ .HasForeignKey("RoleId")
+ .OnDelete(DeleteBehavior.Cascade);
+
+ b.HasOne("isn.Data.ApplicationUser")
+ .WithMany()
+ .HasForeignKey("UserId")
+ .OnDelete(DeleteBehavior.Cascade);
+ });
+
+ modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b =>
+ {
+ b.HasOne("isn.Data.ApplicationUser")
+ .WithMany()
+ .HasForeignKey("UserId")
+ .OnDelete(DeleteBehavior.Cascade);
+ });
+
+ modelBuilder.Entity("isn.Data.ApiKey", b =>
+ {
+ b.HasOne("isn.Data.ApplicationUser", "User")
+ .WithMany()
+ .HasForeignKey("UserId")
+ .OnDelete(DeleteBehavior.Cascade);
+ });
+#pragma warning restore 612, 618
+ }
+ }
+}
diff --git a/src/isnd/Migrations/20210502153508_api-keys.cs b/src/isnd/Migrations/20210502153508_api-keys.cs
new file mode 100644
index 0000000..844a35a
--- /dev/null
+++ b/src/isnd/Migrations/20210502153508_api-keys.cs
@@ -0,0 +1,39 @@
+using Microsoft.EntityFrameworkCore.Migrations;
+
+namespace isndhost.Migrations
+{
+ public partial class apikeys : Migration
+ {
+ protected override void Up(MigrationBuilder migrationBuilder)
+ {
+ migrationBuilder.CreateTable(
+ name: "ApiKeys",
+ columns: table => new
+ {
+ Id = table.Column