Merge remote-tracking branch 'origin/main'

This commit is contained in:
atef 2023-03-17 10:39:56 +01:00
commit c52501f715
647 changed files with 11625 additions and 6219 deletions

View File

@ -1,10 +1,5 @@
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
</PropertyGroup>
<Import Project="../InnovEnergy.App.props" />
<ItemGroup>
<PackageReference Include="Flurl.Http" Version="3.2.4" />
@ -26,17 +21,24 @@
<PackageReference Include="Swashbuckle.AspNetCore.Filters.Abstractions" Version="7.0.6" />
</ItemGroup>
<ItemGroup>
<Reference Include="AWSSDK.Core">
<HintPath>..\..\..\..\..\..\.nuget\packages\awssdk.core\3.7.8.10\lib\netcoreapp3.1\AWSSDK.Core.dll</HintPath>
</Reference>
<Reference Include="SQLite-net">
<HintPath>..\..\..\..\..\.nuget\packages\sqlite-net-pcl\1.8.116\lib\netstandard2.0\SQLite-net.dll</HintPath>
</Reference>
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\lib\WebServer\WebServer.csproj" />
<ProjectReference Include="../../Lib/Utils/Utils.csproj" />
</ItemGroup>
<ItemGroup>
<Folder Include="Resources" />
</ItemGroup>
<ItemGroup>
<None Update="Resources\s3cmd.py">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
</ItemGroup>
</Project>

View File

@ -0,0 +1,390 @@
using InnovEnergy.App.Backend.Database;
using InnovEnergy.App.Backend.DataTypes;
using InnovEnergy.App.Backend.DataTypes.Methods;
using InnovEnergy.App.Backend.Relations;
using Microsoft.AspNetCore.Mvc;
using static System.Net.HttpStatusCode;
using Folder = InnovEnergy.App.Backend.DataTypes.Folder;
using Installation = InnovEnergy.App.Backend.DataTypes.Installation;
using Object = System.Object;
using User = InnovEnergy.App.Backend.DataTypes.User;
namespace InnovEnergy.App.Backend.Controllers;
[ApiController]
[Route("api/")]
public class Controller
{
private static readonly HttpResponseMessage _Unauthorized = new HttpResponseMessage(Unauthorized);
private static readonly HttpResponseMessage _Ok = new HttpResponseMessage(OK);
private static readonly HttpResponseMessage _BadRequest = new HttpResponseMessage(BadRequest);
[Returns<String>]
[Returns(Unauthorized)]
[Returns(BadRequest)]
[HttpPost($"{nameof(Login)}")]
public Object Login(Credentials credentials)
{
var session = credentials.Login();
return session is null
? _Unauthorized
: session;
}
[Returns(OK)]
[Returns(Unauthorized)]
[HttpPost($"{nameof(Logout)}")]
public Object Logout()
{
var session = GetSession();
return session.Logout()
? _Ok
: _Unauthorized;
}
[Returns<User>]
[Returns(Unauthorized)]
[HttpGet($"{nameof(GetUserById)}")]
public Object GetUserById(Int64 id)
{
var caller = GetSession()?.User;
if (caller == null)
return _Unauthorized;
var user = Db.GetUserById(id);
if (user is null || !caller.HasAccessTo(user))
return _Unauthorized;
user.Password = "";
return user;
}
[Returns<Installation>]
[Returns(Unauthorized)]
[HttpGet($"{nameof(GetInstallationById)}")]
public Object GetInstallationById(Int64 id)
{
var user = GetSession()?.User;
if (user == null)
return _Unauthorized;
var installation = Db.GetInstallationById(id);
if (installation is null || !user.HasAccessTo(installation))
return _Unauthorized;
return installation;
}
[Returns<Installation>]
[Returns(Unauthorized)]
[HttpGet($"{nameof(GetUsersWithAccessToInstallation)}")]
public Object GetUsersWithAccessToInstallation(Int64 id)
{
var user = GetSession()?.User;
if (user == null)
return _Unauthorized;
var installation = Db.GetInstallationById(id);
if (installation is null || !user.HasAccessTo(installation))
return _Unauthorized;
var usersWithInheritedAccess = installation
.Ancestors()
.SelectMany(f => f.UsersWithDirectAccess()
.Where(u => u.IsDescendantOf(user))
.Select(u => new { folderId = f.Id, user = u }))
.OfType<Object>();
var usersWithDirectAccess = installation.UsersWithDirectAccess()
.Where(u => u.IsDescendantOf(user))
.Select(u => new { installationId = installation.Id, user = u })
.OfType<Object>();
return usersWithInheritedAccess.Concat(usersWithDirectAccess);
}
[Returns<Installation>]
[Returns(Unauthorized)]
[HttpGet($"{nameof(GetUsersWithAccessToFolder)}")]
public Object GetUsersWithAccessToFolder(Int64 id)
{
var user = GetSession()?.User;
if (user == null)
return _Unauthorized;
var folder = Db.GetFolderById(id);
if (folder is null || !user.HasAccessTo(folder))
return _Unauthorized;
return folder
.Ancestors()
.Append(folder)
.SelectMany(f => f.UsersWithDirectAccess()
.Where(u => u.IsDescendantOf(user))
.Select(u => new { folderId = f.Id, user = u }));
}
[Returns<Folder>]
[Returns(Unauthorized)]
[HttpGet($"{nameof(GetFolderById)}")]
public Object GetFolderById(Int64 id)
{
var user = GetSession()?.User;
if (user == null)
return _Unauthorized;
var folder = Db.GetFolderById(id);
if (folder is null || !user.HasAccessTo(folder))
return _Unauthorized;
return folder;
}
[Returns<Installation[]>] // assuming swagger knows about arrays but not lists (JSON)
[Returns(Unauthorized)]
[HttpGet($"{nameof(GetAllInstallations)}/")]
public Object GetAllInstallations()
{
var user = GetSession()?.User;
return user is null
? _Unauthorized
: user.AccessibleInstallations();
}
[Returns<Folder[]>] // assuming swagger knows about arrays but not lists (JSON)
[Returns(Unauthorized)]
[HttpGet($"{nameof(GetAllFolders)}/")]
public Object GetAllFolders()
{
var user = GetSession()?.User;
return user is null
? _Unauthorized
: user.AccessibleFolders();
}
// [Returns<Folder[]>] // assuming swagger knows about arrays but not lists (JSON)
// [Returns(Unauthorized)]
// [HttpGet($"{nameof(GetUsersOfFolder)}/")]
// public Object GetUsersOfFolder(Int64 folderId)
// {
// var caller = GetCaller();
// if (caller == null)
// return new HttpResponseMessage(Unauthorized);
//
// var folder = Db.GetFolderById(folderId);
//
// if (folder is null || !caller.HasAccessTo(folder))
// return new HttpResponseMessage(Unauthorized);
//
// return descendantUsers;
// }
[Returns<TreeNode[]>] // assuming swagger knows about arrays but not lists (JSON)
[Returns(Unauthorized)]
[HttpGet($"{nameof(GetAllFoldersAndInstallations)}/")]
public Object GetAllFoldersAndInstallations()
{
var user = GetSession()?.User;
return user is null
? _Unauthorized
: user.AccessibleFoldersAndInstallations();
}
[Returns(OK)]
[Returns(Unauthorized)]
[HttpPost($"{nameof(CreateUser)}/")]
public Object CreateUser(User newUser)
{
var session = GetSession();
return session.Create(newUser)
? newUser
: _Unauthorized ;
}
[Returns(OK)]
[Returns(Unauthorized)]
[HttpPost($"{nameof(CreateInstallation)}/")]
public Object CreateInstallation(Installation installation)
{
var session = GetSession();
return session.Create(installation)
? installation
: _Unauthorized;
}
[Returns(OK)]
[Returns(Unauthorized)]
[Returns(InternalServerError)]
[HttpPost($"{nameof(CreateFolder)}/")]
public Object CreateFolder(Folder folder)
{
var session = GetSession();
return session.Create(folder)
? folder
: _Unauthorized;
}
[Returns(OK)]
[Returns(Unauthorized)]
[HttpPost($"{nameof(GrantUserAccessToFolder)}/")]
public Object GrantUserAccessToFolder([FromQuery] Int64 folderId, [FromQuery] Int64? id)
{
var session = GetSession();
var user = id is not null ? Db.GetUserById(id) : session?.User;
return session.GrantUserAccessTo(user, Db.GetFolderById(folderId))
? _Ok
: _Unauthorized;
}
[Returns(OK)]
[Returns(Unauthorized)]
[HttpPost($"{nameof(GrantUserAccessToInstallation)}/")]
public Object GrantUserAccessToInstallation([FromQuery] Int64 installationId, [FromQuery] Int64? id)
{
var session = GetSession();
var user = id is not null ? Db.GetUserById(id) : session?.User;
return session.GrantUserAccessTo(user, Db.GetInstallationById(installationId))
? _Ok
: _Unauthorized;
}
[Returns(OK)]
[Returns(Unauthorized)]
[HttpPost($"{nameof(RevokeUserAccessToInstallation)}/")]
public Object RevokeUserAccessToInstallation([FromQuery] Int64 installationId, [FromQuery] Int64? id)
{
var session = GetSession();
var user = id is not null ? Db.GetUserById(id) : session?.User;
return session.RevokeAccessTo(user, Db.GetInstallationById(installationId))
? _Ok
: _Unauthorized;
}
[Returns(OK)]
[Returns(Unauthorized)]
[HttpPost($"{nameof(RevokeUserAccessToFolder)}/")]
public Object RevokeUserAccessToFolder([FromQuery] Int64 folderId, [FromQuery] Int64? id)
{
var session = GetSession();
var user = id is not null ? Db.GetUserById(id) : session?.User;
return session.RevokeAccessTo(user, Db.GetFolderById(folderId))
? _Ok
: _Unauthorized;
}
[Returns(OK)]
[Returns(Unauthorized)]
[HttpPut($"{nameof(UpdateUser)}/")]
public Object UpdateUser(User updatedUser)
{
var session = GetSession();
if (!session.Update(updatedUser)) return _Unauthorized;
updatedUser.Password = "";
return updatedUser;
}
[Returns(OK)]
[Returns(Unauthorized)]
[HttpPut($"{nameof(UpdateInstallation)}/")]
public Object UpdateInstallation(Installation installation)
{
var session = GetSession();
return session.Update(installation)
? installation
: _Unauthorized;
}
[Returns(OK)]
[Returns(Unauthorized)]
[HttpPut($"{nameof(UpdateFolder)}/")]
public Object UpdateFolder(Folder folder)
{
var session = GetSession();
return session.Update(folder)
? folder
: _Unauthorized;
}
[Returns(OK)]
[Returns(Unauthorized)]
[HttpDelete($"{nameof(DeleteUser)}/")]
public Object DeleteUser(Int64 userId)
{
var session = GetSession();
var user = Db.GetUserById(userId);
return session.Delete(user)
? _Ok
: _Unauthorized;
}
[Returns(OK)]
[Returns(Unauthorized)]
[HttpDelete($"{nameof(DeleteInstallation)}/")]
public Object DeleteInstallation(Int64 installationId)
{
var session = GetSession();
var installation = Db.GetInstallationById(installationId);
return session.Delete(installation)
? _Ok
: _Unauthorized;
}
[ProducesResponseType(200)]
[ProducesResponseType(401)]
[HttpDelete($"{nameof(DeleteFolder)}/")]
public Object DeleteFolder(Int64 folderId)
{
var session = GetSession();
var folder = Db.GetFolderById(folderId);
return session.Delete(folder)
? _Ok
: _Unauthorized;
}
private static Session? GetSession()
{
var ctxAccessor = new HttpContextAccessor();
return ctxAccessor.HttpContext?.Items["Session"] as Session;
}
}

View File

@ -0,0 +1,22 @@
using System.Net;
using Microsoft.AspNetCore.Mvc;
namespace InnovEnergy.App.Backend.Controllers;
public class ReturnsAttribute : ProducesResponseTypeAttribute
{
public ReturnsAttribute(HttpStatusCode statusCode) : base((Int32)statusCode)
{
}
}
public class ReturnsAttribute<T> : ProducesResponseTypeAttribute
{
public ReturnsAttribute(HttpStatusCode statusCode) : base(typeof(T), (Int32)statusCode)
{
}
public ReturnsAttribute() : base(typeof(T), (Int32)HttpStatusCode.OK)
{
}
}

View File

@ -0,0 +1,6 @@
using System.Diagnostics.CodeAnalysis;
namespace InnovEnergy.App.Backend.DataTypes;
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)]
public record Credentials(String Username, String Password);

View File

@ -0,0 +1,3 @@
namespace InnovEnergy.App.Backend.DataTypes;
public class Folder : TreeNode {}

View File

@ -1,6 +1,4 @@
using SQLite;
namespace Backend.Model;
namespace InnovEnergy.App.Backend.DataTypes;
public class Installation : TreeNode
@ -16,6 +14,6 @@ public class Installation : TreeNode
public Double Long { get; set; }
public String S3Bucket { get; set; } = "";
public String S3Url { get; set; } = "";
}

View File

@ -0,0 +1,27 @@
using InnovEnergy.App.Backend.Database;
using InnovEnergy.App.Backend.Relations;
using InnovEnergy.Lib.Utils;
namespace InnovEnergy.App.Backend.DataTypes.Methods;
public static class CredentialsMethods
{
public static Session? Login(this Credentials credentials)
{
var (username, password) = credentials;
if (username.IsNullOrEmpty() || password.IsNullOrEmpty())
return null;
var user = Db.GetUserByEmail(username);
if (user is null || !user.VerifyPassword(password))
return null;
var session = new Session(user);
return Db.Create(session)
? session
: null;
}
}

View File

@ -0,0 +1,97 @@
using System.Collections;
using InnovEnergy.App.Backend.Database;
using InnovEnergy.Lib.Utils;
namespace InnovEnergy.App.Backend.DataTypes.Methods;
public static class FolderMethods
{
public static IEnumerable<User> UsersWithAccess(this Folder folder)
{
return UsersWithDirectAccess(folder).Concat(UsersWithInheritedAccess(folder));
}
public static IEnumerable<User> UsersWithDirectAccess(this Folder folder)
{
return Db.FolderAccess
.Where(access => access.FolderId == folder.Id)
.Select(access => Db.GetUserById(access.UserId))
.NotNull();
}
public static IEnumerable<User> UsersWithInheritedAccess(this Folder folder)
{
return folder.Ancestors().SelectMany(f => f.UsersWithDirectAccess()).NotNull();
}
public static IEnumerable<Folder> ChildFolders(this Folder parent)
{
return Db
.Folders
.Where(f => f.ParentId == parent.Id);
}
public static IEnumerable<Installation> ChildInstallations(this Folder parent)
{
return Db
.Installations
.Where(f => f.ParentId == parent.Id);
}
public static IEnumerable<Folder> DescendantFolders(this Folder parent)
{
return parent
.TraverseDepthFirstPreOrder(ChildFolders)
.Skip(1); // skip self
}
public static Boolean IsDescendantOf(this Folder folder, Folder ancestor)
{
return folder
.Ancestors()
.Any(u => u.Id == ancestor.Id);
}
public static IEnumerable<Folder> Ancestors(this Folder folder)
{
return folder
.Unfold(Parent)
.Skip(1); // skip self
}
public static Folder? Parent(this Folder folder)
{
return IsAbsoluteRoot(folder)
? null
: Db.GetFolderById(folder.ParentId);
}
public static Boolean IsAbsoluteRoot(this Folder folder)
{
return folder.ParentId == 0; // root has ParentId 0 by definition
}
public static Boolean IsRelativeRoot(this Folder folder)
{
return folder.ParentId < 0;
}
public static Boolean WasMoved(this Folder folder)
{
if (folder.IsRelativeRoot())
return false;
var existingFolder = Db.GetFolderById(folder.Id);
return existingFolder is not null
&& existingFolder.ParentId != folder.ParentId;
}
public static Boolean Exists(this Folder folder)
{
return Db.Folders.Any(f => f.Id == folder.Id);
}
}

View File

