Skip to content

Ksztof/store-api

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

347 Commits
 
 
 
 
 
 
 
 

Repository files navigation

Overview

Store-API is a REST API for an online store that serves as the backend for the frontend application, store-front. The API handles operations related to data management, business logic, authentication, authorization, and communication with the database. It acts as the central component of the system, forming a comprehensive e-commerce solution together with the store-front application. It allows guests to make purchases using a temporary ID that matches their temporary cart ID, and logged-in users to make purchases using JWT tokens. By clearly separating the frontend and backend, the API enhances scalability, security, and maintainability, enabling independent development and updates of both parts of the system.

Tech Stack

Used as the main framework for building the API (handling HTTP requests, managing routing, implementing middleware for request processing, and facilitating authentication and authorization processes).

Used as the Object-Relational Mapper (ORM) to facilitate data access and management within the application. This technology simplifies interactions with the SQL Server database and handles database migrations, data modeling, and CRUD operations.

Used as the primary relational database management system for storing and managing the application's data.

Used for securely managing secrets, keys, and certificates, and enabling secure access through Azure Active Directory, without hardcoding credentials in the application.

Used for automating the mapping between different object models, such as mapping data transfer objects (DTOs)

Used for validating data inputs through a fluent API, ensuring that incoming data meets defined rules.

Used for generating interactive API documentation that imitates the frontend of the site.

Used for enabling real-time communication between the server and clients to get response from API about stripe payment status.

Used for sending emails within the application (register link, order summary etc.)

Used for processing payments and handling transactions.

Used for implementing JSON Web Token (JWT) authentication, which is used in the process of user authorization.

Architecture

Clean Architecture

The project utilizes the Clean Architecture design pattern, which divides the project into Presentation, Application, Domain, and Infrastructure layers, thereby separating business logic from infrastructure. This architecture adheres to SOLID principles, improving the structure of the code and reducing its complexity. By implementing this pattern, the code has become scalable and easily extendable with new functionalities.

image

Domain-driven design (DDD)

Implementing DDD in the project allowed us to focus on business logic by encapsulating it within entity classes, where each entity has its own methods that perform data operations according to business rules. This separation ensures that the business logic is distinct from the rest of the system, making it easier to manage and maintain.

Layers

By implementing a layered architecture in accordance with Clean Architecture, including the use of interfaces, we have the ability to swap out individual layers of the application. The layers are not dependent on specific implementations but rely on abstractions provided by interfaces. This allows us, for example, to replace the presentation layer with Blazor components or another frontend technology, or to change the infrastructure layer to switch to MongoDB, or simply to replace specific repository implementations.

image

  • Presentation Layer (Store.Api) - This layer is responsible for handling requests from front-end application. We can find here controllers, extension functions used for clear presentation of API responses, application startup configuration (Program.cs) and validation for inputs and form data submitted in requests.

    image

  • Application Layer (Store.Application) - The application layer manages application logic and implements use cases that define how users can interact with the system. It acts as a mediator between the presentation and domain layers. We can find here specific implementations of application services and contracts, such as the KeyVaultOptions class, which defines the data that should be provided from Azure Key Vault without specifying exactly how these data should be retrieved. This layer also contains interfaces for infrastructure services, which describe how the application layer wants to communicate with the infrastructure layer. In other words, they define how it intends to use services that interact with external providers, which, according to Clean Architecture principles, belong to the infrastructure layer. In this layer, business validation also takes place, for example, to handle conflicts or missing objects, by calling functions defined in repository interfaces that are located in Domain layer, with their specific implementations in the infrastructure layer.

    image

  • Domain Layer (Store.Domain) - The domain layer is the core of the application, containing business logic and domain rules. We can find here entities along with functions that are closely associated with them and implement business logic. These entities are mapped to database tables. The layer also includes repository interfaces and objects used for returning operation results using the result pattern, such as the Result class, which wraps an Error object providing additional information about the operation's outcome. The Error class represents an error by providing details about its code, error specifics, and type (Failure, Validation, NotFound, Conflict, etc.). Additionally, there are Errors classes that provide static methods mapped to the Error object, which are used to create specific errors with the previously mentioned fields.

    image

  • Infrastructure Layer (Store.Infrastructure) - The Infrastructure Layer is responsible for implementing technical details and accessing external resources and systems that support the application's operation but are not part of its business logic. In this layer, we can find configurations including scripts for initializing the database with seed data, classes for configuring JWT settings and Azure Key Vault, which allow for secure management of keys, credentials, and application secrets. It also contains middlewares that handle global error management and token refreshing, migrations, repositories, the database context, and services that implement application logic not related to business logic.

    image

