diff options
| author | Raymaekers Luca <luca@spacehb.net> | 2025-11-19 13:09:51 +0100 |
|---|---|---|
| committer | Raymaekers Luca <luca@spacehb.net> | 2025-11-20 14:21:11 +0100 |
| commit | 9d12460247d080164b176a2c03ba0b39de41cecc (patch) | |
| tree | c84bee046b2c523e16624780788a9f58c6e24c74 | |
| parent | 5f9f9c024b46cd0d9fb24a1a916d7ccadbce1371 (diff) | |
Added synchronization across different devices; .value for buttons
checkpoint
Added login with pin code
checkpoint
checkpoint
| -rw-r--r-- | code/index.tmpl.html | 162 | ||||
| -rw-r--r-- | code/noelan.go | 178 |
2 files changed, 261 insertions, 79 deletions
diff --git a/code/index.tmpl.html b/code/index.tmpl.html index f60ecf6..4c392bb 100644 --- a/code/index.tmpl.html +++ b/code/index.tmpl.html @@ -11,6 +11,8 @@ The `global_all_data` object has all the data necessary for the application to w TODO(luca): Get rid of PicoCSS, because it does not make a good responsive UI. +TODO(luca): Check error messages maybe sending too much information. (e.g., "invalid person" can be bruteforced to find the person's names) + --> {{ end }} @@ -74,6 +76,27 @@ textarea { } } +div#pin-login { + margin: 2em 5em 0em 5em; + display: flex; + justify-content: center; + align-items: center; +} +div#pin-login > input { + display: flex; + flex: 5; +} +button#pin-login-button { + display: flex; + flex: 1; +} +div.pin-text-container { + display: flex; + align-items: center; +} +div.pin-text-container > span { +} + </style> <script> "use strict"; @@ -113,7 +136,21 @@ function findPersonByName(picking_person_name) { //- Pages function setPageChoose() { + let pinLoginNameID = "pin-login-name"; + let pinLoginCodeID = "pin-login-code"; + let pinLoginButtonID = "pin-login-button"; + document.body.innerHTML = ` + <div id="pin-login"> + <form> + <fieldset role="group"> + <input id="${pinLoginNameID}" type="text" placeholder="Nom"> + <input id="${pinLoginCodeID}" type="text" placeholder="Code"> + <input id="${pinLoginButtonID}" type="submit" value="connecter"> + </fieldset> + </form> + </div> + <div class="centered"> <div class="container"> <h1>Qui es-tu?</h1> @@ -191,6 +228,51 @@ function setPageChoose() { }); // button click even listener } // has_picked } // for loop + + let login_button = document.getElementById(pinLoginButtonID); + login_button.addEventListener("click", function(event) { + event.preventDefault(); + + let name = document.getElementById(pinLoginNameID).value; + let code = document.getElementById(pinLoginCodeID).value; + console.log("name:", name); + console.log("code:", code); + + let messageBody = { + name: name, + code: code + }; + const postBody = new URLSearchParams(messageBody).toString(); + + fetch('/api/pin/', { + method: 'POST', + headers: + { + 'Content-Type': 'application/x-www-form-urlencoded' + }, + body: postBody, + }) + .then(function(response) { + if (!response.ok) { + console.error('Network response was not ok'); + } + return response.json(); + }) + .then(function(response) { + global_all_data.token = response.Token; + global_all_data.thisName = name; + global_all_data.thisWishlist = response.ThisWishlist; + global_all_data.otherName = response.OtherName; + global_all_data.otherWishlist = response.OtherWishlist; + global_all_data.initialized = true; + + localStorage.setItem(local_storage_key, JSON.stringify(global_all_data)); + + setPageThisPerson(); + }) + .catch(function(error) { console.error(error); }); + }); + } function setPageOtherPerson() { @@ -218,7 +300,7 @@ function setPageOtherPerson() { }); let wishlist = document.getElementById(wishlistID); - wishlist.textContent = global_all_data.otherWishlist; + wishlist.value = global_all_data.otherWishlist; fetch(`/api/list/?user=${global_all_data.thisName}&name=${global_all_data.otherName}&token=${global_all_data.token}`) .then(function(response) { @@ -228,10 +310,10 @@ function setPageOtherPerson() { return response.text(); }) .then(function(response) { - if(wishlist.textContent != response) + if(wishlist.value != response) { global_all_data.otherWishlist = response; - wishlist.textContent = response; + wishlist.value = response; localStorage.setItem(local_storage_key, JSON.stringify(global_all_data)); } }) @@ -243,7 +325,9 @@ function setPageOtherPerson() { function setPageThisPerson() { let showPersonButtonID = "show-person"; - let writeLetterButtonID = "write-letter"; + let wishlistButtonID = "write-wishlist"; + let getPinButtonID = "get-pin"; + let pinTextID = "pin-text"; document.body.innerHTML = ` <div class="centered"> @@ -251,7 +335,11 @@ function setPageThisPerson() { <h1>Noël-An de <span class="name">${global_all_data.thisName}</span></h1> <div class="buttons"> <button id="${showPersonButtonID}">Voir qui j'ai choisi</button> - <button id="${writeLetterButtonID}">Ma liste des souhaits</button> + <button id="${wishlistButtonID}">Ma liste des souhaits</button> + <button id="${getPinButtonID}">Code PIN</button> + <div class="pin-text-container"> + <span id="${pinTextID}"></span> + </div> </div> <h4><b>Infos:</b></h4> <ul> @@ -264,6 +352,26 @@ function setPageThisPerson() { </div> `; + let getPinButton = document.getElementById(getPinButtonID); + getPinButton.addEventListener("click", function(event) { + event.preventDefault(); + + fetch(`/api/pin/?name=${global_all_data.thisName}&token=${global_all_data.token}`) + .then(function(response) { + if (!response.ok) { + console.error('Network response was not ok'); + } + return response.text(); + }) + .then(function(text_response) { + let pinText = document.getElementById(pinTextID); + pinText.innerText = text_response; + }) + .catch(function(error) { + console.error('There was a problem with the fetch operation:', error); + }); + }); + let showPersonButton = document.getElementById(showPersonButtonID); showPersonButton.addEventListener("click", function(event) { event.preventDefault(); @@ -271,23 +379,23 @@ function setPageThisPerson() { setPageOtherPerson(); }); - let writeLetterButton = document.getElementById(writeLetterButtonID); - writeLetterButton.addEventListener("click", function(event) { + let writeWishlistButton = document.getElementById(wishlistButtonID); + writeWishlistButton.addEventListener("click", async function(event) { event.preventDefault(); history.pushState({home: "/"}, "", "/"); - let sendLetterButtonID = "send-letter"; - let letterTextAreaID = "letter-text"; + let sendWishlistButtonID = "send-wishlist"; + let wishlistTextAreaID = "wishlist-text"; let backButtonID = "back"; document.body.innerHTML = ` <div class="container"> <h1><span class="name">Ma</span> liste des souhaits</h1> - <textarea id="${letterTextAreaID}" rows="15" cols="40" placeholder="Vide..."></textarea> + <textarea id="${wishlistTextAreaID}" rows="15" cols="40" placeholder="Vide..."></textarea> <div class="buttons"> <button id="${backButtonID}">retour</button> - <button id="${sendLetterButtonID}">sauvegarder</button> + <button id="${sendWishlistButtonID}">sauvegarder</button> </div> </div> `; @@ -299,10 +407,10 @@ function setPageThisPerson() { setPageThisPerson(); }); - let letterTextArea = document.getElementById(letterTextAreaID); - letterTextArea.textContent = global_all_data.thisWishlist; + let wishlist = document.getElementById(wishlistTextAreaID); + wishlist.value = global_all_data.thisWishlist; - fetch(`/api/list/?user=${global_all_data.thisName}&name=${global_all_data.thisName}&token=${global_all_data.token}`) + await fetch(`/api/list/?user=${global_all_data.thisName}&name=${global_all_data.thisName}&token=${global_all_data.token}`) .then(function(response) { if (!response.ok) { console.error('Network response was not ok'); @@ -310,10 +418,18 @@ function setPageThisPerson() { return response.text(); }) .then(function(response) { - if(wishlist.textContent != response) + {{ if false }} + console.log("response:", response); + console.log(typeof(response)); + console.log("wishlist.value:", wishlist.value); + console.log(typeof(wishlist.value)); + console.log("wishlist.value === response:", wishlist.value === response); + {{ end }} + + if(wishlist.value !== response) { global_all_data.thisWishlist = response; - letterTextArea.textContent = response; + wishlist.value = response; localStorage.setItem(local_storage_key, JSON.stringify(global_all_data)); } }) @@ -321,20 +437,22 @@ function setPageThisPerson() { console.error('There was a problem with the fetch operation:', error); }); - - let sendLetterButton = document.getElementById(sendLetterButtonID); - sendLetterButton.addEventListener("click", function(event) { + let sendWishlistButton = document.getElementById(sendWishlistButtonID); + sendWishlistButton.addEventListener("click", function(event) { event.preventDefault(); + {{ if false }} console.log("Sending list of", global_all_data.thisName); + console.log("content:", wishlist.value); + {{ end }} - global_all_data.thisWishlist = letterTextArea.value + global_all_data.thisWishlist = wishlist.value; localStorage.setItem(local_storage_key, JSON.stringify(global_all_data)); let messageBody = { name: global_all_data.thisName, token: global_all_data.token, - text: letterTextArea.value, + text: wishlist.value, }; const postBody = new URLSearchParams(messageBody).toString(); @@ -362,7 +480,7 @@ function setPageThisPerson() { .catch(function(error) { console.error('Error:', error); }); }); // add event listener send button - }); // add event listener write letter + }); // add event listener write wishlist } //- Main diff --git a/code/noelan.go b/code/noelan.go index 2e01b81..1e168a2 100644 --- a/code/noelan.go +++ b/code/noelan.go @@ -34,6 +34,7 @@ // - detect when you are offline // - display a tooltip saying that you are offline // TODO(luca): Remove names from here and add them through a config file +// TODO(luca): Stupid / for scrapers & robots.txt package noelan @@ -45,6 +46,7 @@ import ( "flag" "fmt" "html/template" + "math" "math/rand" "net/http" "os/signal" @@ -65,6 +67,9 @@ type Person struct { Wishlist string HasPicked bool Token int64 + + Code string + CodeIsValid bool } // - Globals @@ -76,6 +81,15 @@ const GlobalDataFileName = "people.gob" const GlobalDataDirectoryName = "gobs" const GlobalPerson int = 2 +var GlobalMessages = map[string]string{ + "NotImplemented": "Not implemented yet", + "InvalidPersonAndToken": "Invalid person and token combination", + "NoSuchPerson": "No such person", + "PersonAlreadyPicked": "Person already picked", + "InvalidPerson": "Invalid person", + "InvalidCode": "Invalid code", +} + var GlobalNilPerson = Person{} // - Serializing @@ -182,21 +196,24 @@ func TemplateToString(template_contents string, people []Person, local_storage_k } } - if has_picked_count < len(people) { - err = template_response.ExecuteTemplate(&buf, "tirage", page_data) - if err != nil { - fmt.Println(err) - } - response = buf.String() + if has_picked_count >= len(people) { + page_data.People = []Person{} + } + + err = template_response.ExecuteTemplate(&buf, "tirage", page_data) + if err != nil { + fmt.Println(err) } } + response = buf.String() + return response } func FindPersonByName(people []Person, name string) (bool, *Person) { - var found_person *Person - var found bool + found_person := &GlobalNilPerson + found := false for index, value := range people { if name == value.Name { @@ -209,6 +226,20 @@ func FindPersonByName(people []Person, name string) (bool, *Person) { return found, found_person } +func FindPersonByNameAndValidate(people []Person, name string, token string) (bool, *Person) { + found, person := FindPersonByName(people, name) + + if found { + tokenString := strconv.FormatInt(person.Token, 10) + if token != tokenString { + found = false + person = &GlobalNilPerson + } + } + + return found, person +} + func ShufflePeople(rand *rand.Rand, people []Person, logger *log.Logger) { // Get a shuffled list that has following constaints // 1. One person cannot choose itself @@ -235,27 +266,22 @@ func ShufflePeople(rand *rand.Rand, people []Person, logger *log.Logger) { } func HttpError(logger *log.Logger, message string, person *Person, writer http.ResponseWriter, request *http.Request) { - logger.Printf("ERROR: for %s: %s | %s %s %s %s\n", - person.Name, message, - request.RemoteAddr, request.Method, request.URL, request.Form) - http.Error(writer, "ERROR: " + message, http.StatusNotFound) + if person == &GlobalNilPerson { + logger.Printf("ERROR: %s | %s %s %s %s\n", + message, + request.RemoteAddr, request.Method, request.URL, request.Form) + } else { + logger.Printf("ERROR: for %s: %s | %s %s %s %s\n", + person.Name, message, + request.RemoteAddr, request.Method, request.URL, request.Form) + } + + http.Error(writer, "ERROR: "+message, http.StatusNotFound) } // - Main func Run() { - var people = []Person{ - {Name: "Nawel"}, - {Name: "Tobias"}, - {Name: "Luca"}, - {Name: "Lola"}, - {Name: "Aeris"}, - {Name: "Lionel"}, - {Name: "Aurélie"}, - {Name: "Sean"}, - {Name: "Émilie"}, - {Name: "Yves"}, - {Name: "Marthe"}, - } + var people = GlobalDefaultPeople var local_storage_key int64 var people_initialized = false var local_storage_key_initialized = false @@ -463,21 +489,16 @@ func Run() { text := request.FormValue("text") token := request.FormValue("token") - logger.Println("Edit wishlist of", name) - found, person := FindPersonByName(people, name) + found, person := FindPersonByNameAndValidate(people, name, token) if found { - tokenString := strconv.FormatInt(person.Token, 10) - if token == tokenString { - person.Wishlist = text - person.HasPicked = true + logger.Println("Edit wishlist of", name) + fmt.Printf("text: %#v\n", text) + person.Wishlist = text - fmt.Fprintln(writer, "ok") - } else { - HttpError(logger, "invalid token", person, writer, request) - } + fmt.Fprintln(writer, "ok") } else { - HttpError(logger, "no such person", &GlobalNilPerson, writer, request) + HttpError(logger, GlobalMessages["InvalidPersonAndToken"], person, writer, request) } } else if request.Method == http.MethodGet { // @api_notes @@ -491,28 +512,18 @@ func Run() { name := params.Get("name") token := params.Get("token") - found, person := FindPersonByName(people, user) - + found, person := FindPersonByNameAndValidate(people, user, token) if found { - tokenString := strconv.FormatInt(person.Token, 10) - - if token == tokenString { - - var response string - if people[person.Other].Name == name { - response = people[person.Other].Wishlist - fmt.Fprint(writer, response) - } else if person.Name == user { - response = person.Wishlist - fmt.Fprint(writer, response) - } else { - HttpError(logger, "invalid person: "+name, person, writer, request) - } + if people[person.Other].Name == name { + fmt.Fprint(writer, people[person.Other].Wishlist) + } else if person.Name == user { + fmt.Fprint(writer, person.Wishlist) } else { - HttpError(logger, "invalid token", person, writer, request) + HttpError(logger, GlobalMessages["InvalidPerson"]+": "+name, person, writer, request) } + } else { - HttpError(logger, "no such person", &GlobalNilPerson, writer, request) + HttpError(logger, GlobalMessages["InvalidPersonAndToken"], person, writer, request) } } }) @@ -537,13 +548,67 @@ func Run() { json.NewEncoder(writer).Encode(response) } else { - HttpError(logger, "person already picked", person, writer, request) + HttpError(logger, GlobalMessages["PersonAlreadyPicked"], person, writer, request) } } else { - HttpError(logger, "no such person", &GlobalNilPerson, writer, request) + HttpError(logger, GlobalMessages["NoSuchPerson"], person, writer, request) } }) + http.HandleFunc("/api/pin/", func(writer http.ResponseWriter, request *http.Request) { + if request.Method == http.MethodGet { + + params := request.URL.Query() + name := params.Get("name") + token := params.Get("token") + found, person := FindPersonByNameAndValidate(people, name, token) + if found { + logger.Println("Request pin for", person.Name) + + t := seeded_rand.Float64() + // Linear interpolate code to get value between 0 - 999999 and then convert to string with padded zeroes + person.Code = fmt.Sprintf("%06d", int(math.Round((t+0)*(999999)))) + person.CodeIsValid = true + + fmt.Fprint(writer, person.Code) + } else { + HttpError(logger, GlobalMessages["InvalidPersonAndToken"], person, writer, request) + } + + } else if request.Method == http.MethodPost { + + name := request.FormValue("name") + code := request.FormValue("code") + + found, person := FindPersonByName(people, name) + if found { + + if person.CodeIsValid && code == person.Code { + logger.Println("Pin login for", person.Name) + + type Response struct { + Token int64 + ThisWishlist string + OtherName string + OtherWishlist string + } + + other := people[person.Other] + response := Response{Token: person.Token, ThisWishlist: person.Wishlist, OtherName: other.Name, OtherWishlist: other.Wishlist} + json.NewEncoder(writer).Encode(response) + + person.CodeIsValid = false + } else { + person.CodeIsValid = false + HttpError(logger, GlobalMessages["InvalidCode"], person, writer, request) + } + } else { + HttpError(logger, GlobalMessages["InvalidPerson"], person, writer, request) + } + } + + }) + // Execute the template before-hand since the contents won't change. response := TemplateToString(GlobalPageHTML, people, local_storage_key, internal) http.HandleFunc("/", func(writer http.ResponseWriter, request *http.Request) { @@ -570,7 +635,6 @@ func Run() { if err := http.ListenAndServe(address, nil); err != nil { panic(err) } - } if did_work { |
