Added URL submission
This commit is contained in:
parent
412c625916
commit
5c669c8b93
@ -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>
|
||||
|
@ -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
Normal file
24
assets/submit.html
Normal file
@ -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
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
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
Normal file
79
pages/submit.go
Normal file
@ -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
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))
|
||||
|
||||
|
@ -6,8 +6,8 @@ import (
|
||||
"crypto/md5"
|
||||
"fmt"
|
||||
"mime"
|
||||
"path"
|
||||
"net/http"
|
||||
"path"
|
||||
"strings"
|
||||
)
|
||||
|
||||
|
@ -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
|
||||
return q, 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
|
||||
}
|
||||
*/
|
||||
|
Loading…
Reference in New Issue
Block a user