Developer's guide Software architecture
28 mai 2024 à 05:35Software architecture
Table of Contents
The Petit Rapporteur app is a basic Web app built around a REST API.
Front-end
The front-end is built with VueJS 3 and few dependencies:
- @intlify/unplugin-vue-i18N: bundler for i18n (MIT License)
- bulma: CSS framework (MIT License)
- mande: fetch request wrapper (MIT License)
- mitt: functional event emitter (MIT License)
- pinia: store for VueJS (MIT License)
- pinia-plugin-persistedstate: plugin to make store data persistent (MIT License)
- vue: Vue framework (MIT License)
- vue-i18n: Vue translations (MIT License)
- vue-matomo: Matomo analytics integration (MIT License)
- vue-router: Vue routing (MIT License)
The following are development dependencies and are not required to run the application in production:
- @rushstack/eslint-patch (MIT License)
- @types/jsdom (MIT License)
- @types/node (MIT License)
- @vitejs/plugin-vue (MIT License)
- @vue/eslint-config-prettier (MIT License)
- @vue/eslint-config-typescript (MIT License)
- @vue/test-utils (MIT License)
- @vue/tsconfig (MIT License)
- cypress (MIT License)
- eslint (MIT License)
- eslint-plugin-cypress (MIT License)
- eslint-plugin-vue (MIT License)
- jsdom (MIT License)
- npm-run-all (MIT License)
- prettier (MIT License)
- start-server-and-test (MIT License)
- typescript (Apache 2.0 License)
- vite (MIT License)
- vitest (MIT License)
- vue-tsc (MIT License)
- whatwg-fetch (MIT License)
The philosophy of the front-end is to be as dumb as possible and to do as less computation as possible.
All processing should be done on the backend side.
The frontend mostly only displays data transmitted by the backend and only sometimes has some logic in the way it displays the fields according to their field_type
.
Pages
The front-end is composed of three pages:
- HomeView.vue: page listing all the reports
- ReportCompletionView.vue: page allowing to explore a report and to complete data through tables
- ReportEditionView.vue: page allowing to edit a report (only accessible with the
report:edit
permission)
Components
There are quite a number of components wich are quite diverse. Some are very basic and only contain HTML like some buttons.
One is a bit more complex: ReportFormItem.vue
which is used to display reports contents in ReportCompletionView
and in ReportEditionView
.
It is a component which is displayed in recursion and can be display as imbricated <details><summary></summary></details>
and also as <table></table>
once a container_promo
field has been completely deployed.
Tests
Front-end tests are working but not much used.
Cypress’ end-to-end tests are not at all used besides the example test of checking the homepage.
Whereas the component unit tests only check the specific FieldsTableCell.vue
component for a few behaviours.
Until now it was assumed as the components are quite simple, we would see them break instantly while developing.
Back-end
The back-end is built with Python FastAPI and some dependencies:
- pydantic: model validation (MIT License)
- uvicorn: Python ASGI Web server (BSD-3-Clause License)
- asyncstdlib: Python async library (MIT License)
- python-multipart: a streaming multipart parser (Apache 2.0 License)
- python-i18n: translations library (MIT License)
- websockets: Websockets library (BSD-3-Clause License)
- rq: task queue library (BSD License)
- databases[postgresql]: asyncio support for PostgreSQL database (database) (BSD-3-Clause License)
- SQLAlchemy: database ORM (database) (MIT License)
- alembic: database migration tool (database) (MIT License)
- psycopg2-binary: PostgreSQL driver (database) (LGPL v3.0 License)
- pyjwt: JWT library (authentication) (MIT License)
- passlib[bcrypt]: Bcrypt library (authentication) (BSD License)
The following are development dependencies and are not required to run the application in production:
- pytest (MIT License)
- pytest-asyncio (Apache 2.0 License)
- httpx (BSD-3-Clause License)
- asgi-lifespan (MIT License)
- black (MIT License)
Tests
There are quite a number of unit tests. 168 units tests which need about 4 minutes to be run at the time of writing. Here unit tests are important because they help to catch regressions and to test multiple cases.
Models
The API handles these type of models:
- Field: one field of a report.
- Report: a report. Technically it is simply stored in the database as a
Field
withparent_id: null
. - Policy: a list of allowed actions on a report.
- Share: a list of policies. It is what is used to be encoded in the access token to allow access to a report.
- Token: an access token containing a share.
- User: a “user” even if it doesn’t mean much today. Currently there is one hard-coded
root
user which you can setup the password through theROOT_PASSWORD_HASH
andROOT_PASSWORD_SALT
environment variables. It is used to access the API Web interface (/api/docs
) to manually use routes.
Handling report’s fields
All report’s fields are stored in the fields
database table. Fields are linked to each other through their id
and parent_id
attributes in order to form a tree of fields.
To fetch report with id 1 and all its fields, you need to use a recursive CTE in the SQL query to get the field with id 1
and retrieve all linked fields through their parent_id
.
For example:
WITH RECURSIVE report_1 as (
SELECT id,
name,
parent_id,
data_id,
field_type,
template_id,
0 AS generation_number
FROM fields
WHERE id = 1
UNION ALL
SELECT child.id,
child.name,
child.parent_id,
child.data_id,
child.field_type,
child.template_id,
generation_number+1 AS generation_number
FROM fields child
JOIN report_1 v
ON v.id = child.parent_id)
SELECT * FROM report_1;
To facilitate retrieving reports, views and materialized views are used in PostgreSQL. For instance, report 1 can be instead be fetched the following way:
SELECT * FROM view_report_1;
Users can filter and search through reports. This processing can be quite heavy. Therefore materialized views with these filters are used to optimize performance.
As described above, fields are structured as a nested tree. In order to filter through columns, we first need to transform this tree into a table. PostgreSQL’s crosstab function is used for this purpose.
- For the CSV export we do the following processing:
Recursive CTE -> Crosstab
- For the JSON export we do the following processing:
Recursive CTE -> Crosstab -> Recursive CTE on the filtered fields
Therefore, the CSV export is faster than the JSON one (which is almost the same format used by the Web interface).
If you look in the code, you will see additional SQL views named view_field_tree_$field_id
.
They behave like a report view but can start at any field.
The reason they exist it because in the permission system design it was expected that you could also precise in which sub-fields a user can access.
Currently this feature doesn’t really work and only allows access to this specific field, but not to its children.
For the moment the discussion is still open wether this feature should be removed or not.
Following is a schema with the views currently in use: