diff --git a/src/Analysim.Infrastructure/Data/ApplicationDbContext.cs b/src/Analysim.Infrastructure/Data/ApplicationDbContext.cs index 545dadb..26d61bb 100644 --- a/src/Analysim.Infrastructure/Data/ApplicationDbContext.cs +++ b/src/Analysim.Infrastructure/Data/ApplicationDbContext.cs @@ -70,14 +70,16 @@ protected override void OnModelCreating(ModelBuilder modelBuilder) modelBuilder.Entity() .HasMany(p => p.BlobFiles) .WithOne(p => p.Project) - .HasForeignKey(p => p.ProjectID); + .HasForeignKey(p => p.ProjectID) + .OnDelete(DeleteBehavior.Cascade); // One To Many Relationship (Project -> Notebook) modelBuilder.Entity() .HasMany(p => p.Notebooks) .WithOne(p => p.Project) - .HasForeignKey(p => p.ProjectID); + .HasForeignKey(p => p.ProjectID) + .OnDelete(DeleteBehavior.Cascade); // One To Many Relationship (Notebook -> NotebookContent) modelBuilder.Entity() diff --git a/src/Analysim.Infrastructure/Infrastructure/Migrations/ApplicationDbContextModelSnapshot.cs b/src/Analysim.Infrastructure/Infrastructure/Migrations/ApplicationDbContextModelSnapshot.cs index d510654..985943c 100644 --- a/src/Analysim.Infrastructure/Infrastructure/Migrations/ApplicationDbContextModelSnapshot.cs +++ b/src/Analysim.Infrastructure/Infrastructure/Migrations/ApplicationDbContextModelSnapshot.cs @@ -424,21 +424,21 @@ protected override void BuildModel(ModelBuilder modelBuilder) new { Id = 1, - ConcurrencyStamp = "d630afa7-f2be-44a5-80aa-a1f5efe4d02e", + ConcurrencyStamp = "b2996c18-b35e-4568-b061-1c3c2027543b", Name = "Admin", NormalizedName = "ADMIN" }, new { Id = 2, - ConcurrencyStamp = "183110f8-c919-481c-b637-47e18cf3c099", + ConcurrencyStamp = "90f1b211-a965-4941-b973-71cd8a653cd7", Name = "Customer", NormalizedName = "CUSTOMER" }, new { Id = 3, - ConcurrencyStamp = "61b1a777-55bb-4d58-9105-fa1b1503fe70", + ConcurrencyStamp = "21584cfd-491d-4638-9753-5f9ec755712f", Name = "Moderator", NormalizedName = "MODERATOR" }); @@ -573,7 +573,8 @@ protected override void BuildModel(ModelBuilder modelBuilder) { b.HasOne("Core.Entities.Project", "Project") .WithMany("BlobFiles") - .HasForeignKey("ProjectID"); + .HasForeignKey("ProjectID") + .OnDelete(DeleteBehavior.Cascade); b.HasOne("Core.Entities.User", "User") .WithMany("BlobFiles") @@ -590,7 +591,8 @@ protected override void BuildModel(ModelBuilder modelBuilder) { b.HasOne("Core.Entities.Project", "Project") .WithMany("Notebooks") - .HasForeignKey("ProjectID"); + .HasForeignKey("ProjectID") + .OnDelete(DeleteBehavior.Cascade); b.Navigation("Project"); }); diff --git a/src/Analysim.Infrastructure/Migrations/20240825194033_cascadeProblemSolved.Designer.cs b/src/Analysim.Infrastructure/Migrations/20240825194033_cascadeProblemSolved.Designer.cs new file mode 100644 index 0000000..7ec66d1 --- /dev/null +++ b/src/Analysim.Infrastructure/Migrations/20240825194033_cascadeProblemSolved.Designer.cs @@ -0,0 +1,762 @@ +// +using System; +using Infrastructure.Data; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; + +#nullable disable + +namespace Infrastructure.Migrations +{ + [DbContext(typeof(ApplicationDbContext))] + [Migration("20240825194033_cascadeProblemSolved")] + partial class cascadeProblemSolved + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "6.0.6") + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + + modelBuilder.Entity("Analysim.Core.Entities.BlobFileContent", b => + { + b.Property("BlobFileID") + .HasColumnType("integer"); + + b.Property("Content") + .IsRequired() + .HasColumnType("bytea"); + + b.Property("DateCreated") + .HasColumnType("timestamp with time zone"); + + b.HasKey("BlobFileID"); + + b.ToTable("BlobFileContent"); + }); + + modelBuilder.Entity("Analysim.Core.Entities.NotebookContent", b => + { + b.Property("NotebookID") + .HasColumnType("integer") + .HasColumnOrder(1); + + b.Property("Version") + .HasColumnType("integer") + .HasColumnOrder(2); + + b.Property("Author") + .IsRequired() + .HasColumnType("text"); + + b.Property("Content") + .IsRequired() + .HasColumnType("bytea"); + + b.Property("DateCreated") + .HasColumnType("timestamp with time zone"); + + b.Property("Size") + .HasColumnType("integer"); + + b.HasKey("NotebookID", "Version"); + + b.ToTable("NotebookContent"); + }); + + modelBuilder.Entity("Core.Entities.BlobFile", b => + { + b.Property("BlobFileID") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("BlobFileID")); + + b.Property("Container") + .IsRequired() + .HasColumnType("text"); + + b.Property("DateCreated") + .HasColumnType("timestamp with time zone"); + + b.Property("Directory") + .IsRequired() + .HasColumnType("text"); + + b.Property("Extension") + .IsRequired() + .HasColumnType("text"); + + b.Property("LastModified") + .HasColumnType("timestamp with time zone"); + + b.Property("Name") + .IsRequired() + .HasColumnType("text"); + + b.Property("ProjectID") + .HasColumnType("integer"); + + b.Property("Size") + .HasColumnType("integer"); + + b.Property("Uri") + .IsRequired() + .HasColumnType("text"); + + b.Property("UserID") + .HasColumnType("integer"); + + b.HasKey("BlobFileID"); + + b.HasIndex("ProjectID"); + + b.HasIndex("UserID"); + + b.ToTable("BlobFiles"); + }); + + modelBuilder.Entity("Core.Entities.Notebook", b => + { + b.Property("NotebookID") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("NotebookID")); + + b.Property("Container") + .IsRequired() + .HasColumnType("text"); + + b.Property("DateCreated") + .HasColumnType("timestamp with time zone"); + + b.Property("Directory") + .IsRequired() + .HasColumnType("text"); + + b.Property("Extension") + .IsRequired() + .HasColumnType("text"); + + b.Property("LastModified") + .HasColumnType("timestamp with time zone"); + + b.Property("Name") + .IsRequired() + .HasColumnType("text"); + + b.Property("ProjectID") + .HasColumnType("integer"); + + b.Property("Size") + .HasColumnType("integer"); + + b.Property("Uri") + .IsRequired() + .HasColumnType("text"); + + b.Property("type") + .HasColumnType("text"); + + b.HasKey("NotebookID"); + + b.HasIndex("Directory"); + + b.HasIndex("ProjectID"); + + b.ToTable("Notebook"); + }); + + modelBuilder.Entity("Core.Entities.ObservableNotebookDataset", b => + { + b.Property("ID") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("ID")); + + b.Property("BlobFileID") + .HasColumnType("integer"); + + b.Property("NotebookID") + .HasColumnType("integer"); + + b.Property("datasetName") + .HasColumnType("text"); + + b.Property("datasetURL") + .HasColumnType("text"); + + b.HasKey("ID"); + + b.HasIndex("NotebookID"); + + b.ToTable("ObservableNotebookDataset"); + }); + + modelBuilder.Entity("Core.Entities.Project", b => + { + b.Property("ProjectID") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("ProjectID")); + + b.Property("DateCreated") + .HasColumnType("timestamp with time zone"); + + b.Property("Description") + .HasMaxLength(500) + .HasColumnType("character varying(500)"); + + b.Property("ForkedFromProjectID") + .HasColumnType("integer"); + + b.Property("LastUpdated") + .HasColumnType("timestamp with time zone"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("Route") + .IsRequired() + .HasColumnType("text"); + + b.Property("Visibility") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("ProjectID"); + + b.ToTable("Projects"); + }); + + modelBuilder.Entity("Core.Entities.ProjectTag", b => + { + b.Property("ProjectID") + .HasColumnType("integer") + .HasColumnOrder(2); + + b.Property("TagID") + .HasColumnType("integer") + .HasColumnOrder(1); + + b.HasKey("ProjectID", "TagID"); + + b.HasIndex("TagID"); + + b.ToTable("ProjectTags"); + }); + + modelBuilder.Entity("Core.Entities.ProjectUser", b => + { + b.Property("UserID") + .HasColumnType("integer") + .HasColumnOrder(1); + + b.Property("ProjectID") + .HasColumnType("integer") + .HasColumnOrder(2); + + b.Property("IsFollowing") + .HasColumnType("boolean"); + + b.Property("UserRole") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("UserID", "ProjectID"); + + b.HasIndex("ProjectID"); + + b.ToTable("ProjectUsers"); + }); + + modelBuilder.Entity("Core.Entities.Tag", b => + { + b.Property("TagID") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("TagID")); + + b.Property("Name") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("TagID"); + + b.ToTable("Tag"); + }); + + modelBuilder.Entity("Core.Entities.User", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("AccessFailedCount") + .HasColumnType("integer"); + + b.Property("Bio") + .HasColumnType("text"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("text"); + + b.Property("DateCreated") + .HasColumnType("timestamp with time zone"); + + b.Property("Email") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("EmailConfirmed") + .HasColumnType("boolean"); + + b.Property("LastOnline") + .HasColumnType("timestamp with time zone"); + + b.Property("LockoutEnabled") + .HasColumnType("boolean"); + + b.Property("LockoutEnd") + .HasColumnType("timestamp with time zone"); + + b.Property("NormalizedEmail") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("NormalizedUserName") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("PasswordHash") + .HasColumnType("text"); + + b.Property("PhoneNumber") + .HasColumnType("text"); + + b.Property("PhoneNumberConfirmed") + .HasColumnType("boolean"); + + b.Property("RegistrationSurvey") + .HasColumnType("text"); + + b.Property("SecurityStamp") + .HasColumnType("text"); + + b.Property("TwoFactorEnabled") + .HasColumnType("boolean"); + + b.Property("UserName") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedEmail") + .HasDatabaseName("EmailIndex"); + + b.HasIndex("NormalizedUserName") + .IsUnique() + .HasDatabaseName("UserNameIndex"); + + b.ToTable("AspNetUsers", (string)null); + }); + + modelBuilder.Entity("Core.Entities.UserUser", b => + { + b.Property("UserID") + .HasColumnType("integer") + .HasColumnOrder(1); + + b.Property("FollowerID") + .HasColumnType("integer") + .HasColumnOrder(2); + + b.HasKey("UserID", "FollowerID"); + + b.HasIndex("FollowerID"); + + b.ToTable("UserUsers"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("text"); + + b.Property("Name") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("NormalizedName") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedName") + .IsUnique() + .HasDatabaseName("RoleNameIndex"); + + b.ToTable("AspNetRoles", (string)null); + + b.HasData( + new + { + Id = 1, + ConcurrencyStamp = "b2996c18-b35e-4568-b061-1c3c2027543b", + Name = "Admin", + NormalizedName = "ADMIN" + }, + new + { + Id = 2, + ConcurrencyStamp = "90f1b211-a965-4941-b973-71cd8a653cd7", + Name = "Customer", + NormalizedName = "CUSTOMER" + }, + new + { + Id = 3, + ConcurrencyStamp = "21584cfd-491d-4638-9753-5f9ec755712f", + Name = "Moderator", + NormalizedName = "MODERATOR" + }); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("ClaimType") + .HasColumnType("text"); + + b.Property("ClaimValue") + .HasColumnType("text"); + + b.Property("RoleId") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetRoleClaims", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("ClaimType") + .HasColumnType("text"); + + b.Property("ClaimValue") + .HasColumnType("text"); + + b.Property("UserId") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserClaims", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.Property("LoginProvider") + .HasColumnType("text"); + + b.Property("ProviderKey") + .HasColumnType("text"); + + b.Property("ProviderDisplayName") + .HasColumnType("text"); + + b.Property("UserId") + .HasColumnType("integer"); + + b.HasKey("LoginProvider", "ProviderKey"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserLogins", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.Property("UserId") + .HasColumnType("integer"); + + b.Property("RoleId") + .HasColumnType("integer"); + + b.HasKey("UserId", "RoleId"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetUserRoles", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.Property("UserId") + .HasColumnType("integer"); + + b.Property("LoginProvider") + .HasColumnType("text"); + + b.Property("Name") + .HasColumnType("text"); + + b.Property("Value") + .HasColumnType("text"); + + b.HasKey("UserId", "LoginProvider", "Name"); + + b.ToTable("AspNetUserTokens", (string)null); + }); + + modelBuilder.Entity("Analysim.Core.Entities.BlobFileContent", b => + { + b.HasOne("Core.Entities.BlobFile", "BlobFile") + .WithMany("BlobFileContents") + .HasForeignKey("BlobFileID") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("BlobFile"); + }); + + modelBuilder.Entity("Analysim.Core.Entities.NotebookContent", b => + { + b.HasOne("Core.Entities.Notebook", "Notebook") + .WithMany("NotebookContents") + .HasForeignKey("NotebookID") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Notebook"); + }); + + modelBuilder.Entity("Core.Entities.BlobFile", b => + { + b.HasOne("Core.Entities.Project", "Project") + .WithMany("BlobFiles") + .HasForeignKey("ProjectID") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("Core.Entities.User", "User") + .WithMany("BlobFiles") + .HasForeignKey("UserID") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Project"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Core.Entities.Notebook", b => + { + b.HasOne("Core.Entities.Project", "Project") + .WithMany("Notebooks") + .HasForeignKey("ProjectID") + .OnDelete(DeleteBehavior.Cascade); + + b.Navigation("Project"); + }); + + modelBuilder.Entity("Core.Entities.ObservableNotebookDataset", b => + { + b.HasOne("Core.Entities.Notebook", "notebook") + .WithMany("observableNotebookDatasets") + .HasForeignKey("NotebookID") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("notebook"); + }); + + modelBuilder.Entity("Core.Entities.ProjectTag", b => + { + b.HasOne("Core.Entities.Project", "Project") + .WithMany("ProjectTags") + .HasForeignKey("ProjectID") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Core.Entities.Tag", "Tag") + .WithMany("ProjectTags") + .HasForeignKey("TagID") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Project"); + + b.Navigation("Tag"); + }); + + modelBuilder.Entity("Core.Entities.ProjectUser", b => + { + b.HasOne("Core.Entities.Project", "Project") + .WithMany("ProjectUsers") + .HasForeignKey("ProjectID") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Core.Entities.User", "User") + .WithMany("ProjectUsers") + .HasForeignKey("UserID") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Project"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Core.Entities.UserUser", b => + { + b.HasOne("Core.Entities.User", "Follower") + .WithMany("Following") + .HasForeignKey("FollowerID") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Core.Entities.User", "User") + .WithMany("Followers") + .HasForeignKey("UserID") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Follower"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.HasOne("Core.Entities.User", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.HasOne("Core.Entities.User", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Core.Entities.User", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.HasOne("Core.Entities.User", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Core.Entities.BlobFile", b => + { + b.Navigation("BlobFileContents"); + }); + + modelBuilder.Entity("Core.Entities.Notebook", b => + { + b.Navigation("NotebookContents"); + + b.Navigation("observableNotebookDatasets"); + }); + + modelBuilder.Entity("Core.Entities.Project", b => + { + b.Navigation("BlobFiles"); + + b.Navigation("Notebooks"); + + b.Navigation("ProjectTags"); + + b.Navigation("ProjectUsers"); + }); + + modelBuilder.Entity("Core.Entities.Tag", b => + { + b.Navigation("ProjectTags"); + }); + + modelBuilder.Entity("Core.Entities.User", b => + { + b.Navigation("BlobFiles"); + + b.Navigation("Followers"); + + b.Navigation("Following"); + + b.Navigation("ProjectUsers"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/Analysim.Infrastructure/Migrations/20240825194033_cascadeProblemSolved.cs b/src/Analysim.Infrastructure/Migrations/20240825194033_cascadeProblemSolved.cs new file mode 100644 index 0000000..8aff2b5 --- /dev/null +++ b/src/Analysim.Infrastructure/Migrations/20240825194033_cascadeProblemSolved.cs @@ -0,0 +1,103 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace Infrastructure.Migrations +{ + public partial class cascadeProblemSolved : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropForeignKey( + name: "FK_BlobFiles_Projects_ProjectID", + table: "BlobFiles"); + + migrationBuilder.DropForeignKey( + name: "FK_Notebook_Projects_ProjectID", + table: "Notebook"); + + migrationBuilder.UpdateData( + table: "AspNetRoles", + keyColumn: "Id", + keyValue: 1, + column: "ConcurrencyStamp", + value: "b2996c18-b35e-4568-b061-1c3c2027543b"); + + migrationBuilder.UpdateData( + table: "AspNetRoles", + keyColumn: "Id", + keyValue: 2, + column: "ConcurrencyStamp", + value: "90f1b211-a965-4941-b973-71cd8a653cd7"); + + migrationBuilder.UpdateData( + table: "AspNetRoles", + keyColumn: "Id", + keyValue: 3, + column: "ConcurrencyStamp", + value: "21584cfd-491d-4638-9753-5f9ec755712f"); + + migrationBuilder.AddForeignKey( + name: "FK_BlobFiles_Projects_ProjectID", + table: "BlobFiles", + column: "ProjectID", + principalTable: "Projects", + principalColumn: "ProjectID", + onDelete: ReferentialAction.Cascade); + + migrationBuilder.AddForeignKey( + name: "FK_Notebook_Projects_ProjectID", + table: "Notebook", + column: "ProjectID", + principalTable: "Projects", + principalColumn: "ProjectID", + onDelete: ReferentialAction.Cascade); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropForeignKey( + name: "FK_BlobFiles_Projects_ProjectID", + table: "BlobFiles"); + + migrationBuilder.DropForeignKey( + name: "FK_Notebook_Projects_ProjectID", + table: "Notebook"); + + migrationBuilder.UpdateData( + table: "AspNetRoles", + keyColumn: "Id", + keyValue: 1, + column: "ConcurrencyStamp", + value: "d630afa7-f2be-44a5-80aa-a1f5efe4d02e"); + + migrationBuilder.UpdateData( + table: "AspNetRoles", + keyColumn: "Id", + keyValue: 2, + column: "ConcurrencyStamp", + value: "183110f8-c919-481c-b637-47e18cf3c099"); + + migrationBuilder.UpdateData( + table: "AspNetRoles", + keyColumn: "Id", + keyValue: 3, + column: "ConcurrencyStamp", + value: "61b1a777-55bb-4d58-9105-fa1b1503fe70"); + + migrationBuilder.AddForeignKey( + name: "FK_BlobFiles_Projects_ProjectID", + table: "BlobFiles", + column: "ProjectID", + principalTable: "Projects", + principalColumn: "ProjectID"); + + migrationBuilder.AddForeignKey( + name: "FK_Notebook_Projects_ProjectID", + table: "Notebook", + column: "ProjectID", + principalTable: "Projects", + principalColumn: "ProjectID"); + } + } +} diff --git a/src/Analysim.Web/ClientApp/angular.json b/src/Analysim.Web/ClientApp/angular.json index 2fb7871..19c195a 100644 --- a/src/Analysim.Web/ClientApp/angular.json +++ b/src/Analysim.Web/ClientApp/angular.json @@ -31,6 +31,7 @@ "node_modules/bootstrap/dist/css/bootstrap.min.css", "node_modules/ngx-toastr/toastr.css", "node_modules/@fortawesome/fontawesome-free/css/all.min.css", + "node_modules/highlight.js/styles/default.css", "src/styles.scss" ], "scripts": [ diff --git a/src/Analysim.Web/ClientApp/package-lock.json b/src/Analysim.Web/ClientApp/package-lock.json index 6182452..e82d36b 100644 --- a/src/Analysim.Web/ClientApp/package-lock.json +++ b/src/Analysim.Web/ClientApp/package-lock.json @@ -24,9 +24,12 @@ "d3": "^7.8.5", "file-saver": "^2.0.5", "fontawesome": "^5.6.3", + "highlight.js": "^11.10.0", "jquery": "^3.6.0", "jwt-decode": "^3.1.2", "localforage": "^1.10.0", + "marked": "^13.0.3", + "marked-highlight": "^2.1.3", "ng": "^0.0.0", "ngx-bootstrap": "^9.0.0", "ngx-toastr": "^15.0.0", @@ -48,12 +51,14 @@ "@types/jest": "^29.5.4", "@types/mocha": "^10.0.1", "@types/node": "^20.4.2", + "css-loader": "^7.1.2", "jasmine-core": "~4.1.0", "karma": "~6.3.0", "karma-chrome-launcher": "~3.1.0", "karma-coverage": "~2.2.0", "karma-jasmine": "~5.0.0", "karma-jasmine-html-reporter": "~1.7.0", + "style-loader": "^4.0.0", "typescript": "~4.7.2" } }, @@ -211,6 +216,32 @@ } } }, + "node_modules/@angular-devkit/build-angular/node_modules/css-loader": { + "version": "6.7.1", + "resolved": "https://registry.npmjs.org/css-loader/-/css-loader-6.7.1.tgz", + "integrity": "sha512-yB5CNFa14MbPJcomwNh3wLThtkZgcNyI2bNMRt8iE5Z8Vwl7f8vQXFAzn2HDOJvtDq2NTZBUGMSUNNyrv3/+cw==", + "dev": true, + "dependencies": { + "icss-utils": "^5.1.0", + "postcss": "^8.4.7", + "postcss-modules-extract-imports": "^3.0.0", + "postcss-modules-local-by-default": "^4.0.0", + "postcss-modules-scope": "^3.0.0", + "postcss-modules-values": "^4.0.0", + "postcss-value-parser": "^4.2.0", + "semver": "^7.3.5" + }, + "engines": { + "node": ">= 12.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^5.0.0" + } + }, "node_modules/@angular-devkit/build-angular/node_modules/rxjs": { "version": "6.6.7", "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.6.7.tgz", @@ -5022,29 +5053,78 @@ } }, "node_modules/css-loader": { - "version": "6.7.1", - "resolved": "https://registry.npmjs.org/css-loader/-/css-loader-6.7.1.tgz", - "integrity": "sha512-yB5CNFa14MbPJcomwNh3wLThtkZgcNyI2bNMRt8iE5Z8Vwl7f8vQXFAzn2HDOJvtDq2NTZBUGMSUNNyrv3/+cw==", + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/css-loader/-/css-loader-7.1.2.tgz", + "integrity": "sha512-6WvYYn7l/XEGN8Xu2vWFt9nVzrCn39vKyTEFf/ExEyoksJjjSZV/0/35XPlMbpnr6VGhZIUg5yJrL8tGfes/FA==", "dev": true, "dependencies": { "icss-utils": "^5.1.0", - "postcss": "^8.4.7", - "postcss-modules-extract-imports": "^3.0.0", - "postcss-modules-local-by-default": "^4.0.0", - "postcss-modules-scope": "^3.0.0", + "postcss": "^8.4.33", + "postcss-modules-extract-imports": "^3.1.0", + "postcss-modules-local-by-default": "^4.0.5", + "postcss-modules-scope": "^3.2.0", "postcss-modules-values": "^4.0.0", "postcss-value-parser": "^4.2.0", - "semver": "^7.3.5" + "semver": "^7.5.4" }, "engines": { - "node": ">= 12.13.0" + "node": ">= 18.12.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/webpack" }, "peerDependencies": { - "webpack": "^5.0.0" + "@rspack/core": "0.x || 1.x", + "webpack": "^5.27.0" + }, + "peerDependenciesMeta": { + "@rspack/core": { + "optional": true + }, + "webpack": { + "optional": true + } + } + }, + "node_modules/css-loader/node_modules/postcss": { + "version": "8.4.40", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.40.tgz", + "integrity": "sha512-YF2kKIUzAofPMpfH6hOi2cGnv/HrUlfucspc7pDyvv7kGdqXrfj8SCl/t8owkEgKEuu8ZcRjSOxFxVLqwChZ2Q==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "dependencies": { + "nanoid": "^3.3.7", + "picocolors": "^1.0.1", + "source-map-js": "^1.2.0" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/css-loader/node_modules/semver": { + "version": "7.6.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", + "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" } }, "node_modules/css-prefers-color-scheme": { @@ -7314,6 +7394,14 @@ "integrity": "sha512-7kIufnBqdsBGcSZLPJwqHT3yhk1QTsSlFsVD3kx5ixH/AlgBs9yM1q6DPhXZ8f8gtdqgh7N7/5btRLpQsS2gHw==", "dev": true }, + "node_modules/highlight.js": { + "version": "11.10.0", + "resolved": "https://registry.npmjs.org/highlight.js/-/highlight.js-11.10.0.tgz", + "integrity": "sha512-SYVnVFswQER+zu1laSya563s+F8VDGt7o35d4utbamowvUNLLMovFqwCLSocpZTz3MgaSRA1IbqRWZv97dtErQ==", + "engines": { + "node": ">=12.0.0" + } + }, "node_modules/hosted-git-info": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-5.0.0.tgz", @@ -9424,14 +9512,22 @@ } }, "node_modules/marked": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/marked/-/marked-4.3.0.tgz", - "integrity": "sha512-PRsaiG84bK+AMvxziE/lCFss8juXjNaWzVbN5tXAm4XjeaS9NAHhop+PjQxz2A9h8Q4M/xGmzP8vqNwy6JeK0A==", + "version": "13.0.3", + "resolved": "https://registry.npmjs.org/marked/-/marked-13.0.3.tgz", + "integrity": "sha512-rqRix3/TWzE9rIoFGIn8JmsVfhiuC8VIQ8IdX5TfzmeBucdY05/0UlzKaw0eVtpcN/OdVFpBk7CjKGo9iHJ/zA==", "bin": { "marked": "bin/marked.js" }, "engines": { - "node": ">= 12" + "node": ">= 18" + } + }, + "node_modules/marked-highlight": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/marked-highlight/-/marked-highlight-2.1.3.tgz", + "integrity": "sha512-t35JWm2u8HanOJ+gSJBAYQ0Jgr3vy+gl7ORAXN8bSEQFHl5FYXH0A7YXVMrfhmKaSuBSy6LidXECn3U9Qv/dHA==", + "peerDependencies": { + "marked": ">=4 <14" } }, "node_modules/media-typer": { @@ -9737,10 +9833,16 @@ "dev": true }, "node_modules/nanoid": { - "version": "3.3.4", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.4.tgz", - "integrity": "sha512-MqBkQh/OHTS2egovRtLk45wEyNXwF+cokD+1YPf9u5VfJiRdAiRwB2froX5Co9Rh20xs4siNPm8naNotSD6RBw==", + "version": "3.3.7", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz", + "integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==", "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], "bin": { "nanoid": "bin/nanoid.cjs" }, @@ -10013,6 +10115,17 @@ "marked": "^4.0.10" } }, + "node_modules/notebookjs/node_modules/marked": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/marked/-/marked-4.3.0.tgz", + "integrity": "sha512-PRsaiG84bK+AMvxziE/lCFss8juXjNaWzVbN5tXAm4XjeaS9NAHhop+PjQxz2A9h8Q4M/xGmzP8vqNwy6JeK0A==", + "bin": { + "marked": "bin/marked.js" + }, + "engines": { + "node": ">= 12" + } + }, "node_modules/npm-bundled": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/npm-bundled/-/npm-bundled-1.1.2.tgz", @@ -10718,9 +10831,9 @@ } }, "node_modules/picocolors": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", - "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.1.tgz", + "integrity": "sha512-anP1Z8qwhkbmu7MFP5iTt+wQKXgwzf7zTyGlcdzabySa9vd0Xt392U0rVmz9poOaBj0uHJKyyo9/upk0HrEQew==", "dev": true }, "node_modules/picomatch": { @@ -11162,9 +11275,9 @@ } }, "node_modules/postcss-modules-extract-imports": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/postcss-modules-extract-imports/-/postcss-modules-extract-imports-3.0.0.tgz", - "integrity": "sha512-bdHleFnP3kZ4NYDhuGlVK+CMrQ/pqUm8bx/oGL93K6gVwiclvX5x0n76fYMKuIGKzlABOy13zsvqjb0f92TEXw==", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/postcss-modules-extract-imports/-/postcss-modules-extract-imports-3.1.0.tgz", + "integrity": "sha512-k3kNe0aNFQDAZGbin48pL2VNidTF0w4/eASDsxlyspobzU3wZQLOGj7L9gfRe0Jo9/4uud09DsjFNH7winGv8Q==", "dev": true, "engines": { "node": "^10 || ^12 || >= 14" @@ -11174,9 +11287,9 @@ } }, "node_modules/postcss-modules-local-by-default": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/postcss-modules-local-by-default/-/postcss-modules-local-by-default-4.0.0.tgz", - "integrity": "sha512-sT7ihtmGSF9yhm6ggikHdV0hlziDTX7oFoXtuVWeDd3hHObNkcHRo9V3yg7vCAY7cONyxJC/XXCmmiHHcvX7bQ==", + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/postcss-modules-local-by-default/-/postcss-modules-local-by-default-4.0.5.tgz", + "integrity": "sha512-6MieY7sIfTK0hYfafw1OMEG+2bg8Q1ocHCpoWLqOKj3JXlKu4G7btkmM/B7lFubYkYWmRSPLZi5chid63ZaZYw==", "dev": true, "dependencies": { "icss-utils": "^5.0.0", @@ -11191,9 +11304,9 @@ } }, "node_modules/postcss-modules-scope": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/postcss-modules-scope/-/postcss-modules-scope-3.0.0.tgz", - "integrity": "sha512-hncihwFA2yPath8oZ15PZqvWGkWf+XUfQgUGamS4LqoP1anQLOsOJw0vr7J7IwLpoY9fatA2qiGUGmuZL0Iqlg==", + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/postcss-modules-scope/-/postcss-modules-scope-3.2.0.tgz", + "integrity": "sha512-oq+g1ssrsZOsx9M96c5w8laRmvEu9C3adDSjI8oTcbfkrTE8hx/zfyobUoWIxaKPO8bt6S62kxpw5GqypEw1QQ==", "dev": true, "dependencies": { "postcss-selector-parser": "^6.0.4" @@ -12555,9 +12668,9 @@ } }, "node_modules/source-map-js": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz", - "integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==", + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.0.tgz", + "integrity": "sha512-itJW8lvSA0TXEphiRoawsCksnlf8SyvmFzIhltqAHluXd88pkCd+cXJVHTDwdCr0IzwptSm035IHQktUu1QUMg==", "dev": true, "engines": { "node": ">=0.10.0" @@ -12820,6 +12933,22 @@ "node": ">=6" } }, + "node_modules/style-loader": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/style-loader/-/style-loader-4.0.0.tgz", + "integrity": "sha512-1V4WqhhZZgjVAVJyt7TdDPZoPBPNHbekX4fWnCJL1yQukhCeZhJySUL+gL9y6sNdN95uEOS83Y55SqHcP7MzLA==", + "dev": true, + "engines": { + "node": ">= 18.12.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^5.27.0" + } + }, "node_modules/stylus": { "version": "0.57.0", "resolved": "https://registry.npmjs.org/stylus/-/stylus-0.57.0.tgz", @@ -14147,6 +14276,22 @@ "webpack-subresource-integrity": "5.1.0" }, "dependencies": { + "css-loader": { + "version": "6.7.1", + "resolved": "https://registry.npmjs.org/css-loader/-/css-loader-6.7.1.tgz", + "integrity": "sha512-yB5CNFa14MbPJcomwNh3wLThtkZgcNyI2bNMRt8iE5Z8Vwl7f8vQXFAzn2HDOJvtDq2NTZBUGMSUNNyrv3/+cw==", + "dev": true, + "requires": { + "icss-utils": "^5.1.0", + "postcss": "^8.4.7", + "postcss-modules-extract-imports": "^3.0.0", + "postcss-modules-local-by-default": "^4.0.0", + "postcss-modules-scope": "^3.0.0", + "postcss-modules-values": "^4.0.0", + "postcss-value-parser": "^4.2.0", + "semver": "^7.3.5" + } + }, "rxjs": { "version": "6.6.7", "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.6.7.tgz", @@ -15612,13 +15757,15 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/@csstools/postcss-unset-value/-/postcss-unset-value-1.0.1.tgz", "integrity": "sha512-f1G1WGDXEU/RN1TWAxBPQgQudtLnLQPyiWdtypkPC+mVYNKFKH/HYXSxH4MVNqwF8M0eDsoiU7HumJHCg/L/jg==", - "dev": true + "dev": true, + "requires": {} }, "@csstools/selector-specificity": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/@csstools/selector-specificity/-/selector-specificity-2.0.1.tgz", "integrity": "sha512-aG20vknL4/YjQF9BSV7ts4EWm/yrjagAN7OWBNmlbEOUiu0llj4OGrFoOKK3g2vey4/p2omKCoHrWtPxSwV3HA==", - "dev": true + "dev": true, + "requires": {} }, "@discoveryjs/json-ext": { "version": "0.5.7", @@ -15787,7 +15934,8 @@ "version": "14.0.3", "resolved": "https://registry.npmjs.org/@ngtools/webpack/-/webpack-14.0.3.tgz", "integrity": "sha512-PwvgCeY7mbijazovpA0ggeo81A3yzwOb8AfVD3yfGT15Z2qnEVyL+05Tj6ttRTngceF3gsERamFcB6lRKdcjdw==", - "dev": true + "dev": true, + "requires": {} }, "@nodelib/fs.scandir": { "version": "2.1.5", @@ -16689,7 +16837,8 @@ "version": "1.8.0", "resolved": "https://registry.npmjs.org/acorn-import-assertions/-/acorn-import-assertions-1.8.0.tgz", "integrity": "sha512-m7VZ3jwz4eK6A4Vtt8Ew1/mNbP24u0FhdyfA7fSvnJR6LMdfOYnmuIrrJAgrYfYJ10F/otaHTtrtrtmHdMNzEw==", - "dev": true + "dev": true, + "requires": {} }, "acorn-walk": { "version": "7.2.0", @@ -17089,7 +17238,8 @@ "bootstrap": { "version": "4.6.2", "resolved": "https://registry.npmjs.org/bootstrap/-/bootstrap-4.6.2.tgz", - "integrity": "sha512-51Bbp/Uxr9aTuy6ca/8FbFloBUJZLHwnhTcnjIeRn2suQWsWzcuJhGjKDB5eppVte/8oCdOL3VuwxvZDUggwGQ==" + "integrity": "sha512-51Bbp/Uxr9aTuy6ca/8FbFloBUJZLHwnhTcnjIeRn2suQWsWzcuJhGjKDB5eppVte/8oCdOL3VuwxvZDUggwGQ==", + "requires": {} }, "bootstrap-icons": { "version": "1.11.3", @@ -17738,26 +17888,46 @@ } }, "css-loader": { - "version": "6.7.1", - "resolved": "https://registry.npmjs.org/css-loader/-/css-loader-6.7.1.tgz", - "integrity": "sha512-yB5CNFa14MbPJcomwNh3wLThtkZgcNyI2bNMRt8iE5Z8Vwl7f8vQXFAzn2HDOJvtDq2NTZBUGMSUNNyrv3/+cw==", + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/css-loader/-/css-loader-7.1.2.tgz", + "integrity": "sha512-6WvYYn7l/XEGN8Xu2vWFt9nVzrCn39vKyTEFf/ExEyoksJjjSZV/0/35XPlMbpnr6VGhZIUg5yJrL8tGfes/FA==", "dev": true, "requires": { "icss-utils": "^5.1.0", - "postcss": "^8.4.7", - "postcss-modules-extract-imports": "^3.0.0", - "postcss-modules-local-by-default": "^4.0.0", - "postcss-modules-scope": "^3.0.0", + "postcss": "^8.4.33", + "postcss-modules-extract-imports": "^3.1.0", + "postcss-modules-local-by-default": "^4.0.5", + "postcss-modules-scope": "^3.2.0", "postcss-modules-values": "^4.0.0", "postcss-value-parser": "^4.2.0", - "semver": "^7.3.5" + "semver": "^7.5.4" + }, + "dependencies": { + "postcss": { + "version": "8.4.40", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.40.tgz", + "integrity": "sha512-YF2kKIUzAofPMpfH6hOi2cGnv/HrUlfucspc7pDyvv7kGdqXrfj8SCl/t8owkEgKEuu8ZcRjSOxFxVLqwChZ2Q==", + "dev": true, + "requires": { + "nanoid": "^3.3.7", + "picocolors": "^1.0.1", + "source-map-js": "^1.2.0" + } + }, + "semver": { + "version": "7.6.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", + "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", + "dev": true + } } }, "css-prefers-color-scheme": { "version": "6.0.3", "resolved": "https://registry.npmjs.org/css-prefers-color-scheme/-/css-prefers-color-scheme-6.0.3.tgz", "integrity": "sha512-4BqMbZksRkJQx2zAjrokiGMd07RqOa2IxIrrN10lyBe9xhn9DEvjUK79J6jkeiv9D9hQFXKb6g1jwU62jziJZA==", - "dev": true + "dev": true, + "requires": {} }, "css-select": { "version": "4.3.0", @@ -19337,6 +19507,11 @@ "integrity": "sha512-7kIufnBqdsBGcSZLPJwqHT3yhk1QTsSlFsVD3kx5ixH/AlgBs9yM1q6DPhXZ8f8gtdqgh7N7/5btRLpQsS2gHw==", "dev": true }, + "highlight.js": { + "version": "11.10.0", + "resolved": "https://registry.npmjs.org/highlight.js/-/highlight.js-11.10.0.tgz", + "integrity": "sha512-SYVnVFswQER+zu1laSya563s+F8VDGt7o35d4utbamowvUNLLMovFqwCLSocpZTz3MgaSRA1IbqRWZv97dtErQ==" + }, "hosted-git-info": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-5.0.0.tgz", @@ -19513,7 +19688,8 @@ "version": "5.1.0", "resolved": "https://registry.npmjs.org/icss-utils/-/icss-utils-5.1.0.tgz", "integrity": "sha512-soFhflCVWLfRNOPU3iv5Z9VUdT44xFRbzjLsEzSr5AQmgqPMTHdU3PMT1Cf1ssx8fLNJDA1juftYl+PUcv3MqA==", - "dev": true + "dev": true, + "requires": {} }, "ieee754": { "version": "1.2.1", @@ -20318,7 +20494,8 @@ "ws": { "version": "7.5.9", "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.9.tgz", - "integrity": "sha512-F+P9Jil7UiSKSkppIiD94dN07AwvFixvLIj1Og1Rl9GGMuNipJnV9JzjD6XuqmAeiswGvUmNLjr5cFuXwNS77Q==" + "integrity": "sha512-F+P9Jil7UiSKSkppIiD94dN07AwvFixvLIj1Og1Rl9GGMuNipJnV9JzjD6XuqmAeiswGvUmNLjr5cFuXwNS77Q==", + "requires": {} } } }, @@ -20542,7 +20719,8 @@ "version": "1.7.0", "resolved": "https://registry.npmjs.org/karma-jasmine-html-reporter/-/karma-jasmine-html-reporter-1.7.0.tgz", "integrity": "sha512-pzum1TL7j90DTE86eFt48/s12hqwQuiD+e5aXx2Dc9wDEn2LfGq6RoAxEZZjFiN0RDSCOnosEKRZWxbQ+iMpQQ==", - "dev": true + "dev": true, + "requires": {} }, "karma-source-map-support": { "version": "1.4.0", @@ -20932,9 +21110,15 @@ } }, "marked": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/marked/-/marked-4.3.0.tgz", - "integrity": "sha512-PRsaiG84bK+AMvxziE/lCFss8juXjNaWzVbN5tXAm4XjeaS9NAHhop+PjQxz2A9h8Q4M/xGmzP8vqNwy6JeK0A==" + "version": "13.0.3", + "resolved": "https://registry.npmjs.org/marked/-/marked-13.0.3.tgz", + "integrity": "sha512-rqRix3/TWzE9rIoFGIn8JmsVfhiuC8VIQ8IdX5TfzmeBucdY05/0UlzKaw0eVtpcN/OdVFpBk7CjKGo9iHJ/zA==" + }, + "marked-highlight": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/marked-highlight/-/marked-highlight-2.1.3.tgz", + "integrity": "sha512-t35JWm2u8HanOJ+gSJBAYQ0Jgr3vy+gl7ORAXN8bSEQFHl5FYXH0A7YXVMrfhmKaSuBSy6LidXECn3U9Qv/dHA==", + "requires": {} }, "media-typer": { "version": "0.3.0", @@ -21159,9 +21343,9 @@ "dev": true }, "nanoid": { - "version": "3.3.4", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.4.tgz", - "integrity": "sha512-MqBkQh/OHTS2egovRtLk45wEyNXwF+cokD+1YPf9u5VfJiRdAiRwB2froX5Co9Rh20xs4siNPm8naNotSD6RBw==", + "version": "3.3.7", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz", + "integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==", "dev": true }, "needle": { @@ -21362,6 +21546,13 @@ "dompurify": "^2.2.9", "jsdom": "^16.6.0", "marked": "^4.0.10" + }, + "dependencies": { + "marked": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/marked/-/marked-4.3.0.tgz", + "integrity": "sha512-PRsaiG84bK+AMvxziE/lCFss8juXjNaWzVbN5tXAm4XjeaS9NAHhop+PjQxz2A9h8Q4M/xGmzP8vqNwy6JeK0A==" + } } }, "npm-bundled": { @@ -21908,9 +22099,9 @@ "dev": true }, "picocolors": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", - "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.1.tgz", + "integrity": "sha512-anP1Z8qwhkbmu7MFP5iTt+wQKXgwzf7zTyGlcdzabySa9vd0Xt392U0rVmz9poOaBj0uHJKyyo9/upk0HrEQew==", "dev": true }, "picomatch": { @@ -22089,13 +22280,15 @@ "version": "5.0.0", "resolved": "https://registry.npmjs.org/postcss-font-variant/-/postcss-font-variant-5.0.0.tgz", "integrity": "sha512-1fmkBaCALD72CK2a9i468mA/+tr9/1cBxRRMXOUaZqO43oWPR5imcyPjXwuv7PXbCid4ndlP5zWhidQVVa3hmA==", - "dev": true + "dev": true, + "requires": {} }, "postcss-gap-properties": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/postcss-gap-properties/-/postcss-gap-properties-3.0.3.tgz", "integrity": "sha512-rPPZRLPmEKgLk/KlXMqRaNkYTUpE7YC+bOIQFN5xcu1Vp11Y4faIXv6/Jpft6FMnl6YRxZqDZG0qQOW80stzxQ==", - "dev": true + "dev": true, + "requires": {} }, "postcss-image-set-function": { "version": "4.0.6", @@ -22121,7 +22314,8 @@ "version": "4.0.1", "resolved": "https://registry.npmjs.org/postcss-initial/-/postcss-initial-4.0.1.tgz", "integrity": "sha512-0ueD7rPqX8Pn1xJIjay0AZeIuDoF+V+VvMt/uOnn+4ezUKhZM/NokDeP6DwMNyIoYByuN/94IQnt5FEkaN59xQ==", - "dev": true + "dev": true, + "requires": {} }, "postcss-lab-function": { "version": "4.2.0", @@ -22148,24 +22342,27 @@ "version": "5.0.4", "resolved": "https://registry.npmjs.org/postcss-logical/-/postcss-logical-5.0.4.tgz", "integrity": "sha512-RHXxplCeLh9VjinvMrZONq7im4wjWGlRJAqmAVLXyZaXwfDWP73/oq4NdIp+OZwhQUMj0zjqDfM5Fj7qby+B4g==", - "dev": true + "dev": true, + "requires": {} }, "postcss-media-minmax": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/postcss-media-minmax/-/postcss-media-minmax-5.0.0.tgz", "integrity": "sha512-yDUvFf9QdFZTuCUg0g0uNSHVlJ5X1lSzDZjPSFaiCWvjgsvu8vEVxtahPrLMinIDEEGnx6cBe6iqdx5YWz08wQ==", - "dev": true + "dev": true, + "requires": {} }, "postcss-modules-extract-imports": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/postcss-modules-extract-imports/-/postcss-modules-extract-imports-3.0.0.tgz", - "integrity": "sha512-bdHleFnP3kZ4NYDhuGlVK+CMrQ/pqUm8bx/oGL93K6gVwiclvX5x0n76fYMKuIGKzlABOy13zsvqjb0f92TEXw==", - "dev": true + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/postcss-modules-extract-imports/-/postcss-modules-extract-imports-3.1.0.tgz", + "integrity": "sha512-k3kNe0aNFQDAZGbin48pL2VNidTF0w4/eASDsxlyspobzU3wZQLOGj7L9gfRe0Jo9/4uud09DsjFNH7winGv8Q==", + "dev": true, + "requires": {} }, "postcss-modules-local-by-default": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/postcss-modules-local-by-default/-/postcss-modules-local-by-default-4.0.0.tgz", - "integrity": "sha512-sT7ihtmGSF9yhm6ggikHdV0hlziDTX7oFoXtuVWeDd3hHObNkcHRo9V3yg7vCAY7cONyxJC/XXCmmiHHcvX7bQ==", + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/postcss-modules-local-by-default/-/postcss-modules-local-by-default-4.0.5.tgz", + "integrity": "sha512-6MieY7sIfTK0hYfafw1OMEG+2bg8Q1ocHCpoWLqOKj3JXlKu4G7btkmM/B7lFubYkYWmRSPLZi5chid63ZaZYw==", "dev": true, "requires": { "icss-utils": "^5.0.0", @@ -22174,9 +22371,9 @@ } }, "postcss-modules-scope": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/postcss-modules-scope/-/postcss-modules-scope-3.0.0.tgz", - "integrity": "sha512-hncihwFA2yPath8oZ15PZqvWGkWf+XUfQgUGamS4LqoP1anQLOsOJw0vr7J7IwLpoY9fatA2qiGUGmuZL0Iqlg==", + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/postcss-modules-scope/-/postcss-modules-scope-3.2.0.tgz", + "integrity": "sha512-oq+g1ssrsZOsx9M96c5w8laRmvEu9C3adDSjI8oTcbfkrTE8hx/zfyobUoWIxaKPO8bt6S62kxpw5GqypEw1QQ==", "dev": true, "requires": { "postcss-selector-parser": "^6.0.4" @@ -22211,13 +22408,15 @@ "version": "3.0.3", "resolved": "https://registry.npmjs.org/postcss-overflow-shorthand/-/postcss-overflow-shorthand-3.0.3.tgz", "integrity": "sha512-CxZwoWup9KXzQeeIxtgOciQ00tDtnylYIlJBBODqkgS/PU2jISuWOL/mYLHmZb9ZhZiCaNKsCRiLp22dZUtNsg==", - "dev": true + "dev": true, + "requires": {} }, "postcss-page-break": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/postcss-page-break/-/postcss-page-break-3.0.4.tgz", "integrity": "sha512-1JGu8oCjVXLa9q9rFTo4MbeeA5FMe00/9C7lN4va606Rdb+HkxXtXsmEDrIraQ11fGz/WvKWa8gMuCKkrXpTsQ==", - "dev": true + "dev": true, + "requires": {} }, "postcss-place": { "version": "7.0.4", @@ -22294,7 +22493,8 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/postcss-replace-overflow-wrap/-/postcss-replace-overflow-wrap-4.0.0.tgz", "integrity": "sha512-KmF7SBPphT4gPPcKZc7aDkweHiKEEO8cla/GjcBK+ckKxiZslIu3C4GCRW3DNfL0o7yW7kMQu9xlZ1kXRXLXtw==", - "dev": true + "dev": true, + "requires": {} }, "postcss-selector-not": { "version": "5.0.0", @@ -22855,7 +23055,8 @@ "version": "3.5.2", "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz", "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==", - "dev": true + "dev": true, + "requires": {} }, "json-schema-traverse": { "version": "0.4.1", @@ -23191,9 +23392,9 @@ "dev": true }, "source-map-js": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz", - "integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==", + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.0.tgz", + "integrity": "sha512-itJW8lvSA0TXEphiRoawsCksnlf8SyvmFzIhltqAHluXd88pkCd+cXJVHTDwdCr0IzwptSm035IHQktUu1QUMg==", "dev": true }, "source-map-loader": { @@ -23403,6 +23604,13 @@ "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", "dev": true }, + "style-loader": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/style-loader/-/style-loader-4.0.0.tgz", + "integrity": "sha512-1V4WqhhZZgjVAVJyt7TdDPZoPBPNHbekX4fWnCJL1yQukhCeZhJySUL+gL9y6sNdN95uEOS83Y55SqHcP7MzLA==", + "dev": true, + "requires": {} + }, "stylus": { "version": "0.57.0", "resolved": "https://registry.npmjs.org/stylus/-/stylus-0.57.0.tgz", @@ -23561,7 +23769,8 @@ "version": "3.5.2", "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz", "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==", - "dev": true + "dev": true, + "requires": {} }, "json-schema-traverse": { "version": "0.4.1", @@ -23981,7 +24190,8 @@ "version": "3.5.2", "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz", "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==", - "dev": true + "dev": true, + "requires": {} }, "json-schema-traverse": { "version": "0.4.1", @@ -24081,7 +24291,8 @@ "version": "8.8.0", "resolved": "https://registry.npmjs.org/ws/-/ws-8.8.0.tgz", "integrity": "sha512-JDAgSYQ1ksuwqfChJusw1LSJ8BizJ2e/vVu5Lxjq3YvNJNlROv1ui4i+c/kUUrPheBvQl4c5UbERhTwKa6QBJQ==", - "dev": true + "dev": true, + "requires": {} } } }, @@ -24222,7 +24433,8 @@ "version": "8.2.3", "resolved": "https://registry.npmjs.org/ws/-/ws-8.2.3.tgz", "integrity": "sha512-wBuoj1BDpC6ZQ1B7DWQBYVLphPWkm8i9Y0/3YdHjHKHiohOJ1ws+3OccDWtH+PoC9DZD5WOTrJvNbWvjS6JWaA==", - "dev": true + "dev": true, + "requires": {} }, "xml-name-validator": { "version": "3.0.0", diff --git a/src/Analysim.Web/ClientApp/package.json b/src/Analysim.Web/ClientApp/package.json index c6d9c61..31f13ba 100644 --- a/src/Analysim.Web/ClientApp/package.json +++ b/src/Analysim.Web/ClientApp/package.json @@ -26,9 +26,12 @@ "d3": "^7.8.5", "file-saver": "^2.0.5", "fontawesome": "^5.6.3", + "highlight.js": "^11.10.0", "jquery": "^3.6.0", "jwt-decode": "^3.1.2", "localforage": "^1.10.0", + "marked": "^13.0.3", + "marked-highlight": "^2.1.3", "ng": "^0.0.0", "ngx-bootstrap": "^9.0.0", "ngx-toastr": "^15.0.0", @@ -50,12 +53,14 @@ "@types/jest": "^29.5.4", "@types/mocha": "^10.0.1", "@types/node": "^20.4.2", + "css-loader": "^7.1.2", "jasmine-core": "~4.1.0", "karma": "~6.3.0", "karma-chrome-launcher": "~3.1.0", "karma-coverage": "~2.2.0", "karma-jasmine": "~5.0.0", "karma-jasmine-html-reporter": "~1.7.0", + "style-loader": "^4.0.0", "typescript": "~4.7.2" } } diff --git a/src/Analysim.Web/ClientApp/src/app/projects/project-file-explorer/project-file-explorer.component.ts b/src/Analysim.Web/ClientApp/src/app/projects/project-file-explorer/project-file-explorer.component.ts index 43f348b..3674a31 100644 --- a/src/Analysim.Web/ClientApp/src/app/projects/project-file-explorer/project-file-explorer.component.ts +++ b/src/Analysim.Web/ClientApp/src/app/projects/project-file-explorer/project-file-explorer.component.ts @@ -57,16 +57,19 @@ export class ProjectFileExplorerComponent implements OnInit { async ngOnInit() { this.dataBrowserURL = 'https://observablehq.com/embed/@sfsu/untitled?cell=*&dataset='; - this.currentDirectory = this.extractDirectory(this.router.url); + this.currentDirectory = this.extractDirectory(this.router.url).slice(5); this.setDirectoryFile(this.currentDirectory); - this.folders = "/ " + this.currentDirectory.split("/").join(" / "); + let directory = this.currentDirectory.endsWith('.csv') ? this.currentDirectory.split('/').slice(0, -1).join('/') : this.currentDirectory; + directory = directory.endsWith('/') ? directory.slice(0,-1) : directory; + this.folders = "/ " + directory.split("/").join(" / "); this.router.events.subscribe((ev) => { if (ev instanceof NavigationEnd) { this.currentDirectory = this.extractDirectory(this.router.url); this.setDirectoryFile(this.currentDirectory) + directory = this.currentDirectory.endsWith('.csv') ? this.currentDirectory.split('/').slice(0, -1).join('/') : this.currentDirectory; + directory = directory.endsWith('/') ? directory.slice(0,-1) : directory; } - // console.log(this.currentDirectory); - this.folders = "/ " + this.currentDirectory.split("/").join(" / "); + this.folders = "/ " + directory.split("/").join(" / "); // console.log(this.folders); }); } diff --git a/src/Analysim.Web/ClientApp/src/app/projects/project-overview/project-overview-view/project-content/modal-upload-notebook/modal-upload-notebook.component.ts b/src/Analysim.Web/ClientApp/src/app/projects/project-overview/project-overview-view/project-content/modal-upload-notebook/modal-upload-notebook.component.ts index 6eec719..57112dc 100644 --- a/src/Analysim.Web/ClientApp/src/app/projects/project-overview/project-overview-view/project-content/modal-upload-notebook/modal-upload-notebook.component.ts +++ b/src/Analysim.Web/ClientApp/src/app/projects/project-overview/project-overview-view/project-content/modal-upload-notebook/modal-upload-notebook.component.ts @@ -35,6 +35,7 @@ export class ModalUploadNotebookComponent implements OnInit { @Input() currentDirectory: string; ngOnInit(): void { + // console.log(this.project); this.showCreateNotebook = true; this.notebookName = new FormControl('', [Validators.required, Validators.minLength(1), Validators.maxLength(20)]); @@ -70,6 +71,13 @@ export class ModalUploadNotebookComponent implements OnInit { } addNotebook() { + + for(let notebook of this.project.notebooks){ + if(notebook.name.toLowerCase() === this.notebookName.value.toLowerCase() && notebook.extension === ".ipynb"){ + alert("Notebook name must be unique"); + return; + } + } if (this.showCreateNotebook) { this.notebook = { 'file': this.file, diff --git a/src/Analysim.Web/ClientApp/src/app/projects/project-overview/project-overview-view/project-content/project-content.component.ts b/src/Analysim.Web/ClientApp/src/app/projects/project-overview/project-overview-view/project-content/project-content.component.ts index b556bdc..0175968 100644 --- a/src/Analysim.Web/ClientApp/src/app/projects/project-overview/project-overview-view/project-content/project-content.component.ts +++ b/src/Analysim.Web/ClientApp/src/app/projects/project-overview/project-overview-view/project-content/project-content.component.ts @@ -96,7 +96,7 @@ export class ProjectContentComponent implements OnInit { displayNotebook(notebook: Notebook) { this.currentNotebook = notebook; - console.log(this.currentNotebook); + // console.log(this.currentNotebook); this.displayNotebookModalRef = this.modalService.show(this.displayNotebookModal, { backdrop: 'static', }); diff --git a/src/Analysim.Web/ClientApp/src/app/projects/project-overview/project-overview-view/project-content/project-notebook-item/project-notebook-item-display/project-notebook-item-display.component.ts b/src/Analysim.Web/ClientApp/src/app/projects/project-overview/project-overview-view/project-content/project-notebook-item/project-notebook-item-display/project-notebook-item-display.component.ts index f563bd3..7e4791f 100644 --- a/src/Analysim.Web/ClientApp/src/app/projects/project-overview/project-overview-view/project-content/project-notebook-item/project-notebook-item-display/project-notebook-item-display.component.ts +++ b/src/Analysim.Web/ClientApp/src/app/projects/project-overview/project-overview-view/project-content/project-notebook-item/project-notebook-item-display/project-notebook-item-display.component.ts @@ -57,7 +57,7 @@ export class ProjectNotebookItemDisplayComponent { console.log("some unknown error occurred , please open the notebook again."); alert("some unknown error occurred , please open the notebook again."); } - }, 25000); + }, 30000); } loadNotebook() { @@ -67,6 +67,7 @@ export class ProjectNotebookItemDisplayComponent { this.projectService.getNotebookFile(this.notebook, this.version) .subscribe(nbContent => { + // console.log("the notebook content is : ", nbContent); const notebookName = `${this.notebook.name}${this.notebook.extension}`; const notebookData = { content: nbContent, // The content of the notebook diff --git a/src/Analysim.Web/ClientApp/src/app/projects/project/project.component.html b/src/Analysim.Web/ClientApp/src/app/projects/project/project.component.html index 9b546bf..ed378fb 100644 --- a/src/Analysim.Web/ClientApp/src/app/projects/project/project.component.html +++ b/src/Analysim.Web/ClientApp/src/app/projects/project/project.component.html @@ -98,14 +98,20 @@

