Add demo mode + various fixes.
This commit is contained in:
+111
-26
@@ -1,6 +1,6 @@
|
||||
use clap::{
|
||||
app_from_crate, crate_authors, crate_description, crate_name, crate_version, App, Arg,
|
||||
ArgMatches, SubCommand,
|
||||
app_from_crate, crate_authors, crate_description, crate_name, crate_version, App, AppSettings,
|
||||
Arg, ArgMatches, SubCommand,
|
||||
};
|
||||
use dotenv::dotenv;
|
||||
use pslink_shared::datatypes::{Secret, User};
|
||||
@@ -12,7 +12,7 @@ use std::{
|
||||
};
|
||||
|
||||
use pslink::{
|
||||
models::{NewUser, UserDbOperations},
|
||||
models::{NewLink, NewUser, UserDbOperations},
|
||||
ServerConfig, ServerError,
|
||||
};
|
||||
|
||||
@@ -20,7 +20,7 @@ use tracing::{error, info, trace, warn};
|
||||
|
||||
static MIGRATOR: Migrator = sqlx::migrate!();
|
||||
|
||||
/// generate the commandline options available
|
||||
/// generate the command line options available
|
||||
#[allow(clippy::too_many_lines)]
|
||||
fn generate_cli() -> App<'static, 'static> {
|
||||
app_from_crate!()
|
||||
@@ -47,7 +47,7 @@ fn generate_cli() -> App<'static, 'static> {
|
||||
.short("u")
|
||||
.help("The host url or the page that will be part of the short urls.")
|
||||
.env("PSLINK_PUBLIC_URL")
|
||||
.default_value("localhost:8080")
|
||||
.default_value("127.0.0.1:8080")
|
||||
.global(true),
|
||||
)
|
||||
.arg(
|
||||
@@ -63,7 +63,7 @@ fn generate_cli() -> App<'static, 'static> {
|
||||
Arg::with_name("brand_name")
|
||||
.long("brand-name")
|
||||
.short("b")
|
||||
.help("The Brandname that will apper in various places.")
|
||||
.help("The brand name that will appear in various places.")
|
||||
.env("PSLINK_BRAND_NAME")
|
||||
.default_value("Pslink")
|
||||
.global(true),
|
||||
@@ -74,7 +74,7 @@ fn generate_cli() -> App<'static, 'static> {
|
||||
.short("i")
|
||||
.help("The host (ip) that will run the pslink service")
|
||||
.env("PSLINK_IP")
|
||||
.default_value("localhost")
|
||||
.default_value("127.0.0.1")
|
||||
.global(true),
|
||||
)
|
||||
.arg(
|
||||
@@ -95,11 +95,11 @@ fn generate_cli() -> App<'static, 'static> {
|
||||
.long("secret")
|
||||
.help(concat!(
|
||||
"The secret that is used to encrypt the",
|
||||
" password database keep this as inacessable as possible.",
|
||||
" As commandlineparameters are visible",
|
||||
" password database keep this as inaccessible as possible.",
|
||||
" As command line parameters are visible",
|
||||
" to all users",
|
||||
" it is not wise to use this as",
|
||||
" a commandline parameter but rather as an environment variable.",
|
||||
" a command line parameter but rather as an environment variable.",
|
||||
))
|
||||
.env("PSLINK_SECRET")
|
||||
.default_value("")
|
||||
@@ -125,6 +125,12 @@ fn generate_cli() -> App<'static, 'static> {
|
||||
.about("Create an admin user.")
|
||||
.display_order(2),
|
||||
)
|
||||
.subcommand(
|
||||
SubCommand::with_name("demo")
|
||||
.about("Create a database and demo user.")
|
||||
.display_order(3)
|
||||
.setting(AppSettings::Hidden),
|
||||
)
|
||||
}
|
||||
|
||||
/// parse the options to the [`ServerConfig`] struct
|
||||
@@ -142,7 +148,7 @@ async fn parse_args_to_config(config: ArgMatches<'_>) -> ServerConfig {
|
||||
warn!("If you change the secret all passwords will be invalid");
|
||||
warn!("Using an auto generated one for this run.");
|
||||
} else {
|
||||
warn!("The provided secret was too short. Using an autogenerated one.");
|
||||
warn!("The provided secret was too short. Using an auto generated one.");
|
||||
}
|
||||
|
||||
thread_rng()
|
||||
@@ -158,7 +164,7 @@ async fn parse_args_to_config(config: ArgMatches<'_>) -> ServerConfig {
|
||||
.value_of("database")
|
||||
.expect(concat!(
|
||||
"Neither the DATABASE_URL environment variable",
|
||||
" nor the commandline parameters",
|
||||
" nor the command line parameters",
|
||||
" contain a valid database location."
|
||||
))
|
||||
.parse::<PathBuf>()
|
||||
@@ -186,7 +192,7 @@ async fn parse_args_to_config(config: ArgMatches<'_>) -> ServerConfig {
|
||||
.value_of("port")
|
||||
.expect("Failed to read the port value")
|
||||
.parse::<u32>()
|
||||
.expect("Failed to parse the portnumber");
|
||||
.expect("Failed to parse the port number");
|
||||
let protocol = config
|
||||
.value_of("protocol")
|
||||
.expect("Failed to read the protocol value")
|
||||
@@ -209,7 +215,7 @@ async fn parse_args_to_config(config: ArgMatches<'_>) -> ServerConfig {
|
||||
/// Setup and launch the command
|
||||
///
|
||||
/// # Panics
|
||||
/// This funcion panics if preconditions like the availability of the database are not met.
|
||||
/// This function panics if preconditions like the availability of the database are not met.
|
||||
pub async fn setup() -> Result<Option<crate::ServerConfig>, ServerError> {
|
||||
// load the environment .env file if available.
|
||||
dotenv().ok();
|
||||
@@ -225,7 +231,7 @@ pub async fn setup() -> Result<Option<crate::ServerConfig>, ServerError> {
|
||||
.value_of("database")
|
||||
.expect(concat!(
|
||||
"Neither the DATABASE_URL environment variable",
|
||||
" nor the commandline parameters",
|
||||
" nor the command line parameters",
|
||||
" contain a valid database location."
|
||||
))
|
||||
.parse::<PathBuf>()
|
||||
@@ -234,13 +240,14 @@ pub async fn setup() -> Result<Option<crate::ServerConfig>, ServerError> {
|
||||
if !db.exists() {
|
||||
trace!("No database file found {}", db.display());
|
||||
if !(config.subcommand_matches("migrate-database").is_none()
|
||||
| config.subcommand_matches("generate-env").is_none())
|
||||
| config.subcommand_matches("generate-env").is_none()
|
||||
| config.subcommand_matches("demo").is_none())
|
||||
{
|
||||
let msg = format!(
|
||||
concat!(
|
||||
"Database not found at {}!",
|
||||
" Create a new database with: `pslink migrate-database`",
|
||||
"or adjust the databasepath."
|
||||
"Create a new database with: `pslink migrate-database`",
|
||||
"or adjust the database path."
|
||||
),
|
||||
db.display()
|
||||
);
|
||||
@@ -268,12 +275,40 @@ pub async fn setup() -> Result<Option<crate::ServerConfig>, ServerError> {
|
||||
};
|
||||
}
|
||||
if let Some(_create_config) = config.subcommand_matches("create-admin") {
|
||||
return match create_admin(&server_config).await {
|
||||
return match request_admin_credentials(&server_config).await {
|
||||
Ok(_) => Ok(None),
|
||||
Err(e) => Err(e),
|
||||
};
|
||||
}
|
||||
|
||||
if let Some(_runserver_config) = config.subcommand_matches("demo") {
|
||||
let num_users = User::count_admins(&server_config).await;
|
||||
|
||||
match num_users {
|
||||
Err(_) => {
|
||||
generate_env_file(&server_config).expect("Failed to generate env file.");
|
||||
apply_migrations(&server_config)
|
||||
.await
|
||||
.expect("Failed to apply migrations.");
|
||||
let new_admin = NewUser::new(
|
||||
"demo".to_string(),
|
||||
"demo@teilgedanken.de".to_string(),
|
||||
"demo",
|
||||
&server_config.secret,
|
||||
)
|
||||
.expect("Failed to generate new user credentials.");
|
||||
create_admin(&new_admin, &server_config)
|
||||
.await
|
||||
.expect("Failed to create admin");
|
||||
add_example_links(&server_config).await;
|
||||
return Ok(Some(server_config));
|
||||
}
|
||||
_ => {
|
||||
return Err(ServerError::User("The database is not empty aborting because this could mean that creating a demo instance would lead in data loss.".to_string()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(_runserver_config) = config.subcommand_matches("runserver") {
|
||||
let num_users = User::count_admins(&server_config).await?;
|
||||
|
||||
@@ -294,8 +329,54 @@ pub async fn setup() -> Result<Option<crate::ServerConfig>, ServerError> {
|
||||
}
|
||||
}
|
||||
|
||||
async fn add_example_links(server_config: &ServerConfig) {
|
||||
NewLink {
|
||||
title: "Pslink Repository".to_owned(),
|
||||
target: "https://github.com/enaut/pslink".to_owned(),
|
||||
code: "pslink".to_owned(),
|
||||
author: 1,
|
||||
created_at: chrono::Local::now().naive_utc(),
|
||||
}
|
||||
.insert(server_config)
|
||||
.await
|
||||
.expect("Failed to insert example 1");
|
||||
|
||||
NewLink {
|
||||
title: "Seed".to_owned(),
|
||||
target: "https://seed-rs.org/".to_owned(),
|
||||
code: "seed".to_owned(),
|
||||
author: 1,
|
||||
created_at: chrono::Local::now().naive_utc(),
|
||||
}
|
||||
.insert(server_config)
|
||||
.await
|
||||
.expect("Failed to insert example 1");
|
||||
|
||||
NewLink {
|
||||
title: "actix".to_owned(),
|
||||
target: "https://actix.rs/".to_owned(),
|
||||
code: "actix".to_owned(),
|
||||
author: 1,
|
||||
created_at: chrono::Local::now().naive_utc(),
|
||||
}
|
||||
.insert(server_config)
|
||||
.await
|
||||
.expect("Failed to insert example 1");
|
||||
|
||||
NewLink {
|
||||
title: "rust".to_owned(),
|
||||
target: "https://www.rust-lang.org/".to_owned(),
|
||||
code: "rust".to_owned(),
|
||||
author: 1,
|
||||
created_at: chrono::Local::now().naive_utc(),
|
||||
}
|
||||
.insert(server_config)
|
||||
.await
|
||||
.expect("Failed to insert example 1");
|
||||
}
|
||||
|
||||
/// Interactively create a new admin user.
|
||||
async fn create_admin(config: &ServerConfig) -> Result<(), ServerError> {
|
||||
async fn request_admin_credentials(config: &ServerConfig) -> Result<(), ServerError> {
|
||||
info!("Creating an admin user.");
|
||||
let sin = io::stdin();
|
||||
|
||||
@@ -306,7 +387,7 @@ async fn create_admin(config: &ServerConfig) -> Result<(), ServerError> {
|
||||
io::stdout().flush().unwrap();
|
||||
let new_username = sin.lock().lines().next().unwrap().unwrap();
|
||||
|
||||
print!("Please enter the emailadress for {}: ", new_username);
|
||||
print!("Please enter the email address for {}: ", new_username);
|
||||
io::stdout().flush().unwrap();
|
||||
let new_email = sin.lock().lines().next().unwrap().unwrap();
|
||||
|
||||
@@ -325,16 +406,20 @@ async fn create_admin(config: &ServerConfig) -> Result<(), ServerError> {
|
||||
&config.secret,
|
||||
)?;
|
||||
|
||||
new_admin.insert_user(config).await?;
|
||||
let created_user = User::get_user_by_name(&new_username, config).await?;
|
||||
create_admin(&new_admin, config).await
|
||||
}
|
||||
|
||||
async fn create_admin(new_user: &NewUser, config: &ServerConfig) -> Result<(), ServerError> {
|
||||
new_user.insert_user(config).await?;
|
||||
let created_user = User::get_user_by_name(&new_user.username, config).await?;
|
||||
created_user.toggle_admin(config).await?;
|
||||
|
||||
info!("Admin user created: {}", new_username);
|
||||
info!("Admin user created: {}", &new_user.username);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Apply any pending migrations to the database. The migrations are embedded in the binary and don't need any addidtional files.
|
||||
/// Apply any pending migrations to the database. The migrations are embedded in the binary and don't need any additional files.
|
||||
async fn apply_migrations(config: &ServerConfig) -> Result<(), ServerError> {
|
||||
info!(
|
||||
"Creating a database file and running the migrations in the file {}:",
|
||||
@@ -344,7 +429,7 @@ async fn apply_migrations(config: &ServerConfig) -> Result<(), ServerError> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// The commandline parameters provided or if missing the default parameters can be converted and written to a .env file. That way the configuration is saved and automatically reused for subsequent launches.
|
||||
/// The command line parameters provided or if missing the default parameters can be converted and written to a .env file. That way the configuration is saved and automatically reused for subsequent launches.
|
||||
fn generate_env_file(server_config: &ServerConfig) -> Result<(), ServerError> {
|
||||
if std::path::Path::new(".env").exists() {
|
||||
return Err(ServerError::User(
|
||||
|
||||
@@ -79,7 +79,7 @@ async fn main() -> std::result::Result<(), std::io::Error> {
|
||||
// include the static files into the binary
|
||||
include!(concat!(env!("OUT_DIR"), "/generated.rs"));
|
||||
|
||||
/// Launch the pslink-webservice
|
||||
/// Launch the pslink-web-service
|
||||
///
|
||||
/// # Errors
|
||||
/// This produces a [`ServerError`] if:
|
||||
|
||||
@@ -67,9 +67,9 @@ fn detect_language(request: &HttpRequest) -> Result<Lang, ServerError> {
|
||||
);
|
||||
info!("supported languages: {:?}", supported);
|
||||
|
||||
if let Some(languagecode) = supported.get(0) {
|
||||
info!("Supported Language: {}", languagecode);
|
||||
Ok(languagecode
|
||||
if let Some(language_code) = supported.get(0) {
|
||||
info!("Supported Language: {}", language_code);
|
||||
Ok(language_code
|
||||
.to_string()
|
||||
.parse()
|
||||
.expect("Failed to parse 2 language"))
|
||||
|
||||
@@ -368,7 +368,7 @@ impl NewLink {
|
||||
///
|
||||
/// # Errors
|
||||
/// fails with [`ServerError`] if the database cannot be acessed or constraints are not met.
|
||||
pub(crate) async fn insert(self, server_config: &ServerConfig) -> Result<(), ServerError> {
|
||||
pub async fn insert(self, server_config: &ServerConfig) -> Result<(), ServerError> {
|
||||
sqlx::query!(
|
||||
"Insert into links (
|
||||
title,
|
||||
|
||||
Reference in New Issue
Block a user