Skip to content

Commit

Permalink
chore: improve message updating api
Browse files Browse the repository at this point in the history
  • Loading branch information
zensh committed Oct 11, 2023
1 parent 0c5889c commit 0dc51db
Show file tree
Hide file tree
Showing 5 changed files with 83 additions and 89 deletions.
2 changes: 1 addition & 1 deletion Cargo.lock

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

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "writing"
version = "1.2.0"
version = "1.2.1"
edition = "2021"
rust-version = "1.64"
description = ""
Expand Down
55 changes: 12 additions & 43 deletions src/api/message.rs
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,7 @@ pub struct UpdateMessageInput {
pub version: i16,
#[validate(length(min = 0, max = 1024))]
pub context: Option<String>,
pub language: Option<PackObject<Language>>,
pub message: Option<PackObject<Vec<u8>>>,
}

Expand All @@ -153,9 +154,6 @@ impl UpdateMessageInput {
if let Some(context) = self.context {
cols.set_as("context", &context);
}
if let Some(message) = self.message {
cols.set_as("message", &message.unwrap());
}

if cols.is_empty() {
return Err(HTTPError::new(400, "No fields to update".to_string()).into());
Expand All @@ -177,53 +175,24 @@ pub async fn update(
let id = *input.id.to_owned();
let version = input.version;
let mut doc = db::Message::with_pk(id);
let cols = input.into()?;
ctx.set_kvs(vec![
("action", "update_message".into()),
("id", doc.id.to_string().into()),
])
.await;

let ok = doc.update(&app.scylla, cols, version).await?;
ctx.set("updated", ok.into()).await;
doc._fields = vec!["updated_at".to_string(), "version".to_string()];
Ok(to.with(SuccessResponse::new(MessageOutput::from(doc, &to))))
}

#[derive(Debug, Deserialize, Validate)]
pub struct UpdateI18nMessageInput {
pub id: PackObject<xid::Id>,
#[validate(range(min = 1, max = 32767))]
pub version: i16,
pub language: PackObject<Language>,
pub message: PackObject<Vec<u8>>,
}

pub async fn update_i18n(
State(app): State<Arc<AppState>>,
Extension(ctx): Extension<Arc<ReqContext>>,
to: PackObject<UpdateI18nMessageInput>,
) -> Result<PackObject<SuccessResponse<MessageOutput>>, HTTPError> {
let (to, input) = to.unpack();
input.validate()?;
valid_user(ctx.user)?;

let id = *input.id.to_owned();
let version = input.version;
let language = input.language.to_639_3().to_string();
let mut doc = db::Message::with_pk(id);
ctx.set_kvs(vec![
("action", "update_i18n_message".into()),
("action", "update_message".into()),
("id", doc.id.to_string().into()),
("language", language.clone().into()),
("version", version.to_string().into()),
])
.await;

let ok = doc
.update_i18n(&app.scylla, language, input.message.unwrap(), version)
.await?;
let ok = if let Some(message) = input.message {
let language = input.language.unwrap_or_default().to_639_3().to_string();
ctx.set("language", language.clone().into()).await;
doc.update_message(&app.scylla, language, message.unwrap(), version)
.await?
} else {
let cols = input.into()?;
doc.update(&app.scylla, cols, version).await?
};
ctx.set("updated", ok.into()).await;
doc._fields = vec!["updated_at".to_string()];
Ok(to.with(SuccessResponse::new(MessageOutput::from(doc, &to))))
}

Expand Down
97 changes: 62 additions & 35 deletions src/db/model_message.rs
Original file line number Diff line number Diff line change
Expand Up @@ -201,7 +201,7 @@ impl Message {
cols: ColumnsMap,
version: i16,
) -> anyhow::Result<bool> {
let valid_fields = ["context", "message"];
let valid_fields = ["context"];
let update_fields = cols.keys();
for field in &update_fields {
if !valid_fields.contains(&field.as_str()) {
Expand All @@ -220,21 +220,13 @@ impl Message {
)
.into());
}
if version == 32767 {
return Err(
HTTPError::new(400, format!("Message version overflow, got {}", version)).into(),
);
}

self.day = xid_day(self.id);
let mut set_fields: Vec<String> = Vec::with_capacity(update_fields.len() + 2);
let mut params: Vec<CqlValue> = Vec::with_capacity(update_fields.len() + 2 + 3);
let mut set_fields: Vec<String> = Vec::with_capacity(update_fields.len() + 1);
let mut params: Vec<CqlValue> = Vec::with_capacity(update_fields.len() + 1 + 3);

let new_updated_at = unix_ms() as i64;
set_fields.push("updated_at=?".to_string());
set_fields.push("version=?".to_string());
params.push(new_updated_at.to_cql());
params.push((version + 1).to_cql());
for field in &update_fields {
set_fields.push(format!("{}=?", field));
params.push(cols.get(field).unwrap().to_owned());
Expand All @@ -256,11 +248,11 @@ impl Message {
}

self.updated_at = new_updated_at;
self.version += 1;
self._fields = vec!["updated_at".to_string()];
Ok(true)
}

pub async fn update_i18n(
pub async fn update_message(
&mut self,
db: &scylladb::ScyllaDB,
lang: String,
Expand All @@ -271,7 +263,8 @@ impl Message {
return Err(HTTPError::new(400, format!("Invalid language: {}", lang)).into());
}

self.get_one(db, vec!["version".to_string()]).await?;
self.get_one(db, vec!["version".to_string(), "language".to_string()])
.await?;
if self.version != version {
return Err(HTTPError::new(
409,
Expand All @@ -283,28 +276,53 @@ impl Message {
.into());
}

self.day = xid_day(self.id);
let query = format!(
"UPDATE message SET updated_at=?,{}=? WHERE day=? AND id=? IF version=?",
lang
);
let mut params: Vec<CqlValue> = Vec::with_capacity(4);

let new_updated_at = unix_ms() as i64;
params.push(new_updated_at.to_cql());
params.push(message.to_cql());
params.push(self.day.to_cql());
params.push(self.id.to_cql());
params.push(version.to_cql());
let res = if lang == self.language.to_639_3() {
if version == 32767 {
return Err(HTTPError::new(
400,
format!("Message version overflow, got {}", version),
)
.into());
}

let query = format!(
"UPDATE message SET updated_at=?,message=?,version=? WHERE day=? AND id=? IF version=?",
);
let params = (
new_updated_at,
message.to_cql(),
version + 1,
self.day,
self.id.to_cql(),
version,
);
self.version += 1;
self.updated_at = new_updated_at;
self._fields = vec!["updated_at".to_string(), "version".to_string()];
db.execute(query, params).await?
} else {
let query = format!(
"UPDATE message SET updated_at=?,{}=? WHERE day=? AND id=? IF version=?",
lang
);
let params = (
new_updated_at,
message.to_cql(),
self.day,
self.id.to_cql(),
version,
);
self.updated_at = new_updated_at;
self._fields = vec!["updated_at".to_string()];
db.execute(query, params).await?
};

let res = db.execute(query, params).await?;
if !extract_applied(res) {
return Err(
HTTPError::new(409, "Message update failed, please try again".to_string()).into(),
);
}

self.updated_at = new_updated_at;
Ok(true)
}

Expand Down Expand Up @@ -425,7 +443,7 @@ mod tests {
cols.set_as("context", &"context 1".to_string());
let res = doc.update(db, cols, 1).await.unwrap();
assert!(res);
assert_eq!(doc.version, 2);
assert_eq!(doc.version, 1);

let mut cols = ColumnsMap::new();
cols.set_as("context", &"context 2".to_string());
Expand All @@ -434,39 +452,48 @@ mod tests {
assert!(res);
}

// update_i18n
// update_message
{
let mut doc = Message::with_pk(id);
doc.get_one(db, vec!["i18n".to_string()]).await.unwrap();
assert_eq!(doc.id, id);
assert_eq!(doc.context.as_str(), "");
assert_eq!(doc.version, 3);
assert_eq!(doc.version, 1);
assert_eq!(doc.language, Language::Eng);
assert_eq!(doc.message, message);
assert!(doc._fields.contains(&"zho".to_string()));
assert!(doc._i18n_messages.is_empty());

let res = doc
.update_i18n(db, "zho".to_string(), message.clone(), 1)
.update_message(db, "zho".to_string(), message.clone(), 2)
.await;
assert!(res.is_err());
let err: erring::HTTPError = res.unwrap_err().into();
assert_eq!(err.code, 409); // version not match

doc.update_i18n(db, "zho".to_string(), message.clone(), 3)
doc.update_message(db, "zho".to_string(), message.clone(), 1)
.await
.unwrap();

let mut doc2 = Message::with_pk(id);
doc2.get_one(db, vec!["i18n".to_string()]).await.unwrap();
assert_eq!(doc2.id, id);
assert_eq!(doc2.context.as_str(), "");
assert_eq!(doc2.version, 3);
assert_eq!(doc2.version, 1);
assert_eq!(doc2.language, Language::Eng);
assert_eq!(doc2.message, message);
assert!(doc2._fields.contains(&"zho".to_string()));
assert!(doc2._i18n_messages.len() == 1);
assert_eq!(doc2._i18n_messages.get("zho").unwrap(), &message);

doc2.update_message(db, "eng".to_string(), message.clone(), 1)
.await
.unwrap();
assert_eq!(doc2.version, 2);
let res = doc2
.update_message(db, "eng".to_string(), message.clone(), 1)
.await;
assert!(res.is_err());
}

// delete
Expand Down
16 changes: 7 additions & 9 deletions src/router.rs
Original file line number Diff line number Diff line change
Expand Up @@ -137,15 +137,13 @@ pub async fn new(cfg: conf::Conf) -> anyhow::Result<(Arc<api::AppState>, Router)
)
.nest(
"/v1/message",
Router::new()
.route(
"/",
routing::post(api::message::create)
.get(api::message::get)
.patch(api::message::update)
.delete(api::message::delete),
)
.route("/update_i18n", routing::post(api::message::update_i18n)),
Router::new().route(
"/",
routing::post(api::message::create)
.get(api::message::get)
.patch(api::message::update)
.delete(api::message::delete),
),
)
.nest(
"/v1/bookmark",
Expand Down

0 comments on commit 0dc51db

Please sign in to comment.