diff --git a/EventsExpress.Core/DTOs/ChildEventDTO.cs b/EventsExpress.Core/DTOs/ChildEventDTO.cs new file mode 100644 index 000000000..217c19f69 --- /dev/null +++ b/EventsExpress.Core/DTOs/ChildEventDTO.cs @@ -0,0 +1,32 @@ +using System; +using System.Collections.Generic; +using EventsExpress.Db.Entities; +using EventsExpress.Db.Enums; +using Microsoft.AspNetCore.Http; +using NetTopologySuite.Geometries; + +namespace EventsExpress.Core.DTOs +{ + public class ChildEventDto + { + public Guid Id { get; set; } + + public string Title { get; set; } + + public string Description { get; set; } + + public DateTime? DateFrom { get; set; } + + public DateTime? DateTo { get; set; } + + public Point Point { get; set; } + + public LocationType Type { get; set; } + + public Uri OnlineMeeting { get; set; } + + public EventStatus EventStatus { get; set; } + + public bool? IsMultiEvent { get; set; } + } +} diff --git a/EventsExpress.Core/DTOs/EventDTO.cs b/EventsExpress.Core/DTOs/EventDTO.cs index 0a959af88..b908653eb 100644 --- a/EventsExpress.Core/DTOs/EventDTO.cs +++ b/EventsExpress.Core/DTOs/EventDTO.cs @@ -48,5 +48,9 @@ public class EventDto public IEnumerable OwnerIds { get; set; } public IEnumerable Owners { get; set; } + + public bool? IsMultiEvent { get; set; } + + public IEnumerable Events { get; set; } } } diff --git a/EventsExpress.Core/IServices/IEventService.cs b/EventsExpress.Core/IServices/IEventService.cs index aad38f022..d79355ec0 100644 --- a/EventsExpress.Core/IServices/IEventService.cs +++ b/EventsExpress.Core/IServices/IEventService.cs @@ -14,7 +14,7 @@ public interface IEventService Task EditNextEvent(EventDto eventDTO); - Task Edit(EventDto e); + Task Edit(EventDto eventInstance); Task Publish(Guid eventId); diff --git a/EventsExpress.Core/Services/EventService.cs b/EventsExpress.Core/Services/EventService.cs index 09b9b0c6e..47864d5be 100644 --- a/EventsExpress.Core/Services/EventService.cs +++ b/EventsExpress.Core/Services/EventService.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Threading; using System.Threading.Tasks; using AutoMapper; using EventsExpress.Core.DTOs; @@ -221,7 +222,7 @@ public async Task CreateNextEvent(Guid eventId) return createResult; } - public async Task Edit(EventDto e) + private async Task InternalEdit(EventDto e) { var ev = Context.Events .Include(e => e.EventLocation) @@ -229,7 +230,6 @@ public async Task Edit(EventDto e) .ThenInclude(c => c.Category) .Include(e => e.EventSchedule) .FirstOrDefault(x => x.Id == e.Id); - if (e.OnlineMeeting != null || e.Point != null) { var locationDTO = Mapper.Map(e); @@ -267,6 +267,7 @@ public async Task Edit(EventDto e) ev.DateFrom = e.DateFrom; ev.DateTo = e.DateTo; ev.IsPublic = e.IsPublic; + ev.IsMultiEvent = e.IsMultiEvent; var eventCategories = e.Categories?.Select(x => new EventCategory { Event = ev, CategoryId = x.Id }) .ToList(); @@ -278,7 +279,42 @@ public async Task Edit(EventDto e) return ev.Id; } - public async Task Publish(Guid eventId) + public async Task Edit(EventDto eventInstance) + { + if (eventInstance.Events.CollectionIsNullOrEmpty()) + { + await InternalEdit(eventInstance); + } + else + { + eventInstance.IsMultiEvent = true; + await InternalEdit(eventInstance); + ChildEventDto[] childEventDtos = eventInstance.Events.ToArray(); + EventDto[] childs = new EventDto[childEventDtos.Length]; + for (int i = 0; i < childEventDtos.Length; i++) + { + childs[i] = Mapper.Map(childEventDtos[i]); + childs[i].Id = CreateDraft(); + childs[i].IsMultiEvent = true; + childs[i].Inventories = eventInstance.Inventories; + childs[i].IsPublic = eventInstance.IsPublic; + childs[i].IsReccurent = eventInstance.IsReccurent; + childs[i].MaxParticipants = eventInstance.MaxParticipants; + childs[i].Categories = eventInstance.Categories; + Context.MultiEventStatus.Add( + new MultiEventStatus + { + ParentId = eventInstance.Id, + ChildId = childs[i].Id, + }); + await InternalEdit(childs[i]); + } + } + + return eventInstance.Id; + } + + private void InternalPublish(Guid eventId) { var ev = Context.Events .Include(e => e.EventLocation) @@ -292,33 +328,36 @@ public async Task Publish(Guid eventId) throw new EventsExpressException("Not found"); } - Dictionary exept = new Dictionary(); - var result = _validator.Validate(ev); - - if (result.IsValid) - { - ev.StatusHistory.Add( + ev.StatusHistory.Add( new EventStatusHistory { EventStatus = EventStatus.Active, CreatedOn = DateTime.UtcNow, UserId = CurrentUserId(), }); - await Context.SaveChangesAsync(); - EventDto dtos = Mapper.Map(ev); - await _mediator.Publish(new EventCreatedMessage(dtos)); - return ev.Id; - } - else + } + + public async Task Publish(Guid eventId) + { + var ev = Context.Events.FirstOrDefault(x => x.Id == eventId); + InternalPublish(eventId); + if (ev.IsMultiEvent == true) { - var p = result.Errors.Select(e => new KeyValuePair(e.PropertyName, e.ErrorMessage)); - foreach (var x in p) + var childsId = Context.MultiEventStatus + .Where(x => x.ParentId == eventId) + .Select(x => x.ChildId) + .ToArray(); + + foreach (var item in childsId) { - exept.Add(x.Key, x.Value); + InternalPublish(item); } - - throw new EventsExpressException("validation failed", exept); } + + await Context.SaveChangesAsync(); + EventDto dtos = Mapper.Map(ev); + await _mediator.Publish(new EventCreatedMessage(dtos)); + return eventId; } public async Task EditNextEvent(EventDto eventDTO) @@ -340,8 +379,7 @@ public async Task EditNextEvent(EventDto eventDTO) public EventDto EventById(Guid eventId) { - var res = Mapper.Map( - Context.Events + var request = Context.Events .Include(e => e.EventLocation) .Include(e => e.Owners) .ThenInclude(o => o.User) @@ -355,9 +393,13 @@ public EventDto EventById(Guid eventId) .ThenInclude(u => u.Relationships) .Include(e => e.StatusHistory) .Include(e => e.EventSchedule) - .FirstOrDefault(x => x.Id == eventId)); + .Include(e => e.ChildEvents) + .ThenInclude(e => e.ChildEvent) + .ThenInclude(e => e.EventLocation) + .FirstOrDefault(x => x.Id == eventId); - return res; + var res = Mapper.Map(request); + return res; } public IEnumerable GetAll(EventFilterViewModel model, out int count) diff --git a/EventsExpress.Db/Configurations/MultiEventStatusConfiguration.cs b/EventsExpress.Db/Configurations/MultiEventStatusConfiguration.cs new file mode 100644 index 000000000..9e77e576c --- /dev/null +++ b/EventsExpress.Db/Configurations/MultiEventStatusConfiguration.cs @@ -0,0 +1,21 @@ +namespace EventsExpress.Db.Configurations +{ + using EventsExpress.Db.Entities; + using Microsoft.EntityFrameworkCore; + using Microsoft.EntityFrameworkCore.Metadata.Builders; + + public class MultiEventStatusConfiguration : IEntityTypeConfiguration + { + public void Configure(EntityTypeBuilder builder) + { + builder.HasOne(e => e.ParentEvent) + .WithMany(ec => ec.ChildEvents) + .HasForeignKey(e => e.ParentId) + .OnDelete(DeleteBehavior.ClientCascade); + builder.HasOne(e => e.ChildEvent) + .WithMany(ec => ec.ParentEvents) + .HasForeignKey(e => e.ChildId) + .OnDelete(DeleteBehavior.ClientCascade); + } + } +} diff --git a/EventsExpress.Db/EF/AppDbContext.cs b/EventsExpress.Db/EF/AppDbContext.cs index d2a2ffe5e..9bd872352 100644 --- a/EventsExpress.Db/EF/AppDbContext.cs +++ b/EventsExpress.Db/EF/AppDbContext.cs @@ -47,6 +47,8 @@ public AppDbContext(DbContextOptions options, ISecurityContext sec public DbSet Events { get; set; } + public DbSet MultiEventStatus { get; set; } + public DbSet EventOwners { get; set; } public DbSet EventLocations { get; set; } diff --git a/EventsExpress.Db/Entities/Event.cs b/EventsExpress.Db/Entities/Event.cs index 191800f30..54d4391a8 100644 --- a/EventsExpress.Db/Entities/Event.cs +++ b/EventsExpress.Db/Entities/Event.cs @@ -29,6 +29,9 @@ public class Event : BaseEntity [Track] public Guid? EventLocationId { get; set; } + [Track] + public bool? IsMultiEvent { get; set; } + public virtual EventSchedule EventSchedule { get; set; } public virtual EventLocation EventLocation { get; set; } @@ -44,5 +47,9 @@ public class Event : BaseEntity public virtual ICollection Inventories { get; set; } public virtual ICollection StatusHistory { get; set; } + + public virtual ICollection ChildEvents { get; set; } + + public virtual ICollection ParentEvents { get; set; } } } diff --git a/EventsExpress.Db/Entities/MultiEventStatus.cs b/EventsExpress.Db/Entities/MultiEventStatus.cs new file mode 100644 index 000000000..31b30fbbc --- /dev/null +++ b/EventsExpress.Db/Entities/MultiEventStatus.cs @@ -0,0 +1,24 @@ +using System; +using System.Collections.Generic; +using EventsExpress.Db.EF; +using EventsExpress.Db.Enums; + +namespace EventsExpress.Db.Entities +{ + [Track] + public class MultiEventStatus + { + [Track] + public Guid Id { get; set; } + + [Track] + public Guid ParentId { get; set; } + + public virtual Event ChildEvent { get; set; } + + public virtual Event ParentEvent { get; set; } + + [Track] + public Guid ChildId { get; set; } + } +} diff --git a/EventsExpress.Db/EventsExpress.Db.csproj b/EventsExpress.Db/EventsExpress.Db.csproj index 0309bba7e..45fa033d0 100644 --- a/EventsExpress.Db/EventsExpress.Db.csproj +++ b/EventsExpress.Db/EventsExpress.Db.csproj @@ -2,7 +2,14 @@ netcoreapp3.1 - + + + + + + + + diff --git a/EventsExpress.Db/Migrations/20211022085402_AddMultiEvents.Designer.cs b/EventsExpress.Db/Migrations/20211022085402_AddMultiEvents.Designer.cs new file mode 100644 index 000000000..d852cab7e --- /dev/null +++ b/EventsExpress.Db/Migrations/20211022085402_AddMultiEvents.Designer.cs @@ -0,0 +1,1152 @@ +// +using System; +using EventsExpress.Db.EF; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using NetTopologySuite.Geometries; + +namespace EventsExpress.Db.Migrations +{ + [DbContext(typeof(AppDbContext))] + [Migration("20211022085402_AddMultiEvents")] + partial class AddMultiEvents + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "3.1.9") + .HasAnnotation("Relational:MaxIdentifierLength", 128) + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + modelBuilder.Entity("EventsExpress.Db.Entities.Account", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("IsBlocked") + .HasColumnType("bit"); + + b.Property("UserId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("UserId") + .IsUnique() + .HasFilter("[UserId] IS NOT NULL"); + + b.ToTable("Accounts"); + }); + + modelBuilder.Entity("EventsExpress.Db.Entities.AccountRole", b => + { + b.Property("AccountId") + .HasColumnType("uniqueidentifier"); + + b.Property("RoleId") + .HasColumnType("smallint"); + + b.HasKey("AccountId", "RoleId"); + + b.HasIndex("RoleId"); + + b.ToTable("AccountRoles"); + }); + + modelBuilder.Entity("EventsExpress.Db.Entities.AuthExternal", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("AccountId") + .HasColumnType("uniqueidentifier"); + + b.Property("Email") + .HasColumnType("nvarchar(max)"); + + b.Property("Type") + .HasColumnType("tinyint"); + + b.HasKey("Id"); + + b.HasIndex("AccountId"); + + b.ToTable("AuthExternal"); + }); + + modelBuilder.Entity("EventsExpress.Db.Entities.AuthLocal", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("AccountId") + .HasColumnType("uniqueidentifier"); + + b.Property("Email") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("EmailConfirmed") + .HasColumnType("bit"); + + b.Property("PasswordHash") + .HasColumnType("nvarchar(max)"); + + b.Property("Salt") + .HasColumnType("nvarchar(16)") + .HasMaxLength(16); + + b.HasKey("Id"); + + b.HasIndex("AccountId") + .IsUnique(); + + b.ToTable("AuthLocal"); + }); + + modelBuilder.Entity("EventsExpress.Db.Entities.Category", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("Name") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.ToTable("Categories"); + }); + + modelBuilder.Entity("EventsExpress.Db.Entities.CategoryOfMeasuring", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CategoryName") + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.ToTable("CategoriesOfMeasurings"); + }); + + modelBuilder.Entity("EventsExpress.Db.Entities.ChangeInfo", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("ChangesType") + .HasColumnType("int"); + + b.Property("EntityKeys") + .HasColumnType("nvarchar(max)"); + + b.Property("EntityName") + .HasColumnType("nvarchar(max)"); + + b.Property("PropertyChangesText") + .HasColumnType("nvarchar(max)"); + + b.Property("Time") + .HasColumnType("datetime2"); + + b.Property("UserId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("ChangeInfos"); + }); + + modelBuilder.Entity("EventsExpress.Db.Entities.ChatRoom", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("Title") + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.ToTable("ChatRoom"); + }); + + modelBuilder.Entity("EventsExpress.Db.Entities.Comments", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CommentsId") + .HasColumnType("uniqueidentifier"); + + b.Property("Date") + .HasColumnType("datetime2"); + + b.Property("EventId") + .HasColumnType("uniqueidentifier"); + + b.Property("Text") + .HasColumnType("nvarchar(max)"); + + b.Property("UserId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("CommentsId"); + + b.HasIndex("EventId"); + + b.HasIndex("UserId"); + + b.ToTable("Comments"); + }); + + modelBuilder.Entity("EventsExpress.Db.Entities.ContactAdmin", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("AssigneeId") + .HasColumnType("uniqueidentifier"); + + b.Property("DateCreated") + .HasColumnType("datetime2"); + + b.Property("DateUpdated") + .HasColumnType("datetime2"); + + b.Property("Email") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("EmailBody") + .HasColumnType("nvarchar(max)"); + + b.Property("ResolutionDetails") + .HasColumnType("nvarchar(max)"); + + b.Property("SenderId") + .HasColumnType("uniqueidentifier"); + + b.Property("Status") + .HasColumnType("tinyint"); + + b.Property("Subject") + .HasColumnType("tinyint"); + + b.Property("Title") + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.HasIndex("AssigneeId"); + + b.HasIndex("SenderId"); + + b.ToTable("ContactAdmin"); + }); + + modelBuilder.Entity("EventsExpress.Db.Entities.Event", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("DateFrom") + .HasColumnType("date"); + + b.Property("DateTo") + .HasColumnType("date"); + + b.Property("Description") + .HasColumnType("nvarchar(max)"); + + b.Property("EventLocationId") + .HasColumnType("uniqueidentifier"); + + b.Property("IsMultiEvent") + .HasColumnType("bit"); + + b.Property("IsPublic") + .HasColumnType("bit"); + + b.Property("MaxParticipants") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasDefaultValue(2147483647); + + b.Property("Title") + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.HasIndex("EventLocationId"); + + b.ToTable("Events"); + }); + + modelBuilder.Entity("EventsExpress.Db.Entities.EventCategory", b => + { + b.Property("EventId") + .HasColumnType("uniqueidentifier"); + + b.Property("CategoryId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("EventId", "CategoryId"); + + b.HasIndex("CategoryId"); + + b.ToTable("EventCategory"); + }); + + modelBuilder.Entity("EventsExpress.Db.Entities.EventLocation", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("OnlineMeeting") + .HasColumnType("nvarchar(max)"); + + b.Property("Point") + .HasColumnType("geography"); + + b.Property("Type") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.ToTable("EventLocations"); + }); + + modelBuilder.Entity("EventsExpress.Db.Entities.EventOwner", b => + { + b.Property("UserId") + .HasColumnType("uniqueidentifier"); + + b.Property("EventId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("UserId", "EventId"); + + b.HasIndex("EventId"); + + b.HasIndex("UserId", "EventId"); + + b.ToTable("EventOwners"); + }); + + modelBuilder.Entity("EventsExpress.Db.Entities.EventSchedule", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("EventId") + .HasColumnType("uniqueidentifier"); + + b.Property("Frequency") + .HasColumnType("int"); + + b.Property("IsActive") + .HasColumnType("bit"); + + b.Property("LastRun") + .HasColumnType("datetime2"); + + b.Property("NextRun") + .HasColumnType("datetime2"); + + b.Property("Periodicity") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("EventId") + .IsUnique(); + + b.ToTable("EventSchedules"); + }); + + modelBuilder.Entity("EventsExpress.Db.Entities.EventStatusHistory", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedOn") + .HasColumnType("datetime2"); + + b.Property("EventId") + .HasColumnType("uniqueidentifier"); + + b.Property("EventStatus") + .HasColumnType("int"); + + b.Property("Reason") + .HasColumnType("nvarchar(max)"); + + b.Property("UserId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("EventId"); + + b.HasIndex("UserId"); + + b.ToTable("EventStatusHistory"); + }); + + modelBuilder.Entity("EventsExpress.Db.Entities.Inventory", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("EventId") + .HasColumnType("uniqueidentifier"); + + b.Property("ItemName") + .HasColumnType("nvarchar(max)"); + + b.Property("NeedQuantity") + .HasColumnType("float"); + + b.Property("UnitOfMeasuringId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("EventId"); + + b.HasIndex("UnitOfMeasuringId"); + + b.ToTable("Inventories"); + }); + + modelBuilder.Entity("EventsExpress.Db.Entities.Message", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("ChatRoomId") + .HasColumnType("uniqueidentifier"); + + b.Property("DateCreated") + .HasColumnType("datetime2"); + + b.Property("Edited") + .HasColumnType("bit"); + + b.Property("ParentId") + .HasColumnType("uniqueidentifier"); + + b.Property("Seen") + .HasColumnType("bit"); + + b.Property("SenderId") + .HasColumnType("uniqueidentifier"); + + b.Property("Text") + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.HasIndex("ChatRoomId"); + + b.HasIndex("ParentId"); + + b.HasIndex("SenderId"); + + b.ToTable("Message"); + }); + + modelBuilder.Entity("EventsExpress.Db.Entities.MultiEventStatus", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("ChildId") + .HasColumnType("uniqueidentifier"); + + b.Property("ParentId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("ChildId"); + + b.HasIndex("ParentId"); + + b.ToTable("MultiEventStatus"); + }); + + modelBuilder.Entity("EventsExpress.Db.Entities.NotificationTemplate", b => + { + b.Property("Id") + .HasColumnType("int"); + + b.Property("Message") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("Subject") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("Title") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.HasKey("Id"); + + b.HasIndex("Title") + .IsUnique(); + + b.ToTable("NotificationTemplates"); + }); + + modelBuilder.Entity("EventsExpress.Db.Entities.NotificationType", b => + { + b.Property("Id") + .HasColumnType("int"); + + b.Property("Name") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.ToTable("NotificationTypes"); + + b.HasData( + new + { + Id = 1, + Name = "Profile Change" + }, + new + { + Id = 2, + Name = "Own Event Change" + }, + new + { + Id = 3, + Name = "Visited Event Change" + }); + }); + + modelBuilder.Entity("EventsExpress.Db.Entities.Permission", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("Name") + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.ToTable("Permissions"); + }); + + modelBuilder.Entity("EventsExpress.Db.Entities.Rate", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("EventId") + .HasColumnType("uniqueidentifier"); + + b.Property("Score") + .HasColumnType("tinyint"); + + b.Property("UserFromId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("EventId"); + + b.HasIndex("UserFromId"); + + b.ToTable("Rates"); + }); + + modelBuilder.Entity("EventsExpress.Db.Entities.Relationship", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("Attitude") + .HasColumnType("int"); + + b.Property("UserFromId") + .HasColumnType("uniqueidentifier"); + + b.Property("UserToId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("UserFromId"); + + b.HasIndex("UserToId"); + + b.ToTable("Relationships"); + }); + + modelBuilder.Entity("EventsExpress.Db.Entities.Role", b => + { + b.Property("Id") + .HasColumnType("smallint"); + + b.Property("Name") + .IsRequired() + .HasColumnType("nvarchar(30)") + .HasMaxLength(30); + + b.HasKey("Id"); + + b.ToTable("Roles"); + + b.HasData( + new + { + Id = (short)0, + Name = "User" + }, + new + { + Id = (short)1, + Name = "Admin" + }); + }); + + modelBuilder.Entity("EventsExpress.Db.Entities.UnitOfMeasuring", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CategoryId") + .HasColumnType("uniqueidentifier"); + + b.Property("IsDeleted") + .HasColumnType("bit"); + + b.Property("ShortName") + .HasColumnType("nvarchar(450)"); + + b.Property("UnitName") + .HasColumnType("nvarchar(450)"); + + b.HasKey("Id"); + + b.HasIndex("CategoryId"); + + b.HasIndex("UnitName", "ShortName", "CategoryId") + .IsUnique() + .HasFilter("IsDeleted = 0"); + + b.ToTable("UnitOfMeasurings"); + }); + + modelBuilder.Entity("EventsExpress.Db.Entities.User", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("Birthday") + .HasColumnType("date"); + + b.Property("Email") + .HasColumnType("nvarchar(max)"); + + b.Property("Gender") + .HasColumnType("tinyint"); + + b.Property("Name") + .HasColumnType("nvarchar(max)"); + + b.Property("Phone") + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.ToTable("Users"); + }); + + modelBuilder.Entity("EventsExpress.Db.Entities.UserCategory", b => + { + b.Property("UserId") + .HasColumnType("uniqueidentifier"); + + b.Property("CategoryId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("UserId", "CategoryId"); + + b.HasIndex("CategoryId"); + + b.ToTable("UserCategory"); + }); + + modelBuilder.Entity("EventsExpress.Db.Entities.UserChat", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("ChatId") + .HasColumnType("uniqueidentifier"); + + b.Property("UserId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("ChatId"); + + b.HasIndex("UserId"); + + b.ToTable("UserChat"); + }); + + modelBuilder.Entity("EventsExpress.Db.Entities.UserEvent", b => + { + b.Property("UserId") + .HasColumnType("uniqueidentifier"); + + b.Property("EventId") + .HasColumnType("uniqueidentifier"); + + b.Property("Status") + .HasColumnType("int"); + + b.Property("UserStatusEvent") + .HasColumnType("int"); + + b.HasKey("UserId", "EventId"); + + b.HasIndex("EventId"); + + b.ToTable("UserEvent"); + }); + + modelBuilder.Entity("EventsExpress.Db.Entities.UserEventInventory", b => + { + b.Property("InventoryId") + .HasColumnType("uniqueidentifier"); + + b.Property("UserId") + .HasColumnType("uniqueidentifier"); + + b.Property("EventId") + .HasColumnType("uniqueidentifier"); + + b.Property("Quantity") + .HasColumnType("float"); + + b.HasKey("InventoryId", "UserId", "EventId"); + + b.HasIndex("UserId", "EventId"); + + b.ToTable("UserEventInventories"); + }); + + modelBuilder.Entity("EventsExpress.Db.Entities.UserNotificationType", b => + { + b.Property("UserId") + .HasColumnType("uniqueidentifier"); + + b.Property("NotificationTypeId") + .HasColumnType("int"); + + b.HasKey("UserId", "NotificationTypeId"); + + b.HasIndex("NotificationTypeId"); + + b.ToTable("UserNotificationTypes"); + }); + + modelBuilder.Entity("EventsExpress.Db.Entities.UserToken", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("AccountId") + .HasColumnType("uniqueidentifier"); + + b.Property("Created") + .HasColumnType("datetime2"); + + b.Property("CreatedByIp") + .HasColumnType("nvarchar(max)"); + + b.Property("Expires") + .HasColumnType("datetime2"); + + b.Property("ReplacedByToken") + .HasColumnType("nvarchar(max)"); + + b.Property("Revoked") + .HasColumnType("datetime2"); + + b.Property("RevokedByIp") + .HasColumnType("nvarchar(max)"); + + b.Property("Token") + .HasColumnType("nvarchar(max)"); + + b.Property("Type") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("AccountId"); + + b.ToTable("UserTokens"); + }); + + modelBuilder.Entity("EventsExpress.Db.Entities.Account", b => + { + b.HasOne("EventsExpress.Db.Entities.User", "User") + .WithOne("Account") + .HasForeignKey("EventsExpress.Db.Entities.Account", "UserId"); + }); + + modelBuilder.Entity("EventsExpress.Db.Entities.AccountRole", b => + { + b.HasOne("EventsExpress.Db.Entities.Account", "Account") + .WithMany("AccountRoles") + .HasForeignKey("AccountId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EventsExpress.Db.Entities.Role", "Role") + .WithMany("Accounts") + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("EventsExpress.Db.Entities.AuthExternal", b => + { + b.HasOne("EventsExpress.Db.Entities.Account", "Account") + .WithMany("AuthExternal") + .HasForeignKey("AccountId") + .OnDelete(DeleteBehavior.NoAction) + .IsRequired(); + }); + + modelBuilder.Entity("EventsExpress.Db.Entities.AuthLocal", b => + { + b.HasOne("EventsExpress.Db.Entities.Account", "Account") + .WithOne("AuthLocal") + .HasForeignKey("EventsExpress.Db.Entities.AuthLocal", "AccountId") + .OnDelete(DeleteBehavior.NoAction) + .IsRequired(); + }); + + modelBuilder.Entity("EventsExpress.Db.Entities.ChangeInfo", b => + { + b.HasOne("EventsExpress.Db.Entities.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("EventsExpress.Db.Entities.Comments", b => + { + b.HasOne("EventsExpress.Db.Entities.Comments", "Parent") + .WithMany("Children") + .HasForeignKey("CommentsId"); + + b.HasOne("EventsExpress.Db.Entities.Event", "Event") + .WithMany() + .HasForeignKey("EventId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EventsExpress.Db.Entities.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("EventsExpress.Db.Entities.ContactAdmin", b => + { + b.HasOne("EventsExpress.Db.Entities.User", "Assignee") + .WithMany() + .HasForeignKey("AssigneeId"); + + b.HasOne("EventsExpress.Db.Entities.User", "Sender") + .WithMany() + .HasForeignKey("SenderId"); + }); + + modelBuilder.Entity("EventsExpress.Db.Entities.Event", b => + { + b.HasOne("EventsExpress.Db.Entities.EventLocation", "EventLocation") + .WithMany() + .HasForeignKey("EventLocationId"); + }); + + modelBuilder.Entity("EventsExpress.Db.Entities.EventCategory", b => + { + b.HasOne("EventsExpress.Db.Entities.Category", "Category") + .WithMany("Events") + .HasForeignKey("CategoryId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EventsExpress.Db.Entities.Event", "Event") + .WithMany("Categories") + .HasForeignKey("EventId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("EventsExpress.Db.Entities.EventOwner", b => + { + b.HasOne("EventsExpress.Db.Entities.Event", "Event") + .WithMany("Owners") + .HasForeignKey("EventId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EventsExpress.Db.Entities.User", "User") + .WithMany("Events") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("EventsExpress.Db.Entities.EventSchedule", b => + { + b.HasOne("EventsExpress.Db.Entities.Event", "Event") + .WithOne("EventSchedule") + .HasForeignKey("EventsExpress.Db.Entities.EventSchedule", "EventId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("EventsExpress.Db.Entities.EventStatusHistory", b => + { + b.HasOne("EventsExpress.Db.Entities.Event", "Event") + .WithMany("StatusHistory") + .HasForeignKey("EventId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EventsExpress.Db.Entities.User", "User") + .WithMany("ChangedStatusEvents") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("EventsExpress.Db.Entities.Inventory", b => + { + b.HasOne("EventsExpress.Db.Entities.Event", "Event") + .WithMany("Inventories") + .HasForeignKey("EventId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("EventsExpress.Db.Entities.UnitOfMeasuring", "UnitOfMeasuring") + .WithMany("Inventories") + .HasForeignKey("UnitOfMeasuringId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + }); + + modelBuilder.Entity("EventsExpress.Db.Entities.Message", b => + { + b.HasOne("EventsExpress.Db.Entities.ChatRoom", null) + .WithMany("Messages") + .HasForeignKey("ChatRoomId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EventsExpress.Db.Entities.Message", "Parent") + .WithMany() + .HasForeignKey("ParentId"); + + b.HasOne("EventsExpress.Db.Entities.User", "Sender") + .WithMany() + .HasForeignKey("SenderId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("EventsExpress.Db.Entities.MultiEventStatus", b => + { + b.HasOne("EventsExpress.Db.Entities.Event", "ChildEvent") + .WithMany("ParentEvents") + .HasForeignKey("ChildId") + .OnDelete(DeleteBehavior.ClientCascade) + .IsRequired(); + + b.HasOne("EventsExpress.Db.Entities.Event", "ParentEvent") + .WithMany("ChildEvents") + .HasForeignKey("ParentId") + .OnDelete(DeleteBehavior.ClientCascade) + .IsRequired(); + }); + + modelBuilder.Entity("EventsExpress.Db.Entities.Rate", b => + { + b.HasOne("EventsExpress.Db.Entities.Event", "Event") + .WithMany("Rates") + .HasForeignKey("EventId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("EventsExpress.Db.Entities.User", "UserFrom") + .WithMany("Rates") + .HasForeignKey("UserFromId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + }); + + modelBuilder.Entity("EventsExpress.Db.Entities.Relationship", b => + { + b.HasOne("EventsExpress.Db.Entities.User", "UserFrom") + .WithMany() + .HasForeignKey("UserFromId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EventsExpress.Db.Entities.User", "UserTo") + .WithMany("Relationships") + .HasForeignKey("UserToId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + }); + + modelBuilder.Entity("EventsExpress.Db.Entities.UnitOfMeasuring", b => + { + b.HasOne("EventsExpress.Db.Entities.CategoryOfMeasuring", "Category") + .WithMany("UnitOfMeasurings") + .HasForeignKey("CategoryId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("EventsExpress.Db.Entities.UserCategory", b => + { + b.HasOne("EventsExpress.Db.Entities.Category", "Category") + .WithMany("Users") + .HasForeignKey("CategoryId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EventsExpress.Db.Entities.User", "User") + .WithMany("Categories") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("EventsExpress.Db.Entities.UserChat", b => + { + b.HasOne("EventsExpress.Db.Entities.ChatRoom", "Chat") + .WithMany("Users") + .HasForeignKey("ChatId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EventsExpress.Db.Entities.User", "User") + .WithMany("Chats") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("EventsExpress.Db.Entities.UserEvent", b => + { + b.HasOne("EventsExpress.Db.Entities.Event", "Event") + .WithMany("Visitors") + .HasForeignKey("EventId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EventsExpress.Db.Entities.User", "User") + .WithMany("EventsToVisit") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("EventsExpress.Db.Entities.UserEventInventory", b => + { + b.HasOne("EventsExpress.Db.Entities.Inventory", "Inventory") + .WithMany("UserEventInventories") + .HasForeignKey("InventoryId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("EventsExpress.Db.Entities.UserEvent", "UserEvent") + .WithMany("Inventories") + .HasForeignKey("UserId", "EventId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + }); + + modelBuilder.Entity("EventsExpress.Db.Entities.UserNotificationType", b => + { + b.HasOne("EventsExpress.Db.Entities.NotificationType", "NotificationType") + .WithMany("Users") + .HasForeignKey("NotificationTypeId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EventsExpress.Db.Entities.User", "User") + .WithMany("NotificationTypes") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("EventsExpress.Db.Entities.UserToken", b => + { + b.HasOne("EventsExpress.Db.Entities.Account", "Account") + .WithMany("RefreshTokens") + .HasForeignKey("AccountId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/EventsExpress.Db/Migrations/20211022085402_AddMultiEvents.cs b/EventsExpress.Db/Migrations/20211022085402_AddMultiEvents.cs new file mode 100644 index 000000000..12420b8ff --- /dev/null +++ b/EventsExpress.Db/Migrations/20211022085402_AddMultiEvents.cs @@ -0,0 +1,61 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +namespace EventsExpress.Db.Migrations +{ + public partial class AddMultiEvents : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "IsMultiEvent", + table: "Events", + nullable: true); + + migrationBuilder.CreateTable( + name: "MultiEventStatus", + columns: table => new + { + Id = table.Column(nullable: false), + ParentId = table.Column(nullable: false), + ChildId = table.Column(nullable: false), + }, + constraints: table => + { + table.PrimaryKey("PK_MultiEventStatus", x => x.Id); + table.ForeignKey( + name: "FK_MultiEventStatus_Events_ChildId", + column: x => x.ChildId, + principalTable: "Events", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + table.ForeignKey( + name: "FK_MultiEventStatus_Events_ParentId", + column: x => x.ParentId, + principalTable: "Events", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + }); + + migrationBuilder.CreateIndex( + name: "IX_MultiEventStatus_ChildId", + table: "MultiEventStatus", + column: "ChildId"); + + migrationBuilder.CreateIndex( + name: "IX_MultiEventStatus_ParentId", + table: "MultiEventStatus", + column: "ParentId"); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "MultiEventStatus"); + + migrationBuilder.DropColumn( + name: "IsMultiEvent", + table: "Events"); + } + } +} diff --git a/EventsExpress.Db/Migrations/AppDbContextModelSnapshot.cs b/EventsExpress.Db/Migrations/AppDbContextModelSnapshot.cs index 90614cd1f..73aa0fa37 100644 --- a/EventsExpress.Db/Migrations/AppDbContextModelSnapshot.cs +++ b/EventsExpress.Db/Migrations/AppDbContextModelSnapshot.cs @@ -279,6 +279,9 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.Property("EventLocationId") .HasColumnType("uniqueidentifier"); + b.Property("IsMultiEvent") + .HasColumnType("bit"); + b.Property("IsPublic") .HasColumnType("bit"); @@ -476,6 +479,27 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.ToTable("Message"); }); + modelBuilder.Entity("EventsExpress.Db.Entities.MultiEventStatus", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("ChildId") + .HasColumnType("uniqueidentifier"); + + b.Property("ParentId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("ChildId"); + + b.HasIndex("ParentId"); + + b.ToTable("MultiEventStatus"); + }); + modelBuilder.Entity("EventsExpress.Db.Entities.NotificationTemplate", b => { b.Property("Id") @@ -983,6 +1007,21 @@ protected override void BuildModel(ModelBuilder modelBuilder) .IsRequired(); }); + modelBuilder.Entity("EventsExpress.Db.Entities.MultiEventStatus", b => + { + b.HasOne("EventsExpress.Db.Entities.Event", "ChildEvent") + .WithMany("ParentEvents") + .HasForeignKey("ChildId") + .OnDelete(DeleteBehavior.ClientCascade) + .IsRequired(); + + b.HasOne("EventsExpress.Db.Entities.Event", "ParentEvent") + .WithMany("ChildEvents") + .HasForeignKey("ParentId") + .OnDelete(DeleteBehavior.ClientCascade) + .IsRequired(); + }); + modelBuilder.Entity("EventsExpress.Db.Entities.Rate", b => { b.HasOne("EventsExpress.Db.Entities.Event", "Event") diff --git a/EventsExpress.Test/ControllerTests/EventControllerTests.cs b/EventsExpress.Test/ControllerTests/EventControllerTests.cs index 102a60453..5cbb04e36 100644 --- a/EventsExpress.Test/ControllerTests/EventControllerTests.cs +++ b/EventsExpress.Test/ControllerTests/EventControllerTests.cs @@ -34,6 +34,7 @@ internal class EventControllerTests private Mock mockSecurityContextService; private Mock mockPhotoservice; private Mock> mockValidator; + private Mock> mockEventDtolValidator; private UserDto _userDto; private Guid _idUser = Guid.NewGuid(); private string _userEmal = "user@gmail.com"; @@ -50,7 +51,8 @@ protected void Initialize() mockPhotoservice = new Mock(); service = new Mock(); mockValidator = new Mock>(); - eventController = new EventController(service.Object, MockMapper.Object, mockSecurityContextService.Object, mockPhotoservice.Object); + mockEventDtolValidator = new Mock>(); + eventController = new EventController(service.Object, MockMapper.Object, mockSecurityContextService.Object, mockPhotoservice.Object, mockEventDtolValidator.Object); eventController.ControllerContext = new ControllerContext(); eventController.ControllerContext.HttpContext = new DefaultHttpContext(); diff --git a/EventsExpress.Test/ServiceTests/EventServiceTest.cs b/EventsExpress.Test/ServiceTests/EventServiceTest.cs index 11e434c30..1ae90972a 100644 --- a/EventsExpress.Test/ServiceTests/EventServiceTest.cs +++ b/EventsExpress.Test/ServiceTests/EventServiceTest.cs @@ -518,13 +518,6 @@ public void EditEvent_ValidEvent_Success(EventDto eventDto) mockPhotoService.Verify(x => x.ChangeTempToImagePhoto(eventDto.Id), Times.Once); } - [Test] - [Category("Edit Event")] - public void EditEvent_InvalidEvent_Failed() - { - Assert.ThrowsAsync(async () => await service.Edit(null)); - } - [Test] [TestCaseSource(typeof(GetEventExistingId), nameof(GetEventExistingId.TestCasesForAddUserToEvent))] [Category("Add user to event")] diff --git a/EventsExpress.Test/ServiceTests/TestClasses/Event/EditingOrCreatingExistingDto.cs b/EventsExpress.Test/ServiceTests/TestClasses/Event/EditingOrCreatingExistingDto.cs index a6a525086..836946bbe 100644 --- a/EventsExpress.Test/ServiceTests/TestClasses/Event/EditingOrCreatingExistingDto.cs +++ b/EventsExpress.Test/ServiceTests/TestClasses/Event/EditingOrCreatingExistingDto.cs @@ -102,6 +102,51 @@ public class EditingOrCreatingExistingDto : IEnumerable Type = LocationType.Map, }; + private static EventDto multiEventDTOReccurentDraft = new EventDto + { + Id = GetEventExistingId.ThirdEventId, + DateFrom = DateTime.Today, + DateTo = DateTime.Today, + Description = "Multi event", + Owners = new List() + { + new User + { + Id = Guid.NewGuid(), + }, + }, + Title = "MultiEvent", + IsPublic = true, + IsReccurent = true, + Frequency = 1, + Periodicity = Periodicity.Weekly, + EventStatus = EventStatus.Draft, + Categories = new List() + { + new CategoryDto + { + Id = Guid.NewGuid(), + Name = "Category#1", + }, + }, + Point = new Point(50.45, 35.34), + MaxParticipants = 14, + Type = LocationType.Map, + Events = new List() + { + new ChildEventDto + { + Id = GetEventExistingId.SecondEventId, + DateFrom = DateTime.Today, + DateTo = DateTime.Today, + Description = "Child event", + Title = "Child", + Point = new Point(50.45, 35.34), + Type = LocationType.Map, + }, + }, + }; + public static EventDto EventDTOMap { get => eventDTOMap; @@ -117,11 +162,17 @@ public static EventDto EventDTOReccurentDraft get => eventDTOReccurentDraft; } + public static EventDto MultiEventDTOReccurentDraft + { + get => multiEventDTOReccurentDraft; + } + public IEnumerator GetEnumerator() { yield return new object[] { EventDTOMap }; yield return new object[] { EventDTOOnline }; yield return new object[] { EventDTOReccurentDraft }; + yield return new object[] { MultiEventDTOReccurentDraft }; } } } diff --git a/EventsExpress/ClientApp/src/actions/event/event-add-action.js b/EventsExpress/ClientApp/src/actions/event/event-add-action.js index debaf5045..55a70589b 100644 --- a/EventsExpress/ClientApp/src/actions/event/event-add-action.js +++ b/EventsExpress/ClientApp/src/actions/event/event-add-action.js @@ -57,6 +57,8 @@ export function publish_event(eventId) { } } + + function eventWasCreated(eventId) { return { type: EVENT_WAS_CREATED, diff --git a/EventsExpress/ClientApp/src/components/event/MultieventForm.css b/EventsExpress/ClientApp/src/components/event/MultieventForm.css new file mode 100644 index 000000000..d01d74d5f --- /dev/null +++ b/EventsExpress/ClientApp/src/components/event/MultieventForm.css @@ -0,0 +1,7 @@ +.childEvent { + border: 1px solid #808080; + border-radius: 10px; + padding: 10px; + margin: 20px; + list-style-type: none; +} diff --git a/EventsExpress/ClientApp/src/components/event/MultieventForm.js b/EventsExpress/ClientApp/src/components/event/MultieventForm.js new file mode 100644 index 000000000..a11a64c01 --- /dev/null +++ b/EventsExpress/ClientApp/src/components/event/MultieventForm.js @@ -0,0 +1,102 @@ +import React from 'react' +import "./MultieventForm.css"; +import IconButton from "@material-ui/core/IconButton"; +import { Field, FieldArray} from 'redux-form' +import { + renderDatePicker, LocationMapWithMarker, renderTextField, renderTextArea +} from '../helpers/form-helpers'; + + +const renderField = ({ input, label, type, meta: { touched, error } }) => ( +
+ +
+ + {touched && error && {error}} +
+
+) + + + +const renderMembers = ({ fields, meta: { error, submitFailed }, disabledDate, form_values }) => ( +
    + + + {submitFailed && error && {error}} + + {fields.map((member, index) => ( +
  • + +
    +

    Event #{index + 1}

    +
    +
    + +
    +
    + +
    +
    + + + + + + +
    +
    + +
    +
  • + ))} + fields.push({})} + size="small"> + Add Event + +