@ -0,0 +1,145 @@
using CliWrap;
using CliWrap.Buffered;
using InnovEnergy.App.Backend.Database;
using InnovEnergy.Lib.Utils;
namespace InnovEnergy.App.Backend.DataTypes.Methods;
public static class InstallationMethods
{
public static async Task RenewS3BucketUrl(this Installation installation)
{
await RenewS3BucketUrl(installation, TimeSpan.FromDays(1));
}
public static async Task RenewS3BucketUrl(this Installation installation, TimeSpan validity)
{
const String secret = "55MAqyO_FqUmh7O64VIO0egq50ERn_WIAWuc2QC44QU";
const String apiKey = "EXO44d2979c8e570eae81ead564";
const String salt = "3e5b3069-214a-43ee-8d85-57d72000c19d";
var cmd = Cli
.Wrap("python3")
.WithArguments(new[]
{
"Resources/s3cmd.py", "signurl", $"s3://{installation.Id}-{salt}", validity.TotalSeconds.ToString(), "--access_key",
apiKey, "--secret_key", secret
});
var x = await cmd.ExecuteBufferedAsync();
installation.S3Url = x.StandardOutput.Replace("\n", "").Replace(" ", "");
Db.Update(installation);
}
public static async Task<Boolean> CreateBucket(this Installation installation)
{
const String secret = "-T9TAqy9a3-0-xj7HKsFFJOCcxfRpcnL6OW5oOrOcWU";
const String apiKey = "EXO87ca85e29dd412f1238f1cf0";
const String salt = "3e5b3069-214a-43ee-8d85-57d72000c19d";
var cmd = Cli
.Wrap("python3")
.WithArguments(new[]
{
"Resources/s3cmd.py", "mb", $"s3://{installation.Id}-{salt}", "--access_key",
apiKey, "--secret_key", secret
});
var x = await cmd.ExecuteBufferedAsync();
//Updating the url in the db as not wait until the next bi-daily update
var cmd2 = Cli
.Wrap("python3")
.WithArguments(new[]
{
"Resources/s3cmd.py", "signurl", $"s3://{installation.Id}-{salt}",
TimeSpan.FromDays(1).TotalSeconds.ToString(), "--access_key",
apiKey, "--secret_key", secret
});
var y = await cmd2.ExecuteBufferedAsync();
installation.S3Url = y.StandardOutput.Replace("\n", "").Replace(" ", "");
Db.Update(installation);
return x.ExitCode == 0;
}
public static async Task<Boolean> DeleteBucket(this Installation installation)
{
const String secret = "-T9TAqy9a3-0-xj7HKsFFJOCcxfRpcnL6OW5oOrOcWU";
const String apiKey = "EXO87ca85e29dd412f1238f1cf0";
const String salt = "3e5b3069-214a-43ee-8d85-57d72000c19d";
var cmd = Cli
.Wrap("python3")
.WithArguments(new[]
{
"Resources/s3cmd.py", "rb", $"s3://{installation.Id}-{salt}", "--access_key",
apiKey, "--secret_key", secret
});
var x = await cmd.ExecuteBufferedAsync();
return x.ExitCode == 0;
}
public static IEnumerable<User> UsersWithAccess(this Installation installation)
{
return UsersWithDirectAccess(installation).Concat(UsersWithInheritedAccess(installation));
}
public static IEnumerable<User> UsersWithDirectAccess(this Installation installation)
{
return Db.InstallationAccess
.Where(access => access.InstallationId == installation.Id)
.Select(access => Db.GetUserById(access.UserId))
.NotNull();
}
public static IEnumerable<User> UsersWithInheritedAccess(this Installation installation)
{
return installation.Ancestors().SelectMany(f => f.UsersWithDirectAccess()).NotNull();
}
public static IEnumerable<Folder> Ancestors(this Installation installation)
{
var parentFolder = Parent(installation);
if (parentFolder is null)
return Enumerable.Empty<Folder>();
return parentFolder
.Ancestors()
.Prepend(parentFolder);
}
public static Folder? Parent(this Installation installation)
{
return installation.IsRelativeRoot()
? null
: Db.GetFolderById(installation.ParentId);
}
public static Boolean IsRelativeRoot(this Installation i)
{
return i.ParentId < 0;
}
public static Boolean WasMoved(this Installation installation)
{
if (installation.IsRelativeRoot())
return false;
var existingInstallation = Db.GetInstallationById(installation.Id);
return existingInstallation is not null
&& existingInstallation.ParentId != installation.ParentId;
}
public static Boolean Exists(this Installation installation)
{
return Db.Installations.Any(i => i.Id == installation.Id);
}
}

View File

@ -0,0 +1,186 @@
using InnovEnergy.App.Backend.Database;
using InnovEnergy.App.Backend.Relations;
namespace InnovEnergy.App.Backend.DataTypes.Methods;
public static class SessionMethods
{
public static Boolean Create(this Session? session, Folder? folder)
{
var user = session?.User;
return user is not null
&& folder is not null
&& user.HasWriteAccess
&& user.HasAccessTo(folder.Parent())
&& Db.Create(folder)
&& Db.Create(new FolderAccess() { UserId = user.Id, FolderId = folder.Id });
}
public static Boolean Update(this Session? session, Folder? folder)
{
var user = session?.User;
return user is not null
&& folder is not null
&& user.HasWriteAccess
&& user.HasAccessTo(folder)
&& (folder.IsRelativeRoot() || user.HasAccessTo(folder.Parent()))
&& Db.Update(folder);
}
public static Boolean Delete(this Session? session, Folder? folder)
{
var user = session?.User;
return user is not null
&& folder is not null
&& user.HasWriteAccess
&& user.HasAccessTo(folder)
&& Db.Delete(folder);
}
public static Boolean Create(this Session? session, Installation? installation)
{
var user = session?.User;
//Note: keep generation of access _after_ generation of object to prevent "zombie" access-rights.
return user is not null
&& installation is not null
&& user.HasWriteAccess
&& user.HasAccessTo(installation.Parent())
&& Db.Create(installation)
&& installation.CreateBucket().Result
&& Db.Create(new InstallationAccess { UserId = user.Id, InstallationId = installation.Id });
}
public static Boolean Update(this Session? session, Installation? installation)
{
var user = session?.User;
return user is not null
&& installation is not null
&& user.HasWriteAccess
&& installation.Exists()
&& user.HasAccessTo(installation)
&& (installation.IsRelativeRoot() || user.HasAccessTo(installation.Parent())) // TODO: triple check this
&& Db.Update(installation);
}
public static Boolean Delete(this Session? session, Installation? installation)
{
var user = session?.User;
return user is not null
&& installation is not null
&& user.HasWriteAccess
&& user.HasAccessTo(installation)
&& Db.Delete(installation);
}
public static Boolean Create(this Session? session, User? newUser)
{
var sessionUser = session?.User;
if (sessionUser is null || newUser is null || !sessionUser.HasWriteAccess)
return false;
newUser.ParentId = sessionUser.Id; // Important!
return Db.Create(newUser);
}
public static Boolean Update(this Session? session, User? editedUser)
{
var sessionUser = session?.User;
if (editedUser == null || sessionUser == null) return false;
//Password change is only allowed for oneself
if ( editedUser.Id != sessionUser.Id) editedUser.Password = sessionUser.Password;
else
{
editedUser.Password = sessionUser.SaltAndHashPassword(editedUser.Password);
}
return sessionUser.HasWriteAccess
&& sessionUser.HasAccessTo(editedUser)
&& (editedUser.IsRelativeRoot() || sessionUser.HasAccessTo(editedUser.Parent()) || editedUser.Id == sessionUser.Id) // TODO: triple check this
&& Db.Update(editedUser);
}
public static Boolean Delete(this Session? session, User? userToDelete)
{
var sessionUser = session?.User;
return sessionUser is not null
&& userToDelete is not null
&& sessionUser.HasWriteAccess
&& sessionUser.HasAccessTo(userToDelete) // TODO: && user.HasAccessTo(installation.Parent()) ???
&& Db.Delete(userToDelete);
}
public static Boolean GrantUserAccessTo(this Session? session, User? user, Installation? installation)
{
var sessionUser = session?.User;
return sessionUser is not null
&& user is not null
&& installation is not null
&& user.IsDescendantOf(sessionUser)
&& sessionUser.HasAccessTo(installation)
&& !user.HasAccessTo(installation)
&& Db.Create(new InstallationAccess { UserId = user.Id, InstallationId = installation.Id });
}
public static Boolean GrantUserAccessTo(this Session? session, User? user, Folder? folder)
{
var sessionUser = session?.User;
return sessionUser is not null
&& user is not null
&& folder is not null
&& user.IsDescendantOf(sessionUser)
&& sessionUser.HasAccessTo(folder)
&& !user.HasAccessTo(folder)
&& Db.Create(new FolderAccess { UserId = user.Id, FolderId = folder.Id });
}
public static Boolean RevokeAccessTo(this Session? session, User? user, Installation? installation)
{
var sessionUser = session?.User;
return sessionUser is not null
&& user is not null
&& installation is not null
&& user.IsDescendantOf(sessionUser)
&& sessionUser.HasAccessTo(installation)
&& user.HasAccessTo(installation)
&& Db.InstallationAccess.Delete(access =>
access.UserId == user.Id && access.InstallationId == installation.Id) > 0;
}
public static Boolean RevokeAccessTo(this Session? session, User? user, Folder? folder)
{
var sessionUser = session?.User;
return sessionUser is not null
&& user is not null
&& folder is not null
&& user.IsDescendantOf(sessionUser)
&& sessionUser.HasAccessTo(folder)
&& user.HasAccessTo(folder)
&& Db.FolderAccess.Delete(access =>
access.UserId == user.Id && access.FolderId == folder.Id) > 0;
}
public static Boolean Logout(this Session? session)
{
return session is not null
&& Db.Sessions.Delete(s => s.Token == session.Token) > 0;
}
}

View File

