Compare commits

49 Commits

Author SHA1 Message Date
301a276a7b Find package versions ignoring character case 2025-03-04 14:28:29 +00:00
bc1464db7f Web search result fixed 2025-02-01 19:44:31 +00:00
913f2ae5dd Layout 2024-11-03 16:20:43 +00:00
27ad3841a0 Fixes he layout 2024-11-03 16:10:03 +00:00
a6f9d974d3 Licence 2024-11-03 13:30:21 +00:00
a3d3e51259 uodated : usefull 2024-11-03 11:12:48 +00:00
753f54dad0 Home index 2024-11-02 15:49:26 +00:00
72ecf30458 repo cleanup 2024-10-20 19:54:36 +01:00
5f1dca3d61 dotnet package resolution 2024-10-20 19:47:49 +01:00
c615886a4c unit testing 2024-10-19 13:25:58 +01:00
2b45e421dd GetMetaData OK 2024-10-09 23:19:38 +01:00
7ca465d4a9 JQuery version bump 2024-07-28 14:07:06 +01:00
8532b81638 WIP package resolution 2024-07-19 10:21:25 +02:00
01405abdf2 Package deletion and types 2024-04-28 15:44:12 +01:00
62a8bf27cb Error Page 2024-04-28 15:43:10 +01:00
9c9ee6cc4c trying with isn.abst 2024-04-22 21:51:06 +01:00
3af05fc034 dependencies 2024-04-16 22:30:39 +01:00
04a467dd83 registration and detail url's 2024-03-31 15:59:43 +01:00
96f91c3ba0 net-8, localization, privacy, and js ref's 2024-03-30 09:45:17 +00:00
aeecc524a4 WIP /search 2024-03-24 16:53:12 +00:00
e229439174 Fixes the registration page index 2024-03-16 14:33:10 +00:00
c6c52a48c4 some details 2024-03-11 20:55:52 +00:00
8656bce151 fixes the catalog page 2024-03-11 01:02:11 +00:00
d6180aa154 REORG 2024-03-10 20:10:05 +00:00
89e1b5a235 Testing the push method (TODO coherence between return code a physical result) 2024-03-09 21:43:56 +00:00
e69462999e Cx strings contains user secrets ... 2024-03-09 16:13:30 +00:00
e1a012e15b orthograph 2024-02-12 02:45:47 +00:00
16258d1c33 version bumps 2024-02-12 02:22:10 +00:00
019d858ac2 fixes the chown 2024-02-12 02:21:50 +00:00
72839e8251 Release ! 2023-05-09 23:44:25 +01:00
7987fa04a0 update 2023-05-07 20:05:10 +01:00
9e79edc624 Trying and find a package from API 2023-05-01 22:05:39 +01:00
06b0115405 Commit Id and TimerStamps 2023-04-30 23:40:39 +01:00
cea05fe8ff Package Version constraints 2023-04-30 20:25:03 +01:00
fa6272ac28 cleaner code 2023-04-26 18:22:48 +01:00
77a16c5551 avoid a null ref 2023-04-26 18:22:39 +01:00
e2775dedaf Searching deleted package 2023-04-26 01:22:33 +01:00
e5932a11c2 cleanup 2023-04-26 01:22:00 +01:00
4de566ee41 Cleanup 2023-04-26 01:21:23 +01:00
d3b96088ab a better result 2023-04-25 22:40:58 +01:00
56e4b6c526 gives the store-api-key some options 2023-04-18 22:07:14 +01:00
b20e822427 ignore current source setting, at serialisation 2023-04-18 22:06:44 +01:00
9fc3f7ea4a MAssive attack 2023-04-18 08:43:22 +01:00
9363cd756b makes "store-api-ey" a command 2023-04-17 19:04:59 +01:00
9e2cebde2e This "push" is only for testing purposes 2023-04-17 19:04:10 +01:00
a2e22c357a Get a failed path 2023-04-17 19:03:11 +01:00
ee636a29ac db upgrade 2023-04-17 01:33:03 +01:00
d3f49a3c01 make packs 2023-04-17 01:32:47 +01:00
58d1f26701 No release yet 2023-04-17 01:20:42 +01:00
1743 changed files with 237318 additions and 9325 deletions

2
.build
View File

@ -18,7 +18,7 @@
<target name="build" description="build all">
<exec program="dotnet" commandline="build" />
</target>
<target name="test" description="build all">
<target name="test" description="test all">
<exec program="dotnet" commandline="test" />
</target>
</project>

14
.gitignore vendored
View File

@ -6,14 +6,16 @@
/src/isn/.vscode/
/test/isnd.tests/obj/
/test/isnd.tests/bin/
/packages/
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
.fake
/artifacts/
/.vs/
/.vscode/
appsettings.Development.json

View File

@ -1,38 +0,0 @@
<Properties StartupConfiguration="{9D758F00-17FF-433D-B088-F9C2D97C9BD1}|Default">
<MonoDevelop.Ide.Workbench ActiveDocument="src/isn/Program.cs">
<Files>
<File FileName="src/isn/Program.cs" Line="66" Column="10" />
</Files>
<Pads>
<Pad Id="ProjectPad">
<State name="__root__">
<Node name="isn" expanded="True">
<Node name="src" expanded="True">
<Node name="isn" expanded="True" selected="True" />
<Node name="isn.abst" expanded="True" />
</Node>
<Node name="test" expanded="True">
<Node name="isnd.tests" expanded="True" />
</Node>
</Node>
</State>
</Pad>
<Pad Id="MonoDevelop.UnitTesting.TestPad">
<State name="__root__">
<Node name="isn" expanded="True">
<Node name="test" expanded="True">
<Node name="isn.tests" selected="True" />
</Node>
</Node>
</State>
</Pad>
</Pads>
</MonoDevelop.Ide.Workbench>
<MonoDevelop.Ide.DebuggingService.PinnedWatches />
<MonoDevelop.Ide.Workspace ActiveConfiguration="Debug" />
<MonoDevelop.Ide.ItemProperties.test.isnd.tests PreferredExecutionTarget="MonoDevelop.Default" />
<MonoDevelop.Ide.DebuggingService.Breakpoints>
<BreakpointStore />
</MonoDevelop.Ide.DebuggingService.Breakpoints>
<MultiItemStartupConfigurations />
</Properties>

48
.vscode/launch.json vendored
View File

@ -1,48 +0,0 @@
{
// Utilisez IntelliSense pour en savoir plus sur les attributs possibles.
// Pointez pour afficher la description des attributs existants.
// Pour plus d'informations, visitez : https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"name": ".NET Core Attach",
"type": "coreclr",
"request": "attach"
},
{
"name": "push",
"type": "coreclr",
"request": "launch",
"preLaunchTask": "build",
"program": "${workspaceFolder}/src/isn/bin/Debug/netcoreapp2.1/isn.dll",
"requireExactSource": false,
"args": [
"push", "/home/paul/Nupkgs/Yavsc.Abstract.1.0.8.nupkg"
],
"cwd": "${workspaceFolder}/src/isn",
"stopAtEntry": false,
"console": "internalConsole"
},
{
"name": "web",
"type": "coreclr",
"request": "launch",
"preLaunchTask": "build",
"program": "${workspaceFolder}/src/isnd/bin/Debug/netcoreapp2.1/isnd.dll",
"args": [],
"cwd": "${workspaceFolder}/src/isnd",
"stopAtEntry": false,
"requireExactSource": false,
"serverReadyAction": {
"action": "openExternally",
"pattern": "\\bNow listening on:\\s+(https?://\\S+)"
},
"env": {
"ASPNETCORE_ENVIRONMENT": "Development"
},
"sourceFileMap": {
"/Views": "${workspaceFolder}/Views"
}
}
]
}

50
.vscode/settings.json vendored
View File

@ -1,50 +0,0 @@
{
"omnisharp.msbuild": false,
"dotnet-test-explorer.testProjectPath": "**/*.sln",
"dotnet-test-explorer.runInParallel": false,
"dotnet-test-explorer.showCodeLens": true,
"dotnet-test-explorer.testArguments": "",
"nxunitExplorer.modules": [
"test/i*/bin/Debug/*/*.tests.dll"
],
"nxunitExplorer.skippattern": "",
"dotnetCoreExplorer.runEnvVars": {
"ASPNETCORE_ENVIRONMENT": "Testing"
},
"dotnetCoreExplorer.searchpatterns": "test/**/bin/**/*.tests.{dll,exe}",
"sqltools.connections": [
{
"previewLimit": 50,
"server": "localhost",
"port": 5432,
"driver": "PostgreSQL",
"name": "isndev",
"group": "isn",
"database": "dotnetmvc",
"username": "dotnetmvc",
"password": "RvJa=y#b/tfg"
},
{
"previewLimit": 50,
"server": "localhost",
"port": 5432,
"askForPassword": true,
"driver": "PostgreSQL",
"name": "isnd",
"group": "isn",
"database": "webid",
"username": "webid"
},
{
"previewLimit": 50,
"server": "localhost",
"port": 5432,
"askForPassword": true,
"driver": "PostgreSQL",
"name": "isnddev",
"group": "isn",
"database": "isnd",
"username": "paul"
}
]
}

View File

@ -1,8 +0,0 @@
using System;
namespace {{namespace}}
{
public class {{name}}
{
}
}

View File

@ -1,3 +0,0 @@
export class {{name}} {
}

View File

@ -1,9 +0,0 @@
Imports System
Namespace {{namespace}}
Public Class {{name}}
End Class
End Namespace

View File

@ -1,3 +0,0 @@
export default {{name}} {
}

View File

@ -1,8 +0,0 @@
using System;
namespace {{namespace}}
{
public enum {{name}}
{
}
}

View File

@ -1,8 +0,0 @@
using System;
namespace {{namespace}}
{
public interface {{name}}
{
}
}

View File

@ -1,3 +0,0 @@
export interface {{name}} {
}

View File

@ -1,46 +0,0 @@
{
"templates": [
{
"name": "Class",
"extension": "cs",
"file": "./class.cs-template",
"parameters": "./template-parameters.js"
},
{
"name": "Interface",
"extension": "cs",
"file": "./interface.cs-template",
"parameters": "./template-parameters.js"
},
{
"name": "Enum",
"extension": "cs",
"file": "./enum.cs-template",
"parameters": "./template-parameters.js"
},
{
"name": "Class",
"extension": "ts",
"file": "./class.ts-template",
"parameters": "./template-parameters.js"
},
{
"name": "Interface",
"extension": "ts",
"file": "./interface.ts-template",
"parameters": "./template-parameters.js"
},
{
"name": "Default",
"extension": "ts",
"file": "./default.ts-template",
"parameters": "./template-parameters.js"
},
{
"name": "Class",
"extension": "vb",
"file": "./class.vb-template",
"parameters": "./template-parameters.js"
}
]
}

View File

@ -1,17 +0,0 @@
var path = require("path");
module.exports = function(filename, projectPath, folderPath) {
var namespace = "Unknown";
if (projectPath) {
namespace = path.basename(projectPath, path.extname(projectPath));
if (folderPath) {
namespace += "." + folderPath.replace(path.dirname(projectPath), "").substring(1).replace(/[\\\/]/g, ".");
}
namespace = namespace.replace(/[\\\-]/g, "_");
}
return {
namespace: namespace,
name: path.basename(filename, path.extname(filename))
}
};

67
.vscode/tasks.json vendored
View File

@ -1,18 +1,6 @@
{
"version": "2.0.0",
"tasks": [
{
"label": "restore",
"command": "dotnet",
"type": "process",
"args": [
"restore",
"/property:GenerateFullPaths=true",
"/consoleloggerparameters:NoSummary",
"--ignore-failed-sources"
],
"problemMatcher": "$msCompile"
},
{
"label": "build",
"command": "dotnet",
@ -21,23 +9,29 @@
"build",
"/p:Configuration=Debug",
"/property:GenerateFullPaths=true",
"/consoleloggerparameters:NoSummary",
"--ignore-failed-sources"
"/consoleloggerparameters:NoSummary"
],
"problemMatcher": "$msCompile"
"problemMatcher": "$msCompile",
"group": "build"
},
{
"label": "buildcli",
"command": "msbuild",
"label": "db-upgrade",
"command": "dotnet",
"type": "process",
"args": [
"src/isn",
"/p:Configuration=Debug",
"/property:GenerateFullPaths=true",
"/consoleloggerparameters:NoSummary",
"/restore"
"ef",
"database",
"update"
],
"problemMatcher": "$msCompile"
"problemMatcher": "$msCompile",
"options": {
"cwd": "${workspaceFolder}/src/isnd",
"env": {
"ASPNETCORE_ENV": "Development"
}
},
"dependsOn":["build"],
"group": "test"
},
{
"label": "publish",
@ -45,16 +39,17 @@
"type": "process",
"args": [
"publish",
"/property:GenerateFullPaths=true",
"/consoleloggerparameters:NoSummary"
"/p:GenerateFullPaths=true",
"/consoleloggerparameters:NoSummary",
"/p:TargetFramework=netcoreapp2.1",
"/p:RuntimeIdentifier=linux-x64",
"/p:PublishDir=${workspaceFolder}/artifacts"
],
"problemMatcher": "$msCompile"
},
{
"label": "copyTestConfig",
"command": "dotnet",
"type": "process",
"args": [ "build", "/t:CopyTestConfig" ]
"problemMatcher": "$msCompile",
"options": {
"cwd": "${workspaceFolder}"
},
"group": "none"
},
{
"label": "test",
@ -62,7 +57,7 @@
"type": "process",
"options": {
"env": {
"ASPNETCORE_ENVIRONMENT": "Testing"
"ASPNETCORE_ENVIRONMENT": "Development"
}
},
"args": [
@ -71,7 +66,8 @@
"--logger:xunit"
],
"problemMatcher": "$msCompile",
"dependsOn": [ "build", "copyTestConfig"]
"dependsOn": [ "build"],
"group": "test"
},
{
"label": "watch",
@ -87,7 +83,8 @@
"ASPNETCORE_ENVIRONMENT": "Development"
}
},
"problemMatcher": "$msCompile"
"problemMatcher": "$msCompile",
"group": "none"
}
]
}

View File

@ -1,5 +0,0 @@
mode: Mainline
branches: {}
ignore:
sha: []
merge-message-formats: {}

46
Makefile Normal file
View File

