Skip to content

Commit

Permalink
feat: quick note CRUD APIs
Browse files Browse the repository at this point in the history
  • Loading branch information
khorshuheng committed Dec 20, 2024
1 parent ea131f0 commit 414d721
Show file tree
Hide file tree
Showing 15 changed files with 646 additions and 2 deletions.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

90 changes: 90 additions & 0 deletions libs/client-api/src/http_quick_note.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
use client_api_entity::{
CreateQuickNoteParams, ListQuickNotesQueryParams, QuickNote, QuickNotes, UpdateQuickNoteParams,
};
use reqwest::Method;
use shared_entity::response::{AppResponse, AppResponseError};
use uuid::Uuid;

use crate::Client;

fn quick_note_resources_url(base_url: &str, workspace_id: Uuid) -> String {
format!("{base_url}/api/workspace/{workspace_id}/quick-note")
}

fn quick_note_resource_url(base_url: &str, workspace_id: Uuid, quick_note_id: Uuid) -> String {
let quick_note_resources_prefix = quick_note_resources_url(base_url, workspace_id);
format!("{quick_note_resources_prefix}/{quick_note_id}")
}

// Quick Note API
impl Client {
pub async fn create_quick_note(
&self,
workspace_id: Uuid,
data: Option<serde_json::Value>,
) -> Result<QuickNote, AppResponseError> {
let url = quick_note_resources_url(&self.base_url, workspace_id);
let resp = self
.http_client_with_auth(Method::POST, &url)
.await?
.json(&CreateQuickNoteParams { data })
.send()
.await?;
AppResponse::<QuickNote>::from_response(resp)
.await?
.into_data()
}

pub async fn list_quick_notes(
&self,
workspace_id: Uuid,
search_term: Option<String>,
offset: Option<i32>,
limit: Option<i32>,
) -> Result<QuickNotes, AppResponseError> {
let url = quick_note_resources_url(&self.base_url, workspace_id);
let resp = self
.http_client_with_auth(Method::GET, &url)
.await?
.query(&ListQuickNotesQueryParams {
search_term,
offset,
limit,
})
.send()
.await?;
AppResponse::<QuickNotes>::from_response(resp)
.await?
.into_data()
}

pub async fn update_quick_note(
&self,
workspace_id: Uuid,
quick_note_id: Uuid,
data: serde_json::Value,
) -> Result<(), AppResponseError> {
let url = quick_note_resource_url(&self.base_url, workspace_id, quick_note_id);
let resp = self
.http_client_with_auth(Method::PUT, &url)
.await?
.json(&UpdateQuickNoteParams { data })
.send()
.await?;
AppResponse::<()>::from_response(resp).await?.into_error()
}

pub async fn delete_quick_note(
&self,
workspace_id: Uuid,
quick_note_id: Uuid,
) -> Result<(), AppResponseError> {
let url = quick_note_resource_url(&self.base_url, workspace_id, quick_note_id);
let resp = self
.http_client_with_auth(Method::DELETE, &url)
.await?
.send()
.await?;
AppResponse::<()>::from_response(resp).await?.into_error()
}
}
1 change: 1 addition & 0 deletions libs/client-api/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ mod http_collab;
mod http_history;
mod http_member;
mod http_publish;
mod http_quick_note;
mod http_search;
mod http_template;
mod http_view;
Expand Down
31 changes: 31 additions & 0 deletions libs/database-entity/src/dto.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1167,6 +1167,37 @@ pub struct WorkspaceNamespace {
pub is_original: bool,
}

#[derive(Clone, Serialize, Deserialize, Debug)]
pub struct QuickNote {
pub id: Uuid,
pub data: serde_json::Value,
pub created_at: DateTime<Utc>,
pub last_updated_at: DateTime<Utc>,
}

#[derive(Clone, Serialize, Deserialize, Debug)]
pub struct QuickNotes {
pub quick_notes: Vec<QuickNote>,
pub has_more: bool,
}

#[derive(Serialize, Deserialize, Debug)]
pub struct CreateQuickNoteParams {
pub data: Option<serde_json::Value>,
}

#[derive(Serialize, Deserialize, Debug)]
pub struct UpdateQuickNoteParams {
pub data: serde_json::Value,
}

#[derive(Serialize, Deserialize, Debug)]
pub struct ListQuickNotesQueryParams {
pub search_term: Option<String>,
pub offset: Option<i32>,
pub limit: Option<i32>,
}

#[cfg(test)]
mod test {
use crate::dto::{CollabParams, CollabParamsV0};
Expand Down
1 change: 1 addition & 0 deletions libs/database/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ pub mod index;
pub mod listener;
pub mod pg_row;
pub mod publish;
pub mod quick_note;
pub mod resource_usage;
pub mod template;
pub mod user;
Expand Down
24 changes: 22 additions & 2 deletions libs/database/src/pg_row.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,9 @@ use chrono::{DateTime, Utc};
use database_entity::dto::{
AFAccessLevel, AFRole, AFUserProfile, AFWebUser, AFWorkspace, AFWorkspaceInvitationStatus,
AccessRequestMinimal, AccessRequestStatus, AccessRequestWithViewId, AccessRequesterInfo,
AccountLink, GlobalComment, Reaction, Template, TemplateCategory, TemplateCategoryMinimal,
TemplateCategoryType, TemplateCreator, TemplateCreatorMinimal, TemplateGroup, TemplateMinimal,
AccountLink, GlobalComment, QuickNote, Reaction, Template, TemplateCategory,
TemplateCategoryMinimal, TemplateCategoryType, TemplateCreator, TemplateCreatorMinimal,
TemplateGroup, TemplateMinimal,
};
use serde::{Deserialize, Serialize};
use sqlx::FromRow;
Expand Down Expand Up @@ -647,3 +648,22 @@ impl TryFrom<AFAccessRequestWithViewIdColumn> for AccessRequestWithViewId {
})
}
}

