feat: accept config and process webhook events
This commit is contained in:
parent
c6585ffca5
commit
28ee8510c3
13 changed files with 2894 additions and 0 deletions
2105
Cargo.lock
generated
Normal file
2105
Cargo.lock
generated
Normal file
File diff suppressed because it is too large
Load diff
35
Cargo.toml
Normal file
35
Cargo.toml
Normal file
|
@ -0,0 +1,35 @@
|
||||||
|
[package]
|
||||||
|
name = "conductor"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2021"
|
||||||
|
repository = "https://git.batsense.net/librepages/conductor"
|
||||||
|
readme = "README.md"
|
||||||
|
license = "AGPLv3 or later version"
|
||||||
|
authors = ["realaravinth <realaravinth@batsense.net>"]
|
||||||
|
build = "build.rs"
|
||||||
|
|
||||||
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
actix-web = "4"
|
||||||
|
futures-util = { version = "0.3.17", default-features = false, features = ["std"] }
|
||||||
|
lazy_static = "1.4.0"
|
||||||
|
log = "0.4.17"
|
||||||
|
pretty_env_logger = "0.4.0"
|
||||||
|
serde = { version = "1", features=["derive"]}
|
||||||
|
actix-web-codegen-const-routes = { version = "0.1.0", tag = "0.1.0", git = "https://github.com/realaravinth/actix-web-codegen-const-routes" }
|
||||||
|
derive_builder = "0.11.2"
|
||||||
|
config = "0.11"
|
||||||
|
derive_more = "0.99.17"
|
||||||
|
url = { version = "2.2.2", features = ["serde"]}
|
||||||
|
serde_json = { version ="1", features = ["raw_value"]}
|
||||||
|
clap = { vesrion = "3.2.20", features = ["derive"]}
|
||||||
|
|
||||||
|
|
||||||
|
[build-dependencies]
|
||||||
|
serde_json = "1"
|
||||||
|
sqlx = { version = "0.6.1", features = [ "runtime-actix-rustls", "postgres", "time", "offline"] }
|
||||||
|
|
||||||
|
[dev-dependencies]
|
||||||
|
actix-rt = "2.7.0"
|
||||||
|
base64 = "0.13.0"
|
37
build.rs
Normal file
37
build.rs
Normal file
|
@ -0,0 +1,37 @@
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2021 Aravinth Manivannan <realaravinth@batsense.net>
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Affero General Public License as
|
||||||
|
* published by the Free Software Foundation, either version 3 of the
|
||||||
|
* License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Affero General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
use std::process::Command;
|
||||||
|
|
||||||
|
use sqlx::types::time::OffsetDateTime;
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
// note: add error checking yourself.
|
||||||
|
let output = Command::new("git")
|
||||||
|
.args(&["rev-parse", "HEAD"])
|
||||||
|
.output()
|
||||||
|
.unwrap();
|
||||||
|
let git_hash = String::from_utf8(output.stdout).unwrap();
|
||||||
|
println!("cargo:rustc-env=GIT_HASH={}", git_hash);
|
||||||
|
|
||||||
|
let now = OffsetDateTime::now_utc();
|
||||||
|
println!(
|
||||||
|
"cargo:rustc-env=COMPILED_DATE={}-{}-{}",
|
||||||
|
now.year(),
|
||||||
|
now.month(),
|
||||||
|
now.day()
|
||||||
|
);
|
||||||
|
}
|
35
config/default.toml
Normal file
35
config/default.toml
Normal file
|
@ -0,0 +1,35 @@
|
||||||
|
debug = true
|
||||||
|
source_code = "https://git.batsense.net/librepages/conductor"
|
||||||
|
|
||||||
|
[server]
|
||||||
|
# Please set a unique value, your mCaptcha instance's security depends on this being
|
||||||
|
# unique
|
||||||
|
# The port at which you want authentication to listen to
|
||||||
|
# takes a number, choose from 1000-10000 if you dont know what you are doing
|
||||||
|
port = 7000
|
||||||
|
#IP address. Enter 0.0.0.0 to listen on all available addresses
|
||||||
|
#ip= "0.0.0.0"
|
||||||
|
ip= "192.168.0.104"
|
||||||
|
# enter your hostname, eg: example.com
|
||||||
|
domain = "localhost"
|
||||||
|
# Set true if you have setup TLS with a reverse proxy like Nginx.
|
||||||
|
# Does HTTPS redirect and sends additional headers that can only be used if
|
||||||
|
# HTTPS available to improve security
|
||||||
|
proxy_has_tls = false
|
||||||
|
#url_prefix = ""
|
||||||
|
#
|
||||||
|
#[database]
|
||||||
|
## This section deals with the database location and how to access it
|
||||||
|
## Please note that at the moment, we have support for only postgresqa.
|
||||||
|
## Example, if you are Batman, your config would be:
|
||||||
|
## hostname = "batcave.org"
|
||||||
|
## port = "5432"
|
||||||
|
## username = "batman"
|
||||||
|
## password = "somereallycomplicatedBatmanpassword"
|
||||||
|
#hostname = "localhost"
|
||||||
|
#port = "5432"
|
||||||
|
#username = "postgres"
|
||||||
|
#password = "password"
|
||||||
|
#name = "postgres"
|
||||||
|
#pool = 4
|
||||||
|
#database_type="postgres" # "postgres"
|
17
src/api/mod.rs
Normal file
17
src/api/mod.rs
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2022 Aravinth Manivannan <realaravinth@batsense.net>
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Affero General Public License as
|
||||||
|
* published by the Free Software Foundation, either version 3 of the
|
||||||
|
* License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Affero General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
pub mod v1;
|
120
src/api/v1/meta.rs
Normal file
120
src/api/v1/meta.rs
Normal file
|
@ -0,0 +1,120 @@
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2022 Aravinth Manivannan <realaravinth@batsense.net>
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Affero General Public License as
|
||||||
|
* published by the Free Software Foundation, either version 3 of the
|
||||||
|
* License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Affero General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
use actix_web::{web, HttpResponse, Responder};
|
||||||
|
use derive_builder::Builder;
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
use crate::AppCtx;
|
||||||
|
use crate::{GIT_COMMIT_HASH, VERSION};
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, Deserialize, Builder, Serialize)]
|
||||||
|
pub struct BuildDetails {
|
||||||
|
pub version: &'static str,
|
||||||
|
pub git_commit_hash: &'static str,
|
||||||
|
pub source_code: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub mod routes {
|
||||||
|
pub struct Meta {
|
||||||
|
pub build_details: &'static str,
|
||||||
|
pub health: &'static str,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Meta {
|
||||||
|
pub const fn new() -> Self {
|
||||||
|
Self {
|
||||||
|
build_details: "/api/v1/meta/build",
|
||||||
|
health: "/api/v1/meta/health",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// emits build details of the bninary
|
||||||
|
#[actix_web_codegen_const_routes::get(path = "crate::API_V1_ROUTES.meta.build_details")]
|
||||||
|
async fn build_details(ctx: AppCtx) -> impl Responder {
|
||||||
|
let build = BuildDetails {
|
||||||
|
version: VERSION,
|
||||||
|
git_commit_hash: GIT_COMMIT_HASH,
|
||||||
|
source_code: ctx.source_code.clone(),
|
||||||
|
};
|
||||||
|
HttpResponse::Ok().json(build)
|
||||||
|
}
|
||||||
|
|
||||||
|
//#[derive(Clone, Debug, Deserialize, Builder, Serialize)]
|
||||||
|
///// Health check return datatype
|
||||||
|
//pub struct Health {
|
||||||
|
// db: bool,
|
||||||
|
//}
|
||||||
|
//
|
||||||
|
///// checks all components of the system
|
||||||
|
//#[actix_web_codegen_const_routes::get(path = "crate::API_V1_ROUTES.meta.health")]
|
||||||
|
//async fn health(ctx: crate::AppCtx) -> impl Responder {
|
||||||
|
// let mut resp_builder = HealthBuilder::default();
|
||||||
|
//
|
||||||
|
// resp_builder.db(ctx.db.ping().await);
|
||||||
|
//
|
||||||
|
// HttpResponse::Ok().json(resp_builder.build().unwrap())
|
||||||
|
//}
|
||||||
|
//
|
||||||
|
pub fn services(cfg: &mut web::ServiceConfig) {
|
||||||
|
cfg.service(build_details);
|
||||||
|
// cfg.service(health);
|
||||||
|
}
|
||||||
|
#[cfg(test)]
|
||||||
|
pub mod tests {
|
||||||
|
use actix_web::{http::StatusCode, test, App};
|
||||||
|
|
||||||
|
use crate::api::v1::services;
|
||||||
|
use crate::*;
|
||||||
|
|
||||||
|
#[actix_rt::test]
|
||||||
|
async fn build_details_works() {
|
||||||
|
let settings = Settings::new().unwrap();
|
||||||
|
let ctx = AppCtx::new(crate::ctx::Ctx::new(&settings).await);
|
||||||
|
let app = test::init_service(App::new().app_data(ctx.clone()).configure(services)).await;
|
||||||
|
|
||||||
|
let resp = test::call_service(
|
||||||
|
&app,
|
||||||
|
test::TestRequest::get()
|
||||||
|
.uri(API_V1_ROUTES.meta.build_details)
|
||||||
|
.to_request(),
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
assert_eq!(resp.status(), StatusCode::OK);
|
||||||
|
}
|
||||||
|
|
||||||
|
// #[actix_rt::test]
|
||||||
|
// async fn health_works() {
|
||||||
|
// use actix_web::test;
|
||||||
|
//
|
||||||
|
// let settings = Settings::new().unwrap();
|
||||||
|
// let ctx = AppCtx::new(crate::ctx::Ctx::new(&settings).await);
|
||||||
|
// let app = test::init_service(App::new().app_data(ctx.clone()).configure(services)).await;
|
||||||
|
//
|
||||||
|
// let resp = test::call_service(
|
||||||
|
// &app,
|
||||||
|
// test::TestRequest::get()
|
||||||
|
// .uri(API_V1_ROUTES.meta.health)
|
||||||
|
// .to_request(),
|
||||||
|
// )
|
||||||
|
// .await;
|
||||||
|
// assert_eq!(resp.status(), StatusCode::OK);
|
||||||
|
//
|
||||||
|
// let health_resp: super::Health = test::read_body_json(resp).await;
|
||||||
|
// }
|
||||||
|
}
|
49
src/api/v1/mod.rs
Normal file
49
src/api/v1/mod.rs
Normal file
|
@ -0,0 +1,49 @@
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2022 Aravinth Manivannan <realaravinth@batsense.net>
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Affero General Public License as
|
||||||
|
* published by the Free Software Foundation, either version 3 of the
|
||||||
|
* License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Affero General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
use actix_web::web;
|
||||||
|
|
||||||
|
pub mod webhook;
|
||||||
|
pub mod meta;
|
||||||
|
|
||||||
|
pub const API_V1_ROUTES: routes::Routes = routes::Routes::new();
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Eq, PartialEq)]
|
||||||
|
pub struct SignedInUser(String);
|
||||||
|
|
||||||
|
pub fn services(cfg: &mut web::ServiceConfig) {
|
||||||
|
meta::services(cfg);
|
||||||
|
webhook::services(cfg);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub mod routes {
|
||||||
|
use crate::api::v1::webhook::routes::Webhook;
|
||||||
|
use crate::api::v1::meta::routes::Meta;
|
||||||
|
|
||||||
|
pub struct Routes {
|
||||||
|
pub meta: Meta,
|
||||||
|
pub webhook: Webhook,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Routes {
|
||||||
|
pub const fn new() -> Self {
|
||||||
|
Self {
|
||||||
|
meta: Meta::new(),
|
||||||
|
webhook: Webhook::new(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
89
src/api/v1/webhook.rs
Normal file
89
src/api/v1/webhook.rs
Normal file
|
@ -0,0 +1,89 @@
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2022 Aravinth Manivannan <realaravinth@batsense.net>
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Affero General Public License as
|
||||||
|
* published by the Free Software Foundation, either version 3 of the
|
||||||
|
* License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Affero General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
use std::collections::HashMap;
|
||||||
|
|
||||||
|
use actix_web::{web, HttpRequest, HttpResponse, Responder};
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
use crate::errors::*;
|
||||||
|
use crate::AppCtx;
|
||||||
|
use crate::*;
|
||||||
|
|
||||||
|
pub mod routes {
|
||||||
|
use super::*;
|
||||||
|
#[derive(Debug, Eq, PartialEq, Deserialize, Serialize)]
|
||||||
|
pub struct Webhook {
|
||||||
|
pub post_event: &'static str,
|
||||||
|
}
|
||||||
|
impl Webhook {
|
||||||
|
pub const fn new() -> Self {
|
||||||
|
Self {
|
||||||
|
post_event: "/api/v1/events/new",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn services(cfg: &mut web::ServiceConfig) {
|
||||||
|
cfg.service(post_event);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize, Serialize, Debug)]
|
||||||
|
#[serde(untagged)]
|
||||||
|
enum EventType {
|
||||||
|
NewHostname(String),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[actix_web_codegen_const_routes::post(path = "API_V1_ROUTES.webhook.post_event")]
|
||||||
|
async fn post_event(ctx: AppCtx, payload: web::Json<EventType>) -> ServiceResult<impl Responder> {
|
||||||
|
Ok(HttpResponse::Created())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
pub mod tests {
|
||||||
|
use actix_web::{
|
||||||
|
http::{header, StatusCode},
|
||||||
|
test, App,
|
||||||
|
};
|
||||||
|
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[actix_rt::test]
|
||||||
|
async fn submit_works() {
|
||||||
|
let settings = Settings::new().unwrap();
|
||||||
|
let ctx = AppCtx::new(crate::ctx::Ctx::new(&settings).await);
|
||||||
|
let app = test::init_service(
|
||||||
|
App::new()
|
||||||
|
.app_data(ctx.clone())
|
||||||
|
.configure(crate::routes::services),
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
let new_hostname = EventType::NewHostname("demo.librepages.org".into());
|
||||||
|
|
||||||
|
// upload json
|
||||||
|
let upload_json = test::call_service(
|
||||||
|
&app,
|
||||||
|
test::TestRequest::post()
|
||||||
|
.uri(API_V1_ROUTES.webhook.post_event)
|
||||||
|
.set_json(&new_hostname)
|
||||||
|
.to_request(),
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
assert_eq!(upload_json.status(), StatusCode::CREATED);
|
||||||
|
}
|
||||||
|
}
|
56
src/ctx.rs
Normal file
56
src/ctx.rs
Normal file
|
@ -0,0 +1,56 @@
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2022 Aravinth Manivannan <realaravinth@batsense.net>
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Affero General Public License as
|
||||||
|
* published by the Free Software Foundation, either version 3 of the
|
||||||
|
* License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Affero General Public License for more details.
|
||||||
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
//! App data: database connections, etc.
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
//use crate::errors::ServiceResult;
|
||||||
|
use crate::settings::Settings;
|
||||||
|
|
||||||
|
/// App data
|
||||||
|
pub struct Ctx {
|
||||||
|
// /// database ops defined by db crates
|
||||||
|
// pub db: BoxDB,
|
||||||
|
/// app settings
|
||||||
|
pub settings: Settings,
|
||||||
|
pub source_code: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Ctx {
|
||||||
|
#[cfg(not(tarpaulin_include))]
|
||||||
|
/// create new instance of app data
|
||||||
|
pub async fn new(s: &Settings) -> ArcCtx {
|
||||||
|
let source_code = {
|
||||||
|
let mut url = s.source_code.clone();
|
||||||
|
if !url.ends_with('/') {
|
||||||
|
url.push('/');
|
||||||
|
}
|
||||||
|
let mut base = url::Url::parse(&url).unwrap();
|
||||||
|
base = base.join("tree/").unwrap();
|
||||||
|
base = base.join(crate::GIT_COMMIT_HASH).unwrap();
|
||||||
|
base.into()
|
||||||
|
};
|
||||||
|
|
||||||
|
let data = Ctx {
|
||||||
|
// db,
|
||||||
|
settings: s.clone(),
|
||||||
|
source_code,
|
||||||
|
};
|
||||||
|
|
||||||
|
Arc::new(data)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub type ArcCtx = Arc<Ctx>;
|
94
src/errors.rs
Normal file
94
src/errors.rs
Normal file
|
@ -0,0 +1,94 @@
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2022 Aravinth Manivannan <realaravinth@batsense.net>
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Affero General Public License as
|
||||||
|
* published by the Free Software Foundation, either version 3 of the
|
||||||
|
* License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Affero General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
use std::convert::From;
|
||||||
|
|
||||||
|
use actix_web::http;
|
||||||
|
use actix_web::http::StatusCode;
|
||||||
|
use actix_web::HttpResponse;
|
||||||
|
use actix_web::HttpResponseBuilder;
|
||||||
|
use actix_web::ResponseError;
|
||||||
|
use derive_more::{Display, Error};
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
use url::ParseError;
|
||||||
|
|
||||||
|
#[derive(Debug, Display, PartialEq, Eq, Error)]
|
||||||
|
#[cfg(not(tarpaulin_include))]
|
||||||
|
#[allow(dead_code)]
|
||||||
|
pub enum ServiceError {
|
||||||
|
#[display(fmt = "unauthorized")]
|
||||||
|
Unauthorized,
|
||||||
|
|
||||||
|
#[display(fmt = "internal server error")]
|
||||||
|
InternalServerError,
|
||||||
|
|
||||||
|
#[display(
|
||||||
|
fmt = "This server is is closed for registration. Contact admin if this is unexpecter"
|
||||||
|
)]
|
||||||
|
ClosedForRegistration,
|
||||||
|
|
||||||
|
#[display(fmt = "The value you entered for URL is not a URL")] //405j
|
||||||
|
NotAUrl,
|
||||||
|
|
||||||
|
#[display(fmt = "Wrong password")]
|
||||||
|
WrongPassword,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize)]
|
||||||
|
#[cfg(not(tarpaulin_include))]
|
||||||
|
pub struct ErrorToResponse {
|
||||||
|
pub error: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(not(tarpaulin_include))]
|
||||||
|
impl ResponseError for ServiceError {
|
||||||
|
#[cfg(not(tarpaulin_include))]
|
||||||
|
fn error_response(&self) -> HttpResponse {
|
||||||
|
HttpResponseBuilder::new(self.status_code())
|
||||||
|
.append_header((
|
||||||
|
http::header::CONTENT_TYPE,
|
||||||
|
"application/json; charset=UTF-8",
|
||||||
|
))
|
||||||
|
.body(
|
||||||
|
serde_json::to_string(&ErrorToResponse {
|
||||||
|
error: self.to_string(),
|
||||||
|
})
|
||||||
|
.unwrap(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(not(tarpaulin_include))]
|
||||||
|
fn status_code(&self) -> StatusCode {
|
||||||
|
match self {
|
||||||
|
ServiceError::ClosedForRegistration => StatusCode::FORBIDDEN,
|
||||||
|
ServiceError::InternalServerError => StatusCode::INTERNAL_SERVER_ERROR,
|
||||||
|
ServiceError::NotAUrl => StatusCode::BAD_REQUEST,
|
||||||
|
ServiceError::WrongPassword => StatusCode::UNAUTHORIZED,
|
||||||
|
ServiceError::Unauthorized => StatusCode::UNAUTHORIZED,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<ParseError> for ServiceError {
|
||||||
|
#[cfg(not(tarpaulin_include))]
|
||||||
|
fn from(_: ParseError) -> ServiceError {
|
||||||
|
ServiceError::NotAUrl
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(not(tarpaulin_include))]
|
||||||
|
pub type ServiceResult<V> = std::result::Result<V, ServiceError>;
|
140
src/main.rs
Normal file
140
src/main.rs
Normal file
|
@ -0,0 +1,140 @@
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2022 Aravinth Manivannan <realaravinth@batsense.net>
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Affero General Public License as
|
||||||
|
* published by the Free Software Foundation, either version 3 of the
|
||||||
|
* License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Affero General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
use std::env;
|
||||||
|
|
||||||
|
use actix_web::http::StatusCode;
|
||||||
|
use actix_web::web::JsonConfig;
|
||||||
|
use actix_web::{error::InternalError, middleware, App, HttpServer};
|
||||||
|
use clap::{Parser, Subcommand};
|
||||||
|
use log::info;
|
||||||
|
|
||||||
|
use lazy_static::lazy_static;
|
||||||
|
|
||||||
|
mod api;
|
||||||
|
mod ctx;
|
||||||
|
//mod docs;
|
||||||
|
#[cfg(not(tarpaulin_include))]
|
||||||
|
mod errors;
|
||||||
|
//#[macro_use]
|
||||||
|
//mod pages;
|
||||||
|
//#[macro_use]
|
||||||
|
mod routes;
|
||||||
|
mod settings;
|
||||||
|
//mod static_assets;
|
||||||
|
//#[cfg(test)]
|
||||||
|
//#[macro_use]
|
||||||
|
//mod tests;
|
||||||
|
//
|
||||||
|
pub use crate::ctx::Ctx;
|
||||||
|
//pub use crate::static_assets::static_files::assets::*;
|
||||||
|
pub use api::v1::API_V1_ROUTES;
|
||||||
|
//pub use docs::DOCS;
|
||||||
|
//pub use pages::routes::ROUTES as PAGES;
|
||||||
|
pub use settings::Settings;
|
||||||
|
//use static_assets::FileMap;
|
||||||
|
|
||||||
|
lazy_static! {
|
||||||
|
pub static ref SETTINGS: Settings= Settings::new().unwrap();
|
||||||
|
// pub static ref S: String = env::var("S").unwrap();
|
||||||
|
// pub static ref FILES: FileMap = FileMap::new();
|
||||||
|
// pub static ref JS: &'static str =
|
||||||
|
// FILES.get("./static/cache/bundle/bundle.js").unwrap();
|
||||||
|
// pub static ref CSS: &'static str =
|
||||||
|
// FILES.get("./static/cache/bundle/css/main.css").unwrap();
|
||||||
|
// pub static ref MOBILE_CSS: &'static str =
|
||||||
|
// FILES.get("./static/cache/bundle/css/mobile.css").unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub const COMPILED_DATE: &str = env!("COMPILED_DATE");
|
||||||
|
pub const GIT_COMMIT_HASH: &str = env!("GIT_HASH");
|
||||||
|
pub const VERSION: &str = env!("CARGO_PKG_VERSION");
|
||||||
|
pub const PKG_NAME: &str = env!("CARGO_PKG_NAME");
|
||||||
|
pub const PKG_DESCRIPTION: &str = env!("CARGO_PKG_DESCRIPTION");
|
||||||
|
pub const PKG_HOMEPAGE: &str = env!("CARGO_PKG_HOMEPAGE");
|
||||||
|
|
||||||
|
pub const CACHE_AGE: u32 = 604800;
|
||||||
|
|
||||||
|
use ctx::ArcCtx;
|
||||||
|
pub type AppCtx = actix_web::web::Data<ArcCtx>;
|
||||||
|
|
||||||
|
#[derive(Parser)]
|
||||||
|
#[clap(author, version, about, long_about = None)]
|
||||||
|
struct Cli {
|
||||||
|
#[clap(subcommand)]
|
||||||
|
command: Commands,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Subcommand)]
|
||||||
|
enum Commands {
|
||||||
|
/// run server
|
||||||
|
Serve,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[actix_web::main]
|
||||||
|
#[cfg(not(tarpaulin_include))]
|
||||||
|
async fn main() -> std::io::Result<()> {
|
||||||
|
env::set_var("RUST_LOG", "info");
|
||||||
|
|
||||||
|
pretty_env_logger::init();
|
||||||
|
|
||||||
|
let cli = Cli::parse();
|
||||||
|
|
||||||
|
info!(
|
||||||
|
"{}: {}.\nFor more information, see: {}\nBuild info:\nVersion: {} commit: {}",
|
||||||
|
PKG_NAME, PKG_DESCRIPTION, PKG_HOMEPAGE, VERSION, GIT_COMMIT_HASH
|
||||||
|
);
|
||||||
|
|
||||||
|
let settings = Settings::new().unwrap();
|
||||||
|
let ctx = Ctx::new(&settings).await;
|
||||||
|
let ctx = actix_web::web::Data::new(ctx);
|
||||||
|
|
||||||
|
match &cli.command {
|
||||||
|
Commands::Serve => serve(settings, ctx).await.unwrap(),
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn serve(settings: Settings, ctx: AppCtx) -> std::io::Result<()> {
|
||||||
|
let ip = settings.server.get_ip();
|
||||||
|
println!("Starting server on: http://{ip}");
|
||||||
|
|
||||||
|
HttpServer::new(move || {
|
||||||
|
App::new()
|
||||||
|
.wrap(middleware::Logger::default())
|
||||||
|
.wrap(
|
||||||
|
middleware::DefaultHeaders::new().add(("Permissions-Policy", "interest-cohort=()")),
|
||||||
|
)
|
||||||
|
.wrap(middleware::Compress::default())
|
||||||
|
.app_data(ctx.clone())
|
||||||
|
.wrap(middleware::NormalizePath::new(
|
||||||
|
middleware::TrailingSlash::Trim,
|
||||||
|
))
|
||||||
|
.app_data(get_json_err())
|
||||||
|
.configure(routes::services)
|
||||||
|
})
|
||||||
|
.bind(ip)?
|
||||||
|
.run()
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(not(tarpaulin_include))]
|
||||||
|
pub fn get_json_err() -> JsonConfig {
|
||||||
|
JsonConfig::default().error_handler(|err, _| {
|
||||||
|
//debug!("JSON deserialization error: {:?}", &err);
|
||||||
|
InternalError::new(err, StatusCode::BAD_REQUEST).into()
|
||||||
|
})
|
||||||
|
}
|
21
src/routes.rs
Normal file
21
src/routes.rs
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2022 Aravinth Manivannan <realaravinth@batsense.net>
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Affero General Public License as
|
||||||
|
* published by the Free Software Foundation, either version 3 of the
|
||||||
|
* License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Affero General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
use actix_web::web;
|
||||||
|
|
||||||
|
pub fn services(cfg: &mut web::ServiceConfig) {
|
||||||
|
crate::api::v1::services(cfg);
|
||||||
|
}
|
96
src/settings.rs
Normal file
96
src/settings.rs
Normal file
|
@ -0,0 +1,96 @@
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2022 Aravinth Manivannan <realaravinth@batsense.net>
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Affero General Public License as
|
||||||
|
* published by the Free Software Foundation, either version 3 of the
|
||||||
|
* License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Affero General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
use std::env;
|
||||||
|
use std::path::Path;
|
||||||
|
|
||||||
|
use config::{Config, ConfigError, Environment, File};
|
||||||
|
use derive_more::Display;
|
||||||
|
use log::warn;
|
||||||
|
use serde::Deserialize;
|
||||||
|
use serde::Serialize;
|
||||||
|
use url::Url;
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Deserialize)]
|
||||||
|
pub struct Server {
|
||||||
|
pub port: u32,
|
||||||
|
pub domain: String,
|
||||||
|
pub ip: String,
|
||||||
|
pub url_prefix: Option<String>,
|
||||||
|
pub proxy_has_tls: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Server {
|
||||||
|
#[cfg(not(tarpaulin_include))]
|
||||||
|
pub fn get_ip(&self) -> String {
|
||||||
|
format!("{}:{}", self.ip, self.port)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Deserialize)]
|
||||||
|
pub struct Settings {
|
||||||
|
pub debug: bool,
|
||||||
|
pub server: Server,
|
||||||
|
pub source_code: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(not(tarpaulin_include))]
|
||||||
|
impl Settings {
|
||||||
|
pub fn new() -> Result<Self, ConfigError> {
|
||||||
|
let mut s = Config::new();
|
||||||
|
|
||||||
|
const CURRENT_DIR: &str = "./config/default.toml";
|
||||||
|
const ETC: &str = "/etc/lpconductor/config.toml";
|
||||||
|
|
||||||
|
if let Ok(path) = env::var("LPCONDUCTOR_CONFIG") {
|
||||||
|
s.merge(File::with_name(&path))?;
|
||||||
|
} else if Path::new(CURRENT_DIR).exists() {
|
||||||
|
// merging default config from file
|
||||||
|
s.merge(File::with_name(CURRENT_DIR))?;
|
||||||
|
} else if Path::new(ETC).exists() {
|
||||||
|
s.merge(File::with_name(ETC))?;
|
||||||
|
} else {
|
||||||
|
log::warn!("configuration file not found");
|
||||||
|
}
|
||||||
|
|
||||||
|
s.merge(Environment::with_prefix("LPCONDUCTOR").separator("_"))?;
|
||||||
|
|
||||||
|
check_url(&s);
|
||||||
|
|
||||||
|
match env::var("PORT") {
|
||||||
|
Ok(val) => {
|
||||||
|
s.set("server.port", val).unwrap();
|
||||||
|
}
|
||||||
|
Err(e) => warn!("couldn't interpret PORT: {}", e),
|
||||||
|
}
|
||||||
|
|
||||||
|
match s.try_into::<Self>() {
|
||||||
|
Ok(val) => {
|
||||||
|
Ok(val)
|
||||||
|
},
|
||||||
|
Err(e) => Err(ConfigError::Message(format!("\n\nError: {}. If it says missing fields, then please refer to https://github.com/mCaptcha/mcaptcha#configuration to learn more about how mcaptcha reads configuration\n\n", e))),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(not(tarpaulin_include))]
|
||||||
|
fn check_url(s: &Config) {
|
||||||
|
let url = s
|
||||||
|
.get::<String>("source_code")
|
||||||
|
.expect("Couldn't access source_code");
|
||||||
|
|
||||||
|
Url::parse(&url).expect("Please enter a URL for source_code in settings");
|
||||||
|
}
|
Loading…
Reference in a new issue