@ -0,0 +1,46 @@
CONFIGURATION=Debug
TARGETFV=net8.0
all: build-isn build-isnd
build-%: src/%
dotnet build -p:Configuration=$(CONFIGURATION) $^
pack-%: src/%
dotnet pack $^
watch:
dotnet watch --project=src/isnd
test-push:
isn push src/isn/bin/Debug/isn.*.nupkg
clean-%: src/%
rm -rf $^/bin $^/obj
packs: pack-isn pack-isnd pack-isn.abst
clean: clean-isnd clean-isn clean-isn.abst
TARGETFRAMEWORK=net8.0
server-update:
dotnet build -c Release src/isnd
dotnet publish -c Release -f $(TARGETFRAMEWORK) src/isnd
sudo systemctl stop isnd
sudo cp -a src/isnd/bin/Release/$(TARGETFRAMEWORK)/publish/* /srv/www/isnd
sudo systemctl start isnd
client-update:
dotnet build -c Release src/isn
# MAJ du client
sudo cp -a src/isn/bin/Release/$(TARGETFRAMEWORK)/* /usr/local/lib/isn
sudo chown -R root:root /usr/local/lib/isn
src/isn.abst/bin/Release/isn.abst.1.0.24.nupkg:
dotnet pack src/isn.abst -c Release
push-test: src/isn.abst/bin/Release/isn.abst.1.0.24.nupkg
isn push -s "http://localhost:3002/v3/index.json" src/isn.abst/bin/Release/isn.abst.1.0.24.nupkg

View File

@ -1,6 +1,6 @@
# ISN
[![pipeline status](http://gitlab.pschneider.fr/Paul/nuget-host/badges/main/pipeline.svg)](http://gitlab.pschneider.fr/Paul/nuget-host/-/commits/main)
Hébergement de paquets logiciels Nuget, fournissant une implémentation minimale de l'API Nuget V3
## Usage
@ -18,61 +18,83 @@ wget http://localhost:5000/package/index.json?q=your&prerelease=true&semVerLevel
## Installation
Depuis le dossier de la solution, compiler la solution :
### Compilation
Dans le dossier de la solution, compiler la solution :
````bash
dotnet build /restore -c Release
dotnet publish -c Release
dotnet publish -c Release src/isnd
````
### Déployer le serveur
### Déploiement du serveur
La livraison initiale, aujourd'hui :
````bash
sudo mkdir -p /srv/www/isnd
sudo cp -a src/isnd/bin/Release/netcoreapp2.1/publish/* /srv/www/isnd
sudo cp contrib/isnd /etc/init.d/isnd
sudo chmod +x /etc/init.d/isnd
sudo chown -R www-data.www-data /srv/www/isnd
sudo cp -a src/isnd/bin/Release/net8.0/publish/* /srv/www/isnd
sudo cp contrib/isnd.service /etc/systemd/system
chown -R isn:isn /srv/www/isnd/
sudo systemctl daemon-reload
````
* Créer une base de donées Postgresql,
* ajuster un fichier de configuration `/srv/www/isnd/appsettings.Production.json`
Une base de donées Postgresql est requise, avec, pour faire simple,
son utilisateur, et le droit de créer des tables (ce dernier droit pourrait expirer, mais gare aux mises à jour).
Il faudra éditer la configuration pour indiquer :
* dans /etc/systemd/system/isnd.service , la connextion à une base de donnée Postresgql, sous la forme :
`"Server=<pgserver>;Port=<pgport>;Database=<dbname>;Username=<dbusername>;Password=<dbpass>;"`
* dans /srv/www/isnd/appsettings.Production.json, la connection au serveur de messagerie,
* l'URL externe du ou des sites à propulser, et à utiliser dans la description de service,
* et les autres détails.
Pour faire ceci, vous pourrez éditer une copie du fichier `appsettings.json` vers `appsettings.Production.json`,
pour renseigner toutes les valeurs spécifiées.
* Démarrer le serveur :
````bash
sudo systemctl start isnd
````
* Activer le serveur :
* Activation du serveur :
````bash
sudo systemctl enable isnd
````
### Installer le client
### Installation du client
````bash
sudo mkdir /usr/local/lib/isn
sudo cp -a src/isn/bin/Release/netcoreapp2.1/* /usr/local/lib/isn
sudo cp -a src/isn/bin/Release/net6.0/* /usr/local/lib/isn
sudo chown -R root.root /usr/local/lib/isn
sudo ln -s /usr/local/lib/isn/isn /usr/local/bin/isn
````
### Mises à jour
Dans le détail, la séquence serait du style :
````bash
# compiler tout
dotnet build -c Release
dotnet publish -c Release -f netcoreapp2.1 src/isnd
dotnet publish -c Release -f net8.0 src/isnd
# 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/net8.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/net8.0/* /usr/local/lib/isn
sudo chown -R root:root /usr/local/lib/isn
````
On pourra cibler "client-update" ou "server-update", à la construction :
````bash
make server-update
make client-update
````
## TODO

View File

@ -1,3 +0,0 @@
#!/bin/bash
/usr/bin/mono /usr/local/lib/isn/isn.exe $*

View File

@ -1,110 +0,0 @@
#!/bin/bash
### BEGIN INIT INFO
# Provides: isnd
# Required-Start: $local_fs $network $named $time $syslog $postgresql
# Required-Stop: $local_fs $network $named $time $syslog $postgresql
# Default-Start: 2 3 4 5
# Default-Stop: 0 1 6
# Description: Script to run asp.net 5 application in background
### END INIT INFO
# Author: Ivan Derevianko aka druss <drussilla7@gmail.com>
# Modified by: Paul Schneider <redienhcs.luap@gmail.com>
. /lib/init/vars.sh
. /lib/lsb/init-functions
NAME=isnd
PATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin
TMP_SAVE_runlevel_VAR=$runlevel
unset runlevel
running() {
if [ -f $PIDFILE ]
then
PID=$(cat $PIDFILE)
if kill -0 $PID 2>/dev/null
then
return 0
fi
fi
return 1
}
export WWW_USER=www-data
export ROOT=/srv/www/${NAME}
export DESC="$NAME"
export PIDFILE=/var/run/kestrel-${NAME}.pid
export LOGDIR=/var/log
export DOTNET_CLI_HOME=$ROOT
export ASPDOTNETCORE_ENVIRONMENT=Production
export ASPDOTNETCORE_LOGLEVEL=Information
status() {
if running;
then
echo "Service running $DESC ($NAME; pid: $PID)"
else
echo "Service stopped $DESC ($NAME)"
fi
echo WWW_USER: $WWW_USER ROOT:$ROOT DESC: $DESC NAME: $NAME PIDFILE: $PIDFILE LOGDIR=$LOGDIR
}
start() {
if running; then
echo "Service already running $DESC" "$NAME"
log_end_msg 0
else
cd $ROOT
log_daemon_msg "Starting service $NAME for user $WWW_USER"
if ! start-stop-daemon -SbmCv -u $WWW_USER -p $PIDFILE -d $ROOT -g www-data -x /usr/bin/dotnet isnd.dll run > "${LOGDIR}/kestrel-${NAME}.log"
then
log_daemon_msg "Could not start $NAME : $?, see ${LOGDIR}/kestrel-${NAME}.log"
log_end_msg 2
else
log_daemon_msg "Service $DESC started ($NAME), logs: ${LOGDIR}/kestrel-${NAME}.log"
log_end_msg 0
fi
fi
}
stop() {
if running
then
log_daemon_msg "Stopping service $NAME"
start-stop-daemon -K -p "$PIDFILE"
log_daemon_msg "$DESC stopped"
log_end_msg 0
else
log_daemon_msg "$DESC Service not running"
log_end_msg 1
fi
}
case "$1" in
start)
start
;;
stop)
stop
;;
restart)
stop
start
;;
status)
status
;;
*)
echo "Usage: $0 {start|stop|restart}"
esac
export runlevel=$TMP_SAVE_runlevel_VAR

View File

@ -1,11 +0,0 @@
#!/bin/bash
set -e
# compiler tout
dotnet publish -c Release
# MAJ du serveur
sudo systemctl stop isnd
sudo cp -a src/isnd/bin/Release/netcoreapp2.1/publish/* /srv/www/isnd
sudo systemctl start isnd
# MAJ du client
sudo cp -a src/isn/bin/Release/net472/* /usr/local/lib/isn
sudo chmod +x /usr/local/lib/isn/isn.exe

View File

@ -1,30 +1,41 @@
{
"version": 1,
"isRoot": true,
"tools": {
"codecov.tool": {
"version": "1.13.0",
"commands": [
"codecov"
]
},
"gitversion.tool": {
"version": "5.10.1",
"commands": [
"dotnet-gitversion"
]
},
"gitreleasemanager.tool": {
"version": "0.13.0",
"commands": [
"dotnet-gitreleasemanager"
]
},
"Wyam2.Tool": {
"version": "3.0.0-rc3",
"commands": [
"wyam2"
]
}
"version": 1,
"isRoot": true,
"tools": {
"codecov.tool": {
"version": "1.13.0",
"commands": [
"codecov"
],
"rollForward": false
},
"gitversion.tool": {
"version": "5.10.1",
"commands": [
"dotnet-gitversion"
],
"rollForward": false
},
"gitreleasemanager.tool": {
"version": "0.13.0",
"commands": [
"dotnet-gitreleasemanager"
],
"rollForward": false
},
"Wyam2.Tool": {
"version": "3.0.0-rc3",
"commands": [
"wyam2"
],
"rollForward": false
},
"dotnet-ef": {
"version": "8.0.7",
"commands": [
"dotnet-ef"
],
"rollForward": false
}
}
}
}

27
gitversion.json Normal file
View File

@ -0,0 +1,27 @@
{
"AssemblySemFileVer": "1.1.1.0",
"AssemblySemVer": "1.1.1.0",
"BranchName": "main",
"BuildMetaData": null,
"CommitDate": "2024-10-09",
"CommitsSinceVersionSource": 19,
"EscapedBranchName": "main",
"FullBuildMetaData": "Branch.main.Sha.2b45e421dda285b73bf7eb3387915030cd751b88",
"FullSemVer": "1.1.1-19",
"InformationalVersion": "1.1.1-19+Branch.main.Sha.2b45e421dda285b73bf7eb3387915030cd751b88",
"Major": 1,
"MajorMinorPatch": "1.1.1",
"Minor": 1,
"Patch": 1,
"PreReleaseLabel": "",
"PreReleaseLabelWithDash": "",
"PreReleaseNumber": 19,
"PreReleaseTag": "19",
"PreReleaseTagWithDash": "-19",
"SemVer": "1.1.1-19",
"Sha": "2b45e421dda285b73bf7eb3387915030cd751b88",
"ShortSha": "2b45e42",
"UncommittedChanges": 20,
"VersionSourceSha": "72839e8251297935a4ab138a99152733f07a23f2",
"WeightedPreReleaseNumber": 55019
}

35
src/.vscode/launch.json vendored Normal file
View File

@ -0,0 +1,35 @@
{
"version": "0.2.0",
"configurations": [
{
// Use IntelliSense to find out which attributes exist for C# debugging
// Use hover for the description of the existing attributes
// For further information visit https://github.com/dotnet/vscode-csharp/blob/main/debugger-launchjson.md.
"name": ".NET Core Launch (web)",
"type": "coreclr",
"request": "launch",
"preLaunchTask": "build",
// If you have changed target frameworks, make sure to update the program path.
"program": "${workspaceFolder}/isnd/bin/Debug/net8.0/isnd.dll",
"args": [],
"cwd": "${workspaceFolder}/isnd",
"stopAtEntry": false,
// Enable launching a web browser when ASP.NET Core starts. For more information: https://aka.ms/VSCode-CS-LaunchJson-WebBrowser
"serverReadyAction": {
"action": "openExternally",
"pattern": "\\bNow listening on:\\s+(https?://\\S+)"
},
"env": {
"ASPNETCORE_ENVIRONMENT": "Development"
},
"sourceFileMap": {
"/Views": "${workspaceFolder}/Views"
}
},
{
"name": ".NET Core Attach",
"type": "coreclr",
"request": "attach"
}
]
}

41
src/.vscode/tasks.json vendored Normal file
View File

@ -0,0 +1,41 @@
{
"version": "2.0.0",
"tasks": [
{
"label": "build",
"command": "dotnet",
"type": "process",
"args": [
"build",
"${workspaceFolder}/isnd/isnd.csproj",
"/property:GenerateFullPaths=true",
"/consoleloggerparameters:NoSummary;ForceNoAlign"
],
"problemMatcher": "$msCompile"
},
{
"label": "publish",
"command": "dotnet",
"type": "process",
"args": [
"publish",
"${workspaceFolder}/isnd/isnd.csproj",
"/property:GenerateFullPaths=true",
"/consoleloggerparameters:NoSummary;ForceNoAlign"
],
"problemMatcher": "$msCompile"
},
{
"label": "watch",
"command": "dotnet",
"type": "process",
"args": [
"watch",
"run",
"--project",
"${workspaceFolder}/isnd/isnd.csproj"
],
"problemMatcher": "$msCompile"
}
]
}

11
src/Directory.Build.props Normal file
View File

@ -0,0 +1,11 @@
<Project>
<PropertyGroup>
<Version>1.0.8</Version>
<Authors>paul</Authors>
<Copyright>WFPL</Copyright>
<PackageLicenseExpression></PackageLicenseExpression>
<ProjectUrl></ProjectUrl>
<RepositoryUrl></RepositoryUrl>
<RepositoryType>git</RepositoryType>
</PropertyGroup>
</Project>

11
src/isn.abst/APIKO.cs Normal file
View File

@ -0,0 +1,11 @@
using System.Collections.Generic;
namespace isn
{
public class APIKO
{
public string Context { get; set; }
public Dictionary<string, string[]> Errors { get; set; }
}
}

24
src/isn.abst/ApiConfig.cs Normal file
View File

@ -0,0 +1,24 @@
using System;
using isn.abst;
namespace isnd.Entities
{
public static class ApiConfig
{
public const string Index = "/index.json";
public const string Catalog = "/catalog";
public const string Package = "/package";
public const string Search = "/search";
public const string AutoComplete = "/autocomplete";
public const string Registration = "/registration";
public const string ContentBase = "/content";
public const string Nuget = "/nuget";
public const string PackageDetailUriTemplate = Package+"/{id}/{version}";
[Obsolete("use the V3 search")]
public const string V2Find = "/v2/FindPackagesById()"; // /FindPackagesById()??$filter=IsLatestVersion&$orderby=Version desc&$top=1&id='isn.abst'
}
}

View File

@ -1,9 +1,14 @@
using isnd.Data.Catalog;
using Newtonsoft.Json;
namespace isn.Abstract
{
public class ApiIndexViewModel
public class ApiIndexViewModel : Permalink
{
public ApiIndexViewModel(string id) : base(id, "ApiIndex")
{
}
[JsonProperty("version")]
public string Version { get; set; }

View File

@ -0,0 +1,9 @@
namespace isn.abst
{
public static class Constants
{
public const string PacketFileExtension = "nupkg";
public const string SpecFileExtension = "nuspec";
public const string ApiVersionPrefix = "/v3";
}
}

48
src/isn.abst/Permalink.cs Normal file
View File

@ -0,0 +1,48 @@
using Newtonsoft.Json;
namespace isnd.Data.Catalog
{
public abstract class Permalink
{
public Permalink(string id)
{
Type = GetType().Name;
this.id = id;
}
public Permalink(string id, string type)
{
Type = type;
this.id = id;
}
[JsonProperty("@type")]
public virtual string Type { get; set; }
[JsonProperty("@id")]
public string Id { get => id; }
protected string id;
public string GetId() { return id; }
public override bool Equals(object obj)
{
if (obj!=null)
{
if (GetType().IsAssignableFrom(obj.GetType()))
{
var rpobj = (Permalink) obj;
return this.id == rpobj.id;
}
}
return base.Equals(obj);
}
public override int GetHashCode()
{
return id.GetHashCode();
}
}
}

View File

@ -1,14 +1,15 @@
using isnd.Data.Catalog;
using Newtonsoft.Json;
namespace isn.Abstract
{
public class Resource
public class Resource : Permalink
{
[JsonProperty("@id")]
public string Id {get; set; }
public Resource(string id, string typename) : base(id)
{
Type = typename;
}
[JsonProperty("@type")]
public string Type {get; set; }
[JsonProperty("comment")]
public string Comment {get; set; }

View File

@ -4,7 +4,7 @@ using System.Linq;
namespace isnd.Attributes
{
internal class SafeNameAttribute : ValidationAttribute
public class SafeNameAttribute : ValidationAttribute
{
public override bool IsValid(object value)
{

View File

@ -1,14 +1,8 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<PackageVersion>1.0.1</PackageVersion>
<Version>0.1.175</Version>
<TargetFramework>netcoreapp2.1</TargetFramework>
<NoWarn>NETSDK1138</NoWarn>
<AssemblyVersion>0.1.175.0</AssemblyVersion>
<FileVersion>0.1.175.0</FileVersion>
<InformationalVersion>0.1.175+Branch.main.Sha.3e09afcbfe0eff74c0b3aa0fb974e0801f4708b6</InformationalVersion>
</PropertyGroup>
<TargetFrameworks>net7.0;net8.0</TargetFrameworks>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Newtonsoft.Json" Version="11.0.1" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
</ItemGroup>
</Project>

View File

@ -1,46 +0,0 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace isn
{
public interface IDataProtector
{
string Protect(string data);
string UnProtect(string data);
}
public class DefaultDataProtector : IDataProtector
{
private byte delta = 145;
public DefaultDataProtector()
{
}
public string Protect(string data)
{
List<Byte> protd = new List<byte>();
StringBuilder sb = new StringBuilder();
foreach (byte c in Encoding.UTF8.GetBytes(data))
{
protd.Add((byte) (c ^ delta));
}
return System.Convert.ToBase64String(protd.ToArray());
}
public string UnProtect(string data)
{
StringBuilder sb = new StringBuilder();
List<byte> unps = new List<byte>();
foreach (byte c in System.Convert.FromBase64CharArray(data.ToCharArray(),0,data.Length))
{
unps.Add((byte) (c ^ delta));
}
return Encoding.UTF8.GetString(unps.ToArray());
}
}
}

View File

@ -1,9 +0,0 @@
namespace isn
{
public class IsnSourceSettings
{
internal string Source { get; set; }
internal string[] Keys { get; set; }
}
}

View File

@ -2,6 +2,8 @@
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Security.Cryptography;
using System.Text;
using Mono.Options;
using Newtonsoft.Json;
@ -16,15 +18,21 @@ namespace isn
if (cfgSettingIf.Exists)
{
var json = File.ReadAllText(cfgSettingIf.FullName);
settings = JsonConvert.DeserializeObject<Settings>(json);
currentSource = settings.DefaultSource;
Settings = JsonConvert.DeserializeObject<Settings>(json);
CurrentSource = Settings.DefaultSourceKey;
}
if (Settings==null)
{
Settings= Settings.Create();
}
}
static OptionSet storeoptions = new OptionSet {
{ "s|source=", "use source", val => currentSource = currentSource ?? val },
static readonly OptionSet storeoptions = new OptionSet {
{ "s|source=", "use source", val => Settings.CurrentSourceKey ??= val },
{ "h|help", "show this message and exit", h => shouldShowPushHelp = h != null },
};
private static string _configFileName =
private static readonly string _configFileName =
Path.Combine(
Path.Combine(Environment.GetFolderPath(
Environment.SpecialFolder.UserProfile), ".isn"),
@ -32,54 +40,42 @@ namespace isn
;
public const string push = "push";
static OptionSet options = new OptionSet {
static readonly OptionSet options = new OptionSet {
{ "h|help", "show this message and exit", h => shouldShowHelp = h != null },
{ "v|version", "show soft version info and exit", h => shouldShowVersion = h != null }
};
static OptionSet pushoptions = new OptionSet {
{ "k|api-key=", "use api key", val => apiKey = apiKey ?? val },
{ "p|store-api-key", "store used api key (=<true|false>)", val => storApiKey = val != null },
{ "s|source=", "use source", val => currentSource = currentSource ?? val },
static readonly OptionSet pushoptions = new OptionSet {
{ "k|api-key=", "use api key", val => Settings.CurrentSource.SetApiKey(val)},
{ "s|source=", "use source", val => Settings.CurrentSourceKey = val },
{ "h|help", "show this message and exit", h => shouldShowPushHelp = h != null },
};
static OptionSet sourceoptions = new OptionSet {
static readonly OptionSet sourceoptions = new OptionSet {
{ "h|help", "show this message and exit", h => shouldShowSourceHelp = h != null },
};
static OptionSet showOptions = new OptionSet {
static readonly OptionSet showOptions = new OptionSet {
{ "h|help", "show this message and exit", h => shouldShowSourceHelp = h != null },
};
private static bool shouldShowHelp;
private static bool shouldShowVersion;
private static bool shouldShowSourceHelp;
private static bool shouldShowPushHelp;
private static string apiKey = null;
private static string currentSource = null;
private static int pushKO = 0;
private static bool storApiKey = false;
public static IDataProtector Protector { get; set; } = new DefaultDataProtector();
static Settings settings = null;
public static Settings Settings
{
get
{
if (settings == null)
LoadConfig();
if (settings == null)
settings = new Settings
{
DataProtectionTitle = "isn",
Sources = new Dictionary<string, SourceSettings>()
};
return settings;
}
get; set;
}
public static string CurrentSource { get => Settings.CurrentSourceKey; set => Settings.CurrentSourceKey = value; }
static int Main(string[] args)
{
LoadConfig();
var commandSet = new CommandSet("isn");
var srclst = new Command("list")
{
@ -150,13 +146,15 @@ namespace isn
{
Run = sargs => Add(sargs)
};
commandSet.Add(add);
var pushCmd = new Command(push)
{
Run = async sargs =>
Run = sargs =>
{
var pargs = pushoptions.Parse(sargs);
if (args.Count()==0) shouldShowPushHelp=true;
if (shouldShowPushHelp)
{
// output the options
@ -165,17 +163,19 @@ namespace isn
return;
}
List<PushReport> reports = PushPkg(pargs);
Console.WriteLine(JsonConvert.SerializeObject(reports));
pushKO = reports.Count(r => !r.OK && !r.AlreadyPresent);
}
};
var setapikey = new Command("set-api-key")
var setapikey = new Command("store-api-key", "Store this API key")
{
Run = sargs => StoreApiKey(sargs)
};
commandSet.Add(pushCmd);
setapikey.Options = pushoptions;
commandSet.Add(setapikey);
commandSet.Add(pushCmd);
commandSet.Add(srcCmd);
commandSet.Add(showCommand);
@ -202,9 +202,11 @@ namespace isn
return 1;
}
int runCode = commandSet.Run(args);
return (runCode == 0 && pushKO > 0) ? 500 : runCode;
if (shouldShowVersion)
{
Console.WriteLine("isn version " + GitVersionInformation.AssemblySemFileVer);
}
return commandSet.Run(args);
}
}
}

View File

@ -1,19 +1,90 @@
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations.Schema;
using System.Security.Cryptography;
using System.Text;
using Newtonsoft.Json;
namespace isn
{
public class SourceSettings
{
public string ApiKey { get; set; }
public string Alias { get; set; }
/// <summary>
/// Protected API Key
/// </summary>
/// <value></value>
public string ApiKey { get; set; }
/// <summary>
/// Key alias
/// </summary>
/// <value></value>
public string Url { get; set; }
public SourceSettings()
{
}
public string GetClearApiKey()
{
return ApiKey;
}
public void SetApiKey(string key)
{
ApiKey = key;
}
}
public class Settings
{
public string DataProtectionTitle {get; set; }
private Settings()
{
}
public static Settings Create()
{
return new Settings {
Sources = new Dictionary<string, SourceSettings>()
};
}
public Dictionary<string, SourceSettings> Sources { get; set; }
public bool AutoUpdateApiKey { get; set; } = false;
public string DefaultSource { get; set; }
private string defSourceKey;
/// <summary>
/// Default source by its alias
/// </summary>
/// <value></value>
public string DefaultSourceKey
{
get => defSourceKey;
set
{
defSourceKey = value;
}
}
[JsonIgnore, NotMapped]
public string CurrentSourceKey {get; set;}
[JsonIgnore, NotMapped]
public SourceSettings CurrentSource
{
get
{
if (CurrentSourceKey==null) return null;
if (!Sources.ContainsKey(CurrentSourceKey))
{
Sources.Add(CurrentSourceKey, new SourceSettings());
}
return Sources[CurrentSourceKey];
}
}
}
}
}

View File

@ -17,43 +17,24 @@ namespace isn
// var json = await client.GetStringAsync(new System.Uri(url));
Task.Run(async ()=> {
try {
var response = await client.GetStringAsync(url);
result = JsonConvert.DeserializeObject<ApiIndexViewModel>(response);
} catch (HttpRequestException ex)
{
if (ex.StatusCode==HttpStatusCode.NotFound)
{
Console.Error.WriteLine("Not found ... server's down ?");
}
else
{
Console.Error.WriteLine($"{ex.StatusCode} : {ex.Message}");
}
}
}).Wait();
return result;
}
public static async Task<ApiIndexViewModel> GetServerResourcesUsingWebRequestAsync(string url)
{
ApiIndexViewModel viewModel=null;
var uri = new Uri(url);
HttpWebRequest httpWebRequest = (HttpWebRequest)WebRequest.Create(url);
httpWebRequest.Method = "GET";
httpWebRequest.AllowAutoRedirect = false;
try{
using (var resp = await httpWebRequest.GetResponseAsync())
{
using (var stream = resp.GetResponseStream())
{
using (var reader = new StreamReader(stream))
{
var json = await reader.ReadToEndAsync();
Console.Write("got json : "+json);
viewModel = JsonConvert.DeserializeObject<ApiIndexViewModel>(json);
}
}
}
}
catch(Exception ex)
{
Console.Error.WriteLine(ex.Message);
}
return viewModel;
}
}
}

View File

@ -4,6 +4,7 @@ using System.IO;
using System.Net;
using System.Net.Http;
using System.Threading.Tasks;
using Newtonsoft.Json;
namespace isn
{
@ -24,35 +25,19 @@ namespace isn
using (var multipartFormDataContent = new MultipartFormDataContent())
{
/* var values = new[]
{
new KeyValuePair<string, string>("Id", Guid.NewGuid().ToString()),
new KeyValuePair<string, string>("Key", "awesome"),
new KeyValuePair<string, string>("From", "khalid@home.com")
//other values
};foreach (var keyValuePair in values)
{
multipartFormDataContent.Add(new StringContent(keyValuePair.Value),
String.Format("\"{0}\"", keyValuePair.Key));
} */
multipartFormDataContent.Add(new ByteArrayContent(File.ReadAllBytes(fi.FullName)),
'"' + "File" + '"',
'"' + fi.Name + '"');
var result = await client.PutAsync(uri, multipartFormDataContent);
if (result.IsSuccessStatusCode)
{
string report = await result.Content.ReadAsStringAsync();
Console.WriteLine(report);
}
if (result.IsSuccessStatusCode) return
new PushReport() {
KO = JsonConvert.DeserializeObject<APIKO>(await result.Content.ReadAsStringAsync())
};
else
{
string ereport = await result.Content.ReadAsStringAsync();
Console.WriteLine(ereport);
}
return new PushReport();
return new PushReport() {
OK = true
};
}
}
}

