diff --git a/backend/Squirrel.Core/Dockerfile b/backend/Squirrel.Core/Dockerfile index ad5638c11..c862e0cf9 100644 --- a/backend/Squirrel.Core/Dockerfile +++ b/backend/Squirrel.Core/Dockerfile @@ -21,6 +21,7 @@ RUN dotnet publish "Squirrel.Core.WebAPI.csproj" -c Release -o /app/publish FROM base AS final WORKDIR /app COPY Squirrel.Core/Squirrel.Core.WebAPI/Resources/SquirrelSetup.exe /app/Resources/ +COPY Squirrel.Core/Squirrel.Core.WebAPI/Resources/SquirrelSetup-osx-x64.zip /app/Resources/ COPY --from=publish /app/publish . ENV ASPNETCORE_URLS http://*:5050 ENTRYPOINT ["dotnet", "Squirrel.Core.WebAPI.dll"] diff --git a/backend/Squirrel.Core/Squirrel.Core.BLL/Interfaces/IBranchService.cs b/backend/Squirrel.Core/Squirrel.Core.BLL/Interfaces/IBranchService.cs index 6833b39ca..cd6417a12 100644 --- a/backend/Squirrel.Core/Squirrel.Core.BLL/Interfaces/IBranchService.cs +++ b/backend/Squirrel.Core/Squirrel.Core.BLL/Interfaces/IBranchService.cs @@ -12,4 +12,7 @@ public interface IBranchService Task<(BranchCommit?, bool)> FindHeadBranchCommitAsync(Branch branch); Task UpdateBranchAsync(int branchId, BranchUpdateDto branchUpdateDto); Task DeleteBranchAsync(int branchId); + Task MergeBranchAsync(int sourceId, int destId); + Task> GetCommitsFromBranchInternalAsync(int branchId, int destinationId); + Task> GetAllBranchDetailsAsync(int projectId, int selectedBranch); } \ No newline at end of file diff --git a/backend/Squirrel.Core/Squirrel.Core.BLL/MappingProfiles/BranchProfile.cs b/backend/Squirrel.Core/Squirrel.Core.BLL/MappingProfiles/BranchProfile.cs index 40d16f2fb..f88e8b121 100644 --- a/backend/Squirrel.Core/Squirrel.Core.BLL/MappingProfiles/BranchProfile.cs +++ b/backend/Squirrel.Core/Squirrel.Core.BLL/MappingProfiles/BranchProfile.cs @@ -10,5 +10,8 @@ public BranchProfile() { CreateMap()!.ReverseMap(); CreateMap()!.ReverseMap(); + CreateMap() + .ForMember(x => x.LastUpdatedBy, s => s.MapFrom(x => x.Author)) + .ForMember(x => x.UpdatedAt, s => s.MapFrom(x => x.CreatedAt)); } } \ No newline at end of file diff --git a/backend/Squirrel.Core/Squirrel.Core.BLL/Services/BranchService.cs b/backend/Squirrel.Core/Squirrel.Core.BLL/Services/BranchService.cs index 900b88985..79052d63b 100644 --- a/backend/Squirrel.Core/Squirrel.Core.BLL/Services/BranchService.cs +++ b/backend/Squirrel.Core/Squirrel.Core.BLL/Services/BranchService.cs @@ -12,14 +12,22 @@ namespace Squirrel.Core.BLL.Services; public sealed class BranchService : BaseService, IBranchService { - public BranchService(SquirrelCoreContext context, IMapper mapper) : base(context, mapper) + private readonly IUserIdGetter _userIdGetter; + private readonly IUserService _userService; + public BranchService(SquirrelCoreContext context, IMapper mapper, IUserIdGetter userIdGetter, IUserService userService) : base(context, mapper) { + _userIdGetter = userIdGetter; + _userService = userService; } public async Task AddBranchAsync(int projectId, BranchCreateDto branchDto) { + var userId = _userIdGetter.GetCurrentUserId(); + var user = await _userService.GetUserByIdInternalAsync(userId) ?? throw new EntityNotFoundException(nameof(User), userId); + var branch = _mapper.Map(branchDto); branch.ProjectId = projectId; + branch.CreatedBy = userId; branch.IsActive = true; await EnsureUniquenessAsync(branch.Name, projectId); @@ -45,6 +53,7 @@ public async Task AddBranchAsync(int projectId, BranchCreateDto branc { return headBranchCommit.IsHead ? (headBranchCommit, isHeadOnAnotherBranch) : throw new Exception("Last commit should be head!"); } + currentBranch = await _context.Branches .Include(x => x.BranchCommits) .ThenInclude(x => x.Commit) @@ -68,6 +77,63 @@ public async Task AddBranchAsync(int projectId, BranchCreateDto branc return (await FindHeadBranchCommitAsync(branch)).Item1?.Id; } + public async Task MergeBranchAsync(int sourceId, int destId) + { + var dest = await GetFullBranchEntityAsync(destId) ?? throw new EntityNotFoundException(nameof(Branch), destId); + + BranchCommit? lastCommit = default; + foreach (var commit in await GetCommitsFromBranchInternalAsync(sourceId, destId)) + { + var branchCommit = new BranchCommit + { + CommitId = commit.Id, + BranchId = dest.Id, + IsMerged = true + }; + dest.BranchCommits.Add(branchCommit); + lastCommit = branchCommit; + } + + if(lastCommit is not null) + { + lastCommit.IsHead = true; + var previousHead = await FindHeadBranchCommitAsync(dest); + if (previousHead.Item2 && previousHead.Item1 is not null) + { + previousHead.Item1.IsHead = false; + _context.BranchCommits.Update(previousHead.Item1); + } + } + + var entity = _context.Branches.Update(dest).Entity; + await _context.SaveChangesAsync(); + + return _mapper.Map(entity); + } + + public async Task> GetCommitsFromBranchInternalAsync(int branchId, int destinationId) + { + var source = await GetFullBranchEntityAsync(branchId) ?? throw new EntityNotFoundException(nameof(Branch), branchId); + var createdAt = source.CreatedAt; + var isOriginal = true; + var commits = new List(); + + while (source is not null && source.Id != destinationId) + { + foreach (var commit in source.Commits + .Where(x => isOriginal ? true : x.CreatedAt <= createdAt) + .OrderByDescending(x => x.CreatedAt)) + { + commits.Add(commit); + } + + isOriginal = false; + source = await GetFullBranchEntityAsync(source.ParentBranchId ?? default); + } + + return commits; + } + public BranchDto[] GetAllBranches(int projectId) { @@ -76,6 +142,28 @@ public BranchDto[] GetAllBranches(int projectId) return _mapper.Map(branches); } + public async Task> GetAllBranchDetailsAsync(int projectId, int selectedBranch) + { + var entities = _context.Branches + .Include(x => x.Author) + .Where(x => x.ProjectId == projectId); + var branches = _mapper.Map>(entities); + + var selectedBranchCommits = await GetCommitsFromBranchInternalAsync(selectedBranch, 0); + foreach (var branch in branches) + { + var branchCommits = await GetCommitsFromBranchInternalAsync(branch.Id, 0); + var aheadCommits = branchCommits.Where(x => !selectedBranchCommits.Any(y => y.Id == x.Id)); + + var behindCommits = selectedBranchCommits.Where(x => !branchCommits.Any(y => y.Id == x.Id)); + + branch.Ahead = aheadCommits.Count(); + branch.Behind = behindCommits.Count(); + } + + return branches; + } + public async Task DeleteBranchAsync(int branchId) { var entity = await _context.Branches.FirstOrDefaultAsync(x => x.Id == branchId); @@ -105,6 +193,17 @@ public async Task UpdateBranchAsync(int branchId, BranchUpdateDto bra return _mapper.Map(updatedEntity); } + private async Task GetFullBranchEntityAsync(int branchId) + { + return await _context.Branches + .AsSplitQuery() + .Include(x => x.Commits) + .Include(x => x.ParentBranch) + .Include(x => x.BranchCommits) + .ThenInclude(x => x.Commit) + .FirstOrDefaultAsync(x => x.Id == branchId); + } + private async Task InheritBranchInternalAsync(Branch branch, int parentId) { if (await _context.Branches.AnyAsync(x => x.Id == parentId && branch.ProjectId == x.ProjectId)) diff --git a/backend/Squirrel.Core/Squirrel.Core.Common/DTO/Branch/BranchDetailsDto.cs b/backend/Squirrel.Core/Squirrel.Core.Common/DTO/Branch/BranchDetailsDto.cs new file mode 100644 index 000000000..d085e131b --- /dev/null +++ b/backend/Squirrel.Core/Squirrel.Core.Common/DTO/Branch/BranchDetailsDto.cs @@ -0,0 +1,16 @@ +using Squirrel.Core.Common.DTO.Users; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Squirrel.Core.Common.DTO.Branch; +public class BranchDetailsDto: BranchDto +{ + public UserDto? LastUpdatedBy { get; set; } = null!; + public int Ahead { get; set; } + public int Behind { get; set; } + public DateTime CreatedAt { get; set; } + public DateTime? UpdatedAt { get; set; } = null!; +} diff --git a/backend/Squirrel.Core/Squirrel.Core.Common/DTO/Branch/BranchDto.cs b/backend/Squirrel.Core/Squirrel.Core.Common/DTO/Branch/BranchDto.cs index b7cb822d2..0fc458438 100644 --- a/backend/Squirrel.Core/Squirrel.Core.Common/DTO/Branch/BranchDto.cs +++ b/backend/Squirrel.Core/Squirrel.Core.Common/DTO/Branch/BranchDto.cs @@ -1,6 +1,6 @@ namespace Squirrel.Core.Common.DTO.Branch; -public sealed class BranchDto +public class BranchDto { public int Id { get; set; } public string Name { get; set; } = null!; diff --git a/backend/Squirrel.Core/Squirrel.Core.Common/DTO/Branch/MergeBranchDto.cs b/backend/Squirrel.Core/Squirrel.Core.Common/DTO/Branch/MergeBranchDto.cs new file mode 100644 index 000000000..3321674ed --- /dev/null +++ b/backend/Squirrel.Core/Squirrel.Core.Common/DTO/Branch/MergeBranchDto.cs @@ -0,0 +1,7 @@ +namespace Squirrel.Core.Common.DTO.Branch; + +public class MergeBranchDto +{ + public int SourceId { get; set; } + public int DestinationId { get; set; } +} diff --git a/backend/Squirrel.Core/Squirrel.Core.DAL/Context/EntityConfigurations/BranchConfig.cs b/backend/Squirrel.Core/Squirrel.Core.DAL/Context/EntityConfigurations/BranchConfig.cs index 8470cd90d..648c30062 100644 --- a/backend/Squirrel.Core/Squirrel.Core.DAL/Context/EntityConfigurations/BranchConfig.cs +++ b/backend/Squirrel.Core/Squirrel.Core.DAL/Context/EntityConfigurations/BranchConfig.cs @@ -13,6 +13,14 @@ public void Configure(EntityTypeBuilder builder) builder.Property(x => x.IsActive).IsRequired(); builder.Property(x => x.ProjectId).IsRequired(); + builder.Property(x => x.CreatedAt) + .IsRequired() + .HasDefaultValueSql(SquirrelCoreContext.SqlGetDateFunction) + .ValueGeneratedOnAdd(); + + builder.Property(x => x.CreatedBy) + .IsRequired(false); + builder.HasOne(x => x.ParentBranch) .WithMany() .HasForeignKey(x => x.ParentBranchId) diff --git a/backend/Squirrel.Core/Squirrel.Core.DAL/Context/EntityConfigurations/UserConfig.cs b/backend/Squirrel.Core/Squirrel.Core.DAL/Context/EntityConfigurations/UserConfig.cs index 1f276017d..4a8ad2292 100644 --- a/backend/Squirrel.Core/Squirrel.Core.DAL/Context/EntityConfigurations/UserConfig.cs +++ b/backend/Squirrel.Core/Squirrel.Core.DAL/Context/EntityConfigurations/UserConfig.cs @@ -27,6 +27,12 @@ public void Configure(EntityTypeBuilder builder) .IsRequired() .OnDelete(DeleteBehavior.NoAction); + builder.HasMany(x => x.Branches) + .WithOne(x => x.Author) + .HasForeignKey(x => x.CreatedBy) + .IsRequired() + .OnDelete(DeleteBehavior.NoAction); + builder.HasMany(x => x.Commits) .WithOne(x => x.Author) .HasForeignKey(x => x.CreatedBy) diff --git a/backend/Squirrel.Core/Squirrel.Core.DAL/Entities/Branch.cs b/backend/Squirrel.Core/Squirrel.Core.DAL/Entities/Branch.cs index e13a69072..99a10deed 100644 --- a/backend/Squirrel.Core/Squirrel.Core.DAL/Entities/Branch.cs +++ b/backend/Squirrel.Core/Squirrel.Core.DAL/Entities/Branch.cs @@ -1,9 +1,10 @@ using Squirrel.Core.DAL.Entities.Common; +using Squirrel.Core.DAL.Entities.Common.AuditEntity; using Squirrel.Core.DAL.Entities.JoinEntities; namespace Squirrel.Core.DAL.Entities; -public sealed class Branch : Entity +public sealed class Branch : AuditEntity { public string Name { get; set; } = string.Empty; public bool IsActive { get; set; } = true; @@ -12,6 +13,7 @@ public sealed class Branch : Entity public int? ParentBranchId { get; set; } public Project Project { get; set; } = null!; public Branch? ParentBranch { get; set; } + public User? Author { get; set; } = null!; public ICollection Commits { get; set; } = new List(); public ICollection BranchCommits { get; set; } = new List(); public ICollection PullRequestsFromThisBranch { get; set; } = new List(); diff --git a/backend/Squirrel.Core/Squirrel.Core.DAL/Entities/User.cs b/backend/Squirrel.Core/Squirrel.Core.DAL/Entities/User.cs index b2b006809..7358afd55 100644 --- a/backend/Squirrel.Core/Squirrel.Core.DAL/Entities/User.cs +++ b/backend/Squirrel.Core/Squirrel.Core.DAL/Entities/User.cs @@ -17,6 +17,7 @@ public sealed class User : Entity public bool IsGoogleAuth { get; set; } public ICollection Commits { get; set; } = new List(); + public ICollection Branches { get; set; } = new List(); public ICollection Comments { get; set; } = new List(); public ICollection PullRequests { get; set; } = new List(); public ICollection UserProjects { get; set; } = new List(); diff --git a/backend/Squirrel.Core/Squirrel.Core.DAL/Migrations/20230928203631_BranchToAuditEntity.Designer.cs b/backend/Squirrel.Core/Squirrel.Core.DAL/Migrations/20230928203631_BranchToAuditEntity.Designer.cs new file mode 100644 index 000000000..33cd5e373 --- /dev/null +++ b/backend/Squirrel.Core/Squirrel.Core.DAL/Migrations/20230928203631_BranchToAuditEntity.Designer.cs @@ -0,0 +1,948 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Squirrel.Core.DAL.Context; + +#nullable disable + +namespace Squirrel.Core.DAL.Migrations +{ + [DbContext(typeof(SquirrelCoreContext))] + [Migration("20230928203631_BranchToAuditEntity")] + partial class BranchToAuditEntity + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "6.0.7") + .HasAnnotation("Relational:MaxIdentifierLength", 128); + + SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder, 1L, 1); + + modelBuilder.Entity("Squirrel.Core.DAL.Entities.Branch", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id"), 1L, 1); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("int"); + + b.Property("IsActive") + .HasColumnType("bit"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("ParentBranchId") + .HasColumnType("int"); + + b.Property("ProjectId") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("ParentBranchId"); + + b.HasIndex("ProjectId"); + + b.ToTable("Branches"); + }); + + modelBuilder.Entity("Squirrel.Core.DAL.Entities.ChangeRecord", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id"), 1L, 1); + + b.Property("CreatedAt") + .ValueGeneratedOnAdd() + .HasColumnType("datetime2") + .HasDefaultValueSql("getutcdate()"); + + b.Property("CreatedBy") + .IsRequired() + .HasColumnType("int"); + + b.Property("UniqueChangeId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("CreatedBy"); + + b.ToTable("ChangeRecords"); + }); + + modelBuilder.Entity("Squirrel.Core.DAL.Entities.Comment", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id"), 1L, 1); + + b.Property("CommentedEntity") + .HasColumnType("int"); + + b.Property("CommentedEntityId") + .HasColumnType("int"); + + b.Property("Content") + .IsRequired() + .HasMaxLength(500) + .HasColumnType("nvarchar(500)"); + + b.Property("CreatedAt") + .ValueGeneratedOnAdd() + .HasColumnType("datetime2") + .HasDefaultValueSql("getutcdate()"); + + b.Property("CreatedBy") + .IsRequired() + .HasColumnType("int"); + + b.Property("UpdatedAt") + .ValueGeneratedOnAddOrUpdate() + .HasColumnType("datetime2") + .HasDefaultValueSql("getutcdate()"); + + b.HasKey("Id"); + + b.HasIndex("CreatedBy"); + + b.ToTable("Comments"); + }); + + modelBuilder.Entity("Squirrel.Core.DAL.Entities.Commit", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id"), 1L, 1); + + b.Property("CreatedAt") + .ValueGeneratedOnAdd() + .HasColumnType("datetime2") + .HasDefaultValueSql("getutcdate()"); + + b.Property("CreatedBy") + .IsRequired() + .HasColumnType("int"); + + b.Property("IsSaved") + .HasColumnType("bit"); + + b.Property("Message") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("PostScript") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("PreScript") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.HasIndex("CreatedBy"); + + b.ToTable("Commits"); + }); + + modelBuilder.Entity("Squirrel.Core.DAL.Entities.CommitFile", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id"), 1L, 1); + + b.Property("BlobId") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("CommitId") + .HasColumnType("int"); + + b.Property("FileName") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("FileType") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("CommitId"); + + b.ToTable("CommitFiles"); + }); + + modelBuilder.Entity("Squirrel.Core.DAL.Entities.CommitParent", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id"), 1L, 1); + + b.Property("CommitId") + .HasColumnType("int"); + + b.Property("ParentId") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("CommitId"); + + b.HasIndex("ParentId"); + + b.ToTable("CommitParents"); + }); + + modelBuilder.Entity("Squirrel.Core.DAL.Entities.JoinEntities.BranchCommit", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id"), 1L, 1); + + b.Property("BranchId") + .HasColumnType("int"); + + b.Property("CommitId") + .HasColumnType("int"); + + b.Property("IsHead") + .HasColumnType("bit"); + + b.Property("IsMerged") + .HasColumnType("bit"); + + b.HasKey("Id"); + + b.HasIndex("BranchId"); + + b.HasIndex("CommitId"); + + b.ToTable("BranchCommits"); + }); + + modelBuilder.Entity("Squirrel.Core.DAL.Entities.JoinEntities.ProjectTag", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id"), 1L, 1); + + b.Property("ProjectId") + .HasColumnType("int"); + + b.Property("TagId") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("ProjectId"); + + b.HasIndex("TagId"); + + b.ToTable("ProjectTags"); + }); + + modelBuilder.Entity("Squirrel.Core.DAL.Entities.JoinEntities.PullRequestReviewer", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id"), 1L, 1); + + b.Property("PullRequestId") + .HasColumnType("int"); + + b.Property("UserId") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("PullRequestId"); + + b.HasIndex("UserId"); + + b.ToTable("PullRequestReviewers"); + }); + + modelBuilder.Entity("Squirrel.Core.DAL.Entities.JoinEntities.UserProject", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id"), 1L, 1); + + b.Property("ProjectId") + .HasColumnType("int"); + + b.Property("UserId") + .HasColumnType("int"); + + b.Property("UserRole") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("ProjectId"); + + b.HasIndex("UserId"); + + b.ToTable("UserProjects"); + }); + + modelBuilder.Entity("Squirrel.Core.DAL.Entities.Project", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id"), 1L, 1); + + b.Property("CreatedAt") + .ValueGeneratedOnAdd() + .HasColumnType("datetime2") + .HasDefaultValueSql("getutcdate()"); + + b.Property("CreatedBy") + .IsRequired() + .HasColumnType("int"); + + b.Property("DbEngine") + .HasColumnType("int"); + + b.Property("DefaultBranchId") + .HasColumnType("int"); + + b.Property("Description") + .HasMaxLength(1000) + .HasColumnType("nvarchar(1000)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.Property("UpdatedAt") + .ValueGeneratedOnAddOrUpdate() + .HasColumnType("datetime2") + .HasDefaultValueSql("getutcdate()"); + + b.HasKey("Id"); + + b.HasIndex("CreatedBy"); + + b.HasIndex("DefaultBranchId") + .IsUnique() + .HasFilter("[DefaultBranchId] IS NOT NULL"); + + b.ToTable("Projects"); + }); + + modelBuilder.Entity("Squirrel.Core.DAL.Entities.ProjectDatabase", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id"), 1L, 1); + + b.Property("DbName") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.Property("Guid") + .HasColumnType("uniqueidentifier"); + + b.Property("ProjectId") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasAlternateKey("Guid"); + + b.HasIndex("ProjectId"); + + b.ToTable("ProjectDatabases"); + }); + + modelBuilder.Entity("Squirrel.Core.DAL.Entities.PullRequest", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id"), 1L, 1); + + b.Property("CreatedAt") + .ValueGeneratedOnAdd() + .HasColumnType("datetime2") + .HasDefaultValueSql("getutcdate()"); + + b.Property("CreatedBy") + .IsRequired() + .HasColumnType("int"); + + b.Property("IsReviewed") + .HasColumnType("bit"); + + b.Property("ProjectId") + .HasColumnType("int"); + + b.Property("SourceBranchId") + .HasColumnType("int"); + + b.Property("Status") + .HasColumnType("int"); + + b.Property("TargetBranchId") + .HasColumnType("int"); + + b.Property("Title") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("UpdatedAt") + .ValueGeneratedOnAddOrUpdate() + .HasColumnType("datetime2") + .HasDefaultValueSql("getutcdate()"); + + b.HasKey("Id"); + + b.HasIndex("CreatedBy"); + + b.HasIndex("ProjectId"); + + b.HasIndex("SourceBranchId"); + + b.HasIndex("TargetBranchId"); + + b.ToTable("PullRequests"); + }); + + modelBuilder.Entity("Squirrel.Core.DAL.Entities.RefreshToken", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id"), 1L, 1); + + b.Property("CreatedAt") + .ValueGeneratedOnAdd() + .HasColumnType("datetime2") + .HasDefaultValueSql("getutcdate()"); + + b.Property("CreatedBy") + .HasColumnType("int"); + + b.Property("ExpiresAt") + .HasColumnType("datetime2"); + + b.Property("Token") + .IsRequired() + .HasMaxLength(500) + .HasColumnType("nvarchar(500)"); + + b.Property("UserId") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("RefreshTokens"); + }); + + modelBuilder.Entity("Squirrel.Core.DAL.Entities.Script", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id"), 1L, 1); + + b.Property("Content") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("CreatedAt") + .ValueGeneratedOnAdd() + .HasColumnType("datetime2") + .HasDefaultValueSql("getutcdate()"); + + b.Property("CreatedBy") + .IsRequired() + .HasColumnType("int"); + + b.Property("FileName") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("LastUpdatedByUserId") + .HasColumnType("int"); + + b.Property("ProjectId") + .HasColumnType("int"); + + b.Property("Title") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("UpdatedAt") + .ValueGeneratedOnAddOrUpdate() + .HasColumnType("datetime2") + .HasDefaultValueSql("getutcdate()"); + + b.HasKey("Id"); + + b.HasIndex("CreatedBy"); + + b.HasIndex("LastUpdatedByUserId"); + + b.HasIndex("ProjectId"); + + b.ToTable("Scripts"); + }); + + modelBuilder.Entity("Squirrel.Core.DAL.Entities.Tag", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id"), 1L, 1); + + b.Property("Name") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.HasKey("Id"); + + b.ToTable("Tags"); + }); + + modelBuilder.Entity("Squirrel.Core.DAL.Entities.User", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id"), 1L, 1); + + b.Property("AvatarUrl") + .HasMaxLength(500) + .HasColumnType("nvarchar(500)"); + + b.Property("Email") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.Property("EmailNotification") + .HasColumnType("bit"); + + b.Property("FirstName") + .IsRequired() + .HasMaxLength(25) + .HasColumnType("nvarchar(25)"); + + b.Property("IsGoogleAuth") + .HasColumnType("bit"); + + b.Property("LastName") + .IsRequired() + .HasMaxLength(25) + .HasColumnType("nvarchar(25)"); + + b.Property("PasswordHash") + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("Salt") + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("SquirrelNotification") + .HasColumnType("bit"); + + b.Property("Username") + .IsRequired() + .HasMaxLength(25) + .HasColumnType("nvarchar(25)"); + + b.HasKey("Id"); + + b.HasAlternateKey("Email"); + + b.HasIndex("Email") + .IsUnique(); + + b.HasIndex("Username") + .IsUnique(); + + b.ToTable("Users"); + }); + + modelBuilder.Entity("Squirrel.Core.DAL.Entities.Branch", b => + { + b.HasOne("Squirrel.Core.DAL.Entities.Branch", "ParentBranch") + .WithMany() + .HasForeignKey("ParentBranchId"); + + b.HasOne("Squirrel.Core.DAL.Entities.Project", "Project") + .WithMany("Branches") + .HasForeignKey("ProjectId") + .OnDelete(DeleteBehavior.NoAction) + .IsRequired(); + + b.Navigation("ParentBranch"); + + b.Navigation("Project"); + }); + + modelBuilder.Entity("Squirrel.Core.DAL.Entities.ChangeRecord", b => + { + b.HasOne("Squirrel.Core.DAL.Entities.User", "User") + .WithMany("ChangeRecords") + .HasForeignKey("CreatedBy") + .OnDelete(DeleteBehavior.NoAction) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Squirrel.Core.DAL.Entities.Comment", b => + { + b.HasOne("Squirrel.Core.DAL.Entities.User", "Author") + .WithMany("Comments") + .HasForeignKey("CreatedBy") + .OnDelete(DeleteBehavior.NoAction) + .IsRequired(); + + b.Navigation("Author"); + }); + + modelBuilder.Entity("Squirrel.Core.DAL.Entities.Commit", b => + { + b.HasOne("Squirrel.Core.DAL.Entities.User", "Author") + .WithMany("Commits") + .HasForeignKey("CreatedBy") + .OnDelete(DeleteBehavior.NoAction) + .IsRequired(); + + b.Navigation("Author"); + }); + + modelBuilder.Entity("Squirrel.Core.DAL.Entities.CommitFile", b => + { + b.HasOne("Squirrel.Core.DAL.Entities.Commit", "Commit") + .WithMany("CommitFiles") + .HasForeignKey("CommitId") + .OnDelete(DeleteBehavior.NoAction) + .IsRequired(); + + b.Navigation("Commit"); + }); + + modelBuilder.Entity("Squirrel.Core.DAL.Entities.CommitParent", b => + { + b.HasOne("Squirrel.Core.DAL.Entities.Commit", "Commit") + .WithMany("CommitsAsChild") + .HasForeignKey("CommitId") + .OnDelete(DeleteBehavior.NoAction) + .IsRequired(); + + b.HasOne("Squirrel.Core.DAL.Entities.Commit", "ParentCommit") + .WithMany("CommitsAsParent") + .HasForeignKey("ParentId") + .OnDelete(DeleteBehavior.NoAction) + .IsRequired(); + + b.Navigation("Commit"); + + b.Navigation("ParentCommit"); + }); + + modelBuilder.Entity("Squirrel.Core.DAL.Entities.JoinEntities.BranchCommit", b => + { + b.HasOne("Squirrel.Core.DAL.Entities.Branch", "Branch") + .WithMany("BranchCommits") + .HasForeignKey("BranchId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Squirrel.Core.DAL.Entities.Commit", "Commit") + .WithMany("BranchCommits") + .HasForeignKey("CommitId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Branch"); + + b.Navigation("Commit"); + }); + + modelBuilder.Entity("Squirrel.Core.DAL.Entities.JoinEntities.ProjectTag", b => + { + b.HasOne("Squirrel.Core.DAL.Entities.Project", "Project") + .WithMany("ProjectTags") + .HasForeignKey("ProjectId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Squirrel.Core.DAL.Entities.Tag", "Tag") + .WithMany("ProjectTags") + .HasForeignKey("TagId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Project"); + + b.Navigation("Tag"); + }); + + modelBuilder.Entity("Squirrel.Core.DAL.Entities.JoinEntities.PullRequestReviewer", b => + { + b.HasOne("Squirrel.Core.DAL.Entities.PullRequest", "PullRequest") + .WithMany("PullRequestReviewers") + .HasForeignKey("PullRequestId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Squirrel.Core.DAL.Entities.User", "User") + .WithMany("PullRequestReviewers") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("PullRequest"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Squirrel.Core.DAL.Entities.JoinEntities.UserProject", b => + { + b.HasOne("Squirrel.Core.DAL.Entities.Project", "Project") + .WithMany("UserProjects") + .HasForeignKey("ProjectId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Squirrel.Core.DAL.Entities.User", "User") + .WithMany("UserProjects") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Project"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Squirrel.Core.DAL.Entities.Project", b => + { + b.HasOne("Squirrel.Core.DAL.Entities.User", "Author") + .WithMany("OwnProjects") + .HasForeignKey("CreatedBy") + .OnDelete(DeleteBehavior.NoAction) + .IsRequired(); + + b.HasOne("Squirrel.Core.DAL.Entities.Branch", "DefaultBranch") + .WithOne() + .HasForeignKey("Squirrel.Core.DAL.Entities.Project", "DefaultBranchId") + .OnDelete(DeleteBehavior.NoAction); + + b.Navigation("Author"); + + b.Navigation("DefaultBranch"); + }); + + modelBuilder.Entity("Squirrel.Core.DAL.Entities.ProjectDatabase", b => + { + b.HasOne("Squirrel.Core.DAL.Entities.Project", "Project") + .WithMany("ProjectDatabases") + .HasForeignKey("ProjectId") + .OnDelete(DeleteBehavior.NoAction) + .IsRequired(); + + b.Navigation("Project"); + }); + + modelBuilder.Entity("Squirrel.Core.DAL.Entities.PullRequest", b => + { + b.HasOne("Squirrel.Core.DAL.Entities.User", "Author") + .WithMany("PullRequests") + .HasForeignKey("CreatedBy") + .OnDelete(DeleteBehavior.NoAction) + .IsRequired(); + + b.HasOne("Squirrel.Core.DAL.Entities.Project", "Project") + .WithMany("PullRequests") + .HasForeignKey("ProjectId") + .OnDelete(DeleteBehavior.NoAction) + .IsRequired(); + + b.HasOne("Squirrel.Core.DAL.Entities.Branch", "SourceBranch") + .WithMany("PullRequestsFromThisBranch") + .HasForeignKey("SourceBranchId") + .OnDelete(DeleteBehavior.NoAction) + .IsRequired(); + + b.HasOne("Squirrel.Core.DAL.Entities.Branch", "TargetBranch") + .WithMany("PullRequestsIntoThisBranch") + .HasForeignKey("TargetBranchId") + .OnDelete(DeleteBehavior.NoAction) + .IsRequired(); + + b.Navigation("Author"); + + b.Navigation("Project"); + + b.Navigation("SourceBranch"); + + b.Navigation("TargetBranch"); + }); + + modelBuilder.Entity("Squirrel.Core.DAL.Entities.RefreshToken", b => + { + b.HasOne("Squirrel.Core.DAL.Entities.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Squirrel.Core.DAL.Entities.Script", b => + { + b.HasOne("Squirrel.Core.DAL.Entities.User", "Author") + .WithMany("Scripts") + .HasForeignKey("CreatedBy") + .OnDelete(DeleteBehavior.NoAction) + .IsRequired(); + + b.HasOne("Squirrel.Core.DAL.Entities.User", "LastUpdatedBy") + .WithMany() + .HasForeignKey("LastUpdatedByUserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Squirrel.Core.DAL.Entities.Project", "Project") + .WithMany("Scripts") + .HasForeignKey("ProjectId") + .OnDelete(DeleteBehavior.NoAction) + .IsRequired(); + + b.Navigation("Author"); + + b.Navigation("LastUpdatedBy"); + + b.Navigation("Project"); + }); + + modelBuilder.Entity("Squirrel.Core.DAL.Entities.Branch", b => + { + b.Navigation("BranchCommits"); + + b.Navigation("PullRequestsFromThisBranch"); + + b.Navigation("PullRequestsIntoThisBranch"); + }); + + modelBuilder.Entity("Squirrel.Core.DAL.Entities.Commit", b => + { + b.Navigation("BranchCommits"); + + b.Navigation("CommitFiles"); + + b.Navigation("CommitsAsChild"); + + b.Navigation("CommitsAsParent"); + }); + + modelBuilder.Entity("Squirrel.Core.DAL.Entities.Project", b => + { + b.Navigation("Branches"); + + b.Navigation("ProjectDatabases"); + + b.Navigation("ProjectTags"); + + b.Navigation("PullRequests"); + + b.Navigation("Scripts"); + + b.Navigation("UserProjects"); + }); + + modelBuilder.Entity("Squirrel.Core.DAL.Entities.PullRequest", b => + { + b.Navigation("PullRequestReviewers"); + }); + + modelBuilder.Entity("Squirrel.Core.DAL.Entities.Tag", b => + { + b.Navigation("ProjectTags"); + }); + + modelBuilder.Entity("Squirrel.Core.DAL.Entities.User", b => + { + b.Navigation("ChangeRecords"); + + b.Navigation("Comments"); + + b.Navigation("Commits"); + + b.Navigation("OwnProjects"); + + b.Navigation("PullRequestReviewers"); + + b.Navigation("PullRequests"); + + b.Navigation("Scripts"); + + b.Navigation("UserProjects"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/backend/Squirrel.Core/Squirrel.Core.DAL/Migrations/20230928203631_BranchToAuditEntity.cs b/backend/Squirrel.Core/Squirrel.Core.DAL/Migrations/20230928203631_BranchToAuditEntity.cs new file mode 100644 index 000000000..d9c0e98c1 --- /dev/null +++ b/backend/Squirrel.Core/Squirrel.Core.DAL/Migrations/20230928203631_BranchToAuditEntity.cs @@ -0,0 +1,37 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace Squirrel.Core.DAL.Migrations +{ + public partial class BranchToAuditEntity : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "CreatedAt", + table: "Branches", + type: "datetime2", + nullable: false, + defaultValue: new DateTime(1, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified)); + + migrationBuilder.AddColumn( + name: "CreatedBy", + table: "Branches", + type: "int", + nullable: true); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn( + name: "CreatedAt", + table: "Branches"); + + migrationBuilder.DropColumn( + name: "CreatedBy", + table: "Branches"); + } + } +} diff --git a/backend/Squirrel.Core/Squirrel.Core.DAL/Migrations/20230929094351_BranchCreatedAt.Designer.cs b/backend/Squirrel.Core/Squirrel.Core.DAL/Migrations/20230929094351_BranchCreatedAt.Designer.cs new file mode 100644 index 000000000..bd45d580c --- /dev/null +++ b/backend/Squirrel.Core/Squirrel.Core.DAL/Migrations/20230929094351_BranchCreatedAt.Designer.cs @@ -0,0 +1,961 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Squirrel.Core.DAL.Context; + +#nullable disable + +namespace Squirrel.Core.DAL.Migrations +{ + [DbContext(typeof(SquirrelCoreContext))] + [Migration("20230929094351_BranchCreatedAt")] + partial class BranchCreatedAt + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "6.0.7") + .HasAnnotation("Relational:MaxIdentifierLength", 128); + + SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder, 1L, 1); + + modelBuilder.Entity("Squirrel.Core.DAL.Entities.Branch", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id"), 1L, 1); + + b.Property("AuthorId") + .HasColumnType("int"); + + b.Property("CreatedAt") + .ValueGeneratedOnAdd() + .HasColumnType("datetime2") + .HasDefaultValueSql("getutcdate()"); + + b.Property("CreatedBy") + .HasColumnType("int"); + + b.Property("IsActive") + .HasColumnType("bit"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("ParentBranchId") + .HasColumnType("int"); + + b.Property("ProjectId") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("AuthorId"); + + b.HasIndex("ParentBranchId"); + + b.HasIndex("ProjectId"); + + b.ToTable("Branches"); + }); + + modelBuilder.Entity("Squirrel.Core.DAL.Entities.ChangeRecord", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id"), 1L, 1); + + b.Property("CreatedAt") + .ValueGeneratedOnAdd() + .HasColumnType("datetime2") + .HasDefaultValueSql("getutcdate()"); + + b.Property("CreatedBy") + .IsRequired() + .HasColumnType("int"); + + b.Property("UniqueChangeId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("CreatedBy"); + + b.ToTable("ChangeRecords"); + }); + + modelBuilder.Entity("Squirrel.Core.DAL.Entities.Comment", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id"), 1L, 1); + + b.Property("CommentedEntity") + .HasColumnType("int"); + + b.Property("CommentedEntityId") + .HasColumnType("int"); + + b.Property("Content") + .IsRequired() + .HasMaxLength(500) + .HasColumnType("nvarchar(500)"); + + b.Property("CreatedAt") + .ValueGeneratedOnAdd() + .HasColumnType("datetime2") + .HasDefaultValueSql("getutcdate()"); + + b.Property("CreatedBy") + .IsRequired() + .HasColumnType("int"); + + b.Property("UpdatedAt") + .ValueGeneratedOnAddOrUpdate() + .HasColumnType("datetime2") + .HasDefaultValueSql("getutcdate()"); + + b.HasKey("Id"); + + b.HasIndex("CreatedBy"); + + b.ToTable("Comments"); + }); + + modelBuilder.Entity("Squirrel.Core.DAL.Entities.Commit", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id"), 1L, 1); + + b.Property("CreatedAt") + .ValueGeneratedOnAdd() + .HasColumnType("datetime2") + .HasDefaultValueSql("getutcdate()"); + + b.Property("CreatedBy") + .IsRequired() + .HasColumnType("int"); + + b.Property("IsSaved") + .HasColumnType("bit"); + + b.Property("Message") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("PostScript") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("PreScript") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.HasIndex("CreatedBy"); + + b.ToTable("Commits"); + }); + + modelBuilder.Entity("Squirrel.Core.DAL.Entities.CommitFile", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id"), 1L, 1); + + b.Property("BlobId") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("CommitId") + .HasColumnType("int"); + + b.Property("FileName") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("FileType") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("CommitId"); + + b.ToTable("CommitFiles"); + }); + + modelBuilder.Entity("Squirrel.Core.DAL.Entities.CommitParent", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id"), 1L, 1); + + b.Property("CommitId") + .HasColumnType("int"); + + b.Property("ParentId") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("CommitId"); + + b.HasIndex("ParentId"); + + b.ToTable("CommitParents"); + }); + + modelBuilder.Entity("Squirrel.Core.DAL.Entities.JoinEntities.BranchCommit", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id"), 1L, 1); + + b.Property("BranchId") + .HasColumnType("int"); + + b.Property("CommitId") + .HasColumnType("int"); + + b.Property("IsHead") + .HasColumnType("bit"); + + b.Property("IsMerged") + .HasColumnType("bit"); + + b.HasKey("Id"); + + b.HasIndex("BranchId"); + + b.HasIndex("CommitId"); + + b.ToTable("BranchCommits"); + }); + + modelBuilder.Entity("Squirrel.Core.DAL.Entities.JoinEntities.ProjectTag", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id"), 1L, 1); + + b.Property("ProjectId") + .HasColumnType("int"); + + b.Property("TagId") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("ProjectId"); + + b.HasIndex("TagId"); + + b.ToTable("ProjectTags"); + }); + + modelBuilder.Entity("Squirrel.Core.DAL.Entities.JoinEntities.PullRequestReviewer", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id"), 1L, 1); + + b.Property("PullRequestId") + .HasColumnType("int"); + + b.Property("UserId") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("PullRequestId"); + + b.HasIndex("UserId"); + + b.ToTable("PullRequestReviewers"); + }); + + modelBuilder.Entity("Squirrel.Core.DAL.Entities.JoinEntities.UserProject", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id"), 1L, 1); + + b.Property("ProjectId") + .HasColumnType("int"); + + b.Property("UserId") + .HasColumnType("int"); + + b.Property("UserRole") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("ProjectId"); + + b.HasIndex("UserId"); + + b.ToTable("UserProjects"); + }); + + modelBuilder.Entity("Squirrel.Core.DAL.Entities.Project", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id"), 1L, 1); + + b.Property("CreatedAt") + .ValueGeneratedOnAdd() + .HasColumnType("datetime2") + .HasDefaultValueSql("getutcdate()"); + + b.Property("CreatedBy") + .IsRequired() + .HasColumnType("int"); + + b.Property("DbEngine") + .HasColumnType("int"); + + b.Property("DefaultBranchId") + .HasColumnType("int"); + + b.Property("Description") + .HasMaxLength(1000) + .HasColumnType("nvarchar(1000)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.Property("UpdatedAt") + .ValueGeneratedOnAddOrUpdate() + .HasColumnType("datetime2") + .HasDefaultValueSql("getutcdate()"); + + b.HasKey("Id"); + + b.HasIndex("CreatedBy"); + + b.HasIndex("DefaultBranchId") + .IsUnique() + .HasFilter("[DefaultBranchId] IS NOT NULL"); + + b.ToTable("Projects"); + }); + + modelBuilder.Entity("Squirrel.Core.DAL.Entities.ProjectDatabase", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id"), 1L, 1); + + b.Property("DbName") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.Property("Guid") + .HasColumnType("uniqueidentifier"); + + b.Property("ProjectId") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasAlternateKey("Guid"); + + b.HasIndex("ProjectId"); + + b.ToTable("ProjectDatabases"); + }); + + modelBuilder.Entity("Squirrel.Core.DAL.Entities.PullRequest", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id"), 1L, 1); + + b.Property("CreatedAt") + .ValueGeneratedOnAdd() + .HasColumnType("datetime2") + .HasDefaultValueSql("getutcdate()"); + + b.Property("CreatedBy") + .IsRequired() + .HasColumnType("int"); + + b.Property("IsReviewed") + .HasColumnType("bit"); + + b.Property("ProjectId") + .HasColumnType("int"); + + b.Property("SourceBranchId") + .HasColumnType("int"); + + b.Property("Status") + .HasColumnType("int"); + + b.Property("TargetBranchId") + .HasColumnType("int"); + + b.Property("Title") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("UpdatedAt") + .ValueGeneratedOnAddOrUpdate() + .HasColumnType("datetime2") + .HasDefaultValueSql("getutcdate()"); + + b.HasKey("Id"); + + b.HasIndex("CreatedBy"); + + b.HasIndex("ProjectId"); + + b.HasIndex("SourceBranchId"); + + b.HasIndex("TargetBranchId"); + + b.ToTable("PullRequests"); + }); + + modelBuilder.Entity("Squirrel.Core.DAL.Entities.RefreshToken", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id"), 1L, 1); + + b.Property("CreatedAt") + .ValueGeneratedOnAdd() + .HasColumnType("datetime2") + .HasDefaultValueSql("getutcdate()"); + + b.Property("CreatedBy") + .HasColumnType("int"); + + b.Property("ExpiresAt") + .HasColumnType("datetime2"); + + b.Property("Token") + .IsRequired() + .HasMaxLength(500) + .HasColumnType("nvarchar(500)"); + + b.Property("UserId") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("RefreshTokens"); + }); + + modelBuilder.Entity("Squirrel.Core.DAL.Entities.Script", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id"), 1L, 1); + + b.Property("Content") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("CreatedAt") + .ValueGeneratedOnAdd() + .HasColumnType("datetime2") + .HasDefaultValueSql("getutcdate()"); + + b.Property("CreatedBy") + .IsRequired() + .HasColumnType("int"); + + b.Property("FileName") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("LastUpdatedByUserId") + .HasColumnType("int"); + + b.Property("ProjectId") + .HasColumnType("int"); + + b.Property("Title") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("UpdatedAt") + .ValueGeneratedOnAddOrUpdate() + .HasColumnType("datetime2") + .HasDefaultValueSql("getutcdate()"); + + b.HasKey("Id"); + + b.HasIndex("CreatedBy"); + + b.HasIndex("LastUpdatedByUserId"); + + b.HasIndex("ProjectId"); + + b.ToTable("Scripts"); + }); + + modelBuilder.Entity("Squirrel.Core.DAL.Entities.Tag", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id"), 1L, 1); + + b.Property("Name") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.HasKey("Id"); + + b.ToTable("Tags"); + }); + + modelBuilder.Entity("Squirrel.Core.DAL.Entities.User", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id"), 1L, 1); + + b.Property("AvatarUrl") + .HasMaxLength(500) + .HasColumnType("nvarchar(500)"); + + b.Property("Email") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.Property("EmailNotification") + .HasColumnType("bit"); + + b.Property("FirstName") + .IsRequired() + .HasMaxLength(25) + .HasColumnType("nvarchar(25)"); + + b.Property("IsGoogleAuth") + .HasColumnType("bit"); + + b.Property("LastName") + .IsRequired() + .HasMaxLength(25) + .HasColumnType("nvarchar(25)"); + + b.Property("PasswordHash") + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("Salt") + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("SquirrelNotification") + .HasColumnType("bit"); + + b.Property("Username") + .IsRequired() + .HasMaxLength(25) + .HasColumnType("nvarchar(25)"); + + b.HasKey("Id"); + + b.HasAlternateKey("Email"); + + b.HasIndex("Email") + .IsUnique(); + + b.HasIndex("Username") + .IsUnique(); + + b.ToTable("Users"); + }); + + modelBuilder.Entity("Squirrel.Core.DAL.Entities.Branch", b => + { + b.HasOne("Squirrel.Core.DAL.Entities.User", "Author") + .WithMany() + .HasForeignKey("AuthorId"); + + b.HasOne("Squirrel.Core.DAL.Entities.Branch", "ParentBranch") + .WithMany() + .HasForeignKey("ParentBranchId"); + + b.HasOne("Squirrel.Core.DAL.Entities.Project", "Project") + .WithMany("Branches") + .HasForeignKey("ProjectId") + .OnDelete(DeleteBehavior.NoAction) + .IsRequired(); + + b.Navigation("Author"); + + b.Navigation("ParentBranch"); + + b.Navigation("Project"); + }); + + modelBuilder.Entity("Squirrel.Core.DAL.Entities.ChangeRecord", b => + { + b.HasOne("Squirrel.Core.DAL.Entities.User", "User") + .WithMany("ChangeRecords") + .HasForeignKey("CreatedBy") + .OnDelete(DeleteBehavior.NoAction) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Squirrel.Core.DAL.Entities.Comment", b => + { + b.HasOne("Squirrel.Core.DAL.Entities.User", "Author") + .WithMany("Comments") + .HasForeignKey("CreatedBy") + .OnDelete(DeleteBehavior.NoAction) + .IsRequired(); + + b.Navigation("Author"); + }); + + modelBuilder.Entity("Squirrel.Core.DAL.Entities.Commit", b => + { + b.HasOne("Squirrel.Core.DAL.Entities.User", "Author") + .WithMany("Commits") + .HasForeignKey("CreatedBy") + .OnDelete(DeleteBehavior.NoAction) + .IsRequired(); + + b.Navigation("Author"); + }); + + modelBuilder.Entity("Squirrel.Core.DAL.Entities.CommitFile", b => + { + b.HasOne("Squirrel.Core.DAL.Entities.Commit", "Commit") + .WithMany("CommitFiles") + .HasForeignKey("CommitId") + .OnDelete(DeleteBehavior.NoAction) + .IsRequired(); + + b.Navigation("Commit"); + }); + + modelBuilder.Entity("Squirrel.Core.DAL.Entities.CommitParent", b => + { + b.HasOne("Squirrel.Core.DAL.Entities.Commit", "Commit") + .WithMany("CommitsAsChild") + .HasForeignKey("CommitId") + .OnDelete(DeleteBehavior.NoAction) + .IsRequired(); + + b.HasOne("Squirrel.Core.DAL.Entities.Commit", "ParentCommit") + .WithMany("CommitsAsParent") + .HasForeignKey("ParentId") + .OnDelete(DeleteBehavior.NoAction) + .IsRequired(); + + b.Navigation("Commit"); + + b.Navigation("ParentCommit"); + }); + + modelBuilder.Entity("Squirrel.Core.DAL.Entities.JoinEntities.BranchCommit", b => + { + b.HasOne("Squirrel.Core.DAL.Entities.Branch", "Branch") + .WithMany("BranchCommits") + .HasForeignKey("BranchId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Squirrel.Core.DAL.Entities.Commit", "Commit") + .WithMany("BranchCommits") + .HasForeignKey("CommitId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Branch"); + + b.Navigation("Commit"); + }); + + modelBuilder.Entity("Squirrel.Core.DAL.Entities.JoinEntities.ProjectTag", b => + { + b.HasOne("Squirrel.Core.DAL.Entities.Project", "Project") + .WithMany("ProjectTags") + .HasForeignKey("ProjectId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Squirrel.Core.DAL.Entities.Tag", "Tag") + .WithMany("ProjectTags") + .HasForeignKey("TagId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Project"); + + b.Navigation("Tag"); + }); + + modelBuilder.Entity("Squirrel.Core.DAL.Entities.JoinEntities.PullRequestReviewer", b => + { + b.HasOne("Squirrel.Core.DAL.Entities.PullRequest", "PullRequest") + .WithMany("PullRequestReviewers") + .HasForeignKey("PullRequestId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Squirrel.Core.DAL.Entities.User", "User") + .WithMany("PullRequestReviewers") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("PullRequest"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Squirrel.Core.DAL.Entities.JoinEntities.UserProject", b => + { + b.HasOne("Squirrel.Core.DAL.Entities.Project", "Project") + .WithMany("UserProjects") + .HasForeignKey("ProjectId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Squirrel.Core.DAL.Entities.User", "User") + .WithMany("UserProjects") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Project"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Squirrel.Core.DAL.Entities.Project", b => + { + b.HasOne("Squirrel.Core.DAL.Entities.User", "Author") + .WithMany("OwnProjects") + .HasForeignKey("CreatedBy") + .OnDelete(DeleteBehavior.NoAction) + .IsRequired(); + + b.HasOne("Squirrel.Core.DAL.Entities.Branch", "DefaultBranch") + .WithOne() + .HasForeignKey("Squirrel.Core.DAL.Entities.Project", "DefaultBranchId") + .OnDelete(DeleteBehavior.NoAction); + + b.Navigation("Author"); + + b.Navigation("DefaultBranch"); + }); + + modelBuilder.Entity("Squirrel.Core.DAL.Entities.ProjectDatabase", b => + { + b.HasOne("Squirrel.Core.DAL.Entities.Project", "Project") + .WithMany("ProjectDatabases") + .HasForeignKey("ProjectId") + .OnDelete(DeleteBehavior.NoAction) + .IsRequired(); + + b.Navigation("Project"); + }); + + modelBuilder.Entity("Squirrel.Core.DAL.Entities.PullRequest", b => + { + b.HasOne("Squirrel.Core.DAL.Entities.User", "Author") + .WithMany("PullRequests") + .HasForeignKey("CreatedBy") + .OnDelete(DeleteBehavior.NoAction) + .IsRequired(); + + b.HasOne("Squirrel.Core.DAL.Entities.Project", "Project") + .WithMany("PullRequests") + .HasForeignKey("ProjectId") + .OnDelete(DeleteBehavior.NoAction) + .IsRequired(); + + b.HasOne("Squirrel.Core.DAL.Entities.Branch", "SourceBranch") + .WithMany("PullRequestsFromThisBranch") + .HasForeignKey("SourceBranchId") + .OnDelete(DeleteBehavior.NoAction) + .IsRequired(); + + b.HasOne("Squirrel.Core.DAL.Entities.Branch", "TargetBranch") + .WithMany("PullRequestsIntoThisBranch") + .HasForeignKey("TargetBranchId") + .OnDelete(DeleteBehavior.NoAction) + .IsRequired(); + + b.Navigation("Author"); + + b.Navigation("Project"); + + b.Navigation("SourceBranch"); + + b.Navigation("TargetBranch"); + }); + + modelBuilder.Entity("Squirrel.Core.DAL.Entities.RefreshToken", b => + { + b.HasOne("Squirrel.Core.DAL.Entities.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Squirrel.Core.DAL.Entities.Script", b => + { + b.HasOne("Squirrel.Core.DAL.Entities.User", "Author") + .WithMany("Scripts") + .HasForeignKey("CreatedBy") + .OnDelete(DeleteBehavior.NoAction) + .IsRequired(); + + b.HasOne("Squirrel.Core.DAL.Entities.User", "LastUpdatedBy") + .WithMany() + .HasForeignKey("LastUpdatedByUserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Squirrel.Core.DAL.Entities.Project", "Project") + .WithMany("Scripts") + .HasForeignKey("ProjectId") + .OnDelete(DeleteBehavior.NoAction) + .IsRequired(); + + b.Navigation("Author"); + + b.Navigation("LastUpdatedBy"); + + b.Navigation("Project"); + }); + + modelBuilder.Entity("Squirrel.Core.DAL.Entities.Branch", b => + { + b.Navigation("BranchCommits"); + + b.Navigation("PullRequestsFromThisBranch"); + + b.Navigation("PullRequestsIntoThisBranch"); + }); + + modelBuilder.Entity("Squirrel.Core.DAL.Entities.Commit", b => + { + b.Navigation("BranchCommits"); + + b.Navigation("CommitFiles"); + + b.Navigation("CommitsAsChild"); + + b.Navigation("CommitsAsParent"); + }); + + modelBuilder.Entity("Squirrel.Core.DAL.Entities.Project", b => + { + b.Navigation("Branches"); + + b.Navigation("ProjectDatabases"); + + b.Navigation("ProjectTags"); + + b.Navigation("PullRequests"); + + b.Navigation("Scripts"); + + b.Navigation("UserProjects"); + }); + + modelBuilder.Entity("Squirrel.Core.DAL.Entities.PullRequest", b => + { + b.Navigation("PullRequestReviewers"); + }); + + modelBuilder.Entity("Squirrel.Core.DAL.Entities.Tag", b => + { + b.Navigation("ProjectTags"); + }); + + modelBuilder.Entity("Squirrel.Core.DAL.Entities.User", b => + { + b.Navigation("ChangeRecords"); + + b.Navigation("Comments"); + + b.Navigation("Commits"); + + b.Navigation("OwnProjects"); + + b.Navigation("PullRequestReviewers"); + + b.Navigation("PullRequests"); + + b.Navigation("Scripts"); + + b.Navigation("UserProjects"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/backend/Squirrel.Core/Squirrel.Core.DAL/Migrations/20230929094351_BranchCreatedAt.cs b/backend/Squirrel.Core/Squirrel.Core.DAL/Migrations/20230929094351_BranchCreatedAt.cs new file mode 100644 index 000000000..9dd7a5862 --- /dev/null +++ b/backend/Squirrel.Core/Squirrel.Core.DAL/Migrations/20230929094351_BranchCreatedAt.cs @@ -0,0 +1,64 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace Squirrel.Core.DAL.Migrations +{ + public partial class BranchCreatedAt : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AlterColumn( + name: "CreatedAt", + table: "Branches", + type: "datetime2", + nullable: false, + defaultValueSql: "getutcdate()", + oldClrType: typeof(DateTime), + oldType: "datetime2"); + + migrationBuilder.AddColumn( + name: "AuthorId", + table: "Branches", + type: "int", + nullable: true); + + migrationBuilder.CreateIndex( + name: "IX_Branches_AuthorId", + table: "Branches", + column: "AuthorId"); + + migrationBuilder.AddForeignKey( + name: "FK_Branches_Users_AuthorId", + table: "Branches", + column: "AuthorId", + principalTable: "Users", + principalColumn: "Id"); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropForeignKey( + name: "FK_Branches_Users_AuthorId", + table: "Branches"); + + migrationBuilder.DropIndex( + name: "IX_Branches_AuthorId", + table: "Branches"); + + migrationBuilder.DropColumn( + name: "AuthorId", + table: "Branches"); + + migrationBuilder.AlterColumn( + name: "CreatedAt", + table: "Branches", + type: "datetime2", + nullable: false, + oldClrType: typeof(DateTime), + oldType: "datetime2", + oldDefaultValueSql: "getutcdate()"); + } + } +} diff --git a/backend/Squirrel.Core/Squirrel.Core.DAL/Migrations/20230929104151_UserManyToOneBranch.Designer.cs b/backend/Squirrel.Core/Squirrel.Core.DAL/Migrations/20230929104151_UserManyToOneBranch.Designer.cs new file mode 100644 index 000000000..a20b4b81c --- /dev/null +++ b/backend/Squirrel.Core/Squirrel.Core.DAL/Migrations/20230929104151_UserManyToOneBranch.Designer.cs @@ -0,0 +1,962 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Squirrel.Core.DAL.Context; + +#nullable disable + +namespace Squirrel.Core.DAL.Migrations +{ + [DbContext(typeof(SquirrelCoreContext))] + [Migration("20230929104151_UserManyToOneBranch")] + partial class UserManyToOneBranch + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "6.0.7") + .HasAnnotation("Relational:MaxIdentifierLength", 128); + + SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder, 1L, 1); + + modelBuilder.Entity("Squirrel.Core.DAL.Entities.Branch", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id"), 1L, 1); + + b.Property("CreatedAt") + .ValueGeneratedOnAdd() + .HasColumnType("datetime2") + .HasDefaultValueSql("getutcdate()"); + + b.Property("CreatedBy") + .HasColumnType("int"); + + b.Property("IsActive") + .HasColumnType("bit"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("ParentBranchId") + .HasColumnType("int"); + + b.Property("ProjectId") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("CreatedBy"); + + b.HasIndex("ParentBranchId"); + + b.HasIndex("ProjectId"); + + b.ToTable("Branches"); + }); + + modelBuilder.Entity("Squirrel.Core.DAL.Entities.ChangeRecord", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id"), 1L, 1); + + b.Property("CreatedAt") + .ValueGeneratedOnAdd() + .HasColumnType("datetime2") + .HasDefaultValueSql("getutcdate()"); + + b.Property("CreatedBy") + .IsRequired() + .HasColumnType("int"); + + b.Property("UniqueChangeId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("CreatedBy"); + + b.ToTable("ChangeRecords"); + }); + + modelBuilder.Entity("Squirrel.Core.DAL.Entities.Comment", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id"), 1L, 1); + + b.Property("CommentedEntity") + .HasColumnType("int"); + + b.Property("CommentedEntityId") + .HasColumnType("int"); + + b.Property("Content") + .IsRequired() + .HasMaxLength(500) + .HasColumnType("nvarchar(500)"); + + b.Property("CreatedAt") + .ValueGeneratedOnAdd() + .HasColumnType("datetime2") + .HasDefaultValueSql("getutcdate()"); + + b.Property("CreatedBy") + .IsRequired() + .HasColumnType("int"); + + b.Property("UpdatedAt") + .ValueGeneratedOnAddOrUpdate() + .HasColumnType("datetime2") + .HasDefaultValueSql("getutcdate()"); + + b.HasKey("Id"); + + b.HasIndex("CreatedBy"); + + b.ToTable("Comments"); + }); + + modelBuilder.Entity("Squirrel.Core.DAL.Entities.Commit", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id"), 1L, 1); + + b.Property("CreatedAt") + .ValueGeneratedOnAdd() + .HasColumnType("datetime2") + .HasDefaultValueSql("getutcdate()"); + + b.Property("CreatedBy") + .IsRequired() + .HasColumnType("int"); + + b.Property("IsSaved") + .HasColumnType("bit"); + + b.Property("Message") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("PostScript") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("PreScript") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.HasIndex("CreatedBy"); + + b.ToTable("Commits"); + }); + + modelBuilder.Entity("Squirrel.Core.DAL.Entities.CommitFile", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id"), 1L, 1); + + b.Property("BlobId") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("CommitId") + .HasColumnType("int"); + + b.Property("FileName") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("FileType") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("CommitId"); + + b.ToTable("CommitFiles"); + }); + + modelBuilder.Entity("Squirrel.Core.DAL.Entities.CommitParent", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id"), 1L, 1); + + b.Property("CommitId") + .HasColumnType("int"); + + b.Property("ParentId") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("CommitId"); + + b.HasIndex("ParentId"); + + b.ToTable("CommitParents"); + }); + + modelBuilder.Entity("Squirrel.Core.DAL.Entities.JoinEntities.BranchCommit", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id"), 1L, 1); + + b.Property("BranchId") + .HasColumnType("int"); + + b.Property("CommitId") + .HasColumnType("int"); + + b.Property("IsHead") + .HasColumnType("bit"); + + b.Property("IsMerged") + .HasColumnType("bit"); + + b.HasKey("Id"); + + b.HasIndex("BranchId"); + + b.HasIndex("CommitId"); + + b.ToTable("BranchCommits"); + }); + + modelBuilder.Entity("Squirrel.Core.DAL.Entities.JoinEntities.ProjectTag", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id"), 1L, 1); + + b.Property("ProjectId") + .HasColumnType("int"); + + b.Property("TagId") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("ProjectId"); + + b.HasIndex("TagId"); + + b.ToTable("ProjectTags"); + }); + + modelBuilder.Entity("Squirrel.Core.DAL.Entities.JoinEntities.PullRequestReviewer", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id"), 1L, 1); + + b.Property("PullRequestId") + .HasColumnType("int"); + + b.Property("UserId") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("PullRequestId"); + + b.HasIndex("UserId"); + + b.ToTable("PullRequestReviewers"); + }); + + modelBuilder.Entity("Squirrel.Core.DAL.Entities.JoinEntities.UserProject", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id"), 1L, 1); + + b.Property("ProjectId") + .HasColumnType("int"); + + b.Property("UserId") + .HasColumnType("int"); + + b.Property("UserRole") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("ProjectId"); + + b.HasIndex("UserId"); + + b.ToTable("UserProjects"); + }); + + modelBuilder.Entity("Squirrel.Core.DAL.Entities.Project", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id"), 1L, 1); + + b.Property("CreatedAt") + .ValueGeneratedOnAdd() + .HasColumnType("datetime2") + .HasDefaultValueSql("getutcdate()"); + + b.Property("CreatedBy") + .IsRequired() + .HasColumnType("int"); + + b.Property("DbEngine") + .HasColumnType("int"); + + b.Property("DefaultBranchId") + .HasColumnType("int"); + + b.Property("Description") + .HasMaxLength(1000) + .HasColumnType("nvarchar(1000)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.Property("UpdatedAt") + .ValueGeneratedOnAddOrUpdate() + .HasColumnType("datetime2") + .HasDefaultValueSql("getutcdate()"); + + b.HasKey("Id"); + + b.HasIndex("CreatedBy"); + + b.HasIndex("DefaultBranchId") + .IsUnique() + .HasFilter("[DefaultBranchId] IS NOT NULL"); + + b.ToTable("Projects"); + }); + + modelBuilder.Entity("Squirrel.Core.DAL.Entities.ProjectDatabase", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id"), 1L, 1); + + b.Property("DbName") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.Property("Guid") + .HasColumnType("uniqueidentifier"); + + b.Property("ProjectId") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasAlternateKey("Guid"); + + b.HasIndex("ProjectId"); + + b.ToTable("ProjectDatabases"); + }); + + modelBuilder.Entity("Squirrel.Core.DAL.Entities.PullRequest", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id"), 1L, 1); + + b.Property("CreatedAt") + .ValueGeneratedOnAdd() + .HasColumnType("datetime2") + .HasDefaultValueSql("getutcdate()"); + + b.Property("CreatedBy") + .IsRequired() + .HasColumnType("int"); + + b.Property("IsReviewed") + .HasColumnType("bit"); + + b.Property("ProjectId") + .HasColumnType("int"); + + b.Property("SourceBranchId") + .HasColumnType("int"); + + b.Property("Status") + .HasColumnType("int"); + + b.Property("TargetBranchId") + .HasColumnType("int"); + + b.Property("Title") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("UpdatedAt") + .ValueGeneratedOnAddOrUpdate() + .HasColumnType("datetime2") + .HasDefaultValueSql("getutcdate()"); + + b.HasKey("Id"); + + b.HasIndex("CreatedBy"); + + b.HasIndex("ProjectId"); + + b.HasIndex("SourceBranchId"); + + b.HasIndex("TargetBranchId"); + + b.ToTable("PullRequests"); + }); + + modelBuilder.Entity("Squirrel.Core.DAL.Entities.RefreshToken", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id"), 1L, 1); + + b.Property("CreatedAt") + .ValueGeneratedOnAdd() + .HasColumnType("datetime2") + .HasDefaultValueSql("getutcdate()"); + + b.Property("CreatedBy") + .HasColumnType("int"); + + b.Property("ExpiresAt") + .HasColumnType("datetime2"); + + b.Property("Token") + .IsRequired() + .HasMaxLength(500) + .HasColumnType("nvarchar(500)"); + + b.Property("UserId") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("RefreshTokens"); + }); + + modelBuilder.Entity("Squirrel.Core.DAL.Entities.Script", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id"), 1L, 1); + + b.Property("Content") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("CreatedAt") + .ValueGeneratedOnAdd() + .HasColumnType("datetime2") + .HasDefaultValueSql("getutcdate()"); + + b.Property("CreatedBy") + .IsRequired() + .HasColumnType("int"); + + b.Property("FileName") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("LastUpdatedByUserId") + .HasColumnType("int"); + + b.Property("ProjectId") + .HasColumnType("int"); + + b.Property("Title") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("UpdatedAt") + .ValueGeneratedOnAddOrUpdate() + .HasColumnType("datetime2") + .HasDefaultValueSql("getutcdate()"); + + b.HasKey("Id"); + + b.HasIndex("CreatedBy"); + + b.HasIndex("LastUpdatedByUserId"); + + b.HasIndex("ProjectId"); + + b.ToTable("Scripts"); + }); + + modelBuilder.Entity("Squirrel.Core.DAL.Entities.Tag", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id"), 1L, 1); + + b.Property("Name") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.HasKey("Id"); + + b.ToTable("Tags"); + }); + + modelBuilder.Entity("Squirrel.Core.DAL.Entities.User", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id"), 1L, 1); + + b.Property("AvatarUrl") + .HasMaxLength(500) + .HasColumnType("nvarchar(500)"); + + b.Property("Email") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.Property("EmailNotification") + .HasColumnType("bit"); + + b.Property("FirstName") + .IsRequired() + .HasMaxLength(25) + .HasColumnType("nvarchar(25)"); + + b.Property("IsGoogleAuth") + .HasColumnType("bit"); + + b.Property("LastName") + .IsRequired() + .HasMaxLength(25) + .HasColumnType("nvarchar(25)"); + + b.Property("PasswordHash") + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("Salt") + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("SquirrelNotification") + .HasColumnType("bit"); + + b.Property("Username") + .IsRequired() + .HasMaxLength(25) + .HasColumnType("nvarchar(25)"); + + b.HasKey("Id"); + + b.HasAlternateKey("Email"); + + b.HasIndex("Email") + .IsUnique(); + + b.HasIndex("Username") + .IsUnique(); + + b.ToTable("Users"); + }); + + modelBuilder.Entity("Squirrel.Core.DAL.Entities.Branch", b => + { + b.HasOne("Squirrel.Core.DAL.Entities.User", "Author") + .WithMany("Branches") + .HasForeignKey("CreatedBy") + .OnDelete(DeleteBehavior.NoAction) + .IsRequired(); + + b.HasOne("Squirrel.Core.DAL.Entities.Branch", "ParentBranch") + .WithMany() + .HasForeignKey("ParentBranchId"); + + b.HasOne("Squirrel.Core.DAL.Entities.Project", "Project") + .WithMany("Branches") + .HasForeignKey("ProjectId") + .OnDelete(DeleteBehavior.NoAction) + .IsRequired(); + + b.Navigation("Author"); + + b.Navigation("ParentBranch"); + + b.Navigation("Project"); + }); + + modelBuilder.Entity("Squirrel.Core.DAL.Entities.ChangeRecord", b => + { + b.HasOne("Squirrel.Core.DAL.Entities.User", "User") + .WithMany("ChangeRecords") + .HasForeignKey("CreatedBy") + .OnDelete(DeleteBehavior.NoAction) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Squirrel.Core.DAL.Entities.Comment", b => + { + b.HasOne("Squirrel.Core.DAL.Entities.User", "Author") + .WithMany("Comments") + .HasForeignKey("CreatedBy") + .OnDelete(DeleteBehavior.NoAction) + .IsRequired(); + + b.Navigation("Author"); + }); + + modelBuilder.Entity("Squirrel.Core.DAL.Entities.Commit", b => + { + b.HasOne("Squirrel.Core.DAL.Entities.User", "Author") + .WithMany("Commits") + .HasForeignKey("CreatedBy") + .OnDelete(DeleteBehavior.NoAction) + .IsRequired(); + + b.Navigation("Author"); + }); + + modelBuilder.Entity("Squirrel.Core.DAL.Entities.CommitFile", b => + { + b.HasOne("Squirrel.Core.DAL.Entities.Commit", "Commit") + .WithMany("CommitFiles") + .HasForeignKey("CommitId") + .OnDelete(DeleteBehavior.NoAction) + .IsRequired(); + + b.Navigation("Commit"); + }); + + modelBuilder.Entity("Squirrel.Core.DAL.Entities.CommitParent", b => + { + b.HasOne("Squirrel.Core.DAL.Entities.Commit", "Commit") + .WithMany("CommitsAsChild") + .HasForeignKey("CommitId") + .OnDelete(DeleteBehavior.NoAction) + .IsRequired(); + + b.HasOne("Squirrel.Core.DAL.Entities.Commit", "ParentCommit") + .WithMany("CommitsAsParent") + .HasForeignKey("ParentId") + .OnDelete(DeleteBehavior.NoAction) + .IsRequired(); + + b.Navigation("Commit"); + + b.Navigation("ParentCommit"); + }); + + modelBuilder.Entity("Squirrel.Core.DAL.Entities.JoinEntities.BranchCommit", b => + { + b.HasOne("Squirrel.Core.DAL.Entities.Branch", "Branch") + .WithMany("BranchCommits") + .HasForeignKey("BranchId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Squirrel.Core.DAL.Entities.Commit", "Commit") + .WithMany("BranchCommits") + .HasForeignKey("CommitId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Branch"); + + b.Navigation("Commit"); + }); + + modelBuilder.Entity("Squirrel.Core.DAL.Entities.JoinEntities.ProjectTag", b => + { + b.HasOne("Squirrel.Core.DAL.Entities.Project", "Project") + .WithMany("ProjectTags") + .HasForeignKey("ProjectId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Squirrel.Core.DAL.Entities.Tag", "Tag") + .WithMany("ProjectTags") + .HasForeignKey("TagId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Project"); + + b.Navigation("Tag"); + }); + + modelBuilder.Entity("Squirrel.Core.DAL.Entities.JoinEntities.PullRequestReviewer", b => + { + b.HasOne("Squirrel.Core.DAL.Entities.PullRequest", "PullRequest") + .WithMany("PullRequestReviewers") + .HasForeignKey("PullRequestId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Squirrel.Core.DAL.Entities.User", "User") + .WithMany("PullRequestReviewers") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("PullRequest"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Squirrel.Core.DAL.Entities.JoinEntities.UserProject", b => + { + b.HasOne("Squirrel.Core.DAL.Entities.Project", "Project") + .WithMany("UserProjects") + .HasForeignKey("ProjectId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Squirrel.Core.DAL.Entities.User", "User") + .WithMany("UserProjects") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Project"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Squirrel.Core.DAL.Entities.Project", b => + { + b.HasOne("Squirrel.Core.DAL.Entities.User", "Author") + .WithMany("OwnProjects") + .HasForeignKey("CreatedBy") + .OnDelete(DeleteBehavior.NoAction) + .IsRequired(); + + b.HasOne("Squirrel.Core.DAL.Entities.Branch", "DefaultBranch") + .WithOne() + .HasForeignKey("Squirrel.Core.DAL.Entities.Project", "DefaultBranchId") + .OnDelete(DeleteBehavior.NoAction); + + b.Navigation("Author"); + + b.Navigation("DefaultBranch"); + }); + + modelBuilder.Entity("Squirrel.Core.DAL.Entities.ProjectDatabase", b => + { + b.HasOne("Squirrel.Core.DAL.Entities.Project", "Project") + .WithMany("ProjectDatabases") + .HasForeignKey("ProjectId") + .OnDelete(DeleteBehavior.NoAction) + .IsRequired(); + + b.Navigation("Project"); + }); + + modelBuilder.Entity("Squirrel.Core.DAL.Entities.PullRequest", b => + { + b.HasOne("Squirrel.Core.DAL.Entities.User", "Author") + .WithMany("PullRequests") + .HasForeignKey("CreatedBy") + .OnDelete(DeleteBehavior.NoAction) + .IsRequired(); + + b.HasOne("Squirrel.Core.DAL.Entities.Project", "Project") + .WithMany("PullRequests") + .HasForeignKey("ProjectId") + .OnDelete(DeleteBehavior.NoAction) + .IsRequired(); + + b.HasOne("Squirrel.Core.DAL.Entities.Branch", "SourceBranch") + .WithMany("PullRequestsFromThisBranch") + .HasForeignKey("SourceBranchId") + .OnDelete(DeleteBehavior.NoAction) + .IsRequired(); + + b.HasOne("Squirrel.Core.DAL.Entities.Branch", "TargetBranch") + .WithMany("PullRequestsIntoThisBranch") + .HasForeignKey("TargetBranchId") + .OnDelete(DeleteBehavior.NoAction) + .IsRequired(); + + b.Navigation("Author"); + + b.Navigation("Project"); + + b.Navigation("SourceBranch"); + + b.Navigation("TargetBranch"); + }); + + modelBuilder.Entity("Squirrel.Core.DAL.Entities.RefreshToken", b => + { + b.HasOne("Squirrel.Core.DAL.Entities.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Squirrel.Core.DAL.Entities.Script", b => + { + b.HasOne("Squirrel.Core.DAL.Entities.User", "Author") + .WithMany("Scripts") + .HasForeignKey("CreatedBy") + .OnDelete(DeleteBehavior.NoAction) + .IsRequired(); + + b.HasOne("Squirrel.Core.DAL.Entities.User", "LastUpdatedBy") + .WithMany() + .HasForeignKey("LastUpdatedByUserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Squirrel.Core.DAL.Entities.Project", "Project") + .WithMany("Scripts") + .HasForeignKey("ProjectId") + .OnDelete(DeleteBehavior.NoAction) + .IsRequired(); + + b.Navigation("Author"); + + b.Navigation("LastUpdatedBy"); + + b.Navigation("Project"); + }); + + modelBuilder.Entity("Squirrel.Core.DAL.Entities.Branch", b => + { + b.Navigation("BranchCommits"); + + b.Navigation("PullRequestsFromThisBranch"); + + b.Navigation("PullRequestsIntoThisBranch"); + }); + + modelBuilder.Entity("Squirrel.Core.DAL.Entities.Commit", b => + { + b.Navigation("BranchCommits"); + + b.Navigation("CommitFiles"); + + b.Navigation("CommitsAsChild"); + + b.Navigation("CommitsAsParent"); + }); + + modelBuilder.Entity("Squirrel.Core.DAL.Entities.Project", b => + { + b.Navigation("Branches"); + + b.Navigation("ProjectDatabases"); + + b.Navigation("ProjectTags"); + + b.Navigation("PullRequests"); + + b.Navigation("Scripts"); + + b.Navigation("UserProjects"); + }); + + modelBuilder.Entity("Squirrel.Core.DAL.Entities.PullRequest", b => + { + b.Navigation("PullRequestReviewers"); + }); + + modelBuilder.Entity("Squirrel.Core.DAL.Entities.Tag", b => + { + b.Navigation("ProjectTags"); + }); + + modelBuilder.Entity("Squirrel.Core.DAL.Entities.User", b => + { + b.Navigation("Branches"); + + b.Navigation("ChangeRecords"); + + b.Navigation("Comments"); + + b.Navigation("Commits"); + + b.Navigation("OwnProjects"); + + b.Navigation("PullRequestReviewers"); + + b.Navigation("PullRequests"); + + b.Navigation("Scripts"); + + b.Navigation("UserProjects"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/backend/Squirrel.Core/Squirrel.Core.DAL/Migrations/20230929104151_UserManyToOneBranch.cs b/backend/Squirrel.Core/Squirrel.Core.DAL/Migrations/20230929104151_UserManyToOneBranch.cs new file mode 100644 index 000000000..63637dddc --- /dev/null +++ b/backend/Squirrel.Core/Squirrel.Core.DAL/Migrations/20230929104151_UserManyToOneBranch.cs @@ -0,0 +1,65 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace Squirrel.Core.DAL.Migrations +{ + public partial class UserManyToOneBranch : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropForeignKey( + name: "FK_Branches_Users_AuthorId", + table: "Branches"); + + migrationBuilder.DropIndex( + name: "IX_Branches_AuthorId", + table: "Branches"); + + migrationBuilder.DropColumn( + name: "AuthorId", + table: "Branches"); + + migrationBuilder.CreateIndex( + name: "IX_Branches_CreatedBy", + table: "Branches", + column: "CreatedBy"); + + migrationBuilder.AddForeignKey( + name: "FK_Branches_Users_CreatedBy", + table: "Branches", + column: "CreatedBy", + principalTable: "Users", + principalColumn: "Id"); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropForeignKey( + name: "FK_Branches_Users_CreatedBy", + table: "Branches"); + + migrationBuilder.DropIndex( + name: "IX_Branches_CreatedBy", + table: "Branches"); + + migrationBuilder.AddColumn( + name: "AuthorId", + table: "Branches", + type: "int", + nullable: true); + + migrationBuilder.CreateIndex( + name: "IX_Branches_AuthorId", + table: "Branches", + column: "AuthorId"); + + migrationBuilder.AddForeignKey( + name: "FK_Branches_Users_AuthorId", + table: "Branches", + column: "AuthorId", + principalTable: "Users", + principalColumn: "Id"); + } + } +} diff --git a/backend/Squirrel.Core/Squirrel.Core.DAL/Migrations/SquirrelCoreContextModelSnapshot.cs b/backend/Squirrel.Core/Squirrel.Core.DAL/Migrations/SquirrelCoreContextModelSnapshot.cs index 7e7e705fc..d91421e46 100644 --- a/backend/Squirrel.Core/Squirrel.Core.DAL/Migrations/SquirrelCoreContextModelSnapshot.cs +++ b/backend/Squirrel.Core/Squirrel.Core.DAL/Migrations/SquirrelCoreContextModelSnapshot.cs @@ -30,6 +30,14 @@ protected override void BuildModel(ModelBuilder modelBuilder) SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id"), 1L, 1); + b.Property("CreatedAt") + .ValueGeneratedOnAdd() + .HasColumnType("datetime2") + .HasDefaultValueSql("getutcdate()"); + + b.Property("CreatedBy") + .HasColumnType("int"); + b.Property("IsActive") .HasColumnType("bit"); @@ -46,6 +54,8 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.HasKey("Id"); + b.HasIndex("CreatedBy"); + b.HasIndex("ParentBranchId"); b.HasIndex("ProjectId"); @@ -615,6 +625,12 @@ protected override void BuildModel(ModelBuilder modelBuilder) modelBuilder.Entity("Squirrel.Core.DAL.Entities.Branch", b => { + b.HasOne("Squirrel.Core.DAL.Entities.User", "Author") + .WithMany("Branches") + .HasForeignKey("CreatedBy") + .OnDelete(DeleteBehavior.NoAction) + .IsRequired(); + b.HasOne("Squirrel.Core.DAL.Entities.Branch", "ParentBranch") .WithMany() .HasForeignKey("ParentBranchId"); @@ -625,6 +641,8 @@ protected override void BuildModel(ModelBuilder modelBuilder) .OnDelete(DeleteBehavior.NoAction) .IsRequired(); + b.Navigation("Author"); + b.Navigation("ParentBranch"); b.Navigation("Project"); @@ -918,6 +936,8 @@ protected override void BuildModel(ModelBuilder modelBuilder) modelBuilder.Entity("Squirrel.Core.DAL.Entities.User", b => { + b.Navigation("Branches"); + b.Navigation("ChangeRecords"); b.Navigation("Comments"); diff --git a/backend/Squirrel.Core/Squirrel.Core.WebAPI/Controllers/BranchController.cs b/backend/Squirrel.Core/Squirrel.Core.WebAPI/Controllers/BranchController.cs index 1ce76f27b..97ff305b4 100644 --- a/backend/Squirrel.Core/Squirrel.Core.WebAPI/Controllers/BranchController.cs +++ b/backend/Squirrel.Core/Squirrel.Core.WebAPI/Controllers/BranchController.cs @@ -1,7 +1,10 @@ -using Microsoft.AspNetCore.Authorization; +using AutoMapper; +using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using Squirrel.Core.BLL.Interfaces; using Squirrel.Core.Common.DTO.Branch; +using Squirrel.Core.Common.DTO.Commit; +using Squirrel.Core.DAL.Entities; namespace Squirrel.Core.WebAPI.Controllers; [Route("api/[controller]")] @@ -22,6 +25,12 @@ public ActionResult> GetAllBranches(int projectId) return Ok(_branchService.GetAllBranches(projectId)); } + [HttpGet("{projectId}/{sourceId}")] + public async Task>> GetAllBranchDetails(int projectId, int sourceId) + { + return Ok(await _branchService.GetAllBranchDetailsAsync(projectId, sourceId)); + } + [HttpGet("lastcommit/{branchId}")] public async Task> GetLastBranchCommit(int branchId) { @@ -34,6 +43,12 @@ public async Task> AddBranchAsync(int projectId, [FromBo return Ok(await _branchService.AddBranchAsync(projectId, dto)); } + [HttpPost("merge")] + public async Task> MergeBranch([FromBody] MergeBranchDto dto) + { + return Ok(await _branchService.MergeBranchAsync(dto.SourceId, dto.DestinationId)); + } + [HttpPut("{branchId}")] public async Task> UpdateBranchAsync(int branchId, [FromBody] BranchUpdateDto dto) { diff --git a/backend/Squirrel.Core/Squirrel.Core.WebAPI/Validators/Branch/MergeBranchDtoValidator.cs b/backend/Squirrel.Core/Squirrel.Core.WebAPI/Validators/Branch/MergeBranchDtoValidator.cs new file mode 100644 index 000000000..ad14bbd25 --- /dev/null +++ b/backend/Squirrel.Core/Squirrel.Core.WebAPI/Validators/Branch/MergeBranchDtoValidator.cs @@ -0,0 +1,13 @@ +using FluentValidation; +using Squirrel.Core.Common.DTO.Branch; + +namespace Squirrel.Core.WebAPI.Validators.Branch; + +public class MergeBranchDtoValidator: AbstractValidator +{ + public MergeBranchDtoValidator() + { + RuleFor(x => x.SourceId) + .NotEqual(x => x.DestinationId); + } +} diff --git a/docker-compose.apps.prod.yml b/docker-compose.apps.prod.yml index 82f4c07d8..3abfb45b0 100644 --- a/docker-compose.apps.prod.yml +++ b/docker-compose.apps.prod.yml @@ -13,10 +13,6 @@ services: ASPNETCORE_ENVIRONMENT: 'Production' env_file: - /etc/environment - volumes: - - ./resources:/app/Resources - - ./logs:/app/Logs - squirrel_notifier: image: alinasielina/squirrel_notifier:latest @@ -43,8 +39,6 @@ services: ASPNETCORE_ENVIRONMENT: 'Production' env_file: - /etc/environment - volumes: - - ./logs:/app/Logs squirrel_frontend: depends_on: @@ -65,4 +59,4 @@ networks: back: driver: bridge front: - driver: bridge \ No newline at end of file + driver: bridge diff --git a/frontend/src/app/core/services/branch.service.ts b/frontend/src/app/core/services/branch.service.ts index c8ad3c6e7..547944c3f 100644 --- a/frontend/src/app/core/services/branch.service.ts +++ b/frontend/src/app/core/services/branch.service.ts @@ -1,8 +1,11 @@ import { Injectable } from '@angular/core'; +import { BranchDetailsDto } from 'src/app/models/branch/branch-details-dto'; import { BranchDto } from 'src/app/models/branch/branch-dto'; import { CreateBranchDto } from 'src/app/models/branch/create-branch-dto'; +import { MergeBranchDto } from 'src/app/models/branch/merge-branch-dto'; +import { EventService } from './event.service'; import { HttpInternalService } from './http-internal.service'; @Injectable({ @@ -11,13 +14,20 @@ import { HttpInternalService } from './http-internal.service'; export class BranchService { private routePrefix = '/api/branch'; - // eslint-disable-next-line no-empty-function - constructor(private httpService: HttpInternalService) { } + constructor( + private httpService: HttpInternalService, + private eventService: EventService, + // eslint-disable-next-line no-empty-function + ) { } public getAllBranches(projectId: number) { return this.httpService.getRequest(`${this.routePrefix}/${projectId}`); } + public getAllBranchDetails(projectId: number, selectedBranchId: number) { + return this.httpService.getRequest(`${this.routePrefix}/${projectId}/${selectedBranchId}`); + } + public getLastCommitId(branchId: number) { return this.httpService.getRequest(`${this.routePrefix}/lastcommit/${branchId}`); } @@ -28,9 +38,14 @@ export class BranchService { public selectBranch(projectId: number, branchId: number) { localStorage.setItem(`currentBranch_${projectId}`, branchId.toString()); + this.eventService.branchChanged(branchId); } public getCurrentBranch(projectId: number) { return Number(localStorage.getItem(`currentBranch_${projectId}`)); } + + public mergeBranch(dto: MergeBranchDto) { + return this.httpService.postRequest(`${this.routePrefix}/merge`, dto); + } } diff --git a/frontend/src/app/core/services/event.service.ts b/frontend/src/app/core/services/event.service.ts index 6d3503d26..e1ec5b06b 100644 --- a/frontend/src/app/core/services/event.service.ts +++ b/frontend/src/app/core/services/event.service.ts @@ -14,16 +14,21 @@ export class EventService { public changesSavedEvent$: Observable; + public branchChangedEvent$: Observable; + private onChangesSaved = new Subject(); private onUserChanged = new Subject(); private onChangesLoaded = new Subject(); + private onBranchChanged = new Subject(); + constructor() { this.userChangedEvent$ = this.onUserChanged.asObservable(); this.changesLoadedEvent$ = this.onChangesLoaded.asObservable(); this.changesSavedEvent$ = this.onChangesSaved.asObservable(); + this.branchChangedEvent$ = this.onBranchChanged.asObservable(); } public changesLoaded(item: DatabaseItem[] | undefined) { @@ -37,4 +42,8 @@ export class EventService { public changesSaved(guid: string | undefined) { this.onChangesSaved.next(guid); } + + public branchChanged(branchId: number | undefined) { + this.onBranchChanged.next(branchId); + } } diff --git a/frontend/src/app/material/material.module.ts b/frontend/src/app/material/material.module.ts index 08d745e3f..d9b4ae7b2 100644 --- a/frontend/src/app/material/material.module.ts +++ b/frontend/src/app/material/material.module.ts @@ -5,8 +5,11 @@ import { MatButtonModule } from '@angular/material/button'; import { MatCardModule } from '@angular/material/card'; import { MatChipsModule } from '@angular/material/chips'; import { MatDialogModule } from '@angular/material/dialog'; +import { MatFormFieldModule } from '@angular/material/form-field'; import { MatIconModule } from '@angular/material/icon'; +import { MatInputModule } from '@angular/material/input'; import { MatMenuModule } from '@angular/material/menu'; +import { MatSelectModule } from '@angular/material/select'; import { MatTableModule } from '@angular/material/table'; import { MatToolbarModule } from '@angular/material/toolbar'; @@ -23,6 +26,9 @@ import { MatToolbarModule } from '@angular/material/toolbar'; MatDialogModule, MatTableModule, MatMenuModule, + MatFormFieldModule, + MatInputModule, + MatSelectModule, ], exports: [ CommonModule, @@ -35,6 +41,9 @@ import { MatToolbarModule } from '@angular/material/toolbar'; MatDialogModule, MatTableModule, MatMenuModule, + MatFormFieldModule, + MatInputModule, + MatSelectModule, ], }) export class MaterialModule {} diff --git a/frontend/src/app/models/branch/merge-branch-dto.ts b/frontend/src/app/models/branch/merge-branch-dto.ts new file mode 100644 index 000000000..6a45eaf19 --- /dev/null +++ b/frontend/src/app/models/branch/merge-branch-dto.ts @@ -0,0 +1,4 @@ +export interface MergeBranchDto { + sourceId: number, + destinationId: number +} diff --git a/frontend/src/app/modules/branches/branch-list/branch-list.component.html b/frontend/src/app/modules/branches/branch-list/branch-list.component.html index f69d1c431..c18df2591 100644 --- a/frontend/src/app/modules/branches/branch-list/branch-list.component.html +++ b/frontend/src/app/modules/branches/branch-list/branch-list.component.html @@ -1,6 +1,7 @@
Branches
+
diff --git a/frontend/src/app/modules/branches/branch-list/branch-list.component.ts b/frontend/src/app/modules/branches/branch-list/branch-list.component.ts index 584d04667..dde2668bb 100644 --- a/frontend/src/app/modules/branches/branch-list/branch-list.component.ts +++ b/frontend/src/app/modules/branches/branch-list/branch-list.component.ts @@ -1,67 +1,120 @@ -import { Component } from '@angular/core'; +import { Component, OnInit } from '@angular/core'; import { FormBuilder, FormGroup } from '@angular/forms'; +import { MatDialog } from '@angular/material/dialog'; +import { BaseComponent } from '@core/base/base.component'; +import { BranchService } from '@core/services/branch.service'; +import { EventService } from '@core/services/event.service'; +import { NotificationService } from '@core/services/notification.service'; +import { ProjectService } from '@core/services/project.service'; +import { SpinnerService } from '@core/services/spinner.service'; +import { finalize, takeUntil } from 'rxjs'; import { BranchDetailsDto } from 'src/app/models/branch/branch-details-dto'; -import { UserDto } from 'src/app/models/user/user-dto'; +import { BranchDto } from 'src/app/models/branch/branch-dto'; + +import { BranchMergeModalComponent } from '../branch-merge-modal/branch-merge-modal.component'; @Component({ selector: 'app-branch-list', templateUrl: './branch-list.component.html', styleUrls: ['./branch-list.component.sass'], }) -export class BranchListComponent { +export class BranchListComponent extends BaseComponent implements OnInit { public dropdownItems: string[]; public branches: BranchDetailsDto[]; + public filteredBranches: BranchDetailsDto[]; + public searchForm: FormGroup = new FormGroup({}); - constructor(private fb: FormBuilder) { + private branchList: BranchDto[] = []; + + private previousSelection: string[] = []; + + constructor( + private fb: FormBuilder, + private dialog: MatDialog, + private branchService: BranchService, + private projectService: ProjectService, + private spinner: SpinnerService, + private notificationService: NotificationService, + private eventService: EventService, + ) { + super(); this.dropdownItems = this.getBranchTypes(); this.searchForm = this.fb.group({ search: ['', []], }); + } - this.branches = this.getBranches(); + public ngOnInit(): void { + this.getBranchesList(); + this.getBranches(); + this.eventService.branchChangedEvent$ + .pipe(takeUntil(this.unsubscribe$)).subscribe((x) => { + if (x !== undefined) { + this.getBranches(); + } + }); } public getBranchTypes() { - // TODO: fetch data from server, remove placeholder data - return ['All', 'Open', 'Merged']; + return ['All', 'Active']; + } + + public onBranchTypeSelectionChange($event: string[]) { + this.previousSelection = $event; + if ($event.some(x => x === 'All')) { + this.filteredBranches = this.branches; + } + + if ($event.some(x => x === 'Active')) { + this.filteredBranches = this.branches.filter((x) => x.isActive); + } + } + + public openMergeDialog() { + const dialogRef = this.dialog.open(BranchMergeModalComponent, { + data: { + projectId: this.projectService.currentProjectId, + branches: this.branchList }, + }); + + dialogRef.componentInstance.branchMerged.subscribe((branch) => { + this.branchService.selectBranch(this.projectService.currentProjectId, branch.id); + }); } - public onBranchTypeSelectionChange($event: any) { - // TODO: add filter logic - // eslint-disable-next-line no-console - console.log($event); + public getBranchesList() { + this.branchService + .getAllBranches(this.projectService.currentProjectId) + .pipe(takeUntil(this.unsubscribe$)) + .subscribe((branches) => { + this.branchList = branches; + }); } public getBranches() { - const user = { - id: 1, - avatarUrl: 'https://picsum.photos/200', - email: 'test@test.test', - firstName: 'John', - lastName: 'Smith', - userName: 'Johnny', - } as UserDto; - const date = new Date(1478708162000); - const branches = []; - - for (let i = 0; i < 15; i++) { - const branch = { - name: 'task/fix-empty-list-generation', - isActive: true, - lastUpdatedBy: user, - ahead: Number((Math.random() * 5).toFixed(0)), - behind: Number((Math.random() * 5).toFixed(0)), - createdAt: date, - updatedAt: date, - } as BranchDetailsDto; - - branches.push(branch); - } + this.spinner.show(); + const projectId = this.projectService.currentProjectId; - return branches; + this.branchService + .getAllBranchDetails(projectId, this.branchService.getCurrentBranch(projectId)) + .pipe( + takeUntil(this.unsubscribe$), + finalize(() => { + this.spinner.hide(); + }), + ) + .subscribe( + (branches: BranchDetailsDto[]) => { + this.branches = branches; + this.onBranchTypeSelectionChange(this.previousSelection); + }, + () => { + this.notificationService.error('Failed to load branches'); + }, + ); } } diff --git a/frontend/src/app/modules/branches/branch-merge-modal/branch-merge-modal.component.html b/frontend/src/app/modules/branches/branch-merge-modal/branch-merge-modal.component.html new file mode 100644 index 000000000..8b8a7d09e --- /dev/null +++ b/frontend/src/app/modules/branches/branch-merge-modal/branch-merge-modal.component.html @@ -0,0 +1,28 @@ + +

Merge Branch

+ + + + + + + + + + + diff --git a/frontend/src/app/modules/branches/branch-merge-modal/branch-merge-modal.component.sass b/frontend/src/app/modules/branches/branch-merge-modal/branch-merge-modal.component.sass new file mode 100644 index 000000000..2e797b154 --- /dev/null +++ b/frontend/src/app/modules/branches/branch-merge-modal/branch-merge-modal.component.sass @@ -0,0 +1,11 @@ +@import "/src/styles/reusable" + +.full-width + width: 100% + height: 40% + +.modal-buttons + gap: 5% + +.drop-down-container + margin-top: 20px diff --git a/frontend/src/app/modules/branches/branch-merge-modal/branch-merge-modal.component.ts b/frontend/src/app/modules/branches/branch-merge-modal/branch-merge-modal.component.ts new file mode 100644 index 000000000..b0a7073a0 --- /dev/null +++ b/frontend/src/app/modules/branches/branch-merge-modal/branch-merge-modal.component.ts @@ -0,0 +1,89 @@ +import { Component, EventEmitter, Inject, Input, OnInit, Output } from '@angular/core'; +import { FormBuilder, FormGroup, Validators } from '@angular/forms'; +import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog'; +import { BaseComponent } from '@core/base/base.component'; +import { BranchService } from '@core/services/branch.service'; +import { NotificationService } from '@core/services/notification.service'; +import { SpinnerService } from '@core/services/spinner.service'; +import { catchError, Observable, of, takeUntil, tap } from 'rxjs'; + +import { BranchDto } from 'src/app/models/branch/branch-dto'; +import { MergeBranchDto } from 'src/app/models/branch/merge-branch-dto'; + +@Component({ + selector: 'app-branch-merge-modal', + templateUrl: './branch-merge-modal.component.html', + styleUrls: ['./branch-merge-modal.component.sass'], +}) +export class BranchMergeModalComponent extends BaseComponent implements OnInit { + @Input() public projectId: number; + + @Output() public branchMerged = new EventEmitter(); + + public branches: BranchDto[] = []; + + public branchForm: FormGroup; + + constructor( + public dialogRef: MatDialogRef, + private fb: FormBuilder, + private branchService: BranchService, + private notificationService: NotificationService, + private spinner: SpinnerService, + @Inject(MAT_DIALOG_DATA) public data: { projectId: number, branches: BranchDto[] }, + ) { + super(); + this.projectId = data.projectId; + this.branches = data.branches; + } + + public mergeBranch() { + this.spinner.show(); + const dto = { + destinationId: this.branchForm.value.destination, + sourceId: this.branchForm.value.source, + } as MergeBranchDto; + + this.branchService + .mergeBranch(dto) + .pipe( + catchError((): Observable => { + this.notificationService.error('Failed to merge branch'); + + return of(null); + }), + takeUntil(this.unsubscribe$), + tap(() => this.spinner.hide()), + + ) + .subscribe( + (mergedBranch: BranchDto | null) => { + if (mergedBranch) { + this.dialogRef.close(mergedBranch); + this.notificationService.info('Branch merged successfully'); + this.branchMerged.emit(mergedBranch); + } + }, + ); + } + + public validate() { + return this.branchForm.valid && + (this.branchForm.value.source !== this.branchForm.value.destination); + } + + public createForm() { + this.branchForm = this.fb.group({ + source: ['', Validators.required], + destination: ['', Validators.required], + }); + } + + public close() { + this.dialogRef.close(); + } + + public ngOnInit(): void { + this.createForm(); + } +} diff --git a/frontend/src/app/modules/branches/branch/branch.component.html b/frontend/src/app/modules/branches/branch/branch.component.html index 94a6c8d59..532b3a303 100644 --- a/frontend/src/app/modules/branches/branch/branch.component.html +++ b/frontend/src/app/modules/branches/branch/branch.component.html @@ -4,8 +4,8 @@
-
{{branch.behind}}
-
{{branch.ahead}}
+
{{branch.behind}}
+
{{branch.ahead}}
@@ -13,7 +13,7 @@
- + {{calculateTime(branch.updatedAt)}}
diff --git a/frontend/src/app/modules/branches/branch/branch.component.sass b/frontend/src/app/modules/branches/branch/branch.component.sass index 95bf264f3..97614ae3c 100644 --- a/frontend/src/app/modules/branches/branch/branch.component.sass +++ b/frontend/src/app/modules/branches/branch/branch.component.sass @@ -52,7 +52,6 @@ .activity flex: 20 display: flex - justify-content: center .updated-by display: flex justify-content: center @@ -64,21 +63,7 @@ justify-content: center .avatar - width: 44px - height: 44px - min-width: 44px - min-height: 44px - border-radius: 50% - overflow: hidden - outline: 4px solid $avatar-offline-outline margin: 15px - .online - outline: 4px solid $avatar-online-outline - img - width: 100% - height: 100% - border-radius: 50% - object-fit: cover .primary-info display: flex diff --git a/frontend/src/app/modules/branches/branch/branch.component.ts b/frontend/src/app/modules/branches/branch/branch.component.ts index de841c9eb..2300f88aa 100644 --- a/frontend/src/app/modules/branches/branch/branch.component.ts +++ b/frontend/src/app/modules/branches/branch/branch.component.ts @@ -12,7 +12,12 @@ export class BranchComponent { @Input() public branch: BranchDetailsDto; public calculateTime(date: Date): string { - return moment(date).startOf('seconds').fromNow(); + const mDate = new Date(date); + const time = mDate.getTime(); + + const localDate = new Date(time - mDate.getTimezoneOffset() * 60 * 1000); + + return moment(localDate).startOf('seconds').fromNow(); } public calculateFilling(): string { diff --git a/frontend/src/app/modules/branches/branches.module.ts b/frontend/src/app/modules/branches/branches.module.ts index ae331a2f9..592422c6d 100644 --- a/frontend/src/app/modules/branches/branches.module.ts +++ b/frontend/src/app/modules/branches/branches.module.ts @@ -6,12 +6,14 @@ import { MaterialModule } from 'src/app/material/material.module'; import { BranchComponent } from './branch/branch.component'; import { BranchListComponent } from './branch-list/branch-list.component'; +import { BranchMergeModalComponent } from './branch-merge-modal/branch-merge-modal.component'; import { BranchesRoutingModule } from './branches-routing.module'; @NgModule({ declarations: [ BranchListComponent, BranchComponent, + BranchMergeModalComponent, ], imports: [ CommonModule, diff --git a/frontend/src/app/modules/downloads/downloads-page/downloads.component.html b/frontend/src/app/modules/downloads/downloads-page/downloads.component.html index c2cdd3a4d..7589e363c 100644 --- a/frontend/src/app/modules/downloads/downloads-page/downloads.component.html +++ b/frontend/src/app/modules/downloads/downloads-page/downloads.component.html @@ -20,11 +20,11 @@

Downloads

- + - +
diff --git a/frontend/src/app/modules/main/create-db-modal/create-db-modal.component.html b/frontend/src/app/modules/main/create-db-modal/create-db-modal.component.html index 2a186ad5d..5c7a25fc1 100644 --- a/frontend/src/app/modules/main/create-db-modal/create-db-modal.component.html +++ b/frontend/src/app/modules/main/create-db-modal/create-db-modal.component.html @@ -1,6 +1,6 @@ -