Add README, License, Dockerfile, docker-compose.yml

This commit is contained in:
Miroslav Vasilev 2024-01-11 16:48:54 +02:00
parent 817c742d09
commit 9bf72469a3
12 changed files with 261 additions and 37 deletions

View file

@ -1,2 +1,3 @@
DATABASE_URL= // the url to the database ( example local postgres: postgres://postgres:postgres@localhost/findthetime )
EVENT_UID_SIZE= // size of snowflake ids for events ( 20 )
EVENT_UID_SIZE= // size of snowflake ids for events ( 20 )
LOG_LEVEL= // see https://docs.rs/tracing-subscriber/0.3.15/tracing_subscriber/struct.EnvFilter.html#. Default is info

105
Cargo.lock generated
View file

@ -426,7 +426,7 @@ checksum = "25cbce373ec4653f1a01a31e8a5e5ec0c622dc27ff9c4e6606eefef5cbbed4a5"
[[package]]
name = "findtheti-me"
version = "0.1.0"
version = "0.1.2"
dependencies = [
"axum",
"chrono",
@ -439,6 +439,8 @@ dependencies = [
"sqlx",
"tokio",
"tower-http",
"tracing",
"tracing-subscriber",
]
[[package]]
@ -913,6 +915,15 @@ version = "0.4.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f"
[[package]]
name = "matchers"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8263075bb86c5a1b1427b5ae862e8889656f126e9f77c484496e8b47cf5c5558"
dependencies = [
"regex-automata 0.1.10",
]
[[package]]
name = "matchit"
version = "0.7.3"
@ -998,6 +1009,16 @@ dependencies = [
"minimal-lexical",
]
[[package]]
name = "nu-ansi-term"
version = "0.46.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "77a8165726e8236064dbb45459242600304b42a5ea24ee2948e18e023bf7ba84"
dependencies = [
"overload",
"winapi",
]
[[package]]
name = "num-bigint-dig"
version = "0.8.4"
@ -1071,6 +1092,12 @@ version = "1.19.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92"
[[package]]
name = "overload"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39"
[[package]]
name = "parking_lot"
version = "0.12.1"
@ -1245,8 +1272,17 @@ checksum = "380b951a9c5e80ddfd6136919eef32310721aa4aacd4889a8d39124b026ab343"
dependencies = [
"aho-corasick",
"memchr",
"regex-automata",
"regex-syntax",
"regex-automata 0.4.3",
"regex-syntax 0.8.2",
]
[[package]]
name = "regex-automata"
version = "0.1.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132"
dependencies = [
"regex-syntax 0.6.29",
]
[[package]]
@ -1257,9 +1293,15 @@ checksum = "5f804c7828047e88b2d32e2d7fe5a105da8ee3264f01902f796c8e067dc2483f"
dependencies = [
"aho-corasick",
"memchr",
"regex-syntax",
"regex-syntax 0.8.2",
]
[[package]]
name = "regex-syntax"
version = "0.6.29"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1"
[[package]]
name = "regex-syntax"
version = "0.8.2"
@ -1398,6 +1440,15 @@ dependencies = [
"digest",
]
[[package]]
name = "sharded-slab"
version = "0.1.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6"
dependencies = [
"lazy_static",
]
[[package]]
name = "signature"
version = "2.2.0"
@ -1757,6 +1808,16 @@ dependencies = [
"syn 2.0.48",
]
[[package]]
name = "thread_local"
version = "1.1.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3fdd6f064ccff2d6567adcb3873ca630700f00b5ad3f060c25b5dcfd9a4ce152"
dependencies = [
"cfg-if",
"once_cell",
]
[[package]]
name = "tinyvec"
version = "1.6.0"
@ -1908,6 +1969,36 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54"
dependencies = [
"once_cell",
"valuable",
]
[[package]]
name = "tracing-log"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3"
dependencies = [
"log",
"once_cell",
"tracing-core",
]
[[package]]
name = "tracing-subscriber"
version = "0.3.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ad0f048c97dbd9faa9b7df56362b8ebcaa52adb06b498c050d2f4e32f90a7a8b"
dependencies = [
"matchers",
"nu-ansi-term",
"once_cell",
"regex",
"sharded-slab",
"smallvec",
"thread_local",
"tracing",
"tracing-core",
"tracing-log",
]
[[package]]
@ -1975,6 +2066,12 @@ version = "2.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "daf8dba3b7eb870caf1ddeed7bc9d2a049f3cfdfae7cb521b087cc33ae4c49da"
[[package]]
name = "valuable"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d"
[[package]]
name = "vcpkg"
version = "0.2.15"

View file

@ -1,6 +1,6 @@
[package]
name = "findtheti-me"
version = "0.1.0"
version = "0.1.2"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
@ -16,4 +16,6 @@ serde = { version = "1.0.195", features = ["derive"] }
serde_json = "1.0.111"
sqlx = { version = "0.7.3", features = ["runtime-tokio", "postgres", "chrono"] }
tokio = {version = "1.35.1", features = ["macros", "rt-multi-thread", "rt", "net"]}
tower-http = { version = "0.5.0", features = ["fs"] }
tower-http = { version = "0.5.0", features = ["fs", "trace"] }
tracing = "0.1.40"
tracing-subscriber = { version = "0.3.18", features = ["env-filter", "std"] }

View file

@ -52,7 +52,7 @@ RUN chown -R appuser ./findtheti-me
USER appuser
ENV RUST_LOG="findtheti-me=debug,info"
ENV LOG_LEVEL=info
ENV EVENT_UID_SIZE=20
WORKDIR ./findtheti-me

21
LICENSE Normal file
View file

@ -0,0 +1,21 @@
MIT License
Copyright (c) [year] [fullname]
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View file

@ -1,15 +1,76 @@
# Setup For Development
## Backend
![](project-image.png)
A convenient scheduling assistant written in Rust and React
## Setup
The simplest way to set this application up is via docker. Its images can be found at https://hub.docker.com/r/mvv97/findthetime.
Also, it is only compatible with PostgreSQL at the moment. It is required to have a PostgreSQL database already setup and running.
### Simple (With Docker)
To use `findtheti.me` with docker, simply run
```sh
docker run
-e DATABASE_URL='postgresql://{postgres user}:{postgres password}@{postgres host}/{postgres database}'
mvv97/findthetime
```
#### Example docker-compose.yml
```yml
version: "3.4"
services:
postgresql:
image: "docker.io/library/postgres:16-alpine"
restart: unless-stopped
volumes:
- '/data/findtheti-me/postgres_data:/var/lib/postgresql/data'
environment:
POSTGRES_PASSWORD: ${PG_PASS:?database password required}
POSTGRES_USER: ${PG_USER:-findthetime}
POSTGRES_DB: ${PG_DB:-findthetime}
findthetime:
image: "docker.io/mvv97/findthetime:latest"
restart: unless-stopped
environment:
DATABASE_URL: "postgres://${PG_USER:-findthetime}:${PG_PASS}@postgresql/${PG_DB:-findthetime}"
ports:
- '8080:8080'
```
### Advanced (Without Docker)
1. Compile Backend (`cargo build --release`)
2. Build Frontend (`cd frontend && yarn install --production && yarn build`)
3. Copy the `findtheti-me` file from `target/release` and place it into your desired installation folder
4. Copy the `frontend/dist` folder and place it into the same installation folder, maintaining the directory tree.
In the end, your folder structure should be as follows:
```
installationDir/
| |-frontend/
| |-dist/
|-findtheti-me
```
5. Next, create a `.env` file in the root of the installation directory, and look at `.env.example` for what should be in there
Finally, run `./findtheti-me` in the root, and the application should start.
## Setup For Development
### Backend
1. Create a PostgreSQL database
2. Configure a `.env` in the project root directory ( following `.env.example` )
3. Run `cargo sqlx migrate run` to run all migrations ( ensure you've created the database beforehand )
4. `cargo run`
4. `cargo run` to run the backend ( or `cargo build` to compile it, with the `--release` flag for an optimized build )
## Frontend
### Frontend
1. `yarn install`
2. `yarn dev` ( or `yarn build`/`yarn preview` )
## Docker Build Image
### Docker Build Image
1. Do Backend and Frontend setups first
2. Run `cargo sqlx prepare` ( ensure .sqlx directory has been created. The one included in this git repo may be out of date. )
3. `docker build .` ( or `podman build .` ) in root directory

21
docker-compose.yml Normal file
View file

@ -0,0 +1,21 @@
version: "3.4"
services:
postgresql:
image: "docker.io/library/postgres:16-alpine"
restart: unless-stopped
volumes:
- '/data/findtheti-me/postgres_data:/var/lib/postgresql/data'
environment:
POSTGRES_PASSWORD: ${PG_PASS:?database password required}
POSTGRES_USER: ${PG_USER:-findthetime}
POSTGRES_DB: ${PG_DB:-findthetime}
findthetime:
image: "docker.io/mvv97/findthetime:latest"
restart: unless-stopped
environment:
DATABASE_URL: "postgres://${PG_USER:-findthetime}:${PG_PASS}@postgresql/${PG_DB:-findthetime}"
ports:
- '8080:8080'

BIN
project-image.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

View file

@ -46,7 +46,9 @@ pub(crate) fn error<T: Serialize>(e: ApplicationError) -> UniversalResponseDto<T
UniversalResponseDto {
status: e.status,
result: None,
error: Some(ErrorDto { message: format!("{}", e)})
error: Some(ErrorDto {
message: format!("{}", e),
}),
}
}
@ -128,7 +130,7 @@ impl From<sqlx::Error> for ApplicationError {
fn from(value: sqlx::Error) -> Self {
Self {
msg: value.to_string(),
status: StatusCode::INTERNAL_SERVER_ERROR
status: StatusCode::INTERNAL_SERVER_ERROR,
}
}
}
@ -137,7 +139,7 @@ impl From<MigrateError> for ApplicationError {
fn from(value: MigrateError) -> Self {
Self {
msg: value.to_string(),
status: StatusCode::INTERNAL_SERVER_ERROR
status: StatusCode::INTERNAL_SERVER_ERROR,
}
}
}
@ -146,7 +148,7 @@ impl From<VarError> for ApplicationError {
fn from(value: VarError) -> Self {
Self {
msg: value.to_string(),
status: StatusCode::INTERNAL_SERVER_ERROR
status: StatusCode::INTERNAL_SERVER_ERROR,
}
}
}

View file

@ -1,8 +1,9 @@
use std::net::SocketAddr;
use axum::{
extract::{Path, State, ConnectInfo},
Json, http::StatusCode,
extract::{ConnectInfo, Path, State},
http::StatusCode,
Json,
};
use chrono::{DateTime, TimeZone, Utc};
use rand::{distributions::Alphanumeric, Rng};
@ -15,7 +16,7 @@ use crate::{
entity::{
availability::Availability,
event::{Event, EventType},
}
},
};
#[derive(Deserialize)]
@ -25,7 +26,7 @@ pub struct CreateEventDto {
name: String,
description: Option<String>,
event_type: String,
duration: i32
duration: i32,
}
#[derive(Serialize)]
@ -36,7 +37,7 @@ pub struct EventDto {
name: String,
description: Option<String>,
event_type: String,
duration: i32
duration: i32,
}
#[derive(Deserialize)]
@ -164,7 +165,7 @@ pub async fn fetch_event(
name: event.name,
description: event.description,
event_type: event.event_type.to_string(),
duration: event.duration
duration: event.duration,
})
})
})
@ -194,7 +195,9 @@ pub async fn create_availabilities(
let current_availabilities = db::fetch_event_availabilities(txn, event.id).await?;
let already_submitted = current_availabilities.iter().any(|a| {
(dto.user_email.is_some() && a.user_email.is_some() && a.user_email == dto.user_email)
(dto.user_email.is_some()
&& a.user_email.is_some()
&& a.user_email == dto.user_email)
|| a.user_ip == user_ip
|| a.user_name == dto.user_name
});
@ -202,7 +205,7 @@ pub async fn create_availabilities(
if already_submitted {
return Err(ApplicationError::new(
"Availability already submitted".to_string(),
StatusCode::UNPROCESSABLE_ENTITY
StatusCode::UNPROCESSABLE_ENTITY,
));
}

View file

@ -10,7 +10,7 @@ pub(crate) struct Event {
pub from_date: Option<NaiveDateTime>,
pub to_date: Option<NaiveDateTime>,
pub event_type: EventType,
pub duration: i32
pub duration: i32,
}
#[derive(Debug)]

View file

@ -3,7 +3,10 @@ use std::net::SocketAddr;
use axum::Router;
use dotenv::dotenv;
use tokio::net::TcpListener;
use tower_http::services::ServeDir;
use tower_http::trace::TraceLayer;
use tower_http::{services::ServeDir, trace};
use tracing::Level;
use tracing_subscriber::{fmt, layer::SubscriberExt, util::SubscriberInitExt, EnvFilter};
mod api;
mod db;
@ -16,29 +19,42 @@ async fn main() {
dotenv().ok();
tracing_subscriber::registry()
.with(fmt::layer())
.with(EnvFilter::from_env("LOG_LEVEL"))
.init();
let api_routes = api::routes().await.expect("Unable to create api routes");
let mut routes = Router::new()
.nest("/api", api_routes);
let mut routes = Router::new().nest("/api", api_routes);
// If in release mod, serve static files
if !cfg!(debug_assertions) {
println!("Initializing frontend routes...");
routes = routes.nest_service("/", ServeDir::new("./frontend/dist"))
routes = routes
.nest_service("/", ServeDir::new("./frontend/dist"))
.fallback_service(ServeDir::new("./frontend/dist"));
}
println!("Routes initialized...");
let addr = SocketAddr::from(([127, 0, 0, 1], 8080));
let addr = SocketAddr::from(([0, 0, 0, 0], 8080));
let listener = TcpListener::bind(addr).await.unwrap();
println!("Starting server...");
axum::serve(listener, routes.into_make_service_with_connect_info::<SocketAddr>())
.await
.unwrap();
}
axum::serve(
listener,
routes
.layer(
TraceLayer::new_for_http()
.make_span_with(trace::DefaultMakeSpan::new().level(Level::INFO))
.on_response(trace::DefaultOnResponse::new().level(Level::INFO)),
)
.into_make_service_with_connect_info::<SocketAddr>(),
)
.await
.unwrap();
}