aboutsummaryrefslogtreecommitdiff
path: root/main.go
diff options
context:
space:
mode:
Diffstat (limited to 'main.go')
-rw-r--r--main.go308
1 files changed, 81 insertions, 227 deletions
diff --git a/main.go b/main.go
index d4c2c15..ddc8728 100644
--- a/main.go
+++ b/main.go
@@ -1,279 +1,133 @@
-package main
-
// ideez is a web application that allows people to post their ideas, it is meant to help
// brainstorming.
-
-// ToDo's
-// - [ ] Create a Server out of this so you can run multiple instances
-// - [ ] change CreatedAt to be Last Updated
-// - [x] edit a post
-// - [x] Store ideas to a file (encoder/gob)
-// - [x] Change the date format printing
-// - [x] outsource removing the posts to a separate cli tool
-// - [x] Add a post
-// - [x] Remove a post
+package main
import (
- _ "embed"
- "encoding/gob"
+ "crypto/rand"
+ "encoding/hex"
"errors"
- "fmt"
"html/template"
- "io"
"log"
"net/http"
"os"
- "os/signal"
- "runtime/debug"
- "time"
-)
-
-// File for persistent storage
-var DataFilename = "ideas.data"
-
-// Version number used for debugging compatibility with the .data file
-var Version string
+ "strings"
-// layout for how the date should be output in html
-var DateLayout string = "02/01/2006 on 15:04"
-
-// template for ideas html
-var (
- //go:embed t_idea/index.html
- ideas_html string
- //go:embed t_idea/edit.html
- idea_edit_html string
+ _ "embed"
)
-var Ideas []Idea
-
-// Represents an idea
-// LastUpdated is a formatted date string
-// out of 5 rating of the idea
-// formatted time string with DateLayout
-type Idea struct {
- Title string
- Text string
- Author string
- LastUpdated string
-}
-
-// Data passed to the ideas_html template
-type PageData struct {
- Ideas []Idea
- Error string
+type ServersPageData struct {
+ Ideezes []Ideez
+ Address template.URL
+ Error string
}
-func GetVersion() string {
- buildinfo, ok := debug.ReadBuildInfo()
- if !ok {
- log.Fatalln("Could not read buildinfo to know package version.")
- }
- return buildinfo.Main.Version
-}
-
-func main() {
- Version = GetVersion()
-
- // If the .data file does not exist, create it in the cache directory
- {
- p := os.Getenv("XDG_CACHE_HOME")
- if p == "" {
- p = "."
- }
- DataFilename = p + "/" + DataFilename
-
- f, err := os.Open(DataFilename)
- if errors.Is(err, os.ErrNotExist) {
- // The file does not exist, so create one with only Version encoded
- log.Println("File does not exist, creating", DataFilename)
- f, err = os.Create(DataFilename)
- if err != nil {
- log.Fatalln("Error while creating data file:", err)
- }
-
- enc := gob.NewEncoder(f)
- if err := enc.Encode(Version); err != nil {
- log.Fatalln("Error while encoding Version:", err)
- }
- } else if err != nil {
- log.Fatalln("Error while opening data file:", err)
- } else {
- dec := gob.NewDecoder(f)
-
- // Check the version
- var v string
- if err := dec.Decode(&v); err != io.EOF && err != nil {
- log.Fatalln("Error while decoding version:", err)
- }
- if v != Version {
- log.Fatalf("Version mismatch for datafile@%s != package@%s\n", v, Version)
- }
-
- // Decode the data and import it into Ideas
- err = dec.Decode(&Ideas)
- if err != nil && err != io.EOF {
- log.Fatalln("Error while decoding ideas:", err)
- }
- log.Printf("Imported @%s: %d ideas\n", v, len(Ideas))
- if err := f.Close(); err != nil {
- log.Fatalln("Error while closing file:", err)
- }
- }
-
- }
-
- // Handle SIGINT
- // Save all Ideas into the data file
- // NOTE (Luca): This system might seem dumb, but it only adds overhead at startup
- // and exit of the program which is fine.
- go func() {
- c := make(chan os.Signal, 1)
- signal.Notify(c, os.Interrupt)
- <-c
-
- f, err := os.Create(DataFilename)
- if err != nil {
- log.Fatalln(err)
- }
- defer f.Close()
+const (
+ // Name of the directory where the data files are stored
+ DATA_DIR = "ideez_servers"
+)
- enc := gob.NewEncoder(f)
- if err := enc.Encode(Version); err != nil {
- log.Fatalln(err)
- }
+var address string = "localhost:15118"
- if err := enc.Encode(Ideas); err != nil {
- log.Fatalln("Error while saving ideas:", err)
- }
+//go:embed templates/servers.html
+var servers_html string
- log.Println("data saved.")
- os.Exit(0)
- }()
+// All servers that are currently managed
+var Ideezes []Ideez
- // Handling http
+// Directory path where the data files are stored
+var DataDir string
- tmpl, err := template.New("ideas_html").Parse(ideas_html)
- if err != nil {
- log.Fatalln(err)
+func getAddress(r *http.Request) template.URL {
+ p, ok := r.Header["X-Forwarded-Proto"]
+ if ok {
+ return template.URL(p[0] + "://" + r.Host)
}
- _, err = tmpl.New("edit").Parse(idea_edit_html)
- if err != nil {
- log.Fatalln(err)
+ if r.TLS == nil {
+ return template.URL("http://" + r.Host)
+ } else {
+ return template.URL("https://" + r.Host)
}
+}
- // TODO (Luca): Make the app more interactive by using websockets instead,
- // such that adding or editing does not require to
- // refresh the page.
- // Another approach would be to use htmx?
- //
+func main() {
+ var (
+ logger = log.New(os.Stdout, "Ideez: ", log.Ldate)
+ )
mux := http.NewServeMux()
fs := http.FileServer(http.Dir("assets/"))
mux.Handle("/static/", http.StripPrefix("/static/", fs))
- mux.HandleFunc("GET /{$}", func(w http.ResponseWriter, r *http.Request) {
- http.Redirect(w, r, "/ideas/", http.StatusMovedPermanently)
- })
+ tmpl, err := template.New("servers").Parse(servers_html)
- mux.HandleFunc("GET /ideas/", func(w http.ResponseWriter, r *http.Request) {
- tmpl.Execute(w, PageData{Ideas, ""})
+ // list servers
+ mux.HandleFunc("GET /{$}", func(w http.ResponseWriter, r *http.Request) {
+ tmpl.ExecuteTemplate(w, "servers", ServersPageData{Ideezes, getAddress(r), ""})
})
- mux.HandleFunc("POST /idea/create/", func(w http.ResponseWriter, r *http.Request) {
- i := Idea{
- Title: r.FormValue("title"),
- Author: r.FormValue("author"),
- LastUpdated: time.Now().Format(DateLayout),
- Text: r.FormValue("text"),
- }
- if i.Title == "" || i.Author == "" || i.Text == "" {
- tmpl.Execute(w, PageData{Ideas, "All fields are required"})
+ // create a new server
+ mux.HandleFunc("POST /server/create/", func(w http.ResponseWriter, r *http.Request) {
+ addr := getAddress(r)
+ name := r.FormValue("name")
+ logger.Println("Create server", name)
+ if name == "" {
+ tmpl.ExecuteTemplate(w, "servers", ServersPageData{Ideezes, addr, "You must provide a title"})
return
}
- for _, v := range Ideas {
- if i.Title == v.Title {
- tmpl.Execute(w, PageData{Ideas, "An idea with title '" + v.Title + "' already exists!"})
+ for _, v := range Ideezes {
+ if v.Name == name {
+ tmpl.ExecuteTemplate(w, "servers", ServersPageData{Ideezes, addr, "A server with name '" + name + "' already exists!"})
return
}
}
- Ideas = append(Ideas, i)
- log.Println("Added new idea:", i.Title)
- http.Redirect(w, r, "/ideas/", http.StatusMovedPermanently)
- })
-
- // A page to edit the idea, this page should lead to POST /idea/edit for confirming the edit. If
- // the user cancels they should be redirected to the start page. This is done in the html.
- mux.HandleFunc("GET /idea/edit/", func(w http.ResponseWriter, r *http.Request) {
- t := r.URL.Query().Get("t")
- if t == "" {
- tmpl.Execute(w, PageData{Ideas, "You must provide a title."})
- return
+ bytes := make([]byte, SERVER_ID_BYTES)
+ _, err := rand.Read(bytes)
+ if err != nil {
+ panic(err)
}
+ id := hex.EncodeToString(bytes)
- for _, i := range Ideas {
- if i.Title == t {
- tmpl.ExecuteTemplate(w, "edit", i)
- return
- }
- }
- tmpl.Execute(w, PageData{Ideas, "No idea with title '" + t + "'."})
+ server := NewIdeez(mux, name, id)
+ Ideezes = append(Ideezes, server)
+ http.Redirect(w, r, "/", http.StatusMovedPermanently)
})
- // Perform the edit action on the idea
- mux.HandleFunc("POST /idea/edit/", func(w http.ResponseWriter, r *http.Request) {
- t := r.FormValue("title")
- if t == "" {
- tmpl.Execute(w, PageData{Ideas, "You must provide a title."})
- return
- }
-
- var i *Idea
- for j := range Ideas {
- if Ideas[j].Title == t {
- i = &Ideas[j]
- break
- }
+ // import the servers from the data files
+ {
+ p := os.Getenv("XDG_CACHE_HOME")
+ if p == "" {
+ p = "."
}
- if i.Title == "" {
- tmpl.Execute(w, PageData{Ideas, "No idea with title '" + t + "'."})
+ DataDir = p + "/" + DATA_DIR
+ if err := os.Mkdir(DataDir, 0755); err == nil {
+ logger.Println("Directory does not exist, creating " + DataDir)
+ } else if !errors.Is(err, os.ErrExist) {
+ panic(err)
}
- i.Title = r.FormValue("title")
- i.Text = r.FormValue("text")
- i.LastUpdated = time.Now().Format(DateLayout) + " (edit)"
- log.Printf("Edited '%s'\n", i.Title)
-
- http.Redirect(w, r, "/ideas/", http.StatusMovedPermanently)
- })
-
- mux.HandleFunc("POST /idea/delete/", func(w http.ResponseWriter, r *http.Request) {
- t := r.FormValue("title")
- if t == "" {
- tmpl.Execute(w, PageData{Ideas, "You must provide a title."})
- return
+ entries, err := os.ReadDir(DataDir)
+ if err != nil {
+ panic(err)
}
- for i, v := range Ideas {
- if t == v.Title {
- log.Println("Deleted:", v.Title)
- Ideas = append(Ideas[:i], Ideas[i+1:]...)
- http.Redirect(w, r, "/ideas/", http.StatusMovedPermanently)
- return
+ counter := 0
+ for _, file := range entries {
+ if !strings.HasSuffix(file.Name(), ".data") {
+ continue
}
+ id := strings.TrimSuffix(file.Name(), ".data")
+ ideez := NewIdeez(mux, "", id)
+ Ideezes = append(Ideezes, ideez)
+ counter++
}
- tmpl.Execute(w, PageData{Ideas, "No idea with title '" + t + "'."})
- })
-
- mux.HandleFunc("POST /comment/create/", func(w http.ResponseWriter, r *http.Request) {
- fmt.Fprintln(w, "Not implemented yet.")
- })
+ logger.Printf("Imported %d Ideezes\n", counter)
+ }
- log.Println("Listening on http://localhost:8080/ideas")
- err = http.ListenAndServe(":8080", mux)
+ logger.Println("Listening on http://" + address)
+ err = http.ListenAndServe(address, mux)
if err != nil {
- log.Fatalln(err)
+ panic(err)
}
}