From 19217b18adb08c4b98c5658181e3434e0f33a246 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?So=C5=88a=20Neme=C4=8Dkayov=C3=A1?= <xnemeck@fi.muni.cz>
Date: Sun, 20 Oct 2024 16:32:38 +0200
Subject: [PATCH] Create Event, EventParticipant, EventComment tables

---
 Api/Api.csproj                                |   4 -
 Api/Controllers/EventCommentController.cs     | 138 +++++++++++++++++
 Api/Controllers/EventController.cs            | 129 ++++++++++++++++
 Api/Controllers/EventParticipantController.cs | 139 ++++++++++++++++++
 Api/Models/Converter.cs                       |  77 +++++++++-
 Api/Models/Event/EventCreateModel.cs          |  21 +++
 Api/Models/Event/EventFilter.cs               |  13 ++
 Api/Models/Event/EventModel.cs                |  17 +++
 Api/Models/Event/EventUpdateModel.cs          |  14 ++
 .../EventComment/EventCommentCreateModel.cs   |  19 +++
 Api/Models/EventComment/EventCommentFilter.cs |  12 ++
 Api/Models/EventComment/EventCommentModel.cs  |  15 ++
 .../EventComment/EventCommentUpdateModel.cs   |  10 ++
 .../EventParticipantCreateModel.cs            |  17 +++
 .../EventParticipantFilter.cs                 |  11 ++
 .../EventParticipant/EventParticipantModel.cs |  16 ++
 .../EventParticipantUpdateModel.cs            |   9 ++
 DAL/Constants/SeedingValues.cs                |  19 +++
 DAL/Data/DataInitializer.cs                   |  87 +++++++++++
 DAL/Data/RestaurantDBContext.cs               |   3 +
 DAL/Enums/ParticipantType.cs                  |  11 ++
 DAL/Models/Event.cs                           |  31 ++++
 DAL/Models/EventComment.cs                    |  26 ++++
 DAL/Models/EventParticipant .cs               |  24 +++
 DAL/Models/Restaurant.cs                      |   7 +
 25 files changed, 864 insertions(+), 5 deletions(-)
 create mode 100644 Api/Controllers/EventCommentController.cs
 create mode 100644 Api/Controllers/EventController.cs
 create mode 100644 Api/Controllers/EventParticipantController.cs
 create mode 100644 Api/Models/Event/EventCreateModel.cs
 create mode 100644 Api/Models/Event/EventFilter.cs
 create mode 100644 Api/Models/Event/EventModel.cs
 create mode 100644 Api/Models/Event/EventUpdateModel.cs
 create mode 100644 Api/Models/EventComment/EventCommentCreateModel.cs
 create mode 100644 Api/Models/EventComment/EventCommentFilter.cs
 create mode 100644 Api/Models/EventComment/EventCommentModel.cs
 create mode 100644 Api/Models/EventComment/EventCommentUpdateModel.cs
 create mode 100644 Api/Models/EventParticipant/EventParticipantCreateModel.cs
 create mode 100644 Api/Models/EventParticipant/EventParticipantFilter.cs
 create mode 100644 Api/Models/EventParticipant/EventParticipantModel.cs
 create mode 100644 Api/Models/EventParticipant/EventParticipantUpdateModel.cs
 create mode 100644 DAL/Enums/ParticipantType.cs
 create mode 100644 DAL/Models/Event.cs
 create mode 100644 DAL/Models/EventComment.cs
 create mode 100644 DAL/Models/EventParticipant .cs
 create mode 100644 DAL/Models/Restaurant.cs

diff --git a/Api/Api.csproj b/Api/Api.csproj
index 606a5b3..90b3202 100644
--- a/Api/Api.csproj
+++ b/Api/Api.csproj
@@ -18,8 +18,4 @@
     <ProjectReference Include="..\DAL\DAL.csproj" />
   </ItemGroup>
 
-  <ItemGroup>
-    <Folder Include="Models\" />
-  </ItemGroup>
-
 </Project>
