mirror of
https://github.com/GothenburgBitFactory/taskchampion-sync-server.git
synced 2025-06-26 10:54:29 +02:00
Split the server into three crates (#56)
This will make it easier to build variations on the server, or embed it into larger projects.
This commit is contained in:
parent
5769781553
commit
47ce4c1e3b
22 changed files with 1243 additions and 57 deletions
15
.github/workflows/checks.yml
vendored
15
.github/workflows/checks.yml
vendored
|
@ -62,11 +62,24 @@ jobs:
|
||||||
override: true
|
override: true
|
||||||
minimal: true
|
minimal: true
|
||||||
|
|
||||||
- uses: actions-rs/cargo@v1.0.3
|
- name: taskchampion-sync-server
|
||||||
|
uses: actions-rs/cargo@v1.0.3
|
||||||
with:
|
with:
|
||||||
command: rustdoc
|
command: rustdoc
|
||||||
args: -p taskchampion-sync-server --all-features -- -Z unstable-options --check -Dwarnings
|
args: -p taskchampion-sync-server --all-features -- -Z unstable-options --check -Dwarnings
|
||||||
|
|
||||||
|
- name: taskchampion-sync-server-core
|
||||||
|
uses: actions-rs/cargo@v1.0.3
|
||||||
|
with:
|
||||||
|
command: rustdoc
|
||||||
|
args: -p taskchampion-sync-server-core --all-features -- -Z unstable-options --check -Dwarnings
|
||||||
|
|
||||||
|
- name: taskchampion-sync-server-storage-sqlite
|
||||||
|
uses: actions-rs/cargo@v1.0.3
|
||||||
|
with:
|
||||||
|
command: rustdoc
|
||||||
|
args: -p taskchampion-sync-server-storage-sqlite --all-features -- -Z unstable-options --check -Dwarnings
|
||||||
|
|
||||||
fmt:
|
fmt:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
name: "Formatting"
|
name: "Formatting"
|
||||||
|
|
41
Cargo.lock
generated
41
Cargo.lock
generated
|
@ -687,9 +687,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "futures-io"
|
name = "futures-io"
|
||||||
version = "0.3.30"
|
version = "0.3.31"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "a44623e20b9681a318efdd71c299b6b222ed6f231972bfe2f224ebad6311f0c1"
|
checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "futures-macro"
|
name = "futures-macro"
|
||||||
|
@ -1406,9 +1406,36 @@ dependencies = [
|
||||||
"futures",
|
"futures",
|
||||||
"log",
|
"log",
|
||||||
"pretty_assertions",
|
"pretty_assertions",
|
||||||
"rusqlite",
|
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
|
"taskchampion-sync-server-core",
|
||||||
|
"taskchampion-sync-server-storage-sqlite",
|
||||||
|
"tempfile",
|
||||||
|
"thiserror",
|
||||||
|
"uuid",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "taskchampion-sync-server-core"
|
||||||
|
version = "0.4.1"
|
||||||
|
dependencies = [
|
||||||
|
"anyhow",
|
||||||
|
"chrono",
|
||||||
|
"env_logger",
|
||||||
|
"log",
|
||||||
|
"pretty_assertions",
|
||||||
|
"uuid",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "taskchampion-sync-server-storage-sqlite"
|
||||||
|
version = "0.4.1"
|
||||||
|
dependencies = [
|
||||||
|
"anyhow",
|
||||||
|
"chrono",
|
||||||
|
"pretty_assertions",
|
||||||
|
"rusqlite",
|
||||||
|
"taskchampion-sync-server-core",
|
||||||
"tempfile",
|
"tempfile",
|
||||||
"thiserror",
|
"thiserror",
|
||||||
"uuid",
|
"uuid",
|
||||||
|
@ -1429,18 +1456,18 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "thiserror"
|
name = "thiserror"
|
||||||
version = "2.0.0"
|
version = "2.0.3"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "15291287e9bff1bc6f9ff3409ed9af665bec7a5fc8ac079ea96be07bca0e2668"
|
checksum = "c006c85c7651b3cf2ada4584faa36773bd07bac24acfb39f3c431b36d7e667aa"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"thiserror-impl",
|
"thiserror-impl",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "thiserror-impl"
|
name = "thiserror-impl"
|
||||||
version = "2.0.0"
|
version = "2.0.3"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "22efd00f33f93fa62848a7cab956c3d38c8d43095efda1decfc2b3a5dc0b8972"
|
checksum = "f077553d607adc1caf65430528a576c757a71ed73944b66ebb58ef2bbd243568"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
|
|
17
Cargo.toml
17
Cargo.toml
|
@ -1,11 +1,12 @@
|
||||||
[package]
|
[workspace]
|
||||||
name = "taskchampion-sync-server"
|
resolver = "2"
|
||||||
version = "0.4.1"
|
members = [
|
||||||
authors = ["Dustin J. Mitchell <dustin@mozilla.com>"]
|
"core",
|
||||||
edition = "2021"
|
"server",
|
||||||
publish = false
|
"sqlite",
|
||||||
|
]
|
||||||
|
|
||||||
[dependencies]
|
[workspace.dependencies]
|
||||||
uuid = { version = "^1.11.0", features = ["serde", "v4"] }
|
uuid = { version = "^1.11.0", features = ["serde", "v4"] }
|
||||||
actix-web = "^4.9.0"
|
actix-web = "^4.9.0"
|
||||||
anyhow = "1.0"
|
anyhow = "1.0"
|
||||||
|
@ -18,8 +19,6 @@ log = "^0.4.17"
|
||||||
env_logger = "^0.11.5"
|
env_logger = "^0.11.5"
|
||||||
rusqlite = { version = "0.32", features = ["bundled"] }
|
rusqlite = { version = "0.32", features = ["bundled"] }
|
||||||
chrono = { version = "^0.4.38", features = ["serde"] }
|
chrono = { version = "^0.4.38", features = ["serde"] }
|
||||||
|
|
||||||
[dev-dependencies]
|
|
||||||
actix-rt = "2"
|
actix-rt = "2"
|
||||||
tempfile = "3"
|
tempfile = "3"
|
||||||
pretty_assertions = "1"
|
pretty_assertions = "1"
|
||||||
|
|
|
@ -13,6 +13,12 @@ This repository was spun off from Taskwarrior itself after the 3.0.0
|
||||||
release. It is still under development and currently best described as
|
release. It is still under development and currently best described as
|
||||||
a reference implementation of the Taskchampion sync protocol.
|
a reference implementation of the Taskchampion sync protocol.
|
||||||
|
|
||||||
|
It is comprised of three crates:
|
||||||
|
|
||||||
|
- `taskchampion-sync-server-core` implements the core of the protocol
|
||||||
|
- `taskchmpaion-sync-server-sqlite` implements an SQLite backend for the core
|
||||||
|
- `taskchampion-sync-server` implements a simple HTTP server for the protocol
|
||||||
|
|
||||||
## Installation
|
## Installation
|
||||||
|
|
||||||
### From binary
|
### From binary
|
||||||
|
|
15
core/Cargo.toml
Normal file
15
core/Cargo.toml
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
[package]
|
||||||
|
name = "taskchampion-sync-server-core"
|
||||||
|
version = "0.4.1"
|
||||||
|
authors = ["Dustin J. Mitchell <dustin@mozilla.com>"]
|
||||||
|
edition = "2021"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
uuid.workspace = true
|
||||||
|
anyhow.workspace = true
|
||||||
|
log.workspace = true
|
||||||
|
env_logger.workspace = true
|
||||||
|
chrono.workspace = true
|
||||||
|
|
||||||
|
[dev-dependencies]
|
||||||
|
pretty_assertions.workspace = true
|
8
core/README.md
Normal file
8
core/README.md
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
# taskchampion-sync-server-core
|
||||||
|
|
||||||
|
This crate implements the core logic of the taskchampion sync protocol.
|
||||||
|
|
||||||
|
This should be considered a reference implementation, with [the protocol
|
||||||
|
documentation](https://gothenburgbitfactory.org/taskchampion/sync-protocol.html).
|
||||||
|
representing the authoritative definition of the protocol. Other
|
||||||
|
implementations are encouraged.
|
|
@ -1,6 +1,7 @@
|
||||||
use super::{Client, Snapshot, Storage, StorageTxn, Uuid, Version};
|
use super::{Client, Snapshot, Storage, StorageTxn, Version};
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::sync::{Mutex, MutexGuard};
|
use std::sync::{Mutex, MutexGuard};
|
||||||
|
use uuid::Uuid;
|
||||||
|
|
||||||
struct Inner {
|
struct Inner {
|
||||||
/// Clients, indexed by client_id
|
/// Clients, indexed by client_id
|
||||||
|
@ -16,6 +17,11 @@ struct Inner {
|
||||||
children: HashMap<(Uuid, Uuid), Uuid>,
|
children: HashMap<(Uuid, Uuid), Uuid>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// In-memory storage for testing and experimentation.
|
||||||
|
///
|
||||||
|
/// This is not for production use, but supports testing of sync server implementations.
|
||||||
|
///
|
||||||
|
/// NOTE: this does not implement transaction rollback.
|
||||||
pub struct InMemoryStorage(Mutex<Inner>);
|
pub struct InMemoryStorage(Mutex<Inner>);
|
||||||
|
|
||||||
impl InMemoryStorage {
|
impl InMemoryStorage {
|
||||||
|
@ -32,9 +38,6 @@ impl InMemoryStorage {
|
||||||
|
|
||||||
struct InnerTxn<'a>(MutexGuard<'a, Inner>);
|
struct InnerTxn<'a>(MutexGuard<'a, Inner>);
|
||||||
|
|
||||||
/// In-memory storage for testing and experimentation.
|
|
||||||
///
|
|
||||||
/// NOTE: this does not implement transaction rollback.
|
|
||||||
impl Storage for InMemoryStorage {
|
impl Storage for InMemoryStorage {
|
||||||
fn txn<'a>(&'a self) -> anyhow::Result<Box<dyn StorageTxn + 'a>> {
|
fn txn<'a>(&'a self) -> anyhow::Result<Box<dyn StorageTxn + 'a>> {
|
||||||
Ok(Box::new(InnerTxn(self.0.lock().expect("poisoned lock"))))
|
Ok(Box::new(InnerTxn(self.0.lock().expect("poisoned lock"))))
|
32
core/src/lib.rs
Normal file
32
core/src/lib.rs
Normal file
|
@ -0,0 +1,32 @@
|
||||||
|
//! This crate implements the core logic of the taskchampion sync protocol.
|
||||||
|
//!
|
||||||
|
//! This should be considered a reference implementation, with [the protocol
|
||||||
|
//! documentation](https://gothenburgbitfactory.org/taskchampion/sync-protocol.html). representing
|
||||||
|
//! the authoritative definition of the protocol. Other implementations are encouraged.
|
||||||
|
//!
|
||||||
|
//! This crate uses an abstract storage backend. Note that this does not implement the
|
||||||
|
//! HTTP-specific portions of the protocol, nor provide any storage implementations.
|
||||||
|
//!
|
||||||
|
//! ## API Methods
|
||||||
|
//!
|
||||||
|
//! The following API methods are implemented. These methods are documented in more detail in
|
||||||
|
//! the protocol documentation.
|
||||||
|
//!
|
||||||
|
//! * [`add_version`]
|
||||||
|
//! * [`get_child_version`]
|
||||||
|
//! * [`add_snapshot`]
|
||||||
|
//! * [`get_snapshot`]
|
||||||
|
//!
|
||||||
|
//! Each API method takes:
|
||||||
|
//!
|
||||||
|
//! * [`StorageTxn`] to access storage. Methods which modify storage will commit the transaction before returning.
|
||||||
|
//! * [`ServerConfig`] providing basic configuration for the server's behavior.
|
||||||
|
//! * `client_id` and a [`Client`] providing the client metadata.
|
||||||
|
|
||||||
|
mod inmemory;
|
||||||
|
mod server;
|
||||||
|
mod storage;
|
||||||
|
|
||||||
|
pub use inmemory::*;
|
||||||
|
pub use server::*;
|
||||||
|
pub use storage::*;
|
1037
core/src/server.rs
Normal file
1037
core/src/server.rs
Normal file
File diff suppressed because it is too large
Load diff
|
@ -1,15 +1,7 @@
|
||||||
use chrono::{DateTime, Utc};
|
use chrono::{DateTime, Utc};
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
|
|
||||||
#[cfg(debug_assertions)]
|
/// A representation of stored metadata about a client.
|
||||||
mod inmemory;
|
|
||||||
|
|
||||||
#[cfg(debug_assertions)]
|
|
||||||
pub use inmemory::InMemoryStorage;
|
|
||||||
|
|
||||||
mod sqlite;
|
|
||||||
pub use self::sqlite::SqliteStorage;
|
|
||||||
|
|
||||||
#[derive(Clone, PartialEq, Eq, Debug)]
|
#[derive(Clone, PartialEq, Eq, Debug)]
|
||||||
pub struct Client {
|
pub struct Client {
|
||||||
/// The latest version for this client (may be the nil version)
|
/// The latest version for this client (may be the nil version)
|
||||||
|
@ -18,6 +10,7 @@ pub struct Client {
|
||||||
pub snapshot: Option<Snapshot>,
|
pub snapshot: Option<Snapshot>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Metadata about a snapshot, not including the snapshot data itself.
|
||||||
#[derive(Clone, PartialEq, Eq, Debug)]
|
#[derive(Clone, PartialEq, Eq, Debug)]
|
||||||
pub struct Snapshot {
|
pub struct Snapshot {
|
||||||
/// ID of the version at which this snapshot was made
|
/// ID of the version at which this snapshot was made
|
||||||
|
@ -32,11 +25,19 @@ pub struct Snapshot {
|
||||||
|
|
||||||
#[derive(Clone, PartialEq, Eq, Debug)]
|
#[derive(Clone, PartialEq, Eq, Debug)]
|
||||||
pub struct Version {
|
pub struct Version {
|
||||||
|
/// The uuid identifying this version.
|
||||||
pub version_id: Uuid,
|
pub version_id: Uuid,
|
||||||
|
/// The uuid identifying this version's parent.
|
||||||
pub parent_version_id: Uuid,
|
pub parent_version_id: Uuid,
|
||||||
|
/// The data carried in this version.
|
||||||
pub history_segment: Vec<u8>,
|
pub history_segment: Vec<u8>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// A transaction in the storage backend.
|
||||||
|
///
|
||||||
|
/// Transactions must be sequentially consistent. That is, the results of transactions performed
|
||||||
|
/// in storage must be as if each were executed sequentially in some order. In particular, the
|
||||||
|
/// `Client.latest_version` must not change between a call to `get_client` and `add_version`.
|
||||||
pub trait StorageTxn {
|
pub trait StorageTxn {
|
||||||
/// Get information about the given client
|
/// Get information about the given client
|
||||||
fn get_client(&mut self, client_id: Uuid) -> anyhow::Result<Option<Client>>;
|
fn get_client(&mut self, client_id: Uuid) -> anyhow::Result<Option<Client>>;
|
26
server/Cargo.toml
Normal file
26
server/Cargo.toml
Normal file
|
@ -0,0 +1,26 @@
|
||||||
|
[package]
|
||||||
|
name = "taskchampion-sync-server"
|
||||||
|
version = "0.4.1"
|
||||||
|
authors = ["Dustin J. Mitchell <dustin@mozilla.com>"]
|
||||||
|
edition = "2021"
|
||||||
|
publish = false
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
taskchampion-sync-server-core = { path = "../core" }
|
||||||
|
taskchampion-sync-server-storage-sqlite = { path = "../sqlite" }
|
||||||
|
uuid.workspace = true
|
||||||
|
actix-web.workspace = true
|
||||||
|
anyhow.workspace = true
|
||||||
|
thiserror.workspace = true
|
||||||
|
futures.workspace = true
|
||||||
|
serde_json.workspace = true
|
||||||
|
serde.workspace = true
|
||||||
|
clap.workspace = true
|
||||||
|
log.workspace = true
|
||||||
|
env_logger.workspace = true
|
||||||
|
chrono.workspace = true
|
||||||
|
|
||||||
|
[dev-dependencies]
|
||||||
|
actix-rt.workspace = true
|
||||||
|
tempfile.workspace = true
|
||||||
|
pretty_assertions.workspace = true
|
|
@ -1,8 +1,8 @@
|
||||||
use crate::api::{client_id_header, failure_to_ise, ServerState, SNAPSHOT_CONTENT_TYPE};
|
use crate::api::{client_id_header, failure_to_ise, ServerState, SNAPSHOT_CONTENT_TYPE};
|
||||||
use crate::server::{add_snapshot, VersionId, NIL_VERSION_ID};
|
|
||||||
use actix_web::{error, post, web, HttpMessage, HttpRequest, HttpResponse, Result};
|
use actix_web::{error, post, web, HttpMessage, HttpRequest, HttpResponse, Result};
|
||||||
use futures::StreamExt;
|
use futures::StreamExt;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
use taskchampion_sync_server_core::{add_snapshot, VersionId, NIL_VERSION_ID};
|
||||||
|
|
||||||
/// Max snapshot size: 100MB
|
/// Max snapshot size: 100MB
|
||||||
const MAX_SIZE: usize = 100 * 1024 * 1024;
|
const MAX_SIZE: usize = 100 * 1024 * 1024;
|
||||||
|
@ -77,10 +77,10 @@ pub(crate) async fn service(
|
||||||
mod test {
|
mod test {
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::api::CLIENT_ID_HEADER;
|
use crate::api::CLIENT_ID_HEADER;
|
||||||
use crate::storage::{InMemoryStorage, Storage};
|
|
||||||
use crate::Server;
|
use crate::Server;
|
||||||
use actix_web::{http::StatusCode, test, App};
|
use actix_web::{http::StatusCode, test, App};
|
||||||
use pretty_assertions::assert_eq;
|
use pretty_assertions::assert_eq;
|
||||||
|
use taskchampion_sync_server_core::{InMemoryStorage, Storage};
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
|
|
||||||
#[actix_rt::test]
|
#[actix_rt::test]
|
|
@ -2,10 +2,12 @@ use crate::api::{
|
||||||
client_id_header, failure_to_ise, ServerState, HISTORY_SEGMENT_CONTENT_TYPE,
|
client_id_header, failure_to_ise, ServerState, HISTORY_SEGMENT_CONTENT_TYPE,
|
||||||
PARENT_VERSION_ID_HEADER, SNAPSHOT_REQUEST_HEADER, VERSION_ID_HEADER,
|
PARENT_VERSION_ID_HEADER, SNAPSHOT_REQUEST_HEADER, VERSION_ID_HEADER,
|
||||||
};
|
};
|
||||||
use crate::server::{add_version, AddVersionResult, SnapshotUrgency, VersionId, NIL_VERSION_ID};
|
|
||||||
use actix_web::{error, post, web, HttpMessage, HttpRequest, HttpResponse, Result};
|
use actix_web::{error, post, web, HttpMessage, HttpRequest, HttpResponse, Result};
|
||||||
use futures::StreamExt;
|
use futures::StreamExt;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
use taskchampion_sync_server_core::{
|
||||||
|
add_version, AddVersionResult, SnapshotUrgency, VersionId, NIL_VERSION_ID,
|
||||||
|
};
|
||||||
|
|
||||||
/// Max history segment size: 100MB
|
/// Max history segment size: 100MB
|
||||||
const MAX_SIZE: usize = 100 * 1024 * 1024;
|
const MAX_SIZE: usize = 100 * 1024 * 1024;
|
||||||
|
@ -105,10 +107,10 @@ pub(crate) async fn service(
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod test {
|
mod test {
|
||||||
use crate::api::CLIENT_ID_HEADER;
|
use crate::api::CLIENT_ID_HEADER;
|
||||||
use crate::storage::{InMemoryStorage, Storage};
|
|
||||||
use crate::Server;
|
use crate::Server;
|
||||||
use actix_web::{http::StatusCode, test, App};
|
use actix_web::{http::StatusCode, test, App};
|
||||||
use pretty_assertions::assert_eq;
|
use pretty_assertions::assert_eq;
|
||||||
|
use taskchampion_sync_server_core::{InMemoryStorage, Storage};
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
|
|
||||||
#[actix_rt::test]
|
#[actix_rt::test]
|
|
@ -2,9 +2,9 @@ use crate::api::{
|
||||||
client_id_header, failure_to_ise, ServerState, HISTORY_SEGMENT_CONTENT_TYPE,
|
client_id_header, failure_to_ise, ServerState, HISTORY_SEGMENT_CONTENT_TYPE,
|
||||||
PARENT_VERSION_ID_HEADER, VERSION_ID_HEADER,
|
PARENT_VERSION_ID_HEADER, VERSION_ID_HEADER,
|
||||||
};
|
};
|
||||||
use crate::server::{get_child_version, GetVersionResult, VersionId};
|
|
||||||
use actix_web::{error, get, web, HttpRequest, HttpResponse, Result};
|
use actix_web::{error, get, web, HttpRequest, HttpResponse, Result};
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
use taskchampion_sync_server_core::{get_child_version, GetVersionResult, VersionId};
|
||||||
|
|
||||||
/// Get a child version.
|
/// Get a child version.
|
||||||
///
|
///
|
||||||
|
@ -57,11 +57,10 @@ pub(crate) async fn service(
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod test {
|
mod test {
|
||||||
use crate::api::CLIENT_ID_HEADER;
|
use crate::api::CLIENT_ID_HEADER;
|
||||||
use crate::server::NIL_VERSION_ID;
|
|
||||||
use crate::storage::{InMemoryStorage, Storage};
|
|
||||||
use crate::Server;
|
use crate::Server;
|
||||||
use actix_web::{http::StatusCode, test, App};
|
use actix_web::{http::StatusCode, test, App};
|
||||||
use pretty_assertions::assert_eq;
|
use pretty_assertions::assert_eq;
|
||||||
|
use taskchampion_sync_server_core::{InMemoryStorage, Storage, NIL_VERSION_ID};
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
|
|
||||||
#[actix_rt::test]
|
#[actix_rt::test]
|
|
@ -1,9 +1,9 @@
|
||||||
use crate::api::{
|
use crate::api::{
|
||||||
client_id_header, failure_to_ise, ServerState, SNAPSHOT_CONTENT_TYPE, VERSION_ID_HEADER,
|
client_id_header, failure_to_ise, ServerState, SNAPSHOT_CONTENT_TYPE, VERSION_ID_HEADER,
|
||||||
};
|
};
|
||||||
use crate::server::get_snapshot;
|
|
||||||
use actix_web::{error, get, web, HttpRequest, HttpResponse, Result};
|
use actix_web::{error, get, web, HttpRequest, HttpResponse, Result};
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
use taskchampion_sync_server_core::get_snapshot;
|
||||||
|
|
||||||
/// Get a snapshot.
|
/// Get a snapshot.
|
||||||
///
|
///
|
||||||
|
@ -42,11 +42,11 @@ pub(crate) async fn service(
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod test {
|
mod test {
|
||||||
use crate::api::CLIENT_ID_HEADER;
|
use crate::api::CLIENT_ID_HEADER;
|
||||||
use crate::storage::{InMemoryStorage, Snapshot, Storage};
|
|
||||||
use crate::Server;
|
use crate::Server;
|
||||||
use actix_web::{http::StatusCode, test, App};
|
use actix_web::{http::StatusCode, test, App};
|
||||||
use chrono::{TimeZone, Utc};
|
use chrono::{TimeZone, Utc};
|
||||||
use pretty_assertions::assert_eq;
|
use pretty_assertions::assert_eq;
|
||||||
|
use taskchampion_sync_server_core::{InMemoryStorage, Snapshot, Storage};
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
|
|
||||||
#[actix_rt::test]
|
#[actix_rt::test]
|
|
@ -1,7 +1,5 @@
|
||||||
use crate::server::ClientId;
|
|
||||||
use crate::storage::Storage;
|
|
||||||
use crate::ServerConfig;
|
|
||||||
use actix_web::{error, http::StatusCode, web, HttpRequest, Result, Scope};
|
use actix_web::{error, http::StatusCode, web, HttpRequest, Result, Scope};
|
||||||
|
use taskchampion_sync_server_core::{ClientId, ServerConfig, Storage};
|
||||||
|
|
||||||
mod add_snapshot;
|
mod add_snapshot;
|
||||||
mod add_version;
|
mod add_version;
|
|
@ -3,8 +3,9 @@
|
||||||
use actix_web::{middleware::Logger, App, HttpServer};
|
use actix_web::{middleware::Logger, App, HttpServer};
|
||||||
use clap::{arg, builder::ValueParser, value_parser, Command};
|
use clap::{arg, builder::ValueParser, value_parser, Command};
|
||||||
use std::ffi::OsString;
|
use std::ffi::OsString;
|
||||||
use taskchampion_sync_server::storage::SqliteStorage;
|
use taskchampion_sync_server::Server;
|
||||||
use taskchampion_sync_server::{Server, ServerConfig};
|
use taskchampion_sync_server_core::ServerConfig;
|
||||||
|
use taskchampion_sync_server_storage_sqlite::SqliteStorage;
|
||||||
|
|
||||||
#[actix_web::main]
|
#[actix_web::main]
|
||||||
async fn main() -> anyhow::Result<()> {
|
async fn main() -> anyhow::Result<()> {
|
||||||
|
@ -62,7 +63,7 @@ async fn main() -> anyhow::Result<()> {
|
||||||
mod test {
|
mod test {
|
||||||
use super::*;
|
use super::*;
|
||||||
use actix_web::{test, App};
|
use actix_web::{test, App};
|
||||||
use taskchampion_sync_server::storage::InMemoryStorage;
|
use taskchampion_sync_server_core::InMemoryStorage;
|
||||||
|
|
||||||
#[actix_rt::test]
|
#[actix_rt::test]
|
||||||
async fn test_index_get() {
|
async fn test_index_get() {
|
|
@ -1,15 +1,11 @@
|
||||||
#![deny(clippy::all)]
|
#![deny(clippy::all)]
|
||||||
|
|
||||||
mod api;
|
mod api;
|
||||||
mod server;
|
|
||||||
pub mod storage;
|
|
||||||
|
|
||||||
use crate::storage::Storage;
|
|
||||||
use actix_web::{get, middleware, web, Responder};
|
use actix_web::{get, middleware, web, Responder};
|
||||||
use api::{api_scope, ServerState};
|
use api::{api_scope, ServerState};
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
use taskchampion_sync_server_core::{ServerConfig, Storage};
|
||||||
pub use server::ServerConfig;
|
|
||||||
|
|
||||||
#[get("/")]
|
#[get("/")]
|
||||||
async fn index() -> impl Responder {
|
async fn index() -> impl Responder {
|
||||||
|
@ -47,13 +43,9 @@ impl Server {
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod test {
|
mod test {
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::storage::InMemoryStorage;
|
|
||||||
use actix_web::{test, App};
|
use actix_web::{test, App};
|
||||||
use pretty_assertions::assert_eq;
|
use pretty_assertions::assert_eq;
|
||||||
|
use taskchampion_sync_server_core::InMemoryStorage;
|
||||||
pub(crate) fn init_logging() {
|
|
||||||
let _ = env_logger::builder().is_test(true).try_init();
|
|
||||||
}
|
|
||||||
|
|
||||||
#[actix_rt::test]
|
#[actix_rt::test]
|
||||||
async fn test_cache_control() {
|
async fn test_cache_control() {
|
17
sqlite/Cargo.toml
Normal file
17
sqlite/Cargo.toml
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
[package]
|
||||||
|
name = "taskchampion-sync-server-storage-sqlite"
|
||||||
|
version = "0.4.1"
|
||||||
|
authors = ["Dustin J. Mitchell <dustin@mozilla.com>"]
|
||||||
|
edition = "2021"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
taskchampion-sync-server-core = { path = "../core" }
|
||||||
|
uuid.workspace = true
|
||||||
|
anyhow.workspace = true
|
||||||
|
thiserror.workspace = true
|
||||||
|
rusqlite.workspace = true
|
||||||
|
chrono.workspace = true
|
||||||
|
|
||||||
|
[dev-dependencies]
|
||||||
|
tempfile.workspace = true
|
||||||
|
pretty_assertions.workspace = true
|
4
sqlite/README.md
Normal file
4
sqlite/README.md
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
# taskchampion-sync-server-storage-sqlite
|
||||||
|
|
||||||
|
This crate implements a SQLite storage backend for the
|
||||||
|
`taskchampion-sync-server-core`.
|
|
@ -1,9 +1,11 @@
|
||||||
use super::{Client, Snapshot, Storage, StorageTxn, Uuid, Version};
|
//! Tihs crate implements a SQLite storage backend for the TaskChampion sync server.
|
||||||
use anyhow::Context;
|
use anyhow::Context;
|
||||||
use chrono::{TimeZone, Utc};
|
use chrono::{TimeZone, Utc};
|
||||||
use rusqlite::types::{FromSql, ToSql};
|
use rusqlite::types::{FromSql, ToSql};
|
||||||
use rusqlite::{params, Connection, OptionalExtension};
|
use rusqlite::{params, Connection, OptionalExtension};
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
|
use taskchampion_sync_server_core::{Client, Snapshot, Storage, StorageTxn, Version};
|
||||||
|
use uuid::Uuid;
|
||||||
|
|
||||||
#[derive(Debug, thiserror::Error)]
|
#[derive(Debug, thiserror::Error)]
|
||||||
enum SqliteError {
|
enum SqliteError {
|
||||||
|
@ -31,7 +33,7 @@ impl ToSql for StoredUuid {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// An on-disk storage backend which uses SQLite
|
/// An on-disk storage backend which uses SQLite.
|
||||||
pub struct SqliteStorage {
|
pub struct SqliteStorage {
|
||||||
db_file: std::path::PathBuf,
|
db_file: std::path::PathBuf,
|
||||||
}
|
}
|
||||||
|
@ -41,6 +43,10 @@ impl SqliteStorage {
|
||||||
Ok(Connection::open(&self.db_file)?)
|
Ok(Connection::open(&self.db_file)?)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Create a new instance using a database at the given directory.
|
||||||
|
///
|
||||||
|
/// The database will be stored in a file named `taskchampion-sync-server.sqlite3` in the given
|
||||||
|
/// directory.
|
||||||
pub fn new<P: AsRef<Path>>(directory: P) -> anyhow::Result<SqliteStorage> {
|
pub fn new<P: AsRef<Path>>(directory: P) -> anyhow::Result<SqliteStorage> {
|
||||||
std::fs::create_dir_all(&directory)
|
std::fs::create_dir_all(&directory)
|
||||||
.with_context(|| format!("Failed to create `{}`.", directory.as_ref().display()))?;
|
.with_context(|| format!("Failed to create `{}`.", directory.as_ref().display()))?;
|
Loading…
Add table
Add a link
Reference in a new issue