@ -0,0 +1,205 @@
using System.Net.Mail;
using System.Security.Cryptography;
using InnovEnergy.App.Backend.Database;
using InnovEnergy.Lib.Utils;
using Convert = System.Convert;
using static System.Text.Encoding;
namespace InnovEnergy.App.Backend.DataTypes.Methods;
public static class UserMethods
{
public static IEnumerable<Installation> AccessibleInstallations(this User user)
{
var direct = user.DirectlyAccessibleInstallations();
var fromFolders = user
.AccessibleFolders()
.SelectMany(u => u.ChildInstallations());
return direct
.Concat(fromFolders)
.Distinct();
}
public static IEnumerable<Folder> AccessibleFolders(this User user)
{
return user
.DirectlyAccessibleFolders()
.SelectMany(f => f.DescendantFolders().Prepend(f))
.Distinct();
// Distinct because the user might have direct access
// to a child folder of a folder he has already access to
// ---TODO shouldn't we prevent doubling permissions? -K"
}
public static IEnumerable<TreeNode> AccessibleFoldersAndInstallations(this User user)
{
var folders = user.AccessibleFolders() as IEnumerable<TreeNode>;
var installations = user.AccessibleInstallations();
return folders.Concat(installations);
}
public static IEnumerable<Installation> DirectlyAccessibleInstallations(this User user)
{
return Db
.InstallationAccess
.Where(r => r.UserId == user.Id)
.Select(r => r.InstallationId)
.Select(Db.GetInstallationById)
.NotNull()
.Do(i => i.ParentId = 0); // hide inaccessible parents from calling user
}
public static IEnumerable<Folder> DirectlyAccessibleFolders(this User user)
{
return Db
.FolderAccess
.Where(r => r.UserId == user.Id)
.Select(r => r.FolderId)
.Select(Db.GetFolderById)
.NotNull()
.Do(i => i.ParentId = 0); // hide inaccessible parents from calling user;
}
public static IEnumerable<User> ChildUsers(this User parent)
{
return Db
.Users
.Where(f => f.ParentId == parent.Id);
}
public static IEnumerable<User> DescendantUsers(this User parent)
{
return parent
.TraverseDepthFirstPreOrder(ChildUsers)
.Skip(1); // skip self
}
public static Boolean IsDescendantOf(this User user, User ancestor)
{
// if (user.Id == ancestor.Id) return true;
return user
.Ancestors()
.Any(u => u.Id == ancestor.Id);
}
private static IEnumerable<User> Ancestors(this User user)
{
return user
.Unfold(Parent)
.Skip(1); // skip self
}
public static Boolean VerifyPassword(this User user, String password)
{
return user.Password == user.SaltAndHashPassword(password);
}
public static String SaltAndHashPassword(this User user, String password)
{
var dataToHash = $"{password}{user.Salt()}";
return dataToHash
.Apply(UTF8.GetBytes)
.Apply(SHA256.HashData)
.Apply(Convert.ToBase64String);
}
public static User? Parent(this User u)
{
return u.IsAbsoluteRoot()
? null
: Db.GetUserById(u.ParentId);
}
public static Boolean IsAbsoluteRoot(this User u)
{
return u.ParentId == 0;
}
public static Boolean IsRelativeRoot(this User u)
{
return u.ParentId < 0;
}
public static Boolean HasDirectAccessTo(this User user, Folder folder)
{
return Db
.FolderAccess
.Any(r => r.FolderId == folder.Id && r.UserId == user.Id);
}
public static Boolean HasAccessTo(this User user, Folder? folder)
{
if (folder is null)
return false;
return user.HasDirectAccessTo(folder)
|| folder
.Ancestors()
.Any(user.HasDirectAccessTo);
}
public static Boolean HasDirectAccessTo(this User user, Installation installation)
{
return Db
.InstallationAccess
.Any(r => r.UserId == user.Id && r.InstallationId == installation.Id);
}
public static Boolean HasAccessTo(this User user, Installation? installation)
{
if (installation is null)
return false;
return user.HasDirectAccessTo(installation) ||
installation.Ancestors().Any(user.HasDirectAccessTo);
}
public static Boolean HasAccessTo(this User user, User? other)
{
if (other is null)
return false;
if (other.Id == user.Id)
return true;
return other
.Ancestors()
.Contains(user);
}
public static Boolean IsRelativeRoot(this User user, Installation i)
{
// TODO: determine not by id but by accessibility
return i.ParentId < 0;
}
public static String Salt(this User user)
{
// + id => salt unique per user
// + InnovEnergy => globally unique
return $"{user.Id}InnovEnergy";
}
// TODO?
private static Boolean IsValidEmail(String email)
{
try
{
var emailAddress = new MailAddress(email);
}
catch
{
return false;
}
return true;
}
}

View File

@ -0,0 +1,23 @@
using System.Diagnostics.CodeAnalysis;
namespace InnovEnergy.App.Backend.DataTypes;
public abstract partial class TreeNode
{
public override Boolean Equals(Object? obj)
{
if (ReferenceEquals(null, obj)) return false;
if (ReferenceEquals(this, obj)) return true;
if (obj.GetType() != GetType()) return false;
return Equals((TreeNode)obj);
}
protected Boolean Equals(TreeNode other) => Id == other.Id;
[SuppressMessage("ReSharper", "NonReadonlyMemberInGetHashCode")]
public override Int32 GetHashCode() => Id.GetHashCode();
public static Boolean operator ==(TreeNode? left, TreeNode? right) => Equals(left, right);
public static Boolean operator !=(TreeNode? left, TreeNode? right) => !Equals(left, right);
}

View File

@ -0,0 +1,18 @@
using SQLite;
namespace InnovEnergy.App.Backend.DataTypes;
public abstract partial class TreeNode
{
[PrimaryKey, AutoIncrement]
public Int64 Id { get; set; }
public String Name { get; set; } = "";
public String Information { get; set; } = ""; // unstructured random info
[Indexed] // parent/child relation
public Int64 ParentId { get; set; }
[Ignore]
public String Type => GetType().Name;
}

View File

@ -0,0 +1,14 @@
using SQLite;
namespace InnovEnergy.App.Backend.DataTypes;
public class User : TreeNode
{
[Indexed]
public String Email { get; set; } = null!;
public Boolean HasWriteAccess { get; set; } = false;
public String Language { get; set; } = null!;
public String Password { get; set; } = null!;
// TODO: must reset pwd
}

View File

@ -0,0 +1,46 @@
using InnovEnergy.App.Backend.DataTypes;
using InnovEnergy.App.Backend.DataTypes.Methods;
using InnovEnergy.App.Backend.Relations;
namespace InnovEnergy.App.Backend.Database;
public static partial class Db
{
public static Boolean Create(Installation installation)
{
// SQLite wrapper is smart and *modifies* t's Id to the one generated (autoincrement) by the insertion
return Connection.Insert(installation) > 0;
}
public static Boolean Create(Folder folder)
{
return Connection.Insert(folder) > 0;
}
public static Boolean Create(User user)
{
if (GetUserByEmail(user.Email) is not null)
return false;
user.Password = user.SaltAndHashPassword(user.Password);
return Connection.Insert(user) > 0;
}
public static Boolean Create(Session session)
{
return Connection.Insert(session) > 0;
}
public static Boolean Create(InstallationAccess installationAccess)
{
return Connection.Insert(installationAccess) > 0;
}
public static Boolean Create(FolderAccess folderAccess)
{
return Connection.Insert(folderAccess) > 0;
}
}

View File

@ -0,0 +1,91 @@
using System.Reactive.Linq;
using InnovEnergy.App.Backend.DataTypes;
using InnovEnergy.App.Backend.DataTypes.Methods;
using InnovEnergy.App.Backend.Relations;
using InnovEnergy.Lib.Utils;
using SQLite;
namespace InnovEnergy.App.Backend.Database;
public static partial class Db
{
internal const String DbPath = "./db.sqlite";
private static SQLiteConnection Connection { get; } = new SQLiteConnection(DbPath);
public static TableQuery<Session> Sessions => Connection.Table<Session>();
public static TableQuery<Folder> Folders => Connection.Table<Folder>();
public static TableQuery<Installation> Installations => Connection.Table<Installation>();
public static TableQuery<User> Users => Connection.Table<User>();
public static TableQuery<FolderAccess> FolderAccess => Connection.Table<FolderAccess>();
public static TableQuery<InstallationAccess> InstallationAccess => Connection.Table<InstallationAccess>();
static Db()
{
// on startup create/migrate tables
Connection.RunInTransaction(() =>
{
Connection.CreateTable<User>();
Connection.CreateTable<Installation>();
Connection.CreateTable<Folder>();
Connection.CreateTable<FolderAccess>();
Connection.CreateTable<InstallationAccess>();
Connection.CreateTable<Session>();
});
Observable.Interval(TimeSpan.FromDays(0.5))
.StartWith(0) // Do it right away (on startup)
.SelectMany(Cleanup)
.Subscribe(); // and then daily
}
private static Boolean RunTransaction(Func<Boolean> func)
{
var savepoint = Connection.SaveTransactionPoint();
var success = false;
try
{
success = func();
}
finally
{
if (success)
Connection.Release(savepoint);
else
Connection.RollbackTo(savepoint);
}
return success;
}
private static async Task<Boolean> Cleanup(Int64 _)
{
await UpdateS3Urls();
DeleteStaleSessions();
return true;
}
private static void DeleteStaleSessions()
{
var deadline = DateTime.Now - Session.MaxAge;
Sessions.Delete(s => s.LastSeen < deadline);
}
private static Task UpdateS3Urls()
{
var renewTasks = Installations.Select(i => i.RenewS3BucketUrl()).ToArray();
return Task.WhenAll(renewTasks);
}
}

View File

