TWIZ7QV4GCTQK743IKZSOJCIAEX62GZHFIYGOIFCFGIBOGPSY2WAC {% extends "base" %}{% block body %}<div style="width:30%;" class="container-fluid"><form action="/projects" method="post"><div class="mb-3 row"><label for="user_name" class="col-sm-2 col-form-label">Name</label><div class="col-sm-10"><input type="text" class="form-control" name="name" required></div></div><button class="btn btn-primary" type="submit">Create!</button></form></div>{% endblock body %}
use crate::database::Database;use crate::models::projects::Project;use lazy_static::lazy_static;use regex::Regex;use rocket::{form::{self, Context, Error, Form},response::{Flash, Redirect},Route, State,};use rocket_dyn_templates::Template;#[get("/new")]fn new() -> Template {Template::render("projects/new", &Context::default())}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("/", data = "<project>")]async fn create(db: &State<Database>,project: Form<NewProject>,) -> Result<Flash<Redirect>, Flash<Redirect>> {let proj = Project {id: -1,owner_id: 1, // TODO replace this with username: project.name.clone(),};match proj.create(db).await {Ok(_) => Ok(Flash::success(Redirect::to("/"), "Project is created")),Err(_e) => Err(Flash::error(Redirect::to("/projects/new"),"Something went wrong",)),}}pub fn routes() -> Vec<Route> {routes![new, create]}
use crate::database::Database;use rocket::serde::{Deserialize, Serialize};use rocket::State;use sqlx::{query, query_as};#[derive(Debug, Clone, Deserialize, Serialize)]#[serde(crate = "rocket::serde")]pub struct User {pub id: i32,pub name: String,pub email: String,#[serde(skip_deserializing)]pub password: String,}impl User {pub async fn create(&self, db: &State<Database>) -> Result<i32, sqlx::Error> {let result = query!("INSERT INTO users (name, email, password) VALUES ($1, $2, crypt($3, gen_salt('bf'))) RETURNING id",&self.name,&self.email.to_lowercase(),&self.password).fetch_one(&**db).await?;// TODO when I figure out Rust, update self with this idOk(result.id)}/// Validates a user and password combination, returns a User struct when/// valid.pub async fn authenticate(db: &State<Database>,name: String,password: String,) -> Result<Option<User>, sqlx::Error> {let result = query_as!(User,"SELECT * FROM users WHERE name = $1 AND password = crypt($2, password)",name,password).fetch_optional(&**db).await?;Ok(result)}}
use crate::database::Database;use rocket::serde::{Deserialize, Serialize};use rocket::State;use sqlx::query;#[derive(Debug, Clone, Deserialize, Serialize, sqlx::FromRow)]#[serde(crate = "rocket::serde")]pub struct Project {pub id: i32,pub owner_id: i32,pub name: String,}impl Project {pub async fn create(&self, db: &State<Database>) -> Result<i32, sqlx::Error> {let result = query!("INSERT INTO projects (owner_id, name) VALUES ($1, $2) RETURNING id",&self.owner_id,&self.name.to_lowercase(),).fetch_one(&**db).await?;Ok(result.id)}}
-- Add up migration script hereCREATE TABLE IF NOT EXISTS projects (id SERIAL PRIMARY KEY,owner_id integer REFERENCES users,name VARCHAR(64) NOT NULL UNIQUE);CREATE UNIQUE INDEX unique_project_name ON projects(name);
-- Add down migration script hereDROP TABLE IF EXISTS projects;