diff options
| -rw-r--r-- | ideas.html | 21 | ||||
| -rw-r--r-- | main.go | 131 | 
2 files changed, 128 insertions, 24 deletions
| @@ -38,14 +38,33 @@      color: #bcbcbc;  } +.error { +    color: red; +} +      </style>  </head>  <body> +    {{ with .Error }} +    <p class="error">{{.}}</p> +    {{ end}} +    <form action="/create/" method="post"> +        <input name="title"  type="text" placeholder="Title"></br> +        <input name="text"   type="text" placeholder="Idea description..."></br> +        <input name="author" type="text" placeholder="Your Name"></br> +        <input type="submit" value="Post"> +    </form> +    <hr> +    <form action="/delete/" method="post"> +        <input type="text" placeholder="Title" name="title"></br> +        <input type="submit" value="Delete"> +    </form> +    <hr>      <ul>      {{ range .Ideas }}          <div class="idea">              <h2 class="title">{{.Title}}</h2> -            <p class="description">{{.Description}}</p> +            <p class="text">{{.Text}}</p>              <p class="creation">by <span class="author">{{.Author}}</span> on <span class="date">{{.CreatedAt}}</span></p>          </div>      {{ end }} @@ -10,6 +10,7 @@ import (  	"os"  	"os/signal"  	"runtime/debug" +	"time"  )  // File for persistent storage @@ -21,12 +22,12 @@ var Version string  // layout for how the date should be output in html  var DateLayout string = "02/01/2006 on 15:04" -// template for listing out the ideas, should be bundled inside the executable +// template for /ideas  //  //go:embed ideas.html  var ideas_html string -var data Store +var Ideas []Idea  // ToDo's  // - [ ] Add a post @@ -41,17 +42,24 @@ var data Store  // out of 5 rating of the idea  // formatted time string with DateLayout  type Idea struct { -	Title       string -	Description string -	Author      string -	CreatedAt   string +	Title     string +	Text      string +	Author    string +	CreatedAt string +	Comment   []Comment  } -// Data needed through the programm, this data is written to DataFilename for persistent storage -// Is also the format for the Data file -type Store struct { -	Version string -	Ideas   []Idea +// Represents a comment on an idea +type Comment struct { +	Text      string +	Author    string +	CreatedAt string +} + +// Data passed to the ideas_html template +type PageData struct { +	Ideas []Idea +	Error string  }  func GetVersion() string { @@ -65,7 +73,7 @@ func GetVersion() string {  func main() {  	Version = GetVersion() -	// Create a .data file  in the cache directory and decode eventual ideas from it +	// If the .data file does not exist, create it in the cache directory  	{  		p := os.Getenv("XDG_CACHE_HOME")  		// TODO: remove for testing @@ -80,25 +88,33 @@ func main() {  			log.Fatalln("Error while opening data file:", err)  		} +		// Decode the data and import it into Ideas +  		dec := gob.NewDecoder(f) -		if err := dec.Decode(&data); err != io.EOF && err != nil { -			log.Fatalln("Error while decoding ideas:", err) +		var v string +		if err := dec.Decode(&v); err != io.EOF && err != nil { +			log.Fatalln("Error while decoding version:", err)  		} -		log.Println(data) -		if data.Version != Version { -			log.Fatalf("Version mismatch for datafile@%s != package@%s\n", data.Version, Version) +		if v != Version { +			log.Fatalf("Version mismatch for datafile@%s != package@%s\n", v, Version)  		} -		log.Printf("Imported @%s: %d ideas\n", data.Version, len(data.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 -	// On program exit it should write all its data to the datafile -	// TODO: Instead of recreating the file the new ideas should be appended, a way to achieve this would be that every time a new idea is added it is appended to the file, this way there is no need to ensure this. +	// 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) @@ -111,20 +127,89 @@ func main() {  		defer f.Close()  		enc := gob.NewEncoder(f) -		if err := enc.Encode(data); err != nil { +		if err := enc.Encode(Version); err != nil {  			log.Fatalln(err)  		} + +		if err := enc.Encode(Ideas); err != nil { +			log.Fatalln("Error while saving ideas:", err) +		} +  		log.Println("data saved.")  		os.Exit(0)  	}() +	// Handling http +  	tmpl, err := template.New("ideas_html").Parse(ideas_html)  	if err != nil {  		log.Fatalln(err)  	} -	http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { -		tmpl.Execute(w, data) +	// TODO (Luca): Make the app more interactive by using websockets instead, +	// such that adding, editing or commenting on an idea does not require to +	// refresh the page. +	// Another approach would be to use htmx? + +	http.HandleFunc("/ideas/", func(w http.ResponseWriter, r *http.Request) { +		tmpl.Execute(w, PageData{Ideas, ""}) +	}) + +	http.HandleFunc("/create/", func(w http.ResponseWriter, r *http.Request) { +		if r.Method != http.MethodPost { +			http.Redirect(w, r, "/ideas/", http.StatusMovedPermanently) +			return +		} +		i := Idea{ +			Title:     r.FormValue("title"), +			Author:    r.FormValue("author"), +			CreatedAt: time.Now().Format(DateLayout), +			Text:      r.FormValue("text"), +		} + +		for _, v := range Ideas { +			if i.Title == v.Title { +				tmpl.Execute(w, PageData{Ideas, "An idea with title '" + v.Title + "' already exists!"}) +				return +			} +		} + +		Ideas = append(Ideas, i) +		log.Println("Added new idea:", i.Title) +		http.Redirect(w, r, "/ideas/", http.StatusMovedPermanently) +	}) + +	http.HandleFunc("/edit/", func(w http.ResponseWriter, r *http.Request) { +		if r.Method != http.MethodPost { +			http.Redirect(w, r, "/ideas/", http.StatusMovedPermanently) +			return +		} +		http.Redirect(w, r, "/ideas/", http.StatusMovedPermanently) +	}) + +	http.HandleFunc("/delete/", func(w http.ResponseWriter, r *http.Request) { +		if r.Method != http.MethodPost { +			http.Redirect(w, r, "/ideas/", http.StatusMovedPermanently) +			return +		} +		t := r.FormValue("title") +		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 +			} +		} +		tmpl.Execute(w, PageData{Ideas, "No idea with name '" + t + "'."}) +	}) + +	http.HandleFunc("/comment/", func(w http.ResponseWriter, r *http.Request) { +		if r.Method != http.MethodPost { +			http.Redirect(w, r, "/ideas/", http.StatusMovedPermanently) +			return +		} +		http.Redirect(w, r, "/ideas/", http.StatusMovedPermanently)  	})  	log.Println("Listening on http://localhost:8080") | 