Design patterns

  • Dependency Injection (DI) - Used to manage dependencies and promote loose coupling between components by injecting required services at runtime.
  • Repository Pattern - Used to abstract data access logic and provide a cleaner way to interact with data sources through defined interfaces.
  • Result Pattern - Used to standardize the handling of operation outcomes, encapsulating both success and failure states with relevant information.
  • Enum-based Strategy Pattern - Used in the ResultExtensions class to determine HTTP status codes, titles, and types based on the ErrorType enumeration, allowing for streamlined and consistent error response handling across the application.
  • Middleware Pattern - Applied in the request pipeline of ASP.NET Core to handle cross-cutting concerns such as authentication, logging, and error handling
  • Options Pattern - Used to manage configuration settings in a structured and type-safe manner via strongly typed classes.
  • Factory Pattern - Static methods in UserResult, like Success() and Failure(Error error), act as factories to create instances of UserResult in a controlled way.
  • Singleton Pattern - Ensures that a particular service or class instance is created only once and reused across the application.
  • Strategy Pattern - Allows dynamic selection or change of execution strategy without altering the class implementation, used in JWT validation by configuring TokenValidationParameters and ValidationService to dynamically inject and execute the appropriate validator instance based on the type being validated..
  • Chain of Responsibility Pattern - Used in middleware configurations where each middleware processes the request and decides whether to pass it along the chain.
  • Unit of Work Pattern - Automatically implemented by EF Core to ensure atomicity and consistency in database operations.
  • Aspect-Oriented Programming (AOP) - implemented through middleware to handle cross-cutting concerns like token validation in a centralized manner, improving modularity and maintainability.

DTO

Each layer of the application has its own DTOs, which promotes complete separation between them. This ensures that data returned from, for example, the domain layer, will not include sensitive information that could be exposed to the user. DTOs are organized in DTO folders and are divided into two types: request and response. Additionally, each DTO has a suffix clearly indicating its purpose and the layer it is intended for, such as DtoApp for the application layer, DtoDom for the domain layer, and so on. Objects used for transferring data (Models) that are simply part of other DTOs but are not DTOs themselves — they do not have the DTO suffix.

Communication between layers

Communication follows the principles of Clean Architecture, with dependencies inverted and directed towards the inner domain, using interfaces to ensure that each layer adheres to the proper separation of concerns.

Authentication, Authorization, and Configuration:

In the Api configuration values such as connection strings, secrets, and keys are securely stored in Azure Key Vault, and accessed using the Options Pattern to ensure type-safe and manageable configuration handling. Authentication is implemented using JWT Bearer tokens, providing secure access control based on roles defined within the application, such as Visitor and Administrator. Authorization is enforced at the controller and method level, using attributes like [Authorize(Roles = UserRoles.Administrator)] for administrators, [Authorize(Roles = UserRoles.Visitor)] for standard users, and [AllowAnonymous] for actions that are accessible to unauthenticated guests.

Error Handling

Errors that occur throughout the application's operation are returned to the controllers using the result pattern, while exceptions are thrown only in unforeseen situations due to the system overhead associated with exception handling. The error handling approach in the API can be summarized as: exceptions are thrown where errors are not expected, and where errors are anticipated, the result pattern is used. The application utilizes various types of result classes, which slightly differ from each other but always include the fields IsSuccess, IsFailure, and Error to describe the outcome of an operation. The result classes have static methods that, when called, create a result object with appropriate values assigned to its fields, such as public static EntityResult<TEntity> Success(TEntity entity) => new(true, Error.None, entity); or public static EntityResult<TEntity> Failure(Error error) => new(false, error, default);.

