Open source Nest implementation
use crate::database::Database;
use crate::models::projects;
use crate::models::users::User;
use lazy_static::lazy_static;
use regex::Regex;
use rocket::{
    form::{self, Error, Form},
    response::{Flash, Redirect},
    Route, State,
};
use rocket_dyn_templates::Template;

#[get("/projects/new")]
fn new(user: User) -> Template {
    Template::render(
        "projects/new",
        crate::context! {
            current_user: user,
        },
    )
}

fn validate_project_name<'v>(name: &String) -> form::Result<'v, ()> {
    lazy_static! {
        static ref RE: Regex = Regex::new(r"\A[\w\d]{2,20}\z").unwrap();
    }

    if !RE.is_match(&name) {
        Err(Error::validation(
            "only up to 20 letters or digets are to be used as project name",
        ))?;
    }

    Ok(())
}

#[derive(FromForm)]
struct NewProject {
    #[field(validate = validate_project_name())]
    name: String,
}

#[post("/projects", data = "<project>")]
async fn create(
    db: &State<Database>,
    project: Form<NewProject>,
    user: User,
) -> Result<Flash<Redirect>, Flash<Redirect>> {
    // TODO replace this with user
    match projects::create(&**db, user.id, project.name.clone()).await {
        Ok(_) => Ok(Flash::success(Redirect::to("/"), "Project is created")),
        Err(_e) => Err(Flash::error(
            Redirect::to("/projects/new"),
            "Something went wrong",
        )),
    }
}

// Rank 3 is assigned as project/new and users/new might else collide
#[get("/<org_path>/<proj_path>", rank = 3)]
async fn show_anonymous(
    db: &State<Database>,
    org_path: String,
    proj_path: String,
) -> Result<Template, rocket::http::Status> {
    let p = if let Some(p) = projects::find(&**db, org_path, proj_path).await {
        p
    } else {
        return Err(rocket::http::Status::InternalServerError);
    };

    Ok(Template::render(
        "projects/show",
        crate::context! {
            project: p.clone(),
            organisation: p.organisation(&**db).await.ok(),
        },
    ))
}
#[get("/<org_path>/<proj_path>", rank = 4)]
async fn show(
    db: &State<Database>,
    user: User,
    org_path: String,
    proj_path: String,
) -> Result<Template, rocket::http::Status> {
    let p = if let Some(p) = projects::find(&**db, org_path, proj_path).await {
        p
    } else {
        return Err(rocket::http::Status::InternalServerError);
    };

    Ok(Template::render(
        "projects/show",
        crate::context! {
            current_user: user,
            project: p.clone(),
            organisation: p.organisation(&**db).await.ok(),
        },
    ))
}

use std::vec::Vec;

#[get("/projects/explore")]
async fn explore(
    db: &State<Database>,
    current_user: Option<User>,
) -> Result<Template, rocket::http::Status> {
    let projects = if let Ok(projects) = projects::list(&**db).await {
        projects
    } else {
        return Err(rocket::http::Status::InternalServerError);
    };

    let mut disp = Vec::new();
    for proj in projects.iter() {
        disp.push(projects::DispProject::new(&**db, proj.clone()).await)
    }

    Ok(Template::render(
        "projects/explore",
        crate::context! {
            current_user: current_user,
            projects: disp,
        },
    ))
}

pub fn routes() -> Vec<Route> {
    routes![new, create, show, show_anonymous, explore]
}