one interior_ref_list and merchandise_list per shop
still some bugs with routing I need to figure out
This commit is contained in:
parent
3f124ce439
commit
df48b64ffd
@ -20,7 +20,7 @@ pub struct Caches {
|
|||||||
pub list_owners: Cache<ListParams, CachedResponse>,
|
pub list_owners: Cache<ListParams, CachedResponse>,
|
||||||
pub list_interior_ref_lists: Cache<ListParams, CachedResponse>,
|
pub list_interior_ref_lists: Cache<ListParams, CachedResponse>,
|
||||||
pub list_merchandise_lists: Cache<ListParams, CachedResponse>,
|
pub list_merchandise_lists: Cache<ListParams, CachedResponse>,
|
||||||
pub latest_interior_ref_list_by_shop_id: Cache<i32, CachedResponse>,
|
pub interior_ref_list_by_shop_id: Cache<i32, CachedResponse>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Caches {
|
impl Caches {
|
||||||
@ -35,10 +35,7 @@ impl Caches {
|
|||||||
list_owners: Cache::new("list_owners", 100),
|
list_owners: Cache::new("list_owners", 100),
|
||||||
list_interior_ref_lists: Cache::new("list_interior_ref_lists", 100),
|
list_interior_ref_lists: Cache::new("list_interior_ref_lists", 100),
|
||||||
list_merchandise_lists: Cache::new("list_merchandise_lists", 100),
|
list_merchandise_lists: Cache::new("list_merchandise_lists", 100),
|
||||||
latest_interior_ref_list_by_shop_id: Cache::new(
|
interior_ref_list_by_shop_id: Cache::new("interior_ref_list_by_shop_id", 100),
|
||||||
"latest_interior_ref_list_by_shop_id",
|
|
||||||
100,
|
|
||||||
),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -37,7 +37,10 @@ pub fn migration() -> String {
|
|||||||
|
|
||||||
m.create_table("merchandise_lists", |t| {
|
m.create_table("merchandise_lists", |t| {
|
||||||
t.add_column("id", types::primary().indexed(true));
|
t.add_column("id", types::primary().indexed(true));
|
||||||
t.add_column("shop_id", types::foreign("shops", "id").indexed(true));
|
t.add_column(
|
||||||
|
"shop_id",
|
||||||
|
types::foreign("shops", "id").indexed(true).unique(true),
|
||||||
|
);
|
||||||
t.add_column("owner_id", types::foreign("owners", "id").indexed(true));
|
t.add_column("owner_id", types::foreign("owners", "id").indexed(true));
|
||||||
t.add_column("form_list", types::custom("jsonb"));
|
t.add_column("form_list", types::custom("jsonb"));
|
||||||
t.add_column("created_at", types::custom("timestamp(3)"));
|
t.add_column("created_at", types::custom("timestamp(3)"));
|
||||||
@ -59,7 +62,10 @@ pub fn migration() -> String {
|
|||||||
|
|
||||||
m.create_table("interior_ref_lists", |t| {
|
m.create_table("interior_ref_lists", |t| {
|
||||||
t.add_column("id", types::primary().indexed(true));
|
t.add_column("id", types::primary().indexed(true));
|
||||||
t.add_column("shop_id", types::foreign("shops", "id").indexed(true));
|
t.add_column(
|
||||||
|
"shop_id",
|
||||||
|
types::foreign("shops", "id").indexed(true).unique(true),
|
||||||
|
);
|
||||||
t.add_column("owner_id", types::foreign("owners", "id").indexed(true));
|
t.add_column("owner_id", types::foreign("owners", "id").indexed(true));
|
||||||
t.add_column("ref_list", types::custom("jsonb"));
|
t.add_column("ref_list", types::custom("jsonb"));
|
||||||
t.add_column("created_at", types::custom("timestamp(3)"));
|
t.add_column("created_at", types::custom("timestamp(3)"));
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
use anyhow::{anyhow, Result};
|
use anyhow::{anyhow, Result};
|
||||||
use http::StatusCode;
|
use http::StatusCode;
|
||||||
use ipnetwork::IpNetwork;
|
use ipnetwork::IpNetwork;
|
||||||
|
use sqlx::types::Json;
|
||||||
use std::net::SocketAddr;
|
use std::net::SocketAddr;
|
||||||
use tracing::instrument;
|
use tracing::instrument;
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
@ -79,6 +80,35 @@ pub async fn create_shop(
|
|||||||
.create(&env.db)
|
.create(&env.db)
|
||||||
.await
|
.await
|
||||||
.map_err(reject_anyhow)?;
|
.map_err(reject_anyhow)?;
|
||||||
|
|
||||||
|
// also save empty interior_ref_list and merchandise_list rows
|
||||||
|
if let Some(shop_id) = saved_shop.id {
|
||||||
|
let interior_ref_list = InteriorRefList {
|
||||||
|
id: None,
|
||||||
|
shop_id,
|
||||||
|
owner_id: Some(owner_id),
|
||||||
|
ref_list: Json::default(),
|
||||||
|
created_at: None,
|
||||||
|
updated_at: None,
|
||||||
|
};
|
||||||
|
interior_ref_list
|
||||||
|
.create(&env.db)
|
||||||
|
.await
|
||||||
|
.map_err(reject_anyhow)?;
|
||||||
|
let merchandise_list = MerchandiseList {
|
||||||
|
id: None,
|
||||||
|
shop_id,
|
||||||
|
owner_id: Some(owner_id),
|
||||||
|
form_list: Json::default(),
|
||||||
|
created_at: None,
|
||||||
|
updated_at: None,
|
||||||
|
};
|
||||||
|
merchandise_list
|
||||||
|
.create(&env.db)
|
||||||
|
.await
|
||||||
|
.map_err(reject_anyhow)?;
|
||||||
|
}
|
||||||
|
|
||||||
let url = saved_shop.url(&env.api_url).map_err(reject_anyhow)?;
|
let url = saved_shop.url(&env.api_url).map_err(reject_anyhow)?;
|
||||||
let reply = json(&saved_shop);
|
let reply = json(&saved_shop);
|
||||||
let reply = with_header(reply, "Location", url.as_str());
|
let reply = with_header(reply, "Location", url.as_str());
|
||||||
@ -139,10 +169,7 @@ pub async fn delete_shop(
|
|||||||
.await
|
.await
|
||||||
.map_err(reject_anyhow)?;
|
.map_err(reject_anyhow)?;
|
||||||
env.caches.list_shops.clear().await;
|
env.caches.list_shops.clear().await;
|
||||||
env.caches
|
env.caches.interior_ref_list_by_shop_id.delete(id).await;
|
||||||
.latest_interior_ref_list_by_shop_id
|
|
||||||
.delete(id)
|
|
||||||
.await;
|
|
||||||
Ok(StatusCode::NO_CONTENT)
|
Ok(StatusCode::NO_CONTENT)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -307,12 +334,55 @@ pub async fn create_interior_ref_list(
|
|||||||
let reply = with_status(reply, StatusCode::CREATED);
|
let reply = with_status(reply, StatusCode::CREATED);
|
||||||
env.caches.list_interior_ref_lists.clear().await;
|
env.caches.list_interior_ref_lists.clear().await;
|
||||||
env.caches
|
env.caches
|
||||||
.latest_interior_ref_list_by_shop_id
|
.interior_ref_list_by_shop_id
|
||||||
.delete(saved_interior_ref_list.shop_id)
|
.delete(saved_interior_ref_list.shop_id)
|
||||||
.await;
|
.await;
|
||||||
Ok(reply)
|
Ok(reply)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub async fn update_interior_ref_list(
|
||||||
|
id: i32,
|
||||||
|
interior_ref_list: InteriorRefList,
|
||||||
|
api_key: Option<Uuid>,
|
||||||
|
env: Environment,
|
||||||
|
) -> Result<impl Reply, Rejection> {
|
||||||
|
let owner_id = authenticate(&env, api_key).await.map_err(reject_anyhow)?;
|
||||||
|
let interior_ref_list_with_id_and_owner_id = if interior_ref_list.owner_id.is_some() {
|
||||||
|
InteriorRefList {
|
||||||
|
id: Some(id),
|
||||||
|
..interior_ref_list
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
InteriorRefList {
|
||||||
|
id: Some(id),
|
||||||
|
owner_id: Some(owner_id),
|
||||||
|
..interior_ref_list
|
||||||
|
}
|
||||||
|
};
|
||||||
|
let updated_interior_ref_list = interior_ref_list_with_id_and_owner_id
|
||||||
|
.update(&env.db, owner_id, id)
|
||||||
|
.await
|
||||||
|
.map_err(reject_anyhow)?;
|
||||||
|
let url = updated_interior_ref_list
|
||||||
|
.url(&env.api_url)
|
||||||
|
.map_err(reject_anyhow)?;
|
||||||
|
let reply = json(&updated_interior_ref_list);
|
||||||
|
let reply = with_header(reply, "Location", url.as_str());
|
||||||
|
let reply = with_status(reply, StatusCode::CREATED);
|
||||||
|
env.caches
|
||||||
|
.interior_ref_list
|
||||||
|
.delete_response(id)
|
||||||
|
.await
|
||||||
|
.map_err(reject_anyhow)?;
|
||||||
|
env.caches
|
||||||
|
.interior_ref_list_by_shop_id
|
||||||
|
.delete_response(updated_interior_ref_list.shop_id)
|
||||||
|
.await
|
||||||
|
.map_err(reject_anyhow)?;
|
||||||
|
env.caches.list_interior_ref_lists.clear().await;
|
||||||
|
Ok(reply)
|
||||||
|
}
|
||||||
|
|
||||||
pub async fn delete_interior_ref_list(
|
pub async fn delete_interior_ref_list(
|
||||||
id: i32,
|
id: i32,
|
||||||
api_key: Option<Uuid>,
|
api_key: Option<Uuid>,
|
||||||
@ -332,21 +402,20 @@ pub async fn delete_interior_ref_list(
|
|||||||
.map_err(reject_anyhow)?;
|
.map_err(reject_anyhow)?;
|
||||||
env.caches.list_interior_ref_lists.clear().await;
|
env.caches.list_interior_ref_lists.clear().await;
|
||||||
env.caches
|
env.caches
|
||||||
.latest_interior_ref_list_by_shop_id
|
.interior_ref_list_by_shop_id
|
||||||
.delete(interior_ref_list.shop_id)
|
.delete(interior_ref_list.shop_id)
|
||||||
.await;
|
.await;
|
||||||
Ok(StatusCode::NO_CONTENT)
|
Ok(StatusCode::NO_CONTENT)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn get_latest_interior_ref_list_by_shop_id(
|
pub async fn get_interior_ref_list_by_shop_id(
|
||||||
shop_id: i32,
|
shop_id: i32,
|
||||||
env: Environment,
|
env: Environment,
|
||||||
) -> Result<impl Reply, Rejection> {
|
) -> Result<impl Reply, Rejection> {
|
||||||
env.caches
|
env.caches
|
||||||
.latest_interior_ref_list_by_shop_id
|
.interior_ref_list_by_shop_id
|
||||||
.get_response(shop_id, || async {
|
.get_response(shop_id, || async {
|
||||||
let interior_ref_list =
|
let interior_ref_list = InteriorRefList::get_by_shop_id(&env.db, shop_id).await?;
|
||||||
InteriorRefList::get_latest_by_shop_id(&env.db, shop_id).await?;
|
|
||||||
let reply = json(&interior_ref_list);
|
let reply = json(&interior_ref_list);
|
||||||
let reply = with_status(reply, StatusCode::OK);
|
let reply = with_status(reply, StatusCode::OK);
|
||||||
Ok(reply)
|
Ok(reply)
|
||||||
@ -406,6 +475,44 @@ pub async fn create_merchandise_list(
|
|||||||
Ok(reply)
|
Ok(reply)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub async fn update_merchandise_list(
|
||||||
|
id: i32,
|
||||||
|
merchandise_list: MerchandiseList,
|
||||||
|
api_key: Option<Uuid>,
|
||||||
|
env: Environment,
|
||||||
|
) -> Result<impl Reply, Rejection> {
|
||||||
|
let owner_id = authenticate(&env, api_key).await.map_err(reject_anyhow)?;
|
||||||
|
let merchandise_list_with_id_and_owner_id = if merchandise_list.owner_id.is_some() {
|
||||||
|
MerchandiseList {
|
||||||
|
id: Some(id),
|
||||||
|
..merchandise_list
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
MerchandiseList {
|
||||||
|
id: Some(id),
|
||||||
|
owner_id: Some(owner_id),
|
||||||
|
..merchandise_list
|
||||||
|
}
|
||||||
|
};
|
||||||
|
let updated_merchandise_list = merchandise_list_with_id_and_owner_id
|
||||||
|
.update(&env.db, owner_id, id)
|
||||||
|
.await
|
||||||
|
.map_err(reject_anyhow)?;
|
||||||
|
let url = updated_merchandise_list
|
||||||
|
.url(&env.api_url)
|
||||||
|
.map_err(reject_anyhow)?;
|
||||||
|
let reply = json(&updated_merchandise_list);
|
||||||
|
let reply = with_header(reply, "Location", url.as_str());
|
||||||
|
let reply = with_status(reply, StatusCode::CREATED);
|
||||||
|
env.caches
|
||||||
|
.merchandise_list
|
||||||
|
.delete_response(id)
|
||||||
|
.await
|
||||||
|
.map_err(reject_anyhow)?;
|
||||||
|
env.caches.list_merchandise_lists.clear().await;
|
||||||
|
Ok(reply)
|
||||||
|
}
|
||||||
|
|
||||||
pub async fn delete_merchandise_list(
|
pub async fn delete_merchandise_list(
|
||||||
id: i32,
|
id: i32,
|
||||||
api_key: Option<Uuid>,
|
api_key: Option<Uuid>,
|
||||||
|
30
src/main.rs
30
src/main.rs
@ -71,6 +71,7 @@ where
|
|||||||
{
|
{
|
||||||
warp::body::content_length_limit(1024 * 64).and(warp::body::json())
|
warp::body::content_length_limit(1024 * 64).and(warp::body::json())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::main]
|
#[tokio::main]
|
||||||
async fn main() -> Result<()> {
|
async fn main() -> Result<()> {
|
||||||
dotenv().ok();
|
dotenv().ok();
|
||||||
@ -199,6 +200,15 @@ async fn main() -> Result<()> {
|
|||||||
.and(with_env(env.clone()))
|
.and(with_env(env.clone()))
|
||||||
.and_then(handlers::delete_interior_ref_list),
|
.and_then(handlers::delete_interior_ref_list),
|
||||||
);
|
);
|
||||||
|
let update_interior_ref_list_handler = warp::path("interior_ref_lists").and(
|
||||||
|
warp::path::param()
|
||||||
|
.and(warp::path::end())
|
||||||
|
.and(warp::patch())
|
||||||
|
.and(json_body::<InteriorRefList>())
|
||||||
|
.and(warp::header::optional("api-key"))
|
||||||
|
.and(with_env(env.clone()))
|
||||||
|
.and_then(handlers::update_interior_ref_list),
|
||||||
|
);
|
||||||
let list_interior_ref_lists_handler = warp::path("interior_ref_lists").and(
|
let list_interior_ref_lists_handler = warp::path("interior_ref_lists").and(
|
||||||
warp::path::end()
|
warp::path::end()
|
||||||
.and(warp::get())
|
.and(warp::get())
|
||||||
@ -206,13 +216,13 @@ async fn main() -> Result<()> {
|
|||||||
.and(with_env(env.clone()))
|
.and(with_env(env.clone()))
|
||||||
.and_then(handlers::list_interior_ref_lists),
|
.and_then(handlers::list_interior_ref_lists),
|
||||||
);
|
);
|
||||||
let get_latest_interior_ref_list_by_shop_id_handler = warp::path("shops").and(
|
let get_interior_ref_list_by_shop_id_handler = warp::path("shops").and(
|
||||||
warp::path::param()
|
warp::path::param()
|
||||||
.and(warp::path("latest_interior_ref_list"))
|
.and(warp::path("interior_ref_list"))
|
||||||
.and(warp::path::end())
|
.and(warp::path::end())
|
||||||
.and(warp::get())
|
.and(warp::get())
|
||||||
.and(with_env(env.clone()))
|
.and(with_env(env.clone()))
|
||||||
.and_then(handlers::get_latest_interior_ref_list_by_shop_id),
|
.and_then(handlers::get_interior_ref_list_by_shop_id),
|
||||||
);
|
);
|
||||||
let get_merchandise_list_handler = warp::path("merchandise_lists").and(
|
let get_merchandise_list_handler = warp::path("merchandise_lists").and(
|
||||||
warp::path::param()
|
warp::path::param()
|
||||||
@ -237,6 +247,15 @@ async fn main() -> Result<()> {
|
|||||||
.and(with_env(env.clone()))
|
.and(with_env(env.clone()))
|
||||||
.and_then(handlers::delete_merchandise_list),
|
.and_then(handlers::delete_merchandise_list),
|
||||||
);
|
);
|
||||||
|
let update_merchandise_list_handler = warp::path("merchandise_lists").and(
|
||||||
|
warp::path::param()
|
||||||
|
.and(warp::path::end())
|
||||||
|
.and(warp::patch())
|
||||||
|
.and(json_body::<MerchandiseList>())
|
||||||
|
.and(warp::header::optional("api-key"))
|
||||||
|
.and(with_env(env.clone()))
|
||||||
|
.and_then(handlers::update_merchandise_list),
|
||||||
|
);
|
||||||
let list_merchandise_lists_handler = warp::path("merchandise_lists").and(
|
let list_merchandise_lists_handler = warp::path("merchandise_lists").and(
|
||||||
warp::path::end()
|
warp::path::end()
|
||||||
.and(warp::get())
|
.and(warp::get())
|
||||||
@ -258,15 +277,18 @@ async fn main() -> Result<()> {
|
|||||||
update_shop_handler,
|
update_shop_handler,
|
||||||
create_shop_handler,
|
create_shop_handler,
|
||||||
list_shops_handler,
|
list_shops_handler,
|
||||||
get_latest_interior_ref_list_by_shop_id_handler,
|
get_interior_ref_list_by_shop_id_handler,
|
||||||
get_interior_ref_list_handler,
|
get_interior_ref_list_handler,
|
||||||
delete_interior_ref_list_handler,
|
delete_interior_ref_list_handler,
|
||||||
|
update_interior_ref_list_handler,
|
||||||
create_interior_ref_list_handler,
|
create_interior_ref_list_handler,
|
||||||
list_interior_ref_lists_handler,
|
list_interior_ref_lists_handler,
|
||||||
get_merchandise_list_handler,
|
get_merchandise_list_handler,
|
||||||
delete_merchandise_list_handler,
|
delete_merchandise_list_handler,
|
||||||
|
update_merchandise_list_handler,
|
||||||
create_merchandise_list_handler,
|
create_merchandise_list_handler,
|
||||||
list_merchandise_lists_handler,
|
list_merchandise_lists_handler,
|
||||||
|
// warp::any().map(|| StatusCode::NOT_FOUND),
|
||||||
))
|
))
|
||||||
.recover(problem::unpack_problem)
|
.recover(problem::unpack_problem)
|
||||||
.with(warp::compression::gzip())
|
.with(warp::compression::gzip())
|
||||||
|
@ -7,7 +7,7 @@ use sqlx::types::Json;
|
|||||||
use tracing::instrument;
|
use tracing::instrument;
|
||||||
|
|
||||||
use super::ListParams;
|
use super::ListParams;
|
||||||
use super::Model;
|
use super::{Model, UpdateableModel};
|
||||||
use crate::problem::forbidden_permission;
|
use crate::problem::forbidden_permission;
|
||||||
|
|
||||||
// sqlx queries for this model need to be `query_as_unchecked!` because `query_as!` does not
|
// sqlx queries for this model need to be `query_as_unchecked!` because `query_as!` does not
|
||||||
@ -126,15 +126,41 @@ impl Model for InteriorRefList {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[async_trait]
|
||||||
|
impl UpdateableModel for InteriorRefList {
|
||||||
|
#[instrument(level = "debug", skip(self, db))]
|
||||||
|
async fn update(self, db: &PgPool, owner_id: i32, id: i32) -> Result<Self> {
|
||||||
|
let interior_ref_list =
|
||||||
|
sqlx::query!("SELECT owner_id FROM interior_ref_lists WHERE id = $1", id)
|
||||||
|
.fetch_one(db)
|
||||||
|
.await?;
|
||||||
|
if interior_ref_list.owner_id == owner_id {
|
||||||
|
Ok(sqlx::query_as_unchecked!(
|
||||||
|
Self,
|
||||||
|
"UPDATE interior_ref_lists SET
|
||||||
|
ref_list = $2,
|
||||||
|
updated_at = now()
|
||||||
|
WHERE id = $1
|
||||||
|
RETURNING *",
|
||||||
|
id,
|
||||||
|
self.ref_list,
|
||||||
|
)
|
||||||
|
.fetch_one(db)
|
||||||
|
.await?)
|
||||||
|
} else {
|
||||||
|
return Err(forbidden_permission());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl InteriorRefList {
|
impl InteriorRefList {
|
||||||
#[instrument(level = "debug", skip(db))]
|
#[instrument(level = "debug", skip(db))]
|
||||||
pub async fn get_latest_by_shop_id(db: &PgPool, shop_id: i32) -> Result<Self> {
|
pub async fn get_by_shop_id(db: &PgPool, shop_id: i32) -> Result<Self> {
|
||||||
sqlx::query_as_unchecked!(
|
sqlx::query_as_unchecked!(
|
||||||
Self,
|
Self,
|
||||||
"SELECT interior_ref_lists.* FROM interior_ref_lists
|
"SELECT interior_ref_lists.* FROM interior_ref_lists
|
||||||
INNER JOIN shops ON (interior_ref_lists.shop_id = shops.id)
|
INNER JOIN shops ON (interior_ref_lists.shop_id = shops.id)
|
||||||
WHERE shops.id = $1
|
WHERE shops.id = $1
|
||||||
ORDER BY interior_ref_lists.created_at DESC
|
|
||||||
LIMIT 1",
|
LIMIT 1",
|
||||||
shop_id,
|
shop_id,
|
||||||
)
|
)
|
||||||
|
@ -7,7 +7,7 @@ use sqlx::types::Json;
|
|||||||
use tracing::instrument;
|
use tracing::instrument;
|
||||||
|
|
||||||
use super::ListParams;
|
use super::ListParams;
|
||||||
use super::Model;
|
use super::{Model, UpdateableModel};
|
||||||
use crate::problem::forbidden_permission;
|
use crate::problem::forbidden_permission;
|
||||||
|
|
||||||
// sqlx queries for this model need to be `query_as_unchecked!` because `query_as!` does not
|
// sqlx queries for this model need to be `query_as_unchecked!` because `query_as!` does not
|
||||||
@ -117,3 +117,30 @@ impl Model for MerchandiseList {
|
|||||||
Ok(result)
|
Ok(result)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[async_trait]
|
||||||
|
impl UpdateableModel for MerchandiseList {
|
||||||
|
#[instrument(level = "debug", skip(self, db))]
|
||||||
|
async fn update(self, db: &PgPool, owner_id: i32, id: i32) -> Result<Self> {
|
||||||
|
let merchandise_list =
|
||||||
|
sqlx::query!("SELECT owner_id FROM merchandise_lists WHERE id = $1", id)
|
||||||
|
.fetch_one(db)
|
||||||
|
.await?;
|
||||||
|
if merchandise_list.owner_id == owner_id {
|
||||||
|
Ok(sqlx::query_as_unchecked!(
|
||||||
|
Self,
|
||||||
|
"UPDATE merchandise_lists SET
|
||||||
|
form_list = $2,
|
||||||
|
updated_at = now()
|
||||||
|
WHERE id = $1
|
||||||
|
RETURNING *",
|
||||||
|
id,
|
||||||
|
self.form_list,
|
||||||
|
)
|
||||||
|
.fetch_one(db)
|
||||||
|
.await?)
|
||||||
|
} else {
|
||||||
|
return Err(forbidden_permission());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user