add statuses

This commit requires wiping the database, because
it changes the accounts migration as well.
This commit is contained in:
anna 2022-12-06 02:40:42 +01:00
parent a5d9f049a9
commit 323fe471c5
Signed by: fef
GPG key ID: EC22E476DC2D3D84
14 changed files with 190 additions and 4 deletions

View file

@ -1,10 +1,10 @@
CREATE TABLE accounts (
id SERIAL PRIMARY KEY,
id BIGSERIAL PRIMARY KEY,
name VARCHAR NOT NULL,
domain VARCHAR NOT NULL,
display_name VARCHAR DEFAULT NULL,
created_at TIMESTAMP NOT NULL DEFAULT now(),
updated_at TIMESTAMP NOT NULL DEFAULT NOW()
updated_at TIMESTAMP NOT NULL DEFAULT now()
);
CREATE UNIQUE INDEX index_accounts_on_name_and_domain ON accounts (name, domain);

View file

@ -0,0 +1,12 @@
CREATE TABLE statuses (
id BIGSERIAL PRIMARY KEY,
account_id BIGINT REFERENCES accounts(id),
uri VARCHAR,
content TEXT NOT NULL,
summary TEXT DEFAULT NULL,
sensitive BOOLEAN NOT NULL DEFAULT FALSE,
created_at TIMESTAMP NOT NULL DEFAULT now(),
updated_at TIMESTAMP NOT NULL DEFAULT now()
);
CREATE INDEX index_statuses_on_created_at ON statuses (created_at);

View file

