commit
36ee100cb2
@ -0,0 +1 @@
|
|||||||
|
/target
|
@ -0,0 +1,24 @@
|
|||||||
|
[package]
|
||||||
|
name = "noted"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2021"
|
||||||
|
|
||||||
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
rocket = { version = "0.5.0-rc.2", features = ["secrets"] }
|
||||||
|
dotenvy = "0.15"
|
||||||
|
bcrypt = "0.13.0"
|
||||||
|
serde = "1.0.144"
|
||||||
|
|
||||||
|
[dependencies.rocket_dyn_templates]
|
||||||
|
version = "0.1.0-rc.2"
|
||||||
|
features = ["tera"]
|
||||||
|
|
||||||
|
[dependencies.rocket_db_pools]
|
||||||
|
version = "0.1.0-rc.2"
|
||||||
|
features = ["sqlx_postgres"]
|
||||||
|
|
||||||
|
[dependencies.sqlx]
|
||||||
|
version = "0.5.13"
|
||||||
|
features = ["macros", "runtime-tokio-rustls"]
|
@ -0,0 +1,2 @@
|
|||||||
|
[default.databases.notes]
|
||||||
|
url = "postgres://notearoni:password@localhost/notearoni"
|
@ -0,0 +1,60 @@
|
|||||||
|
* Add `task` statues:
|
||||||
|
|
||||||
|
```sql
|
||||||
|
CREATE TABLE task (
|
||||||
|
...
|
||||||
|
status INT -- 0 = created, 1 = in-progress, 2 = complete
|
||||||
|
removed BOOL -- decides if an item has been removed (save them just in case)
|
||||||
|
);
|
||||||
|
```
|
||||||
|
|
||||||
|
* Add subtasks/subtasks:
|
||||||
|
|
||||||
|
```sql
|
||||||
|
CREATE TABLE `task (
|
||||||
|
...
|
||||||
|
parent_id INT -- self-referential ID
|
||||||
|
);
|
||||||
|
```
|
||||||
|
|
||||||
|
* Add task deadlines
|
||||||
|
|
||||||
|
```sql
|
||||||
|
CREATE TABLE task (
|
||||||
|
...
|
||||||
|
deadline DATETIME -- add an (optional) datetime as a time for the task to be complete
|
||||||
|
);
|
||||||
|
```
|
||||||
|
|
||||||
|
* Add `/new_list/` route.
|
||||||
|
* Add `/new_item/` route (choose from a dropdown of your lists).
|
||||||
|
* Add `perms` table.
|
||||||
|
|
||||||
|
```sql
|
||||||
|
CREATE TABLE perms (
|
||||||
|
list_id INT, -- foregin key for list
|
||||||
|
user_id INT, -- foregin key for user
|
||||||
|
perms INT -- 0 = none, 1 = read, 2 = write, 4 = admin (can change perms) binary OR results together to find which permissions are granted
|
||||||
|
);
|
||||||
|
```
|
||||||
|
|
||||||
|
* Add a way to remove ALL your tasks. It needs to be in reverse ID order so that sub-tasks are deleted before the parent task.
|
||||||
|
* Create command-line client like the following:
|
||||||
|
|
||||||
|
```shell
|
||||||
|
$ todor show-lists
|
||||||
|
* school
|
||||||
|
* work
|
||||||
|
* family [selected]
|
||||||
|
* social
|
||||||
|
$ todor set-list work
|
||||||
|
$ todor show-lists
|
||||||
|
* school
|
||||||
|
* work [selected]
|
||||||
|
* family
|
||||||
|
* social
|
||||||
|
$ todor list
|
||||||
|
* Email sponsorship decal [In-Progress]
|
||||||
|
* Send email thanking authors of Rocket.rs [Incomplete]
|
||||||
|
* ...
|
||||||
|
```
|
@ -0,0 +1,2 @@
|
|||||||
|
-- This file should undo anything in `up.sql`
|
||||||
|
DROP TABLE users;
|
@ -0,0 +1,8 @@
|
|||||||
|
-- Your SQL goes here
|
||||||
|
CREATE TABLE users (
|
||||||
|
id INTEGER PRIMARY KEY NOT NULL GENERATED ALWAYS AS IDENTITY,
|
||||||
|
uuid VARCHAR(36) NOT NULL DEFAULT gen_random_uuid(),
|
||||||
|
username VARCHAR(16) NOT NULL UNIQUE,
|
||||||
|
password VARCHAR(512) NOT NULL, -- always a hashed value
|
||||||
|
email VARCHAR(32) NOT NULL UNIQUE
|
||||||
|
);
|
@ -0,0 +1,3 @@
|
|||||||
|
-- Add down migration script here
|
||||||
|
DROP TABLE note;
|
||||||
|
DROP TABLE list;
|
@ -0,0 +1,40 @@
|
|||||||
|
CREATE TABLE list (
|
||||||
|
id INT GENERATED ALWAYS AS IDENTITY,
|
||||||
|
owner_id INT NOT NULL,
|
||||||
|
name VARCHAR(255) NOT NULL,
|
||||||
|
PRIMARY KEY(id),
|
||||||
|
CONSTRAINT fk_owner
|
||||||
|
FOREIGN KEY(owner_id)
|
||||||
|
REFERENCES users(id)
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE note (
|
||||||
|
id INT GENERATED ALWAYS AS IDENTITY,
|
||||||
|
list_id INT NOT NULL, -- references a list
|
||||||
|
writer_id INT NOT NULL, -- references a user
|
||||||
|
content VARCHAR(255) NOT NULL, -- about one twitter post worth
|
||||||
|
modified_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), -- the last time this item was modified
|
||||||
|
due_at TIMESTAMPTZ, -- a nullable time at which the task needs to be complete
|
||||||
|
PRIMARY KEY(id),
|
||||||
|
CONSTRAINT fk_writer
|
||||||
|
FOREIGN KEY(writer_id)
|
||||||
|
REFERENCES users(id),
|
||||||
|
CONSTRAINT fk_list
|
||||||
|
FOREIGN KEY(list_id)
|
||||||
|
REFERENCES list(id)
|
||||||
|
);
|
||||||
|
|
||||||
|
-- The following section automatically updates the "modified" column when a record is updated
|
||||||
|
CREATE OR REPLACE FUNCTION trigger_set_timestamp()
|
||||||
|
RETURNS TRIGGER AS $$
|
||||||
|
BEGIN
|
||||||
|
NEW.modified_at = NOW();
|
||||||
|
RETURN NEW;
|
||||||
|
END
|
||||||
|
$$ LANGUAGE plpgsql;
|
||||||
|
|
||||||
|
CREATE TRIGGER set_timestamp
|
||||||
|
BEFORE UPDATE ON note
|
||||||
|
FOR EACH ROW
|
||||||
|
EXECUTE PROCEDURE trigger_set_timestamp();
|
||||||
|
|
@ -0,0 +1,2 @@
|
|||||||
|
-- Add down migration script here
|
||||||
|
DROP TABLE perms;
|
@ -0,0 +1,14 @@
|
|||||||
|
-- Add up migration script here
|
||||||
|
CREATE TABLE perms (
|
||||||
|
id INTEGER GENERATED ALWAYS AS IDENTITY,
|
||||||
|
list_id INTEGER NOT NULL, -- the list affected by the permission
|
||||||
|
user_id INTEGER NOT NULL, -- the user affected by the permission
|
||||||
|
read BOOLEAN NOT NULL, -- can the user read the list
|
||||||
|
write BOOLEAN NOT NULL, -- can the user write to the list
|
||||||
|
CONSTRAINT fk_list
|
||||||
|
FOREIGN KEY(list_id)
|
||||||
|
REFERENCES list(id),
|
||||||
|
CONSTRAINT fk_user
|
||||||
|
FOREIGN KEY(user_id)
|
||||||
|
REFERENCES users(id)
|
||||||
|
);
|
@ -0,0 +1,190 @@
|
|||||||
|
use serde;
|
||||||
|
use rocket::{
|
||||||
|
self,
|
||||||
|
http::Status,
|
||||||
|
serde::Serialize,
|
||||||
|
request::{
|
||||||
|
self,
|
||||||
|
FromRequest,
|
||||||
|
},
|
||||||
|
Request,
|
||||||
|
outcome::{
|
||||||
|
try_outcome,
|
||||||
|
Outcome::{
|
||||||
|
Success,
|
||||||
|
Failure,
|
||||||
|
Forward,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
use rocket_db_pools::{
|
||||||
|
Database,
|
||||||
|
Connection,
|
||||||
|
};
|
||||||
|
use sqlx::{
|
||||||
|
pool::PoolConnection,
|
||||||
|
Postgres,
|
||||||
|
};
|
||||||
|
|
||||||
|
#[derive(Database)]
|
||||||
|
#[database("notes")]
|
||||||
|
pub struct Notes(sqlx::PgPool);
|
||||||
|
|
||||||
|
pub type Result<T, E = rocket::response::Debug<sqlx::Error>> = std::result::Result<T, E>;
|
||||||
|
|
||||||
|
#[derive(Serialize)]
|
||||||
|
pub struct Note {
|
||||||
|
pub id: i32,
|
||||||
|
pub content: String,
|
||||||
|
pub list_id: i32,
|
||||||
|
}
|
||||||
|
impl ToString for Note {
|
||||||
|
fn to_string(&self) -> String {
|
||||||
|
format!("Note: {}", self.content)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize)]
|
||||||
|
pub struct User {
|
||||||
|
pub id: i32,
|
||||||
|
pub uuid: String,
|
||||||
|
pub username: String,
|
||||||
|
pub password: String,
|
||||||
|
pub email: String,
|
||||||
|
}
|
||||||
|
impl ToString for User {
|
||||||
|
fn to_string(&self) -> String {
|
||||||
|
format!("User: {}", self.username)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub enum UserError {
|
||||||
|
NoCookie,
|
||||||
|
InvalidCookie,
|
||||||
|
WrappedDBError(Option<rocket_db_pools::Error<sqlx::Error>>),
|
||||||
|
DBError(sqlx::Error),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[rocket::async_trait]
|
||||||
|
impl<'a> FromRequest<'a> for User {
|
||||||
|
type Error = UserError;
|
||||||
|
|
||||||
|
async fn from_request(req: &'a Request<'_>) -> request::Outcome<Self, Self::Error> {
|
||||||
|
let cookies = req.cookies();
|
||||||
|
let mut db = match req.guard::<Connection<Notes>>().await {
|
||||||
|
Success(dbv) => dbv,
|
||||||
|
Failure((http_err, err)) => return Failure((http_err, UserError::WrappedDBError(err))),
|
||||||
|
Forward(next) => return Forward(next),
|
||||||
|
};
|
||||||
|
let user_uuid = match cookies.get_private("user_uuid") {
|
||||||
|
Some(crumb) => crumb,
|
||||||
|
None => return Forward(()), // this will redirect to the login page
|
||||||
|
};
|
||||||
|
match sqlx::query!("SELECT * FROM users WHERE uuid = $1", user_uuid.value())
|
||||||
|
.fetch_optional(&mut *db)
|
||||||
|
.await {
|
||||||
|
Ok(Some(row)) => {
|
||||||
|
Success(User {
|
||||||
|
id: row.id,
|
||||||
|
uuid: row.uuid,
|
||||||
|
username: row.username,
|
||||||
|
password: row.password,
|
||||||
|
email: row.email,
|
||||||
|
})
|
||||||
|
},
|
||||||
|
Ok(None) => Forward(()), // this will redirect to the login page
|
||||||
|
Err(e) => Failure((Status::InternalServerError, UserError::DBError(e))),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize)]
|
||||||
|
pub struct List {
|
||||||
|
pub id: i32,
|
||||||
|
pub name: String,
|
||||||
|
pub owner_id: i32,
|
||||||
|
}
|
||||||
|
impl ToString for List {
|
||||||
|
fn to_string(&self) -> String {
|
||||||
|
format!("{}: {} (owned by {})", self.id, self.name, self.owner_id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn get_list_notes(db: &mut PoolConnection<Postgres>, lid: i32) -> Result<Vec<Note>> {
|
||||||
|
Ok(sqlx::query!("
|
||||||
|
SELECT id,content,list_id
|
||||||
|
FROM note
|
||||||
|
WHERE list_id = $1", lid)
|
||||||
|
.fetch_all(db)
|
||||||
|
.await?
|
||||||
|
.iter()
|
||||||
|
.map(|r| Note {
|
||||||
|
id: r.id,
|
||||||
|
content: r.content.clone(),
|
||||||
|
list_id: r.list_id
|
||||||
|
})
|
||||||
|
.collect())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn get_user_lists(db: &mut PoolConnection<Postgres>, uid: i32) -> Result<Vec<List>> {
|
||||||
|
Ok(sqlx::query!("
|
||||||
|
SELECT id,name,owner_id
|
||||||
|
FROM list
|
||||||
|
WHERE owner_id = $1", uid)
|
||||||
|
.fetch_all(db)
|
||||||
|
.await?
|
||||||
|
.iter()
|
||||||
|
.map(|r| List {
|
||||||
|
name: r.name.clone(),
|
||||||
|
id: r.id,
|
||||||
|
owner_id: r.owner_id
|
||||||
|
})
|
||||||
|
.collect())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn get_user_lists_from_perms(db: &mut PoolConnection<Postgres>, uid: i32) -> Result<Vec<List>> {
|
||||||
|
Ok(sqlx::query!("
|
||||||
|
SELECT list.id,list.name,list.owner_id
|
||||||
|
FROM list
|
||||||
|
JOIN perms ON list.id=perms.list_id
|
||||||
|
WHERE perms.user_id = $1
|
||||||
|
AND perms.read = TRUE", uid)
|
||||||
|
.fetch_all(db)
|
||||||
|
.await?
|
||||||
|
.iter()
|
||||||
|
.map(|r| List {
|
||||||
|
name: r.name.clone(),
|
||||||
|
id: r.id,
|
||||||
|
owner_id: r.owner_id
|
||||||
|
})
|
||||||
|
.collect())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn get_user_from_email(db: &mut PoolConnection<Postgres>, email: String) -> Result<Option<User>> {
|
||||||
|
match sqlx::query!(
|
||||||
|
"SELECT * FROM users WHERE email = $1",
|
||||||
|
email
|
||||||
|
).fetch_optional(&mut *db)
|
||||||
|
.await? {
|
||||||
|
Some(row) => Ok(Some(User {
|
||||||
|
id: row.id,
|
||||||
|
uuid: row.uuid,
|
||||||
|
username: row.username,
|
||||||
|
password: row.password,
|
||||||
|
email: row.email,
|
||||||
|
})),
|
||||||
|
None => Ok(None),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn add_permission(db: &mut PoolConnection<Postgres>, user_id: i32, list_id: i32, perm: i32) -> Result<()> {
|
||||||
|
sqlx::query!("
|
||||||
|
INSERT INTO perms (user_id, list_id, read, write) VALUES ($1, $2, $3, $4)",
|
||||||
|
user_id,
|
||||||
|
list_id,
|
||||||
|
perm >= 1,
|
||||||
|
perm >= 2
|
||||||
|
).execute(db)
|
||||||
|
.await?;
|
||||||
|
Ok(())
|
||||||
|
}
|
@ -0,0 +1,27 @@
|
|||||||
|
use rocket::form::Form;
|
||||||
|
|
||||||
|
#[derive(FromForm)]
|
||||||
|
pub struct NewUserForm<'a> {
|
||||||
|
pub username: &'a str,
|
||||||
|
pub password: &'a str,
|
||||||
|
pub email: &'a str,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(FromForm)]
|
||||||
|
pub struct NewListForm<'a> {
|
||||||
|
pub name: &'a str,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(FromForm)]
|
||||||
|
pub struct UserLoginForm<'a> {
|
||||||
|
pub username: &'a str,
|
||||||
|
pub password: &'a str,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(FromForm)]
|
||||||
|
pub struct PermsForm<'a> {
|
||||||
|
pub list_id: i32,
|
||||||
|
pub user_email: &'a str,
|
||||||
|
pub perm: i32,
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,208 @@
|
|||||||
|
pub mod forms;
|
||||||
|
pub mod db;
|
||||||
|
|
||||||
|
use bcrypt::{
|
||||||
|
hash,
|
||||||
|
verify,
|
||||||
|
DEFAULT_COST
|
||||||
|
};
|
||||||
|
use rocket::{
|
||||||
|
form::Form,
|
||||||
|
response::Redirect,
|
||||||
|
State
|
||||||
|
};
|
||||||
|
use forms::{
|
||||||
|
UserLoginForm,
|
||||||
|
NewUserForm,
|
||||||
|
NewListForm,
|
||||||
|
PermsForm,
|
||||||
|
};
|
||||||
|
use db::{
|
||||||
|
List,
|
||||||
|
Notes,
|
||||||
|
Result,
|
||||||
|
add_permission,
|
||||||
|
get_user_from_email,
|
||||||
|
get_user_lists,
|
||||||
|
get_user_lists_from_perms,
|
||||||
|
User,
|
||||||
|
};
|
||||||
|
|
||||||
|
#[macro_use] extern crate rocket;
|
||||||
|
use rocket_dyn_templates::{Template, context};
|
||||||
|
use rocket_db_pools::{
|
||||||
|
Database,
|
||||||
|
Connection,
|
||||||
|
sqlx::{
|
||||||
|
self,
|
||||||
|
Row,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
use rocket::{
|
||||||
|
serde::{
|
||||||
|
Serialize
|
||||||
|
},
|
||||||
|
http::{
|
||||||
|
Cookie,
|
||||||
|
CookieJar,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
#[get("/hello/<name>/<age>")]
|
||||||
|
async fn hello(name: &str, age: u8) -> String {
|
||||||
|
format!("Hello, {} year old named {}!", age, name)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[get("/")]
|
||||||
|
async fn home() -> Template {
|
||||||
|
Template::render("index", context!{})
|
||||||
|
}
|
||||||
|
#[get("/create")]
|
||||||
|
async fn create() -> Template {
|
||||||
|
Template::render("new", context!{})
|
||||||
|
}
|
||||||
|
|
||||||
|
#[post("/new", data="<user>")]
|
||||||
|
async fn new_user(user: Form<NewUserForm<'_>>, mut db: Connection<Notes>) -> Result<String> {
|
||||||
|
let check_exists = sqlx::query!("SELECT id FROM users WHERE username = $1 OR email = $2", user.username, user.email)
|
||||||
|
.fetch_optional(&mut *db)
|
||||||
|
.await?;
|
||||||
|
if check_exists.is_some() {
|
||||||
|
return Ok(format!("This account already exists!"));
|
||||||
|
}
|
||||||
|
let hashed_pass = match hash(user.password, DEFAULT_COST) {
|
||||||
|
Ok(pass) => pass,
|
||||||
|
Err(e) => panic!("Could not hash a password! {}", e)
|
||||||
|
};
|
||||||
|
sqlx::query!("INSERT INTO users (username,password,email) VALUES ($1, $2, $3)", user.username, hashed_pass, user.email)
|
||||||
|
.execute(&mut *db)
|
||||||
|
.await?;
|
||||||
|
Ok(format!("Thanks, {}, for creating an account on our service.", user.username))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[post("/login", data="<user>")]
|
||||||
|
async fn login(user: Form<UserLoginForm<'_>>, mut db: Connection<Notes>, cookies: &CookieJar<'_>) -> Result<String> {
|
||||||
|
match cookies.get_private("user_uuid") {
|
||||||
|
Some(crumb) => println!("UUID: {:?}", crumb.value()),
|
||||||
|
_ => {}
|
||||||
|
};
|
||||||
|
let result = sqlx::query!("SELECT * FROM users WHERE username=$1", user.username)
|
||||||
|
.fetch_optional(&mut *db)
|
||||||
|
.await?;
|
||||||
|
let success = match result {
|
||||||
|
Some(ref db_user) => verify(&user.password, &db_user.password).unwrap(),
|
||||||
|
_ => false,
|
||||||
|
};
|
||||||
|
if success {
|
||||||
|
cookies.add_private(Cookie::new("user_uuid", result.unwrap().uuid));
|
||||||
|
Ok(format!("Yay! Thanks for logging in to our service!"))
|
||||||
|
} else {
|
||||||
|
Ok(format!("Incorrect login!"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[post("/list", data="<new_list>")]
|
||||||
|
async fn new_list(mut db: Connection<Notes>, user: User, new_list: Form<NewListForm<'_>>) -> Result<String> {
|
||||||
|
sqlx::query!("INSERT INTO list (owner_id, name) VALUES ($1, $2)", user.id, new_list.name)
|
||||||
|
.execute(&mut *db)
|
||||||
|
.await?;
|
||||||
|
Ok(format!("You added a new list: {}", new_list.name))
|
||||||
|
}
|
||||||
|
#[post("/list", data="<new_list>", rank=2)]
|
||||||
|
async fn new_list_not_logged_in(new_list: Form<NewListForm<'_>>) -> Redirect {
|
||||||
|
Redirect::to(uri!(home))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[get("/lists")]
|
||||||
|
async fn show_list(mut db: Connection<Notes>, user: User) -> Result<Template> {
|
||||||
|
Ok(Template::render("show_lists", context!{
|
||||||
|
lists: get_user_lists(&mut db, user.id).await?,
|
||||||
|
perm_lists: get_user_lists_from_perms(&mut db, user.id).await?
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
#[get("/lists", rank=2)]
|
||||||
|
async fn show_list_not_logged_in() -> Redirect {
|
||||||
|
Redirect::to(uri!(home))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[get("/new/list")]
|
||||||
|
async fn new_list_form(_user: User) -> Template {
|
||||||
|
Template::render("new_list", context!{})
|
||||||
|
}
|
||||||
|
#[get("/new/list", rank=2)]
|
||||||
|
async fn new_list_form_not_logged_in() -> Redirect {
|
||||||
|
Redirect::to(uri!(home))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[post("/perms", data="<perms>")]
|
||||||
|
async fn new_perms(perms: Form<PermsForm<'_>>, user: User, mut db: Connection<Notes>) -> Result<String> {
|
||||||
|
let perm_user = match get_user_from_email(&mut db, perms.user_email.to_string()).await? {
|
||||||
|
Some(u) => {
|
||||||
|
if u.id == user.id { return Ok(format!("You may not grant yourself a permission.")); } else { u }
|
||||||
|
},
|
||||||
|
None => return Ok(format!("Permission is added.")),
|
||||||
|
};
|
||||||
|
match add_permission(&mut db, perm_user.id, perms.list_id, perms.perm).await {
|
||||||
|
Err(_) => Ok(format!("There was an error adding this permission.")),
|
||||||
|
Ok(_) => Ok(format!("Permission is added.")),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#[post("/perms", data="<perms>", rank=2)]
|
||||||
|
async fn new_perms_not_logged_in(perms: Form<PermsForm<'_>>) -> Redirect {
|
||||||
|
Redirect::to(uri!(home))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[get("/new/perms")]
|
||||||
|
async fn add_perms_form(mut db: Connection<Notes>, user: User) -> Result<Template> {
|
||||||
|
Ok(Template::render("new_perms", context!{
|
||||||
|
lists: get_user_lists(&mut db, user.id).await?
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
#[get("/new/perms", rank=2)]
|
||||||
|
async fn add_perms_form_not_logged_in() -> Redirect {
|
||||||
|
Redirect::to(uri!(home))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[get("/logout")]
|
||||||
|
async fn logout(mut db: Connection<Notes>, cookies: &CookieJar<'_>) -> Result<String> {
|
||||||
|
let uuid = cookies.get_private("user_uuid");
|
||||||
|
if uuid.is_none() {
|
||||||
|
return Ok(format!("You aren't logged in, lol!"));
|
||||||
|
}
|
||||||
|
cookies.remove_private(Cookie::named("user_uuid"));
|
||||||
|
let actual_uuid = uuid.unwrap();
|
||||||
|
let result = sqlx::query!("SELECT * FROM users WHERE uuid = $1", actual_uuid.value())
|
||||||
|
.fetch_optional(&mut *db)
|
||||||
|
.await?;
|
||||||
|
if let Some(user) = result {
|
||||||
|
Ok(format!("User '{}', logged out.", user.username))
|
||||||
|
} else {
|
||||||
|
Ok(format!("We don't know what happened..."))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[launch]
|
||||||
|
fn rocket() -> _ {
|
||||||
|
rocket::build()
|
||||||
|
.attach(Template::fairing())
|
||||||
|
.attach(Notes::init())
|
||||||
|
.mount("/", routes![
|
||||||
|
hello,
|
||||||
|
home,
|
||||||
|
login,
|
||||||
|
new_user,
|
||||||
|
create,
|
||||||
|
logout
|
||||||
|
])
|
||||||
|
.mount("/new", routes![
|
||||||
|
new_list, new_list_not_logged_in,
|
||||||
|
new_perms, new_perms_not_logged_in,
|
||||||
|
])
|
||||||
|
.mount("/show", routes![
|
||||||
|
show_list, show_list_not_logged_in
|
||||||
|
])
|
||||||
|
.mount("/forms", routes![
|
||||||
|
add_perms_form, add_perms_form_not_logged_in,
|
||||||
|
new_list_form, new_list_form_not_logged_in
|
||||||
|
])
|
||||||
|
}
|
@ -0,0 +1,16 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<header>
|
||||||
|
{% include "includes/header" %}
|
||||||
|
</header>
|
||||||
|
<main>
|
||||||
|
{% block content %}{% endblock %}
|
||||||
|
</main>
|
||||||
|
<footer>
|
||||||
|
{% include "includes/footer" %}
|
||||||
|
</footer>
|
||||||
|
</body>
|
||||||
|
</html>
|
@ -0,0 +1,5 @@
|
|||||||
|
{% extends "base" %}
|
||||||
|
{% block content %}
|
||||||
|
<h1>Error</h1>
|
||||||
|
<p>{{ error }}</p>
|
||||||
|
{% endblock %}
|
@ -0,0 +1 @@
|
|||||||
|
© Tait Hoyem, 2022
|
@ -0,0 +1,8 @@
|
|||||||
|
<nav>
|
||||||
|
<a href="/">Home</a>
|
||||||
|
<a href="/create">New User</a>
|
||||||
|
<a href="/forms/new/list">New List</a>
|
||||||
|
<a href="/show/lists">Show Lists</a>
|
||||||
|
<a href="/forms/new/perms">Add Permissions</a>
|
||||||
|
<a href="/logout/">Log out</a>
|
||||||
|
</nav>
|
@ -0,0 +1,10 @@
|
|||||||
|
{% extends "base" %}
|
||||||
|
{% block content %}
|
||||||
|
<form method="POST" action="/login/">
|
||||||
|
<label for="username">Username</label>
|
||||||
|
<input type="text" name="username" id="username">
|
||||||
|
<label for="password">Password</label>
|
||||||
|
<input type="password" name="password" id="password">
|
||||||
|
<input type="submit" value="Login">
|
||||||
|
</form>
|
||||||
|
{% endblock %}
|
@ -0,0 +1,12 @@
|
|||||||
|
{% extends "base" %}
|
||||||
|
{% block content %}
|
||||||
|
<form method="POST" action="/new/">
|
||||||
|
<label for="username">Username</label>
|
||||||
|
<input type="text" id="username" name="username">
|
||||||
|
<label for="password">Password</label>
|
||||||
|
<input type="text" id="password" name="password">
|
||||||
|
<label for="email">Email</label>
|
||||||
|
<input type="email" id="email" name="email">
|
||||||
|
<input type="submit" value="Create Account">
|
||||||
|
</form>
|
||||||
|
{% endblock %}
|
@ -0,0 +1,8 @@
|
|||||||
|
{% extends "base" %}
|
||||||
|
{% block content %}
|
||||||
|
<form method="POST" action="/new/list">
|
||||||
|
<label for="name">Name</label>
|
||||||
|
<input type="text" name="name" id="name">
|
||||||
|
<input type="submit" value="Create New List">
|
||||||
|
</form>
|
||||||
|
{% endblock %}
|
@ -0,0 +1,19 @@
|
|||||||
|
{% extends "base" %}
|
||||||
|
{% block content %}
|
||||||
|
<form method="POST" action="/new/perms">
|
||||||
|
<label for="list_id">List to share</label>
|
||||||
|
<select id="list_id" name="list_id">
|
||||||
|
{% for list in lists %}
|
||||||
|
<option value="{{ list.id }}">{{ list.name }}</option>
|
||||||
|
{% endfor %}
|
||||||
|
</select>
|
||||||
|
<label for="email">Email of user you want to add these perms to</label>
|
||||||
|
<input type="email" name="user_email" id="email">
|
||||||
|
<label for="perms">Permissions</label>
|
||||||
|
<select id="perms" name="perm">
|
||||||
|
<option value="1">Read</option>
|
||||||
|
<option value="2">Write</option>
|
||||||
|
</select>
|
||||||
|
<input type="submit" value="Add Permission">
|
||||||
|
</form>
|
||||||
|
{% endblock %}
|
@ -0,0 +1,11 @@
|
|||||||
|
{% extends "base" %}
|
||||||
|
{% block content %}
|
||||||
|
<ol>
|
||||||
|
{% for list in lists %}
|
||||||
|
<li><a href="/show/list/{{ list.id }}">{{ list.name }}</a></li>
|
||||||
|
{% endfor %}
|
||||||
|
{% for list in perm_lists %}
|
||||||
|
<li><a href="/show/list/{{ list.id }}">{{ list.name }}</a> (shared from {{ list.owner_id }})</li>
|
||||||
|
{% endfor %}
|
||||||
|
</ol>
|
||||||
|
{% endblock %}
|
Loading…
Reference in new issue