View File

@ -1,195 +0,0 @@
using System;
using System.IO;
using System.Net;
using System.Text;
using System.Threading.Tasks;
using Newtonsoft.Json;
namespace isn
{
public class UploadFilesToServerUsingWebRequest
{
public void UploadFilesToServer(PushReport report, Uri uri, FileInfo fi,
string apikey)
{
// string formdataTemplate = "Content-Disposition: form-data; name=\"{0}\"\r\n\r\n{1}";
const int TXLEN = 0x1000;
/// the form-data file upload, properly formatted
string fileheaderTemplate = "Content-Disposition: form-data; name=\"{0}\"; filename=\"{1}\";\r\nContent-Type: {2}\r\n\r\n";
ServicePointManager.ServerCertificateValidationCallback = delegate { return true; };
// "X-NuGet-ApiKey
string boundary = "----------" + DateTime.Now.Ticks.ToString("x");
string fileheader = string.Format(fileheaderTemplate, "file", fi.Name, "application/octet-stream");
byte[] fileheaderbytes = Encoding.ASCII.GetBytes(fileheader);
var boundarybytes = System.Text.Encoding.ASCII.GetBytes("\r\n--" + boundary + "\r\n");
var endBoundaryBytes = System.Text.Encoding.ASCII.GetBytes("\r\n--" + boundary + "--");
HttpWebRequest httpWebRequest = (HttpWebRequest) WebRequest.Create(uri);
httpWebRequest.Method = "PUT";
httpWebRequest.ContentType = "multipart/form-data; boundary=" + boundary;
httpWebRequest.AllowAutoRedirect = false;
httpWebRequest.Headers.Add("X-NuGet-Client-Version", Constants.ClientVersion);
httpWebRequest.Headers.Add("X-NuGet-ApiKey", apikey);
httpWebRequest.ContentLength = boundarybytes.Length +
fileheaderbytes.Length + fi.Length + endBoundaryBytes.Length;
httpWebRequest.BeginGetRequestStream(async (result) =>
{
try
{
HttpWebRequest request = (HttpWebRequest)result.AsyncState;
using (Stream requestStream = request.EndGetRequestStream(result))
{
await WriteToStream(requestStream, boundarybytes, boundarybytes.Length);
await WriteToStream(requestStream, fileheaderbytes, fileheaderbytes.Length);
using (var fss = fi.OpenRead())
{
byte[] buffer = new byte[TXLEN];
var form_bytes_read = fss.Read(buffer, 0, TXLEN);
while (form_bytes_read > 0)
{
await WriteToStream(requestStream, buffer, form_bytes_read);
form_bytes_read = fss.Read(buffer, 0, TXLEN);
}
}
requestStream.Write(endBoundaryBytes, 0, endBoundaryBytes.Length);
requestStream.Close();
}
}
catch (Exception rex)
{
report.Executed = false;
report.StackTrace = rex.StackTrace;
report.Message = rex.Message;
Console.Error.WriteLine("Stack trace:");
Console.Error.WriteLine(rex.StackTrace);
WebResponse eresp = httpWebRequest.GetResponse();
if (!CheckResponse(eresp, report))
throw new Exception("Invalid server response type");
}
}, httpWebRequest);
WebResponse resp = httpWebRequest.GetResponse();
if (!CheckResponse(resp, report)) throw new Exception("Invalid server response type");
if (Program.Settings.AutoUpdateApiKey)
Program.EnsureKeyStored();
}
static bool CheckResponse(WebResponse resp, PushReport report)
{
Stream stream = resp.GetResponseStream();
StreamReader re = new StreamReader(stream);
if (resp is HttpWebResponse)
{
if (resp.ContentType == "text/json")
{
String json = re.ReadToEnd();
report.Message = json;
var hrep = resp as HttpWebResponse;
report.StatusCode = hrep.StatusCode.ToString();
report.OK = hrep.StatusCode == HttpStatusCode.Accepted
|| hrep.StatusCode == HttpStatusCode.OK;
return true;
}
}
return false;
}
/// <summary>
/// Creates HTTP POST request &amp; uploads database to server. Author : Farhan Ghumra
/// </summary>
internal async Task UploadFilesToServerAsync(PushReport report, Uri uri, FileInfo fi,
string apikey)
{
// string formdataTemplate = "Content-Disposition: form-data; name=\"{0}\"\r\n\r\n{1}";
const int TXLEN = 0x1000;
/// the form-data file upload, properly formatted
string fileheaderTemplate = "Content-Disposition: form-data; name=\"{0}\"; filename=\"{1}\";\r\nContent-Type: {2}\r\n\r\n";
ServicePointManager.ServerCertificateValidationCallback = delegate { return true; };
// "X-NuGet-ApiKey
string boundary = "----------" + DateTime.Now.Ticks.ToString("x");
string fileheader = string.Format(fileheaderTemplate, "file", fi.Name, "application/octet-stream");
byte[] fileheaderbytes = Encoding.ASCII.GetBytes(fileheader);
var boundarybytes = System.Text.Encoding.ASCII.GetBytes("\r\n--" + boundary + "\r\n");
var endBoundaryBytes = System.Text.Encoding.ASCII.GetBytes("\r\n--" + boundary + "--");
HttpWebRequest httpWebRequest = (HttpWebRequest)WebRequest.Create(uri);
httpWebRequest.Method = "PUT";
httpWebRequest.ContentType = "multipart/form-data; boundary=" + boundary;
httpWebRequest.AllowAutoRedirect = false;
httpWebRequest.Headers.Add("X-NuGet-Client-Version", Constants.ClientVersion);
httpWebRequest.Headers.Add("X-NuGet-ApiKey", apikey);
httpWebRequest.ContentLength = boundarybytes.Length +
fileheaderbytes.Length + fi.Length + endBoundaryBytes.Length;
httpWebRequest.BeginGetRequestStream(async (result) =>
{
try
{
HttpWebRequest request = (HttpWebRequest)result.AsyncState;
using (Stream requestStream = request.EndGetRequestStream(result))
{
await WriteToStream(requestStream, boundarybytes, boundarybytes.Length);
await WriteToStream(requestStream, fileheaderbytes, fileheaderbytes.Length);
using (var fss = fi.OpenRead())
{
byte[] buffer = new byte[TXLEN];
var form_bytes_read = fss.Read(buffer, 0, TXLEN);
while (form_bytes_read > 0)
{
await WriteToStream(requestStream, buffer, form_bytes_read);
form_bytes_read = fss.Read(buffer, 0, TXLEN);
}
}
requestStream.Write(endBoundaryBytes, 0, endBoundaryBytes.Length);
requestStream.Close();
}
}
catch (Exception rex)
{
report.Message = rex.Message;
Console.Error.WriteLine(rex.Message);
Console.Error.WriteLine("Stack trace:");
Console.Error.WriteLine(rex.StackTrace);
throw;
}
}, httpWebRequest);
WebResponse resp = await httpWebRequest.GetResponseAsync();
Stream stream = resp.GetResponseStream();
StreamReader re = new StreamReader(stream);
if (resp is HttpWebResponse)
{
String json = await re.ReadToEndAsync();
report.Message = json;
var hrep = resp as HttpWebResponse;
report.StatusCode = hrep.StatusCode.ToString();
report.OK = hrep.StatusCode == HttpStatusCode.Accepted
|| hrep.StatusCode == HttpStatusCode.OK;
}
else throw new Exception("Invalid server response type");
}
/// <summary>
/// Writes byte array to stream. Author : Farhan Ghumra
/// </summary>
private static async Task WriteToStream(Stream s, byte[] bytes, int len)
{
await s.WriteAsync(bytes, 0, len);
}
}
}

