parent
f34d2d90d0
commit
5b76117cf8
@ -1,26 +1,15 @@ |
||||
# ---> 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 |
||||
*.test |
||||
*.prof |
||||
|
||||
# Binary |
||||
ribbit |
||||
# Database |
||||
ribbit.db |
||||
|
||||
# Example Feeds |
||||
rssfeed.xml |
||||
|
||||
# vim-restconsole |
||||
console.rest |
||||
|
@ -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) |
||||
} |
@ -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 |
||||
} |
@ -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) |
||||
} |
@ -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) |
||||
} |
@ -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 |
||||
} |
@ -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 |
||||
} |
@ -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 |
||||
} |
@ -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) |
||||
} |
@ -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) |
||||
} |
||||
} |
@ -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 |
||||
} |
||||
} |
||||
} |
@ -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) |
||||
} |
||||
} |
||||
} |
@ -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 |
||||
} |
@ -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 |
||||
} |
@ -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) |
||||
} |
@ -0,0 +1,51 @@ |
||||
package main |
||||
|
||||
import "time" |
||||
|
||||
const ( |
||||
RESULT_OK = 0 |
||||
RESULT_ERR = 1 |
||||
) |
||||