basics of the account page

This commit is contained in:
Artemis 2025-01-25 17:19:37 +01:00
parent c40f8aed4a
commit 1a152e7acb
11 changed files with 143 additions and 15 deletions

View file

@ -23,9 +23,7 @@ a profile is
- privacy policy and GDPR notice
- p2: saving register form as it gets filled / re-display it with partial values
- account
- basic username / password thingy
- p2: optional email for forgotten password i guess
- "my registered tags" page
- ability to delete a tag (remove all data except the ID to lock the ID)
- ability to delete the whole account (incl. all registered tags)
- deleting a tag keeping the account allows to re-use the tag later on. no one else can use it still

View file

@ -130,6 +130,7 @@ main {
header {
display: flex;
justify-content: space-between;
align-items: baseline;
}
section {
@ -198,6 +199,11 @@ div.dual-fields {
margin-left: 1em;
}
p.subnav {
display: flex;
gap: 10pt;
}
p.heading {
margin-top: 0;
}
@ -295,6 +301,28 @@ input#ident {
color: var(--clr-primary-a50);
}
.profile>div.header {
padding-bottom: .5em;
}
.profile .subnav {
margin-top: .5em;
font-size: .9em;
}
.profile .subnav a:not(:first-of-type) {
margin-left: 4pt;
}
.profile:not(:first-of-type) {
margin-top: 2em;
}
.label {
text-transform: uppercase;
color: var(--clr-primary-a50);
}
.id {
font-family: monospace, monospace;
font-size: 1.1em;
@ -326,6 +354,10 @@ input#ident {
gap: 4pt;
flex-direction: column;
}
p.subnav {
flex-direction: column;
}
}
@media screen and (max-width: 700px) {

View file

@ -1,8 +1,19 @@
use crate::db::schema::DollProfile;
use sqlx::{pool::PoolConnection, Postgres};
use uuid::Uuid;
use super::schema::{CreateDollProfile, DollTagsDb};
pub async fn list(mut db: DollTagsDb, from: &Uuid) -> sqlx::Result<Vec<DollProfile>> {
sqlx::query_as!(
DollProfile,
"select * from doll_profiles where bound_to_id = $1",
from
)
.fetch_all(&mut **db)
.await
}
pub async fn get(
mut db: DollTagsDb,
ident: i32,

View file

@ -76,7 +76,8 @@ pub struct CreateDollProfile<'a> {
///
/// If you only need to check the login status without any security impact
/// (e.g. for page template rendering reasons), use [`Session`][crate::auth::session::Session] instead.
#[derive(Debug)]
#[derive(Debug, Serialize)]
#[serde(crate = "rocket::serde")]
pub struct User {
pub id: Uuid,
pub created_at: chrono::DateTime<Utc>,

View file

@ -8,7 +8,7 @@ use rocket::fairing::AdHoc;
use rocket::fs::{relative, FileServer};
use rocket_db_pools::Database;
use routes::form::accounts;
use routes::{error_handlers, form, public};
use routes::{account, error_handlers, form, public};
pub mod auth;
pub mod db;
@ -27,13 +27,21 @@ fn rocket() -> _ {
catchers![error_handlers::not_found, session::unauthorized],
)
.mount("/assets", FileServer::from(relative!("/assets")))
.mount(
"/account",
routes![
account::index,
account::show_settings,
form::register_tag::show_register,
form::register_tag::handle_register,
],
)
.mount(
"/",
routes![
public::index,
public::short_url,
public::show_profile,
form::register_tag::show_register,
form::register_tag::handle_register,
accounts::show_register,
accounts::handle_register,
accounts::show_login,

31
src/routes/account.rs Normal file
View file

@ -0,0 +1,31 @@
use rocket_dyn_templates::{context, Template};
use crate::{
db::{
doll,
schema::{DollTagsDb, User},
},
pages::CommonTemplateState,
};
use super::error_handlers::PageResult;
#[get("/")]
pub async fn index(db: DollTagsDb, user: User, meta: CommonTemplateState) -> PageResult {
let tags = doll::list(db, &user.id).await?;
Ok(Template::render(
"account/index",
context! {
meta,
user,
tags,
},
)
.into())
}
#[get("/settings")]
pub fn show_settings(user: User, meta: CommonTemplateState) -> Template {
todo!("woof");
}

View file

@ -3,20 +3,19 @@ use rocket::{
response::Redirect,
};
use rocket_dyn_templates::{context, Template};
use uuid::uuid;
use crate::{
db::{
doll,
schema::{CreateDollProfile, DollTagsDb},
schema::{CreateDollProfile, DollTagsDb, User},
},
ids::{id_public_to_db, pick_ids},
pages::CommonTemplateState,
routes::{error_handlers::PageResult, public},
};
#[get("/register_tag")]
pub async fn show_register(db: DollTagsDb, meta: CommonTemplateState) -> PageResult {
#[get("/new_tag")]
pub async fn show_register(db: DollTagsDb, _user: User, meta: CommonTemplateState) -> PageResult {
let ids = pick_ids(db).await?;
Ok(Template::render(
@ -92,10 +91,11 @@ fn validate_chassis<'v>(a: &str, b: &str, c: &str, field: &str) -> form::Result<
Ok(())
}
#[post("/register_tag", data = "<tag>")]
#[post("/new_tag", data = "<tag>")]
pub async fn handle_register(
mut db: DollTagsDb,
tag: Form<Contextual<'_, TagForm<'_>>>,
user: User,
meta: CommonTemplateState,
) -> PageResult {
let tag = match tag.value {
@ -132,7 +132,7 @@ pub async fn handle_register(
let microchip_id = normalize_opt(&normalized_microchip_id);
if doll::id_exists(&mut *db, id, microchip_id.unwrap_or("")).await? {
return Ok(Redirect::found(uri!(show_register)).into());
return Ok(Redirect::found(uri!("/account", show_register)).into());
}
doll::create(
@ -153,7 +153,7 @@ pub async fn handle_register(
chassis_type: normalize_opt(tag.chassis_type),
chassis_id: normalize_opt(tag.chassis_id),
chassis_color: normalize_opt(tag.chassis_color),
bound_to_id: &uuid!("00000000-0000-0000-0000-000000000000"), // TODO: remove once accounts exist
bound_to_id: &user.id,
},
)
.await?;

View file

@ -1,3 +1,4 @@
pub mod account;
pub mod error_handlers;
pub mod form;
pub mod public;

View file

@ -1,5 +1,6 @@
use rocket::response::Redirect;
use rocket_dyn_templates::{context, Template};
use uuid::Uuid;
use crate::{
db::{doll, schema::DollTagsDb},
@ -27,6 +28,11 @@ pub fn index(
)
}
#[get("/profile/<id>")]
pub fn short_url(id: &str) -> Redirect {
Redirect::to(uri!(show_profile(Some(id), Some(""))))
}
#[get("/profile?<ident>&<microchip_id>")]
pub async fn show_profile(
db: DollTagsDb,

View file

@ -0,0 +1,41 @@
{% extends "base" %}
{% block title %}Log in - {% endblock title %}
{% block main %}
<aside>
<p class="subnav">
<a href="/account/new_tag">New tag</a>
<a href="/account/settings">Account settings</a>
<a href="/logout">Log out</a>
</p>
</aside>
<section>
<h2>Your {{tags | length}} tags</h2>
{% for profile in tags %}
<article class="profile">
<div class="header">
<p class="ident">
Tag ID <span class="id">{{profile.id | pretty_id}}</span>,
{% if profile.microchip_id %}
Microchip ID <span class="id">{{profile.microchip_id}}</span>
{% else %}
Not microchipped
{% endif %}
</p>
<p class="pronouns">
<span class="label">Pronouns</span>
<span class="pronoun_fragment">{{profile.pronoun_subject}}</span> /
<span class="pronoun_fragment">{{profile.pronoun_object}}</span> /
<span class="pronoun_fragment">{{profile.pronoun_possessive}}</span>
</p>
</div>
<h3>{{profile.name}}</h3>
<div class="subnav">
<a href="/profile/{{profile.id}}">Public page</a>
<a href="/account/edit/{{profile.id}}">Edit</a>
<a href="/account/delete/{{profile.id}}">Delete</a>
</div>
</article>
{% endfor %}
</section>
{% endblock main %}

View file

@ -13,8 +13,7 @@
<nav>
{% if meta.logged_in %}
<a class="btn" href="/register_tag">New tag</a>
<a href="/logout" class="btn">Log out</a>
<a class="btn" href="/account">Account</a>
{% else %}
<a href="/login" class="btn">Log in</a>
{% endif %}