Add demo mode + various fixes.
This commit is contained in:
		
							parent
							
								
									7c2ce180c6
								
							
						
					
					
						commit
						e98b468b10
					
				
							
								
								
									
										532
									
								
								Cargo.lock
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										532
									
								
								Cargo.lock
									
									
									
										generated
									
									
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
							
								
								
									
										23
									
								
								README.md
									
									
									
									
									
								
							
							
						
						
									
										23
									
								
								README.md
									
									
									
									
									
								
							@ -168,3 +168,26 @@ ExecStart=/var/pslink/pslink runserver
 | 
				
			|||||||
[Install]
 | 
					[Install]
 | 
				
			||||||
WantedBy=multi-user.target
 | 
					WantedBy=multi-user.target
 | 
				
			||||||
```
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					### Setup a demo container
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					First build the standalone binary:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					```bash
 | 
				
			||||||
 | 
					$ cargo make build_standalone
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Create a temporary directory and copy the binary from above:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					```bash
 | 
				
			||||||
 | 
					$ mkdir /tmp/pslink-container/
 | 
				
			||||||
 | 
					$ cp target/x86_64-unknown-linux-musl/release/pslink /tmp/pslink-container/
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Run the container (podman is used here but docker could be used exactly the same):
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					```bash
 | 
				
			||||||
 | 
					$ podman run --expose 8080 -p=8080:8080 -it pslink-container ./pslink demo -i 0.0.0.0
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Note that this is **absolutely not for a production use** and only for demo purposes as the links are **deleted on every restart**.
 | 
				
			||||||
 | 
				
			|||||||
@ -16,7 +16,7 @@ use crate::Msg;
 | 
				
			|||||||
