Compare commits
2 commits
main
...
original_s
Author | SHA1 | Date | |
---|---|---|---|
|
85473b4938 | ||
|
a71420fc7b |
8 changed files with 57 additions and 204 deletions
22
Cargo.lock
generated
22
Cargo.lock
generated
|
@ -1868,6 +1868,7 @@ dependencies = [
|
||||||
"time",
|
"time",
|
||||||
"tokio-stream",
|
"tokio-stream",
|
||||||
"url",
|
"url",
|
||||||
|
"uuid",
|
||||||
"webpki-roots",
|
"webpki-roots",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@ -2228,15 +2229,6 @@ version = "1.16.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "497961ef93d974e23eb6f433eb5fe1b7930b659f06d12dec6fc44a8f554c0bba"
|
checksum = "497961ef93d974e23eb6f433eb5fe1b7930b659f06d12dec6fc44a8f554c0bba"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "ulid"
|
|
||||||
version = "1.0.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "13a3aaa69b04e5b66cc27309710a569ea23593612387d67daaf102e73aa974fd"
|
|
||||||
dependencies = [
|
|
||||||
"rand",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "unicase"
|
name = "unicase"
|
||||||
version = "2.6.0"
|
version = "2.6.0"
|
||||||
|
@ -2308,6 +2300,16 @@ version = "0.2.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a"
|
checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "uuid"
|
||||||
|
version = "1.4.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d023da39d1fde5a8a3fe1f3e01ca9632ada0a63e9797de55a879d6e2236277be"
|
||||||
|
dependencies = [
|
||||||
|
"getrandom",
|
||||||
|
"serde",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "valuable"
|
name = "valuable"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
|
@ -2454,8 +2456,8 @@ dependencies = [
|
||||||
"tower-http 0.4.1",
|
"tower-http 0.4.1",
|
||||||
"tracing",
|
"tracing",
|
||||||
"tracing-subscriber",
|
"tracing-subscriber",
|
||||||
"ulid",
|
|
||||||
"unicode-segmentation",
|
"unicode-segmentation",
|
||||||
|
"uuid",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|
|
@ -22,7 +22,8 @@ justerror = "1"
|
||||||
password-hash = { version = "0.5", features = ["std", "getrandom"] }
|
password-hash = { version = "0.5", features = ["std", "getrandom"] }
|
||||||
rand = "0.8"
|
rand = "0.8"
|
||||||
serde = { version = "1", features = ["derive"] }
|
serde = { version = "1", features = ["derive"] }
|
||||||
sqlx = { version = "0.6", default-features = false, features = ["runtime-tokio-rustls", "any", "sqlite", "chrono", "time"] }
|
sqlx = { version = "0.6", default-features = false, features = ["runtime-tokio-rustls", "any",
|
||||||
|
"sqlite", "chrono", "time", "uuid"] }
|
||||||
thiserror = "1"
|
thiserror = "1"
|
||||||
tokio = { version = "1", features = ["full", "tracing"], default-features = false }
|
tokio = { version = "1", features = ["full", "tracing"], default-features = false }
|
||||||
tokio-retry = "0.3.0"
|
tokio-retry = "0.3.0"
|
||||||
|
@ -31,9 +32,9 @@ tower = { version = "0.4", features = ["util", "timeout"], default-features = fa
|
||||||
tower-http = { version = "0.4", features = ["add-extension", "trace"] }
|
tower-http = { version = "0.4", features = ["add-extension", "trace"] }
|
||||||
tracing = "0.1"
|
tracing = "0.1"
|
||||||
tracing-subscriber = { version = "0.3", features = ["env-filter"] }
|
tracing-subscriber = { version = "0.3", features = ["env-filter"] }
|
||||||
ulid = { version = "1", features = ["rand"] }
|
|
||||||
unicode-segmentation = "1"
|
unicode-segmentation = "1"
|
||||||
rand_distr = "0.4.3"
|
rand_distr = "0.4.3"
|
||||||
|
uuid = { version = "1.4.0", features = ["serde", "v4"] }
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
axum-test = "9.0.0"
|
axum-test = "9.0.0"
|
||||||
|
|
|
@ -1,14 +1,10 @@
|
||||||
-- indices
|
-- indices
|
||||||
drop index if exists user_username_dex;
|
drop index if exists user_dex;
|
||||||
drop index if exists user_email_dex;
|
drop index if exists watch_dex;
|
||||||
drop index if exists watch_title_dex;
|
drop index if exists w2wdex;
|
||||||
drop index if exists witch_added_by_dex;
|
drop index if exists note_dex;
|
||||||
drop index if exists quests_user_dex;
|
|
||||||
drop index if exists quests_watch_dex;
|
|
||||||
drop index if exists note_user_dex;
|
|
||||||
drop index if exists note_watch_dex;
|
|
||||||
-- tables
|
-- tables
|
||||||
drop table if exists watch_quests;
|
drop table if exists watch_quest;
|
||||||
drop table if exists watch_notes;
|
drop table if exists watch_notes;
|
||||||
drop table if exists follows;
|
drop table if exists follows;
|
||||||
drop table if exists users;
|
drop table if exists users;
|
||||||
|
|
|
@ -29,24 +29,25 @@ create table if not exists watches (
|
||||||
|
|
||||||
-- table of what people want to watch
|
-- table of what people want to watch
|
||||||
create table if not exists watch_quests (
|
create table if not exists watch_quests (
|
||||||
|
id blob not null primary key,
|
||||||
user blob not null,
|
user blob not null,
|
||||||
watch blob not null,
|
watch blob not null,
|
||||||
|
party blob, -- list of user IDs, but we can also scan for friends that want to watch the same thing
|
||||||
priority int, -- 1-5 how much do you want to watch it
|
priority int, -- 1-5 how much do you want to watch it
|
||||||
public boolean not null default true,
|
public boolean not null,
|
||||||
watched boolean not null default false,
|
watched boolean not null,
|
||||||
when_added int not null default (unixepoch()),
|
when_added int,
|
||||||
when_watched int,
|
when_watched int,
|
||||||
last_updated int not null default (unixepoch()),
|
last_updated int not null default (unixepoch()),
|
||||||
foreign key (user) references users (id) on delete cascade on update no action,
|
foreign key (user) references users (id) on delete cascade on update no action,
|
||||||
foreign key (watch) references watches (id) on delete cascade on update no action,
|
foreign key (watch) references watches (id) on delete cascade on update no action
|
||||||
primary key (user, watch)
|
);
|
||||||
) without rowid;
|
|
||||||
|
|
||||||
-- friend lists; this should really be a graph db, maybe the whole thing should be
|
-- friend lists; this should really be a graph db, maybe the whole thing should be
|
||||||
-- TODO: look into replacing sqlite with https://www.cozodb.org/
|
-- TODO: look into replacing sqlite with https://www.cozodb.org/
|
||||||
create table if not exists follows (
|
create table if not exists follows (
|
||||||
user blob not null primary key,
|
user blob not null primary key,
|
||||||
follows blob, -- possibly empty friends list in some app-specific format
|
coven blob, -- possibly empty friends list in some app-specific format
|
||||||
last_updated int not null default (unixepoch()),
|
last_updated int not null default (unixepoch()),
|
||||||
foreign key (user) references users (id) on delete cascade on update no action
|
foreign key (user) references users (id) on delete cascade on update no action
|
||||||
);
|
);
|
||||||
|
@ -64,10 +65,10 @@ create table if not exists watch_notes (
|
||||||
|
|
||||||
-- indices, not needed for follows
|
-- indices, not needed for follows
|
||||||
create index if not exists user_username_dex on users (username);
|
create index if not exists user_username_dex on users (username);
|
||||||
create index if not exists user_email_dex on users (lower(email));
|
create index if not exists user_email_dex on users (email);
|
||||||
create index if not exists watch_title_dex on watches (lower(title));
|
create index if not exists watch_title_dex on watches (title);
|
||||||
create index if not exists watch_added_by_dex on watches (added_by);
|
create index if not exists watch_added_by_dex on watches (added_by);
|
||||||
create index if not exists quests_user_dex on watch_quests (user);
|
create index if not exists quests_user_dex on watch_quests (user);
|
||||||
create index if not exists quests_watch_dex on watch_quests (watch);
|
create index if not exists questswatch_dex on watch_quests (watch);
|
||||||
create index if not exists note_user_dex on watch_notes (user);
|
create index if not exists note_user_dex on watch_notes (user);
|
||||||
create index if not exists note_watch_dex on watch_notes (watch);
|
create index if not exists note_watch_dex on watch_notes (watch);
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
drop trigger if exists update_last_updated_users;
|
drop trigger if exists update_last_updated_users;
|
||||||
drop trigger if exists update_last_updated_watches;
|
drop trigger if exists update_last_updated_watches;
|
||||||
drop trigger if exists update_last_updated_watch_quests;
|
drop trigger if exists update_last_updated_watch_quest;
|
||||||
drop trigger if exists update_last_updated_follows;
|
drop trigger if exists update_last_updated_follows;
|
||||||
drop trigger if exists update_last_updated_watch_notes;
|
drop trigger if exists update_last_updated_watch_notes;
|
||||||
|
|
|
@ -16,7 +16,7 @@ create trigger if not exists update_last_updated_watch_quests
|
||||||
after update on watch_quests
|
after update on watch_quests
|
||||||
when OLD.last_updated = NEW.last_updated or OLD.last_updated is null
|
when OLD.last_updated = NEW.last_updated or OLD.last_updated is null
|
||||||
BEGIN
|
BEGIN
|
||||||
update watch_quests set last_updated = (select unixepoch()) where id=NEW.id;
|
update watch_quest set last_updated = (select unixepoch()) where id=NEW.id;
|
||||||
END;
|
END;
|
||||||
|
|
||||||
create trigger if not exists update_last_updated_follows
|
create trigger if not exists update_last_updated_follows
|
||||||
|
|
185
src/db_id.rs
185
src/db_id.rs
|
@ -1,48 +1,34 @@
|
||||||
use std::{
|
use std::fmt::{Debug, Display};
|
||||||
borrow::Cow,
|
|
||||||
fmt::{Debug, Display},
|
|
||||||
};
|
|
||||||
|
|
||||||
use chrono::Utc;
|
use chrono::Utc;
|
||||||
use serde::{de::Visitor, Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use sqlx::{
|
use uuid::{Error, Uuid};
|
||||||
encode::IsNull,
|
|
||||||
sqlite::{SqliteArgumentValue, SqliteValueRef},
|
|
||||||
Decode, Encode, Sqlite,
|
|
||||||
};
|
|
||||||
use ulid::Ulid;
|
|
||||||
|
|
||||||
#[derive(Default, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
#[derive(
|
||||||
pub struct DbId(pub Ulid);
|
Default, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, sqlx::Type, Serialize, Deserialize,
|
||||||
|
)]
|
||||||
|
#[sqlx(transparent)]
|
||||||
|
pub struct DbId(pub Uuid);
|
||||||
|
|
||||||
impl DbId {
|
impl DbId {
|
||||||
pub fn bytes(&self) -> [u8; 16] {
|
|
||||||
self.to_be_bytes()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn to_be_bytes(self) -> [u8; 16] {
|
|
||||||
self.0 .0.to_be_bytes()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn is_nil(&self) -> bool {
|
pub fn is_nil(&self) -> bool {
|
||||||
self.0.is_nil()
|
self.0.is_nil()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
Self(Ulid::new())
|
Self(Uuid::new_v4())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn from_string(s: &str) -> Result<Self, ulid::DecodeError> {
|
pub fn from_string(s: &str) -> Result<Self, Error> {
|
||||||
let id = Ulid::from_string(s)?;
|
Ok(Self(Uuid::try_parse(s)?))
|
||||||
Ok(id.into())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn as_string(&self) -> String {
|
pub fn as_string(&self) -> String {
|
||||||
self.0.to_string()
|
self.0.simple().to_string()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn created_at(&self) -> chrono::DateTime<Utc> {
|
pub fn created_at(&self) -> chrono::DateTime<Utc> {
|
||||||
self.0.datetime().into()
|
chrono::DateTime::default()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -62,153 +48,14 @@ impl Debug for DbId {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<Ulid> for DbId {
|
impl From<Uuid> for DbId {
|
||||||
fn from(value: Ulid) -> Self {
|
fn from(value: Uuid) -> Self {
|
||||||
DbId(value)
|
DbId(value)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<u128> for DbId {
|
impl From<u128> for DbId {
|
||||||
fn from(value: u128) -> Self {
|
fn from(value: u128) -> Self {
|
||||||
DbId(value.into())
|
DbId(Uuid::from_u128(value))
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//-************************************************************************
|
|
||||||
// sqlx traits for going in and out of the db
|
|
||||||
//-************************************************************************
|
|
||||||
|
|
||||||
impl sqlx::Type<sqlx::Sqlite> for DbId {
|
|
||||||
fn type_info() -> <sqlx::Sqlite as sqlx::Database>::TypeInfo {
|
|
||||||
<&[u8] as sqlx::Type<sqlx::Sqlite>>::type_info()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'q> Encode<'q, Sqlite> for DbId {
|
|
||||||
fn encode_by_ref(&self, args: &mut Vec<SqliteArgumentValue<'q>>) -> IsNull {
|
|
||||||
args.push(SqliteArgumentValue::Blob(Cow::Owned(self.bytes().to_vec())));
|
|
||||||
IsNull::No
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Decode<'_, Sqlite> for DbId {
|
|
||||||
fn decode(value: SqliteValueRef<'_>) -> Result<Self, sqlx::error::BoxDynError> {
|
|
||||||
let bytes = <&[u8] as Decode<Sqlite>>::decode(value)?;
|
|
||||||
let bytes: [u8; 16] = bytes.try_into().unwrap_or_default();
|
|
||||||
Ok(u128::from_be_bytes(bytes).into())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//-************************************************************************
|
|
||||||
// serde traits
|
|
||||||
//-************************************************************************
|
|
||||||
impl Serialize for DbId {
|
|
||||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
|
||||||
where
|
|
||||||
S: serde::Serializer,
|
|
||||||
{
|
|
||||||
serializer.serialize_bytes(&self.bytes())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
struct DbIdVisitor;
|
|
||||||
|
|
||||||
impl<'de> Visitor<'de> for DbIdVisitor {
|
|
||||||
type Value = DbId;
|
|
||||||
|
|
||||||
fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
|
|
||||||
formatter.write_str("16 bytes")
|
|
||||||
}
|
|
||||||
|
|
||||||
fn visit_bytes<E>(self, v: &[u8]) -> Result<Self::Value, E>
|
|
||||||
where
|
|
||||||
E: serde::de::Error,
|
|
||||||
{
|
|
||||||
match std::convert::TryInto::<[u8; 16]>::try_into(v) {
|
|
||||||
Ok(v) => Ok(u128::from_be_bytes(v).into()),
|
|
||||||
Err(_) => Err(serde::de::Error::invalid_length(v.len(), &self)),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn visit_string<E>(self, v: String) -> Result<Self::Value, E>
|
|
||||||
where
|
|
||||||
E: serde::de::Error,
|
|
||||||
{
|
|
||||||
DbId::from_string(&v)
|
|
||||||
.map_err(|_| serde::de::Error::invalid_value(serde::de::Unexpected::Str(&v), &self))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
|
|
||||||
where
|
|
||||||
E: serde::de::Error,
|
|
||||||
{
|
|
||||||
DbId::from_string(v)
|
|
||||||
.map_err(|_| serde::de::Error::invalid_value(serde::de::Unexpected::Str(v), &self))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn visit_byte_buf<E>(self, v: Vec<u8>) -> Result<Self::Value, E>
|
|
||||||
where
|
|
||||||
E: serde::de::Error,
|
|
||||||
{
|
|
||||||
let len = v.len();
|
|
||||||
match std::convert::TryInto::<[u8; 16]>::try_into(v) {
|
|
||||||
Ok(v) => Ok(u128::from_be_bytes(v).into()),
|
|
||||||
Err(_) => Err(serde::de::Error::invalid_length(len, &self)),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn visit_seq<A>(self, mut seq: A) -> Result<Self::Value, A::Error>
|
|
||||||
where
|
|
||||||
A: serde::de::SeqAccess<'de>,
|
|
||||||
{
|
|
||||||
let mut raw_bytes_from_db = [0u8; 16];
|
|
||||||
let size = seq.size_hint().unwrap_or(0);
|
|
||||||
let mut count = 0;
|
|
||||||
while let Some(val) = seq.next_element()? {
|
|
||||||
if count > 15 {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
raw_bytes_from_db[count] = val;
|
|
||||||
count += 1;
|
|
||||||
}
|
|
||||||
if count != 16 || size > 16 {
|
|
||||||
let sz = if count < 16 { count } else { size };
|
|
||||||
Err(serde::de::Error::invalid_length(sz, &self))
|
|
||||||
} else {
|
|
||||||
Ok(u128::from_be_bytes(raw_bytes_from_db).into())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'de> Deserialize<'de> for DbId {
|
|
||||||
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
|
||||||
where
|
|
||||||
D: serde::Deserializer<'de>,
|
|
||||||
{
|
|
||||||
deserializer.deserialize_bytes(DbIdVisitor)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//-************************************************************************
|
|
||||||
// serialization tests
|
|
||||||
//-************************************************************************
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod test {
|
|
||||||
use serde_test::{assert_tokens, Token};
|
|
||||||
|
|
||||||
use super::*;
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_ser_de() {
|
|
||||||
let bytes: [u8; 16] = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15];
|
|
||||||
let id: DbId = u128::from_be_bytes(bytes).into();
|
|
||||||
|
|
||||||
assert_tokens(
|
|
||||||
&id,
|
|
||||||
&[Token::Bytes(&[
|
|
||||||
0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15,
|
|
||||||
])],
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -52,12 +52,18 @@ impl From<&ImportMovieOmega> for Watch {
|
||||||
// utility functions for building CLI tools, currently just for benchmarking
|
// utility functions for building CLI tools, currently just for benchmarking
|
||||||
//-************************************************************************
|
//-************************************************************************
|
||||||
pub async fn add_watch_quests(pool: &SqlitePool, quests: &[WatchQuest]) -> Result<(), ()> {
|
pub async fn add_watch_quests(pool: &SqlitePool, quests: &[WatchQuest]) -> Result<(), ()> {
|
||||||
let mut builder = sqlx::QueryBuilder::new("insert into watch_quests (user, watch) ");
|
let mut builder =
|
||||||
|
sqlx::QueryBuilder::new("insert into watch_quests (id, user, watch, public, watched) ");
|
||||||
builder.push_values(quests, |mut b, quest| {
|
builder.push_values(quests, |mut b, quest| {
|
||||||
|
let id = DbId::new();
|
||||||
let user = quest.user;
|
let user = quest.user;
|
||||||
let watch = quest.watch;
|
let watch = quest.watch;
|
||||||
//eprintln!("{user}, {watch}");
|
//eprintln!("{user}, {watch}");
|
||||||
b.push_bind(user).push_bind(watch);
|
b.push_bind(id)
|
||||||
|
.push_bind(user)
|
||||||
|
.push_bind(watch)
|
||||||
|
.push_bind(true)
|
||||||
|
.push_bind(false);
|
||||||
});
|
});
|
||||||
|
|
||||||
let q = builder.build();
|
let q = builder.build();
|
||||||
|
|
Loading…
Reference in a new issue