diff --git a/TableBookingAPI/TableBooking.Logic/Converters/TableConverters/TableToGetConverter.cs b/TableBookingAPI/TableBooking.Logic/Converters/TableConverters/TableToGetConverter.cs index ec97484..89951d5 100644 --- a/TableBookingAPI/TableBooking.Logic/Converters/TableConverters/TableToGetConverter.cs +++ b/TableBookingAPI/TableBooking.Logic/Converters/TableConverters/TableToGetConverter.cs @@ -21,7 +21,8 @@ public GetTablesDto TableToTableDto(Table table) { Id = table.Id, RestaurantId = table.RestaurantId, - NumberOfSeats = table.NumberOfSeats + NumberOfSeats = table.NumberOfSeats, + Bookings = table.Bookings }; } } diff --git a/TableBookingAPI/TableBooking.Logic/Repositories/BookingRepository.cs b/TableBookingAPI/TableBooking.Logic/Repositories/BookingRepository.cs index 816f627..accfd88 100644 --- a/TableBookingAPI/TableBooking.Logic/Repositories/BookingRepository.cs +++ b/TableBookingAPI/TableBooking.Logic/Repositories/BookingRepository.cs @@ -13,12 +13,12 @@ public BookingRepository(TableBookingContext context) : base(context) public async Task> GetAllBookingsForSpecificUserAsync(Guid userId) { - return await _objectSet.Where(x => x.Id.Equals(userId)).ToListAsync(); + return await _objectSet.Where(x => x.AppUserId.Equals(userId)).ToListAsync(); } public async Task GetBookingByIdForSpecificUserAsync(Guid bookingId, Guid userId) { - return await _objectSet.FirstOrDefaultAsync(x => x.Id.Equals(bookingId) && x.Id.Equals(userId)); + return await _objectSet.FirstOrDefaultAsync(x => x.Id.Equals(bookingId) && x.AppUserId.Equals(userId)); } } } diff --git a/TableBookingAPI/TableBooking.Logic/Repositories/GenericRepository.cs b/TableBookingAPI/TableBooking.Logic/Repositories/GenericRepository.cs index 765cf03..438fc49 100644 --- a/TableBookingAPI/TableBooking.Logic/Repositories/GenericRepository.cs +++ b/TableBookingAPI/TableBooking.Logic/Repositories/GenericRepository.cs @@ -35,10 +35,22 @@ public async Task Delete(object id) _objectSet.Remove(objectToDelete); } - public async virtual Task Update(T entity) + public async Task Update(T entity) { - await Update(entity); - } + var existingEntity = await _objectSet.FindAsync(GetKeyValues(entity)); + + if (existingEntity != null) + { + _context.Entry(existingEntity).State = EntityState.Detached; + } + _objectSet.Update(entity); + } + + private object[] GetKeyValues(T entity) + { + var keyProperties = _context.Model.FindEntityType(typeof(T)).FindPrimaryKey().Properties; + return keyProperties.Select(prop => prop.PropertyInfo.GetValue(entity)).ToArray(); + } } } diff --git a/TableBookingAPI/TableBooking.Logic/Repositories/TableRepository.cs b/TableBookingAPI/TableBooking.Logic/Repositories/TableRepository.cs index 6471c9d..cb58f54 100644 --- a/TableBookingAPI/TableBooking.Logic/Repositories/TableRepository.cs +++ b/TableBookingAPI/TableBooking.Logic/Repositories/TableRepository.cs @@ -13,7 +13,7 @@ public TableRepository(TableBookingContext context) : base(context) public async Task> GetTablesByRestaurantIdAsync(Guid restaurantId) { return await _objectSet - .Include(x => x.RestaurantId) + .Include(x => x.Restaurant) .Where(x => x.RestaurantId.Equals(restaurantId)) .ToListAsync(); } diff --git a/TableBookingAPI/TableBooking.Model/Dtos/BookingDtos/BookingDto.cs b/TableBookingAPI/TableBooking.Model/Dtos/BookingDtos/BookingDto.cs index 10d7a02..e915bf4 100644 --- a/TableBookingAPI/TableBooking.Model/Dtos/BookingDtos/BookingDto.cs +++ b/TableBookingAPI/TableBooking.Model/Dtos/BookingDtos/BookingDto.cs @@ -7,7 +7,7 @@ public class BookingDto public Guid Id { get; set; } public DateTime Date { get; set; } public int DurationInMinutes { get; set; } - public TableDto TableDto { get; set; } - public Guid UserId { get; set; } + public int AmountOfPeople { get; set; } + public Guid AppUserId { get; set; } } } diff --git a/TableBookingAPI/TableBooking.Model/Dtos/BookingDtos/CreateBookingDto.cs b/TableBookingAPI/TableBooking.Model/Dtos/BookingDtos/CreateBookingDto.cs index 29a3186..05986a5 100644 --- a/TableBookingAPI/TableBooking.Model/Dtos/BookingDtos/CreateBookingDto.cs +++ b/TableBookingAPI/TableBooking.Model/Dtos/BookingDtos/CreateBookingDto.cs @@ -1,9 +1,11 @@ -namespace TableBooking.Model.Dtos.BookingDtos +using TableBooking.Model.Models; + +namespace TableBooking.Model.Dtos.BookingDtos { public class CreateBookingDto { public DateTime Date { get; set; } public int DurationInMinutes { get; set; } - public Guid TableId { get; set; } + public int AmountOfPeople { get; set; } } } \ No newline at end of file diff --git a/TableBookingAPI/TableBooking.Model/Dtos/BookingDtos/UpdateBookingDto.cs b/TableBookingAPI/TableBooking.Model/Dtos/BookingDtos/UpdateBookingDto.cs index ab13ae7..c9b5f86 100644 --- a/TableBookingAPI/TableBooking.Model/Dtos/BookingDtos/UpdateBookingDto.cs +++ b/TableBookingAPI/TableBooking.Model/Dtos/BookingDtos/UpdateBookingDto.cs @@ -4,6 +4,6 @@ public class UpdateBookingDto { public DateTime Date { get; set; } public int DurationInMinutes { get; set; } - public Guid TableId { get; set; } + public int AmountOfPeople { get; set; } } } diff --git a/TableBookingAPI/TableBooking.Model/Dtos/RestaurantDtos/RestaurantShortInfoDto.cs b/TableBookingAPI/TableBooking.Model/Dtos/RestaurantDtos/RestaurantShortInfoDto.cs index 53aa2a2..e190689 100644 --- a/TableBookingAPI/TableBooking.Model/Dtos/RestaurantDtos/RestaurantShortInfoDto.cs +++ b/TableBookingAPI/TableBooking.Model/Dtos/RestaurantDtos/RestaurantShortInfoDto.cs @@ -7,6 +7,7 @@ public class RestaurantShortInfoDto public string Name { get; set; } public string Type { get; set; } public string Description { get; set; } + public string Phone { get; set; } public string Location { get; set; } public string ImageURL { get; set; } public int Rating { get; set; } diff --git a/TableBookingAPI/TableBooking.Model/Dtos/TableDtos/GetTablesDto.cs b/TableBookingAPI/TableBooking.Model/Dtos/TableDtos/GetTablesDto.cs index a04f491..68eb2b5 100644 --- a/TableBookingAPI/TableBooking.Model/Dtos/TableDtos/GetTablesDto.cs +++ b/TableBookingAPI/TableBooking.Model/Dtos/TableDtos/GetTablesDto.cs @@ -1,9 +1,12 @@ -namespace TableBooking.Model.Dtos.TableDtos +using TableBooking.Model.Models; + +namespace TableBooking.Model.Dtos.TableDtos { public class GetTablesDto { public Guid Id { get; set; } public int NumberOfSeats { get; set; } public Guid RestaurantId { get; set; } + public IEnumerable? Bookings { get; set; } } } diff --git a/TableBookingAPI/TableBooking.Model/Dtos/TableDtos/TableDto.cs b/TableBookingAPI/TableBooking.Model/Dtos/TableDtos/TableDto.cs index c3ecee8..fe62546 100644 --- a/TableBookingAPI/TableBooking.Model/Dtos/TableDtos/TableDto.cs +++ b/TableBookingAPI/TableBooking.Model/Dtos/TableDtos/TableDto.cs @@ -2,7 +2,7 @@ namespace TableBooking.Model.Dtos.TableDtos { - public class TableDto : Entity + public class TableDto { public int NumberOfSeats { get; set; } public Guid RestaurantId { get; set; } diff --git a/TableBookingAPI/TableBooking.Model/Migrations/20231119221110_AmountOfPeopleInBookings.Designer.cs b/TableBookingAPI/TableBooking.Model/Migrations/20231119221110_AmountOfPeopleInBookings.Designer.cs new file mode 100644 index 0000000..d2f737f --- /dev/null +++ b/TableBookingAPI/TableBooking.Model/Migrations/20231119221110_AmountOfPeopleInBookings.Designer.cs @@ -0,0 +1,279 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; +using TableBooking.Model; + +#nullable disable + +namespace TableBooking.Model.Migrations +{ + [DbContext(typeof(TableBookingContext))] + [Migration("20231119221110_AmountOfPeopleInBookings")] + partial class AmountOfPeopleInBookings + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "6.0.15") + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + + modelBuilder.Entity("TableBooking.Model.Models.AppUser", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AccessFailedCount") + .HasColumnType("integer"); + + b.Property("ConcurrencyStamp") + .HasColumnType("text"); + + b.Property("Email") + .HasColumnType("text"); + + b.Property("EmailConfirmed") + .HasColumnType("boolean"); + + b.Property("LockoutEnabled") + .HasColumnType("boolean"); + + b.Property("LockoutEnd") + .HasColumnType("timestamp with time zone"); + + b.Property("NormalizedEmail") + .HasColumnType("text"); + + b.Property("NormalizedUserName") + .HasColumnType("text"); + + b.Property("PasswordHash") + .HasColumnType("text"); + + b.Property("PhoneNumber") + .HasColumnType("text"); + + b.Property("PhoneNumberConfirmed") + .HasColumnType("boolean"); + + b.Property("RefreshToken") + .HasColumnType("text"); + + b.Property("RefreshTokenExpiryTime") + .HasColumnType("timestamp with time zone"); + + b.Property("SecurityStamp") + .HasColumnType("text"); + + b.Property("TwoFactorEnabled") + .HasColumnType("boolean"); + + b.Property("UserName") + .HasColumnType("text"); + + b.HasKey("Id"); + + b.ToTable("Users"); + }); + + modelBuilder.Entity("TableBooking.Model.Models.Booking", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AmountOfPeople") + .HasColumnType("integer"); + + b.Property("AppUserId") + .HasColumnType("uuid"); + + b.Property("Date") + .HasColumnType("timestamp with time zone"); + + b.Property("DurationInMinutes") + .HasColumnType("integer"); + + b.Property("TableId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("AppUserId"); + + b.HasIndex("TableId"); + + b.ToTable("Bookings"); + }); + + modelBuilder.Entity("TableBooking.Model.Models.Rating", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AppUserId") + .HasColumnType("uuid"); + + b.Property("Comment") + .IsRequired() + .HasColumnType("text"); + + b.Property("DateOfRating") + .HasColumnType("timestamp with time zone"); + + b.Property("NumberOfLikes") + .HasColumnType("integer"); + + b.Property("RatingStars") + .HasColumnType("integer"); + + b.Property("RestaurantId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("AppUserId"); + + b.HasIndex("RestaurantId"); + + b.ToTable("Ratings"); + }); + + modelBuilder.Entity("TableBooking.Model.Models.Restaurant", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CloseTime") + .HasColumnType("timestamp with time zone"); + + b.Property("Description") + .HasColumnType("text"); + + b.Property("Location") + .IsRequired() + .HasColumnType("text"); + + b.Property("Name") + .IsRequired() + .HasColumnType("text"); + + b.Property("OpenTime") + .HasColumnType("timestamp with time zone"); + + b.Property("Phone") + .IsRequired() + .HasColumnType("text"); + + b.Property("Price") + .HasColumnType("integer"); + + b.Property("PrimaryImageURL") + .HasColumnType("text"); + + b.Property("Rating") + .HasColumnType("real"); + + b.Property("SecondaryImageURL") + .HasColumnType("text"); + + b.Property("Type") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.ToTable("Restaurants"); + }); + + modelBuilder.Entity("TableBooking.Model.Models.Table", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("NumberOfSeats") + .HasColumnType("integer"); + + b.Property("RestaurantId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("RestaurantId"); + + b.ToTable("Tables"); + }); + + modelBuilder.Entity("TableBooking.Model.Models.Booking", b => + { + b.HasOne("TableBooking.Model.Models.AppUser", null) + .WithMany("Bookings") + .HasForeignKey("AppUserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("TableBooking.Model.Models.Table", "Table") + .WithMany("Bookings") + .HasForeignKey("TableId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Table"); + }); + + modelBuilder.Entity("TableBooking.Model.Models.Rating", b => + { + b.HasOne("TableBooking.Model.Models.AppUser", "AppUser") + .WithMany() + .HasForeignKey("AppUserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("TableBooking.Model.Models.Restaurant", "Restaurant") + .WithMany() + .HasForeignKey("RestaurantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("AppUser"); + + b.Navigation("Restaurant"); + }); + + modelBuilder.Entity("TableBooking.Model.Models.Table", b => + { + b.HasOne("TableBooking.Model.Models.Restaurant", null) + .WithMany("Tables") + .HasForeignKey("RestaurantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("TableBooking.Model.Models.AppUser", b => + { + b.Navigation("Bookings"); + }); + + modelBuilder.Entity("TableBooking.Model.Models.Restaurant", b => + { + b.Navigation("Tables"); + }); + + modelBuilder.Entity("TableBooking.Model.Models.Table", b => + { + b.Navigation("Bookings"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/TableBookingAPI/TableBooking.Model/Migrations/20231119221110_AmountOfPeopleInBookings.cs b/TableBookingAPI/TableBooking.Model/Migrations/20231119221110_AmountOfPeopleInBookings.cs new file mode 100644 index 0000000..a92a1f1 --- /dev/null +++ b/TableBookingAPI/TableBooking.Model/Migrations/20231119221110_AmountOfPeopleInBookings.cs @@ -0,0 +1,26 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace TableBooking.Model.Migrations +{ + public partial class AmountOfPeopleInBookings : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "AmountOfPeople", + table: "Bookings", + type: "integer", + nullable: false, + defaultValue: 0); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn( + name: "AmountOfPeople", + table: "Bookings"); + } + } +} diff --git a/TableBookingAPI/TableBooking.Model/Migrations/20231202144812_restaurant_rating_and_approle.Designer.cs b/TableBookingAPI/TableBooking.Model/Migrations/20231202144812_restaurant_rating_and_approle.Designer.cs new file mode 100644 index 0000000..f19146e --- /dev/null +++ b/TableBookingAPI/TableBooking.Model/Migrations/20231202144812_restaurant_rating_and_approle.Designer.cs @@ -0,0 +1,314 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; +using TableBooking.Model; + +#nullable disable + +namespace TableBooking.Model.Migrations +{ + [DbContext(typeof(TableBookingContext))] + [Migration("20231202144812_restaurant_rating_and_approle")] + partial class restaurant_rating_and_approle + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "6.0.15") + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + + modelBuilder.Entity("TableBooking.Model.Models.AppRole", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ConcurrencyStamp") + .HasColumnType("text"); + + b.Property("Name") + .HasColumnType("text"); + + b.Property("NormalizedName") + .HasColumnType("text"); + + b.HasKey("Id"); + + b.ToTable("Roles"); + }); + + modelBuilder.Entity("TableBooking.Model.Models.AppUser", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AccessFailedCount") + .HasColumnType("integer"); + + b.Property("AppRoleId") + .HasColumnType("uuid"); + + b.Property("ConcurrencyStamp") + .HasColumnType("text"); + + b.Property("Email") + .HasColumnType("text"); + + b.Property("EmailConfirmed") + .HasColumnType("boolean"); + + b.Property("LockoutEnabled") + .HasColumnType("boolean"); + + b.Property("LockoutEnd") + .HasColumnType("timestamp with time zone"); + + b.Property("NormalizedEmail") + .HasColumnType("text"); + + b.Property("NormalizedUserName") + .HasColumnType("text"); + + b.Property("PasswordHash") + .HasColumnType("text"); + + b.Property("PhoneNumber") + .HasColumnType("text"); + + b.Property("PhoneNumberConfirmed") + .HasColumnType("boolean"); + + b.Property("RefreshToken") + .HasColumnType("text"); + + b.Property("RefreshTokenExpiryTime") + .HasColumnType("timestamp with time zone"); + + b.Property("SecurityStamp") + .HasColumnType("text"); + + b.Property("TwoFactorEnabled") + .HasColumnType("boolean"); + + b.Property("UserName") + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("AppRoleId"); + + b.ToTable("Users"); + }); + + modelBuilder.Entity("TableBooking.Model.Models.Booking", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AmountOfPeople") + .HasColumnType("integer"); + + b.Property("AppUserId") + .HasColumnType("uuid"); + + b.Property("Date") + .HasColumnType("timestamp with time zone"); + + b.Property("DurationInMinutes") + .HasColumnType("integer"); + + b.Property("TableId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("AppUserId"); + + b.HasIndex("TableId"); + + b.ToTable("Bookings"); + }); + + modelBuilder.Entity("TableBooking.Model.Models.Rating", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AppUserId") + .HasColumnType("uuid"); + + b.Property("Comment") + .IsRequired() + .HasColumnType("text"); + + b.Property("DateOfRating") + .HasColumnType("timestamp with time zone"); + + b.Property("NumberOfLikes") + .HasColumnType("integer"); + + b.Property("RatingStars") + .HasColumnType("integer"); + + b.Property("RestaurantId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("AppUserId"); + + b.HasIndex("RestaurantId"); + + b.ToTable("Ratings"); + }); + + modelBuilder.Entity("TableBooking.Model.Models.Restaurant", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CloseTime") + .HasColumnType("timestamp with time zone"); + + b.Property("Description") + .HasColumnType("text"); + + b.Property("Location") + .IsRequired() + .HasColumnType("text"); + + b.Property("Name") + .IsRequired() + .HasColumnType("text"); + + b.Property("OpenTime") + .HasColumnType("timestamp with time zone"); + + b.Property("Phone") + .IsRequired() + .HasColumnType("text"); + + b.Property("Price") + .HasColumnType("integer"); + + b.Property("PrimaryImageURL") + .HasColumnType("text"); + + b.Property("Rating") + .HasPrecision(1, 1) + .HasColumnType("double precision"); + + b.Property("SecondaryImageURL") + .HasColumnType("text"); + + b.Property("Type") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.ToTable("Restaurants"); + }); + + modelBuilder.Entity("TableBooking.Model.Models.Table", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("NumberOfSeats") + .HasColumnType("integer"); + + b.Property("RestaurantId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("RestaurantId"); + + b.ToTable("Tables"); + }); + + modelBuilder.Entity("TableBooking.Model.Models.AppUser", b => + { + b.HasOne("TableBooking.Model.Models.AppRole", "AppRole") + .WithMany() + .HasForeignKey("AppRoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("AppRole"); + }); + + modelBuilder.Entity("TableBooking.Model.Models.Booking", b => + { + b.HasOne("TableBooking.Model.Models.AppUser", null) + .WithMany("Bookings") + .HasForeignKey("AppUserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("TableBooking.Model.Models.Table", null) + .WithMany("Bookings") + .HasForeignKey("TableId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("TableBooking.Model.Models.Rating", b => + { + b.HasOne("TableBooking.Model.Models.AppUser", "AppUser") + .WithMany() + .HasForeignKey("AppUserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("TableBooking.Model.Models.Restaurant", "Restaurant") + .WithMany() + .HasForeignKey("RestaurantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("AppUser"); + + b.Navigation("Restaurant"); + }); + + modelBuilder.Entity("TableBooking.Model.Models.Table", b => + { + b.HasOne("TableBooking.Model.Models.Restaurant", null) + .WithMany("Tables") + .HasForeignKey("RestaurantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("TableBooking.Model.Models.AppUser", b => + { + b.Navigation("Bookings"); + }); + + modelBuilder.Entity("TableBooking.Model.Models.Restaurant", b => + { + b.Navigation("Tables"); + }); + + modelBuilder.Entity("TableBooking.Model.Models.Table", b => + { + b.Navigation("Bookings"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/TableBookingAPI/TableBooking.Model/Migrations/20231202144812_restaurant_rating_and_approle.cs b/TableBookingAPI/TableBooking.Model/Migrations/20231202144812_restaurant_rating_and_approle.cs new file mode 100644 index 0000000..bd6d328 --- /dev/null +++ b/TableBookingAPI/TableBooking.Model/Migrations/20231202144812_restaurant_rating_and_approle.cs @@ -0,0 +1,85 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace TableBooking.Model.Migrations +{ + public partial class restaurant_rating_and_approle : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "AppRoleId", + table: "Users", + type: "uuid", + nullable: false, + defaultValue: new Guid("00000000-0000-0000-0000-000000000000")); + + migrationBuilder.AlterColumn( + name: "Rating", + table: "Restaurants", + type: "double precision", + precision: 1, + scale: 1, + nullable: false, + oldClrType: typeof(float), + oldType: "real"); + + migrationBuilder.CreateTable( + name: "Roles", + columns: table => new + { + Id = table.Column(type: "uuid", nullable: false), + Name = table.Column(type: "text", nullable: true), + NormalizedName = table.Column(type: "text", nullable: true), + ConcurrencyStamp = table.Column(type: "text", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_Roles", x => x.Id); + }); + + migrationBuilder.CreateIndex( + name: "IX_Users_AppRoleId", + table: "Users", + column: "AppRoleId"); + + migrationBuilder.AddForeignKey( + name: "FK_Users_Roles_AppRoleId", + table: "Users", + column: "AppRoleId", + principalTable: "Roles", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropForeignKey( + name: "FK_Users_Roles_AppRoleId", + table: "Users"); + + migrationBuilder.DropTable( + name: "Roles"); + + migrationBuilder.DropIndex( + name: "IX_Users_AppRoleId", + table: "Users"); + + migrationBuilder.DropColumn( + name: "AppRoleId", + table: "Users"); + + migrationBuilder.AlterColumn( + name: "Rating", + table: "Restaurants", + type: "real", + nullable: false, + oldClrType: typeof(double), + oldType: "double precision", + oldPrecision: 1, + oldScale: 1); + } + } +} diff --git a/TableBookingAPI/TableBooking.Model/Migrations/TableBookingContextModelSnapshot.cs b/TableBookingAPI/TableBooking.Model/Migrations/TableBookingContextModelSnapshot.cs index e277a52..e2676ef 100644 --- a/TableBookingAPI/TableBooking.Model/Migrations/TableBookingContextModelSnapshot.cs +++ b/TableBookingAPI/TableBooking.Model/Migrations/TableBookingContextModelSnapshot.cs @@ -22,6 +22,26 @@ protected override void BuildModel(ModelBuilder modelBuilder) NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + modelBuilder.Entity("TableBooking.Model.Models.AppRole", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ConcurrencyStamp") + .HasColumnType("text"); + + b.Property("Name") + .HasColumnType("text"); + + b.Property("NormalizedName") + .HasColumnType("text"); + + b.HasKey("Id"); + + b.ToTable("Roles"); + }); + modelBuilder.Entity("TableBooking.Model.Models.AppUser", b => { b.Property("Id") @@ -31,6 +51,9 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.Property("AccessFailedCount") .HasColumnType("integer"); + b.Property("AppRoleId") + .HasColumnType("uuid"); + b.Property("ConcurrencyStamp") .HasColumnType("text"); @@ -78,6 +101,8 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.HasKey("Id"); + b.HasIndex("AppRoleId"); + b.ToTable("Users"); }); @@ -87,6 +112,9 @@ protected override void BuildModel(ModelBuilder modelBuilder) .ValueGeneratedOnAdd() .HasColumnType("uuid"); + b.Property("AmountOfPeople") + .HasColumnType("integer"); + b.Property("AppUserId") .HasColumnType("uuid"); @@ -175,8 +203,9 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.Property("PrimaryImageURL") .HasColumnType("text"); - b.Property("Rating") - .HasColumnType("real"); + b.Property("Rating") + .HasPrecision(1, 1) + .HasColumnType("double precision"); b.Property("SecondaryImageURL") .HasColumnType("text"); @@ -209,6 +238,17 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.ToTable("Tables"); }); + modelBuilder.Entity("TableBooking.Model.Models.AppUser", b => + { + b.HasOne("TableBooking.Model.Models.AppRole", "AppRole") + .WithMany() + .HasForeignKey("AppRoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("AppRole"); + }); + modelBuilder.Entity("TableBooking.Model.Models.Booking", b => { b.HasOne("TableBooking.Model.Models.AppUser", null) @@ -217,13 +257,11 @@ protected override void BuildModel(ModelBuilder modelBuilder) .OnDelete(DeleteBehavior.Cascade) .IsRequired(); - b.HasOne("TableBooking.Model.Models.Table", "Table") + b.HasOne("TableBooking.Model.Models.Table", null) .WithMany("Bookings") .HasForeignKey("TableId") .OnDelete(DeleteBehavior.Cascade) .IsRequired(); - - b.Navigation("Table"); }); modelBuilder.Entity("TableBooking.Model.Models.Rating", b => diff --git a/TableBookingAPI/TableBooking.Model/Models/AppRole.cs b/TableBookingAPI/TableBooking.Model/Models/AppRole.cs index ec53207..b3f471d 100644 --- a/TableBookingAPI/TableBooking.Model/Models/AppRole.cs +++ b/TableBookingAPI/TableBooking.Model/Models/AppRole.cs @@ -4,12 +4,5 @@ namespace TableBooking.Model.Models { public class AppRole : IdentityRole { - public AppRole() - { - } - public AppRole(string roleName) - { - Name = roleName; - } } } \ No newline at end of file diff --git a/TableBookingAPI/TableBooking.Model/Models/AppUser.cs b/TableBookingAPI/TableBooking.Model/Models/AppUser.cs index d863b2f..8a3c101 100644 --- a/TableBookingAPI/TableBooking.Model/Models/AppUser.cs +++ b/TableBookingAPI/TableBooking.Model/Models/AppUser.cs @@ -7,5 +7,7 @@ public class AppUser : IdentityUser public string? RefreshToken { get; set; } public DateTime? RefreshTokenExpiryTime { get; set; } public IEnumerable Bookings { get; set; } + public Guid AppRoleId { get; set; } + public AppRole AppRole { get; set; } } } \ No newline at end of file diff --git a/TableBookingAPI/TableBooking.Model/Models/Booking.cs b/TableBookingAPI/TableBooking.Model/Models/Booking.cs index 9fed1aa..5188adf 100644 --- a/TableBookingAPI/TableBooking.Model/Models/Booking.cs +++ b/TableBookingAPI/TableBooking.Model/Models/Booking.cs @@ -4,7 +4,7 @@ public class Booking : Entity { public DateTime Date { get; set; } public int DurationInMinutes { get; set; } - public Table Table { get; set; } + public int AmountOfPeople { get; set; } public Guid AppUserId { get; set; } public Guid TableId { get; set; } } diff --git a/TableBookingAPI/TableBooking.Model/Models/Restaurant.cs b/TableBookingAPI/TableBooking.Model/Models/Restaurant.cs index b1906e4..aa3ae99 100644 --- a/TableBookingAPI/TableBooking.Model/Models/Restaurant.cs +++ b/TableBookingAPI/TableBooking.Model/Models/Restaurant.cs @@ -1,4 +1,6 @@ -namespace TableBooking.Model.Models +using Microsoft.EntityFrameworkCore; + +namespace TableBooking.Model.Models { public enum Price { @@ -15,7 +17,8 @@ public class Restaurant : Entity public string Phone { get; set; } public string? PrimaryImageURL { get; set; } public string? SecondaryImageURL { get; set; } - public float Rating { get; set; } + [Precision(1,1)] + public double Rating { get; set; } public Price Price { get; set; } public DateTime OpenTime { get; set; } public DateTime CloseTime { get; set; } diff --git a/TableBookingAPI/TableBooking.Model/Models/Table.cs b/TableBookingAPI/TableBooking.Model/Models/Table.cs index 9fa2ec6..51db151 100644 --- a/TableBookingAPI/TableBooking.Model/Models/Table.cs +++ b/TableBookingAPI/TableBooking.Model/Models/Table.cs @@ -4,6 +4,7 @@ public class Table : Entity { public int NumberOfSeats { get; set; } public Guid RestaurantId { get; set; } - public IEnumerable Bookings { get; set; } + public IEnumerable? Bookings { get; set; } + public Restaurant Restaurant { get; set; } } } diff --git a/TableBookingAPI/TableBooking.Model/Seed/seed.sql b/TableBookingAPI/TableBooking.Model/Seed/seed.sql index e254139..dfbc74b 100644 --- a/TableBookingAPI/TableBooking.Model/Seed/seed.sql +++ b/TableBookingAPI/TableBooking.Model/Seed/seed.sql @@ -1,5 +1,4 @@ ALTER ROLE "TableBookingUser" SET search_path = public; - -- Create Restaurants table CREATE TABLE IF NOT EXISTS "Restaurants" ( "Id" UUID PRIMARY KEY, @@ -12,7 +11,7 @@ CREATE TABLE IF NOT EXISTS "Restaurants" ( "PrimaryImageURL" TEXT, "SecondaryImageURL" TEXT, "Price" INT, - "Rating" INT, + "Rating" DOUBLE PRECISION, "Phone" VARCHAR(20) ); @@ -23,6 +22,14 @@ CREATE TABLE IF NOT EXISTS "Tables" ( "RestaurantId" UUID REFERENCES "Restaurants"("Id") ); +-- Create Role table +CREATE TABLE IF NOT EXISTS "Roles" ( + "Id" UUID PRIMARY KEY, + "Name" VARCHAR(30), + "NormalizedName" VARCHAR(255), + "ConcurrencyStamp" VARCHAR(255) +); + -- Create Users table CREATE TABLE IF NOT EXISTS "Users" ( "Id" UUID PRIMARY KEY, @@ -41,15 +48,17 @@ CREATE TABLE IF NOT EXISTS "Users" ( "TwoFactorEnabled" BOOLEAN, "LockoutEnd" TIMESTAMPTZ, "LockoutEnabled" BOOLEAN, - "AccessFailedCount" INT + "AccessFailedCount" INT, + "AppRoleId" UUID REFERENCES "Roles"("Id") ); -- Create Bookings table CREATE TABLE IF NOT EXISTS "Bookings" ( "Id" UUID PRIMARY KEY, "Date" TIMESTAMPTZ, + "AmountOfPeople" INT, "DurationInMinutes" INT, - "UserId" UUID REFERENCES "Users"("Id"), + "AppUserId" UUID REFERENCES "Users"("Id"), "TableId" UUID REFERENCES "Tables"("Id") ); @@ -88,6 +97,17 @@ INSERT INTO "Tables" ("Id","NumberOfSeats", "RestaurantId") VALUES ('423d89b6-1d INSERT INTO "Tables" ("Id","NumberOfSeats", "RestaurantId") VALUES ('840fffa7-ba45-4f44-a139-d794dcc4c647',4, 'a7f7be1c-adae-40df-b315-f772857936d5'); INSERT INTO "Tables" ("Id","NumberOfSeats", "RestaurantId") VALUES ('d3f5aa07-4803-4008-8fed-7fa6a4de5fe6',8, 'a7f7be1c-adae-40df-b315-f772857936d5'); +--Seed Roles records +INSERT INTO "Roles"( + "Id", "Name", "NormalizedName", "ConcurrencyStamp") + VALUES ('65329b28-837c-46cb-8f3c-e2ce20a81cac', 'Admin', 'admin', 'xd1'); +INSERT INTO "Roles"( + "Id", "Name", "NormalizedName", "ConcurrencyStamp") + VALUES ('5ad1268f-f61f-4b1c-b690-cbf8c3d35019', 'User', 'user', 'xd2'); +INSERT INTO "Roles"( + "Id", "Name", "NormalizedName", "ConcurrencyStamp") + VALUES ('6e380994-fb58-4364-aeaa-83f7a06e1cc2', 'Restaurant', 'restaurant', 'xd3'); + -- seed Users records INSERT INTO "Users" ( "Id", @@ -106,7 +126,8 @@ INSERT INTO "Users" ( "TwoFactorEnabled", "LockoutEnd", "LockoutEnabled", - "AccessFailedCount" + "AccessFailedCount", + "AppRoleId" ) VALUES ( 'abc663f0-08b1-4c52-afe4-1d446b11017f', 'refreshtoken1', @@ -124,7 +145,8 @@ INSERT INTO "Users" ( FALSE, '2023-10-12 14:30:00+00'::timestamptz, TRUE, - 0 + 0, + '65329b28-837c-46cb-8f3c-e2ce20a81cac' ); INSERT INTO "Users" ( @@ -144,7 +166,8 @@ INSERT INTO "Users" ( "TwoFactorEnabled", "LockoutEnd", "LockoutEnabled", - "AccessFailedCount" + "AccessFailedCount", + "AppRoleId" ) VALUES ( '123663f0-08b1-4c52-afe4-1d446b11017f', 'refreshtoken2', @@ -162,19 +185,20 @@ INSERT INTO "Users" ( TRUE, '2023-10-12 14:30:00+00'::timestamptz, TRUE, - 2 + 2, + '5ad1268f-f61f-4b1c-b690-cbf8c3d35019' ); -- seed Bookings records -INSERT INTO "Bookings" ("Id", "Date", "DurationInMinutes", "UserId", "TableId") VALUES ('fc9663f0-08b1-4c52-afe4-1d446b11017f','2030-01-01 14:30:00+00', 5, 'abc663f0-08b1-4c52-afe4-1d446b11017f', '123e1a20-6801-4a5e-a327-ecc5cb2bd906'); -INSERT INTO "Bookings" ("Id", "Date", "DurationInMinutes", "UserId", "TableId") VALUES ('c4c4a1c0-3cb4-445e-ba81-311a6b939b4a','2050-05-07 20:15:00+00', 3, '123663f0-08b1-4c52-afe4-1d446b11017f', '3eb2eb68-a47e-47cd-8a22-06c20197a0b3'); +INSERT INTO "Bookings" ("Id", "Date","AmountOfPeople", "DurationInMinutes", "AppUserId", "TableId") VALUES ('fc9663f0-08b1-4c52-afe4-1d446b11017f','2030-01-01 14:30:00+00', 1, 5, 'abc663f0-08b1-4c52-afe4-1d446b11017f', '123e1a20-6801-4a5e-a327-ecc5cb2bd906'); +INSERT INTO "Bookings" ("Id", "Date","AmountOfPeople", "DurationInMinutes", "AppUserId", "TableId") VALUES ('c4c4a1c0-3cb4-445e-ba81-311a6b939b4a','2050-05-07 20:15:00+00', 2, 3, '123663f0-08b1-4c52-afe4-1d446b11017f', '3eb2eb68-a47e-47cd-8a22-06c20197a0b3'); -- Seed Users records -INSERT INTO "Users" ("Id", "RefreshToken", "RefreshTokenExpiryTime", "UserName", "NormalizedUserName", "Email", "NormalizedEmail", "EmailConfirmed", "PasswordHash", "SecurityStamp", "ConcurrencyStamp", "PhoneNumber", "PhoneNumberConfirmed", "TwoFactorEnabled", "LockoutEnd", "LockoutEnabled", "AccessFailedCount") -VALUES ('e8213e4a-c336-4345-b93e-26231379acda', 'refreshtoken1', '2023-11-12 00:00:00+00', 'user uno', 'normalized user uno name', 'email1', 'normalized_email1', FALSE, 'hashpasswd1', 'securitystamp1', 'concurrencystamp1', '123-456-789', FALSE, TRUE, '2023-10-12 14:30:00+00', TRUE, 0); +INSERT INTO "Users" ("Id", "RefreshToken", "RefreshTokenExpiryTime", "UserName", "NormalizedUserName", "Email", "NormalizedEmail", "EmailConfirmed", "PasswordHash", "SecurityStamp", "ConcurrencyStamp", "PhoneNumber", "PhoneNumberConfirmed", "TwoFactorEnabled", "LockoutEnd", "LockoutEnabled", "AccessFailedCount", "AppRoleId") +VALUES ('e8213e4a-c336-4345-b93e-26231379acda', 'refreshtoken1', '2023-11-12 00:00:00+00', 'user uno', 'normalized user uno name', 'email1', 'normalized_email1', FALSE, 'hashpasswd1', 'securitystamp1', 'concurrencystamp1', '123-456-789', FALSE, TRUE, '2023-10-12 14:30:00+00', TRUE, 0, '5ad1268f-f61f-4b1c-b690-cbf8c3d35019'); -INSERT INTO "Users" ("Id", "RefreshToken", "RefreshTokenExpiryTime", "UserName", "NormalizedUserName", "Email", "NormalizedEmail", "EmailConfirmed", "PasswordHash", "SecurityStamp", "ConcurrencyStamp", "PhoneNumber", "PhoneNumberConfirmed", "TwoFactorEnabled", "LockoutEnd", "LockoutEnabled", "AccessFailedCount") -VALUES ('c52fd4e3-1e46-4c38-84cc-d686800b425c', 'refreshtoken2', '2023-11-12 00:00:00+00', 'user nienazarty', 'normalized user nienazarty name', 'email2', 'normalized_email2', TRUE, 'hashpasswd2', 'securitystamp2', 'concurrencystamp2', '123-333-789', TRUE, FALSE, '2023-10-12 14:30:00+00', TRUE, 2); +INSERT INTO "Users" ("Id", "RefreshToken", "RefreshTokenExpiryTime", "UserName", "NormalizedUserName", "Email", "NormalizedEmail", "EmailConfirmed", "PasswordHash", "SecurityStamp", "ConcurrencyStamp", "PhoneNumber", "PhoneNumberConfirmed", "TwoFactorEnabled", "LockoutEnd", "LockoutEnabled", "AccessFailedCount", "AppRoleId") +VALUES ('c52fd4e3-1e46-4c38-84cc-d686800b425c', 'refreshtoken2', '2023-11-12 00:00:00+00', 'user nienazarty', 'normalized user nienazarty name', 'email2', 'normalized_email2', TRUE, 'hashpasswd2', 'securitystamp2', 'concurrencystamp2', '123-333-789', TRUE, FALSE, '2023-10-12 14:30:00+00', TRUE, 2, '6e380994-fb58-4364-aeaa-83f7a06e1cc2'); -- seed Ratings records INSERT INTO "Ratings"( diff --git a/TableBookingAPI/TableBooking.Model/TableBookingContext.cs b/TableBookingAPI/TableBooking.Model/TableBookingContext.cs index 68ce0ab..ec51b0e 100644 --- a/TableBookingAPI/TableBooking.Model/TableBookingContext.cs +++ b/TableBookingAPI/TableBooking.Model/TableBookingContext.cs @@ -29,6 +29,7 @@ protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) public DbSet Tables { get; set; } public DbSet Ratings { get; set; } public DbSet Users { get; set; } + public DbSet Roles { get; set; } } } \ No newline at end of file diff --git a/TableBookingAPI/TableBooking/Controllers/BookingController.cs b/TableBookingAPI/TableBooking/Controllers/BookingController.cs index 908093b..5783f93 100644 --- a/TableBookingAPI/TableBooking/Controllers/BookingController.cs +++ b/TableBookingAPI/TableBooking/Controllers/BookingController.cs @@ -56,18 +56,19 @@ public async Task DeleteUserBooking(Guid id) return await _bookingService.DeleteBookingAsync(id, userId); } - [HttpPost("CreateBooking")] - public async Task CreateUserBooking([FromBody] CreateBookingDto bookingToCreateDto) + [HttpPost("CreateBooking/{restaurantId}")] + public async Task CreateUserBooking([FromBody] CreateBookingDto bookingToCreateDto, Guid restaurantId) { var userId = Guid.Parse(User.FindFirstValue(ClaimTypes.NameIdentifier)); - return await _bookingService.CreateBookingAsync(bookingToCreateDto, userId); + + return await _bookingService.CreateBookingAsync(bookingToCreateDto, userId, restaurantId); } - [HttpPut("UpdateBooking/{id}")] - public async Task UpdateUserBooking([FromBody] UpdateBookingDto updateBookignDto, Guid tableId) + [HttpPut("UpdateBooking/{bookingId}")] + public async Task UpdateUserBooking([FromBody] UpdateBookingDto updateBookingDto, Guid bookingId) { var userId = Guid.Parse(User.FindFirstValue(ClaimTypes.NameIdentifier)); - return await _bookingService.UpdateBookingAsync(updateBookignDto, userId); + return await _bookingService.UpdateBookingAsync(updateBookingDto, userId, bookingId); } } diff --git a/TableBookingAPI/TableBooking/Controllers/RestaurantController.cs b/TableBookingAPI/TableBooking/Controllers/RestaurantController.cs index 314a9d8..cbddf1c 100644 --- a/TableBookingAPI/TableBooking/Controllers/RestaurantController.cs +++ b/TableBookingAPI/TableBooking/Controllers/RestaurantController.cs @@ -1,3 +1,4 @@ +using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using TableBooking.Api.Interfaces; using TableBooking.Model.Dtos.RestaurantDtos; @@ -34,16 +35,16 @@ public async Task CreateRestaurant([FromBody] RestaurantShortInfo return await _restaurantService.CreateRestaurantAsync(restaurantShortInfoDto); } - [HttpDelete("DeleteRestaurant/{id:int}")] + [HttpDelete("DeleteRestaurant/{id:Guid}")] public async Task DeleteRestaurant(Guid id) { return await _restaurantService.DeleteRestaurantAsync(id); } - [HttpPut("UpdateRestaurant")] - public async Task UpdateRestaurant() + [HttpPut("UpdateRestaurant/{restaurantId}")] + public async Task UpdateRestaurant([FromBody] RestaurantShortInfoDto restaurantShortInfoDto, Guid restaurantId) { - return Ok(); + return await _restaurantService.UpdateRestaurantAsync(restaurantShortInfoDto, restaurantId); } diff --git a/TableBookingAPI/TableBooking/Controllers/TableController.cs b/TableBookingAPI/TableBooking/Controllers/TableController.cs index 1158b1f..d1450e6 100644 --- a/TableBookingAPI/TableBooking/Controllers/TableController.cs +++ b/TableBookingAPI/TableBooking/Controllers/TableController.cs @@ -39,13 +39,13 @@ public async Task CreateTable([FromBody] TableDto tableDto) return await _tableService.CreateTableAsync(tableDto); } - [HttpPut("UpdateTable")] - public async Task UpdateTable([FromBody] TableDto tableDto) + [HttpPut("UpdateTable/{tableId}")] + public async Task UpdateTable([FromBody] TableDto tableDto, Guid tableId) { - return await _tableService.UpdateTableAsync(tableDto); + return await _tableService.UpdateTableAsync(tableDto, tableId); } - [HttpDelete("DeleteTable/{id:int}")] + [HttpDelete("DeleteTable/{id:Guid}")] public async Task DeleteTable(Guid id) { return await _tableService.DeleteTableAsync(id); diff --git a/TableBookingAPI/TableBooking/Dockerfile b/TableBookingAPI/TableBooking/Dockerfile index d85ad82..a9987e3 100644 --- a/TableBookingAPI/TableBooking/Dockerfile +++ b/TableBookingAPI/TableBooking/Dockerfile @@ -7,14 +7,14 @@ EXPOSE 443 FROM mcr.microsoft.com/dotnet/sdk:6.0 AS build WORKDIR /src -COPY ["TableBooking/TableBooking.csproj", "TableBooking/"] -RUN dotnet restore "TableBooking/TableBooking.csproj" +COPY ["TableBooking/TableBooking.Api.csproj", "TableBooking/"] +RUN dotnet restore "TableBooking/TableBooking.Api.csproj" COPY . . WORKDIR "/src/TableBooking" -RUN dotnet build "TableBooking.csproj" -c Release -o /app/build +RUN dotnet build "TableBooking.Api.csproj" -c Release -o /app/build FROM build AS publish -RUN dotnet publish "TableBooking.csproj" -c Release -o /app/publish /p:UseAppHost=false +RUN dotnet publish "TableBooking.Api.csproj" -c Release -o /app/publish /p:UseAppHost=false FROM base AS final WORKDIR /app diff --git a/TableBookingAPI/TableBooking/Interfaces/IBookingService.cs b/TableBookingAPI/TableBooking/Interfaces/IBookingService.cs index 5fe65fb..995bd27 100644 --- a/TableBookingAPI/TableBooking/Interfaces/IBookingService.cs +++ b/TableBookingAPI/TableBooking/Interfaces/IBookingService.cs @@ -7,8 +7,8 @@ public interface IBookingService { public Task GetAllBookings(Guid userId); public Task GetBookingByIdAsync(Guid bookingId, Guid userId); - public Task CreateBookingAsync(CreateBookingDto createBookingDto, Guid userId); - public Task UpdateBookingAsync(UpdateBookingDto updateBookingDto, Guid userId); + public Task CreateBookingAsync(CreateBookingDto createBookingDto, Guid userId, Guid restaurantId); + public Task UpdateBookingAsync(UpdateBookingDto updateBookingDto, Guid userId, Guid bookingId); public Task DeleteBookingAsync(Guid bookingId, Guid userId); } } diff --git a/TableBookingAPI/TableBooking/Interfaces/IRestaurantService.cs b/TableBookingAPI/TableBooking/Interfaces/IRestaurantService.cs index 0011980..e94c135 100644 --- a/TableBookingAPI/TableBooking/Interfaces/IRestaurantService.cs +++ b/TableBookingAPI/TableBooking/Interfaces/IRestaurantService.cs @@ -9,7 +9,7 @@ public interface IRestaurantService public Task GetAllRestaurantsAsync(string? restaurantName, Price? price); public Task GetRestaurantByIdAsync(Guid restaurantId); public Task CreateRestaurantAsync(RestaurantShortInfoDto dto); - public Task UpdateRestaurantAsync(RestaurantShortInfoDto dto); + public Task UpdateRestaurantAsync(RestaurantShortInfoDto dto, Guid restaurantId); public Task DeleteRestaurantAsync(Guid restaurantId); } diff --git a/TableBookingAPI/TableBooking/Interfaces/ITableService.cs b/TableBookingAPI/TableBooking/Interfaces/ITableService.cs index e4cbbf1..fa6a8a7 100644 --- a/TableBookingAPI/TableBooking/Interfaces/ITableService.cs +++ b/TableBookingAPI/TableBooking/Interfaces/ITableService.cs @@ -10,7 +10,7 @@ public interface ITableService public Task GetTableByIdAsync(Guid tableId); public Task GetTableByRestaurantAsync(Guid restaurantId); public Task CreateTableAsync(TableDto dto); - public Task UpdateTableAsync(TableDto dto); + public Task UpdateTableAsync(TableDto dto, Guid tableId); public Task DeleteTableAsync(Guid tableId); public Task
GetTableObjectByIdAsync(Guid tableId); } diff --git a/TableBookingAPI/TableBooking/Program.cs b/TableBookingAPI/TableBooking/Program.cs index 3c38987..b686004 100644 --- a/TableBookingAPI/TableBooking/Program.cs +++ b/TableBookingAPI/TableBooking/Program.cs @@ -121,6 +121,7 @@ { options.User.RequireUniqueEmail = true; options.ClaimsIdentity.UserIdClaimType = ClaimTypes.NameIdentifier; + options.ClaimsIdentity.RoleClaimType = ClaimTypes.Role; }); builder.Services.AddScoped(); @@ -142,6 +143,7 @@ app.UseSwagger(); app.UseSwaggerUI(); } +AppContext.SetSwitch("Npgsql.EnableLegacyTimestampBehavior", true); app.UseSerilogRequestLogging(); app.MapHealthChecks("/healthz"); //.RequireHost("*:5001").RequireAuthorization(); diff --git a/TableBookingAPI/TableBooking/Services/BookingService.cs b/TableBookingAPI/TableBooking/Services/BookingService.cs index 83cd89b..69130fb 100644 --- a/TableBookingAPI/TableBooking/Services/BookingService.cs +++ b/TableBookingAPI/TableBooking/Services/BookingService.cs @@ -20,17 +20,17 @@ public BookingService(IUnitOfWork unitOfWork, ITableConverter tableConverter, IT _tableConverter = tableConverter; _tableService = tableService; } - public async Task CreateBookingAsync(CreateBookingDto request, Guid userId) + public async Task CreateBookingAsync(CreateBookingDto request, Guid userId, Guid restaurantId) { - var table = await _tableService.GetTableObjectByIdAsync(request.TableId); - + var restaurantTables = await _unitOfWork.TableRepository.GetTablesByRestaurantIdAsync(restaurantId); + var table = restaurantTables.FirstOrDefault(x => x.Bookings == null); var newBooking = new Booking { Date = request.Date, DurationInMinutes = request.DurationInMinutes, - TableId = request.TableId, + TableId = table.Id, AppUserId = userId, - Table = table + AmountOfPeople = request.AmountOfPeople }; await _unitOfWork.BookingRepository.InsertAsync(newBooking); @@ -41,8 +41,8 @@ public async Task CreateBookingAsync(CreateBookingDto request, Gu Id = newBooking.Id, Date = newBooking.Date, DurationInMinutes = newBooking.DurationInMinutes, - TableDto = _tableConverter.TableToTableDto(newBooking.Table), - UserId = userId + AmountOfPeople = newBooking.AmountOfPeople, + AppUserId = userId }; return new CreatedResult(String.Empty, bookingDto); } @@ -53,7 +53,7 @@ public async Task DeleteBookingAsync(Guid bookingId, Guid userId) if (booking == null) return new BadRequestObjectResult("Bad request"); - await _unitOfWork.BookingRepository.Delete(booking); + await _unitOfWork.BookingRepository.Delete(booking.Id); await _unitOfWork.SaveChangesAsync(); return new NoContentResult(); } @@ -71,8 +71,8 @@ public async Task GetBookingByIdAsync(Guid bookingId, Guid userId Id = booking.Id, Date = booking.Date, DurationInMinutes = booking.DurationInMinutes, - TableDto = _tableConverter.TableToTableDto(booking.Table), - UserId = userId + AmountOfPeople = booking.AmountOfPeople, + AppUserId = userId }; return new OkObjectResult(bookingDto); } @@ -86,9 +86,26 @@ public async Task GetAllBookings(Guid userId) return new OkObjectResult(bookings); } - public async Task UpdateBookingAsync(UpdateBookingDto updateBookingDto, Guid userId) + public async Task UpdateBookingAsync(UpdateBookingDto updateBookingDto, Guid userId, Guid bookingId) { - return new OkObjectResult("XD"); + var booking = await _unitOfWork.BookingRepository.GetBookingByIdForSpecificUserAsync(bookingId, userId); + if (booking == null) + return new BadRequestObjectResult($"Booking with id {bookingId} doesn't exist."); + + var newBooking = new Booking + { + Id = booking.Id, + Date = updateBookingDto.Date, + DurationInMinutes = updateBookingDto.DurationInMinutes, + AmountOfPeople = updateBookingDto.AmountOfPeople, + TableId = booking.TableId, + AppUserId = userId + }; + + await _unitOfWork.BookingRepository.Update(newBooking); + await _unitOfWork.SaveChangesAsync(); + + return new OkObjectResult(newBooking); } } } diff --git a/TableBookingAPI/TableBooking/Services/RatingService.cs b/TableBookingAPI/TableBooking/Services/RatingService.cs index fa72769..153ba9c 100644 --- a/TableBookingAPI/TableBooking/Services/RatingService.cs +++ b/TableBookingAPI/TableBooking/Services/RatingService.cs @@ -34,6 +34,18 @@ public async Task CreateRatingAsync(CreateRatingDto dto) await _unitOfWork.RatingRepository.InsertAsync(rating); await _unitOfWork.SaveChangesAsync(); + var ratings = await _unitOfWork.RatingRepository.GetRatingsAsync(dto.RestaurantId) ; + var numberOfRaitings = ratings.Count(); + var result = 0d; + + if (numberOfRaitings > 0 && numberOfRaitings % 5 == 0) + { + result = ratings.Select(x => x.RatingStars).Average(); + restaurant.Rating = result; + await _unitOfWork.RestaurantRepository.Update(restaurant); + await _unitOfWork.SaveChangesAsync(); + } + return new OkObjectResult(_ratingConverter.RatingToRatingDto(rating)); } diff --git a/TableBookingAPI/TableBooking/Services/RestaurantService.cs b/TableBookingAPI/TableBooking/Services/RestaurantService.cs index fb79757..6d0e57f 100644 --- a/TableBookingAPI/TableBooking/Services/RestaurantService.cs +++ b/TableBookingAPI/TableBooking/Services/RestaurantService.cs @@ -3,6 +3,7 @@ using TableBooking.Logic.Interfaces; using TableBooking.Model.Models; using TableBooking.Api.Interfaces; +using TableBooking.Model.Dtos.BookingDtos; namespace TableBooking.Api.Services { @@ -21,6 +22,7 @@ public async Task CreateRestaurantAsync(RestaurantShortInfoDto dt Name = dto.Name, CloseTime = dto.CloseTime, Description = dto.Description, + Phone = dto.Phone, Location = dto.Location, Rating = 0, Price = dto.Price, @@ -37,7 +39,7 @@ public async Task DeleteRestaurantAsync(Guid restaurantId) var restaurantToDelete = await _unitOfWork.RestaurantRepository.GetByIdAsync(restaurantId); if (restaurantToDelete == null) return new NotFoundObjectResult($"Restaurant with Id = {restaurantId} not found"); - await _unitOfWork.RestaurantRepository.Delete(restaurantToDelete); + await _unitOfWork.RestaurantRepository.Delete(restaurantToDelete.Id); await _unitOfWork.SaveChangesAsync(); return new OkObjectResult(restaurantToDelete); } @@ -57,9 +59,33 @@ public async Task GetRestaurantByIdAsync(Guid restaurantId) return new OkObjectResult(restaurant); } - public Task UpdateRestaurantAsync(RestaurantShortInfoDto dto) + public async Task UpdateRestaurantAsync(RestaurantShortInfoDto dto, Guid restaurantId) { - throw new NotImplementedException(); + var restaurant = await _unitOfWork.RestaurantRepository.GetByIdAsync(restaurantId); + if (restaurant == null) + return new BadRequestObjectResult($"Booking with id {restaurantId} doesn't exist."); + + var newRestaurant = new Restaurant + { + Id = restaurant.Id, + Description = dto.Description, + Location = dto.Location, + Name = dto.Name, + Phone = dto.Phone, + Price = dto.Price, + PrimaryImageURL = dto.ImageURL, + SecondaryImageURL = dto.ImageURL, + Tables = restaurant.Tables, + Type = dto.Type, + Rating = restaurant.Rating, + CloseTime = dto.CloseTime, + OpenTime = dto.OpenTime + }; + + await _unitOfWork.RestaurantRepository.Update(newRestaurant); + await _unitOfWork.SaveChangesAsync(); + + return new OkObjectResult(newRestaurant); } } } diff --git a/TableBookingAPI/TableBooking/Services/TableService.cs b/TableBookingAPI/TableBooking/Services/TableService.cs index fd078d2..6fa34de 100644 --- a/TableBookingAPI/TableBooking/Services/TableService.cs +++ b/TableBookingAPI/TableBooking/Services/TableService.cs @@ -22,7 +22,7 @@ public async Task CreateTableAsync(TableDto dto) var table = new Table { NumberOfSeats = dto.NumberOfSeats, - + RestaurantId = dto.RestaurantId }; await _unitOfWork.TableRepository.InsertAsync(table); await _unitOfWork.SaveChangesAsync(); @@ -34,7 +34,7 @@ public async Task DeleteTableAsync(Guid tableId) var tableToDelete = await _unitOfWork.TableRepository.GetByIdAsync(tableId); if (tableToDelete == null) return new NotFoundObjectResult($"Restaurant with Id = {tableId} not found"); - await _unitOfWork.TableRepository.Delete(tableToDelete); + await _unitOfWork.TableRepository.Delete(tableToDelete.Id); await _unitOfWork.SaveChangesAsync(); return new OkObjectResult(tableToDelete); } @@ -43,7 +43,7 @@ public async Task GetAllTablesAsync() { var tables = await _unitOfWork.TableRepository.GetAllAsync(); if (tables == null) return new BadRequestObjectResult("No tables found"); - return new OkObjectResult(tables); + return new OkObjectResult(_tableConverter.TablesToTableDtos(tables)); } public async Task GetTableByIdAsync(Guid tableId) @@ -51,7 +51,7 @@ public async Task GetTableByIdAsync(Guid tableId) var table = await _unitOfWork.TableRepository.GetByIdAsync(tableId); if (table == null) return new BadRequestObjectResult($"Can't find table with {tableId}"); - return new OkObjectResult(table); + return new OkObjectResult(_tableConverter.TableToTableDto(table)); } public async Task
GetTableObjectByIdAsync(Guid tableId) @@ -69,11 +69,17 @@ public async Task GetTableByRestaurantAsync(Guid restaurantId) return new OkObjectResult(_tableConverter.TablesToTableDtos(tables)); } - public async Task UpdateTableAsync(TableDto dto) + public async Task UpdateTableAsync(TableDto dto, Guid tableId) { + var updateTable = await _unitOfWork.TableRepository.GetByIdAsync(tableId); + if (updateTable == null) + return new BadRequestObjectResult($"Booking with id {tableId} doesn't exist."); + var table = new Table { - NumberOfSeats = dto.NumberOfSeats + Id = updateTable.Id, + NumberOfSeats = dto.NumberOfSeats, + RestaurantId = dto.RestaurantId }; await _unitOfWork.TableRepository.Update(table); await _unitOfWork.SaveChangesAsync(); diff --git a/TableBookingAPI/TableBooking/Services/UserService.cs b/TableBookingAPI/TableBooking/Services/UserService.cs index 1e66196..6f3d220 100644 --- a/TableBookingAPI/TableBooking/Services/UserService.cs +++ b/TableBookingAPI/TableBooking/Services/UserService.cs @@ -17,6 +17,7 @@ public class UserService : IUserService private readonly UserManager _userManager; private readonly RoleManager _roleManager; private readonly IConfiguration _configuration; + private readonly string userRoleId = "5ad1268f-f61f-4b1c-b690-cbf8c3d35019"; public UserService( UserManager userManager, @@ -35,31 +36,40 @@ public async Task Register(UserRegisterDto dto) if (userExists != null) return new BadRequestObjectResult("Bad request: Registration failed"); + var appUserRole = await _roleManager.FindByIdAsync(userRoleId); + if (appUserRole == null) + return new BadRequestObjectResult("Bad request: Registration failed"); + var user = new AppUser() { Email = dto.Email, SecurityStamp = Guid.NewGuid().ToString(), - UserName = dto.Username + UserName = dto.Username, + AppRoleId = appUserRole.Id }; + var result = await _userManager.CreateAsync(user, dto.Password); if (!result.Succeeded) - return new BadRequestObjectResult("Invalid password lenght"); + return new BadRequestObjectResult("Invalid password lenght Or Bad Email"); return new OkObjectResult(new ResultDto { Status = "Success", Message = "User created successfully!" }); } public async Task Login(UserLoginDto dto) { - var user = await _userManager.FindByNameAsync(dto.Username); + var user = await _userManager.FindByNameAsync(dto.Username) ; if (user == null || !await _userManager.CheckPasswordAsync(user, dto.Password)) { return new UnauthorizedResult(); } + var role = await _roleManager.FindByIdAsync(user.AppRoleId.ToString()); + if (role == null) return new BadRequestObjectResult("Bad request"); var authClaims = new List { new Claim(ClaimTypes.Name, user.UserName), new Claim(ClaimTypes.NameIdentifier, user.Id.ToString()), + new Claim(ClaimTypes.Role, role.Name), new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString()) }; diff --git a/TableBookingAPI/docker-compose.yml b/TableBookingAPI/docker-compose.yml index 79467ee..9ebb0f8 100644 --- a/TableBookingAPI/docker-compose.yml +++ b/TableBookingAPI/docker-compose.yml @@ -16,18 +16,16 @@ services: volumes: - ./TableBooking.Model/Seed/seed.sql:/docker-entrypoint-initdb.d/seed.sql - # api: - # build: - # context: . - # dockerfile: ./TableBooking/Dockerfile - # ports: - # - "7012:7012" - # depends_on: - # - db - # # condition: service_healthy - # # command: ["python", "app.py"] - # networks: - # - table_booking_network + api: + build: + context: . + dockerfile: ./TableBooking/Dockerfile + ports: + - "7012:7012" + depends_on: + - db + networks: + - table_booking_network networks: table_booking_network: