aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--config/config.go5
-rw-r--r--main.go54
-rw-r--r--models/db.go55
-rw-r--r--web/rice-box.go8
-rw-r--r--web/web.go2
5 files changed, 98 insertions, 26 deletions
diff --git a/config/config.go b/config/config.go
index 9a6df3a..91ffd87 100644
--- a/config/config.go
+++ b/config/config.go
@@ -7,9 +7,8 @@ import (
)
type Settings struct {
- DBDriver string `json:"db_driver"`
- DBServer string `json:"db"`
- WebServer string `json:"web"`
+ DBFile string `json:"db"`
+ Port int `json:"web"`
Username string `json:"username"`
DigestPassword string `json:"password"`
ProxyImages bool `json:"proxy_images"`
diff --git a/main.go b/main.go
index afa34e7..56b0ba7 100644
--- a/main.go
+++ b/main.go
@@ -12,42 +12,64 @@ import (
)
func main() {
- var serve, update, verbose bool
- var configFile, newFeed, export string
+ var help, update, verbose, proxyImages bool
+ var dbfile, newFeed, export, password string
+ var port int
- flag.StringVarP(&configFile, "config", "c", "config.json", "`configuration` file")
- flag.BoolVarP(&update, "update", "u", false, "fetch feeds and store them in the database")
- flag.BoolVarP(&serve, "serve", "s", false, "run http server")
- flag.BoolVarP(&verbose, "verbose", "v", false, "verbose output")
+ // dbfile
+ // port
+ // proxyImages
+ // username
+ // password
+
+ flag.BoolVarP(&help, "help", "h", false, "print usage information")
+
+ flag.BoolVarP(&update, "update", "u", false, "fetch feeds and store new items")
flag.StringVarP(&newFeed, "add", "a", "", "add the feed at URL `http://example.com/rss.xml`")
- flag.StringVarP(&export, "export", "x", "", "export feeds as `text`, json or opml")
+ flag.StringVarP(&export, "export", "x", "", "export feed. format required: text, json or opml")
+
+ // flag.BoolVarP(&serve, "serve", "s", false, "run neko app by starting HTTP server")
+
+ flag.StringVarP(&dbfile, "db", "d", "neko.db", "sqlite database file")
+ flag.StringVarP(&dbfile, "password", "p", "", "password to access web interface")
+
+ flag.IntVarP(&port, "http", "s", 4994, "HTTP port to serve on")
+ flag.BoolVarP(&verbose, "verbose", "v", true, "verbose output")
+ flag.BoolVarP(&proxyImages, "imageproxy", "i", false, "rewrite and proxy all image requests for privacy (experimental)")
+
flag.Parse()
- // no command
- if !update && !serve && newFeed == "" && export == "" {
+ if help {
flag.Usage()
return
}
- config.Read(configFile)
- models.InitDB()
vlog.VERBOSE = verbose
+ config.Config.DBFile = dbfile
+ config.Config.Port = port
+ config.Config.ProxyImages = proxyImages
+ config.Config.DigestPassword = password
+
+ models.InitDB()
if update {
vlog.Printf("starting crawl\n")
crawler.Crawl()
- }
- if serve {
- vlog.Printf("starting web server at %s\n",
- config.Config.WebServer)
- web.Serve()
+ return
}
if newFeed != "" {
vlog.Printf("creating new feed\n")
feed.NewFeed(newFeed)
+ return
}
if export != "" {
vlog.Printf("feed export\n")
exporter.ExportFeeds(export)
+ return
}
+
+ vlog.Printf("starting web server at 127.0.0.1:%d\n",
+ config.Config.Port)
+ web.Serve()
+
}
diff --git a/models/db.go b/models/db.go
index 31d8bdd..d5bc7dc 100644
--- a/models/db.go
+++ b/models/db.go
@@ -5,8 +5,8 @@ package models
import (
"adammathes.com/neko/config"
+ "adammathes.com/neko/vlog"
"database/sql"
- _ "github.com/go-sql-driver/mysql"
_ "github.com/mattn/go-sqlite3"
"log"
)
@@ -16,7 +16,9 @@ var DB *sql.DB
func InitDB() {
var err error
// DB, err = sql.Open("mysql", dataSourceName)
- DB, err = sql.Open(config.Config.DBDriver, config.Config.DBServer)
+ vlog.Printf("using sqlite3 db file %s\n", config.Config.DBFile)
+
+ DB, err = sql.Open("sqlite3", config.Config.DBFile)
if err != nil {
log.Panic(err)
}
@@ -24,4 +26,53 @@ func InitDB() {
if err = DB.Ping(); err != nil {
log.Panic(err)
}
+
+ schema := `
+CREATE TABLE IF NOT EXISTS feed (
+ id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
+ url varchar(100) NOT NULL UNIQUE,
+ web_url varchar(255) NOT NULL DEFAULT '',
+ title varchar(255) NOT NULL DEFAULT '',
+ last_updated timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ category varchar(255) NOT NULL DEFAULT 'uncategorized'
+);
+CREATE INDEX feed_url ON feed (url);
+CREATE INDEX feed_category ON feed (category);
+
+CREATE TABLE IF NOT EXISTS item (
+ id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
+ feed_id int(11) NOT NULL,
+ title text NOT NULL DEFAULT '',
+ url varchar(255) NOT NULL UNIQUE,
+ description text NOT NULL DEFAULT '',
+ publish_date timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ read_state tinyint(1) NOT NULL DEFAULT '0',
+ starred tinyint(1) NOT NULL DEFAULT '0',
+ full_content text NOT NULL DEFAULT '',
+ header_image text NOT NULL DEFAULT '',
+ CONSTRAINT item_ibfk_1 FOREIGN KEY (feed_id) REFERENCES feed(id) ON DELETE CASCADE
+);
+CREATE INDEX item_url ON item (url);
+CREATE INDEX item_publish_date ON item (publish_date);
+CREATE INDEX item_feed_id ON item (feed_id);
+CREATE INDEX item_read_state ON item (read_state);
+
+CREATE VIRTUAL TABLE fts_item using fts4(content="item", title, url, description);
+CREATE TRIGGER item_bu BEFORE UPDATE ON item BEGIN
+ DELETE FROM fts_item WHERE docid=old.rowid;
+END;
+CREATE TRIGGER item_bd BEFORE DELETE ON item BEGIN
+ DELETE FROM fts_item WHERE docid=old.rowid;
+END;
+CREATE TRIGGER item_au AFTER UPDATE ON item BEGIN
+ INSERT INTO fts_item(docid, title, url, description) VALUES(new.rowid, new.title, new.url, new.description);
+END;
+CREATE TRIGGER item_ai AFTER INSERT ON item BEGIN
+ INSERT INTO fts_item(docid, title, url, description) VALUES(new.rowid, new.title, new.url, new.description);
+END;
+
+ INSERT INTO fts_item(fts_item) VALUES('rebuild');
+`
+
+ _, _ = DB.Exec(schema)
}
diff --git a/web/rice-box.go b/web/rice-box.go
index d82fd72..bb8b8b5 100644
--- a/web/rice-box.go
+++ b/web/rice-box.go
@@ -60,8 +60,8 @@ func init() {
}
filec := &embedded.EmbeddedFile{
Filename: "ui.html",
- FileModTime: time.Unix(1529168764, 0),
- Content: string("<!DOCTYPE html>\n<html>\n <head>\n <title>neko rss mode</title>\n <link rel=\"stylesheet\" href=\"/static/style.css\" />\n <script src=\"/static/jquery-3.3.1.min.js\"></script>\n <script src=\"/static/jquery.tmpl.min.js\"></script>\n <script src=\"/static/underscore-1.8.3.min.js\"></script>\n <script src=\"/static/backbone-1.3.3.min.js\"></script>\n <script>\n PUBLIC_VERSION = false;\n </script>\n <script src=\"/static/ui.js\"></script>\n <meta name=\"viewport\" content=\"width=device-width,height=device-height, initial-scale=1, maximum-scale=1\" />\n <base target=\"_blank\">\n </head>\n <body>\n <h1 class=\"logo\" onclick=\"$('#filters').toggleClass('hidden');\">🐱</h1>\n\n <div id=\"filters\">\n\n <div id=\"controls\"></div>\n <h4 onclick=\"$('#tags').toggle();\">Tags</h4> \n <ul id=\"tags\" style=\"display: none;\">\n </ul>\n \n <h4 onclick=\"$('#feeds').toggle();\">Feeds</h4> \n <ul id=\"feeds\" style=\"display: none;\">\n </ul>\n </div>\n\n </div>\n\n <div id=\"c\">\n <div id=\"items\">\n </div> \n </div>\n \n <script id=\"item_template\" type=\"text/jqtmp\">\n <h2><a class=\"i\" id=\"i_${item_id}\" href=\"${item.url}\">${item.title }</a> \n <span class={{if item.starred}}\"unstar\"{{else}}\"star\"{{/if}}>★</span>\n </h2>\n <p class=\"dateline\" style=\"clear: both;\">\n <a href=\"${item.feed_url}\">${item.feed_title}</a> | <a href=\"${item.url}\">${item.p_url}</a>\n | ${item.feed_category} |\n <span class=\"full\">{{if item.full}}hide{{else}}scrape{{/if}} full text</span>\n \n </p>\n {{if item.header_image}}\n <div class=\"img\"><img src=\"${item.header_image}\" /></div>\n {{/if}}\n <div class=\"description\">\n {{if item.full}}\n {{html item.full_content}}\n {{else}}\n {{html item.description}}\n {{/if}}\n </div>\n </script>\n\n <script id=\"tag_template\" type=\"text/jqtmp\">\n {{if tag.selected}}<b>{{/if}}\n ${tag.title}\n {{if tag.selected}}</b>{{/if}}\n </script>\n\n <script id=\"feed_template\" type=\"text/jqtmp\">\n {{if feed.selected}}<b>{{/if}}\n <span class=\"txt\">\n {{if feed.title}}\n ${feed.title}\n {{else}}\n ${feed.url}\n {{/if}}\n\n </span>\n <span class=\"edit\">[e]</span>\n <span class=\"delete\">[x]</span>\n {{if feed.selected}}</b>{{/if}}\n </script>\n\n <script id=\"controls_template\" type=\"text/jqtmp\">\n <ul>\n <li>\n <a {{if app.unreadFilter}}style=\"font-weight: bold;\"{{/if}} \n class=\"unread_filter\">unread</a>\n </li>\n <li>\n <a \n {{if app.allFilter}}style=\"font-weight: bold;\"{{/if}} \n class=\"all_filter\">all</a> \n </li>\n <li>\n <a {{if app.starredFilter}}style=\"font-weight: bold;\"{{/if}}\n class=\"starred_filter\">★ starred</a>\n </li>\n <li>\n <button class=\"new_feed\"> + New </button>\n </li>\n <li>\n <input id=\"search\" type=\"search\" /><button class=\"search_go\">search</button>\n </li>\n\t\t<li>\n\t\t</li>\n\n\t </ul>\n </script> \n \n</body>\n</html> \n"),
+ FileModTime: time.Unix(1529169644, 0),
+ Content: string("<!DOCTYPE html>\n<html>\n <head>\n <title>neko rss mode</title>\n <link rel=\"stylesheet\" href=\"/static/style.css\" />\n <script src=\"/static/jquery-3.3.1.min.js\"></script>\n <script src=\"/static/jquery.tmpl.min.js\"></script>\n <script src=\"/static/underscore-1.8.3.min.js\"></script>\n <script src=\"/static/backbone-1.3.3.min.js\"></script>\n <script>\n PUBLIC_VERSION = false;\n </script>\n <script src=\"/static/ui.js\"></script>\n <meta name=\"viewport\" content=\"width=device-width,height=device-height, initial-scale=1, maximum-scale=1\" />\n <base target=\"_blank\">\n </head>\n <body>\n <h1 class=\"logo\" onclick=\"$('#filters').toggleClass('hidden');\">🐱</h1>\n\n <div id=\"filters\">\n\n <div id=\"controls\"></div>\n <h4 onclick=\"$('#tags').toggle();\">Tags</h4> \n <ul id=\"tags\" style=\"display: none;\">\n </ul>\n \n <h4 onclick=\"$('#feeds').toggle();\">Feeds</h4> \n <ul id=\"feeds\" style=\"display: none;\">\n </ul>\n </div>\n\n </div>\n\n <div id=\"c\">\n <div id=\"items\">\n </div> \n </div>\n \n <script id=\"item_template\" type=\"text/jqtmp\">\n <h2><a class=\"i\" id=\"i_${item_id}\" href=\"${item.url}\">${item.title }</a> \n <span class={{if item.starred}}\"unstar\"{{else}}\"star\"{{/if}}>★</span>\n </h2>\n <p class=\"dateline\" style=\"clear: both;\">\n <a href=\"${item.feed_url}\">${item.feed_title}</a> | <a href=\"${item.url}\">${item.p_url}</a>\n | ${item.feed_category} |\n <span class=\"full\">{{if item.full}}hide{{else}}scrape{{/if}} full text</span>\n \n </p>\n {{if item.header_image}}\n <div class=\"img\"><img src=\"${item.header_image}\" /></div>\n {{/if}}\n <div class=\"description\">\n {{if item.full}}\n {{html item.full_content}}\n {{else}}\n {{html item.description}}\n {{/if}}\n </div>\n </script>\n\n <script id=\"tag_template\" type=\"text/jqtmp\">\n {{if tag.selected}}<b>{{/if}}\n ${tag.title}\n {{if tag.selected}}</b>{{/if}}\n </script>\n\n <script id=\"feed_template\" type=\"text/jqtmp\">\n {{if feed.selected}}<b>{{/if}}\n <span class=\"txt\">\n {{if feed.title}}\n ${feed.title}\n {{else}}\n ${feed.url}\n {{/if}}\n\n </span>\n <span class=\"edit\">[e]</span>\n <span class=\"delete\">[x]</span>\n {{if feed.selected}}</b>{{/if}}\n </script>\n\n <script id=\"controls_template\" type=\"text/jqtmp\">\n <ul>\n <li>\n <a {{if app.unreadFilter}}style=\"font-weight: bold;\"{{/if}} \n class=\"unread_filter\">unread</a>\n </li>\n <li>\n <a \n {{if app.allFilter}}style=\"font-weight: bold;\"{{/if}} \n class=\"all_filter\">all</a> \n </li>\n <li>\n <a {{if app.starredFilter}}style=\"font-weight: bold;\"{{/if}}\n class=\"starred_filter\">★ starred</a>\n </li>\n <li>\n <button class=\"new_feed\"> + new </button>\n </li>\n <li>\n <input id=\"search\" type=\"search\" /><button class=\"search_go\">search</button>\n </li>\n\t\t<li>\n\t\t</li>\n\n\t </ul>\n </script> \n \n</body>\n</html> \n"),
}
filed := &embedded.EmbeddedFile{
Filename: "ui.js",
@@ -82,7 +82,7 @@ func init() {
// define dirs
dir1 := &embedded.EmbeddedDir{
Filename: "",
- DirModTime: time.Unix(1529169528, 0),
+ DirModTime: time.Unix(1529169644, 0),
ChildFiles: []*embedded.EmbeddedFile{
file2, // ".DS_Store"
file3, // "backbone-1.3.3.min.js"
@@ -108,7 +108,7 @@ func init() {
// register embeddedBox
embedded.RegisterEmbeddedBox(`../static`, &embedded.EmbeddedBox{
Name: `../static`,
- Time: time.Unix(1529169528, 0),
+ Time: time.Unix(1529169644, 0),
Dirs: map[string]*embedded.EmbeddedDir{
"": dir1,
},
diff --git a/web/web.go b/web/web.go
index 1e1f628..8b3b19d 100644
--- a/web/web.go
+++ b/web/web.go
@@ -295,5 +295,5 @@ func Serve() {
http.HandleFunc("/", AuthWrap(indexHandler))
- log.Fatal(http.ListenAndServe(config.Config.WebServer, nil))
+ log.Fatal(http.ListenAndServe(":"+strconv.Itoa(config.Config.Port), nil))
}