Sepparate library part and binary part

closes #5
This commit is contained in:
Dietrich 2021-04-01 14:58:40 +02:00
parent 0ed010b78f
commit 34b957332e
Signed by: dietrich
GPG Key ID: 9F3C20C0F85DF67C
6 changed files with 326 additions and 238 deletions

View File

@ -10,7 +10,7 @@ use std::{
path::PathBuf, path::PathBuf,
}; };
use crate::{models::NewUser, models::User, ServerConfig, ServerError}; use pslink::{models::NewUser, models::User, ServerConfig, ServerError};
use slog::{Drain, Logger}; use slog::{Drain, Logger};
@ -192,7 +192,7 @@ async fn parse_args_to_config(config: ArgMatches<'_>, log: Logger) -> ServerConf
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")
.parse::<crate::Protocol>() .parse::<pslink::Protocol>()
.expect("Failed to parse the protocol"); .expect("Failed to parse the protocol");
let log = log.new(slog_o!("host" => public_url.clone())); let log = log.new(slog_o!("host" => public_url.clone()));

View File

@ -15,179 +15,15 @@ extern crate slog;
extern crate slog_async; extern crate slog_async;
mod cli; mod cli;
mod forms;
pub mod models;
mod queries;
mod views; mod views;
use pslink::ServerConfig;
use std::{fmt::Display, path::PathBuf, str::FromStr};
use actix_identity::{CookieIdentityPolicy, IdentityService}; use actix_identity::{CookieIdentityPolicy, IdentityService};
use actix_web::{web, App, HttpResponse, HttpServer}; use actix_web::{web, App, HttpServer};
use fluent_templates::{static_loader, FluentLoader}; use fluent_templates::{static_loader, FluentLoader};
use qrcode::types::QrError;
use sqlx::{Pool, Sqlite};
use tera::Tera; use tera::Tera;
#[derive(Debug)]
pub enum ServerError {
Argonautic,
Database(sqlx::Error),
DatabaseMigration(sqlx::migrate::MigrateError),
Environment,
Template(tera::Error),
Qr(QrError),
Io(std::io::Error),
User(String),
}
impl std::fmt::Display for ServerError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Argonautic => write!(f, "Argonautica Error"),
Self::Database(e) => write!(f, "Database Error: {}", e),
Self::DatabaseMigration(e) => {
write!(f, "Migration Error: {}", e)
}
Self::Environment => write!(f, "Environment Error"),
Self::Template(e) => write!(f, "Template Error: {:?}", e),
Self::Qr(e) => write!(f, "Qr Code Error: {:?}", e),
Self::Io(e) => write!(f, "IO Error: {:?}", e),
Self::User(data) => write!(f, "{}", data),
}
}
}
impl actix_web::error::ResponseError for ServerError {
fn error_response(&self) -> HttpResponse {
match self {
Self::Argonautic => HttpResponse::InternalServerError().json("Argonautica Error"),
Self::Database(e) => {
HttpResponse::InternalServerError().json(format!("Database Error: {:?}", e))
}
Self::DatabaseMigration(_) => {
unimplemented!("A migration error should never be rendered")
}
Self::Environment => HttpResponse::InternalServerError().json("Environment Error"),
Self::Template(e) => {
HttpResponse::InternalServerError().json(format!("Template Error: {:?}", e))
}
Self::Qr(e) => {
HttpResponse::InternalServerError().json(format!("Qr Code Error: {:?}", e))
}
Self::Io(e) => HttpResponse::InternalServerError().json(format!("IO Error: {:?}", e)),
Self::User(data) => HttpResponse::InternalServerError().json(data),
}
}
}
impl From<std::env::VarError> for ServerError {
fn from(e: std::env::VarError) -> Self {
eprintln!("Environment error {:?}", e);
Self::Environment
}
}
impl From<sqlx::Error> for ServerError {
fn from(err: sqlx::Error) -> Self {
eprintln!("Database error {:?}", err);
Self::Database(err)
}
}
impl From<sqlx::migrate::MigrateError> for ServerError {
fn from(err: sqlx::migrate::MigrateError) -> Self {
eprintln!("Database error {:?}", err);
Self::DatabaseMigration(err)
}
}
impl From<argonautica::Error> for ServerError {
fn from(e: argonautica::Error) -> Self {
eprintln!("Authentication error {:?}", e);
Self::Argonautic
}
}
impl From<tera::Error> for ServerError {
fn from(e: tera::Error) -> Self {
eprintln!("Template error {:?}", e);
Self::Template(e)
}
}
impl From<QrError> for ServerError {
fn from(e: QrError) -> Self {
eprintln!("Template error {:?}", e);
Self::Qr(e)
}
}
impl From<std::io::Error> for ServerError {
fn from(e: std::io::Error) -> Self {
eprintln!("IO error {:?}", e);
Self::Io(e)
}
}
#[derive(Debug, Clone)]
enum Protocol {
Http,
Https,
}
impl Display for Protocol {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Http => f.write_str("http"),
Self::Https => f.write_str("https"),
}
}
}
impl FromStr for Protocol {
type Err = ServerError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s {
"http" => Ok(Self::Http),
"https" => Ok(Self::Https),
_ => Err(ServerError::User("Failed to parse Protocol".to_owned())),
}
}
}
#[derive(Debug, Clone)]
pub(crate) struct ServerConfig {
secret: String,
db: PathBuf,
db_pool: Pool<Sqlite>,
public_url: String,
internal_ip: String,
port: u32,
protocol: Protocol,
log: slog::Logger,
empty_forward_url: String,
brand_name: String,
}
impl ServerConfig {
pub fn to_env_strings(&self) -> Vec<String> {
vec![
format!("PSLINK_DATABASE=\"{}\"\n", self.db.display()),
format!("PSLINK_PORT={}\n", self.port),
format!("PSLINK_PUBLIC_URL=\"{}\"\n", self.public_url),
format!("PSLINK_EMPTY_FORWARD_URL=\"{}\"\n", self.empty_forward_url),
format!("PSLINK_BRAND_NAME=\"{}\"\n", self.brand_name),
format!("PSLINK_IP=\"{}\"\n", self.internal_ip),
format!("PSLINK_PROTOCOL=\"{}\"\n", self.protocol),
concat!(
"# The SECRET_KEY variable is used for password encryption.\n",
"# If it is changed all existing passwords are invalid.\n"
)
.to_owned(),
format!("PSLINK_SECRET=\"{}\"\n", self.secret),
]
}
}
include!(concat!(env!("OUT_DIR"), "/generated.rs")); include!(concat!(env!("OUT_DIR"), "/generated.rs"));
static_loader! { static_loader! {
@ -204,38 +40,41 @@ fn build_tera() -> Tera {
tera.register_function("fluent", FluentLoader::new(&*LOCALES)); tera.register_function("fluent", FluentLoader::new(&*LOCALES));
tera.add_raw_templates(vec![ tera.add_raw_templates(vec![
("admin.html", include_str!("../templates/admin.html")), ("admin.html", include_str!("../../../templates/admin.html")),
("base.html", include_str!("../templates/base.html")), ("base.html", include_str!("../../../templates/base.html")),
( (
"edit_link.html", "edit_link.html",
include_str!("../templates/edit_link.html"), include_str!("../../../templates/edit_link.html"),
), ),
( (
"edit_profile.html", "edit_profile.html",
include_str!("../templates/edit_profile.html"), include_str!("../../../templates/edit_profile.html"),
), ),
( (
"index_users.html", "index_users.html",
include_str!("../templates/index_users.html"), include_str!("../../../templates/index_users.html"),
), ),
("index.html", include_str!("../templates/index.html")), ("index.html", include_str!("../../../templates/index.html")),
("login.html", include_str!("../templates/login.html")), ("login.html", include_str!("../../../templates/login.html")),
( (
"not_found.html", "not_found.html",
include_str!("../templates/not_found.html"), include_str!("../../../templates/not_found.html"),
),
(
"signup.html",
include_str!("../../../templates/signup.html"),
), ),
("signup.html", include_str!("../templates/signup.html")),
( (
"submission.html", "submission.html",
include_str!("../templates/submission.html"), include_str!("../../../templates/submission.html"),
), ),
( (
"view_link.html", "view_link.html",
include_str!("../templates/view_link.html"), include_str!("../../../templates/view_link.html"),
), ),
( (
"view_profile.html", "view_profile.html",
include_str!("../templates/view_profile.html"), include_str!("../../../templates/view_profile.html"),
), ),
]) ])
.expect("failed to parse templates"); .expect("failed to parse templates");

View File

@ -15,10 +15,10 @@ use image::{DynamicImage, ImageOutputFormat, Luma};
use qrcode::{render::svg, QrCode}; use qrcode::{render::svg, QrCode};
use tera::{Context, Tera}; use tera::{Context, Tera};
use super::forms::LinkForm; use pslink::forms::LinkForm;
use super::models::{LoginUser, NewUser}; use pslink::models::{LoginUser, NewUser};
use crate::queries; use pslink::queries;
use crate::ServerError; use pslink::ServerError;
fn redirect_builder(target: &str) -> HttpResponse { fn redirect_builder(target: &str) -> HttpResponse {
HttpResponse::SeeOther() HttpResponse::SeeOther()
@ -62,7 +62,7 @@ fn detect_language(request: &HttpRequest) -> Result<String, ServerError> {
} }
/// Show the list of all available links if a user is authenticated /// Show the list of all available links if a user is authenticated
pub(crate) async fn index( pub async fn index(
tera: web::Data<Tera>, tera: web::Data<Tera>,
config: web::Data<crate::ServerConfig>, config: web::Data<crate::ServerConfig>,
id: Identity, id: Identity,
@ -80,7 +80,7 @@ pub(crate) async fn index(
} }
/// Show the list of all available links if a user is authenticated /// Show the list of all available links if a user is authenticated
pub(crate) async fn index_users( pub async fn index_users(
tera: web::Data<Tera>, tera: web::Data<Tera>,
config: web::Data<crate::ServerConfig>, config: web::Data<crate::ServerConfig>,
id: Identity, id: Identity,
@ -97,7 +97,7 @@ pub(crate) async fn index_users(
Ok(redirect_builder("/admin/login")) Ok(redirect_builder("/admin/login"))
} }
} }
pub(crate) async fn view_link_empty( pub async fn view_link_empty(
tera: web::Data<Tera>, tera: web::Data<Tera>,
config: web::Data<crate::ServerConfig>, config: web::Data<crate::ServerConfig>,
id: Identity, id: Identity,
@ -105,7 +105,7 @@ pub(crate) async fn view_link_empty(
view_link(tera, config, id, web::Path::from("".to_owned())).await view_link(tera, config, id, web::Path::from("".to_owned())).await
} }
pub(crate) async fn view_link( pub async fn view_link(
tera: web::Data<Tera>, tera: web::Data<Tera>,
config: web::Data<crate::ServerConfig>, config: web::Data<crate::ServerConfig>,
id: Identity, id: Identity,
@ -144,7 +144,7 @@ pub(crate) async fn view_link(
} }
} }
pub(crate) async fn view_profile( pub async fn view_profile(
tera: web::Data<Tera>, tera: web::Data<Tera>,
config: web::Data<crate::ServerConfig>, config: web::Data<crate::ServerConfig>,
id: Identity, id: Identity,
@ -171,7 +171,7 @@ pub(crate) async fn view_profile(
} }
} }
pub(crate) async fn edit_profile( pub async fn edit_profile(
tera: web::Data<Tera>, tera: web::Data<Tera>,
config: web::Data<crate::ServerConfig>, config: web::Data<crate::ServerConfig>,
id: Identity, id: Identity,
@ -197,7 +197,7 @@ pub(crate) async fn edit_profile(
} }
} }
pub(crate) async fn process_edit_profile( pub async fn process_edit_profile(
data: web::Form<NewUser>, data: web::Form<NewUser>,
config: web::Data<crate::ServerConfig>, config: web::Data<crate::ServerConfig>,
id: Identity, id: Identity,
@ -213,7 +213,7 @@ pub(crate) async fn process_edit_profile(
} }
} }
pub(crate) async fn download_png( pub async fn download_png(
id: Identity, id: Identity,
config: web::Data<crate::ServerConfig>, config: web::Data<crate::ServerConfig>,
link_code: web::Path<String>, link_code: web::Path<String>,
@ -237,7 +237,7 @@ pub(crate) async fn download_png(
} }
} }
pub(crate) async fn signup( pub async fn signup(
tera: web::Data<Tera>, tera: web::Data<Tera>,
config: web::Data<crate::ServerConfig>, config: web::Data<crate::ServerConfig>,
id: Identity, id: Identity,
@ -257,7 +257,7 @@ pub(crate) async fn signup(
} }
} }
pub(crate) async fn process_signup( pub async fn process_signup(
data: web::Form<NewUser>, data: web::Form<NewUser>,
config: web::Data<crate::ServerConfig>, config: web::Data<crate::ServerConfig>,
id: Identity, id: Identity,
@ -270,7 +270,7 @@ pub(crate) async fn process_signup(
} }
} }
pub(crate) async fn toggle_admin( pub async fn toggle_admin(
data: web::Path<String>, data: web::Path<String>,
config: web::Data<crate::ServerConfig>, config: web::Data<crate::ServerConfig>,
id: Identity, id: Identity,
@ -282,7 +282,7 @@ pub(crate) async fn toggle_admin(
))) )))
} }
pub(crate) async fn set_language( pub async fn set_language(
data: web::Path<String>, data: web::Path<String>,
config: web::Data<crate::ServerConfig>, config: web::Data<crate::ServerConfig>,
id: Identity, id: Identity,
@ -291,7 +291,7 @@ pub(crate) async fn set_language(
Ok(redirect_builder("/admin/index/")) Ok(redirect_builder("/admin/index/"))
} }
pub(crate) async fn login( pub async fn login(
tera: web::Data<Tera>, tera: web::Data<Tera>,
id: Identity, id: Identity,
config: web::Data<crate::ServerConfig>, config: web::Data<crate::ServerConfig>,
@ -311,7 +311,7 @@ pub(crate) async fn login(
Ok(HttpResponse::Ok().body(rendered)) Ok(HttpResponse::Ok().body(rendered))
} }
pub(crate) async fn process_login( pub async fn process_login(
data: web::Form<LoginUser>, data: web::Form<LoginUser>,
config: web::Data<crate::ServerConfig>, config: web::Data<crate::ServerConfig>,
id: Identity, id: Identity,
@ -343,12 +343,12 @@ pub(crate) async fn process_login(
} }
} }
pub(crate) async fn logout(id: Identity) -> Result<HttpResponse, ServerError> { pub async fn logout(id: Identity) -> Result<HttpResponse, ServerError> {
id.forget(); id.forget();
Ok(redirect_builder("/admin/login/")) Ok(redirect_builder("/admin/login/"))
} }
pub(crate) async fn redirect( pub async fn redirect(
tera: web::Data<Tera>, tera: web::Data<Tera>,
config: web::Data<crate::ServerConfig>, config: web::Data<crate::ServerConfig>,
data: web::Path<String>, data: web::Path<String>,
@ -381,13 +381,13 @@ pub(crate) async fn redirect(
} }
} }
pub(crate) async fn redirect_empty( pub async fn redirect_empty(
config: web::Data<crate::ServerConfig>, config: web::Data<crate::ServerConfig>,
) -> Result<HttpResponse, ServerError> { ) -> Result<HttpResponse, ServerError> {
Ok(redirect_builder(&config.empty_forward_url)) Ok(redirect_builder(&config.empty_forward_url))
} }
pub(crate) async fn create_link( pub async fn create_link(
tera: web::Data<Tera>, tera: web::Data<Tera>,
config: web::Data<crate::ServerConfig>, config: web::Data<crate::ServerConfig>,
id: Identity, id: Identity,
@ -407,7 +407,7 @@ pub(crate) async fn create_link(
} }
} }
pub(crate) async fn process_link_creation( pub async fn process_link_creation(
data: web::Form<LinkForm>, data: web::Form<LinkForm>,
config: web::Data<crate::ServerConfig>, config: web::Data<crate::ServerConfig>,
id: Identity, id: Identity,
@ -419,7 +419,7 @@ pub(crate) async fn process_link_creation(
))) )))
} }
pub(crate) async fn edit_link( pub async fn edit_link(
tera: web::Data<Tera>, tera: web::Data<Tera>,
config: web::Data<crate::ServerConfig>, config: web::Data<crate::ServerConfig>,
id: Identity, id: Identity,
@ -436,7 +436,7 @@ pub(crate) async fn edit_link(
} }
Ok(redirect_builder("/admin/login/")) Ok(redirect_builder("/admin/login/"))
} }
pub(crate) async fn process_link_edit( pub async fn process_link_edit(
data: web::Form<LinkForm>, data: web::Form<LinkForm>,
config: web::Data<crate::ServerConfig>, config: web::Data<crate::ServerConfig>,
id: Identity, id: Identity,
@ -451,7 +451,7 @@ pub(crate) async fn process_link_edit(
} }
} }
pub(crate) async fn process_link_delete( pub async fn process_link_delete(
id: Identity, id: Identity,
config: web::Data<crate::ServerConfig>, config: web::Data<crate::ServerConfig>,
link_code: web::Path<String>, link_code: web::Path<String>,

185
src/lib.rs Normal file
View File

@ -0,0 +1,185 @@
extern crate sqlx;
#[allow(unused_imports)]
#[macro_use(
slog_o,
slog_info,
slog_warn,
slog_error,
slog_log,
slog_record,
slog_record_static,
slog_b,
slog_kv
)]
extern crate slog;
extern crate slog_async;
pub mod forms;
pub mod models;
pub mod queries;
use std::{fmt::Display, path::PathBuf, str::FromStr};
use actix_web::HttpResponse;
use qrcode::types::QrError;
use sqlx::{Pool, Sqlite};
#[derive(Debug)]
pub enum ServerError {
Argonautic,
Database(sqlx::Error),
DatabaseMigration(sqlx::migrate::MigrateError),
Environment,
Template(tera::Error),
Qr(QrError),
Io(std::io::Error),
User(String),
}
impl std::fmt::Display for ServerError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Argonautic => write!(f, "Argonautica Error"),
Self::Database(e) => write!(f, "Database Error: {}", e),
Self::DatabaseMigration(e) => {
write!(f, "Migration Error: {}", e)
}
Self::Environment => write!(f, "Environment Error"),
Self::Template(e) => write!(f, "Template Error: {:?}", e),
Self::Qr(e) => write!(f, "Qr Code Error: {:?}", e),
Self::Io(e) => write!(f, "IO Error: {:?}", e),
Self::User(data) => write!(f, "{}", data),
}
}
}
impl actix_web::error::ResponseError for ServerError {
fn error_response(&self) -> HttpResponse {
match self {
Self::Argonautic => HttpResponse::InternalServerError().json("Argonautica Error"),
Self::Database(e) => {
HttpResponse::InternalServerError().json(format!("Database Error: {:?}", e))
}
Self::DatabaseMigration(_) => {
unimplemented!("A migration error should never be rendered")
}
Self::Environment => HttpResponse::InternalServerError().json("Environment Error"),
Self::Template(e) => {
HttpResponse::InternalServerError().json(format!("Template Error: {:?}", e))
}
Self::Qr(e) => {
HttpResponse::InternalServerError().json(format!("Qr Code Error: {:?}", e))
}
Self::Io(e) => HttpResponse::InternalServerError().json(format!("IO Error: {:?}", e)),
Self::User(data) => HttpResponse::InternalServerError().json(data),
}
}
}
impl From<std::env::VarError> for ServerError {
fn from(e: std::env::VarError) -> Self {
eprintln!("Environment error {:?}", e);
Self::Environment
}
}
impl From<sqlx::Error> for ServerError {
fn from(err: sqlx::Error) -> Self {
eprintln!("Database error {:?}", err);
Self::Database(err)
}
}
impl From<sqlx::migrate::MigrateError> for ServerError {
fn from(err: sqlx::migrate::MigrateError) -> Self {
eprintln!("Database error {:?}", err);
Self::DatabaseMigration(err)
}
}
impl From<argonautica::Error> for ServerError {
fn from(e: argonautica::Error) -> Self {
eprintln!("Authentication error {:?}", e);
Self::Argonautic
}
}
impl From<tera::Error> for ServerError {
fn from(e: tera::Error) -> Self {
eprintln!("Template error {:?}", e);
Self::Template(e)
}
}
impl From<QrError> for ServerError {
fn from(e: QrError) -> Self {
eprintln!("Template error {:?}", e);
Self::Qr(e)
}
}
impl From<std::io::Error> for ServerError {
fn from(e: std::io::Error) -> Self {
eprintln!("IO error {:?}", e);
Self::Io(e)
}
}
#[derive(Debug, Clone)]
pub enum Protocol {
Http,
Https,
}
impl Display for Protocol {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Http => f.write_str("http"),
Self::Https => f.write_str("https"),
}
}
}
impl FromStr for Protocol {
type Err = ServerError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s {
"http" => Ok(Self::Http),
"https" => Ok(Self::Https),
_ => Err(ServerError::User("Failed to parse Protocol".to_owned())),
}
}
}
#[derive(Debug, Clone)]
pub struct ServerConfig {
pub secret: String,
pub db: PathBuf,
pub db_pool: Pool<Sqlite>,
pub public_url: String,
pub internal_ip: String,
pub port: u32,
pub protocol: Protocol,
pub log: slog::Logger,
pub empty_forward_url: String,
pub brand_name: String,
}
impl ServerConfig {
#[must_use]
pub fn to_env_strings(&self) -> Vec<String> {
vec![
format!("PSLINK_DATABASE=\"{}\"\n", self.db.display()),
format!("PSLINK_PORT={}\n", self.port),
format!("PSLINK_PUBLIC_URL=\"{}\"\n", self.public_url),
format!("PSLINK_EMPTY_FORWARD_URL=\"{}\"\n", self.empty_forward_url),
format!("PSLINK_BRAND_NAME=\"{}\"\n", self.brand_name),
format!("PSLINK_IP=\"{}\"\n", self.internal_ip),
format!("PSLINK_PROTOCOL=\"{}\"\n", self.protocol),
concat!(
"# The SECRET_KEY variable is used for password encryption.\n",
"# If it is changed all existing passwords are invalid.\n"
)
.to_owned(),
format!("PSLINK_SECRET=\"{}\"\n", self.secret),
]
}
}

