Compare commits

..

No commits in common. "master" and "csrf" have entirely different histories.
master ... csrf

14 changed files with 61 additions and 181 deletions

1
.gitignore vendored
View File

@ -1,7 +1,6 @@
# Compiled Object files, Static and Dynamic libs (Shared Objects)
transmet
db/dbconf.yml
csrf-secret.txt
.Rhistory
.project
*.iml

View File

@ -1,4 +1,4 @@
# transmet ![thrree eyed smiley face](https://raw.githubusercontent.com/dballard/transmet/master/favicon.ico)
# transmet
Quick fast personal link store that exports to a HTML template for quick posting to a blog
Usecase: storing interesting news articles you come across during a week with at the moment notes/commentary and
@ -6,8 +6,8 @@ Usecase: storing interesting news articles you come across during a week with at
## Note
As this is a personal project, some of the niceities like user managment
are left to be done in SQL. I needed a tool to store links and export
As this is a personal project, some of the niceities like user managment and category managment
(that are one time tasks) are left to be done in SQL. I needed a tool to store links and export
to html so that's what I've focused on.
# Install
@ -18,55 +18,56 @@ go get github.com/dballard/transmet
sudo apt-get install postgres postgresql-contrib
Setup postgres to handle a local connection for transmet in pg_hba.conf
Setup postgres to hadle a local connection for transmet in pg_hba.conf
either:
host transmet transmet 127.0.0.1/32 md5
'''host transmet transmet 127.0.0.1/32 md5'''
or a more liberal:
host all all 127.0.0.1/32 md5
and do the same for
host all all ::1/128 md5
ipv6
'''host all all 127.0.0.1/32 md5 '''
create ssl certs and put them somewhere
enable SSL in postgresql.conf
ssl = true
ssl_cert_file = 'WHER_YOU_PUT/server.crt'
ssl_key_file = 'WHERE_YOU_PUT/server.key'
'''
ssl = true
ssl_cert_file = 'WHER_YOU_PUT/server.crt'
ssl_key_file = 'WHERE_YOU_PUT/server.key'
'''
Create postgress DB and user
sh
sudo -u postgres --or-- sudo su - postgres
createuser -S -P -E transmet
createdb --owner transmet --encoding utf8 transmet
psql
\c transmet
CREATE EXTENSION pgcrypto;
'''sh
sudo -u postgres
createuser -S -P -E transmet
createdb --owner transmet --encoding utf8 transmet
psql
\c transmet
CREATE EXTENSION pgcrypto;
'''
put DB details in
db/dbconf.yml (copied from db/dbconf.EXAMPLE)
config/prod.json (copied from config/local.json)
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
go get bitbucket.org/liamstask/goose/cmd/goose
goose up
goose up
## Run
Assumed `GOPATH=/opt/go`
Assumed GOPATH=/opt/go
edit transmet.conf to point to correct location
sudo cp transmet.conf /etc/init
./gen-csrf.sh
sudo service transmet start
sudo cp transmet.conf /etc/init
sudo service transmet start
## Setup environment
### 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')));
### Adding Categories
INSERT INTO categories (name, parent_id) VALUES ('NAME', [null or PARENT_ID]);

1
csrf-secret.txt Normal file
View File

@ -0,0 +1 @@
RUN GEN-CSRF.SH TO INIT THIS FI

View File

@ -1,39 +0,0 @@
/* Sticky footer styles
-------------------------------------------------- */
html {
position: relative;
min-height: 100%;
}
body {
/* Margin bottom by footer height */
margin-bottom: 60px;
}
.footer {
position: absolute;
bottom: 0;
width: 100%;
/* Set the fixed height of the footer here */
height: 60px;
background-color: #f5f5f5;
}
/* Custom page CSS
-------------------------------------------------- */
/* Not required for template or sticky footer method. */
body > .container {
padding: 0px 15px 0;
}
.container .text-muted {
margin: 20px 0;
}
.footer > .container {
padding-right: 15px;
padding-left: 15px;
}
code {
font-size: 80%;
}

View File

@ -38,27 +38,10 @@ body {
font-size: 12px;
}
.post-content {
.post-preview {
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 {
font-size: 12px;
}

View File

@ -1,3 +1,3 @@
#!/bin/sh
dd if=/dev/urandom of=csrf-secret.txt count=32 bs=1
dd if=/dev/random of=csrf-secret.txt count=32 bs=1

View File

@ -42,16 +42,4 @@ $(document).ready( function () {
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');
}
});
});

View File

