Architecture - COS-301/graduates GitHub Wiki

Welcome to the Architecture Wiki!

Table Of Contents

  1. Project Overview
  2. Design Architecture
  3. UI Architecture
  4. Service Architecture
  5. Data Architecture
  6. API Architecture
  7. DevOps Architecture
  8. Testing Architecture

Project Overview

Project Architecture

The software architecture of a system details the structure of the system with regards to: the interfacing, interaction among its components and subsystems and helps developers to design a system. The architecture of a system provides a crucial representation of a system that allows everyone that is a part of the system to have a joint understanding of the system.

Architecture Overview

N-Tier Architecture

The project will follow a N-tier architecture pattern, it consists of Presentation Layer, API Layer, Service Layer and Data Layer. The client interacts with the service layer which will then generate requests which will then be pushed through the various layers which will then process the request and then respond back to the presentation layer which will then be viewed by the client.

Architecture Motivation

The N-Tier architecture was chosen due to its simplistic concepts and segregation of functions. Furthermore due to the feature driven methodology each of the roles in the team could be allocated to a specific layer in the architecture pattern. For example service engineers could focus on service engineer, UI and design engineers could focus on the presentation layer and data engineers could focus on the data layer. Each role then defines an interface that is used by the layer below it, hiding unnecessary implementation details from the other layers. This allows for better cohesion of application functionality, and decouples unrelated functionality, by grouping functionality on a layer-by-layer basis.

Package Diagram

Project Layout

High Level Overview

There are four top level folders where most of the application development will take place.

Nx Structure approach

We will be using the micro libs approach, meaning our library types would fall under feature/data-access/UI/util categories which will be further explained below, but if more information is need you can access it at: https://nx.dev/structure/library-types

  1. Apps
    • Should configure any dependencies and are responsible for linking libraries.
    • This folder should not contain any components, services, controller or any other backend logic.
  2. Libs
    • Will contain components, services, controllers and other utilities.
  3. Prisma
    • Configurations relating to the database and contains the relevant database schemas.
  4. Tools
    • Contains generation and other utility scripts.

Apps

The Apps folder will be responsible for any dependency configuration and library linking needed by the application. It should not contain any code relating to any services or business logic.

The apps folder will have the following structure:

β”œβ”€β”€ apps
β”‚   β”œβ”€β”€ api 
β”‚       β”œβ”€β”€ src
β”‚           β”œβ”€β”€ app
|               β”œβ”€β”€ app.module.ts // API related features will be imported here
β”‚           β”œβ”€β”€ assets
β”‚           β”œβ”€β”€ environments
β”‚   β”œβ”€β”€ client 
β”‚       β”œβ”€β”€ src
β”‚           β”œβ”€β”€ app
|               β”œβ”€β”€ app.module.ts // Client related features will be imported here
β”‚           β”œβ”€β”€ assets
β”‚           β”œβ”€β”€ environments
β”‚   β”œβ”€β”€ client-e2e // end-to-end testing

Libs

The libs folder will contain the controllers, services and utilities used by the application.

The libs folder will have the following structure: Note for team, it is important we enforce this structure well, and also maybe communicate this to other teams to reduce confusion and rework if work is being done in the wrong directories. If we can enforce this from the start we will have a solid base structure.

