summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitignore1
-rw-r--r--index.tmpl.html294
-rw-r--r--main.go301
3 files changed, 468 insertions, 128 deletions
diff --git a/.gitignore b/.gitignore
index a9a5aec..0ca623a 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1 +1,2 @@
tmp
+people.gob
diff --git a/index.tmpl.html b/index.tmpl.html
index 43e51c4..25b7186 100644
--- a/index.tmpl.html
+++ b/index.tmpl.html
@@ -1,5 +1,5 @@
<!DOCTYPE html>
- <html lang="fr">
+ <html lang="fr" data-theme="dark">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
@@ -18,16 +18,15 @@ h1 {
}
button {
- font-size: 1em;
+ font-size: 1rem;
}
-#buttons {
- margin: 1.5em;
+.buttons {
display: flex;
justify-content: center;
align-items: center;
flex-wrap: wrap;
- gap: 1em;
+ gap: 1rem;
}
body {
@@ -37,106 +36,255 @@ body {
height: 50vh;
}
-span#name {
- color: blue;
+span.name {
+ color: #87bfcf;
}
+textarea {
+ display: block !important;
+}
+
+.fade {
+ animation: fadeOut 1s forwards; /* Duration and direction of the animation */
+}
+
+@keyframes fadeOut {
+ 0% {
+ opacity: 1;
+ }
+ 100% {
+ opacity: 0;
+ }
+}
</style>
<script>
let people =
[{{ range .People }}
- {"name": "{{.Name}}", "other": {{.Other}}}, {{ end }}
+ {"name": "{{.Name}}", "other":{{.Other}}, "has_picked": {{.HasPicked}},}, {{ end }}
];
-let page1_html = `
+let local_storage_key = "{{.KeyID}}";
+
+function setPageFirst() {
+ document.body.innerHTML = `
<div>
<h1>Qui es-tu?</h1>
- <div id="buttons">
- {{ range .People }}
- <button>{{ .Name }}</button>
- {{ end }}
+ <div class="buttons">
</div>
-</div>
-`;
+</div>`;
-let local_storage_key = "clicked_{{.Seed}}";
-
-function setPage1() {
- document.body.innerHTML = page1_html;
- let buttons = document.querySelectorAll("button");
- for(let index = 0; index < buttons.length; index += 1)
+ let buttons = document.querySelector("div.buttons");
+ for(let index = 0; index < people.length; index += 1)
{
- let button = buttons[index];
- button.addEventListener("click", function(event) {
- event.preventDefault();
-
- let name = button.innerText;
- let person = null;
- for(let person_index = 0; person_index < people.length; person_index += 1)
- {
- if(people[person_index].name === name)
+ let person = people[index];
+ if(!person.has_picked)
+ {
+ let button = document.createElement('button');
+ button.textContent = people[index].name;
+ buttons.appendChild(button);
+ button.addEventListener("click", function(event) {
+ event.preventDefault();
+
+ let name = person.name;
+ let thisPerson = null;
+ for(let person_index = 0; person_index < people.length; person_index += 1)
{
- person = people[person_index];
- break;
+ if(people[person_index].name === name)
+ {
+ thisPerson = people[person_index];
+ break;
+ }
}
- }
- {{ if false }}
- fetch('/person/', {
- method: 'POST',
- headers:
- {
- 'Content-Type': 'application/x-www-form-urlencoded'
- },
- body: "name=" + person.name
- })
- .then(function(response) {
- response.text().
- then(function(text_response) {
- if(text_response !== 'ok') {
- console.log("Error");
+ document.body.innerHTML = `
+ <h1>Tu es bien ${thisPerson.name}?</h1>
+ <div class="buttons">
+ <button id="yes">Oui</button>
+ <button id="no">Non</button>
+ </div>`;
+
+ let yes_button = document.getElementById("yes");
+ let no_button = document.getElementById("no");
+
+ yes_button.addEventListener("click", function(event) {
+ event.preventDefault();
+ localStorage.setItem(local_storage_key, thisPerson.name);
+ setPagePickingperson(thisPerson);
+ });
+
+ no_button.addEventListener("click", function(event) {
+ event.preventDefault();
+ setPageFirst();
+ });
+
+ }); // button click even listener
+ } // has_picked
+ } // for loop
+
+}
+
+function findPersonByName(picking_person_name) {
+ let result = null;
+
+ for(let peopleIndex = 0; peopleIndex < people.length; peopleIndex += 1)
+ {
+ if(people[peopleIndex].name === picking_person_name)
+ {
+ result = people[peopleIndex];
+ break;
+ }
+ }
+
+ return result;
+}
+
+function setPagePerson(person) {
+ let wishlistID = "wishlist";
+ document.body.innerHTML = `
+ <div>
+ <h1>Tu as&nbsp;<span class="name">${person}</span>.</h1>
+ <h4>Sa liste des souhaits:</h4>
+ <textarea disabled readonly rows="5" cols="40" id="${wishlistID}" placeholder="vide..."></textarea>
+ </div>
+ `;
+ let list = null;
+ fetch(`/list/?name=${person}`)
+ .then(response => {
+ if (!response.ok) {
+ console.error('Network response was not ok');
}
- })
- })
- .catch(error => console.error('Error:', error));
- {{ end }}
+ return response.text();
+ })
+ .then(data => {
+ let wishlist = document.getElementById(wishlistID);
+ wishlist.value = data;
+ })
+ .catch(error => {
+ console.error('There was a problem with the fetch operation:', error);
+ });
+}
- document.body.innerHTML = `
- <h1>Tu es bien ${person.name}?</h1>
- <div id="buttons">
- <button id="yes">Oui</button>
- <button id="no">Non</button>
- </div>`;
+function setPagePickingperson(thisPerson) {
+ let showPersonButtonID = "show-person";
+ let writeLetterButtonID = "write-letter";
- let yes_button = document.getElementById("yes");
- let no_button = document.getElementById("no");
+ document.body.innerHTML = `
+ <main class="container">
+ <h1>Noël-An de &nbsp;<span class="name">${thisPerson.name}</span></h1>
+ <div class="buttons">
+ <button id="${showPersonButtonID}">Voir qui j'ai choisi</button>
+ <button id="${writeLetterButtonID}">Écrire ma liste des souhaits</button>
+ </div>
+ <h4><b>Infos:</b></h4>
+ <ul>
+ <li>Ne dévoilez à persone qui vous avez tiré au sort!</li>
+ <li>Vous pouvez écrire une liste de souhaits, la personne ayant tiré votre nom y a accès.</li>
+ <li>Il n'est pas obligé de respecter la liste ou d'en écrire une. Elle sert pour inspirer des idées de cadeaux.</li>
+ </ul>
+ </main>
+ `;
- yes_button.addEventListener("click", function(event) {
- event.preventDefault();
- let other_person = people[person.other];
- document.body.innerHTML = `<h1>Tu as&nbsp;<span id="name">${other_person.name}</span>.</h1>`;
- localStorage.setItem(local_storage_key, other_person.name);
+ let showPersonButton = document.getElementById(showPersonButtonID);
+ showPersonButton.addEventListener("click", function(event) {
+ event.preventDefault();
+
+ setPagePerson(people[thisPerson.other].name);
+ });
+
+ let writeLetterButton = document.getElementById(writeLetterButtonID);
+ writeLetterButton.addEventListener("click", function(event) {
+ event.preventDefault();
+
+ let sendLetterButtonID = "send-letter";
+ let letterTextAreaID = "letter-text";
+
+ document.body.innerHTML = `
+ <div>
+ <h1>Liste des souhaits</h1>
+ <textarea id="${letterTextAreaID}" rows="5" cols="40" placeholder="..."></textarea>
+ <div class="buttons">
+ <button id="${sendLetterButtonID}">sauvegarder</button>
+ </div>
+ </div>
+ `;
+
+
+ let letterTextArea = document.getElementById(letterTextAreaID);
+ fetch(`/list/?name=${thisPerson.name}`)
+ .then(response => {
+ if (!response.ok) {
+ console.error('Network response was not ok');
+ }
+ return response.text();
+ })
+ .then(data => {
+ letterTextArea.textContent = data;
+ })
+ .catch(error => {
+ console.error('There was a problem with the fetch operation:', error);
});
- no_button.addEventListener("click", function(event) {
+ let sendLetterButton = document.getElementById(sendLetterButtonID);
+ sendLetterButton.addEventListener("click", function(event) {
event.preventDefault();
- setPage1();
- });
- }); // button click even listener
- }
+ console.log("Sending list of", thisPerson.name);
+
+ let messageBody = {
+ name: thisPerson.name,
+ text: letterTextArea.value,
+ };
+ const postBody = new URLSearchParams(messageBody).toString();
+
+ fetch('/list/', {
+ method: 'POST',
+ headers:
+ {
+ 'Content-Type': 'application/x-www-form-urlencoded'
+ },
+ body: postBody,
+ })
+ .then(function(response) {
+ response.text().
+ then(function(text_response) {
+ const textElement = document.createElement('div');
+ textElement.textContent = 'Sauvegardé';
+ textElement.classList.add('fade');
+ let buttons = document.querySelector("div.buttons");
+ buttons.appendChild(textElement);
+ setTimeout(() => {
+ textElement.remove();
+ }, 1000);
+ })
+ })
+ .catch(error => console.error('Error:', error));
+
+ }); // add event listener send button
+
+ }); // add event listener write letter
+
}
window.onload = function() {
- let clicked = localStorage.getItem(local_storage_key);
- if(!clicked)
+
+ // NOTE(luca): Users will expect going back to go to the home page so we do this manually.
+ var stateObj = { foo: "bar" };
+ history.pushState(stateObj, "home", "/");
+ window.addEventListener('popstate', function(event) {
+ window.location.reload();
+ });
+
+ let picking_person_name = localStorage.getItem(local_storage_key);
+ let thisPerson = findPersonByName(picking_person_name);
+
+ if(!picking_person_name)
{
- setPage1();
+ setPageFirst();
}
else
{
- document.body.innerHTML = `<h1>Tu as déjà choisi.</h1>`;
+ setPagePickingperson(thisPerson);
}
};
diff --git a/main.go b/main.go
index 8c81329..c16c169 100644
--- a/main.go
+++ b/main.go
@@ -1,32 +1,76 @@
+// Documentation
+//
+// Secret santa app that's random so people don't have to worry about the picking process.
+// No emails or login, just use the domain.
+//
+// Run it with `go run .`
+//
+//
+// TODOs
+//
+// TODO: Command line flags
+// - serve to serve it
+// - unpick to unpick all
+// - editor (to have one where i can edit the gob file manually)
+//
+// TODO: Make it safer by doing this.
+// 1. Have no ID
+// 2. Choose name
+// 3. Get (and store in local storage)
+// - other person name
+// - token to make requests
+// - this name's id
+// 4. Set this ID picked on server
+//
+// -> This data is used to know if the person has picked or not.
+//
+// 1. Already have an ID (in local storage)
+// 2. Get
+// - Wishlists (for sync)
+
package main
//- Libraries
-import "fmt"
-import "net/http"
-import "math/rand"
-import "html/template"
-import "strings"
-import _ "embed"
+import (
+ "encoding/gob"
+ "errors"
+ "fmt"
+ "html/template"
+ "io"
+ "math/rand"
+ "net/http"
+ "os/signal"
+ "strings"
+
+ _ "embed"
+ "log"
+ "os"
+)
// - Types
type Person struct {
- Name string
- Other int
+ Name string
+ Other int
+ Wishlist string
+ HasPicked bool
+ Token int64
}
type PageData struct {
- People []Person
- Seed int64
+ People []Person
+ KeyID int64
}
-var DEBUG = true
-
//- Globals
+var DEBUG = true
+var local_storage_key_id int64
+
//go:embed index.tmpl.html
var page_html string
-var people = []Person{
+// This is the default list when it is not decoded from the data file see below
+var global_people = []Person{
{Name: "Nawel"},
{Name: "Tobias"},
{Name: "Luca"},
@@ -38,81 +82,228 @@ var people = []Person{
{Name: "Yves"},
{Name: "Marthe"},
}
-var people_count = len(people)
+
+var global_version int = 1
+
+var data_file_name string = "people.gob"
+
+func DecodeData(logger *log.Logger, file_name string) {
+ file, err := os.Open(file_name)
+ if errors.Is(err, os.ErrNotExist) {
+ logger.Println("Datafile does not exist. Creating", file_name)
+ file, err = os.Create(file_name)
+ if err != nil {
+ logger.Fatalln(err)
+ }
+
+ } else if err != nil {
+ logger.Fatalln(err)
+ } else {
+ dec := gob.NewDecoder(file)
+
+ // Check the version
+ var version int
+ if err := dec.Decode(&version); err != io.EOF && err != nil {
+ logger.Fatalln(err)
+ }
+ if version != global_version {
+ logger.Fatalf("Version mismatch for datafile@%s != package@%s\n", version, global_version)
+ }
+
+ // Decode the data and import it into global_people
+ if err := dec.Decode(&global_people); err != nil && err != io.EOF {
+ logger.Fatalln(err)
+ }
+
+ logger.Printf("Imported %d people.\n", len(global_people))
+
+ if err := file.Close(); err != nil {
+ logger.Fatalln(err)
+ }
+ }
+}
+
+func EncodeData(logger *log.Logger, file_name string) {
+ file, err := os.Create(file_name)
+ if err != nil {
+ logger.Fatalln(err)
+ }
+ defer file.Close()
+
+ enc := gob.NewEncoder(file)
+ if err := enc.Encode(global_version); err != nil {
+ logger.Fatalln(err)
+ }
+ if err := enc.Encode(global_people); err != nil {
+ logger.Fatalln(err)
+ }
+}
+
+func (person Person) String() string {
+ var digits string
+ if person.Token > 99999 {
+ digits = fmt.Sprintf("%d", person.Token)[:6]
+ } else {
+ digits = fmt.Sprintf("%d", person.Token)
+ }
+
+ return fmt.Sprintf("%s_%s(%t)\n%s\n",
+ person.Name, digits, person.HasPicked, person.Wishlist)
+}
func TemplateToString(page_html string, people []Person, seed int64) string {
- var buf strings.Builder
- template_response, err := template.New("roulette").Parse(page_html)
- if err != nil {
- fmt.Println(err)
- }
- err = template_response.ExecuteTemplate(&buf, "roulette", PageData{people, seed})
- if err != nil {
- fmt.Println(err)
- }
- response := buf.String()
+ var buf strings.Builder
+ template_response, err := template.New("roulette").Parse(page_html)
+ if err != nil {
+ fmt.Println(err)
+ }
+ err = template_response.ExecuteTemplate(&buf, "roulette", PageData{people, local_storage_key_id})
+ if err != nil {
+ fmt.Println(err)
+ }
+ response := buf.String()
- return response
+ return response
+}
+
+func FindPersonByName(people []Person, name string) (bool, *Person) {
+ var found_person *Person
+ var found bool
+
+ for index, value := range people {
+ if name == value.Name {
+ found_person = &people[index]
+ found = true
+ break
+ }
+ }
+
+ return found, found_person
+}
+
+func FindPersonByOtherName(people []Person, name string) (bool, *Person) {
+ var found_person *Person
+ var found bool
+
+ for index, person := range people {
+ if name == people[person.Other].Name {
+ found = true
+ found_person = &people[index]
+ break
+ }
+ }
+
+ return found, found_person
}
// - Main
func main() {
- var seed int64
- seed = rand.Int63()
- seed = 1924480304604450476
- fmt.Println("seed:", seed)
+ logger := log.New(os.Stdout, "[noel] ", log.Ldate|log.Ltime)
- src := rand.NewSource(seed)
- r := rand.New(src)
+ seed := rand.Int63()
+ seed = 1623876946084255669
+ logger.Println("seed:", seed)
+ source := rand.NewSource(seed)
+ seeded_rand := rand.New(source)
rand.Seed(seed)
+ // Get a shuffled list that has following constaints
+ // 1. One person cannot choose itself
+ // 2. Every person has picked another person
var list []int
correct := false
for !correct {
- list = r.Perm(people_count)
+ list = seeded_rand.Perm(len(global_people))
correct = true
- for i, v := range list {
- if v == i {
- fmt.Println("incorrect, need to reshuffle")
+ for i, version := range list {
+ if version == i {
+ logger.Println("incorrect, need to reshuffle")
correct = false
break
}
}
}
- for i, v := range list {
- people[i].Other = v
+ local_storage_key_id = seeded_rand.Int63()
+
+ // Initialize people
+ for index, value := range list {
+ global_people[index].Other = value
+ global_people[index].Token = seeded_rand.Int63()
}
+ DecodeData(logger, data_file_name)
+
+ fmt.Println(global_people)
+
+ go func() {
+ c := make(chan os.Signal, 1)
+ signal.Notify(c, os.Interrupt)
+ <-c
+
+ EncodeData(logger, data_file_name)
+
+ logger.Println("data saved.")
+ os.Exit(0)
+ }()
+
+ defer EncodeData(logger, data_file_name)
+
http.Handle("/static/", http.StripPrefix("/static/", http.FileServer(http.Dir("./assets"))))
- http.HandleFunc("/person/", func(writer http.ResponseWriter, request *http.Request) {
- name := request.FormValue("name")
+ http.HandleFunc("/unpickall/", func(writer http.ResponseWriter, request *http.Request) {
+ for index := range global_people {
+ global_people[index].HasPicked = false
+ }
+ fmt.Fprintln(writer, "Done.")
+ })
+
+ http.HandleFunc("/list/", func(writer http.ResponseWriter, request *http.Request) {
+ if request.Method == http.MethodPost {
+ name := request.FormValue("name")
+ text := request.FormValue("text")
+
+ logger.Println("Edit wishlist of", name)
+ found, person := FindPersonByName(global_people, name)
+
+ if found {
+ person.Wishlist = text
+ person.HasPicked = true
+ logger.Println(global_people)
- var found bool
- for _, value := range people {
- if name == value.Name {
- found = true
+ fmt.Fprintln(writer, "ok")
+
+ } else {
+ fmt.Fprintln(writer, "error")
}
- }
+ } else if request.Method == http.MethodGet {
+ params := request.URL.Query()
+ name := params.Get("name")
- if found {
- fmt.Fprintln(writer, "ok")
- } else {
- fmt.Fprintln(writer, "error")
+ found, picking_person := FindPersonByOtherName(global_people, name)
+ if found {
+ picked_person := &global_people[picking_person.Other]
+ fmt.Fprintln(writer, picked_person.Wishlist)
+ } else {
+ fmt.Fprintln(writer, "error")
+ }
}
})
// Execute the template before-hand since the contents won't change.
-
- response := TemplateToString(page_html, people, seed);
-
+ response := TemplateToString(page_html, global_people, seed)
http.HandleFunc("/", func(writer http.ResponseWriter, request *http.Request) {
if DEBUG {
- response = TemplateToString(page_html, people, seed);
+ buffer, err := os.ReadFile("index.tmpl.html")
+ if err != nil {
+ fmt.Print(err)
+ }
+
+ file_contents := string(buffer)
+ response = TemplateToString(file_contents, global_people, seed)
}
- fmt.Fprint(writer, response)
+ fmt.Fprint(writer, response)
})
var address string
@@ -121,8 +312,8 @@ func main() {
} else {
address = "localhost:15118"
}
- fmt.Printf("Listening on http://%s\n", address)
- if err := http.ListenAndServe(address, nil); err != nil {
+ logger.Printf("Listening on http://%s\n", address)
+ if err := http.ListenAndServe(address, nil); err != nil {
panic(err)
}