The Error class, which is a field of the Result class, includes properties like Code — indicating the error code that makes it easy to understand why the error was returned, Description — providing a full error description, and Type — representing the type of error, such as Failure, Validation, NotFound, Conflict, etc. The Error class also has its own static methods that set the appropriate error type assigned to the Type field, for example, public static Error NotFound(string code, string description) => new(code, description, ErrorType.NotFound); and public static Error Validation(string code, string description) => new(code, description, ErrorType.Validation);. To standardize errors and facilitate their quick creation, static methods stored in static Errors classes are used, which return instances of the Error class e.g. public static Error NotFoundByProductId(int productId) => Error.NotFound( $"{typeof(T).Name}.NotFoundByProductId", $"Entity with product ID: {productId} is missing");, public static readonly Error NotRequestedForAccountDeletion = Error.Validation("User.NotRequestedForAccountDeletion", "the user has not requested to delete the account"); .

Security

application adheres to strict security standards, ensuring that all cookies and tokens are issued in a secure manner. Cookies are set with HttpOnly and Secure flags, and tokens are managed via secure practices, including the use of CORS policies and HTTPS. The application leverages Azure Key Vault to securely store sensitive configuration data, and JWT Bearer tokens are used for authentication and authorization.

Refresh Token Middleware

In the infrastructure layer, there is a middleware called JwtRefreshMiddleware, which handles token refresh operations. With each request to the Api, it retrieves cookies containing the refresh token and authentication token from the request context. It then validates the authentication token's validity, if the authentication token has expired but the refresh token is still valid, a new authentication token is issued and returned to the user. If a new token is issued, the request is processed based on the new token without requiring the client to make another request to the API after the new authentication token is returned to the browser.

Global Error Handling Middleware

The API also implements ExceptionHandlingMiddleware, which captures all unhandled exceptions thrown throughout the application's execution. This middleware automatically wraps the thrown exception in a ProblemDetails object (Microsoft.AspNetCore.Http.Extensions), which is then returned to the user’s browser in the standard error format used by the API.

image

Extensions

The Api provides extension classes ValidationResultExtensions and ResultExtensions, which are used to wrap form validation errors and business logic errors in a ProblemDetails object, offering a more detailed description of the issue. Additionally, two other extension classes, ServiceCollectionExtensions and ApplicationBuilderExtensions, help organize the startup configuration. These classes are responsible for registering services, configurations, and dependencies such as JWT authentication, database, CORS, and Azure Key Vault, as well as configuring the middleware pipeline, including custom middleware, authentication, and API endpoints setup.

ResultExtensions 1 ResultExtensions 2 ResultExtensions 3


ValidationResultExtensions and ResultExtensions



Startup 1 Startup 2 Startup 3

ServiceCollectionExtensions and ApplicationBuilderExtensions

Form validation and business logic validation

Following the Separation of Concerns principle, form validation takes place in the presentation layer at the controller level using FluentValidation, while business logic validation is handled in the application layer, ensuring a clear separation between input data validation and business rules. Additionally, the validation approach uses the Strategy Pattern with Dependency Injection, allowing for dynamic selection of the appropriate validator, making the method generic.

Form validation 1 Form validation 2 Form validation 3



Business Business validation 2

Mapper

AutoMapper library was used for mapping, and the mapping configurations were defined in classes with the MappingProfile prefix.

Mapper Mapper Mapper

Tests

Test preparation is the next stage of development (in preparation...)

Endpoints

Carts Controller

AddProductsToCartAsync

  • HTTP Path: POST /api/carts/products
  • Request Type: POST
  • Authorization: Administrator
  • Description: This endpoint is used to add products to the cart
  • Input Type: FromBody
  • Input: NewProductsDtoApi
{
  "products": [
    {
      "productId": "int",
      "quantity": "decimal"
    }
  ]
}
  • Output: Code 200 Ok(CartResponseDto)
{
  "CartId": "int?",
  "CartLineResponse": [
    {
      "productId": "int?",
      "ProductName": "string",
      "Quantity": "decimal",
      "UnitPrice": "decimal",
      "TotalPrice": "decimal"
    }
  ]
}



