Skip to content

Architecture

Wilson Wong edited this page Jul 4, 2024 · 20 revisions

This page outlines the general architecture and design principles of the Permit Connect Navigator Service (PCNS). It is mainly intended for a technical audience, and for people who want to have a better understanding of how the system works.

Table of Contents

Infrastructure

image

Figure 1 – The general infrastructure and network topology of PCNS

We receive data from an external service Common Hosted Forms Service (CHEFS) and from our PCNS client written in VueJs, managed by our PCNS NodeJs application. File upload/download is managed by the external service Common Object Management Service (COMS). The NodeJs application interfaces with our PCNS Postgres database.

Database structure

The PostgreSQL database is written and handled via managed, code-first migrations. We generally store tables containing activities, initiatives, enquiries, permits, submissions, users, and how they relate to each other.

PCNS is a has a mono-repository architecture containing both a frontend and backend. The following figures depict the database schema structure as of July 2024.

image

Figure 2 – The public schema for a PCNS database

The database tracks activities, initiatives, enquiries, permits, submissions, users, and a few other tables and how they relate to each other. We enforce foreign key integrity by invoking onUpdate and onDelete cascades in Postgres. This ensures that we do not have dangling references when entries are removed from the system.

image

Figure 3 – The audit schema for a PCNS database

We use a generic audit schema table to track any update and delete operations done on the database. This table is only modified by database via table triggers, and is not normally accessible by the COMS application itself. This should meet most general security, tracking and auditing requirements.

Code Design

The code structure in PCNS follows a simple, layered structure following best practice recommendations from Express, Node, ES6, and Typescript coding styles and utilize Eslint and Prettier to enforce those recommendations.

Organization – Backend

The backend is an ExpressJs application managing a PostgresDB. We utilize the KnexJs package for database migration management and configuration and PrismaJs for database object-relation management.

The codebase has the following discrete layers:

Layer Purpose
Controller Contains controller express logic for determining what services to invoke and in what order
DB Contains the direct database table model definitions and typical modification queries
Middleware Contains middleware functions for handling authentication, authorization and feature toggles
Routes Contains defined Express routes for defining the PCNS API shape and invokes controllers
Services Contains logic for interacting with the Database, COMS API, or other external APIs for specific tasks
Validators Contains logic which examines and enforces incoming request shapes and patterns

Each layer is designed to focus on one specific aspect of business logic. Calls between layers are designed to be deliberate, scoped, and contained. This hopefully makes it easier to tell what each piece of code is doing and what it depends on. For example, the validation layer sits between the routes and controllers. It ensures that incoming network calls are properly formatted before proceeding with execution.

Middleware

PCNS middleware focuses on ensuring that the appropriate business logic filters are applied as early as possible. Concerns such as feature toggles, authentication and authorization are handled here. Express executes middleware in the order of introduction. It will sequentially execute and then invoke the next callback as a part of its call stack. Because of this, we must ensure that the order we introduce and execute our middleware adhere to the following pattern:

  1. Validation and structural checks
  2. Permission and authorization checks
  3. Any remaining middleware hooks before invoking the controller

Organization – Frontend

The frontend utilizes the VueJs framework to build the user interface, using Typescript. We utilize several library packages with this framework that shape the structure of our frontend.

The following is a partial list of important packages used in the frontend:

Package Purpose
Axios Library for making HTTP requests
Pinia State management framework for VueJs
Primevue Vue component and template library
Vite Javascript bundler, hot-module replacement capabilities
Vitest Javascript unit testing framework
Vue-router Client-side routing library for VueJs
Vue-test-utils VueJs unit test utility library

Security and Rule-based Access Controls

Note: Currently under proposal status

SSO Roles

SSO Roles will have the following structure: {initiative}.{user_type}

Examples

  • housing.admin
  • housing.navigator
  • housing.supervisor
  • pcns.developer
  • pcns.proponent

From this we can differentiate between initiative specific roles, and application wide roles. Where housing is an example of a initiative specific role and pcns is an application wide role. There is no guarantee that a housing navigator will also be a navigator for a new initiative. But we can guarantee that a developer is an application wide developer, and a proponent is an application wide proponent.

Resources

The following is a list of currently known resource types within the application as of July 4th, 2024. These are not restricted to DB tables, and can include possible separation of concerns within the application. Resources are subject to update.

  • document
  • enquiry
  • note
  • permit
  • roadmap
  • sso
  • submission
  • user

The following resources are application concerns in addition to the above.

  • navigation
  • testing

Actions

These are defined actions that a piece of data may undergo. They are as follows:

  • create
  • read
  • update
  • delete

There may be additional actions added in the future and are not limited to CRUD operations.

Scopes

A scope is an optional suffix to a permission. This can be used to define a subset of the resource the permission is allowed to access. If a scope is not defined for a permission it defaults to all resource access.

The following is a list of currently identified scopes required and their use:

  • self ** filters results to resources created by current user id ** ideally accomplished by modifying the database query instead of filtering the full data set

More scopes may be added in the future.

Permissions

A permission is a grouping of an initiative, user type, resource, action, and an optional scope. Action is not required to be a CRUD operation. A piece of data may have the following permission:

initiative user_type resource action scope
housing navigator submission read
housing proponent submission read self

In this case, a housing initiative's navigator may read all submission resources. And a housing initiative's proponent may read only their own submissions.

Table of Contents

Clone this wiki locally