diff --git a/Api/Controllers/EventCommentController.cs b/Api/Controllers/EventCommentController.cs
new file mode 100644
index 0000000..5cf5008
--- /dev/null
+++ b/Api/Controllers/EventCommentController.cs
@@ -0,0 +1,138 @@
+using Api.Models;
+using Api.Models.EventComment;
+using DAL.Data;
+using DAL.Models;
+using Microsoft.AspNetCore.Mvc;
+using Microsoft.EntityFrameworkCore;
+
+namespace Api.Controllers
+{
+    [Route("/[controller]")]
+    [ApiController]
+    public class EventCommentController : Controller
+    {
+        private readonly RestaurantDBContext _context;
+
+        public EventCommentController(RestaurantDBContext context)
+        {
+            _context = context;
+        }
+
+        [HttpGet]
+        [Route("{commentId:guid}")]
+        public async Task<IActionResult> GetEventCommentById([FromRoute] Guid commentId)
+        {
+            var comment = await _context.EventComments
+                .Include(c => c.Poster)
+                .FirstOrDefaultAsync(c => c.Id == commentId);
+
+            if (comment is null || comment.DeletedAt is not null)
+            {
+                return NotFound();
+            }
+
+            return Ok(Converter.ToEventCommentModel(comment));
+        }
+
+        [HttpGet]
+        public async Task<IActionResult> GetEventComments([FromQuery] EventCommentFilter filter,
+            [FromQuery] int limit = 0, [FromQuery] int offset = 0)
+        {
+            var filterFunc = Converter.ToEventCommentFilterFunc(filter);
+
+            var commentsQuery = _context.EventComments
+                .Include(c => c.Poster)
+                .Where(filterFunc)
+                .OrderByDescending(c => c.CreatedAt)
+                .Skip(offset);
+
+            var comments = limit > 0
+                ? await commentsQuery.Take(limit).ToListAsync()
+                : await commentsQuery.ToListAsync();
+
+            return Ok(comments.Select(Converter.ToEventCommentModel).ToList());
+        }
+
+        [HttpPost]
+        public async Task<IActionResult> CreateEventComment([FromBody] EventCommentCreateModel data)
+        {
+            if (!ModelState.IsValid)
+            {
+                return BadRequest(ModelState);
+            }
+
+            var poster = await _context.Users.FindAsync(data.PosterId);
+            if (poster is null || poster.DeletedAt is not null)
+            {
+                return NotFound("Poster not found.");
+            }
+
+            var eventEntity = await _context.Events.FindAsync(data.EventId);
+            if (eventEntity is null || eventEntity.DeletedAt is not null)
+            {
+                return NotFound("Event not found.");
+            }
+
+            var comment = new EventComment
+            {
+                Id = Guid.NewGuid(),
+                PosterId = data.PosterId,
+                EventId = data.EventId,
+                Content = data.Content,
+                ParentCommentId = data.ParentCommentId,
+                CreatedAt = DateTime.UtcNow,
+                UpdatedAt = DateTime.UtcNow
+            };
+
+            await _context.EventComments.AddAsync(comment);
+            await _context.SaveChangesAsync();
+
+            return CreatedAtAction(nameof(CreateEventComment), Converter.ToEventCommentModel(comment));
+        }
+
+        [HttpPatch]
+        [Route("{commentId:guid}")]
+        public async Task<IActionResult> UpdateEventComment([FromRoute] Guid commentId, [FromBody] EventCommentUpdateModel data)
+        {
+            if (!ModelState.IsValid)
+            {
+                return BadRequest(ModelState);
+            }
+
+            var comment = await _context.EventComments.FindAsync(commentId);
+
+            if (comment is null || comment.DeletedAt is not null)
+            {
+                return NotFound();
+            }
+
+            comment.Content = data.Content ?? comment.Content;
+            comment.UpdatedAt = DateTime.UtcNow;
+
+            _context.EventComments.Update(comment);
+            await _context.SaveChangesAsync();
+
+            return Ok(Converter.ToEventCommentModel(comment));
+        }
+
+        [HttpDelete]
+        [Route("{commentId:guid}")]
+        public async Task<IActionResult> DeleteEventComment([FromRoute] Guid commentId)
+        {
+            var comment = await _context.EventComments.FindAsync(commentId);
+
+            if (comment is null)
+            {
+                return NotFound();
+            }
+
+            comment.UpdatedAt = DateTime.UtcNow;
+            comment.DeletedAt = DateTime.UtcNow;
+
+            _context.EventComments.Update(comment);
+            await _context.SaveChangesAsync();
+
+            return Ok();
+        }
+    }
+}
diff --git a/Api/Controllers/EventController.cs b/Api/Controllers/EventController.cs
new file mode 100644
index 0000000..20c983c
--- /dev/null
+++ b/Api/Controllers/EventController.cs
@@ -0,0 +1,129 @@
+using Api.Models;
+using Api.Models.Event;
+using DAL.Data;
+using DAL.Models;
+using Microsoft.AspNetCore.Mvc;
+using Microsoft.EntityFrameworkCore;
+
+namespace Api.Controllers
+{
+    [Route("/[controller]")]
+    [ApiController]
+    public class EventController : Controller
+    {
+        private readonly RestaurantDBContext _context;
+
+        public EventController(RestaurantDBContext context)
+        {
+            _context = context;
+        }
+
+        [HttpGet]
+        [Route("{eventId:guid}")]
+        public async Task<IActionResult> GetEventById([FromRoute] Guid eventId)
+        {
+            var eventEntity = await _context.Events
+                .Include(e => e.Comments)
+                .Include(e => e.Participants)
+                .SingleOrDefaultAsync(e => e.Id == eventId);
+
+            if (eventEntity is null || eventEntity.DeletedAt is not null)
+            {
+                return NotFound();
+            }
+
+            return Ok(Converter.ToEventModel(eventEntity));
+        }
+
+        [HttpGet]
+        public async Task<IActionResult> GetEvents([FromQuery] EventFilter filter,
+            [FromQuery] int limit = 0, [FromQuery] int offset = 0)
+        {
+            var filterFunc = Converter.ToEventFilterFunc(filter);
+            var eventsQuery = _context.Events
+                .Include(e => e.Comments)
+                .Include(e => e.Participants)
+                .Where(filterFunc)
+                .OrderBy(e => e.Date)
+                .Skip(offset);
+
+            var events = limit > 0
+                ? await eventsQuery.Take(limit).ToListAsync()
+                : await eventsQuery.ToListAsync();
+
+            return Ok(events.Select(Converter.ToEventModel).ToList());
+        }
+
+        [HttpPost]
+        public async Task<IActionResult> CreateEvent([FromBody] EventCreateModel data)
+        {
+            if (!ModelState.IsValid)
+            {
+                return BadRequest(ModelState);
+            }
+
+            var eventEntity = new Event
+            {
+                Id = Guid.NewGuid(),
+                Title = data.Title,
+                RestaurantId = data.RestaurantId,
+                Content = data.Content,
+                Date = data.Date,
+                CreatedAt = DateTime.UtcNow,
+                UpdatedAt = DateTime.UtcNow
+            };
+
+            await _context.Events.AddAsync(eventEntity);
+            await _context.SaveChangesAsync();
+
+            return CreatedAtAction(nameof(CreateEvent), Converter.ToEventModel(eventEntity));
+        }
+
+        [HttpPatch]
+        [Route("{eventId:guid}")]
+        public async Task<IActionResult> UpdateEvent([FromRoute] Guid eventId, [FromBody] EventUpdateModel data)
+        {
+            if (!ModelState.IsValid)
+            {
+                return BadRequest(ModelState);
+            }
+
+            var eventEntity = await _context.Events.FindAsync(eventId);
+
+            if (eventEntity is null || eventEntity.DeletedAt is not null)
+            {
+                return NotFound();
+            }
+
+            eventEntity.Title = data.Title ?? eventEntity.Title;
+            eventEntity.Content = data.Content ?? eventEntity.Content;
+            eventEntity.Date = data.Date ?? eventEntity.Date;
+            eventEntity.UpdatedAt = DateTime.UtcNow;
+
+            _context.Events.Update(eventEntity);
+            await _context.SaveChangesAsync();
+
+            return Ok(Converter.ToEventModel(eventEntity));
+        }
+
+        [HttpDelete]
+        [Route("{eventId:guid}")]
+        public async Task<IActionResult> DeleteEvent([FromRoute] Guid eventId)
+        {
+            var eventEntity = await _context.Events.FindAsync(eventId);
+
+            if (eventEntity is null)
+            {
+                return NotFound();
+            }
+
+            eventEntity.UpdatedAt = DateTime.UtcNow;
+            eventEntity.DeletedAt = DateTime.UtcNow;
+
+            _context.Events.Update(eventEntity);
+            await _context.SaveChangesAsync();
+
+            return Ok();
+        }
+    }
+}
diff --git a/Api/Controllers/EventParticipantController.cs b/Api/Controllers/EventParticipantController.cs
new file mode 100644
index 0000000..dce9b54
--- /dev/null
+++ b/Api/Controllers/EventParticipantController.cs
@@ -0,0 +1,139 @@
+using Api.Models;
+using Api.Models.EventParticipant;
+using DAL.Data;
+using DAL.Models;
+using Microsoft.AspNetCore.Mvc;
+using Microsoft.EntityFrameworkCore;
+
+namespace Api.Controllers
+{
+    [Route("/[controller]")]
+    [ApiController]
+    public class EventParticipantController : Controller
+    {
+        private readonly RestaurantDBContext _context;
+
+        public EventParticipantController(RestaurantDBContext context)
+        {
+            _context = context;
+        }
+
+        [HttpGet]
+        [Route("{participantId:guid}")]
+        public async Task<IActionResult> GetEventParticipantById([FromRoute] Guid participantId)
+        {
+            var participant = await _context.EventParticipants
+                .Include(p => p.User)
+                .Include(p => p.Event)
+                .FirstOrDefaultAsync(p => p.Id == participantId);
+
+            if (participant is null)
+            {
+                return NotFound();
+            }
+
+            return Ok(Converter.ToEventParticipantModel(participant));
+        }
+
+        [HttpGet]
+        public async Task<IActionResult> GetEventParticipants([FromQuery] EventParticipantFilter filter,
+            [FromQuery] int limit = 0, [FromQuery] int offset = 0)
+        {
+            var filterFunc = Converter.ToEventParticipantFilterFunc(filter);
+
+            var participantsQuery = _context.EventParticipants
+                .Include(p => p.User)
+                .Include(p => p.Event)
+                .OrderBy(p => p.User.Name)
+                .Where(filterFunc)
+                .Skip(offset);
+
+            var participants = limit > 0
+                ? await participantsQuery.Take(limit).ToListAsync()
+                : await participantsQuery.ToListAsync();
+
+            return Ok(participants.Select(Converter.ToEventParticipantModel).ToList());
+        }
+
+        [HttpPost]
+        public async Task<IActionResult> CreateEventParticipant([FromBody] EventParticipantCreateModel data)
+        {
+            if (!ModelState.IsValid)
+            {
+                return BadRequest(ModelState);
+            }
+
+            var user = await _context.Users.FindAsync(data.UserId);
+            if (user is null)
+            {
+                return NotFound("User not found.");
+            }
+
+            var eventEntity = await _context.Events.FindAsync(data.EventId);
+            if (eventEntity is null || eventEntity.DeletedAt is not null)
+            {
+                return NotFound("Event not found.");
+            }
+
+            var participant = new EventParticipant
+            {
+                Id = Guid.NewGuid(),
+                UserId = data.UserId,
+                EventId = data.EventId,
+                Attendance = data.Attendance,
+                CreatedAt = DateTime.UtcNow,
+                UpdatedAt = DateTime.UtcNow
+            };
+
+            await _context.EventParticipants.AddAsync(participant);
+            await _context.SaveChangesAsync();
+
+            return CreatedAtAction(nameof(CreateEventParticipant), Converter.ToEventParticipantModel(participant));
+        }
+
+        [HttpPatch]
+        [Route("{participantId:guid}")]
+        public async Task<IActionResult> UpdateEventParticipant([FromRoute] Guid participantId, [FromBody] EventParticipantUpdateModel data)
+        {
+            if (!ModelState.IsValid)
+            {
+                return BadRequest(ModelState);
+            }
+
+            var participant = await _context.EventParticipants.FindAsync(participantId);
+
+            if (participant is null)
+            {
+                return NotFound();
+            }
+
+            participant.Attendance = data.Attendance ?? participant.Attendance;
+            participant.UpdatedAt = DateTime.UtcNow;
+
+            _context.EventParticipants.Update(participant);
+            await _context.SaveChangesAsync();
+
+            return Ok(Converter.ToEventParticipantModel(participant));
+        }
+
+        [HttpDelete]
+        [Route("{participantId:guid}")]
+        public async Task<IActionResult> DeleteEventParticipant([FromRoute] Guid participantId)
+        {
+            var participant = await _context.EventParticipants.FindAsync(participantId);
+
+            if (participant is null)
+            {
+                return NotFound();
+            }
+
+            participant.UpdatedAt = DateTime.UtcNow;
+            participant.DeletedAt = DateTime.UtcNow;
+
+            _context.EventParticipants.Update(participant);
+            await _context.SaveChangesAsync();
+
+            return Ok();
+        }
+    }
+}
diff --git a/Api/Models/Converter.cs b/Api/Models/Converter.cs
index 63e322b..7b892d8 100644
--- a/Api/Models/Converter.cs
+++ b/Api/Models/Converter.cs
@@ -1,4 +1,7 @@
-using Api.Models.Review;
+using Api.Models.Event;
+using Api.Models.EventComment;
+using Api.Models.EventParticipant;
+using Api.Models.Review;
 using Api.Models.ReviewAggregate;
 using Api.Models.ReviewComment;
 using Api.Models.User;
