diff --git a/.gitignore b/.gitignore
index 7cdb778..4870095 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,3 +1,5 @@
.stack-work/
haskellDB.cabal
-*~
\ No newline at end of file
+*~
+*.lock
+*.cfg
\ No newline at end of file
diff --git a/.vscode/settings.json b/.vscode/settings.json
new file mode 100644
index 0000000..f429a43
--- /dev/null
+++ b/.vscode/settings.json
@@ -0,0 +1,4 @@
+{
+ "editor.fontFamily": "iosevka",
+ "editor.fontLigatures": true
+}
\ No newline at end of file
diff --git a/ChangeLog.md b/ChangeLog.md
index 266afbe..fc57a6a 100644
--- a/ChangeLog.md
+++ b/ChangeLog.md
@@ -1,3 +1,21 @@
# Changelog for haskellDB
## Unreleased changes
+
+## [V0.1] - 2019/10/09
+### Added
+- First working version of the software!
+
+## [V0.2] - 2019/10/22
+### Added
+- Functions for tracking use of processings and filaments
+
+### Fixed
+- Dropdown menu behaviour in the client interface
+
+## [V0.3] - 2019/11/03
+### Added
+- This CHANGELOG
+
+### Changed
+- There is no more need for storing passwords in plain text! Check the README for more info!
diff --git a/README.md b/README.md
index a296404..c22f1df 100644
--- a/README.md
+++ b/README.md
@@ -1,3 +1,50 @@
# haskellDB
-An application aiming to provide access to a database for Maker Station
\ No newline at end of file
+An application aiming to provide a (not so) easy way to manage the works commissioned to Maker Station.
+
+It's a lovely mix of Haskell, HTML, Javascript and CSS.
+
+## How to make it work
+
+### Requirements:
+- PostgreSQL 11.5
+- GHCI >= 8.6.5
+- Stack
+- Internet browser of your choice
+
+### Preparation:
+- Install the softwares listed above
+- Clone this repository
+- Use "db_schema.sql" to generate the database (use pgAdmin, or whatever tool you are most comfortable with)
+- Create a file named "server.cfg" in the repository directory, containing the following lines:
+ ```
+ db="name_of_your_db"
+ dbLocation="localhost"
+ dbPort=5432
+ dbUser="your_db_user"
+ port=8080
+ apiName="MSManager"
+ ```
+ Changes could be necessary, depending on your postgres installation and necessities.
+- Move to the repository directory on the command line
+- Use `stack ghci`
+
+The following steps are needed to create the first admin user.
+
+- In the GHCI prompt use `connectWithInfo "localhost" 5432 "your_db_user" "your_db_user_password" "name_of_your_db" >>= insertUser "username" "password"`, changing the function arguments with the ones you need and wish to use.
+- Open pgAdmin (or whatever tool you use to execute queries in the database) and execute the query `UPDATE utenti SET admin = true WHERE username = 'username'`, changing username with the one chosen in the previous step.
+
+You have now created your first admin user!
+
+- Return to the GHCI prompt, use `:main PASSWORD`, where PASSWORD is the password of the PostgreSQL user you used in the "server.cfg" file
+ - If you wish to execute the server outside of the GHCI prompt (and that would be great!) use Stack to build and install the application, and call it from the command line with PASSWORD as the first argument
+- Open your browser, and connect to `localhost:8080`
+ - The web interface was tested on Firefox, but it should work on other browsers too (please, avoid Internet Explorer)
+- That's it. Log in, and everything should work!
+
+## A little disclaimer
+- This system doesn't aim to be safe: at the moment, I don't have the knowledge to make it safe. So, it isn't. Don't use as if it were. In particular, the main problems are:
+ - there is no safe connection to the server (no https), so your passwords are sent without encryption
+- This is not easy to install, and some operations are doable only through SQL queries: again, I have neither the knowledge nor the time to resolve this
+- The user interface is probably badly written, and not accessible: I'm still learning, and I will (probably) correct it in the future
+- The Haskell code works, but it could be better: I repeat, I'm still learning
\ No newline at end of file
diff --git a/app/Main.hs b/app/Main.hs
index de1c1ab..8477c45 100644
--- a/app/Main.hs
+++ b/app/Main.hs
@@ -1,6 +1,27 @@
+{-# LANGUAGE OverloadedStrings #-}
+
module Main where
-import Lib
+import Data.Semigroup ((<>))
+import Options.Applicative
+import Query
+import Schema
+import Server
+
+configFile :: FilePath
+configFile = "server.cfg"
+
+passwordParser :: Parser String
+passwordParser = argument str (metavar "PASSWORD")
+
+pswdInfo :: ParserInfo String
+pswdInfo =
+ info (passwordParser <**> helper)
+ ( fullDesc
+ <> progDesc "Starts the server application using the PASSWORD for connecting to the database"
+ )
main :: IO ()
-main = someFunc
+main = do
+ pswd <- execParser pswdInfo
+ parseConfig configFile >>= (flip runServer pswd)
diff --git a/client/common.js b/client/common.js
new file mode 100644
index 0000000..1e18d14
--- /dev/null
+++ b/client/common.js
@@ -0,0 +1,406 @@
+/*
+----------------------------------------------------------------------------------------
+GENERAL FUNCTIONS
+----------------------------------------------------------------------------------------
+*/
+
+/* fetches data from the given route, and executes the given action */
+function fetchData(route, action) {
+ fetch(window.location.protocol + "//" + window.location.host.toString() + "/" + route).then(action);
+}
+
+/* sets the content of the "result_area" element with the results from the given route inserted in the given table by the tableSetter */
+function setTable(route, table, tableSetter) {
+ fetchData(route, function(response) {
+ if (response.ok) {
+ response.json().then(jsonResponse => {
+ if (checkIfJsonIsError(jsonResponse) && jsonResponse["response"]["code"] != "200") {
+ showClearElem("result_area");
+ var error = document.createElement("P");
+ error.innerHTML = jsonResponse["response"]["message"];
+ error.classList.add("error");
+ resultDiv.appendChild(error);
+ } else if (!checkIfJsonIsError(jsonResponse)) {
+ setTableFromData(jsonResponse, table, tableSetter);
+ }
+ });
+ }
+ });
+}
+
+/* sets the content of the "result_area" element with the given data, inserted in the given table by the tableSetter */
+function setTableFromData(data, table, tableSetter) {
+ var resultDiv = document.getElementById("result_area");
+ document.getElementById("result_area").classList.remove("hidden");
+ tableSetter(data, table);
+ resultDiv.appendChild(table);
+}
+
+/* clears the page */
+function clearPage() {
+ var hideableElements = document.getElementsByClassName("hideable");
+ for (var i = 0; i < hideableElements.length; i++) {
+ hideableElements[i].classList.add("hidden");
+ while (hideableElements[i].firstChild) {
+ hideableElements[i].removeChild(hideableElements[i].firstChild);
+ }
+ }
+ var groupHideableElements = document.getElementsByClassName("group_hideable");
+ for (var i = 0; i < groupHideableElements.length; i++) {
+ groupHideableElements[i].classList.add("hidden");
+ }
+}
+
+/* makes visible the element with elemId, as if it was created anew */
+function showClearElem(elemId) {
+ var elem = document.getElementById(elemId);
+ if (elem != null) {
+ while(elem.firstChild) {
+ elem.removeChild(elem.firstChild);
+ }
+ elem.classList.remove("hidden");
+ while (elem.parentElement) {
+ elem.parentElement.classList.remove("hidden");
+ elem = elem.parentElement;
+ }
+ }
+}
+
+/* creates a variable number of radio buttons into the form, for the table with the given tableSetter, with [name, label, route] properties */
+function createFiltersRadioButtons(form, table, tableSetter, name, ...radioButtons) {
+ radioButtons.forEach(button => {
+ var input = document.createElement("input");
+ var label = document.createElement("label");
+ input.type = "radio";
+ input.name = name;
+ input.id = button[0];
+ input.onclick = () => { if (input.checked) { setTable(button[2], table, tableSetter); } };
+ label.for = input.id;
+ label.innerHTML = button[1];
+ form.appendChild(input);
+ form.appendChild(label);
+ form.appendChild(document.createElement("br"));
+ });
+}
+
+/* creates a variable number of checkBoxes into the form, with [name, value, label] properties */
+function createFiltersCheckBoxes(form, ...checkBoxes) {
+ checkBoxes.forEach(box => {
+ var input = document.createElement("input");
+ var label = document.createElement("label");
+ input.type = "checkbox";
+ input.name = box[0];
+ input.value = box[1];
+ input.id = box[0];
+ label.for = input.id;
+ label.innerHTML = box[2];
+ form.appendChild(input);
+ form.appendChild(label);
+ form.appendChild(document.createElement("br"));
+ });
+}
+
+/* creates a variable number of checkBoxes into the form, with [name, value, label] properties, that have the "hidden" class */
+function createHiddenFiltersCheckBoxes(form, ...checkBoxes) {
+ checkBoxes.forEach(box => {
+ var input = document.createElement("input");
+ input.type = "checkbox";
+ input.name = box[0];
+ input.value = box[1];
+ input.checked = true;
+ input.id = box[0] + "_hidden";
+ input.classList.add("hidden");
+ form.appendChild(input);
+ });
+}
+
+/* change the checked boxes based on the value of the "cf_select" element */
+function changeCheckedBoxes() {
+ var list = document.getElementById("cf_select");
+ fetchData("people", response => {
+ if (response.ok) {
+ response.json().then(jsonResponse => {
+ if (!checkIfJsonIsError(jsonResponse)) {
+ for (const index in jsonResponse) {
+ if (list.value == jsonResponse[index]._personCf) {
+ document.getElementById("partner").checked = jsonResponse[index]._personSocio;
+ document.getElementById("cutter").checked = jsonResponse[index]._personOperatoreIntagliatrice;
+ document.getElementById("printer").checked = jsonResponse[index]._personOperatoreStampante;
+ }
+ }
+ }
+ });
+ }
+ });
+}
+
+/* disables the invisibles checkboxes if the visible ones are checked */
+function disableCheckBoxes(form) {
+ var checkBoxes = document.getElementsByTagName("input");
+ for (const index in checkBoxes) {
+ var box = checkBoxes.item(index);
+ if (box.type == "checkbox" && box.classList.contains("hidden")) {
+ var shownId = box.id.split("_")[0];
+ var shownBox = document.getElementById(shownId);
+ if (shownBox.checked) {
+ box.disabled = true;
+ }
+ }
+ }
+}
+
+/* sends to the said route the content of the given form, giving a visual notice of the result of the operation */
+function sendFormData(formId, route) {
+ var form = document.getElementById(formId);
+ var fd = new FormData(form);
+ var post = {
+ method: "POST",
+ body: fd
+ };
+ var errors = form.getElementsByClassName("error");
+ while (errors.length > 0) {
+ errors.item(0).parentNode.removeChild(errors.item(0));
+ }
+ var oks = form.getElementsByClassName("ok");
+ while (oks.length > 0) {
+ oks.item(0).parentNode.removeChild(oks.item(0));
+ }
+ fetch(window.location.protocol + "//" + window.location.host.toString() + "/" + route, post).then(function(response) {
+ if (response.ok) {
+ response.json().then(jsonResponse => {
+ if (checkIfJsonIsError(jsonResponse) && jsonResponse["response"]["code"] != "200") {
+ var error = document.createElement("P");
+ error.innerHTML = jsonResponse["response"]["message"];
+ error.classList.add("error");
+ form.appendChild(error);
+ } else if (checkIfJsonIsError(jsonResponse) && jsonResponse["response"]["code"] == "200") {
+ var ok = document.createElement("P");
+ ok.innerHTML = jsonResponse["response"]["message"];
+ ok.classList.add("ok");
+ form.appendChild(ok);
+ }
+ });
+ }
+ });
+}
+
+/* sends to the said route the content of the given form, doing something with the results */
+function useChosenData(formId, route, action) {
+ var form = document.getElementById(formId);
+ var fd = new FormData(form);
+ var post = {
+ method: "POST",
+ body: fd
+ };
+ var errors = form.getElementsByClassName("error");
+ while (errors.length > 0) {
+ errors.item(0).parentNode.removeChild(errors.item(0));
+ }
+ var oks = form.getElementsByClassName("ok");
+ while (oks.length > 0) {
+ oks.item(0).parentNode.removeChild(oks.item(0));
+ }
+ fetch(window.location.protocol + "//" + window.location.host.toString() + "/" + route, post).then(function(response) {
+ if (response.ok) {
+ response.json().then(jsonResponse => {
+ if (checkIfJsonIsError(jsonResponse)) {
+ var error = document.createElement("P");
+ error.innerHTML = jsonResponse["response"]["message"];
+ error.classList.add("error");
+ form.appendChild(error);
+ } else {
+ action(jsonResponse);
+ }
+ });
+ }
+ });
+}
+
+/* creates a text input, with the given name and placeholder */
+function createTextInput(name, placeholder) {
+ var input = document.createElement("input");
+ input.type = "text";
+ input.name = name;
+ input.placeholder = placeholder;
+ return input;
+}
+
+/* creates a table with the given id, id of the headers and columns [headerId, name] */
+function createTable(tableId, headersId, ...headers) {
+ var resultTable = document.createElement("table");
+ resultTable.id = tableId;
+ var head = document.createElement("thead");
+ var body = document.createElement("tbody");
+ body.id = "table_body";
+ var header = document.createElement("tr");
+ head.appendChild(header);
+ header.id = headersId;
+ resultTable.appendChild(head);
+ headers.forEach(h => {
+ var th = document.createElement("th");
+ th.id = h[0];
+ th.innerHTML = h[1];
+ header.appendChild(th);
+ });
+ resultTable.appendChild(body);
+ return resultTable;
+}
+
+function checkIfJsonIsError(json) {
+ return json.hasOwnProperty("response");
+}
+
+/* function to create the form to assign an A element to a B element, using the given route */
+function assignAToB(route, ARoute, BRoute, AName, BName, getACode, getAString, getBCode, getBString) {
+ clearPage();
+ var form = document.getElementById("input_form");
+ fetchData(ARoute, responseA => {
+ if (responseA.ok) {
+ responseA.json().then(jsonResponseA => {
+ if (checkIfJsonIsError(jsonResponseA) && jsonResponseA["response"]["code"] != "200") {
+ var error = document.createElement("P");
+ error.innerHTML = jsonResponseA["response"]["message"];
+ error.classList.add("error");
+ form.appendChild(error);
+ } else if (!checkIfJsonIsError(jsonResponseA)) {
+ fetchData(BRoute, responseB => {
+ if (responseB.ok) {
+ responseB.json().then(jsonResponseB => {
+ if (checkIfJsonIsError(jsonResponseB) && jsonResponseB["response"]["code"] != "200") {
+ var error = document.createElement("P");
+ error.innerHTML = jsonResponseB["response"]["message"];
+ error.classList.add("error");
+ form.appendChild(error);
+ } else if (!checkIfJsonIsError(jsonResponseB)) {
+ showClearElem(form.id);
+ var AList = document.createElement("select");
+ AList.name = AName;
+ AList.id = AName + "_select";
+ form.appendChild(AList);
+ form.appendChild(document.createElement("br"));
+ for (const index in jsonResponseA) {
+ var elem = document.createElement("option");
+ elem.value = getACode(jsonResponseA[index]);
+ elem.innerHTML = getAString(jsonResponseA[index]);
+ AList.appendChild(elem);
+ }
+ var BList = document.createElement("select");
+ BList.name = BName;
+ BList.id = BName + "_select";
+ form.appendChild(BList);
+ form.appendChild(document.createElement("br"));
+ for (const index in jsonResponseB) {
+ var elem = document.createElement("option");
+ elem.value = getBCode(jsonResponseB[index]);
+ elem.innerHTML = getBString(jsonResponseB[index]);
+ BList.appendChild(elem);
+ }
+ var okButton = document.createElement("button");
+ okButton.type = "button";
+ okButton.innerHTML = "Assegna";
+ okButton.onclick = () => sendFormData("input_form", route);
+ form.appendChild(okButton);
+ }
+ });
+ }
+ });
+ }
+ });
+ }
+ });
+}
+
+/* function to insert a new work into the given route (either a print or a cut) */
+function insertWork(route) {
+ clearPage();
+ var form = document.getElementById("input_form");
+ fetchData("people", response => {
+ if (response.ok) {
+ response.json().then(jsonResponse => {
+ if (checkIfJsonIsError(jsonResponse) && jsonResponse["response"]["code"] != "200") {
+ var error = document.createElement("P");
+ error.innerHTML = jsonResponse["response"]["message"];
+ error.classList.add("error");
+ form.appendChild(error);
+ } else if (!checkIfJsonIsError(jsonResponse)) {
+ showClearElem(form.id);
+ var peopleList = document.createElement("select");
+ peopleList.name = "client";
+ peopleList.id = "client_select";
+ form.appendChild(peopleList);
+ form.appendChild(document.createElement("br"));
+ for (const index in jsonResponse) {
+ var elem = document.createElement("option");
+ elem.value = jsonResponse[index]._personCf;
+ elem.innerHTML = jsonResponse[index]._personNome + " " + jsonResponse[index]._personCognome + " -- " + jsonResponse[index]._personCf;
+ peopleList.appendChild(elem);
+ }
+ var dateInput = document.createElement("input");
+ dateInput.type = "date";
+ dateInput.name = "date";
+ dateInput.placeholder = "gg/mm/aaaa";
+ var descrInput = createTextInput("descr", "Descrizione");
+ var button = document.createElement("button");
+ button.type = "button";
+ button.innerHTML = "Inserisci";
+ form.appendChild(dateInput);
+ form.appendChild(descrInput);
+ form.appendChild(button);
+ button.onclick = () => sendFormData("input_form", route);
+ }
+ });
+ }
+ });
+}
+
+/* function to complete a work into the given route (either a print or a cut) */
+function completeWork(dataRoute, workType, workCode, workToString, insertRoute) {
+ clearPage();
+ var form = document.getElementById("input_form");
+ fetchData(dataRoute, response => {
+ if (response.ok) {
+ response.json().then(jsonResponse => {
+ if (checkIfJsonIsError(jsonResponse) && jsonResponse["response"]["code"] != "200") {
+ var error = document.createElement("P");
+ error.innerHTML = jsonResponse["response"]["message"];
+ error.classList.add("error");
+ form.appendChild(error);
+ } else if (!checkIfJsonIsError(jsonResponse)) {
+ showClearElem(form.id);
+ var list = document.createElement("select");
+ list.name = workType;
+ list.id = workType + "_select";
+ form.appendChild(list);
+ for (const index in jsonResponse) {
+ var elem = document.createElement("option");
+ elem.value = workCode(jsonResponse[index]);
+ elem.innerHTML = workToString(jsonResponse[index]);
+ list.appendChild(elem);
+ }
+ form.appendChild(document.createElement("br"));
+ var totalInput = createTextInput("total", "Costo totale (usare . per i decimali)");
+ var materialInput = createTextInput("materials", "Costo materiali (usare . per i decimali)");
+ var timeInput = createTextInput("time", "Tempo di completamento in ore (usare . per i decimali)");
+ var dateInput = document.createElement("input");
+ dateInput.type = "date";
+ dateInput.name = "date";
+ dateInput.placeholder = "gg/mm/aaaa";
+ var okButton = document.createElement("button");
+ okButton.type = "button";
+ okButton.innerHTML = "Modifica";
+ okButton.onclick = () => sendFormData("input_form", insertRoute);
+ form.append(totalInput, materialInput, timeInput, dateInput, document.createElement("br"), okButton);
+ }
+ });
+ }
+ });
+}
+
+function showNotYetImplemented(areaId) {
+ clearPage();
+ var p = document.createElement("p");
+ p.classList.add("alert");
+ p.innerHTML = "Questa funzione sarà implementata a breve!";
+ showClearElem(areaId);
+ document.getElementById(areaId).appendChild(p);
+}
\ No newline at end of file
diff --git a/client/index.css b/client/index.css
new file mode 100644
index 0000000..2417ba2
--- /dev/null
+++ b/client/index.css
@@ -0,0 +1,309 @@
+/*
+Colors:
+ base: #d30068, complement
+ background: #c2f56e light light green
+ sub menu focus: #aff53d light green
+ menu focus: #92ec00 green
+ sub menu shadow: #7eb12c dark green
+ menu focus text/sub menu text: #5f9900 dark dark green
+ menu shadow2: #e93a90 light red
+ menu shadow1: #d30068 red
+ menu background: #e969a8 ligh light red
+ menu text: #890043 dark dark red
+*/
+@import url('https://fonts.googleapis.com/css?family=Asap&display=swap');
+
+.hidden {
+ display: none;
+}
+body {
+ background-color: #c2f56e;
+ font-family: 'Asap', sans-serif;
+}
+.fixed {
+ /* position: fixed; */
+ width: 99%;
+ min-height: 5%;
+}
+.scrollable {
+ /* padding-top: 2.5%; */
+}
+footer {
+ position: fixed;
+ left: 0;
+ bottom: 0;
+ width: 99%;
+ text-align: right;
+}
+footer > p > a {
+ text-decoration: none;
+ background: #e969a8;
+ color: #890043;
+ padding: 5px 5px;
+ border-radius: 3px;
+ box-shadow: 0 1px 3px 0 rgba(211, 0, 104, 0.12), 0 1px 2px 0 rgba(233, 58, 144, 0.24);
+}
+.menu {
+ background-color: #e969a8;
+ color: #890043;
+ box-shadow: 0 1px 3px 0 rgba(211, 0, 104, 0.12), 0 1px 2px 0 rgba(233, 58, 144, 0.24);
+ border-radius: 3px;
+}
+.menu_list {
+ display: flex;
+ text-align: center;
+ padding-left: 0;
+ margin-top: 0;
+ margin-bottom: 0;
+ list-style: none;
+}
+.menu_item {
+ flex-grow: 1;
+ padding: 5px;
+}
+.menu_item:hover {
+ background-color: #92ec00;
+ color: #5f9900;
+}
+.sub_menu {
+ display: none;
+ position: absolute;
+ border-radius: 5px;
+ background-color: #ffffff;
+ z-index: 1;
+ list-style: none;
+ margin: 1px;
+ padding: 15px;
+ padding-top: 3px;
+ padding-bottom: 5px;
+ box-shadow: 0 2px 3px 0 rgba(126, 177, 44, 0.12), 0 2px 3px 0 rgba(126, 177, 44, 0.24);
+}
+.sub_menu:hover {
+ display: block;
+}
+.sub_menu_item {
+ color: #5f9900;
+ padding: 3px 3px;
+ border-radius: 3px;
+ text-decoration: none;
+ display: flex;
+ cursor: pointer;
+}
+.sub_menu_item:hover {
+ background-color: #aff53d;
+ color: rgba(41, 74, 41, 0.75);
+}
+.menu_item:hover .sub_menu {
+ display: block;
+}
+#user_input {
+ overflow: hidden;
+ background-color: transparent;
+ padding: 10px 20px 30px 30px;
+ border-radius: 10px;
+ position: absolute;
+ top: 15%;
+ left: 10%;
+ width: 80%;
+}
+#input_form.hidden {
+ display: none;
+}
+#input_form {
+ overflow: hidden;
+ background-color: white;
+ padding: 10px 20px 30px 30px;
+ border-radius: 10px;
+ transition: transform 300ms, box-shadow 300ms;
+ box-shadow: 3px 3px 3px #e93a90;
+}
+input[type="text"], input[type="password"] {
+ font-family: 'Asap', sans-serif;
+ display: block;
+ border-radius: 5px;
+ font-size: 16px;
+ background: white;
+ color: #142114;
+ width: 100%;
+ border: 0;
+ padding: 10px 10px;
+ margin: 15px -10px;
+}
+input[type="text"]:focus, input[type="password"]:focus {
+ border: 1px solid #c2f56e;
+}
+button {
+ font-family: 'Asap', sans-serif;
+ cursor: pointer;
+ color: #5f9900;
+ font-size: 16px;
+ text-transform: uppercase;
+ width: auto;
+ border: 0;
+ padding: 8px 5px 8px 5px;
+ margin-top: 10px;
+ margin-left: -5px;
+ border-radius: 5px;
+ background-color: #c2f56e;
+}
+button:hover {
+ background-color: #aff53d;
+}
+.error {
+ color: #890043;
+ width: 100%;
+ border: 0;
+ padding: 10px 0;
+ margin-top: 10px;
+ margin-left: -5px;
+ margin-bottom: -15px;
+}
+.alert {
+ color: #d30068;
+ width: 100%;
+ border: 0;
+ padding: 10px 0;
+ margin-top: 10px;
+ margin-left: -5px;
+ margin-bottom: 10px;
+}
+.ok {
+ color: #5f9900;
+ width: 100%;
+ border: 0;
+ padding: 10px 0;
+ margin-top: 10px;
+ margin-left: -5px;
+ margin-bottom: -15px;
+}
+
+#result {
+ margin: 3%;
+}
+
+#filters_form.hidden {
+ display: none;
+}
+#filters_form {
+ overflow: hidden;
+ display: inline-block;
+ padding: 5px 5px 5px 5px;
+ box-sizing: border-box;
+ width: 20%;
+ vertical-align: middle;
+ margin-right: 10px;
+}
+input[type=radio] {
+ display: inline-block;
+}
+label {
+ display: inline-block;
+}
+
+#result_area.hidden {
+ display: none;
+}
+#result_area {
+ background-color: white;
+ padding: 10px 10px 10px 10px;
+ border-radius: 10px;
+ display: inline-block;
+ width: 76%;
+ transition: transform 300ms, box-shadow 300ms;
+ box-shadow: 3px 3px 3px #e93a90;
+ text-align: center;
+ vertical-align: middle;
+}
+#filters_form.hidden+#result_area {
+ width: 98%;
+}
+.result_elem.hidden{
+ display: none;
+}
+#result_table, #result_table_works {
+ width: 100%;
+ line-height: 1.5em;
+ border-spacing: 0;
+ border-radius: 10px;
+ table-layout: fixed;
+}
+#result_table_works.hidden{
+ display: none;
+}
+th {
+ background: #c2f56e;
+}
+th:last-of-type {
+ border-top-right-radius: 10px;
+}
+th:first-of-type {
+ border-top-left-radius: 10px;
+}
+.result_elem {
+ font-family: 'Asap', sans-serif;
+ font-size: 16px;
+ color: #142114;
+ border-bottom: 1px solid #c2f56e;
+}
+td {
+ position: relative;
+ border-bottom: 1px solid #c2f56e;
+}
+.result_elem {
+ flex-grow: 1;
+ padding: 5px;
+}
+.complete_description {
+ display: none;
+ text-align: left;
+ position: absolute;
+ white-space: nowrap;
+ top: 0;
+ left: 0;
+ border-radius: 5px;
+ background-color: #ffffff;
+ z-index: 1;
+ list-style: none;
+ margin: 1px;
+ padding: 15px;
+ padding-top: 3px;
+ padding-bottom: 5px;
+ box-shadow: 0 2px 3px 0 rgba(126, 177, 44, 0.12), 0 2px 3px 0 rgba(126, 177, 44, 0.24);
+}
+.result_elem:hover {
+ background-color: #92ec00;
+}
+.complete_description:hover {
+ display: block;
+}
+.result_elem:hover .complete_description {
+ display: block;
+}
+select {
+ font-family: 'Asap', sans-serif;
+ font-size: 16px;
+ width: 100%;
+ color: #142114;
+ padding: 2px;
+ border-radius: 3px;
+ border-color: #c2f56e;
+ cursor: pointer;
+ display: inline-block;
+ margin: 10px 10px 10px 0px;
+ box-shadow: 2px 2px 2px 2px rgba(126, 177, 44, 0.12), 0 2px 3px 0 rgba(126, 177, 44, 0.24);
+}
+input[type="checkbox"].hidden {
+ display: none;
+}
+input[type="checkbox"] {
+ font-family: 'Asap', sans-serif;
+ font-size: 16px;
+ color: #142114;
+ display: inline-block;
+ margin: 7px;
+}
+input[type="date"] {
+ border-color: #c2f56e;
+ font-family: 'Asap', sans-serif;
+ font-size: 16px;
+}
diff --git a/client/index.html b/client/index.html
new file mode 100644
index 0000000..14cb2a6
--- /dev/null
+++ b/client/index.html
@@ -0,0 +1,85 @@
+
+
+
+
+
+
+
+ Menù
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/client/index.js b/client/index.js
new file mode 100644
index 0000000..386356a
--- /dev/null
+++ b/client/index.js
@@ -0,0 +1,852 @@
+window.onload = function() {
+ /* people */
+ document.getElementById("insert_person").onclick = () => insertPerson();
+ document.getElementById("show_people").onclick = () => showPeople();
+ document.getElementById("modify_person").onclick = () => modifyPerson();
+ /* cuts */
+ document.getElementById("insert_cut").onclick = () => insertCut();
+ document.getElementById("assign_cut").onclick = () => assignCut();
+ document.getElementById("complete_cut").onclick = () => completeCut();
+ document.getElementById("assign_processing").onclick = () => assignProcessing();
+ document.getElementById("show_assignments_cuts").onclick = () => showCutsAssignments();
+ document.getElementById("show_cuts").onclick = () => showCuts();
+ document.getElementById("insert_processing").onclick = () => insertProcessing();
+ document.getElementById("show_processings").onclick = () => showProcessings();
+ /* prints */
+ document.getElementById("insert_print").onclick = () => insertPrint();
+ document.getElementById("assign_print").onclick = () => assignPrint();
+ document.getElementById("complete_print").onclick = () => completePrint();
+ document.getElementById("assign_filament").onclick = () => assignFilament();
+ document.getElementById("show_assignments_prints").onclick = () => showPrintsAssignments();
+ document.getElementById("assign_printer").onclick = () => assignPrinter();
+ document.getElementById("show_prints").onclick = () => showPrints();
+ /* materials */
+ document.getElementById("insert_material").onclick = () => insertMaterial();
+ document.getElementById("insert_class").onclick = () => insertClass();
+ document.getElementById("show_materials").onclick = () => showMaterials();
+ document.getElementById("show_classes").onclick = () => showMaterialsClasses();
+ /* plastics */
+ document.getElementById("insert_plastic").onclick = () => insertPlastic();
+ document.getElementById("insert_filament").onclick = () => insertFilament();
+ document.getElementById("show_plastics").onclick = () => showPlastics();
+ document.getElementById("show_filaments").onclick = () => showFilaments();
+}
+
+/*
+----------------------------------------------------------------------------------------
+PEOPLE FUNCTIONS
+----------------------------------------------------------------------------------------
+*/
+
+/* creates the form and sends the data to insert a new person in the database */
+function insertPerson() {
+ clearPage();
+ var form = document.getElementById("input_form");
+ showClearElem(form.id);
+ var cfInput = createTextInput("cf", "Codice fiscale");
+ var nameInput = createTextInput("name", "Nome");
+ var surnameInput = createTextInput("surname", "Cognome");
+ var button = document.createElement("button");
+ button.type = "button";
+ button.innerHTML = "Inserisci";
+ form.appendChild(cfInput);
+ form.appendChild(nameInput);
+ form.appendChild(surnameInput);
+ form.appendChild(button);
+ button.onclick = () => sendFormData("input_form", "insert_person");
+}
+
+/* creates the list to show the people in the database, with the appropriate filters */
+function showPeople() {
+ clearPage();
+ var form = document.getElementById("filters_form");
+ form.classList.remove("hidden");
+ var resultTable = createTable("result_table", "headers", ["cf", "Codice fiscale"], ["name", "Nome"], ["surname", "Cognome"], ["expense", "Spesa totale"]);
+ document.getElementById("result_area").appendChild(resultTable);
+ var setter = (jsonResponse, table) => {
+ var body = table.getElementsByTagName("tbody")[0];
+ showClearElem(body.id);
+ for (const index in jsonResponse) {
+ var listElem = document.createElement("tr");
+ var person = jsonResponse[index];
+ listElem.classList.add("result_elem");
+ var nameCell = document.createElement("td");
+ nameCell.innerHTML = person._personNome;
+ var surnameCell = document.createElement("td");
+ surnameCell.innerHTML = person._personCognome;
+ var cfCell = document.createElement("td");
+ cfCell.innerHTML = person._personCf;
+ var expenseCell = document.createElement("td");
+ expenseCell.innerHTML = person._personSpesaTotale + " €";
+ listElem.append(cfCell, nameCell, surnameCell, expenseCell);
+ body.appendChild(listElem);
+ }
+ };
+ createFiltersRadioButtons(form, resultTable, setter, "people_type", ["all", "Tutti", "people"], ["partners", "Soci", "partners"], ["cutters", "Operatori intagliatrice", "cutterOperators"], ["printers", "Operatori stampante", "printerOperators"]);
+ setTable("people", resultTable, setter);
+}
+
+/* show the appropriate forms to modify a person */
+function modifyPerson() {
+ clearPage();
+ var form = document.getElementById("input_form");
+ showClearElem(form.id);
+ fetchData("people", response => {
+ if (response.ok) {
+ response.json().then(jsonResponse => {
+ if (checkIfJsonIsError(jsonResponse) && jsonResponse["response"]["code"] != "200") {
+ var error = document.createElement("P");
+ error.innerHTML = jsonResponse["response"]["message"];
+ error.classList.add("error");
+ form.appendChild(error);
+ } else if (!checkIfJsonIsError(jsonResponse)) {
+ var list = document.createElement("select");
+ list.name = "cf";
+ list.id = "cf_select";
+ form.appendChild(list);
+ for (const index in jsonResponse) {
+ var elem = document.createElement("option");
+ elem.value = jsonResponse[index]._personCf;
+ elem.innerHTML = jsonResponse[index]._personNome + " " + jsonResponse[index]._personCognome + " -- " + jsonResponse[index]._personCf;
+ list.appendChild(elem);
+ }
+ list.onchange = () => changeCheckedBoxes();
+ changeCheckedBoxes();
+ form.appendChild(document.createElement("br"));
+ createFiltersCheckBoxes(form, ["partner", "true", "Socio"], ["cutter", "true", "Operatore intagliatrice"], ["printer", "true", "Operatore stampante"]);
+ createHiddenFiltersCheckBoxes(form, ["partner", "false", "Socio"], ["cutter", "false", "Operatore intagliatrice"], ["printer", "false", "Operatore stampante"]);
+ var okButton = document.createElement("button");
+ okButton.type = "button";
+ okButton.innerHTML = "Modifica";
+ okButton.onclick = () => {
+ disableCheckBoxes(form);
+ sendFormData("input_form", "modify_person")
+ };
+ form.appendChild(okButton);
+ }
+ });
+ }
+ });
+}
+
+/*
+----------------------------------------------------------------------------------------
+CUTS FUNCTIONS
+----------------------------------------------------------------------------------------
+*/
+
+/* creates the form and insert a new cut */
+function insertCut() {
+ insertWork("insert_cut");
+}
+
+/* assigns a cut to an operator*/
+function assignCut() {
+ assignAToB("assign_cut_operator", "cuts", "cutterOperators", "cut", "operator",
+ cut => cut._cutCodiceIntaglio, cut => (cut._cutCodiceIntaglio + " -- " + cut._cutDataRichiesta + " -- " + cut._cutCfRichiedente),
+ operator => operator._personCf, operator => (operator._personNome + " " + operator._personCognome + " -- " + operator._personCf));
+}
+
+/* completes a cut*/
+function completeCut() {
+ completeWork("cuts", "cut", cut => cut._cutCodiceIntaglio,
+ cut => (cut._cutCodiceIntaglio + " -- " + cut._cutDataRichiesta + " -- " + cut._cutCfRichiedente),
+ "modify_cut");
+}
+
+/* assigns a processing to a cut*/
+function assignProcessing() {
+ assignAToB("assign_cut_processing", "cuts", "processings", "cut", "processing",
+ cut => cut._cutCodiceIntaglio, cut => (cut._cutCodiceIntaglio + " -- " + cut._cutDataRichiesta + " -- " + cut._cutCfRichiedente),
+ processing => processing._processingCodiceLavorazione, processing => processing._processingCodiceLavorazione);
+}
+
+/* shows which processings are assigned to which cuts */
+function showCutsAssignments() {
+ clearPage();
+ var form = document.getElementById("filters_form");
+ form.classList.remove("hidden");
+ fetchData("cuts", response => {
+ if (response.ok) {
+ response.json().then(jsonResponse => {
+ if (checkIfJsonIsError(jsonResponse) && jsonResponse["response"]["code"] != "200") {
+ var error = document.createElement("P");
+ error.innerHTML = jsonResponse["response"]["message"];
+ error.classList.add("error");
+ form.appendChild(error);
+ } else if (!checkIfJsonIsError(jsonResponse)) {
+ var list = document.createElement("select");
+ list.name = "cut";
+ list.id = "cut_select";
+ var elem = document.createElement("option");
+ elem.value = "none";
+ elem.innerHTML = "";
+ list.appendChild(elem);
+ form.appendChild(list);
+ for (const index in jsonResponse) {
+ var cut = jsonResponse[index];
+ elem = document.createElement("option");
+ elem.value = cut._cutCodiceIntaglio
+ elem.innerHTML = cut._cutCodiceIntaglio + " -- " + cut._cutDataRichiesta + " -- " + cut._cutCfRichiedente;
+ list.appendChild(elem);
+ }
+ var resultTable = createTable("result_table", "headers", ["code", "Codice lavorazione"]);
+ document.getElementById("result_area").appendChild(resultTable);
+ var setter = (jsonResponse, table) => {
+ var body = table.getElementsByTagName("tbody")[0];
+ showClearElem(body.id);
+ for (const index in jsonResponse) {
+ var listElem = document.createElement("tr");
+ var composition = jsonResponse[index];
+ listElem.classList.add("result_elem");
+ var codeCell = document.createElement("td");
+ codeCell.innerHTML = composition._compositionCodiceLavorazione;
+ listElem.append(codeCell);
+ body.appendChild(listElem);
+ }
+ };
+ var changer = () => {
+ if (list.value == "none") {
+ setTableFromData([], resultTable, setter);
+ } else {
+ useChosenData("filters_form", "assignments_cut", data => {
+ setTableFromData(data, resultTable, setter);
+ });
+ }
+ };
+ changer();
+ list.onchange = () => changer();
+ }
+ });
+ }
+ });
+}
+
+/* shows all the cuts in the database*/
+function showCuts() {
+ clearPage();
+ var form = document.getElementById("filters_form");
+ form.classList.remove("hidden");
+ var resultTable = createTable("result_table_works", "headers", ["code", "Cod. intaglio"], ["request_day", "Data richiesta"],
+ ["complete_day", "Data consegna"], ["client", "CF richiedente"]);
+ document.getElementById("result_area").appendChild(resultTable);
+ var setter = (jsonResponse, table) => {
+ var body = table.getElementsByTagName("tbody")[0];
+ showClearElem(body.id);
+ for (const index in jsonResponse) {
+ var listElem = document.createElement("tr");
+ var cut = jsonResponse[index];
+ listElem.classList.add("result_elem");
+ var codeCell = document.createElement("td");
+ codeCell.innerHTML = cut._cutCodiceIntaglio;
+ var requestCell = document.createElement("td");
+ requestCell.innerHTML = cut._cutDataRichiesta;
+ var completeCell = document.createElement("td");
+ completeCell.innerHTML = cut._cutDataConsegna;
+ var clientCell = document.createElement("td");
+ clientCell.innerHTML = cut._cutCfRichiedente;
+ var descrDiv = document.createElement("div");
+ descrDiv.classList.add("complete_description");
+ var descrPar = document.createElement("p");
+ descrPar.innerHTML = "Codice intaglio: " + cut._cutCodiceIntaglio
+ + " Data richiesta: " + cut._cutDataRichiesta
+ + " Codice fiscale richiedente: " + cut._cutCfRichiedente
+ + " Data consegna: " + (cut._cutDataConsegna == null ? "" : cut._cutDataConsegna)
+ + " Costo totale: " + (cut._cutCostoTotale == null ? "" : cut._cutCostoTotale)
+ + " Costo materiali: " + (cut._cutCostoMateriali == null ? "" : cut._cutCostoMateriali)
+ + " Tempo esecuzione: " + (cut._cutTempo == null ? "" : cut._cutTempo)
+ + " Codice fiscale incaricato: " + (cut._cutCfIncaricato == null ? "" : cut._cutCfIncaricato)
+ + " Descrizione: " + (cut._cutDescrizione == null ? "" : cut._cutDescrizione);
+ descrDiv.appendChild(descrPar);
+ codeCell.appendChild(descrDiv);
+ listElem.append(codeCell, requestCell, completeCell, clientCell);
+ body.appendChild(listElem);
+ }
+ };
+ createFiltersRadioButtons(form, resultTable, setter, "cut_type", ["all", "Tutte", "cuts"], ["complete", "Complete", "complete_cuts"], ["incomplete", "Incomplete", "incomplete_cuts"]);
+ setTable("cuts", resultTable, setter);
+}
+
+/* creates the form and inserts a new processing */
+function insertProcessing() {
+ clearPage();
+ var form = document.getElementById("input_form");
+ fetchData("types", responseTypes => {
+ if (responseTypes.ok) {
+ responseTypes.json().then(jsonResponseTypes => {
+ if (checkIfJsonIsError(jsonResponseTypes) && jsonResponseTypes["response"]["code"] != "200") {
+ var error = document.createElement("P");
+ error.innerHTML = jsonResponseTypes["response"]["message"];
+ error.classList.add("error");
+ form.appendChild(error);
+ } else if (!checkIfJsonIsError(jsonResponseTypes)) {
+ fetchData("materials", responseMaterials => {
+ if (responseMaterials.ok) {
+ responseMaterials.json().then(jsonResponseMaterials => {
+ if (checkIfJsonIsError(jsonResponseMaterials) && jsonResponseMaterials["response"]["code"] != "200") {
+ var error = document.createElement("P");
+ error.innerHTML = jsonResponseMaterials["response"]["message"];
+ error.classList.add("error");
+ form.appendChild(error);
+ } else if (!checkIfJsonIsError(jsonResponseMaterials)) {
+ showClearElem(form.id);
+ var typesList = document.createElement("select");
+ typesList.name = "type";
+ typesList.id = "type_select";
+ form.appendChild(typesList);
+ form.appendChild(document.createElement("br"));
+ for (const index in jsonResponseTypes) {
+ var elem = document.createElement("option");
+ elem.value = jsonResponseTypes[index]._typeCodiceTipo;
+ elem.innerHTML = jsonResponseTypes[index]._typeCodiceTipo + " " + jsonResponseTypes[index]._typeNome;
+ typesList.appendChild(elem);
+ }
+ var materialsTypes = document.createElement("select");
+ materialsTypes.name = "material";
+ materialsTypes.id = "material_select";
+ form.appendChild(materialsTypes);
+ form.appendChild(document.createElement("br"));
+ for (const index in jsonResponseMaterials) {
+ var elem = document.createElement("option");
+ elem.value = jsonResponseMaterials[index]._materialCodiceMateriale;
+ elem.innerHTML = jsonResponseMaterials[index]._materialNome + " " + jsonResponseMaterials[index]._materialSpessore;
+ materialsTypes.appendChild(elem);
+ }
+ var maxPInput = createTextInput("max_potency", "Potenza massima");
+ var minPInput = createTextInput("min_potency", "Potenze minima");
+ var speedInput = createTextInput("speed", "Velocità");
+ var descrInput = createTextInput("description", "Descrizione");
+ var button = document.createElement("button");
+ button.type = "button";
+ button.innerHTML = "Inserisci";
+ form.append(maxPInput, minPInput, speedInput, descrInput);
+ form.appendChild(button);
+ button.onclick = () => sendFormData("input_form", "insert_processing");
+ }
+ });
+ }
+ });
+ }
+ });
+ }
+ });
+}
+
+/* */
+function showProcessings() {
+ clearPage();
+ var form = document.getElementById("filters_form");
+ form.classList.remove("hidden");
+ fetchData("materials", response => {
+ if (response.ok) {
+ response.json().then(jsonResponse => {
+ if (checkIfJsonIsError(jsonResponse) && jsonResponse["response"]["code"] != "200") {
+ var error = document.createElement("P");
+ error.innerHTML = jsonResponse["response"]["message"];
+ error.classList.add("error");
+ form.appendChild(error);
+ } else if (!checkIfJsonIsError(jsonResponse)) {
+ var list = document.createElement("select");
+ list.name = "material";
+ list.id = "material_select";
+ var elem = document.createElement("option");
+ elem.value = "all";
+ elem.innerHTML = "Tutti le lavorazioni";
+ list.appendChild(elem);
+ form.appendChild(list);
+ for (const index in jsonResponse) {
+ elem = document.createElement("option");
+ elem.value = jsonResponse[index]._materialCodiceMateriale
+ elem.innerHTML = jsonResponse[index]._materialCodiceClasse + " " + jsonResponse[index]._materialNome + " " + jsonResponse[index]._materialSpessore;
+ list.appendChild(elem);
+ }
+ var resultTable = createTable("result_table", "headers", ["code", "Codice lavorazione"], ["description", "Descrizione"]);
+ document.getElementById("result_area").appendChild(resultTable);
+ var setter = (jsonResponse, table) => {
+ var body = table.getElementsByTagName("tbody")[0];
+ showClearElem(body.id);
+ for (const index in jsonResponse) {
+ var listElem = document.createElement("tr");
+ var processing = jsonResponse[index];
+ listElem.classList.add("result_elem");
+ var codeCell = document.createElement("td");
+ codeCell.innerHTML = processing._processingCodiceLavorazione;
+ var descrCell = document.createElement("td");
+ descrCell.innerHTML = processing._processingDescrizione;
+ var descrDiv = document.createElement("div");
+ descrDiv.classList.add("complete_description");
+ var descrPar = document.createElement("p");
+ descrPar.innerHTML = "Codice lavorazione: " + processing._processingCodiceLavorazione
+ + " Tipo: " + processing._processingCodiceTipo
+ + " Materiale: " + processing._processingCodiceMateriale
+ + " Potenza massima: " + processing._processingPotenzaMassima
+ + " Potenza minima: " + processing._processingPotenzaMinima
+ + " Velocità: " + processing._processingVelocita
+ + " Descrizione: " + processing._processingDescrizione;
+ descrDiv.appendChild(descrPar);
+ codeCell.appendChild(descrDiv);
+ listElem.append(codeCell, descrCell);
+ body.appendChild(listElem);
+ }
+ };
+ var changer = () => {
+ if (list.value == "all") {
+ setTable("processings", resultTable, setter);
+ } else {
+ useChosenData("filters_form", "select_processings_by_material", data => {
+ setTableFromData(data, resultTable, setter);
+ });
+ }
+ };
+ changer();
+ list.onchange = () => changer();
+ }
+ });
+ }
+ });
+}
+
+
+/*
+----------------------------------------------------------------------------------------
+PRINTS FUNCTIONS
+----------------------------------------------------------------------------------------
+*/
+
+/* creates the form and sends the data to insert a new print */
+function insertPrint() {
+ insertWork("insert_print");
+}
+
+/* assigns a print to an operator */
+function assignPrint() {
+ assignAToB("assign_print_operator", "prints", "printerOperators", "print", "operator",
+ print => print._printCodiceStampa, print => (print._printCodiceStampa + " -- " + print._printDataRichiesta + " -- " + print._printCfRichiedente),
+ operator => operator._personCf, operator => (operator._personNome + " " + operator._personCognome + " -- " + operator._personCf));
+}
+
+/* completese a print */
+function completePrint() {
+ completeWork("prints", "print", print => print._printCodiceStampa,
+ print => (print._printCodiceStampa + " -- " + print._printDataRichiesta + " -- " + print._printCfRichiedente),
+ "modify_print");
+}
+
+/* assigns a filament to a print */
+function assignFilament() {
+ assignAToB("assign_print_filament", "prints", "filaments", "print", "filament", print => print._printCodiceStampa,
+ print => (print._printCodiceStampa + " -- " + print._printDataRichiesta + " -- " + print._printCfRichiedente),
+ filament => filament._filamentCodiceFilamento,
+ filament => (filament._filamentMarca + " " + filament._filamentColore));
+}
+
+/* shows which filaments are assigned to which prints */
+function showPrintsAssignments() {
+ clearPage();
+ var form = document.getElementById("filters_form");
+ form.classList.remove("hidden");
+ fetchData("prints", response => {
+ if (response.ok) {
+ response.json().then(jsonResponse => {
+ if (checkIfJsonIsError(jsonResponse) && jsonResponse["response"]["code"] != "200") {
+ var error = document.createElement("P");
+ error.innerHTML = jsonResponse["response"]["message"];
+ error.classList.add("error");
+ form.appendChild(error);
+ } else if (!checkIfJsonIsError(jsonResponse)) {
+ var list = document.createElement("select");
+ list.name = "print";
+ list.id = "print_select";
+ var elem = document.createElement("option");
+ elem.value = "none";
+ elem.innerHTML = "";
+ list.appendChild(elem);
+ form.appendChild(list);
+ for (const index in jsonResponse) {
+ var print = jsonResponse[index];
+ elem = document.createElement("option");
+ elem.value = print._printCodiceStampa
+ elem.innerHTML = print._printCodiceStampa + " -- " + print._printDataRichiesta + " -- " + print._printCfRichiedente;
+ list.appendChild(elem);
+ }
+ var resultTable = createTable("result_table", "headers", ["code", "Codice filamento"]);
+ document.getElementById("result_area").appendChild(resultTable);
+ var setter = (jsonResponse, table) => {
+ var body = table.getElementsByTagName("tbody")[0];
+ showClearElem(body.id);
+ for (const index in jsonResponse) {
+ var listElem = document.createElement("tr");
+ var use = jsonResponse[index];
+ listElem.classList.add("result_elem");
+ var codeCell = document.createElement("td");
+ codeCell.innerHTML = use._useCodiceFilamento;
+ listElem.append(codeCell);
+ body.appendChild(listElem);
+ }
+ };
+ var changer = () => {
+ if (list.value == "none") {
+ setTableFromData([], resultTable, setter);
+ } else {
+ useChosenData("filters_form", "assignments_print", data => {
+ setTableFromData(data, resultTable, setter);
+ });
+ }
+ };
+ changer();
+ list.onchange = () => changer();
+ }
+ });
+ }
+ });
+}
+
+/* assigns a printer to a print */
+function assignPrinter() {
+ assignAToB("assign_print_printer", "printers", "prints", "printer", "print", printer => printer._printerCodiceStampante,
+ printer => (printer._printerMarca + " " + printer._printerModello), print => print._printCodiceStampa,
+ print => (print._printCodiceStampa + " -- " + print._printDataRichiesta + " -- " + print._printCfRichiedente));
+}
+
+/* shows the prints */
+function showPrints() {
+ clearPage();
+ var form = document.getElementById("filters_form");
+ form.classList.remove("hidden");
+ var resultTable = createTable("result_table_works", "headers", ["code", "Cod. stampa"], ["request_day", "Data richiesta"],
+ ["complete_day", "Data consegna"], ["client", "CF richiedente"]);
+ document.getElementById("result_area").appendChild(resultTable);
+ var setter = (jsonResponse, table) => {
+ var body = table.getElementsByTagName("tbody")[0];
+ showClearElem(body.id);
+ for (const index in jsonResponse) {
+ var listElem = document.createElement("tr");
+ var print = jsonResponse[index];
+ listElem.classList.add("result_elem");
+ var codeCell = document.createElement("td");
+ codeCell.innerHTML = print._printCodiceStampa;
+ var requestCell = document.createElement("td");
+ requestCell.innerHTML = print._printDataRichiesta;
+ var completeCell = document.createElement("td");
+ completeCell.innerHTML = print._printDataConsegna;
+ var clientCell = document.createElement("td");
+ clientCell.innerHTML = print._printCfRichiedente;
+ var descrDiv = document.createElement("div");
+ descrDiv.classList.add("complete_description");
+ var descrPar = document.createElement("p");
+ descrPar.innerHTML = "Codice stampa: " + print._printCodiceStampa
+ + " Data richiesta: " + print._printDataRichiesta
+ + " Codice fiscale richiedente: " + print._printCfRichiedente
+ + " Data consegna: " + (print._printDataConsegna == null ? "" : print._printDataConsegna)
+ + " Costo totale: " + (print._printCostoTotale == null ? "" : print._printCostoTotale)
+ + " Costo materiali: " + (print._printCostoMateriali == null ? "" : print._printCostoMateriali)
+ + " Tempo esecuzione: " + (print._printTempo == null ? "" : print._printTempo)
+ + " Codice fiscale incaricato: " + (print._printCfIncaricato == null ? "" : print._printCfIncaricato)
+ + " Codice stampante: " + (print._printCodiceStampante == null ? "" : print._printCodiceStampante)
+ + " Descrizione: " + (print._printDescrizione == null ? "" : print._printDescrizione);
+ descrDiv.appendChild(descrPar);
+ codeCell.appendChild(descrDiv);
+ listElem.append(codeCell, requestCell, completeCell, clientCell);
+ body.appendChild(listElem);
+ }
+ };
+ createFiltersRadioButtons(form, resultTable, setter, "print_type", ["all", "Tutte", "prints"], ["complete", "Complete", "complete_prints"], ["incomplete", "Incomplete", "incomplete_prints"]);
+ setTable("prints", resultTable, setter);
+}
+
+/*
+----------------------------------------------------------------------------------------
+MATERIALS FUNCTIONS
+----------------------------------------------------------------------------------------
+*/
+
+/* creates the form and sends the data to insert a new material into the database */
+function insertMaterial() {
+ clearPage();
+ var form = document.getElementById("input_form");
+ showClearElem(form.id);
+ fetchData("materials_classes", response => {
+ if (response.ok) {
+ response.json().then(jsonResponse => {
+ if (checkIfJsonIsError(jsonResponse) && jsonResponse["response"]["code"] != "200") {
+ var error = document.createElement("P");
+ error.innerHTML = jsonResponse["response"]["message"];
+ error.classList.add("error");
+ form.appendChild(error);
+ } else if (!checkIfJsonIsError(jsonResponse)) {
+ var list = document.createElement("select");
+ list.name = "class";
+ list.id = "materials_classes_select";
+ form.appendChild(list);
+ for (const index in jsonResponse) {
+ var elem = document.createElement("option");
+ elem.value = jsonResponse[index]._materialsclassCodiceClasse;
+ elem.innerHTML = jsonResponse[index]._materialsclassCodiceClasse + " -- " + jsonResponse[index]._materialsclassNome;
+ list.appendChild(elem);
+ }
+ var materialCode = createTextInput("code", "Codice materiale (2 caratteri)");
+ var nameInput = createTextInput("name", "Nome");
+ var widthInput = createTextInput("width", "Spessore (mm)");
+ var descrInput = createTextInput("description", "Descrizione");
+ var okButton = document.createElement("button");
+ okButton.type = "button";
+ okButton.innerHTML = "Inserisci";
+ okButton.onclick = () => sendFormData("input_form", "insert_material");
+ form.append(materialCode, nameInput, widthInput, descrInput, okButton);
+ }
+ });
+ }
+ });
+}
+
+/* creates the form and sends the data to insert a new materials class into the database */
+function insertClass() {
+ clearPage();
+ var form = document.getElementById("input_form");
+ showClearElem(form.id);
+ var codeInput = createTextInput("code", "Codice classe (2 caratteri)");
+ var nameInput = createTextInput("name", "Nome");
+ var button = document.createElement("button");
+ button.type = "button";
+ button.innerHTML = "Inserisci";
+ form.appendChild(codeInput);
+ form.appendChild(nameInput);
+ form.appendChild(button);
+ button.onclick = () => sendFormData("input_form", "insert_class");
+}
+
+/* shows the available materials */
+function showMaterials() {
+ clearPage();
+ var form = document.getElementById("filters_form");
+ form.classList.remove("hidden");
+ fetchData("materials_classes", response => {
+ if (response.ok) {
+ response.json().then(jsonResponse => {
+ if (checkIfJsonIsError(jsonResponse) && jsonResponse["response"]["code"] != "200") {
+ var error = document.createElement("P");
+ error.innerHTML = jsonResponse["response"]["message"];
+ error.classList.add("error");
+ form.appendChild(error);
+ } else if (!checkIfJsonIsError(jsonResponse)) {
+ var list = document.createElement("select");
+ list.name = "class_code";
+ list.id = "class_select";
+ var elem = document.createElement("option");
+ elem.value = "all";
+ elem.innerHTML = "Tutti i materiali";
+ list.appendChild(elem);
+ form.appendChild(list);
+ for (const index in jsonResponse) {
+ elem = document.createElement("option");
+ elem.value = jsonResponse[index]._materialsclassCodiceClasse
+ elem.innerHTML = jsonResponse[index]._materialsclassCodiceClasse + " -- " + jsonResponse[index]._materialsclassNome;
+ list.appendChild(elem);
+ }
+ var resultTable = createTable("result_table", "headers", ["code", "Codice"], ["class", "Classe"], ["name", "Nome"], ["width", "Spessore (mm)"], ["description", "Descrizione"]);
+ var setter = (jsonResponse, table) => {
+ var body = table.getElementsByTagName("tbody")[0];
+ showClearElem(body.id);
+ for (const index in jsonResponse) {
+ var listElem = document.createElement("tr");
+ var material = jsonResponse[index];
+ listElem.classList.add("result_elem");
+ var codeCell = document.createElement("td");
+ codeCell.innerHTML = material._materialCodiceMateriale;
+ var classCell = document.createElement("td");
+ classCell.innerHTML = material._materialCodiceClasse;
+ var nameCell = document.createElement("td");
+ nameCell.innerHTML = material._materialNome;
+ var widthCell = document.createElement("td");
+ widthCell.innerHTML = material._materialSpessore;
+ var descrCell = document.createElement("td");
+ descrCell.innerHTML = material._materialDescrizione;
+ listElem.append(codeCell, classCell, nameCell, widthCell, descrCell);
+ body.appendChild(listElem);
+ }
+ };
+ var changer = () => {
+ if (list.value == "all") {
+ setTable("materials", resultTable, setter);
+ } else {
+ useChosenData("filters_form", "select_materials", data => {
+ setTableFromData(data, resultTable, setter);
+ });
+ }
+ };
+ changer();
+ list.onchange = () => changer();
+ }
+ });
+ }
+ });
+}
+
+/* shows the available materials classes */
+function showMaterialsClasses() {
+ clearPage();
+ document.getElementById("filters_form").classList.add("hidden");
+ var resultTable = createTable("result_table", "headers", ["code", "Codice"], ["name", "Nome"]);
+ setTable("materials_classes", resultTable, (jsonResponse, table) => {
+ for (const index in jsonResponse) {
+ var listElem = document.createElement("tr");
+ var mClass = jsonResponse[index];
+ listElem.classList.add("result_elem");
+ var codeCell = document.createElement("td");
+ codeCell.innerHTML = mClass._materialsclassCodiceClasse;
+ var nameCell = document.createElement("td");
+ nameCell.innerHTML = mClass._materialsclassNome;
+ listElem.append(codeCell, nameCell);
+ table.appendChild(listElem);
+ }
+ });
+}
+
+/*
+----------------------------------------------------------------------------------------
+PLASTICS FUNCTIONS
+----------------------------------------------------------------------------------------
+*/
+
+/* creates the form and sends the data to insert a new plastic in the database */
+function insertPlastic() {
+ clearPage();
+ var form = document.getElementById("input_form");
+ showClearElem(form.id);
+ var plasticCode = createTextInput("code", "Codice plastica (3 caratteri)");
+ var nameInput = createTextInput("name", "Nome");
+ var descriptionInput = createTextInput("description", "Descrizione");
+ var button = document.createElement("button");
+ button.type = "button";
+ button.innerHTML = "Inserisci";
+ form.appendChild(plasticCode);
+ form.appendChild(nameInput);
+ form.appendChild(descriptionInput);
+ form.appendChild(button);
+ button.onclick = () => sendFormData("input_form", "insert_plastic");
+}
+
+/* creates the form and sends the data to insert a new filament in the database */
+function insertFilament() {
+ clearPage();
+ var form = document.getElementById("input_form");
+ showClearElem(form.id);
+ fetchData("plastics", response => {
+ if (response.ok) {
+ response.json().then(jsonResponse => {
+ if (checkIfJsonIsError(jsonResponse) && jsonResponse["response"]["code"] != "200") {
+ var error = document.createElement("P");
+ error.innerHTML = jsonResponse["response"]["message"];
+ error.classList.add("error");
+ form.appendChild(error);
+ } else if (!checkIfJsonIsError(jsonResponse)) {
+ var list = document.createElement("select");
+ list.name = "plastic";
+ list.id = "plastic_select";
+ form.appendChild(list);
+ for (const index in jsonResponse) {
+ var elem = document.createElement("option");
+ elem.value = jsonResponse[index]._plasticCodicePlastica;
+ elem.innerHTML = jsonResponse[index]._plasticCodicePlastica + " -- " + jsonResponse[index]._plasticNome;
+ list.appendChild(elem);
+ }
+ var filamentCode = createTextInput("code", "Codice filamento (4 caratteri)");
+ var brandInput = createTextInput("brand", "Marca");
+ var colorInput = createTextInput("color", "Colore");
+ var okButton = document.createElement("button");
+ okButton.type = "button";
+ okButton.innerHTML = "Inserisci";
+ okButton.onclick = () => sendFormData("input_form", "insert_filament");
+ form.append(filamentCode, brandInput, colorInput, okButton);
+ }
+ });
+ }
+ });
+}
+
+/* shows the filaments, giving the possibility to select the plastic */
+function showFilaments() {
+ clearPage();
+ var form = document.getElementById("filters_form");
+ form.classList.remove("hidden");
+ fetchData("plastics", response => {
+ if (response.ok) {
+ response.json().then(jsonResponse => {
+ if (checkIfJsonIsError(jsonResponse) && jsonResponse["response"]["code"] != "200") {
+ var error = document.createElement("P");
+ error.innerHTML = jsonResponse["response"]["message"];
+ error.classList.add("error");
+ form.appendChild(error);
+ } else if (!checkIfJsonIsError(jsonResponse)) {
+ var list = document.createElement("select");
+ list.name = "plastic_code";
+ list.id = "plastic_select";
+ var elem = document.createElement("option");
+ elem.value = "all";
+ elem.innerHTML = "Tutte le plastiche";
+ list.appendChild(elem);
+ form.appendChild(list);
+ for (const index in jsonResponse) {
+ elem = document.createElement("option");
+ elem.value = jsonResponse[index]._plasticCodicePlastica;
+ elem.innerHTML = jsonResponse[index]._plasticCodicePlastica + " -- " + jsonResponse[index]._plasticNome;
+ list.appendChild(elem);
+ }
+ var resultTable = createTable("result_table", "headers", ["code", "Codice"], ["plastic", "Plastica"], ["brand", "Marca"], ["color", "Colore"]);
+ var setter = (jsonResponse, table) => {
+ var body = table.getElementsByTagName("tbody")[0];
+ showClearElem(body.id);
+ for (const index in jsonResponse) {
+ var listElem = document.createElement("tr");
+ var filament = jsonResponse[index];
+ listElem.classList.add("result_elem");
+ var codeCell = document.createElement("td");
+ codeCell.innerHTML = filament._filamentCodiceFilamento;
+ var plasticCell = document.createElement("td");
+ plasticCell.innerHTML = filament._filamentCodicePlastica;
+ var brandCell = document.createElement("td");
+ brandCell.innerHTML = filament._filamentMarca;
+ var colorCell = document.createElement("td");
+ colorCell.innerHTML = filament._filamentColore;
+ listElem.append(codeCell, plasticCell, brandCell, colorCell);
+ body.appendChild(listElem);
+ }
+ };
+ var changer = () => {
+ if (list.value == "all") {
+ setTable("filaments", resultTable, setter);
+ } else {
+ useChosenData("filters_form", "select_filaments", data => {
+ setTableFromData(data, resultTable, setter);
+ });
+ }
+ };
+ changer();
+ list.onchange = () => changer();
+ }
+ });
+ }
+ });
+}
+
+/* shows the plastics in the database */
+function showPlastics() {
+ clearPage();
+ document.getElementById("filters_form").classList.add("hidden");
+ var resultTable = createTable("result_table", "headers", ["code", "Codice"], ["name", "Nome"], ["description", "Descrizione"]);
+ setTable("plastics", resultTable, (jsonResponse, table) => {
+ for (const index in jsonResponse) {
+ var listElem = document.createElement("tr");
+ var plastic = jsonResponse[index];
+ listElem.classList.add("result_elem");
+ var codeCell = document.createElement("td");
+ codeCell.innerHTML = plastic._plasticCodicePlastica;
+ var nameCell = document.createElement("td");
+ nameCell.innerHTML = plastic._plasticNome;
+ var descrCell = document.createElement("td");
+ descrCell.innerHTML = plastic._plasticDescrizione;
+ listElem.append(codeCell, nameCell, descrCell);
+ table.appendChild(listElem);
+ }
+ });
+}
\ No newline at end of file
diff --git a/client/login.css b/client/login.css
new file mode 100644
index 0000000..b95ee39
--- /dev/null
+++ b/client/login.css
@@ -0,0 +1,123 @@
+/*
+Colors:
+ base: #d30068, complement
+ background: #c2f56e light light green
+ focus button: #aff53d light green
+ font button: #5f9900 dark dark green
+ shadow1/wave2: #e93a90 light red
+ wave1: #e969a8 ligh light red
+ error: #890043 dark dark red
+*/
+@import url('https://fonts.googleapis.com/css?family=Asap&display=swap');
+
+body {
+ background-color: #c2f56e;
+ font-family: 'Asap', sans-serif;
+}
+#login_form, section {
+ overflow: hidden;
+ background-color: white;
+ padding: 10px 20px 30px 30px;
+ border-radius: 10px;
+ position: absolute;
+ top: 50%;
+ left: 50%;
+ width: 40%;
+ transform: translate(-50%, -50%);
+ transition: transform 300ms, box-shadow 300ms;
+ box-shadow: 3px 3px 3px #e93a90;
+}
+section {
+ text-align: center;
+ width: 30%;
+}
+#login_form::before, #login_form::after {
+ content: '';
+ position: absolute;
+ width: 50rem;
+ height: 50rem;
+ border-top-left-radius: 40%;
+ border-top-right-radius: 45%;
+ border-bottom-left-radius: 35%;
+ border-bottom-right-radius: 40%;
+ z-index: -1;
+}
+#login_form::before {
+ left: 40%;
+ bottom: -130%;
+ background-color: rgba(233, 58, 144, 0.15);
+ animation: waves 6s infinite linear;
+}
+#login_form::after {
+ left: 35%;
+ bottom: -125%;
+ background-color: rgba(233, 105, 168, 0.2);
+ animation: waves 7s infinite;
+}
+input {
+ font-family: 'Asap', sans-serif;
+ display: block;
+ border-radius: 5px;
+ font-size: 16px;
+ background: white;
+ color: #142114;
+ width: 100%;
+ border: 0;
+ padding: 10px 10px;
+ margin: 15px -10px;
+}
+button {
+ font-family: 'Asap', sans-serif;
+ cursor: pointer;
+ color: #5f9900;
+ font-size: 16px;
+ text-transform: uppercase;
+ width: auto;
+ border: 0;
+ padding: 8px 5px 8px 5px;
+ margin-top: 10px;
+ margin-left: -5px;
+ border-radius: 5px;
+ background-color: #c2f56e;
+}
+button:hover {
+ background-color: #aff53d;
+}
+#login_error {
+ color: #890043;
+ width: 100%;
+ border: 0;
+ padding: 10px 0;
+ margin-top: 10px;
+ margin-left: -5px;
+ margin-bottom: -15px;
+}
+@keyframes waves {
+ from {
+ transform: rotate(0);
+ }
+ to {
+ transform: rotate(360deg);
+ }
+}
+section > h1 {
+ font-family: 'Asap', sans-serif;
+ font-size: 28px;
+ color: #142114;
+ padding: 10px 10px;
+}
+a {
+ text-decoration: none;
+ font-family: 'Asap', sans-serif;
+ cursor: pointer;
+ color: #5f9900;
+ font-size: 16px;
+ text-transform: uppercase;
+ width: auto;
+ border: 0;
+ padding: 8px 5px 8px 5px;
+ margin-top: 10px;
+ margin-left: -5px;
+ border-radius: 5px;
+ background-color: #c2f56e;
+}
diff --git a/client/login.html b/client/login.html
new file mode 100644
index 0000000..294798a
--- /dev/null
+++ b/client/login.html
@@ -0,0 +1,20 @@
+
+
+
+
+
+
+ Login
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/client/login.js b/client/login.js
new file mode 100644
index 0000000..3483aba
--- /dev/null
+++ b/client/login.js
@@ -0,0 +1,24 @@
+window.onload = function() {
+ document.getElementById('submit_button').onclick = function() {
+ var form = new FormData(document.getElementById('login_form'));
+ var post = {
+ method: 'POST',
+ body: form
+ };
+ fetch(window.location.href.toString() + 'login', post).then(function(response) {
+ if (!response.redirected && response.ok) {
+ response.json().then(jsonResponse => {
+ if (jsonResponse["response"]["code"] == "401") {
+ var error = document.createElement("P");
+ error.innerHTML = jsonResponse["response"]["message"];
+ error.id = "login_error";
+ document.getElementById('login_form')
+ .appendChild(error);
+ }
+ });
+ } else if (response.redirected == true) {
+ window.location.replace(response.url);
+ }
+ });
+ }
+}
\ No newline at end of file
diff --git a/client/manager.css b/client/manager.css
new file mode 100644
index 0000000..11296d3
--- /dev/null
+++ b/client/manager.css
@@ -0,0 +1 @@
+@import "index.css"
\ No newline at end of file
diff --git a/client/manager.html b/client/manager.html
new file mode 100644
index 0000000..67faeb2
--- /dev/null
+++ b/client/manager.html
@@ -0,0 +1,31 @@
+
+
+
+
+
+
+
+ Menù admin
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/client/manager.js b/client/manager.js
new file mode 100644
index 0000000..fd58e3a
--- /dev/null
+++ b/client/manager.js
@@ -0,0 +1,68 @@
+window.onload = () => {
+ document.getElementById("insert_printer").onclick = () => insertPrinter();
+ document.getElementById("insert_type").onclick = () => insertType();
+ document.getElementById("insert_admin").onclick = () => insertAdmin();
+}
+
+/*
+----------------------------------------------------------------------------------------
+ADMIN FUNCTIONS
+----------------------------------------------------------------------------------------
+*/
+
+/* creates the form and sends the data to insert a new type of processing in the database */
+function insertType() {
+ /* c. */clearPage();
+ var form = document.getElementById("input_form");
+ /* c. */showClearElem(form.id);
+ var codeInput = /* c. */createTextInput("code", "Codice tipo di lavorazione (3 caratteri)");
+ var nameInput = /* c. */createTextInput("name", "Nome tipo di lavorazione");
+ var descrInput = /* c. */createTextInput("description", "Descrizione");
+ var button = document.createElement("button");
+ button.type = "button";
+ button.innerHTML = "Inserisci";
+ form.appendChild(codeInput);
+ form.appendChild(nameInput);
+ form.appendChild(descrInput);
+ form.appendChild(button);
+ button.onclick = () => /* c. */sendFormData("input_form", "insert_type");
+}
+
+/* creates the form and insert a new printer in the database */
+function insertPrinter() {
+ /* c. */clearPage();
+ var form = document.getElementById("input_form");
+ /* c. */showClearElem(form.id);
+ var codeInput = /* c. */createTextInput("code", "Codice stampante (3 caratteri)");
+ var brandInput = /* c. */createTextInput("brand", "Marca");
+ var modelInput = /* c. */createTextInput("model", "Modello");
+ var descrInput = /* c. */createTextInput("description", "Descrizione");
+ var button = document.createElement("button");
+ button.type = "button";
+ button.innerHTML = "Inserisci";
+ form.appendChild(codeInput);
+ form.appendChild(brandInput);
+ form.appendChild(modelInput);
+ form.appendChild(descrInput);
+ form.appendChild(button);
+ button.onclick = () => /* c. */sendFormData("input_form", "insert_printer");
+}
+
+/* creates the form and insert a new admin in the database */
+function insertAdmin() {
+ /* c. */clearPage();
+ var form = document.getElementById("input_form");
+ /* c. */showClearElem(form.id);
+ var usernameInput = /* c. */createTextInput("username", "Username");
+ var passwordInput = document.createElement("input");
+ passwordInput.type = "password";
+ passwordInput.name = "password";
+ passwordInput.placeholder = "Password";
+ var button = document.createElement("button");
+ button.type = "button";
+ button.innerHTML = "Inserisci";
+ form.appendChild(usernameInput);
+ form.appendChild(passwordInput);
+ form.appendChild(button);
+ button.onclick = () => /* c. */sendFormData("input_form", "insert_user");
+}
\ No newline at end of file
diff --git a/client/no_auth.html b/client/no_auth.html
new file mode 100644
index 0000000..fa97784
--- /dev/null
+++ b/client/no_auth.html
@@ -0,0 +1,16 @@
+
+
+
+
+
+ Login
+
+
+
+
+
Non sei autorizzato!
+ Vai al login
+
+
+
+
\ No newline at end of file
diff --git a/db_schema.sql b/db_schema.sql
new file mode 100644
index 0000000..0c0c06a
--- /dev/null
+++ b/db_schema.sql
@@ -0,0 +1,666 @@
+--
+-- PostgreSQL database dump
+--
+
+-- Dumped from database version 11.4
+-- Dumped by pg_dump version 11.4
+
+-- Started on 2019-10-06 10:54:41
+
+SET statement_timeout = 0;
+SET lock_timeout = 0;
+SET idle_in_transaction_session_timeout = 0;
+SET client_encoding = 'UTF8';
+SET standard_conforming_strings = on;
+SELECT pg_catalog.set_config('search_path', '', false);
+SET check_function_bodies = false;
+SET xmloption = content;
+SET client_min_messages = warning;
+SET row_security = off;
+
+--
+-- TOC entry 2 (class 3079 OID 16729)
+-- Name: uuid-ossp; Type: EXTENSION; Schema: -; Owner:
+--
+
+CREATE EXTENSION IF NOT EXISTS "uuid-ossp" WITH SCHEMA public;
+
+
+--
+-- TOC entry 2941 (class 0 OID 0)
+-- Dependencies: 2
+-- Name: EXTENSION "uuid-ossp"; Type: COMMENT; Schema: -; Owner:
+--
+
+COMMENT ON EXTENSION "uuid-ossp" IS 'generate universally unique identifiers (UUIDs)';
+
+
+SET default_tablespace = '';
+
+SET default_with_oids = false;
+
+--
+-- TOC entry 198 (class 1259 OID 16456)
+-- Name: classi_di_materiali; Type: TABLE; Schema: public; Owner: postgres
+--
+
+CREATE TABLE public.classi_di_materiali (
+ codice_classe character(2) NOT NULL,
+ nome character varying(30) NOT NULL
+);
+
+
+ALTER TABLE public.classi_di_materiali OWNER TO postgres;
+
+--
+-- TOC entry 205 (class 1259 OID 16643)
+-- Name: composizioni; Type: TABLE; Schema: public; Owner: postgres
+--
+
+CREATE TABLE public.composizioni (
+ codice_lavorazione character(20) NOT NULL,
+ codice_intaglio integer NOT NULL
+);
+
+
+ALTER TABLE public.composizioni OWNER TO postgres;
+
+--
+-- TOC entry 207 (class 1259 OID 16663)
+-- Name: filamenti; Type: TABLE; Schema: public; Owner: postgres
+--
+
+CREATE TABLE public.filamenti (
+ codice_filamento character(7) NOT NULL,
+ codice_plastica character(3) NOT NULL,
+ marca character varying(30) NOT NULL,
+ colore character varying(30) NOT NULL
+);
+
+
+ALTER TABLE public.filamenti OWNER TO postgres;
+
+--
+-- TOC entry 203 (class 1259 OID 16593)
+-- Name: intagli; Type: TABLE; Schema: public; Owner: postgres
+--
+
+CREATE TABLE public.intagli (
+ codice_intaglio integer NOT NULL,
+ data_richiesta date NOT NULL,
+ data_consegna date,
+ tempo double precision,
+ costo_materiali numeric(6,2),
+ costo_totale numeric(6,2),
+ cf_richiedente character(16) NOT NULL,
+ cf_incaricato character(16),
+ descrizione character varying(400) NOT NULL,
+ CONSTRAINT date_intaglio CHECK ((data_richiesta <= data_consegna)),
+ CONSTRAINT tempo_intaglio CHECK ((tempo > (0)::double precision))
+);
+
+
+ALTER TABLE public.intagli OWNER TO postgres;
+
+--
+-- TOC entry 210 (class 1259 OID 16722)
+-- Name: intagli_codice_intaglio_seq; Type: SEQUENCE; Schema: public; Owner: postgres
+--
+
+ALTER TABLE public.intagli ALTER COLUMN codice_intaglio ADD GENERATED BY DEFAULT AS IDENTITY (
+ SEQUENCE NAME public.intagli_codice_intaglio_seq
+ START WITH 0
+ INCREMENT BY 1
+ MINVALUE 0
+ NO MAXVALUE
+ CACHE 1
+);
+
+
+--
+-- TOC entry 202 (class 1259 OID 16561)
+-- Name: lavorazioni; Type: TABLE; Schema: public; Owner: postgres
+--
+
+CREATE TABLE public.lavorazioni (
+ codice_tipo character(3) NOT NULL,
+ codice_lavorazione character(20) NOT NULL,
+ codice_materiale character(8) NOT NULL,
+ potenza_massima integer NOT NULL,
+ potenza_minima integer NOT NULL,
+ velocita integer NOT NULL,
+ descrizione character varying(400) NOT NULL
+);
+
+
+ALTER TABLE public.lavorazioni OWNER TO postgres;
+
+--
+-- TOC entry 199 (class 1259 OID 16461)
+-- Name: materiali; Type: TABLE; Schema: public; Owner: postgres
+--
+
+CREATE TABLE public.materiali (
+ codice_classe character(2) NOT NULL,
+ codice_materiale character(8) NOT NULL,
+ nome character varying(30) NOT NULL,
+ spessore double precision NOT NULL,
+ descrizione character varying(400) NOT NULL
+);
+
+
+ALTER TABLE public.materiali OWNER TO postgres;
+
+--
+-- TOC entry 200 (class 1259 OID 16496)
+-- Name: persone; Type: TABLE; Schema: public; Owner: postgres
+--
+
+CREATE TABLE public.persone (
+ cf character(16) NOT NULL,
+ nome character varying(30) NOT NULL,
+ cognome character varying(30) NOT NULL,
+ socio boolean NOT NULL,
+ operatore_intagliatrice boolean NOT NULL,
+ operatore_stampante boolean NOT NULL,
+ spesa_totale numeric(6,2) NOT NULL,
+ CONSTRAINT abilitazioni CHECK (((socio = true) OR ((operatore_intagliatrice = false) AND (operatore_stampante = false))))
+);
+
+
+ALTER TABLE public.persone OWNER TO postgres;
+
+--
+-- TOC entry 206 (class 1259 OID 16658)
+-- Name: plastiche; Type: TABLE; Schema: public; Owner: postgres
+--
+
+CREATE TABLE public.plastiche (
+ codice_plastica character(3) NOT NULL,
+ nome character varying(100) NOT NULL,
+ descrizione character varying(400) NOT NULL
+);
+
+
+ALTER TABLE public.plastiche OWNER TO postgres;
+
+--
+-- TOC entry 212 (class 1259 OID 16791)
+-- Name: sessioni; Type: TABLE; Schema: public; Owner: postgres
+--
+
+CREATE TABLE public.sessioni (
+ ora_creazione timestamp(6) with time zone NOT NULL,
+ id_sessione integer NOT NULL,
+ utente character varying(30) NOT NULL
+);
+
+
+ALTER TABLE public.sessioni OWNER TO postgres;
+
+--
+-- TOC entry 213 (class 1259 OID 16796)
+-- Name: sessioni_id_sessione_seq; Type: SEQUENCE; Schema: public; Owner: postgres
+--
+
+ALTER TABLE public.sessioni ALTER COLUMN id_sessione ADD GENERATED BY DEFAULT AS IDENTITY (
+ SEQUENCE NAME public.sessioni_id_sessione_seq
+ START WITH 0
+ INCREMENT BY 1
+ MINVALUE 0
+ NO MAXVALUE
+ CACHE 1
+);
+
+
+--
+-- TOC entry 197 (class 1259 OID 16451)
+-- Name: stampanti; Type: TABLE; Schema: public; Owner: postgres
+--
+
+CREATE TABLE public.stampanti (
+ codice_stampante character(3) NOT NULL,
+ marca character varying(30) NOT NULL,
+ modello character varying(30) NOT NULL,
+ descrizione character varying(400) NOT NULL
+);
+
+
+ALTER TABLE public.stampanti OWNER TO postgres;
+
+--
+-- TOC entry 204 (class 1259 OID 16616)
+-- Name: stampe; Type: TABLE; Schema: public; Owner: postgres
+--
+
+CREATE TABLE public.stampe (
+ codice_stampa integer NOT NULL,
+ data_richiesta date NOT NULL,
+ data_consegna date,
+ tempo double precision,
+ costo_materiali numeric(6,2),
+ costo_totale numeric(6,2),
+ descrizione character varying(400) NOT NULL,
+ cf_richiedente character(16) NOT NULL,
+ cf_incaricato character(16),
+ codice_stampante character(3),
+ CONSTRAINT date_stampe CHECK ((data_richiesta <= data_consegna)),
+ CONSTRAINT tempo_stampe CHECK ((tempo > (0)::double precision))
+);
+
+
+ALTER TABLE public.stampe OWNER TO postgres;
+
+--
+-- TOC entry 209 (class 1259 OID 16720)
+-- Name: stampe_codice_stampa_seq; Type: SEQUENCE; Schema: public; Owner: postgres
+--
+
+ALTER TABLE public.stampe ALTER COLUMN codice_stampa ADD GENERATED BY DEFAULT AS IDENTITY (
+ SEQUENCE NAME public.stampe_codice_stampa_seq
+ START WITH 0
+ INCREMENT BY 1
+ MINVALUE 0
+ NO MAXVALUE
+ CACHE 1
+);
+
+
+--
+-- TOC entry 201 (class 1259 OID 16503)
+-- Name: tipi; Type: TABLE; Schema: public; Owner: postgres
+--
+
+CREATE TABLE public.tipi (
+ codice_tipo character(3) NOT NULL,
+ nome character varying(30) NOT NULL,
+ descrizione character varying(400) NOT NULL
+);
+
+
+ALTER TABLE public.tipi OWNER TO postgres;
+
+--
+-- TOC entry 208 (class 1259 OID 16675)
+-- Name: usi; Type: TABLE; Schema: public; Owner: postgres
+--
+
+CREATE TABLE public.usi (
+ codice_stampa integer NOT NULL,
+ codice_filamento character(7) NOT NULL
+);
+
+
+ALTER TABLE public.usi OWNER TO postgres;
+
+--
+-- TOC entry 211 (class 1259 OID 16786)
+-- Name: utenti; Type: TABLE; Schema: public; Owner: postgres
+--
+
+CREATE TABLE public.utenti (
+ username character varying(30) NOT NULL,
+ hash bytea NOT NULL,
+ admin boolean DEFAULT false NOT NULL
+);
+
+
+ALTER TABLE public.utenti OWNER TO postgres;
+
+--
+-- TOC entry 2798 (class 2606 OID 16790)
+-- Name: utenti admins_pkey; Type: CONSTRAINT; Schema: public; Owner: postgres
+--
+
+ALTER TABLE ONLY public.utenti
+ ADD CONSTRAINT admins_pkey PRIMARY KEY (username);
+
+
+--
+-- TOC entry 2772 (class 2606 OID 16509)
+-- Name: classi_di_materiali classi di materiali_pkey; Type: CONSTRAINT; Schema: public; Owner: postgres
+--
+
+ALTER TABLE ONLY public.classi_di_materiali
+ ADD CONSTRAINT "classi di materiali_pkey" PRIMARY KEY (codice_classe);
+
+
+--
+-- TOC entry 2788 (class 2606 OID 16878)
+-- Name: composizioni composizioni_pkey; Type: CONSTRAINT; Schema: public; Owner: postgres
+--
+
+ALTER TABLE ONLY public.composizioni
+ ADD CONSTRAINT composizioni_pkey PRIMARY KEY (codice_lavorazione, codice_intaglio);
+
+
+--
+-- TOC entry 2760 (class 2606 OID 16714)
+-- Name: intagli consegna_intaglio; Type: CHECK CONSTRAINT; Schema: public; Owner: postgres
+--
+
+ALTER TABLE public.intagli
+ ADD CONSTRAINT consegna_intaglio CHECK (((data_consegna IS NULL) OR ((data_consegna IS NOT NULL) AND (costo_totale IS NOT NULL) AND (costo_materiali IS NOT NULL) AND (tempo IS NOT NULL) AND (cf_incaricato IS NOT NULL)))) NOT VALID;
+
+
+--
+-- TOC entry 2764 (class 2606 OID 16702)
+-- Name: stampe consegna_stampe; Type: CHECK CONSTRAINT; Schema: public; Owner: postgres
+--
+
+ALTER TABLE public.stampe
+ ADD CONSTRAINT consegna_stampe CHECK (((data_consegna IS NULL) OR ((data_consegna IS NOT NULL) AND (costo_totale IS NOT NULL) AND (costo_materiali IS NOT NULL) AND (tempo IS NOT NULL) AND (cf_incaricato IS NOT NULL) AND (codice_stampante IS NOT NULL)))) NOT VALID;
+
+
+--
+-- TOC entry 2761 (class 2606 OID 16719)
+-- Name: intagli costi_intaglio; Type: CHECK CONSTRAINT; Schema: public; Owner: postgres
+--
+
+ALTER TABLE public.intagli
+ ADD CONSTRAINT costi_intaglio CHECK (((costo_totale >= costo_materiali) AND (costo_materiali >= (0)::numeric))) NOT VALID;
+
+
+--
+-- TOC entry 2765 (class 2606 OID 16707)
+-- Name: stampe costi_stampe; Type: CHECK CONSTRAINT; Schema: public; Owner: postgres
+--
+
+ALTER TABLE public.stampe
+ ADD CONSTRAINT costi_stampe CHECK (((costo_totale >= costo_materiali) AND (costo_materiali >= (0)::numeric))) NOT VALID;
+
+
+--
+-- TOC entry 2792 (class 2606 OID 16817)
+-- Name: filamenti filamenti_codice_plastica_marca_colore_key; Type: CONSTRAINT; Schema: public; Owner: postgres
+--
+
+ALTER TABLE ONLY public.filamenti
+ ADD CONSTRAINT filamenti_codice_plastica_marca_colore_key UNIQUE (codice_plastica, marca, colore);
+
+
+--
+-- TOC entry 2794 (class 2606 OID 16667)
+-- Name: filamenti filamenti_pkey; Type: CONSTRAINT; Schema: public; Owner: postgres
+--
+
+ALTER TABLE ONLY public.filamenti
+ ADD CONSTRAINT filamenti_pkey PRIMARY KEY (codice_filamento);
+
+
+--
+-- TOC entry 2784 (class 2606 OID 16601)
+-- Name: intagli intagli_pkey; Type: CONSTRAINT; Schema: public; Owner: postgres
+--
+
+ALTER TABLE ONLY public.intagli
+ ADD CONSTRAINT intagli_pkey PRIMARY KEY (codice_intaglio);
+
+
+--
+-- TOC entry 2757 (class 2606 OID 16584)
+-- Name: lavorazioni intervallo_potenza_max; Type: CHECK CONSTRAINT; Schema: public; Owner: postgres
+--
+
+ALTER TABLE public.lavorazioni
+ ADD CONSTRAINT intervallo_potenza_max CHECK (((potenza_massima >= 0) AND (potenza_massima < 300))) NOT VALID;
+
+
+--
+-- TOC entry 2758 (class 2606 OID 16585)
+-- Name: lavorazioni intervallo_potenza_min; Type: CHECK CONSTRAINT; Schema: public; Owner: postgres
+--
+
+ALTER TABLE public.lavorazioni
+ ADD CONSTRAINT intervallo_potenza_min CHECK (((potenza_minima >= 0) AND (potenza_massima < 300))) NOT VALID;
+
+
+--
+-- TOC entry 2759 (class 2606 OID 16586)
+-- Name: lavorazioni intervallo_velocita; Type: CHECK CONSTRAINT; Schema: public; Owner: postgres
+--
+
+ALTER TABLE public.lavorazioni
+ ADD CONSTRAINT intervallo_velocita CHECK (((velocita > 0) AND (velocita < 300))) NOT VALID;
+
+
+--
+-- TOC entry 2780 (class 2606 OID 16866)
+-- Name: lavorazioni lavorazioni_pkey; Type: CONSTRAINT; Schema: public; Owner: postgres
+--
+
+ALTER TABLE ONLY public.lavorazioni
+ ADD CONSTRAINT lavorazioni_pkey PRIMARY KEY (codice_lavorazione);
+
+
+--
+-- TOC entry 2782 (class 2606 OID 16854)
+-- Name: lavorazioni lavorazioni_unique; Type: CONSTRAINT; Schema: public; Owner: postgres
+--
+
+ALTER TABLE ONLY public.lavorazioni
+ ADD CONSTRAINT lavorazioni_unique UNIQUE (codice_materiale, potenza_minima, potenza_massima, codice_tipo, velocita);
+
+
+--
+-- TOC entry 2774 (class 2606 OID 16843)
+-- Name: materiali materiali_pkey; Type: CONSTRAINT; Schema: public; Owner: postgres
+--
+
+ALTER TABLE ONLY public.materiali
+ ADD CONSTRAINT materiali_pkey PRIMARY KEY (codice_materiale);
+
+
+--
+-- TOC entry 2776 (class 2606 OID 16502)
+-- Name: persone persone_pkey; Type: CONSTRAINT; Schema: public; Owner: postgres
+--
+
+ALTER TABLE ONLY public.persone
+ ADD CONSTRAINT persone_pkey PRIMARY KEY (cf);
+
+
+--
+-- TOC entry 2790 (class 2606 OID 16662)
+-- Name: plastiche plastiche_pkey; Type: CONSTRAINT; Schema: public; Owner: postgres
+--
+
+ALTER TABLE ONLY public.plastiche
+ ADD CONSTRAINT plastiche_pkey PRIMARY KEY (codice_plastica);
+
+
+--
+-- TOC entry 2800 (class 2606 OID 16799)
+-- Name: sessioni sessioni_pkey; Type: CONSTRAINT; Schema: public; Owner: postgres
+--
+
+ALTER TABLE ONLY public.sessioni
+ ADD CONSTRAINT sessioni_pkey PRIMARY KEY (id_sessione);
+
+
+--
+-- TOC entry 2756 (class 2606 OID 16694)
+-- Name: persone spesa; Type: CHECK CONSTRAINT; Schema: public; Owner: postgres
+--
+
+ALTER TABLE public.persone
+ ADD CONSTRAINT spesa CHECK ((spesa_totale >= (0)::numeric)) NOT VALID;
+
+
+--
+-- TOC entry 2754 (class 2606 OID 16560)
+-- Name: materiali spessore_positivo; Type: CHECK CONSTRAINT; Schema: public; Owner: postgres
+--
+
+ALTER TABLE public.materiali
+ ADD CONSTRAINT spessore_positivo CHECK ((spessore > (0)::double precision)) NOT VALID;
+
+
+--
+-- TOC entry 2770 (class 2606 OID 16455)
+-- Name: stampanti stampanti_pkey; Type: CONSTRAINT; Schema: public; Owner: postgres
+--
+
+ALTER TABLE ONLY public.stampanti
+ ADD CONSTRAINT stampanti_pkey PRIMARY KEY (codice_stampante);
+
+
+--
+-- TOC entry 2786 (class 2606 OID 16624)
+-- Name: stampe stampe_pkey; Type: CONSTRAINT; Schema: public; Owner: postgres
+--
+
+ALTER TABLE ONLY public.stampe
+ ADD CONSTRAINT stampe_pkey PRIMARY KEY (codice_stampa);
+
+
+--
+-- TOC entry 2778 (class 2606 OID 16890)
+-- Name: tipi tipi_pkey; Type: CONSTRAINT; Schema: public; Owner: postgres
+--
+
+ALTER TABLE ONLY public.tipi
+ ADD CONSTRAINT tipi_pkey PRIMARY KEY (codice_tipo);
+
+
+--
+-- TOC entry 2796 (class 2606 OID 16679)
+-- Name: usi usi_pkey; Type: CONSTRAINT; Schema: public; Owner: postgres
+--
+
+ALTER TABLE ONLY public.usi
+ ADD CONSTRAINT usi_pkey PRIMARY KEY (codice_stampa, codice_filamento);
+
+
+--
+-- TOC entry 2801 (class 2606 OID 16510)
+-- Name: materiali classe_materiale; Type: FK CONSTRAINT; Schema: public; Owner: postgres
+--
+
+ALTER TABLE ONLY public.materiali
+ ADD CONSTRAINT classe_materiale FOREIGN KEY (codice_classe) REFERENCES public.classi_di_materiali(codice_classe) ON DELETE CASCADE;
+
+
+--
+-- TOC entry 2810 (class 2606 OID 16653)
+-- Name: composizioni composizioni_codice_intaglio_fkey; Type: FK CONSTRAINT; Schema: public; Owner: postgres
+--
+
+ALTER TABLE ONLY public.composizioni
+ ADD CONSTRAINT composizioni_codice_intaglio_fkey FOREIGN KEY (codice_intaglio) REFERENCES public.intagli(codice_intaglio) ON DELETE CASCADE;
+
+
+--
+-- TOC entry 2809 (class 2606 OID 16879)
+-- Name: composizioni composizioni_codice_lavorazione_fkey; Type: FK CONSTRAINT; Schema: public; Owner: postgres
+--
+
+ALTER TABLE ONLY public.composizioni
+ ADD CONSTRAINT composizioni_codice_lavorazione_fkey FOREIGN KEY (codice_lavorazione) REFERENCES public.lavorazioni(codice_lavorazione) ON DELETE CASCADE;
+
+
+--
+-- TOC entry 2811 (class 2606 OID 16818)
+-- Name: filamenti filamenti_codice_plastica_fkey; Type: FK CONSTRAINT; Schema: public; Owner: postgres
+--
+
+ALTER TABLE ONLY public.filamenti
+ ADD CONSTRAINT filamenti_codice_plastica_fkey FOREIGN KEY (codice_plastica) REFERENCES public.plastiche(codice_plastica) ON DELETE CASCADE;
+
+
+--
+-- TOC entry 2805 (class 2606 OID 16607)
+-- Name: intagli incaricato_intaglio; Type: FK CONSTRAINT; Schema: public; Owner: postgres
+--
+
+ALTER TABLE ONLY public.intagli
+ ADD CONSTRAINT incaricato_intaglio FOREIGN KEY (cf_incaricato) REFERENCES public.persone(cf);
+
+
+--
+-- TOC entry 2807 (class 2606 OID 16630)
+-- Name: stampe incaricato_stampa; Type: FK CONSTRAINT; Schema: public; Owner: postgres
+--
+
+ALTER TABLE ONLY public.stampe
+ ADD CONSTRAINT incaricato_stampa FOREIGN KEY (cf_incaricato) REFERENCES public.persone(cf);
+
+
+--
+-- TOC entry 2802 (class 2606 OID 16855)
+-- Name: lavorazioni materiale_lavorazione_fkey; Type: FK CONSTRAINT; Schema: public; Owner: postgres
+--
+
+ALTER TABLE ONLY public.lavorazioni
+ ADD CONSTRAINT materiale_lavorazione_fkey FOREIGN KEY (codice_materiale) REFERENCES public.materiali(codice_materiale) ON DELETE CASCADE;
+
+
+--
+-- TOC entry 2804 (class 2606 OID 16602)
+-- Name: intagli richiedente_intaglio; Type: FK CONSTRAINT; Schema: public; Owner: postgres
+--
+
+ALTER TABLE ONLY public.intagli
+ ADD CONSTRAINT richiedente_intaglio FOREIGN KEY (cf_richiedente) REFERENCES public.persone(cf);
+
+
+--
+-- TOC entry 2806 (class 2606 OID 16625)
+-- Name: stampe richiedente_stampa; Type: FK CONSTRAINT; Schema: public; Owner: postgres
+--
+
+ALTER TABLE ONLY public.stampe
+ ADD CONSTRAINT richiedente_stampa FOREIGN KEY (cf_richiedente) REFERENCES public.persone(cf);
+
+
+--
+-- TOC entry 2814 (class 2606 OID 16800)
+-- Name: sessioni sessioni_admin_fkey; Type: FK CONSTRAINT; Schema: public; Owner: postgres
+--
+
+ALTER TABLE ONLY public.sessioni
+ ADD CONSTRAINT sessioni_admin_fkey FOREIGN KEY (utente) REFERENCES public.utenti(username);
+
+
+--
+-- TOC entry 2808 (class 2606 OID 16635)
+-- Name: stampe stampante; Type: FK CONSTRAINT; Schema: public; Owner: postgres
+--
+
+ALTER TABLE ONLY public.stampe
+ ADD CONSTRAINT stampante FOREIGN KEY (codice_stampante) REFERENCES public.stampanti(codice_stampante);
+
+
+--
+-- TOC entry 2803 (class 2606 OID 16891)
+-- Name: lavorazioni tipo_lavorazione_fkey; Type: FK CONSTRAINT; Schema: public; Owner: postgres
+--
+
+ALTER TABLE ONLY public.lavorazioni
+ ADD CONSTRAINT tipo_lavorazione_fkey FOREIGN KEY (codice_tipo) REFERENCES public.tipi(codice_tipo) ON DELETE CASCADE;
+
+
+--
+-- TOC entry 2813 (class 2606 OID 16685)
+-- Name: usi usi_codice_filamento_fkey; Type: FK CONSTRAINT; Schema: public; Owner: postgres
+--
+
+ALTER TABLE ONLY public.usi
+ ADD CONSTRAINT usi_codice_filamento_fkey FOREIGN KEY (codice_filamento) REFERENCES public.filamenti(codice_filamento);
+
+
+--
+-- TOC entry 2812 (class 2606 OID 16680)
+-- Name: usi usi_codice_stampa_fkey; Type: FK CONSTRAINT; Schema: public; Owner: postgres
+--
+
+ALTER TABLE ONLY public.usi
+ ADD CONSTRAINT usi_codice_stampa_fkey FOREIGN KEY (codice_stampa) REFERENCES public.stampe(codice_stampa);
+
+
+-- Completed on 2019-10-06 10:54:41
+
+--
+-- PostgreSQL database dump complete
+--
+
diff --git a/package.yaml b/package.yaml
index d6c6173..d009a6f 100644
--- a/package.yaml
+++ b/package.yaml
@@ -20,10 +20,24 @@ extra-source-files:
description: Please see the README on GitHub at
dependencies:
+- aeson
- base >= 4.7 && < 5
-- Spock >= 0.11
+- beam-core
+- beam-postgres
+- bytestring
+- configurator
+- cryptonite
+- hvect
+- memory
- mtl
+- optparse-applicative
+- postgresql-libpq
+- scientific
+- Spock >= 0.11
- text
+- time
+- utf8-string
+- wai-middleware-static
library:
source-dirs: src
diff --git a/src/Lib.hs b/src/Lib.hs
deleted file mode 100644
index d36ff27..0000000
--- a/src/Lib.hs
+++ /dev/null
@@ -1,6 +0,0 @@
-module Lib
- ( someFunc
- ) where
-
-someFunc :: IO ()
-someFunc = putStrLn "someFunc"
diff --git a/src/Query.hs b/src/Query.hs
new file mode 100644
index 0000000..2d8df9d
--- /dev/null
+++ b/src/Query.hs
@@ -0,0 +1,679 @@
+{-# LANGUAGE DeriveAnyClass #-}
+{-# LANGUAGE DeriveGeneric #-}
+{-# LANGUAGE FlexibleContexts #-}
+{-# LANGUAGE FlexibleInstances #-}
+{-# LANGUAGE GADTs #-}
+{-# LANGUAGE MultiParamTypeClasses #-}
+{-# LANGUAGE OverloadedStrings #-}
+{-# LANGUAGE PartialTypeSignatures #-}
+{-# LANGUAGE StandaloneDeriving #-}
+{-# LANGUAGE TypeApplications #-}
+{-# LANGUAGE TypeFamilies #-}
+{-# LANGUAGE TypeSynonymInstances #-}
+
+-- |Module used for the queries in the database
+module Query where
+
+import Control.Exception
+import Data.ByteString.UTF8 (fromString, toString)
+import Data.Int (Int)
+import Data.Scientific
+import Data.Text
+import Data.Time.Calendar
+import Database.Beam
+import Database.Beam.Backend.SQL (BeamSqlBackend)
+import Database.Beam.Postgres
+import Database.PostgreSQL.LibPQ (ExecStatus (NonfatalError))
+import Schema
+
+-- constants and general functions
+-- |Creates a standard uri for connecting to the database with the given username and password
+createUri :: String -> String -> String
+createUri user pswd = "postgres://" ++ user ++ ":" ++ pswd ++ "@localhost/FabLab"
+
+-- |Given an uri, returns a connection to the database
+connectWithUri :: String -> IO Connection
+connectWithUri uri = connectPostgreSQL $ fromString uri
+
+-- |Connects to the database with the given characteristics
+connectWithInfo :: String -> Integer -> String -> String -> String -> IO Connection
+connectWithInfo host port user pswd db = connect $ ConnectInfo host (fromInteger port) user pswd db
+
+-- |Given a connection, close it
+closeConnection :: Connection -> IO ()
+closeConnection = close
+
+runBeam :: Connection -> Pg a -> IO a
+runBeam = runBeamPostgres --Debug putStrLn -- change for debug or production purposes
+
+allElementsOfTable
+ :: (Table t, BeamSqlBackend be)
+ => ( DatabaseSettings be FabLabDB
+ -> DatabaseEntity be FabLabDB (TableEntity t)
+ )
+ -> Q be FabLabDB s (t (QExpr be s))
+allElementsOfTable table = all_ (table fabLabDB)
+
+-- |A generic select for list of elements with filters
+{-genericSelectList :: (Table t, Generic (t Identity),
+ Generic (t Database.Beam.Backend.Types.Exposed),
+ Database.Beam.Backend.SQL.Row.GFromBackendRow
+ Postgres
+ (GHC.Generics.Rep (t Database.Beam.Backend.Types.Exposed))
+ (GHC.Generics.Rep (t Identity))) =>
+ (DatabaseSettings Postgres FabLabDB
+ -> DatabaseEntity Postgres FabLabDB (TableEntity t))
+ -> Maybe
+ (t (QExpr Postgres QBaseScope) -> QExpr Postgres QBaseScope Bool)
+ -> Connection
+ -> IO (Either SomeException [t Identity])-}
+genericSelectList table maybeFilter =
+ let pool = case maybeFilter of
+ Nothing -> allElementsOfTable table
+ Just f -> filter_ f $ allElementsOfTable table
+ in \conn ->
+ try
+ $ runBeam conn
+ $ runSelectReturningList
+ $ select pool
+
+-- |A generic select for a single element with filters
+genericSelectOne table f =
+ \conn ->
+ try
+ $ runBeam conn
+ $ runSelectReturningOne
+ $ select
+ $ filter_ f
+ $ allElementsOfTable table
+
+-- |Prepares a code to be used as key
+prepareCode :: String -> Text
+prepareCode = toUpper . pack
+
+-- |Prepares a string to be used as a name in the database
+prepareName :: String -> Text
+prepareName = toTitle . pack
+
+-- people queries
+-- |Select all people in the database
+selectAllPeople :: Connection -> IO (Either SqlError [Person])
+selectAllPeople = genericSelectList _persone Nothing
+
+-- |Select all partners in the database
+selectAllPartners :: Connection -> IO (Either SqlError [Person])
+selectAllPartners = genericSelectList _persone $ Just (\p -> _personSocio p ==. (val_ True))
+
+-- |Select all laser cutter operators in the database
+selectAllLaserCutterOperators :: Connection -> IO (Either SqlError [Person])
+selectAllLaserCutterOperators =
+ genericSelectList _persone $ Just (\p -> _personOperatoreIntagliatrice p ==. (val_ True))
+
+-- |Select all 3D printer operators in the database
+selectAllPrinterOperators :: Connection -> IO (Either SqlError [Person])
+selectAllPrinterOperators =
+ genericSelectList _persone $ Just (\p -> _personOperatoreStampante p ==. (val_ True))
+
+-- |Select all people with the given cf (should be 0 or 1) in the database
+selectPersonFromCF :: String -> (Connection -> IO (Either SqlError (Maybe Person)))
+selectPersonFromCF cf =
+ genericSelectOne _persone (\p -> _personCf p ==. (val_ (pack cf)))
+
+-- |Add a person to the database
+insertPerson :: String -> String -> String -> (Connection -> IO (Either SqlError ()))
+insertPerson cf name surname =
+ \conn ->
+ try
+ $ runBeam conn
+ $ runInsert
+ $ insert (_persone fabLabDB)
+ $ insertValues
+ [ Person
+ (prepareCode cf)
+ (prepareName name)
+ (prepareName surname)
+ False
+ False
+ False
+ 0.0
+ ]
+
+-- |Modify a person already in the database
+modifyPerson :: String -> Bool -> Bool -> Bool -> (Connection -> IO (Either SqlError ()))
+modifyPerson cf partner cutter printer =
+ \conn ->
+ try
+ $ runBeam conn
+ $ runUpdate
+ $ update (_persone fabLabDB)
+ ( \p ->
+ mconcat
+ [ _personSocio p <-. (val_ partner),
+ _personOperatoreIntagliatrice p <-. (val_ cutter),
+ _personOperatoreStampante p <-. (val_ printer)
+ ]
+ )
+ (\p -> _personCf p ==. (val_ (prepareCode cf)))
+
+-- materials queries
+-- |Select all materials in the database
+selectAllMaterials :: Connection -> IO (Either SqlError [Material])
+selectAllMaterials = genericSelectList _materiali Nothing
+
+-- |Select all classes of materials in the database
+selectAllMaterialsClasses :: Connection -> IO (Either SqlError [MaterialsClass])
+selectAllMaterialsClasses = genericSelectList _classi_di_materiali Nothing
+
+-- |Select all the materials classes with the given code (should be 1 or 0) in the database
+selectMaterialsClassFromCode :: String -> (Connection -> IO (Either SqlError (Maybe MaterialsClass)))
+selectMaterialsClassFromCode code =
+ genericSelectOne _classi_di_materiali (\c -> _materialsclassCodiceClasse c ==. (val_ (prepareCode code)))
+
+-- |Select all materials with the given code (should be 1 or 0) in the database
+selectMaterialFromCode :: String -> (Connection -> IO (Either SqlError (Maybe Material)))
+selectMaterialFromCode code =
+ genericSelectOne _materiali (\m -> _materialCodiceMateriale m ==. (val_ (prepareCode code)))
+
+-- |Select all materials of a given class in the database
+selectMaterialsByClass :: String -> (Connection -> IO (Either SqlError [Material]))
+selectMaterialsByClass classCode =
+ \conn -> do
+ selectedClasses <- (selectMaterialsClassFromCode classCode) conn
+ case selectedClasses of
+ Left ex -> return $ Left ex
+ Right Nothing -> return $ Right []
+ Right (Just mClass) ->
+ try $ runBeam conn
+ $ runSelectReturningList
+ $ select
+ $ filter_ (\m -> _materialCodiceClasse m ==. val_ (pk mClass))
+ $ allElementsOfTable _materiali
+
+-- |Add a class of materials to the database in the database
+insertMaterialsClass :: String -> String -> (Connection -> IO (Either SqlError ()))
+insertMaterialsClass code name =
+ \conn ->
+ try
+ $ runBeam conn
+ $ runInsert
+ $ insert (_classi_di_materiali fabLabDB)
+ $ insertValues
+ [ MaterialsClass
+ (prepareCode code)
+ (prepareName name)
+ ]
+
+-- |Add a material to the database. The code is the id of the material inside the materials class.
+insertMaterial :: String -> String -> String -> Double -> String -> (Connection -> IO (Either SqlError ()))
+insertMaterial code classCode name width descr =
+ \conn -> do
+ selectedClasses <- (selectMaterialsClassFromCode classCode) conn
+ case selectedClasses of
+ Left ex -> return $ Left ex
+ Right Nothing -> return $ Left $ SqlError "" NonfatalError "No class with the given code was present" "" ""
+ Right (Just mClass) ->
+ try $ runBeam conn
+ $ runInsert
+ $ insert (_materiali fabLabDB)
+ $ insertValues
+ [ Material
+ (pk mClass)
+ (prepareCode (classCode ++ code ++ show width))
+ (prepareName name)
+ width
+ (pack descr)
+ ]
+
+-- processings queries
+-- |Select the processing with the given code in the database
+selectProcessingFromCode :: String -> (Connection -> IO (Either SqlError (Maybe Processing)))
+selectProcessingFromCode pCode =
+ genericSelectOne _lavorazioni (\p -> _processingCodiceLavorazione p ==. val_ (prepareCode pCode))
+
+-- |Select all processings in the database
+selectAllProcessings :: Connection -> IO (Either SqlError [Processing])
+selectAllProcessings = genericSelectList _lavorazioni Nothing
+
+-- |Select all types of processing in the database
+selectAllTypes :: Connection -> IO (Either SqlError [Type])
+selectAllTypes = genericSelectList _tipi Nothing
+
+-- |Select all types of processing with the given code (should be 1 or 0) in the database
+selectTypeFromCode :: String -> (Connection -> IO (Either SqlError (Maybe Type)))
+selectTypeFromCode code =
+ genericSelectOne _tipi (\t -> _typeCodiceTipo t ==. (val_ (prepareCode code)))
+
+-- |Select all processings on a given material in the database
+selectProcessingsByMaterials :: String -> (Connection -> IO (Either SqlError [Processing]))
+selectProcessingsByMaterials mCode =
+ \conn -> do
+ selectedMaterials <- (selectMaterialFromCode mCode) conn
+ case selectedMaterials of
+ Left ex -> return $ Left ex
+ Right Nothing -> return $ Left $ SqlError "" NonfatalError "No material with the given code was present" "" ""
+ Right (Just material) ->
+ try $ runBeam conn
+ $ runSelectReturningList
+ $ select
+ $ filter_ (\p -> _processingCodiceMateriale p ==. val_ (pk material))
+ $ allElementsOfTable _lavorazioni
+
+-- |Add a type of processing to the database
+insertType :: String -> String -> String -> (Connection -> IO (Either SqlError ()))
+insertType code name descr =
+ \conn ->
+ try
+ $ runBeam conn
+ $ runInsert
+ $ insert (_tipi fabLabDB)
+ $ insertValues
+ [ Type
+ (prepareCode code)
+ (prepareName name)
+ (pack descr)
+ ]
+
+-- |Add a new processing to the database
+insertProcessing :: String -> String -> Int -> Int -> Int -> String -> (Connection -> IO (Either SqlError ()))
+insertProcessing typeCode materialCode maxPotency minPotency speed descr =
+ \conn -> do
+ selectedTypes <- (selectTypeFromCode typeCode) conn
+ selectedMaterials <- (selectMaterialFromCode materialCode) conn
+ case (selectedTypes, selectedMaterials) of
+ (Left ex, Left ex') -> return $ Left $ (error $ (toString $ sqlErrorMsg ex) ++ (toString $ sqlErrorMsg ex'))
+ (Left ex, _) -> return $ Left ex
+ (_, Left ex) -> return $ Left ex
+ (Right Nothing, _) -> return $ Left $ SqlError "" NonfatalError "No type with the given code was present" "" ""
+ (_, Right Nothing) -> return $ Left $ SqlError "" NonfatalError "No material with the given code was present" "" ""
+ (Right (Just pType), Right (Just material)) ->
+ let code = materialCode ++ (show maxPotency) ++ (show minPotency) ++ (show speed) ++ typeCode
+ in try $ runBeam conn
+ $ runInsert
+ $ insert (_lavorazioni fabLabDB)
+ $ insertValues
+ [ Processing
+ (pk pType)
+ (prepareCode code)
+ (pk material)
+ maxPotency
+ minPotency
+ speed
+ (pack descr)
+ ]
+
+-- plastics and filaments queries
+-- |Select all filaments in the database
+selectAllFilaments :: Connection -> IO (Either SqlError [Filament])
+selectAllFilaments = genericSelectList _filamenti Nothing
+
+-- |Select all plastics in the database
+selectAllPlastics :: Connection -> IO (Either SqlError [Plastic])
+selectAllPlastics = genericSelectList _plastiche Nothing
+
+-- |Select all the plastics with the given code (should be 1 or 0) in the database
+selectPlasticFromCode :: String -> (Connection -> IO (Either SqlError (Maybe Plastic)))
+selectPlasticFromCode code =
+ genericSelectOne _plastiche (\p -> _plasticCodicePlastica p ==. (val_ (prepareCode code)))
+
+-- |Select all the filaments with the given code (should be 1 or 0) in the database
+selectFilamentFromCode :: String -> (Connection -> IO (Either SqlError (Maybe Filament)))
+selectFilamentFromCode code =
+ genericSelectOne _filamenti (\f -> _filamentCodiceFilamento f ==. (val_ (prepareCode code)))
+
+-- |Select the filaments made of a given plastic in the database
+selectFilamentsByPlastic :: String -> (Connection -> IO (Either SqlError [Filament]))
+selectFilamentsByPlastic plasticCode =
+ \conn -> do
+ selectedPlastics <- (selectPlasticFromCode plasticCode) conn
+ case selectedPlastics of
+ Left ex -> return $ Left ex
+ Right Nothing -> return $ Left $ SqlError "" NonfatalError "No plastic with the given code was present" "" ""
+ Right (Just plastic) ->
+ try $ runBeam conn
+ $ runSelectReturningList
+ $ select
+ $ filter_ (\f -> _filamentCodicePlastica f ==. val_ (pk plastic))
+ $ allElementsOfTable _filamenti
+
+-- |Add a type of plastic to the database
+insertPlastic :: String -> String -> String -> (Connection -> IO (Either SqlError ()))
+insertPlastic code name descr =
+ \conn ->
+ try
+ $ runBeam conn
+ $ runInsert
+ $ insert (_plastiche fabLabDB)
+ $ insertValues
+ [ Plastic
+ (prepareCode code)
+ (prepareName name)
+ (pack descr)
+ ]
+
+-- |Add a filament to the database. The code is the id of the filament inside the type of plastic
+insertFilament :: String -> String -> String -> String -> (Connection -> IO (Either SqlError ()))
+insertFilament code plasticCode brand color =
+ \conn -> do
+ selectedPlastics <- (selectPlasticFromCode plasticCode) conn
+ case selectedPlastics of
+ Left ex -> return $ Left ex
+ Right Nothing -> return $ Left $ SqlError "" NonfatalError "No class with the given code was present" "" ""
+ Right (Just plastic) ->
+ try $ runBeam conn
+ $ runInsert
+ $ insert (_filamenti fabLabDB)
+ $ insertValues
+ [ Filament
+ (prepareCode $ plasticCode ++ code)
+ (pk plastic)
+ (prepareName brand)
+ (prepareName color)
+ ]
+
+-- printers queries
+-- |Select all printers in the database
+selectAllPrinters :: Connection -> IO (Either SqlError [Printer])
+selectAllPrinters = genericSelectList _stampanti Nothing
+
+-- |Select all the printers with the given code (should be 0 or 1) in the database
+selectPrinterFromCode :: String -> (Connection -> IO (Either SqlError (Maybe Printer)))
+selectPrinterFromCode code =
+ genericSelectOne _stampanti (\p -> _printerCodiceStampante p ==. (val_ (prepareCode code)))
+
+-- |Add a printer to the database
+insertPrinter :: String -> String -> String -> String -> (Connection -> IO (Either SqlError ()))
+insertPrinter code brand model descr =
+ \conn ->
+ try
+ $ runBeam conn
+ $ runInsert
+ $ insert (_stampanti fabLabDB)
+ $ insertValues
+ [ Printer
+ (prepareCode code)
+ (prepareName brand)
+ (prepareName model)
+ (pack descr)
+ ]
+
+-- |Assign a printer to a print
+assignPrinter :: String -> Int -> (Connection -> IO (Either SqlError ()))
+assignPrinter printerCode printCode =
+ \conn -> do
+ selectedPrinters <- (selectPrinterFromCode printerCode) conn
+ case selectedPrinters of
+ Left ex -> return $ Left ex
+ Right Nothing -> return $ Left $ SqlError "" NonfatalError "No printer with the given code was present" "" ""
+ Right (Just printer) ->
+ try $ runBeam conn
+ $ runUpdate
+ $ update (_stampe fabLabDB)
+ (\s -> _printCodiceStampante s <-. just_ (val_ (pk printer)))
+ (\s -> _printCodiceStampa s ==. (val_ printCode))
+
+-- prints queries
+-- |Select the print with the given code in the database (should be 0 or 1)
+selectPrintFromCode :: Int -> (Connection -> IO (Either SqlError (Maybe Print)))
+selectPrintFromCode pCode =
+ genericSelectOne _stampe (\p -> _printCodiceStampa p ==. val_ pCode)
+
+-- |Select all prints in the database
+selectAllPrints :: Connection -> IO (Either SqlError [Print])
+selectAllPrints = genericSelectList _stampe Nothing
+
+-- |Select all the print that aren't completed in the database
+selectAllIncompletePrints :: Connection -> IO (Either SqlError [Print])
+selectAllIncompletePrints =
+ genericSelectList _stampe $ Just (\p -> _printDataConsegna p ==. val_ Nothing)
+
+-- |Select all the completed prints in the database
+selectAllCompletePrints :: Connection -> IO (Either SqlError [Print])
+selectAllCompletePrints =
+ genericSelectList _stampe $ Just (\p -> _printDataConsegna p /=. val_ Nothing)
+
+-- |Add a new print to the database
+insertPrint :: String -> Day -> String -> (Connection -> IO (Either SqlError ()))
+insertPrint cf insertDate descr =
+ \conn -> do
+ selectedPeople <- (selectPersonFromCF cf) conn
+ case selectedPeople of
+ Left ex -> return $ Left ex
+ Right Nothing -> return $ Left $ SqlError "" NonfatalError "No person with the given code was present" "" ""
+ Right (Just person) ->
+ try $ runBeam conn
+ $ runInsert
+ $ insert (_stampe fabLabDB)
+ $ insertExpressions
+ [ Print
+ { _printCodiceStampa = default_,
+ _printDataRichiesta = val_ insertDate,
+ _printDataConsegna = val_ Nothing,
+ _printTempo = val_ Nothing,
+ _printCostoMateriali = val_ Nothing,
+ _printCostoTotale = val_ Nothing,
+ _printDescrizione = val_ (pack descr),
+ _printCfRichiedente = val_ (pk person),
+ _printCfIncaricato = nothing_,
+ _printCodiceStampante = nothing_
+ }
+ ]
+
+-- |Assign a print to an operator
+assignPrint :: Int -> String -> (Connection -> IO (Either SqlError ()))
+assignPrint code cf =
+ \conn -> do
+ selectedOperators <- (selectPersonFromCF cf) conn
+ case selectedOperators of
+ Left ex -> return $ Left ex
+ Right Nothing -> return $ Left $ SqlError "" NonfatalError "No person with the given code was present" "" ""
+ Right (Just operator) ->
+ try $ runBeam conn
+ $ runUpdate
+ $ update (_stampe fabLabDB)
+ (\s -> _printCfIncaricato s <-. just_ (val_ (pk operator)))
+ (\s -> _printCodiceStampa s ==. val_ code)
+
+-- |Assign a filament to a print in the database
+assignFilament :: Int -> String -> (Connection -> IO (Either SqlError ()))
+assignFilament pCode fCode =
+ \conn -> do
+ selectedFilaments <- (selectFilamentFromCode fCode) conn
+ selectedPrints <- (selectPrintFromCode pCode) conn
+ case (selectedFilaments, selectedPrints) of
+ (Left ex, Left ex') -> return $ Left $ (error $ (toString $ sqlErrorMsg ex) ++ (toString $ sqlErrorMsg ex'))
+ (Left ex, _) -> return $ Left ex
+ (_, Left ex) -> return $ Left ex
+ (Right Nothing, _) -> return $ Left $ SqlError "" NonfatalError "No filament with the given code was present" "" ""
+ (_, Right Nothing) -> return $ Left $ SqlError "" NonfatalError "No print with the given code was present" "" ""
+ (Right (Just filament), Right (Just selectedPrint)) ->
+ try $ runBeam conn
+ $ runInsert
+ $ insert (_usi fabLabDB)
+ $ insertExpressions
+ [ Use
+ { _useCodiceFilamento = val_ (pk filament),
+ _useCodiceStampa = val_ (pk selectedPrint)
+ }
+ ]
+
+-- |Selects the filaments assigned to the given print
+assignmentsByPrint :: Int -> (Connection -> IO (Either SqlError [Use]))
+assignmentsByPrint pCode =
+ \conn -> do
+ selectedPrint <- selectPrintFromCode pCode conn
+ case selectedPrint of
+ Left ex -> return $ Left ex
+ Right Nothing -> return $ Left $ SqlError "" NonfatalError "No print with the given code was present" "" ""
+ Right (Just p) ->
+ try
+ $ runBeam conn
+ $ runSelectReturningList
+ $ select
+ $ filter_ (\u -> _useCodiceStampa u ==. val_ (pk p))
+ $ allElementsOfTable _usi
+
+-- |Complete a print
+completePrint :: Int -> Day -> Double -> Scientific -> Scientific -> (Connection -> IO (Either SqlError ()))
+completePrint pCode deliveryDate workTime total materials =
+ \conn -> do
+ completeResult <-
+ try
+ $ runBeam conn
+ $ runUpdate
+ $ update (_stampe fabLabDB)
+ ( \s ->
+ mconcat
+ [ _printDataConsegna s <-. val_ (Just deliveryDate),
+ _printCostoMateriali s <-. val_ (Just materials),
+ _printCostoTotale s <-. val_ (Just total),
+ _printTempo s <-. val_ (Just workTime)
+ ]
+ )
+ (\s -> _printCodiceStampa s ==. val_ pCode)
+ case completeResult of
+ Left ex -> return $ Left ex
+ Right () -> do
+ maybePrint <- selectPrintFromCode pCode conn
+ case maybePrint of
+ Left ex -> return $ Left ex
+ Right Nothing -> return $ Left $ SqlError "" NonfatalError "Inconsistent state: a print existed, and then no more!" "" ""
+ Right (Just print) ->
+ let (PersonId cf) = _printCfRichiedente print
+ in try
+ $ runBeam conn
+ $ runUpdate
+ $ update (_persone fabLabDB)
+ (\p -> _personSpesaTotale p <-. (current_ (_personSpesaTotale p) + (val_ total)))
+ (\p -> _personCf p ==. val_ cf)
+
+-- cuts queries
+-- |Select the cut with the given code in the database (should be 0 or 1)
+selectCutFromCode :: Int -> (Connection -> IO (Either SqlError (Maybe Cut)))
+selectCutFromCode cCode =
+ genericSelectOne _intagli (\c -> _cutCodiceIntaglio c ==. val_ cCode)
+
+-- |Select all cuts in the database
+selectAllCuts :: Connection -> IO (Either SqlError [Cut])
+selectAllCuts = genericSelectList _intagli Nothing
+
+-- |Select all the print that aren't completed in the database
+selectAllIncompleteCuts :: Connection -> IO (Either SqlError [Cut])
+selectAllIncompleteCuts =
+ genericSelectList _intagli $ Just (\c -> _cutDataConsegna c ==. val_ Nothing)
+
+-- |Select all the completed prints in the database
+selectAllCompleteCuts :: Connection -> IO (Either SqlError [Cut])
+selectAllCompleteCuts =
+ genericSelectList _intagli $ Just (\c -> _cutDataConsegna c /=. val_ Nothing)
+
+-- |Add a new cut to the database
+insertCut :: String -> Day -> String -> (Connection -> IO (Either SqlError ()))
+insertCut cf insertDate descr =
+ \conn -> do
+ selectedPeople <- selectPersonFromCF cf conn
+ case selectedPeople of
+ Left ex -> return $ Left ex
+ Right Nothing -> return $ Left $ SqlError "" NonfatalError "No person with the given code was present" "" ""
+ Right (Just person) ->
+ try $ runBeam conn
+ $ runInsert
+ $ insert (_intagli fabLabDB)
+ $ insertExpressions
+ [ Cut
+ { _cutCodiceIntaglio = default_,
+ _cutDataRichiesta = val_ insertDate,
+ _cutDataConsegna = val_ Nothing,
+ _cutTempo = val_ Nothing,
+ _cutCostoMateriali = val_ Nothing,
+ _cutCostoTotale = val_ Nothing,
+ _cutDescrizione = val_ (pack descr),
+ _cutCfRichiedente = val_ (pk person),
+ _cutCfIncaricato = nothing_
+ }
+ ]
+
+-- |Assign a cut to an operator
+assignCut :: Int -> String -> (Connection -> IO (Either SqlError ()))
+assignCut code cf =
+ \conn -> do
+ selectedOperators <- (selectPersonFromCF cf) conn
+ case selectedOperators of
+ Left ex -> return $ Left ex
+ Right Nothing -> return $ Left $ SqlError "" NonfatalError "No person with the given code was present" "" ""
+ Right (Just operator) ->
+ try $ runBeam conn
+ $ runUpdate
+ $ update (_intagli fabLabDB)
+ (\c -> _cutCfIncaricato c <-. just_ (val_ (pk operator)))
+ (\c -> _cutCodiceIntaglio c ==. val_ code)
+
+-- |Assign a processing to a print
+assignProcessing :: Int -> String -> (Connection -> IO (Either SqlError ()))
+assignProcessing cCode pCode =
+ \conn -> do
+ selectedProcessings <- (selectProcessingFromCode pCode) conn
+ selectedCuts <- (selectCutFromCode cCode) conn
+ case (selectedProcessings, selectedCuts) of
+ (Left ex, Left ex') -> return $ Left $ (error $ (toString $ sqlErrorMsg ex) ++ (toString $ sqlErrorMsg ex'))
+ (Left ex, _) -> return $ Left ex
+ (_, Left ex) -> return $ Left ex
+ (Right Nothing, _) -> return $ Left $ SqlError "" NonfatalError "No processing with the given code was present" "" ""
+ (_, Right Nothing) -> return $ Left $ SqlError "" NonfatalError "No cut with the given code was present" "" ""
+ (Right (Just processing), Right (Just cut)) -> do
+ try $ runBeam conn
+ $ runInsert
+ $ insert (_composizioni fabLabDB)
+ $ insertExpressions
+ [ Composition
+ { _compositionCodiceLavorazione = val_ (pk processing),
+ _compositionCodiceIntaglio = val_ (pk cut)
+ }
+ ]
+
+-- |Selects the filaments assigned to the given cut
+assignmentsByCut :: Int -> (Connection -> IO (Either SqlError [Composition]))
+assignmentsByCut cCode =
+ \conn -> do
+ selectedCut <- selectCutFromCode cCode conn
+ case selectedCut of
+ Left ex -> return $ Left ex
+ Right Nothing -> return $ Left $ SqlError "" NonfatalError "No cut with the given code was present" "" ""
+ Right (Just cut) -> try $ runBeam conn
+ $ runSelectReturningList
+ $ select
+ $ filter_ (\c -> _compositionCodiceIntaglio c ==. val_ (pk cut))
+ $ allElementsOfTable _composizioni
+
+-- |Complete a cut
+completeCut :: Int -> Day -> Double -> Scientific -> Scientific -> (Connection -> IO (Either SqlError ()))
+completeCut code deliveryDate workTime total materials =
+ \conn -> do
+ completeResult <-
+ try
+ $ runBeam conn
+ $ runUpdate
+ $ update (_intagli fabLabDB)
+ ( \s ->
+ mconcat
+ [ _cutDataConsegna s <-. val_ (Just deliveryDate),
+ _cutCostoMateriali s <-. val_ (Just materials),
+ _cutCostoTotale s <-. val_ (Just total),
+ _cutTempo s <-. val_ (Just workTime)
+ ]
+ )
+ (\s -> _cutCodiceIntaglio s ==. val_ code)
+ case completeResult of
+ Left ex -> return $ Left ex
+ Right () -> do
+ maybeCut <- selectCutFromCode code conn
+ case maybeCut of
+ Left ex -> return $ Left ex
+ Right Nothing -> return $ Left $ SqlError "" NonfatalError "Inconsistent state: a cut existed, and then no more!" "" ""
+ Right (Just cut) ->
+ let (PersonId cf) = _cutCfRichiedente cut
+ in try
+ $ runBeam conn
+ $ runUpdate
+ $ update (_persone fabLabDB)
+ (\p -> _personSpesaTotale p <-. (current_ (_personSpesaTotale p) + (val_ total)))
+ (\p -> _personCf p ==. val_ cf)
diff --git a/src/Schema.hs b/src/Schema.hs
new file mode 100644
index 0000000..222288d
--- /dev/null
+++ b/src/Schema.hs
@@ -0,0 +1,440 @@
+{-# LANGUAGE DeriveAnyClass #-}
+{-# LANGUAGE DeriveGeneric #-}
+{-# LANGUAGE FlexibleContexts #-}
+{-# LANGUAGE FlexibleInstances #-}
+{-# LANGUAGE GADTs #-}
+{-# LANGUAGE MultiParamTypeClasses #-}
+{-# LANGUAGE OverloadedStrings #-}
+{-# LANGUAGE StandaloneDeriving #-}
+{-# LANGUAGE TypeApplications #-}
+{-# LANGUAGE TypeFamilies #-}
+{-# LANGUAGE TypeSynonymInstances #-}
+
+-- |Module used for defining the database schema
+module Schema where
+
+import Data.Int (Int)
+import Data.Scientific
+import Data.Text
+import Data.Time.Calendar
+import Database.Beam
+
+-- datatypes
+-- |Data representing a person in the database
+data PersonT f
+ = Person
+ { _personCf :: Columnar f Text,
+ _personNome :: Columnar f Text,
+ _personCognome :: Columnar f Text,
+ _personSocio :: Columnar f Bool,
+ _personOperatoreIntagliatrice :: Columnar f Bool,
+ _personOperatoreStampante :: Columnar f Bool,
+ _personSpesaTotale :: Columnar f Scientific
+ }
+ deriving (Beamable, Generic)
+
+instance Table PersonT where
+
+ data PrimaryKey PersonT f = PersonId (Columnar f Text) deriving (Beamable, Generic)
+
+ primaryKey = PersonId . _personCf
+
+type Person = PersonT Identity
+
+type PersonId = PrimaryKey PersonT Identity
+
+deriving instance Eq Person
+
+deriving instance Show Person
+
+deriving instance Eq PersonId
+
+deriving instance Show PersonId
+
+deriving instance Eq (PrimaryKey PersonT (Nullable Identity))
+
+deriving instance Show (PrimaryKey PersonT (Nullable Identity))
+
+-- |Data representing a print
+data PrintT f
+ = Print
+ { _printCodiceStampa :: Columnar f Int,
+ _printDataRichiesta :: Columnar f Day,
+ _printDataConsegna :: Columnar f (Maybe Day),
+ _printTempo :: Columnar f (Maybe Double),
+ _printCostoMateriali :: Columnar f (Maybe Scientific),
+ _printCostoTotale :: Columnar f (Maybe Scientific),
+ _printDescrizione :: Columnar f Text,
+ _printCfRichiedente :: PrimaryKey PersonT f,
+ _printCfIncaricato :: PrimaryKey PersonT (Nullable f),
+ _printCodiceStampante :: PrimaryKey PrinterT (Nullable f)
+ }
+ deriving (Beamable, Generic)
+
+instance Table PrintT where
+
+ data PrimaryKey PrintT f = PrintId (Columnar f Int) deriving (Beamable, Generic)
+
+ primaryKey = PrintId . _printCodiceStampa
+
+type Print = PrintT Identity
+
+type PrintId = PrimaryKey PrintT Identity
+
+deriving instance Eq Print
+
+deriving instance Show Print
+
+deriving instance Eq PrintId
+
+deriving instance Show PrintId
+
+-- |Data representing a cut
+data CutT f
+ = Cut
+ { _cutCodiceIntaglio :: Columnar f Int,
+ _cutDataRichiesta :: Columnar f Day,
+ _cutDataConsegna :: Columnar f (Maybe Day),
+ _cutTempo :: Columnar f (Maybe Double),
+ _cutCostoMateriali :: Columnar f (Maybe Scientific),
+ _cutCostoTotale :: Columnar f (Maybe Scientific),
+ _cutDescrizione :: Columnar f Text,
+ _cutCfRichiedente :: PrimaryKey PersonT f,
+ _cutCfIncaricato :: PrimaryKey PersonT (Nullable f)
+ }
+ deriving (Beamable, Generic)
+
+instance Table CutT where
+
+ data PrimaryKey CutT f = CutId (Columnar f Int) deriving (Beamable, Generic)
+
+ primaryKey = CutId . _cutCodiceIntaglio
+
+type Cut = CutT Identity
+
+type CutId = PrimaryKey CutT Identity
+
+deriving instance Eq Cut
+
+deriving instance Show Cut
+
+deriving instance Eq CutId
+
+deriving instance Show CutId
+
+-- |Data representing a printer
+data PrinterT f
+ = Printer
+ { _printerCodiceStampante :: Columnar f Text,
+ _printerMarca :: Columnar f Text,
+ _printerModello :: Columnar f Text,
+ _printerDescrizione :: Columnar f Text
+ }
+ deriving (Beamable, Generic)
+
+instance Table PrinterT where
+
+ data PrimaryKey PrinterT f = PrinterId (Columnar f Text) deriving (Beamable, Generic)
+
+ primaryKey = PrinterId . _printerCodiceStampante
+
+type Printer = PrinterT Identity
+
+type PrinterId = PrimaryKey PrinterT Identity
+
+deriving instance Eq Printer
+
+deriving instance Show Printer
+
+deriving instance Eq PrinterId
+
+deriving instance Show PrinterId
+
+deriving instance Eq (PrimaryKey PrinterT (Nullable Identity))
+
+deriving instance Show (PrimaryKey PrinterT (Nullable Identity))
+
+-- |Data representing a type of plastic
+data PlasticT f
+ = Plastic
+ { _plasticCodicePlastica :: Columnar f Text,
+ _plasticNome :: Columnar f Text,
+ _plasticDescrizione :: Columnar f Text
+ }
+ deriving (Beamable, Generic)
+
+instance Table PlasticT where
+
+ data PrimaryKey PlasticT f = PlasticId (Columnar f Text) deriving (Beamable, Generic)
+
+ primaryKey = PlasticId . _plasticCodicePlastica
+
+type Plastic = PlasticT Identity
+
+type PlasticId = PrimaryKey PlasticT Identity
+
+deriving instance Eq Plastic
+
+deriving instance Show Plastic
+
+deriving instance Eq PlasticId
+
+deriving instance Show PlasticId
+
+-- |Data representing a filament
+data FilamentT f
+ = Filament
+ { _filamentCodiceFilamento :: Columnar f Text,
+ _filamentCodicePlastica :: PrimaryKey PlasticT f,
+ _filamentMarca :: Columnar f Text,
+ _filamentColore :: Columnar f Text
+ }
+ deriving (Beamable, Generic)
+
+instance Table FilamentT where
+
+ data PrimaryKey FilamentT f = FilamentId (Columnar f Text) deriving (Beamable, Generic)
+
+ primaryKey = FilamentId . _filamentCodiceFilamento
+
+type Filament = FilamentT Identity
+
+type FilamentId = PrimaryKey FilamentT Identity
+
+deriving instance Eq Filament
+
+deriving instance Show Filament
+
+deriving instance Eq FilamentId
+
+deriving instance Show FilamentId
+
+-- |Data representing a way of executing a cut
+data ProcessingT f
+ = Processing
+ { _processingCodiceTipo :: PrimaryKey TypeT f,
+ _processingCodiceLavorazione :: Columnar f Text,
+ _processingCodiceMateriale :: PrimaryKey MaterialT f,
+ _processingPotenzaMassima :: Columnar f Int,
+ _processingPotenzaMinima :: Columnar f Int,
+ _processingVelocita :: Columnar f Int,
+ _processingDescrizione :: Columnar f Text
+ }
+ deriving (Beamable, Generic)
+
+instance Table ProcessingT where
+
+ data PrimaryKey ProcessingT f = ProcessingId (Columnar f Text) deriving (Beamable, Generic)
+
+ primaryKey = ProcessingId . _processingCodiceLavorazione
+
+type Processing = ProcessingT Identity
+
+type ProcessingId = PrimaryKey ProcessingT Identity
+
+deriving instance Eq Processing
+
+deriving instance Show Processing
+
+deriving instance Eq ProcessingId
+
+deriving instance Show ProcessingId
+
+-- |Data representing a type of processing
+data TypeT f
+ = Type
+ { _typeCodiceTipo :: Columnar f Text,
+ _typeNome :: Columnar f Text,
+ _typeDescrizione :: Columnar f Text
+ }
+ deriving (Beamable, Generic)
+
+instance Table TypeT where
+
+ data PrimaryKey TypeT f = TypeId (Columnar f Text) deriving (Beamable, Generic)
+
+ primaryKey = TypeId . _typeCodiceTipo
+
+type Type = TypeT Identity
+
+type TypeId = PrimaryKey TypeT Identity
+
+deriving instance Eq Type
+
+deriving instance Show Type
+
+deriving instance Eq TypeId
+
+deriving instance Show TypeId
+
+-- |Data representing a material
+data MaterialT f
+ = Material
+ { _materialCodiceClasse :: PrimaryKey MaterialsClassT f,
+ _materialCodiceMateriale :: Columnar f Text,
+ _materialNome :: Columnar f Text,
+ _materialSpessore :: Columnar f Double,
+ _materialDescrizione :: Columnar f Text
+ }
+ deriving (Beamable, Generic)
+
+instance Table MaterialT where
+
+ data PrimaryKey MaterialT f = MaterialId (Columnar f Text) deriving (Beamable, Generic)
+
+ primaryKey = MaterialId . _materialCodiceMateriale
+
+type Material = MaterialT Identity
+
+type MaterialId = PrimaryKey MaterialT Identity
+
+deriving instance Eq Material
+
+deriving instance Show Material
+
+deriving instance Eq MaterialId
+
+deriving instance Show MaterialId
+
+-- |Data representing a class of materials
+data MaterialsClassT f
+ = MaterialsClass
+ { _materialsclassCodiceClasse :: Columnar f Text,
+ _materialsclassNome :: Columnar f Text
+ }
+ deriving (Beamable, Generic)
+
+instance Table MaterialsClassT where
+
+ data PrimaryKey MaterialsClassT f = MaterialsClassId (Columnar f Text) deriving (Beamable, Generic)
+
+ primaryKey = MaterialsClassId . _materialsclassCodiceClasse
+
+type MaterialsClass = MaterialsClassT Identity
+
+type MaterialsClassId = PrimaryKey MaterialsClassT Identity
+
+deriving instance Eq MaterialsClass
+
+deriving instance Show MaterialsClass
+
+deriving instance Eq MaterialsClassId
+
+deriving instance Show MaterialsClassId
+
+-- |Data representing a processing used in a cut
+data CompositionT f
+ = Composition
+ { _compositionCodiceLavorazione :: PrimaryKey ProcessingT f,
+ _compositionCodiceIntaglio :: PrimaryKey CutT f
+ }
+ deriving (Beamable, Generic)
+
+instance Table CompositionT where
+
+ data PrimaryKey CompositionT f = CompositionId (PrimaryKey ProcessingT f) (PrimaryKey CutT f) deriving (Beamable, Generic)
+
+ primaryKey = CompositionId <$> _compositionCodiceLavorazione <*> _compositionCodiceIntaglio
+
+type Composition = CompositionT Identity
+
+type CompositionId = PrimaryKey CompositionT Identity
+
+deriving instance Eq Composition
+
+deriving instance Show Composition
+
+deriving instance Eq CompositionId
+
+deriving instance Show CompositionId
+
+-- |Data representing a filament used in a print
+data UseT f
+ = Use
+ { _useCodiceFilamento :: PrimaryKey FilamentT f,
+ _useCodiceStampa :: PrimaryKey PrintT f
+ }
+ deriving (Beamable, Generic)
+
+instance Table UseT where
+
+ data PrimaryKey UseT f = UseId (PrimaryKey FilamentT f) (PrimaryKey PrintT f) deriving (Beamable, Generic)
+
+ primaryKey = UseId <$> _useCodiceFilamento <*> _useCodiceStampa
+
+type Use = UseT Identity
+
+type UseId = PrimaryKey UseT Identity
+
+deriving instance Eq Use
+
+deriving instance Show Use
+
+deriving instance Eq UseId
+
+deriving instance Show UseId
+
+-- |Data representing the database
+data FabLabDB f
+ = FabLabDB
+ { _persone :: f (TableEntity PersonT),
+ _stampe :: f (TableEntity PrintT),
+ _intagli :: f (TableEntity CutT),
+ _stampanti :: f (TableEntity PrinterT),
+ _plastiche :: f (TableEntity PlasticT),
+ _filamenti :: f (TableEntity FilamentT),
+ _lavorazioni :: f (TableEntity ProcessingT),
+ _tipi :: f (TableEntity TypeT),
+ _materiali :: f (TableEntity MaterialT),
+ _classi_di_materiali :: f (TableEntity MaterialsClassT),
+ _composizioni :: f (TableEntity CompositionT),
+ _usi :: f (TableEntity UseT)
+ }
+ deriving (Database be, Generic)
+
+fabLabDB :: DatabaseSettings be FabLabDB
+fabLabDB =
+ withDbModification defaultDbSettings
+ dbModification
+ { _stampe =
+ modifyTableFields
+ tableModification
+ { _printCfRichiedente = PersonId (fieldNamed "cf_richiedente"),
+ _printCfIncaricato = PersonId (fieldNamed "cf_incaricato"),
+ _printCodiceStampante = PrinterId (fieldNamed "codice_stampante")
+ },
+ _intagli =
+ modifyTableFields
+ tableModification
+ { _cutCfRichiedente = PersonId (fieldNamed "cf_richiedente"),
+ _cutCfIncaricato = PersonId (fieldNamed "cf_incaricato")
+ },
+ _materiali =
+ modifyTableFields
+ tableModification
+ { _materialCodiceClasse = MaterialsClassId (fieldNamed "codice_classe")
+ },
+ _filamenti =
+ modifyTableFields
+ tableModification
+ { _filamentCodicePlastica = PlasticId (fieldNamed "codice_plastica")
+ },
+ _lavorazioni =
+ modifyTableFields
+ tableModification
+ { _processingCodiceTipo = TypeId (fieldNamed "codice_tipo"),
+ _processingCodiceMateriale = MaterialId (fieldNamed "codice_materiale")
+ },
+ _composizioni =
+ modifyTableFields
+ tableModification
+ { _compositionCodiceIntaglio = CutId (fieldNamed "codice_intaglio"),
+ _compositionCodiceLavorazione = ProcessingId (fieldNamed "codice_lavorazione")
+ },
+ _usi =
+ modifyTableFields
+ tableModification
+ { _useCodiceFilamento = FilamentId (fieldNamed "codice_filamento"),
+ _useCodiceStampa = PrintId (fieldNamed "codice_stampa")
+ }
+ }
diff --git a/src/Server.hs b/src/Server.hs
new file mode 100644
index 0000000..5cd2f16
--- /dev/null
+++ b/src/Server.hs
@@ -0,0 +1,676 @@
+{-# LANGUAGE DataKinds #-}
+{-# LANGUAGE DeriveAnyClass #-}
+{-# LANGUAGE FlexibleContexts #-}
+{-# LANGUAGE FlexibleInstances #-}
+{-# LANGUAGE GADTs #-}
+{-# LANGUAGE OverloadedStrings #-}
+{-# LANGUAGE StandaloneDeriving #-}
+{-# LANGUAGE TypeFamilies #-}
+{-# LANGUAGE TypeOperators #-}
+{-# LANGUAGE TypeSynonymInstances #-}
+
+-- |Module used for the connectivity part of the application
+module Server where
+
+import Control.Exception
+import Data.Aeson hiding (json)
+import qualified Data.Configurator as C
+import Data.HVect
+import Data.List as L (groupBy)
+import Data.Maybe
+import Data.Scientific
+import Data.Text
+import Data.Text.Encoding
+import Data.Time
+import Database.Beam
+import Database.Beam.Postgres (Connection, SqlError, sqlErrorMsg)
+import GHC.Generics
+import Network.Wai.Middleware.Static
+import Query as Q
+import Schema
+import Users as U
+import Web.Spock
+import Web.Spock.Config
+
+-- datatypes
+data IsGuest = IsGuest
+
+-- |Represents the information necessary to start the application
+data ApiCfg
+ = ApiCfg
+ { acfg_db :: Text,
+ acfg_db_location :: Text,
+ acfg_db_port :: Integer,
+ acfg_db_user :: Text,
+ acfg_port :: Int,
+ acfg_name :: Text
+ }
+
+-- app :: SpockM conn sess st ()
+-- SpockM conn sess st = SpockCtxM () conn sess st
+-- SpockCtxM ctx conn sess st = SpockCtxT ctx (WebStateM conn sess st)
+-- json :: (ToJSON a, MonadIO m) => a -> ActionT m ()
+type SessionVal = Maybe SessionID
+
+type Api ctx = SpockCtxM ctx Connection SessionVal () ()
+
+type ApiAction ctx a = SpockActionCtx ctx Connection SessionVal () a
+
+-- utility functions and constants
+getClientFilePath :: String -> FilePath
+getClientFilePath fileName = "client/" ++ fileName
+
+getFileName :: String -> String
+getFileName partialUri = Prelude.last $ L.groupBy (\x y -> y /= '/') partialUri
+
+-- |Parses the configuration file
+parseConfig :: FilePath -> IO ApiCfg
+parseConfig cfgFile = do
+ cfg <- C.load [C.Required cfgFile]
+ db <- C.require cfg "db"
+ dbLocation <- C.require cfg "dbLocation"
+ dbPort <- C.require cfg "dbPort"
+ dbUser <- C.require cfg "dbUser"
+ port <- C.require cfg "port"
+ name <- C.require cfg "apiName"
+ return (ApiCfg db dbLocation dbPort dbUser port name)
+
+-- |Function used to get the connection used to interrogate the database
+getFabLabConnection
+ :: String -- ^ Host name
+ -> Integer -- ^ Port
+ -> String -- ^ Username
+ -> String -- ^ Password
+ -> String -- ^ Name of the database
+ -> IO Connection
+getFabLabConnection = Q.connectWithInfo
+
+-- |Function used to get the PoolOrConn necessary to interrogate the database
+getPoolOrConn :: IO Connection -> PoolOrConn Connection
+getPoolOrConn conn =
+ PCConn
+ $ ConnBuilder
+ conn
+ (Q.closeConnection)
+ (PoolCfg 1 12 1)
+
+-- |Sends a json message with the given code and description
+messageJson :: MonadIO m => Int -> Text -> ActionCtxT ctx m b
+messageJson code message =
+ json
+ $ object
+ [ "response" .= object ["code" .= code, "message" .= message]
+ ]
+
+-- |Basic authentication level
+baseHook :: Monad m => ActionCtxT () m (HVect '[])
+baseHook = return HNil
+
+-- |Authorized user authentication level
+authHook :: ActionCtxT (HVect ts1) (WebStateM Connection SessionVal st) (HVect (User : ts1))
+authHook = do
+ oldCtx <- getContext
+ sess <- readSession
+ mUser <- getUserFromSession
+ case mUser of
+ Nothing -> redirect "no_auth" -- messageJson 401 "Admin non autorizzato"
+ Just val -> return (val :&: oldCtx)
+
+-- |Admin authorization level
+adminHook :: ActionCtxT (HVect ts1) (WebStateM Connection SessionVal st) (HVect (User : ts1))
+adminHook = do
+ oldCtx <- getContext
+ sess <- readSession
+ mUser <- getUserFromSession
+ case mUser of
+ Nothing -> redirect ""
+ Just user ->
+ case _userAdmin user of
+ True -> return (user :&: oldCtx)
+ False -> redirect "no_auth" --messageJson 401 "Admin non autorizzato"
+
+-- |Function to get the user of the current session
+getUserFromSession :: ActionCtxT ctx (WebStateM Connection SessionVal st) (Maybe User)
+getUserFromSession =
+ do
+ sessId <- readSession
+ case sessId of
+ Nothing -> return Nothing
+ Just sId -> do
+ queryResult <- runQuery $ selectSessionFromId $ sId
+ case queryResult of
+ Left ex -> return Nothing
+ Right Nothing -> return Nothing
+ Right (Just session) ->
+ let (UserId id) = _sessionUtente session
+ in do
+ queryResult' <- runQuery $ selectUserFromUsername $ unpack id
+ case queryResult' of
+ Left ex -> return Nothing
+ Right a -> return a
+
+-- |Functions used to log in a user
+loginAction :: Text -> Text -> ApiAction ctx ()
+loginAction user pswd = do
+ queryResult <- runQuery $ checkUser (unpack user) (unpack pswd)
+ case queryResult of
+ Left ex -> messageJson 500 "Problema durante l'autenticazione"
+ Right WrongUsername -> messageJson 401 "Username errata"
+ Right WrongPassword -> messageJson 401 "Password errata"
+ Right AllOk -> do
+ time <- liftIO getCurrentTime
+ insertResult <- runQuery $ insertSession (unpack user) time
+ case insertResult of
+ Right () -> do
+ mSession <- runQuery $ selectMostRecentSession $ unpack user
+ case mSession of
+ Left ex -> messageJson 500 $ decodeUtf8 $ sqlErrorMsg ex
+ Right Nothing -> messageJson 666 "Spero seriamente che questo testo non sarà mai mostrato"
+ Right (Just session) ->
+ let sid = _sessionIdSessione session
+ in do
+ writeSession (Just sid)
+ redirect "app"
+ Left ex -> messageJson 500 $ decodeUtf8 $ sqlErrorMsg ex
+
+-- |Executes a query that returns a list and sends the result as a json message
+executeQueryListAndSendResult
+ :: (HasSpock (ActionCtxT ctx m), ToJSON a, MonadIO m)
+ => (SpockConn (ActionCtxT ctx m) -> IO (Either SqlError [a]))
+ -> ActionCtxT ctx m b
+executeQueryListAndSendResult query = do
+ queryResult <- runQuery query
+ case queryResult of
+ Left ex -> messageJson 500 $ decodeUtf8 $ sqlErrorMsg ex
+ Right result -> json result
+
+-- |Executes a query to insert or modify some data and sends the result as a json message
+executeModifyQueryAndSendResult
+ :: (HasSpock (ActionCtxT ctx m), MonadIO m)
+ => (SpockConn (ActionCtxT ctx m) -> IO (Either SqlError ()))
+ -> ActionCtxT ctx m b
+executeModifyQueryAndSendResult query = do
+ queryResult <- runQuery query
+ case queryResult of
+ Left ex -> messageJson 500 $ decodeUtf8 $ sqlErrorMsg ex
+ Right () -> messageJson 200 "Operazione riuscita"
+
+-- |Checks if a list of Maybe contains all Just values or not
+testParameters :: [Maybe a] -> Bool
+testParameters = (Prelude.all id) . (fmap isJust)
+
+-- |Converts a String to a Bool, with "true", "True" and "TRUE" corresponding to True
+toBool :: String -> Bool
+toBool = flip elem ["true", "True", "TRUE"]
+
+-- |Sends a message signalling a missing parameter
+missingParameter :: MonadIO m => ActionCtxT ctx m b
+missingParameter = messageJson 422 "Parametro mancante"
+
+-- server functions
+-- |Runs the server using the given configuration and the password for the database
+runServer :: ApiCfg -> String -> IO ()
+runServer cfg pswd =
+ let ioConn =
+ getFabLabConnection (unpack $ acfg_db_location cfg)
+ (acfg_db_port cfg)
+ (unpack $ acfg_db_user cfg)
+ pswd
+ (unpack $ acfg_db cfg)
+ in do
+ spockCfg <- defaultSpockCfg Nothing (getPoolOrConn ioConn) ()
+ runSpock 8080 (spock spockCfg app)
+
+app :: Api ()
+app = do
+ middleware $ staticPolicy $ addBase "static"
+ prehook baseHook $ do
+ -- routes for unauthenticated users
+ get root
+ $ file "text/html"
+ $ getClientFilePath "login.html"
+ get "no_auth"
+ $ file "text/html"
+ $ getClientFilePath "no_auth.html"
+ post "login" $ do
+ maybeUser <- param "username"
+ maybePswd <- param "password"
+ if testParameters [maybeUser, maybePswd]
+ then
+ loginAction
+ (fromJust maybeUser)
+ (fromJust maybePswd)
+ else missingParameter
+ get "login.js"
+ $ file "application/javascript"
+ $ getClientFilePath "login.js"
+ get ("login.css")
+ $ file "text/css"
+ $ getClientFilePath "login.css"
+ prehook authHook $ do
+ -- routes for authenticated users
+ get "app"
+ $ file "text/html"
+ $ getClientFilePath "index.html"
+ post "insert_person" $ do
+ maybeCf <- param "cf"
+ maybeName <- param "name"
+ maybeSurname <- param "surname"
+ if testParameters [maybeCf, maybeName, maybeSurname]
+ then
+ executeModifyQueryAndSendResult
+ $ insertPerson
+ (fromJust maybeCf)
+ (fromJust maybeName)
+ (fromJust maybeSurname)
+ else missingParameter
+ post "modify_person" $ do
+ maybeCf <- param "cf"
+ maybePartner <- param "partner"
+ maybeCutter <- param "cutter"
+ maybePrinter <- param "printer"
+ if testParameters [maybeCf, maybePartner, maybeCutter, maybePrinter]
+ then
+ executeModifyQueryAndSendResult
+ $ modifyPerson
+ (fromJust maybeCf)
+ (toBool $ fromJust maybePartner)
+ (toBool $ fromJust maybeCutter)
+ (toBool $ fromJust maybePrinter)
+ else missingParameter
+ post "insert_class" $ do
+ maybeCode <- param "code"
+ maybeName <- param "name"
+ if testParameters [maybeCode, maybeName]
+ then
+ executeModifyQueryAndSendResult
+ $ insertMaterialsClass
+ (fromJust maybeCode)
+ (fromJust maybeName)
+ else missingParameter
+ post "insert_material" $ do
+ maybeCode <- param "code"
+ maybeClass <- param "class"
+ maybeName <- param "name"
+ maybeWidth <- param "width"
+ maybeDescr <- param "description"
+ if testParameters [maybeCode, maybeClass, maybeName, maybeWidth, maybeDescr]
+ then
+ executeModifyQueryAndSendResult
+ $ insertMaterial
+ (fromJust maybeCode)
+ (fromJust maybeClass)
+ (fromJust maybeName)
+ (read $ fromJust maybeWidth :: Double)
+ (fromJust maybeDescr)
+ else missingParameter
+ post "select_materials" $ do
+ maybeCCode <- param "class_code"
+ case maybeCCode of
+ Nothing -> missingParameter
+ Just cCode -> executeQueryListAndSendResult $ selectMaterialsByClass cCode
+ post "insert_processing" $ do
+ maybeTypeCode <- param "type"
+ maybeMaterialCode <- param "material"
+ maybeMaxP <- param "max_potency"
+ maybeMinP <- param "min_potency"
+ maybeSpeed <- param "speed"
+ maybeDescr <- param "description"
+ if testParameters [maybeTypeCode, maybeMaterialCode, maybeMaxP, maybeMinP, maybeSpeed, maybeDescr]
+ then
+ executeModifyQueryAndSendResult
+ $ insertProcessing
+ (fromJust maybeTypeCode)
+ (fromJust maybeMaterialCode)
+ (read $ fromJust maybeMaxP :: Int)
+ (read $ fromJust maybeMinP :: Int)
+ (read $ fromJust maybeSpeed :: Int)
+ (fromJust maybeDescr)
+ else missingParameter
+ post "select_processings_by_material" $ do
+ maybeMaterialCode <- param "material"
+ case maybeMaterialCode of
+ Nothing -> missingParameter
+ Just code -> executeQueryListAndSendResult $ selectProcessingsByMaterials code
+ post "assign_cut_processing" $ do
+ maybeCut <- param "cut"
+ maybeProcessing <- param "processing"
+ if testParameters [maybeCut, maybeProcessing]
+ then
+ executeModifyQueryAndSendResult
+ $ assignProcessing
+ (read $ fromJust maybeCut :: Int)
+ (fromJust maybeProcessing)
+ else missingParameter
+ post "assignments_cut" $ do
+ maybeCut <- param "cut"
+ case maybeCut of
+ Nothing -> missingParameter
+ Just cut -> executeQueryListAndSendResult $ assignmentsByCut (read $ cut :: Int)
+ post "insert_plastic" $ do
+ maybeCode <- param "code"
+ maybeName <- param "name"
+ maybeDescr <- param "description"
+ if testParameters [maybeCode, maybeName, maybeDescr]
+ then
+ executeModifyQueryAndSendResult
+ $ insertPlastic
+ (fromJust maybeCode)
+ (fromJust maybeName)
+ (fromJust maybeDescr)
+ else missingParameter
+ post "insert_filament" $ do
+ maybeCode <- param "code"
+ maybePlastic <- param "plastic"
+ maybeBrand <- param "brand"
+ maybeColor <- param "color"
+ if testParameters [maybeCode, maybePlastic, maybeBrand, maybeColor]
+ then
+ executeModifyQueryAndSendResult
+ $ insertFilament
+ (fromJust maybeCode)
+ (fromJust maybePlastic)
+ (fromJust maybeBrand)
+ (fromJust maybeColor)
+ else missingParameter
+ post "select_filaments" $ do
+ maybePCode <- param "plastic_code"
+ case maybePCode of
+ Nothing -> missingParameter
+ Just code -> executeQueryListAndSendResult $ selectFilamentsByPlastic code
+ post "insert_print" $ do
+ maybeCf <- param "client"
+ maybeDate <- param "date"
+ maybeDescr <- param "descr"
+ if testParameters [maybeCf, maybeDate, maybeDescr]
+ then
+ executeModifyQueryAndSendResult
+ $ insertPrint
+ (fromJust maybeCf)
+ (read $ fromJust maybeDate :: Day)
+ (fromJust maybeDescr)
+ else missingParameter
+ post "assign_print_operator" $ do
+ maybeCf <- param "operator"
+ maybeCode <- param "print"
+ if testParameters [maybeCode, maybeCf]
+ then
+ executeModifyQueryAndSendResult
+ $ assignPrint
+ (read $ fromJust maybeCode :: Int)
+ (fromJust maybeCf)
+ else missingParameter
+ post "assign_print_printer" $ do
+ maybeCodePrinter <- param "printer"
+ maybeCode <- param "print"
+ if testParameters [maybeCode, maybeCodePrinter]
+ then
+ executeModifyQueryAndSendResult
+ $ assignPrinter
+ (fromJust maybeCodePrinter)
+ (read $ fromJust maybeCode :: Int)
+ else missingParameter
+ post "assign_print_filament" $ do
+ maybePrint <- param "print"
+ maybeFilament <- param "filament"
+ if testParameters [maybePrint, maybeFilament]
+ then
+ executeModifyQueryAndSendResult
+ $ assignFilament
+ (read $ fromJust maybePrint :: Int)
+ (fromJust maybeFilament)
+ else missingParameter
+ post "assignments_print" $ do
+ maybePrint <- param "print"
+ case maybePrint of
+ Nothing -> missingParameter
+ Just p -> executeQueryListAndSendResult $ assignmentsByPrint (read $ p :: Int)
+ post "modify_print" $ do
+ maybePrint <- param "print"
+ maybeDate <- param "date"
+ maybeTime <- param "time"
+ maybeTotal <- param "total"
+ maybeMaterials <- param "materials"
+ if testParameters [maybePrint, maybeTime, maybeTotal, maybeMaterials, maybeDate]
+ then
+ executeModifyQueryAndSendResult
+ $ completePrint
+ (read $ fromJust maybePrint :: Int)
+ (read $ fromJust maybeDate :: Day)
+ (read $ fromJust maybeTime :: Double)
+ (read $ fromJust maybeTotal :: Scientific)
+ (read $ fromJust maybeMaterials :: Scientific)
+ else missingParameter
+ post "insert_cut" $ do
+ maybeCf <- param "client"
+ maybeDate <- param "date"
+ maybeDescr <- param "descr"
+ if testParameters [maybeCf, maybeDate, maybeDescr]
+ then
+ executeModifyQueryAndSendResult
+ $ insertCut
+ (fromJust maybeCf)
+ (read $ fromJust maybeDate :: Day)
+ (fromJust maybeDescr)
+ else missingParameter
+ post "assign_cut_operator" $ do
+ maybeCf <- param "operator"
+ maybeCode <- param "cut"
+ if testParameters [maybeCode, maybeCf]
+ then
+ executeModifyQueryAndSendResult
+ $ assignCut
+ (read $ fromJust maybeCode :: Int)
+ (fromJust maybeCf)
+ else missingParameter
+ post "modify_cut" $ do
+ maybeCut <- param "cut"
+ maybeDate <- param "date"
+ maybeTime <- param "time"
+ maybeTotal <- param "total"
+ maybeMaterials <- param "materials"
+ if testParameters [maybeCut, maybeTime, maybeTotal, maybeMaterials, maybeDate]
+ then
+ executeModifyQueryAndSendResult
+ $ completeCut
+ (read $ fromJust maybeCut :: Int)
+ (read $ fromJust maybeDate :: Day)
+ (read $ fromJust maybeTime :: Double)
+ (read $ fromJust maybeTotal :: Scientific)
+ (read $ fromJust maybeMaterials :: Scientific)
+ else missingParameter
+ get "people" $ do
+ executeQueryListAndSendResult selectAllPeople
+ get "partners" $ do
+ executeQueryListAndSendResult selectAllPartners
+ get "cutterOperators" $ do
+ executeQueryListAndSendResult selectAllLaserCutterOperators
+ get "printerOperators" $ do
+ executeQueryListAndSendResult selectAllPrinterOperators
+ get "materials" $ do
+ executeQueryListAndSendResult selectAllMaterials
+ get "materials_classes" $ do
+ executeQueryListAndSendResult selectAllMaterialsClasses
+ get "processings" $ do
+ executeQueryListAndSendResult selectAllProcessings
+ get "types" $ do
+ executeQueryListAndSendResult selectAllTypes
+ get "filaments" $ do
+ executeQueryListAndSendResult selectAllFilaments
+ get "plastics" $ do
+ executeQueryListAndSendResult selectAllPlastics
+ get "printers" $ do
+ executeQueryListAndSendResult selectAllPrinters
+ get "prints" $ do
+ executeQueryListAndSendResult selectAllPrints
+ get "incomplete_prints" $ do
+ executeQueryListAndSendResult selectAllIncompletePrints
+ get "complete_prints" $ do
+ executeQueryListAndSendResult selectAllCompletePrints
+ get "cuts" $ do
+ executeQueryListAndSendResult selectAllCuts
+ get "incomplete_cuts" $ do
+ executeQueryListAndSendResult selectAllIncompleteCuts
+ get "complete_cuts" $ do
+ executeQueryListAndSendResult selectAllCompleteCuts
+ get "index.js"
+ $ file "application/javascript"
+ $ getClientFilePath "index.js"
+ get "index.css"
+ $ file "text/css"
+ $ getClientFilePath "index.css"
+ get "common.js"
+ $ file "application/javascript"
+ $ getClientFilePath "common.js"
+ prehook adminHook $ do
+ -- routes for authenticated admins
+ get "manager"
+ $ file "text/html"
+ $ getClientFilePath "manager.html"
+ post "insert_type" $ do
+ maybeCode <- param "code"
+ maybeName <- param "name"
+ maybeDescr <- param "description"
+ if testParameters [maybeCode, maybeName, maybeDescr]
+ then
+ executeModifyQueryAndSendResult
+ $ insertType
+ (fromJust maybeCode)
+ (fromJust maybeName)
+ (fromJust maybeDescr)
+ else missingParameter
+ post "insert_printer" $ do
+ maybeCode <- param "code"
+ maybeBrand <- param "brand"
+ maybeModel <- param "model"
+ maybeDescr <- param "description"
+ if testParameters [maybeCode, maybeBrand, maybeModel, maybeDescr]
+ then
+ executeModifyQueryAndSendResult
+ $ insertPrinter
+ (fromJust maybeCode)
+ (fromJust maybeBrand)
+ (fromJust maybeModel)
+ (fromJust maybeDescr)
+ else missingParameter
+ post "insert_user" $ do
+ maybeUser <- param "username"
+ maybePswd <- param "password"
+ if testParameters [maybeUser, maybePswd]
+ then
+ executeModifyQueryAndSendResult
+ $ insertUser
+ (fromJust maybeUser)
+ (fromJust maybePswd)
+ else messageJson 401 "Parametro mancante"
+ get "manager.js"
+ $ file "application/javascript"
+ $ getClientFilePath "manager.js"
+ get "manager.css"
+ $ file "text/css"
+ $ getClientFilePath "manager.css"
+
+-- orphan istances (argh) because they are not necessary for the db part of the application, only for the server one
+deriving instance FromJSON Person
+
+deriving instance ToJSON Person
+
+deriving instance FromJSON PersonId
+
+deriving instance ToJSON PersonId
+
+deriving instance FromJSON (PrimaryKey PersonT (Nullable Identity))
+
+deriving instance ToJSON (PrimaryKey PersonT (Nullable Identity))
+
+deriving instance FromJSON Print
+
+deriving instance ToJSON Print
+
+deriving instance FromJSON PrintId
+
+deriving instance ToJSON PrintId
+
+deriving instance FromJSON Cut
+
+deriving instance ToJSON Cut
+
+deriving instance FromJSON CutId
+
+deriving instance ToJSON CutId
+
+deriving instance FromJSON Printer
+
+deriving instance ToJSON Printer
+
+deriving instance FromJSON PrinterId
+
+deriving instance ToJSON PrinterId
+
+deriving instance FromJSON (PrimaryKey PrinterT (Nullable Identity))
+
+deriving instance ToJSON (PrimaryKey PrinterT (Nullable Identity))
+
+deriving instance FromJSON Plastic
+
+deriving instance ToJSON Plastic
+
+deriving instance FromJSON PlasticId
+
+deriving instance ToJSON PlasticId
+
+deriving instance FromJSON Filament
+
+deriving instance ToJSON Filament
+
+deriving instance FromJSON FilamentId
+
+deriving instance ToJSON FilamentId
+
+deriving instance FromJSON Processing
+
+deriving instance ToJSON Processing
+
+deriving instance FromJSON ProcessingId
+
+deriving instance ToJSON ProcessingId
+
+deriving instance FromJSON Type
+
+deriving instance ToJSON Type
+
+deriving instance FromJSON TypeId
+
+deriving instance ToJSON TypeId
+
+deriving instance FromJSON Material
+
+deriving instance ToJSON Material
+
+deriving instance FromJSON MaterialId
+
+deriving instance ToJSON MaterialId
+
+deriving instance FromJSON MaterialsClass
+
+deriving instance ToJSON MaterialsClass
+
+deriving instance FromJSON MaterialsClassId
+
+deriving instance ToJSON MaterialsClassId
+
+deriving instance FromJSON Composition
+
+deriving instance ToJSON Composition
+
+deriving instance FromJSON CompositionId
+
+deriving instance ToJSON CompositionId
+
+deriving instance FromJSON Use
+
+deriving instance ToJSON Use
+
+deriving instance FromJSON UseId
+
+deriving instance ToJSON UseId
diff --git a/src/Users.hs b/src/Users.hs
new file mode 100644
index 0000000..2717614
--- /dev/null
+++ b/src/Users.hs
@@ -0,0 +1,249 @@
+{-# LANGUAGE DeriveAnyClass #-}
+{-# LANGUAGE DeriveGeneric #-}
+{-# LANGUAGE FlexibleContexts #-}
+{-# LANGUAGE FlexibleInstances #-}
+{-# LANGUAGE GADTs #-}
+{-# LANGUAGE MultiParamTypeClasses #-}
+{-# LANGUAGE OverloadedStrings #-}
+{-# LANGUAGE PartialTypeSignatures #-}
+{-# LANGUAGE StandaloneDeriving #-}
+{-# LANGUAGE TypeApplications #-}
+{-# LANGUAGE TypeFamilies #-}
+{-# LANGUAGE TypeSynonymInstances #-}
+
+-- |Module used for managing users of the application
+module Users where
+
+import Control.Exception
+import Crypto.KDF.BCrypt (hashPassword, validatePassword)
+import qualified Data.ByteString as BS
+import Data.ByteString.UTF8 as BSU
+import Data.Int (Int)
+import Data.Text as T
+import Data.Text.Encoding
+import Data.Time
+import Database.Beam
+import Database.Beam.Backend.SQL (BeamSqlBackend)
+import Database.Beam.Postgres
+import Database.PostgreSQL.LibPQ (ExecStatus (NonfatalError))
+
+-- datatypes
+-- |A session id
+type SessionID = Int
+
+-- |The result of a user login
+data CheckUserResult
+ = AllOk
+ | WrongUsername
+ | WrongPassword
+ deriving (Eq, Show)
+
+-- |The result of an admin login
+data CheckAdminResult
+ = AdminOk
+ | WrongLogin
+ | NotAnAdmin
+ deriving (Eq, Show)
+
+-- |Data representing an user
+data UserT f
+ = User
+ { _userUsername :: Columnar f Text,
+ _userHash :: Columnar f BS.ByteString,
+ _userAdmin :: Columnar f Bool
+ }
+ deriving (Beamable, Generic)
+
+instance Table UserT where
+
+ data PrimaryKey UserT f = UserId (Columnar f Text) deriving (Beamable, Generic)
+
+ primaryKey = UserId . _userUsername
+
+type User = UserT Identity
+
+type UserId = PrimaryKey UserT Identity
+
+deriving instance Eq User
+
+deriving instance Show User
+
+deriving instance Eq UserId
+
+deriving instance Show UserId
+
+-- |Data representing a session
+data SessionT f
+ = Session
+ { _sessionIdSessione :: Columnar f SessionID,
+ _sessionOraCreazione :: Columnar f UTCTime,
+ _sessionUtente :: PrimaryKey UserT f
+ }
+ deriving (Beamable, Generic)
+
+instance Table SessionT where
+
+ data PrimaryKey SessionT f = SessionId (Columnar f SessionID) deriving (Beamable, Generic)
+
+ primaryKey = SessionId . _sessionIdSessione
+
+type Session = SessionT Identity
+
+type SessionId = PrimaryKey SessionT Identity
+
+deriving instance Eq Session
+
+deriving instance Show Session
+
+deriving instance Eq SessionId
+
+deriving instance Show SessionId
+
+-- |Data representing the database
+data UserDB f
+ = UserDB
+ { _utenti :: f (TableEntity UserT),
+ _sessioni :: f (TableEntity SessionT)
+ }
+ deriving (Database be, Generic)
+
+userDb :: DatabaseSettings be UserDB
+userDb =
+ withDbModification defaultDbSettings
+ dbModification
+ { _sessioni =
+ modifyTableFields
+ tableModification
+ { _sessionUtente = UserId (fieldNamed "utente")
+ }
+ }
+
+-- functions
+-- |Connects to the database with the given characteristics
+connectWithInfo
+ :: String -- ^ name of the host
+ -> Integer -- ^ port
+ -> String -- ^ username
+ -> String -- ^ password
+ -> String -- ^ name of the database
+ -> IO Connection
+connectWithInfo host port user pswd db = connect $ ConnectInfo host (fromInteger port) user pswd db
+
+-- |Given a connection, close it
+closeConnection :: Connection -> IO ()
+closeConnection = close
+
+runBeam :: Connection -> Pg a -> IO a
+runBeam = runBeamPostgres --Debug putStrLn -- change for debug or production purposes
+
+-- users functions
+-- | Insert a new user into the database
+insertUser :: String -> String -> (Connection -> IO (Either SqlError ()))
+insertUser name pswd =
+ \conn -> do
+ hash <- hashPassword 12 $ BSU.fromString pswd
+ try
+ $ runBeam conn
+ $ runInsert
+ $ insert (_utenti userDb)
+ $ insertValues
+ [ User
+ (T.pack name)
+ hash
+ False
+ ]
+
+-- |Select the users with the given username
+selectUserFromUsername :: String -> (Connection -> IO (Either SqlError (Maybe User)))
+selectUserFromUsername name =
+ \conn ->
+ try
+ $ runBeam conn
+ $ runSelectReturningOne
+ $ select
+ $ filter_ (\n -> _userUsername n ==. (val_ (T.pack name)))
+ $ all_ (_utenti userDb)
+
+-- |Checks if a user is in the database, with the correct password
+checkUser :: String -> String -> (Connection -> IO (Either SqlError CheckUserResult))
+checkUser user pswd =
+ \conn -> do
+ mUser <- selectUserFromUsername user conn
+ case mUser of
+ Left ex -> return $ Left ex
+ Right Nothing -> return $ Right WrongUsername
+ Right (Just user) -> return $ Right $ if validatePassword (BSU.fromString pswd) (_userHash user) then AllOk else WrongPassword
+
+-- |Checks if a user is an admin in the database, with the correct password
+checkAdmin :: String -> String -> (Connection -> IO (Either SqlError CheckAdminResult))
+checkAdmin user pswd =
+ \conn -> do
+ checkResult <- checkUser user pswd conn
+ case checkResult of
+ Left ex -> return $ Left ex
+ Right AllOk -> do
+ mUser <- selectUserFromUsername user conn
+ case mUser of
+ Left ex -> return $ Left ex
+ Right Nothing -> return $ Right WrongLogin
+ Right (Just user) -> return $ Right $ if _userAdmin user then AdminOk else NotAnAdmin
+ _ -> return $ Right $ WrongLogin
+
+-- sessions functions
+-- | Insert a new user into the database
+insertSession :: String -> UTCTime -> (Connection -> IO (Either SqlError ()))
+insertSession name time =
+ \conn -> do
+ selectedUsers <- selectUserFromUsername name conn
+ case selectedUsers of
+ Left ex -> return $ Left ex
+ Right Nothing -> return $ Left $ SqlError "" NonfatalError "No user with the given username was present" "" ""
+ Right (Just user) ->
+ try
+ $ runBeam conn
+ $ runInsert
+ $ insert (_sessioni userDb)
+ $ insertExpressions
+ [ Session
+ { _sessionIdSessione = default_,
+ _sessionOraCreazione = val_ time,
+ _sessionUtente = val_ $ pk user
+ }
+ ]
+
+-- |Select the users with the given username
+selectSessionFromId :: SessionID -> (Connection -> IO (Either SqlError (Maybe Session)))
+selectSessionFromId sId =
+ \conn ->
+ try
+ $ runBeam conn
+ $ runSelectReturningOne
+ $ select
+ $ filter_ (\s -> _sessionIdSessione s ==. (val_ $ sId))
+ $ all_
+ $ _sessioni userDb
+
+-- |Select most recent session for a given user
+selectMostRecentSession :: String -> (Connection -> IO (Either SqlError (Maybe Session)))
+selectMostRecentSession user =
+ \conn -> do
+ mUser <- selectUserFromUsername user conn
+ case mUser of
+ Left ex -> return $ Left ex
+ Right Nothing -> return $ Left $ SqlError "" NonfatalError "No user with the given username was present" "" ""
+ Right (Just user) ->
+ try
+ $ runBeam conn
+ $ runSelectReturningOne
+ $ select
+ $ limit_ 1
+ $ orderBy_ (desc_ . _sessionOraCreazione)
+ $ filter_ (\s -> _sessionUtente s ==. (val_ $ pk user))
+ $ all_
+ $ _sessioni userDb
+
+-- |Check if the session is still valid
+checkSessionValidity :: Session -> IO Bool
+checkSessionValidity session = do
+ time <- getCurrentTime
+ return $ diffUTCTime time (_sessionOraCreazione session) < (3600 * 2)
diff --git a/stack.yaml b/stack.yaml
index f2879ca..2fbaddb 100644
--- a/stack.yaml
+++ b/stack.yaml
@@ -43,6 +43,8 @@ extra-deps:
- Spock-0.13.0.0
- Spock-core-0.13.0.0
- reroute-0.5.0.0
+- stm-containers-0.2.16
+- focus-0.1.5.2
# Override default flag values for local packages and extra-deps
# flags: {}