diff --git a/.gitignore b/.gitignore index 68dafe9..2c6c27e 100644 --- a/.gitignore +++ b/.gitignore @@ -1,24 +1,8 @@ .sass-cache/ -/src/isnd/bin/ -/src/isnd/obj/ -/src/isn/obj -/src/isn/bin -/src/isn/.vscode/ -/test/isnd.tests/obj/ -/test/isnd.tests/bin/ -/packages/ +bin/ +obj/ bower_components/ -/test/isn.tests/bin -/test/isn.tests/obj/ -appsettings.Testing.json -appsettings.Development.json -/wwwroot/.sass-cache/ -/src/isnd/wwwroot/.sass-cache/ -/src/isn.abst/bin -/src/isn.abst/obj -/src/isnd/packages/ -/test/data/test-isn/bin/ -/test/data/test-isn/obj +appsettings.*.json .fake /artifacts/ /.vs/ diff --git a/Directory.Build.props b/Directory.Build.props new file mode 100644 index 0000000..ff02831 --- /dev/null +++ b/Directory.Build.props @@ -0,0 +1,11 @@ + + + net10.0 + true + WTFPL + 1.0.8 + true + 85fd766d-5d23-4476-aed1-463b2942e86a + WTFPL + + \ No newline at end of file diff --git a/GitVersion.yml b/GitVersion.yml deleted file mode 100644 index 122fa7e..0000000 --- a/GitVersion.yml +++ /dev/null @@ -1,5 +0,0 @@ -mode: Mainline -branches: {} -ignore: - sha: [] -merge-message-formats: {} diff --git a/README.md b/README.md index 0e34410..b4f0003 100644 --- a/README.md +++ b/README.md @@ -12,7 +12,7 @@ isnd& # get an api-key from -isn push -k -s http://localhost:5000/index.json your-lame-versionned.nupkg +isn push -k -s http://localhost:5000/api/v3/index.json your-lame-versionned.nupkg wget http://localhost:5000/package/index.json?q=your&prerelease=true&semVerLevel=2.0.0 ```` @@ -55,8 +55,8 @@ sudo systemctl enable isnd ````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 cp -a src/isn/bin/Release/net10.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 ```` @@ -66,12 +66,15 @@ sudo ln -s /usr/local/lib/isn/isn /usr/local/bin/isn set -e # compiler tout dotnet build -c Release -dotnet publish -c Release -f net7.0 src/isnd +# publier +dotnet publish -c Release src/isnd +dotnet publish -c Release src/isn + # MAJ du serveur sudo systemctl stop isnd -sudo cp -a src/isnd/bin/Release/net7.0/publish/* /srv/www/isnd +sudo cp -a src/isnd/bin/Release/net10.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 +sudo cp -a src/isn/bin/Release/net10.0/* /usr/local/lib/isn +sudo chown -R root:root /usr/local/lib/isn ```` diff --git a/contrib/testinstnuget/call.lines b/contrib/testinstnuget/call.lines index c6abc1a..e69de29 100644 --- a/contrib/testinstnuget/call.lines +++ b/contrib/testinstnuget/call.lines @@ -1,2 +0,0 @@ -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 deleted file mode 100644 index 5c81113..0000000 --- a/contrib/testinstnuget/urls.adoc +++ /dev/null @@ -1,4 +0,0 @@ -= URL's - - - diff --git a/contrib/upgrade-isn-isnd.sh b/contrib/upgrade-isn-isnd.sh index 8a93152..457348b 100755 --- a/contrib/upgrade-isn-isnd.sh +++ b/contrib/upgrade-isn-isnd.sh @@ -3,11 +3,14 @@ set -e # compiler tout dotnet build -c Release -dotnet publish -c Release -f netcoreapp2.1 src/isnd +dotnet publish -c Release src/isnd +dotnet publish -c Release src/isn # MAJ du serveur sudo systemctl stop isnd -sudo cp -a src/isnd/bin/Release/netcoreapp2.1/publish/* /srv/www/isnd +sudo cp -a src/isnd/bin/Release/net10.0/publish/* /srv/www/isnd sudo systemctl start isnd # MAJ du client -sudo cp -a src/isn/bin/Release/netcoreapp2.1/* /usr/local/lib/isn -sudo chown -R root.root /usr/local/lib/isn +sudo cp -a src/isn/bin/Release/net10.0/* /usr/local/lib/isn +sudo chown -R root:root /usr/local/lib/isn + + diff --git a/isn.sln b/isn.sln index 1e20c9f..41f4f70 100644 --- a/isn.sln +++ b/isn.sln @@ -1,101 +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 + +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.abstract", "src\isn.abstract\isn.abstract.csproj", "{F0671F01-ADD7-47A6-8A29-1AAC8565DBE2}" +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(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 + {F0671F01-ADD7-47A6-8A29-1AAC8565DBE2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {F0671F01-ADD7-47A6-8A29-1AAC8565DBE2}.Debug|Any CPU.Build.0 = Debug|Any CPU + {F0671F01-ADD7-47A6-8A29-1AAC8565DBE2}.Debug|x64.ActiveCfg = Debug|Any CPU + {F0671F01-ADD7-47A6-8A29-1AAC8565DBE2}.Debug|x64.Build.0 = Debug|Any CPU + {F0671F01-ADD7-47A6-8A29-1AAC8565DBE2}.Debug|x86.ActiveCfg = Debug|Any CPU + {F0671F01-ADD7-47A6-8A29-1AAC8565DBE2}.Debug|x86.Build.0 = Debug|Any CPU + {F0671F01-ADD7-47A6-8A29-1AAC8565DBE2}.Release|Any CPU.ActiveCfg = Release|Any CPU + {F0671F01-ADD7-47A6-8A29-1AAC8565DBE2}.Release|Any CPU.Build.0 = Release|Any CPU + {F0671F01-ADD7-47A6-8A29-1AAC8565DBE2}.Release|x64.ActiveCfg = Release|Any CPU + {F0671F01-ADD7-47A6-8A29-1AAC8565DBE2}.Release|x64.Build.0 = Release|Any CPU + {F0671F01-ADD7-47A6-8A29-1AAC8565DBE2}.Release|x86.ActiveCfg = Release|Any CPU + {F0671F01-ADD7-47A6-8A29-1AAC8565DBE2}.Release|x86.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + 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} + {F0671F01-ADD7-47A6-8A29-1AAC8565DBE2} = {E8A2DF68-847A-4D88-B002-64FB666F696C} + EndGlobalSection +EndGlobal diff --git a/src/Directory.Build.props b/src/Directory.Build.props new file mode 100644 index 0000000..ff02831 --- /dev/null +++ b/src/Directory.Build.props @@ -0,0 +1,11 @@ + + + net10.0 + true + WTFPL + 1.0.8 + true + 85fd766d-5d23-4476-aed1-463b2942e86a + WTFPL + + \ No newline at end of file diff --git a/src/isn.abst/isn.abst.csproj b/src/isn.abst/isn.abst.csproj deleted file mode 100644 index 411e36d..0000000 --- a/src/isn.abst/isn.abst.csproj +++ /dev/null @@ -1,15 +0,0 @@ - - - 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.abst/ApiConfig.cs b/src/isn.abstract/ApiConfig.cs similarity index 89% rename from src/isn.abst/ApiConfig.cs rename to src/isn.abstract/ApiConfig.cs index 42d9633..907b0e6 100644 --- a/src/isn.abst/ApiConfig.cs +++ b/src/isn.abstract/ApiConfig.cs @@ -1,4 +1,6 @@ -using isn.abst; + + +using Isn.Abstract; namespace isnd.Entities { @@ -16,6 +18,5 @@ namespace isnd.Entities public const string CatalogLeaf = "catalog-leaf"; public const string Delete = "delete"; public const string Registration = "registration"; - internal const string GetNuspec = Constants.SpecFileEstension; } -} \ No newline at end of file +} diff --git a/src/isn.abst/ApiIndexViewModel.cs b/src/isn.abstract/ApiIndexViewModel.cs similarity index 91% rename from src/isn.abst/ApiIndexViewModel.cs rename to src/isn.abstract/ApiIndexViewModel.cs index a8e9709..7034ae9 100644 --- a/src/isn.abst/ApiIndexViewModel.cs +++ b/src/isn.abstract/ApiIndexViewModel.cs @@ -1,7 +1,7 @@ using isnd.Data.Catalog; using Newtonsoft.Json; -namespace isn.Abstract +namespace Isn.Abstract { public class ApiIndexViewModel : HappyIdOwner { diff --git a/src/isn.abst/Constants.cs b/src/isn.abstract/Constants.cs similarity index 62% rename from src/isn.abst/Constants.cs rename to src/isn.abstract/Constants.cs index 59ab714..1a12cc5 100644 --- a/src/isn.abst/Constants.cs +++ b/src/isn.abstract/Constants.cs @@ -1,10 +1,14 @@ -namespace isn.abst +namespace Isn.Abstract { public static class Constants { public const string PaquetFileEstension = "nupkg"; public const string SpecFileEstension = "nuspec"; public const string JsonFileEstension = "json"; - public const string PackageRootServerPrefix = "~/pkgs/"; + + /** + * The prefix for API routes. + */ + public const string APIPrefix = "api/v3/"; } } \ No newline at end of file diff --git a/src/isn.abst/HappyIdOwner.cs b/src/isn.abstract/HappyIdOwner.cs similarity index 100% rename from src/isn.abst/HappyIdOwner.cs rename to src/isn.abstract/HappyIdOwner.cs diff --git a/src/isn.abst/Resource.cs b/src/isn.abstract/Resource.cs similarity index 90% rename from src/isn.abst/Resource.cs rename to src/isn.abstract/Resource.cs index ab069a5..da21eff 100644 --- a/src/isn.abst/Resource.cs +++ b/src/isn.abstract/Resource.cs @@ -1,7 +1,7 @@ using isnd.Data.Catalog; using Newtonsoft.Json; -namespace isn.Abstract +namespace Isn.Abstract { public class Resource : HappyIdOwner { diff --git a/src/isn.abst/SafeNameAttribute.cs b/src/isn.abstract/SafeNameAttribute.cs similarity index 100% rename from src/isn.abst/SafeNameAttribute.cs rename to src/isn.abstract/SafeNameAttribute.cs diff --git a/src/isn.abstract/isn.abstract.csproj b/src/isn.abstract/isn.abstract.csproj new file mode 100644 index 0000000..8fdeb35 --- /dev/null +++ b/src/isn.abstract/isn.abstract.csproj @@ -0,0 +1,13 @@ + + + true + 1.0.0.0 + 1.0.0.0 + 1.0.0+Branch.main.Sha.654065d7fef34ba4ed2fb3bca5bc91de9ad16e85 + 1.0.0 + + + + + + \ No newline at end of file diff --git a/src/isn/Constants.cs b/src/isn/Constants.cs index e68c94c..3bb3423 100644 --- a/src/isn/Constants.cs +++ b/src/isn/Constants.cs @@ -1,4 +1,4 @@ -namespace isn +namespace Isn { internal static class Constants { diff --git a/src/isn/IDataProtector.cs b/src/isn/IDataProtector.cs index d91ea23..e0904ac 100644 --- a/src/isn/IDataProtector.cs +++ b/src/isn/IDataProtector.cs @@ -2,7 +2,7 @@ using System; using System.Collections.Generic; using System.Text; -namespace isn +namespace Isn { public interface IDataProtector { diff --git a/src/isn/IsnSourceSettings.cs b/src/isn/IsnSourceSettings.cs index bc2491c..1390359 100644 --- a/src/isn/IsnSourceSettings.cs +++ b/src/isn/IsnSourceSettings.cs @@ -1,4 +1,4 @@ -namespace isn +namespace Isn { public class IsnSourceSettings { diff --git a/src/isn/IsndErrorMessage.cs b/src/isn/IsndErrorMessage.cs index fa6b062..bdd61d7 100644 --- a/src/isn/IsndErrorMessage.cs +++ b/src/isn/IsndErrorMessage.cs @@ -1,4 +1,4 @@ -namespace isn +namespace Isn { public class IsndErrorMessage { diff --git a/src/isn/Program.cs b/src/isn/Program.cs index 8a543a6..5abbbb3 100644 --- a/src/isn/Program.cs +++ b/src/isn/Program.cs @@ -5,14 +5,14 @@ using System.Linq; using Mono.Options; using Newtonsoft.Json; -namespace isn +namespace Isn { public partial class Program { public static void LoadConfig() { - FileInfo cfgSettingIf = new FileInfo(_configFileName); + FileInfo cfgSettingIf = new FileInfo(ConfigFileName); if (cfgSettingIf.Exists) { var json = File.ReadAllText(cfgSettingIf.FullName); @@ -29,12 +29,13 @@ namespace isn { "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") - ; + private static string ConfigDirectory => + Environment.GetEnvironmentVariable("ISN_CONFIG_DIR") + ?? Path.Combine( + Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), + ".isn"); + + private static string ConfigFileName => Path.Combine(ConfigDirectory, "config.json"); public const string push = "push"; static readonly OptionSet options = new OptionSet { diff --git a/src/isn/Settings.cs b/src/isn/Settings.cs index 2bd0def..9c4c1c8 100644 --- a/src/isn/Settings.cs +++ b/src/isn/Settings.cs @@ -2,7 +2,7 @@ using System; using System.Collections.Generic; using System.Linq; -namespace isn +namespace Isn { public class SourceSettings { diff --git a/src/isn/SourceHelpers.cs b/src/isn/SourceHelpers.cs index ba8762b..8b1ba21 100644 --- a/src/isn/SourceHelpers.cs +++ b/src/isn/SourceHelpers.cs @@ -4,9 +4,9 @@ using System.Net; using System.Net.Http; using System.Threading.Tasks; using Newtonsoft.Json; -using isn.Abstract; +using Isn.Abstract; -namespace isn +namespace Isn { public static class SourceHelpers { diff --git a/src/isn/UploadFilesToServerUsingHttpClient.cs b/src/isn/UploadFilesToServerUsingHttpClient.cs index ce51297..de5c33b 100644 --- a/src/isn/UploadFilesToServerUsingHttpClient.cs +++ b/src/isn/UploadFilesToServerUsingHttpClient.cs @@ -5,7 +5,7 @@ using System.Net; using System.Net.Http; using System.Threading.Tasks; -namespace isn +namespace Isn { public static class UploadFilesToServerUsingHttpClient { diff --git a/src/isn/UploadFilesToServerUsingWebRequest.cs b/src/isn/UploadFilesToServerUsingWebRequest.cs index 21ead1a..cd2aad8 100644 --- a/src/isn/UploadFilesToServerUsingWebRequest.cs +++ b/src/isn/UploadFilesToServerUsingWebRequest.cs @@ -5,7 +5,7 @@ using System.Text; using System.Threading.Tasks; using Newtonsoft.Json; -namespace isn +namespace Isn { public class UploadFilesToServerUsingWebRequest diff --git a/src/isn/commands/PushCommand.cs b/src/isn/commands/PushCommand.cs index bf4e08a..6820814 100644 --- a/src/isn/commands/PushCommand.cs +++ b/src/isn/commands/PushCommand.cs @@ -6,7 +6,7 @@ using System.Net.Http; using System.Threading.Tasks; using Newtonsoft.Json; -namespace isn +namespace Isn { public static class PushCommand { @@ -18,6 +18,8 @@ namespace isn source = Program.Settings.Sources.First(s=>s.Value.Alias==source).Key; if (source==null) throw new InvalidOperationException("source is invalid"); + var resolvedApiKey = string.IsNullOrWhiteSpace(apikey) ? ResolveSourceApiKey(source) : apikey; + var resources = SourceHelpers.GetServerResources(source); if (resources.Resources == null) throw new InvalidOperationException("source gave no resource"); @@ -38,7 +40,7 @@ namespace isn try { Console.WriteLine("Connecting to "+ pubRes.Id); - return client.UploadFilesToServer(new Uri(pubRes.Id), fi, apikey); + return client.UploadFilesToServer(new Uri(pubRes.Id), fi, resolvedApiKey); } catch (HttpRequestException hrex) { @@ -64,5 +66,25 @@ namespace isn return report; } } + + private static string ResolveSourceApiKey(string source) + { + if (string.IsNullOrWhiteSpace(source)) + { + return null; + } + + if (Program.Settings.Sources.Values.Any(s => s.Alias == source)) + { + source = Program.Settings.Sources.First(s => s.Value.Alias == source).Key; + } + + if (Program.Settings.Sources.TryGetValue(source, out var sourceSettings)) + { + return sourceSettings.GetClearApiKey(); + } + + return null; + } } } \ No newline at end of file diff --git a/src/isn/commands/push.cs b/src/isn/commands/push.cs index 58daf08..e00e307 100644 --- a/src/isn/commands/push.cs +++ b/src/isn/commands/push.cs @@ -4,7 +4,7 @@ using System.Threading.Tasks; using Newtonsoft.Json; -namespace isn +namespace Isn { partial class Program diff --git a/src/isn/commands/set-api-key.cs b/src/isn/commands/set-api-key.cs index 7b2f018..7da6021 100644 --- a/src/isn/commands/set-api-key.cs +++ b/src/isn/commands/set-api-key.cs @@ -4,7 +4,7 @@ using System.IO; using Newtonsoft.Json; -namespace isn +namespace Isn { partial class Program @@ -61,7 +61,7 @@ namespace isn } public static void SaveConfig() { - FileInfo cfgSettingIf = new FileInfo(_configFileName); + FileInfo cfgSettingIf = new FileInfo(ConfigFileName); if (!cfgSettingIf.Directory.Exists) cfgSettingIf.Directory.Create(); File.WriteAllText( cfgSettingIf.FullName, diff --git a/src/isn/commands/show.config.cs b/src/isn/commands/show.config.cs index ff00e36..b1d8cd8 100644 --- a/src/isn/commands/show.config.cs +++ b/src/isn/commands/show.config.cs @@ -3,7 +3,7 @@ using System.Collections.Generic; using Newtonsoft.Json; -namespace isn +namespace Isn { partial class Program diff --git a/src/isn/commands/sources.add.cs b/src/isn/commands/sources.add.cs index 90d2aed..b01fe91 100644 --- a/src/isn/commands/sources.add.cs +++ b/src/isn/commands/sources.add.cs @@ -3,7 +3,7 @@ using System; using System.Collections.Generic; using Newtonsoft.Json; -namespace isn +namespace Isn { partial class Program diff --git a/src/isn/commands/sources.list.cs b/src/isn/commands/sources.list.cs index 6a1239a..79a8c7f 100644 --- a/src/isn/commands/sources.list.cs +++ b/src/isn/commands/sources.list.cs @@ -3,7 +3,7 @@ using System.Collections.Generic; using System.Linq; using Newtonsoft.Json; -namespace isn +namespace Isn { partial class Program { diff --git a/src/isn/commands/sources.set-default.cs b/src/isn/commands/sources.set-default.cs index 8a691b3..a30a1c1 100644 --- a/src/isn/commands/sources.set-default.cs +++ b/src/isn/commands/sources.set-default.cs @@ -1,7 +1,7 @@ using System; using System.Linq; -namespace isn +namespace Isn { partial class Program diff --git a/src/isn/isn.csproj b/src/isn/isn.csproj index a61ef49..a99ff29 100644 --- a/src/isn/isn.csproj +++ b/src/isn/isn.csproj @@ -1,25 +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 + 1.0.0.0 + 1.0.0.0 + 1.0.0+Branch.main.Sha.654065d7fef34ba4ed2fb3bca5bc91de9ad16e85 + 1.0.0 - - - - - - + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + + + + \ No newline at end of file diff --git a/src/isn/model/PushReport.cs b/src/isn/model/PushReport.cs index d8e082e..61f54bd 100644 --- a/src/isn/model/PushReport.cs +++ b/src/isn/model/PushReport.cs @@ -1,7 +1,7 @@ using System.Net; using System.Text; -namespace isn +namespace Isn { public class PushReport { diff --git a/src/isnd/Controllers/AccountController.cs b/src/isnd/Controllers/AccountController.cs index e367166..9774f30 100644 --- a/src/isnd/Controllers/AccountController.cs +++ b/src/isnd/Controllers/AccountController.cs @@ -29,14 +29,22 @@ namespace isnd.Controllers IAuthenticationSchemeProvider schemeProvider, SignInManager signInManager, UserManager userManager, - IOptions startupAdminListConfig ) + IOptions startupAdminListConfig, + RoleManager roleManager + ) { _schemeProvider = schemeProvider; _signInManager = signInManager; _userManager = userManager; _startupAdminList = startupAdminListConfig.Value; + var roles = roleManager.Roles.ToList(); + if (!roles.Any(r => r.Name == IsndConstants.AdministratorRoleName)) + { + var role = new IdentityRole(IsndConstants.AdministratorRoleName); + roleManager.CreateAsync(role).GetAwaiter().GetResult(); + } } - + /// /// Entry point into the login workflow /// @@ -240,10 +248,10 @@ namespace isnd.Controllers [Authorize] public async Task GetAdminrole() { - string username = User.FindFirstValue(ClaimTypes.NameIdentifier); - if (_startupAdminList.Users.Contains(username)) + string userEmail = User.FindFirstValue(ClaimTypes.Email); + if (_startupAdminList.Users.Contains(userEmail)) { - var user = await _userManager.FindByNameAsync(username); + var user = await _userManager.FindByEmailAsync(userEmail); var roles = await _userManager.GetRolesAsync(user); if (!roles.Contains(IsndConstants.AdministratorRoleName)) { diff --git a/src/isnd/Controllers/ApiKeysController.cs b/src/isnd/Controllers/ApiKeysController.cs index bbc2d21..fc5c278 100644 --- a/src/isnd/Controllers/ApiKeysController.cs +++ b/src/isnd/Controllers/ApiKeysController.cs @@ -65,7 +65,7 @@ namespace isnd.Controllers return View(); } ApiKey newKey = new ApiKey { UserId = userid, Name = model.Name, - CreationDate = DateTime.Now }; + CreationDate = DateTime.UtcNow }; _ = dbContext.ApiKeys.Add(newKey); _ = await dbContext.SaveChangesAsync(); return View("Details", new DetailModel { Name = newKey.Name, diff --git a/src/isnd/Controllers/HomeController.cs b/src/isnd/Controllers/HomeController.cs index 45512c1..399d228 100644 --- a/src/isnd/Controllers/HomeController.cs +++ b/src/isnd/Controllers/HomeController.cs @@ -1,12 +1,9 @@ -using Microsoft.AspNetCore.Hosting; -using Microsoft.AspNetCore.Mvc; -using Microsoft.Extensions.Logging; +using Microsoft.AspNetCore.Mvc; using isnd.Data; using System.Linq; using isnd.ViewModels; -using Unleash; using System.Reflection; -using Microsoft.EntityFrameworkCore; +using Isn.Abstract; namespace isnd.Controllers { @@ -16,31 +13,33 @@ namespace isnd.Controllers public class HomeController : Controller { private readonly ApplicationDbContext _dbContext; - private readonly IUnleash _unleashĈlient; /// /// Home controller ctor /// /// - /// public HomeController( - ApplicationDbContext dbContext, - IUnleash unleashĈlient) + ApplicationDbContext dbContext) { _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 - }); } + /** + * Index + */ + public IActionResult Index() + { + return View(new HomeIndexViewModel + { + PkgCount = _dbContext.Packages + .Where(p => p.Versions.Count > 0) + .Count() + }); + } + + /** + * About + */ public IActionResult About() { ViewData["Message"] = "Your application description page."; @@ -48,18 +47,24 @@ namespace isnd.Controllers return View(); } + /** + * Contact + */ public IActionResult Contact() { ViewData["Message"] = "Your contact page."; - var ass = typeof(isn.Abstract.Resource).GetType().Assembly; + var ass = typeof(Resource).GetType().Assembly; var attrs = ass.GetCustomAttributes(true); // ass.GetCustomAttributes(true); - System.Reflection.AssemblyFileVersionAttribute v = (AssemblyFileVersionAttribute) + System.Reflection.AssemblyFileVersionAttribute v = (AssemblyFileVersionAttribute) GetType().Assembly.GetCustomAttribute(typeof(AssemblyFileVersionAttribute)); return View(); } + /** + * Privacy + */ public IActionResult Privacy() { ViewData["Message"] = "Your Privacy page."; @@ -67,6 +72,9 @@ namespace isnd.Controllers return View(ViewData); } + /** + * Error + */ public IActionResult Error() { return View(); diff --git a/src/isnd/Controllers/Packages/ApiIndex.cs b/src/isnd/Controllers/Packages/ApiIndex.cs index 56930cf..1a1e435 100644 --- a/src/isnd/Controllers/Packages/ApiIndex.cs +++ b/src/isnd/Controllers/Packages/ApiIndex.cs @@ -1,12 +1,9 @@ using Microsoft.AspNetCore.Mvc; -using isnd.Entities; using isnd.Services; -using isn.Abstract; -using isn.abst; using isnd.Interfaces; -using Unleash; using System.Linq; - +using isnd.Entities; +using Isn.Abstract; namespace isnd.Controllers { @@ -22,18 +19,17 @@ namespace isnd.Controllers /// Api Controller Constructor /// /// - /// - public ApiController(IPackageManager pm, IUnleash unleashĈlient) + public ApiController(IPackageManager pm) { packageManager = pm; - resources = packageManager.GetResources(unleashĈlient).ToArray(); + resources = packageManager.GetResources().ToArray(); } /// /// API index /// /// - [HttpGet(Constants.PackageRootServerPrefix + ApiConfig.IndexDotJson)] + [HttpGet("~/" + Constants.APIPrefix + ApiConfig.IndexDotJson)] public IActionResult ApiIndex() { return Ok(new ApiIndexViewModel(packageManager.CatalogBaseUrl + ApiConfig.IndexDotJson){ Version = PackageManager.BASE_API_LEVEL, Resources = resources }); diff --git a/src/isnd/Controllers/Packages/PackagesController.AutoComplete.cs b/src/isnd/Controllers/Packages/PackagesController.AutoComplete.cs index 6f1fbf6..b3d5711 100644 --- a/src/isnd/Controllers/Packages/PackagesController.AutoComplete.cs +++ b/src/isnd/Controllers/Packages/PackagesController.AutoComplete.cs @@ -1,7 +1,7 @@ using isnd.Services; using isnd.Entities; using Microsoft.AspNetCore.Mvc; -using isn.abst; +using Isn.Abstract; namespace isnd.Controllers { @@ -9,7 +9,7 @@ namespace isnd.Controllers { // GET /autocomplete?id=isn.protocol&prerelease=true - [HttpGet(Constants.PackageRootServerPrefix + ApiConfig.AutoComplete)] + [HttpGet("~/" + Constants.APIPrefix + ApiConfig.AutoComplete)] public IActionResult AutoComplete( string id, string semVerLevel, diff --git a/src/isnd/Controllers/Packages/PackagesController.Catalog.cs b/src/isnd/Controllers/Packages/PackagesController.Catalog.cs index 950291d..d5a2357 100644 --- a/src/isnd/Controllers/Packages/PackagesController.Catalog.cs +++ b/src/isnd/Controllers/Packages/PackagesController.Catalog.cs @@ -1,14 +1,7 @@ -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; +using Isn.Abstract; namespace isnd.Controllers { @@ -16,14 +9,14 @@ namespace isnd.Controllers { // https://docs.microsoft.com/en-us/nuget/api/catalog-resource#versioning - [HttpGet(Constants.PackageRootServerPrefix + ApiConfig.Catalog)] + [HttpGet("~/" + Constants.APIPrefix + ApiConfig.Catalog)] public async Task CatalogIndex() { return Ok(await packageManager.GetCatalogIndexAsync()); } - [HttpGet(Constants.PackageRootServerPrefix + "{apiVersion}/" + ApiConfig.Registration + "/{id}/{lower}.json")] + [HttpGet("~/" + Constants.APIPrefix + "{apiVersion}/" + ApiConfig.Registration + "/{id}/{lower}.json")] public async Task CatalogRegistration(string apiVersion, string id, string lower) { if (lower.Equals("index", System.StringComparison.InvariantCultureIgnoreCase)) diff --git a/src/isnd/Controllers/Packages/PackagesController.Delete.cs b/src/isnd/Controllers/Packages/PackagesController.Delete.cs index 74c193e..bccefb4 100644 --- a/src/isnd/Controllers/Packages/PackagesController.Delete.cs +++ b/src/isnd/Controllers/Packages/PackagesController.Delete.cs @@ -5,13 +5,13 @@ using isnd.Entities; using System.ComponentModel.DataAnnotations; using isnd.Attributes; using System.Security.Claims; -using isn.abst; +using Isn.Abstract; namespace isnd.Controllers { public partial class PackagesController { - [HttpDelete(Constants.PackageRootServerPrefix + ApiConfig.Delete + "/{id}/{lower?}/{type?}")] + [HttpDelete("~/" + Constants.APIPrefix + ApiConfig.Delete + "/{id}/{lower?}/{type?}")] public async Task ApiDelete( [FromRoute][SafeName][Required] string id, [FromRoute][SafeName][Required] string lower, diff --git a/src/isnd/Controllers/Packages/PackagesController.GetPackage.cs b/src/isnd/Controllers/Packages/PackagesController.GetPackage.cs index b044ae7..182184a 100644 --- a/src/isnd/Controllers/Packages/PackagesController.GetPackage.cs +++ b/src/isnd/Controllers/Packages/PackagesController.GetPackage.cs @@ -1,22 +1,38 @@ using System.ComponentModel.DataAnnotations; using System.IO; +using System.Linq; using Microsoft.AspNetCore.Mvc; -using isnd.Attributes; +using Isn.Abstract; using isnd.Entities; -using isn.abst; +using isnd.Attributes; namespace isnd.Controllers { public partial class PackagesController { - // Web get the paquet - [HttpGet(Constants.PackageRootServerPrefix + ApiConfig.GetPackage + "/{id}/{lower}/{idf}-{lowerf}." + // NuGet v3 flat-container versions index. + [HttpGet("~/" + Constants.APIPrefix + ApiConfig.GetPackage + "/{id}/" + ApiConfig.IndexDotJson)] + public IActionResult GetPackageVersionsById( + [FromRoute][SafeName][Required] string id) + { + var versions = dbContext.PackageVersions + .Where(v => v.PackageId.ToLower() == id.ToLower()) + .Select(v => v.FullString) + .Distinct() + .OrderBy(v => v) + .ToArray(); + + return Ok(new { versions }); + } + + // NuGet v3 flat-container package download by filename, e.g. id.version.nupkg. + [HttpGet("~/" + Constants.APIPrefix + ApiConfig.GetPackage + "/{id}/{lower}/{filename}." + Constants.PaquetFileEstension)] - public IActionResult GetPackage( - [FromRoute][SafeName][Required] string id, + public IActionResult GetPackageByFilename( + [FromRoute][SafeName][Required] string id, [FromRoute][SafeName][Required] string lower, - [FromRoute] string idf, [FromRoute] string lowerf) + [FromRoute][SafeName][Required] string filename) { var pkgpath = Path.Combine(isndSettings.PackagesRootDir, id, lower, $"{id}-{lower}." + Constants.PaquetFileEstension @@ -32,7 +48,9 @@ namespace isnd.Controllers } // Web get spec - [HttpGet(Constants.PackageRootServerPrefix + Constants.SpecFileEstension + "/{id}/{lower}/{idf}-{lowerf}." + [HttpGet("~/" + Constants.APIPrefix + Constants.SpecFileEstension + "/{id}/{lower}/{idf}.{lowerf}." + + Constants.SpecFileEstension)] + [HttpGet("~/" + Constants.APIPrefix + Constants.SpecFileEstension + "/{id}/{lower}/{idf}-{lowerf}." + Constants.SpecFileEstension)] public IActionResult GetNuspec( [FromRoute][SafeName][Required] string id, diff --git a/src/isnd/Controllers/Packages/PackagesController.GetVersions.cs b/src/isnd/Controllers/Packages/PackagesController.GetVersions.cs index 5cdda12..55cfad9 100644 --- a/src/isnd/Controllers/Packages/PackagesController.GetVersions.cs +++ b/src/isnd/Controllers/Packages/PackagesController.GetVersions.cs @@ -1,13 +1,13 @@ using Microsoft.AspNetCore.Mvc; using NuGet.Versioning; using isnd.Entities; -using isn.abst; +using Isn.Abstract; namespace isnd.Controllers { public partial class PackagesController { - [HttpGet(Constants.PackageRootServerPrefix + ApiConfig.GetVersion + "/{id}/{lower}/" + ApiConfig.IndexDotJson)] + [HttpGet("~/" + Constants.APIPrefix + ApiConfig.GetVersion + "/{id}/{lower}/" + ApiConfig.IndexDotJson)] public IActionResult GetVersions( string id, string lower, diff --git a/src/isnd/Controllers/Packages/PackagesController.Put.cs b/src/isnd/Controllers/Packages/PackagesController.Put.cs index f1570e8..12335ce 100644 --- a/src/isnd/Controllers/Packages/PackagesController.Put.cs +++ b/src/isnd/Controllers/Packages/PackagesController.Put.cs @@ -15,7 +15,7 @@ using isnd.Data; using isnd.Helpers; using isnd.Entities; using Microsoft.AspNetCore.Http; -using isn.abst; +using Isn.Abstract; using isnd.Data.Packages; using Microsoft.AspNetCore.Authorization; @@ -25,18 +25,18 @@ namespace isnd.Controllers public partial class PackagesController { // TODO [Authorize(Policy = IsndConstants.RequireValidApiKey)] - [HttpPut(Constants.PackageRootServerPrefix + ApiConfig.Publish)] + [HttpPut("~/" + Constants.APIPrefix + ApiConfig.Publish)] public async Task Put() { try { var clientVersionId = Request.Headers["X-NuGet-Client-Version"]; - string apiKey = Request.Headers["X-NuGet-ApiKey"][0]; + string apiKey = Request.Headers["X-NuGet-ApiKey"].FirstOrDefault() ?? string.Empty; ViewData["versionId"] = typeof(PackagesController).Assembly.FullName; var files = new List(); ViewData["files"] = files; - var clearkey = protector.Unprotect(apiKey); + var clearkey = ApiKeyProtector.TryUnprotectKey(protector, apiKey); var apikey = dbContext.ApiKeys.SingleOrDefault(k => k.Id == clearkey); if (apikey == null) { @@ -46,7 +46,7 @@ namespace isnd.Controllers Commit commit = new Commit { Action = PackageAction.PublishPackage, - TimeStamp = DateTime.Now + TimeStamp = DateTime.UtcNow }; dbContext.Commits.Add(commit); @@ -145,7 +145,7 @@ namespace isnd.Controllers dbContext.PackageVersions.Remove(v); } - // FIXME default type or null + // Default package type must be non-null for composite key consistency. if (types==null || types.Count==0) dbContext.PackageVersions.Add (new PackageVersion{ @@ -157,7 +157,7 @@ namespace isnd.Controllers Revision = version.Revision, IsPrerelease = version.IsPrerelease, FullString = version.ToFullString(), - Type = null, + Type = string.Empty, LatestCommit = commit }); else @@ -171,14 +171,14 @@ namespace isnd.Controllers Patch = version.Patch, IsPrerelease = version.IsPrerelease, FullString = version.ToFullString(), - Type = type.Name, + Type = type.Name ?? string.Empty, LatestCommit = commit }; dbContext.PackageVersions.Add(pkgver); } await dbContext.SaveChangesAsync(); - packageManager.ÛpdateCatalogForAsync(commit); + packageManager.UpdateCatalogForAsync(commit); logger.LogInformation($"new paquet : {spec.Name}"); } diff --git a/src/isnd/Controllers/Packages/PackagesController.Search.cs b/src/isnd/Controllers/Packages/PackagesController.Search.cs index 8a67c63..61c19d1 100644 --- a/src/isnd/Controllers/Packages/PackagesController.Search.cs +++ b/src/isnd/Controllers/Packages/PackagesController.Search.cs @@ -3,7 +3,7 @@ using System; using System.Threading.Tasks; using Microsoft.AspNetCore.Mvc; using isnd.Entities; -using isn.abst; +using Isn.Abstract; namespace isnd.Controllers { @@ -11,7 +11,7 @@ namespace isnd.Controllers public partial class PackagesController { // GET {@id}?q={QUERY}&skip={SKIP}&take={TAKE}&prerelease={PRERELEASE}&semVerLevel={SEMVERLEVEL}&packageType={PACKAGETYPE} - [HttpGet(Constants.PackageRootServerPrefix + ApiConfig.Search)] + [HttpGet("~/" + Constants.APIPrefix + ApiConfig.Search)] public IActionResult Search( string q, int skip = 0, diff --git a/src/isnd/Controllers/Packages/PackagesController.WebViews.cs b/src/isnd/Controllers/Packages/PackagesController.WebViews.cs index eb6c477..6432261 100644 --- a/src/isnd/Controllers/Packages/PackagesController.WebViews.cs +++ b/src/isnd/Controllers/Packages/PackagesController.WebViews.cs @@ -60,6 +60,7 @@ namespace isnd.Controllers { return NotFound(); } + pkgtype ??= string.Empty; // var report = await packageManager.DeletePackageAsync(id, lower, type); var packageVersion = await dbContext.PackageVersions.Include(p => p.Package) @@ -77,6 +78,7 @@ namespace isnd.Controllers public async Task DeleteConfirmed(string PackageId, string FullString, string Type) { + Type ??= string.Empty; PackageVersion packageVersion = await dbContext.PackageVersions .Include(pv => pv.Package) .FirstOrDefaultAsync(m => m.PackageId == PackageId diff --git a/src/isnd/Controllers/Packages/PackagesController.cs b/src/isnd/Controllers/Packages/PackagesController.cs index ddf9e69..c890314 100644 --- a/src/isnd/Controllers/Packages/PackagesController.cs +++ b/src/isnd/Controllers/Packages/PackagesController.cs @@ -1,21 +1,13 @@ -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; +using Isn.Abstract; namespace isnd.Controllers { @@ -30,13 +22,15 @@ namespace isnd.Controllers private readonly IsndSettings isndSettings; readonly ApplicationDbContext dbContext; private readonly IPackageManager packageManager; - private readonly IUnleash unleashĈlient; + + /** + * PackagesController + */ public PackagesController( ILoggerFactory loggerFactory, IDataProtectionProvider provider, IOptions isndOptions, - IUnleash unleashĈlient, ApplicationDbContext dbContext, IPackageManager pm) { @@ -45,8 +39,7 @@ namespace isnd.Controllers protector = provider.CreateProtector(isndSettings.ProtectionTitle); this.dbContext = dbContext; packageManager = pm; - this.unleashĈlient = unleashĈlient; - resources = packageManager.GetResources(unleashĈlient).ToArray(); + resources = packageManager.GetResources().ToArray(); } } } diff --git a/src/isnd/Data/ApiKeys/ApiKey.cs b/src/isnd/Data/ApiKeys/ApiKey.cs index 517e93a..f891693 100644 --- a/src/isnd/Data/ApiKeys/ApiKey.cs +++ b/src/isnd/Data/ApiKeys/ApiKey.cs @@ -20,6 +20,6 @@ namespace isnd.Data.ApiKeys public DateTime CreationDate { get; set; } - public bool IsValid => CreationDate.AddDays(ValidityPeriodInDays) > DateTime.Now; + public bool IsValid => CreationDate.AddDays(ValidityPeriodInDays) > DateTime.UtcNow; } } \ No newline at end of file diff --git a/src/isnd/Data/ApplicationDbContext.cs b/src/isnd/Data/ApplicationDbContext.cs index d01c0b6..c965f22 100644 --- a/src/isnd/Data/ApplicationDbContext.cs +++ b/src/isnd/Data/ApplicationDbContext.cs @@ -22,6 +22,19 @@ namespace isnd.Data public ApplicationDbContext(DbContextOptions options) : base(options) { } + protected override void OnModelCreating(ModelBuilder builder) + { + base.OnModelCreating(builder); + + builder.Entity(entity => + { + entity.HasKey(v => new { v.PackageId, v.FullString, v.Type }); + entity.Property(v => v.Type) + .IsRequired() + .HasDefaultValue(string.Empty); + }); + } + /// /// User API keys /// diff --git a/src/isnd/Data/Packages/PackageVersion.cs b/src/isnd/Data/Packages/PackageVersion.cs index 9120d93..83047ee 100644 --- a/src/isnd/Data/Packages/PackageVersion.cs +++ b/src/isnd/Data/Packages/PackageVersion.cs @@ -1,7 +1,7 @@ using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations.Schema; -using isn.abst; +using Isn.Abstract; using isnd.Data.Catalog; using isnd.Data.Packages; using isnd.Data.Packages.Catalog; @@ -38,7 +38,6 @@ namespace isnd.Data /// [StringLength(256)] [Required] - [Key] public string FullString { get; set; } public bool IsPrerelease { get; set; } @@ -58,9 +57,9 @@ namespace isnd.Data public string CommitId { get => CommitNId.ToString(); } public virtual Commit LatestCommit { get; set; } - public string NugetLink => $"{Constants.PaquetFileEstension}/{PackageId}/{FullString}/{PackageId}-{FullString}." + public string NugetLink => $"{Constants.APIPrefix}{Constants.PaquetFileEstension}/{PackageId}/{FullString}/{PackageId}-{FullString}." + Constants.PaquetFileEstension; - public string NuspecLink => $"{Constants.SpecFileEstension}/{PackageId}/{FullString}/{PackageId}-{FullString}." + public string NuspecLink => $"{Constants.APIPrefix}{Constants.SpecFileEstension}/{PackageId}/{FullString}/{PackageId}-{FullString}." + Constants.SpecFileEstension; public string SementicVersionString { get => $"{Major}.{Minor}.{Patch}"; } diff --git a/src/isnd/Entities/SmtpSettings.cs b/src/isnd/Entities/SmtpSettings.cs index 5a4a861..3d2ae1d 100644 --- a/src/isnd/Entities/SmtpSettings.cs +++ b/src/isnd/Entities/SmtpSettings.cs @@ -2,11 +2,30 @@ namespace isnd.Entities { public class SmtpSettings { - public string Server {get; set;} + /** + * The SMTP server host. + */ + public string Host {get; set;} + + /** + * The SMTP server port. + */ public int Port {get; set;} + /** + * The name of the sender. + */ public string SenderName {get; set;} - public string SenderEMail {get; set;} + /** + * The email address of the sender. + */ + public string SenderEmail {get; set;} + /** + * The username for SMTP authentication. + */ public string UserName {get; set;} + /** + * The password for SMTP authentication. + */ public string Password {get; set;} } } \ No newline at end of file diff --git a/src/isnd/Helpers/ApiKeyProtector.cs b/src/isnd/Helpers/ApiKeyProtector.cs new file mode 100644 index 0000000..98f5e72 --- /dev/null +++ b/src/isnd/Helpers/ApiKeyProtector.cs @@ -0,0 +1,82 @@ +using System; +using System.Security.Cryptography; +using System.Text; +using Microsoft.AspNetCore.DataProtection; + +namespace isnd.Helpers +{ + public static class ApiKeyProtector + { + private const byte LegacyDelta = 145; + + public static string TryUnprotectKey(IDataProtector protector, string protectedValue) + { + if (string.IsNullOrWhiteSpace(protectedValue)) + { + return protectedValue; + } + + if (TryUnprotect(protector, protectedValue, out var unprotected)) + { + return unprotected; + } + + if (TryUnprotectLegacy(protectedValue, out unprotected)) + { + return unprotected; + } + + return protectedValue; + } + + private static bool TryUnprotect(IDataProtector protector, string protectedValue, out string unprotected) + { + try + { + unprotected = protector.Unprotect(protectedValue); + return true; + } + catch (CryptographicException) + { + unprotected = null; + return false; + } + catch (FormatException) + { + unprotected = null; + return false; + } + } + + private static bool TryUnprotectLegacy(string protectedValue, out string unprotected) + { + try + { + unprotected = UnprotectLegacy(protectedValue); + return true; + } + catch (FormatException) + { + unprotected = null; + return false; + } + } + + public static string UnprotectLegacy(string protectedValue) + { + if (string.IsNullOrWhiteSpace(protectedValue)) + { + return protectedValue; + } + + var bytes = Convert.FromBase64String(protectedValue); + var unprotectedBytes = new byte[bytes.Length]; + for (var index = 0; index < bytes.Length; index++) + { + unprotectedBytes[index] = (byte)(bytes[index] ^ LegacyDelta); + } + + return Encoding.UTF8.GetString(unprotectedBytes); + } + } +} diff --git a/src/isnd/Helpers/PackageVersionHelpers.cs b/src/isnd/Helpers/PackageVersionHelpers.cs index 49f41d6..a60d283 100644 --- a/src/isnd/Helpers/PackageVersionHelpers.cs +++ b/src/isnd/Helpers/PackageVersionHelpers.cs @@ -2,7 +2,7 @@ using System; using System.Collections.Generic; using System.Linq; using System.Security.Claims; -using isn.abst; +using Isn.Abstract; using isnd.Data; using isnd.Data.Catalog; using isnd.Entities; diff --git a/src/isnd/Helpers/UnleashHelpers.cs b/src/isnd/Helpers/UnleashHelpers.cs deleted file mode 100644 index d107bff..0000000 --- a/src/isnd/Helpers/UnleashHelpers.cs +++ /dev/null @@ -1,32 +0,0 @@ -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/IPackageManager.cs b/src/isnd/Interfaces/IPackageManager.cs index 8e5c2b2..9dd8ffc 100644 --- a/src/isnd/Interfaces/IPackageManager.cs +++ b/src/isnd/Interfaces/IPackageManager.cs @@ -1,35 +1,76 @@ using System.Collections.Generic; using System.Threading.Tasks; -using isn.Abstract; -using isnd.Controllers; +using Isn.Abstract; 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 { + /** + * IPackageManager + */ public interface IPackageManager { + /** + * CatalogBaseUrl + */ string CatalogBaseUrl { get; } + /** + * AutoComplete generation + */ AutoCompleteResult AutoComplete(string pkgid, int skip, int take, bool prerelease = false, string packageType = null); + /** + * GetVersions + */ 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); + /** + * GetResources + */ + IEnumerable GetResources(); + /** + * UpdateCatalogForAsync + */ + Task UpdateCatalogForAsync(Commit commit); + /** + * DeletePackageAsync + */ Task DeletePackageAsync(string pkgid, string version, string type); + + /** + * UserAskForPackageDeletionAsync + */ Task UserAskForPackageDeletionAsync(string userid, string pkgId, string lower, string type); + + /** + * GetPackageAsync + */ Task GetPackageAsync(string pkgid, string version, string type); + + /** + * GetCatalogEntryAsync + */ Task GetCatalogEntryAsync(string pkgId, string version, string pkgType); + /** + * SearchCatalogEntriesById + */ IEnumerable SearchCatalogEntriesById(string pkgId, string semver, string pkgType); + /** + * GetCatalogIndexAsync + */ Task GetCatalogIndexAsync(); + /** + * GetPackageRegistrationIndexAsync + */ Task GetPackageRegistrationIndexAsync(RegistrationPageIndexQuery query); - +/** + * SearchPackageAsync + */ Task SearchPackageAsync(RegistrationPageIndexQuery query); } diff --git a/src/isnd/Migrations/20260705174847_net10.Designer.cs b/src/isnd/Migrations/20260705174847_net10.Designer.cs new file mode 100644 index 0000000..75e320c --- /dev/null +++ b/src/isnd/Migrations/20260705174847_net10.Designer.cs @@ -0,0 +1,458 @@ +// +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; + +#nullable disable + +namespace isndhost.Migrations +{ + [DbContext(typeof(ApplicationDbContext))] + [Migration("20260705174847_net10")] + partial class net10 + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "10.0.9") + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b => + { + b.Property("Id") + .HasColumnType("text"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("text"); + + b.Property("Name") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("NormalizedName") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedName") + .IsUnique() + .HasDatabaseName("RoleNameIndex"); + + b.ToTable("AspNetRoles", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("ClaimType") + .HasColumnType("text"); + + b.Property("ClaimValue") + .HasColumnType("text"); + + b.Property("RoleId") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetRoleClaims", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("ClaimType") + .HasColumnType("text"); + + b.Property("ClaimValue") + .HasColumnType("text"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserClaims", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.Property("LoginProvider") + .HasColumnType("text"); + + b.Property("ProviderKey") + .HasColumnType("text"); + + b.Property("ProviderDisplayName") + .HasColumnType("text"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("LoginProvider", "ProviderKey"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserLogins", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.Property("UserId") + .HasColumnType("text"); + + b.Property("RoleId") + .HasColumnType("text"); + + b.HasKey("UserId", "RoleId"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetUserRoles", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.Property("UserId") + .HasColumnType("text"); + + b.Property("LoginProvider") + .HasColumnType("text"); + + b.Property("Name") + .HasColumnType("text"); + + b.Property("Value") + .HasColumnType("text"); + + b.HasKey("UserId", "LoginProvider", "Name"); + + b.ToTable("AspNetUserTokens", (string)null); + }); + + modelBuilder.Entity("isnd.Data.ApiKeys.ApiKey", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("text"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Name") + .HasColumnType("text"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("text"); + + b.Property("ValidityPeriodInDays") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("ApiKeys"); + }); + + modelBuilder.Entity("isnd.Data.ApplicationUser", b => + { + b.Property("Id") + .HasColumnType("text"); + + b.Property("AccessFailedCount") + .HasColumnType("integer"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("text"); + + b.Property("Email") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("EmailConfirmed") + .HasColumnType("boolean"); + + b.Property("FullName") + .HasColumnType("text"); + + b.Property("LockoutEnabled") + .HasColumnType("boolean"); + + b.Property("LockoutEnd") + .HasColumnType("timestamp with time zone"); + + b.Property("NormalizedEmail") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("NormalizedUserName") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("PasswordHash") + .HasColumnType("text"); + + b.Property("PhoneNumber") + .HasColumnType("text"); + + b.Property("PhoneNumberConfirmed") + .HasColumnType("boolean"); + + b.Property("SecurityStamp") + .HasColumnType("text"); + + b.Property("TwoFactorEnabled") + .HasColumnType("boolean"); + + b.Property("UserName") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedEmail") + .HasDatabaseName("EmailIndex"); + + b.HasIndex("NormalizedUserName") + .IsUnique() + .HasDatabaseName("UserNameIndex"); + + b.ToTable("AspNetUsers", (string)null); + }); + + modelBuilder.Entity("isnd.Data.PackageVersion", b => + { + b.Property("FullString") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("CommitNId") + .HasColumnType("bigint"); + + b.Property("IsPrerelease") + .HasColumnType("boolean"); + + b.Property("Major") + .HasColumnType("integer"); + + b.Property("Minor") + .HasColumnType("integer"); + + b.Property("PackageId") + .IsRequired() + .HasMaxLength(1024) + .HasColumnType("character varying(1024)"); + + b.Property("Patch") + .HasColumnType("integer"); + + b.Property("Revision") + .HasColumnType("integer"); + + b.Property("Type") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.HasKey("FullString"); + + b.HasIndex("CommitNId"); + + b.HasIndex("PackageId"); + + b.ToTable("PackageVersions"); + }); + + modelBuilder.Entity("isnd.Data.Packages.Commit", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("Action") + .HasColumnType("integer"); + + b.Property("TimeStamp") + .HasColumnType("timestamp with time zone"); + + b.HasKey("Id"); + + b.ToTable("Commits"); + }); + + modelBuilder.Entity("isnd.Data.Packages.Package", b => + { + b.Property("Id") + .HasMaxLength(1024) + .HasColumnType("character varying(1024)"); + + b.Property("CommitNId") + .HasColumnType("bigint"); + + b.Property("CommitTimeStamp") + .HasColumnType("timestamp with time zone"); + + b.Property("Description") + .HasMaxLength(1024) + .HasColumnType("character varying(1024)"); + + b.Property("OwnerId") + .IsRequired() + .HasColumnType("text"); + + b.Property("Public") + .HasColumnType("boolean"); + + b.HasKey("Id"); + + b.HasIndex("CommitNId"); + + b.HasIndex("OwnerId"); + + b.ToTable("Packages"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.HasOne("isnd.Data.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.HasOne("isnd.Data.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("isnd.Data.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.HasOne("isnd.Data.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("isnd.Data.ApiKeys.ApiKey", b => + { + b.HasOne("isnd.Data.ApplicationUser", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("isnd.Data.PackageVersion", b => + { + b.HasOne("isnd.Data.Packages.Commit", "LatestCommit") + .WithMany("Versions") + .HasForeignKey("CommitNId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("isnd.Data.Packages.Package", "Package") + .WithMany("Versions") + .HasForeignKey("PackageId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("LatestCommit"); + + b.Navigation("Package"); + }); + + modelBuilder.Entity("isnd.Data.Packages.Package", b => + { + b.HasOne("isnd.Data.Packages.Commit", "LatestVersion") + .WithMany() + .HasForeignKey("CommitNId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("isnd.Data.ApplicationUser", "Owner") + .WithMany() + .HasForeignKey("OwnerId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("LatestVersion"); + + b.Navigation("Owner"); + }); + + modelBuilder.Entity("isnd.Data.Packages.Commit", b => + { + b.Navigation("Versions"); + }); + + modelBuilder.Entity("isnd.Data.Packages.Package", b => + { + b.Navigation("Versions"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/isnd/Migrations/20260705174847_net10.cs b/src/isnd/Migrations/20260705174847_net10.cs new file mode 100644 index 0000000..56789db --- /dev/null +++ b/src/isnd/Migrations/20260705174847_net10.cs @@ -0,0 +1,79 @@ +using Microsoft.EntityFrameworkCore.Migrations; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; + +#nullable disable + +namespace isndhost.Migrations +{ + /// + public partial class net10 : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AlterColumn( + name: "Id", + table: "Commits", + type: "bigint", + nullable: false, + oldClrType: typeof(long), + oldType: "bigint") + .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn) + .OldAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.SerialColumn); + + migrationBuilder.AlterColumn( + name: "Id", + table: "AspNetUserClaims", + type: "integer", + nullable: false, + oldClrType: typeof(int), + oldType: "integer") + .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn) + .OldAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.SerialColumn); + + migrationBuilder.AlterColumn( + name: "Id", + table: "AspNetRoleClaims", + type: "integer", + nullable: false, + oldClrType: typeof(int), + oldType: "integer") + .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn) + .OldAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.SerialColumn); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.AlterColumn( + name: "Id", + table: "Commits", + type: "bigint", + nullable: false, + oldClrType: typeof(long), + oldType: "bigint") + .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.SerialColumn) + .OldAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); + + migrationBuilder.AlterColumn( + name: "Id", + table: "AspNetUserClaims", + type: "integer", + nullable: false, + oldClrType: typeof(int), + oldType: "integer") + .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.SerialColumn) + .OldAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); + + migrationBuilder.AlterColumn( + name: "Id", + table: "AspNetRoleClaims", + type: "integer", + nullable: false, + oldClrType: typeof(int), + oldType: "integer") + .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.SerialColumn) + .OldAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); + } + } +} diff --git a/src/isnd/Migrations/20260705211205_pkgver_composite_identity_fix.Designer.cs b/src/isnd/Migrations/20260705211205_pkgver_composite_identity_fix.Designer.cs new file mode 100644 index 0000000..77ef305 --- /dev/null +++ b/src/isnd/Migrations/20260705211205_pkgver_composite_identity_fix.Designer.cs @@ -0,0 +1,457 @@ +// +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; + +#nullable disable + +namespace isndhost.Migrations +{ + [DbContext(typeof(ApplicationDbContext))] + [Migration("20260705211205_pkgver_composite_identity_fix")] + partial class pkgver_composite_identity_fix + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "10.0.9") + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b => + { + b.Property("Id") + .HasColumnType("text"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("text"); + + b.Property("Name") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("NormalizedName") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedName") + .IsUnique() + .HasDatabaseName("RoleNameIndex"); + + b.ToTable("AspNetRoles", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("ClaimType") + .HasColumnType("text"); + + b.Property("ClaimValue") + .HasColumnType("text"); + + b.Property("RoleId") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetRoleClaims", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("ClaimType") + .HasColumnType("text"); + + b.Property("ClaimValue") + .HasColumnType("text"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserClaims", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.Property("LoginProvider") + .HasColumnType("text"); + + b.Property("ProviderKey") + .HasColumnType("text"); + + b.Property("ProviderDisplayName") + .HasColumnType("text"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("LoginProvider", "ProviderKey"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserLogins", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.Property("UserId") + .HasColumnType("text"); + + b.Property("RoleId") + .HasColumnType("text"); + + b.HasKey("UserId", "RoleId"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetUserRoles", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.Property("UserId") + .HasColumnType("text"); + + b.Property("LoginProvider") + .HasColumnType("text"); + + b.Property("Name") + .HasColumnType("text"); + + b.Property("Value") + .HasColumnType("text"); + + b.HasKey("UserId", "LoginProvider", "Name"); + + b.ToTable("AspNetUserTokens", (string)null); + }); + + modelBuilder.Entity("isnd.Data.ApiKeys.ApiKey", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("text"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Name") + .HasColumnType("text"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("text"); + + b.Property("ValidityPeriodInDays") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("ApiKeys"); + }); + + modelBuilder.Entity("isnd.Data.ApplicationUser", b => + { + b.Property("Id") + .HasColumnType("text"); + + b.Property("AccessFailedCount") + .HasColumnType("integer"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("text"); + + b.Property("Email") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("EmailConfirmed") + .HasColumnType("boolean"); + + b.Property("FullName") + .HasColumnType("text"); + + b.Property("LockoutEnabled") + .HasColumnType("boolean"); + + b.Property("LockoutEnd") + .HasColumnType("timestamp with time zone"); + + b.Property("NormalizedEmail") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("NormalizedUserName") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("PasswordHash") + .HasColumnType("text"); + + b.Property("PhoneNumber") + .HasColumnType("text"); + + b.Property("PhoneNumberConfirmed") + .HasColumnType("boolean"); + + b.Property("SecurityStamp") + .HasColumnType("text"); + + b.Property("TwoFactorEnabled") + .HasColumnType("boolean"); + + b.Property("UserName") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedEmail") + .HasDatabaseName("EmailIndex"); + + b.HasIndex("NormalizedUserName") + .IsUnique() + .HasDatabaseName("UserNameIndex"); + + b.ToTable("AspNetUsers", (string)null); + }); + + modelBuilder.Entity("isnd.Data.PackageVersion", b => + { + b.Property("PackageId") + .HasMaxLength(1024) + .HasColumnType("character varying(1024)"); + + b.Property("FullString") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("Type") + .ValueGeneratedOnAdd() + .HasMaxLength(256) + .HasColumnType("character varying(256)") + .HasDefaultValue(""); + + b.Property("CommitNId") + .HasColumnType("bigint"); + + b.Property("IsPrerelease") + .HasColumnType("boolean"); + + b.Property("Major") + .HasColumnType("integer"); + + b.Property("Minor") + .HasColumnType("integer"); + + b.Property("Patch") + .HasColumnType("integer"); + + b.Property("Revision") + .HasColumnType("integer"); + + b.HasKey("PackageId", "FullString", "Type"); + + b.HasIndex("CommitNId"); + + b.ToTable("PackageVersions"); + }); + + modelBuilder.Entity("isnd.Data.Packages.Commit", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("Action") + .HasColumnType("integer"); + + b.Property("TimeStamp") + .HasColumnType("timestamp with time zone"); + + b.HasKey("Id"); + + b.ToTable("Commits"); + }); + + modelBuilder.Entity("isnd.Data.Packages.Package", b => + { + b.Property("Id") + .HasMaxLength(1024) + .HasColumnType("character varying(1024)"); + + b.Property("CommitNId") + .HasColumnType("bigint"); + + b.Property("CommitTimeStamp") + .HasColumnType("timestamp with time zone"); + + b.Property("Description") + .HasMaxLength(1024) + .HasColumnType("character varying(1024)"); + + b.Property("OwnerId") + .IsRequired() + .HasColumnType("text"); + + b.Property("Public") + .HasColumnType("boolean"); + + b.HasKey("Id"); + + b.HasIndex("CommitNId"); + + b.HasIndex("OwnerId"); + + b.ToTable("Packages"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.HasOne("isnd.Data.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.HasOne("isnd.Data.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("isnd.Data.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.HasOne("isnd.Data.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("isnd.Data.ApiKeys.ApiKey", b => + { + b.HasOne("isnd.Data.ApplicationUser", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("isnd.Data.PackageVersion", b => + { + b.HasOne("isnd.Data.Packages.Commit", "LatestCommit") + .WithMany("Versions") + .HasForeignKey("CommitNId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("isnd.Data.Packages.Package", "Package") + .WithMany("Versions") + .HasForeignKey("PackageId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("LatestCommit"); + + b.Navigation("Package"); + }); + + modelBuilder.Entity("isnd.Data.Packages.Package", b => + { + b.HasOne("isnd.Data.Packages.Commit", "LatestVersion") + .WithMany() + .HasForeignKey("CommitNId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("isnd.Data.ApplicationUser", "Owner") + .WithMany() + .HasForeignKey("OwnerId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("LatestVersion"); + + b.Navigation("Owner"); + }); + + modelBuilder.Entity("isnd.Data.Packages.Commit", b => + { + b.Navigation("Versions"); + }); + + modelBuilder.Entity("isnd.Data.Packages.Package", b => + { + b.Navigation("Versions"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/isnd/Migrations/20260705211205_pkgver_composite_identity_fix.cs b/src/isnd/Migrations/20260705211205_pkgver_composite_identity_fix.cs new file mode 100644 index 0000000..f61688a --- /dev/null +++ b/src/isnd/Migrations/20260705211205_pkgver_composite_identity_fix.cs @@ -0,0 +1,72 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace isndhost.Migrations +{ + /// + public partial class pkgver_composite_identity_fix : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropPrimaryKey( + name: "PK_PackageVersions", + table: "PackageVersions"); + + migrationBuilder.DropIndex( + name: "IX_PackageVersions_PackageId", + table: "PackageVersions"); + + migrationBuilder.Sql("UPDATE \"PackageVersions\" SET \"Type\" = '' WHERE \"Type\" IS NULL;"); + + migrationBuilder.AlterColumn( + name: "Type", + table: "PackageVersions", + type: "character varying(256)", + maxLength: 256, + nullable: false, + defaultValue: "", + oldClrType: typeof(string), + oldType: "character varying(256)", + oldMaxLength: 256, + oldNullable: true); + + migrationBuilder.AddPrimaryKey( + name: "PK_PackageVersions", + table: "PackageVersions", + columns: new[] { "PackageId", "FullString", "Type" }); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropPrimaryKey( + name: "PK_PackageVersions", + table: "PackageVersions"); + + migrationBuilder.AlterColumn( + name: "Type", + table: "PackageVersions", + type: "character varying(256)", + maxLength: 256, + nullable: true, + oldClrType: typeof(string), + oldType: "character varying(256)", + oldMaxLength: 256, + oldDefaultValue: ""); + + migrationBuilder.Sql("UPDATE \"PackageVersions\" SET \"Type\" = NULL WHERE \"Type\" = '';"); + + migrationBuilder.AddPrimaryKey( + name: "PK_PackageVersions", + table: "PackageVersions", + column: "FullString"); + + migrationBuilder.CreateIndex( + name: "IX_PackageVersions_PackageId", + table: "PackageVersions", + column: "PackageId"); + } + } +} diff --git a/src/isnd/Migrations/ApplicationDbContextModelSnapshot.cs b/src/isnd/Migrations/ApplicationDbContextModelSnapshot.cs index 7d8c883..917c397 100644 --- a/src/isnd/Migrations/ApplicationDbContextModelSnapshot.cs +++ b/src/isnd/Migrations/ApplicationDbContextModelSnapshot.cs @@ -6,6 +6,8 @@ using Microsoft.EntityFrameworkCore.Storage.ValueConversion; using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; using isnd.Data; +#nullable disable + namespace isndhost.Migrations { [DbContext(typeof(ApplicationDbContext))] @@ -15,23 +17,161 @@ namespace isndhost.Migrations { #pragma warning disable 612, 618 modelBuilder - .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.SerialColumn) - .HasAnnotation("ProductVersion", "2.1.14-servicing-32113") + .HasAnnotation("ProductVersion", "10.0.9") .HasAnnotation("Relational:MaxIdentifierLength", 63); + NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b => + { + b.Property("Id") + .HasColumnType("text"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("text"); + + b.Property("Name") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("NormalizedName") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedName") + .IsUnique() + .HasDatabaseName("RoleNameIndex"); + + b.ToTable("AspNetRoles", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("ClaimType") + .HasColumnType("text"); + + b.Property("ClaimValue") + .HasColumnType("text"); + + b.Property("RoleId") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetRoleClaims", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("ClaimType") + .HasColumnType("text"); + + b.Property("ClaimValue") + .HasColumnType("text"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserClaims", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.Property("LoginProvider") + .HasColumnType("text"); + + b.Property("ProviderKey") + .HasColumnType("text"); + + b.Property("ProviderDisplayName") + .HasColumnType("text"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("LoginProvider", "ProviderKey"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserLogins", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.Property("UserId") + .HasColumnType("text"); + + b.Property("RoleId") + .HasColumnType("text"); + + b.HasKey("UserId", "RoleId"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetUserRoles", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.Property("UserId") + .HasColumnType("text"); + + b.Property("LoginProvider") + .HasColumnType("text"); + + b.Property("Name") + .HasColumnType("text"); + + b.Property("Value") + .HasColumnType("text"); + + b.HasKey("UserId", "LoginProvider", "Name"); + + b.ToTable("AspNetUserTokens", (string)null); + }); + modelBuilder.Entity("isnd.Data.ApiKeys.ApiKey", b => { b.Property("Id") - .ValueGeneratedOnAdd(); + .ValueGeneratedOnAdd() + .HasColumnType("text"); - b.Property("CreationDate"); + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); - b.Property("Name"); + b.Property("Name") + .HasColumnType("text"); b.Property("UserId") - .IsRequired(); + .IsRequired() + .HasColumnType("text"); - b.Property("ValidityPeriodInDays"); + b.Property("ValidityPeriodInDays") + .HasColumnType("integer"); b.HasKey("Id"); @@ -43,63 +183,124 @@ namespace isndhost.Migrations modelBuilder.Entity("isnd.Data.ApplicationUser", b => { b.Property("Id") - .ValueGeneratedOnAdd(); + .HasColumnType("text"); - b.Property("AccessFailedCount"); + b.Property("AccessFailedCount") + .HasColumnType("integer"); b.Property("ConcurrencyStamp") - .IsConcurrencyToken(); + .IsConcurrencyToken() + .HasColumnType("text"); b.Property("Email") - .HasMaxLength(256); + .HasMaxLength(256) + .HasColumnType("character varying(256)"); - b.Property("EmailConfirmed"); + b.Property("EmailConfirmed") + .HasColumnType("boolean"); - b.Property("FullName"); + b.Property("FullName") + .HasColumnType("text"); - b.Property("LockoutEnabled"); + b.Property("LockoutEnabled") + .HasColumnType("boolean"); - b.Property("LockoutEnd"); + b.Property("LockoutEnd") + .HasColumnType("timestamp with time zone"); b.Property("NormalizedEmail") - .HasMaxLength(256); + .HasMaxLength(256) + .HasColumnType("character varying(256)"); b.Property("NormalizedUserName") - .HasMaxLength(256); + .HasMaxLength(256) + .HasColumnType("character varying(256)"); - b.Property("PasswordHash"); + b.Property("PasswordHash") + .HasColumnType("text"); - b.Property("PhoneNumber"); + b.Property("PhoneNumber") + .HasColumnType("text"); - b.Property("PhoneNumberConfirmed"); + b.Property("PhoneNumberConfirmed") + .HasColumnType("boolean"); - b.Property("SecurityStamp"); + b.Property("SecurityStamp") + .HasColumnType("text"); - b.Property("TwoFactorEnabled"); + b.Property("TwoFactorEnabled") + .HasColumnType("boolean"); b.Property("UserName") - .HasMaxLength(256); + .HasMaxLength(256) + .HasColumnType("character varying(256)"); b.HasKey("Id"); b.HasIndex("NormalizedEmail") - .HasName("EmailIndex"); + .HasDatabaseName("EmailIndex"); b.HasIndex("NormalizedUserName") .IsUnique() - .HasName("UserNameIndex"); + .HasDatabaseName("UserNameIndex"); - b.ToTable("AspNetUsers"); + b.ToTable("AspNetUsers", (string)null); + }); + + modelBuilder.Entity("isnd.Data.PackageVersion", b => + { + b.Property("PackageId") + .HasMaxLength(1024) + .HasColumnType("character varying(1024)"); + + b.Property("FullString") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("Type") + .ValueGeneratedOnAdd() + .HasMaxLength(256) + .HasColumnType("character varying(256)") + .HasDefaultValue(""); + + b.Property("CommitNId") + .HasColumnType("bigint"); + + b.Property("IsPrerelease") + .HasColumnType("boolean"); + + b.Property("Major") + .HasColumnType("integer"); + + b.Property("Minor") + .HasColumnType("integer"); + + b.Property("Patch") + .HasColumnType("integer"); + + b.Property("Revision") + .HasColumnType("integer"); + + b.HasKey("PackageId", "FullString", "Type"); + + b.HasIndex("CommitNId"); + + b.ToTable("PackageVersions"); }); modelBuilder.Entity("isnd.Data.Packages.Commit", b => { b.Property("Id") - .ValueGeneratedOnAdd(); + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); - b.Property("Action"); + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - b.Property("TimeStamp"); + b.Property("Action") + .HasColumnType("integer"); + + b.Property("TimeStamp") + .HasColumnType("timestamp with time zone"); b.HasKey("Id"); @@ -109,20 +310,25 @@ namespace isndhost.Migrations modelBuilder.Entity("isnd.Data.Packages.Package", b => { b.Property("Id") - .ValueGeneratedOnAdd() - .HasMaxLength(1024); + .HasMaxLength(1024) + .HasColumnType("character varying(1024)"); - b.Property("CommitNId"); + b.Property("CommitNId") + .HasColumnType("bigint"); - b.Property("CommitTimeStamp"); + b.Property("CommitTimeStamp") + .HasColumnType("timestamp with time zone"); b.Property("Description") - .HasMaxLength(1024); + .HasMaxLength(1024) + .HasColumnType("character varying(1024)"); b.Property("OwnerId") - .IsRequired(); + .IsRequired() + .HasColumnType("text"); - b.Property("Public"); + b.Property("Public") + .HasColumnType("boolean"); b.HasKey("Id"); @@ -133,145 +339,55 @@ namespace isndhost.Migrations b.ToTable("Packages"); }); - modelBuilder.Entity("isnd.Data.PackageVersion", b => - { - b.Property("FullString") - .ValueGeneratedOnAdd() - .HasMaxLength(256); - - b.Property("CommitNId"); - - b.Property("IsPrerelease"); - - b.Property("Major"); - - b.Property("Minor"); - - b.Property("PackageId") - .IsRequired() - .HasMaxLength(1024); - - b.Property("Patch"); - - b.Property("Revision"); - - b.Property("Type") - .HasMaxLength(256); - - b.HasKey("FullString"); - - b.HasIndex("CommitNId"); - - b.HasIndex("PackageId"); - - b.ToTable("PackageVersions"); - }); - - 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") + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) .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") + b.HasOne("isnd.Data.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) .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") + b.HasOne("isnd.Data.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) .IsRequired(); - - b.HasKey("LoginProvider", "ProviderKey"); - - b.HasIndex("UserId"); - - b.ToTable("AspNetUserLogins"); }); modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => { - b.Property("UserId"); + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); - b.Property("RoleId"); - - b.HasKey("UserId", "RoleId"); - - b.HasIndex("RoleId"); - - b.ToTable("AspNetUserRoles"); + b.HasOne("isnd.Data.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); }); 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"); + b.HasOne("isnd.Data.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); }); modelBuilder.Entity("isnd.Data.ApiKeys.ApiKey", b => @@ -279,20 +395,10 @@ namespace isndhost.Migrations b.HasOne("isnd.Data.ApplicationUser", "User") .WithMany() .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade); - }); + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); - modelBuilder.Entity("isnd.Data.Packages.Package", b => - { - b.HasOne("isnd.Data.Packages.Commit", "LatestVersion") - .WithMany() - .HasForeignKey("CommitNId") - .OnDelete(DeleteBehavior.Cascade); - - b.HasOne("isnd.Data.ApplicationUser", "Owner") - .WithMany() - .HasForeignKey("OwnerId") - .OnDelete(DeleteBehavior.Cascade); + b.Navigation("User"); }); modelBuilder.Entity("isnd.Data.PackageVersion", b => @@ -300,57 +406,47 @@ namespace isndhost.Migrations b.HasOne("isnd.Data.Packages.Commit", "LatestCommit") .WithMany("Versions") .HasForeignKey("CommitNId") - .OnDelete(DeleteBehavior.Cascade); + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); b.HasOne("isnd.Data.Packages.Package", "Package") .WithMany("Versions") .HasForeignKey("PackageId") - .OnDelete(DeleteBehavior.Cascade); + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("LatestCommit"); + + b.Navigation("Package"); }); - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + modelBuilder.Entity("isnd.Data.Packages.Package", b => { - b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole") + b.HasOne("isnd.Data.Packages.Commit", "LatestVersion") .WithMany() - .HasForeignKey("RoleId") - .OnDelete(DeleteBehavior.Cascade); + .HasForeignKey("CommitNId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("isnd.Data.ApplicationUser", "Owner") + .WithMany() + .HasForeignKey("OwnerId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("LatestVersion"); + + b.Navigation("Owner"); }); - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + modelBuilder.Entity("isnd.Data.Packages.Commit", b => { - b.HasOne("isnd.Data.ApplicationUser") - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade); + b.Navigation("Versions"); }); - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + modelBuilder.Entity("isnd.Data.Packages.Package", b => { - b.HasOne("isnd.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("isnd.Data.ApplicationUser") - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => - { - b.HasOne("isnd.Data.ApplicationUser") - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade); + b.Navigation("Versions"); }); #pragma warning restore 612, 618 } diff --git a/src/isnd/Services/EmailSender.cs b/src/isnd/Services/EmailSender.cs index 31b85e7..7f3d1fd 100644 --- a/src/isnd/Services/EmailSender.cs +++ b/src/isnd/Services/EmailSender.cs @@ -4,6 +4,7 @@ using Microsoft.AspNetCore.Identity.UI.Services; using Microsoft.Extensions.Options; using Microsoft.AspNetCore.Hosting; using MailKit.Net.Smtp; +using MailKit.Security; using MimeKit; using System; @@ -36,14 +37,26 @@ namespace isnd.Services { try { var message = new MimeMessage(); - message.From.Add(new MailboxAddress(Options.SenderName, Options.SenderEMail)); - message.To.Add(new MailboxAddress(name??email, email)); + message.From.Add(new MailboxAddress(Options.SenderName, Options.SenderEmail)); + message.To.Add(new MailboxAddress(name ?? email, email)); message.Body = new TextPart("html") { Text = body }; using (var client = new SmtpClient()) { - client.ServerCertificateValidationCallback = (s,c,h,e)=>true; - await client.ConnectAsync(Options.Server); - await client.AuthenticateAsync(Options.UserName, Options.Password); + client.ServerCertificateValidationCallback = (s,c,h,e) => true; + if (Options.Port > 0) + { + await client.ConnectAsync(Options.Host, Options.Port, SecureSocketOptions.Auto); + } + else + { + await client.ConnectAsync(Options.Host); + } + + if (!string.IsNullOrWhiteSpace(Options.UserName)) + { + await client.AuthenticateAsync(Options.UserName, Options.Password); + } + await client.SendAsync(message); await client.DisconnectAsync(true); } diff --git a/src/isnd/Services/PackageManager.cs b/src/isnd/Services/PackageManager.cs index 2dc301e..0ba1c04 100644 --- a/src/isnd/Services/PackageManager.cs +++ b/src/isnd/Services/PackageManager.cs @@ -1,39 +1,45 @@ using System; using System.Collections.Generic; using System.Linq; -using System.Text; using System.Threading.Tasks; -using isn.Abstract; +using Isn.Abstract; using isnd.Data; using isnd.Data.Catalog; using isnd.Data.Packages; -using isnd.Data.Packages.Catalog; using isnd.Entities; -using isnd.Helpers; using isnd.Interfaces; using isnd.ViewModels; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Options; using NuGet.Versioning; -using Unleash; namespace isnd.Services { public class PackageManager : IPackageManager { public const string BASE_API_LEVEL = "3.0.0"; + private static string NormalizePackageType(string packageType) => packageType ?? string.Empty; ApplicationDbContext dbContext; + /** + * Constructor for the PackageManager class. + * @param dbContext The database context. + * @param siteConfigOptionsOptions The options for the IsndSettings configuration. + */ public PackageManager(ApplicationDbContext dbContext, IOptions siteConfigOptionsOptions) { this.dbContext = dbContext; isndSettings = siteConfigOptionsOptions.Value; - extUrl = isndSettings.ExternalUrl + "/"; + extUrl = isndSettings.ExternalUrl + "/" +Constants.APIPrefix; } - public IEnumerable GetResources(IUnleash unleashClient) + /** + * GetResources + */ + + public IEnumerable GetResources() { var res = new List @@ -44,7 +50,8 @@ namespace isnd.Services }, // under dev, only leash in release mode - new Resource(extUrl + ApiConfig.GetPackage, "PackageBaseAddress/3.0.0") + // NuGet clients expect a trailing slash on PackageBaseAddress to resolve relative paths. + new Resource(extUrl + ApiConfig.GetPackage + "/", "PackageBaseAddress/3.0.0") { Comment = @"Package Base Address service - Base URL of where NuGet packages are stored, in the format https:///nupkg/{id-lower}/{version-lower}/{id-lower}.{version-lower}.nupkg" }, @@ -62,39 +69,15 @@ namespace isnd.Services Comment = "Base URL of storage where isn package registration info is stored. This base URL includes SemVer 2.0.0 packages." } }; - /* new Resource(extUrl + ApiConfig.Catalog, "Catalog/" + BASE_API_LEVEL) - { - Comment = "Package Catalog Index" - },*/ - - /* res.Add( - ); res.Add( - new Resource(extUrl + "v3.0.0/" + ApiConfig.Registration, "RegistrationsBaseUrl") - { - - Comment = "Base URL of storage where isn package registration info is stored in GZIP format. This base URL includes SemVer 2.0.0 packages." - }); - - res.Add( - new Resource(extUrl + "v3.0.0-beta/" + ApiConfig.Registration, "RegistrationsBaseUrl/3.0.0-beta") - { - Comment = "Base URL of storage where isn package registration info is stored in GZIP format. This base URL includes SemVer 2.0.0 packages." - }); - res.Add( - new Resource(extUrl + "v3.0.0-rc/" + ApiConfig.Registration,"RegistrationsBaseUrl/3.0.0-rc") - { - Comment = "Base URL of storage where isn package registration info is stored in GZIP format. This base URL includes SemVer 2.0.0 packages." - }); - res.Add(new Resource(extUrl + "v3.4.0/" + ApiConfig.Registration,"RegistrationsBaseUrl/3.4.0") - { - Comment = "Base URL of storage where isn package registration info is stored in GZIP format. This base URL includes SemVer 2.0.0 packages." - }); - - res.Add(); */ return res; } + /** + * AutoComplete generation + */ + + public AutoCompleteResult AutoComplete(string id, int skip, int take, bool prerelease = false, string packageType = null) @@ -135,18 +118,24 @@ namespace isnd.Services } - + /** + * CatalogBaseUrl + */ public string CatalogBaseUrl => extUrl; private IsndSettings isndSettings; private string extUrl; + /** + * GetCatalogIndexAsync + */ + public virtual async Task GetCatalogIndexAsync() { - return await ÛpdateCatalogForAsync(null); + return await UpdateCatalogForAsync(null); } - public async Task ÛpdateCatalogForAsync(Commit reason = null) + public async Task UpdateCatalogForAsync(Commit reason = null) { int i = 0; @@ -203,11 +192,12 @@ namespace isnd.Services public async Task DeletePackageAsync(string pkgid, string version, string type) { + type = NormalizePackageType(type); // TODO deletion on disk var commit = new Commit { Action = PackageAction.DeletePackage, - TimeStamp = DateTime.Now + TimeStamp = DateTime.UtcNow }; dbContext.Commits.Add(commit); var pkg = await dbContext.PackageVersions.SingleOrDefaultAsync( @@ -221,12 +211,13 @@ namespace isnd.Services } dbContext.PackageVersions.Remove(pkg); await dbContext.SaveChangesAsync(); - await ÛpdateCatalogForAsync(commit); + await UpdateCatalogForAsync(commit); return new PackageDeletionReport { Deleted = true, DeletedVersion = pkg }; } public async Task GetPackageAsync(string pkgid, string version, string type) { + type = NormalizePackageType(type); return await dbContext.PackageVersions.SingleOrDefaultAsync( v => v.PackageId == pkgid && v.FullString == version && @@ -247,6 +238,7 @@ namespace isnd.Services public async Task UserAskForPackageDeletionAsync(string uid, string id, string lower, string type) { + type = NormalizePackageType(type); PackageVersion packageVersion = await dbContext.PackageVersions .Include(pv => pv.Package) .FirstOrDefaultAsync(m => m.PackageId == id diff --git a/src/isnd/Startup.cs b/src/isnd/Startup.cs index 93be266..2238cf1 100644 --- a/src/isnd/Startup.cs +++ b/src/isnd/Startup.cs @@ -1,5 +1,4 @@ -using System.Linq; -using System.Collections.Generic; + using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; using Microsoft.EntityFrameworkCore; @@ -15,41 +14,46 @@ using isnd.Entities; using isnd.Authorization; using isnd.Data.Roles; using Microsoft.AspNetCore.Authorization; -using Unleash; using Microsoft.Extensions.Options; using isnd.Helpers; using Microsoft.IdentityModel.Tokens; using System; -using Microsoft.OpenApi.Models; using System.IO; -using Microsoft.AspNetCore.Mvc; -using Microsoft.AspNetCore.Identity.EntityFrameworkCore; -using System.Text.Json; -using System.Text.Json.Serialization; -using isnd.Data.Catalog; -using Newtonsoft.Json; using Microsoft.AspNetCore.HttpOverrides; +using Microsoft.OpenApi; namespace isnd { +/** + * Startup class for configuring services and the app's request pipeline. + */ public class Startup { + /** + * Constructor for the Startup class. + * @param config The configuration object. + */ public Startup(IConfiguration config) { Configuration = config; } + /** + * The configuration object. + */ public static IConfiguration Configuration { get; private set; } - // This method gets called by the runtime. Use this method to add services to the container. + /** + * This method gets called by the runtime. Use this method to add services to the container. + * @param services The service collection. + */ public void ConfigureServices(IServiceCollection services) { var smtpSettingsconf = Configuration.GetSection("Smtp"); var isndSettingsconf = Configuration.GetSection("Isn"); - var adminStartupListConf = Configuration.GetSection("AdminList"); - var unleashConf = Configuration.GetSection("Unleash"); + var adminStartupListConf = Configuration.GetSection("AdminStartupList"); services.Configure(options => { @@ -57,14 +61,26 @@ namespace isnd ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedProto; }); + var connectionString = Configuration.GetConnectionString("DefaultConnection"); + var useInMemoryDatabase = Configuration.GetValue("UseInMemoryDatabase") || + string.IsNullOrWhiteSpace(connectionString) || + connectionString.Contains("<", StringComparison.Ordinal); + services.Configure(smtpSettingsconf) .Configure(isndSettingsconf) .Configure(adminStartupListConf) - .Configure(unleashConf) .Configure(o => o.Path = "~/migrate") .AddDbContext(options => - options.UseNpgsql( - Configuration.GetConnectionString("DefaultConnection"))) + { + if (useInMemoryDatabase) + { + options.UseInMemoryDatabase("isnd-tests"); + } + else + { + options.UseNpgsql(connectionString); + } + }) .AddIdentity() .AddRoles() .AddEntityFrameworkStores() @@ -89,20 +105,7 @@ namespace isnd .AddTransient() .AddTransient() .AddSingleton() - .AddSingleton(s => - { - var config = s.GetRequiredService>(); - if (config.Value == null) - throw new System.Exception("No unleash client settings"); - if (config.Value.ApiUrl == null) - throw new System.Exception("No unleash client ApiUrl"); - if (config.Value.ClientApiKey == null) - throw new System.Exception("No unleash client ClientApiKey"); - return s.GetRequiredService().CreateUnleahClient(config.Value); - }); - - - services.AddAuthentication("Bearer") + .AddAuthentication("Bearer") .AddJwtBearer("Bearer", options => { options.Authority = isndSettingsconf.GetValue("ExternalUrl"); @@ -141,7 +144,13 @@ namespace isnd }); } - // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. + /** + * This method gets called by the runtime. Use this method to configure the HTTP request pipeline. + * @param app The application builder. + * @param env The web host environment. + * @param dbContext The application database context. + * @param isnSettingsOption The isnd settings options. + */ public void Configure(IApplicationBuilder app, IWebHostEnvironment env, ApplicationDbContext dbContext, diff --git a/src/isnd/ViewModels/HomeIndexViewModel.cs b/src/isnd/ViewModels/HomeIndexViewModel.cs index 3dac63c..1beadca 100644 --- a/src/isnd/ViewModels/HomeIndexViewModel.cs +++ b/src/isnd/ViewModels/HomeIndexViewModel.cs @@ -1,11 +1,8 @@ -using Unleash; - namespace isnd.ViewModels { public class HomeIndexViewModel { public int PkgCount { get; set; } - public IUnleash UnleashClient; } } \ No newline at end of file diff --git a/src/isnd/Views/Home/Features.cshtml b/src/isnd/Views/Home/Features.cshtml index e38c6a9..ab2d034 100644 --- a/src/isnd/Views/Home/Features.cshtml +++ b/src/isnd/Views/Home/Features.cshtml @@ -3,15 +3,8 @@ foreach (string leashed in new string[] { "pkg-push", "pkg-get", "pkg-autocomplete","pkg-search","pkg-catalog"}) { - if (Model.UnleashClient.IsEnabled(leashed)) - { - //do some magic -

