Archify Sync server, Dockerfile and Helm chart

Rust server (axum, yrs, sqlx/PostgreSQL): live shared models over the y-websocket protocol, versions, browser sign-in with PKCE, invites, single sign-on through OIDC, metrics. Docker image on distroless, compose file, and a Helm chart with one pod, optional bundled PostgreSQL and secrets from existing Secrets.
This commit is contained in:
root-at-skic committed 2026-11-06 12:06:59 +02:00
commit 5c43b90ef8
67 files changed
+8362

No files matched your search

+12
View File
@@ -0,0 +1,12 @@
[package]
name = "archify-sync-protocol"
version.workspace = true
edition.workspace = true
license.workspace = true
repository.workspace = true
[dependencies]
chrono = { version = "0.4.45", features = ["serde"] }
serde = { version = "1.0.229", features = ["derive"] }
ts-rs = { version = "12.0.1", features = ["uuid-impl", "chrono-impl"] }
uuid = { version = "1.29.0", features = ["serde", "v7"] }
+17
View File
@@ -0,0 +1,17 @@
$schema: 'https://moonrepo.dev/schemas/project.json'
language: rust
tasks:
check:
command: cargo clippy -p archify-sync-protocol --all-targets -- -D warnings
inputs: ['src/**/*', 'Cargo.toml']
test:
command: cargo test -p archify-sync-protocol
inputs: ['src/**/*', 'Cargo.toml']
format:
command: cargo fmt -p archify-sync-protocol --check
inputs: ['src/**/*']
# Writes the TypeScript types to packages/protocol/src/generated.
types:
command: cargo test -p archify-sync-protocol export_bindings
inputs: ['src/**/*']
outputs: ['/packages/protocol/src/generated/**/*']
+205
View File
@@ -0,0 +1,205 @@
//! What the sync server and the extension agree on: the shape of a space's
//! CRDT document and the HTTP API's JSON. The types are exported to
//! TypeScript (`packages/protocol`) with `cargo test -p archify-sync-protocol`.
use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
use ts_rs::TS;
use uuid::Uuid;
/// Version of the wire protocol. A client sends it as `?v=` on the WebSocket
/// and the server refuses a version it does not speak.
pub const PROTOCOL_VERSION: u32 = 1;
/// Root map of a space's Yjs document: model-root-relative POSIX path → `Y.Text`.
pub const FILES_MAP: &str = "files";
/// Error body of every failed request.
#[derive(Debug, Clone, Serialize, Deserialize, TS)]
#[ts(export)]
pub struct ApiError {
/// Stable code for clients (`unauthorized`, `not_found`, `invalid`, `conflict`, …).
pub error: String,
pub message: String,
}
// ---------------------------------------------------------------- accounts
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, TS)]
#[serde(rename_all = "lowercase")]
#[ts(export)]
pub enum OrgRole {
Admin,
Member,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, TS)]
#[serde(rename_all = "lowercase")]
#[ts(export)]
pub enum SpaceRole {
Owner,
Editor,
}
#[derive(Debug, Clone, Serialize, Deserialize, TS)]
#[ts(export)]
pub struct Account {
pub id: Uuid,
pub organisation_id: Uuid,
pub name: String,
pub display_name: String,
pub role: OrgRole,
}
/// `POST /v1/setup`: the first admin, with the code the server printed on its first start.
#[derive(Debug, Clone, Serialize, Deserialize, TS)]
#[ts(export)]
pub struct SetupRequest {
pub code: String,
pub organisation: String,
pub name: String,
pub display_name: Option<String>,
pub password: String,
}
/// `POST /v1/auth/token`: the code from the browser sign-in, or a refresh token.
#[derive(Debug, Clone, Serialize, Deserialize, TS)]
#[serde(tag = "grant_type", rename_all = "snake_case")]
#[ts(export)]
pub enum TokenRequest {
AuthorizationCode {
code: String,
code_verifier: String,
redirect_uri: String,
},
RefreshToken {
refresh_token: String,
},
}
#[derive(Debug, Clone, Serialize, Deserialize, TS)]
#[ts(export)]
pub struct TokenResponse {
pub access_token: String,
/// Seconds until the access token expires.
pub expires_in: u64,
/// Single use: every refresh returns a new one, and reusing an old one signs the session out.
pub refresh_token: String,
pub account: Account,
}
#[derive(Debug, Clone, Serialize, Deserialize, TS)]
#[ts(export)]
pub struct LogoutRequest {
pub refresh_token: String,
}
// ------------------------------------------------------------------ spaces
#[derive(Debug, Clone, Serialize, Deserialize, TS)]
#[ts(export)]
pub struct Space {
pub id: Uuid,
pub name: String,
pub role: SpaceRole,
pub created_at: DateTime<Utc>,
}
#[derive(Debug, Clone, Serialize, Deserialize, TS)]
#[ts(export)]
pub struct CreateSpaceRequest {
pub name: String,
}
#[derive(Debug, Clone, Serialize, Deserialize, TS)]
#[ts(export)]
pub struct Member {
pub account_id: Uuid,
pub name: String,
pub display_name: String,
pub role: SpaceRole,
}
#[derive(Debug, Clone, Serialize, Deserialize, TS)]
#[ts(export)]
pub struct CreateInviteRequest {
/// Hours until the link stops working; the server's default when absent.
pub expires_in_hours: Option<u32>,
pub max_uses: Option<u32>,
}
#[derive(Debug, Clone, Serialize, Deserialize, TS)]
#[ts(export)]
pub struct Invite {
/// Shown once; the server keeps only its hash.
pub token: String,
/// The link to send: opens the server's join page.
pub url: String,
pub expires_at: DateTime<Utc>,
}
#[derive(Debug, Clone, Serialize, Deserialize, TS)]
#[ts(export)]
pub struct JoinResponse {
pub space: Space,
}
// ---------------------------------------------------------------- versions
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, TS)]
#[serde(rename_all = "lowercase")]
#[ts(export)]
pub enum VersionKind {
/// Taken by the server after a pause in editing or when the last editor leaves.
Snapshot,
/// Marked by a person as an official version.
Publish,
/// A restore of an earlier version; history is never rewritten.
Restore,
}
#[derive(Debug, Clone, Serialize, Deserialize, TS)]
#[ts(export)]
pub struct Version {
pub id: i64,
pub major: i32,
pub minor: i32,
pub kind: VersionKind,
pub name: Option<String>,
pub created_by: Option<Uuid>,
pub created_at: DateTime<Utc>,
/// For a restore: the version it restored.
pub restored_from: Option<i64>,
}
#[derive(Debug, Clone, Serialize, Deserialize, TS)]
#[ts(export)]
pub struct PublishRequest {
pub name: String,
}
/// The model files of a version as text, for comparing and showing.
#[derive(Debug, Clone, Serialize, Deserialize, TS)]
#[ts(export)]
pub struct VersionFiles {
pub version: Version,
pub files: Vec<VersionFile>,
}
#[derive(Debug, Clone, Serialize, Deserialize, TS)]
#[ts(export)]
pub struct VersionFile {
pub path: String,
pub text: String,
}
/// `GET /v1/info`: what a client needs before signing in.
#[derive(Debug, Clone, Serialize, Deserialize, TS)]
#[ts(export)]
pub struct ServerInfo {
pub name: String,
pub version: String,
pub protocol: u32,
/// False until the first admin has used the first-start code.
pub set_up: bool,
}