github.com/mssola/todo@v0.0.0-20181029153210-d25348dc3f48/app/topics.go (about)

     1  // Copyright (C) 2014-2017 Miquel Sabaté Solà <mikisabate@gmail.com>
     2  //
     3  // This Source Code Form is subject to the terms of the Mozilla Public
     4  // License, v. 2.0. If a copy of the MPL was not distributed with this
     5  // file, You can obtain one at http://mozilla.org/MPL/2.0/.
     6  
     7  package app
     8  
     9  import (
    10  	"log"
    11  	"net/http"
    12  	"time"
    13  
    14  	"github.com/docker/distribution/uuid"
    15  	"github.com/gorilla/mux"
    16  	"github.com/microcosm-cc/bluemonday"
    17  	"github.com/mssola/todo/lib"
    18  	"github.com/russross/blackfriday"
    19  )
    20  
    21  // Topic is my way to divide different "contexts" inside my To Do list.
    22  // Moreover this type also has the "Markdown" attribute. This attribute does
    23  // not match any column from the database, but it comes handy in the API layer.
    24  type Topic struct {
    25  	ID        string    `json:"id"`
    26  	Name      string    `json:"name"`
    27  	Contents  string    `json:"contents"`
    28  	CreatedAt time.Time `db:"created_at" json:"created_at"`
    29  	Markdown  string    `db:"-" json:"markdown"`
    30  }
    31  
    32  // RenderMarkdown generates the Markdown code for the current contents of this
    33  // topic.
    34  func (t *Topic) RenderMarkdown() {
    35  	unsafe := blackfriday.MarkdownCommon([]byte(t.Contents))
    36  	t.Markdown = string(bluemonday.UGCPolicy().SanitizeBytes(unsafe))
    37  }
    38  
    39  // TopicData is the data that will be assed to the renderShow function in order
    40  // to render the main page.
    41  type TopicData struct {
    42  	lib.ViewData
    43  
    44  	Current *Topic
    45  
    46  	Topics []Topic
    47  }
    48  
    49  // Given a name, try to create a new topic.
    50  func createTopic(name string) (*Topic, error) {
    51  	id := uuid.Generate().String()
    52  	t := &Topic{ID: id, Name: name, CreatedAt: time.Now()}
    53  	return t, Db.Insert(t)
    54  }
    55  
    56  // Sends the main page with the given topic rendered in it as
    57  // the current one.
    58  func renderShow(res http.ResponseWriter, topic *Topic, print bool) {
    59  	var topics []Topic
    60  	_, err := Db.Select(&topics, "select * from topics order by name")
    61  	if err != nil {
    62  		log.Printf("Select went wrong: %v", err)
    63  	}
    64  	topic.RenderMarkdown()
    65  
    66  	// And render the page.
    67  	o := &TopicData{
    68  		Current: topic,
    69  		Topics:  topics,
    70  	}
    71  	o.JS = "topics"
    72  	o.Print = print
    73  	lib.Render(res, "topics/show", o)
    74  }
    75  
    76  // TopicsIndex responds to: GET /topics
    77  func TopicsIndex(res http.ResponseWriter, req *http.Request) {
    78  	if lib.JSONEncoding(req) {
    79  		TopicsIndexJSON(res, req)
    80  		return
    81  	}
    82  
    83  	var err error
    84  	var t Topic
    85  
    86  	if id := lib.GetCookie(req, "topic"); id != "" && id != nil {
    87  		err = Db.SelectOne(&t, "select * from topics where id=$1", id)
    88  	} else {
    89  		err = Db.SelectOne(&t, "select * from topics order by name limit 1")
    90  	}
    91  	if err != nil {
    92  		log.Printf("Could not select topics: %v", err)
    93  	}
    94  	renderShow(res, &t, false)
    95  }
    96  
    97  // TopicsCreate responds to: POST /topics
    98  func TopicsCreate(res http.ResponseWriter, req *http.Request) {
    99  	if lib.JSONEncoding(req) {
   100  		TopicsCreateJSON(res, req)
   101  		return
   102  	}
   103  
   104  	if t, err := createTopic(req.FormValue("name")); err != nil {
   105  		http.Redirect(res, req, "/topics", http.StatusFound)
   106  	} else {
   107  		http.Redirect(res, req, "/topics/"+t.ID, http.StatusFound)
   108  	}
   109  }
   110  
   111  // TopicsShow responds to: GET /topics/:id
   112  func TopicsShow(res http.ResponseWriter, req *http.Request) {
   113  	if lib.JSONEncoding(req) {
   114  		TopicsShowJSON(res, req)
   115  		return
   116  	}
   117  
   118  	var t Topic
   119  
   120  	p := mux.Vars(req)
   121  	err := Db.SelectOne(&t, "select * from topics where id=$1", p["id"])
   122  	if err != nil {
   123  		log.Printf("Could not select topic: %v", err)
   124  	}
   125  	if t.ID != "" {
   126  		lib.SetCookie(res, req, "topic", t.ID)
   127  	}
   128  	print := req.URL.Query().Get("print") == "1"
   129  	renderShow(res, &t, print)
   130  }
   131  
   132  // TopicsUpdate responds to: PUT/PATCH /posts/:id
   133  func TopicsUpdate(res http.ResponseWriter, req *http.Request) {
   134  	var err error
   135  	p := mux.Vars(req)
   136  
   137  	// We can either rename, or change the contents, but not both things at the
   138  	// same time.
   139  	name := req.FormValue("name")
   140  	if name != "" {
   141  		_, err = Db.Exec("update topics set name=$1 where id=$2", name, p["id"])
   142  	} else {
   143  		cts := req.FormValue("contents")
   144  		_, err = Db.Exec("update topics set contents=$1 where id=$2", cts, p["id"])
   145  	}
   146  	if err != nil {
   147  		log.Printf("Could not update topic: %v", err)
   148  	}
   149  	http.Redirect(res, req, "/topics", http.StatusFound)
   150  }
   151  
   152  // TopicsDestroy responds to: DELETE /posts/:id
   153  func TopicsDestroy(res http.ResponseWriter, req *http.Request) {
   154  	p := mux.Vars(req)
   155  	_, err := Db.Exec("delete from topics where id=$1", p["id"])
   156  	if err != nil {
   157  		log.Printf("Could not perform delete of topic: %v", err)
   158  	}
   159  	http.Redirect(res, req, "/topics", http.StatusFound)
   160  }