Code

This section is to support people who want to contribute to the source code.

Building software means taking a stand, making decisions, choices: you can read our choices on the homepage

To help with issues you can check for Contibution welcome, good first issue or help needed tags.

Please use clean, concise titles for your pull requests. Unless the pull request is about refactoring code or other internal tasks, assume that the person reading the pull request title is not a programmer but an user and try to describe your change or fix from their perspective. We use commit squashing, so the final commit in the main branch will carry the title of the pull request, and commits from the main branch are fed into the changelog. Please use conventional commits in your commit messages.
Please make forks you want to merge upstream public and possibly allow commits from upstream members when requesting a merge request.

  1. API / Backend
    1. Access query parameters
    2. Access route api params
    3. Access Request Headers
    4. Access body
    5. Set header
    6. Access global settings
    7. return errors
    8. Getting random values
    9. logs
    10. Send a raw file
  2. Authentication
    1. Require an admin user
    2. Get current logged user
    3. Require a logged user
    4. Documenting API
  3. Task Manager / Queue scheduling
    1. Sending E-mail
  4. FrontEnd
    1. Settings
    2. Mobile first
    3. Composable
    4. Call API
    5. Notify user
  5. access/ Authorization / validate / user / role

API / Backend

In Nuxt3, API endpoints and server routes are defined inside the /server directory. For more details, see the Nuxt Server Directory documentation. Nuxt3 uses Nitro as the server engine, which automatically scans the /server directory for API and route files. Server code runs only on the backend and can securely access environment variables, the database and other server-side resources.

There are two main ways to create backend logic:

  • API Routes:
    Place files inside /server/api/. Each file (e.g. server/api/utils/ping.ts automatically becomes an endpoint (/api/utils/ping). The filename and extension determine the HTTP method (.get.ts, .post.ts, etc.).

  • Custom Server Routes:
    For more advanced routing, place files in /server/routes/. The filename and folder structure map directly to the route path. You can use any HTTP method and have full control over the request/response.

Access query parameters

You can use event.context.params?.query or getQuery(event)

example

Access route api params

You can use event.context.params?.name or getRouterParam(event, 'name') or `const { name } = getRouterParams(event)

Access Request Headers

getHeaders(event)

Access body

This is needed to access POST or PUT HTTP method.

readBody(event)

Set header

setHeader('Content-Type', 'application/json')

Access global settings

You can use the publicSettings or secretSettings server utilities

return errors

throw createError({ status: 404, message: 'Not found' })

Getting random values

crypto.randomUUID()

logs

const log = useLogger('context')

log.info / warning / error / debug

Send a raw file

return sendStream(event, fs.createReadStream(filePath))

The event card fallback image is an example => server/routes/fallbackimage.png.ts

Authentication

There are some utilities so manage authentication via cookies and via JWT token.

Require an admin user

await requireAdminSession(event)

Get current logged user

const { user } = await getUser(event)

Require a logged user

const { user } = await requireUserSession(event)

Documenting API

As you can see in the API page, there are some documented API. This is done via the scripts/gendocs.js (its quite an hack because of jsdoc/tsdoc) that parse some files in server/api to extract the documentation and use the scripts/api_template.hbs template to create the docs/api.md source that jekyll will parse.

The HTTP method and the API routes are calculated from file path.

/**
 * Login via JWT
 * To interact with some API you'll need an `access_token` you'll get from this call
 * @param {string} email
 * @param {string} password
 * @return A 401 HTTP Status in case of bad credentials
 * @auth
 * @see #234
 * @example
 * ```js
 * const auth = { email: 'admin', password: 'my_super_secret_password' }
 * fetch("http://localhost:4000/api/login/token", {
 *   "headers": { "content-type": "application/json" },
 *   "body": auth,
 *   "method": "POST"
 * })
 * ```
 */

Task Manager / Queue scheduling

The Task Manager is an utility for scheduling and running background tasks. It is used to automate recurring maintenance and notification jobs without blocking the main application flow.
We initially evaluated several existing solutions, including Redis-based queue managers like Bull and job schedulers like Croner, however, Redis’s licensing model at the time was incompatible and we want to avoid adding another dependency.

We also reviewed nightcoral, a lightweight and well-designed task queue but while the codebase was impressive we realized we could achieve similar functionalities by extending our existing TaskManager implementation adding exception management and customizable retries on failure.

What it’s used for:

  • Cleaning up unused / orphan resources (tags, places, AP actors - running once a day)
  • Creating new instances of recurring events (running each half an hour)
  • Removing past federated events and related resources (twice a day)
  • Sending ActivityPub notifications
  • Sending e-mails

What we will use it for:

  • Sending daily or periodic notifications
  • Synchronizing federated profiles

How it works:

  • Tasks are defined as instances of the Task class, specifying a name, method to execute, repeat interval, and other options.
  • The TaskManager manages a queue of these tasks, running them at the specified intervals.
  • Tasks can be set to repeat, run once, retry on failure.

Example: Adding a new task

First of all you’ll need the utils useTaskManager: this will expose you the singleton instance of the TaskManager.
To add a new task, you can use the add method of the TaskManager. For example, the code that enqueue an email is:

  useTaskManager().add({
    name: 'MAIL',
    method: sendMail,
    args: [addresses, template, locals, locale, bcc],
  })

Task options:

  • name: String, name of the task (used in logs)
  • method: Function to execute
  • args: Array, arguments to pass to the method (optional)
  • repeat: Boolean, whether the task should repeat (default: false)
  • repeatDelay: Number, interval in seconds between executions (default: 1)
  • callAtStart: Boolean, whether to run the task immediately when added (default: false)
  • retries: Array, delays in seconds for retrying failed tasks (default: [10, 30])

Sending E-mail

To send emails in the background, use the enqueueMail utility. This schedules the email to be sent as a task, ensuring that email delivery does not block the main application flow and retrying in case of failure. For example:

enqueueMail(
  'user@example.com',      // recipient addresses (string or array)
  'register',              // template name (template files are in `/server/emails`)
  { username: 'Alice' },   // template variables (optional)
  'en',                    // locale (optional)
  false                    // use BCC for recipient (optional)
)

This will add a mail-sending task to the [[Task Manager]] queue. The actual sending is handled asynchronously, and you can customize the template and variables as needed. Email templates are located in the /server/emails/ directory. The content of these emails is localized, with translations and language-specific strings stored in i18n/locales/email.


FrontEnd

Settings

const settings = useState<Settings>('settings')

Mobile first

Use :fullscreen="$vuetify.display?.mobile" for any v-dialog
NOTE: (the issue with this is that $vuetify.display is ready on client only so we do have hydration issues (i.e. i want the calendar to have 2 cols only not in mobile, this could not be done in SSR, let’s test with https://masteringnuxt.com/blog/mobile-detection-with-nuxt-ssr)

Composable

we use composables for utilities like validators, times formatter, events management. to share state in composable use useState

Call API

use useFetch if you need SSR (i.e. loading events at home page). internally it calls the method only once in SSR and pass the payload to the client that has not to re-fetch the data. useFetch has an internal key that uses to refresh the request.

use $fetch for data needed in client only (i.e. a v-combobox with autocompletion)

Notify user

$notify('message')

the message could be a translated string.

access/ Authorization / validate / user / role

// this is a middleware that redirect to login page when user is not logged in definePageMeta({ middleware: [‘authenticated’] })

const { loggedIn, session, user, clear, fetch } = useUserSession() const { isAdmin } = useAuth()