pub fn navigation(i18n: &I18n, base_url: &Url, user: &User) -> Node<Msg> {
 | 
					pub fn navigation(i18n: &I18n, base_url: &Url, user: &User) -> Node<Msg> {
 | 
				
			||||||
    // A shortcut for translating strings.
 | 
					    // A shortcut for translating strings.
 | 
				
			||||||
    let t = move |key: &str| i18n.translate(key, None);
 | 
					    let t = move |key: &str| i18n.translate(key, None);
 | 
				
			||||||
    // Translate the wellcome message
 | 
					    // Translate the welcome message
 | 
				
			||||||
    let welcome = i18n.translate(
 | 
					    let welcome = i18n.translate(
 | 
				
			||||||
        "welcome-user",
 | 
					        "welcome-user",
 | 
				
			||||||
        Some(&fluent_args![ "username" => user.username.clone()]),
 | 
					        Some(&fluent_args![ "username" => user.username.clone()]),
 | 
				
			||||||
 | 
				
			|||||||
@ -91,7 +91,7 @@ impl<T> Deref for Cached<T> {
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/// There can allways be only one dialog.
 | 
					/// There can always be only one dialog.
 | 
				
			||||||
#[derive(Debug, Clone)]
 | 
					#[derive(Debug, Clone)]
 | 
				
			||||||
enum Dialog {
 | 
					enum Dialog {
 | 
				
			||||||
    EditLink {
 | 
					    EditLink {
 | 
				
			||||||
@ -150,7 +150,7 @@ pub enum Msg {
 | 
				
			|||||||
    Query(QueryMsg), // Messages related to querying links
 | 
					    Query(QueryMsg), // Messages related to querying links
 | 
				
			||||||
    Edit(EditMsg),   // Messages related to editing links
 | 
					    Edit(EditMsg),   // Messages related to editing links
 | 
				
			||||||
    ClearAll,        // Clear all messages
 | 
					    ClearAll,        // Clear all messages
 | 
				
			||||||
    SetupObserver,   // Make an observer for endles scroll
 | 
					    SetupObserver,   // Make an observer for endless scroll
 | 
				
			||||||
    Observed(Vec<IntersectionObserverEntry>),
 | 
					    Observed(Vec<IntersectionObserverEntry>),
 | 
				
			||||||
    SetMessage(String), // Set a message to the user
 | 
					    SetMessage(String), // Set a message to the user
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
@ -244,7 +244,7 @@ pub fn update(msg: Msg, model: &mut Model, orders: &mut impl Orders<Msg>) {
 | 
				
			|||||||
                    log!("element not yet registered! ");
 | 
					                    log!("element not yet registered! ");
 | 
				
			||||||
                };
 | 
					                };
 | 
				
			||||||
            } else {
 | 
					            } else {
 | 
				
			||||||
                log!("Failed to get observer!")
 | 
					                log!("Failed to get observer!");
 | 
				
			||||||
            };
 | 
					            };
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
@ -255,13 +255,13 @@ pub fn process_query_messages(msg: QueryMsg, model: &mut Model, orders: &mut imp
 | 
				
			|||||||
    match msg {
 | 
					    match msg {
 | 
				
			||||||
        QueryMsg::Fetch => {
 | 
					        QueryMsg::Fetch => {
 | 
				
			||||||
            orders.skip(); // No need to rerender
 | 
					            orders.skip(); // No need to rerender
 | 
				
			||||||
            initial_load(model, orders)
 | 
					            initial_load(model, orders);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        QueryMsg::FetchAdditional => {
 | 
					        QueryMsg::FetchAdditional => {
 | 
				
			||||||
            orders.skip(); // No need to rerender
 | 
					            orders.skip(); // No need to rerender
 | 
				
			||||||
            consecutive_load(model, orders)
 | 
					            consecutive_load(model, orders);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        // Default to ascending ordering but if the links are already sorted according to this collumn toggle between ascending and descending ordering.
 | 
					        // Default to ascending ordering but if the links are already sorted according to this column toggle between ascending and descending ordering.
 | 
				
			||||||
        QueryMsg::OrderBy(column) => {
 | 
					        QueryMsg::OrderBy(column) => {
 | 
				
			||||||
            model.formconfig.order = model.formconfig.order.as_ref().map_or_else(
 | 
					            model.formconfig.order = model.formconfig.order.as_ref().map_or_else(
 | 
				
			||||||
                || {
 | 
					                || {
 | 
				
			||||||
@ -316,7 +316,7 @@ pub fn process_query_messages(msg: QueryMsg, model: &mut Model, orders: &mut imp
 | 
				
			|||||||
        QueryMsg::ReceivedAdditional(response) => {
 | 
					        QueryMsg::ReceivedAdditional(response) => {
 | 
				
			||||||
            if response.len() < model.formconfig.amount {
 | 
					            if response.len() < model.formconfig.amount {
 | 
				
			||||||
                log!("There are no more links! ");
 | 
					                log!("There are no more links! ");
 | 
				
			||||||
                model.everything_loaded = true
 | 
					                model.everything_loaded = true;
 | 
				
			||||||
            };
 | 
					            };
 | 
				
			||||||
            let mut new_links = response
 | 
					            let mut new_links = response
 | 
				
			||||||
                .into_iter()
 | 
					                .into_iter()
 | 
				
			||||||
@ -376,7 +376,7 @@ fn load_links(orders: &mut impl Orders<Msg>, data: LinkRequestForm) {
 | 
				
			|||||||
                .json(&data),
 | 
					                .json(&data),
 | 
				
			||||||
            Msg::SetMessage("Failed to parse data".to_string())
 | 
					            Msg::SetMessage("Failed to parse data".to_string())
 | 
				
			||||||
        );
 | 
					        );
 | 
				
			||||||
        // send the request and recieve a response
 | 
					        // send the request and receive a response
 | 
				
			||||||
        let response = unwrap_or_return!(
 | 
					        let response = unwrap_or_return!(
 | 
				
			||||||
            fetch(request).await,
 | 
					            fetch(request).await,
 | 
				
			||||||
            Msg::SetMessage("Failed to send data".to_string())
 | 
					            Msg::SetMessage("Failed to send data".to_string())
 | 
				
			||||||
@ -554,7 +554,7 @@ fn delete_link(link_delta: LinkDelta, orders: &mut impl Orders<Msg>) {
 | 
				
			|||||||
                .json(&link_delta),
 | 
					                .json(&link_delta),
 | 
				
			||||||
            Msg::SetMessage("serialization failed".to_string())
 | 
					            Msg::SetMessage("serialization failed".to_string())
 | 
				
			||||||
        );
 | 
					        );
 | 
				
			||||||
        // perform the request and recieve a respnse
 | 
					        // perform the request and receive a response
 | 
				
			||||||
        let response =
 | 
					        let response =
 | 
				
			||||||
            unwrap_or_return!(fetch(request).await, Msg::Edit(EditMsg::FailedToDeleteLink));
 | 
					            unwrap_or_return!(fetch(request).await, Msg::Edit(EditMsg::FailedToDeleteLink));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -746,7 +746,8 @@ fn view_link(l: &Cached<FullLink>, logged_in_user: &User) -> Node<Msg> {
 | 
				
			|||||||
                C!["table_qr"],
 | 
					                C!["table_qr"],
 | 
				
			||||||
                a![
 | 
					                a![
 | 
				
			||||||
                    ev(Ev::Click, |event| event.stop_propagation()),
 | 
					                    ev(Ev::Click, |event| event.stop_propagation()),
 | 
				
			||||||
                    attrs![At::Href => format!["/admin/download/png/{}",  &l.link.code], At::Download => true.as_at_value()],
 | 
					                    attrs![At::Href => format!("/admin/download/png/{}", &l.link.code),
 | 
				
			||||||
 | 
					                    At::Download => true.as_at_value()],
 | 
				
			||||||
                    raw!(&l.cache)
 | 
					                    raw!(&l.cache)
 | 
				
			||||||
                ]
 | 
					                ]
 | 
				
			||||||
            ]
 | 
					            ]
 | 
				
			||||||
 | 
				
			|||||||
@ -62,7 +62,7 @@ struct FilterInput {
 | 
				
			|||||||
    filter_input: ElRef<web_sys::HtmlInputElement>,
 | 
					    filter_input: ElRef<web_sys::HtmlInputElement>,
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/// The message splits the contained message into messages related to querrying and messages related to editing.
 | 
					/// The message splits the contained message into messages related to querying and messages related to editing.
 | 
				
			||||||
#[derive(Clone)]
 | 
					#[derive(Clone)]
 | 
				
			||||||
pub enum Msg {
 | 
					pub enum Msg {
 | 
				
			||||||
    Query(UserQueryMsg),
 | 
					    Query(UserQueryMsg),
 | 
				
			||||||
@ -161,7 +161,7 @@ pub fn process_query_messages(msg: UserQueryMsg, model: &mut Model, orders: &mut
 | 
				
			|||||||
        }
 | 
					        }
 | 
				
			||||||
        UserQueryMsg::EmailFilterChanged(s) => {
 | 
					        UserQueryMsg::EmailFilterChanged(s) => {
 | 
				
			||||||
            log!("Filter is: ", &s);
 | 
					            log!("Filter is: ", &s);
 | 
				
			||||||
            // FIXME: Sanitazion does not work for @
 | 
					            // FIXME: Sanitation does not work for @
 | 
				
			||||||
            let sanit = s.chars().filter(|x| x.is_alphanumeric()).collect();
 | 
					            let sanit = s.chars().filter(|x| x.is_alphanumeric()).collect();
 | 
				
			||||||
            model.formconfig.filter[UserOverviewColumns::Email].sieve = sanit;
 | 
					            model.formconfig.filter[UserOverviewColumns::Email].sieve = sanit;
 | 
				
			||||||
            orders.send_msg(Msg::Query(UserQueryMsg::Fetch));
 | 
					            orders.send_msg(Msg::Query(UserQueryMsg::Fetch));
 | 
				
			||||||
@ -238,7 +238,7 @@ pub fn process_user_edit_messages(
 | 
				
			|||||||
            let data = model
 | 
					            let data = model
 | 
				
			||||||
                .user_edit
 | 
					                .user_edit
 | 
				
			||||||
                .take()
 | 
					                .take()
 | 
				
			||||||
                .expect("A user should allways be there on save");
 | 
					                .expect("A user should always be there on save");
 | 
				
			||||||
            log!("Saving User: ", &data.username);
 | 
					            log!("Saving User: ", &data.username);
 | 
				
			||||||
            save_user(data, orders);
 | 
					            save_user(data, orders);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
				
			|||||||
@ -1,6 +1,6 @@
 | 
				
			|||||||
use clap::{
 | 
					use clap::{
 | 
				
			||||||
    app_from_crate, crate_authors, crate_description, crate_name, crate_version, App, Arg,
 | 
					    app_from_crate, crate_authors, crate_description, crate_name, crate_version, App, AppSettings,
 | 
				
			||||||
    ArgMatches, SubCommand,
 | 
					    Arg, ArgMatches, SubCommand,
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
use dotenv::dotenv;
 | 
					use dotenv::dotenv;
 | 
				
			||||||
use pslink_shared::datatypes::{Secret, User};
 | 
					use pslink_shared::datatypes::{Secret, User};
 | 
				
			||||||
@ -12,7 +12,7 @@ use std::{
 | 
				
			|||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
use pslink::{
 | 
					use pslink::{
 | 
				
			||||||
    models::{NewUser, UserDbOperations},
 | 
					    models::{NewLink, NewUser, UserDbOperations},
 | 
				
			||||||
    ServerConfig, ServerError,
 | 
					    ServerConfig, ServerError,
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -20,7 +20,7 @@ use tracing::{error, info, trace, warn};
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
static MIGRATOR: Migrator = sqlx::migrate!();
 | 
					static MIGRATOR: Migrator = sqlx::migrate!();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/// generate the commandline options available
 | 
					/// generate the command line options available
 | 
				
			||||||
#[allow(clippy::too_many_lines)]
 | 
					#[allow(clippy::too_many_lines)]
 | 
				
			||||||
fn generate_cli() -> App<'static, 'static> {
 | 
					fn generate_cli() -> App<'static, 'static> {
 | 
				
			||||||
    app_from_crate!()
 | 
					    app_from_crate!()
 | 
				
			||||||
@ -47,7 +47,7 @@ fn generate_cli() -> App<'static, 'static> {
 | 
				
			|||||||
                .short("u")
 | 
					                .short("u")
 | 
				
			||||||
                .help("The host url or the page that will be part of the short urls.")
 | 
					                .help("The host url or the page that will be part of the short urls.")
 | 
				
			||||||
                .env("PSLINK_PUBLIC_URL")
 | 
					                .env("PSLINK_PUBLIC_URL")
 | 
				
			||||||
                .default_value("localhost:8080")
 | 
					                .default_value("127.0.0.1:8080")
 | 
				
			||||||
                .global(true),
 | 
					                .global(true),
 | 
				
			||||||
        )
 | 
					        )
 | 
				
			||||||
        .arg(
 | 
					        .arg(
 | 
				
			||||||
@ -63,7 +63,7 @@ fn generate_cli() -> App<'static, 'static> {
 | 
				
			|||||||
            Arg::with_name("brand_name")
 | 
					            Arg::with_name("brand_name")
 | 
				
			||||||
                .long("brand-name")
 | 
					                .long("brand-name")
 | 
				
			||||||
                .short("b")
 | 
					                .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")
 | 
					                .env("PSLINK_BRAND_NAME")
 | 
				
			||||||
                .default_value("Pslink")
 | 
					                .default_value("Pslink")
 | 
				
			||||||
                .global(true),
 | 
					                .global(true),
 | 
				
			||||||
@ -74,7 +74,7 @@ fn generate_cli() -> App<'static, 'static> {
 | 
				
			|||||||
                .short("i")
 | 
					                .short("i")
 | 
				
			||||||
                .help("The host (ip) that will run the pslink service")
 | 
					                .help("The host (ip) that will run the pslink service")
 | 
				
			||||||
                .env("PSLINK_IP")
 | 
					                .env("PSLINK_IP")
 | 
				
			||||||
                .default_value("localhost")
 | 
					                .default_value("127.0.0.1")
 | 
				
			||||||
                .global(true),
 | 
					                .global(true),
 | 
				
			||||||
        )
 | 
					        )
 | 
				
			||||||
        .arg(
 | 
					        .arg(
 | 
				
			||||||
@ -95,11 +95,11 @@ fn generate_cli() -> App<'static, 'static> {
 | 
				
			|||||||
                .long("secret")
 | 
					                .long("secret")
 | 
				
			||||||
                .help(concat!(
 | 
					                .help(concat!(
 | 
				
			||||||
                    "The secret that is used to encrypt the",
 | 
					                    "The secret that is used to encrypt the",
 | 
				
			||||||
                    " password database keep this as inacessable as possible.",
 | 
					                    " password database keep this as inaccessible as possible.",
 | 
				
			||||||
                    " As commandlineparameters are visible",
 | 
					                    " As command line parameters are visible",
 | 
				
			||||||
                    " to all users",
 | 
					                    " to all users",
 | 
				
			||||||
                    " it is not wise to use this as",
 | 
					                    " 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")
 | 
					                .env("PSLINK_SECRET")
 | 
				
			||||||
                .default_value("")
 | 
					                .default_value("")
 | 
				
			||||||
@ -125,6 +125,12 @@ fn generate_cli() -> App<'static, 'static> {
 | 
				
			|||||||
                .about("Create an admin user.")
 | 
					                .about("Create an admin user.")
 | 
				
			||||||
                .display_order(2),
 | 
					                .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
 | 
					/// 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!("If you change the secret all passwords will be invalid");
 | 
				
			||||||
            warn!("Using an auto generated one for this run.");
 | 
					            warn!("Using an auto generated one for this run.");
 | 
				
			||||||
        } else {
 | 
					        } 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()
 | 
					        thread_rng()
 | 
				
			||||||
@ -158,7 +164,7 @@ async fn parse_args_to_config(config: ArgMatches<'_>) -> ServerConfig {
 | 
				
			|||||||
        .value_of("database")
 | 
					        .value_of("database")
 | 
				
			||||||
        .expect(concat!(
 | 
					        .expect(concat!(
 | 
				
			||||||
            "Neither the DATABASE_URL environment variable",
 | 
					            "Neither the DATABASE_URL environment variable",
 | 
				
			||||||
            " nor the commandline parameters",
 | 
					            " nor the command line parameters",
 | 
				
			||||||
            " contain a valid database location."
 | 
					            " contain a valid database location."
 | 
				
			||||||
        ))
 | 
					        ))
 | 
				
			||||||
        .parse::<PathBuf>()
 | 
					        .parse::<PathBuf>()
 | 
				
			||||||
@ -186,7 +192,7 @@ async fn parse_args_to_config(config: ArgMatches<'_>) -> ServerConfig {
 | 
				
			|||||||
        .value_of("port")
 | 
					        .value_of("port")
 | 
				
			||||||
        .expect("Failed to read the port value")
 | 
					        .expect("Failed to read the port value")
 | 
				
			||||||
        .parse::<u32>()
 | 
					        .parse::<u32>()
 | 
				
			||||||
        .expect("Failed to parse the portnumber");
 | 
					        .expect("Failed to parse the port number");
 | 
				
			||||||
    let protocol = config
 | 
					    let protocol = config
 | 
				
			||||||
        .value_of("protocol")
 | 
					        .value_of("protocol")
 | 
				
			||||||
        .expect("Failed to read the protocol value")
 | 
					        .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
 | 
					/// Setup and launch the command
 | 
				
			||||||
///
 | 
					///
 | 
				
			||||||
/// # Panics
 | 
					/// # 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> {
 | 
					pub async fn setup() -> Result<Option<crate::ServerConfig>, ServerError> {
 | 
				
			||||||
    // load the environment .env file if available.
 | 
					    // load the environment .env file if available.
 | 
				
			||||||
    dotenv().ok();
 | 
					    dotenv().ok();
 | 
				
			||||||
@ -225,7 +231,7 @@ pub async fn setup() -> Result<Option<crate::ServerConfig>, ServerError> {
 | 
				
			|||||||
        .value_of("database")
 | 
					        .value_of("database")
 | 
				
			||||||
        .expect(concat!(
 | 
					        .expect(concat!(
 | 
				
			||||||
            "Neither the DATABASE_URL environment variable",
 | 
					            "Neither the DATABASE_URL environment variable",
 | 
				
			||||||
            " nor the commandline parameters",
 | 
					            " nor the command line parameters",
 | 
				
			||||||
            " contain a valid database location."
 | 
					            " contain a valid database location."
 | 
				
			||||||
        ))
 | 
					        ))
 | 
				
			||||||
        .parse::<PathBuf>()
 | 
					        .parse::<PathBuf>()
 | 
				
			||||||
@ -234,13 +240,14 @@ pub async fn setup() -> Result<Option<crate::ServerConfig>, ServerError> {
 | 
				
			|||||||
    if !db.exists() {
 | 
					    if !db.exists() {
 | 
				
			||||||
        trace!("No database file found {}", db.display());
 | 
					        trace!("No database file found {}", db.display());
 | 
				
			||||||
        if !(config.subcommand_matches("migrate-database").is_none()
 | 
					        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!(
 | 
					            let msg = format!(
 | 
				
			||||||
                concat!(
 | 
					                concat!(
 | 
				
			||||||
                    "Database not found at {}!",
 | 
					                    "Database not found at {}!",
 | 
				
			||||||
                    " Create a new database with: `pslink migrate-database`",
 | 
					                    "Create a new database with: `pslink migrate-database`",
 | 
				
			||||||
                    "or adjust the databasepath."
 | 
					                    "or adjust the database path."
 | 
				
			||||||
                ),
 | 
					                ),
 | 
				
			||||||
                db.display()
 | 
					                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") {
 | 
					    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),
 | 
					            Ok(_) => Ok(None),
 | 
				
			||||||
            Err(e) => Err(e),
 | 
					            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") {
 | 
					    if let Some(_runserver_config) = config.subcommand_matches("runserver") {
 | 
				
			||||||
        let num_users = User::count_admins(&server_config).await?;
 | 
					        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.
 | 
					/// 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.");
 | 
					    info!("Creating an admin user.");
 | 
				
			||||||
    let sin = io::stdin();
 | 
					    let sin = io::stdin();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -306,7 +387,7 @@ async fn create_admin(config: &ServerConfig) -> Result<(), ServerError> {
 | 
				
			|||||||
    io::stdout().flush().unwrap();
 | 
					    io::stdout().flush().unwrap();
 | 
				
			||||||
    let new_username = sin.lock().lines().next().unwrap().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();
 | 
					    io::stdout().flush().unwrap();
 | 
				
			||||||
    let new_email = sin.lock().lines().next().unwrap().unwrap();
 | 
					    let new_email = sin.lock().lines().next().unwrap().unwrap();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -325,16 +406,20 @@ async fn create_admin(config: &ServerConfig) -> Result<(), ServerError> {
 | 
				
			|||||||
        &config.secret,
 | 
					        &config.secret,
 | 
				
			||||||
    )?;
 | 
					    )?;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    new_admin.insert_user(config).await?;
 | 
					    create_admin(&new_admin, config).await
 | 
				
			||||||
    let created_user = User::get_user_by_name(&new_username, 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?;
 | 
					    created_user.toggle_admin(config).await?;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    info!("Admin user created: {}", new_username);
 | 
					    info!("Admin user created: {}", &new_user.username);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    Ok(())
 | 
					    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> {
 | 
					async fn apply_migrations(config: &ServerConfig) -> Result<(), ServerError> {
 | 
				
			||||||
    info!(
 | 
					    info!(
 | 
				
			||||||
        "Creating a database file and running the migrations in the file {}:",
 | 
					        "Creating a database file and running the migrations in the file {}:",
 | 
				
			||||||
@ -344,7 +429,7 @@ async fn apply_migrations(config: &ServerConfig) -> Result<(), ServerError> {
 | 
				
			|||||||
    Ok(())
 | 
					    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> {
 | 
					fn generate_env_file(server_config: &ServerConfig) -> Result<(), ServerError> {
 | 
				
			||||||
    if std::path::Path::new(".env").exists() {
 | 
					    if std::path::Path::new(".env").exists() {
 | 
				
			||||||
        return Err(ServerError::User(
 | 
					        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 the static files into the binary
 | 
				
			||||||
include!(concat!(env!("OUT_DIR"), "/generated.rs"));
 | 
					include!(concat!(env!("OUT_DIR"), "/generated.rs"));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/// Launch the pslink-webservice
 | 
					/// Launch the pslink-web-service
 | 
				
			||||||
///
 | 
					///
 | 
				
			||||||
/// # Errors
 | 
					/// # Errors
 | 
				
			||||||
/// This produces a [`ServerError`] if:
 | 
					/// This produces a [`ServerError`] if:
 | 
				
			||||||
 | 
				
			|||||||
@ -67,9 +67,9 @@ fn detect_language(request: &HttpRequest) -> Result<Lang, ServerError> {
 | 
				
			|||||||
    );
 | 
					    );
 | 
				
			||||||
    info!("supported languages: {:?}", supported);
 | 
					    info!("supported languages: {:?}", supported);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if let Some(languagecode) = supported.get(0) {
 | 
					    if let Some(language_code) = supported.get(0) {
 | 
				
			||||||
        info!("Supported Language: {}", languagecode);
 | 
					        info!("Supported Language: {}", language_code);
 | 
				
			||||||
        Ok(languagecode
 | 
					        Ok(language_code
 | 
				
			||||||
            .to_string()
 | 
					            .to_string()
 | 
				
			||||||
            .parse()
 | 
					            .parse()
 | 
				
			||||||
            .expect("Failed to parse 2 language"))
 | 
					            .expect("Failed to parse 2 language"))
 | 
				
			||||||
 | 
				
			|||||||
@ -368,7 +368,7 @@ impl NewLink {
 | 
				
			|||||||
    ///
 | 
					    ///
 | 
				
			||||||
    /// # Errors
 | 
					    /// # Errors
 | 
				
			||||||
    /// fails with [`ServerError`] if the database cannot be acessed or constraints are not met.
 | 
					    /// 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!(
 | 
					        sqlx::query!(
 | 
				
			||||||
            "Insert into links (
 | 
					            "Insert into links (
 | 
				
			||||||
                title,
 | 
					                title,
 | 
				
			||||||
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user