The NECST lab shares a short.io account, but sharing an API key is not a workable interface for a team. It gives everyone the same level of access, leaves little local context about who created a link, and makes naming conventions dependent on people remembering them.
Brevis is the internal layer I built around that account. It gives the lab a guided way to create and find short links while keeping the short.io credentials on the server. The local database records ownership and the information needed to manage each link, while short.io remains responsible for redirects.
Links with a structure
Instead of asking everyone to invent a slug, Brevis asks them to choose a link type and fill in its fields. A publication link, event link or teaching resource can each have its own fields and slug template.
Those types are data, not hard-coded forms. An administrator can define a new type, its validation rules and its template from the admin area. The same type engine powers interactive creation and spreadsheet imports, so both paths apply the same formatting and validation rules.
Ownership and conflicts
Every link has a creator. Search is shared across the team, but editing is not: people manage their own links and administrators can manage all of them.
When a requested slug already exists, Brevis identifies the conflict and proposes an alternative. Owners can deliberately overwrite their own links, while an administrator can resolve conflicts across the account. This turns a remote API error into a decision with enough context to make it safely.
The app also separates local search metadata from the tags sent to short.io. Closed-vocabulary fields can be synchronised for filtering, while free-text values stay inside Brevis. That keeps useful search context without sending arbitrary internal text to an external service.
Bulk import without blind writes
The bulk workflow accepts a spreadsheet, validates every row and shows errors in the browser before creating anything. Invalid rows can be corrected inline, and no request reaches short.io until the user confirms the reviewed batch.
Importing follows the same ownership, conflict and slug-generation rules as the single-link form. There is one model for creating links rather than a separate, more permissive path hidden behind a file upload.
Access built for an internal tool
Regular users sign in with a one-time code sent by email, so the application does not add another password for staff to manage. Registration is restricted to approved email domains, and a new account remains inactive until an administrator approves it. Superusers retain password access as a recovery path if email is unavailable.
The application runs behind a trusted proxy and restores the real client address before authentication rate limits are evaluated. Without that boundary, every person in the lab would appear to come from the proxy and could share one rate limit by accident.
Stack
- Django 5 on Python, with server-rendered templates and gunicorn in production.
- PostgreSQL 16 for users, link ownership, configurable link types and import history.
- Server-rendered Django templates with JavaScript and SweetAlert2 for browser interactions and confirmation flows.
- Tailwind CSS for the interface, compiled as part of the container build.
- The short.io API for publishing and updating redirects. Its API key never reaches the browser.
- Docker Compose for the application, database and reverse proxy.
- Caddy for automatic TLS and the HTTPS reverse proxy.
How deployment works
Every push to main starts a two-stage GitHub Actions pipeline. A hosted runner
builds the Docker image, runs the complete Django test suite inside that exact
image, and publishes it to GitHub Container Registry only if the tests pass. The
artifact is tagged with the commit SHA, so a deployment always identifies the
source revision it is running.
The deployment job then runs on a self-hosted runner attached to the production VM. It copies only the Compose and Caddy configuration, pulls the image that passed CI, and recreates the services without building on the server. Persistent configuration and database volumes live outside the runner workspace, so a new checkout cannot replace production state.
Caddy terminates TLS and is the only public entry point. Gunicorn is exposed only to the proxy network, while PostgreSQL sits on a separate internal Docker network with no external gateway. The proxy cannot reach the database directly.
Private by design
There is no repository or live-demo link on this page. Brevis is an operational tool for the lab, and its useful public story is the engineering behind it, not access to the team account it protects.