Skip to content

Commit

Permalink
Admin endpoints for menu items (#251)
Browse files Browse the repository at this point in the history
It is possible to add, update and read menu items as a board member.

Board members can also specify menu items on products when adding and updating products.

Co-authored-by: Jonas Anker Rasmussen <jonasanker@gmail.com>
  • Loading branch information
A-Guldborg and jonasanker authored Jan 21, 2024
1 parent a42d7e1 commit 7b13729
Show file tree
Hide file tree
Showing 16 changed files with 357 additions and 93 deletions.
4 changes: 2 additions & 2 deletions coffeecard/CoffeeCard.Library/Services/v2/AccountService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -234,7 +234,7 @@ public async Task<UserSearchResponse> SearchUsers(String search, int pageNum, in
throw new ArgumentException($"The value of {nameof(pageNum)} is outside of the range of total users");
}

var userByPage = await query
var usersByPage = await query
.OrderBy(u => u.Id)
.Skip(skip).Take(pageLength)
.Select(u => new SimpleUserResponse
Expand All @@ -250,7 +250,7 @@ public async Task<UserSearchResponse> SearchUsers(String search, int pageNum, in
return new UserSearchResponse
{
TotalUsers = totalUsers,
Users = userByPage
Users = usersByPage
};
}

Expand Down
15 changes: 15 additions & 0 deletions coffeecard/CoffeeCard.Library/Services/v2/IMenuItemService.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using CoffeeCard.Models.DataTransferObjects.v2.Product;
using CoffeeCard.Models.DataTransferObjects.v2.Products;

namespace CoffeeCard.Library.Services.v2
{
public interface IMenuItemService : IDisposable
{
Task<IEnumerable<MenuItemResponse>> GetAllMenuItemsAsync();
Task<MenuItemResponse> AddMenuItemAsync(AddMenuItemRequest newMenuItem);
Task<MenuItemResponse> UpdateMenuItemAsync(int menuItemid, UpdateMenuItemRequest changedMenuItem);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ public interface ITicketService : IDisposable
{
Task IssueTickets(Purchase purchase);

Task<List<TicketResponse>> GetTickets(User user, bool includeUsed);
Task<IEnumerable<TicketResponse>> GetTicketsAsync(User user, bool includeUsed);

public Task<UsedTicketResponse> UseTicketAsync(User user, int productId);

Expand Down
101 changes: 101 additions & 0 deletions coffeecard/CoffeeCard.Library/Services/v2/MenuItemService.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
using System.Collections.Generic;
using System.Data;
using System.Linq;
using System.Threading.Tasks;
using CoffeeCard.Common.Errors;
using CoffeeCard.Library.Persistence;
using CoffeeCard.Models.DataTransferObjects.v2.Product;
using CoffeeCard.Models.DataTransferObjects.v2.Products;
using CoffeeCard.Models.Entities;
using Microsoft.EntityFrameworkCore;
using Serilog;

namespace CoffeeCard.Library.Services.v2
{
public sealed class MenuItemService : IMenuItemService
{
private readonly CoffeeCardContext _context;

public MenuItemService(CoffeeCardContext context)
{
_context = context;
}

public async Task<IEnumerable<MenuItemResponse>> GetAllMenuItemsAsync()
{
return await _context.MenuItems
.OrderBy(p => p.Id)
.Select(p => new MenuItemResponse
{
Id = p.Id,
Name = p.Name
})
.ToListAsync();
}

public async Task<MenuItemResponse> AddMenuItemAsync(AddMenuItemRequest newMenuItem)
{
var nameExists = await CheckMenuItemNameExists(newMenuItem.Name);
if (nameExists)
{
throw new ConflictException($"Menu item already exists with name {newMenuItem.Name}");
}

var menuItem = new MenuItem()
{
Name = newMenuItem.Name
};

_context.MenuItems.Add(menuItem);
await _context.SaveChangesAsync();

var result = new MenuItemResponse
{
Id = menuItem.Id,
Name = menuItem.Name
};

return result;
}

private async Task<bool> CheckMenuItemNameExists(string name)
{
return await _context.MenuItems
.AnyAsync(p => p.Name == name);
}

public async Task<MenuItemResponse> UpdateMenuItemAsync(int id, UpdateMenuItemRequest changedMenuItem)
{
var menuItem = await _context.MenuItems.FirstOrDefaultAsync(p => p.Id == id);

if (menuItem == null)
{
Log.Warning("No menu item was found by Menu Item Id: {Id}", id);
throw new EntityNotFoundException($"No menu item was found by Menu Item Id {id}");
}

var nameExists = await CheckMenuItemNameExists(changedMenuItem.Name);
if (nameExists)
{
throw new ConflictException($"Menu item already exists with name {changedMenuItem.Name}");
}

menuItem.Name = changedMenuItem.Name;

await _context.SaveChangesAsync();

var result = new MenuItemResponse
{
Id = id,
Name = menuItem.Name
};

return result;
}

public void Dispose()
{
_context?.Dispose();
}
}
}
47 changes: 36 additions & 11 deletions coffeecard/CoffeeCard.Library/Services/v2/ProductService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
using CoffeeCard.Common.Errors;
using CoffeeCard.Library.Persistence;
using CoffeeCard.Models.DataTransferObjects.v2.Product;
using CoffeeCard.Models.DataTransferObjects.v2.Products;
using CoffeeCard.Models.Entities;
using Microsoft.EntityFrameworkCore;
using Serilog;
Expand Down Expand Up @@ -84,10 +85,15 @@ public async Task<ChangedProductResponse> AddProduct(AddProductRequest newProduc
NumberOfTickets = newProduct.NumberOfTickets,
ExperienceWorth = 0,
Visible = newProduct.Visible,
ProductUserGroup = newProduct.AllowedUserGroups.Select(userGroup => new ProductUserGroup
{
UserGroup = userGroup
}).ToList()
ProductUserGroup = newProduct.AllowedUserGroups
.Select(userGroup => new ProductUserGroup
{
UserGroup = userGroup
}).ToList(),
EligibleMenuItems = _context.MenuItems
.Where(mi => newProduct.MenuItemIds
.Contains(mi.Id))
.ToList()
};

_context.Products.Add(product);
Expand All @@ -100,7 +106,13 @@ public async Task<ChangedProductResponse> AddProduct(AddProductRequest newProduc
Name = product.Name,
NumberOfTickets = product.NumberOfTickets,
Visible = product.Visible,
AllowedUserGroups = newProduct.AllowedUserGroups
AllowedUserGroups = newProduct.AllowedUserGroups,
MenuItems = product.EligibleMenuItems
.Select(mi => new MenuItemResponse
{
Id = mi.Id,
Name = mi.Name
})
};

return result;
Expand All @@ -109,16 +121,23 @@ public async Task<ChangedProductResponse> AddProduct(AddProductRequest newProduc
public async Task<ChangedProductResponse> UpdateProduct(UpdateProductRequest changedProduct)
{
var product = await GetProductAsync(changedProduct.Id);

product.Price = changedProduct.Price;
product.Description = changedProduct.Description;
product.NumberOfTickets = changedProduct.NumberOfTickets;
product.Name = changedProduct.Name;
product.Visible = changedProduct.Visible;
product.ProductUserGroup = changedProduct.AllowedUserGroups.Select(userGroup => new ProductUserGroup
{
ProductId = changedProduct.Id,
UserGroup = userGroup
}).ToList();
product.ProductUserGroup = changedProduct.AllowedUserGroups
.Select(userGroup => new ProductUserGroup
{
ProductId = changedProduct.Id,
UserGroup = userGroup
})
.ToList();
product.EligibleMenuItems = _context.MenuItems
.Where(mi => changedProduct.MenuItemIds
.Contains(mi.Id))
.ToList();

await _context.SaveChangesAsync();

Expand All @@ -129,7 +148,13 @@ public async Task<ChangedProductResponse> UpdateProduct(UpdateProductRequest cha
Name = product.Name,
NumberOfTickets = product.NumberOfTickets,
Visible = product.Visible,
AllowedUserGroups = changedProduct.AllowedUserGroups
AllowedUserGroups = changedProduct.AllowedUserGroups,
MenuItems = product.EligibleMenuItems
.Select(item => new MenuItemResponse
{
Id = item.Id,
Name = item.Name
})
};

return result;
Expand Down
7 changes: 4 additions & 3 deletions coffeecard/CoffeeCard.Library/Services/v2/TicketService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -43,9 +43,9 @@ public async Task IssueTickets(Purchase purchase)
Log.Information("Issued {NoTickets} Tickets for ProductId {ProductId}, PurchaseId {PurchaseId}", purchase.NumberOfTickets, purchase.ProductId, purchase.Id);
}

public Task<List<TicketResponse>> GetTickets(User user, bool includeUsed)
public async Task<IEnumerable<TicketResponse>> GetTicketsAsync(User user, bool includeUsed)
{
return _context.Tickets
return await _context.Tickets
.Where(t => t.Owner.Equals(user) && t.IsUsed == includeUsed)
.Include(t => t.Purchase)
.Include(t => t.UsedOnMenuItem)
Expand All @@ -57,7 +57,8 @@ public Task<List<TicketResponse>> GetTickets(User user, bool includeUsed)
ProductId = t.ProductId,
ProductName = t.Purchase.ProductName,
UsedOnMenuItemName = t.UsedOnMenuItem != null ? t.UsedOnMenuItem.Name : null
}).ToListAsync();
})
.ToListAsync();
}

public async Task<UsedTicketResponse> UseTicketAsync(User user, int productId)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
using System.Collections.Generic;
using System.ComponentModel;
using System.ComponentModel.DataAnnotations;
using CoffeeCard.Models.Entities;

namespace CoffeeCard.Models.DataTransferObjects.v2.Product
{
/// <summary>
/// Initiate a new menuitem add request.
/// </summary>
public class AddMenuItemRequest
{
/// <summary>
/// Gets or sets the name of the product.
/// </summary>
/// <value>Product Name</value>
/// <example>Latte</example>
[Required]
[MinLength(1, ErrorMessage = "Name cannot be an empty string")]
public string Name { get; set; }
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,69 +8,67 @@ namespace CoffeeCard.Models.DataTransferObjects.v2.Product
/// <summary>
/// Initiate a new product add request.
/// </summary>
/// <example>
/// {
/// "Name": "Latte",
/// "Price": 25,
/// "NumberOfTickets": 10,
/// "Description": "xxx",
/// "Visible": true,
/// "AllowedUserGroups": ["Manager", "Board"]
/// }
/// </example>
public class AddProductRequest
{
/// <summary>
/// Gets or sets the price of the product.
/// </summary>
/// <value>Product Price</value>
/// <example> 10 </example>
/// <example>10</example>
[Required]
[Range(0, int.MaxValue, ErrorMessage = "Price must be a non-negative integer.")]
[Range(0, int.MaxValue, ErrorMessage = "Price must be a non-negative integer")]
public int Price { get; set; }

/// <summary>
/// Gets or sets the number of tickets associated with the product.
/// </summary>
/// <value> Number of tickets associated with a product </value>
/// <example> 5 </example>
/// <value>Number of tickets associated with a product</value>
/// <example>5</example>
[Required]
[Range(0, int.MaxValue, ErrorMessage = "Number of Tickets must be a non-negative integer.")]
[Range(0, int.MaxValue, ErrorMessage = "Number of Tickets must be a non-negative integer")]
public int NumberOfTickets { get; set; }

/// <summary>
/// Gets or sets the name of the product.
/// </summary>
/// <value> Product Name </value>
/// <example> Latte </example>
/// <value>Product Name</value>
/// <example>Latte</example>
[Required]
[MinLength(1, ErrorMessage = "Name cannot be an empty string.")]
[MinLength(1, ErrorMessage = "Name cannot be an empty string")]
public string Name { get; set; }

/// <summary>
/// Gets or sets the description of the product.
/// </summary>
/// <value> Product Description </value>
/// <example> A homemade latte with soy milk </example>
/// <value>Product Description</value>
/// <example>A homemade latte with soy milk</example>
[Required]
[MinLength(1, ErrorMessage = "Description cannot be an empty string.")]
[MinLength(1, ErrorMessage = "Description cannot be an empty string")]
public string Description { get; set; }

/// <summary>
/// Gets or sets the visibility of the product. Default is true.
/// </summary>
/// <value> Product Visibility </value>
/// <example> true </example>
/// <value>Product Visibility</value>
/// <example>true</example>
[Required]
[DefaultValue(true)]
public bool Visible { get; set; } = true;

/// <summary>
/// Gets or sets the user groups that can access the product.
/// </summary>
/// <value> Product User Groups </value>
/// <example> Manager, Board </example>
/// <value>Product User Groups</value>
/// <example>["Manager", "Board"]</example>
[Required]
public IEnumerable<UserGroup> AllowedUserGroups { get; set; } = new List<UserGroup>();
public IEnumerable<UserGroup> AllowedUserGroups { get; set; }

/// <summary>
/// Gets or sets the menu items that are eligible for the product.
/// </summary>
/// <value> Product Menu Item Ids </value>
/// <example>[1, 2]</example>
[Required]
public IEnumerable<int> MenuItemIds { get; set; }
}
}
Loading

0 comments on commit 7b13729

Please sign in to comment.