add API entities and more accounts endpoints

This commit is contained in:
anna 2022-12-27 01:52:28 +01:00
parent 8ea3ebda54
commit e387e43dab
Signed by: fef
GPG key ID: EC22E476DC2D3D84
8 changed files with 125 additions and 10 deletions

View file

@ -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)
}
} }

View file

@ -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
View 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
View file

@ -0,0 +1,3 @@
mod account;
pub use account::*;

View file

@ -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;

View file

@ -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
}
} }

View file

@ -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
}
} }

View file

@ -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);