update shorturl to use badgerhold

This commit is contained in:
Xuu
2020-10-30 11:08:04 -06:00
parent db4ac49a49
commit 6c01df76f5
15 changed files with 16578 additions and 279 deletions

View File

@@ -94,7 +94,7 @@ func publicFavicon16x16Png() (*asset, error) {
return nil, err
}
info := bindataFileInfo{name: "public/favicon-16x16.png", size: 445, mode: os.FileMode(0644), modTime: time.Unix(1597590050, 0)}
info := bindataFileInfo{name: "public/favicon-16x16.png", size: 445, mode: os.FileMode(0644), modTime: time.Unix(1603755438, 0)}
a := &asset{bytes: bytes, info: info, digest: [32]uint8{0xc, 0x68, 0x7c, 0x67, 0x37, 0x47, 0x67, 0x80, 0x51, 0xf, 0x5f, 0x47, 0x4e, 0x83, 0x3b, 0xe4, 0x54, 0x78, 0xa6, 0xba, 0x9d, 0x9, 0x84, 0xd4, 0x2d, 0x9d, 0xf6, 0x13, 0x25, 0xd0, 0x83, 0x8d}}
return a, nil
}
@@ -114,7 +114,7 @@ func publicFavicon32x32Png() (*asset, error) {
return nil, err
}
info := bindataFileInfo{name: "public/favicon-32x32.png", size: 1141, mode: os.FileMode(0644), modTime: time.Unix(1597590050, 0)}
info := bindataFileInfo{name: "public/favicon-32x32.png", size: 1141, mode: os.FileMode(0644), modTime: time.Unix(1603755438, 0)}
a := &asset{bytes: bytes, info: info, digest: [32]uint8{0x16, 0x5, 0x8a, 0x65, 0x62, 0x83, 0x24, 0xec, 0xdb, 0x3d, 0xb9, 0x9e, 0x94, 0x20, 0x89, 0x8b, 0x53, 0x6e, 0x25, 0xf, 0x15, 0x89, 0x4a, 0x4a, 0x7e, 0xd0, 0x5b, 0xaf, 0x16, 0xa9, 0x57, 0xba}}
return a, nil
}
@@ -134,7 +134,7 @@ func publicIndexHtml() (*asset, error) {
return nil, err
}
info := bindataFileInfo{name: "public/index.html", size: 3503, mode: os.FileMode(0644), modTime: time.Unix(1597590050, 0)}
info := bindataFileInfo{name: "public/index.html", size: 3503, mode: os.FileMode(0644), modTime: time.Unix(1603755438, 0)}
a := &asset{bytes: bytes, info: info, digest: [32]uint8{0x2a, 0x7, 0x6d, 0x5, 0x69, 0x46, 0x56, 0x1e, 0xc4, 0x45, 0x1a, 0xb5, 0x1f, 0xb9, 0xc6, 0x1, 0xfc, 0x42, 0x26, 0x9a, 0xf4, 0xac, 0xae, 0xa9, 0xe9, 0x26, 0x91, 0x76, 0x3e, 0xfb, 0xee, 0xf0}}
return a, nil
}
@@ -154,7 +154,7 @@ func publicOauth2RedirectHtml() (*asset, error) {
return nil, err
}
info := bindataFileInfo{name: "public/oauth2-redirect.html", size: 2388, mode: os.FileMode(0644), modTime: time.Unix(1597590050, 0)}
info := bindataFileInfo{name: "public/oauth2-redirect.html", size: 2388, mode: os.FileMode(0644), modTime: time.Unix(1603755438, 0)}
a := &asset{bytes: bytes, info: info, digest: [32]uint8{0x9a, 0x53, 0xfa, 0x6e, 0xf1, 0xfe, 0x72, 0x19, 0xa6, 0x82, 0xc8, 0x77, 0x99, 0xb3, 0x9d, 0x52, 0x2e, 0x51, 0xa2, 0x78, 0xd2, 0xa, 0xd4, 0xa8, 0xe9, 0xa8, 0x83, 0x64, 0xbe, 0xe9, 0xc, 0xee}}
return a, nil
}
@@ -174,7 +174,7 @@ func publicSwaggerUiBundleJs() (*asset, error) {
return nil, err
}
info := bindataFileInfo{name: "public/swagger-ui-bundle.js", size: 1555141, mode: os.FileMode(0644), modTime: time.Unix(1597590050, 0)}
info := bindataFileInfo{name: "public/swagger-ui-bundle.js", size: 1555141, mode: os.FileMode(0644), modTime: time.Unix(1603755438, 0)}
a := &asset{bytes: bytes, info: info, digest: [32]uint8{0xc7, 0x3b, 0xa0, 0x63, 0x96, 0x6a, 0x32, 0x2e, 0x38, 0x7b, 0x1a, 0x83, 0x36, 0x51, 0xae, 0x7a, 0x6e, 0xa7, 0xe8, 0xa4, 0x7b, 0x8b, 0x1b, 0x75, 0xa1, 0x2a, 0xd3, 0xa1, 0x4b, 0x57, 0x13, 0x35}}
return a, nil
}
@@ -194,7 +194,7 @@ func publicSwaggerUiStandalonePresetJs() (*asset, error) {
return nil, err
}
info := bindataFileInfo{name: "public/swagger-ui-standalone-preset.js", size: 440915, mode: os.FileMode(0644), modTime: time.Unix(1597590050, 0)}
info := bindataFileInfo{name: "public/swagger-ui-standalone-preset.js", size: 440915, mode: os.FileMode(0644), modTime: time.Unix(1603755438, 0)}
a := &asset{bytes: bytes, info: info, digest: [32]uint8{0xa9, 0xd0, 0xb, 0x24, 0x4, 0x3b, 0xa, 0x54, 0x83, 0x5c, 0x3, 0x32, 0x6f, 0x41, 0x76, 0x93, 0xe, 0x2c, 0xb0, 0xd1, 0x89, 0xb7, 0xff, 0xc4, 0x71, 0xfc, 0x33, 0x2b, 0x90, 0xe4, 0xd3, 0x79}}
return a, nil
}
@@ -214,7 +214,7 @@ func publicSwaggerUiCss() (*asset, error) {
return nil, err
}
info := bindataFileInfo{name: "public/swagger-ui.css", size: 153540, mode: os.FileMode(0644), modTime: time.Unix(1597590050, 0)}
info := bindataFileInfo{name: "public/swagger-ui.css", size: 153540, mode: os.FileMode(0644), modTime: time.Unix(1603755438, 0)}
a := &asset{bytes: bytes, info: info, digest: [32]uint8{0x0, 0x44, 0x96, 0x15, 0x88, 0xba, 0xa, 0x54, 0xb7, 0x45, 0x18, 0x63, 0x69, 0x98, 0x6a, 0x19, 0xca, 0x51, 0x11, 0xc8, 0x22, 0x3d, 0x11, 0x31, 0x1b, 0x2c, 0x55, 0xa5, 0xa, 0xb6, 0xab, 0x2a}}
return a, nil
}
@@ -234,7 +234,7 @@ func publicSwaggerUiJs() (*asset, error) {
return nil, err
}
info := bindataFileInfo{name: "public/swagger-ui.js", size: 361688, mode: os.FileMode(0644), modTime: time.Unix(1597590050, 0)}
info := bindataFileInfo{name: "public/swagger-ui.js", size: 361688, mode: os.FileMode(0644), modTime: time.Unix(1603755438, 0)}
a := &asset{bytes: bytes, info: info, digest: [32]uint8{0xce, 0xf9, 0x34, 0xcb, 0xf8, 0xd7, 0xb6, 0xb0, 0xed, 0xf3, 0xb, 0xfc, 0x8d, 0xec, 0x66, 0xa2, 0xe8, 0x8f, 0xcd, 0xcd, 0x8, 0x5b, 0xda, 0xcf, 0x96, 0x7e, 0x21, 0xd2, 0xdf, 0x58, 0x66, 0x29}}
return a, nil
}
@@ -254,7 +254,7 @@ func publicSwaggerJson() (*asset, error) {
return nil, err
}
info := bindataFileInfo{name: "public/swagger.json", size: 4895, mode: os.FileMode(0644), modTime: time.Unix(1599262060, 0)}
info := bindataFileInfo{name: "public/swagger.json", size: 4895, mode: os.FileMode(0644), modTime: time.Unix(1603758903, 0)}
a := &asset{bytes: bytes, info: info, digest: [32]uint8{0x71, 0xcc, 0xe, 0x5f, 0xc1, 0xe9, 0xd6, 0xa9, 0x46, 0x93, 0xde, 0x3d, 0x71, 0x10, 0x7f, 0xa9, 0x1e, 0x98, 0x11, 0x70, 0xc6, 0xcf, 0x1c, 0xa, 0x57, 0x84, 0xfc, 0x3c, 0xf7, 0x28, 0x3, 0x67}}
return a, nil
}