DeleteProductFromCartAsync

  • HTTP Path: DELETE /api/carts/products/{productId}
  • Request Type: DELETE
  • Authorization: Administrator
  • Description: This endpoint is used to delete single product from the cart
  • Input Type: Parameter
  • Argumets: "productId": "int"
  • Output: Code 200 Ok(CartResponseDto)
{
  "CartId": "int?",
  "CartLineResponse": [
    {
      "productId": "int?",
      "ProductName": "string",
      "Quantity": "decimal",
      "UnitPrice": "decimal",
      "TotalPrice": "decimal"
    }
  ]
}



ModifyProductAsync

  • HTTP Path: PATCH /api/carts/products
  • Request Type: PATCH
  • Authorization: Administrator
  • Description: This endpoint is used to modify the quantity of a product in the cart
  • Input Type: FromBody
  • Input model: ModifyProductDtoApi
{
  "Product": {
    "ProductId": "int",
    "Quantity": "decimal"
  }
}
  • Output: Code 200 Ok(CartResponseDto)
{
  "CartId": "int?",
  "CartLineResponse": [
    {
      "productId": "int?",
      "ProductName": "string",
      "Quantity": "decimal",
      "UnitPrice": "decimal",
      "TotalPrice": "decimal"
    }
  ]
}



CheckCartAsync

  • HTTP Path: GET /api/carts
  • Request Type: GET
  • Authorization: AllowAnonymous
  • Description: This endpoint is used to retrieve information about the cart's contents, including its total value and creation date
  • Input Type: -
  • Output: Code 204 NoContent/Code 200 OK(AboutCartDomResDto)
{
  "TotalCartValue": "decimal",
  "AboutProductsInCart": [
    {
      "ProductId": "int",
      "ProductName": "string",
      "ProductUnitPrice": "decimal",
      "ProductTotalPrice": "decimal",
      "Description": "string",
      "Manufacturer": "string",
      "Quantity": "decimal"
    }
  ],
  "CreatedAt": "DateTime"
}



ClearCartAsync

  • HTTP Path: DELETE /api/carts
  • Request Type: DELETE
  • Authorization: AllowAnonymous
  • Input Type: -
  • Description: This endpoint is used to delete cart content
  • Output: Code 204 NoContent



GetCartByIdAsync

  • HTTP Path: GET /api/carts/{cartId}
  • Request Type: GET
  • Authorization: Administrator
  • Description: This endpoint is used to get cart content by Id
  • Input Type: Parameter
  • Argumets: "cartId": "int"
  • Output: Code 200 Ok(CartResponseDto)
{
  "CartId": "int?",
  "CartLineResponse": [
    {
      "productId": "int?",
      "ProductName": "string",
      "Quantity": "decimal",
      "UnitPrice": "decimal",
      "TotalPrice": "decimal"
    }
  ]
}



ReplaceCartContentAsync

  • HTTP Path: PUT /api/carts
  • Request Type: PUT
  • Authorization: AllowAnonymous
  • Description: This endpoint is used to replace cart content
  • Input Type: FromBody
  • Input model: NewProductsDtoApi
{
  "Products": [
    {
      "ProductId": "int",
      "Quantity": "decimal"
    }
  ]
}
  • Output: Code 204 NoContent/Code 200 Ok(AboutCartDomResDto)
{
  "TotalCartValue": "decimal",
  "AboutProductsInCart": [
    {
      "ProductId": "int",
      "ProductName": "string",
      "ProductUnitPrice": "decimal",
      "ProductTotalPrice": "decimal",
      "Description": "string",
      "Manufacturer": "string",
      "Quantity": "decimal"
    }
  ],
  "CreatedAt": "DateTime"
}



CheckCurrentCartAsync

  • HTTP Path: POST /api/carts/check-current-cart
  • Request Type: POST
  • Authorization: AllowAnonymous
  • Input Type: FromBody
  • Description: This endpoint is used to check by date whether the current cart is up to date. If it is, the function returns nothing, if not, it returns the most up-to-date cart
  • Input model: CheckCurrentCartDtoApi
{
  "CreatedAt": "DateTime"
}
  • Output: Code 204 NoContent/Code 200 Ok(AboutCartDomResDto)
{
  "TotalCartValue": "decimal",
  "AboutProductsInCart": [
    {
      "ProductId": "int",
      "ProductName": "string",
      "ProductUnitPrice": "decimal",
      "ProductTotalPrice": "decimal",
      "Description": "string",
      "Manufacturer": "string",
      "Quantity": "decimal"
    }
  ],
  "CreatedAt": "DateTime"
}



Orders Controller

SubmitOrderAsync

  • HTTP Path: POST /api/orders/{method?}
  • Request Type: POST
  • Authorization: AllowAnonymous
  • Description: This endpoint is used to place an order, paying by card or cash on delivery
  • Input Type: FromBody + Parameter
  • Argumets: "method": "string?"
  • Input model: CrateOrderDtoApi
{
  "FirstName": "string",
  "LastName": "string",
  "Email": "string",
  "Street": "string",
  "StreetNumber": "string",
  "HomeNumber": "string",
  "PostCode": "string",
  "City": "string",
  "PhoneNumber": "string"
}
  • Output: Code 200 Ok(OrderResponseDto)
{
  "Id": "int",
  "TotalCartValue": "decimal",
  "AboutProductsInCart": [
    {
      "ProductId": "int",
      "ProductName": "string",
      "ProductUnitPrice": "decimal",
      "ProductTotalPrice": "decimal",
      "Description": "string",
      "Manufacturer": "string",
      "Quantity": "decimal"
    }
  ],
  "ShippingDetil": {
    "FirstName": "string",
    "LastName": "string",
    "Email": "string",
    "Street": "string",
    "StreetNumber": "string",
    "HomeNumber": "string",
    "PostCode": "string",
    "City": "string",
    "PhoneNumber": "string"
  }
}



GetOrderByIdAsync

  • HTTP Path: GET /api/orders/{orderId}
  • Request Type: GET
  • Authorization: Administrator
  • Description: This endpoint is used to get order by Id
  • Input Type: Parameter
  • Argumets: "orderId": "int"
  • Output: Code 200 Ok(OrderResponseDto)
{
  "Id": "int",
  "TotalCartValue": "decimal",
  "AboutProductsInCart": [
    {
      "ProductId": "int",
      "ProductName": "string",
      "ProductUnitPrice": "decimal",
      "ProductTotalPrice": "decimal",
      "Description": "string",
      "Manufacturer": "string",
      "Quantity": "decimal"
    }
  ],
  "ShippingDetil": {
    "FirstName": "string",
    "LastName": "string",
    "Email": "string",
    "Street": "string",
    "StreetNumber": "string",
    "HomeNumber": "string",
    "PostCode": "string",
    "City": "string",
    "PhoneNumber": "string"
  }
}



DeleteOrderAsync

  • HTTP Path: DELETE /api/orders/{orderId}
  • Request Type: DELETE
  • Authorization: Administrator
  • Description: This endpoint is used to submit order deletion by administrator
  • Input Type: Parameter
  • Argumets: "orderId": "int"
  • Output: Code 204 NoContent

MarkOrderAsDeletedAsync

  • HTTP Path: PATCH /api/orders/{orderId}//mark-as-deleted
  • Request Type: PATCH
  • Authorization: AllowAnonymous
  • Description: This endpoint is used to mark order as - to deletion by administrator, for user order becomes deleted
  • Input Type: Parameter
  • Argumets: "orderId": "int"
  • Output: Code 204 NoContent

GetOrdersAsync

  • HTTP Path: GET /api/orders
  • Request Type: GET
  • Authorization: Administrator
  • Description: This endpoint is used to get all orders
  • Argumets: -
  • Output: Code 200 OK(IEnumerable<OrdersResDto>)
[
  {
    "Status": "string",
    "CartLineResponse": [
      {
        "productId": "int?",
        "ProductName": "string",
        "Quantity": "decimal",
        "UnitPrice": "decimal",
        "TotalPrice": "decimal"
      }
    ],
    "ShippingInfo": {
      "FirstName": "string",
      "LastName": "string",
      "Email": "string",
      "Street": "string",
      "StreetNumber": "string",
      "HomeNumber": "string",
      "PostCode": "string",
      "City": "string",
      "PhoneNumber": "string"
    }
  }
]