View File

@ -10,12 +10,18 @@ namespace isn
{
public class PushCommand
{
static public PushReport Run(string pkg, string source)
Settings settings;
public PushCommand(Settings settings)
{
if (source == null) source = Program.Settings.DefaultSource;
if (source == null) throw new InvalidOperationException("source is null");
string apikey = Program.Protector.UnProtect(Program.Settings.Sources[source].ApiKey);
this.settings = settings;
}
public PushReport Run(string pkg, string source, string apiKey)
{
var resources = SourceHelpers.GetServerResources(source);
if (resources == null) return null;
if (resources.Resources == null)
throw new InvalidOperationException("source gave no resource");
if (!resources.Resources.Any(res => res.Type == "PackagePublish/2.0.0"))
@ -27,26 +33,30 @@ namespace isn
var report = new PushReport
{
PkgName = fi.Name,
Message = "Le fichier n'existe pas"
Message = "The package does not exist : " + fi.FullName
};
return report;
}
using (var client = new HttpClient())
using (var client = new HttpClient(
new HttpClientHandler
{
AllowAutoRedirect = false
}
))
try
{
Console.WriteLine("Connecting to "+ pubRes.Id);
return client.UploadFilesToServer(new Uri(pubRes.Id), fi, apikey);
return client.UploadFilesToServer(new Uri(pubRes.Id), fi, settings.Sources[source].ApiKey);
}
catch (HttpRequestException hrex)
catch (HttpRequestException httpEx)
{
var report = new PushReport
{
PkgName = fi.Name,
Message = "HttpRequest: " + hrex.Message,
StackTrace = hrex.StackTrace,
StatusCode = hrex.HResult.ToString()
Message = "HttpRequest: " + httpEx.Message,
StackTrace = httpEx.StackTrace,
StatusCode = httpEx.HResult.ToString()
};
Console.Error.WriteLine(hrex.Message);
Console.Error.WriteLine(httpEx.Message);
return report;
}
catch (Exception ex)

View File

@ -12,17 +12,15 @@ namespace isn
public static List<PushReport> PushPkg(IEnumerable<string> pkgs)
{
List<PushReport> pushReports = new List<PushReport>();
var cmd = new PushCommand(Settings);
if (Settings.CurrentSource == null) throw new InvalidOperationException("source is null");
var source = Settings.CurrentSource;
foreach (string pkg in pkgs)
{
var report = PushCommand.Run(pkg, currentSource);
var report = cmd.Run(pkg, source.Url, source.ApiKey);
pushReports.Add(report);
}
if (storApiKey)
{
EnsureKeyStored();
}
return pushReports;
}

View File

@ -1,75 +0,0 @@
using System;
using System.Collections.Generic;
using System.IO;
using Newtonsoft.Json;
namespace isn
{
partial class Program
{
private static void StoreApiKey(IEnumerable<string> storeArgs)
{
var args = storeoptions.Parse(storeArgs);
if (shouldShowPushHelp)
{
// output the options
Console.Error.WriteLine("Push Options:");
storeoptions.WriteOptionDescriptions(Console.Out);
}
else
{
apiKey = args[0];
EnsureKeyStored();
}
}
public static void EnsureKeyStored()
{
if (currentSource == null)
{
if (Settings.DefaultSource == null)
return;
currentSource = Settings.DefaultSource;
}
if (Settings.Sources.ContainsKey(currentSource))
{
if (apiKey == null)
{
// Une suppression
Settings.Sources.Remove(currentSource);
if (Settings.DefaultSource == currentSource) Settings.DefaultSource = null;
}
else
{
// Une mise À jour
string ptd = Protector.Protect(apiKey);
Settings.Sources[currentSource].ApiKey = ptd;
if (Settings.DefaultSource == null) Settings.DefaultSource = currentSource;
}
}
else if (apiKey != null)
{
// une addition
string ptd = Protector.Protect(apiKey);
Settings.Sources.Add(currentSource, new SourceSettings { ApiKey = ptd });
}
SaveConfig();
}
public static void SaveConfig()
{
FileInfo cfgSettingIf = new FileInfo(_configFileName);
if (!cfgSettingIf.Directory.Exists) cfgSettingIf.Directory.Create();
File.WriteAllText(
cfgSettingIf.FullName,
JsonConvert.SerializeObject(
Settings,
Formatting.Indented
));
}
}
}

View File

@ -16,7 +16,7 @@ namespace isn
{
SourceSettings setting = Settings.Sources[arg];
throw new InvalidOperationException
(setting.Alias);
(setting.Url);
}
throw new NotImplementedException();
}

View File

@ -11,9 +11,9 @@ namespace isn
SourceSettings settings =
Settings.Sources.ContainsKey(arg) ?
Settings.Sources[arg] :
Settings.Sources.Values.FirstOrDefault((s)=> s.Alias == arg) ;
Settings.Sources.Values.FirstOrDefault((s)=> s.Url == arg) ;
if (settings==null) throw new InvalidOperationException(arg);
Settings.DefaultSource = arg;
Settings.DefaultSourceKey = arg;
SaveConfig();
}

View File

@ -0,0 +1,46 @@
using System;
using System.Collections.Generic;
using System.IO;
using Newtonsoft.Json;
namespace isn
{
partial class Program
{
private static void StoreApiKey(IEnumerable<string> storeArgs)
{
var args = storeoptions.Parse(storeArgs);
if (args.Count != 1)
{
Console.Error.WriteLine("StoreApiKey command takes only one argument, the key.");
shouldShowPushHelp=true;
}
if (shouldShowPushHelp)
{
// output the options
Console.Error.WriteLine("StoreApiKey Options:");
storeoptions.WriteOptionDescriptions(Console.Out);
}
else
{
Settings.CurrentSource.SetApiKey(args[0]);
SaveConfig();
}
}
public static void SaveConfig()
{
FileInfo cfgSettingIf = new FileInfo(_configFileName);
if (!cfgSettingIf.Directory.Exists) cfgSettingIf.Directory.Create();
File.WriteAllText(
cfgSettingIf.FullName,
JsonConvert.SerializeObject(
Settings,
Formatting.Indented
));
Console.WriteLine("config saved .");
}
}
}

View File

@ -1,27 +1,22 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFrameworks>netcoreapp2.1</TargetFrameworks>
<TargetFrameworks>net8.0</TargetFrameworks>
<RootNamespace>nuget_cli</RootNamespace>
<UserSecretsId>45b74c62-05bc-4603-95b4-3e80ae2fdf50</UserSecretsId>
<Version>0.1.175</Version>
<PackageVersion>1.0.1</PackageVersion>
<IsPackable>true</IsPackable>
<PackageLicenseExpression>WTFPL</PackageLicenseExpression>
<IsTool>true</IsTool>
<NoWarn>NETSDK1138</NoWarn>
<AssemblyVersion>0.1.175.0</AssemblyVersion>
<FileVersion>0.1.175.0</FileVersion>
<InformationalVersion>0.1.175+Branch.main.Sha.3e09afcbfe0eff74c0b3aa0fb974e0801f4708b6</InformationalVersion>
<InformationalVersion>1.0.7+Branch.main.Sha.3695c1742965d93eba0ad851656cfaa3e44ba327</InformationalVersion>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="System.Security.Cryptography.Pkcs" Version="8.0.1" />
<PackageReference Include="Mono.Options" Version="5.3.0" />
<PackageReference Include="Newtonsoft.Json" Version="11.0.2" />
<PackageReference Include="unleash.client" Version="1.6.1" />
<PackageReference Include="Microsoft.AspNetCore.App" />
<Reference Include="System.Net.Http" Version="4.0.0" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
<PackageReference Include="unleash.client" Version="4.1.13" />
<PackageReference Include="GitVersion.MsBuild" Version="6.0.3" />
<ProjectReference Include="../isn.abst/isn.abst.csproj" />
</ItemGroup>
</Project>

View File

@ -1,15 +1,42 @@
using System.Threading.Tasks.Dataflow;
using System.Net;
using System.Text;
using Newtonsoft.Json;
namespace isn
{
public class PushReport
{
public PushReport()
{
}
public string PkgName { get; set; }
public bool Executed { get; internal set; }
public bool OK { get; internal set; }
public bool AlreadyPresent { get; internal set; }
public string Message { get; internal set; }
public string StatusCode { get; internal set; }
public string StackTrace { get; internal set; }
public bool Executed { get; set; }
public bool OK { get; set; }
public bool AlreadyPresent { get; set; }
public string Message { get; set; }
public string StatusCode { get; set; }
public string StackTrace { get; set; }
public APIKO KO { get; set; }
public string ToDoc()
{
StringBuilder sb = new StringBuilder($"= push {PkgName}\n\n");
if (Executed) sb.AppendLine("* Executed");
if (OK) sb.AppendLine("* OK");
if (!string.IsNullOrWhiteSpace(Message))
sb.AppendLine("* Message :" + Message);
if (!string.IsNullOrWhiteSpace(StatusCode))
sb.AppendLine($"* Status Code : ");
if (!string.IsNullOrWhiteSpace(StackTrace))
sb.AppendLine($"* StackTrace : " + StackTrace);
if (KO!=null)
sb.AppendLine($"* KO : " + KO.Context);
return sb.ToString();
}
}
}

View File

@ -5,6 +5,4 @@ namespace isnd.Authorization
internal class ValidApiKeyRequirement : IAuthorizationRequirement
{
}
}

View File

