package main import ( "os" "encoding/json" "crypto/sha512" "fmt" "net/http" "io/ioutil" "sort" "strconv" "strings" "sync" "time" "encoding/hex" "github.com/labstack/echo" "github.com/labstack/echo/middleware" "github.com/gorilla/feeds" ) type ( JSTime time.Time Entry struct { Artist string `json:"artist" form:"artist"` Linkto string `json:"linkto,omitempty" form:"linkto,omitempty"` Track string `json:"track,omitempty" form:"track,omitempty"` Album string `json:"album,omitempty" form:"album,omitempty"` Cover string `json:"cover,omitempty" form:"cover,omitempty"` Date JSTime `json:"date" form:"date"` } Response struct { Ok bool `json:"ok"` Message string `json:"message"` } ) type ( entryList struct { filename string modtime time.Time v []Entry mu sync.RWMutex } ) const ( jsTimeLayout = "2006-01-02" adminUsername = "danolo" adminPassword = "bd4cad796950f50352225de3c773d8f3c39622bc17f34ad661eabe615cdf6d32751c5751e0648dc17d890f40330018334a2ae899878f200f6dc80121ddb70cc9" ) var ( list *entryList = &entryList{ filename: "list.json", modtime: time.Unix(0, 0), } ) 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 (list *entryList) Len() int { return len(list.v) } func (list *entryList) Swap(i, j int) { list.v[i], list.v[j] = list.v[j], list.v[i] } func (list *entryList) Less(i, j int) bool { return time.Time(list.v[i].Date).After(time.Time(list.v[j].Date)) } func (list *entryList) 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 *entryList) write() (err error) { data, err := json.MarshalIndent(list.v, "", " ") if err == nil { err = ioutil.WriteFile(list.filename, data, 0644) } if err == nil { list.modtime = time.Now() } return } func (list *entryList) read() (err error) { if list.modified() { list.v = make([]Entry, 0, len(list.v)) if data, err := ioutil.ReadFile(list.filename); err == nil { err = json.Unmarshal(data, &list.v) } } return } func (list *entryList) addEntry(entry *Entry) (err error) { list.mu.Lock() defer list.mu.Unlock() if err = list.read(); err == nil { list.v = append(list.v, *entry) sort.Sort(list) err = list.write() } return } func (list *entryList) readSliceList(oval int, nval int) (entries []Entry, err error) { list.mu.RLock() defer list.mu.RUnlock() if nval <= 0 { nval = list.Len() } if oval <= 0 { oval = 0 } err = list.read() if err == nil { if oval >= list.Len() { entries = nil } else if oval+nval >= list.Len() { entries = list.v[oval:] } else { entries = list.v[oval : oval+nval] } } return } func (list *entryList) deleteElement(pos int) (err error) { list.mu.Lock() defer list.mu.Unlock() if err = list.read(); err == nil { if pos >= 0 && pos < list.Len() { if pos == 0 { list.v = list.v[pos+1:] } else if pos == list.Len() - 1 { list.v = list.v[:pos] } else { list.v = append(list.v[:pos], list.v[pos+1:]...) } err = list.write() } } return } // TODO rutas relativas func (list *entryList) getFeed() (feed *feeds.Feed) { list.mu.RLock() defer list.mu.RUnlock() err := list.read() if err == nil { feed = &feeds.Feed { Title: "danoloan.es muse", Link: &feeds.Link { Href: "https://danoloan.es/muse" }, Description: "bitácora musical de danoloan", Author: &feeds.Author { Name: "danoloan", Email: "danolo@danoloan.es" }, } feed.Items = make([]*feeds.Item, 0, list.Len()) for _, elem := range list.v { item := &feeds.Item { Created: time.Time(elem.Date), } if elem.Track != "" { item.Title = elem.Track } else if elem.Album != "" { item.Title = elem.Album } item.Description = fmt.Sprintf("

%s - %s

\n", item.Title, elem.Artist) if elem.Cover != ""{ item.Description = fmt.Sprintf("%s\n", item.Description, elem.Cover) } if elem.Linkto != "" { item.Link = &feeds.Link{ Href: elem.Linkto } } else { item.Link = &feeds.Link{ Href: "https://danoloan.es/muse" } } feed.Items = append(feed.Items, item) } } return } 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 AddController(c echo.Context) (error) { var res Response var status int var err error entry := new(Entry) if err = c.Bind(entry); err != nil { res = Response{ false, err.Error() } status = http.StatusBadRequest } else if err = list.addEntry(entry); err != nil { res = Response{ false, "Failed to write into list" } status = http.StatusInternalServerError } else { res = Response{ true, "OK" } status = http.StatusOK } return c.JSON(status, res) } func DelController(c echo.Context) (error) { var res Response var status int var pos int pos, err := strconv.Atoi(c.QueryParam("p")) if err == nil { err = list.deleteElement(pos) } if err == nil { res = Response{ true, "OK" } status = http.StatusOK } else { res = Response{ false, "Failed to delete from list" } status = http.StatusInternalServerError } return c.JSON(status, res) } 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 RSSController(c echo.Context) (err error) { if blob, err := list.getFeed().ToRss(); err == nil { c.Blob(http.StatusOK, "application/xml", []byte(blob)) } return } func main() { e := echo.New() e.GET("/list", ListController) e.GET("/rss", RSSController) e.Static("/", "index.html") e.Static("/static", "static") admin := e.Group("/admin", middleware.BasicAuth(auth)) admin.POST("/add", AddController) admin.GET("/del", DelController) admin.Static("/", "admin/index.html") admin.Static("/static", "admin/static") e.Logger.Fatal(e.Start(":30303")) }