#[derive(FromRow, Serialize, Debug)]
pub struct AFQuickNoteRow {
pub quick_note_id: Uuid,
pub data: serde_json::Value,
pub created_at: DateTime<Utc>,
pub updated_at: DateTime<Utc>,
}

impl From<AFQuickNoteRow> for QuickNote {
fn from(value: AFQuickNoteRow) -> Self {
Self {
id: value.quick_note_id,
data: value.data,
created_at: value.created_at,
last_updated_at: value.updated_at,
}
}
}
104 changes: 104 additions & 0 deletions libs/database/src/quick_note.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
use app_error::AppError;
use database_entity::dto::QuickNote;
use sqlx::{Executor, Postgres, QueryBuilder};
use uuid::Uuid;

use crate::pg_row::AFQuickNoteRow;

pub async fn insert_new_quick_note<'a, E: Executor<'a, Database = Postgres>>(
executor: E,
workspace_id: Uuid,
uid: i64,
data: &serde_json::Value,
) -> Result<QuickNote, AppError> {
let quick_note = sqlx::query_as!(
QuickNote,
r#"
INSERT INTO af_quick_note (workspace_id, uid, data) VALUES ($1, $2, $3)
RETURNING quick_note_id AS id, data, created_at AS "created_at!", updated_at AS "last_updated_at!"
"#,
workspace_id,
uid,
data
)
.fetch_one(executor)
.await?;
Ok(quick_note)
}

pub async fn select_quick_notes_with_one_more_than_limit<
'a,
E: Executor<'a, Database = Postgres>,
>(
executor: E,
workspace_id: Uuid,
uid: i64,
search_term: Option<String>,
offset: Option<i32>,
limit: Option<i32>,
) -> Result<Vec<QuickNote>, AppError> {
let mut query_builder: QueryBuilder<Postgres> = QueryBuilder::new(
r#"
SELECT
quick_note_id,
data,
created_at,
updated_at
FROM af_quick_note WHERE workspace_id =
"#,
);
query_builder.push_bind(workspace_id);
query_builder.push(" AND uid = ");
query_builder.push_bind(uid);
if let Some(search_term) = search_term.filter(|term| !term.is_empty()) {
query_builder.push(" AND data @? ");
let json_path_query = format!("'$.**.insert ? (@ like_regex \".*{}.*\")'", search_term);
query_builder.push(json_path_query);
}
query_builder.push(" ORDER BY created_at DESC");
if let Some(limit) = limit {
query_builder.push(" LIMIT ");
query_builder.push_bind(limit);
query_builder.push(" + 1 ");
}
if let Some(offset) = offset {
query_builder.push(" OFFSET ");
query_builder.push_bind(offset);
}
let query = query_builder.build_query_as::<AFQuickNoteRow>();
let quick_notes_with_one_more_than_limit = query
.fetch_all(executor)
.await?
.into_iter()
.map(Into::into)
.collect();
Ok(quick_notes_with_one_more_than_limit)
}

pub async fn update_quick_note_by_id<'a, E: Executor<'a, Database = Postgres>>(
executor: E,
quick_note_id: Uuid,
data: &serde_json::Value,
) -> Result<(), AppError> {
sqlx::query!(
"UPDATE af_quick_note SET data = $1, updated_at = NOW() WHERE quick_note_id = $2",
data,
quick_note_id
)
.execute(executor)
.await?;
Ok(())
}

pub async fn delete_quick_note_by_id<'a, E: Executor<'a, Database = Postgres>>(
executor: E,
quick_note_id: Uuid,
) -> Result<(), AppError> {
sqlx::query!(
"DELETE FROM af_quick_note WHERE quick_note_id = $1",
quick_note_id
)
.execute(executor)
.await?;
Ok(())
}
17 changes: 17 additions & 0 deletions migrations/20241216080018_quick_notes.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
CREATE TABLE IF NOT EXISTS af_quick_note (
quick_note_id UUID NOT NULL DEFAULT gen_random_uuid (),
workspace_id UUID NOT NULL,
uid BIGINT NOT NULL REFERENCES af_user (uid) ON DELETE CASCADE,
updated_at TIMESTAMP
WITH
TIME ZONE DEFAULT CURRENT_TIMESTAMP,
created_at TIMESTAMP
WITH
TIME ZONE DEFAULT CURRENT_TIMESTAMP,
data JSONB NOT NULL,
PRIMARY KEY (quick_note_id)
);

CREATE INDEX IF NOT EXISTS idx_workspace_id_on_af_quick_note ON af_quick_note (workspace_id);

CREATE INDEX IF NOT EXISTS idx_uid_on_af_quick_note ON af_quick_note (uid);
Loading

0 comments on commit 414d721

Please sign in to comment.