Browse Source

Added URL submission

master
Christopher Ramey 4 years ago
committed by cdramey
parent
commit
5c669c8b93
  1. 2
      assets/index.html
  2. 1
      assets/qurl.css
  3. 24
      assets/submit.html
  4. 3
      load.go
  5. 13
      main.go
  6. 79
      pages/submit.go
  7. 22
      qurl/qurl.go
  8. 2
      static/static.go
  9. 91
      storage/bolt/qurl.go

2
assets/index.html

@ -9,7 +9,7 @@
<body>
<div id="c">
qurl.org is a simple url shortening service, in the same vein as
<a href="https://bit.ly">bit.ly</a>, and
<a href="https://bit.ly">bit.ly</a> and
<a href="https://tinyurl.com">tinyurl.com</a>.
qurl.org is <a href="http://binarythought.com/qurl/LICENSE">open source</a>,
it's code is <a href="https://binarythought.com/fossils/qurl/">freely available</a>

1
assets/qurl.css

@ -10,6 +10,7 @@ input:focus { outline: 0; }
#u:focus { border-color: #129fea; }
#s { margin-top: 8px; color: #fff; background-color: #0078e7; border-color: transparent; }
#s:hover { background-image: linear-gradient(transparent,rgba(0,0,0,.05) 40%,rgba(0,0,0,.1)); cursor: hover; }
#m { margin-bottom: 16px; }
@media screen and (min-width: 640px) {
#c { max-width: 600px; }
#u, #s { display: inline-block; margin: 0; }

24
assets/submit.html

@ -0,0 +1,24 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>qurl.org</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
<link rel="stylesheet" media="screen" href="qurl.css">
</head>
<body>
<div id="c">
<div id="m">
<div>{{if .Message}}{{.Message}}{{end}}</div>
<div>{{if .URL}}<a href="{{.URL}}">{{.URL}}</a>{{end}}</div>
</div>
qurl.org is a simple url shortening service, in the same vein as
<a href="https://bit.ly">bit.ly</a> and
<a href="https://tinyurl.com">tinyurl.com</a>.
qurl.org is <a href="http://binarythought.com/qurl/LICENSE">open source</a>,
it's code is <a href="https://binarythought.com/fossils/qurl/">freely available</a>
and has an <a href="api/index.html">easy to use API</a>.
</div>
</body>
</html>

3
load.go

@ -61,7 +61,8 @@ func loadjson(stor storage.Storage, filename string) error {
err := stor.AddQURL(qurl)
if err != nil {
return fmt.Errorf("Error adding qurl: %s", err.Error())
fmt.Printf("\nError adding qurl: %s\n", err.Error())
continue
}
count++

13
main.go

@ -6,14 +6,14 @@ import (
"net"
"net/http"
"os"
"qurl/storage"
"qurl/pages"
"qurl/static"
"qurl/storage"
"runtime"
)
//go:generate bindata -m Assets -r assets -p static -o static/assets.go assets
func main() {
dburl := flag.String("u", "bolt:./qurl.db", "url to database")
lsaddr := flag.String("l", "127.0.0.1:8080", "listen address/port")
@ -59,6 +59,15 @@ func main() {
mux.Handle("/favicon.ico", &static.StaticContent{Content: "favicon.ico"})
mux.Handle("/qurl.css", &static.StaticContent{Content: "qurl.css"})
submit := &pages.SubmitHandler{Storage: stor}
err = submit.Init()
if err != nil {
fmt.Fprintf(os.Stderr, "Submit init error: %s\n", err.Error())
return
}
mux.Handle("/submit.html", submit)
fmt.Fprintf(os.Stdout, "qurl listening .. \n")
err = http.Serve(listen, mux)
if err != nil {

79
pages/submit.go

@ -0,0 +1,79 @@
package pages
import (
"bytes"
"fmt"
"html/template"
"net/http"
"qurl/qurl"
"qurl/static"
"qurl/storage"
"time"
)
type SubmitHandler struct {
Storage storage.Storage
template *template.Template
}
type submitPage struct {
Message string
URL string
}
func (ctx *SubmitHandler) Init() error {
ctx.template = template.New("submit.html")
_, err := ctx.template.Parse(string(static.Assets["submit.html"]))
if err != nil {
return err
}
return nil
}
func (ctx *SubmitHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
var pg submitPage
u := r.FormValue("url")
if u == "" {
pg.Message = "Not a valid URL."
} else {
q, err := ctx.Storage.GetQURLByURL(u)
if err != nil {
http.Error(w, fmt.Sprintf("Database error: %s", err.Error()),
http.StatusInternalServerError)
return
}
if q != nil {
pg.Message = "URL already exists."
pg.URL = fmt.Sprintf("https://qurl.org/%s", qurl.ToString(q.ID))
} else {
q = &qurl.QURL{
URL: u,
Created: time.Now(),
}
err := ctx.Storage.AddQURL(q)
if err != nil {
http.Error(w, fmt.Sprintf("Database error: %s", err.Error()),
http.StatusInternalServerError)
return
}
pg.Message = "URL Added."
pg.URL = fmt.Sprintf("https://qurl.org/%s", qurl.ToString(q.ID))
}
}
var buf bytes.Buffer
err := ctx.template.Execute(&buf, pg)
if err != nil {
http.Error(w, fmt.Sprintf("Template execute error: %s", err.Error()),
http.StatusInternalServerError)
return
}
w.Header().Set("Content-Length", fmt.Sprintf("%d", buf.Len()))
w.Header().Set("Content-Type", "text/html")
w.Write(buf.Bytes())
}

22
qurl/qurl.go

@ -4,6 +4,7 @@ import (
"fmt"
"strings"
"time"
"net/url"
)
type QURL struct {
@ -14,6 +15,27 @@ type QURL struct {
Browser string
}
func (q *QURL) CheckValid() error {
if q == nil {
return fmt.Errorf("QURL is nil")
}
if q.URL == "" {
return fmt.Errorf("URL may not be empty")
}
u, err := url.Parse(q.URL)
if err != nil {
return err
}
if !u.IsAbs() {
return fmt.Errorf("Relative URLs are not allowed")
}
return nil
}
const alpha = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"
const alphalen = uint64(len(alpha))

2
static/static.go

@ -6,8 +6,8 @@ import (
"crypto/md5"
"fmt"
"mime"
"path"
"net/http"
"path"
"strings"
)

91
storage/bolt/qurl.go

@ -3,12 +3,10 @@ package bolt
import (
"encoding/binary"
"qurl/qurl"
// "bytes"
// "fmt"
)
var (
QURLBucket = []byte{0x00}
QURLBucket = []byte{0x00}
ReverseBucket = []byte{0x01}
URLField = []byte{0x00}
@ -17,7 +15,12 @@ var (
BrowserField = []byte{0x03}
)
func (stor *BoltStorage) AddQURL(qurl *qurl.QURL) error {
func (stor *BoltStorage) AddQURL(q *qurl.QURL) error {
err := q.CheckValid()
if err != nil {
return err
}
tx, err := stor.DB.Begin(true)
if err != nil {
return err
@ -30,16 +33,21 @@ func (stor *BoltStorage) AddQURL(qurl *qurl.QURL) error {
}
// Populate the ID from the sequence if we don't have one
if qurl.ID == 0 {
qurl.ID, err = rb.NextSequence()
if q.ID == 0 {
s, err := rb.NextSequence()
if err != nil {
return err
}
// Bolt's sequence starts at 1, so for
// backwards compatibility we have subtract
// one so we're zero-based
q.ID = (s - 1)
}
// Create a byte array from the ID
bid := make([]byte, 8)
binary.BigEndian.PutUint64(bid, qurl.ID)
binary.BigEndian.PutUint64(bid, q.ID)
// Add an entry into the reverse indexed bucket for quickly
// determining if a URL is already in the database
@ -47,7 +55,7 @@ func (stor *BoltStorage) AddQURL(qurl *qurl.QURL) error {
if err != nil {
return err
}
err = ab.Put([]byte(qurl.URL), bid)
err = ab.Put([]byte(q.URL), bid)
qb, err := rb.CreateBucketIfNotExists(bid)
if err != nil {
@ -55,14 +63,14 @@ func (stor *BoltStorage) AddQURL(qurl *qurl.QURL) error {
}
// Write the ID to URL
err = qb.Put(URLField, []byte(qurl.URL))
err = qb.Put(URLField, []byte(q.URL))
if err != nil {
return err
}
if !qurl.Created.IsZero() {
if !q.Created.IsZero() {
// Create byte array from the Created date
bdt, err := qurl.Created.MarshalBinary()
bdt, err := q.Created.MarshalBinary()
if err != nil {
return err
}
@ -74,15 +82,15 @@ func (stor *BoltStorage) AddQURL(qurl *qurl.QURL) error {
}
}
if qurl.IP != nil {
err = qb.Put(IPField, qurl.IP)
if q.IP != nil {
err = qb.Put(IPField, q.IP)
if err != nil {
return err
}
}
if len(qurl.Browser) > 0 {
err = qb.Put(BrowserField, []byte(qurl.Browser))
if len(q.Browser) > 0 {
err = qb.Put(BrowserField, []byte(q.Browser))
if err != nil {
return err
}
@ -107,7 +115,10 @@ func (stor *BoltStorage) SetQURLSequence(seq uint64) error {
return err
}
qb.SetSequence(seq)
// Since the sequence number is decremented by one
// for backwards compatibility (see above)
// we increment it by one when setting the sequence
qb.SetSequence(seq + 1)
if err := tx.Commit(); err != nil {
return err
@ -118,10 +129,10 @@ func (stor *BoltStorage) SetQURLSequence(seq uint64) error {
func (stor *BoltStorage) GetQURLByURL(u string) (*qurl.QURL, error) {
tx, err := stor.DB.Begin(false)
if err != nil {
return nil, err
}
defer tx.Rollback()
if err != nil {
return nil, err
}
defer tx.Rollback()
ab := tx.Bucket(ReverseBucket)
if ab == nil {
@ -143,46 +154,12 @@ func (stor *BoltStorage) GetQURLByURL(u string) (*qurl.QURL, error) {
return nil, nil
}
qurl := &qurl.QURL{ ID: binary.BigEndian.Uint64(bid) }
q := &qurl.QURL{ID: binary.BigEndian.Uint64(bid)}
qu := qb.Get(URLField)
if qu != nil {
qurl.URL = string(qu)
q.URL = string(qu)
}
return qurl, nil
}
/*
func (stor *BoltStorage) GetQURLByURL(u string) (*qurl.QURL, error) {
tx, err := stor.DB.Begin(false)
if err != nil {
return nil, err
}
defer tx.Rollback()
rb := tx.Bucket(QURLBucket)
if rb == nil {
return nil, nil
}
bu := []byte(u)
rc := rb.Cursor()
for k, _ := rc.First(); k != nil; k, _ = rc.Next() {
qb := rb.Bucket(k)
if qb == nil {
continue
}
qu := qb.Get(URLField)
if bytes.Equal(bu, qu) {
qurl := &qurl.QURL{
ID: binary.BigEndian.Uint64(k),
URL: string(qu),
}
return qurl, nil
}
}
return nil, nil
return q, nil
}
*/
Loading…
Cancel
Save