+) + +const MultieventForm = props => { + const { form_values, disabledDate } = props + return ( + + + ) +} + +export default MultieventForm; \ No newline at end of file diff --git a/EventsExpress/ClientApp/src/components/event/event-form.js b/EventsExpress/ClientApp/src/components/event/event-form.js index 10fd7d5ce..70b2b0607 100644 --- a/EventsExpress/ClientApp/src/components/event/event-form.js +++ b/EventsExpress/ClientApp/src/components/event/event-form.js @@ -15,6 +15,7 @@ import "./event-form.css"; import FormControlLabel from '@material-ui/core/FormControlLabel'; import Radio from '@material-ui/core/Radio'; import asyncValidatePhoto from '../../containers/async-validate-photo'; +import MultieventForm from './MultieventForm' momentLocaliser(moment); @@ -22,15 +23,7 @@ const photoService = new PhotoService(); class EventForm extends Component { - state = { checked: this.props.initialValues.isReccurent }; - - handleChange = () => { - this.setState(state => ({ - checked: !state.checked, - })); - } - - + checkLocation = (location) => { if (location !== null) { @@ -53,10 +46,9 @@ class EventForm extends Component { render() { - const { form_values, all_categories, disabledDate, user_name } = this.props; - const { checked } = this.state; - + const { form_values, all_categories, disabledDate, user_name } = this.props; + return (
@@ -99,17 +91,16 @@ class EventForm extends Component { {this.props.haveReccurentCheckBox &&
- +
+ } - {this.props.haveReccurentCheckBox && checked && + {this.props.haveReccurentCheckBox && form_values && form_values.isReccurent &&
- } + } +
+ + +
+ {this.props.form_values + && this.props.form_values.isMultiEvent && + + +
+ +
+ + }
diff --git a/EventsExpress/ClientApp/src/components/helpers/form-helpers/location-map-with-marker.jsx b/EventsExpress/ClientApp/src/components/helpers/form-helpers/location-map-with-marker.jsx index eb20efd65..0c3c5213b 100644 --- a/EventsExpress/ClientApp/src/components/helpers/form-helpers/location-map-with-marker.jsx +++ b/EventsExpress/ClientApp/src/components/helpers/form-helpers/location-map-with-marker.jsx @@ -8,10 +8,7 @@ export default function LocationMapWithMarker(props) { if (props.input.value.latitude != undefined) { initialPos = { lat: props.input.value.latitude, lng: props.input.value.longitude }; } - else { - props.input.value.latitude = initialPos.lat; - props.input.value.longitude = initialPos.lng; - } + const [location, setLocation] = React.useState(initialPos); function handleChange(latlng) { diff --git a/EventsExpress/ClientApp/src/components/helpers/form-helpers/render-date-picker.jsx b/EventsExpress/ClientApp/src/components/helpers/form-helpers/render-date-picker.jsx index 7d4f56897..417410236 100644 --- a/EventsExpress/ClientApp/src/components/helpers/form-helpers/render-date-picker.jsx +++ b/EventsExpress/ClientApp/src/components/helpers/form-helpers/render-date-picker.jsx @@ -9,6 +9,9 @@ export default ({ input: { onChange, value }, meta: { touched, invalid, error }, if (new Date(value) < new Date(minValue)) { onChange(moment(minValue).format('L')) } + if (new Date(value) > new Date(maxValue)) { + onChange(moment(maxValue).format('L')) + } } return { - await this.props.edit_event({ ...validateEventForm(values), user_id: this.props.user_id, id: this.props.event.id }); + await this.props.edit_event({ ...normalizeEventForm(values), user_id: this.props.user_id, id: this.props.event.id }); this.props.alert('Your event has been successfully saved!'); this.props.history.goBack(); } diff --git a/EventsExpress/ClientApp/src/containers/event-draft.js b/EventsExpress/ClientApp/src/containers/event-draft.js index f95153a6f..71d7dd3aa 100644 --- a/EventsExpress/ClientApp/src/containers/event-draft.js +++ b/EventsExpress/ClientApp/src/containers/event-draft.js @@ -6,7 +6,7 @@ import eventStatusEnum from '../constants/eventStatusEnum'; import { connect } from 'react-redux'; import { getFormValues , isPristine} from 'redux-form'; import { edit_event, publish_event} from '../actions/event/event-add-action'; -import { validateEventForm } from './event-validate-form' +import { normalizeEventForm } from './event-normalize-form' import { change_event_status } from '../actions/event/event-item-view-action'; import { setSuccessAllert } from '../actions/alert-action'; import Button from "@material-ui/core/Button"; @@ -18,13 +18,14 @@ class EventDraftWrapper extends Component { onPublish = async (values) => { if (!this.props.pristine) { - await this.props.edit_event({ ...validateEventForm(values), user_id: this.props.user_id, id: this.props.event.id }); + await this.props.edit_event({ ...normalizeEventForm(values), user_id: this.props.user_id, id: this.props.event.id }); } - return this.props.publish(this.props.event.id); + + return this.props.publish(this.props.event.id, this.props.event); } onSave = async () => { - await this.props.edit_event({ ...validateEventForm(this.props.form_values), user_id: this.props.user_id, id: this.props.event.id }); + await this.props.edit_event({ ...normalizeEventForm(this.props.form_values), user_id: this.props.user_id, id: this.props.event.id }); this.props.alert('Your event has been successfully saved!'); } @@ -113,7 +114,7 @@ const mapDispatchToProps = (dispatch) => { return { edit_event: (data) => dispatch(edit_event(data)), delete: (eventId, reason) => dispatch(change_event_status(eventId, reason, eventStatusEnum.Deleted)), - publish: (data) => dispatch(publish_event(data)), + publish: (eventId) => dispatch(publish_event(eventId)), get_categories: () => dispatch(get_categories()), alert: (msg) => dispatch(setSuccessAllert(msg)), } diff --git a/EventsExpress/ClientApp/src/containers/event-validate-form.js b/EventsExpress/ClientApp/src/containers/event-normalize-form.js similarity index 91% rename from EventsExpress/ClientApp/src/containers/event-validate-form.js rename to EventsExpress/ClientApp/src/containers/event-normalize-form.js index ee14b67f0..17f38cdf5 100644 --- a/EventsExpress/ClientApp/src/containers/event-validate-form.js +++ b/EventsExpress/ClientApp/src/containers/event-normalize-form.js @@ -1,5 +1,5 @@ -export const validateEventForm = values => { +export const normalizeEventForm = values => { if (!values) return values; diff --git a/EventsExpress/ClientApp/src/services/EventService.js b/EventsExpress/ClientApp/src/services/EventService.js index 2a4ba0e61..833557d4c 100644 --- a/EventsExpress/ClientApp/src/services/EventService.js +++ b/EventsExpress/ClientApp/src/services/EventService.js @@ -25,7 +25,7 @@ export default class EventService { return baseService.setResource(`event/${data.id}/edit`, data) } publishEvent = (id) => { - return baseService.setResource(`event/${id}/publish`) + return baseService.setResource(`event/${id}/publish`) } setEventStatus = data => baseService.setResource(`EventStatusHistory/${data.EventId}/SetStatus`, data); diff --git a/EventsExpress/Controllers/EventController.cs b/EventsExpress/Controllers/EventController.cs index 866218839..f697b1de0 100644 --- a/EventsExpress/Controllers/EventController.cs +++ b/EventsExpress/Controllers/EventController.cs @@ -27,13 +27,15 @@ public class EventController : ControllerBase private readonly IEventService _eventService; private readonly IMapper _mapper; private readonly ISecurityContext _securityContextService; + private readonly IValidator _validator; - public EventController(IEventService eventService, IMapper mapper, ISecurityContext securityContextService, IPhotoService photoService) + public EventController(IEventService eventService, IMapper mapper, ISecurityContext securityContextService, IPhotoService photoService, IValidator validator) { _photoService = photoService; _eventService = eventService; _mapper = mapper; _securityContextService = securityContextService; + _validator = validator; } [HttpPost("[action]/{eventId:Guid}")] @@ -105,7 +107,7 @@ public IActionResult Create() /// If Edit process failed. [HttpPost("{eventId:Guid}/[action]")] [UserAccessTypeFilterAttribute] - public async Task Edit(Guid eventId, EventEditViewModel model) + public async Task Edit(Guid eventId, [FromBody] EventEditViewModel model) { if (!ModelState.IsValid) { @@ -121,9 +123,26 @@ public async Task Edit(Guid eventId, EventEditViewModel model) [UserAccessTypeFilterAttribute] public async Task Publish(Guid eventId) { - var result = await _eventService.Publish(eventId); + var eventDTo = _eventService.EventById(eventId); + Dictionary exept = new Dictionary(); + var resultValidation = _validator.Validate(eventDTo); - return Ok(new { id = result }); + if (resultValidation.IsValid) + { + var result = await _eventService.Publish(eventId); + + return Ok(new { id = result }); + } + else + { + var p = resultValidation.Errors.Select(e => new KeyValuePair(e.PropertyName, e.ErrorMessage)); + foreach (var x in p) + { + exept.Add(x.Key, x.Value); + } + + throw new EventsExpressException("validation failed", exept); + } } /// diff --git a/EventsExpress/Mapping/EventMapperProfile.cs b/EventsExpress/Mapping/EventMapperProfile.cs index 0bda061f6..6f400e447 100644 --- a/EventsExpress/Mapping/EventMapperProfile.cs +++ b/EventsExpress/Mapping/EventMapperProfile.cs @@ -1,21 +1,18 @@ -using System; -using System.IO; -using System.Linq; -using AutoMapper; -using Azure.Storage.Blobs; -using EventsExpress.Core.DTOs; -using EventsExpress.Core.Extensions; -using EventsExpress.Core.IServices; -using EventsExpress.Core.Services; -using EventsExpress.Db.Entities; -using EventsExpress.Db.Enums; -using EventsExpress.ValueResolvers; -using EventsExpress.ViewModels; -using EventsExpress.ViewModels.Base; -using NetTopologySuite.Geometries; - -namespace EventsExpress.Mapping +namespace EventsExpress.Mapping { + using System; + using System.Collections.Generic; + using System.Linq; + using AutoMapper; + using EventsExpress.Core.DTOs; + using EventsExpress.Core.IServices; + using EventsExpress.Db.Entities; + using EventsExpress.Db.Enums; + using EventsExpress.ValueResolvers; + using EventsExpress.ViewModels; + using EventsExpress.ViewModels.Base; + using NetTopologySuite.Geometries; + public class EventMapperProfile : Profile { public EventMapperProfile() @@ -23,6 +20,7 @@ public EventMapperProfile() CreateMap() .ForMember(dest => dest.Point, opts => opts.MapFrom(src => src.EventLocation.Point)) .ForMember(dest => dest.Type, opts => opts.MapFrom(src => src.EventLocation.Type)) + .ForMember(dest => dest.Events, opts => opts.MapFrom(src => MapEventsDtoFromMultiEventStatus(src.ChildEvents))) .ForMember(dest => dest.OnlineMeeting, opts => opts.MapFrom(src => src.EventLocation.OnlineMeeting)) .ForMember(dest => dest.Owners, opt => opt.MapFrom(x => x.Owners.Select(z => z.User))) .ForMember( @@ -38,6 +36,12 @@ public EventMapperProfile() .ForMember(dest => dest.OwnerIds, opts => opts.Ignore()) .ForMember(dest => dest.Photo, opts => opts.Ignore()); + CreateMap() + .ForMember(dest => dest.Point, opts => opts.MapFrom(src => src.EventLocation.Point)) + .ForMember(dest => dest.Type, opts => opts.MapFrom(src => src.EventLocation.Type)) + .ForMember(dest => dest.OnlineMeeting, opts => opts.MapFrom(src => src.EventLocation.OnlineMeeting)) + .ForMember(dest => dest.EventStatus, opts => opts.MapFrom(src => src.StatusHistory.LastOrDefault().EventStatus)); + CreateMap() .ForMember(dest => dest.Owners, opt => opt.MapFrom(src => src.Owners.Select(x => new EventOwner @@ -47,6 +51,9 @@ public EventMapperProfile() }))) .ForMember(dest => dest.Visitors, opt => opt.Ignore()) .ForMember(dest => dest.Categories, opt => opt.Ignore()) + .ForMember(dest => dest.ChildEvents, opt => opt.MapFrom(src => src.Events)) + .ForMember(dest => dest.ParentEvents, opt => opt.Ignore()) + .ForMember(dest => dest.IsMultiEvent, opt => opt.Ignore()) .ForMember(dest => dest.Inventories, opts => opts.MapFrom(src => src.Inventories.Select(x => MapInventoryFromInventoryDto(x)))) .ForMember(dest => dest.EventLocationId, opts => opts.Ignore()) @@ -86,9 +93,30 @@ public EventMapperProfile() .ForMember(dest => dest.Point, opts => opts.MapFrom(src => PointOrNullEdit(src))) .ForMember(dest => dest.OnlineMeeting, opts => opts.MapFrom(src => OnlineMeetingOrNullEdit(src))) .ForMember(dest => dest.Type, opts => opts.MapFrom(src => src.Location.Type)) + .ForMember(dest => dest.Events, opts => opts.MapFrom(src => src.Events)) + .ForMember(dest => dest.IsMultiEvent, opts => opts.Ignore()) + .ForMember(dest => dest.Photo, opts => opts.Ignore()) + .ForMember(dest => dest.Visitors, opts => opts.Ignore()); + + CreateMap() + .ForMember(dest => dest.Categories, opts => opts.Ignore()) + .ForMember(dest => dest.Inventories, opts => opts.Ignore()) + .ForMember(dest => dest.Owners, opts => opts.Ignore()) + .ForMember(dest => dest.OwnerIds, opts => opts.Ignore()) + .ForMember(dest => dest.Point, opts => opts.MapFrom(src => src.Point)) + .ForMember(dest => dest.OnlineMeeting, opts => opts.MapFrom(src => src.OnlineMeeting)) + .ForMember(dest => dest.Type, opts => opts.MapFrom(src => src.Type)) + .ForMember(dest => dest.Events, opts => opts.Ignore()) + .ForMember(dest => dest.IsMultiEvent, opts => opts.Ignore()) .ForMember(dest => dest.Photo, opts => opts.Ignore()) .ForMember(dest => dest.Visitors, opts => opts.Ignore()); + CreateMap() + .ForMember(dest => dest.Point, opts => opts.MapFrom(src => PointOrNullEdit(src))) + .ForMember(dest => dest.OnlineMeeting, opts => opts.MapFrom(src => OnlineMeetingOrNullEdit(src))) + .ForMember(dest => dest.Type, opts => opts.MapFrom(src => src.Location.Type)) + .ForMember(dest => dest.IsMultiEvent, opts => opts.Ignore()); + CreateMap() .ForMember(dest => dest.Categories, opts => opts.MapFrom(src => src.Categories.Select(x => MapCategoryViewModelToCategoryDto(x)))) .ForMember(dest => dest.Owners, opts => opts.Ignore()) @@ -98,6 +126,8 @@ public EventMapperProfile() .ForMember(dest => dest.Type, opts => opts.MapFrom(src => src.Location.Type)) .ForMember(dest => dest.Periodicity, opts => opts.MapFrom(src => src.Periodicity)) .ForMember(dest => dest.IsReccurent, opts => opts.MapFrom(src => src.IsReccurent)) + .ForMember(dest => dest.IsMultiEvent, opts => opts.Ignore()) + .ForMember(dest => dest.Events, opts => opts.Ignore()) .ForMember(dest => dest.Inventories, opts => opts.MapFrom(src => src.Inventories.Select(x => MapInventoryDtoFromInventoryViewModel(x)))) .ForMember(dest => dest.Id, opts => opts.Ignore()) @@ -137,8 +167,15 @@ private static Uri OnlineMeetingOrNullEdit(EventEditViewModel eventEditViewModel private static Point PointOrNullEdit(EventEditViewModel editViewModel) { - return editViewModel.Location?.Type == LocationType.Map ? + if (editViewModel.Location.Latitude == null || editViewModel.Location.Longitude == null) + { + return null; + } + else + { + return editViewModel.Location?.Type == LocationType.Map ? new Point(editViewModel.Location.Latitude.Value, editViewModel.Location.Longitude.Value) { SRID = 4326 } : null; + } } private static Uri OnlineMeetingOrNullCreate(EventCreateViewModel eventCreateViewModel) @@ -167,8 +204,8 @@ private static CategoryDto MapCategoryViewModelToCategoryDto(CategoryViewModel c { return new CategoryDto { - Id = categoryViewModel.Id, - Name = categoryViewModel.Name, + Id = categoryViewModel.Id, + Name = categoryViewModel.Name, }; } @@ -255,6 +292,39 @@ private static InventoryDto MapInventoryDtoFromInventory(Inventory inventory) }; } + private static ChildEventDto MapChildEventDtoFromMultiEvent(Event chieldEvent) + { + return new ChildEventDto + { + Id = chieldEvent.Id, + Title = chieldEvent.Title, + Description = chieldEvent.Description, + DateFrom = chieldEvent.DateFrom, + DateTo = chieldEvent.DateTo, + Point = chieldEvent.EventLocation.Point, + Type = chieldEvent.EventLocation.Type, + OnlineMeeting = chieldEvent.EventLocation.OnlineMeeting, + EventStatus = chieldEvent.StatusHistory.LastOrDefault().EventStatus, + IsMultiEvent = chieldEvent.IsMultiEvent, + }; + } + + private static IEnumerable MapEventsDtoFromMultiEventStatus(ICollection childEvents) + { + if (childEvents == null) + { + return null; + } + + var list = new List(); + foreach (var item in childEvents) + { + list.Add(MapChildEventDtoFromMultiEvent(item.ChildEvent)); + } + + return list; + } + private UserPreviewViewModel MapUserToUserPreviewViewModel(User user) { return new UserPreviewViewModel diff --git a/EventsExpress/Validation/EventsDtoValidator.cs b/EventsExpress/Validation/EventsDtoValidator.cs new file mode 100644 index 000000000..0b118074a --- /dev/null +++ b/EventsExpress/Validation/EventsDtoValidator.cs @@ -0,0 +1,39 @@ +using System; +using EventsExpress.Core.DTOs; +using EventsExpress.Db.Entities; +using EventsExpress.Db.Enums; +using FluentValidation; + +namespace EventsExpress.Validation +{ + public class EventsDtoValidator : AbstractValidator + { + public EventsDtoValidator() + { + RuleFor(x => x.Title).NotEmpty().WithMessage("Field is required!"); + RuleFor(x => x.Title).MaximumLength(60).WithMessage("Title length exceeded the recommended length of 60 character!"); + RuleFor(x => x.Description).NotEmpty().WithMessage("Field is required!"); + RuleFor(x => x.DateFrom).NotEmpty().WithMessage("Field is required!"); + RuleFor(x => x.DateFrom).GreaterThan(DateTime.Today).WithMessage("Date from must be older than the current date!"); + RuleFor(x => x.DateTo).NotEmpty().WithMessage("Field is required!"); + RuleFor(x => x.DateTo).GreaterThan(x => x.DateFrom).WithMessage("Date to must be older than date from!"); + RuleFor(x => x.MaxParticipants).GreaterThan(0).WithMessage("Incorrect quantity of participants!"); + RuleFor(x => x.Categories).NotEmpty().WithMessage("Sellect at least 1 category"); + When(x => x.Frequency != 0, () => + { + RuleFor(x => x.Frequency).GreaterThan(0).WithMessage("Incorrect frequency!"); + }); + When(x => x.Point == null, () => + { + RuleFor(x => x.OnlineMeeting).NotEmpty().OverridePropertyName("location.type").WithMessage("Field is required!"); + }); + RuleForEach(x => x.Events).ChildRules(eve => { eve.RuleFor(x => x.Title).NotEmpty().WithMessage("Field is required!"); }); + RuleForEach(x => x.Events).ChildRules(eve => { eve.RuleFor(x => x.Title).MaximumLength(60).WithMessage("Title length exceeded the recommended length of 60 character!"); }); + RuleForEach(x => x.Events).ChildRules(eve => { eve.RuleFor(x => x.Description).NotEmpty().WithMessage("Field is required!"); }); + RuleForEach(x => x.Events).ChildRules(eve => { eve.RuleFor(x => x.DateFrom).NotEmpty().WithMessage("Field is required!"); }); + RuleForEach(x => x.Events).ChildRules(eve => { eve.RuleFor(x => x.DateFrom).GreaterThan(DateTime.Today).WithMessage("Date from must be older than the current date!"); }); + RuleForEach(x => x.Events).ChildRules(eve => { eve.RuleFor(x => x.DateTo).NotEmpty().WithMessage("Field is required!"); }); + RuleForEach(x => x.Events).ChildRules(eve => { eve.RuleFor(x => x.DateTo).GreaterThan(x => x.DateFrom).WithMessage("Date to must be older than date from!"); }); + } + } +} diff --git a/EventsExpress/ViewModels/EventEditViewModel.cs b/EventsExpress/ViewModels/EventEditViewModel.cs index 58bad86cd..2fc1693c5 100644 --- a/EventsExpress/ViewModels/EventEditViewModel.cs +++ b/EventsExpress/ViewModels/EventEditViewModel.cs @@ -17,5 +17,7 @@ public class EventEditViewModel : EventViewModelBase public EventStatus EventStatus { get; set; } public IEnumerable Inventories { get; set; } + + public IEnumerable Events { get; set; } } }