@leashed

- } - else - { + //do old boring stuff

No @leashed (disabled)

} - } } \ No newline at end of file diff --git a/src/isnd/appsettings.json b/src/isnd/appsettings.json index 61e4a9b..06ba611 100644 --- a/src/isnd/appsettings.json +++ b/src/isnd/appsettings.json @@ -16,16 +16,11 @@ }, "AllowedHosts": "*", "Smtp": { - "Server": "", + "Host": "", "Port": 25, "SenderName": "", "SenderEmail": "" }, - "Unleash": - { - "ClientApiKey": "lame-unleash-client-api-key", - "ApiUrl": "http://localhost:4242/api/" - }, "Logging": { "LogLevel": { "Default": "Warning" diff --git a/src/isnd/isnd.csproj b/src/isnd/isnd.csproj index c3c424c..9b04000 100644 --- a/src/isnd/isnd.csproj +++ b/src/isnd/isnd.csproj @@ -1,48 +1,42 @@  - net7.0 - 85fd766d-5d23-4476-aed1-463b2942e86a true - WTFPL - NETSDK1138,CS1591,MSB3243 - 1.0.7.0 - 1.0.7.0 - 1.0.7+Branch.main.Sha.3695c1742965d93eba0ad851656cfaa3e44ba327 - 1.0.7 - true + 1.0.0.0 + 1.0.0.0 + 1.0.0+Branch.main.Sha.654065d7fef34ba4ed2fb3bca5bc91de9ad16e85 + 1.0.0 - - - - + runtime; build; native; contentfiles; analyzers; buildtransitive all - - + + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - All - - - - - - - - - diff --git a/test/data/nuget-artifacts/dummy.nuget.1.0.0.nupkg b/test/data/nuget-artifacts/dummy.nuget.1.0.0.nupkg new file mode 100644 index 0000000..070a94b Binary files /dev/null and b/test/data/nuget-artifacts/dummy.nuget.1.0.0.nupkg differ diff --git a/test/data/nuget-artifacts/dummy.nuget.1.0.0/dummy.nuget.nuspec b/test/data/nuget-artifacts/dummy.nuget.1.0.0/dummy.nuget.nuspec new file mode 100644 index 0000000..feb3a4a --- /dev/null +++ b/test/data/nuget-artifacts/dummy.nuget.1.0.0/dummy.nuget.nuspec @@ -0,0 +1,9 @@ + + + + dummy.nuget + 1.0.0 + isnd.tests + Dummy test package used by integration tests. + + diff --git a/test/data/test-isn/ANuGet.Config b/test/data/test-isn/ANuGet.Config deleted file mode 100644 index 072ade1..0000000 --- a/test/data/test-isn/ANuGet.Config +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - diff --git a/test/data/test-isn/test-isn.csproj b/test/data/test-isn/test-isn.csproj index 9836c56..51e4d77 100644 --- a/test/data/test-isn/test-isn.csproj +++ b/test/data/test-isn/test-isn.csproj @@ -1,13 +1,13 @@  Exe - net7.0 + net10.0 test_isn enable enable - 1.0.7.0 - 1.0.7.0 - 1.0.7+Branch.main.Sha.3695c1742965d93eba0ad851656cfaa3e44ba327 - 1.0.7 + 1.0.0.0 + 1.0.0.0 + 1.0.0+Branch.main.Sha.654065d7fef34ba4ed2fb3bca5bc91de9ad16e85 + 1.0.0 \ No newline at end of file diff --git a/test/data/test-isn/testcmd b/test/data/test-isn/testcmd deleted file mode 100644 index 2127221..0000000 --- a/test/data/test-isn/testcmd +++ /dev/null @@ -1 +0,0 @@ -nuget install -NoCache -Verbosity detailed -ConfigFile ANuGet.Config Yavsc.Abstract diff --git a/test/isn.tests/PushTest.cs b/test/isn.tests/PushTest.cs index 6e0f264..bc985ed 100644 --- a/test/isn.tests/PushTest.cs +++ b/test/isn.tests/PushTest.cs @@ -1,29 +1,190 @@ using System; -using System.Data; +using System.Collections.Generic; using System.IO; -using System.Xml; +using System.Linq; +using System.Net; using System.Net.Http; - +using System.Net.Sockets; +using System.Text; using System.Threading.Tasks; using Newtonsoft.Json; -using isn.Abstract; -using System.Linq; using Xunit; -using isn.abst; -using isnd.Entities; +using Isn.Abstract; -namespace isn.tests +namespace Isn.tests { - public class Tests + public sealed class LocalSourceFixture : IDisposable { + private readonly HttpListener listener; + private readonly Task listenerTask; + private readonly string configDirectory; + private readonly string originalConfigDirectory; + + public string LastApiKeyHeader { get; private set; } + + public LocalSourceFixture() + { + var port = GetFreePort(); + var prefix = $"http://127.0.0.1:{port}/"+Constants.APIPrefix; + listener = new HttpListener(); + listener.Prefixes.Add(prefix); + listener.Start(); + + listenerTask = Task.Run(async () => + { + while (listener.IsListening) + { + HttpListenerContext context; + try + { + context = await listener.GetContextAsync(); + } + catch (HttpListenerException) + { + break; + } + catch (ObjectDisposedException) + { + break; + } + + if (context.Request.HttpMethod == "PUT") + { + LastApiKeyHeader = context.Request.Headers["X-NuGet-ApiKey"]; + } + + var indexJson = JsonConvert.SerializeObject(new ApiIndexViewModel(prefix + "index.json") + { + Version = "3.0.0", + Resources = new[] + { + new Resource(prefix + "put", "PackagePublish/2.0.0") + { + Comment = "test publish endpoint" + } + } + }); + var payload = Encoding.UTF8.GetBytes(indexJson); + context.Response.ContentType = "application/json"; + context.Response.ContentLength64 = payload.Length; + await context.Response.OutputStream.WriteAsync(payload, 0, payload.Length); + context.Response.Close(); + } + }); + + configDirectory = Path.Combine(Path.GetTempPath(), $"isn-tests-{Guid.NewGuid():N}"); + Directory.CreateDirectory(configDirectory); + originalConfigDirectory = Environment.GetEnvironmentVariable("ISN_CONFIG_DIR"); + Environment.SetEnvironmentVariable("ISN_CONFIG_DIR", configDirectory); + + SourceUrl = prefix + "index.json"; + ConfigureIsnSettings(SourceUrl); + Program.LoadConfig(); + } + + public string SourceUrl { get; } + + public void Dispose() + { + listener.Close(); + if (listenerTask != null) + { + try + { + listenerTask.GetAwaiter().GetResult(); + } + catch (Exception) + { + } + } + + Environment.SetEnvironmentVariable("ISN_CONFIG_DIR", originalConfigDirectory); + if (Directory.Exists(configDirectory)) + { + Directory.Delete(configDirectory, recursive: true); + } + } + + private static int GetFreePort() + { + var listener = new TcpListener(IPAddress.Loopback, 0); + listener.Start(); + var port = ((IPEndPoint)listener.LocalEndpoint).Port; + listener.Stop(); + return port; + } + + private void ConfigureIsnSettings(string sourceUrl) + { + var configPath = Path.Combine(configDirectory, "config.json"); + var settings = new Settings + { + DataProtectionTitle = "isn", + Sources = new Dictionary + { + [sourceUrl] = new SourceSettings { Alias = "test" } + }, + DefaultSourceKey = sourceUrl + }; + + File.WriteAllText(configPath, JsonConvert.SerializeObject(settings, Formatting.Indented)); + } + } + + public class Tests : IClassFixture + { + private readonly LocalSourceFixture fixture; + + public Tests(LocalSourceFixture fixture) + { + this.fixture = fixture; + } + + [Fact] + public void SupportsIsolatedConfigDirectoryOverride() + { + var tempDir = Path.Combine(Path.GetTempPath(), $"isn-test-{Guid.NewGuid():N}"); + Directory.CreateDirectory(tempDir); + var originalConfigDir = Environment.GetEnvironmentVariable("ISN_CONFIG_DIR"); + var configFile = Path.Combine(tempDir, "config.json"); + var expectedSource = "https://example.test/index.json"; + var settings = new Settings + { + DataProtectionTitle = "isn", + Sources = new Dictionary + { + [expectedSource] = new SourceSettings { Alias = "override" } + }, + DefaultSourceKey = expectedSource + }; + + try + { + Environment.SetEnvironmentVariable("ISN_CONFIG_DIR", tempDir); + File.WriteAllText(configFile, JsonConvert.SerializeObject(settings, Formatting.Indented)); + + Program.LoadConfig(); + + Assert.Equal(expectedSource, Program.Settings.DefaultSourceKey); + Assert.True(Program.Settings.Sources.ContainsKey(expectedSource)); + } + finally + { + Environment.SetEnvironmentVariable("ISN_CONFIG_DIR", originalConfigDir); + if (Directory.Exists(tempDir)) + { + Directory.Delete(tempDir, recursive: true); + } + } + } [Fact] public void HaveADefaultDataProtector() { - string pass = "a lame and big pass"; - isn.IDataProtector _protector = new isn.DefaultDataProtector(); - string protectedpass = _protector.Protect(pass); - string unprotectedpass = _protector.UnProtect(protectedpass); + var pass = "a lame and big pass"; + IDataProtector protector = new DefaultDataProtector(); + var protectedpass = protector.Protect(pass); + var unprotectedpass = protector.UnProtect(protectedpass); Console.WriteLine(protectedpass); Assert.Equal(pass, unprotectedpass); Assert.True(protectedpass != null); @@ -33,33 +194,76 @@ namespace isn.tests [Fact] public async Task TestHttpClient() { - string url = "https://isn.pschneider.fr/" + ApiConfig.IndexDotJson; - HttpClient client = new HttpClient(); - // var json = await client.GetStringAsync(new System.Uri(url)); - var response = await client.GetAsync(url); + using var client = new HttpClient(); + var response = await client.GetAsync(fixture.SourceUrl); var json = await response.Content.ReadAsStringAsync(); var vm = JsonConvert.DeserializeObject(json); Console.WriteLine(JsonConvert.SerializeObject(vm)); Assert.NotNull(vm); Assert.NotNull(vm.Resources); } + [Fact] public void TestPush() { Program.LoadConfig(); - var report = Program.PushPkg(new string[] { "/home/paul/Nupkgs/Yavsc.Abstract.1.0.8." - + Constants.PaquetFileEstension }); + var report = Program.PushPkg(new[] { GetDummyPackagePath() }); + Assert.NotNull(report); + Assert.Single(report); + } + + [Fact] + public void PushUsesStoredApiKeyFromConfigWhenNoExplicitApiKeyIsProvided() + { + Program.LoadConfig(); + var clearApiKey = "stored-api-key"; + Program.Settings.Sources[fixture.SourceUrl].SetApiKey(clearApiKey); + Program.SaveConfig(); + Program.LoadConfig(); + + var report = Program.PushPkg(new[] { GetDummyPackagePath() }); + + Assert.NotNull(report); + Assert.Single(report); + Assert.Equal(clearApiKey, fixture.LastApiKeyHeader); } [Fact] public void GetServerResourcesUsingHttpClientAsyncTest() { - var model = SourceHelpers.GetServerResources("Https://isn.pschneider.fr/index.json"); + var model = SourceHelpers.GetServerResources(fixture.SourceUrl); Console.WriteLine(JsonConvert.SerializeObject(model)); Assert.NotNull(model.Resources); - var pub = model.Resources.FirstOrDefault((r) => r.Type.StartsWith("PackagePublish/")); + var pub = model.Resources.FirstOrDefault(r => r.Type.StartsWith("PackagePublish/")); Assert.True(pub != null); } + private static string GetDummyPackagePath() + { + var localOutputDummy = Path.Combine(AppContext.BaseDirectory, "dummy.nupkg"); + if (File.Exists(localOutputDummy)) + { + return localOutputDummy; + } + + var dir = new DirectoryInfo(AppContext.BaseDirectory); + while (dir != null) + { + if (File.Exists(Path.Combine(dir.FullName, "isn.sln"))) + { + var sharedArtifact = Path.Combine(dir.FullName, "test", "data", "nuget-artifacts", "dummy.nuget.1.0.0.nupkg"); + if (File.Exists(sharedArtifact)) + { + return sharedArtifact; + } + + break; + } + + dir = dir.Parent; + } + + throw new FileNotFoundException("Unable to locate a dummy NuGet package for push tests."); + } } } \ No newline at end of file diff --git a/test/isn.tests/isn.tests.csproj b/test/isn.tests/isn.tests.csproj index faae98f..9371392 100644 --- a/test/isn.tests/isn.tests.csproj +++ b/test/isn.tests/isn.tests.csproj @@ -1,20 +1,18 @@  - net7.0 false - NETSDK1138 - 1.0.7.0 - 1.0.7.0 - 1.0.7+Branch.main.Sha.3695c1742965d93eba0ad851656cfaa3e44ba327 - 1.0.7 + 1.0.0.0 + 1.0.0.0 + 1.0.0+Branch.main.Sha.654065d7fef34ba4ed2fb3bca5bc91de9ad16e85 + 1.0.0 - - + + - - - + + + diff --git a/test/isnd.tests/UnitTestWebHost.cs b/test/isnd.tests/UnitTestWebHost.cs index 99ac24e..126372a 100644 --- a/test/isnd.tests/UnitTestWebHost.cs +++ b/test/isnd.tests/UnitTestWebHost.cs @@ -1,28 +1,40 @@ -using System.Threading; using System; -using Microsoft.AspNetCore.Hosting; -using Microsoft.AspNetCore; -using Xunit; +using System.IO; +using System.IO.Compression; +using System.Linq; +using System.Net.Http; +using System.Net.Http.Headers; +using System.Security.Cryptography; +using System.Threading; +using System.Threading.Tasks; +using System.Xml.Linq; +using Isn.Abstract; using isnd.Data; +using isnd.Data.ApiKeys; +using isnd.Helpers; +using isnd.tests; +using Microsoft.AspNetCore.DataProtection; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Options; -using System.Diagnostics; -using Microsoft.AspNetCore.Hosting.Server; -using Microsoft.AspNetCore.Hosting.Server.Features; -using Microsoft.Extensions.Configuration; -using isnd.tests; -using NuGet.Protocol; +using NuGet.Common; using NuGet.Configuration; -using System.Threading.Tasks; +using NuGet.Protocol; using NuGet.Protocol.Core.Types; +using NuGet.Versioning; +using Xunit; namespace isnd.host.tests -{ +{ [Collection("Web server collection")] public class UnitTestWebHost : IClassFixture { - WebServerFixture server; + private const string DummyPackageId = "dummy.nuget"; + private const string DummyPackageVersion = "1.0.0"; + private const string DummyArtifactRelativePath = "test/data/nuget-artifacts/dummy.nuget.1.0.0.nupkg"; + + private readonly WebServerFixture server; + public UnitTestWebHost(WebServerFixture server) { this.server = server; @@ -31,83 +43,324 @@ namespace isnd.host.tests [Fact] public void TestHaveTestDbContextAndMigrate() { - using (var serviceScope = server.Host.Services.CreateScope()) + using var serviceScope = server.Host.Services.CreateScope(); + var services = serviceScope.ServiceProvider; + var myDependency = services.GetRequiredService(); + if (myDependency.Database.ProviderName?.Contains("InMemory", StringComparison.OrdinalIgnoreCase) == true) + { + myDependency.Database.EnsureCreated(); + } + else { - var services = serviceScope.ServiceProvider; - var myDependency = services.GetRequiredService(); myDependency.Database.Migrate(); } } [Fact] - void TestDropUser() + public void LegacyApiKeyProtectorCanUnprotectValuesFromOlderClients() { - - using (var serviceScope = server.Host.Services.CreateScope()) - { - var services = serviceScope.ServiceProvider; - var dbContext = services.GetRequiredService(); - var paul = dbContext.Users.FirstOrDefaultAsync(u => u.Email == "paul@pschneider.fr").Result; - if (paul!=null) - { - dbContext.Users.Remove(paul); - dbContext.SaveChanges(); - } - } + const string clearValue = "legacy-api-key"; + var legacyProtectedValue = new Isn.DefaultDataProtector().Protect(clearValue); + + var unprotected = ApiKeyProtector.TryUnprotectKey(new ThrowingProtector(), legacyProtectedValue); + + Assert.Equal(clearValue, unprotected); } + [Fact] + public void ApiKeyProtectorReturnsRawValueWhenInputIsNotProtected() + { + const string rawApiKey = "CfDJ8AstXUEkxpJBrmoENwy__WNPxqcOwAuxyYgiU3Mebns5yxAT3VrC5vTuWTRSGZBFB7IGUyFkUYsp2nhRy64NqeUzLgOwEcU4FPOf-yaAtPeTMeStRd60bRh2ZYyQkQ3hrhW-lwpCLwYtNOnOyX2jayuzByF-4QfHIEhg6lbWenzf"; + + var result = ApiKeyProtector.TryUnprotectKey(new ThrowingProtector(), rawApiKey); + + Assert.Equal(rawApiKey, result); + } + + [Fact] + public void TestDropUser() + { + using var serviceScope = server.Host.Services.CreateScope(); + var services = serviceScope.ServiceProvider; + var dbContext = services.GetRequiredService(); + var paul = dbContext.Users.FirstOrDefaultAsync(u => u.Email == "paul@pschneider.fr").Result; + if (paul != null) + { + dbContext.Users.Remove(paul); + dbContext.SaveChanges(); + } + } [Fact] public void NugetInstallsTest() { + var pkgSourceUrl = GetServerBaseUrl() + "/" + Constants.APIPrefix + "index.json"; + using var client = new HttpClient(); + var response = client.GetAsync(pkgSourceUrl).GetAwaiter().GetResult(); + var body = response.Content.ReadAsStringAsync().GetAwaiter().GetResult(); + + Assert.True(response.IsSuccessStatusCode, $"Expected {pkgSourceUrl} to be reachable but got {(int)response.StatusCode} {response.ReasonPhrase}"); + Assert.False(string.IsNullOrWhiteSpace(body)); + Assert.Contains("PackagePublish/2.0.0", body, StringComparison.OrdinalIgnoreCase); + Assert.Contains("RegistrationsBaseUrl/Versioned", body, StringComparison.OrdinalIgnoreCase); + } + + [Fact] + public void DummyNugetArtifactShouldBeAReadablePackage() + { + var packagePath = GetDummyArtifactPath(); + Assert.True(File.Exists(packagePath), $"Missing dummy artifact at {packagePath}"); + + var fileInfo = new FileInfo(packagePath); + Assert.True(fileInfo.Length > 0, "Dummy artifact is empty."); + + using var stream = File.OpenRead(packagePath); + using var archive = new ZipArchive(stream, ZipArchiveMode.Read, leaveOpen: false); + var nuspecEntry = archive.GetEntry("dummy.nuget.nuspec"); + Assert.NotNull(nuspecEntry); + + using var nuspecStream = nuspecEntry.Open(); + var nuspec = XDocument.Load(nuspecStream); + XNamespace ns = "http://schemas.microsoft.com/packaging/2013/05/nuspec.xsd"; + var id = nuspec.Root?.Element(ns + "metadata")?.Element(ns + "id")?.Value; + var version = nuspec.Root?.Element(ns + "metadata")?.Element(ns + "version")?.Value; + + Assert.Equal(DummyPackageId, id); + Assert.Equal(DummyPackageVersion, version); + } + + [Fact] + public async Task PushDummyPackageAndDiscoverItThroughNuGetEndpoints() + { + var apiKeyValue = $"dummy-key-{Guid.NewGuid():N}"; + var packagePath = GetDummyArtifactPath(); + Assert.True(File.Exists(packagePath), $"Missing dummy artifact at {packagePath}"); + using (var serviceScope = server.Host.Services.CreateScope()) - { var isnSettings = serviceScope.ServiceProvider.GetService>().Value; - string pkgSourceUrl = isnSettings.ExternalUrl + "/index.json"; - ProcessStartInfo psi = new ProcessStartInfo("nuget"); - psi.ArgumentList.Add("install"); - psi.ArgumentList.Add("gitversion"); - psi.ArgumentList.Add("-PreRelease"); - psi.ArgumentList.Add("-Source"); - psi.ArgumentList.Add(pkgSourceUrl); - Process p = Process.Start(psi); - p.WaitForExit(); - Assert.True(p.ExitCode == 0, "nuget install failed!"); + { + var dbContext = serviceScope.ServiceProvider.GetRequiredService(); + var userId = $"dummy-user-{Guid.NewGuid():N}"; + + await ResetDummyPackageStateAsync(serviceScope.ServiceProvider, dbContext); + + dbContext.Users.Add(new ApplicationUser + { + Id = userId, + UserName = userId, + NormalizedUserName = userId.ToUpperInvariant(), + Email = $"{userId}@tests.local", + NormalizedEmail = $"{userId}@tests.local".ToUpperInvariant(), + EmailConfirmed = true + }); + + dbContext.ApiKeys.Add(new ApiKey + { + Id = apiKeyValue, + UserId = userId, + Name = "dummy-key", + CreationDate = DateTime.UtcNow, + ValidityPeriodInDays = 30 + }); + + await dbContext.SaveChangesAsync(); } + + await PushPackageAsync(packagePath, apiKeyValue); + await AssertRegistrationContainsPushedVersionAsync(DummyPackageId, DummyPackageVersion); + await AssertAutocompleteContainsPushedVersionAsync(DummyPackageId, DummyPackageVersion); + } + + [Fact] + public async Task NuGetV3ProtocolCanDownloadDummyPackage() + { + var apiKeyValue = $"dummy-key-{Guid.NewGuid():N}"; + var packagePath = GetDummyArtifactPath(); + Assert.True(File.Exists(packagePath), $"Missing dummy artifact at {packagePath}"); + + using (var serviceScope = server.Host.Services.CreateScope()) + { + var dbContext = serviceScope.ServiceProvider.GetRequiredService(); + var userId = $"dummy-user-{Guid.NewGuid():N}"; + + await ResetDummyPackageStateAsync(serviceScope.ServiceProvider, dbContext); + + dbContext.Users.Add(new ApplicationUser + { + Id = userId, + UserName = userId, + NormalizedUserName = userId.ToUpperInvariant(), + Email = $"{userId}@tests.local", + NormalizedEmail = $"{userId}@tests.local".ToUpperInvariant(), + EmailConfirmed = true + }); + + dbContext.ApiKeys.Add(new ApiKey + { + Id = apiKeyValue, + UserId = userId, + Name = "dummy-key", + CreationDate = DateTime.UtcNow, + ValidityPeriodInDays = 30 + }); + + await dbContext.SaveChangesAsync(); + } + + await PushPackageAsync(packagePath, apiKeyValue); + + var indexUrl = GetServerBaseUrl() + "/" + Constants.APIPrefix + "index.json"; + var repository = Repository.Factory.GetCoreV3(indexUrl); + var findPackageById = await repository.GetResourceAsync(); + Assert.NotNull(findPackageById); + + await using var nupkgStream = new MemoryStream(); + using var cache = new SourceCacheContext(); + var copied = await findPackageById.CopyNupkgToStreamAsync( + DummyPackageId, + NuGetVersion.Parse(DummyPackageVersion), + nupkgStream, + cache, + NullLogger.Instance, + CancellationToken.None); + + Assert.True(copied, "NuGet v3 client could not download the package from the feed."); + Assert.True(nupkgStream.Length > 0, "Downloaded package stream is empty."); } [Fact] public void TestRegistrationV3Resource() { - using (var serviceScope = server.Host.Services.CreateScope()) - { var isnSettings = serviceScope.ServiceProvider.GetService>().Value; - string pkgSourceUrl = isnSettings.ExternalUrl + "/index.json"; - NullThrottle throttle = new NullThrottle(); + using var serviceScope = server.Host.Services.CreateScope(); + var isnSettings = serviceScope.ServiceProvider.GetService>().Value; + string pkgSourceUrl = isnSettings.ExternalUrl + "/pkgs/index.json"; + var throttle = new NullThrottle(); - PackageSource packageSource = new PackageSource(pkgSourceUrl); - HttpSource client = new HttpSource(packageSource, PkgSourceMessageHandler, throttle); - NuGet.Protocol.RegistrationResourceV3 res = new NuGet.Protocol.RegistrationResourceV3(client , - new Uri(isnSettings.ExternalUrl + "/v3.4.0//registration")); - } + var packageSource = new PackageSource(pkgSourceUrl); + var client = new HttpSource(packageSource, PkgSourceMessageHandler, throttle); + _ = new RegistrationResourceV3(client, new Uri(isnSettings.ExternalUrl + "/v3.4.0//registration")); } - + [Fact] public void TrueTestRegistrationV3Resource() { - using (var serviceScope = server.Host.Services.CreateScope()) - { - var isnSettings = serviceScope.ServiceProvider.GetService>().Value; - string pkgSourceUrl = isnSettings.ExternalUrl + "/index.json"; + using var serviceScope = server.Host.Services.CreateScope(); + var isnSettings = serviceScope.ServiceProvider.GetService>().Value; + string pkgSourceUrl = isnSettings.ExternalUrl + "/pkgs/index.json"; var prov = new RegistrationResourceV3Provider(); var source = new PackageSource(pkgSourceUrl); - var repo = new SourceRepository(source, new INuGetResourceProvider[]{ prov }); + var repo = new SourceRepository(source, new INuGetResourceProvider[] { prov }); prov.TryCreate(repo, CancellationToken.None); - - } } private Task PkgSourceMessageHandler() { throw new NotImplementedException(); } + + private string GetServerBaseUrl() + { + var address = server.Addresses.FirstOrDefault(); + Assert.False(string.IsNullOrWhiteSpace(address), "No listening address was captured from WebServerFixture."); + return address.TrimEnd('/'); + } + + private string GetDummyArtifactPath() + { + var root = FindRepoRoot(); + return Path.Combine(root, DummyArtifactRelativePath); + } + + private static string FindRepoRoot() + { + var dir = new DirectoryInfo(AppContext.BaseDirectory); + while (dir != null) + { + if (File.Exists(Path.Combine(dir.FullName, "isn.sln"))) + { + return dir.FullName; + } + + dir = dir.Parent; + } + + throw new DirectoryNotFoundException("Could not locate repository root from AppContext.BaseDirectory."); + } + + private static async Task ResetDummyPackageStateAsync(IServiceProvider serviceProvider, ApplicationDbContext dbContext) + { + var settings = serviceProvider.GetRequiredService>().Value; + var versions = dbContext.PackageVersions.Where(v => v.PackageId == DummyPackageId); + var packages = dbContext.Packages.Where(p => p.Id == DummyPackageId); + + dbContext.PackageVersions.RemoveRange(versions); + dbContext.Packages.RemoveRange(packages); + await dbContext.SaveChangesAsync(); + + var packageRootDir = settings.PackagesRootDir; + if (!Path.IsPathRooted(packageRootDir)) + { + packageRootDir = Path.Combine(FindRepoRoot(), packageRootDir); + } + + var packageOnDiskPath = Path.Combine(packageRootDir, DummyPackageId); + if (Directory.Exists(packageOnDiskPath)) + { + Directory.Delete(packageOnDiskPath, recursive: true); + } + } + + private async Task PushPackageAsync(string packagePath, string apiKey) + { + using var client = new HttpClient { BaseAddress = new Uri(GetServerBaseUrl()) }; + using var request = new HttpRequestMessage(HttpMethod.Put, $"/{Constants.APIPrefix}{isnd.Entities.ApiConfig.Publish}"); + using var multipart = new MultipartFormDataContent(); + await using var packageStream = File.OpenRead(packagePath); + using var packageContent = new StreamContent(packageStream); + + packageContent.Headers.ContentType = new MediaTypeHeaderValue("application/octet-stream"); + multipart.Add(packageContent, "package", Path.GetFileName(packagePath)); + request.Content = multipart; + request.Headers.Add("X-NuGet-ApiKey", apiKey); + request.Headers.Add("X-NuGet-Client-Version", "6.11.1"); + + var response = await client.SendAsync(request); + var responseBody = await response.Content.ReadAsStringAsync(); + Assert.True(response.IsSuccessStatusCode, + $"Expected push to succeed but got {(int)response.StatusCode} {response.ReasonPhrase}. Body: {responseBody}"); + } + + private async Task AssertRegistrationContainsPushedVersionAsync(string packageId, string packageVersion) + { + using var client = new HttpClient { BaseAddress = new Uri(GetServerBaseUrl()) }; + var lowerId = packageId.ToLowerInvariant(); + var route = $"/{Constants.APIPrefix}v3.4.0/{isnd.Entities.ApiConfig.Registration}/{lowerId}/index.json"; + var response = await client.GetAsync(route); + var body = await response.Content.ReadAsStringAsync(); + + Assert.True(response.IsSuccessStatusCode, + $"Expected registration endpoint to succeed but got {(int)response.StatusCode} {response.ReasonPhrase}. Body: {body}"); + Assert.Contains(packageVersion, body, StringComparison.OrdinalIgnoreCase); + } + + private async Task AssertAutocompleteContainsPushedVersionAsync(string packageId, string packageVersion) + { + using var client = new HttpClient { BaseAddress = new Uri(GetServerBaseUrl()) }; + var route = $"/{Constants.APIPrefix}{isnd.Entities.ApiConfig.AutoComplete}?id={packageId}&semVerLevel=3.0.0&prerelease=true"; + var response = await client.GetAsync(route); + var body = await response.Content.ReadAsStringAsync(); + + Assert.True(response.IsSuccessStatusCode, + $"Expected autocomplete endpoint to succeed but got {(int)response.StatusCode} {response.ReasonPhrase}. Body: {body}"); + Assert.Contains(packageVersion, body, StringComparison.OrdinalIgnoreCase); + } + + private sealed class ThrowingProtector : IDataProtector + { + public byte[] Protect(byte[] plaintext) => throw new CryptographicException("unexpected protect call"); + + public byte[] Unprotect(byte[] protectedData) => throw new CryptographicException("unexpected unprotect call"); + + public IDataProtector CreateProtector(string purpose) => this; + } } } diff --git a/test/isnd.tests/WebServerFixture.cs b/test/isnd.tests/WebServerFixture.cs index b45be22..b634ccc 100644 --- a/test/isnd.tests/WebServerFixture.cs +++ b/test/isnd.tests/WebServerFixture.cs @@ -38,8 +38,8 @@ namespace isnd.tests .UseStartup(typeof(Startup)) .ConfigureAppConfiguration((builderContext, config) => { - config.AddJsonFile("appsettings.json", false); - config.AddJsonFile("appsettings.Development.json", false); + config.AddJsonFile("appsettings.json", optional: false); + config.AddJsonFile("appsettings.Development.json", optional: true); }); Host = webhostBuilder.Build(); diff --git a/test/isnd.tests/isnd.tests.csproj b/test/isnd.tests/isnd.tests.csproj index f3572a8..657cd31 100644 --- a/test/isnd.tests/isnd.tests.csproj +++ b/test/isnd.tests/isnd.tests.csproj @@ -1,13 +1,12 @@  - net7.0 false d7144e46-4e63-4391-ba86-64b61f6e7be4 NETSDK1138 - 1.0.7.0 - 1.0.7.0 - 1.0.7+Branch.main.Sha.3695c1742965d93eba0ad851656cfaa3e44ba327 - 1.0.7 + 1.0.0.0 + 1.0.0.0 + 1.0.0+Branch.main.Sha.654065d7fef34ba4ed2fb3bca5bc91de9ad16e85 + 1.0.0 diff --git a/test/isnd.tests/wwwroot/favicon.ico b/test/isnd.tests/wwwroot/favicon.ico deleted file mode 100644 index e69de29..0000000