refactor
This commit is contained in:
@@ -3,16 +3,10 @@ name = "packager"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
default-run = "packager"
|
||||
|
||||
[[bin]]
|
||||
name = "packager"
|
||||
path = "src/main.rs"
|
||||
|
||||
[[bin]]
|
||||
name = "packager-adm"
|
||||
path = "src/bin/adm.rs"
|
||||
|
||||
[profile.dev]
|
||||
opt-level = 0
|
||||
lto = "off"
|
||||
|
||||
@@ -6,4 +6,10 @@ COPY target/x86_64-unknown-linux-musl/release/packager /usr/local/bin/packager
|
||||
|
||||
ENTRYPOINT ["tini", "--"]
|
||||
|
||||
CMD ["/usr/local/bin/packager", "--bind", "0.0.0.0", "--port", "3000", "--database-url", "/var/lib/packager/db/db.sqlite"]
|
||||
CMD [ \
|
||||
"/usr/local/bin/packager", \
|
||||
"--database-url", "/var/lib/packager/db/db.sqlite", \
|
||||
"serve", \
|
||||
"--bind", "0.0.0.0", \
|
||||
"--port", "3000" \
|
||||
]
|
||||
|
||||
@@ -1,107 +0,0 @@
|
||||
use std::fmt;
|
||||
use std::process::exit;
|
||||
|
||||
use clap::{Parser, Subcommand};
|
||||
|
||||
use packager::{models, sqlite, StartError};
|
||||
|
||||
#[derive(Parser, Debug)]
|
||||
#[command(author, version, about, long_about = None)]
|
||||
struct Args {
|
||||
#[arg(long)]
|
||||
database_url: String,
|
||||
|
||||
#[command(subcommand)]
|
||||
command: Command,
|
||||
}
|
||||
|
||||
#[derive(Subcommand, Debug)]
|
||||
enum Command {
|
||||
#[command(subcommand)]
|
||||
User(UserCommand),
|
||||
}
|
||||
|
||||
#[derive(Subcommand, Debug)]
|
||||
enum UserCommand {
|
||||
Create(UserCreate),
|
||||
}
|
||||
|
||||
#[derive(Parser, Debug)]
|
||||
struct UserCreate {
|
||||
#[arg(long)]
|
||||
username: String,
|
||||
#[arg(long)]
|
||||
fullname: String,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
enum Error {
|
||||
Generic { message: String },
|
||||
UserExists { username: String },
|
||||
}
|
||||
|
||||
impl std::error::Error for Error {}
|
||||
|
||||
impl fmt::Display for Error {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
match self {
|
||||
Self::Generic { message } => write!(f, "{}", message),
|
||||
Self::UserExists { username } => write!(f, "user \"{username}\" already exists"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<StartError> for Error {
|
||||
fn from(starterror: StartError) -> Self {
|
||||
Self::Generic {
|
||||
message: starterror.to_string(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
let args = Args::parse();
|
||||
|
||||
let database_pool = sqlite::init_database_pool(&args.database_url).await?;
|
||||
|
||||
match args.command {
|
||||
Command::User(cmd) => match cmd {
|
||||
UserCommand::Create(user) => {
|
||||
let id = match models::user::create(
|
||||
&database_pool,
|
||||
models::user::NewUser {
|
||||
username: &user.username,
|
||||
fullname: &user.fullname,
|
||||
},
|
||||
)
|
||||
.await
|
||||
{
|
||||
Ok(id) => id,
|
||||
Err(error) => {
|
||||
if let models::Error::Query(models::QueryError::Duplicate {
|
||||
description: _,
|
||||
}) = error
|
||||
{
|
||||
println!(
|
||||
"Error: {}",
|
||||
Error::UserExists {
|
||||
username: user.username,
|
||||
}
|
||||
.to_string()
|
||||
);
|
||||
exit(1);
|
||||
}
|
||||
return Err(error.into());
|
||||
}
|
||||
};
|
||||
println!(
|
||||
"User \"{}\" created successfully (id {})",
|
||||
user.username, id
|
||||
)
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -17,6 +17,7 @@ pub enum RequestError {
|
||||
AuthenticationUserNotFound { username: String },
|
||||
AuthenticationHeaderMissing,
|
||||
AuthenticationHeaderInvalid { message: String },
|
||||
Transport { inner: hyper::Error },
|
||||
}
|
||||
|
||||
impl std::error::Error for RequestError {}
|
||||
@@ -35,6 +36,9 @@ impl fmt::Display for RequestError {
|
||||
Self::AuthenticationHeaderInvalid { message } => {
|
||||
write!(f, "Authentication header invalid: {message}")
|
||||
}
|
||||
Self::Transport { inner } => {
|
||||
write!(f, "HTTP error: {inner}")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -43,52 +47,19 @@ impl fmt::Display for RequestError {
|
||||
pub enum Error {
|
||||
Model(models::Error),
|
||||
Request(RequestError),
|
||||
Start(StartError),
|
||||
Command(CommandError),
|
||||
}
|
||||
|
||||
impl std::error::Error for Error {}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum StartError {
|
||||
DatabaseInitError { message: String },
|
||||
DatabaseMigrationError { message: String },
|
||||
}
|
||||
|
||||
impl std::error::Error for StartError {}
|
||||
|
||||
impl fmt::Display for StartError {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
match self {
|
||||
Self::DatabaseInitError { message } => {
|
||||
write!(f, "database initialization error: {message}")
|
||||
}
|
||||
Self::DatabaseMigrationError { message } => {
|
||||
write!(f, "database migration error: {message}")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<sqlx::Error> for StartError {
|
||||
fn from(value: sqlx::Error) -> Self {
|
||||
Self::DatabaseInitError {
|
||||
message: value.to_string(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<sqlx::migrate::MigrateError> for StartError {
|
||||
fn from(value: sqlx::migrate::MigrateError) -> Self {
|
||||
Self::DatabaseMigrationError {
|
||||
message: value.to_string(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for Error {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
match self {
|
||||
Self::Model(model_error) => write!(f, "Model error: {model_error}"),
|
||||
Self::Request(request_error) => write!(f, "Request error: {request_error}"),
|
||||
Self::Start(start_error) => write!(f, "{start_error}"),
|
||||
Self::Command(command_error) => write!(f, "{command_error}"),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -99,6 +70,18 @@ impl From<models::Error> for Error {
|
||||
}
|
||||
}
|
||||
|
||||
impl From<StartError> for Error {
|
||||
fn from(value: StartError) -> Self {
|
||||
Self::Start(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<hyper::Error> for Error {
|
||||
fn from(value: hyper::Error) -> Self {
|
||||
Self::Request(RequestError::Transport { inner: value })
|
||||
}
|
||||
}
|
||||
|
||||
impl IntoResponse for Error {
|
||||
fn into_response(self) -> Response {
|
||||
match self {
|
||||
@@ -146,8 +129,74 @@ impl IntoResponse for Error {
|
||||
StatusCode::UNAUTHORIZED,
|
||||
view::ErrorPage::build(&request_error.to_string()),
|
||||
),
|
||||
RequestError::Transport { inner } => (
|
||||
StatusCode::INTERNAL_SERVER_ERROR,
|
||||
view::ErrorPage::build(&inner.to_string()),
|
||||
),
|
||||
},
|
||||
Self::Start(start_error) => (
|
||||
StatusCode::INTERNAL_SERVER_ERROR,
|
||||
view::ErrorPage::build(&start_error.to_string()),
|
||||
),
|
||||
Self::Command(command_error) => (
|
||||
StatusCode::INTERNAL_SERVER_ERROR,
|
||||
view::ErrorPage::build(&command_error.to_string()),
|
||||
),
|
||||
}
|
||||
.into_response()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum StartError {
|
||||
DatabaseInitError { message: String },
|
||||
DatabaseMigrationError { message: String },
|
||||
}
|
||||
|
||||
impl std::error::Error for StartError {}
|
||||
|
||||
impl fmt::Display for StartError {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
match self {
|
||||
Self::DatabaseInitError { message } => {
|
||||
write!(f, "database initialization error: {message}")
|
||||
}
|
||||
Self::DatabaseMigrationError { message } => {
|
||||
write!(f, "database migration error: {message}")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<sqlx::Error> for StartError {
|
||||
fn from(value: sqlx::Error) -> Self {
|
||||
Self::DatabaseInitError {
|
||||
message: value.to_string(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<sqlx::migrate::MigrateError> for StartError {
|
||||
fn from(value: sqlx::migrate::MigrateError) -> Self {
|
||||
Self::DatabaseMigrationError {
|
||||
message: value.to_string(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum CommandError {
|
||||
UserExists { username: String },
|
||||
}
|
||||
|
||||
impl std::error::Error for CommandError {}
|
||||
|
||||
impl fmt::Display for CommandError {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
match self {
|
||||
Self::UserExists { username } => {
|
||||
write!(f, "user \"{username}\" already exists")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,7 +11,7 @@ pub mod sqlite;
|
||||
|
||||
mod view;
|
||||
|
||||
pub use error::{Error, RequestError, StartError};
|
||||
pub use error::{CommandError, Error, RequestError, StartError};
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct AppState {
|
||||
|
||||
199
rust/src/main.rs
199
rust/src/main.rs
@@ -1,15 +1,31 @@
|
||||
use packager::{auth, routing, sqlite, AppState, ClientState, StartError};
|
||||
|
||||
use std::net::{IpAddr, SocketAddr};
|
||||
use std::process::ExitCode;
|
||||
use std::str::FromStr;
|
||||
|
||||
use clap::Parser;
|
||||
use clap::{Parser, Subcommand};
|
||||
|
||||
use packager::{auth, models, routing, sqlite, AppState, ClientState, Error};
|
||||
|
||||
#[derive(Parser, Debug)]
|
||||
#[command(author, version, about, long_about = None)]
|
||||
struct Args {
|
||||
#[arg(long)]
|
||||
database_url: String,
|
||||
|
||||
#[command(subcommand)]
|
||||
command: Command,
|
||||
}
|
||||
|
||||
#[derive(Subcommand, Debug)]
|
||||
enum Command {
|
||||
Serve(Serve),
|
||||
#[command(subcommand)]
|
||||
Admin(Admin),
|
||||
Migrate,
|
||||
}
|
||||
|
||||
#[derive(Parser, Debug)]
|
||||
struct Serve {
|
||||
#[arg(long, default_value_t = 3000)]
|
||||
port: u16,
|
||||
#[arg(long)]
|
||||
@@ -18,42 +34,143 @@ struct Args {
|
||||
disable_auth_and_assume_user: Option<String>,
|
||||
}
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<(), StartError> {
|
||||
tracing_subscriber::fmt()
|
||||
.with_max_level(tracing::Level::DEBUG)
|
||||
.init();
|
||||
|
||||
let args = Args::parse();
|
||||
|
||||
let database_pool = sqlite::init_database_pool(&args.database_url).await?;
|
||||
sqlite::migrate(&database_pool).await?;
|
||||
|
||||
let state = AppState {
|
||||
database_pool,
|
||||
client_state: ClientState::new(),
|
||||
auth_config: if let Some(assume_user) = args.disable_auth_and_assume_user {
|
||||
auth::AuthConfig::Disabled { assume_user }
|
||||
} else {
|
||||
auth::AuthConfig::Enabled
|
||||
},
|
||||
};
|
||||
|
||||
// build our application with a route
|
||||
let app = routing::router(state);
|
||||
let addr = SocketAddr::from((
|
||||
IpAddr::from_str(&args.bind)
|
||||
.map_err(|error| format!("error parsing bind address {}: {}", &args.bind, error))
|
||||
.unwrap(),
|
||||
args.port,
|
||||
));
|
||||
tracing::debug!("listening on {}", addr);
|
||||
axum::Server::try_bind(&addr)
|
||||
.map_err(|error| format!("error binding to {}: {}", addr, error))
|
||||
.unwrap()
|
||||
.serve(app.into_make_service())
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
Ok(())
|
||||
#[derive(Subcommand, Debug)]
|
||||
enum Admin {
|
||||
#[command(subcommand)]
|
||||
User(UserCommand),
|
||||
}
|
||||
|
||||
#[derive(Subcommand, Debug)]
|
||||
enum UserCommand {
|
||||
Create(UserCreate),
|
||||
}
|
||||
|
||||
#[derive(Parser, Debug)]
|
||||
struct UserCreate {
|
||||
#[arg(long)]
|
||||
username: String,
|
||||
#[arg(long)]
|
||||
fullname: String,
|
||||
}
|
||||
|
||||
struct MainResult(Result<(), Error>);
|
||||
|
||||
impl std::process::Termination for MainResult {
|
||||
fn report(self) -> std::process::ExitCode {
|
||||
match self.0 {
|
||||
Ok(_) => ExitCode::SUCCESS,
|
||||
Err(e) => {
|
||||
eprintln!("Error: {e}");
|
||||
ExitCode::FAILURE
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Error> for MainResult {
|
||||
fn from(error: Error) -> Self {
|
||||
Self(Err(error))
|
||||
}
|
||||
}
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> MainResult {
|
||||
let args = Args::parse();
|
||||
match args.command {
|
||||
Command::Serve(serve_args) => {
|
||||
tracing_subscriber::fmt()
|
||||
.with_max_level(tracing::Level::DEBUG)
|
||||
.init();
|
||||
|
||||
let database_pool = match sqlite::init_database_pool(&args.database_url).await {
|
||||
Ok(pool) => pool,
|
||||
Err(e) => return <_ as Into<Error>>::into(e).into(),
|
||||
};
|
||||
|
||||
if let Err(e) = sqlite::migrate(&database_pool).await {
|
||||
return <_ as Into<Error>>::into(e).into();
|
||||
}
|
||||
|
||||
let state = AppState {
|
||||
database_pool,
|
||||
client_state: ClientState::new(),
|
||||
auth_config: if let Some(assume_user) = serve_args.disable_auth_and_assume_user {
|
||||
auth::AuthConfig::Disabled { assume_user }
|
||||
} else {
|
||||
auth::AuthConfig::Enabled
|
||||
},
|
||||
};
|
||||
|
||||
// build our application with a route
|
||||
let app = routing::router(state);
|
||||
let addr = SocketAddr::from((
|
||||
IpAddr::from_str(&serve_args.bind)
|
||||
.map_err(|error| {
|
||||
format!("error parsing bind address {}: {}", &serve_args.bind, error)
|
||||
})
|
||||
.unwrap(),
|
||||
serve_args.port,
|
||||
));
|
||||
tracing::debug!("listening on {}", addr);
|
||||
if let Err(e) = axum::Server::try_bind(&addr)
|
||||
.map_err(|error| format!("error binding to {}: {}", addr, error))
|
||||
.unwrap()
|
||||
.serve(app.into_make_service())
|
||||
.await
|
||||
{
|
||||
return <hyper::Error as Into<Error>>::into(e).into();
|
||||
}
|
||||
}
|
||||
Command::Admin(admin_command) => match admin_command {
|
||||
Admin::User(cmd) => match cmd {
|
||||
UserCommand::Create(user) => {
|
||||
let database_pool = match sqlite::init_database_pool(&args.database_url).await {
|
||||
Ok(pool) => pool,
|
||||
Err(e) => return <_ as Into<Error>>::into(e).into(),
|
||||
};
|
||||
|
||||
let id = match models::user::create(
|
||||
&database_pool,
|
||||
models::user::NewUser {
|
||||
username: &user.username,
|
||||
fullname: &user.fullname,
|
||||
},
|
||||
)
|
||||
.await
|
||||
.map_err(|error| match error {
|
||||
models::Error::Query(models::QueryError::Duplicate { description: _ }) => {
|
||||
Error::Command(packager::CommandError::UserExists {
|
||||
username: user.username.clone(),
|
||||
})
|
||||
}
|
||||
_ => Error::Model(error),
|
||||
}) {
|
||||
Ok(id) => id,
|
||||
Err(e) => {
|
||||
return e.into();
|
||||
}
|
||||
};
|
||||
|
||||
println!(
|
||||
"User \"{}\" created successfully (id {})",
|
||||
&user.username, id
|
||||
)
|
||||
}
|
||||
},
|
||||
},
|
||||
Command::Migrate => {
|
||||
let database_pool = match sqlite::init_database_pool(&args.database_url).await {
|
||||
Ok(pool) => pool,
|
||||
Err(e) => return <_ as Into<Error>>::into(e).into(),
|
||||
};
|
||||
|
||||
if let Err(e) = sqlite::migrate(&database_pool).await {
|
||||
return <_ as Into<Error>>::into(e).into();
|
||||
}
|
||||
|
||||
println!("Migrations successfully applied");
|
||||
}
|
||||
}
|
||||
|
||||
MainResult(Ok(()))
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user