β”œβ”€β”€ libs
β”‚   β”œβ”€β”€ api 
β”‚       β”œβ”€β”€ shell
β”‚           β”œβ”€β”€ src
β”‚               β”œβ”€β”€ feature
β”‚                   β”œβ”€β”€ lib
β”‚                       β”œβ”€β”€ api-shell-feature.module.ts // Used to export feature libraries, which will be accessed by files in the apps folder
β”‚       β”œβ”€β”€ example-section // Section within the application ex. Registration which encapsulated both Login and Signup Features
β”‚           β”œβ”€β”€ api 
β”‚               β”œβ”€β”€ feature-name
β”‚                   β”œβ”€β”€ api
β”‚                       β”œβ”€β”€ feature
β”‚                           β”œβ”€β”€ src
β”‚                               β”œβ”€β”€ lib
β”‚                                   β”œβ”€β”€ feature-name.spec.ts // Feature related testing
β”‚                                   β”œβ”€β”€ feature-name.controller.ts // Feature related API endpoints go here
β”‚                                   β”œβ”€β”€ feature-name.module.ts // Used to import controller and export library
β”‚                       β”œβ”€β”€ shared // For libraries that might be reused by other developers (can go on any level that you feel makes sense)
β”‚           β”œβ”€β”€ repository // Repositories for database data 
β”‚               β”œβ”€β”€ data-access
β”‚                   β”œβ”€β”€ src
β”‚                       β”œβ”€β”€ lib
β”‚                           β”œβ”€β”€ data-access.spec.ts // Data access related testing
β”‚                           β”œβ”€β”€ data-access.ts // Data access logic goes here
β”‚               β”œβ”€β”€ shared // For libraries that might be reused by other developers
β”‚           β”œβ”€β”€ services // Responsible for data access and retrieval, also responsible for business logic 
β”‚               β”œβ”€β”€ feature
β”‚                   β”œβ”€β”€ src
β”‚                       β”œβ”€β”€ lib // Service Architecture File Structure Section
β”‚                           β”œβ”€β”€ commands
β”‚                           β”œβ”€β”€ queries
β”‚                           β”œβ”€β”€ events
β”‚                           β”œβ”€β”€ models
β”‚                           β”œβ”€β”€ interfaces
β”‚                           β”œβ”€β”€ name.service.ts // Responsible for executing the Commands and Queries
β”‚               β”œβ”€β”€ shared // For libraries that might be reused by other developers
β”‚   β”œβ”€β”€ client
β”‚       β”œβ”€β”€ section-within-application
β”‚           β”œβ”€β”€ feature-related-to-section // Indicate feature name
β”‚               β”œβ”€β”€ src
β”‚                   β”œβ”€β”€ lib
β”‚                       β”œβ”€β”€ feature.component.scss
β”‚                       β”œβ”€β”€ feature.component.spec.ts
β”‚                       β”œβ”€β”€ feature.component.ts
β”‚                       β”œβ”€β”€ feature.component.html
β”‚                       β”œβ”€β”€ feature.module.ts
β”‚                       β”œβ”€β”€ feature-routing.module.ts

Working in Shared Directories

Shared libraries should contain who owns that code. It is the owners of the code's responsibility to maintain and make their service available under the shared libs for others to make use of it.

Prisma

The Prisma folder will be responsible for the configurations and structure relating to the database. It contains the relevant database schemas.

The prisma folder will have the following structure:

β”œβ”€β”€ prisma
β”‚   β”œβ”€β”€ migrations
β”‚       β”œβ”€β”€ dateAndId_changemade
β”‚           β”œβ”€β”€ migration.sql //Contains code representing the the sql added
β”‚   β”œβ”€β”€ schema.prisma //Code detailing the entire system schema
β”‚   β”œβ”€β”€ seed.ts //Code for authorization seeding
β”‚   β”œβ”€β”€ sow.ts //Code for authorization sowing

Tools

The Tools folder will contain generation and other utility scripts.

The tools folder will have the following structure:

β”œβ”€β”€ tools
β”‚   β”œβ”€β”€ generators
β”‚   β”œβ”€β”€ scripts 
β”‚       β”œβ”€β”€ merge-reports.js // Code for jest coverage of merge reports

Design Architecture

UI Architecture

Styling

For global styling that is constant throughout the solution, we will be using the styles.scss file located in graduates/apps/client/src/ This will also be the place where the global color pallet of the solution will reside

NGXS

NGXS Overview

A very important aspect of our client is that is has to be Stateful. To ensure this we will be using NGXS and it’s 4 main concepts. Below is a very brief example of how the 4 concepts of NGXS work, but for more examples and more difficult features of NGXS state management, the documentation has a lot of examples and go into further depth.

NGXS Store

This is a global state, dispatcher for actions as well as a selector. In context with our solution, the store will be responsible for making the calls to the API. In order to dispatch actions, you need to include the store in your desired component/service and then do a dispatch to the function you need

/*
 food.actions.ts
*/
export class AddFood {
  static readonly type = '[Shop] Add Food;
  constructor(public name: string) {}
}
import { Store } from '@ngxs/store';
import { AddFood } from './food.actions';

@Component({ ... })
export class ShopComponent {
  constructor(private store: Store) {}

  addFood(name: string) {
    this.store.dispatch(new AddFood(name));
  }
}

A usefull feature of Store is the Snapshop. Calling store.snapshot() will return all the current values of the store.

NGXS Actions

These should be seen as commands that trigger events or could also be a result of a previous event. Each action will contain a type as a unique identifier Example:

export class buyFood {
  static readonly type = '[Shop] Buy Food';
}

If we want to include extra data in a action it happens as follows:

export class buyBread {
  static readonly type = '[Shop] Buy Food';
  constructor(public type: string, public breadCount: number) {}
}

In the above example type refers to what type of bread should be bought and breadCount would be the amount of bread that should be bought

NGXS State

This is a class definition of the state, along with decorators that are going to describe the metadata and actions.

