diff options
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); } |
