added archival

This commit is contained in:
Artemis 2025-01-25 17:50:36 +01:00
parent 1a152e7acb
commit 3f9ac03c92
10 changed files with 137 additions and 22 deletions

View file

@ -0,0 +1,2 @@
alter table doll_profiles
add column archived_at timestamptz;

View file

@ -1,13 +1,21 @@
use crate::db::schema::DollProfile;
use sqlx::{pool::PoolConnection, Postgres};
use uuid::Uuid;
use super::schema::{CreateDollProfile, DollTagsDb};
use super::schema::{CreateDollProfile, DbHook};
pub async fn list(mut db: DollTagsDb, from: &Uuid) -> sqlx::Result<Vec<DollProfile>> {
pub async fn list(db: &mut DbHook, from: &Uuid) -> sqlx::Result<Vec<DollProfile>> {
sqlx::query_as!(
DollProfile,
"select * from doll_profiles where bound_to_id = $1",
"select * from doll_profiles where bound_to_id = $1 and archived_at is null",
from
)
.fetch_all(&mut **db)
.await
}
pub async fn list_archived(db: &mut DbHook, from: &Uuid) -> sqlx::Result<Vec<i32>> {
sqlx::query_scalar!(
"select id from doll_profiles where bound_to_id = $1 and archived_at is not null",
from
)
.fetch_all(&mut **db)
@ -15,14 +23,14 @@ pub async fn list(mut db: DollTagsDb, from: &Uuid) -> sqlx::Result<Vec<DollProfi
}
pub async fn get(
mut db: DollTagsDb,
db: &mut DbHook,
ident: i32,
microchip_id: &str,
) -> sqlx::Result<Option<DollProfile>> {
sqlx::query_as!(
DollProfile,
r#"
select * from doll_profiles where id = $1 or microchip_id = $2
select * from doll_profiles where (id = $1 or microchip_id = $2) and archived_at is null
"#,
ident,
microchip_id
@ -31,7 +39,7 @@ pub async fn get(
.await
}
pub async fn check_ids(mut db: DollTagsDb, idents: &Vec<i32>) -> sqlx::Result<Vec<i32>> {
pub async fn check_ids(db: &mut DbHook, idents: &Vec<i32>) -> sqlx::Result<Vec<i32>> {
sqlx::query_scalar!(
"select id from doll_profiles where id in (select * from unnest($1::int[]))",
idents
@ -40,11 +48,7 @@ pub async fn check_ids(mut db: DollTagsDb, idents: &Vec<i32>) -> sqlx::Result<Ve
.await
}
pub async fn id_exists(
db: &mut PoolConnection<Postgres>,
ident: i32,
microchip_id: &str,
) -> sqlx::Result<bool> {
pub async fn id_exists(db: &mut DbHook, ident: i32, microchip_id: &str) -> sqlx::Result<bool> {
Ok(sqlx::query!(
"select id from doll_profiles where id = $1 or microchip_id = $2",
ident,
@ -55,7 +59,7 @@ pub async fn id_exists(
.is_some())
}
pub async fn create(mut db: DollTagsDb, doll: CreateDollProfile<'_>) -> sqlx::Result<()> {
pub async fn create(db: &mut DbHook, doll: CreateDollProfile<'_>) -> sqlx::Result<()> {
sqlx::query!(
r#"
insert into doll_profiles
@ -82,3 +86,41 @@ pub async fn create(mut db: DollTagsDb, doll: CreateDollProfile<'_>) -> sqlx::Re
Ok(())
}
/// deleting a doll profile only wipes the data associated to it but retains two bits of info:
/// - the tag's ID
/// - the account which created this tag
///
/// this is to ensure that no one else will be able to re-use this tag for some time while allowing
/// the account holder to "re-create" one with this ID.
///
/// A period of time after which deleted accounts will have their IDs freed is to be set.
pub async fn delete(db: &mut DbHook, id: i32) -> sqlx::Result<()> {
sqlx::query!(
r#"
update doll_profiles
set microchip_id = null,
name = '',
pronoun_subject = '',
pronoun_object = '',
pronoun_possessive = '',
handler_name = '',
handler_link = null,
kind = null,
breed = null,
behaviour = null,
description = null,
chassis_type = null,
chassis_id = null,
chassis_color = null,
updated_at = current_timestamp,
archived_at = current_timestamp
where id = $1
"#,
id
)
.execute(&mut **db)
.await?;
Ok(())
}

View file

@ -1,12 +1,17 @@
use ::chrono::Utc;
use rocket::serde::Serialize;
use rocket_db_pools::{Connection, Database};
use sqlx::types::{chrono, Uuid};
use sqlx::{
pool::PoolConnection,
types::{chrono, Uuid},
Postgres,
};
#[derive(Database)]
#[database("dolltags")]
pub struct DollTags(sqlx::PgPool);
pub type DollTagsDb = Connection<DollTags>;
pub type DbHook = PoolConnection<Postgres>;
// Doll Profiles stuff
#[derive(Debug, Serialize)]
@ -16,6 +21,7 @@ pub struct DollProfile {
pub microchip_id: Option<String>,
pub created_at: chrono::DateTime<Utc>,
pub updated_at: Option<chrono::DateTime<Utc>>,
pub archived_at: Option<chrono::DateTime<Utc>>,
pub bound_to_id: Uuid,

View file

@ -9,9 +9,9 @@ pub fn generate_ids() -> Vec<i32> {
(1..=10).map(|_| uniform.sample(&mut rng)).collect()
}
pub async fn pick_ids(db: DollTagsDb) -> Result<Vec<i32>, sqlx::Error> {
pub async fn pick_ids(mut db: DollTagsDb) -> Result<Vec<i32>, sqlx::Error> {
let mut ids_bundle = generate_ids();
let occupied_ids = doll::check_ids(db, &ids_bundle).await?;
let occupied_ids = doll::check_ids(&mut *db, &ids_bundle).await?;
ids_bundle.retain(|&id| !occupied_ids.contains(&id));
Ok(ids_bundle.iter().take(5).map(|v| *v).collect::<Vec<i32>>())
}

View file

@ -34,6 +34,8 @@ fn rocket() -> _ {
account::show_settings,
form::register_tag::show_register,
form::register_tag::handle_register,
account::ask_delete,
account::confirm_delete,
],
)
.mount(

View file

@ -1,3 +1,4 @@
use rocket::response::Redirect;
use rocket_dyn_templates::{context, Template};
use crate::{
@ -11,8 +12,9 @@ use crate::{
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?;
pub async fn index(mut db: DollTagsDb, user: User, meta: CommonTemplateState) -> PageResult {
let tags = doll::list(&mut *db, &user.id).await?;
let archived_tags = doll::list_archived(&mut *db, &user.id).await?;
Ok(Template::render(
"account/index",
@ -20,6 +22,7 @@ pub async fn index(db: DollTagsDb, user: User, meta: CommonTemplateState) -> Pag
meta,
user,
tags,
archived_tags,
},
)
.into())
@ -29,3 +32,32 @@ pub async fn index(db: DollTagsDb, user: User, meta: CommonTemplateState) -> Pag
pub fn show_settings(user: User, meta: CommonTemplateState) -> Template {
todo!("woof");
}
#[get("/delete/<id>")]
pub async fn ask_delete(
mut db: DollTagsDb,
id: i32,
_user: User,
meta: CommonTemplateState,
) -> PageResult {
let db_tag = doll::get(&mut *db, id, "").await?;
if let Some(tag) = db_tag {
Ok(Template::render(
"tag/delete",
context! {
meta,
tag,
},
)
.into())
} else {
Ok(Redirect::to(uri!("/account", index)).into())
}
}
#[get("/yes_delete_this/<id>")]
pub async fn confirm_delete(mut db: DollTagsDb, id: i32, _user: User) -> PageResult {
doll::delete(&mut *db, id).await?;
Ok(Redirect::to(uri!("/account", index)).into())
}

View file

@ -136,7 +136,7 @@ pub async fn handle_register(
}
doll::create(
db,
&mut *db,
CreateDollProfile {
id,
microchip_id,

View file

@ -1,6 +1,5 @@
use rocket::response::Redirect;
use rocket_dyn_templates::{context, Template};
use uuid::Uuid;
use crate::{
db::{doll, schema::DollTagsDb},
@ -35,7 +34,7 @@ pub fn short_url(id: &str) -> Redirect {
#[get("/profile?<ident>&<microchip_id>")]
pub async fn show_profile(
db: DollTagsDb,
mut db: DollTagsDb,
ident: Option<&str>,
microchip_id: Option<&str>,
meta: CommonTemplateState,
@ -43,7 +42,7 @@ pub async fn show_profile(
let internal_id = ident.and_then(|v| id_public_to_db(v)).unwrap_or(0);
let microchip_id = microchip_id.unwrap_or("");
let profile = match doll::get(db, internal_id, microchip_id).await? {
let profile = match doll::get(&mut *db, internal_id, microchip_id).await? {
Some(p) => p,
None => return Ok(Redirect::to(uri!(index(Some(true), ident, Some(microchip_id)))).into()),
};

View file

@ -38,4 +38,18 @@
</article>
{% endfor %}
</section>
{% if archived_tags | length > 0 %}
<section>
<h2>Your archived tags</h2>
<p>You have {{archived_tags | length}} archived tags for which you may reuse the ID.</p>
<ul>
{% for id in archived_tags %}
<li><a href="/account/edit/{{id}}">{{id}}</a></li>
{% endfor %}
</ul>
</section>
{% endif %}
{% endblock main %}

View file

@ -0,0 +1,18 @@
{% extends "base" %}
{% block title %}Log in - {% endblock title %}
{% block main %}
<h2>Are you sure to delete it?</h2>
<p>You are about to delete the tag {{ tag.id | pretty_id }} registered for "{{tag.name}}".</p>
<p>
Doing so will irreversibly remove the data,
and the ID will be kept for you in case you were to want to create a new
tag using this ID.
</p>
<p class="subnav">
<a href="/account">No, take me back</a>
<a href="/account/yes_delete_this/{{tag.id}}">Yes, delete this tag</a>
</p>
{% endblock main %}