Skip to content

Commit

Permalink
Merge pull request #3 from Clem-Fern/dev
Browse files Browse the repository at this point in the history
0.2.0
  • Loading branch information
Clem-Fern authored Nov 4, 2023
2 parents e9f3b67 + 80c2414 commit ec8c429
Show file tree
Hide file tree
Showing 9 changed files with 216 additions and 121 deletions.
182 changes: 96 additions & 86 deletions Cargo.lock

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "rtabby-web-api"
version = "0.1.0"
version = "0.2.0"
edition = "2021"

[features]
Expand Down
44 changes: 29 additions & 15 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,34 +27,40 @@ To run your own instance with docker compose.

### Installation

* From source
```sh
git clone https://github.com/Clem-Fern/rtabby-web-api
```

* Using rtabby-web-api image from Github Docker Repository
* Using rtabby-web-api image from Github Docker Repository **(recommended)**
```sh
docker pull ghcr.io/clem-fern/rtabby-web-api
mkdir -p rtabby-web-api/config
cd rtabby-web-api
wget https://raw.githubusercontent.com/Clem-Fern/rtabby-web-api/master/docker-compose.yml
```
Edit `docker-compose.yml` to use the image you have pulled before

* From source
```sh
git clone https://github.com/Clem-Fern/rtabby-web-api
cd rtabby-web-api
```

Edit `docker-compose.yml` to use local context build instead of the published image
```yaml
# Comment build line
#build: .
# Uncomment image
image: rtabby-web-api
# Uncomment build line
build: .
# Comment image
# image: rtabby-web-api
```


### Configuration

1. Create `config` directory. It will be used to store your config and certificate(not mandatory)

```sh
cd rtabby-web-api
# pwd
# ./rtabby-web-api
mkdir config
cp users.exemple.yml config/users.yml
# Only from source installation and optional
# users.yml file will be created at first start
# cp users.exemple.yml config/users.yml
```