@State<ZooStateModel>({
  name: 'zoo',
  defaults: {
    feed: false
  }
})
@Injectable()
export class ZooState {
  constructor(private zooService: ZooService) {}
}

Our state will listen to actions via a decorator called @Action . This decorator accepts an action class. Important concepts around State are calling actions, setting the state. Note NGXS supports Async actions which is very useful. Actions can also dispatch other actions.

NGXS Selects

These will be functions that 'slice' a part of the state from our global state. PLEASE follow the pattern of keeping READ and WRITE separate.

You can select a piece of data from Store using @Select and passing in the a state class, function or selector.

import { Select } from '@ngxs/store';
import { ZooState, ZooStateModel } from './zoo.state';

@Component({ ... })
export class ShopComponent {
  // Read name of the state from the state class
  @Select(ShopState) foods$: Observable<string[]>;

  // memoized milk selector to only return milk
  @Select(ShopState.milk) milk$: Observable<string[]>;

  // Function
  @Select(state => state.shop.foods) foods$: Observable<string[]>;

  // Read name of the state from the parameter
  @Select() shop$: Observable<ShopStateModel>;
}

Store also a select function which will come in handy when you cannot declare statically with @select. Other important features of Selects to keep in mind are Lazy selectors, joining of selectors.

Service Architecture

The service layer for this project is where all the relevant business logic will live and will also be responsible for bridging the gap between the controllers(API) level and the database repositories. This layer will follow the CQRS pattern, also known as the Command and Query Responsibility Segregation pattern. The main goal of this pattern is to separate read and update operations on some data store, in the context of our application this will be our database. The read operations on the data store will be represented by queries and the update operations will be represented as commands. Queries are responsible for generating so called DTO entities, also known as Data Transfer Objects. To make these queries and commands more reactive these will then be used in the context of events.

An example of the CQRS pattern in a layered architecture is given by:

Service Architecture - Non Reactive

But as mentioned before this is not a very reactive architecture, so we introduce the notion of events to allow the system the ability to respond and create "events".

NestJS CQRS

The NestJS framework provides a module to aid in the implementation of this pattern. This is module allows us to create commands and queries which can then be dispatched from the services layer or directly from the controllers if needed.

Commands

Each action to be performed by the system is captured in a command. A command will then in turn be consumed by a command handler which is responsible for executing the command. To create a command we need the following:

  1. Command - Capture the data that will be extracted when the command is dispatched. (has a 1-1 relationship with a command handler)
  2. Command Handler - This is where the logic will live when handling the specific command. (has a 1-1 relationship with a command)
  3. Controller/Service - Responsible for dispatching the command.

This will be illustrated at the hand of an example: Our command will be a create user command, which will cause a new item to be added to our users repository.

/*
users.service.ts
*/
@Injectable()
export class UsersService {
  constructor(private commandBus: CommandBus) {} // Constructor takes the command bus(responsible for consuming commands)

  async createUser(userDto: UserDto) {
    return this.commandBus.execute(
      new CreateUserCommand(userDto.name, userDto.email, userDto.password)
    );
  }
}

The above service will dispatch a createUserCommand when called. Let's take a look at how the command looks:

/*
create-user.command.ts
*/
export class CreateUserCommand {
  constructor(
    public readonly userName: string,
    public readonly userEmail: string,
    public readonly userPassword: string
  ) {}
}

The Command Bus was said to execute a command, the execution is delegated to a Command Handler. So now we need to write a command handler for our Create User Command which is where the actual data manipulation will happen.

/*
create-user.handler.ts
*/
@CommandHandler(CreateUserCommand)
export class CreateUserHandler implements ICommandHandler<CreateUserCommand> {
  constructor(private repository: UserRepository) {} 

  async execute(command: CreateUserCommand) : Promise<User> {
    // Destruct data from command object
    const { userName, userEmail, userPassword } = command; 
    
    // Create new user object from User model
    const user = new User();
    user.name = userName;
    user.email = userEmail;
    user.password = userPassword;
    
    return this.repository.save(user);
  }
}

The decorator at the top of the file(@CommandHandler) tells NestJS that this handler is available to execute any create user commands. We use the user repository to insert our new user.

Furthermore we might need to react to any changes caused by commands so this is where we introduce events.

Events

Events allow us to make our application more reactive, by reacting to changes. Much like how commands are dispatched by the service layer or sometimes even the controllers layer, events are dispatched by either Models or the EventBus. To continue using our above example for commands, we already have the User model available to us. In order for a model to dispatch an event, it needs to inherit from the AggregateRoot class.

