BLOG-133 Unique label name and semantic ID (#135)
All checks were successful
Frontend CI / build (push) Successful in 1m23s
All checks were successful
Frontend CI / build (push) Successful in 1m23s
### Description - It returns status `409 CONFLICT` if there is already a label with same name or a post with same semantic ID. ### Package Changes _No response_ ### Screenshots _No response_ ### Reference Resolves #133. ### Checklist - [x] A milestone is set - [x] The related issuse has been linked to this branch Reviewed-on: #135 Co-authored-by: SquidSpirit <squid@squidspirit.com> Co-committed-by: SquidSpirit <squid@squidspirit.com>
This commit is contained in:
parent
565df7aace
commit
1ae104cd56
22
backend/.sqlx/query-4e022312b61c557d23f4541961f07b7afa8bf1f62f4a8e248d6520864b126a6a.json
generated
Normal file
22
backend/.sqlx/query-4e022312b61c557d23f4541961f07b7afa8bf1f62f4a8e248d6520864b126a6a.json
generated
Normal file
@ -0,0 +1,22 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "\n SELECT id\n FROM post\n WHERE semantic_id = $1 AND deleted_time IS NULL\n ",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "id",
|
||||
"type_info": "Int4"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Text"
|
||||
]
|
||||
},
|
||||
"nullable": [
|
||||
false
|
||||
]
|
||||
},
|
||||
"hash": "4e022312b61c557d23f4541961f07b7afa8bf1f62f4a8e248d6520864b126a6a"
|
||||
}
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "\n INSERT INTO post (\n title, description, preview_image_url, content, published_time\n ) VALUES ($1, $2, $3, $4, $5)\n RETURNING id\n ",
|
||||
"query": "\n INSERT INTO post (\n semantic_id, title, description, preview_image_url, content, published_time\n ) VALUES ($1, $2, $3, $4, $5, $6)\n RETURNING id\n ",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
@ -11,6 +11,7 @@
|
||||
],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Varchar",
|
||||
"Text",
|
||||
"Text",
|
||||
"Text",
|
||||
@ -22,5 +23,5 @@
|
||||
false
|
||||
]
|
||||
},
|
||||
"hash": "f0c2c0fe0a30790e88449da79c859d4e3829b9b2a6a496c9a429a05fbdb2e30a"
|
||||
"hash": "82c4c8d03298009e776d42ae6069182cc318e0b1d8497a675cf4449adcaf1927"
|
||||
}
|
20
backend/.sqlx/query-9e486528becb873ace62656c78805034ddca950db7306ee04257d6b589b5a0d9.json
generated
Normal file
20
backend/.sqlx/query-9e486528becb873ace62656c78805034ddca950db7306ee04257d6b589b5a0d9.json
generated
Normal file
@ -0,0 +1,20 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "\n UPDATE post\n SET \n semantic_id = $1,\n title = $2, \n description = $3, \n preview_image_url = $4, \n content = $5, \n published_time = $6\n WHERE id = $7\n ",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Varchar",
|
||||
"Text",
|
||||
"Text",
|
||||
"Text",
|
||||
"Text",
|
||||
"Timestamp",
|
||||
"Int4"
|
||||
]
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "9e486528becb873ace62656c78805034ddca950db7306ee04257d6b589b5a0d9"
|
||||
}
|
@ -1,19 +0,0 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "\n UPDATE post\n SET \n title = $1, \n description = $2, \n preview_image_url = $3, \n content = $4, \n published_time = $5\n WHERE id = $6\n ",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Text",
|
||||
"Text",
|
||||
"Text",
|
||||
"Text",
|
||||
"Timestamp",
|
||||
"Int4"
|
||||
]
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "d0867ba2857fedcdc9a754d0394c4f040d559118d0b9f8b6f4dcd6e6fde5d381"
|
||||
}
|
@ -5,6 +5,8 @@ pub enum PostError {
|
||||
NotFound,
|
||||
Unauthorized,
|
||||
InvalidSemanticId,
|
||||
DuplicatedSemanticId,
|
||||
DuplicatedLabelName,
|
||||
Unexpected(anyhow::Error),
|
||||
}
|
||||
|
||||
@ -17,6 +19,8 @@ impl Display for PostError {
|
||||
f,
|
||||
"Semantic ID shouldn't be numeric and must conform to `^[0-9a-zA-Z_\\-]+$`"
|
||||
),
|
||||
PostError::DuplicatedSemanticId => write!(f, "Semantic ID already exists"),
|
||||
PostError::DuplicatedLabelName => write!(f, "Label name already exists"),
|
||||
PostError::Unexpected(e) => write!(f, "Unexpected error: {}", e),
|
||||
}
|
||||
}
|
||||
|
@ -32,7 +32,14 @@ impl LabelDbService for LabelDbServiceImpl {
|
||||
)
|
||||
.fetch_one(&self.db_pool)
|
||||
.await
|
||||
.map_err(|e| PostError::Unexpected(DatabaseError(e).into()))?;
|
||||
.map_err(|e| {
|
||||
if let sqlx::Error::Database(db_err) = &e {
|
||||
if db_err.constraint() == Some("idx_label_name") {
|
||||
return PostError::DuplicatedLabelName;
|
||||
}
|
||||
}
|
||||
PostError::Unexpected(DatabaseError(e).into())
|
||||
})?;
|
||||
|
||||
Ok(id)
|
||||
}
|
||||
@ -50,7 +57,14 @@ impl LabelDbService for LabelDbServiceImpl {
|
||||
)
|
||||
.execute(&self.db_pool)
|
||||
.await
|
||||
.map_err(|e| PostError::Unexpected(DatabaseError(e).into()))?
|
||||
.map_err(|e| {
|
||||
if let sqlx::Error::Database(db_err) = &e {
|
||||
if db_err.constraint() == Some("idx_label_name") {
|
||||
return PostError::DuplicatedLabelName;
|
||||
}
|
||||
}
|
||||
PostError::Unexpected(DatabaseError(e).into())
|
||||
})?
|
||||
.rows_affected();
|
||||
|
||||
if affected_rows == 0 {
|
||||
|
@ -211,7 +211,14 @@ impl PostDbService for PostDbServiceImpl {
|
||||
)
|
||||
.fetch_one(&mut *tx)
|
||||
.await
|
||||
.map_err(|e| PostError::Unexpected(DatabaseError(e).into()))?;
|
||||
.map_err(|e| {
|
||||
if let sqlx::Error::Database(db_err) = &e {
|
||||
if db_err.constraint() == Some("idx_post_semantic_id") {
|
||||
return PostError::DuplicatedSemanticId;
|
||||
}
|
||||
}
|
||||
PostError::Unexpected(DatabaseError(e).into())
|
||||
})?;
|
||||
|
||||
for (order, &label_id) in label_ids.iter().enumerate() {
|
||||
sqlx::query!(
|
||||
@ -266,7 +273,14 @@ impl PostDbService for PostDbServiceImpl {
|
||||
)
|
||||
.execute(&mut *tx)
|
||||
.await
|
||||
.map_err(|e| PostError::Unexpected(DatabaseError(e).into()))?
|
||||
.map_err(|e| {
|
||||
if let sqlx::Error::Database(db_err) = &e {
|
||||
if db_err.constraint() == Some("idx_post_semantic_id") {
|
||||
return PostError::DuplicatedSemanticId;
|
||||
}
|
||||
}
|
||||
PostError::Unexpected(DatabaseError(e).into())
|
||||
})?
|
||||
.rows_affected();
|
||||
|
||||
if affected_rows == 0 {
|
||||
|
@ -34,7 +34,10 @@ pub async fn create_label_handler(
|
||||
Ok(label) => HttpResponse::Created().json(label),
|
||||
Err(e) => match e {
|
||||
PostError::Unauthorized => HttpResponse::Unauthorized().finish(),
|
||||
PostError::NotFound | PostError::InvalidSemanticId => {
|
||||
PostError::DuplicatedLabelName => HttpResponse::Conflict().finish(),
|
||||
PostError::NotFound
|
||||
| PostError::InvalidSemanticId
|
||||
| PostError::DuplicatedSemanticId => {
|
||||
capture_anyhow(&anyhow!(e));
|
||||
HttpResponse::InternalServerError().finish()
|
||||
}
|
||||
|
@ -37,7 +37,8 @@ pub async fn create_post_handler(
|
||||
Err(e) => match e {
|
||||
PostError::Unauthorized => HttpResponse::Unauthorized().finish(),
|
||||
PostError::InvalidSemanticId => HttpResponse::BadRequest().finish(),
|
||||
PostError::NotFound => {
|
||||
PostError::DuplicatedSemanticId => HttpResponse::Conflict().finish(),
|
||||
PostError::NotFound | PostError::DuplicatedLabelName => {
|
||||
capture_anyhow(&anyhow!(e));
|
||||
HttpResponse::InternalServerError().finish()
|
||||
}
|
||||
|
@ -24,7 +24,11 @@ pub async fn get_all_labels_handler(
|
||||
match result {
|
||||
Ok(labels) => HttpResponse::Ok().json(labels),
|
||||
Err(e) => match e {
|
||||
PostError::NotFound | PostError::Unauthorized | PostError::InvalidSemanticId => {
|
||||
PostError::NotFound
|
||||
| PostError::Unauthorized
|
||||
| PostError::InvalidSemanticId
|
||||
| PostError::DuplicatedSemanticId
|
||||
| PostError::DuplicatedLabelName => {
|
||||
capture_anyhow(&anyhow!(e));
|
||||
HttpResponse::InternalServerError().finish()
|
||||
}
|
||||
|
@ -36,7 +36,11 @@ pub async fn get_all_post_info_handler(
|
||||
match result {
|
||||
Ok(post_info_list) => HttpResponse::Ok().json(post_info_list),
|
||||
Err(e) => match e {
|
||||
PostError::NotFound | PostError::Unauthorized | PostError::InvalidSemanticId => {
|
||||
PostError::NotFound
|
||||
| PostError::Unauthorized
|
||||
| PostError::InvalidSemanticId
|
||||
| PostError::DuplicatedSemanticId
|
||||
| PostError::DuplicatedLabelName => {
|
||||
capture_anyhow(&anyhow!(e));
|
||||
HttpResponse::InternalServerError().finish()
|
||||
}
|
||||
|
@ -34,7 +34,9 @@ pub async fn get_post_by_id_handler(
|
||||
Err(e) => match e {
|
||||
PostError::NotFound => HttpResponse::NotFound().finish(),
|
||||
PostError::Unauthorized => HttpResponse::Unauthorized().finish(),
|
||||
PostError::InvalidSemanticId => {
|
||||
PostError::InvalidSemanticId
|
||||
| PostError::DuplicatedSemanticId
|
||||
| PostError::DuplicatedLabelName => {
|
||||
capture_anyhow(&anyhow!(e));
|
||||
HttpResponse::InternalServerError().finish()
|
||||
}
|
||||
|
@ -40,7 +40,8 @@ pub async fn update_label_handler(
|
||||
Err(e) => match e {
|
||||
PostError::NotFound => HttpResponse::NotFound().finish(),
|
||||
PostError::Unauthorized => HttpResponse::Unauthorized().finish(),
|
||||
PostError::InvalidSemanticId => {
|
||||
PostError::DuplicatedLabelName => HttpResponse::Conflict().finish(),
|
||||
PostError::InvalidSemanticId | PostError::DuplicatedSemanticId => {
|
||||
capture_anyhow(&anyhow!(e));
|
||||
HttpResponse::InternalServerError().finish()
|
||||
}
|
||||
|
@ -1,4 +1,5 @@
|
||||
use actix_web::{HttpResponse, Responder, web};
|
||||
use anyhow::anyhow;
|
||||
use auth::framework::web::auth_middleware::UserId;
|
||||
use sentry::integrations::anyhow::capture_anyhow;
|
||||
|
||||
@ -39,7 +40,12 @@ pub async fn update_post_handler(
|
||||
Err(e) => match e {
|
||||
PostError::NotFound => HttpResponse::NotFound().finish(),
|
||||
PostError::Unauthorized => HttpResponse::Unauthorized().finish(),
|
||||
PostError::DuplicatedSemanticId => HttpResponse::Conflict().finish(),
|
||||
PostError::InvalidSemanticId => HttpResponse::BadRequest().finish(),
|
||||
PostError::DuplicatedLabelName => {
|
||||
capture_anyhow(&anyhow!(e));
|
||||
HttpResponse::InternalServerError().finish()
|
||||
}
|
||||
PostError::Unexpected(e) => {
|
||||
capture_anyhow(&e);
|
||||
HttpResponse::InternalServerError().finish()
|
||||
|
@ -0,0 +1,2 @@
|
||||
-- Remove unique index from label name column
|
||||
DROP INDEX IF EXISTS "idx_label_name";
|
@ -0,0 +1,2 @@
|
||||
-- Add unique index to label name column
|
||||
CREATE UNIQUE INDEX "idx_label_name" ON "label" ("name");
|
Loading…
x
Reference in New Issue
Block a user