@ -0,0 +1,63 @@
using InnovEnergy.App.Backend.DataTypes;
using InnovEnergy.App.Backend.DataTypes.Methods;
using InnovEnergy.App.Backend.Relations;
namespace InnovEnergy.App.Backend.Database;
public static partial class Db
{
public static Boolean Delete(Folder folder)
{
return RunTransaction(DeleteFolderAndAllItsDependencies);
Boolean DeleteFolderAndAllItsDependencies()
{
return folder
.DescendantFolders()
.All(DeleteDescendantFolderAndItsDependencies);
}
Boolean DeleteDescendantFolderAndItsDependencies(Folder f)
{
FolderAccess .Delete(r => r.FolderId == f.Id);
Installations.Delete(r => r.ParentId == f.Id);
return Folders.Delete(r => r.Id == f.Id) > 0;
}
}
public static Boolean Delete(Installation installation)
{
return RunTransaction(DeleteInstallationAndItsDependencies);
Boolean DeleteInstallationAndItsDependencies()
{
InstallationAccess.Delete(i => i.InstallationId == installation.Id);
return Installations.Delete(i => i.Id == installation.Id) > 0;
}
}
public static Boolean Delete(User user)
{
return RunTransaction(DeleteUserAndHisDependencies);
Boolean DeleteUserAndHisDependencies()
{
FolderAccess .Delete(u => u.UserId == user.Id);
InstallationAccess.Delete(u => u.UserId == user.Id);
return Users.Delete(u => u.Id == user.Id) > 0;
}
}
#pragma warning disable CS0618
// private!!
private static Boolean Delete(Session session)
{
return Sessions.Delete(s => s.Id == session.Id) > 0;
}
}

View File

@ -1,12 +1,12 @@
using Backend.Model.Relations;
using InnovEnergy.App.Backend.Relations;
namespace Backend.Database;
namespace InnovEnergy.App.Backend.Database;
public partial class Db
public static partial class Db
{
public void CreateFakeRelations()
public static void CreateFakeRelations()
{
_Db.RunInTransaction(() =>
Connection.RunInTransaction(() =>
{
CreateFakeUserTree();
CreateFakeFolderTree();
@ -16,9 +16,9 @@ public partial class Db
});
}
private void CreateFakeUserTree()
private static void CreateFakeUserTree()
{
foreach (var userId in Enumerable.Range(1, NbUsers))
foreach (var userId in Enumerable.Range(1, Users.Count()))
{
var user = GetUserById(userId);
if (user is null)
@ -32,9 +32,9 @@ public partial class Db
}
}
private void CreateFakeFolderTree()
private static void CreateFakeFolderTree()
{
foreach (var folderId in Enumerable.Range(1, NbFolders))
foreach (var folderId in Enumerable.Range(1, Folders.Count()))
{
var folder = GetFolderById(folderId);
if (folder is null)
@ -48,9 +48,9 @@ public partial class Db
}
}
private void LinkFakeInstallationsToFolders()
private static void LinkFakeInstallationsToFolders()
{
var nFolders = NbFolders;
var nFolders = Folders.Count();
foreach (var installation in Installations)
{
@ -59,42 +59,42 @@ public partial class Db
}
}
private void GiveFakeUsersAccessToFolders()
private static void GiveFakeUsersAccessToFolders()
{
foreach (var uf in User2Folder) // remove existing relations
_Db.Delete(uf);
foreach (var uf in FolderAccess) // remove existing relations
Connection.Delete(uf);
var nFolders = NbFolders;
var nUsers = NbUsers;
var nFolders = Folders.Count();
var nUsers = Users.Count();
foreach (var user in Users)
while (Random.Shared.Next((Int32)(nUsers - user.Id + 1)) != 0)
{
var relation = new User2Folder
var relation = new FolderAccess
{
UserId = user.Id,
FolderId = Random.Shared.Next(nFolders) + 1
};
_Db.Insert(relation);
Connection.Insert(relation);
}
}
private void GiveFakeUsersAccessToInstallations()
private static void GiveFakeUsersAccessToInstallations()
{
foreach (var ui in User2Installation) // remove existing relations
_Db.Delete(ui);
foreach (var ui in InstallationAccess) // remove existing relations
Connection.Delete(ui);
var nbInstallations = NbInstallations;
var nbInstallations = Installations.Count();
foreach (var user in Users)
while (Random.Shared.Next(5) != 0)
{
var relation = new User2Installation
var relation = new InstallationAccess
{
UserId = user.Id,
InstallationId = Random.Shared.Next(nbInstallations) + 1
};
_Db.Insert(relation);
Connection.Insert(relation);
}
}
}

View File

@ -0,0 +1,86 @@
using InnovEnergy.App.Backend.DataTypes;
using InnovEnergy.App.Backend.Relations;
namespace InnovEnergy.App.Backend.Database;
public static partial class Db
{
public static Folder? GetFolderById(Int64 id)
{
return Folders
.FirstOrDefault(f => f.Id == id);
}
public static Installation? GetInstallationById(Int64 id)
{
return Installations
.FirstOrDefault(i => i.Id == id);
}
public static User? GetUserById(Int64? id)
{
return Users
.FirstOrDefault(u => u.Id == id);
}
// private!!
private static Session? GetSessionById(Int64 id)
{
#pragma warning disable CS0618
return Sessions
.FirstOrDefault(u => u.Id == id);
#pragma warning restore CS0618
}
public static User? GetUserByEmail(String email)
{
return Users
.FirstOrDefault(u => u.Email == email);
}
public static Session? GetSession(String token)
{
var session = Sessions
.FirstOrDefault(s => s.Token == token);
// cannot use session.Valid in the DB query above.
// It does not exist in the db (IgnoreAttribute)
if (session is null)
return null;
if (!session.Valid)
{
Delete(session);
return null;
}
return session;
}
public static User? GetUserBySessionToken(String token)
{
var session = Sessions
.FirstOrDefault(s => s.Token == token);
// cannot user session.Expired in the DB query above.
// It does not exist in the db (IgnoreAttribute)
if (session is null)
return null;
if (!session.Valid)
{
Delete(session);
return null;
}
return GetUserById(session.UserId);
}
}

View File

@ -0,0 +1,63 @@
using InnovEnergy.App.Backend.DataTypes;
using InnovEnergy.App.Backend.DataTypes.Methods;
using InnovEnergy.App.Backend.Relations;
namespace InnovEnergy.App.Backend.Database;
public static partial class Db
{
public static Boolean Update(Folder folder)
{
if (folder.IsRelativeRoot()) // TODO: triple check
{
var original = GetFolderById(folder.Id);
if (original is null)
return false;
folder.ParentId = original.ParentId;
}
return Connection.InsertOrReplace(folder) > 0;
}
public static Boolean Update(Installation installation)
{
if (installation.IsRelativeRoot()) // TODO: triple check
{
var original = GetInstallationById(installation.Id);
if (original is null)
return false;
installation.ParentId = original.ParentId;
}
return Connection.InsertOrReplace(installation) > 0;
}
public static Boolean Update(User user)
{
var originalUser = GetUserById(user.Id);
return originalUser is not null
&& user.Id == originalUser.Id // these columns must not be modified!
&& user.ParentId == originalUser.ParentId
&& user.Email == originalUser.Email
&& Connection.InsertOrReplace(user) > 0;
}
public static Boolean Update(this Session session)
{
#pragma warning disable CS0618
var originalSession = GetSessionById(session.Id);
#pragma warning restore CS0618
return originalSession is not null
&& session.Token == originalSession.Token // these columns must not be modified!
&& session.UserId == originalSession.UserId
&& Connection.InsertOrReplace(session) > 0;
}
}

View File

@ -0,0 +1,24 @@
using Microsoft.OpenApi.Models;
using Swashbuckle.AspNetCore.SwaggerGen;
namespace InnovEnergy.App.Backend;
/// <summary>
/// This is for convenient testing! Todo throw me out?
/// Operation filter to add the requirement of the custom header
/// </summary>
public class HeaderFilter : IOperationFilter
{
public void Apply(OpenApiOperation operation, OperationFilterContext context)
{
operation.Parameters ??= new List<OpenApiParameter>();
operation.Parameters.Add(new OpenApiParameter
{
Name = "auth",
In = ParameterLocation.Header,
Content = new Dictionary<String, OpenApiMediaType>(),
Required = false
});
}
}

View File

@ -0,0 +1,63 @@
using InnovEnergy.App.Backend.Database;
using Microsoft.OpenApi.Models;
namespace InnovEnergy.App.Backend;
public static class Program
{
public static void Main(String[] args)
{
Db.CreateFakeRelations();
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddControllers(); // TODO: remove magic, specify controllers explicitly
// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
builder.Services.AddHttpContextAccessor();
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddCors(o => o.AddDefaultPolicy(p => p.WithOrigins("*").AllowAnyHeader().AllowAnyMethod()));
builder.Services.AddSwaggerGen(c =>
{
c.SwaggerDoc("v1", new OpenApiInfo { Title = "InnovEnergy Backend API", Version = "v1" });
c.UseAllOfToExtendReferenceSchemas();
c.OperationFilter<HeaderFilter>(); //Todo testing throw me out
});
var app = builder.Build();
// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
app.UseSwagger();
app.UseSwaggerUI(cfg => cfg.EnableFilter());
}
app.UseCors();
app.UseHttpsRedirection();
app.UseAuthorization();
app.Use(SetSessionUser);
app.MapControllers();
app.Run();
}
private static async Task SetSessionUser(HttpContext ctx, RequestDelegate next)
{
var headers = ctx.Request.Headers;
var hasToken = headers.TryGetValue("auth", out var token) ;
if (hasToken)
{
var session = Db.GetSession(token);
if (session is not null)
ctx.Items["Session"] = session;
}
await next(ctx);
}
}

