Skip to content

Commit

Permalink
Add Page, Page Content Model (#44)
Browse files Browse the repository at this point in the history
* Add Page Model

* Add load pages with Document, pageContents with Page

* Add Mutation to create page, page content
  • Loading branch information
cptrodgers authored May 7, 2024
1 parent 24d4495 commit 6aafa4e
Show file tree
Hide file tree
Showing 8 changed files with 374 additions and 0 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
-- This file should undo anything in `up.sql`
DROP TABLE page_contents;
DROP TABLE pages;
21 changes: 21 additions & 0 deletions graphql_server/migrations/2024-05-07-054140_page-models/up.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
-- Your SQL goes here
CREATE TABLE pages (
id UUID PRIMARY KEY,
document_id UUID NOT NULL REFERENCES documents(id),
index INT NOT NULL,
title TEXT NOT NULL,
layout INT NOT NULL DEFAULT 0,
created_by_id INT NOT NULL REFERENCES users(id),
deleted_at BIGINT,
updated_at BIGINT DEFAULT (EXTRACT(EPOCH FROM now()))::BIGINT NOT NULL,
created_at BIGINT DEFAULT (EXTRACT(EPOCH FROM now()))::BIGINT NOT NULL
);

CREATE TABLE page_contents (
id UUID PRIMARY KEY,
page_id UUID NOT NULL REFERENCES pages(id),
index INT NOT NULL,
body TEXT NOT NULL,
updated_at BIGINT DEFAULT (EXTRACT(EPOCH FROM now()))::BIGINT NOT NULL,
created_at BIGINT DEFAULT (EXTRACT(EPOCH FROM now()))::BIGINT NOT NULL
)
2 changes: 2 additions & 0 deletions graphql_server/src/db/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ pub mod document_assigned_users;
pub mod documents;
pub mod file;
pub mod notification;
pub mod page;
pub mod page_block;
pub mod quiz;
pub mod rubric;
Expand All @@ -19,6 +20,7 @@ pub use document_assigned_users::*;
pub use documents::*;
pub use file::*;
pub use notification::*;
pub use page::*;
pub use page_block::*;
pub use quiz::*;
pub use rubric::*;
Expand Down
132 changes: 132 additions & 0 deletions graphql_server/src/db/page.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
use diesel::result::Error;
use diesel::sql_types::Integer;
use diesel::{ExpressionMethods, PgConnection, QueryDsl, RunQueryDsl};
use uuid::Uuid;

use super::schema::{page_contents, pages};
use crate::impl_enum_for_db;
use crate::util::get_now_as_secs;

#[derive(
Debug, Clone, Copy, Eq, PartialEq, FromPrimitive, ToPrimitive, AsExpression, FromSqlRow, Enum,
)]
#[sql_type = "Integer"]
pub enum PageLayout {
Horizontal,
Vertical,
}

impl_enum_for_db!(PageLayout);

impl Default for PageLayout {
fn default() -> Self {
Self::Horizontal
}
}

#[derive(Debug, Clone, Insertable, Queryable, SimpleObject, InputObject)]
#[graphql(input_name = "PageInput", complex)]
#[table_name = "pages"]
pub struct Page {
pub id: Uuid,
pub document_id: Uuid,
pub index: i32,
pub title: String,
pub layout: PageLayout,
#[graphql(skip_input)]
pub created_by_id: i32,
#[graphql(skip_input)]
pub deleted_at: Option<i64>,
#[graphql(skip_input)]
pub updated_at: i64,
#[graphql(skip_input)]
pub created_at: i64,
}

impl Page {
pub fn upsert(conn: &PgConnection, mut page: Self) -> Result<Self, Error> {
page.updated_at = get_now_as_secs();
page.created_at = get_now_as_secs();
diesel::insert_into(pages::table)
.values(&page)
.on_conflict(pages::id)
.do_update()
.set((
pages::updated_at.eq(&page.updated_at),
pages::title.eq(&page.title),
pages::index.eq(&page.index),
pages::layout.eq(&page.layout),
))
.get_result(conn)
}

pub fn find(conn: &PgConnection, id: Uuid) -> Result<Self, Error> {
pages::table.find(id).first(conn)
}

pub fn find_all_by_document_ids(
conn: &PgConnection,
document_ids: Vec<Uuid>,
) -> Result<Vec<Self>, Error> {
pages::table
.filter(pages::document_id.eq_any(document_ids))
.filter(pages::deleted_at.is_null())
.get_results(conn)
}

pub fn soft_delete(conn: &PgConnection, id: Uuid) -> Result<(), Error> {
diesel::update(pages::table.find(id))
.set(pages::deleted_at.eq(get_now_as_secs()))
.execute(conn)?;

Ok(())
}

pub fn restore(conn: &PgConnection, id: Uuid) -> Result<Self, Error> {
diesel::update(pages::table.find(id))
.set(pages::deleted_at.eq(None::<i64>))
.get_result(conn)
}
}

