summaryrefslogtreecommitdiff
path: root/main.go
diff options
context:
space:
mode:
authorRaymaekers Luca <luca@spacehb.net>2025-11-07 18:26:36 +0100
committerRaymaekers Luca <luca@spacehb.net>2025-11-07 18:26:36 +0100
commit18d189f5c79ed30a96410c9443eddb80c7a89d1e (patch)
tree6778b725b8a13ea3d71d2f192c452b8ce07a4b3b /main.go
parentbef456df253a057c536b6fda209c45c8807eee4b (diff)
Organized so it is easier to share code across modules
Diffstat (limited to 'main.go')
-rw-r--r--main.go537
1 files changed, 2 insertions, 535 deletions
diff --git a/main.go b/main.go
index 6812724..4a34862 100644
--- a/main.go
+++ b/main.go
@@ -1,540 +1,7 @@
-// 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's name & list
-// - this person's name & list
-// - token to make requests
-// 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 (
- "encoding/gob"
- "encoding/json"
- "errors"
- "flag"
- "fmt"
- "html/template"
- "math/rand"
- "net/http"
- "os/signal"
- "strconv"
- "strings"
-
- _ "embed"
- "io"
- "log"
- "os"
- "time"
-)
-
-// - Types
-type Person struct {
- Name string
- Other int
- Wishlist string
- HasPicked bool
- Token int64
-}
-
-//- Globals
-
-var nil_person = Person{Name: "nil"}
-var global_local_storage_key int64
-var global_local_storage_key_initialized = false
-
-//go:embed index.tmpl.html
-var page_html string
-
-// - Globals
-var global_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 global_people_initialized = false
-
-var global_version int = 2
-var global_data_file_name string = "people.gob"
-var global_data_directory_name string = "gobs"
-
-func EncodeWrapper(encoder *gob.Encoder, value any, logger *log.Logger) {
- if err := encoder.Encode(value); err != nil {
- logger.Fatalln(err)
- }
-}
-
-// - Serializing
-func EncodeData(logger *log.Logger, file_name string) {
-
- file, err := os.Create(file_name)
- if err != nil {
- logger.Fatalln(err)
- }
- defer file.Close()
-
- encoder := gob.NewEncoder(file)
-
- EncodeWrapper(encoder, global_version, logger)
- EncodeWrapper(encoder, global_local_storage_key, logger)
- EncodeWrapper(encoder, global_people, logger)
-
- logger.Printf("saved %d people.\n", len(global_people))
-}
-
-func DecodeWrapper(decoder *gob.Decoder, value any, logger *log.Logger) {
- if err := decoder.Decode(value); err != nil {
- logger.Fatalln(err)
- }
-}
-
-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:", file_name)
- } else if err != nil {
- logger.Fatalln(err)
- } else {
-
- decoder := gob.NewDecoder(file)
-
- var version int
- DecodeWrapper(decoder, &version, logger)
- logger.Printf("datafile@%d program@%d\n", version, global_version)
-
- // NOTE(luca): this will automatically migrate v1 to v2
- if version == 2 {
- DecodeWrapper(decoder, &global_local_storage_key, logger)
- global_local_storage_key_initialized = true
- }
+import "noelan/code"
- DecodeWrapper(decoder, &global_people, logger)
-
- logger.Printf("Imported %d people.\n", len(global_people))
-
- global_people_initialized = true
-
- if err := file.Close(); err != nil {
- logger.Fatalln("Error closing file", err)
- }
- }
-}
-
-// - Person
-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)", person.Name, digits, person.HasPicked)
-}
-
-func TemplateToString(page_html string, people []Person, seed int64, internal bool) string {
- var buf strings.Builder
- template_response, err := template.New("tirage").Parse(page_html)
- if err != nil {
- fmt.Println(err)
- }
-
- type PageData struct {
- People []Person
- Key int64
- Internal bool
- }
- err = template_response.ExecuteTemplate(&buf, "tirage", PageData{People: people, Key: global_local_storage_key, Internal: internal})
- 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
-
- 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
-}
-
-func ShufflePeople(rand *rand.Rand, people []Person, logger *log.Logger) {
- // 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 = rand.Perm(len(people))
-
- correct = true
- for i, version := range list {
- if version == i {
- logger.Println("incorrect, need to reshuffle")
- correct = false
- break
- }
- }
- }
-
- // Initialize people
- for index, value := range list {
- people[index].Other = value
- }
-}
-
-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, message, http.StatusNotFound)
-}
-
-// - Main
func main() {
- var did_work bool
- var internal, slow bool
- var serve, shuffle, unpickall, show_people, reset_tokens bool
- var add_person, remove_person, unpick string
- var set_local_storage_key int64
-
- flag.BoolVar(&serve, "serve", false, "run http server")
- flag.BoolVar(&internal, "internal", false, "run commands in internal mode")
- flag.BoolVar(&slow, "slow", false, "run commands in slow mode")
- flag.BoolVar(&shuffle, "shuffle", false, "shuffle people again")
- flag.BoolVar(&reset_tokens, "reset_tokens", false, "reset tokens of people")
- flag.BoolVar(&unpickall, "unpickall", false, "unpick all people")
- flag.BoolVar(&show_people, "show_people", false, "show people")
- flag.StringVar(&add_person, "add_person", "", "add person by name")
- flag.StringVar(&remove_person, "remove_person", "", "remove person by name")
- flag.StringVar(&unpick, "unpick", "", "unpick person by name")
- flag.Int64Var(&set_local_storage_key, "set_local_storage_key", 0, "Set the local storage key")
-
- flag.Parse()
-
- logger := log.New(os.Stdout, "[noel] ", log.Ldate|log.Ltime)
- var seed int64
- if internal {
- seed = rand.Int63()
- } else {
- seed = 7967946373046491984
- }
- logger.Println("seed:", seed)
- source := rand.NewSource(seed)
- seeded_rand := rand.New(source)
- rand.Seed(seed)
-
- // backup
- {
- file, err := os.Open(global_data_file_name)
- defer file.Close()
- if errors.Is(err, os.ErrNotExist) {
- logger.Println("Datafile does not exist:", global_data_file_name)
- } else if err != nil {
- logger.Fatalln(err)
- } else {
- _, err := os.Stat(global_data_directory_name)
- if os.IsNotExist(err) {
- err = os.MkdirAll(global_data_directory_name, 0755)
- if err != nil {
- logger.Printf("Error creating directory: %v\n", err)
- }
- logger.Println("Directory created successfully")
- } else if err != nil {
- logger.Printf("Error checking directory: %v\n", err)
- } else {
- // Directory already exists
- }
-
- now := time.Now()
- formatted_now := now.Format("060102_03_04_05")
-
- destination_file_name := fmt.Sprintf("./%s/%s__%s", global_data_directory_name, formatted_now, global_data_file_name)
- destination, err := os.Create(destination_file_name)
- if err != nil {
- logger.Fatalln("Error creating copy:", err)
- }
- defer destination.Close()
-
- _, err = io.Copy(destination, file)
- if err == nil {
- logger.Println("Created backup.")
- } else {
- logger.Fatalln("Error creating backup:", err)
- }
- }
- }
-
- // Init people
- {
- DecodeData(logger, global_data_file_name)
- if !global_people_initialized {
- logger.Println("Initialize people.")
- ShufflePeople(seeded_rand, global_people, logger)
-
- for index := range global_people {
- // NOTE(luca): since javascript cannot handle big numbers we crop them
- global_people[index].Token = seeded_rand.Int63() / 10000
- }
-
- global_people_initialized = true
- }
-
- if !global_local_storage_key_initialized {
- logger.Println("Initialize local storage key.")
- global_local_storage_key = rand.Int63()
- global_local_storage_key_initialized = true
- }
- }
-
- if len(add_person) > 0 {
- global_people = append(global_people, Person{Name: add_person})
- fmt.Println(global_people)
- did_work = true
- }
-
- if len(remove_person) > 0 {
- for index, person := range global_people {
- if person.Name == remove_person {
- global_people = append(global_people[:index], global_people[index+1:]...)
- break
- }
- }
- logger.Println("remove:", remove_person)
- did_work = true
- }
-
- if reset_tokens {
- for index := range global_people {
- // NOTE(luca): since javascript cannot handle big numbers we crop them
- global_people[index].Token = seeded_rand.Int63() / 10000
- }
-
- logger.Println("reset tokens.")
- did_work = true
- }
-
- if unpickall {
- did_work = true
- for index := range global_people {
- global_people[index].HasPicked = false
- }
- logger.Println("Unpicked all.")
- }
-
- if shuffle {
- did_work = true
- ShufflePeople(seeded_rand, global_people, logger)
- logger.Println("Shuffled people.")
- }
-
- if show_people {
- for _, person := range global_people {
- fmt.Printf("%12s [%d] %t\n", person.Name, person.Token, person.HasPicked)
- }
- }
-
- if len(unpick) > 0 {
- did_work = true
- logger.Println("unpick:", unpick)
- found, person := FindPersonByName(global_people, unpick)
- if found {
- person.HasPicked = false
- logger.Println(person)
- } else {
- logger.Fatalln("No such person")
- }
- }
-
- if set_local_storage_key != 0 {
- did_work = true
- global_local_storage_key = set_local_storage_key
- global_local_storage_key_initialized = true
- }
-
- if serve {
- did_work = true
- logger.Println("local storage key:", global_local_storage_key)
- logger.Println(global_people)
-
- for index := range global_people {
- fmt.Print(global_people[index].Other)
- }
- fmt.Print("\n")
-
- go func() {
- c := make(chan os.Signal, 1)
- signal.Notify(c, os.Interrupt)
- <-c
-
- if did_work {
- EncodeData(logger, global_data_file_name)
- }
-
- logger.Println("data saved.")
- os.Exit(0)
- }()
-
- http.Handle("/static/", http.StripPrefix("/static/", http.FileServer(http.Dir("./assets"))))
-
- http.HandleFunc("/list/", func(writer http.ResponseWriter, request *http.Request) {
- if request.Method == http.MethodPost {
- name := request.FormValue("name")
- text := request.FormValue("text")
- token := request.FormValue("token")
-
- logger.Println("Edit wishlist of", name)
- found, person := FindPersonByName(global_people, name)
-
- if found {
- tokenString := strconv.FormatInt(person.Token, 10)
- if token == tokenString {
- person.Wishlist = text
- person.HasPicked = true
-
- fmt.Fprintln(writer, "ok")
- } else {
- HttpError(logger, "invalid token", person, writer, request)
- }
- } else {
- HttpError(logger, "no such person", &nil_person, writer, request)
- }
- } else if request.Method == http.MethodGet {
- params := request.URL.Query()
- name := params.Get("name")
- token := params.Get("token")
-
- found, person := FindPersonByName(global_people, name)
-
- if found {
- tokenString := strconv.FormatInt(person.Token, 10)
-
- if token == tokenString {
- fmt.Fprint(writer, global_people[person.Other].Wishlist)
- } else {
- HttpError(logger, "invalid token", person, writer, request)
- }
- } else {
- HttpError(logger, "no such person", &nil_person, writer, request)
- }
- }
- })
-
- http.HandleFunc("/choose/", func(writer http.ResponseWriter, request *http.Request) {
- params := request.URL.Query()
- name := params.Get("name")
- found, person := FindPersonByName(global_people, name)
- if found {
- if !person.HasPicked {
- person.HasPicked = true
-
- type Response struct {
- Token int64
- ThisWishlist string
- OtherName string
- OtherWishlist string
- }
- other_person := global_people[person.Other]
-
- response := Response{person.Token, person.Wishlist, other_person.Name, other_person.Wishlist}
-
- json.NewEncoder(writer).Encode(response)
- } else {
- HttpError(logger, "person already picked", person, writer, request)
- }
- } else {
- HttpError(logger, "no such person", &nil_person, writer, request)
- }
- })
-
- // Execute the template before-hand since the contents won't change.
- response := TemplateToString(page_html, global_people, seed, internal)
- http.HandleFunc("/", func(writer http.ResponseWriter, request *http.Request) {
- if slow {
- buffer, err := os.ReadFile("index.tmpl.html")
- if err == nil {
- file_contents := string(buffer)
- response = TemplateToString(file_contents, global_people, seed, internal)
- } else {
- logger.Println(err)
- }
- }
-
- fmt.Fprint(writer, response)
- })
-
- var address string
- if internal {
- address = "0.0.0.0:15118"
- } else {
- address = "localhost:15118"
- }
- logger.Printf("Listening on http://%s\n", address)
- if err := http.ListenAndServe(address, nil); err != nil {
- panic(err)
- }
-
- }
-
- if did_work {
- EncodeData(logger, global_data_file_name)
- }
-
- return
+ noelan.Run()
}