refactor sync-server into a lib crate with a binary
This commit is contained in:
@@ -77,9 +77,8 @@ pub(crate) async fn service(
|
|||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod test {
|
mod test {
|
||||||
use crate::api::ServerState;
|
|
||||||
use crate::app_scope;
|
|
||||||
use crate::storage::{InMemoryStorage, Storage};
|
use crate::storage::{InMemoryStorage, Storage};
|
||||||
|
use crate::Server;
|
||||||
use actix_web::{http::StatusCode, test, App};
|
use actix_web::{http::StatusCode, test, App};
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
|
|
||||||
@@ -88,16 +87,16 @@ mod test {
|
|||||||
let client_key = Uuid::new_v4();
|
let client_key = Uuid::new_v4();
|
||||||
let version_id = Uuid::new_v4();
|
let version_id = Uuid::new_v4();
|
||||||
let parent_version_id = Uuid::new_v4();
|
let parent_version_id = Uuid::new_v4();
|
||||||
let server_box: Box<dyn Storage> = Box::new(InMemoryStorage::new());
|
let storage: Box<dyn Storage> = Box::new(InMemoryStorage::new());
|
||||||
|
|
||||||
// set up the storage contents..
|
// set up the storage contents..
|
||||||
{
|
{
|
||||||
let mut txn = server_box.txn().unwrap();
|
let mut txn = storage.txn().unwrap();
|
||||||
txn.new_client(client_key, Uuid::nil()).unwrap();
|
txn.new_client(client_key, Uuid::nil()).unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
let server_state = ServerState::new(server_box);
|
let server = Server::new(storage);
|
||||||
let mut app = test::init_service(App::new().service(app_scope(server_state))).await;
|
let mut app = test::init_service(App::new().service(server.service())).await;
|
||||||
|
|
||||||
let uri = format!("/v1/client/add-version/{}", parent_version_id);
|
let uri = format!("/v1/client/add-version/{}", parent_version_id);
|
||||||
let req = test::TestRequest::post()
|
let req = test::TestRequest::post()
|
||||||
@@ -125,16 +124,16 @@ mod test {
|
|||||||
let client_key = Uuid::new_v4();
|
let client_key = Uuid::new_v4();
|
||||||
let version_id = Uuid::new_v4();
|
let version_id = Uuid::new_v4();
|
||||||
let parent_version_id = Uuid::new_v4();
|
let parent_version_id = Uuid::new_v4();
|
||||||
let server_box: Box<dyn Storage> = Box::new(InMemoryStorage::new());
|
let storage: Box<dyn Storage> = Box::new(InMemoryStorage::new());
|
||||||
|
|
||||||
// set up the storage contents..
|
// set up the storage contents..
|
||||||
{
|
{
|
||||||
let mut txn = server_box.txn().unwrap();
|
let mut txn = storage.txn().unwrap();
|
||||||
txn.new_client(client_key, version_id).unwrap();
|
txn.new_client(client_key, version_id).unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
let server_state = ServerState::new(server_box);
|
let server = Server::new(storage);
|
||||||
let mut app = test::init_service(App::new().service(app_scope(server_state))).await;
|
let mut app = test::init_service(App::new().service(server.service())).await;
|
||||||
|
|
||||||
let uri = format!("/v1/client/add-version/{}", parent_version_id);
|
let uri = format!("/v1/client/add-version/{}", parent_version_id);
|
||||||
let req = test::TestRequest::post()
|
let req = test::TestRequest::post()
|
||||||
@@ -159,9 +158,9 @@ mod test {
|
|||||||
async fn test_bad_content_type() {
|
async fn test_bad_content_type() {
|
||||||
let client_key = Uuid::new_v4();
|
let client_key = Uuid::new_v4();
|
||||||
let parent_version_id = Uuid::new_v4();
|
let parent_version_id = Uuid::new_v4();
|
||||||
let server_box: Box<dyn Storage> = Box::new(InMemoryStorage::new());
|
let storage: Box<dyn Storage> = Box::new(InMemoryStorage::new());
|
||||||
let server_state = ServerState::new(server_box);
|
let server = Server::new(storage);
|
||||||
let mut app = test::init_service(App::new().service(app_scope(server_state))).await;
|
let mut app = test::init_service(App::new().service(server.service())).await;
|
||||||
|
|
||||||
let uri = format!("/v1/client/add-version/{}", parent_version_id);
|
let uri = format!("/v1/client/add-version/{}", parent_version_id);
|
||||||
let req = test::TestRequest::post()
|
let req = test::TestRequest::post()
|
||||||
@@ -178,9 +177,9 @@ mod test {
|
|||||||
async fn test_empty_body() {
|
async fn test_empty_body() {
|
||||||
let client_key = Uuid::new_v4();
|
let client_key = Uuid::new_v4();
|
||||||
let parent_version_id = Uuid::new_v4();
|
let parent_version_id = Uuid::new_v4();
|
||||||
let server_box: Box<dyn Storage> = Box::new(InMemoryStorage::new());
|
let storage: Box<dyn Storage> = Box::new(InMemoryStorage::new());
|
||||||
let server_state = ServerState::new(server_box);
|
let server = Server::new(storage);
|
||||||
let mut app = test::init_service(App::new().service(app_scope(server_state))).await;
|
let mut app = test::init_service(App::new().service(server.service())).await;
|
||||||
|
|
||||||
let uri = format!("/v1/client/add-version/{}", parent_version_id);
|
let uri = format!("/v1/client/add-version/{}", parent_version_id);
|
||||||
let req = test::TestRequest::post()
|
let req = test::TestRequest::post()
|
||||||
|
|||||||
@@ -45,8 +45,8 @@ pub(crate) async fn service(
|
|||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod test {
|
mod test {
|
||||||
use crate::api::ServerState;
|
use crate::api::ServerState;
|
||||||
use crate::app_scope;
|
|
||||||
use crate::storage::{InMemoryStorage, Storage};
|
use crate::storage::{InMemoryStorage, Storage};
|
||||||
|
use crate::Server;
|
||||||
use actix_web::{http::StatusCode, test, App};
|
use actix_web::{http::StatusCode, test, App};
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
|
|
||||||
@@ -55,18 +55,18 @@ mod test {
|
|||||||
let client_key = Uuid::new_v4();
|
let client_key = Uuid::new_v4();
|
||||||
let version_id = Uuid::new_v4();
|
let version_id = Uuid::new_v4();
|
||||||
let parent_version_id = Uuid::new_v4();
|
let parent_version_id = Uuid::new_v4();
|
||||||
let server_box: Box<dyn Storage> = Box::new(InMemoryStorage::new());
|
let storage: Box<dyn Storage> = Box::new(InMemoryStorage::new());
|
||||||
|
|
||||||
// set up the storage contents..
|
// set up the storage contents..
|
||||||
{
|
{
|
||||||
let mut txn = server_box.txn().unwrap();
|
let mut txn = storage.txn().unwrap();
|
||||||
txn.new_client(client_key, Uuid::new_v4()).unwrap();
|
txn.new_client(client_key, Uuid::new_v4()).unwrap();
|
||||||
txn.add_version(client_key, version_id, parent_version_id, b"abcd".to_vec())
|
txn.add_version(client_key, version_id, parent_version_id, b"abcd".to_vec())
|
||||||
.unwrap();
|
.unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
let server_state = ServerState::new(server_box);
|
let server = Server::new(storage);
|
||||||
let mut app = test::init_service(App::new().service(app_scope(server_state))).await;
|
let mut app = test::init_service(App::new().service(server.service())).await;
|
||||||
|
|
||||||
let uri = format!("/v1/client/get-child-version/{}", parent_version_id);
|
let uri = format!("/v1/client/get-child-version/{}", parent_version_id);
|
||||||
let req = test::TestRequest::get()
|
let req = test::TestRequest::get()
|
||||||
@@ -97,9 +97,9 @@ mod test {
|
|||||||
async fn test_client_not_found() {
|
async fn test_client_not_found() {
|
||||||
let client_key = Uuid::new_v4();
|
let client_key = Uuid::new_v4();
|
||||||
let parent_version_id = Uuid::new_v4();
|
let parent_version_id = Uuid::new_v4();
|
||||||
let server_box: Box<dyn Storage> = Box::new(InMemoryStorage::new());
|
let storage: Box<dyn Storage> = Box::new(InMemoryStorage::new());
|
||||||
let server_state = ServerState::new(server_box);
|
let server = Server::new(storage);
|
||||||
let mut app = test::init_service(App::new().service(app_scope(server_state))).await;
|
let mut app = test::init_service(App::new().service(server.service())).await;
|
||||||
|
|
||||||
let uri = format!("/v1/client/get-child-version/{}", parent_version_id);
|
let uri = format!("/v1/client/get-child-version/{}", parent_version_id);
|
||||||
let req = test::TestRequest::get()
|
let req = test::TestRequest::get()
|
||||||
@@ -116,15 +116,15 @@ mod test {
|
|||||||
async fn test_version_not_found() {
|
async fn test_version_not_found() {
|
||||||
let client_key = Uuid::new_v4();
|
let client_key = Uuid::new_v4();
|
||||||
let parent_version_id = Uuid::new_v4();
|
let parent_version_id = Uuid::new_v4();
|
||||||
let server_box: Box<dyn Storage> = Box::new(InMemoryStorage::new());
|
let storage: Box<dyn Storage> = Box::new(InMemoryStorage::new());
|
||||||
|
|
||||||
// create the client, but not the version
|
// create the client, but not the version
|
||||||
{
|
{
|
||||||
let mut txn = server_box.txn().unwrap();
|
let mut txn = storage.txn().unwrap();
|
||||||
txn.new_client(client_key, Uuid::new_v4()).unwrap();
|
txn.new_client(client_key, Uuid::new_v4()).unwrap();
|
||||||
}
|
}
|
||||||
let server_state = ServerState::new(server_box);
|
let server = Server::new(storage);
|
||||||
let mut app = test::init_service(App::new().service(app_scope(server_state))).await;
|
let mut app = test::init_service(App::new().service(server.service())).await;
|
||||||
|
|
||||||
let uri = format!("/v1/client/get-child-version/{}", parent_version_id);
|
let uri = format!("/v1/client/get-child-version/{}", parent_version_id);
|
||||||
let req = test::TestRequest::get()
|
let req = test::TestRequest::get()
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ pub(crate) const CLIENT_KEY_HEADER: &str = "X-Client-Key";
|
|||||||
pub(crate) const PARENT_VERSION_ID_HEADER: &str = "X-Parent-Version-Id";
|
pub(crate) const PARENT_VERSION_ID_HEADER: &str = "X-Parent-Version-Id";
|
||||||
|
|
||||||
/// The type containing a reference to the Storage object in the Actix state.
|
/// The type containing a reference to the Storage object in the Actix state.
|
||||||
pub(crate) type ServerState = Arc<Box<dyn Storage>>;
|
pub(crate) type ServerState = Arc<dyn Storage>;
|
||||||
|
|
||||||
pub(crate) fn api_scope() -> Scope {
|
pub(crate) fn api_scope() -> Scope {
|
||||||
web::scope("")
|
web::scope("")
|
||||||
|
|||||||
@@ -1,29 +1,9 @@
|
|||||||
#![deny(clippy::all)]
|
#![deny(clippy::all)]
|
||||||
|
|
||||||
use crate::storage::{SqliteStorage, Storage};
|
use actix_web::{middleware::Logger, App, HttpServer};
|
||||||
use actix_web::{get, middleware::Logger, web, App, HttpServer, Responder, Scope};
|
|
||||||
use api::{api_scope, ServerState};
|
|
||||||
use clap::Arg;
|
use clap::Arg;
|
||||||
|
use taskchampion_sync_server::storage::SqliteStorage;
|
||||||
mod api;
|
use taskchampion_sync_server::Server;
|
||||||
mod server;
|
|
||||||
mod storage;
|
|
||||||
|
|
||||||
// TODO: use hawk to sign requests
|
|
||||||
|
|
||||||
#[get("/")]
|
|
||||||
async fn index() -> impl Responder {
|
|
||||||
format!("TaskChampion sync server v{}", env!("CARGO_PKG_VERSION"))
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Return a scope defining the URL rules for this server, with access to
|
|
||||||
/// the given ServerState.
|
|
||||||
pub(crate) fn app_scope(server_state: ServerState) -> Scope {
|
|
||||||
web::scope("")
|
|
||||||
.data(server_state)
|
|
||||||
.service(index)
|
|
||||||
.service(api_scope())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[actix_web::main]
|
#[actix_web::main]
|
||||||
async fn main() -> anyhow::Result<()> {
|
async fn main() -> anyhow::Result<()> {
|
||||||
@@ -56,33 +36,26 @@ async fn main() -> anyhow::Result<()> {
|
|||||||
let data_dir = matches.value_of("data-dir").unwrap();
|
let data_dir = matches.value_of("data-dir").unwrap();
|
||||||
let port = matches.value_of("port").unwrap();
|
let port = matches.value_of("port").unwrap();
|
||||||
|
|
||||||
let server_box: Box<dyn Storage> = Box::new(SqliteStorage::new(data_dir)?);
|
let server = Server::new(Box::new(SqliteStorage::new(data_dir)?));
|
||||||
let server_state = ServerState::new(server_box);
|
|
||||||
|
|
||||||
log::warn!("Serving on port {}", port);
|
log::warn!("Serving on port {}", port);
|
||||||
HttpServer::new(move || {
|
HttpServer::new(move || App::new().wrap(Logger::default()).service(server.service()))
|
||||||
App::new()
|
.bind(format!("0.0.0.0:{}", port))?
|
||||||
.wrap(Logger::default())
|
.run()
|
||||||
.service(app_scope(server_state.clone()))
|
.await?;
|
||||||
})
|
|
||||||
.bind(format!("0.0.0.0:{}", port))?
|
|
||||||
.run()
|
|
||||||
.await?;
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod test {
|
mod test {
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::api::ServerState;
|
|
||||||
use crate::storage::{InMemoryStorage, Storage};
|
|
||||||
use actix_web::{test, App};
|
use actix_web::{test, App};
|
||||||
|
use taskchampion_sync_server::storage::{InMemoryStorage, Storage};
|
||||||
|
|
||||||
#[actix_rt::test]
|
#[actix_rt::test]
|
||||||
async fn test_index_get() {
|
async fn test_index_get() {
|
||||||
let server_box: Box<dyn Storage> = Box::new(InMemoryStorage::new());
|
let server = Server::new(Box::new(InMemoryStorage::new()));
|
||||||
let server_state = ServerState::new(server_box);
|
let mut app = test::init_service(App::new().service(server.service())).await;
|
||||||
let mut app = test::init_service(App::new().service(app_scope(server_state))).await;
|
|
||||||
|
|
||||||
let req = test::TestRequest::get().uri("/").to_request();
|
let req = test::TestRequest::get().uri("/").to_request();
|
||||||
let resp = test::call_service(&mut app, req).await;
|
let resp = test::call_service(&mut app, req).await;
|
||||||
37
sync-server/src/lib.rs
Normal file
37
sync-server/src/lib.rs
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
#![deny(clippy::all)]
|
||||||
|
|
||||||
|
mod api;
|
||||||
|
mod server;
|
||||||
|
pub mod storage;
|
||||||
|
|
||||||
|
use crate::storage::Storage;
|
||||||
|
use actix_web::{get, web, Responder, Scope};
|
||||||
|
use api::{api_scope, ServerState};
|
||||||
|
|
||||||
|
#[get("/")]
|
||||||
|
async fn index() -> impl Responder {
|
||||||
|
format!("TaskChampion sync server v{}", env!("CARGO_PKG_VERSION"))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A Server represents a sync server.
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct Server {
|
||||||
|
storage: ServerState,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Server {
|
||||||
|
/// Create a new sync server with the given storage implementation.
|
||||||
|
pub fn new(storage: Box<dyn Storage>) -> Self {
|
||||||
|
Self {
|
||||||
|
storage: storage.into(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get an Actix-web service for this server.
|
||||||
|
pub fn service(&self) -> Scope {
|
||||||
|
web::scope("")
|
||||||
|
.data(self.storage.clone())
|
||||||
|
.service(index)
|
||||||
|
.service(api_scope())
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -10,10 +10,11 @@ struct Inner {
|
|||||||
versions: HashMap<(Uuid, Uuid), Version>,
|
versions: HashMap<(Uuid, Uuid), Version>,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) struct InMemoryStorage(Mutex<Inner>);
|
pub struct InMemoryStorage(Mutex<Inner>);
|
||||||
|
|
||||||
impl InMemoryStorage {
|
impl InMemoryStorage {
|
||||||
pub(crate) fn new() -> Self {
|
#[allow(clippy::new_without_default)]
|
||||||
|
pub fn new() -> Self {
|
||||||
Self(Mutex::new(Inner {
|
Self(Mutex::new(Inner {
|
||||||
clients: HashMap::new(),
|
clients: HashMap::new(),
|
||||||
versions: HashMap::new(),
|
versions: HashMap::new(),
|
||||||
|
|||||||
@@ -1,27 +1,28 @@
|
|||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(debug_assertions)]
|
||||||
mod inmemory;
|
mod inmemory;
|
||||||
#[cfg(test)]
|
|
||||||
pub(crate) use inmemory::InMemoryStorage;
|
#[cfg(debug_assertions)]
|
||||||
|
pub use inmemory::InMemoryStorage;
|
||||||
|
|
||||||
mod sqlite;
|
mod sqlite;
|
||||||
pub(crate) use self::sqlite::SqliteStorage;
|
pub use self::sqlite::SqliteStorage;
|
||||||
|
|
||||||
#[derive(Clone, PartialEq, Debug, Serialize, Deserialize)]
|
#[derive(Clone, PartialEq, Debug, Serialize, Deserialize)]
|
||||||
pub(crate) struct Client {
|
pub struct Client {
|
||||||
pub(crate) latest_version_id: Uuid,
|
pub latest_version_id: Uuid,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, PartialEq, Debug, Serialize, Deserialize)]
|
#[derive(Clone, PartialEq, Debug, Serialize, Deserialize)]
|
||||||
pub(crate) struct Version {
|
pub struct Version {
|
||||||
pub(crate) version_id: Uuid,
|
pub version_id: Uuid,
|
||||||
pub(crate) parent_version_id: Uuid,
|
pub parent_version_id: Uuid,
|
||||||
pub(crate) history_segment: Vec<u8>,
|
pub history_segment: Vec<u8>,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) trait StorageTxn {
|
pub trait StorageTxn {
|
||||||
/// Get information about the given client
|
/// Get information about the given client
|
||||||
fn get_client(&mut self, client_key: Uuid) -> anyhow::Result<Option<Client>>;
|
fn get_client(&mut self, client_key: Uuid) -> anyhow::Result<Option<Client>>;
|
||||||
|
|
||||||
@@ -58,7 +59,7 @@ pub(crate) trait StorageTxn {
|
|||||||
|
|
||||||
/// A trait for objects able to act as storage. Most of the interesting behavior is in the
|
/// A trait for objects able to act as storage. Most of the interesting behavior is in the
|
||||||
/// [`crate::storage::StorageTxn`] trait.
|
/// [`crate::storage::StorageTxn`] trait.
|
||||||
pub(crate) trait Storage: Send + Sync {
|
pub trait Storage: Send + Sync {
|
||||||
/// Begin a transaction
|
/// Begin a transaction
|
||||||
fn txn<'a>(&'a self) -> anyhow::Result<Box<dyn StorageTxn + 'a>>;
|
fn txn<'a>(&'a self) -> anyhow::Result<Box<dyn StorageTxn + 'a>>;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -49,7 +49,7 @@ impl ToSql for Client {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// An on-disk storage backend which uses SQLite
|
/// An on-disk storage backend which uses SQLite
|
||||||
pub(crate) struct SqliteStorage {
|
pub struct SqliteStorage {
|
||||||
db_file: std::path::PathBuf,
|
db_file: std::path::PathBuf,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user