Skip to content

Commit

Permalink
feat: Added blok patch method
Browse files Browse the repository at this point in the history
  • Loading branch information
leo91000 committed Jul 16, 2022
1 parent eb6c3e4 commit c50e1d9
Show file tree
Hide file tree
Showing 19 changed files with 415 additions and 37 deletions.
4 changes: 2 additions & 2 deletions Dockerfile.dev
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,9 @@ RUN cargo install cargo-watch just

WORKDIR /lyonkit-api

COPY ./ .
COPY ./ .\

RUN cargo build -p server
RUN cargo build && cargo test --no-run

ENV PORT=8080
EXPOSE $PORT
Expand Down
2 changes: 2 additions & 0 deletions crates/migration/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ mod m20220517_000003_create_api_keys_table;
mod m20220517_000004_create_bloks_table;
mod m20220610_000005_create_images_table;
mod m20220610_000006_create_posts_table;
mod m20220716_000007_fix_blok_priority_trigger;
pub(crate) mod utils;

pub struct Migrator;
Expand All @@ -21,6 +22,7 @@ impl MigratorTrait for Migrator {
Box::new(m20220517_000004_create_bloks_table::Migration),
Box::new(m20220610_000005_create_images_table::Migration),
Box::new(m20220610_000006_create_posts_table::Migration),
Box::new(m20220716_000007_fix_blok_priority_trigger::Migration),
]
}
}
65 changes: 65 additions & 0 deletions crates/migration/src/m20220716_000007_fix_blok_priority_trigger.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
use crate::utils::macros::exec_stmt;
use sea_orm_migration::{prelude::*, MigrationName};

pub struct Migration;

impl MigrationName for Migration {
fn name(&self) -> &str {
"m20220716_000007_fix_blok_priority_trigger"
}
}

#[async_trait::async_trait]
impl MigrationTrait for Migration {
async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> {
exec_stmt!(
manager,
r#"
create or replace function tg_bloks__set_priority() returns trigger as $$
declare
max_priority integer;
begin
-- Set default priority to maximum
if new.priority is null or new.priority = 0 then
select max(b1.priority) into max_priority from bloks b1 where b1.page_id = new.page_id;
new.priority := coalesce(max_priority + 1, 1);
end if;
-- Offset other priorities if conflict
update bloks b2 set priority = b2.priority + 1 where b2.page_id = new.page_id and b2.priority = new.priority and b2.id != new.id;
return new;
end;
$$ language plpgsql volatile;
"#
)?;

Ok(())
}

async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> {
exec_stmt!(
manager,
r#"
create or replace function tg_bloks__set_priority() returns trigger as $$
declare
max_priority integer;
begin
-- Set default priority to maximum
if new.priority is null or new.priority = 0 then
select max(b1.priority) into max_priority from bloks b1 where b1.page_id = new.page_id;
new.priority := coalesce(max_priority + 1, 0);
end if;
-- Offset other priorities if conflict
update bloks b2 set priority = b2.priority + 1 where b2.page_id = new.page_id and b2.priority = new.priority and b2.id != new.id;
return new;
end;
$$ language plpgsql volatile;
"#
)?;

Ok(())
}
}
8 changes: 8 additions & 0 deletions crates/server/src/errors/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@ pub enum ApiError {
MissingField(String),
ImageNotDecodable,
InternalServerError,
PatchNotNullable(String),
PatchAtLeastOneField,
}

impl Display for ApiError {
Expand Down Expand Up @@ -59,6 +61,8 @@ impl Display for ApiError {
ApiError::MissingField(field) => write!(f, "Missing field \"{}\"", field),
ApiError::ImageNotDecodable => write!(f, "Provide image is not decodable, make sure you provide a valid image or that you use a supported image format !"),
ApiError::InternalServerError => write!(f, "Internal Server Error"),
ApiError::PatchNotNullable(field) => write!(f, "Field {field} is not nullable"),
ApiError::PatchAtLeastOneField => write!(f, "You must patch at least one field"),
}
}
}
Expand All @@ -77,6 +81,8 @@ impl ApiErrorTrait for ApiError {
ApiError::MissingField(_) => String::from("FLMIS"),
ApiError::ImageNotDecodable => String::from("IMGND"),
ApiError::InternalServerError => String::from("INTSE"),
ApiError::PatchNotNullable(_) => String::from("PTHNN"),
ApiError::PatchAtLeastOneField => String::from("PTHOF"),
}
}

