Skip to content

Vulpes Backend

A backend server for OneLiteFeather's Vulpes project, providing a REST API and database access.

Features

  • REST API for managing custom attributes, fonts, items, and notifications
  • OpenAPI documentation
  • Automatic Dart Dio client generation

OpenAPI and Dart Client Generation

This project automatically generates a Dart Dio client from the OpenAPI specification during the build process. The client is then pushed to a separate Git repository with the project version as a tag.

How it works

  1. The OpenAPI specification is generated during the build process using Micronaut's OpenAPI support.
  2. The OpenAPI Generator Gradle plugin is used to generate a Dart Dio client from the specification.
  3. The generated client is pushed to the vulpes-client repository with the project version as a tag.

Configuration

The OpenAPI Generator is configured in the build.gradle.kts file:

openApiGenerate {
    generatorName.set("dart-dio")
    inputSpec.set("$buildDir/tmp/kapt3/classes/main/META-INF/swagger/vulpes-backend-1.0.yml")
    outputDir.set("$buildDir/generated/dart-client")
    apiPackage.set("net.onelitefeather.vulpes.client.api")
    invokerPackage.set("net.onelitefeather.vulpes.client.invoker")
    modelPackage.set("net.onelitefeather.vulpes.client.model")
    configOptions.set(mapOf(
        "pubName" to "vulpes_client",
        "pubVersion" to (project.version as String),
        "pubDescription" to "Vulpes API Client",
        "pubAuthor" to "OneLiteFeather",
        "pubAuthorEmail" to "p.glanz@madfix.me",
        "pubHomepage" to "https://github.com/OneLiteFeatherNET/vulpes-client",
        "pubRepository" to "https://github.com/OneLiteFeatherNET/vulpes-client",
        "dateLibrary" to "core",
        "enumUnknownDefaultCase" to "true"
    ))
}

GitHub Actions

The GitHub Actions workflow is configured to run the client generation and repository pushing during the release process. The workflow uses a custom secret called CLIENT_REPO_TOKEN for authenticating with GitHub when pushing to the client repository.

To set up the CLIENT_REPO_TOKEN:

  1. Create a personal access token with the repo scope.
  2. Add the token as a secret in the repository settings with the name CLIENT_REPO_TOKEN.

Development

Prerequisites

  • Java 21
  • Gradle
  • Node.js (for semantic-release)

Building

./gradlew build

Running

./gradlew run

Testing

./gradlew test

Seed data for manual testing

The seed environment fills an empty database with realistic test data on startup, so every screen of the UI can be tested without creating data by hand. Start the local database (docker compose -f docker/compose.yml up -d) and run:

MICRONAUT_ENVIRONMENTS=local,seed ./gradlew run

Seeding only happens when the database contains no projects; otherwise it is skipped and the log says so. To throw away all data and seed again:

MICRONAUT_ENVIRONMENTS=local,seed VULPES_SEED_RESET=true ./gradlew run

Warning: a reset deletes all Vulpes data in the database, including projects you created yourself.

The data is the same on every run (VULPES_SEED_RANDOM_SEED changes the generated filler). It contains:

Project Purpose
eldoria_rpg — Eldoria RPG Fantasy RPG setup: enchanted weapons with coloured lore, icon fonts, quest sounds, custom attributes, notifications and a custom "Shadow Realm" dimension. Every entity type has children.
skyblock_lab — Skyblock Lab Smaller labor project. Shares one key per entity type with Eldoria (starter_sword, hud_icons, ui_click, lobby, bonus_health, welcome) to show project scoping.
edge_cases — Edge Cases More than one page per list, records without children, every enum value, validation min/max values, 255-character, Unicode and §-formatted texts, long lore/char lists for reordering, unsafe enchantments.
empty_project — Empty Project No entities at all, for empty states.

The seeder lives in src/dev and is only on the classpath of ./gradlew run; it is not part of the built jars or the Docker image.

Error handling

Every endpoint answers a failure with a single body shape, RFC 9457 Problem Details, served as application/problem+json:

{
  "type": "https://vulpes.onelitefeather.net/errors/resource-not-found",
  "title": "Resource not found",
  "status": 404,
  "detail": "Attribute not found.",
  "instance": "/project/6f1c.../attribute/update",
  "code": "RESOURCE_NOT_FOUND",
  "traceId": "4bf92f3577b34da6a3ce929d0e0e4736",
  "errors": []
}

This holds for framework errors too — unbindable path variables, malformed JSON, 405, 415 — because the shape is produced by an ErrorResponseProcessor, which is the hook every built-in Micronaut handler routes its body through.

For clients

  • Branch and localize on code, never on detail. The codes are the ErrorCode enum and reach the generated Dart client as an enum; detail is English prose and may be reworded at any time.
  • On VALIDATION_FAILED, errors lists the rejected fields as {field, code, message}, where field is the request property path (displayName), so a form can mark the matching input.
  • Show traceId in support dialogs. It is the OpenTelemetry trace id when tracing is enabled, and always identifies the matching server log line.

For contributors

  • Raise failures with ApiException; the status, title and problem type come from the ErrorCode you pass.
  • The message you pass is the response body. Author it at the throw site from data the caller already sent us. Never forward a message from JDBC, Hibernate or any other lower layer — those carry table names, column names and SQL fragments (CWE-209). Details for 5xx are a fixed constant for the same reason.
  • When the honest reason differs from what the caller may learn — a cross-project access, for instance — pass it as internalDetail. It is logged and never serialized.

License

This project is licensed under the AGPL-3.0 License - see the LICENSE file for details.

About

Backend services for the Vulpes service

Topics

Resources

Code of conduct

Contributing

Security policy

Stars

0 stars

Watchers

2 watching

Forks

Releases

Sponsor this project

Packages

Contributors

Languages