Added URL submission
This commit is contained in:
		@ -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
 | 
			
		||||
}
 | 
			
		||||
*/
 | 
			
		||||
 | 
			
		||||
		Reference in New Issue
	
	Block a user