#[derive(Debug, Clone, Insertable, Queryable, SimpleObject, InputObject)]
#[graphql(input_name = "PageContentInput")]
#[table_name = "page_contents"]
pub struct PageContent {
pub id: Uuid,
pub page_id: Uuid,
pub index: i32,
pub body: String,
#[graphql(skip_input)]
pub updated_at: i64,
#[graphql(skip_input)]
pub created_at: i64,
}

impl PageContent {
pub fn upsert(conn: &PgConnection, mut page_content: Self) -> Result<Self, Error> {
page_content.updated_at = get_now_as_secs();
page_content.created_at = get_now_as_secs();

diesel::insert_into(page_contents::table)
.values(&page_content)
.on_conflict(page_contents::id)
.do_update()
.set((
page_contents::index.eq(&page_content.index),
page_contents::body.eq(&page_content.body),
page_contents::updated_at.eq(&page_content.updated_at),
))
.get_result(conn)
}

pub fn find(conn: &PgConnection, id: Uuid) -> Result<Self, Error> {
page_contents::table.find(id).first(conn)
}

pub fn find_all_by_pages(conn: &PgConnection, page_ids: Vec<Uuid>) -> Result<Vec<Self>, Error> {
page_contents::table
.filter(page_contents::page_id.eq_any(page_ids))
.get_results(conn)
}
}
30 changes: 30 additions & 0 deletions graphql_server/src/db/schema.rs
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,31 @@ diesel::table! {
}
}

diesel::table! {
page_contents (id) {
id -> Uuid,
page_id -> Uuid,
index -> Int4,
body -> Text,
updated_at -> Int8,
created_at -> Int8,
}
}

diesel::table! {
pages (id) {
id -> Uuid,
document_id -> Uuid,
index -> Int4,
title -> Text,
layout -> Int4,
created_by_id -> Int4,
deleted_at -> Nullable<Int8>,
updated_at -> Int8,
created_at -> Int8,
}
}