Payments Controller

GetClientSecretAsync

  • HTTP Path: POST /api/payments
  • Request Type: POST
  • Authorization: AllowAnonymous
  • Description: This endpoint is used to get stripe secret for user
  • Input Type: FromBody
  • Input model: GetClientSecretDtoApi
{
  "Amount": "int",
  "Currency": "string"
}
  • Output: Code 200 Ok(string)

UpdatePaymentIntentAsync

  • HTTP Path: POST /api/payments/update-payment-intent
  • Request Type: POST
  • Authorization: AllowAnonymous
  • Description: This endpoint is used to update payment intent metadata with order id
  • Input Type: FromBody
  • Input model: UpdatePaymentIntentDtoApi
{
  "clientSecret": "string"
}
  • Output: Code 204 NoContent

ConfirmPaymentAsync

  • HTTP Path: POST /api/payments/confirm-payment
  • Request Type: POST
  • Authorization: AllowAnonymous
  • Description: This endpoint is used to confirm stripe payment on the backend side
  • Input Type: FromBody
  • Input model: ConfirmPaymentDtoApi
{
  "PaymentIntentId": "string",
  "PaymentMethodId": "string"
}
  • Output: Code 204 NoContent

ConfirmPaymentAsync

  • HTTP Path: POST /api/payments/webhook
  • Request Type: POST
  • Authorization: AllowAnonymous
  • Description: This is a webhook triggered by the Stripe API to retrieve the payment status, which is returned to the frontend using SignalR

Products Controller

CreateProductAsync

  • HTTP Path: POST /api/products
  • Request Type: POST
  • Authorization: Administrator
  • Description: This endpoint is used to create product
  • Input Type: FromBody
  • Input model: CreateProductDtoApi
{
  "ProductName": "string",
  "ProductPrice": "decimal",
  "ProductDescription": "string",
  "ProductCategoriesIds": [
    "int"
  ],
  "ProductManufacturer": "string?"
}
  • Output: Code 200 Ok(ProductResponseDto)
{
  "Id": "int",
  "Name": "string",
  "Price": "decimal",
  "Description": "string",
  "Manufacturer": "string?",
  "DateAdded": "DateTime"
}



DeleteProductAsync

  • HTTP Path: DELETE /api/products/{productId}
  • Request Type: DELETE
  • Authorization: Administrator
  • Description: This endpoint is used to delete product
  • Input Type: Parameter
  • Argumets: "productId": "int"
  • Output: Code 204 NoContent

GetProductByIdAsync

  • HTTP Path: GET /api/products/{productId}
  • Request Type: GET
  • Authorization: AllowAnonymous
  • Description: This endpoint is used to get product by Id
  • Input Type: Parameter
  • Argumets: "productId": "int"
  • Output: Code 200 Ok(ProductResponseDto)
{
  "Id": "int",
  "Name": "string",
  "Price": "decimal",
  "Description": "string",
  "Manufacturer": "string?",
  "DateAdded": "DateTime"
}



GetAllProductsAsync

  • HTTP Path: GET /api/products
  • Request Type: GET
  • Authorization: AllowAnonymous
  • Description: This endpoint is used to get all products
  • Input Type: -
  • Output: Code 200 Ok(ProductResponseDto)
{
  "Id": "int",
  "Name": "string",
  "Price": "decimal",
  "Description": "string",
  "Manufacturer": "string?",
  "DateAdded": "DateTime"
}



UpdateProductAsync

  • HTTP Path: PUT /api/products
  • Request Type: PUT
  • Authorization: Administrator
  • Description: This endpoint is used to update product
  • Input Type: FromBody
  • Input model: UpdateProductDtoApi
{
  "productId": "int",
  "ProductName": "string?",
  "ProductPrice": "decimal?",
  "ProductDescription": "string?",
  "ProductManufacturer": "string?",
  "ProductCategoriesIds": [
    "int"
  ]
}
  • Output: Code 200 Ok(ProductResponseDto)
{
  "Id": "int",
  "Name": "string",
  "Price": "decimal",
  "Description": "string",
  "Manufacturer": "string?",
  "DateAdded": "DateTime"
}



