168 lines
3.4 KiB
Go
168 lines
3.4 KiB
Go
package routes
|
|
|
|
import (
|
|
"net/http"
|
|
"net/url"
|
|
"regexp"
|
|
|
|
"github.com/gorilla/mux"
|
|
"github.com/timshannon/badgerhold"
|
|
|
|
"sour.is/x/toolbox/httpsrv"
|
|
"sour.is/x/toolbox/log"
|
|
"sour.is/x/toolbox/uuid"
|
|
)
|
|
|
|
type logger struct{}
|
|
|
|
func (logger) Errorf(s string, o ...interface{}) { log.Errorf(s, o...) }
|
|
func (logger) Warningf(s string, o ...interface{}) { log.Warningf(s, o...) }
|
|
func (logger) Infof(s string, o ...interface{}) { log.Infof(s, o...) }
|
|
func (logger) Debugf(s string, o ...interface{}) { log.Debugf(s, o...) }
|
|
|
|
func init() {
|
|
s := &shortDB{}
|
|
httpsrv.RegisterModule("short", s.config)
|
|
|
|
httpsrv.HttpRegister("short", httpsrv.HttpRoutes{
|
|
{Name: "get", Method: "GET", Pattern: "/s/{id}", HandlerFunc: s.get},
|
|
{Name: "put", Method: "PUT", Pattern: "/s/{id}", HandlerFunc: s.put},
|
|
})
|
|
}
|
|
|
|
type shortDB struct {
|
|
options badgerhold.Options
|
|
}
|
|
|
|
func (s *shortDB) config(config map[string]string) {
|
|
s.options = badgerhold.DefaultOptions
|
|
s.options.Options = s.options.WithLogger(logger{})
|
|
|
|
var ok bool
|
|
if s.options.Dir, ok = config["index"]; !ok {
|
|
s.options.Dir = "data"
|
|
}
|
|
|
|
if s.options.ValueDir, ok = config["value"]; !ok {
|
|
s.options.ValueDir = "data"
|
|
}
|
|
|
|
store, err := badgerhold.Open(s.options)
|
|
defer func() {
|
|
if err = store.Close(); err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
}()
|
|
if err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
}
|
|
func (s *shortDB) get(w http.ResponseWriter, r *http.Request) {
|
|
vars := mux.Vars(r)
|
|
|
|
id := vars["id"]
|
|
|
|
store, err := badgerhold.Open(s.options)
|
|
defer func() {
|
|
if err = store.Close(); err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
}()
|
|
if err != nil {
|
|
httpsrv.WriteText(w, 500, err.Error())
|
|
return
|
|
}
|
|
|
|
var url shortURL
|
|
err = store.Get(id, &url)
|
|
if err != nil && err != badgerhold.ErrNotFound {
|
|
httpsrv.WriteText(w, 500, err.Error())
|
|
return
|
|
}
|
|
if err == badgerhold.ErrNotFound {
|
|
httpsrv.WriteText(w, 404, "not found")
|
|
return
|
|
}
|
|
|
|
w.Header().Set("Location", url.URL)
|
|
w.WriteHeader(http.StatusFound)
|
|
}
|
|
func (s *shortDB) put(w http.ResponseWriter, r *http.Request) {
|
|
vars := mux.Vars(r)
|
|
|
|
id := vars["id"]
|
|
secret := r.FormValue("secret")
|
|
u, err := url.Parse(r.FormValue("url"))
|
|
if err != nil {
|
|
httpsrv.WriteText(w, 400, "bad url")
|
|
return
|
|
}
|
|
|
|
store, err := badgerhold.Open(s.options)
|
|
defer func() {
|
|
if err = store.Close(); err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
}()
|
|
if err != nil {
|
|
httpsrv.WriteText(w, 500, err.Error())
|
|
return
|
|
}
|
|
|
|
url := &shortURL{}
|
|
err = store.Get(id, url)
|
|
if err != nil && err != badgerhold.ErrNotFound {
|
|
httpsrv.WriteText(w, 500, err.Error())
|
|
return
|
|
}
|
|
|
|
if err == badgerhold.ErrNotFound {
|
|
url = newShortURL(id, secret, u.String())
|
|
|
|
err := store.Insert(url.ID, url)
|
|
if err != nil {
|
|
httpsrv.WriteText(w, 500, err.Error())
|
|
return
|
|
}
|
|
|
|
httpsrv.WriteObject(w, 200, url)
|
|
return
|
|
}
|
|
|
|
if secret == "" {
|
|
httpsrv.WriteError(w, 401, "no auth")
|
|
return
|
|
}
|
|
|
|
if secret != url.Secret {
|
|
httpsrv.WriteError(w, 403, "forbidden")
|
|
return
|
|
}
|
|
|
|
err = store.Upsert(url.ID, url)
|
|
if err != nil {
|
|
httpsrv.WriteText(w, 500, err.Error())
|
|
return
|
|
}
|
|
|
|
httpsrv.WriteObject(w, 200, url)
|
|
}
|
|
|
|
type shortURL struct {
|
|
ID string `badgerhold:"unique"`
|
|
URL string
|
|
Secret string
|
|
}
|
|
|
|
func newShortURL(id, secret, u string) *shortURL {
|
|
m, err := regexp.MatchString("[a-z-]{1,64}", id)
|
|
if id == "" || !m || err != nil {
|
|
id = uuid.V4()
|
|
}
|
|
m, err = regexp.MatchString("[a-z-]{1,64}", secret)
|
|
if secret == "" || !m || err != nil {
|
|
secret = uuid.V4()
|
|
}
|
|
return &shortURL{ID: id, Secret: secret, URL: u}
|
|
}
|