@ -107,15 +107,11 @@ func main() {
//errHandler := csrf.ErrorHandler( CSRFErrorHandler{} )
// Terrible. TODO: Get SSL for prod, and then wrap in if(dev) { {
//csrfSecurityOption := csrf.Secure(false)
//csrfMaxTimeOption := csrf.MaxAge(3600 * 24 * 3) // 3 Days - a little more wiggle room
csrfSecurityOption := csrf.Secure(false)
fmt.Println("Listening on", config.Port, "...")
// 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)
err := http.ListenAndServe(":"+config.Port, csrf.Protect([]byte(csrfSecret()) /*errHandler,*/, csrfSecurityOption)(muxRouter))
if err != nil {
fmt.Println("Fatal Error: ", err)
}

View File

@ -8,8 +8,6 @@ import (
_ "github.com/lib/pq"
"strconv"
"time"
"html/template"
"strings"
)
type News struct {
@ -19,7 +17,6 @@ type News struct {
Category_id int
Date time.Time
Notes string
htmlNotes template.HTML
}
const (
@ -97,7 +94,7 @@ func Get(db *sql.DB, id int) (*News, error) {
func LoadPage(db *sql.DB, offset, amount int) ([]*News, int, error) {
categories.LoadCategories(db) // required by addContainer
rows, err := db.Query("SELECT " + SQL_NEWS_FIELDS + " FROM news ORDER BY timestamp DESC LIMIT $1 OFFSET $2", amount, offset)
rows, err := db.Query("SELECT " + SQL_NEWS_FIELDS + " FROM news order by timestamp DESC")
if err != nil {
fmt.Println("DB errpr reading LoadPage news: ", err)
return nil, 0, err
@ -141,10 +138,6 @@ func (news *News) Id() int {
return news.id
}
func (news *News) HTMLNotes() template.HTML {
return news.htmlNotes
}
func nullStringToString(str *sql.NullString) string {
if str.Valid {
return str.String
@ -189,9 +182,6 @@ func scanNews(rows *sql.Rows) (*News, error) {
news.Url = nullStringToString(&url)
news.Title = nullStringToString(&title)
news.Notes = nullStringToString(&notes)
// support wordpress style <quote> tags
news.Notes = strings.Replace(news.Notes, "quote>", "blockquote>", -1)
news.htmlNotes = template.HTML(news.Notes)
if category_id.Valid {
news.Category_id = int(category_id.Int64)

View File

@ -144,17 +144,10 @@ func addFormHandler(w http.ResponseWriter, r *http.Request, user *user.User, ses
if title == "" && url != "" {
title = getUrlTitle(url)
}
var notes = ""
selection := r.URL.Query().Get("selection")
if selection != "" {
notes = "<quote>" + selection + "</quote>"
}
popup := r.URL.Query().Get("popup")
ShowTemplate("post", w, r, map[string]interface{}{"mode": "add", "user": user, "flashes": flashes, "link": url, "categories": categories.CategoriesTree, "title": title, "popup": popup, "category_id": -1, "notes": notes})
ShowTemplate("post", w, r, map[string]interface{}{"mode": "add", "user": user, "flashes": flashes, "link": url, "categories": categories.CategoriesTree, "title": title, "popup": popup, "category_id": -1})
}
func addPostHandler(w http.ResponseWriter, r *http.Request, user *user.User, session *sessions.Session) {
@ -418,7 +411,7 @@ func newsFormHandler(w http.ResponseWriter, r *http.Request, user *user.User, se
session.AddFlash("Error loading news", flash_err)
}
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})
ShowTemplate("news", w, r, map[string]interface{}{"user": user, "flashes": flashes, "news": news, "count": count, "categories": categories.CategoriesFlat})
}
func ServeFileHandler(res http.ResponseWriter, req *http.Request) {

View File

@ -5,6 +5,7 @@ import (
"fmt"
"github.com/dballard/transmet/categories"
"github.com/gorilla/csrf"
"html/template"
"net/http"
"path/filepath"

View File

@ -9,7 +9,6 @@
<!-- Bootstrap -->
<link href="/css/bootstrap.min.css" rel="stylesheet">
<link href="/css/sticky-footer-navbar.css" rel="stylesheet">
<link href="/css/jquery-ui.min.css" rel="stylesheet">
<link href="/css/template.css" rel="stylesheet">
<link href="/css/signin.css" rel="stylesheet">
@ -18,10 +17,11 @@
</head>
<body>
<nav class="navbar navbar-inverse navbar-fixed-top" role="navigation">
<div class="navbar navbar-inverse navbar-fixed-top" role="navigation">
<div class="container">
<div class="navbar-header">
<button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target=".navbar-collapse">
<span class="sr-only">Toggle navigation</span>
<span class="icon-bar"></span>
@ -32,34 +32,27 @@
</div>
<div class="collapse navbar-collapse">
<ul class="nav navbar-nav">
{{if .user}}
<li><a href="/news/add">add</a></li>
<li><a href="/news/export">export</a></li>
<li><a href="/categories">categories</a></li>
{{end}}
<li><a href="/news/add">add</a></li>
<li><a href="/news/export">export</a></li>
<li><a href="/categories">categories</a></li>
</ul>
<ul class="nav navbar-nav navbar-right">
{{if .user}}
<li><a href="">{{.user.Username}}</a></li>
<li><form method="POST" action="/logout">{{ .csrfField }}<input type="submit" value="Logout" class="btn-sm btn-primary btn-block" /></form></li>
<li>{{.user.Username}}</li>
<li><form method="POST" action="/logout">{{ .csrfField }}<input type="submit" value="Logout" class="btn btn-sm btn-primary btn-block" /></form></li>
{{else}}
<li><a href="/login">Log in</a></li>
{{end}}
</ul>
</div>
</div>
</nav>
</div>
<div class="container">
{{template "body" .}}
</div>
<footer class="footer">
<div class="container">
<p class="text-muted">Powered by <a href="https://github.com/dballard/transmet">transmet</a> <img src="/favicon.ico" />
</div>
</footer>
<!-- jQuery (necessary for Bootstrap's JavaScript plugins) -->
<script src="/js/jquery.min.js"></script>
<!-- Include all compiled plugins (below), or include individual files as needed -->

View File

@ -35,7 +35,7 @@ new category select
</div>
<div class="col-xs-1">
<form method="POST" action="/categories/{{.category.Id}}/delete" class="cat-delete">{{ $.csrfField }}
<input type="submit" class="btn btn-default btn-sm btn-block" value="Delete" />
<input type="submit" class="btn btn-sm btn-block" value="Delete" />
</form>
</div>
<div class="col-xs-3">

View File

@ -7,24 +7,16 @@
</div>
<div class="col-xs-6">
Drag this bookmarklet to bookmark bar and click anywhere to add a link
Drag this link to bookmark bar and click anywhere to add a link
</div>
<div class="col-xs-4"></div>
{{template "pager" .}}
<div class="col-xs-4"></div>
<div class="col-xs-12">&nbsp;</div>
</div>
<div class="news-posts">
{{range $news_post := .news}}
{{template "row-news" dict "post" $news_post "categories" $.categories "csrfField" $.csrfField}}
{{end}}
</div>
<div class="row">
{{template "pager" .}}
</div>
{{end}}
@ -47,39 +39,21 @@
<div class="col-xs-8 post-url">
<a href="{{.post.Url}}">{{truncate .post.Url 100}}</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 class="col-xs-2 post-edit"><a href="/news/{{.post.Id}}/edit" class="btn btn-sm btn-block">Edit</a></div>
</div>
<div class="row">
<div class="col-xs-2">&nbsp;</div>
<div class="col-xs-8 post-content state-up"> {{ .post.HTMLNotes }}</div>
<div class="col-xs-8 post-preview">{{truncate .post.Notes 500}}</div>
<div class="col-xs-2 post-delete">
<form method="POST" action="/news/{{.post.Id}}/delete" class="confirm-news-delete">{{ .csrfField }}
<input type="submit" class="btn btn-default btn-sm btn-block" value="Delete" />
<input type="submit" class="btn btn-sm btn-block" value="Delete" />
</form>
</div>
</div>
<div class="row">
<div class="col-xs-2">&nbsp;</div>
<div class="col-xs-8 btn-default btn content-slider state-up">v</div>
</div>
</div>
{{end}}
{{define "pager"}}
<div class="col-xs-12">&nbsp;</div>
<div class="col-xs-2">
{{if ge .count .amount }}
<a href="?offset={{ add .offset 1}}">&lt; Prev</a>
{{end}}
</div>
<div class="col-xs-8">&nbsp;</div>
<div class="col-xs-2">
{{ if ne .offset 0 }}
<a href="?offset={{ minus .offset 1 }}">Next &gt</a>
{{end}}
</div>
{{end}}
<!-- 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: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);a=function(){if(!w.open(u,'t','toolbar=0,resizable=1,scrollbars=1,status=1,width=720,height=410'))l.href=u;};if (/Firefox/.test(navigator.userAgent)) setTimeout(a, 0); else a();void(0)
{{end}}