View File

@ -0,0 +1,9 @@
using SQLite;
namespace InnovEnergy.App.Backend.Relations;
public class FolderAccess : Relation<Int64, Int64>
{
[Indexed] public Int64 UserId { get => Left ; init => Left = value;}
[Indexed] public Int64 FolderId { get => Right; init => Right = value;}
}

View File

@ -0,0 +1,9 @@
using SQLite;
namespace InnovEnergy.App.Backend.Relations;
public class InstallationAccess : Relation<Int64, Int64>
{
[Indexed] public Int64 UserId { get => Left ; init => Left = value;}
[Indexed] public Int64 InstallationId { get => Right; init => Right = value;}
}

View File

@ -1,7 +1,7 @@
using System.Diagnostics.CodeAnalysis;
using SQLite;
namespace Backend.Model.Relations;
namespace InnovEnergy.App.Backend.Relations;
public abstract class Relation<L,R>
{

View File

@ -0,0 +1,43 @@
using InnovEnergy.App.Backend.Database;
using InnovEnergy.App.Backend.DataTypes;
using InnovEnergy.Lib.Utils;
using SQLite;
namespace InnovEnergy.App.Backend.Relations;
public class Session : Relation<String, Int64>
{
public static TimeSpan MaxAge { get; } = TimeSpan.FromDays(7);
[Unique ] public String Token { get => Left ; init => Left = value;}
[Indexed] public Int64 UserId { get => Right; init => Right = value;}
[Indexed] public DateTime LastSeen { get; set; }
[Ignore] public Boolean Valid => DateTime.Now - LastSeen < MaxAge
&& !User.Email.IsNullOrEmpty();
[Ignore] public User User => _User ??= Db.GetUserById(UserId)!;
private User? _User;
[Obsolete("To be used only by deserializer")]
public Session()
{}
public Session(User user)
{
_User = user;
Token = CreateToken();
UserId = user.Id;
LastSeen = DateTime.Now;
}
private static String CreateToken()
{
var token = new Byte[24];
Random.Shared.NextBytes(token);
return Convert.ToBase64String(token);
}
}

File diff suppressed because it is too large Load Diff

Binary file not shown.

View File

@ -1,9 +1,9 @@
using CliWrap;
using CliWrap.Buffered;
using InnovEnergy.Lib.Utils;
using static InnovEnergy.BmsTunnel.CliPrograms;
using static InnovEnergy.App.BmsTunnel.CliPrograms;
namespace InnovEnergy.BmsTunnel;
namespace InnovEnergy.App.BmsTunnel;
using Nodes = IReadOnlyList<Byte>;

View File

@ -3,7 +3,7 @@ using System.Text;
using CliWrap.Buffered;
using InnovEnergy.Lib.Utils;
namespace InnovEnergy.BmsTunnel;
namespace InnovEnergy.App.BmsTunnel;
public class BmsTunnel : IDisposable

View File

@ -1,9 +1,5 @@
<Project Sdk="Microsoft.NET.Sdk">
<Import Project="../InnovEnergy.app.props" />
<PropertyGroup>
<RootNamespace>InnovEnergy.BmsTunnel</RootNamespace>
</PropertyGroup>
<Import Project="../InnovEnergy.App.props" />
<ItemGroup>
<PackageReference Include="CliWrap" Version="3.6.0" />
@ -11,7 +7,7 @@
</ItemGroup>
<ItemGroup>
<ProjectReference Include="../../lib/Utils/Utils.csproj" />
<ProjectReference Include="../../Lib/Utils/Utils.csproj" />
</ItemGroup>
</Project>

View File

@ -1,6 +1,6 @@
using CliWrap;
namespace InnovEnergy.BmsTunnel;
namespace InnovEnergy.App.BmsTunnel;
public static class CliPrograms
{

View File

@ -5,7 +5,7 @@
using InnovEnergy.Lib.Utils;
using static System.String;
namespace InnovEnergy.BmsTunnel;
namespace InnovEnergy.App.BmsTunnel;
public static class Program
{

View File

@ -0,0 +1,13 @@
<Project Sdk="Microsoft.NET.Sdk">
<Import Project="../InnovEnergy.App.props" />
<ItemGroup>
<ProjectReference Include="../../Lib/Utils/Utils.csproj" />
<ProjectReference Include="../../Lib/WebServer/WebServer.csproj" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="System.Linq.Async" Version="5.0.0" />
</ItemGroup>
</Project>

View File

@ -1,14 +1,14 @@
using System.Net;
using System.Text;
using InnovEnergy.Collector.Influx;
using InnovEnergy.Collector.Records;
using InnovEnergy.Collector.Utils;
using InnovEnergy.App.Collector.Influx;
using InnovEnergy.App.Collector.Records;
using InnovEnergy.App.Collector.Utils;
using InnovEnergy.Lib.Utils;
using InnovEnergy.Lib.Utils.Net;
using Convert = System.Convert;
namespace InnovEnergy.Collector;
namespace InnovEnergy.App.Collector;
using Data = IReadOnlyList<String>;

View File

@ -1,15 +1,15 @@
using System.Net;
using System.Text;
using InnovEnergy.Collector.Influx;
using InnovEnergy.Collector.Records;
using InnovEnergy.Collector.Utils;
using InnovEnergy.App.Collector.Influx;
using InnovEnergy.App.Collector.Records;
using InnovEnergy.App.Collector.Utils;
using InnovEnergy.Lib.Utils;
using Convert = System.Convert;
// NOT (YET) USED
namespace InnovEnergy.Collector;
namespace InnovEnergy.App.Collector;
using Data = IReadOnlyList<String>;

View File

@ -2,7 +2,7 @@ using static System.AttributeTargets;
#nullable disable
namespace InnovEnergy.Collector.Influx;
namespace InnovEnergy.App.Collector.Influx;
[AttributeUsage(Property)]
public class FieldAttribute : Attribute

View File

@ -1,10 +1,10 @@
using System.Text;
using InnovEnergy.Collector.Utils;
using InnovEnergy.App.Collector.Utils;
using InnovEnergy.Lib.Utils;
using static System.Globalization.CultureInfo;
using static InnovEnergy.Collector.Influx.LineProtocolSyntax;
using static InnovEnergy.App.Collector.Influx.LineProtocolSyntax;
namespace InnovEnergy.Collector.Influx;
namespace InnovEnergy.App.Collector.Influx;
public static class InfluxRecord
{

View File

@ -1,7 +1,7 @@
using System.Diagnostics.CodeAnalysis;
using static System.Globalization.CultureInfo;
namespace InnovEnergy.Collector.Influx;
namespace InnovEnergy.App.Collector.Influx;
internal static class LineProtocolSyntax
{

View File

@ -1,6 +1,6 @@
using static System.AttributeTargets;
namespace InnovEnergy.Collector.Influx;
namespace InnovEnergy.App.Collector.Influx;
[AttributeUsage(Property)]
public class TagAttribute : Attribute

View File

@ -2,17 +2,17 @@
using System.Reactive.Concurrency;
using System.Reactive.Linq;
using System.Reactive.Subjects;
using InnovEnergy.Collector.Influx;
using InnovEnergy.Collector.Records;
using InnovEnergy.Lib.Utils;
using static System.Text.Encoding;
using static InnovEnergy.Lib.Utils.ExceptionHandling;
using System.Text.Json;
using InnovEnergy.App.Collector.Influx;
using InnovEnergy.App.Collector.Records;
using InnovEnergy.Lib.Utils;
using InnovEnergy.Lib.Utils.Net;
using InnovEnergy.Lib.WebServer;
using static System.Text.Encoding;
using static InnovEnergy.Lib.Utils.ExceptionHandling;
namespace InnovEnergy.Collector;
namespace InnovEnergy.App.Collector;
// TODO: net6

View File

@ -1,10 +1,10 @@
using InnovEnergy.Collector.Influx;
using InnovEnergy.App.Collector.Influx;
// ReSharper disable IdentifierTypo
// ReSharper disable UnusedAutoPropertyAccessor.Global
// ReSharper disable InconsistentNaming
namespace InnovEnergy.Collector.Records;
namespace InnovEnergy.App.Collector.Records;
public class Alarms : BatteryRecord
{

View File

@ -0,0 +1,4 @@
namespace InnovEnergy.App.Collector.Records;
public abstract class BatteryRecord
{}

View File

@ -1,9 +1,9 @@
using InnovEnergy.Collector.Influx;
using InnovEnergy.App.Collector.Influx;
// ReSharper disable UnusedAutoPropertyAccessor.Global
// ReSharper disable MemberCanBePrivate.Global
namespace InnovEnergy.Collector.Records;
namespace InnovEnergy.App.Collector.Records;
public class BatteryStatus : BatteryRecord
{

View File

@ -1,8 +1,8 @@
using InnovEnergy.Collector.Influx;
using InnovEnergy.App.Collector.Influx;
// ReSharper disable UnusedAutoPropertyAccessor.Global
namespace InnovEnergy.Collector.Records;
namespace InnovEnergy.App.Collector.Records;
public class Error : BatteryRecord
{

View File

@ -1,6 +1,6 @@
using InnovEnergy.Collector.Influx;
using InnovEnergy.App.Collector.Influx;
namespace InnovEnergy.Collector.Records;
namespace InnovEnergy.App.Collector.Records;
public class InstallationStatus : BatteryRecord
{

View File

@ -1,8 +1,8 @@
using InnovEnergy.Collector.Influx;
using InnovEnergy.App.Collector.Influx;
// ReSharper disable UnusedAutoPropertyAccessor.Global
namespace InnovEnergy.Collector.Records;
namespace InnovEnergy.App.Collector.Records;
public class IoStatus : BatteryRecord
{

View File

@ -1,9 +1,9 @@
using InnovEnergy.Collector.Influx;
using InnovEnergy.App.Collector.Influx;
// ReSharper disable UnusedAutoPropertyAccessor.Global
// ReSharper disable UnusedMember.Global
namespace InnovEnergy.Collector.Records;
namespace InnovEnergy.App.Collector.Records;
public class Leds : BatteryRecord
{

View File

@ -1,7 +1,6 @@
using InnovEnergy.Collector.Influx;
using InnovEnergy.App.Collector.Influx;
namespace InnovEnergy.Collector.Records;
namespace InnovEnergy.App.Collector.Records;
#pragma warning disable CS8618

View File

@ -1,7 +1,6 @@
using InnovEnergy.Collector.Influx;
using InnovEnergy.App.Collector.Influx;
namespace InnovEnergy.Collector.Records;
namespace InnovEnergy.App.Collector.Records;
#pragma warning disable CS8618

View File

@ -1,6 +1,6 @@
using System.Net;
namespace InnovEnergy.Collector;
namespace InnovEnergy.App.Collector;
public static class Settings
{

View File

@ -1,6 +1,6 @@
using System.Globalization;
namespace InnovEnergy.Collector.Utils;
namespace InnovEnergy.App.Collector.Utils;
public static class Extensions
{

View File

@ -1,4 +1,4 @@
namespace InnovEnergy.Collector.Utils;
namespace InnovEnergy.App.Collector.Utils;
internal static class Log
{

View File

@ -1,7 +1,7 @@
using System.Reflection;
using InnovEnergy.Lib.Utils;
namespace InnovEnergy.Collector.Utils;
namespace InnovEnergy.App.Collector.Utils;
public readonly struct Property
{

View File

@ -1,4 +1,4 @@
namespace InnovEnergy.Collector.Utils;
namespace InnovEnergy.App.Collector.Utils;
public static class ReadOnlyListExtensions
{

View File

@ -1,4 +1,4 @@
namespace InnovEnergy.Collector.Utils;
namespace InnovEnergy.App.Collector.Utils;
public static class Utils
{

View File

@ -0,0 +1,65 @@
using System.Reflection;
using InnovEnergy.Lib.Victron.VeDBus;
namespace InnovEnergy.App.EmuMeterDriver;
public static class Config
{
public const String Version = "1.0";
public const String BusName = "com.victronenergy.grid.emu";
public const Byte ModbusNodeId = 1;
public const String OwnAddress = "10.0.0.1";
public const String PeerAddress = "10.0.0.2";
//public const String PeerAddress = "127.0.0.1";
public const UInt16 PeerPort = 502;
public static TimeSpan TcpTimeout { get; } = TimeSpan.FromSeconds(2);
public static readonly TimeSpan UpdatePeriod = TimeSpan.FromSeconds(1);
public static readonly IReadOnlyList<Signal> Signals = new Signal[]
{
new(s => s.Ac.L1.Current, "/Ac/L1/Current", "0.0 A"),
new(s => s.Ac.L2.Current, "/Ac/L2/Current", "0.0 A"),
new(s => s.Ac.L3.Current, "/Ac/L3/Current", "0.0 A"),
new(s => s.Ac.L1.Current + s.Ac.L2.Current + s.Ac.L3.Current, "/Ac/Current", "0.0 A"),
new(s => s.Ac.L1.Voltage, "/Ac/L1/Voltage", "0.0 A"),
new(s => s.Ac.L2.Voltage, "/Ac/L2/Voltage", "0.0 A"),
new(s => s.Ac.L3.Voltage, "/Ac/L3/Voltage", "0.0 A"),
new(s => (s.Ac.L1.Voltage + s.Ac.L2.Voltage + s.Ac.L3.Voltage) / 3.0m, "/Ac/Voltage", "0.0 A"),
new(s => s.Ac.L1.ActivePower, "/Ac/L1/Power", "0 W"),
new(s => s.Ac.L2.ActivePower, "/Ac/L2/Power", "0 W"),
new(s => s.Ac.L3.ActivePower, "/Ac/L3/Power", "0 W"),
new(s => s.Ac.ActivePower, "/Ac/Power", "0 W"),
// new(s => s.EnergyImportL123, "Ac/Energy/Forward", "0.00 kWh"),
// new(s => s.EnergyExportL123, "Ac/Energy/Reverse", "0.00 kWh"),
//
// new(s => s.EnergyImportL1, "Ac/L1/Energy/Forward", "0.00 kWh"),
// new(s => s.EnergyExportL1, "Ac/L1/Energy/Reverse", "0.00 kWh"),
//
// new(s => s.EnergyImportL2, "Ac/L2/Energy/Forward", "0.00 kWh"),
// new(s => s.EnergyExportL2, "Ac/L2/Energy/Reverse", "0.00 kWh"),
//
// new(s => s.EnergyImportL3, "Ac/L3/Energy/Forward", "0.00 kWh"),
// new(s => s.EnergyExportL3, "Ac/L3/Energy/Reverse", "0.00 kWh"),
};
public static VeProperties DefaultProperties => new VeProperties
{
new("/ProductName" , "Grid meter" ),
new("/CustomName" , "EMU Professional II"),
new("/DeviceInstance" , 30),
new("/DeviceType" , 72),
new("/Mgmt/Connection" , "Modbus TCP"),
new("/Mgmt/ProcessName" , Assembly.GetEntryAssembly()?.Location ?? "unknown"),
new("/Mgmt/ProcessVersion", Version),
new("/Connected" , 1),
new("/ProductId" , 45058, "b002"),
new("/Role" , "grid"),
};
}

View File

@ -5,8 +5,7 @@ using InnovEnergy.Lib.Protocols.Modbus.Clients;
using InnovEnergy.Lib.Utils;
using InnovEnergy.Lib.Victron.VeDBus;
namespace InnovEnergy.EmuMeter;
namespace InnovEnergy.App.EmuMeterDriver;
public static class EmuMeterDriver
{

View File

@ -0,0 +1,16 @@
<Project Sdk="Microsoft.NET.Sdk">
<Import Project="../InnovEnergy.App.props" />
<ItemGroup>
<ProjectReference Include="../../Lib/Devices/EmuMeter/EmuMeter.csproj" />
<ProjectReference Include="../../Lib/Protocols/DBus/DBus.csproj" />
<ProjectReference Include="../../Lib/Protocols/Modbus/Modbus.csproj" />
<ProjectReference Include="../../Lib/Utils/Utils.csproj" />
<ProjectReference Include="../../Lib/Victron/VeDBus/VeDBus.csproj" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="CliWrap" Version="3.6.0" />
</ItemGroup>
</Project>

View File

@ -2,7 +2,7 @@ using System.Text.Json.Nodes;
using CliWrap;
using CliWrap.Buffered;
namespace InnovEnergy.EmuMeter;
namespace InnovEnergy.App.EmuMeterDriver;
public readonly struct Nic
{

View File

@ -1,4 +1,4 @@
using InnovEnergy.EmuMeter;
using InnovEnergy.App.EmuMeterDriver;
using InnovEnergy.Lib.Protocols.DBus;
using InnovEnergy.Lib.Utils;
using InnovEnergy.Lib.Utils.Net;

View File

@ -2,7 +2,7 @@ using InnovEnergy.Lib.Devices.EmuMeter;
using InnovEnergy.Lib.Protocols.DBus.Protocol.DataTypes;
using InnovEnergy.Lib.Victron.VeDBus;
namespace InnovEnergy.EmuMeter;
namespace InnovEnergy.App.EmuMeterDriver;
public record Signal(Func<EmuMeterStatus, Object> Source, ObjectPath Path, String Format = "")
{

View File

@ -1,4 +1,4 @@
namespace InnovEnergy.EmuMeter;
namespace InnovEnergy.App.EmuMeterDriver;
public static class Utils
{

View File

@ -11,7 +11,6 @@
<PublishSingleFile>true</PublishSingleFile>
<PublishReadyToRun>true</PublishReadyToRun>
<TrimmerRemoveSymbols>true</TrimmerRemoveSymbols>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)' == 'Debug' ">
@ -23,3 +22,4 @@
</PropertyGroup>
</Project>

View File

@ -1,6 +1,4 @@
namespace InnovEnergy.OpenVpnCertificatesServer;
using System;
namespace InnovEnergy.App.OpenVpnCertificatesServer;
public static class Files
{

View File

@ -1,9 +1,8 @@
namespace InnovEnergy.OpenVpnCertificatesServer;
using System;
using System.Net;
using Flurl;
namespace InnovEnergy.App.OpenVpnCertificatesServer;
public static class Http
{
// TODO: use worker thread

View File

@ -1,9 +1,5 @@
<Project Sdk="Microsoft.NET.Sdk">
<Import Project="../InnovEnergy.app.props" />
<PropertyGroup>
<RootNamespace>InnovEnergy.OpenVpnCertificatesServer</RootNamespace>
</PropertyGroup>
<Import Project="../InnovEnergy.App.props" />
<ItemGroup>
<PackageReference Include="BouncyCastle" Version="1.8.9" />
@ -12,8 +8,9 @@
</ItemGroup>
<ItemGroup>
<ProjectReference Include="../../lib/Protocols/DBus/DBus.csproj" />
<ProjectReference Include="../../lib/Victron/VictronVRM/VictronVRM.csproj" />
<ProjectReference Include="../../Lib/Protocols/DBus/DBus.csproj" />
<ProjectReference Include="../../Lib/Victron/VictronVRM/VictronVRM.csproj" />
<ProjectReference Include="../../Lib/Utils/Utils.csproj" />
</ItemGroup>
</Project>

View File

@ -7,7 +7,7 @@ using Org.BouncyCastle.Math;
using Org.BouncyCastle.Security;
using Org.BouncyCastle.X509;
namespace InnovEnergy.OpenVpnCertificatesServer.PKI;
namespace InnovEnergy.App.OpenVpnCertificatesServer.PKI;
public static class CertificateAuthority
{

View File

@ -1,6 +1,6 @@
using Org.BouncyCastle.OpenSsl;
namespace InnovEnergy.OpenVpnCertificatesServer.PKI;
namespace InnovEnergy.App.OpenVpnCertificatesServer.PKI;
public static class Pem
{

View File

@ -1,6 +1,6 @@
using Org.BouncyCastle.OpenSsl;
namespace InnovEnergy.OpenVpnCertificatesServer.PKI;
namespace InnovEnergy.App.OpenVpnCertificatesServer.PKI;
public class PwdFinder : IPasswordFinder
{

View File

@ -1,15 +1,14 @@
using InnovEnergy.Lib.Utils;
using InnovEnergy.Lib.Victron.VictronVRM;
using InnovEnergy.OpenVpnCertificatesServer.PKI;
using System.Diagnostics;
using System.Diagnostics;
using System.Text;
using Flurl;
using ICSharpCode.SharpZipLib.Tar;
using InnovEnergy.App.OpenVpnCertificatesServer.PKI;
using InnovEnergy.Lib.Utils;
using InnovEnergy.Lib.Victron.VictronVRM;
using Org.BouncyCastle.Crypto;
using static InnovEnergy.App.OpenVpnCertificatesServer.PKI.CertificateAuthority;
using static InnovEnergy.OpenVpnCertificatesServer.PKI.CertificateAuthority;
namespace InnovEnergy.OpenVpnCertificatesServer;
namespace InnovEnergy.App.OpenVpnCertificatesServer;
// dotnet publish OpenVpnCertificatesServer.csproj -c Release -r linux-x64 -p:PublishSingleFile=true --self-contained true && scp bin/Release/net6.0/linux-x64/publish/OpenVpnCertificatesServer ig@salidomo.innovenergy.ch:~/get_cert/get_cert

View File

@ -1,9 +1,8 @@
namespace InnovEnergy.OpenVpnCertificatesServer;
using System;
using System.Text;
using ICSharpCode.SharpZipLib.Tar;
namespace InnovEnergy.App.OpenVpnCertificatesServer;
public static class Utils
{
public static void WriteFile(this TarOutputStream tar, String fileName, Byte[] contents, Boolean executable = false)

View File

@ -1,4 +1,4 @@
namespace InnovEnergy.RemoteSupportConsole;
namespace InnovEnergy.App.RemoteSupportConsole;
public static class Login
{

View File

@ -2,7 +2,7 @@ using System.Reactive.Linq;
using System.Reactive.Threading.Tasks;
using CliWrap;
namespace InnovEnergy.RemoteSupportConsole;
namespace InnovEnergy.App.RemoteSupportConsole;
public static class ObservablePipeSource
{

View File

@ -4,7 +4,7 @@ using InnovEnergy.Lib.Utils;
using InnovEnergy.Lib.Victron.VictronVRM;
using static System.Globalization.CompareOptions;
namespace InnovEnergy.RemoteSupportConsole;
namespace InnovEnergy.App.RemoteSupportConsole;
// dotnet publish -c release -r ubuntu-x64
// dotnet publish RemoteSupportConsole.csproj -c Release -r linux-x64 -p:PublishSingleFile=true --self-contained true

View File

@ -0,0 +1,16 @@
<Project Sdk="Microsoft.NET.Sdk">
<Import Project="../InnovEnergy.App.props" />
<ItemGroup>
<ProjectReference Include="../../Lib/Utils/Utils.csproj" />
<ProjectReference Include="../../Lib/Victron/VictronVRM/VictronVRM.csproj" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="CliWrap" Version="3.6.0" />
<PackageReference Include="System.Linq.Async" Version="6.0.1" />
</ItemGroup>
</Project>

View File

@ -1,7 +1,7 @@
using CliWrap;
using InnovEnergy.Lib.Utils;
namespace InnovEnergy.RemoteSupportConsole;
namespace InnovEnergy.App.RemoteSupportConsole;
public static class Ssh
{

View File

@ -2,7 +2,7 @@ using InnovEnergy.Lib.Utils;
using InnovEnergy.Lib.Victron.VictronVRM;
using static System.ConsoleColor;
namespace InnovEnergy.RemoteSupportConsole;
namespace InnovEnergy.App.RemoteSupportConsole;
public static class VpnConnection
{

View File

@ -2,7 +2,7 @@ using Flurl;
using Flurl.Http;
using InnovEnergy.Lib.Utils;
namespace InnovEnergy.RemoteSupportConsole;
namespace InnovEnergy.App.RemoteSupportConsole;
public static class VpnInfo
{

View File

@ -7,7 +7,7 @@ using InnovEnergy.Lib.Victron.VictronVRM;
using static System.ConsoleColor;
using static System.StringSplitOptions;
namespace InnovEnergy.RemoteSupportConsole;
namespace InnovEnergy.App.RemoteSupportConsole;
public static class VrmConnection
{

View File

@ -3,7 +3,7 @@ using InnovEnergy.Lib.Utils;
using InnovEnergy.Lib.Victron.VictronVRM;
using static System.ConsoleColor;
namespace InnovEnergy.RemoteSupportConsole;
namespace InnovEnergy.App.RemoteSupportConsole;
public static class VrmInfo

View File

@ -1,4 +1,4 @@
namespace InnovEnergy.RemoteSupportConsole;
namespace InnovEnergy.App.RemoteSupportConsole;
public readonly record struct VrmProxy(IDisposable Connection, String User, String Host, String Port) : IDisposable
{

View File

@ -0,0 +1,25 @@
<Project Sdk="Microsoft.NET.Sdk">
<Import Project="../InnovEnergy.App.props" />
<ItemGroup>
<ProjectReference Include="../../Lib/Devices/Adam6060/Adam6060.csproj"/>
<ProjectReference Include="../../Lib/Devices/AMPT/Ampt.csproj"/>
<ProjectReference Include="../../Lib/Devices/Battery48TL/Battery48TL.csproj"/>
<ProjectReference Include="../../Lib/Devices/EmuMeter/EmuMeter.csproj"/>
<ProjectReference Include="../../Lib/Devices/Trumpf/TruConvertAc/TruConvertAc.csproj"/>
<ProjectReference Include="../../Lib/Devices/Trumpf/TruConvertDc/TruConvertDc.csproj"/>
<ProjectReference Include="../../Lib/Devices/Trumpf/TruConvert/TruConvert.csproj"/>
<ProjectReference Include="../../Lib/StatusApi/StatusApi.csproj"/>
<ProjectReference Include="../../Lib/Utils/Utils.csproj"/>
<ProjectReference Include="../../Lib/Time/Time.csproj"/>
</ItemGroup>
<ItemGroup>
<PackageReference Include="CliWrap" Version="3.6.0"/>
<PackageReference Include="Flurl.Http" Version="3.2.4"/>
<PackageReference Include="System.IO.Ports" Version="7.0.0"/>
<PackageReference Include="DecimalMath.DecimalEx" Version="1.0.2"/>
</ItemGroup>
</Project>

View File

@ -1,6 +1,6 @@
using InnovEnergy.Lib.Utils;
namespace InnovEnergy.SaliMax;
namespace InnovEnergy.App.SaliMax;
public static class AsciiArt
{

View File

@ -1,4 +1,4 @@
namespace InnovEnergy.SaliMax;
namespace InnovEnergy.App.SaliMax;
public enum BusPort
{

View File

@ -1,6 +1,6 @@
using InnovEnergy.Lib.Devices.Battery48TL;
namespace InnovEnergy.SaliMax.Controller;
namespace InnovEnergy.App.SaliMax.Controller;
public class AvgBatteriesStatus
{

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