add statuses
This commit requires wiping the database, because it changes the accounts migration as well.
This commit is contained in:
parent
a5d9f049a9
commit
323fe471c5
14 changed files with 190 additions and 4 deletions
|
@ -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);
|
||||
|
|
12
migrations/20221205235226_create_statuses.sql
Normal file
12
migrations/20221205235226_create_statuses.sql
Normal 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);
|
|
@ -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
3
src/data/account/mod.rs
Normal file
|
@ -0,0 +1,3 @@
|
|||
mod pg;
|
||||
|
||||
pub use pg::PgAccountDataSource;
|
|
@ -1 +1,5 @@
|
|||
pub mod account;
|
||||
pub mod status;
|
||||
|
||||
pub use account::*;
|
||||
pub use status::*;
|
||||
|
|
3
src/data/status/mod.rs
Normal file
3
src/data/status/mod.rs
Normal file
|
@ -0,0 +1,3 @@
|
|||
mod pg;
|
||||
|
||||
pub use pg::PgStatusDataSource;
|
70
src/data/status/pg.rs
Normal file
70
src/data/status/pg.rs
Normal 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)
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
})
|
||||
|
|
|
@ -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
31
src/model/status.rs
Normal 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
|
||||
}
|
||||
}
|
|
@ -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
39
src/repo/status.rs
Normal 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
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue