# [WP] BistroQ - Milestone 2 Report
> [!Note]
> This report can be viewed in [HackMD - Milestone 2](https://hackmd.io/gwAceDgMRQSu2DYeAvD3Sg) (We strongly recommend to read on the web for better reading experience).
> [!Note] Links
> Milestone 1 report: [HackMD - Milestone 1](https://hackmd.io/4tSDCY54TkaXIRHzsXetDg)
> Link to project: [Google Drive](https://drive.google.com/drive/folders/1L-M17WuqwJLnX6Ev0qFW0QjEw0gDJ0Uv?usp=sharing)
> Link to demo video [Youtube](https://www.youtube.com/watch?v=Jig65qtiblU)
> [!Important]
> The demo video does not have sound. Please turn on subtitle in Vietnamese.
## Table Of Contents
- [Overview](#Overview)
- [Project Setup](#Project-Setup)
- [Backend Setup](#Backend-Setup)
- [Frontend Setup](#Frontend-Setup)
- [Testing Acount](#Testing-Acount)
- [Features](#Features)
- [Completed Works](#Completed-Works)
- [UI/UX](#UIUX)
- [Design Pattern](#Design-Pattern)
- [Publish-Subscribe Pattern](#Publish-Subscribe-Pattern)
- [Strategy Pattern](#Strategy-Pattern)
- [Factory Pattern](#Factory-Pattern)
- [Architecture](#Architecture)
- [WinUI](#WinUI)
- [Backend](#Backend)
- [Advanced Topics](#Advanced-Topics)
- [Semaphore (Reused)](#Semaphore-Reused)
- [Cloud Service](#Cloud-Service)
- [DispatcherTimer](#DispatcherTimer)
- [Storyboard](#Storyboard)
- [AutoMapper](#AutoMapper)
- [IMessenger](#IMessenger)
- [IDisposable](#IDisposable)
- [Resources](#Resources)
- [Storage File and File Picker](#Storage-File-and-File-Picker)
- [Unit test](#Unit-test)
- [Git Flow Evidence](#Git-Flow-Evidence)
- [Code Review Evidence](#Code-Review-Evidence)
- [PR Cloud Product Image API](#PR-Cloud-Product-Image-API)
- [PR Table Fix API](#PR-Table-Fix-API)
- [PR Cashier Table Enhancement Page](#PR-Cashier-Table-Enhancement-Page)
- [PR Refactor WinUI Version 1](#PR-Refactor-WinUI-Version-1)
- [PR Client Order Page](#PR-Client-Order-Page)
- [PR Kitchen Order Page](#PR-Kitchen-Order-Page)
- [PR API Integration Client Order Page](#PR-API-Integration-Client-Order-Page)
- [PR API Integration Kitchen Order Page](#PR-API-Integration-Kitchen-Order-Page)
- [PR Update Client Order API](#PR-Update-Client-Order-API)
- [PR Update Database And Kitchen Order API](#PR-Update-Database-And-Kitchen-Order-API)
- [PR Product and Category Admin Interface](#PR-Product-and-Category-Admin-Interface)
- [PR Client Order Crud Test](#PR-Client-Order-Crud-Test)
## Overview
- In this milestone, we've upgraded our WinUI project to .NET 8.0.
## Project Setup
### Backend Setup
1. Create `.env` based on `.env.sample` file and fill the API Key with the key provided in submission in Moodle.
2. Ensure MySQL and database is working
3. Run database migration:
```bash!
dotnet ef database update --project BistroQ.Infrastructure --startup-project BistroQ.API --context BistroQ.Infrastructure.Data.BistroQContext --verbose # Run migrations
```
4. Run server:
```bash!
dotnet watch run --project .\BistroQ.API
```
### Frontend Setup
1. Have backend server running successfully.
2. Run `BistroQ.Presentation` Project through Visual Studio.
### Testing Acount
| Role | Username | Password |
| ------- | -------- | -------- |
| Admin | admin | admin |
| Kitchen | kitchen | kitchen |
| Cashier | cashier | cashier |
| Client | client1 | client |
| Client | client2 | client |
| Client | client3 | client |
| Client | client4 | client |
| Client | client5 | client |
| Client | client6 | client |
| Client | client7 | client |
| Client | client8 | client |
| Client | client9 | client |
## Features
### Completed Works
In this milestone, we have done these tasks below:
| Task | Working hours (est.) | Responsible by |
| ---- | -------------------- | -------------- |
| API for Order (Client) | 1.25 hour | Tiến |
| API for Order (Cashier) | 0.75 hour | Toàn |
| API for Order (Kitchen) | 1 hour | Toàn |
| Client screen (See product list, filter products by category, cart, order) | 2.75 hours | Tiến |
| Tracking order by status (client) | 0.75 hour | Tiến |
| Cashier screen (See available tables, see orders on table) | 2 hours | Toàn |
| Kitchen screen (See incoming orders, change state of orders, view old orders) | 2.75 hours | Toàn |
| API and Configurate Cloud service (for storing images) | 1.75 hours | Toàn |
| Admin Product Interface | 0.75 hour *(excluded CRUD)* | Toàn |
| Admin Category Interface | 0.5 hour *(excluded CRUD)* | Toàn |
## UI/UX
Our application supports both light and dark themes. You can watch [Demo Video](https://www.youtube.com/watch?v=Jig65qtiblU) or try it yourself for a better experience.
## Design Pattern
:::info
We continued to reuse most of the design patterns from the previous milestone. The following are the newly introduced ones:
:::
### Publish-Subscribe Pattern
Handling and passing `Events` across `Pages` and `Controls` can sometimes be complex. To address this, we decided to adopt a communication system that strongly follows the `Publish-Subscribe Pattern`. This approach is ideal for our needs, especially since we plan to implement `WebSocket` functionality in next milestone. Additionally, it aligns with Microsoft's recommendation in [Enterprise Application Patterns in MAUI](https://learn.microsoft.com/en-us/dotnet/architecture/maui/).
We used `IMessenger` to implement this pattern, you can view detail at [IMessenger](#IMessenger).
### Strategy Pattern
We used this pattern in `KitchenOrderPage` because the chef has multiple actions to perform on an `Order Item` based on its status. By implementing this pattern, we moved the logic for these actions to separate classes for better organization and maintainability.
```csharp!
public record KitchenActionMessage(KitchenAction Action);
public enum KitchenAction { Complete, Move, Cancel }
// Strategy Interface
public interface IOrderItemActionStrategy
{
Task ExecuteAsync(IEnumerable<OrderItemViewModel> orderItems);
}
public class CompleteItemStrategy : IOrderItemActionStrategy
{
public async Task ExecuteAsync(IEnumerable<OrderItemViewModel> orderItems)
{
// Implementation
}
}
public class MoveItemStrategy : IOrderItemActionStrategy
{
public async Task ExecuteAsync(IEnumerable<OrderItemViewModel> orderItems)
{
// Implementation
}
}
public class CancelItemStrategy : IOrderItemActionStrategy
{
public async Task ExecuteAsync(IEnumerable<OrderItemViewModel> orderItems)
{
// Implementation
}
}
// Call strategies
public async void Receive(KitchenActionMessage message)
{
var strategy = _strategyFactory.GetStrategy(message.Action, State);
strategy.ExecuteAsync(State.SelectedItems);
}
```
### Factory Pattern
We used this pattern to create an `OrderItemStrategy` instance
(usage example above), eliminating the need for hard-coding `if-else` or `switch-case` statements inside the `ViewModel`.
```csharp!
public class KitchenStrategyFactory
{
private readonly Dictionary<KitchenAction, Func<IOrderItemDataService, KitchenOrderState, IOrderItemActionStrategy>> _strategies;
private readonly IOrderItemDataService _orderItemDataService;
public KitchenStrategyFactory(IOrderItemDataService service)
{
_orderItemDataService = service;
_strategies = new Dictionary<KitchenAction, Func<IOrderItemDataService, KitchenOrderState, IOrderItemActionStrategy>>
{
[KitchenAction.Complete] = (s, state) => new CompleteItemStrategy(s, state),
[KitchenAction.Move] = (s, state) => new MoveItemStrategy(s, state),
[KitchenAction.Cancel] = (s, state) => new CancelItemStrategy(s, state)
};
}
public IOrderItemActionStrategy GetStrategy(KitchenAction action, KitchenOrderState state) =>
_strategies[action](_orderItemDataService, state);
}
```
## Architecture
### WinUI
- We have decided to reconstruct our WinUI project. The folder structure shown below.
```
.
├── BistroQ.Domain
│ ├── Contracts
│ │ ├── Common
│ │ ├── Http
│ │ └── Services
│ ├── Dtos
│ │ ├── Auth
│ │ ├── Category
│ │ ├── Common
│ │ ├── Orders
│ │ ├── Products
│ │ ├── Tables
│ │ └── Zones
│ ├── Enums
│ ├── Helpers
│ └── Models
│ ├── Entities
│ └── Exceptions
├── BistroQ.Presentation
│ ├── Activation
│ ├── Assets
│ │ └── Icons
│ ├── Behaviors
│ ├── Contracts
│ │ ├── Services
│ │ └── ViewModels
│ ├── Controls
│ │ └── Skeleton
│ ├── Converters
│ ├── Enums
│ ├── Helpers
│ ├── Mappings
│ ├── Messages
│ ├── Models
│ ├── Properties
│ ├── Services
│ ├── Strings
│ │ └── en-us
│ ├── Styles
│ ├── Validation
│ ├── ViewModels
│ │ ├── AdminTable
│ │ ├── AdminZone
│ │ ├── CashierTable
│ │ ├── Client
│ │ ├── Commons
│ │ ├── KitchenHistory
│ │ ├── KitchenOrder
│ │ ├── Models
│ │ └── States
│ └── Views
│ ├── AdminTable
│ ├── AdminZone
│ ├── CashierTable
│ ├── Client
│ ├── KitchenHistory
│ ├── KitchenOrder
│ └── UserControls
├── BistroQ.Service
│ ├── Auth
│ ├── Common
│ ├── Data
│ └── Http
├── BistroQ.Tests.MsTest
```
- Key differences:
- Separate UI Model and Domain Model. Connect through `AutoMapper`.
- Create `Services` project containing business logic.
- Rename Projects: `BistroQ` to `BistroQ.Presentation`, `BistroQ.Core` to `BistroQ.Domain`.
:::success
Now we can see clearly each layer doing its job.
:::
### Backend
:::info
We decided to keep backend architecture since it was well-constructed.
:::
## Advanced Topics
### Semaphore (Reused)
Used in `DialogService` to ensure that only one dialog is displayed at a time. The system will throw an exception if multiple dialogs are shown simultaneously.
```csharp
private static readonly SemaphoreSlim _semaphore = new(1);
await _semaphore.WaitAsync();
try
{
await dialog.ShowAsync();
}
finally
{
_semaphore.Release();
}
```
### Cloud Service
We used `AWS S3` to store Products Images and retrieve them via `CloudFront`, avoiding local storage.
```csharp!
public class AwsStorageService : ICloudStorageService
{
private readonly IAmazonS3 _s3Client;
private readonly AwsSettings _awsSettings;
public async Task UploadFileAsync(Stream fileStream, string key, string contentType)
{
try
{
var putRequest = new PutObjectRequest
{
BucketName = _awsSettings.BucketName,
Key = key,
InputStream = fileStream,
ContentType = contentType,
};
await _s3Client.PutObjectAsync(putRequest);
}
catch (AmazonS3Exception ex)
{
throw new ApplicationException($"Error uploading to S3: {ex.Message}", ex);
}
}
}
```
### DispatcherTimer
A `DispatcherTimer` is a timer class which used to execute code at specified intervals on the UI thread. We used it to implement a time counter for tracking the duration a table has been in use for dining.
```csharp
public partial class TimeCounterViewModel : ObservableObject
{
private DispatcherTimer _timer;
private TimeSpan _elapsedTime;
private DateTime _startTime;
public TimeCounterViewModel()
{
_timer = new DispatcherTimer();
_timer.Interval = TimeSpan.FromSeconds(1);
_timer.Tick += Timer_Tick;
}
// This will be called every second
private void Timer_Tick(object sender, object e)
{
ElapsedTime = DateTime.Now - _startTime;
}
}
```
### Storyboard
This is an animation container that lets you create and control animations in WinUI. We used it to create a repeated fade animation for skeleton (shows when the content is loading).
```xml!
<Rectangle x:Name="SkeletonRectangle"
Width="{x:Bind Width, Mode=OneWay}"
Height="{x:Bind Height, Mode=OneWay}"
Fill="{ThemeResource SystemControlBackgroundBaseLowBrush}"
RadiusX="4" RadiusY="4">
<Rectangle.Resources>
<Storyboard x:Name="PulseAnimation">
<DoubleAnimationUsingKeyFrames
Storyboard.TargetName="SkeletonRectangle"
Storyboard.TargetProperty="Opacity"
RepeatBehavior="Forever">
<LinearDoubleKeyFrame KeyTime="0:0:0" Value="0.8"/>
<LinearDoubleKeyFrame KeyTime="0:0:0.5" Value="0.2"/>
<LinearDoubleKeyFrame KeyTime="0:0:1" Value="0.8"/>
</DoubleAnimationUsingKeyFrames>
</Storyboard>
</Rectangle.Resources>
</Rectangle>
```
### AutoMapper
`AutoMapper` is an object-to-object mapping library in `.NET`. We used it to map between DTOs and objects across layers, eliminating the need for manual coding.
```csharp!
public class MappingProfile : Profile
{
public MappingProfile()
{
CreateMap<Table, TableViewModel>();
CreateMap<Table, TableViewModel>().ReverseMap();
// Other Mapping Definitions
}
}
// Usage
var table = new Table();
var tableVm = _mapper.Map<TableViewModel>(table);
```
### IMessenger
`IMessenger` is a messaging interface used in WinUI to integrate MVVM pattern. We used it in mostly all across controls/pages communication.
```csharp!
// At source
[RelayCommand]
public void CheckoutRequestedCommand()
{
_messenger.Send(new CheckoutRequestedMessage(Order.TableId));
}
// At destination
public partial class CashierTableViewModel :
ObservableObject,
IRecipient<CheckoutRequestedMessage>,
IDisposable
{
public CashierTableViewModel(IMessenger messenger)
{
_messenger = messenger;
_messenger.RegisterAll(this);
}
public void Receive(CheckoutRequestedMessage message)
{
Debug.WriteLine("Checkout requested");
}
public void Dispose()
{
_messenger.UnregisterAll(this);
}
}
```
### IDisposable
An interface in C# that provides a mechanism for managing resources. We used it to unregister messages and dispose of ViewModels when navigating away from a view.
```csharp!
public AdminTablePage()
{
ViewModel = App.GetService<AdminTableViewModel>();
InitializeComponent();
this.Unloaded += (s, e) => ViewModel.Dispose();
}
```
### Resources
This allows the creation of reusable elements. For instance, the following defines a custom color that adapts to both light and dark themes.
```xaml!
<ResourceDictionary.ThemeDictionaries>
<ResourceDictionary x:Key="Light">
<Color x:Key="SuccessBaseColor">#2ECC71</Color>
<Color x:Key="SuccessHoverColor">#27AE60</Color>
</ResourceDictionary>
<ResourceDictionary x:Key="Dark">
<Color x:Key="SuccessBaseColor">#2ECC71</Color>
<Color x:Key="SuccessHoverColor">#27AE60</Color>
</ResourceDictionary>
</ResourceDictionary.ThemeDictionaries>
```
### Storage File and File Picker
The `FilePicker` is a UI element that allows users to select files, while `StorageFile` is a class representing a file in the system, used for reading, writing, and managing files in a Windows app. We utilized these two components to implement file uploading and send files to the API
```csharp!
// ProductEditPage.xaml.cs
private async void ProductEditPage_SelectImageButton_Click(object sender, Microsoft.UI.Xaml.RoutedEventArgs e)
{
FileOpenPicker fileOpenPicker = new()
{
ViewMode = PickerViewMode.Thumbnail,
FileTypeFilter = { ".jpg", ".jpeg", ".png", ".gif" },
};
var windowHandle = WindowNative.GetWindowHandle(App.MainWindow);
InitializeWithWindow.Initialize(fileOpenPicker, windowHandle);
var file = await fileOpenPicker.PickSingleFileAsync(); // Returns StorageFile
if (file != null)
{
ViewModel.Form.ImageFile = await FileWrapper.FromStorageFileAsync(file);
}
}
// FileWrapper.cs
public static async Task<FileWrapper> FromStorageFileAsync(StorageFile file)
{
var stream = await file.OpenStreamForReadAsync();
var memoryStream = new MemoryStream();
await stream.CopyToAsync(memoryStream);
memoryStream.Position = 0;
var contentType = file.ContentType;
var fileName = file.Name;
return new FileWrapper(memoryStream, fileName, contentType); // This will be passed to call api via service
}
```
## Unit test
We have implement some tests in backend. We decided to mock the backend services and test routes in controllers.
To run the test, please use the Test runner/Test explorer in IDE like Visual Studio or Jetbrains Rider.
## Documentation
In our API project, we have prepared the Doxyfile for generating documentations. Please install `doxygen` and its dependencies, then run the command below to generate documents.
```bash
doxygen
```
## Git Flow Evidence
#### WinUI project



#### ASP .NET project


## Code Review Evidence
:::success
These PRs are sorted in ascending chronological order.
:::
:::info
### PR Cloud Product Image API
:::


:::info
### PR Table Fix API
:::


:::info
### PR Cashier Table Enhancement Page
:::




:::info
### PR Refactor WinUI Version 1
:::



:::info
### PR Client Order Page
:::







:::info
### PR Kitchen Order Page
:::




:::info
### PR API Integration Client Order Page
:::



:::info
### PR API Integration Kitchen Order Page
:::




:::info
### PR Update Client Order API
:::




:::info
### PR Update Database And Kitchen Order API
:::


:::info
### PR Product and Category Admin Interface
:::




:::info
### PR Client Order Crud Test
:::


