commit e3c31484aee7e7e35ede8d3ebc1c77061d2e800f Author: Matthias Puchstein Date: Fri Nov 14 16:02:27 2025 +0100 added AGENTS.md diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..f04a40e --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,149 @@ +# StudIP Sync Agent + +## Goal + +Implement a command-line tool in Rust that performs a **one-way sync** of files from Stud.IP (JSON:API at Uni Trier) to the local filesystem. [web:68] +The local directory structure must be: `///`. [web:88] + +## Environment + +- Target OS: Linux (Arch, follow XDG base directory conventions). [web:135] +- Language: Rust (2021 edition). +- Use `reqwest` + `tokio` for HTTP and async, `serde` for JSON and TOML, and standard Rust CLI patterns. [web:111][web:131] +- Build as a single binary named `studip-sync`. + +## Code Quality: Formatting and Linting + +- Use `rustfmt` as the standard formatter for all Rust code; code must be kept `cargo fmt` clean. [web:144][web:148] +- Use `clippy` as the linter; the project must pass `cargo clippy --all-targets --all-features -- -D warnings` with no warnings. [web:144][web:149][web:159] +- Add a `rustfmt.toml` and (optionally) a `clippy.toml` where needed, but prefer default settings to stay idiomatic. [web:144][web:151] +- If CI is present, include steps that run `cargo fmt --all -- --check`, `cargo clippy --all-targets --all-features -- -D warnings`, and `cargo test`. [web:147][web:150][web:159] + +## API + +- Base URL: configurable, default `https://studip.uni-trier.de`. [web:68] +- JSON:API root: `/jsonapi.php/v1`. [web:68] +- Authentication: HTTP Basic (username/password), encoded once as base64 and stored in TOML config. [web:1][web:118] +- Use JSON:API routes such as: + - `GET /users/me` to resolve the current user and related links (courses, folders, file-refs). [web:68][web:106] + - `GET /users/{user_id}/courses` to list enrolled courses. [web:85] + - Course-specific routes for folders and documents/file-refs, using the documented JSON:API routes for Stud.IP (e.g. `/courses/{course_id}/documents`). [web:88][web:93] + +## Configuration (TOML, including paths) + +All configuration and state in this project must use **TOML**. [web:131] + +- Primary config file: XDG-compliant, e.g. `~/.config/studip-sync/config.toml`. [web:131][web:135] +- Example `config.toml` keys: + +``` + +base_url = "https://studip.uni-trier.de" +jsonapi_path = "/jsonapi.php/v1" + +# Authorization header value without the "Basic " prefix, base64("username:password"). + +basic_auth_b64 = "..." + +# Local base directory for synced files. + +download_root = "/home//StudIP" + +# Maximum concurrent HTTP downloads. + +max_concurrent_downloads = 3 + +``` + +- The `download_root` directory determines where the tool creates `semester/course/folders/files`. [web:68] +- The config file must be created with mode `0600` and never contain anything except necessary settings and the base64-encoded credential. [web:118][web:122] + +### Credentials and auth + +- On first run (or when running `studip-sync auth`), prompt interactively for username and password. [web:118] +- Construct `username:password`, base64-encode it, and store the result as `basic_auth_b64` in `config.toml`. [web:1][web:118] +- At runtime, send `Authorization: Basic ` on all JSON:API requests. [web:1][web:68] +- Never log or print the password, `basic_auth_b64`, or full `Authorization` header. [web:118][web:128] +- On HTTP `401` or `403` from a known-good endpoint like `/users/me`, treat this as auth failure: +- Non-interactive runs: exit with a non-zero code and a clear message asking the user to run `studip-sync auth`. [web:118] +- Interactive runs: optionally prompt again and update `basic_auth_b64`. + +## State (TOML as well) + +- State file must also be TOML, stored under XDG data dir, e.g. `~/.local/share/studip-sync/state.toml`. [web:131][web:135] +- State is non-secret cached data: + +``` + +user_id = "cbcee42edfea9232fecc3e414ef79d06" + +[semesters."ws2526"] +id = "830eb86ad41d8f695d016647d557218a" +title = "Wintersemester 2025/26" + +[semesters."ss25"] +id = "..." +title = "Sommersemester 2025" + +[courses."830eb86a-...-course-id"] +name = "Rechnerstrukturen - Übung" +semester_key = "ws2526" +last_sync = "2025-11-14T12:34:56Z" + +``` + +- The tool should: +- Cache `user_id` after the first successful `/users/me` call. [web:68][web:106] +- Cache semester IDs and human-readable keys (`ws2526`, `ss25`) after discovering them via JSON:API. [web:68] +- Optionally store course and last-sync metadata to reduce API calls (e.g. using `filter[since]` if supported). [web:88][web:93] + +## Directory structure + +- All downloads must go under `download_root`, respecting: + `download_root////`. +- `semester_key` is resolved from the state file (`ws2526`, `ss25`, etc.). [web:68] +- `course_name` and Stud.IP folder/file names should be normalized to safe filesystem paths (handle spaces, umlauts, and special characters) while staying human-readable. [web:68][web:104] + +## Sync semantics + +- One-way sync: Stud.IP → local filesystem only; never upload or modify data on Stud.IP. [web:68] +- Default behavior: +- Create directories and download new or changed files under `download_root`. +- Never delete local files by default. +- Provide optional flags: +- `--prune`: delete local files that no longer exist on Stud.IP. +- `--dry-run`: print planned actions (creates/downloads/deletes) without modifying the filesystem. + +## Minimizing API usage and load + +- Use cached `user_id` and semester mappings from `state.toml` to avoid repeated discovery calls. [web:68] +- When listing course documents, use JSON:API pagination and any available filters (e.g. `filter[since]`) supported by Stud.IP’s document routes. [web:88][web:93] +- Avoid re-downloading unchanged files by checking JSON:API attributes such as ID, size, and modification time against the stored state. [web:93][web:106] + +## CLI interface + +- Binary name: `studip-sync`. +- Subcommands: +- `studip-sync auth`: set or update credentials; writes `config.toml`. +- `studip-sync sync`: perform sync from Stud.IP to `download_root`. +- `studip-sync list-courses`: list known courses with semester keys and IDs from state (refreshing if needed). +- Use standard exit codes: +- `0` on success. +- Non-zero on errors (auth failure, network error, JSON parse error, filesystem failure). [web:118] + +## Performance & safety + +- Limit concurrent HTTP requests (configurable via `max_concurrent_downloads`, default 3). [web:68] +- Stream file downloads directly to disk; do not load entire files into memory. [web:88] +- Handle HTTP and I/O errors gracefully with clear messages and without panicking. +- Keep dependencies minimal and use idiomatic Rust project structuring for maintainability. [web:136][web:137] + +## Extensibility + +- Internally, separate concerns into modules: +- `config` (TOML load/save for config and state). +- `studip_client` (JSON:API HTTP client). +- `sync` (sync logic and directory mapping). +- `cli` (argument parsing, subcommands). [web:136][web:137] +- Represent core entities as Rust types: `Semester`, `Course`, `Folder`, `FileRef`. [web:68][web:93] +- Design so that a future `MoodleProvider` can implement the same internal traits (e.g. `LmsProvider`) without changing the CLI surface.