18 Commits

Author SHA1 Message Date
d206729d5e v0.7.1 2025-10-11 18:57:23 +00:00
bf19b76577 Bump actix-rt from 2.10.0 to 2.11.0 (#151)
Bumps [actix-rt](https://github.com/actix/actix-net) from 2.10.0 to 2.11.0.
- [Release notes](https://github.com/actix/actix-net/releases)
- [Commits](https://github.com/actix/actix-net/compare/rt-v2.10.0...rt-v2.11.0)

---
updated-dependencies:
- dependency-name: actix-rt
  dependency-version: 2.11.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-10-10 21:21:10 -04:00
505dd3f442 Bump tempfile from 3.22.0 to 3.23.0 (#153)
Bumps [tempfile](https://github.com/Stebalien/tempfile) from 3.22.0 to 3.23.0.
- [Changelog](https://github.com/Stebalien/tempfile/blob/master/CHANGELOG.md)
- [Commits](https://github.com/Stebalien/tempfile/compare/v3.22.0...v3.23.0)

---
updated-dependencies:
- dependency-name: tempfile
  dependency-version: 3.23.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-09-23 22:09:46 -04:00
e10f3e6cfb Bump tempfile from 3.21.0 to 3.22.0 (#152)
Bumps [tempfile](https://github.com/Stebalien/tempfile) from 3.21.0 to 3.22.0.
- [Changelog](https://github.com/Stebalien/tempfile/blob/master/CHANGELOG.md)
- [Commits](https://github.com/Stebalien/tempfile/compare/v3.21.0...v3.22.0)

---
updated-dependencies:
- dependency-name: tempfile
  dependency-version: 3.22.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-09-10 19:04:12 -04:00
213be852b8 Fix add_version calls when no history exists on the server (#149)
This fixes a bug in 25911b44a6 where the
check in the storage implementation was too strict (not allowing
`clients.latest_version_id == Uuid::nil()`), causing spurious failures.

Well, two bugs, one in each storage implementation.
2025-08-20 15:21:04 -04:00
b57dd24d9e Bump tempfile from 3.20.0 to 3.21.0 (#150)
Bumps [tempfile](https://github.com/Stebalien/tempfile) from 3.20.0 to 3.21.0.
- [Changelog](https://github.com/Stebalien/tempfile/blob/master/CHANGELOG.md)
- [Commits](https://github.com/Stebalien/tempfile/commits)

---
updated-dependencies:
- dependency-name: tempfile
  dependency-version: 3.21.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-08-20 08:45:41 -04:00
a9cf67c8e2 Document Cargo features in the binaries page (#148) 2025-08-18 08:29:32 -04:00
daf6855f14 Bump slab from 0.4.10 to 0.4.11 (#147)
Bumps [slab](https://github.com/tokio-rs/slab) from 0.4.10 to 0.4.11.
- [Release notes](https://github.com/tokio-rs/slab/releases)
- [Changelog](https://github.com/tokio-rs/slab/blob/master/CHANGELOG.md)
- [Commits](https://github.com/tokio-rs/slab/compare/v0.4.10...v0.4.11)

---
updated-dependencies:
- dependency-name: slab
  dependency-version: 0.4.11
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-08-12 22:09:38 -04:00
dbc9a6909b Bump actions/checkout from 4 to 5 (#145)
Bumps [actions/checkout](https://github.com/actions/checkout) from 4 to 5.
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](https://github.com/actions/checkout/compare/v4...v5)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-version: '5'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-08-12 19:55:19 -04:00
1ad9e344c7 Bump uuid from 1.17.0 to 1.18.0 (#146)
Bumps [uuid](https://github.com/uuid-rs/uuid) from 1.17.0 to 1.18.0.
- [Release notes](https://github.com/uuid-rs/uuid/releases)
- [Commits](https://github.com/uuid-rs/uuid/compare/v1.17.0...v1.18.0)

---
updated-dependencies:
- dependency-name: uuid
  dependency-version: 1.18.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-08-12 19:55:00 -04:00
3820a8deea Document the different sslmodes recognized by the postgres crate (#144)
LibPQ will happily accept invalid certificates or hostnames, while the
Rust client will not. This can cause some confusion, so draw attention
to it here.
2025-08-04 07:24:59 -04:00
2de70ac336 Capture and log errors from bb8 (#143) 2025-08-03 11:13:58 -04:00
c2b4c94fb5 Merge remote-tracking branch 'origin/main' 2025-08-02 21:01:41 -04:00
0a317cd86d Only publish docs on tags (#136) 2025-08-01 17:48:34 -04:00
1b80398365 Merge post-0.7.0-release updates into main 2025-07-30 22:41:25 -04:00
a94be2649e Use correct option to docker/build-push-action for Dockerfiles 2025-07-30 21:32:27 -04:00
624efa8b0d stop excluding the postgres crate 2025-07-30 21:23:53 -04:00
ae9adf1572 Bump to -pre version 2025-07-30 21:23:03 -04:00
15 changed files with 126 additions and 41 deletions

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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
View File

@ -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",

View File

@ -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"

View File

@ -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"

View File

@ -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"

View File

@ -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.

View File

@ -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

View File

@ -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

View File

@ -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
}
}

View File

@ -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

View File

@ -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

View File

@ -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(())
}
}