3ab3

A social CMS for a cultural organization

Backstory

In our twenties, me and my friends spent a lot of time at a friend's house in my hometown. This house is special, because its 250 years old, includes a professional restaurant kitchen infrastructure as well as an old bowling alley and a lot of space. Thanks to my friends parents extremely kind nature, we were allowed (spoilt) to throw a big party on the premises roughly once per year, which obviously was every 20 year olds wet dream. These parties started with 200 people, and then grew more and more professional, until they reached an (estimated) size of approximately 800 guests, 9 different acts in the lineup and roughly 20k in sales that night. In other words, they grew out of proportion and we needed to organize ourselves better to handle the financial risk associated with such events. For this reason we founded a cultural organization called 3ab3. With this we also introduced a bar that was periodically stocked with beverages and a system, that would allow members of the organization to drink without having to immediately pay in cash (they never had cash with them anyways). As can be easily imagined, this quickly became the main stream of financial and material liquidity into and out of the organization. We needed a way to keep track of who owes the organization how much money. And this is where this project starts.As every highly motivated individual who just discovered a new thing (programming in Java and creating UIs with it) I went ahead and over-engineered the bejesus out of the problem. I created a desktop application that was essentially a glorified Excel sheet with our logo in the top left corner. It got the job done but it wasn't spectacular either, it was saving everything in a single XML file that was just growing indefinitely (who needs databases, right?). Then i found out about web development and i decided it would be amazing if the whole thing could be on the web instead, because then everyone would gain access to their own personal page where they can see how much money they need to pay. So i did it in a dockerized Django app with a React frontend. This was quite the ambitious scope for my first ever web development project. But it worked out! It took a ton of perseverance, a ton I cannot stress this enough. Because i basically didn't know at all what i was doing. Also there were minor issues, for example that on launch day, every member was spammed with dozens of welcome emails until i finally managed to kill that docker container that hung itself up. But then the thing worked. And 3 years later i had learned so many new technologies and tools that i decided it was time to rewrite everything from the ground up. Because that's what bored engineers do with their free time, when they have played too much Factorio and they think they could also just program something that is actually useful for somebody.

Shot from one of our infamous parties back in the day

So what does the 3rd version actually do?

It does all the things. No literally, it does all of them. Here is a (probably non-exhaustive) list of features:

The platform maintains the financial balance of each member and entire organization and visualizes this information over time.

Per-member financial balance over time

It allows to register payments and to invoice member contributions as well as to automatically send payment reminder emails to all members whose debt has exceeded a certain threshold.

There is a news feed where users can create posts, like and unlike posts, comment with emojis and gifs, like and unlike comments as well as embed youtube videos and spotify players.

Interactive news feed with posts, comments, likes and upcoming events

There is an event management system, that allows the members to create events, vote for events that they would like to take part in, plan and manage these events with todo lists and finally publish them on the organizations public website.

Event management system for planning, publishing and promotion of cultural events

There is a built-in real-time chat for the members with emoji and gif browsers.

Realtime chat functionality

And then there is a bunch of other minor features such as dark mode a document management system for PDF files and I also translated the entire page into german and swiss-german dialect (because it's fun, nobody usually does that).

If I had to name one single best feature of the whole application though, it would be the passwordless login flow. The user just specifies their email, and if there is a user registered for that email, they receive a magic link that they can click and then they are logged in. Simple as that. It is a relatively recent addition that was motivated by countless requests of members of the organization forgetting their passwords and not being able to login. Moving to a passwordless login flow completely solved this issue for us.

Technology

Many of the features of this project would be worth separate blog posts, which I may write at some point when I find the time. Here i want to give a rough idea of what were the technical requirements regarding infrastructure and throughput, and then also what kind of technologies were used to implement this project.

Tech Stack

Here is how this technology roughly plays together. I will explain the individual parts in a bit more detail below.

Rough architecture diagram

Nginx: All inbound and outbound traffic is routed through an nginx container, which at the same time also serves the frontend code. It handles the SSL encryption and routes the api requests to the app server container. It also serves static resources such as images and other files. I configured it so that it can resize images on the fly when serving them, using the http image filter module

clike
1
location ~ "^/cdn/(?<image>.+)@(?<width>[0-9]*)$" {
2
alias /usr/src/app/storage/$image;
3
image_filter resize $width -;
4
image_filter_jpeg_quality 80;
5
image_filter_buffer 8M;
6
}

This allows the client for example to request images resized to a 400px by postfixing the resource URL with @400. I was very excited about this solution back in the day. Today I would probably rather use a service like Cloudinary, which allows to upload images and then it does the resizing on demand as well as placeholder images and all that good stuff for you.

App server: The app server is a standard NodeJS process running express and Apollo Server for the GraphQL protocol. It also accepts a WebSocket connection for pushing realtime updates from the server to the client. Since our users were seemingly unable to remember their login credentials, the project is now using a password-less login flow using emails with magic links. It's essentially like a password reset on every login. To send emails, the app server communicates with the Mailgun API.

Prisma: All database access is handled by a Prisma v1 server. The app server does not directly issue database queries; it uses the prisma client API to do this. This has the advantage that we get a nicely documented and structured ORM layer to interact with from our GraphQL resolvers. The newer serverless versions of Prisma are even nicer with their fantastic typescript support, but at the time when i built this project, version 2 was just announced and not yet stable. The prisma server also has a subscription API which allows to subscribe to certain database events, which was perfect to power the WebSocket based GraphQL subscriptions required for realtime features such as messaging.

Postgres: For the database PostgreSQL seems to always be a safe choice. It was also one of the databases supported by Prisma v1, so the choice was easy here.

Client: The member exclusive frontend is a standard React single page application (SPA). It's bundled with webpack and then served statically by Nginx. Since the entire frontend app is locked behind a login, this seemed like a reasonable approach. It uses many widely used libraries such as material UI as a UI component library, react router for client side routing and Apollo client as a graphql client and remote data cache. For image optimization it uses a custom image component that measures the screen and requests images in quantized appropriate sizes from the nginx location described above.

Website: The public facing website is implemented using NextJS, a react framework for server side rendering and static site generation. The requirements here were very different from the member exclusive client, we needed good search engine optimization and load times, therefore a server side rendered app was the right approach here. Server rendering also allowed me to directly query the database using the Prisma client; there was no need to go through the GraphQL API to get the data for the pages. The visual design of this page was largely authored by a friend of mine, i'm not going to take credit for most of it.

The public facing website of the organization

The live version of this page can be visited on 3ab3.ch