@@ -88,5 +91,77 @@ namespace Api.Models
                      && (filter.RestaurantId == null || r.RestaurantId == filter.RestaurantId)
                      && r.DeletedAt == null && r.Poster.DeletedAt == null;
         }
+
+        // EVENT
+        public static EventModel ToEventModel(DAL.Models.Event eventEntity)
+        {
+            return new EventModel
+            {
+                Id = eventEntity.Id,
+                Title = eventEntity.Title,
+                RestaurantId = eventEntity.RestaurantId,
+                Date = eventEntity.Date.ToLocalTime(),
+                Content = eventEntity.Content,
+                CreatedAt = eventEntity.CreatedAt.ToLocalTime(),
+                UpdatedAt = eventEntity.UpdatedAt.ToLocalTime(),
+                DeletedAt = eventEntity.DeletedAt?.ToLocalTime(),
+            };
+        }
+
+        public static Expression<Func<DAL.Models.Event, bool>> ToEventFilterFunc(EventFilter filter)
+        {
+            return e => (filter.RestaurantId == null || e.RestaurantId == filter.RestaurantId)
+                      && (filter.DateFrom == null || e.Date >= filter.DateFrom)
+                      && (filter.DateTo == null || e.Date <= filter.DateTo)
+                      && e.DeletedAt == null;
+        }
+
+        // EVENT COMMENT
+        public static EventCommentModel ToEventCommentModel(DAL.Models.EventComment comment)
+        {
+            return new EventCommentModel
+            {
+                Id = comment.Id,
+                Poster = ToUserModel(comment.Poster).Name,
+                EventId = comment.EventId,
+                ParentCommentId = comment.ParentCommentId,
+                Content = comment.Content,
+                CreatedAt = comment.CreatedAt.ToLocalTime(),
+                UpdatedAt = comment.UpdatedAt.ToLocalTime(),
+                DeletedAt = comment.DeletedAt?.ToLocalTime(),
+            };
+        }
+
+        public static Expression<Func<DAL.Models.EventComment, bool>> ToEventCommentFilterFunc(EventCommentFilter filter)
+        {
+            return c => (filter.PosterId == null || c.PosterId == filter.PosterId)
+                      && (filter.EventId == null || c.EventId == filter.EventId)
+                      && (filter.ParentCommentId == null || c.ParentCommentId == filter.ParentCommentId)
+                      && c.DeletedAt == null;
+        }
+
+
+        // EVENT PARTICIPANT
+        public static EventParticipantModel ToEventParticipantModel(DAL.Models.EventParticipant participant)
+        {
+            return new EventParticipantModel
+            {
+                Id = participant.Id,
+                UserId = participant.UserId,
+                EventId = participant.EventId,
+                Attendance = participant.Attendance,
+                CreatedAt = participant.CreatedAt.ToLocalTime(),
+                UpdatedAt = participant.UpdatedAt.ToLocalTime(),
+                DeletedAt = participant.DeletedAt?.ToLocalTime(),
+            };
+        }
+
+        public static Expression<Func<DAL.Models.EventParticipant, bool>> ToEventParticipantFilterFunc(EventParticipantFilter filter)
+        {
+            return p => (filter.UserId == null || p.UserId == filter.UserId)
+                      && (filter.EventId == null || p.EventId == filter.EventId)
+                      && p.DeletedAt == null;
+        }
+
     }
 }
diff --git a/Api/Models/Event/EventCreateModel.cs b/Api/Models/Event/EventCreateModel.cs
new file mode 100644
index 0000000..cd5fe1f
--- /dev/null
+++ b/Api/Models/Event/EventCreateModel.cs
@@ -0,0 +1,21 @@
+using System.ComponentModel.DataAnnotations;
+
+namespace Api.Models.Event
+{
+    public class EventCreateModel
+    {
+        [Required]
+        [MaxLength(100)]
+        public string Title { get; set; }
+
+        [Required]
+        public Guid RestaurantId { get; set; }
+
+        [Required]
+        public DateTime Date { get; set; }
+
+        [Required]
+        [MaxLength(3600)]
+        public string Content { get; set; }
+    }
+}
diff --git a/Api/Models/Event/EventFilter.cs b/Api/Models/Event/EventFilter.cs
new file mode 100644
index 0000000..36fd422
--- /dev/null
+++ b/Api/Models/Event/EventFilter.cs
@@ -0,0 +1,13 @@
+using System;
+
+namespace Api.Models.Event
+{
+    public class EventFilter
+    {
+        public Guid? RestaurantId { get; set; }
+
+        public DateTime? DateFrom { get; set; }
+
+        public DateTime? DateTo { get; set; }
+    }
+}
diff --git a/Api/Models/Event/EventModel.cs b/Api/Models/Event/EventModel.cs
new file mode 100644
index 0000000..4053d7e
--- /dev/null
+++ b/Api/Models/Event/EventModel.cs
@@ -0,0 +1,17 @@
+using Api.Models.EventComment;
+using Api.Models.EventParticipant;
+
+namespace Api.Models.Event
+{
+    public class EventModel
+    {
+        public Guid Id { get; set; }
+        public string Title { get; set; }
+        public Guid RestaurantId { get; set; }
+        public DateTime Date { get; set; }
+        public string Content { get; set; }
+        public DateTime CreatedAt { get; set; }
+        public DateTime? UpdatedAt { get; set; }
+        public DateTime? DeletedAt { get; set; }
+    }
+}
diff --git a/Api/Models/Event/EventUpdateModel.cs b/Api/Models/Event/EventUpdateModel.cs
new file mode 100644
index 0000000..5cda265
--- /dev/null
+++ b/Api/Models/Event/EventUpdateModel.cs
@@ -0,0 +1,14 @@
+using System.ComponentModel.DataAnnotations;
+
+namespace Api.Models.Event
+{
+    public class EventUpdateModel
+    {
+        public string Title { get; set; }
+
+        [MaxLength(3200)]
+        public string Content { get; set; }
+
+        public DateTime? Date { get; set; }
+    }
+}
diff --git a/Api/Models/EventComment/EventCommentCreateModel.cs b/Api/Models/EventComment/EventCommentCreateModel.cs
new file mode 100644
index 0000000..5893d32
--- /dev/null
+++ b/Api/Models/EventComment/EventCommentCreateModel.cs
@@ -0,0 +1,19 @@
+using System.ComponentModel.DataAnnotations;
+
+namespace Api.Models.EventComment
+{
+    public class EventCommentCreateModel
+    {
+        [Required]
+        public Guid PosterId { get; set; }
+
+        [Required]
+        public Guid EventId { get; set; }
+
+        [Required]
+        [MaxLength(1800)]
+        public string Content { get; set; }
+
+        public Guid? ParentCommentId { get; set; }
+    }
+}
diff --git a/Api/Models/EventComment/EventCommentFilter.cs b/Api/Models/EventComment/EventCommentFilter.cs
new file mode 100644
index 0000000..3af2cab
--- /dev/null
+++ b/Api/Models/EventComment/EventCommentFilter.cs
@@ -0,0 +1,12 @@
+namespace Api.Models.EventComment
+{
+    public class EventCommentFilter
+    {
+        public Guid? EventId { get; set; }
+        public Guid? PosterId { get; set; }
+        public Guid? ParentCommentId { get; set; }
+        public string ContentKeyword { get; set; }
+        public DateTime? CreatedAfter { get; set; }
+        public DateTime? CreatedBefore { get; set; }
+    }
+}
diff --git a/Api/Models/EventComment/EventCommentModel.cs b/Api/Models/EventComment/EventCommentModel.cs
new file mode 100644
index 0000000..2024977
--- /dev/null
+++ b/Api/Models/EventComment/EventCommentModel.cs
@@ -0,0 +1,15 @@
+namespace Api.Models.EventComment
+{
+    public class EventCommentModel
+    {
+        public Guid Id { get; set; }
+        public Guid PosterId { get; set; }
+        public string Poster { get; set; }
+        public Guid EventId { get; set; }
+        public string Content { get; set; }
+        public Guid? ParentCommentId { get; set; }
+        public DateTime CreatedAt { get; set; }
+        public DateTime UpdatedAt { get; set; }
+        public DateTime? DeletedAt { get; set; }
+    }
+}
diff --git a/Api/Models/EventComment/EventCommentUpdateModel.cs b/Api/Models/EventComment/EventCommentUpdateModel.cs
new file mode 100644
index 0000000..e0baaf6
--- /dev/null
+++ b/Api/Models/EventComment/EventCommentUpdateModel.cs
@@ -0,0 +1,10 @@
+using System.ComponentModel.DataAnnotations;
+
+namespace Api.Models.EventComment
+{
+    public class EventCommentUpdateModel
+    {
+        [MaxLength(1800)]
+        public string Content { get; set; }
+    }
+}
diff --git a/Api/Models/EventParticipant/EventParticipantCreateModel.cs b/Api/Models/EventParticipant/EventParticipantCreateModel.cs
new file mode 100644
index 0000000..dc217e7
--- /dev/null
+++ b/Api/Models/EventParticipant/EventParticipantCreateModel.cs
@@ -0,0 +1,17 @@
+using DAL.Enums;
+using System.ComponentModel.DataAnnotations;
+
+namespace Api.Models.EventParticipant
+{
+    public class EventParticipantCreateModel
+    {
+        [Required]
+        public Guid UserId { get; set; }
+
+        [Required]
+        public Guid EventId { get; set; }
+
+        [Required]
+        public ParticipantType Attendance { get; set; }
+    }
+}
diff --git a/Api/Models/EventParticipant/EventParticipantFilter.cs b/Api/Models/EventParticipant/EventParticipantFilter.cs
new file mode 100644
index 0000000..86dbc09
--- /dev/null
+++ b/Api/Models/EventParticipant/EventParticipantFilter.cs
@@ -0,0 +1,11 @@
+using DAL.Enums;
+
+namespace Api.Models.EventParticipant
+{
+    public class EventParticipantFilter
+    {
+        public Guid? UserId { get; set; }
+        public Guid? EventId { get; set; }
+        public ParticipantType? Attendance { get; set; }
+    }
+}
diff --git a/Api/Models/EventParticipant/EventParticipantModel.cs b/Api/Models/EventParticipant/EventParticipantModel.cs
new file mode 100644
index 0000000..2fe62c5
--- /dev/null
+++ b/Api/Models/EventParticipant/EventParticipantModel.cs
@@ -0,0 +1,16 @@
+using Api.Models.User;
+using DAL.Enums;
+
+namespace Api.Models.EventParticipant
+{
+    public class EventParticipantModel
+    {
+        public Guid Id { get; set; }
+        public Guid UserId { get; set; }
+        public Guid EventId { get; set; }
+        public ParticipantType Attendance { get; set; }
+        public DateTime CreatedAt { get; set; }
+        public DateTime UpdatedAt { get; set; }
+        public DateTime? DeletedAt { get; set; }
+    }
+}
diff --git a/Api/Models/EventParticipant/EventParticipantUpdateModel.cs b/Api/Models/EventParticipant/EventParticipantUpdateModel.cs
new file mode 100644
index 0000000..3925295
--- /dev/null
+++ b/Api/Models/EventParticipant/EventParticipantUpdateModel.cs
@@ -0,0 +1,9 @@
+using DAL.Enums;
+
+namespace Api.Models.EventParticipant
+{
+    public class EventParticipantUpdateModel
+    {
+        public ParticipantType? Attendance { get; set; }
+    }
+}
diff --git a/DAL/Constants/SeedingValues.cs b/DAL/Constants/SeedingValues.cs
index a8d54f8..eb1067d 100644
--- a/DAL/Constants/SeedingValues.cs
+++ b/DAL/Constants/SeedingValues.cs
@@ -2,11 +2,30 @@
 {
     public class SeedingValues
     {
+        // User IDs
         public static readonly Guid User1Id = new("afde48a8-99e3-423e-a48d-9e4e6a3d823a");
         public static readonly Guid User2Id = new("32669bac-a1bd-4e9d-9580-ee4a7764cda3");
+
+        // Review IDs
         public static readonly Guid Review1Id = new("32669bac-a1bd-4e9d-9580-ee4a7764cda3");
+
+        // Review Comment IDs
         public static readonly Guid ReviewComment1Id = new("af16972e-d611-4c1c-8ce3-a93d667c1b9a");
         public static readonly Guid ReviewComment2Id = new("9ddbe84f-f2f4-4532-bca7-13152905c3b1");
+
+        // Restaurant IDs
         public static readonly Guid Restaurant1Id = new("104fdc28-a328-4f7b-85fe-81550cf1e247");
+
+        // Event IDs
+        public static readonly Guid Event1Id = new("10ba7f93-895f-4be5-987e-45e6d55bda3f");
+        public static readonly Guid Event2Id = new("c68be8ab-fc64-456a-a5fb-f97f2a96e2d1");
+
+        // Event Participant IDs
+        public static readonly Guid EventParticipant1Id = new("7c673b56-473e-4fd4-b49b-f69e8fd9adbb");
+        public static readonly Guid EventParticipant2Id = new("6f995134-cbcf-42d2-9ec9-e028d85726b9");
+
+        // Event Comment IDs
+        public static readonly Guid EventComment1Id = new("88a4b4d4-b74d-40c5-823e-e708f15664d3");
+        public static readonly Guid EventComment2Id = new("e0e27f5b-bd6f-47d9-b5fa-5b7e7e77d462");
     }
 }