@ -13,6 +13,7 @@ using Microsoft.Extensions.Options;
using isnd.Data;
using isnd.Entities;
using isnd.Data.ApiKeys;
using isnd.Interfaces;
namespace isnd.Controllers
@ -23,17 +24,20 @@ namespace isnd.Controllers
private readonly ApplicationDbContext dbContext;
private readonly IsndSettings isndSettings;
private readonly UserManager<ApplicationUser> _userManager;
private readonly IApiKeyProvider apiKeyProvider;
private readonly IDataProtector protector;
public ApiKeysController(ApplicationDbContext dbContext,
IOptions<IsndSettings> isndSettingsOptions,
IDataProtectionProvider provider,
UserManager<ApplicationUser> userManager)
UserManager<ApplicationUser> userManager,
IApiKeyProvider apiKeyProvider
)
{
this.dbContext = dbContext;
this.isndSettings = isndSettingsOptions.Value;
protector = provider.CreateProtector(isndSettings.ProtectionTitle);
_userManager = userManager;
this.apiKeyProvider = apiKeyProvider;
}
[HttpGet]
@ -57,17 +61,17 @@ namespace isnd.Controllers
[HttpPost]
public async Task<ActionResult> Create(CreateModel model)
{
string userid = User.FindFirstValue(ClaimTypes.NameIdentifier);
IQueryable<ApiKey> userKeys = GetUserKeys();
string userId = User.FindFirstValue(ClaimTypes.NameIdentifier);
IQueryable<ApiKey> userKeys = apiKeyProvider.GetUserKeys(User.Identity.Name);
if (userKeys.Count() >= isndSettings.MaxUserKeyCount)
{
ModelState.AddModelError(null, "Maximum key count reached");
return View();
}
ApiKey newKey = new ApiKey { UserId = userid, Name = model.Name,
CreationDate = DateTime.Now };
_ = dbContext.ApiKeys.Add(newKey);
_ = await dbContext.SaveChangesAsync();
model.UserId = userId;
ApiKey newKey = await apiKeyProvider.CreateApiKeyAsync(model);
return View("Details", new DetailModel { Name = newKey.Name,
ProtectedValue = protector.Protect(newKey.Id),
ApiKey = newKey });
@ -79,7 +83,6 @@ namespace isnd.Controllers
string userid = User.FindFirstValue(ClaimTypes.NameIdentifier);
ApiKey key = await dbContext.ApiKeys.FirstOrDefaultAsync(k => k.Id == id && k.UserId == userid);
return View(new DeleteModel { ApiKey = key });
}
[HttpPost]
@ -103,7 +106,7 @@ namespace isnd.Controllers
ApiKey key = await dbContext.ApiKeys.FirstOrDefaultAsync(k => k.Id == id && k.UserId == userid);
if (key == null)
{
ModelState.AddModelError(null, "Key not found");
ModelState.AddModelError("id", "Key not found");
return View();
}
return View("Details", new DetailModel { ApiKey = key, Name = key.Name, ProtectedValue = protector.Protect(key.Id)});

View File

@ -4,32 +4,42 @@ using Microsoft.Extensions.Logging;
using isnd.Data;
using System.Linq;
using isnd.ViewModels;
using Unleash;
using System.Reflection;
using Microsoft.EntityFrameworkCore;
using Microsoft.Build.Logging;
using Microsoft.Extensions.Options;
using isnd.Entities;
using isn.abst;
namespace isnd.Controllers
{
/// <summary>
/// Home Controller
/// </summary>
public class HomeController : Controller
{
private readonly ApplicationDbContext _dbContext;
private readonly IUnleash _unleashĈlient;
private readonly IsndSettings _isndSettings;
/// <summary>
/// Home controller ctor
/// </summary>
/// <param name="dbContext"></param>
/// <param name="isndSettings"></param>
public HomeController(
ApplicationDbContext dbContext,
IUnleash unleashĈlient)
ApplicationDbContext dbContext, IOptions<IsndSettings> isndSettings)
{
_dbContext = dbContext;
_unleashĈlient = unleashĈlient;
_isndSettings = isndSettings.Value;
}
public IActionResult Index()
{
return View(new HomeIndexViewModel{
PkgCount = _dbContext.Packages.Count(),
UnleashClient = _unleashĈlient
});
PkgCount = _dbContext.Packages
.Where(p => p.Versions.Count > 0)
.Count(),
APIUrl = _isndSettings.ExternalUrl + Constants.ApiVersionPrefix + ApiConfig.Index});
}
public IActionResult About()
@ -58,9 +68,9 @@ namespace isnd.Controllers
return View(ViewData);
}
public IActionResult Error()
public IActionResult Error(string id)
{
return View();
return View("Error", id);
}
}
}

View File

@ -10,7 +10,7 @@ namespace isnd.Controllers
public class NewUpdateController : Controller
{
[Authorize(Policy = IsndConstants.RequireAdminPolicyName)]
public IActionResult NewRelease(NewReleaseInfo version)
public IActionResult NewRelease(NewReleaseInfo release)
{
throw new NotImplementedException("web hook");
}

View File

@ -27,7 +27,10 @@ namespace isnd
// GET: PackageVersion
public async Task<IActionResult> Index(PackageVersionIndexViewModel model)
{
var applicationDbContext = _context.PackageVersions.Include(p => p.Package).Where(
var applicationDbContext = _context.PackageVersions.Include(p => p.Package)
.Include(p => p.Package.Owner)
.Include(p => p.Package.Versions)
.Where(
p => (model.Prerelease || !p.IsPrerelease)
&& ((model.PackageId == null) || p.PackageId.StartsWith(model.PackageId)));
model.Versions = await applicationDbContext.ToArrayAsync();
@ -47,60 +50,5 @@ namespace isnd
return View("Index", model);
}
// GET: PackageVersion/Details/5
public async Task<IActionResult> Details(string pkgid, string version)
{
if (pkgid == null || version == null)
{
return NotFound();
}
var packageVersion = await _context.PackageVersions
.Include(p => p.Package)
.FirstOrDefaultAsync(m => m.PackageId == pkgid && m.FullString == version);
if (packageVersion == null)
{
return NotFound();
}
return View(packageVersion);
}
[Authorize]
public async Task<IActionResult> Delete(string pkgid, string version, string pkgtype)
{
if (pkgid == null || version == null)
{
return NotFound();
}
var packageVersion = await _context.PackageVersions.Include(p => p.Package)
.FirstOrDefaultAsync(m => m.PackageId == pkgid
&& m.FullString == version && m.Type == pkgtype);
if (packageVersion == null) return NotFound();
if (!User.IsOwner(packageVersion)) return Unauthorized();
var pkg = await _pm.GetPackageAsync(pkgid, version, pkgtype);
return View(pkg);
}
// POST: PackageVersion/Delete/5
[HttpPost, ActionName("Delete")]
[ValidateAntiForgeryToken]
public async Task<IActionResult> DeleteConfirmed(string PackageId, string FullString,
string Type)
{
PackageVersion packageVersion = await _context.PackageVersions
.FirstOrDefaultAsync(m => m.PackageId == PackageId
&& m.FullString == FullString && m.Type == Type);
if (packageVersion == null) return NotFound();
if (!User.IsOwner(packageVersion)) return Unauthorized();
await _pm.DeletePackageAsync(PackageId, FullString, Type);
return RedirectToAction(nameof(Index));
}
}
}

View File

