From 612cee76a0a5a7f1f1b06a183d353a7b4570033c Mon Sep 17 00:00:00 2001 From: Arthur Poulet Date: Wed, 23 Jun 2021 23:52:20 +0200 Subject: [PATCH 1/6] js: add an ajax wrapper --- public/js/ajax.js | 34 ++++++++++++++++++++++++++++++++++ src/views/layout.slim | 14 +++----------- 2 files changed, 37 insertions(+), 11 deletions(-) create mode 100644 public/js/ajax.js diff --git a/public/js/ajax.js b/public/js/ajax.js new file mode 100644 index 0000000..79184f3 --- /dev/null +++ b/public/js/ajax.js @@ -0,0 +1,34 @@ +async function ajax({ + method = "GET", + url = ".", + body = undefined, + headers = {}, + on_success = () => {}, + on_failure = () => {}, +}) { + const xhttp = new XMLHttpRequest(); + const return_on_sent = new Promise((resolve, reject) => { + try { + xhttp.onreadystatechange = () => { + if (xhttp.readyState == 4) { + const response_status = String(xhttp.status); + if (/2\d\d/.test(response_status)) { + resolve(on_success(xhttp.responseText, xhttp)); + } else { + resolve(on_failure(xhttp.responseText, xhttp)); + } + } + }; + } catch (err) { + reject(err); + } + }); + + xhttp.open(method, url, true); + Object.keys(headers).forEach((header_key) => { + xhttp.setRequestHeader(header_key, headers[header_key]); + }) + xhttp.send(body); + + return return_on_sent; +} diff --git a/src/views/layout.slim b/src/views/layout.slim index f2108c6..98100a5 100644 --- a/src/views/layout.slim +++ b/src/views/layout.slim @@ -2,7 +2,8 @@ doctype html html lang="en" head /! Required meta tags - title Life Pex + title + | Life Pex meta charset="utf-8" / meta content="width=device-width, initial-scale=1" name="viewport" / /! Bootstrap CSS @@ -46,16 +47,6 @@ html lang="en" li.nav-item a.btn.btn-lg.btn-dark href="/logout" Logout - / li.nav-item.dropdown - / a.btn.btn-lg.btn-dark.nav-link.dropdown-toggle#nav-drop-more href="#" role="button" data-bs-toggle="dropdown" aria-expanded="false" - / | More - / ul.dropdown-menu aria-labelledby="nav-drop-more" - / li - / a.btn.btn-lg.btn-dark href="/password" Change password - / li - / a.btn.btn-lg.btn-dark href="/about" About lifepex - / li - / a.btn.btn-lg.btn-dark href="/logout" Logout - else li.nav-item a.btn.btn-lg.btn-dark href="/login" Login @@ -81,3 +72,4 @@ html lang="en" / script src="https://code.jquery.com/jquery-3.4.1.slim.min.js" integrity="sha384-J6qa4849blE2+poT4WnyKhv5vZF5SrPo0iEjwBvKU7imGFAV0wwj1yYfoRSJoZ+n" crossorigin="anonymous" script src="https://cdn.jsdelivr.net/npm/popper.js@1.16.0/dist/umd/popper.min.js" integrity="sha384-Q6E9RHvbIyZFJoft+2mJbHaEWldlvI9IOYy5n3zV9zzTtmI3UksdQRVvoxMfooAo" crossorigin="anonymous" script src="https://cdn.jsdelivr.net/npm/bootstrap@5.0.0-beta3/dist/js/bootstrap.bundle.min.js" integrity="sha384-JEW9xMcG8R+pH31jmWH6WWP0WintQrMb4s7ZOdauHnUtxwoG2vI5DkLtS3qm9Ekf" crossorigin="anonymous" + script src="/js/ajax.js" -- 2.44.0 From 0f25ed78e0cb0b64c2a6d40261cc15335e6eac91 Mon Sep 17 00:00:00 2001 From: Arthur Poulet Date: Wed, 23 Jun 2021 23:52:52 +0200 Subject: [PATCH 2/6] csrf: fix csrf for api --- src/app.rb | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/app.rb b/src/app.rb index 8eed0b0..f097359 100755 --- a/src/app.rb +++ b/src/app.rb @@ -71,7 +71,8 @@ class LifePex::App < Sinatra::Base set :session_secret, LifePex::SECRET enable :sessions - use Rack::Csrf, skip: ["*:/api*"] if ENV["LIFEPEX_ENV"] != "test" + # use Rack::Csrf, skip: ["POST:/api/*"], :raise => (LifePex::APP_ENV != "production") if LifePex::APP_ENV != "test" + set :protection, :except => :json_csrf if LifePex::APP_ENV != "test" LifePex::Systems.constants .filter { |system| system.to_s =~ /System$/ } -- 2.44.0 From 3d7028e291b02409aeb6eb7063edb929a0928875 Mon Sep 17 00:00:00 2001 From: Arthur Poulet Date: Wed, 23 Jun 2021 23:53:47 +0200 Subject: [PATCH 3/6] forms: make dynamic forms for + and - It still requires a few polishing touches. But it is 100% functional --- public/js/index.js | 68 +++++++++++++++++++++++++++++++++++++++-- public/js/recap.js | 21 ++++++------- src/systems/pex.rb | 4 +-- src/systems/user_pex.rb | 16 ++++++++-- src/views/index.slim | 4 +-- 5 files changed, 92 insertions(+), 21 deletions(-) diff --git a/public/js/index.js b/public/js/index.js index 08b3932..d8143f6 100644 --- a/public/js/index.js +++ b/public/js/index.js @@ -17,10 +17,70 @@ function toggle(node) { } } -document.addEventListener("DOMContentLoaded", (_event) => { - const togglers = document.querySelectorAll('.pex-editor-toggler'); +Array.toObject = function (arr) { + return arr.reduce((base, current) => { + base[current[0]] = current[1]; + return base; + }, {}); +} - togglers.forEach((t) => { +function __map__(cb = (e) => e) { + const arr = []; + for (i = 0; i < this.length; i++) { arr.push(cb(this[i])) }; + return arr; +} + +HTMLCollection.prototype.map = __map__; +NodeList.prototype.map = __map__; +NamedNodeMap.prototype.map = __map__; +HTMLInputElement.prototype.attributesObject = function () { + return Array.toObject(this.attributes.map((attr) => [attr.name, attr.value])); +} +HTMLFormElement.prototype.attributesObject = function () { + return Array.toObject(this.attributes.map((attr) => [attr.name, attr.value])); +} + +function parseCookie(str) { + return str + .split(";") + .map((v) => v.split("=")) + .reduce((acc, v) => { + acc[decodeURIComponent(v[0].trim())] = decodeURIComponent(v[1].trim()); + return acc; + }, {}); +} + +function userpexValidation(event) { + event.preventDefault(); + + const attributes = event.target.attributesObject(); + + const fields = event.target.childNodes.map().filter(e => e.nodeName == 'INPUT'); + const fields_attributes = Array.toObject(fields.map(attr => [attr.name, attr.value])); + + ajax({ + method: fields_attributes["type"] == "-" ? "DELETE" : "POST", + url: `/api/user-pex/v1/pexs/${fields_attributes["id"]}/validation`, + body: JSON.stringify({ date: parseCookie(document.cookie).date }), + headers: { Accept: "application/json", "Content-Type": "application/json" }, + on_success: (body) => { + // const json_output = JSON.parse(body); + const change_step = fields_attributes["type"] == "-" ? -1 : 1; + const text_target = event.target.parentNode.parentElement.childNodes[3]; + const new_text = Number(text_target.textContent) + change_step; + text_target.textContent = String(new_text); + // console.log("OK", event) + }, + on_failure: (body, req) => { + // console.log("KO", event); + }, + }); +} + +document.addEventListener("DOMContentLoaded", (_event) => { + const editor_togglers = document.querySelectorAll('.pex-editor-toggler'); + + editor_togglers.forEach((t) => { const name = t.attributes.name.value; const pex_editor = document.querySelector(`.pex-editor[name="${name}"]`); hide(pex_editor); @@ -29,4 +89,6 @@ document.addEventListener("DOMContentLoaded", (_event) => { toggle(pex_editor); }); }); + + }); diff --git a/public/js/recap.js b/public/js/recap.js index b2ed589..59b12bf 100644 --- a/public/js/recap.js +++ b/public/js/recap.js @@ -15,16 +15,15 @@ function setupChart(...cumuls) { }); } -document.addEventListener('DOMContentLoaded', function () { - const xhttp = new XMLHttpRequest(); - xhttp.onreadystatechange = () => { - if (xhttp.readyState == 4 && xhttp.status == 200) { - const json_output = JSON.parse(xhttp.responseText); +document.addEventListener('DOMContentLoaded', async function () { + ajax({ + method: "GET", + url: "/api/pex/v1/recap", + body: null, + headers: { Accept: "application/json" }, + on_success: (body) => { + const json_output = JSON.parse(body); setupChart(...json_output.pex_tables); - } - }; - - xhttp.open("GET", "/api/pex/v1/recap", true); - xhttp.setRequestHeader("Accept", "application/json"); - xhttp.send(); + }, + }); }); diff --git a/src/systems/pex.rb b/src/systems/pex.rb index 60400d3..f6c70b5 100644 --- a/src/systems/pex.rb +++ b/src/systems/pex.rb @@ -26,12 +26,12 @@ class LifePex::Systems::PexSystem < LifePex::Systems::AuthSystem end get "/today", auth: [] do - cookies["date"] = "today" + cookies.set "date", { value: "today", httponly: false } redirect "/" end get "/yesterday", auth: [] do - cookies["date"] = "yesterday" + cookies.set "date", { value: "yesterday", httponly: false } redirect "/" end diff --git a/src/systems/user_pex.rb b/src/systems/user_pex.rb index 93231db..3831ff8 100644 --- a/src/systems/user_pex.rb +++ b/src/systems/user_pex.rb @@ -4,11 +4,21 @@ require_relative "./api_response" class LifePex::Systems::UserPexSystem < LifePex::Systems::AuthSystem include JSON::API include LifePex::Systems::ApiResponse + set :protection, :except => [:frame_options, :json_csrf] + + def date_input_convertor(date) + if date && Date.respond_to?(date) + Date.send date + elsif date == "yesterday" + Date.today - 1 + else + Date.today + end + end extend DocMyRoutes::Annotatable # included by PexSystem register Sinatra::Namespace # included by PexSystem namespace '/api/user-pex/v1' do - namespace '/pexs' do summary 'Get the amount of user_pex for a given day' @@ -30,7 +40,7 @@ class LifePex::Systems::UserPexSystem < LifePex::Systems::AuthSystem status_codes [200] post "/:pex_id/validation", auth: [], provides: 'json' do pex_id = params["pex_id"] - date = json_params["date"] || Date.today.to_s + date = date_input_convertor(json_params["date"]) user_pex = LifePex::UserPex.new( user_id: current_user_id, pex_id: pex_id, @@ -48,7 +58,7 @@ class LifePex::Systems::UserPexSystem < LifePex::Systems::AuthSystem status_codes [200] delete "/:pex_id/validation", auth: [], provides: 'json' do pex_id = params["pex_id"] - date = json_params["date"] || Date.today.to_s + date = date_input_convertor(json_params["date"]) user_pex = LifePex::UserPex.find( user_id: current_user_id, pex_id: pex_id, diff --git a/src/views/index.slim b/src/views/index.slim index 2279695..58e4285 100644 --- a/src/views/index.slim +++ b/src/views/index.slim @@ -48,7 +48,7 @@ td.col-8=pex_by_model[:name] td.col-1.center - if pex_by_model[:user_pexs][:amount] > 0 - form method="POST" action="/" + form method="POST" action="/" onsubmit="return userpexValidation(event)" == csrf_tag input type="hidden" name="id" value=pex_by_model[:id] input type="hidden" name="type" value="-" @@ -57,7 +57,7 @@ td.col-1.center =pex_by_model[:user_pexs][:amount] td.col-1.center - form method="POST" action="/" + form method="POST" action="/" onsubmit="return userpexValidation(event)" == csrf_tag input type="hidden" name="id" value=pex_by_model[:id] input type="hidden" name="type" value="+" -- 2.44.0 From 7eb4b680a5f2a860d9e6730ae04bfe1c4684dd31 Mon Sep 17 00:00:00 2001 From: Arthur Poulet Date: Thu, 24 Jun 2021 21:13:56 +0200 Subject: [PATCH 4/6] pex: improve the way to compute total validations --- public/js/index.js | 19 ++++++++------- src/systems/api_response.rb | 3 ++- src/systems/user_pex.rb | 46 ++++++++++++++++++++++++++----------- 3 files changed, 44 insertions(+), 24 deletions(-) diff --git a/public/js/index.js b/public/js/index.js index d8143f6..3cc01b3 100644 --- a/public/js/index.js +++ b/public/js/index.js @@ -22,11 +22,13 @@ Array.toObject = function (arr) { base[current[0]] = current[1]; return base; }, {}); -} +}; function __map__(cb = (e) => e) { const arr = []; - for (i = 0; i < this.length; i++) { arr.push(cb(this[i])) }; + for (i = 0; i < this.length; i++) { + arr.push(cb(this[i])); + } return arr; } @@ -35,10 +37,10 @@ NodeList.prototype.map = __map__; NamedNodeMap.prototype.map = __map__; HTMLInputElement.prototype.attributesObject = function () { return Array.toObject(this.attributes.map((attr) => [attr.name, attr.value])); -} +}; HTMLFormElement.prototype.attributesObject = function () { return Array.toObject(this.attributes.map((attr) => [attr.name, attr.value])); -} +}; function parseCookie(str) { return str @@ -61,15 +63,12 @@ function userpexValidation(event) { ajax({ method: fields_attributes["type"] == "-" ? "DELETE" : "POST", url: `/api/user-pex/v1/pexs/${fields_attributes["id"]}/validation`, - body: JSON.stringify({ date: parseCookie(document.cookie).date }), + body: JSON.stringify({ date: parseCookie(document.cookie).date, force_count_total: true }), headers: { Accept: "application/json", "Content-Type": "application/json" }, on_success: (body) => { - // const json_output = JSON.parse(body); - const change_step = fields_attributes["type"] == "-" ? -1 : 1; + const json_body = JSON.parse(body); const text_target = event.target.parentNode.parentElement.childNodes[3]; - const new_text = Number(text_target.textContent) + change_step; - text_target.textContent = String(new_text); - // console.log("OK", event) + text_target.textContent = String(json_body.count_total); }, on_failure: (body, req) => { // console.log("KO", event); diff --git a/src/systems/api_response.rb b/src/systems/api_response.rb index 03dc539..feae13b 100644 --- a/src/systems/api_response.rb +++ b/src/systems/api_response.rb @@ -4,11 +4,12 @@ module LifePex::Systems::ApiResponse any.to_json end - def api_response_entity(message = nil, entity_type = nil, entity = nil) + def api_response_entity(message = nil, entity_type = nil, entity = nil, **more) api_response({ "message" => message, "entity_type" => entity_type, entity_type => entity, + **more, }.compact) end diff --git a/src/systems/user_pex.rb b/src/systems/user_pex.rb index 3831ff8..b8e5070 100644 --- a/src/systems/user_pex.rb +++ b/src/systems/user_pex.rb @@ -18,13 +18,15 @@ class LifePex::Systems::UserPexSystem < LifePex::Systems::AuthSystem extend DocMyRoutes::Annotatable # included by PexSystem register Sinatra::Namespace # included by PexSystem - namespace '/api/user-pex/v1' do - namespace '/pexs' do + namespace "/api/user-pex/v1" do + namespace "/pexs" do - summary 'Get the amount of user_pex for a given day' - produces 'application/json' + summary "Get the amount of user_pex for a given day" + produces "application/json" status_codes [200] - get "/:pex_id/amount/by-date/:date", auth: [], provides: 'json' do + parameter :id, required: true, type: "integer", in: "path" + parameter :date, required: true, type: "string", in: "path" + get "/:pex_id/amount/by-date/:date", auth: [], provides: "json" do pex_id = params["pex_id"] date = params["date"] count = LifePex::UserPex.where( @@ -32,13 +34,16 @@ class LifePex::Systems::UserPexSystem < LifePex::Systems::AuthSystem pex_id: pex_id, created_at: date, ).count - api_response({ count: count, entity_type: 'user_pex' }) + api_response({ count: count, entity_type: "user_pex" }) end - summary 'Create a new user_pex for a given day' - produces 'application/json' + summary "Create a new user_pex for a given day" + produces "application/json" status_codes [200] - post "/:pex_id/validation", auth: [], provides: 'json' do + parameter :id, required: true, type: "integer", in: "path" + parameter :date, required: false, type: "string", in: "body" + parameter :force_count_total, required: false, type: "bool", in: "body", description: "if true, force the server to computes the total amount of validation compatible after the operation" + post "/:pex_id/validation", auth: [], provides: "json" do pex_id = params["pex_id"] date = date_input_convertor(json_params["date"]) user_pex = LifePex::UserPex.new( @@ -46,17 +51,26 @@ class LifePex::Systems::UserPexSystem < LifePex::Systems::AuthSystem pex_id: pex_id, created_at: date, ).save + count_total = LifePex::UserPex.where( + user_id: current_user_id, + pex_id: pex_id, + created_at: date, + ).count if json_params["force_count_total"] == true api_response_entity( "Successfuly added one user_pex", "user_pex", user_pex, + count_total: count_total, ) end - summary 'Remove an existing user_pex for a given day' - produces 'application/json' + summary "Remove an existing user_pex for a given day" + produces "application/json" status_codes [200] - delete "/:pex_id/validation", auth: [], provides: 'json' do + parameter :id, required: true, type: "integer", in: "path" + parameter :date, required: false, type: "string", in: "body" + parameter :force_count_total, required: false, type: "bool", in: "body", description: "if true, force the server to computes the total amount of validation compatible after the operation" + delete "/:pex_id/validation", auth: [], provides: "json" do pex_id = params["pex_id"] date = date_input_convertor(json_params["date"]) user_pex = LifePex::UserPex.find( @@ -65,11 +79,17 @@ class LifePex::Systems::UserPexSystem < LifePex::Systems::AuthSystem created_at: date, ) if user_pex + count_total = LifePex::UserPex.where( + user_id: current_user_id, + pex_id: pex_id, + created_at: date, + ).count if json_params["force_count_total"] == true user_pex.destroy api_response({ message: "Successfuly destroyed 1 user_pex", entity_type: "user_pex", - count: 1, + count_destroyed: 1, + count_total: count_total, }) else api_error(400, "Nothing to destroy") -- 2.44.0 From 531756e3b60187d448b7fc84d68bf346bb95b4d9 Mon Sep 17 00:00:00 2001 From: Arthur Poulet Date: Thu, 24 Jun 2021 21:39:27 +0200 Subject: [PATCH 5/6] pex: improve pex validation * fix 0 validations * fix user_pex json api --- public/js/index.js | 8 ++++++-- src/systems/api_response.rb | 2 +- src/systems/user_pex.rb | 15 ++++++++++----- src/views/index.slim | 18 +++++++++--------- 4 files changed, 26 insertions(+), 17 deletions(-) diff --git a/public/js/index.js b/public/js/index.js index 3cc01b3..5944fc2 100644 --- a/public/js/index.js +++ b/public/js/index.js @@ -69,6 +69,8 @@ function userpexValidation(event) { const json_body = JSON.parse(body); const text_target = event.target.parentNode.parentElement.childNodes[3]; text_target.textContent = String(json_body.count_total); + const should_hide_decrease_button = json_body.count_total == 0; + event.target.parentNode.parentNode.childNodes[2].childNodes[0].hidden = should_hide_decrease_button; }, on_failure: (body, req) => { // console.log("KO", event); @@ -78,7 +80,6 @@ function userpexValidation(event) { document.addEventListener("DOMContentLoaded", (_event) => { const editor_togglers = document.querySelectorAll('.pex-editor-toggler'); - editor_togglers.forEach((t) => { const name = t.attributes.name.value; const pex_editor = document.querySelector(`.pex-editor[name="${name}"]`); @@ -89,5 +90,8 @@ document.addEventListener("DOMContentLoaded", (_event) => { }); }); - + const userpexvalidation0 = document.querySelectorAll('.userpexvalidationvalue').map().filter(tag => tag.textContent == "0"); + userpexvalidation0.forEach((tag) => { + tag.parentNode.parentNode.childNodes[2].childNodes[0].hidden = true; + }); }); diff --git a/src/systems/api_response.rb b/src/systems/api_response.rb index feae13b..9f2129b 100644 --- a/src/systems/api_response.rb +++ b/src/systems/api_response.rb @@ -17,6 +17,6 @@ module LifePex::Systems::ApiResponse halt(status, { message => message, **more, - }) + }.to_json) end end diff --git a/src/systems/user_pex.rb b/src/systems/user_pex.rb index b8e5070..6dd9347 100644 --- a/src/systems/user_pex.rb +++ b/src/systems/user_pex.rb @@ -12,7 +12,11 @@ class LifePex::Systems::UserPexSystem < LifePex::Systems::AuthSystem elsif date == "yesterday" Date.today - 1 else - Date.today + begin + Date.parse date + rescue => _ + Date.today + end end end @@ -46,6 +50,7 @@ class LifePex::Systems::UserPexSystem < LifePex::Systems::AuthSystem post "/:pex_id/validation", auth: [], provides: "json" do pex_id = params["pex_id"] date = date_input_convertor(json_params["date"]) + api_error(403, "You may not have created or access to this pex id") if LifePex::Pex.where(id: pex_id).select(:user_id).first&.user_id != current_user_id user_pex = LifePex::UserPex.new( user_id: current_user_id, pex_id: pex_id, @@ -73,18 +78,18 @@ class LifePex::Systems::UserPexSystem < LifePex::Systems::AuthSystem delete "/:pex_id/validation", auth: [], provides: "json" do pex_id = params["pex_id"] date = date_input_convertor(json_params["date"]) - user_pex = LifePex::UserPex.find( + user_pex = LifePex::UserPex.where( user_id: current_user_id, pex_id: pex_id, created_at: date, - ) + ).select(:id).first if user_pex + user_pex.destroy count_total = LifePex::UserPex.where( user_id: current_user_id, pex_id: pex_id, created_at: date, ).count if json_params["force_count_total"] == true - user_pex.destroy api_response({ message: "Successfuly destroyed 1 user_pex", entity_type: "user_pex", @@ -92,7 +97,7 @@ class LifePex::Systems::UserPexSystem < LifePex::Systems::AuthSystem count_total: count_total, }) else - api_error(400, "Nothing to destroy") + api_error(404, "Nothing to destroy") end end diff --git a/src/views/index.slim b/src/views/index.slim index 58e4285..62151d6 100644 --- a/src/views/index.slim +++ b/src/views/index.slim @@ -47,17 +47,17 @@ | u td.col-8=pex_by_model[:name] td.col-1.center - - if pex_by_model[:user_pexs][:amount] > 0 - form method="POST" action="/" onsubmit="return userpexValidation(event)" - == csrf_tag - input type="hidden" name="id" value=pex_by_model[:id] - input type="hidden" name="type" value="-" - button.btn.force-1-col type="submit" style="display: block;" - | - + form.userpexvalidation.userpexvalidationdecrease method="POST" action="/" onsubmit="return userpexValidation(event)" + == csrf_tag + input type="hidden" name="id" value=pex_by_model[:id] + input type="hidden" name="type" value="-" + button.btn.force-1-col type="submit" style="display: block;" + | - td.col-1.center - =pex_by_model[:user_pexs][:amount] + .userpexvalidation.userpexvalidationvalue + =pex_by_model[:user_pexs][:amount] td.col-1.center - form method="POST" action="/" onsubmit="return userpexValidation(event)" + form.userpexvalidation.userpexvalidationincrease method="POST" action="/" onsubmit="return userpexValidation(event)" == csrf_tag input type="hidden" name="id" value=pex_by_model[:id] input type="hidden" name="type" value="+" -- 2.44.0 From f6a951f704315bff23c84dca88f4b74205aac104 Mon Sep 17 00:00:00 2001 From: Arthur Poulet Date: Thu, 24 Jun 2021 22:10:58 +0200 Subject: [PATCH 6/6] userpex: improve error and fix test databases --- .env.sample | 2 +- README.md | 15 ++++++++++++++- public/js/ajax.js | 2 +- public/js/error.js | 11 +++++++++++ public/js/index.js | 2 +- src/app.rb | 7 +++++-- src/views/layout.slim | 3 ++- 7 files changed, 35 insertions(+), 7 deletions(-) create mode 100644 public/js/error.js diff --git a/.env.sample b/.env.sample index f5a8c70..bd4f0e7 100644 --- a/.env.sample +++ b/.env.sample @@ -2,4 +2,4 @@ LIFEPEX_DB=sqlite://sqlite.db LIFEPEX_BIND=127.0.0.1 LIFEPEX_BASE_URL= LIFEPEX_SECRET= -LIFEPEX_ENV= +PORT= \ No newline at end of file diff --git a/README.md b/README.md index ec59c93..407d316 100644 --- a/README.md +++ b/README.md @@ -81,4 +81,17 @@ xdg-open ./public/doc/index.html ### Testing -If you want to run the test, simply type `rake test` (you will need the startup env variable to be set first). +Generate first a specific configuration file + +``` +cp .env.local .env.test.local +editor .env.test.local # don't forget to set a new database !!! +``` + +Then init the database + +``` +LIFEPEX_ENV=test ./init/database.rb +``` + +Then if you want to run the test, simply type `rake test` (you will need the startup env variable to be set first). diff --git a/public/js/ajax.js b/public/js/ajax.js index 79184f3..9e9fc31 100644 --- a/public/js/ajax.js +++ b/public/js/ajax.js @@ -27,7 +27,7 @@ async function ajax({ xhttp.open(method, url, true); Object.keys(headers).forEach((header_key) => { xhttp.setRequestHeader(header_key, headers[header_key]); - }) + }); xhttp.send(body); return return_on_sent; diff --git a/public/js/error.js b/public/js/error.js new file mode 100644 index 0000000..aaea054 --- /dev/null +++ b/public/js/error.js @@ -0,0 +1,11 @@ +function flashError(message) { + const new_flash_error = document.createElement('p'); + new_flash_error.classList.add("alert"); + new_flash_error.classList.add("alert-danger"); + new_flash_error.classList.add("alert-dismissible"); + new_flash_error.classList.add("fade"); + new_flash_error.classList.add("show"); + new_flash_error.setAttribute("role", "alert"); + new_flash_error.innerHTML = `${message}