forked from electricdusk/rushlink
Put existing pages in templates
This commit is contained in:
parent
372d7c0487
commit
60fd92c956
@ -5,6 +5,10 @@
|
|||||||
<title>{{block "title" .}}rushlink{{end}}</title>{{block "head-append" .}}{{end}}
|
<title>{{block "title" .}}rushlink{{end}}</title>{{block "head-append" .}}{{end}}
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
{{block "body" .}}{{end}}
|
{{block "body" .}}
|
||||||
|
<pre>
|
||||||
|
{{call .Pre}}
|
||||||
|
</pre>
|
||||||
|
{{end}}
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
@ -1,8 +1,3 @@
|
|||||||
{{define "title"}}
|
{{define "title"}}
|
||||||
Error - rushlink
|
Error - rushlink
|
||||||
{{end}}
|
{{end}}
|
||||||
{{define "body"}}
|
|
||||||
<pre>
|
|
||||||
{{.Message}}
|
|
||||||
</pre>
|
|
||||||
{{end}}
|
|
3
assets/templates/html/pasteMeta.html.tmpl
Normal file
3
assets/templates/html/pasteMeta.html.tmpl
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
{{define "title"}}
|
||||||
|
'{{.Paste.Key}}' meta info - rushlink
|
||||||
|
{{end}}
|
5
assets/templates/txt/pasteMeta.txt.tmpl
Normal file
5
assets/templates/txt/pasteMeta.txt.tmpl
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
key: {{.Paste.Key}}
|
||||||
|
type: {{.Paste.Type}}
|
||||||
|
state: {{.Paste.State}}
|
||||||
|
created: {{.Paste.TimeCreated}}
|
||||||
|
owner: {{if .IsOwner}}yes{{else}}no{{end}}
|
@ -1,7 +1,6 @@
|
|||||||
package handlers
|
package handlers
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
|
||||||
"crypto/rand"
|
"crypto/rand"
|
||||||
"crypto/subtle"
|
"crypto/subtle"
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
@ -28,7 +27,7 @@ type StoredPaste struct {
|
|||||||
Type PasteType
|
Type PasteType
|
||||||
State PasteState
|
State PasteState
|
||||||
Content []byte
|
Content []byte
|
||||||
Key []byte
|
Key string
|
||||||
OwnerToken [16]byte
|
OwnerToken [16]byte
|
||||||
TimeCreated time.Time
|
TimeCreated time.Time
|
||||||
}
|
}
|
||||||
@ -46,31 +45,31 @@ const (
|
|||||||
const CookieOwnerToken = "owner_token"
|
const CookieOwnerToken = "owner_token"
|
||||||
|
|
||||||
// These keys are designated reserved, and will not be randomly chosen
|
// These keys are designated reserved, and will not be randomly chosen
|
||||||
var ReservedPasteKeys [][]byte = [][]byte{[]byte("xd42"), []byte("example")}
|
var ReservedPasteKeys = []string{"xd42", "example"}
|
||||||
|
|
||||||
// Base64 encoding and decoding
|
// Base64 encoding and decoding
|
||||||
var base64Alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_"
|
var base64Alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_"
|
||||||
var base64Encoder = base64.RawURLEncoding.WithPadding(base64.NoPadding)
|
var base64Encoder = base64.RawURLEncoding.WithPadding(base64.NoPadding)
|
||||||
|
|
||||||
func (t PasteType) String() (string, error) {
|
func (t PasteType) String() string {
|
||||||
switch t {
|
switch t {
|
||||||
case TypePaste:
|
case TypePaste:
|
||||||
return "paste", nil
|
return "paste"
|
||||||
case TypeRedirect:
|
case TypeRedirect:
|
||||||
return "redirect", nil
|
return "redirect"
|
||||||
default:
|
default:
|
||||||
return "", fmt.Errorf("invalid PasteType (%v)", t)
|
return "invalid"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t PasteState) String() (string, error) {
|
func (t PasteState) String() string {
|
||||||
switch t {
|
switch t {
|
||||||
case StatePresent:
|
case StatePresent:
|
||||||
return "present", nil
|
return "present"
|
||||||
case StateDeleted:
|
case StateDeleted:
|
||||||
return "deleted", nil
|
return "deleted"
|
||||||
default:
|
default:
|
||||||
return "", fmt.Errorf("invalid PasteState (%v)", t)
|
return "invalid"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -134,31 +133,17 @@ func pasteGetHandlerInner(w http.ResponseWriter, r *http.Request, noRedirect, sh
|
|||||||
}
|
}
|
||||||
|
|
||||||
if showMeta {
|
if showMeta {
|
||||||
typeString, err := storedPaste.Type.String()
|
isOwner := false
|
||||||
if err != nil {
|
|
||||||
log.Printf("error: %v\n", err)
|
|
||||||
RenderInternalServerError(w, r, err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
stateString, err := storedPaste.State.String()
|
|
||||||
if err != nil {
|
|
||||||
log.Printf("error: %v\n", err)
|
|
||||||
RenderInternalServerError(w, r, err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
isOwner := "no"
|
|
||||||
ownerToken, ok := getOwnerTokenFromRequest(r)
|
ownerToken, ok := getOwnerTokenFromRequest(r)
|
||||||
if ok && subtle.ConstantTimeCompare(ownerToken[:], storedPaste.OwnerToken[:]) == 1 {
|
if ok && subtle.ConstantTimeCompare(ownerToken[:], storedPaste.OwnerToken[:]) == 1 {
|
||||||
isOwner = "yes"
|
isOwner = true
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO(dsprenkels) This should be put into a template
|
data := map[string]interface{}{
|
||||||
w.WriteHeader(http.StatusOK)
|
"Paste": storedPaste,
|
||||||
fmt.Fprintf(w, "key: %v\n", string(storedPaste.Key))
|
"IsOwner": isOwner,
|
||||||
fmt.Fprintf(w, "type: %v\n", typeString)
|
}
|
||||||
fmt.Fprintf(w, "state: %v\n", stateString)
|
Render(w, r, "pasteMeta", data)
|
||||||
fmt.Fprintf(w, "created: %v\n", storedPaste.TimeCreated.String())
|
|
||||||
fmt.Fprintf(w, "are you the owner: %v\n", isOwner)
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -265,7 +250,7 @@ func shortenURL(tx *bolt.Tx, userURL *url.URL, ownerKey [16]byte) (*StoredPaste,
|
|||||||
// Generate a key until it is not in the database, this occurs in O(log N),
|
// Generate a key until it is not in the database, this occurs in O(log N),
|
||||||
// where N is the amount of keys stored in the url-shorten database.
|
// where N is the amount of keys stored in the url-shorten database.
|
||||||
epoch := 0
|
epoch := 0
|
||||||
var urlKey []byte
|
var urlKey string
|
||||||
for {
|
for {
|
||||||
var err error
|
var err error
|
||||||
urlKey, err = generateURLKey(epoch)
|
urlKey, err = generateURLKey(epoch)
|
||||||
@ -273,14 +258,14 @@ func shortenURL(tx *bolt.Tx, userURL *url.URL, ownerKey [16]byte) (*StoredPaste,
|
|||||||
return nil, errors.Wrap(err, "url-key generation failed")
|
return nil, errors.Wrap(err, "url-key generation failed")
|
||||||
}
|
}
|
||||||
|
|
||||||
found := shortenBucket.Get(urlKey)
|
found := shortenBucket.Get([]byte(urlKey))
|
||||||
if found == nil {
|
if found == nil {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
isReserved := false
|
isReserved := false
|
||||||
for _, reservedKey := range ReservedPasteKeys {
|
for _, reservedKey := range ReservedPasteKeys {
|
||||||
if bytes.HasPrefix(urlKey, reservedKey) {
|
if strings.HasPrefix(urlKey, reservedKey) {
|
||||||
isReserved = true
|
isReserved = true
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
@ -305,17 +290,17 @@ func shortenURL(tx *bolt.Tx, userURL *url.URL, ownerKey [16]byte) (*StoredPaste,
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.Wrap(err, "encoding for database failed")
|
return nil, errors.Wrap(err, "encoding for database failed")
|
||||||
}
|
}
|
||||||
if err := shortenBucket.Put(urlKey, storedBytes); err != nil {
|
if err := shortenBucket.Put([]byte(urlKey), storedBytes); err != nil {
|
||||||
return nil, errors.Wrap(err, "database transaction failed")
|
return nil, errors.Wrap(err, "database transaction failed")
|
||||||
}
|
}
|
||||||
return &storedPaste, nil
|
return &storedPaste, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func generateURLKey(epoch int) ([]byte, error) {
|
func generateURLKey(epoch int) (string, error) {
|
||||||
urlKey := make([]byte, 4+epoch)
|
urlKey := make([]byte, 4+epoch)
|
||||||
_, err := rand.Read(urlKey)
|
_, err := rand.Read(urlKey)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return "", err
|
||||||
}
|
}
|
||||||
// Put all the values in the range 0..64 for easier base64-encoding
|
// Put all the values in the range 0..64 for easier base64-encoding
|
||||||
for i := 0; i < len(urlKey); i++ {
|
for i := 0; i < len(urlKey); i++ {
|
||||||
@ -343,7 +328,7 @@ func generateURLKey(epoch int) ([]byte, error) {
|
|||||||
for i := range urlKey {
|
for i := range urlKey {
|
||||||
urlKey[i] = base64Alphabet[urlKey[i]]
|
urlKey[i] = base64Alphabet[urlKey[i]]
|
||||||
}
|
}
|
||||||
return urlKey, nil
|
return string(urlKey), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func generateOwnerToken() ([16]byte, error) {
|
func generateOwnerToken() ([16]byte, error) {
|
||||||
|
@ -5,6 +5,7 @@ package handlers
|
|||||||
//go:generate go-bindata -pkg $GOPACKAGE -prefix ../assets ../assets/...
|
//go:generate go-bindata -pkg $GOPACKAGE -prefix ../assets ../assets/...
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
"fmt"
|
"fmt"
|
||||||
html "html/template"
|
html "html/template"
|
||||||
"io"
|
"io"
|
||||||
@ -72,7 +73,7 @@ func parseFail(tmplName string, err error) {
|
|||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
func Render(w http.ResponseWriter, r *http.Request, tmplName string, data interface{}) {
|
func Render(w http.ResponseWriter, r *http.Request, tmplName string, data map[string]interface{}) {
|
||||||
contentType, err := resolveResponseContentType(r, []string{"text/plain", "text/html"})
|
contentType, err := resolveResponseContentType(r, []string{"text/plain", "text/html"})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
w.WriteHeader(http.StatusNotAcceptable)
|
w.WriteHeader(http.StatusNotAcceptable)
|
||||||
@ -95,8 +96,23 @@ func Render(w http.ResponseWriter, r *http.Request, tmplName string, data interf
|
|||||||
err = fmt.Errorf("'%v' not in HTMLTemplates", tmplName)
|
err = fmt.Errorf("'%v' not in HTMLTemplates", tmplName)
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Construct a (lazy) plain-text view for inclusion in <pre>
|
||||||
|
pre := func() string {
|
||||||
|
tmpl := TextTemplates[tmplName]
|
||||||
|
if tmpl == nil {
|
||||||
|
panic(fmt.Errorf("'%v' not in TextTemplates", tmplName))
|
||||||
|
}
|
||||||
|
var buf bytes.Buffer
|
||||||
|
if err := tmpl.Execute(&buf, data); err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
return buf.String()
|
||||||
|
}
|
||||||
|
data = mergeData(map[string]interface{}{"Pre": pre}, data)
|
||||||
err = tmpl.Execute(w, data)
|
err = tmpl.Execute(w, data)
|
||||||
default:
|
default:
|
||||||
|
// Fall back to plain text without template
|
||||||
w.WriteHeader(http.StatusNotAcceptable)
|
w.WriteHeader(http.StatusNotAcceptable)
|
||||||
io.WriteString(w, "could not resolve an acceptable content-type\n")
|
io.WriteString(w, "could not resolve an acceptable content-type\n")
|
||||||
}
|
}
|
||||||
@ -108,7 +124,7 @@ func Render(w http.ResponseWriter, r *http.Request, tmplName string, data interf
|
|||||||
|
|
||||||
func RenderError(w http.ResponseWriter, r *http.Request, status int, msg string) {
|
func RenderError(w http.ResponseWriter, r *http.Request, status int, msg string) {
|
||||||
w.WriteHeader(status)
|
w.WriteHeader(status)
|
||||||
Render(w, r, "error", struct{ Message string }{msg})
|
Render(w, r, "error", map[string]interface{}{"Message": msg})
|
||||||
}
|
}
|
||||||
|
|
||||||
func RenderInternalServerError(w http.ResponseWriter, r *http.Request, err interface{}) {
|
func RenderInternalServerError(w http.ResponseWriter, r *http.Request, err interface{}) {
|
||||||
@ -116,6 +132,15 @@ func RenderInternalServerError(w http.ResponseWriter, r *http.Request, err inter
|
|||||||
RenderError(w, r, http.StatusInternalServerError, msg)
|
RenderError(w, r, http.StatusInternalServerError, msg)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Merge the second data map into the first one, overwriting any key that is
|
||||||
|
// already present.
|
||||||
|
func mergeData(into, from map[string]interface{}) map[string]interface{} {
|
||||||
|
for k, v := range from {
|
||||||
|
into[k] = v
|
||||||
|
}
|
||||||
|
return into
|
||||||
|
}
|
||||||
|
|
||||||
// Try to resolve the preferred content-type for the response to this request.
|
// Try to resolve the preferred content-type for the response to this request.
|
||||||
//
|
//
|
||||||
// This is done by reading from the `types` argument. If one of them matches
|
// This is done by reading from the `types` argument. If one of them matches
|
||||||
|
@ -23,20 +23,20 @@ func StartMetricsServer() {
|
|||||||
var (
|
var (
|
||||||
_ = promauto.NewGaugeFunc(prometheus.GaugeOpts{
|
_ = promauto.NewGaugeFunc(prometheus.GaugeOpts{
|
||||||
Namespace: "rushlink",
|
Namespace: "rushlink",
|
||||||
Subsystem: "shorten",
|
Subsystem: "pastes",
|
||||||
Name: "urls_total",
|
Name: "urls_total",
|
||||||
Help: "The current amount of shortened urls in the database.",
|
Help: "The current amount of pastes in the database.",
|
||||||
}, func() float64 {
|
}, func() float64 {
|
||||||
var metric float64
|
var metric float64
|
||||||
if err := db.DB.View(func(tx *bolt.Tx) error {
|
if err := db.DB.View(func(tx *bolt.Tx) error {
|
||||||
bucket := tx.Bucket([]byte("shorten"))
|
bucket := tx.Bucket([]byte("pastes"))
|
||||||
if bucket == nil {
|
if bucket == nil {
|
||||||
return errors.New("bucket 'shorten' could not be found")
|
return errors.New("bucket 'pastes' could not be found")
|
||||||
}
|
}
|
||||||
metric = float64(bucket.Stats().KeyN)
|
metric = float64(bucket.Stats().KeyN)
|
||||||
return nil
|
return nil
|
||||||
}); err != nil {
|
}); err != nil {
|
||||||
log.Printf("error: %v", errors.Wrap(err, "fetching shorten_urls_total metric"))
|
log.Printf("error: %v", errors.Wrap(err, "fetching pastes_total metric"))
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
return metric
|
return metric
|
||||||
|
Loading…
Reference in New Issue
Block a user