radix is the website and the decision and membership database of GEWIS - GEmeenschap van Wiskunde en Informatica Studenten - in a single application. It succeeds GEWISWEB and GEWISDB, which were separate applications until they were merged here.
Two things that were always about the same association, and were always kept in step by hand, now live in one codebase:
- The register. Every meeting, decision, member, body and mailing list of the association. This is the ledger: the authoritative record of what GEWIS decided and who its members are, written by the board and read by every other GEWIS system through the API.
- The website. Activities, photos, course documents, custom pages, and the careers portal, for members and for the world.
They meet in the middle. The website shows the association's decisions, the bodies that exist because of them, and the members installed in those bodies; it now reads them from the same application that records them, instead of copying them across an HTTP boundary twice an hour.
That leaves radix with two databases, and the split matters for anything that touches data:
- PostgreSQL holds the ledger - the editable source of truth, behind the
defaultconnection. - MariaDB holds everything else, behind the
webconnection: the website's own data, and the projection of the ledger that the site and the API read from.
The website provides its members and other visitors with lots of functionality:
-
Activities:
- Create activities with a wide range of options for sign-up lists.
- Enables members to sign up for various events and activities, enhancing engagement and participation.
-
Career:
- Allows companies that collaborate with GEWIS to publish job vacancies and opportunities.
- Facilitates connections between students and potential employers, aiding in career development.
-
Decisions:
- Provides a platform for members to view and interact with decisions and meetings.
- Ensures transparency and member involvement in the decision-making process.
-
Education:
- Offers an extensive archive of course documents, including exams and summaries.
- Serves as a valuable resource for students looking to study or review past materials.
-
Pages:
- Custom pages created by the board to provide dynamic content.
- Allows for flexible and timely updates to information and announcements.
-
Photos:
- Maintains a comprehensive photo archive of the numerous activities organised by GEWIS.
- Helps preserve and share memories of events and gatherings with the community.
The decision and membership database provides the board and other GEWIS systems with lots of functionality:
-
Management of Decisions:
- Organise and manage various types of meetings.
- Handle a range of decisions, from financial budgets and statements to the installation of members in various organs, along with customisable decisions.
- While decisions can be altered to reflect changes, they remain more or less immutable to maintain historical accuracy.
-
Management of Memberships:
- The join page, which join.gewis.nl redirects onto, facilitates new memberships and can automatically collect membership fees through Stripe.
- Validation of student information ensures that all member information is accurate.
- Allows for detailed and precise editing of member information.
-
Checker Module:
- Ensures that the database remains in a consistent state by enforcing many constraints derived from the Articles of Association and Internal Regulations.
- For instance, it prevents members from being installed in an organ if their membership has expired, ensuring adherence to (regulatory) requirements.
-
API:
- Serves a consistent projection of the ledger, so decisions and membership information can be queried without touching the register itself.
- Used by most GEWIS systems as a single, reliable source of truth, ensuring consistency and accuracy across all systems.
- Covers members, bodies, board and body installations, keyholders, mailing lists, activities and photos, so applications no longer need AD or LDAP for them.
- Each token carries only the permissions it was given, down to individual properties of a member.
And there is plenty more!
radix is built on PHP and the Symfony framework. The Symfony framework provides a solid foundation for building scalable and maintainable web applications.
We recommend developing natively on a Linux machine or through WSL2 on Windows (note: Arch-based distributions are not recommended) with the PhpStorm IDE or another IDE with good support for PHP.
Alternatively, you can use GitHub Codespaces.
You will need at least:
dockeranddocker compose(make sure that you have enabled Buildkit)gitmake- A
.xlffile editor (e.g. POEdit)
PHP, Composer, and all other runtime tooling live inside the Docker image, no need to install them yourself.
It is possible to use rootless docker on many Linux systems. For this, install uidmap, ensure IP forwarding is enabled, run dockerd-rootless-setuptool.sh install and set the DOCKER_HOST variable in your profile (e.g. .bashrc). Note that a rootless daemon cannot bind a port below 1024, which is where the application (80) and Matomo (82) are published by default. Move them in your .env.local:
APP_PORT=8000
APP_URL=http://localhost:8000
MATOMO_PORT=8082
APP_URL is what the URLs in that file are built from, so the application is reached on http://localhost:8000/ and everywhere the documentation below says http://localhost/ you read that instead. Alternatively, allow the low ports on the host once with sudo sysctl net.ipv4.ip_unprivileged_port_start=80 (persist it in /etc/sysctl.d/) and leave the ports as they are.
To set up radix locally, follow these steps:
- Fork the repository.
- Clone your fork (
git clone git@github.com:{username}/radix.git). - Run
make startto build and serve the application (a.env.localwill be created for you; alter it to your needs). The first build may take 5-10 minutes. - Run
make seedto get some test data (migrations will run automatically). - Go to
http://localhost/in your browser and you are greeted with the GEWIS website. - Log in with membership number
8000and the passwordgewiswebgewis. - The register is part of the administration, under
http://localhost/en/admin/; the same sign-in reaches it, and member8000holds both the website's and the register's administrator roles.
During development, several other services are accessible on your local machine:
- phpMyAdmin - Management interface for MariaDB at
http://localhost:8080/. - pgAdmin - Management interface for PostgreSQL at
http://localhost:8081/. - MailPit - Email testing at
http://localhost:8025/. - Mailman - Mailing list management at
http://localhost:8021/(its REST API is on8020). - Listmonk - Newsletter management at
http://localhost:8022/. - RabbitMQ - Message broker management at
http://localhost:15672/. - Matomo - Analytics platform at
http://localhost:82/.
Production runs the image abc.docker-registry.gewis.nl/web/radix/app through Docker, deployed with Portainer.
compose.yaml is the production stack; compose.override.yaml is what turns it into the development one, and
docker compose up in a checkout loads both. Portainer names compose.yaml on its own, which is also what stops
compose merging the development override. Every variable is guarded with ${VAR:?...}, so one that Portainer's stack
environment is missing aborts the deploy rather than falling back to a development default. The single app container
runs the migrations on start.
Values that rarely differ between deployments are passed the same way, but with a default instead of a guard: the mail
display names, the pinned Stripe and Mailman API versions, the watermark tag, the TU/e subnets. config/services.yaml
defaults each of them as well, which is what a run outside the stack falls back to, so setting one in the stack
environment is all it takes to change it.
We welcome contributions from the community, especially GEWIS members! To contribute:
- Perform the steps from Installation.
- Create your feature of bug fix branch (
git switch -c feature/my-amazing-feature). - Commit your changes (
git commit -m 'feat: added my amazing feature'). NOTE: radix requires commits to be signed, see this GitHub article for more information on how to sign commits. - Push to the branch (
git push origin feature/my-amazing-feature). - Open a pull request.
Note
More detailed information on GEWIS' contribution guidelines, including conventions on branch names and commit messages, can be found in the contribution guidelines.
While developing, use these commonly used commands from the Makefile:
make bash- Shell into the FrankenPHPappcontainer.make sf c='...'- Run a Symfony console command inside the container (e.g.make sf c=check:database).make composer c='...'- Run a Composer command inside the container (e.g.make composer c=update).make translations- Extract translatable strings into the.xlffiles. Run this whenever you add or edit a user-facing string in PHP, Twig, or a form type.make lint/make lint-fix- Run PHP_CodeSniffer (or PHPCBF to autofix) against the project's coding standard.make lint-twig- Validate the Twig templates.make phpstan- Perform static analysis using PHPStan.make igor- Run Igor to validate the codebase for FrankenPHP's worker mode.make cc- Clear the cache and restart the worker.
For a complete list of available commands, run make help.
Tip
If you are using AI coding tools (Claude Code, Copilot, Cursor, ...), they will pick up AGENTS.md automatically. It documents architecture, conventions, and gotchas in more depth than this README. However, it is not only for AI coding tools, have a look too if you are interested.
The test suite runs with PHPUnit, inside the app container, against isolated copies of both databases:
make test-prepare- Build the test schemas and load the seed into them. Run this once, and again after a schema or fixture change; the tests roll back their own writes, so the seed survives a run.make test- Run the suite. Passc=to hand options to PHPUnit, for examplemake test c="--stop-on-failure".
Tests that need a database run against real PostgreSQL and MariaDB matching production rather than an in-memory substitute, because the schema uses more of both than a substitute can reproduce.
Beyond the tests, make sf c=check:database runs the consistency checks derived from the Articles of Association and
Internal Regulations over the register. It is worth running after anything that changes decisions or installations.
A general overview of important folders required for the functioning of the application:
./
├── assets # Front-end sources (Sass, TypeScript Stimulus controllers).
├── config # Global configuration files for the application.
├── data # Persistent private data-related files, such as cryptographic keys and logs.
├── docker # Docker-related files to construct the containers.
├── migrations # Doctrine migrations, one directory per database.
├── public # Publicly accessible files, including the entry point (index.php).
├── src # The application itself, grouped by the role each class plays.
├── templates # Twig templates.
├── tests # The test suite.
└── translations # The `.xlf` translation files.Within src, classes are grouped by what they do rather than by feature: Controller, Service, Entity,
Repository, Form, Twig, and so on. Each of those is then split by domain - Database for the ledger of what the
association decided and who its members are, Decision for the projection of it that the site and the API read,
Report for the machinery that keeps that projection level with the ledger, Checker for the consistency checks,
Activity, Career, Education, Frontpage and Photo for the website's own features, User for accounts and the
API, and Application for what is shared.
Some additional configuration needs to be done to set up the Stripe API:
- Create a restricted key on https://dashboard.stripe.com/test/apikeys and set it in the
STRIPE_SECRET_KEYenvironment variable (check.env.local.distfor the permissions to set) - Copy the publishable key from https://dashboard.stripe.com/test/apikeys and set it in the
STRIPE_PUBLISHABLE_KEYenvironment variable - Copy the webhook signing secret from the output of
make stripewebhooksecret - Create a product with a one-off price on https://dashboard.stripe.com/test/products?active=true and copy its price ID to
STRIPE_MEMBERSHIP_PRICE_ID
Tip: to reduce waiting time for checkout sessions to expire, you can speed up this process by invoking docker compose exec stripe stripe checkout sessions expire cs_test_fromcheckoutsessionstable.
This will enable cash payment and send the retry email.
Note: the links in the e-mails do not resolve in the development setup. Replace the host with http://localhost/ to follow them.
To experiment with the API, import the openapi.yaml file into your favourite REST client. That file is generated
from the application by make openapi and is never edited by hand; a running deployment serves the same document at
GET /api/docs.json.
A running deployment also serves Swagger UI at /api-docs, where you can paste a token
and try the endpoints.
Every endpoint is read with the token of an API principal, created under Users → API principals in the
administration, and only answers for the permissions that principal was given. Collections are paged: ?page= and
?itemsPerPage= (100 by default, 500 at most), with the totals in the response's meta.
Everything except the oldest member endpoints needs the contract version, as either
Accept: application/vnd.gewis.gewisdb+json;version=5.0.0 or X-Api-Version: 5.0.0.
Alternatively, you can use PowerShell, for example:
((Invoke-WebRequest -Uri http://localhost/api/organFunctions -Headers @{"Authorization" = "Bearer APITOKEN"; "Accept" = "application/vnd.gewis.gewisdb+json;version=4.3.3"}).Content | ConvertFrom-Json).data | Format-ListThis software is licensed under the GNU General Public License v3.0 (GPL-3.0), see LICENSE.