/*
user.model.ts || user.entity.ts
*/
export class User extends AggregateRoot {
  constructor() {
    super();
  }
}

Now that our User model has been setup to work with events, it is now possible for our command to emit an event. To create an event we need the following:

  1. Event Class - Holds our data that we want to extract when handling the event (has a 1-1 relationship with an event handler)
  2. Event Handler Class - This is where the logic will live when handling the specific event. (has a 1-1 relationship with an event)
  3. Event Publisher || Model - Something that will dispatch the events, this is usually done in the model class, but can be manually executed by the EventBus class. (1-n relationship with events)

We want to emit a createUser event so we can react to that event and send the user an email welcoming them to our imaginary application. To allow for this we will make some modifications to our CreateUserHandler.

/*
create-user.handler.ts
*/
@CommandHandler(CreateUserCommand)
export class CreateUserHandler implements ICommandHandler<CreateUserCommand> {
  constructor(
     private repository: UserRepository,
     private publisher: EventPublisher
   ) {} 

  async execute(command: CreateUserCommand) : Promise<User> {
    // Destruct data from command object
    const { userName, userEmail, userPassword } = command; 
    
    // Link our User Model type with the EventPublisher to allow for it to respond to the user events
    const UserModel = this.publisher.mergeObjectContext(
        await this.repository.save(User); 
    );

    // Create new user using the type reference returned by merging the EventPublisher and User type
    const user = new UserModel();    
    user.name = userName;
    user.email = userEmail;
    user.password = userPassword;
    
    // Link create user event with the object 
    user.apply(new CreateUserEvent(user.name, user.email));
    
    // Commit the event, will publish all registered events
    user.commit();
    
    return user;
  }
}

Now we need to create an actual Create User Event to capture our user data such as their name and email.

/*
create-user.event.ts
*/
export class CreateUserEvent {
  constructor(
    public readonly userName: string,
    public readonly userEmail: string,
  ) {}
}

Now we need to add the logic to handle the event:

/*
create-user-event.handler.ts
*/

@EventsHandler(CreateUserEvent)
export class CreateUserEventHandler
  implements IEventHandler<CreateUserEvent> {
  handle(event: CreateUserEvent) {
    const { userName, userEmail } = event;
    sendWelcomeEmail(userName, userEmail);
  }
}

This should make it clear of how to implement a reactive system using the NestJS event system.

Queries

Queries will be used when data needs to read from the data store. Queries follow a similar architecture to Commands and Events.

To create a query we need the following:

  1. Query - Capture the data that will be extracted when the query is dispatched. (has a 1-1 relationship with a command handler)
  2. Query Handler - This is where the logic will live when handling the specific query. (has a 1-1 relationship with a command)
  3. Controller/Service - Responsible for dispatching the query.

This will be illustrated using an example, where we will implement a query to get a user by their email from our data store.

/*
get-user.query.ts
*/
export class GetUserByEmailQuery {
    constructor(
      public readonly userEmail
    ) {}
}
/*
get-user-by-email.handler.ts
*/

@QueryHandler(GetUserByEmailQuery)
export class GetUserByEmailHandler implements IQueryHandler<GetUserByEmailQuery> {
  constructor(private readonly repository: UserRepository) {}

  async execute(query: GetUserByEmailQuery) {
    const { userEmail } = query;
    return this.repository.find(userEmail);
  }
}

This now allows us to call this query in the service/controller level.

Putting it all together

Now all of these different commands, queries and events need to be registered in a module file so NestJS can construct its dependency graph.

/*
registration.module.ts // Responsible for user login and signup
*/

// ...other imports
import { CommandHandlers } from './commands/handlers'; // For our commands
import { EventHandlers } from './events/handlers'; // For our events
import { QueryHandlers } from './queries/handlers'; // For our queries

@Module({
  imports: [CqrsModule],
  providers: [
    UserRegistrationService,
    ...CommandHandlers,
    ...EventHandlers,
    ...QueryHandlers,
  ],
})
export class RegistrationModule {}

This is where we register our command, query and event handlers and bundle them in to a module for organisation.

These functions can now be called from the controller layer:

/*
registration.controller.ts
*/

// This is an example using a REST API not graphql as being used in the project at this moment in time
@Controller('registration')
export class RegistrationController {
  constructor(
    private userRegistrationService: UserRegistrationService
  ) {}

  @Post('/signup')
  async createNewUser(@Body() dto: NewUserDto) {
    return this.userRegistrationService.createUser(dto);
  }

  @Get('/user/:email')
  async findUserByEmail(@Param('email') email: string): Promise<User> {
    return this.userRegistrationService.getUserByEmail(email);
  }
}