diff --git a/DAL/Data/DataInitializer.cs b/DAL/Data/DataInitializer.cs
index 96720b7..55869d2 100644
--- a/DAL/Data/DataInitializer.cs
+++ b/DAL/Data/DataInitializer.cs
@@ -11,6 +11,9 @@ namespace DAL.Data
             var users = PrepareUserModels();
             var reviews = PrepareReviewModels();
             var reviewComments = PrepareReviewCommentsModels();
+            var events = PrepareEventModels();
+            var eventParticipants = PrepareEventParticipantModels();
+            var eventComments = PrepareEventCommentModels();
 
             var reviewAggregates = reviews
                 .GroupBy(r => r.RestaurantId)
@@ -30,6 +33,12 @@ namespace DAL.Data
                 .HasData(reviewComments);
             modelBuilder.Entity<ReviewAggregate>()
                 .HasData(reviewAggregates);
+            modelBuilder.Entity<Event>()
+                .HasData(events);
+            modelBuilder.Entity<EventParticipant>()
+                .HasData(eventParticipants);
+            modelBuilder.Entity<EventComment>()
+                .HasData(eventComments);
         }
 
         private static List<User> PrepareUserModels()
@@ -91,5 +100,83 @@ namespace DAL.Data
                 }
             ];
         }
+
+        private static List<Event> PrepareEventModels()
+        {
+            return new List<Event>
+            {
+                new Event
+                {
+                    Id = SeedingValues.Event1Id,
+                    Title = "Exclusive Dinner Event",
+                    RestaurantId = SeedingValues.Restaurant1Id,
+                    Date = DateTime.UtcNow.AddDays(10),
+                    Content = "Join us for an exclusive dinner with gourmet cuisine.",
+                    CreatedAt = DateTime.UtcNow,
+                    UpdatedAt = DateTime.UtcNow
+                },
+                new Event
+                {
+                    Id = SeedingValues.Event2Id,
+                    Title = "Wine Tasting Evening",
+                    RestaurantId = SeedingValues.Restaurant1Id,
+                    Date = DateTime.UtcNow.AddDays(15),
+                    Content = "An evening of exquisite wine tasting with fine cheeses.",
+                    CreatedAt = DateTime.UtcNow,
+                    UpdatedAt = DateTime.UtcNow
+                }
+            };
+        }
+
+        private static List<EventParticipant> PrepareEventParticipantModels()
+        {
+            return new List<EventParticipant>
+            {
+                new EventParticipant
+                {
+                    Id = SeedingValues.EventParticipant1Id,
+                    UserId = SeedingValues.User1Id,
+                    EventId = SeedingValues.Event1Id,
+                    Attendance = Enums.ParticipantType.Attending,
+                    CreatedAt = DateTime.UtcNow,
+                    UpdatedAt = DateTime.UtcNow
+                },
+                new EventParticipant
+                {
+                    Id = SeedingValues.EventParticipant2Id,
+                    UserId = SeedingValues.User2Id,
+                    EventId = SeedingValues.Event1Id,
+                    Attendance = Enums.ParticipantType.Tentative,
+                    CreatedAt = DateTime.UtcNow,
+                    UpdatedAt = DateTime.UtcNow
+                }
+            };
+        }
+
+        private static List<EventComment> PrepareEventCommentModels()
+        {
+            return new List<EventComment>
+            {
+                new EventComment
+                {
+                    Id = SeedingValues.EventComment1Id,
+                    PosterId = SeedingValues.User2Id,
+                    EventId = SeedingValues.Event1Id,
+                    Content = "Looking forward to this event!",
+                    CreatedAt = DateTime.UtcNow,
+                    UpdatedAt = DateTime.UtcNow
+                },
+                new EventComment
+                {
+                    Id = SeedingValues.EventComment2Id,
+                    PosterId = SeedingValues.User1Id,
+                    EventId = SeedingValues.Event1Id,
+                    ParentCommentId = SeedingValues.EventComment1Id,
+                    Content = "Me too! It sounds like a great night.",
+                    CreatedAt = DateTime.UtcNow,
+                    UpdatedAt = DateTime.UtcNow
+                }
+            };
+        }
     }
 }
diff --git a/DAL/Data/RestaurantDBContext.cs b/DAL/Data/RestaurantDBContext.cs
index 90842ab..668134e 100644
--- a/DAL/Data/RestaurantDBContext.cs
+++ b/DAL/Data/RestaurantDBContext.cs
@@ -9,6 +9,9 @@ namespace DAL.Data
         public DbSet<Review> Reviews { get; set; }
         public DbSet<ReviewComment> ReviewComments { get; set; }
         public DbSet<ReviewAggregate> ReviewAggregate { get; set; }
+        public DbSet<Event> Events { get; set; }
+        public DbSet<EventComment>  EventComments { get; set; }
+        public DbSet<EventParticipant> EventParticipants { get; set; }
 
         public RestaurantDBContext(DbContextOptions<RestaurantDBContext> options) : base(options) { }
 
diff --git a/DAL/Enums/ParticipantType.cs b/DAL/Enums/ParticipantType.cs
new file mode 100644
index 0000000..b5f52c6
--- /dev/null
+++ b/DAL/Enums/ParticipantType.cs
@@ -0,0 +1,11 @@
+namespace DAL.Enums
+{
+    public enum ParticipantType
+    {
+        Creator,
+        Attending,
+        Declined,
+        Tentative
+    }
+
+}
diff --git a/DAL/Models/Event.cs b/DAL/Models/Event.cs
new file mode 100644
index 0000000..a8e865a
--- /dev/null
+++ b/DAL/Models/Event.cs
@@ -0,0 +1,31 @@
+using System.ComponentModel.DataAnnotations.Schema;
+using System.ComponentModel.DataAnnotations;
+
+namespace DAL.Models
+{
+
+    public class Event : BaseEntity
+    {
+        [Required]
+        [MaxLength(100)]
+        public string Title { get; set; }
+
+        [Required]
+        public Guid RestaurantId { get; set; }
+
+        [Required]
+        public DateTime Date { get; set; }
+
+        [Required]
+        [MaxLength(3600)]
+        public string Content { get; set; }
+
+        [ForeignKey(nameof(RestaurantId))]
+        public virtual Restaurant Restaurant { get; set; }
+
+        public virtual ICollection<EventParticipant> Participants { get; set; } = new List<EventParticipant>();
+
+        public virtual ICollection<EventComment> Comments { get; set; } = new List<EventComment>();
+    }
+
+}
diff --git a/DAL/Models/EventComment.cs b/DAL/Models/EventComment.cs
new file mode 100644
index 0000000..1f20505
--- /dev/null
+++ b/DAL/Models/EventComment.cs
@@ -0,0 +1,26 @@
+using System.ComponentModel.DataAnnotations;
+using System.ComponentModel.DataAnnotations.Schema;
+
+namespace DAL.Models
+{
+    public class EventComment : CommentBase
+    {
+        [Required]
+        public Guid PosterId { get; set; }
+
+        public Guid? ParentCommentId { get; set; }
+
+        [Required]
+        public Guid EventId { get; set; }
+
+        [ForeignKey(nameof(PosterId))]
+        public virtual User Poster {  get; set; }
+
+        [ForeignKey(nameof(ParentCommentId))]
+        public virtual EventComment ParentComment { get; set; }
+
+        [ForeignKey(nameof(EventId))]
+        public virtual Event Event { get; set; }
+    }
+
+}
diff --git a/DAL/Models/EventParticipant .cs b/DAL/Models/EventParticipant .cs
new file mode 100644
index 0000000..a9c2fd9
--- /dev/null
+++ b/DAL/Models/EventParticipant .cs	
@@ -0,0 +1,24 @@
+using System.ComponentModel.DataAnnotations.Schema;
+using System.ComponentModel.DataAnnotations;
+using DAL.Enums;
+
+namespace DAL.Models
+{
+    public class EventParticipant : BaseEntity
+    {
+        [Required]
+        public Guid UserId { get; set; }
+
+        [Required]
+        public Guid EventId { get; set; }
+
+        [Required]
+        public ParticipantType Attendance { get; set; }
+
+        [ForeignKey(nameof(UserId))]
+        public virtual User User { get; set; }
+
+        [ForeignKey(nameof(EventId))]
+        public virtual Event Event { get; set; }
+    }
+}
diff --git a/DAL/Models/Restaurant.cs b/DAL/Models/Restaurant.cs
new file mode 100644
index 0000000..33e43ae
--- /dev/null
+++ b/DAL/Models/Restaurant.cs
@@ -0,0 +1,7 @@
+namespace DAL.Models
+{
+    public class Restaurant
+    {
+        // placeholder till implemented
+    }
+}
-- 
GitLab