2. Add some user into `users.yml`.
Expand All @@ -76,7 +82,8 @@ To run your own instance with docker compose.
- "8080:8080"
environment:
- DATABASE_URL=mysql://tabby:tabby@db/tabby
-
- SSL_CERTIFICATE=cert.pem
- SSL_CERTIFICATE_KEY=cert.key
volumes:
- ./config:/config
```
Expand All @@ -92,6 +99,7 @@ To run your own instance with docker compose.
|BIND_PORT|Port listening on (Optional)|8989|8080|
|SSL_CERTIFICATE|Server certificate (Optional)|cert.pem|None|
|SSL_CERTIFICATE_KEY|Server certificate private key(Optional)|private.key|None|
|CLEANUP_USERS|Delete configurations own by unknown user (Be careful)(Optional)|true|false|

## Usage

Expand All @@ -107,4 +115,10 @@ To run your own instance with docker compose.

## Contributing

Build dependencies:
* Docker
* libmysqlclient for the Mysql backend (diesel depend on this)
* Rust 1.65 or later
* Diesel-rs to interact with migrations and schemas

Feel free to fork, request some features, submit issue or PR. Even give me some tips if you want, to help improve my code and knowledge in Rust ;)
34 changes: 31 additions & 3 deletions docker-compose.yml
Original file line number Diff line number Diff line change
@@ -1,20 +1,42 @@
services:
rtabby:
container_name: rtabby-web-api
build: .
#image: rtabby-web-api
#build: .
image: ghcr.io/clem-fern/rtabby-web-api

# If running as root, setup your user/volume owner UID and GID
#user: "1000:1000"

cap_add:
- "CAP_DAC_OVERRIDE"
cap_drop: ['ALL']
read_only: true

ports:
- "8080:8080"
environment:
- DATABASE_URL=mysql://tabby:tabby@db/tabby
volumes:
- ./config:/config
networks:
- frontend
- default
depends_on:
db:
condition: 'service_healthy'
db:
container_name: rtabby-database
image: mariadb:latest
cap_add:
- "CAP_CHOWN"
- "CAP_DAC_OVERRIDE"
- "CAP_SETGID"
- "CAP_SETUID"
cap_drop: ['ALL']
read_only: true
tmpfs:
- /run/mysqld/
- /tmp
volumes:
- database:/var/lib/mysql
environment:
Expand All @@ -30,4 +52,10 @@ services:
retries: 20
start_period: 6s
volumes:
database:
database:
networks:
default:
name: rtabby_net_backend
internal: true
frontend:
name: rtabby_net_frontend
14 changes: 14 additions & 0 deletions src/app_config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@ use serde::Deserialize;
use crate::models::user::{User, UserWithoutToken};
use crate::error::ConfigError;
use std::collections::HashMap;
use std::fs::File;
use std::path::Path;
use std::io::Write;

#[derive(Clone, Debug, Deserialize)]
pub struct AppConfig {
Expand All @@ -14,6 +17,17 @@ pub fn load_file(file: &str) -> Result<AppConfig, ConfigError> {
serde_yaml::from_reader(config_file).map_err(ConfigError::Yaml)
}

pub fn create_config_file_if_not_exist(file: &str) -> Result<(), ConfigError> {
let path = Path::new(file);
if path.exists() {
Ok(())
} else {
let mut config = File::create(path)?;
write!(config, include_str!("../users.exemple.yml"))?;
Err(ConfigError::NoConfig(String::from(file)))
}
}

#[derive(Clone, Debug, Default)]
pub struct MappedAppConfig {
pub users: HashMap<String, UserWithoutToken>,
Expand Down
1 change: 1 addition & 0 deletions src/env.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ pub const ENV_BIND_ADDR: &str = "BIND_ADDR";
pub const ENV_BIND_PORT: &str = "BIND_PORT";
pub const ENV_SSL_CERTIFICATE: &str = "SSL_CERTIFICATE";
pub const ENV_SSL_CERTIFICATE_KEY: &str = "SSL_CERTIFICATE_KEY";
pub const ENV_CLEANUP_USERS: &str = "CLEANUP_USERS";

pub const ENV_DATABASE_URL: &str = "DATABASE_URL";

Expand Down
32 changes: 20 additions & 12 deletions src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,17 +6,17 @@ use diesel::r2d2;
use crate::models::DbError;

#[derive(Debug)]
pub enum StorageInitializationError {
pub enum StorageError {
Migration(Box<dyn error::Error + Send + Sync + 'static>),
R2d2(r2d2::PoolError),
#[allow(dead_code)]
Db(DbError),
MysqlConnection(diesel::ConnectionError)
}

impl error::Error for StorageInitializationError {}
impl error::Error for StorageError {}

impl fmt::Display for StorageInitializationError {
impl fmt::Display for StorageError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match *self {
Self::Migration(ref err) => write!(f, "Failed to initialize databse storage (diesel migrations): {err}"),
Expand All @@ -27,21 +27,27 @@ impl fmt::Display for StorageInitializationError {
}
}

impl From<Box<dyn error::Error + Send + Sync + 'static>> for StorageInitializationError {
fn from(err: Box<dyn error::Error + Send + Sync + 'static>) -> StorageInitializationError {
StorageInitializationError::Migration(err)
impl From<Box<dyn error::Error + Send + Sync>> for StorageError {
fn from(err: Box<dyn error::Error + Send + Sync>) -> StorageError {
StorageError::Migration(err)
}
}

impl From<r2d2::PoolError> for StorageInitializationError {
fn from(err: r2d2::PoolError) -> StorageInitializationError {
StorageInitializationError::R2d2(err)
impl From<r2d2::PoolError> for StorageError {
fn from(err: r2d2::PoolError) -> StorageError {
StorageError::R2d2(err)
}
}

impl From<diesel::ConnectionError> for StorageInitializationError {
fn from(err: diesel::ConnectionError) -> StorageInitializationError {
StorageInitializationError::MysqlConnection(err)
impl From<diesel::ConnectionError> for StorageError {
fn from(err: diesel::ConnectionError) -> StorageError {
StorageError::MysqlConnection(err)
}
}

impl From<diesel::result::Error> for StorageError {
fn from(err: diesel::result::Error) -> StorageError {
StorageError::Db(Box::new(err))
}
}

Expand Down Expand Up @@ -80,6 +86,7 @@ pub enum ConfigError {
Yaml(serde_yaml::Error),
#[allow(dead_code)]
DuplicatedEntry(String),
NoConfig(String),
}

impl error::Error for ConfigError {}
Expand All @@ -90,6 +97,7 @@ impl fmt::Display for ConfigError {
Self::Io(ref err) => write!(f, "Encountered IO error while building deserializing configuration: {err}"),
Self::Yaml(ref err) => write!(f, "Encountered Yaml error while building deserializing configuration: {err}"),
Self::DuplicatedEntry(ref entry) => write!(f, "The following data is not unique in configuration: {entry}"),
Self::NoConfig(ref entry) => write!(f, "{entry} configuration not found. The file has beeen created from template. Edit {entry} to add your own users.")
}
}
}
Expand Down
13 changes: 11 additions & 2 deletions src/main.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
extern crate env_logger;
extern crate log;
use log::{info, error};
use log::{info, error, warn};

use std::error::Error;

Expand Down Expand Up @@ -55,6 +55,10 @@ async fn main() -> Result<(), Box<dyn Error>> {
async fn run_app() -> Result<(), Box<dyn Error>> {
// LOAD CONFIG FILE
let config_file_name = env::var(env::ENV_CONFIG_FILE).unwrap_or(String::from("users.yml"));

// Check if the config file already exist, else create one and exit
app_config::create_config_file_if_not_exist(&config_file_name)?;

let config: AppConfig = app_config::load_file(&config_file_name)?;
let config: MappedAppConfig = config.into();

Expand All @@ -64,7 +68,12 @@ async fn run_app() -> Result<(), Box<dyn Error>> {
let storage: Storage = Storage::new();
storage.init()?;

// TODO : storage clean up on start
// storage clean up on start
if env::var(env::ENV_CLEANUP_USERS).unwrap_or(String::from("false")).to_lowercase().parse().unwrap_or(false) {
warn!("Cleaning up old user configurations from storage.");
storage.cleanup(&config)?;
}


let pool = storage.pool()?;

Expand Down
15 changes: 13 additions & 2 deletions src/storage.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ use diesel_migrations::{embed_migrations, EmbeddedMigrations, MigrationHarness};
use log::info;
use diesel::r2d2::{Pool, ConnectionManager};

use crate::app_config::MappedAppConfig;
use crate::env;
use crate::error;

Expand All @@ -30,15 +31,25 @@ impl Storage {
&self.url
}

pub fn init(&self) -> Result<(), error::StorageInitializationError> {
pub fn init(&self) -> Result<(), error::StorageError> {
let mut conn = establish_connection(self.url().as_str())?;

run_migrations(&mut conn)?; // RUN PENDING MIGRATIONS

Ok(())
}

pub fn pool(&self) -> Result<MySqlPool, error::StorageInitializationError> {
pub fn cleanup(&self, app_config: &MappedAppConfig) -> Result<(), error::StorageError> {
let mut conn = establish_connection(self.url().as_str())?;

use crate::schema::configs::dsl::*;

diesel::delete(configs.filter(user.ne_all(app_config.users.keys()))).execute(&mut conn)?;

Ok(())
}

pub fn pool(&self) -> Result<MySqlPool, error::StorageError> {
let pool = Pool::new(ConnectionManager::new(self.url().clone()))?;
Ok(pool)
}
Expand Down

0 comments on commit ec8c429

Please sign in to comment.