This is the service layer which will fire the commands and queries to the handlers.

/*
user-registration.service.ts
*/

@Injectable()
export class UserRegistration {
  constructor(
     private readonly commandBus: CommandBus,
     private readonly queryBus: queryBus
   ) {}

  async createUser(dto: NewUserDto) {
    return this.commandBus.execute(
      new CreateUserCommand(dto.userName, dto.userEmail, dto.userPassword)
    );
  }

  async getUserByEmail(email: string) {
    return this.queryBus.execute(
      new GetUserByEmailQuery(email)
    );
  }
}

File Structure

Each service layer file will have three directories for each of the CQRS pattern participants.

β”œβ”€β”€ lib
β”‚   β”œβ”€β”€ commands
β”‚       β”œβ”€β”€ handlers // Contains the command handlers
β”‚       β”œβ”€β”€ impl // Contains the command objects 
β”‚   β”œβ”€β”€ queries
β”‚       β”œβ”€β”€ handlers // Contains the query handlers
β”‚       β”œβ”€β”€ impl // Contains the query objects 
β”‚   β”œβ”€β”€ events 
β”‚       β”œβ”€β”€ handlers // Contains the event handlers
β”‚       β”œβ”€β”€ impl // Contains the event objects 
β”‚   β”œβ”€β”€ models // Used for events, will inherit from the Aggregate Root class
β”‚   β”œβ”€β”€ interfaces // Contains DTO entities if necessary 

These services will then be called by the controller layer, the controller layer will directly interact with the commands and queries which will then be executed by the api query resolver function.

Note on Bundling

Just a quick note on how to bundle everything, it would be a good idea to place a module(*.module.ts) file in the service directory, in the same directory as the service file. Then update the index.ts file in the directory one level up from the current one to export this module.

β”œβ”€β”€ src
β”‚   β”œβ”€β”€ lib
β”‚       β”œβ”€β”€ commands
β”‚           β”œβ”€β”€ handlers // Contains the command handlers
β”‚           β”œβ”€β”€ impl // Contains the command objects 
β”‚       β”œβ”€β”€ queries
β”‚           β”œβ”€β”€ handlers // Contains the query handlers
β”‚           β”œβ”€β”€ impl // Contains the query objects 
β”‚       β”œβ”€β”€ events 
β”‚           β”œβ”€β”€ handlers // Contains the event handlers
β”‚           β”œβ”€β”€ impl // Contains the event objects 
β”‚       β”œβ”€β”€ models // Used for events, will inherit from the Aggregate Root class
β”‚       β”œβ”€β”€ interfaces // Contains DTO entities if necessary 
β”‚   β”œβ”€β”€ index.ts // Change export to say export * from './lib/api-registration-service.module';

The format of the service module file can be seen in the Putting it all together. This module file should then be imported into a feature bundle alongside the controller and data-access files and should be added to the imports array in the module object.

Data Architecture

image

The database for the Graduates Portal will serve as a resource where one can file and record the creation of data pertaining to the various users of the system. It will allow the entry, update, editing and reporting of all the data regarding the portal.

The structure of the database has been modelled after a 3 Tier Architecture, as it allows for development, maintenance, logic, data access, data storage, and user interface of the system’s database to be worked on independently as separate modules. This modular approach is beneficial for the Mini-Project due to the nature in which different teams work on separate features within the database and then come together at the end of the project to integrate all the features together. This modular approach helps simplify the independent workflow and helps make the integration of the many projects with the database much less complicated.

The 3 Tier Architecture pattern in which the system’s database portion is modelled after consists of three fundamental layers, namely, the client, server and database layers.

This Architectural Pattern ensures that the users of the system will not have to work with the database directly. This is due to the server layer that separates the client and the database. The server will accept requests from the client, which it will then pass on to the database, the database will then proceed to handle these requests and subsequently sends a response back to the server which then passes the response back to the client.

The main benefits and thus, reasons this Database Architecture Pattern was chosen was due to its ability to separate development of different features within the database. It also allowed for a more simplified integration of features through the database as well as an indirect communication between the Client and the Database, which is important for efficiency, simplicity and safety for all users of the system.

Database Structure

The basic file structure and the configuration relating to the database may be viewed under prisma.

Where the structure and functionality of the Database will be outlined below.

SQL Format

SQL will be used in the creation and updating of all tables of data. An example of what the format of most of the SQL files can be seen below:

--Creating a β€˜User’ Table
CREATE TABLE β€œUser” (
	β€œid” SERIAL NOT NULL,
	β€œemail” SERIAL NOT NULL,
	β€œname” TEXT,

	CONSTRAINT β€œUser_pkey” PIMARY KEY (β€œid”)
);

