Signed-off-by: NotAShelf <raf@notashelf.dev> Change-Id: I2d3a5a9d745630f5da305664f0bdc66e6a6a6964
276 lines
9 KiB
Markdown
276 lines
9 KiB
Markdown
# Hacking Pinakes
|
||
|
||
Pinakes is a lot of things. It also _plans_ to be a lot of things. One of those
|
||
things, as you might have noticed, is _complete_. To be complete in features, to
|
||
be complete in documentation and to be complete in hackability. This document
|
||
covers, very comprehensively, how you may:
|
||
|
||
- Develop Pinakes
|
||
- Build Pinakes
|
||
- Contribute to Pinakes
|
||
|
||
for developers as well as:
|
||
|
||
- Distribute Pinakes
|
||
|
||
for Pinakes maintainers and packagers.
|
||
|
||
## Development Environment
|
||
|
||
[Nix]: https://nixos.org
|
||
[Direnv]: https://direnv.net
|
||
|
||
Pinakes uses [Nix] with flakes for pure, reproducible developer environments.
|
||
All of the build dependencies that you need, from Rust to GTK and webkit, are
|
||
provided in the devshell. In addition to _not_ requiring system libraries, we'd
|
||
like to _actively discourage you_ from relying on system libraries.
|
||
|
||
[Direnv] may be preferred by some users as an alternative to `nix develop`.
|
||
|
||
```bash
|
||
# Enter the dev shell
|
||
$ nix develop
|
||
|
||
# Or with direnv (recommended)
|
||
$ direnv allow
|
||
```
|
||
|
||
Nix is the only supported way to get all dependencies. Distro package managers
|
||
_may_ work for the core crates but are not supported for the UI.
|
||
|
||
## Building Pinakes
|
||
|
||
Pinakes is a Cargo workspace with multiple crates targeting multiple aspects of
|
||
the projects. The user-facing crates are provided in `packages/*`.
|
||
`pinakes-server` and `pinakes-tui` can be built normally with `cargo` while the
|
||
UI crate, i.e., `pinakes-ui` **must** be built with the Dioxus CLI (`dx`) so
|
||
that the SCSS stylesheets are compiled correctly. Everything else should work
|
||
perfectly fine with the standard `cargo build`.
|
||
|
||
[Just]: https://just.systems
|
||
|
||
To make the things a _little_ bit more convenient, this project uses [Just] as
|
||
its command runner and provides a few recipes for common tasks.
|
||
|
||
```bash
|
||
# Build all crates (server + TUI + UI)
|
||
$ just build-all
|
||
|
||
# Build individual components
|
||
$ just build-server # pinakes-server (HTTP API)
|
||
$ just build-tui # pinakes-tui (terminal UI)
|
||
$ just build-ui # pinakes-ui (Dioxus desktop/web UI, requires dx)
|
||
```
|
||
|
||
The dependencies required for the recipes, including Just itself, is provided by
|
||
the Nix devshell. You may also run the cargo equivalents of those commands:
|
||
|
||
```bash
|
||
# Core crates
|
||
$ cargo build -p pinakes-server
|
||
$ cargo build -p pinakes-tui
|
||
|
||
# The UI crate must be built with dx, not cargo.
|
||
$ dx build -p pinakes-ui
|
||
```
|
||
|
||
Release builds follow the same pattern, but with `--release`. Nothing else
|
||
should be required for the most part.
|
||
|
||
## Running Pinakes
|
||
|
||
For the time being, UI clients require the server to be running before they can
|
||
function. Offline sync is planned, but likely come later alongside cross-device
|
||
sync. All UIs connect to the server over HTTP.
|
||
|
||
```bash
|
||
# 1. Copy and edit the example config (first time only)
|
||
$ cp pinakes.example.toml pinakes.toml
|
||
|
||
# 2. Start the server (defaults to 127.0.0.1:3000)
|
||
$ cargo run -p pinakes-server -- pinakes.toml
|
||
|
||
# 3a. Run the TUI client
|
||
$ cargo run -p pinakes-tui
|
||
|
||
# 3b. Run the desktop/web UI
|
||
$ cargo run -p pinakes-ui
|
||
|
||
# Connect TUI to a non-default server address
|
||
$ cargo run -p pinakes-tui -- --server http://localhost:3000
|
||
```
|
||
|
||
The GUI reads `PINAKES_SERVER_URL` if set; otherwise it assumes
|
||
`http://127.0.0.1:3000`. You may change the host and port if you require.
|
||
|
||
## Testing
|
||
|
||
[cargo-nextest]: https://nexte.st
|
||
|
||
Pinakes boasts, or well, attempts to boast a very comprehensive testing suite to
|
||
ensure no regressions are introduced while in development. For fast parallel
|
||
test execution, we use [cargo-nextest] and while you are recommended to use it
|
||
over `cargo test` both should be supported. For CI and package tests, only
|
||
`cargo-nextest` is supported.
|
||
|
||
```bash
|
||
# Run all workspace tests (preferred)
|
||
$ just test
|
||
|
||
# Equivalent without Just
|
||
$ cargo nextest run --workspace
|
||
|
||
# Tests for a single crate
|
||
$ cargo nextest run -p pinakes-core
|
||
$ cargo nextest run -p pinakes-server
|
||
|
||
# Run a specific test by name and show output
|
||
$ cargo test -p pinakes-core -- test_name --nocapture
|
||
```
|
||
|
||
## Linting and Formatting
|
||
|
||
### Formatting
|
||
|
||
There is a treewide formatter provided within the `flake.nix` that invokes the
|
||
recommended formatter tooling across the various filetypes, including database
|
||
migrations and Markdown sources. While a `just fmt` recipe is provided, it will
|
||
not cover most of the files. Still, you may format the Rust sources as such:
|
||
|
||
```bash
|
||
# Format all code
|
||
$ just fmt # or: cargo fmt
|
||
|
||
# Check formatting without modifying files
|
||
$ cargo fmt --check
|
||
```
|
||
|
||
### Linting
|
||
|
||
For now lints only cover Rust sources, and are provided by the Clippy tool. You
|
||
may, as usual, invoke it with `just lint`.
|
||
|
||
```bash
|
||
# Run Clippy
|
||
$ just lint # or: cargo clippy --workspace
|
||
|
||
# Treat Clippy warnings as errors (used in CI)
|
||
$ cargo clippy --workspace -- -D warnings # `-D warnings` is recommended
|
||
```
|
||
|
||
All Clippy warnings, besides some of the really annoying ones or false
|
||
positives, must be resolved at the source. **Do not** use `#[allow(...)]` or
|
||
`#[expect(...)]` to silence warnings unless there is a documented reason.
|
||
|
||
It may be okay, at times, to suppress lints but you should focus on _resolving_
|
||
them rather than suppressing them. _If_ suppressing, prefer `#[expect]` and
|
||
provide the `reason` parameter.
|
||
|
||
## Generating API Documentation
|
||
|
||
The REST API documentation is generated from OpenAPI annotations in
|
||
`pinakes-server` using `cargo xtask`:
|
||
|
||
```bash
|
||
# Generate the API documentation at docs/api/
|
||
$ just docs # or: cargo xtask docs
|
||
```
|
||
|
||
This writes:
|
||
|
||
- `docs/api.md` - Index linking all generated files
|
||
- `docs/api/<tag>.md` - One Markdown file per API tag
|
||
- `docs/api/openapi.json` - Full OpenAPI 3.0 specification
|
||
|
||
Please do not edit files under `docs/api/` by hand; they are regenerated on each
|
||
run.
|
||
|
||
## Code Style
|
||
|
||
There are many conventions that are in play within the Pinakes codebase. You'll
|
||
get used and adjust to most of them naturally, but here are some of the general
|
||
conventions written down for convenience.
|
||
|
||
### General
|
||
|
||
1. **Make illegal states unrepresentable**; ıse ADTs and newtype wrappers to
|
||
encode constraints in the type system. Parse and validate at system
|
||
boundaries (user input, external APIs); trust internal types everywhere else.
|
||
2. This one is kind of obvious, but please use the `tracing` crate with
|
||
structured fields. Do not use `println!` or `eprintln!` in library or server
|
||
code.
|
||
3. All storage operations must be implemented for both the SQLite and PostgreSQL
|
||
backends. Neither backend is optional.
|
||
|
||
Naming is kind of obvious, but I'd like to remind you that we use `snake_case`
|
||
for functions and variables, `PascalCase` for types and traits,
|
||
`SCREAMING_SNAKE_CASE` for constants. There is not much else to it.
|
||
|
||
### Error Handling
|
||
|
||
- **`unwrap()` and `expect()` are banned** in non-test code. Use `?`,
|
||
`ok_or_else`, `match`, or `if let` instead.
|
||
- The only accepted exception is when a failure is truly impossible (e.g., a
|
||
hardcoded regex that is known-valid at compile time). In that case, add
|
||
`#[expect(clippy::expect_used, reason = "...")]` with a clear explanation.
|
||
- Wrap storage errors with the `db_ctx` helper so error messages include the
|
||
operation and relevant ID:
|
||
|
||
```rust
|
||
stmt.execute(params).map_err(db_ctx("insert_media", &media_id))?;
|
||
```
|
||
|
||
### Disallowed Types and APIs
|
||
|
||
Pinakes uses hashers from `rustc_hash`, i.e., `FxHashMap` and `FxHashSet` over
|
||
the `std` equivalents due to their faster nature without random state. This is
|
||
enforced by clippy.
|
||
|
||
As Pinakes uses Rust 1.90+, the now-deprecated `once_cell` crate is also banned.
|
||
Use the stdlib equivalents:
|
||
|
||
- `once_cell::unsync::OnceCell` -> `std::cell::OnceCell`
|
||
- `once_cell::sync::OnceCell` -> `std::sync::OnceLock`
|
||
- `once_cell::unsync::Lazy` -> `std::cell::LazyCell`
|
||
- `once_cell::sync::Lazy` -> `std::sync::LazyLock`
|
||
|
||
## Contributing
|
||
|
||
1. Fork the repository and create a feature branch.
|
||
2. Enter the dev shell: `nix develop` (or `direnv allow`).
|
||
3. Make your changes; keep commits focused and descriptive. We use **scoped
|
||
commits**.
|
||
4. Run `just fmt` and `just lint` before opening a pull request.
|
||
5. Run `just test` to verify nothing is broken.
|
||
6. Open a pull request against `main`. Describe what you changed and why.
|
||
|
||
For significant changes, consider opening an issue first to discuss the
|
||
approach.
|
||
|
||
## Packaging and Distribution
|
||
|
||
Pinakes is released under EUPL v1.2. For distributable release builds, please
|
||
respect the license. You may create release builds for the user-facing crates
|
||
using `cargo build --release` for server and TUI components. Use `dx` instead
|
||
for the GUI:
|
||
|
||
```bash
|
||
# Server
|
||
$ cargo build --release -p pinakes-server
|
||
|
||
# TUI
|
||
$ cargo build --release -p pinakes-tui
|
||
|
||
# GUI
|
||
$ dx build --release -p pinakes-ui
|
||
```
|
||
|
||
The resulting binaries are self-contained except for system libraries required
|
||
by the UI (GTK3, libsoup, webkit2gtk). Database migrations run automatically on
|
||
first server startup via the `refinery` crate; no manual migration step is
|
||
needed.
|
||
|
||
SQLite (the default backend) requires no external database server. PostgreSQL
|
||
deployments need the `pg_trgm` extension enabled on the target database. It is
|
||
generally advisable to document those requirements for the users, even though
|
||
they do not require additional packaging steps.
|