Added URL submission
This commit is contained in:
parent
412c625916
commit
5c669c8b93
@ -9,7 +9,7 @@
|
|||||||
<body>
|
<body>
|
||||||
<div id="c">
|
<div id="c">
|
||||||
qurl.org is a simple url shortening service, in the same vein as
|
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>.
|
<a href="https://tinyurl.com">tinyurl.com</a>.
|
||||||
qurl.org is <a href="http://binarythought.com/qurl/LICENSE">open source</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>
|
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; }
|
#u:focus { border-color: #129fea; }
|
||||||
#s { margin-top: 8px; color: #fff; background-color: #0078e7; border-color: transparent; }
|
#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; }
|
#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) {
|
@media screen and (min-width: 640px) {
|
||||||
#c { max-width: 600px; }
|
#c { max-width: 600px; }
|
||||||
#u, #s { display: inline-block; margin: 0; }
|
#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)
|
err := stor.AddQURL(qurl)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("Error adding qurl: %s", err.Error())
|
fmt.Printf("\nError adding qurl: %s\n", err.Error())
|
||||||
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
count++
|
count++
|
||||||
|
13
main.go
13
main.go
@ -6,14 +6,14 @@ import (
|
|||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
"qurl/storage"
|
"qurl/pages"
|
||||||
"qurl/static"
|
"qurl/static"
|
||||||
|
"qurl/storage"
|
||||||
"runtime"
|
"runtime"
|
||||||
)
|
)
|
||||||
|
|
||||||
//go:generate bindata -m Assets -r assets -p static -o static/assets.go assets
|
//go:generate bindata -m Assets -r assets -p static -o static/assets.go assets
|
||||||
|
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
dburl := flag.String("u", "bolt:./qurl.db", "url to database")
|
dburl := flag.String("u", "bolt:./qurl.db", "url to database")
|
||||||
lsaddr := flag.String("l", "127.0.0.1:8080", "listen address/port")
|
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("/favicon.ico", &static.StaticContent{Content: "favicon.ico"})
|
||||||
mux.Handle("/qurl.css", &static.StaticContent{Content: "qurl.css"})
|
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")
|
fmt.Fprintf(os.Stdout, "qurl listening .. \n")
|
||||||
err = http.Serve(listen, mux)
|
err = http.Serve(listen, mux)
|
||||||
if err != nil {
|
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"
|
"fmt"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
"net/url"
|
||||||
)
|
)
|
||||||
|
|
||||||
type QURL struct {
|
type QURL struct {
|
||||||
@ -14,6 +15,27 @@ type QURL struct {
|
|||||||
Browser string
|
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 alpha = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"
|
||||||
const alphalen = uint64(len(alpha))
|
const alphalen = uint64(len(alpha))
|
||||||
|
|
||||||
|
@ -6,8 +6,8 @@ import (
|
|||||||
"crypto/md5"
|
"crypto/md5"
|
||||||
"fmt"
|
"fmt"
|
||||||
"mime"
|
"mime"
|
||||||
"path"
|
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"path"
|
||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -3,8 +3,6 @@ package bolt
|
|||||||
import (
|
import (
|
||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
"qurl/qurl"
|
"qurl/qurl"
|
||||||
// "bytes"
|
|
||||||
// "fmt"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@ -17,7 +15,12 @@ var (
|
|||||||
BrowserField = []byte{0x03}
|
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)
|
tx, err := stor.DB.Begin(true)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
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
|
// Populate the ID from the sequence if we don't have one
|
||||||
if qurl.ID == 0 {
|
if q.ID == 0 {
|
||||||
qurl.ID, err = rb.NextSequence()
|
s, err := rb.NextSequence()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
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
|
// Create a byte array from the ID
|
||||||
bid := make([]byte, 8)
|
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
|
// Add an entry into the reverse indexed bucket for quickly
|
||||||
// determining if a URL is already in the database
|
// determining if a URL is already in the database
|
||||||
@ -47,7 +55,7 @@ func (stor *BoltStorage) AddQURL(qurl *qurl.QURL) error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
err = ab.Put([]byte(qurl.URL), bid)
|
err = ab.Put([]byte(q.URL), bid)
|
||||||
|
|
||||||
qb, err := rb.CreateBucketIfNotExists(bid)
|
qb, err := rb.CreateBucketIfNotExists(bid)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -55,14 +63,14 @@ func (stor *BoltStorage) AddQURL(qurl *qurl.QURL) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Write the ID to URL
|
// Write the ID to URL
|
||||||
err = qb.Put(URLField, []byte(qurl.URL))
|
err = qb.Put(URLField, []byte(q.URL))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if !qurl.Created.IsZero() {
|
if !q.Created.IsZero() {
|
||||||
// Create byte array from the Created date
|
// Create byte array from the Created date
|
||||||
bdt, err := qurl.Created.MarshalBinary()
|
bdt, err := q.Created.MarshalBinary()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -74,15 +82,15 @@ func (stor *BoltStorage) AddQURL(qurl *qurl.QURL) error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if qurl.IP != nil {
|
if q.IP != nil {
|
||||||
err = qb.Put(IPField, qurl.IP)
|
err = qb.Put(IPField, q.IP)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(qurl.Browser) > 0 {
|
if len(q.Browser) > 0 {
|
||||||
err = qb.Put(BrowserField, []byte(qurl.Browser))
|
err = qb.Put(BrowserField, []byte(q.Browser))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -107,7 +115,10 @@ func (stor *BoltStorage) SetQURLSequence(seq uint64) error {
|
|||||||
return err
|
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 {
|
if err := tx.Commit(); err != nil {
|
||||||
return err
|
return err
|
||||||
@ -143,46 +154,12 @@ func (stor *BoltStorage) GetQURLByURL(u string) (*qurl.QURL, error) {
|
|||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
qurl := &qurl.QURL{ ID: binary.BigEndian.Uint64(bid) }
|
q := &qurl.QURL{ID: binary.BigEndian.Uint64(bid)}
|
||||||
|
|
||||||
qu := qb.Get(URLField)
|
qu := qb.Get(URLField)
|
||||||
if qu != nil {
|
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