--Creating a Unique Index for the user’s email address
CREATE UNIQUE INDEX β€œUser_email_key” ON β€œUser”(β€œemail”);

Users of the system

Students will not be the only users of the graduate’s portal, therefore, provisions in the database need to be made for all users, these different users include:

Users:
ADMIN
STUDENT
COMPANY

User Permissions

All the permissions for the system should not be accessible by all users, hence why permissions are required for each user. The different permissions that each type of user has access to is outlined below:

ADMIN Permissions:
-	ADD_USER: to add a new user to the database.
-	ADD_USER_COMPANY_USER: to be able to add users which are part of a company to the database.

-	EDIT_PROFILE: to edit any users profile in the database.
-	REMOVE_PROFILE: to remove any users profile from the database.
-	VIEW_PROFILE: to view any user’s profile.

-	POST_BLOG_ADMIN: to put a blog post to the blog page. 
-	EDIT_BLOG_ADMIN: to edit a blog post on the blog page.
-	REMOVE_BLOG_ADMIN: to remove a blog post from the blog page.

-	EDIT_STORY: to edit any user’s story.
-	REMOVE_STORY: to remove any user’s story.
-	VIEW_STORY: to view any user’s story.

-	VIEW_STORY_COUNT: to see the view count of any user’s story.
-	VIEWED_BY_STORY: to see anyone that has viewed a user’s story.

-	ADD_PERMISSIONS: to give any permissions to a user.
-	ADD_PERMISSIONS_EDIT: to give the permission to edit anything to a user.
-	ADD_PERMISSIONS_VIEW: to give the permission to view anything to a user. 
-	ADD_PERMISSION_REMOVE: to give the permission to remove anything to a user.
-	ADD_PERMISSION_$PERMISSION$: to give the permission - $PERMISSION$ - to a user.

-	REMOVE_PERMISSIONS: to remove any permissions from a user.
-	REMOVE_PERMISSIONS_EDIT: to remove the permission to edit anything from a user.
-	REMOVE_PERMISSIONS_VIEW: to remove the permission to view anything from a user.
-	REMOVE_PERMISSION_REMOVE: to remove the permission to remove anything from a user.
-	REMOVE_PERMISSION_$PERMISSION$: to remove the permission - $PERMISSION$ - from a user.

-	VIEW_PERMISSIONS: to view any permissions of a user.
-	VIEW _PERMISSIONS_EDIT: to view the permission to edit anything of a user.
-	VIEW _PERMISSIONS_VIEW: to view the permission to view anything of a user.
-	VIEW _PERMISSION_REMOVE: to view the permission to remove anything of a user.
-	VIEW _PERMISSION_$PERMISSION$: to view the permission - $PERMISSION$ - of a user.
STUDENT Permissions:
-	EDIT_PROFILE_USER: to edit a student’s own profile.
-	REMOVE_PROFILE_USER: to remove a student’s own profile.

-	VIEW_PROFILE_COMPANY: to view the profile of a company.

-	POST_STORY: to post a story to a student’s own profile.
-	EDIT_STORY_USER: to edit a student’s own story.
-	REMOVE_STORY_USER: to remove a student’s own story from their profile.
-	VIEW_STORY_COUNT_USER: to see the story view count of the students’ own stories.
-	VIEWED_BY_STORY_USER: to see who viewed a student’s own story. 

-	VIEW_STORY_COMPANY: to view the stories of companies.
COMPANY Permissions:
-	EDIT_PROFILE_COMPANY: to edit a company’s own profile.
-	EDIT_PROFILE_COMPANY_USERS: to edit the users’ profiles that fall under the company.

-	REMOVE_PROFILE_COMPANY: to remove a company’s own profile.
-	REMOVE_PROFILE_COMPANY_USERS: to remove the users’ profiles that fall under the company.

-	VIEW_PROFILE_STUDENT: to view the profile of a student.
-	VIEW_PROFILE_COMPANY_USERS: to view the profiles of users that fall under the company.

-	POST_STORY_COMPANY: to post a story to a company’s own profile.

-	EDIT_STORY_COMPANY: to edit a company’s own story.
-	EDIT_STORY_COMPANY_USERS: to edit a user’s story that falls under the company.

-	REMOVE_STORY_COMPANY: to remove a company’s own story from their profile.
-	REMOVE_STORY_COMPANY_USERS: to remove a user’s story that falls under the company.

-	VIEW_STORY_COUNT_COMPANY: to see the story view count of the company’s own stories.
-	VIEW_STORY_COUNT_COMPANY_USERS: to see the story view count of the users’ stories that fall under the company.

