refactor model IDs to use safe types
This is a major refactor that introduces a new generic type Id<T, I> for tagging any ID with the model they reference without any runtime overhead. From now on, it should be pretty much impossible to pass e.g. a note ID to a function that expects, say, an account ID. Furthermore, the follow and like tables have been simplified to use the two IDs that define the relationship as a composite primary key. Finally, likes now have a memory cache.
This commit is contained in:
parent
7845fb3680
commit
a0d2dc4151
37 changed files with 550 additions and 218 deletions
|
@ -1,10 +1,9 @@
|
||||||
CREATE TABLE follows (
|
CREATE TABLE follows (
|
||||||
id BIGSERIAL PRIMARY KEY,
|
|
||||||
iri VARCHAR,
|
iri VARCHAR,
|
||||||
follower_id BIGINT REFERENCES accounts (id) ON DELETE CASCADE,
|
follower_id BIGINT REFERENCES accounts (id) ON DELETE CASCADE,
|
||||||
followee_id BIGINT REFERENCES accounts (id) ON DELETE CASCADE,
|
followee_id BIGINT REFERENCES accounts (id) ON DELETE CASCADE,
|
||||||
created_at TIMESTAMP NOT NULL DEFAULT now()
|
created_at TIMESTAMP NOT NULL DEFAULT now(),
|
||||||
|
CONSTRAINT follows_pkey PRIMARY KEY (follower_id, followee_id)
|
||||||
);
|
);
|
||||||
|
|
||||||
CREATE UNIQUE INDEX index_follows_on_iri ON follows(iri);
|
CREATE UNIQUE INDEX index_follows_on_iri ON follows(iri);
|
||||||
CREATE UNIQUE INDEX index_follows_on_follower_id_and_followee_id ON follows (follower_id, followee_id);
|
|
||||||
|
|
|
@ -1,10 +1,9 @@
|
||||||
CREATE TABLE likes (
|
CREATE TABLE likes (
|
||||||
id BIGSERIAL PRIMARY KEY,
|
|
||||||
iri VARCHAR,
|
iri VARCHAR,
|
||||||
note_id BIGINT REFERENCES notes (id) ON DELETE CASCADE,
|
note_id BIGINT REFERENCES notes (id) ON DELETE CASCADE,
|
||||||
account_id BIGINT REFERENCES accounts (id) ON DELETE CASCADE,
|
account_id BIGINT REFERENCES accounts (id) ON DELETE CASCADE,
|
||||||
created_at TIMESTAMP NOT NULL DEFAULT now()
|
created_at TIMESTAMP NOT NULL DEFAULT now(),
|
||||||
|
CONSTRAINT likes_pkey PRIMARY KEY (note_id, account_id)
|
||||||
);
|
);
|
||||||
|
|
||||||
CREATE UNIQUE INDEX index_likes_on_iri ON likes (iri);
|
CREATE UNIQUE INDEX index_likes_on_iri ON likes (iri);
|
||||||
CREATE UNIQUE INDEX index_likes_on_note_id_and_account_id ON likes (note_id, account_id);
|
|
||||||
|
|
198
src/core/id.rs
Normal file
198
src/core/id.rs
Normal file
|
@ -0,0 +1,198 @@
|
||||||
|
use sqlx::{
|
||||||
|
database::{HasArguments, HasValueRef},
|
||||||
|
encode::IsNull,
|
||||||
|
error::BoxDynError,
|
||||||
|
Database,
|
||||||
|
};
|
||||||
|
use std::{
|
||||||
|
any::type_name,
|
||||||
|
fmt,
|
||||||
|
hash::{Hash, Hasher},
|
||||||
|
marker::PhantomData,
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Primitive type for regular database primary keys.
|
||||||
|
pub type IntId = i64;
|
||||||
|
|
||||||
|
/// Safe ID type for database models.
|
||||||
|
/// `T` is the database model that this ID references,
|
||||||
|
/// `I` is the primitive type representing the raw database type.
|
||||||
|
///
|
||||||
|
/// Database models should define their own ID types, like so:
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// pub type MyId = Id<MyModel>;
|
||||||
|
///
|
||||||
|
/// pub struct MyModel {
|
||||||
|
/// pub id: MyId,
|
||||||
|
/// // ...
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
pub struct Id<T, I = IntId> {
|
||||||
|
id: I,
|
||||||
|
_phantom: PhantomData<T>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T, I> Id<T, I> {
|
||||||
|
pub fn inner(&self) -> &I {
|
||||||
|
&self.id
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn into_inner(self) -> I {
|
||||||
|
self.id
|
||||||
|
}
|
||||||
|
|
||||||
|
pub const fn from(id: I) -> Self {
|
||||||
|
Id {
|
||||||
|
id,
|
||||||
|
_phantom: PhantomData,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T, I> Clone for Id<T, I>
|
||||||
|
where
|
||||||
|
I: Clone,
|
||||||
|
{
|
||||||
|
fn clone(&self) -> Self {
|
||||||
|
Id {
|
||||||
|
id: self.id.clone(),
|
||||||
|
_phantom: PhantomData,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl<T, I> Copy for Id<T, I> where I: Copy {}
|
||||||
|
|
||||||
|
impl<T, I> From<I> for Id<T, I> {
|
||||||
|
fn from(id: I) -> Self {
|
||||||
|
Id {
|
||||||
|
id,
|
||||||
|
_phantom: PhantomData,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T, I> PartialEq for Id<T, I>
|
||||||
|
where
|
||||||
|
I: PartialEq,
|
||||||
|
{
|
||||||
|
fn eq(&self, other: &Self) -> bool {
|
||||||
|
self.id.eq(&other.id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl<T, I> Eq for Id<T, I> where I: Eq {}
|
||||||
|
|
||||||
|
impl<T, I> Hash for Id<T, I>
|
||||||
|
where
|
||||||
|
I: Hash,
|
||||||
|
{
|
||||||
|
fn hash<H>(&self, state: &mut H)
|
||||||
|
where
|
||||||
|
H: Hasher,
|
||||||
|
{
|
||||||
|
self.id.hash(state);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe impl<T, I> Send for Id<T, I> where I: Send {}
|
||||||
|
unsafe impl<T, I> Sync for Id<T, I> where I: Sync {}
|
||||||
|
|
||||||
|
impl<'r, DB: Database, T, I> sqlx::Decode<'r, DB> for Id<T, I>
|
||||||
|
where
|
||||||
|
I: sqlx::Decode<'r, DB>,
|
||||||
|
{
|
||||||
|
fn decode(value: <DB as HasValueRef<'r>>::ValueRef) -> Result<Self, BoxDynError> {
|
||||||
|
let id = I::decode(value)?;
|
||||||
|
Ok(Id {
|
||||||
|
id,
|
||||||
|
_phantom: PhantomData,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'q, DB: Database, T, I> sqlx::Encode<'q, DB> for Id<T, I>
|
||||||
|
where
|
||||||
|
I: sqlx::Encode<'q, DB>,
|
||||||
|
{
|
||||||
|
fn encode(self, buf: &mut <DB as HasArguments<'q>>::ArgumentBuffer) -> IsNull {
|
||||||
|
self.id.encode(buf)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn encode_by_ref(&self, buf: &mut <DB as HasArguments<'q>>::ArgumentBuffer) -> IsNull {
|
||||||
|
self.id.encode_by_ref(buf)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn produces(&self) -> Option<DB::TypeInfo> {
|
||||||
|
self.id.produces()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn size_hint(&self) -> usize {
|
||||||
|
self.id.size_hint()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<DB: Database, T, I> sqlx::Type<DB> for Id<T, I>
|
||||||
|
where
|
||||||
|
I: sqlx::Type<DB>,
|
||||||
|
{
|
||||||
|
fn type_info() -> DB::TypeInfo {
|
||||||
|
I::type_info()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn compatible(ty: &DB::TypeInfo) -> bool {
|
||||||
|
I::compatible(ty)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T, I> serde::Serialize for Id<T, I>
|
||||||
|
where
|
||||||
|
I: serde::Serialize,
|
||||||
|
{
|
||||||
|
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||||
|
where
|
||||||
|
S: serde::Serializer,
|
||||||
|
{
|
||||||
|
self.id.serialize(serializer)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'de, T, I> serde::Deserialize<'de> for Id<T, I>
|
||||||
|
where
|
||||||
|
I: serde::Deserialize<'de>,
|
||||||
|
{
|
||||||
|
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||||
|
where
|
||||||
|
D: serde::Deserializer<'de>,
|
||||||
|
{
|
||||||
|
let id = I::deserialize(deserializer)?;
|
||||||
|
Ok(Id {
|
||||||
|
id,
|
||||||
|
_phantom: PhantomData,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn deserialize_in_place<D>(deserializer: D, place: &mut Self) -> Result<(), D::Error>
|
||||||
|
where
|
||||||
|
D: serde::Deserializer<'de>,
|
||||||
|
{
|
||||||
|
I::deserialize_in_place(deserializer, &mut place.id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T, I> fmt::Debug for Id<T, I>
|
||||||
|
where
|
||||||
|
I: fmt::Debug,
|
||||||
|
{
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
|
write!(f, "{}#{:?}", type_name::<T>(), &self.id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T, I> fmt::Display for Id<T, I>
|
||||||
|
where
|
||||||
|
I: fmt::Display,
|
||||||
|
{
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
|
fmt::Display::fmt(&self.id, f)
|
||||||
|
}
|
||||||
|
}
|
|
@ -7,8 +7,8 @@ pub use crate::util::validate::{Insane, Sane};
|
||||||
|
|
||||||
mod error;
|
mod error;
|
||||||
pub use error::*;
|
pub use error::*;
|
||||||
|
mod id;
|
||||||
pub type Id = i64;
|
pub use id::*;
|
||||||
|
|
||||||
pub fn utc_now() -> NaiveDateTime {
|
pub fn utc_now() -> NaiveDateTime {
|
||||||
Utc::now().naive_utc()
|
Utc::now().naive_utc()
|
||||||
|
|
|
@ -1,10 +1,8 @@
|
||||||
use crate::core::*;
|
|
||||||
|
|
||||||
use crate::data::memcache::{Indexable, MemCache};
|
use crate::data::memcache::{Indexable, MemCache};
|
||||||
use crate::model::Account;
|
use crate::model::{Account, AccountId};
|
||||||
|
|
||||||
pub struct MemAccountDataSource {
|
pub struct MemAccountDataSource {
|
||||||
cache: MemCache<Id, Account>,
|
cache: MemCache<AccountId, Account>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl MemAccountDataSource {
|
impl MemAccountDataSource {
|
||||||
|
@ -14,7 +12,7 @@ impl MemAccountDataSource {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn by_id(&self, id: Id) -> Option<Account> {
|
pub async fn by_id(&self, id: AccountId) -> Option<Account> {
|
||||||
self.cache.get(id).await
|
self.cache.get(id).await
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -23,8 +21,8 @@ impl MemAccountDataSource {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Indexable<Id> for Account {
|
impl Indexable<AccountId> for Account {
|
||||||
fn get_id(&self) -> Id {
|
fn get_id(&self) -> AccountId {
|
||||||
self.id
|
self.id
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
use sqlx::PgPool;
|
use sqlx::PgPool;
|
||||||
|
|
||||||
use crate::core::*;
|
use crate::core::*;
|
||||||
use crate::model::account::{Account, NewAccount};
|
use crate::model::{Account, AccountId, NewAccount};
|
||||||
|
|
||||||
pub struct PgAccountDataSource {
|
pub struct PgAccountDataSource {
|
||||||
pool: PgPool,
|
pool: PgPool,
|
||||||
|
@ -12,7 +12,7 @@ impl PgAccountDataSource {
|
||||||
PgAccountDataSource { pool }
|
PgAccountDataSource { pool }
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn by_id(&self, id: Id) -> Result<Account> {
|
pub async fn by_id(&self, id: AccountId) -> Result<Account> {
|
||||||
let account: Account = sqlx::query_as("SELECT * FROM accounts WHERE id = $1")
|
let account: Account = sqlx::query_as("SELECT * FROM accounts WHERE id = $1")
|
||||||
.bind(id)
|
.bind(id)
|
||||||
.fetch_one(&self.pool)
|
.fetch_one(&self.pool)
|
||||||
|
|
|
@ -2,7 +2,8 @@ use sqlx::PgPool;
|
||||||
|
|
||||||
use crate::core::*;
|
use crate::core::*;
|
||||||
use crate::data::Count;
|
use crate::data::Count;
|
||||||
use crate::model::follow::{Follow, NewFollow};
|
use crate::model::AccountId;
|
||||||
|
use crate::model::{Follow, FollowId, NewFollow};
|
||||||
|
|
||||||
pub struct PgFollowDataSource {
|
pub struct PgFollowDataSource {
|
||||||
pool: PgPool,
|
pool: PgPool,
|
||||||
|
@ -13,12 +14,10 @@ impl PgFollowDataSource {
|
||||||
PgFollowDataSource { pool }
|
PgFollowDataSource { pool }
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn by_id(&self, id: Id) -> Result<Follow> {
|
pub async fn by_id(&self, id: FollowId) -> Result<Follow> {
|
||||||
let follow: Follow = sqlx::query_as("SELECT * FROM follows WHERE id = $1")
|
let (follower_id, followee_id) = id.into_inner();
|
||||||
.bind(id)
|
self.by_follower_and_followee(follower_id, followee_id)
|
||||||
.fetch_one(&self.pool)
|
.await
|
||||||
.await?;
|
|
||||||
Ok(follow)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn create(&self, new: NewFollow) -> Result<Follow> {
|
pub async fn create(&self, new: NewFollow) -> Result<Follow> {
|
||||||
|
@ -37,8 +36,8 @@ impl PgFollowDataSource {
|
||||||
|
|
||||||
pub async fn by_follower_and_followee(
|
pub async fn by_follower_and_followee(
|
||||||
&self,
|
&self,
|
||||||
follower_id: Id,
|
follower_id: AccountId,
|
||||||
followee_id: Id,
|
followee_id: AccountId,
|
||||||
) -> Result<Follow> {
|
) -> Result<Follow> {
|
||||||
let follow: Follow =
|
let follow: Follow =
|
||||||
sqlx::query_as("SELECT * FROM follows WHERE follower_id = $1 AND followee_id = $2")
|
sqlx::query_as("SELECT * FROM follows WHERE follower_id = $1 AND followee_id = $2")
|
||||||
|
@ -49,7 +48,7 @@ impl PgFollowDataSource {
|
||||||
Ok(follow)
|
Ok(follow)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn followees_of(&self, account_id: Id) -> Result<Vec<Follow>> {
|
pub async fn followees_of(&self, account_id: AccountId) -> Result<Vec<Follow>> {
|
||||||
let followees: Vec<Follow> = sqlx::query_as("SELECT * FROM follows WHERE follower_id = $1")
|
let followees: Vec<Follow> = sqlx::query_as("SELECT * FROM follows WHERE follower_id = $1")
|
||||||
.bind(account_id)
|
.bind(account_id)
|
||||||
.fetch_all(&self.pool)
|
.fetch_all(&self.pool)
|
||||||
|
@ -57,7 +56,7 @@ impl PgFollowDataSource {
|
||||||
Ok(followees)
|
Ok(followees)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn followers_of(&self, account_id: Id) -> Result<Vec<Follow>> {
|
pub async fn followers_of(&self, account_id: AccountId) -> Result<Vec<Follow>> {
|
||||||
let followers: Vec<Follow> = sqlx::query_as("SELECT * FROM follows WHERE followee_id = $1")
|
let followers: Vec<Follow> = sqlx::query_as("SELECT * FROM follows WHERE followee_id = $1")
|
||||||
.bind(account_id)
|
.bind(account_id)
|
||||||
.fetch_all(&self.pool)
|
.fetch_all(&self.pool)
|
||||||
|
@ -65,7 +64,7 @@ impl PgFollowDataSource {
|
||||||
Ok(followers)
|
Ok(followers)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn following_count_of(&self, account_id: Id) -> Result<u32> {
|
pub async fn following_count_of(&self, account_id: AccountId) -> Result<u32> {
|
||||||
let followee_count: Count =
|
let followee_count: Count =
|
||||||
sqlx::query_as("SELECT COUNT(*) AS count FROM follows WHERE follower_id = $1")
|
sqlx::query_as("SELECT COUNT(*) AS count FROM follows WHERE follower_id = $1")
|
||||||
.bind(account_id)
|
.bind(account_id)
|
||||||
|
@ -74,7 +73,7 @@ impl PgFollowDataSource {
|
||||||
Ok(followee_count.count as u32)
|
Ok(followee_count.count as u32)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn follower_count_of(&self, account_id: Id) -> Result<u32> {
|
pub async fn follower_count_of(&self, account_id: AccountId) -> Result<u32> {
|
||||||
let follower_count: Count =
|
let follower_count: Count =
|
||||||
sqlx::query_as("SELECT COUNT(*) AS count FROM follows WHERE followee_id = $1")
|
sqlx::query_as("SELECT COUNT(*) AS count FROM follows WHERE followee_id = $1")
|
||||||
.bind(account_id)
|
.bind(account_id)
|
||||||
|
|
72
src/data/like/mem.rs
Normal file
72
src/data/like/mem.rs
Normal file
|
@ -0,0 +1,72 @@
|
||||||
|
use crate::data::memcache::{Indexable, MemCache};
|
||||||
|
use crate::model::{AccountId, Like, LikeId, NoteId};
|
||||||
|
|
||||||
|
pub struct MemLikeDataSource {
|
||||||
|
cache: MemCache<LikeId, Like>,
|
||||||
|
counts: MemCache<NoteId, LikeCount>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
struct LikeCount {
|
||||||
|
note_id: NoteId,
|
||||||
|
likes: u64,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl MemLikeDataSource {
|
||||||
|
pub fn new() -> MemLikeDataSource {
|
||||||
|
MemLikeDataSource {
|
||||||
|
cache: MemCache::new(),
|
||||||
|
counts: MemCache::new(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn by_note_and_account(
|
||||||
|
&self,
|
||||||
|
note_id: NoteId,
|
||||||
|
account_id: AccountId,
|
||||||
|
) -> Option<Like> {
|
||||||
|
self.cache.get((note_id, account_id).into()).await
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn store(&self, like: Like) {
|
||||||
|
self.cache.put(like).await;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Delete a like entry from the cache **without decrementing the cached count**.
|
||||||
|
pub async fn delete(&self, note_id: NoteId, account_id: AccountId) {
|
||||||
|
self.cache.del((note_id, account_id).into()).await;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn get_count_by_note(&self, note_id: NoteId) -> Option<u64> {
|
||||||
|
self.counts.get(note_id).await.map(|count| count.likes)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn store_count_by_note(&self, note_id: NoteId, count: u64) {
|
||||||
|
self.counts
|
||||||
|
.put(LikeCount {
|
||||||
|
note_id,
|
||||||
|
likes: count,
|
||||||
|
})
|
||||||
|
.await;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn increment_count(&self, note_id: NoteId) {
|
||||||
|
self.counts.update(note_id, |entry| entry.likes += 1).await;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn decrement_count(&self, note_id: NoteId) {
|
||||||
|
self.counts.update(note_id, |entry| entry.likes -= 1).await;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Indexable<LikeId> for Like {
|
||||||
|
fn get_id(&self) -> LikeId {
|
||||||
|
(self.note_id, self.account_id).into()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Indexable<NoteId> for LikeCount {
|
||||||
|
fn get_id(&self) -> NoteId {
|
||||||
|
self.note_id
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,3 +1,5 @@
|
||||||
pub mod pg;
|
mod mem;
|
||||||
|
mod pg;
|
||||||
|
|
||||||
|
pub use mem::MemLikeDataSource;
|
||||||
pub use pg::PgLikeDataSource;
|
pub use pg::PgLikeDataSource;
|
||||||
|
|
|
@ -2,7 +2,7 @@ use sqlx::PgPool;
|
||||||
|
|
||||||
use crate::core::*;
|
use crate::core::*;
|
||||||
use crate::data::Count;
|
use crate::data::Count;
|
||||||
use crate::model::{Like, NewLike};
|
use crate::model::{AccountId, Like, NewLike, NoteId};
|
||||||
|
|
||||||
pub struct PgLikeDataSource {
|
pub struct PgLikeDataSource {
|
||||||
pool: PgPool,
|
pool: PgPool,
|
||||||
|
@ -13,14 +13,6 @@ impl PgLikeDataSource {
|
||||||
PgLikeDataSource { pool }
|
PgLikeDataSource { pool }
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn by_id(&self, id: Id) -> Result<Like> {
|
|
||||||
let like: Like = sqlx::query_as("SELECT * FROM likes WHERE id = $1")
|
|
||||||
.bind(id)
|
|
||||||
.fetch_one(&self.pool)
|
|
||||||
.await?;
|
|
||||||
Ok(like)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn create(&self, new: NewLike) -> Result<Like> {
|
pub async fn create(&self, new: NewLike) -> Result<Like> {
|
||||||
let like: Like = sqlx::query_as(
|
let like: Like = sqlx::query_as(
|
||||||
"INSERT INTO likes (iri, note_id, account_id)
|
"INSERT INTO likes (iri, note_id, account_id)
|
||||||
|
@ -35,7 +27,7 @@ impl PgLikeDataSource {
|
||||||
Ok(like)
|
Ok(like)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn delete(&self, note_id: Id, account_id: Id) -> Result<()> {
|
pub async fn delete(&self, note_id: NoteId, account_id: AccountId) -> Result<()> {
|
||||||
sqlx::query("DELETE FROM likes WHERE note_id = $1 AND account_id = $2")
|
sqlx::query("DELETE FROM likes WHERE note_id = $1 AND account_id = $2")
|
||||||
.bind(note_id)
|
.bind(note_id)
|
||||||
.bind(account_id)
|
.bind(account_id)
|
||||||
|
@ -44,7 +36,7 @@ impl PgLikeDataSource {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn count_by_note(&self, note_id: Id) -> Result<u64> {
|
pub async fn count_by_note(&self, note_id: NoteId) -> Result<u64> {
|
||||||
let result: Count =
|
let result: Count =
|
||||||
sqlx::query_as("SELECT COUNT(*) as count FROM likes WHERE note_id = $1")
|
sqlx::query_as("SELECT COUNT(*) as count FROM likes WHERE note_id = $1")
|
||||||
.bind(note_id)
|
.bind(note_id)
|
||||||
|
@ -53,7 +45,11 @@ impl PgLikeDataSource {
|
||||||
Ok(result.count as u64)
|
Ok(result.count as u64)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn by_note_and_account(&self, note_id: Id, account_id: Id) -> Result<Like> {
|
pub async fn by_note_and_account(
|
||||||
|
&self,
|
||||||
|
note_id: NoteId,
|
||||||
|
account_id: AccountId,
|
||||||
|
) -> Result<Like> {
|
||||||
let like: Like =
|
let like: Like =
|
||||||
sqlx::query_as("SELECT * FROM likes WHERE note_id = $1 AND account_id = $2")
|
sqlx::query_as("SELECT * FROM likes WHERE note_id = $1 AND account_id = $2")
|
||||||
.bind(note_id)
|
.bind(note_id)
|
||||||
|
|
|
@ -137,14 +137,16 @@ impl Hasher for Djb2 {
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
|
type TestId = Id<TestEntry>;
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq)]
|
#[derive(Clone, Debug, PartialEq)]
|
||||||
struct TestEntry {
|
struct TestEntry {
|
||||||
id: Id,
|
id: TestId,
|
||||||
data: i32,
|
data: i32,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Indexable<Id> for TestEntry {
|
impl Indexable<TestId> for TestEntry {
|
||||||
fn get_id(&self) -> Id {
|
fn get_id(&self) -> TestId {
|
||||||
self.id
|
self.id
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -152,14 +154,16 @@ mod tests {
|
||||||
#[actix_web::test]
|
#[actix_web::test]
|
||||||
async fn store_stuff() {
|
async fn store_stuff() {
|
||||||
let cache = MemCache::with_capacity(256);
|
let cache = MemCache::with_capacity(256);
|
||||||
for id in 0..1024 {
|
for i in 0i64..1024i64 {
|
||||||
|
let id = i.into();
|
||||||
let entry = TestEntry { id, data: 0 };
|
let entry = TestEntry { id, data: 0 };
|
||||||
cache.put(entry.clone()).await;
|
cache.put(entry.clone()).await;
|
||||||
assert_eq!(cache.get(id).await, Some(entry));
|
assert_eq!(cache.get(id).await, Some(entry));
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut had_entries = false;
|
let mut had_entries = false;
|
||||||
for id in 0..1024 {
|
for i in 0..1024 {
|
||||||
|
let id = i.into();
|
||||||
let entry = cache.del(id).await;
|
let entry = cache.del(id).await;
|
||||||
assert_eq!(cache.get(id).await, None);
|
assert_eq!(cache.get(id).await, None);
|
||||||
if let Some(entry) = entry {
|
if let Some(entry) = entry {
|
||||||
|
@ -172,19 +176,19 @@ mod tests {
|
||||||
|
|
||||||
#[actix_web::test]
|
#[actix_web::test]
|
||||||
async fn update_stuff() {
|
async fn update_stuff() {
|
||||||
const ID: Id = 420;
|
let id = 420.into();
|
||||||
let cache = MemCache::with_capacity(256);
|
let cache = MemCache::with_capacity(256);
|
||||||
cache.put(TestEntry { id: ID, data: 69 }).await;
|
cache.put(TestEntry { id, data: 69 }).await;
|
||||||
cache.update(ID, |entry| entry.data = 1312).await;
|
cache.update(id, |entry| entry.data = 1312).await;
|
||||||
assert_eq!(cache.get(ID).await, Some(TestEntry { id: ID, data: 1312 }));
|
assert_eq!(cache.get(id).await, Some(TestEntry { id, data: 1312 }));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[actix_web::test]
|
#[actix_web::test]
|
||||||
#[should_panic]
|
#[should_panic]
|
||||||
async fn mutate_id() {
|
async fn mutate_id() {
|
||||||
const ID: Id = 420;
|
let id = 420.into();
|
||||||
let cache = MemCache::with_capacity(256);
|
let cache = MemCache::with_capacity(256);
|
||||||
cache.put(TestEntry { id: ID, data: 69 }).await;
|
cache.put(TestEntry { id, data: 69 }).await;
|
||||||
cache.update(ID, |entry| entry.id = 1312).await;
|
cache.update(id, |entry| entry.id = 1312.into()).await;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,12 +1,12 @@
|
||||||
/// Small support utility for in-memory cache.
|
/// Small support utility for in-memory cache.
|
||||||
pub mod memcache;
|
pub mod memcache;
|
||||||
|
|
||||||
pub mod account;
|
mod account;
|
||||||
pub mod follow;
|
mod follow;
|
||||||
pub mod like;
|
mod like;
|
||||||
pub mod note;
|
mod note;
|
||||||
pub mod object;
|
mod object;
|
||||||
pub mod user;
|
mod user;
|
||||||
|
|
||||||
pub use account::*;
|
pub use account::*;
|
||||||
pub use follow::*;
|
pub use follow::*;
|
||||||
|
|
|
@ -1,10 +1,8 @@
|
||||||
use crate::core::*;
|
|
||||||
|
|
||||||
use crate::data::memcache::{Indexable, MemCache};
|
use crate::data::memcache::{Indexable, MemCache};
|
||||||
use crate::model::Note;
|
use crate::model::{Note, NoteId};
|
||||||
|
|
||||||
pub struct MemNoteDataSource {
|
pub struct MemNoteDataSource {
|
||||||
cache: MemCache<Id, Note>,
|
cache: MemCache<NoteId, Note>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl MemNoteDataSource {
|
impl MemNoteDataSource {
|
||||||
|
@ -14,7 +12,7 @@ impl MemNoteDataSource {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn by_id(&self, id: Id) -> Option<Note> {
|
pub async fn by_id(&self, id: NoteId) -> Option<Note> {
|
||||||
self.cache.get(id).await
|
self.cache.get(id).await
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -22,13 +20,13 @@ impl MemNoteDataSource {
|
||||||
self.cache.put(note).await
|
self.cache.put(note).await
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn delete(&self, id: Id) {
|
pub async fn delete(&self, id: NoteId) {
|
||||||
self.cache.del(id).await;
|
self.cache.del(id).await;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Indexable<Id> for Note {
|
impl Indexable<NoteId> for Note {
|
||||||
fn get_id(&self) -> Id {
|
fn get_id(&self) -> NoteId {
|
||||||
self.id
|
self.id
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,7 +3,7 @@ use sqlx::PgPool;
|
||||||
|
|
||||||
use crate::core::*;
|
use crate::core::*;
|
||||||
use crate::data::Count;
|
use crate::data::Count;
|
||||||
use crate::model::note::{NewNote, Note};
|
use crate::model::{AccountId, NewNote, Note, NoteId};
|
||||||
|
|
||||||
pub struct PgNoteDataSource {
|
pub struct PgNoteDataSource {
|
||||||
pool: PgPool,
|
pool: PgPool,
|
||||||
|
@ -14,7 +14,7 @@ impl PgNoteDataSource {
|
||||||
PgNoteDataSource { pool }
|
PgNoteDataSource { pool }
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn by_id(&self, id: Id) -> Result<Note> {
|
pub async fn by_id(&self, id: NoteId) -> Result<Note> {
|
||||||
let note: Note = sqlx::query_as("SELECT * FROM notes WHERE id = $1")
|
let note: Note = sqlx::query_as("SELECT * FROM notes WHERE id = $1")
|
||||||
.bind(id)
|
.bind(id)
|
||||||
.fetch_one(&self.pool)
|
.fetch_one(&self.pool)
|
||||||
|
@ -22,7 +22,7 @@ impl PgNoteDataSource {
|
||||||
Ok(note)
|
Ok(note)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn by_account(&self, id: Id, since: NaiveDateTime) -> Result<Vec<Note>> {
|
pub async fn by_account(&self, id: AccountId, since: NaiveDateTime) -> Result<Vec<Note>> {
|
||||||
let notes: Vec<Note> = sqlx::query_as(
|
let notes: Vec<Note> = sqlx::query_as(
|
||||||
"SELECT * FROM notes
|
"SELECT * FROM notes
|
||||||
WHERE account_id = $1 AND created_at < $1
|
WHERE account_id = $1 AND created_at < $1
|
||||||
|
@ -61,7 +61,7 @@ impl PgNoteDataSource {
|
||||||
Ok(note)
|
Ok(note)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn delete(&self, id: Id) -> Result<()> {
|
pub async fn delete(&self, id: NoteId) -> Result<()> {
|
||||||
sqlx::query("DELETE FROM notes WHERE id = $1")
|
sqlx::query("DELETE FROM notes WHERE id = $1")
|
||||||
.bind(id)
|
.bind(id)
|
||||||
.execute(&self.pool)
|
.execute(&self.pool)
|
||||||
|
@ -69,7 +69,7 @@ impl PgNoteDataSource {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn count_by_account(&self, account_id: Id) -> Result<u32> {
|
pub async fn count_by_account(&self, account_id: AccountId) -> Result<u32> {
|
||||||
let count: Count =
|
let count: Count =
|
||||||
sqlx::query_as("SELECT COUNT(*) AS count FROM notes WHERE account_id = $1")
|
sqlx::query_as("SELECT COUNT(*) AS count FROM notes WHERE account_id = $1")
|
||||||
.bind(account_id)
|
.bind(account_id)
|
||||||
|
|
|
@ -1,10 +1,8 @@
|
||||||
use uuid::Uuid;
|
|
||||||
|
|
||||||
use crate::data::memcache::{Indexable, MemCache};
|
use crate::data::memcache::{Indexable, MemCache};
|
||||||
use crate::model::Object;
|
use crate::model::{Object, ObjectId};
|
||||||
|
|
||||||
pub struct MemObjectDataSource {
|
pub struct MemObjectDataSource {
|
||||||
cache: MemCache<Uuid, Object>,
|
cache: MemCache<ObjectId, Object>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl MemObjectDataSource {
|
impl MemObjectDataSource {
|
||||||
|
@ -14,7 +12,7 @@ impl MemObjectDataSource {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn by_id(&self, id: Uuid) -> Option<Object> {
|
pub async fn by_id(&self, id: ObjectId) -> Option<Object> {
|
||||||
self.cache.get(id).await
|
self.cache.get(id).await
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -23,8 +21,8 @@ impl MemObjectDataSource {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Indexable<Uuid> for Object {
|
impl Indexable<ObjectId> for Object {
|
||||||
fn get_id(&self) -> Uuid {
|
fn get_id(&self) -> ObjectId {
|
||||||
self.id
|
self.id
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,8 +1,7 @@
|
||||||
use sqlx::types::Uuid;
|
|
||||||
use sqlx::PgPool;
|
use sqlx::PgPool;
|
||||||
|
|
||||||
use crate::core::*;
|
use crate::core::*;
|
||||||
use crate::model::object::*;
|
use crate::model::{NewObject, Object, ObjectId};
|
||||||
|
|
||||||
pub struct PgObjectDataSource {
|
pub struct PgObjectDataSource {
|
||||||
pool: PgPool,
|
pool: PgPool,
|
||||||
|
@ -37,7 +36,7 @@ impl PgObjectDataSource {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn by_id(&self, id: Uuid) -> Result<Object> {
|
pub async fn by_id(&self, id: ObjectId) -> Result<Object> {
|
||||||
let object: Object = sqlx::query_as("SELECT * FROM objects WHERE id = $1")
|
let object: Object = sqlx::query_as("SELECT * FROM objects WHERE id = $1")
|
||||||
.bind(id)
|
.bind(id)
|
||||||
.fetch_one(&self.pool)
|
.fetch_one(&self.pool)
|
||||||
|
|
|
@ -1,10 +1,8 @@
|
||||||
use crate::core::*;
|
|
||||||
|
|
||||||
use crate::data::memcache::{Indexable, MemCache};
|
use crate::data::memcache::{Indexable, MemCache};
|
||||||
use crate::model::User;
|
use crate::model::{User, UserId};
|
||||||
|
|
||||||
pub struct MemUserDataSource {
|
pub struct MemUserDataSource {
|
||||||
cache: MemCache<Id, User>,
|
cache: MemCache<UserId, User>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl MemUserDataSource {
|
impl MemUserDataSource {
|
||||||
|
@ -14,7 +12,7 @@ impl MemUserDataSource {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn by_id(&self, id: Id) -> Option<User> {
|
pub async fn by_id(&self, id: UserId) -> Option<User> {
|
||||||
self.cache.get(id).await
|
self.cache.get(id).await
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -23,8 +21,8 @@ impl MemUserDataSource {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Indexable<Id> for User {
|
impl Indexable<UserId> for User {
|
||||||
fn get_id(&self) -> Id {
|
fn get_id(&self) -> UserId {
|
||||||
self.id
|
self.id
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,7 +2,7 @@ use serde;
|
||||||
use sqlx::{Executor, PgPool};
|
use sqlx::{Executor, PgPool};
|
||||||
|
|
||||||
use crate::core::*;
|
use crate::core::*;
|
||||||
use crate::model::user::{NewUser, User};
|
use crate::model::{AccountId, NewUser, User, UserId};
|
||||||
|
|
||||||
pub struct PgUserDataSource {
|
pub struct PgUserDataSource {
|
||||||
pool: PgPool,
|
pool: PgPool,
|
||||||
|
@ -13,7 +13,7 @@ impl PgUserDataSource {
|
||||||
PgUserDataSource { pool }
|
PgUserDataSource { pool }
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn by_id(&self, id: Id) -> Result<User> {
|
pub async fn by_id(&self, id: UserId) -> Result<User> {
|
||||||
let user: User = sqlx::query_as("SELECT * FROM users WHERE id = $1")
|
let user: User = sqlx::query_as("SELECT * FROM users WHERE id = $1")
|
||||||
.bind(id)
|
.bind(id)
|
||||||
.fetch_one(&self.pool)
|
.fetch_one(&self.pool)
|
||||||
|
@ -21,7 +21,7 @@ impl PgUserDataSource {
|
||||||
Ok(user)
|
Ok(user)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn by_account(&self, account_id: Id) -> Result<User> {
|
pub async fn by_account(&self, account_id: AccountId) -> Result<User> {
|
||||||
let user: User = sqlx::query_as("SELECT * FROM users WHERE account_id = $1")
|
let user: User = sqlx::query_as("SELECT * FROM users WHERE account_id = $1")
|
||||||
.bind(account_id)
|
.bind(account_id)
|
||||||
.fetch_one(&self.pool)
|
.fetch_one(&self.pool)
|
||||||
|
|
|
@ -29,7 +29,7 @@ impl Account {
|
||||||
let following_count = state.repo.follows.following_count_of(model.id).await?;
|
let following_count = state.repo.follows.following_count_of(model.id).await?;
|
||||||
let statuses_count = state.repo.notes.count_by_account(model.id).await?;
|
let statuses_count = state.repo.notes.count_by_account(model.id).await?;
|
||||||
Ok(Account {
|
Ok(Account {
|
||||||
id: format!("{}", model.id),
|
id: model.id.to_string(),
|
||||||
username: model.name.clone(),
|
username: model.name.clone(),
|
||||||
display_name: model.display_name.as_ref().unwrap_or(&model.name).clone(),
|
display_name: model.display_name.as_ref().unwrap_or(&model.name).clone(),
|
||||||
created_at: DateTime::from_utc(model.created_at, Utc),
|
created_at: DateTime::from_utc(model.created_at, Utc),
|
||||||
|
|
|
@ -39,7 +39,7 @@ impl Status {
|
||||||
};
|
};
|
||||||
let author = state.repo.accounts.by_id(model.account_id).await?;
|
let author = state.repo.accounts.by_id(model.account_id).await?;
|
||||||
Ok(Status {
|
Ok(Status {
|
||||||
id: format!("{}", model.id),
|
id: model.id.to_string(),
|
||||||
created_at: DateTime::from_utc(model.created_at, Utc),
|
created_at: DateTime::from_utc(model.created_at, Utc),
|
||||||
sensitive: model.sensitive,
|
sensitive: model.sensitive,
|
||||||
spoiler_text: model.summary.clone().unwrap_or_default(),
|
spoiler_text: model.summary.clone().unwrap_or_default(),
|
||||||
|
|
|
@ -2,12 +2,14 @@ use chrono::prelude::*;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use sqlx::FromRow;
|
use sqlx::FromRow;
|
||||||
|
|
||||||
use crate::core::Id;
|
use crate::core::*;
|
||||||
use crate::util::validate::{ResultBuilder, Validate};
|
use crate::util::validate::{ResultBuilder, Validate};
|
||||||
|
|
||||||
|
pub type AccountId = Id<Account>;
|
||||||
|
|
||||||
#[derive(Clone, Deserialize, Serialize, FromRow)]
|
#[derive(Clone, Deserialize, Serialize, FromRow)]
|
||||||
pub struct Account {
|
pub struct Account {
|
||||||
pub id: Id,
|
pub id: AccountId,
|
||||||
pub iri: Option<String>,
|
pub iri: Option<String>,
|
||||||
pub name: String,
|
pub name: String,
|
||||||
pub domain: String,
|
pub domain: String,
|
||||||
|
@ -23,8 +25,8 @@ pub struct NewAccount {
|
||||||
pub display_name: Option<String>,
|
pub display_name: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<Account> for Id {
|
impl From<Account> for AccountId {
|
||||||
fn from(account: Account) -> Id {
|
fn from(account: Account) -> AccountId {
|
||||||
account.id
|
account.id
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -33,7 +35,7 @@ impl Validate for NewAccount {
|
||||||
fn validate(&self, builder: ResultBuilder) -> ResultBuilder {
|
fn validate(&self, builder: ResultBuilder) -> ResultBuilder {
|
||||||
builder
|
builder
|
||||||
.field(&self.name, "name", |f| {
|
.field(&self.name, "name", |f| {
|
||||||
f.check("Must not be empty", |v| v.len() > 0)
|
f.check("Must not be empty", |v| !v.is_empty())
|
||||||
.check("Must be at most 16 characters long", |v| v.len() <= 16)
|
.check("Must be at most 16 characters long", |v| v.len() <= 16)
|
||||||
})
|
})
|
||||||
.field(&self.display_name, "display_name", |f| {
|
.field(&self.display_name, "display_name", |f| {
|
||||||
|
|
|
@ -3,26 +3,28 @@ use serde::{Deserialize, Serialize};
|
||||||
use sqlx::FromRow;
|
use sqlx::FromRow;
|
||||||
|
|
||||||
use crate::core::*;
|
use crate::core::*;
|
||||||
|
use crate::model::account::AccountId;
|
||||||
use crate::util::validate::{ResultBuilder, Validate};
|
use crate::util::validate::{ResultBuilder, Validate};
|
||||||
|
|
||||||
|
pub type FollowId = Id<Follow, (AccountId, AccountId)>;
|
||||||
|
|
||||||
#[derive(Deserialize, Serialize, FromRow)]
|
#[derive(Deserialize, Serialize, FromRow)]
|
||||||
pub struct Follow {
|
pub struct Follow {
|
||||||
pub id: Id,
|
|
||||||
pub iri: Option<String>,
|
pub iri: Option<String>,
|
||||||
pub follower_id: Id,
|
pub follower_id: AccountId,
|
||||||
pub followee_id: Id,
|
pub followee_id: AccountId,
|
||||||
pub created_at: NaiveDateTime,
|
pub created_at: NaiveDateTime,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct NewFollow {
|
pub struct NewFollow {
|
||||||
pub iri: Option<String>,
|
pub iri: Option<String>,
|
||||||
pub follower_id: Id,
|
pub follower_id: AccountId,
|
||||||
pub followee_id: Id,
|
pub followee_id: AccountId,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<Follow> for Id {
|
impl From<Follow> for FollowId {
|
||||||
fn from(follow: Follow) -> Id {
|
fn from(follow: Follow) -> FollowId {
|
||||||
follow.id
|
(follow.follower_id, follow.followee_id).into()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -3,26 +3,28 @@ use serde::{Deserialize, Serialize};
|
||||||
use sqlx::FromRow;
|
use sqlx::FromRow;
|
||||||
|
|
||||||
use crate::core::*;
|
use crate::core::*;
|
||||||
|
use crate::model::{AccountId, NoteId};
|
||||||
use crate::util::validate::{ResultBuilder, Validate};
|
use crate::util::validate::{ResultBuilder, Validate};
|
||||||
|
|
||||||
#[derive(Deserialize, Serialize, FromRow)]
|
pub type LikeId = Id<Like, (NoteId, AccountId)>;
|
||||||
|
|
||||||
|
#[derive(Clone, Deserialize, Serialize, FromRow)]
|
||||||
pub struct Like {
|
pub struct Like {
|
||||||
pub id: Id,
|
|
||||||
pub iri: Option<String>,
|
pub iri: Option<String>,
|
||||||
pub note_id: Id,
|
pub note_id: NoteId,
|
||||||
pub account_id: Id,
|
pub account_id: AccountId,
|
||||||
pub created_at: NaiveDateTime,
|
pub created_at: NaiveDateTime,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct NewLike {
|
pub struct NewLike {
|
||||||
pub iri: Option<String>,
|
pub iri: Option<String>,
|
||||||
pub note_id: Id,
|
pub note_id: NoteId,
|
||||||
pub account_id: Id,
|
pub account_id: AccountId,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<Like> for Id {
|
impl From<Like> for LikeId {
|
||||||
fn from(like: Like) -> Id {
|
fn from(val: Like) -> LikeId {
|
||||||
like.id
|
(val.note_id, val.account_id).into()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,13 +1,13 @@
|
||||||
pub mod account;
|
mod account;
|
||||||
pub mod follow;
|
mod follow;
|
||||||
pub mod like;
|
mod like;
|
||||||
pub mod note;
|
mod note;
|
||||||
pub mod object;
|
mod object;
|
||||||
pub mod user;
|
mod user;
|
||||||
|
|
||||||
pub use account::{Account, NewAccount};
|
pub use account::*;
|
||||||
pub use follow::{Follow, NewFollow};
|
pub use follow::*;
|
||||||
pub use like::{Like, NewLike};
|
pub use like::*;
|
||||||
pub use note::{NewNote, Note};
|
pub use note::*;
|
||||||
pub use object::{NewObject, Object};
|
pub use object::*;
|
||||||
pub use user::{NewUser, User};
|
pub use user::*;
|
||||||
|
|
|
@ -3,12 +3,15 @@ use serde::{Deserialize, Serialize};
|
||||||
use sqlx::FromRow;
|
use sqlx::FromRow;
|
||||||
|
|
||||||
use crate::core::*;
|
use crate::core::*;
|
||||||
|
use crate::model::AccountId;
|
||||||
use crate::util::validate::{ResultBuilder, Validate};
|
use crate::util::validate::{ResultBuilder, Validate};
|
||||||
|
|
||||||
|
pub type NoteId = Id<Note>;
|
||||||
|
|
||||||
#[derive(Clone, Deserialize, Serialize, FromRow)]
|
#[derive(Clone, Deserialize, Serialize, FromRow)]
|
||||||
pub struct Note {
|
pub struct Note {
|
||||||
pub id: Id,
|
pub id: NoteId,
|
||||||
pub account_id: Id,
|
pub account_id: AccountId,
|
||||||
pub iri: Option<String>,
|
pub iri: Option<String>,
|
||||||
pub content: String,
|
pub content: String,
|
||||||
pub source: Option<String>,
|
pub source: Option<String>,
|
||||||
|
@ -20,7 +23,7 @@ pub struct Note {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct NewNote {
|
pub struct NewNote {
|
||||||
pub account_id: Id,
|
pub account_id: AccountId,
|
||||||
pub iri: Option<String>,
|
pub iri: Option<String>,
|
||||||
pub content: String,
|
pub content: String,
|
||||||
pub source: Option<String>,
|
pub source: Option<String>,
|
||||||
|
@ -29,9 +32,9 @@ pub struct NewNote {
|
||||||
pub sensitive: bool,
|
pub sensitive: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<Note> for Id {
|
impl From<Note> for NoteId {
|
||||||
fn from(status: Note) -> Id {
|
fn from(val: Note) -> NoteId {
|
||||||
status.id
|
val.id
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -2,9 +2,13 @@ use chrono::NaiveDateTime;
|
||||||
use sqlx::types::Uuid;
|
use sqlx::types::Uuid;
|
||||||
use sqlx::FromRow;
|
use sqlx::FromRow;
|
||||||
|
|
||||||
|
use crate::core::*;
|
||||||
|
|
||||||
|
pub type ObjectId = Id<Object, Uuid>;
|
||||||
|
|
||||||
#[derive(Clone, FromRow)]
|
#[derive(Clone, FromRow)]
|
||||||
pub struct Object {
|
pub struct Object {
|
||||||
pub id: Uuid,
|
pub id: ObjectId,
|
||||||
pub iri: String,
|
pub iri: String,
|
||||||
pub data: String,
|
pub data: String,
|
||||||
pub created_at: NaiveDateTime,
|
pub created_at: NaiveDateTime,
|
||||||
|
@ -12,7 +16,13 @@ pub struct Object {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct NewObject {
|
pub struct NewObject {
|
||||||
pub id: Uuid,
|
pub id: ObjectId,
|
||||||
pub iri: String,
|
pub iri: String,
|
||||||
pub data: String,
|
pub data: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl From<Object> for ObjectId {
|
||||||
|
fn from(val: Object) -> ObjectId {
|
||||||
|
val.id
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -2,12 +2,15 @@ use serde::{Deserialize, Serialize};
|
||||||
use sqlx::FromRow;
|
use sqlx::FromRow;
|
||||||
|
|
||||||
use crate::core::*;
|
use crate::core::*;
|
||||||
|
use crate::model::AccountId;
|
||||||
use crate::util::validate::{ResultBuilder, Validate};
|
use crate::util::validate::{ResultBuilder, Validate};
|
||||||
|
|
||||||
|
pub type UserId = Id<User>;
|
||||||
|
|
||||||
#[derive(Clone, Deserialize, Serialize, FromRow)]
|
#[derive(Clone, Deserialize, Serialize, FromRow)]
|
||||||
pub struct User {
|
pub struct User {
|
||||||
pub id: Id,
|
pub id: UserId,
|
||||||
pub account_id: Id,
|
pub account_id: AccountId,
|
||||||
pub email: String,
|
pub email: String,
|
||||||
pub password: String,
|
pub password: String,
|
||||||
pub reason: Option<String>,
|
pub reason: Option<String>,
|
||||||
|
@ -16,15 +19,15 @@ pub struct User {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct NewUser {
|
pub struct NewUser {
|
||||||
pub account_id: Id,
|
pub account_id: AccountId,
|
||||||
pub email: String,
|
pub email: String,
|
||||||
pub password: String,
|
pub password: String,
|
||||||
pub reason: Option<String>,
|
pub reason: Option<String>,
|
||||||
pub locale: String,
|
pub locale: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<User> for Id {
|
impl From<User> for UserId {
|
||||||
fn from(user: User) -> Id {
|
fn from(user: User) -> UserId {
|
||||||
user.id
|
user.id
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -32,7 +35,7 @@ impl From<User> for Id {
|
||||||
impl Validate for NewUser {
|
impl Validate for NewUser {
|
||||||
fn validate(&self, builder: ResultBuilder) -> ResultBuilder {
|
fn validate(&self, builder: ResultBuilder) -> ResultBuilder {
|
||||||
builder.field(&self.email, "email", |f| {
|
builder.field(&self.email, "email", |f| {
|
||||||
f.check("Email must not be empty", |v| v.len() > 0)
|
f.check("Email must not be empty", |v| !v.is_empty())
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
use sqlx::PgPool;
|
use sqlx::PgPool;
|
||||||
|
|
||||||
use crate::core::*;
|
use crate::core::*;
|
||||||
use crate::data::account::{MemAccountDataSource, PgAccountDataSource};
|
use crate::data::{MemAccountDataSource, PgAccountDataSource};
|
||||||
use crate::model::account::{Account, NewAccount};
|
use crate::model::{Account, AccountId, NewAccount};
|
||||||
|
|
||||||
pub struct AccountRepo {
|
pub struct AccountRepo {
|
||||||
db: PgAccountDataSource,
|
db: PgAccountDataSource,
|
||||||
|
@ -19,7 +19,7 @@ impl AccountRepo {
|
||||||
|
|
||||||
pub async fn by_id<I>(&self, id: I) -> Result<Account>
|
pub async fn by_id<I>(&self, id: I) -> Result<Account>
|
||||||
where
|
where
|
||||||
I: Into<Id> + Send,
|
I: Into<AccountId> + Send,
|
||||||
{
|
{
|
||||||
let id = id.into();
|
let id = id.into();
|
||||||
match self.mem.by_id(id).await {
|
match self.mem.by_id(id).await {
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
use sqlx::PgPool;
|
use sqlx::PgPool;
|
||||||
|
|
||||||
use crate::core::*;
|
use crate::core::*;
|
||||||
use crate::data::follow::PgFollowDataSource;
|
use crate::data::PgFollowDataSource;
|
||||||
use crate::model::follow::{Follow, NewFollow};
|
use crate::model::{AccountId, Follow, FollowId, NewFollow};
|
||||||
|
|
||||||
pub struct FollowRepo {
|
pub struct FollowRepo {
|
||||||
db: PgFollowDataSource,
|
db: PgFollowDataSource,
|
||||||
|
@ -17,7 +17,7 @@ impl FollowRepo {
|
||||||
|
|
||||||
pub async fn by_id<I>(&self, id: I) -> Result<Follow>
|
pub async fn by_id<I>(&self, id: I) -> Result<Follow>
|
||||||
where
|
where
|
||||||
I: Into<Id> + Send,
|
I: Into<FollowId> + Send,
|
||||||
{
|
{
|
||||||
self.db.by_id(id.into()).await
|
self.db.by_id(id.into()).await
|
||||||
}
|
}
|
||||||
|
@ -35,8 +35,8 @@ impl FollowRepo {
|
||||||
followee_id: J,
|
followee_id: J,
|
||||||
) -> Result<Follow>
|
) -> Result<Follow>
|
||||||
where
|
where
|
||||||
I: Into<Id> + Send,
|
I: Into<AccountId> + Send,
|
||||||
J: Into<Id> + Send,
|
J: Into<AccountId> + Send,
|
||||||
{
|
{
|
||||||
let (follower_id, followee_id) = (follower_id.into(), followee_id.into());
|
let (follower_id, followee_id) = (follower_id.into(), followee_id.into());
|
||||||
self.db
|
self.db
|
||||||
|
@ -46,8 +46,8 @@ impl FollowRepo {
|
||||||
|
|
||||||
pub async fn follows<I, J>(&self, follower_id: I, followee_id: J) -> Result<bool>
|
pub async fn follows<I, J>(&self, follower_id: I, followee_id: J) -> Result<bool>
|
||||||
where
|
where
|
||||||
I: Into<Id> + Send,
|
I: Into<AccountId> + Send,
|
||||||
J: Into<Id> + Send,
|
J: Into<AccountId> + Send,
|
||||||
{
|
{
|
||||||
match self
|
match self
|
||||||
.by_follower_and_followee(follower_id, followee_id)
|
.by_follower_and_followee(follower_id, followee_id)
|
||||||
|
@ -61,28 +61,28 @@ impl FollowRepo {
|
||||||
|
|
||||||
pub async fn followees_of<I>(&self, account_id: I) -> Result<Vec<Follow>>
|
pub async fn followees_of<I>(&self, account_id: I) -> Result<Vec<Follow>>
|
||||||
where
|
where
|
||||||
I: Into<Id> + Send,
|
I: Into<AccountId> + Send,
|
||||||
{
|
{
|
||||||
self.db.followees_of(account_id.into()).await
|
self.db.followees_of(account_id.into()).await
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn followers_of<I>(&self, account_id: I) -> Result<Vec<Follow>>
|
pub async fn followers_of<I>(&self, account_id: I) -> Result<Vec<Follow>>
|
||||||
where
|
where
|
||||||
I: Into<Id> + Send,
|
I: Into<AccountId> + Send,
|
||||||
{
|
{
|
||||||
self.db.followers_of(account_id.into()).await
|
self.db.followers_of(account_id.into()).await
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn following_count_of<I>(&self, account_id: I) -> Result<u32>
|
pub async fn following_count_of<I>(&self, account_id: I) -> Result<u32>
|
||||||
where
|
where
|
||||||
I: Into<Id> + Send,
|
I: Into<AccountId> + Send,
|
||||||
{
|
{
|
||||||
self.db.following_count_of(account_id.into()).await
|
self.db.following_count_of(account_id.into()).await
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn follower_count_of<I>(&self, account_id: I) -> Result<u32>
|
pub async fn follower_count_of<I>(&self, account_id: I) -> Result<u32>
|
||||||
where
|
where
|
||||||
I: Into<Id> + Send,
|
I: Into<AccountId> + Send,
|
||||||
{
|
{
|
||||||
self.db.follower_count_of(account_id.into()).await
|
self.db.follower_count_of(account_id.into()).await
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,57 +1,72 @@
|
||||||
use sqlx::PgPool;
|
use sqlx::PgPool;
|
||||||
|
|
||||||
use crate::core::*;
|
use crate::core::*;
|
||||||
use crate::data::PgLikeDataSource;
|
use crate::data::{MemLikeDataSource, PgLikeDataSource};
|
||||||
use crate::model::{Like, NewLike};
|
use crate::model::{AccountId, Like, NewLike, NoteId};
|
||||||
|
|
||||||
pub struct LikeRepo {
|
pub struct LikeRepo {
|
||||||
db: PgLikeDataSource,
|
db: PgLikeDataSource,
|
||||||
|
mem: MemLikeDataSource,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl LikeRepo {
|
impl LikeRepo {
|
||||||
pub fn new(db_pool: PgPool) -> LikeRepo {
|
pub fn new(db_pool: PgPool) -> LikeRepo {
|
||||||
LikeRepo {
|
LikeRepo {
|
||||||
db: PgLikeDataSource::new(db_pool),
|
db: PgLikeDataSource::new(db_pool),
|
||||||
|
mem: MemLikeDataSource::new(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn by_id<I>(&self, id: I) -> Result<Like>
|
|
||||||
where
|
|
||||||
I: Into<Id> + Send,
|
|
||||||
{
|
|
||||||
self.db.by_id(id.into()).await
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn create<T, E>(&self, new: T) -> Result<Like>
|
pub async fn create<T, E>(&self, new: T) -> Result<Like>
|
||||||
where
|
where
|
||||||
T: TryInto<Sane<NewLike>, Error = E> + Send,
|
T: TryInto<Sane<NewLike>, Error = E> + Send,
|
||||||
E: Into<Error>,
|
E: Into<Error>,
|
||||||
{
|
{
|
||||||
let new = new.try_into().map_err(|e| e.into())?.inner();
|
let new = new.try_into().map_err(|e| e.into())?.inner();
|
||||||
self.db.create(new).await
|
let like = self.db.create(new).await?;
|
||||||
|
self.mem.store(like.clone()).await;
|
||||||
|
self.mem.increment_count(like.note_id).await;
|
||||||
|
Ok(like)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn delete<N, A>(&self, note_id: N, account_id: A) -> Result<()>
|
pub async fn delete<N, A>(&self, note_id: N, account_id: A) -> Result<()>
|
||||||
where
|
where
|
||||||
N: Into<Id>,
|
N: Into<NoteId>,
|
||||||
A: Into<Id>,
|
A: Into<AccountId>,
|
||||||
{
|
{
|
||||||
self.db.delete(note_id.into(), account_id.into()).await
|
let (note_id, account_id) = (note_id.into(), account_id.into());
|
||||||
|
self.db.delete(note_id, account_id).await?;
|
||||||
|
self.mem.delete(note_id, account_id).await;
|
||||||
|
self.mem.decrement_count(note_id).await;
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn count_by_note<I>(&self, note_id: I) -> Result<u64>
|
pub async fn count_by_note<I>(&self, note_id: I) -> Result<u64>
|
||||||
where
|
where
|
||||||
I: Into<Id> + Send,
|
I: Into<NoteId> + Send,
|
||||||
{
|
{
|
||||||
self.db.count_by_note(note_id.into()).await
|
let note_id = note_id.into();
|
||||||
|
if let Some(count) = self.mem.get_count_by_note(note_id).await {
|
||||||
|
return Ok(count);
|
||||||
|
}
|
||||||
|
|
||||||
|
let count = self.db.count_by_note(note_id).await?;
|
||||||
|
self.mem.store_count_by_note(note_id, count).await;
|
||||||
|
Ok(count)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn by_note_and_account<N, A>(&self, note_id: N, account_id: A) -> Result<Like>
|
pub async fn by_note_and_account<N, A>(&self, note_id: N, account_id: A) -> Result<Like>
|
||||||
where
|
where
|
||||||
N: Into<Id>,
|
N: Into<NoteId>,
|
||||||
A: Into<Id>,
|
A: Into<AccountId>,
|
||||||
{
|
{
|
||||||
let (note_id, account_id) = (note_id.into(), account_id.into());
|
let (note_id, account_id) = (note_id.into(), account_id.into());
|
||||||
self.db.by_note_and_account(note_id, account_id).await
|
if let Some(like) = self.mem.by_note_and_account(note_id, account_id).await {
|
||||||
|
return Ok(like);
|
||||||
|
}
|
||||||
|
|
||||||
|
let like = self.db.by_note_and_account(note_id, account_id).await?;
|
||||||
|
self.mem.store(like.clone()).await;
|
||||||
|
Ok(like)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,18 +1,18 @@
|
||||||
use sqlx::PgPool;
|
use sqlx::PgPool;
|
||||||
|
|
||||||
pub mod account;
|
mod account;
|
||||||
pub mod follow;
|
mod follow;
|
||||||
pub mod like;
|
mod like;
|
||||||
pub mod note;
|
mod note;
|
||||||
pub mod object;
|
mod object;
|
||||||
pub mod user;
|
mod user;
|
||||||
|
|
||||||
use account::AccountRepo;
|
pub use account::AccountRepo;
|
||||||
use follow::FollowRepo;
|
pub use follow::FollowRepo;
|
||||||
use like::LikeRepo;
|
pub use like::LikeRepo;
|
||||||
use note::NoteRepo;
|
pub use note::NoteRepo;
|
||||||
use object::ObjectRepo;
|
pub use object::ObjectRepo;
|
||||||
use user::UserRepo;
|
pub use user::UserRepo;
|
||||||
|
|
||||||
/// The central collection of all data accessible to the app.
|
/// The central collection of all data accessible to the app.
|
||||||
/// This is included in `AppState` so it is accessible everywhere.
|
/// This is included in `AppState` so it is accessible everywhere.
|
||||||
|
|
|
@ -3,7 +3,7 @@ use sqlx::PgPool;
|
||||||
|
|
||||||
use crate::core::*;
|
use crate::core::*;
|
||||||
use crate::data::{MemNoteDataSource, PgNoteDataSource};
|
use crate::data::{MemNoteDataSource, PgNoteDataSource};
|
||||||
use crate::model::{NewNote, Note};
|
use crate::model::{AccountId, NewNote, Note, NoteId};
|
||||||
|
|
||||||
pub struct NoteRepo {
|
pub struct NoteRepo {
|
||||||
db: PgNoteDataSource,
|
db: PgNoteDataSource,
|
||||||
|
@ -20,7 +20,7 @@ impl NoteRepo {
|
||||||
|
|
||||||
pub async fn by_id<I>(&self, id: I) -> Result<Note>
|
pub async fn by_id<I>(&self, id: I) -> Result<Note>
|
||||||
where
|
where
|
||||||
I: Into<Id> + Send,
|
I: Into<NoteId> + Send,
|
||||||
{
|
{
|
||||||
let id = id.into();
|
let id = id.into();
|
||||||
match self.mem.by_id(id).await {
|
match self.mem.by_id(id).await {
|
||||||
|
@ -31,7 +31,7 @@ impl NoteRepo {
|
||||||
|
|
||||||
pub async fn by_account<I>(&self, account_id: I, since: NaiveDateTime) -> Result<Vec<Note>>
|
pub async fn by_account<I>(&self, account_id: I, since: NaiveDateTime) -> Result<Vec<Note>>
|
||||||
where
|
where
|
||||||
I: Into<Id> + Send,
|
I: Into<AccountId> + Send,
|
||||||
{
|
{
|
||||||
self.db.by_account(account_id.into(), since).await
|
self.db.by_account(account_id.into(), since).await
|
||||||
}
|
}
|
||||||
|
@ -49,7 +49,7 @@ impl NoteRepo {
|
||||||
|
|
||||||
pub async fn delete<I>(&self, note_id: I) -> Result<()>
|
pub async fn delete<I>(&self, note_id: I) -> Result<()>
|
||||||
where
|
where
|
||||||
I: Into<Id>,
|
I: Into<NoteId>,
|
||||||
{
|
{
|
||||||
let note_id = note_id.into();
|
let note_id = note_id.into();
|
||||||
self.db.delete(note_id).await?;
|
self.db.delete(note_id).await?;
|
||||||
|
@ -59,7 +59,7 @@ impl NoteRepo {
|
||||||
|
|
||||||
pub async fn count_by_account<I>(&self, account_id: I) -> Result<u32>
|
pub async fn count_by_account<I>(&self, account_id: I) -> Result<u32>
|
||||||
where
|
where
|
||||||
I: Into<Id> + Send,
|
I: Into<AccountId> + Send,
|
||||||
{
|
{
|
||||||
self.db.count_by_account(account_id.into()).await
|
self.db.count_by_account(account_id.into()).await
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,9 +1,8 @@
|
||||||
use sqlx::types::Uuid;
|
|
||||||
use sqlx::PgPool;
|
use sqlx::PgPool;
|
||||||
|
|
||||||
use crate::core::*;
|
use crate::core::*;
|
||||||
use crate::data::object::{MemObjectDataSource, PgObjectDataSource};
|
use crate::data::{MemObjectDataSource, PgObjectDataSource};
|
||||||
use crate::model::{NewObject, Object};
|
use crate::model::{NewObject, Object, ObjectId};
|
||||||
|
|
||||||
pub struct ObjectRepo {
|
pub struct ObjectRepo {
|
||||||
db: PgObjectDataSource,
|
db: PgObjectDataSource,
|
||||||
|
@ -37,7 +36,7 @@ impl ObjectRepo {
|
||||||
|
|
||||||
pub async fn by_id<U>(&self, id: U) -> Result<Object>
|
pub async fn by_id<U>(&self, id: U) -> Result<Object>
|
||||||
where
|
where
|
||||||
U: Into<Uuid>,
|
U: Into<ObjectId>,
|
||||||
{
|
{
|
||||||
let id = id.into();
|
let id = id.into();
|
||||||
match self.mem.by_id(id).await {
|
match self.mem.by_id(id).await {
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
use sqlx::PgPool;
|
use sqlx::PgPool;
|
||||||
|
|
||||||
use crate::core::*;
|
use crate::core::*;
|
||||||
use crate::data::user::{MemUserDataSource, PgUserDataSource};
|
use crate::data::{MemUserDataSource, PgUserDataSource};
|
||||||
use crate::model::user::{NewUser, User};
|
use crate::model::{AccountId, NewUser, User, UserId};
|
||||||
|
|
||||||
pub struct UserRepo {
|
pub struct UserRepo {
|
||||||
db: PgUserDataSource,
|
db: PgUserDataSource,
|
||||||
|
@ -19,7 +19,7 @@ impl UserRepo {
|
||||||
|
|
||||||
pub async fn by_id<I>(&self, id: I) -> Result<User>
|
pub async fn by_id<I>(&self, id: I) -> Result<User>
|
||||||
where
|
where
|
||||||
I: Into<Id> + Send,
|
I: Into<UserId> + Send,
|
||||||
{
|
{
|
||||||
let id = id.into();
|
let id = id.into();
|
||||||
match self.mem.by_id(id).await {
|
match self.mem.by_id(id).await {
|
||||||
|
@ -30,7 +30,7 @@ impl UserRepo {
|
||||||
|
|
||||||
pub async fn by_account<I>(&self, account_id: I) -> Result<User>
|
pub async fn by_account<I>(&self, account_id: I) -> Result<User>
|
||||||
where
|
where
|
||||||
I: Into<Id> + Send,
|
I: Into<AccountId> + Send,
|
||||||
{
|
{
|
||||||
self.db.by_account(account_id.into()).await
|
self.db.by_account(account_id.into()).await
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,7 +4,7 @@ use serde::Deserialize;
|
||||||
use crate::core::*;
|
use crate::core::*;
|
||||||
use crate::ent;
|
use crate::ent;
|
||||||
use crate::middle::AuthData;
|
use crate::middle::AuthData;
|
||||||
use crate::model::{NewAccount, NewUser};
|
use crate::model::{AccountId, NewAccount, NewUser};
|
||||||
use crate::state::AppState;
|
use crate::state::AppState;
|
||||||
use crate::util::password;
|
use crate::util::password;
|
||||||
use crate::util::validate::{ResultBuilder, Validate};
|
use crate::util::validate::{ResultBuilder, Validate};
|
||||||
|
@ -70,7 +70,11 @@ async fn verify_credentials(auth: AuthData, state: AppState) -> Result<HttpRespo
|
||||||
}
|
}
|
||||||
|
|
||||||
#[get("/{id}")]
|
#[get("/{id}")]
|
||||||
async fn get_by_id(auth: AuthData, path: web::Path<Id>, state: AppState) -> Result<HttpResponse> {
|
async fn get_by_id(
|
||||||
|
auth: AuthData,
|
||||||
|
path: web::Path<AccountId>,
|
||||||
|
state: AppState,
|
||||||
|
) -> Result<HttpResponse> {
|
||||||
let id = path.into_inner();
|
let id = path.into_inner();
|
||||||
if let Some(auth) = auth.maybe() {
|
if let Some(auth) = auth.maybe() {
|
||||||
if auth.id == id {
|
if auth.id == id {
|
||||||
|
@ -85,7 +89,7 @@ async fn get_by_id(auth: AuthData, path: web::Path<Id>, state: AppState) -> Resu
|
||||||
}
|
}
|
||||||
|
|
||||||
#[get("/{id}/statuses")]
|
#[get("/{id}/statuses")]
|
||||||
async fn get_notes(path: web::Path<Id>, state: AppState) -> Result<HttpResponse> {
|
async fn get_notes(path: web::Path<AccountId>, state: AppState) -> Result<HttpResponse> {
|
||||||
let id = path.into_inner();
|
let id = path.into_inner();
|
||||||
let account = state.repo.accounts.by_id(id).await?;
|
let account = state.repo.accounts.by_id(id).await?;
|
||||||
let notes = state.repo.notes.by_account(account, utc_now()).await?;
|
let notes = state.repo.notes.by_account(account, utc_now()).await?;
|
||||||
|
|
|
@ -4,7 +4,7 @@ use serde::Deserialize;
|
||||||
use crate::core::*;
|
use crate::core::*;
|
||||||
use crate::ent;
|
use crate::ent;
|
||||||
use crate::middle::AuthData;
|
use crate::middle::AuthData;
|
||||||
use crate::model::{NewLike, NewNote};
|
use crate::model::{NewLike, NewNote, NoteId};
|
||||||
use crate::state::AppState;
|
use crate::state::AppState;
|
||||||
|
|
||||||
#[derive(Deserialize)]
|
#[derive(Deserialize)]
|
||||||
|
@ -37,14 +37,22 @@ async fn create_note(
|
||||||
}
|
}
|
||||||
|
|
||||||
#[get("/{id}")]
|
#[get("/{id}")]
|
||||||
async fn get_by_id(path: web::Path<Id>, state: AppState, auth: AuthData) -> Result<HttpResponse> {
|
async fn get_by_id(
|
||||||
|
path: web::Path<NoteId>,
|
||||||
|
state: AppState,
|
||||||
|
auth: AuthData,
|
||||||
|
) -> Result<HttpResponse> {
|
||||||
let id = path.into_inner();
|
let id = path.into_inner();
|
||||||
let note = state.repo.notes.by_id(id).await?;
|
let note = state.repo.notes.by_id(id).await?;
|
||||||
Ok(HttpResponse::Ok().json(ent::Status::from_model(&state, ¬e, &auth).await?))
|
Ok(HttpResponse::Ok().json(ent::Status::from_model(&state, ¬e, &auth).await?))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[delete("/{id}")]
|
#[delete("/{id}")]
|
||||||
async fn del_by_id(path: web::Path<Id>, state: AppState, auth: AuthData) -> Result<HttpResponse> {
|
async fn del_by_id(
|
||||||
|
path: web::Path<NoteId>,
|
||||||
|
state: AppState,
|
||||||
|
auth: AuthData,
|
||||||
|
) -> Result<HttpResponse> {
|
||||||
let id = path.into_inner();
|
let id = path.into_inner();
|
||||||
let auth_account = auth.require()?;
|
let auth_account = auth.require()?;
|
||||||
let note = state.repo.notes.by_id(id).await?;
|
let note = state.repo.notes.by_id(id).await?;
|
||||||
|
@ -57,27 +65,51 @@ async fn del_by_id(path: web::Path<Id>, state: AppState, auth: AuthData) -> Resu
|
||||||
}
|
}
|
||||||
|
|
||||||
#[post("/{id}/favourite")]
|
#[post("/{id}/favourite")]
|
||||||
async fn add_favourite(path: web::Path<Id>, state: AppState, auth: AuthData) -> Result<HttpResponse> {
|
async fn add_favourite(
|
||||||
|
path: web::Path<NoteId>,
|
||||||
|
state: AppState,
|
||||||
|
auth: AuthData,
|
||||||
|
) -> Result<HttpResponse> {
|
||||||
let note_id = path.into_inner();
|
let note_id = path.into_inner();
|
||||||
let auth_account = auth.require()?;
|
let auth_account = auth.require()?;
|
||||||
let note = state.repo.notes.by_id(note_id).await?;
|
let note = state.repo.notes.by_id(note_id).await?;
|
||||||
if state.repo.likes.by_note_and_account(note_id, auth_account.id).await.is_err() {
|
if state
|
||||||
state.repo.likes.create(Insane::from(NewLike {
|
.repo
|
||||||
iri: None,
|
.likes
|
||||||
note_id: note.id,
|
.by_note_and_account(note_id, auth_account.id)
|
||||||
account_id: auth_account.id,
|
.await
|
||||||
})).await?;
|
.is_err()
|
||||||
|
{
|
||||||
|
state
|
||||||
|
.repo
|
||||||
|
.likes
|
||||||
|
.create(Insane::from(NewLike {
|
||||||
|
iri: None,
|
||||||
|
note_id: note.id,
|
||||||
|
account_id: auth_account.id,
|
||||||
|
}))
|
||||||
|
.await?;
|
||||||
}
|
}
|
||||||
let response = ent::Status::from_model(&state, ¬e, &auth).await?;
|
let response = ent::Status::from_model(&state, ¬e, &auth).await?;
|
||||||
Ok(HttpResponse::Ok().json(response))
|
Ok(HttpResponse::Ok().json(response))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[post("/{id}/unfavourite")]
|
#[post("/{id}/unfavourite")]
|
||||||
async fn del_favourite(path: web::Path<Id>, state: AppState, auth: AuthData) -> Result<HttpResponse> {
|
async fn del_favourite(
|
||||||
|
path: web::Path<NoteId>,
|
||||||
|
state: AppState,
|
||||||
|
auth: AuthData,
|
||||||
|
) -> Result<HttpResponse> {
|
||||||
let note_id = path.into_inner();
|
let note_id = path.into_inner();
|
||||||
let auth_account = auth.require()?;
|
let auth_account = auth.require()?;
|
||||||
let note = state.repo.notes.by_id(note_id).await?;
|
let note = state.repo.notes.by_id(note_id).await?;
|
||||||
if state.repo.likes.by_note_and_account(note_id, auth_account.id).await.is_ok() {
|
if state
|
||||||
|
.repo
|
||||||
|
.likes
|
||||||
|
.by_note_and_account(note_id, auth_account.id)
|
||||||
|
.await
|
||||||
|
.is_ok()
|
||||||
|
{
|
||||||
state.repo.likes.delete(note.id, auth_account.id).await?;
|
state.repo.likes.delete(note.id, auth_account.id).await?;
|
||||||
}
|
}
|
||||||
let response = ent::Status::from_model(&state, ¬e, &auth).await?;
|
let response = ent::Status::from_model(&state, ¬e, &auth).await?;
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
use jsonwebtoken::{decode, encode, Algorithm, DecodingKey, EncodingKey, Header, Validation};
|
use jsonwebtoken::{decode, encode, Algorithm, Header, Validation};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
use crate::core::*;
|
use crate::core::*;
|
||||||
|
@ -36,6 +36,6 @@ pub async fn validate(state: &AppState, token: &str) -> Result<Account> {
|
||||||
// tokens that are valid for longer than VALID_SECS are sus
|
// tokens that are valid for longer than VALID_SECS are sus
|
||||||
return Err(Error::BadCredentials);
|
return Err(Error::BadCredentials);
|
||||||
}
|
}
|
||||||
let account_id: Id = claims.sub.parse().expect("We issued an invalid token??");
|
let account_id: IntId = claims.sub.parse().expect("We issued an invalid token??");
|
||||||
state.repo.accounts.by_id(account_id).await
|
state.repo.accounts.by_id(account_id).await
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue