add follows

This commit is contained in:
anna 2022-12-08 01:12:23 +01:00
parent cd4c81af57
commit 57026cb21d
Signed by: fef
GPG key ID: EC22E476DC2D3D84
8 changed files with 204 additions and 0 deletions

View file

@ -0,0 +1,9 @@
CREATE TABLE follows (
id BIGSERIAL PRIMARY KEY,
follower_id BIGINT REFERENCES accounts (id) ON DELETE CASCADE,
followee_id BIGINT REFERENCES accounts (id) ON DELETE CASCADE,
created_at TIMESTAMP DEFAULT now()
);
CREATE INDEX index_follows_on_follower_id ON follows (follower_id);
CREATE INDEX index_follows_on_followee_id ON follows (followee_id);

3
src/data/follow/mod.rs Normal file
View file

@ -0,0 +1,3 @@
pub mod pg;
pub use pg::PgFollowDataSource;

79
src/data/follow/pg.rs Normal file
View file

@ -0,0 +1,79 @@
use sqlx::PgPool;
use crate::core::*;
use crate::model::follow::{Follow, NewFollow};
pub struct PgFollowDataSource {
pool: PgPool,
}
impl PgFollowDataSource {
pub fn new(pool: PgPool) -> PgFollowDataSource {
PgFollowDataSource { pool }
}
pub async fn by_id<I>(&self, id: I) -> Result<Follow>
where
I: Into<Id> + Send,
{
let follow: Follow = sqlx::query_as("SELECT * FROM follows WHERE id = $1")
.bind(id.into())
.fetch_one(&self.pool)
.await?;
Ok(follow)
}
pub async fn create<T>(&self, new: T) -> Result<Follow>
where
T: Into<NewFollow> + Send,
{
let new = new.into();
let follow: Follow =
sqlx::query_as("INSERT INTO follows (follower_id, followee_id) VALUES ($1, $2)")
.bind(new.follower_id)
.bind(new.followee_id)
.fetch_one(&self.pool)
.await?;
Ok(follow)
}
pub async fn by_follower_and_followee<I, J>(
&self,
follower_id: I,
followee_id: J,
) -> Result<Follow>
where
I: Into<Id> + Send,
J: Into<Id> + Send,
{
let follow: Follow =
sqlx::query_as("SELECT * FROM follows WHERE follower_id = $1 AND followee_id = $2")
.bind(follower_id.into())
.bind(followee_id.into())
.fetch_one(&self.pool)
.await?;
Ok(follow)
}
pub async fn followees_of<I>(&self, account_id: I) -> Result<Vec<Follow>>
where
I: Into<Id> + Send,
{
let followees: Vec<Follow> = sqlx::query_as("SELECT * FROM follows WHERE follower_id = $1")
.bind(account_id.into())
.fetch_all(&self.pool)
.await?;
Ok(followees)
}
pub async fn followers_of<I>(&self, account_id: I) -> Result<Vec<Follow>>
where
I: Into<Id> + Send,
{
let followers: Vec<Follow> = sqlx::query_as("SELECT * FROM follows WHERE followee_id = $1")
.bind(account_id.into())
.fetch_all(&self.pool)
.await?;
Ok(followers)
}
}

View file

@ -1,7 +1,9 @@
pub mod account;
pub mod follow;
pub mod status;
pub mod user;
pub use account::*;
pub use follow::*;
pub use status::*;
pub use user::*;

31
src/model/follow.rs Normal file
View file

@ -0,0 +1,31 @@
use chrono::prelude::*;
use serde::{Deserialize, Serialize};
use sqlx::FromRow;
use crate::core::*;
use crate::util::validate::{ResultBuilder, Validate};
#[derive(Deserialize, Serialize, FromRow)]
pub struct Follow {
pub id: Id,
pub follower_id: Id,
pub followee_id: Id,
pub created_at: NaiveDateTime,
}
pub struct NewFollow {
pub follower_id: Id,
pub followee_id: Id,
}
impl From<Follow> for Id {
fn from(follow: Follow) -> Id {
follow.id
}
}
impl Validate for NewFollow {
fn validate(&self, builder: ResultBuilder) -> ResultBuilder {
builder
}
}

View file

@ -1,7 +1,9 @@
pub mod account;
pub mod follow;
pub mod status;
pub mod user;
pub use account::{Account, NewAccount};
pub use follow::{Follow, NewFollow};
pub use status::{NewStatus, Status};
pub use user::{NewUser, User};

74
src/repo/follow.rs Normal file
View file

@ -0,0 +1,74 @@
use sqlx::PgPool;
use crate::core::*;
use crate::data::follow::PgFollowDataSource;
use crate::model::follow::{Follow, NewFollow};
pub struct FollowRepo {
db: PgFollowDataSource,
}
impl FollowRepo {
pub fn new(db_pool: PgPool) -> FollowRepo {
FollowRepo {
db: PgFollowDataSource::new(db_pool),
}
}
pub async fn by_id<I>(&self, id: I) -> Result<Follow>
where
I: Into<Id> + Send,
{
self.db.by_id(id).await
}
pub async fn create<T>(&self, new: T) -> Result<Follow>
where
T: Into<NewFollow> + Send,
{
self.db.create(new).await
}
pub async fn by_follower_and_followee<I, J>(
&self,
follower_id: I,
followee_id: J,
) -> Result<Follow>
where
I: Into<Id> + Send,
J: Into<Id> + Send,
{
self.db
.by_follower_and_followee(follower_id, followee_id)
.await
}
pub async fn follows<I, J>(&self, follower_id: I, followee_id: J) -> Result<bool>
where
I: Into<Id> + Send,
J: Into<Id> + Send,
{
match self
.by_follower_and_followee(follower_id, followee_id)
.await
{
Ok(_) => Ok(true),
Err(Error::NotFound) => Ok(false),
Err(e) => Err(e),
}
}
pub async fn followees_of<I>(&self, account_id: I) -> Result<Vec<Follow>>
where
I: Into<Id> + Send,
{
self.db.followees_of(account_id).await
}
pub async fn followers_of<I>(&self, account_id: I) -> Result<Vec<Follow>>
where
I: Into<Id> + Send,
{
self.db.followers_of(account_id).await
}
}

View file

@ -1,9 +1,11 @@
use sqlx::PgPool;
pub mod account;
pub mod follow;
pub mod status;
pub mod user;
use account::AccountRepo;
use follow::FollowRepo;
use status::StatusRepo;
use user::UserRepo;
@ -12,6 +14,7 @@ use user::UserRepo;
/// All interactions with resident data must happen through this interface.
pub struct AppRepo {
pub accounts: AccountRepo,
pub follows: FollowRepo,
pub statuses: StatusRepo,
pub users: UserRepo,
}
@ -20,6 +23,7 @@ impl AppRepo {
pub fn new(db_pool: PgPool) -> AppRepo {
AppRepo {
accounts: AccountRepo::new(db_pool.clone()),
follows: FollowRepo::new(db_pool.clone()),
statuses: StatusRepo::new(db_pool.clone()),
users: UserRepo::new(db_pool.clone()),
}