@ -1,12 +1,14 @@
use actix_web::body::BoxBody;
use actix_web::http::StatusCode;
use actix_web::{HttpResponse, ResponseError};
use chrono::prelude::*;
use serde::{Serialize, Serializer};
use std::time::Instant;
use std::{fmt, io};
pub use log::{debug, error, info, trace, warn};
pub type Id = i32;
pub type Id = i64;
pub type Result<T> = std::result::Result<T, Error>;
@ -17,6 +19,10 @@ pub enum Error {
NotFound,
}
pub fn utc_now() -> NaiveDateTime {
Utc::now().naive_utc()
}
impl ResponseError for Error {
fn status_code(&self) -> StatusCode {
match self {

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

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

View file

@ -1 +1,5 @@
pub mod account;
pub mod status;
pub use account::*;
pub use status::*;

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

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

70
src/data/status/pg.rs Normal file
View file

@ -0,0 +1,70 @@
use chrono::prelude::*;
use serde;
use sqlx::{Executor, PgPool};
use crate::core::*;
use crate::model::status::{NewStatus, Status};
pub struct PgStatusDataSource {
pool: PgPool,
}
impl PgStatusDataSource {
pub fn new(pool: PgPool) -> PgStatusDataSource {
PgStatusDataSource { pool }
}
pub async fn by_id<I>(&self, id: I) -> Result<Status>
where
I: Into<Id> + Send,
{
let id: Id = id.into();
let status: Status = sqlx::query_as("SELECT * FROM statuses WHERE id = $1")
.bind(id)
.fetch_one(&self.pool)
.await?;
Ok(status)
}
pub async fn by_account<I>(&self, id: I, since: NaiveDateTime) -> Result<Vec<Status>>
where
I: Into<Id> + Send,
{
let id: Id = id.into();
let statuses: Vec<Status> = sqlx::query_as(
"SELECT * FROM statuses \
WHERE account_id = $1 AND created_at < $1 \
ORDER BY created_at \
LIMIT 20",
)
.bind(id)
.bind(since)
.fetch_all(&self.pool)
.await?;
Ok(statuses)
}
pub async fn create<T>(&self, new: T) -> Result<Status>
where
T: Into<NewStatus> + Send,
{
let new = new.into();
let status: Status = sqlx::query_as(
"INSERT INTO statuses ( \
account_id, \
uri, \
content, \
summary, \
sensitive \
) VALUES ($1, $2, $3, $4, $5)",
)
.bind(new.account_id)
.bind(new.uri)
.bind(new.content)
.bind(new.summary)
.bind(new.sensitive)
.fetch_one(&self.pool)
.await?;
Ok(status)
}
}

View file

@ -27,6 +27,7 @@ async fn main() -> std::io::Result<()> {
info!("Starting application");
HttpServer::new(move || {
App::new()
.wrap(actix_web::middleware::DefaultHeaders::new().add(("Server", "nyano")))
.app_data(state.clone())
.configure(route::configure)
})

View file

@ -1 +1,5 @@
pub mod account;
pub mod status;
pub use account::{Account, NewAccount};
pub use status::{NewStatus, Status};

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

@ -0,0 +1,31 @@
use chrono::prelude::*;
use serde::{Deserialize, Serialize};
use sqlx::FromRow;
use crate::core::*;
#[derive(Deserialize, Serialize, FromRow)]
pub struct Status {
pub id: Id,
pub account_id: Id,
pub uri: Option<String>,
pub content: String,
pub summary: Option<String>,
pub sensitive: bool,
pub created_at: NaiveDateTime,
pub updated_at: NaiveDateTime,
}
pub struct NewStatus {
pub account_id: Id,
pub uri: String,
pub content: String,
pub summary: Option<String>,
pub sensitive: bool,
}
impl Into<Id> for Status {
fn into(self) -> Id {
self.id
}
}

View file

@ -1,19 +1,23 @@
use sqlx::PgPool;
pub mod account;
pub mod status;
use account::AccountRepo;
use status::StatusRepo;
/// The central collection of all data accessible to the app.
/// This is included in `AppState` so it is accessible everywhere.
/// All interactions with resident data must happen through this interface.
pub struct AppRepo {
pub accounts: AccountRepo,
pub statuses: StatusRepo,
}
impl AppRepo {
pub fn new(db_pool: PgPool) -> AppRepo {
AppRepo {
accounts: AccountRepo::new(db_pool),
accounts: AccountRepo::new(db_pool.clone()),
statuses: StatusRepo::new(db_pool.clone()),
}
}
}

39
src/repo/status.rs Normal file
View file

@ -0,0 +1,39 @@
use chrono::NaiveDateTime;
use sqlx::PgPool;
use crate::core::*;
use crate::data::PgStatusDataSource;
use crate::model::{NewStatus, Status};
pub struct StatusRepo {
db: PgStatusDataSource,
}
impl StatusRepo {
pub fn new(db_pool: PgPool) -> StatusRepo {
StatusRepo {
db: PgStatusDataSource::new(db_pool),
}
}
pub async fn by_id<I>(&self, id: I) -> Result<Status>
where
I: Into<Id> + Send,
{
self.db.by_id(id).await
}
pub async fn by_account<I>(&self, account_id: I, since: NaiveDateTime) -> Result<Vec<Status>>
where
I: Into<Id> + Send,
{
self.db.by_account(account_id, since).await
}
pub async fn create<T>(&self, new: T) -> Result<Status>
where
T: Into<NewStatus> + Send,
{
self.db.create(new).await
}
}

View file

@ -11,6 +11,15 @@ async fn get_by_id(path: web::Path<Id>, state: AppState) -> Result<HttpResponse>
Ok(HttpResponse::Ok().json(account))
}
#[get("/{id}/statuses")]
async fn get_statuses(path: web::Path<Id>, state: AppState) -> Result<HttpResponse> {
let id = path.into_inner();
let account = state.repo.accounts.by_id(id).await?;
let statuses = state.repo.statuses.by_account(account, utc_now()).await?;
Ok(HttpResponse::Ok().json(statuses))
}
pub fn configure(cfg: &mut web::ServiceConfig) {
cfg.service(get_by_id);
cfg.service(get_statuses);
}