Added post versions; updated db boilerplate

pull/4/head
Jef Roosens 2021-12-26 17:46:17 +01:00
parent 8af3ff6996
commit b0d7f77983
Signed by: Jef Roosens
GPG Key ID: 955C0660072F691F
10 changed files with 211 additions and 34 deletions

View File

@ -2,9 +2,7 @@
drop trigger insert_enforce_version_titles on versions;
drop trigger update_enforce_version_titles on versions;
drop function enforce_version_titles;
drop index sections_shortname_index;
drop table versions cascade;
drop table tags cascade;
drop table posts cascade;
drop table sections cascade;

View File

@ -6,17 +6,17 @@ create table sections (
-- Name to use when routing (this just makes for prettier URLs)
shortname varchar(32) UNIQUE NOT NULL,
-- Optional description of the section
description text,
description text NOT NULL DEFAULT '',
-- Wether to show the section in the default list on the homepage
is_default boolean NOT NULL DEFAULT false,
-- Wether the posts should contain titles or not
has_titles boolean NOT NULL DEFAULT true,
-- Wether posts in this section should be shown publicly
is_private boolean NOT NULL DEFAULT false
is_private boolean NOT NULL DEFAULT false,
-- Wether the section is archived
is_archived boolean NOT NULL DEFAULT false
);
create index sections_shortname_index on sections(shortname);
create table posts (
id uuid DEFAULT gen_random_uuid() PRIMARY KEY,
@ -51,13 +51,6 @@ create table versions (
CONSTRAINT no_null_published_date CHECK (is_draft OR publish_date IS NOT NULL)
);
create table tags (
post_id uuid NOT NULL REFERENCES posts(id) ON DELETE CASCADE,
value varchar(64) NOT NULL,
PRIMARY KEY (post_id, value)
);
create function enforce_version_titles() returns trigger as $$
begin
-- Draft versions shouldn't be evaluated.

View File

@ -3,9 +3,12 @@
pub mod posts;
pub mod sections;
pub mod versions;
pub use posts::{NewPost, PatchPost, Post};
pub use sections::{NewSection, PatchSection, Section};
pub use versions::{NewVersion, PatchVersion, Version};
pub const MAX_POSTS: u32 = 64;
pub const MAX_SECTIONS: u32 = 64;
pub const MAX_VERSIONS: u32 = 16;

View File

@ -1,4 +1,3 @@
use chrono::NaiveDate;
use diesel::{insert_into, prelude::*, Insertable, PgConnection, Queryable};
use rb::errors::{RbError, RbOption, RbResult};
use serde::{Deserialize, Serialize};
@ -12,9 +11,8 @@ pub struct Post
{
pub id: Uuid,
pub section_id: Uuid,
pub title: Option<String>,
pub publish_date: NaiveDate,
pub content: String,
pub is_private: bool,
pub is_archived: bool,
}
/// A new post to be added to the database.
@ -24,9 +22,8 @@ pub struct Post
pub struct NewPost
{
pub section_id: Uuid,
pub title: Option<String>,
pub publish_date: NaiveDate,
pub content: String,
pub is_private: Option<bool>,
pub is_archived: Option<bool>,
}
/// A patch to be applied to a row in the database.
@ -35,9 +32,8 @@ pub struct NewPost
pub struct PatchPost
{
pub section_id: Option<Uuid>,
pub title: Option<String>,
pub publish_date: Option<NaiveDate>,
pub content: Option<String>,
pub is_private: Option<bool>,
pub is_archived: Option<bool>,
}
/// Get a list of posts, specified by the offset & a limit. The maximum for `limit_` is determined

View File

@ -12,9 +12,11 @@ pub struct Section
pub id: Uuid,
pub title: String,
pub shortname: String,
pub description: Option<String>,
pub description: String,
pub is_default: bool,
pub has_titles: bool,
pub is_private: bool,
pub is_archived: bool,
}
/// A new section to add. Any `Option` values will get replaced by their default value in the
@ -29,6 +31,8 @@ pub struct NewSection
pub description: Option<String>,
pub is_default: Option<bool>,
pub has_titles: Option<bool>,
pub is_private: Option<bool>,
pub is_archived: Option<bool>,
}
/// A patch to apply to a section.
@ -42,6 +46,8 @@ pub struct PatchSection
description: Option<String>,
is_default: Option<bool>,
has_titles: Option<bool>,
pub is_private: Option<bool>,
pub is_archived: Option<bool>,
}
/// Get an amount of sections from the database, given an offset & limit. The maximum value for

103
src/db/versions.rs 100644
View File

@ -0,0 +1,103 @@
use chrono::NaiveDate;
use diesel::{insert_into, prelude::*, Insertable, PgConnection, Queryable};
use rb::errors::{RbError, RbOption, RbResult};
use serde::{Deserialize, Serialize};
use uuid::Uuid;
use crate::schema::{versions, versions::dsl::*};
/// A version inside the database.
#[derive(Queryable, Serialize)]
pub struct Version
{
pub id: Uuid,
pub post_id: Uuid,
pub title: Option<String>,
pub publish_date: Option<NaiveDate>,
pub content: String,
pub is_draft: bool,
}
#[derive(Deserialize, Insertable)]
#[table_name = "versions"]
#[serde(rename_all = "camelCase")]
pub struct NewVersion
{
pub post_id: Uuid,
pub title: Option<String>,
pub publish_date: Option<NaiveDate>,
pub content: Option<String>,
pub is_draft: Option<bool>,
}
#[derive(Deserialize, AsChangeset)]
#[table_name = "versions"]
pub struct PatchVersion
{
pub id: Uuid,
pub post_id: Option<Uuid>,
pub title: Option<String>,
pub publish_date: Option<NaiveDate>,
pub content: Option<String>,
pub is_draft: Option<bool>,
}
pub fn get(conn: &PgConnection, offset_: u32, limit_: u32) -> RbResult<Vec<Version>>
{
Ok(versions
.offset(offset_.into())
.limit(std::cmp::min(limit_, super::MAX_VERSIONS).into())
.load(conn)
.map_err(|_| RbError::DbError("Couldn't query versions."))?)
}
pub fn get_for_post(
conn: &PgConnection,
post_id_: &Uuid,
offset_: u32,
limit_: u32,
) -> RbResult<Vec<Version>>
{
Ok(versions
.offset(offset_.into())
.filter(post_id.eq(post_id_))
.limit(std::cmp::min(limit_, super::MAX_VERSIONS).into())
.load(conn)
.map_err(|_| RbError::DbError("Couldn't query versions."))?)
}
pub fn find(conn: &PgConnection, id_: &Uuid) -> RbOption<Version>
{
match versions.find(id_).first(conn) {
Ok(val) => Ok(Some(val)),
Err(diesel::NotFound) => Ok(None),
_ => Err(RbError::DbError("Couldn't find version.")),
}
}
pub fn create(conn: &PgConnection, new: &NewVersion) -> RbResult<Version>
{
Ok(insert_into(versions)
.values(new)
.get_result(conn)
.map_err(|_| RbError::DbError("Couldn't insert version."))?)
// TODO check for conflict?
}
pub fn update(conn: &PgConnection, id_: &Uuid, patch: &PatchVersion) -> RbResult<Version>
{
Ok(diesel::update(versions.filter(id.eq(id_)))
.set(patch)
.get_result(conn)
.map_err(|_| RbError::DbError("Couldn't update version."))?)
}
pub fn delete(conn: &PgConnection, id_: &Uuid) -> RbResult<()>
{
diesel::delete(versions.filter(id.eq(id_)))
.execute(conn)
.map_err(|_| RbError::DbError("Couldn't delete version."))?;
Ok(())
}

View File

@ -95,6 +95,17 @@ fn rocket() -> _
v1::posts::patch,
v1::posts::delete
],
)
.mount(
"/v1/versions",
routes![
v1::versions::get,
v1::versions::get_for_post,
v1::versions::create,
v1::versions::find,
v1::versions::patch,
v1::versions::delete
],
);
let new_figment = rocket.figment();

View File

@ -12,17 +12,11 @@ table! {
id -> Uuid,
title -> Varchar,
shortname -> Varchar,
description -> Nullable<Text>,
description -> Text,
is_default -> Bool,
has_titles -> Bool,
is_private -> Bool,
}
}
table! {
tags (post_id, value) {
post_id -> Uuid,
value -> Varchar,
is_archived -> Bool,
}
}
@ -38,12 +32,10 @@ table! {
}
joinable!(posts -> sections (section_id));
joinable!(tags -> posts (post_id));
joinable!(versions -> posts (post_id));
allow_tables_to_appear_in_same_query!(
posts,
sections,
tags,
versions,
);

View File

@ -1,2 +1,3 @@
pub mod posts;
pub mod sections;
pub mod versions;

74
src/v1/versions.rs 100644
View File

@ -0,0 +1,74 @@
use rb::{
errors::{RbOption, RbResult},
guards::Admin,
};
use rb_blog::db;
use rocket::serde::json::Json;
use uuid::Uuid;
use crate::RbDbConn;
#[get("/?<offset>&<limit>", rank = 1)]
pub async fn get(conn: RbDbConn, offset: u32, limit: u32) -> RbResult<Json<Vec<db::Version>>>
{
Ok(Json(
conn.run(move |c| db::versions::get(c, offset, limit))
.await?,
))
}
#[get("/?<post_id>&<offset>&<limit>", rank = 0)]
pub async fn get_for_post(
conn: RbDbConn,
post_id: Uuid,
offset: u32,
limit: u32,
) -> RbResult<Json<Vec<db::Version>>>
{
Ok(Json(
conn.run(move |c| db::versions::get_for_post(c, &post_id, offset, limit))
.await?,
))
}
#[post("/", data = "<new>")]
pub async fn create(
_admin: Admin,
conn: RbDbConn,
new: Json<db::NewVersion>,
) -> RbResult<Json<db::Version>>
{
Ok(Json(
conn.run(move |c| db::versions::create(c, &new.into_inner()))
.await?,
))
}
#[get("/<id>")]
pub async fn find(conn: RbDbConn, id: uuid::Uuid) -> RbOption<Json<db::Version>>
{
Ok(conn
.run(move |c| db::versions::find(c, &id))
.await?
.and_then(|p| Some(Json(p))))
}
#[patch("/<id>", data = "<patch>")]
pub async fn patch(
_admin: Admin,
conn: RbDbConn,
id: uuid::Uuid,
patch: Json<db::PatchVersion>,
) -> RbResult<Json<db::Version>>
{
Ok(Json(
conn.run(move |c| db::versions::update(c, &id, &patch.into_inner()))
.await?,
))
}
#[delete("/<id>")]
pub async fn delete(_admin: Admin, conn: RbDbConn, id: uuid::Uuid) -> RbResult<()>
{
Ok(conn.run(move |c| db::versions::delete(c, &id)).await?)
}