Initial Commit
This commit is contained in:
parent
f34d2d90d0
commit
5b76117cf8
31
.gitignore
vendored
31
.gitignore
vendored
@ -1,26 +1,15 @@
|
|||||||
# ---> Go
|
# ---> Go
|
||||||
# Compiled Object files, Static and Dynamic libs (Shared Objects)
|
|
||||||
*.o
|
|
||||||
*.a
|
|
||||||
*.so
|
|
||||||
|
|
||||||
# Folders
|
|
||||||
_obj
|
|
||||||
_test
|
|
||||||
|
|
||||||
# Architecture specific extensions/prefixes
|
|
||||||
*.[568vq]
|
|
||||||
[568vq].out
|
|
||||||
|
|
||||||
*.cgo1.go
|
|
||||||
*.cgo2.c
|
|
||||||
_cgo_defun.c
|
|
||||||
_cgo_gotypes.go
|
|
||||||
_cgo_export.*
|
|
||||||
|
|
||||||
_testmain.go
|
|
||||||
|
|
||||||
*.exe
|
*.exe
|
||||||
*.test
|
*.test
|
||||||
*.prof
|
*.prof
|
||||||
|
|
||||||
|
# Binary
|
||||||
|
ribbit
|
||||||
|
# Database
|
||||||
|
ribbit.db
|
||||||
|
|
||||||
|
# Example Feeds
|
||||||
|
rssfeed.xml
|
||||||
|
|
||||||
|
# vim-restconsole
|
||||||
|
console.rest
|
||||||
|
16
endpoints_api.go
Normal file
16
endpoints_api.go
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/gorilla/mux"
|
||||||
|
)
|
||||||
|
|
||||||
|
func handleApiCall(w http.ResponseWriter, req *http.Request) {
|
||||||
|
w.Header().Set("Content-Type", "application/json")
|
||||||
|
vars := mux.Vars(req)
|
||||||
|
_ = vars
|
||||||
|
|
||||||
|
fmt.Fprint(w, req.Header)
|
||||||
|
}
|
91
endpoints_api_comics.go
Normal file
91
endpoints_api_comics.go
Normal file
@ -0,0 +1,91 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/gorilla/mux"
|
||||||
|
)
|
||||||
|
|
||||||
|
func handleApiComicsCall(w http.ResponseWriter, req *http.Request) {
|
||||||
|
w.Header().Set("Content-Type", "application/json")
|
||||||
|
|
||||||
|
vars := mux.Vars(req)
|
||||||
|
cid, cidok := vars["cid"]
|
||||||
|
fn, fnok := vars["function"]
|
||||||
|
switch req.Method {
|
||||||
|
case "GET":
|
||||||
|
if !cidok {
|
||||||
|
handleApiListComics(w)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if !fnok {
|
||||||
|
handleApiShowComic(cid, w)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
switch fn {
|
||||||
|
case "search":
|
||||||
|
handleApiSearchComic(cid, w)
|
||||||
|
return
|
||||||
|
|
||||||
|
default:
|
||||||
|
http.Error(w, "You did a bad", 400)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
default:
|
||||||
|
http.Error(w, "You did a bad", 400)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func handleApiListComics(w http.ResponseWriter) {
|
||||||
|
var res []byte
|
||||||
|
var err error
|
||||||
|
if res, err = json.Marshal(m.Comics); err != nil {
|
||||||
|
http.Error(w, "I did a bad", 500)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
fmt.Fprint(w, string(res))
|
||||||
|
}
|
||||||
|
|
||||||
|
func handleApiSearchComic(nm string, w http.ResponseWriter) {
|
||||||
|
nm = strings.ToLower(nm)
|
||||||
|
var cs []Comic
|
||||||
|
for _, c := range m.Comics {
|
||||||
|
if strings.Contains(strings.ToLower(c.Name), nm) {
|
||||||
|
cs = append(cs, c)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var res []byte
|
||||||
|
var err error
|
||||||
|
if res, err = json.Marshal(cs); err != nil {
|
||||||
|
http.Error(w, "I did a bad", 500)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
fmt.Fprint(w, string(res))
|
||||||
|
}
|
||||||
|
|
||||||
|
func handleApiShowComic(cid string, w http.ResponseWriter) {
|
||||||
|
var err error
|
||||||
|
var c *Comic
|
||||||
|
pts := strings.Split(cid, ";")
|
||||||
|
if len(pts) != 2 {
|
||||||
|
http.Error(w, "You did a bad", 400)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if c, err = m.GetComic(pts[0], pts[1]); err != nil {
|
||||||
|
http.Error(w, err.Error(), 400)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var res []byte
|
||||||
|
if res, err = json.Marshal(c); err != nil {
|
||||||
|
http.Error(w, err.Error(), 400)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
fmt.Fprint(w, string(res))
|
||||||
|
return
|
||||||
|
}
|
122
endpoints_api_users.go
Normal file
122
endpoints_api_users.go
Normal file
@ -0,0 +1,122 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/gorilla/mux"
|
||||||
|
)
|
||||||
|
|
||||||
|
func handleApiUsersCall(w http.ResponseWriter, req *http.Request) {
|
||||||
|
w.Header().Set("Content-Type", "application/json")
|
||||||
|
|
||||||
|
vars := mux.Vars(req)
|
||||||
|
uid, uidok := vars["uid"]
|
||||||
|
fn, fnok := vars["function"]
|
||||||
|
switch req.Method {
|
||||||
|
case "GET":
|
||||||
|
if !uidok {
|
||||||
|
handleApiListUsers(w)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
switch fn {
|
||||||
|
default:
|
||||||
|
handleApiShowUser(uid, w)
|
||||||
|
}
|
||||||
|
|
||||||
|
case "POST", "PUT":
|
||||||
|
if !uidok {
|
||||||
|
http.Error(w, "You did a bad", 400)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if !fnok {
|
||||||
|
// Creating a new user
|
||||||
|
var u *User
|
||||||
|
var err error
|
||||||
|
if u, err = m.GetUserByName(uid); err == nil {
|
||||||
|
http.Error(w, "You did a bad", 400)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
u = NewUser(uid)
|
||||||
|
m.SaveUser(u)
|
||||||
|
m.LoadUsers()
|
||||||
|
handleApiShowUser(u.Uuid, w)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
switch fn {
|
||||||
|
case "slugs":
|
||||||
|
slug, slugok := vars["slug"]
|
||||||
|
if !slugok {
|
||||||
|
http.Error(w, "You did a bad", 400)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
handleApiSubUser(uid, slug, w)
|
||||||
|
|
||||||
|
default:
|
||||||
|
http.Error(w, "You did a bad", 400)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
default:
|
||||||
|
http.Error(w, "You did a bad", 400)
|
||||||
|
return
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func handleApiListUsers(w http.ResponseWriter) {
|
||||||
|
var res []byte
|
||||||
|
var err error
|
||||||
|
if res, err = json.Marshal(m.Users); err != nil {
|
||||||
|
http.Error(w, "I did a bad", 500)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
fmt.Fprint(w, string(res))
|
||||||
|
}
|
||||||
|
|
||||||
|
func handleApiShowUser(uid string, w http.ResponseWriter) {
|
||||||
|
var err error
|
||||||
|
var u *User
|
||||||
|
if u, err = m.GetUser(uid); err != nil {
|
||||||
|
http.Error(w, err.Error(), 400)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var res []byte
|
||||||
|
if res, err = json.Marshal(u); err != nil {
|
||||||
|
http.Error(w, err.Error(), 400)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
fmt.Fprint(w, string(res))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func handleApiSubUser(uid string, slug string, w http.ResponseWriter) {
|
||||||
|
fmt.Println("Sub User", uid, slug)
|
||||||
|
var u *User
|
||||||
|
var err error
|
||||||
|
if u, err = m.GetUser(uid); err != nil {
|
||||||
|
http.Error(w, err.Error(), 400)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
pts := strings.Split(slug, ";")
|
||||||
|
if len(pts) != 2 {
|
||||||
|
http.Error(w, err.Error(), 400)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
_, err = m.GetComic(pts[0], pts[1])
|
||||||
|
if err != nil {
|
||||||
|
http.Error(w, err.Error(), 400)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
u.SubSlugs = append(u.SubSlugs, slug)
|
||||||
|
err = m.SaveUser(u)
|
||||||
|
if err != nil {
|
||||||
|
http.Error(w, err.Error(), 400)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
m.LoadUsers()
|
||||||
|
handleApiShowUser(u.Uuid, w)
|
||||||
|
}
|
39
endpoints_public.go
Normal file
39
endpoints_public.go
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/gorilla/mux"
|
||||||
|
)
|
||||||
|
|
||||||
|
func handleRequest(w http.ResponseWriter, req *http.Request) {
|
||||||
|
vars := mux.Vars(req)
|
||||||
|
|
||||||
|
var fOk, uidOk bool
|
||||||
|
var f, uid string
|
||||||
|
f, fOk = vars["function"]
|
||||||
|
uid, uidOk = vars["uid"]
|
||||||
|
if !fOk || !uidOk {
|
||||||
|
// Not sure what you want me to do here, Hoss.
|
||||||
|
http.Error(w, "You did a bad", 400)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
switch f {
|
||||||
|
case "rss":
|
||||||
|
handleRssFeed(uid, w)
|
||||||
|
default:
|
||||||
|
http.Error(w, "You did a bad", 400)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func handleRssFeed(uid string, w http.ResponseWriter) {
|
||||||
|
w.Header().Set("Content-Type", "application/xml")
|
||||||
|
|
||||||
|
v, err := buildRssFeed(uid)
|
||||||
|
if err != nil {
|
||||||
|
http.Error(w, err.Error(), 400)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
fmt.Fprint(w, v)
|
||||||
|
}
|
68
helper_dilbert.go
Normal file
68
helper_dilbert.go
Normal file
@ -0,0 +1,68 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/PuerkitoBio/goquery"
|
||||||
|
)
|
||||||
|
|
||||||
|
func downloadDilbertList() []Comic {
|
||||||
|
var ret []Comic
|
||||||
|
ret = append(ret, *NewComic("dilbert", "Dilbert", "Scott Adams", "dilbert"))
|
||||||
|
return ret
|
||||||
|
}
|
||||||
|
|
||||||
|
func getDilbertRssItem(slug string) (string, error) {
|
||||||
|
desc, err := getDilbertFeedDesc(time.Now())
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
comic, err := m.GetComic(SRC_DILBERT, slug)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
desc = "<![CDATA[" + desc + "]]>"
|
||||||
|
ret := " <item>\n"
|
||||||
|
ret += " <title>" + comic.Name + "</title>\n"
|
||||||
|
ret += " <pubDate>" + comic.LastUpdate.Format(time.RFC1123Z) + "</pubDate>\n"
|
||||||
|
ret += " <guid>dilbert;" + slug + ";" + comic.LastUpdate.Format(time.RFC1123Z) + "</guid>\n"
|
||||||
|
ret += " <link>" + getDilbertComicUrl(time.Now()) + "</link>\n"
|
||||||
|
ret += " <description>" + desc + "</description>\n"
|
||||||
|
ret += " </item>\n"
|
||||||
|
return ret, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func getDilbertComicUrl(date time.Time) string {
|
||||||
|
return fmt.Sprintf(
|
||||||
|
"http://dilbert.com/strip/%4d-%02d-%02d",
|
||||||
|
date.Year(),
|
||||||
|
date.Month(),
|
||||||
|
date.Day(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
func getDilbertFeedDesc(date time.Time) (string, error) {
|
||||||
|
res, err := http.Get(getDilbertComicUrl(date))
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
defer res.Body.Close()
|
||||||
|
if res.StatusCode != 200 {
|
||||||
|
return "", errors.New(fmt.Sprintf("Status code error: %d %s", res.StatusCode, res.Status))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load the HTML document
|
||||||
|
doc, err := goquery.NewDocumentFromReader(res.Body)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
// Find the Picture
|
||||||
|
src, exists := doc.Find("img.img-comic").Attr("src")
|
||||||
|
if !exists {
|
||||||
|
return "", errors.New("Couldn't find image source")
|
||||||
|
}
|
||||||
|
return "<img src=\"" + src + "\" />", nil
|
||||||
|
}
|
98
helper_gocomics.go
Normal file
98
helper_gocomics.go
Normal file
@ -0,0 +1,98 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/PuerkitoBio/goquery"
|
||||||
|
)
|
||||||
|
|
||||||
|
func downloadGoComicsList() []Comic {
|
||||||
|
var ret []Comic
|
||||||
|
lstUrl := "http://www.gocomics.com/comics/a-to-z"
|
||||||
|
res, err := http.Get(lstUrl)
|
||||||
|
if err != nil {
|
||||||
|
return ret
|
||||||
|
}
|
||||||
|
defer res.Body.Close()
|
||||||
|
if res.StatusCode != 200 {
|
||||||
|
return ret
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load the HTML document
|
||||||
|
doc, err := goquery.NewDocumentFromReader(res.Body)
|
||||||
|
if err != nil {
|
||||||
|
return ret
|
||||||
|
}
|
||||||
|
doc.Find("a.amu-media-item-link").Each(func(i int, s *goquery.Selection) {
|
||||||
|
// For each item found, get the band and title
|
||||||
|
slug, exists := s.Attr("href")
|
||||||
|
if exists {
|
||||||
|
pts := strings.Split(slug, "/")
|
||||||
|
if len(pts) > 2 {
|
||||||
|
slug = pts[1]
|
||||||
|
}
|
||||||
|
name := s.Find("h4.media-heading").Text()
|
||||||
|
author := s.Find("h6.media-subheading").Text()
|
||||||
|
author = strings.TrimPrefix(author, "By ")
|
||||||
|
ret = append(ret, *NewComic(slug, name, author, "gocomics"))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
return ret
|
||||||
|
}
|
||||||
|
|
||||||
|
func getGoComicsRssItem(slug string) (string, error) {
|
||||||
|
desc, err := getGoComicsFeedDesc(slug, time.Now())
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
comic, err := m.GetComic(SRC_GOCOMICS, slug)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
desc = "<![CDATA[" + desc + "]]>"
|
||||||
|
ret := " <item>\n"
|
||||||
|
ret += " <title>" + comic.Name + "</title>\n"
|
||||||
|
ret += " <pubDate>" + comic.LastUpdate.Format(time.RFC1123Z) + "</pubDate>\n"
|
||||||
|
ret += " <guid>gocomics;" + slug + ";" + comic.LastUpdate.Format(time.RFC1123Z) + "</guid>\n"
|
||||||
|
ret += " <link>" + getGoComicsComicUrl(slug, time.Now()) + "</link>\n"
|
||||||
|
ret += " <description>" + desc + "</description>\n"
|
||||||
|
ret += " </item>\n"
|
||||||
|
return ret, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func getGoComicsComicUrl(slug string, date time.Time) string {
|
||||||
|
return fmt.Sprintf(
|
||||||
|
"http://www.gocomics.com/%s/%04d/%02d/%02d",
|
||||||
|
slug,
|
||||||
|
date.Year(),
|
||||||
|
date.Month(),
|
||||||
|
date.Day(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
func getGoComicsFeedDesc(slug string, date time.Time) (string, error) {
|
||||||
|
res, err := http.Get(getGoComicsComicUrl(slug, date))
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
defer res.Body.Close()
|
||||||
|
if res.StatusCode != 200 {
|
||||||
|
return "", errors.New(fmt.Sprintf("Status code error: %d %s", res.StatusCode, res.Status))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load the HTML document
|
||||||
|
doc, err := goquery.NewDocumentFromReader(res.Body)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
// Find the Picture
|
||||||
|
src, exists := doc.Find("picture.item-comic-image>img").Attr("src")
|
||||||
|
if !exists {
|
||||||
|
return "", errors.New("Couldn't find image source")
|
||||||
|
}
|
||||||
|
return "<img src=\"" + src + "\" />", nil
|
||||||
|
}
|
74
helper_xkcd.go
Normal file
74
helper_xkcd.go
Normal file
@ -0,0 +1,74 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/PuerkitoBio/goquery"
|
||||||
|
)
|
||||||
|
|
||||||
|
func downloadXKCDList() []Comic {
|
||||||
|
var ret []Comic
|
||||||
|
ret = append(ret, *NewComic("xkcd", "XKCD", "Randall Munroe", "xkcd"))
|
||||||
|
return ret
|
||||||
|
}
|
||||||
|
|
||||||
|
func getXKCDRssItem(slug string) (string, error) {
|
||||||
|
desc, err := getXKCDFeedDesc(time.Now())
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
comic, err := m.GetComic(SRC_XKCD, slug)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
desc = "<![CDATA[" + desc + "]]>"
|
||||||
|
ret := " <item>\n"
|
||||||
|
ret += " <title>" + comic.Name + "</title>\n"
|
||||||
|
ret += " <pubDate>" + comic.LastUpdate.Format(time.RFC1123Z) + "</pubDate>\n"
|
||||||
|
ret += " <guid>xkcd;" + slug + ";" + comic.LastUpdate.Format(time.RFC1123Z) + "</guid>\n"
|
||||||
|
ret += " <link>" + getXKCDComicUrl(time.Now()) + "</link>\n"
|
||||||
|
ret += " <description>" + desc + "</description>\n"
|
||||||
|
ret += " </item>\n"
|
||||||
|
return ret, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func getXKCDComicUrl(date time.Time) string {
|
||||||
|
// TODO: Actually make this work correctly
|
||||||
|
// Get the previous comic number
|
||||||
|
// and find the next one
|
||||||
|
return fmt.Sprintf(
|
||||||
|
"http://xkcd.com/",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
func getXKCDFeedDesc(date time.Time) (string, error) {
|
||||||
|
res, err := http.Get(getXKCDComicUrl(date))
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
defer res.Body.Close()
|
||||||
|
if res.StatusCode != 200 {
|
||||||
|
return "", errors.New(fmt.Sprintf("Status code error: %d %s", res.StatusCode, res.Status))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load the HTML document
|
||||||
|
doc, err := goquery.NewDocumentFromReader(res.Body)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
// Find the Picture
|
||||||
|
sel := doc.Find("div#comic>img")
|
||||||
|
src, exists := sel.Attr("src")
|
||||||
|
if !exists {
|
||||||
|
return "", errors.New("Couldn't find image source")
|
||||||
|
}
|
||||||
|
src = "https:" + src
|
||||||
|
title, exists := sel.Attr("title")
|
||||||
|
if !exists {
|
||||||
|
title = ""
|
||||||
|
}
|
||||||
|
return "<img src=\"" + src + "\" /><p>" + title + "</p>", nil
|
||||||
|
}
|
102
helpers.go
Normal file
102
helpers.go
Normal file
@ -0,0 +1,102 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
SRC_GOCOMICS = "gocomics"
|
||||||
|
SRC_DILBERT = "dilbert"
|
||||||
|
SRC_XKCD = "xkcd"
|
||||||
|
)
|
||||||
|
|
||||||
|
func downloadComicsList() []Comic {
|
||||||
|
var ret []Comic
|
||||||
|
ret = append(ret, downloadGoComicsList()...)
|
||||||
|
ret = append(ret, downloadDilbertList()...)
|
||||||
|
ret = append(ret, downloadXKCDList()...)
|
||||||
|
return ret
|
||||||
|
}
|
||||||
|
|
||||||
|
func getRssItem(source, slug string) (string, error) {
|
||||||
|
switch source {
|
||||||
|
case SRC_GOCOMICS:
|
||||||
|
return getGoComicsRssItem(slug)
|
||||||
|
case SRC_DILBERT:
|
||||||
|
return getDilbertRssItem(slug)
|
||||||
|
case SRC_XKCD:
|
||||||
|
return getXKCDRssItem(slug)
|
||||||
|
}
|
||||||
|
return "", errors.New("Invalid source")
|
||||||
|
}
|
||||||
|
|
||||||
|
func getComicUrl(source, slug string, dt time.Time) (string, error) {
|
||||||
|
switch source {
|
||||||
|
case SRC_GOCOMICS:
|
||||||
|
return getGoComicsComicUrl(slug, dt), nil
|
||||||
|
case SRC_DILBERT:
|
||||||
|
return getDilbertComicUrl(dt), nil
|
||||||
|
case SRC_XKCD:
|
||||||
|
return getXKCDComicUrl(dt), nil
|
||||||
|
}
|
||||||
|
return "", errors.New("Invalid source")
|
||||||
|
}
|
||||||
|
|
||||||
|
func getComicDesc(source, slug string, dt time.Time) (string, error) {
|
||||||
|
switch source {
|
||||||
|
case SRC_GOCOMICS:
|
||||||
|
return getGoComicsFeedDesc(slug, dt)
|
||||||
|
case SRC_DILBERT:
|
||||||
|
return getDilbertFeedDesc(dt)
|
||||||
|
case SRC_XKCD:
|
||||||
|
return getXKCDFeedDesc(dt)
|
||||||
|
}
|
||||||
|
return "", errors.New("Unknown Comic Source")
|
||||||
|
}
|
||||||
|
|
||||||
|
func buildRssFeed(uid string) (string, error) {
|
||||||
|
var usr *User
|
||||||
|
var err error
|
||||||
|
if usr, err = m.GetUser(uid); err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
output := []string{
|
||||||
|
"<?xml version=\"1.0\"?>",
|
||||||
|
"<rss version=\"2.0\">",
|
||||||
|
" <channel>",
|
||||||
|
" <title>BCW Comic Feed</title>",
|
||||||
|
" <link>http://comics.bullercodeworks.com/edit/" + uid + "</link>",
|
||||||
|
" <description>Comic feed for " + usr.Username + "</description>",
|
||||||
|
" <language>en-us</language>",
|
||||||
|
" <lastBuildDate>" + time.Now().Format(time.RFC1123) + "</lastBuildDate>",
|
||||||
|
" <ttl>40</ttl>",
|
||||||
|
}
|
||||||
|
|
||||||
|
//date := time.Now()
|
||||||
|
for _, slug := range usr.SubSlugs {
|
||||||
|
pts := strings.Split(slug, ";")
|
||||||
|
if len(pts) != 2 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if comic, err := m.GetComic(pts[0], pts[1]); err == nil {
|
||||||
|
output = append(output, comic.GetRssItem())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
output = append(output, []string{
|
||||||
|
" </channel>",
|
||||||
|
"</rss>",
|
||||||
|
}...)
|
||||||
|
return strings.Join(output, "\n"), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func addStringIfUnique(st string, sl []string) []string {
|
||||||
|
for i := range sl {
|
||||||
|
if sl[i] == st {
|
||||||
|
return sl
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return append(sl, st)
|
||||||
|
}
|
169
main.go
Normal file
169
main.go
Normal file
@ -0,0 +1,169 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"net/http"
|
||||||
|
"os"
|
||||||
|
"os/signal"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"syscall"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/gorilla/handlers"
|
||||||
|
"github.com/gorilla/mux"
|
||||||
|
"github.com/gorilla/sessions"
|
||||||
|
"github.com/justinas/alice"
|
||||||
|
)
|
||||||
|
|
||||||
|
const AppName = "ribbit"
|
||||||
|
const DbName = AppName + ".db"
|
||||||
|
|
||||||
|
var sessionStore *sessions.CookieStore
|
||||||
|
var r *mux.Router
|
||||||
|
var m *model
|
||||||
|
|
||||||
|
var scheduler *JobScheduler
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
var err error
|
||||||
|
if m, err = NewModel(); err != nil {
|
||||||
|
errorExit("Unable to initialize Model: " + err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(os.Args) > 2 {
|
||||||
|
key, val := os.Args[1], os.Args[2]
|
||||||
|
switch key {
|
||||||
|
case "--add-user-sub":
|
||||||
|
if len(os.Args) < 3 {
|
||||||
|
errorExit("Usage: --add-user-sub <username> <comic-slug>\nFor a list of slugs, use --list-comics")
|
||||||
|
}
|
||||||
|
slug := os.Args[3]
|
||||||
|
var u *User
|
||||||
|
if u, err = m.GetUserByName(val); err != nil {
|
||||||
|
errorExit("Couldn't find a user with the username " + val)
|
||||||
|
}
|
||||||
|
pts := strings.Split(slug, ";")
|
||||||
|
if len(pts) != 2 {
|
||||||
|
errorExit("Invalid slug given: " + slug)
|
||||||
|
}
|
||||||
|
_, err := m.GetComic(pts[0], pts[1])
|
||||||
|
if err != nil {
|
||||||
|
errorExit("Couldn't find comic with slug: " + slug)
|
||||||
|
}
|
||||||
|
fmt.Println(u.SubSlugs)
|
||||||
|
fmt.Println(slug)
|
||||||
|
u.SubSlugs = append(u.SubSlugs, slug)
|
||||||
|
fmt.Println(u.SubSlugs)
|
||||||
|
m.SaveUser(u)
|
||||||
|
done()
|
||||||
|
default:
|
||||||
|
errorExit("Unknown argument")
|
||||||
|
}
|
||||||
|
} else if len(os.Args) > 1 {
|
||||||
|
switch os.Args[1] {
|
||||||
|
case "--test":
|
||||||
|
//d, _ := getXKCDFeedDesc(time.Now())
|
||||||
|
fmt.Println(buildRssFeed("09af5fda-43dc-416e-93ad-cc050e0c098a"))
|
||||||
|
done()
|
||||||
|
case "--list-comics":
|
||||||
|
comics := m.GetAllComics()
|
||||||
|
for _, c := range comics {
|
||||||
|
fmt.Printf("[ %s;%s ] %s\n", c.Source, c.Slug, c.Name)
|
||||||
|
}
|
||||||
|
done()
|
||||||
|
case "--update-feeds":
|
||||||
|
fmt.Println("Updating User Feeds...")
|
||||||
|
m.UpdateAllUserFeeds()
|
||||||
|
fmt.Println("Done.")
|
||||||
|
done()
|
||||||
|
case "--update-comics":
|
||||||
|
fmt.Println("Updating the Comics List...")
|
||||||
|
comics := downloadComicsList()
|
||||||
|
for _, c := range comics {
|
||||||
|
fmt.Printf("Updating [ %s - %s, %s ]\n", c.Slug, c.Name, c.Artist)
|
||||||
|
m.SaveComic(&c)
|
||||||
|
}
|
||||||
|
m.saveChanges()
|
||||||
|
fmt.Println("Done.")
|
||||||
|
|
||||||
|
default:
|
||||||
|
errorExit("Unknown argument")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
r = mux.NewRouter()
|
||||||
|
r.StrictSlash(true)
|
||||||
|
//r.PathPrefix("/assets/").Handler(http.FileServer())
|
||||||
|
|
||||||
|
pub := r.PathPrefix("/").Subrouter()
|
||||||
|
pub.HandleFunc("/", handleRequest)
|
||||||
|
pub.HandleFunc("/api", handleApiCall)
|
||||||
|
pub.HandleFunc("/api/users", handleApiUsersCall)
|
||||||
|
pub.HandleFunc("/api/users/{uid}", handleApiUsersCall)
|
||||||
|
pub.HandleFunc("/api/users/{uid}/{function}", handleApiUsersCall)
|
||||||
|
pub.HandleFunc("/api/users/{uid}/{function}/{slug}", handleApiUsersCall)
|
||||||
|
pub.HandleFunc("/api/comics", handleApiComicsCall)
|
||||||
|
pub.HandleFunc("/api/comics/{cid}", handleApiComicsCall)
|
||||||
|
pub.HandleFunc("/api/comics/{cid}/{function}", handleApiComicsCall)
|
||||||
|
pub.HandleFunc("/{function}", handleRequest)
|
||||||
|
pub.HandleFunc("/{function}/{uid}", handleRequest)
|
||||||
|
pub.HandleFunc("/{function}/{uid}/{subfunc}", handleRequest)
|
||||||
|
pub.HandleFunc("/{function}/{uid}/{subfunc}/{slug}", handleRequest)
|
||||||
|
|
||||||
|
http.Handle("/", r)
|
||||||
|
chain := alice.New(loggingHandler).Then(r)
|
||||||
|
|
||||||
|
// Save changes to the DB every 5 minutes
|
||||||
|
go func() {
|
||||||
|
for {
|
||||||
|
if m.Site.LastSave.IsZero() || (time.Now().Day() != m.Site.LastSave.Day() && time.Now().Hour() == 2) {
|
||||||
|
fmt.Println("Updating GoComics List...")
|
||||||
|
comics := downloadComicsList()
|
||||||
|
for _, c := range comics {
|
||||||
|
fmt.Printf("Updating [ %s - %s, %s ]\n", c.Slug, c.Name, c.Artist)
|
||||||
|
m.SaveComic(&c)
|
||||||
|
}
|
||||||
|
fmt.Println("Updating User Feeds...")
|
||||||
|
m.UpdateAllUserFeeds()
|
||||||
|
m.saveChanges()
|
||||||
|
fmt.Println("Done.")
|
||||||
|
}
|
||||||
|
time.Sleep(time.Minute)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
// Set up a channel to intercept Ctrl+C for graceful shutdowns
|
||||||
|
c := make(chan os.Signal, 1)
|
||||||
|
signal.Notify(c, os.Interrupt, syscall.SIGTERM)
|
||||||
|
go func() {
|
||||||
|
<-c
|
||||||
|
// Save the changes when the app quits
|
||||||
|
fmt.Println("\nFinishing up...")
|
||||||
|
m.saveChanges()
|
||||||
|
os.Exit(0)
|
||||||
|
}()
|
||||||
|
|
||||||
|
fmt.Printf("Listening on port %d\n", m.Site.Port)
|
||||||
|
log.Fatal(http.ListenAndServe("127.0.0.1:"+strconv.Itoa(m.Site.Port), chain))
|
||||||
|
}
|
||||||
|
|
||||||
|
func loggingHandler(h http.Handler) http.Handler {
|
||||||
|
return handlers.LoggingHandler(os.Stdout, h)
|
||||||
|
}
|
||||||
|
|
||||||
|
func done() {
|
||||||
|
os.Exit(0)
|
||||||
|
}
|
||||||
|
|
||||||
|
func errorExit(msg string) {
|
||||||
|
fmt.Println(msg)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
func assertError(err error) {
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
}
|
100
model.go
Normal file
100
model.go
Normal file
@ -0,0 +1,100 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/br0xen/boltease"
|
||||||
|
)
|
||||||
|
|
||||||
|
type model struct {
|
||||||
|
bolt *boltease.DB
|
||||||
|
dbFileName string
|
||||||
|
|
||||||
|
Users []User
|
||||||
|
Comics []Comic
|
||||||
|
|
||||||
|
Site *SiteData
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewModel() (*model, error) {
|
||||||
|
var err error
|
||||||
|
m := new(model)
|
||||||
|
m.dbFileName = DbName
|
||||||
|
m.bolt, err = boltease.Create(m.dbFileName, 0600, nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = m.initDB(); err != nil {
|
||||||
|
return nil, errors.New("Unable to initialzie DB: " + err.Error())
|
||||||
|
}
|
||||||
|
m.LoadSiteData()
|
||||||
|
if err = m.LoadUsers(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if err = m.LoadComics(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return m, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *model) initDB() error {
|
||||||
|
var err error
|
||||||
|
if err = m.bolt.OpenDB(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer m.bolt.CloseDB()
|
||||||
|
|
||||||
|
if err = m.bolt.MkBucketPath([]string{"site"}); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err = m.bolt.MkBucketPath([]string{"users"}); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err = m.bolt.MkBucketPath([]string{"comics"}); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *model) saveChanges() {
|
||||||
|
m.Site.LastSave = time.Now()
|
||||||
|
m.SaveSite()
|
||||||
|
//m.SaveAllComics(m.Comics)
|
||||||
|
m.SaveAllUsers(m.Users)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *model) UpdateAllUserFeeds() {
|
||||||
|
var allSubs []string
|
||||||
|
for _, usr := range m.Users {
|
||||||
|
// Pull all user subs
|
||||||
|
for _, sub := range usr.SubSlugs {
|
||||||
|
allSubs = addStringIfUnique(sub, allSubs)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// So we have allSubs which contains all subscribed comics for all users
|
||||||
|
for _, sub := range allSubs {
|
||||||
|
fmt.Println("Updating Comic: " + sub)
|
||||||
|
pts := strings.Split(sub, ";")
|
||||||
|
if len(pts) != 2 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
c, err := m.GetComic(pts[0], pts[1])
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println(sub, ":", err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if err = c.Update(); err != nil {
|
||||||
|
fmt.Println(sub, ":", err.Error())
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if err = m.SaveComic(c); err != nil {
|
||||||
|
fmt.Println(sub, ":", err.Error())
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
194
model_comics.go
Normal file
194
model_comics.go
Normal file
@ -0,0 +1,194 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Comic struct {
|
||||||
|
Name string
|
||||||
|
Artist string
|
||||||
|
Slug string
|
||||||
|
Source string
|
||||||
|
Desc string
|
||||||
|
LastUpdate time.Time
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewComic(s, n, a, source string) *Comic {
|
||||||
|
return &Comic{
|
||||||
|
Name: n,
|
||||||
|
Artist: a,
|
||||||
|
Slug: s,
|
||||||
|
Source: source,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Comic) GetBucket() []string {
|
||||||
|
return []string{"comics", c.Source, c.Slug}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Comic) Update() error {
|
||||||
|
dt := time.Now()
|
||||||
|
desc, err := getComicDesc(c.Source, c.Slug, dt)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if desc == c.Desc {
|
||||||
|
return errors.New("Comic didn't change")
|
||||||
|
}
|
||||||
|
c.Desc = desc
|
||||||
|
c.LastUpdate = dt
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Comic) GetUrl(dt time.Time) string {
|
||||||
|
var v string
|
||||||
|
var e error
|
||||||
|
if v, e = getComicUrl(c.Source, c.Slug, dt); e != nil {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Comic) GetDesc(dt time.Time) string {
|
||||||
|
var v string
|
||||||
|
var e error
|
||||||
|
if v, e = getComicDesc(c.Source, c.Slug, dt); e != nil {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Comic) GetRssItem() string {
|
||||||
|
var v string
|
||||||
|
var e error
|
||||||
|
if v, e = getRssItem(c.Source, c.Slug); e != nil {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
|
||||||
|
// DB Function to save a comic
|
||||||
|
func (m *model) SaveComic(c *Comic) error {
|
||||||
|
var err error
|
||||||
|
if err = m.bolt.OpenDB(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer m.bolt.CloseDB()
|
||||||
|
bkt := c.GetBucket()
|
||||||
|
if err = m.bolt.MkBucketPath(bkt); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err = m.bolt.SetValue(bkt, "name", c.Name); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err = m.bolt.SetValue(bkt, "artist", c.Artist); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err = m.bolt.SetValue(bkt, "desc", c.Desc); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err = m.bolt.SetTimestamp(bkt, "lastupdate", c.LastUpdate); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// DB Function to get a comic
|
||||||
|
func (m *model) GetComic(source, slug string) (*Comic, error) {
|
||||||
|
var err error
|
||||||
|
if err = m.bolt.OpenDB(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer m.bolt.CloseDB()
|
||||||
|
ret := new(Comic)
|
||||||
|
ret.Source = source
|
||||||
|
ret.Slug = slug
|
||||||
|
bkt := ret.GetBucket()
|
||||||
|
if ret.Name, err = m.bolt.GetValue(bkt, "name"); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if ret.Artist, err = m.bolt.GetValue(bkt, "artist"); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if ret.Desc, err = m.bolt.GetValue(bkt, "desc"); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if ret.LastUpdate, err = m.bolt.GetTimestamp(bkt, "lastupdate"); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return ret, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load all comics into the model
|
||||||
|
func (m *model) LoadComics() error {
|
||||||
|
m.Comics = m.GetAllComics()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Save all comics to the DB
|
||||||
|
func (m *model) SaveAllComics(comics []Comic) {
|
||||||
|
var err error
|
||||||
|
if err = m.bolt.OpenDB(); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer m.bolt.CloseDB()
|
||||||
|
for i := range comics {
|
||||||
|
m.SaveComic(&comics[i])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get all comics from the db
|
||||||
|
func (m *model) GetAllComics() []Comic {
|
||||||
|
var ret []Comic
|
||||||
|
var err error
|
||||||
|
if err = m.bolt.OpenDB(); err != nil {
|
||||||
|
return ret
|
||||||
|
}
|
||||||
|
defer m.bolt.CloseDB()
|
||||||
|
var srcs []string
|
||||||
|
bkt := []string{"comics"}
|
||||||
|
if srcs, err = m.bolt.GetBucketList(bkt); err != nil {
|
||||||
|
return ret
|
||||||
|
}
|
||||||
|
for _, src := range srcs {
|
||||||
|
srcBkt := append(bkt, src)
|
||||||
|
var slugs []string
|
||||||
|
if slugs, err = m.bolt.GetBucketList(srcBkt); err != nil {
|
||||||
|
return ret
|
||||||
|
}
|
||||||
|
for _, slg := range slugs {
|
||||||
|
c, err := m.GetComic(src, slg)
|
||||||
|
if err == nil {
|
||||||
|
ret = append(ret, *c)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ret
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete a comic from the DB
|
||||||
|
func (m *model) DeleteComic(slug string) error {
|
||||||
|
var err error
|
||||||
|
if err = m.bolt.OpenDB(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer m.bolt.CloseDB()
|
||||||
|
|
||||||
|
return m.bolt.DeleteBucket([]string{"comics"}, slug)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *model) RemoveMissingComics(comics []Comic) {
|
||||||
|
for _, c := range m.Comics {
|
||||||
|
var fnd bool
|
||||||
|
for _, nc := range comics {
|
||||||
|
if nc.Slug == c.Slug {
|
||||||
|
fnd = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !fnd {
|
||||||
|
m.DeleteComic(c.Slug)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
86
model_site.go
Normal file
86
model_site.go
Normal file
@ -0,0 +1,86 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import "time"
|
||||||
|
|
||||||
|
type SiteData struct {
|
||||||
|
Title string
|
||||||
|
Port int
|
||||||
|
SessionName string
|
||||||
|
ServerDir string
|
||||||
|
SessionSecret string
|
||||||
|
|
||||||
|
LastSave time.Time
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewSiteData() *SiteData {
|
||||||
|
ret := new(SiteData)
|
||||||
|
ret.Title = "BCW Comic Feed"
|
||||||
|
ret.Port = 8080
|
||||||
|
ret.SessionName = "bcw-comic-feed"
|
||||||
|
ret.ServerDir = "./"
|
||||||
|
return ret
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *model) LoadSiteData() {
|
||||||
|
m.Site = m.GetSite()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *model) SaveSite() error {
|
||||||
|
var err error
|
||||||
|
if err = m.bolt.OpenDB(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer m.bolt.CloseDB()
|
||||||
|
bkt := []string{"site"}
|
||||||
|
if err = m.bolt.SetValue(bkt, "title", m.Site.Title); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err = m.bolt.SetInt(bkt, "port", m.Site.Port); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err = m.bolt.SetValue(bkt, "session-name", m.Site.SessionName); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err = m.bolt.SetValue(bkt, "session-secret", m.Site.SessionSecret); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err = m.bolt.SetValue(bkt, "server-dir", m.Site.ServerDir); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err = m.bolt.SetTimestamp(bkt, "last-save", m.Site.LastSave); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *model) GetSite() *SiteData {
|
||||||
|
s := NewSiteData()
|
||||||
|
var err error
|
||||||
|
if err = m.bolt.OpenDB(); err != nil {
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
defer m.bolt.CloseDB()
|
||||||
|
bkt := []string{"site"}
|
||||||
|
var wrkStr string
|
||||||
|
var wrkInt int
|
||||||
|
var wrkTm time.Time
|
||||||
|
if wrkStr, err = m.bolt.GetValue(bkt, "title"); err == nil {
|
||||||
|
s.Title = wrkStr
|
||||||
|
}
|
||||||
|
if wrkInt, err = m.bolt.GetInt(bkt, "port"); err == nil {
|
||||||
|
s.Port = wrkInt
|
||||||
|
}
|
||||||
|
if wrkStr, err = m.bolt.GetValue(bkt, "session-name"); err == nil {
|
||||||
|
s.SessionName = wrkStr
|
||||||
|
}
|
||||||
|
if wrkStr, err = m.bolt.GetValue(bkt, "session-secret"); err == nil {
|
||||||
|
s.SessionSecret = wrkStr
|
||||||
|
}
|
||||||
|
if wrkStr, err = m.bolt.GetValue(bkt, "server-dir"); err == nil {
|
||||||
|
s.ServerDir = wrkStr
|
||||||
|
}
|
||||||
|
if wrkTm, err = m.bolt.GetTimestamp(bkt, "last-save"); err == nil {
|
||||||
|
s.LastSave = wrkTm
|
||||||
|
}
|
||||||
|
return s
|
||||||
|
}
|
143
model_user.go
Normal file
143
model_user.go
Normal file
@ -0,0 +1,143 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/pborman/uuid"
|
||||||
|
)
|
||||||
|
|
||||||
|
type User struct {
|
||||||
|
Username string `json:username`
|
||||||
|
Uuid string `json:uuid`
|
||||||
|
SubSlugs []string `json:subs`
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewUser(un string) *User {
|
||||||
|
u := new(User)
|
||||||
|
u.Username = un
|
||||||
|
u.Uuid = uuid.New()
|
||||||
|
return u
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *User) UpdateFeed() error {
|
||||||
|
for _, slug := range u.SubSlugs {
|
||||||
|
pts := strings.Split(slug, ";")
|
||||||
|
if len(pts) != 2 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if comic, err := m.GetComic(pts[0], pts[1]); err == nil {
|
||||||
|
comic.Update()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *model) SaveUser(u *User) error {
|
||||||
|
var err error
|
||||||
|
if err = m.bolt.OpenDB(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer m.bolt.CloseDB()
|
||||||
|
bkt := []string{"users", u.Uuid}
|
||||||
|
if err = m.bolt.MkBucketPath(bkt); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err = m.bolt.SetValue(bkt, "username", u.Username); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
var newSubs []string
|
||||||
|
for _, v := range u.SubSlugs {
|
||||||
|
if strings.TrimSpace(v) != "" {
|
||||||
|
newSubs = append(newSubs, v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
slugs := strings.Join(newSubs, ",")
|
||||||
|
if err = m.bolt.SetValue(bkt, "subs", slugs); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *model) GetUser(uid string) (*User, error) {
|
||||||
|
var err error
|
||||||
|
if err = m.bolt.OpenDB(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer m.bolt.CloseDB()
|
||||||
|
ret := new(User)
|
||||||
|
bkt := []string{"users", uid}
|
||||||
|
ret.Uuid = uid
|
||||||
|
if ret.Username, err = m.bolt.GetValue(bkt, "username"); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
var subs string
|
||||||
|
if subs, err = m.bolt.GetValue(bkt, "subs"); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
ret.SubSlugs = strings.Split(subs, ",")
|
||||||
|
return ret, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *model) SaveAllUsers(users []User) {
|
||||||
|
var err error
|
||||||
|
if err = m.bolt.OpenDB(); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer m.bolt.CloseDB()
|
||||||
|
for i := range users {
|
||||||
|
m.SaveUser(&users[i])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *model) LoadUsers() error {
|
||||||
|
m.Users = m.GetAllUsers()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *model) GetAllUsers() []User {
|
||||||
|
var err error
|
||||||
|
var ret []User
|
||||||
|
if err = m.bolt.OpenDB(); err != nil {
|
||||||
|
return ret
|
||||||
|
}
|
||||||
|
defer m.bolt.CloseDB()
|
||||||
|
|
||||||
|
uids := m.GetUserIdList()
|
||||||
|
for _, uid := range uids {
|
||||||
|
if u, e := m.GetUser(uid); e == nil {
|
||||||
|
ret = append(ret, *u)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ret
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *model) GetUserByName(nm string) (*User, error) {
|
||||||
|
var err error
|
||||||
|
if err = m.bolt.OpenDB(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer m.bolt.CloseDB()
|
||||||
|
usrids := m.GetUserIdList()
|
||||||
|
for i := range usrids {
|
||||||
|
bkt := []string{"users", usrids[i]}
|
||||||
|
var tstuname string
|
||||||
|
if tstuname, _ = m.bolt.GetValue(bkt, "username"); tstuname == nm {
|
||||||
|
// Found it
|
||||||
|
return m.GetUser(usrids[i])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil, errors.New("No user with username " + nm + " found")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *model) GetUserIdList() []string {
|
||||||
|
var ret []string
|
||||||
|
var err error
|
||||||
|
if err = m.bolt.OpenDB(); err != nil {
|
||||||
|
return ret
|
||||||
|
}
|
||||||
|
defer m.bolt.CloseDB()
|
||||||
|
bkt := []string{"users"}
|
||||||
|
ret, _ = m.bolt.GetBucketList(bkt)
|
||||||
|
return ret
|
||||||
|
}
|
54
page_session.go
Normal file
54
page_session.go
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/gorilla/sessions"
|
||||||
|
)
|
||||||
|
|
||||||
|
// This is basically a convenience struct for
|
||||||
|
// easier session management (hopefully ;)
|
||||||
|
type pageSession struct {
|
||||||
|
session *sessions.Session
|
||||||
|
req *http.Request
|
||||||
|
w http.ResponseWriter
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *pageSession) getStringValue(key string) (string, error) {
|
||||||
|
val := p.session.Values[key]
|
||||||
|
var retVal string
|
||||||
|
var ok bool
|
||||||
|
if retVal, ok = val.(string); !ok {
|
||||||
|
return "", fmt.Errorf("Unable to create string from %s", key)
|
||||||
|
}
|
||||||
|
return retVal, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *pageSession) setStringValue(key, val string) {
|
||||||
|
p.session.Values[key] = val
|
||||||
|
p.session.Save(p.req, p.w)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *pageSession) setFlashMessage(msg, status string) {
|
||||||
|
p.setStringValue("flash_message", msg)
|
||||||
|
p.setStringValue("flash_status", status)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *pageSession) getFlashMessage() (string, string) {
|
||||||
|
var err error
|
||||||
|
var msg, status string
|
||||||
|
if msg, err = p.getStringValue("flash_message"); err != nil {
|
||||||
|
return "", "hidden"
|
||||||
|
}
|
||||||
|
if status, err = p.getStringValue("flash_status"); err != nil {
|
||||||
|
return "", "hidden"
|
||||||
|
}
|
||||||
|
p.setFlashMessage("", "hidden")
|
||||||
|
return msg, status
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *pageSession) expireSession() {
|
||||||
|
p.session.Options.MaxAge = -1
|
||||||
|
p.session.Save(p.req, p.w)
|
||||||
|
}
|
51
scheduler.go
Normal file
51
scheduler.go
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import "time"
|
||||||
|
|
||||||
|
const (
|
||||||
|
RESULT_OK = 0
|
||||||
|
RESULT_ERR = 1
|
||||||
|
)
|
||||||
|
|
||||||
|
type JobScheduler struct {
|
||||||
|
Jobs []Job
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewJobScheduler() *JobScheduler {
|
||||||
|
return new(JobScheduler)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *JobScheduler) AddJob(j *Job) {
|
||||||
|
s.Jobs = append(s.Jobs, *j)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *JobScheduler) Run() {
|
||||||
|
for _, j := range s.Jobs {
|
||||||
|
if j.ShouldRunNow() {
|
||||||
|
j.Run()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type Job struct {
|
||||||
|
freq time.Duration
|
||||||
|
lastRun time.Time
|
||||||
|
lastResult int
|
||||||
|
action func() int
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewJob(a func() int, f time.Duration) *Job {
|
||||||
|
j := new(Job)
|
||||||
|
j.action = a
|
||||||
|
j.freq = f
|
||||||
|
return j
|
||||||
|
}
|
||||||
|
|
||||||
|
func (j *Job) ShouldRunNow() bool {
|
||||||
|
return time.Now().After(j.lastRun.Add(j.freq))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (j *Job) Run() {
|
||||||
|
j.lastResult = j.action()
|
||||||
|
j.lastRun = time.Now()
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user