@ -1,30 +1,39 @@
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.abst;
using isnd.Interfaces;
using System.Linq;
using isnd.Entities;
namespace isnd.Controllers
{
public partial class PackagesController : Controller
/// <summary>
/// Api Controller
/// </summary>
public class ApiController : Controller
{
[HttpGet(_pkgRootPrefix + ApiConfig.Base)]
private readonly IPackageManager packageManager;
private readonly Resource[] resources;
/// <summary>
/// Api Controller Constructor
/// </summary>
/// <param name="pm"></param>
public ApiController(IPackageManager pm)
{
packageManager = pm;
resources = packageManager.GetResources().ToArray();
}
/// <summary>
/// API index
/// </summary>
/// <returns></returns>
[HttpGet("~" + Constants.ApiVersionPrefix + ApiConfig.Index)]
public IActionResult ApiIndex()
{
return Ok(new ApiIndexViewModel{ Version = PackageManager.BASE_API_LEVEL, Resources = resources });
return Ok(new ApiIndexViewModel(packageManager.CatalogBaseUrl + ApiConfig.Index){ Version = PackageManager.BASE_API_LEVEL, Resources = resources });
}
}

View File

@ -1,61 +0,0 @@
using System.Linq;
using System.Threading.Tasks;
using isnd.Data.Catalog;
using isnd.Helpers;
using isnd.Services;
using isnd.Entities;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
namespace isnd.Controllers
{
public partial class PackagesController
{
// https://docs.microsoft.com/en-us/nuget/api/catalog-resource#versioning
[HttpGet(_pkgRootPrefix + ApiConfig.Catalog)]
public IActionResult CatalogIndex()
{
return Ok(PackageManager.CurrentCatalogIndex);
}
[HttpGet(_pkgRootPrefix + ApiConfig.CatalogPage + "-{id}")]
public IActionResult Index(string id)
{
// https://docs.microsoft.com/en-us/nuget/api/catalog-resource#versioning
return Ok(PackageManager.CurrentCatalogPages[int.Parse(id)]);
}
[HttpGet(_pkgRootPrefix + ApiConfig.CatalogLeaf + "/{id}/{*lower}")]
public async Task<IActionResult> CatalogLeafAsync(string id, string lower)
{
string pkgType = ParamHelpers.Optional(ref lower);
var pkgVersion = await dbContext.PackageVersions
.Include(v => v.LatestCommit)
.SingleOrDefaultAsync(
v => v.PackageId == id &&
v.FullString == lower &&
v.Type == pkgType
);
if (pkgVersion == null) return NotFound();
var pub = await dbContext.Commits
.Include(c => c.Versions)
.OrderBy(c => c.CommitTimeStamp)
.SingleOrDefaultAsync
(
c => c.Action == PackageAction.PublishPackage
&& c.Versions.Contains(pkgVersion)
);
return Ok(new CatalogLeaf
{
CommitId = id,
Id = pkgVersion.PackageId,
CommitTimeStamp = pkgVersion.LatestCommit.CommitTimeStamp
});
}
}
}

View File

@ -1,21 +0,0 @@
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using isnd.Helpers;
using isnd.Entities;
using System.ComponentModel.DataAnnotations;
using isnd.Attributes;
namespace isnd.Controllers
{
public partial class PackagesController
{
[HttpDelete(_pkgRootPrefix + ApiConfig.Delete + "/{id}/{*lower}")]
public async Task<IActionResult> Delete(
[FromRoute][SafeName][Required] string id,
[FromRoute][SafeName][Required] string lower)
{
string pkgtype = ParamHelpers.Optional(ref lower);
var report = await packageManager.DeletePackageAsync(id, lower, pkgtype);
return Ok(report);
}
}
}

View File

@ -1,51 +0,0 @@
using System.ComponentModel.DataAnnotations;
using System.IO;
using Microsoft.AspNetCore.Mvc;
using isnd.Attributes;
using isnd.Entities;
namespace isnd.Controllers
{
public partial class PackagesController
{
// Web get nupkg
[HttpGet(_pkgRootPrefix + ApiConfig.Get + "/{id}/{lower}/{idf}-{lowerf}.nupkg")]
public IActionResult GetPackage(
[FromRoute][SafeName][Required] string id,
[FromRoute][SafeName][Required] string lower,
[FromRoute] string idf, [FromRoute] string lowerf)
{
var pkgpath = Path.Combine(isndSettings.PackagesRootDir,
id, lower, $"{id}-{lower}.nupkg"
);
FileInfo pkgfi = new FileInfo(pkgpath);
if (!pkgfi.Exists)
{
return BadRequest("!pkgfi.Exists");
}
return File(pkgfi.OpenRead(), "application/zip; charset=binary");
}
// Web get spec
[HttpGet(_pkgRootPrefix + ApiConfig.Get + "/{id}/{lower}/{idf}-{lowerf}.nuspec")]
public IActionResult GetNuspec(
[FromRoute][SafeName][Required] string id,
[FromRoute][SafeName][Required] string lower,
[FromRoute][SafeName][Required] string idf,
[FromRoute][SafeName][Required] string lowerf)
{
var pkgpath = Path.Combine(isndSettings.PackagesRootDir,
id, lower, $"{id}.nuspec");
FileInfo pkgfi = new FileInfo(pkgpath);
if (!pkgfi.Exists)
{
return BadRequest("!pkgfi.Exists");
}
return File(pkgfi.OpenRead(), "text/xml; charset=utf-8");
}
}
}

View File

@ -0,0 +1,18 @@
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using isnd.Entities;
using isn.abst;
namespace isnd.Controllers
{
public partial class PackagesController
{
[HttpGet("~" + Constants.ApiVersionPrefix + ApiConfig.PackageDetailUriTemplate)]
public async Task<IActionResult> Details(string id, string version)
{
var result = await packageManager.GetPackageDetailsAsync(id, version);
if (result==null) return NotFound();
return Ok(result);
}
}
}

View File

@ -1,14 +1,15 @@
using isnd.Services;
using isnd.Entities;
using Microsoft.AspNetCore.Mvc;
using isn.abst;
namespace isnd.Controllers
{
public partial class PackagesController
public partial class PackagesController
{
// GET /autocomplete?id=isn.protocol&prerelease=true
[HttpGet(_pkgRootPrefix + ApiConfig.AutoComplete)]
[HttpGet("~" + Constants.ApiVersionPrefix + ApiConfig.AutoComplete)]
public IActionResult AutoComplete(
string id,
string semVerLevel,
@ -17,26 +18,17 @@ namespace isnd.Controllers
int skip = 0,
int take = 25)
{
CheckParams(take, semVerLevel);
if (ModelState.ErrorCount > 0) return BadRequest(ModelState);
return Ok(packageManager.AutoComplete(id,skip,take,prerelease,packageType));
}
protected void CheckParams(int maxTake)
{
if (maxTake > maxTake)
if (take > maxTake)
{
ModelState.AddModelError("take", "Maximum exceeded");
}
}
protected void CheckParams(int take, string semVerLevel)
{
CheckParams(take);
if (semVerLevel != PackageManager.BASE_API_LEVEL)
{
ModelState.AddModelError("semVerLevel", PackageManager.BASE_API_LEVEL + " expected");
}
if (ModelState.ErrorCount > 0) return BadRequest(ModelState);
return Ok(packageManager.AutoComplete(id,skip,take,prerelease,packageType));
}
}
}

View File

@ -0,0 +1,43 @@
using System.Threading.Tasks;
using isnd.Entities;
using Microsoft.AspNetCore.Mvc;
using isn.abst;
namespace isnd.Controllers
{
public partial class PackagesController
{
// https://docs.microsoft.com/en-us/nuget/api/catalog-resource#versioning
[HttpGet("~" + Constants.ApiVersionPrefix + ApiConfig.Catalog)]
public async Task<IActionResult> CatalogIndex()
{
return Ok(await packageManager.GetCatalogIndexAsync());
}
[HttpGet("~" + Constants.ApiVersionPrefix + ApiConfig.Registration + "/{id}/{version}.json")]
public async Task<IActionResult> CatalogRegistration(string id, string version)
{
if ("index" == version)
{
var query = new Data.Catalog.PackageRegistrationQuery
{
Query = id,
Prerelease = true
};
var index = await packageManager.GetPackageRegistrationIndexAsync(query);
if (index == null) return NotFound();
return Ok(index);
}
// return Package details
var leaf = await packageManager.GetPackageDetailsAsync(id, version, null);
if (null == leaf) return NotFound(new { id, version });
return Ok(leaf);
}
}
}

View File

@ -0,0 +1,25 @@
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using isnd.Helpers;
using isnd.Entities;
using System.ComponentModel.DataAnnotations;
using isnd.Attributes;
using System.Security.Claims;
using isn.abst;
namespace isnd.Controllers
{
public partial class PackagesController
{
[HttpDelete("~" + Constants.ApiVersionPrefix + ApiConfig.Package + "/{id}/{lower?}/{type?}")]
public async Task<IActionResult> ApiDelete(
[FromRoute][SafeName][Required] string id,
[FromRoute][SafeName][Required] string lower,
[FromRoute] string type)
{
var uid = User.FindFirstValue(ClaimTypes.NameIdentifier);
var report = await packageManager.UserAskForPackageDeletionAsync(uid, id, lower, type);
return Ok(report);
}
}
}

View File

@ -0,0 +1,55 @@
using System.ComponentModel.DataAnnotations;
using System.IO;
using Microsoft.AspNetCore.Mvc;
using isnd.Attributes;
using isnd.Entities;
using isn.abst;
namespace isnd.Controllers
{
public partial class PackagesController
{
[HttpGet("~" + Constants.ApiVersionPrefix + ApiConfig.ContentBase + "/{id}/{version}/{idf}.{versionFromName}."
+ Constants.PacketFileExtension)]
public IActionResult GetPackage(
[FromRoute][SafeName][Required] string id,
[FromRoute][SafeName][Required] string version,
[FromRoute] string idf, [FromRoute] string versionFromName)
{
var pkgPath = Path.Combine(isndSettings.PackagesRootDir,
id, version, $"{id}-{version}." + Constants.PacketFileExtension
);
FileInfo pkgFileInfo = new FileInfo(pkgPath);
if (!pkgFileInfo.Exists)
{
return BadRequest("Package does´nt exist in the file system.");
}
return File(pkgFileInfo.OpenRead(), "application/zip; charset=binary");
}
// Web get spec
[HttpGet("~" + Constants.ApiVersionPrefix + ApiConfig.ContentBase + "/{id}/{lower}/{idf}-{lowerFromName}."
+ Constants.SpecFileExtension)]
public IActionResult GetNuspec(
[FromRoute][SafeName][Required] string id,
[FromRoute][SafeName][Required] string lower,
[FromRoute][SafeName][Required] string idf,
[FromRoute][SafeName][Required] string lowerFromName)
{
var pkgPath = Path.Combine(isndSettings.PackagesRootDir,
id, lower, $"{id}." + Constants.SpecFileExtension);
FileInfo pkgFileInfo = new FileInfo(pkgPath);
if (!pkgFileInfo.Exists)
{
return BadRequest("Package info does´nt exist in the file system.");
}
return File(pkgFileInfo.OpenRead(), "text/xml; charset=utf-8");
}
}
}

View File

@ -1,28 +1,37 @@
using Microsoft.AspNetCore.Mvc;
using NuGet.Versioning;
using isnd.Entities;
using isn.abst;
namespace isnd.Controllers
{
public partial class PackagesController
public partial class PackagesController
{
[HttpGet(_pkgRootPrefix + ApiConfig.Get + "/{id}/{lower}/index.json")]
[HttpGet("~" + Constants.ApiVersionPrefix + ApiConfig.ContentBase + "/{id}/{version}.json")]
public IActionResult GetVersions(
string id,
string lower,
string version,
bool prerelease = false,
string packageType = null,
int skip = 0,
int take = 25)
int take = 50)
{
NuGetVersion parsedVersion=null;
if (take > maxTake)
{
ModelState.AddModelError("take", "Maximum exceeded");
}
// NugetVersion
if (!NuGetVersion.TryParse(lower, out NuGetVersion parsedVersion))
if ("index"==version)
{
ModelState.AddModelError("lower", "invalid version string");
version=null;
}
if (version!=null)
{
if (!NuGetVersion.TryParse(version, out parsedVersion))
{
ModelState.AddModelError("version", "invalid version string");
}
}
if (!ModelState.IsValid)
{

View File

@ -0,0 +1,54 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.DataProtection;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging;
using isnd.Entities;
using Microsoft.AspNetCore.Http;
using isn.abst;
namespace isnd.Controllers
{
public partial class PackagesController
{
// TODO [Authorize(Policy = IsndConstants.RequireValidApiKey)]
[HttpPut("~" + Constants.ApiVersionPrefix + ApiConfig.Package)]
public async Task<IActionResult> Put()
{
try
{
var clientVersionId = Request.Headers["X-NuGet-Client-Version"];
string apiKey = Request.Headers["X-NuGet-ApiKey"][0];
ViewData["versionId"] = typeof(PackagesController).Assembly.FullName;
var files = new List<string>();
ViewData["files"] = files;
var clearKey = protector.Unprotect(apiKey);
var dbApiKey = dbContext.ApiKeys.SingleOrDefault(k => k.Id == clearKey);
if (dbApiKey == null)
{
logger.LogError("403 : no api-key");
return Unauthorized();
}
foreach (IFormFile file in Request.Form.Files)
{
var version = await packageManager.PutPackageAsync(file.OpenReadStream(), dbApiKey.UserId);
logger.LogInformation($"new package : {version.PackageId} {version.NugetLink}");
}
return Ok();
}
catch (Exception ex)
{
var message = $"PUT exception : {ex.Message} ({ex.GetType().Name})";
logger.LogError(message);
logger.LogError("Stack Trace : " + ex.StackTrace);
return new ObjectResult(new { ViewData, message })
{ StatusCode = 500 };
}
}
}
}

View File

@ -0,0 +1,37 @@
using Microsoft.AspNetCore.Mvc;
using isnd.Entities;
using isn.abst;
using System.Threading.Tasks;
using isnd.Data.Catalog;
namespace isnd.Controllers
{
// TODO /search GET {@id}?q={QUERY}&skip={SKIP}&take={TAKE}&prerelease={PRERELEASE}&semVerLevel={SEMVERLEVEL}&packageType={PACKAGETYPE}
public partial class PackagesController
{
// Web get the paquet
[HttpGet("~" + Constants.ApiVersionPrefix + ApiConfig.Search)]
[HttpHead("~" + Constants.ApiVersionPrefix + ApiConfig.Search)]
[HttpHead("~" + ApiConfig.V2Find)]
public async Task<IActionResult> Search(
string q=null,
int skip=0,
int take=25,
bool prerelease=false,
string semVerLevel = "2.0.0",
string packageType = "Dependency")
{
PackageRegistrationQuery query = new PackageRegistrationQuery
{
Prerelease= prerelease,
Query = q,
Skip = skip,
Take = take
};
var result = await packageManager.SearchPackageAsync(query);
return Ok(result);
}
}
}

View File

@ -0,0 +1,114 @@
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using isn.abst;
using isnd.Data;
using isnd.Data.Catalog;
using isnd.Entities;
using isnd.Helpers;
using isnd.ViewModels;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Components.Web.Virtualization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
namespace isnd.Controllers
{
public partial class PackagesController
{
// Web search
public async Task<IActionResult> Index(PackageRegistrationQuery model)
{
var pkgs = await packageManager.SearchPackageAsync(model);
var result = new RegistrationPageIndexQueryAndResult
{
Source = packageManager.CatalogBaseUrl+ApiConfig.Index,
Query = model
};
List<Catalog> registrations = new List<Catalog>();
if (pkgs.data!=null)
if (pkgs.data.Length>0)
{
var grouped = pkgs.data.GroupBy(x => x.PackageId);
if (grouped!=null) if (grouped.Count()>0)
{
foreach (var pk in grouped.ToArray())
{
registrations.Add(new Catalog(apiBase, pk.Key, pkgs.GetResults().Single(p=>p.Id == pk.Key).Versions));
}
}
}
return View(new RegistrationPageIndexQueryAndResult
{
Source = packageManager.CatalogBaseUrl+ApiConfig.Index,
Query = model,
Result = registrations.ToArray()
});
}
public async Task<IActionResult> Details(PackageDetailViewModel model)
{
if (model.pkgid == null)
{
return NotFound();
}
var packageVersion = dbContext.PackageVersions
.Include(p=>p.LatestCommit)
.Include(p => p.Package)
.Where(m => m.PackageId == model.pkgid)
.OrderByDescending(p => p)
;
if (packageVersion.Count() == 0)
{
return NotFound();
}
bool results = await packageVersion.AnyAsync();
model.latest = await packageVersion.FirstAsync();
model.totalHits = packageVersion.Count();
model.data = packageVersion.Take(MAX_PKG_VERSION_LIST).ToArray();
return View("Details", model);
}
const int MAX_PKG_VERSION_LIST = 50;
[Authorize, HttpGet]
public async Task<IActionResult> Delete(string pkgid, string version, string pkgtype)
{
if (pkgid == null || version == null)
{
return NotFound();
}
var packageVersion = await dbContext.PackageVersions.Include(p => p.Package)
.FirstOrDefaultAsync(m => m.PackageId == pkgid && m.FullString == version
&& (pkgtype!=null && m.Type == pkgtype || m.Type != "Delete" ));
if (packageVersion == null) return NotFound();
if (!User.IsOwner(packageVersion)) return Challenge();
var pkg = await packageManager.GetPackageDetailsAsync(pkgid, version, pkgtype);
return View(pkg);
}
// POST: PackageVersion/Delete/5
[HttpPost]
[ValidateAntiForgeryToken][Authorize]
public async Task<IActionResult> DeleteConfirmed(string pkgid, string version,
string type)
{
var packageVersion = await dbContext.PackageVersions
.Include(pv => pv.Package)
.Where(m => m.PackageId == pkgid
&& m.FullString == version && (type==null || m.Type == type))
.ToArrayAsync();
if (packageVersion.Length==0) return NotFound();
if (!User.IsOwner(packageVersion.First())) return Unauthorized();
await packageManager.DeletePackageAsync(pkgid, version, type);
return RedirectToAction(nameof(Index));
}
}
}

View File

@ -1,21 +1,14 @@
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.abst;
namespace isnd.Controllers
{
@ -24,20 +17,18 @@ namespace isnd.Controllers
{
const int maxTake = 100;
private readonly Resource[] resources;
private readonly string apiBase;
private readonly ILogger<PackagesController> logger;
private readonly IDataProtector protector;
private readonly IsndSettings isndSettings;
readonly ApplicationDbContext dbContext;
private readonly IPackageManager packageManager;
private readonly IUnleash unleashĈlient;
const string _pkgRootPrefix = "~/";
public PackagesController(
ILoggerFactory loggerFactory,
IDataProtectionProvider provider,
IOptions<IsndSettings> isndOptions,
IUnleash unleashĈlient,
ApplicationDbContext dbContext,
IPackageManager pm)
{
@ -46,8 +37,10 @@ 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();
this.apiBase = isndSettings.ExternalUrl + Constants.ApiVersionPrefix;
}
}
}

View File

@ -1,217 +0,0 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.IO.Compression;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Microsoft.AspNetCore.DataProtection;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging;
using NuGet.Packaging.Core;
using NuGet.Versioning;
using isnd.Data;
using isnd.Helpers;
using isnd.Entities;
using Microsoft.AspNetCore.Http;
using isnd.Data.Catalog;
namespace isnd.Controllers
{
public partial class PackagesController
{
[HttpPut(_pkgRootPrefix + ApiConfig.Publish)]
public async Task<IActionResult> Put()
{
try
{
var clientVersionId = Request.Headers["X-NuGet-Client-Version"];
string apiKey = Request.Headers["X-NuGet-ApiKey"][0];
ViewData["versionId"] = typeof(PackagesController).Assembly.FullName;
var files = new List<string>();
ViewData["files"] = files;
var clearkey = protector.Unprotect(apiKey);
var apikey = dbContext.ApiKeys.SingleOrDefault(k => k.Id == clearkey);
if (apikey == null)
{
logger.LogError("403 : no api-key");
return Unauthorized();
}
Commit commit = new Commit
{
Action = PackageAction.PublishPackage,
TimeStamp = DateTime.Now
};
dbContext.Commits.Add(commit);
foreach (IFormFile file in Request.Form.Files)
{
string initpath = Path.Combine(Environment.GetEnvironmentVariable("TEMP") ??
Environment.GetEnvironmentVariable("TMP") ?? "/tmp",
$"isn-{Guid.NewGuid()}.nupkg");
using (FileStream fw = new FileStream(initpath, FileMode.Create))
{
file.CopyTo(fw);
}
using (FileStream fw = new FileStream(initpath, FileMode.Open))
{
var archive = new ZipArchive(fw);
var nuspec = archive.Entries.FirstOrDefault(e => e.FullName.EndsWith(".nuspec"));
if (nuspec == null) return BadRequest(new { error = "no nuspec from archive" });
string pkgpath;
NuGetVersion version;
string pkgid;
string fullpath;
using (var specstr = nuspec.Open())
{
NuspecCoreReader reader = new NuspecCoreReader(specstr);
string pkgdesc = reader.GetDescription();
var types = reader.GetPackageTypes();
pkgid = reader.GetId();
version = reader.GetVersion();
string pkgidpath = Path.Combine(isndSettings.PackagesRootDir,
pkgid);
pkgpath = Path.Combine(pkgidpath, version.ToFullString());
string name = $"{pkgid}-{version}.nupkg";
fullpath = Path.Combine(pkgpath, name);
var destpkgiddir = new DirectoryInfo(pkgidpath);
Package package = dbContext.Packages.SingleOrDefault(p => p.Id == pkgid);
if (package != null)
{
if (package.OwnerId != apikey.UserId)
{
return new ForbidResult();
}
package.Description = pkgdesc;
}
else
{
package = new Package
{
Id = pkgid,
Description = pkgdesc,
OwnerId = apikey.UserId,
LatestVersion = commit
};
dbContext.Packages.Add(package);
}
if (!destpkgiddir.Exists) destpkgiddir.Create();
var source = new FileInfo(initpath);
var dest = new FileInfo(fullpath);
var destdir = new DirectoryInfo(dest.DirectoryName);
if (dest.Exists)
{
// La version existe sur le disque,
// mais si elle ne l'est pas en base de donnéés,
// on remplace la version sur disque.
var pkgv = dbContext.PackageVersions.Where(
v => v.PackageId == package.Id
);
if (pkgv !=null && pkgv.Count()==0)
{
dest.Delete();
}
else {
logger.LogWarning("400 : pkgversion:existant");
ModelState.AddModelError("pkgversion", "existant" );
return BadRequest(ModelState);
}
}
{
if (!destdir.Exists) destdir.Create();
source.MoveTo(fullpath);
files.Add(name);
string fullstringversion = version.ToFullString();
var pkgvers = dbContext.PackageVersions.Where
(v => v.PackageId == package.Id && v.FullString == fullstringversion);
if (pkgvers.Count() > 0)
{
foreach (var v in pkgvers.ToArray())
dbContext.PackageVersions.Remove(v);
}
// FIXME default type or null
if (types==null || types.Count==0)
dbContext.PackageVersions.Add
(new PackageVersion{
Package = package,
Major = version.Major,
Minor = version.Minor,
Patch = version.Patch,
IsPrerelease = version.IsPrerelease,
FullString = version.ToFullString(),
Type = null,
LatestCommit = commit
});
else
foreach (var type in types)
{
var pkgver = new PackageVersion
{
Package = package,
Major = version.Major,
Minor = version.Minor,
Patch = version.Patch,
IsPrerelease = version.IsPrerelease,
FullString = version.ToFullString(),
Type = type.Name,
LatestCommit = commit
};
dbContext.PackageVersions.Add(pkgver);
}
await dbContext.SaveChangesAsync();
packageManager.ÛpdateCatalogFor(commit);
logger.LogInformation($"new package : {nuspec.Name}");
}
}
using (var shacrypto = System.Security.Cryptography.SHA512.Create())
{
using (var stream = System.IO.File.OpenRead(fullpath))
{
var hash = shacrypto.ComputeHash(stream);
var shafullname = fullpath + ".sha512";
var hashtext = Convert.ToBase64String(hash);
var hashtextbytes = Encoding.ASCII.GetBytes(hashtext);
using (var shafile = System.IO.File.OpenWrite(shafullname))
{
shafile.Write(hashtextbytes, 0, hashtextbytes.Length);
}
}
}
string nuspecfullpath = Path.Combine(pkgpath, pkgid + ".nuspec");
FileInfo nfpi = new FileInfo(nuspecfullpath);
if (nfpi.Exists)
nfpi.Delete();
nuspec.ExtractToFile(nuspecfullpath);
}
}
return Ok(ViewData);
}
catch (Exception ex)
{
logger.LogError(ex.Message);
logger.LogError("Stack Trace: " + ex.StackTrace);
return new ObjectResult(new { ViewData, ex.Message })
{ StatusCode = 500 };
}
}
}
}

View File

@ -1,27 +0,0 @@
using System;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using isnd.Entities;
namespace isnd.Controllers
{
public partial class PackagesController
{
// GET {@id}?q={QUERY}&skip={SKIP}&take={TAKE}&prerelease={PRERELEASE}&semVerLevel={SEMVERLEVEL}&packageType={PACKAGETYPE}
[HttpGet(_pkgRootPrefix + ApiConfig.Search)]
public async Task<IActionResult> Search(
string q,
int skip = 0,
int take = 25,
bool prerelease = false,
string semVerLevel = null,
string packageType = null
)
{
throw new NotImplementedException();
}
}
}

View File

@ -1,55 +0,0 @@
using System;
using System.Linq;
using System.Threading.Tasks;
using isnd.ViewModels;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
namespace isnd.Controllers
{
public partial class PackagesController
{
// Web search
public async Task<IActionResult> Index(PackageIndexViewModel model)
{
var applicationDbContext = dbContext.Packages.Include(p => p.Versions).Where(
p => ( model.Prerelease || p.Versions.Any(v => !v.IsPrerelease))
&& ((model.Query == null) || p.Id.StartsWith(model.Query)));
model.Data = await applicationDbContext.ToArrayAsync();
return View(model);
}
public async Task<IActionResult> Details(string pkgid)
{
if (pkgid == null)
{
return NotFound();
}
var packageVersion = dbContext.PackageVersions
.Include(p => p.Package)
.Where(m => m.PackageId == pkgid)
.OrderByDescending(p => p)
;
if (packageVersion == null)
{
return NotFound();
}
bool results = await packageVersion.AnyAsync();
var latest = await packageVersion.FirstAsync();
return View("Details", new PackageDetailViewModel
{
latest = latest,
pkgid = pkgid,
totalHits = packageVersion.Count(),
data = packageVersion.Take(MAX_PKG_VERSION_LIST).ToArray()
});
}
const int MAX_PKG_VERSION_LIST = 50;
}
}

View File

@ -18,7 +18,7 @@ namespace isnd.Data.ApiKeys
public int ValidityPeriodInDays{ get; set; }
public DateTime CreationDate { get; set; }
public DateTimeOffset CreationDate { get; set; }
public bool IsValid => CreationDate.AddDays(ValidityPeriodInDays) > DateTime.Now;
}

View File

@ -9,6 +9,8 @@ namespace isnd.Data.ApiKeys
[Display(Name = "Key Name")]
public string Name { get; set; }
public string UserId { get; set; }
public int ValidityPeriodInDays { get; set; }
}
}

View File

@ -1,24 +1,63 @@
using System;
using System.Collections.Generic;
using System.Text;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Identity.EntityFrameworkCore;
using Microsoft.AspNetCore.Identity.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore;
using isnd.Data;
using isnd.Data.ApiKeys;
using isnd.Data.Catalog;
using isnd.Data.Historic;
using isnd.Data.Packages;
using Microsoft.VisualStudio.Web.CodeGenerators.Mvc.Templates.BlazorIdentity.Pages;
using Microsoft.DotNet.Scaffolding.Shared.ProjectModel;
namespace isnd.Data
{
/// <summary>
/// Application Db Context
/// </summary>
public class ApplicationDbContext : IdentityDbContext<ApplicationUser>
{
/// <summary>
/// db context ctor
/// </summary>
/// <param name="options"></param>
public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options)
: base(options) { }
protected override void OnModelCreating(ModelBuilder builder)
{
base.OnModelCreating(builder);
_ = builder.Entity<PackageVersion>()
.HasKey( v => new { v.PackageId, v.FullString } );
_ = builder.Entity<PackageVersion>()
.HasOne(v => v.Package).WithMany(p => p.Versions).HasForeignKey(x => x.PackageId);
_ = builder.Entity<PackageDependencyGroup>().HasOne(g=>g.PackageVersion)
.WithMany(v => v.DependencyGroups).HasForeignKey(x => new { x.PackageId, x.PackageVersionFullString } );
}
/// <summary>
/// User API keys
/// </summary>
/// <value></value>
public DbSet<ApiKey> ApiKeys { get; set; }
/// <summary>
/// Packages
/// </summary>
/// <value></value>
public DbSet<Package> Packages { get; set; }
/// <summary>
/// Package Versions
/// </summary>
/// <value></value>
public DbSet<PackageVersion> PackageVersions { get; set; }
/// <summary>
/// Commits
/// </summary>
/// <value></value>
public DbSet<Commit> Commits { get; set; }
public DbSet<Dependency> Dependencies { get; set; }
public DbSet<PackageDependencyGroup> PackageDependencyGroups { get; set; }
}
}

