Skip to content

Commit

Permalink
feat: Added names to jig-codes
Browse files Browse the repository at this point in the history
  • Loading branch information
MendyBerger committed Mar 21, 2024
1 parent de0bafb commit 5a592e9
Show file tree
Hide file tree
Showing 11 changed files with 299 additions and 56 deletions.
38 changes: 38 additions & 0 deletions backend/api/sqlx-data.json
Original file line number Diff line number Diff line change
Expand Up @@ -1130,6 +1130,23 @@
},
"query": "select id as \"id: AudioId\" from user_audio_library order by created_at desc"
},
"16f8910b20347fe554aa439d7e678c377d5798bf89a14267614e1d47521bc8b0": {
"describe": {
"columns": [],
"nullable": [],
"parameters": {
"Left": [
"Int4",
"Bool",
"Text",
"Int2",
"Bool",
"Bool"
]
}
},
"query": "\n update jig_code\n set name = case when $2 then $3 else name end,\n direction = coalesce($4, direction),\n scoring = coalesce($5, scoring),\n drag_assist = coalesce($6, drag_assist)\n where code = $1\n "
},
"172c8df92f296df4a816c4c9d77c091c56919f84f54bc1cc1174060aff2d64c6": {
"describe": {
"columns": [
Expand Down Expand Up @@ -12248,6 +12265,27 @@
},
"query": "\nselect author_id as \"author_id: UserId\",\n published_at as \"published_at?\"\nfrom playlist\nwhere id = $1\n "
},
"b7d2bcf9ab498de0fa50e3b47d3e5498541bffde1c02345a178747824d0318ad": {
"describe": {
"columns": [
{
"name": "authed!",
"ordinal": 0,
"type_info": "Bool"
}
],
"nullable": [
null
],
"parameters": {
"Left": [
"Uuid",
"Int4"
]
}
},
"query": "\n select exists (\n select 1 from jig_code where creator_id = $1 and code = $2\n ) as \"authed!\"\n \n "
},
"b7d83cfd90ef7d1f18b8f070a52bccb1dc58d040a1e012e776c9cd47704efc27": {
"describe": {
"columns": [],
Expand Down
24 changes: 24 additions & 0 deletions backend/api/src/db/jig.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
use crate::translate::translate_text;
use anyhow::Context;
use serde_json::value::Value;
use shared::domain::jig::codes::JigCode;
use shared::domain::jig::{AdminJigExport, JigUpdateAdminDataRequest};
use shared::domain::module::StableModuleId;
use shared::domain::playlist::{PlaylistAdminData, PlaylistRating};
Expand Down Expand Up @@ -1898,6 +1899,29 @@ select exists (
Ok(())
}

pub async fn is_users_code(db: &PgPool, user_id: UserId, code: JigCode) -> Result<(), error::Auth> {
let authed = sqlx::query!(
//language=SQL
r#"
select exists (
select 1 from jig_code where creator_id = $1 and code = $2
) as "authed!"
"#,
user_id.0,
code.0
)
.fetch_one(db)
.await?
.authed;

if !authed {
return Err(error::Auth::Forbidden);
}

Ok(())
}

async fn update_draft_or_live(
conn: &mut PgConnection,
jig_data_id: Uuid,
Expand Down
35 changes: 34 additions & 1 deletion backend/api/src/db/jig/codes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ use chrono::{DateTime, Duration, Utc};
use rand::{rngs::ThreadRng, Rng};
use shared::config::{JIG_PLAYER_SESSION_CODE_MAX, JIG_PLAYER_SESSION_VALID_DURATION_SECS};
use shared::domain::jig::codes::{
JigCodeListRequest, JigCodeSessionResponse, JigPlayerSessionCreateRequest,
JigCodeListRequest, JigCodeSessionResponse, JigCodeUpdateRequest, JigPlayerSessionCreateRequest,
};
use shared::domain::jig::{
codes::{JigCode, JigCodeResponse, JigPlaySession},
Expand Down Expand Up @@ -75,6 +75,39 @@ returning created_at as "created_at: DateTime<Utc>"
Err(anyhow::anyhow!("Maximum retries reached for creating a new jig session").into())
}

pub async fn update(
db: &PgPool,
code: JigCode,
opts: &JigCodeUpdateRequest,
) -> Result<(), error::JigCode> {
let name = opts.name.clone();
let direction = opts.settings.as_ref().map(|opts| opts.direction);
let scoring = opts.settings.as_ref().map(|opts| opts.scoring);
let drag_assist = opts.settings.as_ref().map(|opts| opts.drag_assist);

sqlx::query!(
//language=SQL
r#"
update jig_code
set name = case when $2 then $3 else name end,
direction = coalesce($4, direction),
scoring = coalesce($5, scoring),
drag_assist = coalesce($6, drag_assist)
where code = $1
"#,
code.0,
name.is_some(),
name.flatten(),
direction.map(|d| d as i16),
scoring,
drag_assist,
)
.execute(db)
.await?;

Ok(())
}

fn session_create_error_or_continue(db_err: Box<dyn DatabaseError>) -> Result<(), error::JigCode> {
let constraint = db_err.downcast_ref::<PgDatabaseError>().constraint();

Expand Down
4 changes: 4 additions & 0 deletions backend/api/src/http/endpoints/jig.rs
Original file line number Diff line number Diff line change
Expand Up @@ -573,6 +573,10 @@ pub fn configure(cfg: &mut ServiceConfig) {
<jig::codes::Create as ApiEndpoint>::Path::PATH,
jig::codes::Create::METHOD.route().to(codes::create),
)
.route(
<jig::codes::Update as ApiEndpoint>::Path::PATH,
jig::codes::Update::METHOD.route().to(codes::update),
)
.route(
<jig::codes::JigCodeList as ApiEndpoint>::Path::PATH,
jig::codes::JigCodeList::METHOD
Expand Down
17 changes: 17 additions & 0 deletions backend/api/src/http/endpoints/jig/codes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,23 @@ pub async fn create(
Ok(HttpResponse::Created().json(jig_code))
}

pub async fn update(
db: Data<PgPool>,
claims: TokenUser,
path: web::Path<JigCode>,
req: Json<<codes::Update as ApiEndpoint>::Req>,
) -> Result<HttpResponse, error::JigCode> {
let code = path.into_inner();
let req = req.into_inner();
let user_id = claims.user_id();

db::jig::is_users_code(&*db, user_id, code).await?;

db::jig::codes::update(&db, code, &req).await?;

Ok(HttpResponse::NoContent().into())
}

/// Get all jig codes for user.
pub async fn list_user_codes(
db: Data<PgPool>,
Expand Down
16 changes: 16 additions & 0 deletions backend/api/tests/integration/jig/codes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,22 @@ async fn create_and_list(port: u16) -> anyhow::Result<()> {

insta::assert_json_snapshot!(format!("{}-1",name), body, { ".**.index" => "[index]", ".**.created_at" => "[timestamp]", ".**.expires_at" => "[timestamp]" });

let resp = client
.patch(&format!(
"http://0.0.0.0:{}/v1/jig/codes/{}",
port,
body.index.to_string()
))
.json(&serde_json::json!({
"name": "test-name"
}))
.login()
.send()
.await?
.error_for_status()?;

assert_eq!(resp.status(), StatusCode::NO_CONTENT);

let _resp = client
.post(&format!("http://0.0.0.0:{}/v1/jig/codes", port))
.json(&serde_json::json!({
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ expression: body
{
"index": "[index]",
"jig_id": "3a71522a-cd77-11eb-8dc1-af3e35f7c743",
"name": null,
"name": "test-name",
"settings": {
"direction": "rtl",
"scoring": false,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,9 @@ use futures::join;
use shared::{
api::endpoints,
domain::jig::{
codes::{JigCodeListPath, JigCodeListRequest},
codes::{
JigCode, JigCodeListPath, JigCodeListRequest, JigCodeUpdatePath, JigCodeUpdateRequest,
},
JigGetLivePath,
},
};
Expand Down Expand Up @@ -41,4 +43,17 @@ impl JigCodes {
let jig = bail_on_err!(jig);
self.jig.set(Some(jig));
}

pub fn save_name(self: &Rc<Self>, code: JigCode, new_name: String) {
spawn_local(async move {
let req = JigCodeUpdateRequest {
name: Some(Some(new_name)),
settings: None,
};
let _ =
endpoints::jig::codes::Update::api_with_auth(JigCodeUpdatePath(code), Some(req))
.await
.toast_on_err();
});
}
}
80 changes: 68 additions & 12 deletions frontend/apps/crates/entry/classroom/src/codes/jig_codes/dom.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
use components::asset_card::render_asset_card;
use dominator::{clone, html, DomBuilder};
use futures_signals::{signal::SignalExt, signal_vec::SignalVecExt};
use dominator::{clone, html, DomBuilder, EventOptions};
use futures_signals::{
signal::{not, Mutable, SignalExt},
signal_vec::SignalVecExt,
};
use shared::domain::jig::TextDirection;
use std::rc::Rc;
use utils::{
component::Component,
date_formatters, link,
date_formatters, events,
routes::{ClassroomCodesRoute, ClassroomRoute, Route},
};
use web_sys::ShadowRoot;
Expand All @@ -32,10 +35,10 @@ impl Component<JigCodes> for Rc<JigCodes> {
.class("codes")
.child(html!("div", {
.class("header")
// .child(html!("span", {
// .class("cell")
// .text("Name")
// }))
.child(html!("span", {
.class("cell")
.text("Name")
}))
.child(html!("span", {
.class("cell")
.text("Code")
Expand All @@ -58,12 +61,65 @@ impl Component<JigCodes> for Rc<JigCodes> {
// }))
}))
.children_signal_vec(state.codes.signal_vec_cloned().map(clone!(state => move |code| {
link!(Route::Classroom(ClassroomRoute::Codes(ClassroomCodesRoute::JigCodeSession(state.jig_id, code.index))), {
let editing = Mutable::new(false);
let original_name = Mutable::new(code.name.clone().unwrap_or_default());
let name = Mutable::new(original_name.get_cloned());
let route = Route::Classroom(ClassroomRoute::Codes(ClassroomCodesRoute::JigCodeSession(state.jig_id, code.index)));
html!("a", {
.class("code")
// .child(html!("span", {
// .class("cell")
// .text(&code.name.unwrap_or_default())
// }))
.prop("href", route.to_string())
.event_with_options(&dominator::EventOptions { preventable: true, bubbles: true }, clone!(editing => move |e:events::Click| {
e.prevent_default();
if !editing.get() {
route.go_to();
}
}))
.child(html!("div", {
.class("cell")
.class("name")
.child(html!("input", {
.prop_signal("readOnly", not(editing.signal()))
.prop_signal("value", name.signal_cloned())
.focused_signal(editing.signal())
}))
.child(html!("div", {
.class("actions")
.event_with_options(&EventOptions { preventable: true, bubbles: true }, move |e: events::Click| {
e.stop_propagation();
e.prevent_default();
})
.children_signal_vec(editing.signal().map(clone!(state => move |e| match e {
false => vec![
html!("fa-button", {
.prop("icon", "fa-regular fa-pen-to-square")
.prop("title", "Edit")
.event(clone!(editing => move |_: events::Click| {
editing.set(true);
}))
})
],
true => vec![
html!("fa-button", {
.prop("icon", "fa-regular fa-floppy-disk")
.prop("title", "Save")
.event(clone!(state, editing, name, original_name => move |_: events::Click| {
editing.set(false);
original_name.set(name.get_cloned());
state.save_name(code.index, name.get_cloned());
}))
}),
html!("fa-button", {
.prop("icon", "fa-regular fa-xmark")
.prop("title", "Cancel")
.event(clone!(editing, name, original_name => move |_: events::Click| {
editing.set(false);
name.set(original_name.get_cloned());
}))
}),
],
})).to_signal_vec())
}))
}))
.child(html!("span", {
.class("cell")
.text(&code.index.to_string())
Expand Down
Loading

0 comments on commit 5a592e9

Please sign in to comment.