Expand All @@ -87,6 +93,8 @@ impl ApiErrorTrait for ApiError {
ApiError::NotFound => StatusCode::NOT_FOUND,
ApiError::ReferenceNotFound(_)
| ApiError::InvalidContentType(_, _)
| ApiError::PatchNotNullable(_)
| ApiError::PatchAtLeastOneField
| ApiError::MissingField(_) => StatusCode::BAD_REQUEST,
ApiError::ImageNotDecodable => StatusCode::UNPROCESSABLE_ENTITY,
_ => StatusCode::INTERNAL_SERVER_ERROR,
Expand Down
1 change: 1 addition & 0 deletions crates/server/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ pub mod middlewares;
pub mod server;
pub mod services;
pub mod telemetry;
mod utils;

pub async fn main() -> io::Result<()> {
openssl_probe::init_ssl_cert_env_vars();
Expand Down
7 changes: 3 additions & 4 deletions crates/server/src/services/blok/mod.rs
Original file line number Diff line number Diff line change
@@ -1,15 +1,14 @@
use crate::services::blok::routes::{create_blok, delete_blok, get_blok, update_blok};
use crate::services::blok::routes::{create_blok, delete_blok, get_blok, patch_blok, update_blok};
use actix_web::{web::scope, Scope};

mod models;
pub(crate) mod models;
mod routes;

pub use models::BlokOutput;

pub fn blok_service() -> Scope {
scope("/blok")
.service(get_blok)
.service(create_blok)
.service(update_blok)
.service(patch_blok)
.service(delete_blok)
}
28 changes: 28 additions & 0 deletions crates/server/src/services/blok/models/blok_input.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
use entity::blok;
use getset::Getters;
use sea_orm::ActiveValue::Set;
use sea_orm::NotSet;
use serde::Deserialize;
use serde_json::Value;

#[derive(Deserialize, Clone, Getters)]
#[serde(rename_all = "camelCase")]
#[getset(get = "pub")]
pub struct BlokInput {
page_id: i32,
component_id: String,
props: Value,
priority: Option<i32>,
}

impl BlokInput {
pub fn active_model(&self) -> blok::ActiveModel {
blok::ActiveModel {
page_id: Set(self.page_id.to_owned()),
component_id: Set(self.component_id.to_owned()),
props: Set(self.props.to_owned()),
priority: self.priority.map(Set).unwrap_or(NotSet),
..Default::default()
}
}
}
Original file line number Diff line number Diff line change
@@ -1,33 +1,10 @@
use crate::errors::{utils::try_unwrap_active_value, ApiError};
use crate::errors::utils::try_unwrap_active_value;
use crate::errors::ApiError;
use chrono::{DateTime, Utc};
use entity::blok;
use getset::Getters;
use sea_orm::ActiveValue::{NotSet, Set};
use serde::{Deserialize, Serialize};
use serde::Serialize;
use serde_json::Value;

#[derive(Deserialize, Clone, Getters)]
#[serde(rename_all = "camelCase")]
#[getset(get = "pub")]
pub struct BlokInput {
page_id: i32,
component_id: String,
props: Value,
priority: Option<i32>,
}

impl BlokInput {
pub fn active_model(&self) -> blok::ActiveModel {
blok::ActiveModel {
page_id: Set(self.page_id.to_owned()),
component_id: Set(self.component_id.to_owned()),
props: Set(self.props.to_owned()),
priority: self.priority.map(Set).unwrap_or(NotSet),
..Default::default()
}
}
}

#[derive(Serialize, Clone)]
#[serde(rename_all = "camelCase")]
pub struct BlokOutput {
Expand Down
18 changes: 18 additions & 0 deletions crates/server/src/services/blok/models/blok_patch_input.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
use crate::utils::serde_json_patch::Patch;
use getset::Getters;
use serde::Deserialize;
use serde_json::Value;

#[derive(Deserialize, Clone, Getters)]
#[serde(rename_all = "camelCase")]
#[getset(get = "pub")]
pub struct BlokPatchInput {
#[serde(default)]
page_id: Patch<i32>,
#[serde(default)]
component_id: Patch<String>,
#[serde(default)]
props: Patch<Value>,
#[serde(default)]
priority: Patch<i32>,
}
7 changes: 7 additions & 0 deletions crates/server/src/services/blok/models/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
mod blok_input;
mod blok_output;
mod blok_patch_input;

pub use blok_input::BlokInput;
pub use blok_output::BlokOutput;
pub use blok_patch_input::BlokPatchInput;
91 changes: 89 additions & 2 deletions crates/server/src/services/blok/routes.rs
Original file line number Diff line number Diff line change
@@ -1,15 +1,18 @@
use super::models::{BlokInput, BlokOutput};
use crate::middlewares::api_key::ApiKey;
use crate::services::blok::models::BlokPatchInput;
use crate::utils::serde_json_patch::Patch::Value;
use crate::{
errors::{utils::db_err_into_api_err, ApiError},
middlewares::api_key::WriteApiKey,
server::AppState,
services::blok::models::{BlokInput, BlokOutput},
};
use actix_web::{delete, get, post, put, web, Error as ActixError, HttpResponse};
use actix_web::{delete, get, patch, post, put, web, Error as ActixError, HttpResponse};
use entity::blok::{Column, Entity, Model};
use entity::page::{Column as PageColumn, Entity as PageEntity};
use sea_orm::prelude::*;
use sea_orm::ActiveValue::Set;
use sea_orm::IntoActiveModel;

#[get("/{id}")]
pub async fn get_blok(
Expand Down Expand Up @@ -106,6 +109,90 @@ pub async fn update_blok(
)
}

#[patch("/{id}")]
pub async fn patch_blok(
data: web::Data<AppState>,
path_id: web::Path<i32>,
body: web::Json<BlokPatchInput>,
api_key: WriteApiKey,
) -> Result<HttpResponse, ActixError> {
let id = path_id.into_inner();

if body.page_id().is_null() {
return Err(ApiError::PatchNotNullable(String::from("pageId")).into());
}

if body.component_id().is_null() {
return Err(ApiError::PatchNotNullable(String::from("componentId")).into());
}

if body.props().is_null() {
return Err(ApiError::PatchNotNullable(String::from("props")).into());
}

if body.priority().is_null() {
return Err(ApiError::PatchNotNullable(String::from("priority")).into());
}

if body.page_id().is_missing()
&& body.component_id().is_missing()
&& body.props().is_missing()
&& body.priority().is_missing()
{
return Err(ApiError::PatchAtLeastOneField.into());
}

let mut blok = Entity::find()
.find_also_related(PageEntity)
.filter(Column::Id.eq(id))
.one(data.conn())
.await
.map_err(db_err_into_api_err)?
.and_then(|(blok, page)| page.map(|p| (blok, p)))
.and_then(|(blok, page)| {
if &page.namespace == api_key.namespace() {
return Some(blok);
}
None
})
.ok_or(ApiError::NotFound)?
.into_active_model();

if let Value(page_id) = body.page_id() {
PageEntity::find()
.filter(PageColumn::Namespace.eq(api_key.namespace().to_owned()))
.filter(PageColumn::Id.eq(*page_id))
.one(data.conn())
.await
.map_err(db_err_into_api_err)?
.ok_or_else(|| ApiError::ReferenceNotFound("pageId".to_string()))?;

blok.page_id = Set(*page_id);
}

if let Value(component_id) = body.component_id() {
blok.component_id = Set(component_id.clone());
}

if let Value(props) = body.props() {
blok.props = Set(props.clone());
}

if let Value(priority) = body.priority() {
blok.priority = Set(*priority);
}

blok.id = Set(id);

Ok(
blok
.save(data.conn())
.await
.map_err(db_err_into_api_err)
.and_then(|model| Ok(HttpResponse::Ok().json(BlokOutput::try_from(model)?)))?,
)
}

#[delete("/{id}")]
pub async fn delete_blok(
data: web::Data<AppState>,
Expand Down
2 changes: 1 addition & 1 deletion crates/server/src/services/page/models.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use crate::errors::{utils::try_unwrap_active_value, ApiError};
use crate::services::blok::BlokOutput;
use crate::services::blok::models::BlokOutput;
use chrono::{DateTime, Utc};
use entity::page;
use sea_orm::ActiveValue::Set;
Expand Down
1 change: 1 addition & 0 deletions crates/server/src/utils/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
pub mod serde_json_patch;
Loading

0 comments on commit c50e1d9

Please sign in to comment.