Compare commits
7 Commits
Author | SHA1 | Date |
---|---|---|
|
9476bf1e74 | |
|
94221611c1 | |
|
b45d101657 | |
|
62366427fb | |
|
e45be9fbd3 | |
|
765659b3ee | |
|
de09e150b2 |
54
README.md
54
README.md
|
@ -20,55 +20,53 @@ sudo apt-get install postgres postgresql-contrib
|
||||||
|
|
||||||
Setup postgres to handle a local connection for transmet in pg_hba.conf
|
Setup postgres to handle a local connection for transmet in pg_hba.conf
|
||||||
either:
|
either:
|
||||||
'''host transmet transmet 127.0.0.1/32 md5'''
|
host transmet transmet 127.0.0.1/32 md5
|
||||||
or a more liberal:
|
or a more liberal:
|
||||||
'''host all all 127.0.0.1/32 md5 '''
|
host all all 127.0.0.1/32 md5
|
||||||
and do the same for
|
and do the same for
|
||||||
'''host all all ::1/128 md5'''
|
host all all ::1/128 md5
|
||||||
ipv6
|
ipv6
|
||||||
|
|
||||||
create ssl certs and put them somewhere
|
create ssl certs and put them somewhere
|
||||||
|
|
||||||
enable SSL in postgresql.conf
|
enable SSL in postgresql.conf
|
||||||
'''
|
|
||||||
ssl = true
|
ssl = true
|
||||||
ssl_cert_file = 'WHER_YOU_PUT/server.crt'
|
ssl_cert_file = 'WHER_YOU_PUT/server.crt'
|
||||||
ssl_key_file = 'WHERE_YOU_PUT/server.key'
|
ssl_key_file = 'WHERE_YOU_PUT/server.key'
|
||||||
'''
|
|
||||||
|
|
||||||
Create postgress DB and user
|
Create postgress DB and user
|
||||||
|
|
||||||
'''sh
|
sh
|
||||||
sudo -u postgres --or-- sudo su - postgres
|
sudo -u postgres --or-- sudo su - postgres
|
||||||
createuser -S -P -E transmet
|
createuser -S -P -E transmet
|
||||||
createdb --owner transmet --encoding utf8 transmet
|
createdb --owner transmet --encoding utf8 transmet
|
||||||
psql
|
psql
|
||||||
\c transmet
|
\c transmet
|
||||||
CREATE EXTENSION pgcrypto;
|
CREATE EXTENSION pgcrypto;
|
||||||
'''
|
|
||||||
|
|
||||||
put DB details in
|
|
||||||
db/dbconf.yml (copied from db/dbconf.EXAMPLE)
|
|
||||||
config/prod.json (copied from config/local.json)
|
|
||||||
|
|
||||||
go get bitbucket.org/liamstask/goose/cmd/goose
|
put DB details in:
|
||||||
|
* db/dbconf.yml (copied from db/dbconf.EXAMPLE)
|
||||||
|
* config/prod.json (copied from config/local.json)
|
||||||
|
|
||||||
goose up
|
go get bitbucket.org/liamstask/goose/cmd/goose
|
||||||
|
|
||||||
|
goose up
|
||||||
|
|
||||||
## Run
|
## Run
|
||||||
|
|
||||||
Assumed GOPATH=/opt/go
|
Assumed `GOPATH=/opt/go`
|
||||||
|
|
||||||
edit transmet.conf to point to correct location
|
edit transmet.conf to point to correct location
|
||||||
|
|
||||||
sudo cp transmet.conf /etc/init
|
sudo cp transmet.conf /etc/init
|
||||||
|
./gen-csrf.sh
|
||||||
./gen-csrf.sh
|
sudo service transmet start
|
||||||
|
|
||||||
sudo service transmet start
|
|
||||||
|
|
||||||
## Setup environment
|
## Setup environment
|
||||||
|
|
||||||
### Adding a user
|
### Adding a user
|
||||||
|
|
||||||
INSERT INTO users (username, password) VALUES('USERNAME', crypt('PASSWORD', gen_salt('bf')));
|
INSERT INTO users (username, password) VALUES('USERNAME', crypt('PASSWORD', gen_salt('bf')));
|
||||||
|
|
|
@ -38,10 +38,27 @@ body {
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.post-preview {
|
.post-content {
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
|
max-height: 5em;
|
||||||
|
overflow: hidden;
|
||||||
|
padding-bottom: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.post-content blockquote {
|
||||||
|
font-size: 12px;
|
||||||
|
font-family: serif;
|
||||||
|
}
|
||||||
|
|
||||||
|
.content-slider {
|
||||||
|
text-align: center;
|
||||||
|
font-weight: bold;
|
||||||
|
font-size: 16px;
|
||||||
|
border-radius: 3px;
|
||||||
|
padding: 0px;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
.post-delete, .post-edit {
|
.post-delete, .post-edit {
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
}
|
}
|
12
js/funcs.js
12
js/funcs.js
|
@ -42,4 +42,16 @@ $(document).ready( function () {
|
||||||
form.submit();
|
form.submit();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
$(".content-slider").click(function (e) {
|
||||||
|
var contentDiv = $(this).parents('.news-row').find('.post-content');
|
||||||
|
if (contentDiv.hasClass("state-up")) {
|
||||||
|
contentDiv.removeClass('state-up').addClass('state-down').animate({'max-height': '100%', 'height': '100%'});
|
||||||
|
$(this).html('^');
|
||||||
|
} else {
|
||||||
|
contentDiv.removeClass('state-down').addClass('state-up').animate({height: '5em'});
|
||||||
|
$(this).html('v');
|
||||||
|
}
|
||||||
|
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
11
main.go
11
main.go
|
@ -104,15 +104,18 @@ func main() {
|
||||||
dbConnect()
|
dbConnect()
|
||||||
initTemplates()
|
initTemplates()
|
||||||
muxRouter := init_route_handlers()
|
muxRouter := init_route_handlers()
|
||||||
errHandler := csrf.ErrorHandler( CSRFErrorHandler{} )
|
//errHandler := csrf.ErrorHandler( CSRFErrorHandler{} )
|
||||||
|
|
||||||
// Terrible. TODO: Get SSL for prod, and then wrap in if(dev) { {
|
// Terrible. TODO: Get SSL for prod, and then wrap in if(dev) { {
|
||||||
csrfSecurityOption := csrf.Secure(false)
|
//csrfSecurityOption := csrf.Secure(false)
|
||||||
csrfMaxTimeOption := csrf.MaxAge(3600 * 24 * 3) // 3 Days - a little more wiggle room
|
//csrfMaxTimeOption := csrf.MaxAge(3600 * 24 * 3) // 3 Days - a little more wiggle room
|
||||||
|
|
||||||
fmt.Println("Listening on", config.Port, "...")
|
fmt.Println("Listening on", config.Port, "...")
|
||||||
|
|
||||||
err := http.ListenAndServe(":"+config.Port, csrf.Protect([]byte(csrfSecret()), errHandler, csrfSecurityOption, csrfMaxTimeOption)(muxRouter))
|
// Disabled CSRF until SSL (and sorting why the popup is throwing CSRF errs
|
||||||
|
// for tor and FF with ublock + https everywhere)
|
||||||
|
//err := http.ListenAndServe(":"+config.Port, csrf.Protect([]byte(csrfSecret()), errHandler, csrfSecurityOption, csrfMaxTimeOption)(muxRouter))
|
||||||
|
err := http.ListenAndServe(":"+config.Port, muxRouter)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Println("Fatal Error: ", err)
|
fmt.Println("Fatal Error: ", err)
|
||||||
}
|
}
|
||||||
|
|
12
news/news.go
12
news/news.go
|
@ -8,6 +8,8 @@ import (
|
||||||
_ "github.com/lib/pq"
|
_ "github.com/lib/pq"
|
||||||
"strconv"
|
"strconv"
|
||||||
"time"
|
"time"
|
||||||
|
"html/template"
|
||||||
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
type News struct {
|
type News struct {
|
||||||
|
@ -17,6 +19,7 @@ type News struct {
|
||||||
Category_id int
|
Category_id int
|
||||||
Date time.Time
|
Date time.Time
|
||||||
Notes string
|
Notes string
|
||||||
|
htmlNotes template.HTML
|
||||||
}
|
}
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
@ -94,7 +97,7 @@ func Get(db *sql.DB, id int) (*News, error) {
|
||||||
func LoadPage(db *sql.DB, offset, amount int) ([]*News, int, error) {
|
func LoadPage(db *sql.DB, offset, amount int) ([]*News, int, error) {
|
||||||
categories.LoadCategories(db) // required by addContainer
|
categories.LoadCategories(db) // required by addContainer
|
||||||
|
|
||||||
rows, err := db.Query("SELECT " + SQL_NEWS_FIELDS + " FROM news order by timestamp DESC")
|
rows, err := db.Query("SELECT " + SQL_NEWS_FIELDS + " FROM news ORDER BY timestamp DESC LIMIT $1 OFFSET $2", amount, offset)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Println("DB errpr reading LoadPage news: ", err)
|
fmt.Println("DB errpr reading LoadPage news: ", err)
|
||||||
return nil, 0, err
|
return nil, 0, err
|
||||||
|
@ -138,6 +141,10 @@ func (news *News) Id() int {
|
||||||
return news.id
|
return news.id
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (news *News) HTMLNotes() template.HTML {
|
||||||
|
return news.htmlNotes
|
||||||
|
}
|
||||||
|
|
||||||
func nullStringToString(str *sql.NullString) string {
|
func nullStringToString(str *sql.NullString) string {
|
||||||
if str.Valid {
|
if str.Valid {
|
||||||
return str.String
|
return str.String
|
||||||
|
@ -182,6 +189,9 @@ func scanNews(rows *sql.Rows) (*News, error) {
|
||||||
news.Url = nullStringToString(&url)
|
news.Url = nullStringToString(&url)
|
||||||
news.Title = nullStringToString(&title)
|
news.Title = nullStringToString(&title)
|
||||||
news.Notes = nullStringToString(¬es)
|
news.Notes = nullStringToString(¬es)
|
||||||
|
// support wordpress style <quote> tags
|
||||||
|
news.Notes = strings.Replace(news.Notes, "quote>", "blockquote>", -1)
|
||||||
|
news.htmlNotes = template.HTML(news.Notes)
|
||||||
|
|
||||||
if category_id.Valid {
|
if category_id.Valid {
|
||||||
news.Category_id = int(category_id.Int64)
|
news.Category_id = int(category_id.Int64)
|
||||||
|
|
|
@ -418,7 +418,7 @@ func newsFormHandler(w http.ResponseWriter, r *http.Request, user *user.User, se
|
||||||
session.AddFlash("Error loading news", flash_err)
|
session.AddFlash("Error loading news", flash_err)
|
||||||
}
|
}
|
||||||
|
|
||||||
ShowTemplate("news", w, r, map[string]interface{}{"user": user, "flashes": flashes, "news": news, "count": count, "categories": categories.CategoriesFlat, "url": config.Url})
|
ShowTemplate("news", w, r, map[string]interface{}{"user": user, "flashes": flashes, "news": news, "count": count, "offset": argOffset, "amount": amount, "categories": categories.CategoriesFlat, "url": config.Url})
|
||||||
}
|
}
|
||||||
|
|
||||||
func ServeFileHandler(res http.ResponseWriter, req *http.Request) {
|
func ServeFileHandler(res http.ResponseWriter, req *http.Request) {
|
||||||
|
|
|
@ -5,7 +5,6 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/dballard/transmet/categories"
|
"github.com/dballard/transmet/categories"
|
||||||
"github.com/gorilla/csrf"
|
"github.com/gorilla/csrf"
|
||||||
|
|
||||||
"html/template"
|
"html/template"
|
||||||
"net/http"
|
"net/http"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
|
|
@ -40,8 +40,8 @@
|
||||||
</ul>
|
</ul>
|
||||||
<ul class="nav navbar-nav navbar-right">
|
<ul class="nav navbar-nav navbar-right">
|
||||||
{{if .user}}
|
{{if .user}}
|
||||||
<li>{{.user.Username}}</li>
|
<li><a href="">{{.user.Username}}</a></li>
|
||||||
<li><form method="POST" action="/logout">{{ .csrfField }}<input type="submit" value="Logout" class="btn btn-sm btn-primary btn-block" /></form></li>
|
<li><form method="POST" action="/logout">{{ .csrfField }}<input type="submit" value="Logout" class="btn-sm btn-primary btn-block" /></form></li>
|
||||||
{{else}}
|
{{else}}
|
||||||
<li><a href="/login">Log in</a></li>
|
<li><a href="/login">Log in</a></li>
|
||||||
{{end}}
|
{{end}}
|
||||||
|
|
|
@ -35,7 +35,7 @@ new category select
|
||||||
</div>
|
</div>
|
||||||
<div class="col-xs-1">
|
<div class="col-xs-1">
|
||||||
<form method="POST" action="/categories/{{.category.Id}}/delete" class="cat-delete">{{ $.csrfField }}
|
<form method="POST" action="/categories/{{.category.Id}}/delete" class="cat-delete">{{ $.csrfField }}
|
||||||
<input type="submit" class="btn btn-sm btn-block" value="Delete" />
|
<input type="submit" class="btn btn-default btn-sm btn-block" value="Delete" />
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-xs-3">
|
<div class="col-xs-3">
|
||||||
|
|
|
@ -10,7 +10,11 @@
|
||||||
Drag this bookmarklet to bookmark bar and click anywhere to add a link
|
Drag this bookmarklet to bookmark bar and click anywhere to add a link
|
||||||
</div>
|
</div>
|
||||||
<div class="col-xs-4"></div>
|
<div class="col-xs-4"></div>
|
||||||
|
|
||||||
|
{{template "pager" .}}
|
||||||
|
|
||||||
<div class="col-xs-12"> </div>
|
<div class="col-xs-12"> </div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
<div class="news-posts">
|
<div class="news-posts">
|
||||||
{{range $news_post := .news}}
|
{{range $news_post := .news}}
|
||||||
|
@ -18,6 +22,10 @@
|
||||||
{{end}}
|
{{end}}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="row">
|
||||||
|
{{template "pager" .}}
|
||||||
|
</div>
|
||||||
|
|
||||||
{{end}}
|
{{end}}
|
||||||
|
|
||||||
<!-- print a news row -->
|
<!-- print a news row -->
|
||||||
|
@ -39,19 +47,39 @@
|
||||||
<div class="col-xs-8 post-url">
|
<div class="col-xs-8 post-url">
|
||||||
<a href="{{.post.Url}}">{{truncate .post.Url 100}}</a>
|
<a href="{{.post.Url}}">{{truncate .post.Url 100}}</a>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-xs-2 post-edit"><a href="/news/{{.post.Id}}/edit" class="btn btn-sm btn-block">Edit</a></div>
|
<div class="col-xs-2 post-edit"><a href="/news/{{.post.Id}}/edit" class="btn btn-default btn-sm btn-block">Edit</a></div>
|
||||||
</div>
|
</div>
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-xs-2"> </div>
|
<div class="col-xs-2"> </div>
|
||||||
<div class="col-xs-8 post-preview">{{truncate .post.Notes 500}}</div>
|
<div class="col-xs-8 post-content state-up"> {{ .post.HTMLNotes }}</div>
|
||||||
<div class="col-xs-2 post-delete">
|
<div class="col-xs-2 post-delete">
|
||||||
<form method="POST" action="/news/{{.post.Id}}/delete" class="confirm-news-delete">{{ .csrfField }}
|
<form method="POST" action="/news/{{.post.Id}}/delete" class="confirm-news-delete">{{ .csrfField }}
|
||||||
<input type="submit" class="btn btn-sm btn-block" value="Delete" />
|
<input type="submit" class="btn btn-default btn-sm btn-block" value="Delete" />
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-xs-2"> </div>
|
||||||
|
<div class="col-xs-8 btn-default btn content-slider state-up">v</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{{end}}
|
{{end}}
|
||||||
|
|
||||||
|
{{define "pager"}}
|
||||||
|
<div class="col-xs-12"> </div>
|
||||||
|
|
||||||
|
<div class="col-xs-2">
|
||||||
|
{{if ge .count .amount }}
|
||||||
|
<a href="?offset={{ add .offset 1}}">< Prev</a>
|
||||||
|
{{end}}
|
||||||
|
</div>
|
||||||
|
<div class="col-xs-8"> </div>
|
||||||
|
<div class="col-xs-2">
|
||||||
|
{{ if ne .offset 0 }}
|
||||||
|
<a href="?offset={{ minus .offset 1 }}">Next ></a>
|
||||||
|
{{end}}
|
||||||
|
</div>
|
||||||
|
{{end}}
|
||||||
|
|
||||||
<!-- JS for the launcher of the add bookmarklet -->
|
<!-- JS for the launcher of the add bookmarklet -->
|
||||||
{{define "launch-add"}}javascript:(function() { var d=document,w=window,e=w.getSelection,k=d.getSelection,x=d.selection,s=(e?e():(k)?k():(x?x.createRange().text:0)),f='{{.url}}/news/add',l=d.location,e=encodeURIComponent,u=f+'?popup=1&url='+e(l.href)+'&title='+e(d.title)+'&selection='+e(s);a=function(){if(!w.open(u,'t','toolbar=0,resizable=1,scrollbars=1,status=1,width=720,height=480'))l.href=u;};if (/Firefox/.test(navigator.userAgent)) setTimeout(a, 0); else a();void(0) })();{{end}}
|
{{define "launch-add"}}javascript:(function() { var d=document,w=window,e=w.getSelection,k=d.getSelection,x=d.selection,s=(e?e():(k)?k():(x?x.createRange().text:0)),f='{{.url}}/news/add',l=d.location,e=encodeURIComponent,u=f+'?popup=1&url='+e(l.href)+'&title='+e(d.title)+'&selection='+e(s);a=function(){if(!w.open(u,'t','toolbar=0,resizable=1,scrollbars=1,status=1,width=720,height=480'))l.href=u;};if (/Firefox/.test(navigator.userAgent)) setTimeout(a, 0); else a();void(0) })();{{end}}
|
Loading…
Reference in New Issue