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