mirror of
https://github.com/AppFlowy-IO/AppFlowy.git
synced 2024-08-30 18:12:39 +00:00
config server launch
This commit is contained in:
parent
49e5f38406
commit
54342850b2
@ -23,9 +23,11 @@ log = "0.4.14"
|
||||
serde_json = "1.0"
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
serde_repr = "0.1"
|
||||
serde-aux = "1.0.1"
|
||||
derive_more = {version = "0.99", features = ["display"]}
|
||||
protobuf = {version = "2.20.0"}
|
||||
uuid = { version = "0.8", features = ["serde", "v4"] }
|
||||
config = { version = "0.10.1", default-features = false, features = ["yaml"] }
|
||||
|
||||
flowy-log = { path = "../rust-lib/flowy-log" }
|
||||
flowy-user = { path = "../rust-lib/flowy-user" }
|
||||
|
9
backend/configuration/base.yaml
Normal file
9
backend/configuration/base.yaml
Normal file
@ -0,0 +1,9 @@
|
||||
application:
|
||||
port: 8000
|
||||
host: 0.0.0.0
|
||||
database:
|
||||
host: "localhost"
|
||||
port: 5433
|
||||
username: "postgres"
|
||||
password: "password"
|
||||
database_name: "flowy"
|
5
backend/configuration/local.yaml
Normal file
5
backend/configuration/local.yaml
Normal file
@ -0,0 +1,5 @@
|
||||
application:
|
||||
host: 127.0.0.1
|
||||
base_url: "http://127.0.0.1"
|
||||
database:
|
||||
require_ssl: false
|
4
backend/configuration/production.yaml
Normal file
4
backend/configuration/production.yaml
Normal file
@ -0,0 +1,4 @@
|
||||
application:
|
||||
host: 0.0.0.0
|
||||
database:
|
||||
require_ssl: true
|
@ -2,6 +2,22 @@
|
||||
set -x
|
||||
set -eo pipefail
|
||||
|
||||
if ! [ -x "$(command -v psql)" ]; then
|
||||
echo >&2 "Error: `psql` is not installed."
|
||||
echo >&2 "install using brew: brew install libpq."
|
||||
echo >&2 "link to /usr/local/bin: brew link --force libpq ail"
|
||||
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if ! [ -x "$(command -v sqlx)" ]; then
|
||||
echo >&2 "Error: `sqlx` is not installed."
|
||||
echo >&2 "Use:"
|
||||
echo >&2 " cargo install --version=0.5.5 sqlx-cli --no-default-features --features postgres"
|
||||
echo >&2 "to install it."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
until psql -h "localhost" -U "${DB_USER}" -p "${DB_PORT}" -d "postgres" -c '\q';
|
||||
do
|
||||
>&2 echo "Postgres is still unavailable - sleeping"
|
||||
|
83
backend/src/application.rs
Normal file
83
backend/src/application.rs
Normal file
@ -0,0 +1,83 @@
|
||||
use crate::{
|
||||
config::{get_configuration, DatabaseSettings, Settings},
|
||||
context::AppContext,
|
||||
routers::*,
|
||||
user_service::Auth,
|
||||
ws_service::WSServer,
|
||||
};
|
||||
use actix::Actor;
|
||||
use actix_web::{dev::Server, middleware, web, web::Data, App, HttpServer, Scope};
|
||||
use sqlx::{postgres::PgPoolOptions, PgPool};
|
||||
use std::{net::TcpListener, sync::Arc};
|
||||
|
||||
pub struct Application {
|
||||
port: u16,
|
||||
server: Server,
|
||||
app_ctx: Arc<AppContext>,
|
||||
}
|
||||
|
||||
impl Application {
|
||||
pub async fn build(configuration: Settings) -> Result<Self, std::io::Error> {
|
||||
let app_ctx = init_app_context(&configuration).await;
|
||||
let address = format!(
|
||||
"{}:{}",
|
||||
configuration.application.host, configuration.application.port
|
||||
);
|
||||
let listener = TcpListener::bind(&address)?;
|
||||
let port = listener.local_addr().unwrap().port();
|
||||
let server = run(listener, app_ctx.clone())?;
|
||||
Ok(Self {
|
||||
port,
|
||||
server,
|
||||
app_ctx,
|
||||
})
|
||||
}
|
||||
|
||||
pub async fn run_until_stopped(self) -> Result<(), std::io::Error> { self.server.await }
|
||||
}
|
||||
|
||||
pub fn run(listener: TcpListener, app_ctx: Arc<AppContext>) -> Result<Server, std::io::Error> {
|
||||
let server = HttpServer::new(move || {
|
||||
App::new()
|
||||
.wrap(middleware::Logger::default())
|
||||
.app_data(web::JsonConfig::default().limit(4096))
|
||||
.service(ws_scope())
|
||||
.service(user_scope())
|
||||
.app_data(Data::new(app_ctx.ws_server.clone()))
|
||||
.app_data(Data::new(app_ctx.db_pool.clone()))
|
||||
.app_data(Data::new(app_ctx.auth.clone()))
|
||||
})
|
||||
.listen(listener)?
|
||||
.run();
|
||||
Ok(server)
|
||||
}
|
||||
|
||||
fn ws_scope() -> Scope { web::scope("/ws").service(ws::start_connection) }
|
||||
|
||||
fn user_scope() -> Scope {
|
||||
web::scope("/user").service(web::resource("/register").route(web::post().to(user::register)))
|
||||
}
|
||||
|
||||
async fn init_app_context(configuration: &Settings) -> Arc<AppContext> {
|
||||
let _ = flowy_log::Builder::new("flowy").env_filter("Debug").build();
|
||||
let pg_pool = Arc::new(
|
||||
get_connection_pool(&configuration.database)
|
||||
.await
|
||||
.expect("Failed to connect to Postgres."),
|
||||
);
|
||||
|
||||
let ws_server = WSServer::new().start();
|
||||
|
||||
let auth = Arc::new(Auth::new(pg_pool.clone()));
|
||||
|
||||
let ctx = AppContext::new(ws_server, pg_pool, auth);
|
||||
|
||||
Arc::new(ctx)
|
||||
}
|
||||
|
||||
pub async fn get_connection_pool(configuration: &DatabaseSettings) -> Result<PgPool, sqlx::Error> {
|
||||
PgPoolOptions::new()
|
||||
.connect_timeout(std::time::Duration::from_secs(2))
|
||||
.connect_with(configuration.with_db())
|
||||
.await
|
||||
}
|
@ -1,48 +0,0 @@
|
||||
use crate::config::DatabaseConfig;
|
||||
use std::{convert::TryFrom, sync::Arc};
|
||||
|
||||
pub struct Config {
|
||||
pub http_port: u16,
|
||||
pub database: Arc<DatabaseConfig>,
|
||||
}
|
||||
|
||||
impl Config {
|
||||
pub fn new() -> Self {
|
||||
Config {
|
||||
http_port: 3030,
|
||||
database: Arc::new(DatabaseConfig::default()),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn server_addr(&self) -> String { format!("0.0.0.0:{}", self.http_port) }
|
||||
}
|
||||
|
||||
pub enum Environment {
|
||||
Local,
|
||||
Production,
|
||||
}
|
||||
|
||||
impl Environment {
|
||||
#[allow(dead_code)]
|
||||
pub fn as_str(&self) -> &'static str {
|
||||
match self {
|
||||
Environment::Local => "local",
|
||||
Environment::Production => "production",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<String> for Environment {
|
||||
type Error = String;
|
||||
|
||||
fn try_from(s: String) -> Result<Self, Self::Error> {
|
||||
match s.to_lowercase().as_str() {
|
||||
"local" => Ok(Self::Local),
|
||||
"production" => Ok(Self::Production),
|
||||
other => Err(format!(
|
||||
"{} is not a supported environment. Use either `local` or `production`.",
|
||||
other
|
||||
)),
|
||||
}
|
||||
}
|
||||
}
|
99
backend/src/config/configuration.rs
Normal file
99
backend/src/config/configuration.rs
Normal file
@ -0,0 +1,99 @@
|
||||
use serde_aux::field_attributes::deserialize_number_from_string;
|
||||
use sqlx::postgres::{PgConnectOptions, PgSslMode};
|
||||
use std::convert::{TryFrom, TryInto};
|
||||
|
||||
#[derive(serde::Deserialize, Clone)]
|
||||
pub struct Settings {
|
||||
pub database: DatabaseSettings,
|
||||
pub application: ApplicationSettings,
|
||||
}
|
||||
|
||||
#[derive(serde::Deserialize, Clone)]
|
||||
pub struct ApplicationSettings {
|
||||
#[serde(deserialize_with = "deserialize_number_from_string")]
|
||||
pub port: u16,
|
||||
pub host: String,
|
||||
pub base_url: String,
|
||||
}
|
||||
|
||||
#[derive(serde::Deserialize, Clone)]
|
||||
pub struct DatabaseSettings {
|
||||
pub username: String,
|
||||
pub password: String,
|
||||
#[serde(deserialize_with = "deserialize_number_from_string")]
|
||||
pub port: u16,
|
||||
pub host: String,
|
||||
pub database_name: String,
|
||||
pub require_ssl: bool,
|
||||
}
|
||||
|
||||
impl DatabaseSettings {
|
||||
pub fn without_db(&self) -> PgConnectOptions {
|
||||
let ssl_mode = if self.require_ssl {
|
||||
PgSslMode::Require
|
||||
} else {
|
||||
PgSslMode::Prefer
|
||||
};
|
||||
PgConnectOptions::new()
|
||||
.host(&self.host)
|
||||
.username(&self.username)
|
||||
.password(&self.password)
|
||||
.port(self.port)
|
||||
.ssl_mode(ssl_mode)
|
||||
}
|
||||
|
||||
pub fn with_db(&self) -> PgConnectOptions { self.without_db().database(&self.database_name) }
|
||||
}
|
||||
|
||||
pub fn get_configuration() -> Result<Settings, config::ConfigError> {
|
||||
let mut settings = config::Config::default();
|
||||
let base_path = std::env::current_dir().expect("Failed to determine the current directory");
|
||||
let configuration_dir = base_path.join("configuration");
|
||||
|
||||
settings.merge(config::File::from(configuration_dir.join("base")).required(true))?;
|
||||
|
||||
let environment: Environment = std::env::var("APP_ENVIRONMENT")
|
||||
.unwrap_or_else(|_| "local".into())
|
||||
.try_into()
|
||||
.expect("Failed to parse APP_ENVIRONMENT.");
|
||||
|
||||
settings
|
||||
.merge(config::File::from(configuration_dir.join(environment.as_str())).required(true))?;
|
||||
|
||||
// Add in settings from environment variables (with a prefix of APP and '__' as
|
||||
// separator) E.g. `APP_APPLICATION__PORT=5001 would set
|
||||
// `Settings.application.port`
|
||||
settings.merge(config::Environment::with_prefix("app").separator("__"))?;
|
||||
|
||||
settings.try_into()
|
||||
}
|
||||
|
||||
/// The possible runtime environment for our application.
|
||||
pub enum Environment {
|
||||
Local,
|
||||
Production,
|
||||
}
|
||||
|
||||
impl Environment {
|
||||
pub fn as_str(&self) -> &'static str {
|
||||
match self {
|
||||
Environment::Local => "local",
|
||||
Environment::Production => "production",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<String> for Environment {
|
||||
type Error = String;
|
||||
|
||||
fn try_from(s: String) -> Result<Self, Self::Error> {
|
||||
match s.to_lowercase().as_str() {
|
||||
"local" => Ok(Self::Local),
|
||||
"production" => Ok(Self::Production),
|
||||
other => Err(format!(
|
||||
"{} is not a supported environment. Use either `local` or `production`.",
|
||||
other
|
||||
)),
|
||||
}
|
||||
}
|
||||
}
|
@ -1,5 +0,0 @@
|
||||
host = "localhost"
|
||||
port = 5433
|
||||
username = "postgres"
|
||||
password = "password"
|
||||
database_name = "flowy"
|
@ -1,33 +0,0 @@
|
||||
use serde::Deserialize;
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct DatabaseConfig {
|
||||
username: String,
|
||||
password: String,
|
||||
port: u16,
|
||||
host: String,
|
||||
database_name: String,
|
||||
}
|
||||
|
||||
impl DatabaseConfig {
|
||||
pub fn connect_url(&self) -> String {
|
||||
format!(
|
||||
"postgres://{}:{}@{}:{}/{}",
|
||||
self.username, self.password, self.host, self.port, self.database_name
|
||||
)
|
||||
}
|
||||
|
||||
pub fn set_env_db_url(&self) {
|
||||
let url = self.connect_url();
|
||||
std::env::set_var("DATABASE_URL", url);
|
||||
}
|
||||
}
|
||||
|
||||
impl std::default::Default for DatabaseConfig {
|
||||
fn default() -> DatabaseConfig {
|
||||
let toml_str: &str = include_str!("config.toml");
|
||||
let config: DatabaseConfig = toml::from_str(toml_str).unwrap();
|
||||
config.set_env_db_url();
|
||||
config
|
||||
}
|
||||
}
|
@ -1,3 +0,0 @@
|
||||
mod database;
|
||||
|
||||
pub use database::*;
|
@ -1,7 +1,5 @@
|
||||
mod config;
|
||||
mod configuration;
|
||||
mod const_define;
|
||||
mod database;
|
||||
|
||||
pub use config::*;
|
||||
pub use configuration::*;
|
||||
pub use const_define::*;
|
||||
pub use database::*;
|
||||
|
@ -1,25 +1,18 @@
|
||||
use crate::{config::Config, user_service::Auth, ws_service::WSServer};
|
||||
use crate::{user_service::Auth, ws_service::WSServer};
|
||||
use actix::Addr;
|
||||
|
||||
use sqlx::PgPool;
|
||||
use std::sync::Arc;
|
||||
|
||||
pub struct AppContext {
|
||||
pub config: Arc<Config>,
|
||||
pub ws_server: Addr<WSServer>,
|
||||
pub db_pool: Arc<PgPool>,
|
||||
pub auth: Arc<Auth>,
|
||||
}
|
||||
|
||||
impl AppContext {
|
||||
pub fn new(
|
||||
config: Arc<Config>,
|
||||
ws_server: Addr<WSServer>,
|
||||
db_pool: Arc<PgPool>,
|
||||
auth: Arc<Auth>,
|
||||
) -> Self {
|
||||
pub fn new(ws_server: Addr<WSServer>, db_pool: Arc<PgPool>, auth: Arc<Auth>) -> Self {
|
||||
AppContext {
|
||||
config,
|
||||
ws_server,
|
||||
db_pool,
|
||||
auth,
|
||||
|
@ -1,7 +1,7 @@
|
||||
mod config;
|
||||
pub mod application;
|
||||
pub mod config;
|
||||
mod context;
|
||||
mod entities;
|
||||
mod routers;
|
||||
pub mod startup;
|
||||
pub mod user_service;
|
||||
pub mod ws_service;
|
||||
|
@ -1,10 +1,11 @@
|
||||
use backend::startup::{init_app_context, run};
|
||||
use backend::{application::Application, config::get_configuration};
|
||||
use std::net::TcpListener;
|
||||
|
||||
#[actix_web::main]
|
||||
async fn main() -> std::io::Result<()> {
|
||||
let app_ctx = init_app_context().await;
|
||||
let listener =
|
||||
TcpListener::bind(app_ctx.config.server_addr()).expect("Failed to bind server address");
|
||||
run(app_ctx, listener)?.await
|
||||
let configuration = get_configuration().expect("Failed to read configuration.");
|
||||
let application = Application::build(configuration).await?;
|
||||
application.run_until_stopped().await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
@ -1,50 +0,0 @@
|
||||
use crate::{
|
||||
config::Config,
|
||||
context::AppContext,
|
||||
routers::*,
|
||||
user_service::Auth,
|
||||
ws_service::WSServer,
|
||||
};
|
||||
use actix::Actor;
|
||||
use actix_web::{dev::Server, middleware, web, web::Data, App, HttpServer, Scope};
|
||||
use sqlx::PgPool;
|
||||
use std::{net::TcpListener, sync::Arc};
|
||||
|
||||
pub fn run(app_ctx: Arc<AppContext>, listener: TcpListener) -> Result<Server, std::io::Error> {
|
||||
let server = HttpServer::new(move || {
|
||||
App::new()
|
||||
.wrap(middleware::Logger::default())
|
||||
.app_data(web::JsonConfig::default().limit(4096))
|
||||
.service(ws_scope())
|
||||
.service(user_scope())
|
||||
.app_data(Data::new(app_ctx.ws_server.clone()))
|
||||
.app_data(Data::new(app_ctx.db_pool.clone()))
|
||||
.app_data(Data::new(app_ctx.auth.clone()))
|
||||
})
|
||||
.listen(listener)?
|
||||
.run();
|
||||
Ok(server)
|
||||
}
|
||||
|
||||
fn ws_scope() -> Scope { web::scope("/ws").service(ws::start_connection) }
|
||||
|
||||
fn user_scope() -> Scope {
|
||||
web::scope("/user").service(web::resource("/register").route(web::post().to(user::register)))
|
||||
}
|
||||
|
||||
pub async fn init_app_context() -> Arc<AppContext> {
|
||||
let _ = flowy_log::Builder::new("flowy").env_filter("Debug").build();
|
||||
let config = Arc::new(Config::new());
|
||||
|
||||
// TODO: what happened when PgPool connect fail?
|
||||
let db_pool = Arc::new(
|
||||
PgPool::connect(&config.database.connect_url())
|
||||
.await
|
||||
.expect("Failed to connect to Postgres."),
|
||||
);
|
||||
let ws_server = WSServer::new().start();
|
||||
let auth = Arc::new(Auth::new(db_pool.clone()));
|
||||
|
||||
let ctx = AppContext::new(config, ws_server, db_pool, auth);
|
||||
Arc::new(ctx)
|
||||
}
|
Loading…
Reference in New Issue
Block a user