-	VIEWED_BY_STORY_COMPANY: to see who viewed a company’s own story. 
-	VIEWED_BY_STORY_COMPANY_USERS: to see who viewed a user’s story that falls under the company. 

-	VIEW_STORY_STUDENT: to view the stories of students.
-	VIEW_STORY_COMPANY_USERS: to view the stories of users that fall under the company.

User Profiles

Since each user will have their own profile, it makes sense to create a database entry for each user’s profile information. The type of user and their respective profile information that will be depicted in the database representation is as shown below:

user (the table that holds the information of each registered user in the system)
-       user_id (Primary Key, unique id used to differentiate each user)
-       email
-       password
-       password_salt (for hashing)
-       name
-       date_of_birth
-       company_id (Foreign Key, an id used to determine which company (if the user is part of a company) the user is a part of)
-       date_created
-       suspended (a flag to determine if the user's account has been suspended)
-       validated

user_profile (the table used to hold information for the user's accounts' profile)
-       user_id (Primary Key/Foreign Key, a unique id used to differentiate the user as well as determine which user's profile to use)
-       studentNumber
-       profile_picture
-       bio (a short description of the user)
-       employmentStatus
-       openToOffers

user_tag 
-       user_id (Primary Key/Foreign Key, a unique id used to differentiate the user as well as determine which user's tag to use)
-       tag (Primary Key, used to determine defining features of the user)

user_social_media
-       user_id (Primary Key/Foreign Key, a unique id used to differentiate the user as well as determine which user's social media to use)
-       type (Primary Key, the type of Social Media)
-       link (to the user's specific social media)

user_location
-       user_id (Primary Key/Foreign Key, a unique id used to differentiate the user as well as determine which user's location to use)
-       location (Primary Key)

user_email
-       user_id (Primary Key/Foreign Key, a unique id used to differentiate the user as well as determine which user's email to use)
-       contact email (Primary Key)

user_degree
-       user_id (Primary Key/Foreign Key, a unique id used to differentiate the user)
-       degreeType
-       degreeName

user_permission (a table used to determine which permissions a user has)
-       user_id (Primary Key/Foreign Key, a unique id used to differentiate the user as well as determine which user's permissions to use)
-       permission_type (Primary Key)
-       permission_category (Primary Key)
-       permission_tenant (Primary Key)    

user_validation (the table used to validate a user's account)
-       user_id (Primary Key/Foreign Key, a unique id used to differentiate the user as well as determine which user's validation to use)
-       validated (a flag to determine if the user's account has been validated or not)

user_scouted (the table used to determine if a user has been scouted by a company)
-       user_id_scout (Primary Key/Foreign Key, a unique id used to differentiate the user as well as determine which user's scouting to use)
-       scouted (a flag to determine if a user has been scouted or not)

user_role (the table to determine what roles a user has)
-       user_id (Primary Key/Foreign Key, a unique id used to differentiate the user as well as determine which user's role to use)
-       role (Primary Key, the role of the user)

role_permission (table for information about users roles)
-       role (Primary Key/Foreign Key, a unique id used to differentiate the user as well as determine which user's role to use)
-       permission_type (Primary Key)
-       permission_category (Primary Key)
-       permission_tenant (Primary Key)

user_permissions (table for information about users roles)
-       userid (Primary Key/Foreign Key, a unique id used to differentiate the user)
-       permission_type (Primary Key)
-       permission_category (Primary Key)
-       permission_tenant (Primary Key)

UserProfileFile (table for information about users roles)
-       userid (Primary Key/Foreign Key, a unique id used to differentiate the user)
-       fileId 
-       filePath 
-       fileCategory
-       fileExtension



student_profile (the table that holds the student’s information)
-	u_num (Primary Key of the user’s number in the database)
-	UserID (Foreign Key of ID in the β€˜User’ table, used to differentiate each student)
-	profile_picture (A headshot of the student)
-	first_name
-	last_name
-	dob (The date of birth of the student)
-	cellphone_number
-	bio (A short paragraph that the user can use to describe themselves and/or any accomplishments they may like to highlight)
-	notable_achievements (Foreign Key of the achievement in the β€˜achievements’ table -expanded below- to show the achievements of the student)
-	story (Foreign Key of story_ID in the β€˜story’ table -expanded below-, to link the student to any stories they may post)

short (the table that holds the information and metadata of the stories posted by users)
-	story_ID (Primary Key, an ID used to differentiate each story)
-	user_id (Foreign Key of User_ID in β€˜student_profile’)
-       description
-       link
-       thumbnail
-       date_posted
-       archived (a flag used to determine if the story has been archived)

short_tag
-       story_id (Primary Key, Foreign Key, used to differentiate each story and determine which user posted story to use)
-       tag (Primary Key)

short report (a table used to determine if a story has been reported)
-       story_id (Primary Key, Foreign Key, used to differentiate each story and determine which user posted story to use)
-       user_id (Primary Key/Foreign Key, a unique id used to differentiate the user as well as determine which user's story to use)
-       reason (the reason given for reporting the story)


notifications (the table that holds the information about notifications for users)
-	notification_id (Primary Key, used to differentiate each unique notification)
-	user_id_from (Foreign Key, used to show which user sent the notification)
-	user_id_from (Foreign Key, used to show which user to send the notification to)
-	data (the actual data of the notification)
-	date (the date and time the notification was sent)
-	seen (a flag to determine if the user has viewed the notification or not)

blog (the table used to hold information about blogs users have posted)
-       id (a unique id used to differentiate blogs)
-       user_id (Foreign Key used to determine which user the blog belongs to)
-       title 
-       content
-       date
-       archived (a flag used to determine if the blog is archived)

blog_media (the table used to store the media of user posted blogs)
-       blog_id (Primary Key/Foreign Key, an id used to differentiate each blog and to determine which user posted blog is being used)
-       media (Primary Key, used to differentiate between the different media used in the blogs)

blog_comment (the table used to store information on the comments left on user blogs)
-       comment_id (Primary Key, a unique id used to differentiate each comment)
-       blog_id (Foreign Key, used to determine which blog the comment pertains to)
-       user_id (Foreign Key used to determine which user left the comment)
-       date_posted

API Architecture

Layers:

The API architecture is devised into 3 main layers, which specify where their specific files and applications will be stored in the repository.These layers are namely:

API Layer

This layer contains the resolvers, these resolvers are used to resolve the data that is required and is also used to present the required data by using GraphQL. These resolvers do this by calling the Service layer.

Service Layer

This is the layer where all the CQRS services are located. They are the starting point for all the dispatched CQRS queries, events and commands. The services can however not do this directly to the databases, they resolve this problem by calling the Repository Layer.

Repository Layer

This layer is responsible for the connection between the Service layer and the databases. Therefore only the Repository layer elements will be able to communicate with the databases and acquire/send data to the various databases.

DevOps Architecture

Git Actions

https://nx.dev/ci/monorepo-ci-github-actions

Subsystems

image

Image taken from CI/CD team documentation

Testing Architecture

Overview

WhatsApp Image 2022-04-15 at 3 12 29 PM (1)

Unit Testing

image

Integration Testing

image

e2e Testing

image

Usability Testing

image

Phases:

  1. Unit Testing Phase
  2. Integration Testing Phase
  3. End to End Testing Phase
  4. Usability Testing Phase

Steps to be performed at Phases 1 and 2 (Jest):

  1. Libraries and modules are written and implemented, afterwards tests need to be written by the involved developer(s).
  2. Upon completed implementation and written tests, the tester will review the modules and libraries and determine whether the tests are adequate or if more tests are needed, also providing Quality Assurance.
  3. Once reviewed and the tests are proper and complete, the tester will log the results of each test using the protocol defined on the next page.
  4. No module or library may leave the current phase or be pushed until a tester has reviewed and logged the results for the test.

Steps to be performed at Phase 3 (Cypress):

  1. Testers will coordinate to effectively test the system’s features from start to end, ensuring the expected functionality is achieved.
  2. More details on steps to follow in next Sprints.

Steps to be performed at Phase 4:

  1. Testers will coordinate to test the UI and feel of the system, ensuring it delivers aesthetical as well as functional quality and testing the system’s usability.
  2. More details on steps to follow in next Sprints.

Protocol for logging tests:

Format:

Commit Hash:          42eeff
Status:               Passed/Failed
Date:                 yyyy/mm/dd
Tester(s):            Bob Ross, Ben Stiller
Developer(s):         Timbur Lee
Description:          Commit Title if passed and Error explanation if failed (short)

Procedures for tests:

1. Pass

  • i. Log the test with the given format.
  • ii. Move the segment to the next phase if applicable.

2. Fail

  • i. Log the test with the given format.
  • ii. Open a GitHub Issue with the same format and titled β€œFailed Test”.
  • iii. Notify the involved developer(s).
  • iv. Await valid and passed test results then close the GitHub Issue (only allowed by testers).
  • v. Follow β€œPass” step.
⚠️ **GitHub.com Fallback** ⚠️