Users Controller

LoginAsync

  • HTTP Path: POST /api/users/login
  • Request Type: POST
  • Authorization: AllowAnonymous
  • Description: This endpoint is used to login user and get cookie with JWT token
  • Input Type: FromBody
  • Input model: AuthenticateUserDtoApi
{
  "Email": "string",
  "Password": "string"
}
  • Output: Code 204 NoContent

RegisterUserAsync

  • HTTP Path: POST /api/users
  • Request Type: POST
  • Authorization: AllowAnonymous
  • Description: This endpoint is used to register new account
  • Input Type: FromBody
  • Input model: RegisterUserDtoApi
{
  "Login": "string",
  "Email": "string",
  "Password": "string",
  "ConfirmPassword": "string"
}
  • Output: Code 204 NoContent

ConfirmEmailAsync

  • HTTP Path: GET /api/users/confirm/{userId}/{token}
  • Request Type: GET
  • Authorization: AllowAnonymous
  • Description: This endpoint is used to confirm account registration via activation link from email
  • Input Type: Parameter
  • Input arguments: "userId":"string", "token":"string"
  • Output: Code 200 Ok(string)

RequestDeletionAsync

  • HTTP Path: PATCH /api/users/request-deletion
  • Request Type: PATCH
  • Authorization: Visitor, Administrator
  • Description: This endpoint is used to request account deletion, account becomes not visible for users
  • Input Type: -
  • Output: Code 204 NoContent

SubmitDeletionAsync

  • HTTP Path: GET /api/users/{id}
  • Request Type: GET
  • Authorization: Administrator
  • Description: This endpoint is used to submit account deletion by user Id
  • Input Type: parameter
  • Input arguments: "id":"string"
  • Output: Code 204 NoContent

RemoveRefreshToken

  • HTTP Path: GET /api/users/logout
  • Request Type: GET
  • Authorization: Visitor, Administrator
  • Description: This endpoint is used to remove cookie with authentication token from user's browser
  • Input Type: -
  • Output: Code 204 NoContent

RemoveGuestSessionId

  • HTTP Path: GET /api/users
  • Request Type: GET
  • Authorization: AllowAnonymous
  • Description: This endpoint is used to remove cookie guest session Id from user's browser
  • Input Type: -
  • Output: Code 204 NoContent

Database

The tables in the database are mapped from the entities in project using the Entity Framework Core ORM. The relationships between these entities are defined in the ShopDbContext file. This file also contains the DbSet properties for each entity, such as Products, ProductCategories, Carts, CartLines, Orders, and ShippingDetails, which are used to interact with the corresponding tables in the database.

{
    modelBuilder.Entity<Cart>()
        .HasMany(c => c.CartLines);

    modelBuilder.Entity<CartLine>()
        .HasOne(cl => cl.Product)
        .WithMany()
        .HasForeignKey(cl => cl.ProductId)
        .OnDelete(DeleteBehavior.Restrict);

    modelBuilder.Entity<Order>()
        .HasOne(o => o.Cart)
        .WithOne(c => c.Order)
        .HasForeignKey<Order>(o => o.CartId)
        .OnDelete(DeleteBehavior.Restrict);

    modelBuilder.Entity<Order>()
        .HasOne(o => o.StoreUser)
        .WithMany(su => su.Orders)
        .HasForeignKey(o => o.StoreUserId)
        .OnDelete(DeleteBehavior.Restrict);

    modelBuilder.Entity<ProductProductCategory>()
         .HasKey(pc => new { pc.Id });

    modelBuilder.Entity<ProductProductCategory>()
        .HasOne(pc => pc.Product)
        .WithMany(p => p.ProductProductCategories)
        .HasForeignKey(pc => pc.ProductId)
        .OnDelete(DeleteBehavior.Restrict);

    modelBuilder.Entity<ProductProductCategory>()
        .HasOne(pc => pc.ProductCategory)
        .WithMany(c => c.ProductProductCategories)
        .HasForeignKey(pc => pc.ProductCategoryId)
        .OnDelete(DeleteBehavior.Restrict);

    modelBuilder.Entity<StoreUser>()
        .HasOne(su => su.Cart)
        .WithOne(c => c.StoreUser)
        .HasForeignKey<Cart>(c => c.StoreUserId)
        .OnDelete(DeleteBehavior.Restrict);

    modelBuilder.Entity<Order>()
        .HasOne(o => o.ShippingDetail)
        .WithOne(sd => sd.Order)
        .HasForeignKey<Order>(o => o.ShippingDetailId)
        .OnDelete(DeleteBehavior.Restrict);
}

ERD Diagram

Untitled (1)

Entities

ASP.NET Core Identity tables

  • StoreUser - class extends the default IdentityUser provided by ASP.NET Core Identity. This table represents the user in the system and stores references to their orders and cart.

    • StoreUser (1)->(N) Order
    • StoreUser (1) -> (1) Cart
  • AspNetRoleClaims - Stores claims associated with roles, enabling role-based authorization by attaching specific claims to a role

  • AspNetRoles - Stores roles that can be assigned to users, such as "Administrator" or "Visitor."

  • AspNetUserClaims - Stores claims associated with users, allowing specific claims-based authorization on a per-user basis

  • AspNetUserLogins - Tracks external login information (e.g., Google, Facebook) associated with users, allowing them to log in using third-party services

  • AspNetUserRoles - A join table between users and roles, associating a specific user with one or more roles.

  • AspNetUsers - `Stores user data, including information such as usernames, email addresses, and password hashes.

  • AspNetUserTokens - Stores security tokens associated with users, which can be used for actions like password resets or multi-factor authentication.`

Order

The Order table represents a placed order and stores information about its status in the Status column. The table is linked to the Cart table, allowing the ordered products to be tracked and to the StoreUser table, indicating who placed the order. Order has also a reference to the ShippingDetail table, which stores shipping-related information.

  • Order (1) -> (1) Cart
  • Order (N) -> (1) StoreUser
  • Order (1) -> (1) ShippingDetail

ShippingDetail

The ShippingDetail table represents the delivery details of the user placing the order and it's only connected to Order table.

  • ShippingDetail (1) -> (1) Order

Cart

The Cart table represents the user's shopping cart and contains information about its creation date CreatedAt and status CartStatus, which indicates whether the cart is archived or active. Additionally, this entity stores a reference to the user via StoreUserId, to the Order if it has already been placed, and to a list of products CartLine, which act as an aggregate for the cart and store information about the products added to the cart. The StoreUserId field is nullable because the Cart table may not be linked to a specific user if the cart was created by a guest, who is simply identified by the cart's Id. This field can be filled once the guest logs into their account, at which point their Id will be assigned to the cart. Similarly, the Order column will be populated only after an order is placed.

  • Cart (1) -> (1) StoreUser
  • Cart (1) -> (N) CartLine
  • Cart (1) -> (1) Order

CartLine

The CartLine entity represents the product added to the cart and provides information about its quantity.

  • CartLine (N) -> (1) Product

Product

The Product table represents a product in the database and contains information about its name Name, price Price, manufacturer Manufacturer, description Description, as well as the date it was added and modified DateAdded, DateUpdated. The table also holds references to the ProductProductCategory table, which describes the categories from ProductCategory table, assigned to the product.

  • Product (1) -> (N) ProductProductCategory
  • Product (N) -> (M) ProductCategory

ProductProductCategory

The ProductProductCategory table is a join table that describes the many-to-many relationship between the roduct and ProductCategory tables.

  • ProductProductCategory (N) -> (1) Product
  • ProductProductCategory (N) -> (1) ProductCategory

ProductCategory

The ProductCategory table stores information about product categories that can be used for filtering.

  • ProductCategory (1) -> (N) ProductProductCategory
  • ProductCategory (N) -> (M) Product

CI/CD

The backend API is connected to Azure Web App and the CI/CD process starts automatically after each push to the main branch. The API is built and deployed using GitHub Actions, with Entity Framework Core migrations applied during the process. Secrets, such as Azure credentials and the SQL connection string, are used for secure configuration. After every push, the API is deployed to Azure Web App.

About

Online shop

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors