initial commit of Hugotator POGGERS
This commit is contained in:
commit
a99dfefeb3
8 changed files with 2846 additions and 0 deletions
1
.env
Normal file
1
.env
Normal file
|
@ -0,0 +1 @@
|
||||||
|
DATABASE_URL=sqlite://local.database
|
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
/target
|
||||||
|
tmp/*
|
2579
Cargo.lock
generated
Normal file
2579
Cargo.lock
generated
Normal file
File diff suppressed because it is too large
Load diff
17
Cargo.toml
Normal file
17
Cargo.toml
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
|
||||||
|
[package]
|
||||||
|
name = "backend"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2021"
|
||||||
|
|
||||||
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
actix-web = "^4.2"
|
||||||
|
sea-orm = { version = "^0", features = [ "sqlx-sqlite", "runtime-actix-native-tls", "macros" ] }
|
||||||
|
serde = { version = "^1", features = [ "derive" ] }
|
||||||
|
toml = { version = "^0.5" }
|
||||||
|
chrono = { version = "^0.4", features = [ "unstable-locales" ] }
|
||||||
|
git2 = { version = "0.15" }
|
||||||
|
log = { version = "^0.4" }
|
||||||
|
|
110
src/init_site.rs
Normal file
110
src/init_site.rs
Normal file
|
@ -0,0 +1,110 @@
|
||||||
|
use git2::{Repository, Error as Git2Error, Remote};
|
||||||
|
use git2::string_array::StringArray;
|
||||||
|
use super::{SiteConfig, Site};
|
||||||
|
use std::path::Path;
|
||||||
|
|
||||||
|
use crate::utils::{unwrap_opt_or_return, unwrap_or_return};
|
||||||
|
|
||||||
|
use log::error;
|
||||||
|
use log::info;
|
||||||
|
use log::warn;
|
||||||
|
|
||||||
|
const REPOSITORIES_CONTAINER_PATH: &str = "./tmp/repositories";
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
struct DetailledRemote {
|
||||||
|
slug: String, // expected to be origin
|
||||||
|
url: String
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub enum DetailledRemotesErr {
|
||||||
|
GitError(Git2Error),
|
||||||
|
CannotGetRemoteUrl
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_detailled_remotes(repo_handle: &Repository) -> Result<Vec<DetailledRemote>, DetailledRemotesErr> {
|
||||||
|
let raw_remotes: StringArray = match repo_handle.remotes() {
|
||||||
|
Ok(res) => res,
|
||||||
|
Err(err) => {
|
||||||
|
return Err(DetailledRemotesErr::GitError(err));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut detailled_remotes: Vec<DetailledRemote> = vec![];
|
||||||
|
|
||||||
|
for raw_remote in raw_remotes.iter().flatten() {
|
||||||
|
let slug = raw_remote.to_string();
|
||||||
|
let remote: Remote = unwrap_or_return!(
|
||||||
|
repo_handle.find_remote(&slug),
|
||||||
|
|err| DetailledRemotesErr::GitError(err)
|
||||||
|
);
|
||||||
|
let url = unwrap_opt_or_return!(remote.url(), DetailledRemotesErr::CannotGetRemoteUrl).to_string();
|
||||||
|
|
||||||
|
detailled_remotes.push(DetailledRemote {
|
||||||
|
slug,
|
||||||
|
url
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(detailled_remotes)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub enum InitSiteErr {
|
||||||
|
RepositoryCloneErr(Git2Error),
|
||||||
|
ExistingRepositoryInvalid(Git2Error),
|
||||||
|
ExistingRepositoryCannotGetRemotes(DetailledRemotesErr),
|
||||||
|
ExistingRepositoryInvalidRemoteLayout,
|
||||||
|
ExistingRepositoryInvalidMainRemoteUrl
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn init_site(site_conf: SiteConfig) -> Result<Site, InitSiteErr> {
|
||||||
|
let remote_url = &site_conf.git_remote_url;
|
||||||
|
|
||||||
|
// check if the path exists
|
||||||
|
// check if the path contain an actual repository refering to the same remote url
|
||||||
|
// clone the repo
|
||||||
|
// check if the cloned repo contain an actual hugo website (config.toml must be presents)
|
||||||
|
// try to find the binding in the `content` directory, check that the stru
|
||||||
|
|
||||||
|
let clone_destination: String = format!("{REPOSITORIES_CONTAINER_PATH}/{0}", site_conf.slug);
|
||||||
|
let repo = if !Path::new(&clone_destination).exists() {
|
||||||
|
match Repository::clone(&remote_url, clone_destination) {
|
||||||
|
Ok(repo) => repo,
|
||||||
|
Err(e) => {
|
||||||
|
return Err(InitSiteErr::RepositoryCloneErr(e))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
match Repository::open(clone_destination) {
|
||||||
|
Ok(repo) => {
|
||||||
|
let remotes_details =
|
||||||
|
unwrap_or_return!(get_detailled_remotes(&repo), |e| InitSiteErr::ExistingRepositoryCannotGetRemotes(e));
|
||||||
|
|
||||||
|
// make sure that the origin remote is present and match the configured remote url
|
||||||
|
match remotes_details.iter().find(|r| r.slug == "origin") {
|
||||||
|
None => {
|
||||||
|
return Err(InitSiteErr::ExistingRepositoryInvalidRemoteLayout)
|
||||||
|
},
|
||||||
|
Some(DetailledRemote { url, .. }) if url != remote_url => {
|
||||||
|
dbg!(url);
|
||||||
|
dbg!(remote_url);
|
||||||
|
return Err(InitSiteErr::ExistingRepositoryInvalidMainRemoteUrl);
|
||||||
|
}
|
||||||
|
_ => ()
|
||||||
|
}
|
||||||
|
|
||||||
|
repo
|
||||||
|
},
|
||||||
|
Err(e) => {
|
||||||
|
return Err(InitSiteErr::ExistingRepositoryInvalid(e))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
dbg!(&repo.state());
|
||||||
|
Ok(Site {
|
||||||
|
config: site_conf
|
||||||
|
})
|
||||||
|
}
|
110
src/main.rs
Normal file
110
src/main.rs
Normal file
|
@ -0,0 +1,110 @@
|
||||||
|
mod init_site;
|
||||||
|
mod utils;
|
||||||
|
|
||||||
|
use actix_web::{get, post, web, App, HttpResponse, HttpServer, Responder};
|
||||||
|
use chrono::prelude::{DateTime, Utc};
|
||||||
|
use serde::Deserialize;
|
||||||
|
use toml;
|
||||||
|
use std::fs;
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
struct Post {
|
||||||
|
id: u64,
|
||||||
|
title: String,
|
||||||
|
description: String,
|
||||||
|
content: String,
|
||||||
|
created_at: DateTime<Utc>
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#[get("/")]
|
||||||
|
async fn hello() -> impl Responder {
|
||||||
|
HttpResponse::Ok().body("Ignition sequence started")
|
||||||
|
}
|
||||||
|
|
||||||
|
#[get("/posts")]
|
||||||
|
async fn get_posts() -> impl Responder {
|
||||||
|
HttpResponse::Ok().body("test: get posts")
|
||||||
|
}
|
||||||
|
|
||||||
|
#[post("/echo")]
|
||||||
|
async fn echo(req_body: String) -> impl Responder {
|
||||||
|
HttpResponse::Ok().body(req_body)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
struct AppState {
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Default, Clone, Deserialize, Debug)]
|
||||||
|
struct ServerConfig {
|
||||||
|
host: Option<String>,
|
||||||
|
port: Option<u16>
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#[derive(Clone, Deserialize, Debug)]
|
||||||
|
enum PostingKind {
|
||||||
|
/// a micro-bloging kind of post (less than 80 words)
|
||||||
|
Micro,
|
||||||
|
|
||||||
|
/// a full article (more than 80 words)
|
||||||
|
Article,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A hugo directory under `content`
|
||||||
|
#[derive(Clone, Deserialize, Debug)]
|
||||||
|
struct SiteContentBindingConfig {
|
||||||
|
slug: String,
|
||||||
|
posting_kind: PostingKind
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Deserialize, Debug)]
|
||||||
|
pub struct SiteConfig {
|
||||||
|
slug: String, // for example "werobot_blog"
|
||||||
|
git_remote_url: String,
|
||||||
|
content_path: String,
|
||||||
|
content_bindings: Vec<SiteContentBindingConfig>
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct Site {
|
||||||
|
config: SiteConfig
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Deserialize, Debug)]
|
||||||
|
struct Config {
|
||||||
|
server: Option<ServerConfig>,
|
||||||
|
sites: Vec<SiteConfig>,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#[actix_web::main]
|
||||||
|
async fn main() -> std::io::Result<()> {
|
||||||
|
let config: Config = toml::from_str(
|
||||||
|
&fs::read_to_string("./tmp/config.example.toml")?
|
||||||
|
)?;
|
||||||
|
dbg!(&config);
|
||||||
|
|
||||||
|
// initialize all the sites
|
||||||
|
for site_conf in config.sites {
|
||||||
|
println!("Initializing site {}...", site_conf.slug);
|
||||||
|
let site_res = init_site::init_site(site_conf);
|
||||||
|
dbg!(site_res);
|
||||||
|
}
|
||||||
|
|
||||||
|
let bind_config = (
|
||||||
|
config.server.as_ref().and_then(|sc| sc.host.clone()).unwrap_or_else(|| "127.0.0.1".to_string()),
|
||||||
|
config.server.as_ref().and_then(|sc| sc.port).unwrap_or(6968),
|
||||||
|
);
|
||||||
|
HttpServer::new(|| {
|
||||||
|
App::new()
|
||||||
|
.service(hello)
|
||||||
|
.service(echo)
|
||||||
|
.service(get_posts)
|
||||||
|
})
|
||||||
|
.bind(bind_config)?
|
||||||
|
.run()
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
|
3
src/scan.rs
Normal file
3
src/scan.rs
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
pub fn scan_directory() {
|
||||||
|
|
||||||
|
}
|
24
src/utils.rs
Normal file
24
src/utils.rs
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
macro_rules! unwrap_or_return {
|
||||||
|
( $to_match: expr, $err_func: expr ) => {
|
||||||
|
match $to_match {
|
||||||
|
Ok(res) => res,
|
||||||
|
Err(err) => {
|
||||||
|
return Err($err_func(err))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub(crate) use unwrap_or_return;
|
||||||
|
|
||||||
|
|
||||||
|
macro_rules! unwrap_opt_or_return {
|
||||||
|
( $e: expr, $err: expr ) => {
|
||||||
|
match $e {
|
||||||
|
Some(res) => res,
|
||||||
|
None => {
|
||||||
|
return Err($err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub(crate) use unwrap_opt_or_return;
|
Loading…
Reference in a new issue