package main import ( "encoding/json" "fmt" "html/template" "crypto/sha512" "io" "io/ioutil" "net/http" "os" "os/exec" "sort" "strconv" "strings" "sync" "time" "encoding/hex" "mime" "github.com/labstack/echo" "github.com/labstack/echo/middleware" "github.com/gorilla/feeds" "github.com/google/uuid" //"github.com/labstack/echo/middleware" ) type ( JSTime time.Time Media struct { Type string `json:"type"` File string `json:"file"` Height int `json:"height"` Width int `json:"width"` } Entry struct { Date JSTime `json:"date"` Description string `json:"description"` Post string `json:"post"` Likes int `json:"likes"` Tags []string `json:"tags"` Media []Media `json:"media"` Prev *Entry `json:"-"` Next *Entry `json:"-"` // Users []string } Response struct { Ok bool Message string } TemplateRenderer struct { template *template.Template } ) type ( entryMap struct { filename string modtime time.Time mapped map[string]*Entry sorted []*Entry mu sync.Mutex } entryList []*Entry ) const ( jsTimeLite = "2006-01-02" jsTimeLayout = "2006-01-02 15:04:05" adminUsername = "danolo" adminPassword = "bd4cad796950f50352225de3c773d8f3c39622bc17f34ad661eabe615cdf6d32751c5751e0648dc17d890f40330018334a2ae899878f200f6dc80121ddb70cc9" ) var ( list *entryMap = &entryMap{ filename: "img/list.json", modtime: time.Unix(0, 0), } ) // FUNCTIONS func genName() string { return strings.Replace(uuid.New().String(), "-", "", -1) } // JSTime func (ct *JSTime) UnmarshalJSON(b []byte) (err error) { s := strings.Trim(string(b), `"`) nt, err := time.Parse(jsTimeLayout, s) *ct = JSTime(nt) return } func (ct JSTime) MarshalJSON() ([]byte, error) { return []byte(ct.String()), nil } func (ct JSTime) String() string { t := time.Time(ct) return fmt.Sprintf("%q", t.Format(jsTimeLayout)) } func (ct JSTime) Unix() int64 { return time.Time(ct).Unix() } // Entry func (entry Entry) Count() map[string]int { count := make(map[string]int) for _, media := range entry.Media { count[media.Type] += 1 } return count } func (entry Entry) Len() int { return len(entry.Media) } // TemplateRenderer func (renderer *TemplateRenderer) Render(w io.Writer, name string, data interface{}, c echo.Context) error { return renderer.template.ExecuteTemplate(w, name, data) } // entryMap func (list *entryMap) Len() int { return len(list.mapped) } func (list *entryMap) modified() (mod bool) { info, err := os.Stat(list.filename) if err == nil { mod = list.modtime.Before(info.ModTime()) list.modtime = info.ModTime() } else { mod = true } return } func (list *entryMap) write() (err error) { data, err := json.MarshalIndent(list.mapped, "", " ") if err == nil { err = ioutil.WriteFile(list.filename, data, 0644) } if err == nil { list.modtime = time.Now() } return } func (list *entryMap) read() (err error) { if list.modified() { if data, err := ioutil.ReadFile(list.filename); err == nil { // read map list.mapped = make(map[string]*Entry) err = json.Unmarshal(data, &list.mapped) // sort list list.sorted = make([]*Entry, 0, len(list.mapped)) for _, entry:= range list.mapped { list.sorted = append(list.sorted, entry) } sort.Sort(entryList(list.sorted)) // assign prev/next for index, value := range list.sorted { if index-1 >= 0 { value.Next = list.sorted[index-1] } if index+1 < len(list.sorted) { value.Prev = list.sorted[index+1] } } } } return } func (list *entryMap) getSortedEntries() entryList { list.mu.Lock() defer list.mu.Unlock() list.read() return entryList(list.sorted) } func (list *entryMap) readSliceList(oval int, nval int) (result entryList, err error) { if nval <= 0 { nval = list.Len() } if oval <= 0 { oval = 0 } if err == nil { entries := list.getSortedEntries() if oval >= list.Len() { result = nil } else if oval+nval >= list.Len() { result = entries[oval:] } else { result = entries[oval : oval+nval] } } return } func (list *entryMap) addEntry(entry *Entry) (err error) { list.mu.Lock() defer list.mu.Unlock() list.read() if entry != nil { list.mapped[entry.Post] = entry err = list.write() } return } func (list *entryMap) getEntry(uuid string) (result *Entry, err error) { list.mu.Lock() defer list.mu.Unlock() if err = list.read(); err == nil { result = list.mapped[uuid] } return } func (list *entryMap) getFeed() (feed *feeds.Feed) { feed = &feeds.Feed { Title: "danoloan.es igar", Link: &feeds.Link { Href: "https://danoloan.es/igar/" }, Description: "bitácora fotográfica de danoloan", Author: &feeds.Author { Name: "danoloan", Email: "danolo@danoloan.es" }, } feed.Items = make([]*feeds.Item, 0, list.Len()) for _, elem := range list.getSortedEntries() { item := &feeds.Item { Title: elem.Description, Link: &feeds.Link{ Href: fmt.Sprintf("https://danoloan.es/igar/view?uuid=%s", elem.Post) }, Description: fmt.Sprintf("

%s

\n
\n", elem.Description), Created: time.Time(elem.Date), } for _, media := range elem.Media { if media.Type == "GraphImage" { // TODO ruta relativa al proxy item.Description = fmt.Sprintf("%s\n\t", item.Description, elem.Post, media.File) } } item.Description = fmt.Sprintf("%s\n
", item.Description) feed.Items = append(feed.Items, item) } return } // entryList func (list entryList) Len() int { return len(list) } func (list entryList) Swap(i, j int) { list[i], list[j] = list[j], list[i] } func (list entryList) Less(i, j int) bool { return time.Time(list[i].Date).After(time.Time(list[j].Date)) } // CONTROLLERS func ListController(c echo.Context) (res error) { var err error var oval, nval int oval, err = strconv.Atoi(c.QueryParam("o")) if err != nil { oval = 0 } nval, err = strconv.Atoi(c.QueryParam("n")) if err != nil { nval = 0 } slice, err := list.readSliceList(oval, nval) if err == nil { res = c.JSON(http.StatusOK, slice) } else { response := Response{false, "Error fetching song list"} res = c.JSON(http.StatusInternalServerError, response) } return } func ViewController(c echo.Context) (err error) { uuid := c.QueryParam("uuid") entry, err := list.getEntry(uuid) if err == nil { err = c.Render(http.StatusOK, "view.html", entry) } if err != nil { c.Logger().Error(err) } return } func RSSController(c echo.Context) (err error) { if blob, err := list.getFeed().ToRss(); err == nil { c.Blob(http.StatusOK, "application/xml", []byte(blob)) } return } func JSONController(c echo.Context) (err error) { if blob, err := list.getFeed().ToJSON(); err == nil { c.Blob(http.StatusOK, "application/json", []byte(blob)) } return } func AtomController(c echo.Context) (err error) { if blob, err := list.getFeed().ToAtom(); err == nil { c.Blob(http.StatusOK, "application/xml", []byte(blob)) } return } // TODO pura mierda func PostController(c echo.Context) (err error) { desc := c.FormValue("desc") date := c.FormValue("date") file, err := c.FormFile("file") entry := &Entry { Description: desc, Post: genName(), } if date != "" { time, _ := time.Parse(jsTimeLite, date) entry.Date = JSTime(time) } else { entry.Date = JSTime(time.Now()) } if err != nil { return } media := Media { Type: "GraphImage", File: genName(), } extension, err := mime.ExtensionsByType(file.Header.Get("Content-Type")) if err == nil { media.File = fmt.Sprintf("%s%s", media.File, extension[0]) } entry.Media = append(entry.Media, media) src, err := file.Open() if err != nil { return } defer src.Close() postdir := fmt.Sprintf("img/%s", entry.Post) filepath := fmt.Sprintf("%s/%s", postdir, media.File) os.MkdirAll(postdir, os.ModePerm) dst, err := os.Create(filepath) if err != nil { return } defer dst.Close() list.addEntry(entry) if _, err = io.Copy(dst, src); err != nil { return } if err = exec.Command("node", "thumbs/make_thumb.js", postdir).Run(); err != nil { c.Logger().Error(err.Error()) } return c.String(http.StatusCreated, "") } func auth(username, password string, c echo.Context) (bool, error) { hash := sha512.Sum512([]byte(password)) hashString := hex.EncodeToString(hash[:]) if username == adminUsername && hashString == adminPassword { return true, nil } return false, nil } func main() { e := echo.New() e.Renderer = &TemplateRenderer{ template: template.Must(template.ParseGlob("templates/*.html")), } e.Static("/", "index.html") e.GET("/list", ListController) e.GET("/view", ViewController) e.GET("/rss", RSSController) e.GET("/json", JSONController) e.GET("/atom", AtomController) e.Static("/static", "static") e.Static("/img", "img") admin := e.Group("/admin", middleware.BasicAuth(auth)) admin.GET("/", func(c echo.Context) error { return c.Render(http.StatusOK, "admin.html", struct { List entryList }{ List: list.getSortedEntries(), }) }) admin.POST("/post", PostController) //admin.DELETE("/post", DeleteController) admin.Static("/static", "admin/static") e.Logger.Fatal(e.Start(":40404")) }