mirror of
https://github.com/AppFlowy-IO/AppFlowy.git
synced 2025-04-24 22:57:12 -04:00
config server launch
This commit is contained in:
parent
49e5f38406
commit
54342850b2
16 changed files with 230 additions and 159 deletions
|
@ -23,9 +23,11 @@ log = "0.4.14"
|
||||||
serde_json = "1.0"
|
serde_json = "1.0"
|
||||||
serde = { version = "1.0", features = ["derive"] }
|
serde = { version = "1.0", features = ["derive"] }
|
||||||
serde_repr = "0.1"
|
serde_repr = "0.1"
|
||||||
|
serde-aux = "1.0.1"
|
||||||
derive_more = {version = "0.99", features = ["display"]}
|
derive_more = {version = "0.99", features = ["display"]}
|
||||||
protobuf = {version = "2.20.0"}
|
protobuf = {version = "2.20.0"}
|
||||||
uuid = { version = "0.8", features = ["serde", "v4"] }
|
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-log = { path = "../rust-lib/flowy-log" }
|
||||||
flowy-user = { path = "../rust-lib/flowy-user" }
|
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 -x
|
||||||
set -eo pipefail
|
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';
|
until psql -h "localhost" -U "${DB_USER}" -p "${DB_PORT}" -d "postgres" -c '\q';
|
||||||
do
|
do
|
||||||
>&2 echo "Postgres is still unavailable - sleeping"
|
>&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 const_define;
|
||||||
mod database;
|
|
||||||
|
|
||||||
pub use config::*;
|
pub use configuration::*;
|
||||||
pub use const_define::*;
|
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 actix::Addr;
|
||||||
|
|
||||||
use sqlx::PgPool;
|
use sqlx::PgPool;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
pub struct AppContext {
|
pub struct AppContext {
|
||||||
pub config: Arc<Config>,
|
|
||||||
pub ws_server: Addr<WSServer>,
|
pub ws_server: Addr<WSServer>,
|
||||||
pub db_pool: Arc<PgPool>,
|
pub db_pool: Arc<PgPool>,
|
||||||
pub auth: Arc<Auth>,
|
pub auth: Arc<Auth>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl AppContext {
|
impl AppContext {
|
||||||
pub fn new(
|
pub fn new(ws_server: Addr<WSServer>, db_pool: Arc<PgPool>, auth: Arc<Auth>) -> Self {
|
||||||
config: Arc<Config>,
|
|
||||||
ws_server: Addr<WSServer>,
|
|
||||||
db_pool: Arc<PgPool>,
|
|
||||||
auth: Arc<Auth>,
|
|
||||||
) -> Self {
|
|
||||||
AppContext {
|
AppContext {
|
||||||
config,
|
|
||||||
ws_server,
|
ws_server,
|
||||||
db_pool,
|
db_pool,
|
||||||
auth,
|
auth,
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
mod config;
|
pub mod application;
|
||||||
|
pub mod config;
|
||||||
mod context;
|
mod context;
|
||||||
mod entities;
|
mod entities;
|
||||||
mod routers;
|
mod routers;
|
||||||
pub mod startup;
|
|
||||||
pub mod user_service;
|
pub mod user_service;
|
||||||
pub mod ws_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;
|
use std::net::TcpListener;
|
||||||
|
|
||||||
#[actix_web::main]
|
#[actix_web::main]
|
||||||
async fn main() -> std::io::Result<()> {
|
async fn main() -> std::io::Result<()> {
|
||||||
let app_ctx = init_app_context().await;
|
let configuration = get_configuration().expect("Failed to read configuration.");
|
||||||
let listener =
|
let application = Application::build(configuration).await?;
|
||||||
TcpListener::bind(app_ctx.config.server_addr()).expect("Failed to bind server address");
|
application.run_until_stopped().await?;
|
||||||
run(app_ctx, listener)?.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…
Add table
Add a link
Reference in a new issue