add API entities and more accounts endpoints
This commit is contained in:
parent
8ea3ebda54
commit
e387e43dab
8 changed files with 125 additions and 10 deletions
|
@ -1,6 +1,7 @@
|
|||
use sqlx::PgPool;
|
||||
|
||||
use crate::core::*;
|
||||
use crate::data::Count;
|
||||
use crate::model::follow::{Follow, NewFollow};
|
||||
|
||||
pub struct PgFollowDataSource {
|
||||
|
@ -63,4 +64,22 @@ impl PgFollowDataSource {
|
|||
.await?;
|
||||
Ok(followers)
|
||||
}
|
||||
|
||||
pub async fn following_count_of(&self, account_id: Id) -> Result<u32> {
|
||||
let followee_count: Count =
|
||||
sqlx::query_as("SELECT COUNT(*) AS count FROM follows WHERE follower_id = $1")
|
||||
.bind(account_id)
|
||||
.fetch_one(&self.pool)
|
||||
.await?;
|
||||
Ok(followee_count.count as u32)
|
||||
}
|
||||
|
||||
pub async fn follower_count_of(&self, account_id: Id) -> Result<u32> {
|
||||
let follower_count: Count =
|
||||
sqlx::query_as("SELECT COUNT(*) AS count FROM follows WHERE followee_id = $1")
|
||||
.bind(account_id)
|
||||
.fetch_one(&self.pool)
|
||||
.await?;
|
||||
Ok(follower_count.count as u32)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,6 +2,7 @@ use chrono::prelude::*;
|
|||
use sqlx::PgPool;
|
||||
|
||||
use crate::core::*;
|
||||
use crate::data::Count;
|
||||
use crate::model::note::{NewNote, Note};
|
||||
|
||||
pub struct PgNoteDataSource {
|
||||
|
@ -57,4 +58,13 @@ impl PgNoteDataSource {
|
|||
.await?;
|
||||
Ok(note)
|
||||
}
|
||||
|
||||
pub async fn count_by_account(&self, account_id: Id) -> Result<u32> {
|
||||
let count: Count =
|
||||
sqlx::query_as("SELECT COUNT(*) AS count FROM notes WHERE account_id = $1")
|
||||
.bind(account_id)
|
||||
.fetch_one(&self.pool)
|
||||
.await?;
|
||||
Ok(count.count as u32)
|
||||
}
|
||||
}
|
||||
|
|
49
src/ent/account.rs
Normal file
49
src/ent/account.rs
Normal file
|
@ -0,0 +1,49 @@
|
|||
use chrono::prelude::*;
|
||||
use serde::Serialize;
|
||||
|
||||
use crate::core::*;
|
||||
use crate::model;
|
||||
use crate::state::AppState;
|
||||
|
||||
/// <https://docs.joinmastodon.org/entities/Account/>
|
||||
#[derive(Serialize)]
|
||||
pub struct Account {
|
||||
pub id: String,
|
||||
pub username: String,
|
||||
pub display_name: String,
|
||||
pub created_at: DateTime<Utc>,
|
||||
pub followers_count: u32,
|
||||
pub following_count: u32,
|
||||
pub statuses_count: u32,
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
pub struct CredentialAccount {
|
||||
#[serde(flatten)]
|
||||
pub account: Account,
|
||||
}
|
||||
|
||||
impl Account {
|
||||
pub async fn from_model(state: &AppState, model: &model::Account) -> Result<Account> {
|
||||
let followers_count = state.repo.follows.follower_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?;
|
||||
Ok(Account {
|
||||
id: format!("{}", model.id),
|
||||
username: model.name.clone(),
|
||||
display_name: model.display_name.as_ref().unwrap_or(&model.name).clone(),
|
||||
created_at: DateTime::from_utc(model.created_at, Utc),
|
||||
followers_count,
|
||||
following_count,
|
||||
statuses_count,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl CredentialAccount {
|
||||
pub async fn from_model(state: &AppState, model: &model::Account) -> Result<CredentialAccount> {
|
||||
Ok(CredentialAccount {
|
||||
account: Account::from_model(state, model).await?,
|
||||
})
|
||||
}
|
||||
}
|
3
src/ent/mod.rs
Normal file
3
src/ent/mod.rs
Normal file
|
@ -0,0 +1,3 @@
|
|||
mod account;
|
||||
|
||||
pub use account::*;
|
|
@ -18,6 +18,9 @@ mod core;
|
|||
/// data sources of different preferences.
|
||||
mod data;
|
||||
|
||||
/// JSON entities for the REST API.
|
||||
mod ent;
|
||||
|
||||
/// Asynchronous background workers.
|
||||
mod job;
|
||||
|
||||
|
|
|
@ -72,4 +72,18 @@ impl FollowRepo {
|
|||
{
|
||||
self.db.followers_of(account_id.into()).await
|
||||
}
|
||||
|
||||
pub async fn following_count_of<I>(&self, account_id: I) -> Result<u32>
|
||||
where
|
||||
I: Into<Id> + Send,
|
||||
{
|
||||
self.db.following_count_of(account_id.into()).await
|
||||
}
|
||||
|
||||
pub async fn follower_count_of<I>(&self, account_id: I) -> Result<u32>
|
||||
where
|
||||
I: Into<Id> + Send,
|
||||
{
|
||||
self.db.follower_count_of(account_id.into()).await
|
||||
}
|
||||
}
|
||||
|
|
|
@ -46,4 +46,11 @@ impl NoteRepo {
|
|||
self.mem.store(note.clone()).await;
|
||||
Ok(note)
|
||||
}
|
||||
|
||||
pub async fn count_by_account<I>(&self, account_id: I) -> Result<u32>
|
||||
where
|
||||
I: Into<Id> + Send,
|
||||
{
|
||||
self.db.count_by_account(account_id.into()).await
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,18 +2,13 @@ use actix_web::{get, post, web, HttpResponse};
|
|||
use serde::Deserialize;
|
||||
|
||||
use crate::core::*;
|
||||
use crate::ent;
|
||||
use crate::middle::AuthData;
|
||||
use crate::model::{NewAccount, NewUser};
|
||||
use crate::state::AppState;
|
||||
use crate::util::password;
|
||||
use crate::util::validate::{ResultBuilder, Validate};
|
||||
|
||||
#[get("/self")]
|
||||
async fn get_self(account: AuthData) -> Result<HttpResponse> {
|
||||
let account = account.require()?;
|
||||
Ok(HttpResponse::Ok().json(account))
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
struct SignupData {
|
||||
username: String,
|
||||
|
@ -25,7 +20,7 @@ struct SignupData {
|
|||
}
|
||||
|
||||
#[post("")]
|
||||
async fn signup(data: web::Json<SignupData>, state: AppState) -> Result<HttpResponse> {
|
||||
async fn signup(data: web::Form<SignupData>, state: AppState) -> Result<HttpResponse> {
|
||||
let data: Sane<SignupData> = Insane::from(data.into_inner()).try_into()?;
|
||||
let data = data.inner();
|
||||
let account = state
|
||||
|
@ -67,11 +62,26 @@ impl Validate for SignupData {
|
|||
}
|
||||
}
|
||||
|
||||
#[get("/verify_credentials")]
|
||||
async fn verify_credentials(auth: AuthData, state: AppState) -> Result<HttpResponse> {
|
||||
let account = auth.require()?;
|
||||
let response = ent::CredentialAccount::from_model(&state, account).await?;
|
||||
Ok(HttpResponse::Ok().json(response))
|
||||
}
|
||||
|
||||
#[get("/{id}")]
|
||||
async fn get_by_id(path: web::Path<Id>, state: AppState) -> Result<HttpResponse> {
|
||||
async fn get_by_id(auth: AuthData, path: web::Path<Id>, state: AppState) -> Result<HttpResponse> {
|
||||
let id = path.into_inner();
|
||||
if let Some(auth) = auth.maybe() {
|
||||
if auth.id == id {
|
||||
let response = ent::CredentialAccount::from_model(&state, auth).await?;
|
||||
return Ok(HttpResponse::Ok().json(response));
|
||||
}
|
||||
}
|
||||
|
||||
let account = state.repo.accounts.by_id(id).await?;
|
||||
Ok(HttpResponse::Ok().json(account))
|
||||
let response = ent::Account::from_model(&state, &account).await?;
|
||||
Ok(HttpResponse::Ok().json(response))
|
||||
}
|
||||
|
||||
#[get("/{id}/statuses")]
|
||||
|
@ -83,7 +93,7 @@ async fn get_notes(path: web::Path<Id>, state: AppState) -> Result<HttpResponse>
|
|||
}
|
||||
|
||||
pub fn configure(cfg: &mut web::ServiceConfig) {
|
||||
cfg.service(get_self)
|
||||
cfg.service(verify_credentials)
|
||||
.service(get_by_id)
|
||||
.service(get_notes)
|
||||
.service(signup);
|
||||
|
|
Loading…
Reference in a new issue