Skip to content

Architecture

Carlo Beltrame edited this page Nov 13, 2023 · 11 revisions
eCamp v3 architecture

eCamp v3 is made up of several parts or services, each responsible for a specific set of tasks within the application.

The landing page https://ecamp3.ch is maintained in a separate repository, and implemented using Astro and hosted separately from the rest of the application on Netlify.

The main user-facing application website which is visible when visiting https://app.ecamp3.ch is our frontend (in the frontend directory in this repo). This service is written using Vue.js and the material design framework Vuetify. All displayed text in the frontend is translatable via vue-i18n.

The frontend itself cannot store any user data, and can be seen as only a visual representation of the user's camps. All the camp data is retrieved from and stored into our separate backend service via an API. The frontend interacts with the API using a custom library called hal-json-vuex. Using this library looks a little like this:

// retrieve the motto property of the camp
this.camp.motto
// change the motto of the camp to 'Pirates'
this.camp.$patch({ motto: 'Pirates' })
// retrieve the color of the activity, given by its related category (e.g. LS, LA, LP)
this.activity.category().color

On the last line, the hal-json-vuex library internally checks whether the activity's category has already been loaded from the API, and if so it uses a cached version of the category. This is one of many convenience features implemented in hal-json-vuex.

The API itself (and its OpenAPI documentation page) is reachable publicly on the internet, at https://app.ecamp3.ch/api. Of course, in order to retrieve or edit any relevant data, you need to be logged in. The easiest way to do that is to log in in the frontend, and then visit the API. The API follows the HAL JSON standard. In this format, the API responses allow easy traversal of relations such as from each activity to its respective category, as seen above. Also, in HAL JSON we use so-called URIs instead of IDs whereever practical. An example URI of a camp could look like this: https://app.ecamp3.ch/api/camps/af8f0169b185, so it just describes a complete API endpoint, instead of just a context-less ID.

The API is provided by our backend service (in the api directory in this repo), implemented in PHP using the framework API Platform, which itself is based on the PHP framework Symfony and the Doctrine ORM. The API is served by a caddy reverse proxy, which is just a simple FPM webserver. There are only a handful of translated strings in the backend, for all emails sent out by eCamp v3 as well as for some server side validation messages.

For PDF printing, we are still evaluating two completely different tech stacks, exposed to the user as Print Layout #1 and Print Layout #2.

Print Layout #1 (which we also call nuxt print, code in the print directory in this repo) renders the PDF on the server, by generating a HTML page in a server-side Nuxt app, and then converting this HTML page to PDF using a headless chrome browser called browserless, which also runs on the server. Nuxt is a Vue.js framework, so this is implemented in JavaScript and Vue.js, similar to the frontend.

Print Layout #2 (which we also call client print, code in the pdf directory in this repo) renders the PDF directly in the user's browser. To acheive this, we run react-pdf inside a web worker. However, we have replaced all parts of react-pdf which actually have to do with React with a Vue.js implementation, so this is also implemented in JavaScript and Vue.js. We run this code in a web worker, because running computationally intensive operations in a browser can lead to freezing UI if it is run on the main thread. A web worker is the best way to run things in a separate thread, so the page can stay responsive while generating a PDF.

Sometimes we find that some of our code can be shared between the frontend and the two print services. In this case, we extract that code to a common directory (common in this repo). This currently contains some business logic such as the computation of user initials, as well as some shared translation strings.