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 sqlx::PgPool;
|
||||||
|
|
||||||
use crate::core::*;
|
use crate::core::*;
|
||||||
|
use crate::data::Count;
|
||||||
use crate::model::follow::{Follow, NewFollow};
|
use crate::model::follow::{Follow, NewFollow};
|
||||||
|
|
||||||
pub struct PgFollowDataSource {
|
pub struct PgFollowDataSource {
|
||||||
|
@ -63,4 +64,22 @@ impl PgFollowDataSource {
|
||||||
.await?;
|
.await?;
|
||||||
Ok(followers)
|
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 sqlx::PgPool;
|
||||||
|
|
||||||
use crate::core::*;
|
use crate::core::*;
|
||||||
|
use crate::data::Count;
|
||||||
use crate::model::note::{NewNote, Note};
|
use crate::model::note::{NewNote, Note};
|
||||||
|
|
||||||
pub struct PgNoteDataSource {
|
pub struct PgNoteDataSource {
|
||||||
|
@ -57,4 +58,13 @@ impl PgNoteDataSource {
|
||||||
.await?;
|
.await?;
|
||||||
Ok(note)
|
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.
|
/// data sources of different preferences.
|
||||||
mod data;
|
mod data;
|
||||||
|
|
||||||
|
/// JSON entities for the REST API.
|
||||||
|
mod ent;
|
||||||
|
|
||||||
/// Asynchronous background workers.
|
/// Asynchronous background workers.
|
||||||
mod job;
|
mod job;
|
||||||
|
|
||||||
|
|
|
@ -72,4 +72,18 @@ impl FollowRepo {
|
||||||
{
|
{
|
||||||
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>
|
||||||
|
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;
|
self.mem.store(note.clone()).await;
|
||||||
Ok(note)
|
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 serde::Deserialize;
|
||||||
|
|
||||||
use crate::core::*;
|
use crate::core::*;
|
||||||
|
use crate::ent;
|
||||||
use crate::middle::AuthData;
|
use crate::middle::AuthData;
|
||||||
use crate::model::{NewAccount, NewUser};
|
use crate::model::{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};
|
||||||
|
|
||||||
#[get("/self")]
|
|
||||||
async fn get_self(account: AuthData) -> Result<HttpResponse> {
|
|
||||||
let account = account.require()?;
|
|
||||||
Ok(HttpResponse::Ok().json(account))
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Deserialize)]
|
#[derive(Deserialize)]
|
||||||
struct SignupData {
|
struct SignupData {
|
||||||
username: String,
|
username: String,
|
||||||
|
@ -25,7 +20,7 @@ struct SignupData {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[post("")]
|
#[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: Sane<SignupData> = Insane::from(data.into_inner()).try_into()?;
|
||||||
let data = data.inner();
|
let data = data.inner();
|
||||||
let account = state
|
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}")]
|
#[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();
|
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?;
|
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")]
|
#[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) {
|
pub fn configure(cfg: &mut web::ServiceConfig) {
|
||||||
cfg.service(get_self)
|
cfg.service(verify_credentials)
|
||||||
.service(get_by_id)
|
.service(get_by_id)
|
||||||
.service(get_notes)
|
.service(get_notes)
|
||||||
.service(signup);
|
.service(signup);
|
||||||
|
|
Loading…
Reference in a new issue