View File

@@ -1,18 +1,19 @@
package routes
package readutil
import (
"compress/bzip2"
"compress/gzip"
"errors"
"io"
"github.com/andybalholm/brotli"
xz "github.com/remyoudompheng/go-liblzma"
"sour.is/x/paste/src/pkg/readutil"
"sour.is/x/toolbox/log"
)
// Decompress varius types of compression types
func Decompress(in io.Reader) (io.Reader, error) {
rdr := readutil.NewPreviewReader(in)
rdr := NewPreviewReader(in)
mime, err := ReadMIMEWithSize(rdr, "", 32768)
if err != nil {
return rdr.Drain(), err
@@ -31,8 +32,12 @@ func Decompress(in io.Reader) (io.Reader, error) {
case "application/brotli":
r = brotli.NewReader(r)
default:
return r, ErrUnsupportedType
}
log.Debugs("Decompress:", "mime", mime)
return r, err
}
var ErrUnsupportedType = errors.New("Unsupported decompression type")

View File

@@ -9,15 +9,18 @@ import (
"sour.is/x/toolbox/log"
)
// PreviewReader allows for seeking into a stream that does not support rewind
type PreviewReader struct {
r io.Reader
buf bytes.Buffer
}
// NewPreviewReader wrapps a reader with PreviewReader
func NewPreviewReader(r io.Reader) *PreviewReader {
return &PreviewReader{r: r}
}
// ErrReaderDrained if reading from a drained PreviewReader
var ErrReaderDrained = errors.New("io.Reader has been drained")
func (pr *PreviewReader) Read(p []byte) (n int, err error) {
@@ -36,6 +39,7 @@ func (pr *PreviewReader) Read(p []byte) (n int, err error) {
return i, err
}
// Drain returns reader that is reset
func (pr *PreviewReader) Drain() io.Reader {
dr := &drainReader{r: pr.r, buf: &pr.buf}
pr.r = nil

View File

@@ -1,15 +1,15 @@
package routes
package readutil
import (
"bytes"
"io"
"net/http"
"path/filepath"
"strings"
"github.com/andybalholm/brotli"
"github.com/h2non/filetype"
"github.com/h2non/filetype/types"
"sour.is/x/paste/src/pkg/readutil"
"sour.is/x/toolbox/log"
)
@@ -24,8 +24,11 @@ func init() {
}
func br(buf []byte) bool {
if len(buf) < 32768 {
return false
}
var r io.Reader = bytes.NewReader(buf)
r = readutil.NewPreviewReader(r)
r = NewPreviewReader(r)
br := brotli.NewReader(r)
i, err := br.Read(make([]byte, 1))
log.Debugs("BR:", "i", i, "err", err)
@@ -41,10 +44,12 @@ func br(buf []byte) bool {
return true
}
// ReadMIME of read stream with default size 32 byte
func ReadMIME(in io.Reader, filename string) (string, error) {
return ReadMIMEWithSize(in, filename, 32)
return ReadMIMEWithSize(in, filename, 512)
}
// ReadMIMEWithSize and return best guess for mime
func ReadMIMEWithSize(in io.Reader, filename string, n int) (string, error) {
mime := "application/octet-stream"
@@ -54,6 +59,11 @@ func ReadMIMEWithSize(in io.Reader, filename string, n int) (string, error) {
return mime, err
}
mime = http.DetectContentType(buf)
if mime != "application/octet-stream" {
return mime, nil
}
kind, err := filetype.Match(buf)
if kind == types.Unknown {

View File

@@ -4,6 +4,7 @@ import (
"archive/tar"
"crypto/sha256"
"encoding/base64"
"fmt"
"io"
"io/ioutil"
"net/http"
@@ -30,9 +31,11 @@ func init() {
{Name: "get-path", Method: "GET", Pattern: "/a/{name}/{path:.*}", HandlerFunc: a.get},
{Name: "get", Method: "GET", Pattern: "/a/{name}", HandlerFunc: a.get},
{Name: "put", Method: "PUT", Pattern: "/a", HandlerFunc: a.put},
{Name: "get", Method: "GET", Pattern: "/a", HandlerFunc: a.list},
})
}
// Artifact stores items to disk
type Artifact struct {
store string
maxSize int64
@@ -90,7 +93,7 @@ func (a *Artifact) get(w http.ResponseWriter, r *http.Request) {
if !hasPath {
pr := readutil.NewPreviewReader(f)
mime, err := ReadMIME(pr, name)
mime, err := readutil.ReadMIME(pr, name)
if err != nil {
httpsrv.WriteError(w, http.StatusInternalServerError, err.Error())
return
@@ -102,8 +105,8 @@ func (a *Artifact) get(w http.ResponseWriter, r *http.Request) {
return
}
rdr, err := Decompress(f)
if err != nil {
rdr, err := readutil.Decompress(f)
if err != nil && err != readutil.ErrUnsupportedType {
httpsrv.WriteError(w, http.StatusInternalServerError, err.Error())
return
}
@@ -161,14 +164,14 @@ func (a *Artifact) get(w http.ResponseWriter, r *http.Request) {
renderer := html.NewRenderer(opts)
b := markdown.ToHTML(md, p, renderer)
w.Write(b)
_, _ = w.Write(b)
w.WriteHeader(http.StatusOK)
return
}
pr := readutil.NewPreviewReader(tr)
mime, err := ReadMIME(pr, hdr.Name)
mime, err := readutil.ReadMIME(pr, hdr.Name)
if err != nil {
httpsrv.WriteError(w, http.StatusInternalServerError, err.Error())
return
@@ -220,7 +223,24 @@ func (a *Artifact) put(w http.ResponseWriter, r *http.Request) {
fname := filepath.Join(a.store, id)
log.Debugs("Artifact: moving file", "src", tmp.Name(), "dst", fname)
os.Rename(tmp.Name(), fname)
_ = os.Rename(tmp.Name(), fname)
httpsrv.WriteText(w, http.StatusCreated, id)
}
func (a *Artifact) list(w http.ResponseWriter, r *http.Request) {
err := filepath.Walk(a.store, func(path string, info os.FileInfo, err error) error {
if err != nil {
return err
}
if info.IsDir() {
return nil
}
_, err = fmt.Fprintln(w, "FILE: ", info.Name())
return err
})
if err != nil {
httpsrv.WriteError(w, http.StatusInternalServerError, err.Error())
return
}
}

File diff suppressed because one or more lines are too long

View File

@@ -388,7 +388,7 @@ func (s *identity) getStyle(ctx context.Context, email string) (*Style, error) {
hash := md5.New()
email = strings.TrimSpace(strings.ToLower(email))
hash.Write([]byte(email))
_, _ = hash.Write([]byte(email))
id := hash.Sum(nil)

View File

@@ -20,7 +20,7 @@ import (
)
func init() {
a := &Image{}
a := &image{}
httpsrv.RegisterModule("image", a.config)
httpsrv.HttpRegister("image", httpsrv.HttpRoutes{
@@ -30,12 +30,12 @@ func init() {
})
}
type Image struct {
type image struct {
store string
maxSize int64
}
func (a *Image) config(config map[string]string) {
func (a *image) config(config map[string]string) {
a.store = "data/"
if config["store"] != "" {
a.store = config["store"]
@@ -55,7 +55,7 @@ func (a *Image) config(config map[string]string) {
log.Noticef("Image: store max-size set to [%d]", a.maxSize)
}
func (a *Image) get(w http.ResponseWriter, r *http.Request) {
func (a *image) get(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
name := vars["name"]
@@ -85,14 +85,14 @@ func (a *Image) get(w http.ResponseWriter, r *http.Request) {
pr := readutil.NewPreviewReader(f)
mime, err := ReadMIME(pr, name)
mime, _ := readutil.ReadMIME(pr, name)
w.Header().Set("Content-Type", mime)
w.Header().Set("X-Content-Type-Options", "nosniff")
io.Copy(w, pr.Drain())
_, _ = io.Copy(w, pr.Drain())
}
func (a *Image) put(w http.ResponseWriter, r *http.Request) {
func (a *image) put(w http.ResponseWriter, r *http.Request) {
defer r.Body.Close()
if h := r.Header.Get("Content-Length"); h != "" {
@@ -151,6 +151,6 @@ func isImageOrVideo(in io.Reader) bool {
return filetype.IsImage(buf) || filetype.IsVideo(buf)
}
func (a *Image) getStyle(w http.ResponseWriter, r *http.Request) {
func (a *image) getStyle(w http.ResponseWriter, r *http.Request) {
}

View File

@@ -192,7 +192,7 @@ func (p *Paste) getPaste(w http.ResponseWriter, r *http.Request) {
}
w.WriteHeader(http.StatusOK)
io.Copy(w, pr.Drain())
_, _ = io.Copy(w, pr.Drain())
if !keep {
deleteFile(fname)
@@ -217,19 +217,19 @@ func (p *Paste) postPaste(w http.ResponseWriter, r *http.Request) {
id := base64.RawURLEncoding.EncodeToString(s256.Sum(nil)[12:])
fname := filepath.Join(p.store, id)
os.Rename(tmp.Name(), fname)
_ = os.Rename(tmp.Name(), fname)
httpsrv.WriteText(w, http.StatusCreated, "OK "+id)
}
func checkErr(err error, w http.ResponseWriter) bool {
if err != nil {
httpsrv.WriteObject(w, http.StatusBadRequest, err)
return true
}
// func checkErr(err error, w http.ResponseWriter) bool {
// if err != nil {
// httpsrv.WriteObject(w, http.StatusBadRequest, err)
// return true
// }
return false
}
// return false
// }
func deleteFile(path string) {
_ = ioutil.WriteFile(path, nil, 0644)

View File

@@ -1,99 +1,131 @@
package routes
import (
"bytes"
"encoding/json"
"net/http"
"net/url"
"regexp"
"github.com/coreos/bbolt"
"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: "getShort", Method: "GET", Pattern: "/s/{id}", HandlerFunc: s.getShort},
{Name: "putShort", Method: "PUT", Pattern: "/s/{id}", HandlerFunc: s.putShort},
{Name: "get", Method: "GET", Pattern: "/s/{id}", HandlerFunc: s.get},
{Name: "put", Method: "PUT", Pattern: "/s/{id}", HandlerFunc: s.put},
})
}
type shortDB struct {
path string
bucket string
options badgerhold.Options
}
func (s *shortDB) config(config map[string]string) {
s.bucket = "shortURL"
if config["bucket"] != "" {
s.bucket = config["bucket"]
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"
}
s.path = "data/meta.db"
if config["store"] != "" {
s.path = config["store"]
if s.options.ValueDir, ok = config["value"]; !ok {
s.options.ValueDir = "data"
}
db, err := bbolt.Open(s.path, 0666, nil)
if err != nil {
log.Fatalf("ShortURL: failed to open db at [%s]", s.path)
}
defer db.Close()
db.Update(func(tx *bbolt.Tx) error {
_, err := tx.CreateBucketIfNotExists([]byte(s.bucket))
if err != nil {
log.Fatalf("ShortURL: create bucket: %s", err)
store, err := badgerhold.Open(s.options)
defer func() {
if err = store.Close(); err != nil {
log.Fatal(err)
}
return nil
})
log.Noticef("ShortURL: opened db at [%s] bucket [%s]", s.path, s.bucket)
}()
if err != nil {
log.Fatal(err)
}
}
func (s *shortDB) getShort(w http.ResponseWriter, r *http.Request) {
func (s *shortDB) get(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
id := vars["id"]
url := s.GetURL(id)
if url == nil {
httpsrv.WriteError(w, 404, "not found")
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) putShort(w http.ResponseWriter, r *http.Request) {
defer r.Body.Close()
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.WriteError(w, 400, "bad url")
httpsrv.WriteText(w, 400, "bad url")
return
}
short := s.GetURL(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
}
if short == nil {
short = newshort(id, secret, u.String())
url := &shortURL{}
err = store.Get(id, url)
if err != nil && err != badgerhold.ErrNotFound {
httpsrv.WriteText(w, 500, err.Error())
return
}
s.PutURL(short.ID, short)
httpsrv.WriteObject(w, 200, short)
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
}
@@ -102,24 +134,27 @@ func (s *shortDB) putShort(w http.ResponseWriter, r *http.Request) {
return
}
if secret != short.Secret {
if secret != url.Secret {
httpsrv.WriteError(w, 403, "forbidden")
return
}
short.URL = u.String()
err = store.Upsert(url.ID, url)
if err != nil {
httpsrv.WriteText(w, 500, err.Error())
return
}
s.PutURL(short.ID, short)
httpsrv.WriteObject(w, 200, short)
httpsrv.WriteObject(w, 200, url)
}
type shortURL struct {
ID string
ID string `badgerhold:"unique"`
URL string
Secret string
}
func newshort(id, secret, u string) *shortURL {
func newShortURL(id, secret, u string) *shortURL {
m, err := regexp.MatchString("[a-z-]{1,64}", id)
if id == "" || !m || err != nil {
id = uuid.V4()
@@ -130,64 +165,3 @@ func newshort(id, secret, u string) *shortURL {
}
return &shortURL{ID: id, Secret: secret, URL: u}
}
func (s *shortURL) Bytes() []byte {
if s == nil {
return nil
}
var w bytes.Buffer
json.NewEncoder(&w).Encode(*s)
return w.Bytes()
}
func URLFromBytes(b []byte) *shortURL {
if len(b) == 0 {
return nil
}
var s shortURL
json.Unmarshal(b, &s)
log.Debug(s)
return &s
}
func (s *shortDB) GetURL(id string) *shortURL {
db, err := bbolt.Open(s.path, 0666, nil)
if err != nil {
log.Errorf("ShortURL: failed to open db at [%s]", s.path)
return nil
}
defer db.Close()
var url *shortURL
err = db.View(func(tx *bbolt.Tx) error {
b := tx.Bucket([]byte(s.bucket))
v := b.Get([]byte(id))
url = URLFromBytes(v)
return nil
})
if err != nil {
log.Errorf("ShortURL: failed to open db at [%s]", s.path)
}
return url
}
func (s *shortDB) PutURL(id string, url *shortURL) {
db, err := bbolt.Open(s.path, 0666, nil)
if err != nil {
log.Errorf("ShortURL: failed to open db at [%s]", s.path)
return
}
defer db.Close()
err = db.Update(func(tx *bbolt.Tx) error {
b := tx.Bucket([]byte(s.bucket))
return b.Put([]byte(id), url.Bytes())
})
if err != nil {
log.Errorf("ShortURL: failed to write db at [%s]", s.path)
}
}