damn migrating to pgsql was fast here

This commit is contained in:
Artemis 2025-01-24 16:24:49 +01:00
parent fe2c2a1b8d
commit 5541477da5
15 changed files with 83 additions and 91 deletions

2
.env
View file

@ -1 +1 @@
DATABASE_URL="sqlite://dolltags.sqlite"
DATABASE_URL="postgres://postgres:woofwoof@localhost/dolltags"

2
.gitignore vendored
View file

@ -1,4 +1,2 @@
# built binary
cap
/target
*.sqlite*

View file

@ -6,9 +6,9 @@ edition = "2021"
[dependencies]
rocket = "0.5.1"
rocket_dyn_templates = { version = "0.2.0", features = ["tera"] }
rocket_db_pools = { version = "0.2.0", features = ["sqlx_sqlite"] }
rocket_db_pools = { version = "0.2.0", features = ["sqlx_postgres"] }
sqlx = { version = "0.7", default-features = false, features = [
"sqlite",
"postgres",
"macros",
"chrono",
"migrate",

View file

@ -29,4 +29,3 @@ a profile is
- 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
- pgsql migration? may make hosting easier for me as well as cleaner migrations and types

View file

@ -1,2 +1,2 @@
[default.databases.dolltags]
url = "dolltags.sqlite"
url = "postgres://postgres:woofwoof@localhost/dolltags"

View file

@ -1,26 +1,23 @@
-- base schema
create table doll_profiles (
id integer not null primary key,
microchip_id varchar(32) unique,
created_at timestamptz not null default current_timestamp,
updated_at timestamptz,
-- notnulls are tmp till i deal with proper validation
create table if not exists doll_profiles (
id integer not null primary key, -- 000000 format
created_at text not null default current_timestamp,
updated_at text,
name varchar(256) not null,
pronoun_subject varchar(32) not null,
pronoun_object varchar(32) not null,
pronoun_possessive varchar(32) not null,
-- base info
name text not null,
pronouns text not null, -- format as `{subject}/{object}/{possessive}` eg `she/her/hers`
handler_name text not null,
handler_url text,
handler_name varchar(256) not null,
handler_link varchar(2048),
-- customisation options for various entities
description text,
kind text,
breed text,
chassis_type text,
chassis_id text,
chassis_color text,
kind varchar(256),
breed varchar(256),
behaviour varchar(256),
description varchar(2048),
-- ID'ing
behaviour text,
microchip_id text unique
) strict;
chassis_type varchar(256),
chassis_id varchar(256),
chassis_color varchar(256)
);

View file

@ -9,7 +9,7 @@ pkgs.mkShell {
# native dependencies
# pkg-config
# Dev env
sqlite sqlitebrowser sqlx-cli
sqlx-cli
]
);
@ -19,5 +19,4 @@ pkgs.mkShell {
# Rust
# See https://discourse.nixos.org/t/rust-src-not-found-and-other-misadventures-of-developing-rust-on-nixos/11570/3?u=samuela. for more details.
RUST_SRC_PATH = pkgs.rust.packages.stable.rustPlatform.rustLibSrc;
DATABASE_URL = "sqlite://dolltags.sqlite";
}

View file

@ -1,51 +1,41 @@
use crate::db::schema::DollProfile;
use sqlx::{pool::PoolConnection, types::chrono, Sqlite};
use sqlx::{pool::PoolConnection, Postgres};
use super::schema::{CreateDollProfile, DollTagsDb};
pub async fn get(
mut db: DollTagsDb,
ident: i64,
ident: i32,
microchip_id: &str,
) -> sqlx::Result<Option<DollProfile>> {
sqlx::query_as!(
DollProfile,
r#"
select
id, microchip_id,
name, pronouns, handler_name, handler_url, kind, breed, behaviour, description, chassis_type, chassis_id, chassis_color,
created_at as "created_at!: chrono::NaiveDateTime", updated_at as "updated_at!: Option<chrono::NaiveDateTime>"
from doll_profiles where id = ? or microchip_id = ?
select * from doll_profiles where id = $1 or microchip_id = $2
"#,
ident,
microchip_id
)
.fetch_optional(&mut **db).await
.fetch_optional(&mut **db)
.await
}
pub async fn check_ids(mut db: DollTagsDb, idents: &Vec<i64>) -> sqlx::Result<Vec<i64>> {
let idents_sql_input = idents.iter().map(|_| "?").collect::<Vec<&str>>().join(", ");
let sql = format!(
r#"select id from doll_profiles where id in ({})"#,
idents_sql_input
);
let mut query = sqlx::query_scalar(&sql);
for ident in idents {
query = query.bind(ident);
}
query.fetch_all(&mut **db).await
pub async fn check_ids(mut db: DollTagsDb, idents: &Vec<i32>) -> sqlx::Result<Vec<i32>> {
sqlx::query_scalar!(
"select id from doll_profiles where id in (select * from unnest($1::int[]))",
idents
)
.fetch_all(&mut **db)
.await
}
pub async fn id_exists(
db: &mut PoolConnection<Sqlite>,
ident: i64,
db: &mut PoolConnection<Postgres>,
ident: i32,
microchip_id: &str,
) -> sqlx::Result<bool> {
Ok(sqlx::query!(
"select id from doll_profiles where id = ? or microchip_id = ?",
"select id from doll_profiles where id = $1 or microchip_id = $2",
ident,
microchip_id
)
@ -54,19 +44,21 @@ pub async fn id_exists(
.is_some())
}
pub async fn create(mut db: DollTagsDb, doll: CreateDollProfile<'_>) -> sqlx::Result<i64> {
pub async fn create(mut db: DollTagsDb, doll: CreateDollProfile<'_>) -> sqlx::Result<()> {
sqlx::query!(
r#"
insert into doll_profiles
(id, microchip_id, name, pronouns, handler_name, handler_url, kind, breed, behaviour, description, chassis_type, chassis_id, chassis_color)
values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
(id, microchip_id, name, pronoun_subject, pronoun_object, pronoun_possessive, handler_name, handler_link, kind, breed, behaviour, description, chassis_type, chassis_id, chassis_color)
values ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15)
"#,
doll.id,
doll.microchip_id,
doll.name,
doll.pronouns,
doll.pronoun_subject,
doll.pronoun_object,
doll.pronoun_possessive,
doll.handler_name,
doll.handler_url,
doll.handler_link,
doll.kind,
doll.breed,
doll.behaviour,
@ -76,5 +68,5 @@ pub async fn create(mut db: DollTagsDb, doll: CreateDollProfile<'_>) -> sqlx::Re
doll.chassis_color,
).execute(&mut **db).await?;
Ok(doll.id)
Ok(())
}

View file

@ -1,25 +1,28 @@
use ::chrono::Utc;
use rocket::serde::Serialize;
use rocket_db_pools::{Connection, Database};
use sqlx::types::chrono;
#[derive(Database)]
#[database("dolltags")]
pub struct DollTags(sqlx::SqlitePool);
pub struct DollTags(sqlx::PgPool);
pub type DollTagsDb = Connection<DollTags>;
// Doll Profiles stuff
#[derive(Debug, Serialize)]
#[serde(crate = "rocket::serde")]
pub struct DollProfile {
pub id: i64,
pub id: i32,
pub microchip_id: Option<String>,
pub created_at: chrono::NaiveDateTime,
pub updated_at: Option<chrono::NaiveDateTime>,
pub created_at: chrono::DateTime<Utc>,
pub updated_at: Option<chrono::DateTime<Utc>>,
pub name: String,
pub pronouns: String,
pub pronoun_subject: String,
pub pronoun_object: String,
pub pronoun_possessive: String,
pub handler_name: String,
pub handler_url: Option<String>,
pub handler_link: Option<String>,
pub kind: Option<String>,
pub breed: Option<String>,
@ -43,13 +46,15 @@ impl DollProfile {
#[derive(Debug)]
pub struct CreateDollProfile<'a> {
pub id: i64,
pub id: i32,
pub microchip_id: Option<&'a str>,
pub name: &'a str,
pub pronouns: &'a str,
pub pronoun_subject: &'a str,
pub pronoun_object: &'a str,
pub pronoun_possessive: &'a str,
pub handler_name: &'a str,
pub handler_url: Option<&'a str>,
pub handler_link: Option<&'a str>,
pub kind: Option<&'a str>,
pub breed: Option<&'a str>,

View file

@ -3,29 +3,29 @@ use regex::Regex;
use crate::db::{doll, schema::DollTagsDb};
pub fn generate_ids() -> Vec<i64> {
let uniform = Uniform::new_inclusive::<i64, i64>(100_000, 999_999);
pub fn generate_ids() -> Vec<i32> {
let uniform = Uniform::new_inclusive::<i32, i32>(100_000, 999_999);
let mut rng = thread_rng();
(1..=10).map(|_| uniform.sample(&mut rng)).collect()
}
pub async fn pick_ids(db: DollTagsDb) -> Result<Vec<i64>, sqlx::Error> {
pub async fn pick_ids(db: DollTagsDb) -> Result<Vec<i32>, sqlx::Error> {
let mut ids_bundle = generate_ids();
let occupied_ids = doll::check_ids(db, &ids_bundle).await?;
ids_bundle.retain(|&id| !occupied_ids.contains(&id));
Ok(ids_bundle.iter().take(5).map(|v| *v).collect::<Vec<i64>>())
Ok(ids_bundle.iter().take(5).map(|v| *v).collect::<Vec<i32>>())
}
pub fn id_public_to_db(id: &str) -> Option<i64> {
pub fn id_public_to_db(id: &str) -> Option<i32> {
let id_re = Regex::new(r"^\d{6}$").unwrap();
if id_re.is_match(id) {
id.parse::<i64>().ok()
id.parse::<i32>().ok()
} else {
None
}
}
pub fn id_db_to_public(id: i64) -> String {
pub fn id_db_to_public(id: i32) -> String {
let first = id / 1000;
let second = id % 1000;
format!("{:0>3}-{:0>3}", first, second)

View file

@ -23,7 +23,7 @@ fn rocket() -> _ {
engines
.tera
.register_filter("pretty_id", |v: &Value, _: &HashMap<String, Value>| {
let value = try_get_value!("pretty_id", "value", i64, v);
let value = try_get_value!("pretty_id", "value", i32, v);
Ok(to_value(ids::id_db_to_public(value)).unwrap())
});
}))

View file

@ -100,7 +100,7 @@ pub async fn handle_register(
// in case the form validation fails, this will be tasked with rendering the page again with submitted values and display errors
let ids = pick_ids(db).await?;
println!("{:?}", &tag.context);
debug!("registration form invalid, context: {:?}", &tag.context);
return Ok(Template::render(
"register",
@ -113,7 +113,7 @@ pub async fn handle_register(
}
};
println!("register: {:?}", tag);
debug!("registering tag: {:?}", tag);
fn normalize_opt(opt: &str) -> Option<&str> {
if opt.len() != 0 {
Some(opt)
@ -123,10 +123,6 @@ pub async fn handle_register(
}
let id = id_public_to_db(&tag.ident).expect("id format was wrong but is now right??");
let pronouns = format!(
"{}/{}/{}",
tag.pronoun_subject, tag.pronoun_object, tag.pronoun_possessive
);
let normalized_microchip_id = tag.microchip_id.to_lowercase();
let microchip_id = normalize_opt(&normalized_microchip_id);
@ -140,9 +136,11 @@ pub async fn handle_register(
id,
microchip_id,
name: tag.name,
pronouns: pronouns.as_str(),
pronoun_subject: tag.pronoun_subject,
pronoun_object: tag.pronoun_object,
pronoun_possessive: tag.pronoun_possessive,
handler_name: tag.handler_name,
handler_url: normalize_opt(tag.handler_link),
handler_link: normalize_opt(tag.handler_link),
kind: normalize_opt(tag.kind),
breed: normalize_opt(tag.breed),
behaviour: normalize_opt(tag.behaviour),

View file

@ -39,7 +39,7 @@ pub async fn show_profile(
&& profile.chassis_type.is_some()
&& profile.chassis_color.is_some();
println!("{:?}", profile);
debug!("showing profile: {:?}", profile);
Ok(Template::render(
"show_profile",
context! {

View file

@ -1,7 +1,7 @@
{% macro pretty_pronouns(pronouns) %}
{% set fragments = pronouns | split(pat="/") %}
{% for fr in fragments %}
<span class="pronoun_fragment">{{fr}}</span>
{% if not loop.last %}/{% endif %}
{% endfor %}
{% endmacro pretty_pronouns %}
{% endmacro pretty_pronouns %}

View file

@ -13,7 +13,11 @@
Not microchipped
{% endif %}
</p>
<p class="pronouns">{{ macros::pretty_pronouns(pronouns=profile.pronouns) }}</p>
<p class="pronouns">
<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>
</header>
<h2>{{profile.name}}</h2>