View File

@ -24,7 +24,12 @@ impl User {
.await; .await;
user.map_err(ServerError::Database) user.map_err(ServerError::Database)
} }
pub(crate) async fn get_user_by_name(
/// get a user by its username
///
/// # Errors
/// fails with [`ServerError`] if the user does not exist or the database cannot be acessed.
pub async fn get_user_by_name(
name: &str, name: &str,
server_config: &ServerConfig, server_config: &ServerConfig,
) -> Result<Self, ServerError> { ) -> Result<Self, ServerError> {
@ -63,11 +68,11 @@ impl User {
.await?; .await?;
Ok(()) Ok(())
} }
/// Change an admin user to normal user and a normal user to admin
pub(crate) async fn toggle_admin( ///
self, /// # Errors
server_config: &ServerConfig, /// fails with [`ServerError`] if the database cannot be acessed. (the user should exist)
) -> Result<(), ServerError> { pub async fn toggle_admin(self, server_config: &ServerConfig) -> Result<(), ServerError> {
let new_role = 2 - (self.role + 1) % 2; let new_role = 2 - (self.role + 1) % 2;
sqlx::query!("UPDATE users SET role = ? where id = ?", new_role, self.id) sqlx::query!("UPDATE users SET role = ? where id = ?", new_role, self.id)
.execute(&server_config.db_pool) .execute(&server_config.db_pool)
@ -90,7 +95,13 @@ impl User {
Ok(()) Ok(())
} }
pub(crate) async fn count_admins(server_config: &ServerConfig) -> Result<Count, ServerError> { /// Count the admin accounts
///
/// this is usefull for determining if any admins exist at all.
///
/// # Errors
/// fails with [`ServerError`] if the database cannot be acessed.
pub async fn count_admins(server_config: &ServerConfig) -> Result<Count, ServerError> {
let num = sqlx::query_as!(Count, "select count(*) as number from users where role = 2") let num = sqlx::query_as!(Count, "select count(*) as number from users where role = 2")
.fetch_one(&server_config.db_pool) .fetch_one(&server_config.db_pool)
.await?; .await?;
@ -106,7 +117,11 @@ pub struct NewUser {
} }
impl NewUser { impl NewUser {
pub(crate) fn new( /// Create a new user that can then be inserted in the database
///
/// # Errors
/// fails with [`ServerError`] if the password could not be encrypted.
pub fn new(
username: String, username: String,
email: String, email: String,
password: &str, password: &str,
@ -136,10 +151,11 @@ impl NewUser {
Ok(hash) Ok(hash)
} }
pub(crate) async fn insert_user( /// Insert this user into the database
&self, ///
server_config: &ServerConfig, /// # Errors
) -> Result<(), ServerError> { /// fails with [`ServerError`] if the database cannot be acessed.
pub async fn insert_user(&self, server_config: &ServerConfig) -> Result<(), ServerError> {
sqlx::query!( sqlx::query!(
"Insert into users ( "Insert into users (
username, username,

View File

@ -30,7 +30,10 @@ impl Role {
} }
/// queries the user matching the given [`actix_identity::Identity`] and determins its authentication and permission level. Returns a [`Role`] containing the user if it is authenticated. /// queries the user matching the given [`actix_identity::Identity`] and determins its authentication and permission level. Returns a [`Role`] containing the user if it is authenticated.
pub(crate) async fn authenticate( ///
/// # Errors
/// Fails only if there are issues using the database.
pub async fn authenticate(
id: &Identity, id: &Identity,
server_config: &ServerConfig, server_config: &ServerConfig,
) -> Result<Role, ServerError> { ) -> Result<Role, ServerError> {
@ -62,7 +65,10 @@ pub struct FullLink {
} }
/// Returns a List of `FullLink` meaning `Links` enriched by their author and statistics. This returns all links if the user is either Admin or Regular user. /// Returns a List of `FullLink` meaning `Links` enriched by their author and statistics. This returns all links if the user is either Admin or Regular user.
pub(crate) async fn list_all_allowed( ///
/// # Errors
/// Fails with [`ServerError`] if access to the database fails.
pub async fn list_all_allowed(
id: &Identity, id: &Identity,
server_config: &ServerConfig, server_config: &ServerConfig,
) -> Result<List<FullLink>, ServerError> { ) -> Result<List<FullLink>, ServerError> {
@ -126,7 +132,10 @@ pub(crate) async fn list_all_allowed(
} }
/// Only admins can list all users /// Only admins can list all users
pub(crate) async fn list_users( ///
/// # Errors
/// Fails with [`ServerError`] if access to the database fails or this user does not have permissions.
pub async fn list_users(
id: &Identity, id: &Identity,
server_config: &ServerConfig, server_config: &ServerConfig,
) -> Result<List<User>, ServerError> { ) -> Result<List<User>, ServerError> {
@ -151,7 +160,11 @@ pub struct Item<T> {
} }
/// Get a user if permissions are accordingly /// Get a user if permissions are accordingly
pub(crate) async fn get_user( ///
/// # Errors
/// Fails with [`ServerError`] if access to the database fails or this user does not have permissions.
#[allow(clippy::clippy::missing_panics_doc)]
pub async fn get_user(
id: &Identity, id: &Identity,
user_id: &str, user_id: &str,
server_config: &ServerConfig, server_config: &ServerConfig,
@ -181,7 +194,10 @@ pub(crate) async fn get_user(
} }
/// Get a user **without permission checks** (needed for login) /// Get a user **without permission checks** (needed for login)
pub(crate) async fn get_user_by_name( ///
/// # Errors
/// Fails with [`ServerError`] if access to the database fails.
pub async fn get_user_by_name(
username: &str, username: &str,
server_config: &ServerConfig, server_config: &ServerConfig,
) -> Result<User, ServerError> { ) -> Result<User, ServerError> {
@ -189,7 +205,11 @@ pub(crate) async fn get_user_by_name(
Ok(user) Ok(user)
} }
pub(crate) async fn create_user( /// Create a new user and save it to the database
///
/// # Errors
/// Fails with [`ServerError`] if access to the database fails, this user does not have permissions or the user already exists.
pub async fn create_user(
id: &Identity, id: &Identity,
data: &web::Form<NewUser>, data: &web::Form<NewUser>,
server_config: &ServerConfig, server_config: &ServerConfig,
@ -222,7 +242,11 @@ pub(crate) async fn create_user(
/// Take a [`actix_web::web::Form<NewUser>`] and update the corresponding entry in the database. /// Take a [`actix_web::web::Form<NewUser>`] and update the corresponding entry in the database.
/// The password is only updated if a new password of at least 4 characters is provided. /// The password is only updated if a new password of at least 4 characters is provided.
/// The `user_id` is never changed. /// The `user_id` is never changed.
pub(crate) async fn update_user( ///
/// # Errors
/// Fails with [`ServerError`] if access to the database fails, this user does not have permissions, or the given data is malformed.
#[allow(clippy::clippy::missing_panics_doc)]
pub async fn update_user(
id: &Identity, id: &Identity,
user_id: &str, user_id: &str,
server_config: &ServerConfig, server_config: &ServerConfig,
@ -266,8 +290,11 @@ pub(crate) async fn update_user(
Err(ServerError::User("Permission denied".to_owned())) Err(ServerError::User("Permission denied".to_owned()))
} }
} }
/// Demote an admin user to a normal user or promote a normal user to admin privileges.
pub(crate) async fn toggle_admin( ///
/// # Errors
/// Fails with [`ServerError`] if access to the database fails, this user does not have permissions or the user does not exist.
pub async fn toggle_admin(
id: &Identity, id: &Identity,
user_id: &str, user_id: &str,
server_config: &ServerConfig, server_config: &ServerConfig,
@ -305,7 +332,11 @@ pub(crate) async fn toggle_admin(
} }
} }
pub(crate) async fn set_language( /// Set the language of a given user
///
/// # Errors
/// Fails with [`ServerError`] if access to the database fails, this user does not have permissions or the language given is invalid.
pub async fn set_language(
id: &Identity, id: &Identity,
lang_code: &str, lang_code: &str,
server_config: &ServerConfig, server_config: &ServerConfig,
@ -326,7 +357,10 @@ pub(crate) async fn set_language(
} }
/// Get one link if permissions are accordingly. /// Get one link if permissions are accordingly.
pub(crate) async fn get_link( ///
/// # Errors
/// Fails with [`ServerError`] if access to the database fails or this user does not have permissions.
pub async fn get_link(
id: &Identity, id: &Identity,
link_code: &str, link_code: &str,
server_config: &ServerConfig, server_config: &ServerConfig,
@ -341,7 +375,10 @@ pub(crate) async fn get_link(
} }
/// Get link **without authentication** /// Get link **without authentication**
pub(crate) async fn get_link_simple( ///
/// # Errors
/// Fails with [`ServerError`] if access to the database fails.
pub async fn get_link_simple(
link_code: &str, link_code: &str,
server_config: &ServerConfig, server_config: &ServerConfig,
) -> Result<Link, ServerError> { ) -> Result<Link, ServerError> {
@ -351,19 +388,23 @@ pub(crate) async fn get_link_simple(
slog_info!(server_config.log, "Foun d link for {:?}", link); slog_info!(server_config.log, "Foun d link for {:?}", link);
Ok(link) Ok(link)
} }
/// Click on a link /// Click on a link
pub(crate) async fn click_link( ///
link_id: i64, /// # Errors
server_config: &ServerConfig, /// Fails with [`ServerError`] if access to the database fails.
) -> Result<(), ServerError> { pub async fn click_link(link_id: i64, server_config: &ServerConfig) -> Result<(), ServerError> {
slog_info!(server_config.log, "Clicking on {:?}", link_id); slog_info!(server_config.log, "Clicking on {:?}", link_id);
let new_click = NewClick::new(link_id); let new_click = NewClick::new(link_id);
new_click.insert_click(server_config).await?; new_click.insert_click(server_config).await?;
Ok(()) Ok(())
} }
/// Click on a link /// Delete a link
pub(crate) async fn delete_link( ///
/// # Errors
/// Fails with [`ServerError`] if access to the database fails or this user does not have permissions.
pub async fn delete_link(
id: &Identity, id: &Identity,
link_code: &str, link_code: &str,
server_config: &ServerConfig, server_config: &ServerConfig,
@ -379,7 +420,10 @@ pub(crate) async fn delete_link(
} }
/// Update a link if the user is admin or it is its own link. /// Update a link if the user is admin or it is its own link.
pub(crate) async fn update_link( ///
/// # Errors
/// Fails with [`ServerError`] if access to the database fails or this user does not have permissions.
pub async fn update_link(
id: &Identity, id: &Identity,
link_code: &str, link_code: &str,
data: web::Form<LinkForm>, data: web::Form<LinkForm>,
@ -415,7 +459,11 @@ pub(crate) async fn update_link(
} }
} }
pub(crate) async fn create_link( /// Create a new link
///
/// # Errors
/// Fails with [`ServerError`] if access to the database fails or this user does not have permissions.
pub async fn create_link(
id: &Identity, id: &Identity,
data: web::Form<LinkForm>, data: web::Form<LinkForm>,
server_config: &ServerConfig, server_config: &ServerConfig,