forked from github-mirrorer/taskchampion-sync-server
Compare commits
18 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| d206729d5e | |||
| bf19b76577 | |||
| 505dd3f442 | |||
| e10f3e6cfb | |||
| 213be852b8 | |||
| b57dd24d9e | |||
| a9cf67c8e2 | |||
| daf6855f14 | |||
| dbc9a6909b | |||
| 1ad9e344c7 | |||
| 3820a8deea | |||
| 2de70ac336 | |||
| c2b4c94fb5 | |||
| 0a317cd86d | |||
| 1b80398365 | |||
| a94be2649e | |||
| 624efa8b0d | |||
| ae9adf1572 |
13
.github/workflows/checks.yml
vendored
13
.github/workflows/checks.yml
vendored
@ -13,7 +13,7 @@ jobs:
|
||||
name: "Check & Clippy"
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v5
|
||||
|
||||
- name: Cache cargo registry
|
||||
uses: actions/cache@v4
|
||||
@ -48,7 +48,7 @@ jobs:
|
||||
name: "Rustdoc"
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v5
|
||||
|
||||
- name: Cache cargo registry
|
||||
uses: actions/cache@v4
|
||||
@ -96,7 +96,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
name: "Formatting"
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v5
|
||||
|
||||
- uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
@ -114,19 +114,18 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
name: "Cargo Semver Checks"
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v5
|
||||
- uses: obi1kenobi/cargo-semver-checks-action@v2
|
||||
with:
|
||||
# exclude the binary package from semver checks, since it is not published as a crate.
|
||||
# exclude postgres temporarily until it is released
|
||||
exclude: taskchampion-sync-server,taskchampion-sync-server-storage-postgres
|
||||
exclude: taskchampion-sync-server
|
||||
|
||||
mdbook:
|
||||
runs-on: ubuntu-latest
|
||||
name: "mdBook Documentation"
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v5
|
||||
|
||||
- name: Setup mdBook
|
||||
uses: peaceiris/actions-mdbook@v2
|
||||
|
||||
6
.github/workflows/publish-docs.yml
vendored
6
.github/workflows/publish-docs.yml
vendored
@ -2,8 +2,8 @@ name: docs
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
tags:
|
||||
- '*'
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
@ -13,7 +13,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v5
|
||||
|
||||
- name: Setup mdBook
|
||||
uses: peaceiris/actions-mdbook@v2
|
||||
|
||||
2
.github/workflows/rust-tests.yml
vendored
2
.github/workflows/rust-tests.yml
vendored
@ -39,7 +39,7 @@ jobs:
|
||||
--health-retries 5
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v5
|
||||
|
||||
- name: Cache cargo registry
|
||||
uses: actions/cache@v4
|
||||
|
||||
2
.github/workflows/security.yml
vendored
2
.github/workflows/security.yml
vendored
@ -14,7 +14,7 @@ jobs:
|
||||
permissions: write-all
|
||||
name: "Audit Rust Dependencies"
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v5
|
||||
- uses: rustsec/audit-check@v2.0.0
|
||||
with:
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
24
Cargo.lock
generated
24
Cargo.lock
generated
@ -85,9 +85,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "actix-rt"
|
||||
version = "2.10.0"
|
||||
version = "2.11.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "24eda4e2a6e042aa4e55ac438a2ae052d3b5da0ecf83d7411e1a368946925208"
|
||||
checksum = "92589714878ca59a7626ea19734f0e07a6a875197eec751bb5d3f99e64998c63"
|
||||
dependencies = [
|
||||
"actix-macros",
|
||||
"futures-core",
|
||||
@ -1776,9 +1776,9 @@ checksum = "56199f7ddabf13fe5074ce809e7d3f42b42ae711800501b5b16ea82ad029c39d"
|
||||
|
||||
[[package]]
|
||||
name = "slab"
|
||||
version = "0.4.10"
|
||||
version = "0.4.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "04dc19736151f35336d325007ac991178d504a119863a2fcb3758cdb5e52c50d"
|
||||
checksum = "7a2ae44ef20feb57a68b23d846850f861394c2e02dc425a50098ae8c90267589"
|
||||
|
||||
[[package]]
|
||||
name = "smallvec"
|
||||
@ -1859,7 +1859,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "taskchampion-sync-server"
|
||||
version = "0.7.0"
|
||||
version = "0.7.1"
|
||||
dependencies = [
|
||||
"actix-rt",
|
||||
"actix-web",
|
||||
@ -1883,7 +1883,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "taskchampion-sync-server-core"
|
||||
version = "0.7.0"
|
||||
version = "0.7.1"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-trait",
|
||||
@ -1898,7 +1898,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "taskchampion-sync-server-storage-postgres"
|
||||
version = "0.7.0"
|
||||
version = "0.7.1"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-trait",
|
||||
@ -1921,7 +1921,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "taskchampion-sync-server-storage-sqlite"
|
||||
version = "0.7.0"
|
||||
version = "0.7.1"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-trait",
|
||||
@ -1946,9 +1946,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "tempfile"
|
||||
version = "3.20.0"
|
||||
version = "3.23.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e8a64e3985349f2441a1a9ef0b853f869006c3855f2cda6862a94d26ebb9d6a1"
|
||||
checksum = "2d31c77bdf42a745371d260a26ca7163f1e0924b64afa0b688e61b5a9fa02f16"
|
||||
dependencies = [
|
||||
"fastrand",
|
||||
"getrandom",
|
||||
@ -2209,9 +2209,9 @@ checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821"
|
||||
|
||||
[[package]]
|
||||
name = "uuid"
|
||||
version = "1.17.0"
|
||||
version = "1.18.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3cf4199d1e5d15ddd86a694e4d0dffa9c323ce759fea589f00fef9d81cc1931d"
|
||||
checksum = "f33196643e165781c20a5ead5582283a7dacbb87855d867fbc2df3f81eddc1be"
|
||||
dependencies = [
|
||||
"getrandom",
|
||||
"js-sys",
|
||||
|
||||
@ -10,7 +10,7 @@ rust-version = "1.85.0" # MSRV
|
||||
|
||||
[workspace.dependencies]
|
||||
async-trait = "0.1.88"
|
||||
uuid = { version = "^1.17.0", features = ["serde", "v4"] }
|
||||
uuid = { version = "^1.18.0", features = ["serde", "v4"] }
|
||||
actix-web = "^4.11.0"
|
||||
anyhow = "1.0"
|
||||
thiserror = "2.0"
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "taskchampion-sync-server-core"
|
||||
version = "0.7.0"
|
||||
version = "0.7.1"
|
||||
authors = ["Dustin J. Mitchell <dustin@mozilla.com>"]
|
||||
edition = "2021"
|
||||
description = "Core of sync protocol for TaskChampion"
|
||||
|
||||
@ -43,7 +43,7 @@ services:
|
||||
condition: service_completed_successfully
|
||||
|
||||
tss:
|
||||
image: ghcr.io/gothenburgbitfactory/taskchampion-sync-server:0.7.0
|
||||
image: ghcr.io/gothenburgbitfactory/taskchampion-sync-server:0.7.1
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
- "RUST_LOG=info"
|
||||
|
||||
@ -10,16 +10,38 @@ One binary is provided for each storage backend:
|
||||
- `taskchampion-sync-server` (SQLite)
|
||||
- `taskchampion-sync-server-postgres` (Postgres)
|
||||
|
||||
### Building the Binary
|
||||
|
||||
This is a standard Rust project, and can be built with `cargo build --release`.
|
||||
|
||||
By default, only the SQLite binary is built. To also build the Postgres binary,
|
||||
use
|
||||
```none
|
||||
cargo build --release --features postgres
|
||||
```
|
||||
|
||||
To disable building the SQLite binary and build only the Postgres binary, use
|
||||
|
||||
```none
|
||||
cargo build --release --no-default-features --features postgres
|
||||
```
|
||||
|
||||
### Running the Binary
|
||||
|
||||
The server is configured with command-line options or environment variables.
|
||||
See the `--help` output for full details.
|
||||
|
||||
For the SQLite binary, the `--data-dir` option or `DATA_DIR` environment
|
||||
variable specifies where the server should store its data. For the Postgres
|
||||
binary, the `--connection` option or `CONNECTION` environment variable
|
||||
specifies the connection information, in the form of a [LibPQ-style connection
|
||||
variable specifies where the server should store its data.
|
||||
|
||||
For the Postgres binary, the `--connection` option or `CONNECTION` environment
|
||||
variable specifies the connection information, in the form of a [LibPQ-style
|
||||
connection
|
||||
URI](https://www.postgresql.org/docs/current/libpq-connect.html#LIBPQ-CONNSTRING-URIS).
|
||||
Note that unlike LibPQ, the Rust client only supports `sslmode` values
|
||||
`disable`, `prefer`, and `require`, and will always validate CA hostnames and
|
||||
certificates when using TLS.
|
||||
|
||||
The remaining options are common to all binaries.
|
||||
|
||||
The `--listen` option specifies the interface and port the server listens on.
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
# Docker Compose
|
||||
|
||||
The
|
||||
[`docker-compose.yml`](https://raw.githubusercontent.com/GothenburgBitFactory/taskchampion-sync-server/refs/tags/v0.7.0/docker-compose.yml)
|
||||
[`docker-compose.yml`](https://raw.githubusercontent.com/GothenburgBitFactory/taskchampion-sync-server/refs/tags/v0.7.1/docker-compose.yml)
|
||||
file in this repository is sufficient to run taskchampion-sync-server,
|
||||
including setting up TLS certificates using Lets Encrypt, thanks to
|
||||
[Caddy](https://caddyserver.com/). This setup uses the SQLite backend, which is
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "taskchampion-sync-server-storage-postgres"
|
||||
version = "0.7.0"
|
||||
version = "0.7.1"
|
||||
authors = ["Dustin J. Mitchell <dustin@v.igoro.us>"]
|
||||
edition = "2021"
|
||||
description = "Postgres backend for TaskChampion-sync-server"
|
||||
@ -16,7 +16,7 @@ bb8.workspace = true
|
||||
chrono.workspace = true
|
||||
env_logger.workspace = true
|
||||
log.workspace = true
|
||||
taskchampion-sync-server-core = { path = "../core", version = "0.7.0" }
|
||||
taskchampion-sync-server-core = { path = "../core", version = "0.7.1" }
|
||||
thiserror.workspace = true
|
||||
tokio-postgres.workspace = true
|
||||
tokio.workspace = true
|
||||
|
||||
@ -39,6 +39,26 @@ use uuid::Uuid;
|
||||
#[cfg(test)]
|
||||
mod testing;
|
||||
|
||||
/// An `ErrorSink` implementation that logs errors to the Rust log.
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct LogErrorSink;
|
||||
|
||||
impl LogErrorSink {
|
||||
fn new() -> Box<Self> {
|
||||
Box::new(Self)
|
||||
}
|
||||
}
|
||||
|
||||
impl bb8::ErrorSink<tokio_postgres::Error> for LogErrorSink {
|
||||
fn sink(&self, e: tokio_postgres::Error) {
|
||||
log::error!("Postgres connection error: {e}");
|
||||
}
|
||||
|
||||
fn boxed_clone(&self) -> Box<dyn bb8::ErrorSink<tokio_postgres::Error>> {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
/// A storage backend which uses Postgres.
|
||||
pub struct PostgresStorage {
|
||||
pool: bb8::Pool<PostgresConnectionManager<MakeTlsConnector>>,
|
||||
@ -49,7 +69,10 @@ impl PostgresStorage {
|
||||
let connector = native_tls::TlsConnector::new()?;
|
||||
let connector = postgres_native_tls::MakeTlsConnector::new(connector);
|
||||
let manager = PostgresConnectionManager::new_from_stringlike(connection_string, connector)?;
|
||||
let pool = bb8::Pool::builder().build(manager).await?;
|
||||
let pool = bb8::Pool::builder()
|
||||
.error_sink(LogErrorSink::new())
|
||||
.build(manager)
|
||||
.await?;
|
||||
Ok(Self { pool })
|
||||
}
|
||||
}
|
||||
@ -249,8 +272,13 @@ impl StorageTxn for Txn {
|
||||
"UPDATE clients
|
||||
SET latest_version_id = $1,
|
||||
versions_since_snapshot = versions_since_snapshot + 1
|
||||
WHERE client_id = $2 and latest_version_id = $3",
|
||||
&[&version_id, &self.client_id, &parent_version_id],
|
||||
WHERE client_id = $2 and (latest_version_id = $3 or latest_version_id = $4)",
|
||||
&[
|
||||
&version_id,
|
||||
&self.client_id,
|
||||
&parent_version_id,
|
||||
&Uuid::nil(),
|
||||
],
|
||||
)
|
||||
.await
|
||||
.context("error updating latest_version_id")?;
|
||||
@ -666,4 +694,22 @@ mod test {
|
||||
})
|
||||
.await
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
/// When an add_version call specifies a `parent_version_id` that does not exist in the
|
||||
/// DB, but no other versions exist, the call succeeds.
|
||||
async fn test_add_version_no_history() -> anyhow::Result<()> {
|
||||
with_db(async |connection_string, db_client| {
|
||||
let storage = PostgresStorage::new(connection_string).await?;
|
||||
let client_id = make_client(&db_client).await?;
|
||||
|
||||
let mut txn = storage.txn(client_id).await?;
|
||||
let version_id = Uuid::new_v4();
|
||||
let parent_version_id = Uuid::new_v4();
|
||||
txn.add_version(version_id, parent_version_id, b"v1".to_vec())
|
||||
.await?;
|
||||
Ok(())
|
||||
})
|
||||
.await
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "taskchampion-sync-server"
|
||||
version = "0.7.0"
|
||||
version = "0.7.1"
|
||||
authors = ["Dustin J. Mitchell <dustin@mozilla.com>"]
|
||||
edition = "2021"
|
||||
publish = false
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "taskchampion-sync-server-storage-sqlite"
|
||||
version = "0.7.0"
|
||||
version = "0.7.1"
|
||||
authors = ["Dustin J. Mitchell <dustin@mozilla.com>"]
|
||||
edition = "2021"
|
||||
description = "SQLite backend for TaskChampion-sync-server"
|
||||
@ -9,7 +9,7 @@ repository = "https://github.com/GothenburgBitFactory/taskchampion-sync-server"
|
||||
license = "MIT"
|
||||
|
||||
[dependencies]
|
||||
taskchampion-sync-server-core = { path = "../core", version = "0.7.0" }
|
||||
taskchampion-sync-server-core = { path = "../core", version = "0.7.1" }
|
||||
async-trait.workspace = true
|
||||
uuid.workspace = true
|
||||
anyhow.workspace = true
|
||||
|
||||
@ -276,11 +276,12 @@ impl StorageTxn for Txn {
|
||||
SET
|
||||
latest_version_id = ?,
|
||||
versions_since_snapshot = versions_since_snapshot + 1
|
||||
WHERE client_id = ? and latest_version_id = ?",
|
||||
WHERE client_id = ? and (latest_version_id = ? or latest_version_id = ?)",
|
||||
params![
|
||||
StoredUuid(version_id),
|
||||
StoredUuid(self.client_id),
|
||||
StoredUuid(parent_version_id)
|
||||
StoredUuid(parent_version_id),
|
||||
StoredUuid(Uuid::nil())
|
||||
],
|
||||
)
|
||||
.context("Error updating client for new version")?;
|
||||
@ -489,4 +490,21 @@ mod test {
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
/// When an add_version call specifies a `parent_version_id` that does not exist in the
|
||||
/// DB, but no other versions exist, the call succeeds.
|
||||
async fn test_add_version_no_history() -> anyhow::Result<()> {
|
||||
let tmp_dir = TempDir::new()?;
|
||||
let storage = SqliteStorage::new(tmp_dir.path())?;
|
||||
let client_id = Uuid::new_v4();
|
||||
let mut txn = storage.txn(client_id).await?;
|
||||
txn.new_client(Uuid::nil()).await?;
|
||||
|
||||
let version_id = Uuid::new_v4();
|
||||
let parent_version_id = Uuid::new_v4();
|
||||
txn.add_version(version_id, parent_version_id, b"v1".to_vec())
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user