Added post versions; updated db boilerplate
parent
8af3ff6996
commit
b0d7f77983
|
@ -2,9 +2,7 @@
|
||||||
drop trigger insert_enforce_version_titles on versions;
|
drop trigger insert_enforce_version_titles on versions;
|
||||||
drop trigger update_enforce_version_titles on versions;
|
drop trigger update_enforce_version_titles on versions;
|
||||||
drop function enforce_version_titles;
|
drop function enforce_version_titles;
|
||||||
drop index sections_shortname_index;
|
|
||||||
|
|
||||||
drop table versions cascade;
|
drop table versions cascade;
|
||||||
drop table tags cascade;
|
|
||||||
drop table posts cascade;
|
drop table posts cascade;
|
||||||
drop table sections cascade;
|
drop table sections cascade;
|
||||||
|
|
|
@ -6,17 +6,17 @@ create table sections (
|
||||||
-- Name to use when routing (this just makes for prettier URLs)
|
-- Name to use when routing (this just makes for prettier URLs)
|
||||||
shortname varchar(32) UNIQUE NOT NULL,
|
shortname varchar(32) UNIQUE NOT NULL,
|
||||||
-- Optional description of the section
|
-- Optional description of the section
|
||||||
description text,
|
description text NOT NULL DEFAULT '',
|
||||||
-- Wether to show the section in the default list on the homepage
|
-- Wether to show the section in the default list on the homepage
|
||||||
is_default boolean NOT NULL DEFAULT false,
|
is_default boolean NOT NULL DEFAULT false,
|
||||||
-- Wether the posts should contain titles or not
|
-- Wether the posts should contain titles or not
|
||||||
has_titles boolean NOT NULL DEFAULT true,
|
has_titles boolean NOT NULL DEFAULT true,
|
||||||
-- Wether posts in this section should be shown publicly
|
-- 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 (
|
create table posts (
|
||||||
id uuid DEFAULT gen_random_uuid() PRIMARY KEY,
|
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)
|
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 $$
|
create function enforce_version_titles() returns trigger as $$
|
||||||
begin
|
begin
|
||||||
-- Draft versions shouldn't be evaluated.
|
-- Draft versions shouldn't be evaluated.
|
||||||
|
|
|
@ -3,9 +3,12 @@
|
||||||
|
|
||||||
pub mod posts;
|
pub mod posts;
|
||||||
pub mod sections;
|
pub mod sections;
|
||||||
|
pub mod versions;
|
||||||
|
|
||||||
pub use posts::{NewPost, PatchPost, Post};
|
pub use posts::{NewPost, PatchPost, Post};
|
||||||
pub use sections::{NewSection, PatchSection, Section};
|
pub use sections::{NewSection, PatchSection, Section};
|
||||||
|
pub use versions::{NewVersion, PatchVersion, Version};
|
||||||
|
|
||||||
pub const MAX_POSTS: u32 = 64;
|
pub const MAX_POSTS: u32 = 64;
|
||||||
pub const MAX_SECTIONS: u32 = 64;
|
pub const MAX_SECTIONS: u32 = 64;
|
||||||
|
pub const MAX_VERSIONS: u32 = 16;
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
use chrono::NaiveDate;
|
|
||||||
use diesel::{insert_into, prelude::*, Insertable, PgConnection, Queryable};
|
use diesel::{insert_into, prelude::*, Insertable, PgConnection, Queryable};
|
||||||
use rb::errors::{RbError, RbOption, RbResult};
|
use rb::errors::{RbError, RbOption, RbResult};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
@ -12,9 +11,8 @@ pub struct Post
|
||||||
{
|
{
|
||||||
pub id: Uuid,
|
pub id: Uuid,
|
||||||
pub section_id: Uuid,
|
pub section_id: Uuid,
|
||||||
pub title: Option<String>,
|
pub is_private: bool,
|
||||||
pub publish_date: NaiveDate,
|
pub is_archived: bool,
|
||||||
pub content: String,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A new post to be added to the database.
|
/// A new post to be added to the database.
|
||||||
|
@ -24,9 +22,8 @@ pub struct Post
|
||||||
pub struct NewPost
|
pub struct NewPost
|
||||||
{
|
{
|
||||||
pub section_id: Uuid,
|
pub section_id: Uuid,
|
||||||
pub title: Option<String>,
|
pub is_private: Option<bool>,
|
||||||
pub publish_date: NaiveDate,
|
pub is_archived: Option<bool>,
|
||||||
pub content: String,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A patch to be applied to a row in the database.
|
/// A patch to be applied to a row in the database.
|
||||||
|
@ -35,9 +32,8 @@ pub struct NewPost
|
||||||
pub struct PatchPost
|
pub struct PatchPost
|
||||||
{
|
{
|
||||||
pub section_id: Option<Uuid>,
|
pub section_id: Option<Uuid>,
|
||||||
pub title: Option<String>,
|
pub is_private: Option<bool>,
|
||||||
pub publish_date: Option<NaiveDate>,
|
pub is_archived: Option<bool>,
|
||||||
pub content: Option<String>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get a list of posts, specified by the offset & a limit. The maximum for `limit_` is determined
|
/// Get a list of posts, specified by the offset & a limit. The maximum for `limit_` is determined
|
||||||
|
|
|
@ -12,9 +12,11 @@ pub struct Section
|
||||||
pub id: Uuid,
|
pub id: Uuid,
|
||||||
pub title: String,
|
pub title: String,
|
||||||
pub shortname: String,
|
pub shortname: String,
|
||||||
pub description: Option<String>,
|
pub description: String,
|
||||||
pub is_default: bool,
|
pub is_default: bool,
|
||||||
pub has_titles: 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
|
/// 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 description: Option<String>,
|
||||||
pub is_default: Option<bool>,
|
pub is_default: Option<bool>,
|
||||||
pub has_titles: Option<bool>,
|
pub has_titles: Option<bool>,
|
||||||
|
pub is_private: Option<bool>,
|
||||||
|
pub is_archived: Option<bool>,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A patch to apply to a section.
|
/// A patch to apply to a section.
|
||||||
|
@ -42,6 +46,8 @@ pub struct PatchSection
|
||||||
description: Option<String>,
|
description: Option<String>,
|
||||||
is_default: Option<bool>,
|
is_default: Option<bool>,
|
||||||
has_titles: 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
|
/// Get an amount of sections from the database, given an offset & limit. The maximum value for
|
||||||
|
|
|
@ -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(())
|
||||||
|
}
|
11
src/main.rs
11
src/main.rs
|
@ -95,6 +95,17 @@ fn rocket() -> _
|
||||||
v1::posts::patch,
|
v1::posts::patch,
|
||||||
v1::posts::delete
|
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();
|
let new_figment = rocket.figment();
|
||||||
|
|
|
@ -12,17 +12,11 @@ table! {
|
||||||
id -> Uuid,
|
id -> Uuid,
|
||||||
title -> Varchar,
|
title -> Varchar,
|
||||||
shortname -> Varchar,
|
shortname -> Varchar,
|
||||||
description -> Nullable<Text>,
|
description -> Text,
|
||||||
is_default -> Bool,
|
is_default -> Bool,
|
||||||
has_titles -> Bool,
|
has_titles -> Bool,
|
||||||
is_private -> Bool,
|
is_private -> Bool,
|
||||||
}
|
is_archived -> Bool,
|
||||||
}
|
|
||||||
|
|
||||||
table! {
|
|
||||||
tags (post_id, value) {
|
|
||||||
post_id -> Uuid,
|
|
||||||
value -> Varchar,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -38,12 +32,10 @@ table! {
|
||||||
}
|
}
|
||||||
|
|
||||||
joinable!(posts -> sections (section_id));
|
joinable!(posts -> sections (section_id));
|
||||||
joinable!(tags -> posts (post_id));
|
|
||||||
joinable!(versions -> posts (post_id));
|
joinable!(versions -> posts (post_id));
|
||||||
|
|
||||||
allow_tables_to_appear_in_same_query!(
|
allow_tables_to_appear_in_same_query!(
|
||||||
posts,
|
posts,
|
||||||
sections,
|
sections,
|
||||||
tags,
|
|
||||||
versions,
|
versions,
|
||||||
);
|
);
|
||||||
|
|
|
@ -1,2 +1,3 @@
|
||||||
pub mod posts;
|
pub mod posts;
|
||||||
pub mod sections;
|
pub mod sections;
|
||||||
|
pub mod versions;
|
||||||
|
|
|
@ -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?)
|
||||||
|
}
|
Reference in New Issue