View File

@ -5,6 +5,8 @@ namespace isnd.Data
// Add profile data for application users by adding properties to the ApplicationUser class
public class ApplicationUser : IdentityUser
{
[PersonalData]
public string FullName { get; set; }
}
}

View File

@ -0,0 +1,11 @@
using NuGet.Versioning;
namespace isnd.Data.Catalog
{
public class AlternatePackage
{
public string id { get ; set; }
public VersionRange range { get ; set; }
}
}

View File

@ -1,24 +0,0 @@
using System;
using System.Collections.Generic;
using Newtonsoft.Json;
namespace isnd.Data.Catalog
{
public class CatalogIndex : IObject
{
[JsonProperty("@id")]
public string Id { get; set ; }
[JsonProperty("items")]
public List<PageRef> Items { get; set; }
[JsonProperty("count")]
public int Count { get => Items?.Count ?? 0; }
public string CommitId { get; set; }
public DateTime CommitTimeStamp { get; set; }
}
}

View File

@ -1,29 +0,0 @@
using System;
using System.Collections.Generic;
using Newtonsoft.Json;
namespace isnd.Data.Catalog
{
public class CatalogLeaf : IObject
{
[JsonProperty("@type")]
public List<string> RefType { get; set; }
[JsonProperty("commitId")]
public string CommitId { get; set; }
[JsonProperty("commitTimeStamp")]
public DateTime CommitTimeStamp { get; set; }
[JsonProperty("published")]
public DateTime Published { get; set; }
[JsonProperty("id")]
public string Id { get; set; }
[JsonProperty("version")]
public string Version { get; set; }
}
}

View File

@ -0,0 +1,14 @@
namespace isnd.Data.Catalog
{
public class Deprecation
{
/*
Legacy The package is no longer maintained
CriticalBugs The package has bugs which make it unsuitable for usage
Other The package is deprecated due to a reason not on this list
*/
public string[] reasons { get; set; } // array of strings yes The reasons why the package was deprecated
public string message { get; set; } // The additional details about this deprecation
public AlternatePackage alternatePackage { get; set; } // object no The alternate package that should be used instead
}
}

View File

@ -1,7 +0,0 @@
namespace isnd.Data.Catalog
{
public class PackageDetail : CatalogLeaf
{
}
}

View File

@ -0,0 +1,161 @@
using System.ComponentModel;
using System.Net.Sockets;
using System.ComponentModel.DataAnnotations;
using Newtonsoft.Json;
using isnd.Data.Packages;
using NuGet.Versioning;
using System.Collections.Generic;
using System;
using isnd.Interfaces;
using NuGet.Protocol.Core.Types;
using NuGet.Protocol;
using NuGet.Packaging;
using NuGet.Packaging.Core;
using System.Threading.Tasks;
using isnd.Entities;
namespace isnd.Data.Catalog
{
public class PackageDetails : Permalink, IObject
{
/// <summary>
/// Creates a catalog entry
/// </summary>
/// <param name="pkg">package id</param>
/// <param name="apiBase">api Base</param>
/// <returns></returns>
public PackageDetails(PackageVersion pkg, string apiBase): base( apiBase + ApiConfig.Package + "/" + pkg.PackageId + "/" + pkg.FullString)
{
Title = PackageId = pkg.Package.Id;
Version = pkg.FullString;
Authors = $"{pkg.Package.Owner.FullName} <${pkg.Package.Owner.Email}>";
packageContent = apiBase + pkg.NugetLink;
CommitId = pkg.CommitId;
Published = CommitTimeStamp = pkg.LatestCommit.CommitTimeStamp;
Listed = !pkg.IsDeleted && pkg.Package.Public;
if (pkg.DependencyGroups!=null)
{
DependencySets = pkg.DependencyGroups;
}
Description = pkg.Description;
Owners = pkg.Package.Owner.Email;
// TODO Licence Project Urls, Summary, Title, Owners, etc ...
}
[JsonProperty("@type")]
public string[] RefType { get; protected set; } = new string[] { "PackageDetails" };
[JsonProperty("commitId")]
public string CommitId { get; set; }
[JsonProperty("commitTimeStamp")]
public DateTimeOffset CommitTimeStamp { get; set; }
/// <summary>
/// Authors
/// </summary>
/// <value>string or array of strings</value>
[JsonProperty("authors")]
public string Authors { get; set; }
/// <summary>
/// The deprecation associated with the package
/// </summary>
/// <value></value>
public Deprecation deprecation { get; set; }
[JsonProperty("description")]
public string Description { get; set; }
[JsonProperty("iconUrl")]
public Uri IconUrl { get; set; }
public string language { get; set; }
[JsonProperty("licenseUrl")]
public Uri LicenseUrl { get; set; }
public string licenseExpression { get; set; }
public string minClientVersion { get; set; }
[JsonProperty("projectUrl")]
public Uri ProjectUrl { get; set; }
[JsonProperty("requireLicenseAcceptance")]
public bool RequireLicenseAcceptance { get; set; }
[JsonProperty("summary")]
public string Summary { get; set; }
/// <summary>
/// The tags
/// </summary>
/// <value></value>
[JsonProperty("tags")]
public string Tags { get; set; }
[JsonProperty("title")]
public string Title { get; set; }
/// <summary>
/// The security vulnerabilities of the package
/// </summary>
/// <value></value>
public IEnumerable<PackageVulnerabilityMetadata> Vulnerabilities { get; }
public string packageContent { get; set; }
/// <summary>
/// A string containing a ISO 8601 timestamp of when the package was published
/// </summary>
/// <value></value>
[JsonProperty("published")]
public DateTimeOffset? Published { get; set; }
/// <summary>
/// The full version string after normalization
/// </summary>
/// <value></value>
[Required,JsonRequired]
[JsonProperty("version")]
public string Version { get; set; }
[Required,JsonRequired]
[JsonProperty("id")]
public string PackageId { get; set; }
public long? DownloadCount { get; set; }
public Uri ReadmeUrl { get; set; }
public Uri ReportAbuseUrl { get; set; }
public string Owners { get; set; }
public bool Listed { get; set; }
public LicenseMetadata LicenseMetadata { get; set; }
[JsonProperty("dependencyGroups")]
public IEnumerable<PackageDependencyGroup> DependencySets {get; set;}
public Task<PackageDeprecationMetadata> GetDeprecationMetadataAsync()
{
throw new NotImplementedException();
}
public Task<IEnumerable<VersionInfo>> GetVersionsAsync()
{
throw new NotImplementedException();
}
}
}

View File

@ -0,0 +1,33 @@
using isnd.Data.Packages;
using isnd.Entities;
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.Linq;
namespace isnd.Data.Catalog
{
public class Catalog : Permalink
{
public Catalog(string url) : base(url)
{
Items = new List<RegistrationPage>();
}
public Catalog(string apiBase, string pkgId, IEnumerable<PackageVersion> versions) : base($"{apiBase}{ApiConfig.Registration}/{pkgId}/index.json")
{
Items = new List<RegistrationPage>
{
new RegistrationPage(pkgId, apiBase, versions)
};
}
[JsonProperty("count")]
public int Count { get => Items.Count; }
[JsonProperty("items")]
public List<RegistrationPage> Items { get; set; }
}
}

View File

@ -0,0 +1,25 @@
using System.Collections.Generic;
using isnd.Data.Packages;
using Newtonsoft.Json;
namespace isnd.Data.Catalog
{
public class PackageRegistrationQuery
{
public PackageRegistrationQuery()
{
}
[JsonProperty("query")]
public string Query { get; set; }
[JsonProperty("prerelease")]
public bool Prerelease { get; set; } = true;
[JsonProperty("skip")]
public int Skip { get; set; } = 0;
[JsonProperty("take")]
public int Take { get; set; } = 25;
}
}

View File

@ -1,20 +0,0 @@
using System;
using System.Collections.Generic;
using Newtonsoft.Json;
namespace isnd.Data.Catalog
{
public class Page : IObject
{
[JsonProperty("@id")]
public string Id { get; set; }
[JsonProperty("parent")]
public string Parent { get; set; }
[JsonProperty("items")]
public virtual List<PackageRef> Items { get; set; }
public string CommitId { get; set; }
public DateTime CommitTimeStamp { get; set; }
}
}

View File

@ -1,26 +0,0 @@
using System;
using Newtonsoft.Json;
namespace isnd.Data.Catalog
{
public class PageRef : IObject
{
/// <summary>
/// Page Url
/// </summary>
/// <value></value>
[JsonProperty("@id")]
public string Id { get; set; }
/// <summary>
/// Page entry count
/// </summary>
/// <value></value>
[JsonProperty("count")]
public int Count { get; set; }
public string CommitId { get; set; }
public DateTime CommitTimeStamp { get; set; }
}
}

View File

@ -0,0 +1,54 @@
using System.ComponentModel.DataAnnotations;
using isnd.Entities;
using Newtonsoft.Json;
using isn.abst;
namespace isnd.Data.Catalog
{
/// <summary>
/// Hosts a catalog entry,
/// the atomic content reference
/// </summary>
public class RegistrationLeaf
{
public RegistrationLeaf(string apiBase, string pkgId, string fullVersionString, PackageDetails entry)
{
this.registration = apiBase + ApiConfig.Registration + "/" + pkgId + "/" + fullVersionString + ".json";
Id = registration;
this.PackageContent = apiBase + ApiConfig.ContentBase + "/" + pkgId + "/" + fullVersionString
+ "/" + pkgId + "-" + fullVersionString + "." + Constants.PacketFileExtension;
Entry = entry;
}
/*
@id string yes
catalogEntry object yes
packageContent string yes
*/
/// <summary>
/// The URL to the registration leaf
/// </summary>
/// <value></value>
[JsonProperty("@id")]
[Key][Required]
[StringLength(1024)]
public string Id { get; set; }
/// <summary>
/// The catalog entry containing the package metadata
/// </summary>
/// <value></value>
[JsonProperty("catalogEntry")]
public PackageDetails Entry { get; set; }
/// <summary>
/// The URL to the package content (.nupkg)
/// </summary>
/// <value></value>
[JsonProperty("packageContent")]
public string PackageContent { get; set; }
public string registration { get; set; }
}
}

View File

@ -0,0 +1,100 @@
using System;
using System.Collections.Generic;
using System.Linq;
using isnd.Data.Packages;
using Newtonsoft.Json;
using NuGet.Versioning;
namespace isnd.Data.Catalog
{
public class RegistrationPage : Permalink
{
private readonly string pkgid;
private readonly List<RegistrationLeaf> items;
private readonly string apiBase;
public RegistrationPage (string pkgid, string apiBase) : base(apiBase + $"/registration/{pkgid}/index.json")
{
Parent = apiBase + $"/registration/{pkgid}/index.json";
this.items = new List<RegistrationLeaf>();
this.pkgid = pkgid;
this.apiBase = apiBase;
}
public RegistrationPage(string pkgid, string apiBase, IEnumerable<PackageVersion> versions) : this(pkgid, apiBase)
{
AddVersionRange(versions);
}
public string GetPackageId()
{
return pkgid;
}
/// <summary>
/// The array of registration leaves and their associate metadata
/// </summary>
/// <value></value>
[JsonProperty("items")]
public RegistrationLeaf[] Items { get => items.ToArray(); }
public void AddVersionRange(IEnumerable<PackageVersion> vitems)
{
if (vitems.Count() == 0) return;
NuGetVersion upper = null;
NuGetVersion lower = null;
PackageVersion latest = null;
if (Lower!=null) lower = new NuGetVersion(Lower);
if (Upper!=null) upper = new NuGetVersion(Upper);
// Assert.True(items.All(p=>p.Id == id));
long commitMax = 0;
foreach (var p in vitems)
{
if (p.LatestCommit==null) continue;
var pkg = p.ToPackage(apiBase);
if (items.Contains(pkg)) continue;
if (upper == null) upper = p.NugetVersion;
else if ( upper < p.NugetVersion) upper = p.NugetVersion;
if (lower == null) lower = p.NugetVersion;
else if (lower > p.NugetVersion) lower = p.NugetVersion;
if (p.CommitNId > commitMax)
{
latest = p;
}
items.Add(pkg);
}
Upper = upper?.ToFullString() ?? lower?.ToFullString();
Lower = lower?.ToFullString() ?? upper?.ToFullString();
}
/// <summary>
/// The highest SemVer 2.0.0 version in the page (inclusive)
/// </summary>
/// <value></value>
[JsonProperty("upper"), JsonRequired]
public string Upper { get; private set; }
/// <summary>
/// The lowest SemVer 2.0.0 version in the page (inclusive)
/// </summary>
/// <value></value>
[JsonProperty("lower"), JsonRequired]
public string Lower { get; private set; }
/// <summary>
/// The URL to the registration index
/// </summary>
/// <value></value>
[JsonProperty("parent")]
public string Parent { get; set; }
[JsonProperty("count")]
public int Count { get => items.Count; }
}
}

View File

@ -0,0 +1,9 @@
namespace isnd.Data.Catalog
{
public class Vulnerabilitie
{
public string advisoryUrl { get; set; } // string yes Location of security advisory for the package
public string severity { get; set; } // string yes Severity of advisory: "0" = Low, "1" = Moderate, "2" = High, "3" = Critical
}
}

View File

@ -1,6 +1,7 @@
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
using isnd.Data.Catalog;
using isnd.Data.Packages;
using isnd.Data.Packages.Catalog;
namespace isnd.Data.Historic
{

View File

@ -1,48 +0,0 @@
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
using isnd.Data.Catalog;
using Newtonsoft.Json;
namespace isnd.Data
{
public class PackageVersion
{
[Required]
[ForeignKey("Package")]
[StringLength(1024)]
public string PackageId { get; set; }
[Required]
public int Major { get; set; }
[Required]
public int Minor { get; set; }
[Required]
public int Patch { get; set; }
[StringLength(256)]
[Required][Key]
public string FullString { get; set; }
public bool IsPrerelease { get; set; }
[StringLength(256)]
public string Type { get; set; }
[JsonIgnore]
public virtual Package Package { get; set; }
[Required][JsonIgnore]
[ForeignKey("LatestCommit")]
public long CommitNId { get; set ; }
[NotMapped]
public string CommitId { get => CommitNId.ToString(); }
public virtual Commit LatestCommit{ get; set; }
public string NugetLink => $"/package/{PackageId}/{FullString}/{PackageId}-{FullString}.nupkg";
public string NuspecLink => $"/package/{PackageId}/{FullString}/{PackageId}-{FullString}.nuspec";
}
}

View File

@ -1,14 +1,18 @@
using System;
using Newtonsoft.Json;
namespace isnd.Data.Catalog
namespace isnd.Interfaces
{
public interface IObject
{
[JsonProperty("@type")]
public string Type { get => GetType().Name; }
[JsonProperty("commitId")]
string CommitId { get; }
[JsonProperty("commitTimeStamp")]
DateTime CommitTimeStamp { get; }
DateTimeOffset CommitTimeStamp { get; }
}
}

View File

@ -1,18 +1,18 @@
using System;
using System.ComponentModel.DataAnnotations.Schema;
using isnd.Interfaces;
using Newtonsoft.Json;
namespace isnd.Data.Catalog
namespace isnd.Data.Packages.Catalog
{
/// <summary>
/// An presence of package in a catalog,
/// for availability, or deletion,
///
/// </summary>
public class PackageRef : IObject
public class PackageDetails : IObject
{
[JsonProperty("@id")]
public string RefId { get; set; }
@ -46,6 +46,7 @@ namespace isnd.Data.Catalog
public virtual Commit LastCommit { get; set; }
[JsonProperty("commitTimeStamp")]
public DateTime CommitTimeStamp { get; set; }
public DateTimeOffset CommitTimeStamp { get; set; }
}
}

View File

@ -0,0 +1,12 @@
using System;
using isnd.Interfaces;
namespace isnd.Data.Packages.Catalog
{
public class PackageEntry : IObject
{
public string CommitId => throw new NotImplementedException();
public DateTimeOffset CommitTimeStamp => throw new NotImplementedException();
}
}

View File

@ -3,8 +3,9 @@ using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
using Newtonsoft.Json;
using isnd.Interfaces;
namespace isnd.Data.Catalog
namespace isnd.Data.Packages
{
public enum PackageAction
{
@ -20,7 +21,7 @@ namespace isnd.Data.Catalog
public long Id { get; set; }
[Required][JsonIgnore]
public DateTime TimeStamp{ get; set; }
public DateTimeOffset TimeStamp{ get; set; }
[Required]
public PackageAction Action { get; set; }
@ -29,7 +30,7 @@ namespace isnd.Data.Catalog
public string CommitId { get => Id.ToString(); }
[NotMapped]
public DateTime CommitTimeStamp { get => TimeStamp; }
public DateTimeOffset CommitTimeStamp { get => TimeStamp; }
[ForeignKey("CommitNId")]

View File

@ -0,0 +1,42 @@
using System.ComponentModel.DataAnnotations;
using Newtonsoft.Json;
using Microsoft.EntityFrameworkCore.Metadata.Internal;
using System.ComponentModel.DataAnnotations.Schema;
using isnd.Data.Packages;
namespace isnd.Data
{
public class Dependency
{
[JsonIgnore]
[Key][DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public string Id { get;set;}
/// <summary>
/// Dependency Package Identifier
/// </summary>
/// <value></value>
[JsonProperty("id")]
public string PackageId { get;set;}
/// <summary>
/// Dependency Group Id
/// </summary>
/// <value></value>
[Required]
[ForeignKey("Group")]
[JsonIgnore]
public string DependencyGroupId { set ; get ; }
[JsonIgnore]
public virtual PackageDependencyGroup Group{ get; set; }
// TODO Version Range
[JsonProperty("range")][StringLength(1024)]
public string Version { get; set; }
[JsonProperty("exclude")][StringLength(2048)]
public string Exclude { get; set; }
}
}

View File

@ -0,0 +1,19 @@
using System;
using System.Collections.Generic;
namespace isnd.Data.Packages
{
public interface IPackage
{
string Id { get; set; }
string OwnerId { get; set; }
string Description { get; set; }
bool Public { get; set; }
ApplicationUser Owner { get; set; }
List<PackageVersion> Versions { get; set; }
long CommitNId { get; set; }
string CommitId { get; }
Commit LatestCommit { get; set; }
DateTimeOffset CommitTimeStamp { get; set; }
}
}

View File

@ -2,14 +2,18 @@ using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
using isnd.Data.Catalog;
using System.Linq;
using isnd.Interfaces;
using Newtonsoft.Json;
using NuGet.Versioning;
namespace isnd.Data
namespace isnd.Data.Packages
{
public class Package : IObject
public class Package
{
[Key][Required]
[Key]
[Required]
[StringLength(1024)]
public string Id { get; set; }
@ -20,7 +24,7 @@ namespace isnd.Data
[StringLength(1024)]
public string Description { get; set; }
public bool Public { get ; set;}
public bool Public { get; set; }
[JsonIgnore]
virtual public ApplicationUser Owner { get; set; }
@ -30,19 +34,25 @@ namespace isnd.Data
public virtual List<PackageVersion> Versions { get; set; }
/// <summary>
/// Latest package version put, or post,
/// or { delete when no more active version }.
/// Latest version at put, posted,
/// or even deletion when no more active version.
/// </summary>
/// <value></value>
[Required][JsonIgnore]
public long CommitNId { get; set ; }
[Required]
[JsonIgnore]
[ForeignKey("LatestCommit")]
public long CommitNId { get; set; }
[NotMapped]
public string CommitId { get => CommitNId.ToString(); }
[ForeignKey("CommitNId")]
public virtual Commit LatestCommit { get; set; }
public virtual Commit LatestVersion{ get; set; }
public DateTime CommitTimeStamp { get; set; }
public PackageVersion GetLatestVersion()
{
var latest = Versions.Max(v => v.NugetVersion);
return Versions.FirstOrDefault(v=> v.NugetVersion == latest);
}
}
}

View File

@ -0,0 +1,44 @@
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
using System.Linq;
using isnd.Data.Catalog;
using Microsoft.DotNet.Scaffolding.Shared.ProjectModel;
using Microsoft.Identity.Client;
using Newtonsoft.Json;
using NuGet.Frameworks;
using NuGet.Packaging;
using NuGet.Packaging.Core;
using NuGet.ProjectModel;
using NuGet.Versioning;
namespace isnd.Data
{
public class PackageDependencyGroup
{
[JsonIgnore]
[Key, DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public string Id { get ; set;}
[Required]
[JsonIgnore]
public string PackageId { get ; set;}
[Required]
[JsonIgnore]
public string PackageVersionFullString { get ; set;}
[JsonProperty("targetFramework")]
[Required]
public string TargetFramework { get; set; }
[JsonProperty("dependencies")]
[ForeignKey("DependencyGroupId")]
public virtual List<Dependency> Dependencies { get; set; }
[JsonIgnore]
public virtual PackageVersion PackageVersion { get; set; }
}
}

View File

@ -0,0 +1,90 @@
using System.Security.Principal;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
using isn.abst;
using isnd.Data.Catalog;
using isnd.Data.Packages;
using isnd.Data.Packages.Catalog;
using isnd.Entities;
using Microsoft.EntityFrameworkCore;
using Newtonsoft.Json;
using NuGet.Versioning;
using System;
using NuGet.Packaging;
using System.Collections.Generic;
using Package = isnd.Data.Packages.Package;
using Microsoft.EntityFrameworkCore.Metadata.Internal;
namespace isnd.Data
{
[PrimaryKey("PackageId", "FullString")]
public class PackageVersion
{
[Required]
[ForeignKey("Package")]
[StringLength(1024)]
[JsonProperty("id")]
public string PackageId { get; set; }
[Required]
public int Major { get; set; }
[Required]
public int Minor { get; set; }
[Required]
public int Patch { get; set; }
public int Revision { get; set; }
/// <summary>
/// Full version string
/// </summary>
/// <value></value>
[StringLength(256)]
[Required]
public string FullString { get; set; }
public bool IsPrerelease { get; set; }
[StringLength(256)]
public string Type { get; set; }
[JsonIgnore]
public virtual Packages.Package Package { get; set; }
[Required]
[JsonIgnore]
[ForeignKey("LatestCommit")]
public long CommitNId { get; set; }
[NotMapped]
public string CommitId { get => CommitNId.ToString(); }
public virtual Commit LatestCommit { get; set; }
public virtual List<PackageDependencyGroup> DependencyGroups { get; set; }
public string NugetLink => $"{ApiConfig.ContentBase}/{PackageId}/{FullString}/{PackageId}-{FullString}."
+ Constants.PacketFileExtension;
public string NuspecLink => $"{ApiConfig.ContentBase}/{PackageId}/{FullString}/{PackageId}-{FullString}."
+ Constants.SpecFileExtension;
public string SementicVersionString { get => $"{Major}.{Minor}.{Patch}"; }
public NuGetVersion NugetVersion { get => new NuGetVersion(FullString); }
public Catalog.RegistrationLeaf ToPackage(string apiBase)
{
return new Catalog.RegistrationLeaf(apiBase, this.PackageId , FullString, new Catalog.PackageDetails(this, apiBase));
}
public bool IsDeleted => LatestCommit?.Action == PackageAction.DeletePackage;
[StringLength(1024)]
public string Authors { get; set; }
[StringLength(1024)]
public string Description { get; set; }
}
}

View File

@ -1,21 +0,0 @@
namespace isnd.Entities
{
public static class ApiConfig
{
public const string Publish = "put";
public const string Base = "index.json";
public const string Catalog = "catalog";
public const string CatalogPage = "catalog-page";
public const string Get = "package";
public const string Search = "search";
public const string AutoComplete = "autocomplete";
public const string CatalogLeaf = "catalog-leaf";
public const string CatalogPackageDetail = "package-detail";
public const string Delete = "delete";
public const string Registration = "registration";
}
}

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