feat: enhance user registration and follow functionality, add popular tags endpoint, and update tests
This commit is contained in:
@@ -14,3 +14,4 @@ models = { path = "../models" }
|
||||
validator = "0.20"
|
||||
rand = "0.8.5"
|
||||
sea-orm = { version = "1.1.12" }
|
||||
chrono = { workspace = true }
|
||||
|
@@ -13,7 +13,6 @@ fn hash_password(password: &str) -> Result<String, BcryptError> {
|
||||
}
|
||||
|
||||
pub async fn register_user(db: &DbConn, params: RegisterParams) -> Result<user::Model, UserError> {
|
||||
// Validate the parameters
|
||||
params
|
||||
.validate()
|
||||
.map_err(|e| UserError::Validation(e.to_string()))?;
|
||||
@@ -22,8 +21,10 @@ pub async fn register_user(db: &DbConn, params: RegisterParams) -> Result<user::
|
||||
hash_password(¶ms.password).map_err(|e| UserError::Internal(e.to_string()))?;
|
||||
|
||||
let new_user = user::ActiveModel {
|
||||
username: Set(params.username),
|
||||
username: Set(params.username.clone()),
|
||||
password_hash: Set(Some(hashed_password)),
|
||||
email: Set(Some(params.email)),
|
||||
display_name: Set(Some(params.username)),
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
|
@@ -7,7 +7,7 @@ use models::domains::follow;
|
||||
|
||||
pub async fn add_follower(
|
||||
db: &DbConn,
|
||||
followed_id: Uuid,
|
||||
following_id: Uuid,
|
||||
follower_actor_id: &str,
|
||||
) -> Result<(), UserError> {
|
||||
let follower_username = follower_actor_id
|
||||
@@ -20,21 +20,21 @@ pub async fn add_follower(
|
||||
.map_err(|e| UserError::Internal(e.to_string()))?
|
||||
.ok_or(UserError::NotFound)?;
|
||||
|
||||
follow_user(db, follower.id, followed_id)
|
||||
follow_user(db, follower.id, following_id)
|
||||
.await
|
||||
.map_err(|e| UserError::Internal(e.to_string()))?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn follow_user(db: &DbConn, follower_id: Uuid, followed_id: Uuid) -> Result<(), DbErr> {
|
||||
if follower_id == followed_id {
|
||||
pub async fn follow_user(db: &DbConn, follower_id: Uuid, following_id: Uuid) -> Result<(), DbErr> {
|
||||
if follower_id == following_id {
|
||||
return Err(DbErr::Custom("Users cannot follow themselves".to_string()));
|
||||
}
|
||||
|
||||
let follow = follow::ActiveModel {
|
||||
follower_id: Set(follower_id),
|
||||
followed_id: Set(followed_id),
|
||||
following_id: Set(following_id),
|
||||
};
|
||||
|
||||
follow.insert(db).await?;
|
||||
@@ -44,11 +44,11 @@ pub async fn follow_user(db: &DbConn, follower_id: Uuid, followed_id: Uuid) -> R
|
||||
pub async fn unfollow_user(
|
||||
db: &DbConn,
|
||||
follower_id: Uuid,
|
||||
followed_id: Uuid,
|
||||
following_id: Uuid,
|
||||
) -> Result<(), UserError> {
|
||||
let deleted_result = follow::Entity::delete_many()
|
||||
.filter(follow::Column::FollowerId.eq(follower_id))
|
||||
.filter(follow::Column::FollowedId.eq(followed_id))
|
||||
.filter(follow::Column::FollowingId.eq(following_id))
|
||||
.exec(db)
|
||||
.await
|
||||
.map_err(|e| UserError::Internal(e.to_string()))?;
|
||||
@@ -60,18 +60,18 @@ pub async fn unfollow_user(
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn get_followed_ids(db: &DbConn, user_id: Uuid) -> Result<Vec<Uuid>, DbErr> {
|
||||
pub async fn get_following_ids(db: &DbConn, user_id: Uuid) -> Result<Vec<Uuid>, DbErr> {
|
||||
let followed_users = follow::Entity::find()
|
||||
.filter(follow::Column::FollowerId.eq(user_id))
|
||||
.all(db)
|
||||
.await?;
|
||||
|
||||
Ok(followed_users.into_iter().map(|f| f.followed_id).collect())
|
||||
Ok(followed_users.into_iter().map(|f| f.following_id).collect())
|
||||
}
|
||||
|
||||
pub async fn get_follower_ids(db: &DbConn, user_id: Uuid) -> Result<Vec<Uuid>, DbErr> {
|
||||
let followers = follow::Entity::find()
|
||||
.filter(follow::Column::FollowedId.eq(user_id))
|
||||
.filter(follow::Column::FollowingId.eq(user_id))
|
||||
.all(db)
|
||||
.await?;
|
||||
Ok(followers.into_iter().map(|f| f.follower_id).collect())
|
||||
|
@@ -1,6 +1,8 @@
|
||||
use models::domains::{tag, thought_tag};
|
||||
use chrono::{Duration, Utc};
|
||||
use models::domains::{tag, thought, thought_tag};
|
||||
use sea_orm::{
|
||||
sqlx::types::uuid, ColumnTrait, ConnectionTrait, DbErr, EntityTrait, QueryFilter, Set,
|
||||
prelude::Expr, sea_query::Alias, sqlx::types::uuid, ColumnTrait, ConnectionTrait, DbErr,
|
||||
EntityTrait, QueryFilter, QueryOrder, QuerySelect, RelationTrait, Set,
|
||||
};
|
||||
use std::collections::HashSet;
|
||||
|
||||
@@ -84,3 +86,34 @@ where
|
||||
thought_tag::Entity::insert_many(links).exec(db).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn get_popular_tags<C>(db: &C) -> Result<Vec<String>, DbErr>
|
||||
where
|
||||
C: ConnectionTrait,
|
||||
{
|
||||
let seven_days_ago = Utc::now() - Duration::days(7);
|
||||
|
||||
let popular_tags = tag::Entity::find()
|
||||
.select_only()
|
||||
.column(tag::Column::Name)
|
||||
.column_as(Expr::col((tag::Entity, tag::Column::Id)).count(), "count")
|
||||
.join(
|
||||
sea_orm::JoinType::InnerJoin,
|
||||
tag::Relation::ThoughtTag.def(),
|
||||
)
|
||||
.join(
|
||||
sea_orm::JoinType::InnerJoin,
|
||||
thought_tag::Relation::Thought.def(),
|
||||
)
|
||||
.filter(thought::Column::CreatedAt.gte(seven_days_ago))
|
||||
.group_by(tag::Column::Name)
|
||||
.group_by(tag::Column::Id)
|
||||
.order_by_desc(Expr::col(Alias::new("count")))
|
||||
.order_by_asc(tag::Column::Name)
|
||||
.limit(10)
|
||||
.into_tuple::<(String, i64)>()
|
||||
.all(db)
|
||||
.await?;
|
||||
|
||||
Ok(popular_tags.into_iter().map(|(name, _)| name).collect())
|
||||
}
|
||||
|
@@ -69,9 +69,9 @@ pub async fn get_thoughts_by_user(
|
||||
|
||||
pub async fn get_feed_for_user(
|
||||
db: &DbConn,
|
||||
followed_ids: Vec<Uuid>,
|
||||
following_ids: Vec<Uuid>,
|
||||
) -> Result<Vec<ThoughtWithAuthor>, UserError> {
|
||||
if followed_ids.is_empty() {
|
||||
if following_ids.is_empty() {
|
||||
return Ok(vec![]);
|
||||
}
|
||||
|
||||
@@ -83,7 +83,7 @@ pub async fn get_feed_for_user(
|
||||
.column(thought::Column::AuthorId)
|
||||
.column_as(user::Column::Username, "author_username")
|
||||
.join(JoinType::InnerJoin, thought::Relation::User.def())
|
||||
.filter(thought::Column::AuthorId.is_in(followed_ids))
|
||||
.filter(thought::Column::AuthorId.is_in(following_ids))
|
||||
.order_by_desc(thought::Column::CreatedAt)
|
||||
.into_model::<ThoughtWithAuthor>()
|
||||
.all(db)
|
||||
|
@@ -1,6 +1,7 @@
|
||||
use sea_orm::prelude::Uuid;
|
||||
use sea_orm::{
|
||||
ActiveModelTrait, ColumnTrait, DbConn, DbErr, EntityTrait, QueryFilter, Set, TransactionTrait,
|
||||
ActiveModelTrait, ColumnTrait, DbConn, DbErr, EntityTrait, JoinType, QueryFilter, QueryOrder,
|
||||
QuerySelect, RelationTrait, Set, TransactionTrait,
|
||||
};
|
||||
|
||||
use models::domains::{top_friends, user};
|
||||
@@ -127,3 +128,12 @@ pub async fn update_user_profile(
|
||||
.await
|
||||
.map_err(|e| UserError::Internal(e.to_string()))
|
||||
}
|
||||
|
||||
pub async fn get_top_friends(db: &DbConn, user_id: Uuid) -> Result<Vec<user::Model>, DbErr> {
|
||||
user::Entity::find()
|
||||
.join(JoinType::InnerJoin, top_friends::Relation::User.def().rev())
|
||||
.filter(top_friends::Column::UserId.eq(user_id))
|
||||
.order_by_asc(top_friends::Column::Position)
|
||||
.all(db)
|
||||
.await
|
||||
}
|
||||
|
Reference in New Issue
Block a user