diesel::table! {
quiz_answers (quiz_id, user_id) {
quiz_id -> Uuid,
Expand Down Expand Up @@ -299,6 +324,9 @@ diesel::joinable!(documents -> files (cover_photo_id));
diesel::joinable!(documents -> spaces (space_id));
diesel::joinable!(notification_receivers -> notifications (notification_id));
diesel::joinable!(notification_receivers -> users (user_id));
diesel::joinable!(page_contents -> pages (page_id));
diesel::joinable!(pages -> documents (document_id));
diesel::joinable!(pages -> users (created_by_id));
diesel::joinable!(quiz_answers -> quizzes (quiz_id));
diesel::joinable!(quiz_answers -> users (user_id));
diesel::joinable!(quiz_structures -> users (user_id));
Expand Down Expand Up @@ -332,6 +360,8 @@ diesel::allow_tables_to_appear_in_same_query!(
files,
notification_receivers,
notifications,
page_contents,
pages,
quiz_answers,
quiz_structures,
quizzes,
Expand Down
76 changes: 76 additions & 0 deletions graphql_server/src/graphql/data_loader.rs
Original file line number Diff line number Diff line change
Expand Up @@ -584,3 +584,79 @@ impl Loader<FindDocumentAssignedUsers> for IkigaiDataLoader {
Ok(result)
}
}

#[derive(Debug, Clone, Hash, Eq, PartialEq)]
pub struct FindPageByDocumentId {
pub document_id: Uuid,
}

#[async_trait::async_trait]
impl Loader<FindPageByDocumentId> for IkigaiDataLoader {
type Value = Vec<Page>;
type Error = IkigaiError;

async fn load(
&self,
keys: &[FindPageByDocumentId],
) -> std::result::Result<HashMap<FindPageByDocumentId, Self::Value>, Self::Error> {
if keys.is_empty() {
return Ok(HashMap::new());
}

let document_ids = keys.iter().map(|key| key.document_id).unique().collect();
let conn = get_conn_from_actor().await?;
let pages = Page::find_all_by_document_ids(&conn, document_ids)?;

let mut result: HashMap<FindPageByDocumentId, Self::Value> = HashMap::new();
for page in pages {
let key = FindPageByDocumentId {
document_id: page.document_id,
};
if let Some(inner_pages) = result.get_mut(&key) {
inner_pages.push(page);
} else {
result.insert(key, vec![page]);
}
}

Ok(result)
}
}

#[derive(Debug, Clone, Hash, Eq, PartialEq)]
pub struct FindPageContentByPageId {
pub page_id: Uuid,
}

#[async_trait::async_trait]
impl Loader<FindPageContentByPageId> for IkigaiDataLoader {
type Value = Vec<PageContent>;
type Error = IkigaiError;

async fn load(
&self,
keys: &[FindPageContentByPageId],
) -> std::result::Result<HashMap<FindPageContentByPageId, Self::Value>, Self::Error> {
if keys.is_empty() {
return Ok(HashMap::new());
}

let page_ids = keys.iter().map(|key| key.page_id).unique().collect();
let conn = get_conn_from_actor().await?;
let page_contents = PageContent::find_all_by_pages(&conn, page_ids)?;

let mut result: HashMap<FindPageContentByPageId, Self::Value> = HashMap::new();
for page_content in page_contents {
let key = FindPageContentByPageId {
page_id: page_content.page_id,
};
if let Some(inner_pages) = result.get_mut(&key) {
inner_pages.push(page_content);
} else {
result.insert(key, vec![page_content]);
}
}

Ok(result)
}
}
88 changes: 88 additions & 0 deletions graphql_server/src/graphql/document_action/document_mutation.rs
Original file line number Diff line number Diff line change
Expand Up @@ -368,4 +368,92 @@ impl DocumentMutation {

Ok(true)
}

async fn document_add_or_update_page(&self, ctx: &Context<'_>, mut page: Page) -> Result<Page> {
document_quick_authorize(
ctx,
page.document_id,
DocumentActionPermission::EditDocument,
)
.await?;
let user_id = get_user_id_from_ctx(ctx).await?;

let conn = get_conn_from_ctx(ctx).await?;
let existing_page = Page::find(&conn, page.id);
if existing_page.map(|page| page.document_id) != Ok(page.document_id) {
return Err(IkigaiError::new_bad_request(
"Cannot update page of other document",
))
.format_err()?;
}

page.created_by_id = user_id;
let page = Page::upsert(&conn, page).format_err()?;
Ok(page)
}

async fn document_remove_page(&self, ctx: &Context<'_>, page_id: Uuid) -> Result<bool> {
let page = {
let conn = get_conn_from_ctx(ctx).await?;
Page::find(&conn, page_id).format_err()?
};
document_quick_authorize(
ctx,
page.document_id,
DocumentActionPermission::EditDocument,
)
.await?;

let conn = get_conn_from_ctx(ctx).await?;
Page::soft_delete(&conn, page_id).format_err()?;

Ok(true)
}

async fn document_restore_page(&self, ctx: &Context<'_>, page_id: Uuid) -> Result<Page> {
let page = {
let conn = get_conn_from_ctx(ctx).await?;
Page::find(&conn, page_id).format_err()?
};
document_quick_authorize(
ctx,
page.document_id,
DocumentActionPermission::EditDocument,
)
.await?;

let conn = get_conn_from_ctx(ctx).await?;
let page = Page::restore(&conn, page_id).format_err()?;

Ok(page)
}

async fn document_add_or_update_page_content(
&self,
ctx: &Context<'_>,
page_content: PageContent,
) -> Result<PageContent> {
let page = {
let conn = get_conn_from_ctx(ctx).await?;
Page::find(&conn, page_content.page_id).format_err()?
};
document_quick_authorize(
ctx,
page.document_id,
DocumentActionPermission::EditDocument,
)
.await?;

let conn = get_conn_from_ctx(ctx).await?;
let existing_page_content = PageContent::find(&conn, page_content.id);
if existing_page_content.map(|content| content.page_id) != Ok(page.id) {
return Err(IkigaiError::new_bad_request(
"Cannot update content of other page",
))
.format_err();
}

let content = PageContent::upsert(&conn, page_content).format_err()?;
Ok(content)
}
}
Loading

0 comments on commit 6aafa4e

Please sign in to comment.