aboutsummaryrefslogtreecommitdiffstats
path: root/code/api
diff options
context:
space:
mode:
Diffstat (limited to 'code/api')
-rw-r--r--code/api/Database/AppDatabase.cs6
-rw-r--r--code/api/Database/Models/File.cs3
-rw-r--r--code/api/Database/Models/Folder.cs3
-rw-r--r--code/api/Database/Models/Permission.cs3
-rw-r--r--code/api/Database/Models/PermissionGroup.cs3
-rw-r--r--code/api/Database/Models/User.cs3
-rw-r--r--code/api/Database/Models/_Base.cs4
-rw-r--r--code/api/Endpoints/EndpointBase.cs2
-rw-r--r--code/api/Endpoints/Storage/Files/CreateEndpoint.cs6
-rw-r--r--code/api/Endpoints/Storage/Files/DeleteEndpoint.cs6
-rw-r--r--code/api/Endpoints/Storage/Files/PutEndpoint.cs6
-rw-r--r--code/api/Endpoints/Storage/Folders/CreateEndpoint.cs6
-rw-r--r--code/api/Endpoints/Storage/Folders/DeleteEndpoint.cs6
-rw-r--r--code/api/Endpoints/Storage/Shares/CreateShareEndpoint.cs6
-rw-r--r--code/api/Endpoints/Storage/Shares/DeleteShareEndpoint.cs6
-rw-r--r--code/api/Endpoints/Storage/Shares/EditShareEndpoint.cs6
-rw-r--r--code/api/Endpoints/Storage/TreeEndpoint.cs28
-rw-r--r--code/api/Endpoints/Storage/UploadEndpoint.cs61
-rw-r--r--code/api/I2R.Storage.Api.csproj4
-rw-r--r--code/api/Migrations/20221222153132_FolderParent.Designer.cs426
-rw-r--r--code/api/Migrations/20221222153132_FolderParent.cs211
-rw-r--r--code/api/Migrations/20221230174414_Second.Designer.cs0
-rw-r--r--code/api/Migrations/20221230174414_Second.cs22
-rw-r--r--code/api/Migrations/AppDatabaseModelSnapshot.cs26
-rw-r--r--code/api/Models/FileSystemEntry.cs3
-rw-r--r--code/api/Models/StorageBlobId.cs11
-rw-r--r--code/api/Pages/Home.cshtml4
-rw-r--r--code/api/Pages/Login.cshtml2
-rw-r--r--code/api/Program.cs9
-rw-r--r--code/api/Services/Abstractions/IResourceService.cs12
-rw-r--r--code/api/Services/System/ChunkUploaderService.cs16
-rw-r--r--code/api/Services/System/DefaultResourceService.cs40
-rw-r--r--code/api/Services/System/PermissionService.cs19
-rw-r--r--code/api/Services/System/ShareService.cs6
-rw-r--r--code/api/Services/System/StorageService.cs39
-rw-r--r--code/api/Statics/AppEnvVariables.cs1
-rw-r--r--code/api/Statics/AppHeaders.cs6
-rw-r--r--code/api/Statics/SystemConstants.cs7
-rw-r--r--code/api/Utilities/FileValidators.cs11
-rw-r--r--code/api/wwwroot/scripts/components/profile-modal.js24
-rw-r--r--code/api/wwwroot/scripts/helpers.js35
41 files changed, 408 insertions, 690 deletions
diff --git a/code/api/Database/AppDatabase.cs b/code/api/Database/AppDatabase.cs
index eac20b3..84b4842 100644
--- a/code/api/Database/AppDatabase.cs
+++ b/code/api/Database/AppDatabase.cs
@@ -1,19 +1,17 @@
-using File = I2R.Storage.Api.Database.Models.File;
-
namespace I2R.Storage.Api.Database;
public class AppDatabase : DbContext
{
public AppDatabase(DbContextOptions<AppDatabase> options) : base(options) { }
public DbSet<User> Users { get; set; }
- public DbSet<File> Files { get; set; }
+ public DbSet<Models.File> Files { get; set; }
public DbSet<Folder> Folders { get; set; }
public DbSet<Permission> Permissions { get; set; }
public DbSet<PermissionGroup> PermissionGroups { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder) {
modelBuilder.Entity<User>(e => { e.ToTable("users"); });
- modelBuilder.Entity<File>(e => {
+ modelBuilder.Entity<Models.File>(e => {
e.HasMany(c => c.Permissions);
e.HasOne(c => c.Folder);
e.ToTable("files");
diff --git a/code/api/Database/Models/File.cs b/code/api/Database/Models/File.cs
index b1f51a5..66404f8 100644
--- a/code/api/Database/Models/File.cs
+++ b/code/api/Database/Models/File.cs
@@ -2,6 +2,9 @@ namespace I2R.Storage.Api.Database.Models;
public class File : Base
{
+ public File() { }
+
+ public File(Guid createdBy) : base(createdBy) { }
public string Name { get; set; }
public string MimeType { get; set; }
public long SizeInBytes { get; set; }
diff --git a/code/api/Database/Models/Folder.cs b/code/api/Database/Models/Folder.cs
index 7a05f45..ff9515a 100644
--- a/code/api/Database/Models/Folder.cs
+++ b/code/api/Database/Models/Folder.cs
@@ -2,6 +2,9 @@ namespace I2R.Storage.Api.Database.Models;
public class Folder : Base
{
+ public Folder() { }
+
+ public Folder(Guid createdBy) : base(createdBy) { }
public string Name { get; set; }
public Folder Parent { get; set; }
public Guid? ParentId { get; set; }
diff --git a/code/api/Database/Models/Permission.cs b/code/api/Database/Models/Permission.cs
index 3076d0e..ae9d2b1 100644
--- a/code/api/Database/Models/Permission.cs
+++ b/code/api/Database/Models/Permission.cs
@@ -2,6 +2,9 @@ namespace I2R.Storage.Api.Database.Models;
public class Permission : Base
{
+ public Permission() { }
+
+ public Permission(Guid createdBy) : base(createdBy) { }
public Guid ContentId { get; set; }
public bool IsFile { get; set; }
public bool CanRead { get; set; }
diff --git a/code/api/Database/Models/PermissionGroup.cs b/code/api/Database/Models/PermissionGroup.cs
index 712f0cb..54ecffe 100644
--- a/code/api/Database/Models/PermissionGroup.cs
+++ b/code/api/Database/Models/PermissionGroup.cs
@@ -2,6 +2,9 @@ namespace I2R.Storage.Api.Database.Models;
public class PermissionGroup : Base
{
+ public PermissionGroup() { }
+
+ public PermissionGroup(Guid createdBy) : base(createdBy) { }
public string Name { get; set; }
public string Description { get; set; }
public List<User> Users { get; set; }
diff --git a/code/api/Database/Models/User.cs b/code/api/Database/Models/User.cs
index bd2d4ec..cd62bba 100644
--- a/code/api/Database/Models/User.cs
+++ b/code/api/Database/Models/User.cs
@@ -2,9 +2,6 @@ namespace I2R.Storage.Api.Database.Models;
public class User : Base
{
- public User() { }
-
- public User(Guid createdBy) : base(createdBy) { }
public string Username { get; set; }
public string Password { get; set; }
public EUserRole Role { get; set; }
diff --git a/code/api/Database/Models/_Base.cs b/code/api/Database/Models/_Base.cs
index 2a05a3a..4fdc6c1 100644
--- a/code/api/Database/Models/_Base.cs
+++ b/code/api/Database/Models/_Base.cs
@@ -31,4 +31,8 @@ public class Base
LastModifiedAt = AppDateTime.UtcNow;
LastModifiedBy = performingUserId;
}
+
+ public void SetOwner(Guid ownerId = default) {
+ OwningUserId = ownerId;
+ }
} \ No newline at end of file
diff --git a/code/api/Endpoints/EndpointBase.cs b/code/api/Endpoints/EndpointBase.cs
index a16f40f..320ce8d 100644
--- a/code/api/Endpoints/EndpointBase.cs
+++ b/code/api/Endpoints/EndpointBase.cs
@@ -8,6 +8,7 @@ public class EndpointBase : ControllerBase
[NonAction]
protected ActionResult KnownProblem(string title = default, string subtitle = default, Dictionary<string, string[]> errors = default) {
+ HttpContext.Response.Headers.Add(AppHeaders.IS_KNOWN_PROBLEM, "1");
return BadRequest(new KnownProblemModel {
Title = title,
Subtitle = subtitle,
@@ -18,6 +19,7 @@ public class EndpointBase : ControllerBase
[NonAction]
protected ActionResult KnownProblem(KnownProblemModel problem) {
+ HttpContext.Response.Headers.Add(AppHeaders.IS_KNOWN_PROBLEM, "1");
problem.TraceId = HttpContext.TraceIdentifier;
return BadRequest(problem);
}
diff --git a/code/api/Endpoints/Storage/Files/CreateEndpoint.cs b/code/api/Endpoints/Storage/Files/CreateEndpoint.cs
new file mode 100644
index 0000000..c53a8e3
--- /dev/null
+++ b/code/api/Endpoints/Storage/Files/CreateEndpoint.cs
@@ -0,0 +1,6 @@
+namespace I2R.Storage.Api.Endpoints.Storage.Files;
+
+public class CreateEndpoint
+{
+
+} \ No newline at end of file
diff --git a/code/api/Endpoints/Storage/Files/DeleteEndpoint.cs b/code/api/Endpoints/Storage/Files/DeleteEndpoint.cs
new file mode 100644
index 0000000..ab95d94
--- /dev/null
+++ b/code/api/Endpoints/Storage/Files/DeleteEndpoint.cs
@@ -0,0 +1,6 @@
+namespace I2R.Storage.Api.Endpoints.Storage.Files;
+
+public class DeleteEndpoint
+{
+
+} \ No newline at end of file
diff --git a/code/api/Endpoints/Storage/Files/PutEndpoint.cs b/code/api/Endpoints/Storage/Files/PutEndpoint.cs
new file mode 100644
index 0000000..129eeed
--- /dev/null
+++ b/code/api/Endpoints/Storage/Files/PutEndpoint.cs
@@ -0,0 +1,6 @@
+namespace I2R.Storage.Api.Endpoints.Storage.Files;
+
+public class PutEndpoint
+{
+
+} \ No newline at end of file
diff --git a/code/api/Endpoints/Storage/Folders/CreateEndpoint.cs b/code/api/Endpoints/Storage/Folders/CreateEndpoint.cs
new file mode 100644
index 0000000..2eb1352
--- /dev/null
+++ b/code/api/Endpoints/Storage/Folders/CreateEndpoint.cs
@@ -0,0 +1,6 @@
+namespace I2R.Storage.Api.Endpoints.Storage.Folders;
+
+public class CreateEndpoint
+{
+
+} \ No newline at end of file
diff --git a/code/api/Endpoints/Storage/Folders/DeleteEndpoint.cs b/code/api/Endpoints/Storage/Folders/DeleteEndpoint.cs
new file mode 100644
index 0000000..73fa907
--- /dev/null
+++ b/code/api/Endpoints/Storage/Folders/DeleteEndpoint.cs
@@ -0,0 +1,6 @@
+namespace I2R.Storage.Api.Endpoints.Storage.Folders;
+
+public class DeleteEndpoint
+{
+
+} \ No newline at end of file
diff --git a/code/api/Endpoints/Storage/Shares/CreateShareEndpoint.cs b/code/api/Endpoints/Storage/Shares/CreateShareEndpoint.cs
new file mode 100644
index 0000000..dd049e7
--- /dev/null
+++ b/code/api/Endpoints/Storage/Shares/CreateShareEndpoint.cs
@@ -0,0 +1,6 @@
+namespace I2R.Storage.Api.Endpoints.Storage.Shares;
+
+public class CreateShareEndpoint
+{
+
+} \ No newline at end of file
diff --git a/code/api/Endpoints/Storage/Shares/DeleteShareEndpoint.cs b/code/api/Endpoints/Storage/Shares/DeleteShareEndpoint.cs
new file mode 100644
index 0000000..ad85887
--- /dev/null
+++ b/code/api/Endpoints/Storage/Shares/DeleteShareEndpoint.cs
@@ -0,0 +1,6 @@
+namespace I2R.Storage.Api.Endpoints.Storage.Shares;
+
+public class DeleteShareEndpoint
+{
+
+} \ No newline at end of file
diff --git a/code/api/Endpoints/Storage/Shares/EditShareEndpoint.cs b/code/api/Endpoints/Storage/Shares/EditShareEndpoint.cs
new file mode 100644
index 0000000..30e4bf7
--- /dev/null
+++ b/code/api/Endpoints/Storage/Shares/EditShareEndpoint.cs
@@ -0,0 +1,6 @@
+namespace I2R.Storage.Api.Endpoints.Storage.Shares;
+
+public class EditShareEndpoint
+{
+
+} \ No newline at end of file
diff --git a/code/api/Endpoints/Storage/TreeEndpoint.cs b/code/api/Endpoints/Storage/TreeEndpoint.cs
index 857a570..f832034 100644
--- a/code/api/Endpoints/Storage/TreeEndpoint.cs
+++ b/code/api/Endpoints/Storage/TreeEndpoint.cs
@@ -2,8 +2,32 @@ namespace I2R.Storage.Api.Endpoints.Storage;
public class TreeEndpoint : EndpointBase
{
+ private readonly AppDatabase _database;
+ private readonly IPaginationService _pagination;
+
+ public TreeEndpoint(AppDatabase database, IPaginationService pagination) {
+ _database = database;
+ _pagination = pagination;
+ }
+
[HttpGet("~/storage/tree")]
- public async Task<ActionResult> Handle(Guid parent = default) {
- return Ok();
+ public async Task<ActionResult<KeysetPaginationResult<FileSystemEntry>>> Handle(Guid parent = default) {
+ return Ok(await _pagination.KeysetPaginateAsync(
+ _database.Folders.Include(c => c.Files).ConditionalWhere(() => parent != default, folder => folder.ParentId == parent),
+ b => b.Descending(a => a.Name),
+ async id => await _database.Folders.FirstOrDefaultAsync(c => c.Id == id.AsGuid()),
+ query => query.Select(p => new FileSystemEntry() {
+ Id = p.Id,
+ Name = p.Name,
+ MimeType = SystemConstants.FolderMimeType,
+ SizeInBytes = -1,
+ Files = p.Files.Select(c => new FileSystemEntry() {
+ Id = c.Id,
+ Name = c.Name,
+ MimeType = c.MimeType,
+ SizeInBytes = c.SizeInBytes
+ }).ToList()
+ })
+ ));
}
} \ No newline at end of file
diff --git a/code/api/Endpoints/Storage/UploadEndpoint.cs b/code/api/Endpoints/Storage/UploadEndpoint.cs
new file mode 100644
index 0000000..e3feffb
--- /dev/null
+++ b/code/api/Endpoints/Storage/UploadEndpoint.cs
@@ -0,0 +1,61 @@
+using File = I2R.Storage.Api.Database.Models.File;
+
+namespace I2R.Storage.Api.Endpoints.Storage;
+
+public class UploadEndpoint : EndpointBase
+{
+ private readonly DefaultResourceService _resourceService;
+ private readonly IStringLocalizer<SharedResources> _localizer;
+ private readonly AppDatabase _database;
+ private readonly StorageService _storageService;
+
+ public UploadEndpoint(DefaultResourceService resourceService, IStringLocalizer<SharedResources> localizer, AppDatabase database, StorageService storageService) {
+ _resourceService = resourceService;
+ _localizer = localizer;
+ _database = database;
+ _storageService = storageService;
+ }
+
+ public class Request
+ {
+ public IFormFileCollection FormFileCollection { get; set; }
+ public Guid FolderId { get; set; }
+ }
+
+ [HttpPost("~/storage/upload")]
+ public async Task<ActionResult> Handle(Request request) {
+ var folder = await _storageService.GetFileSystemEntryAsync(request.FolderId);
+ if (folder == default) {
+ return NotFound();
+ }
+
+ var problem = new KnownProblemModel();
+ foreach (var formFile in request.FormFileCollection) {
+ if (!formFile.FileName.IsValidFileName()) {
+ problem.AddError("file_" + formFile.Name, _localizer["{fileName} is an invalid file name"]);
+ continue;
+ }
+
+ if (problem.Errors.Any()) {
+ return KnownProblem(problem);
+ }
+
+ var file = new File(LoggedInUser.Id) {
+ Name = formFile.FileName,
+ FolderId = folder.Id,
+ MimeType = formFile.ContentType,
+ SizeInBytes = formFile.Length,
+ OwningUserId = LoggedInUser.Id
+ };
+ var id = new StorageBlobId() {
+ Id = file.Id,
+ Bucket = LoggedInUser.Id
+ };
+ await _resourceService.SetBlobAsync(id, formFile.OpenReadStream());
+ await _database.Files.AddAsync(file);
+ await _database.SaveChangesAsync();
+ }
+
+ return Ok(await _storageService.GetFileSystemEntryAsync(folder.Id));
+ }
+} \ No newline at end of file
diff --git a/code/api/I2R.Storage.Api.csproj b/code/api/I2R.Storage.Api.csproj
index 4dc596f..3b088b5 100644
--- a/code/api/I2R.Storage.Api.csproj
+++ b/code/api/I2R.Storage.Api.csproj
@@ -25,8 +25,4 @@
<LastGenOutput>SharedResources.Designer.cs</LastGenOutput>
</EmbeddedResource>
</ItemGroup>
-
- <ItemGroup>
- <Folder Include="STORAGE_ROOT" />
- </ItemGroup>
</Project>
diff --git a/code/api/Migrations/20221222153132_FolderParent.Designer.cs b/code/api/Migrations/20221222153132_FolderParent.Designer.cs
deleted file mode 100644
index ba2c456..0000000
--- a/code/api/Migrations/20221222153132_FolderParent.Designer.cs
+++ /dev/null
@@ -1,426 +0,0 @@
-// <auto-generated />
-using System;
-using I2R.Storage.Api.Database;
-using Microsoft.EntityFrameworkCore;
-using Microsoft.EntityFrameworkCore.Infrastructure;
-using Microsoft.EntityFrameworkCore.Migrations;
-using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
-using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
-
-#nullable disable
-
-namespace I2R.Storage.Api.Migrations
-{
- [DbContext(typeof(AppDatabase))]
- [Migration("20221222153132_FolderParent")]
- partial class FolderParent
- {
- /// <inheritdoc />
- protected override void BuildTargetModel(ModelBuilder modelBuilder)
- {
-#pragma warning disable 612, 618
- modelBuilder
- .HasAnnotation("ProductVersion", "7.0.1")
- .HasAnnotation("Relational:MaxIdentifierLength", 63);
-
- NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder);
-
- modelBuilder.Entity("I2R.Storage.Api.Database.Models.File", b =>
- {
- b.Property<Guid>("Id")
- .ValueGeneratedOnAdd()
- .HasColumnType("uuid")
- .HasColumnName("id");
-
- b.Property<DateTime>("CreatedAt")
- .HasColumnType("timestamp with time zone")
- .HasColumnName("created_at");
-
- b.Property<Guid?>("CreatedBy")
- .HasColumnType("uuid")
- .HasColumnName("created_by");
-
- b.Property<Guid>("FolderId")
- .HasColumnType("uuid")
- .HasColumnName("folder_id");
-
- b.Property<bool>("IsBinned")
- .HasColumnType("boolean")
- .HasColumnName("is_binned");
-
- b.Property<bool>("IsEncrypted")
- .HasColumnType("boolean")
- .HasColumnName("is_encrypted");
-
- b.Property<DateTime?>("LastDeletedAt")
- .HasColumnType("timestamp with time zone")
- .HasColumnName("last_deleted_at");
-
- b.Property<Guid?>("LastDeletedBy")
- .HasColumnType("uuid")
- .HasColumnName("last_deleted_by");
-
- b.Property<DateTime?>("LastModifiedAt")
- .HasColumnType("timestamp with time zone")
- .HasColumnName("last_modified_at");
-
- b.Property<Guid?>("LastModifiedBy")
- .HasColumnType("uuid")
- .HasColumnName("last_modified_by");
-
- b.Property<string>("MimeType")
- .HasColumnType("text")
- .HasColumnName("mime_type");
-
- b.Property<string>("Name")
- .HasColumnType("text")
- .HasColumnName("name");
-
- b.Property<Guid?>("OwningUserId")
- .HasColumnType("uuid")
- .HasColumnName("owning_user_id");
-
- b.Property<long>("SizeInBytes")
- .HasColumnType("bigint")
- .HasColumnName("size_in_bytes");
-
- b.HasKey("Id")
- .HasName("pk_files");
-
- b.HasIndex("FolderId")
- .HasDatabaseName("ix_files_folder_id");
-
- b.ToTable("files", (string)null);
- });
-
- modelBuilder.Entity("I2R.Storage.Api.Database.Models.Folder", b =>
- {
- b.Property<Guid>("Id")
- .ValueGeneratedOnAdd()
- .HasColumnType("uuid")
- .HasColumnName("id");
-
- b.Property<DateTime>("CreatedAt")
- .HasColumnType("timestamp with time zone")
- .HasColumnName("created_at");
-
- b.Property<Guid?>("CreatedBy")
- .HasColumnType("uuid")
- .HasColumnName("created_by");
-
- b.Property<bool>("IsBinned")
- .HasColumnType("boolean")
- .HasColumnName("is_binned");
-
- b.Property<bool>("IsEncrypted")
- .HasColumnType("boolean")
- .HasColumnName("is_encrypted");
-
- b.Property<DateTime?>("LastDeletedAt")
- .HasColumnType("timestamp with time zone")
- .HasColumnName("last_deleted_at");
-
- b.Property<Guid?>("LastDeletedBy")
- .HasColumnType("uuid")
- .HasColumnName("last_deleted_by");
-
- b.Property<DateTime?>("LastModifiedAt")
- .HasColumnType("timestamp with time zone")
- .HasColumnName("last_modified_at");
-
- b.Property<Guid?>("LastModifiedBy")
- .HasColumnType("uuid")
- .HasColumnName("last_modified_by");
-
- b.Property<string>("Name")
- .HasColumnType("text")
- .HasColumnName("name");
-
- b.Property<Guid?>("OwningUserId")
- .HasColumnType("uuid")
- .HasColumnName("owning_user_id");
-
- b.Property<Guid?>("ParentId")
- .HasColumnType("uuid")
- .HasColumnName("parent_id");
-
- b.HasKey("Id")
- .HasName("pk_folders");
-
- b.HasIndex("ParentId")
- .HasDatabaseName("ix_folders_parent_id");
-
- b.ToTable("folders", (string)null);
- });
-
- modelBuilder.Entity("I2R.Storage.Api.Database.Models.Permission", b =>
- {
- b.Property<Guid>("Id")
- .ValueGeneratedOnAdd()
- .HasColumnType("uuid")
- .HasColumnName("id");
-
- b.Property<bool>("CanRead")
- .HasColumnType("boolean")
- .HasColumnName("can_read");
-
- b.Property<bool>("CanWrite")
- .HasColumnType("boolean")
- .HasColumnName("can_write");
-
- b.Property<Guid>("ContentId")
- .HasColumnType("uuid")
- .HasColumnName("content_id");
-
- b.Property<DateTime>("CreatedAt")
- .HasColumnType("timestamp with time zone")
- .HasColumnName("created_at");
-
- b.Property<Guid?>("CreatedBy")
- .HasColumnType("uuid")
- .HasColumnName("created_by");
-
- b.Property<Guid?>("FileId")
- .HasColumnType("uuid")
- .HasColumnName("file_id");
-
- b.Property<Guid?>("FolderId")
- .HasColumnType("uuid")
- .HasColumnName("folder_id");
-
- b.Property<Guid>("GroupId")
- .HasColumnType("uuid")
- .HasColumnName("group_id");
-
- b.Property<bool>("IsFile")
- .HasColumnType("boolean")
- .HasColumnName("is_file");
-
- b.Property<DateTime?>("LastDeletedAt")
- .HasColumnType("timestamp with time zone")
- .HasColumnName("last_deleted_at");
-
- b.Property<Guid?>("LastDeletedBy")
- .HasColumnType("uuid")
- .HasColumnName("last_deleted_by");
-
- b.Property<DateTime?>("LastModifiedAt")
- .HasColumnType("timestamp with time zone")
- .HasColumnName("last_modified_at");
-
- b.Property<Guid?>("LastModifiedBy")
- .HasColumnType("uuid")
- .HasColumnName("last_modified_by");
-
- b.Property<Guid?>("OwningUserId")
- .HasColumnType("uuid")
- .HasColumnName("owning_user_id");
-
- b.HasKey("Id")
- .HasName("pk_permissions");
-
- b.HasIndex("FileId")
- .HasDatabaseName("ix_permissions_file_id");
-
- b.HasIndex("FolderId")
- .HasDatabaseName("ix_permissions_folder_id");
-
- b.HasIndex("GroupId")
- .HasDatabaseName("ix_permissions_group_id");
-
- b.ToTable("permissions", (string)null);
- });
-
- modelBuilder.Entity("I2R.Storage.Api.Database.Models.PermissionGroup", b =>
- {
- b.Property<Guid>("Id")
- .ValueGeneratedOnAdd()
- .HasColumnType("uuid")
- .HasColumnName("id");
-
- b.Property<DateTime>("CreatedAt")
- .HasColumnType("timestamp with time zone")
- .HasColumnName("created_at");
-
- b.Property<Guid?>("CreatedBy")
- .HasColumnType("uuid")
- .HasColumnName("created_by");
-
- b.Property<string>("Description")
- .HasColumnType("text")
- .HasColumnName("description");
-
- b.Property<DateTime?>("LastDeletedAt")
- .HasColumnType("timestamp with time zone")
- .HasColumnName("last_deleted_at");
-
- b.Property<Guid?>("LastDeletedBy")
- .HasColumnType("uuid")
- .HasColumnName("last_deleted_by");
-
- b.Property<DateTime?>("LastModifiedAt")
- .HasColumnType("timestamp with time zone")
- .HasColumnName("last_modified_at");
-
- b.Property<Guid?>("LastModifiedBy")
- .HasColumnType("uuid")
- .HasColumnName("last_modified_by");
-
- b.Property<string>("Name")
- .HasColumnType("text")
- .HasColumnName("name");
-
- b.Property<Guid?>("OwningUserId")
- .HasColumnType("uuid")
- .HasColumnName("owning_user_id");
-
- b.HasKey("Id")
- .HasName("pk_permission_groups");
-
- b.ToTable("permission_groups", (string)null);
- });
-
- modelBuilder.Entity("I2R.Storage.Api.Database.Models.User", b =>
- {
- b.Property<Guid>("Id")
- .ValueGeneratedOnAdd()
- .HasColumnType("uuid")
- .HasColumnName("id");
-
- b.Property<DateTime>("CreatedAt")
- .HasColumnType("timestamp with time zone")
- .HasColumnName("created_at");
-
- b.Property<Guid?>("CreatedBy")
- .HasColumnType("uuid")
- .HasColumnName("created_by");
-
- b.Property<string>("FirstName")
- .HasColumnType("text")
- .HasColumnName("first_name");
-
- b.Property<DateTime?>("LastDeletedAt")
- .HasColumnType("timestamp with time zone")
- .HasColumnName("last_deleted_at");
-
- b.Property<Guid?>("LastDeletedBy")
- .HasColumnType("uuid")
- .HasColumnName("last_deleted_by");
-
- b.Property<DateTime?>("LastLoggedOn")
- .HasColumnType("timestamp with time zone")
- .HasColumnName("last_logged_on");
-
- b.Property<DateTime?>("LastModifiedAt")
- .HasColumnType("timestamp with time zone")
- .HasColumnName("last_modified_at");
-
- b.Property<Guid?>("LastModifiedBy")
- .HasColumnType("uuid")
- .HasColumnName("last_modified_by");
-
- b.Property<string>("LastName")
- .HasColumnType("text")
- .HasColumnName("last_name");
-
- b.Property<Guid?>("OwningUserId")
- .HasColumnType("uuid")
- .HasColumnName("owning_user_id");
-
- b.Property<string>("Password")
- .HasColumnType("text")
- .HasColumnName("password");
-
- b.Property<Guid?>("PermissionGroupId")
- .HasColumnType("uuid")
- .HasColumnName("permission_group_id");
-
- b.Property<int>("Role")
- .HasColumnType("integer")
- .HasColumnName("role");
-
- b.Property<string>("Username")
- .HasColumnType("text")
- .HasColumnName("username");
-
- b.HasKey("Id")
- .HasName("pk_users");
-
- b.HasIndex("PermissionGroupId")
- .HasDatabaseName("ix_users_permission_group_id");
-
- b.ToTable("users", (string)null);
- });
-
- modelBuilder.Entity("I2R.Storage.Api.Database.Models.File", b =>
- {
- b.HasOne("I2R.Storage.Api.Database.Models.Folder", "Folder")
- .WithMany("Files")
- .HasForeignKey("FolderId")
- .OnDelete(DeleteBehavior.Cascade)
- .IsRequired()
- .HasConstraintName("fk_files_folders_folder_id");
-
- b.Navigation("Folder");
- });
-
- modelBuilder.Entity("I2R.Storage.Api.Database.Models.Folder", b =>
- {
- b.HasOne("I2R.Storage.Api.Database.Models.Folder", "Parent")
- .WithMany()
- .HasForeignKey("ParentId")
- .HasConstraintName("fk_folders_folders_parent_id");
-
- b.Navigation("Parent");
- });
-
- modelBuilder.Entity("I2R.Storage.Api.Database.Models.Permission", b =>
- {
- b.HasOne("I2R.Storage.Api.Database.Models.File", null)
- .WithMany("Permissions")
- .HasForeignKey("FileId")
- .HasConstraintName("fk_permissions_files_file_id");
-
- b.HasOne("I2R.Storage.Api.Database.Models.Folder", null)
- .WithMany("Permissions")
- .HasForeignKey("FolderId")
- .HasConstraintName("fk_permissions_folders_folder_id");
-
- b.HasOne("I2R.Storage.Api.Database.Models.PermissionGroup", "Group")
- .WithMany()
- .HasForeignKey("GroupId")
- .OnDelete(DeleteBehavior.Cascade)
- .IsRequired()
- .HasConstraintName("fk_permissions_permission_groups_group_id");
-
- b.Navigation("Group");
- });
-
- modelBuilder.Entity("I2R.Storage.Api.Database.Models.User", b =>
- {
- b.HasOne("I2R.Storage.Api.Database.Models.PermissionGroup", null)
- .WithMany("Users")
- .HasForeignKey("PermissionGroupId")
- .HasConstraintName("fk_users_permission_groups_permission_group_id");
- });
-
- modelBuilder.Entity("I2R.Storage.Api.Database.Models.File", b =>
- {
- b.Navigation("Permissions");
- });
-
- modelBuilder.Entity("I2R.Storage.Api.Database.Models.Folder", b =>
- {
- b.Navigation("Files");
-
- b.Navigation("Permissions");
- });
-
- modelBuilder.Entity("I2R.Storage.Api.Database.Models.PermissionGroup", b =>
- {
- b.Navigation("Users");
- });
-#pragma warning restore 612, 618
- }
- }
-}
diff --git a/code/api/Migrations/20221222153132_FolderParent.cs b/code/api/Migrations/20221222153132_FolderParent.cs
deleted file mode 100644
index 1525d4c..0000000
--- a/code/api/Migrations/20221222153132_FolderParent.cs
+++ /dev/null
@@ -1,211 +0,0 @@
-using System;
-using Microsoft.EntityFrameworkCore.Migrations;
-
-#nullable disable
-
-namespace I2R.Storage.Api.Migrations
-{
- /// <inheritdoc />
- public partial class FolderParent : Migration
- {
- /// <inheritdoc />
- protected override void Up(MigrationBuilder migrationBuilder)
- {
- migrationBuilder.AlterColumn<string>(
- name: "username",
- table: "users",
- type: "text",
- nullable: true,
- oldClrType: typeof(string),
- oldType: "text");
-
- migrationBuilder.AlterColumn<string>(
- name: "password",
- table: "users",
- type: "text",
- nullable: true,
- oldClrType: typeof(string),
- oldType: "text");
-
- migrationBuilder.AlterColumn<string>(
- name: "last_name",
- table: "users",
- type: "text",
- nullable: true,
- oldClrType: typeof(string),
- oldType: "text");
-
- migrationBuilder.AlterColumn<string>(
- name: "first_name",
- table: "users",
- type: "text",
- nullable: true,
- oldClrType: typeof(string),
- oldType: "text");
-
- migrationBuilder.AlterColumn<string>(
- name: "name",
- table: "permission_groups",
- type: "text",
- nullable: true,
- oldClrType: typeof(string),
- oldType: "text");
-
- migrationBuilder.AlterColumn<string>(
- name: "description",
- table: "permission_groups",
- type: "text",
- nullable: true,
- oldClrType: typeof(string),
- oldType: "text");
-
- migrationBuilder.AlterColumn<string>(
- name: "name",
- table: "folders",
- type: "text",
- nullable: true,
- oldClrType: typeof(string),
- oldType: "text");
-
- migrationBuilder.AddColumn<Guid>(
- name: "parent_id",
- table: "folders",
- type: "uuid",
- nullable: true);
-
- migrationBuilder.AlterColumn<string>(
- name: "name",
- table: "files",
- type: "text",
- nullable: true,
- oldClrType: typeof(string),
- oldType: "text");
-
- migrationBuilder.AlterColumn<string>(
- name: "mime_type",
- table: "files",
- type: "text",
- nullable: true,
- oldClrType: typeof(string),
- oldType: "text");
-
- migrationBuilder.CreateIndex(
- name: "ix_folders_parent_id",
- table: "folders",
- column: "parent_id");
-
- migrationBuilder.AddForeignKey(
- name: "fk_folders_folders_parent_id",
- table: "folders",
- column: "parent_id",
- principalTable: "folders",
- principalColumn: "id");
- }
-
- /// <inheritdoc />
- protected override void Down(MigrationBuilder migrationBuilder)
- {
- migrationBuilder.DropForeignKey(
- name: "fk_folders_folders_parent_id",
- table: "folders");
-
- migrationBuilder.DropIndex(
- name: "ix_folders_parent_id",
- table: "folders");
-
- migrationBuilder.DropColumn(
- name: "parent_id",
- table: "folders");
-
- migrationBuilder.AlterColumn<string>(
- name: "username",
- table: "users",
- type: "text",
- nullable: false,
- defaultValue: "",
- oldClrType: typeof(string),
- oldType: "text",
- oldNullable: true);
-
- migrationBuilder.AlterColumn<string>(
- name: "password",
- table: "users",
- type: "text",
- nullable: false,
- defaultValue: "",
- oldClrType: typeof(string),
- oldType: "text",
- oldNullable: true);
-
- migrationBuilder.AlterColumn<string>(
- name: "last_name",
- table: "users",
- type: "text",
- nullable: false,
- defaultValue: "",
- oldClrType: typeof(string),
- oldType: "text",
- oldNullable: true);
-
- migrationBuilder.AlterColumn<string>(
- name: "first_name",
- table: "users",
- type: "text",
- nullable: false,
- defaultValue: "",
- oldClrType: typeof(string),
- oldType: "text",
- oldNullable: true);
-
- migrationBuilder.AlterColumn<string>(
- name: "name",
- table: "permission_groups",
- type: "text",
- nullable: false,
- defaultValue: "",
- oldClrType: typeof(string),
- oldType: "text",
- oldNullable: true);
-
- migrationBuilder.AlterColumn<string>(
- name: "description",
- table: "permission_groups",
- type: "text",
- nullable: false,
- defaultValue: "",
- oldClrType: typeof(string),
- oldType: "text",
- oldNullable: true);
-
- migrationBuilder.AlterColumn<string>(
- name: "name",
- table: "folders",
- type: "text",
- nullable: false,
- defaultValue: "",
- oldClrType: typeof(string),
- oldType: "text",
- oldNullable: true);
-
- migrationBuilder.AlterColumn<string>(
- name: "name",
- table: "files",
- type: "text",
- nullable: false,
- defaultValue: "",
- oldClrType: typeof(string),
- oldType: "text",
- oldNullable: true);
-
- migrationBuilder.AlterColumn<string>(
- name: "mime_type",
- table: "files",
- type: "text",
- nullable: false,
- defaultValue: "",
- oldClrType: typeof(string),
- oldType: "text",
- oldNullable: true);
- }
- }
-}
diff --git a/code/api/Migrations/20221230174414_Second.Designer.cs b/code/api/Migrations/20221230174414_Second.Designer.cs
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/code/api/Migrations/20221230174414_Second.Designer.cs
diff --git a/code/api/Migrations/20221230174414_Second.cs b/code/api/Migrations/20221230174414_Second.cs
new file mode 100644
index 0000000..4012c3f
--- /dev/null
+++ b/code/api/Migrations/20221230174414_Second.cs
@@ -0,0 +1,22 @@
+using Microsoft.EntityFrameworkCore.Migrations;
+
+#nullable disable
+
+namespace I2R.Storage.Api.Migrations
+{
+ /// <inheritdoc />
+ public partial class Second : Migration
+ {
+ /// <inheritdoc />
+ protected override void Up(MigrationBuilder migrationBuilder)
+ {
+
+ }
+
+ /// <inheritdoc />
+ protected override void Down(MigrationBuilder migrationBuilder)
+ {
+
+ }
+ }
+}
diff --git a/code/api/Migrations/AppDatabaseModelSnapshot.cs b/code/api/Migrations/AppDatabaseModelSnapshot.cs
index 4d471ce..9f0ab71 100644
--- a/code/api/Migrations/AppDatabaseModelSnapshot.cs
+++ b/code/api/Migrations/AppDatabaseModelSnapshot.cs
@@ -66,10 +66,12 @@ namespace I2R.Storage.Api.Migrations
.HasColumnName("last_modified_by");
b.Property<string>("MimeType")
+ .IsRequired()
.HasColumnType("text")
.HasColumnName("mime_type");
b.Property<string>("Name")
+ .IsRequired()
.HasColumnType("text")
.HasColumnName("name");
@@ -130,6 +132,7 @@ namespace I2R.Storage.Api.Migrations
.HasColumnName("last_modified_by");
b.Property<string>("Name")
+ .IsRequired()
.HasColumnType("text")
.HasColumnName("name");
@@ -137,16 +140,9 @@ namespace I2R.Storage.Api.Migrations
.HasColumnType("uuid")
.HasColumnName("owning_user_id");
- b.Property<Guid?>("ParentId")
- .HasColumnType("uuid")
- .HasColumnName("parent_id");
-
b.HasKey("Id")
.HasName("pk_folders");
- b.HasIndex("ParentId")
- .HasDatabaseName("ix_folders_parent_id");
-
b.ToTable("folders", (string)null);
});
@@ -244,6 +240,7 @@ namespace I2R.Storage.Api.Migrations
.HasColumnName("created_by");
b.Property<string>("Description")
+ .IsRequired()
.HasColumnType("text")
.HasColumnName("description");
@@ -264,6 +261,7 @@ namespace I2R.Storage.Api.Migrations
.HasColumnName("last_modified_by");
b.Property<string>("Name")
+ .IsRequired()
.HasColumnType("text")
.HasColumnName("name");
@@ -293,6 +291,7 @@ namespace I2R.Storage.Api.Migrations
.HasColumnName("created_by");
b.Property<string>("FirstName")
+ .IsRequired()
.HasColumnType("text")
.HasColumnName("first_name");
@@ -317,6 +316,7 @@ namespace I2R.Storage.Api.Migrations
.HasColumnName("last_modified_by");
b.Property<string>("LastName")
+ .IsRequired()
.HasColumnType("text")
.HasColumnName("last_name");
@@ -325,6 +325,7 @@ namespace I2R.Storage.Api.Migrations
.HasColumnName("owning_user_id");
b.Property<string>("Password")
+ .IsRequired()
.HasColumnType("text")
.HasColumnName("password");
@@ -337,6 +338,7 @@ namespace I2R.Storage.Api.Migrations
.HasColumnName("role");
b.Property<string>("Username")
+ .IsRequired()
.HasColumnType("text")
.HasColumnName("username");
@@ -361,16 +363,6 @@ namespace I2R.Storage.Api.Migrations
b.Navigation("Folder");
});
- modelBuilder.Entity("I2R.Storage.Api.Database.Models.Folder", b =>
- {
- b.HasOne("I2R.Storage.Api.Database.Models.Folder", "Parent")
- .WithMany()
- .HasForeignKey("ParentId")
- .HasConstraintName("fk_folders_folders_parent_id");
-
- b.Navigation("Parent");
- });
-
modelBuilder.Entity("I2R.Storage.Api.Database.Models.Permission", b =>
{
b.HasOne("I2R.Storage.Api.Database.Models.File", null)
diff --git a/code/api/Models/FileSystemEntry.cs b/code/api/Models/FileSystemEntry.cs
index 432f87c..cdb1213 100644
--- a/code/api/Models/FileSystemEntry.cs
+++ b/code/api/Models/FileSystemEntry.cs
@@ -4,6 +4,7 @@ public class FileSystemEntry
{
public Guid Id { get; set; }
public string Name { get; set; }
- public string ContentType { get; set; }
+ public string MimeType { get; set; }
public long SizeInBytes { get; set; }
+ public List<FileSystemEntry> Files { get; set; }
} \ No newline at end of file
diff --git a/code/api/Models/StorageBlobId.cs b/code/api/Models/StorageBlobId.cs
new file mode 100644
index 0000000..12a5157
--- /dev/null
+++ b/code/api/Models/StorageBlobId.cs
@@ -0,0 +1,11 @@
+namespace I2R.Storage.Api.Models;
+
+public struct StorageBlobId
+{
+ public Guid Id { get; set; }
+ public Guid Bucket { get; set; }
+
+ public override string ToString() {
+ return Bucket + ":" + Id;
+ }
+} \ No newline at end of file
diff --git a/code/api/Pages/Home.cshtml b/code/api/Pages/Home.cshtml
index b7c07c2..b914874 100644
--- a/code/api/Pages/Home.cshtml
+++ b/code/api/Pages/Home.cshtml
@@ -5,6 +5,4 @@
ViewData["Title"] = "Home";
}
-<h1>Welcome</h1>
-
-<button class="do-logout">Logout</button> \ No newline at end of file
+<h1>Home</h1> \ No newline at end of file
diff --git a/code/api/Pages/Login.cshtml b/code/api/Pages/Login.cshtml
index d68e96b..0e8b304 100644
--- a/code/api/Pages/Login.cshtml
+++ b/code/api/Pages/Login.cshtml
@@ -25,4 +25,4 @@
@section Scripts {
<script src="~/scripts/page-specific/login.js" asp-append-version="true"></script>
-} \ No newline at end of file
+}
diff --git a/code/api/Program.cs b/code/api/Program.cs
index 5fde778..f6c0fae 100644
--- a/code/api/Program.cs
+++ b/code/api/Program.cs
@@ -1,4 +1,5 @@
global using Microsoft.AspNetCore.Mvc;
+global using I2R.Storage.Api.Services.System;
global using IOL.Helpers;
global using I2R.Storage.Api.Services.Admin;
global using Microsoft.AspNetCore.Mvc.RazorPages;
@@ -13,6 +14,10 @@ global using I2R.Storage.Api.Statics;
global using Microsoft.AspNetCore.Authorization;
global using System.Security.Claims;
global using I2R.Storage.Api.Models;
+global using MR.AspNetCore.Pagination;
+global using MR.EntityFrameworkCore.KeysetPagination;
+global using System.Text.Json;
+using I2R.Storage.Api.Services.System;
using Microsoft.AspNetCore.Authentication.Cookies;
using Microsoft.AspNetCore.Localization;
@@ -32,6 +37,9 @@ builder.Services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationSc
builder.Services.AddLocalization();
builder.Services.AddRequestLocalization(o => { o.DefaultRequestCulture = new RequestCulture("en"); });
builder.Services.AddScoped<UserService>();
+builder.Services.AddScoped<StorageService>();
+builder.Services.AddScoped<ShareService>();
+builder.Services.AddScoped<DefaultResourceService>();
builder.Services.AddDbContext<AppDatabase>(o => {
o.UseNpgsql(builder.Configuration.GetAppDbConnectionString(), b => {
b.UseQuerySplittingBehavior(QuerySplittingBehavior.SplitQuery);
@@ -39,6 +47,7 @@ builder.Services.AddDbContext<AppDatabase>(o => {
});
o.UseSnakeCaseNamingConvention();
});
+builder.Services.AddPagination();
builder.Services.AddRazorPages().AddRazorRuntimeCompilation();
builder.Services.AddControllers();
diff --git a/code/api/Services/Abstractions/IResourceService.cs b/code/api/Services/Abstractions/IResourceService.cs
new file mode 100644
index 0000000..904d59b
--- /dev/null
+++ b/code/api/Services/Abstractions/IResourceService.cs
@@ -0,0 +1,12 @@
+using I2R.Storage.Api.Services.System;
+
+namespace I2R.Storage.Api.Services.Abstractions;
+
+public interface IResourceService
+{
+ public Task SetBlobAsync(StorageBlobId id, Stream stream, CancellationToken cancellationToken = default);
+ public Task<byte[]> GetBlobAsync(StorageBlobId id, CancellationToken cancellationToken = default);
+ public Task RemoveBlobAsync(StorageBlobId id, CancellationToken cancellationToken = default);
+ public Task SetBlobMetadataAsync(StorageBlobId id, Dictionary<string, string> metadata, CancellationToken cancellationToken = default);
+ public Task<Dictionary<string,string>> GetBlobMetadataAsync(StorageBlobId id, CancellationToken cancellationToken = default);
+} \ No newline at end of file
diff --git a/code/api/Services/System/ChunkUploaderService.cs b/code/api/Services/System/ChunkUploaderService.cs
new file mode 100644
index 0000000..d650fa4
--- /dev/null
+++ b/code/api/Services/System/ChunkUploaderService.cs
@@ -0,0 +1,16 @@
+namespace I2R.Storage.Api.Services.System;
+
+public class ChunkUploaderService
+{
+ private readonly ILogger<ChunkUploaderService> _logger;
+ private readonly AppDatabase _database;
+
+ public ChunkUploaderService(AppDatabase database, ILogger<ChunkUploaderService> logger) {
+ _database = database;
+ _logger = logger;
+ }
+
+ public async Task<Guid> StartUploadSession() {
+ return default;
+ }
+} \ No newline at end of file
diff --git a/code/api/Services/System/DefaultResourceService.cs b/code/api/Services/System/DefaultResourceService.cs
new file mode 100644
index 0000000..5198432
--- /dev/null
+++ b/code/api/Services/System/DefaultResourceService.cs
@@ -0,0 +1,40 @@
+using I2R.Storage.Api.Services.Abstractions;
+using File = System.IO.File;
+
+namespace I2R.Storage.Api.Services.System;
+
+public class DefaultResourceService : IResourceService
+{
+ private readonly IConfiguration _configuration;
+
+ public DefaultResourceService(IConfiguration configuration) {
+ _configuration = configuration;
+ }
+
+ public async Task SetBlobAsync(StorageBlobId id, Stream stream, CancellationToken cancellationToken = default) {
+ await stream.CopyToAsync(File.OpenWrite(EnsureCreatedAndReturnBasedPath(id)), cancellationToken);
+ }
+
+ public Task<byte[]> GetBlobAsync(StorageBlobId id, CancellationToken cancellationToken = default) {
+ return File.ReadAllBytesAsync(EnsureCreatedAndReturnBasedPath(id), cancellationToken);
+ }
+
+ public Task RemoveBlobAsync(StorageBlobId id, CancellationToken cancellationToken = default) {
+ File.Delete(EnsureCreatedAndReturnBasedPath(id));
+ return Task.CompletedTask;
+ }
+
+ public async Task SetBlobMetadataAsync(StorageBlobId id, Dictionary<string, string> metadata, CancellationToken cancellationToken = default) {
+ await File.OpenWrite(EnsureCreatedAndReturnBasedPath(id) + SystemConstants.MetadataFileExtension).WriteAsync(JsonSerializer.SerializeToUtf8Bytes(metadata), cancellationToken);
+ }
+
+ public async Task<Dictionary<string, string>> GetBlobMetadataAsync(StorageBlobId id, CancellationToken cancellationToken = default) {
+ return JsonSerializer.Deserialize<Dictionary<string, string>>(await File.ReadAllBytesAsync(EnsureCreatedAndReturnBasedPath(id) + SystemConstants.MetadataFileExtension, cancellationToken));
+ }
+
+ private string EnsureCreatedAndReturnBasedPath(StorageBlobId id) {
+ var withoutId = Path.Combine(Directory.GetCurrentDirectory(), _configuration.GetValue(AppEnvVariables.STORAGE_ROOT, "__FILESYSTEM__"), id.Bucket.ToString());
+ Directory.CreateDirectory(withoutId);
+ return Path.Combine(withoutId, id.Id.ToString());
+ }
+} \ No newline at end of file
diff --git a/code/api/Services/System/PermissionService.cs b/code/api/Services/System/PermissionService.cs
new file mode 100644
index 0000000..f55d8c1
--- /dev/null
+++ b/code/api/Services/System/PermissionService.cs
@@ -0,0 +1,19 @@
+namespace I2R.Storage.Api.Services.System;
+
+public class PermissionService
+{
+ private readonly AppDatabase _database;
+ private readonly ILogger<PermissionService> _logger;
+
+ public PermissionService(ILogger<PermissionService> logger, AppDatabase database) {
+ _logger = logger;
+ _database = database;
+ }
+
+ public async Task GrantAllOnFile(Guid fileId, Guid userId) { }
+ public async Task GrantAllOnFolder(Guid fileId, Guid userId) { }
+ public async Task UserCanReadFile(Guid fileId, Guid userId) { }
+ public async Task UserCanReadFolder(Guid folderId, Guid userId) { }
+ public async Task UserCanWriteFile(Guid fileId, Guid userId) { }
+ public async Task UserCanWriteFolder(Guid folderId, Guid userId) { }
+} \ No newline at end of file
diff --git a/code/api/Services/System/ShareService.cs b/code/api/Services/System/ShareService.cs
new file mode 100644
index 0000000..047e607
--- /dev/null
+++ b/code/api/Services/System/ShareService.cs
@@ -0,0 +1,6 @@
+namespace I2R.Storage.Api.Services.System;
+
+public class ShareService
+{
+
+} \ No newline at end of file
diff --git a/code/api/Services/System/StorageService.cs b/code/api/Services/System/StorageService.cs
index adbb883..328be2c 100644
--- a/code/api/Services/System/StorageService.cs
+++ b/code/api/Services/System/StorageService.cs
@@ -1,6 +1,3 @@
-using MR.AspNetCore.Pagination;
-using MR.EntityFrameworkCore.KeysetPagination;
-
namespace I2R.Storage.Api.Services.System;
public class StorageService
@@ -13,11 +10,39 @@ public class StorageService
_logger = logger;
}
- public async Task<KeysetPaginationResult<FileSystemEntry>> GetFileSystemEntriesAsync(Guid parent = default) {
- var keysetQuery = _database.Folders
+ public async Task<List<FileSystemEntry>> GetFileSystemEntriesAsync(Guid parent = default) {
+ var fileSystemEntriesContext = _database.Folders
.Include(c => c.Files)
.ConditionalWhere(() => parent != default, folder => folder.ParentId == parent)
- .Select(p => new FileSystemEntry() { });
- return default;
+ .Select(p => new FileSystemEntry() {
+ Id = p.Id,
+ Name = p.Name,
+ MimeType = SystemConstants.FolderMimeType,
+ SizeInBytes = -1,
+ Files = p.Files.Select(c => new FileSystemEntry() {
+ Id = c.Id,
+ Name = c.Name,
+ MimeType = c.MimeType,
+ SizeInBytes = c.SizeInBytes
+ }).ToList()
+ })
+ .KeysetPaginate(builder => { builder.Ascending(entry => entry.Name); });
+ var fileSystemEntries = await fileSystemEntriesContext.Query.ToListAsync();
+ fileSystemEntriesContext.EnsureCorrectOrder(fileSystemEntries);
+ return fileSystemEntries;
+ }
+
+ public async Task<FileSystemEntry> GetFileSystemEntryAsync(Guid folder) {
+ return _database.Folders.Include(c => c.Files).Select(c => new FileSystemEntry() {
+ Id = c.Id,
+ SizeInBytes = -1,
+ MimeType = SystemConstants.FolderMimeType,
+ Files = c.Files.Select(p => new FileSystemEntry() {
+ SizeInBytes = p.SizeInBytes,
+ MimeType = p.MimeType,
+ Id = p.Id,
+ Name = p.Name
+ }).ToList()
+ }).FirstOrDefault(c => c.Id == folder);
}
} \ No newline at end of file
diff --git a/code/api/Statics/AppEnvVariables.cs b/code/api/Statics/AppEnvVariables.cs
index c10914e..7563de2 100644
--- a/code/api/Statics/AppEnvVariables.cs
+++ b/code/api/Statics/AppEnvVariables.cs
@@ -3,4 +3,5 @@ namespace I2R.Storage.Api.Statics;
public class AppEnvVariables
{
public const string APPDB_CONNECTION_STRING = "APPDB_CONNECTION_STRING";
+ public const string STORAGE_ROOT = "STORAGE_ROOT";
} \ No newline at end of file
diff --git a/code/api/Statics/AppHeaders.cs b/code/api/Statics/AppHeaders.cs
new file mode 100644
index 0000000..a803264
--- /dev/null
+++ b/code/api/Statics/AppHeaders.cs
@@ -0,0 +1,6 @@
+namespace I2R.Storage.Api.Statics;
+
+public static class AppHeaders
+{
+ public const string IS_KNOWN_PROBLEM = "X-IsKnownProblem";
+} \ No newline at end of file
diff --git a/code/api/Statics/SystemConstants.cs b/code/api/Statics/SystemConstants.cs
new file mode 100644
index 0000000..5f6da55
--- /dev/null
+++ b/code/api/Statics/SystemConstants.cs
@@ -0,0 +1,7 @@
+namespace I2R.Storage.Api.Statics;
+
+public static class SystemConstants
+{
+ public const string FolderMimeType = "internal/folder";
+ public const string MetadataFileExtension = ".__meta";
+} \ No newline at end of file
diff --git a/code/api/Utilities/FileValidators.cs b/code/api/Utilities/FileValidators.cs
new file mode 100644
index 0000000..c58c380
--- /dev/null
+++ b/code/api/Utilities/FileValidators.cs
@@ -0,0 +1,11 @@
+using System.Text.RegularExpressions;
+
+namespace I2R.Storage.Api.Utilities;
+
+public static class FileValidators
+{
+ private static Regex _fileNameRegex => new(@"([^\\/]+)$");
+ private static Regex _folderNameRegex => new(@"^(\w+\.?)*\w+$");
+ public static bool IsValidFileName(this string value) => _fileNameRegex.IsMatch(value);
+ public static bool IsValidFolderName(this string value) => _folderNameRegex.IsMatch(value);
+} \ No newline at end of file
diff --git a/code/api/wwwroot/scripts/components/profile-modal.js b/code/api/wwwroot/scripts/components/profile-modal.js
index 91d4ed3..f4e90d5 100644
--- a/code/api/wwwroot/scripts/components/profile-modal.js
+++ b/code/api/wwwroot/scripts/components/profile-modal.js
@@ -1,17 +1,19 @@
class ProfileModal extends HTMLElement {
constructor() {
super();
- const sessionData = session.get();
- const root = create_element("div", {
- style: {
- padding: "5px",
- display: ""
- }
- }, [
- create_element("h4", {innerText: sessionData.username, style: {margin:0}}),
- create_element("p", {innerText: sessionData.role})
- ]);
- this.innerHTML = root.innerHTML;
+ retry(session.get, res => (res?.username?.length > 0 ?? false), 0).then(sessionData => {
+ const root = create_element("div", {
+ style: {
+ padding: "5px",
+ display: ""
+ }
+ }, [
+ create_element("h4", {innerText: sessionData.username, style: {margin: 0}}),
+ create_element("p", {innerText: sessionData.role}),
+ create_element("button", {innerText: "Log out", classList: ["do-logout"]})
+ ]);
+ this.innerHTML = root.innerHTML;
+ });
}
}
diff --git a/code/api/wwwroot/scripts/helpers.js b/code/api/wwwroot/scripts/helpers.js
index 411e55c..f215863 100644
--- a/code/api/wwwroot/scripts/helpers.js
+++ b/code/api/wwwroot/scripts/helpers.js
@@ -13,6 +13,41 @@ function move_focus(element) {
}
}
+function is_promise(p) {
+ if (typeof p === 'object' && typeof p.then === 'function') {
+ return true;
+ }
+
+ return false;
+}
+
+function sleep(ms) {
+ return new Promise(resolve => setTimeout(resolve, ms));
+}
+
+async function retry(action, predicateOrRetryCount = 4, cooldown = 250) {
+ let result = undefined;
+ const maxTries = 1000;
+ if (typeof predicateOrRetryCount === "number") {
+ let tries = 0;
+ if (predicateOrRetryCount > maxTries) throw new Error("Retry count is larger than limit: " + maxTries);
+ while (!result && tries < predicateOrRetryCount) {
+ await sleep(cooldown);
+ result = is_promise(action) ? await action() : action();
+ tries++;
+ }
+ }
+ if (typeof predicateOrRetryCount === "function") {
+ let tries = 0;
+ while (predicateOrRetryCount(result) === false && tries < maxTries) {
+ await sleep(cooldown);
+ result = is_promise(action) ? await action() : action();
+ tries++;
+ }
+ }
+ return result;
+}
+
function create_element_from_object(elementOptions) {
return create_element(elementOptions.name, elementOptions.properties, elementOptions.children);
}