- +
+
+
+
+
{{isFull ? 'Read Less..' : 'Read More..'}}
+
Run Notebook
- + @@ -117,7 +123,7 @@

+ + + diff --git a/src/Analysim.Web/ClientApp/src/app/projects/project/project.component.scss b/src/Analysim.Web/ClientApp/src/app/projects/project/project.component.scss index 03e91e9..df315b5 100644 --- a/src/Analysim.Web/ClientApp/src/app/projects/project/project.component.scss +++ b/src/Analysim.Web/ClientApp/src/app/projects/project/project.component.scss @@ -34,7 +34,7 @@ // } -.project-overview-container { +:host ::ng-deep .project-overview-container { border: 1px solid rgba(0,0,0,.125); padding-top: 20px; @@ -42,6 +42,58 @@ padding: 0px 20px; } + .project-notebook-row{ + /* Jupyter Notebook-like styling */ + .notebook-readme{ + max-height: 100vh; + overflow: hidden; + -webkit-mask-image: linear-gradient(rgb(0, 0, 0) 70%, transparent); + mask-image: linear-gradient(rgb(0, 0, 0) 70%, transparent); + } + + .notebook-readme-full{ + margin-bottom: 4px; + } + +.notebook-cell pre code div { + background: #cfc8c8; + padding: 10px; +} +.notebook-cell pre{ + padding: 10px; +} +.notebook-cell img { + max-width: 100%; + height: auto; +} + +pre code.hljs{display:block;overflow-x:auto;padding:1em} +code.hljs{padding:3px 5px} +.hljs{background:#f3f3f3;color:#444} +.hljs-comment{color:#697070} +.hljs-punctuation,.hljs-tag{color:#444a} +.hljs-tag .hljs-attr,.hljs-tag .hljs-name{color:#444} +.hljs-attribute,.hljs-doctag,.hljs-keyword,.hljs-meta .hljs-keyword,.hljs-name,.hljs-selector-tag{font-weight:700}.hljs-deletion,.hljs-number,.hljs-quote,.hljs-selector-class,.hljs-selector-id,.hljs-string,.hljs-template-tag,.hljs-type{color:#800}.hljs-section,.hljs-title{color:#800;font-weight:700}.hljs-link,.hljs-operator,.hljs-regexp,.hljs-selector-attr,.hljs-selector-pseudo,.hljs-symbol,.hljs-template-variable,.hljs-variable{color:#ab5656}.hljs-literal{color:#695}.hljs-addition,.hljs-built_in,.hljs-bullet,.hljs-code{color:#397300}.hljs-meta{color:#1f7199}.hljs-meta .hljs-string{color:#38a}.hljs-emphasis{font-style:italic}.hljs-strong{font-weight:700} + } + + .buttons{ + display: flex; + gap: 20px; + width: max-content; + margin: auto; + } + .readme-button{ + margin: auto; + width: max-content; + padding: 8px; + color: white; + background-color: var(--button-background-color) !important; + border-radius: 5px; + // margin-top:-20px; + margin-bottom: 30px; + cursor: pointer; + } + .project-detail-row { .project-description { width: 70%; @@ -325,3 +377,4 @@ } } } + diff --git a/src/Analysim.Web/ClientApp/src/app/projects/project/project.component.ts b/src/Analysim.Web/ClientApp/src/app/projects/project/project.component.ts index 15220a9..41021fd 100644 --- a/src/Analysim.Web/ClientApp/src/app/projects/project/project.component.ts +++ b/src/Analysim.Web/ClientApp/src/app/projects/project/project.component.ts @@ -9,6 +9,9 @@ import { ProjectFileExplorerComponent } from '../project-file-explorer/project-f import { ProjectUser } from 'src/app/interfaces/project-user'; import { BsModalService, BsModalRef } from 'ngx-bootstrap/modal'; import { DomSanitizer, SafeUrl } from '@angular/platform-browser'; +import { marked, Marked } from 'marked'; +import hljs from 'highlight.js'; +import { Notebook } from 'src/app/interfaces/notebook'; @Component({ @@ -31,10 +34,12 @@ export class ProjectComponent implements OnInit { @ViewChild('deleteModal') deleteModal: TemplateRef @ViewChild('memberListModal') memberListModal: TemplateRef @ViewChildren(ProjectFileExplorerComponent) fileExplorer: ProjectFileExplorerComponent + @ViewChild('displayNotebookModal') displayNotebookModal: TemplateRef; forkModalRef: BsModalRef; deleteModalRef: BsModalRef; memberListModalRef: BsModalRef; + displayNotebookModalRef: BsModalRef; project: Project = null currentUser$: Observable = null @@ -43,9 +48,14 @@ export class ProjectComponent implements OnInit { fileDirectory: string forkedFrom: Project = null profileImageUrls: { [key: string]: SafeUrl } = {}; + notebookContent: any; + versions: number[] = []; + latestVersion: number = 1; + readmeNotebook: Notebook; + isFull: boolean = false; toggleMoreOption: boolean = false - toggleNotebookExpand: boolean = false + toggleNotebookExpand: boolean = true toggleView: string = "File" showFiles: boolean = false; @@ -69,7 +79,7 @@ export class ProjectComponent implements OnInit { this.projectService.getProjectByRoute(owner, projectname).subscribe( result => { this.project = result - //console.log("project is : ", this.project); + // console.log("project is : ", this.project); this.loadProfileImages(); this.forkedFrom = null if (this.project.forkedFromProjectID != 0) { @@ -79,10 +89,25 @@ export class ProjectComponent implements OnInit { } ) } - console.log(this.project.forkedFromProjectID) + // console.log(this.project.forkedFromProjectID) if (this.currentUser != null && this.project.projectUsers.find(x => x.userID == this.currentUser.id) != undefined) { this.projectUser = this.project.projectUsers.find(x => x.userID == this.currentUser.id) } + this.readmeNotebook = result.notebooks.filter((notebook) => notebook.name.toLowerCase() === "readme")[0]; + this.projectService.getNotebookVersions(result.notebooks.filter((notebook) => notebook.name.toLowerCase() === "readme")[0]).subscribe(versions => { + this.versions = versions; + //console.log("Versions: ", this.versions); + if (this.versions.length > 0) { + this.latestVersion = this.versions[0]; // Default to the latest version + } + // console.log("the loatest version : ", this.latestVersion); + }); + // console.log("readme file is : ", result.notebooks.filter((notebook) => notebook.name.toLowerCase() === "readme")[0]); + this.projectService.getNotebookFile(result.notebooks.filter((notebook) => notebook.name.toLowerCase() === "readme")[0], this.latestVersion).subscribe( + notebookJson => { + this.notebookContent = this.sanitizer.bypassSecurityTrustHtml(this.renderNotebook(notebookJson)); + // console.log("the notebook content is : ", this.notebookContent); + }); } ) @@ -126,6 +151,59 @@ export class ProjectComponent implements OnInit { } } + NavigateToNotebook(){ + this.displayNotebookModalRef = this.modalService.show(this.displayNotebookModal, { + backdrop: 'static', + }); + + this.router.navigate([this.router.url.split('/').slice(0, 4).join('/') + "/notebook/" + this.readmeNotebook.name], { + queryParams: { + isNotebook: true, + notebookId: this.readmeNotebook.notebookID, + version: this.latestVersion, + }, queryParamsHandling: 'merge' + }); + } + + toggleNotebook() { + this.isFull = !this.isFull; + } + + closeDisplayNotebookModal() { + this.displayNotebookModalRef.hide(); + this.router.navigate([this.router.url.split('/').slice(0,5).join('/')]) + } + + renderNotebook(notebookJson: any): string { + // Simple rendering of the notebook. Customize as needed. + let htmlContent = `
`; + for (const cell of notebookJson.cells) { + htmlContent += '
'; + if (cell.cell_type === 'markdown') { + htmlContent += marked(cell.source.join('')); + } else if (cell.cell_type === 'code') { + htmlContent += '
' + hljs.highlight(cell.source.join(''), {language: 'python'}).value + '
'; + if (cell.outputs) { + for (const output of cell.outputs) { + if (output.data && output.data['text/html']) { + htmlContent += output.data['text/html'].join(''); + } else if (output.data && output.data['image/png']) { + htmlContent += ``; + } else if (output.data && output.data['text/plain']) { + htmlContent += '
' + output.data['text/plain'].join('') + '
'; + } + else if (output.text) { + htmlContent += '
' + output.text.join('') + '
'; + } + } + } + } + htmlContent += '
'; + } + htmlContent += '
'; + return htmlContent; + } + loadProfileImages(): void { if (this.project && this.project.projectUsers) { this.project.projectUsers.forEach(member => { diff --git a/src/Analysim.Web/ClientApp/src/app/services/account.service.ts b/src/Analysim.Web/ClientApp/src/app/services/account.service.ts index c31c98b..c17ec02 100644 --- a/src/Analysim.Web/ClientApp/src/app/services/account.service.ts +++ b/src/Analysim.Web/ClientApp/src/app/services/account.service.ts @@ -1,5 +1,5 @@ import { Injectable } from '@angular/core'; -import { HttpClient, HttpParams } from '@angular/common/http'; +import { HttpClient, HttpHeaders, HttpParams } from '@angular/common/http'; import { BehaviorSubject, Observable, empty, throwError } from 'rxjs'; import { map, catchError } from 'rxjs/operators'; import { Router } from '@angular/router'; @@ -158,7 +158,10 @@ export class AccountService { body.append('userID', userID.toString()) body.append('followerID', followerID.toString()) - return this.http.post(this.urlFollow, body) + const headers = new HttpHeaders().set('Authorization', `Bearer ${localStorage.getItem('jwt')}`); + + + return this.http.post(this.urlFollow, body, {headers}) .pipe( map(body => { console.log(body.message) @@ -295,8 +298,9 @@ export class AccountService { let body = new FormData() body.append('file', file) body.append('userID', userID.toString()) + const headers = new HttpHeaders().set('Authorization', `Bearer ${localStorage.getItem('jwt')}`); - return this.http.post(this.urlUploadProfileImage, body).pipe( + return this.http.post(this.urlUploadProfileImage, body, {headers}).pipe( map(body => { console.log(body.message) return body.result @@ -311,8 +315,10 @@ export class AccountService { updateUser(bio: string, userID: number): Observable { let body = new FormData() body.append('bio', bio) + const headers = new HttpHeaders().set('Authorization', `Bearer ${localStorage.getItem('jwt')}`); + - return this.http.put(this.urlUpdateUser + userID, body) + return this.http.put(this.urlUpdateUser + userID, body, {headers}) .pipe( map(body => { console.log(body.message) @@ -326,7 +332,9 @@ export class AccountService { } unfollow(userID: number, followerID: number): Observable { - return this.http.delete(this.urlUnfollow + userID + '/' + followerID) + const headers = new HttpHeaders().set('Authorization', `Bearer ${localStorage.getItem('jwt')}`); + + return this.http.delete(this.urlUnfollow + userID + '/' + followerID, {headers}) .pipe( map(body => { console.log(body.message) @@ -340,7 +348,9 @@ export class AccountService { } deleteProfileImage(blobFileID: number): Observable { - return this.http.delete(this.urlDeleteProfileImage + blobFileID).pipe( + const headers = new HttpHeaders().set('Authorization', `Bearer ${localStorage.getItem('jwt')}`); + + return this.http.delete(this.urlDeleteProfileImage + blobFileID, {headers}).pipe( map(body => { console.log(body.message) return body.result diff --git a/src/Analysim.Web/ClientApp/src/app/services/project.service.ts b/src/Analysim.Web/ClientApp/src/app/services/project.service.ts index d356d0a..509ee12 100644 --- a/src/Analysim.Web/ClientApp/src/app/services/project.service.ts +++ b/src/Analysim.Web/ClientApp/src/app/services/project.service.ts @@ -1,5 +1,5 @@ import { Injectable } from '@angular/core'; -import { HttpClient, HttpParams } from '@angular/common/http'; +import { HttpClient, HttpHeaders, HttpParams } from '@angular/common/http'; import { Router } from '@angular/router'; import { map, catchError } from 'rxjs/operators'; import { Observable, empty, throwError } from 'rxjs'; @@ -13,6 +13,7 @@ import { User } from '../interfaces/user'; import { NotificationService } from './notification.service'; import { saveAs } from 'file-saver'; import { Notebook, NotebookFile, NotebookURL } from '../interfaces/notebook'; +import { getItem } from 'localforage'; @Injectable({ providedIn: 'root' @@ -242,10 +243,11 @@ export class ProjectService { body.append('name', projectName) body.append('visibility', visibility) body.append('description', description) - body.append('userid', currentUser.id.toString()) body.append('route', currentUser.userName + "/" + projectName) - return this.http.post(this.urlCreateProject, body) + const headers = new HttpHeaders().set('Authorization', `Bearer ${localStorage.getItem('jwt')}`); + + return this.http.post(this.urlCreateProject, body, {headers}) .pipe( map(body => { console.log(body.message) @@ -260,14 +262,16 @@ export class ProjectService { forkProject(userID: number, projectID: number, blobFilesID: number[]): Observable { let body = new FormData() - body.append('userID', userID.toString()) + // body.append('userID', userID.toString()) body.append('projectID', projectID.toString()) for (let i = 0; i < blobFilesID.length; i++) { body.append('BlobFilesID', blobFilesID[i].toString()) } body.getAll('BlobFilesID') - return this.http.post(this.urlForkProject, body) + const headers = new HttpHeaders().set('Authorization', `Bearer ${localStorage.getItem('jwt')}`); + + return this.http.post(this.urlForkProject, body, {headers}) .pipe( map(body => { console.log(body.message) @@ -285,7 +289,9 @@ export class ProjectService { body.append('userID', userID.toString()) body.append('projectID', projectID.toString()) - return this.http.post(this.urlForkProjectWithoutBlob, body) + const headers = new HttpHeaders().set('Authorization', `Bearer ${localStorage.getItem('jwt')}`); + + return this.http.post(this.urlForkProjectWithoutBlob, body, {headers}) .pipe( map(body => { console.log(body.message) @@ -307,7 +313,9 @@ export class ProjectService { body.append('userrole', userRole) body.append('isFollowing', isFollowing ? 'true' : 'false') - return this.http.post(this.urlAddUser, body) + const headers = new HttpHeaders().set('Authorization', `Bearer ${localStorage.getItem('jwt')}`); + + return this.http.post(this.urlAddUser, body, {headers}) .pipe( map(body => { console.log(body.message) @@ -325,7 +333,9 @@ export class ProjectService { body.append('projectid', projectID.toString()) body.append('tagname', tagName.toString()) - return this.http.post(this.urlAddTag, body) + const headers = new HttpHeaders().set('Authorization', `Bearer ${localStorage.getItem('jwt')}`); + + return this.http.post(this.urlAddTag, body, {headers}) .pipe( map(body => { console.log(body.message) @@ -342,10 +352,12 @@ export class ProjectService { let body = new FormData() body.append('file', file) body.append('directory', directory) - body.append('userID', userID.toString()) + // body.append('userID', userID.toString()) body.append('projectID', projectID.toString()) - return this.http.post(this.urlUploadFile, body).pipe( + const headers = new HttpHeaders().set('Authorization', `Bearer ${localStorage.getItem('jwt')}`); + + return this.http.post(this.urlUploadFile, body, {headers}).pipe( map(body => { console.log(body.result) return body.result @@ -364,7 +376,9 @@ export class ProjectService { body.append('ProjectID', notebook.projectID.toString()); body.append('directory', directory); - return this.http.post(this.urlUploadNotebook, body).pipe( + const headers = new HttpHeaders().set('Authorization', `Bearer ${localStorage.getItem('jwt')}`); + + return this.http.post(this.urlUploadNotebook, body, {headers}).pipe( map(body => { console.log(body.result) return body.result @@ -383,7 +397,9 @@ export class ProjectService { body.append('ProjectID', notebook.projectID.toString()); body.append('directory', directory); - return this.http.post(this.urlUploadNotebookNewVersion, body).pipe( + const headers = new HttpHeaders().set('Authorization', `Bearer ${localStorage.getItem('jwt')}`); + + return this.http.post(this.urlUploadNotebookNewVersion, body, {headers}).pipe( map(body => { console.log(body.message) return body.message @@ -404,7 +420,9 @@ export class ProjectService { body.append('observableNotebookDatasets', JSON.stringify(notebookURL.datasets)); body.append('directory', directory); - return this.http.post(this.urlUploadExistingNotebook, body).pipe( + const headers = new HttpHeaders().set('Authorization', `Bearer ${localStorage.getItem('jwt')}`); + + return this.http.post(this.urlUploadExistingNotebook, body, {headers}).pipe( map(body => { console.log(body.result) return body.result @@ -419,10 +437,12 @@ export class ProjectService { createFolder(directory: string, userID: number, projectID: number): Observable { let body = new FormData() body.append('directory', directory) - body.append('userID', userID.toString()) + // body.append('userID', userID.toString()) body.append('projectID', projectID.toString()) - return this.http.post(this.urlCreateFolder, body).pipe( + const headers = new HttpHeaders().set('Authorization', `Bearer ${localStorage.getItem('jwt')}`); + + return this.http.post(this.urlCreateFolder, body, {headers}).pipe( map(body => { console.log(body.message) return body.result @@ -440,7 +460,9 @@ export class ProjectService { body.append('folderName', folderName); body.append('projectID', projectID.toString()); - return this.http.post(this.urlCreateNotebookFolder, body).pipe( + const headers = new HttpHeaders().set('Authorization', `Bearer ${localStorage.getItem('jwt')}`); + + return this.http.post(this.urlCreateNotebookFolder, body, {headers}).pipe( map(body => { console.log(body.message) return body.result @@ -458,7 +480,9 @@ export class ProjectService { body.append('datasetName', `${file.name}${file.extension}`); body.append('blobFileID', file.blobFileID.toString()); - return this.http.post(this.urlAddDatasetToNotebook, body).pipe( + const headers = new HttpHeaders().set('Authorization', `Bearer ${localStorage.getItem('jwt')}`); + + return this.http.post(this.urlAddDatasetToNotebook, body, {headers}).pipe( map(body => { console.log(body.message) return body.result @@ -497,7 +521,9 @@ export class ProjectService { body.append('visibility', updateProject.visibility) body.append('description', updateProject.description) - return this.http.put(this.urlUpdateProject + updateProject.projectID, body) + const headers = new HttpHeaders().set('Authorization', `Bearer ${localStorage.getItem('jwt')}`); + + return this.http.put(this.urlUpdateProject + updateProject.projectID, body, {headers}) .pipe( map(body => { console.log(body.message) @@ -517,8 +543,9 @@ export class ProjectService { body.append('userrole', userRole.userRole) body.append('isFollowing', userRole.isFollowing ? 'true' : 'false') + const headers = new HttpHeaders().set('Authorization', `Bearer ${localStorage.getItem('jwt')}`); - return this.http.put(this.urlupdateUser, body) + return this.http.put(this.urlupdateUser, body, {headers}) .pipe( map(body => { console.log(body.message) @@ -538,7 +565,9 @@ export class ProjectService { body.append('blobFileID', file.blobFileID.toString()); //console.log("calling delete dataset from notebook api"); - return this.http.put(this.urlDeleteDatasetFromNotebook, body).pipe( + const headers = new HttpHeaders().set('Authorization', `Bearer ${localStorage.getItem('jwt')}`); + + return this.http.put(this.urlDeleteDatasetFromNotebook, body, {headers}).pipe( map(result => { console.log("after calling delete dataset from notebook : ", result.message) return result.result @@ -551,7 +580,12 @@ export class ProjectService { } deleteProject(projectID: number): Observable { - return this.http.delete(this.urlDeleteProject + projectID) + let body = new FormData() + // body.append('projectID', projectID.toString()) + + const headers = new HttpHeaders().set('Authorization', `Bearer ${localStorage.getItem('jwt')}`); + + return this.http.delete(this.urlDeleteProject + projectID, {headers}) .pipe( map(body => { console.log(body.message) @@ -565,8 +599,10 @@ export class ProjectService { } removeUser(projectID: number, userID: number): Observable { + const headers = new HttpHeaders().set('Authorization', `Bearer ${localStorage.getItem('jwt')}`); + - return this.http.delete(this.urlRemoveUser + projectID + '/' + userID) + return this.http.delete(this.urlRemoveUser + projectID + '/' + userID, {headers}) .pipe( map(body => { console.log(body.message) @@ -581,7 +617,10 @@ export class ProjectService { removeTag(projectID: number, tagID: number): Observable { - return this.http.delete(this.urlRemoveTag + projectID + '/' + tagID) + const headers = new HttpHeaders().set('Authorization', `Bearer ${localStorage.getItem('jwt')}`); + + + return this.http.delete(this.urlRemoveTag + projectID + '/' + tagID, {headers}) .pipe( map(body => { console.log(body.message) @@ -595,7 +634,10 @@ export class ProjectService { } deleteFile(blobFileID: number, isMember: boolean): Observable { - return this.http.delete(this.urlDeleteFile + blobFileID + '/' + isMember).pipe( + const headers = new HttpHeaders().set('Authorization', `Bearer ${localStorage.getItem('jwt')}`); + + return this.http.delete(this.urlDeleteFile + blobFileID + '/' + isMember, {headers}) + .pipe( map(body => { console.log(body.message) return body.result @@ -608,7 +650,9 @@ export class ProjectService { } deleteNotebook(notebookID: number, version: number, isMember: boolean): Observable { - return this.http.delete(this.urlDeleteNotebook + notebookID + '/' + version + '/' + isMember).pipe( + const headers = new HttpHeaders().set('Authorization', `Bearer ${localStorage.getItem('jwt')}`); + + return this.http.delete(this.urlDeleteNotebook + notebookID + '/' + version + '/' + isMember, {headers}).pipe( map(body => { console.log(body.message) return body.result diff --git a/src/Analysim.Web/Controllers/AccountController.cs b/src/Analysim.Web/Controllers/AccountController.cs index 70f4f8e..6a284c8 100644 --- a/src/Analysim.Web/Controllers/AccountController.cs +++ b/src/Analysim.Web/Controllers/AccountController.cs @@ -27,6 +27,7 @@ using Microsoft.AspNetCore.WebUtilities; using System.Data; using Analysim.Core.Entities; +using Microsoft.AspNetCore.Authorization; namespace Web.Controllers { @@ -213,12 +214,17 @@ public IActionResult Search([FromQuery(Name = "term")] List searchTerms) * Description: Create and return new UserUser * Response Status: 200 Ok, 404 Not Found */ + [Authorize] [HttpPost("[action]")] public async Task Follow([FromForm] AccountFollowVM formdata) { + var username = User.FindFirst(ClaimTypes.NameIdentifier)?.Value; + var user = await _dbContext.Users.SingleOrDefaultAsync(u => u.UserName == username); + if (user == null) return NotFound(new { message = "User Not Found" }); + // Find User - var user = await _dbContext.Users.FindAsync(formdata.UserID); - if (user == null) return NotFound(new { message = "User Not Found " + formdata.UserID }); + //var user = await _dbContext.Users.FindAsync(formdata.UserID); + //if (user == null) return NotFound(new { message = "User Not Found " + formdata.UserID }); // Find Follower var follower = await _dbContext.Users.FindAsync(formdata.FollowerID); @@ -227,7 +233,7 @@ public async Task Follow([FromForm] AccountFollowVM formdata) // Create Many To Many Connection var userFollower = new UserUser { - UserID = formdata.UserID, + UserID = user.Id, FollowerID = formdata.FollowerID }; @@ -671,18 +677,23 @@ public async Task Login([FromForm] AccountLoginVM formdata) * URL : /api/account/uploadprofileimage * Description: Upload File To Azure Storage */ + [Authorize] [HttpPost("[action]")] public async Task UploadProfileImage([FromForm] AccountUploadVM formdata) { try { + var username = User.FindFirst(ClaimTypes.NameIdentifier)?.Value; + var user = await _dbContext.Users.SingleOrDefaultAsync(u => u.UserName == username); + if (user == null) return NotFound(new { message = "User Not Found" }); + // Return Bad Request Status if (formdata.File == null) return BadRequest("Null File"); if (formdata.File.Length == 0) return BadRequest("Empty File"); // Find User - var user = await _dbContext.Users.FindAsync(formdata.UserID); - if (user == null) return NotFound(new { message = "User Not Found" }); + //var user = await _dbContext.Users.FindAsync(formdata.UserID); + //if (user == null) return NotFound(new { message = "User Not Found" }); //Create File Path With File //var filePath = user.UserName + "/profileImage" + Path.GetExtension(formdata.File.FileName); @@ -701,7 +712,7 @@ public async Task UploadProfileImage([FromForm] AccountUploadVM f var fileContent = memoryStream.ToArray(); // Check For Existing - var blobFile = _dbContext.BlobFiles.FirstOrDefault(x => x.UserID == formdata.UserID && x.Name == "profileImage"); + var blobFile = _dbContext.BlobFiles.FirstOrDefault(x => x.UserID == user.Id && x.Name == "profileImage"); if (blobFile != null) { blobFile.Extension = Path.GetExtension(formdata.File.FileName); @@ -737,7 +748,7 @@ public async Task UploadProfileImage([FromForm] AccountUploadVM f //content = fileContent, DateCreated = DateTime.UtcNow, LastModified = DateTime.UtcNow, - UserID = formdata.UserID + UserID = user.Id }; // Update Database with entry @@ -784,20 +795,25 @@ public async Task UploadProfileImage([FromForm] AccountUploadVM f * Description: Update Project * Response Status: 200 Ok, 404 Not Found */ + [Authorize] [HttpPut("[action]/{userID}")] - public IActionResult UpdateUser([FromRoute] int userID, [FromForm] AccountUpdateVM formdata) + public async Task UpdateUser([FromRoute] int userID, [FromForm] AccountUpdateVM formdata) { + var username = User.FindFirst(ClaimTypes.NameIdentifier)?.Value; + var user = await _dbContext.Users.SingleOrDefaultAsync(u => u.UserName == username); + if (user == null) return NotFound(new { message = "User Not Found" }); + // Check Model State if (!ModelState.IsValid) return BadRequest(ModelState); // Find User - var user = _dbContext.Users + var newuser = _dbContext.Users .Include(u => u.Followers) .Include(u => u.Following) .Include(u => u.ProjectUsers) .Include(u => u.BlobFiles) - .FirstOrDefault(u => u.Id == userID); - if (user == null) return NotFound(new { message = "User Not Found" }); + .FirstOrDefault(u => u.Id == user.Id); + if (newuser == null) return NotFound(new { message = "User Not Found" }); // Update Bio user.Bio = formdata.Bio; @@ -823,13 +839,18 @@ public IActionResult UpdateUser([FromRoute] int userID, [FromForm] AccountUpdate * Description: Have the follower unfollow the user * Response Status: 200 Ok, 404 Not Found */ + [Authorize] [HttpDelete("[action]/{userID}/{followerID}")] public async Task Unfollow([FromRoute] int userID, [FromRoute] int followerID) { - // Find User - var user = await _dbContext.Users.FindAsync(userID); + var username = User.FindFirst(ClaimTypes.NameIdentifier)?.Value; + var user = await _dbContext.Users.SingleOrDefaultAsync(u => u.UserName == username); if (user == null) return NotFound(new { message = "User Not Found" }); + // Find User + //var user = await _dbContext.Users.FindAsync(userID); + //if (user == null) return NotFound(new { message = "User Not Found" }); + // Find Follower var follower = await _dbContext.Users.FindAsync(followerID); if (follower == null) return NotFound(new { message = "Follower Not Found" }); @@ -955,11 +976,16 @@ public IActionResult GetFollowings([FromRoute] int userID) * Param : {fileID} * Description: Delete File From Azure Storage */ + [Authorize] [HttpDelete("[action]/{fileID}")] public async Task DeleteProfileImage([FromRoute] int fileID) { try { + var username = User.FindFirst(ClaimTypes.NameIdentifier)?.Value; + var user = await _dbContext.Users.SingleOrDefaultAsync(u => u.UserName == username); + if (user == null) return NotFound(new { message = "User Not Found" }); + // Find File var blobFile = await _dbContext.BlobFiles.FindAsync(fileID); if (blobFile == null) return NotFound(new { message = "File Not Found" }); diff --git a/src/Analysim.Web/Controllers/ProjectController.cs b/src/Analysim.Web/Controllers/ProjectController.cs index 154ead8..f26e8b5 100644 --- a/src/Analysim.Web/Controllers/ProjectController.cs +++ b/src/Analysim.Web/Controllers/ProjectController.cs @@ -27,6 +27,8 @@ using System.Web; using Newtonsoft.Json; using Analysim.Core.Entities; +using Microsoft.AspNetCore.Authorization; +using System.Security.Claims; namespace Web.Controllers { @@ -321,11 +323,13 @@ public async Task GetNotebookVersions([FromRoute] int notebookID) * Param : ProjectViewModel * Description: Fork Project */ + [Authorize] [HttpPost("[action]")] public async Task ForkProject([FromForm] ProjectForkVM formdata) { // Find User - var user = await _dbContext.Users.FindAsync(formdata.UserID); + var username = User.FindFirst(ClaimTypes.NameIdentifier)?.Value; + var user = await _dbContext.Users.SingleOrDefaultAsync(u => u.UserName == username); if (user == null) return NotFound(new { message = "User Not Found" }); // Find Project @@ -414,11 +418,13 @@ await _dbContext.BlobFiles.AddAsync( * Param : ProjectViewModel * Description: Fork Project Without Blob */ + [Authorize] [HttpPost("[action]")] public async Task ForkProjectWithoutBlob([FromForm] ProjectForkVM formdata) { // Find User - var user = await _dbContext.Users.FindAsync(formdata.UserID); + var username = User.FindFirst(ClaimTypes.NameIdentifier)?.Value; + var user = await _dbContext.Users.SingleOrDefaultAsync(u => u.UserName == username); if (user == null) return NotFound(new { message = "User Not Found" }); // Find Project @@ -478,11 +484,14 @@ await _dbContext.AddAsync( * Param : ProjectViewModel * Description: Create Project */ + [Authorize] [HttpPost("[action]")] - public async Task CreateProject([FromForm] ProjectVM formdata) - { + public async Task CreateProject([FromForm] ProjectVM formdata) + { // Find User - var user = await _dbContext.Users.FindAsync(formdata.UserID); + var username = User.FindFirst(ClaimTypes.NameIdentifier)?.Value; + var user = await _dbContext.Users.SingleOrDefaultAsync(u => u.UserName == username); + //var user = await _dbContext.Users.FindAsync(userId); if (user == null) return NotFound(new { message = "User Not Found" }); // Check If Project Already Exist @@ -563,6 +572,56 @@ await _dbContext.ProjectTags.AddRangeAsync( // Save Changes await _dbContext.SaveChangesAsync(); + //uploading a readme file + var readmeContent = new + { + cells = new[] + { + new + { + cell_type = "markdown", + metadata = new { }, + source = new[] { $"# Hello, this is Readme file of {formdata.Name}" } + } + }, + metadata = new { }, + nbformat = 4, + nbformat_minor = 2 + }; + + var readmeJson = System.Text.Json.JsonSerializer.Serialize(readmeContent); + var readmeFileContent = System.Text.Encoding.UTF8.GetBytes(readmeJson); + + Notebook readmeNotebook = new Notebook + { + Container = "notebook-" + newProject.Name.ToLower(), + Name = "readme", + Directory = "notebook/", + Extension = ".ipynb", + Uri = "", + Size = readmeFileContent.Length, + DateCreated = DateTime.UtcNow, + LastModified = DateTime.UtcNow, + ProjectID = newProject.ProjectID, + type = "new" + }; + + await _dbContext.Notebook.AddAsync(readmeNotebook); + await _dbContext.SaveChangesAsync(); + + NotebookContent readmeNotebookContent = new NotebookContent + { + NotebookID = readmeNotebook.NotebookID, + Version = 1, + Content = readmeFileContent, + Author = "hello", + Size = readmeFileContent.Length, + DateCreated = DateTime.UtcNow + }; + + await _dbContext.NotebookContent.AddAsync(readmeNotebookContent); + await _dbContext.SaveChangesAsync(); + // Return Ok Request return Ok(new { @@ -577,9 +636,21 @@ await _dbContext.ProjectTags.AddRangeAsync( * Param : ProjectUserViewModel * Description: Add User To Project */ + [Authorize] [HttpPost("[action]")] public async Task AddUser([FromForm] ProjectUserVM formdata) { + var username = User.FindFirst(ClaimTypes.NameIdentifier)?.Value; + var user = await _dbContext.Users.SingleOrDefaultAsync(u => u.UserName == username); + + var checkowner = _dbContext.Projects + .SingleOrDefault(p => p.ProjectUsers.Any(aup => + aup.User.Id == user.Id && + aup.Project.ProjectID == formdata.ProjectID && + aup.UserRole == "owner")); + + if (checkowner == null) return Unauthorized(new { message = "You are not the owner of the project" }); + // Find Tag In Database var projectUser = _dbContext.ProjectUsers.Find(formdata.UserID, formdata.ProjectID); @@ -634,9 +705,20 @@ public async Task AddUser([FromForm] ProjectUserVM formdata) * Description: Add Tag To Project */ [HttpPost("[action]")] - //[Authorize(Policy = "RequireLoggedIn")] + [Authorize] public async Task AddTag([FromForm] ProjectTagVM formdata) { + var username = User.FindFirst(ClaimTypes.NameIdentifier)?.Value; + var user = await _dbContext.Users.SingleOrDefaultAsync(u => u.UserName == username); + + var checkowner = _dbContext.Projects + .SingleOrDefault(p => p.ProjectUsers.Any(aup => + aup.User.Id == user.Id && + aup.Project.ProjectID == formdata.ProjectID && + aup.UserRole == "owner")); + + if (checkowner == null) return Unauthorized(new { message = "You are not the owner of the project" }); + // Find Tag In Database Tag tag = _dbContext.Tag.SingleOrDefault(t => t.Name == formdata.TagName); if (tag == null) @@ -683,11 +765,24 @@ public async Task AddTag([FromForm] ProjectTagVM formdata) * Param : FileUploadProjectViewModel * Description: Upload file to Azure Storage */ + [Authorize] [HttpPost("[action]")] public async Task UploadFile([FromForm] ProjectFileUploadVM formdata) { try { + var username = User.FindFirst(ClaimTypes.NameIdentifier)?.Value; + var user = await _dbContext.Users.SingleOrDefaultAsync(u => u.UserName == username); + if (user == null) return NotFound(new { message = "User Not Found" }); + + var checkOwner = _dbContext.Projects + .SingleOrDefault(p => p.ProjectUsers.Any(aup => + aup.User.Id == user.Id && + aup.Project.ProjectID == formdata.ProjectID && + aup.UserRole == "owner")); + + if (checkOwner == null) return Unauthorized(new { message = "You are not the owner of the project" }); + if (formdata.Directory == null) { formdata.Directory = ""; } // Return Bad Request Status @@ -695,8 +790,8 @@ public async Task UploadFile([FromForm] ProjectFileUploadVM formd if (formdata.File.Length == 0) return BadRequest("Empty File"); // Find User - var user = await _dbContext.Users.FindAsync(formdata.UserID); - if (user == null) return NotFound(new { message = "User Not Found" }); + //var user = await _dbContext.Users.FindAsync(formdata.UserID); + //if (user == null) return NotFound(new { message = "User Not Found" }); // Find Project var project = await _dbContext.Projects.FindAsync(formdata.ProjectID); @@ -778,12 +873,24 @@ public async Task UploadFile([FromForm] ProjectFileUploadVM formd * Param : NotebookUploadProjectViewModel * Description: Upload Notebook to Azure Storage */ + [Authorize] [HttpPost("[action]")] public async Task UploadNotebook([FromForm] ProjectNotebookUploadVM noteBookData) { try { + var username = User.FindFirst(ClaimTypes.NameIdentifier)?.Value; + var user = await _dbContext.Users.SingleOrDefaultAsync(u => u.UserName == username); + if (user == null) return NotFound(new { message = "User Not Found" }); + + var checkOwner = _dbContext.Projects + .SingleOrDefault(p => p.ProjectUsers.Any(aup => + aup.User.Id == user.Id && + aup.Project.ProjectID == noteBookData.ProjectID && + aup.UserRole == "owner")); + + if (checkOwner == null) return Unauthorized(new { message = "You are not the owner of the project" }); // Find Project var project = await _dbContext.Projects.FindAsync(noteBookData.ProjectID); @@ -856,12 +963,24 @@ public async Task UploadNotebook([FromForm] ProjectNotebookUpload * Param : NotebookUploadProjectViewModel * Description: Upload Notebook's new version */ + [Authorize] [HttpPost("[action]")] public async Task UploadNotebookNewVersion([FromForm] ProjectNotebookUploadVM noteBookData) { try { + var username = User.FindFirst(ClaimTypes.NameIdentifier)?.Value; + var user = await _dbContext.Users.SingleOrDefaultAsync(u => u.UserName == username); + if (user == null) return NotFound(new { message = "User Not Found" }); + + var checkOwner = _dbContext.Projects + .SingleOrDefault(p => p.ProjectUsers.Any(aup => + aup.User.Id == user.Id && + aup.Project.ProjectID == noteBookData.ProjectID && + aup.UserRole == "owner")); + + if (checkOwner == null) return Unauthorized(new { message = "You are not the owner of the project" }); // Find Project var project = await _dbContext.Projects.FindAsync(noteBookData.ProjectID); @@ -923,11 +1042,24 @@ static string GrabId(string line) return match.Value; } + [Authorize] [HttpPost("[action]")] public async Task UploadExistingNotebook([FromForm] ExistingProjectUploadVM noteBookData) { try { + var username = User.FindFirst(ClaimTypes.NameIdentifier)?.Value; + var user = await _dbContext.Users.SingleOrDefaultAsync(u => u.UserName == username); + if (user == null) return NotFound(new { message = "User Not Found" }); + + var checkOwner = _dbContext.Projects + .SingleOrDefault(p => p.ProjectUsers.Any(aup => + aup.User.Id == user.Id && + aup.Project.ProjectID ==noteBookData.ProjectID && + aup.UserRole == "owner")); + + if (checkOwner == null) return Unauthorized(new { message = "You are not the owner of the project" }); + var project = await _dbContext.Projects.FindAsync(noteBookData.ProjectID); if (project == null) return NotFound(new { message = "Project Not Found" }); @@ -1060,17 +1192,26 @@ public async Task UploadExistingNotebook([FromForm] ExistingProje * Param : FileUploadProjectViewModel * Description: Upload Folder To Azure Storage */ + [Authorize] [HttpPost("[action]")] public async Task CreateFolder([FromForm] FolderUploadProfileViewModel formdata) { try { - if (formdata.Directory == null) { formdata.Directory = ""; } - - // Find User - var user = await _dbContext.Users.FindAsync(formdata.UserID); + var username = User.FindFirst(ClaimTypes.NameIdentifier)?.Value; + var user = await _dbContext.Users.SingleOrDefaultAsync(u => u.UserName == username); if (user == null) return NotFound(new { message = "User Not Found" }); + var checkOwner = _dbContext.Projects + .SingleOrDefault(p => p.ProjectUsers.Any(aup => + aup.User.Id == user.Id && + aup.Project.ProjectID == formdata.ProjectID && + aup.UserRole == "owner")); + + if (checkOwner == null) return Unauthorized(new { message = "You are not the owner of the project" }); + + if (formdata.Directory == null) { formdata.Directory = ""; } + // Find Project var project = await _dbContext.Projects.FindAsync(formdata.ProjectID); if (project == null) return NotFound(new { message = "Project Not Found" }); @@ -1117,11 +1258,24 @@ public async Task CreateFolder([FromForm] FolderUploadProfileView } + [Authorize] [HttpPost("[action]")] public async Task CreateNotebookFolder([FromForm] FolderUploadProfileViewModel formdata) { try { + var username = User.FindFirst(ClaimTypes.NameIdentifier)?.Value; + var user = await _dbContext.Users.SingleOrDefaultAsync(u => u.UserName == username); + if (user == null) return NotFound(new { message = "User Not Found" }); + + var checkOwner = _dbContext.Projects + .SingleOrDefault(p => p.ProjectUsers.Any(aup => + aup.User.Id == user.Id && + aup.Project.ProjectID == formdata.ProjectID && + aup.UserRole == "owner")); + + if (checkOwner == null) return Unauthorized(new { message = "You are not the owner of the project" }); + if (formdata.Directory == null) { formdata.Directory = ""; } // Find Project @@ -1176,11 +1330,26 @@ public async Task CreateNotebookFolder([FromForm] FolderUploadPro * Param : * Description: add dataset to the notebook */ + [Authorize] [HttpPost("[action]")] public async Task AddDatasetToNotebook([FromForm] int notebookID, [FromForm] int blobFileID, [FromForm] string datasetName) { try { + var username = User.FindFirst(ClaimTypes.NameIdentifier)?.Value; + var user = await _dbContext.Users.SingleOrDefaultAsync(u => u.UserName == username); + if (user == null) return NotFound(new { message = "User Not Found" }); + + var notebook = _dbContext.Notebook.SingleOrDefault(n => n.NotebookID == notebookID); + + var checkOwner = _dbContext.Projects + .SingleOrDefault(p => p.ProjectUsers.Any(aup => + aup.User.Id == user.Id && + aup.Project.ProjectID == notebook.ProjectID && + aup.UserRole == "owner")); + + if (checkOwner == null) return Unauthorized(new { message = "You are not the owner of the project" }); + var dataset = await _dbContext.ObservableNotebookDataset .FirstOrDefaultAsync(d => d.NotebookID == notebookID && d.BlobFileID == blobFileID); @@ -1221,9 +1390,22 @@ public async Task AddDatasetToNotebook([FromForm] int notebookID, * Param : {projectID}, ProjectViewModel * Description: Update Project */ + [Authorize] [HttpPut("[action]/{projectID}")] public async Task UpdateProject([FromRoute] int projectID, [FromForm] ProjectVM formdata) { + var username = User.FindFirst(ClaimTypes.NameIdentifier)?.Value; + var user = await _dbContext.Users.SingleOrDefaultAsync(u => u.UserName == username); + if (user == null) return NotFound(new { message = "User Not Found" }); + + var checkOwner = _dbContext.Projects + .SingleOrDefault(p => p.ProjectUsers.Any(aup => + aup.User.Id == user.Id && + aup.Project.ProjectID == projectID && + aup.UserRole == "owner")); + + if (checkOwner == null) return Unauthorized(new { message = "You are not the owner of the project" }); + // Check Model State if (!ModelState.IsValid) return BadRequest(ModelState); @@ -1234,20 +1416,20 @@ public async Task UpdateProject([FromRoute] int projectID, [FromF // Check If Project Already Exist - var user = _dbContext.Users + var newuser = _dbContext.Users .SingleOrDefault(p => p.ProjectUsers.Any(aup => aup.User.Id == p.Id && aup.ProjectID == projectID && aup.Project.Name == formdata.Name && aup.UserRole == "owner")); - if (user == null) return NotFound(new { message = "User Not Found" }); + if (newuser == null) return NotFound(new { message = "User Not Found" }); // If the product was found project.Name = formdata.Name; project.Visibility = formdata.Visibility; project.Description = formdata.Description; project.LastUpdated = DateTime.UtcNow; - project.Route = user.UserName + "/" + formdata.Name; + project.Route = newuser.UserName + "/" + formdata.Name; // Set Entity State _dbContext.Entry(project).State = EntityState.Modified; @@ -1270,11 +1452,26 @@ public async Task UpdateProject([FromRoute] int projectID, [FromF * Param : * Description: delete dataset from notebook */ + [Authorize] [HttpPut("[action]")] public async Task DeleteDatasetFromNotebook([FromForm] int notebookID, [FromForm] int blobFileID) { try { + var username = User.FindFirst(ClaimTypes.NameIdentifier)?.Value; + var user = await _dbContext.Users.SingleOrDefaultAsync(u => u.UserName == username); + if (user == null) return NotFound(new { message = "User Not Found" }); + + var notebook = _dbContext.Notebook.SingleOrDefault(n => n.NotebookID == notebookID); + + var checkOwner = _dbContext.Projects + .SingleOrDefault(p => p.ProjectUsers.Any(aup => + aup.User.Id == user.Id && + aup.Project.ProjectID == notebook.ProjectID && + aup.UserRole == "owner")); + + if (checkOwner == null) return Unauthorized(new { message = "You are not the owner of the project" }); + var dataset = await _dbContext.ObservableNotebookDataset .FirstOrDefaultAsync(d => d.NotebookID == notebookID && d.BlobFileID == blobFileID); @@ -1303,9 +1500,21 @@ public async Task DeleteDatasetFromNotebook([FromForm] int notebo * Param : ProjectUserViewModel * Description: Update Project */ + [Authorize] [HttpPut("[action]")] public async Task UpdateUser([FromForm] ProjectUserVM formdata) { + var username = User.FindFirst(ClaimTypes.NameIdentifier)?.Value; + var user = await _dbContext.Users.SingleOrDefaultAsync(u => u.UserName == username); + if (user == null) return NotFound(new { message = "User Not Found" }); + + var checkOwner = _dbContext.Projects + .SingleOrDefault(p => p.ProjectUsers.Any(aup => + aup.User.Id == user.Id && + aup.Project.ProjectID == formdata.ProjectID && + aup.UserRole == "owner")); + + if (checkOwner == null) return Unauthorized(new { message = "You are not the owner of the project" }); // Find Many To Many var userRole = await _dbContext.ProjectUsers.FindAsync(formdata.UserID, formdata.ProjectID); @@ -1345,9 +1554,25 @@ public async Task UpdateUser([FromForm] ProjectUserVM formdata) }); } + [Authorize] [HttpPut("[action]")] public async Task RenameNotebook([FromForm] NotebookNameChangeVM notebookNameChangeVM) { + var username = User.FindFirst(ClaimTypes.NameIdentifier)?.Value; + var user = await _dbContext.Users.SingleOrDefaultAsync(u => u.UserName == username); + if (user == null) return NotFound(new { message = "User Not Found" }); + + var notebookE = await _dbContext.Notebook.FindAsync(notebookNameChangeVM.NotebookID); + if (notebookE == null) return NotFound(new { message = "File Not Found" }); + + var checkOwner = _dbContext.Projects + .SingleOrDefault(p => p.ProjectUsers.Any(aup => + aup.User.Id == user.Id && + aup.Project.ProjectID == notebookE.ProjectID && + aup.UserRole == "owner")); + + if (checkOwner == null) return Unauthorized(new { message = "You are not the owner of the project" }); + Notebook notebook = await _dbContext.Notebook.FindAsync(notebookNameChangeVM.NotebookID); if (notebook != null) @@ -1375,43 +1600,65 @@ public async Task RenameNotebook([FromForm] NotebookNameChangeVM [HttpDelete("[action]/{projectID}")] public async Task DeleteProject([FromRoute] int projectID) { - // Check Model State - if (!ModelState.IsValid) return BadRequest(ModelState); + try + { + var username = User.FindFirst(ClaimTypes.NameIdentifier)?.Value; + var user = await _dbContext.Users.SingleOrDefaultAsync(u => u.UserName == username); + if (user == null) return NotFound(new { message = "User Not Found" }); - // Find Project - var deleteProject = await _dbContext.Projects.FindAsync(projectID); - if (deleteProject == null) return NotFound(new { message = "Project Not Found" }); + var checkOwner = _dbContext.Projects + .SingleOrDefault(p => p.ProjectUsers.Any(aup => + aup.User.Id == user.Id && + aup.Project.ProjectID == projectID && + aup.UserRole == "owner")); - // Remove all users that follow the project - foreach (var user in deleteProject.ProjectUsers) - { - _dbContext.ProjectUsers.Remove(user); - } + if (checkOwner == null) return Unauthorized(new { message = "You are not the owner of the project" }); + // Check Model State + if (!ModelState.IsValid) return BadRequest(ModelState); - // Delete from Azure - //var containerClient = _blobServiceClient.GetBlobContainerClient(deleteProject.Name.ToLower()); - // await containerClient.DeleteBlobIfExistsAsync(deleteProject.Name.ToLower()); - //containerClient.DeleteIfExists(); + // Find Project + var deleteProject = await _dbContext.Projects.FindAsync(projectID); + if (deleteProject == null) return NotFound(new { message = "Project Not Found" }); - // get the project by project ID - var blobsResult = _dbContext.BlobFiles - .Where(p => p.ProjectID == projectID).ToList(); + // Remove all users that follow the project + foreach (var newuser in deleteProject.ProjectUsers) + { + _dbContext.ProjectUsers.Remove(newuser); + } - // delete blobFiles - _dbContext.BlobFiles.RemoveRange(blobsResult); + // Delete from Azure + //var containerClient = _blobServiceClient.GetBlobContainerClient(deleteProject.Name.ToLower()); + // await containerClient.DeleteBlobIfExistsAsync(deleteProject.Name.ToLower()); + //containerClient.DeleteIfExists(); - // remove the project - _dbContext.Projects.Remove(await _dbContext.Projects.FindAsync(projectID)); + // get the project by project ID + var blobsResult = _dbContext.BlobFiles + .Where(p => p.ProjectID == projectID).ToList(); - // Save Change - await _dbContext.SaveChangesAsync(); + // delete blobFiles + _dbContext.BlobFiles.RemoveRange(blobsResult); - // Return Ok Status - return Ok(new + // remove the project + _dbContext.Projects.Remove(await _dbContext.Projects.FindAsync(projectID)); + + // Save Change + await _dbContext.SaveChangesAsync(); + + // Return Ok Status + return Ok(new + { + result = deleteProject, + message = "Project successfully deleted." + }); + } + catch (Exception e) { - result = deleteProject, - message = "Project successfully deleted." - }); + // Return Bad Request If There Is Any Error + return BadRequest(new + { + error = e + }); + } } @@ -1421,9 +1668,22 @@ public async Task DeleteProject([FromRoute] int projectID) * Param : {projectID}/{userID} * Description: Delete User */ + [Authorize] [HttpDelete("[action]/{projectID}/{userID}")] public async Task RemoveUser([FromRoute] int projectID, int userID) { + var username = User.FindFirst(ClaimTypes.NameIdentifier)?.Value; + var user = await _dbContext.Users.SingleOrDefaultAsync(u => u.UserName == username); + if (user == null) return NotFound(new { message = "User Not Found" }); + + var checkOwner = _dbContext.Projects + .SingleOrDefault(p => p.ProjectUsers.Any(aup => + aup.User.Id == user.Id && + aup.Project.ProjectID == projectID && + aup.UserRole == "owner")); + + if (checkOwner == null) return Unauthorized(new { message = "You are not the owner of the project" }); + // Find Many To Many var projectUser = await _dbContext.ProjectUsers.FindAsync(userID, projectID); if (projectUser == null) return NotFound(new { message = "User Not Found" }); @@ -1448,9 +1708,22 @@ public async Task RemoveUser([FromRoute] int projectID, int userI * Param : {projectID}/{tagID} * Description: Delete User */ + [Authorize] [HttpDelete("[action]/{projectID}/{tagID}")] public async Task RemoveTag([FromRoute] int projectID, [FromRoute] int tagID) { + var username = User.FindFirst(ClaimTypes.NameIdentifier)?.Value; + var user = await _dbContext.Users.SingleOrDefaultAsync(u => u.UserName == username); + if (user == null) return NotFound(new { message = "User Not Found" }); + + var checkOwner = _dbContext.Projects + .SingleOrDefault(p => p.ProjectUsers.Any(aup => + aup.User.Id == user.Id && + aup.Project.ProjectID == projectID && + aup.UserRole == "owner")); + + if (checkOwner == null) return Unauthorized(new { message = "You are not the owner of the project" }); + // Find ProjectTag In Database ProjectTag projectTag = _dbContext.ProjectTags .Include(pt => pt.Tag) @@ -1494,18 +1767,28 @@ public async Task RemoveTag([FromRoute] int projectID, [FromRoute * Param : {fileID} * Description: Delete File From Azure Storage */ + [Authorize] [HttpDelete("[action]/{fileID}/{isMember}")] public async Task DeleteFile([FromRoute] int fileID, [FromRoute] bool isMember) { try { - if (isMember) - { + var username = User.FindFirst(ClaimTypes.NameIdentifier)?.Value; + var user = await _dbContext.Users.SingleOrDefaultAsync(u => u.UserName == username); + if (user == null) return NotFound(new { message = "User Not Found" }); - // Find File - var blobFile = await _dbContext.BlobFiles.FindAsync(fileID); - if (blobFile == null) return NotFound(new { message = "File Not Found" }); + var blobFile = await _dbContext.BlobFiles.FindAsync(fileID); + if (blobFile == null) return NotFound(new { message = "File Not Found" }); + + var checkOwner = _dbContext.Projects + .SingleOrDefault(p => p.ProjectUsers.Any(aup => + aup.User.Id == user.Id && + aup.Project.ProjectID == blobFile.ProjectID && + aup.UserRole == "owner")); + if (checkOwner == null) return Unauthorized(new { message = "You are not the owner of the project" }); + if (isMember) + { if (blobFile.Extension != ".$$") { var dataset = await _dbContext.ObservableNotebookDataset.FirstOrDefaultAsync(data => data.BlobFileID == blobFile.BlobFileID); @@ -1552,17 +1835,28 @@ public async Task DeleteFile([FromRoute] int fileID, [FromRoute] } + [Authorize] [HttpDelete("[action]/{notebookID}/{version}/{isMember}")] public async Task DeleteNotebook([FromRoute] int notebookID, [FromRoute] int version, [FromRoute] bool isMember) { try { + var username = User.FindFirst(ClaimTypes.NameIdentifier)?.Value; + var user = await _dbContext.Users.SingleOrDefaultAsync(u => u.UserName == username); + if (user == null) return NotFound(new { message = "User Not Found" }); + + var notebook = await _dbContext.Notebook.FindAsync(notebookID); + if (notebook == null) return NotFound(new { message = "File Not Found" }); + + var checkOwner = _dbContext.Projects + .SingleOrDefault(p => p.ProjectUsers.Any(aup => + aup.User.Id == user.Id && + aup.Project.ProjectID == notebook.ProjectID && + aup.UserRole == "owner")); + + if (checkOwner == null) return Unauthorized(new { message = "You are not the owner of the project" }); if (isMember) { - // Find File - var notebook = await _dbContext.Notebook.FindAsync(notebookID); - if (notebook == null) return NotFound(new { message = "Notebook Not Found" }); - //await _blobService.DeleteNotebookAsync(notebook); // Delete Blob Files From Database